Отримання загальної кількості рядків з OFFSET / FETCH NEXT


92

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

Раніше я отримував весь набір записів і міг програмувати на цьому сторінку підкачки. Але використовуючи спосіб SQL із ТІЛЬКИ FETCH NEXT X ROWS, мені повертають лише 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 записів ПІДЧИСЛЕННЯ (*) НАД () зайняло 1 хвилину 3 секунди. Описаний нижче підхід Джеймсом Мобергом зайняв 13 секунд для отримання того самого набору даних. Я впевнений, що підхід Count Over чудово працює для менших наборів даних, але коли ви починаєте отримувати справді великий розмір, він значно сповільнюється.
matthew_360

Або ви можете просто використати COUNT (1) OVER (), що швидше пришвидшується, оскільки йому не потрібно читати фактичні дані з таблиці, як це робить count (*)
ldx

1
@AaronBertrand Справді? це повинно означати, що у вас або є індекс, який включає всі стовпці, або що це було значно покращено з 2008R2. У цій версії підрахунок (*) працює послідовно, це означає, що спочатку виділяється * (як у: всі стовпці), а потім підраховується. Якщо ви зробили підрахунок (1), ви просто вибираєте константу, що набагато швидше, ніж читання фактичних даних.
ldx

5
@idx Ні, це теж не спрацювало у 2008 R2, вибачте. Я використовую SQL Server з 6.5, і я не згадую часів, коли механізм був недостатньо розумним, щоб просто сканувати найвужчий індекс як для COUNT (*), так і для COUNT (1). Звичайно, не з 2000 року. Але, привіт, у мене є екземпляр 2008 R2, чи можете ви встановити повторне видання на SQLfiddle, яке демонструє цю різницю, про яку ви стверджуєте, що існує? Я радий спробувати.
Аарон Бертран,

2
на базі даних SQL Server 2016, пошук за таблицею з приблизно 25 мільйонами рядків, підкачками близько 3000 результатів (з кількома об’єднаннями, включаючи функцію, що має значення таблиці), це зайняло мілісекунди - чудово!
jkerak

140

Я зіткнувся з деякими проблемами з продуктивністю, використовуючи метод 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 (*) у змінну. Я міг би встановити його як параметр ВИХІД моєї збереженої процедури. Будь-які ідеї?
До Ка

1
Чи є спосіб отримати рахунок в окремій таблиці? Здається, ви можете використовувати "TempResult" лише для першого попереднього оператора SELECT.
matthew_360

4
Чому це працює так добре? У першому CTE всі рядки виділяються, а потім вибираються. Я б здогадався, що виділення всього рядка в першій КТЕ суттєво сповільнить ситуацію. У будь-якому випадку, дякую за це!
jbd

1
у моєму випадку це сповільнилося, ніж COUNT (1) OVER () .. можливо, тому, що функція в select.
Тіджу Джон

1
Це ідеально підходить для невеликої бази даних, коли рядки мільйонні, це займає занадто багато часу.
Кія,

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.