Relacional Consultoria e Sistemas
Rua da Candelária, 60 – 10º andar
Tel.: (0xx21) 2213-9191
e-mail: [email protected]
Copyright © 1998-2003
Lúcia M. A. Fernandes
Diretora de Tecnologia e Treinamento
e-mail: [email protected]
PL/SQL9i
BÁSICO E AVANÇADO
PL/SQL9I – BÁSICO E AVANÇADO
CAPÍTULO 1: INTRODUÇÃO..................................................................................................8
ESTRUTURA ....................................................................................................................................... 8
MODULARIDADE ................................................................................................................................ 9
ARQUITETURA ................................................................................................................................... 9
VANTAGENS DA PL/SQL ................................................................................................................. 10
SUPORTE PARA SQL........................................................................................................................................ 10
SUPORTE PARA PROGRAMAÇÃO ORIENTADA A OBJETO ......................................................................... 10
PERFORMANCE ................................................................................................................................................ 10
PORTABILIDADE ............................................................................................................................................... 10
PRODUTIVIDADE .............................................................................................................................................. 11
INTEGRAÇÃO COM O ORACLE ....................................................................................................................... 11
EXEMPLO........................................................................................................................................................... 12
COMPONENTES DA LINGUAGEM................................................................................................... 13
IDENTIFICADORES ........................................................................................................................................... 13
PALAVRAS RESERVADAS ............................................................................................................................... 14
LITERAIS ............................................................................................................................................................ 15
COMENTÁRIOS ................................................................................................................................................. 16
FIM DE LINHA .................................................................................................................................................... 16
VARIÁVEIS ........................................................................................................................................ 17
TIPOS DE DADOS............................................................................................................................. 17
ESCALARES ...................................................................................................................................................... 17
COMPOSTOS..................................................................................................................................................... 32
CONSTANTES .................................................................................................................................. 33
%TYPE .............................................................................................................................................. 34
ATRIBUIÇÃO ..................................................................................................................................... 35
ESCOPO E VISIBILIDADE ................................................................................................................ 36
QUALIFICAÇÃO ................................................................................................................................ 38
RESTRIÇÕES .................................................................................................................................... 39
CONVERSÃO IMPLÍCITA.................................................................................................................. 41
COMANDOS ...................................................................................................................................... 43
IF......................................................................................................................................................................... 43
SELECT INTO .................................................................................................................................................... 46
GOTO ................................................................................................................................................................. 47
NULL................................................................................................................................................................... 48
CASE .................................................................................................................................................................. 49
FUNÇÕES ......................................................................................................................................... 52
FUNÇÕES DE SQL VÁLIDAS EM PL/SQL ........................................................................................................ 52
FUNÇÕES DE SQL INVÁLIDAS EM PL/SQL .................................................................................................... 52
SQLCODE .......................................................................................................................................................... 53
ÍNDICE - I
PL/SQL9I – BÁSICO E AVANÇADO
SQLERRM .......................................................................................................................................................... 54
LABORATÓRIO 1 .............................................................................................................................. 55
CAPÍTULO 2: PROCESSAMENTO REPETITIVO ................................................................59
CURSOR ........................................................................................................................................... 59
DECLARAÇÃO ................................................................................................................................................... 59
ABERTURA ........................................................................................................................................................ 61
FETCH ................................................................................................................................................................ 62
%ROWTYPE....................................................................................................................................................... 63
FECHAMENTO................................................................................................................................................... 65
CURSORES IMPLÍCITOS ................................................................................................................. 66
%FOUND ............................................................................................................................................................ 66
%NOTFOUND .................................................................................................................................................... 67
%ISOPEN ........................................................................................................................................................... 68
%ROWCOUNT ................................................................................................................................................... 69
UPDATE PARA CURSOR ................................................................................................................. 70
DELETE PARA CURSOR .................................................................................................................. 71
OUTROS COMANDOS PARA PROCESSAMENTO REPETITIVO ................................................... 72
LOOP .................................................................................................................................................................. 72
EXIT .................................................................................................................................................................... 73
WHILE................................................................................................................................................................. 74
FOR LOOP ......................................................................................................................................................... 75
CURSOR LOOP ................................................................................................................................................. 78
LABORATÓRIO 2 .............................................................................................................................. 80
CAPÍTULO 3: TRATAMENTO DE ERRO..............................................................................82
VANTAGENS DAS EXCEPTIONS .................................................................................................... 83
DEFININDO EXCEPTIONS ............................................................................................................... 83
EXCEPTIONS PRÉ-DEFINIDAS ........................................................................................................................ 84
CAUSANDO UMA EXCEPTION ........................................................................................................ 88
O VERBO RAISE................................................................................................................................................ 88
A PRAGMA EXCEPTION_INIT .......................................................................................................................... 89
A PROCEDURE RAISE_APPLICATION_ERROR ............................................................................................. 90
PROPAGAÇÃO DA EXCEÇÃO ......................................................................................................... 92
DETERMINANDO O ERRO RECEBIDO ........................................................................................... 95
AS FUNÇÕES SQLCODE E SQLERRM............................................................................................................ 95
WHEN OTHERS ................................................................................................................................................. 96
EXCEPTIONS ADQUIRIDAS NA DECLARAÇÃO.............................................................................................. 97
LABORATÓRIO 3: ............................................................................................................................. 98
CAPÍTULO 4: TABELAS E REGISTROS............................................................................101
INDEX-BY TABLES ......................................................................................................................... 101
DECLARAÇÃO E ATRIBUIÇÃO....................................................................................................................... 102
ÍNDICE - II
PL/SQL9I – BÁSICO E AVANÇADO
MANIPULANDO INDEX-BY TABLES............................................................................................................... 103
MÉTODOS........................................................................................................................................................ 104
EXCEPTIONS PARA COLEÇÕES ................................................................................................................... 110
CRIANDO MATRIZES ...................................................................................................................................... 111
REGISTROS .................................................................................................................................... 113
DECLARAÇÕES............................................................................................................................................... 113
REFERENCIANDO REGISTROS..................................................................................................................... 115
MANIPULANDO REGISTROS ......................................................................................................................... 118
LABORATÓRIO 4: ........................................................................................................................... 121
CAPÍTULO 5: BULK BIND ..................................................................................................122
CONCEITOS.................................................................................................................................... 122
O COMANDO FORALL.................................................................................................................... 124
A CLÁUSULA BULK COLLECT ....................................................................................................... 125
O ATRIBUTO %BULK_ROWCOUNT............................................................................................................... 127
O ATRIBUTO %BULK_EXCEPTIONS ............................................................................................................. 128
OS DEMAIS ATRIBUTOS ................................................................................................................................ 130
CARACTERÍSTICAS OU RESTRIÇÕES ......................................................................................................... 130
LABORATÓRIO 5: ........................................................................................................................... 132
CAPÍTULO 6: SUBPROGRAMAS.......................................................................................133
CONCEITO ...................................................................................................................................... 133
CARACTERÍSTICAS DOS SUBPROGRAMAS................................................................................................ 133
PARÂMETROS ................................................................................................................................ 137
MODOS DOS PARÂMETROS ......................................................................................................................... 137
PASSAGEM DE PARÂMETRO POR REFERÊNCIA ....................................................................................... 140
VALOR DEFAULT ............................................................................................................................................ 141
NOTAÇÃO POSICIONAL E NOMEADA PARA PASSAGEM DOS PARÂMETROS ....................................... 142
DECLARAÇÕES FORWARD .......................................................................................................... 144
OVERLOADING ............................................................................................................................... 146
STORED SUBPROGRAM ............................................................................................................... 148
USO DE FUNÇÕES EM COMANDOS DE SQL............................................................................................... 150
VERIFICANDO ERROS DE COMPILAÇÃO..................................................................................................... 151
PRIVILÉGIOS EM ROTINAS ARMAZENADAS ............................................................................................... 152
USANDO AUTHID CURRENT_USER.............................................................................................................. 154
REFERÊNCIAS EXTERNAS ............................................................................................................................ 155
CUIDADOS ADICIONAIS ................................................................................................................................. 156
USO DE COLEÇÕES E REGISTROS EM PARÂMETROS............................................................. 158
USANDO FUNÇÕES PARA A CRIAÇÃO DE ÍNDICES .................................................................. 161
LABORATÓRIO 6: ........................................................................................................................... 162
CAPÍTULO 7: PACKAGES..................................................................................................164
CONCEITO ...................................................................................................................................... 164
ÍNDICE - III
PL/SQL9I – BÁSICO E AVANÇADO
PACKAGE SPECIFICATION ........................................................................................................... 164
PACKAGE BODY ............................................................................................................................ 166
RESTRIÇÕES................................................................................................................................................... 167
USANDO PACKAGES ..................................................................................................................... 168
USANDO PACOTES PARA TROCA DE INFORMAÇÕES .............................................................. 171
OVERLOADING ............................................................................................................................... 173
USO DE FUNÇÕES DE PACOTES EM COMANDOS SQL ............................................................ 175
NÍVEL DE PUREZA .......................................................................................................................................... 175
A PRAGMA RESTRICT REFERENCES .......................................................................................................... 176
NÍVEL DE PUREZA E OVERLOADING ........................................................................................................... 177
NIVEL DE PUREZA E COMANDOS DE SQL .................................................................................................. 178
RESTRIÇÕES................................................................................................................................................... 179
O ARGUMENTO TRUST.................................................................................................................................. 180
ALGUNS PACOTES DO ORACLE .................................................................................................. 182
PACKAGE STANDARD.................................................................................................................................... 182
PACKAGE DBMS_STANDARD ....................................................................................................................... 183
PACKAGE DBMS_OUTPUT ............................................................................................................................ 184
PACKAGE DBMS_PIPE ................................................................................................................................... 184
PACKAGE UTL_FILE ....................................................................................................................................... 185
PACKAGE DBMS_SQL .................................................................................................................................... 185
PACKAGE DBMS_ALERT................................................................................................................................ 185
PACKAGE DBMS_RANDOM ........................................................................................................................... 185
PACKAGE DBMS_FLASHBACK...................................................................................................................... 185
PACKAGE DBMS_LOB .................................................................................................................................... 185
PACKAGE DBMS_ROWID............................................................................................................................... 185
PACKAGE UTL_HTTP ..................................................................................................................................... 185
LABORATÓRIO 7: ........................................................................................................................... 186
CAPÍTULO 8: O PACOTE DBMS_OUTPUT.......................................................................188
CONCEITO ...................................................................................................................................... 188
ROTINAS DO PACOTE ................................................................................................................... 189
ENABLE............................................................................................................................................................ 189
DISABLE........................................................................................................................................................... 189
PUT................................................................................................................................................................... 190
NEW_LINE........................................................................................................................................................ 190
PUT_LINE......................................................................................................................................................... 191
GET_LINE......................................................................................................................................................... 192
GET_LINES ...................................................................................................................................................... 193
EXEMPLO USANDO O SQL*PLUS ................................................................................................................. 194
EXEMPLO USANDO OUTRO PROGRAMA .................................................................................................... 195
LABORATÓRIO 8: ........................................................................................................................... 196
CAPÍTULO 9: O PACOTE UTL_FILE..................................................................................197
ÍNDICE - IV
PL/SQL9I – BÁSICO E AVANÇADO
CONCEITO ...................................................................................................................................... 197
CONHECENDO O PACOTE............................................................................................................ 200
FOPEN.............................................................................................................................................................. 200
FOPEN_NCHAR............................................................................................................................................... 201
IS_OPEN .......................................................................................................................................................... 201
FCLOSE............................................................................................................................................................ 202
FCLOSE_ALL ................................................................................................................................................... 203
GET_LINE......................................................................................................................................................... 204
GET_LINE_NCHAR.......................................................................................................................................... 205
PUT................................................................................................................................................................... 206
PUT_NCHAR .................................................................................................................................................... 207
NEW_LINE........................................................................................................................................................ 207
PUT_LINE......................................................................................................................................................... 208
PUT_LINE_NCHAR .......................................................................................................................................... 209
FFLUSH ............................................................................................................................................................ 210
PUTF................................................................................................................................................................. 210
PUTF_NCHAR.................................................................................................................................................. 212
UM EXEMPLO DE LEITURA ........................................................................................................... 213
UM EXEMPLO DE GRAVAÇÃO ...................................................................................................... 214
LABORATÓRIO 9: ........................................................................................................................... 216
CAPÍTULO 10: O PACOTE DBMS_RANDOM ...................................................................217
INTRODUÇÃO ................................................................................................................................. 217
LABORATÓRIO 10: ......................................................................................................................... 219
CAPÍTULO 11: VARIÁVEIS CURSOR ................................................................................220
CONCEITO ...................................................................................................................................... 220
VANTAGENS.................................................................................................................................................... 220
DEFINIÇÃO ...................................................................................................................................................... 220
MANIPULANDO VARIÁVEIS CURSOR .......................................................................................... 222
RESTRIÇÕES PARA VARIÁVEIS CURSOR ................................................................................................... 226
EXPRESSÕES CURSOR ................................................................................................................ 228
RESTRIÇÕES RELATIVAS A EXPRESSÕES CURSOR ................................................................................ 228
LABORATÓRIO 11: ......................................................................................................................... 231
CAPÍTULO 12: SQL DINÂMICO EM PL/SQL .....................................................................232
CONCEITO ...................................................................................................................................... 232
USANDO SQL DINÂMICO............................................................................................................... 233
O COMANDO EXECUTE IMMEDIATE ............................................................................................................ 233
OS COMANDOS OPEN-FOR, FETCH E CLOSE ............................................................................................ 236
PASSANDO UM ARGUMENTO NULL............................................................................................................. 239
BULK DINÂMICO.............................................................................................................................................. 240
LABORATÓRIO 12: ......................................................................................................................... 244
ÍNDICE - V
PL/SQL9I – BÁSICO E AVANÇADO
CAPÍTULO 13: TRIGGERS .................................................................................................245
CONCEITO ...................................................................................................................................... 245
EVENTO ........................................................................................................................................................... 246
TIPO.................................................................................................................................................................. 251
AÇÃO................................................................................................................................................................ 255
RESTRIÇÕES................................................................................................................................................... 256
TABELAS MUTANTES E RESTRITAS ............................................................................................ 257
COMPILANDO TRIGGERS ............................................................................................................. 260
ALTER TRIGGER ............................................................................................................................................. 261
TRIGGERS COM EVENTOS DE DDL E DATABASE ..................................................................... 262
TRIGGERS E STORED PROCEDURES ......................................................................................... 264
TRANSAÇÕES AUTÔNOMAS ........................................................................................................ 265
TRANSAÇÕES AUTÔNOMAS VERSUS DEPENDENTES ............................................................................. 267
ERROS POSSÍVEIS ......................................................................................................................................... 269
COMPILANDO PL/SQL PARA EXECUÇÃO NATIVA ...................................................................... 270
LABORATÓRIO 13: ......................................................................................................................... 271
CAPÍTULO 14: EXTERNAL PROCEDURES - JAVA .........................................................272
INTRODUÇÃO ................................................................................................................................. 272
CRIANDO A INTERFACE................................................................................................................................. 272
DEFININDO A BIBLIOTECA............................................................................................................................. 273
REGISTRANDO CADA ROTINA ...................................................................................................................... 274
LABORATÓRIO 14: ......................................................................................................................... 277
CAPÍTULO 15: TABELAS DO CURSO...............................................................................278
TABELA DE DEPARTAMENTOS .................................................................................................... 278
DADOS ............................................................................................................................................................. 278
DESCRIÇÃO..................................................................................................................................................... 278
TABELA DE FUNCIONÁRIOS ......................................................................................................... 279
DADOS ............................................................................................................................................................. 279
DESCRIÇÃO..................................................................................................................................................... 279
TABELA DE ATIVIDADES ............................................................................................................... 280
DADOS ............................................................................................................................................................. 280
DESCRIÇÃO..................................................................................................................................................... 280
TABELA DE PROJETOS ................................................................................................................. 281
DADOS ............................................................................................................................................................. 281
DESCRIÇÃO..................................................................................................................................................... 281
TABELA DE PROJETOS X ATIVIDADES ....................................................................................... 282
DADOS ............................................................................................................................................................. 282
DESCRIÇÃO..................................................................................................................................................... 283
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS.......................................................284
ÍNDICE - VI
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 1 ............................................................................................................................ 284
LABORATÓRIO 2 ............................................................................................................................ 299
LABORATÓRIO 3: ........................................................................................................................... 306
LABORATÓRIO 4: ........................................................................................................................... 312
LABORATÓRIO 5: ........................................................................................................................... 319
LABORATÓRIO 6: ........................................................................................................................... 320
LABORATÓRIO 7: ........................................................................................................................... 329
LABORATÓRIO 8: ........................................................................................................................... 336
LABORATÓRIO 9: ........................................................................................................................... 341
LABORATÓRIO 10: ......................................................................................................................... 344
LABORATÓRIO 11: ......................................................................................................................... 346
LABORATÓRIO 12: ......................................................................................................................... 349
LABORATÓRIO 13: ......................................................................................................................... 351
LABORATÓRIO 14: ......................................................................................................................... 356
ÍNDICE - VII
PL/SQL9I – BÁSICO E AVANÇADO
CAPÍTULO 1: INTRODUÇÃO
A PL/SQL é uma linguagem procedural da Oracle que estende a SQL com comandos que permitem a
criação de procedimentos de programação.
Com ela, podemos usar comandos de SQL DML para manipular os dados da base de dados Oracle e
estabelecer fluxos de controle para processar estes dados.
A linguagem permite a declaração de constantes, variáveis, subprogramas (procedures e funções),
que favorecem a estruturação de código, e possui mecanismos para controle de erros de execução.
Incorpora os novos conceitos de objeto, encapsulamento e, ainda, permite a interface com rotinas
escritas em outras linguagens.
ESTRUTURA
A PL/SQL é uma linguagem estruturada em blocos. Cada bloco pode conter qualquer número de subblocos.
Um bloco permite que se façam declarações locais ao bloco, que deixam de existir quando o bloco
termina.
DECLARE
<declarações>............
BEGIN
<lógica>.....................
EXCEPTION
<erros>.......................
END;
opcional
obrigatória
opcional
A sintaxe acima apresenta a estrutura de um bloco PL/SQL, que é composto de três partes:
◊
Uma parte declarativa, onde definimos as variáveis locais àquele bloco.
◊ Uma parte de lógica, onde definimos a ação que aquele bloco deve realizar, incluindo a
declaração de outros blocos subordinados (ou embutidos) a este.
◊ Uma parte de tratamento de erros, que permite que tenhamos acesso ao erro ocorrido e
à determinação de uma ação de correção. Nessa parte, também podemos declarar outros
blocos subordinados.
CAPÍTULO 1: INTRODUÇÃO - 8
PL/SQL9I – BÁSICO E AVANÇADO
MODULARIDADE
Modularidade é um conceito que determina a divisão do programa em módulos com ações bem
definidas, que visam a facilitar o entendimento e a manutenção.
Um problema complexo poderia ser subdividido em problemas menos complexos que, por sua vez,
poderiam ser novamente subdivididos até que obtivéssemos problemas simples com soluções de fácil
implementação.
A PL/SQL, por possuir uma estrutura de blocos, favorece a modularidade. Além de blocos anônimos,
temos a possibilidade de criar procedures e funções armazenadas na base de dados e compartilhadas
por outras aplicações, ou packages que permitem que grupemos procedures, funções e variáveis
relacionadas.
ARQUITETURA
A PL/SQL não é um produto independente; podemos considerá-la um módulo executor de blocos e
subprogramas.
Esse módulo pode ser instalado no Oracle Server e nas ferramentas de Alunoolvimento da Oracle
(Forms Builder, Report Builder, etc.). Estes dois ambientes são independentes e podem conter, até
mesmo, versões diferentes da PL/SQL.
Esse módulo, no entanto, trabalha da mesma forma: ele é capaz de tratar os comandos de PL/SQL,
mas não os comandos de SQL, que devem ser resolvidos por mecanismos internos do Oracle Server.
Em ferramentas que possuem esse módulo embutido, a ferramenta passa para seu módulo executor
local o bloco de PL/SQL que pode ser processado no próprio ambiente, com a exceção dos comandos
de SQL encontrados. Grande parte do trabalho é realizada localmente, sem necessidade de envio de
informações para o ambiente servidor.
Em ferramentas que não têm esse módulo embutido, tais como SQL*Plus e Enterprise Manager,
torna-se necessário o envio de todo o bloco para o servidor, para que este acione o seu módulo
executor local e processe o bloco de PL/SQL.
CAPÍTULO 1: INTRODUÇÃO - 9
PL/SQL9I – BÁSICO E AVANÇADO
VANTAGENS DA PL/SQL
A PL/SQL oferece as seguintes vantagens:
SUPORTE PARA SQL
A PL/SQL permite a utilização, integrada no código, dos comandos da SQL DML, das funções de
SQL, de comandos de controle de cursor e dos comandos para controle da transação (Commit,
Rollback, etc.).
SUPORTE PARA PROGRAMAÇÃO ORIENTADA A OBJETO
Quando criamos um tipo objeto, definimos suas características através de seus atributos e métodos.
Os métodos são escritos em PL/SQL.
Na criação de um bloco de PL/SQL, podemos declarar variáveis com quaisquer dos tipos pré-definidos
existentes no banco de dados ou com os tipos criados pelo usuário, inclusive tipos objeto.
PERFORMANCE
A utilização da PL/SQL pode reduzir o tráfego na rede pelo envio de um bloco contendo diversos
comandos de SQL grupados em blocos para o Oracle. Isto é possível em um Oracle Precompiler ou
OCI (Oracle Call Interface).
A PL/SQL também adiciona performance às ferramentas que possuem um módulo executor local, pois
não necessitam enviar comandos de PL/SQL para serem processados pelo servidor, sendo enviados
apenas os comandos de SQL.
Adicionalmente, a criação de programas armazenados no banco de dados (stored subprogram) pode
reduzir não só o tráfego na rede como também a programação quando estabelecemos ações que
podem ser compartilhadas em diversas aplicações.
PORTABILIDADE
Aplicações escritas em PL/SQL são portáveis para qualquer sistema operacional e plataforma nos
quais o Oracle execute. Não há necessidade de customização.
Isso significa que podemos escrever programas ou bibliotecas de programas que podem ser utilizados
em ambientes diferentes.
CAPÍTULO 1: INTRODUÇÃO - 10
PL/SQL9I – BÁSICO E AVANÇADO
PRODUTIVIDADE
O aprendizado da PL/SQL pode ser aproveitado no Alunoolvimento de aplicações batch, online,
relatórios, etc.
No Alunoolvimento de um programa utilizando uma ferramenta tal como Forms Builder ou Report
Builder, já estaremos familiarizados com as construções da lógica de programação, pois trata-se da
mesma PL/SQL usada para Alunoolvimento das aplicações em batch.
INTEGRAÇÃO COM O ORACLE
As variáveis usadas pela PL/SQL podem ter os mesmos tipos existentes no banco de dados, tanto os
tipos predefinidos quanto aqueles definidos pelos usuários.
Os atributos %TYPE e %ROWTYPE permitem a integração com o dicionário de dados Oracle, pois
poderemos declarar uma variável com o mesmo tipo de uma coluna definida em uma tabela do banco
de dados.
Essa facilidade produz independência do dado, reduz os custos de manutenção e permite que os
programas se adaptem às mudanças ocorridas no banco de dados.
CAPÍTULO 1: INTRODUÇÃO - 11
PL/SQL9I – BÁSICO E AVANÇADO
EXEMPLO
SQL> SELECT vl_sal FROM func WHERE cd_mat = 150;
VL_SAL
----------2907.2
SQL>DECLARE
2
Wrowid
ROWID;
3
Sal
NUMBER(5);
4 BEGIN
5
SELECT vl_sal, rowid
6
INTO Sal, Wrowid
7
FROM func
-- obtém o salário
8
WHERE cd_mat = 150;
9 /*
10
Atualiza os valores de salário
11
------------------------------------12 */
13
UPDATE func
14
SET vl_sal = Sal * 1.3
15
WHERE rowid = Wrowid;
16 -17 END;
18 /
PL/SQL procedure successfully completed.
SQL> SELECT vl_sal FROM func WHERE cd_mat = 150;
VL_SAL
----------3779.1
OBS: Observe que a indicação de fim de comando, em PL/SQL, é feita com um ponto e vírgula (;).
Este assunto será visto em detalhes mais adiante.
CAPÍTULO 1: INTRODUÇÃO - 12
PL/SQL9I – BÁSICO E AVANÇADO
COMPONENTES DA LINGUAGEM
IDENTIFICADORES
Um identificador consiste de uma letra seguida de outras letras, números, $ (dólar), _ (sublinhado) e #
(símbolo numérico). Possui um limite máximo de 30 caracteres.
As letras podem ser maiúsculas ou minúsculas indiscriminadamente, pois a linguagem não é sensível
à forma.
Opcionalmente, os identificadores podem ser declarados (e usados) entre aspas. Com essa sintaxe,
podemos declarar variáveis com outros caracteres além daqueles estabelecidos no início do texto
(exceto aspas). O tamanho continua limitado a 30 caracteres (excluindo-se as aspas).
SQL> DECLARE
2
WTESTE
NUMBER;
3
"ID DE FUNC" NUMBER;
4
"abc def"
NUMBER;
5 BEGIN
6
WTESTE
:= 10;
7
"ID DE FUNC" := 20;
8
"ABC DEF"
:= 30;
9 END;
10 /
"ABC DEF"
:= 30;
*
ERRO na linha 8:
ORA-06550: linha 8, coluna 3:
PLS-00201: o identificador 'ABC DEF' deve ser declarado
ORA-06550: linha 8, coluna 3:
PL/SQL: Statement ignored
L01_01
No exemplo acima, observamos que, quando declaramos uma variável entre aspas, seu uso passa a
ser sensível à forma de escrever, ou seja, ficamos limitados à forma como declaramos a variável em
relação às maiúsculas e minúsculas.
CAPÍTULO 1: INTRODUÇÃO - 13
PL/SQL9I – BÁSICO E AVANÇADO
PALAVRAS RESERVADAS
Alguns identificadores possuem um significado especial em PL/SQL e não devem ser utilizados na
declaração de variáveis.
SQL> DECLARE
2 END
VARCHAR2(10);
3 END_CASA
VARCHAR2(20);
4 BEGIN
5 END := 'RUA A';
6 END_CASA := 'RUA B S/N.';
7 END;
8 /
END
VARCHAR2(10);
*
ERRO na linha 2:
ORA-06550: linha 2, coluna 3:
PLS-00103: Encontrado o símbolo "END" quando um dos seguintes símbolos era
esperado:
begin function package pragma procedure subtype type use
<an identifier> <a double-quoted delimited-identifier> form
current cursor
O símbolo "begin foi inserido antes de "END" para continuar.
ORA-06550: linha 3, coluna 17:
PLS-00103: Encontrado o símbolo "VARCHAR2" quando um dos seguintes símbolos era
esperado:
:= . ( @ % ;
O símbolo ":=" foi substituído por "VARCHAR2" para continuar.
ORA-06550: linha 5, coluna 3:
PLS-00103: Encontrado o símbolo "END" quando um dos seguintes símbolos era
esperado:
begin case declare exit for goto if loop mod null pragma
raise return select update while with <an identifier>
<a double-quoted delimited-identi
ORA-06550: linha 7, coluna 4:
PLS-00103: Encontrado o símbolo "end-of-file" quando um dos seguintes símbolos
era esperado:
begin case declare end exception exit for goto if loop mod
null pragma raise return select update while w
L01_02
No exemplo acima, podemos observar que o uso de um identificador, com o nome de END, causou
vários erros de compilação. A utilização de END como parte do nome de identificadores não causa
maiores prejuízos.
Alternativamente, podemos declarar palavras reservadas como identificadores, desde que seu nome
seja posto entre aspas. Essa forma de utilização das aspas não é encorajada pela Oracle.
CAPÍTULO 1: INTRODUÇÃO - 14
PL/SQL9I – BÁSICO E AVANÇADO
LITERAIS
Corresponde à representação explícita de um número, caracter, string ou boleano.
NÚMERO
Podemos usar dois tipos de literais numéricos: inteiros e reais. Podemos, ainda, representá-los
usando notação científica.
◊
Inteiros: {3, 100, 017, -125, +096}
◊
Reais: {12.5, +020.30, .78, 17., -05.32}
◊
Notação Científica: 12E3 (= 12*103 = 12000) ou 500e-4(=500*10-4 = 500/10000 = .05)
OBS: A letra E utilizada na notação científica indica o expoente de 10 que será usado para multiplicar
o número que aparece antes da letra.
CARACTER
Um literal caracter corresponde a um único caracter apresentado entre aspas simples (apóstrofos).
Nesse caso PL/SQL é sensível à forma, isto é, ‘A’ e ‘a’ são diferentes.
Como exemplo de literais caracteres, temos: ‘$’, ‘@’, ‘S’, ‘8’, ‘(‘, ‘s’.
STRING
Literais strings são uma seqüência de zero ou mais caracteres apresentados entre aspas simples
(apóstrofes).
Como exemplo de literais string, temos: ‘abc’, ‘
abc
‘, ‘ABC’, ‘ A B C ‘, ‘$5.000,00’, ‘aspas “ duplas’.
BOLEANO
Literais boleanos são os valores TRUE, FALSE e a indicação de ausência de valor NULL.
CAPÍTULO 1: INTRODUÇÃO - 15
PL/SQL9I – BÁSICO E AVANÇADO
COMENTÁRIOS
Um comentário em PL/SQL pode ser informado de duas formas:
◊
Dois hífens em qualquer ponto da linha torna o restante dela comentário.
◊
/* (início) e */ (fim) marcam uma região que será ignorada pelo compilador.
Como restrição, temos que não podemos embutir um comentário em outro e, ainda, não podemos
utilizar comentários com dois hífens em blocos de PL/SQL que venham a ser processados,
dinamicamente, por um Oracle Precompiler, porque os caracteres de fim de linha são ignorados (não
são considerados) e, desta forma, o fim do comentário não é percebido até o fim do bloco. Neste caso,
devemos usar /* e */.
SQL> DECLARE
2 VALOR
VARCHAR2(10);
3 END_CASA
VARCHAR2(20);
4 BEGIN
5 VALOR := -- A atribuição será feita na outra linha
6
'RUA A';
7 END_CASA := /* a atribuição virá a seguir */ 'RUA B S/N';
8 END;
9 /
L01_03
Procedimento PL/SQL concluído com sucesso.
FIM DE LINHA
A indicação de fim de linha de comando em PL/SQL é feita com um ponto-e-vírgula (;).
Observe que o comportamento do SQL*Plus mudará quando encontrar o primeiro comando de
PL/SQL, pois não aguardará mais o ponto-e-vírgula (;) para indicação de fim de comando.
Para concluirmos a digitação de um programa, deveremos utilizar a barra (/) para encerrar e executar,
ou o ponto (.) para encerrar sem executar.
CAPÍTULO 1: INTRODUÇÃO - 16
PL/SQL9I – BÁSICO E AVANÇADO
VARIÁVEIS
Cada constante ou variável possui um tipo de dado que especifica o formato de armazenamento,
restrições e intervalo de valores. O dado pode ser simples ou composto.
SINTAXE
<variável> <tipo> [ NOT NULL ] [ {:= | DEFAULT } <valor inicial> ]
TIPOS DE DADOS
ESCALARES
São um conjunto de tipos de dados predefinidos e que não possuem componentes internos (não são
subdivididos).
BINARY_INTEGER
Tipo de dado numérico para armazenamento de inteiros válidos no seguinte intervalo de valores: de 231 +1 (-2147483647) e 231 -1 (2147483647).
Um Binary_Integer possui um conjunto de subtipos (derivado do tipo básico Binary_Integer):
◊
Natural – Com intervalo de valores válidos entre 0 a 231 -1.
◊
NaturalN – Com intervalo de valores válidos entre 0 a 231 -1 e sem a possibilidade de associação
de valores NULLS.
◊
Positive – Com intervalo de valores válidos entre 1 a 231 -1.
◊
PositiveN – Com intervalo de valores válidos entre 1 a 231 -1 e sem a possibilidade de
associação de valores NULLS.
◊
SignType – Restrito à seguinte lista de valores: -1, 0 e 1.
CAPÍTULO 1: INTRODUÇÃO - 17
PL/SQL9I – BÁSICO E AVANÇADO
SQL>
2
3
4
5
6
7
8
9
10
11
12
DECLARE
WNATURAL
WNATURAL_N
WPOSITIVE
WPOSITIVE_N
WSIGNTYPE
BEGIN
WNATURAL
WPOSITIVE
WSIGNTYPE
END;
/
NATURAL;
NATURALN := 0;
POSITIVE;
POSITIVEN := 1;
SIGNTYPE;
:= 0;
:= 1;
:= -1;
Procedimento PL/SQL concluído com sucesso.
L01_04
No exemplo a declaração das variáveis de tipo NaturalN e PositiveN necessitou de um valor inicial
(:=), uma vez que não admite NULL, e toda variável de PL/SQL sem valor inicial definido está
implicitamente NULL.
CAPÍTULO 1: INTRODUÇÃO - 18
PL/SQL9I – BÁSICO E AVANÇADO
NUMBER
Tipo de dado numérico para armazenamento de valores fixos ou em ponto flutuante com precisão de
até 38 dígitos e magnitude de 1.0E-130 a 9.99E125.
Podemos especificar precisão e escala. Se não especificarmos precisão, o default é 38 (ou o maior
tamanho válido para o sistema operacional, o que for menor). Escala pode variar de -84 a 127.
Escalas negativas causam o arredondamento da parte inteira.
SQL>
SQL>
SQL>
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
VARIABLE P1 NUMBER
VARIABLE P2 NUMBER
VARIABLE P3 NUMBER
DECLARE
WNUM1
NUMBER;
WNUM2
NUMBER(38, -4);
WNUM3
NUMBER(38, 127);
BEGIN
WNUM1 := 9.99E125;
WNUM2 := 38925;
WNUM3 := 123456789987654321.0E-130;
:P1
:= WNUM1;
:P2
:= WNUM2;
:P3
:= WNUM3;
END;
/
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT
P1
---------9,990E+125
P2
---------40000
P3
---------1,235E-113
L01_05
Inicialmente, declaramos três variáveis no SQL*Plus para recebermos o resultado das atribuições
feitas no programa. O comando Print (de SQL*Plus) nos mostra o resultado.
A variável wnum1 não teve seu valor alterado, pois a declaramos sem qualquer restrição quanto à
precisão ou escala. Já a variável wnum2, por possuir uma escala negativa, causou o arredondamento
da parte inteira do número que passou de 38.925 para 40.000.
CAPÍTULO 1: INTRODUÇÃO - 19
PL/SQL9I – BÁSICO E AVANÇADO
A variável wnum3 perde os três últimos números informados, pois a escala é 127 e o valor é
multiplicado por 10-130.
O tipo Number também possui um conjunto de subtipos (derivados do tipo básico Number):
◊
Decimal, Dec e Numeric – armazenamento em ponto fixo com uma precisão máxima de 38
dígitos decimais.
◊
Double Precision, Float – armazenamento em ponto flutuante com uma precisão máxima de 126
dígitos binários, o que equivale a 38 dígitos decimais.
OBS: Para efetuarmos a conversão de precisão binária para precisão decimal, devemos multiplicar a
precisão binária por 0.30103 e para realizar a operação inversa devemos multiplicar a precisão
decimal por 3.32193.
◊
Real - armazenamento em ponto flutuante com uma precisão máxima de 63 dígitos binários, o
que equivale a 18 dígitos decimais.
◊
Integer, Int e Smallint – armazenamento de inteiros com uma precisão máxima de 38 dígitos.
CAPÍTULO 1: INTRODUÇÃO - 20
PL/SQL9I – BÁSICO E AVANÇADO
PLS_INTEGER
Tipo de dado numérico para armazenamento de inteiros válidos no seguinte intervalo de valores: de 231 + 1 (-2.147.483.647) a 231 -1 (2.147.483.647).
É similar ao tipo Binary_Integer, porém é mais rápido para efetuar cálculos que um Binary_Integer ou
um Number, pois utiliza machine arithmetic, enquanto os demais usam library arithmetic.
Possui uma outra diferença em relação ao Binary_Integer no que se refere à detecção de Overflow.
Quando efetuamos um cálculo usando variáveis Pls_Integer e o valor ultrapassa a capacidade
máxima da variável, ocorre um erro de Overflow mesmo que a área receptora tenha capacidade de
armazenamento (seja um Number, por exemplo). Já com Binary_Integer não ocorrerá qualquer erro se
atribuirmos o resultado a um Number.
SQL> DECLARE
2
WNUM
NUMBER;
3
WPLS
PLS_INTEGER
:= 2147483647;
4
WBIN
BINARY_INTEGER := 2147483647;
5 BEGIN
6
WNUM := WBIN + 1;
7
WNUM := WPLS + 1;
8 END;
9 /
DECLARE
*
ERRO na linha 1:
ORA-01426: esgotamento numérico
ORA-06512: em line 7
L01_06
A recomendação da Oracle é que passemos a utilizar variáveis do tipo Pls_Integer nas novas
aplicações para que possamos obter ganhos de performance.
CAPÍTULO 1: INTRODUÇÃO - 21
PL/SQL9I – BÁSICO E AVANÇADO
CHAR
Tipo de dado alfanumérico de tamanho fixo com comprimento de até 32.767 bytes. Podemos
especificar o tamanho máximo na declaração de uma variável com esse tipo.
Caso isto não seja especificado, o comprimento default é de 1 byte.
O comprimento é especificado em bytes e não em caracteres. Isto é importante quando armazenamos
valores multibyte.
O conjunto de valores válidos para armazenamento depende do charset do banco de dados.
A definição de uma coluna do tipo Char no banco de dados está limitada a 2.000 bytes.
O tipo Char tem um único subtipo Character que possui as mesmas características de seu tipo básico.
SQL>
SQL>
2
3
4
5
6
7
VARIABLE P1 VARCHAR2(100)
DECLARE
WCHAR
CHAR(5);
BEGIN
WCHAR := 'ABC';
:P1
:= '*'||WCHAR||'*';
END;
/
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT P1
P1
---------------------------------------------*ABC *
L01_07
Observe que a variável é completada com brancos à direita até atingir o comprimento especificado
pelo tipo.
CAPÍTULO 1: INTRODUÇÃO - 22
PL/SQL9I – BÁSICO E AVANÇADO
VARCHAR2
Tipo de dado alfanumérico de tamanho variável com comprimento de até 32.767 bytes. A
especificação de tamanho é obrigatória para uma variável declarada com esse tipo.
O comprimento é especificado em bytes e não em caracteres. Isso é importante quando
armazenamos valores multibyte.
O conjunto de valores válidos para armazenamento depende do charset do banco de dados.
A definição de uma coluna do tipo Varchar2 no banco de dados está limitada a 4.000 bytes.
O tipo Varchar2 possui os seguintes subtipos:
◊
String – Possui as mesmas características do seu tipo básico. Considerado um sinônimo.
◊
Varchar – Possui as mesmas características do seu tipo básico atualmente. Em versões futuras,
se tornará um tipo separado com características de comparação diferentes do Varchar2.
SQL>
2
3
4
5
6
7
8
9
DECLARE
WVARCHAR2
WVARCHAR
BEGIN
WVARCHAR2
WVARCHAR
:P1
END;
/
VARCHAR2(32767);
VARCHAR(100);
:= 'ABC';
:= WVARCHAR2;
:= '*'||WVARCHAR||'*';
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT P1
P1
------------------------------------------*ABC*
CAPÍTULO 1: INTRODUÇÃO - 23
PL/SQL9I – BÁSICO E AVANÇADO
LONG
Tipo de dado alfanumérico de tamanho variável com comprimento de até 32.767 bytes. Podemos
especificar o tamanho máximo na declaração de uma variável com esse tipo.
Uma coluna Long no banco de dados armazena até 2 GB (2.147.483.647 bytes).
Podemos fazer referência a colunas Long em comandos de DML Insert, Update e muitos comandos
Select, mas não em expressões, chamadas de funções SQL, ou em certas cláusulas, tais como
Where, Group By e Connect By.
SQL>
2
3
4
5
6
7
DECLARE
WLONG
BEGIN
WLONG
:P1
END;
/
LONG(32767);
:= 'ABC';
:= '*'||WLONG||'*';
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT P1
P1
----------------------------------------------*ABC*
RAW
Tipo de dado binário de tamanho variável com comprimento de até 32.767. A especificação de
tamanho, na declaração de uma variável com esse tipo, é obrigatória. São semelhantes a colunas
Varchar2, porém a PL/SQL não interpreta seu conteúdo.
O SQL*Net8 não realiza conversão entre charsets quando transmitimos dados Raw de um sistema
para outro.
Uma coluna Raw no banco de dados armazena até 2.000 bytes.
LONG RAW
Tipo de dado binário de tamanho variável com comprimento de até 32.767.
A especificação de tamanho, na declaração de uma variável com este tipo, é obrigatória. São
semelhantes a colunas Long, porém a PL/SQL não interpreta seu conteúdo.
Uma coluna Long Raw no banco de dados armazena até 2 GB (2.147.483.647).
CAPÍTULO 1: INTRODUÇÃO - 24
PL/SQL9I – BÁSICO E AVANÇADO
ROWID
Para armazenamento de valores Rowid do banco de dados. Cada tabela criada no banco de dados
possui uma pseudocoluna Rowid que armazena valores hexadecimais que correspondem ao
endereço de cada linha (row).
A partir do Oracle8, os Rowids foram estendidos para suportar particionamento de tabelas e índices.
Desta forma, foi adicionado ao valor já existente na release 7.3 um data object number, que identifica
o segmento.
Objetos criados no mesmo segmento, tais como as tabelas criadas em um cluster, possuem o mesmo
data object number.
SQL> SELECT CD_MAT, ROWID FROM FUNC
2
WHERE ROWNUM < 10;
CD_MAT
---------1
10
11
12
15
20
30
50
60
ROWID
-----------------AAAH2LAAIAAAAAyAAy
AAAH2LAAIAAAAAyAAA
AAAH2LAAIAAAAAyAA1
AAAH2LAAIAAAAAyAA2
AAAH2LAAIAAAAAyAA3
AAAH2LAAIAAAAAyAAB
AAAH2LAAIAAAAAyAAC
AAAH2LAAIAAAAAyAAD
AAAH2LAAIAAAAAyAAE
9 linhas selecionadas.
L01_10
Um Rowid contém o seguinte formato:
◊
Data object number
◊
Data file (onde o primeiro arquivo recebe o número 1)
◊
Data block in the data file
◊
Row in the data block (onde a primeira linha recebe o número 0)
Uma vez que o Rowid representa o endereço da linha, torna-se o meio de acesso mais rápido para
obtenção de informações de uma linha específica.
Para destacarmos as informações de uma variável Rowid, usaremos as rotinas presentes no pacote
Dbms_Rowid.
CAPÍTULO 1: INTRODUÇÃO - 25
PL/SQL9I – BÁSICO E AVANÇADO
UROWID
Armazena um Rowid lógico. Usado para captura do Rowid (lógico) de tabelas Index Organized.
NCHAR
Tipo de dado alfanumérico de tamanho fixo com comprimento de até 32.767 bytes. Podemos
especificar o tamanho máximo na declaração de uma variável com este tipo. Caso isso não seja
especificado, o comprimento default é de 1 byte.
O comprimento é especificado em bytes e não em caracteres. Isto é importante quando armazenamos
valores multibyte.
O conjunto de valores válidos para armazenamento depende do national charset (criado na versão 8
do Oracle e que permite o armazenamento de valores simultaneamente em dois charsets diferentes)
definido para o banco de dados. As colunas alfanuméricas Nchar e Nvarchar2 obedecem ao national
charset e as colunas Char, Varchar2 e Long, ao database charset.
A definição de uma coluna do tipo Nchar no banco de dados está limitada a 2.000 bytes.
A definição de uma coluna do tipo Nchar no banco de dados está limitada a 2.000 bytes. Podemos
intercambiar valores entre colunas Nchar e Char, porém pode haver perda de informação se o charset
da coluna Char não puder representar todo o conteúdo da coluna Nchar (aparecerão caracteres com
?).
NVARCHAR2
Tipo de dado alfanumérico de tamanho variável com comprimento de até 32.767 bytes. A
especificação de tamanho é obrigatória para uma variável declarada com este tipo.
O comprimento é especificado em bytes e não em caracteres. Isso é importante quando
armazenamos valores multi-byte.
O conjunto de valores válidos para armazenamento depende do national charset (criado na versão 8
do Oracle e que permite o armazenamento de valores simultaneamente em dois charsets diferentes)
definido para o banco de dados.
As colunas alfanuméricas Nchar e Nvarchar2 obedecem ao national charset e as colunas Char,
Varchar2 e Long, ao database charset.
A definição de uma coluna do tipo Nvarchar2 no banco de dados está limitada a 4.000 bytes.
BOOLEAN
É um tipo de dado para variáveis (não podem ser definidas como colunas em uma base de dados).
Armazena valores lógicos True e False e a ausência de valor Null. Não possui tamanho e nem
qualquer tipo de parâmetro.
CAPÍTULO 1: INTRODUÇÃO - 26
PL/SQL9I – BÁSICO E AVANÇADO
DATE
Tipo de dado para armazenamento de valores de data e hora. Os valores válidos variam de 01 de
janeiro de 4712 A.C. até 31 de dezembro de 9999 D.C.
Na definição de uma variável de tipo data, não especificamos comprimento.
Podemos operar com variáveis de tipo data (da mesma forma que com colunas Date) adicionando ou
subtraindo valores inteiros, usando as funções de SQL para data e subtraindo duas datas para obter o
intervalo de dias.
Quando associamos uma data a uma variável alfanumérica, ocorre a conversão automática de tipo, da
mesma forma que ocorre quando associamos uma variável alfanumérica a uma variável de data. O
layout default para a conversão depende do valor de Nls_Date_Format em vigor para a sessão.
SQL>
2
3
4
5
6
7
8
9
10
11
12
DECLARE
WVARCHAR
WVARCHAR2
WDATE
WDATE2
BEGIN
WDATE
WVARCHAR
WDATE2
:P1
END;
/
VARCHAR2(20);
VARCHAR2(20) := '05/02/87';
DATE;
DATE;
:=
:=
:=
:=
TO_DATE('01011987', 'DDMMYYYY');
WDATE;
WVARCHAR2;
WVARCHAR||' - '||TO_CHAR(WDATE2, 'DDMMYYYY');
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT P1
P1
-----------------------------------------------------------------01/01/87 - 05021987
L01_11
No exemplo acima percebemos que o formato default de data para a sessão era dd/mm/rr, uma vez
que a atribuição de uma variável Varchar2 a uma variável Date foi interpretada corretamente.
CAPÍTULO 1: INTRODUÇÃO - 27
PL/SQL9I – BÁSICO E AVANÇADO
TIMESTAMP
Extensão ao tipo Date. Ano, mês e dia assim com hora, minuto, segundo e fração de segundo.
Podemos indicar o número de dígitos da parte fracionária do segundo.
TIMESTAMP WITH TIME ZONE
Extensão ao tipo Timestamp. Inclui a apresentação da zona de tempo. Onde a zona de tempo
corresponde à diferença (em horas e minutos) entre a hora local e UTC (hora de Greenwich).
TIMESTAMP WITH LOCAL TIME ZONE
Extensão ao tipo Timestamp. A diferença entre esta opção e a anterior é que a zona de tempo não é
armazenada no banco de dados.
O dado, quando armazenado, é normalizado para a Dbtimezone e, quando recuperado, é visualizado
pelo usuário com a Time Zone da sessão (ocorre uma segunda conversão).
INTERVAL DAY TO SECOND
Armazena um período de tempo em dias, horas, minutos e segundos.
INTERVAL YEAR TO MONTH
Armazena um período de tempo em anos e meses.
SQL>
SQL>
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
VARIABLE P1 VARCHAR2(1000)
VARIABLE P2 VARCHAR2(1000)
DECLARE
WTM
TIMESTAMP := CURRENT_TIMESTAMP;
WTM_TZ
TIMESTAMP WITH TIME ZONE := SYSTIMESTAMP;
WTM_LTZ
TIMESTAMP WITH LOCAL TIME ZONE := LOCALTIMESTAMP;
IDS
INTERVAL DAY TO SECOND :=
NUMTODSINTERVAL (1125.3, 'MINUTE');
IYM
INTERVAL YEAR TO MONTH :=
NUMTOYMINTERVAL (25.3, 'MONTH');
BEGIN
:P1 := WTM||' | '||WTM_TZ||' | '||WTM_LTZ;
:P2 := IDS||' | '||IYM;
END;
/
Procedimento PL/SQL concluído com sucesso.
L01_12
CAPÍTULO 1: INTRODUÇÃO - 28
PL/SQL9I – BÁSICO E AVANÇADO
SQL> SET LINESIZE 90
SQL> PRINT
P1
-----------------------------------------------------------------------------------------25/05/03 14:22:08,000001 | 25/05/03 14:22:08,000001 -03:00 | 25/05/03 14:22:08,000001
P2
-----------------------------------------------------------------------------------------+00 18:45:18.000000 | +02-01
P3
---------1,235E-113
Acima podemos observar que o padrão de apresentação para valores de timestamp já compreende
data, hora, minuto, segundo e, para alguns, a zona de tempo.
A variável Day To Second armazena um valor que pode conter dia (não é data), hora, minuto,
segundo e fração.
Já a variável do tipo Year To Month armazena anos e meses.
CAPÍTULO 1: INTRODUÇÃO - 29
PL/SQL9I – BÁSICO E AVANÇADO
LOBS
São um conjunto de tipos de dados predefinidos e que armazenam valores chamados locators, os
quais especificam a localização dos lobs (large objects) armazenados na linha ou fora dela.
Armazenam valores com comprimento de até 4 GB. Permitem o acesso randômico a trechos do dado.
São diferentes dos tipos Long e Long Raw nos seguintes aspectos:
◊
Podem ser atributos (exceto Nclob) de tipos objeto, Longs não podem.
◊
O comprimento máximo é de 4 GB e dos Longs é 2 GB.
◊
Lobs suportam acesso randômico, enquanto Longs suportam somente acesso seqüencial.
◊
Pode-se declarar diversas colunas Lob em uma mesma tabela; Longs não são permitidos
(somente 1).
A PL/SQL opera com lobs através dos locators, ou seja, quando recuperamos o valor de uma coluna
Lob, o que é obtido é o locator.
Como restrição, temos que locators não podem ser guardados de uma transação (ou sessão) para
outra: não podemos ler a informação, efetuar um Commit e utilizar a informação lida.
Para manusearmos as informações de uma variável Lob, usaremos as rotinas presentes no pacote
Dbms_Lob.
◊
Blob - O tipo Blob tem a capacidade de armazenar grandes valores binários. O tamanho máximo
não pode exceder 4 GB.
O armazenamento da informação pode ser feito na própria linha ou em outro espaço específico.
◊
Clob - O tipo Clob tem a capacidade de armazenar grandes volumes de dados alfanuméricos
single-byte. O tamanho máximo não pode exceder 4 GB.
O armazenamento da informação pode ser feito na própria linha ou em outro espaço específico.
◊
Nclob - O tipo Nclob tem a capacidade de armazenar grandes volumes de dados alfanuméricos
single-byte ou multibyte do tipo Nchar.
O tamanho máximo não pode exceder 4 GB.
O armazenamento da informação pode ser feito na própria linha ou em outro espaço específico.
CAPÍTULO 1: INTRODUÇÃO - 30
PL/SQL9I – BÁSICO E AVANÇADO
◊
Bfile - O tipo Bfile tem a capacidade de armazenar grandes volumes de dados binários fora do
banco de dados.
O locator de um Bfile inclui um diretório que especifica o caminho completo do arquivo no servidor.
O arquivo endereçado por um Bfile fica fora do banco de dados. No banco de dados, armazenamos
apenas um locator (endereço) para o arquivo.
Bfiles são read-only. Não podemos modificá-los. O Oracle não modifica o arquivo físico associado com
o locator. O locator, naturalmente, pode ser modificado.
O número máximo de Bfiles abertos é determinado pelo parâmetro de inicialização do banco de dados
Session_Max_Open_Files.
Bfiles não participam de transações. A integridade é de responsabilidade do sistema operacional do
ambiente servidor. O tamanho do arquivo não pode exceder 4 GB.
O DBA deve garantir que o arquivo exista e que o Oracle tenha permissão para acesso de leitura no
diretório especificado.
CAPÍTULO 1: INTRODUÇÃO - 31
PL/SQL9I – BÁSICO E AVANÇADO
COMPOSTOS
São aqueles tipos que possuem componentes internos que podem ser manuseados individualmente.
Nesse grupo, se encontram os tipos definidos pelo usuário: Table, Record e Varray. Serão vistos em
tópicos específicos.
REFERENCE
São aqueles tipos que armazenam valores chamados ponteiros, que apontam para outros itens do
programa ou do banco de dados.
Fazem parte deste grupo os tipos Ref Cursor e Ref <object>, que também serão vistos em tópicos
específicos.
SUBTIPOS DEFINIDOS PELO USUÁRIO
Cada tipo de PL/SQL define um conjunto de valores válidos e um conjunto de operações aplicáveis às
variáveis declaradas com aquele tipo. Subtipos declaram o mesmo conjunto de operações, mas
apenas um subconjunto de seus valores.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
DECLARE
SUBTYPE DATA
IS DATE NOT NULL;
SUBTYPE TEXTO
IS VARCHAR2;
SUBTYPE CODIGO IS NUMBER;
DT_HOJE
DATA := SYSDATE;
DESCRICAO
TEXTO(100);
CD_MAT
CODIGO(5);
BEGIN
DT_HOJE
:= SYSDATE;
DESCRICAO := 'TESTE DE SUBTIPO';
CD_MAT
:= 12345;
:P1
:= DESCRICAO||'-'||CD_MAT||'-'||DT_HOJE;
END;
/
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT P1
P1
---------------------------------------------------------TESTE DE SUBTIPO-12345-25/05/03
Podemos definir subtipos de um tipo básico e, posteriormente, uma variável com aquele tipo. As
restrições estabelecidas para o subtipo devem ser respeitadas pelo item.
CAPÍTULO 1: INTRODUÇÃO - 32
PL/SQL9I – BÁSICO E AVANÇADO
CONSTANTES
A declaração de uma constante é semelhante à declaração de uma variável, exceto que devemos
adicionar a palavra chave CONSTANT e, imediatamente, associar uma valor inicial.
Seu valor não poderá ser alterado durante o programa.
SINTAXE
<nome da constante> CONSTANT <tipo> { := / DEFAULT } <valor inicial>
SQL>
2
3
4
5
6
7
8
DECLARE
CONSTANTE
CONSTANT REAL := 5;
ZERO
CONSTANT PLS_INTEGER := 0;
BRANCO
CONSTANT VARCHAR2(1) := ' ';
BEGIN
:MSG := CONSTANTE ||'*'||ZERO||'*'||BRANCO||'*';
END;
/
PL/SQL procedure successfully completed.
MSG
-----------------------------------------------------------5*0* *
CAPÍTULO 1: INTRODUÇÃO - 33
PL/SQL9I – BÁSICO E AVANÇADO
%TYPE
O atributo %Type copia o tipo de dado de uma variável ou coluna do banco de dados.
Podemos, desta forma, declarar outra variável baseada na definição de uma coluna do banco de
dados ou baseada na definição de outra variável.
Isso é particularmente usado quando declaramos variáveis que venham a receber informações de
colunas do banco de dados.
A utilização de %Type favorece a independência de dados, uma vez que não precisamos saber
exatamente o tipo de dado da variável a ser declarada e, caso haja modificações no objeto original, a
modificação da variável cópia é feita automaticamente.
SQL> DECLARE
2
V_COD
NUMBER(4)
:=
3
V_DATA
DATE
NOT NULL :=
4 -5
WCOD
V_COD%TYPE NOT NULL :=
6
WDATA
V_DATA%TYPE
:=
7 -8
WDEP
DEPTO.CD_DEPTO%TYPE;
9 BEGIN
10
WDEP
:= NULL;
11
V_COD
:= 1;
12
WDATA
:= NULL;
13 END;
14 /
WDATA
:= NULL;
*
ERRO na linha 12:
ORA-06550: linha 12, coluna 14:
PLS-00382: a expressão é do tipo incorreto
ORA-06550: linha 12, coluna 3:
PL/SQL: Statement ignored
0;
SYSDATE;
1234;
SYSDATE;
L01_14
Observe que a variável wdata se baseou na variável v_data, cuja cláusula Not Null também foi
copiada (esta a causa do erro). Já a variável wdep, que é baseada na coluna cd_depto da tabela
Depto, não tem a obrigatoriedade de Not Null, apesar de esta restrição estar presente na coluna do
banco de dados.
Concluímos, então, que, quando a variável está baseada em uma coluna do banco de dados, são
copiados apenas seu tipo e tamanho, enquanto a restrição existente também é copiada quando a
variável original é local.
CAPÍTULO 1: INTRODUÇÃO - 34
PL/SQL9I – BÁSICO E AVANÇADO
ATRIBUIÇÃO
A atribuição de valor a uma variável é feita com a notação :=.
As variáveis e constantes são criadas e recebem seu valor inicial cada vez que for iniciado o bloco no
qual estão declaradas.
Na ausência de atribuição de valor inicial, a variável é considerada sem valor ou Null.
É muito importante, portanto, que as variáveis sejam inicializadas antes do uso.
SQL>
SQL>
2
3
4
5
6
7
8
VARIABLE RETORNO NUMBER
DECLARE
V1
NUMBER := 1;
V2
NUMBER;
BEGIN
V1 := V1 + V2;
:RETORNO := V1 * 2;
END;
/
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT RETORNO
RETORNO
----------
L01_15
O resultado do programa foi Null, porque fizemos uma operação incluindo uma variável sem valor;
desta forma, o resultado torna-se também sem valor.
Isto ocorrerá sempre que fizermos uma operação incluindo uma variável sem valor.
CAPÍTULO 1: INTRODUÇÃO - 35
PL/SQL9I – BÁSICO E AVANÇADO
ESCOPO E VISIBILIDADE
O escopo de uma variável é a região (bloco, subprograma ou pacote) onde a referência a ela é válida.
Dentro de um mesmo escopo, todas as variáveis devem ter nomes únicos.
Uma variável declarada em um bloco é visível nesse e em todos os blocos subordinados a ele. Se
uma variável é redefinida em um sub-bloco, ambas são acessíveis. No sub-bloco, porém, qualquer
referência não qualificada fará acesso à variável de nível mais interno.
Uma variável declarada em um bloco deixa de existir quando o bloco termina.
SQL>
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
VARIABLE MSG VARCHAR2(200)
DECLARE
WNUM
NUMBER
:= 12;
WCHAR
VARCHAR2(20) := 'WCHAR EXTERNA';
WUNICO
NUMBER
:= 5;
BEGIN
DECLARE
WNUM
NUMBER
:= 10;
WCHAR
VARCHAR2(20) := 'WCHAR INTERNA';
WTOTAL
NUMBER;
BEGIN
WTOTAL := WNUM + WUNICO;
:MSG
:= WCHAR||'-'||WTOTAL;
END;
END;
/
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT MSG
MSG
--------------------------------------------------------WCHAR INTERNA-15
L01_16
Pudemos comprovar que no bloco interno, por default, a referência é sempre à variável local.
CAPÍTULO 1: INTRODUÇÃO - 36
PL/SQL9I – BÁSICO E AVANÇADO
A seguir, faremos referência à variável wtotal no bloco principal e receberemos um erro indicando que
neste bloco não há declaração da variável wtotal.
Esta variável deixa de existir quando passarmos pelo End do sub-bloco.
SQL> DECLARE
2
WNUM
NUMBER
:= 12;
3
WCHAR
VARCHAR2(20) := 'WCHAR EXTERNA';
4
WUNICO
NUMBER
:= 5;
5 BEGIN
6
DECLARE
7
WNUM
NUMBER
:= 10;
8
WCHAR
VARCHAR2(20) := 'WCHAR INTERNA';
9
WTOTAL
NUMBER;
10
BEGIN
11
WTOTAL := WNUM + WUNICO;
12
:MSG
:= WCHAR||'-'||WTOTAL;
13
END;
14
WTOTAL
:= WTOTAL + 1;
15 END;
16 /
WTOTAL
:= WTOTAL + 1;
*
ERRO na linha 14:
ORA-06550: linha 14, coluna 3:
PLS-00201: o identificador 'WTOTAL' deve ser declarado
ORA-06550: linha 14, coluna 3:
PL/SQL: Statement ignored
L01_17
CAPÍTULO 1: INTRODUÇÃO - 37
PL/SQL9I – BÁSICO E AVANÇADO
QUALIFICAÇÃO
Para controlarmos a visibilidade e termos acesso às variáveis dos blocos de nível superior, devemos
qualificar qualquer referência às variáveis externas nos blocos internos.
SQL> BEGIN
2
<<EXT>>
3
DECLARE
4
WCOD
NUMBER := 1;
5
BEGIN
6
DECLARE
7
WCOD
NUMBER:= 5;
8
BEGIN
9
IF WCOD <> EXT.WCOD THEN
10
:MSG := 'DIFERENTE';
11
ELSE
12
:MSG := 'IGUAL';
13
END IF;
14
END;
15
END;
16 END;
17 /
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT MSG
MSG
------------------------------------------DIFERENTE
L01_18
A qualificação pode ser feita com o uso de um Label ou com o nome de um subprograma (será visto
no tópico referente a subprogramas).
CAPÍTULO 1: INTRODUÇÃO - 38
PL/SQL9I – BÁSICO E AVANÇADO
RESTRIÇÕES
O PL/SQL não admite referências a variáveis não declaradas, mesmo que elas venham a ser
declaradas posteriormente. Para que utilizemos uma variável (mesmo sendo na declaração de outra),
devemos ter efetuado sua declaração primeiro.
SQL> DECLARE
2
WCOD
3
WVAL
4 BEGIN
5
WCOD := 1;
6 END;
7 /
WCOD
NUMBER := WVAL + 1;
NUMBER := 0;
NUMBER := WVAL + 1;
*
ERRO na linha 2:
ORA-06550: linha 2, coluna
PLS-00320: a declaração do
incorreta
ORA-06550: linha 2, coluna
PL/SQL: Item ignored
ORA-06550: linha 5, coluna
PLS-00320: a declaração do
incorreta
ORA-06550: linha 5, coluna
PL/SQL: Statement ignored
30:
tipo desta expressão está incompleta ou
20:
3:
tipo desta expressão está incompleta ou
L01_19
3:
A declaração de variáveis deve ser feita de forma unitária. Não podemos declarar, de uma vez,
diversas variáveis referentes a um mesmo tipo.
SQL> DECLARE
2
A, B, C
NUMBER;
3 BEGIN
4
A:= 0;
5 END;
6 /
A, B, C
NUMBER;
*
ERRO na linha 2:
ORA-06550: linha 2, coluna 4:
PLS-00103: Encontrado o símbolo "," quando um dos seguintes
símbolos era esperado:
constant exception <an identifier>
<a double-quoted delimited-identifier> table LONG_ double ref
char time timestamp interval date binary national character
nchar
L01_20
CAPÍTULO 1: INTRODUÇÃO - 39
PL/SQL9I – BÁSICO E AVANÇADO
No exemplo abaixo foi feita a declaração de três variáveis boleanas. A primeira e a segunda formas de
atribuição são ilegais, porque não é possível a conversão de valores entre valores numéricos e
variáveis boleanas ou entre valores alfanuméricos e variáveis boleanas, porém a terceira forma de
atribuição é válida; se a expressão for verdadeira, será atribuído True à variável, caso contrário, False.
SQL> DECLARE
2
WB1
BOOLEAN;
3
WB2
BOOLEAN;
4
WB3
BOOLEAN;
5 BEGIN
6
WB1 := 1;
7
WB2 := 'TRUE';
8
WB3 := (5 > 3);
9 END;
10 /
WB1 := 1;
*
ERRO na linha 6:
ORA-06550: linha 6, coluna 10:
PLS-00382: a expressão é do tipo incorreto
ORA-06550: linha 6, coluna 3:
PL/SQL: Statement ignored
ORA-06550: linha 7, coluna 10:
PLS-00382: a expressão é do tipo incorreto
ORA-06550: linha 7, coluna 3:
PL/SQL: Statement ignored
L01_21
CAPÍTULO 1: INTRODUÇÃO - 40
PL/SQL9I – BÁSICO E AVANÇADO
CONVERSÃO IMPLÍCITA
Quando atribuímos uma variável de um determinado tipo a uma outra variável de outro tipo, ocorre
uma conversão implícita de tipo de dado.
A conversão implícita é possível em PL/SQL, mas não é recomendada, porque pode dificultar a
performance e, ainda, sofrer modificações de uma versão para outra do software. A tabela a seguir
indica o que é possível converter:
Tipo
B_Integer
Blob
Char
Clob
Date
Long
Number
P_Integer
Raw
Urowid
Varchar2
B_Integer Blob Char CLOB Date Long Number P_Integer Raw Urowid Varchar2
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
S
Devemos tomar cuidado adicional com as conversões implícitas, pois o PL/SQL utiliza as funções de
SQL, porém, com os parâmetros de formato defaults.
Assim, a execução de uma rotina em um servidor pode produzir um resultado, enquanto em outro
poderá produzir outro em função de parâmetros de ambiente (Nls, por exemplo).
CAPÍTULO 1: INTRODUÇÃO - 41
PL/SQL9I – BÁSICO E AVANÇADO
SQL>
2
3
4
5
6
7
8
9
10
DECLARE
DT_HOJE
DATE := SYSDATE;
DESCRICAO
VARCHAR2(100) := '123';
CD_MAT
NUMBER(5)
:= 0;
BEGIN
CD_MAT
:= DESCRICAO;
DESCRICAO := DT_HOJE;
:P1
:= DESCRICAO||'-'||CD_MAT||'-'||DT_HOJE;
END;
/
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT P1
P1
---------------------------------------------------------25/05/03-123-25/05/03
L01_22
No exemplo, as atribuições não resultaram em erros, pois os valores eram compatíveis. Qual o
resultado obtido se a variável Descrição fosse inicializada com o valor ‘abc’ em vez de ‘123’?
CAPÍTULO 1: INTRODUÇÃO - 42
PL/SQL9I – BÁSICO E AVANÇADO
COMANDOS
IF
O comando IF verifica uma condição, e dependendo do resultado realiza uma ou outra ação. Permite
a execução condicional de uma determinada ação.
SINTAXE
IF <condição>
THEN
<seqüência de comandos>
END IF;
IF <condição>
THEN
<seqüência de comandos>
ELSE
<seqüência de comandos>
END IF;
IF <condição>
THEN
<seqüência de comandos>
ELSIF <condição> THEN
<seqüência de comandos>
ELSE
<seqüência de comandos>
END IF;
A sintaxe acima contém as formas básica de um comando IF. Nas três formas sintáticas, observamos
a presença do End If para encerrar o comando.
Na terceira forma sintática (IF..ELSIF…END IF), mais especificamente, apenas um End If é
necessário, independente da quantidade de Elsif’s presentes no comando. Devemos considerar que o
End If encerra o IF isolado.
CAPÍTULO 1: INTRODUÇÃO - 43
PL/SQL9I – BÁSICO E AVANÇADO
CONDIÇÃO
CAPÍTULO 1: INTRODUÇÃO - 44
PL/SQL9I – BÁSICO E AVANÇADO
SQL> VARIABLE MSG VARCHAR2(100)
SQL> DECLARE
2
VALOR
NUMBER := &VAL;
3 BEGIN
4
IF VALOR > 0 THEN
5
:MSG := 'Valor maior que zero';
6
ELSIF VALOR = 0 THEN
7
:MSG := 'Valor igual a zero';
8
ELSE
9
:MSG := 'Valor menor que zero';
10
END IF;
11 END;
12 /
Entre o valor para val: -5
antigo
2:
VALOR
NUMBER := &VAL;
novo
2:
VALOR
NUMBER := -5;
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT MSG
MSG
---------------------------------------------Valor menor que zero
L01_23
No exemplo, declaramos, mais uma vez, uma variável no ambiente SQL*Plus e a utilizamos dentro do
programa PL/SQL. Este tipo de variável é chamado de Bind, pois está declarada no ambiente e não
no programa.
Usaremos freqüentemente este tipo de variável para retornar informação para o SQL*Plus.
CAPÍTULO 1: INTRODUÇÃO - 45
PL/SQL9I – BÁSICO E AVANÇADO
SELECT INTO
Dentro da PL/SQL, o comando Select ganha a cláusula Into a fim de obter dados da linha lida para
variáveis do programa. Desta forma, poderemos manusear os dados obtidos.
A cláusula Into segue imediatamente a lista de variáveis da cláusula Select e precede a cláusula
From.
Devemos informar uma área de recepção capaz de comportar todos os dados lidos; caso contrário,
receberemos erro na execução.
O comando Select Into somente faz leitura de uma row. Caso venhamos a selecionar mais de uma
row usando este comando, ocorrerá um erro (Too Many Rows) indicando o excesso. A leitura de
diversas rows é feita com o uso do Cursor (será visto mais adiante).
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
DECLARE
SALARIO
NUMBER;
ENDROW
ROWID;
BEGIN
SELECT VL_SAL, ROWID INTO SALARIO, ENDROW
FROM FUNC
WHERE CD_MAT = 150;
IF SALARIO < 2000 THEN
SALARIO := SALARIO * 1.3;
ELSIF SALARIO IS NULL THEN
SALARIO := 1500;
ELSE
SALARIO := SALARIO * 1.15;
END IF;
UPDATE FUNC SET VL_SAL = SALARIO
WHERE ROWID = ENDROW;
:MSG := 'Salário = '||SALARIO;
COMMIT;
END;
/
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT MSG
MSG
-------------------------------------------------Salário = 3517,712
Fizemos uma leitura e aproveitamos para trazer o Rowid da linha, de tal forma que a atualização se
processasse de forma mais rápida.
CAPÍTULO 1: INTRODUÇÃO - 46
PL/SQL9I – BÁSICO E AVANÇADO
O comando Select fez referência a duas colunas, portanto, na cláusula Into, informamos duas
variáveis para receber os dados lidos na mesma ordem da seleção.
Nesse Select, tínhamos a certeza da quantidade de linhas retornadas uma vez que a pesquisa foi feita
pela PK, resultando em uma única linha de retorno.
GOTO
O comando GoTo efetua um desvio incondicional para um Label. O Label deve ser único dentro do
escopo e deve preceder um comando ou um bloco PL/SQL.
Além de ser usado para desvios, um Label também serve como qualificador de variáveis, como foi
mostrado no item Qualificação.
Veremos no próximo capítulo que a PL/SQL possui diversas estruturas de iteração e que raramente
teremos necessidade de utilizar um comando GoTo.
SQL>
2
3
4
5
6
7
8
9
10
DECLARE
CONTA
NUMBER := 0;
BEGIN
<<INICIO>>
CONTA := CONTA + 1;
IF CONTA < 5 THEN
GOTO INICIO;
END IF;
END;
/
Procedimento PL/SQL concluído com sucesso.
L01_25
No exemplo, aparece um Label (início) que deve ser codificado entre << e >>. No comando GoTo, os
símbolos (<< e >>) não devem ser usados.
RESTRIÇÕES
Existem algumas situações em que o comando GoTo não pode ser usado:
◊
Desvio para dentro de um IF ou Loop (GoTo Proximo_IF).
◊
Desvio para dentro de um subbloco (GoTo Subbloco).
◊
Desvio para fora de um subprograma (GoTo Para_Fora).
◊
Desvio para dentro de um bloco a partir da área de exceção (GoTo Inicio).
CAPÍTULO 1: INTRODUÇÃO - 47
PL/SQL9I – BÁSICO E AVANÇADO
NULL
Este comando, explicitamente, indica que não há ação a ser feita. Serve para compor certas situações
em que um comando é exigido, mas nenhuma ação é, realmente, necessária.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DECLARE
VEZ
NUMBER := 1;
BEGIN
<<INICIO>>
VEZ := VEZ + 1;
IF VEZ > 10 THEN
GOTO FIM;
ELSE
VEZ := VEZ**2 - 1;
END IF;
GOTO INICIO;
<<FIM>>
NULL;
END;
/
L01_26
Procedimento PL/SQL concluído com sucesso.
Já vimos anteriormente que todo Label deve preceder um comando ou um bloco de PL/SQL. No
exemplo anterior, a presença do comando Null teve a finalidade de validar o posicionamento do Label
Fim. Caso o comando não fosse adicionado, não poderíamos programar o Fim antes de End (não é
um comando), que é, na verdade, fim de bloco.
CAPÍTULO 1: INTRODUÇÃO - 48
PL/SQL9I – BÁSICO E AVANÇADO
CASE
A cláusula Case permite a montagem de uma estrutura similar a IF..THEN …ELSE. Ao observarmos a
sintaxe abaixo veremos que existem duas formas sintáticas de utilizarmos o comando.
SINTAXE
Na Primeira forma usamos uma expressão que será comparada em cada uma das cláusulas When,
funcionando como um “seletor” indicativo dos comandos a serem executados.
Caso não venhamos a definir uma cláusula Else e nenhuma das expressões for compatível com a
expressão seletora, o retorno será Null.
Na Segunda forma não temos uma expressão de comparação, neste caso, cada cláusula When
conterá uma condição a ser verificada.
Cada cláusula When é executada uma única vez e em ordem de apresentação. Quando uma condição
é satisfeita, os comandos subordinados são executados e as cláusulas When subseqüentes não são
avaliadas.
Caso nenhuma condição seja satisfeita e não tenhamos definido uma cláusula Else, ocorrerá a
exception Case_Not_Found.
CAPÍTULO 1: INTRODUÇÃO - 49
PL/SQL9I – BÁSICO E AVANÇADO
Nesta primeira forma a comparação é feita pela igualdade. Se valor = 0 ou valor = 1 ou etc.
SQL> VARIABLE MSG VARCHAR2(100)
SQL> DECLARE
2
VALOR
NATURAL := &VAL;
3 BEGIN
4
CASE VALOR
5
WHEN 0 THEN
6
:MSG := 'Valor zero';
7
WHEN 1 THEN
8
:MSG := 'Valor um';
9
WHEN 2 THEN
10
:MSG := 'Valor dois';
11
WHEN 3 THEN
12
:MSG := 'Valor três';
13
ELSE
14
:MSG := 'Valor maior que 3';
15
END CASE;
16 END;
17 /
Entre o valor para val: 0
antigo
2:
VALOR
NATURAL := &VAL;
novo
2:
VALOR
NATURAL := 0;
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT MSG
MSG
---------------------------------------------Valor zero
L01_27
CAPÍTULO 1: INTRODUÇÃO - 50
PL/SQL9I – BÁSICO E AVANÇADO
Nesta forma sintática, podemos definir uma condição diferente para cada cláusula When.
SQL> VARIABLE MSG VARCHAR2(100)
SQL> DECLARE
2
VALOR
NUMBER := &VAL;
3 BEGIN
4
CASE WHEN VALOR > 0 THEN
5
:MSG := 'Valor maior que zero';
6
WHEN VALOR = 0 THEN
7
:MSG := 'Valor igual a zero';
8
ELSE
9
:MSG := 'Valor menor que zero';
10
END CASE;
11 END;
12 /
Entre o valor para val: -5
antigo
2:
VALOR
NUMBER := &VAL;
novo
2:
VALOR
NUMBER := -5;
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT MSG
MSG
--------------------------------------------Valor menor que zero
L01_28
CAPÍTULO 1: INTRODUÇÃO - 51
PL/SQL9I – BÁSICO E AVANÇADO
FUNÇÕES
Em PL/SQL, podemos utilizar quase todas as funções de SQL diretamente nos comandos de
atribuição ou em IFs.
FUNÇÕES DE SQL VÁLIDAS EM PL/SQL
◊
Numéricas Simples – Abs, Bitand, Ceil, Exp, Floor, Ln, Log, Mod, Power, Round, Sign, Sqrt,
Trunc.
◊
Trigonométricas – Acos, Asin, Atan, Atan2, Cos, Cosh, Sin, Sinh, Tan, Tanh.
◊
Alfanuméricas – Chr, Concat, Initcap, Lower, Lpad, Ltrim, Nls_Initcap, Nls_Lower, Nls_Upper,
Replace, Rpad, Rtrim, Soundex, Substr, Substrb, Translate, Upper, Trim.
◊
Alfanuméricas que retornam Valores Numéricos – Ascii, Instr, Instrb, Length, Lengthb, Nlssort.
◊
Datas – Sysdate, Add_Months, Curret_Date, Curret_Timestamp, DBTimezone, Extract, From_Tz,
Last_Day,
LocalTimestamp,
Months_Between,
New_Time,
Next_Day,
NumToDsInterval,
NumToYmInterval,
Round,
SessionTimeZone,
Sysdate,
SysTimestamp,
To_DsInterval,
To_Timestamp, To_Timestamp_Ltz, To_Timestamp_Tz, To_YmInterval, Tz_Offset, Trunc.
◊
Conversão – CharToRowid, Convert, HexToRaw, RawToHex, RowidToChar, To_Blob, To_Char,
To_Clob, To_Date, To_Multi_Byte, To_Nclob, To_Number, To_Single_Byte, Translate Using.
◊
Outras – Bfilename, Empty_Blob ou Empty_Clob, Greatest, Least, Nvl, Nls_Charset_Decl_Len,
Nls_Charset_Id, Nls_Charset_Name, Sys_Context, Sys_Guid, Uid, User e Userenv.
FUNÇÕES DE SQL INVÁLIDAS EM PL/SQL
Das funções escalares, temos Decode, Dump e Vsize.
Nenhuma das funções de grupo pode ser usada diretamente em PL/SQL (Avg, Max, Min, StdDev,
Count, etc.).
Das funções de objeto, temos Deref, Ref e Value. Para nos utilizarmos de uma destas funções, em
PL/SQL, devemos acioná-la de dentro de um comando Select.
CAPÍTULO 1: INTRODUÇÃO - 52
PL/SQL9I – BÁSICO E AVANÇADO
SQLCODE
Retorna o código associado ao último erro ocorrido. Só pode ser usada em PL/SQL (não disponível
em SQL).
Quando ocorre um erro na execução de um programa PL/SQL, podemos ter acesso ao código para
adotarmos as ações apropriadas.
Isto se dá através da cláusula Exception. A função SqlCode obtém o código ocorrido, o que permite o
acesso à mensagem de erro.
SQL>
SQL>
2
3
4
5
6
7
8
9
10
11
VARIABLE MSG VARCHAR2(100)
DECLARE
SAL
NUMBER;
BEGIN
SELECT VL_SAL INTO SAL
FROM FUNC
WHERE CD_MAT > 17000;
EXCEPTION
WHEN OTHERS THEN
:MSG := SQLCODE;
END;
/
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT MSG
MSG
--------------------------------------------100
L01_29
No exemplo, o comando Select executado não retornou nenhuma linha; desta forma, recebemos o
erro NotFound, que corresponde ao código 100 (SqlCode).
Quando ocorre um erro na execução de um programa PL/SQL, podemos ter acesso ao código para
adotarmos as ações apropriadas. Isso se dá através da cláusula Exception, que será tratada em
detalhes em outro tópico.
A função SqlCode obtém o código ocorrido, o que permite o acesso à mensagem de erro.
CAPÍTULO 1: INTRODUÇÃO - 53
PL/SQL9I – BÁSICO E AVANÇADO
SQLERRM
A função SqlErrm retorna o texto correspondente ao erro ocorrido.
Só pode ser usada em PL/SQL (não disponível em SQL). Retorna o texto correspondente ao erro
ocorrido. Recebe como parâmetro o código do erro que desejamos decodificar.
SQL>
2
3
4
5
6
7
8
9
10
11
DECLARE
SAL
NUMBER;
BEGIN
SELECT VL_SAL INTO SAL
FROM FUNC
WHERE CD_MAT > 17000;
EXCEPTION
WHEN OTHERS THEN
:MSG := SQLERRM(SQLCODE);
END;
/
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT MSG
MSG
-------------------------------------------ORA-01403: dados não encontrados
L01_30
O exemplo acima é o mesmo da anterior, com a diferença de que, além do código do erro, obtivemos
também a mensagem correspondente.
A função SqlErrm recebe como parâmetro o código do erro que desejamos decodificar.
CAPÍTULO 1: INTRODUÇÃO - 54
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 1
Neste laboratório, passaremos informações para os programas usando variáveis de substituição e o
retorno da informação será feito através de variáveis Bind.
Antes de iniciar, para que as tabelas não fiquem demasiadamente cheias , refaça a base de dados.
1) Observe as declarações a seguir. Indique quais delas não estão corretas e por quê.
DECLARE
ID
X, Y, Z
END
IND_ESTOQUE
99_MES
%TOTAL
TOTAL_DE_PECAS_VENDIDAS_POR_SEMESTRE
BEGIN
NULL;
END;
/
NUMBER(04);
VARCHAR2(10) := "ABC";
DATE NOT NULL;
BOOLEAN
:= 1;
DATE;
NUMBER := 21V99;
NUMBER := 0;
CAPÍTULO 1: INTRODUÇÃO - 55
PL/SQL9I – BÁSICO E AVANÇADO
2) Observe o programa PL/SQL apresentado abaixo e avalie o escopo das variáveis, como se pede.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
DECLARE
PESO
VARCHAR2(3) := '600';
MSG
VARCHAR2(255);
BEGIN
DECLARE
PESO
NUMBER(3) := 1;
MSG
VARCHAR2(255);
LOCAL
VARCHAR2(50) := 'RIO DE JANEIRO';
BEGIN
PESO := PESO + 1;
-MSG := PESO || ' ESTÁ ACIMA DO PADRÃO'; -LOCAL:= 'SÃO PAULO '|| LOCAL;
-END;
PESO := PESO + 1;
-MSG := PESO || ' ESTÁ ACIMA DO PADRÃO';
-LOCAL:= 'SÃO PAULO '|| LOCAL;
-END;
/
(A)
(B)
(C)
(D)
(E)
(F)
A) Qual o valor de peso no bloco interno?
B) Qual o valor de msg no bloco interno?
C) Qual o valor de local no bloco interno?
D) Qual o valor de peso no bloco externo?
E) Qual o valor de msg no bloco externo?
F) Qual o valor de local no bloco externo?
CAPÍTULO 1: INTRODUÇÃO - 56
PL/SQL9I – BÁSICO E AVANÇADO
3) Construa um bloco PL/SQL que receba um valor, verifique se o valor é válido e informe ao operador
se foi recebido NULL , um valor numérico positivo, um valor numérico negativo ou zero.
4) Crie um bloco PL/SQL para incluir linhas na tabela de departamentos. O código e o nome do
departamento devem ser recebidos como parâmetro (usar Accept). A coluna código de gerente não
deve receber valor na inclusão.
O código do departamento é de preenchimento obrigatório, mas o nome é opcional. Se não for
informado, deve ser gravado ‘Departamento sem valor’.
5) Crie um bloco PL/SQL que retorne a quantidade de funcionários alocados a um determinado
departamento recebido como parâmetro. O código do departamento é de preenchimento obrigatório.
Lembre-se que o usuário poderá passar a informação em letras maiúsculas ou minúsculas.
6) Crie um bloco de PL/SQL que cadastre novos departamentos ou consulte dados de departamento,
conforme os parâmetros recebidos:
Parâmetros:
◊
Código do departamento, preenchimento obrigatório.
◊
Operação (I ou C), preenchimento obrigatório.
◊
Nome do departamento, obrigatório na inclusão.
◊
Código do gerente, opcional.
Críticas:
◊
Na inclusão, deve ser verificado se o departamento já existe.
◊
Na consulta, deve ser verificada a existência do departamento.
◊
Se o código do gerente for preenchido na inclusão, deve ser garantido que esta matrícula
exista na tabela de funcionários.
7) Crie um bloco PL/SQL que calcule a seqüência de Fibonacci (1 1 2 3 5 8 13 21 34 55 ...) para um
determinado número de elementos, recebido como parâmetro e variando de 1 a 30.
8) Crie um bloco PL/SQL que receba como parâmetro uma data e calcule o número de dias úteis no
mês correspondente.
9) Num triângulo ABC tem-se A= 15º, sen B = (÷3) /2 e sen C = (÷2) / 2. Determine os ângulos B e C
montando um bloco de PL/SQL.
10) Monte um bloco de PL/SQL que receba um ângulo como parâmetro e forneça o seno, cosseno e
tangente do ângulo. O ângulo será fornecido em graus. Se for informado um valor de ângulo negativo,
devemos convertê-lo em positivo e prosseguir o cálculo.
11) Crie um bloco de PL/SQL que receba como parâmetro um valor alfanumérico com até três
caracteres de comprimento e transforme-o em numérico (use a seqüência do alfabeto: A = 1, B = 2 e
assim por diante).
CAPÍTULO 1: INTRODUÇÃO - 57
PL/SQL9I – BÁSICO E AVANÇADO
12) Crie um bloco de PL/SQL que receba como parâmetro um nome e indique quantas partes o
compõem. A separação entre as palavras do nome é feita com espaços em branco (lembre-se que
podem haver vários espaços em branco antes, depois e entre as palavras).
13) Crie um bloco de PL/SQL que receba como parâmetro um número de mês e verifique quantos
funcionários fazem aniversário no mês especificado.
14) Crie um bloco de PL/SQL que inclua novos departamentos na tabela Depto, com as seguintes
características:
◊
Receba como parâmetro o código do gerente e o nome do departamento (ambos
obrigatórios).
◊
Obtenha o último código do departamento gravado na tabela e adicione 1, gerando um novo
código (F01 será transformado em F02, F26 será transformado em G00 e assim por diante).
◊
O código do departamento contábil não deve ser preenchido.
15) Faça um bloco PL/SQL que receba como parâmetro um código de departamento e calcule o total
de salários dos funcionários. Arredonde o resultado para a ordem de grandeza das centenas.
16) Faça um bloco PL/SQL que receba como parâmetro uma opção alfanumérica e calcule um dos
seguintes valores:
◊
◊
◊
o timestamp atual;
o nome por extenso da timezone do banco de dados;
o mes e o dia em que o exercício está sendo feito.
17) Repita o exercício anterior usando “expressão case” .
CAPÍTULO 1: INTRODUÇÃO - 58
PL/SQL9I – BÁSICO E AVANÇADO
CAPÍTULO 2: PROCESSAMENTO REPETITIVO
Neste capítulo estudaremos os comandos capazes de gerar processamento repetitivo e os artifícios
que permitirão a leitura e manipulação de diversas linhas de uma tabela.
CURSOR
As linguagens convencionais, tais como Cobol e PL/1, não estão preparadas para trabalhar com
tabelas. Sabemos, no entanto, que o resultado de uma operação relacional é sempre uma tabela
composta de zero, uma ou mais rows, de acordo com o critério de restrição informado.
A fim de solucionar este problema, foi criado um artifício chamado cursor, que permitisse às
linguagens convencionais fazerem acesso a uma base de dados relacional.
A PL/SQL utiliza este artifício para acesso a um conjunto de linhas resultante de uma operação com o
banco de dados.
DECLARAÇÃO
Na declaração de um cursor, já é feita a associação com o comando Select capaz de obter as linhas
do banco de dados.
SINTAXE
CURSOR <nome cursor> [ ( <parâmetro> <tipo> [, <parâmetro <tipo>...] ) ]
RETURN <tipo Record> |
<variável%TYPE |
<table.column>%TYPE |
<TABLE>%rowtype
IS <comando SELECT>
O cursor deve ser criado antes de ser usado, por isso é necessário que a declaração esteja presente
no bloco em que usaremos o cursor ou em um bloco mais externo.
O tipo do retorno deve representar um registro correspondendo às colunas trazidas pelo comando
Select.
A manipulação de um cursor é semelhante àquela usada para arquivos convencionais: abrir (Open),
ler uma linha (Fetch into) e fechar (Close).
CAPÍTULO 2: PROCESSAMENTO REPETITIVO - 59
PL/SQL9I – BÁSICO E AVANÇADO
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DECLARE
CURSOR C1 IS SELECT CD_DEPTO FROM DEPTO;
CURSOR C2 (PDEPTO IN VARCHAR2 := 'A00') IS
SELECT COUNT(*), MAX(VL_SAL)
FROM FUNC
WHERE CD_DEPTO = PDEPTO;
CURSOR C3 (PDEPTO IN VARCHAR2 := 'B01')
RETURN FUNC%ROWTYPE IS
SELECT * FROM FUNC
WHERE CD_DEPTO = PDEPTO
ORDER BY VL_SAL DESC;
BEGIN
NULL;
END;
/
Procedimento PL/SQL concluído com sucesso.
L02_01
CAPÍTULO 2: PROCESSAMENTO REPETITIVO - 60
PL/SQL9I – BÁSICO E AVANÇADO
ABERTURA
A abertura de um cursor é realizada com o comando Open. Os parâmetros devem ser fornecidos a
tempo de Open para que seja possível a execução do comando Select.
SINTAXE
OPEN <nome do cursor> [(<parâmetro> [, <parâmetro> . . . ])]
Os parâmetros declarados com valor inicial podem, a tempo de Open, não receber nenhum valor e
utilizar os valores iniciais previamente declarados.
Para a passagem dos parâmetros, podemos usar a notação posicional ou a notação nomeada.
A notação nomeada aparece abaixo. Quando utilizamos o nome do parâmetro e os símbolos “=>”,
podemos passar os parâmetros em qualquer ordem.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
DECLARE
CURSOR C1 IS SELECT CD_DEPTO FROM DEPTO;
CURSOR C2 (PDEPTO IN VARCHAR2 := 'A00') IS
SELECT COUNT(*), MAX(VL_SAL)
FROM FUNC
WHERE CD_DEPTO = PDEPTO;
CURSOR C3 (PDEPTO IN VARCHAR2 := 'B01')
RETURN FUNC%ROWTYPE IS
SELECT * FROM FUNC
WHERE CD_DEPTO = PDEPTO
ORDER BY VL_SAL DESC;
CURSOR C4 (P1 IN VARCHAR2, P2 IN NUMBER) IS
SELECT * FROM FUNC
WHERE CD_DEPTO = P1 AND NR_CARGO = P2;
BEGIN
OPEN C1;
OPEN C2;
OPEN C3 ('D11');
OPEN C4 (P2 => 20, P1 => 'A00');
END;
/
L02_02
Procedimento PL/SQL concluído com sucesso.
CAPÍTULO 2: PROCESSAMENTO REPETITIVO - 61
PL/SQL9I – BÁSICO E AVANÇADO
FETCH
Associa os valores da linha atual às variáveis do programa e, posteriormente, posiciona o cursor na
próxima linha do conjunto de linhas resultantes da operação de Select.
SINTAXE
FETCH <nome do cursor> INTO <variável> [,<variável> ...]
As variáveis devem ser informadas no Fetch na mesma ordem das expressões correspondentes no
comando Select. Para cada expressão, deve haver uma variável receptora. O tipo de dado da variável
e da expressão deve ser compatível.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
DECLARE
CURSOR C1 IS SELECT CD_DEPTO FROM DEPTO;
CURSOR C2 (PDEPTO IN VARCHAR2 := 'A00') IS
SELECT COUNT(*), MAX(VL_SAL)
FROM FUNC
WHERE CD_DEPTO = PDEPTO;
CURSOR C3 (PDEPTO IN VARCHAR2 := 'B01')
RETURN FUNC%ROWTYPE IS
SELECT * FROM FUNC
WHERE CD_DEPTO = PDEPTO
ORDER BY VL_SAL DESC;
CURSOR C4 (P1 IN VARCHAR2, P2 IN NUMBER) IS
SELECT * FROM FUNC
WHERE CD_DEPTO = P1 AND NR_CARGO = P2;
-VCD_DEPTO DEPTO.CD_DEPTO%TYPE;
VQTD
NUMBER;
VMAX
NUMBER;
BEGIN
OPEN C1;
<<INICIO>>
FETCH C1 INTO VCD_DEPTO;
OPEN C2(VCD_DEPTO);
FETCH C2 INTO VQTD, VMAX;
OPEN C3 (VCD_DEPTO);
OPEN C4 (P2 => 20, P1 => 'A00');
<<LOOP>>
NULL;
END;
/
L02_03
Procedimento PL/SQL concluído com sucesso.
CAPÍTULO 2: PROCESSAMENTO REPETITIVO - 62
PL/SQL9I – BÁSICO E AVANÇADO
%ROWTYPE
A fim de facilitar a recepção de informações das linhas lidas, a PL/SQL implementa o atributo
%Rowtype, que gera uma área (registro) capaz de armazenar, exatamente, uma linha com o layout da
linha de uma tabela do banco de dados ou com o layout apenas das colunas (ou expressões)
referenciadas no Select associado a um cursor.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
DECLARE
CURSOR C1 IS SELECT CD_DEPTO FROM DEPTO;
CURSOR C2 (PDEPTO IN VARCHAR2 := 'A00') IS
SELECT COUNT(*) COUNT, MAX(VL_SAL)
FROM FUNC
WHERE CD_DEPTO = PDEPTO;
CURSOR C3 (PDEPTO IN VARCHAR2 := 'B01')
RETURN FUNC%ROWTYPE IS
SELECT * FROM FUNC
WHERE CD_DEPTO = PDEPTO
ORDER BY VL_SAL DESC;
CURSOR C4 (P1 IN VARCHAR2, P2 IN NUMBER) IS
SELECT * FROM FUNC
WHERE CD_DEPTO = P1 AND NR_CARGO = P2;
-VCD_DEPTO DEPTO.CD_DEPTO%TYPE;
RTOTAL
C2%ROWTYPE;
RFUNC
FUNC%ROWTYPE;
BEGIN
OPEN C1;
<<INICIO>>
FETCH C1 INTO VCD_DEPTO;
OPEN C2(VCD_DEPTO);
FETCH C2 INTO RTOTAL;
:MSG := 'A quantidade de linhas é '||rtotal.count;
OPEN C3 (VCD_DEPTO);
OPEN C4 (P2 => 20, P1 => 'A00');
<<LOOP>>
FETCH C3 INTO RFUNC;
END;
/
Procedimento PL/SQL concluído com sucesso.
No exemplo acima, foram declarados dois registros: um baseado no layout dos dados selecionados
pelo cursor C2 (Rtotal) e outro no layout da linha da tabela Func (Rfunc).
Observe que a referência às colunas presentes no registro declarado é feita com o nome da coluna ou
expressão selecionada precedida do nome da área (rtotal.count).
CAPÍTULO 2: PROCESSAMENTO REPETITIVO - 63
PL/SQL9I – BÁSICO E AVANÇADO
Quando o comando Select for composto por expressões, é importante que utilizemos alias para as
expressões a fim de termos condições de referenciá-las posteriormente.
SQL>
2
3
4
5
6
7
8
9
10
11
12
DECLARE
CURSOR C1 IS SELECT VL_SAL * 1.2 PREVISAO
FROM FUNC;
RC1
C1%ROWTYPE;
BEGIN
IF RC1.PREVISAO > 500000 THEN
:MSG := 'TOTAL ACIMA DE 500000';
ELSE
:MSG := 'AUMENTO APROVADO';
END IF;
END;
/
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT MSG
MSG
--------------------------------------------------AUMENTO APROVADO
L02_05
No exemplo acima, não abrimos o cursor C1 (verbo Open). Neste caso, a área (Rowtype) está com
Null, assim como todas as variáveis de PL/SQL.
CAPÍTULO 2: PROCESSAMENTO REPETITIVO - 64
PL/SQL9I – BÁSICO E AVANÇADO
FECHAMENTO
Para fecharmos um cursor, devemos utilizar o verbo Close. Esse verbo libera a área reservada para a
montagem do conjunto de dados selecionados.
SINTAXE
CLOSE <nome do cursor>
CAPÍTULO 2: PROCESSAMENTO REPETITIVO - 65
PL/SQL9I – BÁSICO E AVANÇADO
CURSORES IMPLÍCITOS
Existem dois tipos de cursores em PL/SQL: um explícito, que acabamos de conhecer e que é
declarado nos programas de aplicação, e outro implícito, que é declarado implicitamente pelo Oracle.
O Oracle implicitamente abre um cursor para cada comando de SQL que não tenha associado um
cursor explícito, inclusive para Select Into. Podemos referenciar este cursor usando a palavra SQL.
Não podemos usar esta referência para executar os comandos Open, Fetch ou Close, porém
podemos utilizá-la para obter informações sobre o último comando executado.
Essas informações, as quais veremos a seguir, estão disponíveis tanto para cursores implícitos quanto
para cursores explícitos e são ditas atributos de cursor.
%FOUND
É um atributo de cursor que indica se a última operação de Fetch foi bem sucedida (para cursores
explícitos) ou se alguma linha foi afetada pelo último comando Insert, Update ou Delete ou, ainda, se o
comando Select Into retornou uma ou mais linhas (para cursores implícitos).
SQL> DECLARE
2
VCD_DEPTO
VARCHAR2(3) := '&DEPTO';
3
CURSOR C1
IS SELECT * FROM FUNC
4
WHERE CD_DEPTO = VCD_DEPTO;
5
RC1
C1%ROWTYPE;
6 BEGIN
7
DELETE FROM FUNC WHERE CD_DEPTO = 'A01';
8
IF SQL%FOUND THEN
9
OPEN C1;
10
<<LOOP>>
11
FETCH C1 INTO RC1;
12
IF C1%FOUND THEN
13
:MSG := :MSG ||' - '||RC1.CD_MAT;
14
GOTO LOOP;
15
END IF;
16
END IF;
17 END;
18 /
Entre o valor para depto: D11
antigo
2:
VCD_DEPTO
VARCHAR2(3) := '&DEPTO';
novo
2:
VCD_DEPTO
VARCHAR2(3) := 'D11';
Procedimento PL/SQL concluído com sucesso.
L02_06
Antes do primeiro Fetch, o valor do atributo é Null para cursores explícitos e também é Null antes de o
comando (Insert, Update, Delete ou Select Into) ser executado.
CAPÍTULO 2: PROCESSAMENTO REPETITIVO - 66
PL/SQL9I – BÁSICO E AVANÇADO
%NOTFOUND
É um atributo de cursor com lógica inversa ao atributo %Found. %Notfound retornará False se o último
comando Fetch retornar uma linha (para cursores explícitos) ou se o último comando Insert, Update ou
Delete não afetar nenhuma linha ou, ainda, se o último comando Select Into não retornar nenhuma
linha (para cursores implícitos).
SQL> DECLARE
2
VCD_DEPTO
VARCHAR2(3) := '&DEPTO';
3
CURSOR C1
IS SELECT * FROM FUNC
4
WHERE CD_DEPTO = VCD_DEPTO;
5
RC1
C1%ROWTYPE;
6 BEGIN
7
:MSG := 'Matrículas = ';
8
DELETE FROM FUNC WHERE CD_DEPTO = 'A01';
9
IF SQL%NOTFOUND THEN
10
GOTO FIM;
11
END IF;
12
OPEN C1;
13
<<LOOP>>
14
FETCH C1 INTO RC1;
15
IF C1%NOTFOUND THEN
16
GOTO FIM;
17
END IF;
18
:MSG := :MSG ||' - '||RC1.CD_MAT;
19
GOTO LOOP;
20
<<FIM>>
21
NULL;
22 END;
23 /
Entre o valor para depto: D11
antigo
2:
VCD_DEPTO
VARCHAR2(3) := '&DEPTO';
novo
2:
VCD_DEPTO
VARCHAR2(3) := 'D11';
Procedimento PL/SQL concluído com sucesso.
L02_07
Antes do primeiro Fetch, o valor do atributo é Null para cursores explícitos e também é Null antes de o
comando (Insert, Update, Delete ou Select Into) ser executado.
CAPÍTULO 2: PROCESSAMENTO REPETITIVO - 67
PL/SQL9I – BÁSICO E AVANÇADO
%ISOPEN
Permite que se verifique se um cursor está aberto ou não. Para cursores implícitos, o resultado será
sempre False uma vez que o Oracle fecha o cursor imediatamente após a operação e antes de a ação
retornar ao programa.
SQL> DECLARE
2
VCD_DEPTO
VARCHAR2(3) := '&DEPTO';
3
CURSOR C1
IS SELECT * FROM FUNC
4
WHERE CD_DEPTO = VCD_DEPTO;
5
RC1
C1%ROWTYPE;
6 BEGIN
7
:MSG := 'Matrículas = ';
8
OPEN C1;
9
<<LOOP>>
10
FETCH C1 INTO RC1;
11
IF C1%NOTFOUND THEN
12
GOTO FIM;
13
END IF;
14
:MSG := :MSG ||' - '||RC1.CD_MAT;
15
GOTO LOOP;
16
<<FIM>>
17
IF C1%ISOPEN THEN
18
CLOSE C1;
19
END IF;
20 END;
21 /
Entre o valor para depto: D11
antigo
2:
VCD_DEPTO
VARCHAR2(3) := '&DEPTO';
novo
2:
VCD_DEPTO
VARCHAR2(3) := 'D11';
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT MSG
MSG
------------------------------------------------------------------Matrículas = - 60 - 150 - 160 - 170 - 180 - 190 - 200 - 210 - 220
L02_08
CAPÍTULO 2: PROCESSAMENTO REPETITIVO - 68
PL/SQL9I – BÁSICO E AVANÇADO
%ROWCOUNT
Indica o número de linhas já lidas (Fetched) para os cursores explícitos ou o número de linhas
afetadas por um comando Insert, Update ou Delete ou, ainda, o número de linhas retornadas por um
comando Select Into (para cursores implícitos).
SQL> DECLARE
2
VCD_DEPTO
VARCHAR2(3) := '&DEPTO';
3
CURSOR C1
IS SELECT * FROM FUNC
4
WHERE CD_DEPTO = VCD_DEPTO;
5
RC1
C1%ROWTYPE;
6 BEGIN
7
:MSG := 'Matrículas = ';
8
OPEN C1;
9
<<LOOP>>
10
FETCH C1 INTO RC1;
11
IF C1%NOTFOUND OR C1%ROWCOUNT > 5 THEN
12
GOTO FIM;
13
END IF;
14
:MSG := :MSG ||' - '||RC1.CD_MAT;
15
GOTO LOOP;
16
<<FIM>>
17
IF C1%ISOPEN THEN
18
CLOSE C1;
19
END IF;
20 END;
21 /
Entre o valor para depto: A00
antigo
2:
VCD_DEPTO
VARCHAR2(3) := '&DEPTO';
novo
2:
VCD_DEPTO
VARCHAR2(3) := 'A00';
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT MSG
MSG
------------------------------------------------------Matrículas = - 10 - 110 - 120
L02_09
Quando o cursor é aberto, o atributo %Rowcount é zerado. Antes do primeiro Fetch, o atributo
continua com zero. O valor só é incrementado após o Fetch ter sido efetuado com sucesso.
Até que o primeiro comando de DML seja executado, o valor do atributo é Null para cursores
implícitos.
CAPÍTULO 2: PROCESSAMENTO REPETITIVO - 69
PL/SQL9I – BÁSICO E AVANÇADO
UPDATE PARA CURSOR
Com a sintaxe do comando Update apresentada a seguir, podemos selecionar uma determinada linha
usando cursor e em seguida atualizá-la, sem que seja necessária uma nova pesquisa.
SINTAXE
UPDATE <nome de tabela / VIEW>
SET <nome de coluna 1> = <expressão 1>
[,<nome de coluna 2> = <expressão 2> . . . ]
WHERE CURRENT OF <nome cursor>
A cláusula Where Current OF indica que será atualizada a linha atualmente apontada pelo cursor.
Para que esta operação seja legal, devemos utilizar a cláusula For Update no comando Select do
cursor associado.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
DECLARE
CURSOR C1
IS SELECT VL_SAL, CD_MAT FROM FUNC
FOR UPDATE OF VL_SAL;
C1%ROWTYPE;
RC1
BEGIN
OPEN C1;
<<LOOP>>
FETCH C1 INTO RC1;
IF C1%FOUND THEN
IF RC1.VL_SAL < 3000 THEN
UPDATE FUNC SET VL_SAL = VL_SAL * 1.3
WHERE CURRENT OF C1;
END IF;
GOTO LOOP;
END IF;
COMMIT;
END;
/
L02_10
Procedimento PL/SQL concluído com sucesso.
A cláusula For Update adicionada ao comando Select indica que todas as linhas escolhidas através da
condição presente na cláusula Where devem sofrer bloqueio (Lock) contra atualizações concorrentes.
A sintaxe OF (presente na cláusula For Update) indica que a coluna vl_sal será modificada na cláusula
Set do comando Update. Poderíamos, alternativamente, ter utilizado apenas For Update sem
mencionar qualquer coluna, que o efeito seria o mesmo. A sintaxe que inclui a palavra OF é
compatível com o padrão ANSI.
No caso do exemplo, todas as linhas da tabela Func foram bloqueadas e impedidas de serem
atualizadas até que o programa efetue um Commit.
CAPÍTULO 2: PROCESSAMENTO REPETITIVO - 70
PL/SQL9I – BÁSICO E AVANÇADO
DELETE PARA CURSOR
Da mesma forma que para o Update, o comando Delete também possui uma sintaxe especial para
trabalhar com cursores. Com esta sintaxe, poderemos remover a linha apontada pelo cursor sem
haver necessidade de estabelecermos uma nova condição de busca.
SINTAXE
DELETE FROM <nome da tabela / VIEW>
WHERE CURRENT OF <nome do cursor>
Precisamos garantir que a linha a ser removida esteja bloqueada contra atualizações concorrentes
antes de efetuarmos o comando Delete. Desta forma, a cláusula For Update deve ser usada no
comando Select do cursor.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DECLARE
CURSOR C1
IS SELECT VL_SAL, CD_MAT FROM FUNC
FOR UPDATE;
C1%ROWTYPE;
RC1
BEGIN
OPEN C1;
<<LOOP>>
FETCH C1 INTO RC1;
IF C1%FOUND THEN
IF RC1.VL_SAL IS NULL THEN
DELETE FROM FUNC WHERE CURRENT OF C1;
END IF;
GOTO LOOP;
END IF;
COMMIT;
END;
/
Procedimento PL/SQL concluído com sucesso.
A cláusula For Update adicionada ao comando Select indica que todas as linhas escolhidas através da
condição presente na cláusula Where devem sofrer bloqueio (Lock) contra atualizações concorrentes.
A sintaxe OF, neste caso, não foi usada.
Seu uso é possível, da mesma forma que ocorreu no comando Update. Poderíamos ter mencionado
qualquer coluna na indicação de bloqueio uma vez que o comando Delete não faz menção a qualquer
coluna especificamente. A sintaxe que inclui a palavra OF é compatível com o padrão ANSI.
No caso do exemplo, todas as linhas da tabela Func foram bloqueadas e impedidas de serem
atualizadas até que o programa efetue um Commit.
CAPÍTULO 2: PROCESSAMENTO REPETITIVO - 71
PL/SQL9I – BÁSICO E AVANÇADO
OUTROS COMANDOS PARA PROCESSAMENTO REPETITIVO
LOOP
A seqüência de comandos é executada um número infinito de vezes ou até que seja interrompida por
um comando Exit.
SINTAXE
LOOP
<seqüência de comandos>
END LOOP
LOOP
<seqüência de comandos>
IF .....
THEN
EXIT; -- encerra o loop
END IF;
END LOOP;
LOOP
<seqüência de comandos>
EXIT WHEN -- encerra o loop
END LOOP;
CAPÍTULO 2: PROCESSAMENTO REPETITIVO - 72
PL/SQL9I – BÁSICO E AVANÇADO
EXIT
O comando Exit tem a finalidade de encerrar um Loop (qualquer das formas). Quando o comando Exit
é executado, o loop é completado incondicionalmente e o controle passa para o próximo comando.
SINTAXE
EXIT [<LABEL>] [WHEN <condição>]
A clausula When oferece uma forma condicional de interrompermos o processamento.
A presença do Label indica que o comando também é qualificável com a utilização de um Label
precedente e, desta forma, o processo repetitivo pode ser nomeado.
SQL>
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
VARIABLE MSG VARCHAR2(1000)
DECLARE
CURSOR C1
IS SELECT VL_SAL, CD_MAT FROM FUNC;
RC1
C1%ROWTYPE;
BEGIN
OPEN C1;
:MSG := 'Matrículas = ';
LOOP
FETCH C1 INTO RC1;
IF C1%FOUND THEN
:MSG := :MSG||RC1.CD_MAT||' - ';
ELSE
EXIT;
END IF;
END LOOP;
END;
/
Procedimento PL/SQL concluído com sucesso.
L02_12
O exemplo usa a forma básica do comando Exit. Nos próximos exemplos, usaremos sintaxes
alternativas.
CAPÍTULO 2: PROCESSAMENTO REPETITIVO - 73
PL/SQL9I – BÁSICO E AVANÇADO
WHILE
O comando While corresponde a uma outra forma de Loop em que estabelecemos uma condição de
interrupção na própria sintaxe do comando.
SINTAXE
WHILE <condição> LOOP
<seqüência de comandos>
END LOOP;
Apesar da possibilidade de determinarmos a condição de interrupção no comando While, o comando
Exit, se utilizado, também provocará a interrupção do processamento.
SQL>
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SET LINESIZE 80
DECLARE
CURSOR C1
IS SELECT VL_SAL, CD_MAT FROM FUNC
ORDER BY VL_SAL DESC;
C1%ROWTYPE;
RC1
BEGIN
OPEN C1;
:MSG := 'Matrículas = ';
FETCH C1 INTO RC1;
WHILE C1%FOUND LOOP
EXIT WHEN C1%ROWCOUNT > 10;
:MSG := :MSG||RC1.CD_MAT||' - ';
FETCH C1 INTO RC1;
END LOOP;
END;
/
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT MSG
MSG
-------------------------------------------------------------------- L02_13
Matrículas = 10 - 110 - 230 - 20 - 50 - 30 - 70 - 280 - 100 - 330 -
No exemplo, o comando While estabelece que o loop deve prosseguir enquanto houverem linhas a
serem lidas.
Já o comando Exit determina que, quando efetuarmos a leitura da décima primeira linha, o
processamento deve ser interrompido. O comando Exit está, desta forma, associado a uma condição.
Somente quando a condição se tornar verdadeira, o comando Exit é acionado.
CAPÍTULO 2: PROCESSAMENTO REPETITIVO - 74
PL/SQL9I – BÁSICO E AVANÇADO
FOR LOOP
O comando For Loop determina que a seqüência de comandos seja executada um número fixo de
vezes. O número de iterações é conhecido (determinado) antes de o Loop ter início.
SINTAXE
FOR <contador> IN [REVERSE] <inferior>..<superior> LOOP
<seqüência de comandos>
END LOOP;
Na sintaxe, observamos que deve ser fornecido um valor inferior e um valor superior que
corresponderão, respectivamente, ao valor inicial e ao valor final de contador, que é incrementado de
1 a cada iteração.
Caso a palavra Reverse seja utilizada na sintaxe, o valor inicial de contador será o valor superior e a
cada iteração o contador será decrementado de 1 até atingir o valor inferior.
A variável Contador é implicitamente declarada como variável local ao Loop e com tipo de dado
Integer. Não pode ser utilizada fora da iteração, porque sua declaração se extingue quando o
processamento executar o End Loop.
Os valores referentes a valor inferior e valor superior podem ser fornecidos por constantes ou
variáveis.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
BEGIN
<<BLOCO1>>
DECLARE
VEZES
NUMBER := 0;
I
NUMBER := 0;
BEGIN
FOR I IN 1..3 LOOP
BLOCO1.I := I + VEZES;
VEZES := VEZES + 1;
END LOOP;
:MSG := 'O valor de I é = '||I;
END;
END;
/
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT MSG
MSG
-------------------------------------------O valor de I é = 5
L02_14
CAPÍTULO 2: PROCESSAMENTO REPETITIVO - 75
PL/SQL9I – BÁSICO E AVANÇADO
No exemplo, usamos o Label do bloco externo para qualificar a variável i declarada fora da iteração. A
variável i interna, utilizada na soma, não precisa de qualificação.
A seqüência de comandos é realizada três vezes, com i sendo declarado com valor inicial igual a 1,
incremento de 1 e valor-limite 3.
SQL> DECLARE
2
VEZES
NUMBER := 0;
3
INICIO
NUMBER := '&INICIO';
4
FIM
NUMBER := '&FIM';
5 BEGIN
6
FOR I IN INICIO..FIM LOOP
7
VEZES := VEZES + 1;
8
END LOOP;
9
FOR I IN 3..INICIO LOOP
10
VEZES := VEZES + 1;
11
END LOOP;
12
:MSG := 'O valor de VEZES é '||VEZES;
13 END;
14 /
Entre o valor para inicio: 1
antigo
3:
INICIO
NUMBER := '&INICIO';
novo
3:
INICIO
NUMBER := '1';
Entre o valor para fim: 4
antigo
4:
FIM
NUMBER := '&FIM';
novo
4:
FIM
NUMBER := '4';
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT MSG
MSG
-------------------------------------------------O valor de VEZES é 4
L02_15
No exemplo apresentado, utilizamos variáveis no lugar de constantes para determinar os valores
inicial e final. A primeira iteração é executada quatro vezes, com i variando de 1 a 4.
A segunda iteração não é executada, porque o valor inicial de i é 3 e o valor final é 1, inferior, portanto,
ao valor inicial. Nesta situação, a iteração não é realizada.
CAPÍTULO 2: PROCESSAMENTO REPETITIVO - 76
PL/SQL9I – BÁSICO E AVANÇADO
DECLARE
VEZES
NUMBER := 5;
INICIO
NUMBER := '&INICIO';
FIM
NUMBER := '&FIM';
BEGIN
:MSG := 'Valores de I: ';
FOR I IN REVERSE 1..5 LOOP
:MSG := :MSG ||I||' ';
END LOOP;
<<EXTERNO>>
FOR I IN 3..15 LOOP
<<INTERNO>>
FOR J IN INICIO..FIM LOOP
IF J > I THEN
EXIT EXTERNO;
END IF;
END LOOP INTERNO;
END LOOP EXTERNO;
END;
/
5
12
PRINT MSG
L02_16
No exemplo, utilizamos a cláusula Reverse para que I adquirisse valor inicial 5 e fosse decrementado
de 1 até atingir 1.
O label foi usado para qualificação do comando Loop e serviu para que a interrupção da iteração se
processasse simultaneamente para os dois Loops. Quando o teste (J > I) resulta em verdadeiro, tanto
o Loop interno quanto o externo são encerrados, passando o controle para o comando presente após
o End do Loop externo (linha 19).
CAPÍTULO 2: PROCESSAMENTO REPETITIVO - 77
PL/SQL9I – BÁSICO E AVANÇADO
CURSOR LOOP
Este comando é similar ao comando For Loop, mas é específico para utilização com cursores.
O registro presente na sintaxe é declarado implicitamente pelo Oracle (como um registro tipo
<cursor>%rowtype) e tem vida útil até o End ser atingido.
Esse comando realiza todas as operações vistas relativas a cursor: quando a iteração inicia, o cursor
é aberto e feito Fetch na primeira linha; se a operação (de leitura) for bem sucedida, é executada a
seqüência de comandos presente entre o Loop e o End Loop.
Esse processo se repete até que a última linha seja processada. Após este processamento, o cursor é
automaticamente fechado.
SINTAXE
FOR <record> IN <cursor> [ ( <parâmetro> [ , <parâmetro>. . . ] ) ] LOOP
<seqüência de comandos>
END LOOP;
O cursor pode ser declarado previamente e utilizado no comando, ou pode ser incluído o comando
Select desejado no próprio comando For.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
DECLARE
CURSOR C1
IS SELECT * FROM FUNC;
VEZES
NUMBER := 0;
BEGIN
FOR RC1 IN C1 LOOP
VEZES := VEZES + 1;
END LOOP;
:MSG := 'VEZES = '||VEZES;
FOR RC1 IN (SELECT * FROM FUNC) LOOP
VEZES := VEZES + 1;
END LOOP;
:MSG := :MSG||' - VEZES = '||VEZES;
END;
/
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT MSG
MSG
------------------------------------------------VEZES = 58 - VEZES = 116
Nos dois exemplos de iteração, o comando For leu todas as linhas da tabela Func. No primeiro caso,
declaramos explicitamente o cursor c1 e, no segundo, declaramos o cursor dentro do comando For.
CAPÍTULO 2: PROCESSAMENTO REPETITIVO - 78
PL/SQL9I – BÁSICO E AVANÇADO
A segunda sintaxe perde a possibilidade de utilizarmos os atributos do cursor uma vez que não
declaramos um nome de cursor para utilizar. O cursor implícito SQL não se aplica ao caso.
Este comando permite até a passagem de parâmetros caso o comando Select associado ao cursor
seja executado em relação a um valor parametrizado.
CAPÍTULO 2: PROCESSAMENTO REPETITIVO - 79
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 2
Todos os exercícios deste laboratório devem ser feitos utilizando-se os atributos %ROWTYPE ou
%TYPE.
1) Leia os n maiores salários da tabela de funcionários e apresente-os em ordem ascendente.
2) Crie uma tabela com o comando apresentado abaixo para receber os n maiores salários da tabela
de funcionários. O parâmetro n deve ser recebido através de Accept. Na coluna total deve ser
armazenado o somatório de todos os salários lidos.
SQL> CREATE TABLE TABTOTAL
2 (NOME
VARCHAR2(25),
3
SALARIO
NUMBER(8),
4
TOTAL
NUMBER(11)
5 );
Tabela criada.
Inclua todos os dados e posteriormente atualize a coluna total com o valor obtido na soma.
3) Faça um programa PL/SQL que receba um código de departamento como parâmetro e apresente o
nome do gerente do departamento e todos os funcionários (matrícula e nome) alocados àquele
departamento.
Utilize cursor com parâmetro.
4) Crie um bloco de PL/SQL que faça o enquadramento dos funcionários de acordo com os seguintes
critérios:
◊
O piso salarial será de 1.000,00 para os cargos inferiores a 51. Para os demais, o piso
salarial será de 1.500,00.
◊
Para cada nível de cargo superior a 55, deverá ser acrescido 250 ao salário.
◊
Para cada nível de instrução superior a 15, deverá ser acrescido 30 ao salário.
O salário do funcionário não poderá ser diminuído. Se o funcionário já receber acima do valor
calculado, deve-se manter o salário anterior.
Deve-se apresentar, simultaneamente, todos os enquadrados (com o velho e novo salário) e aqueles
não enquadrados (com o salário antigo).
CAPÍTULO 2: PROCESSAMENTO REPETITIVO - 80
PL/SQL9I – BÁSICO E AVANÇADO
5) Crie uma tabela com o comando apresentado abaixo para receber a quantidade de funcionários por
departamento. O código do departamento deverá ser convertido (de alfanumérico para numérico).
Utilize Loop e Exit When. Use a função AscII para a conversão do código.
SQL> CREATE TABLE RESULTADO
2 (CD_DEPTO
NUMBER,
3
QT_FUNC
NUMBER,
4
VL_SAL
NUMBER,
5
AV_SAL
NUMBER);
Tabela criada.
Neste exercício, só deve ser preenchida a quantidade de funcionários por departamento.
6) Utilizando a mesma tabela já parcialmente preenchida do exercício anterior, complete-a com o total
de salários e a média salarial.
Utilize Cursor Loop. Inicie a leitura pela tabela resultado e trabalhe com Update Where Current.
7) Crie um bloco de PL/SQL que receba como parâmetro um número de mês e verifique quais os
funcionários aniversariantes. Apresentar o nome e dia de nascimento ordenado por dia de nascimento.
Usar While.
8) Crie um bloco de PL/SQL que receba como parâmetro (de substituição) um grau de instrução e um
código de departamento e determine o cargo, salário e matrícula deste funcionário. Se existir mais de
um, escolha o funcionário mais novo. Se não existir nenhum, apresente mensagem correspondente.
CAPÍTULO 2: PROCESSAMENTO REPETITIVO - 81
PL/SQL9I – BÁSICO E AVANÇADO
CAPÍTULO 3: TRATAMENTO DE ERRO
Cada bloco de PL/SQL possui uma área específica para tratamento de erro. Toda vez que ocorre um
erro no programa, a seqüência de execução é interrompida e o controle é transferido para esta área
especial do bloco onde o erro foi adquirido.
Os erros, em um programa PL/SQL, são chamados de exceptions. Estas exceptions podem
corresponder a erros predefinidos, como por exemplo No_Data_Found, ou a erros definidos pelo
usuário, como por exemplo Salario_Invalido.
Os erros pré-definidos são de conhecimento do Oracle e, portanto, quando o erro ocorre, a PL/SQL
percebe a ocorrência do erro e faz o desvio para a área de tratamento. Já os erros do usuário não são
percebidos pela PL/SQL, a não ser que o programa explicitamente cause o erro.
SQL> VARIABLE MSG VARCHAR2(200)
SQL> DECLARE
2
VCD_DEPTO
VARCHAR2(5) := '&DEPTO';
3
SAL
NUMBER;
4 BEGIN
5
SELECT VL_SAL INTO SAL FROM FUNC
6
WHERE CD_DEPTO = VCD_DEPTO;
7
:MSG := 'Salário igual a '||SAL;
8 EXCEPTION
9
WHEN NO_DATA_FOUND THEN
10
:MSG := 'Não existem funcionários para este departamento';
11
WHEN TOO_MANY_ROWS THEN
12
:MSG := 'Existe mais de um funcionário neste departamento';
13 END;
14 /
Entre o valor para depto: D05
antigo
2:
VCD_DEPTO
VARCHAR2(5) := '&DEPTO';
novo
2:
VCD_DEPTO
VARCHAR2(5) := 'D05';
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT MSG
MSG
-------------------------------------------------------Não existem funcionários para este departamento
L03_01
No exemplo anterior, foi previsto tratamento para dois tipos de erro passíveis de ocorrer na seleção de
dados da tabela Func: nenhum funcionário ser encontrado no departamento especificado ou mais de
um funcionário ser encontrado. Os dois tipos de erro são predefinidos e conhecidos pela PL/SQL,
correspondendo aos códigos +100 e -1422.
Quando ocorre um desvio para a área de exception de um determinado bloco, não é possível o
retorno para a área de lógica do mesmo bloco.
CAPÍTULO 3: TRATAMENTO DE ERRO - 82
PL/SQL9I – BÁSICO E AVANÇADO
VANTAGENS DAS EXCEPTIONS
A principal vantagem da utilização de exceptions é a legibilidade dos programas. Podemos isolar todo
o tratamento de erros da parte de lógica normal a ser executada.
Outra vantagem é a possibilidade de darmos tratamento único a erros ocorridos em pontos diferentes
do programa.
DEFININDO EXCEPTIONS
Uma exception corresponde a uma condição de erro. Podemos declarar estas condições de erro na
parte declarativa de qualquer bloco PL/SQL.
Apesar de a declaração de uma exception ser similar à declaração de uma variável, elas não podem
ser utilizadas em comandos de atribuição nem em comandos de SQL; seu uso é bem específico.
SQL> DECLARE
2
VCD_DEPTO
VARCHAR2(5) := '&DEPTO';
3
E_LENGTH
EXCEPTION;
4
SAL
NUMBER;
5 BEGIN
6
IF LENGTH(VCD_DEPTO) <> 3 THEN
7
RAISE E_LENGTH;
8
END IF;
9
SELECT VL_SAL INTO SAL FROM FUNC
10
WHERE CD_DEPTO = VCD_DEPTO;
11
:MSG := 'Salário igual a '||sal;
12 EXCEPTION
13
WHEN E_LENGTH THEN
14
:MSG := 'Departamento inválido';
15
WHEN NO_DATA_FOUND THEN
16
:MSG := 'Não existem funcionários para este departamento';
17
WHEN TOO_MANY_ROWS THEN
18
:MSG := 'Existe mais de um funcionário neste departamento';
19 END;
20 /
Entre o valor para depto: DTYUI
antigo
2:
VCD_DEPTO
VARCHAR2(5) := '&DEPTO';
novo
2:
VCD_DEPTO
VARCHAR2(5) := 'DTYUI';
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT MSG
MSG
-------------------------------------------------------------------Departamento inválido
L03_02
CAPÍTULO 3: TRATAMENTO DE ERRO - 83
PL/SQL9I – BÁSICO E AVANÇADO
No exemplo anterior, observamos a declaração da condição E_Length que foi utilizada na área de
tratamento da mesma forma que as exceptions pré-definidas (When E_Length Then).
A diferença está na presença do comando Raise E_Length, que tem a finalidade de causar o erro
quando o comprimento da variável for diferente de 3.
EXCEPTIONS PRÉ-DEFINIDAS
Quando criamos um programa PL/SQL, são incorporadas a ele algumas condições de erro prédefinidas, que conheceremos a seguir.
Exception
Oracle Error
SQLCode Condição
Access_into_null
ORA-06530
-6530
É causada se ocorrer uma associação de
valores a atributos de um objeto nãoinicializado (Null).
Case_not_found
ORA-6592
-6592
É causada se nenhuma das opções da cláusula
When for satisfeita e não houver sido declarada
nenhuma cláusula Else.
Collection_is_null
ORA-06531
-06531
É causada se forem aplicados métodos (exceto
Exists) ou associação de valores a uma Nested
Table ou Varray não-inicializados.
Cursor_already_open
ORA-06511
-6511
É causada se for executado um Open para um
cursor já aberto.
Dup_val_on_index
ORA-00001
-1
É causada se for tentada a inclusão de valores
duplicados em uma coluna do banco de dados
que contém uma restrição de unicidade.
Invalid_cursor
ORA-01001
-1001
É causada se for tentada uma operação ilegal
com um cursor. Como, por exemplo, fechar um
cursor não aberto.
Invalid_number
ORA-01722
-1722
É causada se algum comando de SQL tentou
uma conversão de string para numérico quando
a string não representava um número válido.
Em comandos procedurais, a exception
adquirida é Value_Error.
Login_denied
ORA-01017
-1017
É causada se houver uma tentativa de conexão
ao Oracle com um user/password inválidos.
No_data_found
ORA-01403
+100
É causada se num comando Select Into
nenhuma linha foi retornada ou foi feita
referência a um elemento inexistente (deletado)
de uma Nested Table ou uma referência a um
elemento não inicializado em uma tabela
PL/SQL.
CAPÍTULO 3: TRATAMENTO DE ERRO - 84
PL/SQL9I – BÁSICO E AVANÇADO
Exception
Oracle Error
SQLCode Condição
Not_logged_on
ORA-01012
-1012
É causada se um programa de PL/SQL tenta
fazer acesso ao banco de dados sem
estabelecer conexão.
Program_error
ORA-06501
-6501
É causada se ocorrer um problema interno.
Rowtype_mismatch
ORA-06504
-6504
É causada se a host variável cursor e a
PL/SQL variável cursor usadas em uma
associação
tiverem
tipos
de
retorno
incompatíveis.
Self_is_null
ORA-30625
-30625
É causada se o programa tentar fazer uma
chamada a um método de um objeto não
identificado (instância Null), ou seja, o
parâmetro Self (que é sempre o primeiro
parâmetro passado para os métodos) está null.
Storage_error
ORA-06500
-6500
É causada se a PL/SQL sair da memória ou se
a memória estiver corrompida.
Subscript_beyond_count ORA-06533
-6533
É causada se for feita uma referência a um
elemento de uma Nested Table ou Varray
usando um número maior que o número de
elementos da coleção.
Subscript_outside_limit
ORA-06532
-6532
É causada se for feita uma referência a um
elemento de uma nested Table ou Varray
usando um número fora do intervalo legal ( por
exemplo, -1).
Sys_invalid_rowid
ORA-1410
-1410
É causada quando a conversão de uma string
em um rowid falha.
Timeout_on_resource
ORA-00051
-51
É causada se ocorrer timeout enquanto o
Oracle estiver aguardando por um recurso.
Too_many_rows
ORA-01422
-1422
É causada se um comando Select Into retornar
mais de uma linha.
Value_error
ORA-06502
-6502
É causada
conversão,
ocorrerem.
Zero_divide
ORA-01476
-1476
É causada se houver uma tentativa de divisão
por zero.
se uma operação
constraint
error,
aritmética,
truncation
CAPÍTULO 3: TRATAMENTO DE ERRO - 85
PL/SQL9I – BÁSICO E AVANÇADO
Uma vez que estas condições de erro já estão previamente definidas, podemos utilizá-las livremente
nos programas.
SQL> DECLARE
2
MATRICULA
NUMBER := '&MAT';
3 BEGIN
4
:MSG := '';
5 <<INICIO>>
6
:MSG := :MSG || 'INÍCIO - ';
7
BEGIN
8
INSERT INTO FUNC(CD_MAT) VALUES(MATRICULA);
9
:MSG := :MSG || 'INSERT - ';
10
EXCEPTION
11
WHEN DUP_VAL_ON_INDEX THEN
12
:MSG := :MSG || 'DUPLICATES - ';
13
SELECT MAX(CD_MAT) + 1 INTO MATRICULA
14
FROM FUNC;
15
GOTO INICIO;
16
END;
17 END;
18 /
Entre o valor para mat: 10
antigo
2:
MATRICULA
NUMBER := '&MAT';
novo
2:
MATRICULA
NUMBER := '10';
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT MSG
MSG
-----------------------------------------------------INÍCIO - DUPLICATES - INÍCIO - INSERT -
L03_03
Neste exemplo criamos um bloco externo que contém o label <<Inicio>> e um bloco interno para
realizar o tratamento do erro no caso da duplicidade.
Quando o erro foi adquirido, foi dada uma solução de contorno (Max), porém havia necessidade de
retornarmos ao processo de inclusão.
Como o retorno para o mesmo bloco não é possível (como veremos a seguir), o programa foi
construído com dois blocos para que o retorno fosse possível.
CAPÍTULO 3: TRATAMENTO DE ERRO - 86
PL/SQL9I – BÁSICO E AVANÇADO
SQL> DECLARE
2
MATRICULA
NUMBER := '&MAT';
3 BEGIN
4
:MSG := '';
5 <<INICIO>>
6
:MSG := :MSG || 'INÍCIO - ';
7
INSERT INTO FUNC(CD_MAT) VALUES(MATRICULA);
8
:MSG := :MSG || 'INSERT - ';
9 EXCEPTION
10
WHEN DUP_VAL_ON_INDEX THEN
11
:MSG := :MSG || 'DUPLICATES - ';
12
SELECT MAX(CD_MAT) + 1 INTO MATRICULA
13
FROM FUNC;
14
GOTO INICIO;
15 END;
16 /
Entre o valor para mat: 10
antigo
2:
MATRICULA
NUMBER := '&MAT';
novo
2:
MATRICULA
NUMBER := '10';
GOTO INICIO;
*
ERRO na linha 14:
ORA-06550: linha 14, coluna 5:
PLS-00375: instrução GOTO inválida; este GOTO
ramificado para o label 'INICIO'
ORA-06550: linha 14, coluna 5:
PL/SQL: Statement ignored
não
pode
ser
L03_04
CAPÍTULO 3: TRATAMENTO DE ERRO - 87
PL/SQL9I – BÁSICO E AVANÇADO
CAUSANDO UMA EXCEPTION
Já sabemos que as exceptions pré-definidas são causadas implicitamente a tempo de execução. As
exceptions definidas pelo usuário, porém, podem ser controladas de duas formas: com o verbo Raise
ou com a pragma Exception_Init.
O VERBO RAISE
Este verbo causa a exception no momento em que é aplicado. Isso pode ser feito tanto para uma
exception definida pelo usuário quanto para uma exception pré-definida.
SQL> DECLARE
2
MATRICULA
NUMBER := '&MAT';
3
E_MATRICULA
EXCEPTION;
4 BEGIN
5
IF MATRICULA IS NULL THEN
6
RAISE E_MATRICULA;
7
END IF;
8
SELECT CD_MAT INTO MATRICULA FROM FUNC
9
WHERE CD_MAT = MATRICULA;
10
RAISE INVALID_NUMBER;
11 EXCEPTION
12 WHEN E_MATRICULA OR INVALID_NUMBER THEN
13
:MSG := 'Matrícula não informada ou já existente';
14 WHEN NO_DATA_FOUND THEN
15
INSERT INTO FUNC(CD_MAT) VALUES(MATRICULA);
16
:MSG := 'Matrícula incluída';
17 END;
18 /
Entre o valor para mat: 10
antigo
2:
MATRICULA
NUMBER := '&MAT';
novo
2:
MATRICULA
NUMBER := '10';
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT MSG
MSG
---------------------------------------------------------Matrícula não informada ou já existente
L03_05
No exemplo, utilizamos o verbo Raise para causar uma exception definida pelo usuário (E_Matricula)
e também para causar a exception pré-definida Invalid_Number.
CAPÍTULO 3: TRATAMENTO DE ERRO - 88
PL/SQL9I – BÁSICO E AVANÇADO
A PRAGMA EXCEPTION_INIT
A quantidade de itens na lista de exceptions pré-definidas é limitada e muitas vezes desejamos
controlar a ocorrência de outros erros conhecidos do ambiente, tais como deadlock ou constraint error.
Como não temos condições de erro pré-definidas para tratamento destas situações, é muito comum
encontrarmos nos programas o uso de testes do código do erro ocorrido para efetuar uma ou outra
ação (Sqlcode e When Others, que serão vistos mais adiante).
As pragmas são diretivas de compilação, isto é, são instruções fornecidas ao compilador, que modifica
a forma de execução do programa.
A pragma Exception_Init nos proporciona uma forma mais legível de realizarmos o controle dos erros
previstos, mas sem condições de erro pré-definidas.
SQL> DECLARE
2
MATRICULA
NUMBER NOT NULL := '&MAT';
3
DEPARTAMENTO
VARCHAR2(5) := UPPER('&DEPTO');
4
E_RELATION
EXCEPTION;
5
PRAGMA EXCEPTION_INIT(E_RELATION, -2291);
6 BEGIN
7
SELECT CD_MAT INTO MATRICULA FROM FUNC
8
WHERE CD_MAT = MATRICULA;
9
RAISE INVALID_NUMBER;
10 EXCEPTION
11 WHEN INVALID_NUMBER THEN
12
:MSG := 'Matrícula não informada ou já existente';
13 WHEN NO_DATA_FOUND THEN
14
BEGIN
15
INSERT INTO FUNC(CD_MAT, CD_DEPTO)
16
VALUES(MATRICULA, DEPARTAMENTO);
17
:MSG := 'Matrícula incluída';
18
EXCEPTION
19
WHEN E_RELATION THEN
20
:MSG := 'Chave de relacionamento inválida';
21
END;
22 END;
23 /
Entre o valor para mat: 11
Entre o valor para depto: DDD
Procedimento PL/SQL concluído com sucesso.
SQL> PRINT MSG
MSG
---------------------------------------------------------Matrícula não informada ou já existente
L03_06
CAPÍTULO 3: TRATAMENTO DE ERRO - 89
PL/SQL9I – BÁSICO E AVANÇADO
No exemplo anterior, associamos o código de erro ORA-2291 à variável de exception E_Relation,
indicando ao compilador PL/SQL que na ocorrência do erro -2291 deveria ser causada a exception
E_Relation.
Desta forma, pudemos realizar o tratamento adequado para que o programa não fosse abortado.
Observe que a diretiva Pragma é definida na parte declarativa do bloco, pois será usada pelo
compilador e não a tempo de execução.
A PROCEDURE RAISE_APPLICATION_ERROR
Esta procedure permite que seja feito o fornecimento de mensagens de erro a partir de subprogramas
armazenados no banco de dados ou de database triggers.
SINTAXE
RAISE_APPLICATION_ERROR (<erro>, <mensagem> [, TRUE | FALSE ] )
Onde:
◊
<erro> - Corresponde ao número do erro, podendo variar de –20000 a –20999. Deve ser um
número inteiro. Neste intervalo de valores não existem erros do Oracle. Este intervalo foi
reservado para erros do usuário.
◊
<mensagem> - Corresponde à mensagem que desejamos enviar. Pode ser uma string de até
2048 bytes de comprimento.
◊
TRUE / FALSE – O terceiro parâmetro é opcional. Se for enviado True, indica que o erro será
colocado na pilha de erros anteriores. Caso o valor seja False (default), o erro substituirá todos
os erros anteriores.
Se uma aplicação executar uma stored procedure que use a raise_application_error, esta (a
aplicação) receberá o código e a mensagem de erro enviada pelo subprograma e poderá realizar o
tratamento adequado, da mesma forma que faz com os demais erros do Oracle.
CAPÍTULO 3: TRATAMENTO DE ERRO - 90
PL/SQL9I – BÁSICO E AVANÇADO
SQL> DECLARE
2
MATRICULA
NUMBER NOT NULL := '&MAT';
3
DEPARTAMENTO
VARCHAR2(5) := UPPER('&DEPTO');
4
E_RELATION
EXCEPTION;
5
PRAGMA EXCEPTION_INIT(E_RELATION, -2291);
6 BEGIN
7
SELECT CD_MAT INTO MATRICULA FROM FUNC
8
WHERE CD_MAT = MATRICULA;
9
RAISE_APPLICATION_ERROR(-20000, 'Matrícula não informada ou
já existente');
10 EXCEPTION
11 WHEN NO_DATA_FOUND THEN
12
BEGIN
13
INSERT INTO FUNC(CD_MAT, CD_DEPTO)
14
VALUES(MATRICULA, DEPARTAMENTO);
15
:MSG := 'Matrícula incluída';
16
EXCEPTION
17
WHEN E_RELATION THEN
18
:MSG := 'Chave de relacionamento inválida';
19
END;
20 END;
21 /
Entre o valor para mat: 10
Entre o valor para depto: D01
DECLARE
*
ERRO na linha 1:
ORA-20000: Matrícula não informada ou já existente
ORA-06512: em line 9
L03_07
Quando a matrícula já existe na tabela Func, o programa é abortado com a mensagem adequada.
Observe que a mensagem de erro aparece na forma ORA-20000, da mesma forma que as
mensagens de erro do banco de dados.
CAPÍTULO 3: TRATAMENTO DE ERRO - 91
PL/SQL9I – BÁSICO E AVANÇADO
PROPAGAÇÃO DA EXCEÇÃO
Quando uma exception é adquirida em um determinado bloco e é tratada, a condição de erro é
cancelada.
Se não for dado tratamento, o erro é propagado para o bloco imediatamente superior (onde o atual
está contido) com a condição de erro marcada, ocorrendo, então, o desvio para a área de tratamento
de erro desse bloco em busca de tratamento para o erro.
Este procedimento se repete enquanto houver blocos embutidos. Quando atinge o bloco principal,
sem tratamento, o programa é abortado.
SQL> DECLARE
2
VNUM
NUMBER := '&NUMERO';
3
A
EXCEPTION;
4
B
EXCEPTION;
5
C
EXCEPTION;
6 BEGIN
7
:MSG := 'INICIO';
8
BEGIN
9
IF VNUM = 1 THEN
10
RAISE A;
11
ELSIF VNUM = 2 THEN
12
RAISE B;
13
ELSE
14
RAISE C;
15
END IF;
16
EXCEPTION
17
WHEN A THEN
18
:MSG := :MSG ||'- EXECUTOU WHEN A';
19
END;
20 EXCEPTION
21 WHEN B THEN
22
:MSG := :MSG || '- EXECUTOU WHEN B';
23 END;
24 .
SQL> SAVE EX1
Criado arquivo EX1.sql
L03_08
O exemplo acima foi executado três vezes para que possamos acompanhar todas as ocorrências.
CAPÍTULO 3: TRATAMENTO DE ERRO - 92
PL/SQL9I – BÁSICO E AVANÇADO
X=1
SQL> @EX1
Entre o valor para numero: 1
Procedimento PL/SQL concluído com sucesso.
MSG
------------------------------------------INICIO- EXECUTOU WHEN A
Nesta primeira execução, a variável Vnum recebeu o valor 1. A exception A foi causada e tratada no
bloco mais interno. O programa seguiu terminando normalmente.
X=2
SQL> @ex1
Entre o valor para numero: 2
Procedimento PL/SQL concluído com sucesso.
MSG
------------------------------------------INICIO- EXECUTOU WHEN B
Nesta segunda execução, a variável Vnum recebeu o valor 2. A exception B foi causada no bloco mais
interno, porém não foi encontrado tratamento para essa condição de erro nesse bloco.
Desta forma, a exception foi propagada para o bloco externo. Como neste momento foi encontrado
tratamento, o programa termina normalmente.
CAPÍTULO 3: TRATAMENTO DE ERRO - 93
PL/SQL9I – BÁSICO E AVANÇADO
X=3
SQL> @ex1
Entre o valor para numero: 3
DECLARE
*
ERRO na linha 1:
ORA-06510: PL/SQL: exceção não-manipulada definida pelo usuário
ORA-06512: em line 14
Na terceira execução, a variável Vnum recebeu o valor 3. A exception C foi causada no bloco mais
interno, porém não foi encontrado tratamento para essa condição de erro nesse bloco.
A exception foi propagada para o bloco externo e também aí não foi encontrado tratamento; mais uma
vez o erro foi propagado, desta vez para o ambiente, abortando o programa.
CAPÍTULO 3: TRATAMENTO DE ERRO - 94
PL/SQL9I – BÁSICO E AVANÇADO
DETERMINANDO O ERRO RECEBIDO
Já vimos, anteriormente, a utilização da Pragma Exception_Init que seria a forma mais elegante de
darmos tratamento a erros no programa.
Existem, no entanto, outras formas de controlarmos os erros recebidos por um programa PL/SQL, que
veremos a seguir.
AS FUNÇÕES SQLCODE E SQLERRM
Estas duas funções obtêm o código e a mensagem (respectivamente) da última interação com o
banco de dados, bem sucedida ou não (neste caso, obteremos o erro e a mensagem de erro
correspondente).
A função SqlErrm recebe como parâmetro o código do erro e retorna a mensagem correspondente.
SQL> DECLARE
2
VNUM
NUMBER := '&NUMERO';
3 BEGIN
4
:MSG := SQLERRM(VNUM);
5 END;
6 /
Entre o valor para numero: +100
Procedimento PL/SQL concluído com sucesso.
MSG
------------------------------------------ORA-01403: dados não encontrados
L03_09
CAPÍTULO 3: TRATAMENTO DE ERRO - 95
PL/SQL9I – BÁSICO E AVANÇADO
WHEN OTHERS
Podemos utilizar a sintaxe When Others se desejarmos capturar, dentro do programa, qualquer outra
ocorrência não prevista dentro da lista de exceptions (pré-definidas ou do usuário).
É muito comum a utilização conjunta da sintaxe When Others e das funções SqlCode e SqlErrm.
SQL> DECLARE
2
MAT
NUMBER := '&MAT';
3 BEGIN
4
INSERT INTO FUNC(CD_MAT) VALUES(MAT);
5 EXCEPTION
6 WHEN DUP_VAL_ON_INDEX THEN
7
:MSG := 'Matrícula já existente';
8 WHEN OTHERS THEN
9
:MSG := SQLERRM(SQLCODE);
10 END;
11 /
Entre o valor para mat:
Procedimento PL/SQL concluído com sucesso.
MSG
------------------------------------------------------------------ORA-01400: não é possível inserir NULL em ("ALUNO"."FUNC"."CD_MAT")
L03_10
CAPÍTULO 3: TRATAMENTO DE ERRO - 96
PL/SQL9I – BÁSICO E AVANÇADO
EXCEPTIONS ADQUIRIDAS NA DECLARAÇÃO
Às vezes, ocorrem exceptions a tempo de declaração por falha nos valores atribuídos para
inicialização.
Este tipo de ocorrência não é capturado no conjunto de exceptions do bloco corrente, pois a
ocorrência se propaga imediatamente para o bloco que a contém.
SQL> BEGIN
2
DECLARE
3
MAT
NUMBER := '&MAT';
4
BEGIN
5
INSERT INTO FUNC(CD_MAT) VALUES(MAT);
6
EXCEPTION
7
WHEN DUP_VAL_ON_INDEX THEN
8
:MSG := 'Matrícula já existente';
9
WHEN VALUE_ERROR THEN
10
:MSG := 'Erro capturado no bloco interno';
11
WHEN OTHERS THEN
12
:MSG := SQLERRM(SQLCODE);
13
END;
14 EXCEPTION
15 WHEN VALUE_ERROR THEN
16
:MSG := 'Erro capturado no bloco externo';
17 END;
18 /
Entre o valor para mat: A
Procedimento PL/SQL concluído com sucesso.
MSG
---------------------------------------------------Erro capturado no bloco externo
L03_11
No exemplo acima, só foi possível a captura do erro no bloco externo (criado por fora da aplicação).
Como teste, execute o exemplo anterior (L03_10) passando para a variável MAT o valor A.
CAPÍTULO 3: TRATAMENTO DE ERRO - 97
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 3:
1. Considerando-se o programa PL/SQL abaixo, responda às questões que se seguem:
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
DECLARE
VALOR
A
B
C
BEGIN
BEGIN
IF
NUMBER(3) := &X;
EXCEPTION;
EXCEPTION;
EXCEPTION;
VALOR = 1 THEN
RAISE A;
ELSIF VALOR = 2 THEN
RAISE B;
ELSIF VALOR = 3 THEN
RAISE C;
END IF;
VALOR := 100;
EXCEPTION
WHEN A THEN
VALOR := 200;
END;
VALOR := 300;
EXCEPTION
WHEN B THEN
VALOR := 400;
END;
/
a) O que acontece se VALOR = 1 ? Qual o conteúdo final de VALOR ?
b) O que acontece se VALOR = 2 ? Qual o conteúdo final de VALOR?
c) O que acontece se VALOR = 3 ? Qual o conteúdo final de VALOR ?
CAPÍTULO 3: TRATAMENTO DE ERRO - 98
PL/SQL9I – BÁSICO E AVANÇADO
2. Faça um programa para realizar o cadastramento de funcionários que estabeleça as seguintes
críticas:
◊
O funcionário não deve existir no banco de dados. Para tal, consulte por nome e sobrenome.
◊
O código do departamento deve existir na tabela de departamentos.
◊
Só serão admitidos funcionários com menos de 50 anos.
◊
Só serão admitidos funcionários com grau de instrução entre 12 e 18.
◊
O cargo deverá estar, garantidamente, entre 55 e 60.
◊
Sexo deverá estar preenchido com F ou M.
Os parâmetros informados são: nome e sobrenome do funcionário, código do departamento, grau de
instrução, data de nascimento, cargo e sexo.
Para cada um dos erros encontrados, o programa deverá ser interrompido com a mensagem
adequada. Obrigatoriamente, o tratamento deverá utilizar a área de Exception.
3. Para o programa apresentado abaixo, identifique e explique por que ocorreu o erro.
SQL> DECLARE
2
DUMMY
NUMBER := '&NUMERO';
3 BEGIN
4
IF DUMMY > 5 THEN
5
:VALOR := 0;
6
END IF;
7 EXCEPTION
8 WHEN OTHERS THEN
9
RAISE_APPLICATION_ERROR(-20999, 'Erro de atribuição na declaração');
10 END;
11 /
Entre o valor para numero: A
antigo
2:
DUMMY
NUMBER := '&NUMERO';
novo
2:
DUMMY
NUMBER := 'A';
DECLARE
*
ERRO na linha 1:
ORA-06502: PL/SQL: error: erro de conversão de caractere em número
numérico ou de valor
ORA-06512: em line 2
CAPÍTULO 3: TRATAMENTO DE ERRO - 99
PL/SQL9I – BÁSICO E AVANÇADO
4. Qual a solução de contorno que permita que controlemos o tipo de erro do exercício 3 ?
5. Crie um programa para cadastramento de funcionários. Informa, somente, dois parâmetros:
Matrícula e Código do departamento.
O programa não deve abortar se a matrícula já existir (Dup_Val_On_Index) ou se o código do
departamento não existir na tabela Depto (use a diretiva Exception_Init para definir este controle).
6. Faça um programa que receba três números e os apresente em ordem ascendente. Para tal,
estabeleça todas as críticas necessárias para que o usuário forneça dados corretos.
CAPÍTULO 3: TRATAMENTO DE ERRO - 100
PL/SQL9I – BÁSICO E AVANÇADO
CAPÍTULO 4: TABELAS E REGISTROS
Uma coleção é um grupo ordenado de elementos todos do mesmo tipo. Cada elemento possui um
único subscrito que determina o posicionamento do elemento na coleção.
A PL/SQL trabalha com três tipos de coleções: Nested Tables, Varrays e Index-By Tables (também
chamadas de tabelas de PL/SQL).
Em PL/SQL, coleções funcionam como arrays, porém só admitem uma dimensão e devem ser
indexados por um inteiro.
As coleções Nested Tables e Varrays podem ser criadas no banco de dados e utilizadas em PL/SQL
ou criadas diretamente em PL/SQL. Neste curso estudaremos apenas a coleção nativa da própria
PL/SQL, isto é a Index-By Table.
INDEX-BY TABLES
Index By Tables são semelhantes às outras coleções em diversos pontos.
SINTAXE
TYPE <nome tipo> IS TABLE OF <tipo> INDEX BY BINARY_INTEGER
Veremos, a seguir, suas características básicas:
◊
Em SQL não podemos manipular Index-By Tables.
◊
Index-By Tables são definidas usando-se a cláusula Index By Binary_Integer.
◊
Uma Index-By Table não inicializada está apenas vazia e não podemos comparar usando IS
NULL.
◊
A tempo de execução, Index-By Tables tornam-se non-null automaticamente.
◊
Index-By Tables não podem ficar Null. A exception Collection_Is_Null é aplicável apenas a
Nested Tables.
◊
Para Index-By Tables, o subscrito pode variar de -2.147.483.647 a 2.147.483.647.
◊
Para as Index-By Tables não são validados. Assim, as exceptions Subscript_Outside_Limit e
Subscript_Beyound_Count são aplicáveis apenas a Nested Tables.
◊
Para estender uma Index-By Table, basta que especifiquemos subscritos maiores.
◊
As procedures Extend e Trim não podem ser aplicadas Index-By Tables.
CAPÍTULO 4: TABELAS E REGISTROS - 101
PL/SQL9I – BÁSICO E AVANÇADO
DECLARAÇÃO E ATRIBUIÇÃO
No exemplo a seguir, declaramos um tipo referente à coleção. Após a declaração do tipo de dados
desejado, devemos declarar as variáveis com o tipo definido.
SQL> DECLARE
2
TYPE TTABLE
3
4 -5
TTEXTO
6 BEGIN
7
IF TTEXTO IS
8
:MSG
:=
9
ELSE
10
:MSG
:=
11
END IF;
12
TTEXTO(1) :=
13 END;
14 /
Procedimento PL/SQL
IS TABLE OF VARCHAR2(10)
INDEX BY BINARY_INTEGER;
TTABLE;
NULL THEN
'A coleção foi considerada NULL';
'A coleção foi implicitamente inicializada';
'AA';
concluído com sucesso.
MSG
----------------------------------------------------------------A coleção foi implicitamente inicializada
L04_01
Observe a Index-By Table foi inicializada implicitamente, por este motivo já podemos utilizá-la
imediatamente após a declaração e só fazer a atribuição a cada elemento do array em particular.
SQL> DECLARE
2
TYPE TTABLE
IS TABLE OF VARCHAR2(10)
3
INDEX BY BINARY_INTEGER;
4 -5
TTEXTO
TTABLE;
6 BEGIN
7
TTEXTO(5) := 'AA';
8
TTEXTO(2) := 'BB';
9
TTEXTO(3) := TTEXTO(2)||TTEXTO(5);
10
:MSG := TTEXTO(3);
11 END;
12 /
Procedimento PL/SQL concluído com sucesso.
MSG
----------------------------------------------------BBAA
L04_02
Podemos observar que a referência a um determinado elemento da coleção (Index-By Table) é feita
com o uso de um indexador. A Index-By Table é inicializada diretamente com a atribuição de valor a
um índice qualquer, que não precisa ser seqüencial.
CAPÍTULO 4: TABELAS E REGISTROS - 102
PL/SQL9I – BÁSICO E AVANÇADO
MANIPULANDO INDEX-BY TABLES
Como já vimos anteriormente, podemos comparar Index-By Tables com IS NULL para verificarmos se
a coleção foi inicializada. Podemos, também, atribuir todo o conteúdo de uma coleção a outra.
SQL> DECLARE
2
TYPE TTABLE
IS TABLE OF VARCHAR2(10) INDEX BY BINARY_INTEGER;
3
NTEXTO1
TTABLE;
4
NTEXTO2
TTABLE;
5 BEGIN
6
IF NTEXTO1 IS NOT NULL THEN
7
NTEXTO1(2) := 'AAA';
8
END IF;
9
NTEXTO2 := NTEXTO1;
10
:MSG := 'TÉRMINO NORMAL -> ' ||NTEXTO2(2);
11 EXCEPTION
12 WHEN OTHERS THEN
13
:MSG := SQLERRM(SQLCODE);
14 END;
15 /
Procedimento PL/SQL concluído com sucesso.
MSG
------------------------------------------------------------------TÉRMINO NORMAL -> AAA
L04_03
Efetuamos a comparação para Null atribuindo um valor a uma das posições da coleção.Em seguida
fizemos a atribuição de uma coleção para outra. Isto é possível porque ambas são do mesmo tipo.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
IF
DECLARE
TYPE TTABLE
IS TABLE OF VARCHAR2(10) INDEX BY BINARY_INTEGER;
NTEXTO1
TTABLE;
NTEXTO2
TTABLE;
BEGIN
NTEXTO1(2) := 'AAA';
NTEXTO2 := NTEXTO1;
IF NTEXTO1 = NTEXTO2 THEN
NTEXTO2(1) := NTEXTO1(2);
END IF;
:MSG := 'TÉRMINO NORMAL';
EXCEPTION
WHEN OTHERS THEN
:MSG := SQLERRM(SQLCODE);
END;
/
NTEXTO1 = NTEXTO2 THEN
*
ERRO na linha 8:
ORA-06550: linha 8, coluna 14:
PLS-00306: número incorreto de tipos de argumentos na chamada para '='
ORA-06550: linha 8, coluna 3:
PL/SQL: Statement ignored
L04_04
CAPÍTULO 4: TABELAS E REGISTROS - 103
PL/SQL9I – BÁSICO E AVANÇADO
O erro que aparece no segundo exemplo indica que uma coleção não pode ser comparada com outra,
mesmo que as duas correspondam ao mesmo tipo. Podemos, no entanto, compar posições
específicas do array de cada uma delas.
MÉTODOS
As coleções possuem métodos, que são funções predefinidas com ações específicas que agem sobre
as coleções facilitando sua manipulação.
Os seguintes métodos são aplicáveis a coleções. Nem todos os métodos, porém, são aplicáveis a
Index-By Tables.
Método
Tipo
Parâmetro
Significado
Exists
Function
Índice do elemento
Indica se o elemento foi alocado.
Count
Function
Retorna a quantidade de elementos atual da
coleção
Limit
Function
Retorna a quantidade máxima de elementos
da coleção. Para Nested Tables retorna Null.
First e Last
Function
Retorna o primeiro e o último indexador válido
da coleção.
Prior e Next Function
Índice do elemento
Retorna o índice válido anterior (ou posterior)
relativo ao índice recebido como parâmetro.
Extend
Procedure
Qtd de elementos ou
sem parâmetro ou o
número de cópias e o
índice do elemento a
ser copiado.
Incrementa elementos a uma coleção,
aumentando o tamanho útil da coleção. Para
Varrays este incremento não deve ultrapassar
o limite estabelecido a tempo de definição.
Trim
Procedure
Sem parâmetro ou a Remove o último elemento da coleção ou os n
qtd de elementos
últimos elementos da coleção.
Delete
Procedure
Sem parâmetro ou o Remove todos os elementos de uma coleção
índice do elemento ou ou um elemento específico ou um intervalo de
índice
inicial
do elementos.
elemento e o índice
final
CAPÍTULO 4: TABELAS E REGISTROS - 104
PL/SQL9I – BÁSICO E AVANÇADO
EXISTS
Este método retorna True se o elemento correspondente ao parâmetro existe na coleção.
SQL> DECLARE
2
TYPE TTABLE IS TABLE OF VARCHAR2(20) INDEX BY BINARY_INTEGER;
3
TTEXTO
TTABLE;
4
I
NUMBER := 0;
5
CURSOR C1
IS SELECT NM_FUNC FROM FUNC;
6 BEGIN
7
FOR RC1 IN C1 LOOP
8
I := I + 1;
9
IF NOT TTEXTO.EXISTS(I) THEN
10
TTEXTO(I) := RC1.NM_FUNC;
11
END IF;
12
END LOOP;
13 END;
14 /
Procedimento PL/SQL concluído com sucesso.
L04_05
Observe que o método Exists verifica a inicialização de um elemento qualquer em uma Index-By
Table. Caso façamos acesso a um elemento sem que tenhamos feito uma atribuição anterior a este
índice, receberemos um erro. Veja o exemplo a seguir.
SQL> DECLARE
2
TYPE TTABLE IS TABLE OF VARCHAR2(20) INDEX BY BINARY_INTEGER;
3
TTEXTO
TTABLE;
4
I
NUMBER := 0;
5
CURSOR C1
IS SELECT NM_FUNC FROM FUNC;
6 BEGIN
7
FOR RC1 IN C1 LOOP
8
I := I + 1;
9
IF TTEXTO(I) IS NULL THEN
10
TTEXTO(I) := RC1.NM_FUNC;
11
END IF;
12
END LOOP;
13 END;
14 /
DECLARE
*
ERRO na linha 1:
ORA-01403: dados não encontrados
ORA-06512: em line 10
L04_06
Para que possamos utilizar uma determinada posição do array devemos ter atribuído dados a ele ou
utilizar o método Exists para verificar o preenchimento.
CAPÍTULO 4: TABELAS E REGISTROS - 105
PL/SQL9I – BÁSICO E AVANÇADO
COUNT
Este método retorna o número de elementos que a coleção contém atualmente e ignora os elementos
deletados.
SQL> DECLARE
2
TYPE TTABLE
IS TABLE
OF VARCHAR2(20)
3
INDEX BY BINARY_INTEGER;
4
TTEXTO
TTABLE;
5
I
NUMBER := 0;
6
CURSOR C1
IS SELECT NM_FUNC FROM FUNC;
7 BEGIN
8
FOR RC1 IN C1 LOOP
9
I := I + 1;
10
TTEXTO(I) := RC1.NM_FUNC;
11
END LOOP;
12
:MSG := 'Quantidade de elementos lidos : '||TTEXTO.COUNT;
13 END;
14 /
Procedimento PL/SQL concluído com sucesso.
MSG
---------------------------------------------------------------Quantidade de elementos lidos : 59
L04_07
Podemos usar o método Count em todos os locais em que uma expressão inteira é permitida (como
por exemplo, em um For i in 1..Ttexto.Count Loop).
O método Count retorna a quantidade de elementos ativos, que poderá ser inferior ao índice do último
elemento.
A atribuição de Null a um determinado elemento não o remove da lista de elementos ativos. Somente
o método Delete realiza esta ação.
LIMIT
Este método somente retorna um valor para Varrays. Para as demais coleções, o resultado é Null,
pois nem Nested Tables nem Index-By Tables possuem limite.
CAPÍTULO 4: TABELAS E REGISTROS - 106
PL/SQL9I – BÁSICO E AVANÇADO
FIRST E LAST
Retorna o índice do primeiro e/ou do último elemento preenchido da coleção. Se a coleção estiver
vazia, estes métodos retornam Null. Se a coleção contiver um único elemento, First e Last retornam o
mesmo índice.
O método First retornará o índice do primeiro elemento ativo e o método Last o índice do último
elemento ativo.
SQL> DECLARE
2
TYPE TTABLE
IS TABLE OF VARCHAR2(20)
3
INDEX BY BINARY_INTEGER;
4
TTEXTO
TTABLE;
5
I
NUMBER := 0;
6
CURSOR C1
IS SELECT NM_FUNC FROM FUNC;
7 BEGIN
8
FOR RC1 IN C1 LOOP
9
I := I + 10;
10
IF TTEXTO.EXISTS(I) THEN
11
I := I + 1;
12
END IF;
13
TTEXTO(I) := RC1.NM_FUNC;
14
END LOOP;
15
:MSG := 'LIMITE = '||TTEXTO.LIMIT||' QTD = '||TTEXTO.COUNT||
16
' ULTIMO = '||TTEXTO.LAST;
17 END;
18 /
Procedimento PL/SQL concluído com sucesso.
MSG
----------------------------------------------------------------LIMITE = QTD = 59 ULTIMO = 590
L04_08
Neste exemplo, ao preenchermos a Index-By Table, não utilizamos índices seqüenciais; o incremento
foi 10. Isso só é possível para Index-By Tables.
CAPÍTULO 4: TABELAS E REGISTROS - 107
PL/SQL9I – BÁSICO E AVANÇADO
PRIOR E NEXT
Retorna o índice que precede ou sucede o índice passado como parâmetro.
Se o índice passado como parâmetro for o primeiro, o método Prior (n) retornará Null, o mesmo
ocorrendo para Next (n), onde n é o último elemento ativo da coleção.
Estes métodos ignoram os elementos Null.
SQL>
SQL>
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
VARIABLE MSG VARCHAR2(1000)
SET LINESIZE 60
DECLARE
TYPE TTABLE
IS TABLE OF VARCHAR2(20)
INDEX BY BINARY_INTEGER;
TTEXTO
TTABLE;
I
NUMBER := 0;
CURSOR C1
IS SELECT NM_FUNC FROM FUNC;
BEGIN
:MSG := '';
FOR RC1 IN C1 LOOP
I := I + 10;
TTEXTO(I) := RC1.NM_FUNC;
END LOOP;
I := TTEXTO.FIRST;
WHILE I IS NOT NULL LOOP
:MSG := :MSG ||I||'-';
I := TTEXTO.NEXT(I);
END LOOP;
END;
/
Procedimento PL/SQL concluído com sucesso.
MSG
-----------------------------------------------------------10-20-30-40-50-60-70-80-90-100-110-120-130-140-150-160-170-1
80-190-200-210-220-230-240-250-260-270-280-290-300-310-320-3
30-340-350-360-370-380-390-400-410-420-430-440-450-460-470-4
80-490-500-510-520-530-540-550-560-570-580-590-
L04_09
Preenchemos os elementos com intervalos no indexador de 10. O método Next foi utilizado para
navegação pelos elementos da coleção e, ainda, para controlar o término da pesquisa.
Esses métodos são aplicáveis às três coleções da PL/SQL.
CAPÍTULO 4: TABELAS E REGISTROS - 108
PL/SQL9I – BÁSICO E AVANÇADO
EXTEND
Este método incrementa o tamanho de uma coleção. Esse método não é aplicável Index-By Tables.
TRIM
Este método remove um ou mais elementos do fim da coleção. Se utilizado sem parâmetros, remove
um elemento. O parâmetro, se usado, indica a quantidade de elementos finais a serem removidos.
Este método não é aplicável a Index-By Tables.
DELETE
Este método admite três sintaxes:
◊
O formato sem parâmetros remove todos os elementos da coleção.
◊
O formato com um parâmetro remove o n-ésimo elemento da coleção, onde n é o parâmetro
informado.
◊
O formato com dois parâmetros remove todos os elementos no intervalo m a n, sendo m e n
os parâmetros informados.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
DECLARE
TYPE TTABLE
IS TABLE OF VARCHAR2(20)
INDEX BY BINARY_INTEGER;
TTABLE;
NUMBER := 0;
IS SELECT NM_FUNC FROM FUNC;
TTEXTO
I
CURSOR C1
BEGIN
FOR R1 IN C1 LOOP
I := I + 1;
TTEXTO(I) := R1.NM_FUNC;
END LOOP;
TTEXTO.DELETE(TTEXTO.LAST);
TTEXTO.DELETE(2,10);
:MSG := ' QTD = '||TTEXTO.COUNT||' ULTIMO = '||TTEXTO.LAST;
END;
/
Procedimento PL/SQL concluído com sucesso.
MSG
-----------------------------------------------------------QTD = 49 ULTIMO = 58
L04_10
Acima, removemos o último elemento da coleção e alguns elementos centrais.
CAPÍTULO 4: TABELAS E REGISTROS - 109
PL/SQL9I – BÁSICO E AVANÇADO
EXCEPTIONS PARA COLEÇÕES
Veremos a seguir as condições de erro associadas a coleções.
Exception
Condição
Collection_is_null
É causada se forem aplicados métodos (exceto Exists) ou associação
de valores a uma Nested Table ou Varray não inicializados.
No_data_found
É causada se foi feita referência a um elemento inexistente (deletado)
de uma Nested Table ou uma referência a um elemento não
inicializado em uma Index-By Table.
Subscript_beyond_count
É causada se for feita uma referência a um elemento de uma coleção
usando um índice maior que o número de elementos da coleção.
Subscript_outside_limit
É causada se for feita uma referência a um elemento de uma Nested
Table ou Varray usando um número fora do intervalo legal (por
exemplo –1).
Value_error
É causada se um subscrito é Null ou não-inteiro.
SQL> DECLARE
2
TYPE TTABLE
IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
3
TTEXTO
TTABLE;
4 BEGIN
5
IF TTEXTO(5) = 2 THEN
6
:MSG := 'NÃO DEU ERRO';
7
END IF;
8 EXCEPTION
9 WHEN NO_DATA_FOUND THEN
10
:MSG := 'Acesso a uma posição não inicializada';
11 END;
12 /
Procedimento PL/SQL concluído com sucesso.
MSG
-----------------------------------------------------------Acesso a uma posição não inicializada
L04_11
No exemplo, fizemos uma tentativa de acesso a uma posição que não recebeu atribuição inicial. O
tratamento é similar a uma pesquisa sem sucesso a tabelas de SQL. A exception No_Data_Found é
adquirida na linha 5 do exemplo e tratada adequadamente.
CAPÍTULO 4: TABELAS E REGISTROS - 110
PL/SQL9I – BÁSICO E AVANÇADO
CRIANDO MATRIZES
A partir da versão 9i podemos criar um array dentro de outro array, isto é, podemos criar uma IndexBy Table cujo elemento é uma coleção (do tipo Index By Table ou não).
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
DECLARE
TYPE TTABLE
IS TABLE OF VARCHAR2(12) INDEX BY BINARY_INTEGER;
TYPE TMATRIZ IS TABLE OF TTABLE INDEX BY BINARY_INTEGER;
TTEXTO
TTABLE;
MTEXTO
TMATRIZ;
I
NUMBER;
BEGIN
:MSG := '';
FOR R1 IN (SELECT CD_MAT, NM_FUNC, NM_SOBRENOME,
TO_CHAR(DT_NASC, 'DD/MM/YYYY') DATA
FROM FUNC) LOOP
MTEXTO(R1.CD_MAT)(1) := R1.NM_FUNC;
MTEXTO(R1.CD_MAT)(2) := R1.NM_SOBRENOME;
MTEXTO(R1.CD_MAT)(3) := R1.DATA;
END LOOP;
I := MTEXTO.FIRST;
WHILE I IS NOT NULL LOOP
:MSG := :MSG||MTEXTO(I)(2)||'-';
I := MTEXTO.NEXT(I);
END LOOP;
END;
/
Procedimento PL/SQL concluído com sucesso.
MSG
----------------------------------------------------------------------HENDERSON----TEIXEIRA-KWAN-GOMES-SOUZA-PEREIRA-HONOFRE-SIQUEIRA--------LOURENCO-OLIVA-QUEIROZ-NOVAES-AZEVEDO-PINTO-YVES-SANTOS-WILARES-BAR
BOSA-JONES-LUZ-JANUARIO-MEDEIROS-SANTANA-JUVENTO-SEVERO-PONTES-SARAIVA
-SALGADO-MARQUES-LOPES-ROBERTO-GONCALVES-WI_SON-DILSON--------------
L04_12
Neste exemplo declaramos uma matriz para receber 3 colunas da tabela Func (nome, sobrenome e
nascimento).
Pudemos observar que o método First utilizado sem indicação de posição se refere ao nível mais
externo da matriz, assim como o método Next.
Se desejássemos navegar em uma “linha” específica, deveríamos usar Mtexto(i).First e
Mtexto(i).Next(j).
CAPÍTULO 4: TABELAS E REGISTROS - 111
PL/SQL9I – BÁSICO E AVANÇADO
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
DECLARE
TYPE TTABLE
IS TABLE OF VARCHAR2(25) INDEX BY BINARY_INTEGER;
TYPE TMATRIZ IS TABLE OF TTABLE INDEX BY BINARY_INTEGER;
TYPE TTRI
IS TABLE OF TMATRIZ INDEX BY BINARY_INTEGER;
MTRI
TTRI;
BEGIN
FOR I IN 1..3 LOOP
FOR J IN 1..4 LOOP
FOR K IN 1..6 LOOP
MTRI(I)(J)(K) := 'P-'||I||'-'||J||'-'||K;
END LOOP;
END LOOP;
END LOOP;
:MSG := 'MTRI.LAST='||MTRI.LAST||CHR(10);
:MSG := :MSG||'MTRI(2).LAST='||MTRI(2).LAST||CHR(10);
:MSG := :MSG||'MTRI(2)(3).LAST='||MTRI(2)(3).LAST||CHR(10);
:MSG := :MSG||MTRI(2)(3)(6);
END;
/
Procedimento PL/SQL concluído com sucesso.
MSG
-----------------------------------------------------------MTRI.LAST=3
MTRI(2).LAST=4
MTRI(2)(3).LAST=6
P-2-3-6
L04_13
No exemplo criamos uma matriz tridimensional. Observe o uso do método Last aplicável a cada “nível”
da matriz.
CAPÍTULO 4: TABELAS E REGISTROS - 112
PL/SQL9I – BÁSICO E AVANÇADO
REGISTROS
Um registro nada mais é que uma área estruturada, um grupo de itens de dados relacionados e
armazenados em campos contendo seu próprio nome e tipo de dados.
O atributo %Rowtype permite a declaração de um registro com o layout de uma tabela definida no
banco de dados ou das colunas selecionadas em um cursor.
A definição de registro permite total flexibilidade na declaração da área. Podemos definir nomes
próprios e tipos e tamanhos que desejarmos.
DECLARAÇÕES
Na declaração de um registro, podemos associar nessa estrutura elementos com os tipos escalares,
Varrays, Nested Tables, Index-By e objetos.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DECLARE
TYPE TREG IS RECORD (NOME
VARCHAR2(30),
MAT
NUMBER NOT NULL := 0,
DATA
VARCHAR2(20),
DEP
DEPTO.CD_DEPTO%TYPE,
RAMAL
NUMBER(04) );
VREG
TREG;
CURSOR C1 IS SELECT NM_FUNC||' '||NM_SOBRENOME, CD_MAT,
TO_CHAR(DT_NASC, 'DD-MM-YYYY'),
CD_DEPTO, NR_RAMAL FROM FUNC;
BEGIN
OPEN C1;
FETCH C1 INTO VREG;
END;
/
L04_14
Procedimento PL/SQL concluído com sucesso.
No exemplo, definimos um registro semelhante à linha a ser lida através do cursor.
CAPÍTULO 4: TABELAS E REGISTROS - 113
PL/SQL9I – BÁSICO E AVANÇADO
SQL>
2
3
4
5
6
7
8
9
10
DECLARE
TYPE TTAB
TYPE TREG
VREG
BEGIN
NULL;
END;
/
IS TABLE OF VARCHAR2(100) INDEX BY BINARY_INTEGER;
IS RECORD (NOME
VARCHAR2(30),
MAT
NUMBER NOT NULL := 0,
IXTAB
TTAB);
TREG;
L04_15
Procedimento PL/SQL concluído com sucesso.
Definimos um registro que contém um elemento que é uma coleção.
Observe que em ambos os exemplos a declaração de tipo não cria uma área para recepção dos
dados. Apenas cria o layout da área.
É indispensável a criação da variável com o tipo Treg para que seja possível a utilização da área.
A coluna Mat foi definida como Not Null, tornando obrigatória a especificação de um valor inicial.
CAPÍTULO 4: TABELAS E REGISTROS - 114
PL/SQL9I – BÁSICO E AVANÇADO
REFERENCIANDO REGISTROS
Os campos em um registro são referenciados pelo nome.
SQL> SET LINESIZE 70
SQL> DECLARE
2
TYPE TREG IS RECORD (NOME
VARCHAR2(30),
3
MAT
NUMBER NOT NULL := 0,
4
DEP
DEPTO.CD_DEPTO%TYPE);
5
VREG
TREG;
6
CURSOR C1 IS SELECT NM_FUNC||' '||NM_SOBRENOME, CD_MAT,
7
CD_DEPTO FROM FUNC;
8 BEGIN
9
OPEN C1;
10
:MSG := '';
11
LOOP
12
FETCH C1 INTO VREG;
13
EXIT WHEN C1%NOTFOUND;
14
IF VREG.MAT > 200 AND VREG.DEP IN ('D11', 'E11') THEN
15
:MSG := :MSG || VREG.NOME ||'; ';
16
END IF;
17
END LOOP;
18 END;
19 /
Procedimento PL/SQL concluído com sucesso.
MSG
---------------------------------------------------------------------WILIAM JONES; JOANA LUZ; ELINE SEVERO; JOAO PONTES; FELIPE SARAIVA; MA
RINA SALGADO;
L04_16
No exemplo, fazemos referência aos elementos do registro Vreg usando o nome do elemento
qualificado pelo nome do registro Vreg.Nome, Vreg.Mat e Vreg.Dep.
CAPÍTULO 4: TABELAS E REGISTROS - 115
PL/SQL9I – BÁSICO E AVANÇADO
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
DECLARE
TYPE TTAB
TYPE TREG
IS TABLE OF VARCHAR2(100) INDEX BY BINARY_INTEGER;
IS RECORD (NOME
VARCHAR2(30),
MAT
NUMBER NOT NULL := 0,
IXTAB
TTAB,
NTAB
TTAB,
VARR
TTAB);
TREG;
VREG
BEGIN
VREG.VARR(1) := 'teste';
VREG.NTAB(-5) := 'negativo';
VREG.IXTAB(0) := 'zero';
END;
/
L04_17
Procedimento PL/SQL concluído com sucesso.
No exemplo, fazemos referência aos elementos Index-By Table da mesma forma que aos elementos
escalares, ou seja, qualificando o elemento com o nome do registro.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
DECLARE
TYPE TTABLE
TYPE TOBJ
TYPE TREG
IS TABLE OF VARCHAR2(100)
INDEX BY BINARY_INTEGER;
IS RECORD (NOME
VARCHAR2(30),
NTAB
TTABLE);
IS RECORD (MAT
NUMBER NOT NULL := 0,
REG
TOBJ);
TREG;
VREG
BEGIN
VREG.REG.NTAB(1) := 'POSITIVO';
VREG.MAT
:= 1;
VREG.REG.NOME
:= 'TESTE';
END;
/
Procedimento PL/SQL concluído com sucesso.
L04_18
No exemplo, criamos um registro como elemento de outro registro. Desta forma, houve necessidade
de dupla qualificação: Vreg faz referência à variável com o tipo Treg e Reg corresponde ao campo do
registro Treg que é do tipo Tobj, que também é um registro.
Assim a referência ao elemento Mat é feita com a qualificação Vreg.Mat, pois está diretamente
definido em Treg. Já Nome é um elemento de Tobj, que por sua vez é um elemento de Treg; sua
referência então é feita com Vreg.Reg.Nome.
CAPÍTULO 4: TABELAS E REGISTROS - 116
PL/SQL9I – BÁSICO E AVANÇADO
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
DECLARE
TYPE TREG
IS RECORD (NOME
VARCHAR2(30),
MAT
NUMBER NOT NULL := 0,
DATA
VARCHAR2(20),
DEP
DEPTO.CD_DEPTO%TYPE,
RAMAL
NUMBER(04) );
IS TABLE OF TREG INDEX BY BINARY_INTEGER;
TTABLE;
TYPE TTABLE
NTAB
BEGIN
NTAB(1).NOME := 'A';
NTAB(1).MAT := 5;
IF NTAB(1).NOME = 'A' THEN
:MSG := NTAB(1).MAT;
END IF;
END;
/
Procedimento PL/SQL concluído com sucesso.
MSG
----------------------------------------------------------------5
L04_19
No exemplo, invertemos as definições feitas até agora; declaramos um registro como elemento de
uma Index-By Table.
Com isso, cada elemento da Index-By Table possui o layout do registro Treg. Observe que a
qualificação é feita pelo elemento da tabela Ntab(n).
CAPÍTULO 4: TABELAS E REGISTROS - 117
PL/SQL9I – BÁSICO E AVANÇADO
MANIPULANDO REGISTROS
A manipulação de um registro é similar à utilização do %RowType que corresponde, na verdade, a um
registro (com o layout da tabela ou das colunas selecionadas pelo cursor).
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
DECLARE
CURSOR C1
IS SELECT CD_MAT, NM_FUNC, DT_NASC
FROM FUNC;
IS TABLE OF C1%ROWTYPE INDEX BY BINARY_INTEGER;
TNESTED;
NUMBER := 0;
TYPE TNESTED
NTAB
I
BEGIN
OPEN C1;
LOOP
I := I + 1;
FETCH C1 INTO NTAB(I);
EXIT WHEN C1%NOTFOUND;
IF NTAB(I).NM_FUNC = 'DAVI' THEN
:MSG := NTAB(I).DT_NASC;
END IF;
END LOOP;
END;
/
Procedimento PL/SQL concluído com sucesso.
MSG
---------------------------------------------------------------------29/05/71
L04_20
Neste primeiro exemplo, declaramos um registro (%RowType) como elemento da Index-By Table. Os
nomes dos elementos deste registro correspondem às colunas selecionadas do banco de dados.
CAPÍTULO 4: TABELAS E REGISTROS - 118
PL/SQL9I – BÁSICO E AVANÇADO
SQL> DECLARE
2
CURSOR C1
IS SELECT CD_MAT, NM_FUNC, DT_NASC
3
FROM FUNC;
4
TYPE TPROJ
IS TABLE OF VARCHAR2(6) INDEX BY BINARY_INTEGER;
5
TYPE TREG
IS RECORD (MAT
NUMBER(5),
6
NOME
FUNC.NM_FUNC%TYPE,
7
DATA
DATE,
8
PROJ
TPROJ);
9
TYPE TNESTED
IS TABLE OF TREG INDEX BY BINARY_INTEGER;
10
NTAB
TNESTED;
11
I
NUMBER := 0;
12
J
NUMBER := 0;
13 BEGIN
14
OPEN C1;
15
LOOP
16
I := I + 1;
17
FETCH C1 INTO NTAB(I).MAT, NTAB(I).NOME, NTAB(I).DATA;
18
EXIT WHEN C1%NOTFOUND;
19
J := 0;
20
FOR R1 IN (SELECT CD_PROJ FROM PROJ
21
WHERE CD_RESP = NTAB(I).MAT) LOOP
22
J := J + 1;
23
NTAB(I).PROJ(J) := R1.CD_PROJ;
24
END LOOP;
25
IF NTAB(I).NOME = 'DANIEL' THEN
26
:MSG := NTAB(I).NOME;
27
J := NTAB(I).PROJ.FIRST;
28
WHILE J IS NOT NULL LOOP
29
:MSG := :MSG||'-'||NTAB(I).PROJ(J);
30
J := NTAB(I).PROJ.NEXT(J);
31
END LOOP;
32
END IF;
33
END LOOP;
34 END;
35 /
Procedimento PL/SQL concluído com sucesso.
MSG
---------------------------------------------------------------------DANIEL-AD3112
L04_21
No exemplo, repetimos a mesma operação realizada anteriormente, substituindo o registro implícito
(%RowType) pela definição explícita de Treg. Os nomes dos elementos foram alterados, mas
sintaticamente não houve diferença de utilização.
Observe que acrescentamos um array dentro do registro, o qual foi incluído dentro de outro array. Esta
forma sintática somente é permitida a partir da versão 9i.
A leitura realizada nos dois exemplos permitiu que toda a tabela fosse carregada na memória para
manipulação.
CAPÍTULO 4: TABELAS E REGISTROS - 119
PL/SQL9I – BÁSICO E AVANÇADO
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
IF
DECLARE
CURSOR C1
TYPE TREG
IS SELECT NM_FUNC, DT_NASC
FROM FUNC;
IS RECORD (NOME
FUNC.NM_FUNC%TYPE,
DATA
DATE);
TREG;
TREG;
REG1
REG2
BEGIN
OPEN C1;
FETCH C1 INTO REG1;
REG2 := REG1;
IF REG1 = REG2 THEN
:MSG := 'Registros iguais';
END IF;
END;
/
REG1 = REG2 THEN
*
ERRO na linha 12:
ORA-06550: linha 12, coluna 11:
PLS-00306: número incorreto de tipos de argumentos na chamada para
'='
ORA-06550: linha 12, coluna 3:
PL/SQL: Statement ignored
L04_22
Observamos que a atribuição de um registro para outro é possível se as duas variáveis fizerem
referência ao mesmo tipo registro. A comparação, porém, não é possível.
CAPÍTULO 4: TABELAS E REGISTROS - 120
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 4:
1. Quais os três tipos de coleções existentes em PL/SQL? Cite duas características das Index-By
Tables.
2. O que são registros em PL/SQL?
3. Crie um bloco de PL/SQL que receba números (quantidade qualquer) e apresente-os em ordem
ascendente. O programa deve receber um único parâmetro alfanumérico contendo os números
separados por vírgula (use coleção).
4. Leia a tabela de funcionários e preencha a seguinte tabela em memória:
TabCargo – cujo layout é composto do número do cargo e quantidade e código do departamento.
Ao término da carga, apresente a quantidade de funcionários por cargo e por departamento.
Use uma Index-ByTable para acumular a quantidade.
5. Faça um programa que calcule um elemento da seqüência de Fibonacci que será passado como
parâmetro.
6. Faça um bloco de PL/SQL que leia os n (recebido como parâmetro) funcionários com maior salário
e apresente-os em ordem de matrícula. Deve ser mostrado o nome, cargo e salário de cada
funcionário.
7. Suponha um tabuleiro de xadrez com oito linhas e oito colunas. Receba como parâmetro (linha e
coluna) o posicionamento de uma rainha no tabuleiro. Faça um programa que determine as outras
possíveis posições em que a outra rainha poderá ser colocada sabendo-se que, no xadrez, a rainha
pode se deslocar tanto na reta quanto na diagonal.
CAPÍTULO 4: TABELAS E REGISTROS - 121
PL/SQL9I – BÁSICO E AVANÇADO
CAPÍTULO 5: BULK BIND
CONCEITOS
A associação de valores a variáveis de PL/SQL em comandos de SQL é chamada Bind. A associação
de uma coleção inteira, de uma única vez, é chamada Bulk Bind.
O objetivo de um Bulk Bind é aumento de performance. Observe o bloco de PL/SQL abaixo.
SQL>
2
3
4
5
6
7
8
9
10
11
12
DECLARE
TYPE L_NUM
IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
WCD_LISTA
L_NUM;
BEGIN
FOR I IN 1..2000 LOOP
WCD_LISTA(I) := I;
END LOOP;
FOR I IN 1..2000 LOOP
DELETE FROM FUNC WHERE CD_MAT = WCD_LISTA(I);
END LOOP;
END;
/
L05_01
No exemplo, o comando Delete é executado 2.000 vezes; isto significa que o PL/SQL (Engine) envia
um comando SQL Delete para cada valor da lista.
Cada vez que a PL/SQL Engine tem necessidade de estabelecer uma interrupção para acionar a SQL
Engine, ocorre um overhead. Sendo assim, quando trabalhamos com coleções (Nested Tables, IndexBy, Varray e Host Arrays) e utilizamos iterações (loops) usando elementos destas coleções, como
variáveis Bind (em comandos de SQL), adicionamos um overhead à nossa execução.
Se a iteração afetar cinco ou mais linhas do banco de dados, o uso de Bulk Binds pode aumentar a
performance consideravelmente.
Observe o mesmo trecho de programa em que substituímos o comando For pelo comando ForAll.
SQL>
2
3
4
5
6
7
8
9
10
11
DECLARE
TYPE L_NUM
IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
WCD_LISTA
L_NUM;
BEGIN
FOR I IN 1..2000 LOOP
WCD_LISTA(I) := I;
END LOOP;
FORALL I IN 1..2000
DELETE FROM FUNC WHERE CD_MAT = WCD_LISTA(I);
END;
/
L05_02
No exemplo anterior, a coleção Wcd_Lista é passada inteira de uma única vez para a SQL Engine.
CAPÍTULO 5: BULK BIND - 122
PL/SQL9I – BÁSICO E AVANÇADO
SQL> CREATE TABLE LISTA(CD_LISTA
2
TX_LISTA
Tabela criada.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
32
NUMBER,
VARCHAR2(200));
DECLARE
TYPE L_NUM
IS TABLE OF NUMBER
INDEX BY BINARY_INTEGER;
TYPE L_CHAR IS TABLE OF VARCHAR2(200) INDEX BY BINARY_INTEGER;
WCD_LISTA
L_NUM;
WTX_LISTA
L_CHAR;
T1
NUMBER;
T1F
NUMBER;
T2
NUMBER;
T2F
NUMBER;
BEGIN
-- Preenche as tabelas em memória -FOR I IN 1..90000 LOOP
WCD_LISTA(I) := I;
WTX_LISTA(I) := 'TEXTO DA LINHA '||TO_CHAR(I);
END LOOP;
-- Inclui as linhas na tabela usando For Loop
SELECT TO_CHAR(CURRENT_TIMESTAMP, 'SSSSS') INTO T1 FROM DUAL;
FOR I IN 1..90000 LOOP
INSERT INTO LISTA VALUES (WCD_LISTA(I), WTX_LISTA(I));
END LOOP;
SELECT TO_CHAR(CURRENT_TIMESTAMP, 'SSSSS') INTO T1F FROM DUAL;
COMMIT;
-- Inclui as linhas na tabela usando ForAll
SELECT TO_CHAR(CURRENT_TIMESTAMP, 'SSSSS') INTO T2 FROM DUAL;
FORALL I IN 1..90000
INSERT INTO LISTA VALUES(WCD_LISTA(I), WTX_LISTA(I));
SELECT TO_CHAR(CURRENT_TIMESTAMP, 'SSSSS') INTO T2F FROM DUAL;
COMMIT;
:MSG := 'Tempo para Insert com For Loop = '||
TO_CHAR(T1F - T1)||CHR(10)||
'Tempo para Insert com ForAll = '||TO_CHAR(T2F - T2);
END;
/
Procedimento PL/SQL concluído com sucesso.
MSG
-------------------------------------------------------------------Tempo para Insert com For Loop = 13
Tempo para Insert com ForAll = 3
L05_03
Este exemplo mostra uma significativa diferença de tempo entre as duas operações. É claro que esta
diferença terá variações para maior ou menor de acordo com a configuração do equipamento.
No entanto, não devemos nos esquecer que a performance das aplicações pode ser melhorada com o
uso deste recurso.
CAPÍTULO 5: BULK BIND - 123
PL/SQL9I – BÁSICO E AVANÇADO
O COMANDO FORALL
Este comando indica à PL/SQL Engine para preparar toda a coleção de entrada (Bulk Bind) antes de
enviá-la à SQL Engine.
SINTAXE
FORALL <index> IN <valor inferior>..<valor superior> <comando SQL>;
Como características ou restrições, temos:
◊
O comando SQL presente na sintaxe pode ser Insert, Update ou Delete, fazendo referência a
elementos da coleção (a SQL Engine executa um comando Select para cada índice no intervalo).
◊
As fronteiras (<valor inferior> e <valor superior>) devem especificar um intervalo válido de
índices numéricos consecutivos (não precisa ser a coleção inteira, pode ser parte da coleção
desde que seja consecutiva).
◊
Todos os elementos da coleção no intervalo especificado devem existir.
◊
O subscrito da coleção não pode ser uma expressão, portanto o texto Where cd_mat =
wcd_lista(I + 1) é inválido.
◊
Antes de cada comando SQL executado pelo ForAll, é criado um SavePoint implícito. Desta
forma, se no comando ForAll de um determinado programa a terceira execução (ou a utilização do
terceiro índice da lista) falha, a primeira e segunda execuções do comando SQL associado não
são desmanchadas; apenas a partir do terceiro índice a ação falha.
CAPÍTULO 5: BULK BIND - 124
PL/SQL9I – BÁSICO E AVANÇADO
A CLÁUSULA BULK COLLECT
A cláusula Bulk Collect indica à SQL Engine para preparar toda a coleção de saída (Bulk Bind) antes
de retorná-la para a PL/SQL Engine. Esta cláusula pode ser usada junto com a cláusula INTO nos
comandos Select Into, Fetch Into e Returning Into. Reveja a sintaxe destes comandos.
SINTAXE
... Bulk Collect Into <coleção> [, <coleção>] ...
Abaixo utilizamos a cláusula Bulk Collect Into em um comando Select. Observe que a coleção (IndexBy Table) não precisou ser inicializada. A SQL Engine realiza esta operação implicitamente (para
Varrays não estende a coleção acima de seu tamanho máximo).
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
DECLARE
TYPE L_NUM IS TABLE OF LISTA.CD_LISTA%TYPE INDEX BY BINARY_INTEGER;
TYPE L_CHAR IS TABLE OF LISTA.TX_LISTA%TYPE INDEX BY BINARY_INTEGER;
WCD_LISTA
L_NUM;
WTX_LISTA
L_CHAR;
BEGIN
SELECT CD_LISTA, TX_LISTA
BULK COLLECT INTO WCD_LISTA, WTX_LISTA FROM LISTA;
:MSG := 'Códigos lidos: ';
FOR I IN 20000..20005 LOOP
:MSG := :MSG || WCD_LISTA(I) ||'; ';
END LOOP;
END;
/
Procedimento PL/SQL concluído com sucesso.
MSG
------------------------------------------------------------------------- L5_04
Códigos lidos: 70037; 70038; 70039; 70040; 70041; 70042;
A SQL Engine montará todas as linhas da tabela Lista (180.000) em uma Nested Table antes de
retornar esta coleção para a PL/SQL Engine.
CAPÍTULO 5: BULK BIND - 125
PL/SQL9I – BÁSICO E AVANÇADO
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
DECLARE
TYPE L_NUM
TYPE L_CHAR
WCD_LISTA
WTX_LISTA
CURSOR C1
IS TABLE OF LISTA.CD_LISTA%TYPE
INDEX BY BINARY_INTEGER;
IS TABLE OF LISTA.TX_LISTA%TYPE
INDEX BY BINARY_INTEGER;
L_NUM;
L_CHAR;
IS SELECT CD_LISTA, TX_LISTA FROM LISTA
WHERE MOD(CD_LISTA, 3) = 0;
BEGIN
OPEN C1;
FETCH C1 BULK COLLECT INTO WCD_LISTA, WTX_LISTA;
:MSG := 'Códigos lidos: ';
FOR I IN 10..20 LOOP
:MSG := :MSG || WCD_LISTA(I) ||'; ';
END LOOP;
END;
/
Procedimento PL/SQL concluído com sucesso.
MSG
---------------------------------------------------------------------Códigos lidos: 90000; 89859; 89862; 89865; 89868; 89871; 89874; 89877;
89880; 89883; 89886;
L05_05
Utilizamos o comando Fetch juntamente com a cláusula Bulk Collect Into. Observe que não foi
necessário um Loop para obtenção de todas as linhas. A leitura das informações para a PL/SQL
Engine foi realizada em um único passo.
A cláusula Bulk Collect juntamente com o comando Fetch (veja abaixo) permite que limitemos a
quantidade de linhas lidas. A cláusula Limit estabeleceu que apenas 200 linhas seriam lidas uma vez
que somente nos interessávamos por uma parte do conteúdo do arquivo.
....
10 BEGIN
11
OPEN C1;
12
:MSG := 'Códigos lidos: ';
13
FETCH C1 BULK COLLECT INTO WCD_LISTA, WTX_LISTA LIMIT 200;
14
FOR I IN 10..20 LOOP
15
:MSG := :MSG || WCD_LISTA(I) ||'; ';
16
END LOOP;
17 END;
18 /
Procedimento PL/SQL concluído com sucesso.
MSG
---------------------------------------------------------------------Códigos lidos: 90000; 89859; 89862; 89865; 89868; 89871; 89874; 89877;
89880; 89883; 89886;
L05_06
CAPÍTULO 5: BULK BIND - 126
PL/SQL9I – BÁSICO E AVANÇADO
O ATRIBUTO %BULK_ROWCOUNT
Para processar qualquer comando de SQL, a PL/SQL Engine abre um cursor implícito (isto já
sabemos) de nome SQL.
Posteriormente, podemos usar os atributos do cursor (%Found, %IsOpen, %NotFound e %RowCount)
para obter informações sobre as operações realizadas. Este cursor implícito SQL ganha agora um
atributo composto chamado %Bulk_RowCount, que indica o resultado do processamento para cada
elemento da coleção.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
DECLARE
TYPE L_NUM
IS TABLE OF LISTA.CD_LISTA%TYPE
INDEX BY BINARY_INTEGER;
L_NUM;
WCD_LISTA
BEGIN
DELETE FROM LISTA;
FOR I IN 1..5 LOOP
WCD_LISTA(I) := I;
END LOOP;
FOR I IN 1..100 LOOP
INSERT INTO LISTA
VALUES (MOD(I, 3), 'LINHA ' || I || ' DA LISTA');
END LOOP;
FORALL I IN WCD_LISTA.FIRST..WCD_LISTA.LAST
UPDATE LISTA
SET TX_LISTA = 'NOVA TXLISTA '
WHERE CD_LISTA = WCD_LISTA(I);
:MSG := 'SQL%BULK_ROWCOUNT ';
FOR I IN 1..5 LOOP
:MSG := :MSG || ' ('||I||')='|| SQL%BULK_ROWCOUNT(I);
END LOOP;
END;
/
Procedimento PL/SQL concluído com sucesso.
MSG
---------------------------------------------------------------SQL%BULK_ROWCOUNT (1)=34 (2)=33 (3)=0 (4)=0 (5)=0
L05_07
A coleção Index-By Table criada possui cinco elementos, preenchida com os valores 1 a 5.
Quando ocorre a execução do comando ForAll, para cada elemento da lista é iniciada a execução de
um comando Update para o banco de dados que pode atualizar n linhas. O atributo %Bulk_RowCount
nos indicará quantas linhas foram atualizadas para cada uma das posições da coleção.
Como resultado, temos que para a posição 1 ocorreram 34 atualizações; para a posição 2 ocorreram
33 atualizações e para as demais, nenhuma atualização.
CAPÍTULO 5: BULK BIND - 127
PL/SQL9I – BÁSICO E AVANÇADO
O atributo %Bulk_RowCount e o comando ForAll usam os mesmos subscritos, ou seja, se o comando
ForAll utilizar os subscritos de -5 a 10, o atributo %Bulk_RowCount informará a quantidade de
processamento para os elementos dos atributos -5 a 10.
O ATRIBUTO %BULK_EXCEPTIONS
Durante a execução de um comando For All podemos decidir interromper a execução do comando
quando a primeira exception for encontrada ou podemos prosseguir e armazenar todas as exceptions
ocorridas para posterior tratamento.
Para esta segunda forma devemos incluir a cláusula Save Exceptions na execução do comando For
All. A presença da cláusula Save Exceptions fará com que a execução prossiga até o fim e todas as
exceptions sejam armazenadas (em ordem de ocorrência) em uma coleção.
O acesso a esta coleção é feito com o atributo %Bulk_Exceptions. Nesta coleção encontraremos um
registro para cada erro adquirido. O índice desta coleção navega seqüencialmente pela lista de erros
encontrados.
A propriedade Count do atributo %Bulk_Exceptions indica a quantidade total de erros, desta forma o
primeiro
erro
é
obtido
com
SQL%Bulk_Exceptions(1)
e
o
último
com
SQL%Bulk_Exceptions(SQL%Bulk_Exceptions.Count).
Cada registro da coleção possui dois campos:
◊
O primeiro, %Bulk_Exceptions(i).ERROR_INDEX, indica a iteração que recebeu a exception.
◊
O segundo, %Bulk_Exceptions(i).ERROR_CODE, contém o código do erro.
CAPÍTULO 5: BULK BIND - 128
PL/SQL9I – BÁSICO E AVANÇADO
SQL> DECLARE
2
TYPE L_NUM
IS TABLE OF LISTA.CD_LISTA%TYPE
3
INDEX BY BINARY_INTEGER;
4
WCD_LISTA
L_NUM;
5 BEGIN
6
DELETE FROM LISTA;
7
FOR I IN 1..5 LOOP
8
WCD_LISTA(I) := I;
9
END LOOP;
10
FOR I IN 1..100 LOOP
11
INSERT INTO LISTA
12
VALUES (MOD(I, 4), CURRENT_TIMESTAMP||'LINHA ' ||
13
I||' DA LISTA '||CURRENT_TIMESTAMP);
14
END LOOP;
15
FORALL I IN WCD_LISTA.FIRST..WCD_LISTA.LAST SAVE EXCEPTIONS
16
UPDATE LISTA
17
SET TX_LISTA = TX_LISTA||TX_LISTA||TX_LISTA||'NOVA TXLISTA '
18
WHERE CD_LISTA = WCD_LISTA(I);
19 EXCEPTION
20 WHEN OTHERS THEN
21
:MSG := 'COUNT= '||SQL%BULK_EXCEPTIONS.COUNT||
22
' SQL%BULK_EXCEPTIONS ';
23
FOR I IN 1..SQL%BULK_EXCEPTIONS.COUNT LOOP
24
:MSG := :MSG || ' ('||SQL%BULK_EXCEPTIONS(I).ERROR_INDEX||')='
25
|| SQL%BULK_EXCEPTIONS(I).ERROR_CODE;
26
END LOOP;
27 END;
28 /
Procedimento PL/SQL concluído com sucesso.
MSG
---------------------------------------------------------------------COUNT= 3 SQL%BULK_EXCEPTIONS (1)=1401 (2)=1401 (3)=1401
L05_08
Neste exemplo repetimos o exemplo anterior, porém, atribuímos um texto bastante longo à coluna
Tx_Lista no momento do Update para forçar a ocorrência de algum erro.
A inclusão do texto Save Exceptions garantiu que a interrupção não ocorresse no primeiro erro e sim
no fim de toda a operação (no caso de atualização).
Houve o desvio para a área de tratamento de erro e lá obtivemos a quantidade total de erros ocorridos
com sql%bulk_exceptions.count e as duas informações sobre os erros: a iteração com erro e o código
do erro.
No nosso caso a iteração com erro coincide com o indexador I pois foram, exatamente, os três
primeiros comandos Update que geraram erro, no entanto se alterássemos o preenchimento do array
Wcd_lista (invertendo a ordem de preenchimento), as iterações com erro seriam 3, 4 e 5. Neste caso
você notaria a diferença entre o indexador e a iteração. Utilize o arquivo L05_08a para teste.
CAPÍTULO 5: BULK BIND - 129
PL/SQL9I – BÁSICO E AVANÇADO
OS DEMAIS ATRIBUTOS
Podemos utilizar os demais atributos de cursor (%Found, %IsOpen, %NotFound e %RowCount )
juntamente com Bulk Binds:
◊
%RowCount – Retorna o número total de linhas processadas por todas as execuções do
comando SQL.
◊
%Found e %NotFound – Referem-se apenas à última execução do comando SQL.
Para sabermos se uma determina execução não produziu resultados, podemos utilizar
%Bulk_Rowcount, que, para o elemento indicado (se nenhuma linha for afetada), retornará zero.
CARACTERÍSTICAS OU RESTRIÇÕES
Como características ou restrições, temos:
◊
Quando a SQL Engine inicializa a coleção, ela destrói (grava por cima) qualquer elemento
preexistente e começa o preenchimento da coleção pelo elemento 1, e consecutivamente de 1 em
1.
◊
A coleção Bulk Bind deve ser uma coleção de valores escalares. Não podem ser valores
compostos.
◊
Não podemos Bulk Fetch de um cursor em um Registro (Record), mesmo que este Record
faça referência a uma coleção (Nested Table, Varray ou Index-By).
◊
Não podemos usar o comando Select .... Bulk Collect em um comando ForAll.
◊
Podemos usar ForAll e Bulk Bind juntos.
◊
%Bulk_RowCount não pode ser associado a outras coleções, além de não poder ser passado
como parâmetro para subprogramas.
CAPÍTULO 5: BULK BIND - 130
PL/SQL9I – BÁSICO E AVANÇADO
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
DECLARE
TYPE L_NUM
TYPE L_CHAR
IS TABLE
INDEX
IS TABLE
INDEX
L_CHAR;
L_NUM;
OF
BY
OF
BY
LISTA.CD_LISTA%TYPE
BINARY_INTEGER;
LISTA.TX_LISTA%TYPE
BINARY_INTEGER;
WTX_RETORNO
WCD_LISTA
BEGIN
DELETE FROM LISTA;
FOR I IN 1..5 LOOP
WCD_LISTA(I) := I;
END LOOP;
FOR I IN 1..100 LOOP
INSERT INTO LISTA
VALUES (MOD(I, 3), 'LINHA ' || I || ' DA LISTA');
END LOOP;
FORALL I IN WCD_LISTA.FIRST..WCD_LISTA.LAST
UPDATE LISTA SET TX_LISTA = TX_LISTA||'-NOVA'
WHERE CD_LISTA = WCD_LISTA(I)
RETURNING TX_LISTA BULK COLLECT INTO WTX_RETORNO;
:MSG := 'TEXTOS '||CHR(10);
FOR I IN 1..5 LOOP
:MSG := :MSG || ' ('||I||')='|| WTX_RETORNO(I) ||
CHR(10);
END LOOP;
END;
/
Procedimento PL/SQL concluído com sucesso.
MSG
------------------------------------------------------------------TEXTOS
(1)=LINHA 1 DA LISTA-NOVA
(2)=LINHA 4 DA LISTA-NOVA
(3)=LINHA 7 DA LISTA-NOVA
(4)=LINHA 10 DA LISTA-NOVA
(5)=LINHA 13 DA LISTA-NOVA
L05_09
No exemplo, a cada atualização, a SQL Engine guarda todas as modificações ocorridas em cada uma
das linhas envolvidas e as vai adicionando ao fim da lista da atualização anterior.
Desta forma, obtivemos informação de todas as linhas atualizadas e não apenas da última.
CAPÍTULO 5: BULK BIND - 131
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 5:
1. Crie um bloco de PL/SQL que receba como parâmetro uma série de matrículas separadas por
vírgula em um único parâmetro.
Monte este número em um array e faça uma consulta ao banco de dados, obtendo o nome e salário
de todos os funcionários que existam na lista. Use ForAll e Bulk Collect.
CAPÍTULO 5: BULK BIND - 132
PL/SQL9I – BÁSICO E AVANÇADO
CAPÍTULO 6: SUBPROGRAMAS
CONCEITO
Subprogramas são blocos de PL/SQL que têm nome e, portanto, podem ser chamados de outros
blocos. Além desta característica, os subprogramas podem receber parâmetros formais em vez das
variáveis de substituição que foram usadas até agora.
A PL/SQL possui dois tipos de subprogramas: Procedures e Functions.
CARACTERÍSTICAS DOS SUBPROGRAMAS
De um modo geral, suas características são bastante similares. As semelhanças e diferenças estão
relacionadas a seguir:
◊
Os subprogramas, como todos os blocos de PL/SQL, também possuem uma área para
declaração de variáveis, uma área para Alunoolvimento da lógica e uma área opcional para
tratamento de erros.
◊
Os subprogramas podem ser armazenados no banco de dados e acionados por qualquer
programa aplicativo que tenha autorização de execução.
◊
Os subprogramas podem receber um ou mais parâmetros formais.
◊
Uma function sempre retorna um valor para o bloco de PL/SQL de onde foi acionada.
◊
Uma procedure pode retornar ou não um valor para o bloco de PL/SQL de onde foi acionada.
◊
Uma Function armazenada no banco de dados pode ser acionada em comandos de SQL.
Uma procedure, nas mesmas condições não pode.
◊
Uma procedure é acionada como um comando de PL/SQL, isto é, isolada. Não faz parte de
expressões.
◊
Uma função, para ser acionada, deve fazer parte de uma expressão PL/SQL (por exemplo,
uma atribuição a uma variável).
◊
Uma função deve, obrigatoriamente, executar o comando Return para transferir a informação
de volta para o programa acionador.
CAPÍTULO 6: SUBPROGRAMAS - 133
PL/SQL9I – BÁSICO E AVANÇADO
A Sintaxe nos mostra que as definições de procedure e function são bastante similares, diferindo
apenas na presença da cláusula Return para funções onde devemos determinar o tipo de retorno
esperado.
SINTAXE
CAPÍTULO 6: SUBPROGRAMAS - 134
PL/SQL9I – BÁSICO E AVANÇADO
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
DECLARE
CURSOR C1
IS SELECT VL_SAL, NM_FUNC FROM FUNC;
FUNCTION INSS (SAL IN NUMBER, LIMITE IN NUMBER) RETURN NUMBER IS
VINSS
NUMBER;
BEGIN
VINSS := SAL * 0.08;
IF VINSS > LIMITE THEN
VINSS := LIMITE;
END IF;
RETURN VINSS;
END INSS;
BEGIN
:MSG := '';
FOR RC1 IN C1 LOOP
IF INSS(RC1.VL_SAL, 1000) > 400 THEN
:MSG := :MSG || RC1.NM_FUNC || '; ';
END IF;
END LOOP;
END;
/
L06_01
Procedimento PL/SQL concluído com sucesso.
MSG
-----------------------------------------------------------------------CRISTINA; VICENTE; JOAQUIM;
No exemplo vemos a declaração de uma função dentro de um bloco anônimo. A função recebe dois
parâmetros de entrada (IN) e retorna um valor numérico (Return Number).
O comando de PL/SQL Return (Return Vinss) foi usado para retornar o resultado do cálculo para o
bloco de onde a função foi acionada.
No bloco anônimo, a função foi utilizada em uma expressão IF. Isso pode ser feito porque a função
retorna um valor. A variável Vinss foi declarada localmente na função Inss e tem vida útil até o End da
função. A cláusula End da função foi acompanhada do nome da função Inss (isso é opcional).
Repetiremos, agora, a mesma ação, substituindo a função por uma procedure.
CAPÍTULO 6: SUBPROGRAMAS - 135
PL/SQL9I – BÁSICO E AVANÇADO
SQL> DECLARE
2
VINSS
NUMBER;
3
CURSOR C1
IS SELECT VL_SAL, NM_FUNC FROM FUNC;
4
PROCEDURE INSS (SAL
IN NUMBER,
5
LIMITE IN NUMBER,
6
VALOR IN OUT NUMBER) IS
7
VINSS
NUMBER;
8
BEGIN
9
VALOR := SAL * 0.08;
10
IF VALOR > LIMITE THEN
11
VALOR := LIMITE;
12
END IF;
13
END INSS;
14 BEGIN
15
:MSG := '';
16
FOR RC1 IN C1 LOOP
17
INSS(RC1.VL_SAL, 1000, VINSS);
18
IF VINSS > 400 THEN
19
:MSG := :MSG || RC1.NM_FUNC || '; ';
20
END IF;
21
END LOOP;
22 END;
23 /
Procedimento PL/SQL concluído com sucesso.
MSG
------------------------------------------------------------CRISTINA; VICENTE; JOAQUIM;
L06_02
No exemplo, utilizamos uma procedure para realizar a mesma tarefa anterior.
Primeiramente, tivemos de incluir um novo parâmetro na procedure (tipo In Out) que pudesse retornar
informação para o bloco principal.
A chamada da procedure não pode ser feita diretamente no comando IF. Desta forma, a procedure foi
acionada e atribuiu à variável Vinss (declarada no bloco principal) o cálculo do Inss realizado dentro
da procedure.
CAPÍTULO 6: SUBPROGRAMAS - 136
PL/SQL9I – BÁSICO E AVANÇADO
PARÂMETROS
Os subprogramas recebem informação dos blocos que os acionaram através de parâmetros. Esses
parâmetros são ditos formais.
Nesse item, trataremos dos modos como um parâmetro pode ser passado, a notação para chamada
dos subprogramas que possuem parâmetros e valores defaults.
MODOS DOS PARÂMETROS
Como visto na sintaxe, um parâmetro pode ser definido de três formas:
◊
IN – Indica um parâmetro de entrada. Dentro do subprograma, um parâmetro do tipo IN
funciona como uma constante, isto é, podemos ler seu valor, porém não podemos fazer qualquer
tipo de atribuição a eles. Caso o modo não seja informado, este é o modo default.
SQL> DECLARE
2 VPREC
NUMBER := 30;
3 PROCEDURE CALC(VALOR IN NUMBER) IS
4 BEGIN
5
IF VALOR <= 10 THEN
6
UPDATE FUNC SET VL_SAL = VL_SAL * VALOR;
7
ELSE
8
VALOR := 5;
9
UPDATE FUNC SET VL_SAL = VL_SAL * VALOR;
10
END IF;
11 END CALC;
12 BEGIN
13 CALC(30);
14 CALC(VPREC);
15 END;
16 /
VALOR := 5;
*
ERRO na linha 8:
ORA-06550: linha 8, coluna 8:
PLS-00363: a expressão 'VALOR' não pode ser usada como um destino de atribuição
ORA-06550: linha 8, coluna 8:
PL/SQL: Statement ignored
L06_03
No exemplo, tentamos atribuir 5 ao parâmetro Valor e recebemos um erro de compilação pois esta
ação não é válida para este modo de parâmetro.
Observe, ainda, que a transferência de informações entre o bloco principal e a procedure pode ser
feita de duas formas: com o valor (30) ou através de uma variável (Vprec). Essas duas sintaxes são
válidas porque o parâmetro é de entrada.
CAPÍTULO 6: SUBPROGRAMAS - 137
PL/SQL9I – BÁSICO E AVANÇADO
◊
OUT – Indica um parâmetro de saída. Dentro do subprograma, um parâmetro do tipo OUT
pode receber atribuições, funciona como uma variável local que tanto pode receber atribuições
como pode ser lida.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DECLARE
VINSS
NUMBER := 2000;
SALARIO
NUMBER := 3000;
PROCEDURE INSS(SAL IN NUMBER, VALOR OUT NUMBER) IS
BEGIN
IF VALOR >= 1000 THEN
VALOR := 1000;
ELSE
VALOR := SAL * 0.8;
END IF;
END INSS;
BEGIN
INSS(3000, VINSS);
INSS(SALARIO, VINSS);
:MSG := VINSS;
END;
/
Procedimento PL/SQL concluído com sucesso.
MSG
---------------------------------------------------------2400
L06_04
No exemplo da listagem 3.100 o parâmetro valor pode ser usado, internamente na procedure, como
se fosse uma variável local, tanto para atribuição como para leitura.
O conteúdo da variável VINSS, no entanto foi perdido (observe que ela possui um valor inicial) porque
a passagem do parâmetro não é feita por referência. Na verdade é criada uma variável local cujo valor
(após o término do subprograma) é copiado para a variável externa (no caso, VINSS).
Outra característica do modo Out é que, ao iniciar o subprograma, o Oracle associa Null ao parâmetro
Out.
Desta forma, se associarmos, dentro do subprograma, um valor ao parâmetro, ao encerrarmos a
rotina o valor atribuído será retornado ao bloco chamador; porém, se esta ação não for feita, será
retornado Null.
CAPÍTULO 6: SUBPROGRAMAS - 138
PL/SQL9I – BÁSICO E AVANÇADO
◊
IN OUT – Indica um parâmetro que será usado simultaneamente como entrada e como saída.
Dentro do subprograma, poderemos ler seu valor e atribuir um novo valor a ele.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DECLARE
VINSS
NUMBER;
SALARIO
NUMBER := 3000;
PROCEDURE INSS(SAL IN NUMBER, VALOR IN OUT NUMBER) IS
BEGIN
VALOR := SAL * 0.08;
IF VALOR >= 1000 THEN
VALOR := 1000;
END IF;
END INSS;
BEGIN
INSS(3000, VINSS);
INSS(SALARIO, VINSS);
END;
/
L06_05
Procedimento PL/SQL concluído com sucesso.
No exemplo modificamos o modo do parâmetro para IN OUT, desta forma, para passarmos valor do
bloco principal para o subprograma, obrigatoriamente, devemos passar o parâmetro através de uma
variável (Vinss), porque o valor, apesar de ser lido, também receberá uma atribuição. E, para que o
resultado seja transferido para o bloco principal, há necessidade de se informar uma variável.
CAPÍTULO 6: SUBPROGRAMAS - 139
PL/SQL9I – BÁSICO E AVANÇADO
PASSAGEM DE PARÂMETRO POR REFERÊNCIA
Até a versão 8 a passagem de parâmetros IN OUT e OUT era feita com cópia, isto é, haviam variáveis
locais nas subrotinas que recebiam o valor das variáveis do programa principal e ao término da
subrotina este valor era copiado de volta.
Os parâmetros passados como entrada (IN) são passados por referência. Os parâmetros IN OUT
suportam a semântica copy-in e copy-out. Os parâmetros OUT suportam a semântica copy-out.
A partir da versão 8i uma nova sintaxe, usando-se o modo NOCOPY, permite que os parâmetros OUT
e IN OUT sejam passados por referência.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DECLARE
VINSS
NUMBER;
SALARIO
NUMBER := 3000;
PROCEDURE INSS(SAL
IN NUMBER,
VALOR IN OUT NOCOPY NUMBER) IS
BEGIN
VALOR := SAL * 0.08;
IF VALOR >= 1000 THEN
VALOR := 1000;
END IF;
END INSS;
BEGIN
INSS(3000, VINSS);
INSS(SALARIO, VINSS);
:MSG := VINSS;
END;
/
Procedimento PL/SQL concluído com sucesso.
MSG
----------------------------------------------------240
L06_06
Isto pode trazer ganhos de performance para aplicações que passem grandes estruturas de dados,
como parâmetros IN OUT e OUT.
CAPÍTULO 6: SUBPROGRAMAS - 140
PL/SQL9I – BÁSICO E AVANÇADO
VALOR DEFAULT
Quando definimos um parâmetro com o modo IN, podemos atribuir um valor default para o caso do
parâmetro não ser informado.
SQL> DECLARE
2
VPREC
NUMBER := 0.07;
3
PROCEDURE CALC (VALOR IN NUMBER := 0.10) IS
4
BEGIN
5
:MSG := :MSG || TO_CHAR(VALOR, '099D00')|| '-';
6
IF VALOR <= 1 THEN
7
UPDATE FUNC
8
SET VL_SAL = VL_SAL * VALOR + VL_SAL;
9
ELSE
10
UPDATE FUNC
11
SET VL_SAL = (VL_SAL * VALOR) / 100 + VL_SAL;
12
END IF;
13
END CALC;
14 BEGIN
15
:MSG := '';
16
CALC(30);
17
CALC(VPREC);
18
CALC;
19 END;
20 /
Procedimento PL/SQL concluído com sucesso.
MSG
-------------------------------------------------------------030,00- 000,07- 000,10-
L06_07
Acionamos a procedure INSS de três formas diferentes: passando um valor diretamente, passando
uma variável e não passando parâmetro.
No terceiro caso, o valor recebido pelo subprograma será exatamente o valor definido como default
(0.10).
CAPÍTULO 6: SUBPROGRAMAS - 141
PL/SQL9I – BÁSICO E AVANÇADO
NOTAÇÃO POSICIONAL E NOMEADA PARA PASSAGEM DOS PARÂMETROS
Quando acionamos uma procedure ou uma função que recebe parâmetros, podemos nos valer de
duas notações diferentes para a passagem dos parâmetros:
◊
Posicional – Indica que a passagem se dará exatamente na ordem da declaração dos
parâmetros, não podendo haver falhas, exceto para os parâmetros finais e se forem de entrada e
tiverem um valor default definido.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
DECLARE
VPREC
NUMBER := 0.07;
PROCEDURE CALC (TIPO IN VARCHAR2 := 'P',
VALOR IN NUMBER := 0.10) IS
BEGIN
IF UPPER(TIPO) = 'P' THEN
UPDATE FUNC
SET VL_SAL = VL_SAL * VALOR + VL_SAL;
ELSE
UPDATE FUNC
SET VL_SAL = (VL_SAL * VALOR) / 100 + VL_SAL;
END IF;
END CALC;
BEGIN
CALC;
CALC('M', 30);
CALC('M');
--CALC(,30);
--CALC(30);
END;
/
Procedimento PL/SQL concluído com sucesso.
L06_08
No exemplo, acionamos a rotina CALC de 5 formas diferentes:
◊
na primeira, sem parâmetros, pois os dois parâmetros são de entrada e possuem valor
default;
◊
na segunda, informamos apenas o primeiro parâmetro, pois o segundo é de entrada e possui
valor default;
◊
na terceira, informamos os dois parâmetros;
◊
a quarta forma é inválida em PL/SQL;
◊
a quinta forma é inválida em PL/SQL.
CAPÍTULO 6: SUBPROGRAMAS - 142
PL/SQL9I – BÁSICO E AVANÇADO
◊
Nomeada – Indica que a passagem se dará em uma ordem aleatória de acordo com o nome
do parâmetro que será informado na passagem.
SQL>
2
3
4
5
6
7
8
9
10
11
12
DECLARE
VINSS
NUMBER := 10;
PROCEDURE INSS(SAL
IN NUMBER := 2000,
VALOR OUT NUMBER) IS
BEGIN
VALOR := SAL * 0.08;
END INSS;
BEGIN
INSS(3000, VINSS);
INSS(VALOR => VINSS);
END;
/
L06_09
Procedimento PL/SQL concluído com sucesso.
No exemplo acima, acionamos a rotina Inss de duas formas:
◊
na primeira, informamos os dois parâmetros usando a notação posicional;
◊
na segunda, informamos apenas o segundo parâmetro e usamos a notação nomeada, isto é,
o nome do parâmetro é seguido dos símbolos igual e maior (=>) e do valor a ser transferido.
Observe que o segundo parâmetro não pode deixar de ser informado, pois é um parâmetro de
saída.
Esta sintaxe permite, adicionalmente, que informemos os parâmetros na ordem que desejarmos.
CAPÍTULO 6: SUBPROGRAMAS - 143
PL/SQL9I – BÁSICO E AVANÇADO
DECLARAÇÕES FORWARD
A PL/SQL exige que se declare um subprograma antes de podermos utilizá-lo. Se desejássemos criar
dois procedimentos mutuamente recursivos, esta regra impediria esta forma de utilização.
Como solução de contorno para este tipo de situação, a PL/SQL fornece uma forma de declaração
parcial chamada de declaração Forward, que interrompe a declaração da procedure ou função após a
definição dos parâmetros (para as procedures) ou do tipo de retorno (para as funções), permitindo que
o compilador realize a validação da chamada do subprograma mesmo sem ter informações sobre o
corpo (ou lógica) deste.
Posteriormente, repete-se a definição do subprograma, agora contendo a chamada e a lógica.
SQL> DECLARE
2
PROCEDURE B (P1 IN NUMBER);
3
PROCEDURE A (P1 IN NUMBER) IS
4
BEGIN
5
IF P1 > 10 THEN
6
B(P1);
7
ELSE
8
:MSG := :MSG ||'A-'||P1||'; ';
9
END IF;
10
END;
11
PROCEDURE B(P1 IN NUMBER) IS
12
BEGIN
13
IF P1 <= 10 THEN
14
A(P1);
15
ELSE
16
:MSG := :MSG||'B-'||P1||'; ';
17
END IF;
18
END;
19 BEGIN
20
:MSG := '';
21
A('&VALOR');
22
B('&VALOR');
23 END;
24 /
Entre o valor para valor: 5
antigo 21:
A('&VALOR');
novo 21:
A('5');
Entre o valor para valor: 10
antigo 22:
B('&VALOR');
novo 22:
B('10');
Procedimento PL/SQL concluído com sucesso.
MSG
----------------------------------------------A-5; A-10;
L06_10
CAPÍTULO 6: SUBPROGRAMAS - 144
PL/SQL9I – BÁSICO E AVANÇADO
Observe que a procedure B foi declarada duas vezes. Na primeira declaração, foram especificados
apenas o nome da procedure e os parâmetros formais. Na segunda declaração, foi repetida a
especificação do nome e dos parâmetros e completada com a definição do corpo da procedure.
Este procedimento foi necessário uma vez que a procedure B chamava a procedure A e a procedure A
chamava a procedure B. Não poderíamos, portanto, realizar este tipo de utilização sem o recurso da
declaração Forward.
A declaração Forward pode ser útil para outras situações, tais como:
◊
Definição dos subprogramas em ordem alfabética.
◊
Grupamento dos subprogramas em um package.
◊
Definição de programas mutuamente recursivos (como no exemplo).
CAPÍTULO 6: SUBPROGRAMAS - 145
PL/SQL9I – BÁSICO E AVANÇADO
OVERLOADING
A PL/SQL permite a definição de dois subprogramas com o mesmo nome, desde que seus parâmetros
formais sejam diferentes em número, ordem ou tipo familiar (por exemplo, Integer e Real são tipos
diferentes, porém pertencem ao mesmo grupo familiar). Essa característica é chamada de
Overloading.
Como restrição, temos que esta técnica só se aplica a subprogramas locais ou subprogramas
definidos em pacotes (serão vistos em outro tópico).
Alem disso, não podemos “overload” dois subprogramas se seus parâmetros diferirem apenas em
relação aos nomes ou aos modos dos parâmetros.
E, finalmente, não podemos “overload” duas funções em que a única diferença entre elas seja o tipo
de retorno (mesmo que estejam em grupos familiares diferentes).
SQL> DECLARE
2
VINSS
NUMBER;
3
PROCEDURE CALC (TIPO IN VARCHAR2 := 'P',
4
VALOR IN NUMBER := 0.10) IS
5
BEGIN
6
:MSG := :MSG || 'PRIMEIRA CALC; ';
7
IF UPPER(TIPO) = 'P' THEN
8
UPDATE FUNC
9
SET VL_SAL = VL_SAL * VALOR + VL_SAL;
10
ELSE
11
UPDATE FUNC
12
SET VL_SAL = (VL_SAL * VALOR) / 100 + VL_SAL;
13
END IF;
14
END CALC;
15
PROCEDURE CALC (SAL IN NUMBER := 2000, VALOR OUT NUMBER) IS
16
BEGIN
17
VALOR := SAL * 0.08;
18
:MSG := :MSG || 'SEGUNDA CALC; ';
19
END CALC;
20 BEGIN
21
:MSG := '';
22
CALC;
23
CALC('M', 30);
24
CALC('M');
25
CALC(3000, VINSS);
26
--CALC(VALOR => 30);
27
--CALC(VALOR => VINSS);
28 END;
29 /
Procedimento PL/SQL concluído com sucesso.
MSG
------------------------------------------------------------------PRIMEIRA CALC; PRIMEIRA CALC; PRIMEIRA CALC; SEGUNDA CALC;
L06_11
CAPÍTULO 6: SUBPROGRAMAS - 146
PL/SQL9I – BÁSICO E AVANÇADO
No exemplo, criamos duas rotinas com o nome de CALC subordinadas ao mesmo programa principal.
Os parâmetros das duas são diferentes (na primeira, varchar e number e, na segunda, number e
number), o que tornou o “overloading” possível.
Observe, porém, que na chamada dos programas não podemos informar apenas o segundo
parâmetro, pois é o primeiro que define qual das duas rotinas deve ser acionada. Teste retirar os
comentários das duas últimas chamadas para verificar o erro adquirido.
CAPÍTULO 6: SUBPROGRAMAS - 147
PL/SQL9I – BÁSICO E AVANÇADO
STORED SUBPROGRAM
O objetivo de armazenarmos uma procedure ou function no banco de dados é permitir que a rotina
seja compartilhada por diversas aplicações.
SINTAXE
Na Sintaxe, observamos que a forma de criação de uma rotina no banco de dados é semelhante à
sintaxe para criação de uma rotina dentro de um bloco principal. A diferença está na cláusula Create,
que estabelece o armazenamento da rotina no banco de dados.
Como vantagem do armazenamento no banco de dados, temos:
◊
A produtividade pode ser incrementada à medida que uma determinada rotina pode ser
compartilhada por diversas aplicações, reduzindo a redundância de codificação.
◊
A performance pode ser incrementada uma vez que as rotinas armazenadas no banco de
dados podem diminuir a necessidade de tráfego de informações pela rede. Elas são executadas
no ambiente servidor e, quando acionadas, executam os vários comandos de SQL e PL/SQL
diretamente no servidor sem necessidade de transitar essas informações pela rede.
◊
Em relação à segurança, também podemos ter vantagens uma vez que o DBA pode restringir
o acesso direto a determinadas tabelas e autorizar a utilização apenas de rotinas que façam
acesso, dentro de regras previamente estabelecidas, a essas tabelas.
CAPÍTULO 6: SUBPROGRAMAS - 148
PL/SQL9I – BÁSICO E AVANÇADO
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE OR REPLACE FUNCTION NOME(PDEPTO IN VARCHAR2)
RETURN VARCHAR2 IS
NOME
VARCHAR2(100);
BEGIN
SELECT NM_DEPTO INTO NOME
FROM DEPTO
WHERE CD_DEPTO = PDEPTO;
RETURN NOME;
EXCEPTION
WHEN NO_DATA_FOUND THEN
NOME := 'DEPARTAMENTO INEXISTENTE';
RETURN NOME;
END;
/
L06_12
Função criada.
No exemplo, vemos a criação de uma função no banco. A utilização dessa função pode ser feita em
outras aplicações que utilizem PL/SQL ou diretamente em um comando de SQL.
CAPÍTULO 6: SUBPROGRAMAS - 149
PL/SQL9I – BÁSICO E AVANÇADO
USO DE FUNÇÕES EM COMANDOS DE SQL
SQL> COL DEPARTAMENTO FOR A50
SQL> SELECT CD_MAT, NOME(CD_DEPTO) DEPARTAMENTO
2
FROM FUNC
3
WHERE ROWNUM < 6;
CD_MAT
---------10
20
30
50
60
DEPARTAMENTO
-------------------------------------DIRETORIA DA EMPRESA
ASSESSORIA
CENTRO DE INFORMACAO
DIRETORIA DE SUPORTE/PRODUCAO
GERENCIA DE SISTEMAS COMERCIAIS
L06_13
Funções podem ser utilizadas em comandos de SQL (Insert, Update, Select e Delete), como vemos
no exemplo. Procedures não retornam valores, desta forma devem ser utilizadas em outros blocos de
PL/SQL.
CAPÍTULO 6: SUBPROGRAMAS - 150
PL/SQL9I – BÁSICO E AVANÇADO
VERIFICANDO ERROS DE COMPILAÇÃO
No exemplo a seguir, o procedimento criado contém erros de compilação. Para verificarmos quais os
erros encontrados, devemos usar o comando Show (de SQL*Plus).
A sintaxe Or Replace permite que façamos modificações na rotina e executemos o processo de
criação de novo.
O Oracle fará o armazenamento no banco de dados se a rotina não existir (mesmo com erros de
compilação), ou a modificação se a rotina já existir anteriormente.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CREATE OR REPLACE PROCEDURE TESTE
(MATRICULA IN NUMBER,
PERCENTUAL IN NUMBER := 0.1) IS
VPERC
NUMBER;
BEGIN
IF PERCENTUAL > 1 THEN
VPERC := PERCENT / 100;
ELSIF PERCENTUAL > 0 THEN
VPERC := PERCENTUAL;
END IF;
IF VPERC IS NOT NULL THEN
UPDATE FUNC
SET VL_SAL = VL_SAL * VPERC;
COMMIT;
END IF;
END;
/
Aviso: Procedimento criado com erros de compilação.
SQL> SHOW ERRORS
Erros para PROCEDURE TESTE:
LINE/COL
-------7/6
7/15
ERROR
--------------------------------------------------------PL/SQL: Statement ignored
PLS-00201: o identificador 'PERCENT' deve ser declarado
L06_14
CAPÍTULO 6: SUBPROGRAMAS - 151
PL/SQL9I – BÁSICO E AVANÇADO
PRIVILÉGIOS EM ROTINAS ARMAZENADAS
Normalmente, uma rotina armazenada no banco de dados (e métodos de objetos) executam com o
privilégio de quem definiu a rotina (ou método) e não com o privilégio de quem aciona a rotina.
SQL> CREATE USER DAVI IDENTIFIED BY DAVI
2 DEFAULT TABLESPACE USERS;
Usuário criado.
SQL> CREATE USER DANIEL IDENTIFIED BY DANIEL
2 DEFAULT TABLESPACE USERS;
Usuário criado.
SQL> GRANT CONNECT TO DAVI, DANIEL;
Operação de Grant bem-sucedida.
SQL> CREATE OR REPLACE
2 FUNCTION SALARIO(MATRICULA IN NUMBER) RETURN NUMBER IS
3
SAL
NUMBER;
4 BEGIN
5
SELECT VL_SAL INTO SAL FROM FUNC
6
WHERE CD_MAT = MATRICULA;
7
RETURN SAL;
8 EXCEPTION
9 WHEN NO_DATA_FOUND THEN
10
RETURN -1;
11 END;
12 /
Função criada.
SQL> GRANT EXECUTE ON SALARIO TO DAVI, DANIEL;
Operação de Grant bem-sucedida.
SQL> CONNECT DAVI/DAVI
Conectado.
SQL> SELECT aluno.SALARIO(100) FROM DUAL;
ALUNO.SALARIO(100)
-----------------16781,79
SQL> CONNECT DANIEL/DANIEL
Conectado.
SQL> SELECT aluno.SALARIO(100) FROM DUAL;
ALUNO.SALARIO(100)
-----------------16781,79
L06_15
CAPÍTULO 6: SUBPROGRAMAS - 152
PL/SQL9I – BÁSICO E AVANÇADO
No exemplo anterior criamos dois usuários (Davi e Daniel). Em seguida, criamos a rotina Salário (no
usuário Aluno – Owner da rotina).
Os usuários Davi e Daniel receberam autorização de execução sobre a função Salário, porém não
receberam autorização de uso da tabela Func.
Observe, no entanto, que ambos puderam executar a rotina e efetuar a leitura da tabela Func, cujo
dono é Aluno. Observe ainda que o Oracle descobriu que a tabela Func, a ser usada na função,
pertencia ao usuário Aluno.
OBS: mesmo que houvesse uma tabela Func no schema de Davi ou Daniel, a tabela Func lida seria a
de Aluno.
CAPÍTULO 6: SUBPROGRAMAS - 153
PL/SQL9I – BÁSICO E AVANÇADO
USANDO AUTHID CURRENT_USER
A cláusula AuthId Current_User vem a mudar esta situação. Quando a função Salário for criada com
esta cláusula, o Oracle passará a considerar tanto o schema quanto os privilégios do usuário que
acionar a rotina (e não daquele dono da rotina) para executá-la.
SQL> CREATE OR REPLACE
2 FUNCTION SALARIO(MATRICULA IN NUMBER) RETURN NUMBER
3
AUTHID CURRENT_USER IS
4
SAL
NUMBER;
5 BEGIN
6
SELECT VL_SAL INTO SAL FROM FUNC
7
WHERE CD_MAT = MATRICULA;
8
RETURN SAL;
9 EXCEPTION
10 WHEN NO_DATA_FOUND THEN
11
RETURN -1;
12 END;
13 /
Função criada.
SQL> GRANT EXECUTE ON SALARIO TO DAVI, DANIEL;
Operação de Grant bem-sucedida.
SQL> CONNECT DANIEL/DANIEL
Conectado.
SQL> SELECT ALUNO.SALARIO(200) FROM DUAL;
SELECT ALUNO.SALARIO(200) FROM DUAL
*
ERRO na linha 1:
ORA-00942: a tabela ou view não existe
ORA-06512: em "ALUNO.SALARIO", line 5
L06_16
No exemplo acima, suponha que tenhamos retornado ao usuário Aluno e feito a criação da rotina
Salário utilizando a cláusula AuthId Current_User.
Quando o usuário Daniel tentou executar a função, recebeu um erro (não em relação à função) em
relação à tabela Func a que a função faz acesso.
A mensagem de erro indica que a tabela (ou view) não foi encontrada no schema do usuário Daniel,
onde a pesquisa foi feita.
CAPÍTULO 6: SUBPROGRAMAS - 154
PL/SQL9I – BÁSICO E AVANÇADO
REFERÊNCIAS EXTERNAS
Quando especificamos a cláusula AuthId Current_User, os privilégios do executor da rotina são
verificados a tempo de execução e as referências externas são resolvidas no schema deste executor,
presentes em:
◊
Comandos que manipulam os dados: Select, Insert, Update e Delete.
◊
Comando para controle de transações: Lock Table.
◊
Comandos para controle de Cursores: Open e Open-For.
◊
Comandos de SQL dinâmicos: Execute Immediate e Open-For-Using.
◊
Comandos de SQL “parseados” pela rotina Parse do pacote Dbms_Sql.
Para os outros comandos, os privilégios do dono da rotina são verificados a tempo de compilação e as
referências externas são resolvidas no schema do dono da rotina.
Por exemplo, suponhamos que nossa rotina Salário tivesse a atribuição “Sal := Scott.Soma(Sal);” (sem
as aspas).
Essa referência externa é resolvida no schema do usuário Aluno (dono da rotina Salário), ou seja,
quem deve ter direito de execução da rotina Soma é o usuário Aluno.
CAPÍTULO 6: SUBPROGRAMAS - 155
PL/SQL9I – BÁSICO E AVANÇADO
CUIDADOS ADICIONAIS
Neste tópico, listaremos alguns cuidados que deveremos observar na criação e uso da cláusula AuthId
com a opção Current_User.
◊
Apesar de a rotina ser executada com os privilégios do executor, o criador da rotina precisa
ter os privilégios referentes aos objetos acionados pela rotina a tempo de compilação, pois esta
verificação será feita.
Suponha, portanto, que os usuários Davi e Daniel possuam uma tabela Func em seus schemas e
que o usuário Aluno não possua essa tabela.
A criação da rotina Salario não seria possível, apesar de a tempo de execução cada usuário (Davi
e Daniel) fazer acesso à sua própria tabela.
Uma solução de contorno para este problema seria a criação de uma tabela Func no schema do
usuário Aluno, contendo pelo menos as colunas utilizadas na rotina.
◊
Outra questão a ser analisada seria a situação inversa a esta anterior.
Suponhamos que desejássemos que os usuários que executassem esta rotina fizessem acesso
exatamente à tabela Func do usuário Aluno.
Para tal deveríamos usar, na cláusula From, o texto Aluno.Func para ter esta garantia ou, ainda,
criar um sinônimo público para a tabela (esta segunda opção só não resolveria a situação se o
usuário executor tivesse uma tabela própria com o nome de Func).
SQL> CONNECT ALUNO/ALUNO
Conectado.
SQL> GRANT SELECT ON FUNC TO DAVI;
Operação de Grant bem-sucedida.
SQL> CONNECT DAVI/DAVI
Conectado.
SQL> SELECT ALUNO.SALARIO(200) FROM DUAL;
SELECT ALUNO.SALARIO(200) FROM DUAL
*
ERRO na linha 1:
ORA-00942: a tabela ou view não existe
ORA-06512: em "ALUNO.SALARIO", line 5
SQL> CREATE SYNONYM FUNC FOR ALUNO.FUNC;
Sinônimo criado.
SQL> SELECT ALUNO.SALARIO(200) FROM DUAL;
ALUNO.SALARIO(200)
-----------------13694,02
L06_17
CAPÍTULO 6: SUBPROGRAMAS - 156
PL/SQL9I – BÁSICO E AVANÇADO
Observe, no exemplo, que o usuário Davi recebeu o privilégio de acesso à tabela Func do usuário
Aluno, mas esta ação não foi suficiente para que a execução da rotina Salário fosse bem sucedida.
Houve necessidade da criação do sinônimo que indicasse ao Oracle (a tempo de execução) a que
tabela Func deveria ser feito o acesso.
CAPÍTULO 6: SUBPROGRAMAS - 157
PL/SQL9I – BÁSICO E AVANÇADO
USO DE COLEÇÕES E REGISTROS EM PARÂMETROS
Tanto as coleções como os registros podem ser utilizados em parâmetros de procedures e functions e,
ainda, na cláusula return para functions.
Quando o programa é declarado em um bloco de PL/SQL, o tipo correspondente ao parâmetro pode
ser definido dentro do próprio bloco. Quando a rotina é armazenada no banco de dados, o tipo deve
estar definido no banco de dados ou dentro de um pacote.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
CREATE OR REPLACE PROCEDURE TESTE IS
TYPE
REG_PARM IS RECORD (MAT NUMBER,
PERC NUMBER := 0.1);
TYPE
ARR_PARM IS TABLE OF REG_PARM
INDEX BY BINARY_INTEGER;
VIPS
ARR_PARM;
PROCEDURE AUMENTO(PARAM IN ARR_PARM) IS
VPERC
NUMBER;
BEGIN
FOR I IN 1..PARAM.COUNT LOOP
IF PARAM(I).PERC > 1 THEN
VPERC := PARAM(I).PERC / 100;
ELSIF PARAM(I).PERC > 0 THEN
VPERC := PARAM(I).PERC;
END IF;
IF VPERC IS NOT NULL THEN
UPDATE FUNC
SET VL_SAL = VL_SAL * VPERC
WHERE CD_MAT = PARAM(I).MAT;
END IF;
END LOOP;
END AUMENTO;
BEGIN
VIPS(1).MAT :=100;
VIPS(1).PERC := 5;
VIPS(2).MAT := 10;
VIPS(2).PERC := 6;
AUMENTO(VIPS);
END;
/
L06_18
Procedimento criado.
No exemplo acima, criamos uma rotina no banco de dados chamada Teste. Essa rotina declara
internamente uma procedure (Aumento) que recebe como parâmetro uma Index-By derivada de um
Record.
Os dois tipos são declarados dentro da procedure Teste, antes da declaração da procedure Aumento.
CAPÍTULO 6: SUBPROGRAMAS - 158
PL/SQL9I – BÁSICO E AVANÇADO
De um modo geral, devemos declarar os tipos, as variáveis e os cursores antes das procedures e
functions em qualquer bloco de PL/SQL. As rotinas devem ser as últimas definições presentes na
parte declarativa.
Da mesma forma que nesse exemplo, poderíamos definir um tipo que fosse usado na cláusula Return
de uma função.
Observe que a passagem de parâmetro ocorre da mesma forma que para um escalar. A variável
passada como parâmetro possui o tipo da Index-By Table.
SQL> CREATE OR REPLACE PROCEDURE VERIFICA IS
2
TYPE TAB_NOME
IS TABLE OF VARCHAR2(100)
3
INDEX BY BINARY_INTEGER;
4
TYPE TAB_DEPTO
IS TABLE OF VARCHAR2(3)
5
INDEX BY BINARY_INTEGER;
6
VNOME
TAB_NOME;
7
VDEPTO
TAB_DEPTO;
8
TEXTO
VARCHAR2(100);
9
FUNCTION NOME(PDEPTO IN TAB_DEPTO)
10
RETURN TAB_NOME IS
11
NM_AUX
VARCHAR2(100);
12
PNOME
TAB_NOME;
13
BEGIN
14
FOR I IN 1..PDEPTO.COUNT LOOP
15
BEGIN
16
SELECT NM_DEPTO INTO NM_AUX FROM DEPTO
17
WHERE CD_DEPTO = PDEPTO(I);
18
PNOME(I) := NM_AUX;
19
EXCEPTION
20
WHEN NO_DATA_FOUND THEN
21
PNOME(I) := 'DEPARTAMENTO INEXISTENTE';
22
END;
23
END LOOP;
24
RETURN PNOME;
25
END NOME;
26 BEGIN
27
VDEPTO(1) := 'A00';
28
VDEPTO(2) := 'A01';
29
VNOME
:= NOME(VDEPTO);
30
TEXTO
:= NOME(VDEPTO)(2);
31 END VERIFICA;
32 /
Procedimento criado.
L06_19
No exemplo observamos a criação de uma procedure Verifica que declara internamente a função
Nome.
Essa função recebe como parâmetro uma Index-By Table baseada em um escalar (varchar2) e
retorna outra Index-By Table também baseada em um escalar (varchar2).
CAPÍTULO 6: SUBPROGRAMAS - 159
PL/SQL9I – BÁSICO E AVANÇADO
A chamada da função, nesse caso, pode ser feita de duas formas (linhas 29 e 30):
◊
Na primeira, usamos uma área receptora de formato idêntico ao retorno da função, isto é,
uma Index-By Table baseada em um Varchar2.
◊
No segundo caso, utilizamos como área receptora um escalar simples (Texto) que receberá a
segunda posição da Index-By Table retornada e não a tabela inteira.
A segunda sintaxe, apesar de menos elegante e à primeira vista confusa, pode ser usada se
desejarmos obter apenas uma determinada informação do array retornado.
CAPÍTULO 6: SUBPROGRAMAS - 160
PL/SQL9I – BÁSICO E AVANÇADO
USANDO FUNÇÕES PARA A CRIAÇÃO DE ÍNDICES
Os índices podem ser baseados em funções (e expressões) e não apenas baseados em colunas da
tabela. Um índice baseado em função pré-calcula o valor da função (ou expressão) para todas as
linhas da tabela e o armazena no índice.
Para ambientes de Data Warehouse, a utilização desta característica pode trazer significativos ganhos
de performance.
SQL> CREATE INDEX IND_UPPER ON FUNC (UPPER(NM_SOBRENOME));
L06_20
Índice criado.
No exemplo construímos o índice baseado na função predefinida UPPER.
SQL> CREATE INDEX IND_SOMA ON FUNC (NR_CARGO + NR_GIT);
L06_21
Índice criado.
Nesse caso, utilizamos a soma das colunas cargo e grau de instrução na criação do índice.
SQL> CREATE OR REPLACE
2 FUNCTION SOMA(VALOR1 IN NUMBER, VALOR2 IN NUMBER)
3
RETURN NUMBER DETERMINISTIC IS
4 BEGIN
5
RETURN (NVL(VALOR1,-200) + NVL(VALOR2,-500));
6 END;
7 /
Função criada.
SQL> CREATE INDEX IND_SOMA_FUNC
2
ON FUNC(SOMA(NR_CARGO, NR_GIT))
3 /
Índice criado.
L06_22
Finalmente, criamos um índice baseado em uma função definida pelo usuário. Observe que a função
possui a cláusula Deterministic. Sua utilização é necessária, pois esta cláusula indica que se
repetirmos os mesmos parâmetros para a função ela produzirá o mesmo resultado.
Se o índice fará o cálculo da função para todas as linhas da tabela, é indispensável que o valor gerado
pela função se mantenha constante independentemente do ambiente ou do usuário que estiver
executando.
CAPÍTULO 6: SUBPROGRAMAS - 161
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 6:
1. Qual a diferença entre Procedures e Functions?
2. Crie uma função armazenada no banco de dados que retorne o nome do departamento, cujo código
deve ser passado como parâmetro. Caso o departamento não exista, a função deve retornar
‘Departamento Inexistente’.
3. Crie uma função ou procedure na base de dados que determine o departamento que possui menos
funcionários.
4. Crie uma função ou procedure na base de dados que receba como parâmetro um código de
departamento e determine o ramal do gerente do departamento. Caso o departamento não tenha
gerente, deverá ser escolhido o ramal do funcionário com maior cargo deste departamento (se existir
mais de um, obtenha o ramal do funcionário mais velho e com maior cargo).
5. Crie uma função ou procedure na base de dados que receba como parâmetro um grau de instrução
e um código de departamento e determine o cargo e o salário correspondentes a estes parâmetros.
Se existir mais de um, escolha o do funcionário mais novo. Se não existir nenhum, retorne Null.
6. Crie um programa na base de dados que realize a admissão dos funcionários. Esse programa
deverá receber como parâmetro nome completo (nome e sobrenome), data de nascimento, sexo e
grau de instrução.
Para a construção do programa, as seguintes regras devem ser estabelecidas:
◊
◊
◊
◊
◊
◊
Usar a rotina do exercício 3 para determinar o departamento.
Usar a rotina do exercício 4 para determinar o ramal.
Usar a rotina do exercício 5 para determinar o salário e o cargo.
A data de admissão corresponde ao dia do cadastramento.
A matrícula deverá ser gerada a partir da última cadastrada.
Os parâmetros são opcionais (exceto nome) e devem ter valores defaults.
O programa deve ser testado com passagem nomeada dos parâmetros.
7. Crie uma função que receba como parâmetro uma Index-By Table e retorne os dados da Index-By
Table em ordem crescente.
8. Faça uma rotina que receba como parâmetro um número de cargo e determine o nome do
funcionário mais velho com aquele cargo. Se não houver ninguém, o programa deve abortar com o
erro Oracle correspondente.
Faça um programa que acione o programa anterior, e de acordo com seu resultado mostre o nome do
funcionário ou a mensagem “Nenhum funcionário para o cargo Informado”.
CAPÍTULO 6: SUBPROGRAMAS - 162
PL/SQL9I – BÁSICO E AVANÇADO
9. Crie um programa para gravar informações na tabela abaixo.
SQL> CREATE TABLE RESULTADO
2 (COD
NUMBER,
3
QTD
NUMBER);
As seguintes características devem ser atendidas:
◊
Criar uma função ou procedure interna ao programa que receba como parâmetro o código do
departamento e retorne a quantidade de funcionários que trabalham neste departamento.
◊
Criar uma função ou procedure que receba como parâmetro um código de departamento e
transforme-o em numérico (use Translate).
◊
A coluna Código deverá receber o novo código do departamento.
◊
A coluna qtd deverá ser preenchida com a quantidade de funcionários do departamento.
10. Crie um função no banco de dados que calcule o n-ésimo elemento de uma série de Fibonacci.
Utilize recursividade.
11. Crie um programa que contenha as duas funções a seguir:
◊
A primeira função recebe como parâmetro um código de cargo e calcula o número de
funcionários com aquele cargo. Caso a quantidade de funcionários seja ímpar, deve ser acionada
a outra função.
◊
A segunda função recebe como parâmetro um cargo. Se o número for ímpar, deve retornar a
quantidade de funcionários do sexo feminino com o cargo informado. Se o número for par, deve
retornar a quantidade de funcionários do departamento D11 com aquele cargo. Neste caso
também deve ser acionada a primeira função.
O programa principal deve acionar a primeira função ou a segunda de acordo com o parâmetro opção:
◊
◊
◊
opção = 0
opção = 1
opção = outro valor
acionar somente a rotina A
acionar somente a rotina B
acionar as duas rotinas.
O programa principal recebe dois parâmetros: opção (é opcional) e cargo (obrigatório).
O programa principal deve ser uma procedure armazenada na base de dados.
12. Faça um programa que receba uma data no formato DDMMYYYY e verifique se é uma data válida.
Havendo erro, aborte com a mensagem adequada.
Faça um programa que acione o anterior e retorne a mensagem associada ao erro sem abortar.
CAPÍTULO 6: SUBPROGRAMAS - 163
PL/SQL9I – BÁSICO E AVANÇADO
CAPÍTULO 7: PACKAGES
CONCEITO
Um package é uma área para armazenamento de subprogramas, tipos, constantes, cursores e
variáveis de PL/SQL. É definido em duas partes: uma parte de especificação e uma parte de corpo.
PACKAGE SPECIFICATION
A especificação é a interface com as aplicações. É nela que declaramos os tipos, variáveis,
constantes, exceções, cursores e subprogramas que desejamos que sejam visíveis e compartilháveis
pelas aplicações. Tudo que for definido na parte de especificação pode ser compartilhado.
A definição dos subprogramas e cursores não é completa; fazemos apenas uma definição do tipo
Forward uma vez que as aplicações só necessitam saber quais os parâmetros a serem passados para
a rotina (ou cursor) e quais os retornos fornecidos. Não há necessidade de se saber como a rotina
efetua a operação.
SINTAXE
CREATE [OR REPLACE] PACKAGE <nome do pacote> IS
<declarações>
END [<nome do pacote>];
Na Sintaxe está definida a parte de especificação de um pacote. Apesar de a sintaxe do pacote
lembrar a sintaxe de definição de rotinas, os pacotes diferem das rotinas nos seguintes aspectos: não
podem ser chamados, parametrizados ou definidos dentro de outros pacotes.
O objetivo de empacotar é primeiramente organizacional. O pacote oferece a possibilidade de
juntarmos itens e subprogramas que realizam ações associadas.
SQL> CREATE OR REPLACE PACKAGE PFUNC IS
2
TYPE REGFUNC IS RECORD (NOME
VARCHAR2(50),
3
NASC
DATE,
4
SEXO
VARCHAR2(1),
5
CARGO
NUMBER);
6
CURSOR CFUNC(MAT IN NUMBER) RETURN REGFUNC;
7
FUNCTION INCLUI(NOME IN VARCHAR2,
8
NASC IN DATE,
9
SEXO IN VARCHAR2,
10
CARGO IN NUMBER)
11
RETURN BOOLEAN;
12
FUNCTION EXCLUI(MAT IN NUMBER)
13
RETURN BOOLEAN;
14 END PFUNC;
15 /
Pacote criado.
L07_01
CAPÍTULO 7: PACKAGES - 164
PL/SQL9I – BÁSICO E AVANÇADO
No exemplo criamos um pacote contendo as rotinas necessárias para a manutenção da tabela Func.
Podemos incluir novos funcionários e removê-los.
O cursor definido permite a consulta das informações.
Observe que as informações declaradas no pacote determinam quais os nomes das rotinas e itens, os
parâmetros recebidos e o retorno esperado. Não definimos detalhes de implementação. Isto será feito
no corpo.
CAPÍTULO 7: PACKAGES - 165
PL/SQL9I – BÁSICO E AVANÇADO
PACKAGE BODY
Na parte de corpo do pacote, definimos por completo todas as rotinas. Não só aquelas declaradas na
parte de especificação como também todas aquelas rotinas que serão chamadas pelas outras, mas
que não desejamos disponibilizar diretamente para as aplicações.
Definimos também, agora, os cursores por completo, além de todas as variáveis que venham a ser
usadas pelas rotinas e que não desejamos disponibilizar para as demais aplicações.
O corpo implementa detalhes e declarações privadas que são invisíveis pelas aplicações.
SINTAXE
CREATE [OR REPLACE] PACKAGE BODY <nome do pacote> IS
<corpo dos subprogramas>
[BEGIN
<comandos de inicialização>]
END [<nome do pacote>];
Na Sintaxe acima, observamos que a definição da especificação e do corpo do pacote são similares.
A diferença é que na parte de especificação as definições são do tipo Forward, enquanto na parte de
corpo as definições são completas.
No corpo, aparece ainda a possibilidade de definirmos uma área de lógica (<inicialização>) que será
executada na primeira vez em que o pacote for utilizado em uma sessão do usuário. Nesta área,
podemos estabelecer ações para inicialização de variáveis, cursores, etc.
SQL> CREATE OR REPLACE PACKAGE BODY PFUNC IS
2
CURSOR CFUNC(MAT IN NUMBER) RETURN REGFUNC IS
3
SELECT NM_FUNC||' '||NM_SOBRENOME, DT_NASC,
4
IN_SEXO, NR_CARGO
5
FROM FUNC
6
WHERE CD_MAT = MAT;
7
FUNCTION INCLUI(NOME IN VARCHAR2, NASC IN DATE,
8
SEXO IN VARCHAR2, CARGO IN NUMBER)
9
RETURN BOOLEAN IS
10
BEGIN
11
INSERT INTO FUNC (CD_MAT, NM_FUNC, NM_SOBRENOME, DT_NASC,
12
IN_SEXO, NR_CARGO, DT_ADM, VL_SAL, CD_DEPTO)
13
VALUES (SEQ_MAT.NEXTVAL, SUBSTR(NOME,1,12), SUBSTR(NOME, 13),
14
NASC, UPPER(SEXO), CARGO, SYSDATE, 2000, 'D11');
15
RETURN TRUE;
16
END INCLUI;
17
FUNCTION EXCLUI (MAT IN NUMBER) RETURN BOOLEAN IS
18
BEGIN
19
DELETE FROM FUNC WHERE CD_MAT = MAT;
20
RETURN TRUE;
21
END;
22 END PFUNC;
L07_02
CAPÍTULO 7: PACKAGES - 166
PL/SQL9I – BÁSICO E AVANÇADO
No exemplo anterior, vemos parte da definição do corpo do pacote Pfunc, onde completamos a
definição do cursor e das rotinas declaradas na especificação.
Uma vez que apenas a parte de especificação é utilizada para compilação dos programas que façam
uso dos pacotes, podemos alterar o corpo do pacote sem haver necessidade de recompilar os
programas que o usam.
RESTRIÇÕES
Como restrições à definição e acesso a pacotes, temos:
◊
Um pacote não pode ser chamado diretamente. Faremos referência aos objetos declarados
dentro do pacote.
◊
Um pacote não pode receber parâmetro. Somente as rotinas declaradas no pacote podem
fazê-lo.
◊
Um pacote não pode ser “aninhado”. Não podemos declarar um pacote dentro de outro
pacote.
◊
Não podemos fazer referência remota a variáveis definidas em pacotes (direta ou
indiretamente). Se utilizarmos remotamente uma rotina que faça referência a uma variável de
pacote, receberemos erro.
◊
Não podemos fazer referência a variáveis Bind dentro de pacotes. Variáveis Bind são aquelas
variáveis definidas no ambiente do aplicativo (por exemplo, :msg no SQL*Plus).
CAPÍTULO 7: PACKAGES - 167
PL/SQL9I – BÁSICO E AVANÇADO
USANDO PACKAGES
Para fazermos referência aos tipos, objetos e subprogramas definidos dentro da área de especificação
de um pacote, devemos mencionar o nome do pacote como qualificador do item, como no exemplo.
SQL> SET SERVEROUT ON
SQL> BEGIN
2
FOR RFUNC IN PFUNC.CFUNC(&MAT) LOOP
3
DBMS_OUTPUT.PUT_LINE(RFUNC.NOME);
4
END LOOP;
5 END;
6 /
Entre o valor para mat: 100
antigo
2:
FOR RFUNC IN PFUNC.CFUNC(&MAT) LOOP
novo
2:
FOR RFUNC IN PFUNC.CFUNC(100) LOOP
TEODORO SIQUEIRA
L07_03
Procedimento PL/SQL concluído com sucesso.
Neste exemplo fizemos uma referência ao cursor Cfunc definido dentro do pacote Pfunc usando o
nome do pacote como qualificador (Pfunc.Cfunc).
A Oracle organiza as rotinas utilitárias (predefinidas) em pacotes. Nos tópicos seguintes,
conheceremos alguns destes pacotes, que possuem rotinas de grande utilidade para o
desenvolvimento de aplicações no ambiente Oracle.
No exemplo acima usamos a rotina Put_Line de um pacote chamado Dbms_Output com o objetivo de
obter o valor da coluna Nome lida no comando Select (associado ao cursor Cfunc declarado no
pacote).
SQL> DESC PFUNC
FUNCTION EXCLUI RETURNS BOOLEAN
Nome do Argumento
-----------------------------MAT
FUNCTION INCLUI RETURNS BOOLEAN
Nome do Argumento
-----------------------------NOME
NASC
SEXO
CARGO
Tipo
In/Out Default?
--------------- ------ -------NUMBER
IN
Tipo
--------------VARCHAR2
DATE
VARCHAR2
NUMBER
In/Out Default?
------ -------IN
IN
IN
IN
Acima, executamos o comando Desc (SQL*Plus) para o pacote Pfunc.
CAPÍTULO 7: PACKAGES - 168
PL/SQL9I – BÁSICO E AVANÇADO
A seguir utilizamos o cursor Cfunc do pacote Pfunc e a rotina Exclui.
SQL> SET AUTOPRINT ON
SQL> DECLARE
2
RFUNC
PFUNC.CFUNC%ROWTYPE;
3
MAT
FUNC.CD_MAT%TYPE := &MAT;
4 BEGIN
5
OPEN PFUNC.CFUNC(MAT);
6
FETCH PFUNC.CFUNC INTO RFUNC;
7
IF PFUNC.EXCLUI(MAT) THEN
8
:MSG := TO_CHAR(RFUNC.NASC, 'DD/MM/YYYY');
9
END IF;
10
CLOSE PFUNC.CFUNC;
11 EXCEPTION
12
WHEN OTHERS THEN
13
CLOSE PFUNC.CFUNC;
14 END;
15 /
Entre o valor para mat: 140
antigo
3:
MAT
FUNC.CD_MAT%TYPE := &MAT;
novo
3:
MAT
FUNC.CD_MAT%TYPE := 140;
Procedimento PL/SQL concluído com sucesso.
MSG
----------------------------------------------------------19/01/1956
L07_04
Observe a criação da área Rfunc com o layout do cursor Cfunc declarado no package Pfunc.
O cursor não precisa ser declarado no programa uma vez que sua especificação já está feita no
package. Dentro do programa, faremos apenas a abertura do cursor, obtenção das linhas e
fechamento do cursor.
Observe que houve uma preocupação com o fechamento do cursor (em caso de qualquer erro, o
cursor é fechado). Isto foi feito porque as variáveis do pacote e o estado do cursor permanecem
disponíveis até que a sessão seja fechada.
CAPÍTULO 7: PACKAGES - 169
PL/SQL9I – BÁSICO E AVANÇADO
SQL> DECLARE
2
RFUNC
PFUNC.CFUNC%ROWTYPE;
3
MAT
FUNC.CD_MAT%TYPE := &MAT;
4 BEGIN
5
OPEN PFUNC.CFUNC(MAT);
6
FETCH PFUNC.CFUNC INTO RFUNC;
7 END;
8 /
Entre o valor para mat: 150
antigo
3:
MAT
FUNC.CD_MAT%TYPE := &MAT;
novo
3:
MAT
FUNC.CD_MAT%TYPE := 150;
Procedimento PL/SQL concluído com sucesso.
SQL> /
Entre o valor para mat: 160
antigo
3:
MAT
FUNC.CD_MAT%TYPE := &MAT;
novo
3:
MAT
FUNC.CD_MAT%TYPE := 160;
DECLARE
*
ERRO na linha 1:
ORA-06511: PL/SQL: cursor já aberto
ORA-06512: em "ALUNO.PFUNC", line 3
ORA-06512: em line 5
L07_05
Observe que o programa foi executado a primeira vez e não houve fechamento do cursor. Na segunda
execução, recebemos um erro indicando que o cursor ainda permanecia aberto.
Isso ocorre exclusivamente com objetos de pacotes. Quando mencionamos pela primeira vez um
elemento de um pacote em uma sessão, todo o pacote é carregado na memória.
Os elementos definidos no pacote podem ser compartilhados pelos diversos procedimentos que forem
executados nesta sessão. Seus valores não são reinicializados até que a sessão conclua.
Desta forma podemos utilizar pacotes para troca de informações entre processos que executem na
mesma sessão.
CAPÍTULO 7: PACKAGES - 170
PL/SQL9I – BÁSICO E AVANÇADO
USANDO PACOTES PARA TROCA DE INFORMAÇÕES
No pacote Pglobal criado definimos três variáveis e um cursor C1.
SQL> CREATE OR REPLACE
2
DATA
3
NOME
4
NUMERO
5
CURSOR C1
6 END PGLOBAL;
7 /
Pacote criado.
PACKAGE PGLOBAL IS
DATE;
VARCHAR2(100);
NUMBER;
RETURN FUNC%ROWTYPE;
SQL> CREATE OR REPLACE PACKAGE BODY PGLOBAL IS
2
CURSOR C1
RETURN FUNC%ROWTYPE IS SELECT * FROM FUNC;
3 BEGIN
4
DATA
:= SYSDATE;
5
NOME
:= 'VAZIO';
6
NUMERO := 0;
7 END PGLOBAL;
8 /
Corpo de Pacote criado.
L07_06
As variáveis foram inicializadas na parte de inicialização do pacote da seguinte forma: a data recebeu
a data corrente, o nome indica vazio e o número foi inicializado com zero.
SQL> BEGIN
2
:MSG := 'DATA = '||TO_CHAR(PGLOBAL.DATA,
3
'DD/MM/YYYY HH24:MI')||CHR(10)||
4
'NOME = '||PGLOBAL.NOME||CHR(10)||
5
'NUMERO='||TO_CHAR(PGLOBAL.NUMERO);
6
FOR R1 IN PGLOBAL.C1 LOOP
7
IF R1.CD_MAT = &MAT THEN
8
PGLOBAL.DATA := R1.DT_NASC;
9
PGLOBAL.NOME := R1.NM_FUNC||' '||R1.NM_SOBRENOME;
10
PGLOBAL.NUMERO := R1.NR_CARGO;
11
EXIT;
12
END IF;
13
END LOOP;
14 END;
15 /
L07_07
No exemplo apresentamos um programa que consulta e modifica o valor das variáveis do pacote.
O primeiro passo do programa é verificar o valor atual das três variáveis (a função CHR com o valor
10 inclui uma quebra de linha no texto armazenado na variável MSG).
O segundo passo do programa é modificar o valor das três variáveis de acordo com um valor de
matrícula recebido como parâmetro.
CAPÍTULO 7: PACKAGES - 171
PL/SQL9I – BÁSICO E AVANÇADO
Entre o valor para mat: 120
antigo
6:
IF R1.CD_MAT = &MAT THEN
novo
6:
IF R1.CD_MAT = 120 THEN
Procedimento PL/SQL concluído com sucesso.
MSG
---------------------------------------------DATA = 05/06/2003 13:54
NOME = VAZIO
NUMERO=0
SQL> /
Entre o valor para mat: 10
antigo
6:
IF R1.CD_MAT = &MAT THEN
novo
6:
IF R1.CD_MAT = 10 THEN
Procedimento PL/SQL concluído com sucesso.
MSG
---------------------------------------------DATA = 18/10/1962 00:00
NOME = SILVIO OLIVA
NUMERO=58
Na primeira execução, verificamos que o valor das variáveis do pacote correspondem aos valores da
inicialização.
Em seguida, repetimos a execução e observamos que os valores apresentados correspondem aos
dados do funcionário de matrícula 100, passado como parâmetro na primeira execução.
Qualquer referência às variáveis definidas nesse pacote durante a sessão atual recuperará os valores
correntes destas variáveis. Seus valores somente serão perdidos quando houver uma troca de
sessão.
SQL> CONNECT ALUNO/ALUNO
Conectado.
SQL> /
Entre o valor para mat: 10
antigo
6:
IF R1.CD_MAT = &MAT THEN
novo
6:
IF R1.CD_MAT = 10 THEN
Procedimento PL/SQL concluído com sucesso.
MSG
---------------------------------------------DATA = 05/06/2003 14:07
NOME = VAZIO
NUMERO=0
Após a troca de sessão, os valores das variáveis do pacote foram inicializados.
CAPÍTULO 7: PACKAGES - 172
PL/SQL9I – BÁSICO E AVANÇADO
Essa característica dos pacotes permite que façamos troca de informações entre programas que
executem em seqüência, por exemplo, o primeiro programa atribui valores às variáveis e o segundo
consulta e/ou modifica para que um terceiro faça uso.
OVERLOADING
Já sabemos que a PL/SQL permite a definição de dois subprogramas com o mesmo nome, desde que
seus parâmetros formais sejam diferentes em número, ordem ou tipo familiar (por exemplo, Integer e
Real são tipos diferentes, porém pertencem ao mesmo grupo familiar). Essa característica é chamada
de Overloading.
Como restrição, temos que esta técnica só se aplica a subprogramas locais ou subprogramas
definidos em pacotes.
A característica de Overloading pode ser bastante explorada quando definimos as rotinas dentro de
pacotes.
SQL> CREATE OR REPLACE PACKAGE POVER IS
2
FUNCTION DISPLAY (DATA IN DATE) RETURN VARCHAR2;
3
FUNCTION DISPLAY (TEXTO IN VARCHAR2) RETURN VARCHAR2;
4
FUNCTION DISPLAY (NUMERO IN NUMBER) RETURN VARCHAR2;
5 END POVER;
6 /
Pacote criado.
SQL> CREATE OR REPLACE PACKAGE BODY POVER IS
2
FUNCTION DISPLAY (DATA IN DATE) RETURN VARCHAR2 IS
3
BEGIN
4
RETURN 'DATA = '||TO_CHAR(DATA, 'DD/MM/YY HH24:MI');
5
END;
6
FUNCTION DISPLAY (TEXTO IN VARCHAR2) RETURN VARCHAR2 IS
7
BEGIN
8
RETURN 'TEXTO = ' || TEXTO;
9
END;
10
FUNCTION DISPLAY (NUMERO IN NUMBER) RETURN VARCHAR2 IS
11
BEGIN
12
RETURN 'NUMERO = '|| TO_CHAR(NUMERO, 'L999G999G999D99');
13
END;
14 END POVER;
15 /
Corpo de Pacote criado.
L07_08
No exemplo criamos três funções todas com o nome de Display. A primeira recebe como parâmetro
uma data, a segunda um texto e a terceira um número.
CAPÍTULO 7: PACKAGES - 173
PL/SQL9I – BÁSICO E AVANÇADO
SQL> SET AUTOPRINT ON
SQL> EXECUTE :MSG := POVER.DISPLAY(SYSDATE);
Procedimento PL/SQL concluído com sucesso.
MSG
------------------------------------------------------------DATA = 05/06/03 14:13
SQL> EXECUTE :MSG := POVER.DISPLAY ('TESTE DE OVERLOADING');
Procedimento PL/SQL concluído com sucesso.
MSG
------------------------------------------------------------TEXTO = TESTE DE OVERLOADING
SQL> EXECUTE :MSG := POVER.DISPLAY (1234567);
Procedimento PL/SQL concluído com sucesso.
MSG
------------------------------------------------------------NUMERO =
Cr$1.234.567,00
L07_09
Verificamos que as três execuções da rotina Display retornaram informações diferentes, de acordo
com o parâmetro informado. O Oracle determina o tipo do parâmetro recebido e aciona a rotina
Display correspondente.
CAPÍTULO 7: PACKAGES - 174
PL/SQL9I – BÁSICO E AVANÇADO
USO DE FUNÇÕES DE PACOTES EM COMANDOS SQL
Já utilizamos funções armazenadas na base de dados em comandos de SQL DML. As funções
declaradas em pacote também podem ser utilizadas em comandos de SQL DML, desde que validado
o nível de pureza da aplicação.
NÍVEL DE PUREZA
Para que uma função possa ser utilizada em um comando de SQL DML, ela deve respeitar
determinadas regras:
◊
A função não pode modificar tabelas do banco de dados, ou seja, não pode executar os
comandos INSERT, UPDATE ou DELETE.
◊
Funções que lêem ou modificam os valores de variáveis em pacotes não podem ser
executadas remotamente ou em paralelo.
◊
Somente funções chamadas nas cláusulas SELECT, VALUES ou SET podem modificar o
valor de variáveis de pacotes.
◊
A função não poderá chamar outro subprograma que fira uma das regras acima. Da mesma
forma, a função não poderá fazer referência a uma VIEW que fira uma das regras acima (o Oracle
substitui a referência a uma VIEW por um stored SELECT que pode incluir uma chamada de
função).
As funções isoladas, isto é, armazenadas diretamente no banco de dados podem ter estas regras
verificadas quando acionadas por alguma aplicação (ou diretamente por um comando de SQL), ou
seja, quando uma aplicação (ou comando de SQL) menciona uma função armazenada no banco de
dados, o Oracle faz a verificação do nível de pureza da função e retorna ou não um erro, dependendo
do emprego (ver as regras do nível de pureza).
Quando armazenamos funções em pacotes, a verificação não é possível uma vez que, para efeito de
compilação de uma aplicação ou de um comando de SQL, tudo que precisamos ter definido é a parte
de especificação do pacote.
Na parte de especificação não sabemos, ainda, se a rotina atualiza ou não o banco de dados, se
modifica ou não variáveis de pacotes, etc.; seria necessária uma verificação do corpo da rotina.
Para que essa ação não seja necessária (o que feriria todo o conceito de package), devemos
adicionar na parte de especificação uma pragma (diretiva de compilação, já comentada anteriormente)
chamada Restrict_References a fim de garantir o nível de pureza que desejarmos.
Quando o corpo do pacote for compilado, as restrições que tivermos estabelecido serão verificadas.
Houveram modificações no uso desta pragma como veremos mais adiante (a partir da 8i). Seu uso foi
bastante reduzido.
CAPÍTULO 7: PACKAGES - 175
PL/SQL9I – BÁSICO E AVANÇADO
A PRAGMA RESTRICT REFERENCES
No exemplo abaixo observamos que a pragma incluída na parte de especificação possui cinco
parâmetros que indicam qual o nível de pureza desejado.
SQL> CREATE OR REPLACE PACKAGE PDML IS
2
FUNCTION DISPLAY (DATA IN DATE) RETURN VARCHAR2;
3
PRAGMA RESTRICT_REFERENCES(DISPLAY, WNDS, WNPS, RNDS, RNPS);
4 END PDML;
5 /
Pacote criado.
SQL> CREATE OR REPLACE PACKAGE BODY PDML IS
2
FUNCTION DISPLAY (DATA IN DATE) RETURN VARCHAR2 IS
3
BEGIN
4
RETURN 'DATA = '||TO_CHAR(DATA, 'DD/MM/YY HH24:MI');
5
END;
6 END PDML;
7 /
Corpo de Pacote criado.
L07_10
O primeiro parâmetro determina o nome da função que estamos verificando e é de preenchimento
obrigatório.
O segundo parâmetro (WNDS – Write no database state) indica que a função não atualizará o banco
de dados e também é obrigatório se desejarmos que a função possa vir a ser utilizada em algum
comando de SQL. Os demais parâmetros são opcionais e indicam:
◊
WNPS – write no package state – indica que a função não deverá modificar variáveis de
pacotes.
◊
RNDS – read no database state – indica que a função não fará leituras de tabelas do banco
de dados.
◊
RNPS – read no package state – indica que a função não fará leituras a variáveis de pacotes.
Por exemplo, se desejarmos que a função seja utilizada remotamente em comandos de SQL DML,
devemos garantir que ela não leia ou modifique o valor de variáveis de pacotes (WNPS e RNPS).
CAPÍTULO 7: PACKAGES - 176
PL/SQL9I – BÁSICO E AVANÇADO
NÍVEL DE PUREZA E OVERLOADING
A seguir, recriamos o pacote Pover. Para que cada uma das rotinas Display seja validada quanto ao
nível de pureza, repetimos a presença da pragma Restrict_References após cada uma delas.
SQL> CREATE OR REPLACE PACKAGE POVER IS
2
FUNCTION DISPLAY (DATA IN DATE) RETURN VARCHAR2;
3
PRAGMA RESTRICT_REFERENCES (DISPLAY, WNDS, WNPS, RNDS, RNPS);
4
FUNCTION DISPLAY (TEXTO IN VARCHAR2) RETURN VARCHAR2;
5
PRAGMA RESTRICT_REFERENCES (DISPLAY, WNDS, WNPS, RNDS, RNPS);
6
FUNCTION DISPLAY (NUMERO IN NUMBER) RETURN VARCHAR2;
7
PRAGMA RESTRICT_REFERENCES (DISPLAY, WNDS, WNPS, RNDS, RNPS);
8 END POVER;
9 /
Pacote criado.
SQL> CREATE OR REPLACE PACKAGE BODY POVER IS
2
FUNCTION DISPLAY (DATA IN DATE) RETURN VARCHAR2 IS
3
BEGIN
4
RETURN 'DATA = '||TO_CHAR(DATA, 'DD/MM/YY HH24:MI');
5
END;
6
FUNCTION DISPLAY (TEXTO IN VARCHAR2) RETURN VARCHAR2 IS
7
BEGIN
8
RETURN 'TEXTO = ' || TEXTO;
9
END;
10
FUNCTION DISPLAY (NUMERO IN NUMBER) RETURN VARCHAR2 IS
11
BEGIN
12
RETURN 'NUMERO = '|| TO_CHAR(NUMERO, 'L999G999G999D99');
13
END;
14 END POVER;
15 /
Corpo de Pacote criado.
L07_11
A tempo de compilação do Body, as três rotinas devem obedecer aos critérios estabelecidos na
diretiva de compilação correspondente.
SQL>
SQL>
SQL>
SQL>
2
3
COL SALARIO FOR A35
COL DATA FOR A25
COL TEXTO FOR A15
SELECT POVER.DISPLAY(DT_NASC) DATA, POVER.DISPLAY('TESTE') TEXTO,
POVER.DISPLAY(VL_SAL) SALARIO
FROM FUNC WHERE CD_MAT = 10;
DATA
TEXTO
SALARIO
--------------------- --------------- ----------------------DATA = 14/08/53 00:00 TEXTO = TESTE
NUMERO = Cr$26.040,27
L07_12
Acima verificamos a utilização das três rotinas em um comando Select.
CAPÍTULO 7: PACKAGES - 177
PL/SQL9I – BÁSICO E AVANÇADO
NIVEL DE PUREZA E COMANDOS DE SQL
Nessa versão podemos escrever uma função que seja chamada de um comando de SQL sem a
necessidade da verificação do nível de pureza da função, ou seja, a pragma Restrict_References não
precisa ser usada em funções chamadas de comandos de SQL.
SQL> CREATE OR REPLACE PACKAGE RESTR IS
2
FUNCTION GRAVA(MAT IN NUMBER, SALARIO IN NUMBER) RETURN NUMBER;
3
FUNCTION LER(MAT IN NUMBER) RETURN VARCHAR2;
4 END;
5 /
Pacote criado.
SQL> CREATE OR REPLACE PACKAGE BODY RESTR IS
2 FUNCTION GRAVA(MAT IN NUMBER, SALARIO IN NUMBER) RETURN NUMBER IS
3 BEGIN
4
UPDATE FUNC
5
SET VL_SAL = SALARIO
6
WHERE CD_MAT = MAT;
7
RETURN SQL%ROWCOUNT;
8 END;
9 FUNCTION LER(MAT IN NUMBER) RETURN VARCHAR2 IS
10
NOME
VARCHAR2(100);
11 BEGIN
12
SELECT NM_FUNC||' '||NM_SOBRENOME INTO NOME
13
FROM FUNC
14
WHERE CD_MAT = MAT;
15 RETURN NOME;
16 EXCEPTION
17 WHEN NO_DATA_FOUND THEN
18
RETURN 'NOME INEXISTENTE';
19 END;
20 END;
21 /
Corpo de Pacote criado.
SQL> SELECT RESTR.GRAVA(120, 3000) FROM DUAL;
SELECT RESTR.GRAVA(120, 3000) FROM DUAL
*
ERRO na linha 1:
ORA-14551: não é possível executar uma operação DML dentro de uma
consulta
ORA-06512: em "ALUNO.RESTR", line 4
SQL> SELECT RESTR.LER(120) FROM DUAL;
RESTR.LER(120)
--------------------------------------------------------------------SILVIO OLIVA
L07_13
CAPÍTULO 7: PACKAGES - 178
PL/SQL9I – BÁSICO E AVANÇADO
O fato de não precisarmos utilizar a pragma não quer dizer que podemos atualizar o banco de dados
em um comando Select.
O exemplo anterior nos mostra que a leitura foi executada normalmente, pois não feria o nível de
pureza; porém, a gravação foi impedida. Não precisamos, no entanto, definir a pragma para que essas
validações sejam realizadas.
A pragma Restrict_References permanece disponível para garantir que a rotina desenvolvida possua
apenas os efeitos desejados.
RESTRIÇÕES
Quando um comando de SQL é executado, algumas verificações são feitas para saber se o comando
está logicamente embutido dentro da execução de um comando SQL já em execução. Isto ocorre se o
comando está sendo executado de um trigger ou de uma função que já foi chamada de um outro
comando SQL.
Estas verificações são realizadas, implicitamente, sem a necessidade da Pragma (se desejarmos
restrições adicionais, devemos usar a pragma).
Nestes casos, as seguintes restrições são aplicáveis:
◊
Uma função chamada de uma query ou um comando DML não pode encerrar a transação
corrente, criar um savepoint ou retornar (rollback) para um savepoint. Nem efetuar um comando
Alter System ou Alter Session.
◊
Uma função chamada de uma query (SELECT) ou de um comando DML executado em
paralelo, não pode executar um comando de DML que modifique o banco de dados.
◊
Uma função chamada de um comando DML não pode ler ou modificar uma tabela que esteja
sendo modificada pelo comando DML que a chamou.
Estas restrições se aplicam independentemente de qual mecanismo for usado para a execução do
comando de SQL dentro da função ou trigger:
◊
Se aplicam a comandos SQL chamados de PL/SQL quando embutidos diretamente no texto
(de uma rotina ou trigger) ou que estejam embutidos em strings para execução dinâmica (usando
Execute Immediate ou o pacote Dbms_Sql).
◊
Se aplicam a comandos embutidos em Java com sintaxe SQLJ ou que executem usando
JDBC.
◊
Se aplicam a comandos executados com OCI usando CallBack Context de dentro de uma
função “External” C.
Como solução de contorno para estas situações temos o uso de transações Autônomas, que são
executadas em uma sessão independente daquela que a chamou (ver tópico deste assunto).
CAPÍTULO 7: PACKAGES - 179
PL/SQL9I – BÁSICO E AVANÇADO
O ARGUMENTO TRUST
A Pragma Restrict_References ganhou mais um argumento, além dos quatro anteriores (WNDS,
WNPS, RNPS, RNDS).
O argumento Trust facilita a chamada de funções que tenham a declaração Restrict_References para
aquelas que não tem. Este argumento “promete” ao compilador que as restrições declaradas na
pragma serão respeitadas, não havendo necessidade real de o compilador efetuar a verificação.
Isto pode ser útil quando temos um código que está usando a pragma e que aciona outro que não
está. Temos duas formas de resolver esta situação:
SQL> CREATE OR REPLACE PACKAGE RES_TRUST IS
2
FUNCTION GRAVA(MAT IN NUMBER, SALARIO IN NUMBER) RETURN NUMBER;
3
FUNCTION CALL_GRAVA(MAT IN NUMBER) RETURN VARCHAR2;
4
PRAGMA RESTRICT_REFERENCES(CALL_GRAVA, WNDS, TRUST);
5 END;
6 /
Pacote criado.
SQL> CREATE OR REPLACE PACKAGE BODY RES_TRUST IS
2 FUNCTION GRAVA(MAT IN NUMBER, SALARIO IN NUMBER) RETURN NUMBER IS
3
BEGIN
4
UPDATE FUNC
5
SET VL_SAL = SALARIO
6
WHERE CD_MAT = MAT;
7
RETURN SQL%ROWCOUNT;
8
END;
9
FUNCTION CALL_GRAVA(MAT IN NUMBER) RETURN VARCHAR2 IS
10
BEGIN
11
RETURN GRAVA(MAT, 5000);
12
END;
13 END;
14 /
Corpo de Pacote criado.
L07_14
No exemplo acima observamos que a rotina Grava utilizou a pragma Restrict_References com a
finalidade de permitir a compilação do pacote (ela “mentiu” para o compilador).
Como teste, retire o texto PRAGMA RESTRICT_REFERENCES (GRAVA, WNDS, TRUST); da
especificação do pacote e verifique o erro de compilação recebido. Este erro ocorre porque a rotina
chamadora (Call_Grava) possui a indicação da pragma sem a nova cláusula Trust. Isto faz com que o
compilador verifique não só o código dela como também o código dos programas chamados por ela.
A solução apresentada acima é uma das formas de resolver a questão. A segunda opção seria a
colocação da pragma apenas para a rotina Call_Grava, porém com a inclusão da opção Trust.
CAPÍTULO 7: PACKAGES - 180
PL/SQL9I – BÁSICO E AVANÇADO
SQL> CREATE OR REPLACE PACKAGE RES_TRUST IS
2
FUNCTION GRAVA(MAT IN NUMBER, SALARIO IN NUMBER) RETURN NUMBER;
3
FUNCTION CALL_GRAVA(MAT IN NUMBER) RETURN VARCHAR2;
4
PRAGMA RESTRICT_REFERENCES(CALL_GRAVA, WNDS, TRUST);
5 END;
6 /
Pacote criado.
SQL> CREATE OR REPLACE PACKAGE BODY RES_TRUST IS
2 FUNCTION GRAVA(MAT IN NUMBER, SALARIO IN NUMBER) RETURN NUMBER IS
3
BEGIN
4
UPDATE FUNC
5
SET VL_SAL = SALARIO
6
WHERE CD_MAT = MAT;
7
RETURN SQL%ROWCOUNT;
8
END;
9
FUNCTION CALL_GRAVA(MAT IN NUMBER) RETURN VARCHAR2 IS
10
BEGIN
11
RETURN GRAVA(MAT, 5000);
12
END;
13 END;
14 /
Corpo de Pacote criado.
L07_15
Da mesma forma, o corpo do pacote é compilado.
SQL> EXECUTE :MSG := RES_TRUST.CALL_GRAVA(200);
Procedimento PL/SQL concluído com sucesso.
MSG
----------------------------------------------------1
L07_16
A execução apresentada acima indica que a chamada funcionou. Não houve erro porque, nas
condições de uso da rotina, a atualização do banco de dados é permitida.
SQL> SELECT RES_TRUST.CALL_GRAVA(200) FROM DUAL;
SELECT RES_TRUST.CALL_GRAVA(200) FROM DUAL
*
ERRO na linha 1:
ORA-14551: não é possível executar uma operação DML dentro de uma
consulta
ORA-06512: em "ALUNO.RES_TRUST", line 4
ORA-06512: em "ALUNO.RES_TRUST", line 11
L07_17
A utilização feita acima, no entanto, retorna um erro, porque houve violação do nível de pureza.
CAPÍTULO 7: PACKAGES - 181
PL/SQL9I – BÁSICO E AVANÇADO
ALGUNS PACOTES DO ORACLE
Como já comentamos anteriormente, a Oracle usa e abusa de pacotes. Pacotes têm uma grande
vantagem organizacional.
Desta forma, a Oracle grupa diversas rotinas, tipos, variáveis e condições de erro em pacotes que
tenham uma finalidade em comum. Nesse item, conheceremos alguns desses pacotes. Os próximos
capítulos tratarão dos pacotes mais comumente utilizados em ambiente de desenvolvimento.
PACKAGE STANDARD
Esse pacote define o ambiente PL/SQL. Todas as funções de SQL que se aplicam a PL/SQL estão
nele definidas.
A parte de especificação desse pacote define tipos, condições de erro e subprogramas que se tornam
disponíveis, automaticamente, para cada aplicação escrita em PL/SQL.
Como o conteúdo do pacote é diretamente visível pelas aplicações, podemos acioná-lo de um
database trigger (veremos mais adiante), um subprograma armazenado na base de dados, uma
aplicação escrita em uma linguagem Host (usando Oracle Precompiler), uma aplicação OCI e de
várias ferramentas, incluindo Forms Builder, Reports Builder e SQL*Plus.
Nas aplicações, não temos necessidade de mencionar o nome do pacote para que as funções sejam
utilizadas (Standard.To_Char). No entanto, este procedimento pode ser feito sem problemas.
SQL> DECLARE
2
NUMERO
NUMBER := &VALOR;
3
FUNCTION ABS(P1 IN NUMBER) RETURN NUMBER IS
4
BEGIN
5
IF P1 >= 0 THEN
6
RETURN P1;
7
ELSE
8
RETURN P1 * -1;
9
END IF;
10
END ABS;
11 BEGIN
12
IF ABS(NUMERO) = STANDARD.ABS(NUMERO) THEN
13
:MSG := 'FUNÇÃO CORRETA';
14
ELSE
15
:MSG := 'FUNÇÃO INCORRETA';
16
END IF;
17 END;
18 /
Entre o valor para valor: -40
Procedimento PL/SQL concluído com sucesso.
MSG
-------------------------------------------------------FUNÇÃO CORRETA
L07_18
CAPÍTULO 7: PACKAGES - 182
PL/SQL9I – BÁSICO E AVANÇADO
Na página anterior criamos uma função ABS local e comparamos o resultado desta função com o
resultado da função ABS presente no pacote Standard.
PACKAGE DBMS_STANDARD
Este pacote fornece facilidades para a linguagem PL/SQL que ajudam as aplicações a interagirem
com o Oracle Rdbms.
SQL> DESC SYS.DBMS_STANDARD
FUNCTION CLIENT_IP_ADDRESS RETURNS VARCHAR2
PROCEDURE COMMIT
PROCEDURE COMMIT_CM
Nome do Argumento
Tipo
In/Out
------------------------------ ----------------------- -----VC
VARCHAR2
IN
FUNCTION DATABASE_NAME RETURNS VARCHAR2
FUNCTION DELETING RETURNS BOOLEAN
FUNCTION DES_ENCRYPTED_PASSWORD RETURNS VARCHAR2
Nome do Argumento
Tipo
In/Out
------------------------------ ----------------------- -----USER
VARCHAR2
IN
FUNCTION DICTIONARY_OBJ_NAME RETURNS VARCHAR2
FUNCTION DICTIONARY_OBJ_NAME_LIST RETURNS BINARY_INTEGER
Nome do Argumento
Tipo
In/Out
------------------------------ ----------------------- -----OBJECT_LIST
DBMS_STANDARD
OUT
FUNCTION DICTIONARY_OBJ_OWNER RETURNS VARCHAR2
FUNCTION DICTIONARY_OBJ_OWNER_LIST RETURNS BINARY_INTEGER
Nome do Argumento
Tipo
In/Out
------------------------------ ----------------------- -----OWNER_LIST
DBMS_STANDARD
OUT
FUNCTION DICTIONARY_OBJ_TYPE RETURNS VARCHAR2
FUNCTION GRANTEE RETURNS BINARY_INTEGER
Nome do Argumento
Tipo
In/Out
------------------------------ ----------------------- -----USER_LIST
DBMS_STANDARD
OUT
FUNCTION INSERTING RETURNS BOOLEAN
FUNCTION INSTANCE_NUM RETURNS BINARY_INTEGER
FUNCTION IS_ALTER_COLUMN RETURNS BOOLEAN
Nome do Argumento
Tipo
In/Out
------------------------------ ----------------------- -----COLUMN_NAME
VARCHAR2
IN
FUNCTION IS_CREATING_NESTED_TABLE RETURNS BOOLEAN
FUNCTION IS_DROP_COLUMN RETURNS BOOLEAN
Nome do Argumento
Tipo
In/Out
------------------------------ ----------------------- -----COLUMN_NAME
VARCHAR2
IN
FUNCTION IS_SERVERERROR RETURNS BOOLEAN
Nome do Argumento
Tipo
In/Out
------------------------------ ----------------------- -----ERRNO
BINARY_INTEGER
IN
FUNCTION LOGIN_USER RETURNS VARCHAR2
FUNCTION PARTITION_POS RETURNS BINARY_INTEGER
Default?
--------
Default?
-------DEFAULT
Default?
--------
Default?
--------
Default?
--------
Default?
--------
Default?
--------
Default?
--------
CAPÍTULO 7: PACKAGES - 183
PL/SQL9I – BÁSICO E AVANÇADO
No exemplo vemos uma parte das funções e procedures presentes no pacote Dbms_Standard.
PACKAGE DBMS_OUTPUT
Com este pacote, podemos trocar informação entre aplicações ou obter informações (display) para
efeito de depuração. Podemos visualizar as informações enviadas por uma aplicação PL/SQL no
SQL*Plus se usarmos o comando SET com a opção SERVEROUTPUT ON.
SQL> SET SERVEROUT ON
SQL> DECLARE
2
NUMERO
NUMBER := &VALOR;
3
FUNCTION ABS(P1 IN NUMBER) RETURN NUMBER IS
4
BEGIN
5
IF P1 >= 0 THEN
6
RETURN P1;
7
ELSE
8
RETURN P1 * -1;
9
END IF;
10
END ABS;
11 BEGIN
12
IF ABS(NUMERO) = STANDARD.ABS(NUMERO) THEN
13
DBMS_OUTPUT.PUT_LINE('FUNÇÃO CORRETA');
14
ELSE
15
DBMS_OUTPUT.PUT_LINE('FUNÇÃO INCORRETA');
16
END IF;
17 END;
18 /
Entre o valor para valor: 20
antigo
2:
NUMERO
NUMBER := &VALOR;
novo
2:
NUMERO
NUMBER := 20;
FUNÇÃO CORRETA
Procedimento PL/SQL concluído com sucesso.
No exemplo usamos o comando Set Serveroutput On no SQL*Plus e a execução do bloco de PL/SQL
utilizou a procedure Put_Line do pacote DBMS_OUTPUT. Ao término do programa, o texto “Função
Correta” foi apresentado. Essa forma de utilização é muito útil para depuração de aplicações.
PACKAGE DBMS_PIPE
Este pacote permite a troca de informações entre aplicações de diferentes sessões e até de diferentes
usuários através de uma área de memória chamada Pipe.
CAPÍTULO 7: PACKAGES - 184
PL/SQL9I – BÁSICO E AVANÇADO
PACKAGE UTL_FILE
Com este pacote podemos gerar e/ou ler informações de arquivos de texto externos ao banco de
dados. Oferece uma variedade de rotinas que nos permitem a abertura, leitura, fechamento e
gravação de dados no arquivo.
PACKAGE DBMS_SQL
Tem a finalidade de permitir que aplicações executem comandos de SQL DML e DDL dinamicamente
a tempo de execução. A execução de um comando dinamicamente significa que o comando que
desejamos executar será passado para o Oracle em uma string e não explicitamente codificado dentro
do aplicativo.
PACKAGE DBMS_ALERT
Permite que acionemos database triggers quando valores específicos forem modificados no banco de
dados.
PACKAGE DBMS_RANDOM
Permite a geração de números randômicos.
PACKAGE DBMS_FLASHBACK
Permite que tenhamos acesso a dados antes do momento de sua atualização.
PACKAGE DBMS_LOB
Manipulação de Lobs.
PACKAGE DBMS_ROWID
Consulta e conversão de valores de Rowid.
PACKAGE UTL_HTTP
Esse pacote permite que programas PL/SQL façam chamadas HTTP. Podemos usá-lo para recuperar
dados da Internet ou efetuar chamada ao Oracle Web Server.
CAPÍTULO 7: PACKAGES - 185
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 7:
1. Crie um pacote que tenha uma rotina que receba como parâmetro:
A) Um numérico e retorne um alfanumérico com o valor editado para o formato monetário
brasileiro e símbolo financeiro “R$”.
B) Um alfanumérico e retorne um alfanumérico com o texto apresentado de trás para a frente.
C) Uma data e retorne um alfanumérico com as seguintes características: dia da semana, dia no
ano, mês por extenso, mês em algarismos romanos, ano por extenso e ano ISO.
Use Overloading.
2. Crie duas funções em um pacote.
◊
A primeira deverá ler o funcionário cuja matrícula foi recebida como parâmetro. Se o
funcionário tiver mais de dez anos de casa, salário inferior a 2.500,00 e não tiver recebido
promoção no último ano, a segunda rotina deverá ser acionada.
◊
A segunda rotina recebe como parâmetro um código de matrícula e efetua a promoção do
funcionário gerando um aumento de 10% e registrando esta atualização no histórico.
Garanta que estas rotinas sejam disponibilizadas para todos os usuários, mas que somente aqueles
com acesso às tabelas mencionadas possam atualizá-las.
3. Crie uma função em um pacote que receba um texto e retorne o mesmo texto com todas as letras
maiúsculas. Essa função deve ser testada em um comando Update para atualização dos nomes e
sobrenomes dos funcionários.
4. Crie um pacote que declare uma variável Date, uma variável Varchar2(100) e uma variável Number.
Em uma mesma sessão do SQL*Plus:
A) Dê valor inicial para as três variáveis do pacote.
B) Execute as três funções do Exercício 1 passando como parâmetro as variáveis do pacote.
C) Apresente o valor das três variáveis.
5. Crie uma rotina em um pacote que receba como parâmetro uma data de nascimento, uma datalimite e calcule a idade em relação à data limite.
Essa rotina deve ser usada em um relatório a ser gerado para disco que apresente o nome do
funcionário completo e sua idade no dia da admissão e no dia de hoje.
CAPÍTULO 7: PACKAGES - 186
PL/SQL9I – BÁSICO E AVANÇADO
6. Crie um pacote que declare as seguintes variáveis:
◊
Um tipo tabela de varchar2(100).
◊
Um tipo tabela para datas.
◊
Um tipo registro de (uma data, um código numérico e um texto de 100 posições).
◊
Um tipo tabela do registro.
◊
Um tipo registro de (um código numérico e um valor numérico).
◊
Um tipo tabela do registro anterior.
◊
Um tipo tabela de Func (use Rowtype).
◊
Um tipo tabela de Depto (use Rowtype).
◊
Um cursor com todas as colunas de Func para um determinado departamento e cargo
recebidos como parâmetro.
◊
Um cursor com todas as colunas de Depto para um determinado código de gerente recebido
como parâmetro.
7. Crie um programa que receba como parâmetro um código de departamento e um número de cargo
e separe os dados da tabela Func em três grupos para utilização como parâmetro por três outros
programas da seguinte forma:
◊
O programa Rot1 deve receber uma lista de datas de admissão.
◊
O programa Rot2 deve receber uma lista de datas de nascimento, matrículas e nomes.
◊
O programa Rot3 deve receber uma lista de cargos e salários.
Utilize os tipos e cursores declarados no exercício anterior. Não é necessário criar os três programas.
CAPÍTULO 7: PACKAGES - 187
PL/SQL9I – BÁSICO E AVANÇADO
CAPÍTULO 8: O PACOTE DBMS_OUTPUT
CONCEITO
Este pacote tem a finalidade de enviar mensagens a partir de procedures, packages ou triggers. Ele
se utiliza de um buffer em memória para transferência das mensagens.
Quando um programa envia mensagens através do pacote Dbms_Output, essas mensagens são
armazenadas na área de buffer e somente apresentadas ao término do programa.
Caso estejamos executando o programa no SQL*Plus ou Server*Manager, basta que executemos o
comando Set Serveroutput On para que todas as mensagens enfileiradas pelo programa sejam
apresentadas.
Se estivermos executando rotinas encadeadas ou blocos anônimos, podemos obter as linhas geradas
pelo programa anterior usando a rotina Get_Line do próprio pacote.
Este package pode ser especialmente útil para depuração.
No quadro abaixo apresentamos os componentes do pacote.
Componente
Tipo
Descrição
Enable
Procedure
Habilita a chamada das demais rotinas do pacote.
Disable
Procedure
Desabilita a chamada das demais rotinas do pacote.
Put
Procedure
Inclui uma informação na área de buffer.
New_Line
Procedure
Acrescenta indicação de quebra de linha na área de buffer.
Put_Line
Procedure
Inclui uma informação na área de buffer
simultaneamente, o caracter para quebra de linha.
Get_line
Procedure
Obtém uma linha da área de buffer.
Get_lines
Procedure
Obtém diversas linhas da área de buffer.
Chararr
Type
Tipo tabela de varchar2(255). Pode ser usado para obtenção de
diversas linhas da área de buffer.
e
adiciona,
A seguir, detalharemos cada uma das rotinas que compõem o pacote.
CAPÍTULO 8: O PACOTE DBMS_OUTPUT - 188
PL/SQL9I – BÁSICO E AVANÇADO
ROTINAS DO PACOTE
ENABLE
Esta procedure habilita chamadas para Put, Put_Line, New_Line, Get_Line e Get_Lines.
As chamadas a estas procedures são ignoradas se o package DBMS_OUTPUT não estiver habilitado.
Devemos especificar o tamanho da área de buffer em bytes.
Esse tamanho pode variar de 2.000 até 1.000.000. Se esse tamanho for excedido, receberemos uma
mensagem de erro (ORU-10027: Buffer overflow, Limit of buffer_limit bytes).
Podemos executar essa procedure diversas vezes. O maior valor de buffer especificado dentre as
múltiplas chamadas é o que será utilizado.
SQL> BEGIN
2
DBMS_OUTPUT.ENABLE(2000);
3
DBMS_OUTPUT.PUT_LINE('TESTE');
4 END;
5 /
TESTE
Procedimento PL/SQL concluído com sucesso.
L08_01
No exemplo, a procedure Enable alocou uma área de buffer de 2.000 bytes. Seu uso é dispensado
para tamanhos inferiores a 20.000 bytes pois este é o tamanho default da área de buffer. A procedure
Put_Line incluiu o texto “Teste” na área de buffer e esse texto foi apresentado ao término da execução
do programa.
DISABLE
Esta procedure desabilita as chamadas para Put, Put_Line, New_Line, Get_Line e Get_Lines e limpa
o buffer.
SQL> BEGIN
2
DBMS_OUTPUT.DISABLE;
3
DBMS_OUTPUT.PUT_LINE('TESTE');
4 END;
5 /
Procedimento PL/SQL concluído com sucesso.
L08_02
No exemplo acima utilizamos a procedure Disable. Observe que mesmo executando um Put_Line
posteriormente, nenhum resultado foi apresentado. A utilização dessa procedure pode ser útil quando
estivermos depurando um programa e não desejarmos mais que algumas mensagens sejam
informadas. Como a chamada da rotina Disable é um comando, este pode ser incluído dentro de um
IF estabelecendo uma situação condicional de aparecimento ou não das mensagens.
CAPÍTULO 8: O PACOTE DBMS_OUTPUT - 189
PL/SQL9I – BÁSICO E AVANÇADO
PUT
Estas procedures estão “overloading” no pacote. São três rotinas, cada qual recebendo um tipo de
parâmetro. O parâmetro enviado será armazenado na área de buffer imediatamente após a última
informação. Não é incluído qualquer caracter indicativo de fim de linha.
O uso da rotina Put deve levar em consideração que, a tempo de leitura, as procedures Get_Line e
Get_Lines não retornam uma linha que não tenha sido terminada com o caracter newline.
Se o tamanho especificado pelo buffer for excedido, será recebida uma mensagem de erro.
Devemos lembrar que, ao passarmos uma data ou um numérico para a rotina Put (ou Put_Line)
correspondente, esta fará a conversão default (To_Char) para string. Se desejarmos um formato
específico, devemos usar explicitamente a rotina To_Char e passar o parâmetro como alfanumérico.
SQL> SET SERVEROUT ON
SQL> BEGIN
2
DBMS_OUTPUT.PUT('T');
3
DBMS_OUTPUT.PUT('E');
4
DBMS_OUTPUT.PUT('S');
5
DBMS_OUTPUT.PUT('T');
6
DBMS_OUTPUT.PUT('E');
7
DBMS_OUTPUT.NEW_LINE;
8 END;
9 /
TESTE
L08_03
Procedimento PL/SQL concluído com sucesso.
Neste exemplo usamos a procedure Put cinco vezes, cada uma delas com uma letra da palavra Teste.
O resultado foi apresentado em uma única linha contendo todos os caracteres armazenados
individualmente.
NEW_LINE
Esta procedure coloca uma marca de fim de linha no buffer. Normalmente, usamos esta procedure
após uma ou mais chamadas à procedure Put para indicar fim de mensagem.
Cada chamada a New_Line gerará uma linha a ser retornada pelo Get_Line.
CAPÍTULO 8: O PACOTE DBMS_OUTPUT - 190
PL/SQL9I – BÁSICO E AVANÇADO
PUT_LINE
Estas procedures enviam o parâmetro informado para a área de buffer, acrescentando,
automaticamente, um caracter indicativo de fim de linha após o texto enviado.
Devemos lembrar que ao passarmos uma data ou um numérico para a rotina Put (ou Put_Line)
correspondente, esta fará a conversão default (To_Char) para string. Se desejarmos um formato
específico, devemos usar explicitamente a rotina To_Char e passar o parâmetro como alfanumérico.
SQL> BEGIN
2
DBMS_OUTPUT.PUT_LINE('T');
3
DBMS_OUTPUT.PUT_LINE('E');
4
DBMS_OUTPUT.PUT_LINE('S');
5
DBMS_OUTPUT.PUT_LINE('T');
6
DBMS_OUTPUT.PUT_LINE('E');
7 END;
8 /
T
E
S
T
E
L08_04
Procedimento PL/SQL concluído com sucesso.
Acima utilizamos o mesmo exemplo anterior, substituindo a procedure Put pela Put_Line. O resultado
apresentou cinco linhas, pois após cada texto enviado para o buffer foi acrescido um indicador de fim
de linha.
CAPÍTULO 8: O PACOTE DBMS_OUTPUT - 191
PL/SQL9I – BÁSICO E AVANÇADO
GET_LINE
Esta procedure recupera uma única linha da informação do buffer, excluindo o caracter newline final.
O tamanho máximo da linha é de 255 bytes.
Se a procedure concluir com sucesso, o status retornado é zero, caso contrário 1, indicando que não
existem linhas no buffer.
Quando reutilizamos a área de buffer após uma leitura, todas as linhas restantes, ainda presentes no
buffer, são descartadas para que a área possa ser preenchida novamente.
SQL> BEGIN
2
BEGIN
3
DBMS_OUTPUT.PUT_LINE(SYSDATE);
4
DBMS_OUTPUT.PUT_LINE(12345);
5
DBMS_OUTPUT.PUT_LINE('TESTE');
6
DBMS_OUTPUT.PUT_LINE(TO_CHAR(SYSDATE, 'DD/MM/YYYY HH24:MI'));
7
DBMS_OUTPUT.PUT_LINE(LTRIM(TO_CHAR(12345, 'L999G990D00')));
8
END;
-- FIM BLOCO 1
9
DECLARE
10
LINHA
VARCHAR2(100);
11
ACHOU
NUMBER;
12
BEGIN
13
:MSG := '';
14
DBMS_OUTPUT.GET_LINE(LINHA, ACHOU);
15
WHILE ACHOU = 0 LOOP
16
:MSG := :MSG || LINHA || '; ';
17
DBMS_OUTPUT.GET_LINE(LINHA, ACHOU);
18
END LOOP;
19
END;
20 END;
21 /
Procedimento PL/SQL concluído com sucesso.
MSG
---------------------------------------------------------------------05/06/03; 12345; TESTE; 05/06/2003 18:12; Cr$12.345,00;
L08_05
Acima criamos dois blocos dentro de um programa. No primeiro bloco, preenchemos o buffer com
Sysdate, um número, um texto, uma data formatada e um número formatado. No segundo bloco,
utilizamos a rotina Get_Line para obter uma linha de cada vez e concatenamos a linha obtida na
variável de ambiente msg.
Observe a diferença de formatação quando enviamos diretamente a data (ou número) e quando
formatamos o resultado. Observe, ainda, que, quando recuperamos a linha do buffer a rotina Get_Line
remove o caracter indicativo de quebra de linha do texto retornado.
CAPÍTULO 8: O PACOTE DBMS_OUTPUT - 192
PL/SQL9I – BÁSICO E AVANÇADO
GET_LINES
Esta procedure recupera um conjunto de linhas e pode ser usada no lugar da Get_Line para reduzir o
número de chamadas ao servidor. Devemos especificar o número de linhas que desejamos recuperar
do buffer.
A procedure retorna também o número real de linhas recuperado. Se esse número for menor que o
número de linhas requisitado, significa que não existem mais linhas no buffer.
Quando reutilizamos a área de buffer após uma leitura, todas as linhas restantes ainda presentes no
buffer são descartadas para que a área possa ser preenchida novamente.
Cada linha no array pode ter até 255 bytes de comprimento.
SQL> SET SERVEROUT ON
SQL> BEGIN
2
BEGIN
-- BLOCO 1
3
DBMS_OUTPUT.PUT_LINE(SYSDATE);
4
DBMS_OUTPUT.PUT_LINE(12345);
5
DBMS_OUTPUT.PUT_LINE('TESTE');
6
DBMS_OUTPUT.PUT_LINE(TO_CHAR(SYSDATE, 'DD/MM/YYYY HH24:MI'));
7
DBMS_OUTPUT.PUT_LINE(LTRIM(TO_CHAR(12345, 'L999G990D00')));
8
END;
-- FIM BLOCO 1
9
DECLARE
10
LINHAS
DBMS_OUTPUT.CHARARR;
11
ACHOU
NUMBER := 10;
12
BEGIN
13
:MSG := '';
14
DBMS_OUTPUT.GET_LINES(LINHAS, ACHOU);
15
FOR I IN 1..ACHOU LOOP
16
:MSG := :MSG || LINHAS(I) || '; ';
17
END LOOP;
18
END;
19 END;
20 /
Procedimento PL/SQL concluído com sucesso.
MSG
---------------------------------------------------------------------05/06/03; 12345; TESTE; 05/06/2003 18:15; Cr$12.345,00;
L08_06
No exemplo substituímos o uso da rotina Get_Line pela chamada da Get_Lines. Solicitamos a leitura
de dez linhas (valor inicial da variável Achou).
O resultado mostrou que havia apenas três linhas na área de buffer. Observe que o tipo da variável
Linhas é Dbms_Output.Chararr, ou seja, um tipo Table definido dentro do pacote Dbms_Output. Cada
linha desta tabela PL/SQL tem comprimento máximo de 255 bytes.
CAPÍTULO 8: O PACOTE DBMS_OUTPUT - 193
PL/SQL9I – BÁSICO E AVANÇADO
EXEMPLO USANDO O SQL*PLUS
O package DBMS_OUTPUT é muito usado para depuração de stored procedures e triggers.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CREATE OR REPLACE
FUNCTION DEPTOSAL (PDEPTO IN CHAR) RETURN NUMBER IS
CURSOR C1
IS SELECT VL_SAL FROM FUNC
WHERE CD_DEPTO = PDEPTO;
TOT_SAL
NUMBER(11,2) := 0;
CONTADOR
NUMBER(10)
:= 1;
BEGIN
DBMS_OUTPUT.ENABLE(10000);
FOR R1 IN C1 LOOP
TOT_SAL := TOT_SAL + R1.VL_SAL;
DBMS_OUTPUT.PUT_LINE('LOOP N. = '||TO_CHAR(CONTADOR)||
'; ACUMULADO = '||TO_CHAR(TOT_SAL));
CONTADOR := CONTADOR + 1;
END LOOP;
DBMS_OUTPUT.PUT_LINE('TOTAL DE SALÁRIOS = '||TO_CHAR(TOT_SAL));
RETURN TOT_SAL;
END DEPTOSAL;
/
L08_07
Função criada.
No exemplo criamos uma função de base que está preenchendo a área de buffer com dados da leitura
da tabela Func.
SQL> VARIABLE TOTAL NUMBER
SQL> EXECUTE :TOTAL := DEPTOSAL('A00');
LOOP N. = 1; ACUMULADO = 26040,27
LOOP N. = 2; ACUMULADO = 48995,24
LOOP N. = 3; ACUMULADO = 63434,62
TOTAL DE SALÁRIOS = 63434,62
Procedimento PL/SQL concluído com sucesso.
TOTAL
---------63434,62
L08_08
No exemplo fizemos uma execução da função no SQL*Plus para verificar o resultado acumulado.
As mensagens são apresentadas na linha do SQL*PLUS.
CAPÍTULO 8: O PACOTE DBMS_OUTPUT - 194
PL/SQL9I – BÁSICO E AVANÇADO
EXEMPLO USANDO OUTRO PROGRAMA
Este pacote também pode ser usado para passagem de informação entre programas executados no
ambiente Server.
SQL> set linesize 70
SQL> variable msg varchar2(500)
SQL> SET VERIFY OFF
SQL> DECLARE
2
LINHAS
DBMS_OUTPUT.CHARARR;
3
TOTAL
NUMBER;
4
QTD
NUMBER := 10;
5 BEGIN
6
:MSG := 'LOOP: ';
7
TOTAL := DEPTOSAL(UPPER('&DEPTO'));
8
WHILE QTD = 10 LOOP
9
DBMS_OUTPUT.GET_LINES(LINHAS, QTD);
10
FOR I IN 1..QTD LOOP
11
:MSG := :MSG || LINHAS(I) || '; ';
12
END LOOP;
13
END LOOP;
14 END;
15 /
Entre o valor para depto: e11
Procedimento PL/SQL concluído com sucesso.
MSG
------------------------------------------------------------------LOOP: LOOP N. = 1; ACUMULADO = 14686,26; LOOP N. = 2; ACUMULADO =
31532,21; LOOP N. = 3; ACUMULADO = 41376,67; LOOP N. = 4; ACUMULADO
= 5276 7,76; LOOP N. = 5; ACUMULADO = 62971,59; TOTAL DE SALÁRIOS =
62971,59;
Neste bloco anônimo, fizemos uma chamada à função Deptosal gerada anteriormente. A função
preenche a área de buffer.
Essa área é visível pelo bloco (ou rotina) que acione a rotina ou bloco que preenche o buffer. Com a
rotina Get_Lines, fizemos a leitura das linhas presentes no buffer e atribuímos as diversas linhas à
variável de ambiente Msg.
CAPÍTULO 8: O PACOTE DBMS_OUTPUT - 195
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 8:
1. Descreva com suas palavras o pacote DBMS_OUTPUT.
2. Crie um programa que receba como parâmetro um número de mês e emita uma carta
parabenizando o funcionário pela data do seu aniversário (use DBMS_OUTPUT). Não deve ser usada
stored procedure.
3. Usando o pacote DBMS_OUTPUT, gere um arquivo contendo o nome, data de nascimento e salário
dos funcionários. Alinhe os dados por coluna.
4. Faça uma rotina que usa a DBMS_OUTPUT para colocar no buffer o nome dos aniversariantes do
mês (recebido como parâmetro).
5. Faça um programa que leia a área de buffer do programa anterior e verifique se o funcionário cujo
nome será fornecido como parâmetro é aniversariante. Informe o resultado em uma variável Bind.
6. Faça um script que coloque no prompt do SQL*PLUS o nome do usuário e a linguagem em que
está o banco de dados atualmente. Garanta que este script seja executado todas as vezes em que o
SQL*Plus for acionado (utilize seus conhecimentos de SQL*Plus).
7. Gere uma área de buffer com o seguinte layout: nome, sobrenome, data de nascimento (formato
dd/mm/yyyy), sexo e salário.
Os dados devem ser separados pelo símbolo #.
Caso uma determinada coluna não tenha valor (Null), deve ser substituído por “**” para colunas
alfanuméricas e -1 para colunas numéricas.
Use DBMS_OUTPUT para gerar o buffer.
8. Faça um programa que leia o buffer gerado pelo programa anterior e crie novas linhas na tabela
Func. O número da matrícula deve ser gerado a partir do último armazenado em Func.
CAPÍTULO 8: O PACOTE DBMS_OUTPUT - 196
PL/SQL9I – BÁSICO E AVANÇADO
CAPÍTULO 9: O PACOTE UTL_FILE
CONCEITO
O pacote Utl_File possui um conjunto de rotinas que têm a finalidade de permitir o acesso ou geração
de arquivos externos ao banco de dados. As rotinas são similares aos processos de manipulação de
arquivos convencionais.
Para utilização deste package o DBA deve acrescentar o parâmetro UTL_FILE_DIR ao arquivo de
inicialização INIT.ORA a fim de determinar quais diretórios estão disponíveis para acesso.
Sabemos que a PL/SQL executa no ambiente Server; desta forma, os arquivos lidos ou gravados com
o uso deste pacote serão lidos ou gerados no ambiente servidor (no ambiente em que se acha o
banco de dados).
Este pacote não declara apenas procedures e funções, são declaradas exceções e tipos também.
O quadro abaixo apresenta um resumo dos diversos objetos presentes no pacote.
Componente
Tipo
Descrição
Invalid_Path
Exception
Localização ou nome do arquivo inválido
Invalid_Mode
Exception
O parâmetro <modo> na rotina FOPEN está inválido.
Invalid_Filehandle
Exception
O handle do arquivo está inválido.
Invalid_Operation
Exception
O arquivo não pode ser aberto ou operado como requisitado.
Read_Error
Exception
Um erro de sistema operacional ocorreu durante a operação
de leitura.
Write_Error
Exception
Um erro de sistema operacional ocorreu durante a operação
de gravação.
Internal_Error
Exception
Um erro não identificado ocorreu.
CAPÍTULO 9: O PACOTE UTL_FILE - 197
PL/SQL9I – BÁSICO E AVANÇADO
Componente
Tipo
Descrição
Fopen
Function
Abre um arquivo para leitura ou gravação.
Is_Open
Function
Verifica se File Handle está preenchido.
Fclose
Procedure
Fecha o arquivo informado como parâmetro.
Fclose_All
Procedure
Fecha todos os arquivos abertos.
Get_Line
Procedure
Lê uma linha de um arquivo de entrada.
Put
Procedure
Gera uma linha em um arquivo de saída. Não inclui o caracter
de fim de linha.
Put_Line
Procedure
Gera uma linha em um arquivo de saída. Inclui o caracter de
fim de linha.
New_Line
Procedure
Inclui um caracter de fim de linha.
Putf
Procedure
Similar ao Put, podendo receber parâmetros.
Fflush
Procedure
Envia os dados pendentes (em memória) para o arquivo de
disco associado.
File_Type
Tipo
Tipo Record que armazena as informações referentes ao
arquivo aberto. O conteúdo deste tipo é privativo do pacote
Utl_File. Os usuários do pacote não devem fazer referência ou
modificar seus componentes.
O conteúdo do tipo File_Type é privativo do pacote Utl_File. Os usuários do pacote não devem fazer
referência ou modificar seus componentes.
Esse pacote trabalha de forma semelhante ao pacote Dbms_Output no que se refere à area de
trabalho. Ele se utiliza de um buffer em memória para transferência dos dados a serem armazenados
ou lidos do arquivo em disco.
Quando um programa envia linhas para gravação através do pacote Utl_File, essas linhas são
armazenadas na área de buffer e transferidas para disco quando o buffer se enche ou quando
acionamos a rotina Fflush, que força a gravação do buffer para disco.
A seguir, veremos cada uma das rotinas que compõem o pacote, sua ação e as condições de erro que
podem ser adquiridas em cada caso.
Para que possamos ler e gravar arquivos com este pacote, nosso primeiro passo será modificar o
arquivo Init<dbname>.ora presente no diretório <oracle-hohe>\database. Esse arquivo contém
diversos parâmetros para inicialização do banco de dados.
CAPÍTULO 9: O PACOTE UTL_FILE - 198
PL/SQL9I – BÁSICO E AVANÇADO
###########################################
# Diagnóstico e Estatística
###########################################
background_dump_dest=C:\oracle\admin\Ora9i\bdump
core_dump_dest=C:\oracle\admin\Ora9i\cdump
timed_statistics=TRUE
user_dump_dest=C:\oracle\admin\Ora9i\udump
###########################################
# Distribuído, Replicação e Snapshot
###########################################
db_domain=""
remote_login_passwordfile=EXCLUSIVE
###########################################
# Diversos
###########################################
compatible=9.0.0
db_name=Ora9i
UTL_FILE_DIR = *
global_names = false
job_queue_processes = 2
job_queue_interval = 600
Acima apresentamos um trecho deste arquivo em que acrescentamos a linha Utl_File_Dir = *.
Esta sintaxe permite o acesso a qualquer diretório da máquina. Como estamos trabalhando com o
Personal Oracle9i, a máquina cliente e a servidora são uma só; desta forma o diretório a ser utilizado
para leitura ou gravação pode ser o disco corrente. Se desejarmos limitar o acesso para determinados
diretórios, devemos usar a sintaxe Utl_File_Dir = <nome dos diretórios>.
O arquivo init.ora somente é lido uma vez pelo banco de dados quando o Oracle entra em atividade.
Desta forma não temos outra alternativa a não ser retirar o banco do ar e colocá-lo novamente.
CAPÍTULO 9: O PACOTE UTL_FILE - 199
PL/SQL9I – BÁSICO E AVANÇADO
CONHECENDO O PACOTE
FOPEN
A função Fopen abre um arquivo para leitura ou gravação. O Path do diretório já deve existir, uma vez
que não é feita criação pelo Fopen.
A função retorna um file handle que deve ser usado em todas as operações de I/O subseqüentes no
arquivo.
Na sintaxe apresentada, observamos que essa função recebe três parâmetros, a saber:
◊
<location> – Indica o diretório do arquivo a ser aberto.
◊
<file> – Indica o nome do arquivo (incluindo a extensão). Não deve ser incluído o caminho
(Path).
◊
<open_mode> – Indica o modo como o arquivo será usado. Os valores válidos são: ‘r’ para
leitura, ‘w’ para gravação e ‘a’ para acrescentarmos dados ao fim do arquivo.
◊
<max_linesize> – na versão 9i foi criada uma outra função Fopen com um quarto parâmetro
que indica o tamanho máximo da linha lida ou gravada. O valor default para este parâmetro é
1023 bytes e o valor máximo é 32767.
SQL> DECLARE
2
SAIDA
UTL_FILE.FILE_TYPE;
3
LINHA
VARCHAR2(1022);
4 BEGIN
5
SAIDA := UTL_FILE.FOPEN('C:\TEMP', 'SAIDA.TXT', 'A');
6
UTL_FILE.PUT_LINE(SAIDA, 'GRAVAÇÃO EXEMPLO 1');
7
UTL_FILE.FFLUSH(SAIDA);
8
UTL_FILE.FCLOSE(SAIDA);
9 EXCEPTION
10
WHEN UTL_FILE.INVALID_PATH THEN
11
RAISE_APPLICATION_ERROR(-20000, 'Caminho inválido');
12
WHEN UTL_FILE.INVALID_MODE THEN
13
RAISE_APPLICATION_ERROR(-20001, 'Modo de uso inválido');
14
WHEN UTL_FILE.INVALID_OPERATION THEN
15
RAISE_APPLICATION_ERROR(-20002, 'Operação incompatível');
16 END;
17 /
Procedimento PL/SQL concluído com sucesso.
L09_01
No exemplo fizemos um pequeno programa que grava uma linha em um arquivo de saída. Como
primeiro passo do programa, declaramos o file handle (Saida) com o tipo Utl_File.File_Type.
CAPÍTULO 9: O PACOTE UTL_FILE - 200
PL/SQL9I – BÁSICO E AVANÇADO
A segunda etapa é definir como o arquivo deve ser aberto usando-se a rotina Fopen. No nosso caso,
usamos a opção “A”, indicando que se o arquivo não existir deve ser criado e se já existir os dados
devem ser adicionados.
As condições de erro definidas dizem respeito apenas à abertura.
A função Fopen pode causar uma das seguintes condições de erro:
◊
Invalid_Path – Localização ou nome do arquivo inválido.
◊
Invalid_Mode – O parâmetro <modo> da rotina Fopen está inválido.
◊
Invalid_Operation – O arquivo não pode ser aberto ou operado como requisitado.
FOPEN_NCHAR
A função Fopen_Nchar abre um arquivo para leitura ou gravação. Sua sintaxe e forma de trabalho são
similares à da função Fopen vista anteriormente, a diferença é que o arquivo de entrada é formato
Unicode em vez do formato do charset do banco de dados.
IS_OPEN
Testa um file handle para ver se ele identifica um arquivo aberto. Essa função indica apenas se o
arquivo está aberto ou não, ou seja, se o file handle é válido ou não.
Ela não garante que uma operação de I/O posterior usando o file handle não venha a receber um erro
de sistema operacional.
Essa função não adquire qualquer tipo de condição de erro.
CAPÍTULO 9: O PACOTE UTL_FILE - 201
PL/SQL9I – BÁSICO E AVANÇADO
FCLOSE
A procedure Fclose fecha um arquivo identificado por um file handle.
A função Fclose pode causar uma das seguintes condições de erro:
◊
◊
Invalid_Filehandle – O handle do arquivo está inválido.
Write_Error – Um erro de sistema operacional ocorreu durante a operação de gravação.
Podemos receber uma exceção do tipo WRITE_ERROR se existirem buffers de dados ainda não
gravados quando o comando Fclose for executado.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DECLARE
SAIDA
UTL_FILE.FILE_TYPE;
LINHA
VARCHAR2(1022);
BEGIN
SAIDA := UTL_FILE.FOPEN('C:\TEMP', 'SAIDA.SQL', 'A', 2000);
UTL_FILE.PUT_LINE(SAIDA, 'GRAVAÇÃO EXEMPLO 2');
UTL_FILE.FFLUSH(SAIDA);
UTL_FILE.FCLOSE(SAIDA);
EXCEPTION
WHEN UTL_FILE.WRITE_ERROR THEN
RAISE_APPLICATION_ERROR(-20003, 'Gravação com erro');
WHEN UTL_FILE.INVALID_FILEHANDLE THEN
RAISE_APPLICATION_ERROR(-20004, 'Arquivo não identificado');
END;
/
L09_02
Procedimento PL/SQL concluído com sucesso.
O exemplo é semelhante ao anterior, com a diferença de que o tratamento das condições de erro diz
respeito agora aos possíveis erros gerados a tempo de close do arquivo.
Observe, também, que determinamos um tamanho máximo de 2000 bytes para a linha a ser gravada.
CAPÍTULO 9: O PACOTE UTL_FILE - 202
PL/SQL9I – BÁSICO E AVANÇADO
FCLOSE_ALL
Esta procedure fecha todos os file handle abertos na sessão.
A procedure Fclose_All não altera o estado de um file handle adquirido (aberto) pelo usuário. Isto
significa que um teste com Is_Open poderá retornar True mesmo após o arquivo ter sido fechado,
porém nenhuma operação de leitura ou gravação será válida até que um novo Fopen seja realizado
para aquele file handle.
A função Fclose_All pode causar a seguinte condição de erro:
◊
Write_Error – Um erro de sistema operacional ocorreu durante a operação de gravação.
SQL> DECLARE
2
SAIDA
UTL_FILE.FILE_TYPE;
3
LINHA
VARCHAR2(1022);
4 BEGIN
5
SAIDA := UTL_FILE.FOPEN('C:\TEMP', 'SAIDA.SQL', 'A', 2000);
6
UTL_FILE.PUT_LINE(SAIDA, 'GRAVAÇÃO EXEMPLO 2');
7
UTL_FILE.FFLUSH(SAIDA);
8
UTL_FILE.FCLOSE_ALL;
9
IF UTL_FILE.IS_OPEN(SAIDA) THEN
10
:MSG := 'ARQUIVO ABERTO';
11
END IF;
12 EXCEPTION
13
WHEN UTL_FILE.WRITE_ERROR THEN
14
RAISE_APPLICATION_ERROR(-20003, 'Gravação com erro');
15 END;
16 /
Procedimento PL/SQL concluído com sucesso.
MSG
-----------------------------------------------------------------ARQUIVO ABERTO
L09_03
No exemplo, substituímos o fechamento específico pela rotina Fclose_All.
Após o fechamento, testamos o estado do file handle e percebemos que ele indica que o arquivo está
aberto.
Esta situação não ocorre se usarmos o fechamento específico de um arquivo empregando Fclose.
CAPÍTULO 9: O PACOTE UTL_FILE - 203
PL/SQL9I – BÁSICO E AVANÇADO
GET_LINE
Esta procedure lê uma linha de texto de um arquivo aberto identificado pelo file handle e coloca o texto
no buffer de saída (parâmetro).
O tamanho default do texto para leitura é de 1.023 bytes. Para que venhamos a obter uma linha mior
que esta devemos ter informado esste limite ao tempo de abertura do arquivo. A leitura de uma linha
em branco gerará uma string vazia.
A operação de leitura pode receber uma das seguintes exceções:
◊
Invalid_Filehandle – O handle do arquivo está inválido.
◊
Invalid_Operation – O arquivo não pode ser aberto ou operado como requisitado.
◊
Read_Error – Um erro de sistema operacional ocorreu durante a leitura.
◊
CharsetMismatch – Este erro pode ocorrer se houvermos aberto o arquivo para Unicode e
houvermos usado esta rotina em seguida (devemos usar Get_Line_Nchar).
Além dessas, poderá receber também uma das seguintes exceptions predefinidas:
◊
◊
Value_Error – Se a linha não couber na área de buffer.
No_Data_Found – Se o texto não for lido porque encontrou fim de arquivo.
SQL> DECLARE
2
SAIDA
UTL_FILE.FILE_TYPE;
3
LINHA
VARCHAR2(2000);
4 BEGIN
5
SAIDA := UTL_FILE.FOPEN('C:\TEMP', 'SAIDA.SQL', 'R', 2000);
6
:MSG := '';
7
LOOP
8
UTL_FILE.GET_LINE(SAIDA, LINHA);
9
:MSG := :MSG || LINHA || CHR(10);
10
END LOOP;
11 EXCEPTION
12 WHEN UTL_FILE.INVALID_FILEHANDLE THEN
13
RAISE_APPLICATION_ERROR(-20004, 'Arquivo não identificado');
14 WHEN UTL_FILE.INVALID_OPERATION THEN
15
RAISE_APPLICATION_ERROR(-20002, 'Operação incompatível');
16 WHEN UTL_FILE.READ_ERROR THEN
17
RAISE_APPLICATION_ERROR(-20005, 'Erro de Leitura');
18 WHEN NO_DATA_FOUND THEN
19
UTL_FILE.FCLOSE_ALL;
20 WHEN VALUE_ERROR THEN
21
RAISE_APPLICATION_ERROR(-20006, 'Verifique o tamanho da linha');
22 END;
23 /
Procedimento PL/SQL concluído com sucesso.
L09_04
MSG
---------------------------------------------------------------------GRAVAÇÃO EXEMPLO 2
GRAVAÇÃO EXEMPLO 2
CAPÍTULO 9: O PACOTE UTL_FILE - 204
PL/SQL9I – BÁSICO E AVANÇADO
No exemplo lemos as linhas geradas pelas execuções dos exemplos anteriores.
Observe que a utilização das condições de erro Invalid_Filehandle, Invalid_Operation e Read_Error
precisam da presença do nome do pacote precedendo o nome da exception, uma vez que estas
condições estão declaradas dentro do pacote.
Já as exceptions Value_Error e No_Data_Found não necessitam deste prefixo, pois são condições de
erro pré-definidas para todos os blocos de PL/SQL.
A condição de erro No_Data_Found ocorre quando for feita uma tentativa de leitura e já não houver
linhas a serem lidas. Quando isto ocorre no programa, aproveitamos para fechar o arquivo.
GET_LINE_NCHAR
Esta procedure é similar em funcionalidade e sintaxe à procedure Get_Line vista anteriormente. Deve
ser usada quando abrirmos o arquivo com Fopen_Nchar (arquivos Unicode).
CAPÍTULO 9: O PACOTE UTL_FILE - 205
PL/SQL9I – BÁSICO E AVANÇADO
PUT
Esta procedure armazena o texto presente no buffer no arquivo identificado pelo file handle.
A linha incluída não recebe nenhum caracter de fim de linha. É necessária a execução da rotina
New_Line para gerar este caracter quando desejado.
Esta rotina tem a mesma funcionalidade da rotina com o mesmo nome existente no pacote
Dbms_Output.
A operação de gravação pode receber uma das seguintes exceções:
◊
Invalid_Filehandle – O handle do arquivo está inválido.
◊
Invalid_Operation – O arquivo não pode ser aberto ou operado como requisitado.
◊
Write_Error – Um erro de sistema operacional ocorreu durante a operação de gravação.
◊
CharsetMismatch – Este erro pode ocorrer se houvermos aberto o arquivo para Unicode e
houvermos usado esta rotina em seguida (devemos usar Put_Nchar).
SQL> DECLARE
2
SAIDA
UTL_FILE.FILE_TYPE;
3
LINHA
VARCHAR2(2000);
4 BEGIN
5
SAIDA := UTL_FILE.FOPEN('C:\TEMP', 'SAIDA.SQL', 'A', 2000);
6
UTL_FILE.PUT(SAIDA, 'T');
7
UTL_FILE.PUT(SAIDA, 'E');
8
UTL_FILE.PUT(SAIDA, 'S');
9
UTL_FILE.PUT(SAIDA, 'T');
10
UTL_FILE.PUT(SAIDA, 'E');
11
UTL_FILE.PUT(SAIDA, 'S');
12
UTL_FILE.NEW_LINE(SAIDA);
13
UTL_FILE.FFLUSH(SAIDA);
14
UTL_FILE.FCLOSE(SAIDA);
15 EXCEPTION
16
WHEN UTL_FILE.WRITE_ERROR THEN
17
RAISE_APPLICATION_ERROR(-20003, 'Gravação com erro');
18
WHEN UTL_FILE.INVALID_FILEHANDLE THEN
19
RAISE_APPLICATION_ERROR(-20004, 'Arquivo não identificado');
20
WHEN UTL_FILE.INVALID_OPERATION THEN
21
RAISE_APPLICATION_ERROR(-20002, 'Operação incompatível');
22 END;
23 /
Procedimento PL/SQL concluído com sucesso.
L09_05
No exemplo anexamos ao buffer as letras T, E, S, T, E, S separadamente, uma em cada comando
Put.
CAPÍTULO 9: O PACOTE UTL_FILE - 206
PL/SQL9I – BÁSICO E AVANÇADO
SQL> DECLARE
2
SAIDA
UTL_FILE.FILE_TYPE;
3
LINHA
VARCHAR2(2000);
4 BEGIN
5
SAIDA := UTL_FILE.FOPEN('C:\TEMP', 'SAIDA.SQL', 'R', 2000);
6
:MSG := '';
7
LOOP
8
UTL_FILE.GET_LINE(SAIDA, LINHA);
9
:MSG := :MSG || LINHA || CHR(10);
10
END LOOP;
11 EXCEPTION
12 WHEN NO_DATA_FOUND THEN
13
UTL_FILE.FCLOSE_ALL;
14 END;
15 /
Procedimento PL/SQL concluído com sucesso.
MSG
------------------------------------------------------------------GRAVAÇÃO EXEMPLO 2
GRAVAÇÃO EXEMPLO 2
TESTES
L09_06
Ao repetirmos o programa de leitura do arquivo, observamos que o resultado foi gravado em uma
única linha dentro do arquivo.
PUT_NCHAR
Esta procedure é similar em funcionalidade e sintaxe à procedure Put vista anteriormente. Deve ser
usada quando abrirmos o arquivo com Fopen_Nchar (arquivos Unicode).
NEW_LINE
Esta procedure grava um ou mais caracteres de fim de linha para o arquivo identificado pelo file
handle.
A operação de gravação pode receber uma das seguintes exceções:
◊
◊
◊
Invalid_Filehandle – O handle do arquivo está inválido.
Invalid_Operation – O arquivo não pode ser aberto ou operado como requisitado.
Write_Error – Um erro de sistema operacional ocorreu durante a operação de gravação.
No exemplo anterior (L09_05) a rotina New_Line está sendo usada sem o segundo parâmetro, o que
significa a inclusão de apenas um caracter de fim de linha no arquivo (1 é o valor default do
parâmetro).
CAPÍTULO 9: O PACOTE UTL_FILE - 207
PL/SQL9I – BÁSICO E AVANÇADO
PUT_LINE
Esta procedure grava o texto presente no buffer para o arquivo. Esse comando já adiciona um
caracter de fim de linha ao texto.
A operação de gravação pode receber uma das seguintes exceções:
◊
Invalid_Filehandle – O handle do arquivo está inválido.
◊
Invalid_Operation – O arquivo não pode ser aberto ou operado como requisitado.
◊
Write_Error – Um erro de sistema operacional ocorreu durante a operação de gravação.
◊
CharsetMismatch – Este erro pode ocorrer se houvermos aberto o arquivo para Unicode e
houvermos usado esta rotina em seguida (devemos usar Put_Line_Nchar).
SQL> DECLARE
2
SAIDA
UTL_FILE.FILE_TYPE;
3
LINHA
VARCHAR2(2000);
4 BEGIN
5
SAIDA := UTL_FILE.FOPEN('C:\TEMP', 'SAIDA.SQL', 'A', 2000);
6
UTL_FILE.PUT_LINE(SAIDA, 'T');
7
UTL_FILE.PUT_LINE(SAIDA, 'E');
8
UTL_FILE.PUT_LINE(SAIDA, 'S');
9
UTL_FILE.PUT_LINE(SAIDA, 'T');
10
UTL_FILE.PUT_LINE(SAIDA, 'E');
11
UTL_FILE.PUT_LINE(SAIDA, 'S');
12
UTL_FILE.NEW_LINE(SAIDA);
13
UTL_FILE.FFLUSH(SAIDA);
14
UTL_FILE.FCLOSE(SAIDA);
15 EXCEPTION
16
WHEN UTL_FILE.WRITE_ERROR THEN
17
RAISE_APPLICATION_ERROR(-20003, 'Gravação com erro');
18
WHEN UTL_FILE.INVALID_FILEHANDLE THEN
19
RAISE_APPLICATION_ERROR(-20004, 'Arquivo não identificado');
20
WHEN UTL_FILE.INVALID_OPERATION THEN
21
RAISE_APPLICATION_ERROR(-20003, 'Operação incompatível');
22 END;
23 /
Procedimento PL/SQL concluído com sucesso.
L09_07
Acima utilizamos o mesmo exemplo criado para teste da rotina Put. Veremos a seguir o resultado
gravado no arquivo.
CAPÍTULO 9: O PACOTE UTL_FILE - 208
PL/SQL9I – BÁSICO E AVANÇADO
SQL> DECLARE
2
SAIDA
UTL_FILE.FILE_TYPE;
3
LINHA
VARCHAR2(2000);
4 BEGIN
5
SAIDA := UTL_FILE.FOPEN('C:\TEMP', 'SAIDA.SQL', 'R', 2000);
6
:MSG := '';
7
LOOP
8
UTL_FILE.GET_LINE(SAIDA, LINHA);
9
:MSG := :MSG || LINHA || CHR(10);
10
END LOOP;
11 EXCEPTION
12 WHEN NO_DATA_FOUND THEN
13
UTL_FILE.FCLOSE_ALL;
14 END;
15 /
Procedimento PL/SQL concluído com sucesso.
MSG
------------------------------------------------------------------GRAVAÇÃO EXEMPLO 2
GRAVAÇÃO EXEMPLO 2
TESTES
T
E
S
T
E
S
L09_08
Observe que cada uma das letras foi incluída em uma linha diferente e, ao fim do arquivo, foi anexado
mais um caracter de fim de linha, gerando uma linha em branco final.
PUT_LINE_NCHAR
Esta procedure é similar em funcionalidade e sintaxe à procedure Put_Line vista anteriormente. Deve
ser usada quando abrirmos o arquivo com Fopen_Nchar (arquivos Unicode).
CAPÍTULO 9: O PACOTE UTL_FILE - 209
PL/SQL9I – BÁSICO E AVANÇADO
FFLUSH
Esta procedure grava fisicamente todos os dados pendentes para aquele arquivo. O Fflush faz com
que os dados no buffer sejam descarregados em disco.
A operação de gravação pode receber uma das seguintes exceções:
◊
◊
◊
Invalid_Filehandle – O handle do arquivo está inválido.
Invalid_Operation – O arquivo não pode ser aberto ou operado como requisitado.
Write_Error – Um erro de sistema operacional ocorreu durante a operação de gravação.
PUTF
Essa procedure age de forma similar à procedure Put, com a diferença de que realiza formatações na
linha a ser incluída no arquivo.
Trabalha de forma semelhante a um “printf” (do C), porém com limitações. A string de formatação
pode conter qualquer texto.
Os caracteres ‘%s’ e ‘\n’ têm um significado especial neste comando:
◊
◊
%s – Marca um ponto para substituição, no texto, pelo próximo argumento da lista.
\n – É substituído (de acordo com a plataforma) pelo caracter de fim de linha.
Observe na sintaxe do comando que esta rotina recebe até sete parâmetros, sendo o primeiro o file
handle (já conhecido):
◊
<formato> – Indica uma string que pode conter os caracteres %s e \n.
◊
<arg> – De um a cinco argumentos para substituição no %s. Se houver mais %s que
argumentos, o local será ocupado por uma string de comprimento zero.
A operação de gravação pode receber uma das seguintes exceções:
◊
Invalid_Filehandle – O handle do arquivo está inválido.
◊
Invalid_Operation – O arquivo não pode ser aberto ou operado como requisitado.
◊
Write_Error – Um erro de sistema operacional ocorreu durante a operação de gravação.
◊
CharsetMismatch – Este erro pode ocorrer se houvermos aberto o arquivo para Unicode e
houvermos usado esta rotina em seguida (devemos usar Putf_Nchar).
CAPÍTULO 9: O PACOTE UTL_FILE - 210
PL/SQL9I – BÁSICO E AVANÇADO
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
DECLARE
SAIDA
UTL_FILE.FILE_TYPE;
LINHA
VARCHAR2(1022);
BEGIN
SAIDA := UTL_FILE.FOPEN('C:\TEMP', 'SAIDA.SQL', 'A');
UTL_FILE.PUTF(SAIDA, 'Neste exemplo %s serão enviados %s argumentos %s',
'A', '3', ':');
UTL_FILE.NEW_LINE(SAIDA,2);
UTL_FILE.PUTF(SAIDA, 'Já neste os argumentos são: %s, %s, \n %s, %s',
'primeiro', 'segundo', 'terceiro', 'quarto');
UTL_FILE.FFLUSH(SAIDA);
UTL_FILE.FCLOSE(SAIDA);
EXCEPTION
WHEN UTL_FILE.WRITE_ERROR THEN
RAISE_APPLICATION_ERROR(-20003, 'Gravação com erro');
WHEN UTL_FILE.INVALID_FILEHANDLE THEN
RAISE_APPLICATION_ERROR(-20004, 'Arquivo não identificado');
WHEN UTL_FILE.INVALID_OPERATION THEN
RAISE_APPLICATION_ERROR(-20003, 'Operação incompatível');
END;
/
L09_09
Procedimento PL/SQL concluído com sucesso.
No exemplo utilizamos o comando Putf duas vezes para geração de linhas formatadas.
No primeiro caso, fizemos a substituição de três argumentos no texto e utilizamos a rotina New_Line
para encerrar a linha impressa e ainda deixar uma linha em branco.
No segundo caso, quebramos a linha usando o símbolo \n para que os argumentos fossem gravados
em linhas distintas.
Observe que, ao término da execução, não definimos o caracter de fim de linha. Esta ação é feita,
automaticamente, quando o Fflush descarrega o restante do buffer.
CAPÍTULO 9: O PACOTE UTL_FILE - 211
PL/SQL9I – BÁSICO E AVANÇADO
SQL> DECLARE
2
SAIDA
UTL_FILE.FILE_TYPE;
3
LINHA
VARCHAR2(2000);
4 BEGIN
5
SAIDA := UTL_FILE.FOPEN('C:\TEMP', 'SAIDA.SQL', 'R', 2000);
6
:MSG := '';
7
LOOP
8
UTL_FILE.GET_LINE(SAIDA, LINHA);
9
:MSG := :MSG || LINHA || CHR(10);
10
END LOOP;
11 EXCEPTION
12 WHEN NO_DATA_FOUND THEN
13
UTL_FILE.FCLOSE_ALL;
14 END;
15 /
Procedimento PL/SQL concluído com sucesso.
MSG
------------------------------------------------------------------GRAVAÇÃO EXEMPLO 2
GRAVAÇÃO EXEMPLO 2
TESTES
T
E
S
T
E
S
Neste exemplo A serão enviados 3 argumentos :
Já neste os argumentos são: primeiro, segundo,
terceiro, quarto
L09_10
Acima apresentamos o resultado das sucessivas gravações no arquivo Saida.
PUTF_NCHAR
Esta procedure é similar em funcionalidade e sintaxe à procedure Putf vista anteriormente. Deve ser
usada quando abrirmos o arquivo com Fopen_Nchar (arquivos Unicode).
CAPÍTULO 9: O PACOTE UTL_FILE - 212
PL/SQL9I – BÁSICO E AVANÇADO
UM EXEMPLO DE LEITURA
Criaremos agora um exemplo completo de um programa que fará a carga de dados na tabela de
Projetos e Atividades (PrjAtv) a partir de um arquivo externo ao banco de dados. O layout do arquivo
de entrada é apresentado a seguir:
Informação
Coluna
Código do projeto
1a6
Código da atividade
7a9
Data de início
10 a 19 (dd/mm/yyyy)
Data de fim
20 a 29 (dd/mm/yyyy)
O conteúdo do arquivo de entrada é o seguinte:
AD311101010/01/199920/03/1999
AD311103011/02/199919/04/1999
PL210003012/01/199915/05/1999
PL210011013/02/199901/03/1999
OP200012014/03/199905/04/1999
OP200018015/04/199913/05/1999
MA211214016/04/199901/05/1999
MA211205017/02/199930/03/1999
SQL> DECLARE
2
ARQ
UTL_FILE.FILE_TYPE;
3
LINHA
VARCHAR2(50);
4
R1
PRJATV%ROWTYPE;
5 BEGIN
6
ARQ := UTL_FILE.FOPEN('C:\TEMP', 'CARGA.SQL', 'R');
7
LOOP
8
UTL_FILE.GET_LINE(ARQ, LINHA);
9
R1.CD_PROJ := SUBSTR(LINHA, 1, 6);
10
R1.CD_ATIV := TO_NUMBER(SUBSTR(LINHA, 7, 3));
11
R1.DT_INI := TO_DATE(SUBSTR(LINHA, 10, 10), 'DD/MM/YYYY');
12
R1.DT_FIM := TO_DATE(SUBSTR(LINHA, 20, 10), 'DD/MM/YYYY');
13
INSERT INTO PRJATV (CD_PROJ, CD_ATIV, DT_INI, DT_FIM)
14
VALUES (R1.CD_PROJ, R1.CD_ATIV, R1.DT_INI, R1.DT_FIM);
15
END LOOP;
16 EXCEPTION
17 WHEN NO_DATA_FOUND THEN
18
COMMIT;
19
UTL_FILE.FCLOSE_ALL;
20 END;
21 /
Procedimento PL/SQL concluído com sucesso.
L09_11
CAPÍTULO 9: O PACOTE UTL_FILE - 213
PL/SQL9I – BÁSICO E AVANÇADO
UM EXEMPLO DE GRAVAÇÃO
No exemplo a seguir, desejamos enviar uma carta para todos os funcionários aniversariantes do mês
a ser fornecido como parâmetro.
Para cada funcionário, será gerado um arquivo em separado de tal forma que possa ser transmitido
via e-mail.
Rio, xx/xx/xxxx
Prezado(a) Sr(a). xxxxxxxxxx,
Parabéns por seu aniversário dia xx/xx.
Seu presente será uma bonificação de R$ xxx.xxx,xx no
mês subseqüente.
Depto Pessoal.
Acima vemos o layout da carta que deve ser gerada individualmente por funcionário.
SQL> DECLARE
2
CARTA
UTL_FILE.FILE_TYPE;
3
HOJE
VARCHAR2(20) := TO_CHAR(SYSDATE, 'DD/MM/YYYY');
4
CURSOR C1(MES IN NUMBER) IS
5
SELECT NM_FUNC, LTRIM(TO_CHAR(VL_SAL/5, '999G990D00')) VL_SAL,
6
TO_CHAR(DT_NASC, 'DD/MM') DT_NASC FROM FUNC
7
WHERE TO_NUMBER(TO_CHAR(DT_NASC, 'MM')) = MES
8
ORDER BY CD_MAT;
9 BEGIN
10
FOR R1 IN C1(&MES) LOOP
11
CARTA := UTL_FILE.FOPEN('C:\TEMP', R1.NM_FUNC || '.TXT', 'W');
12
UTL_FILE.PUT
(CARTA, 'Rio, '||HOJE);
13
UTL_FILE.NEW_LINE(CARTA, 2);
14
UTL_FILE.PUT
(CARTA, 'Prezado(a) Sr(a). '||R1.NM_FUNC||',');
15
UTL_FILE.NEW_LINE(CARTA, 2);
16
UTL_FILE.PUT_LINE(CARTA, 'Parabéns por seu aniversário dia '
17
||R1.DT_NASC||'.');
18
UTL_FILE.PUT
(CARTA, 'Seu presente será uma bonificação de ');
19
UTL_FILE.PUTF
(CARTA, 'R$ %s no', R1.VL_SAL);
20
UTL_FILE.NEW_LINE(CARTA);
21
UTL_FILE.PUT
(CARTA, 'mês subseqüente.');
22
UTL_FILE.NEW_LINE(CARTA, 2);
23
UTL_FILE.PUT_LINE(CARTA, 'Depto Pessoal.');
24
UTL_FILE.FFLUSH (CARTA);
25
UTL_FILE.FCLOSE (CARTA);
26
END LOOP;
27 END;
28 /
Entre o valor para mes: 5
Procedimento PL/SQL concluído com sucesso.
L09_12
CAPÍTULO 9: O PACOTE UTL_FILE - 214
PL/SQL9I – BÁSICO E AVANÇADO
Na página anterior apresentamos o programa criado para a geração do arquivo conforme o layout
estabelecido.
Observe a seguir o resultado de um dos arquivos gerados.
Rio, 06/06/2003
Prezado(a) Sr(a). DILSON,
Parabéns por seu aniversário dia 17/05.
Seu presente será uma bonificação de R$ 3.059,87 no
mês subseqüente.
Depto Pessoal.
CAPÍTULO 9: O PACOTE UTL_FILE - 215
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 9:
1. Faça um programa que gere um arquivo contendo as seguintes colunas da tabela de funcionários:
cd_mat, nm_func, nm_sobrenome, dt_nasc, vl_sal.
2. Faça um programa que leia um arquivo e realize a carga na tabela de funcionários, completando os
dados de acordo com as seguintes especificações: Data de admissão (será igual à data do
cadastramento), Ramal (fixo igual a 1354), salário (fixo igual a 1.000,00) e cargo (fixo igual a 55).
O layout do arquivo de entrada é o seguinte:
Colunas 1 a 12 (nome), colunas 13 a 24 (sobrenome), colunas 25 a 32 (data de nascimento no
formato ddmmyyyy), colunas 33 a 34 (grau de instrução) e coluna 35 (sexo).
3. Gere um arquivo a partir da tabela Func com as seguintes informações: nome, sobrenome, data de
nascimento (formato dd/mm/yyyy), sexo e salário.
Os dados devem ser separados pelo símbolo # (não deve haver alinhamento de colunas).
Caso uma determinada coluna esteja sem valor (Null), deve ser feita substituição por: “**” para as
colunas alfanuméricas e -1 para as colunas numéricas.
Use o pacote UTL_FILE para gerar o arquivo.
4. Leia o arquivo gerado pelo exercício anterior e crie novas linhas na tabela Func. O número da
matrícula deve ser gerado com o uso de um Sequence de valor inicial 400.
Use o pacote UTL_FILE para ler o arquivo.
CAPÍTULO 9: O PACOTE UTL_FILE - 216
PL/SQL9I – BÁSICO E AVANÇADO
CAPÍTULO 10: O PACOTE DBMS_RANDOM
INTRODUÇÃO
Este pacote tem a finalidade de gerar números randômicos. Este gerador produz números inteiros de
8 dígitos.
Para que a geração tenha sucesso, devemos, como primeira etapa inicializar a geração fornecendo
um número de mais de 5 dígitos.
Ao término da etapa de obtenção dos números randômicos devemos encerrar a execução do pacote
com o uso da rotina Terminate.
Componente
Tipo
Descrição
Initialize
Procedure Inicializa o gerador. Deve-ser fornecer como parâmetro um número
com mais de 5 dígitos.
Seed
Procedure Reinicializa o processo de geração.
Random
Function
Terminate
Procedure Deve ser executada ao término do processo de geração.
Obtém o número randômico.
O primeiro exemplo aborda os requisitos necessários à geração randômica, utilizamos a rotina de
initialização e ao término a rotina de encerramento (conforme as regras).
SQL> DECLARE
2
NUMR
NUMBER;
3 BEGIN
4
NUMR := EXTRACT(MINUTE FROM CURRENT_TIMESTAMP) +
5
EXTRACT(SECOND FROM CURRENT_TIMESTAMP);
6
DBMS_RANDOM.INITIALIZE (NUMR);
7
:MSG := 'NÚMERO INICIAL = '||NUMR;
8
:MSG := :MSG ||CHR(10)||'NÚMEROS GERADOS = ';
9
FOR I IN 1..10 LOOP
10
NUMR := DBMS_RANDOM.RANDOM;
11
:MSG := :MSG||NUMR||' ';
12
END LOOP;
13
DBMS_RANDOM.TERMINATE;
14 END;
15 /
Procedimento PL/SQL concluído com sucesso.
MSG
---------------------------------------------------------------------NÚMERO INICIAL = 104,0000008
NÚMEROS GERADOS = -1564617297 -680619911 690221884 -1836843921 -159793
7587 2110904121 -1417432222 1159762311 -1022650154 411421461
L10_01
CAPÍTULO 10: O PACOTE DBMS_RANDOM - 217
PL/SQL9I – BÁSICO E AVANÇADO
Observe que os números gerados tanto podem ser positivos quanto negativos. Você pode usar a
função ABS para já obter os números somente positivos após a geração, se necessário.
SQL> DECLARE
2
NUMR
NUMBER;
3 BEGIN
4
NUMR := EXTRACT(MINUTE FROM CURRENT_TIMESTAMP) +
5
EXTRACT(SECOND FROM CURRENT_TIMESTAMP);
6
:MSG := 'NÚMERO INICIAL = '||NUMR;
7
:MSG := :MSG ||CHR(10)||'NÚMEROS GERADOS = ';
8
FOR I IN 1..5 LOOP
9
NUMR := ABS(DBMS_RANDOM.RANDOM);
10
:MSG := :MSG||NUMR||' ';
11
END LOOP;
12
DBMS_RANDOM.TERMINATE;
13 END;
14 /
Procedimento PL/SQL concluído com sucesso.
MSG
---------------------------------------------------------------------NÚMERO INICIAL = 58,000000048
NÚMEROS GERADOS = 1718111779 1705162795 413925790 1904256531 45803085
Neste exemplo usamos a função ABS para que o resultado fosse sempre positivo.
Observe também que apesar de não termos executado a rotina Initialize não encontramos problemas
na geração dos números randômicos, no entanto, isto pode não acontecer na sua versão do software.
O primeiro exemplo é mais seguro de uso, em todas as circunstâncias.
CAPÍTULO 10: O PACOTE DBMS_RANDOM - 218
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 10:
1. Faça uma rotina que, aleatoriamente, escolha 3 funcionários para premiação de Natal da empresa.
2. Sabendo-se que uma roleta contém 25 números vermelhos (ímpares) e 25 números pretos (pares).
Determine o número ganhador e se ele é vermelho ou preto.
CAPÍTULO 10: O PACOTE DBMS_RANDOM - 219
PL/SQL9I – BÁSICO E AVANÇADO
CAPÍTULO 11: VARIÁVEIS CURSOR
CONCEITO
Uma variável cursor (como um cursor) aponta para a linha corrente da “tabela” resultante gerada por
uma query que retorne um número indefinido de linhas.
Uma variável cursor, porém, possui uma diferença marcante em relação a um cursor: não está
associada a uma query específica. Pode ser associada a diversas queries ao longo de sua existência
(uma de cada vez). Elas podem, ainda, ser definidas como parâmetros de procedimentos.
VANTAGENS
A principal vantagem é a possibilidade de passar o resultado de uma query entre um PL/SQL
armazenado na base e as várias aplicações-cliente. Nem o PL/SQL nem qualquer das aplicaçõescliente duplicam ou recebem a query resultante.
Eles simplesmente compartilham um ponteiro para a área de trabalho resultante da query. Por
exemplo, um cliente OCI, uma aplicação Oracle Forms e o Oracle Server podem fazer referência à
mesma área de trabalho.
DEFINIÇÃO
A criação de uma variável cursor é similar à declaração de outros tipos (Index-By Table, Varray,
Record). Inicialmente, devemos definir o tipo e posteriormente as variáveis com aquele tipo.
SINTAXE
TYPE
Tipo
IS REF CURSOR
RETURN
Onde:
Tipo de retorno
◊
Tipo - Indica o tipo a ser usado nas declarações subseqüentes.
◊
Tipo de retorno - Deve representar um registro (record) ou uma row em uma tabela da
base de dados.
Na Sintaxe temos o formato para definição de um tipo cursor. O <tipo de retorno> que aparece na
sintaxe representa o layout da área de retorno, podendo ser definido como um record ou uma row em
uma tabela do banco de dados.
Observe que a cláusula Return é opcional, indicando que o tipo pode determinar ou não o layout do
retorno.
CAPÍTULO 11: VARIÁVEIS CURSOR - 220
PL/SQL9I – BÁSICO E AVANÇADO
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
DECLARE
TYPE CTYPE_FUNC
IS REF CURSOR RETURN FUNC%ROWTYPE;
TYPE CTYPE_LIVRE
IS REF CURSOR;
VFUNC
CTYPE_FUNC;
VLIVRE
CTYPE_LIVRE;
BEGIN
OPEN VFUNC FOR SELECT * FROM FUNC
WHERE CD_DEPTO = 'D11';
OPEN VLIVRE FOR SELECT CD_MAT, NM_FUNC FROM FUNC
WHERE CD_DEPTO = 'D11';
OPEN VLIVRE FOR SELECT * FROM DEPTO;
END;
/
L11_01
Procedimento PL/SQL concluído com sucesso.
No exemplo definimos dois tipos e duas variáveis cursor. No primeiro tipo definido, declaramos o
layout esperado para o retorno. No segundo, deixamos sem especificação o layout da área de retorno.
A tempo de Open da variável cursor é que determinamos a query a ser associada à variável.
Observe que para a variável baseada no primeiro tipo, o comando Select associado, obrigatoriamente,
deve selecionar todas as coluna da tabela Func uma vez que o tipo determina que o layout da área de
retorno corresponde a uma row da tabela Func.
Para a variável baseada no segundo tipo, o layout é livre e definido apenas no momento da
associação do comando Select.
CAPÍTULO 11: VARIÁVEIS CURSOR - 221
PL/SQL9I – BÁSICO E AVANÇADO
MANIPULANDO VARIÁVEIS CURSOR
Neste item veremos a sintaxe dos comandos de PL/SQL para uso com variáveis cursor.
SINTAXE
Na Sintaxe vemos os três comandos para manipulação de variáveis do tipo cursor. Para variáveis
cursor não temos o comando Cursor Loop.
SQL> DECLARE
2
TYPE CTYPE_FUNC
IS REF CURSOR RETURN FUNC%ROWTYPE;
3
TYPE CTYPE_LIVRE
IS REF CURSOR;
4
TYPE REG
IS RECORD (MAT
FUNC.CD_MAT%TYPE,
5
NOME
FUNC.NM_FUNC%TYPE);
6
VFUNC
CTYPE_FUNC;
7
VLIVRE
CTYPE_LIVRE;
8
OPCAO
NUMBER := '&OPCAO';
9
RET_FUNC
FUNC%ROWTYPE;
10
RET_DEPTO
DEPTO%ROWTYPE;
11
RET_NOME
REG;
12 BEGIN
13
IF OPCAO = 1 THEN
14
OPEN VFUNC FOR SELECT * FROM FUNC WHERE CD_DEPTO = 'D11';
15
FETCH VFUNC INTO RET_FUNC;
16
DBMS_OUTPUT.PUT_LINE('PRIMEIRA MATRÍCULA = '||RET_FUNC.CD_MAT);
17
ELSIF OPCAO = 2 THEN
18
OPEN VLIVRE FOR SELECT CD_MAT, NM_FUNC FROM FUNC
19
WHERE CD_DEPTO = 'D11';
20
FETCH VLIVRE INTO RET_NOME;
21
DBMS_OUTPUT.PUT_LINE('PRIMEIRO NOME = '||RET_NOME.NOME);
22
ELSIF OPCAO = 3 THEN
23
OPEN VLIVRE FOR SELECT * FROM DEPTO;
24
FETCH VLIVRE INTO RET_DEPTO;
25
DBMS_OUTPUT.PUT_LINE('PRIMEIRO DEPTO = '||RET_DEPTO.CD_DEPTO);
26
END IF;
27 END;
28 /
Entre o valor para opcao: 2
PRIMEIRO NOME = IRACY
L11_02
CAPÍTULO 11: VARIÁVEIS CURSOR - 222
PL/SQL9I – BÁSICO E AVANÇADO
Na página anterior vemos o exemplo completo do programa iniciado anteriormente. De acordo com o
parâmetro recebido, abrimos uma variável cursor específica e realizamos a leitura da primeira linha
obtida.
Veremos a seguir alguns exemplos simples de utilização das variáveis cursor em subprogramas,
packages, passagem de parâmetros.
Inicialmente, definimos tipos em um pacote e em seguida podemos, livremente, utilizá-los dentro de
subprogramas ou em parâmetros.
SQL> CREATE OR REPLACE PACKAGE GLOBAL AS
2
TYPE CTYPE_FUNC
IS REF CURSOR RETURN FUNC%ROWTYPE;
3
TYPE CTYPE_LIVRE IS REF CURSOR;
4 END GLOBAL;
5 /
L11_03
Pacote criado.
O exemplo cria um pacote Global contendo dois tipos: o primeiro, já com layout de retorno definido. O
segundo, totalmente livre.
Em um pacote podemos definir o tipo cursor, não a variável deste tipo. O que criamos na definição de
uma variável cursor é um ponteiro e não um item, não existindo, desta forma, um estado persistente, o
que significa que não pode ser definida em pacotes.
SQL> CREATE OR REPLACE
2 PROCEDURE LERCURSOR(VCURSOR IN OUT GLOBAL.CTYPE_LIVRE) IS
3
AREA
DEPTO%ROWTYPE;
4 BEGIN
5
LOOP
6
FETCH VCURSOR INTO AREA;
7
EXIT WHEN VCURSOR%NOTFOUND;
8
DBMS_OUTPUT.PUT_LINE(AREA.CD_DEPTO);
9
END LOOP;
10 END;
11 /
Procedimento criado.
SQL> CREATE OR REPLACE
2 PROCEDURE FECHACURSOR(VCURSOR IN OUT GLOBAL.CTYPE_LIVRE) IS
3 BEGIN
4
CLOSE VCURSOR;
5 END;
6 /
Procedimento criado.
L11_04
CAPÍTULO 11: VARIÁVEIS CURSOR - 223
PL/SQL9I – BÁSICO E AVANÇADO
O exemplo apresenta a criação de dois procedimentos independentes. O primeiro recebe como
parâmetro uma variável cursor e lê todas as linhas retornadas por ela. O segundo recebe como
parâmetro uma variável cursor e fecha esta variável.
Observe que para que pudéssemos detectar o fim do processo de leitura utilizamos o atributo
Notfound com a variável cursor. Todos os atributos de cursor estão autorizados para utilização com
variável cursor.
SQL>
2
3
4
5
6
7
8
CREATE OR REPLACE PROCEDURE ABRECURSOR IS
VCURSOR
GLOBAL.CTYPE_LIVRE;
BEGIN
OPEN VCURSOR FOR SELECT * FROM DEPTO;
LERCURSOR(VCURSOR);
FECHACURSOR(VCURSOR);
END;
/
Procedimento criado.
L11_05
No exemplo temos a definição de um procedimento que declara uma variável cursor e passa essa
variável para os demais programas.
SQL> EXECUTE ABRECURSOR;
A00
B01
C01
D01
D11
D21
E01
E11
E21
A01
A02
D12
L11_06
Procedimento PL/SQL concluído com sucesso.
Acima vemos o resultado da execução destes programas.
No exemplo anterior definimos uma variável REFCURSOR no SQL*Plus. Este tipo do SQL*Plus
permite que declaremos variáveis cursor com o objetivo de retornar os resultados de uma query
armazenada em subprogramas no banco de dados.
CAPÍTULO 11: VARIÁVEIS CURSOR - 224
PL/SQL9I – BÁSICO E AVANÇADO
SQL> SET AUTOPRINT ON
SQL> VARIABLE VCURSOR REFCURSOR
SQL> CREATE OR REPLACE
2 PROCEDURE ABRE(PDEPTO IN VARCHAR2, PCURSOR IN OUT SYS_REFCURSOR)
IS
3 BEGIN
4
OPEN PCURSOR FOR SELECT * FROM FUNC
5
WHERE CD_DEPTO = PDEPTO;
6 END;
7 /
Procedimento criado.
SQL> EXECUTE ABRE('B01', :VCURSOR);
Procedimento PL/SQL concluído com sucesso.
CD_MAT NM_FUNC
NM_SOBRENOME CD_
NR_RAMAL DT_ADM
---------- ------------ ------------ --- ---------- -------NR_CARGO
NR_GIT I DT_NASC
VL_SAL
---------- ---------- - -------- ---------NM_FOTO
---------------------------------------------------------------------20 MIGUEL
TEIXEIRA
B01
3476 10/10/93
61
18 M 02/02/68
20363,23
L11_07
No exemplo acima declaramos duas variáveis cursor, abrimos uma delas e com o comando de
atribuição apontamos a segunda variável para a mesma área da primeira.
Observe também que usamos uma variável de sistema chamada SYS_REFCURSOR.
Ela representa uma declaração do tipo “type sys_refcursor is ref cursor”, sem definição de retono.
Podemos usá-la para passagem de parâmetro sem necessidade de declarar um tipo cursor.
SQL> DECLARE
2
V1
SYS_REFCURSOR;
3
V2
GLOBAL.CTYPE_FUNC;
4
AREA
FUNC%ROWTYPE;
5 BEGIN
6
OPEN V1 FOR SELECT * FROM FUNC WHERE CD_DEPTO = 'D11';
7
V2 := V1;
8
FETCH V2 INTO AREA;
9
DBMS_OUTPUT.PUT_LINE('MAT = '||AREA.CD_MAT);
10
FETCH V1 INTO AREA;
11
DBMS_OUTPUT.PUT_LINE('MAT = '||AREA.CD_MAT);
12
CLOSE V2;
13 END;
14 /
MAT = 60
MAT = 150
L11_08
Procedimento PL/SQL concluído com sucesso.
CAPÍTULO 11: VARIÁVEIS CURSOR - 225
PL/SQL9I – BÁSICO E AVANÇADO
Quando declaramos uma variável cursor como parâmetro formal de um subprograma que obtém
linhas desta variável, devemos especificar o parâmetro no modo IN (ou IN OUT).
Se o subprograma também abrir a variável cursor, devemos especificar o modo IN OUT.
RESTRIÇÕES PARA VARIÁVEIS CURSOR
As restrições apresentadas a seguir poderão ser futuramente retiradas:
◊
Não podemos declarar variáveis cursor em pacotes, apenas os tipos podem ser definidos.
◊
Subprogramas remotos em outros servidores não podem aceitar os valores de variáveis
cursor. Assim, não podemos usar chamadas a programas remotos para passar variáveis cursor
entre servidores.
◊
A query associada com uma variável cursor não pode ser do tipo For Update.
◊
Não podemos usar operadores de comparação para testar variáveis cursor para igualdade ou
desigualdade.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
IF
DECLARE
V1
SYS_REFCURSOR;
V2
GLOBAL.CTYPE_FUNC;
AREA
FUNC%ROWTYPE;
BEGIN
OPEN V1 FOR SELECT * FROM FUNC WHERE CD_DEPTO = 'D11';
OPEN V2 FOR SELECT * FROM FUNC;
IF V1 = V2 THEN
DBMS_OUTPUT.PUT_LINE('IGUAIS');
END IF;
IF V1 IS NULL THEN
DBMS_OUTPUT.PUT_LINE('V1 NULL');
ELSE
DBMS_OUTPUT.PUT_LINE('V1 NÃO É NULL');
END IF;
END;
/
V1 = V2 THEN
*
ERRO na linha 8:
ORA-06550: linha 8, coluna 9:
PLS-00306: número incorreto de tipos de argumentos na chamada para
'='
ORA-06550: linha 8, coluna 3:
PL/SQL: Statement ignored
L11_09
CAPÍTULO 11: VARIÁVEIS CURSOR - 226
PL/SQL9I – BÁSICO E AVANÇADO
SQL> DECLARE
2
V1
SYS_REFCURSOR;
3
V2
GLOBAL.CTYPE_FUNC;
4
AREA
FUNC%ROWTYPE;
5 BEGIN
6
OPEN V1 FOR SELECT * FROM FUNC WHERE CD_DEPTO = 'D11';
7
OPEN V2 FOR SELECT * FROM FUNC;
8
IF V1 IS NULL THEN
9
DBMS_OUTPUT.PUT_LINE('V1 NULL');
10
ELSE
11
DBMS_OUTPUT.PUT_LINE('V1 NÃO É NULL');
12
END IF;
13 END;
14 /
V1 NÃO É NULL
Procedimento PL/SQL concluído com sucesso.
L11_09
◊
Não podemos associar Null a variáveis cursor.
◊
Não podemos definir variáveis cursor como colunas de uma tabela do banco de dados.
◊
Não podemos definir variáveis cursor como elemento de uma tabela PL/SQL (Index_By
Table, Nested Table ou Varray).
◊
Não podemos usar cursores no lugar de variáveis cursor e vice-versa. Por exemplo, não
podemos usar variáveis cursor em comandos For Loop para cursor.
CAPÍTULO 11: VARIÁVEIS CURSOR - 227
PL/SQL9I – BÁSICO E AVANÇADO
EXPRESSÕES CURSOR
Uma expressão CURSOR retorna um cursor embutido. Cada linha no conjunto resultante poderá ser
composta de dos valores esperados das expressões comuns e, ainda, cursores, resultantes das
expressões do tipo CURSOR que apontam para as subqueries envolvidas na query principal.
A PL/SQL suporta queries com expressões CURSOR como parte da declaração de um cursor, e
variáveis cursor. Também podemos usar estas expressões em SQL dinâmico.
A sintaxe CURSOR (<subquery>) indica que será retornado da query “pai” um ponteiro que fará a
manipulação da subquery.
O cursor “embutido” é aberto quando a linha contendo o cursor é obtida, na query “pai”. O cursor
“embutido” será fechado somente quando for explicitamente “closed” ou quando o cursor “pai” for
reexecutado ou o cursor “pai” for fechado ou o cursor “pai” for cancelado ou ocorrer um erro durante a
operação de fetch de um dos “pais” do cursor.
RESTRIÇÕES RELATIVAS A EXPRESSÕES CURSOR
Não podemos usar expressões CURSOR com um cursor implícito (SQL).
As expressões CURSOR podem aparecer somente:
◊
Em um comando SELECT que não está embutido em quaisquer outras expressões query,
exceto quando for uma subquery da própria expressão cursor.
◊
Como argumentos para Table Functions, na cláusula From de um comando Select.
◊
Não podem aparecer e declarações de views.
◊
Não podemos efetuar operações de Bind ou Execute em expressões cursores.
CAPÍTULO 11: VARIÁVEIS CURSOR - 228
PL/SQL9I – BÁSICO E AVANÇADO
SQL> SET SERVEROUT ON
SQL> DECLARE
2
CURSOR C1
IS SELECT CD_MAT, NM_FUNC, CD_DEPTO,
3
CURSOR(SELECT NM_DEPTO FROM DEPTO D
4
WHERE F.CD_DEPTO = D.CD_DEPTO) CDEPTO,
5
CURSOR(SELECT NM_PROJ FROM PROJ P
6
WHERE F.CD_MAT = P.CD_RESP) CPROJ
7
FROM FUNC F
8
WHERE CD_MAT = 60;
9
TYPE TCURSOR IS REF CURSOR;
10
VDEPTO
TCURSOR;
11
VPROJ
TCURSOR;
12
MAT
FUNC.CD_MAT%TYPE;
13
NOME
FUNC.NM_FUNC%TYPE;
14
DEP
FUNC.CD_DEPTO%TYPE;
15
NMDEPTO
DEPTO.NM_DEPTO%TYPE;
16
NMPROJ
PROJ.NM_PROJ%TYPE;
17 BEGIN
18
OPEN C1;
19
LOOP
20
FETCH C1 INTO MAT, NOME, DEP, VDEPTO, VPROJ;
21
EXIT WHEN C1%NOTFOUND;
22
DBMS_OUTPUT.PUT_LINE ('MATRICULA = '||MAT||' NOME = '||NOME||
23
' DEPTO = '||DEP);
24
LOOP
25
FETCH VDEPTO INTO NMDEPTO;
26
EXIT WHEN VDEPTO%NOTFOUND;
27
DBMS_OUTPUT.PUT_LINE ('DEPARTAMENTO = '||NMDEPTO);
28
END LOOP;
29
LOOP
30
FETCH VPROJ INTO NMPROJ;
31
EXIT WHEN VPROJ%NOTFOUND;
32
DBMS_OUTPUT.PUT_LINE ('PROJETOS = '||NMPROJ);
33
END LOOP;
34
END LOOP;
35 END;
36 /
MATRICULA = 60 NOME = IRACY DEPTO = D11
DEPARTAMENTO = GERENCIA DE SISTEMAS COMERCIAIS
PROJETOS = PROGRAMACAO
PROJETOS = LEVANTAMENTO
Procedimento PL/SQL concluído com sucesso.
Neste exemplo incluímos duas expressões do tipo CURSOR, uma para obter o departamento do
funcionário e outra para obter os projetos que este o funcionário em questão coordenava.
Não houve necessidade de fecharmos as variáveis cursor porque uma nova linha da tabela FUNC era
lida e um novo ponteiro gerado.
CAPÍTULO 11: VARIÁVEIS CURSOR - 229
PL/SQL9I – BÁSICO E AVANÇADO
SQL> DECLARE
2
CURSOR C1
IS SELECT CD_MAT, NM_FUNC, CD_DEPTO,
3
cURSOR(SELECT NM_PROJ,
4
CURSOR(SELECT NM_DEPTO FROM DEPTO D
5
WHERE P.CD_DEPTO = D.CD_DEPTO) CDEPTO
6
FROM PROJ P
7
WHERE F.CD_MAT = P.CD_RESP) CPROJ
8
FROM FUNC F
9
WHERE CD_MAT = 60;
10 -11
TYPE TCURSOR IS REF CURSOR;
12
VDEPTO
TCURSOR;
13
VPROJ
TCURSOR;
14
MAT
FUNC.CD_MAT%TYPE;
15
NOME
FUNC.NM_FUNC%TYPE;
16
DEP
FUNC.CD_DEPTO%TYPE;
17
NMDEPTO
DEPTO.NM_DEPTO%TYPE;
18
NMPROJ
PROJ.NM_PROJ%TYPE;
19 BEGIN
20
OPEN C1;
21
LOOP
22
FETCH C1 INTO MAT, NOME, DEP, VPROJ;
23
EXIT WHEN C1%NOTFOUND;
24
DBMS_OUTPUT.PUT_LINE ('MATRICULA = '||MAT||' NOME = '||NOME||
25
' DEPTO = '||DEP);
26
LOOP
27
FETCH VPROJ INTO NMPROJ, VDEPTO;
28
EXIT WHEN VPROJ%NOTFOUND;
29
DBMS_OUTPUT.PUT_LINE ('PROJETOS = '||NMPROJ);
30
LOOP
31
FETCH VDEPTO INTO NMDEPTO;
32
EXIT WHEN VDEPTO%NOTFOUND;
33
DBMS_OUTPUT.PUT_LINE ('DEPARTAMENTO = '||NMDEPTO);
34
END LOOP;
35
END LOOP;
36
END LOOP;
37 END;
38 /
MATRICULA = 60 NOME = IRACY DEPTO = D11
PROJETOS = PROGRAMACAO
DEPARTAMENTO = GERENCIA DE SISTEMAS COMERCIAIS
PROJETOS = LEVANTAMENTO
DEPARTAMENTO = GERENCIA DE SISTEMAS COMERCIAIS
L11_11
Procedimento PL/SQL concluído com sucesso.
Neste exemplo embutimos uma expressão cursor em outra expressão cursor, observe que para cada
funcionário, para cada linha de projeto foi obtido o nome do departamento associado ao projeto (no
exemplo anterior obtínhamos o departamento do funcionário).
Observe que a mudança na definição da expressão CURSOR também acompanhou uma modificação
na lógica: o loop de departamento está embutido no loop de projeto.
CAPÍTULO 11: VARIÁVEIS CURSOR - 230
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 11:
1. Faça um programa que receba como parâmetro o tipo de informação a ser apresentada:
A) Nome e sobrenome.
B) Matrícula, cargo e salário.
C) Nome, grau de instrução e data de nascimento.
Use uma única variável cursor e DBMS_OUTPUT para mostrar os resultados.
2. Faça três programas, cada um deles deverá receber uma variável cursor e abri-la para as situações
apresentadas no Exercício 1.
3. Faça um programa que declare uma variável cursor e, de acordo com o parâmetro recebido, acione
cada um dos programas do Exercício 2 e mostre as informações selecionadas.
CAPÍTULO 11: VARIÁVEIS CURSOR - 231
PL/SQL9I – BÁSICO E AVANÇADO
CAPÍTULO 12: SQL DINÂMICO EM PL/SQL
CONCEITO
De um modo geral, construímos programas em PL/SQL nos quais incluímos comandos de SQL
estáticos, isto é, comandos que são conhecidos e passíveis de serem compilados durante o
Alunoolvimento do programa.
Eventualmente, temos aplicações que necessitam processar comandos de SQL que só se completam
a tempo de execução.
Por exemplo: imagine que um mesmo programa faça acesso a tabelas residentes em servidores
diferentes (todas com o mesmo layout), sendo a indicação do servidor informada como parâmetro da
rotina.
A tempo de execução, o programa tomaria a decisão de estabelecer acesso à tabela desejada. Uma
forma de resolver esta situação seria a construção de um comando SQL que somente fosse
“parseado” a tempo de execução, de tal forma que o programa considerasse a existência de apenas
uma tabela. Este tipo de construção é chamado de SQL dinâmico.
CAPÍTULO 12: SQL DINÂMICO EM PL/SQL - 232
PL/SQL9I – BÁSICO E AVANÇADO
USANDO SQL DINÂMICO
Comandos de SQL dinâmico são armazenados em strings construídas pelo programa ao tempo de
execução. Estas strings devem conter um comando SQL ou um bloco de PL/SQL válidos. Podem,
ainda, conter argumentos (Bind) a serem supridos durante a execução.
Observe os trechos de programa apresentados abaixo. A PL/SQL não faz distinção entre os dois.
'DELETE FROM func WHERE vl_sal > :w_sal AND nr_cargo < :w_cargo'
'DELETE FROM func WHERE vl_sal > :sal AND nr_cargo < :cargo'
Para processarmos comandos de SQL, tais como Insert, Update, Delete ou blocos de PL/SQL,
usaremos o comando Execute Immediate.
Para processarmos SQL Select, usaremos os comandos Open-For, Fetch e Close.
O COMANDO EXECUTE IMMEDIATE
Na sintaxe a seguir, apresentamos a sintaxe do comando Execute Immediate.
SINTAXE
EXECUTE IMMEDIATE <string com comando dinâmico>
[INTO {<variável>[, <variável>]... | <registro>}]
[USING [IN | OUT | IN OUT] <argumento Bind>
[, [IN | OUT | IN OUT] <argumento Bind>]...];
Onde:
◊
<string dinâmica> – representa um comando SQL qualquer, exceto queries que retornem
múltiplas linhas (sem terminação) ou um bloco de PL/SQL (com terminação). Esta string pode
conter referências a argumentos Bind. Não podemos, porém, usar argumentos Bind para substituir
nomes de objetos dentro da string.
◊
<variável> – É uma variável que armazena o valor de uma coluna selecionada.
◊
<record> – Corresponde a um Record definido pelo usuário ou um %Rowtype que
armazenará uma linha (row) selecionada.
◊
<bind> – É uma expressão cujo valor é passado dinamicamente para o comando SQL ou
bloco de PL/SQL (o hint NoCopy não é permitido em um EXECUTE IMMEDIATE). Se não for
especificado o modo do parâmetro, o default é IN.
SQL dinâmico suporta todos os tipos de dados de SQL (coleções, Lobs, Refs, objetos), mas não
suporta tipos específicos de PL/SQL (boleanos e tabelas Index-By). Desta forma, tanto <variável>
quanto <argumento Bind> devem obedecer a esta regra.
CAPÍTULO 12: SQL DINÂMICO EM PL/SQL - 233
PL/SQL9I – BÁSICO E AVANÇADO
SQL> DECLARE
2
TEXTO
VARCHAR2(1000) := '&VALOR';
3 BEGIN
4
EXECUTE IMMEDIATE TEXTO USING SYSDATE;
5 END;
6 /
Entre o valor para valor: INSERT INTO FUNC(CD_MAT, DT_ADM) VALUES (3, :DT)
L12_01
Procedimento PL/SQL concluído com sucesso.
No exemplo ACIM usamos a expressão SYSDATE como argumento para a variável Bind :DT incluída
no texto do comando SQL.
Todo o comando foi passado, dinamicamente, durante a execução do programa. O comando foi
preenchido na variável TEXTO.
A tempo de execução, o trecho de comando foi analisado (Parse) e executado (Execute).
A utilização de variáveis Bind dentro do texto do comando não é obrigatória, poderíamos ter
concatenado o valor na string dentro da lógica do programa principal, no entanto esta forma de uso é
mais flexível pois o tipo da variável é preservado.
SQL> DECLARE
2
TEXTO
VARCHAR2(1000) := '&VALOR';
3
GRAU
NUMBER := 2;
4 BEGIN
5
EXECUTE IMMEDIATE TEXTO USING GRAU;
6 END;
7 /
Entre o valor para valor: BEGIN UPDATE FUNC SET NR_GIT=:G WHERE CD_MAT=3; END;
Procedimento PL/SQL concluído com sucesso.
L12_02
Neste segundo exemplo, a execução dinâmica foi de um trecho de PL/SQL. Nos utilizamos da variável
local GRAU como argumento para a variável Bind :G referenciada no comando.
CAPÍTULO 12: SQL DINÂMICO EM PL/SQL - 234
PL/SQL9I – BÁSICO E AVANÇADO
SQL> VARIABLE MSG VARCHAR2(1000)
SQL> SET AUTOPRINT ON
SQL> DECLARE
2
TEXTO
VARCHAR2(1000) := '&VALOR';
3 BEGIN
4
EXECUTE IMMEDIATE TEXTO USING SYSDATE, OUT :MSG;
5 END;
6 /
Entre o valor para valor: INSERT INTO FUNC(CD_MAT, DT_ADM) VALUES
(4, :DT) RETURNING ROWID INTO :R
Procedimento PL/SQL concluído com sucesso.
MSG
------------------------------------------------------------------AAAH2LAAIAAAAAyAAL
L12_03
No exemplo acima o trecho de programa executado retornou um valor (Rowid). Sendo assim, a
sintaxe usada para os argumentos indicaram que o primeiro referia-se a um argumento de entrada
(Sysdate → :DT) e o segundo a um argumento de saída (:R → :MSG).
Observe que utilizamos uma variável do ambiente SQL*Plus (msg) para receber o Rowid. Não foi
necessário que transferíssemos a informação para uma variável local do programa.
CAPÍTULO 12: SQL DINÂMICO EM PL/SQL - 235
PL/SQL9I – BÁSICO E AVANÇADO
OS COMANDOS OPEN-FOR, FETCH E CLOSE
Para efetuarmos o processamento de uma consulta, que retorne diversas linhas, de forma dinâmica,
deveremos utilizar três comandos: Open uma variável cursor For uma consulta de múltiplas linhas,
então Fetch as linhas para variáveis locais da aplicação e, finalmente, quando todas as linhas tiverem
sido processadas, Close a variável cursor.
SINTAXE
OPEN {<variável cursor> | :<host variável cursor>}
FOR <string com comando SQL SELECT dinâmico>
[USING <argumento Bind>[, <argumento Bind>]...];
Onde:
◊
<variável cursor> – Corresponde a uma variável definida a partir de um tipo (Type) sem
retorno (Return) especificado.
◊
<host variável cursor> – É uma variável definida em ambiente Host para o PLSQL (por
exemplo OCI).
◊
<string dinâmica> – É uma string que contém um comando Select que retorna diversas
linhas.
◊
< Bind> – É uma expressão cujo valor é passado dinamicamente para o comando SQL.
Qualquer argumento Bind na consulta é avaliado somente quando o cursor é aberto. Desta forma,
para obtermos dados do cursor usando diferentes valores de argumento, devemos reabrir a variável
cursor com os argumentos Bind contendo os novos valores desejados.
O comando Fetch retorna uma linha referente ao resultado da consulta de múltiplas linhas, associando
os valores presentes na lista de seleção às variáveis correspondentes na cláusula INTO,
incrementando o atributo %Rowcount e avançando o cursor para a próxima linha.
SINTAXE
FETCH {<variável cursor> | :<host variável cursor>}
INTO {<variável>[, <variável>]... | <registro>};
Onde:
◊
<variável cursor> – Corresponde à variável usada no comando Open.
◊
<host variável cursor> – É uma variável definida em ambiente Host para o PLSQL (por
exemplo, OCI), usada no comando Open.
◊
<variável> – É uma variável que armazena o valor de uma coluna selecionada.
◊
<registro> – Corresponde a um Record definido pelo usuário ou um %Rowtype que
armazenará uma linha (row) selecionada.
CAPÍTULO 12: SQL DINÂMICO EM PL/SQL - 236
PL/SQL9I – BÁSICO E AVANÇADO
Para cada valor retornado pela consulta (associado à variável cursor), deve existir uma variável de tipo
correspondente compatível com o valor a ser-lhe atribuído.
SINTAXE
CLOSE {<variável cursor>| :<host variável cursor> };
Onde:
◊
<variável cursor> – Corresponde à variável usada no comando Open.
◊
<host variável cursor> – É uma variável definida em ambiente Host para o PLSQL (por
exemplo, OCI), usada no comando Open.
O comando Close desabilita a variável cursor. Após esta ação, o conjunto resultado associado a ela
fica indefinido.
SQL> DECLARE
2
TEXTO
VARCHAR2(1000) := '&VALOR';
3
TYPE RC IS REF CURSOR;
4
C1
RC;
5 BEGIN
6
OPEN C1 FOR TEXTO USING SYSDATE;
7
:MSG := 'LINHAS OBTIDAS: ';
8
LOOP
9
FETCH C1 INTO TEXTO;
10
EXIT WHEN C1%NOTFOUND;
11
:MSG := :MSG || TEXTO || '; ';
12
END LOOP;
13
CLOSE C1;
14 END;
15 /
Entre o valor para valor: SELECT NM_FUNC FROM FUNC WHERE DT_ADM > (:DT 3000)
Procedimento PL/SQL concluído com sucesso.
MSG
---------------------------------------------------------------------LINHAS OBTIDAS: SANDRA; ELIANE; ; DAVI; JOANA; JOAQUIM; DANIEL; SILVIA
; WI_SON; DILSON; ;
L12_04
No exemplo acima usamos um comando Select que recebe uma variável Bind como parâmetro e para
a qual passamos a expressão Sysdate. Para cada linha obtida, efetuamos a concatenação na variável
do SQL*Plus chamada MSG.
CAPÍTULO 12: SQL DINÂMICO EM PL/SQL - 237
PL/SQL9I – BÁSICO E AVANÇADO
SQL> DECLARE
2
COL
VARCHAR2(1000) := '&COLUNA';
3
TAB
VARCHAR2(1000) := '&TABELA';
4
VAL
VARCHAR2(1000) := '&VAL';
5
TEXTO
VARCHAR2(1000);
6
TYPE RC
IS REF CURSOR;
7
C1
RC;
8 BEGIN
9
OPEN C1 FOR 'SELECT '||COL||' FROM '||TAB||' WHERE CD_MAT > :VAL'
10
USING VAL;
11
:MSG := 'LINHAS OBTIDAS: ';
12
LOOP
13
FETCH C1 INTO TEXTO;
14
EXIT WHEN C1%NOTFOUND;
15
:MSG := :MSG || TEXTO || '; ';
16
END LOOP;
17
CLOSE C1;
18 END;
19 /
Entre o valor para coluna: CD_MAT||'' - ''||NM_FUNC
Entre o valor para tabela: FUNC
Entre o valor para val: 300
Procedimento PL/SQL concluído com sucesso.
MSG
--------------------------------------------------------------------------LINHAS OBTIDAS: 310 - MARINA; 320 - ROBERTO; 330 - WI_SON; 336 - MARQUES;
340 - DILSON; 347 - LOPES; 357 - GONCALVES; 401 - SEQUENCE; 7369 - SMITH;
7499 - ALLEN; 7521 - WARD; 7566 - JONES; 7654 - MARTIN; 7698 - BLAKE; 7782
- CLARK; 7788 - SCOTT; 7839 - KING; 7844 - TURNER; 7845 - TESTE SELECT;
L12_05
Neste exemplo, desejávamos que a coluna a ser obtida e a tabela fossem variáveis também.
Este tipo de ação não pode ser feito através de variáveis Bind; utilizamos, então, a concatenação para
transformar o texto.
Para o valor de salário, pudemos usar, normalmente, um parâmetro Bind. Por este motivo, o comando
Open-For só contém referência a um argumento Bind.
CAPÍTULO 12: SQL DINÂMICO EM PL/SQL - 238
PL/SQL9I – BÁSICO E AVANÇADO
PASSANDO UM ARGUMENTO NULL
Se desejássemos passar um determinado argumento como Null para um SQL dinâmico, teríamos
algumas alternativas de uso.
SQL> BEGIN
2
EXECUTE IMMEDIATE 'UPDATE func SET NR_CARGO = :x'
3
USING NULL;
4 END;
5 /
USING NULL;
*
ERRO na linha 3:
ORA-06550: linha 3, coluna 17:
PLS-00457: expressões tem de ter tipos SQL
ORA-06550: linha 2, coluna 3:
PL/SQL: Statement ignored
L12_06
A forma sintática apresentada acima recebe um erro porque o literal Null não é permitido na cláusula
Using. Uma forma de resolvermos o problema seria a concatenação do texto e retirada da variável
Bind.
SQL> DECLARE
2
texto
varchar2(200);
3
valor
varchar2(50) := 'NULL';
4 BEGIN
5
TEXTO := 'UPDATE func SET NR_CARGO = '||Valor;
6
EXECUTE IMMEDIATE TEXTO;
7 END;
8 /
Procedimento PL/SQL concluído com sucesso.
L12_07
A variável Valor poderia receber qualquer valor, inclusive o texto ‘NULL’ (como no exemplo).
Outra forma de contornar a situação seria substituir a palavra NULL pelo nome de uma variável não
inicializada (ou inicializada explicitamente com NULL).
SQL> DECLARE
2
A_NULL NUMBER;
3 BEGIN
4
EXECUTE IMMEDIATE 'UPDATE func SET NR_CARGO = :x'
5
USING A_NULL;
6 END;
7 /
Procedimento PL/SQL concluído com sucesso.
L12_08
Como a variável A_NULL não foi inicializada, seu valor inicial é NULL.
CAPÍTULO 12: SQL DINÂMICO EM PL/SQL - 239
PL/SQL9I – BÁSICO E AVANÇADO
BULK DINÂMICO
Neste tópico faremos uma junção de assuntos já estudados anteriormente isoladamente, isto é,
uniremos a operação de Bulk Bind com SQL dinâmico.
Com pequenas variações nos comandos Execute Immediate, Fetch e ForAll, para a inclusão da
cláusula Bulk Collect, estaremos aptos a manusear listas de valores dinamicamente. Cada um dos
exemplos a seguir abordará uma cláusula em separado.
SQL> DECLARE
2
TYPE TMAT IS TABLE OF NUMBER;
3
TYPE TNOME IS TABLE OF VARCHAR2(100);
4
VMAT
TMAT;
5
VNOME
TNOME;
6
TEXTO
VARCHAR2(200) := 'SELECT CD_MAT, NM_FUNC FROM FUNC '||
7
'WHERE CD_DEPTO = :DEP';
8
DEP
VARCHAR2(3)
:= '&DEPTO';
9 BEGIN
10
EXECUTE IMMEDIATE TEXTO
11
BULK COLLECT INTO VMAT, VNOME
12
USING DEP;
13
FOR I IN VMAT.FIRST..VMAT.LAST LOOP
14
DBMS_OUTPUT.PUT_LINE('MAT = '||VMAT(I)||' NOME = '||VNOME(I));
15
END LOOP;
16 END;
17 /
Entre o valor para depto: A00
MAT = 10 NOME = CRISTINA
MAT = 110 NOME = VICENTE
MAT = 120 NOME = SILVIO
Procedimento PL/SQL concluído com sucesso.
L12_09
Com este exemplo verificamos que o retorno de um conjunto de linhas pode ser feito diretamente com
o comando Execute Immediate sem a necessidade de definição de variável cursor.
CAPÍTULO 12: SQL DINÂMICO EM PL/SQL - 240
PL/SQL9I – BÁSICO E AVANÇADO
SQL> DECLARE
2
TYPE TROWID IS TABLE OF ROWID INDEX BY BINARY_INTEGER;
3
VROWID
TROWID;
4
TEXTO
VARCHAR2(200) := 'UPDATE FUNC SET VL_SAL = VL_SAL * :PCT'||
5
' WHERE CD_DEPTO = :DEP '||
6
'RETURNING ROWID INTO :R';
7
DEP
VARCHAR2(3)
:= '&DEPTO';
8
PCT
NUMBER
:= '&PCT';
9 BEGIN
10
EXECUTE IMMEDIATE TEXTO
11
USING PCT,DEP
12
RETURNING BULK COLLECT INTO VROWID;
13
FOR I IN VROWID.FIRST..VROWID.LAST LOOP
14
DBMS_OUTPUT.PUT_LINE('ROWID = '||VROWID(I));
15
END LOOP;
16 END;
17 /
Entre o valor para depto: A00
Entre o valor para pct: 0,25
ROWID = AAAH2LAAIAAAAAyAAA
ROWID = AAAH2LAAIAAAAAyAAI
ROWID = AAAH2LAAIAAAAAyAAJ
Procedimento PL/SQL concluído com sucesso.
L12_10
A sintaxe do Execute Immediate (com returning bulk collect) apresentada acima somente poderá ser
usada para os comandos Insert, Update e Delete, pois somente estes comandos podem ter saída com
variáveis Bind (por exemplo :R).
CAPÍTULO 12: SQL DINÂMICO EM PL/SQL - 241
PL/SQL9I – BÁSICO E AVANÇADO
SQL> DECLARE
2
TYPE TMAT
IS TABLE OF NUMBER;
3
TYPE TNOME
IS TABLE OF VARCHAR2(100);
4
TYPE TCURSOR IS REF CURSOR;
5
VMAT
TMAT;
6
VNOME
TNOME;
7
VCURSOR
TCURSOR;
8
TEXTO
VARCHAR2(200) := 'SELECT CD_MAT, NM_FUNC FROM FUNC '||
9
'WHERE CD_DEPTO = :DEP';
10
DEP
VARCHAR2(3) := '&DEPTO';
11 BEGIN
12
OPEN VCURSOR FOR TEXTO USING DEP;
13
FETCH VCURSOR BULK COLLECT INTO VMAT, VNOME;
14
FOR I IN VMAT.FIRST..VMAT.LAST LOOP
15
DBMS_OUTPUT.PUT_LINE('MAT = '||VMAT(I)||' NOME = '||VNOME(I));
16
END LOOP;
17 END;
18 /
Entre o valor para depto: A00
MAT = 10 NOME = CRISTINA
MAT = 110 NOME = VICENTE
MAT = 120 NOME = SILVIO
Procedimento PL/SQL concluído com sucesso.
L12_11
Este exemplo é similar àquele estudado anteriormente.
A diferença está na troca do Execute Immediate pelo Open-For e Fetch.
Observe que não houve necessidade de loop para o fetch pois todos os dados do resultado foram
grupados (Bulk Collect) para as duas tabelas.
CAPÍTULO 12: SQL DINÂMICO EM PL/SQL - 242
PL/SQL9I – BÁSICO E AVANÇADO
SQL> DECLARE
2
TYPE TROWID IS TABLE OF ROWID INDEX BY BINARY_INTEGER;
3
VROWID
TROWID;
4
TYPE TLISTA IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
5
VLISTA
TLISTA;
6
PCT
NUMBER
:= '&PCT';
7 BEGIN
8
VLISTA(1) := 10; VLISTA(2) := 30; VLISTA(3) := 100;
9
VLISTA(4) := 120; VLISTA(5) := 220; VLISTA(6) := 320;
10
FORALL I IN 1..6
11
EXECUTE IMMEDIATE
12
'UPDATE FUNC SET VL_SAL = VL_SAL * :PCT WHERE CD_MAT = :M '||
13
'RETURNING ROWID INTO :R'
14
USING PCT, VLISTA(I) RETURNING BULK COLLECT INTO VROWID;
15
FOR I IN VROWID.FIRST..VROWID.LAST LOOP
16
DBMS_OUTPUT.PUT_LINE('ROWID = '||VROWID(I));
17
END LOOP;
18 END;
19 /
Entre o valor para pct: 0,25
ROWID = AAAH2LAAIAAAAAyAAA
ROWID = AAAH2LAAIAAAAAyAAC
ROWID = AAAH2LAAIAAAAAyAAH
ROWID = AAAH2LAAIAAAAAyAAJ
ROWID = AAAH2LAAIAAAAAyAAT
ROWID = AAAH2LAAIAAAAAyAAd
Procedimento PL/SQL concluído com sucesso.
L12_12
No exemplo acima executamos dinamicamente um comando de atualização que recebia como
parâmetro de entrada uma lista das matrículas a serem atualizadas e um valor constante para todas
as linhas.
Este comando retornou em uma coleção todas as linhas atualizadas.
CAPÍTULO 12: SQL DINÂMICO EM PL/SQL - 243
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 12:
1. Faça um programa que receba como parâmetro o nome da tabela, as colunas a serem criadas e a
indicação de permanência ou não. Faça a criação do objeto no schema do usuário.
2. Faça um programa que receba como parâmetro o nome das colunas a serem lidas da tabela Func
(podem ser informadas até três colunas separadas por vírgula). Apresente os dados lidos.
CAPÍTULO 12: SQL DINÂMICO EM PL/SQL - 244
PL/SQL9I – BÁSICO E AVANÇADO
CAPÍTULO 13: TRIGGERS
CONCEITO
Triggers são códigos de PL/SQL armazenados no banco de dados, associados a uma tabela
específica e executados implicitamente pelo Oracle na ocorrência de um determinado evento.
Os triggers podem ser usados para:
◊
◊
◊
◊
◊
Logar modificações.
Garantir críticas complexas.
Gerar o valor de colunas.
Implementar níveis de segurança mais complexos.
Manter tabelas duplicadas.
A Oracle recomenda que limitemos o tamanho do código de PL/SQL do trigger para algo em torno de
60 linhas. Caso tenhamos necessidade de definir um trigger mais complexo, devemos criar stored
procedures com ações específicas e acionar estas rotinas dentro do trigger.
SINTAXE
CAPÍTULO 13: TRIGGERS - 245
PL/SQL9I – BÁSICO E AVANÇADO
Na sintaxe é apresentado o formato para criação de um database trigger. Esta sintaxe é subdividida
em três partes:
◊
◊
◊
Evento.
Tipo (só aplicável a eventos de DML).
Ação.
Cada uma das partes será analisada separadamente.
EVENTO
O evento corresponde ao momento em que o database trigger deve ser acionado pelo Oracle.
SINTAXE
A Sintaxe mostra a parte referente ao evento, que pode ser de DML (o mais comum), de DDL ou
Database.
Podemos acionar o bloco de PL/SQL associado ao trigger quando o usuário, utilizando qualquer meio
de acesso ao banco de dados, fizer um Update, um Delete ou um Insert na tabela a qual database o
trigger está associado.
O bloco pode ser executado antes ou depois de o comando disparado pelo usuário ser executado
(Before ou After).
OS EVENTOS DE DDL E DATABASE
O DBA pode efetuar o controle sobre eventos de DDL tais como:
◊
Create (o trigger é disparado quando um comando Create adiciona um novo objeto ao
dicionário de dados).
◊
Alter (o trigger é disparado quando um objeto do dicionário de dados é modificado).
◊
Drop (o trigger é disparado quando um objeto do dicionário de dados é removido).
◊
Analyze (o trigger é disparado quando o Oracle colecionar ou deletar estatísticas ou validar a
estrutura de um objeto do dicionário de dados).
CAPÍTULO 13: TRIGGERS - 246
PL/SQL9I – BÁSICO E AVANÇADO
◊
Associate Statistics (o trigger é disparado quando houver associação de estatísticas de
determinado tipo a um objeto do dicionário de dados).
◊
Audit (o trigger é disparado quando ocorrer a auditoria sobre um objeto do dicionário de
dados).
◊
Comment (o trigger é disparado quando forem adicionados comentários a um objeto do
dicionário de dados).
◊
Disassociate Statistics (o trigger é disparado quando houver desassociação de estatísticas
de um objeto do dicionário de dados).
◊
Grant (o trigger é disparado quando um usuário der autorização a outro usuário ou role).
◊
Noaudit (o trigger é disparado quando houver interrupção do processo de auditoria sobre um
objeto do dicionário de dados).
◊
Rename (o trigger é disparado quando um objeto do dicionário de dados mudar de nome).
◊
Revoke (o trigger é disparado quando um usuário revogar autorizações de outro usuário ou
role).
◊
Truncate (o trigger é disparado quando uma tabela tider todos os dados removidos).
Para esse tipo de evento, temos como restrição os eventos disparados através de rotinas PL/SQL não
podem ser controlados.
O controle também pode ser efetuado para eventos do banco de dados, tais como:
◊
Servererror (é disparado quando é gravada uma mensagem de erro do servidor).
◊
Logon (é disparado quando uma aplicação cliente estabelece conexão com o banco de
dados).
◊
Logoff (é disparado quando uma aplicação cliente fecha a conexão com o banco de dados).
◊
Startup (é disparado quando o banco de dados é aberto).
◊
Shutdown (é disparado quando uma instância do Oracle é fechada).
Como estes eventos estão associados ao DBA, ou seja, estão associados a comandos de criação de
objetos no banco de dados, autorização, auditoria, etc, serão vistos muito superficialmente neste
material. Daremos ênfase aos eventos de DML.
CAPÍTULO 13: TRIGGERS - 247
PL/SQL9I – BÁSICO E AVANÇADO
SQL> CREATE OR REPLACE TRIGGER EVENTO
2 BEFORE DELETE OR INSERT OR UPDATE OF VL_SAL ON FUNC
3 FOR EACH ROW
4 BEGIN
5
DBMS_OUTPUT.PUT_LINE('MAT = '||:OLD.CD_MAT);
6
DBMS_OUTPUT.PUT_LINE('SAL OLD = ' ||:OLD.VL_SAL||
7
' SAL NEW = '||:NEW.VL_SAL);
8 END;
9 /
Gatilho criado.
L13_01
No exemplo o trigger criado será executado quando o usuário fizer um comando de insert, delete ou,
no caso da atualização, update da coluna vl_sal.
O usuário poderá realizar esta modificação usando o SQL*Plus, Forms, Reports ou qualquer outra
ferramenta que estabeleça conexão com o banco de dados.
Esta característica garante que a ação definida no trigger será realizada toda vez que o evento
ocorrer, independente de quem o acionou.
SQL> SET SERVEROUT
SQL> UPDATE FUNC
2
SET VL_SAL
3
WHERE CD_MAT
MAT = 100
SAL OLD = 16781,79
MAT = 200
SAL OLD = 5000 SAL
ON
= VL_SAL * 1.2
IN (100, 200);
SAL NEW = 20138,15
NEW = 6000
L13_02
2 linhas atualizadas.
No nosso caso, o trigger será executado antes de o evento que causou o disparo do trigger ser
executado.
Acima alteramos a coluna Salário dos funcionários de matrícula 100 e 200. O trigger foi acionado e
apresentou os valores de salário antes e depois da modificação.
CAPÍTULO 13: TRIGGERS - 248
PL/SQL9I – BÁSICO E AVANÇADO
SQL> CREATE OR REPLACE VIEW VFUNC
2 AS SELECT * FROM FUNC
3
WHERE CD_DEPTO = 'D11';
View criada.
SQL> CREATE OR REPLACE TRIGGER EVENTO1
2 INSTEAD OF UPDATE ON VFUNC
3 FOR EACH ROW
4 BEGIN
5
IF :OLD.VL_SAL <> :NEW.VL_SAL THEN
6
DBMS_OUTPUT.PUT_LINE('MAT = '||:OLD.CD_MAT);
7
DBMS_OUTPUT.PUT_LINE('SAL OLD = '||:OLD.VL_SAL);
8
DBMS_OUTPUT.PUT_LINE('SAL NEW = '||:NEW.VL_SAL);
9
UPDATE FUNC SET VL_SAL = :NEW.VL_SAL
10
WHERE CD_MAT = :NEW.CD_MAT;
11
END IF;
12
IF :OLD.CD_DEPTO <> :NEW.CD_DEPTO THEN
13
DBMS_OUTPUT.PUT_LINE('MAT = '||:OLD.CD_MAT);
14
DBMS_OUTPUT.PUT_LINE('DEPTO OLD = '||:OLD.CD_DEPTO);
15
DBMS_OUTPUT.PUT_LINE('DEPTO NEW = '||:NEW.CD_DEPTO);
16
UPDATE FUNC SET CD_DEPTO = :NEW.CD_DEPTO
17
WHERE CD_MAT = :NEW.CD_MAT;
18
END IF;
19 END;
20 /
Gatilho criado.
L13_03
O exemplo mostra um script com a criação da view Vfunc e do trigger Evento1, que será acionado
quando for feita alguma atualização usando a view Vfunc.
A finalidade deste tipo de trigger é permitir que atualizemos a tabela principal quando a view
associada a ela não puder ser atualizada diretamente (por exemplo, possui uma agregação, um
produto cartesiano, não apresenta todas as colunas, etc.).
Esse tipo de trigger substitui a ação associada: Instead of Update, significa que o comando Update
disparado pelo usuário não será executado e sim o programa PL/SQL criado por nós no trigger.
Como vemos no exemplo, quando é acionada a atualização através da view, efetuamos a atualização
desejada na tabela.
CAPÍTULO 13: TRIGGERS - 249
PL/SQL9I – BÁSICO E AVANÇADO
SQL> UPDATE VFUNC
2
SET VL_SAL = VL_SAL * 1.1,
3
CD_DEPTO = 'D21'
4
WHERE CD_MAT = 60;
MAT = 60
SAL OLD = 15920,33
SAL NEW = 17512,36
MAT = 60
SAL OLD = 15920,33 SAL NEW = 17512,36
MAT = 60
DEPTO OLD = D11
DEPTO NEW = D21
1 linha atualizada.
L13_04
No exemplo acima as modificações feitas usando a view atualizaram a tabela. Quando esta
atualização ocorre o trigger da tabela é acionado também. Veja os resultados.
CAPÍTULO 13: TRIGGERS - 250
PL/SQL9I – BÁSICO E AVANÇADO
TIPO
Um trigger pode ser de dois tipos: Comando ou Linha.
A Sintaxe mostra a parte referente à indicação de tipo.
COMANDO
Um trigger de comando é acionado de acordo com o evento, mas associado ao comando como um
todo. Ou seja, será acionado antes ou depois de um determinado comando, independente de o
comando atualizar uma ou mais linhas.
Este tipo de trigger não tem acesso às linhas atualizadas.
Sintaticamente, para indicação deste tipo de trigger basta que não especifiquemos a cláusula For
Each Row.
SQL> CREATE TRIGGER TIPO_COMANDO
2 AFTER UPDATE OF VL_SAL ON FUNC
3 BEGIN
4
DBMS_OUTPUT.PUT_LINE('COMANDO CONCLUÍDO COM SUCESSO');
5 END;
6 /
Gatilho criado.
L13_05
No exemplo criamos um trigger do tipo Comando e verificaremos a seguir como ocorrerá seu
acionamento.
SQL> DROP TRIGGER EVENTO;
Gatilho eliminado.
SQL> UPDATE FUNC
2
SET VL_SAL = VL_SAL * 1.1;
COMANDO CONCLUÍDO COM SUCESSO
57 linhas atualizadas.
L13_06
No exemplo observamos que a mensagem incluída no trigger somente foi executada uma vez, apesar
de 57 linhas serem atualizadas.
CAPÍTULO 13: TRIGGERS - 251
PL/SQL9I – BÁSICO E AVANÇADO
LINHA
Um trigger do tipo Row é acionado de acordo com o evento, mas uma vez para cada linha afetada
pelo comando disparado pelo usuário. Isto traz algumas conseqüências que analisaremos a seguir.
Sintaticamente, para indicação deste tipo de trigger basta que especifiquemos a cláusula For Each
Row.
Observe, porém, que para este tipo de trigger outras partes da sintaxe podem ser especificadas, tais
como Referencing e When.
Quando especificamos um trigger deste tipo, automaticamente, o Oracle cria duas áreas de trabalho
associadas ao bloco de PL/SQL. Essas áreas são do tipo Rowtype da tabela ao qual o trigger se
refere.
SQL> CREATE OR REPLACE TRIGGER TIPO_ROW
2 BEFORE DELETE OR INSERT OR UPDATE OF VL_SAL ON FUNC
3 FOR EACH ROW
4 BEGIN
5
DBMS_OUTPUT.PUT_LINE('MAT = '||:OLD.CD_MAT);
6
DBMS_OUTPUT.PUT_LINE('SAL OLD = ' ||:OLD.VL_SAL||
7
' SAL NEW = '||:NEW.VL_SAL);
8 END;
9 /
Gatilho criado.
L13_07
No PL/SQL do trigger criado, fazemos referência a duas áreas, OLD e NEW. Se fossem criadas dentro
do código de PL/SQL teriam a seguinte sintaxe: “<nome area> <tabela>%Rowtype;”.
Os nomes de área padrões seriam OLD e NEW. Estas áreas são preenchidas pelo Oracle com a
imagem anterior e posterior da linha (antes e depois da modificação).
Caso o evento seja Insert, haverá valor apenas em NEW (OLD estará Null). Caso o evento seja
Delete, haverá valor apenas em OLD (NEW estará Null).
Estas áreas podem ter seus nomes modificados se utilizarmos a cláusula Referencing, mostrada na
sintaxe.
Dentro do código do bloco de PL/SQL, estas áreas devem ser referenciadas com dois-pontos na
frente (:), indicando que não se trata de uma área declarada no bloco, e sim uma área do ambiente.
CAPÍTULO 13: TRIGGERS - 252
PL/SQL9I – BÁSICO E AVANÇADO
SQL> CREATE OR REPLACE TRIGGER TIPO_ROW
2 BEFORE DELETE OR INSERT OR UPDATE OF VL_SAL ON FUNC
3 REFERENCING OLD AS V
4
NEW AS N
5 FOR EACH ROW
6 BEGIN
7
DBMS_OUTPUT.PUT_LINE('MAT = '||:V.CD_MAT);
8
DBMS_OUTPUT.PUT_LINE('SAL OLD = ' ||:V.VL_SAL||
9
' SAL NEW = '||:N.VL_SAL);
10 END;
11 /
Gatilho criado.
SQL> UPDATE FUNC SET VL_SAL = VL_SAL * 1.2
2
WHERE CD_MAT IN (100, 200);
MAT = 100
SAL OLD = 22151,97 SAL NEW = 26582,36
MAT = 200
SAL OLD = 6600 SAL NEW = 7920
COMANDO CONCLUÍDO COM SUCESSO
2 linhas atualizadas.
L13_08
Ao criarmos o trigger, estabelecemos que faremos referência à área OLD com o qualificador V e que
faremos referência à área NEW com o qualificador N. Internamente no bloco de PL/SQL
usamos:V.cd_mat. Observe no resultado que o trigger de comando também foi acionado ao término
das atualizações.
Imaginemos, agora, que desejamos acionar o trigger a cada atualização da coluna VL_SAL da tabela
Func, porém apenas para os funcionários que trabalham do departamento D11.
Esta característica implica a necessidade de incluirmos uma restrição sobre as linhas atualizadas pelo
comando. Essa restrição é feita na cláusula When. Ela não restringe as linhas a serem atualizadas,
apenas aquelas que acionarão o trigger.
CAPÍTULO 13: TRIGGERS - 253
PL/SQL9I – BÁSICO E AVANÇADO
SQL> CREATE OR REPLACE TRIGGER TIPO_ROW
2 BEFORE DELETE OR INSERT OR UPDATE OF VL_SAL ON FUNC
3 REFERENCING OLD AS V
4
NEW AS N
5 FOR EACH ROW
6 WHEN (V.CD_DEPTO = 'D11')
7 BEGIN
8
DBMS_OUTPUT.PUT_LINE('MAT = '||:V.CD_MAT);
9
DBMS_OUTPUT.PUT_LINE('SAL OLD = '||:V.VL_SAL||
10
' SAL NEW = '||:N.VL_SAL);
11 END;
12 /
Gatilho criado.
SQL> UPDATE FUNC SET VL_SAL = VL_SAL * 1.2
2
WHERE CD_MAT IN (100, 200, 60);
MAT = 200
SAL OLD = 7920 SAL NEW = 9504
COMANDO CONCLUÍDO COM SUCESSO
3 linhas atualizadas.
L13_09
No exemplo acima definimos que somente se o departamento da linha original (OLD) fosse D11 o
trigger deveria ser acionado.
Das três linhas modificadas pelo comando Update, apenas a de matrícula 200 acionou o trigger.
CAPÍTULO 13: TRIGGERS - 254
PL/SQL9I – BÁSICO E AVANÇADO
AÇÃO
A última parte de um trigger é composta do bloco de PL/SQL associado ao evento e sobre o qual
veremos algumas particularidades.
PREDICADOS CONDICIONAIS
Quando criamos um trigger associado a mais de um comando de DML (Insert, Update ou Delete),
muitas vezes temos necessidade, dentro do código de PL/SQL, de saber qual o evento causador da
execução do trigger. Isso é possível se usarmos os predicados condicionais:
◊
Inserting – Retorna True se o trigger foi disparado por causa de um comando Insert.
◊
Updating – Retorna True se o trigger foi disparado por causa de um comando Update.
◊
Deleting – Retorna True se o trigger foi disparado por causa de um comando Delete.
SQL> CREATE OR REPLACE TRIGGER TIPO_ROW
2 BEFORE DELETE OR INSERT OR UPDATE OF VL_SAL ON FUNC
3 REFERENCING OLD AS V
4
NEW AS N
5 FOR EACH ROW
6 WHEN (V.CD_DEPTO = 'D11' OR N.CD_DEPTO = 'D11')
7 BEGIN
8
IF UPDATING THEN
9
DBMS_OUTPUT.PUT_LINE('MAT = '||:V.CD_MAT);
10
DBMS_OUTPUT.PUT_LINE('SAL OLD = '||:V.VL_SAL||
11
' SAL NEW = '||:N.VL_SAL);
12
ELSIF INSERTING THEN
13
DBMS_OUTPUT.PUT_LINE('MAT = '||:N.CD_MAT);
14
DBMS_OUTPUT.PUT_LINE('SAL NEW = '||:N.VL_SAL);
15
ELSE
16
DBMS_OUTPUT.PUT_LINE('MAT = '||:V.CD_MAT);
17
DBMS_OUTPUT.PUT_LINE('SAL OLD = '||:V.VL_SAL);
18
END IF;
19 END;
20 /
Gatilho criado.
L13_10
No trigger acima, de acordo com o evento acontecido a mensagem é diferente.
CAPÍTULO 13: TRIGGERS - 255
PL/SQL9I – BÁSICO E AVANÇADO
SQL> INSERT INTO FUNC (CD_MAT, VL_SAL, CD_DEPTO)
2 VALUES (2, 1000, 'D11');
MAT = 2
SAL NEW = 1000
1 linha criada.
SQL> UPDATE FUNC SET VL_SAL = 2000
2
WHERE CD_MAT = 2;
MAT = 2
SAL OLD = 1000 SAL NEW = 2000
COMANDO CONCLUÍDO COM SUCESSO
1 linha atualizada.
SQL> DELETE FROM FUNC
2
WHERE CD_MAT = 2;
MAT = 2
SAL OLD = 2000
1 linha deletada.
L13_11
Acima vemos o resultado das diversas atualizações, com este trigger ativo.
RESTRIÇÕES
Dentro da PL/SQL associada ao trigger temos as seguintes restrições:
◊
Não são permitidos comandos de DDL.
◊
Não são permitidos comandos para controle da transação, como por exemplo Rollback,
Commit, Savepoint, etc.
◊
Variáveis não podem ser definidas como Long ou Long Raw.
◊
New e Old não podem ser utilizados com colunas Long ou Long Raw.
◊
Podemos executar um Select que retorne uma coluna Long ou Long Raw somente se a
coluna puder ser convertida para um tipo restrito (por exemplo Varchar2).
◊
Quando o Oracle detecta uma situação de conflito entre uma transação com um comando
Update (ou Delete) e outra transação com um comando Update, ele realiza um Rollback implícito
de um dos comandos e sinaliza à transação para que o comando possa ser reexecutado.
Quando essa situação ocorre e existe um trigger associado ao evento Before <comando>, o
Oracle re-executa esse trigger a cada vez que o comando for disparado (mesmo na rechamada).
Todas as ações realizadas por este trigger na primeira execução são desmanchadas, exceto as
modificações que ele fizer a variáveis de um package. Se não desejarmos que essa modificação
se repita, devemos criar uma variável de controle que consiga detectar essa situação.
CAPÍTULO 13: TRIGGERS - 256
PL/SQL9I – BÁSICO E AVANÇADO
TABELAS MUTANTES E RESTRITAS
Uma tabela é considerada mutante se estiver sendo modificada concorrentemente pelos comandos
DML de atualização Update, Insert ou Delete e ainda quando necessita de atualização em função de
uma restrição do tipo Delete Cascade.
Uma tabela é considerada restrita ou sob restrição se um trigger precisa efetuar uma leitura para esta
tabela ou se houver necessidade de uma restrição de integridade referencial efetuar uma leitura para
a tabela.
Uma tabela só é considerada em mudança ou sob restrição em relação à sessão que executa o
comando em desenvolvimento.
Um trigger do tipo <comando> não recebe erro indicando que a tabela está em mudança uma vez que
o trigger só será disparado quando o comando acabar ou antes de todo o comando começar; a não
ser que seja disparado em função de uma restrição Delete Cascade.
Já um trigger do tipo <linha> pode receber um erro indicando que a tabela está em mudança uma vez
que este trigger é disparado a cada modificação de uma linha na tabela, no meio da execução de um
comando.
Desta forma, existem algumas restrições que se aplicam a todos os triggers do tipo <linha> e para
aqueles triggers do tipo <comando> que venham a ser disparados em função de uma restrição do tipo
Delete Cascade:
◊
Os comandos SQL de um trigger não podem ler ou modificar a tabela em mutação associada
ao próprio trigger.
◊
Os comandos de um trigger não podem modificar as colunas primary key, foreign key ou
unique key de uma tabela sob restrição associada ao próprio trigger.
Como exceção a essa restrição, temos o caso de um único comando Insert em que a execução
de um trigger Before Row ou After Row não é considerado modificando uma tabela em mudança.
Um comando Insert contendo um Select não é considerado um único comando Insert mesmo que
o comando Select retorne apenas uma linha.
CAPÍTULO 13: TRIGGERS - 257
PL/SQL9I – BÁSICO E AVANÇADO
SQL> CREATE OR REPLACE TRIGGER MODIFICA
2 AFTER UPDATE OR DELETE ON FUNC
3 FOR EACH ROW
4 DECLARE
5
MAXIMO
NUMBER;
6
CONTADOR
NUMBER;
7 BEGIN
8
IF UPDATING THEN
9
SELECT MAX(VL_SAL) INTO MAXIMO
10
FROM FUNC
11
WHERE CD_DEPTO = 'D11';
12
DBMS_OUTPUT.PUT_LINE('EM USO = '||MAXIMO);
13
END IF;
14
IF DELETING THEN
15
SELECT COUNT(*) INTO CONTADOR
16
FROM FUNC;
17
DBMS_OUTPUT.PUT_LINE('EM USO = '||CONTADOR);
18
END IF;
19 END;
20 /
Gatilho criado.
L13_12
No exemplo acima criamos um trigger que efetua leitura na mesma tabela em que ocorrem as
atualizações.
SQL> SELECT CD_MAT, CD_DEPTO, VL_SAL FROM FUNC
2
WHERE CD_MAT IN (10, 30, 60, 160);
CD_MAT
---------10
30
60
160
CD_
VL_SAL
--- ---------A00
28644,3
C01
20770,49
D21
23116,32
D11
17277,56
SQL> UPDATE FUNC SET VL_SAL = VL_SAL * 1.1
2
WHERE CD_MAT IN (10, 30);
UPDATE FUNC SET VL_SAL = VL_SAL * 1.1
*
ERRO na linha 1:
ORA-04091: a tabela ALUNO.FUNC é mutante; talvez o gatilho/função
não possa localizá-la
ORA-06512: em "ALUNO.MODIFICA", line 6
ORA-04088: erro durante a execução do gatilho 'ALUNO.MODIFICA'
L13_13
A execução deste trigger recebe o erro “ORA-04091: a tabela ALUNO.FUNC é mutante;” indicando
que a tabela está em mudança quando o comando de leitura foi disparado.
CAPÍTULO 13: TRIGGERS - 258
PL/SQL9I – BÁSICO E AVANÇADO
Uma forma bastante utilizada como solução de contorno é a criação de uma tabela de trabalho capaz
de armazenar todas as informações necessárias à realização da operação que precisaríamos fazer.
No trigger de linha, em vez de lermos ou modificarmos a tabela mutante, gravamos as informações
necessárias nessa tabela, por exemplo, todas as linhas que o usuário atualizou ou incluiu ou excluiu e
num trigger de comando, disparado após o evento, lemos os dados dessa tabela e efetuamos a
modificação ou leitura desejada.
CAPÍTULO 13: TRIGGERS - 259
PL/SQL9I – BÁSICO E AVANÇADO
COMPILANDO TRIGGERS
Antes da versão 7.3 (do banco de dados), um trigger era compilado todas as vezes em que era
disparado, como se fosse um comando de SQL.
A partir da versão 7.3 (do banco de dados), quando executamos o comando Create Trigger, ocorre a
compilação deste e o código produzido por essa compilação (Pcode) é armazenado no dicionário de
dados.
Se ocorrerem erros durante a compilação do trigger, este é gravado, porém em situação de erro. Se
um comando de DML causar a execução deste trigger, o comando de DML falhará.
Quando realizamos uma modificação em um objeto do qual o trigger é dependente, por exemplo, uma
stored procedure chamada do corpo do trigger, o estado do trigger mudará para inválido. Quando
forem acionados, serão automaticamente compilados.
CAPÍTULO 13: TRIGGERS - 260
PL/SQL9I – BÁSICO E AVANÇADO
ALTER TRIGGER
O comando Alter Trigger permite que explicitamente façamos a compilação do trigger inválido para
que a compilação não ocorra durante o processamento normal.
SQL> ALTER TRIGGER DLOCK COMPILE;
Gatilho alterado.
L13_14
Acima efetuamos a compilação do trigger Dlock.
Outra ação que podemos realizar é desabilitar ou habilitar a execução de um trigger. Esta ação pode
ser muito útil em procedimentos de carga da tabela, a fim de minimizar o tempo da carga.
SQL> ALTER TRIGGER MODIFICA DISABLE;
Gatilho alterado.
SQL> ALTER TRIGGER DLOCK DISABLE;
Gatilho alterado.
SQL> EXECUTE LOCK1;
I = 1
COMANDO CONCLUÍDO COM SUCESSO
Procedimento PL/SQL concluído com sucesso.
SQL> EXECUTE LOCK2;
MAT = 200
SAL OLD = 9504 SAL NEW = 10454,4
COMANDO CONCLUÍDO COM SUCESSO
Procedimento PL/SQL concluído com sucesso.
SQL> ALTER TRIGGER DLOCK ENABLE;
Gatilho alterado.
SQL> EXECUTE LOCK2;
I = 2
MAT = 200
SAL OLD = 10454,4 SAL NEW = 11499,84
COMANDO CONCLUÍDO COM SUCESSO
Procedimento PL/SQL concluído com sucesso.
L13_15
Lembre-se, porém, que um trigger é diferente de uma constraint. Ao habilitarmos o trigger novamente,
nenhuma ação de verificação sobre os dados da tabela é realizada.
Ele passa a ficar ativo a partir do ponto em que foi habilitado, não verificando qualquer incongruência
que possa existir nos dados armazenados (uma constraint só se torna habilitada se todas as linhas da
tabela forem compatíveis com a restrição).
CAPÍTULO 13: TRIGGERS - 261
PL/SQL9I – BÁSICO E AVANÇADO
TRIGGERS COM EVENTOS DE DDL E DATABASE
Neste tópico veremos um exemplo de trigger associado a eventos de DDL e Database. Quando
criamos um trigger, precisamos obter informações sobre a ação efetuada. Os eventos aos quais um
trigger de DDL ou Database se associam são gerais, isto é, se aplicam a diversos usuários e/ou a
diversos objetos.
A Oracle disponibilizou um conjunto de funções que permitem que obtenhamos informações sobre o
usuário que realizou a ação, qual a ação, qual o objeto, em que owner e diversas outras. A lista
completa com as devidas explicações você encontrará no capítulo 16 do manual Oracle9i Application
Developer’s Guide – Fundamentals.
SQL> CREATE TABLE TLOGS
2 (DATA
TIMESTAMP,
3
TEXTO
VARCHAR2(2000))
4 /
Tabela criada.
SQL> CREATE OR REPLACE TRIGGER TDDL
2
AFTER CREATE OR LOGON OR RENAME OR TRUNCATE
3
ON DATABASE
4 DECLARE
5
LUSER
VARCHAR2(30) := ORA_LOGIN_USER;
6
SEVENT
VARCHAR2(20) := ORA_SYSEVENT;
7
DOWNER
VARCHAR2(30) := ' ';
8
DTYPE
VARCHAR2(20) := ' ';
9
DNAME
VARCHAR2(30) := ' ';
10 BEGIN
11
IF SEVENT <> 'LOGON' THEN
12
DTYPE := ORA_DICT_OBJ_TYPE;
13
DOWNER := ORA_DICT_OBJ_OWNER;
14
DNAME := ORA_DICT_OBJ_NAME;
15
INSERT INTO TLOGS
16
VALUES (CURRENT_TIMESTAMP,
17
'Autor -> '||LUSER||'; '||
18
'Evento-> '||SEVENT||'; '||
19
'Owner do Objeto -> '||DOWNER||'; '||
20
'Tipo do Objeto -> '||DTYPE||'; '||
21
'Nome do Objeto -> '||DNAME||';');
22
ELSE
23
INSERT INTO TLOGS
24
VALUES (CURRENT_TIMESTAMP,
25
'Autor -> '||LUSER||'; '||
26
'Evento-> '||SEVENT||';');
27
END IF;
28 END;
29 /
Gatilho criado.
L13_16
CAPÍTULO 13: TRIGGERS - 262
PL/SQL9I – BÁSICO E AVANÇADO
Neste exemplo, como primeiro passo, criamos uma tabela para armazenamento das ocorrências
geradas pelo trigger.
Observe o uso das funções ORA_DICT_OBJ_TYPE (retorna o tipo do objeto criado, truncado ou
renomeado), ORA_LOGIN_USER (retorna o nome do usuário que causou o disparo do trigger), etc.
Com o uso destas rotinas podemos monitorar determinadas ações específicas sobre o banco de
dados.
Uma vez que o trigger será disparado para todo o banco de dados (pois escolhemos o escopo como
ON DATABASE), faremos algumas intervenções usando o usuário Scott.
SQL> CONNECT SCOTT/TIGER
Conectado.
SQL> CREATE TABLE X(COLUNA
Tabela criada.
NUMBER);
SQL> RENAME X TO Y;
Tabela renomeada.
L13_17
SQL> TRUNCATE TABLE Y;
Tabela truncada.
No exemplo acima efetuamos quatro ações diferentes que causarão a chamada do trigger TDDL
quatro vezes.
SQL> connect aluno/aluno
Conectado.
SQL> COL DATA FOR A30
SQL> COL TEXTO FOR A55
SQL> SET LINESIZE 90
SQL> SELECT * FROM TLOGS;
DATA
TEXTO
------------------------------ ------------------------------------------07/06/03 14:18:04,000000
Autor -> SCOTT; Evento-> LOGON;
07/06/03 14:18:04,000001
Autor -> SCOTT; Evento-> CREATE; Owner do
Objeto -> SCOTT; Tipo do Objeto -> TABLE; Nome do Objeto -> X;
07/06/03 14:18:04,000001
Autor -> SCOTT; Evento-> RENAME; Owner do
Objeto -> SCOTT; Tipo do Objeto -> TABLE; Nome do Objeto -> X;
07/06/03 14:18:04,000001
Autor -> SCOTT; Evento-> TRUNCATE; Owner do
Objeto -> SCOTT; Tipo do Objeto -> TABLE; Nome do Objeto -> Y;
07/06/03 14:45:31,000001
Autor -> ALUNO; Evento-> LOGON;
L13_18
CAPÍTULO 13: TRIGGERS - 263
PL/SQL9I – BÁSICO E AVANÇADO
TRIGGERS E STORED PROCEDURES
Você deve ter percebido pela sintaxe de Trigger que podemos fazer chamada a uma stored procedure
em vez de escrevermos um PL/SQL específico ou até mesmo um programa em Java.
SQL> DROP TRIGGER DLOCK;
Gatilho eliminado.
SQL> DROP TRIGGER EVENTO1;
Gatilho eliminado.
SQL> DROP TRIGGER MODIFICA;
Gatilho eliminado.
SQL> DROP TRIGGER TIPO_ROW;
Gatilho eliminado.
SQL> DROP TRIGGER TIPO_COMANDO;
Gatilho eliminado.
SQL> CREATE OR REPLACE PROCEDURE PREINS (MAT NUMBER, NOME VARCHAR2)
IS
2 BEGIN
3
INSERT INTO TLOGS VALUES (CURRENT_TIMESTAMP,
4
'MAT = '||MAT||' NOME = '||NOME);
5 END;
6 /
Procedimento criado.
SQL> CREATE OR REPLACE TRIGGER TCALL
2 BEFORE INSERT ON FUNC
3 FOR EACH ROW
4 CALL PREINS(:NEW.CD_MAT, :NEW.NM_FUNC)
5 /
Gatilho criado.
L13_19
No exemplo a seguir encontramos o resultado da inclusão de uma linha na tabela Func.
SQL> INSERT INTO FUNC (CD_MAT, NM_FUNC) VALUES (16, 'TESTE CALL');
1 linha criada.
SQL> SELECT * FROM TLOGS;
DATA
TEXTO
------------------------------ -----------------------------------07/06/03 14:51:48,000000
MAT = 16 NOME = TESTE CALL
L13_20
CAPÍTULO 13: TRIGGERS - 264
PL/SQL9I – BÁSICO E AVANÇADO
TRANSAÇÕES AUTÔNOMAS
Uma transação autônoma é uma transação independente iniciada por uma outra transação. Pode
executar operações como Commit ou Rollback sem afetar a transação principal. Ela não compartilha
recursos, locks nem commits com a transação que a disparou. É totalmente independente.
Para definirmos uma transação autônoma, devemos usar a pragma Autonomous_Transaction. Essa
pragma (pragmas são diretivas para o compilador, lembra?) indica ao compilador PL/SQL que marque
a rotina como autônoma (ou independente).
Aplicável a:
◊
Blocos anônimos de PL/SQL, porém somente aqueles de nível mais externo (Top-Level). Não
se aplica a blocos internos.
◊
Funções e procedimentos isolados no banco de dados.
◊
Funções e procedimentos presentes em pacotes.
◊
Métodos de um objeto.
◊
Database Triggers.
Nosso primeiro exemplo cria um trigger usando a característica de autonomia.
SQL> DROP TRIGGER TCALL;
Gatilho eliminado.
SQL> CREATE OR REPLACE TRIGGER ISOLADO
2 BEFORE INSERT ON FUNC
3 FOR EACH ROW
4 DECLARE
5
PRAGMA AUTONOMOUS_TRANSACTION;
6 BEGIN
7
INSERT INTO TLOGS
8
VALUES (CURRENT_TIMESTAMP,
9
:NEW.CD_MAT ||' - '||:NEW.NM_FUNC);
10
COMMIT;
11 END;
12 /
Gatilho criado.
L13_21
O trigger acima foi associado ao comando Insert na tabela Func e, para cada linha incluída, uma linha
é gravada na tabela Tlogs.
Observe que o bloco de PL/SQL do trigger contém o comando Commit. Isso não seria possível em
situações normais. Essa característica somente é válida quando usamos a pragma
Autonomous_Transaction.
CAPÍTULO 13: TRIGGERS - 265
PL/SQL9I – BÁSICO E AVANÇADO
Nosso próximo passo será a inclusão de linhas em Func. Acompanhe o exemplo abaixo.
SQL> SELECT CD_MAT, NM_FUNC FROM FUNC
2 WHERE CD_MAT = 2;
não há linhas selecionadas
SQL> INSERT INTO FUNC (CD_MAT, NM_FUNC)
2 VALUES (2, 'NOVO ITEM');
1 linha criada.
SQL> SELECT * FROM TLOGS;
DATA
TEXTO
------------------------------ --------------07/06/03 14:57:29,000001
2 - NOVO ITEM
SQL> ROLLBACK;
Rollback completo.
SQL> SELECT * FROM TLOGS;
DATA
TEXTO
------------------------------ --------------07/06/03 14:57:29,000001
2 - NOVO ITEM
SQL> SELECT CD_MAT, NM_FUNC FROM FUNC
2 WHERE CD_MAT = 2;
não há linhas selecionadas
L13_22
No exemplo criamos uma linha na tabela Func e, conseqüentemente, a trigger Isolado criou uma linha
da tabela Tlogs. Até aí, nada demais.
Quando, porém, fizemos o Rollback, esperaríamos que ambas as ações fossem desmanchadas. No
entanto, a linha incluída na tabela Logs permanece gravada, enquanto a da tabela Func foi
desmanchada.
CAPÍTULO 13: TRIGGERS - 266
PL/SQL9I – BÁSICO E AVANÇADO
TRANSAÇÕES AUTÔNOMAS VERSUS DEPENDENTES
Como diferenças entre uma transação autônoma e uma transação dependente, temos:
◊
Uma transação autônoma não compartilha recursos, tais como Locks, com a transação
principal.
◊
Uma transação autônoma não depende da transação principal. Por exemplo, se a transação
principal efetua um Rollback, uma transação dependente seria desmanchada também, o que não
ocorre com a transação autônoma.
◊
Todas as modificações feitas pela transação autônoma são visíveis por outras transações
imediatamente após essa ter efetuado um Commit. Em uma transação dependente, essa
visibilidade só ocorreria quando a transação principal realizasse um Commit.
◊
Um comando Rollback executado em uma transação autônoma desfaz somente as
modificações realizadas por ela, não afetando a transação principal (que a chamou).
No exemplo abaixo criamos um pacote contendo uma função autônoma.
SQL> CREATE OR REPLACE PACKAGE PACOTE IS
2
FUNCTION SÓ(MAT IN NUMBER, NOME IN VARCHAR2) RETURN VARCHAR2;
3
PRAGMA RESTRICT_REFERENCES(SÓ, WNDS, WNPS);
4 END PACOTE;
5 /
Pacote criado.
SQL> CREATE OR REPLACE PACKAGE BODY PACOTE IS
2
FUNCTION SÓ(MAT IN NUMBER, NOME IN VARCHAR2) RETURN VARCHAR2 IS
3
PRAGMA AUTONOMOUS_TRANSACTION;
4
BEGIN
5
INSERT INTO FUNC(CD_MAT, NM_FUNC)
6
VALUES (MAT, NOME);
7
COMMIT;
8
RETURN 'INCLUÍDA LINHA COM MATRÍCULA '||MAT;
9
END;
10 END PACOTE;
11 /
Corpo de Pacote criado.
L13_23
Observe que a função só foi definida com a pragma Restrict_References usando os parâmetros Wnds
(Write no database state – não atualiza o banco de dados) e Wnps (write no package state – não
atualiza variáveis de pacote). No entanto, o corpo do pacote foi criado sem erros de compilação e
existem um comando Insert e um comando Commit no corpo da função.
Isto ocorre porque uma rotina autônoma sempre tem direito de acesso para leitura e gravação no
banco de dados (elas nunca violam as regras WNDS e RNDS).
CAPÍTULO 13: TRIGGERS - 267
PL/SQL9I – BÁSICO E AVANÇADO
Observe a execução do pacote a seguir.
SQL> SELECT PACOTE.SÓ(2, 'FUNÇÃO SÓ') FROM DUAL;
PACOTE.SÓ(2,'FUNÇÃOSÓ')
-----------------------------------------------------INCLUÍDA LINHA COM MATRÍCULA 2
SQL> SELECT CD_MAT, NM_FUNC FROM FUNC
2 WHERE CD_MAT = 2;
CD_MAT NM_FUNC
---------- -----------2 FUNÇÃO SÓ
SQL> SELECT * FROM TLOGS;
DATA
TEXTO
------------------------------ ----------------------07/06/03 15:03:36,000000
2 - FUNÇÃO SÓ
Executamos a rotina em um comando Select (atualizando o banco de dados).
A linha de funcionário foi incluída.
Quando listamos a tabela Logs, verificamos que uma linha também foi incluída nesta tabela,
proveniente da trigger Isolado (outra transação autônoma).
CAPÍTULO 13: TRIGGERS - 268
PL/SQL9I – BÁSICO E AVANÇADO
ERROS POSSÍVEIS
As observações a seguir indicam as possibilidades de erro quando utilizamos transações autônomas:
◊
Se uma transação autônoma tentar obter um recurso bloqueado (lock) pela transação
principal, que não termina aguardando o retorno da transação autônoma, pode ocorrer um
DeadLock.
◊
O banco de dados possui um parâmetro de inicialização chamado Transactions que
especifica o número máximo de transações concorrentes.
O número de transações concorrentes poderá ser significativamente incrementado (você já
percebeu isso com os exemplos anteriores) com o uso de transações autônomas, uma vez que
estas executam concorrentemente com a transação principal.
CAPÍTULO 13: TRIGGERS - 269
PL/SQL9I – BÁSICO E AVANÇADO
COMPILANDO PL/SQL PARA EXECUÇÃO NATIVA
Podemos ganhar velocidade nas rotinas de PL/SQL se viermos a compilá-las em um código nativo
residente em bibliotecas compartilhadas. As rotinas são convertidas para código C, então compiladas
com o compilador C em uso e linked no processo Oracle.
Podemos usar esta técninca tanto para os pacotes de PL/SQL fornecidos pela Oracle quanto com
nossas próprias procedures.
Esta técnica é mais útil para programas que não gastem muito tempo executando comandos de SQL,
isto é, favorece principalmente os programas que realizam muito processamento.
Verifique com seu DBA as vantagens do emprego desta técnica.
Para maiores informações consulte o capítulo 9 do manual Application Developer’s Guide –
Fundamentals.
CAPÍTULO 13: TRIGGERS - 270
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 13:
1. Faça um trigger para controlar as modificações feitas nos salários dos funcionários.
Para tal crie uma tabela de log com o seguinte layout: operação (I, E ou A), valor anterior, valor atual,
matrícula, timestamp (data + hora).
Se for feita uma inclusão, apenas o valor atual deve ser preenchido. Para alteração, ambos os valores
devem ser preenchidos; para exclusão, apenas o valor anterior deve ser preenchido.
2. Crie um trigger associado à tabela Func que:
◊
Preencha o valor de matrícula se este estiver Null.
◊
Calcule o valor do salário do funcionário, se este estiver Null. O piso salarial é proporcional ao
cargo, começando com cargo 40 de 1.000,00 contendo aumentos sucessivos de 300 a cada novo
cargo.
◊
Determine o departamento que tiver menos funcionários e atribua ao código do departamento
se este estiver Null.
◊
Preencha a data de admissão em caso de inclusão.
◊
Altere os nomes para maiúsculas.
3. Crie um trigger que critique a inclusão ou alteração de dados na tabela Func:
◊
◊
◊
Ramal deve ter quatro posições
Salário deve ser maior que 500,00 e não pode ser diminuído.
A data de nascimento deve ser maior que 1950.
Se tudo estiver correto, converta o nome e sobrenome para maiúscula.
Emita mensagem indicativa do erro.
4. Faça um trigger que, para cada salário incluído, alterado ou excluído em Funcionário, atualize os
valores de salários do departamento da tabela Depto_Acum, cujo layout é:
cd_depto
vl_média
vl_max
vl_min
vl_total
char(03)
number
number
number
number
Se o funcionário mudar de departamento, estes totais também devem ser calculados.
5. Faça um programa que inclua linhas na tabela Func, recebendo como parâmetro matrícula, nome e
departamento do funcionário.
Execute este programa em um comando Select.
CAPÍTULO 13: TRIGGERS - 271
PL/SQL9I – BÁSICO E AVANÇADO
CAPÍTULO 14: EXTERNAL PROCEDURES - JAVA
INTRODUÇÃO
Neste tópico, trataremos da utilização de rotinas criadas fora do banco de dados e utilizadas em
PL/SQL. Essas rotinas são chamadas de External Procedures. A abordagem deste assunto neste
material será superficial. Focaremos principalmente o conceito. Para maiores informações, consulte o
Capítulo 10 do manual Application Developer’s Guide - Fundamentals.
Uma External Procedure nada mais é que uma rotina escrita em uma linguagem de terceira geração e
armazenada em uma DLL (Dynamic Link Library) ou libunit no caso de Java e registrada com a
PL/SQL.
Isto significa que existirá um mecanismo controlado pela PL/SQL que acionará a rotina externa. As
aplicações que desejarem fazer uso de uma rotina externa acionarão uma rotina especial declarada
em PL/SQL, mas que na verdade servirá de interface para a verdadeira rotina externa. Veremos a
seguir como se dará esta interface.
Quando acionarmos esta rotina PL/SQL, a própria PL/SQL acionará a execução da DLL como se
fosse uma subrotina interna sua, passando e recebendo não só os parâmetros como também os erros
e transferindo o resultado para o programa que a acionou. Desta forma, para o programa chamador é
indiferente (sintaticamente) a utilização de uma rotina PL/SQL ou externa.
Atualmente, rotinas chamadas de um código C e Java são suportadas.
CRIANDO A INTERFACE
Para a utilização de rotinas externas, duas etapas são necessárias:
◊
Inicialmente o DBA deve criar uma library no banco de dados que identifique a localização da
biblioteca de rotinas (ou seja, da DLL ou classe).
◊
A segunda etapa refere-se à especificação da rotina para que a PL/SQL possa ativá-la;
chama-se registrar o procedimento (rotina PL/SQL de interface).
CAPÍTULO 14: EXTERNAL PROCEDURES - JAVA - 272
PL/SQL9I – BÁSICO E AVANÇADO
DEFININDO A BIBLIOTECA
O comando Create Library identifica o arquivo em disco onde se encontram as rotinas (funções e
procedures) que desejamos ativar.
SINTAXE
A especificação de <caminho> deve ser completa para que a DLL possa ser efetivamente encontrada.
SQL> CREATE OR REPLACE DIRECTORY LOCAL AS 'C:\TEMP';
Diretório criado.
SQL> CREATE LIBRARY ORASQL AS
2
'C:\ORACLE\ORA9I\BIN\ORASQL9.DLL';
3 /
Biblioteca criada.
SQL> CREATE JAVA CLASS USING BFILE (LOCAL, 'Util.class');
2 /
Java criado.
L14_01
No exemplo acima criamos uma Library no banco de dados que faz referência a uma biblioteca (DLL)
existente em disco e uma referência a uma classe (Util) existente em disco.
CAPÍTULO 14: EXTERNAL PROCEDURES - JAVA - 273
PL/SQL9I – BÁSICO E AVANÇADO
REGISTRANDO CADA ROTINA
Registrar cada rotina da biblioteca corresponde à criação da PL/SQL de interface.
O registro é feito com a criação de uma procedure (ou function), que pode ser feito dentro ou fora de
um pacote (standalone), como se fosse uma declaração Forward acrescida da cláusula External
Library.
SINTAXE
Esta cláusula identifica a biblioteca onde está a rotina, seu nome, sua linguagem de programação,
parâmetros, etc.
CAPÍTULO 14: EXTERNAL PROCEDURES - JAVA - 274
PL/SQL9I – BÁSICO E AVANÇADO
Na Sintaxe, temos as seguintes informações:
◊
Language Java Name ‘<string>’ – Especifica que a linguagem em que a rotina externa está
escrita é Java. A cláusula Name identifica o método dentro da classe.
Observe no exemplo que usamos o nome da classe como qualificador do método, além de
incluirmos a especificação dos parâmetros recebidos (como se fosse a “assinatura” do método,
lembra de declaração forward ?).
Caso a classe estivesse em um package, deveríamos, também, usar o nome do package como
por exemplo “<packagename>.<classname>.<metod>”. Não se esqueça que os nomes em Java
são case sensitive.
◊
Language C - Especifica que a linguagem em que a rotina externa está escrita é C.
◊
Name <nome> – Especifica o nome da rotina que desejamos que seja acionada. Essa rotina
deve existir na library informada. Se especificarmos este nome entre aspas, ele se torna Case
Sensitive, caso contrário o nome é pesquisado em maiúsculas. Se esta cláusula for omitida, o
Oracle pesquisará o nome do subprograma PL/SQL em letras maiúsculas.
◊
Library <nome> – Especifica o nome da Library (definida no comando Create Library).
Quando a library for criada, deve ser autorizada (Grant Execute) para todos os usuários (users)
que vierem a utilizar as rotinas desta DLL.
◊
Agent in (<argumento>) – o nome do processo agent corresponde ao nome do database
link, o que permitirá (se as especificações de tnsnames e listener estiverem adequadas) que a
procedure externa seja acionada em outra máquina.
Quando o nome do agente é especificado nesta cláusula, este nome se sobrepõe ao nome do
agente especificado na library. Se nenhum agente for especificado, o default é “extproc”, agente
na mesma máquina que o programa chamado.
◊
With Context – Especifica que um ponteiro de uma área contendo uma série de informações
de ambiente será passado para a rotina externa. O layout desta área não está disponível para a
rotina externa, no entanto, poderá ser utilizada por rotinas de serviço disponibilizadas pela Oracle
para a realização de ações especiais (inclusive acesso ao banco de dados).
◊
Parameters – Especifica a posição e tipo dos parâmetros passados para a rotina externa.
Em função das diferenças entre as linguagens, podemos especificar propriedades dos parâmetros
tais como o comprimento atual, o comprimento máximo e a forma de transferência dos dados (por
valor ou por referência).
CAPÍTULO 14: EXTERNAL PROCEDURES - JAVA - 275
PL/SQL9I – BÁSICO E AVANÇADO
SQL> CREATE OR REPLACE
2 FUNCTION CALCULA(PARM1 IN PLS_INTEGER) RETURN PLS_INTEGER
3 AS LANGUAGE C
4 NAME "calcula"
5 LIBRARY ORASQL;
6 /
Função criada.
SQL> CREATE OR REPLACE FUNCTION DOBRO (P1 NUMBER) RETURN NUMBER
2 AS LANGUAGE JAVA
3 NAME 'Util.calcDobro(int) return int';
4 /
Função criada.
SQL> CREATE OR REPLACE FUNCTION QUADRADO (P1 NUMBER) RETURN NUMBER
2 AS LANGUAGE JAVA
3 NAME 'Util.calcQuadrado(int) return int';
4 /
Função criada.
L14_02
Neste primeiro exemplo, identificamos uma rotina fictícia chamada “calcula” que está declarada dentro
da library OraSql (na verdade, da DLL Orasql.DLL). Como o nome da rotina está entre aspas, é desta
forma que o Oracle procurará por esta rotina dentro da DLL.
A referência a funções de Java é bem mais simples devido à compatibilidade entre os datatypes de
Java e PL/SQL a passagem de parâmetros é simplificada e não precisa de maiores explicações na
criação da função, como vimos acima.
No exemplo a seguir já podemos utilizar, no SQL*Plus os métodos presentes na classe Util.
SQL> SELECT DOBRO(5), QUADRADO(7) FROM DUAL;
DOBRO(5) QUADRADO(7)
---------- ----------10
49
No exemplo acima já podemos ver os resultados do uso dos métodos.
CAPÍTULO 14: EXTERNAL PROCEDURES - JAVA - 276
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 14:
1. Faça um programa que receba como parâmetro uma opção e um número. Se a opção for “Q”
retorne o quadrado do número e se a opção for “D” retorne o dobro do número. Utilize a classe
Util.class externa para a montagem deste exercício.
CAPÍTULO 14: EXTERNAL PROCEDURES - JAVA - 277
PL/SQL9I – BÁSICO E AVANÇADO
CAPÍTULO 15: TABELAS DO CURSO
TABELA DE DEPARTAMENTOS
DADOS
CD_DEPTO
-------------A00
B01
C01
D01
D11
D21
E01
E11
E21
NM_DEPTO
CD_GERENTE CD_DEPTO_CTB
-------------------------------------------------------------------------------------- ----------------------DIRETORIA DA EMPRESA
10
ASSESSORIA
20 A00
CENTRO DE INFORMACAO
30 A00
DIRETORIA DE SISTEMAS
A00
GERENCIA DE SISTEMAS COMERCIAIS
60 D01
GERENCIA DE SISTEMAS ADMINISTRATIVOS
70 D01
DIRETORIA DE SUPORTE/PRODUCAO
50 A00
OPERACAO
90 E01
SUPORTE DE SOFTWARE
100 E01
DESCRIÇÃO
SQL> DESC DEPTO
Name
Null?
------------------------------- -------CD_DEPTO
NOT NULL
NM_DEPTO
CD_GERENTE
CD_DEPTO_CTB
Type
---CHAR(3)
VARCHAR2(40)
NUMBER(5)
CHAR(3)
CAPÍTULO 15: TABELAS DO CURSO - 278
PL/SQL9I – BÁSICO E AVANÇADO
TABELA DE FUNCIONÁRIOS
DADOS
CD_ NM_
MAT FUNC
----------10
20
30
50
60
70
90
100
110
120
130
140
150
160
170
180
190
200
210
220
230
240
250
260
270
280
290
300
310
320
330
340
--------------CRISTINA
MIGUEL
SANDRA
JOAO
IRACY
EVA
ELIANE
TEODORO
VICENTE
SILVIO
DOLORES
HELENA
BRUNO
ELIZABET
GABRIEL
MARIA
JAIRO
DAVI
WILIAM
JOANA
JOAQUIM
SALVADOR
DANIEL
SILVIA
MARTA
ELINE
JOAO
FELIPE
MARINA
ROBERTO
WILSON
DILSON
NM_
CD_
SOBRENOME DEPTO
---------------------HENDERSON
TEIXEIRA
KWAN
GOMES
SOUZA
PEREIRA
HONOFRE
SIQUEIRA
LOURENCO
OLIVA
QUEIROZ
NOVAES
AZEVEDO
PINTO
YVES
SANTOS
WILARES
BARBOSA
JONES
LUZ
JANUARIO
MEDEIROS
SANTANA
JUVENTO
PARENTE
SEVERO
PONTES
SARAIVA
SALGADO
MARQUES
LOPES
GONCALVES
NR_ DT_
RAMAL ADM
NR_ NR_ IN_
CARGO GIT SEXO
------------- ------------- ------------ ------------- -------- ----------A00
3978 01/01/95
66
18 F
B01
3476 01/10/93
61
18 M
C01
4738 05/04/95
60
20 F
E01
6789 17/08/89
58
16 M
D11
6423 14/09/93
55
16 F
D21
7831 30/09/90
56
16 F
E11
5498 15/08/95
55
16 F
E21
972 16/06/90
54
14 M
A00
3490 16/05/94
58
19 M
A00
2167 05/12/93
58
14 M
C01
4578 28/07/91
55
16 F
C01
1793 15/12/91
56
18 F
D11
4510 12/02/92
55
16 M
D11
3782 11/10/93
54
17 F
D11
2890 15/09/89
54
16 M
D11
1682 07/07/90
53
17 F
D11
2986 26/07/94
53
16 M
D11
4501 03/03/96
55
16 M
D11
942 11/04/94
52
17 M
D11
672 29/08/95
55
18 F
D21
2094 21/11/95
53
14 M
D21
3780 05/12/93
55
17 M
D21
961 30/10/99
52
15 M
D21
8953 11/09/95
52
16 F
D21
9001 30/09/90
55
15 F
E11
8997 24/03/91
54
17 F
E11
4502 30/05/90
42
12 M
E11
2095 19/06/92
48
14 M
E11
3332 12/09/91
43
12 F
E21
9990 07/07/90
52
16 M
E21
2103 23/02/96
55
14 M
E21
5698 05/05/96
54
16 M
DT_
NASC
-----------14/08/53
02/02/68
11/05/61
15/09/55
07/07/55
26/05/63
15/05/71
18/12/66
05/11/69
18/10/62
15/09/55
19/01/56
17/05/67
12/04/65
05/01/71
21/02/69
25/06/72
29/05/71
23/02/63
19/03/68
30/05/65
31/03/74
12/11/69
05/10/66
26/05/73
28/03/66
09/07/66
27/10/56
21/04/71
11/08/72
18/07/71
17/05/66
VL_
SAL
---------5275
4125
3825
4017.5
3225
36170
2975
2615
4650
2925
2380
2842
2528
2225
2468
2134
2045
2774
1827
2984
2218
2876
1918
1725
2738
2625
1534
1775
1590
1995
2537
2384
DESCRIÇÃO
SQL> DESC FUNC
Name
Null?
------------------------------- -------CD_MAT
NOT NULL
NM_FUNC
NM_SOBRENOME
CD_DEPTO
NR_RAMAL
DT_ADM
NR_CARGO
NR_GIT
IN_SEXO
DT_NASC
VL_SAL
Type
---NUMBER(5)
VARCHAR2(12)
VARCHAR2(12)
CHAR(3)
NUMBER(4)
DATE
NUMBER(3)
NUMBER(2)
VARCHAR2(1)
DATE
NUMBER(9,2)
CAPÍTULO 15: TABELAS DO CURSO - 279
PL/SQL9I – BÁSICO E AVANÇADO
TABELA DE ATIVIDADES
DADOS
CD_ATIV
------------10
20
30
40
50
60
70
80
90
100
110
120
130
140
150
160
170
180
NM_SIGLA
-----------------------GERENCIA
CUSTO
LEVANTAMENTO
DEFINICAO
APRESENTACAO
LOGICA
CODIGO
TESTE
FISICO
CURSO
PREPARACAO
PESSOAL
OPERACAO
MANUTENCAO
ADMPROD
ADMDB
ADMREDE
DOC
TX_DESCRICAO
---------------------------------------------------GERÊNCIA
ESTIMATIVA DE CUSTO
FASE DE LEVANTAMENTO
DEFINIÇÃO DE PROGRAMAS
APRESENTAÇÃO DO PROJETO
DESCRIÇÃO DA LÓGICA
CODIFICAÇÃO DE PROGRAMAS
TESTE DE PROGRAMAS
PROJETO FÍSICO
MINISTRAR CURSOS
DESENVOLVIMENTO DE CURSOS
ADMINISTRAÇÃO DE PESSOAL
OPERAÇÃO DE SISTEMAS
MANUTENÇÃO DE SOFTWARE
ADMINISTRAÇÃO DE PRODUÇÃO
ADMINISTRAÇÃO BANCO DE DADOS
ADMINISTRAÇÃO DE REDE
DOCUMENTAÇÃO DE SISTEMAS
DESCRIÇÃO
SQL> DESC ATIV
Name
Null?
------------------------------- -------CD_ATIV
NOT NULL
NM_SIGLA
TX_DESCRICAO
Type
---NUMBER(3)
VARCHAR2(12)
VARCHAR2(30)
CAPÍTULO 15: TABELAS DO CURSO - 280
PL/SQL9I – BÁSICO E AVANÇADO
TABELA DE PROJETOS
DADOS
CD_
NM_
PROJ
PROJ
----------- ------------------------------------------------AD3100
AD3110
AD3111
AD3112
AD3113
IF1000
IF2000
MA2100
MA2110
MA2111
MA2112
MA2113
OP1000
OP1010
OP2000
OP2010
OP2011
OP2012
OP2013
PL2100
SERVICOS ADMINISTRATIVOS
ADMINISTRACAO GERAL
PROGRAMACAO DE PAGAMENTO
PROGRAMACAO DE PESSOAL
ASSISTENCIA MEDICA
CONSULTORIA
TREINAMENTO
AUTOMACAO COMERCIAL
PROGRAMACAO
ANALISE
LEVANTAMENTO
DEPURACAO
SUPORTE PRODUCAO
OPERACAO
SISTEMAS DE CONTROLE
SUPORTE SISTEMAS
SUPORTE SOFTWARE
SUPORTE USUARIO
SUPORTE DB/DC
PLANEJAMENTO
CD_
CD_ QT_ DT_
DEPTO RESP EQP INI
---------- --------- ------- ----------A00
10
6 01/01/96
A00
110
6 01/01/96
D21
240
2 01/01/96
D21
250
1 01/01/96
D21
70
2 01/01/96
C01
30
2 01/01/96
C01
130
1 01/01/96
D21
70
12 01/01/96
D11
60
9 01/01/96
D11
150
2 01/01/96
D11
60
3 01/01/96
D11
170
3 15/02/96
E01
50
6 01/01/96
E11
90
5 01/01/96
E01
50
5 01/01/96
E21
320
4 01/01/96
E21
330
1 01/01/96
E21
340
1 01/01/96
E21
100
1 01/01/96
B01
20
1 01/01/96
DT_
FIM
----------01/02/96
01/02/96
01/02/96
01/02/96
01/02/96
01/02/96
01/02/96
01/02/96
01/02/96
01/02/96
01/02/96
15/09/96
01/02/96
01/02/96
01/02/96
01/02/96
01/02/96
01/02/96
01/02/96
01/02/96
DESCRIÇÃO
SQL> DESC PROJ
Name
------------------------------CD_PROJ
NM_PROJ
CD_DEPTO
CD_RESP
QT_EQP
DT_INI
DT_FIM
Null?
-------NOT NULL
NOT NULL
NOT NULL
NOT NULL
Type
---CHAR(6)
VARCHAR2(30)
CHAR(3)
NUMBER(5)
NUMBER(2)
DATE
DATE
CAPÍTULO 15: TABELAS DO CURSO - 281
PL/SQL9I – BÁSICO E AVANÇADO
TABELA DE PROJETOS X ATIVIDADES
DADOS
CD_PROJ
-------------AD3110
AD3110
AD3110
AD3111
AD3111
AD3111
AD3112
AD3112
AD3112
AD3113
AD3113
AD3113
IF1000
IF1000
IF1000
IF2000
IF2000
IF2000
MA2100
MA2100
MA2110
MA2110
MA2110
MA2110
MA2111
MA2111
MA2111
MA2113
MA2113
MA2113
OP2000
OP2000
OP2000
OP2010
OP2010
OP2010
OP2011
OP2011
OP2011
OP2012
OP2012
OP2012
OP2013
CD_ATIV
-------------10
40
70
20
50
80
20
50
80
30
60
90
10
40
70
30
60
90
40
70
10
40
70
100
20
50
80
30
60
90
20
50
80
10
40
70
30
60
90
20
50
80
10
DT_INI
-----------12/04/89
28/12/91
13/09/94
27/06/90
13/03/93
28/11/95
29/06/90
15/03/93
30/11/95
29/03/91
13/12/93
29/08/96
16/03/89
01/12/91
17/08/94
14/12/90
30/08/93
16/05/96
04/11/91
21/07/94
19/02/89
06/11/91
23/07/94
08/04/97
19/11/89
05/08/92
22/04/95
24/10/90
10/07/93
26/03/96
20/08/90
06/05/93
21/01/96
16/06/89
02/03/92
17/11/94
12/06/91
26/02/94
12/11/96
24/07/90
09/04/93
25/12/95
06/08/89
DT_FIM
-----------22/04/89
07/01/92
23/09/94
28/07/90
13/04/93
29/12/95
01/07/90
17/03/93
02/12/95
21/04/91
05/01/94
21/09/96
23/03/89
08/12/91
24/08/94
01/01/91
17/09/93
03/06/96
05/12/91
21/08/94
21/02/89
08/11/91
25/07/94
10/04/97
12/12/89
28/08/92
15/05/95
08/11/90
25/07/93
10/04/96
30/08/90
16/05/93
31/01/96
01/07/89
17/03/92
02/12/94
08/07/91
24/03/94
08/12/96
31/07/90
16/04/93
01/01/96
24/08/89
CAPÍTULO 15: TABELAS DO CURSO - 282
PL/SQL9I – BÁSICO E AVANÇADO
CD_PROJ
-------------OP2013
OP2013
PL2100
PL2100
PL2100
CD_ATIV
------------40
70
20
50
80
DT_INI
-----------22/04/92
07/01/95
02/02/90
19/10/92
06/07/95
DT_FIM
-----------10/05/92
25/01/95
28/02/90
14/11/92
01/08/95
DESCRIÇÃO
SQL> DESC PRJATV
Name
------------------------------CD_PROJ
CD_ATIV
DT_INI
DT_FIM
Null?
-------NOT NULL
NOT NULL
NOT NULL
NOT NULL
Type
---CHAR(6)
NUMBER(3)
DATE
DATE
CAPÍTULO 15: TABELAS DO CURSO - 283
PL/SQL9I – BÁSICO E AVANÇADO
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS
LABORATÓRIO 1
Neste laboratório, passaremos informações para os programas usando variáveis de substituição e o
retorno da informação será feito através de variáveis Bind.
Antes de iniciar, para que as tabelas não fiquem demasiadamente cheias , refaça a base de dados.
1) Observe as declarações a seguir. Indique quais delas não estão corretas e por quê.
DECLARE
ID
X, Y, Z
END
IND_ESTOQUE
99_MES
%TOTAL
TOTAL_DE_PECAS_VENDIDAS_POR_SEMESTRE
BEGIN
NULL;
END;
/
NUMBER(04);
VARCHAR2(10) := "ABC";
DATE NOT NULL;
BOOLEAN
:= 1;
DATE;
NUMBER := 21V99;
NUMBER := 0;
A única declaração correta é a da variável ID.
X, Y e Z estão incorretas, porque não podemos declarar mais de uma variável simultaneamente.
Data_Hoje está incorreta, porque solicitamos que fosse feita a crítica de NOT NULL e não fornecemos
valor inicial para a variável.
End está incorreto, porque é uma palavra reservada e não pode ser nome de variável.
Ind_Estoque está incorreto, porque um boleano só admite valores True, False e Null.
99_Mes está incorreto, porque as variáveis devem começar com uma letra.
%Total está incorreto, porque não começa com letra e porque o valor inicial da variável não é
numérico.
Total_de_Pecas_Vendidas_por_Semestre está incorreto, porque ultrapassa o tamanho de 30
caracteres máximo para nome de variável.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 284
PL/SQL9I – BÁSICO E AVANÇADO
2) Observe o programa PL/SQL apresentado abaixo e avalie o escopo das variáveis, como se pede.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
DECLARE
PESO
VARCHAR2(3) := '600';
MSG
VARCHAR2(255);
BEGIN
DECLARE
PESO
NUMBER(3) := 1;
MSG
VARCHAR2(255);
LOCAL
VARCHAR2(50) := 'RIO DE JANEIRO';
BEGIN
PESO := PESO + 1;
-MSG := PESO || ' ESTÁ ACIMA DO PADRÃO'; -LOCAL:= 'SÃO PAULO '|| LOCAL;
-END;
PESO := PESO + 1;
-MSG := PESO || ' ESTÁ ACIMA DO PADRÃO';
-LOCAL:= 'SÃO PAULO '|| LOCAL;
-END;
/
(A)
(B)
(C)
(D)
(E)
(F)
A) Qual o valor de peso no bloco interno?
Seu valor é 601.
B) Qual o valor de msg no bloco interno?
O valor é “601 está acima do padrão”. A PL/SQL realiza as conversões necessárias para que esta
concatenação possa ser realizada.
C) Qual o valor de local no bloco interno?
O valor é “São Paulo Rio de Janeiro”
D) Qual o valor de peso no bloco externo?
Seu valor é 2.
E) Qual o valor de msg no bloco externo?
Seu valor é “2 está acima do padrão”.
F) Qual o valor de local no bloco externo?
O programa recebe um erro de compilação, pois a variável local não está declarada no bloco externo;
portanto, deixa de existir quando o fim do bloco interno é atingido (end).
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 285
PL/SQL9I – BÁSICO E AVANÇADO
3) Construa um bloco PL/SQL que receba um valor, verifique se o valor é válido e informe ao operador
se foi recebido NULL , um valor numérico positivo, um valor numérico negativo ou zero.
SQL> VARIABLE MSG VARCHAR2(200)
SQL> SET AUTOPRINT ON
SQL> DECLARE
2
VALOR
NUMBER := TO_NUMBER('&VALOR');
3 BEGIN
4
IF VALOR IS NULL THEN
5
:MSG := 'Valor não informado';
6
ELSIF VALOR > 0 THEN
7
:MSG := 'Valor maior que zero - '||TO_CHAR(VALOR);
8
ELSIF VALOR = 0 THEN
9
:MSG := 'Valor igual a zero';
10
ELSE
11
:MSG := 'Valor menor que zero - '||TO_CHAR(VALOR);
12
END IF;
13 END;
14 /
Entre o valor para valor:
antigo
2:
VALOR
NUMBER := TO_NUMBER('&VALOR');
novo
2:
VALOR
NUMBER := TO_NUMBER('');
Procedimento PL/SQL concluído com sucesso.
MSG
-----------------------------------------------------------------Valor não informado
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 286
PL/SQL9I – BÁSICO E AVANÇADO
4) Crie um bloco PL/SQL para incluir linhas na tabela de departamentos. O código e o nome do
departamento devem ser recebidos como parâmetro (usar Accept). A coluna código de gerente não
deve receber valor na inclusão.
O código do departamento é de preenchimento obrigatório, mas o nome é opcional. Se não for
informado, deve ser gravado ‘Departamento sem valor’.
SQL> ACCEPT CODIGO PROMPT 'Informe o código do Departamento : '
Informe o código do Departamento : F01
SQL> ACCEPT NOME PROMPT 'Informe o nome do Departamento : '
Informe o nome do Departamento :
SQL> DECLARE
2
CODIGO
CHAR(03)
:= '&CODIGO';
3
NOME
VARCHAR2(40) := '&NOME';
4 BEGIN
5
IF CODIGO IS NULL THEN
6
:MSG := 'Código do departamento não informado';
7
GOTO FIM;
8
ELSIF NOME IS NULL THEN
9
NOME := 'Departamento sem valor';
10
END IF;
11
INSERT INTO DEPTO (CD_DEPTO, NM_DEPTO)
12
VALUES (CODIGO, NOME);
13
:MSG := 'Inclusão de novo departamento realizada';
14
COMMIT;
15 <<FIM>>
16
NULL;
17 END;
18 /
antigo
2:
CODIGO
CHAR(03)
:= '&CODIGO';
novo
2:
CODIGO
CHAR(03)
:= 'F01';
antigo
3:
NOME
VARCHAR2(40) := '&NOME';
novo
3:
NOME
VARCHAR2(40) := '';
Procedimento PL/SQL concluído com sucesso.
MSG
-------------------------------------------------------------------Inclusão de novo departamento realizada
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 287
PL/SQL9I – BÁSICO E AVANÇADO
5) Crie um bloco PL/SQL que retorne a quantidade de funcionários alocados a um determinado
departamento recebido como parâmetro. O código do departamento é de preenchimento obrigatório.
Lembre-se que o usuário poderá passar a informação em letras maiúsculas ou minúsculas.
SQL> DECLARE
2
CODIGO
CHAR(03) := UPPER('&Codigo_Departamento');
3
QTD
NUMBER
:= 0;
4 BEGIN
5
SELECT COUNT(*) INTO QTD
6
FROM FUNC
7
WHERE CD_DEPTO = CODIGO;
8
:MSG := 'A quantidade de funcionários no departamento '||CODIGO
9
||' é '||TO_CHAR(QTD);
10 END;
11 /
Entre o valor para codigo_departamento: d11
antigo
2:
CODIGO
CHAR(03) := UPPER('&Codigo_Departamento');
novo
2:
CODIGO
CHAR(03) := UPPER('d11');
Procedimento PL/SQL concluído com sucesso.
MSG
---------------------------------------------------------------------A quantidade de funcionários no departamento D11 é 8
6) Crie um bloco de PL/SQL que cadastre novos departamentos ou consulte dados de departamento,
conforme os parâmetros recebidos:
Parâmetros:
◊
Código do departamento, preenchimento obrigatório.
◊
Operação (I ou C), preenchimento obrigatório.
◊
Nome do departamento, obrigatório na inclusão.
◊
Código do gerente, opcional.
Críticas:
◊
Na inclusão, deve ser verificado se o departamento já existe.
◊
Na consulta, deve ser verificada a existência do departamento.
◊
Se o código do gerente for preenchido na inclusão, deve ser garantido que esta matrícula
exista na tabela de funcionários.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 288
PL/SQL9I – BÁSICO E AVANÇADO
SQL> DECLARE
2
CODIGO
CHAR(03)
:= UPPER('&COD_DEPTO');
3
OPERACAO
CHAR(01)
:= UPPER('&OPERACAO');
4
NOME
VARCHAR2(40) := UPPER('&NOME_DEPTO');
5
GERENTE
NUMBER(5)
:= TO_NUMBER('&COD_GERENTE');
6
QTD
NUMBER(1)
:= 0;
7 BEGIN
8
IF OPERACAO NOT IN ('I', 'C') THEN
9
:MSG := 'Operação inválida';
10
GOTO FIM;
11
END IF;
12
SELECT COUNT(*) INTO QTD
13
FROM DEPTO
14
WHERE CD_DEPTO = CODIGO;
15
IF (QTD = 0 AND OPERACAO = 'C') OR
16
(QTD = 1 AND OPERACAO = 'I') THEN
17
:MSG := 'Código do departamento inválido para operação';
18
GOTO FIM;
19
END IF;
20
IF OPERACAO = 'I' AND NOME IS NULL THEN
21
:MSG := 'Nome do Departamento de preenchimento obrigatório';
22
END IF;
23
IF OPERACAO = 'I' THEN
24
INSERT INTO DEPTO (CD_DEPTO, NM_DEPTO, CD_GERENTE)
25
VALUES
(CODIGO, NOME, GERENTE);
26
COMMIT;
27
ELSE
28
SELECT NM_DEPTO, CD_GERENTE INTO NOME, GERENTE
29
FROM DEPTO
30
WHERE CD_DEPTO = CODIGO;
31
:MSG := 'Código = '||CODIGO||' Nome do Depto = '||NOME||
32
' Gerente = '||NVL(TO_CHAR(GERENTE), ' ');
33
END IF;
34 <<FIM>>
35
NULL;
36 END;
37 /
Entre o valor para cod_depto: c01
antigo
2:
CODIGO
CHAR(03)
:= UPPER('&COD_DEPTO');
novo
2:
CODIGO
CHAR(03)
:= UPPER('c01');
Entre o valor para operacao: c
antigo
3:
OPERACAO
CHAR(01)
:= UPPER('&OPERACAO');
novo
3:
OPERACAO
CHAR(01)
:= UPPER('c');
Entre o valor para nome_depto:
antigo
4:
NOME
VARCHAR2(40) := UPPER('&NOME_DEPTO');
novo
4:
NOME
VARCHAR2(40) := UPPER('');
Entre o valor para cod_gerente:
antigo
5:
GERENTE
NUMBER(5)
:= TO_NUMBER('&COD_GERENTE');
novo
5:
GERENTE
NUMBER(5)
:= TO_NUMBER('');
Procedimento PL/SQL concluído com sucesso.
MSG
--------------------------------------------------------------------------Código = C01 Nome do Depto = CENTRO DE INFORMACAO Gerente = 30
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 289
PL/SQL9I – BÁSICO E AVANÇADO
7) Crie um bloco PL/SQL que calcule a seqüência de Fibonacci (1 1 2 3 5 8 13 21 34 55 ...) para um
determinado número de elementos, recebido como parâmetro e variando de 1 a 30.
SQL> DECLARE
2
MAXIMO
NUMBER := TO_NUMBER('&MAXIMO');
3
ATUAL
NUMBER := 1;
4
ANTERIOR
NUMBER := 1;
5
TOTAL
NUMBER := 0;
6
CONTADOR
NUMBER := 2;
7 BEGIN
8
IF MAXIMO NOT BETWEEN 2 AND 30 OR
9
MAXIMO IS NULL THEN
10
:MSG := 'Valor máximo inválido';
11
GOTO FIM;
12
END IF;
13
:MSG := '1 - 1';
14 <<INICIO>>
15
IF CONTADOR < MAXIMO THEN
16
CONTADOR := CONTADOR + 1;
17
TOTAL := ATUAL + ANTERIOR;
18
:MSG := :MSG || ' - ' || TO_CHAR(TOTAL);
19
ANTERIOR := ATUAL;
20
ATUAL := TOTAL;
21
GOTO INICIO;
22
END IF;
23 <<FIM>>
24
NULL;
25 END;
26 /
Entre o valor para maximo: 8
antigo
2:
MAXIMO
NUMBER := TO_NUMBER('&MAXIMO');
novo
2:
MAXIMO
NUMBER := TO_NUMBER('8');
Procedimento PL/SQL concluído com sucesso.
MSG
-----------------------------------------------------------------1 - 1 - 2 - 3 - 5 - 8 - 13 - 21
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 290
PL/SQL9I – BÁSICO E AVANÇADO
8) Crie um bloco PL/SQL que receba como parâmetro uma data e calcule o número de dias úteis no
mês correspondente.
SQL> DECLARE
2
DATA
DATE
:= TO_DATE('&DATA', 'DD/MM/YYYY');
3
DT_INICIO
DATE
:= TRUNC(DATA, 'MM');
4
DT_FIM
DATE
:= LAST_DAY(DATA);
5
QTD
NUMBER := 0;
6 BEGIN
7 <<INICIO>>
8
IF TO_CHAR(DT_INICIO, 'D') NOT IN (1, 7) THEN
9
QTD := QTD + 1;
10
END IF;
11
DT_INICIO := DT_INICIO + 1;
12
IF DT_INICIO <= DT_FIM THEN
13
GOTO INICIO;
14
END IF;
15
:MSG := 'A quantidade de dias úteis no mes '||
16
RTRIM(TO_CHAR(DATA, 'MONTH')) ||' é '|| TO_CHAR(QTD);
17 END;
18 /
Entre o valor para data: 10/03/1999
antigo
2:
DATA
DATE
:= TO_DATE('&DATA', 'DD/MM/YYYY');
novo
2:
DATA
DATE
:= TO_DATE('10/03/1999',
'DD/MM/YYYY');
Procedimento PL/SQL concluído com sucesso.
MSG
---------------------------------------------------------------------A quantidade de dias úteis no mes MARÇO é 23
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 291
PL/SQL9I – BÁSICO E AVANÇADO
9) Num triângulo ABC tem-se A= 15º, sen B = (÷3) /2 e sen C = (÷2) / 2. Determine os ângulos B e C
montando um bloco de PL/SQL.
SQL> DECLARE
2
ANGB
NUMBER;
3
ANGC
NUMBER;
4
ANGA
NUMBER := 15;
5
SUPL
NUMBER;
6 BEGIN
7
ANGB := TRUNC(ASIN((SQRT(3)/2)) * 57.29578);
8
ANGC := TRUNC(ASIN((SQRT(2)/2)) * 57.29578);
9
IF (ANGA + ANGB + ANGC) <> 180 THEN -- A soma dos ângulos de um
10
SUPL := 180 - ANGB;
-- triângulo é 180 graus
11
IF (SUPL + ANGA + ANGC) = 180 THEN
12
ANGB := SUPL;
-- O seno de um ângulo
13
ELSE
-- obtuso é igual ao
14
ANGC := 180 - ANGC;
-- seno do seu complemento
15
END IF;
16
END IF;
17
:MSG := 'ANGB = '|| ANGB || ' ANGC = '||ANGC;
18 END;
19 /
Procedimento PL/SQL concluído com sucesso.
MSG
------------------------------------------------------------------------ANGB = 120 ANGC = 45
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 292
PL/SQL9I – BÁSICO E AVANÇADO
10) Monte um bloco de PL/SQL que receba um ângulo como parâmetro e forneça o seno, cosseno e
tangente do ângulo. O ângulo será fornecido em graus. Se for informado um valor de ângulo negativo,
devemos convertê-lo em positivo e prosseguir o cálculo.
SQL> DECLARE
2
ANG
NUMBER := '&ANG';
3
SENA
NUMBER;
4
COSA
NUMBER;
5
TANA
NUMBER;
6 BEGIN
7
IF ANG < 0 THEN
8
ANG := 360 + ANG;
9
END IF;
10
SENA := ROUND(SIN(ANG / 57.29578), 2);
11
COSA := ROUND(COS(ANG / 57.29578), 2);
12
IF ANG NOT IN (90, 270) THEN
13
TANA := TRUNC(TAN(ANG / 57.29578), 1);
14
:MSG := 'SEN = '||SENA||' COS = '||COSA||
15
' TAN = '||TANA;
16
ELSE
17
:MSG := 'SEN = '||SENA||' COS = '||COSA;
18
END IF;
19 END;
20 /
Entre o valor para ang: 30
antigo
2:
ANG
NUMBER := '&ANG';
novo
2:
ANG
NUMBER := '30';
Procedimento PL/SQL concluído com sucesso.
MSG
------------------------------------------------------SEN = ,5 COS = ,87 TAN = ,5
11) Crie um bloco de PL/SQL que receba como parâmetro um valor alfanumérico com até três
caracteres de comprimento e transforme-o em numérico (use a seqüência do alfabeto: A = 1, B = 2 e
assim por diante).
SQL> DECLARE
2
ALFA
VARCHAR2(03) := UPPER('&ALFA');
3 BEGIN
4
:MSG := TRANSLATE(ALFA, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
5
'12345678901234567890123456');
6 END;
7 /
Entre o valor para alfa: ad
antigo
2:
ALFA
VARCHAR2(03) := UPPER('&ALFA');
novo
2:
ALFA
VARCHAR2(03) := UPPER('ad');
Procedimento PL/SQL concluído com sucesso.
MSG
--------------------------------------------------------------------14
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 293
PL/SQL9I – BÁSICO E AVANÇADO
12) Crie um bloco de PL/SQL que receba como parâmetro um nome e indique quantas partes o
compõem. A separação entre as palavras do nome é feita com espaços em branco (lembre-se que
podem haver vários espaços em branco antes, depois e entre as palavras).
SQL> DECLARE
2
NOME
VARCHAR2(200)
:= LTRIM(RTRIM('&PNOME'));
3
POS
NUMBER
:= INSTR(NOME, ' ');
4
PARTES
NUMBER
:= 1;
5 BEGIN
6
:MSG := 'O NOME ESTÁ VAZIO';
7
IF NOME IS NOT NULL THEN
8 <<INICIO>>
9
IF POS > 0 THEN
10
NOME
:= LTRIM(SUBSTR(NOME, POS));
11
PARTES := PARTES + 1;
12
POS
:= INSTR(NOME, ' ');
13
GOTO INICIO;
14
END IF;
15
:MSG := 'O NOME É COMPOSTO DE '||PARTES||' PARTES';
16
END IF;
17 END;
18 /
Entre o valor para pnome: GG
LKAGL
Q4IOTYQ
ADKHJGA;
777
antigo
2:
NOME
VARCHAR2(200)
:= LTRIM(RTRIM('&PNOME'));
novo
2:
NOME
VARCHAR2(200)
:= LTRIM(RTRIM('GG
LKAGL
Q4IOTYQ
ADKHJGA;
777')
Procedimento PL/SQL concluído com sucesso.
MSG
--------------------------------------------------------------------------O NOME É COMPOSTO DE 5 PARTES
13) Crie um bloco de PL/SQL que receba como parâmetro um número de mês e verifique quantos
funcionários fazem aniversário no mês especificado.
SQL> DECLARE
2
MES
NUMBER := '&MES';
3
QTD
NUMBER := 0;
4 BEGIN
5
IF MES IS NOT NULL THEN
6
SELECT COUNT(*) INTO QTD
7
FROM FUNC
8
WHERE TO_NUMBER(TO_CHAR(DT_NASC, 'MM')) = MES;
9
:MSG := 'O número de aniversariantes no mês '|| mes
10
||' é '||QTD;
11
END IF;
12 END;
13 /
Entre o valor para mes: 5
antigo
2:
MES
NUMBER := '&MES';
novo
2:
MES
NUMBER := '5';
Procedimento PL/SQL concluído com sucesso.
MSG
---------------------------------------------------------------O número de aniversariantes no mês 5 é 8
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 294
PL/SQL9I – BÁSICO E AVANÇADO
14) Crie um bloco de PL/SQL que inclua novos departamentos na tabela Depto, com as seguintes
características:
◊
Receba como parâmetro o código do gerente e o nome do departamento (ambos
obrigatórios).
◊
Obtenha o último código do departamento gravado na tabela e adicione 1, gerando um novo
código (F01 será transformado em F02, F26 será transformado em G00 e assim por diante).
◊
O código do departamento contábil não deve ser preenchido.
SQL> DECLARE
2
CODIGO
NUMBER := '&GERENTE';
3
NOME
VARCHAR2(40) := '&NOME_DEPARTAMENTO';
4
COD_DEPTO
VARCHAR2(03);
5
NUM_DEPTO
NUMBER(02);
6
TXT_DEPTO
CHAR(01);
7 BEGIN
8
IF CODIGO IS NULL OR
9
NOME IS NULL THEN
10
:MSG := 'Parâmetros inválidos';
11
ELSE
12
SELECT MAX(UPPER(CD_DEPTO)) INTO COD_DEPTO FROM DEPTO;
13
TXT_DEPTO := SUBSTR(COD_DEPTO,1,1);
14
NUM_DEPTO := SUBSTR(COD_DEPTO,2,2);
15
IF NUM_DEPTO < 25 THEN
16
NUM_DEPTO := NUM_DEPTO + 1;
17
COD_DEPTO := TXT_DEPTO||LTRIM(TO_CHAR(NUM_DEPTO, '00'));
18
ELSE
19
TXT_DEPTO := TRANSLATE(TXT_DEPTO, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
20
'BCDEFGHIJKLMNOPQRSTUVWXYZ1');
21
COD_DEPTO := TXT_DEPTO || '00';
22
END IF;
23
INSERT INTO DEPTO(CD_DEPTO, NM_DEPTO, CD_GERENTE)
24
VALUES (COD_DEPTO, NOME, CODIGO);
25
END IF;
26 END;
27 /
Entre o valor para gerente: 70
antigo
2:
CODIGO
NUMBER := '&GERENTE';
novo
2:
CODIGO
NUMBER := '70';
Entre o valor para nome_departamento: NOVO DEPTO
antigo
3:
NOME
VARCHAR2(40) := '&NOME_DEPARTAMENTO';
novo
3:
NOME
VARCHAR2(40) := 'NOVO DEPTO';
Procedimento PL/SQL concluído com sucesso.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 295
PL/SQL9I – BÁSICO E AVANÇADO
15) Faça um bloco PL/SQL que receba como parâmetro um código de departamento e calcule o total
de salários dos funcionários. Arredonde o resultado para a ordem de grandeza das centenas.
SQL> DECLARE
2
CODIGO
VARCHAR2(03) := UPPER('&DEPTO');
3
SAL
NUMBER;
4 BEGIN
5
SELECT SUM(VL_SAL) INTO SAL
6
FROM FUNC
7
WHERE CD_DEPTO = CODIGO;
8
SAL := ROUND(SAL, -2);
9
:MSG := 'O total salarial do departamento '||CODIGO||' é '||SAL;
10 END;
11 /
Entre o valor para depto: D11
antigo
2:
CODIGO
VARCHAR2(03) := UPPER('&DEPTO');
novo
2:
CODIGO
VARCHAR2(03) := UPPER('D11');
Procedimento PL/SQL concluído com sucesso.
MSG
-----------------------------------------------------------------------O total salarial do departamento D11 é 125000
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 296
PL/SQL9I – BÁSICO E AVANÇADO
16) Faça um bloco PL/SQL que receba como parâmetro uma opção alfanumérica e calcule um dos
seguintes valores:
◊
o timestamp atual;
◊
o nome por extenso da timezone do banco de dados;
◊
o mes e o dia em que o exercício está sendo feito.
SQL> DECLARE
2
OPCAO
VARCHAR2(1) := UPPER('&OPÇÃO');
3 BEGIN
4
CASE OPCAO
5
WHEN 'A' THEN
6
:MSG := CURRENT_TIMESTAMP;
7
WHEN 'B' THEN
8
:MSG := EXTRACT(TIMEZONE_REGION FROM FROM_TZ(LOCALTIMESTAMP, DBTIMEZONE));
9
WHEN 'C' THEN
10
:MSG := EXTRACT(DAY FROM CURRENT_DATE)||'-'||EXTRACT(MONTH FROM CURRENT_DATE);
11
ELSE
12
:MSG := 'Valor inválido';
13
END CASE;
14 END;
15 /
Entre o valor para opção: A
antigo
2:
OPCAO
VARCHAR2(1) := UPPER('&OPÇÃO');
novo
2:
OPCAO
VARCHAR2(1) := UPPER('A');
Procedimento PL/SQL concluído com sucesso.
MSG
----------------------------------------------------------------------------------07/06/03 16:17:33,000000815 -03:00
SQL> /
Entre o valor para opção: B
antigo
2:
OPCAO
VARCHAR2(1) := UPPER('&OPÇÃO');
novo
2:
OPCAO
VARCHAR2(1) := UPPER('B');
Procedimento PL/SQL concluído com sucesso.
MSG
----------------------------------------------------------------------------------UNKNOWN
SQL> /
Entre o valor para opção: C
antigo
2:
OPCAO
VARCHAR2(1) := UPPER('&OPÇÃO');
novo
2:
OPCAO
VARCHAR2(1) := UPPER('C');
Procedimento PL/SQL concluído com sucesso.
MSG
----------------------------------------------------------------------------------7-6
SQL> /
Entre o valor para opção: D
antigo
2:
OPCAO
VARCHAR2(1) := UPPER('&OPÇÃO');
novo
2:
OPCAO
VARCHAR2(1) := UPPER('D');
Procedimento PL/SQL concluído com sucesso.
MSG
-------------------------------------------------------------------------------------Valor inválido
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 297
PL/SQL9I – BÁSICO E AVANÇADO
17) Repita o exercício anterior usando “expressão case” .
SQL> DECLARE
2
OPCAO
VARCHAR2(1) := UPPER('&OPÇÃO');
3 BEGIN
4
:msg := CASE OPCAO
5
WHEN 'A' THEN
6
TO_CHAR(CURRENT_TIMESTAMP)
7
WHEN 'B' THEN
8
EXTRACT(TIMEZONE_REGION FROM FROM_TZ(LOCALTIMESTAMP, DBTIMEZONE))
9
WHEN 'C' THEN
10 TO_CHAR(EXTRACT(DAY FROM CURRENT_DATE)||'-'||EXTRACT(MONTH FROM CURRENT_DATE))
11
ELSE
12
'Valor inválido'
13
END;
14 END;
15 /
Entre o valor para opção: A
antigo
2:
OPCAO
VARCHAR2(1) := UPPER('&OPÇÃO');
novo
2:
OPCAO
VARCHAR2(1) := UPPER('A');
Procedimento PL/SQL concluído com sucesso.
MSG
----------------------------------------------------------------------------------07/06/03 16:22:52,000000153 -03:00
SQL> /
Entre o valor para opção: B
antigo
2:
OPCAO
VARCHAR2(1) := UPPER('&OPÇÃO');
novo
2:
OPCAO
VARCHAR2(1) := UPPER('B');
Procedimento PL/SQL concluído com sucesso.
MSG
----------------------------------------------------------------------------------UNKNOWN
SQL> /
Entre o valor para opção: C
antigo
2:
OPCAO
VARCHAR2(1) := UPPER('&OPÇÃO');
novo
2:
OPCAO
VARCHAR2(1) := UPPER('C');
Procedimento PL/SQL concluído com sucesso.
MSG
----------------------------------------------------------------------------------7-6
SQL> /
Entre o valor para opção: D
antigo
2:
OPCAO
VARCHAR2(1) := UPPER('&OPÇÃO');
novo
2:
OPCAO
VARCHAR2(1) := UPPER('D');
Procedimento PL/SQL concluído com sucesso.
MSG
----------------------------------------------------------------------------------Valor inválido
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 298
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 2
Todos os exercícios deste laboratório devem ser feitos utilizando-se os atributos %ROWTYPE ou
%TYPE.
1) Leia os n maiores salários da tabela de funcionários e apresente-os em ordem ascendente.
SQL> DECLARE
2
QTD
NUMBER := NVL(TO_NUMBER('&QTD'), 0);
3
CURSOR C1
IS SELECT VL_SAL FROM FUNC
4
WHERE VL_SAL IS NOT NULL
5
ORDER BY VL_SAL DESC;
6 BEGIN
7
:MSG := '';
8
FOR RC1 IN C1 LOOP
9
IF QTD > 0 THEN
10
QTD := QTD - 1;
11
ELSE
12
EXIT;
13
END IF;
14
:MSG := RC1.VL_SAL||' '||:MSG;
15
END LOOP;
16
:MSG := 'Os maiores salários são: '|| :MSG;
17 END;
18 /
Entre o valor para qtd: 5
antigo
2:
QTD
NUMBER := NVL(TO_NUMBER('&QTD'), 0);
novo
2:
QTD
NUMBER := NVL(TO_NUMBER('5'), 0);
Procedimento PL/SQL concluído com sucesso.
MSG
--------------------------------------------------------------------Os maiores salários são: 23116,32 25250,47 25250,47 28644,3 35088,71
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 299
PL/SQL9I – BÁSICO E AVANÇADO
2) Crie uma tabela com o comando apresentado abaixo para receber os n maiores salários da tabela
de funcionários. O parâmetro n deve ser recebido através de Accept. Na coluna total deve ser
armazenado o somatório de todos os salários lidos.
SQL> CREATE TABLE TABTOTAL
2 (NOME
VARCHAR2(25),
3
SALARIO
NUMBER(8),
4
TOTAL
NUMBER(11)
5 );
Tabela criada.
Inclua todos os dados e posteriormente atualize a coluna total com o valor obtido na soma.
SQL> DECLARE
2
QTD
NUMBER := NVL(TO_NUMBER('&QTD'),0);
3
VTOTAL
NUMBER := 0;
4
CURSOR C1
IS SELECT NM_FUNC, VL_SAL FROM FUNC
5
WHERE VL_SAL IS NOT NULL
6
ORDER BY 2 DESC;
7 BEGIN
8
DELETE FROM TABTOTAL;
9
FOR RC1 IN C1 LOOP
10
EXIT WHEN C1%ROWCOUNT > QTD;
11
VTOTAL := VTOTAL + RC1.VL_SAL;
12
INSERT INTO TABTOTAL
13
VALUES (RC1.NM_FUNC, RC1.VL_SAL, NULL);
14
END LOOP;
15
UPDATE TABTOTAL
16
SET TOTAL = VTOTAL;
17
COMMIT;
18 END;
19 /
Entre o valor para qtd: 5
antigo
2:
QTD
NUMBER := NVL(TO_NUMBER('&QTD'),0);
novo
2:
QTD
NUMBER := NVL(TO_NUMBER('5'),0);
Procedimento PL/SQL concluído com sucesso.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 300
PL/SQL9I – BÁSICO E AVANÇADO
3) Faça um programa PL/SQL que receba um código de departamento como parâmetro e apresente o
nome do gerente do departamento e todos os funcionários (matrícula e nome) alocados àquele
departamento.
Utilize cursor com parâmetro.
SQL> DECLARE
2
CD_DEPTO
CHAR(03) := '&DEPTO';
3
CURSOR C1
(VCD_DEPTO IN CHAR) IS
4
SELECT CD_DEPTO, CD_GERENTE FROM DEPTO
5
WHERE CD_DEPTO = VCD_DEPTO;
6
CURSOR C2
(VCD_DEPTO IN CHAR, VCD_MAT IN NUMBER) IS
7
SELECT CD_MAT, NM_FUNC,
8
DECODE(CD_MAT, VCD_MAT, 'GERENTE', 'FUNCIONARIO') CARGO
9
FROM FUNC
10
WHERE CD_MAT = VCD_MAT
11
OR CD_DEPTO = VCD_DEPTO;
12 BEGIN
13
:MSG := 'Departamento inválido';
14
FOR RC1 IN C1(CD_DEPTO) LOOP
15
:MSG := '';
16
FOR RC2 IN C2(RC1.CD_DEPTO, RC1.CD_GERENTE) LOOP
17
:MSG := LTRIM(RTRIM(:MSG))||' MAT = '||RC2.CD_MAT||
18
' NOME = '||RC2.NM_FUNC||' CARGO = '||RC2.CARGO||';';
19
END LOOP;
20
END LOOP;
21 END;
22 /
Entre o valor para depto: A00
antigo
2:
CD_DEPTO
CHAR(03) := '&DEPTO';
novo
2:
CD_DEPTO
CHAR(03) := 'A00';
Procedimento PL/SQL concluído com sucesso.
MSG
-------------------------------------------------------------------------------MAT = 10 NOME = CRISTINA CARGO = GERENTE; MAT = 110 NOME = VICENTE CARGO = FUNCI
ONARIO; MAT = 120 NOME = SILVIO CARGO = FUNCIONARIO;
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 301
PL/SQL9I – BÁSICO E AVANÇADO
4) Crie um bloco de PL/SQL que faça o enquadramento dos funcionários de acordo com os seguintes
critérios:
◊
O piso salarial será de 1.000,00 para os cargos inferiores a 51. Para os demais, o piso
salarial será de 1.500,00.
◊
Para cada nível de cargo superior a 55, deverá ser acrescido 250 ao salário.
◊
Para cada nível de instrução superior a 15, deverá ser acrescido 30 ao salário.
O salário do funcionário não poderá ser diminuído. Se o funcionário já receber acima do valor
calculado, deve-se manter o salário anterior.
Deve-se apresentar, simultaneamente, todos os enquadrados (com o velho e novo salário) e aqueles
não enquadrados (com o salário antigo).
SQL> VARIABLE MSG VARCHAR2(2000)
SQL> DECLARE
2
SAL
NUMBER;
3
CURSOR C1
IS SELECT CD_MAT, VL_SAL, NR_GIT, NR_CARGO
4
FROM FUNC
5
WHERE VL_SAL IS NOT NULL
6
FOR UPDATE OF VL_SAL;
7 BEGIN
8
:MSG := '';
9
FOR RC1 IN C1 LOOP
10
IF RC1.NR_CARGO < 51 OR RC1.NR_CARGO IS NULL THEN
11
SAL := 1000;
12
ELSE
13
SAL := 1500;
14
END IF;
15
IF RC1.NR_CARGO > 55 THEN
16
SAL := SAL + (RC1.NR_CARGO - 55) * 250;
17
END IF;
18
IF RC1.NR_GIT > 15 THEN
19
FOR I IN 16..RC1.NR_GIT LOOP
20
SAL := SAL + 30;
21
END LOOP;
22
END IF;
23
IF RC1.VL_SAL < SAL THEN
24
UPDATE FUNC
25
SET VL_SAL = SAL
26
WHERE CURRENT OF C1;
27
:MSG := LTRIM(RTRIM(:MSG))||' MAT = '||RC1.CD_MAT||
28
' SAL.ANT = '||RC1.VL_SAL||' SAL.NOVO = '||SAL||';';
29
ELSE
30
:MSG := LTRIM(RTRIM(:MSG))||' MAT = '||RC1.CD_MAT||
31
' SALARIO= '||RC1.VL_SAL||';';
32
END IF;
33
END LOOP;
34
COMMIT;
35 END;
36 /
Procedimento PL/SQL concluído com sucesso.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 302
PL/SQL9I – BÁSICO E AVANÇADO
5) Crie uma tabela com o comando apresentado abaixo para receber a quantidade de funcionários por
departamento. O código do departamento deverá ser convertido (de alfanumérico para numérico).
Utilize Loop e Exit When. Use a função AscII para a conversão do código.
SQL> CREATE TABLE RESULTADO
2 (CD_DEPTO
NUMBER,
3
QT_FUNC
NUMBER,
4
VL_SAL
NUMBER,
5
AV_SAL
NUMBER);
Tabela criada.
Neste exercício, só deve ser preenchida a quantidade de funcionários por departamento.
SQL> CREATE TABLE RESULTADO
2 (CD_DEPTO
NUMBER,
3
QT_FUNC
NUMBER,
4
VL_SAL
NUMBER,
5
AV_SAL
NUMBER);
Tabela criada.
SQL> DECLARE
2
CODIGO
NUMBER;
3
CURSOR C1
IS SELECT CD_DEPTO, COUNT(*) QTD
4
FROM FUNC
5
WHERE CD_DEPTO IS NOT NULL
6
GROUP BY CD_DEPTO;
7
RC1
C1%ROWTYPE;
8 BEGIN
9
OPEN C1;
10
LOOP
11
FETCH C1 INTO RC1;
12
EXIT WHEN C1%NOTFOUND;
13
CODIGO := (ASCII(SUBSTR(UPPER(RC1.CD_DEPTO),1,1)) - 64) * 100;
14
CODIGO := CODIGO + TO_NUMBER(SUBSTR(RC1.CD_DEPTO,2,2));
15
INSERT INTO RESULTADO (CD_DEPTO, QT_FUNC)
16
VALUES
(CODIGO, RC1.QTD);
17
END LOOP;
18 END;
19 /
Procedimento PL/SQL concluído com sucesso.
SQL> SELECT * FROM RESULTADO;
CD_DEPTO
QT_FUNC
VL_SAL
AV_SAL
---------- ---------- ---------- ---------100
3
102
8
201
1
301
2
401
3
411
8
412
1
421
6
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 303
PL/SQL9I – BÁSICO E AVANÇADO
6) Utilizando a mesma tabela já parcialmente preenchida do exercício anterior, complete-a com o total
de salários e a média salarial.
Utilize Cursor Loop. Inicie a leitura pela tabela resultado e trabalhe com Update Where Current.
SQL> DECLARE
2
CURSOR C1
IS SELECT * FROM RESULTADO
3
FOR UPDATE OF VL_SAL, AV_SAL;
4
VCD_DEPTO
VARCHAR2(5);
5
VSUM
NUMBER;
6
VAVG
NUMBER;
7
LETRA
NUMBER;
8 BEGIN
9
:MSG := '';
10
FOR RC1 IN C1 LOOP
11
VCD_DEPTO := LTRIM(TO_CHAR(RC1.CD_DEPTO));
12
LETRA := TO_NUMBER(SUBSTR(VCD_DEPTO,1,LENGTH(VCD_DEPTO)-2));
13
VCD_DEPTO := CHR(LETRA+64)||SUBSTR(VCD_DEPTO,LENGTH(VCD_DEPTO) -1);
14
SELECT ROUND(SUM(VL_SAL)), ROUND(AVG(VL_SAL))
15
INTO VSUM, VAVG
16
FROM FUNC
17
WHERE CD_DEPTO = VCD_DEPTO;
18
:MSG := :MSG||VCD_DEPTO ||'-'||VSUM||'-'||VAVG||'; ';
19
UPDATE RESULTADO
20
SET VL_SAL = VSUM,
21
AV_SAL = VAVG
22
WHERE CURRENT OF C1;
23
END LOOP;
24 END;
25 /
Procedimento PL/SQL concluído com sucesso.
MSG
----------------------------------------------------------------------------A00-69778-23259; A02-12209-1526; B01-22400-22400; C01-37572-18786; D01-30001000; D11-124963-15620; D12-1000-1000; D21-109342-18224; E01-21816-21816;
E11-69269-13854; E21-83911-20978;
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 304
PL/SQL9I – BÁSICO E AVANÇADO
7) Crie um bloco de PL/SQL que receba como parâmetro um número de mês e verifique quais os
funcionários aniversariantes. Apresentar o nome e dia de nascimento ordenado por dia de nascimento.
Usar While.
SQL> DECLARE
2
MES
NUMBER := '&MES';
3
CURSOR C1
IS SELECT NM_FUNC, TO_CHAR(DT_NASC, 'DD') DIA
4
FROM FUNC
5
WHERE TO_NUMBER(TO_CHAR(DT_NASC, 'MM')) = MES
6
ORDER BY DIA;
7
RC1
C1%ROWTYPE;
8 BEGIN
9
:MSG := 'Os aniversariantes do mês são : '||CHR(10);
10
OPEN C1;
11
FETCH C1 INTO RC1;
12
WHILE C1%FOUND LOOP
13
:MSG := :MSG ||RPAD(RC1.NM_FUNC,13)||LPAD(RC1.DIA,2)||'; '||CHR(10);
14
FETCH C1 INTO RC1;
15
END LOOP;
16 END;
17 /
Entre o valor para mes: 3
Procedimento PL/SQL concluído com sucesso.
MSG
----------------------------------------------------------------------------Os aniversariantes do mês são :
JOANA
19;
SALVADOR
31;
8) Crie um bloco de PL/SQL que receba como parâmetro (de substituição) um grau de instrução e um
código de departamento e determine o cargo, salário e matrícula deste funcionário. Se existir mais de
um, escolha o funcionário mais novo. Se não existir nenhum, apresente mensagem correspondente.
SQL> DECLARE
2
CURSOR C1
IS SELECT CD_MAT, NR_CARGO, VL_SAL
3
FROM FUNC
4
WHERE CD_DEPTO = UPPER('&DEPTO')
5
AND NR_GIT
= TO_NUMBER('&GIT')
6
ORDER BY DT_NASC DESC;
7
RC1
C1%ROWTYPE;
8 BEGIN
9
OPEN C1;
10
FETCH C1 INTO RC1;
11
IF C1%NOTFOUND THEN
12
:MSG := 'Não existem funcionários na situação informada';
13
ELSE
14
:MSG := 'MAT = '||RC1.CD_MAT||' SAL = '||RC1.VL_SAL||
15
' CARGO = '||RC1.NR_CARGO;
16
END IF;
17 END;
18 /
Entre o valor para depto: d11
Entre o valor para git: 18
Procedimento PL/SQL concluído com sucesso
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 305
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 3:
1. Considerando-se o programa PL/SQL abaixo, responda às questões que se seguem:
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
DECLARE
VALOR
A
B
C
BEGIN
BEGIN
IF
NUMBER(3) := &X;
EXCEPTION;
EXCEPTION;
EXCEPTION;
VALOR = 1 THEN
RAISE A;
ELSIF VALOR = 2 THEN
RAISE B;
ELSIF VALOR = 3 THEN
RAISE C;
END IF;
VALOR := 100;
EXCEPTION
WHEN A THEN
VALOR := 200;
END;
VALOR := 300;
EXCEPTION
WHEN B THEN
VALOR := 400;
END;
/
A) O que acontece se X = 0? Qual o conteúdo de valor no fim do programa?
Para x = 0 e qualquer valor diferente de 1, 2 e 3, o programa termina normalmente. O conteúdo de
valor ao término do programa é 100.
B) O que acontece se X = 1? Qual o conteúdo de valor no fim do programa?
A condição de erro A é causada; ocorre o desvio da execução para a área de exception do bloco
interno, onde a exception é tratada e a variável valor recebe 300. O programa termina normalmente
com a variável Valor contendo 300.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 306
PL/SQL9I – BÁSICO E AVANÇADO
C) O que acontece se X = 2? Qual o conteúdo de valor no fim do programa?
Se x receber o valor 2, será causada a condição de erro B, que levará a um desvio para a área de
tratamento do bloco interno. Não havendo tratamento para B, a exception é propagada para o bloco
externo, havendo imediatamente um desvio para a área de tratamento do bloco externo, onde existe
um tratamento para a exception B. O programa termina normalmente. O conteúdo de valor ao término
no programa é 400.
D) O que acontece se X = 3? Qual o conteúdo de valor no fim do programa?
Será causada a condição de erro C, que não encontra tratamento no bloco interno, sendo, então,
propagada para o bloco externo, onde também não existe tratamento para o erro. Desta forma, o
programa aborta com a mensagem de erro ORA-06510: PL/SQL: exceção não manipulada definida
pelo usuário.
2. Faça um programa para realizar o cadastramento de funcionários que estabeleça as seguintes
críticas:
◊
O funcionário não deve existir no banco de dados. Para tal, consulte por nome e sobrenome.
◊
O código do departamento deve existir na tabela de departamentos.
◊
Só serão admitidos funcionários com menos de 50 anos.
◊
Só serão admitidos funcionários com grau de instrução entre 12 e 18.
◊
O cargo deverá estar, garantidamente, entre 55 e 60.
◊
Sexo deverá estar preenchido com F ou M.
Os parâmetros informados são: nome e sobrenome do funcionário, código do departamento, grau de
instrução, data de nascimento, cargo e sexo.
Para cada um dos erros encontrados, o programa deverá ser interrompido com a mensagem
adequada. Obrigatoriamente, o tratamento deverá utilizar a área de Exception.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 307
PL/SQL9I – BÁSICO E AVANÇADO
SQL> DECLARE
2
NOME
VARCHAR2(12) := UPPER('&NOME');
3
SOBRENOME
VARCHAR2(12) := UPPER('&SOBRENOME');
4
CODIGO
VARCHAR2(03) := UPPER('&DEPTO');
5
GIT
NUMBER(02)
:= '&GIT';
6
DATA
DATE
:= TO_DATE('&DATA', 'DD/MM/YYYY');
7
CARGO
NUMBER(02)
:= '&CARGO';
8
SEXO
VARCHAR2(01) := UPPER('&SEXO');
9
DUMMY
NUMBER
:= 0;
10
IDADE
EXCEPTION;
11
INSTRU
EXCEPTION;
12 BEGIN
13
BEGIN
14
SELECT CD_MAT INTO DUMMY FROM FUNC
15
WHERE NM_FUNC = NOME AND NM_SOBRENOME = SOBRENOME;
16
RAISE_APPLICATION_ERROR(-20001, 'Funcionário já existente');
17
EXCEPTION
18
WHEN NO_DATA_FOUND THEN
19
NULL;
20
END;
21
SELECT CD_GERENTE INTO DUMMY FROM DEPTO
22
WHERE CD_DEPTO = CODIGO;
23
IF (SYSDATE - DATA) / 365.25 >= 50 THEN
24
RAISE IDADE;
25
END IF;
26
IF GIT NOT BETWEEN 12 AND 18 THEN
27
RAISE INSTRU;
28
END IF;
29
IF CARGO NOT BETWEEN 55 AND 60 THEN
30
RAISE_APPLICATION_ERROR(-20005, 'Cargo inválido');
31
END IF;
32
IF SEXO NOT IN ('F', 'M') THEN
33
RAISE_APPLICATION_ERROR(-20006, 'Sexo diferente de F e M');
34
END IF;
35
-- INCLUSÃO
36
SELECT MAX(CD_MAT) + 1 INTO DUMMY
37
FROM FUNC;
38
INSERT INTO FUNC(CD_MAT, NM_FUNC, NM_SOBRENOME, DT_NASC,
39
CD_DEPTO, NR_GIT, NR_CARGO, IN_SEXO)
40
VALUES (DUMMY, NOME, SOBRENOME, DATA,
41
CODIGO, GIT, CARGO, SEXO);
42 EXCEPTION
43 WHEN NO_DATA_FOUND THEN
44
RAISE_APPLICATION_ERROR(-20002, 'Departamento inexistente');
45 WHEN IDADE THEN
46
RAISE_APPLICATION_ERROR(-20003, 'Idade muito elevada');
47 WHEN INSTRU THEN
48
RAISE_APPLICATION_ERROR(-20004, 'Grau de instrução inválido');
49 END;
50 /
Entre o valor para nome: Tereza
antigo
2:
NOME
VARCHAR2(12) := UPPER('&NOME');
novo
2:
NOME
VARCHAR2(12) := UPPER('Tereza');
Entre o valor para sobrenome: Batista
antigo
3:
SOBRENOME
VARCHAR2(12) := UPPER('&SOBRENOME');
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 308
PL/SQL9I – BÁSICO E AVANÇADO
3. Para o programa apresentado abaixo, identifique e explique por que ocorreu o erro.
SQL> DECLARE
2
DUMMY
NUMBER := '&NUMERO';
3 BEGIN
4
IF DUMMY > 5 THEN
5
:VALOR := 0;
6
END IF;
7 EXCEPTION
8 WHEN OTHERS THEN
9
RAISE_APPLICATION_ERROR(-20999, 'Erro de atribuição na declaração');
10 END;
11 /
Entre o valor para numero: A
antigo
2:
DUMMY
NUMBER := '&NUMERO';
novo
2:
DUMMY
NUMBER := 'A';
DECLARE
*
ERRO na linha 1:
ORA-06502: PL/SQL: error: erro de conversão de caractere em número numérico ou
de valor
ORA-06512: em line 2
O erro ocorre mesmo com a presença da área de tratamento de erro, porque erros adquiridos a tempo
de declaração são propagados diretamente para o bloco externo imediatamente superior àquele em
que ocorreu o erro, não sendo percebidos pela área de tratamento. No nosso caso, não existe bloco
externo, portanto a propagação ocorre para o ambiente, derrubando o programa.
4. Qual a solução de contorno que permita que controlemos o tipo de erro do exercício 3 ?
SQL> VARIABLE VALOR NUMBER
SQL> BEGIN
2
DECLARE
3
DUMMY
NUMBER := '&NUMERO';
4
BEGIN
5
IF DUMMY > 5 THEN
6
:VALOR := 0;
7
END IF;
8
EXCEPTION
9
WHEN OTHERS THEN
10
RAISE_APPLICATION_ERROR(-20999, 'ERRO DE ATRIBUIÇÃO NA DECLARAÇÃO');
11
END;
12 EXCEPTION
13 WHEN OTHERS THEN
14
:MSG := SQLERRM(SQLCODE);
15 END;
16 /
Entre o valor para numero: A
Procedimento PL/SQL concluído com sucesso.
A solução de contorno é a criação de um bloco externo àquele em que estava ocorrendo o erro. Este
bloco é capaz de capturar as condições de erro ocorridas na declaração do bloco interno.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 309
PL/SQL9I – BÁSICO E AVANÇADO
5. Crie um programa para cadastramento de funcionários. Informa, somente, dois parâmetros:
Matrícula e Código do departamento.
O programa não deve abortar se a matrícula já existir (Dup_Val_On_Index) ou se o código do
departamento não existir na tabela Depto (use a diretiva Exception_Init para definir este controle).
SQL> DECLARE
2
MATRICULA
NUMBER := '&MAT';
3
DEPTO
VARCHAR2(03) := '&DEPTO';
4
R_DEPTO
EXCEPTION;
5
PRAGMA
EXCEPTION_INIT(R_DEPTO, -2291);
6 BEGIN
7
INSERT INTO FUNC(CD_MAT, CD_DEPTO)
8
VALUES (MATRICULA, DEPTO);
9 EXCEPTION
10 WHEN DUP_VAL_ON_INDEX THEN
11
:MSG := 'Matrícula já existente';
12 WHEN R_DEPTO THEN
13
:MSG := 'Relacionamento inexistente';
14 END;
15 /
Entre o valor para mat: 3
Entre o valor para depto: DDD
Procedimento PL/SQL concluído com sucesso.
MSG
----------------------------------------------------Relacionamento inexistente
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 310
PL/SQL9I – BÁSICO E AVANÇADO
6. Faça um programa que receba três números e os apresente em ordem ascendente. Para tal,
estabeleça todas as críticas necessárias para que o usuário forneça dados corretos.
SQL> BEGIN
2
DECLARE
3
V1
NUMBER := '&VALOR_1';
4
V2
NUMBER := '&VALOR_2';
5
V3
NUMBER := '&VALOR_3';
6
DUMMY
NUMBER := 0;
7
TROCA
BOOLEAN:= TRUE;
8
BEGIN
9
IF V1 = V2 AND V1 = V3 THEN
10
:MSG := 'Os três números são iguais';
11
ELSE
12
WHILE TROCA LOOP
13
TROCA := FALSE;
14
IF V1 > V2 THEN
15
DUMMY := V1;
16
V1
:= V2;
17
V2
:= DUMMY;
18
TROCA := TRUE;
19
END IF;
20
IF V2 > V3 THEN
21
DUMMY := V2;
22
V2
:= V3;
23
V3
:= DUMMY;
24
TROCA := TRUE;
25
END IF;
26
END LOOP;
27
END IF;
28
:MSG := 'Ordem ascendente : '||V1||'-'||V2||'-'||V3;
29
END;
30 EXCEPTION
31 WHEN VALUE_ERROR THEN
32
:MSG := 'Valor inválido na atribuição';
33 END;
34 /
Entre o valor para valor_1: 8
antigo
3:
V1
NUMBER := '&VALOR_1';
novo
3:
V1
NUMBER := '8';
Entre o valor para valor_2: 4
antigo
4:
V2
NUMBER := '&VALOR_2';
novo
4:
V2
NUMBER := '4';
Entre o valor para valor_3: 9
antigo
5:
V3
NUMBER := '&VALOR_3';
novo
5:
V3
NUMBER := '9';
Procedimento PL/SQL concluído com sucesso.
MSG
-------------------------------------------------------------------Ordem ascendente : 4-8-9
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 311
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 4:
1. Quais os três tipos de coleções existentes em PL/SQL? Cite duas características das Index-By
Tables.
A PL/SQL trabalha com os seguintes tipos de coleções: Nested Tables, Varrays e Index-By Tables.
Como característica comum entre as coleções de PL/SQL, diríamos que funcionam como arrays e
devem ser indexadas por um valor inteiro.
Dentre as características das Index-By Tables temos:
◊
Index-By Tables são definidas usando-se a cláusula Index By Binary_Integer.
◊
Uma Index-By Table não inicializada está apenas vazia e não podemos comparar usando IS
NULL.
◊
A tempo de execução, Index-By Tables tornam-se non-null automaticamente.
2. O que são registros em PL/SQL?
Em PL/SQL, registros são áreas estruturadas, ou seja, um conjunto de itens de dados contendo seus
próprios nomes e tipos, que estão relacionados através de um nome comum (nome do registro).
Uma das formas de criação de registros é através do atributo %Rowtype, que pode criar uma área
estruturada baseado nas colunas de uma tabela definida no banco de dados ou nas colunas
selecionadas em um cursor.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 312
PL/SQL9I – BÁSICO E AVANÇADO
3. Crie um bloco de PL/SQL que receba números (quantidade qualquer) e apresente-os em ordem
ascendente. O programa deve receber um único parâmetro alfanumérico contendo os números
separados por vírgula (use coleção).
SQL> DECLARE
2
TYPE TAB
IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
3
LISTA
VARCHAR2(100) := LTRIM(RTRIM('&NUMEROS'))||',';
4
WORDEM
TAB;
5
I
NUMBER := 0;
6
POS
NUMBER := 1;
7 BEGIN
8
POS := INSTR(LISTA, ',');
9
:MSG := 'A seqüência ordenada de números é: ';
10
IF POS = 0 THEN
11
GOTO FIM;
12
END IF;
13
WHILE POS > 0 LOOP
14
I := SUBSTR(LISTA, 1, POS - 1);
15
IF WORDEM.EXISTS(I) THEN
16
WORDEM(I) := WORDEM(I) + 1;
17
ELSE
18
WORDEM(I) := 1;
19
END IF;
20
LISTA := LTRIM(SUBSTR(LISTA, POS + 1));
21
POS := INSTR(LISTA, ',');
22
END LOOP;
23
I := WORDEM.FIRST;
24
WHILE TRUE LOOP
25
WHILE WORDEM(I) > 0 LOOP
26
:MSG := :MSG||I||' ';
27
WORDEM(I) := WORDEM(I) - 1;
28
END LOOP;
29
EXIT WHEN I = WORDEM.LAST;
30
I := WORDEM.NEXT(I);
31
END LOOP;
32 <<FIM>>
33
NULL;
34 END;
35 /
Entre o valor para numeros: 1, 3, 7, 2, 9, 3, 10, 4, 2, 1
antigo
3:
LISTA
VARCHAR2(100) := LTRIM(RTRIM('&NUMEROS'))||',';
novo
3:
LISTA
VARCHAR2(100) := LTRIM(RTRIM('1, 3, 7, 2, 9, 3, 10,
4, 2, 1'))||',';
Procedimento PL/SQL concluído com sucesso.
MSG
--------------------------------------------------------------------------A seqüência ordenada de números é: 1 1 2 2 3 3 4 7 9 10
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 313
PL/SQL9I – BÁSICO E AVANÇADO
4. Leia a tabela de funcionários e preencha a seguinte tabela em memória:
TabCargo – cujo layout é composto do número do cargo e quantidade e código do departamento.
Ao término da carga, apresente a quantidade de funcionários por cargo e por departamento.
Use uma Index-ByTable para acumular a quantidade.
SQL> DECLARE
2
TYPE REG
IS RECORD (DEPTO
CHAR(03),
3
COD
NUMBER,
4
QTD
NUMBER);
5
TYPE TCARGO
IS TABLE OF REG INDEX BY BINARY_INTEGER;
6
CARGO
TCARGO;
7
CURSOR C1
IS SELECT NR_CARGO, CD_DEPTO
8
FROM FUNC
9
ORDER BY CD_DEPTO, NR_CARGO;
10
A_CARGO
BOOLEAN;
11
I
NUMBER;
12 BEGIN
13
:MSG := '';
-- TAMANHO DE 4000 BYTES
14
FOR R1 IN C1 LOOP
15
A_CARGO := FALSE;
16
FOR I IN 1..CARGO.COUNT() LOOP
17
IF CARGO(I).DEPTO = R1.CD_DEPTO AND
18
CARGO(I).COD
= R1.NR_CARGO THEN
19
CARGO(I).QTD := CARGO(I).QTD + 1;
20
A_CARGO := TRUE;
21
EXIT;
22
END IF;
23
END LOOP;
24
IF NOT A_CARGO THEN
25
I := CARGO.COUNT() + 1;
26
CARGO(I).DEPTO := R1.CD_DEPTO;
27
CARGO(I).COD
:= R1.NR_CARGO;
28
CARGO(I).QTD
:= 1;
29
END IF;
30
END LOOP;
31
FOR I IN 1..CARGO.COUNT() LOOP
32
:MSG := :MSG||' DEPTO= '||CARGO(I).DEPTO||
33
' CARGO= '||CARGO(I).COD||
34
' QTD= '||CARGO(I).QTD||CHR(10);
35
END LOOP;
36 END;
37 /
Procedimento PL/SQL concluído com sucesso.
MSG
-----------------------------------------------------------------------DEPTO= A00 CARGO= 58 QTD= 2
DEPTO= A00 CARGO= 66 QTD= 1
DEPTO= A02 CARGO= 8 QTD= 1
DEPTO= A02 CARGO= 12 QTD= 1
DEPTO= A02 CARGO= 21 QTD= 1
DEPTO= A02 CARGO= 32 QTD= 2
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 314
PL/SQL9I – BÁSICO E AVANÇADO
5. Faça um programa que calcule um elemento da seqüência de Fibonacci que será passado como
parâmetro.
SQL> DECLARE
2
TYPE TFIBO
IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
3
FIBO
TFIBO;
4
QTD
NUMBER := '&QTD';
5 BEGIN
6
FIBO(1) := 1;
7
FIBO(2) := 1;
8
IF QTD IS NULL OR
9
QTD > 100
OR QTD < 3 THEN
10
:MSG := 'Quantidade de elementos inválida';
11
ELSE
12
:MSG := '1-1';
13
FOR I IN 3..QTD LOOP
14
FIBO(I) := FIBO(I-1) + FIBO(I-2);
15
:MSG := :MSG ||'-'||FIBO(I);
16
END LOOP;
17
END IF;
18 END;
19 /
Entre o valor para qtd: 12
antigo
4:
QTD
NUMBER := '&QTD';
novo
4:
QTD
NUMBER := '12';
Procedimento PL/SQL concluído com sucesso.
MSG
-----------------------------------------------------------------------1-1-2-3-5-8-13-21-34-55-89-144
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 315
PL/SQL9I – BÁSICO E AVANÇADO
6. Faça um bloco de PL/SQL que leia os n (recebido como parâmetro) funcionários com maior salário
e apresente-os em ordem de matrícula. Deve ser mostrado o nome, cargo e salário de cada
funcionário.
SQL> DECLARE
2
N
NUMBER := '&QTD_SALARIOS';
3
CURSOR C1
IS SELECT NM_FUNC, NR_CARGO, VL_SAL, CD_MAT
4
FROM FUNC
5
WHERE VL_SAL IS NOT NULL
6
ORDER BY VL_SAL DESC;
7
TYPE TAB
IS TABLE OF C1%ROWTYPE INDEX BY BINARY_INTEGER;
8
TBFUNC
TAB;
9 BEGIN
10
FOR R1 IN C1 LOOP
11
EXIT WHEN C1%ROWCOUNT > N;
12
TBFUNC(R1.CD_MAT) := R1;
13
END LOOP;
14
N := TBFUNC.FIRST;
15
:MSG := '';
16
WHILE N <= TBFUNC.LAST LOOP
17
:MSG := :MSG||'Matrícula = '||TBFUNC(N).CD_MAT||' Nome = '||TBFUNC(N).NM_FUNC||
18
' Cargo = '||TBFUNC(N).NR_CARGO||' Salário = '||TBFUNC(N).VL_SAL||CHR(10);
19
N := TBFUNC.NEXT(N);
20
END LOOP;
21 END;
22 /
Entre o valor para qtd_salarios: 6
antigo
2:
N
NUMBER := '&QTD_SALARIOS';
novo
2:
N
NUMBER := '6';
Procedimento PL/SQL concluído com sucesso.
MSG
-------------------------------------------------------------------------------Matrícula = 10 Nome = CRISTINA Cargo = 66 Salário = 28644,3
Matrícula = 20 Nome = MIGUEL Cargo = 61 Salário = 22399,55
Matrícula = 60 Nome = IRACY Cargo = 55 Salário = 23116,32
Matrícula = 100 Nome = TEODORO Cargo = 54 Salário = 35088,71
Matrícula = 110 Nome = VICENTE Cargo = 58 Salário = 25250,47
Matrícula = 230 Nome = JOAQUIM Cargo = 53 Salário = 25250,47
7. Suponha um tabuleiro de xadrez com oito linhas e oito colunas. Receba como parâmetro (linha e
coluna) o posicionamento de uma rainha no tabuleiro.
Faça um programa que determine as outras possíveis posições em que a outra rainha poderá ser
colocada sabendo-se que, no xadrez, a rainha pode se deslocar tanto na reta quanto na diagonal.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 316
PL/SQL9I – BÁSICO E AVANÇADO
SQL>
SQL>
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
VARIABLE MSG VARCHAR2(1000)
SET AUTOPRINT ON
DECLARE
TYPE ARX
IS TABLE OF CHAR(1) INDEX BY BINARY_INTEGER;
TYPE ARY
IS TABLE OF ARX
INDEX BY BINARY_INTEGER;
TABULEIRO
ARY;
POSX
NUMBER := '&LINHA';
POSY
NUMBER := '&COLUNA';
J
NUMBER;
K
NUMBER;
BEGIN
-- Retas
:MSG := 'As posições disponíveis são: ';
FOR I IN 1..8 LOOP
TABULEIRO(POSX)(I) := 'X';
END LOOP;
FOR I IN 1..8 LOOP
TABULEIRO(I)(POSY) := 'X';
END LOOP;
-- Diagonais
J := POSY - 1;
K := POSY + 1;
FOR I IN (POSX+1)..8 LOOP
IF J >= 1 THEN
TABULEIRO(I)(J) := 'X';
J := J -1;
END IF;
IF K <= 8 THEN
TABULEIRO(I)(K) := 'X';
K := K + 1;
END IF;
END LOOP;
J := POSY - 1;
K := POSY + 1;
FOR I IN REVERSE 1..(POSX-1) LOOP
IF J >= 1 THEN
TABULEIRO(I)(J) := 'X';
J := J -1;
END IF;
IF K <= 8 THEN
TABULEIRO(I)(K) := 'X';
K := K + 1;
END IF;
END LOOP;
FOR I IN 1..8 LOOP
FOR J IN 1..8 LOOP
IF NOT TABULEIRO(I).EXISTS(J) THEN
:MSG := :MSG ||'('||I||','||J||') ';
END IF;
END LOOP;
END LOOP;
END;
/
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 317
PL/SQL9I – BÁSICO E AVANÇADO
Entre o valor para linha: 3
antigo
5:
POSX
NUMBER := '&LINHA';
novo
5:
POSX
NUMBER := '3';
Entre o valor para coluna: 4
antigo
6:
POSY
NUMBER := '&COLUNA';
novo
6:
POSY
NUMBER := '4';
Procedimento PL/SQL concluído com sucesso.
MSG
------------------------------------------------------------------As posições disponíveis são: (1,1) (1,3) (1,5) (1,7) (1,8) (2,1)
(2,2) (2,6) (2,7) (2,8) (4,1) (4,2) (4,6) (4,7) (4,8) (5,1) (5,3)
(5,5) (5,7) (5,8) (6,2) (6,3) (6,5) (6,6) (6,8) (7,1) (7,2) (7,3)
(7,5) (7,6) (7,7) (8,1) (8,2) (8,3) (8,5) (8,6) (8,7) (8,8)
Neste exercício não precisamos verificar a existência de cada uma das posições do array na direção X
(tabuleiro.exists( I )) porque preenchemos um elemento de cada uma das oito posições porque
quando executamos o primeiro loop, para marcar as linhas retas, é criada uma linha para cada
posição.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 318
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 5:
1. Crie um bloco de PL/SQL que receba como parâmetro uma série de matrículas separadas por
vírgula em um único parâmetro.
Monte este número em um array e faça uma consulta ao banco de dados, obtendo o nome e salário
de todos os funcionários que existam na lista. Use ForAll e Bulk Collect.
SQL> DECLARE
2
MATS
VARCHAR2(300) := '&MATRICULAS'||',';
3
TYPE TAB
IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
4
TYPE TABCHAR
IS TABLE OF VARCHAR2(50) INDEX BY BINARY_INTEGER;
5
TABMAT
TAB;
6
SALARIOS
TAB;
7
NOMES
TABCHAR;
8
POS
NUMBER;
9
I
NUMBER := 1;
10 BEGIN
11
POS := INSTR(MATS, ',');
12
WHILE POS > 0 LOOP
13
TABMAT(I) := TO_NUMBER(SUBSTR(MATS,1,POS-1));
14
MATS := SUBSTR(MATS,POS+1);
15
POS := INSTR(MATS, ',');
16
I := I + 1;
17
END LOOP;
18
:MSG := '';
19
IF I > 1 THEN
20
FORALL J IN 1..TABMAT.LAST
21
UPDATE FUNC SET VL_SAL = VL_SAL
22
WHERE CD_MAT = TABMAT(J)
23
RETURNING NM_FUNC, VL_SAL BULK COLLECT INTO NOMES, SALARIOS;
24
COMMIT;
25
I := 1;
26
FOR J IN 1..TABMAT.LAST LOOP
27
IF SQL%BULK_ROWCOUNT(J) > 0 THEN
28
:MSG := :MSG||TABMAT(J)||'-'||NOMES(I)||'-'||SALARIOS(I)||CHR(10);
29
I := I + 1;
30
ELSE
31
:MSG := :MSG ||'UPDATE MAT='||TABMAT(J)||' NÃO REALIZADA'||CHR(10);
32
END IF;
33
END LOOP;
34
END IF;
35 END;
36 /
Entre o valor para matriculas: 100, 12, 150, 7, 180
antigo
2:
MATS
VARCHAR2(300) := '&MATRICULAS'||',';
novo
2:
MATS
VARCHAR2(300) := '100, 12, 150, 7, 180'||',';
Procedimento PL/SQL concluído com sucesso.
MSG
-------------------------------------------------------------------------------100-TEODORO-35088,71
12-TESTE WHEN-1000
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 319
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 6:
1. Qual a diferença entre Procedures e Functions?
A principal diferença entre Procedures e Functions é que uma função obrigatoriamente retorna um
valor ao programa que a executou, enquanto um procedimento pode ou não retornar valor (em um
parâmetro do tipo Out).
2. Crie uma função armazenada no banco de dados que retorne o nome do departamento, cujo código
deve ser passado como parâmetro. Caso o departamento não exista, a função deve retornar
‘Departamento Inexistente’.
SQL> CREATE OR REPLACE FUNCTION FDEPTO(COD IN VARCHAR2) RETURN VARCHAR2 IS
2
NOME
VARCHAR2(100);
3 BEGIN
4
SELECT NM_DEPTO INTO NOME
5
FROM DEPTO
6
WHERE CD_DEPTO = COD;
7
RETURN NOME;
8 EXCEPTION
9 WHEN NO_DATA_FOUND THEN
10
RETURN 'Departamento Inexistente';
11 END;
12 /
Função criada.
SQL> SELECT FDEPTO('A00') FROM DUAL;
FDEPTO('A00')
--------------------------------------------------------------------------DIRETORIA DA EMPRESA
3. Crie uma função ou procedure na base de dados que determine o departamento que possui menos
funcionários.
SQL> CREATE OR REPLACE FUNCTION QDEPTO RETURN VARCHAR2 IS
2
CURSOR C1 IS SELECT CD_DEPTO, COUNT(*) FROM FUNC F GROUP BY CD_DEPTO
3
UNION
4
SELECT CD_DEPTO, 0 FROM DEPTO
5
WHERE NOT EXISTS (SELECT * FROM FUNC
6
WHERE CD_DEPTO = DEPTO.CD_DEPTO)
7
ORDER BY 2;
8 BEGIN
9
FOR R1 IN C1 LOOP
10
RETURN R1.CD_DEPTO;
11
END LOOP;
12 END;
13 /
Função criada.
SQL> SELECT QDEPTO FROM DUAL;
QDEPTO
------------------------------------------------------------------------A01
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 320
PL/SQL9I – BÁSICO E AVANÇADO
4. Crie uma função ou procedure na base de dados que receba como parâmetro um código de
departamento e determine o ramal do gerente do departamento. Caso o departamento não tenha
gerente, deverá ser escolhido o ramal do funcionário com maior cargo deste departamento (se existir
mais de um, obtenha o ramal do funcionário mais velho e com maior cargo).
SQL> CREATE OR REPLACE
2 FUNCTION RAMAL (COD IN VARCHAR2) RETURN NUMBER IS
3
CURSOR C1
IS SELECT NR_RAMAL
4
FROM FUNC, DEPTO
5
WHERE FUNC.CD_MAT
= DEPTO.CD_GERENTE
6
AND DEPTO.CD_DEPTO = COD;
7
CURSOR C2
IS SELECT NR_RAMAL
8
FROM FUNC
9
WHERE NR_CARGO = (SELECT MAX(NR_CARGO)
10
FROM FUNC
11
WHERE CD_DEPTO = COD)
12
AND CD_DEPTO = COD
13
ORDER BY DT_NASC;
14 BEGIN
15
FOR R1 IN C1 LOOP
16
RETURN R1.NR_RAMAL;
17
END LOOP;
18
FOR R2 IN C2 LOOP
19
RETURN R2.NR_RAMAL;
20
END LOOP;
21
RETURN 0;
22 END;
23 /
Função criada.
SQL> SELECT RAMAL('A00') FROM DUAL;
RAMAL('A00')
-----------3978
5. Crie uma função ou procedure na base de dados que receba como parâmetro um grau de instrução
e um código de departamento e determine o cargo e o salário correspondentes a estes parâmetros.
Se existir mais de um, escolha o do funcionário mais novo. Se não existir nenhum, retorne Null.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CREATE OR REPLACE
PROCEDURE SALCARGO (COD IN VARCHAR2, GIT IN NUMBER,
SAL OUT NOCOPY NUMBER, CARGO OUT NOCOPY NUMBER) IS
CURSOR C1
IS SELECT VL_SAL, NR_CARGO
FROM FUNC
WHERE CD_DEPTO = COD
AND NR_GIT
= GIT
ORDER BY DT_NASC DESC NULLS LAST;
BEGIN
FOR R1 IN C1 LOOP
SAL
:= R1.VL_SAL;
CARGO := R1.NR_CARGO;
END LOOP;
END;
/
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 321
PL/SQL9I – BÁSICO E AVANÇADO
6. Crie um programa na base de dados que realize a admissão dos funcionários. Esse programa
deverá receber como parâmetro nome completo (nome e sobrenome), data de nascimento, sexo e
grau de instrução.
Para a construção do programa, as seguintes regras devem ser estabelecidas:
◊
◊
◊
◊
◊
◊
Usar a rotina do exercício 3 para determinar o departamento.
Usar a rotina do exercício 4 para determinar o ramal.
Usar a rotina do exercício 5 para determinar o salário e o cargo.
A data de admissão corresponde ao dia do cadastramento.
A matrícula deverá ser gerada a partir da última cadastrada.
Os parâmetros são opcionais (exceto nome) e devem ter valores defaults.
O programa deve ser testado com passagem nomeada dos parâmetros.
SQL> CREATE OR REPLACE
2 PROCEDURE CFUNC (NOME1 IN VARCHAR2,
3
NOME2 IN VARCHAR2,
4
DNASC IN DATE := TO_DATE('01011901', 'DDMMYYYY'),
5
SEXO IN VARCHAR2 := 'F',
6
GIT
IN NUMBER := 12) IS
7
CDEPTO
VARCHAR2(03);
8
CRAMAL
NUMBER;
9
MAT
NUMBER;
10
SAL
NUMBER;
11
CARGO
NUMBER;
12 BEGIN
13
IF NOME1 IS NULL OR NOME2 IS NULL THEN
14
RAISE_APPLICATION_ERROR(-20001, 'Nome é de preenchimento obrigatório');
15
END IF;
16
CDEPTO := QDEPTO;
17
CRAMAL := RAMAL(CDEPTO);
18
SALCARGO(CDEPTO, GIT, SAL, CARGO);
19
SELECT NVL(MAX(CD_MAT), 0) + 1 INTO MAT FROM FUNC;
20
INSERT INTO FUNC
21
(CD_MAT, NM_FUNC, NM_SOBRENOME, VL_SAL, NR_RAMAL,
22
CD_DEPTO, IN_SEXO, NR_CARGO, DT_NASC, DT_ADM, NR_GIT) VALUES
23
(MAT, NOME1, NOME2, SAL, CRAMAL,
24
CDEPTO, SEXO, CARGO, DNASC, SYSDATE, GIT);
25 END;
26 /
Procedimento criado.
SQL> EXECUTE CFUNC('JOANA', 'D ARC', GIT=> 18);
Procedimento PL/SQL concluído com sucesso.
SQL> SET LINESIZE 500
SQL> SELECT * FROM FUNC WHERE CD_MAT > 330;
CD_MAT NM_FUNC
NM_SOBRENOME CD_ NR_RAMAL DT_ADM
NR_CARGO NR_GIT I DT_NASC
------- ---------- ------------ --- -------- -------- --------- ------- - -------340 DILSON
GONCALVES
E21
5698 05/05/96
54
16 M 17/05/66
336 MARQUES
ROBERTO
D01
M
347 LOPES
WI_SON
D01
M
357 GONCALVES DILSON
D01
M
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 322
PL/SQL9I – BÁSICO E AVANÇADO
7. Crie uma função que receba como parâmetro uma Index-By Table e retorne os dados da Index-By
Table em ordem crescente.
SQL> CREATE OR REPLACE PACKAGE PGLOBAL IS
2
TYPE TTAB IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
3 END;
4 /
Pacote criado.
SQL> CREATE OR REPLACE
2 PROCEDURE PORDENA (LISTA IN OUT NOCOPY PGLOBAL.TTAB) IS
3
ANT
NUMBER;
4
TROCA
BOOLEAN := TRUE;
5 BEGIN
6
WHILE TROCA LOOP
7
TROCA := FALSE;
8
FOR I IN 1..LISTA.COUNT() - 1 LOOP
9
IF LISTA(I) > LISTA(I+1) THEN
10
ANT := LISTA(I);
11
LISTA(I) := LISTA(I+1);
12
LISTA(I+1) := ANT;
13
TROCA := TRUE;
14
END IF;
15
END LOOP;
16
END LOOP;
17 END;
18 /
Procedimento criado.
SQL> DECLARE
2
LISTA
PGLOBAL.TTAB;
3
TEXTO
VARCHAR2(200) := '&LISTA'||',';
4
POS
NUMBER := 0;
5
I
NUMBER:= 0;
6 BEGIN
7
:MSG := '';
8
POS := INSTR(TEXTO, ',');
9
WHILE POS > 0 LOOP
10
I := I + 1;
11
LISTA(I) := SUBSTR(TEXTO, 1, POS - 1);
12
TEXTO := LTRIM(SUBSTR(TEXTO, POS + 1));
13
POS := INSTR(TEXTO, ',');
14
END LOOP;
15
PORDENA(LISTA);
16
FOR I IN 1..LISTA.COUNT() LOOP
17
:MSG := :MSG || ' ' || LISTA(I);
18
END LOOP;
19 END;
20 /
Entre o valor para lista: 5, 8, 10, 12, 13, 2, 5, 1, 6, 7, 9, 20
Procedimento PL/SQL concluído com sucesso.
MSG
------------------------------------------------------------------1 2 5 5 6 7 8 9 10 12 13 20
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 323
PL/SQL9I – BÁSICO E AVANÇADO
8. Faça uma rotina que receba como parâmetro um número de cargo e determine o nome do
funcionário mais velho com aquele cargo. Se não houver ninguém, o programa deve abortar com o
erro Oracle correspondente.
Faça um programa que acione o programa anterior, e de acordo com seu resultado mostre o nome do
funcionário ou a mensagem “Nenhum funcionário para o cargo Informado”.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
CREATE OR REPLACE
FUNCTION PCARGO(CARGO IN NUMBER) RETURN VARCHAR2 IS
CURSOR C1
IS SELECT NM_FUNC || ' ' || NM_SOBRENOME NOME
FROM FUNC
WHERE NR_CARGO = CARGO
ORDER BY DT_NASC DESC NULLS LAST;
BEGIN
FOR R1 IN C1 LOOP
RETURN R1.NOME;
END LOOP;
RAISE_APPLICATION_ERROR(-20001, 'Cargo Inválido');
END;
/
Função criada.
SQL> DECLARE
2
CARGO
NUMBER := '&CARGO';
3
ECARGO
EXCEPTION;
4
PRAGMA
EXCEPTION_INIT(ECARGO, -20001);
5 BEGIN
6
:MSG := PCARGO(CARGO);
7 EXCEPTION
8 WHEN ECARGO THEN
9
:MSG := 'Nenhum funcionário para o cargo informado';
10 END;
11 /
Entre o valor para cargo: 99
Procedimento PL/SQL concluído com sucesso.
MSG
---------------------------------------------------------------------Nenhum funcionário para o cargo informado
9. Crie um programa para gravar informações na tabela abaixo.
SQL> CREATE TABLE RESULTADO
2 (COD
NUMBER,
3
QTD
NUMBER);
As seguintes características devem ser atendidas:
◊
Criar uma função ou procedure interna ao programa que receba como parâmetro o código do
departamento e retorne a quantidade de funcionários que trabalham neste departamento.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 324
PL/SQL9I – BÁSICO E AVANÇADO
◊
Criar uma função ou procedure que receba como parâmetro um código de departamento e
transforme-o em numérico (use Translate).
◊
A coluna Código deverá receber o novo código do departamento.
◊
A coluna qtd deverá ser preenchida com a quantidade de funcionários do departamento.
SQL> CREATE TABLE RESULTADO
2 (COD
NUMBER,
3
QTD
NUMBER);
Tabela criada.
SQL> CREATE OR REPLACE PROCEDURE CRIA IS
2
CURSOR C1
IS SELECT CD_DEPTO FROM DEPTO;
3
CODIGO
NUMBER;
4
QTD
NUMBER;
5
FUNCTION QDEPTO(COD IN VARCHAR2) RETURN NUMBER IS
6
QTD
NUMBER;
7
BEGIN
8
SELECT COUNT(*) INTO QTD
9
FROM FUNC
10
WHERE CD_DEPTO = COD;
11
RETURN QTD;
12
END;
13
FUNCTION CONVERTE(COD IN VARCHAR2) RETURN NUMBER IS
14
NCOD
NUMBER;
15
BEGIN
16
NCOD := TRANSLATE(UPPER(COD), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
17
'12345678901234567890123456');
18
RETURN NCOD;
19
END;
20 BEGIN
21
FOR R1 IN C1 LOOP
22
CODIGO
:= CONVERTE(R1.CD_DEPTO);
23
QTD
:= QDEPTO(R1.CD_DEPTO);
24
INSERT INTO RESULTADO
25
VALUES(CODIGO, QTD);
26
END LOOP;
27 END;
28 /
Procedimento criado.
SQL> EXECUTE CRIA;
Procedimento PL/SQL concluído com sucesso.
SQL> SELECT * FROM RESULTADO;
COD
QTD
---------- ---------100
3
101
1
102
8
201
1
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 325
PL/SQL9I – BÁSICO E AVANÇADO
10. Crie um função no banco de dados que calcule o n-ésimo elemento de uma série de Fibonacci.
Utilize recursividade.
SQL> CREATE OR REPLACE
2 FUNCTION FIBO(I IN NUMBER) RETURN NUMBER IS
3
VALOR
NUMBER;
4 BEGIN
5
IF I = 1 THEN
6
RETURN 1;
7
ELSIF I = 2 THEN
8
RETURN 1;
9
ELSE
10
VALOR := FIBO(I - 1) + FIBO(I - 2);
11
RETURN VALOR;
12
END IF;
13 END;
14 /
Função criada.
SQL> EXECUTE :MSG := FIBO(10);
Procedimento PL/SQL concluído com sucesso.
MSG
------------------------------------------------55
11. Crie um programa que contenha as duas funções a seguir:
◊
A primeira função recebe como parâmetro um código de cargo e calcula o número de
funcionários com aquele cargo. Caso a quantidade de funcionários seja ímpar, deve ser acionada
a outra função.
◊
A segunda função recebe como parâmetro um cargo. Se o número for ímpar, deve retornar a
quantidade de funcionários do sexo feminino com o cargo informado. Se o número for par, deve
retornar a quantidade de funcionários do departamento D11 com aquele cargo. Neste caso
também deve ser acionada a primeira função.
O programa principal deve acionar a primeira função ou a segunda de acordo com o parâmetro opção:
◊
◊
◊
opção = 0
opção = 1
opção = outro valor
acionar somente a rotina A
acionar somente a rotina B
acionar as duas rotinas.
O programa principal recebe dois parâmetros: opção (é opcional) e cargo (obrigatório).
O programa principal deve ser uma procedure armazenada na base de dados.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 326
PL/SQL9I – BÁSICO E AVANÇADO
SQL> CREATE OR REPLACE
2 PROCEDURE TFORWARD (CARGO IN OUT NOCOPY NUMBER, OPCAO IN NUMBER := 9) IS
3
VLOOP
NUMBER := 0;
4
VALOR
NUMBER ;
5
FUNCTION F2(CARGO IN NUMBER) RETURN NUMBER;
6
FUNCTION F1(CARGO IN NUMBER) RETURN NUMBER IS
7
QTD
NUMBER;
8
BEGIN
9
VLOOP := VLOOP + 1;
10
SELECT COUNT(*) INTO QTD
11
FROM FUNC
12
WHERE NR_CARGO = CARGO;
13
IF MOD(QTD, 2) <> 0 AND VLOOP <= 2 THEN
14
QTD := F2(CARGO);
15
END IF;
16
RETURN QTD;
17
END;
18
FUNCTION F2(CARGO IN NUMBER) RETURN NUMBER IS
19
QTD
NUMBER;
20
BEGIN
21
VLOOP := VLOOP + 1;
22
IF MOD(CARGO, 2) <> 0 THEN
23
SELECT COUNT(*) INTO QTD
24
FROM FUNC
25
WHERE IN_SEXO = 'F'
26
AND NR_CARGO = CARGO;
27
ELSE
28
SELECT COUNT(*) INTO QTD
29
FROM FUNC
30
WHERE CD_DEPTO = 'D11'
31
AND NR_CARGO = CARGO;
32
IF VLOOP <= 2 THEN
33
QTD := F1(CARGO);
34
END IF;
35
END IF;
36
RETURN QTD;
37
END;
38 BEGIN
39
CASE OPCAO
40
WHEN 0 THEN VALOR := F1(CARGO);
41
WHEN 1 THEN VALOR := F2(CARGO);
42
ELSE
VALOR := F1(CARGO) + F2(CARGO);
43
END CASE;
44
CARGO := VALOR;
45 END;
46 /
Procedimento criado.
SQL> EXECUTE :CARGO := 60;
Procedimento PL/SQL concluído com sucesso.
CARGO
---------60
SQL> EXECUTE TFORWARD(:CARGO, 3);
Procedimento PL/SQL concluído com sucesso.
CARGO
---------1
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 327
PL/SQL9I – BÁSICO E AVANÇADO
12. Faça um programa que receba uma data no formato DDMMYYYY e verifique se é uma data válida.
Havendo erro, aborte com a mensagem adequada.
Faça um programa que acione o anterior e retorne a mensagem associada ao erro sem abortar.
SQL> DECLARE
2
DATA
VARCHAR2(100) := '&DATA';
3 BEGIN
4
IF CDATA(DATA) THEN
5
:MSG := 'A DATA '|| DATA || ' É VÁLIDA';
6
END IF;
7 EXCEPTION
8 WHEN OTHERS THEN
9
:MSG := SQLCODE || CHR(10) || SQLERRM(SQLCODE) || CHR(10)||
10
' DATA '|| DATA || ' INVÁLIDA';
11 END;
12 /
Entre o valor para data: AAKLDGHAAKADH
Procedimento PL/SQL concluído com sucesso.
MSG
---------------------------------------------------------------------------1858
ORA-01858: foi localizado um caractere não-numérico onde se esperava um
numérico
DATA AAKLDGHAAKADH INVÁLIDA
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 328
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 7:
1. Crie um pacote que tenha uma rotina que receba como parâmetro:
D) Um numérico e retorne um alfanumérico com o valor editado para o formato monetário
brasileiro e símbolo financeiro “R$”.
E) Um alfanumérico e retorne um alfanumérico com o texto apresentado de trás para a frente.
F)
Uma data e retorne um alfanumérico com as seguintes características: dia da semana, dia no
ano, mês por extenso, mês em algarismos romanos, ano por extenso e ano ISO.
Use Overloading.
SQL> CREATE OR REPLACE PACKAGE ROTINAS IS
2
FUNCTION TROCA(VALOR IN NUMBER) RETURN VARCHAR2;
3
FUNCTION TROCA(TEXTO IN VARCHAR2) RETURN VARCHAR2;
4
FUNCTION TROCA(DATA IN DATE) RETURN VARCHAR2;
5 END ROTINAS;
6 /
Pacote criado.
SQL> CREATE OR REPLACE PACKAGE BODY ROTINAS IS
2
FUNCTION TROCA(VALOR IN NUMBER) RETURN VARCHAR2 IS
3
BEGIN
4
RETURN TO_CHAR(VALOR, 'L999G990D00',
5
'NLS_NUMERIC_CHARACTERS=,., NLS_CURRENCY=R$');
6
END;
7
FUNCTION TROCA(TEXTO IN VARCHAR2) RETURN VARCHAR2 IS
8
NTEXTO
VARCHAR2(300) := '';
9
BEGIN
10
FOR I IN REVERSE 1..LENGTH(TEXTO) LOOP
11
NTEXTO := NTEXTO || SUBSTR(TEXTO,I,1);
12
END LOOP;
13
RETURN NTEXTO;
14
END;
15
FUNCTION TROCA(DATA IN DATE) RETURN VARCHAR2 IS
16
BEGIN
17
RETURN TO_CHAR(DATA, 'D # DDD # MONTH # RM # YEAR # IYYY');
18
END;
19 END ROTINAS;
20 /
Corpo de Pacote criado.
SQL> BEGIN
2
:MSG := ROTINAS.TROCA(12459.88)||CHR(10);
3
:MSG := :MSG || ROTINAS.TROCA('ABCDEF')||CHR(10);
4
:MSG := :MSG || ROTINAS.TROCA(TO_DATE('01041989', 'DDMMYYYY'));
5 END;
6 /
Procedimento PL/SQL concluído com sucesso.
MSG
--------------------------------------------------------------------------R$12.459,88
FEDCBA
7 # 091 # ABRIL
# IV
# NINETEEN EIGHTY-NINE # 1989
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 329
PL/SQL9I – BÁSICO E AVANÇADO
2. Crie duas funções em um pacote.
◊
A primeira deverá ler o funcionário cuja matrícula foi recebida como parâmetro. Se o
funcionário tiver mais de dez anos de casa, salário inferior a 2.500,00 e não tiver recebido
promoção no último ano, a segunda rotina deverá ser acionada.
◊
A segunda rotina recebe como parâmetro um código de matrícula e efetua a promoção do
funcionário gerando um aumento de 10% e registrando esta atualização no histórico.
Garanta que estas rotinas sejam disponibilizadas para todos os usuários, mas que somente aqueles
com acesso às tabelas mencionadas possam atualizá-las.
SQL> CREATE OR REPLACE PACKAGE PROMOVE
2
AUTHID CURRENT_USER IS
3
FUNCTION AVALIA (MAT IN NUMBER) RETURN VARCHAR2;
4 END PROMOVE;
5 /
Pacote criado.
SQL> CREATE OR REPLACE PACKAGE BODY PROMOVE IS
2
FUNCTION ATUALIZA(MAT IN NUMBER) RETURN NUMBER IS
3
SAL
NUMBER;
4
CARGO
NUMBER;
5
DEP
VARCHAR2(3);
6
BEGIN
7
UPDATE ALUNO.FUNC SET VL_SAL = VL_SAL * 1.10
8
WHERE CD_MAT = MAT
9
RETURNING VL_SAL, NR_CARGO, CD_DEPTO
10
INTO SAL, CARGO, DEP;
11
INSERT INTO ALUNO.HST_PROMO
12
(DT_PROMOCAO, CD_MAT, VL_SAL,
13
CD_DEPTO, NR_CARGO, TX_MOTIVO)
14
VALUES(SYSDATE, MAT, SAL, DEP, CARGO,
15
'Promoção por Tempo Serviço');
16
RETURN SAL;
17
END;
18
FUNCTION AVALIA (MAT IN NUMBER) RETURN VARCHAR2 IS
19
SAL
NUMBER;
20
BEGIN
21
SELECT 0 INTO SAL
22
FROM ALUNO.FUNC
23
WHERE CD_MAT = MAT
24
AND (SYSDATE - DT_ADM) > 3652.5
25
AND VL_SAL < 2500;
26
BEGIN
27
SELECT 0 INTO SAL
28
FROM ALUNO.HST_PROMO
29
WHERE (SYSDATE - DT_PROMOCAO) < 365.25
30
AND CD_MAT = MAT;
31
RETURN 'Promovido a menos de 1 ano';
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 330
PL/SQL9I – BÁSICO E AVANÇADO
32
EXCEPTION
33
WHEN NO_DATA_FOUND THEN
34
RETURN 'O novo salário é '|| ATUALIZA(MAT);
35
END;
36
EXCEPTION
37
WHEN NO_DATA_FOUND THEN
38
RETURN 'Funcionário não está em condições de ser promovido';
39
END;
40 END PROMOVE;
41 /
Corpo de Pacote criado.
SQL> GRANT EXECUTE ON PROMOVE TO DAVI;
Operação de Grant bem-sucedida.
SQL> GRANT SELECT, UPDATE ON FUNC TO DAVI;
Operação de Grant bem-sucedida.
SQL> GRANT SELECT, INSERT ON HST_PROMO TO DAVI;
Operação de Grant bem-sucedida.
SQL> CONNECT DAVI/DAVI
Conectado.
SQL> VARIABLE MSG VARCHAR2(300)
SQL> EXECUTE :MSG := ALUNO.PROMOVE.AVALIA(170);
Procedimento PL/SQL concluído com sucesso.
MSG
-------------------------------------------------------------------------Funcionário não está em condições de ser promovido
3. Crie uma função em um pacote que receba um texto e retorne o mesmo texto com todas as letras
maiúsculas. Essa função deve ser testada em um comando Update para atualização dos nomes e
sobrenomes dos funcionários.
SQL> CREATE OR REPLACE PACKAGE CONV IS
2
FUNCTION NOME(VALOR IN VARCHAR2) RETURN VARCHAR2;
3
PRAGMA RESTRICT_REFERENCES(NOME, WNDS);
4 END;
5 /
Pacote criado.
SQL> CREATE OR REPLACE PACKAGE BODY CONV IS
2
FUNCTION NOME(VALOR IN VARCHAR2) RETURN VARCHAR2 IS
3
BEGIN
4
RETURN UPPER(VALOR);
5
END;
6 END;
7 /
Corpo de Pacote criado.
SQL> UPDATE FUNC SET NM_FUNC = CONV.NOME(NM_FUNC),
2
NM_SOBRENOME = CONV.NOME(NM_SOBRENOME);
60 linhas atualizadas.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 331
PL/SQL9I – BÁSICO E AVANÇADO
4. Crie um pacote que declare uma variável Date, uma variável Varchar2(100) e uma variável Number.
Em uma mesma sessão do SQL*Plus:
D) Dê valor inicial para as três variáveis do pacote.
E) Execute as três funções do Exercício 1 passando como parâmetro as variáveis do pacote.
F)
Apresente o valor das três variáveis.
SQL> CREATE OR REPLACE PACKAGE VALORES IS
2
DATA
DATE;
3
TEXTO
VARCHAR2(100);
4
NUMERO
NUMBER;
5 END;
6 /
Pacote criado.
SQL> BEGIN
2
VALORES.DATA
:= TO_DATE('03011999', 'DDMMYYYY');
3
VALORES.TEXTO := 'LAZINHA';
4
VALORES.NUMERO := 887502.54;
5 END;
6 /
Procedimento PL/SQL concluído com sucesso.
SQL> BEGIN
2
:MSG := ROTINAS.TROCA(VALORES.DATA) ||CHR(10)||
3
ROTINAS.TROCA(VALORES.TEXTO) ||CHR(10)||
4
ROTINAS.TROCA(VALORES.NUMERO);
5 END;
6 /
Procedimento PL/SQL concluído com sucesso.
MSG
------------------------------------------------------------------1 # 003 # JANEIRO
# I
# NINETEEN NINETY-NINE # 1998
AHNIZAL
R$887.502,54
SQL> BEGIN
2
:MSG := VALORES.DATA ||CHR(10)||
3
VALORES.TEXTO ||CHR(10)||
4
VALORES.NUMERO;
5 END;
6 /
Procedimento PL/SQL concluído com sucesso.
MSG
--------------------------------------------------------------------------03/01/99
LAZINHA
887502,54
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 332
PL/SQL9I – BÁSICO E AVANÇADO
5. Crie uma rotina em um pacote que receba como parâmetro uma data de nascimento, uma datalimite e calcule a idade em relação à data limite.
Essa rotina deve ser usada em um relatório a ser gerado para disco que apresente o nome do
funcionário completo e sua idade no dia da admissão e no dia de hoje.
SQL> CREATE OR REPLACE PACKAGE DATAS IS
2
FUNCTION IDATA(DATAI IN DATE, DATAF IN DATE) RETURN NUMBER;
3 END;
4 /
Pacote criado.
SQL> CREATE OR REPLACE PACKAGE BODY DATAS IS
2
FUNCTION IDATA(DATAI IN DATE, DATAF IN DATE) RETURN NUMBER IS
3
BEGIN
4
RETURN TRUNC((DATAF - DATAI) / 365.25);
5
END;
6 END;
7 /
Corpo de Pacote criado.
SQL> SELECT DATAS.IDATA(DT_NASC, DT_ADM) "Idade Adm",
2
DATAS.IDATA(DT_NASC, SYSDATE) "Idade Hoje",
3
CD_MAT, NM_FUNC
4
FROM FUNC
5
WHERE ROWNUM < 7
6 /
Idade Adm Idade Hoje
CD_MAT
---------- ---------- ---------41
49
10
25
35
20
33
42
30
33
47
50
38
47
60
27
40
70
6 linhas selecionadas.
NM_FUNC
-----------CRISTINA
MIGUEL
SANDRA
JOAO
IRACY
EVA
6. Crie um pacote que declare as seguintes variáveis:
◊
Um tipo tabela de varchar2(100).
◊
Um tipo tabela para datas.
◊
Um tipo registro de (uma data, um código numérico e um texto de 100 posições).
◊
Um tipo tabela do registro.
◊
Um tipo registro de (um código numérico e um valor numérico).
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 333
PL/SQL9I – BÁSICO E AVANÇADO
◊
Um tipo tabela do registro anterior.
◊
Um tipo tabela de Func (use Rowtype).
◊
Um tipo tabela de Depto (use Rowtype).
◊
Um cursor com todas as colunas de Func para um determinado departamento e cargo
recebidos como parâmetro.
◊
Um cursor com todas as colunas de Depto para um determinado código de gerente recebido
como parâmetro.
SQL> CREATE OR REPLACE PACKAGE DEFINE IS
2
TYPE TABVC
IS TABLE OF VARCHAR2(100) INDEX BY BINARY_INTEGER;
3
TYPE VARDT
IS TABLE OF DATE INDEX BY BINARY_INTEGER;
4
TYPE REG1
IS RECORD (DATA DATE,
5
CODIGO NUMBER,
6
TEXTO VARCHAR2(100));
7
TYPE TABREG
IS TABLE OF REG1 INDEX BY BINARY_INTEGER;
8
TYPE REG2
IS RECORD (CODIGO NUMBER,
9
VALOR NUMBER);
10
TYPE VARREG
IS TABLE OF REG2 INDEX BY BINARY_INTEGER;
11
TYPE TBFUNC
IS TABLE OF FUNC%ROWTYPE INDEX BY BINARY_INTEGER;
12
TYPE TBDEPTO
IS TABLE OF DEPTO%ROWTYPE INDEX BY BINARY_INTEGER;
13
CURSOR C1
(DEP IN VARCHAR2, CARGO IN NUMBER)
14
RETURN FUNC%ROWTYPE;
15
CURSOR C2
(GERENTE IN NUMBER)
16
RETURN DEPTO%ROWTYPE;
17 END;
18 /
Pacote criado.
SQL> CREATE OR REPLACE PACKAGE BODY DEFINE IS
2
CURSOR C1
(DEP IN VARCHAR2, CARGO IN NUMBER)
3
RETURN FUNC%ROWTYPE
4
IS SELECT * FROM FUNC
5
WHERE CD_DEPTO = DEP
6
AND NR_CARGO = CARGO;
7
CURSOR C2
(GERENTE IN NUMBER)
8
RETURN DEPTO%ROWTYPE
9
IS SELECT * FROM DEPTO
10
WHERE CD_GERENTE = GERENTE;
11 END;
12 /
Corpo de Pacote criado.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 334
PL/SQL9I – BÁSICO E AVANÇADO
7. Crie um programa que receba como parâmetro um código de departamento e um número de cargo
e separe os dados da tabela Func em três grupos para utilização como parâmetro por três outros
programas da seguinte forma:
◊
O programa Rot1 deve receber uma lista de datas de admissão.
◊
O programa Rot2 deve receber uma lista de datas de nascimento, matrículas e nomes.
◊
O programa Rot3 deve receber uma lista de cargos e salários.
Utilize os tipos e cursores declarados no exercício anterior. Não é necessário criar os três programas.
SQL> CREATE OR REPLACE
2 PROCEDURE TESTE (PDEP IN VARCHAR2, PCARGO IN NUMBER) IS
3
VARDATA
DEFINE.VARDT;
4
TABRECORD
DEFINE.TABREG;
5
VARNUM
DEFINE.VARREG;
6
I
NUMBER := 0;
7 BEGIN
8
FOR R1 IN DEFINE.C1(PDEP, PCARGO) LOOP
9
I := I + 1;
10
VARDATA(I) := R1.DT_ADM;
11
TABRECORD(I).DATA := R1.DT_NASC;
12
TABRECORD(I).CODIGO := R1.CD_MAT;
13
TABRECORD(I).TEXTO := R1.NM_FUNC||' '||R1.NM_SOBRENOME;
14
VARNUM(I).CODIGO := R1.NR_CARGO;
15
VARNUM(I).VALOR := R1.VL_SAL;
16
END LOOP;
17
--ROT1(VARDATA);
18
--ROT2(TABRECORD);
19
--ROT3(VARNUM);
20 END;
21 /
Procedimento criado.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 335
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 8:
1. Descreva com suas palavras o pacote DBMS_OUTPUT.
O pacote DBMS_OUTPUT tem a finalidade de permitir a troca de mensagens entre programas
PL/SQL executados em uma mesma sessão em um ambiente Server ou entre um PL/SQL executando
no ambiente Server e o SQL*PLUS (ambiente Client ou Server).
2. Crie um programa que receba como parâmetro um número de mês e emita uma carta
parabenizando o funcionário pela data do seu aniversário (use DBMS_OUTPUT). Não deve ser usada
stored procedure.
SQL> SET SERVEROUT ON
SQL> SET VERIFY OFF
SQL> SET NEWPAGE 0
SQL> BREAK ON ROW SKIP PAGE
SQL> DEFINE MES = 3
SQL> SPOOL SAIDA.SQL
SQL> DECLARE
2
MES
NUMBER := '&MES';
3 BEGIN
4
FOR R1 IN (SELECT NM_FUNC, DT_NASC FROM FUNC
5
WHERE TO_NUMBER(TO_CHAR(DT_NASC, 'MM')) = MES) LOOP
6
DBMS_OUTPUT.PUT_LINE('Sr(a). '||R1.NM_FUNC||
7
' parabéns pelo seu aniversário dia '||
8
TO_CHAR(R1.DT_NASC, 'DD/MM') || '.');
9
DBMS_OUTPUT.PUT_LINE(CHR(10)||CHR(10));
10
DBMS_OUTPUT.PUT_LINE('
Muitas Felicidades !');
11
DBMS_OUTPUT.PUT_LINE(CHR(10)||CHR(10)||CHR(10));
12
END LOOP;
13 END;
14 /
Sr(a). JOANA parabéns pelo seu aniversário dia 19/03.
Muitas Felicidades !
Sr(a). SALVADOR parabéns pelo seu aniversário dia 31/03.
Muitas Felicidades !
Procedimento PL/SQL concluído com sucesso.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 336
PL/SQL9I – BÁSICO E AVANÇADO
3. Usando o pacote DBMS_OUTPUT, gere um arquivo contendo o nome, data de nascimento e salário
dos funcionários. Alinhe os dados por coluna.
SQL> DECLARE
2
CURSOR C1 IS SELECT NM_FUNC, NM_SOBRENOME, DT_NASC, VL_SAL
3
FROM FUNC;
4 BEGIN
5
FOR R1 IN C1 LOOP
6
DBMS_OUTPUT.PUT(RPAD(LTRIM(RTRIM(R1.NM_FUNC))||' '||
7
LTRIM(RTRIM(R1.NM_SOBRENOME)), 25));
8
DBMS_OUTPUT.PUT(TO_CHAR(R1.DT_NASC, 'DDMMYYYY')||' ');
9
DBMS_OUTPUT.PUT_LINE(LPAD(TO_CHAR(R1.VL_SAL, 'L999G999D99',
10
'NLS_CURRENCY=R$'), 12));
11
END LOOP;
12 END;
13 /
CRISTINA HENDERSON
14081953 R$28.644,30
MIGUEL TEIXEIRA
02021968 R$22.399,55
SANDRA KWAN
11051961 R$20.770,49
JOAO GOMES
15091955 R$21.815,84
IRACY SOUZA
07071955 R$23.116,32
4. Faça uma rotina que usa a DBMS_OUTPUT para colocar no buffer o nome dos aniversariantes do
mês (recebido como parâmetro).
SQL>
2
3
4
5
6
7
8
9
CREATE OR REPLACE
PROCEDURE ANI(MES IN NUMBER) IS
BEGIN
FOR R1 IN (SELECT NM_FUNC FROM FUNC
WHERE TO_NUMBER(TO_CHAR(DT_NASC, 'MM'))=MES) LOOP
DBMS_OUTPUT.PUT_LINE(LTRIM(RTRIM(R1.NM_FUNC)));
END LOOP;
END ANI;
/
Procedimento criado.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 337
PL/SQL9I – BÁSICO E AVANÇADO
5. Faça um programa que leia a área de buffer do programa anterior e verifique se o funcionário cujo
nome será fornecido como parâmetro é aniversariante. Informe o resultado em uma variável Bind.
SQL> DECLARE
2
LINHAS
DBMS_OUTPUT.CHARARR;
3
STATUS
NUMBER := 0;
4
I
NUMBER := 0;
5
NOME
VARCHAR2(100) := UPPER('&NOME');
6 BEGIN
7
ANI(5);
8
:MSG := NOME || ' não faz aniversário';
9
WHILE STATUS = 0 LOOP
10
I := I + 1;
11
DBMS_OUTPUT.GET_LINE(LINHAS(I), STATUS);
12
END LOOP;
13
FOR J IN 1..I LOOP
14
IF NOME = LINHAS(J) THEN
15
:MSG := NOME || ' é aniversariante';
16
EXIT;
17
END IF;
18
END LOOP;
19 END;
20 /
Entre o valor para nome: DILSON
Procedimento PL/SQL concluído com sucesso.
MSG
-------------------------------------------------------DILSON é aniversariante
6. Faça um script que coloque no prompt do SQL*PLUS o nome do usuário e a linguagem em que
está o banco de dados atualmente. Garanta que este script seja executado todas as vezes em que o
SQL*Plus for acionado (utilize seus conhecimentos de SQL*Plus).
SQL> SET SERVEROUT ON
SQL> SPOOL PROMPT.SQL
SQL> DECLARE
2
TEXTO
VARCHAR2(100);
3 BEGIN
4
SELECT LTRIM(RTRIM(USER))||'_'||USERENV('LANG')
5
INTO TEXTO FROM DUAL;
6
DBMS_OUTPUT.PUT_LINE('SET SQLPROMPT '||'"'||TEXTO||'> "');
7 END;
8 /
SET SQLPROMPT "ALUNO_PTB> "
Procedimento PL/SQL concluído com sucesso.
SQL> SPOOL OFF
SQL> SET TERM OFF
SQL> @PROMPT.SQL
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 338
PL/SQL9I – BÁSICO E AVANÇADO
7. Gere uma área de buffer com o seguinte layout: nome, sobrenome, data de nascimento (formato
dd/mm/yyyy), sexo e salário. Os dados devem ser separados pelo símbolo #.
Caso uma determinada coluna não tenha valor (Null), deve ser substituído por “**” para colunas
alfanuméricas e -1 para colunas numéricas.
Use DBMS_OUTPUT para gerar o buffer.
SQL> CREATE OR REPLACE PROCEDURE GRAVA IS
2 BEGIN
3
FOR R1 IN (SELECT NVL(NM_FUNC, '**') NM_FUNC,
4
NVL(NM_SOBRENOME, '**') NM_SOBRENOME,
5
NVL(TO_CHAR(DT_NASC, 'DD/MM/YYYY'), '**') DT_NASC,
6
NVL(IN_SEXO, '**') IN_SEXO,
7
NVL(LTRIM(TO_CHAR(VL_SAL, 'L999G999G999D99',
8
'NLS_CURRENCY=R$')), '-1') VL_SAL
9
FROM FUNC) LOOP
10
DBMS_OUTPUT.PUT(R1.NM_FUNC||'#'||R1.NM_SOBRENOME||'#');
11
DBMS_OUTPUT.PUT_LINE(R1.DT_NASC||'#'||R1.IN_SEXO||'#'||R1.VL_SAL);
12
END LOOP;
13 END;
14 /
Procedimento criado.
SQL> EXECUTE GRAVA;
CRISTINA#HENDERSON#14/08/1953#F#R$28.644,30
MIGUEL#TEIXEIRA#02/02/1968#M#R$22.399,55
SANDRA#KWAN#11/05/1961#F#R$20.770,49
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 339
PL/SQL9I – BÁSICO E AVANÇADO
8. Faça um programa que leia o buffer gerado pelo programa anterior e crie novas linhas na tabela
Func. O número da matrícula deve ser gerado a partir do último armazenado em Func.
SQL> DECLARE
2
LINHA
VARCHAR2(255);
3
TYPE TAB
IS TABLE OF VARCHAR2(100) INDEX BY BINARY_INTEGER;
4
STATUS
NUMBER := 0;
5
POS
NUMBER := 0;
6
MAT
NUMBER;
7
TEXTO
TAB;
8 BEGIN
9
SELECT NVL(MAX(CD_MAT),0) + 1 INTO MAT
10
FROM FUNC;
11
GRAVA;
12
LOOP
13
DBMS_OUTPUT.GET_LINE(LINHA,STATUS);
14
IF STATUS = 0 THEN
15
POS := INSTR(LINHA, '#');
16
WHILE POS > 0 LOOP
17
STATUS
:= STATUS + 1;
18
TEXTO(STATUS) := SUBSTR(LINHA,1,POS - 1);
19
LINHA
:= SUBSTR(LINHA, POS + 1);
20
POS
:= INSTR(LINHA, '#');
21
END LOOP;
22
STATUS
:= STATUS + 1;
23
TEXTO(STATUS) := LINHA;
24
ELSE
25
EXIT;
26
END IF;
27
FOR I IN 1..5 LOOP
28
IF TEXTO(I) = '**' OR TEXTO(I) = '-1' THEN
29
TEXTO(I) := NULL;
30
END IF;
31
END LOOP;
32
INSERT INTO FUNC(CD_MAT, NM_FUNC, NM_SOBRENOME, DT_NASC,
33
IN_SEXO, VL_SAL)
34
VALUES(MAT, TEXTO(1), TEXTO(2), TO_DATE(TEXTO(3), 'DD/MM/YYYY'),
35
TEXTO(4), TO_NUMBER(TEXTO(5), 'L999G999D99', 'NLS_CURRENCY=R$'));
36
MAT := MAT + 1;
37
END LOOP;
38 END;
39 /
CRISTINA#HENDERSON#14/08/1953#F#R$28.644,30
MIGUEL#TEIXEIRA#02/02/1968#M#R$22.399,55
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 340
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 9:
1. Faça um programa que gere um arquivo contendo as seguintes colunas da tabela de funcionários:
cd_mat, nm_func, nm_sobrenome, dt_nasc, vl_sal.
SQL> DECLARE
2
ARQ
UTL_FILE.FILE_TYPE;
3 BEGIN
4
ARQ := UTL_FILE.FOPEN('C:\TEMP', 'R67.TXT', 'A');
5
FOR R1 IN (SELECT CD_MAT,
6
NVL(NM_FUNC, '*') NM_FUNC,
7
NVL(NM_SOBRENOME, '*') NM_SOBRENOME,
8
NVL(VL_SAL, -1) VL_SAL,
9
NVL(TO_CHAR(DT_NASC, 'DD/MM/YYYY'), '
10
FROM FUNC) LOOP
11
UTL_FILE.PUT(ARQ, LPAD(R1.CD_MAT, 5));
12
UTL_FILE.PUT(ARQ, RPAD(R1.NM_FUNC, 12));
13
UTL_FILE.PUT(ARQ, RPAD(R1.NM_SOBRENOME, 12));
14
UTL_FILE.PUT(ARQ, LPAD(R1.VL_SAL, 14));
15
UTL_FILE.PUT(ARQ, R1.DT_NASC);
16
UTL_FILE.NEW_LINE(ARQ, 1);
17
END LOOP;
18
UTL_FILE.FFLUSH(ARQ);
19
UTL_FILE.FCLOSE(ARQ);
20 END;
21 /
Procedimento PL/SQL concluído com sucesso.
') DT_NASC
2. Faça um programa que leia um arquivo e realize a carga na tabela de funcionários, completando os
dados de acordo com as seguintes especificações: Data de admissão (será igual à data do
cadastramento), Ramal (fixo igual a 1354), salário (fixo igual a 1.000,00) e cargo (fixo igual a 55).
O layout do arquivo de entrada é o seguinte:
Colunas 1 a 12 (nome), colunas 13 a 24 (sobrenome), colunas 25 a 32 (data de nascimento no
formato ddmmyyyy), colunas 33 a 34 (grau de instrução) e coluna 35 (sexo).
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 341
PL/SQL9I – BÁSICO E AVANÇADO
SQL> DECLARE
2
ARQ
UTL_FILE.FILE_TYPE;
3
RFUNC
FUNC%ROWTYPE;
4
LINHA
VARCHAR2(100);
5
MAT
NUMBER;
6 BEGIN
7
ARQ := UTL_FILE.FOPEN('C:\TEMP', 'ENTRADA.SQL', 'R');
8
SELECT NVL(MAX(CD_MAT), 0) + 1 INTO MAT
9
FROM FUNC;
10
LOOP
11
UTL_FILE.GET_LINE(ARQ, LINHA);
12
RFUNC.NM_FUNC
:= UPPER(RTRIM(LTRIM(SUBSTR(LINHA,1,12))));
13
RFUNC.NM_SOBRENOME := UPPER(RTRIM(LTRIM(SUBSTR(LINHA,13,12))));
14
RFUNC.DT_NASC
:= TO_DATE(SUBSTR(LINHA,25,6), 'DDMMYY');
15
RFUNC.NR_GIT
:= SUBSTR(LINHA,33,2);
16
RFUNC.IN_SEXO
:= UPPER(SUBSTR(LINHA,35,1));
17
INSERT INTO FUNC
18
(CD_MAT, NM_FUNC, NM_SOBRENOME, DT_NASC, NR_GIT,
19
IN_SEXO, DT_ADM, NR_RAMAL, NR_CARGO, VL_SAL)
20
VALUES
21
(MAT, RFUNC.NM_FUNC, RFUNC.NM_SOBRENOME, RFUNC.DT_NASC,
22
RFUNC.NR_GIT, RFUNC.IN_SEXO, SYSDATE, 1354, 55, 1000);
23
MAT := MAT + 1;
24
END LOOP;
25 EXCEPTION
26 WHEN NO_DATA_FOUND THEN
27
UTL_FILE.FCLOSE(ARQ);
28
COMMIT;
29 END;
30 /
Procedimento PL/SQL concluído com sucesso.
3. Gere um arquivo a partir da tabela Func com as seguintes informações: nome, sobrenome, data de
nascimento (formato dd/mm/yyyy), sexo e salário.
Os dados devem ser separados pelo símbolo # (não deve haver alinhamento de colunas).
Caso uma determinada coluna esteja sem valor (Null), deve ser feita substituição por: “**” para as
colunas alfanuméricas e -1 para as colunas numéricas.
Use o pacote UTL_FILE para gerar o arquivo.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 342
PL/SQL9I – BÁSICO E AVANÇADO
SQL> DECLARE
2
ARQ
UTL_FILE.FILE_TYPE;
3
CURSOR C1
IS SELECT NVL(NM_FUNC, '**')
4
||'#'||NVL(NM_SOBRENOME, '**')
5
||'#'||NVL(IN_SEXO, '**')
6
||'#'||NVL(TO_CHAR(DT_NASC, 'DDMMYYYY'), '**')
7
||'#'||NVL(VL_SAL,'-1') TEXTO
8
FROM FUNC;
9 BEGIN
10
ARQ := UTL_FILE.FOPEN('C:\TEMP', 'R69.TXT', 'A');
11
FOR R1 IN C1 LOOP
12
UTL_FILE.PUT_LINE(ARQ, R1.TEXTO);
13
END LOOP;
14
UTL_FILE.FFLUSH(ARQ);
15
UTL_FILE.FCLOSE(ARQ);
16 END;
17 /
Procedimento PL/SQL concluído com sucesso
4. Leia o arquivo gerado pelo exercício anterior e crie novas linhas na tabela Func. O número da
matrícula deve ser gerado com o uso de um Sequence de valor inicial 400.
Use o pacote UTL_FILE para ler o arquivo.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
DECLARE
TYPE TAB IS TABLE OF VARCHAR2(30);
LINHA
VARCHAR2(100);
ARQ
UTL_FILE.FILE_TYPE;
I
NUMBER := 0;
POS
NUMBER := 0;
TF
TAB := TAB(' ', ' ', ' ', ' ', ' ');
BEGIN
ARQ := UTL_FILE.FOPEN('C:\TEMP', 'R69.TXT', 'R');
LOOP
UTL_FILE.GET_LINE(ARQ, LINHA);
POS := INSTR(LINHA, '#');
I := 0;
WHILE POS > 0 LOOP
I
:= I + 1;
TF(I) := SUBSTR(LINHA,1,POS - 1);
LINHA := SUBSTR(LINHA, POS + 1);
POS
:= INSTR(LINHA, '#');
END LOOP;
I
:= I + 1;
TF(I)
:= LINHA;
FOR J IN 1..5 LOOP
IF TF(I) = '**' OR TF(I) = '-1' THEN
TF(I) := NULL;
END IF;
END LOOP;
INSERT INTO FUNC(CD_MAT, NM_FUNC, NM_SOBRENOME, IN_SEXO, DT_NASC, VL_SAL)
VALUES(SEQMAT.NEXTVAL,TF(1), TF(2), TF(3), TO_DATE(TF(4), 'DD/MM/YYYY'),TF(5));
END LOOP;
EXCEPTION
WHEN NO_DATA_FOUND THEN
UTL_FILE.FCLOSE(ARQ);
COMMIT;
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 343
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 10:
1. Faça uma rotina que, aleatoriamente, escolha 3 funcionários para premiação de Natal da empresa.
SQL> DECLARE
2
NUMP
NUMBER;
3
MAT
NUMBER;
4
CALC
NUMBER;
5
TYPE TPREM IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
6
PREMIO
TPREM;
7 BEGIN
8
SELECT MAX(CD_MAT)+1 INTO MAT FROM FUNC;
9
CALC := EXTRACT(MINUTE FROM CURRENT_TIMESTAMP) +
10
EXTRACT(SECOND FROM CURRENT_TIMESTAMP);
11
DBMS_RANDOM.INITIALIZE(CALC);
12
FOR I IN 1..MAT LOOP
13
NUMP := MOD(ABS(DBMS_RANDOM.RANDOM), MAT);
14
SELECT CUME_DIST(NUMP) WITHIN GROUP (ORDER BY CD_MAT ASC) INTO CALC
15
FROM FUNC WHERE CD_MAT BETWEEN (NUMP - 100) AND (NUMP + 100);
16
SELECT PERCENTILE_DISC(CALC) WITHIN GROUP (ORDER BY CD_MAT) INTO CALC
17
FROM FUNC
18
WHERE CD_MAT BETWEEN (NUMP - 100) AND (NUMP + 100);
19
PREMIO(NVL(PREMIO.LAST,0) + 1) := CALC;
20
FOR I IN 1..(PREMIO.LAST - 1) LOOP
21
IF PREMIO(I) = CALC THEN
22
PREMIO.DELETE(PREMIO.LAST);
23
END IF;
24
END LOOP;
25
IF PREMIO.LAST = 3 THEN
26
EXIT;
27
END IF;
28
END LOOP;
29
:MSG := 'OS FUNCIONÁRIOS SORTEADOS SÃO : ';
30
FOR I IN 1..PREMIO.LAST LOOP
31
:MSG := :MSG ||PREMIO(I)||' ';
32
END LOOP;
33 END;
34 /
Procedimento PL/SQL concluído com sucesso.
MSG
--------------------------------------------------------------------------------OS FUNCIONÁRIOS SORTEADOS SÃO : 347 200 50
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 344
PL/SQL9I – BÁSICO E AVANÇADO
2. Sabendo-se que uma roleta contém 25 números vermelhos (ímpares) e 25 números pretos (pares).
Determine o número ganhador e se ele é vermelho ou preto.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DECLARE
NUMP
NUMBER;
CALC
NUMBER;
BEGIN
CALC := EXTRACT(MINUTE FROM CURRENT_TIMESTAMP) +
EXTRACT(SECOND FROM CURRENT_TIMESTAMP);
DBMS_RANDOM.INITIALIZE(CALC);
NUMP := MOD(ABS(DBMS_RANDOM.RANDOM), 51);
IF MOD(NUMP,2) = 0 THEN
:MSG := 'O ganhador é '||NUMP||' preto';
ELSE
:MSG := 'O ganhador é '||NUMP||' vermelho';
END IF;
END;
/
Procedimento PL/SQL concluído com sucesso.
MSG
----------------------------------------------------------O ganhador é 40 preto
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 345
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 11:
1. Faça um programa que receba como parâmetro o tipo de informação a ser apresentada:
◊
◊
◊
Nome e sobrenome.
Matrícula, cargo e salário.
Nome, grau de instrução e data de nascimento.
Use uma única variável cursor e DBMS_OUTPUT para mostrar os resultados.
SQL> CREATE OR REPLACE PROCEDURE DISPLAY (OPCAO IN VARCHAR2) IS
2
TYPE VCURSOR
IS REF CURSOR;
3
CLER
VCURSOR;
4
DADOS
VARCHAR2(100);
5 BEGIN
6
DBMS_OUTPUT.ENABLE(1000000);
7
IF UPPER(OPCAO) NOT IN ('A', 'B', 'C') OR
8
OPCAO IS NULL THEN
9
RAISE_APPLICATION_ERROR(-20001, 'OPÇÃO INVÁLIDA');
10
ELSIF OPCAO = 'A' THEN
11
OPEN CLER FOR SELECT NM_FUNC ||' '||NM_SOBRENOME
12
FROM FUNC;
13
LOOP
14
FETCH CLER INTO DADOS;
15
EXIT WHEN CLER%NOTFOUND;
16
DBMS_OUTPUT.PUT_LINE(DADOS);
17
END LOOP;
18
ELSIF OPCAO = 'B' THEN
19
OPEN CLER FOR SELECT TO_CHAR(CD_MAT, 'FM9999') ||'-'||
20
TO_CHAR(NR_CARGO, 'FM99')||'-'||
21
TO_CHAR(VL_SAL, 'FML999G990D00')
22
FROM FUNC;
23
LOOP
24
FETCH CLER INTO DADOS;
25
EXIT WHEN CLER%NOTFOUND;
26
DBMS_OUTPUT.PUT_LINE(DADOS);
27
END LOOP;
28
ELSE
29
OPEN CLER FOR SELECT NM_FUNC||'#'||TO_CHAR(DT_NASC, 'DD/MM/YYYY')
30
||'#'||TO_CHAR(NR_GIT,'FM99')
31
FROM FUNC;
32
LOOP
33
FETCH CLER INTO DADOS;
34
EXIT WHEN CLER%NOTFOUND;
35
DBMS_OUTPUT.PUT_LINE(DADOS);
36
END LOOP;
37
END IF;
38 END;
39 /
Procedimento criado.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 346
PL/SQL9I – BÁSICO E AVANÇADO
2. Faça três programas, cada um deles deverá receber uma variável cursor e abri-la para as situações
apresentadas no Exercício 1.
SQL> CREATE OR REPLACE
2 PROCEDURE DNOME(CLER IN OUT SYS_REFCURSOR) IS
3 BEGIN
4
OPEN CLER FOR SELECT NM_FUNC||' '||NM_SOBRENOME FROM FUNC;
5 END;
6 /
Procedimento criado.
SQL> CREATE OR REPLACE PROCEDURE DMAT(CLER IN OUT SYS_REFCURSOR) IS
2 BEGIN
3
OPEN CLER FOR SELECT TO_CHAR(CD_MAT, 'FM9999')||'-'||
4
TO_CHAR(NR_CARGO,'FM99')||'-'||
5
TO_CHAR(VL_SAL, 'FML999G990D00')
6
FROM FUNC;
7 END;
8 /
Procedimento criado.
SQL> CREATE OR REPLACE PROCEDURE DGIT(CLER IN OUT SYS_REFCURSOR) IS
2 BEGIN
3
OPEN CLER FOR SELECT NM_FUNC||'#'||TO_CHAR(DT_NASC, 'DD/MM/YYYY')
4
||'#'||TO_CHAR(NR_GIT,'FM99')
5
FROM FUNC;
6 END;
7 /
Procedimento criado.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 347
PL/SQL9I – BÁSICO E AVANÇADO
3. Faça um programa que declare uma variável cursor e, de acordo com o parâmetro recebido, acione
cada um dos programas do Exercício 2 e mostre as informações selecionadas.
SQL> CREATE OR REPLACE PROCEDURE PROGS(OPCAO IN VARCHAR2) IS
2
CLER
SYS_REFCURSOR;
3
DADOS
VARCHAR2(100);
4 BEGIN
5
DBMS_OUTPUT.ENABLE(1000000);
6
IF UPPER(OPCAO) NOT IN ('A', 'B', 'C') OR
7
OPCAO IS NULL THEN
8
RAISE_APPLICATION_ERROR(-20001, 'OPÇÃO INVÁLIDA');
9
ELSIF OPCAO = 'A' THEN
10
DNOME(CLER);
11
LOOP
12
FETCH CLER INTO DADOS;
13
EXIT WHEN CLER%NOTFOUND;
14
DBMS_OUTPUT.PUT_LINE(DADOS);
15
END LOOP;
16
ELSIF OPCAO = 'B' THEN
17
DMAT(CLER);
18
LOOP
19
FETCH CLER INTO DADOS;
20
EXIT WHEN CLER%NOTFOUND;
21
DBMS_OUTPUT.PUT_LINE(DADOS);
22
END LOOP;
23
ELSE
24
DGIT(CLER);
25
LOOP
26
FETCH CLER INTO DADOS;
27
EXIT WHEN CLER%NOTFOUND;
28
DBMS_OUTPUT.PUT_LINE(DADOS);
29
END LOOP;
30
END IF;
31 END;
32 /
Procedimento criado.
SQL> SET SERVEROUT ON
SQL> EXECUTE PROGS('A');
CRISTINA HENDERSON
MIGUEL TEIXEIRA
SANDRA KWAN
JOAO GOMES
IRACY SOUZA
EVA PEREIRA
ELIANE HONOFRE
TEODORO SIQUEIRA
VICENTE LOURENCO
SILVIO OLIVA
DOLORES QUEIROZ
TESTE CALL
BRUNO AZEVEDO
ELIZABET PINTO
GABRIEL YVES
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 348
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 12:
1. Faça um programa que receba como parâmetro o nome da tabela, as colunas a serem criadas e a
indicação de permanência ou não. Faça a criação do objeto no schema do usuário.
SQL> CREATE OR REPLACE PROCEDURE CRIA_TAB (NOME IN VARCHAR2, COLUNAS IN VARCHAR2,
2
PERMANENTE IN BOOLEAN:= TRUE) IS
3
TEXTO
VARCHAR2(2000);
4 BEGIN
5
IF NOT PERMANENTE THEN
6
TEXTO := 'CREATE GLOBAL TEMPORARY TABLE ';
7
ELSE
8
TEXTO := 'CREATE TABLE ';
9
END IF;
10
TEXTO := TEXTO || NOME ||'('||COLUNAS||')';
11
IF NOT PERMANENTE THEN
12
TEXTO := TEXTO ||' ON COMMIT PRESERVE ROWS';
13
END IF;
14
EXECUTE IMMEDIATE TEXTO;
15 END;
16 /
Procedimento criado.
SQL> EXECUTE CRIA_TAB('TABTESTE', 'NOME VARCHAR2(100), CODIGO NUMBER');
Procedimento PL/SQL concluído com sucesso.
SQL> DESC TABTESTE
Nome
---------------------------------------------------------------------------------NOME
CODIGO
SQL> EXECUTE CRIA_TAB('TABTESTE_TEMP', 'NOME VARCHAR2(100), CODIGO NUMBER', FALSE);
Procedimento PL/SQL concluído com sucesso.
SQL> DESC TABTESTE_TEMP
Nome
---------------------------------------------------------------------------------NOME
CODIGO
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 349
PL/SQL9I – BÁSICO E AVANÇADO
2. Faça um programa que receba como parâmetro o nome das colunas a serem lidas da tabela Func
(podem ser informadas até três colunas separadas por vírgula). Apresente os dados lidos.
SQL> CREATE OR REPLACE PROCEDURE LER_FUNC(COLUNAS IN VARCHAR2) IS
2
TEXTO
VARCHAR2(2000);
3
TYPE VC1 IS REF CURSOR;
4
A
VARCHAR2(100);
5
B
VARCHAR2(100);
6
C
VARCHAR2(100);
7
C1
VC1;
8 BEGIN
9
TEXTO := 'SELECT '||COLUNAS||' FROM FUNC';
10
OPEN C1 FOR TEXTO;
11
LOOP
12
FETCH C1 INTO A, B, C;
13
EXIT WHEN C1%NOTFOUND;
14
DBMS_OUTPUT.PUT_LINE(A||'-'||B||'-'||C);
15
END LOOP;
16 END;
17 /
Procedimento criado.
SQL> SET SERVEROUT ON
SQL> EXECUTE LER_FUNC('NM_FUNC, CD_MAT, DT_NASC');
CRISTINA-10-14/08/53
MIGUEL-20-02/02/68
SANDRA-30-11/05/61
JOAO-50-15/09/55
IRACY-60-07/07/55
EVA-70-26/05/63
ELIANE-90-15/05/71
TEODORO-100-18/12/66
VICENTE-110-05/11/69
SILVIO-120-18/10/62
DOLORES-130-15/09/55
TESTE CALL-16BRUNO-150-17/05/67
ELIZABET-160-12/04/65
GABRIEL-170-05/01/71
MARIA-180-21/02/69
JAIRO-190-25/06/72
DAVI-200-29/05/71
WILIAM-210-23/02/63
JOANA-220-19/03/68
JOAQUIM-230-30/05/65
SALVADOR-240-31/03/74
DANIEL-250-12/11/69
SILVIA-260-05/10/66
ELINE-280-01/02/91
JOAO-290-09/07/66
FELIPE-300-27/10/56
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 350
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 13:
1. Faça um trigger para controlar as modificações feitas nos salários dos funcionários.
Para tal crie uma tabela de log com o seguinte layout: operação (I, E ou A), valor anterior, valor atual,
matrícula, timestamp (data + hora).
Se for feita uma inclusão, apenas o valor atual deve ser preenchido. Para alteração, ambos os valores
devem ser preenchidos; para exclusão, apenas o valor anterior deve ser preenchido.
SQL> CREATE TABLE TLOG
2 (OPERACAO
VARCHAR2(1),
3
SAL_ANT
NUMBER,
4
SAL_ATU
NUMBER,
5
MAT
NUMBER,
6
TIMESTAMP
DATE,
7
PRIMARY KEY (MAT, OPERACAO, TIMESTAMP));
Tabela criada.
SQL> CREATE OR REPLACE TRIGGER SALARIO
2 AFTER INSERT OR DELETE OR UPDATE OF VL_SAL ON FUNC
3 FOR EACH ROW
4 DECLARE
5
OPER
VARCHAR2(1);
6
MAT
NUMBER;
7 BEGIN
8
IF INSERTING THEN
9
OPER := 'I';
10
MAT := :NEW.CD_MAT;
11
ELSIF UPDATING THEN
12
OPER := 'A';
13
MAT := :NEW.CD_MAT;
14
ELSE
15
OPER := 'E';
16
MAT := :OLD.CD_MAT;
17
END IF;
18
INSERT INTO TLOG (OPERACAO, SAL_ANT, SAL_ATU, MAT, TIMESTAMP)
19
VALUES (OPER, :OLD.VL_SAL, :NEW.VL_SAL, MAT, SYSDATE);
20 END;
21 /
Gatilho criado.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 351
PL/SQL9I – BÁSICO E AVANÇADO
2. Crie um trigger associado à tabela Func que:
◊
Preencha o valor de matrícula se este estiver Null.
◊
Calcule o valor do salário do funcionário, se este estiver Null. O piso salarial é proporcional ao
cargo, começando com cargo 40 de 1.000,00 contendo aumentos sucessivos de 300 a cada novo
cargo.
◊
Determine o departamento que tiver menos funcionários e atribua ao código do departamento
se este estiver Null.
◊
Preencha a data de admissão em caso de inclusão.
◊
Altere os nomes para maiúsculas.
SQL> CREATE OR REPLACE TRIGGER MODIFICA
2
BEFORE INSERT OR UPDATE OF VL_SAL, CD_DEPTO, DT_ADM, NM_FUNC, NR_CARGO,
NM_SOBRENOME
3 ON FUNC
4 FOR EACH ROW
5 DECLARE
6
CURSOR C1
IS SELECT CD_DEPTO, 0 FROM DEPTO
7
WHERE NOT EXISTS (SELECT 0 FROM FUNC
8
WHERE FUNC.CD_DEPTO = DEPTO.CD_DEPTO)
9
UNION
10
SELECT CD_DEPTO, COUNT(*) FROM FUNC
11
GROUP BY CD_DEPTO
12
ORDER BY 2;
13
R1
C1%ROWTYPE;
14 BEGIN
15
IF :NEW.CD_MAT IS NULL THEN
16
SELECT SEQMAT.NEXTVAL INTO :NEW.CD_MAT
17
FROM DUAL;
18
END IF;
19
IF :NEW.VL_SAL IS NULL THEN
20
:NEW.VL_SAL := GREATEST((:NEW.NR_CARGO - 40), 0) * 300 + 1000;
21
END IF;
22
IF :NEW.CD_DEPTO IS NULL THEN
23
OPEN C1;
24
FETCH C1 INTO R1;
25
:NEW.CD_DEPTO := R1.CD_DEPTO;
26
END IF;
27
IF INSERTING THEN
28
:NEW.DT_ADM := SYSDATE;
29
END IF;
30
:NEW.NM_FUNC
:= UPPER(:NEW.NM_FUNC);
31
:NEW.NM_SOBRENOME:= UPPER(:NEW.NM_SOBRENOME);
32 END;
33 /
Gatilho criado.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 352
PL/SQL9I – BÁSICO E AVANÇADO
3. Crie um trigger que critique a inclusão ou alteração de dados na tabela Func:
◊
◊
◊
Ramal deve ter quatro posições
Salário deve ser maior que 500,00 e não pode ser diminuído.
A data de nascimento deve ser maior que 1950.
Se tudo estiver correto, converta o nome e sobrenome para maiúscula.
Emita mensagem indicativa do erro.
SQL> CREATE OR REPLACE TRIGGER CRITICA
2 BEFORE INSERT OR UPDATE OF NR_RAMAL, VL_SAL, DT_NASC
3 ON FUNC
4 FOR EACH ROW
5 BEGIN
6
IF LENGTH(LTRIM(RTRIM(:NEW.NR_RAMAL))) <> 4 THEN
7
RAISE_APPLICATION_ERROR(-20001, 'RAMAL INVÁLIDO');
8
END IF;
9
IF UPDATING AND
10
:NEW.VL_SAL < :OLD.VL_SAL THEN
11
RAISE_APPLICATION_ERROR(-20002, 'SALÁRIO NÃO PODE SER DIMINUÍDO');
12
END IF;
13
IF :NEW.DT_NASC <= TO_DATE('311219492359', 'DDMMYYYYHH24MI') + 59/86400 THEN
14
RAISE_APPLICATION_ERROR(-20003, 'DATA DE NASCIMENTO INVÁLIDA');
15
END IF;
16
:NEW.NM_FUNC
:= UPPER(:NEW.NM_FUNC);
17
:NEW.NM_SOBRENOME:= UPPER(:NEW.NM_SOBRENOME);
18 END;
19 /
Gatilho criado.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 353
PL/SQL9I – BÁSICO E AVANÇADO
4. Faça um trigger que, para cada salário incluído, alterado ou excluído em Funcionário, atualize os
valores de salários do departamento da tabela Depto_Acum, cujo layout é:
cd_depto
vl_média
vl_max
vl_min
vl_total
char(03)
number
number
number
number
Se o funcionário mudar de departamento, estes totais também devem ser calculados.
SQL> CREATE TABLE DEPTO_ACUM
2 (CD_DEPTO
CHAR(03) PRIMARY KEY,
3
VL_MEDIA
NUMBER,
4
VL_MAX
NUMBER,
5
VL_MIN
NUMBER,
6
VL_TOTAL
NUMBER);
Tabela criada.
SQL> CREATE OR REPLACE TRIGGER ACUMULA
2 AFTER INSERT OR DELETE OR UPDATE OF VL_SAL
3 ON FUNC
4 FOR EACH ROW
5 BEGIN
6
DELETE FROM DEPTO_ACUM
7
WHERE CD_DEPTO IN (:NEW.CD_DEPTO, :OLD.CD_DEPTO);
8 EXCEPTION
9 WHEN NO_DATA_FOUND THEN
10
NULL;
11 END;
12 /
Gatilho criado.
SQL> DELETE FROM FUNC WHERE CD_DEPTO IS NULL;
16 linhas deletadas.
SQL> CREATE OR REPLACE TRIGGER CALCULA_ACUM
2 AFTER INSERT OR DELETE OR UPDATE OF VL_SAL
3 ON FUNC
4 BEGIN
5
FOR R1 IN (SELECT AVG(VL_SAL) AAVG, MAX(VL_SAL) AMAX,
6
MIN(VL_SAL) AMIN, SUM(VL_SAL) ASUM, CD_DEPTO
7
FROM FUNC
8
WHERE NOT EXISTS (SELECT CD_DEPTO FROM DEPTO_ACUM
9
WHERE CD_DEPTO = FUNC.CD_DEPTO)
10
GROUP BY CD_DEPTO) LOOP
11
INSERT INTO DEPTO_ACUM
12
VALUES (R1.CD_DEPTO, R1.AAVG, R1.AMAX, R1.AMIN, R1.ASUM);
13
END LOOP;
14 END;
15 /
Gatilho criado.
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 354
PL/SQL9I – BÁSICO E AVANÇADO
5. Faça um programa que inclua linhas na tabela Func, recebendo como parâmetro matrícula, nome e
departamento do funcionário.
Execute este programa em um comando Select.
SQL> CREATE OR REPLACE
2 FUNCTION INCLUI(MAT IN
NUMBER, NOME IN VARCHAR2, DEP IN VARCHAR2)
3
RETURN VARCHAR2 IS
4
PRAGMA AUTONOMOUS_TRANSACTION;
5 BEGIN
6
INSERT INTO FUNC (CD_MAT, NM_FUNC, CD_DEPTO)
7
VALUES (MAT, NOME, DEP);
8
COMMIT;
9
RETURN 'INCLUSÃO REALIZADA COM SUCESSO';
10 EXCEPTION
11 WHEN OTHERS THEN
12
RETURN SQLERRM;
13 END;
14 /
Função criada.
SQL> SELECT INCLUI(MAX(CD_MAT) + 1, 'NOVO', 'A00')
2
FROM FUNC;
INCLUI(MAX(CD_MAT)+1,'NOVO','A00')
--------------------------------------------------------------------------INCLUSÃO REALIZADA COM SUCESSO
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 355
PL/SQL9I – BÁSICO E AVANÇADO
LABORATÓRIO 14:
1. Faça um programa que receba como parâmetro uma opção e um número. Se a opção for “Q”
retorne o quadrado do número e se a opção for “D” retorne o dobro do número. Utilize a classe
Util.class externa para a montagem deste exercício.
SQL> CREATE DIRECTORY JAVADIR AS 'C:\TEMP';
Diretório criado.
SQL> CREATE JAVA CLASS USING BFILE (JAVADIR, 'Util.class');
2 /
Java Criado.
SQL> CREATE OR REPLACE FUNCTION JDOB (P1 NUMBER) RETURN NUMBER
2 AS LANGUAGE JAVA
3 NAME 'Util.calcDobro(int) return int';
4 /
Função criada.
SQL> CREATE OR REPLACE FUNCTION JQUA (P1 NUMBER) RETURN NUMBER
2 AS LANGUAGE JAVA
3 NAME 'Util.calcQuadrado(int) return int';
4 /
Função criada.
SQL> DECLARE
2
OPCAO
VARCHAR2(1) := '&OPCAO';
3
WNUM
NUMBER:= '&NUM';
4 BEGIN
5
CASE OPCAO
6
WHEN 'Q' THEN
7
:MSG := 'O quadrado do número '||WNUM||' é '||JQUA(WNUM);
8
WHEN 'D' THEN
9
:MSG := 'O dobro do número '||WNUM||' é '||JDOB(WNUM);
10
ELSE
11
:MSG := 'Opção inválida';
12
END CASE;
13 END;
14 /
Entre o valor para opcao: Q
Entre o valor para num: 3
Procedimento PL/SQL concluído com sucesso.
MSG
--------------------------------------------------------------------------O quadrado do número 3 é 9
SQL> /
Entre o valor para opcao: D
Entre o valor para num: 4
Procedimento PL/SQL concluído com sucesso.
MSG
--------------------------------------------------------------------------O dobro do número 4 é 8
CAPÍTULO 16: RESPOSTAS DOS LABORATÓRIOS - 356
Download

CAPÍTULO 1 : ANALISANDO UMA CONSULTA