Як обмежити кількість рядків, повернених запитом 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

Відповіді:


619

Починаючи з 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.


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;

Дивіться цю відповідь для отримання додаткових прикладів. Дякую Крумі за натяк.


5
Це, безумовно, спосіб зробити це, але будьте в курсі (як йдеться у статті про запитання), ефективність запиту знижується у міру збільшення вашого макс. Rownum. Це хороше рішення для результатів запитів, коли ви хочете бачити лише перші кілька сторінок, але якщо ви використовуєте це як механізм коду для переходу на сторінку через всю таблицю, вам краще переробити код
Chris Gill

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 1010:

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

Вибір рядків між 900000000 та 90001010:

  • AskTom: 130 секунд
  • Аналітичний: 150 секунд

Хороша робота. Ви спробували коротку альтернативу між проміжками, а не> = та <=?
Матьє Лонгтін

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.
zeldi

5
Я провів кілька швидких тестів і отримав подібні результати за 12с. Новий 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()найкраще підходить. Інша інформація не полягає в тому, що ця методика застосовна до будь-якого db, який підтримує згадані функції.
Б / у_By_Already

28

Про Oracle 12c (див. Пункт обмеження рядків у посиланні на SQL ):

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

53
І звичайно, їм довелося використовувати зовсім інший синтаксис, ніж усі інші досі
Матьє Лонгтін,

9
Очевидно після того, як сісти з усіма іншими постачальниками, щоб домовитися про LIMITSQL: 2008, їм тоді довелося взяти листок з книги Microsoft і порушити стандарт.
beldaz

1
Цікаво, що я нещодавно чув, що останній стандарт включає цей синтаксис, тому, можливо, Oracle підштовхнув його до початку перед реалізацією. Можливо, він гнучкіший, ніжLIMIT ... OFFSET
beldaz

3
@Derek: Так, не дотримуватися стандарту шкода. Але нещодавно представлена ​​функціональність у 12cR1 є більш потужною, ніж просто LIMIT n, m(Дивіться мою відповідь). Потім Oracle повинен був реалізувати LIMIT n, mяк синтаксичний цукор, оскільки він еквівалентний OFFSET n ROWS FETCH NEXT m ROWS ONLY.
sampathsris

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 :


2
Перший рядок результату запиту не передає ROWNUM> 1 предикат (…) - підсумковий опис для пояснення цього.
Пьотр Доброгост

6

SQL Standard

Як я пояснив у цій статті , стандарт 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;

db <> скриптова демонстрація

Отримання розширеного тексту 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;

Тут ми фактично створюємо inline view і перейменовуємо 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
о, звичайно, не переглядав кожну відповідь, і я натрапив на запитання, які вже знаходяться на початку, матиму це на увазі.
arjun gaur

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

В якості базового ROW_NUMBER()рішення Downvote вже був опублікований Лей Ріффель. У наркоманії є синтаксичні помилки в показаному коді.
Пьотр Доброгост

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


downvote, оскільки питання задає обмеження впорядкованих рядків, і ви навіть не маєте замовлення
Piotr

@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;

ВАЛ

    10
    10
     9
     9
     8

Вибрано 5 рядків.

SQL>


7
Вам слід вказати, що це стосується, починаючи з Oracle 12c, і що ви копіюєте / вставляєте звідкись - будь ласка, завжди цитуйте свої джерела.
Мат

Джерело цього @Mat. І Ракеш, будь ласка, спробуйте принаймні адаптувати відповідь на оригінальне запитання. Я також надав відповідь, цитуючи те саме джерело, але я намагався бути вичерпним і цитував першоджерело.
sampathsris

-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

Існує також ранг аналітичної функції, який ви можете використовувати для замовлення.


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

Чесно кажучи, краще скористатися наведеними вище відповідями.


5
Це невірно, оскільки пункт WHERE оцінюється перед ORDER BY.
Бен

3
Цікаво вкрадено з моєї поганої відповіді нижче.
EvilTeach
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.