sql >> Database >  >> RDS >> PostgreSQL

Haal het laatste kind per ouder uit de grote tafel - de zoekopdracht is te traag

Het belangrijkste punt is hoogstwaarschijnlijk dat je JOIN en GROUP over alles om max(created) . te krijgen . Verkrijg deze waarde apart.

Je noemde alle indexen die hier nodig zijn:op report_rank.created en op de buitenlandse sleutels. Het gaat je goed daar. (Als je geïnteresseerd bent in beter dan "alright", blijf lezen !)

De LEFT JOIN report_site wordt gedwongen tot een eenvoudige JOIN door de WHERE clausule. Ik heb een gewone JOIN . vervangen . Ik heb je syntaxis ook veel vereenvoudigd.

Bijgewerkt in juli 2015 met eenvoudigere, snellere zoekopdrachten en slimmere functies.

Oplossing voor meerdere rijen

report_rank.created is niet uniek en je wilt alles de laatste rijen.
De vensterfunctie gebruiken rank() in een subquery.

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;

Waarom DESC NULLS LAST ?

Oplossing voor één rij

Als report_rank.created is uniek of je bent tevreden met elke rij met 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;

Moet nog sneller kunnen. Meer opties:

Ultieme snelheid met dynamisch aangepaste gedeeltelijke index

Je hebt misschien het becommentarieerde gedeelte in de laatste vraag opgemerkt:

AND  r.created > f_report_rank_cap()

U noemde 50 miljoen. rijen, dat is veel. Hier is een manier om dingen te versnellen:

  • Maak een eenvoudige IMMUTABLE functie die een tijdstempel retourneert dat gegarandeerd ouder is dan de rijen van interesse, terwijl het zo jong mogelijk is.
  • Maak een gedeeltelijke index alleen op jongere rijen - gebaseerd op deze functie.
  • Gebruik een WHERE voorwaarde in zoekopdrachten die overeenkomt met de indexvoorwaarde.
  • Maak een andere functie die deze objecten bijwerkt naar de laatste rij met dynamische DDL. (Min een veilige marge in het geval dat de nieuwste rij(en) worden verwijderd / gedeactiveerd - als dat kan gebeuren)
  • Roep deze secundaire functie op buiten de tijd met een minimum aan gelijktijdige activiteit per cronjob of op aanvraag. Zo vaak als je wilt, kan geen kwaad, het heeft alleen een kort exclusief slotje op tafel nodig.

Hier is een compleet werkende demo .
@erikcw, je moet het gedeelte met commentaar activeren zoals hieronder wordt aangegeven.

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.';

Bel:

SELECT f_report_rank_set_cap();

Zie:

SELECT f_report_rank_cap();

Maak commentaar op de clausule AND r.created > f_report_rank_cap() in de bovenstaande query en observeer het verschil. Controleer of de index wordt gebruikt met EXPLAIN ANALYZE .

De handleiding over gelijktijdigheid en REINDEX :



  1. virtualenv die verplaatste bibliotheken kan vinden (zoals mysqlclient lib voor MySQLdb)

  2. Hoe een bulkdatabase invoegen in Yii2?

  3. Geheel getal buiten bereik in PostgreSQL-database

  4. Wat is de beste manier om het totale aantal records in een mysql-tabel met php te krijgen?