PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Adicionar restrição de data e hora a um índice parcial de várias colunas do PostgreSQL


Você obtém uma exceção usando now() porque a função não é IMMUTABLE (obviamente) e, citando o manual :

Vejo duas maneiras de utilizar um índice parcial (muito mais eficiente):

1. Índice parcial com condição usando constante data:

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

Supondo created é definido como timestamp . Não funcionaria para fornecer um timestamp constante para um timestamptz coluna (timestamp with time zone ). A conversão de timestamp para timestamptz (ou vice-versa) depende da configuração de fuso horário atual e é não imutável . Use uma constante de tipo de dados correspondente. Entenda os conceitos básicos de carimbos de data/hora com/sem fuso horário:

Solte e recrie esse índice em horas com pouco tráfego, talvez com um cron job diariamente ou semanalmente (ou o que for bom o suficiente para você). Criar um índice é bastante rápido, especialmente um índice parcial que é comparativamente pequeno. Esta solução também não precisa adicionar nada à tabela.

Supondo que sem acesso simultâneo para a tabela, a recriação automática do índice pode ser feita com uma função 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$;

Ligar:
SELECT f_index_recreate();

now() (como você tinha) é o equivalente a CURRENT_TIMESTAMP e retorna timestamptz . Transmitir para timestamp com now()::timestamp ou use LOCALTIMESTAMP em vez de.

db<>fiddle aqui
Antigo sqlfiddle

Se você tiver que lidar com acesso simultâneo para a tabela, use DROP INDEX CONCURRENTLY e CREATE INDEX CONCURRENTLY . Mas você não pode agrupar esses comandos em uma função porque, por documentação :

Portanto, com duas transações separadas :
CREATE INDEX CONCURRENTLY queries_recent_idx2 ON queries_query (user_sid, created)
WHERE  created > '2013-01-07 00:00'::timestamp;  -- your new condition

Então:
DROP INDEX CONCURRENTLY IF EXISTS queries_recent_idx;

Opcionalmente, renomeie para o nome antigo:
ALTER INDEX queries_recent_idx2 RENAME TO queries_recent_idx;

2. Índice parcial com condição na tag "arquivada"


Adicione um archived marque na sua mesa:
ALTER queries_query ADD COLUMN archived boolean NOT NULL DEFAULT FALSE;

UPDATE a coluna em intervalos de sua escolha para "aposentar" linhas mais antigas e criar um índice como:
CREATE INDEX some_index_name ON queries_query (user_sid, created)
WHERE NOT archived;

Adicione uma condição de correspondência às suas consultas (mesmo que pareça redundante) para permitir que ela use o índice. Verifique com EXPLAIN ANALYZE se o planejador de consultas pega - ele deve ser capaz de usar o índice para consultas em uma data mais recente. Mas não entenderá condições mais complexas que não correspondam exatamente.

Você não precisa descartar e recriar o índice, mas o UPDATE na mesa pode ser mais caro do que a recriação do índice e a mesa fica um pouco maior.

Eu iria com o primeiro opção (recriação do índice). Na verdade, estou usando essa solução em vários bancos de dados. O segundo incorre em atualizações mais caras.

Ambas as soluções mantêm sua utilidade ao longo do tempo, o desempenho se deteriora lentamente à medida que mais linhas desatualizadas são incluídas no índice.