Procedimentos Armazenados e Funções SQL

Procedimentos Armazenados e Funções SQL

Analogia do Mundo Real

Imagine que você está em um banco:

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:

💡 SQLite não suporta procedimentos armazenados. Os exemplos abaixo demonstram conceitos e sintaxe MySQL/PostgreSQL. Usuários SQLite podem usar funções personalizadas como alternativa.

Sintaxe de Procedimento Armazenado MySQL

SQL
-- 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

SQL
-- 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:

SQL
-- 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

SQL
-- 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

SQL
-- 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

SQL
-- 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:

SQL
-- 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:

SQL
-- 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)

SQL
-- 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)

SQL
-- 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;
💡 Dica: Procedimentos armazenados são adequados para encapsular lógica de negócios complexa, enquanto funções são adequadas para cálculos reutilizáveis. Escolha a abordagem apropriada com base nas necessidades reais.

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.

SQL
-- 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;
▶ Experimente

Exemplo: Versão PostgreSQL — Função de Ajuste Salarial de Funcionário (Dificuldade ⭐⭐⭐)

SQL
-- 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);
▶ Experimente

Cenários de Aplicação

Cenário 1: Processamento de Dados em Lote (Dificuldade ⭐⭐⭐)

SQL
-- 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 ⭐⭐⭐)

SQL
-- 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:

📝 Exercícios

  1. 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.

  2. 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
  3. 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

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%