powered by simpleCommunicator - 2.0.60     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / Microsoft SQL Server [игнор отключен] [закрыт для гостей] / FAQ.Постраничная выборка
20 сообщений из 20, страница 1 из 1
FAQ.Постраничная выборка
    #32018830
Glory
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Вопрос порционной(постраничной) выборки тесно связан с вопросом нумерации строк в запросе (см. FAQ.Нумерация записей в запросе ). Другими словами, для того, чтобы выбрать N-ую порцию из результатов запроса, нужно сначала пронумеровать результаты этого запроса.

Отсюда и похожие методы решения

Вариант 1 «Классический».

SELECT TOP 100 * FROM MyTable
WHERE id NOT IN (SELECT TOP 100 id FROM MyTable ORDER BY id) ORDER BY id


Главный недостаток этого метода в наличии подзапроса, который будет выполняться для каждой строки главного запроса. Ну и конечно все условия выборки "WHERE" и сортировки "ORDER" в подзапросе и основном запросе должны совпадать.

Достоинство метода в его универсальности, академичности. Он не требует специфики T-SQL, этот метод можно применить практически на любом SQL-сервере.


Вариант 2 «Эффективный, специфический для T-SQL».

Как и в случае нумерации строк данный метод основан на использовании временной таблицы. Для удобства оформим наш запрос как хранимую процедуру, возвращающую n-ую порцию(страницу), содержащую m записей

CREATE PROCEDURE dbo.get_this_page (@rec_per_page int, @page_num int) AS

SELECT identity(int, 1,1) AS RowNum, MyId AS OrigId INTO #tmp FROM mytable
SELECT b.* FROM #tmp AS a
INNER JOIN mytable AS b on a.OrigId = b.MyId
WHERE a.RowNum BETWEEN (@rec_per_page * @page_num) AND (@rec_per_page * (@page_num+1) - 1)

DROP TABLE #tmp

Прмечания.
- предложенный вариант процедуры будет блокировать базу tempdb на все время выполнения 1-го запроса. Если время блокировки становиться неприемлимым, то необходимо разбить этот запрос таким образом
CREATE TABLE #temp(RowNum int identity, OrigId int)
INSERT INTO #temp(OrigId) SELECT MyId FROM mytable

- Если, поле MyId было создано признаком «IDENTITY», то это поле в запросе необходимо «завернуть» в функцию «CONVERT», иначе будет сообщение об ошибке.


PS
2Глеб Уфимцев
Ну как на этот раз у меня получилось - лучше ?
...
Рейтинг: 0 / 0
FAQ.Постраничная выборка
    #32018835
Фотография Александр Гладченко
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Предложение для Glory и Глеба Уфимцева: дабы расширить круг участвующих в обсуждении народного SQL FAQ людей, предлагаю размещать ваши предложения в рассылке. Если даёте "добро", прямо сегодня это и сделаю.
...
Рейтинг: 0 / 0
FAQ.Постраничная выборка
    #32018836
Pandre
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Больше всего меня удивляет (в век интернета) отсутствие у Микрософта сдандартных средств для этих целей !
...
Рейтинг: 0 / 0
FAQ.Постраничная выборка
    #32018842
2Glory
> Ну как на этот раз у меня получилось - лучше ?

Твои советы всегда отличные, за редчайшим исключением.

> предложенный вариант процедуры будет блокировать базу tempdb на все время выполнения 1-го запроса.

Такая проблемка была только на sql6.5 (видимо, из-за отсутствия строчных блокировок), и то больше шума и криков, чем это заслуживало бы, так как это превращалось в проблему достаточно редко. Проблема непомерно раздута и надумана. В пользу этого утверждения говорит то, что первая статья об этой проблеме появилась незадолго перед появлением sql7.0, т.е. после многолетней эксплуатации sql4.2/6.0/6.5, и никто этой проблемы не замечал.
Поэтому переделывать select into в create&insert нужно, только если есть 100% уверенность, что затык в этом. Во всех остальных случаях можно применять смело select into без всяких оглядок.
Чтобы не быть голословным, специально только что перепроверил это на sql7.0sp2. Не блокируется.
...
Рейтинг: 0 / 0
FAQ.Постраничная выборка
    #32018982
Dwarf
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Мое почтение, Glory Сталкивались с вами на activeserverpages.ru.

Небольшая поправка. Поскольку seed у identity задан 1, то:
WHERE a.RowNum BETWEEN (@rec_per_page * @page_num + 1) AND (@rec_per_page * (@page_num + 1))
...
Рейтинг: 0 / 0
FAQ.Постраничная выборка
    #32018984
Glory
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Как же - помню, помню
. И спасибо за поправочку


И сам себя поправлю
Фраза "Главный недостаток этого метода в наличии подзапроса, который будет выполняться для каждой строки главного запроса." неправильна. Подзапрос конечно будет выполняться один раз. Недостаток в том, что т.к. TOP n записей выбираются уже из конечного результата запроса , то проверка условия WHERE будет выполняться для каждой строки главного запроса. При этом, время выполнения этой проверки будет расти вместе с номером выбираемой порции(страницы). Если, например, таблица содержит 100 записей и необходимо выбирать данные порциями по 10 записей, то
для 2-ой порции нужно будет будет проверять подзапрос из 10 записей
для 3-ей порции нужно будет будет проверять подзапрос из 20 записей
для 4-ой порции нужно будет будет проверять подзапрос из 30 записей и т.д.
...
Рейтинг: 0 / 0
FAQ.Постраничная выборка
    #32019021
Фотография Слон
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Я обычно использую следующий вид запроса для выдачи записей из середины таблицы.
Например записи 401-500

SELECT [col1], ..., [coln] FROM
(
SELECT TOP 100 [col1], ..., [coln] FROM
(
SELECT TOP 500 [col1], ..., [coln] FROM [table] ORDER BY [key1] ASC, ..., [keyn] ASC
) x ORDER BY [key1] DESC, ..., [keyn] DESC
) x ORDER BY [key1] ASC, ..., [keyn] ASC

То есть вначале берутся все 500 записей, затем выборка как бы ставится с ног на голову, а потом с перевернутой выборки берется 100 записей сверху, третий шаг - пересортировка записей в первоначальном порядке.
...
Рейтинг: 0 / 0
FAQ.Постраничная выборка
    #32027769
Фотография Александр Гладченко
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Время идёт, поправок и дополнений к этому FAQ нет... Может быть пора опубликовать в рассылке и на сайте?
Я готов сделать редакторскую работу и после получения согласия Glory опубликовать в виде статьи...
...
Рейтинг: 0 / 0
FAQ.Постраничная выборка
    #32027792
V. Motchulsky
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Как-то однажды с утра я зашел в книжный магазин. Смотрю на полке стоит двадцаток книг – выстроены по алфавиту. Решил их купить. Но портфель маленький – влезает только 10 штучек. Думаю, куплю десяток, а потом еще зайду. Продавец со склада вынес 10 книг точно таких как на полке, и я ушел. Захожу после обеда еще за 10-ью. Говорю продавцу – мне с 11 по 20-ую. Продавец снова вынес мне книги со склада и я ушел. Прихожу домой смотрю принес три книги таких же как и с утра. А на полке то были все разные… Иду ругаться в магазин. Оказывается напрасно - в обед завезли три книжки на букву «А».
Наученный горьким опытом, в следующий раз я решил поступить по-другому. Зашел в магазин и говорю - отложите мне все 20-ть книг – 10 я сейчас заберу, а 10 потом. Так и сделал. Но когда я нес домой второй десяток, сосед меня спросил – ты зачем купил старое издание, там в магазин в обеденный перерыв завезли новое - исправленное и дополненное.
И решил я на счет книги больше не покупать…

Мораль. Кто-то с мудрецов сказал: «Нельзя войти дважды в одну и ту же реку.». Тоже касается оператора Select. Дважды выполнив один и тот же оператор – можно получить разные результаты.
...
Рейтинг: 0 / 0
FAQ.Постраничная выборка
    #32027793
Фотография Cat2
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Ну хоть кто-нибудь, напишите, зачем это надо?
Порядковые номера, выборка с 2000 по 3000, первые 20 по алфавиту?
Извините я написал кучу прог и баз, но мне НИ РАЗУ не понадобилось вся эта тряхомудия.
...
Рейтинг: 0 / 0
FAQ.Постраничная выборка
    #32027799
sysop
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
2 CAT2
А ты не обращал внимание на циферки "1 2 3 4 5 6 7 8 9 10 .." внизу страницы этом форуме?
...
Рейтинг: 0 / 0
FAQ.Постраничная выборка
    #32027800
Фотография Cat2
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
В данном случае выборку я бы курсором сделал, а циферок навыводил бы
for i=1 to ЧИСЛО_ЗАПИСЕЙ/ЗАПИСЕЙ_НА_СТРАНИЦЕ и т.д.

Все равно на задние страницы редко кто заходит.
Еще варианты?
...
Рейтинг: 0 / 0
FAQ.Постраничная выборка
    #32027806
Фотография Слон
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Курсор - дело конечно самое простое, но и самое ресурсоемкое. Поэтому к нему прибегают только в самых крайних случаях, когда нельзя ничего сделать при помощи set-based операций. Хотя наименее опытные програмисты на первых порах все делают при его помощи. Насчет того, что на задние страницы никому не нужны - глубокое заблуждение. Если проектируешь серьезную систему, не стоит ее проектировать исходя из того, что что-то не будет использоваться. Клиент может как раз больше всего хотеть видеть именно заднюю страницу. А насчет иных вариантов - так вот вначале они были и перечислены.

Слон
...
Рейтинг: 0 / 0
FAQ.Постраничная выборка
    #32027833
Glory
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
2Александр Гладченко
Да, конечно, если никто больше ничего не хочет добаваить, то пожалуйста.

Кстати была еще одна тема с именем FAQ - Про динамический запрос
...
Рейтинг: 0 / 0
FAQ.Постраничная выборка
    #32027838
Фотография ВладимирМ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Я могу добавить.

Для очень частного случая, когда существует кластерный индекс и требуется отобрать записи именно в порядке возрастания значений этого кластерного индекса можно использовать следующую команду:

SELECT * FROM MyTable WHERE IDENTITYCOL between 101 and 200

Но, повторюсь. В данном случае, под "порядковым" номером строки будет пониматься именно положение строки в соответсвии с кластерным индексом. Никакие ORDER BY на порядок нумерации не повлияют.
...
Рейтинг: 0 / 0
FAQ.Постраничная выборка
    #32027844
Glory
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
2ВладимирМ

На самом деле может оказаться, что из-за "дырок" в значениях IDENTITYCOL запрос вернет записей меньше заданной порции (или вернет вообще пустой набор)
...
Рейтинг: 0 / 0
FAQ.Постраничная выборка
    #32027861
Фотография ВладимирМ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Да, поторопился
Очень уж частный случай у меня получился. Извиняюсь.

Правда у меня есть замечание по 2 способу.

Я не вижу смысла делать временную таблицу #tmp по ВСЕЙ таблице MyTable. Ведь нужно всего-лишь отсечь первые N записей. Почему бы не записать во временную таблицу TOP N, а при выполнении запроса из осносновной таблицы не сделать тот же TOP 100 ... NOT IN (SELECT ... FROM #tmp)

Т.е. фактически повторить первый вариант, но разбив его на 2 независимых запроса.

Ну, или в крайнем случае во временную таблицу записать TOP N+100, а далее уже как у Вас
...
Рейтинг: 0 / 0
FAQ.Постраничная выборка
    #32027868
lizard
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
===================================================
Это из MSDN Замените тип рекордсета на серверный и юзайте на здоровье
===================================================
AbsolutePage, PageCount, and PageSize Properties Example (VB)
[This is preliminary documentation and subject to change.]
'BeginAbsolutePageVB

'To integrate this code
'replace the data source and initial catalog values
'in the connection string

Public Sub AbsolutePageX()

'recordset and connection variables
Dim rstEmployees As ADODB.Recordset
Dim Cnxn As ADODB.Connection
Dim strCnxn As String
Dim strSQL As String
'record variables
Dim strMessage As String
Dim intPage As Integer
Dim intPageCount As Integer
Dim intRecord As Integer

'Open connection
Set Cnxn = New ADODB.Connection
strCnxn = "Provider=sqloledb;Data Source=MyServer;Initial Catalog=Pubs;User Id=sa;Password=;"
Cnxn.Open strCnxn

' Open employee recordset
' Use client cursor to enable AbsolutePosition property
Set rstEmployees = New ADODB.Recordset
strSQL = "employee"
rstEmployees.Open strSQL, strCnxn, adUseClient, adLockReadOnly, adCmdTable

' Display names and hire dates, five records at a time
rstEmployees.PageSize = 5
intPageCount = rstEmployees.PageCount
For intPage = 1 To intPageCount
rstEmployees.AbsolutePage = intPage
strMessage = ""
For intRecord = 1 To rstEmployees.PageSize
strMessage = strMessage & _
rstEmployees!fname & " " & _
rstEmployees!lname & " " & _
rstEmployees!hire_date & vbCr
rstEmployees.MoveNext
If rstEmployees.EOF Then Exit For
Next intRecord
MsgBox strMessage
Next intPage

' clean up
rstEmployees.Close
Cnxn.Close
Set rstEmployees = Nothing
Set Cnxn = Nothing
End Sub
'EndAbsolutePageVB

See Also
AbsolutePage Property | PageCount Property | PageSize Property | Recordset Object
...
Рейтинг: 0 / 0
FAQ.Постраничная выборка
    #32027896
Фотография Cat2
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
a) Про серьезную систему. Вот именно, если проектировать серьезную систему, то надо сто раз подумать, а почему пользователь должен жать клавиши "Выбрать 100001 страницу","Выбрать 100002 страницу". Если он вынужден это делать, значит программа слепана кое-как.
Возьмем пример форума. Меня соверщенно не устраивают эти цифирки внизу.
Я бы сделал такие выборки:
1. По дате последнего ответа за период
2. По дате вопроса за период
3. По поиску в заголовке (с шаблонами)
4. По поиску в тексте (с шаблонами)
5. По отвечающему, с выбором имени из списка
6. По вопрошающему, с выбором имени из списка
А потом бы скомбинировал все это в одном окошечке.


b) Про курсоры.
"Бить, бить и бит!", использующих их, как писал И.В.Сталин. Однако настоящий мастер должен уметь использовать всю палитру инструментов. Только надо их применять там, где они уместны.
Я думаю, что выборка курсором первых десяти строк сожрет ресурсов не больше, чем создание временной таблицы с identity. Сколько времени будет создавться временная таблица на базе с миллионом записей? Во временную таблицу тянется вся база, а курсор прекрашает работу, отобрав нужно количество записей.

c)Про задние страницы форума. 100% приходит на первую страницу. Некоторые, задавшие вопрос, на 2-3 страницы назад. Все просматривают только наиболее упертые и совестливые, которым неудобно беспокоить людей вопросами, на которые уже отвечали ранее. Нужно всегда учитывать конкретные условия работы. Телефонный справочник отличается от системы диагностики оборудования.

d) Про варианты "Классический". На самом деле это "T-SQL для вер. 7 и более". Оборот "TOP n" не входит в стандарт ANSI SQL. Наверное, все верят в свою счастливую звезду, и в то, что им никогда не придется переносить базы с MS SQL на что-нибудь другое. Если использовать только стандартные средства, то эта процедура проидет более менее безболезненно. Надеймся на лучшее, готовся к худшему.
...
Рейтинг: 0 / 0
FAQ.Постраничная выборка
    #32028312
BootMaker
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Упорядоченная выборка 3000-4500 email-ов. На больших объемах и последних страницах летает так же, как и на первых.
Если хочется совсем оптимизировать для последних страниц, можете развернуть выборку с конца.
Давно уже проверено.

select email from (select top 1500 email from
(select top 4500 email from spamtmp order by email)a
order by email desc) b order by email
...
Рейтинг: 0 / 0
20 сообщений из 20, страница 1 из 1
Форумы / Microsoft SQL Server [игнор отключен] [закрыт для гостей] / FAQ.Постраничная выборка
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]