Dominando Consultas com Múltiplas Tabelas no MySQL

Fundamentos da Sintaxe de Consulta

A estrutura básica parra recuperar dados de múltiplas tabelas envolve a cláusula SELECT, listando as colunas desejadas, a cláusula FROM especificando as tabelas de origem e a cláusula WHERE para definir as condições de filtragem e junção.

Preparação do Ambiente e Dados Iniciais

Para ilustrar os conceitos, criaremos um esquema simples contendo departamentos e funcionários.

-- Criação da tabela de departamentos
CREATE TABLE departamento (
    id INT PRIMARY KEY AUTO_INCREMENT,
    nome VARCHAR(50) NOT NULL
);

INSERT INTO departamento (nome) VALUES ('Engenharia'), ('Vendas'), ('Financeiro');

-- Criação da tabela de funcionários
CREATE TABLE funcionario (
    id INT PRIMARY KEY AUTO_INCREMENT,
    nome VARCHAR(50),
    sexo CHAR(1),
    salario DECIMAL(10, 2),
    data_admissao DATE,
    id_departamento INT,
    FOREIGN KEY (id_departamento) REFERENCES departamento(id)
);

INSERT INTO funcionario (nome, sexo, salario, data_admissao, id_departamento) VALUES
('Ana Silva', 'F', 7500.00, '2015-03-10', 1),
('Carlos Souza', 'M', 4200.00, '2018-07-22', 2),
('Mariana Costa', 'F', 8900.00, '2012-11-05', 2),
('Roberto Lima', 'M', 5100.00, '2020-01-15', 3),
('Juliana Alves', 'F', 4800.00, '2019-06-30', 1);

O Conceito de Produto Cartesiano

Quando duas ou mais tabelas são consultadas sem uma condição de junção adequada, o banco de dados gera um produto cartesiano. Isso significa que cada linha da primeira tabela é combinada com todas as linhas da segunda tabela. Para obter resultados úteis, é essencial aplicar condições que eliminem essas combinações inválidas.

Classificação das Junções (Joins)

1. Inner Join (Junção Interna)

Retorna apenas os registros que têm correspondência em ambas as tabelas.

Junção Implícita: Utiliza a cláusula WHERE para filtrar o produto cartesiano.

-- Exemplo: Buscar dados de funcionários e seus respectivos departamentos
SELECT f.nome AS nome_funcionario, f.salario, d.nome AS nome_departamento
FROM funcionario f, departamento d
WHERE f.id_departamento = d.id;

Junção Explícita: Utiliza a palavra-chave INNER JOIN (ou apenas JOIN) com a cláusula ON. Esta é a abordagem recomendada por ser mais legível e padronizada.

SELECT f.nome, f.salario, d.nome
FROM funcionario f
INNER JOIN departamento d ON f.id_departamento = d.id;

2. Outer Join (Junção Externa)

Retorna todos os registros de uma tabela, mesmo que não haja correspondência na outra.

Left Outer Join: Retorna todos os registros da tabela à esquerda e os correspondantes da direita.

-- Adicionando um funcionário sem departamento para o exemplo
INSERT INTO funcionario (nome, sexo, salario, data_admissao, id_departamento) 
VALUES ('Pedro Santos', 'M', 3000.00, '2023-01-10', NULL);

-- Consultando todos os funcionários, exibindo o departamento se existir
SELECT f.nome, d.nome AS departamento
FROM funcionario f
LEFT JOIN departamento d ON f.id_departamento = d.id;

Right Outer Join: Retorna todos os registros da tabela à direita e os correspondentes da esquerda.

SELECT f.nome, d.nome AS departamento
FROM funcionario f
RIGHT JOIN departamento d ON f.id_departamento = d.id;

Subconsultas (Subqueries)

Uma subconsulta é uma consulta aninhada dentro de outra. Elas são úteis para dividir problemas complexos em etapas lógicas.

Subconsulta de Linha e Coluna Únicas: Pode ser usada com operadores de comparação (=, >, <, etc.).

-- Encontrar funcionários que ganham acima da média
SELECT nome, salario
FROM funcionario
WHERE salario > (SELECT AVG(salario) FROM funcionario);

Subconsulta de Múltiplas Linhas e Coluna Única: Utiliza o operador IN.

-- Buscar funcionários dos departamentos de Engenharia e Vendas
SELECT nome, salario
FROM funcionario
WHERE id_departamento IN (SELECT id FROM departamento WHERE nome IN ('Engenharia', 'Vendas'));

Subconsulta de Múltiplas Linhas e Colunas (Tabela Virtual): A subconsulta atua como uma tabela temporária na cláusula FROM.

-- Consultar funcionários admitidos após 2018 e seus departamentos
SELECT d.nome AS departamento, sub.nome AS funcionario, sub.data_admissao
FROM departamento d
INNER JOIN (
    SELECT nome, id_departamento, data_admissao
    FROM funcionario
    WHERE data_admissao > '2018-01-01'
) sub ON d.id = sub.id_departamento;

Exercícios Práticos de Consultas Complexas

Para consolidar o aprendizado, considere o seguinte esquema de banco de dados corporativo:

CREATE TABLE setor (
    id INT PRIMARY KEY,
    nome_setor VARCHAR(50),
    localizacao VARCHAR(50)
);

INSERT INTO setor VALUES
(10, 'Pesquisa', 'Sao Paulo'),
(20, 'Recursos Humanos', 'Rio de Janeiro'),
(30, 'Comercial', 'Belo Horizonte'),
(40, 'Contabilidade', 'Curitiba');

CREATE TABLE cargo (
    id INT PRIMARY KEY,
    titulo VARCHAR(50),
    descricao VARCHAR(100)
);

INSERT INTO cargo VALUES
(1, 'Diretor', 'Gestão estratégica'),
(2, 'Gerente', 'Gestão de equipes'),
(3, 'Analista', 'Execução de tarefas técnicas'),
(4, 'Assistente', 'Suporte operacional');

CREATE TABLE colaborador (
    id INT PRIMARY KEY,
    nome_colab VARCHAR(50),
    id_cargo INT,
    id_gerente INT,
    data_contratacao DATE,
    salario_base DECIMAL(10, 2),
    comissao DECIMAL(10, 2),
    id_setor INT,
    FOREIGN KEY (id_cargo) REFERENCES cargo(id),
    FOREIGN KEY (id_setor) REFERENCES setor(id)
);

INSERT INTO colaborador VALUES
(101, 'Lucas Oliveira', 4, 104, '2010-05-15', 4500.00, NULL, 20),
(102, 'Beatriz Ferreira', 3, 106, '2012-08-20', 8000.00, 1500.00, 30),
(103, 'Gabriel Mendes', 3, 106, '2015-02-10', 7500.00, 2000.00, 30),
(104, 'Camila Rocha', 2, 109, '2008-11-01', 12000.00, NULL, 20),
(105, 'Felipe Santos', 4, 106, '2018-04-12', 4000.00, 500.00, 30),
(106, 'Isabela Lima', 2, 109, '2009-07-30', 11500.00, NULL, 30),
(107, 'Thiago Costa', 2, 109, '2011-09-14', 10500.00, NULL, 10),
(108, 'Larissa Almeida', 4, 104, '2020-01-05', 4200.00, NULL, 20),
(109, 'Marcos Vieira', 1, NULL, '2005-03-22', 25000.00, NULL, 10),
(110, 'Amanda Souza', 3, 106, '2016-12-08', 8500.00, 1000.00, 30);

CREATE TABLE nivel_salarial (
    grau INT PRIMARY KEY,
    salario_min INT,
    salario_max INT
);

INSERT INTO nivel_salarial VALUES
(1, 3000, 5000),
(2, 5001, 8000),
(3, 8001, 12000),
(4, 12001, 18000),
(5, 18001, 50000);

1. Detalhes Básicos de Colaboradores e Cargos

SELECT
    c.id,
    c.nome_colab,
    c.salario_base,
    ca.titulo,
    ca.descricao
FROM colaborador c
INNER JOIN cargo ca ON c.id_cargo = ca.id;

2. Incluindo Informações do Setor

SELECT
    c.id,
    c.nome_colab,
    c.salario_base,
    ca.titulo,
    s.nome_setor,
    s.localizacao
FROM colaborador c
INNER JOIN cargo ca ON c.id_cargo = ca.id
INNER JOIN setor s ON c.id_setor = s.id;

3. Classificação por Nível Salarial

SELECT
    c.nome_colab,
    c.salario_base,
    ns.grau
FROM colaborador c
INNER JOIN nivel_salarial ns
    ON c.salario_base BETWEEN ns.salario_min AND ns.salario_max;

4. Visão Completa do Colaborador

SELECT
    c.id,
    c.nome_colab,
    c.salario_base,
    ca.titulo,
    s.nome_setor,
    s.localizacao,
    ns.grau AS nivel_salario
FROM colaborador c
INNER JOIN cargo ca ON c.id_cargo = ca.id
INNER JOIN setor s ON c.id_setor = s.id
INNER JOIN nivel_salarial ns
    ON c.salario_base BETWEEN ns.salario_min AND ns.salario_max;

5. Contagem de Colaboradores por Setor

SELECT
    s.id,
    s.nome_setor,
    s.localizacao,
    COALESCE(sub.total_colaboradores, 0) AS total
FROM setor s
LEFT JOIN (
    SELECT id_setor, COUNT(id) AS total_colaboradores
    FROM colaborador
    GROUP BY id_setor
) sub ON s.id = sub.id_setor;

6. Hierarquia: Colaborador e seu Gerente Direto

Utilizamos um LEFT JOIN para garantir que colaboradores sem gerente (como o diretor) também sejam listados na consulta final.

SELECT
    col.nome_colab AS colaborador,
    ger.nome_colab AS gerente
FROM colaborador col
LEFT JOIN colaborador ger ON col.id_gerente = ger.id;

Tags: MySQL SQL Joins Subqueries Relational-Databases

Publicado em 6-14 21:31 por Thomas