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