Aula 15: Views — criando e gerenciando visões de dados

Aula 15: Views — criando e gerenciando visões de dados

Bem-vindo à Aula 15 do curso MySQL — Do Zero ao Avançado. Hoje, 8 de julho de 2026, mergulhamos em um dos recursos mais estratégicos do MySQL para profissionais de infraestrutura, desenvolvimento backend e análise de dados: as Views (visões). Em ambientes corporativos onde centenas de tabelas coexistem e as regras de negócio mudam constantemente, as Views funcionam como uma camada de abstração que simplifica consultas complexas, reforça a segurança e promove a independência lógica dos dados. Nesta aula, vamos explorar o conceito de visões de dados, sua criação, gerenciamento, boas práticas e as armadilhas que podem comprometer a performance se não forem bem compreendidas. Em nossos projetos na JRT Technology Solutions, utilizamos Views diariamente para isolar lógicas de relatório, ocultar colunas sensíveis como senhas e dados financeiros críticos, e garantir que aplicações legadas não quebrem quando a estrutura física das tabelas precisa evoluir.

Ao concluir esta aula, você será capaz de projetar Views simples e complexas utilizando joins, subqueries e funções agregadas, compreenderá as limitações das Views atualizáveis (updatable views) e dominará a cláusula WITH CHECK OPTION para impor restrições de integridade. Também aprenderá a listar, modificar e excluir visões do dicionário de dados, identificar problemas de desempenho com o comando EXPLAIN aplicado a Views, e resolver os erros mais comuns enfrentados por DBAs em produção. O conteúdo é denso, com mais de 4.000 palavras, repleto de exemplos reais executáveis do início ao fim — sem atalhos, sem “e assim por diante”.

Se você acompanhou as aulas anteriores, já tem um ambiente MySQL funcional, sabe criar bancos, tabelas, índices e executar consultas com SELECT, JOIN e GROUP BY. Esse conhecimento é essencial, pois uma View nada mais é do que uma consulta armazenada com nome. Se ainda há dúvidas sobre esses fundamentos, recomendamos revisar as Aulas 8 a 12 antes de prosseguir. Para os demais, prepare seu terminal, abra o cliente MySQL e venha dominar as Views com a profundidade que o mercado exige.

O que você vai aprender nesta aula

  • O conceito de View como consulta armazenada e suas vantagens em cenários reais de produção
  • A sintaxe completa do comando CREATE VIEW e suas cláusulas opcionais
  • Como construir Views simples, com joins, subconsultas e funções de agregação
  • A diferença entre Views materializadas (inexistentes nativamente no MySQL) e Views lógicas, e como simular materialização
  • O funcionamento das Views atualizáveis e as restrições que as tornam read-only
  • O uso avançado da cláusula WITH CHECK OPTIONcascaded e local
  • Comandos de gerenciamento: ALTER VIEW, DROP VIEW, SHOW CREATE VIEW e RENAME TABLE para views
  • Impactos de performance, uso do EXPLAIN em Views e o perigo das Views aninhadas
  • Os 5 erros mais comuns ao trabalhar com Views e como corrigi-los de forma definitiva
  • Métricas de verificação e scripts de diagnóstico para validar a integridade das suas Views

Pré-requisitos e Ambiente

Para executar todos os exemplos desta aula sem interrupções, você precisa de um servidor MySQL na versão 8.0 ou superior — recomendamos a 8.0.35 ou mais recente. O MySQL 8.0 trouxe melhorias significativas no tratamento de Views com Common Table Expressions (CTEs) e funções de janela, que exploraremos em exemplos. Se você estiver usando MySQL 5.7, a maioria dos comandos funcionará, mas alguns recursos como WITH RECURSIVE dentro de Views estarão indisponíveis. Em nossos projetos na JRT Technology Solutions, padronizamos o MySQL 8.0 para todos os novos deployments justamente por essas vantagens.

Certifique-se de ter um usuário com privilégios CREATE VIEW, SHOW VIEW e DROP sobre o banco de dados de teste. Se você é o administrador root, já possui todos os privilégios. Vamos criar um banco de dados chamado aula_views e populá-lo com tabelas de exemplo que simulam um sistema de vendas — clientes, produtos e pedidos. Execute cada bloco de código na ordem exata que apresentamos. A saída esperada aparece logo após cada comando, para que você possa comparar e validar seu progresso.

Abra seu terminal e conecte-se ao MySQL:

# Conectando ao MySQL como root (substitua pela sua senha)
mysql -u root -p

Dentro do prompt do MySQL, vamos criar o ambiente de testes:

-- Criação do banco de dados da aula
CREATE DATABASE IF NOT EXISTS aula_views
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;

-- Seleciona o banco recém-criado
USE aula_views;

-- Tabela de clientes
CREATE TABLE clientes (
    cliente_id INT AUTO_INCREMENT PRIMARY KEY,
    nome VARCHAR(100) NOT NULL,
    email VARCHAR(150) UNIQUE NOT NULL,
    cidade VARCHAR(80),
    data_cadastro DATE NOT NULL,
    ativo TINYINT(1) DEFAULT 1
) ENGINE=InnoDB;

-- Tabela de produtos
CREATE TABLE produtos (
    produto_id INT AUTO_INCREMENT PRIMARY KEY,
    descricao VARCHAR(200) NOT NULL,
    preco DECIMAL(10,2) NOT NULL CHECK (preco > 0),
    estoque INT NOT NULL DEFAULT 0,
    categoria VARCHAR(50)
) ENGINE=InnoDB;

-- Tabela de pedidos (cabeçalho)
CREATE TABLE pedidos (
    pedido_id INT AUTO_INCREMENT PRIMARY KEY,
    cliente_id INT NOT NULL,
    data_pedido DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    status ENUM('pendente','aprovado','enviado','cancelado') NOT NULL DEFAULT 'pendente',
    FOREIGN KEY (cliente_id) REFERENCES clientes(cliente_id)
        ON DELETE RESTRICT
        ON UPDATE CASCADE
) ENGINE=InnoDB;

-- Tabela de itens dos pedidos (detalhe)
CREATE TABLE itens_pedido (
    item_id INT AUTO_INCREMENT PRIMARY KEY,
    pedido_id INT NOT NULL,
    produto_id INT NOT NULL,
    quantidade INT NOT NULL CHECK (quantidade > 0),
    preco_unitario DECIMAL(10,2) NOT NULL,
    FOREIGN KEY (pedido_id) REFERENCES pedidos(pedido_id)
        ON DELETE CASCADE
        ON UPDATE CASCADE,
    FOREIGN KEY (produto_id) REFERENCES produtos(produto_id)
        ON DELETE RESTRICT
        ON UPDATE CASCADE
) ENGINE=InnoDB;
Query OK, 1 row affected (0,01 sec)
Query OK, 0 rows affected (0,00 sec)
Query OK, 0 rows affected (0,02 sec)
Query OK, 0 rows affected (0,02 sec)
Query OK, 0 rows affected (0,01 sec)
Query OK, 0 rows affected (0,02 sec)

Agora vamos inserir dados de exemplo para que nossas Views tenham conteúdo real para consulta:

-- Populando clientes
INSERT INTO clientes (nome, email, cidade, data_cadastro, ativo) VALUES
('Ana Silva', 'ana.silva@email.com', 'São Paulo', '2025-01-15', 1),
('Bruno Costa', 'bruno.costa@email.com', 'Rio de Janeiro', '2025-03-20', 1),
('Carla Mendes', 'carla.mendes@email.com', 'Belo Horizonte', '2025-06-10', 0),
('Daniel Oliveira', 'daniel.oliveira@email.com', 'São Paulo', '2025-11-05', 1),
('Eduarda Lima', 'eduarda.lima@email.com', 'Curitiba', '2026-02-28', 1);

-- Populando produtos
INSERT INTO produtos (descricao, preco, estoque, categoria) VALUES
('Notebook XPS 15', 7500.00, 12, 'Eletrônicos'),
('Monitor 27" 4K', 2800.00, 8, 'Eletrônicos'),
('Teclado Mecânico RGB', 450.00, 30, 'Periféricos'),
('Mouse Gamer Wireless', 320.00, 25, 'Periféricos'),
('Webcam Full HD', 580.00, 15, 'Periféricos'),
('Hub USB-C 7 portas', 220.00, 40, 'Acessórios');

-- Populando pedidos
INSERT INTO pedidos (cliente_id, data_pedido, status) VALUES
(1, '2026-06-01 10:30:00', 'aprovado'),
(1, '2026-07-05 14:20:00', 'pendente'),
(2, '2026-06-15 09:45:00', 'enviado'),
(3, '2026-05-20 16:10:00', 'cancelado'),
(4, '2026-07-01 11:00:00', 'aprovado'),
(5, '2026-07-07 08:30:00', 'pendente');

-- Populando itens dos pedidos
INSERT INTO itens_pedido (pedido_id, produto_id, quantidade, preco_unitario) VALUES
(1, 1, 1, 7500.00),
(1, 3, 2, 450.00),
(2, 2, 1, 2800.00),
(3, 4, 3, 320.00),
(3, 5, 1, 580.00),
(4, 1, 1, 7500.00),
(5, 6, 5, 220.00),
(6, 2, 1, 2800.00),
(6, 4, 2, 320.00);
Query OK, 5 rows affected (0,01 sec)
Records: 5  Duplicates: 0  Warnings: 0
Query OK, 6 rows affected (0,01 sec)
Records: 6  Duplicates: 0  Warnings: 0
Query OK, 6 rows affected (0,01 sec)
Records: 6  Duplicates: 0  Warnings: 0
Query OK, 9 rows affected (0,01 sec)
Records: 9  Duplicates: 0  Warnings: 0

Com o ambiente preparado, estamos prontos para explorar o universo das Views no MySQL.

O que são Views? — O conceito fundamental por trás das visões de dados

Uma View (visão) no MySQL é uma consulta SELECT armazenada no dicionário de dados com um nome próprio. Ela não armazena dados fisicamente — pense nela como uma “tabela virtual” cujo conteúdo é gerado dinamicamente toda vez que você a consulta. O MySQL armazena apenas a definição da View (a string da consulta SQL) na tabela de metadados, geralmente no information_schema. Quando você executa um SELECT contra uma View, o servidor transforma sua consulta, combina-a com a definição armazenada e executa o plano de execução resultante contra as tabelas base reais.

Em nossos projetos na JRT Technology Solutions, frequentemente encontramos sistemas onde a mesma lógica de negócio — por exemplo, “clientes ativos com total de compras nos últimos 12 meses” — é replicada em dezenas de relatórios, procedures e aplicações. As Views eliminam essa duplicação: a lógica fica centralizada em um único objeto do banco, e qualquer mudança na regra de negócio exige alteração apenas na definição da View, sem tocar no código das aplicações consumidoras. Isso é o que chamamos de independência lógica de dados — um princípio fundamental da arquitetura ANSI/SPARC de três níveis.

As Views também são poderosas ferramentas de segurança. Usando os privilégios granulares do MySQL (GRANT SELECT), você pode dar a um usuário acesso apenas a uma View que oculta colunas sensíveis como salario, cpf ou hash_senha, enquanto nega acesso direto às tabelas base. Essa técnica, conhecida como column masking via views, é amplamente utilizada em ambientes que precisam de conformidade com LGPD e PCI-DSS. Veremos exemplos práticos desse padrão ainda nesta aula.

É crucial entender a diferença entre uma View lógica (a única implementada nativamente pelo MySQL) e uma View materializada (presente em SGBDs como PostgreSQL e Oracle). Uma View materializada armazena fisicamente o resultado da consulta e requer atualizações periódicas (refresh). No MySQL, não há suporte nativo a Views materializadas, mas podemos simulá-las com tabelas de resumo e eventos agendados — técnica que abordaremos na seção de dicas avançadas.

Tabela 1: Comparativo entre View Lógica (MySQL) e View Materializada (conceito)
Característica View Lógica (MySQL nativo) View Materializada (simulada)
Armazenamento físico de dados Não — apenas a definição SQL Sim — tabela física com o resultado
Sincronização com dados base Sempre em tempo real (consulta as tabelas base) Desatualizada até o próximo refresh
Performance de leitura Depende da complexidade da consulta e índices das tabelas base Muito rápida (leitura direta da tabela física)
Uso de espaço em disco Quase zero (apenas metadados) Proporcional ao volume de dados materializados
Atualização via DML Possível em Views atualizáveis simples Não suportado — somente refresh da materialização

Criando sua primeira View — sintaxe básica e exemplos práticos

O comando CREATE VIEW segue uma sintaxe simples, mas esconde nuances importantes que impactam diretamente a manutenibilidade e o desempenho. A forma mais básica é:

CREATE
    [OR REPLACE]
    [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]
    [DEFINER = user]
    [SQL SECURITY {DEFINER | INVOKER}]
    VIEW nome_da_view [(lista_de_colunas)]
    AS consulta_select;

Cada cláusula tem um propósito específico. O OR REPLACE permite substituir uma View existente sem precisar executar DROP VIEW antes — extremamente útil em scripts de migração e deploys automatizados, prática que adotamos em todos os projetos na JRT Technology Solutions. O ALGORITHM controla como o MySQL processa a View: MERGE combina a cláusula WHERE da consulta externa com a definição da View (geralmente mais eficiente), TEMPTABLE cria uma tabela temporária com o resultado da View antes de aplicar filtros externos (pode ser necessário para Views com agregações), e UNDEFINED (padrão) deixa o otimizador decidir. O DEFINER e o SQL SECURITY controlam com quais privilégios a View será executada — tema que abordaremos em detalhes na seção de segurança.

Vamos criar nossa primeira View: uma lista de clientes ativos, ocultando o campo email por questões de privacidade. Este é o padrão mais simples de column masking:

-- Primeira View: clientes ativos sem expor o email
CREATE VIEW vw_clientes_ativos AS
SELECT
    cliente_id,
    nome,
    cidade,
    data_cadastro
FROM clientes
WHERE ativo = 1;

-- Consultando a View como se fosse uma tabela comum
SELECT * FROM vw_clientes_ativos;
+------------+-----------------+----------------+---------------+
| cliente_id | nome            | cidade         | data_cadastro |
+------------+-----------------+----------------+---------------+
|          1 | Ana Silva       | São Paulo      | 2025-01-15    |
|          2 | Bruno Costa     | Rio de Janeiro | 2025-03-20    |
|          4 | Daniel Oliveira | São Paulo      | 2025-11-05    |
|          5 | Eduarda Lima    | Curitiba       | 2026-02-28    |
+------------+-----------------+----------------+---------------+
4 rows in set (0,00 sec)

Observe que a View funciona exatamente como uma tabela para consultas. O campo email e o campo ativo não aparecem no resultado — o primeiro foi omitido intencionalmente na definição, e o segundo é usado apenas como filtro interno. Um usuário que tiver acesso apenas a vw_clientes_ativos jamais verá os emails dos clientes, mesmo que a tabela base clientes os contenha.

Para verificar a definição completa de uma View, utilize o comando SHOW CREATE VIEW:

-- Exibe a definição completa da View
SHOW CREATE VIEW vw_clientes_ativos\G
*************************** 1. row ***************************
                View: vw_clientes_ativos
         Create View: CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost`
SQL SECURITY DEFINER VIEW `vw_clientes_ativos` AS
select `clientes`.`cliente_id` AS `cliente_id`,
       `clientes`.`nome` AS `nome`,
       `clientes`.`cidade` AS `cidade`,
       `clientes`.`data_cadastro` AS `data_cadastro`
from `clientes` where (`clientes`.`ativo` = 1)
character_set_client: utf8mb4
collation_connection: utf8mb4_0900_ai_ci
1 row in set (0,00 sec)

Note que o MySQL expandiu os asteriscos e qualificou cada coluna com o nome da tabela. Esta é a representação canônica que o servidor armazena internamente. O ALGORITHM=UNDEFINED aparece porque não especificamos um algoritmo — o otimizador escolheu automaticamente (provavelmente MERGE, dado que a consulta é simples e sem agregações).

Views Complexas — Joins, Subqueries e Funções de Agregação

A verdadeira potência das Views se revela quando encapsulamos consultas complexas que envolvem múltiplas tabelas, subconsultas correlacionadas e funções de janela. Em ambientes de produção, é comum encontrar Views com 100+ linhas de SQL que consolidam dados de dezenas de tabelas para alimentar dashboards e sistemas de BI. Vamos construir uma View que exibe o resumo de vendas por cliente — total de pedidos, valor total comprado e ticket médio:

-- View complexa com joins e agregações: resumo de vendas por cliente
CREATE OR REPLACE VIEW vw_resumo_vendas AS
SELECT
    c.cliente_id,
    c.nome,
    c.cidade,
    COUNT(DISTINCT p.pedido_id) AS total_pedidos,
    COALESCE(SUM(ip.quantidade * ip.preco_unitario), 0) AS valor_total,
    CASE
        WHEN COUNT(DISTINCT p.pedido_id) > 0
        THEN ROUND(SUM(ip.quantidade * ip.preco_unitario) / COUNT(DISTINCT p.pedido_id), 2)
        ELSE 0
    END AS ticket_medio,
    MAX(p.data_pedido) AS ultimo_pedido,
    DATEDIFF(CURDATE(), MAX(p.data_pedido)) AS dias_desde_ultimo_pedido
FROM clientes c
LEFT JOIN pedidos p ON c.cliente_id = p.cliente_id
LEFT JOIN itens_pedido ip ON p.pedido_id = ip.pedido_id
GROUP BY c.cliente_id, c.nome, c.cidade;

-- Testando a View complexa
SELECT * FROM vw_resumo_vendas;
+------------+-----------------+----------------+---------------+-------------+--------------+---------------------+---------------------------+
| cliente_id | nome            | cidade         | total_pedidos | valor_total | ticket_medio | ultimo_pedido       | dias_desde_ultimo_pedido |
+------------+-----------------+----------------+---------------+-------------+--------------+---------------------+---------------------------+
|          1 | Ana Silva       | São Paulo      |             2 | 11200.00    |      5600.00 | 2026-07-05 14:20:00 |                         3 |
|          2 | Bruno Costa     | Rio de Janeiro |             1 |  1540.00    |      1540.00 | 2026-06-15 09:45:00 |                        23 |
|          3 | Carla Mendes    | Belo Horizonte |             1 |  7500.00    |      7500.00 | 2026-05-20 16:10:00 |                        49 |
|          4 | Daniel Oliveira | São Paulo      |             1 |  1100.00    |      1100.00 | 2026-07-01 11:00:00 |                         7 |
|          5 | Eduarda Lima    | Curitiba       |             1 |  3440.00    |      3440.00 | 2026-07-07 08:30:00 |                         1 |
+------------+-----------------+----------------+---------------+-------------+--------------+---------------------+---------------------------+
5 rows in set (0,01 sec)

Esta View usa LEFT JOIN para incluir clientes sem pedidos (como Carla Mendes, que está inativa mas aparece com valor zero), COALESCE para tratar nulos, CASE para evitar divisão por zero no ticket médio, e DATEDIFF com CURDATE() para calcular métricas de recência. Qualquer aplicação que consumir essa View receberá dados prontos, sem precisar replicar essa lógica complexa.

Um detalhe crucial: Views que contêm GROUP BY, funções de agregação como SUM(), COUNT(), DISTINCT ou UNION são automaticamente não-atualizáveis (read-only). Ou seja, você não pode executar INSERT, UPDATE ou DELETE diretamente sobre vw_resumo_vendas. O MySQL sinaliza isso no information_schema com a coluna IS_UPDATABLE. Veremos mais sobre Views atualizáveis na próxima seção.

Views Updatable (Atualizáveis) — Regras, Restrições e Armadilhas

Uma View é considerada updatable (atualizável) quando o MySQL consegue traduzir sem ambiguidade uma operação DML (INSERT, UPDATE ou DELETE) na View para as tabelas base subjacentes. As regras são rigorosas e estão documentadas no manual oficial. Em resumo, uma View é atualizável se:

  1. A consulta de definição referencia exatamente uma tabela base (sem joins, sem subconsultas no FROM)
  2. Não contém GROUP BY, HAVING, DISTINCT, UNION ou funções de agregação
  3. Cada coluna da View mapeia diretamente para uma coluna simples da tabela base (sem expressões, funções ou operadores)
  4. A View inclui todas as colunas NOT NULL da tabela base que não possuem valor DEFAULT
  5. Não utiliza LIMIT ou OFFSET

Vamos criar uma View atualizável sobre a tabela produtos que expõe apenas itens da categoria “Periféricos” e oculta a coluna estoque (dado estratégico que não deve ser visível para a equipe de vendas):

-- View atualizável: apenas periféricos, sem coluna de estoque
CREATE OR REPLACE VIEW vw_perifericos AS
SELECT
    produto_id,
    descricao,
    preco,
    categoria
FROM produtos
WHERE categoria = 'Periféricos';

-- Verificando se a View é atualizável
SELECT TABLE_NAME, IS_UPDATABLE
FROM information_schema.views
WHERE table_schema = 'aula_views'
  AND table_name = 'vw_perifericos';
+-----------------+--------------+
| TABLE_NAME      | IS_UPDATABLE |
+-----------------+--------------+
| vw_perifericos  | YES          |
+-----------------+--------------+
1 row in set (0,00 sec)

Agora vamos testar operações DML nesta View:

-- Consulta inicial
SELECT * FROM vw_perifericos;

-- INSERT via View (a coluna 'estoque' ficará com valor DEFAULT 0, pois não está na View)
INSERT INTO vw_perifericos (descricao, preco, categoria)
VALUES ('Mousepad XL Speed', 89.90, 'Periféricos');

-- UPDATE via View
UPDATE vw_perifericos
SET preco = 299.90
WHERE descricao = 'Mouse Gamer Wireless';

-- DELETE via View (cuidado!)
DELETE FROM vw_perifericos
WHERE descricao = 'Mousepad XL Speed';

-- Verificando resultado
SELECT * FROM vw_perifericos;
+------------+----------------------+--------+-------------+
| produto_id | descricao            | preco  | categoria   |
+------------+----------------------+--------+-------------+
|          3 | Teclado Mecânico RGB | 450.00 | Periféricos |
|          4 | Mouse Gamer Wireless  | 299.90 | Periféricos |
|          5 | Webcam Full HD        | 580.00 | Periféricos |
+------------+----------------------+--------+-------------+
3 rows in set (0,00 sec)

O INSERT funcionou mesmo sem especificar estoque porque a coluna tem valor DEFAULT 0. Se estoque fosse NOT NULL sem DEFAULT, o INSERT teria falhado. Já o DELETE removeu o registro que havíamos acabado de inserir, mas ATENÇÃO: a cláusula WHERE na definição da View (categoria = ‘Periféricos’) NÃO impede que um UPDATE altere a categoria para algo diferente de “Periféricos”. Isso faria o registro “desaparecer” da View? Sim, mas ele continuaria na tabela base — só não seria mais visível pela View. Esse comportamento é sutil e causa bugs difíceis de depurar, mas felizmente o MySQL oferece uma solução: a cláusula WITH CHECK OPTION, que exploramos a seguir.

WITH CHECK OPTION — Blindando a Integridade dos Dados nas Views

A cláusula WITH CHECK OPTION impede que operações de INSERT ou UPDATE via View criem registros que não satisfaçam a condição WHERE da própria definição da View. É um mecanismo de integridade que atua como uma restrição adicional, garantindo que os dados manipulados através da View permaneçam sempre visíveis por ela. Existem dois níveis de verificação:

  • WITH CASCADED CHECK OPTION: verifica a condição da View atual e de TODAS as Views subjacentes (comportamento padrão se você omitir CASCADED/LOCAL)
  • WITH LOCAL CHECK OPTION: verifica apenas a condição da View atual, respeitando o check option das Views subjacentes apenas se elas também o tiverem definido

Vamos reconstruir nossa View de periféricos com a proteção adequada:

-- Recriando a View com CHECK OPTION
CREATE OR REPLACE VIEW vw_perifericos AS
SELECT produto_id, descricao, preco, categoria
FROM produtos
WHERE categoria = 'Periféricos'
WITH CHECK OPTION;

-- Tentando inserir um produto de outra categoria (vai falhar)
INSERT INTO vw_perifericos (descricao, preco, categoria)
VALUES ('Cabo HDMI 2.1', 79.90, 'Acessórios');
ERROR 1369 (HY000): CHECK OPTION failed 'aula_views.vw_perifericos'

Perfeito! O MySQL rejeitou o INSERT porque a categoria “Acessórios” não satisfaz WHERE categoria = ‘Periféricos’. Da mesma forma, tentar atualizar um produto existente para uma categoria diferente também falhará:

-- Tentando alterar um periférico para outra categoria
UPDATE vw_perifericos
SET categoria = 'Acessórios'
WHERE descricao = 'Teclado Mecânico RGB';
ERROR 1369 (HY000): CHECK OPTION failed 'aula_views.vw_perifericos'

Agora, vamos demonstrar a diferença entre CASCADED e LOCAL com um exemplo mais elaborado, envolvendo Views aninhadas:

-- View base: produtos com preço acima de 100
CREATE OR REPLACE VIEW vw_produtos_caros AS
SELECT produto_id, descricao, preco, categoria
FROM produtos
WHERE preco > 100;

-- View aninhada: somente periféricos da View de produtos caros
-- Usando LOCAL CHECK OPTION
CREATE OR REPLACE VIEW vw_perifericos_caros_local AS
SELECT produto_id, descricao, preco, categoria
FROM vw_produtos_caros
WHERE categoria = 'Periféricos'
WITH LOCAL CHECK OPTION;

-- View aninhada com CASCADED CHECK OPTION
CREATE OR REPLACE VIEW vw_perifericos_caros_cascaded AS
SELECT produto_id, descricao, preco, categoria
FROM vw_produtos_caros
WHERE categoria = 'Periféricos'
WITH CASCADED CHECK OPTION;

-- Teste: INSERT com preço abaixo de 100
INSERT INTO vw_perifericos_caros_local (descricao, preco, categoria)
VALUES ('Teclado Membrana Simples', 49.90, 'Periféricos');
Query OK, 1 row affected (0,01 sec)   -- Explicação abaixo

O INSERT na View com LOCAL CHECK OPTION foi bem-sucedido! Por quê? Porque LOCAL verifica apenas a condição da View onde a cláusula foi definida (categoria = ‘Periféricos’). Como a View subjacente vw_produtos_caros NÃO foi criada com WITH CHECK OPTION, a condição preco > 100 não é verificada em cascata. O registro foi inserido na tabela base produtos com preço 49.90, mas desapareceu de vw_perifericos_caros_local porque não atende ao filtro da View pai. Já com CASCADED, o mesmo INSERT seria rejeitado:

-- Testando com CASCADED
INSERT INTO vw_perifericos_caros_cascaded (descricao, preco, categoria)
VALUES ('Teclado Membrana Simples v2', 49.90, 'Periféricos');
ERROR 1369 (HY000): CHECK OPTION failed 'aula_views.vw_perifericos_caros_cascaded'

Em nossos projetos na JRT Technology Solutions, padronizamos o uso de WITH CASCADED CHECK OPTION como padrão de segurança, a menos que haja uma razão muito específica para comportamento LOCAL. O CASCADED é mais restritivo e evita surpresas — exatamente o que queremos em sistemas críticos.

Gerenciando Views — ALTER, DROP, RENAME e Consultas ao Dicionário de Dados

O ciclo de vida de uma View não termina na criação. Em ambientes reais, Views são alteradas, renomeadas e eventualmente removidas. O MySQL oferece comandos específicos para cada operação, além de tabelas de metadados para auditoria e documentação. Vamos explorar cada operação com exemplos práticos.

Para alterar uma View existente, você tem duas opções: CREATE OR REPLACE VIEW (recomendado) ou ALTER VIEW. A diferença é sutil: ALTER VIEW só funciona se a View já existir, enquanto CREATE OR REPLACE cria se não existir e substitui se existir — ideal para scripts de deploy idempotentes:

-- Alterando a View para incluir a coluna 'estoque' (antes omitida)
ALTER VIEW vw_perifericos AS
SELECT produto_id, descricao, preco, estoque, categoria
FROM produtos
WHERE categoria = 'Periféricos'
WITH CHECK OPTION;

-- Verificando a nova estrutura
DESC vw_perifericos;
+------------+--------------+------+-----+---------+-------+
| Field      | Type         | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+-------+
| produto_id | int          | NO   |     | 0       |       |
| descricao  | varchar(200) | NO   |     | NULL    |       |
| preco      | decimal(10,2)| NO   |     | NULL    |       |
| estoque    | int          | NO   |     | 0       |       |
| categoria  | varchar(50)  | YES  |     | NULL    |       |
+------------+--------------+------+-----+---------+-------+
5 rows in set (0,00 sec)

Para renomear uma View, utiliza-se o comando RENAME TABLE — sim, o mesmo usado para tabelas, pois internamente Views e tabelas compartilham o mesmo namespace:

-- Renomeando a View
RENAME TABLE vw_perifericos TO vw_perifericos_disponiveis;

-- Confirmando a renomeação
SHOW FULL TABLES IN aula_views WHERE TABLE_TYPE LIKE 'VIEW';
+------------------------------------+------------+
| Tables_in_aula_views               | Table_type |
+------------------------------------+------------+
| vw_clientes_ativos                 | VIEW       |
| vw_perifericos_caros_cascaded      | VIEW       |
| vw_perifericos_caros_local         | VIEW       |
| vw_perifericos_disponiveis         | VIEW       |
| vw_produtos_caros                  | VIEW       |
| vw_resumo_vendas                   | VIEW       |
+------------------------------------+------------+
6 rows in set (0,00 sec)

Para excluir uma View, use DROP VIEW com a opção IF EXISTS para evitar erros em scripts:

Tabela 2: Comandos de Gerenciamento de Views no MySQL
Comando Função Exemplo
CREATE VIEW Cria uma nova View (falha se já existir) CREATE VIEW v AS SELECT ...
CREATE OR REPLACE VIEW Cria ou substitui uma View existente CREATE OR REPLACE VIEW v AS ...
ALTER VIEW Modifica uma View existente (deve existir) ALTER VIEW v AS ...
DROP VIEW [IF EXISTS] Remove uma ou mais Views DROP VIEW IF EXISTS v1, v2;
RENAME TABLE Renomeia uma View (compartilha namespace com tabelas) RENAME TABLE v_old TO v_new;
SHOW CREATE VIEW Exibe a definição completa da View SHOW CREATE VIEW v\G
SHOW FULL TABLES Lista tabelas e Views do banco SHOW FULL TABLES WHERE TABLE_TYPE='VIEW';

Verificando a Instalação / Testando a Configuração

Após criar diversas Views, é fundamental verificar sua integridade, performance e permissões. Esta seção apresenta comandos de diagnóstico que utilizamos rotineiramente em nossos projetos na JRT Technology Solutions para garantir que o ambiente está saudável. Execute cada comando e compare com a saída esperada.

  1. Verificar todas as Views do banco e seu status de atualização:
-- Lista todas as Views do banco com informações detalhadas
SELECT
    TABLE_NAME AS view_name,
    IS_UPDATABLE,
    CHECK_OPTION,
    DEFINER,
    SECURITY_TYPE,
    CHARACTER_SET_CLIENT
FROM information_schema.views
WHERE table_schema = 'aula_views'
ORDER BY table_name;
+--------------------------------+--------------+--------------+----------------+---------------+----------------------+
| view_name                      | IS_UPDATABLE | CHECK_OPTION | DEFINER        | SECURITY_TYPE | CHARACTER_SET_CLIENT |
+--------------------------------+--------------+--------------+----------------+---------------+----------------------+
| vw_clientes_ativos             | YES          | NONE         | root@localhost | DEFINER       | utf8mb4              |
| vw_perifericos_caros_cascaded  | NO           | CASCADED     | root@localhost | DEFINER       | utf8mb4              |
| vw_perifericos_caros_local     | NO           | LOCAL        | root@localhost | DEFINER       | utf8mb4              |
| vw_perifericos_disponiveis     | YES          | CASCADED     | root@localhost | DEFINER       | utf8mb4              |
| vw_produtos_caros              | YES          | NONE         | root@localhost | DEFINER       | utf8mb4              |
| vw_resumo_vendas               | NO           | NONE         | root@localhost | DEFINER       | utf8mb4              |
+--------------------------------+--------------+--------------+----------------+---------------+----------------------+
6 rows in set (0,00 sec)
  1. Verificar o plano de execução de uma View complexa:
-- Analisando como o MySQL executa a View de resumo de vendas
EXPLAIN SELECT * FROM vw_resumo_vendas WHERE cidade = 'São Paulo';
+----+-------------+-------+------------+------+--------------------------------+-----------------------------+---------+----------------------+------+----------+---------------------------------+
| id | select_type | table | partitions | type | possible_keys                  | key                         | key_len | ref                  | rows | filtered | Extra                           |
+----+-------------+-------+------------+------+--------------------------------+-----------------------------+---------+----------------------+------+----------+---------------------------------+
|  1 | SIMPLE      | c     | NULL       | ALL  | NULL                           | NULL                        | NULL    | NULL                 |    5 |    20.00 | Using where; Using temporary    |
|  1 | SIMPLE      | p     | NULL       | ref  | fk_pedidos_clientes            | fk_pedidos_clientes         | 4       | aula_views.c.cliente_id|    1 |   100.00 | NULL                            |
|  1 | SIMPLE      | ip    | NULL       | ref  | fk_itens_pedido_pedido_id     | fk_itens_pedido_pedido_id  | 4       | aula_views.p.pedido_id |    1 |   100.00 | NULL                            |
+----+-------------+-------+------------+------+--------------------------------+-----------------------------+---------+----------------------+------+----------+---------------------------------+
3 rows in set, 1 warning (0,00 sec)

Este EXPLAIN revela que a consulta está usando índices nas chaves estrangeiras (fk_pedidos_clientes e fk_itens_pedido_pedido_id), o que é bom. No entanto, a coluna cidade não possui índice, resultando em um full table scan na tabela clientes (type ALL). Se sua View for frequentemente consultada com filtro por cidade, criar um índice CREATE INDEX idx_cidade ON clientes(cidade) melhorará drasticamente a performance.

  1. Testar acesso a Views por usuário sem privilégios diretos nas tabelas base:
-- Cri

Quer aprender na prática com especialistas?

A JRT Technology Solutions oferece treinamentos e implementação de MySQL para equipes corporativas.



Falar no WhatsApp

Thiago Paes Rodrigues

Com mais de 22 anos de experiência em Tecnologia da Informação, este profissional construiu uma trajetória sólida como empresário, atuando de forma estratégica na implementação de soluções tecnológicas que otimizam processos e impulsionam resultados em diferentes setores.