Как ограничить количество строк, возвращаемых запросом Oracle после упорядочивания?

1032

Есть ли способ заставить 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

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

Матье Лонгтин
источник
16
Стандартизировано в SQL: 2008.
Dalle
14
Том Кайт объявил о пределе для Oracle 12c ...
wolφi
14
Получение следующей страницы в наборе результатов?
Матье Лонгтин
3
@YaroslavShabalin В частности, выгружаемый поиск использует этот паттерн все время. Практически любое приложение с любой функцией поиска будет использовать его. Другим вариантом использования будет загрузка только части длинного списка или клиентской части таблицы и предоставление пользователю возможности расширения.
jpmc26
3
@YaroslavShabalin Вы не можете получить другой набор результатов, если базовые данные не изменятся из-за ORDER BY. Вот и весь смысл заказа в первую очередь. Если базовые данные изменяются, и ваш набор результатов изменяется из-за этого, то почему бы не показать пользователю обновленные результаты вместо устаревшей информации? Кроме того, государственное управление - это чума, которую следует избегать, насколько это возможно. Это постоянный источник осложнений и ошибок; вот почему функционал становится таким популярным. И когда бы вы знали, чтобы истечь весь набор результатов в памяти? В Интернете у вас нет возможности узнать, когда пользователь уходит.
jpmc26

Ответы:

622

Начиная с 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.

Получить первые Nстроки

SELECT val
FROM   rownum_order_test
ORDER BY val DESC
FETCH FIRST 5 ROWS ONLY;

       VAL
----------
        10
        10
         9
         9
         8

5 rows selected.

Получить первые Nстроки, если Nй строки имеет связи, получить все связанные строки

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.

Верх x% строк

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.
sampathsris
источник
1
Просто для расширения: OFFSET FETCHсинтаксис является синтаксическим сахаром. Подробности
Лукаш Шозда
793

Вы можете использовать подзапрос для этого как

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;

(Скопировано из указанной AskTom-статьи)

Обновление 2 : Начиная с Oracle 12c (12.1), доступен синтаксис, ограничивающий строки или начинающийся со смещений.

SELECT * 
FROM   sometable
ORDER BY name
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;

Смотрите этот ответ для большего количества примеров. Спасибо Крумии за подсказку.

Kosi2801
источник
5
Это, безусловно, способ сделать это, но имейте в виду (как говорится в статье о спросе), производительность запросов снижается по мере увеличения вашего максимального значения. Это хорошее решение для результатов запросов, когда вы хотите видеть только первые несколько страниц, но если вы используете это в качестве механизма для кодирования страниц по всей таблице, вам было бы лучше выполнить рефакторинг кода
Крис Джилл
1
+1 Ваша нижняя / верхняя версия фактически помогла мне обойти проблему, когда простое ограниченное сверху предложение rownum резко замедлило мой запрос.
Кельвин
1
Ли Рифель "аналитическое решение только с одним вложенным запросом" является тем.
Даррен Хикс
7
В статье AskTom также есть подсказка оптимизатора, в которой используется SELECT / * + FIRST_ROWS (n) / a. , rownum rnum Перед косой чертой должна стоять звездочка. ТАК чистит это.
Дэвид Манн
1
Обратите внимание, что для Oracle 11 внешний SELECT с ROWNUM не позволит вам вызвать deleteRow для UpdatableResultSet (с ORA-01446) - ожидая этого изменения 12c R1!
nsandersen
185

Я провел тестирование производительности для следующих подходов:

Asktom

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 миллионов записей, сортировка осуществлялась по неиндексированной строке даты и времени:

  • План объяснения показал одинаковое значение для всех трех вариантов (323168)
  • Но победителем является AskTom (с аналитическим следом за ним)

Выбор первых 10 строк занял:

  • AskTom: 28-30 секунд
  • Аналитические: 33-37 секунд
  • Короткая альтернатива: 110-140 секунд

Выбор строк от 100 000 до 100 010:

  • AskTom: 60 секунд
  • Аналитический: 100 секунд

Выбор строк между 9 000 000 и 9 000 010:

  • AskTom: 130 секунд
  • Аналитический: 150 секунд
zeldi
источник
Хорошо сделано. Вы пробовали короткую альтернативу с промежуточным вместо> = и <=?
Матье Лонгтин
4
@MathieuLongtin BETWEEN- это просто сокращение для >= AND <=( stackoverflow.com/questions/4809083/between-clause-versus-and )
wweicker
1
zeldi - На какой версии это было? Oracle внесла аналитические улучшения производительности в 11.1. и 11.2.
Ли Риффель
@Leigh Riffel Это было 10.2.0.5; Однажды я мог бы потратить время, а также проверить версию 11i.
Зельди
5
Я провел несколько быстрых тестов и получил аналогичные результаты для 12c. Новый offsetсинтаксис имеет тот же план и производительность, что и аналитический подход.
Джон Хеллер
55

Аналитическое решение только с одним вложенным запросом:

SELECT * FROM
(
   SELECT t.*, Row_Number() OVER (ORDER BY name) MyRow FROM sometable t
) 
WHERE MyRow BETWEEN 10 AND 20;

Rank()может быть заменено, Row_Number()но может вернуть больше записей, чем вы ожидаете, если для имени есть повторяющиеся значения.

Ли Риффель
источник
3
Я люблю аналитику. Возможно, вы захотите уточнить, в чем разница в поведении между Rank () и Row_Number ().
Дэйв Коста
Действительно, не уверен, почему я не думал о дубликатах. Таким образом, в этом случае, если есть повторяющиеся значения для имени, тогда RANK может дать больше записей, чем вы ожидаете, поэтому вы должны использовать Row_Number.
Ли Риффель
При упоминании rank()этого также стоит отметить, dense_rank()что может быть более полезным для управления выводом, так как последний не «пропускает» числа, тогда как rank()может. В любом случае для этого вопроса row_number()лучше всего подходит. Еще один не является этот метод применим к любой БД, которая поддерживает упомянутые функции.
Used_By_Already
28

В Oracle 12c (см. Предложение по ограничению строк в справочнике по SQL ):

SELECT * 
FROM sometable
ORDER BY name
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
beldaz
источник
53
И, конечно же, им пришлось использовать совершенно другой синтаксис, чем все остальные
Матье Лонгтин
9
Очевидно, после того, как LIMITони сошлись со всеми другими поставщиками, чтобы договориться о SQL: 2008, им пришлось взять листок из книги Microsoft и нарушить стандарт.
Бельдаз
1
Интересно, что недавно я слышал, что самый последний стандарт включает этот синтаксис, поэтому, возможно, Oracle перед этим внедрил его. Возможно, это более гибкий, чемLIMIT ... OFFSET
beldaz
3
@Derek: Да, несоблюдение стандарта вызывает сожаление. Но недавно представленная функциональность в 12cR1 более мощная, чем просто LIMIT n, m(см. Мой ответ). Опять же, Oracle должен был быть реализован LIMIT n, mкак синтаксический сахар, как это эквивалентно OFFSET n ROWS FETCH NEXT m ROWS ONLY.
Сампатрисрис
10
@Derek: На самом деле, я только что заметил это замечание в руководстве PostgreSQL postgresql.org/docs/9.0/static/sql-select.html#AEN69535 "Предложения LIMIT и OFFSET - это синтаксис, специфичный для PostgreSQL, также используемый MySQL. SQL Стандарт 2008 ввел пункты OFFSET ... FETCH {FIRST | NEXT} ... для той же функциональности ". Таким образом, LIMIT никогда не был частью стандарта.
Белдаз
14

Запросы на нумерацию страниц с упорядочением действительно сложны в 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 :

Bartek
источник
2
Первая строка результата запроса не передает ROWNUM> 1 предикат (…) - upvote для объяснения этого.
Петр Доброгост
6

Стандарт SQL

Как я объяснил в этой статье , стандарт SQL: 2008 предоставляет следующий синтаксис для ограничения набора результатов SQL:

SELECT
    title
FROM
    post
ORDER BY
    id DESC
FETCH FIRST 50 ROWS ONLY

Oracle 11g и более ранние версии

До версии 12c для извлечения записей Top-N необходимо было использовать производную таблицу и псевдостолбец ROWNUM:

SELECT *
FROM (
    SELECT
        title
    FROM
        post
    ORDER BY
        id DESC
)
WHERE ROWNUM <= 50
Влад Михалча
источник
5

Менее SELECT заявления. Кроме того, меньше потребляет производительность. Кредиты для: anibal@upf.br

SELECT *
    FROM   (SELECT t.*,
                   rownum AS rn
            FROM   shhospede t) a
    WHERE  a.rn >= in_first
    AND    a.rn <= in_first;
Фелипе В. Джованони
источник
2
Кроме того, это совершенно неверный ответ. Вопрос был об ограничении ПОСЛЕ сортировки. Так что rownum должен быть вне подзапроса.
BitLord
5

В качестве расширения принятого ответа 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;

ДБ <> Fiddle Demo

Извлечение расширенного текста 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"
Лукаш Шозда
источник
3

Если вы не используете Oracle 12C, вы можете использовать запрос TOP N, как показано ниже.

SELECT *
 FROM
   ( SELECT rownum rnum
          , a.*
       FROM sometable a 
   ORDER BY name
   )
WHERE rnum BETWEEN 10 AND 20;

Вы даже можете переместить это из предложения с помощью предложения следующим образом

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 в основном запросе в качестве критерия фильтрации.

Санди
источник
1
В моем случае это не вернуло правильные строки. То, что я сделал, чтобы исправить это, чтобы сделать ORDER BYи rownumотдельно. По сути, я создал подзапрос с ORDER BYпредложением .
Патрик Грегорио
Даунвот как неверный ответ. Вопрос был об ограничении после сортировки, поэтому rownumдолжен быть за пределами подзапроса.
Петр Доброгост
@PiotrDobrogost rownum только снаружи.
Сэнди
2

Я начал готовиться к экзамену 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
Арджун Гаур
источник
3
stackoverflow.com/a/26051830/635608 - это уже было предоставлено в других ответах. Пожалуйста, воздержитесь от публикации материалов, которые уже были опубликованы несколько месяцев назад.
Мат
1
о, конечно, я не нашел ответы на все вопросы, я встречался с подзапросами на ранней стадии, буду помнить об этом.
Арджун Гаур
1
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
Мехул Акабари
источник
Downvote как ROW_NUMBER()решение на основе уже было опубликовано Ли Риффель. В зависимости есть синтаксические ошибки в показанном коде.
Петр Доброгост
1

Для каждой строки, возвращаемой запросом, псевдостолбец 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 

Я реализовал это на oracleсервере11.2.0.1.0

Сумеш Т.Г.
источник
понижение, поскольку вопрос задает вопрос об ограничении упорядоченных рядов, а у вас даже нет порядка
Петр Доброгост
@PiotrDobrogost Поймите, что это не огромная задача, упорядочивание ключевых слов является общим для всех rdbms, только лимит имеет изменения.
Сумаш Т.Г.
-1

В случае SQL-Developer, он автоматически выбирает только первые 50 строк. И если мы прокрутим вниз, он получит еще 50 строк и так далее!

Следовательно, нам не нужно определять, в случае инструмента sql-developer!

Адитья Гоэль
источник
-3

В оракуле

SELECT val FROM   rownum_order_test ORDER BY val DESC FETCH FIRST 5 ROWS ONLY;

VAL

    10
    10
     9
     9
     8

Выбрано 5 строк

SQL>

Ракеш
источник
7
Вы должны указать, что это применимо, начиная с Oracle 12c, и что вы копируете / вставляете это откуда-то - пожалуйста, всегда указывайте свои источники.
Мат
Источник это @Mat. И Ракеш, пожалуйста, постарайтесь хотя бы адаптировать ответ к исходному вопросу. Я также предоставил ответ со ссылкой на тот же источник, но я постарался быть исчерпывающим и привел первоисточник.
Сампатрисрис,
-4

(не проверено) что-то подобное может сделать работу

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

Существует также ранг аналитической функции, который вы можете использовать для упорядочения.

EvilTeach
источник
2
Это не вернет ни одной строки, поскольку ROWNUM является столбцом в наборе результатов, поэтому последнее условие WHERE всегда будет ложным. Кроме того, вы не можете использовать ROWNUM и ЗАКАЗАТЬ ПО ЗАКАЗУ.
Бен
2
Отлично. Давайте оставим это здесь как предупреждение для других.
EvilTeach
-5

То же, что и выше с исправлениями. Работает, но определенно не красиво.

   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

Честно говоря, лучше использовать приведенные выше ответы.

stimepy
источник
5
Это неверно, поскольку предложение WHERE оценивается перед ORDER BY.
Бен
3
Интересно украден у меня плохой ответ ниже.
EvilTeach