Методы подкачки SQL Server 2008?

Мне приходится работать с потенциально большим списком записей, и я искал в Google способы избежать выбора всего списка, вместо этого я хочу позволить пользователям выбирать страницу (например, от 1 до 10) и соответственно отображать записи.

Скажем, для 1000 записей у меня будет 100 страниц по 10 записей в каждой, и сначала будут отображаться 10 последних записей, а затем, если пользователь щелкнет страницу 5, отобразятся записи с 41 по 50.

Хорошая идея - добавить номер строки к каждой записи, а затем запросить ее по номеру строки? Есть ли лучший способ достичь результата разбиения по страницам без излишних накладных расходов? Пока что описанные здесь методы выглядят наиболее многообещающими:

http://developer.berlios.de/docman/display_doc.php?docid=739&group_id=2899 < / а>

http://www.codeproject.com/KB/aspnet/PagingLarge.aspx


person fred    schedule 05.12.2010    source источник


Ответы (7)


Следующая хранимая процедура T-SQL является очень эффективной реализацией разбиения на страницы. Оптимизатор SQL может очень быстро найти первый идентификатор. Объедините это с использованием ROWCOUNT, и вы получите подход, который эффективен как для ЦП, так и для чтения. Для таблицы с большим количеством строк это, безусловно, превосходит любой подход, который я видел с использованием временной таблицы или табличной переменной.

NB: в этом примере я использую столбец с последовательной идентификацией, но код работает с любым столбцом, подходящим для сортировки страниц. Кроме того, разрывы последовательности в используемом столбце не влияют на результат, поскольку код выбирает количество строк, а не значение столбца.

РЕДАКТИРОВАТЬ: если вы сортируете столбец с потенциально неуникальными значениями (например, LastName), добавьте второй столбец в предложение Order By, чтобы снова сделать значения сортировки уникальными.

CREATE  PROCEDURE dbo.PagingTest
(
    @PageNumber int,
    @PageSize int
)
AS

DECLARE @FirstId int, @FirstRow int

SET @FirstRow = ( (@PageNumber - 1) * @PageSize ) + 1
SET ROWCOUNT @FirstRow

-- Add check here to ensure that @FirstRow is not
-- greater than the number of rows in the table.

SELECT   @FirstId = [Id]
FROM     dbo.TestTable
ORDER BY [Id]

SET ROWCOUNT @PageSize

SELECT   *
FROM     dbo.TestTable
WHERE    [Id] >= @FirstId
ORDER BY [Id]

SET ROWCOUNT 0
GO 
person HTTP 410    schedule 05.12.2010
comment
@ScottE: разрывы идентификаторов не имеют значения для этого кода, потому что верхний размер - это количество строк на странице (а не номер идентификатора). - person HTTP 410; 05.12.2010
comment
@ScottE: также, используемый столбец не обязательно должен быть инкрементным идентификатором. Вы можете использовать любой столбец, подходящий для сортировки. - person HTTP 410; 05.12.2010
comment
@Martin: согласен, TOP должен работать так же хорошо с любой версией SQL Server выше, чем 2000. Я не тестировал это, поэтому я просто использовал код, который я тестировал. - person HTTP 410; 05.12.2010
comment
Подумав об этом. Это все равно приведет к выполнению двух разных сканирований индекса, я бы подумал, что это будет немного менее эффективно, чем просто использовать row_count и делать все это за одно сканирование, поскольку ему придется перемещаться по корневой и промежуточным страницам индекса, чтобы найти свое место. при выполнении WHERE [Id] >= @FirstId bit. При этом предполагается, что оба сканирования будут выполняться по одному и тому же индексу. Я полагаю, если бы первое сканирование проводилось против чрезвычайно узкого индекса только по id, а второе - против покрывающего индекса с id в качестве ведущего поля, это могло бы работать лучше! - person Martin Smith; 05.12.2010
comment
@ Дэвид: SQL Server int. Вам не кажется, что 2 ^ 31-1 достаточно велико? - person HTTP 410; 08.12.2010
comment
Запоздалый ответ @RoadWarrior - проблема не в целочисленной точности; это с неуникальными ключами в многосерверной распределенной среде. Доставка журналов и т. Д. Позволяют обойти это, но это проблемы, которые можно легко исправить с помощью GUID. (Я еще не слышал фактических аргументов против этого, но я все слышу.) - person 3Dave; 30.05.2012
comment
@David, за 20 с лишним лет я никогда не использовал сегментирование, репликацию базы данных или любые другие методы распределенной базы данных. Кластер базы данных - это то, что я сделал, поэтому я всегда мог использовать столбцы идентификации. Я согласен с тем, что идентификационные данные - не идеальный подход в среде, где ваша база данных действительно должна быть распределена. - person HTTP 410; 30.05.2012
comment
@RoadWarrior и ints полностью приемлемы в этом случае. Я имею дело с географически распределенными базами данных, которые должны продолжать работать, когда выходит из строя толстый трансатлантический подводный трубопровод, поэтому гарантированная уникальность немного сложнее. - person 3Dave; 30.05.2012
comment
Я так понимаю, это работает, только если столбец, который вы заказываете, содержит уникальные значения? Что, если бы я хотел отсортировать по фамилии - тогда, когда я выбираю @FirstId (или, скорее, @FirstValue), я не могу сделать для него ›=, потому что тогда он будет включать некоторые строки, которых не должно быть, потому что их значениями lastname являются то же, что и значение lastname в @FirstRow. - person MajorRefactoring; 09.11.2012
comment
@MajorRefactoring, да, вы правы. Если вы используете столбец, содержащий неуникальные значения для сортировки страницы, то, если неуникальные значения охватывают границу страницы, вы увидите непредсказуемую сортировку на этой границе страницы. Чтобы исправить это, я рекомендую вам добавить второй столбец в предложение Order By. Пока комбинация столбцов имеет уникальное значение, это будет работать. - person HTTP 410; 10.11.2012
comment
@RoadWarrior - ах. Умная. Хорошая маленькая техника. - person MajorRefactoring; 12.11.2012
comment
Возможно, вас заинтересует метод поиска, который я описал в своем ответе. Это более общий подход к использованию предикатов (и, следовательно, индексации) над смещениями для разбиения на страницы. - person Lukas Eder; 26.10.2013
comment
Привет, а если у вас где условия? - person ; 01.08.2018

Если вы используете CTE с двумя столбцами row_number () - один отсортированный по возрастанию и один по убыванию, вы получите номера строк для разбиения по страницам, а также общее количество записей, добавив два столбца row_number.

create procedure get_pages(@page_number int, @page_length int)
as
    set nocount on;

    with cte as
    (
        select 
            Row_Number() over (order by sort_column desc) as row_num
            ,Row_Number() over (order by sort_column) as inverse_row_num
            ,id as cte_id
        From my_table
    )
    Select 
        row_num+inverse_row_num as total_rows
        ,*  
    from CTE inner join my_table
        on cte_id=df_messages.id
    where row_num between 
        (@page_number)*@page_length 
        and (@page_number+1)*@page_length
    order by rownumber
person 3Dave    schedule 05.12.2010
comment
Примечание об обратной сортировке: если в столбце есть неуникальные данные, то есть вероятность, что сортировка в предложении OVER будет несовместимой. - person Tyler Clendenin; 17.09.2014
comment
Кроме того, вы можете использовать Count (*) OVER () как total_row_count, который будет возвращать столбец с одним и тем же номером снова и снова. - person Tyler Clendenin; 17.09.2014

Использование смещения

Другие объяснили, как функцию ранжирования ROW_NUMBER() OVER() можно использовать для выполнения страниц. Стоит отметить, что SQL Server 2012 наконец-то включил поддержку стандарта SQL _2 _ пункт:

SELECT first_name, last_name, score
FROM players
ORDER BY score DESC
OFFSET 40 ROWS FETCH NEXT 10 ROWS ONLY

Если вы используете SQL Server 2012 и обратная совместимость не является проблемой, вам, вероятно, следует предпочесть этот пункт, поскольку он будет выполняться SQL Server более оптимально в крайних случаях.

Использование метода SEEK

Существует совершенно другой, гораздо более быстрый способ выполнения подкачки в SQL. Это часто называют «методом поиска», как описано в это сообщение в блоге здесь.

SELECT TOP 10 first_name, last_name, score
FROM players
WHERE (score < @previousScore)
   OR (score = @previousScore AND player_id < @previousPlayerId)
ORDER BY score DESC, player_id DESC

Значения @previousScore и @previousPlayerId являются соответствующими значениями последней записи с предыдущей страницы. Это позволяет вам перейти на «следующую» страницу. Если ORDER BY направление ASC, просто используйте вместо этого >.

С помощью описанного выше метода вы не можете сразу перейти на страницу 4, не загрузив сначала предыдущие 40 записей. Но зачастую вы все равно не хотите прыгать так далеко. Вместо этого вы получаете гораздо более быстрый запрос, который может извлекать данные за постоянное время, в зависимости от вашей индексации. Кроме того, ваши страницы остаются «стабильными» независимо от того, изменяются ли базовые данные (например, на странице 1, пока вы находитесь на странице 4).

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

Обратите внимание, что «метод поиска» также называется разбиением по набору клавиш.

person Lukas Eder    schedule 26.10.2013
comment
Хотя можно привести веские доводы в пользу того, что пользовательский интерфейс поиска с переходом на страницу в любом случае не является отличным дизайном, я думаю, вам действительно нужно показать своим пользователям, сколько результатов вернул их поиск (или, по крайней мере, приблизительное значение, как это делает Google, хотя я не уверен, что какие-либо базы данных SQL имеют такую ​​функциональность). Таким образом, для этого метода требуется дополнительный запрос подсчета, что значительно увеличивает его стоимость. - person EricS; 19.08.2014
comment
Это может быть вещь YMMV, но я провел сравнение между этим методом поиска (на sql server 2008, увы) и методом святого Грааля из статьи sqlservercentral, который дает вам счет, и оба работают примерно одинаково. Однако добавление запроса на счетчик к методу поиска сделало запрос Грааля явным победителем. - person EricS; 19.08.2014

Попробуйте что-то вроде этого:

declare @page int = 2
declare @size int = 10

declare @lower int =  (@page - 1) * @size
declare @upper int =  (@page    ) * @size

select * from (
select 
    ROW_NUMBER() over (order by some_column) lfd,
* from your_table
) as t
 where lfd between @lower and @upper
 order by some_column
person bernd_k    schedule 05.12.2010
comment
Известно, что ROW_NUMBER имеет проблемы с производительностью при очень больших наборах результатов: 4guysfromrolla.com/webtech/042606 -1.shtml - person HTTP 410; 06.12.2010
comment
не должно ли это быть 'declare @lower int = (@page * @size) - (@size - 1)' - person Reid Evans; 21.12.2012
comment
@RoadWarrior: Интересная ссылка. Я добавил его в свой недавний сообщение в блоге о более быстром перелистывании страниц. Знаете ли вы о методе поиска? Он не допускает истинных индексированных смещений, но может переходить на следующую страницу за постоянное время. - person Lukas Eder; 26.10.2013
comment
При тестировании @ReidEvans верен, он должен быть (@page * @size) - (@size - 1). (@page - 1) * @size возвращает строки 1-10, затем 10-20 вместо 11-20, как ожидалось. - person Rentering.com; 09.11.2015

Вот обновленная версия кода @ RoadWarrior с использованием TOP. Производительность такая же, и очень быстрая. Убедитесь, что у вас есть индекс на TestTable.ID

CREATE PROC dbo.PagingTest
    @SkipRows int,
    @GetRows int
AS
DECLARE @FirstId int

SELECT   TOP (@SkipRows) 
         @FirstId = [Id]
FROM     dbo.TestTable
ORDER BY [Id]

SELECT   TOP (@GetRows) *
FROM     dbo.TestTable
WHERE    [Id] >= @FirstId
ORDER BY [Id]

GO 
person Lane    schedule 03.08.2013

Попробуй это

Declare @RowStart int, @RowEnd int;


SET @RowStart = 4;
SET @RowEnd = 7; 

With MessageEntities As 
(
    Select ROW_NUMBER() Over (Order By [MESSAGE_ID]) As Row, [MESSAGE_ID]
    From [TBL_NAFETHAH_MESSAGES]
)
Select  m0.MESSAGE_ID, m0.MESSAGE_SENDER_NAME,
        m0.MESSAGE_SUBJECT, m0.MESSAGE_TEXT
From MessageEntities M
    Inner Join [TBL_NAFETHAH_MESSAGES] m0 on M.MESSAGE_ID = m0.MESSAGE_ID
Where M.Row Between @RowStart AND @RowEnd
Order By M.Row Asc
GO
person Aalkhodiry    schedule 25.03.2013

Почему бы не использовать рекомендованный решение:

ВЫБЕРИТЕ СТОИМОСТЬ продукта ИЗ AdventureWorksEntities.Products КАК продукт упорядочить по продукту.ListPrice ПРОПУСТИТЬ @ skip LIMIT @limit

person Vladimir Sosnin    schedule 23.08.2015