Procedimentos Armazenados e Funções SQL
Procedimentos Armazenados e Funções SQL
Analogia do Mundo Real
Imagine que você está em um banco:
- Procedimento armazenado é como o "processo padrão de negócios" do banco — o caixa segue etapas predefinidas, e você apenas diz "quero fazer um depósito", e todo o processo é executado automaticamente.
- Função é como uma tecla de função na calculadora — insira parâmetros, retorne um resultado, e pode ser incorporada em cálculos maiores.
Conceitos Fundamentais
O que é um Procedimento Armazenado
Um Procedimento Armazenado é um conjunto pré-compilado de instruções SQL armazenado no banco de dados que pode:
- Aceitar parâmetros de entrada
- Executar lógica de negócios complexa
- Retornar conjuntos de resultados ou códigos de status
- Melhorar a performance (pré-compilado, tráfego de rede reduzido)
Sintaxe de Procedimento Armazenado MySQL
-- Estrutura básica de procedimento armazenado MySQL
DELIMITER //
CREATE PROCEDURE nome_procedimento (lista_parâmetros)
BEGIN
-- Declarações de variáveis
-- Lógica de negócios
-- Retornar resultados
END //
DELIMITER ;
Sintaxe de Procedimento Armazenado PostgreSQL
-- Estrutura básica de procedimento armazenado PostgreSQL
CREATE OR REPLACE FUNCTION nome_procedimento(lista_parâmetros)
RETURNS tipo_retorno AS $$
DECLARE
-- Declarações de variáveis
BEGIN
-- Lógica de negócios
-- Instrução RETURN
END;
$$ LANGUAGE plpgsql;
Funções Personalizadas SQLite
SQLite não suporta procedimentos armazenados, mas suporta criação de funções definidas pelo usuário através de linguagens de programação:
-- SQLite em si não suporta CREATE FUNCTION
-- Você precisa registrar funções personalizadas via Python, C ou outras linguagens de programação
-- Exemplo Python (usando módulo sqlite3)
import sqlite3
def calcular_bonus(salario, taxa):
return salario * taxa
conn = sqlite3.connect('empresa.db')
conn.create_function('calcular_bonus', 2, calcular_bonus)
-- Após o registro, você pode usar em SQL
SELECT nome, salario, calcular_bonus(salario, 0.1) AS bonus
FROM employees;
Instruções de Controle de Fluxo
Lógica Condicional IF/ELSE
-- Exemplo MySQL
DELIMITER //
CREATE PROCEDURE verificar_salario(IN emp_id INT)
BEGIN
DECLARE emp_salary DECIMAL(10,2);
SELECT salary INTO emp_salary
FROM employees WHERE id = emp_id;
IF emp_salary > 10000 THEN
SELECT 'Salário alto' AS status;
ELSEIF emp_salary > 5000 THEN
SELECT 'Salário médio' AS status;
ELSE
SELECT 'Salário baixo' AS status;
END IF;
END //
DELIMITER ;
Loop WHILE
-- Exemplo MySQL: calcular a soma de 1 a N
DELIMITER //
CREATE PROCEDURE calcular_soma(IN n INT, OUT total INT)
BEGIN
DECLARE i INT DEFAULT 1;
SET total = 0;
WHILE i <= n DO
SET total = total + i;
SET i = i + 1;
END WHILE;
END //
DELIMITER ;
Loop LOOP
-- Exemplo MySQL: LOOP com LEAVE
DELIMITER //
CREATE PROCEDURE processar_funcionarios()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE emp_name VARCHAR(100);
DECLARE cur CURSOR FOR SELECT name FROM employees;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
read_loop: LOOP
FETCH cur INTO emp_name;
IF done THEN
LEAVE read_loop;
END IF;
-- Processar cada registro
SELECT CONCAT('Processando: ', emp_name);
END LOOP;
CLOSE cur;
END //
DELIMITER ;
Conceitos de Cursores
Cursores são usados para processar conjuntos de resultados linha por linha:
-- Fluxo básico de uso de cursor
-- 1. Declarar o cursor
DECLARE cur CURSOR FOR SELECT coluna FROM tabela;
-- 2. Abrir o cursor
OPEN cur;
-- 3. Buscar dados
FETCH cur INTO variável;
-- 4. Processar dados (geralmente em um loop)
-- 5. Fechar o cursor
CLOSE cur;
Funções Armazenadas
Funções armazenadas são semelhantes a procedimentos armazenados, mas têm diferenças importantes:
-- Função armazenada MySQL
DELIMITER //
CREATE FUNCTION get_annual_salary(monthly_salary DECIMAL(10,2))
RETURNS DECIMAL(10,2)
DETERMINISTIC
BEGIN
RETURN monthly_salary * 12;
END //
DELIMITER ;
-- Usar a função
SELECT name, get_annual_salary(salary) AS annual_salary
FROM employees;
Procedimentos Armazenados vs Funções
| Característica | Procedimento Armazenado | Função Armazenada |
|---|---|---|
| Método de chamada | Instrução CALL | Chamada diretamente em SQL |
| Valor de retorno | Pode retornar múltiplos conjuntos de resultados | Deve retornar um único valor |
| Caso de uso | Lógica de negócios complexa | Cálculo e transformação |
| Controle de transação | Pode gerenciar transações | Não pode gerenciar transações |
| Em instruções SQL | Não pode ser usado em SELECT | Pode ser usado em SELECT |
Sintaxe Básica
Criando um Procedimento Armazenado (MySQL)
-- Criar um procedimento armazenado com parâmetros
DELIMITER //
CREATE PROCEDURE get_employees_by_dept(
IN dept_name VARCHAR(50),
IN min_salary DECIMAL(10,2)
)
BEGIN
SELECT e.name, e.salary, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.id
WHERE d.department_name = dept_name
AND e.salary >= min_salary
ORDER BY e.salary DESC;
END //
DELIMITER ;
-- Chamar o procedimento armazenado
CALL get_employees_by_dept('Tech', 8000);
Criando uma Função Armazenada (PostgreSQL)
-- Criar uma função para calcular bônus
CREATE OR REPLACE FUNCTION calculate_bonus(
base_salary NUMERIC,
performance_rating INTEGER
)
RETURNS NUMERIC AS $$
DECLARE
bonus_rate NUMERIC;
BEGIN
CASE performance_rating
WHEN 1 THEN bonus_rate := 0.10;
WHEN 2 THEN bonus_rate := 0.15;
WHEN 3 THEN bonus_rate := 0.20;
WHEN 4 THEN bonus_rate := 0.25;
WHEN 5 THEN bonus_rate := 0.30;
ELSE bonus_rate := 0.05;
END CASE;
RETURN base_salary * bonus_rate;
END;
$$ LANGUAGE plpgsql;
-- Usar a função
SELECT
name,
salary,
performance_rating,
calculate_bonus(salary, performance_rating) AS bonus
FROM employees;
Exemplos
Exemplo: Criando um Procedimento Armazenado de Integração de Funcionário (Dificuldade ⭐⭐)
Crie um procedimento armazenado para lidar com o processo de integração de novos funcionários, incluindo atribuição de número de funcionário, definição de salário inicial e registro de informações de integração.
-- Versão MySQL
DELIMITER //
CREATE PROCEDURE hire_employee(
IN p_name VARCHAR(100),
IN p_department_id INT,
IN p_position VARCHAR(50),
IN p_salary DECIMAL(10,2),
OUT p_employee_id INT,
OUT p_message VARCHAR(200)
)
BEGIN
DECLARE dept_exists INT DEFAULT 0;
DECLARE max_id INT DEFAULT 0;
-- Verificar se o departamento existe
SELECT COUNT(*) INTO dept_exists
FROM departments WHERE id = p_department_id;
IF dept_exists = 0 THEN
SET p_message = 'Erro: O departamento especificado não existe';
SET p_employee_id = -1;
ELSE
-- Obter o maior ID de funcionário
SELECT COALESCE(MAX(id), 0) INTO max_id FROM employees;
SET p_employee_id = max_id + 1;
-- Inserir o novo registro de funcionário
INSERT INTO employees (id, name, department_id, position, salary, hire_date)
VALUES (p_employee_id, p_name, p_department_id, p_position, p_salary, CURDATE());
SET p_message = CONCAT('Sucesso: O funcionário ', p_name, ' foi integrado com ID ', p_employee_id);
END IF;
END //
DELIMITER ;
-- Exemplo de chamada
CALL hire_employee('John Doe', 1, 'Engenheiro Júnior', 8000.00, @new_id, @msg);
SELECT @new_id AS employee_id, @msg AS message;
Exemplo: Versão PostgreSQL — Função de Ajuste Salarial de Funcionário (Dificuldade ⭐⭐⭐)
-- Versão PostgreSQL
CREATE OR REPLACE FUNCTION adjust_salary(
p_employee_id INTEGER,
p_adjustment_type VARCHAR(10), -- 'RAISE' ou 'REDUCE'
p_percentage NUMERIC
)
RETURNS TABLE(
employee_name VARCHAR,
old_salary NUMERIC,
new_salary NUMERIC,
adjustment_amount NUMERIC
) AS $$
DECLARE
v_old_salary NUMERIC;
v_new_salary NUMERIC;
v_name VARCHAR;
BEGIN
-- Obter o salário atual
SELECT name, salary INTO v_name, v_old_salary
FROM employees WHERE id = p_employee_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'ID de funcionário % não existe', p_employee_id;
END IF;
-- Calcular o novo salário
IF p_adjustment_type = 'RAISE' THEN
v_new_salary := v_old_salary * (1 + p_percentage / 100);
ELSE
v_new_salary := v_old_salary * (1 - p_percentage / 100);
END IF;
-- Atualizar o banco de dados
UPDATE employees SET salary = v_new_salary WHERE id = p_employee_id;
-- Retornar o resultado
employee_name := v_name;
old_salary := v_old_salary;
new_salary := v_new_salary;
adjustment_amount := v_new_salary - v_old_salary;
RETURN NEXT;
END;
$$ LANGUAGE plpgsql;
-- Exemplo de uso
SELECT * FROM adjust_salary(1, 'RAISE', 10);
Cenários de Aplicação
Cenário 1: Processamento de Dados em Lote (Dificuldade ⭐⭐⭐)
-- MySQL: atualização em lote de status de pedidos
DELIMITER //
CREATE PROCEDURE update_order_status()
BEGIN
DECLARE affected_rows INT DEFAULT 0;
-- Iniciar transação
START TRANSACTION;
-- Cancelar pedidos pendentes há mais de 30 dias
UPDATE orders
SET status = 'cancelled',
updated_at = NOW()
WHERE status = 'pending'
AND created_at < DATE_SUB(NOW(), INTERVAL 30 DAY);
SET affected_rows = ROW_COUNT();
-- Registrar o processamento
INSERT INTO process_log (process_name, affected_rows, executed_at)
VALUES ('update_order_status', affected_rows, NOW());
-- Confirmar a transação
COMMIT;
SELECT CONCAT('Cancelados ', affected_rows, ' pedidos expirados') AS result;
END //
DELIMITER ;
Cenário 2: Geração de Relatórios Complexos (Dificuldade ⭐⭐⭐)
-- PostgreSQL: gerar relatório salarial por departamento
CREATE OR REPLACE FUNCTION generate_salary_report()
RETURNS TABLE(
department_name VARCHAR,
employee_count BIGINT,
avg_salary NUMERIC,
min_salary NUMERIC,
max_salary NUMERIC,
total_salary NUMERIC
) AS $$
BEGIN
RETURN QUERY
SELECT
d.department_name,
COUNT(e.id) AS employee_count,
ROUND(AVG(e.salary), 2) AS avg_salary,
MIN(e.salary) AS min_salary,
MAX(e.salary) AS max_salary,
SUM(e.salary) AS total_salary
FROM departments d
LEFT JOIN employees e ON d.id = e.department_id
GROUP BY d.department_name
ORDER BY total_salary DESC;
END;
$$ LANGUAGE plpgsql;
-- Uso
SELECT * FROM generate_salary_report();
❓ Perguntas Frequentes
P: SQLite não suporta procedimentos armazenados — o que devo fazer? R: SQLite é projetado como um banco de dados leve e embarcado e não suporta procedimentos armazenados. Você pode implementar a lógica de negócios usando linguagens de programação (Python, Java, etc.), ou usar views (VIEW) para encapsular consultas complexas.
P: Qual a diferença entre um procedimento armazenado e uma view? R: Uma view é uma tabela virtual, usada principalmente para simplificar consultas; um procedimento armazenado é um programa que pode conter lógica de negócios complexa, controle de fluxo e operações de modificação de dados.
P: Quando devo usar procedimentos armazenados? R: Use-os quando precisar encapsular lógica de negócios complexa, reduzir tráfego de rede, melhorar a performance ou unificar interfaces de acesso a dados. Consultas simples são mais adequadas para views.
P: Como faço para depurar um procedimento armazenado? R: MySQL pode usar instruções SELECT para exibir resultados intermediários; PostgreSQL pode usar RAISE NOTICE para exibir informações de depuração; você também pode usar os recursos de depuração das ferramentas de gerenciamento de banco de dados.
📖 Resumo
Nesta lição aprendemos:
- Os conceitos e finalidade dos procedimentos armazenados
- Sintaxe de procedimentos armazenados MySQL e PostgreSQL
- Alternativas de funções personalizadas SQLite
- Instruções de controle de fluxo (IF/ELSE, WHILE, LOOP)
- Conceitos e uso de cursores
- A diferença entre funções armazenadas e procedimentos armazenados
- Aplicação em cenários de negócios reais
📝 Exercícios
-
Exercício Básico: Crie um procedimento armazenado que aceite um ID de departamento como parâmetro e retorne os 3 funcionários mais bem pagos desse departamento.
-
Exercício Intermediário: Crie uma função que calcula o pagamento por tempo de serviço com base nos anos de serviço do funcionário (de hire_date até a data atual):
- 1–3 anos: 5% do salário base
- 3–5 anos: 10% do salário base
- Acima de 5 anos: 15% do salário base
-
Questão Reflexiva: Compare os prós e contras de processar lógica de negócios em código da camada de aplicação vs procedimentos armazenados no banco de dados.
Próxima Lição → 23-triggers-events.md



