SQL Server переписать иерархическую функцию CTE в обычный выбор - PullRequest
0 голосов
/ 21 февраля 2019

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

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

Первая часть скрипта - это оператор выбора, в котором функция вызывается и, очевидно, возвращается в таблицу:

DECLARE @RootNode INT = 1
DECLARE @Level1 INT = 2
DECLARE @Level2 INT = 3
DECLARE @Level3 INT = 4
DECLARE @Level4 INT = 5


TRUNCATE TABLE [...].[Hierarchy]
--
INSERT INTO [...].[Hierarchy]
SELECT Nodes.NodeId, 
       NodeTypeValues.Value AS HierarchyValue, 
       (select NodeTypeValue from [...].[Function_GetTheParentNodesForTheSelectedNodeType] (abc.NodeId, @RootNode)) AS RootLevel,
       (select NodeTypeValue from [...].[Function_GetTheParentNodesForTheSelectedNodeType] (abc.NodeId, @Level1)) AS Level1,
       (select NodeTypeValue from [...].[Function_GetTheParentNodesForTheSelectedNodeType] (abc.NodeId, @Level2)) AS Level2,
       (select NodeTypeValue from [...].[Function_GetTheParentNodesForTheSelectedNodeType] (abc.NodeId, @Level3)) AS Level3,
       (select NodeTypeValue from [...].[Function_GetTheParentNodesForTheSelectedNodeType] (abc.NodeId, @Level4)) AS Level4
       --Level 5...
       --Level 6...
       --Level 7...
  FROM [...].[Nodes] Nodes
       INNER JOIN [...].NodeTypes NodeTypes ON NodeTypes.NodeTypeId = Nodes.NodeTypeId
       INNER JOIN [...].NodeTypeValues NodeTypeValues ON NodeTypeValues.NodeTypeValueId = Nodes.NodeTypeValueId
WHERE NodeTypes.HierarchyTypeId = 1

Вторая часть - это фактическая вызываемая функция,Функция предназначена для обхода и возврата табличного результата обратно в основной запрос к хранилищу:

FUNCTION [...].[Function_GetTheParentNodesForTheSelectedNodeType]

    ( @NodeId int,
      @NodeTypeId int
    )
    RETURNS 
      @ReturnData TABLE 
    (
      NodeTypeValue NVARCHAR(100),
      NodeId INT
    )

AS
BEGIN

    WITH NodeSubTreesUpwards AS 
    (
       SELECT SubRootNode.NodeId AS SubRootNodeId, 
              SubRootNode.*,
              NULL AS ChildNodeId, 
              0 AS HierarchyLevel
        FROM [...].[Nodes] AS SubRootNode
        WHERE SubRootNode.NodeId = @NodeId

      UNION ALL

       SELECT NodeSubTreesUpwards.SubRootNodeId, 
              ParentNode.*,
              Parent.ChildNodeId, (NodeSubTreesUpwards.HierarchyLevel) - 1 AS HierarchyLevel
        FROM NodeSubTreesUpwards
        INNER JOIN [...].[ParentChildNodes] AS Parent ON Parent.ChildNodeId = NodeSubTreesUpwards.NodeId
        INNER JOIN [...].[Nodes] AS ParentNode ON ParentNode.NodeId = Parent.ParentNodeId
    )

    INSERT INTO @ReturnData
    SELECT TOP 1 NodeTypeValues.Value,  NodeSubTreesUpwards.NodeId
          FROM NodeSubTreesUpwards NodeSubTreesUpwards
                   INNER JOIN [...].[NodeTypes] NodeType ON NodeType.NodeTypeId = n.NodeTypeId
                   INNER JOIN [...].[NodeTypeValues] NodeTypeValues ON NodeTypeValues.NodeTypeValueId = n.NodeTypeValueId
     WHERE NodeType.NodeTypeId = @NodeTypeId

   RETURN 

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

Ответы [ 2 ]

0 голосов
/ 21 февраля 2019

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

Сценарий, который выполняет вставку, просто «поворачивает» родительские уровни узла, поэтому к функции обращаются N вызовов, каждый из которых получает более высокий уровень.

Я смешал первый SELECT (без INSERT и переменных) с реализацией функции для массовой работы и за один раз для всех соответствующих записей в следующем SQL.Краткое описание каждого CTE приведено ниже.

Для любых дальнейших исправлений мне понадобится полностью реплицируемый DML + DDL, я сделал все, что мог, не имея правильной схемы.

;WITH RecursionInputNodes AS
(
    SELECT DISTINCT
        Nodes.NodeId
    FROM 
        [...].[Nodes] Nodes
        INNER JOIN [...].NodeTypes NodeTypes ON NodeTypes.NodeTypeId = Nodes.NodeTypeId
        INNER JOIN [...].NodeTypeValues NodeTypeValues ON NodeTypeValues.NodeTypeValueId = Nodes.NodeTypeValueId
    WHERE 
        NodeTypes.HierarchyTypeId = 1
),
RecursiveCTE AS
(
    -- CTE Anchor: Start with all input nodes at lvl 0
    SELECT 
        SubRootNode.NodeId AS NodeId, 
        NULL AS ChildNodeId,
        0 AS HierarchyLevel,
        SubRootNode.NodeTypeId AS NodeTypeId,
        NodeTypeValues.Value AS NodeTypeValue
    FROM
        RecursionInputNodes AS RI
        INNER JOIN [...].[Nodes] AS SubRootNode ON RI.NodeID = RI.NodeId
        INNER JOIN [...].[NodeTypes] NodeType ON NodeType.NodeTypeId = RI.NodeTypeId
        INNER JOIN [...].[NodeTypeValues] NodeTypeValues ON NodeTypeValues.NodeTypeValueId = RI.NodeTypeValueId

    UNION ALL

    -- CTE Recursion: Add each node's parent and decrease lvl by 1 each time
    SELECT 
        R.NodeId,
        Parent.ChildNodeId,
        R.HierarchyLevel - 1 AS HierarchyLevel,
        ParentNode.NodeTypeId AS NodeTypeId,
        NodeTypeValues.Value AS NodeTypeValue
    FROM 
        RecursiveCTE AS R
        INNER JOIN [...].[ParentChildNodes] AS Parent ON Parent.ChildNodeId = R.NodeId
        INNER JOIN [...].[Nodes] AS ParentNode ON ParentNode.NodeId = Parent.ParentNodeId
        INNER JOIN [...].[NodeTypes] NodeType ON NodeType.NodeTypeId = ParentNode.NodeTypeId
        INNER JOIN [...].[NodeTypeValues] NodeTypeValues ON NodeTypeValues.NodeTypeValueId = ParentNode.NodeTypeValueId
),
Just1RowByNodeTypeByNode AS
(
    SELECT
        R.NodeId,
        R.NodeTypeId,
        NodeTypeValue = MAX(R.NodeTypeValue) -- I'm "imitating" the TOP 1 from the function here
    FROM
        RecursiveCTE AS R
    GROUP BY
        R.NodeId,
        R.NodeTypeId
)
SELECT 
    Nodes.NodeId, 
    NodeTypeValues.Value AS HierarchyValue,
    L1.NodeTypeValue AS RootLevel,
    L2.NodeTypeValue AS Level1, -- Note that the alias Level 1 here actually corresponds to the value 2 for NodeTypeId
    L3.NodeTypeValue AS Level2,
    L4.NodeTypeValue AS Level3,
    L5.NodeTypeValue AS Level4
    --Level 5...
    --Level 6...
    --Level 7...
FROM 
    RecursionInputNodes Nodes
    INNER JOIN [...].NodeTypes NodeTypes ON NodeTypes.NodeTypeId = Nodes.NodeTypeId
    INNER JOIN [...].NodeTypeValues NodeTypeValues ON NodeTypeValues.NodeTypeValueId = Nodes.NodeTypeValueId

    LEFT JOIN Just1RowByNodeTypeByNode AS L1 ON Nodes.NodeId = L1.NodeId AND L1.NodeTypeId = 1
    LEFT JOIN Just1RowByNodeTypeByNode AS L2 ON Nodes.NodeId = L2.NodeId AND L2.NodeTypeId = 2
    LEFT JOIN Just1RowByNodeTypeByNode AS L3 ON Nodes.NodeId = L3.NodeId AND L3.NodeTypeId = 3
    LEFT JOIN Just1RowByNodeTypeByNode AS L4 ON Nodes.NodeId = L4.NodeId AND L4.NodeTypeId = 4
    LEFT JOIN Just1RowByNodeTypeByNode AS L5 ON Nodes.NodeId = L5.NodeId AND L5.NodeTypeId = 5
  • RecursionInputNodes содержит список входных узлов для рекурсии.
  • RecursiveCTE - это набор всех входных узлов с их родительскими отношениями до тех пор, пока их больше не будет.Родительские отношения даются через Parent.ChildNodeId = R.NodeId.Я также добавил NodeTypeId и NodeTypeValue, потому что нам нужно отфильтровать их в следующем CTE.
  • Just1RowByNodeTypeByNode используется для определения, NodeId и NodeTypeId, требуемого значения NodeTypeValue, что и требуется вызывающей стороне от функции.NodeTypeId будет отфильтрован (это параметр из исходной функции).Этот шаг «имитирует» TOP 1 из исходной функции.

Я бы порекомендовал выполнять каждый CTE один за другим (каждый с предыдущим, на который они ссылаются), чтобы понять, какпоследний SELECT собрал все вместе.

0 голосов
/ 21 февраля 2019

Попробуйте использовать встроенную табличную функцию (ITVF), так как они имеют лучшие планы выполнения.* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *1006* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *} * * * * * * * * * *} * *} * *} * *}} * * * * *} * *}} *}} *} *} *} *}.оценка. , если вы используете TVF с несколькими утверждениями, он рассматривается как другая таблица.Поскольку нет доступной статистики, SQL Server должен сделать некоторые допущения и в целом дать низкую оценку.Если ваш TVF вернет только несколько строк, все будет хорошо.Но если вы намереваетесь заполнить TVF тысячами строк и если этот TVF объединен с другими таблицами, неэффективный план может быть результатом оценки низкой мощности.

Так что просто создайте две встроенные таблицыфункции из вашего многострочного оператора function Function_GetTheParentNodesForTheSelectedNodeType:

CREATE FUNCTION dbo.ufn_NodeSubTreesUpwards
     ( @NodeId int )
RETURNS table
AS
RETURN (
        SELECT SubRootNode.NodeId AS SubRootNodeId, 
              SubRootNode.*,
              NULL AS ChildNodeId, 
              0 AS HierarchyLevel
        FROM [...].[Nodes] AS SubRootNode
        WHERE SubRootNode.NodeId = @NodeId

      UNION ALL

       SELECT NodeSubTreesUpwards.SubRootNodeId, 
              ParentNode.*,
              Parent.ChildNodeId, (NodeSubTreesUpwards.HierarchyLevel) - 1 AS HierarchyLevel
        FROM NodeSubTreesUpwards
        INNER JOIN [...].[ParentChildNodes] AS Parent 
            ON Parent.ChildNodeId = NodeSubTreesUpwards.NodeId
        INNER JOIN [...].[Nodes] AS ParentNode ON ParentNode.NodeId = Parent.ParentNodeId
       )

и другая функция, которая будет использоваться в вашем INSERT запросе:

CREATE FUNCTION dbo.ufn_GetTheParentNodesForTheSelectedNodeType
     ( @NodeId int,
       @NodeTypeId int )
RETURNS table
AS
RETURN (
    SELECT 
     TOP 1 
     NodeTypeValues.Value
    , NodeSubTreesUpwards.NodeId
    FROM ufn_NodeSubTreesUpwards(@NodeId) NodeSubTreesUpwards
    INNER JOIN [...].[NodeTypes] NodeType ON NodeType.NodeTypeId = n.NodeTypeId
    INNER JOIN [...].[NodeTypeValues] NodeTypeValues 
        ON NodeTypeValues.NodeTypeValueId = n.NodeTypeValueId
        WHERE NodeType.NodeTypeId = @NodeTypeId
       )

UPDATE -Пример использования рекурсивного cte во встроенных табличных функциях:

create function SequenceList ( @variable int )
returns table
as
return (
with cte as
(
select id = 1
union all
select id = cte.id+1
from cte
where id < @variable
)
select id from cte
--option ( maxrecursion 0 )
)

SELECT * FROM dbo.SequenceList(5)

...