sql >> Base de Datos >  >> RDS >> PostgreSQL

Agregar restricción de fecha y hora a un índice parcial de varias columnas de PostgreSQL

Obtienes una excepción usando now() porque la función no es IMMUTABLE (obviamente) y, citando el manual :

Veo dos formas de utilizar un índice parcial (mucho más eficiente):

1. Índice parcial con condición usando constante fecha:

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

Suponiendo created en realidad se define como timestamp . No funcionaría proporcionar una timestamp constante para un timestamptz columna (timestamp with time zone ). El elenco de timestamp a timestamptz (o viceversa) depende de la configuración de la zona horaria actual y no es inmutable . Utilice una constante de tipo de datos coincidente. Comprender los conceptos básicos de las marcas de tiempo con/sin zona horaria:

Soltar y recrear ese índice en horas con poco tráfico, tal vez con un trabajo cron diario o semanal (o lo que sea lo suficientemente bueno para usted). Crear un índice es bastante rápido, especialmente un índice parcial que es comparativamente pequeño. Esta solución tampoco necesita agregar nada a la tabla.

Suponiendo ningún acceso simultáneo a la tabla, la recreación automática del índice podría realizarse con una función como esta:

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

Llamar:

SELECT f_index_recreate();

now() (como tú tenías) es el equivalente de CURRENT_TIMESTAMP y devuelve timestamptz . Transmitir a timestamp con now()::timestamp o usa LOCALTIMESTAMP en su lugar.

db<>fiddle aquí
Antiguo sqlfiddle

Si tiene que lidiar con acceso concurrente a la tabla, use DROP INDEX CONCURRENTLY y CREATE INDEX CONCURRENTLY . Pero no puede envolver estos comandos en una función porque, por documentación :

Entonces, con dos transacciones separadas :

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

Entonces:

DROP INDEX CONCURRENTLY IF EXISTS queries_recent_idx;

Opcionalmente, cambie el nombre al nombre antiguo:

ALTER INDEX queries_recent_idx2 RENAME TO queries_recent_idx;

2. Índice parcial con condición en la etiqueta "archivado"

Agregar un archived etiqueta a tu mesa:

ALTER queries_query ADD COLUMN archived boolean NOT NULL DEFAULT FALSE;

UPDATE la columna a intervalos de su elección para "retirar" las filas más antiguas y crear un índice como:

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

Agregue una condición coincidente a sus consultas (incluso si parece redundante) para permitirle usar el índice. Verifique con EXPLAIN ANALYZE si el planificador de consultas se da cuenta:debería poder usar el índice para consultas en una fecha más reciente. Pero no entenderá condiciones más complejas que no coincidan exactamente.

No tiene que soltar y volver a crear el índice, pero el UPDATE en la mesa puede ser más caro que el juego de índice y la mesa se vuelve un poco más grande.

Yo iría con el primero opción (recreación índice). De hecho, estoy usando esta solución en varias bases de datos. El segundo incurre en actualizaciones más costosas.

Ambas soluciones conservan su utilidad con el tiempo, el rendimiento se deteriora lentamente a medida que se incluyen más filas obsoletas en el índice.