Как эффективно получить упорядоченный иерархический список из большой таблицы (12 тыс. Записей) со столбцом, который ссылается на столбец первичного ключа - PullRequest
0 голосов
/ 16 сентября 2018

Я борюсь с оптимизацией SQL-запроса и ищу помощи. T-SQL для SQL Server 2008.

У меня есть набор Agents, в котором есть столбцы Id и ManagerId. Менеджер - это просто агент, поэтому ManagerId похож на внешний ключ к той же таблице. Я пишу запрос, чтобы вернуть агентов в упорядоченный список на основе управленческой иерархии.

С учетом набора

Id  Name    ManagerId
-----------------------
1   Charlie 4
2   Alpha   NULL
3   Echo    5
4   Bravo   2
5   Delta   1
6   Foxtrot 3
7   Golf    6
8   Hotel   7
9   Juliet  8
10  India   8

Я хочу вернуть значения в следующем порядке:

Id  Name    ManagerId
2   Alpha   NULL
4   Bravo   2
1   Charlie 4
5   Delta   1
3   Echo    5
6   Foxtrot 3
7   Golf    6
8   Hotel   7
9   Juliet  8
10  India   8

Стратегия, которую я использую сейчас, прекрасно работает для 10 значений. Реальный набор, на котором я буду использовать это, составляет приблизительно 12 000. Когда я использую приведенный ниже запрос на тестовом наборе 10000, на моем ноутбуке это занимает около 20 минут. Я использую цикл с подзапросами, поэтому я знаю, что должен быть лучший способ.

CREATE TABLE #hierarchy (rowNumber INT, agentId INT);
CREATE TABLE #finishedManagers (id INT);

DECLARE @index INT = 1;
DECLARE @count INT = (SELECT COUNT(Id) FROM agents);
DECLARE @thisId INT;

WHILE (@index <= @count)

BEGIN
    SET @thisId = ( 
        SELECT TOP 1
            a.Id
        FROM    
            agents a
        WHERE
            a.Id NOT IN (SELECT * FROM #finishedManagers)
            AND
            (a.ManagerId IS NULL OR a.ManagerId IN (SELECT agentId FROM #heirarchy))
            );

    INSERT INTO #hierarchy (rowNumber, agentId)
        SELECT
            @index,
            @thisId

    SET @index = @index + 1;

    INSERT INTO #finishedManagers(id)
        SELECT
            @thisId 
END
GO

SELECT 
    a.*
FROM 
    #hierarchy h
LEFT JOIN
    agents a ON h.agentId = a.Id
ORDER BY
    h.rowNumber;

DROP TABLE #hierarchy;
DROP TABLE #finishedManagers;

Как бы вы поступили об этом?

1 Ответ

0 голосов
/ 16 сентября 2018

Прежде всего, всегда старайтесь по возможности избегать циклов.

Ниже приведен пример использования рекурсивного CTE совместно с типом данных HIERARCHY.

Рекурсивные CTE великолепны, истоит потратить ваше время, чтобы освоиться с ними.Тем не менее, производительность может немного пострадать при больших / более глубоких иерархиях.

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

Пример

Declare @Top   int  = null  --<<  Sets top of Hier Just for FUN Try 3

;with cteP as (
      Select ID
            ,ManagerID 
            ,Name 
            ,HierID = convert(hierarchyid,'/'+convert(varchar(25),ID)+'/')
      From   YourTable 
      Where  IsNull(@Top,-1) = case when @Top is null then isnull(ManagerID ,-1) else ID end
      Union  All
      Select ID  = r.ID
            ,ManagerID  = r.ManagerID 
            ,Name   = r.Name
            ,HierID = convert(hierarchyid,p.HierID.ToString()+convert(varchar(25),r.ID)+'/')
      From   YourTable r
      Join   cteP p on r.ManagerID  = p.ID)
Select Lvl   = HierID.GetLevel()
      ,ID
      ,Name  
      ,ManagerID
 From cteP A
 Order By A.HierID

Возвращает

Lvl ID  Name    ManagerID
1   2   Alpha   NULL
2   4   Bravo   2
3   1   Charlie 4
4   5   Delta   1
5   3   Echo    5
6   6   Foxtrot 3
7   7   Golf    6
8   8   Hotel   7
9   9   Juliet  8
9   10  India   8

РЕДАКТИРОВАТЬ - Temp Table Approach 25 000 строк за 2 секунды

Обратите внимание, что у меня максимальная глубина 30 уровней.

Declare @Top int =null 
Select *
      ,Lvl=1
      ,HierID = convert(hierarchyid,'/'+convert(varchar(25),ID)+'/')
 Into #TempBld 
 From YourTable
 Where  IsNull(@Top,-1) = case when @Top is null then isnull(ManagerID,-1) else ID end

Declare @Cnt int=1
While @Cnt<=30
    Begin
        Insert Into #TempBld 
        Select A.*
              ,Lvl=B.Lvl+1
              ,HierID = convert(hierarchyid,b.HierID.ToString()+convert(varchar(25),a.ID)+'/')
         From  YourTable A
         Join  #TempBld B on (B.Lvl=@Cnt and A.ManagerID=B.ID)
        Set @Cnt=@Cnt+1
    End

Select Lvl
      ,ID
      ,Name
      ,ManagerID
 From #TempBld
 Order by HierID
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...