Постраничный вывод (> 1 000 000 записей)

deleted_2ks3ax
Дата: 19.02.2010 09:39:51
Категорически приветствую! Картинка с другого сайта.

Всегда пользовался практически неизменным паттерном для постраничного вывода, но вот встала задача выводить данные из таблицы в которой при определенных условиях может оказаться огромное количество записей, вот тут то и затык. Ниже код:

create procedure getDataByPage
    @pageNumber int,
    @itemsPerPage int,
    @field1 char(16) = null,
    @field2 char(16) = null,
    @field3 char(16) = null,
    @totalCount int  = null output
as
begin
    declare @startRow int;
    set @startRow = itemsPerPage * (pageNumber - 1);
    
    declare @matchedData (Id int primary key, rowNumber int);

    set nocount off;

    insert into @matchedData(Id, rowNumber)
    select top(@itemsPerPage * @pageNumber)
           d.id,
           row_number() over (order by d.id desc) as rowNumber
    from   data d
    where (@field1 is null or field1 = @field1) 
    and   (@field2 is null or field2 = @field2) 
    and   (@field3 is null or field3 = @field3) 
    order  by p.id desc

    -- Общее количество найденных записей
    set @totalCount = @@rowcount;
    set nocount on;

    select d.id, d.field1, d.field2, d.field3, t1.something1, t2.something2
    from   data d 
    join   table1 t1 on t1.field1 = d.field1
    join   table1 t2 on t2.field2 = d.field2
    where  d.id in (select top(@itemsPerPage) id from @matchedData where rowNumber > @startRow)
end

Клиенту нужно отдавать общее количество записей попавших под фильтр, раньше, когда ранжировалось относительно маленькое количество записей в первой помеченной строчке не было TOP, и все работалос правильно, а сейчас получается так что в @totalCount записывается просто @ItemsPerPage * @pageNumber. т.е. нужно делать отдельный select @totalCount = count(*), а это не так быстро. Может быть есть какое-нибудь «элегантное» решение, кроме как оптимизировать выборку количества, чтобы она летала?
С одной стороны я понимаю, что задача «высосана из пальца» и на самом деле вряд-ли кто-то полезет на 20546 страницу, но... интересно услышать ваши мысли Картинка с другого сайта.

----
Think twice before you press F5.
Паганель
Дата: 19.02.2010 10:06:01
Постраничная выборка
Там еще в комментариях есть ссылка на результаты замеров скорости, проведенных одним добрым человеком
iap
Дата: 19.02.2010 10:09:41
Cтесняюсь спросить, а зачем там SET NOCOUNT OFF?
iap
Дата: 19.02.2010 10:12:02
Ap0k
т.е. нужно делать отдельный select @totalCount = count(*), а это не так быстро.
А COUNT(*)OVER() в SELECTе не катит?
Glory
Дата: 19.02.2010 10:18:23
Ap0k


Клиенту нужно отдавать общее количество записей попавших под фильтр, р

Пишите случайное число больше миллиона. Вряд ли найдется сумашедший, который возьмется это проверять.
aleks2
Дата: 19.02.2010 11:13:11
Ap0k,

Три условия, различных значений в field1, field2, field3 тоже ограниченное число - можно посчитать раз и нафсегда все варианты фильтра и поддерживать триггером
deleted_2ks3ax
Дата: 19.02.2010 12:06:05
Паганель
Постраничная выборка
Там еще в комментариях есть ссылка на результаты замеров скорости, проведенных одним добрым человеком

Спасибо за ссылку, изучаю.

iap
Cтесняюсь спросить, а зачем там SET NOCOUNT OFF?

Это рудимент такой, в принципе он сейчас неактуален, но в шаблоне есть.

iap
А COUNT(*)OVER() в SELECTе не катит?

Не совсем понял идею, что мне это даст при наличии TOP?

aleks2, хардкор, однако Картинка с другого сайта.
iap
Дата: 19.02.2010 12:15:41
Ap0k
Паганель
Постраничная выборка
Там еще в комментариях есть ссылка на результаты замеров скорости, проведенных одним добрым человеком

Спасибо за ссылку, изучаю.

iap
Cтесняюсь спросить, а зачем там SET NOCOUNT OFF?

Это рудимент такой, в принципе он сейчас неактуален, но в шаблоне есть.

iap
А COUNT(*)OVER() в SELECTе не катит?

Не совсем понял идею, что мне это даст при наличии TOP?

aleks2, хардкор, однако Картинка с другого сайта.
Вообще-то, TOP применяется в последнюю очередь...
Легко же проверить.
deleted_2ks3ax
Дата: 19.02.2010 13:07:42
Из всех вариантов остановился на том что вынес выборку количества в отдельный запрос и соответственно «потюнинговал» индексы + убрал row_number () over (order) и сделал колонку identity(1,1) в табличной переменной. По скорости теперь устраивает. 1000-ая страница (по 20 элементов на стр.) выбирается около 300мс. при @TotalCount = 700000 строк.
PS: COUNT(*) OVER () мне и серверу показался неоптимальным, сильно вдаваться в подробности не стал.

----
Think twice before you press F5.