Выберите иерархические данные без использования CTE - PullRequest
0 голосов
/ 23 июня 2018

У меня есть следующая таблица:

CREATE TABLE [dbo].[ProductHierarchy]
(
    [ProductHierarchyID] INT NOT NULL
          PRIMARY KEY CLUSTERED IDENTITY(1, 1),
    [ProductID] INT NOT NULL ,
    [ParentProductID] INT NULL
);

с этими данными:

INSERT INTO [dbo].[ProductHierarchy] ([ProductID], [ParentProductID])
VALUES (1, NULL), (2, 1), (3, 1), (4, 2), (5, 4), (6, 4), (7, 4);

Теперь я могу выполнять иерархический запрос с помощью CTE:

WITH [CTE_Products] ([ProductID], [ParentProductID], [ProductLevel]) AS 
(
    SELECT
        [ProductID],
        [ParentProductID],
        0 AS [ProductLevel]
    FROM 
        [dbo].[ProductHierarchy]
    WHERE 
        [ParentProductID] IS NULL

    UNION ALL

    SELECT  
        [pn].[ProductID],
        [pn].[ParentProductID],
        [p1].[ProductLevel] + 1
    FROM 
        [dbo].[ProductHierarchy] AS [pn]
    INNER JOIN 
        [CTE_Products] AS [p1] ON [p1].[ProductID] = [pn].[ParentProductID]
)
SELECT  
    [ProductID],
    [ParentProductID],
    [ProductLevel]
FROM 
    [CTE_Products]
ORDER BY 
    [ParentProductID];

Но как я могу достичь той же цели с помощью одного оператора SQL без использования CTE? Это возможно?

Ответы [ 2 ]

0 голосов
/ 23 июня 2018

Добрый день,

Прежде чем я начну! Настоятельно рекомендуется переосмыслить структуру вашей базы данных, если это ваше требование. Использование рекурсивного CTE (или, возможно, большинства других рекурсивных решений) означает, что вы используете петлевое решение, которое не рекомендуется в СУБД (которое лучше всего оптимизировано для работы с SET данных). Если вам нужно использовать рекурсивные данные, то вам следует подумать, например, об использовании типа данных иерархии (или реализовать что-то подобное или лучше - да, например, существуют более эффективные алгоритмы для иерархии, основанные на диапазонах, но их более сложно объяснить вкратце сообщение на форуме). Более того, у вас должна быть очень веская причина делать это без рекурсивного CTE, поскольку для текущей структуры рекурсивное CTE должно обеспечивать наилучшую производительность в большинстве случаев.

С учетом вышесказанного, поскольку вы запрашиваете это решение, проверьте, удовлетворяет ли этот запрос вашим потребностям

-- DDL+DML
drop table if exists [ProductHierarchy];
CREATE TABLE [dbo].[ProductHierarchy]
(
    [ProductID] INT NOT NULL ,
    [ParentProductID] INT NULL
);

INSERT  [dbo].[ProductHierarchy]
        ( [ProductID], [ParentProductID] )
VALUES  ( 1, NULL ),
        ( 2, 1 ),
        ( 3, 1 ),
        ( 4, 2 ),
        ( 5, 4 ),
        ( 6, 4 ),
        ( 7, 4 ),
        ( 8, 2)
GO

-- Solution?
SELECT distinct [level], [ProductID] FROM (
    -- I recomend to check this inner quesry first
    -- in order to understand the logic
    select 
        A.ProductID as L1,
        B.ProductID as L2,
        C.ProductID as L3,
        D.ProductID as L4 
    from (
        select ProductID
        from [ProductHierarchy]
        where ParentProductID is null
    ) A
    -- For each level that we wany to get
    -- we will add another sub-query
    -- like bellow
    left join(
        SELECT [ProductID], [ParentProductID]
        FROM [ProductHierarchy]
    ) B 
        ON B.[ParentProductID] = A.[ProductID]
    left join(
        SELECT [ProductID], [ParentProductID]
        FROM [ProductHierarchy]
    ) C 
        ON C.[ParentProductID] = B.[ProductID]
    left join(
        SELECT [ProductID], [ParentProductID]
        FROM [ProductHierarchy]
    ) D 
        ON D.[ParentProductID] = C.[ProductID]
) T
UNPIVOT (
    ProductID for [Level] in (L1,L2,L3,L4)
) as a

Примечание. При сравнении моего решения ( при условии, что оно соответствует вашим потребностям ) с Lukasz Szozda, согласно плану выполнения (я не проверял IO или Time), мое решение будет использовать только 22%, в то время как решение Lukasz использует 78% (протестировано на сервере sql 2017)

0 голосов
/ 23 июня 2018

CTE позволяет решать этот вид рекурсивных запросов. Если вы заранее знаете уровень глубины, вы можете «раскрутить» рекурсивную часть:

SELECT p0.ProductId, p0.ParentProductID, 0 AS ProductLevel
FROM [dbo].[ProductHierarchy] p0
WHERE p0.ParentProductId IS NULL
UNION ALL
SELECT p1.ProductId, p1.ParentProductID, 1 AS ProductLevel
FROM [dbo].[ProductHierarchy] p1
JOIN (SELECT p0.ProductId, p0.ParentProductID, 0 AS ProductLevel
      FROM [dbo].[ProductHierarchy] p0
      WHERE p0.ParentProductId IS NULL) p0
  ON p1.ParentProductId = p0.ProductId
UNION ALL
SELECT p2.ProductId, p2.ParentProductID, 2 AS ProductLevel
FROM [dbo].[ProductHierarchy] p2
JOIN (SELECT p1.ProductId, p1.ParentProductID, 1 AS ProductLevel
      FROM [dbo].[ProductHierarchy] p1
      JOIN (SELECT p0.ProductId, p0.ParentProductID, 0 AS ProductLevel
      FROM [dbo].[ProductHierarchy] p0
      WHERE p0.ParentProductId IS NULL) p0
        ON p1.ParentProductId = p0.ProductId) p1
  ON p2.ParentProductId = p1.ProductId
UNION ALL
SELECT p3.ProductId, p3.ParentProductID, 3 AS ProductLevel
FROM [dbo].[ProductHierarchy] p3
JOIN  (SELECT p2.ProductId, p2.ParentProductID, 2 AS ProductLevel
       FROM [dbo].[ProductHierarchy] p2
       JOIN (SELECT p1.ProductId, p1.ParentProductID, 1 AS ProductLevel
             FROM [dbo].[ProductHierarchy] p1
             JOIN (SELECT p0.ProductId, p0.ParentProductID, 0 AS ProductLevel
                    FROM [dbo].[ProductHierarchy] p0
                    WHERE p0.ParentProductId IS NULL) p0
              ON p1.ParentProductId = p0.ProductId) p1
        ON p2.ParentProductId = p1.ProductId) p2
  ON p3.ParentProductId = p2.ProductId;

Демоверсия DBFiddle

...