2

I'm using a stored procedure with a CTE and doing some paging. I also want to return an output parameter with the total count of the returned query before my paging. My problem is that I get an error that "OrderedSet" is not a valid object name.

    @ft INT,
    @page INT,
    @pagesize INT,
    @count INT OUTPUT
AS
BEGIN

DECLARE @offset INT
SET @offset = @page * @pagesize
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    -- Insert statements for procedure here
    WITH OrderedSet AS (
    SELECT *, ROW_NUMBER() OVER (ORDER BY Id DESC) AS 'Index'
    FROM tbl_BulkUploadFiles buf
    WHERE
    buf.FileType = @ft )
    SELECT * FROM OrderedSet WHERE [Index] BETWEEN @offset AND (@offset + @pagesize)
    SET @count = (SELECT COUNT(*) FROM OrderedSet)
END

So my issue is on the last line, error is that last OrderedSet is not a valid object name.

Thanks in advance for any help!

Andriy M
  • 76,112
  • 17
  • 94
  • 154
Doug E Fresh
  • 820
  • 8
  • 22
  • See this: http://stackoverflow.com/questions/2136659/how-to-reference-one-cte-twice it should help. – jpw Jun 25 '15 at 15:35
  • Maybe you could do the count on the original source table instead of on the CTE?: `SET @count = (SELECT COUNT(*) FROM tbl_BulkUploadFiles buf WHERE buf.FileType = @ft )` – jpw Jun 25 '15 at 15:52
  • @jpw Yeah that would work. However this is a simple query I'm using, some queries I'm going to add paging to have very large search parameter lists and sort options blah blah so I was trying to find a way to avoid copy / pasting those big chunks of code. – Doug E Fresh Jun 25 '15 at 15:57

3 Answers3

3

Here are 2 approaches that avoid copying and pasting all the CTEs multiple times.

Return total rows as column of result set

Benefit here is that you can calculate total rows without multiple queries and temp tables, but you have to add logic to your front end to get the total row count from the first row of the result set before iterating over it to display the paged set. Another consideration is that you must account for no rows being returned, so set your total row count to 0 if no rows returned.

;WITH OrderedSet AS (
    SELECT
        *,
        ROW_NUMBER() OVER (ORDER BY Id DESC) AS Seq,
        ROW_NUMBER() OVER (ORDER BY Id) AS SeqRev
    FROM tbl_BulkUploadFiles buf
    WHERE buf.FileType = @ft
)
    SELECT *, Seq + SeqRev - 1 AS [TotalCount]
    FROM OrderedSet
    WHERE Seq BETWEEN @offset AND (@offset + @pagesize)

Utilize a temp table

While there is a cost of a temp table, if your database instance follows best practices for tempdb (multiple files for multi-cores, reasonable initial size, etc), 200k rows may not be a big deal since the context is lost after the stored proc completes, so the 200k rows don't exist for too long. However, it does present challenges if these stored procs are called quite often concurrently - doesn't scale too well. However, you are not keeping the entire table - just the paged rows, so hopefully your page sizes are much smaller than 200k rows.

The approach below tries to minimize the tempdb cost being able to calculate the row count by getting only the first row due to the method of ASC and DESC ROW_NUMBERs.

;WITH OrderedSet AS (
    SELECT
        *,
        ROW_NUMBER() OVER (ORDER BY Id DESC) AS Seq,
        ROW_NUMBER() OVER (ORDER BY Id) AS SeqRev
    FROM @buf buf --tbl_BulkUploadFiles buf
    WHERE buf.FileType = @ft
)
    SELECT * INTO #T
    FROM OrderedSet
    WHERE Seq BETWEEN @offset AND (@offset + @pagesize)
SET @count = COALESCE((SELECT TOP 1 SeqRev + Seq - 1 FROM #T), 0)
SELECT * FROM #T

Note: The method used above for calculating row counts was adapted from How to reference one CTE twice? and http://www.sqlservercentral.com/articles/T-SQL/66030/.

Community
  • 1
  • 1
Jason W
  • 13,026
  • 3
  • 31
  • 62
  • This is some great information Jason. I will test out these two approaches ( the temp table was already an option I have in my queue of things to try). I was hoping to avoid returning an extra column because I am using dapper and that messes with the mapping sometimes. – Doug E Fresh Jul 08 '15 at 15:38
0

Only alternative I see is repeating the query as inline view

  select @count = numrows FROM 
  (
    SELECT count(*) as numrows, 
    ROW_NUMBER() OVER (ORDER BY Id DESC) AS 'Index'
    FROM tbl_BulkUploadFiles buf
    WHERE
    buf.FileType = @ft 
    ) XXX WHERE [Index] BETWEEN @offset AND (@offset + @pagesize)
Rahul
  • 76,197
  • 13
  • 71
  • 125
  • jpw - I think you may be right there, that is what it seems to be. So do I have to repeat the whole CTE code? Seems there must be an easier way to accomplish this. – Doug E Fresh Jun 25 '15 at 15:39
  • 1
    @Rahul I don't think you can use the same CTE in more than one query. See the question I linked. – jpw Jun 25 '15 at 15:42
  • @Sapy, see my edit in answer; that's the alternative for this situation. – Rahul Jun 25 '15 at 15:58
0

You can't use the CTE in more than one select statement. From the MSDN docs (talking about the CTE itself).

This is derived from a simple query and defined within the execution scope of a single SELECT, INSERT, UPDATE, or DELETE statement.

You either need to run the CTE twice (probably a bad idea) or select the results of the CTE into a temp table and then select the paged data from that along with the total count.

Becuzz
  • 6,846
  • 26
  • 39
  • You are right that it a CTE can only have a single operation on it, I see that now. Some of the similar articles I found used temp tables, I assumed that would cause a performance hit, am I wrong there? What if the temp table had 200k rows jammed into it. – Doug E Fresh Jun 25 '15 at 15:47
  • 1
    @Sapy The temp table certainly won't be free, but I'd be willing to bet it is faster than running the CTE twice. You would have to try it to be sure. Also, if you are using SQL Server 2012 and up you may want to look into [offset and fetch](http://dbadiaries.com/new-t-sql-features-in-sql-server-2012-offset-and-fetch) for paging. – Becuzz Jun 25 '15 at 15:56