39.3. Visões materializadas #

As visões materializadas no PostgreSQL usam o sistema de regras como as visões, mas persistem os resultados em um formato semelhante a uma tabela. As principais diferenças entre

CREATE MATERIALIZED VIEW minha_visão_mat AS SELECT * FROM minha_tabela;

e

CREATE TABLE minha_visão_mat AS SELECT * FROM minha_tabela;

são que a visão materializada não pode ser atualizada diretamente posteriormente, e que a consulta usada para criar a visão materializada é armazenada exatamente da mesma maneira que a consulta de uma visão é armazenada, para poderem ser gerados novos dados para a visão materializada usando

REFRESH MATERIALIZED VIEW minha_visão_mat;

As informações sobre a visão materializada nos catálogos do sistema do PostgreSQL são exatamente as mesmas de uma tabela ou visão. Portanto, para o analisador uma visão materializada é uma relação, assim como uma tabela ou uma visão. Quando uma visão materializada é referenciada em uma consulta, os dados são retornados diretamente da visão materializada, como em uma tabela; a regra é usada apenas para preencher a visão materializada.

Enquanto o acesso aos dados armazenados em uma visão materializada geralmente seja muito mais rápido do que acessar as tabelas subjacentes diretamente, ou por meio de uma visão, os dados nem sempre são os correntes; ainda assim, às vezes, os dados correntes não são necessários. Considere a seguinte tabela de registro de vendas:

CREATE TABLE invoice (
    invoice_no    integer        PRIMARY KEY,
    seller_no     integer,       -- ID do vendedor
    invoice_date  date,          -- data da venda
    invoice_amt   numeric(13,2)  -- valor da venda
);

Se as pessoas quiserem ser capazes de representar rapidamente os dados históricos das vendas, elas podem querer resumir, e podem não se importar com os dados incompletos da data corrente:

CREATE MATERIALIZED VIEW sales_summary AS
  SELECT
      seller_no,
      invoice_date,
      sum(invoice_amt)::numeric(13,2) as sales_amt
    FROM invoice
    WHERE invoice_date < CURRENT_DATE
    GROUP BY
      seller_no,
      invoice_date;

CREATE UNIQUE INDEX sales_summary_seller
  ON sales_summary (seller_no, invoice_date);

Esta visão materializada pode servir para mostrar um gráfico no painel criado para vendedores. Pode ser agendada uma aplicação para atualizar as estatísticas todas as noites usando a seguinte instrução SQL:

REFRESH MATERIALIZED VIEW sales_summary;

Outro uso para uma visão materializada é permitir o acesso mais rápido aos dados trazidos de um sistema remoto por meio de um empacotador de dados estrangeiros. Um exemplo simples usando file_fdw está a seguir, com temporizações, mas como está usando o cache no sistema local a diferença de desempenho em relação ao acesso a um sistema remoto geralmente seria maior do que o mostrado aqui. Note que também se está explorando a capacidade de colocar um índice na visão materializada, enquanto file_fdw não oferece suporte a índices; esta vantagem pode não se aplicar a outros tipos de acesso a dados estrangeiros.

Configuração:

CREATE EXTENSION file_fdw;
CREATE SERVER local_file FOREIGN DATA WRAPPER file_fdw;
CREATE FOREIGN TABLE words (word text NOT NULL)
  SERVER local_file
  OPTIONS (filename '/usr/share/dict/words');
CREATE MATERIALIZED VIEW wrd AS SELECT * FROM words;
CREATE UNIQUE INDEX wrd_word ON wrd (word);
CREATE EXTENSION pg_trgm;
CREATE INDEX wrd_trgm ON wrd USING gist (word gist_trgm_ops);
VACUUM ANALYZE wrd;

Agora vamos verificar a ortografia de uma palavra. Usando file_fdw diretamente:

SELECT count(*) FROM words WHERE word = 'caterpiler';

 count
-------
     0
(1 linha)

Com o EXPLAIN ANALYZE, se vê:

 Aggregate  (cost=21763.99..21764.00 rows=1 width=0) (actual time=188.180..188.181 rows=1.00 loops=1)
   ->  Foreign Scan on words  (cost=0.00..21761.41 rows=1032 width=0) (actual time=188.177..188.177 rows=0.00 loops=1)
         Filter: (word = 'caterpiler'::text)
         Rows Removed by Filter: 479829
         Foreign File: /usr/share/dict/words
         Foreign File Size: 4953699
 Planning time: 0.118 ms
 Execution time: 188.273 ms

Se for usada a visão materializada, a consulta será muito mais rápida:

 Aggregate  (cost=4.44..4.45 rows=1 width=0) (actual time=0.042..0.042 rows=1.00 loops=1)
   ->  Index Only Scan using wrd_word on wrd  (cost=0.42..4.44 rows=1 width=0) (actual time=0.039..0.039 rows=0.00 loops=1)
         Index Cond: (word = 'caterpiler'::text)
         Heap Fetches: 0
         Index Searches: 1
 Planning time: 0.164 ms
 Execution time: 0.117 ms

De qualquer forma, a consulta acima está escrita incorretamente, então vamos procurar o que se deseja. Novamente usando file_fdw e pg_trgm:

SELECT word FROM words ORDER BY word <-> 'caterpiler' LIMIT 10;

     word
---------------
 cater
 caterpillar
 Caterpillar
 caterpillars
 caterpillar's
 Caterpillar's
 caterer
 caterer's
 caters
 catered
(10 linhas)

 Limit  (cost=11583.61..11583.64 rows=10 width=32) (actual time=1431.591..1431.594 rows=10.00 loops=1)
   ->  Sort  (cost=11583.61..11804.76 rows=88459 width=32) (actual time=1431.589..1431.591 rows=10.00 loops=1)
         Sort Key: ((word <-> 'caterpiler'::text))
         Sort Method: top-N heapsort  Memory: 25kB
         ->  Foreign Scan on words  (cost=0.00..9672.05 rows=88459 width=32) (actual time=0.057..1286.455 rows=479829.00 loops=1)
               Foreign File: /usr/share/dict/words
               Foreign File Size: 4953699
 Planning time: 0.128 ms
 Execution time: 1431.679 ms

Usando a visão materializada:

 Limit  (cost=0.29..1.06 rows=10 width=10) (actual time=187.222..188.257 rows=10.00 loops=1)
   ->  Index Scan using wrd_trgm on wrd  (cost=0.29..37020.87 rows=479829 width=10) (actual time=187.219..188.252 rows=10.00 loops=1)
         Order By: (word <-> 'caterpiler'::text)
         Index Searches: 1
 Planning time: 0.196 ms
 Execution time: 198.640 ms

Se for possível realizar a atualização periódica dos dados remotos no banco de dados local, o benefício de desempenho poderá ser substancial.