Ebook SQL e PL/SQL: Do Iniciante ao Expert
Ebook SQL e PL/SQL: Do Iniciante ao Expert
Expert
Prefácio
Bem-vindo(a) à sua jornada para se tornar um(a) expert em SQL e PL/SQL! Este ebook foi cuidadosamente
elaborado para guiá-lo(a) desde os conceitos mais fundamentais de bancos de dados relacionais até as
técnicas mais avançadas de programação e otimização no ambiente Oracle. Se você é um completo iniciante
ou um desenvolvedor experiente buscando aprimorar suas habilidades, este material foi feito para você.
Nossa abordagem é prática e aprofundada. Cada um dos 30 capítulos oferece uma explicação detalhada e
contextualizada de todos os conceitos, com justificativas claras e fundamentação teórica sólida. Você
encontrará exemplos teóricos e práticos, comparações ilustrativas que facilitam a compreensão, tópicos
chave para revisão rápida e exercícios desafiadores para consolidar seu aprendizado. Não nos limitamos a
fontes em português, buscando referências globais para garantir a abrangência e a qualidade do conteúdo.
Ao final deste ebook, você terá não apenas um conhecimento profundo de SQL e PL/SQL, mas também a
capacidade de aplicar esses conhecimentos em cenários do mundo real, otimizar a performance de suas
aplicações e desenvolver soluções robustas e escaláveis. O projeto final, que integra diversas funcionalidades,
servirá como um excelente exemplo prático e um ponto de partida para seus próprios projetos.
Prepare-se para mergulhar no fascinante mundo dos bancos de dados e transformar sua carreira. Boa leitura
e bons estudos!
Manus AI
SQL (Structured Query Language), ou Linguagem de Consulta Estruturada, é a linguagem padrão para
gerenciar e manipular bancos de dados relacionais. Criada na década de 1970 pela IBM, o SQL se tornou a
linguagem universal para interagir com sistemas de gerenciamento de banco de dados (SGBDs) como Oracle,
MySQL, PostgreSQL, SQL Server, entre outros [1].
O SQL não é uma linguagem de programação de propósito geral como Python ou Java. Em vez disso, é uma
linguagem declarativa, o que significa que você descreve o que deseja alcançar (por exemplo, "selecione
todos os clientes do Brasil") em vez de como fazê-lo (os passos exatos para buscar esses dados). O SGBD é
responsável por determinar a maneira mais eficiente de executar sua solicitação.
1.1.1 Por que SQL é Importante?
Padrão da Indústria: Praticamente todos os SGBDs relacionais suportam SQL, o que torna o
conhecimento em SQL altamente transferível.
Gerenciamento de Dados: Permite criar, modificar e excluir estruturas de banco de dados, bem como
inserir, atualizar e consultar dados.
Análise de Dados: É a ferramenta fundamental para extrair insights de grandes volumes de dados,
suportando decisões de negócio.
Base para Outras Tecnologias: Muitas ferramentas de Business Intelligence (BI), frameworks de
desenvolvimento e linguagens de programação interagem com bancos de dados usando SQL.
Um banco de dados relacional é um tipo de banco de dados que armazena e fornece acesso a pontos de
dados que estão relacionados entre si. Ele é baseado no modelo relacional, proposto por E.F. Codd em 1970,
onde os dados são organizados em tabelas (também chamadas de relações) [2].
Tabela (Table/Relation): Uma coleção de dados organizada em linhas e colunas. Cada tabela
representa uma entidade (por exemplo, Clientes, Produtos, Pedidos).
Linha (Row/Record/Tuple): Representa uma única ocorrência da entidade. Uma linha contém um
conjunto de valores para cada coluna da tabela (por exemplo, um cliente específico, um produto
específico).
Chave Primária (Primary Key - PK): Uma coluna ou conjunto de colunas que identifica unicamente
cada linha em uma tabela. Não pode conter valores nulos e deve ser única para cada registro. É a
"identidade" da linha.
Chave Estrangeira (Foreign Key - FK): Uma coluna ou conjunto de colunas em uma tabela que faz
referência à chave primária de outra tabela. Ela estabelece um relacionamento entre as tabelas e ajuda a
manter a integridade referencial dos dados.
Tabela: Clientes
Tabela: Produtos
ProdutoID (PK) Nome Produto Preço
102 Mouse 50
Tabela: Pedidos
1 1 2025-01-15 2550
2 2 2025-01-16 50
Neste exemplo: * ClienteID é a chave primária da tabela Clientes . * ProdutoID é a chave primária da
tabela Produtos . * PedidoID é a chave primária da tabela Pedidos . * ClienteID na tabela Pedidos é uma
chave estrangeira que referencia ClienteID na tabela Clientes . Isso significa que um pedido sempre deve
estar associado a um cliente existente.
DDL (Data Definition Language - Linguagem de Definição de Dados): Usada para definir, modificar e
excluir a estrutura do banco de dados. Inclui comandos como CREATE , ALTER , DROP , TRUNCATE .
DML (Data Manipulation Language - Linguagem de Manipulação de Dados): Usada para manipular os
dados dentro das tabelas. Inclui comandos como INSERT , UPDATE , DELETE , SELECT .
DCL (Data Control Language - Linguagem de Controle de Dados): Usada para gerenciar permissões e
controle de acesso aos dados. Inclui comandos como GRANT , REVOKE .
TCL (Transaction Control Language - Linguagem de Controle de Transações): Usada para gerenciar
transações, garantindo a integridade dos dados. Inclui comandos como COMMIT , ROLLBACK , SAVEPOINT .
PL/SQL (Procedural Language/SQL) é uma extensão procedural da linguagem SQL desenvolvida pela Oracle
Corporation. Enquanto o SQL é declarativo e focado em "o quê" fazer, o PL/SQL adiciona a capacidade de
"como" fazer, introduzindo conceitos de programação como variáveis, estruturas de controle (condicionais,
loops), tratamento de exceções e modularização (procedures, functions, packages) [3].
O PL/SQL permite combinar o poder do SQL para manipulação de dados com a flexibilidade de uma
linguagem de programação procedural. Isso é crucial para desenvolver lógica de negócio complexa
diretamente no banco de dados, o que pode melhorar a performance (reduzindo o tráfego de rede), a
segurança e a manutenibilidade.
1.4.1 Por que PL/SQL é Importante?
Lógica de Negócio no Banco de Dados: Permite implementar regras de negócio complexas e validações
diretamente no SGBD.
Performance: Reduz o tráfego de rede entre a aplicação e o banco de dados, pois a lógica é executada
no servidor.
Tratamento de Erros: Oferece mecanismos robustos para lidar com exceções e garantir a integridade
dos dados.
Controle Não possui estruturas de controle Possui IF-THEN-ELSE, LOOP, WHILE, FOR
1. Definições: Explique com suas palavras o que é SQL e qual a sua principal finalidade.
2. Conceitos Fundamentais: Defina os termos: Tabela, Coluna, Linha, Chave Primária e Chave Estrangeira.
3. Classificação de Comandos: Para cada comando SQL abaixo, indique a qual categoria (DDL, DML, DCL,
TCL) ele pertence:
INSERT
CREATE TABLE
GRANT
SELECT
ROLLBACK
ALTER TABLE
DELETE
COMMIT
4. PL/SQL: Qual a principal diferença entre SQL e PL/SQL? Em que situações o PL/SQL é mais vantajoso?
5. Cenário Prático: Imagine um sistema de biblioteca. Quais tabelas você criaria? Quais seriam as chaves
primárias e estrangeiras? Dê exemplos de 2-3 colunas para cada tabela.
Referências
DDL (Data Definition Language), ou Linguagem de Definição de Dados, é o subconjunto do SQL responsável
por definir, modificar e excluir a estrutura dos objetos do banco de dados. Enquanto os comandos DML (Data
Manipulation Language) trabalham com os dados dentro das tabelas, os comandos DDL operam sobre as
tabelas, índices, visões, sequências e outros objetos do esquema. Eles são a base para a criação e
manutenção da arquitetura do banco de dados [1].
Os comandos DDL são transacionais, mas com uma particularidade importante: cada comando DDL executa
um COMMIT implícito antes e depois de sua execução. Isso significa que quaisquer alterações pendentes de
DML serão salvas antes que o DDL seja executado, e o próprio DDL será salvo imediatamente após sua
conclusão. Portanto, operações DDL não podem ser revertidas com ROLLBACK .
O comando CREATE TABLE é usado para criar uma nova tabela no banco de dados. Ao criar uma tabela, você
define seu nome, as colunas que a compõem, o tipo de dado de cada coluna e as restrições de integridade [2].
O Oracle Database oferece uma vasta gama de tipos de dados. Alguns dos mais comuns incluem:
VARCHAR2(n) String de caracteres de comprimento variável (até 4000 bytes). Nome VARCHAR2(100)
DATE Data e hora (século, ano, mês, dia, hora, minuto, segundo). DataCadastro DATE
CLOB Large Object para dados de caracteres (até 4GB). DescricaoProduto CLOB
BLOB Large Object para dados binários (até 4GB). FotoProduto BLOB
Restrições de integridade são regras que o Oracle impõe aos dados em uma tabela. Elas garantem a precisão e
a consistência dos dados.
NOT NULL : Garante que a coluna não pode conter valores nulos.
UNIQUE : Garante que todos os valores em uma coluna (ou conjunto de colunas) sejam únicos.
PRIMARY KEY : Combinação de NOT NULL e UNIQUE . Identifica unicamente cada linha na tabela.
FOREIGN KEY : Garante a integridade referencial, ligando uma coluna a uma chave primária em outra
tabela.
CHECK : Garante que todos os valores em uma coluna satisfaçam uma condição específica.
DEFAULT : Atribui um valor padrão a uma coluna se nenhum valor for especificado durante a inserção.
O comando ALTER TABLE é usado para modificar a estrutura de uma tabela existente. Você pode adicionar,
modificar ou excluir colunas, adicionar ou remover restrições, e renomear tabelas ou colunas [3].
O comando DROP TABLE é usado para remover uma tabela existente do banco de dados. Isso exclui a
definição da tabela, todos os seus dados, índices, triggers e restrições associadas. É uma operação irreversível
[4].
CASCADE CONSTRAINTS : Opcional. Se a tabela que está sendo excluída tiver chaves primárias
referenciadas por chaves estrangeiras em outras tabelas, esta cláusula remove automaticamente essas
chaves estrangeiras. Se omitida e houver dependências, o comando falhará.
O comando TRUNCATE TABLE é usado para remover todas as linhas de uma tabela de forma rápida e eficiente.
Diferente do DELETE (que é um comando DML), o TRUNCATE é um comando DDL. Ele redefine o high-water
mark da tabela, liberando espaço de armazenamento [5].
Velocidade Muito rápido (libera espaço) Mais lento (remove linha por linha)
Rollback Não pode ser revertido Pode ser revertido com ROLLBACK
Integridade Falha se houver FKs referenciando Pode ser usado com WHERE para remover seletivamente
CREATE TABLE : Cria novas tabelas com colunas, tipos de dados e restrições.
Restrições (Constraints): NOT NULL , UNIQUE , PRIMARY KEY , FOREIGN KEY , CHECK , DEFAULT .
DROP TABLE : Exclui tabelas e seus objetos associados ( CASCADE CONSTRAINTS para dependências).
TRUNCATE TABLE : Remove todos os dados de uma tabela rapidamente (irreversível, não dispara
triggers).
1. Criação de Tabela: Crie uma tabela chamada Funcionarios com as seguintes colunas e restrições:
Adicione uma restrição CHECK para garantir que DataContratacao não seja no futuro.
3. Chave Estrangeira: Crie uma tabela Departamentos com DepartamentoID (PK) e NomeDepartamento .
Em seguida, adicione uma chave estrangeira na tabela Funcionarios que referencie DepartamentoID
em Departamentos .
Use TRUNCATE TABLE para remover todos os dados da tabela Funcionarios (se houver).
5. Cenário de Erro: Tente excluir a tabela Clientes sem usar CASCADE CONSTRAINTS se a tabela Pedidos
ainda existir e a referenciar. O que acontece? Por quê? Em seguida, exclua-a corretamente.
Referências
[1] Oracle. SQL Language Reference - Data Definition Language (DDL) Statements. Disponível em:
[Link]
[Link] [2] Oracle. CREATE TABLE Statement. Disponível em:
[Link] [3] Oracle. ALTER
TABLE Statement. Disponível em: [Link]
database/19/sqlrf/[Link] [4] Oracle. DROP TABLE Statement. Disponível em:
[Link] [5] Oracle.
TRUNCATE TABLE Statement. Disponível em: [Link]
database/19/sqlrf/[Link]
Os comandos DML são transacionais. Isso significa que as alterações feitas por esses comandos não são
permanentes no banco de dados até que uma transação seja confirmada ( COMMIT ). Se ocorrer um erro ou se
você decidir que as alterações não devem ser salvas, elas podem ser desfeitas ( ROLLBACK ).
3.2 INSERT: Inserindo Novas Linhas
O comando INSERT é usado para adicionar uma ou mais novas linhas (registros) a uma tabela. Existem duas
formas principais de usar o INSERT :
Esta forma permite inserir valores diretamente para as colunas especificadas. É importante que a ordem dos
valores corresponda à ordem das colunas.
Sintaxe:
Se você for inserir valores para todas as colunas da tabela, na ordem em que foram definidas, pode
omitir a lista de colunas: sql INSERT INTO nome_tabela VALUES (valor1, valor2, ...);
Exemplos:
Esta forma permite inserir dados em uma tabela a partir dos resultados de uma consulta SELECT . É útil para
copiar dados de uma tabela para outra, ou para consolidar dados de várias fontes.
Sintaxe:
Exemplo:
-- Criar uma tabela de backup de clientes
CREATE TABLE Clientes_Backup (
ClienteID NUMBER,
Nome VARCHAR2(100),
Email VARCHAR2(255)
);
O comando UPDATE é usado para modificar os valores de uma ou mais colunas em linhas existentes de uma
tabela. É crucial usar a cláusula WHERE para especificar quais linhas devem ser atualizadas; caso contrário,
todas as linhas da tabela serão afetadas [2].
Sintaxe:
UPDATE nome_tabela
SET coluna1 = novo_valor1, coluna2 = novo_valor2, ...
[WHERE condicao];
Exemplos:
-- Mudar o status de clientes inativos para 'Suspenso' que não compram há mais de 1 ano
UPDATE Clientes
SET Status = 'Suspenso'
WHERE Status = 'Inativo'
AND DataUltimaCompra < ADD_MONTHS(SYSDATE, -12);
O comando DELETE é usado para remover uma ou mais linhas de uma tabela. Assim como no UPDATE , a
cláusula WHERE é fundamental para especificar quais linhas devem ser excluídas. Se a cláusula WHERE for
omitida, todas as linhas da tabela serão removidas [3].
Sintaxe:
Exemplos:
Conforme visto no Capítulo 2, DELETE e TRUNCATE removem dados de uma tabela, mas com diferenças
cruciais:
Rollback Pode ser revertido com ROLLBACK Não pode ser revertido
WHERE Permite cláusula WHERE para remoção seletiva Não permite cláusula WHERE (remove tudo)
Performance Mais lento (registra cada exclusão) Muito rápido (redefine a tabela)
Uma transação é uma sequência de uma ou mais operações SQL que são tratadas como uma única unidade
lógica de trabalho. As transações garantem a integridade dos dados, seguindo as propriedades ACID
(Atomicidade, Consistência, Isolamento, Durabilidade) [4].
3.5.1 Propriedades ACID
Atomicidade: Uma transação é uma unidade indivisível. Ou todas as suas operações são concluídas com
sucesso ( COMMIT ), ou nenhuma delas é ( ROLLBACK ).
Consistência: Uma transação leva o banco de dados de um estado consistente para outro estado
consistente. As regras e restrições do banco de dados são mantidas.
Isolamento: Múltiplas transações executando concorrentemente não devem interferir umas nas outras.
O resultado final deve ser o mesmo como se tivessem sido executadas sequencialmente.
Durabilidade: Uma vez que uma transação é confirmada ( COMMIT ), suas alterações são permanentes e
sobreviverão a falhas do sistema.
COMMIT : Salva permanentemente todas as alterações feitas na transação atual no banco de dados.
Libera quaisquer locks e torna as alterações visíveis para outras sessões.
ROLLBACK : Desfaz todas as alterações feitas na transação atual desde o último COMMIT ou SAVEPOINT .
Retorna o banco de dados ao estado anterior.
SAVEPOINT : Define um ponto de salvamento dentro de uma transação. Permite que você desfaça parte
de uma transação sem reverter a transação inteira.
Exemplo de Transação:
-- Exemplo de ROLLBACK
INSERT INTO Clientes (ClienteID, Nome, Email)
VALUES (3, 'Carlos Pereira', '[Link]@[Link]');
SAVEPOINT antes_do_produto;
-- Algo deu errado com o pedido, mas quero manter o cliente e o produto
-- ROLLBACK TO antes_do_produto; -- Desfaz apenas a inserção do produto
Transações (ACID): Unidades lógicas de trabalho que garantem a integridade dos dados.
Atomicidade: Tudo ou nada.
1. Inserção de Dados:
Insira 1 novo pedido para um dos clientes, com 2 itens de produtos diferentes.
2. Atualização de Dados:
4. Transações:
Se houver algum erro (simule um erro, por exemplo, tentando inserir um ProdutoID
inexistente), faça ROLLBACK .
5. SAVEPOINT :
Faça ROLLBACK para o SAVEPOINT e verifique se os 3 primeiros produtos foram mantidos e o 6º não
foi inserido.
Referências
[1] Oracle. SQL Language Reference - Data Manipulation Language (DML) Statements. Disponível em:
[Link]
[Link] [2] Oracle. INSERT Statement. Disponível em:
[Link] [3] Oracle. UPDATE
Statement. Disponível em: [Link]
database/19/sqlrf/[Link] [4] Oracle. DELETE Statement. Disponível em:
[Link] [5] Oracle. Transaction
Control Statements. Disponível em: [Link]
database/19/sqlrf/[Link]
O comando SELECT é, sem dúvida, o comando mais utilizado e fundamental em SQL. Ele é responsável por
recuperar dados de uma ou mais tabelas em um banco de dados relacional. Enquanto os comandos DML que
vimos no capítulo anterior ( INSERT , UPDATE , DELETE ) modificam os dados, o SELECT permite visualizar,
analisar e extrair informações sem alterar o conteúdo das tabelas. É através do SELECT que transformamos
dados brutos em informações úteis para tomada de decisões [1].
O SELECT é frequentemente categorizado como parte da DQL (Data Query Language - Linguagem de Consulta
de Dados), um subconjunto do SQL dedicado exclusivamente à recuperação de dados. Sua versatilidade é
impressionante: desde consultas simples que retornam todas as linhas de uma tabela até consultas
complexas que envolvem múltiplas tabelas, funções de agregação, subconsultas e operações matemáticas
avançadas.
SELECT lista_de_colunas
FROM nome_da_tabela
[WHERE condições]
[ORDER BY colunas_de_ordenação]
[LIMIT número_de_linhas];
Neste capítulo, vamos explorar os componentes fundamentais do SELECT , começando com as consultas mais
simples e construindo gradualmente nossa compreensão até consultas mais sofisticadas.
A cláusula SELECT especifica quais colunas você deseja recuperar da tabela. Você pode selecionar uma única
coluna, múltiplas colunas específicas ou todas as colunas da tabela.
Para selecionar apenas uma coluna, especifique o nome da coluna após a palavra-chave SELECT :
SELECT nome_da_coluna
FROM nome_da_tabela;
SELECT NomeProduto
FROM Produtos;
Este comando retornará uma lista com todos os nomes de produtos armazenados na tabela, uma linha para
cada produto.
Para selecionar múltiplas colunas, separe os nomes das colunas com vírgulas:
A ordem das colunas no resultado será a mesma ordem especificada na cláusula SELECT . Isso significa que
você pode controlar como os dados são apresentados, independentemente da ordem física das colunas na
tabela.
O asterisco ( * ) é um caractere especial que representa todas as colunas da tabela. É útil para visualizar
rapidamente todos os dados de uma tabela, especialmente durante o desenvolvimento ou análise
exploratória:
SELECT *
FROM nome_da_tabela;
SELECT *
FROM Produtos;
Importante: Embora SELECT * seja conveniente para exploração e desenvolvimento, não é recomendado
em código de produção por várias razões:
Performance: Recuperar todas as colunas pode ser desnecessário e consumir mais recursos de rede e
memória.
Segurança: Pode expor dados sensíveis que não deveriam ser acessados pela aplicação.
Legibilidade: Não deixa claro quais dados são realmente necessários para a operação.
A cláusula FROM especifica de qual tabela (ou tabelas, no caso de JOIN s que veremos em capítulos
posteriores) os dados devem ser recuperados. É uma cláusula obrigatória em praticamente todas as consultas
SELECT .
SELECT colunas
FROM nome_da_tabela;
O nome da tabela deve existir no banco de dados atual e o usuário deve ter permissões de leitura ( SELECT ) na
tabela. Em alguns SGBDs, você pode precisar especificar o esquema (schema) ou o banco de dados junto com
o nome da tabela:
-- Especificando esquema (comum em SQL Server, PostgreSQL)
SELECT NomeProduto
FROM [Link];
Em muitas situações, uma consulta pode retornar linhas duplicadas. A palavra-chave DISTINCT é usada para
remover essas duplicatas, retornando apenas valores únicos.
Exemplo: Suponha que você queira ver todos os preços únicos dos produtos, sem repetições.
Se a tabela Produtos tiver múltiplos produtos com o mesmo preço, este comando retornará cada preço
único apenas uma vez.
Quando DISTINCT é usado com múltiplas colunas, ele considera a combinação de valores em todas as
colunas especificadas. Uma linha é considerada duplicata apenas se todos os valores das colunas
especificadas forem idênticos a outra linha.
Este comando retornará cada combinação única de preço e estoque. Por exemplo, se dois produtos tiverem
preço R$ 100,00, mas estoques diferentes (50 e 30), ambas as combinações serão retornadas.
O uso de DISTINCT pode impactar a performance, especialmente em tabelas grandes, pois o SGBD precisa
comparar todas as linhas para identificar duplicatas. Use DISTINCT apenas quando necessário e considere se
a lógica de negócio realmente requer a remoção de duplicatas.
Aliases (apelidos) são nomes alternativos temporários que você pode atribuir a colunas e tabelas em uma
consulta. Eles tornam os resultados mais legíveis e são essenciais quando você trabalha com múltiplas
tabelas ou quando os nomes das colunas são longos ou pouco descritivos.
You can create an alias for a column using the AS keyword (which is optional in many SGBDs):
SELECT
NomeProduto AS "Nome do Produto",
Preco AS "Preço (R$)",
Estoque AS "Quantidade em Estoque"
FROM Produtos;
Se o alias contiver espaços ou caracteres especiais, deve ser colocado entre aspas duplas ( " ) ou
colchetes ( [] no SQL Server).
Aliases são especialmente úteis quando você usa funções ou expressões matemáticas.
Aliases de tabelas são extremamente úteis quando você trabalha com múltiplas tabelas (em JOIN s) ou
quando o nome da tabela é longo. Eles tornam a consulta mais concisa e legível.
SELECT alias.nome_da_coluna
FROM nome_da_tabela AS alias;
SELECT
[Link],
[Link]
FROM Produtos AS p;
Embora este exemplo seja simples e o alias possa parecer desnecessário, aliases de tabelas se tornam
indispensáveis quando você trabalha com JOIN s entre múltiplas tabelas, como veremos em capítulos
posteriores.
Aliases são particularmente úteis quando você realiza cálculos ou usa funções em suas consultas:
SELECT
NomeProduto,
Preco,
Estoque,
Preco * Estoque AS "Valor Total em Estoque"
FROM Produtos;
Este exemplo calcula o valor total em estoque para cada produto (preço multiplicado pelo estoque) e atribui o
alias "Valor Total em Estoque" ao resultado do cálculo.
O SQL permite realizar cálculos e manipulações diretamente nas consultas SELECT . Isso é extremamente útil
para análises e relatórios, pois você pode derivar novos valores a partir dos dados existentes sem precisar
modificar as tabelas.
+ (adição)
- (subtração)
* (multiplicação)
/ (divisão)
Exemplo: Calculando o valor total de cada produto em estoque e aplicando um desconto de 10%.
SELECT
NomeProduto,
Preco,
Estoque,
Preco * Estoque AS "Valor Total",
(Preco * Estoque) * 0.9 AS "Valor com 10% Desconto"
FROM Produtos;
A concatenação de strings permite combinar valores de texto. A sintaxe varia entre os SGBDs:
SQL Server:
MySQL:
PostgreSQL:
SELECT Nome || ' ' || Sobrenome AS "Nome Completo"
FROM Clientes;
Oracle:
Valores NULL representam dados ausentes ou desconhecidos. É importante entender como eles se
comportam em cálculos:
Exemplo: Se um produto não tiver descrição (valor NULL ), como isso afeta os resultados?
SELECT
NomeProduto,
Descricao,
'Produto: ' + Descricao AS "Descrição Formatada" -- Resultará em NULL se Descricao for NULL
FROM Produtos;
Para lidar com valores NULL , você pode usar funções como ISNULL() (SQL Server), IFNULL() (MySQL),
COALESCE() (padrão SQL), ou NVL() (Oracle):
-- SQL Server
SELECT
NomeProduto,
ISNULL(Descricao, 'Sem descrição') AS "Descrição"
FROM Produtos;
-- MySQL
SELECT
NomeProduto,
IFNULL(Descricao, 'Sem descrição') AS "Descrição"
FROM Produtos;
Valores NULL : Entenda como valores ausentes afetam cálculos e use funções apropriadas para tratá-los.
Usando as tabelas Produtos , Clientes e Vendas dos capítulos anteriores (certifique-se de que elas
contenham alguns dados de exemplo):
1. Consultas Básicas:
2. Usando DISTINCT:
3. Aliases e Formatação:
Selecione o nome e sobrenome dos clientes, mas renomeie as colunas para "Primeiro Nome" e
"Último Nome".
Crie uma consulta que mostre o nome do produto e seu preço, mas renomeie a coluna de preço
para "Valor (R$)".
4. Cálculos e Expressões:
Calcule o valor total de cada venda (quantidade × preço unitário) e dê um alias apropriado.
Crie uma coluna que concatene o nome e sobrenome dos clientes em um formato "Sobrenome,
Nome".
5. Desafio:
Crie uma consulta que mostre o nome do produto, seu preço atual, o preço com 20% de desconto,
e a diferença entre o preço original e o preço com desconto. Use aliases descritivos para todas as
colunas calculadas.
Referências
A cláusula WHERE é um componente essencial do comando SELECT (e também de UPDATE e DELETE ) que
permite filtrar as linhas retornadas por uma consulta, ou seja, selecionar apenas os registros que satisfazem
uma ou mais condições específicas. Sem a cláusula WHERE , o comando SELECT retornaria todas as linhas da
tabela, o que raramente é o desejado em um cenário real de banco de dados [1].
A WHERE atua como um funil, permitindo que você especifique critérios para incluir ou excluir linhas do
conjunto de resultados. Isso é fundamental para extrair informações precisas e relevantes de grandes volumes
de dados.
Sintaxe Básica:
SELECT colunas
FROM nome_da_tabela
WHERE condicao;
Uma condicao é uma expressão lógica que avalia para TRUE , FALSE ou UNKNOWN (no caso de valores NULL ).
Apenas as linhas para as quais a condição é TRUE são incluídas no resultado.
Os operadores de comparação são usados para comparar um valor com outro. Eles são a base para construir a
maioria das condições na cláusula WHERE .
Operadores lógicos permitem combinar múltiplas condições na cláusula WHERE , criando filtros mais
complexos.
NOT : Inverte o resultado de uma condição (de TRUE para FALSE , e vice-versa).
-- Clientes do estado de SP ou RJ
SELECT Nome, Estado
FROM Clientes
WHERE Estado = 'SP' OR Estado = 'RJ';
1. NOT
2. AND
3. OR
Você pode usar parênteses () para alterar a ordem de avaliação e garantir que as condições sejam avaliadas
como você deseja.
Exemplo: Produtos com preço > 100 E (estoque < 10 OU status = 'Descontinuado')
Além dos operadores de comparação e lógicos, o SQL oferece operadores especiais para tipos específicos de
filtragem.
O operador BETWEEN é usado para selecionar valores dentro de um determinado intervalo (inclusive os
limites). É equivalente a usar >= e <= com AND .
Sintaxe:
Exemplos:
O operador IN é usado para selecionar linhas onde o valor de uma coluna corresponde a qualquer valor em
uma lista de valores especificada. É uma alternativa concisa para múltiplas condições OR .
Sintaxe:
Exemplos:
-- Clientes dos estados de SP, RJ ou MG
SELECT Nome, Estado
FROM Clientes
WHERE Estado IN ('SP', 'RJ', 'MG');
O operador LIKE é usado para buscar por padrões em colunas de texto. Ele utiliza caracteres curinga
(wildcards):
Sintaxe:
Exemplos:
-- Produtos com nome de 5 caracteres, começando com 'M' e terminando com 'e' (Mouse)
SELECT NomeProduto
FROM Produtos
WHERE NomeProduto LIKE 'M___e';
Para verificar se uma coluna contém ou não um valor NULL , você deve usar IS NULL ou IS NOT NULL .
Operadores de comparação ( = , <> ) não funcionam corretamente com NULL .
Sintaxe:
Exemplos:
-- Clientes que não possuem email cadastrado
SELECT Nome, Email
FROM Clientes
WHERE Email IS NULL;
Você pode usar funções em suas condições WHERE para manipular os dados antes de compará-los. Isso é útil
para normalizar dados ou extrair partes específicas de datas ou strings.
Exemplos:
Cuidado: Usar funções em colunas na cláusula WHERE pode impedir o uso de índices nessas colunas,
impactando negativamente a performance. Sempre que possível, tente reescrever a condição para evitar
funções na coluna indexada (ex: DataCadastro BETWEEN DATE '2024-01-01' AND DATE '2024-12-31' é
melhor que TO_CHAR(DataCadastro, 'YYYY') = '2024' ).
Operadores Especiais:
BETWEEN : Para intervalos de valores.
Funções em WHERE: Podem ser usadas, mas com atenção ao impacto na performance de índices.
5.7 Exercícios do Capítulo 5
Usando as tabelas Produtos , Clientes e Vendas (ou Pedidos e ItensPedido ) com dados de exemplo:
1. Filtragem Simples:
2. Combinando Condições:
Selecione produtos que custam entre R50, 00eR 200,00 (inclusive) e que tenham estoque maior
que 10 unidades.
Encontre pedidos feitos em 2023 cujo valor total seja menor que R$ 500,00.
3. Operadores Especiais:
4. Funções em WHERE:
5. Desafio:
Crie uma consulta que retorne os nomes dos clientes que fizeram pedidos com valor total superior
a R$ 2000,00 no ano de 2024, e que não sejam do estado de 'MG'.
Referências
Até agora, aprendemos a selecionar colunas específicas e filtrar linhas com base em condições. No entanto, os
dados retornados por uma consulta SQL não têm uma ordem garantida por padrão. O Sistema de
Gerenciamento de Banco de Dados (SGBD) pode retornar as linhas em qualquer ordem, que pode variar entre
execuções da mesma consulta. Para garantir que os resultados sejam apresentados de forma consistente e
útil, precisamos usar a cláusula ORDER BY [1].
Performance de Aplicações: Algumas operações são mais eficientes quando os dados estão ordenados.
Neste capítulo, exploraremos como usar a cláusula ORDER BY para controlar a ordem dos resultados e como
limitar o número de linhas retornadas para melhorar a performance e a usabilidade.
A cláusula ORDER BY é usada para ordenar o conjunto de resultados de uma consulta por uma ou mais
colunas. A sintaxe básica é:
Por padrão, ORDER BY ordena os dados em ordem crescente (ascendente). A palavra-chave ASC é opcional.
Exemplo: Listar produtos ordenados por preço (do mais barato para o mais caro).
SELECT NomeProduto, Preco
FROM Produtos
ORDER BY Preco ASC;
Exemplo: Listar produtos ordenados por preço (do mais caro para o mais barato).
Strings: Ordem alfabética/lexicográfica (A, B, C, ..., Z). A ordenação pode ser sensível ou insensível a
maiúsculas/minúsculas, dependendo da configuração do banco de dados.
Valores NULL: Geralmente aparecem primeiro (ASC) ou último (DESC), mas isso pode variar entre
SGBDs.
Exemplos:
Ordenação alfabética por nome do produto: sql SELECT NomeProduto, Preco FROM Produtos ORDER
BY NomeProduto;
Ordenação cronológica por data de venda (mais recentes primeiro): sql SELECT VendaID,
DataVenda, Quantidade FROM Vendas ORDER BY DataVenda DESC;
You can order by multiple columns by separating them with commas. SQL will first order by the first specified
column, then by the second (for rows that have the same value in the first column), and so on.
Exemplo: Ordenar clientes primeiro por sobrenome (alfabeticamente) e depois por nome (para sobrenomes
iguais).
SELECT Nome, Sobrenome, Email
FROM Clientes
ORDER BY Sobrenome ASC, Nome ASC;
Exemplo: Ordenar produtos primeiro por preço (do mais caro para o mais barato) e depois por estoque (do
maior para o menor) para produtos com o mesmo preço.
You can use ASC for some columns and DESC for others in the same query.
Exemplo: Ordenar vendas por data (mais recentes primeiro) e depois por quantidade (menor para maior) para
vendas na mesma data.
Em vez de usar o nome da coluna, você pode usar a posição numérica da coluna na lista SELECT (começando
em 1). Embora seja uma funcionalidade disponível, não é recomendada para código de produção, pois torna
o código menos legível e mais propenso a erros se a ordem das colunas no SELECT for alterada.
You can order by calculated expressions or by aliases defined in the SELECT clause.
Em muitas situações, você não quer ver todos os resultados de uma consulta, especialmente quando trabalha
com tabelas grandes. Limitar o número de linhas retornadas pode melhorar significativamente a performance
e a usabilidade. Infelizmente, a sintaxe para limitação varia entre os SGBDs.
SELECT colunas
FROM tabela
ORDER BY coluna
LIMIT numero_de_linhas;
6.6.3 FETCH FIRST (SQL Padrão, Oracle 12c+, PostgreSQL, SQL Server 2012+)
SELECT colunas
FROM tabela
ORDER BY coluna
FETCH FIRST numero_de_linhas ROWS ONLY;
SELECT colunas
FROM (
SELECT colunas
FROM tabela
ORDER BY coluna
)
WHERE ROWNUM <= numero_de_linhas;
Para implementar paginação (dividir resultados em páginas), você precisa combinar limitação com
deslocamento (offset). Novamente, a sintaxe varia entre SGBDs.
SELECT colunas
FROM tabela
ORDER BY coluna
LIMIT numero_de_linhas OFFSET linhas_para_pular;
Exemplo: Página 2 de produtos (5 produtos por página, então pular os primeiros 5).
SELECT colunas
FROM tabela
ORDER BY coluna
OFFSET linhas_para_pular ROWS
FETCH NEXT numero_de_linhas ROWS ONLY;
Índices: Colunas frequentemente usadas em ORDER BY devem ter índices para melhorar a performance.
Memória: Operações de ordenação podem consumir muita memória. O SGBD pode usar arquivos
temporários em disco se os dados não couberem na memória.
Combinação com WHERE: Use WHERE para filtrar dados antes da ordenação, reduzindo o volume de
dados a serem ordenados.
Limitação: Use LIMIT ou equivalente quando você não precisa de todos os resultados.
Ordenação por Múltiplas Colunas: Permite ordenação hierárquica usando várias colunas.
Ordenação por Expressões e Aliases: Flexibilidade para ordenar por valores calculados.
Limitação de Resultados: LIMIT , TOP , FETCH FIRST para controlar o número de linhas retornadas
(sintaxe varia por SGBD).
1. Ordenação Básica:
Ordene os produtos primeiro por preço (mais caro primeiro) e depois por nome (alfabeticamente)
para produtos com o mesmo preço.
Ordene as vendas primeiro por ClienteID e depois por DataVenda (mais antiga primeiro).
3. Limitação de Resultados:
Ordene os produtos pelo valor total em estoque (preço × estoque) do maior para o menor.
Crie uma consulta que mostre o nome completo dos clientes (nome + sobrenome) e ordene por
esse nome completo.
5. Desafio - Paginação:
Implemente uma consulta que simule a "segunda página" de produtos, assumindo 4 produtos por
página, ordenados por preço (mais barato primeiro). Use a sintaxe apropriada para o seu SGBD.
Referências
Até agora, nossas consultas SELECT retornaram linhas individuais de dados. No entanto, muitas vezes
precisamos de resumos ou estatísticas sobre grupos de dados, em vez de detalhes linha a linha. É aqui que as
funções de agregação (também conhecidas como funções de grupo) se tornam indispensáveis. Elas operam
em um conjunto de linhas e retornam um único valor de resumo para esse conjunto [1].
Tomada de Decisão: Obter uma visão consolidada dos dados para identificar tendências e padrões.
Neste capítulo, exploraremos as funções de agregação mais comuns em SQL: COUNT() , SUM() , AVG() ,
MIN() e MAX() . No próximo capítulo, veremos como combiná-las com a cláusula GROUP BY para realizar
agregações em subconjuntos específicos de dados.
A função COUNT() é usada para contar o número de linhas em uma tabela ou o número de valores não nulos
em uma coluna específica.
COUNT(*) retorna o número total de linhas em uma tabela, incluindo aquelas com valores NULL em qualquer
coluna.
SELECT COUNT(*)
FROM nome_da_tabela;
SELECT COUNT(*)
FROM Produtos;
SELECT COUNT(Descricao)
FROM Produtos;
Exemplo: Contar quantos produtos têm uma descrição (ou seja, a coluna Descricao não é NULL ).
SELECT COUNT(Descricao)
FROM Produtos;
COUNT(DISTINCT coluna) retorna o número de valores únicos e não nulos em uma coluna específica.
A função SUM() calcula a soma total dos valores em uma coluna numérica. Ela ignora valores NULL .
SELECT SUM(coluna_numerica)
FROM nome_da_tabela;
SELECT SUM(Estoque)
FROM Produtos;
A função AVG() calcula a média (valor aritmético) dos valores em uma coluna numérica. Ela ignora valores
NULL .
SELECT AVG(coluna_numerica)
FROM nome_da_tabela;
SELECT AVG(Preco)
FROM Produtos;
As funções MIN() e MAX() retornam o menor e o maior valor, respectivamente, de uma coluna. Elas podem
ser usadas com colunas numéricas, de texto ou de data. Ambas ignoram valores NULL .
SELECT MIN(coluna)
FROM nome_da_tabela;
SELECT MIN(Preco)
FROM Produtos;
SELECT MIN(DataVenda)
FROM Vendas;
SELECT MAX(coluna)
FROM nome_da_tabela;
SELECT MAX(Preco)
FROM Produtos;
SELECT MAX(DataVenda)
FROM Vendas;
7.6 Combinando Funções de Agregação com WHERE
As funções de agregação podem ser combinadas com a cláusula WHERE para calcular resumos apenas para
um subconjunto de dados. A cláusula WHERE é aplicada antes da agregação.
SELECT COUNT(*)
FROM Produtos
WHERE Preco > 1000.00;
Exemplo: Calcular o estoque total de produtos que contêm a palavra \'Smart\' no nome.
SELECT SUM(Estoque)
FROM Produtos
WHERE NomeProduto LIKE \'%Smart%\';
Funções de Agregação: Operam em um conjunto de linhas para retornar um único valor de resumo.
1. Contagem:
2. Soma e Média:
Qual o Preco médio dos produtos que contêm a palavra \'Smartphone\' no NomeProduto ?
5. Desafio:
Encontre o ProdutoID do produto mais caro e do produto mais barato. (Dica: Você precisará de
uma subconsulta ou de uma combinação de funções).
Referências
No capítulo anterior, aprendemos sobre funções de agregação que calculam valores de resumo para toda
uma tabela. No entanto, frequentemente precisamos calcular esses resumos para subgrupos específicos de
dados. Por exemplo, em vez de calcular o faturamento total de todas as vendas, podemos querer calcular o
faturamento por cliente ou por produto. É aqui que a cláusula GROUP BY se torna essencial [1].
A cláusula GROUP BY divide as linhas de uma tabela em grupos com base nos valores de uma ou mais
colunas. As funções de agregação são então aplicadas a cada grupo individualmente, retornando um
resultado para cada grupo. Isso nos permite realizar análises mais granulares e obter insights mais específicos
sobre nossos dados.
Além disso, a cláusula HAVING permite filtrar grupos após a agregação, similar ao que a cláusula WHERE faz
para linhas individuais antes da agregação.
8.2 GROUP BY: Agrupando Dados
Este comando agrupa todas as vendas por ClienteID e conta quantas vendas existem para cada cliente.
Você pode agrupar por múltiplas colunas, criando grupos mais específicos.
Este comando cria um grupo para cada combinação única de ClienteID e ProdutoID , calculando o
faturamento para cada combinação.
1. Colunas no SELECT: Todas as colunas não agregadas no SELECT devem aparecer na cláusula GROUP
BY .
2. Funções de Agregação: Você pode usar funções de agregação ( COUNT , SUM , AVG , MIN , MAX ) no
SELECT sem incluí-las no GROUP BY .
Exemplo Correto:
A cláusula WHERE é aplicada antes do agrupamento, filtrando as linhas que serão incluídas nos grupos.
Exemplo: Contar vendas por cliente, mas apenas para vendas realizadas em 2025.
A cláusula HAVING é usada para filtrar grupos após a agregação. É similar à cláusula WHERE , mas opera em
grupos em vez de linhas individuais. Você só pode usar HAVING com colunas que aparecem no GROUP BY ou
com funções de agregação.
-- Contar vendas por cliente, mas apenas vendas com quantidade > 1,
-- e mostrar apenas clientes com mais de 1 venda
SELECT ClienteID, COUNT(*) AS VendasComQuantidadeMaiorQue1
FROM Vendas
WHERE Quantidade > 1 -- Filtra linhas ANTES do agrupamento
GROUP BY ClienteID
HAVING COUNT(*) > 1; -- Filtra grupos APÓS o agrupamento
Você pode usar ORDER BY com consultas que incluem GROUP BY para ordenar os grupos resultantes.
Exemplo: Mostrar o faturamento por cliente, ordenado do maior para o menor faturamento.
Supondo que você queira analisar as vendas por mês (assumindo que DataVenda é do tipo DATE ):
-- SQL Server
SELECT
YEAR(DataVenda) AS Ano,
MONTH(DataVenda) AS Mes,
COUNT(*) AS NumeroDeVendas,
SUM(Quantidade * PrecoUnitario) AS FaturamentoMensal
FROM Vendas
GROUP BY YEAR(DataVenda), MONTH(DataVenda)
ORDER BY Ano, Mes;
-- MySQL
SELECT
YEAR(DataVenda) AS Ano,
MONTH(DataVenda) AS Mes,
COUNT(*) AS NumeroDeVendas,
SUM(Quantidade * PrecoUnitario) AS FaturamentoMensal
FROM Vendas
GROUP BY YEAR(DataVenda), MONTH(DataVenda)
ORDER BY Ano, Mes;
-- PostgreSQL
SELECT
EXTRACT(YEAR FROM DataVenda) AS Ano,
EXTRACT(MONTH FROM DataVenda) AS Mes,
COUNT(*) AS NumeroDeVendas,
SUM(Quantidade * PrecoUnitario) AS FaturamentoMensal
FROM Vendas
GROUP BY EXTRACT(YEAR FROM DataVenda), EXTRACT(MONTH FROM DataVenda)
ORDER BY Ano, Mes;
SELECT
ProdutoID,
SUM(Quantidade) AS QuantidadeTotal,
COUNT(*) AS NumeroDeVendas
FROM Vendas
GROUP BY ProdutoID
ORDER BY QuantidadeTotal DESC
LIMIT 5; -- MySQL/PostgreSQL
-- ou TOP 5 para SQL Server
-- ou FETCH FIRST 5 ROWS ONLY para SQL padrão
SELECT
ClienteID,
SUM(Quantidade * PrecoUnitario) AS FaturamentoCliente
FROM Vendas
GROUP BY ClienteID
HAVING SUM(Quantidade * PrecoUnitario) > (
SELECT AVG(FaturamentoPorCliente)
FROM (
SELECT SUM(Quantidade * PrecoUnitario) AS FaturamentoPorCliente
FROM Vendas
GROUP BY ClienteID
) AS SubConsulta
);
GROUP BY : Divide linhas em grupos com base nos valores de uma ou mais colunas.
Regra do GROUP BY: Todas as colunas não agregadas no SELECT devem aparecer no GROUP BY .
Agrupamento por Múltiplas Colunas: Cria grupos mais específicos usando combinações de valores.
WHERE vs HAVING : WHERE filtra linhas antes do agrupamento; HAVING filtra grupos após a agregação.
1. Agrupamento Básico:
Conte quantas vendas foram realizadas por mês (assumindo que você tem dados de diferentes
meses).
3. Usando HAVING:
Liste produtos cuja quantidade total vendida foi maior que 10 unidades.
Para vendas realizadas em 2025, mostre apenas clientes que fizeram mais de 1 compra.
5. Desafio:
Crie uma consulta que mostre o top 3 clientes por faturamento, incluindo o nome do cliente (você
precisará fazer um JOIN com a tabela Clientes ).
Calcule a média de faturamento por cliente e mostre apenas clientes cujo faturamento individual
está acima dessa média.
Referências
Até agora, todas as nossas consultas operaram em uma única tabela por vez. No entanto, em bancos de dados
relacionais bem projetados, as informações são normalizadas e distribuídas em múltiplas tabelas para evitar
redundância e manter a integridade dos dados. Para extrair informações significativas, frequentemente
precisamos combinar dados de duas ou mais tabelas relacionadas. É aqui que os JOINs se tornam
fundamentais [1].
Considere nosso exemplo de loja eletrônica: a tabela Vendas contém ClienteID e ProdutoID , mas não os
nomes dos clientes ou produtos. Para gerar um relatório de vendas legível, precisamos "unir" a tabela
Vendas com as tabelas Clientes e Produtos para obter essas informações descritivas.
Os JOINs permitem combinar linhas de duas ou mais tabelas com base em uma condição relacionada entre
elas, geralmente envolvendo chaves primárias e estrangeiras.
SELECT colunas
FROM tabela1
JOIN tabela2 ON [Link] = [Link];
[Link] = [Link] : A condição que define como as linhas das duas tabelas devem ser
correspondidas.
O INNER JOIN (ou simplesmente JOIN ) retorna apenas as linhas que têm correspondências em ambas as
tabelas. Se uma linha em uma tabela não tiver uma correspondência na outra tabela, ela não aparecerá no
resultado.
SELECT colunas
FROM tabela1
INNER JOIN tabela2 ON [Link] = [Link];
Vamos unir as tabelas Vendas e Clientes para mostrar o nome do cliente em cada venda:
SELECT
[Link],
[Link],
[Link],
[Link],
[Link],
[Link]
FROM Vendas V
INNER JOIN Clientes C ON [Link] = [Link];
Neste exemplo: * V e C são aliases para as tabelas Vendas e Clientes , respectivamente. * A condição
[Link] = [Link] especifica como as tabelas estão relacionadas. * Apenas vendas que têm um
cliente correspondente na tabela Clientes serão retornadas.
SELECT
[Link],
[Link] AS NomeCliente,
[Link],
[Link],
[Link],
[Link],
[Link] * [Link] AS ValorTotal
FROM Vendas V
INNER JOIN Clientes C ON [Link] = [Link]
INNER JOIN Produtos P ON [Link] = [Link];
Esta consulta une três tabelas para criar um relatório completo de vendas com nomes de clientes e produtos.
O LEFT JOIN retorna todas as linhas da tabela esquerda (primeira tabela mencionada) e as linhas
correspondentes da tabela direita. Se não houver correspondência, os valores da tabela direita serão NULL .
SELECT colunas
FROM tabela1
LEFT JOIN tabela2 ON [Link] = [Link];
Vamos listar todos os clientes e suas vendas, incluindo clientes que nunca fizeram uma compra:
SELECT
[Link],
[Link],
[Link],
[Link],
[Link]
FROM Clientes C
LEFT JOIN Vendas V ON [Link] = [Link]
ORDER BY [Link];
Neste exemplo: * Todos os clientes da tabela Clientes serão listados. * Para clientes que fizeram compras, as
informações de venda serão exibidas. * Para clientes que nunca compraram, VendaID e DataVenda serão
NULL .
O LEFT JOIN é útil para encontrar registros que não têm correspondência:
O RIGHT JOIN é o oposto do LEFT JOIN : retorna todas as linhas da tabela direita e as linhas correspondentes
da tabela esquerda. Se não houver correspondência, os valores da tabela esquerda serão NULL .
SELECT colunas
FROM tabela1
RIGHT JOIN tabela2 ON [Link] = [Link];
Nota: RIGHT JOIN é menos comum que LEFT JOIN porque você pode sempre reescrever um RIGHT JOIN
como um LEFT JOIN simplesmente trocando a ordem das tabelas.
O FULL JOIN retorna todas as linhas quando há uma correspondência em qualquer uma das tabelas. Isso
significa que retorna todas as linhas da tabela esquerda e todas as linhas da tabela direita, preenchendo com
NULL onde não há correspondência.
SELECT colunas
FROM tabela1
FULL OUTER JOIN tabela2 ON [Link] = [Link];
Nota: Nem todos os SGBDs suportam FULL OUTER JOIN (por exemplo, MySQL não suporta nativamente).
Como o MySQL não suporta FULL OUTER JOIN , você pode simulá-lo usando UNION de LEFT JOIN e RIGHT
JOIN :
SELECT
[Link],
[Link],
[Link],
[Link]
FROM Clientes C
LEFT JOIN Vendas V ON [Link] = [Link]
UNION
SELECT
[Link],
[Link],
[Link],
[Link]
FROM Clientes C
RIGHT JOIN Vendas V ON [Link] = [Link]
WHERE [Link] IS NULL;
Um SELF JOIN é uma junção regular, mas a tabela é unida consigo mesma. É útil quando você tem dados
hierárquicos ou relacionamentos dentro da mesma tabela.
Suponha que temos uma tabela Funcionarios com uma coluna GerenteID que referencia o FuncionarioID
do gerente:
Embora a maioria dos JOINs use igualdade simples, você pode usar condições mais complexas:
SELECT
[Link],
[Link],
[Link]
FROM Vendas V
INNER JOIN Clientes C ON [Link] = [Link]
INNER JOIN Produtos P ON [Link] = [Link]
AND [Link] > 1000.00; -- Condição adicional no JOIN
JOINs podem ser operações custosas, especialmente em tabelas grandes. Algumas dicas para otimização:
Índices: Certifique-se de que as colunas usadas nas condições de JOIN tenham índices.
Ordem das Tabelas: Em alguns casos, a ordem das tabelas no JOIN pode afetar a performance.
Filtragem: Use WHERE para filtrar dados antes do JOIN quando possível.
Seletividade: Comece com a tabela mais seletiva (que retorna menos linhas).
LEFT JOIN : Retorna todas as linhas da tabela esquerda, com NULL para não correspondências.
RIGHT JOIN : Retorna todas as linhas da tabela direita, com NULL para não correspondências.
FULL OUTER JOIN : Retorna todas as linhas de ambas as tabelas (nem todos os SGBDs suportam).
SELF JOIN : Une uma tabela consigo mesma, útil para dados hierárquicos.
Condições de JOIN: Geralmente baseadas em chaves primárias e estrangeiras, mas podem ser mais
complexas.
2. LEFT JOINs:
Liste todos os clientes e suas vendas, incluindo clientes que nunca compraram.
Crie um relatório completo de vendas mostrando: ID da venda, data, nome completo do cliente,
nome do produto, quantidade, preço unitário e valor total.
5. Desafio:
Crie uma consulta que mostre o produto mais vendido (em quantidade) para cada cliente.
Liste os clientes que compraram produtos com preço superior a R$ 1500,00, mostrando o nome do
cliente e o nome do produto.
Referências
Uma subconsulta (também conhecida como subquery ou consulta aninhada) é uma consulta SQL que está
aninhada dentro de outra consulta SQL. A subconsulta é executada primeiro, e seu resultado é usado pela
consulta externa (principal). As subconsultas são uma ferramenta poderosa que permite resolver problemas
complexos de forma elegante e são fundamentais para dominar SQL avançado [1].
As subconsultas podem aparecer em várias partes de uma instrução SQL: * Na cláusula SELECT (subconsultas
escalares) * Na cláusula FROM (tabelas derivadas) * Na cláusula WHERE (condições baseadas em outras
consultas) * Na cláusula HAVING (condições de grupo baseadas em outras consultas)
Elas são especialmente úteis quando você precisa: * Comparar valores com resultados de outras consultas *
Filtrar dados baseados em cálculos complexos * Realizar operações que requerem múltiplos passos lógicos *
Evitar JOINs complexos em certas situações
Uma subconsulta escalar retorna exatamente um valor (uma linha e uma coluna). Pode ser usada em
qualquer lugar onde um valor único é esperado.
Exemplo: Mostrar cada produto com a diferença entre seu preço e o preço médio de todos os produtos.
SELECT
NomeProduto,
Preco,
(SELECT AVG(Preco) FROM Produtos) AS PrecoMedio,
Preco - (SELECT AVG(Preco) FROM Produtos) AS DiferencaDoPrecoMedio
FROM Produtos;
Essas subconsultas retornam múltiplas linhas e são frequentemente usadas com operadores como IN , ANY ,
ALL , EXISTS .
SELECT colunas
FROM tabela1
WHERE coluna IN (SELECT coluna FROM tabela2 WHERE condicao);
SELECT
ClienteID,
Nome,
(SELECT COUNT(*) FROM Vendas WHERE [Link] = [Link]) AS NumeroDeCompras
FROM Clientes;
Esta é uma subconsulta correlacionada que conta quantas compras cada cliente fez.
SELECT
ProdutoID,
NomeProduto,
Preco,
(SELECT MAX(Preco) FROM Produtos) AS PrecoMaximo,
ROUND((Preco / (SELECT MAX(Preco) FROM Produtos)) * 100, 2) AS PercentualDoMaximo
FROM Produtos;
Uma subconsulta na cláusula FROM cria uma "tabela temporária" que pode ser usada como qualquer outra
tabela na consulta principal.
SELECT colunas
FROM (SELECT colunas FROM tabela WHERE condicao) AS alias_tabela
WHERE outras_condicoes;
SELECT
ClienteID,
FaturamentoTotal
FROM (
SELECT
ClienteID,
SUM(Quantidade * PrecoUnitario) AS FaturamentoTotal
FROM Vendas
GROUP BY ClienteID
) AS FaturamentoPorCliente
WHERE FaturamentoTotal > 5000;
Esta consulta primeiro calcula o faturamento por cliente e depois filtra apenas aqueles com faturamento
superior a R$ 5000.
10.4.2 Vantagens das Tabelas Derivadas
Esta é uma das aplicações mais comuns de subconsultas, permitindo filtrar dados baseados em resultados de
outras consultas.
O operador IN verifica se um valor está presente em uma lista de valores retornados pela subconsulta.
SELECT colunas
FROM tabela1
WHERE coluna IN (SELECT coluna FROM tabela2 WHERE condicao);
Exemplo: Encontrar clientes que compraram produtos com preço superior a R$ 1500.
Importante: Cuidado com valores NULL ao usar NOT IN . Se a subconsulta retornar algum valor NULL , NOT
IN pode não funcionar como esperado.
O operador EXISTS verifica se a subconsulta retorna pelo menos uma linha. É frequentemente mais eficiente
que IN para subconsultas correlacionadas.
SELECT colunas
FROM tabela1
WHERE EXISTS (SELECT 1 FROM tabela2 WHERE [Link] = [Link]);
ANY (ou SOME ): A condição é verdadeira se for verdadeira para qualquer valor retornado pela
subconsulta.
ALL : A condição é verdadeira se for verdadeira para todos os valores retornados pela subconsulta.
Uma subconsulta correlacionada referencia colunas da consulta externa. Ela é executada uma vez para cada
linha da consulta externa.
10.6.1 Exemplo de Subconsulta Correlacionada
-- Produtos cujo preço está acima da média da sua categoria (assumindo uma coluna Categoria)
SELECT ProdutoID, NomeProduto, Preco, Categoria
FROM Produtos P1
WHERE Preco > (
SELECT AVG(Preco)
FROM Produtos P2
WHERE [Link] = [Link]
);
Muitas vezes, você pode resolver o mesmo problema usando subconsultas ou JOINs. A escolha depende de
fatores como performance, legibilidade e preferência pessoal.
Usando Subconsulta:
Usando JOIN:
Use Subconsultas quando: * A lógica é mais clara e legível * Você precisa de cálculos escalares * Você quer
evitar duplicatas sem usar DISTINCT
Use JOINs quando: * Você precisa de colunas de múltiplas tabelas * A performance é melhor (teste sempre) *
A consulta é mais simples de entender
1. Subconsultas Escalares:
Para cada produto, mostre seu preço e a diferença entre seu preço e o preço médio de todos os
produtos.
Liste produtos que foram vendidos por um preço diferente do seu preço atual.
4. Tabelas Derivadas:
Crie uma consulta que mostre apenas clientes cujo faturamento total está acima da média de
faturamento de todos os clientes.
5. Desafio:
Para cada cliente, encontre o produto mais caro que ele comprou.
Liste produtos cujo preço está acima da média de preços dos produtos da mesma categoria (você
pode assumir uma coluna Categoria ou criar uma baseada no nome do produto).
Referências
Uma View (Visão) em SQL é uma tabela virtual baseada no conjunto de resultados de uma consulta SQL. Uma
View contém linhas e colunas, assim como uma tabela real. Os campos em uma View são campos de uma ou
mais tabelas reais no banco de dados. Você pode adicionar funções SQL, cláusulas WHERE e JOINs a uma
View e apresentar os dados como se fossem de uma única tabela [1].
As Views não armazenam dados fisicamente; elas são consultas armazenadas que são executadas toda vez
que a View é referenciada. Isso significa que os dados que você vê através de uma View são sempre
atualizados, refletindo as mudanças nas tabelas base.
Simplificação de Consultas Complexas: Uma View pode encapsular uma consulta complexa (com
JOINs, subconsultas, funções de agregação) e apresentá-la como uma tabela simples. Isso facilita o
trabalho de usuários e aplicações que precisam acessar esses dados sem entender a complexidade da
consulta subjacente.
Segurança: Você pode conceder permissões de acesso a uma View em vez de conceder acesso direto às
tabelas base. Isso permite que você restrinja quais colunas e linhas um usuário pode ver, protegendo
dados sensíveis.
Consistência de Dados: Ao usar Views, você garante que todos os usuários e aplicações que acessam os
dados através da View vejam os dados da mesma forma, aplicando as mesmas regras de negócio e
cálculos.
Abstração de Dados: Se a estrutura das tabelas base mudar (por exemplo, uma coluna é renomeada),
você pode ajustar a definição da View sem precisar modificar as aplicações que a utilizam, desde que a
View continue a apresentar os dados na mesma estrutura esperada.
Reusabilidade: Uma vez criada, uma View pode ser reutilizada em várias consultas, relatórios e
aplicações.
SELECT ... : A consulta SQL que define os dados que a View irá expor.
Vamos criar uma View que mostra o nome completo dos clientes e seus e-mails:
CREATE VIEW ClientesCompletos AS
SELECT
ClienteID,
Nome || \' \' || Sobrenome AS NomeCompleto, -- Use CONCAT() ou + dependendo do SGBD
Email
FROM Clientes;
Agora, você pode consultar esta View como se fosse uma tabela:
Views são frequentemente usadas para simplificar consultas que envolvem JOINs.
Exemplo: Criar uma View para ver os detalhes de cada venda, incluindo o nome do cliente e o nome do
produto.
Você também pode criar Views que contêm resultados de funções de agregação.
Exemplo: Criar uma View que mostra o faturamento total por cliente.
Consulta à View:
Para modificar uma View existente, você usa o comando ALTER VIEW . A sintaxe é similar à do CREATE VIEW ,
mas você está redefinindo a consulta subjacente.
Para remover uma View, use o comando DROP VIEW . Isso remove a definição da View, mas não afeta as
tabelas base subjacentes ou os dados nelas contidos.
Exemplo:
Uma View pode ser usada não apenas para consultar dados, mas também para inserir, atualizar e excluir
dados, desde que certas condições sejam atendidas. Uma View que permite operações DML é chamada de
View atualizável (ou updatable view).
As regras exatas podem variar ligeiramente entre os SGBDs, mas, em geral, uma View é atualizável se:
Se uma View não atender a essas condições, ela será uma View somente leitura (read-only view) e você só
poderá usá-la para consultas SELECT .
UPDATE ProdutosSimples
SET Preco = 370.00
WHERE ProdutoID = 4;
Essas operações DML na View ProdutosSimples serão refletidas diretamente na tabela base Produtos .
Views (Visões): Tabelas virtuais baseadas em consultas SQL, não armazenam dados fisicamente.
CREATE VIEW : Cria uma nova View a partir de uma consulta SELECT .
Views Atualizáveis: Permitem operações DML ( INSERT , UPDATE , DELETE ) se atenderem a certas
condições (geralmente baseadas em uma única tabela, sem agregações, etc.).
Views Somente Leitura: A maioria das Views complexas são somente leitura.
Crie uma View chamada ProdutosEmEstoque que mostre ProdutoID , NomeProduto e Estoque
para produtos com Estoque maior que 0.
Crie uma View chamada ClientesAtivos que mostre ClienteID , Nome e Email para clientes
que fizeram pelo menos uma compra (você pode usar um INNER JOIN com a tabela Vendas ).
2. Consulta a Views:
Consulte a View ProdutosEmEstoque para encontrar produtos com Estoque entre 10 e 50.
Consulte a View ClientesAtivos para encontrar clientes cujo nome começa com \'M\'.
Crie uma View DadosBasicosClientes que inclua ClienteID , Nome e Sobrenome da tabela
Clientes .
Tente inserir um novo cliente através desta View. Explique o que acontece.
Tente atualizar o Nome de um cliente existente através desta View. Explique o que acontece.
Referências
Um índice em um banco de dados é uma estrutura de dados que melhora a velocidade das operações de
consulta em uma tabela. Funciona de forma similar ao índice de um livro: em vez de ler o livro inteiro para
encontrar um tópico específico, você consulta o índice para encontrar rapidamente a página onde o tópico
está localizado. Da mesma forma, um índice de banco de dados permite que o Sistema de Gerenciamento de
Banco de Dados (SGBD) encontre rapidamente as linhas que atendem a uma condição específica, sem ter que
examinar cada linha da tabela [1].
Sem índices, o SGBD precisa realizar uma varredura completa da tabela (table scan) para encontrar os dados
solicitados. Em tabelas pequenas, isso pode não ser um problema, mas em tabelas com milhões ou bilhões de
linhas, uma varredura completa pode levar minutos ou até horas. Com índices apropriados, a mesma consulta
pode ser executada em milissegundos.
Integridade de Dados: Índices únicos garantem que não haja valores duplicados em colunas
específicas.
Ordenação Rápida: Dados já indexados podem ser retornados em ordem sem necessidade de
ordenação adicional.
No entanto, índices também têm custos: eles ocupam espaço adicional de armazenamento e podem tornar as
operações de inserção, atualização e exclusão mais lentas, pois o SGBD precisa manter os índices atualizados
sempre que os dados mudam.
Para entender como os índices funcionam, imagine uma tabela Clientes com 1 milhão de registros. Se você
quiser encontrar todos os clientes com o sobrenome "Silva", sem um índice, o SGBD teria que examinar cada
uma das 1 milhão de linhas para verificar o sobrenome. Isso é uma operação O(n), onde n é o número de
linhas.
Com um índice na coluna Sobrenome , o SGBD mantém uma estrutura de dados separada (geralmente uma
árvore B ou B+) que mapeia cada valor de sobrenome para as linhas correspondentes na tabela. Quando você
busca por "Silva", o SGBD pode usar o índice para ir diretamente às linhas relevantes, transformando a
operação em O(log n), que é exponencialmente mais rápida.
A maioria dos SGBDs usa árvores B+ para implementar índices, mas existem outras estruturas:
Árvore B+: A estrutura mais comum, eficiente para consultas de intervalo e ordenação.
Hash: Muito rápido para consultas de igualdade exata, mas não suporta consultas de intervalo.
Bitmap: Eficiente para colunas com poucos valores distintos (baixa cardinalidade).
Um índice clustered determina a ordem física de armazenamento dos dados na tabela. Uma tabela pode ter
apenas um índice clustered porque os dados só podem ser fisicamente ordenados de uma maneira. Quando
você cria uma chave primária, muitos SGBDs automaticamente criam um índice clustered nessa chave.
Características: * Os dados da tabela são armazenados na ordem do índice clustered. * Consultas que usam o
índice clustered são extremamente rápidas. * Inserções podem ser mais lentas se não forem feitas em ordem.
Um índice non-clustered é uma estrutura separada que aponta para as linhas de dados na tabela. Uma tabela
pode ter múltiplos índices non-clustered. Eles são como o índice de um livro: fornecem uma referência rápida
para onde encontrar a informação, mas não alteram a ordem física dos dados.
Características: * Não afetam a ordem física de armazenamento dos dados. * Uma tabela pode ter múltiplos
índices non-clustered. * Requerem espaço adicional de armazenamento.
Exemplo:
Um índice único garante que não haja valores duplicados na coluna ou combinação de colunas indexadas.
Chaves primárias automaticamente têm índices únicos.
Exemplo:
Um índice composto é criado em múltiplas colunas. A ordem das colunas no índice é importante e afeta sua
eficácia para diferentes tipos de consultas.
Exemplo:
Este índice será eficaz para consultas que filtram por ClienteID , ou por ClienteID e DataVenda , mas não
será tão eficaz para consultas que filtram apenas por DataVenda .
Alguns SGBDs permitem criar índices que incluem apenas um subconjunto das linhas da tabela, baseado em
uma condição.
Exemplo (SQL Server):
Colunas Frequentemente Usadas em WHERE: Se você frequentemente filtra por uma coluna
específica.
Colunas com Alta Seletividade: Colunas com muitos valores únicos se beneficiam mais de índices.
12.5.2 Situações Onde Índices Podem Não Ser Benéficos
Colunas com Baixa Seletividade: Colunas com poucos valores únicos (como sexo: M/F).
Tabelas com Muitas Operações DML: Se a tabela é frequentemente atualizada, muitos índices podem
prejudicar a performance.
Colunas Raramente Consultadas: Não faz sentido indexar colunas que nunca são usadas em consultas.
INSERT:
UPDATE:
DELETE:
A maioria dos SGBDs fornece ferramentas para visualizar planos de execução, que mostram como uma
consulta é executada e quais índices são usados.
SQL Server:
SET SHOWPLAN_ALL ON
SELECT * FROM Clientes WHERE Sobrenome = \'Silva\';
PostgreSQL:
EXPLAIN ANALYZE
SELECT * FROM Clientes WHERE Sobrenome = \'Silva\';
MySQL:
EXPLAIN
SELECT * FROM Clientes WHERE Sobrenome = \'Silva\';
Índices que não são usados consomem espaço e prejudicam a performance de DML sem fornecer benefícios.
A maioria dos SGBDs fornece estatísticas de uso de índices.
SQL Server:
SELECT
[Link] AS IndexName,
s.user_seeks,
s.user_scans,
s.user_lookups,
s.user_updates
FROM [Link] i
LEFT JOIN sys.dm_db_index_usage_stats s ON i.object_id = s.object_id AND i.index_id = s.index_id
WHERE i.object_id = OBJECT_ID(\'Clientes\');
Exemplo:
Nota: Você não pode remover índices criados automaticamente para chaves primárias ou restrições únicas
sem primeiro remover a restrição.
Analise Consultas Reais: Crie índices baseados em consultas que realmente são executadas, não em
suposições.
Monitore Performance: Use ferramentas de monitoramento para identificar consultas lentas.
Considere Índices Compostos: Para consultas que filtram por múltiplas colunas.
Ordem das Colunas: Em índices compostos, coloque as colunas mais seletivas primeiro.
Remova Índices Não Utilizados: Monitore e remova índices que não são usados.
Considere o Custo de Manutenção: Cada índice adicional aumenta o tempo de INSERT, UPDATE e
DELETE.
Índices: Estruturas de dados que aceleram consultas ao criar \"atalhos\" para os dados.
Melhores Práticas: Indexe baseado em consultas reais, monitore performance, evite sobre-indexação.
2. Análise de Performance:
Execute uma consulta que busca clientes por sobrenome antes e depois de criar um índice.
Compare os tempos de execução.
Use o comando EXPLAIN (ou equivalente no seu SGBD) para ver o plano de execução de uma
consulta com e sem índice.
Identifique todas as chaves estrangeiras nas suas tabelas e certifique-se de que elas têm índices.
Execute uma consulta com JOIN entre Vendas e Produtos e analise se os índices estão sendo
utilizados.
4. Gerenciamento de Índices:
Crie um índice desnecessário (por exemplo, em uma coluna que nunca é consultada).
5. Desafio:
Analise uma consulta complexa que envolve WHERE, JOIN e ORDER BY.
Referências
Em um ambiente de banco de dados, uma transação é uma sequência lógica de uma ou mais operações que
são executadas como uma única unidade de trabalho. O principal objetivo de uma transação é garantir a
integridade e a consistência dos dados, mesmo em caso de falhas do sistema ou acessos concorrentes. Uma
transação deve ser tratada como uma operação atômica: ou todas as suas operações são concluídas com
sucesso (commit), ou nenhuma delas é (rollback) [1].
Imagine uma transferência bancária: envolve a retirada de dinheiro de uma conta e o depósito na outra. Se a
retirada for bem-sucedida, mas o depósito falhar (por exemplo, devido a uma queda de energia), o dinheiro
simplesmente desapareceria. Uma transação garante que ambas as operações (retirada e depósito) sejam
tratadas como uma única unidade. Se uma falhar, a outra também é desfeita, deixando o banco de dados em
um estado consistente.
Para garantir a confiabilidade dos dados, as transações em sistemas de banco de dados relacionais devem
aderir às propriedades ACID:
Atomicidade (Atomicity): Uma transação é uma unidade indivisível de trabalho. Ou todas as suas
operações são concluídas com sucesso (commit), ou nenhuma delas é (rollback). Não há estados
intermediários.
Consistência (Consistency): Uma transação deve levar o banco de dados de um estado consistente para
outro estado consistente. Isso significa que a transação deve obedecer a todas as regras e restrições de
integridade definidas no banco de dados (como chaves primárias, chaves estrangeiras, CHECK
constraints).
Isolamento (Isolation): Transações concorrentes devem ser executadas de forma isolada umas das
outras. O efeito de múltiplas transações executando simultaneamente deve ser o mesmo que se elas
tivessem sido executadas sequencialmente. Isso evita que uma transação veja os dados em um estado
inconsistente devido a outra transação em andamento.
Durabilidade (Durability): Uma vez que uma transação é confirmada (commit), suas alterações são
permanentes e sobreviverão a quaisquer falhas subsequentes do sistema (como quedas de energia ou
reinicializações).
COMMIT : Confirma as alterações feitas por uma transação, tornando-as permanentes no banco de
dados. Uma vez que uma transação é confirmada, ela não pode ser desfeita por um ROLLBACK .
ROLLBACK : Desfaz todas as alterações feitas por uma transação desde o último COMMIT ou SAVEPOINT ,
restaurando o banco de dados ao seu estado anterior. É útil para desfazer erros ou quando uma
transação não pode ser concluída com sucesso.
SAVEPOINT : Define um ponto dentro de uma transação para o qual você pode reverter. Isso permite que
você desfaça apenas parte de uma transação, em vez de toda ela.
-- Iniciar uma transação (sintaxe pode variar: BEGIN TRANSACTION, START TRANSACTION)
BEGIN TRANSACTION;
BEGIN TRANSACTION;
SAVEPOINT ClienteInserido;
-- Oops, percebi um erro na inserção do produto, quero desfazer apenas esta parte
ROLLBACK TO ClienteInserido;
-- O cliente Fernando Lima ainda estará na tabela, mas o Fone de Ouvido não.
COMMIT;
Leitura Suja (Dirty Read): Uma transação lê dados que foram modificados por outra transação que
ainda não foi confirmada. Se a segunda transação for desfeita, a primeira transação terá lido dados
\"sujos\" que nunca existiram de fato.
Leitura Não Repetível (Non-Repeatable Read): Uma transação lê os mesmos dados duas vezes e
obtém resultados diferentes porque outra transação modificou esses dados e os confirmou entre as
duas leituras.
Leitura Fantasma (Phantom Read): Uma transação executa uma consulta que retorna um conjunto de
linhas. Mais tarde, a mesma transação executa a mesma consulta e obtém um conjunto diferente de
linhas porque outra transação inseriu ou excluiu linhas que satisfazem a condição da consulta.
Para lidar com os problemas de concorrência, o padrão SQL define quatro níveis de isolamento de transação.
Cada nível oferece um grau diferente de proteção contra os problemas de concorrência, com um trade-off
entre consistência e performance. Quanto maior o nível de isolamento, maior a consistência, mas menor a
concorrência (e potencialmente menor a performance).
Descrição: É o nível de isolamento mais baixo. Uma transação pode ler dados que ainda não foram
confirmados por outras transações. Isso pode levar a dados inconsistentes, mas oferece a maior
concorrência.
Uso: Raramente usado, exceto em cenários onde a performance é crítica e pequenas inconsistências são
aceitáveis (ex: contadores em tempo real).
Descrição: Uma transação só pode ler dados que foram confirmados por outras transações. Este é o
nível de isolamento padrão na maioria dos SGBDs (como SQL Server, Oracle, PostgreSQL). Cada
instrução dentro da transação vê apenas os dados que foram confirmados no momento em que a
instrução começou.
Uso: Adequado para a maioria das aplicações, oferecendo um bom equilíbrio entre consistência e
concorrência.
Descrição: Uma transação garante que, se ela ler uma linha, ela poderá lê-la novamente e obter o
mesmo valor até que a transação termine. Isso é alcançado bloqueando as linhas lidas para que outras
transações não possam modificá-las. No entanto, novas linhas que satisfazem a condição da consulta
podem ser inseridas por outras transações, levando a Phantom Reads.
Uso: Para aplicações que precisam de consistência em leituras repetidas dentro da mesma transação.
Descrição: É o nível de isolamento mais alto. Garante que o resultado da execução de transações
concorrentes seja o mesmo que se elas tivessem sido executadas sequencialmente. Isso é geralmente
alcançado bloqueando não apenas as linhas lidas, mas também os intervalos de dados, impedindo
inserções que levariam a Phantom Reads.
Uso: Para aplicações que exigem a mais alta consistência de dados, mas pode impactar
significativamente a concorrência e a performance.
A sintaxe para definir o nível de isolamento pode variar entre os SGBDs. Geralmente, é feito com o comando
SET TRANSACTION ISOLATION LEVEL .
BEGIN TRANSACTION;
-- Suas operações DML aqui
COMMIT;
13.5 Bloqueios (Locks)
Para implementar os níveis de isolamento, os SGBDs utilizam mecanismos de bloqueio (locks). Um bloqueio é
um mecanismo que impede que múltiplas transações acessem os mesmos dados simultaneamente de forma
inconsistente.
Bloqueio Compartilhado (Shared Lock - S): Concedido para operações de leitura. Permite que
múltiplas transações leiam os mesmos dados simultaneamente, mas impede que qualquer transação
modifique esses dados.
Bloqueio Exclusivo (Exclusive Lock - X): Concedido para operações de escrita (INSERT, UPDATE,
DELETE). Impede que qualquer outra transação (leitura ou escrita) acesse os dados bloqueados até que
o bloqueio seja liberado.
Linha (Row-level lock): Bloqueia apenas a linha específica que está sendo acessada. Oferece alta
concorrência.
Página (Page-level lock): Bloqueia uma página inteira de dados (um bloco de dados que contém várias
linhas). Menos concorrência que o bloqueio de linha.
Tabela (Table-level lock): Bloqueia a tabela inteira. Baixa concorrência, mas pode ser eficiente para
operações em massa.
13.5.3 Deadlock
Um deadlock (ou impasse) ocorre quando duas ou mais transações estão esperando indefinidamente por um
recurso que está bloqueado por outra transação no mesmo conjunto de transações. O SGBD detecta
deadlocks e geralmente escolhe uma das transações como \"vítima\" para ser desfeita (rollback), permitindo
que as outras transações continuem.
Exemplo de Deadlock:
Níveis de Isolamento: Read Uncommitted, Read Committed (padrão), Repeatable Read, Serializable.
Cada um oferece um trade-off entre consistência e concorrência.
Bloqueios (Locks): Mecanismos usados pelo SGBD para impor isolamento (Compartilhado para leitura,
Exclusivo para escrita).
Deadlock: Situação onde transações se bloqueiam mutuamente, exigindo que o SGBD desfaça uma
delas.
1. Simulação de Transação:
Crie uma transação que insira um novo cliente e, em seguida, insira uma nova venda para esse
cliente. Se a inserção da venda falhar (por exemplo, por um ProdutoID inválido), garanta que a
inserção do cliente também seja desfeita.
2. Uso de SAVEPOINT:
Crie uma transação que insira 3 produtos. Após a inserção do segundo produto, crie um
SAVEPOINT . Em seguida, insira o terceiro produto. Se você decidir que o terceiro produto não deve
ser inserido, use ROLLBACK TO SAVEPOINT para desfazer apenas essa última inserção.
Pesquise qual é o nível de isolamento padrão do SGBD que você está usando (MySQL, PostgreSQL,
SQL Server, Oracle).
4. Análise de Concorrência:
Em um ambiente de teste, abra duas sessões de banco de dados. Na Sessão 1, inicie uma transação
e atualize o Estoque de um produto, mas não faça COMMIT . Na Sessão 2, tente ler o Estoque do
mesmo produto. O que acontece? (Dependerá do nível de isolamento padrão do seu SGBD).
5. Desafio - Deadlock:
Pesquise um exemplo de como simular um deadlock no seu SGBD. Tente reproduzi-lo e observe
como o SGBD o resolve (qual transação é escolhida como vítima).
Referências
As restrições de integridade (integrity constraints) são regras definidas no nível do banco de dados que
garantem a validade, consistência e qualidade dos dados armazenados. Elas atuam como guardiões dos
dados, impedindo que informações inválidas ou inconsistentes sejam inseridas ou mantidas no banco de
dados. Essas restrições são fundamentais para manter a integridade referencial e de domínio, assegurando
que os dados atendam às regras de negócio e aos requisitos da aplicação [1].
Integridade de Entidade: Garante que cada linha em uma tabela seja única e identificável
(implementada através de chaves primárias).
Integridade Referencial: Garante que os relacionamentos entre tabelas sejam válidos (implementada
através de chaves estrangeiras).
Integridade de Domínio: Garante que os valores em uma coluna atendam a critérios específicos
(implementada através de restrições como NOT NULL, CHECK, UNIQUE).
A restrição PRIMARY KEY define uma ou mais colunas como a chave primária da tabela. A chave primária
serve como um identificador único para cada linha na tabela e é fundamental para a integridade de entidade.
Unicidade: Cada valor da chave primária deve ser único na tabela. Não pode haver duas linhas com o
mesmo valor de chave primária.
Não Nulidade: Nenhuma coluna que faz parte da chave primária pode conter valores NULL .
Chave Natural: Usa dados que têm significado no mundo real (ex: CPF, CNPJ, código de produto).
Vantagem: significado semântico. Desvantagem: pode mudar ou ser reutilizada.
Chave Artificial (Surrogate): Usa valores gerados automaticamente pelo sistema (ex: auto-incremento,
UUID). Vantagem: estabilidade e simplicidade. Desvantagem: sem significado semântico.
-- SQL Server
CREATE TABLE Produtos (
ProdutoID INT IDENTITY(1,1) PRIMARY KEY,
NomeProduto VARCHAR(255) NOT NULL
);
-- MySQL
CREATE TABLE Produtos (
ProdutoID INT AUTO_INCREMENT PRIMARY KEY,
NomeProduto VARCHAR(255) NOT NULL
);
-- PostgreSQL
CREATE TABLE Produtos (
ProdutoID SERIAL PRIMARY KEY,
NomeProduto VARCHAR(255) NOT NULL
);
14.3 FOREIGN KEY: Mantendo a Integridade Referencial
A restrição FOREIGN KEY estabelece e mantém um vínculo entre os dados de duas tabelas, garantindo a
integridade referencial. Uma chave estrangeira em uma tabela aponta para uma chave primária (ou única) em
outra tabela.
Referência Válida: O valor da chave estrangeira deve existir na tabela referenciada ou ser NULL (se
permitido).
Prevenção de Órfãos: Impede a criação de registros "órfãos" que referenciam entidades inexistentes.
Cascata de Operações: Pode definir ações automáticas quando a chave primária referenciada é
atualizada ou excluída.
Quando uma linha referenciada por uma chave estrangeira é atualizada ou excluída, você pode especificar
que ação deve ser tomada:
SET NULL: Define a chave estrangeira como NULL nos registros dependentes.
SET DEFAULT: Define a chave estrangeira para seu valor padrão nos registros dependentes.
A restrição UNIQUE garante que todos os valores em uma coluna ou combinação de colunas sejam únicos na
tabela. Diferentemente da PRIMARY KEY , uma tabela pode ter múltiplas restrições UNIQUE , e as colunas
UNIQUE podem conter valores NULL (mas apenas um NULL por coluna, na maioria dos SGBDs).
A restrição NOT NULL garante que uma coluna não possa conter valores nulos. É fundamental para colunas
que devem sempre ter um valor, como nomes, identificadores essenciais, ou campos obrigatórios para a
lógica de negócio.
-- SQL Server
ALTER TABLE Produtos
ALTER COLUMN Descricao VARCHAR(500) NOT NULL;
-- SQL Server
ALTER TABLE Produtos
ALTER COLUMN Descricao VARCHAR(500) NULL;
A restrição CHECK permite definir uma condição que deve ser verdadeira para todos os valores em uma
coluna ou combinação de colunas. É útil para implementar regras de negócio específicas e validações de
domínio.
Não podem referenciar outras tabelas (use triggers para validações complexas).
Embora não seja tecnicamente uma restrição de integridade, a cláusula DEFAULT é frequentemente usada
junto com restrições para definir valores padrão para colunas quando nenhum valor é fornecido durante a
inserção.
-- SQL Server
ALTER TABLE Vendas
ADD CONSTRAINT DF_Vendas_Status DEFAULT \"Ativo\" FOR Status;
É uma boa prática nomear explicitamente suas restrições para facilitar o gerenciamento:
Alguns SGBDs permitem desabilitar temporariamente restrições (útil para cargas de dados em massa):
PRIMARY KEY: Identifica unicamente cada linha; não pode ser nula nem duplicada.
FOREIGN KEY: Mantém integridade referencial entre tabelas; pode ter ações de cascata.
Crie uma tabela Categorias com CategoriaID (PK), NomeCategoria (NOT NULL, UNIQUE),
Descricao (permite NULL).
Crie uma tabela ProdutosCompletos que inclua uma referência à tabela Categorias via FOREIGN
KEY.
2. Restrições CHECK:
Adicione uma restrição CHECK à tabela Produtos para garantir que o Preco seja maior que 0.
Adicione uma restrição CHECK para garantir que o Estoque seja maior ou igual a 0.
Crie uma restrição CHECK que garanta que a DataVenda não seja no futuro.
3. Testando Restrições:
Tente inserir uma venda para um cliente que não existe. O que acontece?
4. Gerenciamento de Restrições:
Liste todas as restrições da tabela Clientes (pesquise como fazer isso no seu SGBD).
Remova uma restrição CHECK e depois adicione-a novamente com um nome diferente.
Configure uma FOREIGN KEY com ON DELETE CASCADE entre Clientes e Vendas .
Referências
As funções de janela (window functions) são uma das funcionalidades mais poderosas e elegantes do SQL
moderno. Elas permitem realizar cálculos em um conjunto de linhas relacionadas à linha atual, sem a
necessidade de agrupar os dados como fazemos com GROUP BY . Isso significa que você pode calcular
agregações, rankings, e análises estatísticas mantendo o detalhamento das linhas individuais [1].
As funções de janela foram introduzidas no padrão SQL:2003 e são suportadas pela maioria dos SGBDs
modernos, incluindo PostgreSQL, SQL Server, Oracle, MySQL (a partir da versão 8.0), e SQLite (a partir da
versão 3.25.0).
A principal vantagem das funções de janela é que elas permitem combinar dados detalhados com
informações agregadas na mesma consulta. Por exemplo, você pode mostrar cada venda individual junto com
o total de vendas do cliente, ou classificar produtos por preço mantendo todas as informações do produto
visíveis.
15.1.1 Diferença entre Funções de Janela e GROUP BY
funcao_janela() OVER (
[PARTITION BY coluna1, coluna2, ...]
[ORDER BY coluna3, coluna4, ...]
[ROWS/RANGE especificacao_frame]
)
funcao_janela() : A função que você deseja aplicar (ex: SUM() , ROW_NUMBER() , RANK() ).
OVER : Palavra-chave que define a "janela" de dados sobre a qual a função opera.
PARTITION BY : (Opcional) Divide os dados em grupos. A função é aplicada separadamente para cada
grupo.
ROWS/RANGE : (Opcional) Define o frame da janela - quais linhas específicas incluir no cálculo.
As funções de classificação atribuem um ranking ou número sequencial às linhas com base em uma ordem
específica.
15.3.1 ROW_NUMBER()
ROW_NUMBER() atribui um número sequencial único a cada linha, começando em 1. Mesmo que haja valores
duplicados, cada linha recebe um número diferente.
SELECT
ProdutoID,
NomeProduto,
Preco,
ROW_NUMBER() OVER (ORDER BY Preco DESC) AS NumeroLinha
FROM Produtos;
15.3.2 RANK()
RANK() atribui o mesmo ranking a linhas com valores iguais, mas deixa "lacunas" na numeração. Se duas
linhas empatam no 2º lugar, a próxima linha será classificada como 4º lugar.
SELECT
ProdutoID,
NomeProduto,
Preco,
RANK() OVER (ORDER BY Preco DESC) AS Ranking
FROM Produtos;
15.3.3 DENSE_RANK()
DENSE_RANK() é similar ao RANK() , mas não deixa lacunas na numeração. Se duas linhas empatam no 2º
lugar, a próxima linha será classificada como 3º lugar.
SELECT
ProdutoID,
NomeProduto,
Preco,
DENSE_RANK() OVER (ORDER BY Preco DESC) AS RankingDenso
FROM Produtos;
15.3.4 NTILE()
NTILE(n) divide as linhas em n grupos aproximadamente iguais e atribui um número de grupo (de 1 a n)
para cada linha.
SELECT
NomeProduto,
Preco,
ROW_NUMBER() OVER (ORDER BY Preco DESC) AS RowNum,
RANK() OVER (ORDER BY Preco DESC) AS Rank,
DENSE_RANK() OVER (ORDER BY Preco DESC) AS DenseRank,
NTILE(3) OVER (ORDER BY Preco DESC) AS Tercil
FROM Produtos
ORDER BY Preco DESC;
As funções analíticas permitem acessar dados de outras linhas em relação à linha atual, sem usar JOINs.
SELECT
DataVenda,
SUM(Quantidade * PrecoUnitario) AS VendasDia,
LAG(SUM(Quantidade * PrecoUnitario), 1) OVER (ORDER BY DataVenda) AS VendasDiaAnterior,
LEAD(SUM(Quantidade * PrecoUnitario), 1) OVER (ORDER BY DataVenda) AS VendasProximoDia
FROM Vendas
GROUP BY DataVenda
ORDER BY DataVenda;
SELECT
ProdutoID,
NomeProduto,
Preco,
FIRST_VALUE(NomeProduto) OVER (ORDER BY Preco DESC) AS ProdutoMaisCaro,
LAST_VALUE(NomeProduto) OVER (ORDER BY Preco DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS ProdutoMaisBarato
FROM Produtos;
Nota: LAST_VALUE() frequentemente requer especificação do frame da janela para funcionar como
esperado.
PARTITION BY divide os dados em grupos (partições) e aplica a função de janela separadamente para cada
grupo. É similar ao GROUP BY , mas mantém o detalhamento das linhas.
SELECT
[Link],
[Link],
[Link],
[Link] * [Link] AS ValorVenda,
-- Total de vendas por cliente
SUM([Link] * [Link]) OVER (PARTITION BY [Link]) AS TotalVendasCliente,
-- Ranking da venda dentro das vendas do cliente
ROW_NUMBER() OVER (PARTITION BY [Link] ORDER BY [Link] * [Link] DESC) AS
RankingVendaCliente,
-- Total geral de vendas
SUM([Link] * [Link]) OVER () AS TotalGeralVendas
FROM Vendas V;
Todas as funções de agregação tradicionais ( SUM , AVG , COUNT , MIN , MAX ) podem ser usadas como funções
de janela.
SELECT
DataVenda,
SUM(Quantidade * PrecoUnitario) AS VendasDia,
SUM(SUM(Quantidade * PrecoUnitario)) OVER (ORDER BY DataVenda) AS VendasAcumuladas
FROM Vendas
GROUP BY DataVenda
ORDER BY DataVenda;
O frame de janela define exatamente quais linhas incluir no cálculo da função de janela. Por padrão, o frame
inclui todas as linhas desde o início da partição até a linha atual.
SELECT
DataVenda,
SUM(Quantidade * PrecoUnitario) AS VendasDia,
-- Soma das últimas 3 vendas (incluindo a atual)
SUM(SUM(Quantidade * PrecoUnitario)) OVER (
ORDER BY DataVenda
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS Soma3Dias,
-- Soma de toda a partição
SUM(SUM(Quantidade * PrecoUnitario)) OVER (
ORDER BY DataVenda
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS SomaTotal
FROM Vendas
GROUP BY DataVenda
ORDER BY DataVenda;
SELECT
ClienteID,
SUM(Quantidade * PrecoUnitario) AS VendasCliente,
ROUND(
100.0 * SUM(Quantidade * PrecoUnitario) /
SUM(SUM(Quantidade * PrecoUnitario)) OVER (),
2
) AS PercentualDoTotal
FROM Vendas
GROUP BY ClienteID
ORDER BY VendasCliente DESC;
15.8.3 Comparação com Período Anterior
SELECT
EXTRACT(YEAR FROM DataVenda) AS Ano,
EXTRACT(MONTH FROM DataVenda) AS Mes,
SUM(Quantidade * PrecoUnitario) AS VendasMes,
LAG(SUM(Quantidade * PrecoUnitario), 1) OVER (ORDER BY EXTRACT(YEAR FROM DataVenda),
EXTRACT(MONTH FROM DataVenda)) AS VendasMesAnterior,
ROUND(
100.0 * (SUM(Quantidade * PrecoUnitario) - LAG(SUM(Quantidade * PrecoUnitario), 1) OVER
(ORDER BY EXTRACT(YEAR FROM DataVenda), EXTRACT(MONTH FROM DataVenda))) /
LAG(SUM(Quantidade * PrecoUnitario), 1) OVER (ORDER BY EXTRACT(YEAR FROM DataVenda),
EXTRACT(MONTH FROM DataVenda)),
2
) AS CrescimentoPercentual
FROM Vendas
GROUP BY EXTRACT(YEAR FROM DataVenda), EXTRACT(MONTH FROM DataVenda)
ORDER BY Ano, Mes;
Frames de Janela: Definem exatamente quais linhas incluir no cálculo ( ROWS , RANGE ).
Casos de Uso: Rankings, totais acumulados, médias móveis, comparações temporais, percentuais.
1. Funções de Ranking:
Classifique todos os produtos por preço (do mais caro para o mais barato) usando RANK() .
2. Funções Analíticas:
Para cada venda, mostre o valor da venda anterior e posterior do mesmo cliente.
Identifique o produto mais caro e mais barato em cada categoria usando FIRST_VALUE() e
LAST_VALUE() .
3. PARTITION BY:
Calcule o total de vendas por cliente e mostre cada venda individual junto com esse total.
Classifique as vendas dentro de cada mês (ranking 1, 2, 3... para cada mês).
4. Totais Acumulados:
5. Desafio:
Identifique os top 3 produtos mais vendidos (em quantidade) para cada cliente.
Para cada produto, calcule que percentual das vendas totais ele representa.
Referências
Uma Expressão de Tabela Comum (CTE - Common Table Expression) é um conjunto de resultados temporário
e nomeado que você pode referenciar dentro de uma única instrução SQL ( SELECT , INSERT , UPDATE ,
DELETE ). As CTEs são definidas usando a cláusula WITH e são uma ferramenta poderosa para organizar e
simplificar consultas complexas, tornando-as mais legíveis e fáceis de manter [1].
As CTEs são temporárias porque seu escopo é limitado à consulta em que são definidas. Elas não são
armazenadas permanentemente no banco de dados como as Views, nem são materializadas (salvas em disco)
a menos que o otimizador de consulta decida fazê-lo por razões de performance. Isso as torna ideais para
cálculos intermediários ou para quebrar uma consulta grande em etapas lógicas menores.
Modularidade: Cada CTE pode ser vista como um módulo que executa uma parte específica da lógica,
tornando a depuração e a manutenção mais fáceis.
Reusabilidade dentro da Consulta: Uma CTE pode ser referenciada múltiplas vezes dentro da mesma
consulta principal, evitando a repetição de código.
(coluna1, coluna2, ...) : (Opcional) Uma lista de nomes de colunas para a CTE. Se omitido, a CTE
usará os nomes de coluna da consulta SELECT interna.
Vamos reescrever o exemplo de faturamento por cliente usando uma CTE para melhorar a legibilidade.
SELECT
ClienteID,
FaturamentoTotal
FROM (
SELECT
ClienteID,
SUM(Quantidade * PrecoUnitario) AS FaturamentoTotal
FROM Vendas
GROUP BY ClienteID
) AS FaturamentoPorCliente
WHERE FaturamentoTotal > 5000;
Com CTE:
WITH FaturamentoPorCliente AS (
SELECT
ClienteID,
SUM(Quantidade * PrecoUnitario) AS FaturamentoTotal
FROM Vendas
GROUP BY ClienteID
)
SELECT
ClienteID,
FaturamentoTotal
FROM FaturamentoPorCliente
WHERE FaturamentoTotal > 5000;
Neste exemplo, a CTE FaturamentoPorCliente calcula o faturamento para cada cliente, e a consulta principal
filtra esses resultados. A lógica fica mais clara e modular.
Você pode definir múltiplas CTEs em uma única cláusula WITH , separadas por vírgulas. Cada CTE
subsequente pode referenciar as CTEs definidas anteriormente.
WITH CTE1 AS (
SELECT ...
),
CTE2 AS (
SELECT ... FROM CTE1 ...
),
CTE3 AS (
SELECT ... FROM CTE1, CTE2 ...
)
SELECT ... FROM CTE3 ...;
WITH ProdutosCaros AS (
SELECT ProdutoID, NomeProduto, Preco
FROM Produtos
WHERE Preco > 1000
),
VendasProdutosCaros AS (
SELECT [Link], [Link], [Link], [Link]
FROM Vendas V
INNER JOIN ProdutosCaros PC ON [Link] = [Link]
)
SELECT DISTINCT [Link], [Link]
FROM Clientes C
INNER JOIN VendasProdutosCaros VPC ON [Link] = [Link];
CTEs são frequentemente usadas em conjunto com funções de janela para tornar a lógica mais
compreensível.
WITH ProdutosRankeados AS (
SELECT
ProdutoID,
NomeProduto,
Categoria,
Preco,
ROW_NUMBER() OVER (PARTITION BY Categoria ORDER BY Preco DESC) AS rn
FROM Produtos
)
SELECT
ProdutoID,
NomeProduto,
Categoria,
Preco
FROM ProdutosRankeados
WHERE rn = 2;
16.4 CTEs Recursivas
CTEs recursivas são um tipo especial de CTE que se refere a si mesma. Elas são usadas para consultar dados
hierárquicos ou em grafos, como estruturas organizacionais, árvores de componentes, ou caminhos em redes
[2].
A sintaxe de uma CTE recursiva consiste em duas partes, unidas por UNION ALL :
1. Membro Âncora (Anchor Member): A consulta inicial que define o conjunto base de resultados. Esta
parte não é recursiva.
2. Membro Recursivo (Recursive Member): A consulta que referencia a própria CTE e expande o conjunto
de resultados a cada iteração. Esta parte deve conter uma condição de terminação para evitar loops
infinitos.
Considere a tabela Funcionarios com FuncionarioID , Nome e GerenteID (que aponta para o
FuncionarioID do gerente).
-- Tabela de exemplo
CREATE TABLE Funcionarios (
FuncionarioID INT PRIMARY KEY,
Nome VARCHAR(100),
GerenteID INT,
FOREIGN KEY (GerenteID) REFERENCES Funcionarios(FuncionarioID)
);
UNION ALL
Neste exemplo: * O membro âncora seleciona o funcionário de nível mais alto (CEO). * O membro recursivo se
junta à CTE HierarquiaFuncionarios para encontrar os subordinados diretos dos funcionários já
identificados, incrementando o nível. * A recursão continua até que não haja mais subordinados a serem
encontrados.
16.5.1 Limitações
Escopo: CTEs são válidas apenas para a instrução SQL em que são definidas. Você não pode referenciá-
las em consultas subsequentes.
Não são Materializadas por Padrão: O SGBD pode reavaliar a CTE a cada referência, o que pode
impactar a performance se a CTE for complexa e referenciada muitas vezes. (Alguns SGBDs permitem
forçar a materialização, como OPTION (RECOMPILE) no SQL Server ou /*+ MATERIALIZE */ no Oracle).
Legibilidade vs. Performance: Embora as CTEs melhorem a legibilidade, elas nem sempre melhoram a
performance. O otimizador de consulta pode tratar uma CTE de forma semelhante a uma subconsulta.
Índices: Certifique-se de que as tabelas base usadas nas CTEs tenham índices apropriados.
Evite Loops Infinitos em Recursivas: Sempre inclua uma condição de terminação no membro recursivo
de CTEs recursivas para evitar loops infinitos. A maioria dos SGBDs tem um limite de recursão padrão
(ex: 100 no SQL Server) para evitar isso.
16.6 Tópicos Chave do Capítulo 16
CTEs (Common Table Expressions): Conjuntos de resultados temporários e nomeados definidos com
WITH .
CTEs Recursivas: Usadas para consultar dados hierárquicos, com membro âncora e membro recursivo.
Considerações: Escopo limitado, não são Views, performance pode variar, cuidado com loops infinitos
em recursivas.
1. CTE Básica:
Crie uma CTE chamada ProdutosCaros que selecione ProdutoID , NomeProduto e Preco para
produtos com preço superior a R$ 1500,00.
2. Múltiplas CTEs:
Crie uma CTE VendasPorCliente que calcule o faturamento total para cada cliente.
Crie uma CTE VendasRankeadas que rankeie as vendas de cada cliente por ValorTotalVenda (do
maior para o menor) usando ROW_NUMBER() .
Insira dados que representem uma hierarquia de produtos (ex: Computador -> Placa Mãe ->
Processador).
Use uma CTE recursiva para listar todos os componentes de um determinado produto principal e
seu nível na hierarquia.
5. Desafio:
Referências
PL/SQL (Procedural Language/SQL) é uma extensão procedural da linguagem SQL desenvolvida pela Oracle
Corporation. Enquanto o SQL é uma linguagem declarativa que permite especificar o que você quer fazer com
os dados, o PL/SQL adiciona capacidades procedurais que permitem especificar como fazer, incluindo
estruturas de controle, variáveis, exceções, cursores, e modularização através de procedures, functions e
packages [1].
O PL/SQL foi introduzido pela primeira vez na versão 6 do Oracle Database em 1991 e tem evoluído
continuamente desde então. Ele combina a flexibilidade e o poder do SQL com as funcionalidades de uma
linguagem de programação de alto nível, permitindo que desenvolvedores criem aplicações robustas e
eficientes diretamente no banco de dados.
Integração Perfeita com SQL: PL/SQL permite misturar comandos SQL com lógica procedural de forma
natural e eficiente.
Segurança: Procedures e functions PL/SQL podem ser usadas para implementar camadas de segurança,
controlando o acesso aos dados através de interfaces bem definidas.
Portabilidade: Código PL/SQL pode ser facilmente movido entre diferentes ambientes Oracle.
17.1.2 Diferenças entre SQL e PL/SQL
O PL/SQL Engine é responsável por processar e executar código PL/SQL. Ele funciona em conjunto com o SQL
Engine do Oracle Database para otimizar a execução de comandos SQL dentro de blocos PL/SQL.
Shared Pool: Área de memória onde o código PL/SQL compilado é armazenado para reutilização.
3. Execução: O PL/SQL Engine executa o bytecode, enviando comandos SQL para o SQL Engine quando
necessário.
Um bloco PL/SQL é a unidade básica de código PL/SQL. Ele tem uma estrutura bem definida com seções
obrigatórias e opcionais:
[DECLARE]
-- Seção de declaração (opcional)
-- Declaração de variáveis, constantes, cursores, exceções definidas pelo usuário
BEGIN
-- Seção executável (obrigatória)
-- Comandos SQL e PL/SQL
[EXCEPTION]
-- Seção de tratamento de exceções (opcional)
-- Tratamento de erros
END;
/
DECLARE: (Opcional) Onde você declara variáveis, constantes, cursores e exceções definidas pelo
usuário. Se não houver declarações, esta seção pode ser omitida.
EXCEPTION: (Opcional) Contém o código para tratamento de exceções que podem ocorrer na seção
BEGIN.
/ (Barra): Indica ao Oracle SQL*Plus ou SQL Developer que o bloco PL/SQL terminou e deve ser
executado.
DECLARE
v_mensagem VARCHAR2(50);
v_contador NUMBER := 0;
BEGIN
v_mensagem := \'Olá, PL/SQL!\';
v_contador := v_contador + 1;
DBMS_OUTPUT.PUT_LINE(v_mensagem);
DBMS_OUTPUT.PUT_LINE(\'Contador: \' || v_contador);
END;
/
Blocos anônimos são blocos PL/SQL que não têm nome e são executados imediatamente. Eles são úteis para:
* Testes rápidos * Scripts de manutenção * Operações pontuais
BEGIN
DBMS_OUTPUT.PUT_LINE(\'Este é um bloco anônimo\');
END;
/
17.4.2 Procedures
Procedures são blocos nomeados que podem aceitar parâmetros e ser reutilizados. Elas são armazenadas no
banco de dados.
CREATE OR REPLACE PROCEDURE mostrar_mensagem(p_texto VARCHAR2)
IS
BEGIN
DBMS_OUTPUT.PUT_LINE(p_texto);
END;
/
17.4.3 Functions
17.4.4 Packages
Packages são coleções de procedures, functions, variáveis e outros elementos PL/SQL relacionados.
DBMS_OUTPUT é um package built-in do Oracle que permite exibir informações na tela durante a execução de
código PL/SQL. É especialmente útil para debugging e para mostrar resultados de processamento.
SET SERVEROUTPUT ON
BEGIN
DBMS_OUTPUT.PUT_LINE(\'Primeira linha\');
DBMS_OUTPUT.PUT(\'Texto sem quebra \');
DBMS_OUTPUT.PUT(\'de linha\');
DBMS_OUTPUT.NEW_LINE;
DBMS_OUTPUT.PUT_LINE(\'Segunda linha\');
END;
/
/*
Este é um comentário
de múltiplas linhas
*/
BEGIN
/* Comentário em bloco */
DBMS_OUTPUT.PUT_LINE(\'Olá\');
END;
/
DECLARE
-- Constantes
c_taxa_desconto CONSTANT NUMBER := 0.10;
c_mensagem_sucesso CONSTANT VARCHAR2(50) := \'Operação realizada com sucesso\';
-- Variáveis
v_cliente_id NUMBER;
v_valor_total NUMBER;
v_valor_desconto NUMBER;
EXCEPTION
WHEN e_valor_invalido THEN
DBMS_OUTPUT.PUT_LINE(\'Erro: Valor deve ser maior que zero\');
END;
/
17.8.1 Vantagens
17.8.2 Desvantagens
Crie um bloco anônimo que declare uma variável para armazenar seu nome e exiba uma
mensagem de boas-vindas usando DBMS_OUTPUT.PUT_LINE .
Declare variáveis para armazenar o nome de um produto, seu preço e quantidade em estoque.
3. Usando Constantes:
Declare uma constante para a taxa de imposto (ex: 0.18 para 18%).
Calcule o valor do imposto sobre um produto e exiba o preço com e sem imposto.
4. Estrutura Completa:
Crie um bloco PL/SQL com todas as seções (DECLARE, BEGIN, EXCEPTION, END).
5. Desafio:
No Capítulo 17, introduzimos a estrutura básica de um bloco PL/SQL e mencionamos os blocos anônimos
como a forma mais simples de código PL/SQL. Um bloco anônimo é um bloco de código que não tem um
nome e não é armazenado no banco de dados. Ele é compilado e executado cada vez que é enviado ao Oracle
Database. Embora não sejam reutilizáveis como procedures ou functions, são extremamente úteis para
tarefas ad-hoc, testes rápidos, scripts de manutenção e para aprender os fundamentos do PL/SQL [1].
Relembrando a estrutura:
[DECLARE]
-- Seção de declaração: variáveis, constantes, cursores, tipos definidos pelo usuário
BEGIN
-- Seção executável: comandos SQL e PL/SQL
[EXCEPTION]
-- Seção de tratamento de exceções: lida com erros
END;
/
DECLARE (Opcional): Usada para declarar todos os itens que serão utilizados no bloco, como variáveis,
constantes, cursores, tipos de dados definidos pelo usuário, etc. Tudo o que é declarado aqui tem
escopo local ao bloco.
BEGIN (Obrigatória): Contém a lógica principal do bloco. É onde você coloca suas instruções SQL (DML,
DQL) e instruções PL/SQL (estruturas de controle, chamadas a procedures/functions).
EXCEPTION (Opcional): Usada para lidar com erros que podem ocorrer durante a execução da seção
BEGIN . Se um erro ocorrer e não for tratado, o bloco será encerrado e a transação será revertida
(rollback).
/ (Barra): Em ferramentas como SQL*Plus ou SQL Developer, a barra em uma nova linha após o END;
indica que o bloco deve ser enviado para execução.
A seção DECLARE é onde você define os elementos que serão usados no seu bloco. É crucial para a
organização e para a tipagem forte do PL/SQL.
18.2.1 Variáveis
Variáveis são espaços de memória nomeados que armazenam valores. Elas devem ser declaradas com um
nome e um tipo de dado.
DECLARE
v_nome_cliente VARCHAR2(100);
v_idade NUMBER;
v_data_nascimento DATE;
v_ativo BOOLEAN;
BEGIN
-- ...
END;
/
18.2.2 Constantes
Constantes são valores que não mudam durante a execução do bloco. Elas são declaradas com a palavra-
chave CONSTANT e devem ser inicializadas.
DECLARE
c_PI CONSTANT NUMBER := 3.14159;
c_MENSAGEM_BOAS_VINDAS CONSTANT VARCHAR2(50) := \'Bem-vindo ao sistema!\';
BEGIN
-- ...
END;
/
%TYPE é um atributo PL/SQL que permite declarar uma variável com o mesmo tipo de dado de uma coluna de
tabela ou de outra variável. Isso é extremamente útil para manter a consistência e evitar erros se o tipo de
dado da coluna mudar no futuro.
DECLARE
v_nome_produto [Link]%TYPE; -- Variável com o mesmo tipo da coluna NomeProduto da
tabela Produtos
v_preco_produto [Link]%TYPE;
v_novo_nome v_nome_produto%TYPE; -- Variável com o mesmo tipo de outra variável
BEGIN
-- ...
END;
/
%ROWTYPE permite declarar uma variável que pode armazenar uma linha inteira de uma tabela ou o resultado
de uma consulta. A variável terá um campo para cada coluna da tabela/consulta, com os tipos de dados
correspondentes.
DECLARE
r_produto Produtos%ROWTYPE; -- Variável que pode armazenar uma linha completa da tabela Produtos
BEGIN
-- ...
END;
/
18.3 Seção BEGIN: Lógica Executável
A seção BEGIN é o coração do bloco, onde a lógica de negócio é implementada. Aqui você pode:
DECLARE
v_contador NUMBER;
BEGIN
v_contador := 10;
v_contador := v_contador + 1;
END;
/
Você pode incorporar comandos SQL diretamente na seção BEGIN . Para atribuir o resultado de uma consulta
SELECT a uma variável, use a cláusula INTO .
DECLARE
v_nome_cliente [Link]%TYPE;
v_email_cliente [Link]%TYPE;
BEGIN
SELECT Nome, Email
INTO v_nome_cliente, v_email_cliente
FROM Clientes
WHERE ClienteID = 101;
DBMS_OUTPUT.PUT_LINE(
\'Cliente: \' || v_nome_cliente || \' - Email: \' || v_email_cliente
);
END;
/
Importante: A cláusula SELECT INTO deve retornar exatamente uma linha. Se retornar zero ou mais de uma
linha, ocorrerá um erro (NO_DATA_FOUND ou TOO_MANY_ROWS, respectivamente), que deve ser tratado na
seção EXCEPTION .
-- Deletar um produto
DELETE FROM Produtos
WHERE ProdutoID = 5;
A seção EXCEPTION é usada para lidar com erros (exceções) que ocorrem durante a execução do bloco. Isso
permite que seu programa reaja a erros de forma controlada, evitando que o programa aborte
inesperadamente [2].
DUP_VAL_ON_INDEX : Tentativa de inserir um valor duplicado em uma coluna com restrição UNIQUE .
DECLARE
v_nome_cliente [Link]%TYPE;
BEGIN
SELECT Nome INTO v_nome_cliente
FROM Clientes
WHERE ClienteID = 999; -- Cliente que não existe
Você pode definir suas próprias exceções para lidar com condições de erro específicas da sua lógica de
negócio.
DECLARE
e_estoque_insuficiente EXCEPTION;
v_estoque_atual [Link]%TYPE;
v_quantidade_desejada NUMBER := 100;
BEGIN
SELECT Estoque INTO v_estoque_atual
FROM Produtos
WHERE ProdutoID = 1;
Você pode aninhar blocos PL/SQL dentro de outros blocos. Isso é útil para controlar o escopo de variáveis e
para tratamento de exceções mais granular.
DECLARE
v_variavel_externa VARCHAR2(50) := \'Fora do bloco interno\';
BEGIN
DBMS_OUTPUT.PUT_LINE(v_variavel_externa);
DECLARE
v_variavel_interna VARCHAR2(50) := \'Dentro do bloco interno\';
BEGIN
DBMS_OUTPUT.PUT_LINE(v_variavel_interna);
DBMS_OUTPUT.PUT_LINE(v_variavel_externa); -- Acessa variável externa
END;
Por padrão, cada bloco PL/SQL é executado dentro de uma transação. Você pode usar COMMIT e ROLLBACK
para controlar explicitamente a transação.
BEGIN
INSERT INTO Clientes (ClienteID, Nome, Sobrenome, Email)
VALUES (105, \'Mariana\', \'Souza\', \'[Link]@[Link]\');
%TYPE : Declara variáveis com o mesmo tipo de uma coluna ou outra variável.
%ROWTYPE : Declara variáveis que representam uma linha inteira de uma tabela ou consulta.
SELECT INTO : Usado para atribuir resultados de consultas a variáveis (deve retornar uma única linha).
EXCEPTION : Seção para tratamento de erros (exceções pré-definidas e definidas pelo usuário).
Crie um bloco anônimo que declare uma variável v_numero do tipo NUMBER e atribua a ela o valor
10. Em seguida, exiba o dobro desse número usando DBMS_OUTPUT.PUT_LINE .
3. Tratamento de Exceções:
Crie um bloco que tente selecionar o nome de um cliente com um ClienteID que não existe. Trate
a exceção NO_DATA_FOUND e exiba uma mensagem amigável.
Crie um bloco que tente inserir um produto com um ProdutoID já existente (violando a PK). Trate
a exceção DUP_VAL_ON_INDEX .
Dentro deste bloco, crie um bloco aninhado que tente atualizar o estoque de um produto
inexistente.
Garanta que, se o erro ocorrer no bloco aninhado, a inserção do produto no bloco externo seja
desfeita.
5. Desafio:
Se houver, atualize o estoque do produto e insira uma nova venda na tabela Vendas .
Se não houver, trate as exceções apropriadas ( NO_DATA_FOUND para produto, e uma exceção
definida pelo usuário para estoque insuficiente).
Referências
O PL/SQL possui um sistema de tipos robusto e flexível que combina os tipos de dados do SQL com tipos
específicos do PL/SQL. Este sistema de tipos é fundamental para garantir a integridade dos dados, otimizar a
performance e facilitar a manutenção do código. O PL/SQL é uma linguagem fortemente tipada, o que
significa que cada variável deve ter um tipo específico declarado e que operações entre tipos incompatíveis
resultarão em erros de compilação ou execução [1].
19.1.1 Categorias de Tipos de Dados
Tipos de Referência: Armazenam ponteiros para outros objetos (REF CURSOR, REF).
Tipos LOB (Large Object): Para armazenar grandes volumes de dados (CLOB, BLOB, BFILE).
NUMBER: O tipo NUMBER é o tipo numérico principal do PL/SQL, capaz de armazenar números inteiros e
decimais com alta precisão.
DECLARE
v_inteiro NUMBER(10); -- Até 10 dígitos
v_decimal NUMBER(10,2); -- 10 dígitos, 2 casas decimais
v_numero_geral NUMBER; -- Precisão máxima (38 dígitos)
v_percentual NUMBER(5,4); -- Para valores como 0.9875 (98.75%)
BEGIN
v_inteiro := 1234567890;
v_decimal := 12345.67;
v_numero_geral := 123456789012345678901234567890123456.78;
v_percentual := 0.9875;
PLS_INTEGER: Tipo inteiro otimizado para performance, mais rápido que NUMBER para operações
aritméticas.
DECLARE
v_contador PLS_INTEGER := 0;
v_limite PLS_INTEGER := 1000000;
BEGIN
FOR i IN 1..v_limite LOOP
v_contador := v_contador + 1;
END LOOP;
DBMS_OUTPUT.PUT_LINE(\'Contador: \' || v_contador);
END;
/
VARCHAR2: Tipo de string de comprimento variável, o mais comumente usado para texto.
DECLARE
v_nome VARCHAR2(100); -- Até 100 caracteres
v_descricao VARCHAR2(4000); -- Até 4000 caracteres
v_texto_longo VARCHAR2(32767); -- Máximo em PL/SQL
BEGIN
v_nome := \'João Silva\';
v_descricao := \'Produto de alta qualidade com garantia estendida\';
DECLARE
v_codigo CHAR(10); -- Sempre 10 caracteres
v_status CHAR(1); -- Sempre 1 caractere
BEGIN
v_codigo := \'PROD001\'; -- Será \'PROD001 \' (com espaços)
v_status := \'A\'; -- Ativo
NVARCHAR2 e NCHAR: Versões Unicode dos tipos de caractere, para suporte a caracteres internacionais.
DECLARE
v_nome_unicode NVARCHAR2(100);
v_simbolo NCHAR(1);
BEGIN
v_nome_unicode := \'José María González\';
v_simbolo := \'€\';
DECLARE
v_data_nascimento DATE;
v_data_atual DATE;
v_data_especifica DATE;
BEGIN
v_data_atual := SYSDATE; -- Data e hora atuais
v_data_nascimento := DATE \'1990-05-15\'; -- Literal de data
v_data_especifica := TO_DATE(\'25/12/2025 14:30:00\', \'DD/MM/YYYY HH24:MI:SS\');
DECLARE
v_intervalo_dia INTERVAL DAY TO SECOND;
v_intervalo_ano INTERVAL YEAR TO MONTH;
v_data_futura DATE;
BEGIN
v_intervalo_dia := INTERVAL \'5\' DAY; -- 5 dias
v_intervalo_ano := INTERVAL \'2-6\' YEAR TO MONTH; -- 2 anos e 6 meses
BOOLEAN: Tipo específico do PL/SQL (não existe no SQL) que armazena valores lógicos.
DECLARE
v_ativo BOOLEAN;
v_maior_idade BOOLEAN;
v_aprovado BOOLEAN := TRUE;
v_reprovado BOOLEAN := FALSE;
v_indefinido BOOLEAN := NULL;
BEGIN
v_ativo := TRUE;
v_maior_idade := (18 >= 18); -- Resultado de expressão lógica
IF v_ativo THEN
DBMS_OUTPUT.PUT_LINE(\'Status: Ativo\');
END IF;
IF v_maior_idade THEN
DBMS_OUTPUT.PUT_LINE(\'É maior de idade\');
END IF;
DECLARE
-- Declaração simples
v_nome VARCHAR2(100);
-- Constante
c_PI CONSTANT NUMBER := 3.14159;
c_EMPRESA CONSTANT VARCHAR2(50) := \'Minha Empresa Ltda\';
-- Usando %TYPE
v_preco_produto [Link]%TYPE;
v_nome_cliente [Link]%TYPE := \'Cliente Padrão\';
-- Usando %ROWTYPE
r_produto Produtos%ROWTYPE;
r_cliente Clientes%ROWTYPE;
BEGIN
-- Atribuições
v_nome := \'João Silva\';
v_contador := v_contador + 1;
v_preco_produto := 99.99;
DECLARE
v_numero NUMBER;
v_texto VARCHAR2(50);
v_data DATE;
v_timestamp TIMESTAMP;
BEGIN
-- Conversões explícitas
v_numero := TO_NUMBER(\'1234.56\');
v_texto := TO_CHAR(789.12, \'999,999.99\');
v_data := TO_DATE(\'25/12/2025\', \'DD/MM/YYYY\');
v_timestamp := TO_TIMESTAMP(\'25/12/2025 14:30:45.123\', \'DD/MM/YYYY HH24:MI:SS.FF3\');
DECLARE
v_global VARCHAR2(50) := \'Variável Global\';
BEGIN
DBMS_OUTPUT.PUT_LINE(v_global);
DECLARE
v_local VARCHAR2(50) := \'Variável Local\';
BEGIN
DBMS_OUTPUT.PUT_LINE(v_global); -- Acessa variável do bloco externo
DBMS_OUTPUT.PUT_LINE(v_local); -- Acessa variável local
END;
DECLARE
v_nome VARCHAR2(50) := \'Nome Externo\';
BEGIN
DBMS_OUTPUT.PUT_LINE(\'Antes do bloco interno: \' || v_nome);
DECLARE
v_nome VARCHAR2(50) := \'Nome Interno\'; -- Sombreia a variável externa
BEGIN
DBMS_OUTPUT.PUT_LINE(\'Dentro do bloco interno: \' || v_nome);
END;
19.6.1 RECORD
DECLARE
TYPE t_endereco IS RECORD (
logradouro VARCHAR2(100),
numero NUMBER(10),
cidade VARCHAR2(50),
cep VARCHAR2(10)
);
v_endereco_cliente t_endereco;
v_endereco_empresa t_endereco;
BEGIN
-- Atribuindo valores aos campos do registro
v_endereco_cliente.logradouro := \'Rua das Flores\';
v_endereco_cliente.numero := 123;
v_endereco_cliente.cidade := \'São Paulo\';
v_endereco_cliente.cep := \'01234-567\';
Sistema de Tipos: PL/SQL é fortemente tipado com tipos escalares, compostos, referência e LOB.
Tipos Numéricos: NUMBER (principal), PLS_INTEGER (otimizado), subtipos como INTEGER , DECIMAL .
Escopo: Variáveis são locais ao bloco; blocos internos podem sombrear variáveis externas.
1. Declaração de Variáveis:
3. Conversões de Tipo:
4. Escopo de Variáveis:
5. Desafio - RECORD:
Defina um tipo RECORD para representar um produto com campos: id , nome , preco , categoria .
Crie uma função que calcule o preço com desconto baseado na categoria.
Referências
As estruturas de controle são fundamentais em qualquer linguagem de programação, pois permitem alterar o
fluxo de execução do programa baseado em condições ou repetir blocos de código. O PL/SQL oferece um
conjunto completo de estruturas de controle que incluem condicionais (IF-THEN-ELSE, CASE) e loops (LOOP,
WHILE, FOR). Essas estruturas transformam o PL/SQL de uma simples extensão do SQL em uma linguagem de
programação completa, capaz de implementar lógicas de negócio complexas [1].
Estruturas de Repetição (Loops): Repetem blocos de código até que uma condição seja atendida.
LOOP básico
WHILE LOOP
20.2.1 IF-THEN-ELSIF-ELSE
A estrutura IF é a forma mais básica de controle condicional. Ela avalia uma expressão booleana e executa
diferentes blocos de código baseado no resultado.
Sintaxe:
IF condicao1 THEN
-- comandos executados se condicao1 for TRUE
ELSIF condicao2 THEN
-- comandos executados se condicao2 for TRUE
ELSIF condicao3 THEN
-- comandos executados se condicao3 for TRUE
ELSE
-- comandos executados se nenhuma condição for TRUE
END IF;
Exemplo Básico:
DECLARE
v_idade NUMBER := 25;
v_categoria VARCHAR2(20);
BEGIN
IF v_idade < 18 THEN
v_categoria := \'Menor de idade\';
ELSIF v_idade >= 18 AND v_idade < 60 THEN
v_categoria := \'Adulto\';
ELSE
v_categoria := \'Idoso\';
END IF;
DECLARE
v_cliente_id NUMBER := 101;
v_total_compras NUMBER;
v_status_cliente VARCHAR2(20);
BEGIN
-- Buscar total de compras do cliente
SELECT NVL(SUM(Quantidade * PrecoUnitario), 0)
INTO v_total_compras
FROM Vendas
WHERE ClienteID = v_cliente_id;
20.2.2 IF Aninhado
O CASE statement é uma alternativa mais elegante ao IF-ELSIF quando você precisa comparar uma
expressão com múltiplos valores.
CASE Simples:
DECLARE
v_dia_semana NUMBER := TO_NUMBER(TO_CHAR(SYSDATE, \'D\')); -- 1=Domingo, 2=Segunda, etc.
v_nome_dia VARCHAR2(20);
BEGIN
CASE v_dia_semana
WHEN 1 THEN v_nome_dia := \'Domingo\';
WHEN 2 THEN v_nome_dia := \'Segunda-feira\';
WHEN 3 THEN v_nome_dia := \'Terça-feira\';
WHEN 4 THEN v_nome_dia := \'Quarta-feira\';
WHEN 5 THEN v_nome_dia := \'Quinta-feira\';
WHEN 6 THEN v_nome_dia := \'Sexta-feira\';
WHEN 7 THEN v_nome_dia := \'Sábado\';
ELSE v_nome_dia := \'Dia inválido\';
END CASE;
O LOOP básico cria um loop infinito que deve ser terminado explicitamente com EXIT .
Sintaxe:
LOOP
-- comandos a serem repetidos
EXIT [WHEN condicao];
END LOOP;
Exemplo:
DECLARE
v_contador NUMBER := 1;
BEGIN
LOOP
DBMS_OUTPUT.PUT_LINE(\'Iteração: \' || v_contador);
v_contador := v_contador + 1;
DBMS_OUTPUT.PUT_LINE(\'Loop finalizado\');
END;
/
O WHILE LOOP executa enquanto uma condição for verdadeira. A condição é avaliada antes de cada iteração.
Sintaxe:
Exemplo:
DECLARE
v_numero NUMBER := 1;
v_soma NUMBER := 0;
BEGIN
WHILE v_numero <= 10 LOOP
v_soma := v_soma + v_numero;
DBMS_OUTPUT.PUT_LINE(\'Número: \' || v_numero || \' - Soma acumulada: \' || v_soma);
v_numero := v_numero + 1;
END LOOP;
O FOR LOOP numérico é usado quando você sabe exatamente quantas iterações precisa fazer.
Sintaxe:
Exemplo Crescente:
BEGIN
DBMS_OUTPUT.PUT_LINE(\'Contagem crescente:\');
FOR i IN 1..5 LOOP
DBMS_OUTPUT.PUT_LINE(\'i = \' || i);
END LOOP;
DBMS_OUTPUT.PUT_LINE(\'Contagem decrescente:\');
FOR i IN REVERSE 1..5 LOOP
DBMS_OUTPUT.PUT_LINE(\'i = \' || i);
END LOOP;
END;
/
DECLARE
v_fatorial NUMBER := 1;
BEGIN
FOR i IN 1..5 LOOP
v_fatorial := v_fatorial * i;
DBMS_OUTPUT.PUT_LINE(i || \'! = \' || v_fatorial);
END LOOP;
END;
/
EXIT termina o loop imediatamente. EXIT WHEN termina o loop quando uma condição é verdadeira.
DECLARE
v_numero NUMBER := 1;
BEGIN
LOOP
IF v_numero = 3 THEN
DBMS_OUTPUT.PUT_LINE(\'Pulando o número 3\');
v_numero := v_numero + 1;
CONTINUE; -- Pula para a próxima iteração
END IF;
CONTINUE pula o resto da iteração atual e vai para a próxima. CONTINUE WHEN faz isso condicionalmente.
BEGIN
FOR i IN 1..10 LOOP
CONTINUE WHEN MOD(i, 2) = 0; -- Pula números pares
DBMS_OUTPUT.PUT_LINE(\'Número ímpar: \' || i);
END LOOP;
END;
/
<<loop_interno>>
FOR j IN 1..3 LOOP
DBMS_OUTPUT.PUT_LINE(\' Loop interno: \' || j);
IF i = 2 AND j = 2 THEN
DBMS_OUTPUT.PUT_LINE(\' Saindo do loop externo\');
EXIT loop_externo; -- Sai do loop externo
END IF;
END LOOP loop_interno;
END LOOP loop_externo;
DECLARE
v_produto_id NUMBER;
v_nome_produto VARCHAR2(255);
v_preco NUMBER;
v_novo_preco NUMBER;
v_contador NUMBER := 0;
BEGIN
-- Simular atualização de preços para produtos caros
FOR produto IN (SELECT ProdutoID, NomeProduto, Preco
FROM Produtos
WHERE Preco > 1000
ORDER BY Preco DESC) LOOP
v_contador := v_contador + 1;
DECLARE
TYPE t_vendas IS TABLE OF Vendas%ROWTYPE INDEX BY PLS_INTEGER;
v_vendas t_vendas;
v_total_vendas NUMBER := 0;
v_vendas_validas NUMBER := 0;
v_vendas_invalidas NUMBER := 0;
BEGIN
-- Simular validação de vendas
FOR venda IN (SELECT * FROM Vendas WHERE DataVenda >= SYSDATE - 30) LOOP
-- Validações
IF [Link] <= 0 THEN
DBMS_OUTPUT.PUT_LINE(\'Venda \' || [Link] || \': Quantidade inválida\');
v_vendas_invalidas := v_vendas_invalidas + 1;
CONTINUE;
END IF;
-- Venda válida
v_vendas_validas := v_vendas_validas + 1;
v_total_vendas := v_total_vendas + ([Link] * [Link]);
1. IF-THEN-ELSE Básico:
Crie um bloco que classifique um produto baseado no seu preço: \
Em PL/SQL, um cursor é um ponteiro para uma área de memória privada, chamada de área de contexto
(context area), que o Oracle utiliza para processar uma instrução SQL. Quando você executa uma instrução
SQL, o Oracle cria essa área de contexto que contém informações sobre a instrução, como o número de linhas
processadas, o plano de execução e os dados retornados. Um cursor é um identificador que permite acessar e
manipular essa área de contexto, linha por linha [1].
Os cursores são essenciais para processar consultas que retornam múltiplas linhas. Enquanto a instrução
SELECT INTO é limitada a consultas que retornam exatamente uma linha, os cursores permitem que você
itere sobre um conjunto de resultados de qualquer tamanho, processando cada linha individualmente.
Processamento Linha a Linha: Permitem que você execute lógica complexa para cada linha retornada
por uma consulta.
Flexibilidade: Podem ser usados com qualquer consulta SELECT que retorne múltiplas linhas.
Controle: Oferecem controle granular sobre o processamento de dados, permitindo validações, cálculos
e outras operações em cada linha.
Alternativa a Loops: São a forma padrão de processar conjuntos de resultados em PL/SQL, substituindo
a necessidade de carregar grandes volumes de dados para a memória da aplicação.
Cursores Implícitos: Criados e gerenciados automaticamente pelo Oracle para todas as instruções SQL
DML ( INSERT , UPDATE , DELETE ) e para consultas SELECT INTO . Você não os declara ou controla
diretamente, mas pode acessar seus atributos.
Cursores Explícitos: Declarados e gerenciados explicitamente pelo desenvolvedor para consultas que
retornam múltiplas linhas. Você tem controle total sobre seu ciclo de vida: DECLARE , OPEN , FETCH ,
CLOSE .
21.3 Cursores Implícitos
Sempre que você executa uma instrução DML ou uma consulta SELECT INTO , o Oracle cria um cursor
implícito chamado SQL . Você pode usar os atributos deste cursor para obter informações sobre a última
operação SQL executada.
SQL%FOUND : Retorna TRUE se a última instrução DML afetou uma ou more linhas, ou se um SELECT
INTO retornou uma linha. Caso contrário, retorna FALSE .
SQL%ISOPEN : Sempre retorna FALSE para cursores implícitos, pois o Oracle os fecha automaticamente
após a execução.
BEGIN
-- Tentar atualizar um produto
UPDATE Produtos
SET Preco = Preco * 1.10 -- Aumento de 10%
WHERE Categoria = \'Eletrônicos\';
COMMIT;
END;
/
Cursores explícitos são usados para processar consultas que retornam múltiplas linhas. Eles oferecem
controle total sobre o processo.
1. DECLARE : Declara o cursor na seção DECLARE do bloco PL/SQL, associando-o a uma consulta SELECT .
2. OPEN : Abre o cursor na seção BEGIN . Isso executa a consulta e popula a área de contexto com os
resultados.
3. FETCH : Recupera uma linha da área de contexto e a armazena em variáveis ou registros. Geralmente,
isso é feito dentro de um loop.
4. CLOSE : Fecha o cursor, liberando a área de contexto. É importante sempre fechar os cursores que você
abre para liberar recursos.
21.4.2 Sintaxe e Exemplo
DECLARE
-- 1. DECLARE: Declara o cursor e as variáveis para armazenar os dados
CURSOR cur_clientes IS
SELECT ClienteID, Nome, Sobrenome
FROM Clientes
WHERE Estado = \'SP\';
v_cliente_id [Link]%TYPE;
v_nome [Link]%TYPE;
v_sobrenome [Link]%TYPE;
BEGIN
-- 2. OPEN: Abre o cursor
OPEN cur_clientes;
LOOP
-- 3. FETCH: Recupera uma linha
FETCH cur_clientes INTO v_cliente_id, v_nome, v_sobrenome;
cursor_name%NOTFOUND : Retorna TRUE se o último FETCH não retornou uma linha (fim do conjunto de
resultados).
O Cursor FOR LOOP é uma forma mais simples e segura de trabalhar com cursores explícitos. Ele gerencia
automaticamente o ciclo de vida do cursor ( OPEN , FETCH , CLOSE ), tornando o código mais conciso e menos
propenso a erros (como esquecer de fechar o cursor).
21.5.1 Sintaxe
DECLARE
CURSOR cur_produtos IS
SELECT NomeProduto, Preco
FROM Produtos
WHERE Estoque > 0;
BEGIN
DBMS_OUTPUT.PUT_LINE(\'--- Produtos em Estoque ---\');
FOR produto_rec IN cur_produtos LOOP
DBMS_OUTPUT.PUT_LINE(\'Produto: \' || produto_rec.NomeProduto || \' - Preço: R$ \' ||
produto_rec.Preco);
END LOOP;
END;
/
Você pode definir a consulta diretamente no FOR LOOP , sem precisar declarar o cursor explicitamente.
BEGIN
DBMS_OUTPUT.PUT_LINE(\'--- Vendas do Mês ---\');
FOR venda_rec IN (SELECT VendaID, DataVenda, Quantidade * PrecoUnitario AS ValorTotal
FROM Vendas
WHERE DataVenda >= TRUNC(SYSDATE, \'MM\')) LOOP
DBMS_OUTPUT.PUT_LINE(\'Venda: \' || venda_rec.VendaID ||
\' - Data: \' || TO_CHAR(venda_rec.DataVenda, \'DD/MM/YYYY\') ||
\' - Valor: R$ \' || venda_rec.ValorTotal);
END LOOP;
END;
/
Você pode criar cursores que aceitam parâmetros, tornando-os mais flexíveis e reutilizáveis.
21.6.1 Sintaxe
DECLARE
CURSOR cur_vendas_cliente (p_cliente_id NUMBER, p_ano NUMBER) IS
SELECT VendaID, DataVenda, Quantidade * PrecoUnitario AS ValorTotal
FROM Vendas
WHERE ClienteID = p_cliente_id
AND EXTRACT(YEAR FROM DataVenda) = p_ano;
BEGIN
DBMS_OUTPUT.PUT_LINE(\'--- Vendas do Cliente 101 em 2025 ---\');
FOR venda_rec IN cur_vendas_cliente(101, 2025) LOOP
DBMS_OUTPUT.PUT_LINE(\'Venda: \' || venda_rec.VendaID || \' - Valor: R$ \' ||
venda_rec.ValorTotal);
END LOOP;
A cláusula FOR UPDATE em um cursor permite bloquear as linhas selecionadas, impedindo que outras sessões
as modifiquem até que sua transação seja confirmada ( COMMIT ) ou desfeita ( ROLLBACK ). Isso é crucial para
evitar condições de corrida quando você precisa ler e depois atualizar os mesmos dados.
21.7.1 Sintaxe
CURSOR cursor_name IS
SELECT ...
FROM ...
FOR UPDATE [OF coluna1, coluna2] [NOWAIT];
NOWAIT : (Opcional) Faz com que o OPEN do cursor falhe imediatamente se as linhas já estiverem
bloqueadas por outra sessão, em vez de esperar.
Para atualizar ou excluir a linha que acabou de ser recuperada ( FETCH ) pelo cursor, use a cláusula WHERE
CURRENT OF .
21.7.3 Exemplo
DECLARE
CURSOR cur_produtos_reajuste IS
SELECT ProdutoID, Preco
FROM Produtos
WHERE Categoria = \'Livros\'
FOR UPDATE OF Preco;
BEGIN
FOR produto_rec IN cur_produtos_reajuste LOOP
-- Aplicar um reajuste de 5% no preço
UPDATE Produtos
SET Preco = produto_rec.Preco * 1.05
WHERE CURRENT OF cur_produtos_reajuste;
END LOOP;
COMMIT;
DBMS_OUTPUT.PUT_LINE(\'Preços dos livros reajustados.\');
END;
/
Um REF CURSOR é um tipo de dado que armazena um ponteiro para um cursor. Diferentemente dos cursores
explícitos, que são estáticos (associados a uma única consulta), os REF CURSORs são dinâmicos e podem ser
associados a diferentes consultas em tempo de execução. Eles são frequentemente usados para passar
conjuntos de resultados entre subprogramas (procedures e functions) e para retornar resultados de uma
procedure para uma aplicação cliente (como Java ou .NET).
Strong REF CURSOR: Associado a uma estrutura de registro específica. Oferece segurança de tipo.
Weak REF CURSOR: Não associado a nenhuma estrutura. Mais flexível, mas menos seguro. O tipo
SYS_REFCURSOR é um REF CURSOR fraco pré-definido.
21.8.2 Exemplo de Uso (Retornando de uma Procedure)
CLOSE v_cursor;
END;
/
Cursor: Ponteiro para uma área de memória que processa uma instrução SQL.
Cursores Implícitos: Gerenciados pelo Oracle para DML e SELECT INTO . Atributos: SQL%FOUND ,
SQL%NOTFOUND , SQL%ROWCOUNT .
Cursores Explícitos: Declarados pelo desenvolvedor para consultas multi-linha. Ciclo de vida: DECLARE ,
OPEN , FETCH , CLOSE .
Cursor FOR LOOP: Forma simplificada e segura de processar cursores, gerenciando automaticamente o
ciclo de vida.
REF CURSOR : Tipo de dado que armazena um ponteiro para um cursor, usado para passar conjuntos de
resultados dinamicamente.
Declare um cursor explícito para selecionar todos os produtos com estoque abaixo de 10.
Use um loop LOOP-FETCH-EXIT para processar o cursor e exibir o nome e o estoque de cada
produto.
Use o cursor para listar todos os produtos de uma categoria específica que custam menos que o
preço máximo.
Crie um cursor com FOR UPDATE para selecionar todos os clientes com status \'Inativo\'.
Dentro do loop, atualize o status desses clientes para \'Arquivado\' usando WHERE CURRENT OF .
A procedure deve retornar um REF CURSOR com todas as vendas para aquele produto.
Crie um bloco anônimo para chamar a procedure e processar o REF CURSOR retornado, exibindo os
detalhes de cada venda.
Referências
Subprogramas são blocos de código PL/SQL nomeados que podem ser armazenados no banco de dados e
reutilizados por múltiplas aplicações. Eles são fundamentais para a modularização, reutilização e
manutenção de código. O PL/SQL oferece dois tipos principais de subprogramas: Procedures e Functions.
Ambos permitem encapsular lógica de negócio complexa, promover a reutilização de código e melhorar a
performance através da compilação e armazenamento no banco de dados [1].
Segurança: Podem ser usados para controlar acesso aos dados através de interfaces bem definidas.
Retorno Não retorna valor (pode ter parâmetros OUT) Sempre retorna um valor
Chamada Chamada como uma instrução independente Chamada como parte de uma expressão
Uso em SQL Não pode ser usada em instruções SQL Pode ser usada em instruções SQL
22.2 Procedures
Uma procedure é um subprograma que executa uma ação específica. Ela pode aceitar parâmetros de entrada,
processar dados e retornar resultados através de parâmetros de saída, mas não retorna um valor diretamente
como uma function.
IN : Parâmetro de entrada (padrão). O valor é passado para a procedure, mas não pode ser modificado
dentro dela.
OUT : Parâmetro de saída. A procedure atribui um valor a este parâmetro, que é retornado ao chamador.
IN OUT : Parâmetro de entrada e saída. O valor é passado para a procedure e pode ser modificado
dentro dela.
22.2.3 Exemplo Simples de Procedure
-- Chamando a procedure
BEGIN
exibir_mensagem(\'Olá, mundo!\');
END;
/
-- Chamando a procedure
DECLARE
v_total NUMBER;
v_numero NUMBER;
v_ticket NUMBER;
BEGIN
calcular_estatisticas_cliente(101, v_total, v_numero, v_ticket);
-- Chamando a procedure
DECLARE
v_preco NUMBER := 1000;
BEGIN
DBMS_OUTPUT.PUT_LINE(\'Preço original: R$ \' || v_preco);
aplicar_desconto(v_preco, 15); -- 15% de desconto
DBMS_OUTPUT.PUT_LINE(\'Preço com desconto: R$ \' || v_preco);
END;
/
22.3 Functions
Uma function é um subprograma que sempre retorna um valor. Ela pode aceitar parâmetros de entrada e
deve conter pelo menos uma instrução RETURN que especifica o valor a ser retornado.
-- Chamando a function
BEGIN
DBMS_OUTPUT.PUT_LINE(\'O quadrado de 5 é: \' || calcular_quadrado(5));
END;
/
RETURN v_categoria;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN \'Inexistente\';
WHEN OTHERS THEN
RETURN \'Erro\';
END obter_categoria_cliente;
/
-- Usando a function
SELECT
ClienteID,
Nome,
obter_categoria_cliente(ClienteID) AS Categoria
FROM Clientes
WHERE ClienteID <= 105;
22.3.4 Function com Múltiplos RETURN
-- Testando a function
BEGIN
DBMS_OUTPUT.PUT_LINE(\'Imposto sobre livro de R$` 100: R`$ \' || calcular_imposto(100,
\'Livros\'));
DBMS_OUTPUT.PUT_LINE(\'Imposto sobre eletrônico de R$` 1000: R`$ \' || calcular_imposto(1000,
\'Eletrônicos\'));
END;
/
Você pode definir valores padrão para parâmetros, tornando-os opcionais na chamada do subprograma.
Você pode passar parâmetros por nome, o que torna o código mais legível e permite pular parâmetros com
valores padrão.
-- Chamada usando nomes de parâmetros
BEGIN
DBMS_OUTPUT.PUT_LINE(\'Desconto: R$ \' ||
calcular_desconto(p_valor => 1000, p_valor_minimo => 500, p_percentual => 25));
END;
/
Você pode definir procedures e functions dentro de outros blocos PL/SQL, tornando-os locais a esse bloco.
DECLARE
-- Function local
FUNCTION eh_par (p_numero NUMBER) RETURN BOOLEAN
IS
BEGIN
RETURN MOD(p_numero, 2) = 0;
END eh_par;
-- Procedure local
PROCEDURE processar_numero (p_numero NUMBER)
IS
BEGIN
IF eh_par(p_numero) THEN
DBMS_OUTPUT.PUT_LINE(p_numero || \' é par\');
ELSE
DBMS_OUTPUT.PUT_LINE(p_numero || \' é ímpar\');
END IF;
END processar_numero;
BEGIN
FOR i IN 1..5 LOOP
processar_numero(i);
END LOOP;
END;
/
RETURN v_preco;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE(\'Produto \' || p_produto_id || \' não encontrado\');
RETURN -1; -- Valor indicando erro
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(\'Erro inesperado: \' || SQLERRM);
RETURN -1;
END obter_preco_produto;
/
Procedures: Executam ações; não retornam valores diretamente; usam parâmetros OUT.
Passagem por Nome: Permite especificar parâmetros por nome para maior clareza.
Subprogramas Locais: Podem ser definidos dentro de blocos para uso local.
1. Procedure Simples:
Crie uma procedure que aceite um ClienteID e exiba todas as informações do cliente.
2. Function de Cálculo:
Crie uma function que calcule o valor total de vendas para um cliente específico.
Use esta function em uma consulta SQL para mostrar o nome do cliente e seu total de vendas.
Crie uma procedure que aceite um ProdutoID e retorne (via parâmetros OUT) o nome do produto,
preço e estoque.
Retorne um código de status (0 = sucesso, 1 = cliente não existe, 2 = produto não existe, 3 =
estoque insuficiente).
Referências
Um Package (Pacote) em PL/SQL é um grupo de tipos de dados, variáveis, constantes, cursores, exceções,
procedures e functions logicamente relacionados. Ele permite organizar e encapsular o código PL/SQL,
tornando-o mais modular, manutenível e reutilizável. Packages são uma das características mais poderosas
do PL/SQL para o desenvolvimento de aplicações robustas e escaláveis [1].
Think of a package as a container for related PL/SQL code. Instead of having individual procedures and
functions scattered across your schema, you can group them into a single, named unit. This not only improves
organization but also offers significant advantages in terms of performance, security, and development
efficiency.
Performance: Quando um componente de um package é chamado pela primeira vez, o Oracle carrega o
package inteiro na memória (SGA - System Global Area). Chamadas subsequentes a outros componentes
do mesmo package são mais rápidas, pois o código já está em memória.
Estado Global: Packages podem manter o estado de variáveis e cursores entre chamadas de
procedures/functions dentro da mesma sessão, o que é útil para gerenciar informações de sessão ou
caches.
v_contador_interno := v_contador_interno + 1;
DBMS_OUTPUT.PUT_LINE(
p_nome ||
\' inserido. Total de inserções nesta sessão: \' ||
v_contador_interno
);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
DBMS_OUTPUT.PUT_LINE(
\'Erro: Email \' || p_email || \' já existe.\'
);
WHEN e_cliente_invalido THEN
DBMS_OUTPUT.PUT_LINE(
\'Erro: Nome ou Email não podem ser nulos.\'
);
END inserir_cliente;
RETURN v_total;
END obter_total_vendas;
Privadas: Visíveis apenas dentro do corpo do package. Mantêm seu valor durante a sessão.
23.3.3 Cursores
Podem ser declarados na especificação para serem abertos e buscados externamente, ou declarados e usados
apenas no corpo.
23.3.4 Exceções
Opcional, no final do corpo do package. É executado automaticamente apenas uma vez por sessão, na
primeira vez que qualquer componente do package é referenciado. Útil para inicializar variáveis globais ou
carregar dados de configuração.
PL/SQL permite sobrecarregar procedures e functions dentro do mesmo package. Isso significa que você pode
ter múltiplos subprogramas com o mesmo nome, desde que suas assinaturas (número, tipo e ordem dos
parâmetros) sejam diferentes. O compilador PL/SQL determina qual versão chamar com base nos argumentos
fornecidos [2].
-- Testando o overloading
BEGIN
DBMS_OUTPUT.PUT_LINE(\'Soma de números: \' || pkg_calculos.somar(10, 20));
DBMS_OUTPUT.PUT_LINE(\'Soma de textos: \' || pkg_calculos.somar(\'Olá, \', \'Mundo!\'));
pkg_calculos.exibir_valor(123);
pkg_calculos.exibir_valor(\'Exemplo de texto\');
END;
/
23.6 Gerenciamento de Packages
SELECT text
FROM user_source
WHERE name = \'NOME_PACKAGE\' AND type = \'PACKAGE BODY\'
ORDER BY line;
Para remover um package, você pode remover apenas o corpo ou o package inteiro (especificação e corpo).
Importante: Se você remover apenas o corpo, a especificação permanece, mas o package se torna inválido.
Se você remover a especificação, o corpo é automaticamente removido.
Se você alterar o corpo de um package, ele será recompilado automaticamente na próxima chamada. Se você
alterar a especificação, todos os objetos dependentes (outros packages, procedures, functions que chamam
componentes deste package) se tornarão inválidos e precisarão ser recompilados.
O Oracle Database vem com uma vasta coleção de packages built-in que fornecem funcionalidades essenciais
para o desenvolvimento PL/SQL. Alguns dos mais importantes incluem:
Bloco de Inicialização: Executado uma vez por sessão na primeira referência ao package.
Acesso: nome_package.nome_componente .
Na especificação, declare uma procedure exibir_data_atual que não recebe parâmetros e uma
function obter_dia_semana que recebe uma data e retorna o nome do dia da semana.
No corpo, implemente ambas as procedures para exibir a mensagem e o nível (se fornecido).
Exceção e_produto_nao_encontrado .
Referências
Um Trigger (Gatilho) é um tipo especial de procedure PL/SQL que é executada automaticamente pelo Oracle
Database em resposta a eventos específicos que ocorrem no banco de dados. Diferentemente de procedures e
functions normais, que são chamadas explicitamente, os triggers são invocados implicitamente quando
determinadas condições são atendidas. Eles são fundamentais para implementar regras de negócio
complexas, auditoria, validação de dados e manutenção da integridade referencial [1].
Os triggers são "invisíveis" para as aplicações cliente - eles executam nos bastidores sempre que os eventos
que os acionam ocorrem. Isso os torna ideais para implementar lógica que deve ser aplicada
consistentemente, independentemente de como os dados são modificados (via SQL*Plus, aplicações Java,
.NET, etc.).
Não podem ser chamados diretamente: Diferentemente de procedures, você não pode executar um
trigger manualmente.
Não têm parâmetros: Não aceitam parâmetros de entrada, mas podem acessar dados através de
pseudo-registros especiais.
Auditoria: Registrar quem, quando e que alterações foram feitas nos dados.
Validação Complexa: Implementar regras de negócio que não podem ser expressas através de
constraints simples.
DML Triggers: Acionados por operações DML ( INSERT , UPDATE , DELETE ) em tabelas ou views.
DDL Triggers: Acionados por operações DDL ( CREATE , ALTER , DROP ) no esquema ou banco de dados.
Database Event Triggers: Acionados por eventos do banco de dados como STARTUP , SHUTDOWN , LOGON ,
LOGOFF .
BEFORE: Executado antes da operação DML. Pode modificar os valores que estão sendo
inseridos/atualizados.
AFTER: Executado após a operação DML. Útil para auditoria e ações que dependem da conclusão da
operação.
INSTEAD OF: Usado apenas com views. Substitui a operação DML padrão por lógica customizada.
Statement-Level (Nível de Instrução): Executado uma vez por instrução SQL, independentemente do
número de linhas afetadas.
Row-Level (Nível de Linha): Executado uma vez para cada linha afetada pela instrução SQL. Identificado pela
cláusula FOR EACH ROW .
Em triggers de nível de linha, você pode acessar os valores das colunas antes e depois da modificação através
dos pseudo-registros :OLD e :NEW .
:OLD : Contém os valores das colunas antes da modificação (disponível em UPDATE e DELETE ).
:NEW : Contém os valores das colunas após a modificação (disponível em INSERT e UPDATE ).
24.4.1 Disponibilidade dos Pseudo-Registros
Dentro de um trigger que responde a múltiplos eventos, você pode usar predicados para determinar qual
operação acionou o trigger:
UPDATING(\'coluna\') : Retorna TRUE se o trigger foi acionado por um UPDATE que modificou a coluna
especificada.
Triggers de statement-level executam uma vez por instrução SQL, independentemente do número de linhas
afetadas.
-- Trigger para log de operações em massa
CREATE OR REPLACE TRIGGER trg_log_operacoes_produtos
AFTER INSERT OR UPDATE OR DELETE
ON Produtos
DECLARE
v_operacao VARCHAR2(10);
v_linhas_afetadas NUMBER;
BEGIN
-- Determinar operação e número de linhas afetadas
IF INSERTING THEN
v_operacao := \'INSERT\';
ELSIF UPDATING THEN
v_operacao := \'UPDATE\';
ELSIF DELETING THEN
v_operacao := \'DELETE\';
END IF;
v_linhas_afetadas := SQL%ROWCOUNT;
-- Log da operação
INSERT INTO LogOperacoes (
DataOperacao,
Usuario,
Tabela,
Operacao,
LinhasAfetadas
) VALUES (
SYSDATE,
USER,
\'PRODUTOS\',
v_operacao,
v_linhas_afetadas
);
DBMS_OUTPUT.PUT_LINE(
\'Operação \' || v_operacao || \' executada em \' ||
v_linhas_afetadas || \' linha(s) da tabela PRODUTOS\'
);
END trg_log_operacoes_produtos;
/
24.9.1 Performance
Ocorre quando um trigger de linha tenta consultar ou modificar a mesma tabela que o acionou. Pode ser
resolvido usando triggers de statement-level ou técnicas avançadas como compound triggers.
Triggers podem acionar outros triggers, criando uma cascata. Tenha cuidado para evitar loops infinitos.
Crie um trigger que registre todas as operações (INSERT, UPDATE, DELETE) na tabela Produtos .
2. Trigger de Validação:
Crie um trigger que só execute para vendas com valor total superior a R$ 1000.
No Capítulo 18, introduzimos a seção EXCEPTION em blocos PL/SQL como um mecanismo para lidar com
erros de forma controlada. O tratamento de exceções permite que um programa PL/SQL reaja a condições de
erro, em vez de falhar abruptamente. Isso é crucial para a robustez e confiabilidade de qualquer aplicação de
banco de dados [1].
[DECLARE]
-- Declarações
BEGIN
-- Comandos executáveis
EXCEPTION
WHEN excecao1 THEN
-- Tratamento para excecao1
WHEN excecao2 THEN
-- Tratamento para excecao2
WHEN OTHERS THEN
-- Tratamento para qualquer outra exceção não tratada
END;
/
25.1.1 Exceções Pré-Definidas
O Oracle PL/SQL possui várias exceções pré-definidas para erros comuns, como NO_DATA_FOUND ,
TOO_MANY_ROWS , DUP_VAL_ON_INDEX , ZERO_DIVIDE , VALUE_ERROR , entre outras. Elas são levantadas
automaticamente pelo sistema quando a condição de erro correspondente ocorre.
DECLARE
v_nome_cliente [Link]%TYPE;
BEGIN
SELECT Nome INTO v_nome_cliente
FROM Clientes
WHERE ClienteID = 9999; -- ID inexistente
Além das exceções pré-definidas, você pode definir suas próprias exceções para lidar com condições de erro
específicas da sua lógica de negócio. Existem duas maneiras de fazer isso:
Você declara uma exceção na seção DECLARE e a levanta explicitamente usando a instrução RAISE .
DECLARE
e_estoque_insuficiente EXCEPTION; -- Declaração da exceção
v_produto_id [Link]%TYPE := 1;
v_quantidade_desejada NUMBER := 100;
v_estoque_atual [Link]%TYPE;
BEGIN
SELECT Estoque INTO v_estoque_atual
FROM Produtos
WHERE ProdutoID = v_produto_id;
Você pode associar uma exceção nomeada a um código de erro Oracle específico (que não possui uma
exceção pré-definida). Isso permite tratar erros de sistema de forma mais legível.
DECLARE
e_tabela_bloqueada EXCEPTION;
PRAGMA EXCEPTION_INIT(e_tabela_bloqueada, -00054); -- ORA-00054: resource busy and acquire with
NOWAIT specified or timeout expired
BEGIN
-- Simular uma operação que pode causar o erro ORA-00054
-- Por exemplo, tentar adquirir um lock exclusivo em uma tabela já bloqueada
-- LOCK TABLE Clientes IN EXCLUSIVE MODE NOWAIT;
Para levantar erros definidos pelo usuário que podem ser propagados para a aplicação cliente, use
RAISE_APPLICATION_ERROR . Esta procedure built-in permite definir um código de erro (entre -20000 e -20999)
e uma mensagem de erro personalizada.
DECLARE
v_idade NUMBER := 15;
BEGIN
IF v_idade < 18 THEN
RAISE_APPLICATION_ERROR(-20001, "Idade mínima de 18 anos não atingida.");
END IF;
DBMS_OUTPUT.PUT_LINE("Acesso concedido.");
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE("Erro capturado: " || SQLERRM);
END;
/
Quando uma exceção é levantada em um bloco PL/SQL e não é tratada nesse bloco, ela se propaga para o
bloco PL/SQL que o chamou (bloco pai). Esse processo continua até que a exceção seja tratada por um
manipulador de exceções ou até que atinja o bloco mais externo, caso em que o programa termina e a
transação é revertida.
DECLARE
e_erro_interno EXCEPTION;
BEGIN
DBMS_OUTPUT.PUT_LINE("Início do bloco externo.");
Dentro da seção EXCEPTION , você pode usar as funções built-in SQLCODE e SQLERRM para obter informações
sobre o erro que ocorreu.
SQLCODE : Retorna o código de erro numérico do Oracle (um número negativo para erros Oracle, 1 para
NO_DATA_FOUND , 0 para sucesso).
DECLARE
v_numero NUMBER;
BEGIN
v_numero := 10 / 0; -- Causa ZERO_DIVIDE
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE("Código de Erro: " || SQLCODE);
DBMS_OUTPUT.PUT_LINE("Mensagem de Erro: " || SQLERRM);
END;
/
Às vezes, você pode querer tratar uma exceção em um bloco, mas também permitir que ela se propague para
o bloco pai. Isso é feito usando a instrução RAISE sem um nome de exceção na seção EXCEPTION .
DECLARE
v_produto_id NUMBER := 9999;
v_nome_produto VARCHAR2(100);
BEGIN
BEGIN -- Bloco interno
SELECT NomeProduto INTO v_nome_produto
FROM Produtos
WHERE ProdutoID = v_produto_id;
O tratamento de exceções em procedures e functions segue os mesmos princípios dos blocos anônimos. É
uma boa prática incluir uma seção EXCEPTION em todos os subprogramas para lidar com erros de forma
controlada e evitar que exceções não tratadas se propaguem para a aplicação cliente.
BEGIN
DBMS_OUTPUT.PUT_LINE("Resultado da divisão (10/2): " || dividir_numeros(10, 2));
DBMS_OUTPUT.PUT_LINE("Resultado da divisão (10/0): " || dividir_numeros(10, 0));
END;
/
Quando uma exceção não tratada ocorre em um bloco PL/SQL, a transação atual é automaticamente
revertida ( ROLLBACK ). Se a exceção for tratada, o PL/SQL não faz um ROLLBACK automático; você deve
gerenciar a transação explicitamente com COMMIT ou ROLLBACK .
BEGIN
INSERT INTO Clientes (ClienteID, Nome, Email) VALUES (1000, 'Teste Erro', 'teste@[Link]');
Transações: Exceções não tratadas causam ROLLBACK automático; exceções tratadas exigem
COMMIT / ROLLBACK explícito.
Crie um bloco que tente selecionar o nome de um cliente usando SELECT INTO .
3. RAISE_APPLICATION_ERROR :
Crie uma procedure validar_idade que recebe uma idade como parâmetro.
Se a idade for menor que 18, use RAISE_APPLICATION_ERROR para levantar um erro com código
-20005 e uma mensagem personalizada.
4. Propagação e Relançamento:
Trate a ZERO_DIVIDE no bloco interno, exiba uma mensagem e relance a exceção ( RAISE ).
Crie um bloco que insira um novo pedido (tabela Pedidos ) e, em seguida, insira os itens do pedido
(tabela ItensPedido ).
Implemente tratamento de exceções para que, se qualquer item do pedido falhar, toda a transação
(pedido e itens) seja revertida.
Use SAVEPOINT para tentar reverter apenas o item que falhou, mas se o erro for irrecuperável,
reverta a transação inteira.
Referências
SQL Dinâmico é a capacidade de construir e executar instruções SQL em tempo de execução, em vez de ter
instruções SQL fixas (estáticas) no código. Isso permite criar aplicações mais flexíveis que podem adaptar suas
consultas e operações baseadas em condições de runtime, entrada do usuário ou configurações do sistema. O
PL/SQL oferece duas abordagens principais para SQL dinâmico: Native Dynamic SQL (usando EXECUTE
IMMEDIATE ) e DBMS_SQL package [1].
26.1.1 Quando Usar SQL Dinâmico
Nomes de Objetos Variáveis: Quando o nome da tabela, coluna ou outro objeto de banco de dados é
determinado em tempo de execução.
Consultas Flexíveis: Para construir consultas com diferentes cláusulas WHERE , ORDER BY ou GROUP BY
baseadas na entrada do usuário.
DDL Dinâmico: Para executar comandos DDL ( CREATE , ALTER , DROP ) programaticamente.
Aplicações Genéricas: Para criar ferramentas que trabalham com diferentes estruturas de banco de
dados.
Vantagens: * Flexibilidade máxima na construção de SQL * Permite criar aplicações genéricas e reutilizáveis *
Possibilita a execução de DDL em PL/SQL * Útil para ferramentas de administração e migração
Desvantagens: * Performance inferior ao SQL estático (sem otimização em tempo de compilação) * Maior
complexidade de código e debugging * Vulnerabilidade a SQL Injection se não usado corretamente * Perda de
verificação de sintaxe em tempo de compilação
EXECUTE IMMEDIATE é a forma mais simples e comumente usada de SQL dinâmico. Permite executar uma
string SQL construída dinamicamente.
DDL Dinâmico:
DECLARE
v_table_name VARCHAR2(30) := \'TEMP_VENDAS_2025\';
v_sql VARCHAR2(1000);
BEGIN
-- Construir comando CREATE TABLE dinamicamente
v_sql := \'CREATE TABLE \' || v_table_name || \' (\' ||
\'VendaID NUMBER PRIMARY KEY, \' ||
\'ClienteID NUMBER, \' ||
\'DataVenda DATE, \' ||
\'Valor NUMBER(10,2))\';
Bind variables são fundamentais para performance e segurança em SQL dinâmico. Elas permitem que o
Oracle reutilize planos de execução e previnem SQL injection.
DECLARE
v_sql VARCHAR2(500);
v_cliente_id NUMBER := 102;
v_novo_email VARCHAR2(100) := \'[Link]@[Link]\';
v_linhas_afetadas NUMBER;
BEGIN
-- UPDATE dinâmico com bind variables
v_sql := \'UPDATE Clientes SET Email = :novo_email WHERE ClienteID = :cliente_id\';
v_linhas_afetadas := SQL%ROWCOUNT;
DBMS_OUTPUT.PUT_LINE(\'Linhas atualizadas: \' || v_linhas_afetadas);
DECLARE
v_sql VARCHAR2(500);
v_cliente_id NUMBER := 101;
v_total_vendas NUMBER;
v_numero_vendas NUMBER;
BEGIN
-- Chamar uma procedure dinamicamente com parâmetros OUT
v_sql := \'BEGIN calcular_estatisticas_cliente(:p_cliente_id, :p_total, :p_numero); END;\';
v_cliente_id NUMBER;
v_nome VARCHAR2(100);
v_email VARCHAR2(255);
BEGIN
-- Construir a consulta base
v_sql := \'SELECT ClienteID, Nome, Email FROM Clientes\';
v_count := v_count + 1;
DBMS_OUTPUT.PUT_LINE(v_count || \'. \' || v_nome || \' (\' || v_email || \')\');
END LOOP;
CLOSE v_cursor;
-- Testando a procedure
BEGIN
buscar_clientes_dinamico(p_nome => \'Silva\', p_estado => \'SP\');
END;
/
26.3.2 ORDER BY Dinâmico
v_produto_id NUMBER;
v_nome_produto VARCHAR2(255);
v_preco NUMBER;
BEGIN
-- Validar parâmetros de ordenação
v_ordem := CASE UPPER(p_ordem_por)
WHEN \'ID\' THEN \'ProdutoID\'
WHEN \'NOME\' THEN \'NomeProduto\'
WHEN \'PRECO\' THEN \'Preco\'
ELSE \'NomeProduto\'
END;
Para casos mais complexos, o package DBMS_SQL oferece maior controle sobre a execução de SQL dinâmico. É
especialmente útil quando você precisa de múltiplas execuções da mesma instrução com diferentes bind
variables.
DECLARE
v_cursor_id NUMBER;
v_sql VARCHAR2(1000);
v_rows_processed NUMBER;
v_cliente_id NUMBER;
v_nome VARCHAR2(100);
v_email VARCHAR2(255);
BEGIN
-- 1. Abrir cursor
v_cursor_id := DBMS_SQL.OPEN_CURSOR;
-- 2. Preparar SQL
v_sql := \'SELECT ClienteID, Nome, Email FROM Clientes WHERE Estado = :estado\';
DBMS_SQL.PARSE(v_cursor_id, v_sql, DBMS_SQL.NATIVE);
-- 4. Bind variable
DBMS_SQL.BIND_VARIABLE(v_cursor_id, \':estado\', \'SP\');
-- 5. Executar
v_rows_processed := DBMS_SQL.EXECUTE(v_cursor_id);
-- 6. Buscar resultados
DBMS_OUTPUT.PUT_LINE(\'--- Clientes de SP ---\');
LOOP
IF DBMS_SQL.FETCH_ROWS(v_cursor_id) > 0 THEN
DBMS_SQL.COLUMN_VALUE(v_cursor_id, 1, v_cliente_id);
DBMS_SQL.COLUMN_VALUE(v_cursor_id, 2, v_nome);
DBMS_SQL.COLUMN_VALUE(v_cursor_id, 3, v_email);
-- 7. Fechar cursor
DBMS_SQL.CLOSE_CURSOR(v_cursor_id);
EXCEPTION
WHEN OTHERS THEN
IF DBMS_SQL.IS_OPEN(v_cursor_id) THEN
DBMS_SQL.CLOSE_CURSOR(v_cursor_id);
END IF;
RAISE;
END;
/
SQL Injection é uma vulnerabilidade séria que pode ocorrer quando entrada do usuário é concatenada
diretamente em strings SQL. Sempre use bind variables para prevenir este problema.
26.5.1 Exemplo Vulnerável (NÃO FAÇA ISSO)
Para melhor performance, reutilize cursores quando executar a mesma instrução múltiplas vezes:
DECLARE
v_cursor_id NUMBER;
v_sql VARCHAR2(500);
v_rows_processed NUMBER;
DBMS_SQL.CLOSE_CURSOR(v_cursor_id);
COMMIT;
END;
/
EXECUTE IMMEDIATE : Forma mais simples de SQL dinâmico (Native Dynamic SQL).
Crie um bloco que aceite um nome de tabela como variável e crie uma tabela de backup com o
sufixo "_BACKUP".
Crie uma procedure que aceite parâmetros opcionais para filtrar produtos (categoria, preço
mínimo, preço máximo).
Crie uma procedure que aceite o nome de uma tabela, nome de uma coluna, novo valor e condição
WHERE.
4. Relatório Dinâmico:
Crie uma procedure que gere um relatório de vendas com ordenação dinâmica.
Permita ordenar por data, cliente, produto ou valor, em ordem crescente ou decrescente.
Valide que o comando é seguro (apenas SELECT, INSERT, UPDATE, DELETE permitidos).
Referências
A performance de um sistema de banco de dados é influenciada por vários fatores: design do banco de dados,
qualidade das consultas SQL, configuração do sistema, hardware disponível e padrões de acesso aos dados.
Este capítulo foca nas técnicas que desenvolvedores podem aplicar para otimizar consultas SQL e código
PL/SQL.
Um plano de execução é a estratégia que o Oracle Database usa para executar uma consulta SQL. Entender
como ler e interpretar planos de execução é fundamental para otimização de performance.
-- Visualizar o plano
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
Usando AUTOTRACE:
Operation: O tipo de operação (TABLE ACCESS, INDEX SCAN, HASH JOIN, etc.)
SELECT
LPAD(\' \', 2 * LEVEL) || operation || \' \' || options AS "Operação",
object_name AS "Objeto",
cost AS "Custo",
cardinality AS "Linhas"
FROM plan_table
START WITH id = 0
CONNECT BY PRIOR id = parent_id
ORDER BY id;
v_cliente_ids t_cliente_ids;
v_novos_emails t_novos_emails;
BEGIN
-- Preparar dados
v_cliente_ids(1) := 101; v_novos_emails(1) := \'novo1@[Link]\';
v_cliente_ids(2) := 102; v_novos_emails(2) := \'novo2@[Link]\';
v_cliente_ids(3) := 103; v_novos_emails(3) := \'novo3@[Link]\';
DECLARE
TYPE t_clientes IS TABLE OF Clientes%ROWTYPE;
v_clientes t_clientes;
CURSOR cur_clientes IS
SELECT * FROM Clientes WHERE Estado = \'SP\';
BEGIN
-- Buscar todos os registros de uma vez
OPEN cur_clientes;
FETCH cur_clientes BULK COLLECT INTO v_clientes;
CLOSE cur_clientes;
-- Processar os dados
FOR i IN 1..v_clientes.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(\'Cliente: \' || v_clientes(i).Nome);
-- Processar cada cliente...
END LOOP;
DECLARE
TYPE t_vendas IS TABLE OF Vendas%ROWTYPE;
v_vendas t_vendas;
CURSOR cur_vendas IS
SELECT * FROM Vendas WHERE DataVenda >= DATE \'2025-01-01\';
BEGIN
OPEN cur_vendas;
LOOP
-- Processar em lotes de 1000 registros
FETCH cur_vendas BULK COLLECT INTO v_vendas LIMIT 1000;
IF g_cache_produtos.EXISTS(p_produto_id) THEN
RETURN g_cache_produtos(p_produto_id);
ELSE
RAISE NO_DATA_FOUND;
END IF;
END obter_produto;
END pkg_cache_produtos;
/
-- Usando o cache
DECLARE
v_produto Produtos%ROWTYPE;
BEGIN
v_produto := pkg_cache_produtos.obter_produto(1);
DBMS_OUTPUT.PUT_LINE(\'Produto: \' || v_produto.NomeProduto);
END;
/
Usando DBMS_UTILITY.GET_TIME:
DECLARE
v_start_time NUMBER;
v_end_time NUMBER;
v_elapsed_time NUMBER;
BEGIN
v_start_time := DBMS_UTILITY.GET_TIME;
v_end_time := DBMS_UTILITY.GET_TIME;
v_elapsed_time := v_end_time - v_start_time;
DECLARE
v_start_time TIMESTAMP;
v_end_time TIMESTAMP;
v_elapsed INTERVAL DAY TO SECOND;
BEGIN
v_start_time := SYSTIMESTAMP;
v_end_time := SYSTIMESTAMP;
v_elapsed := v_end_time - v_start_time;
v_start_time := SYSTIMESTAMP;
-- Executar SQL
EXECUTE IMMEDIATE p_sql_statement;
v_end_time := SYSTIMESTAMP;
-- Exibir resultados
DBMS_OUTPUT.PUT_LINE(\'=== Estatísticas para: \' || p_nome_teste || \' ===\');
DBMS_OUTPUT.PUT_LINE(\'Tempo: \' || (v_end_time - v_start_time));
DBMS_OUTPUT.PUT_LINE(\'Logical Reads: \' || (v_logical_reads_after - v_logical_reads_before));
DBMS_OUTPUT.PUT_LINE(\'Physical Reads: \' || (v_physical_reads_after - v_physical_reads_before));
END;
/
27.6 Técnicas Avançadas de Otimização
DBMS_OUTPUT.PUT_LINE(
TO_CHAR(v_data_atual, \'MM/YYYY\') || \': R$ \' || v_vendas_mes
);
2. Otimização de Loop:
3. Cache de Dados:
4. Bulk Operations:
Antes e depois da execução, capture estatísticas de sessão (logical reads, physical reads, CPU time).
Este capítulo aborda funcionalidades avançadas e especializadas do Oracle Database que são essenciais para
desenvolvedores que desejam dominar completamente o SQL e PL/SQL. Estes tópicos incluem recursos que
podem não ser usados diariamente, mas são fundamentais para resolver problemas complexos e
implementar soluções sofisticadas [1].
O Oracle oferece uma sintaxe especial para consultar dados hierárquicos usando as cláusulas CONNECT BY e
START WITH . Isso é especialmente útil para estruturas organizacionais, árvores de categorias, ou qualquer
relacionamento pai-filho.
SELECT colunas
FROM tabela
START WITH condicao_inicial
CONNECT BY [PRIOR] condicao_hierarquica
[ORDER SIBLINGS BY colunas];
28.2.2 Exemplo Prático: Hierarquia Organizacional
INSERT INTO Funcionarios VALUES (1, \'João Silva\', \'CEO\', NULL, 15000);
INSERT INTO Funcionarios VALUES (2, \'Maria Santos\', \'Diretora TI\', 1, 12000);
INSERT INTO Funcionarios VALUES (3, \'Pedro Costa\', \'Diretor Vendas\', 1, 12000);
INSERT INTO Funcionarios VALUES (4, \'Ana Oliveira\', \'Gerente Dev\', 2, 8000);
INSERT INTO Funcionarios VALUES (5, \'Carlos Lima\', \'Gerente Vendas SP\', 3, 7000);
INSERT INTO Funcionarios VALUES (6, \'Julia Ferreira\', \'Desenvolvedora Senior\', 4, 6000);
INSERT INTO Funcionarios VALUES (7, \'Roberto Alves\', \'Desenvolvedor\', 4, 4500);
INSERT INTO Funcionarios VALUES (8, \'Fernanda Rocha\', \'Vendedora\', 5, 3500);
O Oracle suporta expressões regulares através de várias funções SQL, permitindo busca e manipulação
avançada de texto.
28.3.1 Funções de Expressões Regulares
O Oracle oferece suporte nativo para armazenamento e processamento de dados XML através do tipo
XMLType e várias funções especializadas.
28.4.1 Criando e Manipulando XMLType
A partir do Oracle 12c, há suporte nativo para JSON, permitindo armazenar e consultar dados JSON de forma
eficiente.
28.5.1 Armazenando e Consultando JSON
-- Verificar resultado
SELECT JSON_SERIALIZE(Dados PRETTY) FROM ProdutosJSON WHERE ProdutoID = 1;
-- Nested Table
DECLARE
TYPE t_lista_nomes IS TABLE OF VARCHAR2(100);
v_nomes t_lista_nomes;
BEGIN
v_nomes := t_lista_nomes(\'João\', \'Maria\', \'Pedro\');
-- Adicionar elemento
v_nomes.EXTEND;
v_nomes(v_nomes.COUNT) := \'Ana\';
v_nome := v_salarios.FIRST;
WHILE v_nome IS NOT NULL LOOP
DBMS_OUTPUT.PUT_LINE(v_nome || \': R$ \' || v_salarios(v_nome));
v_nome := v_salarios.NEXT(v_nome);
END LOOP;
END;
/
28.6.2 Object Types
Collections: Nested Tables, VARRAYs, Associative Arrays para estruturas de dados complexas.
Object Types: Tipos definidos pelo usuário com métodos para programação orientada a objetos.
1. Consulta Hierárquica:
Crie uma estrutura de categorias de produtos (categoria pai -> subcategoria).
2. Expressões Regulares:
Implemente uma função que extraia e formate números de telefone de texto livre.
3. Processamento XML:
4. JSON:
Referências
[1] Oracle. Database SQL Language Reference - Advanced Features. Disponível em:
[Link]
Capítulo 29: Integração com Aplicações e
Ferramentas Externas
A capacidade de integração é crucial para: * Interoperabilidade: Permitir que diferentes sistemas trabalhem
juntos * Automação: Reduzir intervenção manual em processos de negócio * Escalabilidade: Distribuir
processamento entre diferentes componentes * Flexibilidade: Adaptar-se a mudanças tecnológicas e de
negócio * Eficiência: Otimizar o fluxo de dados entre sistemas
O package UTL_HTTP permite que o PL/SQL faça requisições HTTP para serviços web externos, possibilitando
integração com APIs REST.
-- Função para fazer requisições GET
CREATE OR REPLACE FUNCTION fazer_requisicao_get (
p_url VARCHAR2,
p_timeout NUMBER DEFAULT 30
) RETURN CLOB
IS
v_request UTL_HTTP.REQ;
v_response UTL_HTTP.RESP;
v_buffer VARCHAR2(32767);
v_resultado CLOB;
BEGIN
-- Configurar timeout
UTL_HTTP.SET_RESPONSE_ERROR_CHECK(TRUE);
UTL_HTTP.SET_DETAILED_EXCP_SUPPORT(TRUE);
-- Iniciar requisição
v_request := UTL_HTTP.BEGIN_REQUEST(p_url, \'GET\');
UTL_HTTP.SET_HEADER(v_request, \'User-Agent\', \'Oracle Database\');
UTL_HTTP.SET_HEADER(v_request, \'Accept\', \'application/json\');
-- Obter resposta
v_response := UTL_HTTP.GET_RESPONSE(v_request);
UTL_HTTP.END_RESPONSE(v_response);
RETURN v_resultado;
EXCEPTION
WHEN OTHERS THEN
IF v_request.private_hndl IS NOT NULL THEN
UTL_HTTP.END_REQUEST(v_request);
END IF;
IF v_response.private_hndl IS NOT NULL THEN
UTL_HTTP.END_RESPONSE(v_response);
END IF;
RAISE;
END fazer_requisicao_get;
/
v_resposta := fazer_requisicao_get(v_url);
-- Configurar headers
UTL_HTTP.SET_HEADER(v_request, \'Content-Type\', p_content_type);
UTL_HTTP.SET_HEADER(v_request, \'Content-Length\', LENGTH(p_dados));
-- Enviar dados
UTL_HTTP.WRITE_TEXT(v_request, p_dados);
-- Obter resposta
v_response := UTL_HTTP.GET_RESPONSE(v_request);
v_status_code := v_response.status_code;
-- Ler resposta
BEGIN
LOOP
UTL_HTTP.READ_TEXT(v_response, v_buffer, 32767);
DBMS_OUTPUT.PUT_LINE(v_buffer);
END LOOP;
EXCEPTION
WHEN UTL_HTTP.END_OF_BODY THEN
NULL;
END;
UTL_HTTP.END_RESPONSE(v_response);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(\'Erro na requisição: \' || SQLERRM);
IF v_request.private_hndl IS NOT NULL THEN
UTL_HTTP.END_REQUEST(v_request);
END IF;
IF v_response.private_hndl IS NOT NULL THEN
UTL_HTTP.END_RESPONSE(v_response);
END IF;
END enviar_dados_post;
/
CURSOR cur_clientes IS
SELECT ClienteID, Nome, Email, Estado, DataCadastro
FROM Clientes
ORDER BY ClienteID;
BEGIN
-- Abrir arquivo para escrita
v_arquivo := UTL_FILE.FOPEN(\'TEMP_DIR\', p_nome_arquivo, \'W\');
-- Escrever cabeçalho
v_linha := \'ID,Nome,Email,Estado,DataCadastro\';
UTL_FILE.PUT_LINE(v_arquivo, v_linha);
-- Escrever dados
FOR cliente IN cur_clientes LOOP
v_linha := [Link] || \',\' ||
\'"\' || REPLACE([Link], \'"\', \'""\') || \'",\' ||
[Link] || \',\' ||
[Link] || \',\' ||
TO_CHAR([Link], \'DD/MM/YYYY\');
UTL_FILE.PUT_LINE(v_arquivo, v_linha);
END LOOP;
-- Fechar arquivo
UTL_FILE.FCLOSE(v_arquivo);
-- Pular cabeçalho
UTL_FILE.GET_LINE(v_arquivo, v_linha);
-- Ler dados linha por linha
LOOP
BEGIN
UTL_FILE.GET_LINE(v_arquivo, v_linha);
-- Inserir produto
INSERT INTO Produtos (ProdutoID, NomeProduto, Preco, Categoria)
VALUES (v_produto_id, v_nome_produto, v_preco, v_categoria);
v_contador := v_contador + 1;
EXCEPTION
WHEN NO_DATA_FOUND THEN
EXIT; -- Fim do arquivo
WHEN OTHERS THEN
v_erros := v_erros + 1;
DBMS_OUTPUT.PUT_LINE(\'Erro na linha: \' || v_linha || \' - \' || SQLERRM);
END;
END LOOP;
UTL_FILE.FCLOSE(v_arquivo);
COMMIT;
DBMS_OUTPUT.PUT_LINE(\'Importação concluída:\');
DBMS_OUTPUT.PUT_LINE(\'Registros importados: \' || v_contador);
DBMS_OUTPUT.PUT_LINE(\'Erros: \' || v_erros);
EXCEPTION
WHEN OTHERS THEN
IF UTL_FILE.IS_OPEN(v_arquivo) THEN
UTL_FILE.FCLOSE(v_arquivo);
END IF;
ROLLBACK;
DBMS_OUTPUT.PUT_LINE(\'Erro na importação: \' || SQLERRM);
END importar_produtos_csv;
/
29.3.2 Processamento de Logs e Arquivos de Texto
LOOP
BEGIN
UTL_FILE.GET_LINE(v_arquivo, v_linha);
v_contador := v_contador + 1;
EXCEPTION
WHEN NO_DATA_FOUND THEN
EXIT;
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(\'Erro ao processar linha: \' || SUBSTR(v_linha, 1, 100));
END;
END LOOP;
UTL_FILE.FCLOSE(v_arquivo);
-- Exibir estatísticas
DBMS_OUTPUT.PUT_LINE(\'=== Análise de Log ===\');
DBMS_OUTPUT.PUT_LINE(\'Total de linhas processadas: \' || v_contador);
DBMS_OUTPUT.PUT_LINE(\'IPs únicos: \' || v_contagem_ips.COUNT);
PROCEDURE enviar_relatorio_vendas (
p_destinatario VARCHAR2,
p_periodo_inicio DATE,
p_periodo_fim DATE
);
END pkg_email;
/
-- Headers
UTL_SMTP.WRITE_DATA(v_conexao, \'From: \' || p_remetente || UTL_TCP.CRLF);
UTL_SMTP.WRITE_DATA(v_conexao, \'To: \' || p_destinatario || UTL_TCP.CRLF);
UTL_SMTP.WRITE_DATA(v_conexao, \'Subject: \' || p_assunto || UTL_TCP.CRLF);
-- Content-Type
IF p_html THEN
v_content_type := \'text/html; charset=UTF-8\';
ELSE
v_content_type := \'text/plain; charset=UTF-8\';
END IF;
-- Corpo da mensagem
UTL_SMTP.WRITE_DATA(v_conexao, p_mensagem);
-- Finalizar
UTL_SMTP.CLOSE_DATA(v_conexao);
UTL_SMTP.QUIT(v_conexao);
PROCEDURE enviar_relatorio_vendas (
p_destinatario VARCHAR2,
p_periodo_inicio DATE,
p_periodo_fim DATE
)
IS
v_relatorio CLOB;
v_total_vendas NUMBER;
v_numero_vendas NUMBER;
v_assunto VARCHAR2(200);
BEGIN
-- Calcular estatísticas
SELECT
COUNT(*),
NVL(SUM(Quantidade * PrecoUnitario), 0)
INTO
v_numero_vendas,
v_total_vendas
FROM Vendas
WHERE DataVenda BETWEEN p_periodo_inicio AND p_periodo_fim;
FOR cliente IN (
SELECT
[Link],
COUNT([Link]) AS NumVendas,
SUM([Link] * [Link]) AS TotalCliente
FROM Clientes c
INNER JOIN Vendas v ON [Link] = [Link]
WHERE [Link] BETWEEN p_periodo_inicio AND p_periodo_fim
GROUP BY [Link], [Link]
ORDER BY TotalCliente DESC
) LOOP
v_relatorio := v_relatorio || \'<tr>\';
v_relatorio := v_relatorio || \'<td>\' || [Link] || \'</td>\';
v_relatorio := v_relatorio || \'<td>\' || [Link] || \'</td>\';
v_relatorio := v_relatorio || \'<td>R$ \' || TO_CHAR([Link],
\'999,999.99\') || \'</td>\';
v_relatorio := v_relatorio || \'</tr>\';
END LOOP;
v_relatorio := v_relatorio || \'</table>\';
v_relatorio := v_relatorio || \'</body></html>\';
enviar_email(
p_servidor_smtp => \'[Link]\', -- Substitua pelo seu servidor SMTP
p_porta => 587, -- Porta comum para TLS/STARTTLS
p_remetente => \'seu_email@[Link]\', -- Substitua pelo seu email
p_destinatario => p_destinatario,
p_assunto => v_assunto,
p_mensagem => v_relatorio,
p_html => TRUE
);
END enviar_relatorio_vendas;
END pkg_email;
/
-- Exemplo de uso
BEGIN
pkg_email.enviar_relatorio_vendas(
p_destinatario => \'seu_email@[Link]\', -- Substitua pelo email do destinatário
p_periodo_inicio => TRUNC(SYSDATE, \'MM\'),
p_periodo_fim => SYSDATE
);
END;
/
Database Links permitem que um banco de dados Oracle acesse objetos (tabelas, views, procedures) em
outro banco de dados, seja ele Oracle ou não-Oracle (via Oracle Gateway).
O Oracle Database é a base para muitas ferramentas de Business Intelligence (BI) e Reporting. A integração
geralmente ocorre via:
UTL_HTTP : Para requisições HTTP (GET, POST) a serviços web e APIs REST.
Crie uma procedure que use UTL_HTTP para consultar uma API de clima (ex: OpenWeatherMap).
Se um produto estiver abaixo de um limite mínimo, envie um email de alerta para o gerente de
estoque usando UTL_SMTP .
Simule um cenário onde você precisa copiar dados de uma tabela de vendas de um banco de
dados remoto para uma tabela local.
Tudo isso deve ser feito via PL/SQL, utilizando os packages UTL_HTTP , UTL_FILE e
UTL_SMTP .
Referências
Este capítulo final consolida todo o conhecimento adquirido ao longo dos 29 capítulos anteriores através de
um projeto prático abrangente. Desenvolveremos um Sistema de Gestão Empresarial Completo que integra
vendas, estoque, clientes, relatórios e integrações externas. O projeto demonstrará a aplicação prática de
conceitos desde SQL básico até técnicas avançadas de PL/SQL, servindo como um portfólio profissional e
referência para futuras implementações [1].
O sistema incluirá: * Gestão de Clientes: Cadastro, histórico, categorização * Gestão de Produtos: Catálogo,
estoque, categorias hierárquicas * Gestão de Vendas: Pedidos, itens, comissões, relatórios * Sistema de
Auditoria: Log completo de operações * Relatórios Gerenciais: Dashboards e análises * Integrações: APIs
externas, importação/exportação * Notificações: Alertas automáticos por email
30.2 Arquitetura e Design do Sistema
-- =====================================================
-- SISTEMA DE GESTÃO EMPRESARIAL - MODELO DE DADOS
-- =====================================================
-- Sequências
CREATE SEQUENCE seq_sistema_config START WITH 1;
CREATE SEQUENCE seq_clientes START WITH 1000;
CREATE SEQUENCE seq_categorias START WITH 1;
CREATE SEQUENCE seq_produtos START WITH 1;
CREATE SEQUENCE seq_pedidos START WITH 1;
CREATE SEQUENCE seq_itens_pedido START WITH 1;
CREATE SEQUENCE seq_auditoria START WITH 1;
-- Tabela de Pedidos
CREATE TABLE Pedidos (
PedidoID NUMBER PRIMARY KEY,
ClienteID NUMBER NOT NULL,
DataPedido DATE DEFAULT SYSDATE,
Status VARCHAR2(20) DEFAULT \'Pendente\' CHECK (Status IN (\'Pendente\', \'Confirmado\',
\'Enviado\', \'Entregue\', \'Cancelado\')),
TipoVenda VARCHAR2(20) DEFAULT \'Balcao\' CHECK (TipoVenda IN (\'Balcao\', \'Online\',
\'Telefone\')),
SubTotal NUMBER(15,2) DEFAULT 0,
Desconto NUMBER(15,2) DEFAULT 0,
Frete NUMBER(15,2) DEFAULT 0,
Impostos NUMBER(15,2) DEFAULT 0,
ValorTotal NUMBER(15,2) DEFAULT 0,
FormaPagamento VARCHAR2(50),
Observacoes VARCHAR2(1000),
DataEntrega DATE,
EnderecoEntrega VARCHAR2(500),
FOREIGN KEY (ClienteID) REFERENCES Clientes(ClienteID)
);
-- Tabela de Auditoria
CREATE TABLE Auditoria (
AuditoriaID NUMBER PRIMARY KEY,
Tabela VARCHAR2(50) NOT NULL,
Operacao VARCHAR2(10) NOT NULL,
ChavePrimaria VARCHAR2(100),
Usuario VARCHAR2(100) DEFAULT USER,
DataOperacao DATE DEFAULT SYSDATE,
ValoresAntigos CLOB,
ValoresNovos CLOB,
IP VARCHAR2(15),
Aplicacao VARCHAR2(100)
);
-- =====================================================
-- PACKAGE DE GESTÃO DE CLIENTES
-- =====================================================
CREATE OR REPLACE PACKAGE pkg_clientes
IS
-- Tipos e constantes
TYPE t_cliente_rec IS RECORD (
ClienteID [Link]%TYPE,
Nome [Link]%TYPE,
Email [Link]%TYPE,
Categoria [Link]%TYPE,
TotalCompras [Link]%TYPE
);
-- Exceções customizadas
e_cliente_nao_encontrado EXCEPTION;
e_email_duplicado EXCEPTION;
e_cpf_invalido EXCEPTION;
PROCEDURE atualizar_cliente (
p_cliente_id NUMBER,
p_nome VARCHAR2 DEFAULT NULL,
p_email VARCHAR2 DEFAULT NULL,
p_telefone VARCHAR2 DEFAULT NULL,
p_endereco VARCHAR2 DEFAULT NULL
);
FUNCTION buscar_clientes (
p_nome VARCHAR2 DEFAULT NULL,
p_email VARCHAR2 DEFAULT NULL,
p_categoria VARCHAR2 DEFAULT NULL,
p_status VARCHAR2 DEFAULT \'Ativo\'
) RETURN t_clientes_tab PIPELINED;
END pkg_clientes;
/
-- Verificar dígitos
RETURN (TO_NUMBER(SUBSTR(v_cpf, 10, 1)) = v_dv1 AND
TO_NUMBER(SUBSTR(v_cpf, 11, 1)) = v_dv2);
END validar_cpf;
-- Verificar dígitos
RETURN (TO_NUMBER(SUBSTR(v_cnpj, 13, 1)) = v_dv1 AND
TO_NUMBER(SUBSTR(v_cnpj, 14, 1)) = v_dv2);
END validar_cnpj;
FUNCTION inserir_cliente (
p_nome VARCHAR2,
p_email VARCHAR2,
p_telefone VARCHAR2 DEFAULT NULL,
p_cpf_cnpj VARCHAR2 DEFAULT NULL,
p_tipo_pessoa CHAR DEFAULT \'F\',
p_endereco VARCHAR2 DEFAULT NULL,
p_cidade VARCHAR2 DEFAULT NULL,
p_estado CHAR DEFAULT NULL,
p_cep VARCHAR2 DEFAULT NULL
) RETURN NUMBER
IS
v_cliente_id NUMBER;
v_count NUMBER;
BEGIN
-- Validar email único
SELECT COUNT(*) INTO v_count
FROM Clientes
WHERE UPPER(Email) = UPPER(p_email);
-- Inserir cliente
v_cliente_id := seq_clientes.NEXTVAL;
RETURN v_cliente_id;
EXCEPTION
WHEN e_email_duplicado THEN
RAISE_APPLICATION_ERROR(-20001, \'Email já cadastrado: \' || p_email);
WHEN e_cpf_invalido THEN
RAISE_APPLICATION_ERROR(-20002, \'CPF/CNPJ inválido: \' || p_cpf_cnpj);
END inserir_cliente;
PROCEDURE atualizar_cliente (
p_cliente_id NUMBER,
p_nome VARCHAR2 DEFAULT NULL,
p_email VARCHAR2 DEFAULT NULL,
p_telefone VARCHAR2 DEFAULT NULL,
p_endereco VARCHAR2 DEFAULT NULL
)
IS
BEGIN
UPDATE Clientes
SET
Nome = NVL(p_nome, Nome),
Email = NVL(p_email, Email),
Telefone = NVL(p_telefone, Telefone),
Endereco = NVL(p_endereco, Endereco)
WHERE ClienteID = p_cliente_id;
IF SQL%NOTFOUND THEN
RAISE e_cliente_nao_encontrado;
END IF;
RETURN v_cliente;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE e_cliente_nao_encontrado;
END obter_cliente;
FUNCTION buscar_clientes (
p_nome VARCHAR2 DEFAULT NULL,
p_email VARCHAR2 DEFAULT NULL,
p_categoria VARCHAR2 DEFAULT NULL,
p_status VARCHAR2 DEFAULT \'Ativo\'
) RETURN t_clientes_tab PIPELINED
IS
v_sql VARCHAR2(4000);
v_cursor SYS_REFCURSOR;
v_cliente_rec t_cliente_rec;
BEGIN
v_sql := \'SELECT ClienteID, Nome, Email, Categoria, TotalCompras FROM Clientes WHERE 1=1\';
-- =====================================================
-- PACKAGE DE SISTEMA E LOGGING
-- =====================================================
CREATE OR REPLACE PACKAGE pkg_sistema
IS
-- Constantes para níveis de log
c_log_info CONSTANT VARCHAR2(10) := \'INFO\';
c_log_warn CONSTANT VARCHAR2(10) := \'WARN\';
c_log_error CONSTANT VARCHAR2(10) := \'ERROR\';
c_log_debug CONSTANT VARCHAR2(10) := \'DEBUG\';
-- Procedures de logging
PROCEDURE log_info (p_modulo VARCHAR2, p_mensagem VARCHAR2, p_detalhes CLOB DEFAULT NULL);
PROCEDURE log_warn (p_modulo VARCHAR2, p_mensagem VARCHAR2, p_detalhes CLOB DEFAULT NULL);
PROCEDURE log_error (p_modulo VARCHAR2, p_mensagem VARCHAR2, p_detalhes CLOB DEFAULT NULL);
PROCEDURE log_debug (p_modulo VARCHAR2, p_mensagem VARCHAR2, p_detalhes CLOB DEFAULT NULL);
-- Configurações do sistema
FUNCTION obter_config (p_chave VARCHAR2) RETURN VARCHAR2;
PROCEDURE definir_config (p_chave VARCHAR2, p_valor VARCHAR2, p_descricao VARCHAR2 DEFAULT NULL);
-- Utilitários
FUNCTION gerar_codigo_barras RETURN VARCHAR2;
FUNCTION formatar_moeda (p_valor NUMBER) RETURN VARCHAR2;
END pkg_sistema;
/
PROCEDURE log_info (p_modulo VARCHAR2, p_mensagem VARCHAR2, p_detalhes CLOB DEFAULT NULL)
IS BEGIN inserir_log(c_log_info, p_modulo, p_mensagem, p_detalhes); END;
PROCEDURE log_warn (p_modulo VARCHAR2, p_mensagem VARCHAR2, p_detalhes CLOB DEFAULT NULL)
IS BEGIN inserir_log(c_log_warn, p_modulo, p_mensagem, p_detalhes); END;
PROCEDURE log_error (p_modulo VARCHAR2, p_mensagem VARCHAR2, p_detalhes CLOB DEFAULT NULL)
IS BEGIN inserir_log(c_log_error, p_modulo, p_mensagem, p_detalhes); END;
PROCEDURE log_debug (p_modulo VARCHAR2, p_mensagem VARCHAR2, p_detalhes CLOB DEFAULT NULL)
IS BEGIN inserir_log(c_log_debug, p_modulo, p_mensagem, p_detalhes); END;
PROCEDURE definir_config (p_chave VARCHAR2, p_valor VARCHAR2, p_descricao VARCHAR2 DEFAULT NULL)
IS
BEGIN
UPDATE SistemaConfig
SET Valor = p_valor, Descricao = NVL(p_descricao, Descricao), DataAtualizacao = SYSDATE
WHERE Chave = p_chave;
IF SQL%NOTFOUND THEN
INSERT INTO SistemaConfig (ConfigID, Chave, Valor, Descricao)
VALUES (seq_sistema_config.NEXTVAL, p_chave, p_valor, p_descricao);
END IF;
END definir_config;
-- =====================================================
-- TRIGGER DE AUDITORIA GENÉRICA
-- =====================================================
CREATE OR REPLACE TRIGGER trg_auditoria_generica
AFTER INSERT OR UPDATE OR DELETE ON Clientes
FOR EACH ROW
DECLARE
v_operacao VARCHAR2(10);
v_chave_primaria VARCHAR2(100);
v_valores_antigos CLOB;
v_valores_novos CLOB;
BEGIN
IF INSERTING THEN
v_operacao := \'INSERT\';
v_chave_primaria := :[Link];
v_valores_novos := JSON_OBJECT(
\'ClienteID\' VALUE :[Link],
\'Nome\' VALUE :[Link],
\'Email\' VALUE :[Link]
).TO_CLOB;
ELSIF UPDATING THEN
v_operacao := \'UPDATE\';
v_chave_primaria := :[Link];
v_valores_antigos := JSON_OBJECT(
\'ClienteID\' VALUE :[Link],
\'Nome\' VALUE :[Link],
\'Email\' VALUE :[Link]
).TO_CLOB;
v_valores_novos := JSON_OBJECT(
\'ClienteID\' VALUE :[Link],
\'Nome\' VALUE :[Link],
\'Email\' VALUE :[Link]
).TO_CLOB;
ELSIF DELETING THEN
v_operacao := \'DELETE\';
v_chave_primaria := :[Link];
v_valores_antigos := JSON_OBJECT(
\'ClienteID\' VALUE :[Link],
\'Nome\' VALUE :[Link],
\'Email\' VALUE :[Link]
).TO_CLOB;
END IF;
Nomenclatura Consistente: Use padrões claros para tabelas, colunas, procedures, packages (ex: PKG_ ,
TRG_ , IDX_ ).
Comentários: Comente blocos de código complexos, justificativas de design e qualquer lógica não
óbvia.
Use a especificação do package para definir a interface pública e o corpo para a implementação.
Use RAISE_APPLICATION_ERROR para retornar mensagens de erro claras para a aplicação cliente.
30.4.4 Segurança
Privilégios Mínimos: Conceda apenas os privilégios necessários para cada usuário ou role.
Bind Variables: Sempre use bind variables para prevenir SQL Injection.
30.4.5 Performance
Índices: Crie índices apropriados para colunas usadas em WHERE , JOIN e ORDER BY .
SQL Otimizado: Escreva consultas eficientes, evite SELECT * , use EXISTS em vez de IN para
subconsultas grandes.
Bulk Operations: Use FORALL e BULK COLLECT para operações DML e consultas em lote.
30.4.6 Testes
30.4.7 Documentação
Parabéns! Você concluiu este ebook abrangente sobre SQL e PL/SQL, cobrindo desde os fundamentos até
tópicos avançados e melhores práticas. O conhecimento e as habilidades que você adquiriu são inestimáveis
no mundo do desenvolvimento de banco de dados.
Lembre-se que a jornada para se tornar um expert é contínua. Continue praticando, explorando novas
funcionalidades do Oracle, lendo a documentação oficial e participando da comunidade. O mundo dos
bancos de dados está em constante evolução, e a aprendizagem contínua é a chave para o sucesso.
Implemente o Projeto Completo: Use o modelo de dados e os packages sugeridos como ponto de
partida.
Adicione Novas Funcionalidades: Pense em recursos adicionais que poderiam ser úteis (ex: gestão de
promoções, sistema de frete complexo, integração com sistemas de pagamento).
Crie uma Interface: Desenvolva uma interface web ou desktop simples para interagir com o banco de
dados.
Com dedicação e prática, você estará no caminho certo para se tornar um verdadeiro expert em SQL e PL/SQL.
Boa sorte!
Referências