Il punto principale è molto probabilmente che tu JOIN
e GROUP
su tutto solo per ottenere max(created)
. Ottieni questo valore separatamente.
Hai menzionato tutti gli indici necessari qui:su report_rank.created
e sulle chiavi esterne. Stai andando bene lì. (Se sei interessato a qualcosa di meglio di "va bene", continua a leggere !)
Il LEFT JOIN report_site
sarà forzato a un semplice JOIN
dal WHERE
clausola. Ho sostituito un semplice JOIN
. Ho anche semplificato molto la tua sintassi.
Aggiornato a luglio 2015 con query più semplici e veloci e funzioni più intelligenti.
Soluzione per più righe
report_rank.created
è non unico e vuoi tutto le ultime righe.
Utilizzo della funzione finestra rank()
in una sottoquery.
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;
Perché DESC NULLS LAST
?
Soluzione per una riga
Se report_rank.created
è unico oppure sei soddisfatto di ogni 1 riga con 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;
Dovrebbe essere più veloce, comunque. Altre opzioni:
Massima velocità con indice parziale regolato dinamicamente
Potresti aver notato la parte commentata nell'ultima query:
AND r.created > f_report_rank_cap()
Hai menzionato 50 milioni. righe, è molto. Ecco un modo per velocizzare le cose:
- Crea un semplice
IMMUTABLE
funzione che restituisce un timestamp che è garantito essere più vecchio delle righe di interesse pur essendo il più giovane possibile. - Crea un indice parziale solo sulle file più giovani - in base a questa funzione.
- Usa un
WHERE
condizione nelle query che corrispondono alla condizione dell'indice. - Crea un'altra funzione che aggiorni questi oggetti all'ultima riga con DDL dinamico. (meno un margine sicuro nel caso in cui le righe più recenti vengano eliminate / disattivate, se ciò può accadere)
- Richiama questa funzione secondaria nei periodi di inattività con un minimo di attività simultanea per cronjob o su richiesta. Tutte le volte che vuoi, non può fare del male, ha solo bisogno di un breve blocco esclusivo sul tavolo.
Ecco una demo completa funzionante .
@erikcw, dovrai attivare la parte commentata come indicato di seguito.
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.';
Chiama:
SELECT f_report_rank_set_cap();
Vedi:
SELECT f_report_rank_cap();
Decommenta la clausola AND r.created > f_report_rank_cap()
nella query precedente e osserva la differenza. Verifica che l'indice venga utilizzato con EXPLAIN ANALYZE
.
Il manuale sulla concorrenza e REINDEX
: