0% acharam este documento útil (0 voto)
9 visualizações215 páginas

Ebook SQL e PL/SQL: Do Iniciante ao Expert

Este ebook é um guia abrangente para aprender SQL e PL/SQL, desde conceitos básicos até técnicas avançadas, com 30 capítulos repletos de explicações, exemplos práticos e exercícios. Ele aborda a importância do SQL na gestão de dados e apresenta o PL/SQL como uma extensão que permite lógica de programação no banco de dados. Ao final, os leitores estarão preparados para aplicar seus conhecimentos em cenários do mundo real e desenvolver soluções robustas.

Enviado por

Gustavo Henrique
Direitos autorais
© All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
9 visualizações215 páginas

Ebook SQL e PL/SQL: Do Iniciante ao Expert

Este ebook é um guia abrangente para aprender SQL e PL/SQL, desde conceitos básicos até técnicas avançadas, com 30 capítulos repletos de explicações, exemplos práticos e exercícios. Ele aborda a importância do SQL na gestão de dados e apresenta o PL/SQL como uma extensão que permite lógica de programação no banco de dados. Ao final, os leitores estarão preparados para aplicar seus conhecimentos em cenários do mundo real e desenvolver soluções robustas.

Enviado por

Gustavo Henrique
Direitos autorais
© All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd

Ebook Completo: SQL e PL/SQL - Do Iniciante ao

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

Capítulo 1: Introdução ao SQL e Bancos de Dados


Relacionais

1.1 O que é SQL?

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.

1.2 Bancos de Dados Relacionais

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].

1.2.1 Conceitos Fundamentais

Tabela (Table/Relation): Uma coleção de dados organizada em linhas e colunas. Cada tabela
representa uma entidade (por exemplo, Clientes, Produtos, Pedidos).

Coluna (Column/Attribute/Field): Representa uma característica ou propriedade da entidade. Cada


coluna tem um nome e um tipo de dado (por exemplo, Nome do Cliente, Preço do Produto).

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.

1.2.2 Exemplo Ilustrativo

Imagine um sistema de vendas. Teríamos tabelas como Clientes , Produtos e Pedidos .

Tabela: Clientes

ClienteID (PK) Nome Email

1 João Silva [Link]@[Link]

2 Maria Santos [Link]@[Link]

Tabela: Produtos
ProdutoID (PK) Nome Produto Preço

101 Notebook 2500

102 Mouse 50

Tabela: Pedidos

PedidoID (PK) ClienteID (FK) Data Pedido Valor Total

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.

1.3 Categorias de Comandos SQL

O SQL é dividido em subconjuntos de comandos, cada um com uma finalidade específica:

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 .

1.4 O que é PL/SQL?

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.

Segurança: Permite encapsular operações complexas em procedures e functions, concedendo acesso


apenas a essas rotinas, não diretamente às tabelas.

Reutilização: Facilita a criação de componentes reutilizáveis (packages, procedures, functions) que


podem ser chamados por diversas aplicações.

Tratamento de Erros: Oferece mecanismos robustos para lidar com exceções e garantir a integridade
dos dados.

1.5 Comparação Ilustrativa: SQL vs. PL/SQL

Característica SQL PL/SQL

Natureza Declarativa (o quê) Procedural (como)

Propósito Gerenciamento e consulta de dados Lógica de negócio, controle de fluxo

Execução Instrução por instrução Blocos de código

Variáveis Não suporta variáveis persistentes Suporta variáveis, constantes, tipos

Controle Não possui estruturas de controle Possui IF-THEN-ELSE, LOOP, WHILE, FOR

Erros Erros resultam em falha da instrução Tratamento de exceções (EXCEPTION)

Exemplo SELECT * FROM Clientes; BEGIN ... END;

1.6 Tópicos Chave do Capítulo 1

SQL: Linguagem padrão para bancos de dados relacionais.

Bancos de Dados Relacionais: Organizados em tabelas, linhas, colunas.

Chave Primária (PK): Identificador único de uma linha.

Chave Estrangeira (FK): Estabelece relacionamento entre tabelas.

DDL: Define a estrutura do banco de dados ( CREATE , ALTER , DROP ).

DML: Manipula dados ( INSERT , UPDATE , DELETE , SELECT ).

DCL: Gerencia permissões ( GRANT , REVOKE ).

TCL: Controla transações ( COMMIT , ROLLBACK ).

PL/SQL: Extensão procedural do SQL da Oracle.

PL/SQL Importância: Lógica de negócio, performance, segurança, reutilização.


1.7 Exercícios do Capítulo 1

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

[1] IBM. A History of SQL. Disponível em: [Link] [2] Codd,


E. F. (1970). A Relational Model of Data for Large Shared Data Banks. Communications of the ACM, 13(6), 377-
387. [3] Oracle. PL/SQL Language Reference. Disponível em:
[Link]

Capítulo 2: Comandos DDL - Definindo a Estrutura


do Banco de Dados

2.1 Introdução aos Comandos DDL

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 .

2.2 CREATE TABLE: Criando Tabelas

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].

2.2.1 Sintaxe Básica

CREATE TABLE nome_tabela (


nome_coluna1 tipo_dado1 [restricoes_coluna1],
nome_coluna2 tipo_dado2 [restricoes_coluna2],
...
[restricoes_tabela]
);

2.2.2 Tipos de Dados Comuns

O Oracle Database oferece uma vasta gama de tipos de dados. Alguns dos mais comuns incluem:

Tipo de Dado Descrição Exemplo de Uso

VARCHAR2(n) String de caracteres de comprimento variável (até 4000 bytes). Nome VARCHAR2(100)

Número com precisão p e escala s . p é o total de dígitos, s é Preco NUMBER(10,2) (até 10


NUMBER(p,s)
o número de dígitos após a vírgula. dígitos, 2 decimais)

INTEGER Número inteiro (sinônimo de NUMBER(38) ). Quantidade INTEGER

DATE Data e hora (século, ano, mês, dia, hora, minuto, segundo). DataCadastro DATE

TIMESTAMP Data e hora com precisão de nanossegundos. DataHoraLog TIMESTAMP

CLOB Large Object para dados de caracteres (até 4GB). DescricaoProduto CLOB

BLOB Large Object para dados binários (até 4GB). FotoProduto BLOB

String de caracteres de comprimento fixo (preenchido com


CHAR(n) Sexo CHAR(1)
espaços).

2.2.3 Restrições de Coluna (Constraints)

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.

2.2.4 Exemplo de CREATE TABLE

CREATE TABLE Clientes (


ClienteID NUMBER PRIMARY KEY, -- Chave Primária
Nome VARCHAR2(100) NOT NULL, -- Não pode ser nulo
Email VARCHAR2(255) UNIQUE, -- Deve ser único
Telefone VARCHAR2(20),
DataCadastro DATE DEFAULT SYSDATE, -- Valor padrão é a data atual
Status VARCHAR2(10) DEFAULT 'Ativo' CHECK (Status IN ('Ativo', 'Inativo', 'Suspenso')) -- Valores
permitidos
);

CREATE TABLE Produtos (


ProdutoID NUMBER PRIMARY KEY,
NomeProduto VARCHAR2(255) NOT NULL,
Descricao CLOB,
Preco NUMBER(10,2) NOT NULL CHECK (Preco > 0), -- Preço deve ser positivo
Estoque INTEGER DEFAULT 0 CHECK (Estoque >= 0) -- Estoque não pode ser negativo
);

CREATE TABLE Pedidos (


PedidoID NUMBER PRIMARY KEY,
ClienteID NUMBER NOT NULL,
DataPedido DATE DEFAULT SYSDATE,
ValorTotal NUMBER(15,2) NOT NULL,

-- Chave Estrangeira: ClienteID referencia a tabela Clientes


CONSTRAINT fk_cliente_pedido FOREIGN KEY (ClienteID) REFERENCES Clientes(ClienteID)
);

CREATE TABLE ItensPedido (


ItemID NUMBER PRIMARY KEY,
PedidoID NUMBER NOT NULL,
ProdutoID NUMBER NOT NULL,
Quantidade INTEGER NOT NULL CHECK (Quantidade > 0),
PrecoUnitario NUMBER(10,2) NOT NULL,

-- Chave Estrangeira: PedidoID referencia a tabela Pedidos


CONSTRAINT fk_pedido_item FOREIGN KEY (PedidoID) REFERENCES Pedidos(PedidoID),

-- Chave Estrangeira: ProdutoID referencia a tabela Produtos


CONSTRAINT fk_produto_item FOREIGN KEY (ProdutoID) REFERENCES Produtos(ProdutoID)
);

2.3 ALTER TABLE: Modificando a Estrutura da Tabela

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].

2.3.1 Adicionar Coluna

ALTER TABLE nome_tabela


ADD nome_nova_coluna tipo_dado [restricoes];

-- Exemplo: Adicionar coluna Estado à tabela Clientes


ALTER TABLE Clientes
ADD Estado CHAR(2) DEFAULT 'SP';
2.3.2 Modificar Coluna

ALTER TABLE nome_tabela


MODIFY nome_coluna tipo_dado [restricoes];

-- Exemplo: Aumentar o tamanho da coluna Nome em Clientes


ALTER TABLE Clientes
MODIFY Nome VARCHAR2(150) NOT NULL;

-- Exemplo: Alterar tipo de dado (cuidado! pode haver perda de dados)


ALTER TABLE Produtos
MODIFY Preco NUMBER(12,2);

2.3.3 Excluir Coluna

ALTER TABLE nome_tabela


DROP COLUMN nome_coluna;

-- Exemplo: Excluir a coluna Telefone da tabela Clientes


ALTER TABLE Clientes
DROP COLUMN Telefone;

2.3.4 Adicionar Restrição

ALTER TABLE nome_tabela


ADD CONSTRAINT nome_restricao tipo_restricao (coluna);

-- Exemplo: Adicionar uma restrição UNIQUE para Email na tabela Clientes


ALTER TABLE Clientes
ADD CONSTRAINT uk_clientes_email UNIQUE (Email);

-- Exemplo: Adicionar uma restrição CHECK para Preco na tabela Produtos


ALTER TABLE Produtos
ADD CONSTRAINT chk_produtos_preco CHECK (Preco > 0);

2.3.5 Excluir Restrição

ALTER TABLE nome_tabela


DROP CONSTRAINT nome_restricao;

-- Exemplo: Excluir a restrição UNIQUE de Email na tabela Clientes


ALTER TABLE Clientes
DROP CONSTRAINT uk_clientes_email;

-- Exemplo: Excluir a chave primária (cuidado! pode ter dependências)


ALTER TABLE Clientes
DROP PRIMARY KEY CASCADE;

2.3.6 Renomear Coluna

ALTER TABLE nome_tabela


RENAME COLUMN nome_antigo TO nome_novo;

-- Exemplo: Renomear NomeProduto para Nome na tabela Produtos


ALTER TABLE Produtos
RENAME COLUMN NomeProduto TO Nome;
2.3.7 Renomear Tabela

ALTER TABLE nome_antigo_tabela


RENAME TO nome_nova_tabela;

-- Exemplo: Renomear Clientes para Consumidores


ALTER TABLE Clientes
RENAME TO Consumidores;

2.4 DROP TABLE: Excluindo Tabelas

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].

2.4.1 Sintaxe Básica

DROP TABLE nome_tabela [CASCADE CONSTRAINTS];

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á.

2.4.2 Exemplo de DROP TABLE

-- Excluir a tabela Produtos


DROP TABLE Produtos;

-- Excluir a tabela Clientes e todas as restrições que a referenciam


DROP TABLE Clientes CASCADE CONSTRAINTS;

2.5 TRUNCATE TABLE: Removendo Todos os Dados

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].

2.5.1 Sintaxe Básica

TRUNCATE TABLE nome_tabela;


2.5.2 Comparação: TRUNCATE vs. DELETE

Característica TRUNCATE TABLE DELETE FROM

Tipo DDL DML

Velocidade Muito rápido (libera espaço) Mais lento (remove linha por linha)

Rollback Não pode ser revertido Pode ser revertido com ROLLBACK

Triggers Não dispara triggers DML Dispara triggers DML

Integridade Falha se houver FKs referenciando Pode ser usado com WHERE para remover seletivamente

Espaço Libera espaço imediatamente Espaço liberado após COMMIT

2.6 Tópicos Chave do Capítulo 2

DDL: Define a estrutura do banco de dados.

CREATE TABLE : Cria novas tabelas com colunas, tipos de dados e restrições.

Tipos de Dados: VARCHAR2 , NUMBER , DATE , TIMESTAMP , CLOB , BLOB , etc.

Restrições (Constraints): NOT NULL , UNIQUE , PRIMARY KEY , FOREIGN KEY , CHECK , DEFAULT .

ALTER TABLE : Modifica a estrutura de tabelas existentes (adicionar/modificar/excluir colunas,


adicionar/excluir/renomear restrições, renomear colunas/tabelas).

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).

Transacionalidade DDL: COMMIT implícito antes e depois da execução.

2.7 Exercícios do Capítulo 2

1. Criação de Tabela: Crie uma tabela chamada Funcionarios com as seguintes colunas e restrições:

FuncionarioID : Chave primária, número inteiro.

Nome : Texto, não nulo, máximo 100 caracteres.

Sobrenome : Texto, não nulo, máximo 100 caracteres.

Email : Texto, único, máximo 255 caracteres.

DataContratacao : Data, padrão a data atual.

Salario : Número com 10 dígitos no total e 2 decimais, maior que 0.

DepartamentoID : Número inteiro.

2. Modificação de Tabela: Usando ALTER TABLE :

Adicione uma coluna Telefone (VARCHAR2(20)) à tabela Funcionarios .


Modifique a coluna Salario para permitir até 12 dígitos no total.

Adicione uma restrição CHECK para garantir que DataContratacao não seja no futuro.

Renomeie a coluna Email para EmailCorporativo .

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 .

4. Exclusão de Dados e Tabela:

Use TRUNCATE TABLE para remover todos os dados da tabela Funcionarios (se houver).

Exclua a tabela ItensPedido criada no exemplo do capítulo.

Exclua a tabela Pedidos (pode ser necessário usar CASCADE CONSTRAINTS ).

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]

Capítulo 3: Comandos DML - Manipulando Dados

3.1 Introdução aos Comandos DML

DML (Data Manipulation Language), ou Linguagem de Manipulação de Dados, é o subconjunto do SQL


responsável por adicionar, modificar e excluir dados dentro das tabelas do banco de dados. Diferentemente
dos comandos DDL, que alteram a estrutura do banco, os comandos DML operam sobre o conteúdo das
tabelas [1].

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 :

3.2.1 Inserindo Valores Específicos

Esta forma permite inserir valores diretamente para as colunas especificadas. É importante que a ordem dos
valores corresponda à ordem das colunas.

Sintaxe:

INSERT INTO nome_tabela (coluna1, coluna2, ...)


VALUES (valor1, valor2, ...);

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:

-- Inserir um novo cliente (especificando colunas)


INSERT INTO Clientes (ClienteID, Nome, Email, DataCadastro, Status)
VALUES (1, 'João Silva', '[Link]@[Link]', SYSDATE, 'Ativo');

-- Inserir um novo produto (omitindo colunas, assumindo ordem)


-- Assumindo que Produtos tem: ProdutoID, NomeProduto, Descricao, Preco, Estoque
INSERT INTO Produtos
VALUES (101, 'Notebook Gamer', 'Notebook de alta performance', 7500.00, 50);

-- Inserir um cliente com valores padrão (DataCadastro e Status)


INSERT INTO Clientes (ClienteID, Nome, Email)
VALUES (2, 'Maria Santos', '[Link]@[Link]');

-- Inserir um pedido (assumindo ClienteID 1 existe)


INSERT INTO Pedidos (PedidoID, ClienteID, DataPedido, ValorTotal)
VALUES (1, 1, SYSDATE, 7550.00);

3.2.2 Inserindo Resultados de uma Consulta (INSERT INTO SELECT)

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:

INSERT INTO nome_tabela (coluna1, coluna2, ...)


SELECT coluna_origem1, coluna_origem2, ...
FROM outra_tabela
WHERE condicao;

Exemplo:
-- Criar uma tabela de backup de clientes
CREATE TABLE Clientes_Backup (
ClienteID NUMBER,
Nome VARCHAR2(100),
Email VARCHAR2(255)
);

-- Inserir todos os clientes ativos na tabela de backup


INSERT INTO Clientes_Backup (ClienteID, Nome, Email)
SELECT ClienteID, Nome, Email
FROM Clientes
WHERE Status = 'Ativo';

-- Inserir produtos com estoque baixo em uma tabela de reabastecimento


CREATE TABLE Produtos_Reabastecer (
ProdutoID NUMBER,
NomeProduto VARCHAR2(255),
EstoqueAtual INTEGER
);

INSERT INTO Produtos_Reabastecer (ProdutoID, NomeProduto, EstoqueAtual)


SELECT ProdutoID, NomeProduto, Estoque
FROM Produtos
WHERE Estoque < 10;

3.3 UPDATE: Modificando Linhas Existentes

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:

-- Atualizar o email de um cliente específico


UPDATE Clientes
SET Email = '[Link]@[Link]'
WHERE ClienteID = 1;

-- Aumentar o preço de todos os produtos em 10%


UPDATE Produtos
SET Preco = Preco * 1.10;

-- 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);

-- Atualizar o estoque de um produto com base em uma venda


UPDATE Produtos
SET Estoque = Estoque - 5 -- Reduzir 5 unidades
WHERE ProdutoID = 101;
3.4 DELETE: Excluindo Linhas Existentes

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:

DELETE FROM nome_tabela


[WHERE condicao];

Exemplos:

-- Excluir um cliente específico


DELETE FROM Clientes
WHERE ClienteID = 2;

-- Excluir todos os produtos com estoque zero


DELETE FROM Produtos
WHERE Estoque = 0;

-- Excluir pedidos antigos (com mais de 5 anos)


DELETE FROM Pedidos
WHERE DataPedido < ADD_MONTHS(SYSDATE, -60);

-- Excluir todos os dados da tabela (sem WHERE)


DELETE FROM Clientes_Backup;

3.4.1 DELETE vs. TRUNCATE (Revisão)

Conforme visto no Capítulo 2, DELETE e TRUNCATE removem dados de uma tabela, mas com diferenças
cruciais:

Característica DELETE FROM TRUNCATE TABLE

Tipo DML DDL

Rollback Pode ser revertido com ROLLBACK Não pode ser revertido

Triggers Dispara triggers DML Não dispara triggers DML

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)

Espaço Espaço liberado após COMMIT Libera espaço imediatamente

3.5 Transações e Controle de Transações (TCL)

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.

3.5.2 Comandos TCL

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:

-- Início implícito da transação

-- Operação 1: Inserir um novo pedido


INSERT INTO Pedidos (PedidoID, ClienteID, DataPedido, ValorTotal)
VALUES (2, 1, SYSDATE, 150.00);

-- Operação 2: Inserir um item para o pedido


INSERT INTO ItensPedido (ItemID, PedidoID, ProdutoID, Quantidade, PrecoUnitario, ValorTotal)
VALUES (1, 2, 102, 3, 50.00, 150.00);

-- Operação 3: Atualizar o estoque do produto


UPDATE Produtos
SET Estoque = Estoque - 3
WHERE ProdutoID = 102;

-- Se todas as operações acima forem bem-sucedidas, confirma a transação


COMMIT;

-- Exemplo de ROLLBACK
INSERT INTO Clientes (ClienteID, Nome, Email)
VALUES (3, 'Carlos Pereira', '[Link]@[Link]');

-- Oops, decidi que não quero este cliente


ROLLBACK;

-- O cliente 3 não estará no banco de dados

Exemplo com SAVEPOINT:


INSERT INTO Clientes (ClienteID, Nome, Email)
VALUES (4, 'Fernanda Costa', '[Link]@[Link]');

SAVEPOINT antes_do_produto;

INSERT INTO Produtos (ProdutoID, NomeProduto, Preco, Estoque)


VALUES (103, 'Teclado Mecânico', 400.00, 20);

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

-- Se tudo estiver OK, confirma tudo


COMMIT;

3.6 Tópicos Chave do Capítulo 3

DML: Linguagem de Manipulação de Dados ( INSERT , UPDATE , DELETE ).

INSERT : Adiciona novas linhas.


INSERT INTO tabela (colunas) VALUES (valores)

INSERT INTO tabela SELECT ... FROM ...

UPDATE : Modifica linhas existentes ( SET e WHERE ).

DELETE : Remove linhas existentes ( WHERE ).

Transações (ACID): Unidades lógicas de trabalho que garantem a integridade dos dados.
Atomicidade: Tudo ou nada.

Consistência: Mantém regras do banco.

Isolamento: Transações independentes.

Durabilidade: Alterações permanentes após COMMIT .

TCL: Comandos de Controle de Transações.


COMMIT : Salva alterações permanentemente.

ROLLBACK : Desfaz alterações.

SAVEPOINT : Define pontos de reversão parciais.

3.7 Exercícios do Capítulo 3

1. Inserção de Dados:

Insira 3 novos clientes na tabela Clientes com dados fictícios.

Insira 2 novos produtos na tabela Produtos .

Insira 1 novo pedido para um dos clientes, com 2 itens de produtos diferentes.

2. Atualização de Dados:

Atualize o Status de um cliente específico para 'Inativo'.

Aumente o Preco de todos os produtos da categoria 'Eletrônicos' em 5%.

Atualize o Email de um cliente, usando o Nome como critério na cláusula WHERE .


3. Exclusão de Dados:

Exclua um produto específico pelo seu ProdutoID .

Exclua todos os clientes cujo Status seja 'Suspenso'.

Exclua todos os pedidos feitos antes de uma data específica.

4. Transações:

Crie um bloco de código que simule uma transação de compra:


Insira um novo pedido.

Insira 3 itens para esse pedido.

Atualize o estoque dos 3 produtos vendidos.

Se todas as operações forem bem-sucedidas, faça COMMIT .

Se houver algum erro (simule um erro, por exemplo, tentando inserir um ProdutoID
inexistente), faça ROLLBACK .

5. SAVEPOINT :

Crie um bloco de código que insira 5 novos produtos.

Após a inserção do 3º produto, defina um SAVEPOINT .

Tente inserir um 6º produto com um erro (ex: Preco negativo).

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]

Capítulo 4: Consultas Básicas com SELECT

4.1 Introdução ao Comando SELECT

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.

A estrutura básica de uma consulta SELECT segue esta sintaxe:

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.

4.2 SELECT: Selecionando Colunas Específicas

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.

4.2.1 Selecionando uma Única Coluna

Para selecionar apenas uma coluna, especifique o nome da coluna após a palavra-chave SELECT :

SELECT nome_da_coluna
FROM nome_da_tabela;

Exemplo: Recuperar apenas os nomes dos produtos da tabela Produtos .

SELECT NomeProduto
FROM Produtos;

Este comando retornará uma lista com todos os nomes de produtos armazenados na tabela, uma linha para
cada produto.

4.2.2 Selecionando Múltiplas Colunas

Para selecionar múltiplas colunas, separe os nomes das colunas com vírgulas:

SELECT coluna1, coluna2, coluna3


FROM nome_da_tabela;

Exemplo: Recuperar o nome e o preço dos produtos.


SELECT NomeProduto, Preco
FROM Produtos;

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.

4.2.3 Selecionando Todas as Colunas (*)

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;

Exemplo: Recuperar todas as informações dos produtos.

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.

Manutenibilidade: Se a estrutura da tabela mudar (novas colunas forem adicionadas), o resultado da


consulta mudará, podendo quebrar aplicações que dependem de uma estrutura específica.

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.

4.3 FROM: Especificando a Tabela de Origem

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];

-- Especificando banco de dados e tabela (comum em MySQL)


SELECT NomeProduto
FROM loja_db.Produtos;

4.4 DISTINCT: Removendo Duplicatas

Em muitas situações, uma consulta pode retornar linhas duplicadas. A palavra-chave DISTINCT é usada para
remover essas duplicatas, retornando apenas valores únicos.

SELECT DISTINCT colunas


FROM nome_da_tabela;

4.4.1 DISTINCT com uma Única Coluna

Exemplo: Suponha que você queira ver todos os preços únicos dos produtos, sem repetições.

SELECT DISTINCT Preco


FROM Produtos;

Se a tabela Produtos tiver múltiplos produtos com o mesmo preço, este comando retornará cada preço
único apenas uma vez.

4.4.2 DISTINCT com Múltiplas Colunas

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.

Exemplo: Encontrar todas as combinações únicas de preço e estoque.

SELECT DISTINCT Preco, Estoque


FROM Produtos;

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.

4.4.3 Considerações sobre Performance

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.

4.5 Aliases: Renomeando Colunas e Tabelas

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.

4.5.1 Aliases para Colunas

You can create an alias for a column using the AS keyword (which is optional in many SGBDs):

SELECT nome_da_coluna AS alias_da_coluna


FROM nome_da_tabela;

-- Ou simplesmente (sem AS)


SELECT nome_da_coluna alias_da_coluna
FROM nome_da_tabela;

Exemplo: Renomear colunas para nomes mais descritivos.

SELECT
NomeProduto AS "Nome do Produto",
Preco AS "Preço (R$)",
Estoque AS "Quantidade em Estoque"
FROM Produtos;

Observações importantes sobre aliases de colunas:

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.

O alias aparece no cabeçalho do resultado da consulta.

4.5.2 Aliases para Tabelas

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;

-- Ou simplesmente (sem AS)


SELECT alias.nome_da_coluna
FROM nome_da_tabela alias;

Exemplo: Usando alias para uma tabela.

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.

4.5.3 Expressões e Cálculos com Aliases

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.

4.6 Expressões e Cálculos Básicos

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.

4.6.1 Operadores Aritméticos

Os operadores aritméticos básicos estão disponíveis em SQL:

+ (adição)

- (subtração)

* (multiplicação)

/ (divisão)

% (módulo - resto da divisão, disponível em alguns SGBDs)

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;

4.6.2 Concatenação de Strings

A concatenação de strings permite combinar valores de texto. A sintaxe varia entre os SGBDs:

SQL Server:

SELECT Nome + ' ' + Sobrenome AS "Nome Completo"


FROM Clientes;

MySQL:

SELECT CONCAT(Nome, ' ', Sobrenome) AS "Nome Completo"


FROM Clientes;

PostgreSQL:
SELECT Nome || ' ' || Sobrenome AS "Nome Completo"
FROM Clientes;

Oracle:

SELECT Nome || ' ' || Sobrenome AS "Nome Completo"


FROM Clientes;

4.6.3 Trabalhando com Valores NULL

Valores NULL representam dados ausentes ou desconhecidos. É importante entender como eles se
comportam em cálculos:

Qualquer operação aritmética com NULL resulta em NULL .

Concatenação com NULL pode resultar em NULL (dependendo do SGBD).

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;

-- PostgreSQL/Oracle (padrão SQL)


SELECT
NomeProduto,
COALESCE(Descricao, 'Sem descrição') AS "Descrição"
FROM Produtos;

4.7 Tópicos Chave do Capítulo 4

SELECT : O comando fundamental para recuperar dados de tabelas.

Seleção de Colunas: Especifique colunas individuais, múltiplas colunas ou todas as colunas ( * ).

FROM : Especifica a tabela de origem dos dados.

DISTINCT : Remove linhas duplicadas dos resultados.

Aliases: Nomes alternativos para colunas e tabelas que melhoram a legibilidade.


Expressões e Cálculos: Realize operações aritméticas e manipulações de string diretamente nas
consultas.

Valores NULL : Entenda como valores ausentes afetam cálculos e use funções apropriadas para tratá-los.

4.8 Exercícios do Capítulo 4

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:

Selecione apenas os nomes dos clientes.

Selecione o nome e o preço de todos os produtos.

Selecione todas as informações de todas as vendas.

2. Usando DISTINCT:

Encontre todos os preços únicos dos produtos.

Encontre todas as datas únicas de vendas.

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.

Calcule quanto seria o preço de cada produto com um aumento de 15%.

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

[1] Oracle. SQL SELECT Statement. Disponível em: [Link]


database/19/sqlrf/[Link]
Capítulo 5: Filtragem de Dados com WHERE

5.1 Introdução à Cláusula WHERE

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.

5.2 Operadores de Comparação

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 .

Operador Descrição Exemplo

= Igual a Preco = 100

> Maior que Estoque > 50

< Menor que DataPedido < SYSDATE

>= Maior ou igual a Quantidade >= 10

<= Menor ou igual a ValorTotal <= 1000

<> ou != Diferente de Status <> 'Inativo'


5.2.1 Exemplos com Operadores de Comparação

-- Selecionar produtos com preço igual a 50.00


SELECT NomeProduto, Preco
FROM Produtos
WHERE Preco = 50.00;

-- Selecionar clientes com ClienteID maior que 100


SELECT ClienteID, Nome
FROM Clientes
WHERE ClienteID > 100;

-- Selecionar vendas com valor total menor ou igual a 500.00


SELECT PedidoID, ValorTotal
FROM Pedidos
WHERE ValorTotal <= 500.00;

-- Selecionar produtos com estoque diferente de 0


SELECT NomeProduto, Estoque
FROM Produtos
WHERE Estoque <> 0;

5.3 Operadores Lógicos (AND, OR, NOT)

Operadores lógicos permitem combinar múltiplas condições na cláusula WHERE , criando filtros mais
complexos.

AND : Retorna TRUE se todas as condições forem TRUE .

OR : Retorna TRUE se pelo menos uma das condições for TRUE .

NOT : Inverte o resultado de uma condição (de TRUE para FALSE , e vice-versa).

5.3.1 Exemplos com Operadores Lógicos

-- Produtos com preço entre 100 e 500 (inclusive)


SELECT NomeProduto, Preco
FROM Produtos
WHERE Preco >= 100 AND Preco <= 500;

-- Clientes do estado de SP ou RJ
SELECT Nome, Estado
FROM Clientes
WHERE Estado = 'SP' OR Estado = 'RJ';

-- Produtos que NÃO estão ativos


SELECT NomeProduto, Status
FROM Produtos
WHERE NOT Status = 'Ativo';

-- Pedidos feitos em 2024 e com valor total acima de 1000


SELECT PedidoID, DataPedido, ValorTotal
FROM Pedidos
WHERE DataPedido BETWEEN DATE '2024-01-01' AND DATE '2024-12-31'
AND ValorTotal > 1000;

5.3.2 Precedência de Operadores

Assim como na matemática, os operadores lógicos têm uma ordem de precedência:

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')

SELECT NomeProduto, Preco, Estoque, Status


FROM Produtos
WHERE Preco > 100 AND (Estoque < 10 OR Status = 'Descontinuado');

5.4 Operadores Especiais

Além dos operadores de comparação e lógicos, o SQL oferece operadores especiais para tipos específicos de
filtragem.

5.4.1 BETWEEN: Intervalo de Valores

O operador BETWEEN é usado para selecionar valores dentro de um determinado intervalo (inclusive os
limites). É equivalente a usar >= e <= com AND .

Sintaxe:

WHERE coluna BETWEEN valor_inicial AND valor_final;

Exemplos:

-- Produtos com preço entre 100 e 500 (inclusive)


SELECT NomeProduto, Preco
FROM Produtos
WHERE Preco BETWEEN 100 AND 500;

-- Vendas realizadas em janeiro de 2025


SELECT PedidoID, DataPedido
FROM Pedidos
WHERE DataPedido BETWEEN DATE '2025-01-01' AND DATE '2025-01-31';

5.4.2 IN: Lista de Valores

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:

WHERE coluna IN (valor1, valor2, ...);

Exemplos:
-- Clientes dos estados de SP, RJ ou MG
SELECT Nome, Estado
FROM Clientes
WHERE Estado IN ('SP', 'RJ', 'MG');

-- Produtos com ProdutoID 101, 103 ou 105


SELECT NomeProduto, ProdutoID
FROM Produtos
WHERE ProdutoID IN (101, 103, 105);

5.4.3 LIKE: Busca por Padrões (Wildcards)

O operador LIKE é usado para buscar por padrões em colunas de texto. Ele utiliza caracteres curinga
(wildcards):

% (percentual): Representa zero ou mais caracteres.

_ (underscore): Representa um único caractere.

Sintaxe:

WHERE coluna LIKE 'padrao';

Exemplos:

-- Clientes cujo nome começa com 'Jo' (João, Jorge, Jose)


SELECT Nome
FROM Clientes
WHERE Nome LIKE 'Jo%';

-- Produtos cujo nome termina com 'er' (Gamer, Mixer)


SELECT NomeProduto
FROM Produtos
WHERE NomeProduto LIKE '%er';

-- Clientes cujo nome contém 'ilva' (Silva, Silveira)


SELECT Nome
FROM Clientes
WHERE Nome LIKE '%ilva%';

-- Produtos com nome de 5 caracteres, começando com 'M' e terminando com 'e' (Mouse)
SELECT NomeProduto
FROM Produtos
WHERE NomeProduto LIKE 'M___e';

-- Produtos que não contêm a palavra 'Pro'


SELECT NomeProduto
FROM Produtos
WHERE NomeProduto NOT LIKE '%Pro%';

5.4.4 IS NULL / IS NOT NULL: Verificando Valores Nulos

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:

WHERE coluna IS NULL;


WHERE coluna IS NOT NULL;

Exemplos:
-- Clientes que não possuem email cadastrado
SELECT Nome, Email
FROM Clientes
WHERE Email IS NULL;

-- Produtos que possuem descrição


SELECT NomeProduto, Descricao
FROM Produtos
WHERE Descricao IS NOT NULL;

5.5 Funções em Cláusulas WHERE

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:

-- Clientes cadastrados no ano de 2024


SELECT Nome, DataCadastro
FROM Clientes
WHERE TO_CHAR(DataCadastro, 'YYYY') = '2024';

-- Produtos cujo nome (em maiúsculas) começa com 'NOTE'


SELECT NomeProduto
FROM Produtos
WHERE UPPER(NomeProduto) LIKE 'NOTE%';

-- Vendas realizadas no mês atual


SELECT PedidoID, DataPedido
FROM Pedidos
WHERE TO_CHAR(DataPedido, 'MMYYYY') = TO_CHAR(SYSDATE, 'MMYYYY');

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' ).

5.6 Tópicos Chave do Capítulo 5

WHERE : Cláusula para filtrar linhas em consultas SELECT , UPDATE e DELETE .

Operadores de Comparação: = , > , < , >= , <= , <> (ou != ).

Operadores Lógicos: AND , OR , NOT para combinar condições.

Precedência: NOT > AND > OR (use parênteses para clareza).

Operadores Especiais:
BETWEEN : Para intervalos de valores.

IN : Para listas de valores.

LIKE : Para busca por padrões com curingas ( % , _ ).

IS NULL / IS NOT NULL : Para verificar valores ausentes.

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:

Selecione todos os clientes do estado de 'SP'.

Encontre todos os produtos com preço superior a R$ 1000,00.

Liste todos os pedidos com status 'Pendente'.

2. Combinando Condições:

Selecione produtos que custam entre R50, 00eR 200,00 (inclusive) e que tenham estoque maior
que 10 unidades.

Liste clientes que moram em 'Rio de Janeiro' ou 'Belo Horizonte'.

Encontre pedidos feitos em 2023 cujo valor total seja menor que R$ 500,00.

3. Operadores Especiais:

Selecione todos os produtos cujo nome começa com a letra 'A'.

Liste os clientes cujo email termina com '.[Link]'.

Encontre todos os produtos que não possuem descrição ( NULL ).

Selecione os clientes com ClienteID 1, 5 ou 10.

4. Funções em WHERE:

Liste todos os clientes cadastrados no mês de março de qualquer ano.

Encontre produtos cujo nome contenha a palavra 'Gaming' (case-insensitive).

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

[1] Oracle. SQL Language Reference - WHERE Clause. Disponível em:


[Link]
411F-B956-F73000673322
Capítulo 6: Ordenação e Limitação de Resultados

6.1 A Importância da Ordenação de Dados

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].

A ordenação de dados é fundamental para:

Apresentação de Relatórios: Dados ordenados são mais fáceis de ler e interpretar.

Análise de Dados: Identificar valores máximos, mínimos ou padrões nos dados.

Performance de Aplicações: Algumas operações são mais eficientes quando os dados estão ordenados.

Experiência do Usuário: Interfaces de usuário frequentemente exibem dados em ordem alfabética,


cronológica ou por relevância.

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.

6.2 ORDER BY: Ordenação Básica

A cláusula ORDER BY é usada para ordenar o conjunto de resultados de uma consulta por uma ou mais
colunas. A sintaxe básica é:

SELECT coluna1, coluna2, ...


FROM nome_da_tabela
WHERE condicao
ORDER BY coluna_ordenacao [ASC|DESC];

coluna_ordenacao : A coluna pela qual você deseja ordenar os resultados.

ASC (Ascending): Ordem crescente (padrão). Do menor para o maior valor.

DESC (Descending): Ordem decrescente. Do maior para o menor valor.

6.2.1 Ordenação Ascendente (ASC)

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;

-- Equivalente (ASC é padrão):


SELECT NomeProduto, Preco
FROM Produtos
ORDER BY Preco;

6.2.2 Ordenação Descendente (DESC)

Para ordenar em ordem decrescente, use a palavra-chave DESC .

Exemplo: Listar produtos ordenados por preço (do mais caro para o mais barato).

SELECT NomeProduto, Preco


FROM Produtos
ORDER BY Preco DESC;

6.2.3 Ordenação por Diferentes Tipos de Dados

A ordenação funciona de forma diferente dependendo do tipo de dado:

Números: Ordem numérica (1, 2, 10, 20, 100).

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.

Datas: Ordem cronológica (datas mais antigas primeiro em ASC).

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;

6.3 Ordenação por Múltiplas Colunas

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.

SELECT coluna1, coluna2, coluna3


FROM nome_da_tabela
ORDER BY coluna1 [ASC|DESC], coluna2 [ASC|DESC], coluna3 [ASC|DESC];

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.

SELECT NomeProduto, Preco, Estoque


FROM Produtos
ORDER BY Preco DESC, Estoque DESC;

6.3.1 Combinando Ordens Diferentes

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.

SELECT VendaID, DataVenda, Quantidade, PrecoUnitario


FROM Vendas
ORDER BY DataVenda DESC, Quantidade ASC;

6.4 Ordenação por Posição da Coluna

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.

SELECT NomeProduto, Preco, Estoque


FROM Produtos
ORDER BY 2; -- Ordena pela segunda coluna (Preco)

6.5 Ordenação por Expressões e Aliases

You can order by calculated expressions or by aliases defined in the SELECT clause.

6.5.1 Ordenação por Expressões

Exemplo: Ordenar produtos pelo valor total em estoque (preço × estoque).

SELECT NomeProduto, Preco, Estoque, Preco * Estoque AS ValorTotalEstoque


FROM Produtos
ORDER BY Preco * Estoque DESC;

6.5.2 Ordenação por Aliases

Exemplo: Usar o alias definido na cláusula SELECT para ordenação.


SELECT NomeProduto, Preco, Estoque, Preco * Estoque AS ValorTotalEstoque
FROM Produtos
ORDER BY ValorTotalEstoque DESC;

6.6 Limitação de Resultados

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.

6.6.1 LIMIT (MySQL, PostgreSQL, SQLite)

SELECT colunas
FROM tabela
ORDER BY coluna
LIMIT numero_de_linhas;

Exemplo: Os 5 produtos mais caros.

SELECT NomeProduto, Preco


FROM Produtos
ORDER BY Preco DESC
LIMIT 5;

6.6.2 TOP (SQL Server, MS Access)

SELECT TOP numero_de_linhas colunas


FROM tabela
ORDER BY coluna;

Exemplo: Os 5 produtos mais caros.

SELECT TOP 5 NomeProduto, Preco


FROM Produtos
ORDER BY Preco DESC;

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;

Exemplo: Os 5 produtos mais caros.

SELECT NomeProduto, Preco


FROM Produtos
ORDER BY Preco DESC
FETCH FIRST 5 ROWS ONLY;
6.6.4 ROWNUM (Oracle - versões mais antigas)

SELECT colunas
FROM (
SELECT colunas
FROM tabela
ORDER BY coluna
)
WHERE ROWNUM <= numero_de_linhas;

Exemplo: Os 5 produtos mais caros.

SELECT NomeProduto, Preco


FROM (
SELECT NomeProduto, Preco
FROM Produtos
ORDER BY Preco DESC
)
WHERE ROWNUM <= 5;

6.7 Paginação de Resultados

Para implementar paginação (dividir resultados em páginas), você precisa combinar limitação com
deslocamento (offset). Novamente, a sintaxe varia entre SGBDs.

6.7.1 LIMIT com OFFSET (MySQL, PostgreSQL, SQLite)

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 NomeProduto, Preco


FROM Produtos
ORDER BY NomeProduto
LIMIT 5 OFFSET 5;

6.7.2 OFFSET FETCH (SQL Server 2012+, PostgreSQL)

SELECT colunas
FROM tabela
ORDER BY coluna
OFFSET linhas_para_pular ROWS
FETCH NEXT numero_de_linhas ROWS ONLY;

Exemplo: Página 2 de produtos (5 produtos por página).

SELECT NomeProduto, Preco


FROM Produtos
ORDER BY NomeProduto
OFFSET 5 ROWS
FETCH NEXT 5 ROWS ONLY;
6.8 Considerações de Performance

A ordenação pode impactar significativamente a performance, especialmente em tabelas grandes. Algumas


considerações importantes:

Í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.

6.9 Tópicos Chave do Capítulo 6

ORDER BY : Essencial para controlar a ordem dos resultados de uma consulta.

ASC (padrão) e DESC : Controlam a direção da ordenação (crescente ou decrescente).

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).

Paginação: Combinação de limitação e deslocamento para dividir resultados em páginas.

Performance: Considere índices e filtragem para otimizar consultas com ordenação.

6.10 Exercícios do Capítulo 6

Usando as tabelas Produtos , Clientes e Vendas :

1. Ordenação Básica:

Liste todos os produtos ordenados por nome (alfabeticamente).

Mostre todos os clientes ordenados por sobrenome (Z para A).

Exiba todas as vendas ordenadas por data (mais recentes primeiro).

2. Ordenação por Múltiplas Colunas:

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:

Mostre apenas os 3 produtos mais caros.

Exiba os 5 clientes em ordem alfabética por nome.

Liste as 10 vendas mais recentes.


4. Ordenação por Expressões:

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

[1] PostgreSQL. Sorting Rows (ORDER BY). Disponível em: [Link]


[Link]

Capítulo 7: Funções de Agregação

7.1 Introdução às Funções de Agregação

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].

As funções de agregação são amplamente utilizadas para:

Análise de Dados: Calcular médias, somas, contagens, valores mínimos e máximos.

Relatórios Gerenciais: Gerar métricas e indicadores de desempenho.

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.

7.2 COUNT(): Contando Linhas e Valores

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.

7.2.1 COUNT(*): Contando Todas as Linhas

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;

Exemplo: Contar o número total de produtos na tabela Produtos .

SELECT COUNT(*)
FROM Produtos;

7.2.2 COUNT(coluna): Contando Valores Não Nulos

COUNT(coluna) retorna o número de linhas onde a coluna especificada não é NULL .

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;

7.2.3 COUNT(DISTINCT coluna): Contando Valores Únicos

COUNT(DISTINCT coluna) retorna o número de valores únicos e não nulos em uma coluna específica.

SELECT COUNT(DISTINCT coluna)


FROM nome_da_tabela;

Exemplo: Contar quantos preços únicos existem na tabela Produtos .

SELECT COUNT(DISTINCT Preco)


FROM Produtos;

7.3 SUM(): Somando Valores Numéricos

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;

Exemplo: Calcular o valor total de todos os produtos em estoque (soma do Estoque ).

SELECT SUM(Estoque)
FROM Produtos;

Exemplo: Calcular o faturamento total de todas as vendas.

SELECT SUM(Quantidade * PrecoUnitario) AS FaturamentoTotal


FROM Vendas;
7.4 AVG(): Calculando a Média

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;

Exemplo: Calcular o preço médio dos produtos.

SELECT AVG(Preco)
FROM Produtos;

7.5 MIN() e MAX(): Encontrando Valores Mínimos e Máximos

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 .

7.5.1 MIN(): Menor Valor

SELECT MIN(coluna)
FROM nome_da_tabela;

Exemplo: Encontrar o produto mais barato.

SELECT MIN(Preco)
FROM Produtos;

Exemplo: Encontrar a data da primeira venda.

SELECT MIN(DataVenda)
FROM Vendas;

7.5.2 MAX(): Maior Valor

SELECT MAX(coluna)
FROM nome_da_tabela;

Exemplo: Encontrar o produto mais caro.

SELECT MAX(Preco)
FROM Produtos;

Exemplo: Encontrar a data da última venda.

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.

Exemplo: Contar quantos produtos custam mais de R$ 1000,00.

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%\';

7.7 Tópicos Chave do Capítulo 7

Funções de Agregação: Operam em um conjunto de linhas para retornar um único valor de resumo.

COUNT(*) : Conta todas as linhas, incluindo nulos.

COUNT(coluna) : Conta linhas com valores não nulos na coluna especificada.

COUNT(DISTINCT coluna) : Conta valores únicos e não nulos na coluna especificada.

SUM(coluna_numerica) : Soma os valores de uma coluna numérica.

AVG(coluna_numerica) : Calcula a média dos valores de uma coluna numérica.

MIN(coluna) : Retorna o menor valor de uma coluna.

MAX(coluna) : Retorna o maior valor de uma coluna.

Interação com WHERE : A cláusula WHERE filtra as linhas antes da agregação.

7.8 Exercícios do Capítulo 7

Usando as tabelas Produtos , Clientes e Vendas com dados de exemplo:

1. Contagem:

Quantos clientes estão cadastrados na tabela Clientes ?

Quantos produtos têm uma Descricao preenchida (não nula)?

Quantos ClienteID s únicos realizaram compras na tabela Vendas ?

2. Soma e Média:

Qual é o Estoque total de todos os produtos?

Qual é o Preco médio dos produtos?

Qual foi o valor total (faturamento) de todas as vendas registradas?


3. Mínimo e Máximo:

Qual é o Preco mínimo de um produto?

Qual é o Preco máximo de um produto?

Qual a data da primeira venda registrada?

Qual a data da última venda registrada?

4. Combinando com WHERE:

Qual o Estoque total de produtos cujo Preco é maior que R$ 1500,00?

Quantas vendas foram realizadas após 1º de janeiro de 2025?

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

[1] W3Schools. SQL Aggregate Functions. Disponível em:


[Link]

Capítulo 8: Agrupamento de Dados (GROUP BY,


HAVING)

8.1 Introdução ao Agrupamento de Dados

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

A sintaxe básica de uma consulta com GROUP BY é:

SELECT coluna1, funcao_agregacao(coluna2)


FROM nome_da_tabela
WHERE condicao
GROUP BY coluna1;

coluna1 : A coluna pela qual você deseja agrupar os dados.

funcao_agregacao(coluna2) : Uma função de agregação aplicada a cada grupo.

8.2.1 Agrupamento por uma Única Coluna

Exemplo: Contar quantas vendas cada cliente realizou.

SELECT ClienteID, COUNT(*) AS NumeroDeVendas


FROM Vendas
GROUP BY ClienteID;

Este comando agrupa todas as vendas por ClienteID e conta quantas vendas existem para cada cliente.

Exemplo: Calcular o faturamento total por cliente.

SELECT ClienteID, SUM(Quantidade * PrecoUnitario) AS FaturamentoPorCliente


FROM Vendas
GROUP BY ClienteID;

8.2.2 Agrupamento por Múltiplas Colunas

Você pode agrupar por múltiplas colunas, criando grupos mais específicos.

SELECT coluna1, coluna2, funcao_agregacao(coluna3)


FROM nome_da_tabela
GROUP BY coluna1, coluna2;

Exemplo: Calcular o faturamento por cliente e por produto.

SELECT ClienteID, ProdutoID, SUM(Quantidade * PrecoUnitario) AS Faturamento


FROM Vendas
GROUP BY ClienteID, ProdutoID;

Este comando cria um grupo para cada combinação única de ClienteID e ProdutoID , calculando o
faturamento para cada combinação.

8.2.3 Regras Importantes do GROUP BY

Quando você usa GROUP BY , existem regras importantes a seguir:

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:

SELECT ClienteID, COUNT(*) AS NumeroDeVendas


FROM Vendas
GROUP BY ClienteID;

Exemplo Incorreto (gerará erro na maioria dos SGBDs):

SELECT ClienteID, ProdutoID, COUNT(*) AS NumeroDeVendas


FROM Vendas
GROUP BY ClienteID; -- ProdutoID deveria estar no GROUP BY

8.3 Combinando GROUP BY com WHERE

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.

SELECT ClienteID, COUNT(*) AS VendasEm2025


FROM Vendas
WHERE DataVenda >= '2025-01-01'
GROUP BY ClienteID;

8.4 HAVING: Filtrando Grupos

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.

SELECT coluna1, funcao_agregacao(coluna2)


FROM nome_da_tabela
WHERE condicao_linha
GROUP BY coluna1
HAVING condicao_grupo;

8.4.1 Filtrando por Resultados de Agregação

Exemplo: Mostrar apenas clientes que fizeram mais de 2 compras.

SELECT ClienteID, COUNT(*) AS NumeroDeVendas


FROM Vendas
GROUP BY ClienteID
HAVING COUNT(*) > 2;

Exemplo: Mostrar produtos cujo faturamento total foi superior a R$ 5000,00.

SELECT ProdutoID, SUM(Quantidade * PrecoUnitario) AS FaturamentoTotal


FROM Vendas
GROUP BY ProdutoID
HAVING SUM(Quantidade * PrecoUnitario) > 5000.00;
8.4.2 Diferença entre WHERE e HAVING

Aspecto WHERE HAVING

Quando é aplicado Antes do agrupamento Após o agrupamento

Opera em Linhas individuais Grupos de linhas

Pode usar funções de agregação Não Sim

Pode usar colunas não agregadas Sim Apenas se estiverem no GROUP BY

Exemplo que demonstra a diferença:

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

8.5 Ordenação com GROUP BY

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.

SELECT ClienteID, SUM(Quantidade * PrecoUnitario) AS FaturamentoTotal


FROM Vendas
GROUP BY ClienteID
ORDER BY FaturamentoTotal DESC;

8.6 Exemplos Práticos Avançados

8.6.1 Análise de Vendas por Mês

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;

8.6.2 Top 5 Produtos Mais Vendidos

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

8.6.3 Clientes com Faturamento Acima da Média

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
);

8.7 Tópicos Chave do Capítulo 8

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.

HAVING : Pode usar funções de agregação para filtrar grupos.

Combinação com ORDER BY : Permite ordenar os grupos resultantes.

8.8 Exercícios do Capítulo 8

Usando as tabelas Produtos , Clientes e Vendas :

1. Agrupamento Básico:

Conte quantas vendas cada produto teve.

Calcule o faturamento total por cliente.

Encontre a quantidade média vendida por produto.

2. Agrupamento por Múltiplas Colunas:

Calcule o faturamento por cliente e por produto.

Conte quantas vendas foram realizadas por mês (assumindo que você tem dados de diferentes
meses).

3. Usando HAVING:

Mostre apenas produtos que tiveram mais de 2 vendas.

Exiba clientes cujo faturamento total foi superior a R$ 3000,00.

Liste produtos cuja quantidade total vendida foi maior que 10 unidades.

4. Combinando WHERE e HAVING:

Para vendas realizadas em 2025, mostre apenas clientes que fizeram mais de 1 compra.

Para produtos com preço superior a R


1000, 00, calculeof aturamentototalemostreapenasaquelescomf aturamentosuperioraR
2000,00.

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

[1] MySQL. GROUP BY Optimization. Disponível em: [Link]


[Link]
Capítulo 9: Unindo Tabelas com JOINs

9.1 A Necessidade de Unir Tabelas

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.

9.2 Sintaxe Básica de JOIN

A sintaxe básica para unir duas tabelas é:

SELECT colunas
FROM tabela1
JOIN tabela2 ON [Link] = [Link];

tabela1 e tabela2 : As tabelas que você deseja unir.

ON : Especifica a condição de junção (como as tabelas estão relacionadas).

[Link] = [Link] : A condição que define como as linhas das duas tabelas devem ser
correspondidas.

9.3 INNER JOIN: Retornando Apenas Correspondências

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];

9.3.1 Exemplo Básico de INNER JOIN

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.

9.3.2 JOIN com Múltiplas Tabelas

Você pode unir mais de duas tabelas em uma única consulta:

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.

9.4 LEFT JOIN (LEFT OUTER JOIN): Incluindo Todas as Linhas da


Tabela Esquerda

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];

9.4.1 Exemplo de LEFT JOIN

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 .

9.4.2 Identificando Registros Sem Correspondência

O LEFT JOIN é útil para encontrar registros que não têm correspondência:

-- Clientes que nunca fizeram uma compra


SELECT
[Link],
[Link],
[Link]
FROM Clientes C
LEFT JOIN Vendas V ON [Link] = [Link]
WHERE [Link] IS NULL;

9.5 RIGHT JOIN (RIGHT OUTER JOIN): Incluindo Todas as Linhas da


Tabela Direita

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];

9.5.1 Exemplo de RIGHT JOIN

-- Todos os produtos e suas vendas (incluindo produtos nunca vendidos)


SELECT
[Link],
[Link],
[Link],
[Link]
FROM Vendas V
RIGHT JOIN Produtos P ON [Link] = [Link]
ORDER BY [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.

9.6 FULL JOIN (FULL OUTER JOIN): Incluindo Todas as Linhas de


Ambas as 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).

9.6.1 Exemplo de FULL JOIN

-- Todos os clientes e todas as vendas, mesmo se não houver correspondência


SELECT
[Link],
[Link],
[Link],
[Link]
FROM Clientes C
FULL OUTER JOIN Vendas V ON [Link] = [Link];

9.6.2 Simulando FULL JOIN em MySQL

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;

9.7 SELF JOIN: Unindo uma Tabela Consigo Mesma

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.

9.7.1 Exemplo de SELF JOIN

Suponha que temos uma tabela Funcionarios com uma coluna GerenteID que referencia o FuncionarioID
do gerente:

CREATE TABLE Funcionarios (


FuncionarioID INT PRIMARY KEY,
Nome VARCHAR(100),
GerenteID INT,
FOREIGN KEY (GerenteID) REFERENCES Funcionarios(FuncionarioID)
);

Para listar funcionários com seus gerentes:


SELECT
[Link] AS Funcionario,
[Link] AS Gerente
FROM Funcionarios F
LEFT JOIN Funcionarios G ON [Link] = [Link];

9.8 Condições de JOIN Mais Complexas

Embora a maioria dos JOINs use igualdade simples, você pode usar condições mais complexas:

9.8.1 JOIN com Múltiplas Condições

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

9.8.2 JOIN com Operadores Diferentes

-- Encontrar vendas onde o preço unitário é diferente do preço atual do produto


SELECT
[Link],
[Link],
[Link] AS PrecoVenda,
[Link] AS PrecoAtual
FROM Vendas V
INNER JOIN Produtos P ON [Link] = [Link]
WHERE [Link] <> [Link];

9.9 Performance e Otimização de JOINs

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).

9.10 Tópicos Chave do Capítulo 9

JOINs: Permitem combinar dados de múltiplas tabelas relacionadas.

INNER JOIN : Retorna apenas linhas com correspondências em ambas as tabelas.

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.

Performance: Use índices e filtragem adequada para otimizar JOINs.

9.11 Exercícios do Capítulo 9

Usando as tabelas Produtos , Clientes e Vendas :

1. INNER JOINs Básicos:

Liste todas as vendas com o nome do cliente e nome do produto.

Mostre o faturamento total por cliente (nome do cliente e valor total).

2. LEFT JOINs:

Liste todos os clientes e suas vendas, incluindo clientes que nunca compraram.

Encontre todos os produtos e suas vendas, incluindo produtos nunca vendidos.

3. Identificando Registros Órfãos:

Encontre clientes que nunca fizeram uma compra.

Encontre produtos que nunca foram vendidos.

4. JOINs com Múltiplas Tabelas:

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

[1] Oracle. SQL Joins. Disponível em: [Link]


database/19/sqlrf/[Link]
Capítulo 10: Subconsultas (Subqueries)

10.1 Introdução às Subconsultas

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

10.2 Tipos de Subconsultas

10.2.1 Subconsultas Escalares

Uma subconsulta escalar retorna exatamente um valor (uma linha e uma coluna). Pode ser usada em
qualquer lugar onde um valor único é esperado.

SELECT coluna1, (SELECT valor FROM tabela2 WHERE condicao) AS coluna_calculada


FROM tabela1;

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;

10.2.2 Subconsultas de Múltiplas Linhas

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);

10.2.3 Subconsultas Correlacionadas vs. Não Correlacionadas

Não Correlacionadas: A subconsulta pode ser executada independentemente da consulta externa.


Correlacionadas: A subconsulta referencia colunas da consulta externa e deve ser executada para cada
linha da consulta externa.

10.3 Subconsultas na Cláusula SELECT

Subconsultas na cláusula SELECT devem retornar exatamente um valor (subconsultas escalares).

10.3.1 Exemplo Básico

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.

10.3.2 Cálculos Complexos

SELECT
ProdutoID,
NomeProduto,
Preco,
(SELECT MAX(Preco) FROM Produtos) AS PrecoMaximo,
ROUND((Preco / (SELECT MAX(Preco) FROM Produtos)) * 100, 2) AS PercentualDoMaximo
FROM Produtos;

10.4 Subconsultas na Cláusula FROM (Tabelas Derivadas)

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;

10.4.1 Exemplo de Tabela Derivada

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

Permitem aplicar filtros em resultados agregados

Simplificam consultas complexas dividindo-as em etapas lógicas

Podem melhorar a legibilidade do código

10.5 Subconsultas na Cláusula WHERE

Esta é uma das aplicações mais comuns de subconsultas, permitindo filtrar dados baseados em resultados de
outras consultas.

10.5.1 Usando IN com Subconsultas

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 todos os produtos que já foram vendidos.

SELECT ProdutoID, NomeProduto, Preco


FROM Produtos
WHERE ProdutoID IN (SELECT DISTINCT ProdutoID FROM Vendas);

Exemplo: Encontrar clientes que compraram produtos com preço superior a R$ 1500.

SELECT ClienteID, Nome, Sobrenome


FROM Clientes
WHERE ClienteID IN (
SELECT DISTINCT [Link]
FROM Vendas V
INNER JOIN Produtos P ON [Link] = [Link]
WHERE [Link] > 1500
);

10.5.2 Usando NOT IN com Subconsultas

-- Produtos que nunca foram vendidos


SELECT ProdutoID, NomeProduto, Preco
FROM Produtos
WHERE ProdutoID NOT IN (SELECT DISTINCT ProdutoID FROM Vendas WHERE ProdutoID IS NOT NULL);

Importante: Cuidado com valores NULL ao usar NOT IN . Se a subconsulta retornar algum valor NULL , NOT
IN pode não funcionar como esperado.

10.5.3 Usando EXISTS

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]);

Exemplo: Clientes que fizeram pelo menos uma compra.

SELECT ClienteID, Nome, Sobrenome


FROM Clientes C
WHERE EXISTS (
SELECT 1
FROM Vendas V
WHERE [Link] = [Link]
);

10.5.4 Usando NOT EXISTS

-- Clientes que nunca fizeram uma compra


SELECT ClienteID, Nome, Sobrenome
FROM Clientes C
WHERE NOT EXISTS (
SELECT 1
FROM Vendas V
WHERE [Link] = [Link]
);

10.5.5 Operadores ANY e ALL

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.

-- Produtos mais caros que qualquer produto vendido


SELECT ProdutoID, NomeProduto, Preco
FROM Produtos
WHERE Preco > ANY (
SELECT [Link]
FROM Vendas V
INNER JOIN Produtos P ON [Link] = [Link]
);

-- Produtos mais caros que todos os produtos vendidos


SELECT ProdutoID, NomeProduto, Preco
FROM Produtos
WHERE Preco > ALL (
SELECT [Link]
FROM Vendas V
INNER JOIN Produtos P ON [Link] = [Link]
);

10.6 Subconsultas Correlacionadas

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]
);

10.6.2 Encontrando Duplicatas

-- Clientes com nomes duplicados


SELECT ClienteID, Nome, Sobrenome
FROM Clientes C1
WHERE EXISTS (
SELECT 1
FROM Clientes C2
WHERE [Link] = [Link]
AND [Link] = [Link]
AND [Link] <> [Link]
);

10.7 Subconsultas vs. JOINs

Muitas vezes, você pode resolver o mesmo problema usando subconsultas ou JOINs. A escolha depende de
fatores como performance, legibilidade e preferência pessoal.

10.7.1 Comparação de Abordagens

Usando Subconsulta:

SELECT ClienteID, Nome, Sobrenome


FROM Clientes
WHERE ClienteID IN (SELECT DISTINCT ClienteID FROM Vendas);

Usando JOIN:

SELECT DISTINCT [Link], [Link], [Link]


FROM Clientes C
INNER JOIN Vendas V ON [Link] = [Link];

10.7.2 Quando Usar Cada Abordagem

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

10.8 Tópicos Chave do Capítulo 10

Subconsultas: Consultas aninhadas dentro de outras consultas.


Tipos: Escalares (um valor), múltiplas linhas, correlacionadas vs. não correlacionadas.

Localização: Podem aparecer em SELECT , FROM , WHERE , HAVING .

Operadores: IN , NOT IN , EXISTS , NOT EXISTS , ANY , ALL .

Tabelas Derivadas: Subconsultas na cláusula FROM criam tabelas temporárias.

Correlacionadas: Referenciam colunas da consulta externa.

Performance: Compare com JOINs para encontrar a melhor abordagem.

10.9 Exercícios do Capítulo 10

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 todos os clientes com o número de compras que cada um fez.

2. Subconsultas com IN/NOT IN:

Encontre todos os produtos que já foram vendidos.

Encontre todos os clientes que nunca fizeram uma compra.

Liste produtos que foram vendidos por um preço diferente do seu preço atual.

3. Subconsultas com EXISTS/NOT EXISTS:

Encontre clientes que fizeram pelo menos uma compra em 2025.

Liste produtos que nunca foram vendidos usando NOT EXISTS .

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

[1] Microsoft. Subqueries (SQL Server). Disponível em: [Link]


databases/performance/subqueries?view=sql-server-ver16
Capítulo 11: Visões (Views)

11.1 O que são Views e Por Que Usá-las?

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.

As Views são extremamente úteis por várias razões:

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.

11.2 CREATE VIEW: Criando uma View

A sintaxe básica para criar uma View é:

CREATE VIEW nome_da_view AS


SELECT coluna1, coluna2, ...
FROM tabela1
WHERE condicao;

nome_da_view : O nome que você dará à sua View.

SELECT ... : A consulta SQL que define os dados que a View irá expor.

11.2.1 Exemplo Básico de View

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:

SELECT NomeCompleto, Email


FROM ClientesCompletos
WHERE NomeCompleto LIKE \'Ana%\';

11.2.2 View com JOINs

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.

CREATE VIEW DetalhesVendas AS


SELECT
[Link],
[Link],
[Link] || \' \' || [Link] AS NomeCliente, -- Use CONCAT() ou + dependendo do SGBD
[Link],
[Link],
[Link],
([Link] * [Link]) AS ValorTotalVenda
FROM Vendas V
INNER JOIN Clientes C ON [Link] = [Link]
INNER JOIN Produtos P ON [Link] = [Link];

Agora, para obter um relatório de vendas, basta consultar a View DetalhesVendas :

SELECT NomeCliente, NomeProduto, Quantidade, ValorTotalVenda


FROM DetalhesVendas
WHERE DataVenda >= \'2025-01-01\'
ORDER BY ValorTotalVenda DESC;

11.2.3 View com Funções de Agregação

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.

CREATE VIEW FaturamentoPorCliente AS


SELECT
[Link],
[Link] || \' \' || [Link] AS NomeCliente, -- Use CONCAT() ou + dependendo do SGBD
SUM([Link] * [Link]) AS FaturamentoTotal
FROM Clientes C
INNER JOIN Vendas V ON [Link] = [Link]
GROUP BY [Link], [Link], [Link];

Consulta à View:

SELECT NomeCliente, FaturamentoTotal


FROM FaturamentoPorCliente
WHERE FaturamentoTotal > 10000;
11.3 ALTER VIEW: Modificando uma 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.

ALTER VIEW nome_da_view AS


SELECT nova_coluna1, nova_coluna2, ...
FROM nova_tabela
WHERE nova_condicao;

Exemplo: Adicionar a coluna Email à View ClientesCompletos .

ALTER VIEW ClientesCompletos AS


SELECT
ClienteID,
Nome || \' \' || Sobrenome AS NomeCompleto, -- Use CONCAT() ou + dependendo do SGBD
Email,
Telefone
FROM Clientes;

11.4 DROP VIEW: Removendo uma View

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.

DROP VIEW nome_da_view;

Exemplo:

DROP VIEW ClientesCompletos;

11.5 Visões Atualizáveis e Não Atualizáveis

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).

11.5.1 Condições para Views Atualizáveis (Regras Gerais)

As regras exatas podem variar ligeiramente entre os SGBDs, mas, em geral, uma View é atualizável se:

É baseada em uma única tabela.

Não contém funções de agregação ( SUM , AVG , COUNT , MIN , MAX ).

Não contém a cláusula GROUP BY ou HAVING .

Não contém a cláusula DISTINCT .

Não contém subconsultas na lista SELECT .

Não contém UNION , UNION ALL , INTERSECT ou EXCEPT .


Todas as colunas NOT NULL da tabela base estão presentes na View.

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 .

11.5.2 Exemplo de View Atualizável

CREATE VIEW ProdutosSimples AS


SELECT ProdutoID, NomeProduto, Preco
FROM Produtos;

Você pode inserir, atualizar ou excluir dados através desta View:

INSERT INTO ProdutosSimples (ProdutoID, NomeProduto, Preco)


VALUES (4, \'Teclado Mecânico\', 350.00);

UPDATE ProdutosSimples
SET Preco = 370.00
WHERE ProdutoID = 4;

DELETE FROM ProdutosSimples


WHERE ProdutoID = 4;

Essas operações DML na View ProdutosSimples serão refletidas diretamente na tabela base Produtos .

11.5.3 Exemplo de View Não Atualizável

A View DetalhesVendas (criada anteriormente com JOINs) e FaturamentoPorCliente (com GROUP BY e


SUM ) são exemplos de Views não atualizáveis, pois violam as regras mencionadas.

11.6 Tópicos Chave do Capítulo 11

Views (Visões): Tabelas virtuais baseadas em consultas SQL, não armazenam dados fisicamente.

Vantagens: Simplificação de consultas, segurança, consistência, abstração e reusabilidade.

CREATE VIEW : Cria uma nova View a partir de uma consulta SELECT .

ALTER VIEW : Modifica a definição de uma View existente.

DROP VIEW : Remove uma View.

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.

11.7 Exercícios do Capítulo 11

1. Criação de Views Simples:

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\'.

3. View com Agregação:

Crie uma View chamada ResumoVendasPorProduto que mostre ProdutoID , NomeProduto ,


QuantidadeTotalVendida e FaturamentoTotalProduto . (Você precisará de JOIN s e GROUP BY ).

Consulte ResumoVendasPorProduto para encontrar os 3 produtos com maior faturamento.

4. Modificação e Exclusão de Views:

Altere a View ProdutosEmEstoque para incluir também a coluna Preco .

Exclua a View ClientesAtivos .

5. Desafio - Views Atualizáveis:

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.

Tente excluir um cliente através desta View. Explique o que acontece.

Referências

[1] W3Schools. SQL Views. Disponível em: [Link]

Capítulo 12: Índices para Otimização de


Performance

12.1 O que são Índices e Por Que São Importantes?

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.

Os índices são fundamentais para:

Performance de Consultas: Aceleram drasticamente as operações SELECT , especialmente aquelas com


cláusulas WHERE , ORDER BY e JOIN .

Integridade de Dados: Índices únicos garantem que não haja valores duplicados em colunas
específicas.

Otimização de JOINs: Facilitam a junção eficiente de tabelas através de chaves estrangeiras.

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.

12.2 Como os Índices Funcionam

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.

12.2.1 Estruturas de Dados de Índices

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).

Índices Espaciais: Para dados geográficos e espaciais.

12.3 Tipos de Índices

12.3.1 Índice Clustered (Agrupado)

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.

Exemplo (SQL Server):

CREATE CLUSTERED INDEX IX_Clientes_ClienteID


ON Clientes (ClienteID);

12.3.2 Índice Non-Clustered (Não Agrupado)

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:

CREATE INDEX IX_Clientes_Sobrenome


ON Clientes (Sobrenome);

12.3.3 Índice Único

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:

CREATE UNIQUE INDEX IX_Clientes_Email


ON Clientes (Email);

12.3.4 Índice Composto (Multi-Coluna)

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:

CREATE INDEX IX_Vendas_Cliente_Data


ON Vendas (ClienteID, DataVenda);

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 .

12.3.5 Índice Parcial (Filtered Index)

Alguns SGBDs permitem criar índices que incluem apenas um subconjunto das linhas da tabela, baseado em
uma condição.
Exemplo (SQL Server):

CREATE INDEX IX_Produtos_Ativos


ON Produtos (NomeProduto)
WHERE Ativo = 1;

12.4 CREATE INDEX: Criando Índices

A sintaxe básica para criar um índice é:

CREATE [UNIQUE] INDEX nome_do_indice


ON nome_da_tabela (coluna1 [ASC|DESC], coluna2 [ASC|DESC], ...);

12.4.1 Exemplos Práticos

Índice simples para acelerar consultas por sobrenome:

CREATE INDEX IX_Clientes_Sobrenome


ON Clientes (Sobrenome);

Índice único para garantir emails únicos:

CREATE UNIQUE INDEX IX_Clientes_Email_Unico


ON Clientes (Email);

Índice composto para consultas por cliente e data:

CREATE INDEX IX_Vendas_Cliente_Data


ON Vendas (ClienteID, DataVenda DESC);

Índice para acelerar JOINs:

CREATE INDEX IX_Vendas_ProdutoID


ON Vendas (ProdutoID);

12.5 Quando Criar Índices

12.5.1 Situações Onde Índices São Benéficos

Colunas Frequentemente Usadas em WHERE: Se você frequentemente filtra por uma coluna
específica.

Colunas de JOIN: Chaves estrangeiras devem quase sempre ter índices.

Colunas de ORDER BY: Para acelerar a ordenação de resultados.

Colunas de GROUP BY: Para acelerar operações de agrupamento.

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

Tabelas Pequenas: O overhead do índice pode ser maior que o benefício.

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.

12.6 Impacto dos Índices na Performance

12.6.1 Benefícios para Consultas (SELECT)

-- Sem índice: Table scan completo


SELECT * FROM Clientes WHERE Sobrenome = \'Silva\';

-- Com índice em Sobrenome: Busca rápida


-- O SGBD usa o índice para ir diretamente às linhas relevantes

12.6.2 Impacto em Operações DML

INSERT:

-- Cada INSERT precisa atualizar todos os índices da tabela


INSERT INTO Clientes (Nome, Sobrenome, Email)
VALUES (\'João\', \'Santos\', \'[Link]@[Link]\');
-- Se há índices em Sobrenome e Email, ambos precisam ser atualizados

UPDATE:

-- Se colunas indexadas são atualizadas, os índices precisam ser reorganizados


UPDATE Clientes
SET Sobrenome = \'Silva\'
WHERE ClienteID = 123;
-- O índice em Sobrenome precisa ser atualizado

DELETE:

-- Entradas dos índices precisam ser removidas


DELETE FROM Clientes WHERE ClienteID = 123;
-- Todos os índices da tabela precisam ser atualizados

12.7 Monitoramento e Análise de Índices

12.7.1 Planos de Execução

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\';

12.7.2 Identificando Índices Não Utilizados

Í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\');

12.8 DROP INDEX: Removendo Índices

Para remover um índice que não é mais necessário:

DROP INDEX nome_do_indice ON nome_da_tabela;

Exemplo:

DROP INDEX IX_Clientes_Sobrenome ON Clientes;

Nota: Você não pode remover índices criados automaticamente para chaves primárias ou restrições únicas
sem primeiro remover a restrição.

12.9 Melhores Práticas para Índices

12.9.1 Diretrizes Gerais

Comece com o Básico: Sempre tenha índices em chaves primárias e estrangeiras.

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.

12.9.2 Evite Sobre-Indexação

Limite o Número de Índices: Muitos índices podem prejudicar a performance de DML.

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.

12.10 Tópicos Chave do Capítulo 12

Índices: Estruturas de dados que aceleram consultas ao criar \"atalhos\" para os dados.

Tipos: Clustered, Non-clustered, Único, Composto, Parcial.

Benefícios: Aceleram SELECT, JOIN, ORDER BY, GROUP BY.

Custos: Espaço adicional, performance mais lenta para DML.

CREATE INDEX: Comando para criar índices.

DROP INDEX: Comando para remover índices.

Monitoramento: Use planos de execução e estatísticas de uso.

Melhores Práticas: Indexe baseado em consultas reais, monitore performance, evite sobre-indexação.

12.11 Exercícios do Capítulo 12

1. Criação de Índices Básicos:

Crie um índice na coluna Email da tabela Clientes .

Crie um índice na coluna DataVenda da tabela Vendas .

Crie um índice composto nas colunas ClienteID e DataVenda da tabela Vendas .

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.

3. Índices para JOINs:

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).

Monitore o uso deste índice (se o seu SGBD permitir).

Remova o índice desnecessário.

5. Desafio:

Analise uma consulta complexa que envolve WHERE, JOIN e ORDER BY.

Identifique quais índices seriam benéficos para esta consulta.

Crie os índices e compare a performance antes e depois.

Referências

[1] Oracle. Database Concepts - Indexes. Disponível em: [Link]


database/19/cncpt/[Link]

Capítulo 13: Transações e Controle de Concorrência

13.1 O Conceito de Transação

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.

13.1.1 Propriedades ACID

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).

13.2 Comandos de Controle de Transação

Os principais comandos SQL para controlar transações são:

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.

13.2.1 Exemplo de Transação

Vamos simular uma transferência de estoque entre dois produtos:

-- Iniciar uma transação (sintaxe pode variar: BEGIN TRANSACTION, START TRANSACTION)
BEGIN TRANSACTION;

-- Diminuir o estoque do Produto A


UPDATE Produtos
SET Estoque = Estoque - 5
WHERE ProdutoID = 1;

-- Aumentar o estoque do Produto B


UPDATE Produtos
SET Estoque = Estoque + 5
WHERE ProdutoID = 2;

-- Verificar se ambas as operações foram bem-sucedidas


-- (Em um cenário real, haveria lógica para verificar erros)

-- Se tudo estiver OK, confirmar a transação


COMMIT;

-- Se algo der errado, desfazer tudo


-- ROLLBACK;
13.2.2 Usando SAVEPOINT

BEGIN TRANSACTION;

INSERT INTO Clientes (ClienteID, Nome, Sobrenome, Email)


VALUES (104, \'Fernando\', \'Lima\', \'[Link]@[Link]\');

SAVEPOINT ClienteInserido;

INSERT INTO Produtos (ProdutoID, NomeProduto, Preco)


VALUES (4, \'Fone de Ouvido\', 150.00);

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

13.3 Controle de Concorrência

Em um ambiente multiusuário, várias transações podem ser executadas simultaneamente. O controle de


concorrência é o mecanismo que o SGBD utiliza para gerenciar essas transações concorrentes, garantindo que
as propriedades ACID sejam mantidas e que os dados permaneçam consistentes [2].

Sem controle de concorrência adequado, podem ocorrer problemas como:

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.

13.4 Níveis de Isolamento de Transação

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).

Os níveis de isolamento, do menor para o maior isolamento, são:

13.4.1 Read Uncommitted (Leitura Não Confirmada)

Permite: Dirty Reads, Non-Repeatable Reads, Phantom Reads.

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).

13.4.2 Read Committed (Leitura Confirmada)

Evita: Dirty Reads.

Permite: Non-Repeatable Reads, Phantom Reads.

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.

13.4.3 Repeatable Read (Leitura Repetível)

Evita: Dirty Reads, Non-Repeatable Reads.

Permite: Phantom Reads.

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.

13.4.4 Serializable (Serializável)

Evita: Dirty Reads, Non-Repeatable Reads, Phantom Reads.

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.

13.4.5 Como Definir o Nível de Isolamento

A sintaxe para definir o nível de isolamento pode variar entre os SGBDs. Geralmente, é feito com o comando
SET TRANSACTION ISOLATION LEVEL .

Exemplo (SQL Server, PostgreSQL):

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

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.

13.5.1 Tipos de Bloqueios

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.

13.5.2 Granularidade dos Bloqueios

Os bloqueios podem ser aplicados em diferentes níveis de granularidade:

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:

Transação A bloqueia Recurso X.

Transação B bloqueia Recurso Y.

Transação A tenta bloquear Recurso Y (e espera por B).

Transação B tenta bloquear Recurso X (e espera por A).

Ambas as transações estão esperando uma pela outra, resultando em um deadlock.

13.6 Tópicos Chave do Capítulo 13

Transação: Unidade atômica de trabalho que garante a integridade dos dados.

Propriedades ACID: Atomicidade, Consistência, Isolamento, Durabilidade.


Comandos de Controle de Transação: COMMIT (confirma), ROLLBACK (desfaz), SAVEPOINT (ponto de
reversão).

Controle de Concorrência: Mecanismo para gerenciar transações simultâneas.

Problemas de Concorrência: Dirty Read, Non-Repeatable Read, Phantom Read.

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).

Granularidade de Bloqueios: Linha, página, tabela.

Deadlock: Situação onde transações se bloqueiam mutuamente, exigindo que o SGBD desfaça uma
delas.

13.7 Exercícios do Capítulo 13

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.

3. Entendendo Níveis de Isolamento:

Pesquise qual é o nível de isolamento padrão do SGBD que você está usando (MySQL, PostgreSQL,
SQL Server, Oracle).

Descreva um cenário em que um Non-Repeatable Read poderia ocorrer no nível Read


Committed .

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).

Na Sessão 1, faça COMMIT . Na Sessão 2, leia novamente. O que acontece?

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

[1] Microsoft. Transactions (SQL Server). Disponível em: [Link]


databases/transactions/transactions?view=sql-server-ver16 [2] Oracle. Database Concepts - Concurrency
Control. Disponível em: [Link]
[Link]

Capítulo 14: Restrições de Integridade

14.1 O Que São Restrições de Integridade?

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].

As restrições de integridade são aplicadas automaticamente pelo Sistema de Gerenciamento de Banco de


Dados (SGBD) sempre que uma operação DML (INSERT, UPDATE, DELETE) é executada. Se uma operação
violar uma restrição, o SGBD rejeita a operação e retorna um erro, mantendo o banco de dados em um estado
consistente.

Existem vários tipos de restrições de integridade:

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).

14.2 PRIMARY KEY: Garantindo a Unicidade de Entidades

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.

14.2.1 Características da PRIMARY KEY

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 .

Imutabilidade: Embora tecnicamente possível, é altamente recomendado que os valores da chave


primária não sejam alterados após a inserção.
Índice Automático: A maioria dos SGBDs cria automaticamente um índice único na chave primária para
garantir acesso rápido e unicidade.

14.2.2 Definindo PRIMARY KEY

Durante a criação da tabela:

CREATE TABLE Clientes (


ClienteID INT PRIMARY KEY,
Nome VARCHAR(100) NOT NULL,
Email VARCHAR(255) UNIQUE
);

Chave primária composta (múltiplas colunas):

CREATE TABLE ItensVenda (


VendaID INT,
ProdutoID INT,
Quantidade INT NOT NULL,
PrecoUnitario DECIMAL(10, 2) NOT NULL,
PRIMARY KEY (VendaID, ProdutoID)
);

Adicionando PRIMARY KEY após a criação da tabela:

ALTER TABLE Clientes


ADD CONSTRAINT PK_Clientes PRIMARY KEY (ClienteID);

14.2.3 Chaves Primárias Naturais vs. Artificiais

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.

Exemplo de chave artificial com auto-incremento:

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

14.3.1 Características da FOREIGN KEY

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.

14.3.2 Definindo FOREIGN KEY

Durante a criação da tabela:

CREATE TABLE Vendas (


VendaID INT PRIMARY KEY,
ClienteID INT,
ProdutoID INT,
DataVenda DATE,
Quantidade INT NOT NULL,
FOREIGN KEY (ClienteID) REFERENCES Clientes(ClienteID),
FOREIGN KEY (ProdutoID) REFERENCES Produtos(ProdutoID)
);

Adicionando FOREIGN KEY após a criação:

ALTER TABLE Vendas


ADD CONSTRAINT FK_Vendas_Clientes
FOREIGN KEY (ClienteID) REFERENCES Clientes(ClienteID);

14.3.3 Ações de Integridade Referencial

Quando uma linha referenciada por uma chave estrangeira é atualizada ou excluída, você pode especificar
que ação deve ser tomada:

NO ACTION / RESTRICT: Impede a operação se houver registros dependentes (padrão).

CASCADE: Propaga a operação para os registros dependentes.

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.

Exemplo com ações de cascata:


CREATE TABLE Vendas (
VendaID INT PRIMARY KEY,
ClienteID INT,
ProdutoID INT,
DataVenda DATE,
FOREIGN KEY (ClienteID) REFERENCES Clientes(ClienteID)
ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (ProdutoID) REFERENCES Produtos(ProdutoID)
ON DELETE SET NULL ON UPDATE CASCADE
);

14.4 UNIQUE: Garantindo Valores Únicos

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).

14.4.1 Definindo UNIQUE

Durante a criação da tabela:

CREATE TABLE Clientes (


ClienteID INT PRIMARY KEY,
Nome VARCHAR(100) NOT NULL,
Email VARCHAR(255) UNIQUE,
CPF VARCHAR(11) UNIQUE
);

Restrição UNIQUE composta:

CREATE TABLE Funcionarios (


FuncionarioID INT PRIMARY KEY,
Nome VARCHAR(100) NOT NULL,
Departamento VARCHAR(50),
NumeroMatricula VARCHAR(20),
UNIQUE (Departamento, NumeroMatricula)
);

Adicionando UNIQUE após a criação:

ALTER TABLE Clientes


ADD CONSTRAINT UK_Clientes_Email UNIQUE (Email);

14.5 NOT NULL: Garantindo Valores Obrigatórios

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.

14.5.1 Definindo NOT NULL

Durante a criação da tabela:


CREATE TABLE Produtos (
ProdutoID INT PRIMARY KEY,
NomeProduto VARCHAR(255) NOT NULL,
Preco DECIMAL(10, 2) NOT NULL,
Descricao TEXT -- Permite NULL
);

Adicionando NOT NULL após a criação:

ALTER TABLE Produtos


ALTER COLUMN Descricao SET NOT NULL; -- PostgreSQL

-- SQL Server
ALTER TABLE Produtos
ALTER COLUMN Descricao VARCHAR(500) NOT NULL;

Removendo NOT NULL:

ALTER TABLE Produtos


ALTER COLUMN Descricao DROP NOT NULL; -- PostgreSQL

-- SQL Server
ALTER TABLE Produtos
ALTER COLUMN Descricao VARCHAR(500) NULL;

14.6 CHECK: Validando Valores de Domínio

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.

14.6.1 Definindo CHECK

Durante a criação da tabela:

CREATE TABLE Produtos (


ProdutoID INT PRIMARY KEY,
NomeProduto VARCHAR(255) NOT NULL,
Preco DECIMAL(10, 2) NOT NULL CHECK (Preco > 0),
Estoque INT NOT NULL CHECK (Estoque >= 0),
Categoria VARCHAR(50) CHECK (Categoria IN (
\"Eletrônicos\", \"Roupas\", \"Livros\", \"Casa\"
"Casa\"
))
);

Restrições CHECK mais complexas:

CREATE TABLE Funcionarios (


FuncionarioID INT PRIMARY KEY,
Nome VARCHAR(100) NOT NULL,
DataNascimento DATE,
DataAdmissao DATE,
Salario DECIMAL(10, 2),
CHECK (DataAdmissao >= DataNascimento),
CHECK (Salario > 0),
CHECK (DATEDIFF(YEAR, DataNascimento, DataAdmissao) >= 16) -- Idade mínima de 16 anos
);

Adicionando CHECK após a criação:


ALTER TABLE Produtos
ADD CONSTRAINT CK_Produtos_Preco_Positivo CHECK (Preco > 0);

14.6.2 Limitações das Restrições CHECK

Não podem referenciar outras tabelas (use triggers para validações complexas).

Não podem usar subconsultas.

Não podem usar funções não determinísticas (como GETDATE() , RAND() ).

14.7 DEFAULT: Definindo Valores Padrão

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.

14.7.1 Definindo DEFAULT

Durante a criação da tabela:

CREATE TABLE Vendas (


VendaID INT PRIMARY KEY,
ClienteID INT NOT NULL,
DataVenda DATE DEFAULT CURRENT_DATE,
Status VARCHAR(20) DEFAULT \"Pendente\",
Desconto DECIMAL(5, 2) DEFAULT 0.00
);

Adicionando DEFAULT após a criação:

ALTER TABLE Vendas


ALTER COLUMN Status SET DEFAULT \"Ativo\"; -- PostgreSQL

-- SQL Server
ALTER TABLE Vendas
ADD CONSTRAINT DF_Vendas_Status DEFAULT \"Ativo\" FOR Status;

14.8 Gerenciamento de Restrições

14.8.1 Nomeando Restrições

É uma boa prática nomear explicitamente suas restrições para facilitar o gerenciamento:

CREATE TABLE Clientes (


ClienteID INT,
Nome VARCHAR(100) NOT NULL,
Email VARCHAR(255),
DataNascimento DATE,
CONSTRAINT PK_Clientes PRIMARY KEY (ClienteID),
CONSTRAINT UK_Clientes_Email UNIQUE (Email),
CONSTRAINT CK_Clientes_DataNascimento CHECK (DataNascimento <= CURRENT_DATE)
);
14.8.2 Removendo Restrições

-- Remover restrição por nome


ALTER TABLE Clientes
DROP CONSTRAINT UK_Clientes_Email;

-- Remover PRIMARY KEY


ALTER TABLE Clientes
DROP CONSTRAINT PK_Clientes;

-- Remover FOREIGN KEY


ALTER TABLE Vendas
DROP CONSTRAINT FK_Vendas_Clientes;

14.8.3 Habilitando e Desabilitando Restrições

Alguns SGBDs permitem desabilitar temporariamente restrições (útil para cargas de dados em massa):

-- SQL Server - Desabilitar


ALTER TABLE Vendas
NOCHECK CONSTRAINT FK_Vendas_Clientes;

-- SQL Server - Habilitar


ALTER TABLE Vendas
CHECK CONSTRAINT FK_Vendas_Clientes;

14.9 Tópicos Chave do Capítulo 14

Restrições de Integridade: Regras que garantem a validade e consistência dos dados.

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.

UNIQUE: Garante valores únicos; permite NULL (geralmente apenas um).

NOT NULL: Impede valores nulos em colunas obrigatórias.

CHECK: Valida valores contra condições específicas; implementa regras de negócio.

DEFAULT: Define valores padrão para colunas.

Gerenciamento: Nomeie restrições explicitamente; podem ser adicionadas, removidas,


habilitadas/desabilitadas.

14.10 Exercícios do Capítulo 14

1. Criação de Tabelas com Restrições:

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 um produto com preço negativo. O que acontece?

Tente inserir uma venda para um cliente que não existe. O que acontece?

Tente inserir dois clientes com o mesmo email. 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.

5. Desafio - Integridade Referencial:

Configure uma FOREIGN KEY com ON DELETE CASCADE entre Clientes e Vendas .

Insira um cliente e algumas vendas para ele.

Exclua o cliente e observe o que acontece com as vendas.

Discuta as implicações de usar CASCADE vs. RESTRICT em diferentes cenários de negócio.

Referências

[1] Oracle. Database Concepts - Data Integrity. Disponível em:


[Link]

Capítulo 15: Funções de Janela (Window Functions)

15.1 Introdução às Funções de Janela

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

Com GROUP BY , você agrupa linhas e perde o detalhamento individual:

-- Com GROUP BY: mostra apenas o total por cliente


SELECT ClienteID, SUM(Quantidade * PrecoUnitario) AS TotalVendas
FROM Vendas
GROUP BY ClienteID;

Com funções de janela, você mantém o detalhamento e adiciona informações agregadas:

-- Com Window Function: mostra cada venda E o total por cliente


SELECT
VendaID,
ClienteID,
Quantidade * PrecoUnitario AS ValorVenda,
SUM(Quantidade * PrecoUnitario) OVER (PARTITION BY ClienteID) AS TotalVendasCliente
FROM Vendas;

15.2 Sintaxe Básica das Funções de Janela

A sintaxe geral de uma função de janela é:

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.

ORDER BY : (Opcional) Define a ordem das linhas dentro de cada partição.

ROWS/RANGE : (Opcional) Define o frame da janela - quais linhas específicas incluir no cálculo.

15.3 Funções de Classificação (Ranking Functions)

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.

-- Dividir produtos em 4 quartis baseados no preço


SELECT
ProdutoID,
NomeProduto,
Preco,
NTILE(4) OVER (ORDER BY Preco) AS Quartil
FROM Produtos;

15.3.5 Exemplo Comparativo das Funções de Ranking

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;

15.4 Funções Analíticas

As funções analíticas permitem acessar dados de outras linhas em relação à linha atual, sem usar JOINs.

15.4.1 LAG() e LEAD()

LAG(coluna, offset) : Acessa o valor de uma coluna em uma linha anterior.


LEAD(coluna, offset) : Acessa o valor de uma coluna em uma linha posterior.

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;

15.4.2 FIRST_VALUE() e LAST_VALUE()

FIRST_VALUE(coluna) : Retorna o primeiro valor na janela ordenada.

LAST_VALUE(coluna) : Retorna o último valor na janela ordenada.

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.

15.5 PARTITION BY: Dividindo Dados em Grupos

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.

15.5.1 Exemplo com PARTITION BY

-- Ranking de produtos por categoria


SELECT
ProdutoID,
NomeProduto,
Categoria,
Preco,
RANK() OVER (PARTITION BY Categoria ORDER BY Preco DESC) AS RankingNaCategoria
FROM Produtos
ORDER BY Categoria, RankingNaCategoria;
15.5.2 Múltiplas Funções de Janela com Diferentes Partições

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;

15.6 Funções de Agregação como Funções de Janela

Todas as funções de agregação tradicionais ( SUM , AVG , COUNT , MIN , MAX ) podem ser usadas como funções
de janela.

15.6.1 Totais Acumulados (Running Totals)

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;

15.6.2 Médias Móveis

-- Média móvel de 3 dias


SELECT
DataVenda,
SUM(Quantidade * PrecoUnitario) AS VendasDia,
AVG(SUM(Quantidade * PrecoUnitario)) OVER (
ORDER BY DataVenda
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS MediaMovel3Dias
FROM Vendas
GROUP BY DataVenda
ORDER BY DataVenda;

15.7 Frames de Janela (Window Frames)

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.

15.7.1 Especificações de Frame

ROWS : Define o frame baseado no número físico de linhas.

RANGE : Define o frame baseado no valor lógico das colunas de ordenação.


15.7.2 Limites de Frame

UNBOUNDED PRECEDING : Desde o início da partição.

n PRECEDING : n linhas antes da linha atual.

CURRENT ROW : A linha atual.

n FOLLOWING : n linhas após a linha atual.

UNBOUNDED FOLLOWING : Até o final da partição.

15.7.3 Exemplos de Frames

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;

15.8 Casos de Uso Práticos

15.8.1 Top N por Categoria

-- Top 3 produtos mais caros por categoria


SELECT *
FROM (
SELECT
ProdutoID,
NomeProduto,
Categoria,
Preco,
ROW_NUMBER() OVER (PARTITION BY Categoria ORDER BY Preco DESC) AS rn
FROM Produtos
) ranked
WHERE rn <= 3;

15.8.2 Percentual do Total

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;

15.9 Tópicos Chave do Capítulo 15

Funções de Janela: Permitem cálculos em conjuntos de linhas relacionadas mantendo o detalhamento


individual.

Sintaxe: funcao() OVER (PARTITION BY ... ORDER BY ... ROWS/RANGE ...)

Funções de Ranking: ROW_NUMBER() , RANK() , DENSE_RANK() , NTILE() .

Funções Analíticas: LAG() , LEAD() , FIRST_VALUE() , LAST_VALUE() .

PARTITION BY: Divide dados em grupos para aplicar a função separadamente.

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.

15.10 Exercícios do Capítulo 15

1. Funções de Ranking:

Classifique todos os produtos por preço (do mais caro para o mais barato) usando RANK() .

Divida os clientes em 5 grupos baseados no seu faturamento total usando NTILE() .

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:

Calcule o total acumulado de vendas por data (running total).


Calcule a média móvel de vendas dos últimos 7 dias.

5. Desafio:

Identifique os top 3 produtos mais vendidos (em quantidade) para cada cliente.

Calcule o crescimento percentual de vendas mês a mês.

Para cada produto, calcule que percentual das vendas totais ele representa.

Referências

[1] PostgreSQL. Window Functions. Disponível em: [Link]


[Link]

Capítulo 16: Expressões de Tabela Comuns (CTEs)

16.1 O que são CTEs e Por Que Usá-las?

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.

16.1.1 Vantagens das CTEs

Legibilidade Aprimorada: Permitem quebrar consultas complexas em blocos lógicos menores e


nomeados, facilitando a compreensão do fluxo de dados e da lógica de negócio.

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.

Simplificação de Subconsultas Aninhadas: Podem substituir subconsultas aninhadas complexas, que


podem ser difíceis de ler e depurar.

CTEs Recursivas: Permitem consultas hierárquicas, como a navegação em estruturas de árvore ou


grafos (veremos mais adiante).
16.2 Sintaxe Básica de CTEs

A sintaxe geral para uma CTE é:

WITH nome_da_cte (coluna1, coluna2, ...)


AS
(
SELECT ... -- Consulta que define a CTE
)
SELECT ... -- Consulta principal que usa a CTE
FROM nome_da_cte
WHERE ...;

WITH : Palavra-chave que inicia a definição da CTE.

nome_da_cte : O nome que você dá à sua Expressão de Tabela Comum.

(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.

AS : Palavra-chave que precede a consulta SELECT que define a CTE.

SELECT ... : A consulta que gera o conjunto de resultados para a CTE.

16.3 Exemplos Práticos de CTEs

16.3.1 Calculando Faturamento por Cliente

Vamos reescrever o exemplo de faturamento por cliente usando uma CTE para melhorar a legibilidade.

Sem CTE (usando subconsulta na cláusula FROM):

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.

16.3.2 Múltiplas CTEs

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 ...;

Exemplo: Encontrar os clientes que compraram os produtos mais caros.

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];

16.3.3 CTEs com Funções de Janela

CTEs são frequentemente usadas em conjunto com funções de janela para tornar a lógica mais
compreensível.

Exemplo: Encontrar o segundo produto mais caro de cada categoria.

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.

WITH RECURSIVE nome_da_cte (coluna1, coluna2, ...)


AS
(
-- Membro Âncora (consulta inicial)
SELECT ...
UNION ALL
-- Membro Recursivo (referencia a própria CTE)
SELECT ... FROM nome_da_cte ...
)
SELECT ... FROM nome_da_cte;

16.4.1 Exemplo de CTE Recursiva (Hierarquia de Funcionários)

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)
);

INSERT INTO Funcionarios (FuncionarioID, Nome, GerenteID) VALUES


(1, \'João\', NULL), -- CEO
(2, \'Maria\', 1), -- Gerente de João
(3, \'Pedro\', 1), -- Gerente de João
(4, \'Ana\', 2), -- Subordinada de Maria
(5, \'Carlos\', 2), -- Subordinado de Maria
(6, \'Julia\', 3); -- Subordinada de Pedro

Para listar todos os funcionários e seus respectivos gerentes na hierarquia:


WITH RECURSIVE HierarquiaFuncionarios AS (
-- Membro Âncora: Seleciona o CEO (funcionário sem gerente)
SELECT
FuncionarioID,
Nome,
GerenteID,
1 AS Nivel -- Nível na hierarquia
FROM Funcionarios
WHERE GerenteID IS NULL

UNION ALL

-- Membro Recursivo: Seleciona funcionários que reportam aos já encontrados


SELECT
[Link],
[Link],
[Link],
[Link] + 1 AS Nivel
FROM Funcionarios F
INNER JOIN HierarquiaFuncionarios HF ON [Link] = [Link]
)
SELECT
FuncionarioID,
Nome,
GerenteID,
Nivel
FROM HierarquiaFuncionarios
ORDER BY Nivel, Nome;

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 Limitações e Considerações de Performance

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 Views: Não são armazenadas permanentemente no banco de dados.

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).

16.5.2 Considerações de Performance

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 .

Vantagens: Melhoram legibilidade, modularidade, reusabilidade e simplificam subconsultas.

Sintaxe: WITH nome_cte AS (SELECT ...) SELECT ... FROM nome_cte;

Múltiplas CTEs: Podem ser definidas e referenciadas sequencialmente.

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.

16.7 Exercícios do Capítulo 16

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.

Em seguida, selecione todos os produtos da CTE ProdutosCaros e ordene-os por preço


decrescente.

2. Múltiplas CTEs:

Crie uma CTE VendasPorCliente que calcule o faturamento total para cada cliente.

Crie uma segunda CTE ClientesTopFaturamento que selecione os clientes da VendasPorCliente


cujo faturamento total é maior que a média de faturamento de todos os clientes.

Finalmente, selecione o nome e sobrenome dos clientes da ClientesTopFaturamento .

3. CTE com Função de Janela:

Crie uma CTE VendasRankeadas que rankeie as vendas de cada cliente por ValorTotalVenda (do
maior para o menor) usando ROW_NUMBER() .

Selecione a venda mais cara de cada cliente usando esta CTE.

4. CTE Recursiva (Hierarquia de Produtos):

Crie uma tabela Componentes com ComponenteID , NomeComponente , ComponentePaiID


(referenciando ComponenteID ).

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:

Crie uma CTE que calcule o total de vendas por mês.


Use uma segunda CTE para calcular a diferença percentual nas vendas de cada mês em relação ao
mês anterior.

Exiba o mês, as vendas do mês, as vendas do mês anterior e a diferença percentual.

Referências

[1] Microsoft. WITH common_table_expression (Transact-SQL). Disponível em:


[Link]
server-ver16 [2] PostgreSQL. WITH Queries (Common Table Expressions). Disponível em:
[Link]

Capítulo 17: Introdução ao PL/SQL

17.1 O que é PL/SQL?

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.

17.1.1 Por Que Usar PL/SQL?

Performance: O código PL/SQL é executado diretamente no servidor de banco de dados, reduzindo o


tráfego de rede e melhorando a performance, especialmente para operações que envolvem múltiplas
consultas SQL.

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.

Modularidade: Permite criar código reutilizável através de procedures, functions e packages.

Tratamento de Exceções: Oferece um sistema robusto de tratamento de erros e exceções.

Portabilidade: Código PL/SQL pode ser facilmente movido entre diferentes ambientes Oracle.
17.1.2 Diferenças entre SQL e PL/SQL

Aspecto SQL PL/SQL

Tipo de Linguagem Declarativa Procedural

Estruturas de Controle Não possui IF, LOOP, WHILE, FOR

Variáveis Não possui Suporta declaração e uso de variáveis

Tratamento de Exceções Limitado Sistema completo de exceções

Modularização Limitada Procedures, Functions, Packages

Execução Comando por comando Blocos de código

Reutilização Limitada Alta (através de subprogramas)

17.2 Arquitetura do 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.

17.2.1 Componentes da Arquitetura

PL/SQL Engine: Processa a lógica procedural (loops, condições, variáveis).

SQL Engine: Processa os comandos SQL dentro dos blocos PL/SQL.

Shared Pool: Área de memória onde o código PL/SQL compilado é armazenado para reutilização.

17.2.2 Processo de Execução

1. Compilação: O código PL/SQL é compilado em bytecode (p-code).

2. Armazenamento: O bytecode é armazenado no banco de dados.

3. Execução: O PL/SQL Engine executa o bytecode, enviando comandos SQL para o SQL Engine quando
necessário.

4. Cache: O código compilado é mantido em cache para execuções subsequentes.

17.3 Estrutura de um Bloco PL/SQL

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;
/

17.3.1 Seções do Bloco PL/SQL

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.

BEGIN: (Obrigatória) Contém os comandos executáveis - tanto SQL quanto PL/SQL.

EXCEPTION: (Opcional) Contém o código para tratamento de exceções que podem ocorrer na seção
BEGIN.

END: (Obrigatória) Marca o fim do bloco.

/ (Barra): Indica ao Oracle SQL*Plus ou SQL Developer que o bloco PL/SQL terminou e deve ser
executado.

17.3.2 Exemplo de Bloco PL/SQL Simples

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;
/

17.4 Tipos de Blocos PL/SQL

17.4.1 Blocos Anônimos

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

Functions são similares às procedures, mas retornam um valor.

CREATE OR REPLACE FUNCTION calcular_quadrado(p_numero NUMBER)


RETURN NUMBER
IS
BEGIN
RETURN p_numero * p_numero;
END;
/

17.4.4 Packages

Packages são coleções de procedures, functions, variáveis e outros elementos PL/SQL relacionados.

CREATE OR REPLACE PACKAGE pkg_matematica


IS
FUNCTION calcular_quadrado(p_numero NUMBER) RETURN NUMBER;
PROCEDURE mostrar_resultado(p_numero NUMBER);
END;
/

17.5 DBMS_OUTPUT: Exibindo Resultados

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.

17.5.1 Habilitando DBMS_OUTPUT

Antes de usar DBMS_OUTPUT , você precisa habilitá-lo:

SET SERVEROUTPUT ON

17.5.2 Principais Procedures do DBMS_OUTPUT

PUT_LINE(texto) : Exibe uma linha de texto.

PUT(texto) : Exibe texto sem quebra de linha.

NEW_LINE : Insere uma quebra de linha.


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;
/

17.6 Comentários em PL/SQL

PL/SQL suporta dois tipos de comentários:

17.6.1 Comentários de Linha Única

-- Este é um comentário de linha única


BEGIN
DBMS_OUTPUT.PUT_LINE(\'Olá\'); -- Comentário no final da linha
END;
/

17.6.2 Comentários de Múltiplas Linhas

/*
Este é um comentário
de múltiplas linhas
*/
BEGIN
/* Comentário em bloco */
DBMS_OUTPUT.PUT_LINE(\'Olá\');
END;
/

17.7 Convenções de Nomenclatura

Seguir convenções de nomenclatura consistentes torna o código mais legível e manutenível:

17.7.1 Convenções Recomendadas

Variáveis: Prefixo v_ (ex: v_nome , v_contador )

Parâmetros: Prefixo p_ (ex: p_cliente_id , p_valor )

Constantes: Prefixo c_ (ex: c_taxa_juros , c_max_tentativas )

Cursores: Prefixo cur_ (ex: cur_clientes , cur_vendas )

Exceções: Prefixo e_ (ex: e_cliente_nao_encontrado )

Tipos: Prefixo t_ (ex: t_lista_clientes )


17.7.2 Exemplo com Convenções

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;

-- Exceção definida pelo usuário


e_valor_invalido EXCEPTION;
BEGIN
v_cliente_id := 101;
v_valor_total := 1000;

IF v_valor_total <= 0 THEN


RAISE e_valor_invalido;
END IF;

v_valor_desconto := v_valor_total * c_taxa_desconto;

DBMS_OUTPUT.PUT_LINE(\'Cliente: \' || v_cliente_id);


DBMS_OUTPUT.PUT_LINE(\'Valor Total: \' || v_valor_total);
DBMS_OUTPUT.PUT_LINE(\'Desconto: \' || v_valor_desconto);
DBMS_OUTPUT.PUT_LINE(c_mensagem_sucesso);

EXCEPTION
WHEN e_valor_invalido THEN
DBMS_OUTPUT.PUT_LINE(\'Erro: Valor deve ser maior que zero\');
END;
/

17.8 Vantagens e Desvantagens do PL/SQL

17.8.1 Vantagens

Performance Superior: Execução no servidor reduz tráfego de rede.

Integração Nativa: Perfeita integração com SQL e recursos do Oracle.

Segurança: Controle de acesso granular através de procedures e functions.

Manutenibilidade: Código centralizado no banco de dados.

Reutilização: Procedures e functions podem ser chamadas por múltiplas aplicações.

Tratamento de Exceções: Sistema robusto de tratamento de erros.

17.8.2 Desvantagens

Específico do Oracle: Não é portável para outros SGBDs.

Debugging: Ferramentas de debugging podem ser limitadas comparadas a IDEs modernas.

Versionamento: Controle de versão de código no banco pode ser mais complexo.

Escalabilidade: Lógica de negócio no banco pode limitar a escalabilidade horizontal.


17.9 Tópicos Chave do Capítulo 17

PL/SQL: Extensão procedural do SQL específica do Oracle.

Estrutura de Bloco: DECLARE (opcional), BEGIN (obrigatório), EXCEPTION (opcional), END.

Tipos de Blocos: Anônimos, Procedures, Functions, Packages.

DBMS_OUTPUT: Package para exibir resultados ( PUT_LINE , PUT , NEW_LINE ).

Comentários: Linha única ( -- ) e múltiplas linhas ( /* */ ).

Convenções: Prefixos para variáveis ( v_ ), parâmetros ( p_ ), constantes ( c_ ).

Vantagens: Performance, integração, segurança, modularidade.

Desvantagens: Específico do Oracle, debugging limitado, versionamento complexo.

17.10 Exercícios do Capítulo 17

1. Primeiro Bloco PL/SQL:

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 .

2. Trabalhando com Variáveis:

Declare variáveis para armazenar o nome de um produto, seu preço e quantidade em estoque.

Calcule o valor total em estoque (preço × quantidade) e exiba todas as informações.

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).

Declare uma variável numérica e tente dividir um número por ela.

Se a variável for zero, trate a exceção de divisão por zero.

5. Desafio:

Crie um bloco que simule um sistema simples de cálculo de desconto:


Declare variáveis para valor da compra, percentual de desconto e valor final.

Se o valor da compra for maior que R$ 1000, aplique 10% de desconto.

Se for maior que R$ 500, aplique 5% de desconto.

Exiba o valor original, desconto aplicado e valor final.


Referências

[1] Oracle. PL/SQL Language Reference. Disponível em: [Link]


database/19/lnpls/[Link]

Capítulo 18: Blocos Anônimos PL/SQL

18.1 Aprofundando em Blocos Anônimos

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].

18.1.1 Estrutura Detalhada do Bloco Anônimo

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).

END (Obrigatória): Marca o fim do bloco PL/SQL.

/ (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.

18.2 Seção DECLARE: Declarando Elementos

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;
/

18.2.3 %TYPE: Referenciando Tipos de Colunas

%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;
/

18.2.4 %ROWTYPE: Referenciando Estruturas de Linhas

%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:

Executar comandos SQL (DML, DQL).

Atribuir valores a variáveis.

Usar estruturas de controle (IF, LOOP, etc.).

Chamar procedures e functions.

Exibir mensagens com DBMS_OUTPUT.PUT_LINE .

18.3.1 Atribuição de Valores

Use o operador := para atribuir valores a variáveis.

DECLARE
v_contador NUMBER;
BEGIN
v_contador := 10;
v_contador := v_contador + 1;
END;
/

18.3.2 Comandos SQL em PL/SQL

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 .

18.3.3 Operações DML em PL/SQL

Você pode executar INSERT , UPDATE e DELETE diretamente no bloco PL/SQL.


DECLARE
v_produto_id [Link]%TYPE := 5;
v_nome_produto [Link]%TYPE := \'Webcam HD\';
v_preco [Link]%TYPE := 120.00;
BEGIN
-- Inserir um novo produto
INSERT INTO Produtos (ProdutoID, NomeProduto, Preco)
VALUES (v_produto_id, v_nome_produto, v_preco);

-- Atualizar o estoque de um produto


UPDATE Produtos
SET Estoque = Estoque + 10
WHERE ProdutoID = 1;

-- Deletar um produto
DELETE FROM Produtos
WHERE ProdutoID = 5;

COMMIT; -- Confirmar as alterações


END;
/

18.4 Seção EXCEPTION: Tratamento de Erros

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].

18.4.1 Exceções Pré-Definidas

O PL/SQL possui várias exceções pré-definidas para erros comuns:

NO_DATA_FOUND : Nenhuma linha foi retornada por um SELECT INTO .

TOO_MANY_ROWS : Mais de uma linha foi retornada por um SELECT INTO .

DUP_VAL_ON_INDEX : Tentativa de inserir um valor duplicado em uma coluna com restrição UNIQUE .

ZERO_DIVIDE : Tentativa de dividir um número por zero.

VALUE_ERROR : Erro de conversão de dados ou truncamento.

DECLARE
v_nome_cliente [Link]%TYPE;
BEGIN
SELECT Nome INTO v_nome_cliente
FROM Clientes
WHERE ClienteID = 999; -- Cliente que não existe

DBMS_OUTPUT.PUT_LINE(\'Nome: \' || v_nome_cliente);


EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE(\'Erro: Cliente não encontrado.\');
WHEN OTHERS THEN -- Captura qualquer outra exceção
DBMS_OUTPUT.PUT_LINE(\'Erro inesperado: \' || SQLERRM);
END;
/

SQLCODE : Retorna o código de erro numérico do Oracle.

SQLERRM : Retorna a mensagem de erro associada ao SQLCODE .


18.4.2 Exceções Definidas pelo Usuário

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;

IF v_estoque_atual < v_quantidade_desejada THEN


RAISE e_estoque_insuficiente; -- Lança a exceção
END IF;

DBMS_OUTPUT.PUT_LINE(\'Estoque suficiente. Processando pedido...\');


EXCEPTION
WHEN e_estoque_insuficiente THEN
DBMS_OUTPUT.PUT_LINE(\'Erro: Estoque insuficiente para o produto.\');
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE(\'Erro: Produto não encontrado.\');
END;
/

18.5 Blocos Aninhados

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;

-- DBMS_OUTPUT.PUT_LINE(v_variavel_interna); -- Erro: v_variavel_interna está fora de escopo


EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(\'Erro no bloco externo: \' || SQLERRM);
END;
/

18.6 Transações em Blocos Anônimos

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]\');

-- Se ocorrer um erro aqui, o INSERT acima será desfeito automaticamente


-- ou você pode fazer um ROLLBACK explícito
-- RAISE_APPLICATION_ERROR(-20001, \'Erro forçado para teste de rollback\');

COMMIT; -- Confirma a inserção


DBMS_OUTPUT.PUT_LINE(\'Cliente inserido e confirmado.\');
EXCEPTION
WHEN OTHERS THEN
ROLLBACK; -- Desfaz todas as alterações desde o BEGIN
DBMS_OUTPUT.PUT_LINE(\'Erro: \' || SQLERRM || \'. Transação desfeita.\');
END;
/

18.7 Tópicos Chave do Capítulo 18

Blocos Anônimos: Unidades de código PL/SQL sem nome, executadas ad-hoc.

Estrutura: DECLARE (opcional), BEGIN (obrigatório), EXCEPTION (opcional), END .

DECLARE : Seção para declarar variáveis, constantes, cursores, tipos.

%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.

BEGIN : Seção executável para lógica de negócio e comandos SQL.

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).

SQLCODE e SQLERRM : Funções para obter informações sobre o erro.

Blocos Aninhados: Permitem controle de escopo e tratamento de exceções granular.

Transações: Blocos anônimos operam dentro de transações; COMMIT e ROLLBACK controlam a


persistência das alterações.

18.8 Exercícios do Capítulo 18

1. Bloco Anônimo Simples:

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 .

2. Usando %TYPE e %ROWTYPE:

Declare uma variável v_nome_produto usando [Link]%TYPE .

Declare uma variável r_cliente usando Clientes%ROWTYPE .

Selecione o NomeProduto do ProdutoID 1 para v_nome_produto .

Selecione a linha completa do ClienteID 101 para r_cliente .

Exiba o nome do produto e o email do cliente usando as variáveis.

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 .

4. Bloco Aninhado com Transação:

Crie um bloco externo que insira um novo produto.

Dentro deste bloco, crie um bloco aninhado que tente atualizar o estoque de um produto
inexistente.

Trate a exceção NO_DATA_FOUND no bloco aninhado.

Garanta que, se o erro ocorrer no bloco aninhado, a inserção do produto no bloco externo seja
desfeita.

5. Desafio:

Crie um bloco anônimo que simule a compra de um produto:


Declare variáveis para ProdutoID , QuantidadeDesejada .

Verifique se o produto existe e se há estoque suficiente.

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).

Use COMMIT ou ROLLBACK conforme a situação.

Referências

[1] Oracle. PL/SQL Language Reference - Anonymous Blocks. Disponível em:


[Link]
514A-413B-B03A-421213032410 [2] Oracle. PL/SQL Language Reference - Exception Handling. Disponível em:
[Link]

Capítulo 19: Variáveis e Tipos de Dados em PL/SQL

19.1 Sistema de Tipos do PL/SQL

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

Os tipos de dados em PL/SQL podem ser categorizados em:

Tipos Escalares: Armazenam um único valor (NUMBER, VARCHAR2, DATE, BOOLEAN).

Tipos Compostos: Armazenam múltiplos valores (RECORD, TABLE, VARRAY).

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).

19.2 Tipos de Dados Escalares

19.2.1 Tipos Numéricos

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;

DBMS_OUTPUT.PUT_LINE(\'Inteiro: \' || v_inteiro);


DBMS_OUTPUT.PUT_LINE(\'Decimal: \' || v_decimal);
DBMS_OUTPUT.PUT_LINE(\'Percentual: \' || v_percentual);
END;
/

Subtipos Numéricos: * INTEGER ou INT : Equivalente a NUMBER(38) * SMALLINT : Equivalente a NUMBER(38)


* DECIMAL(p,s) ou DEC(p,s) : Equivalente a NUMBER(p,s) * NUMERIC(p,s) : Equivalente a NUMBER(p,s) *
FLOAT(p) : Número de ponto flutuante * REAL : Equivalente a FLOAT(63) * DOUBLE PRECISION : Equivalente a
FLOAT(126)

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;
/

19.2.2 Tipos de Caractere

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\';

DBMS_OUTPUT.PUT_LINE(\'Nome: \' || v_nome);


DBMS_OUTPUT.PUT_LINE(\'Descrição: \' || v_descricao);
END;
/

CHAR: Tipo de string de comprimento fixo, preenchido com espaços.

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

DBMS_OUTPUT.PUT_LINE(\'Código: [\' || v_codigo || \']\');


DBMS_OUTPUT.PUT_LINE(\'Status: [\' || v_status || \']\');
END;
/

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 := \'€\';

DBMS_OUTPUT.PUT_LINE(\'Nome: \' || v_nome_unicode);


DBMS_OUTPUT.PUT_LINE(\'Símbolo: \' || v_simbolo);
END;
/

19.2.3 Tipos de Data e Hora

DATE: Armazena data e hora com precisão de segundos.

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\');

DBMS_OUTPUT.PUT_LINE(\'Data atual: \' || TO_CHAR(v_data_atual, \'DD/MM/YYYY HH24:MI:SS\'));


DBMS_OUTPUT.PUT_LINE(\'Nascimento: \' || TO_CHAR(v_data_nascimento, \'DD/MM/YYYY\'));
DBMS_OUTPUT.PUT_LINE(\'Data específica: \' || TO_CHAR(v_data_especifica, \'DD/MM/YYYY
HH24:MI:SS\'));
END;
/

TIMESTAMP: Armazena data e hora com precisão de frações de segundo.


DECLARE
v_timestamp TIMESTAMP;
v_timestamp_tz TIMESTAMP WITH TIME ZONE;
v_timestamp_ltz TIMESTAMP WITH LOCAL TIME ZONE;
BEGIN
v_timestamp := SYSTIMESTAMP;
v_timestamp_tz := SYSTIMESTAMP;
v_timestamp_ltz := SYSTIMESTAMP;

DBMS_OUTPUT.PUT_LINE(\'Timestamp: \' || v_timestamp);


DBMS_OUTPUT.PUT_LINE(\'Timestamp TZ: \' || v_timestamp_tz);
DBMS_OUTPUT.PUT_LINE(\'Timestamp LTZ: \' || v_timestamp_ltz);
END;
/

INTERVAL: Armazena períodos de tempo.

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

v_data_futura := SYSDATE + v_intervalo_dia;

DBMS_OUTPUT.PUT_LINE(\'Intervalo dia: \' || v_intervalo_dia);


DBMS_OUTPUT.PUT_LINE(\'Intervalo ano: \' || v_intervalo_ano);
DBMS_OUTPUT.PUT_LINE(\'Data futura: \' || TO_CHAR(v_data_futura, \'DD/MM/YYYY\'));
END;
/

19.2.4 Tipo Booleano

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;

-- Não é possível exibir BOOLEAN diretamente com DBMS_OUTPUT


-- Deve-se converter para string
DBMS_OUTPUT.PUT_LINE(\'Aprovado: \' ||
CASE WHEN v_aprovado THEN \'SIM\' ELSE \'NÃO\' END);
END;
/
19.3 Declaração e Inicialização de Variáveis

19.3.1 Sintaxe de Declaração

nome_variavel [CONSTANT] tipo_dado [NOT NULL] [:= valor_inicial | DEFAULT valor_inicial];

19.3.2 Exemplos de Declaração

DECLARE
-- Declaração simples
v_nome VARCHAR2(100);

-- Declaração com inicialização


v_contador NUMBER := 0;
v_taxa NUMBER DEFAULT 0.05;

-- Constante
c_PI CONSTANT NUMBER := 3.14159;
c_EMPRESA CONSTANT VARCHAR2(50) := \'Minha Empresa Ltda\';

-- Variável NOT NULL (deve ser inicializada)


v_codigo VARCHAR2(10) NOT NULL := \'INIT\';

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

-- Atribuindo valores a um registro


r_produto.ProdutoID := 1;
r_produto.NomeProduto := \'Smartphone\';
r_produto.Preco := 899.99;

DBMS_OUTPUT.PUT_LINE(\'Nome: \' || v_nome);


DBMS_OUTPUT.PUT_LINE(\'Contador: \' || v_contador);
DBMS_OUTPUT.PUT_LINE(\'PI: \' || c_PI);
DBMS_OUTPUT.PUT_LINE(\'Produto: \' || r_produto.NomeProduto || \' - R$ \' || r_produto.Preco);
END;
/

19.4 Conversões de Tipo

19.4.1 Conversões Implícitas

O PL/SQL realiza algumas conversões automáticas entre tipos compatíveis:


DECLARE
v_numero NUMBER;
v_texto VARCHAR2(20);
v_data DATE;
BEGIN
-- Conversões implícitas
v_numero := \'123.45\'; -- String para NUMBER
v_texto := 456.78; -- NUMBER para VARCHAR2
v_data := \'01/01/2025\'; -- String para DATE (formato padrão)

DBMS_OUTPUT.PUT_LINE(\'Número: \' || v_numero);


DBMS_OUTPUT.PUT_LINE(\'Texto: \' || v_texto);
DBMS_OUTPUT.PUT_LINE(\'Data: \' || v_data);
END;
/

19.4.2 Conversões Explícitas

Para maior controle e clareza, use funções de conversão explícitas:

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\');

DBMS_OUTPUT.PUT_LINE(\'Número: \' || v_numero);


DBMS_OUTPUT.PUT_LINE(\'Texto formatado: \' || v_texto);
DBMS_OUTPUT.PUT_LINE(\'Data: \' || TO_CHAR(v_data, \'DD/MM/YYYY\'));
DBMS_OUTPUT.PUT_LINE(\'Timestamp: \' || v_timestamp);
END;
/

19.5 Escopo e Visibilidade de Variáveis

19.5.1 Escopo Local

Variáveis declaradas em um bloco são visíveis apenas dentro desse bloco:

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;

-- DBMS_OUTPUT.PUT_LINE(v_local); -- ERRO: v_local fora de escopo


END;
/
19.5.2 Sombreamento de Variáveis

Variáveis em blocos internos podem \"sombrear\" variáveis de blocos externos:

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;

DBMS_OUTPUT.PUT_LINE(\'Após o bloco interno: \' || v_nome);


END;
/

19.6 Tipos de Dados Compostos

19.6.1 RECORD

Um RECORD é um tipo composto que agrupa campos relacionados:

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\';

v_endereco_empresa.logradouro := \'Av. Paulista\';


v_endereco_empresa.numero := 1000;
v_endereco_empresa.cidade := \'São Paulo\';
v_endereco_empresa.cep := \'01310-100\';

DBMS_OUTPUT.PUT_LINE(\'Cliente: \' || v_endereco_cliente.logradouro ||


\', \' || v_endereco_cliente.numero ||
\' - \' || v_endereco_cliente.cidade);
END;
/

19.7 Tópicos Chave do Capítulo 19

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 .

Tipos de Caractere: VARCHAR2 (variável), CHAR (fixo), NVARCHAR2 / NCHAR (Unicode).

Tipos de Data: DATE , TIMESTAMP , INTERVAL para diferentes necessidades temporais.


BOOLEAN: Tipo específico do PL/SQL para valores lógicos (TRUE/FALSE/NULL).

Declaração: Sintaxe com tipo, inicialização opcional, CONSTANT , NOT NULL .

%TYPE e %ROWTYPE: Referenciam tipos de colunas e estruturas de tabelas.

Conversões: Implícitas (automáticas) e explícitas (TO_NUMBER, TO_CHAR, TO_DATE).

Escopo: Variáveis são locais ao bloco; blocos internos podem sombrear variáveis externas.

RECORD: Tipo composto para agrupar campos relacionados.

19.8 Exercícios do Capítulo 19

1. Declaração de Variáveis:

Declare variáveis de diferentes tipos: NUMBER , VARCHAR2 , DATE , BOOLEAN .

Inicialize algumas na declaração e outras na seção BEGIN .

Exiba os valores usando DBMS_OUTPUT.PUT_LINE .

2. Usando %TYPE e %ROWTYPE:

Declare uma variável usando [Link]%TYPE .

Declare uma variável usando [Link]%TYPE .

Atribua valores e exiba-os.

3. Conversões de Tipo:

Converta uma string \'1234.56\' para NUMBER .

Converta um número 789.12 para VARCHAR2 com formatação.

Converta uma string \'25/12/2025\' para DATE .

4. Escopo de Variáveis:

Crie um bloco externo com uma variável v_contador .

Crie um bloco interno que declare outra variável v_contador (sombreamento).

Demonstre como cada bloco acessa sua própria versão da variável.

5. Desafio - RECORD:

Defina um tipo RECORD para representar um produto com campos: id , nome , preco , categoria .

Declare uma variável deste tipo e atribua valores.

Crie uma função que calcule o preço com desconto baseado na categoria.

Referências

[1] Oracle. PL/SQL Language Reference - Data Types. Disponível em:


[Link]
Capítulo 20: Estruturas de Controle em PL/SQL

20.1 Introdução às Estruturas de Controle

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].

20.1.1 Tipos de Estruturas de Controle

Estruturas Condicionais: Executam diferentes blocos de código baseados em condições lógicas.


IF-THEN-ELSIF-ELSE

CASE (simples e pesquisado)

Estruturas de Repetição (Loops): Repetem blocos de código até que uma condição seja atendida.
LOOP básico

WHILE LOOP

FOR LOOP (numérico e cursor)

Estruturas de Controle de Fluxo: Alteram o comportamento dos loops.


EXIT e EXIT WHEN

CONTINUE e CONTINUE WHEN

20.2 Estruturas Condicionais

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;

DBMS_OUTPUT.PUT_LINE(\'Idade: \' || v_idade || \' - Categoria: \' || v_categoria);


END;
/

Exemplo com Consulta ao Banco:

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;

-- Classificar cliente baseado no total de compras


IF v_total_compras = 0 THEN
v_status_cliente := \'Novo Cliente\';
ELSIF v_total_compras < 1000 THEN
v_status_cliente := \'Cliente Bronze\';
ELSIF v_total_compras < 5000 THEN
v_status_cliente := \'Cliente Prata\';
ELSIF v_total_compras < 10000 THEN
v_status_cliente := \'Cliente Ouro\';
ELSE
v_status_cliente := \'Cliente Diamante\';
END IF;

DBMS_OUTPUT.PUT_LINE(\'Cliente ID: \' || v_cliente_id);


DBMS_OUTPUT.PUT_LINE(\'Total de Compras: R$ \' || v_total_compras);
DBMS_OUTPUT.PUT_LINE(\'Status: \' || v_status_cliente);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE(\'Cliente não encontrado\');
END;
/

20.2.2 IF Aninhado

Você pode aninhar estruturas IF para criar lógicas mais complexas:


DECLARE
v_produto_id NUMBER := 1;
v_estoque NUMBER;
v_preco NUMBER;
v_acao VARCHAR2(100);
BEGIN
SELECT Estoque, Preco
INTO v_estoque, v_preco
FROM Produtos
WHERE ProdutoID = v_produto_id;

IF v_estoque > 0 THEN


IF v_preco > 1000 THEN
v_acao := \'Produto disponível - Preço premium\';
ELSE
v_acao := \'Produto disponível - Preço normal\';
END IF;
ELSE
IF v_preco > 1000 THEN
v_acao := \'Produto esgotado - Considerar reposição prioritária\';
ELSE
v_acao := \'Produto esgotado - Reposição normal\';
END IF;
END IF;

DBMS_OUTPUT.PUT_LINE(\'Produto ID: \' || v_produto_id);


DBMS_OUTPUT.PUT_LINE(\'Estoque: \' || v_estoque);
DBMS_OUTPUT.PUT_LINE(\'Preço: R$ \' || v_preco);
DBMS_OUTPUT.PUT_LINE(\'Ação: \' || v_acao);
END;
/

20.2.3 CASE Statement

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;

DBMS_OUTPUT.PUT_LINE(\'Hoje é: \' || v_nome_dia);


END;
/

CASE Pesquisado (Searched CASE):


DECLARE
v_nota NUMBER := 85;
v_conceito CHAR(1);
BEGIN
CASE
WHEN v_nota >= 90 THEN v_conceito := \'A\';
WHEN v_nota >= 80 THEN v_conceito := \'B\';
WHEN v_nota >= 70 THEN v_conceito := \'C\';
WHEN v_nota >= 60 THEN v_conceito := \'D\';
ELSE v_conceito := \'F\';
END CASE;

DBMS_OUTPUT.PUT_LINE(\'Nota: \' || v_nota || \' - Conceito: \' || v_conceito);


END;
/

20.3 Estruturas de Repetição (Loops)

20.3.1 LOOP Básico

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;

EXIT WHEN v_contador > 5; -- Sai do loop quando contador > 5


END LOOP;

DBMS_OUTPUT.PUT_LINE(\'Loop finalizado\');
END;
/

20.3.2 WHILE LOOP

O WHILE LOOP executa enquanto uma condição for verdadeira. A condição é avaliada antes de cada iteração.

Sintaxe:

WHILE condicao LOOP


-- comandos a serem repetidos
END LOOP;

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;

DBMS_OUTPUT.PUT_LINE(\'Soma final de 1 a 10: \' || v_soma);


END;
/

20.3.3 FOR LOOP Numérico

O FOR LOOP numérico é usado quando você sabe exatamente quantas iterações precisa fazer.

Sintaxe:

FOR contador IN [REVERSE] limite_inferior..limite_superior LOOP


-- comandos a serem repetidos
END LOOP;

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;
/

Exemplo com Cálculo:

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;
/

20.3.4 Loops Aninhados

Você pode aninhar loops para criar estruturas mais complexas:


BEGIN
DBMS_OUTPUT.PUT_LINE(\'Tabela de multiplicação:\');
FOR i IN 1..3 LOOP
FOR j IN 1..3 LOOP
DBMS_OUTPUT.PUT_LINE(i || \' x \' || j || \' = \' || (i * j));
END LOOP;
DBMS_OUTPUT.PUT_LINE(\'---\');
END LOOP;
END;
/

20.4 Controle de Fluxo em Loops

20.4.1 EXIT e EXIT WHEN

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;

DBMS_OUTPUT.PUT_LINE(\'Número: \' || v_numero);


v_numero := v_numero + 1;

EXIT WHEN v_numero > 5;


END LOOP;
END;
/

20.4.2 CONTINUE e CONTINUE WHEN

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;
/

20.4.3 Labels em Loops

Labels permitem controlar loops aninhados de forma mais precisa:


BEGIN
<<loop_externo>>
FOR i IN 1..3 LOOP
DBMS_OUTPUT.PUT_LINE(\'Loop externo: \' || i);

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

DBMS_OUTPUT.PUT_LINE(\'Fim dos loops\');


END;
/

20.5 Exemplos Práticos Avançados

20.5.1 Processamento de Dados com Loop

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;

-- Aplicar desconto progressivo


CASE
WHEN [Link] > 2000 THEN
v_novo_preco := [Link] * 0.9; -- 10% desconto
WHEN [Link] > 1500 THEN
v_novo_preco := [Link] * 0.95; -- 5% desconto
ELSE
v_novo_preco := [Link] * 0.98; -- 2% desconto
END CASE;

DBMS_OUTPUT.PUT_LINE(\'Produto \' || v_contador || \': \' || [Link]);


DBMS_OUTPUT.PUT_LINE(\' Preço original: R$ \' || [Link]);
DBMS_OUTPUT.PUT_LINE(\' Novo preço: R$ \' || ROUND(v_novo_preco, 2));
DBMS_OUTPUT.PUT_LINE(\' Economia: R$ \' || ROUND([Link] - v_novo_preco, 2));
DBMS_OUTPUT.PUT_LINE(\'---\');

-- Atualizar o preço no banco (descomente para executar)


-- UPDATE Produtos SET Preco = v_novo_preco WHERE ProdutoID = [Link];
END LOOP;

DBMS_OUTPUT.PUT_LINE(\'Total de produtos processados: \' || v_contador);


-- COMMIT; -- Descomente para confirmar as alterações
END;
/
20.5.2 Validação e Processamento Condicional

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;

IF [Link] <= 0 THEN


DBMS_OUTPUT.PUT_LINE(\'Venda \' || [Link] || \': Preço inválido\');
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]);

-- Processar baseado no valor da venda


DECLARE
v_valor_venda NUMBER := [Link] * [Link];
BEGIN
CASE
WHEN v_valor_venda > 5000 THEN
DBMS_OUTPUT.PUT_LINE(\'Venda \' || [Link] || \': ALTA - R$ \' ||
v_valor_venda);
WHEN v_valor_venda > 1000 THEN
DBMS_OUTPUT.PUT_LINE(\'Venda \' || [Link] || \': MÉDIA - R$ \' ||
v_valor_venda);
ELSE
DBMS_OUTPUT.PUT_LINE(\'Venda \' || [Link] || \': BAIXA - R$ \' ||
v_valor_venda);
END CASE;
END;
END LOOP;

DBMS_OUTPUT.PUT_LINE(\'=== RESUMO ===\');


DBMS_OUTPUT.PUT_LINE(\'Vendas válidas: \' || v_vendas_validas);
DBMS_OUTPUT.PUT_LINE(\'Vendas inválidas: \' || v_vendas_invalidas);
DBMS_OUTPUT.PUT_LINE(\'Total faturado: R$ \' || v_total_vendas);
END;
/

20.6 Tópicos Chave do Capítulo 20

Estruturas Condicionais: IF-THEN-ELSIF-ELSE e CASE (simples e pesquisado).

Estruturas de Repetição: LOOP básico, WHILE LOOP , FOR LOOP numérico.

Controle de Fluxo: EXIT , EXIT WHEN , CONTINUE , CONTINUE WHEN .

Labels: Permitem controle preciso de loops aninhados.

Aninhamento: Estruturas podem ser aninhadas para lógicas complexas.

Integração com SQL: Loops podem processar resultados de consultas.

Validação: Estruturas condicionais são essenciais para validação de dados.

Performance: Escolha a estrutura apropriada para cada situação.


20.7 Exercícios do Capítulo 20

1. IF-THEN-ELSE Básico:
Crie um bloco que classifique um produto baseado no seu preço: \

Capítulo 21: Cursores em PL/SQL

21.1 O que são Cursores?

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.

21.1.1 Por Que Usar Cursores?

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.

21.2 Tipos de Cursores

Existem dois tipos principais de cursores em PL/SQL:

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.

21.3.1 Atributos de Cursores Implícitos

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%NOTFOUND : O oposto de SQL%FOUND . Retorna TRUE se nenhuma linha foi afetada/retornada.

SQL%ROWCOUNT : Retorna o número de linhas afetadas pela última instrução DML.

SQL%ISOPEN : Sempre retorna FALSE para cursores implícitos, pois o Oracle os fecha automaticamente
após a execução.

21.3.2 Exemplo de Uso de Atributos de Cursor Implícito

BEGIN
-- Tentar atualizar um produto
UPDATE Produtos
SET Preco = Preco * 1.10 -- Aumento de 10%
WHERE Categoria = \'Eletrônicos\';

-- Verificar se alguma linha foi atualizada


IF SQL%FOUND THEN
DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT || \' produtos foram atualizados.\');
ELSE
DBMS_OUTPUT.PUT_LINE(\'Nenhum produto na categoria Eletrônicos foi encontrado para
atualização.\');
END IF;

COMMIT;
END;
/

21.4 Cursores Explícitos

Cursores explícitos são usados para processar consultas que retornam múltiplas linhas. Eles oferecem
controle total sobre o processo.

21.4.1 Ciclo de Vida de um Cursor Explícito

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;

-- Sair do loop quando não houver mais linhas


EXIT WHEN cur_clientes%NOTFOUND;

-- Processar a linha recuperada


DBMS_OUTPUT.PUT_LINE(\'Cliente: \' || v_cliente_id || \' - \' || v_nome || \' \' ||
v_sobrenome);
END LOOP;

-- 4. CLOSE: Fecha o cursor


CLOSE cur_clientes;
END;
/

21.4.3 Atributos de Cursores Explícitos

cursor_name%FOUND : Retorna TRUE se o último FETCH retornou uma linha.

cursor_name%NOTFOUND : Retorna TRUE se o último FETCH não retornou uma linha (fim do conjunto de
resultados).

cursor_name%ROWCOUNT : Retorna o número de linhas recuperadas até o momento.

cursor_name%ISOPEN : Retorna TRUE se o cursor estiver aberto.

21.5 Cursor FOR LOOP

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

FOR record_name IN cursor_name LOOP


-- Processar record_name.coluna
END LOOP;

record_name : Um registro que é declarado implicitamente e armazena a linha atual do cursor.

cursor_name : O nome do cursor explícito ou uma subconsulta SELECT .


21.5.2 Exemplo com Cursor Explícito

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;
/

21.5.3 Exemplo com Subconsulta

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;
/

21.6 Cursores com Parâmetros

Você pode criar cursores que aceitam parâmetros, tornando-os mais flexíveis e reutilizáveis.

21.6.1 Sintaxe

CURSOR cursor_name (parametro1 tipo_dado, parametro2 tipo_dado) IS


SELECT ...
WHERE coluna1 = parametro1 AND coluna2 > parametro2;
21.6.2 Exemplo

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;

DBMS_OUTPUT.PUT_LINE(\'--- Vendas do Cliente 102 em 2024 ---\');


FOR venda_rec IN cur_vendas_cliente(102, 2024) LOOP
DBMS_OUTPUT.PUT_LINE(\'Venda: \' || venda_rec.VendaID || \' - Valor: R$ \' ||
venda_rec.ValorTotal);
END LOOP;
END;
/

21.7 Cláusula FOR UPDATE

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];

OF coluna1, coluna2 : (Opcional) Bloqueia apenas as colunas especificadas.

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.

21.7.2 Cláusula WHERE CURRENT OF

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;
/

21.8 REF CURSOR (Cursores de Referência)

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).

21.8.1 Tipos de REF CURSOR

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)

CREATE OR REPLACE PROCEDURE obter_clientes_por_estado (


p_estado IN VARCHAR2,
p_cursor_clientes OUT SYS_REFCURSOR
)
IS
BEGIN
OPEN p_cursor_clientes FOR
SELECT ClienteID, Nome, Email
FROM Clientes
WHERE Estado = p_estado;
END;
/

-- Bloco para testar a procedure


DECLARE
v_cursor SYS_REFCURSOR;
v_cliente_id [Link]%TYPE;
v_nome [Link]%TYPE;
v_email [Link]%TYPE;
BEGIN
-- Chamar a procedure para obter o cursor
obter_clientes_por_estado(\'RJ\', v_cursor);

-- Processar o cursor retornado


LOOP
FETCH v_cursor INTO v_cliente_id, v_nome, v_email;
EXIT WHEN v_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(\'ID: \' || v_cliente_id || \' - Nome: \' || v_nome || \' - Email: \' ||
v_email);
END LOOP;

CLOSE v_cursor;
END;
/

21.9 Tópicos Chave do Capítulo 21

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.

Cursores com Parâmetros: Tornam os cursores reutilizáveis e flexíveis.

FOR UPDATE : Bloqueia linhas para evitar conflitos de atualização.

WHERE CURRENT OF : Atualiza/exclui a linha atual do cursor.

REF CURSOR : Tipo de dado que armazena um ponteiro para um cursor, usado para passar conjuntos de
resultados dinamicamente.

21.10 Exercícios do Capítulo 21

1. Cursor Explícito Básico:

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.

2. Cursor FOR LOOP:

Reescreva o exercício anterior usando um Cursor FOR LOOP .

3. Cursor com Parâmetros:

Crie um cursor com parâmetros que aceite uma Categoria e um PrecoMaximo .

Use o cursor para listar todos os produtos de uma categoria específica que custam menos que o
preço máximo.

4. Cursor com FOR UPDATE:

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 .

5. Desafio - REF CURSOR:

Crie uma procedure que aceite um ProdutoID como entrada.

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

[1] Oracle. PL/SQL Language Reference - Cursors. Disponível em:


[Link]

Capítulo 22: Procedures e Functions em PL/SQL

22.1 Introdução aos Subprogramas PL/SQL

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].

22.1.1 Vantagens dos Subprogramas

Modularidade: Dividem código complexo em unidades menores e gerenciáveis.

Reutilização: Podem ser chamados por múltiplas aplicações e outros subprogramas.

Manutenibilidade: Mudanças na lógica de negócio podem ser feitas em um local central.


Performance: São compilados e armazenados no banco, resultando em execução mais rápida.

Segurança: Podem ser usados para controlar acesso aos dados através de interfaces bem definidas.

Encapsulamento: Escondem a complexidade da implementação dos usuários.

22.1.2 Diferenças entre Procedures e Functions

Aspecto Procedure Function

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

Propósito Executar ações (DML, lógica de negócio) Calcular e retornar valores

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.

22.2.1 Sintaxe de Criação

CREATE [OR REPLACE] PROCEDURE nome_procedure


[(parametro1 [IN | OUT | IN OUT] tipo_dado,
parametro2 [IN | OUT | IN OUT] tipo_dado,
...)]
IS | AS
-- Seção de declaração (opcional)
declaracoes_locais
BEGIN
-- Seção executável
comandos_executaveis
[EXCEPTION
-- Seção de tratamento de exceções (opcional)
tratamento_excecoes]
END [nome_procedure];
/

22.2.2 Tipos de Parâmetros

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

CREATE OR REPLACE PROCEDURE exibir_mensagem (p_texto IN VARCHAR2)


IS
BEGIN
DBMS_OUTPUT.PUT_LINE(\'Mensagem: \' || p_texto);
END exibir_mensagem;
/

-- Chamando a procedure
BEGIN
exibir_mensagem(\'Olá, mundo!\');
END;
/

22.2.4 Procedure com Parâmetros IN, OUT e IN OUT

CREATE OR REPLACE PROCEDURE calcular_estatisticas_cliente (


p_cliente_id IN NUMBER,
p_total_compras OUT NUMBER,
p_numero_compras OUT NUMBER,
p_ticket_medio OUT NUMBER
)
IS
BEGIN
SELECT
NVL(SUM(Quantidade * PrecoUnitario), 0),
COUNT(*),
NVL(AVG(Quantidade * PrecoUnitario), 0)
INTO
p_total_compras,
p_numero_compras,
p_ticket_medio
FROM Vendas
WHERE ClienteID = p_cliente_id;

DBMS_OUTPUT.PUT_LINE(\'Estatísticas calculadas para cliente \' || p_cliente_id);


EXCEPTION
WHEN NO_DATA_FOUND THEN
p_total_compras := 0;
p_numero_compras := 0;
p_ticket_medio := 0;
DBMS_OUTPUT.PUT_LINE(\'Cliente não encontrado ou sem compras\');
END calcular_estatisticas_cliente;
/

-- Chamando a procedure
DECLARE
v_total NUMBER;
v_numero NUMBER;
v_ticket NUMBER;
BEGIN
calcular_estatisticas_cliente(101, v_total, v_numero, v_ticket);

DBMS_OUTPUT.PUT_LINE(\'Total de compras: R$ \' || v_total);


DBMS_OUTPUT.PUT_LINE(\'Número de compras: \' || v_numero);
DBMS_OUTPUT.PUT_LINE(\'Ticket médio: R$ \' || ROUND(v_ticket, 2));
END;
/
22.2.5 Procedure com Parâmetro IN OUT

CREATE OR REPLACE PROCEDURE aplicar_desconto (


p_valor IN OUT NUMBER,
p_percentual_desconto IN NUMBER
)
IS
v_desconto NUMBER;
BEGIN
v_desconto := p_valor * (p_percentual_desconto / 100);
p_valor := p_valor - v_desconto;

DBMS_OUTPUT.PUT_LINE(\'Desconto aplicado: R$ \' || ROUND(v_desconto, 2));


END aplicar_desconto;
/

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

22.3.1 Sintaxe de Criação

CREATE [OR REPLACE] FUNCTION nome_function


[(parametro1 [IN] tipo_dado,
parametro2 [IN] tipo_dado,
...)]
RETURN tipo_retorno
IS | AS
-- Seção de declaração (opcional)
declaracoes_locais
BEGIN
-- Seção executável
comandos_executaveis
RETURN valor_retorno;
[EXCEPTION
-- Seção de tratamento de exceções (opcional)
tratamento_excecoes
RETURN valor_retorno_excecao;]
END [nome_function];
/
22.3.2 Exemplo Simples de Function

CREATE OR REPLACE FUNCTION calcular_quadrado (p_numero IN NUMBER)


RETURN NUMBER
IS
BEGIN
RETURN p_numero * p_numero;
END calcular_quadrado;
/

-- Chamando a function
BEGIN
DBMS_OUTPUT.PUT_LINE(\'O quadrado de 5 é: \' || calcular_quadrado(5));
END;
/

-- Usando a function em uma consulta SQL


SELECT
ProdutoID,
NomeProduto,
Preco,
calcular_quadrado(Preco) AS PrecoAoQuadrado
FROM Produtos
WHERE ProdutoID <= 3;

22.3.3 Function Mais Complexa

CREATE OR REPLACE FUNCTION obter_categoria_cliente (p_cliente_id IN NUMBER)


RETURN VARCHAR2
IS
v_total_compras NUMBER;
v_categoria VARCHAR2(20);
BEGIN
-- Calcular total de compras do cliente
SELECT NVL(SUM(Quantidade * PrecoUnitario), 0)
INTO v_total_compras
FROM Vendas
WHERE ClienteID = p_cliente_id;

-- Determinar categoria baseada no total


IF v_total_compras = 0 THEN
v_categoria := \'Novo\';
ELSIF v_total_compras < 1000 THEN
v_categoria := \'Bronze\';
ELSIF v_total_compras < 5000 THEN
v_categoria := \'Prata\';
ELSIF v_total_compras < 10000 THEN
v_categoria := \'Ouro\';
ELSE
v_categoria := \'Diamante\';
END IF;

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

CREATE OR REPLACE FUNCTION calcular_imposto (


p_valor IN NUMBER,
p_tipo_produto IN VARCHAR2
)
RETURN NUMBER
IS
v_taxa_imposto NUMBER;
BEGIN
CASE p_tipo_produto
WHEN \'Livros\' THEN
v_taxa_imposto := 0.05; -- 5%
WHEN \'Eletrônicos\' THEN
v_taxa_imposto := 0.18; -- 18%
WHEN \'Roupas\' THEN
v_taxa_imposto := 0.12; -- 12%
ELSE
v_taxa_imposto := 0.10; -- 10% padrão
END CASE;

RETURN p_valor * v_taxa_imposto;


END calcular_imposto;
/

-- 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;
/

22.4 Parâmetros com Valores Padrão

Você pode definir valores padrão para parâmetros, tornando-os opcionais na chamada do subprograma.

CREATE OR REPLACE FUNCTION calcular_desconto (


p_valor IN NUMBER,
p_percentual IN NUMBER DEFAULT 10,
p_valor_minimo IN NUMBER DEFAULT 0
)
RETURN NUMBER
IS
BEGIN
IF p_valor >= p_valor_minimo THEN
RETURN p_valor * (p_percentual / 100);
ELSE
RETURN 0;
END IF;
END calcular_desconto;
/

-- Chamadas com diferentes números de parâmetros


BEGIN
DBMS_OUTPUT.PUT_LINE(\'Desconto 1: R$` \' || calcular_desconto(1000)); -- Usa padrões: 10%, R`$ 0
DBMS_OUTPUT.PUT_LINE(\'Desconto 2: R$` \' || calcular_desconto(1000, 15)); -- 15%, R`$ 0
DBMS_OUTPUT.PUT_LINE(\'Desconto 3: R$` \' || calcular_desconto(1000, 20, 500)); -- 20%, R`$ 500
END;
/

22.5 Passagem de Parâmetros por Nome

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;
/

22.6 Subprogramas Locais

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;
/

22.7 Gerenciamento de Subprogramas

22.7.1 Visualizando Subprogramas

-- Ver procedures e functions do usuário atual


SELECT object_name, object_type, status
FROM user_objects
WHERE object_type IN (\'PROCEDURE\', \'FUNCTION\')
ORDER BY object_name;

-- Ver código fonte de um subprograma


SELECT text
FROM user_source
WHERE name = \'CALCULAR_QUADRADO\'
ORDER BY line;

22.7.2 Removendo Subprogramas

DROP PROCEDURE exibir_mensagem;


DROP FUNCTION calcular_quadrado;
22.7.3 Compilando Subprogramas

-- Recompilar uma procedure


ALTER PROCEDURE calcular_estatisticas_cliente COMPILE;

-- Recompilar uma function


ALTER FUNCTION obter_categoria_cliente COMPILE;

22.8 Tratamento de Exceções em Subprogramas

CREATE OR REPLACE FUNCTION obter_preco_produto (p_produto_id IN NUMBER)


RETURN NUMBER
IS
v_preco NUMBER;
BEGIN
SELECT Preco
INTO v_preco
FROM Produtos
WHERE ProdutoID = p_produto_id;

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;
/

-- Testando o tratamento de exceções


BEGIN
DBMS_OUTPUT.PUT_LINE(\'Preço do produto 1: R$ \' || obter_preco_produto(1));
DBMS_OUTPUT.PUT_LINE(\'Preço do produto 999: R$ \' || obter_preco_produto(999)); -- Não existe
END;
/

22.9 Tópicos Chave do Capítulo 22

Subprogramas: Blocos nomeados e reutilizáveis (Procedures e Functions).

Procedures: Executam ações; não retornam valores diretamente; usam parâmetros OUT.

Functions: Sempre retornam um valor; podem ser usadas em expressões SQL.

Tipos de Parâmetros: IN (entrada), OUT (saída), IN OUT (entrada e saída).

Valores Padrão: Parâmetros podem ter valores padrão, tornando-os opcionais.

Passagem por Nome: Permite especificar parâmetros por nome para maior clareza.

Subprogramas Locais: Podem ser definidos dentro de blocos para uso local.

Gerenciamento: Criação, compilação, visualização e remoção de subprogramas.

Exceções: Tratamento de erros específico para subprogramas.

22.10 Exercícios do Capítulo 22

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.

3. Procedure com Parâmetros OUT:

Crie uma procedure que aceite um ProdutoID e retorne (via parâmetros OUT) o nome do produto,
preço e estoque.

4. Function com Lógica Condicional:

Crie uma function que determine o status de um produto baseado no estoque:


\'Esgotado\' se estoque = 0

\'Baixo\' se estoque < 10

\'Normal\' se estoque >= 10

5. Desafio - Procedure Complexa:

Crie uma procedure que processe um pedido de venda:


Aceite ClienteID , ProdutoID e Quantidade como parâmetros.

Verifique se o cliente e produto existem.

Verifique se há estoque suficiente.

Se tudo estiver OK, insira a venda e atualize o estoque.

Use tratamento de exceções para lidar com erros.

Retorne um código de status (0 = sucesso, 1 = cliente não existe, 2 = produto não existe, 3 =
estoque insuficiente).

Referências

[1] Oracle. PL/SQL Language Reference - Subprograms. Disponível em:


[Link]

Capítulo 23: Packages em PL/SQL

23.1 O que são Packages?

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.

23.1.1 Vantagens dos Packages

Modularidade: Dividem código complexo em unidades menores e gerenciáveis.

Encapsulamento: Permite ocultar a implementação interna de procedures e functions, expondo apenas


a interface pública. Isso significa que você pode alterar a lógica interna sem afetar as aplicações que
usam o package.

Reutilização: Componentes dentro de um package podem ser facilmente reutilizados em diferentes


partes da aplicação ou por outras aplicações.

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.

Segurança: Permite conceder privilégios de execução a um package inteiro, em vez de a cada


subprograma individualmente, simplificando o gerenciamento de segurança.

Prevenção de Conflitos de Nomes: Nomes de subprogramas e variáveis dentro de um package são


únicos apenas dentro desse package, evitando conflitos com objetos de outros packages ou do
esquema.

23.2 Estrutura de um Package

Um package é composto por duas partes distintas:

1. Especificação (Specification): Define a interface pública do package. Declara todos os componentes


(procedures, functions, variáveis, tipos, etc.) que podem ser acessados de fora do package. É como um
contrato que mostra o que o package oferece, mas não como ele faz.

2. Corpo (Body): Contém a implementação de todos os componentes declarados na especificação, além


de quaisquer componentes privados (que não são visíveis externamente). É onde a lógica real do
package reside.
23.2.1 Package Specification (Especificação)

CREATE [OR REPLACE] PACKAGE nome_package


IS
-- Declaração de variáveis públicas
variavel_publica NUMBER;

-- Declaração de tipos públicos


TYPE t_lista_nomes IS TABLE OF VARCHAR2(100) INDEX BY PLS_INTEGER;

-- Declaração de procedures públicas


PROCEDURE inserir_cliente (
p_nome IN VARCHAR2,
p_email IN VARCHAR2
);

-- Declaração de functions públicas


FUNCTION obter_total_vendas (p_cliente_id IN NUMBER) RETURN NUMBER;

-- Declaração de cursores públicos


CURSOR cur_produtos_ativos RETURN Produtos%ROWTYPE;

-- Declaração de exceções públicas


e_cliente_invalido EXCEPTION;
END nome_package;
/
23.2.2 Package Body (Corpo)

CREATE [OR REPLACE] PACKAGE BODY nome_package


IS
-- Variáveis privadas (visíveis apenas dentro do corpo do package)
v_contador_interno NUMBER := 0;

-- Implementação da procedure pública


PROCEDURE inserir_cliente (
p_nome IN VARCHAR2,
p_email IN VARCHAR2
)
IS
BEGIN
-- Lógica de validação
IF p_nome IS NULL OR p_email IS NULL THEN
RAISE e_cliente_invalido;
END IF;

INSERT INTO Clientes (ClienteID, Nome, Email)


VALUES (seq_clientes.NEXTVAL, p_nome, p_email);

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;

-- Implementação da function pública


FUNCTION obter_total_vendas (p_cliente_id IN NUMBER) RETURN NUMBER
IS
v_total NUMBER;
BEGIN
SELECT NVL(SUM(Quantidade * PrecoUnitario), 0)
INTO v_total
FROM Vendas
WHERE ClienteID = p_cliente_id;

RETURN v_total;
END obter_total_vendas;

-- Implementação do cursor público


CURSOR cur_produtos_ativos RETURN Produtos%ROWTYPE IS
SELECT * FROM Produtos WHERE Ativo = 1;

-- Bloco de inicialização do package (executado uma vez por sessão)


BEGIN
DBMS_OUTPUT.PUT_LINE(
\'Package \' || $$PLSQL_UNIT || \' inicializado na sessão.\'
);
END nome_package;
/

23.3 Componentes de um Package

23.3.1 Variáveis e Constantes

Podem ser declaradas na especificação (públicas) ou no corpo (privadas).


Públicas: Acessíveis de fora do package. Mantêm seu valor durante a sessão.

Privadas: Visíveis apenas dentro do corpo do package. Mantêm seu valor durante a sessão.

23.3.2 Procedures e Functions

Declaradas na especificação e implementadas no corpo. Podem ser sobrecarregadas (overloaded) se tiverem


o mesmo nome, mas diferentes assinaturas de parâmetros.

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

Podem ser declaradas na especificação para serem levantadas e tratadas externamente.

23.3.5 Bloco de Inicialização

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.

23.4 Acessando Componentes do Package

Para acessar um componente público de um package, use a notação nome_package.nome_componente .

-- Habilitar saída para ver mensagens do DBMS_OUTPUT


SET SERVEROUTPUT ON;

-- Inserir um cliente usando a procedure do package


BEGIN
nome_package.inserir_cliente(
p_nome => \'Alice Silva\',
p_email => \'[Link]@[Link]\'
);
COMMIT;
END;
/

-- Obter total de vendas usando a function do package


DECLARE
v_total NUMBER;
BEGIN
v_total := nome_package.obter_total_vendas(101);
DBMS_OUTPUT.PUT_LINE(\'Total de vendas para o cliente 101: R$ \' || v_total);
END;
/

-- Acessar uma variável pública do package


BEGIN
DBMS_OUTPUT.PUT_LINE(\'Valor da variável pública: \' || nome_package.variavel_publica);
nome_package.variavel_publica := 100;
DBMS_OUTPUT.PUT_LINE(\'Novo valor da variável pública: \' || nome_package.variavel_publica);
END;
/
23.5 Overloading (Sobrecarga)

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].

CREATE OR REPLACE PACKAGE pkg_calculos


IS
FUNCTION somar (p_num1 IN NUMBER, p_num2 IN NUMBER) RETURN NUMBER;
FUNCTION somar (p_texto1 IN VARCHAR2, p_texto2 IN VARCHAR2) RETURN VARCHAR2;
PROCEDURE exibir_valor (p_num IN NUMBER);
PROCEDURE exibir_valor (p_texto IN VARCHAR2);
END pkg_calculos;
/

CREATE OR REPLACE PACKAGE BODY pkg_calculos


IS
FUNCTION somar (p_num1 IN NUMBER, p_num2 IN NUMBER) RETURN NUMBER
IS
BEGIN
RETURN p_num1 + p_num2;
END somar;

FUNCTION somar (p_texto1 IN VARCHAR2, p_texto2 IN VARCHAR2) RETURN VARCHAR2


IS
BEGIN
RETURN p_texto1 || p_texto2;
END somar;

PROCEDURE exibir_valor (p_num IN NUMBER)


IS
BEGIN
DBMS_OUTPUT.PUT_LINE(\'Número: \' || p_num);
END exibir_valor;

PROCEDURE exibir_valor (p_texto IN VARCHAR2)


IS
BEGIN
DBMS_OUTPUT.PUT_LINE(\'Texto: \' || p_texto);
END exibir_valor;
END pkg_calculos;
/

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

23.6.1 Visualizando Packages

-- Listar todos os packages no esquema atual


SELECT object_name, object_type, status
FROM user_objects
WHERE object_type = \'PACKAGE\';

-- Listar as especificações dos packages


SELECT object_name, object_type, status
FROM user_objects
WHERE object_type = \'PACKAGE SPECIFICATION\';

-- Listar os corpos dos packages


SELECT object_name, object_type, status
FROM user_objects
WHERE object_type = \'PACKAGE BODY\';

-- Ver o código fonte de um package (especificação ou corpo)


SELECT text
FROM user_source
WHERE name = \'NOME_PACKAGE\' AND type = \'PACKAGE\'
ORDER BY line;

SELECT text
FROM user_source
WHERE name = \'NOME_PACKAGE\' AND type = \'PACKAGE BODY\'
ORDER BY line;

23.6.2 Removendo Packages

Para remover um package, você pode remover apenas o corpo ou o package inteiro (especificação e corpo).

-- Remover apenas o corpo do package


DROP PACKAGE BODY nome_package;

-- Remover o package inteiro (especificação e corpo)


DROP PACKAGE nome_package;

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.

23.6.3 Compilando Packages

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.

-- Recompilar a especificação do package


ALTER PACKAGE nome_package COMPILE PACKAGE;

-- Recompilar o corpo do package


ALTER PACKAGE nome_package COMPILE BODY;

-- Recompilar o package inteiro (especificação e corpo)


ALTER PACKAGE nome_package COMPILE;
23.7 Packages Built-in Importantes

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:

DBMS_OUTPUT : Para exibir informações na tela (já vimos).

DBMS_SQL : Para executar SQL dinâmico.

DBMS_UTILITY : Funções utilitárias diversas (ex: GET_TIME , FORMAT_ERROR_STACK ).

UTL_FILE : Para operações de arquivo no sistema operacional.

UTL_HTTP : Para fazer requisições HTTP (acessar URLs).

DBMS_LOB : Para manipular objetos grandes (LOBs).

DBMS_SCHEDULER : Para agendar tarefas.

23.8 Tópicos Chave do Capítulo 23

Packages: Agrupam componentes PL/SQL relacionados (variáveis, procedures, functions, etc.).

Vantagens: Modularidade, encapsulamento, reutilização, performance, estado global, segurança.

Estrutura: Dividido em Especificação (interface pública) e Corpo (implementação).

Componentes: Variáveis/constantes (públicas/privadas), procedures, functions, cursores, exceções.

Bloco de Inicialização: Executado uma vez por sessão na primeira referência ao package.

Acesso: nome_package.nome_componente .

Overloading: Permite subprogramas com o mesmo nome, mas diferentes assinaturas.

Gerenciamento: CREATE/ALTER/DROP PACKAGE , visualização de user_objects e user_source .

Packages Built-in: DBMS_OUTPUT , DBMS_SQL , UTL_FILE , etc.

23.9 Exercícios do Capítulo 23

1. Criação de um Package Simples:

Crie um package chamado pkg_utilidades .

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 essas duas rotinas.

Teste o package chamando a procedure e a function.

2. Package com Variáveis de Estado:

Crie um package pkg_contador_acessos .

Na especificação, declare uma procedure incrementar_contador e uma function


obter_contador_atual .
No corpo, declare uma variável privada v_contador NUMBER := 0; .

A procedure incrementar_contador deve incrementar v_contador .

A function obter_contador_atual deve retornar o valor de v_contador .

Teste o package em uma sessão, chamando incrementar_contador várias vezes e


obter_contador_atual para ver o estado persistir.

3. Package com Overloading:

Crie um package pkg_log .

Na especificação, declare duas procedures escrever_log :


Uma que recebe apenas uma mensagem ( VARCHAR2 ).

Outra que recebe uma mensagem ( VARCHAR2 ) e um nível de severidade ( NUMBER ).

No corpo, implemente ambas as procedures para exibir a mensagem e o nível (se fornecido).

Teste as duas versões da procedure.

4. Package com Cursor Público:

Crie um package pkg_relatorios .

Na especificação, declare um cursor público cur_clientes_ativos que retorna ClienteID , Nome


e Email de clientes ativos.

No corpo, implemente a consulta para o cursor.

Em um bloco anônimo, abra, busque e feche este cursor, exibindo os dados.

5. Desafio - Package de Gerenciamento de Produtos:

Crie um package pkg_produtos com as seguintes funcionalidades:


Procedure adicionar_produto (IN: nome, preco, estoque, categoria).

Procedure atualizar_estoque (IN: produto_id, quantidade_adicionar).

Function obter_preco (IN: produto_id, OUT: preco).

Function obter_estoque (IN: produto_id, OUT: estoque).

Exceção e_produto_nao_encontrado .

Use DBMS_OUTPUT para mensagens de sucesso/erro.

Implemente tratamento de exceções adequado para cada rotina.

Teste todas as funcionalidades em um bloco anônimo.

Referências

[1] Oracle. PL/SQL Language Reference - Packages. Disponível em:


[Link] [2] Oracle. PL/SQL
Language Reference - Overloading Subprograms. Disponível em:
[Link]
0027731F-430C-446C-964B-47124623126C
Capítulo 24: Triggers em PL/SQL

24.1 O que são Triggers?

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.).

24.1.1 Características dos Triggers

Execução Automática: São executados automaticamente quando eventos específicos ocorrem.

Transparência: As aplicações não precisam saber da existência dos triggers.

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.

Transacionais: Fazem parte da transação que os acionou.

24.1.2 Usos Comuns dos Triggers

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.

Sincronização de Dados: Manter dados derivados ou redundantes atualizados.

Logging: Registrar eventos importantes para análise posterior.

Segurança: Implementar controles de acesso adicionais.

Geração de Valores: Calcular e atribuir valores automaticamente (como timestamps, códigos


sequenciais).

24.2 Tipos de Triggers

24.2.1 Classificação por Evento

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 .

24.2.2 Classificação por Timing (DML Triggers)

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.

24.2.3 Classificação por Nível

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 .

24.3 Sintaxe Básica de DML Triggers

CREATE [OR REPLACE] TRIGGER nome_trigger


{BEFORE | AFTER | INSTEAD OF}
{INSERT | UPDATE | DELETE}
[OR {INSERT | UPDATE | DELETE}]...
ON {nome_tabela | nome_view}
[FOR EACH ROW]
[WHEN (condicao)]
[DECLARE
-- Declarações locais
]
BEGIN
-- Código do trigger
[EXCEPTION
-- Tratamento de exceções
]
END [nome_trigger];
/

24.4 Pseudo-Registros :OLD e :NEW

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

Operação :OLD :NEW

INSERT Todos os valores são NULL Contém os novos valores

UPDATE Contém os valores antigos Contém os novos valores

DELETE Contém os valores sendo deletados Todos os valores são NULL


24.5 Exemplos Práticos de Triggers

24.5.1 Trigger de Auditoria (AFTER)

-- Criar tabela de auditoria


CREATE TABLE AuditoriaClientes (
AuditoriaID NUMBER PRIMARY KEY,
ClienteID NUMBER,
Operacao VARCHAR2(10),
UsuarioOperacao VARCHAR2(30),
DataOperacao DATE,
ValorAntigo VARCHAR2(4000),
ValorNovo VARCHAR2(4000)
);

CREATE SEQUENCE seq_auditoria_clientes START WITH 1;

-- Trigger de auditoria para a tabela Clientes


CREATE OR REPLACE TRIGGER trg_auditoria_clientes
AFTER INSERT OR UPDATE OR DELETE
ON Clientes
FOR EACH ROW
DECLARE
v_operacao VARCHAR2(10);
v_valor_antigo VARCHAR2(4000);
v_valor_novo VARCHAR2(4000);
BEGIN
-- Determinar o tipo de operação
IF INSERTING THEN
v_operacao := \'INSERT\';
v_valor_antigo := NULL;
v_valor_novo := \'ID: \' || :[Link] ||
\', Nome: \' || :[Link] ||
\', Email: \' || :[Link];
ELSIF UPDATING THEN
v_operacao := \'UPDATE\';
v_valor_antigo := \'ID: \' || :[Link] ||
\', Nome: \' || :[Link] ||
\', Email: \' || :[Link];
v_valor_novo := \'ID: \' || :[Link] ||
\', Nome: \' || :[Link] ||
\', Email: \' || :[Link];
ELSIF DELETING THEN
v_operacao := \'DELETE\';
v_valor_antigo := \'ID: \' || :[Link] ||
\', Nome: \' || :[Link] ||
\', Email: \' || :[Link];
v_valor_novo := NULL;
END IF;

-- Inserir registro de auditoria


INSERT INTO AuditoriaClientes (
AuditoriaID,
ClienteID,
Operacao,
UsuarioOperacao,
DataOperacao,
ValorAntigo,
ValorNovo
) VALUES (
seq_auditoria_clientes.NEXTVAL,
COALESCE(:[Link], :[Link]),
v_operacao,
USER,
SYSDATE,
v_valor_antigo,
v_valor_novo
);
END trg_auditoria_clientes;
/

-- Testando o trigger de auditoria


INSERT INTO Clientes (ClienteID, Nome, Email)
VALUES (106, \'Maria Santos\', \'[Link]@[Link]\');
UPDATE Clientes
SET Email = \'[Link]@[Link]\'
WHERE ClienteID = 106;

DELETE FROM Clientes WHERE ClienteID = 106;

-- Verificar os registros de auditoria


SELECT * FROM AuditoriaClientes ORDER BY DataOperacao;

24.5.2 Trigger de Validação (BEFORE)

-- Trigger para validar e normalizar dados antes da inserção/atualização


CREATE OR REPLACE TRIGGER trg_validar_clientes
BEFORE INSERT OR UPDATE
ON Clientes
FOR EACH ROW
BEGIN
-- Normalizar o nome (primeira letra maiúscula)
:[Link] := INITCAP(TRIM(:[Link]));

-- Normalizar o email (minúsculas)


:[Link] := LOWER(TRIM(:[Link]));

-- Validar formato do email


IF :[Link] NOT LIKE \'%@%.%\' THEN
RAISE_APPLICATION_ERROR(-20001, \'Formato de email inválido: \' || :[Link]);
END IF;

-- Validar se o nome não está vazio


IF :[Link] IS NULL OR LENGTH(TRIM(:[Link])) = 0 THEN
RAISE_APPLICATION_ERROR(-20002, \'Nome do cliente não pode estar vazio\');
END IF;

-- Definir data de cadastro se for uma inserção


IF INSERTING THEN
:[Link] := SYSDATE;
END IF;

-- Definir data de última atualização


:[Link] := SYSDATE;
END trg_validar_clientes;
/

-- Testando o trigger de validação


INSERT INTO Clientes (ClienteID, Nome, Email)
VALUES (107, \' joão silva \', \' [Link]@[Link] \');

-- Verificar se os dados foram normalizados


SELECT ClienteID, Nome, Email FROM Clientes WHERE ClienteID = 107;

-- Testar validação de email inválido


-- INSERT INTO Clientes (ClienteID, Nome, Email)
-- VALUES (108, \'Pedro\', \'email_invalido\'); -- Isso gerará um erro
24.5.3 Trigger de Sincronização (AFTER)

-- Criar tabela de estatísticas de vendas


CREATE TABLE EstatisticasVendas (
ClienteID NUMBER PRIMARY KEY,
TotalVendas NUMBER(15,2) DEFAULT 0,
NumeroVendas NUMBER DEFAULT 0,
UltimaVenda DATE,
FOREIGN KEY (ClienteID) REFERENCES Clientes(ClienteID)
);

-- Trigger para manter estatísticas atualizadas


CREATE OR REPLACE TRIGGER trg_atualizar_estatisticas_vendas
AFTER INSERT OR UPDATE OR DELETE
ON Vendas
FOR EACH ROW
DECLARE
v_cliente_id NUMBER;
v_total_vendas NUMBER;
v_numero_vendas NUMBER;
v_ultima_venda DATE;
BEGIN
-- Determinar qual cliente foi afetado
v_cliente_id := COALESCE(:[Link], :[Link]);

-- Calcular novas estatísticas


SELECT
NVL(SUM(Quantidade * PrecoUnitario), 0),
COUNT(*),
MAX(DataVenda)
INTO
v_total_vendas,
v_numero_vendas,
v_ultima_venda
FROM Vendas
WHERE ClienteID = v_cliente_id;

-- Atualizar ou inserir estatísticas


MERGE INTO EstatisticasVendas e
USING (SELECT v_cliente_id AS ClienteID FROM DUAL) d
ON ([Link] = [Link])
WHEN MATCHED THEN
UPDATE SET
TotalVendas = v_total_vendas,
NumeroVendas = v_numero_vendas,
UltimaVenda = v_ultima_venda
WHEN NOT MATCHED THEN
INSERT (ClienteID, TotalVendas, NumeroVendas, UltimaVenda)
VALUES (v_cliente_id, v_total_vendas, v_numero_vendas, v_ultima_venda);
END trg_atualizar_estatisticas_vendas;
/

-- Testando o trigger de sincronização


INSERT INTO Vendas (VendaID, ClienteID, ProdutoID, DataVenda, Quantidade, PrecoUnitario)
VALUES (seq_vendas.NEXTVAL, 101, 1, SYSDATE, 2, 500.00);

-- Verificar se as estatísticas foram atualizadas


SELECT * FROM EstatisticasVendas WHERE ClienteID = 101;
24.5.4 Trigger com Cláusula WHEN

-- Trigger que só executa para produtos caros (preço > 1000)


CREATE OR REPLACE TRIGGER trg_produtos_caros
BEFORE UPDATE OF Preco
ON Produtos
FOR EACH ROW
WHEN ([Link] > 1000)
BEGIN
-- Log de alteração de preço para produtos caros
DBMS_OUTPUT.PUT_LINE(
\'ALERTA: Preço do produto \' || :[Link] ||
\' alterado de R$ \' || :[Link] ||
\' para R$ \' || :[Link]
);

-- Verificar se o aumento é muito grande (mais de 50%)


IF :[Link] > :[Link] * 1.5 THEN
RAISE_APPLICATION_ERROR(-20003,
\'Aumento de preço muito grande para produto caro: \' ||
:[Link] || \'. Máximo permitido: 50%\');
END IF;
END trg_produtos_caros;
/

24.6 Predicados de Trigger

Dentro de um trigger que responde a múltiplos eventos, você pode usar predicados para determinar qual
operação acionou o trigger:

INSERTING : Retorna TRUE se o trigger foi acionado por um INSERT .

UPDATING : Retorna TRUE se o trigger foi acionado por um UPDATE .

DELETING : Retorna TRUE se o trigger foi acionado por um DELETE .

UPDATING(\'coluna\') : Retorna TRUE se o trigger foi acionado por um UPDATE que modificou a coluna
especificada.

CREATE OR REPLACE TRIGGER trg_exemplo_predicados


BEFORE INSERT OR UPDATE OR DELETE
ON Produtos
FOR EACH ROW
BEGIN
IF INSERTING THEN
DBMS_OUTPUT.PUT_LINE(\'Inserindo produto: \' || :[Link]);
ELSIF UPDATING THEN
IF UPDATING(\'Preco\') THEN
DBMS_OUTPUT.PUT_LINE(\'Atualizando preço do produto: \' || :[Link]);
ELSE
DBMS_OUTPUT.PUT_LINE(\'Atualizando produto: \' || :[Link]);
END IF;
ELSIF DELETING THEN
DBMS_OUTPUT.PUT_LINE(\'Deletando produto: \' || :[Link]);
END IF;
END trg_exemplo_predicados;
/

24.7 Triggers de Statement-Level

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.8 Gerenciamento de Triggers

24.8.1 Visualizando Triggers

-- Listar todos os triggers do usuário


SELECT trigger_name, table_name, trigger_type, triggering_event, status
FROM user_triggers
ORDER BY table_name, trigger_name;

-- Ver o código fonte de um trigger


SELECT trigger_body
FROM user_triggers
WHERE trigger_name = \'TRG_AUDITORIA_CLIENTES\';

24.8.2 Habilitando e Desabilitando Triggers

-- Desabilitar um trigger específico


ALTER TRIGGER trg_auditoria_clientes DISABLE;

-- Habilitar um trigger específico


ALTER TRIGGER trg_auditoria_clientes ENABLE;

-- Desabilitar todos os triggers de uma tabela


ALTER TABLE Clientes DISABLE ALL TRIGGERS;

-- Habilitar todos os triggers de uma tabela


ALTER TABLE Clientes ENABLE ALL TRIGGERS;
24.8.3 Removendo Triggers

DROP TRIGGER trg_auditoria_clientes;

24.9 Considerações e Limitações

24.9.1 Performance

Triggers adicionam overhead a operações DML.

Evite lógica complexa em triggers de alta frequência.

Considere usar triggers de statement-level quando possível.

24.9.2 Mutating Table Error

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.

24.9.3 Cascata de Triggers

Triggers podem acionar outros triggers, criando uma cascata. Tenha cuidado para evitar loops infinitos.

24.10 Tópicos Chave do Capítulo 24

Triggers: Procedures especiais executadas automaticamente em resposta a eventos.

Tipos: DML (INSERT/UPDATE/DELETE), DDL, Database Event.

Timing: BEFORE, AFTER, INSTEAD OF.

Nível: Statement-level (por instrução) ou Row-level (por linha).

Pseudo-registros: :OLD (valores antigos) e :NEW (valores novos).

Predicados: INSERTING , UPDATING , DELETING para identificar a operação.

Usos: Auditoria, validação, sincronização, logging, segurança.

Gerenciamento: CREATE/ALTER/DROP TRIGGER , ENABLE/DISABLE .

Considerações: Performance, mutating table error, cascata de triggers.

24.11 Exercícios do Capítulo 24

1. Trigger de Auditoria Simples:

Crie uma tabela LogProdutos para registrar alterações na tabela Produtos .

Crie um trigger que registre todas as operações (INSERT, UPDATE, DELETE) na tabela Produtos .

2. Trigger de Validação:

Crie um trigger BEFORE para a tabela Produtos que:


Garanta que o preço seja sempre positivo.

Normalize o nome do produto (primeira letra maiúscula).

Defina automaticamente a data de cadastro para novos produtos.

3. Trigger de Cálculo Automático:

Adicione uma coluna ValorTotal à tabela Vendas .

Crie um trigger que calcule automaticamente ValorTotal = Quantidade * PrecoUnitario antes


de inserir ou atualizar.

4. Trigger com Cláusula WHEN:

Crie um trigger que só execute para vendas com valor total superior a R$ 1000.

O trigger deve inserir um registro em uma tabela VendasImportantes .

5. Desafio - Sistema de Estoque Automático:

Crie um trigger na tabela Vendas que:


Diminua automaticamente o estoque do produto vendido.

Verifique se há estoque suficiente antes da venda.

Gere um alerta (inserir em tabela AlertasEstoque ) quando o estoque ficar abaixo de 5


unidades.

Capítulo 25: Tratamento de Exceções Avançado em


PL/SQL

25.1 Revisão do Tratamento Básico de Exceções

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].

Relembrando a estrutura básica:

[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

DBMS_OUTPUT.PUT_LINE("Nome do cliente: " || v_nome_cliente);


EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE("Erro: Cliente não encontrado.");
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE("Erro inesperado: " || SQLERRM);
END;
/

25.2 Exceções Definidas pelo Usuário

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:

25.2.1 Exceções Nomeadas (User-Defined Exceptions)

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;

IF v_estoque_atual < v_quantidade_desejada THEN


RAISE e_estoque_insuficiente; -- Levanta a exceção
END IF;

DBMS_OUTPUT.PUT_LINE("Pedido processado com sucesso.");


EXCEPTION
WHEN e_estoque_insuficiente THEN
DBMS_OUTPUT.PUT_LINE("Erro: Estoque insuficiente para o produto " || v_produto_id);
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE("Erro: Produto " || v_produto_id || " não encontrado.");
END;
/

25.2.2 Exceções Associadas a Códigos de Erro Oracle (PRAGMA EXCEPTION_INIT)

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;

DBMS_OUTPUT.PUT_LINE("Operação realizada com sucesso.");


EXCEPTION
WHEN e_tabela_bloqueada THEN
DBMS_OUTPUT.PUT_LINE("Erro: Tabela bloqueada por outra sessão. Tente novamente mais tarde.");
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE("Erro inesperado: " || SQLERRM);
END;
/

25.2.3 Exceções com RAISE_APPLICATION_ERROR

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;
/

25.3 Propagação de Exceções

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.");

BEGIN -- Bloco interno


DBMS_OUTPUT.PUT_LINE("Início do bloco interno.");
RAISE e_erro_interno; -- Levanta exceção no bloco interno
DBMS_OUTPUT.PUT_LINE("Fim do bloco interno (não alcançado).");
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE("Tratado NO_DATA_FOUND no bloco interno.");
-- e_erro_interno NÃO é tratado aqui
END; -- Fim do bloco interno

DBMS_OUTPUT.PUT_LINE("Fim do bloco externo (não alcançado se e_erro_interno não for tratado).");


EXCEPTION
WHEN e_erro_interno THEN
DBMS_OUTPUT.PUT_LINE("Tratado e_erro_interno no bloco externo.");
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE("Tratado OTHERS no bloco externo: " || SQLERRM);
END;
/

25.4 Funções de Erro: SQLCODE e SQLERRM

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).

SQLERRM : Retorna a mensagem de erro associada ao SQLCODE .

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;
/

25.5 RAISE: Relançando Exceções

À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;

DBMS_OUTPUT.PUT_LINE("Produto: " || v_nome_produto);


EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE("Erro no bloco interno: Produto não encontrado. Relançando...");
RAISE; -- Relança a exceção NO_DATA_FOUND para o bloco externo
END;

DBMS_OUTPUT.PUT_LINE("Esta linha não será executada se a exceção for relançada.");


EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE("Erro no bloco externo: Produto não encontrado (capturado novamente).");
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE("Erro inesperado no bloco externo: " || SQLERRM);
END;
/

25.6 Tratamento de Exceções em Procedures e Functions

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.

CREATE OR REPLACE FUNCTION dividir_numeros (p_dividendo NUMBER, p_divisor NUMBER)


RETURN NUMBER
IS
BEGIN
RETURN p_dividendo / p_divisor;
EXCEPTION
WHEN ZERO_DIVIDE THEN
DBMS_OUTPUT.PUT_LINE("Erro: Divisão por zero não permitida.");
RETURN NULL; -- Ou RAISE, ou RAISE_APPLICATION_ERROR
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE("Erro inesperado na função: " || SQLERRM);
RETURN NULL;
END dividir_numeros;
/

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;
/

25.7 Considerações sobre Transações e Exceções

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]');

BEGIN -- Bloco interno com tratamento


INSERT INTO Produtos (ProdutoID, NomeProduto, Preco) VALUES (1, 'Produto Existente', 100); --
Causa DUP_VAL_ON_INDEX
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
DBMS_OUTPUT.PUT_LINE('Erro de duplicidade no produto. Continuar transação.');
-- A transação NÃO é revertida automaticamente aqui
END;

-- Se o erro acima foi tratado, o INSERT do cliente ainda está pendente


COMMIT;
DBMS_OUTPUT.PUT_LINE('Transação confirmada.');
EXCEPTION
WHEN OTHERS THEN
ROLLBACK; -- Se qualquer outra exceção não tratada ocorrer, reverte tudo
DBMS_OUTPUT.PUT_LINE('Erro fatal. Transação revertida: ' || SQLERRM);
END;
/

25.8 Tópicos Chave do Capítulo 25

Tratamento de Exceções: Mecanismo para lidar com erros em PL/SQL.

Exceções Pré-Definidas: Erros comuns do Oracle ( NO_DATA_FOUND , TOO_MANY_ROWS , etc.).

Exceções Definidas pelo Usuário:


Nomeadas ( EXCEPTION e RAISE ).

Associadas a códigos de erro ( PRAGMA EXCEPTION_INIT ).

RAISE_APPLICATION_ERROR : Para levantar erros com mensagens personalizadas para a aplicação


cliente.

Propagação: Exceções não tratadas se propagam para o bloco pai.

SQLCODE e SQLERRM : Funções para obter informações sobre o erro.

RAISE (sem nome): Relança a exceção atual para o bloco pai.

Tratamento em Subprogramas: Essencial para robustez de procedures e functions.

Transações: Exceções não tratadas causam ROLLBACK automático; exceções tratadas exigem
COMMIT / ROLLBACK explícito.

25.9 Exercícios do Capítulo 25

1. Tratamento de NO_DATA_FOUND e TOO_MANY_ROWS :

Crie um bloco que tente selecionar o nome de um cliente usando SELECT INTO .

Teste com um ClienteID que não existe ( NO_DATA_FOUND ).

Teste com uma consulta que retorna múltiplas linhas ( TOO_MANY_ROWS ).

Trate ambas as exceções e exiba mensagens apropriadas.

2. Exceção Definida pelo Usuário:

Crie uma exceção e_saldo_insuficiente .


Crie um bloco que simule uma retirada de dinheiro de uma conta.

Se o saldo for menor que o valor da retirada, levante e_saldo_insuficiente e trate-a.

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.

Teste a procedure e capture o erro em um bloco chamador.

4. Propagação e Relançamento:

Crie um bloco externo e um bloco interno aninhado.

No bloco interno, cause uma ZERO_DIVIDE .

Trate a ZERO_DIVIDE no bloco interno, exiba uma mensagem e relance a exceção ( RAISE ).

No bloco externo, capture a exceção relançada e exiba uma mensagem final.

5. Desafio - Transação com Tratamento de Erro:

Crie um bloco que insira um novo pedido (tabela Pedidos ) e, em seguida, insira os itens do pedido
(tabela ItensPedido ).

Simule um erro na inserção de um item (ex: ProdutoID inválido, causando NO_DATA_FOUND ao


buscar o preço do produto).

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

[1] Oracle. PL/SQL Language Reference - Exception Handling. Disponível em:


[Link]

Capítulo 26: SQL Dinâmico em PL/SQL

26.1 O que é SQL Dinâmico?

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.

Otimização Condicional: Para escolher diferentes estratégias de consulta baseadas em condições


específicas.

26.1.2 Vantagens e Desvantagens

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

26.2 Native Dynamic SQL com EXECUTE IMMEDIATE

EXECUTE IMMEDIATE é a forma mais simples e comumente usada de SQL dinâmico. Permite executar uma
string SQL construída dinamicamente.

26.2.1 Sintaxe Básica

EXECUTE IMMEDIATE sql_string


[INTO {define_variable[, define_variable]... | record}]
[USING [IN | OUT | IN OUT] bind_argument[, bind_argument]...];

26.2.2 Exemplos Básicos

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))\';

EXECUTE IMMEDIATE v_sql;


DBMS_OUTPUT.PUT_LINE(\'Tabela \' || v_table_name || \' criada com sucesso.\');

-- Inserir dados na nova tabela


v_sql := \'INSERT INTO \' || v_table_name ||
\' VALUES (1, 101, SYSDATE, 1500.00)\
26.2.3 Usando Bind Variables

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\';

EXECUTE IMMEDIATE v_sql USING v_novo_email, v_cliente_id;

v_linhas_afetadas := SQL%ROWCOUNT;
DBMS_OUTPUT.PUT_LINE(\'Linhas atualizadas: \' || v_linhas_afetadas);

IF v_linhas_afetadas > 0 THEN


COMMIT;
DBMS_OUTPUT.PUT_LINE(\'Atualização confirmada.\');
ELSE
DBMS_OUTPUT.PUT_LINE(\'Nenhuma linha foi atualizada.\');
END IF;
END;
/

26.2.4 Parâmetros OUT e IN OUT

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;\';

EXECUTE IMMEDIATE v_sql


USING IN v_cliente_id, OUT v_total_vendas, OUT v_numero_vendas;

DBMS_OUTPUT.PUT_LINE(\'Cliente: \' || v_cliente_id);


DBMS_OUTPUT.PUT_LINE(\'Total de vendas: R$ \' || v_total_vendas);
DBMS_OUTPUT.PUT_LINE(\'Número de vendas: \' || v_numero_vendas);
END;
/
26.3 Construindo Consultas Dinâmicas Complexas

26.3.1 WHERE Dinâmico

CREATE OR REPLACE PROCEDURE buscar_clientes_dinamico (


p_nome IN VARCHAR2 DEFAULT NULL,
p_estado IN VARCHAR2 DEFAULT NULL,
p_data_inicio IN DATE DEFAULT NULL,
p_data_fim IN DATE DEFAULT NULL
)
IS
v_sql VARCHAR2(4000);
v_where VARCHAR2(2000) := \'\';
v_count NUMBER := 0;

TYPE t_cursor IS REF CURSOR;


v_cursor t_cursor;

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\';

-- Construir cláusula WHERE dinamicamente


IF p_nome IS NOT NULL THEN
v_where := v_where || \' AND UPPER(Nome) LIKE UPPER(\'\'%\' || p_nome || \'%\'\')\';
END IF;

IF p_estado IS NOT NULL THEN


v_where := v_where || \' AND Estado = \'\'\' || p_estado || \'\'\'\';
END IF;

IF p_data_inicio IS NOT NULL THEN


v_where := v_where || \' AND DataCadastro >= TO_DATE(\'\'\' ||
TO_CHAR(p_data_inicio, \'DD/MM/YYYY\') || \'\'\', \'\'DD/MM/YYYY\'\'\')\';
END IF;

IF p_data_fim IS NOT NULL THEN


v_where := v_where || \' AND DataCadastro <= TO_DATE(\'\'\' ||
TO_CHAR(p_data_fim, \'DD/MM/YYYY\') || \'\'\', \'\'DD/MM/YYYY\'\'\')\';
END IF;

-- Adicionar WHERE se houver condições


IF LENGTH(v_where) > 0 THEN
v_sql := v_sql || \' WHERE 1=1\' || v_where;
END IF;

v_sql := v_sql || \' ORDER BY Nome\';

DBMS_OUTPUT.PUT_LINE(\'SQL Executado: \' || v_sql);


DBMS_OUTPUT.PUT_LINE(\'--- Resultados ---\');

-- Executar a consulta dinâmica


OPEN v_cursor FOR v_sql;
LOOP
FETCH v_cursor INTO v_cliente_id, v_nome, v_email;
EXIT WHEN v_cursor%NOTFOUND;

v_count := v_count + 1;
DBMS_OUTPUT.PUT_LINE(v_count || \'. \' || v_nome || \' (\' || v_email || \')\');
END LOOP;
CLOSE v_cursor;

DBMS_OUTPUT.PUT_LINE(\'Total de registros encontrados: \' || v_count);


END buscar_clientes_dinamico;
/

-- Testando a procedure
BEGIN
buscar_clientes_dinamico(p_nome => \'Silva\', p_estado => \'SP\');
END;
/
26.3.2 ORDER BY Dinâmico

CREATE OR REPLACE PROCEDURE listar_produtos_ordenado (


p_ordem_por IN VARCHAR2 DEFAULT \'NomeProduto\',
p_direcao IN VARCHAR2 DEFAULT \'ASC\'
)
IS
v_sql VARCHAR2(1000);
v_ordem VARCHAR2(100);

TYPE t_cursor IS REF CURSOR;


v_cursor t_cursor;

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;

-- Construir SQL com ORDER BY dinâmico


v_sql := \'SELECT ProdutoID, NomeProduto, Preco FROM Produtos ORDER BY \' ||
v_ordem || \' \' ||
CASE WHEN UPPER(p_direcao) = \'DESC\' THEN \'DESC\' ELSE \'ASC\' END;

DBMS_OUTPUT.PUT_LINE(\'Ordenação: \' || v_ordem || \' \' || p_direcao);


DBMS_OUTPUT.PUT_LINE(\'--- Produtos ---\');

OPEN v_cursor FOR v_sql;


LOOP
FETCH v_cursor INTO v_produto_id, v_nome_produto, v_preco;
EXIT WHEN v_cursor%NOTFOUND;

DBMS_OUTPUT.PUT_LINE(v_produto_id || \'. \' || v_nome_produto || \' - R$ \' || v_preco);


END LOOP;
CLOSE v_cursor;
END listar_produtos_ordenado;
/

-- Testando diferentes ordenações


BEGIN
listar_produtos_ordenado(\'PRECO\', \'DESC\');
END;
/

26.4 DBMS_SQL Package

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.

26.4.1 Processo Básico com DBMS_SQL

1. OPEN_CURSOR : Abre um cursor

2. PARSE : Analisa a instrução SQL

3. BIND_VARIABLE : Associa valores às bind variables (se houver)

4. EXECUTE : Executa a instrução

5. FETCH_ROWS : Busca resultados (para SELECT)


6. CLOSE_CURSOR : Fecha o cursor

26.4.2 Exemplo com DBMS_SQL

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);

-- 3. Definir colunas de saída


DBMS_SQL.DEFINE_COLUMN(v_cursor_id, 1, v_cliente_id);
DBMS_SQL.DEFINE_COLUMN(v_cursor_id, 2, v_nome, 100);
DBMS_SQL.DEFINE_COLUMN(v_cursor_id, 3, v_email, 255);

-- 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);

DBMS_OUTPUT.PUT_LINE(v_cliente_id || \'. \' || v_nome || \' - \' || v_email);


ELSE
EXIT;
END IF;
END LOOP;

-- 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;
/

26.5 Segurança e SQL Injection

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)

-- CÓDIGO INSEGURO - Vulnerável a SQL Injection


DECLARE
v_nome_usuario VARCHAR2(100) := \'admin\'\'; DROP TABLE Clientes; --\';
v_sql VARCHAR2(1000);
v_count NUMBER;
BEGIN
-- Construção insegura - NUNCA faça isso
v_sql := \'SELECT COUNT(*) FROM Usuarios WHERE Nome = \'\'\' || v_nome_usuario || \'\'\'\';

DBMS_OUTPUT.PUT_LINE(\'SQL: \' || v_sql);


-- Este SQL resultaria em: SELECT COUNT(*) FROM Usuarios WHERE Nome = \'admin\'; DROP TABLE
Clientes; --\'
-- Isso poderia executar comandos maliciosos!
END;
/

26.5.2 Exemplo Seguro (FAÇA ASSIM)

-- CÓDIGO SEGURO - Usando bind variables


DECLARE
v_nome_usuario VARCHAR2(100) := \'admin\'\'; DROP TABLE Clientes; --\';
v_sql VARCHAR2(1000);
v_count NUMBER;
BEGIN
-- Construção segura usando bind variables
v_sql := \'SELECT COUNT(*) FROM Usuarios WHERE Nome = :nome_usuario\';

EXECUTE IMMEDIATE v_sql INTO v_count USING v_nome_usuario;

DBMS_OUTPUT.PUT_LINE(\'Usuários encontrados: \' || v_count);


-- A bind variable trata o valor como literal, não como código SQL
END;
/

26.6 Performance e Otimização

26.6.1 Reutilização de Cursores

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;

TYPE t_clientes IS TABLE OF NUMBER INDEX BY PLS_INTEGER;


v_clientes t_clientes;
BEGIN
-- Preparar lista de clientes para atualizar
v_clientes(1) := 101;
v_clientes(2) := 102;
v_clientes(3) := 103;

-- Abrir e preparar cursor uma vez


v_cursor_id := DBMS_SQL.OPEN_CURSOR;
v_sql := \'UPDATE Clientes SET DataUltimaAtualizacao = SYSDATE WHERE ClienteID = :cliente_id\';
DBMS_SQL.PARSE(v_cursor_id, v_sql, DBMS_SQL.NATIVE);

-- Executar múltiplas vezes com diferentes bind variables


FOR i IN v_clientes.FIRST..v_clientes.LAST LOOP
DBMS_SQL.BIND_VARIABLE(v_cursor_id, \':cliente_id\', v_clientes(i));
v_rows_processed := DBMS_SQL.EXECUTE(v_cursor_id);
DBMS_OUTPUT.PUT_LINE(\'Cliente \' || v_clientes(i) || \': \' || v_rows_processed || \'
linha(s) atualizada(s)\');
END LOOP;

DBMS_SQL.CLOSE_CURSOR(v_cursor_id);
COMMIT;
END;
/

26.7 Tópicos Chave do Capítulo 26

SQL Dinâmico: Construção e execução de SQL em tempo de execução.

EXECUTE IMMEDIATE : Forma mais simples de SQL dinâmico (Native Dynamic SQL).

Bind Variables: Essenciais para performance e segurança ( USING clause).

DBMS_SQL : Package para controle avançado de SQL dinâmico.

Casos de Uso: Nomes de objetos variáveis, consultas flexíveis, DDL dinâmico.

Segurança: Sempre usar bind variables para prevenir SQL Injection.

Performance: Reutilizar cursores, evitar concatenação de strings.

REF CURSOR: Útil para retornar resultados dinâmicos de procedures.

26.8 Exercícios do Capítulo 26

1. DDL Dinâmico Básico:

Crie um bloco que aceite um nome de tabela como variável e crie uma tabela de backup com o
sufixo "_BACKUP".

A tabela de backup deve ter a mesma estrutura da tabela original.

2. Consulta Dinâmica com Filtros:

Crie uma procedure que aceite parâmetros opcionais para filtrar produtos (categoria, preço
mínimo, preço máximo).

Construa a consulta dinamicamente baseada nos parâmetros fornecidos.


3. UPDATE Dinâmico:

Crie uma procedure que aceite o nome de uma tabela, nome de uma coluna, novo valor e condição
WHERE.

Execute um UPDATE dinâmico usando esses parâmetros com bind variables.

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.

5. Desafio - Ferramenta de Administração:

Crie uma procedure executar_comando_seguro que:


Aceite uma string SQL como parâmetro.

Valide que o comando é seguro (apenas SELECT, INSERT, UPDATE, DELETE permitidos).

Execute o comando e retorne o número de linhas afetadas.

Implemente log de auditoria para todos os comandos executados.

Use tratamento de exceções robusto.

Referências

[1] Oracle. PL/SQL Language Reference - Dynamic SQL. Disponível em:


[Link]

Capítulo 27: Otimização de Performance em SQL e


PL/SQL

27.1 Introdução à Otimização de Performance

A otimização de performance é um aspecto crítico no desenvolvimento de aplicações de banco de dados.


Uma consulta mal otimizada pode transformar uma aplicação rápida em uma experiência frustrante para o
usuário, especialmente quando lidamos com grandes volumes de dados. O Oracle Database oferece um
conjunto robusto de ferramentas e técnicas para identificar, analisar e resolver problemas de performance [1].

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.

27.1.1 Princípios Fundamentais de Performance

Minimize o Trabalho: Execute apenas as operações necessárias.


Use Índices Eficientemente: Índices são fundamentais para consultas rápidas.

Evite Operações Custosas: Como ordenações desnecessárias e funções em cláusulas WHERE.

Processe Menos Dados: Filtre dados o mais cedo possível.

Reutilize Recursos: Como cursores e planos de execução.

Monitore Continuamente: Performance é um processo contínuo, não um evento único.

27.2 Planos de Execução

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.

27.2.1 Visualizando Planos de Execução

Usando EXPLAIN PLAN:

-- Gerar plano de execução


EXPLAIN PLAN FOR
SELECT [Link], [Link], SUM([Link] * [Link]) AS TotalVendas
FROM Clientes c
INNER JOIN Vendas v ON [Link] = [Link]
WHERE [Link] = \'SP\'
GROUP BY [Link], [Link], [Link]
ORDER BY TotalVendas DESC;

-- Visualizar o plano
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

Usando AUTOTRACE:

SET AUTOTRACE ON EXPLAIN STATISTICS

SELECT [Link], SUM([Link] * [Link]) AS TotalVendas


FROM Clientes c
INNER JOIN Vendas v ON [Link] = [Link]
WHERE [Link] = \'SP\'
GROUP BY [Link], [Link]
ORDER BY TotalVendas DESC;

SET AUTOTRACE OFF

27.2.2 Interpretando Planos de Execução

Elementos importantes em um plano de execução:

Operation: O tipo de operação (TABLE ACCESS, INDEX SCAN, HASH JOIN, etc.)

Cost: Estimativa do custo da operação (menor é melhor)

Rows: Número estimado de linhas processadas

Bytes: Quantidade estimada de dados processados

Time: Tempo estimado de execução


-- Exemplo de análise de plano
EXPLAIN PLAN FOR
SELECT [Link], [Link]
FROM Produtos p
WHERE [Link] = \'Eletrônicos\'
AND [Link] > 1000;

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;

27.3 Otimização de Consultas SQL

27.3.1 Uso Eficiente de Índices

Criando Índices Apropriados:

-- Índice para consultas por categoria


CREATE INDEX idx_produtos_categoria ON Produtos(Categoria);

-- Índice composto para consultas por categoria e preço


CREATE INDEX idx_produtos_cat_preco ON Produtos(Categoria, Preco);

-- Índice para JOINs


CREATE INDEX idx_vendas_cliente ON Vendas(ClienteID);
CREATE INDEX idx_vendas_produto ON Vendas(ProdutoID);

Consultas que Usam Índices Eficientemente:

-- BOM: Usa índice em Categoria


SELECT * FROM Produtos WHERE Categoria = \'Eletrônicos\';

-- BOM: Usa índice composto


SELECT * FROM Produtos
WHERE Categoria = \'Eletrônicos\' AND Preco > 1000;

-- RUIM: Função na coluna impede uso do índice


SELECT * FROM Produtos WHERE UPPER(Categoria) = \'ELETRÔNICOS\';

-- MELHOR: Usar índice funcional ou normalizar dados


CREATE INDEX idx_produtos_cat_upper ON Produtos(UPPER(Categoria));
SELECT * FROM Produtos WHERE UPPER(Categoria) = \'ELETRÔNICOS\';

27.3.2 Otimização de JOINs

Ordem dos JOINs:

-- Consulta com múltiplos JOINs - ordem importa


SELECT [Link], [Link], [Link]
FROM Clientes c
INNER JOIN Vendas v ON [Link] = [Link] -- Tabela menor primeiro
INNER JOIN Produtos p ON [Link] = [Link]
WHERE [Link] = \'SP\' -- Filtro mais seletivo primeiro
AND [Link] >= DATE \'2025-01-01\';

Usando Hints para Controlar JOINs:


-- Forçar uso de HASH JOIN
SELECT /*+ USE_HASH(c,v) */ [Link], SUM([Link] * [Link])
FROM Clientes c
INNER JOIN Vendas v ON [Link] = [Link]
GROUP BY [Link], [Link];

-- Forçar ordem específica de JOIN


SELECT /*+ ORDERED */ [Link], [Link]
FROM Clientes c, Vendas v, Produtos p
WHERE [Link] = [Link]
AND [Link] = [Link];

27.3.3 Otimização de Subconsultas

Transformar Subconsultas em JOINs:

-- MENOS EFICIENTE: Subconsulta correlacionada


SELECT [Link]
FROM Clientes c
WHERE EXISTS (
SELECT 1 FROM Vendas v
WHERE [Link] = [Link]
AND [Link] >= DATE \'2025-01-01\'
);

-- MAIS EFICIENTE: JOIN


SELECT DISTINCT [Link]
FROM Clientes c
INNER JOIN Vendas v ON [Link] = [Link]
WHERE [Link] >= DATE \'2025-01-01\';

Usando WITH (CTE) para Melhor Performance:

-- Evitar recálculo de subconsultas


WITH VendasRecentes AS (
SELECT ClienteID, SUM(Quantidade * PrecoUnitario) AS TotalVendas
FROM Vendas
WHERE DataVenda >= DATE \'2025-01-01\'
GROUP BY ClienteID
),
ClientesAtivos AS (
SELECT ClienteID, Nome
FROM Clientes
WHERE Status = \'Ativo\'
)
SELECT [Link], [Link]
FROM ClientesAtivos ca
INNER JOIN VendasRecentes vr ON [Link] = [Link]
WHERE [Link] > 5000;

27.4 Otimização de PL/SQL

27.4.1 Processamento em Lote (Bulk Operations)

FORALL para DML em Lote:


DECLARE
TYPE t_cliente_ids IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
TYPE t_novos_emails IS TABLE OF VARCHAR2(255) INDEX BY PLS_INTEGER;

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]\';

-- Atualização em lote - muito mais rápido que loop individual


FORALL i IN v_cliente_ids.FIRST..v_cliente_ids.LAST
UPDATE Clientes
SET Email = v_novos_emails(i)
WHERE ClienteID = v_cliente_ids(i);

DBMS_OUTPUT.PUT_LINE(\'Linhas atualizadas: \' || SQL%ROWCOUNT);


COMMIT;
END;
/

BULK COLLECT para Consultas em Lote:

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;

DBMS_OUTPUT.PUT_LINE(\'Total de clientes processados: \' || v_clientes.COUNT);


END;
/

BULK COLLECT com LIMIT:

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;

-- Processar o lote atual


FOR i IN 1..v_vendas.COUNT LOOP
-- Processar cada venda...
NULL;
END LOOP;

EXIT WHEN v_vendas.COUNT < 1000; -- Último lote


END LOOP;
CLOSE cur_vendas;
END;
/
27.4.2 Otimização de Loops

Evitar Consultas Dentro de Loops:

-- INEFICIENTE: Consulta dentro do loop


DECLARE
v_nome_produto VARCHAR2(255);
BEGIN
FOR venda IN (SELECT * FROM Vendas) LOOP
SELECT NomeProduto INTO v_nome_produto
FROM Produtos
WHERE ProdutoID = [Link];

DBMS_OUTPUT.PUT_LINE(\'Venda: \' || [Link] || \' - Produto: \' || v_nome_produto);


END LOOP;
END;
/

-- EFICIENTE: JOIN para evitar consultas repetidas


BEGIN
FOR rec IN (
SELECT [Link], [Link]
FROM Vendas v
INNER JOIN Produtos p ON [Link] = [Link]
) LOOP
DBMS_OUTPUT.PUT_LINE(\'Venda: \' || [Link] || \' - Produto: \' || [Link]);
END LOOP;
END;
/

27.4.3 Cache de Dados em PL/SQL

Usando Package Variables para Cache:


CREATE OR REPLACE PACKAGE pkg_cache_produtos
IS
TYPE t_produto_cache IS TABLE OF Produtos%ROWTYPE INDEX BY PLS_INTEGER;
g_cache_produtos t_produto_cache;
g_cache_carregado BOOLEAN := FALSE;

FUNCTION obter_produto(p_produto_id NUMBER) RETURN Produtos%ROWTYPE;


PROCEDURE carregar_cache;
END pkg_cache_produtos;
/

CREATE OR REPLACE PACKAGE BODY pkg_cache_produtos


IS
PROCEDURE carregar_cache
IS
BEGIN
IF NOT g_cache_carregado THEN
FOR produto IN (SELECT * FROM Produtos) LOOP
g_cache_produtos([Link]) := produto;
END LOOP;
g_cache_carregado := TRUE;
DBMS_OUTPUT.PUT_LINE(\'Cache de produtos carregado.\');
END IF;
END carregar_cache;

FUNCTION obter_produto(p_produto_id NUMBER) RETURN Produtos%ROWTYPE


IS
BEGIN
carregar_cache;

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;
/

27.5 Monitoramento de Performance

27.5.1 Estatísticas de Execução

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;

-- Operação a ser medida


FOR i IN 1..100000 LOOP
NULL; -- Simular processamento
END LOOP;

v_end_time := DBMS_UTILITY.GET_TIME;
v_elapsed_time := v_end_time - v_start_time;

DBMS_OUTPUT.PUT_LINE(\'Tempo decorrido: \' || v_elapsed_time || \' centésimos de segundo\');


END;
/

Usando TIMESTAMP para Medições Precisas:

DECLARE
v_start_time TIMESTAMP;
v_end_time TIMESTAMP;
v_elapsed INTERVAL DAY TO SECOND;
BEGIN
v_start_time := SYSTIMESTAMP;

-- Operação a ser medida


SELECT COUNT(*) FROM Vendas v
INNER JOIN Clientes c ON [Link] = [Link];

v_end_time := SYSTIMESTAMP;
v_elapsed := v_end_time - v_start_time;

DBMS_OUTPUT.PUT_LINE(\'Tempo decorrido: \' || v_elapsed);


END;
/
27.5.2 Análise de Estatísticas de Sessão

-- Criar procedure para capturar estatísticas


CREATE OR REPLACE PROCEDURE capturar_estatisticas (
p_nome_teste VARCHAR2,
p_sql_statement CLOB
)
IS
v_start_time TIMESTAMP;
v_end_time TIMESTAMP;
v_logical_reads_before NUMBER;
v_logical_reads_after NUMBER;
v_physical_reads_before NUMBER;
v_physical_reads_after NUMBER;
BEGIN
-- Capturar estatísticas iniciais
SELECT value INTO v_logical_reads_before
FROM v$`mystat s, v`$statname n
WHERE [Link]# = [Link]#
AND [Link] = \'session logical reads\';

SELECT value INTO v_physical_reads_before


FROM v$`mystat s, v`$statname n
WHERE [Link]# = [Link]#
AND [Link] = \'physical reads\';

v_start_time := SYSTIMESTAMP;

-- Executar SQL
EXECUTE IMMEDIATE p_sql_statement;

v_end_time := SYSTIMESTAMP;

-- Capturar estatísticas finais


SELECT value INTO v_logical_reads_after
FROM v$`mystat s, v`$statname n
WHERE [Link]# = [Link]#
AND [Link] = \'session logical reads\';

SELECT value INTO v_physical_reads_after


FROM v$`mystat s, v`$statname n
WHERE [Link]# = [Link]#
AND [Link] = \'physical reads\';

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

27.6.1 Particionamento de Consultas

-- Processar dados em partições para melhor performance


DECLARE
v_data_inicio DATE := DATE \'2024-01-01\';
v_data_fim DATE := DATE \'2024-12-31\';
v_data_atual DATE;
v_total_vendas NUMBER := 0;
v_vendas_mes NUMBER;
BEGIN
v_data_atual := v_data_inicio;

WHILE v_data_atual <= v_data_fim LOOP


-- Processar um mês por vez
SELECT NVL(SUM(Quantidade * PrecoUnitario), 0)
INTO v_vendas_mes
FROM Vendas
WHERE DataVenda >= v_data_atual
AND DataVenda < ADD_MONTHS(v_data_atual, 1);

v_total_vendas := v_total_vendas + v_vendas_mes;

DBMS_OUTPUT.PUT_LINE(
TO_CHAR(v_data_atual, \'MM/YYYY\') || \': R$ \' || v_vendas_mes
);

v_data_atual := ADD_MONTHS(v_data_atual, 1);


END LOOP;

DBMS_OUTPUT.PUT_LINE(\'Total do ano: R$ \' || v_total_vendas);


END;
/

27.6.2 Uso de Materialized Views

-- Criar Materialized View para consultas frequentes


CREATE MATERIALIZED VIEW mv_vendas_por_cliente
BUILD IMMEDIATE
REFRESH COMPLETE ON DEMAND
AS
SELECT
[Link],
[Link],
[Link],
COUNT([Link]) AS NumeroVendas,
SUM([Link] * [Link]) AS TotalVendas,
AVG([Link] * [Link]) AS TicketMedio,
MAX([Link]) AS UltimaVenda
FROM Clientes c
LEFT JOIN Vendas v ON [Link] = [Link]
GROUP BY [Link], [Link], [Link];

-- Usar a Materialized View em consultas


SELECT Nome, TotalVendas
FROM mv_vendas_por_cliente
WHERE Estado = \'SP\'
AND TotalVendas > 10000
ORDER BY TotalVendas DESC;

-- Atualizar a Materialized View


EXEC DBMS_MVIEW.REFRESH(\'mv_vendas_por_cliente\');

27.7 Tópicos Chave do Capítulo 27

Planos de Execução: Fundamentais para entender como o Oracle executa consultas.


Índices: Criação e uso eficiente para acelerar consultas.

JOINs: Otimização através de ordem, hints e estrutura de consulta.

Bulk Operations: FORALL e BULK COLLECT para processamento em lote.

Cache em PL/SQL: Usar package variables para evitar consultas repetidas.

Monitoramento: Medir performance com timestamps e estatísticas de sistema.

Técnicas Avançadas: Particionamento, Materialized Views, hints.

Princípios: Minimizar trabalho, processar menos dados, reutilizar recursos.

27.8 Exercícios do Capítulo 27

1. Análise de Plano de Execução:

Execute EXPLAIN PLAN para uma consulta complexa com JOINs.

Identifique operações custosas no plano.

Crie índices para melhorar a performance e compare os planos.

2. Otimização de Loop:

Crie um bloco PL/SQL que processe todas as vendas e calcule comissões.

Primeira versão: use cursor com consultas individuais dentro do loop.

Segunda versão: otimize usando JOIN e BULK COLLECT.

Compare os tempos de execução.

3. Cache de Dados:

Implemente um package de cache para dados de produtos.

Crie functions para buscar produtos pelo ID usando o cache.

Meça a diferença de performance entre acesso direto à tabela e cache.

4. Bulk Operations:

Crie um procedure que atualize preços de produtos em lote.

Use FORALL para atualizar milhares de registros eficientemente.

Compare com uma versão que usa loop tradicional.

5. Desafio - Sistema de Monitoramento:

Crie uma procedure que aceite um SQL dinâmico e execute-o.

Antes e depois da execução, capture estatísticas de sessão (logical reads, physical reads, CPU time).

Exiba um relatório comparativo para avaliar a performance do SQL.


Referências

[1] Oracle. Database Performance Tuning Guide. Disponível em:


[Link]

Capítulo 28: Tópicos Avançados e Especializados

28.1 Introdução aos Tópicos Avançados

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].

Os tópicos cobertos neste capítulo representam o conhecimento que diferencia um desenvolvedor


intermediário de um expert. Eles incluem técnicas para manipulação de dados hierárquicos, processamento
de XML e JSON, expressões regulares, e funcionalidades específicas do Oracle que expandem
significativamente as capacidades de desenvolvimento.

28.2 Consultas Hierárquicas (CONNECT BY)

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.

28.2.1 Sintaxe Básica

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

-- Criar tabela de funcionários com hierarquia


CREATE TABLE Funcionarios (
FuncionarioID NUMBER PRIMARY KEY,
Nome VARCHAR2(100),
Cargo VARCHAR2(50),
GerenteID NUMBER,
Salario NUMBER(10,2),
FOREIGN KEY (GerenteID) REFERENCES Funcionarios(FuncionarioID)
);

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);

-- Consulta hierárquica completa


SELECT
LEVEL AS Nivel,
LPAD(\' \', (LEVEL-1)*2) || Nome AS Hierarquia,
Cargo,
Salario,
SYS_CONNECT_BY_PATH(Nome, \' -> \') AS Caminho
FROM Funcionarios
START WITH GerenteID IS NULL -- Começar pelo CEO
CONNECT BY PRIOR FuncionarioID = GerenteID -- Conectar pai com filhos
ORDER SIBLINGS BY Nome;

28.2.3 Funções Hierárquicas Especiais

-- Consulta com todas as funções hierárquicas


SELECT
Nome,
Cargo,
LEVEL AS Nivel,
CONNECT_BY_ROOT Nome AS CEO,
CONNECT_BY_ISLEAF AS EhFolha,
CONNECT_BY_ISCYCLE AS TemCiclo,
SYS_CONNECT_BY_PATH(Nome, \'/\') AS CaminhoCompleto,
PRIOR Nome AS NomeGerente
FROM Funcionarios
START WITH GerenteID IS NULL
CONNECT BY PRIOR FuncionarioID = GerenteID
ORDER SIBLINGS BY Salario DESC;

-- Encontrar todos os subordinados de um gerente específico


SELECT
LPAD(\' \', (LEVEL-1)*2) || Nome AS Subordinados,
Cargo,
Salario
FROM Funcionarios
START WITH FuncionarioID = 2 -- Maria Santos (Diretora TI)
CONNECT BY PRIOR FuncionarioID = GerenteID;

28.3 Expressões Regulares

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

REGEXP_LIKE : Testa se uma string corresponde a um padrão

REGEXP_SUBSTR : Extrai substring que corresponde a um padrão

REGEXP_REPLACE : Substitui texto que corresponde a um padrão

REGEXP_INSTR : Retorna a posição de um padrão na string

REGEXP_COUNT : Conta ocorrências de um padrão

28.3.2 Exemplos Práticos

-- Validação de email usando REGEXP_LIKE


SELECT Nome, Email
FROM Clientes
WHERE REGEXP_LIKE(Email, \'^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$\');

-- Extrair números de telefone formatados


SELECT
Nome,
Telefone,
REGEXP_SUBSTR(Telefone, \'\\d{2}\') AS DDD,
REGEXP_SUBSTR(Telefone, \'\\d{4,5}-?\\d{4}\') AS Numero
FROM Clientes
WHERE REGEXP_LIKE(Telefone, \'^\\(\\d{2}\\)\\s?\\d{4,5}-?\\d{4}$\');

-- Limpar e formatar dados


UPDATE Clientes
SET Telefone = REGEXP_REPLACE(Telefone, \'[^0-9]\', \'\')
WHERE REGEXP_LIKE(Telefone, \'[^0-9]\');

-- Encontrar padrões complexos


SELECT
ProdutoID,
NomeProduto,
REGEXP_COUNT(NomeProduto, \'\\b[A-Z][a-z]+\\b\') AS NumPalavrasCapitalizadas
FROM Produtos
WHERE REGEXP_LIKE(NomeProduto, \'^[A-Z].*[0-9]$\'); -- Começa com maiúscula e termina com número
28.3.3 Validação de Dados com Expressões Regulares

-- Criar função de validação de CPF


CREATE OR REPLACE FUNCTION validar_cpf(p_cpf VARCHAR2) RETURN BOOLEAN
IS
BEGIN
-- Verificar formato: 999.999.999-99 ou 99999999999
IF NOT REGEXP_LIKE(p_cpf, \'^\\d{3}\\.\\d{3}\\.\\d{3}-\\d{2}$`|^\\d{11}`$\') THEN
RETURN FALSE;
END IF;

-- Aqui poderia incluir validação do dígito verificador


RETURN TRUE;
END validar_cpf;
/

-- Usar a função em uma constraint


ALTER TABLE Clientes ADD CONSTRAINT chk_cpf_formato
CHECK (validar_cpf(CPF) = 1);

-- Exemplos de padrões úteis


SELECT
\'Validação de CEP\' AS Tipo,
CASE WHEN REGEXP_LIKE(\'01234-567\', \'^\\d{5}-?\\d{3}$\')
THEN \'Válido\' ELSE \'Inválido\' END AS Resultado
FROM DUAL
UNION ALL
SELECT
\'Validação de Placa\',
CASE WHEN REGEXP_LIKE(\'ABC-1234\', \'^[A-Z]{3}-?\\d{4}$\')
THEN \'Válido\' ELSE \'Inválido\' END
FROM DUAL;

28.4 Processamento de XML

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

-- Criar tabela com coluna XML


CREATE TABLE DocumentosXML (
DocumentoID NUMBER PRIMARY KEY,
Nome VARCHAR2(100),
Conteudo XMLType
);

-- Inserir dados XML


INSERT INTO DocumentosXML VALUES (
1,
\'Pedido 001\',
XMLType(\'<?xml version="1.0"?>
<pedido id="001">
<cliente>
<nome>João Silva</nome>
<email>joao@[Link]</email>
</cliente>
<itens>
<item>
<produto>Notebook</produto>
<quantidade>1</quantidade>
<preco>2500.00</preco>
</item>
<item>
<produto>Mouse</produto>
<quantidade>2</quantidade>
<preco>50.00</preco>
</item>
</itens>
</pedido>\')
);

-- Consultar dados XML usando XPath


SELECT
DocumentoID,
Nome,
[Link](\'//cliente/nome/text()\').getStringVal() AS NomeCliente,
[Link](\'//cliente/email/text()\').getStringVal() AS EmailCliente
FROM DocumentosXML;

-- Extrair múltiplos itens


SELECT
DocumentoID,
[Link],
[Link],
[Link]
FROM DocumentosXML d,
XMLTable(\'/pedido/itens/item\'
PASSING [Link]
COLUMNS
produto VARCHAR2(100) PATH \'produto\',
quantidade NUMBER PATH \'quantidade\',
preco NUMBER(10,2) PATH \'preco\'
) item;
28.4.2 Gerando XML a partir de Dados Relacionais

-- Gerar XML a partir de consulta SQL


SELECT XMLElement("clientes",
XMLAgg(
XMLElement("cliente",
XMLAttributes(ClienteID AS "id"),
XMLElement("nome", Nome),
XMLElement("email", Email),
XMLElement("estado", Estado)
)
)
).getClobVal() AS XML_Clientes
FROM Clientes
WHERE Estado = \'SP\';

-- Gerar XML hierárquico com vendas


SELECT XMLElement("relatorio_vendas",
XMLAgg(
XMLElement("cliente",
XMLAttributes([Link] AS "id"),
XMLElement("nome", [Link]),
XMLElement("vendas",
(SELECT XMLAgg(
XMLElement("venda",
XMLAttributes([Link] AS "id"),
XMLElement("data", TO_CHAR([Link], \'DD/MM/YYYY\')),
XMLElement("valor", [Link] * [Link])
)
)
FROM Vendas v
WHERE [Link] = [Link])
)
)
)
).getClobVal() AS XML_Relatorio
FROM Clientes c
WHERE [Link] = \'RJ\';

28.5 Processamento de JSON

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

-- Criar tabela com dados JSON


CREATE TABLE ProdutosJSON (
ProdutoID NUMBER PRIMARY KEY,
Dados JSON
);

-- Inserir dados JSON


INSERT INTO ProdutosJSON VALUES (1, \'{
"nome": "Smartphone Galaxy",
"categoria": "Eletrônicos",
"preco": 1200.00,
"especificacoes": {
"tela": "6.1 polegadas",
"memoria": "128GB",
"camera": "48MP"
},
"cores": ["Preto", "Branco", "Azul"],
"disponivel": true
}\');

INSERT INTO ProdutosJSON VALUES (2, \'{


"nome": "Notebook Dell",
"categoria": "Informática",
"preco": 2800.00,
"especificacoes": {
"processador": "Intel i7",
"memoria": "16GB RAM",
"armazenamento": "512GB SSD"
},
"cores": ["Prata"],
"disponivel": false
}\');

-- Consultar dados JSON


SELECT
ProdutoID,
JSON_VALUE(Dados, \'$.nome\') AS Nome,
JSON_VALUE(Dados, \'$.categoria\') AS Categoria,
JSON_VALUE(Dados, \'$.preco\') AS Preco,
JSON_VALUE(Dados, \'$.disponivel\') AS Disponivel
FROM ProdutosJSON;

-- Consultar arrays JSON


SELECT
ProdutoID,
JSON_VALUE(Dados, \'$.nome\') AS Nome,
cor.COLUMN_VALUE AS Cor
FROM ProdutosJSON p,
JSON_TABLE([Link], \'$`.cores[*]\'' COLUMNS (COLUMN_VALUE VARCHAR2(50) PATH \'`$\')) cor;

-- Consultar objetos aninhados


SELECT
ProdutoID,
JSON_VALUE(Dados, \'$.nome\') AS Nome,
JSON_VALUE(Dados, \'$.[Link]\') AS Memoria,
JSON_VALUE(Dados, \'$.[Link]\') AS Tela
FROM ProdutosJSON
WHERE JSON_VALUE(Dados, \'$.categoria\') = \'Eletrônicos\';
28.5.2 Atualizando Dados JSON

-- Atualizar valores JSON


UPDATE ProdutosJSON
SET Dados = JSON_MERGEPATCH(Dados, \'{"preco": 1100.00, "promocao": true}\' )
WHERE ProdutoID = 1;

-- Adicionar elemento a array


UPDATE ProdutosJSON
SET Dados = JSON_MERGEPATCH(Dados, \'{"cores": ["Preto", "Branco", "Azul", "Vermelho"]}\' )
WHERE ProdutoID = 1;

-- Verificar resultado
SELECT JSON_SERIALIZE(Dados PRETTY) FROM ProdutosJSON WHERE ProdutoID = 1;

28.6 Tipos de Dados Avançados

28.6.1 Collections (Coleções)

-- 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\';

-- Iterar sobre a coleção


FOR i IN 1..v_nomes.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(i || \': \' || v_nomes(i));
END LOOP;
END;
/

-- VARRAY (Variable Array)


DECLARE
TYPE t_array_numeros IS VARRAY(10) OF NUMBER;
v_numeros t_array_numeros;
BEGIN
v_numeros := t_array_numeros(1, 2, 3, 4, 5);

FOR i IN 1..v_numeros.COUNT LOOP


DBMS_OUTPUT.PUT_LINE(\'Número \' || i || \': \' || v_numeros(i));
END LOOP;
END;
/

-- Associative Array (Index-by Table)


DECLARE
TYPE t_salarios IS TABLE OF NUMBER INDEX BY VARCHAR2(100);
v_salarios t_salarios;
v_nome VARCHAR2(100);
BEGIN
v_salarios(\'João\') := 5000;
v_salarios(\'Maria\') := 6000;
v_salarios(\'Pedro\') := 4500;

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

-- Criar tipo de objeto


CREATE OR REPLACE TYPE t_endereco AS OBJECT (
logradouro VARCHAR2(100),
numero NUMBER,
cidade VARCHAR2(50),
cep VARCHAR2(10),

-- Método para formatar endereço


MEMBER FUNCTION formatar RETURN VARCHAR2
);
/

CREATE OR REPLACE TYPE BODY t_endereco AS


MEMBER FUNCTION formatar RETURN VARCHAR2 IS
BEGIN
RETURN logradouro || \', \' || numero || \' - \' || cidade || \' - \' || cep;
END formatar;
END;
/

-- Usar o tipo de objeto


DECLARE
v_endereco t_endereco;
BEGIN
v_endereco := t_endereco(\'Rua das Flores\', 123, \'São Paulo\', \'01234-567\');
DBMS_OUTPUT.PUT_LINE(\'Endereço: \' || v_endereco.formatar());
END;
/

-- Criar tabela com tipo de objeto


CREATE TABLE ClientesCompletos (
ClienteID NUMBER PRIMARY KEY,
Nome VARCHAR2(100),
Endereco t_endereco
);

INSERT INTO ClientesCompletos VALUES (


1,
\'João Silva\',
t_endereco(\'Av. Paulista\', 1000, \'São Paulo\', \'01310-100\')
);

28.7 Tópicos Chave do Capítulo 28

Consultas Hierárquicas: CONNECT BY para dados em árvore, funções como LEVEL ,


SYS_CONNECT_BY_PATH .

Expressões Regulares: REGEXP_LIKE , REGEXP_SUBSTR , REGEXP_REPLACE para processamento


avançado de texto.

XML: Tipo XMLType , funções extract() , XMLTable para dados semi-estruturados.

JSON: Suporte nativo com JSON_VALUE , JSON_TABLE , JSON_MERGEPATCH .

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.

Aplicações: Validação de dados, processamento de documentos, estruturas hierárquicas.

28.8 Exercícios do Capítulo 28

1. Consulta Hierárquica:
Crie uma estrutura de categorias de produtos (categoria pai -> subcategoria).

Use CONNECT BY para exibir a hierarquia completa com indentação.

Calcule o total de produtos em cada nível da hierarquia.

2. Expressões Regulares:

Crie validações usando regex para: telefone, CPF, email, CEP.

Implemente uma função que extraia e formate números de telefone de texto livre.

Use regex para limpar e padronizar dados de endereço.

3. Processamento XML:

Crie uma tabela para armazenar pedidos em formato XML.

Implemente procedures para inserir e consultar dados XML.

Gere relatórios XML a partir de dados relacionais.

4. JSON:

Modele um catálogo de produtos usando JSON para especificações técnicas.

Implemente busca por características específicas nos dados JSON.

Crie procedures para atualizar propriedades JSON dinamicamente.

5. Desafio - Sistema Híbrido:

Crie um sistema que combine:


Dados relacionais tradicionais (clientes, produtos).

Dados hierárquicos (estrutura organizacional).

Dados JSON (configurações e metadados).

Validação com expressões regulares.

Implemente relatórios que integrem todos esses tipos de dados.

Use Object Types para encapsular lógica de negócio complexa.

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

29.1 Introdução à Integração de Sistemas

A integração entre o Oracle Database e aplicações externas é fundamental no desenvolvimento de sistemas


empresariais modernos. O Oracle oferece diversas tecnologias e ferramentas para facilitar essa integração,
permitindo que o banco de dados se comunique eficientemente com aplicações web, serviços REST, sistemas
de arquivos, e outras bases de dados. Este capítulo explora as principais técnicas e ferramentas disponíveis
para criar soluções integradas robustas [1].

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

29.2 Integração com Serviços Web e APIs REST

29.2.1 Usando UTL_HTTP para Requisições HTTP

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);

-- Ler conteúdo da resposta


DBMS_LOB.CREATETEMPORARY(v_resultado, FALSE);
BEGIN
LOOP
UTL_HTTP.READ_TEXT(v_response, v_buffer, 32767);
DBMS_LOB.WRITEAPPEND(v_resultado, LENGTH(v_buffer), v_buffer);
END LOOP;
EXCEPTION
WHEN UTL_HTTP.END_OF_BODY THEN
NULL; -- Fim normal da resposta
END;

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;
/

-- Exemplo de uso: consultar API de CEP


DECLARE
v_cep VARCHAR2(10) := \'01310-100\';
v_url VARCHAR2(500);
v_resposta CLOB;
v_logradouro VARCHAR2(200);
v_cidade VARCHAR2(100);
BEGIN
v_url := \'[Link] || REPLACE(v_cep, \'-\\\', \'\') || \'/json/\';

v_resposta := fazer_requisicao_get(v_url);

-- Extrair dados do JSON (Oracle 12c+)


v_logradouro := JSON_VALUE(v_resposta, \'$.logradouro\');
v_cidade := JSON_VALUE(v_resposta, \'$.localidade\');

DBMS_OUTPUT.PUT_LINE(\'CEP: \' || v_cep);


DBMS_OUTPUT.PUT_LINE(\'Logradouro: \' || v_logradouro);
DBMS_OUTPUT.PUT_LINE(\'Cidade: \' || v_cidade);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(\'Erro ao consultar CEP: \' || SQLERRM);
END;
/
29.2.2 Enviando Dados via POST

-- Procedure para enviar dados via POST


CREATE OR REPLACE PROCEDURE enviar_dados_post (
p_url VARCHAR2,
p_dados CLOB,
p_content_type VARCHAR2 DEFAULT \'application/json\'
)
IS
v_request UTL_HTTP.REQ;
v_response UTL_HTTP.RESP;
v_buffer VARCHAR2(32767);
v_status_code NUMBER;
BEGIN
-- Iniciar requisição POST
v_request := UTL_HTTP.BEGIN_REQUEST(p_url, \'POST\');

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

DBMS_OUTPUT.PUT_LINE(\'Status da resposta: \' || v_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;
/

-- Exemplo: enviar dados de cliente para API externa


DECLARE
v_dados_cliente CLOB;
BEGIN
-- Construir JSON com dados do cliente
SELECT JSON_OBJECT(
\'id\' VALUE ClienteID,
\'nome\' VALUE Nome,
\'email\' VALUE Email,
\'estado\' VALUE Estado
) INTO v_dados_cliente
FROM Clientes
WHERE ClienteID = 101;

-- Enviar para API externa (URL fictícia)


enviar_dados_post(
\'[Link]
v_dados_cliente
);
END;
/
29.3 Integração com Sistema de Arquivos

29.3.1 Usando UTL_FILE para Operações de Arquivo

-- Configurar diretório no Oracle (como DBA)


-- CREATE OR REPLACE DIRECTORY TEMP_DIR AS \'/tmp/oracle_files\';
-- GRANT READ, WRITE ON DIRECTORY TEMP_DIR TO seu_usuario;

-- Procedure para exportar dados para CSV


CREATE OR REPLACE PROCEDURE exportar_clientes_csv (
p_nome_arquivo VARCHAR2 DEFAULT \'clientes_export.csv\'
)
IS
v_arquivo UTL_FILE.FILE_TYPE;
v_linha VARCHAR2(4000);

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);

DBMS_OUTPUT.PUT_LINE(\'Arquivo \' || p_nome_arquivo || \' criado com sucesso.\');


EXCEPTION
WHEN OTHERS THEN
IF UTL_FILE.IS_OPEN(v_arquivo) THEN
UTL_FILE.FCLOSE(v_arquivo);
END IF;
DBMS_OUTPUT.PUT_LINE(\'Erro ao criar arquivo: \' || SQLERRM);
END exportar_clientes_csv;
/

-- Procedure para importar dados de CSV


CREATE OR REPLACE PROCEDURE importar_produtos_csv (
p_nome_arquivo VARCHAR2
)
IS
v_arquivo UTL_FILE.FILE_TYPE;
v_linha VARCHAR2(4000);
v_contador NUMBER := 0;
v_erros NUMBER := 0;

-- Variáveis para parsing


v_produto_id NUMBER;
v_nome_produto VARCHAR2(255);
v_preco NUMBER;
v_categoria VARCHAR2(100);
BEGIN
-- Abrir arquivo para leitura
v_arquivo := UTL_FILE.FOPEN(\'TEMP_DIR\', p_nome_arquivo, \'R\');

-- 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);

-- Parse simples (assumindo formato: ID,Nome,Preco,Categoria)


v_produto_id := TO_NUMBER(REGEXP_SUBSTR(v_linha, \'[^,]+\', 1, 1));
v_nome_produto := TRIM(\'"\' FROM REGEXP_SUBSTR(v_linha, \'[^,]+\', 1, 2));
v_preco := TO_NUMBER(REGEXP_SUBSTR(v_linha, \'[^,]+\', 1, 3));
v_categoria := TRIM(\'"\' FROM REGEXP_SUBSTR(v_linha, \'[^,]+\', 1, 4));

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

-- Procedure para analisar logs de acesso


CREATE OR REPLACE PROCEDURE analisar_log_acesso (
p_nome_arquivo VARCHAR2
)
IS
v_arquivo UTL_FILE.FILE_TYPE;
v_linha VARCHAR2(4000);
v_ip VARCHAR2(15);
v_timestamp VARCHAR2(30);
v_metodo VARCHAR2(10);
v_url VARCHAR2(500);
v_status NUMBER;
v_contador NUMBER := 0;

TYPE t_ips IS TABLE OF NUMBER INDEX BY VARCHAR2(15);


v_contagem_ips t_ips;
v_ip_atual VARCHAR2(15);
BEGIN
v_arquivo := UTL_FILE.FOPEN(\'TEMP_DIR\', p_nome_arquivo, \'R\');

LOOP
BEGIN
UTL_FILE.GET_LINE(v_arquivo, v_linha);

-- Parse de linha de log (formato Apache Common Log)


-- Exemplo: [Link] - - [25/Dec/2023:10:00:00 +0000] "GET /[Link] HTTP/1.1" 200
1234
v_ip := REGEXP_SUBSTR(v_linha, \'^\\S+\');
v_timestamp := REGEXP_SUBSTR(v_linha, \'\[([^\]]+)\]\', 1, 1, NULL, 1);
v_metodo := REGEXP_SUBSTR(v_linha, \'"(\\w+)\\', 1, 1, NULL, 1);
v_url := REGEXP_SUBSTR(v_linha, \'"\\w+ ([^"]+)\\', 1, 1, NULL, 1);
v_status := TO_NUMBER(REGEXP_SUBSTR(v_linha, \'" (\\d+)\\', 1, 1, NULL, 1));

-- Contar acessos por IP


IF v_contagem_ips.EXISTS(v_ip) THEN
v_contagem_ips(v_ip) := v_contagem_ips(v_ip) + 1;
ELSE
v_contagem_ips(v_ip) := 1;
END IF;

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);

DBMS_OUTPUT.PUT_LINE(\'=== Top 10 IPs ===\');


v_ip_atual := v_contagem_ips.FIRST;
WHILE v_ip_atual IS NOT NULL LOOP
DBMS_OUTPUT.PUT_LINE(v_ip_atual || \': \' || v_contagem_ips(v_ip_atual) || \' acessos\');
v_ip_atual := v_contagem_ips.NEXT(v_ip_atual);
END LOOP;
EXCEPTION
WHEN OTHERS THEN
IF UTL_FILE.IS_OPEN(v_arquivo) THEN
UTL_FILE.FCLOSE(v_arquivo);
END IF;
DBMS_OUTPUT.PUT_LINE(\'Erro na análise: \' || SQLERRM);
END analisar_log_acesso;
/
29.4 Integração com Email

29.4.1 Enviando Emails com UTL_SMTP

-- Package para envio de emails


CREATE OR REPLACE PACKAGE pkg_email
IS
PROCEDURE enviar_email (
p_servidor_smtp VARCHAR2,
p_porta NUMBER DEFAULT 25,
p_remetente VARCHAR2,
p_destinatario VARCHAR2,
p_assunto VARCHAR2,
p_mensagem CLOB,
p_html BOOLEAN DEFAULT FALSE
);

PROCEDURE enviar_relatorio_vendas (
p_destinatario VARCHAR2,
p_periodo_inicio DATE,
p_periodo_fim DATE
);
END pkg_email;
/

CREATE OR REPLACE PACKAGE BODY pkg_email


IS
PROCEDURE enviar_email (
p_servidor_smtp VARCHAR2,
p_porta NUMBER DEFAULT 25,
p_remetente VARCHAR2,
p_destinatario VARCHAR2,
p_assunto VARCHAR2,
p_mensagem CLOB,
p_html BOOLEAN DEFAULT FALSE
)
IS
v_conexao UTL_SMTP.CONNECTION;
v_content_type VARCHAR2(100);
BEGIN
-- Conectar ao servidor SMTP
v_conexao := UTL_SMTP.OPEN_CONNECTION(p_servidor_smtp, p_porta);
UTL_SMTP.HELO(v_conexao, p_servidor_smtp);

-- Configurar remetente e destinatário


UTL_SMTP.MAIL(v_conexao, p_remetente);
UTL_SMTP.RCPT(v_conexao, p_destinatario);

-- Iniciar dados do email


UTL_SMTP.OPEN_DATA(v_conexao);

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

UTL_SMTP.WRITE_DATA(v_conexao, \'Content-Type: \' || v_content_type || UTL_TCP.CRLF);


UTL_SMTP.WRITE_DATA(v_conexao, UTL_TCP.CRLF); -- Linha em branco

-- Corpo da mensagem
UTL_SMTP.WRITE_DATA(v_conexao, p_mensagem);

-- Finalizar
UTL_SMTP.CLOSE_DATA(v_conexao);
UTL_SMTP.QUIT(v_conexao);

DBMS_OUTPUT.PUT_LINE(\'Email enviado com sucesso para: \' || p_destinatario);


EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(\'Erro ao enviar email: \' || SQLERRM);
UTL_SMTP.QUIT(v_conexao);
END enviar_email;

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;

-- Construir relatório HTML


v_relatorio := \'<html><body>\';
v_relatorio := v_relatorio || \'<h2>Relatório de Vendas</h2>\';
v_relatorio := v_relatorio || \'<p><strong>Período:</strong> \' ||
TO_CHAR(p_periodo_inicio, \'DD/MM/YYYY\') || \' a \' ||
TO_CHAR(p_periodo_fim, \'DD/MM/YYYY\') || \'</p>\';
v_relatorio := v_relatorio || \'<p><strong>Total de Vendas:</strong> \' || v_numero_vendas ||
\'</p>\';
v_relatorio := v_relatorio || \'<p><strong>Faturamento:</strong> R$ \' ||
TO_CHAR(v_total_vendas, \'999,999,999.99\') || \'</p>\';

-- Adicionar detalhes por cliente


v_relatorio := v_relatorio || \'<h3>Vendas por Cliente</h3><table border="1">\';
v_relatorio := v_relatorio || \'<tr><th>Cliente</th><th>Vendas</th><th>Total</th></tr>\';

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>\';

v_assunto := \'Relatório de Vendas - \' || TO_CHAR(p_periodo_inicio, \'DD/MM/YYYY\') || \' a


\' || TO_CHAR(p_periodo_fim, \'DD/MM/YYYY\');

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;
/

29.5 Integração com Outros Bancos de Dados (Database Links)

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).

29.5.1 Criando Database Links

-- Database Link para outro banco de dados Oracle


CREATE DATABASE LINK db_link_vendas_remoto
CONNECT TO usuario_remoto IDENTIFIED BY senha_remota
USING \'host_remoto:porta_remota/servico_remoto\';

-- Database Link para um banco de dados não-Oracle (via Gateway)


CREATE DATABASE LINK db_link_sqlserver
CONNECT TO usuario_sqlserver IDENTIFIED BY senha_sqlserver
USING \'nome_do_tns_para_sqlserver\';

29.5.2 Usando Database Links

-- Consultar tabela remota


SELECT * FROM Vendas@db_link_vendas_remoto WHERE DataVenda >= SYSDATE - 30;

-- Inserir dados em tabela remota


INSERT INTO LogAuditoria@db_link_vendas_remoto (Mensagem, DataHora)
VALUES (
\'Novo cliente inserido: \' || :[Link],
SYSDATE
);

-- Chamar procedure remota


BEGIN
atualizar_estoque@db_link_vendas_remoto(p_produto_id => 10, p_quantidade => -5);
END;
/

29.6 Integração com Ferramentas de BI e Reporting

O Oracle Database é a base para muitas ferramentas de Business Intelligence (BI) e Reporting. A integração
geralmente ocorre via:

Conexões JDBC/ODBC: Padrões para conectar aplicações a bancos de dados.

Views e Materialized Views: Para simplificar e otimizar consultas para relatórios.

APIs e Procedures: Para expor dados e lógica de negócio de forma controlada.


29.7 Tópicos Chave do Capítulo 29

Integração de Sistemas: Essencial para interoperabilidade e automação.

UTL_HTTP : Para requisições HTTP (GET, POST) a serviços web e APIs REST.

UTL_FILE : Para operações de leitura/escrita em arquivos no servidor.

UTL_SMTP : Para envio de emails diretamente do PL/SQL.

Database Links: Para acessar dados e objetos em outros bancos de dados.

JSON e XML: Ferramentas nativas para manipulação de dados semi-estruturados em integrações.

Ferramentas de BI: Conexões padrão, views e procedures para reporting.

29.8 Exercícios do Capítulo 29

1. Integração com API Externa (Clima):

Crie uma procedure que use UTL_HTTP para consultar uma API de clima (ex: OpenWeatherMap).

Extraia a temperatura e condições atuais para uma cidade específica.

Armazene os dados em uma tabela de log.

2. Processamento de Arquivo de Log:

Simule um arquivo de log de aplicação com mensagens de erro.

Crie uma procedure que leia este arquivo usando UTL_FILE .

Identifique e conte os tipos de erro, e insira-os em uma tabela de auditoria.

3. Envio de Alerta por Email:

Crie uma procedure que verifique o estoque de produtos.

Se um produto estiver abaixo de um limite mínimo, envie um email de alerta para o gerente de
estoque usando UTL_SMTP .

4. Sincronização de Dados com Database Link:

Simule um cenário onde você precisa copiar dados de uma tabela de vendas de um banco de
dados remoto para uma tabela local.

Use um Database Link para realizar a cópia de forma eficiente.

5. Desafio - Sistema de Notificação de Pedidos:

Crie um sistema que, ao inserir um novo pedido na tabela Pedidos :


Envie uma requisição POST para uma API externa (simulada) para registrar o pedido.

Gere um arquivo CSV com os detalhes do pedido no servidor.

Envie um email de confirmação para o cliente com os detalhes do pedido.

Tudo isso deve ser feito via PL/SQL, utilizando os packages UTL_HTTP , UTL_FILE e
UTL_SMTP .
Referências

[1] Oracle. PL/SQL Packages and Types Reference. Disponível em:


[Link]

Capítulo 30: Projeto Final e Melhores Práticas

30.1 Introdução ao Projeto Final

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].

30.1.1 Objetivos do Projeto

Consolidar Conhecimentos: Aplicar todos os conceitos aprendidos em um contexto real

Demonstrar Competência: Criar um sistema que evidencie domínio técnico

Estabelecer Padrões: Implementar melhores práticas de desenvolvimento

Criar Referência: Desenvolver código reutilizável e bem documentado

Simular Ambiente Real: Abordar desafios típicos de projetos empresariais

30.1.2 Escopo do Sistema

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

30.2.1 Modelo de Dados Completo

-- =====================================================
-- SISTEMA DE GESTÃO EMPRESARIAL - MODELO DE DADOS
-- =====================================================

-- Tabela de Configurações do Sistema


CREATE TABLE SistemaConfig (
ConfigID NUMBER PRIMARY KEY,
Chave VARCHAR2(100) UNIQUE NOT NULL,
Valor VARCHAR2(4000),
Descricao VARCHAR2(500),
DataCriacao DATE DEFAULT SYSDATE,
DataAtualizacao DATE DEFAULT SYSDATE
);

-- 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 Categorias (Hierárquica)


CREATE TABLE Categorias (
CategoriaID NUMBER PRIMARY KEY,
Nome VARCHAR2(100) NOT NULL,
Descricao VARCHAR2(500),
CategoriaPaiID NUMBER,
Ativo NUMBER(1) DEFAULT 1 CHECK (Ativo IN (0,1)),
DataCriacao DATE DEFAULT SYSDATE,
FOREIGN KEY (CategoriaPaiID) REFERENCES Categorias(CategoriaID)
);

-- Tabela de Clientes Expandida


CREATE TABLE Clientes (
ClienteID NUMBER PRIMARY KEY,
Nome VARCHAR2(200) NOT NULL,
Email VARCHAR2(255) UNIQUE NOT NULL,
Telefone VARCHAR2(20),
CPF_CNPJ VARCHAR2(18),
TipoPessoa CHAR(1) CHECK (TipoPessoa IN (\'F\', \'J\')), -- F=Física, J=Jurídica
Endereco VARCHAR2(500),
Cidade VARCHAR2(100),
Estado CHAR(2),
CEP VARCHAR2(10),
DataNascimento DATE,
Categoria VARCHAR2(20) DEFAULT \'Bronze\',
Status VARCHAR2(20) DEFAULT \'Ativo\',
LimiteCredito NUMBER(15,2) DEFAULT 0,
DataCadastro DATE DEFAULT SYSDATE,
DataUltimaCompra DATE,
TotalCompras NUMBER(15,2) DEFAULT 0,
NumeroCompras NUMBER DEFAULT 0,
Observacoes CLOB
);

-- Tabela de Produtos Expandida


CREATE TABLE Produtos (
ProdutoID NUMBER PRIMARY KEY,
CategoriaID NUMBER NOT NULL,
CodigoBarras VARCHAR2(50) UNIQUE,
Nome VARCHAR2(255) NOT NULL,
Descricao CLOB,
Preco NUMBER(15,2) NOT NULL CHECK (Preco >= 0),
PrecoPromocional NUMBER(15,2),
Custo NUMBER(15,2),
EstoqueAtual NUMBER DEFAULT 0 CHECK (EstoqueAtual >= 0),
EstoqueMinimo NUMBER DEFAULT 0,
EstoqueMaximo NUMBER DEFAULT 1000,
Unidade VARCHAR2(10) DEFAULT \'UN\',
Peso NUMBER(10,3),
Dimensoes VARCHAR2(100),
Marca VARCHAR2(100),
Modelo VARCHAR2(100),
Cor VARCHAR2(50),
Tamanho VARCHAR2(20),
Ativo NUMBER(1) DEFAULT 1 CHECK (Ativo IN (0,1)),
Destaque NUMBER(1) DEFAULT 0 CHECK (Destaque IN (0,1)),
DataCadastro DATE DEFAULT SYSDATE,
DataUltimaVenda DATE,
QuantidadeVendida NUMBER DEFAULT 0,
Especificacoes JSON,
FOREIGN KEY (CategoriaID) REFERENCES Categorias(CategoriaID)
);

-- 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 Itens do Pedido


CREATE TABLE ItensPedido (
ItemID NUMBER PRIMARY KEY,
PedidoID NUMBER NOT NULL,
ProdutoID NUMBER NOT NULL,
Quantidade NUMBER NOT NULL CHECK (Quantidade > 0),
PrecoUnitario NUMBER(15,2) NOT NULL,
Desconto NUMBER(15,2) DEFAULT 0,
ValorTotal NUMBER(15,2) NOT NULL,
FOREIGN KEY (PedidoID) REFERENCES Pedidos(PedidoID),
FOREIGN KEY (ProdutoID) REFERENCES Produtos(ProdutoID)
);

-- 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)
);

-- Tabela de Logs do Sistema


CREATE TABLE LogsSistema (
LogID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
Nivel VARCHAR2(10) CHECK (Nivel IN (\'INFO\', \'WARN\', \'ERROR\', \'DEBUG\')),
Modulo VARCHAR2(100),
Mensagem VARCHAR2(4000),
Detalhes CLOB,
Usuario VARCHAR2(100) DEFAULT USER,
DataHora TIMESTAMP DEFAULT SYSTIMESTAMP,
Sessao VARCHAR2(100)
);

-- Índices para Performance


CREATE INDEX idx_clientes_email ON Clientes(Email);
CREATE INDEX idx_clientes_cpf_cnpj ON Clientes(CPF_CNPJ);
CREATE INDEX idx_clientes_categoria ON Clientes(Categoria);
CREATE INDEX idx_produtos_categoria ON Produtos(CategoriaID);
CREATE INDEX idx_produtos_codigo ON Produtos(CodigoBarras);
CREATE INDEX idx_produtos_nome ON Produtos(UPPER(Nome));
CREATE INDEX idx_pedidos_cliente ON Pedidos(ClienteID);
CREATE INDEX idx_pedidos_data ON Pedidos(DataPedido);
CREATE INDEX idx_itens_pedido ON ItensPedido(PedidoID);
CREATE INDEX idx_itens_produto ON ItensPedido(ProdutoID);
CREATE INDEX idx_auditoria_tabela ON Auditoria(Tabela, DataOperacao);
30.2.2 Packages de Negócio

-- =====================================================
-- 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
);

TYPE t_clientes_tab IS TABLE OF t_cliente_rec;

-- Exceções customizadas
e_cliente_nao_encontrado EXCEPTION;
e_email_duplicado EXCEPTION;
e_cpf_invalido EXCEPTION;

-- Procedures e functions públicas


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;

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 obter_cliente (p_cliente_id NUMBER) RETURN t_cliente_rec;

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;

PROCEDURE atualizar_categoria_cliente (p_cliente_id NUMBER);

FUNCTION validar_cpf (p_cpf VARCHAR2) RETURN BOOLEAN;


FUNCTION validar_cnpj (p_cnpj VARCHAR2) RETURN BOOLEAN;

END pkg_clientes;
/

CREATE OR REPLACE PACKAGE BODY pkg_clientes


IS
-- Implementação das funções de validação
FUNCTION validar_cpf (p_cpf VARCHAR2) RETURN BOOLEAN
IS
v_cpf VARCHAR2(11);
v_soma NUMBER;
v_resto NUMBER;
v_dv1 NUMBER;
v_dv2 NUMBER;
BEGIN
-- Remover caracteres não numéricos
v_cpf := REGEXP_REPLACE(p_cpf, \'[^0-9]\', \'\');

-- Verificar se tem 11 dígitos


IF LENGTH(v_cpf) != 11 THEN
RETURN FALSE;
END IF;

-- Verificar se não são todos iguais


IF REGEXP_LIKE(v_cpf, \'^(\\d)\\1{10}$\') THEN
RETURN FALSE;
END IF;

-- Calcular primeiro dígito verificador


v_soma := 0;
FOR i IN 1..9 LOOP
v_soma := v_soma + TO_NUMBER(SUBSTR(v_cpf, i, 1)) * (11 - i);
END LOOP;

v_resto := MOD(v_soma, 11);


v_dv1 := CASE WHEN v_resto < 2 THEN 0 ELSE 11 - v_resto END;

-- Calcular segundo dígito verificador


v_soma := 0;
FOR i IN 1..10 LOOP
v_soma := v_soma + TO_NUMBER(SUBSTR(v_cpf, i, 1)) * (12 - i);
END LOOP;

v_resto := MOD(v_soma, 11);


v_dv2 := CASE WHEN v_resto < 2 THEN 0 ELSE 11 - v_resto END;

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

FUNCTION validar_cnpj (p_cnpj VARCHAR2) RETURN BOOLEAN


IS
v_cnpj VARCHAR2(14);
v_soma NUMBER;
v_resto NUMBER;
v_dv1 NUMBER;
v_dv2 NUMBER;
v_peso NUMBER;
BEGIN
-- Remover caracteres não numéricos
v_cnpj := REGEXP_REPLACE(p_cnpj, \'[^0-9]\', \'\');

-- Verificar se tem 14 dígitos


IF LENGTH(v_cnpj) != 14 THEN
RETURN FALSE;
END IF;

-- Calcular primeiro dígito verificador


v_soma := 0;
v_peso := 5;
FOR i IN 1..12 LOOP
v_soma := v_soma + TO_NUMBER(SUBSTR(v_cnpj, i, 1)) * v_peso;
v_peso := v_peso - 1;
IF v_peso = 1 THEN
v_peso := 9;
END IF;
END LOOP;

v_resto := MOD(v_soma, 11);


v_dv1 := CASE WHEN v_resto < 2 THEN 0 ELSE 11 - v_resto END;

-- Calcular segundo dígito verificador


v_soma := 0;
v_peso := 6;
FOR i IN 1..13 LOOP
v_soma := v_soma + TO_NUMBER(SUBSTR(v_cnpj, i, 1)) * v_peso;
v_peso := v_peso - 1;
IF v_peso = 1 THEN
v_peso := 9;
END IF;
END LOOP;

v_resto := MOD(v_soma, 11);


v_dv2 := CASE WHEN v_resto < 2 THEN 0 ELSE 11 - v_resto END;

-- 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);

IF v_count > 0 THEN


RAISE e_email_duplicado;
END IF;

-- Validar CPF/CNPJ se fornecido


IF p_cpf_cnpj IS NOT NULL THEN
IF p_tipo_pessoa = \'F\' AND NOT validar_cpf(p_cpf_cnpj) THEN
RAISE e_cpf_invalido;
ELSIF p_tipo_pessoa = \'J\' AND NOT validar_cnpj(p_cpf_cnpj) THEN
RAISE e_cpf_invalido;
END IF;
END IF;

-- Inserir cliente
v_cliente_id := seq_clientes.NEXTVAL;

INSERT INTO Clientes (


ClienteID, Nome, Email, Telefone, CPF_CNPJ, TipoPessoa,
Endereco, Cidade, Estado, CEP
) VALUES (
v_cliente_id, TRIM(p_nome), LOWER(TRIM(p_email)), p_telefone,
p_cpf_cnpj, p_tipo_pessoa, p_endereco, p_cidade, p_estado, p_cep
);

pkg_sistema.log_info(\'pkg_clientes\', \'Cliente inserido: \' || v_cliente_id);

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;

pkg_sistema.log_info(\'pkg_clientes\', \'Cliente atualizado: \' || p_cliente_id);


END atualizar_cliente;
FUNCTION obter_cliente (p_cliente_id NUMBER) RETURN t_cliente_rec
IS
v_cliente t_cliente_rec;
BEGIN
SELECT ClienteID, Nome, Email, Categoria, TotalCompras
INTO v_cliente
FROM Clientes
WHERE ClienteID = p_cliente_id;

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\';

IF p_nome IS NOT NULL THEN


v_sql := v_sql || \' AND UPPER(Nome) LIKE UPPER(:nome_param)\
30.3 Sistema de Auditoria e Logging

-- =====================================================
-- 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;
/

CREATE OR REPLACE PACKAGE BODY pkg_sistema


IS
PROCEDURE inserir_log (
p_nivel VARCHAR2,
p_modulo VARCHAR2,
p_mensagem VARCHAR2,
p_detalhes CLOB
)
IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO LogsSistema (Nivel, Modulo, Mensagem, Detalhes, Usuario, Sessao)
VALUES (p_nivel, p_modulo, p_mensagem, p_detalhes, USER, SYS_CONTEXT(\'USERENV\', \'SID\'));
COMMIT;
EXCEPTION
WHEN OTHERS THEN
-- Log de erro no log de erro para evitar loop infinito
DBMS_OUTPUT.PUT_LINE(\'Erro ao inserir log: \' || SQLERRM);
END inserir_log;

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;

FUNCTION obter_config (p_chave VARCHAR2) RETURN VARCHAR2


IS
v_valor [Link]%TYPE;
BEGIN
SELECT Valor INTO v_valor
FROM SistemaConfig
WHERE Chave = p_chave;
RETURN v_valor;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN NULL;
END obter_config;

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;

FUNCTION gerar_codigo_barras RETURN VARCHAR2


IS
BEGIN
RETURN TO_CHAR(SYSTIMESTAMP, \'YYYYMMDDHH24MISSFF3\') || DBMS_RANDOM.STRING(\'X\', 5);
END gerar_codigo_barras;

FUNCTION formatar_moeda (p_valor NUMBER) RETURN VARCHAR2


IS
BEGIN
RETURN \'R$ \' || TO_CHAR(p_valor, \'FM999G999G999D00\', \'NLS_NUMERIC_CHARACTERS =
\'\',.\'\'
NLS_CURRENCY = \'R$\')\');
END formatar_moeda;
END pkg_sistema;
/

-- =====================================================
-- 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;

INSERT INTO Auditoria (


AuditoriaID, Tabela, Operacao, ChavePrimaria, Usuario, DataOperacao, ValoresAntigos,
ValoresNovos, IP, Aplicacao
) VALUES (
seq_auditoria.NEXTVAL, \'Clientes\', v_operacao, v_chave_primaria, USER, SYSDATE,
v_valores_antigos, v_valores_novos,
SYS_CONTEXT(\'USERENV\', \'IP_ADDRESS\'), SYS_CONTEXT(\'USERENV\', \'MODULE\')
);
EXCEPTION
WHEN OTHERS THEN
pkg_sistema.log_error(\'trg_auditoria_generica\', \'Erro na trigger de auditoria: \' ||
SQLERRM);
END;
/

-- Exemplo de uso da auditoria


-- INSERT INTO Clientes (ClienteID, Nome, Email) VALUES (1, \'Teste Auditoria\',
\'auditoria@[Link]\');
-- UPDATE Clientes SET Nome = \'Teste Auditoria Atualizado\' WHERE ClienteID = 1;
-- DELETE FROM Clientes WHERE ClienteID = 1;

30.4 Melhores Práticas de Desenvolvimento

30.4.1 Padronização de Código

Nomenclatura Consistente: Use padrões claros para tabelas, colunas, procedures, packages (ex: PKG_ ,
TRG_ , IDX_ ).

Formatação: Indentação, espaçamento e quebras de linha consistentes.

Comentários: Comente blocos de código complexos, justificativas de design e qualquer lógica não
óbvia.

30.4.2 Modularização com Packages

Agrupe procedures, functions, tipos e variáveis relacionadas em packages.

Use a especificação do package para definir a interface pública e o corpo para a implementação.

Isso melhora a organização, reusabilidade e manutenção do código.

30.4.3 Tratamento de Erros Robusto

Sempre inclua blocos EXCEPTION para tratar erros esperados e inesperados.

Use RAISE_APPLICATION_ERROR para retornar mensagens de erro claras para a aplicação cliente.

Registre erros em uma tabela de log ( LogsSistema ) para análise posterior.

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.

Auditoria: Implemente trilhas de auditoria para monitorar acessos e alterações de dados.

Criptografia: Use criptografia para dados sensíveis (colunas, tablespaces).

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.

Materialized Views: Para sumarizar e pré-calcular dados para relatórios complexos.


Particionamento: Para gerenciar grandes tabelas e melhorar a performance de consultas.

30.4.6 Testes

Testes Unitários: Teste cada procedure e function individualmente.

Testes de Integração: Verifique se os componentes do sistema funcionam juntos corretamente.

Testes de Performance: Monitore e otimize o desempenho sob carga.

30.4.7 Documentação

Documente o modelo de dados, packages, procedures, functions e triggers.

Mantenha a documentação atualizada com as alterações do código.

Use ferramentas de documentação automática quando possível.

30.5 Conclusão e Próximos Passos

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.

30.5.1 Recursos Adicionais

Documentação Oficial Oracle: A fonte mais completa e atualizada.

Oracle Live SQL: Ambiente online para praticar SQL e PL/SQL.

Fóruns e Comunidades: Stack Overflow, Oracle Community, grupos de usuários.

Blogs e Artigos: Siga especialistas e blogs da área.

Certificações Oracle: Uma ótima maneira de validar suas habilidades.

30.5.2 Desafio Final

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.

Otimize e Monitore: Aplique as técnicas de otimização e monitore a performance do seu sistema.

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

[1] Oracle. Oracle Database Concepts. Disponível em: [Link]


database/19/cncpt/

Você também pode gostar