2016-03-22 4 views
0

У меня есть таблица, которая содержит места, например, для тюрьмы.Выбор таблицы иерархии

CREATE TABLE [dbo].[Location](
    [ID] [int] IDENTITY(1,1) NOT NULL, 
    [ParentID] [int] NULL, 
    [LocationTypeID] [int] NOT NULL, 
    [GeoLocation] [geometry] NULL, 
    [Name] [varchar](50) NOT NULL, 
    [Description] [varchar](100) NOT NULL 
CONSTRAINT [pk_location] PRIMARY KEY (ID) 
) 

Данные файл вставка здесь: data.sql file Когда ParentID имеет нулевое значение, это корневой узел системы.

В данных может быть тюрьма (TypeID = 5), здание (TypeID = 6), пол, ячейка и кровать. Идентификатор родителя - это местоположение, в котором находится текущее местоположение.

Я создал представление, обеспечивающее легкий доступ к этой таблице.

CREATE VIEW [dbo].[vwLocations] 
AS 

WITH MyLocation 
AS (
     SELECT ParentID, 
       ID, 
       Name, 
       0 AS LevelNo, 
       CAST(CAST(LocationTypeID AS VARCHAR) + '|' + Name AS VARCHAR(512)) AS SORT_PATH 
     FROM dbo.Location AS A 
     WHERE  (ParentID IS NULL) 
     UNION ALL 
     SELECT C.ParentID, 
       C.ID, 
       C.Name, 
       MyLocation_1.LevelNo + 1 AS Expr1, 
       CAST(MyLocation_1.SORT_PATH + '//' + CAST(C.LocationTypeID AS VARCHAR) + '|' + C.Name AS VARCHAR(512)) AS SORT_PATH 
     FROM dbo.Location AS C 
     INNER JOIN MyLocation AS MyLocation_1 
      ON MyLocation_1.ID = C.ParentID 
    ) 
    SELECT ISNULL(ID, -999) AS LocationID, 
      ParentID, 
      LevelNo AS LevelNumber, 
      SORT_PATH AS FullPath, 
      Name AS Description 
    FROM MyLocation AS ML 
GO 

И это хорошо работает и эффективно для всех случаев. Разработчик возвращает строку, если хочет разбить ее на каждый компонент. Например, я возвращаю строку:

5 | Тюрьма // 6 | P 1 // 7 | P 1 // 8 | 1 // 9 | 01 // 10 | 01

Что они тогда раскол на '//', чтобы получить

5 | Тюремные 6 | P 1 7 | P 1 8 | 1 9 | 01 10 | 01

Это означает, ID 5 означает его тюрьма, ID 6 означает, что это здание, ID 7 - это раздел, ID 8 - пол, 9 - ячейка, а 10 - кровать.

Они делают это в коде.

Проблема в том, что они хотят иметь возможность сортировать по полу, например. Который, я не могу. Что-то мне нужно сделать, это добавить дополнительные столбцы для каждой строки, называемые «Тюрьма», «Строительство» ... и т. Д., И заполнить их.

Таким образом, некоторые из них могут быть пустыми, потому что, если это место, например, этаж, то столбцы Тюрьма, Строительство, раздел и пол будут иметь данные, а остальные будут пустыми.

Возможно ли это?

+0

Вы пробовали подстроить поле в отдельные поля, сохранив это как представление, а затем отсортировав новый столбец в представлении? – JLB

+0

@JLB - Я собирался попробовать это, но может ли это быть сделано в представлении? Будет ли это заявление «CASE»? Но даже если бы это было так, я не знаю, как я могу использовать оператор CASE, чтобы заполнить разные столбцы. Или CASE будет в окончательном выборе, и может быть 5 операторов CASES - по одному для каждого столбца? Или вы говорите два вида, это один из них - первичное извлечение данных, а затем некоторая сортировка во втором представлении - как-то? – Craig

+0

Если я правильно читаю запрос, вы запрашиваете поиск по ширине (BFS) вашей иерархии. Если да, укажите некоторые примеры данных (желательно в формате, в котором я могу скопировать/вставить его в SSMS и извлечь из него таблицу), и я могу предоставить код, который даст вам упорядоченный набор результатов BFS. –

ответ

0

Самая большая проблема с любым решением, подобным этому (и вы не первый, чтобы попробовать), без особых функций в движке базы данных, вы не можете представлять собой произвольно глубокое дерево ... что-либо на уровне 1 (должен быть) соединен с родителем с узлом 0, что-либо на уровне 2 должно быть соединено с родителем на уровне 1 и т. д. и т. д. Таким образом, это больше, чем попытка сортировки по полу (что вполне честно говоря, должен просто быть ORDER BY в конце выбора), но более принципиально вы не можете пересечь дерево, если у вас нет соединения на все возможные уровни.

Упростить это как «деревья не могут быть сохранены в реляционной базе данных»

1

Я большой поклонник типа данных hierarchyid` в. Я собираюсь в погоню:

alter table dbo.Location add 
    [Path] hierarchyid null, 
    [Level] as [Path].GetLevel() persisted; 

WITH MyLocation AS (
    SELECT ParentID, 
      ID, 
      Name, 

      cast(concat('/', ID, '/') as varchar(1000)) as [Path] 
    FROM dbo.Location AS A 
    WHERE  (ParentID IS NULL) 
    UNION ALL 
    SELECT C.ParentID, 
      C.ID, 
      C.Name, 
      cast(concat(p.[Path], c.ID, '/') as varchar(1000)) as [Path] 
    FROM dbo.Location AS C 
    INNER JOIN MyLocation AS p 
     ON p.ID = C.ParentID 
) 
update l 
set [Path] = ml.[Path] 
from dbo.Location as l 
join MyLocation as ml 
    on l.ID = ml.ID 
GO 

Что я сделал здесь добавить колонку hierarchyid` в таблицу. Это позволит мне сохранить путь до конечного предка (в вашем случае, тюрьму) и эффективно запросить предков по пути.

Далее, таблица-функция.

create function dbo.tvf_PivotHierarchy(@id int) 
returns table 
as return 

    select ID, 
     [1], 
     [2], 
     [3], 
     [4], 
     [5], 
     [6] 
    from (
     select p.ID, l.[Description], l.[Level] 
     from dbo.Location as l 
     join dbo.Location as p 
      on p.[Path].IsDescendantOf(l.[Path]) = 1 
     where p.ID = @id 
    ) as p 
    pivot (
     max(Description) 
     for [Level] in (
      [1], 
      [2], 
      [3], 
      [4], 
      [5], 
      [6] 
     ) 
    ) as pvt; 
go 

Пример использования:

with data as (
    select * from (values 
     (67), 
     (115) 
    ) as x(ID) 
) 
select * 
from data as d 
join dbo.Location as l 
    on d.ID = l.ID 
cross apply dbo.tvf_PivotHierarchy(l.ID); 

Все, что здесь происходит то, что для данного ID, я найти все записи, которые являются предками этой записи и запустить его с помощью простого поворота.

Теперь, если вы хотите отсортировать конкретный способ, у вас есть варианты. Если вы хотите сортировать по глубине (т. Е. Тюрьму, здание, все этажи в этом здании, следующее здание и т. Д.), Бросьте order by [Path] по этому последнему запросу; если вы хотите сделать широту (т. е. тюрьму, и т. д.), сделайте order by [Level], [Path].

Я неаккуратно и делаю такие вещи, как select *, и не называю столбцы в сводке чем-то более значимым, потому что результирующие наборы более наглядно иллюстрируют происходящее и отчасти потому, что я ленив. Вы должны очистить это для производственного использования, если вы решите его использовать.

+0

Wow @Ben! Это выглядит очень интересно. Я собираюсь уйти. Так много новых концепций. Благодарю. Я отчитаю. – Craig

+0

Это выглядит очень хорошо! Я не совсем понимаю, есть ли способ вставить строку и не выполнять обновление. Наши скрипты, которые заполняют местоположение, в настоящее время выполняются путем миграции из исходной системы. Есть ли способ заполнить путь и уровень при вставке строк? – Craig

+0

Да. У вас есть два сценария: один, где вы знаете идентификатор перед вставкой, и тот, где вы этого не делаете. В первом вам нужно сделать вставку, чтобы получить значение идентификатора, а затем выполнить постфактум после его появления. В последнем случае вы можете сделать все сразу. В любом случае вы можете получить «Путь», усмехаясь по пути родителя и добавив идентификатор ребенка и взяв косую черту. Уровень - это вычисленный столбец, поэтому вам не нужно беспокоиться об этом. –

Смежные вопросы