Para fins de paginação, preciso executar uma consulta com as cláusulas LIMIT
e OFFSET
. Mas também preciso contar o número de linhas que seriam retornadas por essa consulta sem as cláusulas LIMIT
e OFFSET
.
Eu quero correr:
SELECT * FROM table WHERE /* whatever */ ORDER BY col1 LIMIT ? OFFSET ?
E:
SELECT COUNT(*) FROM table WHERE /* whatever */
Ao mesmo tempo. Existe uma maneira de fazer isso, particularmente uma maneira que permite que o Postgres otimize isso, de modo que seja mais rápido do que executar ambos individualmente?
Respostas:
Sim. Com uma função de janela simples:
SELECT *, count(*) OVER() AS full_count FROM tbl WHERE /* whatever */ ORDER BY col1 OFFSET ? LIMIT ?
Esteja ciente de que o custo será substancialmente maior do que sem o número total, mas normalmente ainda mais barato do que duas consultas separadas. O Postgres tem que contar todas as linhas de qualquer maneira, o que impõe um custo dependendo do número total de linhas qualificadas. Detalhes:
No entanto , como Dani apontou , quando
OFFSET
é pelo menos tão grande quanto o número de linhas retornadas da consulta base, nenhuma linha é retornada. Então também não entendemosfull_count
.Se isso não for aceitável, uma possível solução alternativa para sempre retornar a contagem completa seria com um CTE e um
OUTER JOIN
:WITH cte AS ( SELECT * FROM tbl WHERE /* whatever */ ) SELECT * FROM ( TABLE cte ORDER BY col1 LIMIT ? OFFSET ? ) sub RIGHT JOIN (SELECT count(*) FROM cte) c(full_count) ON true;
Você obtém uma linha de valores NULL com o
full_count
anexado seOFFSET
for muito grande. Caso contrário, é anexado a todas as linhas, como na primeira consulta.Se uma linha com todos os valores NULL for um resultado válido possível, você deve verificar
offset >= full_count
para desambiguar a origem da linha vazia.Isso ainda executa a consulta de base apenas uma vez. Mas adiciona mais sobrecarga à consulta e só paga se isso for menor do que repetir a consulta de base para a contagem.
Se os índices que suportam a ordem de classificação final estiverem disponíveis, pode valer a pena incluir o
ORDER BY
no CTE (redundantemente).fonte
MATERIALIZED
padrão, sendo referenciado duas vezes.)editar: esta resposta é válida ao recuperar a tabela não filtrada. Vou deixar isso acontecer, caso possa ajudar alguém, mas pode não responder exatamente à pergunta inicial.
A resposta de Erwin Brandstetter é perfeita se você precisa de um valor preciso. No entanto, em tabelas grandes, você geralmente só precisa de uma boa aproximação. O Postgres oferece exatamente isso e será muito mais rápido, pois não precisará avaliar cada linha:
SELECT * FROM ( SELECT * FROM tbl WHERE /* something */ ORDER BY /* something */ OFFSET ? LIMIT ? ) data RIGHT JOIN (SELECT reltuples FROM pg_class WHERE relname = 'tbl') pg_count(total_count) ON true;
Na verdade, não tenho certeza se há uma vantagem em externalizar o
RIGHT JOIN
ou tê-lo como em uma consulta padrão. Isso mereceria alguns testes.SELECT t.*, pgc.reltuples AS total_count FROM tbl as t RIGHT JOIN pg_class pgc ON pgc.relname = 'tbl' WHERE /* something */ ORDER BY /* something */ OFFSET ? LIMIT ?
fonte
WHERE
cláusula em suas consultas. A segunda consulta é logicamente errada (recupera uma linha para cada tabela no banco de dados) - e mais cara quando corrigida.Embora a resposta de Erwin Brandstetter funcione perfeitamente, ela retorna a contagem total de linhas em cada linha como a seguir:
col1 - col2 - col3 - total -------------------------- aaaa - aaaa - aaaa - count bbbb - bbbb - bbbb - count cccc - cccc - cccc - count
Você pode querer considerar o uso de uma abordagem que retorna a contagem total apenas uma vez , como a seguir:
total - rows ------------ count - [{col1: 'aaaa'},{col2: 'aaaa'},{col3: 'aaaa'} {col1: 'bbbb'},{col2: 'bbbb'},{col3: 'bbbb'} {col1: 'cccc'},{col2: 'cccc'},{col3: 'cccc'}]
Consulta SQL:
SELECT (SELECT COUNT(*) FROM table) as count, (SELECT json_agg(t.*) FROM ( SELECT * FROM table WHERE /* whatever */ ORDER BY col1 OFFSET ? LIMIT ? ) AS t) AS rows
fonte
É uma má prática chamar duas vezes a mesma consulta para Just para obter o número total de linhas do resultado returend. Isso levará tempo de execução e desperdiçará os recursos do servidor.
Melhor, você pode usar
SQL_CALC_FOUND_ROWS
na consulta que dirá ao MySQL para buscar o número total de contagem de linhas junto com os resultados da consulta limite.Exemplo definido como:
SELECT SQL_CALC_FOUND_ROWS employeeName, phoneNumber FROM employee WHERE employeeName LIKE 'a%' LIMIT 10; SELECT FOUND_ROWS();
Na consulta acima, basta adicionar a
SQL_CALC_FOUND_ROWS
opção na consulta restante necessária e executar a segunda linha, ou seja,SELECT FOUND_ROWS()
retorna o número de linhas no conjunto de resultados retornado por essa instrução.fonte
Não.
Talvez haja um pequeno ganho que você poderia ganhar teoricamente ao executá-los individualmente com maquinários complicados suficientes sob o capô. Mas, se você quiser saber quantas linhas correspondem a uma condição, você terá que contá-las em vez de apenas um subconjunto LIMITed.
fonte