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.