Search code examples
sql-serverselectpaginationcommon-table-expression

SQL Server recursive CTE and paging


I have this table

 CREATE TABLE [dbo].[friend_blocked_list](
 [subdomain] [varchar](50) NOT NULL,
 [un] [nvarchar](50) NOT NULL,
 [friend] [nvarchar](50) NOT NULL,
 [is_blocked] [bit] NOT NULL,
 [approved] [bit] NOT NULL)

where i select data from it with below query. Select query combines users that added user as friend and users that have been added as friend by user

declare @un varchar(50), @subdomain varchar(50)

set @un='user2';
set @subdomain ='test.domain.com';

WITH FRIENDS as
(
  SELECT friend
  FROM friend_blocked_list
  WHERE un=@un and subdomain=@subdomain and approved=1 and is_blocked=0

  UNION ALL

  SELECT un as friend
  FROM friend_blocked_list
  WHERE friend=@un and subdomain=@subdomain and approved=1 and is_blocked=0
)
select friend from FRIENDS group by FRIENDS.friend order by FRIENDS.friend asc

It works fine with small amout of data but i want to be able to do a paging on the server side in order to reduce load. I am trying to combine it with my paging sp below

create PROCEDURE [dbo].[Paging]
    @subdomain varchar(50),
    @un varchar(50),
    @PageNumber int,
    @PageSize int
AS
BEGIN
   --paging
   DECLARE @FirstRow INT,@LastRow INT,@RowCount INT,@PageCount INT

   --find recordcount and pages
   SELECT @RowCount = COUNT(*), @PageCount = COUNT(*) / @PageSize 
   FROM friend_blocked_list 
   WHERE subdomain=@subdomain AND un=@un AND approved=1 AND is_blocked=0;

   --- calculate pages    
   IF @RowCount % @PageSize != 0 SET @PageCount = @PageCount + 1 
   IF @PageNumber < 1 SET @PageNumber = 1 
   IF @PageNumber > @PageCount SET @PageNumber = @PageCount 

   SELECT 
        CurrentPage = @PageNumber, 
        TotalPages = @PageCount, 
        TotalRows = @RowCount 

   -- mora calculation
   SELECT @FirstRow = ( @PageNumber - 1) * @PageSize + 1,
          @LastRow = (@PageNumber - 1) * @PageSize + @PageSize ;

   WITH MyTopics  AS
   (
      SELECT *, ROW_NUMBER() OVER (order by un asc) AS RowNumber
      FROM friend_blocked_list 
      WHERE subdomain=@subdomain AND un=@un AND approved=1 AND is_blocked=0
   )
   SELECT *
   FROM MyTopics
   WHERE RowNumber BETWEEN @FirstRow AND @LastRow
   ORDER BY RowNumber ASC;
end

But as always i am having trouble :). Main problem is the UNION ALL in my query. It prevents me using ROW_NUMBER() OVER.

Any ideas?


Solution

  • Here's your procedure updated for paging:

    CREATE PROCEDURE [dbo].[Paging]
      @subdomain varchar(50),
      @un varchar(50),
      @PageNumber int,
      @PageSize int
    AS
    
      DECLARE @start_row int
      DECLARE @end_row int
    
      SET @end_row = @PageNumber * @PageSize
      SET @start_row = @end_row - (@PageSize - 1)
    
    BEGIN
    
      WITH FRIENDS AS (
        SELECT t.friend
          FROM FRIEND_BLOCKED_LIST t
         WHERE t.un = @un 
           AND t.subdomain = @subdomain 
           AND t.approved = 1 
           AND t.is_blocked = 0
       UNION ALL
        SELECT t.un as friend
          FROM FRIEND_BLOCKED_LIST t
         WHERE t.friend = @un 
           AND t.subdomain = @subdomain 
           AND t.approved = 1 
           AND t.is_blocked = 0)
    SELECT t.friend
      FROM (SELECT f.friend,
                   ROW_NUMBER() OVER (ORDER BY f.friend) AS rownum
              FROM FRIENDS f
          GROUP BY f.friend) t
     WHERE t.rownum BETWEEN @start_row AND @end_row
    
    END
    

    It might be possible to change the query to use one CTE if you could provide more information on the FRIEND_BLOCKED_LIST table. The UNION of two queries with identical WHERE clauses while differentiating between two columns makes me wonder if it couldn't be written better. Could the FRIENDS CTE be rewritten as:

    SELECT COALESCE(t.un, t.friend) as friend
      FROM FRIEND_BLOCKED_LIST t
     WHERE @un = COALESCE(t.un, t.friend)
       AND t.subdomain = @subdomain 
       AND t.approved = 1 
       AND t.is_blocked = 0