簡體   English   中英

SQL Server將分層CTE函數重寫為常規選擇

[英]SQL Server Rewrite Hierarchical CTE Function to a regular Select

我的任務是遷移遍歷層次結構並擴展它的腳本。 首先,腳本運行速度非常慢,其次我們正在進入一個更加受控制的服務器,因此我需要消除功能。 我想知道是否有人可以協助在第二個語句中集成函數正在執行的操作,並在第一個腳本的選擇語句中調用整個腳本。

我理解兩者之間的分離可能會更好地表現,但是這是唯一存在的功能和使用它的唯一選擇語句所以我更願意整合兩者而不是通過獲得批准的過程並補充說。 其次,如果有人能夠看到一種更優化的方式來實現這一目標,那將是很好的,我願意接受建議,記住這大約有11個級別。

腳本的第一部分是select語句,其中函數被調用並顯然返回到表:

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 

我真的試圖把它分開,但一直在努力這樣做,我很可能錯過了一些愚蠢的東西,或者純粹只是不理解創建層次結構的過程,我現在已經坐了一兩天了。 我很樂意在不調用它的情況下使用相同的函數,而是在主select語句中代替被調用的函數,但不確定是否由於遞歸這將是一個問題?

嘗試使用內聯表值函數(ITVF),因為它們具有更好的執行計划。 MSDN上一篇關於多語句表值函數的查詢性能問題的文章

  1. 通常,多語句TVF給出非常低的基數估計。
  2. 如果你使用多語句TVF,它就像另一個表一樣對待。 由於沒有可用的統計信息,SQL Server必須做出一些假設,並且通常會提供較低的估計值。 如果您的TVF只返回幾行,那就沒問題了。 但是如果你打算用數千行填充TVF,並且如果這個TVF與其他表連接,那么效率低的計划可能是由於低基數估計造成的。

因此,只需從多行語句函數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)

事實上,整個劇本的表現非常糟糕。 每個函數調用都會從特定節點生成所有父關系,但只返回與節點類型過濾器對應的1行(它使用TOP 1並且沒有ORDER BY ,因此他們假設變量過濾器生成所需行)。

執行插入的腳本只是“旋轉”節點的父級,這就是為什么有N個函數調用,每個調用更高級別。

我將第一個SELECT (沒有INSERT和變量)與函數的實現混合在一起,並在下面的SQL中用1表示所有相應的記錄。 每個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保存RecursionInputNodes的輸入節點列表。
  • RecursiveCTE是具有父關系的所有輸入節點的集合,直到不再存在。 父關系通過Parent.ChildNodeId = R.NodeId 我還添加了NodeTypeIdNodeTypeValue因為我們需要在下一個CTE上過濾它們。
  • Just1RowByNodeTypeByNode來確定,每個NodeIdNodeTypeId的通緝值NodeTypeValue ,這是主叫方從功能所需要的。 NodeTypeId將被過濾(它是原始函數的參數)。 此步驟“模仿”原始功能的TOP 1

我建議按順序逐個執行每個CTE(每個CTE都有前一個,因為它們被引用)以了解最后一個SELECT如何一起使用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM