Получение общего количества строк из OFFSET / FETCH NEXT

92

Итак, у меня есть функция, которая возвращает ряд записей, для которых я хочу реализовать разбиение на страницы на моем веб-сайте. Мне было предложено использовать для этого функцию Offset / Fetch Next в SQL Server 2012. На нашем веб-сайте есть область, в которой указано общее количество записей и информация о том, на какой странице вы находитесь в данный момент.

Раньше я получал весь набор записей и мог программно построить на нем подкачку. Но при использовании способа SQL с FETCH NEXT X ROWS ONLY мне возвращаются только X строк, поэтому я не знаю, каков мой общий набор записей и как рассчитать мои минимальные и максимальные страницы. Единственный способ, которым я могу это сделать, - это дважды вызвать функцию и произвести подсчет строк в первой, а затем запустить вторую с помощью FETCH NEXT. Есть ли лучший способ, чтобы я не выполнял запрос дважды? Я пытаюсь увеличить производительность, а не замедлить ее.

КристаллСиний
источник

Ответы:

115

Вы можете использовать COUNT(*) OVER()... вот простой пример sys.all_objects:

DECLARE 
  @PageSize INT = 10, 
  @PageNum  INT = 1;

SELECT 
  name, object_id, 
  overall_count = COUNT(*) OVER()
FROM sys.all_objects
ORDER BY name
  OFFSET (@PageNum-1)*@PageSize ROWS
  FETCH NEXT @PageSize ROWS ONLY;

Однако это следует зарезервировать для небольших наборов данных; на больших наборах производительность может быть ужасной. См. Эту статью Пола Уайта, чтобы узнать о лучших альтернативах , включая поддержку индексированных представлений (которые работают только в том случае, если результат нефильтрован или вы WHEREзаранее знаете предложения) и использование ROW_NUMBER()уловок.

Аарон Бертран
источник
44
В таблице с 3 500 000 записей COUNT (*) OVER () занял 1 минуту 3 секунды. Подход, описанный ниже Джеймсом Мобергом, занял 13 секунд для получения того же набора данных. Я уверен, что подход Count Over отлично работает для небольших наборов данных, но когда вы начинаете становиться действительно большими, он значительно замедляется.
matthew_360
Или вы можете просто использовать COUNT (1) OVER (), что чертовски быстрее, так как ему не нужно читать фактические данные из таблицы, как это делает count (*)
ldx
1
@AaronBertrand Правда? это должно означать, что либо у вас есть индекс, включающий все столбцы, либо он был значительно улучшен с 2008 R2. В этой версии счетчик (*) работает последовательно, что означает, что сначала выбирается * (как в: все столбцы), а затем производится подсчет. Если вы выполнили count (1), вы просто выбираете константу, что намного быстрее, чем чтение фактических данных.
ldx
5
@idx Нет, извините, это тоже не то, как это работало в 2008 R2. Я использую SQL Server с 6.5, и я не припомню того времени, когда движок не был достаточно умен, чтобы просто сканировать самый узкий индекс для COUNT (*) или COUNT (1). Конечно, не с 2000 года. Но у меня есть экземпляр 2008 R2, можете ли вы настроить репро на SQLfiddle, которое демонстрирует, что разница, по вашему мнению, существует? Я рада попробовать.
Аарон Бертран,
2
в базе данных sql server 2016, поиск в таблице с примерно 25 миллионами строк, разбиение на страницы около 3000 результатов (с несколькими соединениями, включая функцию с табличным значением), это заняло миллисекунды - потрясающе!
jkerak
142

Я столкнулся с некоторыми проблемами производительности при использовании метода COUNT ( ) OVER (). (Я не уверен, что это был сервер, поскольку для возврата 10 записей потребовалось 40 секунд, а затем не возникло никаких проблем.) Этот метод работал во всех условиях без использования COUNT ( ) OVER () и выполняет то же самое:

DECLARE 
    @PageSize INT = 10, 
    @PageNum  INT = 1;

WITH TempResult AS(
    SELECT ID, Name
    FROM Table
), TempCount AS (
    SELECT COUNT(*) AS MaxRows FROM TempResult
)
SELECT *
FROM TempResult, TempCount
ORDER BY TempResult.Name
    OFFSET (@PageNum-1)*@PageSize ROWS
    FETCH NEXT @PageSize ROWS ONLY
Джеймс Моберг
источник
32
Было бы здорово, если бы была возможность сохранить значение COUNT (*) в переменной. Я мог бы установить его как параметр ВЫХОДА моей хранимой процедуры. Любые идеи?
To Ka
1
Есть ли способ получить счет в отдельной таблице? Похоже, вы можете использовать «TempResult» только для первого предыдущего оператора SELECT.
matthew_360
4
Почему это так хорошо работает? В первом CTE все строки выбираются, а затем сокращаются путем выборки. Я бы предположил, что выделение всей строки в первом CTE значительно замедлит работу. В любом случае спасибо за это!
jbd
1
в моем случае это замедлилось, чем COUNT (1) OVER () .. возможно, из-за функции в select.
Tiju John
1
Это идеально подходит для небольшой базы данных, когда миллионы строк занимают слишком много времени.
Kiya
1

На основании ответа Джеймса Моберга :

Это альтернатива использования Row_Number(), если у вас нет SQL Server 2012 и вы не можете использовать OFFSET

DECLARE 
    @PageNumEnd INT = 10, 
    @PageNum  INT = 1;

WITH TempResult AS(
    SELECT ID, NAME
    FROM Tabla
), TempCount AS (
    SELECT COUNT(*) AS MaxRows FROM TempResult
)

select * 
from
(
    SELECT
     ROW_NUMBER() OVER ( ORDER BY PolizaId DESC) AS 'NumeroRenglon', 
     MaxRows, 
     ID,
     Name
    FROM TempResult, TempCount

)resultados
WHERE   NumeroRenglon >= @PageNum
    AND NumeroRenglon <= @PageNumEnd
ORDER BY NumeroRenglon
elblogdelbeto
источник