sql >> Databasteknik >  >> RDS >> PostgreSQL

Lägg till datetime-begränsning i ett PostgreSQL-partiellt index med flera kolumner

Du får ett undantag med now() eftersom funktionen inte är IMMUTABLE (uppenbarligen) och citerar manualen :

Jag ser två sätt att använda ett (mycket effektivare) delindex:

1. Partiellt index med villkor som använder konstant datum:

CREATE INDEX queries_recent_idx ON queries_query (user_sid, created)
WHERE created > '2013-01-07 00:00'::timestamp;

Förutsatt created är faktiskt definierad som timestamp . Det skulle inte fungera att tillhandahålla en timestamp konstant för en timestamptz kolumn (timestamp with time zone ). Cast från timestamp till timestamptz (eller vice versa) beror på den aktuella tidszonsinställningen och är inte oföränderlig . Använd en konstant för matchande datatyp. Förstå grunderna för tidsstämplar med/utan tidszon:

Släpp och återskapa det indexet på timmar med låg trafik, kanske med ett cron-jobb dagligen eller veckovis (eller vad som är tillräckligt bra för dig). Att skapa ett index går ganska snabbt, särskilt ett partiellt index som är relativt litet. Denna lösning behöver inte heller lägga till något i tabellen.

Förutsatt att ingen samtidig åtkomst till tabellen kan automatisk indexåterskapning göras med en funktion som denna:

CREATE OR REPLACE FUNCTION f_index_recreate()
  RETURNS void
  LANGUAGE plpgsql AS
$func$
BEGIN
   DROP INDEX IF EXISTS queries_recent_idx;
   EXECUTE format('
      CREATE INDEX queries_recent_idx
      ON queries_query (user_sid, created)
      WHERE created > %L::timestamp'
    , LOCALTIMESTAMP - interval '30 days');  -- timestamp constant
--  , now() - interval '30 days');           -- alternative for timestamptz
END
$func$;

Ring:

SELECT f_index_recreate();

now() (som du hade) motsvarar CURRENT_TIMESTAMP och returnerar timestamptz . Casta till timestamp med now()::timestamp eller använd LOCALTIMESTAMP istället.

db<>fiol här
Gammal sqlfiddle

Om du måste hantera samtidig åtkomst i tabellen, använd DROP INDEX CONCURRENTLY och CREATE INDEX CONCURRENTLY . Men du kan inte slå in dessa kommandon i en funktion eftersom, per dokumentation :

Så med två separata transaktioner :

CREATE INDEX CONCURRENTLY queries_recent_idx2 ON queries_query (user_sid, created)
WHERE  created > '2013-01-07 00:00'::timestamp;  -- your new condition

Sedan:

DROP INDEX CONCURRENTLY IF EXISTS queries_recent_idx;

Om du vill kan du byta namn till det gamla namnet:

ALTER INDEX queries_recent_idx2 RENAME TO queries_recent_idx;

2. Delvis index med villkor på "arkiverad"-tagg

Lägg till en archived tagga till ditt bord:

ALTER queries_query ADD COLUMN archived boolean NOT NULL DEFAULT FALSE;

UPDATE kolumnen med intervaller som du väljer att "gå tillbaka" på äldre rader och skapa ett index som:

CREATE INDEX some_index_name ON queries_query (user_sid, created)
WHERE NOT archived;

Lägg till ett matchande villkor i dina frågor (även om det verkar överflödigt) så att det kan använda indexet. Kontrollera med EXPLAIN ANALYZE om frågeplaneraren hakar på - den ska kunna använda indexet för frågor på ett nyare datum. Men det kommer inte att förstå mer komplexa förhållanden som inte matchar exakt.

Du behöver inte släppa och återskapa indexet, utan UPDATE på bordet kan vara dyrare än indexrekreation och bordet blir något större.

Jag skulle gå med den första alternativ (index rekreation). Faktum är att jag använder den här lösningen i flera databaser. Den andra medför dyrare uppdateringar.

Båda lösningarna behåller sin användbarhet över tid, prestandan försämras långsamt när fler föråldrade rader ingår i indexet.




  1. Fastnade i att bygga MySQL-fråga

  2. MySQL BESTÄLLNING AV rand(), namn ASC

  3. Trunkera tabell inom transaktion

  4. PostgreSQL-uppdateringar med flera rader i Node.js