A Linguagem de Consulta de Dados (DQL - Data Query Language) é fundamental para interagir com bancos de dados relacionais. Ela permite recuperar informações armazenadas de diversas formas, desde seleções simples até operações complexas envolvendo múltiplas tabelas e subconsultas.
É útil compreender a ordem de execução das cláusulas SQL, pois ela dita como o banco de dados processa sua consulta:
FROM: Seleciona as tabelas de onde os dados serão extraídos.WHERE: Filtra linhas antes de qualquer agrupamento.GROUP BY: Agrupa linhas que têm os mesmos valores em colunas especificadas.- Funções Agregadas: Calculam valores para cada grupo (ou para o conjunto total).
HAVING: Filtra grupos após o agrupamento.SELECT: Seleciona as colunas a serem exibidas.ORDER BY: Ordena o conujnto de resultados.LIMIT: Restringe o número de linhas retornadas.
Após uma operação GROUP BY, a cláusula SELECT só pode incluir colunas de agrupamento ou resultados de funções agregadas.
1. Configuração Inicial: Criação da Tabela de Exemplo
Para ilustrar as operações de consulta, vamos criar uma tabela chamada itens_loja e popular com alguns dados de exemplo:
CREATE TABLE itens_loja (
id_item INT PRIMARY KEY,
nome_item VARCHAR(50),
valor_unitario DOUBLE,
id_categoria_fk VARCHAR(32)
);
INSERT INTO itens_loja (id_item, nome_item, valor_unitario, id_categoria_fk) VALUES (1, 'Notebook Pro', 5500.00, 'cat01');
INSERT INTO itens_loja (id_item, nome_item, valor_unitario, id_categoria_fk) VALUES (2, 'Mouse Gamer X', 350.50, 'cat01');
INSERT INTO itens_loja (id_item, nome_item, valor_unitario, id_categoria_fk) VALUES (3, 'Teclado Mecânico', 600.00, 'cat01');
INSERT INTO itens_loja (id_item, nome_item, valor_unitario, id_categoria_fk) VALUES (4, 'Camisa Social Slim', 120.00, 'cat02');
INSERT INTO itens_loja (id_item, nome_item, valor_unitario, id_categoria_fk) VALUES (5, 'Calça Jeans Confort', 250.00, 'cat02');
INSERT INTO itens_loja (id_item, nome_item, valor_unitario, id_categoria_fk) VALUES (6, 'Jaqueta de Couro', 800.00, 'cat02');
INSERT INTO itens_loja (id_item, nome_item, valor_unitario, id_categoria_fk) VALUES (7, 'Vestido Verão', 300.00, 'cat02');
INSERT INTO itens_loja (id_item, nome_item, valor_unitario, id_categoria_fk) VALUES (8, 'Perfume Essência', 450.00, 'cat03');
INSERT INTO itens_loja (id_item, nome_item, valor_unitario, id_categoria_fk) VALUES (9, 'Creme Hidratante', 80.00, 'cat03');
INSERT INTO itens_loja (id_item, nome_item, valor_unitario, id_categoria_fk) VALUES (10, 'Shampoo Anti-Queda', 45.00, 'cat03');
INSERT INTO itens_loja (id_item, nome_item, valor_unitario, id_categoria_fk) VALUES (11, 'Chocolate Premium', 65.00, 'cat04');
INSERT INTO itens_loja (id_item, nome_item, valor_unitario, id_categoria_fk) VALUES (12, 'Café Gourmet', 55.00, 'cat05');
INSERT INTO itens_loja (id_item, nome_item, valor_unitario, id_categoria_fk) VALUES (13, 'Tênis Esportivo', 300.00, 'cat02');
2. Sintaxe Básica da Consulta SELECT
SELECT [DISTINCT]
coluna1, coluna2, ... | *
FROM
nome_da_tabela
WHERE
condicoes;
- A cláusula
SELECTespecifica as colunas a serem retornadas. Um asterisco (*) recupera todas as colunas. - A palavra-chave
DISTINCTelimina linhas duplicadas do resultado. - Aliases podem ser atribuídos a tabelas ou colunas usando
AS(que é opcional) para simplificar a leitura.
3. Consultas Simples
Exemplos práticos de consultas básicas:
-- 1. Recuperar todos os dados de todos os itens
SELECT * FROM itens_loja;
-- 2. Consultar apenas o nome e o preço de cada item
SELECT nome_item, valor_unitario FROM itens_loja;
-- 3. Usar aliases para colunas e tabelas
SELECT
pr.nome_item AS "Produto",
pr.valor_unitario AS "Preço Unitário"
FROM
itens_loja AS pr;
-- 4. Exibir valores unitários únicos, removendo duplicatas
SELECT DISTINCT valor_unitario FROM itens_loja;
-- 5. Consulta com expressões: mostrar o preço de cada item com um acréscimo de 50 unidades monetárias
SELECT nome_item, valor_unitario + 50 AS valor_com_acrescimo FROM itens_loja;
4. Consultas com Filtros (WHERE)
Utilize a cláusula WHERE para aplicar condições e filtrar as linhas do resultado:
-- Buscar itens com nome 'Jaqueta de Couro'
SELECT * FROM itens_loja WHERE nome_item = 'Jaqueta de Couro';
-- Encontrar itens com valor unitário de 300.00
SELECT * FROM itens_loja WHERE valor_unitario = 300.00;
-- Itens que NÃO custam 300.00
SELECT * FROM itens_loja WHERE valor_unitario != 300.00;
SELECT * FROM itens_loja WHERE valor_unitario <> 300.00; -- Alternativa ao !=
SELECT * FROM itens_loja WHERE NOT (valor_unitario = 300.00);
-- Itens com valor superior a 100.00
SELECT * FROM itens_loja WHERE valor_unitario > 100.00;
-- Itens com valor entre 200.00 e 500.00 (inclusive)
SELECT * FROM itens_loja WHERE valor_unitario >= 200.00 AND valor_unitario <= 500.00;
SELECT * FROM itens_loja WHERE valor_unitario BETWEEN 200.00 AND 500.00; -- Alternativa usando BETWEEN
-- Itens com valor de 250.00 ou 450.00
SELECT * FROM itens_loja WHERE valor_unitario = 250.00 OR valor_unitario = 450.00;
SELECT * FROM itens_loja WHERE valor_unitario IN (250.00, 450.00); -- Alternativa usando IN
-- Itens cujo nome contém a palavra 'Mouse'
SELECT * FROM itens_loja WHERE nome_item LIKE '%Mouse%';
-- Itens cujo nome começa com 'C'
SELECT * FROM itens_loja WHERE nome_item LIKE 'C%';
-- Itens cujo segundo caractere do nome é 'a'
SELECT * FROM itens_loja WHERE nome_item LIKE '_a%';
-- Itens que não possuem uma categoria associada (id_categoria_fk é NULL)
SELECT * FROM itens_loja WHERE id_categoria_fk IS NULL;
-- Itens que possuem uma categoria associada (id_categoria_fk NÃO é NULL)
SELECT * FROM itens_loja WHERE id_categoria_fk IS NOT NULL;
5. Consultas com Ordenação (ORDER BY)
A cláusula ORDER BY permite organizar os resultados em ordem crescente (ASC, padrão) ou decrescente (DESC) com base em uma ou mais colunas.
-- 1. Listar itens pelo valor unitário em ordem decrescente
SELECT * FROM itens_loja ORDER BY valor_unitario DESC;
-- 2. Ordenar por valor (decrescente) e, em caso de empate, por nome do item (crescente)
SELECT * FROM itens_loja ORDER BY valor_unitario DESC, nome_item ASC;
-- 3. Mostrar os valores unitários distintos, ordenados de forma crescente
SELECT DISTINCT valor_unitario FROM itens_loja ORDER BY valor_unitario ASC;
6. Funções Agregadas
As funções agregadas realizam cálculos em um conjunto de valores de uma coluna e retornam um único resultado. Elas ignoram valores NULL.
| Função Agregada | Descrição |
|---|---|
COUNT(coluna | *) |
Conta o número de linhas não-NULL em uma coluna ou o total de linhas. |
SUM(coluna_numerica) |
Calcula a soma dos valores de uma coluna numérica. |
MAX(coluna) |
Retorna o maior valor de uma coluna. |
MIN(coluna) |
Retorna o menor valor de uma coluna. |
AVG(coluna_numerica) |
Calcula a média dos valores de uma coluna numérica. |
-- 1. Contar o número total de itens na loja
SELECT COUNT(*) AS total_itens FROM itens_loja;
-- 2. Contar quantos itens têm valor unitário acima de 100.00
SELECT COUNT(*) AS itens_caros FROM itens_loja WHERE valor_unitario > 100.00;
-- 3. Calcular a soma dos valores unitários dos itens da categoria 'cat02'
SELECT SUM(valor_unitario) AS soma_categoria_02 FROM itens_loja WHERE id_categoria_fk = 'cat02';
-- 4. Encontrar o valor unitário médio dos itens da categoria 'cat01'
SELECT AVG(valor_unitario) AS media_categoria_01 FROM itens_loja WHERE id_categoria_fk = 'cat01';
-- 5. Determinar o maior e o menor valor unitário entre todos os itens
SELECT MAX(valor_unitario) AS maior_valor, MIN(valor_unitario) AS menor_valor FROM itens_loja;
7. Consultas com Agrupamento (GROUP BY e HAVING)
A cláusula GROUP BY organiza as linhas em grupos, permitindo aplicar funções agregadas a cada grupo. A cláusula HAVING filtra esses grupos, similar ao WHERE, mas opera sobre os resultados agregados.
- Diferença entre
WHEREeHAVING:WHEREfiltra linhas antes do agrupamento.HAVINGfiltra grupos depois do agrupamento e pode usar funções agregadas.
-- 1. Contar quantos itens existem em cada categoria
SELECT id_categoria_fk, COUNT(*) AS quantidade_itens FROM itens_loja GROUP BY id_categoria_fk;
-- 2. Mostrar as categorias que possuem mais de 2 itens, junto com a contagem
SELECT id_categoria_fk, COUNT(*) AS quantidade_itens
FROM itens_loja
GROUP BY id_categoria_fk
HAVING COUNT(*) > 2;
8. Paginação de Resultados (LIMIT)
A cláusula LIMIT é utilizada para restringir o número de linhas retornadas por uma consulta, útil para implementar paginação em aplicações. A sintaxe é LIMIT M, N, onde M é o deslocamento (índice da primeira linha, começando em 0) e N é o número máximo de linhas a serem retornadas.
-- Exemplo de paginação:
-- Para a página 1 (com 5 itens por página): (1 - 1) * 5 = 0
-- Para a página 2 (com 5 itens por página): (2 - 1) * 5 = 5
-- Recuperar os primeiros 5 itens (equivalente à página 1)
SELECT * FROM itens_loja LIMIT 0, 5;
-- Recuperar os próximos 5 itens (equivalente à página 2)
SELECT * FROM itens_loja LIMIT 5, 5;
-- Recuperar os itens do 3º ao 7º (5 itens, começando do índice 2)
SELECT * FROM itens_loja LIMIT 2, 5;
9. Inserindo Dados a Partir de uma Consulta (INSERT INTO SELECT)
Essa instrução permite inserir dados em uma tabela selecionando-os de outra tabela. É útil para copiar dados ou criar subconjuntos.
-- Criação de uma nova tabela para itens de promoção
CREATE TABLE itens_promocao (
id_item INT PRIMARY KEY,
nome_item VARCHAR(50),
valor_original DOUBLE
);
-- Inserir na tabela 'itens_promocao' os itens da categoria 'cat01' da tabela 'itens_loja'
INSERT INTO itens_promocao (id_item, nome_item, valor_original)
SELECT id_item, nome_item, valor_unitario
FROM itens_loja
WHERE id_categoria_fk = 'cat01';
-- Verificar os itens inseridos na tabela de promoção
SELECT * FROM itens_promocao;
Operações com Múltiplas Tabelas
1. Gerenciamento de Chaves Estrangeiras
Para manter a integridade referencial entre tabelas, utilizamos chaves estrangeiras. Considere duas tabelas: uma de categorias (tabela principal) e uma de produtos (tabela secundária). A tabela principal possui a chave primária, e a secundária referencia essa chave através de uma chave estrangeira, estabelecendo uma relação de um-para-muitos.
- Uma chave estrangeira na tabela secundária aponta para a chave primária da tabela principal.
- Os tipos de dados da chave estrangeira e da chave primária correspondente devem ser idênticos.
- O objetivo principal é garantir a consistência dos dados, impedindo referências a registros inexistentes.
Vamos criar as tabelas categorias_itens e produtos_detalhes e adicionar uma chave estrangeira:
-- Criação da tabela principal: Categorias
CREATE TABLE categorias_itens (
id_categoria VARCHAR(32) PRIMARY KEY,
nome_categoria VARCHAR(100)
);
-- Criação da tabela secundária: Produtos
CREATE TABLE produtos_detalhes (
id_produto VARCHAR(32) PRIMARY KEY,
descricao_produto VARCHAR(100),
preco DOUBLE,
id_categoria_fk VARCHAR(32)
);
-- Adicionar a restrição de chave estrangeira à tabela 'produtos_detalhes'
ALTER TABLE produtos_detalhes
ADD CONSTRAINT fk_produto_categoria FOREIGN KEY (id_categoria_fk) REFERENCES categorias_itens (id_categoria);
Exemplos de operações de dados com chave estrangeira:
-- Inserir dados na tabela de categorias
INSERT INTO categorias_itens (id_categoria, nome_categoria) VALUES ('eletr', 'Eletrônicos');
INSERT INTO categorias_itens (id_categoria, nome_categoria) VALUES ('vest', 'Vestuário');
INSERT INTO categorias_itens (id_categoria, nome_categoria) VALUES ('alim', 'Alimentos');
-- Inserir produto sem categoria (id_categoria_fk será NULL)
INSERT INTO produtos_detalhes (id_produto, descricao_produto) VALUES ('prod001', 'Smartphone Básico');
-- Inserir produto com categoria existente ('eletr')
INSERT INTO produtos_detalhes (id_produto, descricao_produto, id_categoria_fk) VALUES ('prod002', 'Smartwatch', 'eletr');
-- Tentativa de inserir produto com categoria inexistente ('brinc') - resultará em erro
INSERT INTO produtos_detalhes (id_produto, descricao_produto, id_categoria_fk) VALUES ('prod003', 'Brinquedo Infantil', 'brinc');
-- Tentativa de deletar uma categoria que está sendo referenciada por um produto - resultará em erro
DELETE FROM categorias_itens WHERE id_categoria = 'eletr';
-- Deletar um produto (operação permitida, pois não afeta a tabela principal)
DELETE FROM produtos_detalhes WHERE id_produto = 'prod001';
Regras para operações de dados em relações um-para-muitos:
- Inserção:
- Dados podem ser inseridos livremente na tabela principal.
- Dados na tabela secundária só podem ser inseridos se a chave estrangeira referenciar um valor existente na chave primária da tabela principal, ou se a chave estrangeira for
NULL(se permitido).
- Exclusão:
- Registros da tabela principal não podem ser excluídos se houver registros na tabela secundária que os referenciam (a menos que a restrição de chave estrangeira seja definida para
ON DELETE CASCADEouSET NULL). - Registros da tabela secundária podem ser excluídos livremente.
- Registros da tabela principal não podem ser excluídos se houver registros na tabela secundária que os referenciam (a menos que a restrição de chave estrangeira seja definida para
2. Consultas Envolvendo Múltiplas Tabelas (JOINs)
2.1. Conexão Cruzada (CROSS JOIN)
O CROSS JOIN retorna o produto cartesiano das duas tabelas, combinando cada linha da primeira tabela com cada linha da segunda. Raramente utilizado diretamente, geralmente é um resultado acidental de uma cláusula JOIN sem condição.
-- Exemplo de CROSS JOIN
SELECT * FROM categorias_itens, produtos_detalhes;
2.2. Conexão Interna (INNER JOIN)
O INNER JOIN retorna apenas as linhas que possuem correspondência em ambas as tabelas, com base na condição especificada na cláusula ON ou WHERE.
-- Sintaxe implícita (usando WHERE para a condição de junção)
-- Encontrar quais categorias têm produtos e listar os detalhes
SELECT
c.nome_categoria,
p.descricao_produto,
p.preco
FROM
categorias_itens c,
produtos_detalhes p
WHERE
c.id_categoria = p.id_categoria_fk;
-- Sintaxe explícita (usando INNER JOIN e ON)
SELECT
c.nome_categoria,
p.descricao_produto,
p.preco
FROM
categorias_itens c
INNER JOIN
produtos_detalhes p ON c.id_categoria = p.id_categoria_fk;
2.3. Conexão Externa (OUTER JOIN)
As junções externas retornam todas as linhas de uma das tabelas (esquerda ou direita) e as linhas correspondentes da outra tabela. Se não houver correspondência, os valores da tabela sem correspondência serão NULL.
LEFT OUTER JOIN (ou LEFT JOIN)
Retorna todas as linhas da tabela da esquerda e as linhas correspondentes da tabela da direita. Se não houver correspondência na tabela da direita, os resultados para essa tabela serão NULL.
-- Listar todas as categorias e seus produtos, incluindo categorias sem produtos
SELECT
c.nome_categoria,
p.descricao_produto,
p.preco
FROM
categorias_itens c
LEFT JOIN
produtos_detalhes p ON c.id_categoria = p.id_categoria_fk;
RIGHT OUTER JOIN (ou RIGHT JOIN)
Retorna todas as linhas da tabela da direita e as linhas correspondentes da tabela da esquerda. Se não houver correspondência na tabela da esquerda, os resultados para essa tabela serão NULL.
-- Listar todos os produtos e suas categorias, incluindo produtos sem categoria
SELECT
c.nome_categoria,
p.descricao_produto,
p.preco
FROM
categorias_itens c
RIGHT JOIN
produtos_detalhes p ON c.id_categoria = p.id_categoria_fk;
Exemplos práticos de junções:
-- 1. Consultar categorias que possuem produtos com preço acima de 200.00
-- Usando INNER JOIN explícito
SELECT DISTINCT c.nome_categoria
FROM categorias_itens c
INNER JOIN produtos_detalhes p ON c.id_categoria = p.id_categoria_fk
WHERE p.preco > 200.00;
-- 2. Contar o número de produtos por categoria, incluindo categorias sem produtos
INSERT INTO categorias_itens (id_categoria, nome_categoria) VALUES ('livros', 'Livros'); -- Adiciona uma categoria sem produtos
SELECT
c.nome_categoria,
COUNT(p.id_produto) AS total_produtos
FROM
categorias_itens c
LEFT JOIN
produtos_detalhes p ON c.id_categoria = p.id_categoria_fk
GROUP BY c.nome_categoria;
3. Subconsultas (Subqueries)
Uma subconsulta é uma consulta SELECT aninhada dentro de outra instrução SQL (SELECT, INSERT, UPDATE, DELETE). Elas podem retornar um único valor, uma lista de valores ou uma tabela.
Subconsulta como um Valor Único
Usada geralmente na cláusula WHERE para comparar com um único valor.
-- Buscar produtos da categoria 'Eletrônicos'
-- Abordagem com JOIN:
SELECT
p.descricao_produto,
p.preco
FROM
produtos_detalhes p, categorias_itens c
WHERE
p.id_categoria_fk = c.id_categoria AND c.nome_categoria = 'Eletrônicos';
-- Abordagem com subconsulta (retornando um único id_categoria):
SELECT
p.descricao_produto,
p.preco
FROM
produtos_detalhes p
WHERE
p.id_categoria_fk = (SELECT id_categoria FROM categorias_itens WHERE nome_categoria = 'Eletrônicos');
-- Encontrar o produto com o preço mais elevado
SELECT
p.descricao_produto,
p.preco
FROM
produtos_detalhes p
WHERE
p.preco = (SELECT MAX(preco) FROM produtos_detalhes);
Subconsulta como uma Tabela
Uma subconsulta pode ser tratada como uma tabela temporária na cláusula FROM.
-- Selecionar todos os produtos de categorias específicas (ex: Eletrônicos)
SELECT
pd.descricao_produto,
pd.preco,
ci.nome_categoria
FROM
produtos_detalhes pd,
(SELECT id_categoria, nome_categoria FROM categorias_itens WHERE nome_categoria = 'Eletrônicos') ci
WHERE
pd.id_categoria_fk = ci.id_categoria;
Subconsulta como Múltiplos Valores
Quando uma subconsulta retorna uma lista de valores, é comum usar operadores como IN ou EXISTS.
-- Recuperar detalhes de produtos das categorias 'Eletrônicos' ou 'Vestuário'
SELECT
p.descricao_produto,
p.preco
FROM
produtos_detalhes p
WHERE
p.id_categoria_fk IN (SELECT id_categoria FROM categorias_itens WHERE nome_categoria IN ('Eletrônicos', 'Vestuário'));
-- Usando subconsulta como tabela na cláusula FROM para a mesma consulta
SELECT
pd.descricao_produto,
pd.preco,
ci.nome_categoria
FROM
produtos_detalhes pd,
(SELECT id_categoria, nome_categoria FROM categorias_itens WHERE nome_categoria IN ('Eletrônicos', 'Vestuário')) ci
WHERE
pd.id_categoria_fk = ci.id_categoria;