Search code examples
sqlsql-servert-sqlcountpagination

Select COUNT(*) of subquery without running it twice


I've got a procedure to return a result set which is limited by page number and some other stuff. As an OUTPUT parameter I need to return a total amount of selected rows according to the parameters except the page number. So I have something like that:

WITH SelectedItems AS
(SELECT Id, Row1, Row2, ROW_NUMBER() OVER (ORDER BY Row1) AS Position
FROM Items
WHERE Row2 = @Row2)
SELECT Id, Row1, Row2
FROM SelectedItems
WHERE Position BETWEEN @From AND @To

And then I need to set the OUTPUT parameter to the number of rows in the innerquery. I can just copy the query and count it, but this query could returns thousands of rows (and will be more in the future), so I am looking for method to do that with a good performance. I was thinking about table variables, is it a good idea? Or any other suggestions?

To be more specific, it's the Microsoft SQL Server 2008.

Thank you, Jan


Solution

  • You can count the total rows as a separate column in your main query using COUNT(*). Like this:

    WITH SelectedItems AS
    (SELECT Id, Row1, Row2, ROW_NUMBER() OVER (ORDER BY Row1) AS Position, 
    COUNT(*) OVER () AS TotalRows
    FROM Items
    WHERE Row2 = @Row2)
    SELECT Id, Row1, Row2
    FROM SelectedItems
    WHERE Position BETWEEN @From AND @To
    

    This will return the count in your result set rather than in a output parameter, but that should fit your requirements. Otherwise, combine with a temp table:

    DECLARE @tmp TABLE (Id int, RowNum int, TotalRows int);
    
    WITH SelectedItems AS
    (SELECT Id, Row1, Row2, ROW_NUMBER() OVER (ORDER BY Row1) AS Position, 
    COUNT(*) OVER () AS TotalRows
    FROM Items
    WHERE Row2 = @Row2)
    INSERT @tmp
    SELECT Id, Row1, Row2
    FROM SelectedItems
    WHERE Position BETWEEN @From AND @To
    
    SELECT TOP 1 @TotalRows = TotalRows FROM @tmp
    SELECT * FROM @tmp
    

    You will find using a temp table for just your paged result will not use much memory (depending on your page size of course) and you're only keeping it live for a short period of time. Selecting the full result set from the temp table and selecting the TotalRows will only take a tiny bit longer.

    This will be much faster than running a totally separate query, which in my test (repeating the WITH) doubled the execution time.