Como limito o número de linhas retornadas por uma consulta do Oracle após o pedido?
-
19-08-2019 - |
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.
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;
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.