Pergunta

Existe uma maneira de fazer um Oracle Consulta se comportar como se ela contém um MySQL limit cláusula?

Dentro MySQL, Eu posso fazer isso:

select * 
from sometable
order by name
limit 20,10

Para chegar ao dia 21 para as 30ºes (pule os 20 primeiros, dê os próximos 10). As linhas são selecionadas após o order by, então realmente começa com o 20º nome em ordem alfabética.

Dentro Oracle, a única coisa que as pessoas mencionam é o rownum pseudo-coluna, mas é avaliada antes da order by, o que significa isso:

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

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

Foi útil?

Solução

A partir do Oracle 12C R1 (12.1), lá é uma Cláusula de limitação de linha. Não usa familiar LIMIT Sintaxe, mas pode fazer o trabalho melhor com mais opções. Você pode encontrar o Sintaxe completa aqui.

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 de página vinculada, na esperança de impedir a podridão do link.

Configurar

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.

Obtenha primeiro N linhas

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

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

5 rows selected.

Obtenha primeiro N linhas, se Nº A linha tem laços, obtenha todas as fileiras amarradas

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.

Topo x% das 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 compensação 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.

Outras dicas

Você pode usar uma subconsulta para isso como

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

Também dê uma olhada no tópico Em rownum e resultados limitantes na Oracle/AskTom para obter mais informações.

Atualizar: Para limitar o resultado com os limites inferiores e superiores, 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 de Asktom-Article especificado)

Atualização 2: Começando com o Oracle 12C (12.1), há uma sintaxe disponível para limitar as linhas ou iniciar em deslocamentos.

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

Ver esta resposta Para mais exemplos. Obrigado a Krumia pela dica.

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, Sort estava em uma linha DateTime não indexada:

  • O Plano de Explicação mostrou o mesmo valor para as três seleções (323168)
  • Mas o vencedor é Asktom (com analítico seguindo logo atrás)

A seleção de 10 primeiros 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

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() poderia ser substituído por Row_Number() mas pode retornar mais registros do que você espera se houver valores duplicados para nome.

No Oracle 12c (ver cláusula de limitação de linha em Referência SQL):

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

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 em que o banco de dados seleciona a linha de uma tabela ou conjunto de visualizações unidas.

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

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

É por isso que a consulta a seguir retorna sem 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 de 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 corretamente definida 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 sobre Vertabelo blog:

Menos instruções selecionadas. Além disso, menos consumo de desempenho. Créditos para: anibal@upf.br

SELECT *
    FROM   (SELECT t.*,
                   rownum AS rn
            FROM   shhospede t) a
    WHERE  a.rn >= in_first
    AND    a.rn <= in_first;

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

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

Você pode até mudar 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 visão embutida e renomeando rownum como rnum. Você pode usar o RNUM na consulta principal como critério de filtro.

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

maiores 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

Comecei a me preparar para o exame Oracle 1Z0-047, validado com 12C enquanto se preparava para ele, deparei-me com um aprimoramento de 12c conhecido como 'buscar primeiro' que você permite buscar linhas /limitar 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

Padrão SQL

Como expliquei em Este artigo, o padrão SQL: 2008 fornece a seguinte sintaxe para limitar o conjunto de resultados SQL:

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

Oracle 11g e versões mais antigas

Antes da versão 12c, para buscar os registros de N.

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

Como uma extensão de resposta aceita Oracle usa internamente ROW_NUMBER/RANK funções. OFFSET FETCH A sintaxe é um açúcar de sintaxe.

Pode ser observado usando DBMS_UTILITY.EXPAND_SQL_TEXT procedimento:

Preparando amostra:

CREATE TABLE rownum_order_test (
  val  NUMBER
);

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

Consulta:

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 <> Fiddle Demo

Buscar 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"

Para cada linha retornada por uma consulta, a pseudocoluna Rownum retorna um número indicando a ordem em que 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 em oracle servidor 11.2.0.1.0

Em 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>

(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 encomendar por.

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

   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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top