Более продвинутая логика для упорядочивания результатов запроса в T-SQL? - PullRequest
0 голосов
/ 28 октября 2009

В настоящее время я пишу запрос SQL, который должен отображать древовидную область областей внутри здания с областями, подобластями и т. Д. К сожалению, я не смог имитировать порядок, используемый некоторыми из наших программных инструментов. Я ограничен MS SQL 2000, поэтому вопрос о порядке становится намного более сложным, и сейчас я просто не в себе.

Логика упорядочения заключается в том, что столбцы Child и Parent связаны между собой. Если значение столбца Child первой строки соответствует столбцу Parent второй строки, то вторая строка идет после первой.

--How it currently returns data
Child Level      Parent
562   Campus      0
86  Area        1
87  Area        1
88  Area        1
90  Sub-Area    86
91  Sub-Area    86
92  Sub-Area    87
93  Sub-Area    87
94  Sub-Area    88
95  Sub-Area    88
3    Unit        90
16    Unit      90
4    Unit        91
6    Unit        91
etc, so on and therefore

--How I want it to return the data
Child Level         Parent
562 Campus          0
1   Building        562
86  Area            1
90  Sub-Area        86
91  Sub-Area        86
87  Area            1
95  Sub-Area        87   
95  Sub-Area        87 

Чтобы эта логика работала правильно, нужно сделать что-то вроде

  1. Вернуть строки здания с их родительским и дочерним кодами
  2. Сопоставить родительские коды области с дочерними кодами здания, а затем вставить строки области под соответствующей строкой здания.
  3. Сопоставьте родительские коды подобласти с дочерними кодами зоны, затем вставьте строки подобласти под соответствующей областью.
  4. Сопоставьте родительские коды юнитов с дочерними кодами подобласти, затем вставьте строки юнитов в соответствующую подобласть

Если это действительно возможно с SQL?

Я хотел бы знать, если я не решаюсь потратить больше времени на это, если я не знаю, действительно ли это возможно. Я понимаю, что мог бы написать оператор CASE с настраиваемым отображением для оператора ORDER BY, но это не сработает для любого другого кампуса (родительский / дочерний коды разные), и я хотел бы иметь возможность повторно использовать этот код в будущем с минимальными настройками.

Спасибо!

РЕДАКТИРОВАТЬ: Добавление запроса в соответствии с просьбой

DECLARE
@BuildingType   int,
@CampusType int

SET @BuildingType= 4
SET @CampusType= 1

select 

b.fkabc_building_child,
(select isnull(c.collectionname, 'none') 
from abc_collections c
where c.pkabc_collections = b.fkabc_building_child) as 'Child Collection', 
l.floorname,
isnull(b.fkabc_collections_parent,0) as fkabc_collections_parent,
b.fkabc_floorbreakdowns

from abc_breakdowns r
left join abc_floorbreakdowns fr 
on fr.pkabc_floorbreakdowns = b.fkabc_floorbreakdowns
inner join abc_buildingtypescampustypes btct
on btct.pkabc_buildingtypescampustypes = fr.fkabc_buildingtypescampustypes
inner join abc_buildingtypes bt
on btct.fkabc_buildingtypes = bt.pkabc_buildingtypes
inner join abc_collectiontypes ct
on btct.fkabc_collectiontypes = ct.pkabc_collectiontypes
inner join abc_collections c
on b.fkabc_building_child = c.pkabc_collections
inner join abc_floors l
on l.pkabc_floors = c.fkabc_floors

where bt.pkabc_buildingtypes = @BuildingType
and ct.pkabc_collectiontypes = @CampusType

Ответы [ 3 ]

2 голосов
/ 28 октября 2009

Примерно так:

-- prepare some test data
declare @table table (Child int, [Level] varchar(30), Parent int)

insert @table values (562 , 'Campus  ',  0  )
insert @table values (1   , 'Building',  562)
insert @table values (86  , 'Area    ',  1  )
insert @table values (87  , 'Area    ',  1  )
insert @table values (88  , 'Area    ',  1  )
insert @table values (90  , 'Sub-Area',  86 )
insert @table values (91  , 'Sub-Area',  86 )
insert @table values (92  , 'Sub-Area',  87 )
insert @table values (93  , 'Sub-Area',  87 )
insert @table values (94  , 'Sub-Area',  88 )
insert @table values (95  , 'Sub-Area',  88 )
insert @table values (3   , 'Unit    ',  90 )
insert @table values (16  , 'Unit    ',  90 )
insert @table values (4   , 'Unit    ',  91 )
insert @table values (6   , 'Unit    ',  91 )

select
  a.Child, a.[Level], a.Parent
, Campus = 
    case a.[Level]
      when 'Unit'     then e.Child
      when 'Sub-Area' then d.Child
      when 'Area'     then c.Child
      when 'Building' then b.Child
      when 'Campus'   then a.Child
    end
, Building = 
    case a.[Level]
      when 'Unit'     then d.Child
      when 'Sub-Area' then c.Child
      when 'Area'     then b.Child
      when 'Building' then a.Child
    end
, Area = 
    case a.[Level]
      when 'Unit'     then c.Child
      when 'Sub-Area' then b.Child
      when 'Area'     then a.Child
    end
, Sub_Area = 
    case a.[Level]
      when 'Unit'     then b.Child
      when 'Sub-Area' then a.Child
    end
, Unit = 
    case a.[Level]
      when 'Unit'     then a.Child
    end

from @table a

left join @table b on a.Parent = b.Child 
  and ((a.[Level] = 'Unit'     and b.[Level] = 'Sub-Area')
    or (a.[Level] = 'Sub-Area' and b.[Level] = 'Area'    )
    or (a.[Level] = 'Area'     and b.[Level] = 'Building')
    or (a.[Level] = 'Building' and b.[Level] = 'Campus'  ))

left join @table c on b.Parent = c.Child 
  and ((b.[Level] = 'Sub-Area' and c.[Level] = 'Area'    )
    or (b.[Level] = 'Area'     and c.[Level] = 'Building')
    or (b.[Level] = 'Building' and c.[Level] = 'Campus'  ))

left join @table d on c.Parent = d.Child 
  and ((c.[Level] = 'Area'     and d.[Level] = 'Building')
    or (c.[Level] = 'Building' and d.[Level] = 'Campus'  ))

left join @table e on d.Parent = e.Child 
  and ((d.[Level] = 'Building' and e.[Level] = 'Campus'  ))

order by 
  4, 5, 6, 7, 8

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

Теперь этот код просто для демонстрации, чтобы проиллюстрировать, как работает запрос. Вам не нужно иметь 5 полей сортировки в SELECT, вы можете переместить их в ORDER BY. И вы не должны использовать порядковые позиции в ORDER BY.

Но вам нужны 4 соединения и логика условного соединения, чтобы вытащить родительские уровни для каждого потомка. И вам нужны операторы CASE, чтобы вытащить ключ сортировки для каждого уровня.

Возможно, вы могли бы обернуть оператор SELECT в производную таблицу и переместить ORDER BY во внешний запрос. например:

SELECT Child, [Level], Parent
FROM (
  SELECT ....
  ) a
ORDER BY Campus, Building, Area, Sub_Area, Unit
1 голос
/ 28 октября 2009

Вот один подход; очень процедурный. К сожалению, в SQL Server 2000 я не думаю, что вы сможете уйти от курсоров, если не будете использовать решение, такое как решение Питера, которое ограничено 5 уровнями и жестко кодирует типы уровней в самом запросе (смешивая данные и метаданные ). Вам придется взвесить эти ограничения с любой заметной разницей в производительности.

Обратите внимание, что я не добавил никакой обработки для циклических ссылок, так что, надеюсь, вы не допустите, чтобы это произошло другими способами.

SET NOCOUNT ON;
GO

DECLARE @foo TABLE
(
 AreaID INT PRIMARY KEY,
 [Level] SYSNAME, 
 ParentAreaID INT
);

INSERT @foo 
SELECT           562, 'Campus',   0
UNION ALL SELECT 86,  'Area',     1
UNION ALL SELECT 87,  'Area',     1
UNION ALL SELECT 88,  'Area',     1
UNION ALL SELECT 90,  'Sub-Area', 86
UNION ALL SELECT 91,  'Sub-Area', 86
UNION ALL SELECT 92,  'Sub-Area', 87
UNION ALL SELECT 93,  'Sub-Area', 87
UNION ALL SELECT 94,  'Sub-Area', 88
UNION ALL SELECT 95,  'Sub-Area', 88
UNION ALL SELECT 3,   'Unit',     90
UNION ALL SELECT 16,  'Unit',     90
UNION ALL SELECT 4,   'Unit',     91
UNION ALL SELECT 6,   'Unit',     91
UNION ALL SELECT 1,   'Building', 562;

DECLARE @nest TABLE
(
 NestID INT IDENTITY(1,1) PRIMARY KEY,
 AreaID INT,
 [Level] INT,
 ParentNestID INT,
 AreaIDPath VARCHAR(4000)
);

DECLARE @rc INT, @l INT;

SET @l = 0;

INSERT @nest(AreaID, [Level], AreaIDPath) 
 SELECT AreaID, 0, CONVERT(VARCHAR(12), AreaID)
 FROM @foo
 WHERE ParentAreaID = 0;

SELECT @rc = @@ROWCOUNT;

WHILE @rc >= 1
BEGIN
 SELECT @l = @l + 1;

 INSERT @nest(AreaID, [Level], ParentNestID)
  SELECT f.AreaID, @l, n.NestID
   FROM @foo AS f
   INNER JOIN @nest AS n
   ON f.ParentAreaID = n.AreaID
   AND n.[Level] = @l - 1;

 SET @rc = @@ROWCOUNT;

 UPDATE n
  SET n.AreaIDPath = COALESCE(n2.AreaIDPath, '') 
   + '\' + CONVERT(VARCHAR(12), n.AreaID) + '\'
     FROM @nest AS n
     INNER JOIN @nest AS n2
     ON n.ParentNestID = n2.NestID
     WHERE n.[Level] = @l
     AND n2.AreaIDPath NOT LIKE '%\' + CONVERT(VARCHAR(12), n.AreaID) + '\%';
END

SELECT
 structure = REPLICATE(' - ', n.[Level]) + RTRIM(f.AreaID), 
 f.AreaID, f.[Level], f.ParentAreaID 
FROM @nest AS n
INNER JOIN @foo AS f
ON n.AreaID = f.AreaID
ORDER BY n.AreaIDPath;

Это действительно то, для чего были созданы рекурсивные CTE в SQL Server 2005. (По сути, это по-прежнему курсор, но синтаксис гораздо чище, чем в описанном выше беспорядке.) До тех пор, пока вы не сможете перейти на SQL Server 2005, вам может повезти, если вы просто используете уровень представления для циклического перебора результирующего набора и упорядочивания объектов соответствующим образом, если это слишком сложно представить в операциях с запросами.

0 голосов
/ 28 октября 2009

Мне бы пришлось потратить больше времени на изучение этого вопроса, чтобы выяснить детали ... но, если вы используете SQL Server 2005 (или 2008), я бы посоветовал изучить использование выражения общей таблицы (CTE). Это позволяет вам строить запрос рекурсивно; так что вы можете выбрать здание, а затем выбрать всех его детей, чтобы добавить в список. Возможно, вам удастся придумать схему нумерации или тому подобное, чтобы получить записи в правильном порядке, используя CTE.

...