# 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 ---