# SQL - Fundamentos (DDL e DML)
## 1. Introdução ao SQL
### 1.1 O que é SQL?
**SQL (Structured Query Language)** é a linguagem padrão para gerenciar e manipular bancos de dados relacionais. Criada nos anos 1970 pela IBM, foi padronizada pela ANSI/ISO.
**Características:**
- Linguagem **declarativa** (especifica O QUE fazer, não COMO)
- **Não-procedural** (diferente de linguagens como C, Java)
- Baseada em **álgebra relacional** e **cálculo relacional**
- **Portável** entre diferentes SGBDs (com variações de dialeto)
### 1.2 Categorias de Comandos SQL
| Categoria | Descrição | Comandos Principais |
|-----------|-----------|---------------------|
| **DDL** | Data Definition Language | CREATE, ALTER, DROP, TRUNCATE |
| **DML** | Data Manipulation Language | SELECT, INSERT, UPDATE, DELETE |
| **DCL** | Data Control Language | GRANT, REVOKE |
| **TCL** | Transaction Control Language | COMMIT, ROLLBACK, SAVEPOINT |
| **DQL** | Data Query Language | SELECT (às vezes separado do DML) |
### 1.3 Padrões SQL
- **SQL-86** (SQL-87): Primeira versão ANSI
- **SQL-92** (SQL2): Grande expansão
- **SQL:1999** (SQL3): Triggers, recursão
- **SQL:2003**: XML, sequences
- **SQL:2011**: Temporal data
- **SQL:2016**: JSON, padrões de busca
---
## 2. DDL - Data Definition Language
DDL define e modifica a **estrutura** dos objetos do banco de dados (esquema).
### 2.1 CREATE - Criando Objetos
#### CREATE DATABASE
```sql
-- SQL Server
CREATE DATABASE Empresa;
-- PostgreSQL com configurações
CREATE DATABASE Empresa
WITH OWNER = postgres
ENCODING = 'UTF8'
LC_COLLATE = 'pt_BR.UTF-8'
LC_CTYPE = 'pt_BR.UTF-8';
-- MySQL
CREATE DATABASE Empresa
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
```
#### CREATE SCHEMA
```sql
-- Organizar objetos em esquemas (namespaces)
CREATE SCHEMA vendas;
CREATE SCHEMA rh;
-- Usar objetos do schema
SELECT * FROM vendas.Cliente;
SELECT * FROM rh.Funcionario;
```
#### CREATE TABLE - Estrutura Básica
```sql
CREATE TABLE Cliente (
ClienteID INT PRIMARY KEY,
Nome VARCHAR(100) NOT NULL,
Email VARCHAR(100) UNIQUE,
DataCadastro DATE DEFAULT CURRENT_DATE,
Ativo BOOLEAN DEFAULT TRUE
);
```
#### CREATE TABLE - Tipos de Dados Comuns
**Numéricos:**
```sql
CREATE TABLE Exemplo (
-- Inteiros
TinyInt TINYINT, -- 1 byte (-128 a 127)
SmallInt SMALLINT, -- 2 bytes (-32768 a 32767)
Int INT, -- 4 bytes
BigInt BIGINT, -- 8 bytes
-- Decimais
Decimal DECIMAL(10,2), -- Precisão exata (10 dígitos, 2 decimais)
Numeric NUMERIC(15,4), -- Sinônimo de DECIMAL
Float FLOAT, -- Ponto flutuante
Real REAL -- Ponto flutuante (menor precisão)
);
```
**Caracteres:**
```sql
CREATE TABLE Textos (
-- Tamanho fixo (preenche com espaços)
Sigla CHAR(2), -- Sempre 2 caracteres
-- Tamanho variável
Nome VARCHAR(100), -- Até 100 caracteres
Descricao TEXT, -- Texto longo (sem limite definido)
-- Unicode (SQL Server)
NomeUnicode NVARCHAR(100), -- Suporta caracteres especiais
-- Binários
Arquivo VARBINARY(MAX) -- Dados binários
);
```
**Data e Hora:**
```sql
CREATE TABLE Eventos (
DataEvento DATE, -- Apenas data (YYYY-MM-DD)
HoraInicio TIME, -- Apenas hora (HH:MM:SS)
DataHora DATETIME, -- Data + Hora
Timestamp TIMESTAMP, -- Timestamp com timezone
DataHora2 DATETIME2, -- SQL Server (maior precisão)
DataOffset DATETIMEOFFSET -- SQL Server (com timezone)
);
```
**Booleanos e Outros:**
```sql
CREATE TABLE Diversos (
Ativo BOOLEAN, -- TRUE/FALSE (PostgreSQL)
Bit BIT, -- 0/1 (SQL Server)
UUID UUID, -- Identificador único (PostgreSQL)
JSON JSON, -- Dados JSON (PostgreSQL, MySQL)
XML XML -- Dados XML
);
```
#### CREATE TABLE - Constraints (Restrições)
```sql
CREATE TABLE Funcionario (
FuncID INT,
Nome VARCHAR(100) NOT NULL, -- Não permite NULL
Email VARCHAR(100) UNIQUE, -- Valor único
Salario DECIMAL(10,2) CHECK (Salario > 0), -- Validação
DeptID INT,
DataAdmissao DATE DEFAULT CURRENT_DATE, -- Valor padrão
-- Chave primária
CONSTRAINT pk_funcionario PRIMARY KEY (FuncID),
-- Chave estrangeira
CONSTRAINT fk_departamento
FOREIGN KEY (DeptID)
REFERENCES Departamento(DeptID)
ON DELETE SET NULL
ON UPDATE CASCADE,
-- Check constraint
CONSTRAINT chk_email CHECK (Email LIKE '%@%')
);
```
#### CREATE TABLE - Chaves Compostas
```sql
CREATE TABLE ItemPedido (
PedidoID INT,
ProdutoID INT,
Quantidade INT NOT NULL,
PrecoUnitario DECIMAL(10,2) NOT NULL,
-- Chave primária composta
PRIMARY KEY (PedidoID, ProdutoID),
FOREIGN KEY (PedidoID) REFERENCES Pedido(PedidoID),
FOREIGN KEY (ProdutoID) REFERENCES Produto(ProdutoID)
);
```
#### CREATE INDEX
```sql
-- Índice simples
CREATE INDEX idx_cliente_nome ON Cliente(Nome);
-- Índice único
CREATE UNIQUE INDEX idx_cliente_email ON Cliente(Email);
-- Índice composto
CREATE INDEX idx_pedido_cliente_data
ON Pedido(ClienteID, DataPedido);
-- Índice com ordenação específica (SQL Server)
CREATE INDEX idx_funcionario_salario
ON Funcionario(Salario DESC);
-- Índice parcial (PostgreSQL)
CREATE INDEX idx_cliente_ativo
ON Cliente(Nome)
WHERE Ativo = TRUE;
```
#### CREATE VIEW
```sql
-- View simples
CREATE VIEW vw_ClientesAtivos AS
SELECT ClienteID, Nome, Email
FROM Cliente
WHERE Ativo = TRUE;
-- View com joins
CREATE VIEW vw_PedidosCompletos AS
SELECT
p.PedidoID,
p.DataPedido,
c.Nome AS NomeCliente,
c.Email,
p.ValorTotal
FROM Pedido p
INNER JOIN Cliente c ON p.ClienteID = c.ClienteID;
-- Usar a view
SELECT * FROM vw_ClientesAtivos;
```
#### CREATE SEQUENCE (PostgreSQL, Oracle)
```sql
-- PostgreSQL
CREATE SEQUENCE seq_cliente
START WITH 1
INCREMENT BY 1
MINVALUE 1
MAXVALUE 9999999
CACHE 20;
-- Usar a sequence
INSERT INTO Cliente (ClienteID, Nome)
VALUES (nextval('seq_cliente'), 'João Silva');
```
### 2.2 ALTER - Modificando Objetos
#### ALTER TABLE - Adicionar Coluna
```sql
-- Adicionar coluna simples
ALTER TABLE Cliente
ADD Telefone VARCHAR(15);
-- Adicionar com default
ALTER TABLE Cliente
ADD DataNascimento DATE DEFAULT '1900-01-01';
-- Adicionar múltiplas colunas (alguns SGBDs)
ALTER TABLE Cliente
ADD (
CPF CHAR(11),
RG VARCHAR(20)
);
```
#### ALTER TABLE - Modificar Coluna
```sql
-- SQL Server
ALTER TABLE Cliente
ALTER COLUMN Telefone VARCHAR(20);
-- PostgreSQL
ALTER TABLE Cliente
ALTER COLUMN Telefone TYPE VARCHAR(20);
-- MySQL
ALTER TABLE Cliente
MODIFY COLUMN Telefone VARCHAR(20);
-- Adicionar NOT NULL
ALTER TABLE Cliente
ALTER COLUMN Nome SET NOT NULL; -- PostgreSQL
ALTER TABLE Cliente
ALTER COLUMN Nome VARCHAR(100) NOT NULL; -- SQL Server
```
#### ALTER TABLE - Remover Coluna
```sql
-- Remover coluna
ALTER TABLE Cliente
DROP COLUMN Telefone;
-- Remover se existir (PostgreSQL, MySQL)
ALTER TABLE Cliente
DROP COLUMN IF EXISTS Telefone;
```
#### ALTER TABLE - Constraints
```sql
-- Adicionar chave primária
ALTER TABLE Cliente
ADD CONSTRAINT pk_cliente PRIMARY KEY (ClienteID);
-- Adicionar chave estrangeira
ALTER TABLE Pedido
ADD CONSTRAINT fk_cliente
FOREIGN KEY (ClienteID)
REFERENCES Cliente(ClienteID);
-- Adicionar unique
ALTER TABLE Cliente
ADD CONSTRAINT uq_email UNIQUE (Email);
-- Adicionar check
ALTER TABLE Produto
ADD CONSTRAINT chk_preco CHECK (Preco > 0);
-- Remover constraint
ALTER TABLE Cliente
DROP CONSTRAINT uq_email;
-- SQL Server - desabilitar/habilitar constraint
ALTER TABLE Pedido NOCHECK CONSTRAINT fk_cliente; -- Desabilitar
ALTER TABLE Pedido CHECK CONSTRAINT fk_cliente; -- Habilitar
```
#### ALTER TABLE - Renomear
```sql
-- Renomear tabela (SQL Server)
EXEC sp_rename 'Cliente', 'Customer';
-- Renomear tabela (PostgreSQL, MySQL)
ALTER TABLE Cliente RENAME TO Customer;
-- Renomear coluna (SQL Server)
EXEC sp_rename 'Cliente.Telefone', 'Phone', 'COLUMN';
-- Renomear coluna (PostgreSQL)
ALTER TABLE Cliente RENAME COLUMN Telefone TO Phone;
-- Renomear coluna (MySQL)
ALTER TABLE Cliente CHANGE Telefone Phone VARCHAR(20);
```
### 2.3 DROP - Removendo Objetos
```sql
-- Remover tabela
DROP TABLE Cliente;
-- Remover se existir
DROP TABLE IF EXISTS Cliente;
-- Remover com CASCADE (remove dependências)
DROP TABLE Cliente CASCADE; -- PostgreSQL
-- Remove também FKs que referenciam a tabela
-- Remover índice
DROP INDEX idx_cliente_nome;
-- Remover view
DROP VIEW vw_ClientesAtivos;
-- Remover database
DROP DATABASE Empresa;
-- Remover schema
DROP SCHEMA vendas CASCADE;
```
### 2.4 TRUNCATE - Limpar Dados
```sql
-- Remove TODOS os dados da tabela (mais rápido que DELETE)
TRUNCATE TABLE Cliente;
-- Diferenças entre TRUNCATE e DELETE:
-- TRUNCATE:
-- - Não pode ter WHERE
-- - Não gera log individual de cada linha (mais rápido)
-- - Reseta AUTO_INCREMENT/IDENTITY
-- - Não dispara triggers (geralmente)
-- - Não pode ser usado se há FK referenciando
-- DELETE:
-- - Pode ter WHERE (deletar seletivamente)
-- - Gera log de cada linha
-- - Não reseta contadores
-- - Dispara triggers
-- - Pode ser usado com FK (se configurado CASCADE)
```
---
## 3. DML - Data Manipulation Language
DML manipula os **dados** dentro das tabelas.
### 3.1 INSERT - Inserindo Dados
#### INSERT Básico
```sql
-- Inserir especificando colunas (recomendado)
INSERT INTO Cliente (ClienteID, Nome, Email)
VALUES (1, 'João Silva', 'joao@email.com');
-- Inserir em todas as colunas (ordem da tabela)
INSERT INTO Cliente
VALUES (2, 'Maria Santos', 'maria@email.com', '2024-01-15', TRUE);
-- Inserir com valores default
INSERT INTO Cliente (ClienteID, Nome)
VALUES (3, 'Pedro Costa'); -- Email=NULL, DataCadastro=CURRENT_DATE
```
#### INSERT Múltiplas Linhas
```sql
-- Inserir várias linhas de uma vez
INSERT INTO Cliente (ClienteID, Nome, Email) VALUES
(4, 'Ana Lima', 'ana@email.com'),
(5, 'Carlos Dias', 'carlos@email.com'),
(6, 'Beatriz Rocha', 'beatriz@email.com');
```
#### INSERT com SELECT
```sql
-- Copiar dados de outra tabela
INSERT INTO ClienteBackup
SELECT * FROM Cliente
WHERE Ativo = TRUE;
-- Inserir resultado de query complexa
INSERT INTO Resumo (Ano, TotalVendas)
SELECT
YEAR(DataPedido) AS Ano,
SUM(ValorTotal) AS TotalVendas
FROM Pedido
GROUP BY YEAR(DataPedido);
```
#### INSERT com AUTO_INCREMENT/IDENTITY
```sql
-- SQL Server (IDENTITY)
CREATE TABLE Cliente (
ClienteID INT IDENTITY(1,1) PRIMARY KEY,
Nome VARCHAR(100)
);
INSERT INTO Cliente (Nome) VALUES ('João'); -- ClienteID gerado automaticamente
-- MySQL (AUTO_INCREMENT)
CREATE TABLE Cliente (
ClienteID INT AUTO_INCREMENT PRIMARY KEY,
Nome VARCHAR(100)
);
INSERT INTO Cliente (Nome) VALUES ('Maria');
-- PostgreSQL (SERIAL ou IDENTITY)
CREATE TABLE Cliente (
ClienteID SERIAL PRIMARY KEY, -- Cria sequence automaticamente
Nome VARCHAR(100)
);
INSERT INTO Cliente (Nome) VALUES ('Pedro');
-- Ou com IDENTITY (PostgreSQL 10+)
CREATE TABLE Cliente (
ClienteID INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
Nome VARCHAR(100)
);
```
#### INSERT RETURNING (PostgreSQL)
```sql
-- Retornar valores inseridos
INSERT INTO Cliente (Nome, Email)
VALUES ('Fernanda', 'fernanda@email.com')
RETURNING ClienteID, DataCadastro;
-- Útil para pegar ID gerado automaticamente
```
### 3.2 UPDATE - Atualizando Dados
#### UPDATE Básico
```sql
-- Atualizar uma coluna
UPDATE Cliente
SET Email = 'novo@email.com'
WHERE ClienteID = 1;
-- Atualizar múltiplas colunas
UPDATE Cliente
SET
Nome = 'João Pedro Silva',
Email = 'joao.pedro@email.com',
Ativo = TRUE
WHERE ClienteID = 1;
```
#### UPDATE com Cálculos
```sql
-- Aumentar salário em 10%
UPDATE Funcionario
SET Salario = Salario * 1.10
WHERE DeptID = 5;
-- Concatenar strings
UPDATE Produto
SET Descricao = Descricao || ' - PROMOÇÃO' -- PostgreSQL
WHERE Preco < 50;
UPDATE Produto
SET Descricao = CONCAT(Descricao, ' - PROMOÇÃO') -- MySQL, SQL Server
WHERE Preco < 50;
```
#### UPDATE com Subconsulta
```sql
-- Atualizar baseado em outra tabela
UPDATE Funcionario
SET Salario = (
SELECT AVG(Salario)
FROM Funcionario
WHERE DeptID = Funcionario.DeptID
)
WHERE FuncID = 10;
-- Atualizar múltiplos registros
UPDATE Produto
SET CategoriaID = (
SELECT CategoriaID
FROM Categoria
WHERE Nome = 'Eletrônicos'
)
WHERE Tipo = 'Eletrônico';
```
#### UPDATE com JOIN (SQL Server, MySQL)
```sql
-- SQL Server
UPDATE p
SET p.Preco = p.Preco * 1.05
FROM Produto p
INNER JOIN Categoria c ON p.CategoriaID = c.CategoriaID
WHERE c.Nome = 'Alimentos';
-- MySQL
UPDATE Produto p
INNER JOIN Categoria c ON p.CategoriaID = c.CategoriaID
SET p.Preco = p.Preco * 1.05
WHERE c.Nome = 'Alimentos';
```
#### ⚠️ UPDATE sem WHERE
```sql
-- CUIDADO! Atualiza TODAS as linhas
UPDATE Cliente
SET Ativo = FALSE; -- Desativa TODOS os clientes!
-- Sempre use WHERE para atualizar seletivamente
UPDATE Cliente
SET Ativo = FALSE
WHERE DataUltimaCompra < '2020-01-01';
```
### 3.3 DELETE - Removendo Dados
#### DELETE Básico
```sql
-- Deletar registro específico
DELETE FROM Cliente
WHERE ClienteID = 1;
-- Deletar com múltiplas condições
DELETE FROM Pedido
WHERE Status = 'Cancelado'
AND DataPedido < '2020-01-01';
```
#### DELETE com Subconsulta
```sql
-- Deletar baseado em outra tabela
DELETE FROM Cliente
WHERE ClienteID IN (
SELECT ClienteID
FROM Pedido
WHERE Status = 'Fraude'
);
-- Deletar clientes sem pedidos
DELETE FROM Cliente
WHERE NOT EXISTS (
SELECT 1
FROM Pedido
WHERE Pedido.ClienteID = Cliente.ClienteID
);
```
#### DELETE com JOIN (SQL Server, MySQL)
```sql
-- SQL Server
DELETE c
FROM Cliente c
INNER JOIN Pedido p ON c.ClienteID = p.ClienteID
WHERE p.Status = 'Fraude';
-- MySQL
DELETE c FROM Cliente c
INNER JOIN Pedido p ON c.ClienteID = p.ClienteID
WHERE p.Status = 'Fraude';
```
#### ⚠️ DELETE sem WHERE
```sql
-- CUIDADO! Remove TODAS as linhas
DELETE FROM Cliente; -- Apaga TODOS os clientes!
-- Melhor usar TRUNCATE se for limpar tudo
TRUNCATE TABLE Cliente; -- Mais rápido
```
### 3.4 SELECT - Consultando Dados (Básico)
#### SELECT Simples
```sql
-- Todas as colunas
SELECT * FROM Cliente;
-- Colunas específicas
SELECT Nome, Email FROM Cliente;
-- Com alias (apelido)
SELECT
Nome AS NomeCliente,
Email AS EmailContato
FROM Cliente;
-- Alias sem AS
SELECT
Nome NomeCliente,
Email EmailContato
FROM Cliente;
```
#### WHERE - Filtragem
```sql
-- Comparações básicas
SELECT * FROM Produto WHERE Preco > 100;
SELECT * FROM Cliente WHERE Ativo = TRUE;
SELECT * FROM Pedido WHERE DataPedido = '2024-01-15';
-- Operadores lógicos
SELECT * FROM Produto
WHERE Preco > 50 AND CategoriaID = 1;
SELECT * FROM Cliente
WHERE Cidade = 'São Paulo' OR Cidade = 'Rio de Janeiro';
SELECT * FROM Produto
WHERE NOT (Preco < 10);
-- BETWEEN
SELECT * FROM Produto
WHERE Preco BETWEEN 50 AND 150;
-- IN
SELECT * FROM Cliente
WHERE Cidade IN ('São Paulo', 'Rio de Janeiro', 'Belo Horizonte');
-- LIKE (padrões)
SELECT * FROM Cliente WHERE Nome LIKE 'João%'; -- Começa com João
SELECT * FROM Cliente WHERE Nome LIKE '%Silva'; -- Termina com Silva
SELECT * FROM Cliente WHERE Nome LIKE '%Santos%'; -- Contém Santos
SELECT * FROM Cliente WHERE Nome LIKE 'Mar_a'; -- _ = um caractere
-- IS NULL / IS NOT NULL
SELECT * FROM Cliente WHERE Email IS NULL;
SELECT * FROM Cliente WHERE Email IS NOT NULL;
```
#### ORDER BY - Ordenação
```sql
-- Ordem crescente (padrão)
SELECT * FROM Produto ORDER BY Preco;
SELECT * FROM Produto ORDER BY Preco ASC;
-- Ordem decrescente
SELECT * FROM Produto ORDER BY Preco DESC;
-- Múltiplas colunas
SELECT * FROM Cliente
ORDER BY Cidade ASC, Nome ASC;
-- Por posição da coluna (não recomendado)
SELECT Nome, Email FROM Cliente ORDER BY 1; -- Ordena pela 1ª coluna (Nome)
```
#### LIMIT / TOP - Limitar Resultados
```sql
-- MySQL, PostgreSQL (LIMIT)
SELECT * FROM Cliente LIMIT 10;
-- SQL Server (TOP)
SELECT TOP 10 * FROM Cliente;
-- PostgreSQL (OFFSET - paginação)
SELECT * FROM Cliente
ORDER BY ClienteID
LIMIT 10 OFFSET 20; -- Pula 20, retorna 10
-- SQL Server (OFFSET FETCH)
SELECT * FROM Cliente
ORDER BY ClienteID
OFFSET 20 ROWS
FETCH NEXT 10 ROWS ONLY;
```
#### DISTINCT - Valores Únicos
```sql
-- Remover duplicatas
SELECT DISTINCT Cidade FROM Cliente;
-- Combinação única de colunas
SELECT DISTINCT Cidade, Estado FROM Cliente;
-- Contar valores únicos
SELECT COUNT(DISTINCT Cidade) AS TotalCidades
FROM Cliente;
```
---
## 4. Funções SQL Básicas
### 4.1 Funções de Agregação
```sql
-- COUNT - contar registros
SELECT COUNT(*) FROM Cliente;
SELECT COUNT(Email) FROM Cliente; -- Conta não-nulos
SELECT COUNT(DISTINCT Cidade) FROM Cliente;
-- SUM - soma
SELECT SUM(ValorTotal) FROM Pedido;
-- AVG - média
SELECT AVG(Preco) FROM Produto;
-- MAX / MIN - maior/menor
SELECT MAX(Preco) FROM Produto;
SELECT MIN(DataPedido) FROM Pedido;
-- Múltiplas agregações
SELECT
COUNT(*) AS TotalProdutos,
AVG(Preco) AS PrecoMedio,
MAX(Preco) AS PrecoMaximo,
MIN(Preco) AS PrecoMinimo
FROM Produto;
```
### 4.2 GROUP BY - Agrupamento
```sql
-- Agrupar por categoria
SELECT
CategoriaID,
COUNT(*) AS QtdeProdutos,
AVG(Preco) AS PrecoMedio
FROM Produto
GROUP BY CategoriaID;
-- Múltiplas colunas de agrupamento
SELECT
Cidade,
Estado,
COUNT(*) AS QtdeClientes
FROM Cliente
GROUP BY Cidade, Estado;
-- HAVING - filtrar grupos (como WHERE, mas para grupos)
SELECT
CategoriaID,
COUNT(*) AS QtdeProdutos
FROM Produto
GROUP BY CategoriaID
HAVING COUNT(*) > 5; -- Apenas categorias com mais de 5 produtos
-- WHERE + HAVING
SELECT
CategoriaID,
AVG(Preco) AS PrecoMedio
FROM Produto
WHERE Ativo = TRUE -- Filtra antes de agrupar
GROUP BY CategoriaID
HAVING AVG(Preco) > 100; -- Filtra após agrupar
```
### 4.3 Funções de String
```sql
-- UPPER / LOWER
SELECT UPPER(Nome) FROM Cliente;
SELECT LOWER(Email) FROM Cliente;
-- CONCAT - concatenar
SELECT CONCAT(Nome, ' - ', Email) FROM Cliente; -- SQL Server, MySQL
SELECT Nome || ' - ' || Email FROM Cliente; -- PostgreSQL, Oracle
-- SUBSTRING - extrair parte
SELECT SUBSTRING(Nome, 1, 5) FROM Cliente; -- Primeiros 5 caracteres
SELECT SUBSTR(Nome, 1, 5) FROM Cliente; -- Alternativa
-- LENGTH / LEN - tamanho
SELECT LENGTH(Nome) FROM Cliente; -- PostgreSQL, MySQL
SELECT LEN(Nome) FROM Cliente; -- SQL Server
-- TRIM - remover espaços
SELECT TRIM(Nome) FROM Cliente; -- Remove início e fim
SELECT LTRIM(Nome) FROM Cliente; -- Remove à esquerda
SELECT RTRIM(Nome) FROM Cliente; -- Remove à direita
-- REPLACE - substituir
SELECT REPLACE(Descricao, 'antigo', 'novo') FROM Produto;
```
### 4.4 Funções de Data
```sql
-- Data/hora atual
SELECT CURRENT_DATE; -- PostgreSQL, MySQL
SELECT GETDATE(); -- SQL Server
SELECT NOW(); -- MySQL
-- Extrair partes da data
SELECT YEAR(DataPedido) FROM Pedido; -- Ano
SELECT MONTH(DataPedido) FROM Pedido; -- Mês
SELECT DAY(DataPedido) FROM Pedido; -- Dia
-- PostgreSQL (EXTRACT)
SELECT EXTRACT(YEAR FROM DataPedido) FROM Pedido;
-- Calcular diferença
SELECT DATEDIFF(day, DataInicio, DataFim) FROM Projeto; -- SQL Server
SELECT AGE(DataFim, DataInicio) FROM Projeto; -- PostgreSQL
-- Adicionar intervalo
SELECT DataPedido + INTERVAL '30 days' FROM Pedido; -- PostgreSQL
SELECT DATEADD(day, 30, DataPedido) FROM Pedido; -- SQL Server
```
### 4.5 Funções de Conversão
```sql
-- CAST - converter tipo
SELECT CAST(Preco AS INT) FROM Produto;
SELECT CAST('2024-01-15' AS DATE);
-- CONVERT (SQL Server)
SELECT CONVERT(VARCHAR, DataPedido, 103) FROM Pedido; -- dd/mm/yyyy
-- TO_CHAR (PostgreSQL, Oracle)
SELECT TO_CHAR(DataPedido, 'DD/MM/YYYY') FROM Pedido;
-- COALESCE - primeiro não-nulo
SELECT COALESCE(Email, Telefone, 'Sem contato') FROM Cliente;
-- NULLIF - retorna NULL se valores iguais
SELECT NULLIF(Desconto, 0) FROM Produto;
```
---
## 5. Boas Práticas SQL
### 5.1 Nomenclatura
```sql
-- ✅ BOM: Nomes descritivos
CREATE TABLE Cliente (
ClienteID INT PRIMARY KEY,
NomeCompleto VARCHAR(100),
DataCadastro DATE
);
-- ❌ RUIM: Nomes genéricos
CREATE TABLE Table1 (
ID INT,
Col1 VARCHAR(100),
Col2 DATE
);
-- Convenções comuns:
-- - PascalCase para tabelas: Cliente, ItemPedido
-- - camelCase ou snake_case para colunas: nomeCompleto ou nome_completo
-- - Prefixos: pk_ para PKs, fk_ para FKs, idx_ para índices
```
### 5.2 Formatação
```sql
-- ✅ BOM: Legível
SELECT
c.ClienteID,
c.Nome,
COUNT(p.PedidoID) AS TotalPedidos
FROM Cliente c
LEFT JOIN Pedido p ON c.ClienteID = p.ClienteID
WHERE c.Ativo = TRUE
GROUP BY c.ClienteID, c.Nome
HAVING COUNT(p.PedidoID) > 5
ORDER BY TotalPedidos DESC;
-- ❌ RUIM: Tudo em uma linha
SELECT c.ClienteID,c.Nome,COUNT(p.PedidoID) FROM Cliente c LEFT JOIN Pedido p ON c.ClienteID=p.ClienteID WHERE c.Ativo=TRUE GROUP BY c.ClienteID,c.Nome HAVING COUNT(p.PedidoID)>5;
```
### 5.3 Performance
```sql
-- ✅ BOM: Especificar colunas
SELECT ClienteID, Nome FROM Cliente;
-- ❌ EVITE: SELECT * em produção
SELECT * FROM Cliente; -- Retorna colunas desnecessárias
-- ✅ BOM: Usar índices
CREATE INDEX idx_cliente_email ON Cliente(Email);
SELECT * FROM Cliente WHERE Email = 'teste@email.com';
-- ❌ RUIM: Função na coluna indexada (não usa índice)
SELECT * FROM Cliente WHERE UPPER(Email) = 'TESTE@EMAIL.COM';
-- ✅ MELHOR: Função no valor
SELECT * FROM Cliente WHERE Email = LOWER('TESTE@EMAIL.COM');
```
### 5.4 Segurança
```sql
-- ❌ NUNCA: Concatenar SQL com input do usuário (SQL Injection)
-- String query = "SELECT * FROM Cliente WHERE Email = '" + userInput + "'";
-- ✅ SEMPRE: Usar parâmetros preparados (prepared statements)
-- PreparedStatement ps = conn.prepareStatement("SELECT * FROM Cliente WHERE Email = ?");
-- ps.setString(1, userInput);
```
---
## Resumo do Módulo
**DDL** define estruturas (CREATE, ALTER, DROP, TRUNCATE), enquanto **DML** manipula dados (INSERT, UPDATE, DELETE, SELECT). SQL oferece tipos de dados variados, constraints para integridade, e funções poderosas para agregação, manipulação de strings e datas. Boas práticas incluem nomenclatura clara, formatação legível e otimização de performance.
---
# 📝 QUESTÕES DE CONCURSO
## Questão 1 (CESPE - Banco do Brasil - 2021)
O comando TRUNCATE TABLE remove todos os registros de uma tabela e permite a especificação de uma cláusula WHERE para remoção seletiva de dados.
<details>
<summary>👉 Ver Resposta</summary>
**ERRADO**
**Explicação**: TRUNCATE TABLE **NÃO permite** cláusula WHERE. Ele remove **todos** os registros da tabela, sem possibilidade de filtragem.
**Diferenças TRUNCATE vs DELETE:**
```sql
-- TRUNCATE - remove TUDO, sem WHERE
TRUNCATE TABLE Cliente;
-- DELETE - pode usar WHERE para remoção seletiva
DELETE FROM Cliente WHERE Ativo = FALSE;
```
**Características do TRUNCATE:**
- ❌ Não aceita WHERE
- ✅ Mais rápido (não gera log de cada linha)
- ✅ Reseta AUTO_INCREMENT/IDENTITY
- ❌ Não dispara triggers (geralmente)
- ❌ Não funciona com FK ativa
Se você precisa remover dados seletivamente, use **DELETE**.
</details>
---
## Questão 2 (FCC - TRT - 2022)
Qual comando SQL é utilizado para modificar a estrutura de uma tabela existente, como adicionar ou remover colunas?
a) UPDATE
b) MODIFY
c) ALTER
d) CHANGE
e) RESTRUCTURE
<details>
<summary>👉 Ver Resposta</summary>
**ALTERNATIVA C (ALTER)**
**Explicação**: O comando **ALTER TABLE** é usado para modificar a estrutura (esquema) de tabelas existentes.
**Exemplos de uso do ALTER:**
```sql
-- Adicionar coluna
ALTER TABLE Cliente ADD Telefone VARCHAR(15);
-- Remover coluna
ALTER TABLE Cliente DROP COLUMN Telefone;
-- Modificar tipo de dado
ALTER TABLE Cliente ALTER COLUMN Email VARCHAR(150);
-- Adicionar constraint
ALTER TABLE Cliente ADD CONSTRAINT pk_cliente PRIMARY KEY (ClienteID);
-- Renomear coluna (PostgreSQL)
ALTER TABLE Cliente RENAME COLUMN Nome TO NomeCompleto;
```
**Por que as outras estão erradas:**
- **UPDATE**: DML para atualizar **dados**, não estrutura
- **MODIFY**: Cláusula usada dentro de ALTER em alguns SGBDs (MySQL), não comando independente
- **CHANGE**: Similar ao MODIFY, usado no MySQL dentro de ALTER
- **RESTRUCTURE**: Não é comando SQL padrão
</details>
---
## Questão 3 (CESGRANRIO - Petrobras - 2023)
Considere os seguintes comandos SQL:
```sql
DELETE FROM Funcionario WHERE DeptID = 5;
TRUNCATE TABLE Funcionario;
```
Sobre esses comandos, é correto afirmar que:
a) Ambos podem usar cláusula WHERE para filtragem
b) TRUNCATE é mais lento que DELETE pois gera mais logs
c) DELETE pode ser revertido com ROLLBACK, TRUNCATE não
d) Ambos resetam contadores AUTO_INCREMENT
e) Apenas DELETE pode disparar triggers
<details>
<summary>👉 Ver Resposta</summary>
**ALTERNATIVA E**
**Explicação**: Em geral, **DELETE dispara triggers**, enquanto **TRUNCATE não** (comportamento pode variar por SGBD, mas é a regra geral).
**Comparação completa:**
| Característica | DELETE | TRUNCATE |
|----------------|--------|----------|
| Cláusula WHERE | ✅ Sim | ❌ Não |
| Velocidade | Mais lento | Mais rápido |
| Log individual | ✅ Sim (cada linha) | ❌ Não (operação única) |
| ROLLBACK | ✅ Sim (dentro de transação) | ⚠️ Depende do SGBD |
| Reset AUTO_INCREMENT | ❌ Não | ✅ Sim |
| Triggers | ✅ Dispara | ❌ Geralmente não |
| Com FK ativa | ⚠️ Depende (CASCADE) | ❌ Não funciona |
**Análise das alternativas:**
**A - ERRADO**: Só DELETE aceita WHERE
**B - ERRADO**: TRUNCATE é **mais rápido** (menos logs)
**C - ERRADO**: Ambos podem ser revertidos com ROLLBACK se dentro de transação (comportamento varia por SGBD)
**D - ERRADO**: Apenas TRUNCATE reseta contadores
**E - CORRETO**: DELETE dispara triggers, TRUNCATE geralmente não
```sql
-- Exemplo com trigger
CREATE TRIGGER trg_audit
AFTER DELETE ON Funcionario
FOR EACH ROW
BEGIN
-- Este trigger será disparado por DELETE
INSERT INTO Auditoria VALUES (OLD.FuncID, NOW());
END;
DELETE FROM Funcionario WHERE FuncID = 1; -- Trigger disparado ✅
TRUNCATE TABLE Funcionario; -- Trigger NÃO disparado ❌
```
</details>
---
## Questão 4 (CESPE - CAIXA - 2021)
Em SQL, qual constraint garante que não haverá valores duplicados em uma coluna ou conjunto de colunas?
a) NOT NULL
b) CHECK
c) UNIQUE
d) PRIMARY KEY
e) FOREIGN KEY
<details>
<summary>👉 Ver Resposta</summary>
**ALTERNATIVA C (UNIQUE)**
**Explicação**: A constraint **UNIQUE** garante que todos os valores em uma coluna (ou combinação de colunas) sejam únicos.
**Detalhamento:**
```sql
-- UNIQUE em uma coluna
CREATE TABLE Cliente (
ClienteID INT PRIMARY KEY,
Email VARCHAR(100) UNIQUE, -- Não permite emails duplicados
Nome VARCHAR(100)
);
-- UNIQUE em múltiplas colunas (combinação única)
CREATE TABLE Matricula (
AlunoID INT,
DisciplinaID INT,
Semestre VARCHAR(10),
UNIQUE (AlunoID, DisciplinaID, Semestre)
-- Aluno não pode se matricular 2x na mesma disciplina no mesmo semestre
);
-- Adicionar UNIQUE posteriormente
ALTER TABLE Cliente
ADD CONSTRAINT uq_cpf UNIQUE (CPF);
```
**Diferenças importantes:**
**UNIQUE vs PRIMARY KEY:**
- **UNIQUE**: Permite **NULL** (exceto se NOT NULL também for especificado)
- **PRIMARY KEY**: **Nunca** permite NULL (= UNIQUE + NOT NULL)
- Tabela pode ter **múltiplos UNIQUE**, mas apenas **uma PRIMARY KEY**
```sql
CREATE TABLE Exemplo (
ID INT PRIMARY KEY, -- Não pode ser NULL, único
Email VARCHAR(100) UNIQUE, -- Pode ser NULL (uma vez), mas se não-nulo, único
CPF CHAR(11) UNIQUE -- Outro unique permitido
);
```
**Análise das outras alternativas:**
**A - NOT NULL**: Garante que não seja nulo, mas permite duplicatas
**B - CHECK**: Valida condições customizadas, não necessariamente unicidade
**D - PRIMARY KEY**: Garante unicidade + não-nulo, mas é mais restritiva (só uma por tabela)
**E - FOREIGN KEY**: Garante integridade referencial, não unicidade
</details>
---
## Questão 5 (FGV - Banco do Brasil - 2023)
Analise o seguinte código SQL:
```sql
INSERT INTO Produto (ProdutoID, Nome, Preco)
VALUES
(1, 'Mouse', 50.00),
(2, 'Teclado', 120.00),
(1, 'Monitor', 800.00);
```
Considerando que ProdutoID é chave primária, o que acontecerá?
a) Todas as 3 linhas serão inseridas com sucesso
b) Apenas as 2 primeiras linhas serão inseridas
c) Nenhuma linha será inserida devido ao erro de PK duplicada
d) A 3ª linha sobrescreverá a 1ª linha
e) Será gerado um novo ProdutoID automaticamente para a 3ª linha
<details>
<summary>👉 Ver Resposta</summary>
**ALTERNATIVA C**
**Explicação**: O comando **falhará completamente** porque ProdutoID=1 está duplicado, violando a constraint de PRIMARY KEY.
**Comportamento transacional:**
```sql
-- O INSERT é uma operação transacional
INSERT INTO Produto VALUES
(1, 'Mouse', 50.00), -- OK
(2, 'Teclado', 120.00), -- OK
(1, 'Monitor', 800.00); -- ERRO! PK duplicada
-- Resultado: NENHUMA linha inserida (rollback automático)
```
**Princípio da atomicidade:**
- Comando SQL é **atômico** (tudo ou nada)
- Se **qualquer** linha falhar, **todas** são revertidas
- Tabela permanece no estado original
**Para inserir parcialmente (ignorar erros):**
```sql
-- MySQL - INSERT IGNORE
INSERT IGNORE INTO Produto VALUES
(1, 'Mouse', 50.00),
(2, 'Teclado', 120.00),
(1, 'Monitor', 800.00);
-- Insere linhas 1 e 2, ignora linha 3
-- PostgreSQL - ON CONFLICT
INSERT INTO Produto VALUES
(1, 'Mouse', 50.00),
(2, 'Teclado', 120.00),
(1, 'Monitor', 800.00)
ON CONFLICT (ProdutoID) DO NOTHING;
-- SQL Server - usa TRY/CATCH ou merge
```
**Por que as outras estão erradas:**
**A**: Violaria integridade de PK
**B**: Não há inserção parcial sem comandos especiais
**D**: UPDATE sobrescreve, INSERT não
**E**: PK não é AUTO_INCREMENT no exemplo
</details>
---
## Questão 6 (CESPE - STJ - 2022)
Qual a diferença fundamental entre os comandos UPDATE e ALTER TABLE?
<details>
<summary>👉 Ver Resposta</summary>
**RESPOSTA:**
**UPDATE** (DML): Modifica **DADOS** dentro das tabelas
**ALTER TABLE** (DDL): Modifica **ESTRUTURA** (esquema) das tabelas
**Comparação prática:**
```sql
-- UPDATE - Altera dados existentes (DML)
UPDATE Cliente
SET Email = 'novo@email.com'
WHERE ClienteID = 1;
-- Modifica o CONTEÚDO da célula
-- ALTER TABLE - Altera estrutura (DDL)
ALTER TABLE Cliente
ADD Telefone VARCHAR(15);
-- Adiciona nova COLUNA à tabela
ALTER TABLE Cliente
ALTER COLUMN Email VARCHAR(150);
-- Modifica TIPO DE DADO da coluna
```
**Categorias e usos:**
| Aspecto | UPDATE | ALTER TABLE |
|---------|--------|-------------|
| Categoria | DML | DDL |
| Afeta | Dados (linhas) | Estrutura (colunas, constraints) |
| Transacional | Sim (pode ROLLBACK) | Depende (geralmente não) |
| WHERE | Pode usar | Não se aplica |
| Exemplo | Atualizar preços | Adicionar coluna |
**Exemplos adicionais:**
```sql
-- UPDATE - Operações em dados
UPDATE Produto SET Preco = Preco * 1.1;
UPDATE Funcionario SET Salario = 5000 WHERE FuncID = 10;
-- ALTER TABLE - Operações em estrutura
ALTER TABLE Produto ADD Descricao TEXT;
ALTER TABLE Funcionario DROP COLUMN Telefone;
ALTER TABLE Cliente ADD CONSTRAINT pk_cliente PRIMARY KEY (ClienteID);
```
**Conceito-chave**:
- **DML** = Manipula o que está **dentro** das tabelas (conteúdo)
- **DDL** = Define **como** as tabelas são estruturadas (forma)
</details>
---
## Questão 7 (FCC - SEFAZ-BA - 2023)
Em relação às constraints (restrições) em SQL, analise:
I. A constraint NOT NULL impede que uma coluna aceite valores nulos
II. Uma tabela pode ter múltiplas constraints UNIQUE
III. A constraint CHECK permite validações customizadas
IV. PRIMARY KEY é equivalente a UNIQUE + NOT NULL + permite múltiplas por tabela
Está correto o que consta em:
a) I e II apenas
b) I, II e III apenas
c) I, II e IV apenas
d) II, III e IV apenas
e) Todas
<details>
<summary>👉 Ver Resposta</summary>
**ALTERNATIVA B (I, II e III)**
**Análise item por item:**
**I. CORRETO** ✓
```sql
CREATE TABLE Cliente (
Nome VARCHAR(100) NOT NULL -- Não aceita NULL
);
INSERT INTO Cliente (Nome) VALUES (NULL); -- ERRO!
```
**II. CORRETO** ✓
```sql
CREATE TABLE Cliente (
ClienteID INT PRIMARY KEY,
Email VARCHAR(100) UNIQUE, -- 1ª UNIQUE
CPF CHAR(11) UNIQUE, -- 2ª UNIQUE
Telefone VARCHAR(15) UNIQUE -- 3ª UNIQUE
);
-- Múltiplas constraints UNIQUE são permitidas
```
**III. CORRETO** ✓
```sql
-- CHECK permite validações customizadas
CREATE TABLE Produto (
ProdutoID INT PRIMARY KEY,
Preco DECIMAL(10,2) CHECK (Preco > 0),
Desconto INT CHECK (Desconto BETWEEN 0 AND 100),
DataValidade DATE CHECK (DataValidade > CURRENT_DATE)
);
-- Validações complexas
ALTER TABLE Funcionario
ADD CONSTRAINT chk_salario_idade
CHECK (
(Idade >= 18 AND Salario >= 1000) OR
(Idade >= 16 AND Salario >= 500)
);
```
**IV. ERRADO** ✗
PRIMARY KEY = UNIQUE + NOT NULL, mas **apenas UMA por tabela**
```sql
CREATE TABLE Exemplo (
ID INT PRIMARY KEY, -- ✅ Uma PK
CodigoAlt INT PRIMARY KEY -- ❌ ERRO! Não pode ter 2 PKs
);
-- Para múltiplos identificadores únicos:
CREATE TABLE Exemplo (
ID INT PRIMARY KEY, -- PK única
CodigoAlt INT UNIQUE, -- Alternativa 1
Email VARCHAR(100) UNIQUE -- Alternativa 2
);
```
**Resumo das constraints:**
| Constraint | Múltiplas? | Permite NULL? | Função |
|------------|------------|---------------|--------|
| NOT NULL | ✅ Sim | ❌ Não | Obriga valor |
| UNIQUE | ✅ Sim | ✅ Sim* | Valor único |
| PRIMARY KEY | ❌ Só uma | ❌ Não | ID principal |
| FOREIGN KEY | ✅ Sim | ✅ Sim* | Referência |
| CHECK | ✅ Sim | ✅ Sim* | Validação custom |
*Permite NULL a menos que combinado com NOT NULL
</details>
---
## Questão 8 (VUNESP - IPSM - 2022)
Considere a query:
```sql
SELECT CategoriaID, COUNT(*), AVG(Preco)
FROM Produto
WHERE Preco > 50
HAVING COUNT(*) > 10
ORDER BY AVG(Preco) DESC;
```
O que está INCORRETO nesta query?
a) Falta GROUP BY CategoriaID
b) WHERE deve vir depois de HAVING
c) AVG(Preco) não pode ser usado em ORDER BY
d) COUNT(*) precisa de alias
e) Não há erro, query está correta
<details>
<summary>👉 Ver Resposta</summary>
**ALTERNATIVA A**
**Explicação**: A query está usando **funções de agregação** (COUNT, AVG) com uma coluna não-agregada (CategoriaID), mas **falta o GROUP BY**.
**Erro:**
```sql
SELECT CategoriaID, COUNT(*), AVG(Preco) -- CategoriaID sem agregação
FROM Produto
WHERE Preco > 50
-- ❌ FALTA: GROUP BY CategoriaID
HAVING COUNT(*) > 10;
```
**Correção:**
```sql
SELECT
CategoriaID,
COUNT(*) AS QtdeProdutos,
AVG(Preco) AS PrecoMedio
FROM Produto
WHERE Preco > 50
GROUP BY CategoriaID -- ✅ Necessário!
HAVING COUNT(*) > 10
ORDER BY AVG(Preco) DESC;
```
**Regra fundamental:**
Quando usar funções de agregação (COUNT, SUM, AVG, MAX, MIN) com colunas não-agregadas, **TODAS** as colunas não-agregadas devem estar no GROUP BY.
**Análise das outras alternativas:**
**B - ERRADO**: WHERE vem **antes** de GROUP BY/HAVING
Ordem correta: WHERE → GROUP BY → HAVING
```sql
-- Ordem SQL:
SELECT ...
FROM ...
WHERE ... -- Filtra linhas ANTES de agrupar
GROUP BY ... -- Agrupa
HAVING ... -- Filtra grupos APÓS agrupar
ORDER BY ... -- Ordena resultado final
```
**C - ERRADO**: AVG(Preco) **pode** ser usado em ORDER BY
**D - ERRADO**: Alias não é obrigatório (mas é boa prática)
**E - ERRADO**: Há erro (falta GROUP BY)
**Exemplo completo correto:**
```sql
SELECT
CategoriaID,
COUNT(*) AS QtdeProdutos,
AVG(Preco) AS PrecoMedio,
SUM(Preco) AS PrecoTotal
FROM Produto
WHERE Ativo = TRUE -- Filtra produtos ativos
GROUP BY CategoriaID -- Agrupa por categoria
HAVING COUNT(*) > 10 -- Apenas categorias com 10+ produtos
ORDER BY AVG(Preco) DESC; -- Ordena por preço médio
```
</details>
---
## Questão 9 (CESPE - TRE - 2020)
Qual função SQL retorna o primeiro valor não-nulo em uma lista de expressões?
a) ISNULL
b) NULLIF
c) COALESCE
d) NVL
e) IFNULL
<details>
<summary>👉 Ver Resposta</summary>
**ALTERNATIVA C (COALESCE)**
**Explicação**: **COALESCE** é a função SQL padrão que retorna o primeiro valor não-nulo de uma lista de argumentos.
**Sintaxe e uso:**
```sql
COALESCE(valor1, valor2, valor3, ..., valorN)
-- Exemplos práticos
SELECT COALESCE(Email, Telefone, 'Sem contato') AS Contato
FROM Cliente;
-- Retorna Email se não for NULL
-- Senão retorna Telefone se não for NULL
-- Senão retorna 'Sem contato'
SELECT
Nome,
COALESCE(Desconto, 0) AS DescontoFinal
FROM Produto;
-- Se Desconto for NULL, usa 0
-- Múltiplos fallbacks
SELECT COALESCE(
EmailPrincipal,
EmailSecundario,
EmailAlternativo,
'nao-informado@empresa.com'
) FROM Usuario;
```
**Diferenças entre funções similares:**
| Função | SGBD | Argumentos | Descrição |
|--------|------|------------|-----------|
| **COALESCE** | Padrão SQL | N argumentos | Primeiro não-NULL |
| **ISNULL** | SQL Server | 2 argumentos | Se NULL, usa 2º |
| **NVL** | Oracle | 2 argumentos | Se NULL, usa 2º |
| **IFNULL** | MySQL | 2 argumentos | Se NULL, usa 2º |
| **NULLIF** | Padrão SQL | 2 argumentos | Se iguais, retorna NULL |
**Exemplos de cada:**
```sql
-- COALESCE (padrão, funciona em todos)
SELECT COALESCE(Col1, Col2, Col3, 'default');
-- ISNULL (SQL Server)
SELECT ISNULL(Email, 'Não informado');
-- NVL (Oracle)
SELECT NVL(Email, 'Não informado');
-- IFNULL (MySQL)
SELECT IFNULL(Email, 'Não informado');
-- NULLIF (retorna NULL se valores iguais)
SELECT NULLIF(Desconto, 0); -- Retorna NULL se Desconto = 0
-- Útil para evitar divisão por zero
SELECT Valor / NULLIF(Quantidade, 0);
```
**Por que COALESCE é a resposta:**
- ✅ **Padrão SQL** (portável entre SGBDs)
- ✅ Aceita **múltiplos** argumentos (não só 2)
- ✅ Mais flexível que alternativas
**Caso de uso real:**
```sql
-- Gerar relatório com dados de contato
SELECT
ClienteID,
Nome,
COALESCE(
EmailPrincipal,
EmailSecundario,
CONCAT(Telefone, '@sms.empresa.com'),
'Sem contato disponível'
) AS MelhorContato
FROM Cliente;
```
</details>
---
## Questão 10 (FGV - TJ-SC - 2023)
Sobre índices em banco de dados, é INCORRETO afirmar:
a) Índices melhoram a performance de consultas SELECT
b) Índices podem desacelerar operações de INSERT e UPDATE
c) Um índice UNIQUE impede valores duplicados
d) Quanto mais índices, melhor a performance geral do banco
e) Índices ocupam espaço adicional em disco
<details>
<summary>👉 Ver Resposta</summary>
**ALTERNATIVA D (INCORRETA)**
**Explicação**: A afirmação é **falsa** porque índices em excesso **prejudicam** a performance de operações de escrita (INSERT, UPDATE, DELETE) e consomem mais espaço.
**Por que D está errada:**
**Trade-off de índices:**
- ✅ **Benefícios**: Aceleram SELECTs (especialmente em WHERE, JOIN, ORDER BY)
- ❌ **Custos**: Desaceleram writes (cada índice precisa ser atualizado)
```sql
-- Com muitos índices:
INSERT INTO Produto (ProdutoID, Nome, Preco, CategoriaID)
VALUES (1000, 'Mouse', 50.00, 5);
-- O INSERT precisa atualizar:
-- 1. A tabela principal
-- 2. Índice da PK (ProdutoID)
-- 3. Índice em Nome (se existir)
-- 4. Índice em Preco (se existir)
-- 5. Índice em CategoriaID (se existir)
-- 6. Todos os outros índices...
-- Quanto mais índices, mais lento o INSERT!
```
**Regras para índices:**
**Quando CRIAR:**
- Colunas frequentemente usadas em WHERE
- Colunas em JOINs
- Colunas em ORDER BY / GROUP BY
- Chaves estrangeiras
- Consultas lentas identificadas
**Quando EVITAR:**
- Tabelas pequenas (< 1000 linhas)
- Colunas raramente consultadas
- Colunas com poucos valores distintos (baixa cardinalidade)
- Tabelas com muitas escritas
**Análise das outras alternativas:**
**A - CORRETO** ✓
```sql
-- Sem índice: Full table scan (lento)
SELECT * FROM Cliente WHERE Email = 'teste@email.com';
-- Com índice: Busca direta (rápido)
CREATE INDEX idx_email ON Cliente(Email);
SELECT * FROM Cliente WHERE Email = 'teste@email.com';
```
**B - CORRETO** ✓
```sql
-- Cada UPDATE precisa atualizar todos os índices
UPDATE Produto SET Preco = 100 WHERE ProdutoID = 1;
-- Atualiza: tabela + índice de Preco + outros índices em Preco
-- INSERT também é afetado
INSERT INTO Cliente VALUES (...);
-- Atualiza: tabela + todos os índices
```
**C - CORRETO** ✓
```sql
CREATE UNIQUE INDEX idx_email_unique ON Cliente(Email);
-- Garante que não haverá emails duplicados
-- Funciona como constraint UNIQUE
```
**E - CORRETO** ✓
Índices são estruturas de dados adicionais (B-Tree, Hash, Bitmap) que ocupam espaço em disco.
**Boas práticas:**
```sql
-- ✅ BOM: Índice em consulta frequente
CREATE INDEX idx_pedido_data ON Pedido(DataPedido)
WHERE Status = 'Pendente'; -- Índice parcial (PostgreSQL)
-- ❌ EXCESSIVO: Muitos índices
CREATE INDEX idx1 ON Produto(Nome);
CREATE INDEX idx2 ON Produto(Preco);
CREATE INDEX idx3 ON Produto(Descricao);
CREATE INDEX idx4 ON Produto(Peso);
CREATE INDEX idx5 ON Produto(Altura);
-- Cada INSERT vai atualizar 5+ índices!
-- ✅ MELHOR: Índice composto quando apropriado
CREATE INDEX idx_produto_busca ON Produto(CategoriaID, Preco);
-- Serve para: WHERE CategoriaID = X AND Preco > Y
```
**Conclusão**: Índices devem ser usados **estrategicamente**, não indiscriminadamente.
</details>
---
## Questão 11 (CESPE - TCU - 2022)
Qual a ordem correta de execução das cláusulas em uma consulta SQL?
a) SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY
b) FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
c) FROM → SELECT → WHERE → HAVING → GROUP BY → ORDER BY
d) SELECT → FROM → GROUP BY → WHERE → HAVING → ORDER BY
e) FROM → WHERE → SELECT → GROUP BY → HAVING → ORDER BY
<details>
<summary>👉 Ver Resposta</summary>
**ALTERNATIVA B**
**Explicação**: A ordem lógica de processamento (não a ordem de escrita) é:
**1. FROM** → Determina as tabelas
**2. WHERE** → Filtra linhas individuais
**3. GROUP BY** → Agrupa linhas
**4. HAVING** → Filtra grupos
**5. SELECT** → Seleciona/calcula colunas
**6. ORDER BY** → Ordena resultado final
**Exemplo com todas as cláusulas:**
```sql
-- Ordem de ESCRITA (como digitamos):
SELECT CategoriaID, AVG(Preco) AS PrecoMedio -- 5. Projetado
FROM Produto -- 1. Origem dos dados
WHERE Ativo = TRUE -- 2. Filtro de linhas
GROUP BY CategoriaID -- 3. Agrupamento
HAVING AVG(Preco) > 100 -- 4. Filtro de grupos
ORDER BY PrecoMedio DESC; -- 6. Ordenação
-- Ordem de PROCESSAMENTO (como o SGBD executa):
-- 1. FROM Produto → Carrega tabela
-- 2. WHERE Ativo = TRUE → Filtra produtos ativos
-- 3. GROUP BY CategoriaID → Agrupa por categoria
-- 4. HAVING AVG > 100 → Mantém apenas categorias com média > 100
-- 5. SELECT ... → Calcula PrecoMedio e seleciona colunas
-- 6. ORDER BY ... → Ordena resultado
```
**Por que essa ordem importa:**
```sql
-- WHERE vs HAVING
SELECT CategoriaID, COUNT(*) AS Total
FROM Produto
WHERE Preco > 50 -- Filtra ANTES de agrupar (mais eficiente)
GROUP BY CategoriaID
HAVING COUNT(*) > 10; -- Filtra DEPOIS de agrupar
-- Não pode usar alias em WHERE (processado antes de SELECT):
SELECT Nome, Preco * 0.9 AS PrecoComDesconto
FROM Produto
WHERE PrecoComDesconto > 100; -- ❌ ERRO! Alias não existe ainda
-- Correto:
WHERE Preco * 0.9 > 100; -- ✅ Usa expressão direta
-- Pode usar alias em ORDER BY (processado depois de SELECT):
ORDER BY PrecoComDesconto DESC; -- ✅ OK! Alias já foi definido
```
**Fluxo completo com exemplo:**
```sql
-- Query:
SELECT
Estado,
COUNT(*) AS QtdeClientes,
AVG(ValorTotal) AS MediaVendas
FROM Cliente c
INNER JOIN Vendas v ON c.ClienteID = v.ClienteID
WHERE v.DataVenda >= '2024-01-01'
GROUP BY Estado
HAVING AVG(ValorTotal) > 1000
ORDER BY MediaVendas DESC
LIMIT 5;
-- Processamento:
-- 1. FROM Cliente c JOIN Vendas v → Une tabelas
-- 2. WHERE DataVenda >= '2024-01-01' → Filtra vendas de 2024
-- 3. GROUP BY Estado → Agrupa por estado
-- 4. HAVING AVG > 1000 → Apenas estados com média > 1000
-- 5. SELECT Estado, COUNT, AVG → Calcula agregações
-- 6. ORDER BY MediaVendas DESC → Ordena por média decrescente
-- 7. LIMIT 5 → Retorna apenas 5 primeiros
```
**Dica mnemônica**: **F**rom **W**here **G**roup **H**aving **S**elect **O**rder
</details>
---
## Questão 12 (FCC - SEFAZ-SP - 2023)
Um desenvolvedor executou:
```sql
CREATE TABLE Pedido (
PedidoID INT IDENTITY(1,1) PRIMARY KEY,
ClienteID INT,
ValorTotal DECIMAL(10,2)
);
INSERT INTO Pedido (ClienteID, ValorTotal) VALUES (10, 500.00);
INSERT INTO Pedido (ClienteID, ValorTotal) VALUES (20, 750.00);
DELETE FROM Pedido WHERE PedidoID = 1;
INSERT INTO Pedido (ClienteID, ValorTotal) VALUES (30, 300.00);
```
Qual será o PedidoID do último registro inserido?
a) 1 (reusa o ID deletado)
b) 2 (próximo sequencial disponível)
c) 3 (continua a sequência)
d) 4 (incrementa após delete)
e) Erro (PedidoID 1 foi deletado)
<details>
<summary>👉 Ver Resposta</summary>
**ALTERNATIVA C (3)**
**Explicação**: **IDENTITY / AUTO_INCREMENT / SERIAL nunca reutiliza valores**, mesmo após DELETE.
**Passo a passo:**
```sql
-- Estado inicial: tabela vazia, próximo ID = 1
INSERT INTO Pedido VALUES (10, 500.00);
-- PedidoID = 1, próximo ID = 2
INSERT INTO Pedido VALUES (20, 750.00);
-- PedidoID = 2, próximo ID = 3
DELETE FROM Pedido WHERE PedidoID = 1;
-- Linha deletada, mas próximo ID continua = 3 (não volta para 1)
INSERT INTO Pedido VALUES (30, 300.00);
-- PedidoID = 3 (continua a sequência, não reusa 1)
-- Estado final da tabela:
-- PedidoID | ClienteID | ValorTotal
-- 2 | 20 | 750.00
-- 3 | 30 | 300.00
```
**Comportamento de contadores automáticos:**
| SGBD | Tipo | Reusa IDs deletados? |
|------|------|---------------------|
| SQL Server | IDENTITY | ❌ Não |
| MySQL | AUTO_INCREMENT | ❌ Não |
| PostgreSQL | SERIAL/IDENTITY | ❌ Não |
| Oracle | SEQUENCE | ❌ Não |
**Exceções (comportamento pode variar):**
```sql
-- SQL Server - DBCC CHECKIDENT reseta contador
TRUNCATE TABLE Pedido; -- Remove todos os dados
-- Próximo ID volta para 1
-- Ou forçar reset:
DBCC CHECKIDENT ('Pedido', RESEED, 0);
-- Próximo ID será 1
-- MySQL - AUTO_INCREMENT volta ao 1 após TRUNCATE
TRUNCATE TABLE Pedido;
INSERT INTO Pedido VALUES (10, 500); -- ID = 1
-- Mas DELETE não reseta:
DELETE FROM Pedido; -- Remove dados
INSERT INTO Pedido VALUES (10, 500); -- ID = próximo da sequência
```
**Lacunas (gaps) na sequência:**
```sql
-- Cenário real:
INSERT INTO Pedido VALUES (10, 500); -- ID = 1
INSERT INTO Pedido VALUES (20, 750); -- ID = 2
DELETE FROM Pedido WHERE PedidoID = 1;
INSERT INTO Pedido VALUES (30, 300); -- ID = 3
DELETE FROM Pedido WHERE PedidoID = 2;
INSERT INTO Pedido VALUES (40, 450); -- ID = 4
-- Resultado: IDs na tabela: 3, 4 (lacunas em 1 e 2)
-- Isso é NORMAL e esperado!
```
**Por que isso acontece:**
- **Performance**: Não verificar lacunas é mais rápido
- **Concorrência**: Evita conflitos em ambientes multi-usuário
- **Integridade**: IDs nunca se repetem, mesmo após delete
**Conclusão**: A resposta é **3** porque contadores automáticos NUNCA reutilizam valores.
</details>
---
## ✅ Fim do Módulo 3
**Você completou:**
- ✅ DDL completo (CREATE, ALTER, DROP, TRUNCATE)
- ✅ DML fundamental (INSERT, UPDATE, DELETE, SELECT básico)
- ✅ Tipos de dados e constraints
- ✅ Funções de agregação, strings e datas
- ✅ Boas práticas SQL
- ✅ 12 questões de concurso com explicações detalhadas
---