PostgreSQL: Views e Views Materializadas no PostgreSQL

Última atualização: 2026-08-26

1. O Que Você Vai Aprender


2. A História

Alice é uma engenheira de dados em uma plataforma de e-commerce. A equipe de operações revisa um relatório resumido de vendas todos os dias às 8:00, que une 5 tabelas, agrega 3 milhões de registros de pedidos e leva 2 horas para consultar.

O chefe dela disse: "O relatório pode voltar instantaneamente?"

Alice definiu a consulta como uma view materializada, atualizada automaticamente todos os dias às 4:00 da manhã. A consulta do relatório pré-calculado caiu de 2 horas para 3 segundos. Mas os dados da view materializada não são em tempo real—esse é o compromisso entre views comuns e views materializadas.


3. Conceito: Views Comuns

(1) Sintaxe do CREATE VIEW

SQL
CREATE [OR REPLACE] VIEW nome_view [(aliases_coluna)] AS
  SELECT ...;

Uma view é uma definição de consulta armazenada—não armazena dados. Cada vez que você consulta uma view, o PostgreSQL expande (reescreve) a definição da view na consulta subjacente e a executa.

Propriedade Descrição
O que é armazenado Apenas o texto da consulta
Atualidade dos dados Tempo real, sempre reflete os dados mais recentes da tabela base
Desempenho Igual a executar a consulta subjacente diretamente
Espaço usado Quase nenhum

▶ Exemplo: Criar uma View de Resumo de Vendas

SQL
CREATE VIEW v_daily_sales AS
SELECT
  order_date,
  region,
  COUNT(*)               AS order_count,
  SUM(amount)            AS total_revenue,
  AVG(amount)            AS avg_order_value
FROM orders
GROUP BY order_date, region;

Output:

TEXT 📖 Somente leitura
 count 
-------
     5
(1 row)

▶ Exemplo: Consultar a View

SQL
SELECT * FROM v_daily_sales
WHERE order_date >= '2025-01-01'
ORDER BY total_revenue DESC;

Output:

TEXT 📖 Somente leitura
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

Equivalente a escrever o SELECT subjacente diretamente—o PostgreSQL o expande automaticamente.

(2) ALTER VIEW e DROP VIEW

Operação Sintaxe Descrição
Renomear ALTER VIEW v RENAME TO v_novo Renomear a view
Definir coluna padrão ALTER VIEW v ALTER COLUMN c SET DEFAULT d Alterar um valor padrão de coluna
Definir proprietário ALTER VIEW v OWNER TO role Alterar o proprietário
Excluir DROP VIEW [IF EXISTS] v [CASCADE] CASCADE exclui views dependentes

▶ Exemplo: Modificar e Excluir uma View

SQL
ALTER VIEW v_daily_sales RENAME TO v_daily_summary;

DROP VIEW IF EXISTS v_daily_summary CASCADE;

Output:

TEXT 📖 Somente leitura
-- instrução SQL executada com sucesso

(3) Mecanismo de Expansão da View

100%
flowchart LR
    A["SELECT * FROM v_daily_sales"] --> B["Reescrever<br/>expandir definição da view"]
    B --> C["SELECT order_date, region, COUNT(*)...<br/>FROM orders<br/>GROUP BY ..."]
    C --> D[Otimizador]
    D --> E[Executar]
    style B fill:#fff9c4
    style D fill:#c8e6c9

4. Conceito: Views Atualizáveis

(1) Quais Views São Atualizáveis

No PostgreSQL, views simples que atendem a todas as seguintes condições são automaticamente atualizáveis (suportam INSERT / UPDATE / DELETE):

Condição Descrição
FROM de uma única tabela base Sem JOIN
Sem GROUP BY / HAVING Sem agregação
Sem DISTINCT Sem desduplicação
Sem funções de janela Sem OVER
Sem operações de conjunto Sem UNION / INTERSECT / EXCEPT
Colunas SELECT são colunas da tabela base Sem expressões/colunas calculadas

▶ Exemplo: View Atualizável

SQL
CREATE VIEW v_active_users AS
SELECT user_id, name, email, status
FROM users
WHERE status = 'active';

Output:

TEXT 📖 Somente leitura
CREATE TABLE
SQL
UPDATE v_active_users SET name = 'Alice Wang' WHERE user_id = 1;

DELETE FROM v_active_users WHERE user_id = 99;

INSERT INTO v_active_users (user_id, name, email, status)
VALUES (101, 'Charlie', 'charlie@example.com', 'active');

(2) WITH CHECK OPTION

Por padrão, linhas atualizadas/inseridas através de uma view podem "escapar" do escopo da view (ex.: alterar status para 'inactive', e essa linha não aparece mais na view). WITH CHECK OPTION proíbe tal escape.

Opção Comportamento
Sem CHECK OPTION Escape permitido; linhas atualizadas podem não aparecer mais na view
WITH CHECK OPTION Escape proibido; linhas atualizadas devem ainda satisfazer a condição da view
WITH CASCADED CHECK OPTION View atual + views dependentes verificadas (recursivo)
WITH LOCAL CHECK OPTION Apenas a condição da view atual verificada

▶ Exemplo: WITH CHECK OPTION Previne Escape

SQL
CREATE VIEW v_active_users_strict AS
SELECT user_id, name, email, status
FROM users
WHERE status = 'active'
WITH CHECK OPTION;
SQL
UPDATE v_active_users_strict SET status = 'inactive' WHERE user_id = 1;
TEXT 📖 Somente leitura
ERRO: nova linha viola a check option da view "v_active_users_strict"
DETALHE: Linha com falha contém (1, ..., inactive).

▶ Exemplo: Inserir em View Atualizável e Depois Consultar

SQL
INSERT INTO v_active_users (user_id, name, email, status)
VALUES (200, 'Bob', 'bob@example.com', 'active');

SELECT * FROM v_active_users WHERE user_id = 200;
TEXT 📖 Somente leitura
 user_id | name |       email        | status
---------+------+--------------------+--------
     200 | Bob  | bob@example.com    | active

5. Conceito: Views Materializadas

(1) Visão Geral de MATERIALIZED VIEW

Uma view materializada realmente armazena o resultado da consulta em disco; as consultas leem os dados pré-calculados diretamente, sem reexecutar a consulta subjacente. Esta é uma especialidade do PostgreSQL.

Propriedade View comum View materializada
O que é armazenado Texto da consulta Texto da consulta + dados do resultado
Atualidade dos dados Tempo real Atualiza apenas na atualização
Desempenho da consulta Igual à consulta subjacente Extremamente rápido (lê pré-calculado)
Espaço usado Quase nenhum Mesmo tamanho do conjunto de resultados
Atualizável Views simples podem ser Sem DML direto
Método de atualização Não necessário REFRESH MATERIALIZED VIEW

▶ Exemplo: Criar uma View Materializada

SQL
CREATE MATERIALIZED VIEW mv_monthly_sales AS
SELECT
  DATE_TRUNC('month', order_date)::date AS month,
  region,
  COUNT(*)    AS order_count,
  SUM(amount) AS total_revenue,
  AVG(amount) AS avg_order_value
FROM orders
GROUP BY DATE_TRUNC('month', order_date), region
WITH DATA;

Output:

TEXT 📖 Somente leitura
 count 
-------
     5
(1 row)

▶ Exemplo: Consultar a View Materializada

SQL
SELECT * FROM mv_monthly_sales
WHERE month >= '2025-01-01'
ORDER BY total_revenue DESC;

Output:

TEXT 📖 Somente leitura
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

Retorna em milissegundos, porque os dados já estão pré-calculados e armazenados.

(2) REFRESH

Sintaxe Comportamento Bloqueio Velocidade
REFRESH MATERIALIZED VIEW mv Atualização completa, substitui todos os dados Adquire bloqueio exclusivo, bloqueia leituras Mais lento
REFRESH MATERIALIZED VIEW CONCURRENTLY mv Atualização incremental (precisa de índice único) Não bloqueia leituras Mais rápido

CONCURRENTLY é uma especialidade do PostgreSQL: a view permanece consultável durante a atualização—o negócio não é bloqueado.

▶ Exemplo: Atualização Completa

SQL
REFRESH MATERIALIZED VIEW mv_monthly_sales;

Output:

TEXT 📖 Somente leitura
-- instrução SQL executada com sucesso

Todos os SELECTs nesta view materializada são bloqueados durante a atualização.

▶ Exemplo: Atualização Incremental CONCURRENTLY

SQL
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_monthly_sales;

Output:

TEXT 📖 Somente leitura
-- instrução SQL executada com sucesso

Pré-requisito: a view materializada deve ter pelo menos um índice único.

SQL
CREATE UNIQUE INDEX idx_mv_monthly_sales_pk
  ON mv_monthly_sales (month, region);

REFRESH MATERIALIZED VIEW CONCURRENTLY mv_monthly_sales;

(3) WITH DATA vs WITH NO DATA

Opção Comportamento
WITH DATA Preencher dados imediatamente na criação (padrão)
WITH NO DATA Não preencher na criação; deve fazer REFRESH antes da primeira consulta

▶ Exemplo: Preenchimento Diferido

SQL
CREATE MATERIALIZED VIEW mv_expensive_report AS
SELECT ... FROM ... WITH NO DATA;

REFRESH MATERIALIZED VIEW mv_expensive_report;

Output:

TEXT 📖 Somente leitura
CREATE TABLE

▶ Exemplo: Atualização Automática Programada (extensão pg_cron)

SQL
SELECT cron.schedule(
  'refresh_monthly_sales',
  '0 4 * * *',
  $$REFRESH MATERIALIZED VIEW CONCURRENTLY mv_monthly_sales$$
);

Output:

TEXT 📖 Somente leitura
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

Atualiza automaticamente todos os dias às 4:00 da manhã.


6. Views vs Views Materializadas vs Tabelas Temporárias

Dimensão View comum View materializada Tabela temporária
Armazena definição da consulta Sim Sim Não
Armazena dados Não Sim Sim
Atualidade dos dados Tempo real Atualização manual Manutenção manual
Desempenho da consulta Depende da consulta subjacente Extremamente rápido Rápido
Persistência entre sessões Sim Sim Não (desaparece após a sessão)
Suporta índices Não (usa índices da tabela base) Sim Sim
Suporta DML Views simples atualizáveis Não Sim
Cenário típico Simplificar consultas Pré-computação de relatórios Resultados intermediários temporários
100%
classDiagram
    class View {
        +armazena texto da consulta
        +dados em tempo real
        +view simples permite DML
        +WITH CHECK OPTION
    }
    class MaterializedView {
        +armazena texto da consulta+dados
        +REFRESH para atualizar
        +CONCURRENTLY
        +indexável
        +WITH DATA/NO DATA
    }
    class TempTable {
        +armazena apenas dados
        +desaparece após a sessão
        +totalmente compatível com DML
        +indexável
    }
    View <|-- MaterializedView : estende
    MaterializedView ..|> TempTable : desempenho similar

7. Melhores Práticas de Gerenciamento de Views

(1) Convenções de Nomenclatura

Tipo Prefixo recomendado Exemplo
View comum v_ v_active_users
View materializada mv_ mv_monthly_sales
Tabela temporária tmp_ tmp_import_data

(2) Dependências de Views e Segurança

Operação Risco Solução
Excluir tabela base View se torna inválida DROP TABLE CASCADE exclui automaticamente views dependentes
Alterar coluna da tabela base View pode gerar erro Atualizar definição com CREATE OR REPLACE VIEW
Controle de privilégio View pode limitar visibilidade de colunas GRANT SELECT ON view TO role

▶ Exemplo: Privilégios em Nível de Coluna com uma View

SQL
CREATE VIEW v_user_public AS
SELECT user_id, name
FROM users;

GRANT SELECT ON v_user_public TO reporter_role;

REVOKE SELECT ON users FROM reporter_role;

Output:

TEXT 📖 Somente leitura
CREATE TABLE

reporter_role só pode ver user_id e name, não colunas sensíveis como email.


8. Exemplo Completo

Otimização de relatório de Alice—pré-computação com view materializada + atualização CONCURRENTLY + view para simplificar consultas:

SQL
CREATE MATERIALIZED VIEW mv_sales_report AS
SELECT
  DATE_TRUNC('month', o.order_date)::date AS month,
  p.category,
  o.region,
  COUNT(*)                                AS order_count,
  COUNT(DISTINCT o.customer_id)           AS unique_customers,
  SUM(o.amount)                           AS total_revenue,
  SUM(o.amount) FILTER (WHERE o.amount >= 50000) AS big_deal_revenue,
  AVG(o.amount)                           AS avg_order_value,
  SUM(oi.quantity * oi.unit_price)        AS total_gmv
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id
GROUP BY DATE_TRUNC('month', o.order_date), p.category, o.region
WITH DATA;

CREATE UNIQUE INDEX idx_mv_sales_report_pk
  ON mv_sales_report (month, category, region);

CREATE VIEW v_sales_dashboard AS
SELECT
  month,
  region,
  SUM(total_revenue)  AS region_revenue,
  SUM(order_count)    AS region_orders,
  SUM(unique_customers) AS region_customers
FROM mv_sales_report
GROUP BY month, region;

REFRESH MATERIALIZED VIEW CONCURRENTLY mv_sales_report;

9. Fluxo de Execução

O fluxo de criação e atualização de uma view materializada:

100%
flowchart TD
    A[CREATE MATERIALIZED VIEW] --> B[Executar consulta subjacente]
    B --> C[Gravar resultados em disco]
    C --> D[Pode criar índices]
    D --> E[Consulta lê dados do disco diretamente]
    E --> F{Precisa atualizar?}
    F -->|REFRESH| G[Reexecutar consulta subjacente]
    G --> H[Substituir dados antigos]
    H --> E
    F -->|CONCURRENTLY| I[Atualização incremental por comparação]
    I --> J[Não bloqueia leituras]
    J --> E

    style A fill:#e1f5fe
    style E fill:#c8e6c9
    style I fill:#fff9c4
Passo Descrição
Criar Executar consulta subjacente, persistir resultado em disco
Consultar Ler dados do disco diretamente, sem executar consulta subjacente
Atualizar Reexecutar consulta subjacente, substituir dados antigos
CONCURRENTLY Atualização incremental, não bloqueia consultas durante a atualização

❓ Perguntas Frequentes

P: Views comuns afetam o desempenho? R: Não. Uma view é apenas reescrita de consulta—o desempenho é idêntico a escrever o SQL subjacente diretamente. Uma view complexa não adiciona sobrecarga extra.

P: Por que não consigo encontrar dados inseridos após inserir através de uma view? R: A linha inserida pode não satisfazer a condição WHERE da view. Use WITH CHECK OPTION para prevenir isso.

P: Por que a atualização CONCURRENTLY precisa de um índice único? R: CONCURRENTLY faz uma atualização incremental comparando dados antigos e novos; o índice único identifica cada linha. Sem ele, você recebe um erro.

P: Uma view materializada suporta UPDATE/DELETE direto? R: Não. Uma view materializada é somente leitura; só pode ser atualizada via REFRESH. Para alterá-la, atualize os dados da tabela base e depois faça REFRESH novamente.

P: Qual a diferença entre WITH CASCADED CHECK OPTION e WITH LOCAL CHECK OPTION? R: CASCADED verifica a condição da view atual e de todas as views dependentes; LOCAL verifica apenas a condição da view atual. Com views aninhadas, CASCADED é mais seguro.

P: Uma view materializada pode ser construída sobre outra view materializada? R: Sim. O PostgreSQL suporta views materializadas aninhadas, mas você deve atualizá-las manualmente na ordem de dependência.


📖 Resumo


📝 Exercícios

  1. ⭐ Crie uma view v_recent_orders que consulte pedidos dos últimos 30 dias, incluindo customer_name e product_name.
  2. ⭐ Crie uma view atualizável v_active_customers que mostre apenas clientes com status = 'active', e adicione WITH CHECK OPTION.
  3. ⭐⭐ Crie uma view materializada mv_daily_category_sales que resuma vendas por dia e categoria, e adicione um índice único para suportar atualização CONCURRENTLY.
  4. ⭐⭐ Projete um esquema de view aninhada: uma view materializada pré-calcula dados detalhados, e uma view comum faz agregação dimensional sobre a view materializada.
  5. ⭐⭐⭐ Escreva uma tarefa programada pg_cron que atualize mv_daily_category_sales todos os dias às 3:00 da manhã, e registre um alerta quando a atualização demorar mais de 5 minutos.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%