Como limite o número de linhas retornadas por uma consulta Oracle após o pedido?

1032

Existe uma maneira de fazer uma Oracleconsulta se comportar como se contenha uma MySQL limitcláusula?

Em MySQL, eu posso fazer isso:

select * 
from sometable
order by name
limit 20,10

para obter as linhas 21 a 30 (pule as 20 primeiras, dê as 10 seguintes). As linhas são selecionadas após o order by, então ele realmente começa com o vigésimo nome em ordem alfabética.

Em Oracle, a única coisa que as pessoas mencionam é a rownumpseudo-coluna, mas ela é avaliada antes order by , o que significa isso:

select * 
from sometable
where rownum <= 10
order by name

retornará um conjunto aleatório de dez linhas ordenadas por nome, que geralmente não é o que eu quero. Também não permite especificar um deslocamento.

Mathieu Longtin
fonte
16
Padronizado em SQL: 2008.
dalle
14
O limite foi anunciado por Tom Kyte para Oracle 12c ...
terça
14
Buscando a próxima página em um conjunto de resultados?
Mathieu Longtin
3
@YaroslavShabalin Em particular, uma pesquisa paginada usa esse padrão todo o tempo. Quase qualquer aplicativo com qualquer tipo de função de pesquisa o utilizará. Outro caso de uso seria carregar apenas parte de uma lista longa ou tabela do lado do cliente e dar ao usuário a opção de expandir.
precisa saber é o seguinte
3
@YaroslavShabalin Você não pode obter um conjunto de resultados diferente, a menos que os dados subjacentes sejam alterados devido a ORDER BY. Esse é o objetivo de pedir primeiro. Se os dados subjacentes mudarem e seu conjunto de resultados mudar por causa disso, por que não mostrar ao usuário os resultados atualizados em vez de informações desatualizadas? Além disso, a gestão do estado é uma praga a ser evitada o máximo possível. É uma fonte constante de complicações e bugs; é por isso que o funcional está ficando tão popular. E quando você saberia expirar todo o conjunto de resultados na memória? Na web, você não tem como saber quando o usuário sai.
precisa saber é o seguinte

Respostas:

621

A partir da Oracle 12c R1 (12,1), não é uma linha limitar a cláusula . Ele não usa LIMITsintaxe familiar , mas pode fazer o trabalho melhor com mais opções. Você pode encontrar a sintaxe completa aqui . (Leia também mais sobre como isso funciona internamente no Oracle nesta resposta ).

Para responder à pergunta original, aqui está a consulta:

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

(Para versões anteriores do Oracle, consulte outras respostas nesta pergunta)


Exemplos:

Os exemplos a seguir foram citados na página vinculada , na esperança de impedir a podridão do link.

Configuração

CREATE TABLE rownum_order_test (
  val  NUMBER
);

INSERT ALL
  INTO rownum_order_test
SELECT level
FROM   dual
CONNECT BY level <= 10;

COMMIT;

O que há na mesa?

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.

Obter primeiras Nlinhas

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

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

5 rows selected.

Obter primeiras Nlinhas, se Na linha tiver vínculos, obter todas as linhas vinculadas

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% Superior de linhas

SELECT val
FROM   rownum_order_test
ORDER BY val
FETCH FIRST 20 PERCENT ROWS ONLY;

       VAL
----------
         1
         1
         2
         2

4 rows selected.

Usando um deslocamento, muito útil para paginação

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.

Você pode combinar o deslocamento com porcentagens

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
fonte
1
Apenas para estender: a OFFSET FETCHsintaxe é um açúcar sintático. Detalhes
Lukasz Szozda
793

Você pode usar uma subconsulta para isso como

select *
from  
( select * 
  from emp 
  order by sal desc ) 
where ROWNUM <= 5;

Consulte também o tópico On ROWNUM e limite de resultados no Oracle / AskTom para obter mais informações.

Atualização : para limitar o resultado com limites inferior e superior, as coisas ficam um pouco mais inchadas com

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;

(Copiado do artigo AskTom especificado)

Atualização 2 : A partir do Oracle 12c (12.1), há uma sintaxe disponível para limitar linhas ou iniciar com compensações.

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

Veja esta resposta para mais exemplos. Obrigado a Krumia pela dica.

Kosi2801
fonte
5
Essa é definitivamente a maneira de fazer isso, mas lembre-se (como diz o artigo ask tom) que o desempenho da consulta diminui à medida que o máximo de tempo de espera aumenta. Esta é uma boa solução para os resultados da consulta em que você deseja apenas ver as primeiras páginas, mas se você estiver usando isso como um mecanismo de código para paginar uma tabela inteira, seria melhor refatorar seu código
Chris Gill
1
Marcou com +1 sua versão inferior / superior, na verdade, me ajudou a solucionar um problema em que uma mera cláusula de limite superior estava atrasando drasticamente minha consulta.
Kelvin
1
A "solução analítica de Leigh Riffel com apenas uma consulta aninhada" é a única.
Darren Hicks
7
O artigo AskTom também possui uma dica de otimizador que usa SELECT / * + FIRST_ROWS (n) / a. , rownum rnum A barra de fechamento deve ser precedida por um asterisco. SO está limpando.
David Mann
1
Observe que, para o Oracle 11, um SELECT externo com ROWNUM impedirá que você chame deleteRow em um UpdatableResultSet (com ORA-01446) - aguardando ansiosamente essa alteração de 12c R1!
Nsandersen
185

Fiz alguns testes de desempenho para as seguintes abordagens:

Asktom

select * from (
  select a.*, ROWNUM rnum from (
    <select statement with order by clause>
  ) a where rownum <= MAX_ROW
) where rnum >= MIN_ROW

Analítico

select * from (
  <select statement with order by clause>
) where myrow between MIN_ROW and MAX_ROW

Alternativa curta

select * from (
  select statement, rownum as RN with order by clause
) where a.rn >= MIN_ROW and a.rn <= MAX_ROW

Resultados

A tabela tinha 10 milhões de registros, a classificação estava em uma linha de data e hora não indexada:

  • O plano de explicação mostrou o mesmo valor para todas as três seleções (323168)
  • Mas o vencedor é o AskTom (com acompanhamento analítico logo atrás)

A seleção das 10 primeiras linhas levou:

  • AskTom: 28-30 segundos
  • Analítico: 33-37 segundos
  • Alternativa curta: 110-140 segundos

Selecionando linhas entre 100.000 e 100.010:

  • AskTom: 60 segundos
  • Analítico: 100 segundos

Selecionando linhas entre 9.000.000 e 9.000.010:

  • AskTom: 130 segundos
  • Analítico: 150 segundos
zeldi
fonte
Bom trabalho. Você tentou a alternativa curta com um entre em vez de> = e <=?
Mathieu Longtin
4
@MathieuLongtin BETWEENé apenas um atalho para >= AND <=( stackoverflow.com/questions/4809083/between-clause-versus-and )
wweicker
1
zeldi - Em qual versão estava? A Oracle fez melhorias de desempenho analítico na 11.1. e 11.2.
Leigh Riffel
@Leigh Riffel Era 10.2.0.5; um dia eu posso levar tempo e também checar a versão 11i.
zeldi
5
Fiz alguns testes rápidos e obtive resultados semelhantes para o 12c. A nova offsetsintaxe possui o mesmo plano e desempenho da abordagem analítica.
precisa saber é o seguinte
55

Uma solução analítica com apenas uma consulta aninhada:

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

Rank()pode ser substituído, Row_Number()mas pode retornar mais registros do que o esperado, se houver valores duplicados para o nome.

Leigh Riffel
fonte
3
Eu amo análises. Você pode esclarecer qual seria a diferença de comportamento entre Rank () e Row_Number ().
Dave Costa
Na verdade, não sei por que não pensei em duplicatas. Portanto, nesse caso, se houver valores duplicados para o nome, RANK poderá fornecer mais registros do que o esperado, portanto, você deve usar Row_Number.
Leigh Riffel
Se mencionar rank(), também vale a pena notar o dense_rank()que pode ser mais útil para o controle de saída, pois o último não "pula" os números, enquanto que rank()pode. De qualquer forma, esta pergunta row_number()é mais adequada. Um outro não é que esta técnica é aplicável a qualquer banco de dados que suporte as funções mencionadas.
Used_By_Already
28

No Oracle 12c (consulte a cláusula de limitação de linha na referência SQL ):

SELECT * 
FROM sometable
ORDER BY name
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
Beldaz
fonte
53
E, claro, eles tiveram que usar uma sintaxe totalmente diferente do que todo mundo até agora
Mathieu Longtin
9
Claramente, depois de se sentar com todos os outros fornecedores para concordar com o LIMITSQL: 2008, eles tiveram que retirar uma folha do livro da Microsoft e quebrar o padrão.
beldaz
1
Curiosamente, ouvi recentemente que o padrão mais recente inclui essa sintaxe, portanto, talvez a Oracle tenha introduzido antes antes da implementação. Indiscutivelmente, é mais flexível do queLIMIT ... OFFSET
beldaz
3
@ Derek: Sim, não seguir o padrão é lamentável. Mas a funcionalidade recém-introduzida no 12cR1 é mais poderosa do que apenas LIMIT n, m(Veja minha resposta). Então, novamente, a Oracle deveria ter implementado LIMIT n, mcomo açúcar sintático, como é equivalente a OFFSET n ROWS FETCH NEXT m ROWS ONLY.
Sampathsris 26/09/14
10
@Derek: Na verdade, acabei de notar esta observação no manual do PostgreSQL postgresql.org/docs/9.0/static/sql-select.html#AEN69535 "As cláusulas LIMIT e OFFSET são sintaxe específica do PostgreSQL, também usada pelo MySQL. O SQL : O padrão 2008 introduziu as cláusulas OFFSET ... FETCH {FIRST | NEXT} ... para a mesma funcionalidade ". Portanto, o LIMIT nunca fez parte do padrão.
22415 beldaz
14

As consultas de paginação com pedidos são realmente complicadas no Oracle.

O Oracle fornece uma pseudocoluna ROWNUM que retorna um número indicando a ordem na qual o banco de dados seleciona a linha de uma tabela ou conjunto de visualizações unidas.

ROWNUM é uma pseudocoluna que coloca muitas pessoas em problemas. Um valor ROWNUM não é atribuído permanentemente a uma linha (esse é um mal-entendido comum). Pode ser confuso quando um valor ROWNUM é realmente atribuído. Um valor ROWNUM é atribuído a uma linha depois de passar os predicados de filtro da consulta, mas antes da agregação ou classificação da consulta .

Além disso, um valor ROWNUM é incrementado somente depois de ser atribuído.

É por isso que a consulta a seguir não retorna linhas:

 select * 
 from (select *
       from some_table
       order by some_column)
 where ROWNUM <= 4 and ROWNUM > 1; 

A primeira linha do resultado da consulta não passa o predicado ROWNUM> 1; portanto, o ROWNUM não aumenta para 2. Por esse motivo, nenhum valor de ROWNUM fica maior que 1, consequentemente, a consulta não retorna linhas.

A consulta definida corretamente deve ficar assim:

select *
from (select *, ROWNUM rnum
      from (select *
            from skijump_results
            order by points)
      where ROWNUM <= 4)
where rnum > 1; 

Saiba mais sobre consultas de paginação em meus artigos no blog Vertabelo :

Bartek
fonte
2
A primeira linha do resultado da consulta não passa no predicado ROWNUM> 1 (…) - upvote por explicar isso.
Piotr Dobrogost 13/03/19
6

Padrão SQL

Como expliquei neste artigo , o SQL: 2008 Standard fornece a seguinte sintaxe para limitar o conjunto de resultados do SQL:

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

Oracle 11g e versões anteriores

Antes da versão 12c, para buscar os registros Top-N, era necessário usar uma tabela derivada e a pseudocoluna ROWNUM:

SELECT *
FROM (
    SELECT
        title
    FROM
        post
    ORDER BY
        id DESC
)
WHERE ROWNUM <= 50
Vlad Mihalcea
fonte
5

Menos instruções SELECT. Além disso, consome menos desempenho. Créditos para: [email protected]

SELECT *
    FROM   (SELECT t.*,
                   rownum AS rn
            FROM   shhospede t) a
    WHERE  a.rn >= in_first
    AND    a.rn <= in_first;
Felipe Q. Giovanoni
fonte
2
Além disso, é uma resposta totalmente incorreta. A pergunta era sobre limitar APÓS a classificação. Portanto, o rownum deve estar fora da subconsulta.
BitLord
5

Como uma extensão da resposta aceita, a Oracle usa internamente ROW_NUMBER/RANKfunções. OFFSET FETCHsintaxe é um açúcar de sintaxe.

Isso pode ser observado usando o DBMS_UTILITY.EXPAND_SQL_TEXTprocedimento:

Preparando a amostra:

CREATE TABLE rownum_order_test (
  val  NUMBER
);

INSERT ALL
  INTO rownum_order_test
SELECT level
FROM   dual
CONNECT BY level <= 10;
COMMIT;

Inquerir:

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

é regular:

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 <> demo de violino

Buscando texto SQL expandido:

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é expandido como 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

e deslocamento:

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"
Lukasz Szozda
fonte
3

Se você não estiver no Oracle 12C, poderá usar a consulta TOP N como abaixo.

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

Você pode até mover isso da cláusula com a cláusula da seguinte maneira

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

Aqui, na verdade, estamos criando uma exibição em linha e renomeando rownum como rnum. Você pode usar rnum na consulta principal como critério de filtro.

sandi
fonte
1
No meu caso, isso não retornou as linhas corretas. O que eu fiz para corrigir isso é fazer o ORDER BYe o rownumseparadamente. Basicamente, criei uma subconsulta que continha a ORDER BYcláusula .
Patrick Gregorio
Downvote como resposta incorreta. A pergunta era sobre a limitação após a classificação, portanto, rownumdeveria estar fora de uma subconsulta.
Piotr Dobrogost
O rownum @PiotrDobrogost está apenas fora.
sandi
2

Comecei a me preparar para o exame Oracle 1z0-047, validado contra 12c. Ao me preparar para isso, deparei-me com um aprimoramento 12c conhecido como 'FETCH FIRST'. Ele permite que você busque linhas / limite linhas conforme sua conveniência. Várias opções estão disponíveis com ele

- 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

Exemplo:

Select * from XYZ a
order by a.pqr
FETCH FIRST 10 ROWS ONLY
arjun gaur
fonte
3
stackoverflow.com/a/26051830/635608 - isso já foi fornecido em outras respostas. Evite postar coisas que já foram publicadas meses atrás.
Mat
1
ah, claro, não passei por todas as respostas, deparei-me com as subconsultas desde o início, manteremos isso em mente.
Arjun gaur
1
select * FROM (SELECT 
   ROW_NUMBER() OVER (ORDER BY sal desc),* AS ROWID, 
 FROM EMP ) EMP  where ROWID=5

maior que os valores descobrem

select * FROM (SELECT 
       ROW_NUMBER() OVER (ORDER BY sal desc),* AS ROWID, 
     FROM EMP ) EMP  where ROWID>5

menos que os valores descobrem

select * FROM (SELECT 
       ROW_NUMBER() OVER (ORDER BY sal desc),* AS ROWID, 
     FROM EMP ) EMP  where ROWID=5
Mehul Akabari
fonte
O voto negativo como ROW_NUMBER()solução baseada já havia sido publicado por Leigh Riffel. Além disso, há erros de sintaxe no código mostrado.
Piotr Dobrogost 13/03/19
1

Para cada linha retornada por uma consulta, a pseudocoluna ROWNUM retorna um número indicando a ordem na qual o Oracle seleciona a linha de uma tabela ou conjunto de linhas unidas. A primeira linha selecionada tem um ROWNUM de 1, a segunda tem 2 e assim por diante.

  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 

Eu implementei isso no oracleservidor11.2.0.1.0

Sumesh TG
fonte
downvote como a questão pergunta sobre como limitar ordenadas fileiras e você não tem mesmo fim
Piotr Dobrogost
@PiotrDobrogost Entenda que não é uma tarefa enorme, ordenar palavras-chave é comum para todos os rdbms que apenas o limite tem alterações.
Sumesh TG 13/03/19
-1

No caso do SQL-Developer, ele busca automaticamente apenas as primeiras 50 linhas. E se rolarmos para baixo, ele buscará outras 50 linhas e assim por diante!

Portanto, não precisamos definir, no caso da ferramenta sql-developer!

Aditya Goel
fonte
-3

No oracle

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

VAL

    10
    10
     9
     9
     8

5 linhas selecionadas.

SQL>

Rakesh
fonte
7
Você deve especificar que isso se aplica a partir do Oracle 12c e copiá-lo / colá-lo de algum lugar - sempre cite suas fontes.
21414 Mat
A fonte é este @Mat. E Rakesh, tente pelo menos adaptar a resposta para a pergunta original. Também forneci uma resposta citando a mesma fonte, mas tentei ser abrangente e citei a fonte original.
Sampathsris
-4

(não testado) algo assim pode fazer o trabalho

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

Há também a classificação da função analítica, que você pode usar para ordenar.

EvilTeach
fonte
2
Isso não retornará uma única linha, pois o ROWNUM é uma coluna no conjunto de resultados, para que a última condição WHERE seja sempre falsa. Além disso, você não pode usar o ROWNUM e uma ORDEM POR uma ORDEM de garantia.
9139 Ben
2
Excelente. Vamos deixar isso aqui como um aviso para os outros.
precisa saber é o seguinte
-5

O mesmo que acima com correções. Funciona, mas definitivamente não é bonito.

   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

Honestamente, é melhor usar as respostas acima.

stimepy
fonte
5
Isso está incorreto, pois a cláusula WHERE é avaliada antes da ORDER BY.
9139 Ben
3
Curiosamente roubado da minha resposta ruim abaixo.
EvilTeach