Melhor maneira de obter contagem de resultado antes de LIMIT foi aplicada
-
03-07-2019 - |
Pergunta
Quando a paginação através de dados provenientes de um DB, você precisa saber quantas páginas haverá para tornar os controles de página de salto.
Atualmente eu fazer isso executando a consulta duas vezes, uma vez envolvido em um count()
para determinar os resultados totais, e uma segunda vez com um limite aplicado para voltar apenas os resultados que eu preciso para a página atual.
Isto parece ineficiente. Existe uma maneira melhor para determinar quantos resultados teriam sido devolvidos antes LIMIT
foi aplicado?
Eu estou usando PHP e Postgres.
Solução
Pure SQL
As coisas mudaram desde 2008. Você pode usar um href="https://www.postgresql.org/docs/current/static/functions-window.html" rel="noreferrer"> função de janela para obter a contagem total e o resultado limitado em uma consulta. (Introduzido com PostgreSQL 8.4 em 2009 ).
SELECT foo
, count(*) OVER() AS full_count
FROM bar
WHERE <some condition>
ORDER BY <some col>
LIMIT <pagesize>
OFFSET <offset>
Note que isso pode ser consideravelmente mais caro do que sem a contagem total. Todas as linhas têm de ser contadas, e um possível atalho levando apenas as principais linhas de um índice de correspondência pode não ser útil mais.
Não importa muito com pequenas mesas ou full_count
<= OFFSET
+ LIMIT
. Matérias para um full_count
substancialmente maior.
caso de canto : quando OFFSET
é pelo menos tão grande como o número de linhas a partir da consulta de base, nenhuma linha é retornado. Então você começa também não full_count
. Alternativa possível:
Considere o seqüência de eventos :
-
cláusula
WHERE
(e condiçõesJOIN
, mas não aqui) Filtro de qualificação linhas da tabela base (s).(funções
GROUP BY
e agregados iria aqui.) -
funções de janela são aplicados considerando todas as linhas de qualificação (dependendo da cláusula
OVER
ea especificação quadro da função). Ocount(*) OVER()
simples é baseada em todas as linhas. -
ORDER BY
(
DISTINCT
ouDISTINCT ON
iria aqui.) -
LIMIT
/OFFSET
são aplicadas com base na ordem estabelecida para selecionar linhas de retorno.
LIMIT
/ OFFSET
torna-se cada vez mais ineficiente com um crescente número de linhas na tabela. Considerar abordagens alternativas se você precisar de um melhor desempenho:
Alternativas para obter última contagem
Há completamente diferentes abordagens para obter a contagem de linhas afetadas ( não a contagem completa antes OFFSET
& LIMIT
foram aplicadas). Postgres tem contabilidade interna quantas linhas onde afetado pelo último comando SQL. Alguns clientes podem acessar essas informações ou contar linhas-se (como psql).
Por exemplo, você pode recuperar o número de linhas afetadas pela plpgsql imediatamente depois de executar um comando SQL com:
GET DIAGNOSTICS integer_var = ROW_COUNT;
Ou você pode usar pg_num_rows
em PHP . Ou funções semelhantes em outros clientes.
Relacionado:
Outras dicas
Como eu descrevo no meu blog, MySQL tem um recurso chamado SQL_CALC_FOUND_ROWS . Isso elimina a necessidade de fazer a consulta duas vezes, mas ainda precisa fazer a consulta em sua entireity, mesmo que a cláusula de limite teria permitido para parar cedo.
Tanto quanto eu sei, não há recurso semelhante para o PostgreSQL. Uma coisa que atente para quando fazer a paginação (a coisa mais comum para o qual limite é IMHO utilizado): fazendo um "OFFSET 1000 LIMIT 10" significa que o DB tem para buscar , pelo menos, 1010 linhas, mesmo se ele só lhe dá 10. Uma maneira mais performance que fazer é lembrar o valor da linha que você está requisitando por para a linha anterior (a 1000, neste caso) e reescrever a consulta como esta:" ... ONDE order_row> value_of_1000_th limite de 10" . A vantagem é que "order_row" é provavelmente indexado (se não, você tem ir um problema). A desvantagem é que se novos elementos são adicionados entre as visualizações de página, isso pode ficar um pouco fora de sincronia (mas, novamente, pode não ser observável pelos visitantes e pode ser um ótimo ganho de performance).
Você poderia atenuar a pena de desempenho por não correr o COUNT () consulta o tempo todo. Cache o número de páginas para, digamos, 5 minutos antes da consulta é executado novamente. A menos que você está vendo um grande número de inserções, que deve funcionar muito bem.
Desde Postgres já faz uma certa quantidade de cache coisas, este tipo de método não é tão ineficiente quanto parece. É definitivamente não duplicando o tempo de execução. Temos temporizadores construídos em nossa camada de DB, então eu vi as provas.
Já que você precisa saber para o propósito de paginação, eu sugiro executar a consulta completo uma vez, escrevendo os dados no disco como um cache do lado do servidor, em seguida, a alimentação que através de seu mecanismo de paginação.
Se você estiver executando a consulta COUNT com a finalidade de decidir se a fornecer os dados para o usuário ou não (ou seja, se houver> X registros, devolver um erro), você precisa ficar com a abordagem COUNT.