Как получить количество строк в cte в отдельном наборе данных? - PullRequest
6 голосов
/ 24 мая 2011

Я определил способ получения быстрых постраничных результатов из базы данных с использованием CTE и функции Row_Number следующим образом ...

DECLARE @PageSize INT = 1
DECLARE @PageNumber INT = 2

DECLARE @Customer TABLE (
  ID       INT IDENTITY(1, 1),
  Name     VARCHAR(10),
  age      INT,
  employed BIT)

INSERT INTO @Customer
    (name,age,employed)
SELECT 'bob',21,1
    UNION ALL
SELECT 'fred',33,1
    UNION ALL
SELECT 'joe',29,1
    UNION ALL
SELECT 'sam',16,1
    UNION ALL
SELECT 'arthur',17,0;


WITH cteCustomers
     AS ( SELECT
            id,
            Row_Number( ) OVER(ORDER BY Age DESC) AS Row
          FROM   @Customer
          WHERE  employed = 1 
     /*Imagine I've joined to loads more tables with a really complex where clause*/
     )       

SELECT
  name,
  age,
  Total = ( SELECT
              Count( id )
            FROM   cteCustomers )
FROM       cteCustomers
INNER JOIN @Customer cust
  /*This is where I choose the columns I want to read, it returns really fast!*/
  ON cust.id = cteCustomers.id
WHERE      row BETWEEN ( @PageSize * @PageNumber - 1 ) AND ( @PageSize * ( @PageNumber ) )
ORDER      BY row ASC

При использовании этой техники возвращаемые результаты действительно очень быстрые даже для сложных объединений и фильтров.

Чтобы выполнить подкачку, мне нужно знать общее количество строк, возвращаемое полным CTE. Я "Bodged" это, поместив столбец, который содержит его

Total = ( SELECT
              Count( id )
            FROM   cteCustomers )

Есть ли лучший способ вернуть итоговое значение в другом наборе результатов, не помещая его в столбец? Поскольку это CTE, я не могу включить его во второй набор результатов.

Ответы [ 2 ]

5 голосов
/ 24 мая 2011

Не используя сначала временную таблицу, я бы использовал CROSS JOIN, чтобы уменьшить риск оценки строки за строкой в ​​COUNT

Чтобы получить общую строку, это должно происходить отдельно с WHERE

WITH cteCustomers
     AS ( SELECT
            id,
            Row_Number( ) OVER(ORDER BY Age DESC) AS Row
          FROM   @Customer
          WHERE  employed = 1 
     /*Imagine I've joined to loads more tables with a really complex where clause*/
     )       

SELECT
  name,
  age,
  Total
FROM       cteCustomers
INNER JOIN @Customer cust
  /*This is where I choose the columns I want to read, it returns really fast!*/
  ON cust.id = cteCustomers.id

CROSS JOIN

(SELECT Count( *) AS Total FROM   cteCustomers ) foo

WHERE      row BETWEEN ( @PageSize * @PageNumber - 1 ) AND ( @PageSize * ( @PageNumber ) )
ORDER      BY row ASC

Однако это не гарантирует точных результатов, как показано здесь:
можно ли получить count () и строки из одного SQL-запроса на SQL-сервере?

Редактировать: после нескольких комментариев.

Как избежать CROSS JOIN

WITH cteCustomers
     AS ( SELECT
            id,
            Row_Number( ) OVER(ORDER BY Age DESC) AS Row,
            COUNT(*) OVER () AS Total --the magic for this edit
          FROM   @Customer
          WHERE  employed = 1 
     /*Imagine I've joined to loads more tables with a really complex where clause*/
     )       

SELECT
  name,
  age,
  Total
FROM       cteCustomers
INNER JOIN @Customer cust
  /*This is where I choose the columns I want to read, it returns really fast!*/
  ON cust.id = cteCustomers.id
WHERE      row BETWEEN ( @PageSize * @PageNumber - 1 ) AND ( @PageSize * ( @PageNumber ) )
ORDER      BY row ASC

Примечание: YMMV для производительности в зависимости от 2005 или 2008, пакет обновления и т. Д.

Редактировать 2:

SQL Server Central показывает другой метод, в котором используется обратный ROW_NUMBER.Выглядит полезным

1 голос
/ 25 мая 2011

@ Digiguru

О боже, это действительно весь грааль!

WITH cteCustomers
AS ( SELECT id,
         Row_Number() OVER(ORDER BY Age DESC) AS Row,
         Row_Number() OVER(ORDER BY id ASC)
            + Row_Number() OVER(ORDER BY id DESC) - 1 AS Total /*<- voodoo here*/
      FROM   @Customer
      WHERE  employed = 1
      /*Imagine I've joined to loads more tables with a really complex where clause*/
   )
SELECT  name,  age,  Total
  /*This is where I choose the columns I want to read, it returns really fast!*/
FROM cteCustomers
INNER JOIN @Customer cust
ON cust.id = cteCustomers.id
WHERE row BETWEEN ( @PageSize * @PageNumber - 1 ) AND ( @PageSize * ( @PageNumber ) )
ORDER BY row ASC

Так очевидно сейчас.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...