表值函数中的错误顺序(保持递归CTE的“顺序”)

时间:2010-10-14 16:50:33

标签: sql-server sql-server-2005 common-table-expression user-defined-functions

几分钟前我问here如何使用递归CTE获取父记录。 这现在有效,但是当我创建一个返回所有父节点的Table值函数时,我得到了错误的顺序(向后,由PK idData排序)。我无法直接订购,因为我需要CTE提供的逻辑顺序。

这给出了正确的顺序(从下一个父级到该父级,依此类推):

declare @fiData int;
set @fiData=16177344;
WITH PreviousClaims(idData,fiData) 
AS(
    SELECT parent.idData,parent.fiData
    FROM tabData parent
    WHERE parent.idData = @fiData

    UNION ALL

    SELECT child.idData,child.fiData
    FROM tabData child
    INNER JOIN PreviousClaims parent ON parent.fiData = child.idData
)
select iddata from PreviousClaims

但是以下函数以向后的顺序返回所有记录(按PK排序):

CREATE FUNCTION [dbo].[_previousClaimsByFiData] (
    @fiData INT
)

RETURNS @retPreviousClaims TABLE 
(
    idData int PRIMARY KEY NOT NULL
)
AS 
BEGIN
    DECLARE @idData int;

    WITH PreviousClaims(idData,fiData) 
    AS(
        SELECT parent.idData,parent.fiData
        FROM tabData parent
        WHERE parent.idData = @fiData

        UNION ALL

        SELECT child.idData,child.fiData
        FROM tabData child
        INNER JOIN PreviousClaims parent ON parent.fiData = child.idData
    )

    INSERT INTO @retPreviousClaims
        SELECT idData FROM PreviousClaims;
    RETURN;
END;

select * from dbo._previousClaimsByFiData(16177344);

更新 由于每个人都认为CTE没有订购(任何“排序”将是完全随意和巧合),我想知道为什么相反似乎是真的。我向很多家长询问了一个孩子的要求,当我从孩子到父母等等时,CTE中的命令完全符合逻辑顺序。这意味着CTE正在像光标一样从一个记录到另一个记录进行迭代,而后面的select则按照这个顺序返回它。但是当我打电话给TVF时,我得到了主键idData的顺序。

解决方案很简单。我只需要删除TVF返回表的父键。所以改变......

RETURNS @retPreviousClaims TABLE 
(
   idData int PRIMARY KEY NOT NULL
)

为...

RETURNS @retPreviousClaims TABLE 
(
     idData int
)

..它保持正确的“顺序”(它们被插入到CTE的临时结果集中的顺序相同)。

UPDATE2: 因为Damien提到“CTE-Order”在某些情况下会发生变化,我会在CTE中添加一个新的列relationLevel,它描述了父记录的关系水平(顺便提一下,这一点非常有用) fe for ssas cube)。 所以最终的Inline-TVF(返回所有列)现在是:

CREATE FUNCTION [dbo].[_previousClaimsByFiData] (
    @fiData INT
)

RETURNS TABLE AS
RETURN(
    WITH PreviousClaims 
    AS(
        SELECT 1 AS relationLevel, child.*
        FROM tabData child
        WHERE child.idData = @fiData

        UNION ALL

        SELECT relationLevel+1, child.*
        FROM tabData child
        INNER JOIN PreviousClaims parent ON parent.fiData = child.idData
    )

    SELECT TOP 100 PERCENT * FROM PreviousClaims order by relationLevel
)

这是一种示范性的关系:

select idData,fiData,relationLevel from dbo._previousClaimsByFiData(46600314);

alt text

谢谢。

3 个答案:

答案 0 :(得分:3)

执行ORDERing的正确方法是向最外面的select添加ORDER BY子句。其他任何东西都依赖于可能随时改变的实现细节(包括数据库/表的大小是否上升,这可能允许更多并行处理)。

如果您需要方便的东西来进行排序,请查看MSDN page on WITH中的示例中的示例D:

WITH DirectReports(ManagerID, EmployeeID, Title, EmployeeLevel) AS 
(
    SELECT ManagerID, EmployeeID, Title, 0 AS EmployeeLevel
    FROM dbo.MyEmployees 
    WHERE ManagerID IS NULL
    UNION ALL
    SELECT e.ManagerID, e.EmployeeID, e.Title, EmployeeLevel + 1
    FROM dbo.MyEmployees AS e
        INNER JOIN DirectReports AS d
        ON e.ManagerID = d.EmployeeID 
)

将一些similay添加到您的CTE的EmployeeLevel列中,一切都应该有效。

答案 1 :(得分:2)

我认为CTE正在创建排序的印象是错误的。这些行按顺序出现(可能是由于它们最初插入tabData的原因)是巧合。无论如何,TVF正在返回一个表,所以如果你想保证订购,你必须向你用来调用它的SELECT显式添加一个ORDER BY:

select * from dbo._previousClaimsByFiData(16177344) order by idData

答案 2 :(得分:2)

在任何地方都没有ORDER BY - 无论是在表值函数中,还是在该TVF的SELECT中。

任何“订购”都将完全随意而巧合。

如果您需要特定订单,则需要指定ORDER BY。

那你为什么不能只为你的SELECT添加一个ORDER BY:

 SELECT * FROM dbo._previousClaimsByFiData(16177344) 
 ORDER BY (whatever you want to order by)....

或将您的ORDER BY放入TVF:

INSERT INTO @retPreviousClaims
    SELECT idData FROM PreviousClaims
    ORDER BY idData DESC (or whatever it is you want to order by...)