---
# System prepended metadata

title: SQL - Fundamentos (DDL e DML)

---

# 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

---