Uma View (ou visão) no SQL é uma tabela virtual baseada no resultado de uma consulta SQL. As views contêm linhas e colunas, assim como tabelas reais, mas não armazenam dados fisicamente - elas são criadas dinamicamente quando consultadas.
- Simplificação de consultas complexas: Encapsula lógica complicada
- Segurança de dados: Restringe acesso a colunas/linhas específicas
- Independência de dados: Proteção contra mudanças no esquema subjacente
- Consistência de dados: Garante que todos usem a mesma definição
- Personalização de dados: Formata dados de acordo com necessidades específicas
CREATE [OR REPLACE] VIEW nome_da_view AS
SELECT colunas
FROM tabelas
WHERE condições;-- View para relatório de produtos ativos
CREATE VIEW produtos_ativos AS
SELECT id, nome, preco, estoque
FROM produtos
WHERE status = 'ativo';
-- Consultando a view
SELECT * FROM produtos_ativos;-- View para detalhes de pedidos
CREATE VIEW detalhes_pedidos AS
SELECT
p.id AS pedido_id,
p.data_pedido,
c.nome AS cliente,
c.email,
SUM(ip.quantidade * ip.preco_unitario) AS valor_total,
COUNT(ip.produto_id) AS quantidade_itens
FROM pedidos p
JOIN clientes c ON p.cliente_id = c.id
JOIN itens_pedido ip ON p.id = ip.pedido_id
GROUP BY p.id, p.data_pedido, c.nome, c.email;
-- Consultando a view
SELECT * FROM detalhes_pedidos
WHERE valor_total > 1000
ORDER BY data_pedido DESC;-- View de clientes VIP (com gasto acima da média)
CREATE VIEW clientes_vip AS
SELECT
c.id,
c.nome,
c.email,
SUM(p.valor_total) AS total_gasto
FROM clientes c
JOIN pedidos p ON c.id = p.cliente_id
GROUP BY c.id, c.nome, c.email
HAVING SUM(p.valor_total) > (
SELECT AVG(valor_total) FROM (
SELECT cliente_id, SUM(valor_total) AS valor_total
FROM pedidos
GROUP BY cliente_id
) AS gastos_clientes
);Uma view materializada armazena fisicamente os resultados da consulta, atualizando-os periodicamente.
-- PostgreSQL
CREATE MATERIALIZED VIEW relatorio_vendas_mensal AS
SELECT
DATE_TRUNC('month', data_venda) AS mes,
categoria_id,
SUM(valor) AS total_vendas,
COUNT(*) AS num_vendas
FROM vendas
JOIN produtos ON vendas.produto_id = produtos.id
GROUP BY mes, categoria_id;
-- Atualizar a view materializada
REFRESH MATERIALIZED VIEW relatorio_vendas_mensal;São views que permitem operações de INSERT, UPDATE e DELETE sob certas condições.
-- View atualizável
CREATE VIEW funcionarios_ti AS
SELECT id, nome, cargo, salario, departamento_id
FROM funcionarios
WHERE departamento_id = 5;
-- Possível fazer UPDATE nesta view
UPDATE funcionarios_ti
SET salario = salario * 1.1
WHERE cargo = 'Desenvolvedor Sênior';-- Alterando uma view
ALTER VIEW produtos_ativos AS
SELECT id, nome, preco, estoque, categoria_id
FROM produtos
WHERE status = 'ativo' AND estoque > 0;
-- Removendo uma view
DROP VIEW IF EXISTS produtos_ativos;- Performance: Views complexas podem ser mais lentas que consultas diretas
- Restrições em views atualizáveis: Nem todas as views permitem modificação de dados
- Limitações de índices: Em geral, views não têm índices próprios (exceto materializadas)
Procedimentos armazenados (Stored Procedures) são conjuntos de instruções SQL compiladas e armazenadas no banco de dados que podem ser executadas posteriormente.
- Desempenho: São pré-compilados e otimizados pelo SGBD
- Segurança: Permitem controle de acesso granular
- Redução de tráfego de rede: Apenas a chamada é transmitida, não toda a consulta
- Reutilização: O mesmo código pode ser usado por múltiplas aplicações
- Manutenção: Alterar a lógica em um único lugar afeta todas as aplicações
- Transações: Podem agrupar múltiplas operações em uma transação atômica
A sintaxe varia um pouco entre diferentes SGBDs:
DELIMITER //
CREATE PROCEDURE criar_pedido(
IN cliente_id INT,
IN forma_pagamento VARCHAR(50),
OUT pedido_id INT
)
BEGIN
DECLARE data_atual DATETIME;
SET data_atual = NOW();
-- Inserir o pedido
INSERT INTO pedidos (cliente_id, data_pedido, forma_pagamento, status)
VALUES (cliente_id, data_atual, forma_pagamento, 'pendente');
-- Obter o ID do pedido inserido
SET pedido_id = LAST_INSERT_ID();
-- Registrar log
INSERT INTO logs_sistema (acao, tabela, registro_id, data_hora)
VALUES ('criar_pedido', 'pedidos', pedido_id, data_atual);
END //
DELIMITER ;CREATE PROCEDURE criar_pedido
@cliente_id INT,
@forma_pagamento VARCHAR(50),
@pedido_id INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE @data_atual DATETIME = GETDATE();
-- Inserir o pedido
INSERT INTO pedidos (cliente_id, data_pedido, forma_pagamento, status)
VALUES (@cliente_id, @data_atual, @forma_pagamento, 'pendente');
-- Obter o ID do pedido inserido
SET @pedido_id = SCOPE_IDENTITY();
-- Registrar log
INSERT INTO logs_sistema (acao, tabela, registro_id, data_hora)
VALUES ('criar_pedido', 'pedidos', @pedido_id, @data_atual);
END;CREATE OR REPLACE PROCEDURE criar_pedido(
cliente_id INT,
forma_pagamento VARCHAR,
INOUT pedido_id INT
)
LANGUAGE plpgsql
AS $$
DECLARE
data_atual TIMESTAMP;
BEGIN
data_atual := NOW();
-- Inserir o pedido
INSERT INTO pedidos (cliente_id, data_pedido, forma_pagamento, status)
VALUES (cliente_id, data_atual, forma_pagamento, 'pendente')
RETURNING id INTO pedido_id;
-- Registrar log
INSERT INTO logs_sistema (acao, tabela, registro_id, data_hora)
VALUES ('criar_pedido', 'pedidos', pedido_id, data_atual);
END;
$$;-- MySQL
CALL criar_pedido(101, 'cartão de crédito', @novo_pedido_id);
SELECT @novo_pedido_id;
-- SQL Server
DECLARE @novo_pedido_id INT;
EXEC criar_pedido 101, 'cartão de crédito', @novo_pedido_id OUTPUT;
SELECT @novo_pedido_id;
-- PostgreSQL
CALL criar_pedido(101, 'cartão de crédito', NULL);-- MySQL
DELIMITER //
CREATE PROCEDURE transferir_fundos(
IN conta_origem INT,
IN conta_destino INT,
IN valor DECIMAL(10,2),
OUT mensagem VARCHAR(100)
)
BEGIN
DECLARE saldo_origem DECIMAL(10,2);
-- Iniciar transação
START TRANSACTION;
-- Verificar saldo da conta de origem
SELECT saldo INTO saldo_origem FROM contas WHERE id = conta_origem FOR UPDATE;
IF saldo_origem < valor THEN
SET mensagem = 'Saldo insuficiente';
ROLLBACK;
ELSE
-- Retirar da conta de origem
UPDATE contas SET saldo = saldo - valor WHERE id = conta_origem;
-- Adicionar à conta de destino
UPDATE contas SET saldo = saldo + valor WHERE id = conta_destino;
-- Registrar a transação
INSERT INTO transacoes (conta_origem, conta_destino, valor, data_hora)
VALUES (conta_origem, conta_destino, valor, NOW());
COMMIT;
SET mensagem = 'Transferência realizada com sucesso';
END IF;
END //
DELIMITER ;-- SQL Server
CREATE PROCEDURE processar_descontos_inativos
AS
BEGIN
DECLARE @data_limite DATE = DATEADD(month, -3, GETDATE());
-- Atualizar preços com desconto para produtos sem vendas recentes
UPDATE produtos
SET preco_promocional = preco * 0.8,
status_promocao = 'ativo'
WHERE id IN (
SELECT p.id
FROM produtos p
LEFT JOIN itens_pedido ip ON p.id = ip.produto_id
LEFT JOIN pedidos pe ON ip.pedido_id = pe.id
GROUP BY p.id
HAVING MAX(pe.data_pedido) < @data_limite OR MAX(pe.data_pedido) IS NULL
)
AND status = 'ativo'
AND preco_promocional IS NULL;
-- Retornar o número de produtos atualizados
SELECT @@ROWCOUNT AS produtos_atualizados;
END;-- MySQL
DELIMITER //
CREATE PROCEDURE calcular_frete_pedidos()
BEGIN
DECLARE pedido_id INT;
DECLARE cliente_id INT;
DECLARE peso_total DECIMAL(10,2);
DECLARE distancia INT;
DECLARE valor_frete DECIMAL(10,2);
DECLARE fim INT DEFAULT 0;
-- Declarar cursor para pedidos sem frete calculado
DECLARE cur_pedidos CURSOR FOR
SELECT p.id, p.cliente_id
FROM pedidos p
WHERE p.valor_frete IS NULL AND p.status = 'aguardando_envio';
-- Handler para fim do cursor
DECLARE CONTINUE HANDLER FOR NOT FOUND SET fim = 1;
OPEN cur_pedidos;
loop_pedidos: LOOP
FETCH cur_pedidos INTO pedido_id, cliente_id;
IF fim = 1 THEN
LEAVE loop_pedidos;
END IF;
-- Calcular peso total do pedido
SELECT SUM(ip.quantidade * pr.peso)
INTO peso_total
FROM itens_pedido ip
JOIN produtos pr ON ip.produto_id = pr.id
WHERE ip.pedido_id = pedido_id;
-- Obter distância de entrega
SELECT distancia_km INTO distancia
FROM clientes c
JOIN enderecos e ON c.id = e.cliente_id AND e.principal = 1
JOIN regioes_entrega r ON e.cep BETWEEN r.cep_inicial AND r.cep_final
WHERE c.id = cliente_id;
-- Calcular valor do frete
SET valor_frete = peso_total * 0.1 + distancia * 0.05;
-- Atualizar pedido
UPDATE pedidos
SET valor_frete = valor_frete
WHERE id = pedido_id;
END LOOP;
CLOSE cur_pedidos;
SELECT 'Cálculo de frete finalizado' AS mensagem;
END //
DELIMITER ;Semelhantes aos procedimentos, mas retornam um único valor e podem ser usadas em consultas SQL.
-- MySQL
DELIMITER //
CREATE FUNCTION calcular_idade(data_nascimento DATE)
RETURNS INT DETERMINISTIC
BEGIN
DECLARE idade INT;
SET idade = YEAR(CURDATE()) - YEAR(data_nascimento) -
(DATE_FORMAT(CURDATE(), '%m%d') < DATE_FORMAT(data_nascimento, '%m%d'));
RETURN idade;
END //
DELIMITER ;
-- Usando a função
SELECT nome, calcular_idade(data_nascimento) AS idade FROM clientes;Triggers são procedimentos armazenados especiais que são executados automaticamente em resposta a eventos no banco de dados.
-- MySQL
DELIMITER //
CREATE TRIGGER atualizar_estoque_apos_venda
AFTER INSERT ON itens_pedido
FOR EACH ROW
BEGIN
-- Diminuir o estoque do produto
UPDATE produtos
SET estoque = estoque - NEW.quantidade
WHERE id = NEW.produto_id;
-- Verificar se o estoque ficou abaixo do mínimo
INSERT INTO alertas_estoque (produto_id, estoque_atual, data_alerta)
SELECT id, estoque, NOW()
FROM produtos
WHERE id = NEW.produto_id AND estoque < estoque_minimo;
END //
DELIMITER ;Views e procedimentos armazenados podem ser combinados para criar soluções poderosas.
-- 1. View para dados base
CREATE VIEW vendas_produto_detalhe AS
SELECT
p.id AS produto_id,
p.nome AS produto,
c.nome AS categoria,
ip.quantidade,
ip.preco_unitario,
pe.data_pedido,
EXTRACT(YEAR FROM pe.data_pedido) AS ano,
EXTRACT(MONTH FROM pe.data_pedido) AS mes
FROM itens_pedido ip
JOIN produtos p ON ip.produto_id = p.id
JOIN categorias c ON p.categoria_id = c.id
JOIN pedidos pe ON ip.pedido_id = pe.id
WHERE pe.status = 'entregue';
-- 2. Procedimento para gerar relatório
DELIMITER //
CREATE PROCEDURE gerar_relatorio_vendas(
IN ano_filtro INT,
IN mes_filtro INT
)
BEGIN
-- Relatório por categoria
SELECT
categoria,
SUM(quantidade) AS unidades_vendidas,
SUM(quantidade * preco_unitario) AS valor_total,
COUNT(DISTINCT produto_id) AS quantidade_produtos
FROM vendas_produto_detalhe
WHERE ano = ano_filtro AND (mes_filtro IS NULL OR mes = mes_filtro)
GROUP BY categoria
ORDER BY valor_total DESC;
-- Relatório dos top 10 produtos
SELECT
produto_id,
produto,
SUM(quantidade) AS unidades_vendidas,
SUM(quantidade * preco_unitario) AS valor_total
FROM vendas_produto_detalhe
WHERE ano = ano_filtro AND (mes_filtro IS NULL OR mes = mes_filtro)
GROUP BY produto_id, produto
ORDER BY unidades_vendidas DESC
LIMIT 10;
-- Evolução mensal (se filtro de mês não informado)
IF mes_filtro IS NULL THEN
SELECT
mes,
SUM(quantidade * preco_unitario) AS valor_total,
COUNT(DISTINCT produto_id) AS produtos_vendidos
FROM vendas_produto_detalhe
WHERE ano = ano_filtro
GROUP BY mes
ORDER BY mes;
END IF;
END //
DELIMITER ;- Nomes Descritivos: Use nomes que indiquem claramente o propósito da view
- Comentários: Documente o objetivo e as restrições da view
- Evite Cascata: Evite criar views baseadas em outras views (limita-se a 1-2 níveis)
- Considere Performance: Use views materializadas para queries complexas frequentes
- **Evite SELECT ***: Especifique apenas as colunas necessárias
- Tratamento de Erros: Sempre inclua lógica para lidar com exceções
- Transações: Use transações para garantir a integridade dos dados
- Validação de Entrada: Verifique parâmetros de entrada para evitar erros
- Evite Lógica Complexa: Mantenha procedimentos focados e específicos
- Segurança: Evite injeção SQL usando parâmetros adequadamente
- Tamanho: Divida procedimentos muito grandes em unidades menores e reutilizáveis
-- MySQL
DELIMITER //
CREATE PROCEDURE atualizar_preco_produto(
IN produto_id INT,
IN novo_preco DECIMAL(10,2),
OUT mensagem VARCHAR(100)
)
COMMENT 'Atualiza o preço de um produto com validações e registro de histórico'
BEGIN
DECLARE produto_existe INT;
DECLARE preco_atual DECIMAL(10,2);
-- Tratamento de erros
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SET mensagem = 'Erro ao atualizar preço. Operação cancelada.';
END;
-- Validação de entrada
IF produto_id IS NULL OR novo_preco IS NULL THEN
SET mensagem = 'ID do produto e novo preço são obrigatórios';
RETURN;
END IF;
IF novo_preco <= 0 THEN
SET mensagem = 'Preço deve ser maior que zero';
RETURN;
END IF;
-- Verificar se produto existe
SELECT COUNT(*), preco INTO produto_existe, preco_atual
FROM produtos WHERE id = produto_id;
IF produto_existe = 0 THEN
SET mensagem = CONCAT('Produto com ID ', produto_id, ' não encontrado');
RETURN;
END IF;
-- Iniciar transação
START TRANSACTION;
-- Registrar histórico
INSERT INTO historico_precos (produto_id, preco_antigo, preco_novo, data_alteracao, usuario)
VALUES (produto_id, preco_atual, novo_preco, NOW(), CURRENT_USER());
-- Atualizar preço
UPDATE produtos
SET preco = novo_preco,
data_atualizacao = NOW()
WHERE id = produto_id;
-- Confirmar transação
COMMIT;
SET mensagem = CONCAT('Preço atualizado com sucesso. Anterior: ', preco_atual, ' / Novo: ', novo_preco);
END //
DELIMITER ;- Usa
DELIMITERpara alterar o delimitador durante criação de procedures - Sintaxe
BEGIN...ENDpara blocos - Suporta procedimentos armazenados e funções, mas não views materializadas nativamente
- Usa
CREATE OR REPLACE PROCEDURE/FUNCTION - Tem linguagem própria (PL/pgSQL) para procedimentos
- Suporta views materializadas nativamente
- Permite herança de tabelas
- Usa
CREATE PROCEDUREsem necessidade de alterar delimitador - Fornece T-SQL como linguagem procedural
- Bom suporte para cursores e tratamento de exceções
- Possui conceito de esquemas para organização
- Usa blocos
DECLARE...BEGIN...END - Tem PL/SQL como linguagem procedural robusta
- Suporte avançado para views materializadas e refreshing
- Pacotes permitem agrupar procedimentos relacionados