Question

I have a query below that works in SQL Server to select the difference between a value in one column and the value from another column, but two rows earlier in the source table. The source table has three columns (PK bigint, Pv float, Cash float).

When I use this query, what I get back is a series of single-row rowsets, rather than one big rowset. I can insert these one-by-one into a temp table then select out of that, but this means for large tables I end up with a big temp table as well as a long delay to the first record being returned.

Is there any way to spool the results of this query efficiently as a single row set?

declare @PK bigint
declare @Pv float
declare @Cash float
declare @Cash_minus_1 float
declare @Cash_minus_2 float
set @Cash_minus_1 = 0
set @Cash_minus_2 = 0
declare myCursor cursor fast_forward
for select PK, Pv, Cash from MyTable order by PK
for read only
open myCursor
fetch next from myCursor
into @PK, @Pv, @Cash
while @@FETCH_STATUS = 0
begin
 select @PK, @Pv, @Cash, @Cash_minus_2
 set @Cash_minus_2 = @Cash_minus_1
 set @Cash_minus_1 = @Cash
 fetch next from myCursor
 into @PK, @Pv, @Cash
end
close myCursor
deallocate myCursor
Was it helpful?

Solution

Assuming SQL Server 2005 or later, you can use ROW_NUMBER() and a common table expression to arrange the table appropriately:

;With OrderedRows as (
    select PK, Pv, Cash,
        ROW_NUMBER() OVER (ORDER BY PK) as rn --Is this correct?
    from MyTable
)
select or1.PK,or1.Pv,or1.Cash,COALESCE(or2.Cash,0)
from
    OrderedRows or1
        left join
    OrderedRows or2
        or1.rn = or2.rn + 2

In the above, I've assumed PK defines the order in which rows should be considered, such that "two rows earlier" from your question even makes sense.


In general, Cursors should be viewed as a tool of last resort. You should always try, first, to formulate your query be talking about what the entire result set should look like - not how you (if you had to) would work it out, row by row.

OTHER TIPS

Use ROW_NUMBER function to assign numbers to each row in wanted order (needed because PK can have gaps which would break the query) and join the table on itself with -2 rows.

WITH CTE AS 
(
    SELECT PK, Pv, Cash, ROW_NUMBER() OVER (ORDER BY PK) RN from MyTable
)
SELECT C.PK, C.Pv, C.Cash, C2.Cash AS Cash_Minus_2 
FROM CTE C
LEFT JOIN CTE C2 ON C.RN = C2.RN + 2

In SQL Server 2012 there is even a window function LAG you can use directly.

SELECT PK, Pv, Cash, LAG(Cash,2,NULL) OVER (ORDER BY PK) AS Cash_Minus_2
FROM MyTable
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top