Есть ли способ заставить запрос Oracle вести себя так, как будто он содержит предложение MySQL limit?
В MySQL я могу сделать это:
select *
from sometable
order by name
limit 20,10
чтобы получить с 21-го по 30-й ряды (первые 20 пропустить, дать следующие 10). Строки выбираются после order by, поэтому он действительно начинается с 20-го имени по алфавиту.
В Oracle единственное, что упоминают, - это псевдостолбец rownum, но он оценивается как передorder by, что означает следующее:
select *
from sometable
where rownum <= 10
order by name
вернет случайный набор из десяти строк, упорядоченных по имени, что обычно не то, что мне нужно. Он также не позволяет указывать смещение.
Похоже, что смещение и row_count поменялись местами в вашем первом примере MySQL. Этот запрос выберет строки с 11 по 30, а не с 21 по 30.
Лимит был объявлен Томом Кайтом для Oracle 12c ...
Считайте это оффтопом, но я с трудом могу представить реальные варианты использования, когда вам нужно извлечь данные из середины набора данных. Без шуток. Может, мне просто повезло, что я еще не подошел к таким требованиям. Если бы кто-то мог привести пример, основанный на реальном опыте ...
Получение следующей страницы в наборе результатов?
@YaroslavShabalin В частности, постраничный поиск использует этот шаблон все время. Практически любое приложение с любой функцией поиска будет использовать его. Другой вариант использования - загрузка только части длинного списка или таблицы на стороне клиента и предоставление пользователю возможности расширения.
@MathieuLongtin @ jpmc26 В Google вы сначала начинаете со страницы номер 10? Зачем нужно выполнять запрос каждый раз, когда загружается следующая страница, если вы можете выполнить его один раз, открыть курсор и перемещаться, загружая определенную часть набора данных? Да, вам придется иметь дело с управлением памятью, если набор результатов большой, но избегайте возможной несогласованности. Учтите, что количество строк в наборе результатов меняется, каждый раз, когда вы повторно запрашиваете с помощью вышеуказанного limit 20,10, вы можете получить другой результат.
@YaroslavShabalin Вы не можете получить другой набор результатов, если базовые данные не изменятся из-за ORDER BY. В этом весь смысл заказа. Если базовые данные изменяются и из-за этого изменяется ваш набор результатов, то почему бы не показать пользователю обновленные результаты вместо устаревшей информации? Кроме того, государственное управление - это чума, которую следует избегать как можно больше. Это постоянный источник проблем и ошибок; вот почему функциональность становится такой популярной. И когда вы узнаете, что срок действия всего набора результатов в памяти истекает? В сети у вас нет возможности узнать, когда пользователь уходит.
@ jpmc26 Хорошо, я понял вашу точку зрения. Теперь это имеет смысл.
@YaroslavShabalin - открытие курсора и удерживание его открытым в течение длительного периода времени в многопользовательском сценарии - хороший способ связать реляционную базу данных в узлы. В однопользовательской базе данных это, вероятно, не имеет значения, но когда у вас есть сотни одновременных подключений, это, скорее всего, приведет к проблемам. Реляционные базы данных обычно проектируются как транзакционные базы данных с относительно короткой продолжительностью транзакции. YMMV. Делитесь и наслаждайтесь.
См. Как работает пагинация в Oracle SQL
См. Также Пейджинг с Oracle


Вы можете использовать для этого подзапрос, например
select *
from
( select *
from emp
order by sal desc )
where ROWNUM <= 5;
Также посмотрите тему О ROWNUM и ограничивающих результатах на Oracle / AskTom для получения дополнительной информации.
Обновлять: Чтобы ограничить результат как нижними, так и верхними границами, все становится немного более раздутым с
select * from
( select a.*, ROWNUM rnum from
( <your_query_goes_here, with order by> ) a
where ROWNUM <= :MAX_ROW_TO_FETCH )
where rnum >= :MIN_ROW_TO_FETCH;
(Скопировано из указанной АскТом-статьи)
Обновление 2: Начиная с Oracle 12c (12.1) доступен синтаксис для ограничения строк или начала смещения.
SELECT *
FROM sometable
ORDER BY name
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
См. Дополнительные примеры в этот ответ. Спасибо Крумии за подсказку.
Это определенно способ сделать это, но имейте в виду (как говорится в статье ask tom), что производительность запроса ухудшается по мере увеличения вашего max rownum. Это хорошее решение для результатов запроса, когда вы хотите видеть только первые несколько страниц, но если вы используете это как механизм для постраничного просмотра кода всей таблицы, вам лучше провести рефакторинг вашего кода.
+1 ваша нижняя / верхняя версия действительно помогла мне обойти проблему, когда простое предложение rownum с ограничением сверху резко замедляло мой запрос.
"Аналитическое решение только с одним вложенным запросом" Ли Риффеля является единственным.
В статье AskTom также есть подсказка оптимизатора, в которой используется SELECT / * + FIRST_ROWS (n) / а., rownum rnum Закрывающая косая черта должна предшествовать звездочке. ТАК вычищает это.
Обратите внимание, что для Oracle 11 внешний SELECT с ROWNUM не позволит вам вызвать deleteRow для UpdatableResultSet (с ORA-01446) - с нетерпением жду этого изменения 12c R1!
В первом запросе разве внутренний запрос не должен извлекать все строки, прежде чем внешний сможет отфильтровать все, кроме первых пяти?
В отличие от подхода FETCH NEXT n ROWS, подзапрос с ограничением ROWNUM применим вместе с предложением FOR UPDATE.
Первый запрос также полезен, потому что сохраняется исходный набор столбцов. В средах, где результат запроса впоследствии обрабатывается каким-либо инструментом ORM, необходимо получить столбцы п из таблицы t.*, а не столбцы п + 1, включая какой-то переименованный rownum. Нестандартный пункт select * except rnum решил бы эту проблему, однако очень немногие базы данных поддерживают его на данный момент AFAIK. Кроме того, решение на основе rownum легко работает только для имитации условия limit. Эмулировать предложение offset сложнее из-за времени вычисления rownum (можно обойти, используя обратный порядок).
(непроверено) что-то вроде этого может сработать
WITH
base AS
(
select * -- get the table
from sometable
order by name -- in the desired order
),
twenty AS
(
select * -- get the first 30 rows
from base
where rownum < 30
order by name -- in the desired order
)
select * -- then get rows 21 .. 30
from twenty
where rownum > 20
order by name -- in the desired order
Существует также ранг аналитической функции, который можно использовать для сортировки.
Это не вернет ни одной строки, поскольку ROWNUM является столбцом в наборе результатов, поэтому последнее условие WHERE всегда будет ложным. К тому же вы не можете использовать ROWNUM и ЗАКАЗ ПО ГАРАНТИЙНОМУ ЗАКАЗУ.
Отлично. Оставим это здесь как предупреждение другим.
Аналитическое решение с одним вложенным запросом:
SELECT * FROM
(
SELECT t.*, Row_Number() OVER (ORDER BY name) MyRow FROM sometable t
)
WHERE MyRow BETWEEN 10 AND 20;
Rank() можно заменить на Row_Number(), но он может вернуть больше записей, чем вы ожидаете, если есть повторяющиеся значения для имени.
Я люблю аналитику. Возможно, вы захотите уточнить, в чем разница в поведении между Rank () и Row_Number ().
Действительно, не знаю, почему я не подумал о дубликатах. Итак, в этом случае, если есть повторяющиеся значения для имени, тогда RANK может дать больше записей, чем вы ожидаете, поэтому вы должны использовать Row_Number.
Если упомянуть rank(), также стоит отметить dense_rank(), который может быть более полезным для управления выводом, поскольку последний не «пропускает» числа, тогда как rank() может. В любом случае для этого вопроса лучше всего подходит row_number(). Еще один недостаток заключается в том, что этот метод применим к любой базе данных, которая поддерживает упомянутые функции.
Меньше операторов SELECT. Кроме того, меньшее потребление производительности. Кредиты на: [email protected]
SELECT *
FROM (SELECT t.*,
rownum AS rn
FROM shhospede t) a
WHERE a.rn >= in_first
AND a.rn <= in_first;
Кроме того, это совершенно неверный ответ. Вопрос касался ограничения ПОСЛЕ сортировки. Так что rownum должен быть вне подзапроса.
Я провел несколько тестов производительности для следующих подходов:
select * from (
select a.*, ROWNUM rnum from (
<select statement with order by clause>
) a where rownum <= MAX_ROW
) where rnum >= MIN_ROW
select * from (
<select statement with order by clause>
) where myrow between MIN_ROW and MAX_ROW
select * from (
select statement, rownum as RN with order by clause
) where a.rn >= MIN_ROW and a.rn <= MAX_ROW
В таблице было 10 миллионов записей, сортировка производилась по неиндексированной строке даты и времени:
На выбор первых 10 строк ушло:
Выбор строк от 100000 до 100 010:
Выбор строк от 9 000 000 до 9 000 010:
Хорошо сделано. Вы пробовали короткую альтернативу с промежуточным звеном вместо> = и <=?
@MathieuLongtin BETWEEN - это просто сокращение от >= AND <= (stackoverflow.com/questions/4809083/between-clause-versus-a nd)
zeldi - На какой это версии? Oracle улучшила аналитическую производительность в версии 11.1. и 11.2.
@Leigh Riffel Это было 10.2.0.5; однажды я мог бы найти время и также проверить версию 11i.
Я провел несколько быстрых тестов и получил аналогичные результаты для 12c. Новый синтаксис offset имеет тот же план и производительность, что и аналитический подход.
myrow не определен
В Oracle 12c (см. Пункт об ограничении строк в Справочник по SQL):
SELECT *
FROM sometable
ORDER BY name
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
И, конечно же, им пришлось использовать совершенно другой синтаксис, чем все остальные.
Ясно, что после того, как все остальные поставщики договорились о LIMIT в SQL: 2008, им пришлось взять листок из книги Microsoft и нарушить стандарт.
Интересно, что недавно я слышал, что самый последний стандарт включает этот синтаксис, так что, возможно, Oracle ввел его первым перед внедрением. Возможно он более гибкий, чем LIMIT ... OFFSET
@ Дерек: Да, несоблюдение стандарта прискорбно. Но недавно представленная функциональность в 12cR1 более мощная, чем просто LIMIT n, m (см. Мой ответ). С другой стороны, Oracle следовало бы реализовать LIMIT n, m как синтаксический сахар, поскольку он эквивалентен OFFSET n ROWS FETCH NEXT m ROWS ONLY.
@Derek: На самом деле, я только что заметил это замечание в руководстве PostgreSQL postgresql.org/docs/9.0/static/sql-select.html#AEN69535 «Пункты LIMIT и OFFSET - это специфичный для PostgreSQL синтаксис, также используемый MySQL. Стандарт SQL: 2008 представил предложения OFFSET ... FETCH {FIRST | NEXT } ... для той же функциональности ". Так что LIMIT никогда не входил в стандарт.
@beldaz Я обнаружил, что ваша ссылка не работает (из-за изменения привязки?), собственно postgresql.org/docs/9.0/static/sql-select.html#AEN69605 (на данный момент, конечно ;-))
@Wirone: Пока: postgresql.org/docs/9.0/static/sql-select.html#SQL-LIMIT
@Krumia Спасибо. Можно даже использовать www.postgresql.org/docs/current/static/sql-select.html#SQL-L IMIT для самой последней версии
Помимо всех аргументов стандартизации, если вам просто нужно что-то, что работает для Oracle, по состоянию на 2014 год это лучший из всех ответов, ИМХО. +1
Запросы пагинации с упорядочением в Oracle действительно сложны.
Oracle предоставляет псевдостолбец ROWNUM, который возвращает число, указывающее порядок, в котором база данных выбирает строку из таблицы или набора объединенных представлений.
ROWNUM - это псевдоколонка, из-за которой многие люди попадают в беду. Значение ROWNUM не присваивается строке постоянно (это распространенное заблуждение). Когда фактически присваивается значение ROWNUM, это может сбивать с толку. Значение ROWNUM присваивается строке после прохождения предикатов фильтра запроса, но перед агрегацией или сортировкой запросов.
Более того, значение ROWNUM увеличивается только после того, как оно присвоено.
Вот почему следующий запрос не возвращает строк:
select *
from (select *
from some_table
order by some_column)
where ROWNUM <= 4 and ROWNUM > 1;
Первая строка результата запроса не передает предикат ROWNUM> 1, поэтому ROWNUM не увеличивается до 2. По этой причине ни одно значение ROWNUM не становится больше 1, следовательно, запрос не возвращает строк.
Правильно определенный запрос должен выглядеть так:
select *
from (select *, ROWNUM rnum
from (select *
from skijump_results
order by points)
where ROWNUM <= 4)
where rnum > 1;
Узнайте больше о запросах пагинации в моих статьях в блоге Vertabelo:
Начиная с Oracle 12c R1 (12.1), существует является и предложение, ограничивающее строку. В нем не используется знакомый синтаксис LIMIT, но он может работать лучше с большим количеством опций. Вы можете найти полный синтаксис здесь. (Также читайте больше о том, как это работает внутри Oracle в этот ответ).
Чтобы ответить на исходный вопрос, вот запрос:
SELECT *
FROM sometable
ORDER BY name
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
(Для более ранних версий Oracle, пожалуйста, обратитесь к другим ответам в этом вопросе)
Следующие примеры были процитированы из связанная страница в надежде предотвратить гниение ссылок.
CREATE TABLE rownum_order_test (
val NUMBER
);
INSERT ALL
INTO rownum_order_test
SELECT level
FROM dual
CONNECT BY level <= 10;
COMMIT;
SELECT val
FROM rownum_order_test
ORDER BY val;
VAL
----------
1
1
2
2
3
3
4
4
5
5
6
6
7
7
8
8
9
9
10
10
20 rows selected.
NSELECT val
FROM rownum_order_test
ORDER BY val DESC
FETCH FIRST 5 ROWS ONLY;
VAL
----------
10
10
9
9
8
5 rows selected.
N, если строка Nth имеет связи, получить все связанные строкиSELECT val
FROM rownum_order_test
ORDER BY val DESC
FETCH FIRST 5 ROWS WITH TIES;
VAL
----------
10
10
9
9
8
8
6 rows selected.
SELECT val
FROM rownum_order_test
ORDER BY val
FETCH FIRST 20 PERCENT ROWS ONLY;
VAL
----------
1
1
2
2
4 rows selected.
SELECT val
FROM rownum_order_test
ORDER BY val
OFFSET 4 ROWS FETCH NEXT 4 ROWS ONLY;
VAL
----------
3
3
4
4
4 rows selected.
SELECT val
FROM rownum_order_test
ORDER BY val
OFFSET 4 ROWS FETCH NEXT 20 PERCENT ROWS ONLY;
VAL
----------
3
3
4
4
4 rows selected.
Просто для расширения: синтаксис OFFSET FETCH - это синтаксический сахар. Подробности
Как мы можем получить LIMIT и OFFSET в Oracle 11G?
@Pra_A В 11G нет встроенной поддержки LIMIT / OFFSET. Если вы проверите другие ответы, все они так или иначе фактически реализовали предел и смещение.
Я начал подготовку к экзамену Oracle 1z0-047, подтвержденному на 12c Во время подготовки к нему я наткнулся на усовершенствование 12c, известное как FETCH FIRST. Это позволяет вам получать строки / ограничивать строки по вашему усмотрению. С ним доступны несколько вариантов
- FETCH FIRST n ROWS ONLY
- OFFSET n ROWS FETCH NEXT N1 ROWS ONLY // leave the n rows and display next N1 rows
- n % rows via FETCH FIRST N PERCENT ROWS ONLY
Пример:
Select * from XYZ a
order by a.pqr
FETCH FIRST 10 ROWS ONLY
о, конечно, не перебирал все ответы, я рано натолкнулся на подзапросы, буду иметь это в виду.
Если вы не используете Oracle 12C, вы можете использовать запрос TOP N, как показано ниже.
SELECT *
FROM
( SELECT rownum rnum
, a.*
FROM sometable a
ORDER BY name
)
WHERE rnum BETWEEN 10 AND 20;
Вы даже можете переместить это предложение в предложение with следующим образом
WITH b AS
( SELECT rownum rnum
, a.*
FROM sometable a ORDER BY name
)
SELECT * FROM b
WHERE rnum BETWEEN 10 AND 20;
На самом деле здесь мы создаем встроенное представление и переименовываем rownum в rnum. Вы можете использовать rnum в основном запросе в качестве критерия фильтрации.
В моем случае это не вернуло правильные строки. Чтобы исправить это, я сделал ORDER BY и rownum отдельно. В основном я создал подзапрос с предложением ORDER BY.
Проголосуйте против, так как это неверный ответ. Вопрос касался ограничения после сортировки, поэтому rownum должен быть вне подзапроса.
@PiotrDobrogost rownum находится только снаружи.
select * FROM (SELECT
ROW_NUMBER() OVER (ORDER BY sal desc),* AS ROWID,
FROM EMP ) EMP where ROWID=5
больше значений узнать
select * FROM (SELECT
ROW_NUMBER() OVER (ORDER BY sal desc),* AS ROWID,
FROM EMP ) EMP where ROWID>5
меньше значений узнают
select * FROM (SELECT
ROW_NUMBER() OVER (ORDER BY sal desc),* AS ROWID,
FROM EMP ) EMP where ROWID=5
Голосовать против решения на основе ROW_NUMBER() уже опубликовал Ли Риффель. Кроме того, в показанном коде есть синтаксические ошибки.
Для каждой строки, возвращаемой запросом, псевдостолбец ROWNUM возвращает число, указывающее порядок, в котором Oracle выбирает строку из таблицы или набора соединенных строк. Первая выбранная строка имеет ROWNUM 1, вторая - 2 и так далее.
SELECT * FROM sometable1 so
WHERE so.id IN (
SELECT so2.id from sometable2 so2
WHERE ROWNUM <=5
)
AND ORDER BY so.somefield AND ROWNUM <= 100
Я реализовал это на сервере oracle11.2.0.1.0
проголосуйте против, так как вопрос задает об ограничении строк упорядоченный, и у вас даже нет порядка
@PiotrDobrogost. Поймите, что это не такая уж большая задача, упорядочивание ключевых слов является общим для всех rdbms. Изменен только предел.
Начиная с версии 12c Oracle поддерживает стандарт SQL: 2008 Standard, который предоставляет следующий синтаксис для ограничения набора результатов SQL:
SELECT
title
FROM
post
ORDER BY
id DESC
FETCH FIRST 50 ROWS ONLY
До версии 12c для получения записей Top-N нужно было использовать производную таблицу и псевдостолбец ROWNUM:
SELECT *
FROM (
SELECT
title
FROM
post
ORDER BY
id DESC
)
WHERE ROWNUM <= 50
Как расширение принятый ответ Oracle внутренне использует функции ROW_NUMBER/RANK. Синтаксис OFFSET FETCH - это синтаксический сахар.
Это можно было наблюдать с помощью процедуры DBMS_UTILITY.EXPAND_SQL_TEXT:
Подготовка образца:
CREATE TABLE rownum_order_test (
val NUMBER
);
INSERT ALL
INTO rownum_order_test
SELECT level
FROM dual
CONNECT BY level <= 10;
COMMIT;
Запрос:
SELECT val
FROM rownum_order_test
ORDER BY val DESC
FETCH FIRST 5 ROWS ONLY;
регулярно:
SELECT "A1"."VAL" "VAL"
FROM (SELECT "A2"."VAL" "VAL","A2"."VAL" "rowlimit_$_0",
ROW_NUMBER() OVER ( ORDER BY "A2"."VAL" DESC ) "rowlimit_$$_rownumber"
FROM "ROWNUM_ORDER_TEST" "A2") "A1"
WHERE "A1"."rowlimit_$$_rownumber"<=5 ORDER BY "A1"."rowlimit_$_0" DESC;
Получение развернутого текста SQL:
declare
x VARCHAR2(1000);
begin
dbms_utility.expand_sql_text(
input_sql_text => '
SELECT val
FROM rownum_order_test
ORDER BY val DESC
FETCH FIRST 5 ROWS ONLY',
output_sql_text => x);
dbms_output.put_line(x);
end;
/
WITH TIES расширяется как RANK:
declare
x VARCHAR2(1000);
begin
dbms_utility.expand_sql_text(
input_sql_text => '
SELECT val
FROM rownum_order_test
ORDER BY val DESC
FETCH FIRST 5 ROWS WITH TIES',
output_sql_text => x);
dbms_output.put_line(x);
end;
/
SELECT "A1"."VAL" "VAL"
FROM (SELECT "A2"."VAL" "VAL","A2"."VAL" "rowlimit_$_0",
RANK() OVER ( ORDER BY "A2"."VAL" DESC ) "rowlimit_$$_rank"
FROM "ROWNUM_ORDER_TEST" "A2") "A1"
WHERE "A1"."rowlimit_$$_rank"<=5 ORDER BY "A1"."rowlimit_$_0" DESC
и смещение:
declare
x VARCHAR2(1000);
begin
dbms_utility.expand_sql_text(
input_sql_text => '
SELECT val
FROM rownum_order_test
ORDER BY val
OFFSET 4 ROWS FETCH NEXT 4 ROWS ONLY',
output_sql_text => x);
dbms_output.put_line(x);
end;
/
SELECT "A1"."VAL" "VAL"
FROM (SELECT "A2"."VAL" "VAL","A2"."VAL" "rowlimit_$_0",
ROW_NUMBER() OVER ( ORDER BY "A2"."VAL") "rowlimit_$$_rownumber"
FROM "ROWNUM_ORDER_TEST" "A2") "A1"
WHERE "A1"."rowlimit_$$_rownumber"<=CASE WHEN (4>=0) THEN FLOOR(TO_NUMBER(4))
ELSE 0 END +4 AND "A1"."rowlimit_$$_rownumber">4
ORDER BY "A1"."rowlimit_$_0"
В случае SQL-Developer он автоматически выбирает только первые 50 строк. И если мы прокрутим вниз, получится еще 50 строк и так далее!
Следовательно, в случае инструмента sql-developer нам не нужно определять!
Стандартизирован в SQL: 2008.