views:

89

answers:

3

Hello, a few minutes ago i asked here how to get parent records with a recursive CTE. This works now, but I get the wrong order(backwards, ordered by the PK idData) when i create a Table valued Function which returns all parents. I cannot order directly because i need the logical order provided by the CTE.

This gives the correct order(from next parent to that parent and so on):

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

But the following function returns all records in backwards order(ordered by 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);

UPDATE: Since everybody beliefs that the CTE is not ordering(Any "ordering" will be totally arbitrary and coincidental), i'm wondering why the opposite seems to be true. I have queried a child claim with many parents and the order in the CTE is exactly the logical order when i go from child to parent and so on. This would mean that the CTE is iterating from record to record like a cursor and the following select returns it in exact this order. But when i call the TVF i got the order of the primary key idData instead.

The solution was simple. I only needed to remove the parent key of the return-Table of the TVF. So change...

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

to...

RETURNS @retPreviousClaims TABLE 
(
     idData int
)

.. and it keeps the right "order" (same order they were inserted into the CTE's temporary result set).

UPDATE2: Because Damien mentioned that the "CTE-Order" could change in certain circumstances, i will add a new column relationLevel to the CTE which describes the level of relationship of the parent records (what is by the way quite useful in general f.e. for a ssas cube). So the final Inline-TVF(which returns all columns) is now:

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
)

This is an exemplary relationship:

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

alt text

Thank you.

+2  A: 

I think the impression that the CTE is creating an ordering is wrong. It's a coincidence that the rows are coming out in order (possibly due to how they were originally inserted into tabData). Regardless, the TVF is returning a table so you have to explicitly add an ORDER BY to the SELECT you're using to call it if you want to guarantee ordering:

select * from dbo._previousClaimsByFiData(16177344) order by idData
aBitObvious
The problem is that the order by idData is worthless, because it is not guaranteed that it is equal with the logical parent-child order. Do i have to iterate through it with a cursor instead of using the CTE?
Tim Schmelter
See if you can find a non-cursor approach to find the ordering. Don't know your data relationship well enough to tell that. Perhaps some sample parent/child data and the proper ordering might help.
aBitObvious
A sample is provided in the previous question mentioned above, here is the link: http://stackoverflow.com/questions/3935005/recursive-cte-to-find-parent-records
Tim Schmelter
Why I'm afraid to use the order by again, although the order should be given by child/parent relationship(i assumed):http://stackoverflow.com/questions/3266299/whats-wrong-with-my-sql-find-a-previous-record/3266398#3266398It should be unnecessary to order what's already iterated by a CTE one moment earlier(if its arbitrayry in a CTE i should use a cursor) .
Tim Schmelter
Thank you too, i will check tomorrow marc_s suggestion what is similar to yours.
Tim Schmelter
I only needed to remove the primary key of the returned table to keep the "natural order" of the CTE. See my updates...
Tim Schmelter
Ok but you really need an order by in the final select. I guess by relationLevel in your updated function.
aBitObvious
+2  A: 

There is no ORDER BY anywhere in sight - neither in the table-valued function, nor in the SELECT from that TVF.

Any "ordering" will be totally arbitrary and coincidental.

If you want a specific order, you need to specify an ORDER BY.

So why can't you just add an ORDER BY to your SELECT:

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

or put your ORDER BY into the TVF:

INSERT INTO @retPreviousClaims
    SELECT idData FROM PreviousClaims
    ORDER BY idData DESC (or whatever it is you want to order by...)
marc_s
Because the order is given by the internal structure of the parent-child relationship. The foreignkey fiData is set in a stored procedure running every night when new data is being imported. The only way i can see (without a cursor) is to use the order by of this stored procedure, but that means a lot of overhead which slows down every query and makes the whole approach redundant. Have a look at this older question of mine where you can find part of this logic and the order by: http://stackoverflow.com/questions/3266299/whats-wrong-with-my-sql-find-a-previous-record
Tim Schmelter
After rethinking i came to conclusion that this is the only way and is not as bad as i first thought, because there are only a few parents to order. Ok, i will need some more Date columns instead of only the PK. Thanks, i will test it tomorrow.
Tim Schmelter
Note though that adding an ORDEr BY in an INSERT is a no-op basically. @retPreviousClaims has no order. The ORDER BY has to be applied to the `select * from dbo._previousClaimsByFiData(...) ORDER BY ....`. If the TVF is used in a query context, the ORDER BY must be applied tot he query. And so on and so forth. Only **results** have order.
Remus Rusanu
And since we're on the topic, the TVF would be much better to be an *inline* TVF and preferable schema bound.
Remus Rusanu
@Remus: What i want to avoid in any circumstance is that every query have to apply that order for itself. One of the reason for storing the parent records in a foreignkey and access the parents through this TVE function was to reduce complexity(and impr. performance) from the applications and encapsulate this logic at the Database. This is going to be more and more redundant if every caller have to bear in mind to order by 4 different Datecolumn descending, after getting the result.What do you mean with inline TVF and preferable schema bound? Thanks for any suggestion :)
Tim Schmelter
I only needed to remove the primary key of the returned table to keep the "natural order". See my updates...
Tim Schmelter
@Tim Schmelter: there is no such thing as a "natural order" - unless you have an explicit ORDER BY in your SQL somwhere, there is **NO** order....
marc_s
I meant the "internal" order they are inserted into the temporary resultset of the CTE. This seems to be the same as when i would iterate from record to record with a cursor. The first record is the one with idData=passed fiData and the last record has fiData=NULL. But when it is not guaranteed that this remains the same in future, i will follow damian's suggestion to order by a "relationLevel".
Tim Schmelter
inline TVF: http://msdn.microsoft.com/en-us/library/ms189294.aspx
Remus Rusanu
schema binding: http://blogs.msdn.com/b/sqlprogrammability/archive/2006/05/12/596424.aspx
Remus Rusanu
+2  A: 

The correct way to do your ORDERing is to add an ORDER BY clause to your outermost select. Anything else is relying on implementation details that may change at any time (including if the size of your database/tables goes up, which may allow more parallel processing to occur).

If you need something convenient to allow the ordering to take place, look at Example D in the examples from the MSDN page on WITH:

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 
)

Add something similay to the EmployeeLevel column to your CTE, and everything should work.

Damien_The_Unbeliever
Thank you for that hint. The result remains the same, but when you say that it could change later in certain circumstances, i will use this method to force the correct order. I will update my question accordingly soon.
Tim Schmelter