PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Ajouter une contrainte datetime à un index partiel multi-colonnes PostgreSQL

Vous obtenez une exception en utilisant now() car la fonction n'est pas IMMUTABLE (évidemment) et, en citant le manuel :

Je vois deux manières d'utiliser un index partiel (beaucoup plus efficace) :

1. Index partiel avec condition utilisant constante jour :

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

En supposant created est en fait défini comme timestamp . Cela ne fonctionnerait pas de fournir un timestamp constante pour un timestamptz colonne (timestamp with time zone ). Le casting de timestamp à timestamptz (ou vice versa) dépend du paramètre de fuseau horaire actuel et n'est pas immuable . Utilisez une constante de type de données correspondant. Comprendre les bases des horodatages avec/sans fuseau horaire :

Déposez et recréez cet index aux heures à faible trafic, peut-être avec un travail cron sur une base quotidienne ou hebdomadaire (ou tout ce qui vous convient). La création d'un index est assez rapide, en particulier un index partiel qui est relativement petit. Cette solution n'a pas non plus besoin d'ajouter quoi que ce soit au tableau.

En supposant aucun accès simultané à la table, la recréation automatique de l'index pourrait être effectuée avec une fonction comme celle-ci :

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$;

Appel :

SELECT f_index_recreate();

now() (comme vous l'aviez) est l'équivalent de CURRENT_TIMESTAMP et renvoie timestamptz . Diffuser en timestamp avec now()::timestamp ou utilisez LOCALTIMESTAMP à la place.

db<>violon ici
Ancien sqlfiddle

Si vous devez faire face à un accès simultané au tableau, utilisez DROP INDEX CONCURRENTLY et CREATE INDEX CONCURRENTLY . Mais vous ne pouvez pas encapsuler ces commandes dans une fonction car, par documentation :

Ainsi, avec deux transactions distinctes :

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

Ensuite :

DROP INDEX CONCURRENTLY IF EXISTS queries_recent_idx;

Facultativement, renommer avec l'ancien nom :

ALTER INDEX queries_recent_idx2 RENAME TO queries_recent_idx;

2. Index partiel avec condition sur la balise "archivé"

Ajouter un archived tag à votre tableau :

ALTER queries_query ADD COLUMN archived boolean NOT NULL DEFAULT FALSE;

UPDATE la colonne à des intervalles de votre choix pour "retirer" les anciennes lignes et créer un index comme :

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

Ajoutez une condition de correspondance à vos requêtes (même si elle semble redondante) pour lui permettre d'utiliser l'index. Vérifier avec EXPLAIN ANALYZE si le planificateur de requêtes s'accroche - il devrait pouvoir utiliser l'index pour les requêtes à une date plus récente. Mais il ne comprendra pas les conditions plus complexes qui ne correspondent pas exactement.

Vous n'avez pas besoin de supprimer et de recréer l'index, mais le UPDATE sur la table peut être plus cher que la recréation d'index et la table devient légèrement plus grande.

J'irais avec le premier option (récréation d'index). En fait, j'utilise cette solution dans plusieurs bases de données. La seconde entraîne des mises à jour plus coûteuses.

Les deux solutions conservent leur utilité au fil du temps, les performances se détériorent lentement à mesure que davantage de lignes obsolètes sont incluses dans l'index.