O ponto principal é provavelmente você
JOIN
e GROUP
sobre tudo apenas para obter max(created)
. Obtenha este valor separadamente. Você mencionou todos os índices necessários aqui:em
report_rank.created
e nas chaves estrangeiras. Você está indo bem aí. (Se você estiver interessado em algo melhor do que "tudo bem", continue lendo !) O
LEFT JOIN report_site
será forçado a um simples JOIN
pelo WHERE
cláusula. Eu substituí um simples JOIN
. Eu também simplifiquei muito sua sintaxe. Atualizado em julho de 2015 com consultas mais simples e rápidas e funções mais inteligentes.
Solução para várias linhas
report_rank.created
é não é exclusivo e você quer todos as últimas linhas.Usando a função de janela
rank()
em uma subconsulta. SELECT r.id, r.keyword_id, r.site_id
, r.rank, r.url, r.competition
, r.source, r.country, r.created -- same as "max"
FROM (
SELECT *, rank() OVER (ORDER BY created DESC NULLS LAST) AS rnk
FROM report_rank r
WHERE EXISTS (
SELECT *
FROM report_site s
JOIN report_profile p ON p.site_id = s.id
JOIN crm_client c ON c.id = p.client_id
JOIN auth_user u ON u.id = c.user_id
WHERE s.id = r.site_id
AND u.is_active
AND c.is_deleted = FALSE
)
) sub
WHERE rnk = 1;
Por que
DESC NULLS LAST
? Solução para uma linha
Se
report_rank.created
é único ou você está satisfeito com qualquer 1 linha com max(created)
:SELECT id, keyword_id, site_id
, rank, url, competition
, source, country, created -- same as "max"
FROM report_rank r
WHERE EXISTS (
SELECT 1
FROM report_site s
JOIN report_profile p ON p.site_id = s.id
JOIN crm_client c ON c.id = p.client_id
JOIN auth_user u ON u.id = c.user_id
WHERE s.id = r.site_id
AND u.is_active
AND c.is_deleted = FALSE
)
-- AND r.created > f_report_rank_cap()
ORDER BY r.created DESC NULLS LAST
LIMIT 1;
Deve ser mais rápido, ainda. Mais opções:
-
Selecione a primeira linha em cada AGRUPAR POR grupo?
-
Otimizar Consulta GROUP BY para recuperar o registro mais recente por usuário
Velocidade máxima com índice parcial ajustado dinamicamente
Você deve ter notado a parte comentada na última consulta:
AND r.created > f_report_rank_cap()
Você mencionou 50 milhões. linhas, isso é muito. Aqui está uma maneira de acelerar as coisas:
- Crie um
IMMUTABLE
simples função retornando um carimbo de data/hora que é garantido ser mais antigo que as linhas de interesse, sendo o mais jovem possível. - Crie um índice parcial somente em linhas mais novas - com base nesta função.
- Use um
WHERE
condição nas consultas que correspondem à condição do índice. - Crie outra função que atualize esses objetos para a linha mais recente com DDL dinâmico. (Menos uma margem segura caso as linhas mais recentes sejam excluídas / desativadas - se isso puder acontecer)
- Invoque esta função secundária fora do horário de funcionamento com um mínimo de atividade simultânea por cronjob ou sob demanda. Quantas vezes você quiser, não pode fazer mal, só precisa de um pequeno cadeado exclusivo na mesa.
Aqui está uma demonstração de trabalho completa .
@erikcw, você terá que ativar a parte comentada conforme as instruções abaixo.
CREATE TABLE report_rank(created timestamp);
INSERT INTO report_rank VALUES ('2011-11-11 11:11'),(now());
-- initial function
CREATE OR REPLACE FUNCTION f_report_rank_cap()
RETURNS timestamp LANGUAGE sql COST 1 IMMUTABLE AS
$y$SELECT timestamp '-infinity'$y$; -- or as high as you can safely bet.
-- initial index; 1st run indexes whole tbl if starting with '-infinity'
CREATE INDEX report_rank_recent_idx ON report_rank (created DESC NULLS LAST)
WHERE created > f_report_rank_cap();
-- function to update function & reindex
CREATE OR REPLACE FUNCTION f_report_rank_set_cap()
RETURNS void AS
$func$
DECLARE
_secure_margin CONSTANT interval := interval '1 day'; -- adjust to your case
_cap timestamp; -- exclude older rows than this from partial index
BEGIN
SELECT max(created) - _secure_margin
FROM report_rank
WHERE created > f_report_rank_cap() + _secure_margin
/* not needed for the demo; @erikcw needs to activate this
AND EXISTS (
SELECT *
FROM report_site s
JOIN report_profile p ON p.site_id = s.id
JOIN crm_client c ON c.id = p.client_id
JOIN auth_user u ON u.id = c.user_id
WHERE s.id = r.site_id
AND u.is_active
AND c.is_deleted = FALSE)
*/
INTO _cap;
IF FOUND THEN
-- recreate function
EXECUTE format('
CREATE OR REPLACE FUNCTION f_report_rank_cap()
RETURNS timestamp LANGUAGE sql IMMUTABLE AS
$y$SELECT %L::timestamp$y$', _cap);
-- reindex
REINDEX INDEX report_rank_recent_idx;
END IF;
END
$func$ LANGUAGE plpgsql;
COMMENT ON FUNCTION f_report_rank_set_cap()
IS 'Dynamically recreate function f_report_rank_cap()
and reindex partial index on report_rank.';
Ligar:
SELECT f_report_rank_set_cap();
Ver:
SELECT f_report_rank_cap();
Descomente a cláusula
AND r.created > f_report_rank_cap()
na consulta acima e observe a diferença. Verifique se o índice é usado com EXPLAIN ANALYZE
. O manual sobre simultaneidade e
REINDEX
: