Prática: Consulta Multitabelas Abrangente

Prática: Consulta Multitabelas Abrangente

📋 Requisitos do Projeto

Esta seção usa um cenário de sistema de gerenciamento de pedidos de e-commerce para aplicar abrangentemente as habilidades de consulta multitabelas aprendidas anteriormente.

Estrutura das Tabelas do Banco de Dados

Usamos um banco de dados unificado de 4 tabelas:

SQL
-- Tabela de departamentos
CREATE TABLE departments (
    department_id   INT PRIMARY KEY,
    department_name VARCHAR(50) NOT NULL,
    city            VARCHAR(50),
    budget          DECIMAL(12,2)
);

-- Tabela de funcionários
CREATE TABLE employees (
    employee_id   INT PRIMARY KEY,
    first_name    VARCHAR(50) NOT NULL,
    last_name     VARCHAR(50) NOT NULL,
    email         VARCHAR(100),
    salary        DECIMAL(10,2),
    department_id INT,
    hire_date     DATE,
    FOREIGN KEY (department_id) REFERENCES departments(department_id)
);

-- Tabela de pedidos
CREATE TABLE orders (
    order_id     INT PRIMARY KEY,
    customer_id  INT,
    employee_id  INT,
    order_date   DATE,
    total_amount DECIMAL(12,2),
    status       VARCHAR(20),
    FOREIGN KEY (employee_id) REFERENCES employees(employee_id)
);

-- Tabela de produtos
CREATE TABLE products (
    product_id   INT PRIMARY KEY,
    product_name VARCHAR(100) NOT NULL,
    price        DECIMAL(10,2),
    stock        INT,
    department_id INT,
    FOREIGN KEY (department_id) REFERENCES departments(department_id)
);

Dados de Teste

SQL
-- Dados de departamentos
INSERT INTO departments VALUES (1, 'Technology', 'Beijing', 500000);
INSERT INTO departments VALUES (2, 'Sales', 'Shanghai', 300000);
INSERT INTO departments VALUES (3, 'Marketing', 'Guangzhou', 200000);
INSERT INTO departments VALUES (4, 'Finance', 'Shenzhen', 150000);

-- Dados de funcionários
INSERT INTO employees VALUES (1, 'Zhang', 'Wei', 'zhangwei@co.com', 12000, 1, '2023-03-15');
INSERT INTO employees VALUES (2, 'Li', 'Na', 'lina@co.com', 9500, 1, '2023-06-20');
INSERT INTO employees VALUES (3, 'Wang', 'Qiang', 'wangqiang@co.com', 8000, 2, '2022-11-10');
INSERT INTO employees VALUES (4, 'Zhao', 'Min', 'zhaomin@co.com', 11000, 2, '2024-01-05');
INSERT INTO employees VALUES (5, 'Liu', 'Yang', 'liuyang@co.com', 7500, 3, '2024-04-18');
INSERT INTO employees VALUES (6, 'Chen', 'Jing', 'chenjing@co.com', 9000, NULL, '2025-02-28');

-- Dados de pedidos
INSERT INTO orders VALUES (1001, 101, 3, '2026-05-10', 2500.00, 'completed');
INSERT INTO orders VALUES (1002, 102, 3, '2026-05-15', 1800.00, 'completed');
INSERT INTO orders VALUES (1003, 103, 4, '2026-06-01', 3200.00, 'shipped');
INSERT INTO orders VALUES (1004, 101, 1, '2026-06-05', 950.00, 'pending');
INSERT INTO orders VALUES (1005, 104, 4, '2026-06-10', 4100.00, 'completed');
INSERT INTO orders VALUES (1006, 105, 3, '2026-06-15', 2800.00, 'cancelled');

-- Dados de produtos
INSERT INTO products VALUES (1, 'Laptop', 6999.00, 50, 1);
INSERT INTO products VALUES (2, 'Wireless Mouse', 129.00, 200, 1);
INSERT INTO products VALUES (3, 'Office Desk', 899.00, 30, 2);
INSERT INTO products VALUES (4, 'Projector', 3500.00, 15, 3);
INSERT INTO products VALUES (5, 'Printer', 2200.00, 25, 1);

🏗️ Prática de Consultas

Consulta 1: Relatório de Desempenho de Pedidos por Funcionário

Requisito: Visualizar a contagem de pedidos e o total de vendas de cada funcionário, incluindo funcionários sem pedidos.

SQL
SELECT
    e.employee_id,
    CONCAT(e.first_name, e.last_name) AS nome,
    d.department_name AS departamento,
    COUNT(o.order_id) AS qtd_pedidos,
    COALESCE(SUM(o.total_amount), 0) AS total_vendas
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
LEFT JOIN orders o ON e.employee_id = o.employee_id
GROUP BY e.employee_id, e.first_name, e.last_name, d.department_name
ORDER BY total_vendas DESC;

Resultado:

TEXT
employee_id | nome       | departamento | qtd_pedidos | total_vendas
-----------+------------+--------------+-------------+-------------
         4 | Zhao Min   | Sales        |           2 |      7300.00
         3 | Wang Qiang | Sales        |           3 |      7100.00
         1 | Zhang Wei  | Technology   |           1 |       950.00
         2 | Li Na      | Technology   |           0 |         0.00
         5 | Liu Yang   | Marketing    |           0 |         0.00
         6 | Chen Jing  | NULL         |           0 |         0.00

Explicação do Código:

  1. LEFT JOIN departments — Junta a tabela de departamentos para obter nomes de departamentos, mantendo funcionários sem departamento
  2. LEFT JOIN orders — Junta a tabela de pedidos, mantendo funcionários sem pedidos
  3. COALESCE(SUM(...), 0) — Converte NULL para 0 para evitar exibir campos vazios
  4. GROUP BY — Agrupa por funcionário, agregando contagem de pedidos e valor total

Consulta 2: Ranking de Vendas por Departamento

Requisito: Calcular o total de vendas por departamento e classificá-los.

SQL
SELECT
    d.department_name AS departamento,
    d.city AS cidade,
    COUNT(DISTINCT e.employee_id) AS qtd_funcionarios,
    COUNT(o.order_id) AS total_pedidos,
    COALESCE(SUM(o.total_amount), 0) AS total_vendas,
    RANK() OVER (ORDER BY COALESCE(SUM(o.total_amount), 0) DESC) AS ranking
FROM departments d
LEFT JOIN employees e ON d.department_id = e.department_id
LEFT JOIN orders o ON e.employee_id = o.employee_id
GROUP BY d.department_id, d.department_name, d.city
ORDER BY total_vendas DESC;

Explicação do Código:

  1. Cadeia de LEFT JOIN: departments → employees → orders
  2. COUNT(DISTINCT e.employee_id) — Deduplica a contagem de funcionários para evitar contagem duplicada por múltiplos pedidos
  3. RANK() — Função de janela que classifica por total de vendas
  4. COALESCE — Trata departamentos sem pedidos, exibindo 0

Consulta 3: Funcionários de Alto Salário e Detalhes de Seus Pedidos

Requisito: Encontrar funcionários cujo salário está acima da média da empresa, junto com suas informações de pedidos.

SQL
SELECT
    e.employee_id,
    CONCAT(e.first_name, e.last_name) AS nome,
    e.salary AS salario,
    o.order_id,
    o.order_date AS data_pedido,
    o.total_amount AS valor_pedido,
    o.status AS status_pedido
FROM employees e
INNER JOIN orders o ON e.employee_id = o.employee_id
WHERE e.salary > (
    SELECT AVG(salary) FROM employees
)
ORDER BY e.salary DESC, o.order_date;

Explicação do Código:

  1. Subconsulta (SELECT AVG(salary) FROM employees) — Calcula o salário médio da empresa
  2. INNER JOIN — Mantém apenas funcionários de alto salário que possuem pedidos
  3. WHERE e.salary > (...) — Usa o resultado da subconsulta como condição de filtro
  4. ORDER BY ordena por salário decrescente primeiro, depois por data crescente

Consulta 4: Consulta Cruzada — Pedidos Sem Produtos do Departamento Correspondente

Requisito: Encontrar pedidos cujo departamento do funcionário não possui produtos.

SQL
SELECT
    o.order_id,
    CONCAT(e.first_name, e.last_name) AS nome_funcionario,
    d.department_name AS departamento,
    o.total_amount AS valor_pedido
FROM orders o
INNER JOIN employees e ON o.employee_id = e.employee_id
LEFT JOIN departments d ON e.department_id = d.department_id
WHERE e.department_id IS NOT NULL
  AND e.department_id NOT IN (
      SELECT DISTINCT department_id FROM products WHERE department_id IS NOT NULL
  );

Explicação do Código:

  1. INNER JOIN employees — Junta pedidos e funcionários
  2. LEFT JOIN departments — Obtém nomes de departamentos
  3. NOT IN (subconsulta) — Exclui departamentos que possuem produtos
  4. Combina JOIN e subconsulta para filtragem mista

Consulta 5: Usar UNION para Mesclar Dados Multidimensionais

Requisito: Gerar um diretório abrangente mesclando "funcionários com pedidos" e "todos os departamentos".

SQL
-- Funcionários com pedidos concluídos
SELECT
    CONCAT(first_name, last_name) AS nome,
    'Funcionário' AS tipo,
    CAST(salary AS VARCHAR) AS detalhes
FROM employees
WHERE employee_id IN (
    SELECT DISTINCT employee_id FROM orders WHERE status = 'completed'
)
UNION ALL
-- Todos os departamentos
SELECT
    department_name,
    'Departamento',
    CONCAT('Orçamento: ', CAST(budget AS VARCHAR))
FROM departments
ORDER BY tipo, nome;

Explicação do Código:

  1. Primeiro SELECT — Usa subconsulta para filtrar funcionários com pedidos concluídos
  2. Segundo SELECT — Obtém todos os departamentos
  3. UNION ALL — Mescla dois conjuntos de resultados, mantendo todas as linhas
  4. Adiciona uma coluna constante "tipo" para distinguir fontes de dados

Consulta 6: Análise Abrangente — Distribuição de Status de Pedidos e Desempenho de Funcionários

Requisito: Calcular a distribuição de status de pedidos para cada funcionário e encontrar funcionários que processaram pedidos "concluídos".

SQL
SELECT
    CONCAT(e.first_name, e.last_name) AS nome_funcionario,
    d.department_name AS departamento,
    SUM(CASE WHEN o.status = 'completed' THEN 1 ELSE 0 END) AS concluidos,
    SUM(CASE WHEN o.status = 'shipped' THEN 1 ELSE 0 END) AS enviados,
    SUM(CASE WHEN o.status = 'pending' THEN 1 ELSE 0 END) AS pendentes,
    SUM(CASE WHEN o.status = 'cancelled' THEN 1 ELSE 0 END) AS cancelados,
    COUNT(o.order_id) AS total_pedidos
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
LEFT JOIN orders o ON e.employee_id = o.employee_id
WHERE e.employee_id IN (
    SELECT DISTINCT employee_id FROM orders
)
GROUP BY e.employee_id, e.first_name, e.last_name, d.department_name
ORDER BY total_pedidos DESC;

Explicação do Código:

  1. CASE WHEN agregação condicional — Converte dados de linhas em colunas (pivô)
  2. LEFT JOIN mantém todos os dados associados
  3. Subconsulta IN (...) filtra funcionários sem pedidos
  4. GROUP BY agrupa e agrega por funcionário

❓ Perguntas Frequentes

P: Quando devo usar INNER JOIN vs LEFT JOIN? R: Se você precisar manter todos os registros da tabela da esquerda (mesmo quando a tabela da direita não tiver correspondência), use LEFT JOIN. Se você só precisa de registros que correspondem em ambas as tabelas, use INNER JOIN. Na prática, consultas de relatórios tipicamente usam LEFT JOIN, enquanto validação de dados usa INNER JOIN.

P: Qual tem melhor desempenho, subconsulta ou JOIN? R: Otimizadores de bancos de dados modernos tipicamente convertem subconsultas em JOINs para execução. Para associações simples, JOIN é mais intuitivo; para verificações de existência ("existe"), subconsultas EXISTS ou IN são mais semânticas. Use EXPLAIN para verificar diferenças de desempenho.

P: Como depurar consultas multitabelas complexas? R: Construa incrementalmente: comece com a subconsulta mais interna, verifique se o resultado está correto, e então envolva camada por camada. Você também pode usar CTEs (cláusula WITH) para dividir consultas complexas em múltiplos conjuntos de resultados temporários legíveis.

P: O SELECT após GROUP BY pode conter apenas funções de agregação e colunas agrupadas? R: Sim. Colunas não agregadas em SELECT devem todas aparecer em GROUP BY, caso contrário o banco de dados lançará um erro. Este é um requisito do padrão SQL.


📖 Resumo

Habilidade Aplicação nesta Seção
INNER JOIN Associar tabelas com relacionamentos de correspondência
LEFT JOIN Manter todos os registros da tabela da esquerda
Subconsulta (WHERE IN) Usada como condição de filtro
Subconsulta (escalar) Retorna um valor único para comparação
UNION ALL Mesclar conjuntos de resultados de diferentes dimensões
CASE WHEN Agregação condicional (pivô)
Funções de Agregação COUNT, SUM, AVG, COALESCE
Funções de Janela RANK() para classificação

Pontos-Chave de Prática:


📝 Exercícios

  1. Escreva uma consulta: Encontrar as informações do departamento do funcionário com o maior salário, incluindo nome do departamento, cidade e orçamento.
  2. Escreva uma consulta: Contar o número de departamentos e total de funcionários por cidade, usando LEFT JOIN para garantir que cidades sem funcionários também sejam exibidas.
  3. Escreva uma consulta: Encontrar funcionários que nunca processaram um pedido, implementando tanto com subconsulta (NOT IN) quanto com LEFT JOIN ... IS NULL, e comparar se os resultados são consistentes.
  4. Escreva uma consulta: Dividir funcionários em três grupos por salário — "Alto (>10000)", "Médio (6000-10000)", "Baixo (<6000)" — e contar o número de pessoas e salário médio para cada grupo.

Próxima Lição

A seguir aprenderemos Funções de Agregação — domine COUNT, SUM, AVG, MAX, MIN, além do uso avançado de GROUP BY e HAVING.

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%