Treinamento PLSQL
Treinamento PLSQL
Sumrio
01 Introduo ao PL/SQL.............................................................................................................................. 5 Objetivo do Curso........................................................................................................................................ 6 Conceitos Bsicos....................................................................................................................................... 7 O que PL/SQL?................................................................................................................................... 7 Por que aprender a PL/SQL?............................................................................................................ 7 Blocos PL/SQL....................................................................................................................................... 7 Comentrios........................................................................................................................................... 8 Execuo de Blocos............................................................................................................................... 9 Ambiente de Execuo.............................................................................................................................. 10 Ferramentas para execuo de declaraes SQL e desenvolvimento PL/SQL...................................10 SQL*Plus......................................................................................................................................... 10 PL/SQL Developer ......................................................................................................................... 12 TOAD............................................................................................................................................... 15 Oracle SQL Developer..................................................................................................................... 18 Declarao de variveis............................................................................................................................ 23 Explorando os datatypes...................................................................................................................... 23 Datatypes escalares (tipos de dados simples)................................................................................23 Tipo Registro................................................................................................................................... 26 O Escopo de uma varivel.................................................................................................................... 28 Usando tags para identificar um varivel......................................................................................... 29 Valores Nulos....................................................................................................................................... 29 Tratamento de excees........................................................................................................................... 31 Funcionamento geral............................................................................................................................ 31 Capturando uma exceo................................................................................................................ 32 Excees pr-definidas da Oracle........................................................................................................ 32 Erros indefinidos da Oracle.................................................................................................................. 33 Erros definidos pelo usurio................................................................................................................. 34 SQLCODE e SQLERRM...................................................................................................................... 35 O procedimento raise_application_error.......................................................................................... 36 Regras de escopo de exceo............................................................................................................. 37 Propagando as excees..................................................................................................................... 37 Exerccios propostos................................................................................................................................. 38 02 Estruturas PL/SQL.................................................................................................................................. 39 Cenrio para prtica.................................................................................................................................. 40 Modelo lgico de entidade e relacionamento.......................................................................................40 Modelo fsico de entidade e relacionamento........................................................................................ 41 Declaraes IF e LOOPs........................................................................................................................... 43 A declarao IF..................................................................................................................................... 43 A declarao IF...THEN...ELSE....................................................................................................... 43 A declarao IF...ELSIF................................................................................................................... 44 Loop Simples........................................................................................................................................ 45 Criando um loop REPEAT...UNTIL.................................................................................................. 45 Loop FOR............................................................................................................................................. 45 Loop WHILE......................................................................................................................................... 46 Qual loop devo usar?............................................................................................................................ 47 Utilizao de Cursores............................................................................................................................... 48 Conceitos bsicos................................................................................................................................. 48 Cursores Explcitos............................................................................................................................... 48 Declarando um cursor..................................................................................................................... 48 Abrindo um Cursor........................................................................................................................... 49 Recuperando dados de um Cursor.................................................................................................. 50 Fechando o Cursor.......................................................................................................................... 51
Elaborado por Josinei Barbosa da Silva
Pgina 1 de 154
Treinamento
Atributos de cursores explcitos....................................................................................................... 52 Cursores, registros e o atributo %ROWTYPE.................................................................................53 Cursores explcitos automatizados (LOOP Cursor FOR)................................................................55 Passando parmetros para cursores............................................................................................... 57 Cursores implcitos............................................................................................................................... 57 Atributos do cursor implcito............................................................................................................ 58 Variveis de cursor............................................................................................................................... 59 Blocos annimos, procedimentos e funes.............................................................................................. 61 Exerccios propostos................................................................................................................................. 64 03 Stored Procedures.................................................................................................................................. 65 Procedimentos armazenados (Stored Procedure).....................................................................................66 Por que usar os procedimentos?.......................................................................................................... 66 Procedimentos versus funes............................................................................................................. 66 Procedimentos................................................................................................................................. 67 Funes........................................................................................................................................... 68 Manuteno de procedimentos armazenados......................................................................................70 Usando os parmetros......................................................................................................................... 70 Definies de parmetro.................................................................................................................. 71 Dependncias de procedimentos......................................................................................................... 71 Segurana da invocao de procedimento........................................................................................... 71 Pacotes...................................................................................................................................................... 72 Vantagens do uso de pacotes......................................................................................................... 72 Estrutura de um pacote........................................................................................................................ 72 A especificao do pacote............................................................................................................... 72 O corpo do pacote........................................................................................................................... 73 Utilizando os subprogramas e variveis de um pacote.........................................................................75 Manuteno dos pacotes...................................................................................................................... 76 Estados de um pacote..................................................................................................................... 76 Recompilando pacotes.................................................................................................................... 76 Triggers..................................................................................................................................................... 77 O que um Trigger?............................................................................................................................. 77 Triggers DML........................................................................................................................................ 77 Tipos de triggers DML..................................................................................................................... 78 Ativando e desativando triggers............................................................................................................ 79 Exerccios Propostos................................................................................................................................. 80 04 Collections.............................................................................................................................................. 82 Colees.................................................................................................................................................... 83 Tabelas por ndice................................................................................................................................ 83 Declarando uma tabela por ndice................................................................................................... 83 Manipulando uma tabela por ndice ................................................................................................ 84 Tabelas aninhadas............................................................................................................................... 87 Declarando uma tabela aninhada.................................................................................................... 87 Manipulando tabelas aninhadas...................................................................................................... 88 Arrays de tamanho varivel.................................................................................................................. 91 Declarando e inicializando um VARRAY......................................................................................... 92 Adicionando e removendo dados de um VARRAY..........................................................................92 Mtodos de tabela da PL/SQL.............................................................................................................. 94 Executando declaraes SELECT em uma coleo............................................................................95 Criando um cursor explcito a partir de uma coleo.......................................................................98 O bulk binding....................................................................................................................................... 99 Usando BULK COLLECT.............................................................................................................. 100 Usando FORALL........................................................................................................................... 102 Tratamento de excees para as colees........................................................................................ 103 Exerccios Propostos.......................................................................................................................... 105 05 Tpicos avanados: PL/SQL................................................................................................................ 106
Elaborado por Josinei Barbosa da Silva
Pgina 2 de 154
Treinamento
SQL Dinmico.......................................................................................................................................... 107 SQL Dinmico em cursores................................................................................................................ 110 Stored Procedure com transao autnoma........................................................................................... 110 Tabela Funo PL/SQL (PIPELINED)..................................................................................................... 114 Montando uma funo PIPELINED.................................................................................................... 115 Utilizando uma funo PIPELINED em uma declarao SQL............................................................117 UTL_FILE: Escrita e leitura de arquivos no servidor................................................................................118 Procedimentos e funes de UTL_FILE............................................................................................. 119 Gerando um arquivo com informaes extradas do banco de dados................................................121 Recuperando informaes de um arquivo.......................................................................................... 122 TEXT_IO............................................................................................................................................. 122 Exerccios Propostos............................................................................................................................... 124 06 Tpicos avanados: SQL e funes incorporadas do Oracle Database...............................................125 SQLs avanados para usar com PL/SQL................................................................................................ 126 O outer join do Oracle......................................................................................................................... 126 ROWNUM........................................................................................................................................... 127 Comando CASE no SELECT............................................................................................................. 128 SELECT combinado com CREATE TABLE e INSERT.......................................................................128 CREATE TABLE AS SELECT....................................................................................................... 129 INSERT SELECT...................................................................................................................... 129 MERGE.............................................................................................................................................. 129 GROUP BY com HAVING.................................................................................................................. 131 Recursos avanados de agrupamento: ROLLUP e CUBE.................................................................132 ROLLUP........................................................................................................................................ 132 CUBE............................................................................................................................................. 132 Consultas hierrquicas com CONNECT BY.......................................................................................133 Funes incorporadas do Oracle Database............................................................................................. 134 07 Dicas de performance e boas prticas em SQL...................................................................................137 Introduo................................................................................................................................................ 138 O Otimizador Oracle................................................................................................................................ 138 Otimizador baseado em regra (RBO)................................................................................................. 138 Otimizador baseado em custo (CBO)................................................................................................. 139 Variveis de ligao (Bind Variables)...................................................................................................... 139 SQL Dinmico.......................................................................................................................................... 141 O uso de ndices...................................................................................................................................... 142 Colunas indexadas no ORDER BY..................................................................................................... 143 EXPLAIN PLAIN...................................................................................................................................... 143 O AUTOTRACE do SQL*Plus............................................................................................................ 144 A clusula WHERE crucial!................................................................................................................... 147 Use o WHERE ao invs do HAVING para filtrar linhas.......................................................................148 Especifique as colunas principais do ndice na clusula WHERE......................................................148 Evite a clausula OR............................................................................................................................ 148 Cuidado com Produto Cartesiano.................................................................................................... 148 SQLs complexas...................................................................................................................................... 149 Quando usar MINUS, IN e EXISTS.................................................................................................... 149 Evite o SORT........................................................................................................................................... 150 EXISTS ao invs de DISTINCT.......................................................................................................... 150 UNION e UNION ALL.............................................................................................................................. 150 Cuidados ao utilizar VIEWs..................................................................................................................... 151 Joins em VIEWs complexas............................................................................................................... 151 Reciclagem de VIEWs........................................................................................................................ 151 Database Link.......................................................................................................................................... 151 Aplicativos versus Banco de Dados......................................................................................................... 151 Utilize o comando CASE para combinar mltiplas varreduras:..........................................................151 Utilize blocos PL/SQL para executar operaes SQL repetidas em LOOPs de sua aplicao..........152
Elaborado por Josinei Barbosa da Silva
Pgina 3 de 154
Treinamento
Pgina 4 de 154
Treinamento
01 Introduo ao PL/SQL
1. 2. 3. 4.
Pgina 5 de 154
Treinamento
Objetivo do Curso
O objetivo do curso apresentar aos desenvolvedores, recursos da linguagem PL/SQL podero auxili-los na manipulao de dados armazenados no banco de dados Oracle. que
Grande parte dos recursos apresentados, como cursores e colees, sero explorados dentro de unidades de programas, que na PL/SQL so representadas por:
Conceitos bsicos como declarao de variveis, tipos e tratamento de excees, tambm sero mostrados e ao final do curso, ser apresentado um tpico de otimizao e boas prticas de SQL.
Pgina 6 de 154
Treinamento
Conceitos Bsicos
O que PL/SQL?
PL/SQL (Procedural Language/Structured Query Language) uma extenso de linguagem de procedimentos desenvolvida pela Oracle para a SQL Padro, para fornecer um modo de executar a lgica de procedimentos no banco de dados. A SQL em si uma linguagem declarativa poderosa. Ela declarativa pois voc descreve resultados desejados, mas no o modo como eles so obtidos. Isso bom porque voc pode isolar um aplicativo dos detalhes especficos de como os dados so armazenados fisicamente. Um programador competente em SQL tambm consegue eliminar uma quantidade grande de trabalho de processamento no nvel do servidor por meio do uso criativo da SQL. Entretanto, existem limites para para aquilo que voc pode realizar com uma nica consulta declarativa. O mundo real geralmente no to simples como desejaramos que ele fosse. Os desenvolvedores quase sempre precisam executar vrias consultas sucessivas e processar os resultados especficos de uma consulta antes de passar para a consulta seguinte. Isso cria dois problemas para um ambiente cliente/servidor: 1. A lgica de procedimentos, ou seja, a definio do processo, reside nas mquinas do cliente; 2. A necessidade de olhar os dados de uma consulta e us-los como a base da consulta seguinte resulta em uma quantidade maior de trfego de rede. A PL/SQL fornece um mecanismo para os desenvolvedores adicionarem um componente de procedimento no nvel de servidor. Ela foi aperfeioada a ponto de os desenvolvedores agora terem acesso a todos os recursos de uma linguagem de procedimentos completa no nvel de servidor. Ela tambm forma a base da programao do conjunto da Oracle de ferramentas de desenvolvimento para cliente/servidor, como Forms & Reports.
Blocos PL/SQL
A PL/SQL chamada de linguagem estruturada em blocos. Um bloco PL/SQL uma unidade sinttica que pode conter cdigo de programa, declaraes de variveis, handlers de erro, procedimento, funes e at mesmo outros blocos PL/SQL. Cada unidade de programa do PL/SQL consiste em um ou mais blocos. Cada bloco pode ser completamente separado ou aninhado com outros blocos. Um bloco PL/SQL formado por trs sesses: 1. Declarativa (opcional); 2. Executvel; 3. Excees (tambm opcional).
Pgina 7 de 154
Treinamento
A rea declarativa, indicada pela palavra chave DECLARE, a parte inicial do bloco e reservada para declarao de variveis, constantes, tipos, excees definidas por usurios, cursores e subrotinas. A seo executvel contm os comandos SQL e PL/SQL que iro manipular dados do banco e iniciada pela palavra chave BEGIN. Quando o bloco no possui a sesso de excees, a seo executvel finalizada pela palavra chave END, seguida do ponto e vrgula, do contrrio, finalizada pelo incio da sesso de excees, que indicada pela palavra chave EXCEPTION. A sesso executvel, iniciada pelo BEGIN e finalizada pelo END seguido do ponto e vrgula, a nica sesso obrigatria em um bloco PL/SQL. O Bloco annimo o tipo mais simples de um bloco PL/SQL e possui a estrutura mostrada na figura abaixo:
Comentrios
Os comentrios so utilizados para documentar cada fase do cdigo e ajudar no debug.. Os comentrios so informativos e no alteram a lgica ou o comportamento do programa. Quando usados de maneira eficiente, os comentrios melhoram a leitura e as futuras manutenes do programa. Em PL/SQL, os comentrios podem ser indicados de duas formas:
Usando os caracteres -- no incio da linha a ser comentada; ou concentrando o texto (ou cdigo) entre os caracteres '/*' e '*/'.
Abaixo temos o exemplo de um bloco PL/SQL que no faz nada, mas demonstra o uso de comentrios: DECLARE -- Declarao de variveis
Elaborado por Josinei Barbosa da Silva
Pgina 8 de 154
Treinamento
BEGIN /*O programa no executa nada*/ NULL; EXCEPTION --Aqui inicia a sesso e excees /*A rea de exceo tambm no faz nada*/ NULL; END; Utilizar '--' em comentrios de uma linha e '/* */' em comentrios de mais de uma linha, uma boa prtica de programao.
Execuo de Blocos
Na execuo de um bloco PL/SQL:
Quando um bloco executado com sucesso, sem erros de compilao ou erros no tratados, a seguinte mensagem ser mostrada:
No colocar ponto e vrgula aps as palavras chaves DECLARE, BEGIN e EXCEPTION; END e qualquer outro comando PL/SQL e SQL so terminados por ponto e vrgula;
Comandos podem ser colocados na mesma linha, mas no recomendado, pois dificulta a visualizao do cdigo;
Abaixo segue o exemplo de um bloco PL/SQL, para ser executado no SQL*Plus, que imprime na tela o total de produtos cadastrados no banco de dados: DECLARE -- Declarao de variveis vCount INTEGER; BEGIN -- Recuperar quantidade de produtos cadastrados SELECT count(*) INTO vCount FROM produto; -- Imprimir, na tela, a quantidade de produtos cadastrados dbms_output.put_line('Existem '||to_char(vCount)||' cadastrados.'); EXCEPTION /* Se ocorrer qualquer erro, informar o usurio que no foi possvel verificar a quantidade de produtos cadastrados */ WHEN OTHERS THEN dbms_output.put_line('No foi possvel verificar a quantidade de'|| 'produtos cadastrados.'); END; /
Pgina 9 de 154
Treinamento
Ambiente de Execuo
O ambiente de execuo PL/SQL possui dois componentes essenciais:
O Motor PL/SQL parte do Oracle Database e, portanto, ao instalar o SGBD da Oracle, o motor PL/SQL estar pronto para ser utilizado. A ferramenta de desenvolvimento pode ser qualquer uma que permita o desenvolvimento de cdigo PL/SQL e execuo de declaraes SQL. Quando voc executa um declarao SQL em uma ferramenta, os seguintes passos devem acontecer: 1. A ferramenta transmitir a declarao SQL pela rede para o servidor de banco de dados; 2. A ferramenta aguardar uma resposta do servidor de banco de dados; 3. O servidor de banco de dados executar a consulta e transmitir os resultados de volta para a ferramenta; 4. A ferramenta exibir os resultados da declarao. Ns j vimos como montar um bloco PL/SQL e executamos esse bloco no SQL*PLUS. Pois, bem, o SQL*PLUS uma ferramenta para execuo de declaraes SQL e desenvolvimento PL/SQL. Existem muitas outras ferramentas para desenvolvimento e execuo de blocos PL/SQL e, embora no seja o foco deste treinamento, veremos a seguir algumas destas ferramentas.
O SQL*Plus uma ferramenta nativa da Oracle que acompanha todas as verses do Oracle Database e est disponvel na verso grfica (Windows) e/ou na verso de prompt. Em ambas verses, os recursos so os mesmos. Abaixo voc pode observar, atravs das figuras, os dois ambientes:
Pgina 10 de 154
Treinamento
Embora seja uma ferramenta poderosa, o SQL*Plus no popular entre os desenvolvedores PL/SQL, pois no disponibiliza recursos com os quais eles esto acostumados, como por exemplo, debugger, autocomplete, exibio de resultados em grid e botes grficos para manipulao e execuo dos comandos. A maior parte dos usurios do SQL*Plus, se concentra na comunidade de DBAs que utilizam a ferramenta para manuteno do banco de dados, pois atravs do SQL*Plus que os scripts de manuteno so executados. Mas mesmo sendo um desenvolvedor, importante saber que sempre que existir na mquina uma instalao do Server ou do Client Oracle, o SQL*Plus estar disponvel (ele pode ser muito til se voc estiver atendendo um cliente que no possui ferramentas de desenvolvimento!).
Pgina 11 de 154
Treinamento
Para conhecer detalhes de uso e configurao do ambiente do SQL*Plus, consulte o site da Oracle (http://www.oracle.com/pls/db92/db92.sql_keywords?letter=A&category=sqlplus).
PL/SQL Developer
Provavelmente este o ambiente de desenvolvimento PL/SQL mais utilizado pelos desenvolvedores e perfeitamente justificvel, pois trata-e de um ambiente completo de desenvolvimento e muito simples de usar. PL/SQL Developer um ambiente de desenvolvimento integrado (IDE) que foi especialmente destinado ao desenvolvimento de programas armazenados em bancos de dados Oracle, ou seja, vai muito alm do desenvolvimento de blocos PL/SQL e execuo de comandos SQL. Entre os vrios recursos para desenvolvedores, podemos destacar:
Debugger integrado: Um debugger poderoso, muito prximo dos encontrados em ambientes RAD, como o Delphi. Nele voc encontrar recursos como Breakpoints, Watches e muito mais.:
Query Builder: Uma tarefa que muito desenvolvedor detesta relacionar tabelas, principalmente em bancos de dados onde comum o uso de chaves primrias compostas. Com o Query Builder muito fcil realizar esse trabalho. Se as chaves primrias e estrangeiras estiverem todas definidas, o usurio montar suas querys apenas utilizando o mouse:Figura 05: Query Builder do PL/SQL Developer
Pgina 12 de 154
Treinamento
SQL Window: Ferramenta ideal para execuo de declaraes de recuperao (SELECT), alterao (UPDATE), incluso (INSERT) ou excluso (DELETE). Os resultados dos comandos de recuperao so exibidos em grid:
Command Window: Ideal para execuo de blocos annimos , scripts e comandos de alterao de objetos (DDL). Seu funcionamento eqivale ao do SQL*PLUS, com a grande vantagem
Elaborado por Josinei Barbosa da Silva
Pgina 13 de 154
Treinamento
de contar com um editor integrado e recursos como o auto-complete e teclas de atalho para execuo do script contido em seu editor. Muitos comandos do SQL*PLUS como o SET SERVEROUTPUT ON e o CL SCR, tambm funcionam nessa ferramenta:
Pgina 14 de 154
Treinamento
O PL/SQL Developer no uma ferramenta gratuita, mas pode ser baixado para teste atravs do site de seu fabricante: www.allroundautomations.com
TOAD
Outra ferramenta de grande aderncia ao trabalho dos desenvolvedores. Muito semelhante ao PL/SQL Developer e amplamente conhecida entre os profissionais de PL/SQL. Possui todos recursos encontrados em seu concorrente, mudando em alguns casos, a maneira de usar. Na figura a seguir, podemos observar que o object browser do TOAD segmentado por tipo de objeto, mas segue a mesma idia do PL/SQL Developer. O seu editor tambm avanado e em suas opes de menu encontramos recursos como SQL Builder (equivalente ao Query Builder do PL/SQL Developer) , visualizadores de sesses e ferramentas de tunning.
Pgina 15 de 154
Treinamento
Pgina 16 de 154
Treinamento
Pgina 17 de 154
Treinamento
A ferramenta TOAD possui verses gratuitas e pagas, podendo ser facilmente encontrada para download na internet. Para maiores informaes, visite o site da Quest, fabricante da ferramenta: www.quest.com
Embora o SQL*Plus seja a ferramenta nativa do Banco de dados Oracle para execuo de comandos SQL e PL/SQL, a Oracle disponibiliza para download gratuito em seu site, uma outra ferramenta para desenvolvedores PL/SQL: O Oracle SQL Developer. Essa ferramenta, desenvolvida em Java, disponibiliza para o desenvolvedor necessrios para escrever seus programas PL/SQL. todos os recursos
Alm dos recursos j vistos no PL/SQL Developer e no TOAD, como debugger, wizards para montagem de querys e object browser, o Oracle SQL Developer possui alguns recursos de destaque, como:
Trabalhar com conexes simultneas: No Oracle SQL Developer voc consegue ativar mais de uma conexo por vez e carregar janelas em forma de abas, para cada conexo, como mostrado na figura abaixo:
Pgina 18 de 154
Treinamento
Figura 13: Vrias conexes abertas no Oracle SQL Developer, visualizadas na tela principal
Pgina 19 de 154
Treinamento
Figura 14: Alterando a conexo da aba de trabalho ativa no Oracle SQL Developer
Pgina 20 de 154
Treinamento
Editor com opo de ocultar blocos: No Oracle SQL Developer voc pode ocultar um bloco e para visualiz-lo novamente basta um clique para expandir sua rea novamente Caso queira visualiz-lo sem expandir, basta posicionar o cursor do mouse sobre o boto de expanso, como a seguir:
Pgina 21 de 154
Treinamento
Figura 16: Editor que oculta blocos e exibe apenas para leitura com um simples movimento do mouse
O Oracle SQL Developer uma ferramenta "pesada", provavelmente por ter sido desenvolvida na plataforma Java, por isso, necessrio uma mquina com bons recursos de memria e processamento para seu uso, do contrrio, o desenvolvedor pode sofrer com a navegao da ferramenta. A escolha da ferramenta depende do gosto do desenvolvedor. Esse treinamento pode ser praticado em qualquer uma das ferramentas apresentadas aqui ou outras que sigam os padres para execuo de SQL e PL/SQL.
Pgina 22 de 154
Treinamento
Declarao de variveis
Devemos sempre declarar as variveis na Sesso Declarativa antes de referenci-las nos blocos PL/SQL. Podemos atribuir um valor inicial para a varivel criada, definir um valor que ser constante e defini-la como NOT NULL. Sintaxe: Identificador [CONSTANT] Tipo de Dado [NOT NULL] [ := | DEFAULT expresso] onde temos:
CONSTANT: Contm valores que no podem ser alterados. Constantes devem ser inicializadas.
Tipo de Dado: um tipo de dado que pode ser escalar, composto, referencial ou LOB.
NOT NULL: valida a varivel que sempre deve conter valor. Variveis NOT NULL tambm devem ser inicializadas. Expresso: qualquer expresso PL/SQL. Pode ser uma expresso literal, outra varivel ou uma expresso envolvendo operadores e funes.
Ao declarar variveis, procure: a) Adotar padres para dar nomes a variveis. Por exemplo, vNome para representar uma varivel e cNome para representar uma varivel de coleo; b) Se usar uma varivel NOT NULL, inicie com algum valor vlido; c) Declarar uma varivel que facilite a leitura e manuteno do cdigo; d) No utilizar uma varivel com o mesmo nome de uma coluna de tabela referenciada no bloco PL/SQL; Dica: voc pode inicializar uma varivel utilizando o operador ':=' ou a palavra chave DEFAULT, como nos exemplos abaixo: vNome varchar2(30) := 'Seu Madruga'; Ou vNnome varchar2(30) DEFAULT 'Seu Madruga';
Explorando os datatypes
A PL/SQL fornece vrios datatypes que voc pode usar e eles podem ser agrupados em vrias categorias: datatypes escalares, datatypes de objeto grande, registros e ponteiros. Neste curso exploraremos os datatypes escalares e os registros.
Pgina 23 de 154
Treinamento
Datatype VARCHAR2 CHAR NUMBER BYNARY_INTEGER PLS_INTEGER DATE BOOLEAN LONG LONG ROW
Uso Strings de caractere de comprimento varivel Strings de caractere de comprimento fixo Nmeros fixos ou de ponto flutuante Valores de inteiros Usado para clculos rpidos de inteiros Datas Valores TRUE ou FALSE Usado para armazenar strings longas de caracteres (obsoleto) Usado para armazenar grandes quantidades de dados binrios (obsoleto)
IMPORTANTE: Alguns datatypes da PL/SQL no possuem equivalentes no banco de dados Oracle, embora muitos coincidam. O datatype PLS_INTEGER, por exemplo, exclusivo da PL/SQL e outros possuem pequenas diferenas, como LONG que possui limites inferiores ao do banco de dados. A PL/SQL tambm fornece subtipos de alguns datatypes. Um subtipo representa um caso especial de um datatype, geralmente representando um intervalo menor de valores do que o tipo pai. Por exemplo, POSITIVE um subtipo de BINARY_INTEGER que contm apenas valores positivos. Em alguns casos, os subtipos s existem para fornecer alternativos por questes de compatibilidade com padro SQL ou com outras marcas conhecidas de banco de dados do mercado. Abaixo segue uma tabela com exemplos de outros subtipos:
Tabela 2: Exemplo de subtipos
Subtipo VARCHAR STRING DECIMAL DEC DOUBLE PRECISION NUMERIC REAL INTEGER INT SMALLINT FLOAT POSITIVE
Subtipo de VARCHAR2 VARCHAR2 NUMBER NUMBER NUMBER NUMBER NUMBER NUMBER NUMBER NUMBER NUMBER BINARY_INTEGER
NATURAL
BYNARY_INTEGER
Uso Apenas para compatibilidade. Uso no recomendado. Apenas para compatibilidade. Igual a NUMBER Igual a DECIMAL Igual a NUMBER Igual a NUMBER Igual a NUMBER Equivalente a NUMBER(38) Igual a INTEGER Igual a NUMBER(38) Igual a NUMBER Permite que apenas os inteiros positivos sejam armazenados, at o mximo de 2.147.483.687. Zero no considerado um valor positivo e, portanto, no um valor permitido. Permite que apenas os nmeros naturais sejam armazenados, o que inclui o zero. O mximo 2.147.483.687
A declarao de uma varivel escalar muito simples, como pode ser visto no exemplo abaixo: DECLARE vDataNasc vCodDepto vCidade vPI vBloqueado BEGIN DATE; NUMBER(2) NOT NULL VARCHAR2(30) CONSTANT NUMBER BOOLEAN
:= := := :=
Pgina 24 de 154
Treinamento
NULL; END; / IMPORTANTE: Sempre que uma varivel no inicializada, seu valor inicial ser NULL. O Atributo %TYPE Quando declaramos uma varivel PL/SQL para manipular valores que esto em tabelas, temos que garantir que esta varivel seja do mesmo tipo e tenha a mesma preciso do campo que ser referenciado. Se isto no acontecer, um erro PL/SQL ocorrer durante a execuo. Para evitar isto, podemos utilizar o atributo %TYPE para declarar a varivel de acordo com a declarao de outra varivel ou coluna de uma tabela. O atributo %TYPE mais utilizado quando o valor armazenado na varivel for um valor derivado de uma coluna de uma tabela do banco. Para usar este atributo ao invs de um tipo de dado que obrigatrio na declarao de uma varivel, basta colocar o nome da tabela e a coluna desejada e logo depois, coloque o atributo %TYPE Sintaxe: Identificador Tabela.Nome_Coluna%TYPE; Quando o bloco compilado, uma atribuio do valor de uma coluna para uma varivel feita, o PL/SQL verifica se o tipo e o tamanho da varivel compatvel com o tipo da coluna que est sendo usada. Ento, quando utilizamos o atributo %TYPE para definir uma varivel, garantimos que a varivel sempre estar compatvel com a coluna, mesmo que o tipo desta coluna tenha sido alterado. Exemplos: DECLARE vProVlMaiorvenda produto.pro_vl_maiorvenda%TYPE; BEGIN SELECT p.pro_vl_maiorvenda INTO vProVlMaiorvenda FROM produto p WHERE p.pro_in_codigo = 1; END; / Neste exemplo, podemos observar duas situaes: 1. No precisamos nos preocupar se o tipo da varivel esta compatvel com o campo da tabela; 2. Garantimos que se alguma alterao for realizada no tipo de dado da coluna, no precisaremos alterar nada na varivel. Agora imagine se nesse mesmo cdigo o tipo da varivel fosse NUMBER(9,2) e sem avisar o desenvolvedor, o responsvel pelos objetos do banco alterasse o tipo da coluna pro_vl_maiorvenda para NUMBER(11,2)? O desenvolvedor s saberia quando esse cdigo fosse executado e o PL/SQL exibisse o erro. No nada bom que o seu cliente receba um erro desses durante o trabalho! Variveis do tipo boolean A declarao de variveis do tipo boolean no difere em nada dos demais tipos, como pudemos ver em alguns exemplos anteriores, mas existe um recurso interessante no que se refere a atribuio de valores
Elaborado por Josinei Barbosa da Silva
Pgina 25 de 154
Treinamento
para variveis do tipo boolean. Essas variveis s recebem os valores TRUE, FALSE e NULL. As expresses aritmticas sempre retornam TRUE ou FALSE e, portanto, voc pode atribuir uma expresso aritmtica para uma varivel do tipo boolean. Vejamos um exemplo: O seguinte bloco PL/SQL est correto: DECLARE -- Declarao de variveis vValorUltimaVenda produto.pro_vl_ultimavenda%TYPE; vValorMaiorVenda produto.pro_vl_maiorvenda%TYPE; vUltimaVendaMaior BOOLEAN := FALSE; BEGIN -- Comando para recuperar valor da ltima venda e da maior venda do produto SELECT p.pro_vl_ultimavenda, p.pro_vl_maiorvenda INTO vValorUltimaVenda, vValorMaiorVenda FROM produto p WHERE p.pro_in_codigo = 1; -- Condio para definir se ltima venda ou no a maior IF vValorUltimaVenda = vValorMaiorVenda THEN vUltimaVendaMaior := TRUE; ELSE vUltimaVendaMaior := FALSE; END IF; END; / Mas poderia ter sido escrito da seguinte maneira: DECLARE -- Declarao de variveis vValorUltimaVenda produto.pro_vl_ultimavenda%TYPE; vValorMaiorVenda produto.pro_vl_maiorvenda%TYPE; vUltimaVendaMaior BOOLEAN := FALSE; BEGIN -- Comando para recuperar valor da ltima venda e da maior venda do produto SELECT p.pro_vl_ultimavenda, p.pro_vl_maiorvenda INTO vValorUltimaVenda, vValorMaiorVenda FROM produto p WHERE p.pro_in_codigo = 1; -- Atribuir valor da condio para definir se ltima venda ou no a maior vUltimaVendaMaior := (vValorUltimaVenda = vValorMaiorVenda); END; / Como pode ser observado, obtemos o mesmo resultado, mas com um cdigo mais limpo.
Tipo Registro
Um registro uma coleo de valores individuais que esto relacionados de alguma forma. Com frequncia os registros so usados para representar uma linha de uma tabela, e assim o relacionamento se baseia no fato de que todos os valores vm da mesma linha. Cada campo de um registro exclusivo e tem seus prprios valores. Um registro como um todo no tem um valor.
Pgina 26 de 154
Treinamento
Usando os registros voc pode agrupar dados semelhantes em uma estrutura e depois manipular sua estrutura como uma entidade ou unidade lgica. Isso ajuda a reproduzir o cdigo, e o mantm mais fcil de atualizar e entender. Para usar um registro voc deve defini-lo declarando um tipo de registro. Depois voc deve declarar uma ou mais variveis PL/SQL como sendo daquele tipo. Voc declara um tipo de registro na parte da declarao de um bloco, como outra varivel qualquer. O exemplo abaixo mostra como declarar e usar um tipo registro: DECLARE -- Definio de tipos TYPE TProduto IS RECORD( Nome VARCHAR2(40) , Marca VARCHAR2(20), ValorUltimaVenda NUMBER(9,2) ); -- Declarao de variveis vProd TProduto; BEGIN -- Atribuir valor para o registro vProduto SELECT p.pro_st_nome, p.pro_st_marca, p.pro_vl_ultimavenda INTO vProd.Nome, vProd.Marca, vProd.ValorUltimaVenda FROM produto p WHERE p.pro_in_codigo = 1; -- Imprimir na tela os dados recuperados dbms_output.put_line('Nome do produto: '||vProd.Nome||chr(10)|| 'Marca: '||vProd.Marca||chr(10)|| 'Valor ltima venda: '||to_char(vProd.ValorUltimaVenda) ); END; / IMPORTANTE: Para que o pacote DBMS_OUTPUT.PUT_LINE imprima o resultado desejado na tela, necessrio ativar a impresso na ferramenta. No SQL*Plus e no Command Window do PL/SQL Developer essa funcionalidade ativada atravs do comando SET SERVEROUTPUT ON. No Oracle SQL Developer basta ativar o boto na aba DBMS Output, da janela Worksheet. O Atributo %ROWTYPE Quando uma varivel de tipo de registro se baseia em uma tabela, isso significa que os campos do registro tm exatamente o mesmo nome e datatype das colunas da tabela especificada. Voc usa o atributo %ROWTYPE para declarar um registro com base em uma tabela. A sintaxe para declarar uma varivel usando o atributo %ROWTYPE a seguinte: nome_variavel tabela%ROWTYPE A maior vantagem no uso do atributo %ROWTYPE que uma alterao na definio de tabela se reflete automaticamente no seu cdigo PL/SQL. IMPORTANTE: O acrscimo de uma coluna a uma tabela ser transparente para o seu cdigo PL/SQL, assim como determinados tipos de alteraes no datatype. Entretanto, quando voc remove uma coluna (drop column) de tabela que o seu cdigo est usando, voc precisa alter-lo para retirar as referencias daquela coluna (inclusive nas variveis de registro).
Pgina 27 de 154
Treinamento
A seguir, mostramos o exemplo de tipo de registro alterado para usar o atributo %ROWTYPE: DECLARE -- Declarao de variveis vProd produto%ROWTYPE; BEGIN -- Atribuir valor para o registro vProduto SELECT p.* INTO vProd FROM produto p WHERE p.pro_in_codigo = 1; -- Imprimir na tela os dados recuperados dbms_output.put_line('Nome do produto: '||vProd.pro_st_nome||chr(10)|| 'Marca: '||vProd.pro_st_marca||chr(10)|| 'Valor ltima venda: '||to_char(vProd.pro_vl_ultimavenda) ); END; / A desvantagem deste exemplo em relao ao anterior, que neste caso todas as colunas da tabela so recuperadas para a varivel vProd. Dependendo do nmero de colunas que * retornar, haver desperdcio de memria.
Pgina 28 de 154
Treinamento
Valores Nulos
comum que os desenvolvedores menos experientes confundam NULL com 0 (zero) ou espao em branco. ISSO UM ERRO! E um erro grave, pois pode acarretar em falha do programa. NULL no um valor propriamente dito. NULL igual a NADA! Quando uma varivel possui valor NULL, quer dizer que no foi atribudo valor algum a essa varivel (ou atriburam NULL). O mesmo vale para o preenchimento de colunas de tabelas do banco de dados Oracle. Se voc executa uma declarao SELECT para recuperar o valor de determinada coluna e, para algumas linhas retornadas, o valor exibido NULL, quer dizer que nada foi gravado naquela coluna para aquele registro.
Pgina 29 de 154
Treinamento
Valores NULL prejudicam, diretamente, expresses aritmticas e condies booleanas (que retornam verdadeiro ou falso), pois o resultado de qualquer soma ou subtrao ou diviso ou multiplicao entre um valor qualquer e NULL, ser NULL. Portanto, tome muito cuidado em utilizar variveis cujo contedo seja NULL. Esse o valor inicial de qualquer varivel, antes da inicializao.
Pgina 30 de 154
Treinamento
Tratamento de excees
Eventualmente o servidor Oracle ou o aplicativo do usurio causa um erro durante o processamento em runtime. Tais erros podem surgir de falhas de hardware ou rede, erros lgicos de aplicativo, erros de integridade de dados e de muitas outras fontes. Esses erros so conhecidos como excees, ou seja, esses eventos indesejados so excees do processamento normal e esperado.
Funcionamento geral
Geralmente, quando um erro ocorre, o processamento do bloco PL/SQL imediatamente encerrado. O processamento corrente no concludo. A Oracle permite que voc esteja preparado para esses erros e implemente lgica nos programas para lidar com os erros, permitindo que o processamento continue. Essa lgica implementada para gerenciar os erros conhecida como cdigo de tratamento de excees. Com o tratamento de excees da Oracle, quando um erro detectado, o controle passado para a parte de tratamento de excees do programa e o processamento completado normalmente. O tratamento de erros tambm fornece informaes valiosas para depurar os aplicativos e para proteger melhor o aplicativo contra erros futuros. Sem um recurso para tratamento de excees, um programa deve verificar as excees a cada comando, como mostrado a seguir: DECLARE vMeta NUMBER :=0; vRealizado NUMBER :=10000; vPercentual NUMBER :=0; BEGIN -- Recuperar meta do vendedor para o ms corrente SELECT r.rep_vl_metamensal INTO vMeta FROM representante r WHERE r.rep_in_codigo = 10; /* S acha percentual se meta for maior que zero para no gerar erro de diviso por zero */ IF vMeta > 0 THEN vPercentual := (vRealizado / vMeta) * 100; dbms_output.put_line('O representante j realizou '||to_char(vPercentual)|| ' de sua meta.' ); ELSE vPercentual := 0; dbms_output.put_line('O representante no possui meta.'); END IF; END; / Se o tratamento de excees do PL/SQL fosse usado no cdigo acima, no seria necessrio utilizar a estrutura IF...ELSE...END IF, deixando assim o cdigo mais limpo e evitando uma verificao obrigatria, mesmo quando a expresso no fosse gerar um erro. Existem trs tipos de excees na PL/SQL:
Treinamento
O desenvolvedor deve, sempre, definir um manipulador para cada possvel exceo dentro do bloco PL/SQL;
Quando uma exceo ocorre, o PL/SQL processa somente um manipulador antes de deixar o
bloco.
Coloque a clusula OTHERS depois de todas as clusulas de manipulao de exceo, pois o seu manipulador ser executado caso nenhum outro manipulador corresponda a exceo gerada;
Podemos ter somente uma clusula OTHERS dentro de uma rea de tratamento de excees; Excees no podem aparecer dentro de comandos SQL.
Excees no_data_found
Pgina 32 de 154
Treinamento
Descrio A SELECT de linha nica retornou mais de uma linha. Houve a tentativa de operao ilegal de cursor. Ocorreu um erro de aritmtica, converso, truncagem ou restrio. A converso de uma string para um nmero, falhou. Ocorreu uma tentativa de dividir por zero. Houve uma tentativa de inserir, em duplicata, um valor em uma coluna (ou um conjunto de colunas) que possui um ndice exclusivo (UNIQUE KEY ou PRIMARY KEY). Houve uma tentativa de abrir um cursor que foi aberto anteriormente. Uma chamada de banco de dados foi feita sem o usurio estar conectado ao Oracle. Uma parte remota de uma transao teve rollback. Um login no banco de dados Oracle falhou por causa de um nome de usurio e/ou senha invlidos. A PL/SQL encontrou um problema interno. A PL/SQL ficou sem memria ou a memria est corrompida. Um timeout ocorreu enquanto o Oracle estava esperando por um recurso Uma varivel de cursor no incompatvel com a linha de cursor Uma declarao catchall que detecta um erro que no foi detectado nas excees anteriores
Exemplo: BEGIN . . . EXCEPTION WHEN NO_DATA_FOUND THEN Comando1; Comando2; . . . WHEN TOO_MANY_ROWS THEN Comando1; Comando2; . . . WHEN OTHERS THEN Comando1; Comando2; END; /
Pgina 33 de 154
Treinamento
ocorrncias de nomes de exceo dentro de um bloco como nmero de erro do servidor Oracle. Exemplo: DECLARE -- Definio de excees eResumoDependente EXCEPTION; -- Associar exceo definida ao erro do Oracle PRAGMA EXCEPTION_INIT (eResumoDependente,-2292); BEGIN -- Excluir cliente DELETE cliente WHERE cli_in_codigo = 10; COMMIT; EXCEPTION -- Tratar erro de dependncia entre a tabela RESUMO_MENSAL_VENDA WHEN eResumoDependente THEN ROLLBACK; dbms_output.put_line('O Cliente 10 no pode ser excludo, pois existe resumo de vendas vinculado.'); END; / (Para o servidor Oracle, o erro -2292 uma violao de integridade)
Pgina 34 de 154
Treinamento
COMMIT; EXCEPTION -- Se registro no existe, informa usurio WHEN eRegistroInexistente THEN ROLLBACK; dbms_output.put_line('Cliente 1000 no existe!'||chr(10)|| 'Nenhum registro foi excludo.' ); END; /
SQLCODE e SQLERRM
Quando uma exceo ocorre, podemos identificar o cdigo do erro ou a mensagem de erro usando duas funes. Baseados nos valores do cdigo ou mensagem, podemos decidir qual ao tomar baseada no erro. As funes so:
SQLCODE: Retorna um valor numrico para o cdigo de erro. SQLERRM: Retorna um caractere contendo a mensagem associada com o nmero do erro.
O uso dessas funes pode ser muito til quando a exceo detectada com a clusula WHEN OTHERS, a qual usada para detectar excees no previstas ou desconhecidas. At a verso do Oracle Database 8i, voc no pode usar SQLCODE e SQLERRM diretamente em uma declarao SQL. Em vez disso, voc deve atribuir seus valores s variveis locais e depois usar essas variveis na declarao SQL (nas verses seguintes, isso j possvel). A funo SQLCODE retorna o cdigo de erro da exceo. SQLERRM, retorna a mensagem de erro correspondente o cdigo de erro da exceo. Quando nenhuma exceo foi levantada, o valor de SQLCODE 0 (zero) e quando a exceo foi declarada pelo usurio, o valor 1; Abaixo, segue um exemplo de uso das funes SQLCODE e SQERRM: DECLARE -- Declarao de variveis vCodeError NUMBER := 0; vMessageError VARCHAR2(255) :=''; -- Declarar exceo eRegistroInexistente exception; BEGIN -- Excluir cliente DELETE cliente c WHERE c.cli_in_codigo = 1000; -- Se cliente no existe, gerar exceo IF SQL%NOTFOUND THEN raise eRegistroInexistente;
Pgina 35 de 154
Treinamento
END IF; -- Validar excluso COMMIT; EXCEPTION -- Se registro no existe, informa usurio WHEN eRegistroInexistente THEN ROLLBACK; dbms_output.put_line('Cliente 1000 no existe!'||chr(10)|| 'Nenhum registro foi excludo.' ); WHEN OTHERS THEN ROLLBACK; vCodeError := SQLCODE; vMessageError := SQLERRM; -- Imprime na tela o cdigo do erro e a mensagem dbms_output.put_line('Cd.Erro: '||to_char(vCodeError)||chr(10)|| 'Mensagem: '||vMessageError );
END; /
Como pode ser visto no exemplo acima, caso no exista o registro a ser excludo, o bloco levanta a exceo eRegistroInexistente e se ocorrer qualquer outra exceo, executado um ROLLBACK e o cdigo do erro, mais sua descrio, so exibidos na tela.
O procedimento raise_application_error
Usando o procedimento RAISE_APPLICATION_ERROR, podemos comunicar uma exceo prdefinida retornando um cdigo de erro e uma mensagem de erro no padro. Com isto, podemos retornar erros para a aplicao e evitar excees no manipuladas. Sintaxe: raise_application_error(num_erro, mensagem[,{TRUE | FALSE }]); Onde:
num_erro nmero pr-definido pelo usurio para excees entre -20000 e -20999;
mensagem uma mensagem pr-definida pelo usurio para a exceo. Pode ser um string de at 2048 bytes; TRUE|FALSE um parmetro booleano opcional. Se TRUE, o erro colocado na fila dos prximos erros e se for FALSE, o padro, o erro substitui todos os erros precedentes.
Exemplo: DECLARE -- Declarar exceo eRegistroInexistente exception; BEGIN -- Excluir cliente DELETE cliente c WHERE c.cli_in_codigo = 1000;
Pgina 36 de 154
Treinamento
-- Se cliente no existe, gerar exceo IF SQL%NOTFOUND THEN raise eRegistroInexistente; END IF; -- Validar excluso COMMIT; EXCEPTION -- Se registro no existe, informa usurio WHEN eRegistroInexistente THEN ROLLBACK; raise_application_error(-20100,'Cliente 1000 no existe!'||chr(10)|| 'Nenhum registro foi excludo.' ); WHEN OTHERS THEN ROLLBACK; raise_application_error(-20101,'Ocorreu um erro no identificado'||chr(10)|| 'ao tentar excluir o cliente 1000.');
END; /
IMPORTANTE: O procedimento RAISE_APLICATION_ERROR pode ser invocado do bloco principal (no exclusividade da rea de exceo)
Propagando as excees
Quando um erro encontrado, a PL/SQL procura o tratamento de exceo apropriado no bloco atual. Se nenhum tratamento estiver presente, ento a PL/SQL propaga o erro para o bloco includo (bloco pai). Se nenhum tratamento for encontrado l, o erro propagado para os blocos includos at que um tratamento seja encontrado. Esse processo pode continuar at o ambiente host receber e lidar com o erro. Por exemplo, se o SQL*Plus recebeu um erro no tratado de um bloco PL/SQL, o SQL*Plus lida com esse erro exibindo o cdigo de erro e a mensagem na tela do usurio.
Pgina 37 de 154
Treinamento
Exerccios propostos
1. Escreva um bloco PL/SQL que recupere a quantidade de registros de uma tabela qualquer e imprima essa quantidade na tela, usando o SQL*Plus. Salve esse bloco em um arquivo do sistema operacional e execute-o novamente no SQL*Plus, usando o comando @. 2. Edit o bloco escrito no exerccio anterior no Command Window do PL/SQL Developer e execute-o. 3. Escreva um bloco PL/SQL que declare uma varivel e exiba o valor. Depois, adicione um bloco aninhado que declara uma varivel de mesmo nome e exiba seu valor. 4. Escreva um bloco que declare uma varivel do tipo date para guardar a data de nascimento de uma pessoa. Atribua uma data para essa varivel, calcule a idade da pessoa com base na data atual (SYSDATE) e imprima essa idade em anos. Dica: Uma subtrao entre datas, em PL/SQL, retorna a diferena em dias. 5. Altere o bloco escrito no exerccio quatro, eliminando a varivel criada para armazenar a data de nascimento e criando um tipo que guarde: o nome da pessoa, a data de nascimento e a idade. Defina uma varivel do tipo criado. Logo no incio do bloco, atribua o nome da pessoa e a data de nascimento para a varivel. Aps calcular a idade da pessoa, atribua o resultado para o campo de idade da varivel. Por fim, imprima na tela o nome da pessoa, a data de nascimento e a sua idade. 6. Escreva um bloco PL/SQL que recupere dados de uma tabela qualquer e armazene seu retorno em uma varivel do tipo registro. O bloco deve conter uma rea de exceo para tratar os seguintes erros: a) Muitas linhas retornadas pela instruo SQL; b) Nenhuma linha retornada pela instruo SQL; c) Outros erros.
Pgina 38 de 154
Treinamento
02 Estruturas PL/SQL
1. 2. 3. 4.
Cenrio para prtica Declaraes IF e LOOPs; Utilizao de cursores; Blocos annimos, procedimentos e funes.
Pgina 39 de 154
Treinamento
Figura 18: Modelo Lgico de Entidade Relacionamento do sistema de vendas Elaborado por Josinei Barbosa da Silva
Pgina 40 de 154
Treinamento
De agora em diante, procuraremos elabora os exerccios com base neste modelo. A criao dos objetos e a criao da massa de dados encontra-se no arquivo TreinamentoPLSQL.sql.
Pgina 41 de 154
Treinamento
Crie a tablespace USERS, caso ainda no exista, e crie um schema (USER) com o seu nome para ser o proprietrio dos objetos.
Pgina 42 de 154
Treinamento
Declaraes IF e LOOPs
A declarao IF
A declarao IF permite avaliar uma ou mais condies, como em outras linguagens. A sintaxe de uma declarao IF no PL/SQL a seguinte: IF <condio> THEN <comandos a serem executados> END IF; Nessa sintaxe, condio a condio que voc quer verificar. Se a condio for avaliada como TRUE, ou seja, verdadeira, os comandos de execuo sero executados. Como exemplo, abaixo temos um bloco PL/SQL que gera uma exceo se no existirem clientes cadastrados: DECLARE vQtdeClientes INTEGER; BEGIN -- Recuperar quantidade de clientes SELECT COUNT(*) INTO vQtdeClientes FROM cliente; -- Se no existir clientes gera uma exceo IF vQtdeClientes = 0 THEN raise_application_error(-20100,'No existem clientes cadastrados'); END IF; END; /
A declarao IF...THEN...ELSE
No exemplo anterior voc no se importou com os resultados quando existem clientes. A declarao IF...THEN...ELSE permite processar uma srie de declaraes abaixo de ELSE se a condio for falsa. A sintaxe da declarao IF...THEN...ELSE : IF <condio> THEN <comandos a serem executados para condio verdadeira> ELSE <comandos a serem executados para condio verdadeira> END IF; Nessa sintaxe, se a condio for FALSE, executado os comandos abaixo de ELSE. Para exemplificar o uso da declarao IF...THEN...ELSE, vamos modificar o exemplo anterior para gerar uma exceo quando no existir clientes ou imprimir na tela a quantidade de clientes cadastrados quando existir: DECLARE vQtdeClientes INTEGER; BEGIN -- Recuperar quantidade de clientes SELECT COUNT(*) INTO vQtdeClientes FROM cliente;
Pgina 43 de 154
Treinamento
-- Se no existir clientes gera uma exceo IF vQtdeClientes = 0 THEN raise_application_error(-20100,'No existem clientes cadastrados'); ELSE dbms_output.put_line('Existem '||to_char(vQtdeClientes)|| ' cadastrados.'); END IF; END; / Assim como em outras linguagens, voc pode aninhar as declaraes IF e IF...THEN...ELSE, como mostrado abaixo: IF <condio_01> THEN IF <condio_02> THEN ... ... ELSE IF <condio_03> THEN ... ... END IF; ... ... END IF; ... ... END IF;
A declarao IF...ELSIF
Voc pode imaginar as declaraes IF aninhadas como executando um AND lgico mas ELSIF como executando um OR lgico. O recurso bom no uso de ELSIF no lugar de IF aninhado que fica muito mais fcil seguir a lgica da declarao ELSIF porque voc pode identificar facilmente quais declaraes ocorrem em quais condies lgicas, como pode ser visto abaixo: IF <condio_01> THEN IF <condio_02> THEN ... ... ELSIF <condio_03> THEN ... ... ELSIF <condio_04> THEN ... ... ELSE ... END IF; END IF; Como podemos observar, ao final de uma seqncia de ELSIF, podemos ter um ELSE. Isso quer dizer que se nenhuma das condies IF ou ELSIF for atendida, os comandos abaixo de ELSE sero executados.
Pgina 44 de 154
Treinamento
Loop Simples
De todos os loops, esse o mais simples de usar e entender. Sua sintaxe a seguinte: LOOP <<Declaraes>> END LOOP; Como vemos na sintaxe, esse loop no tem uma clusula de sada, ou seja, ele infinito. Para abandonar o loop podemos utilizar duas declaraes: EXIT ou EXIT WHEN. Exemplo de loop simples: BEGIN LOOP NULL; EXIT; END LOOP; END; / O exemplo acima no faz nada, mas suficiente para exemplificar o funcionamento de um loop simples.
Loop FOR
Loop FOR tm a mesma estrutura de um loop simples. A diferena que possui um comando de controle antes da palavra chave LOOP, para determinar o nmero de iteraes que o PL/SQL ir realizar. Sintaxe: FOR contador IN [REVERSE] Comando1; Comando2; END LOOP; Onde: Contador uma declarao inteira implcita cujo valor incrementado ou decrementado(se a palavra
Elaborado por Josinei Barbosa da Silva
limite_inferior..limite_superior LOOP
Pgina 45 de 154
Treinamento
chave REVERSE for usada) automaticamente em 1 em cada iterao do loop, at o limite inferior ou limite superior ser alcanado. No preciso declarar nenhum contador, ele declarado implicitamente como um inteiro. IMPORTANTE: A seqncia de comandos executado cada vez que o contador incrementado, como determinado pelos dois limites inferior e superior. Os limites inferior e superior podem ser literais, variveis ou expresses, mas devem ser valores inteiros. Se o limite inferior for um valor maior que o limite superior, a seqncia de comandos no ser executada. Exemplo de um loop que vai de 1 5 e imprime na tela o nmero de cada loop: BEGIN FOR LoopCount IN 1..5 LOOP dbms_output.put_line('Loop '|| to_char(LoopCount)); END LOOP; END; / IMPORTANTE: O Oracle no fornece opes para passar por um loop com um incremento que no seja um e o valor do contador no pode ser alterado durante o loop. Voc pode escrever loops que so executados com um incremento diferente executando as declaraes apenas quando determinada condio verdadeira. O exemplo abaixo demonstra como incrementar por um valor de 2: BEGIN FOR vLoopCount IN 1..6 LOOP IF MOD(vLoopCount,2) = 0 THEN dbms_output.put_line('Contador igual a '||to_char(vLoopCount)); END IF; END LOOP; END; / Quando executar esse bloco, o resultado deve ser o seguinte: Contador igual a 2 Contador igual a 4 Contador igual a 6 Este exemplo mostra apenas uma das vrias maneiras pelas quais voc pode incrementar um loop. A uno MOD, neste caso, simplesmente testa para ter certeza de que o nmero divisvel igualmente por uma valor de 2. Voc pode alterar isso, facilmente, para 3, 5 ou o que quiser incrementar. Para decremento basta adicionar a palavra-chave REVERSE.
Loop WHILE
Podemos usar o Loop WHILE para repetir uma seqncia de comandos enquanto a condio de controle for TRUE. A condio avaliada no incio de cada iterao. O loop termina quando a condio for FALSE. Se a condio for FALSE no incio do Loop, ento nenhuma iterao ser realizada. Sintaxe: WHILE condio LOOP
Elaborado por Josinei Barbosa da Silva
Pgina 46 de 154
Treinamento
Comando1; Comando2; END LOOP; Se a varivel utilizada na condio no mudar durante o corpo do loop, ento a condio ir permanecer TRUE e o loop nunca terminar. Se a condio retornar NULL, o loop ser encerrado e o controle passar para o prximo comando. Abaixo temos o exemplo de um loop que nunca ser executado: DECLARE vCalc NUMBER := 0; BEGIN WHILE vCalc >= 10 LOOP vCalc := vCalc + 1; dbms_output.put_line('O valor de vCalc '||vCalc); END LOOP; END; /
E abaixo uma verso corrigida do exemplo anterior, para que o loop seja executado: DECLARE vCalc NUMBER := 0; BEGIN WHILE vCalc <= 10 LOOP vCalc := vCalc + 1; dbms_output.put_line('O valor de vCalc '||vCalc); END LOOP; END; /
FOR
WHILE
Quando usar Voc pode usar o LOOP simples se quiser criar um loop do tipo REPEAT...UNTIL. O LOOP simples perfeito para executar essa tarefa Use sempre o loop FOR se voc souber especificamente quantas vezes o loop deve ser executado. Se tiver de codificar uma declarao EXIT ou EXIT WHEN em um loop FOR, voc pode reconsiderar seu cdigo e usar um loop simples ou uma abordagem diferente. Use este loop quando voc pode nem querer executar o loop uma vez. Embora voc possa duplicar esse resultado em um loop FOR usando EXIT ou EXIT WHEN, essa situao mais adequada para o loop WHILE. O loop WHILE o loop mais usado porque ele fornece mais flexibilidade
Pgina 47 de 154
Treinamento
Utilizao de Cursores
Os cursores da PL/SQL fornecem um modo pelo qual seu programa pode selecionar vrias linhas de dados do banco de dados e depois processar cada linha individualmente. Especificamente, um cursor um nome atribudo pela Oracle para cada declarao SQL que processada. Esse nome fornece Oracle um meio de orientar e controlar todas as fases do processamento da SQL.
Conceitos bsicos
Existem dois tipos de cursores:
Cursor Implcito: so cursores declarados pelo PL/SQL implicitamente para todos comandos DML e comandos SELECT no PL/SQL, independente da quantidade de registros processadas. Ele precisa fazer isso para gerenciar o processamento da declarao SQL. Cursor Explcito: Cursores definidos pelo usurio para manipular registros recuperados por declaraes SELECT.
Cursores Explcitos
Utilizamos cursores explcitos para processar individualmente cada linha retornada por um comando SELECT. O conjunto de linhas retornadas pelo comando SELECT chamado de conjunto ativo. Um programa PL/SQL abre o cursor, processa as linhas retornadas pela consulta e depois fecha este cursor. O cursor marca a posio corrente dentro do conjunto ativo. Usando cursores explcitos, o programador pode manipular cada registro recuperado por uma declarao SELECT. Abaixo vemos o ciclo de vida de um cursor:
1. Declare o cursor nomeando o mesmo e defina a estrutura da consulta que ser realizada por ele; 2. Atravs do comando OPEN, o cursor aberto e executa a consulta que recuperar o conjunto ativo do banco de dados; 3. Com o comando FETCH, capturamos os dados do registro corrente para utilizarmos em nosso programa. A cada comando FETCH, devemos testar se ainda existe registro no cursor e abandonar o LOOP do cursor atravs do comando EXIT, caso no exista (mais adiante veremos como verificar isso); 4. Quando a manipulao dos dados do cursor for finalizada, ao abandonar o LOOP do cursor, devemos fech-lo atravs do comando CLOSE, que libera as linhas do cursor;
Declarando um cursor
Elaborado por Josinei Barbosa da Silva
Pgina 48 de 154
Treinamento
Declaramos um cursor utilizando o comando CURSOR. Podemos referencia variveis na query, mas elas precisam ser declaradas antes do comando CURSOR. Sintaxe: CURSOR nome_cursor IS
Comando_select;
Onde: nome_cursor um indentificador PL/SQL. Comando_select um comando SQL sem a clusula INTO. Observaes:
No colocar a clusula INTO na declarao de um cursor, pois ela aparecer depois no comando FETCH.
Exemplo: DECLARE CURSOR cs_representante IS SELECT r.rep_in_codigo, r.rep_st_nome FROM representante r; CURSOR cs_notafiscal IS SELECT * FROM nota_fiscal_venda; BEGIN NULL; END; / No exemplo acima, declaramos dois cursores: cs_representante e cs_notafiscal. Em cs_representante ser recuperado o cdigo e o nome de todos os representantes. No cursor cs_notafiscal, ser recuperado todos os dados de todas as notas fiscais..
Abrindo um Cursor
O comando OPEN executa a consulta associada ao cursor e posiciona o cursor antes da primeira linha do conjunto ativo. Sintaxe: OPEN nome_cursor; Onde:
nome_cursor o nome previamente declarado do cursor. OPEN um comando executvel que realiza as seguintes operaes:
Aloca dinamicamente rea de memria para processamento; Faz o PARSE do comando SELECT;
Pgina 49 de 154
Treinamento
Identifica o conjunto de linhas que a consulta retornou. (As linhas retornadas no so colocadas nas variveis quando o comando OPEN executado, isto acontece quando o comando FETCH utilizado);
IMPORTANTE: Se a consulta no retornar linhas quando o cursor for aberto, o PL/SQL no retorna nenhuma exceo. Entretanto, podemos testar o status do cursor depois de cada comando FETCH usando o atributo de cursor SQL%ROWCOUNT, que ser estudado mais adiante.
nome_cursor o nome do cursor declarado na sesso DECLARE; variveln o nome da varivel declarada para armazenar os valores retornados no FETCH;
nome_registro o nome do registro no qual os dados retornados so armazenados. Esta varivel pode ser declarada usando o atributo %ROWTYPE ou uma varivel de tipo registro;
IMPORTANTE:
Valide se o cursor contm linhas. Se quando executar o comando FETCH, o cursor estiver vazio, no teremos nenhuma linha para processar e nenhum erro ser exibido;
Exemplo: DECLARE -- Declarao de variveis vCodigoRep representante.rep_in_codigo%type; vNomeRep representante.rep_st_nome%type; -- Declarao de cursores CURSOR cs_representante is SELECT rep_in_codigo, rep_st_nome FROM representante; BEGIN
Pgina 50 de 154
Treinamento
-- Abre cursor OPEN cs_representante; -- Executa um loop com 10 ciclos FOR i IN 1..10 LOOP -- Extrai dados o registro corrente do cursor e avana para o prximo FETCH cs_representante INTO vCodigoRep , vNomeRep; -- Imprime dados extrados na tela dbms_output.put_line(vCodigoRep||' - '||vNomeRep); END LOOP; END; / No exemplo acima, o programa imprime na tela 10 representantes, um a um.
Fechando o Cursor
O comando CLOSE desabilita o cursor e, conseqentemente, o conjunto ativo torna-se indefinido. Sempre que o processamento do comando SELECT estiver completo, feche o cursor, isto permite que o cursor seja reaberto quando preciso. Sintaxe: CLOSE nome_cursor; Se for aplicado o comando FETCH em um cursor fechado, a exceo INVALID_CURSOR ocorrer. O comando CLOSE libera a rea de memria alocada para o cursor. possvel terminar um bloco PL/SQL sem precisar fechar o cursor, mas importante que se torne um hbito fech-lo, pois assim, estaremos poupando recursos da mquina. IMPORTANTE: Existe um limite mximo de cursores abertos por usurio que determinado pelo parmetro do Oracle OPEN_CURSORS. Exemplo: DECLARE -- Declarao de variveis vCodigoRep representante.rep_in_codigo%type; vNomeRep representante.rep_st_nome%type; -- Declarao de cursores CURSOR cs_representante is SELECT rep_in_codigo, rep_st_nome FROM representante; BEGIN -- Abre cursor OPEN cs_representante; -- Executa um loop com 10 ciclos FOR i IN 1..10 LOOP -- Extrai dados o registro corrente do cursor e avana para o prximo FETCH cs_representante INTO vCodigoRep , vNomeRep; -- Imprime dados extrados na tela dbms_output.put_line(vCodigoRep||' - '||vNomeRep);
Elaborado por Josinei Barbosa da Silva
Pgina 51 de 154
Treinamento
de linhas do cursor o mais recente FETCH retornar uma linha. o mais recente FETCH no retornar uma linha. o cursor estiver aberto.
Um atributo de cursor explcito no pode ser referenciado diretamente de uma instruo SQL. necessrio atribuir o seu retorno para uma varivel. Para extrair o valor de um atributo, ele deve ser precedido do nome do cursor, como no exemplo abaixo: DECLARE -- Declarao de variveis vCodigoRep representante.rep_in_codigo%type; vNomeRep representante.rep_st_nome%type; -- Declarao de cursores CURSOR cs_representante is SELECT rep_in_codigo, rep_st_nome FROM representante; BEGIN -- Abre cursor se ainda no estiver aberto IF NOT cs_representante%ISOPEN THEN OPEN cs_representante; END IF; -- Executa um loop com 10 ciclos FOR i IN 1..10 LOOP -- Extrai dados o registro corrente do cursor e avana para o prximo FETCH cs_representante INTO vCodigoRep , vNomeRep; -- Imprime dados extrados na tela dbms_output.put_line(vCodigoRep||' - '||vNomeRep); END LOOP; -- Fechar cursor CLOSE cs_representante; END;
/
No exemplo acima, verificamos se o cursor j estava aberto, usando para isso o atributo %ISOPEN. Alm de utilizar o atributo %ISOPEN para verificar se o cursor est aberto, recomendado o uso do atributo %NOTFOUND para determinar quando sair do LOOP. Este atributo retorna FALSE se o ltimo FETCH
Pgina 52 de 154
Treinamento
retornar uma linha e TRUE se no retornar (Antes do primeiro FETCH, o valor do atributo NULL). Abaixo, temos um exemplo do uso do atributo %NOTFOUND: DECLARE -- Declarao de variveis vCodigoRep representante.rep_in_codigo%type; vNomeRep representante.rep_st_nome%type; -- Declarao de cursores CURSOR cs_representante is SELECT rep_in_codigo, rep_st_nome FROM representante; BEGIN -- Abre cursor se ainda no estiver aberto IF NOT cs_representante%ISOPEN THEN OPEN cs_representante; END IF; -- Executa um loop com 10 ciclos LOOP -- Extrai dados o registro corrente do cursor e avana para o prximo FETCH cs_representante INTO vCodigoRep , vNomeRep; -- Sai do Loop quando no houver mais registros para processar EXIT WHEN cs_representante%NOTFOUND; -- Imprime dados extrados na tela dbms_output.put_line(vCodigoRep||' - '||vNomeRep); END LOOP; -- Fechar cursor CLOSE cs_representante; END; / O atributo %ROWCOUNT tambm pode ser de grande utilidade, mas lembre-se que:
Antes do primeiro FETCH (mesmo com o cursor j aberto), seu valor 0 (zero);
Se o cursor no estiver aberto e for referenciado pelo atributo %ROWCOUNT, ocorrer a exceo INVALID_CURSOR .
Para processar diversas linhas de um cursor explcito, definimos um LOOP para realizar um fetch em cada iterao. Eventualmente, todas as linhas do cursor so processadas e quando um FETCH no retornar mais linhas, o atributo %NOTFOUND passa a ser TRUE. Use os atributos de cursores explcitos para testar o sucesso de cada FETCH, antes de fazer qualquer referncia no cursor. Um LOOP infinito ir acontecer se nenhum critrio de sada for utilizado.
Pgina 53 de 154
Treinamento
cada caso deve ser estudado com cautela. conveniente utilizar o atributo %ROWTYPE, pois as linhas do cursor sero carregadas neste registro e seus valores carregados diretamente nos campos deste registro. A seguir temos dois exemplos. No primeiro mostramos a utilizao do Tipo registro e no segundo, o uso do atributo %ROWTYPE: Exemplo do uso de Tipo Registro DECLARE -- Declarao de tipo registro TYPE TRepresentante IS RECORD( Codigo representante.rep_in_codigo%type, Nome representante.rep_st_nome%type ); -- Declarao de variveis rRep Trepresentante; -- Declarao de cursores CURSOR cs_representante is SELECT rep_in_codigo, rep_st_nome FROM representante; BEGIN -- Abre cursor se ainda no estiver aberto IF NOT cs_representante%ISOPEN THEN OPEN cs_representante; END IF; -- Executa um loop com 10 ciclos LOOP -- Extrai dados o registro corrente do cursor e avana para o prximo FETCH cs_representante INTO rRep; -- Sai do Loop quando no houver mais registros para processar EXIT WHEN cs_representante%NOTFOUND; -- Imprime dados extrados na tela dbms_output.put_line(rRep.Codigo||' - '||rRep.Nome); END LOOP; -- Fechar cursor CLOSE cs_representante; END; / No exemplo acima, embora seja melhor do que definir uma varivel para cada coluna do cursor, se a SELECT do cursor for alterada para retornar mais colunas ou menos, o TYPE TRepresentante tambm precisar ser alterado. Por esse motivo, o uso do atributo %ROWTYPE muito mais simples, como podemos ver no segundo exemplo: DECLARE -- Declarao de cursores CURSOR cs_representante is SELECT rep_in_codigo, rep_st_nome FROM representante;
Pgina 54 de 154
Treinamento
-- Declarao de variveis rRep cs_representante%ROWTYPE; BEGIN -- Abre cursor se ainda no estiver aberto IF NOT cs_representante%ISOPEN THEN OPEN cs_representante; END IF; -- Executa um loop com 10 ciclos LOOP -- Extrai dados o registro corrente do cursor e avana para o prximo FETCH cs_representante INTO rRep; -- Sai do Loop quando no houver mais registros para processar EXIT WHEN cs_representante%NOTFOUND; -- Imprime dados extrados na tela dbms_output.put_line(rRep.rep_in_codigo||' - '||rRep.rep_st_nome); END LOOP; -- Fechar cursor CLOSE cs_representante; END; / Dessa maneira, qualquer alterao na estrutura do cursor, refletir na varivel que rRep.
Pgina 55 de 154
Treinamento
nome_registro o nome do registro implcito declarado; nome_cursor o nome do cursor previamente declarado pelo usurio.
Exemplo: DECLARE -- Declarao de cursores CURSOR cs_representante is SELECT rep_in_codigo, rep_st_nome FROM representante; BEGIN -- Inicia o loop no conjunto ativo do cursor FOR rRep in cs_Representante LOOP -- Imprime dados extrados na tela dbms_output.put_line(rRep.rep_in_codigo||' - '||rRep.rep_st_nome); END LOOP; END; / IMPORTANTE:
No declare o registro que controla o LOOP por que ele declarado implicitamente;
No use LOOP Cursor FOR quando as operaes do cursor tiverem que ser manipuladas explicitamente.
LOOP Cursor FOR usando Sub-Consultas Quando usamos uma Sub-consulta em um LOOP Cursor FOR, no precisamos declarar um cursor. Veja abaixo: BEGIN -- Inicia o loop no conjunto ativo do cursor FOR rRep in (SELECT rep_in_codigo, rep_st_nome FROM representante) LOOP -- Imprime dados extrados na tela dbms_output.put_line(rRep.rep_in_codigo||' - '||rRep.rep_st_nome); END LOOP; END; / Embora parea simples, o uso desse recurso pode no ser favorvel a formatao do cdigo e, conseqentemente, sua manuteno. No exemplo acima, montamos um LOOP Cursor FOR com base em uma declarao SELECT que acessa apenas uma tabela e retorna duas colunas. Agora, imagine se fosse uma SQL com relacionamento entre vrias tabelas, com chaves primrias compostas, retornando dezenas de colunas, etc., etc.. Com certeza o cdigo no estaria assim, to amigvel. Por isso, procure declarar os cursores, SEMPRE, considerando ser uma boa prtica de programao.
Pgina 56 de 154
Treinamento
nome_cursor o nome do cursor declarado pelo usurio. nome_parametro o nome do parmetro. tipo o tipo do parmetro.
Para cada parmetro declarado no cursor, dever existir um correspondente quando o comando OPEN for usado. Estes parmetros, possuem os mesmos tipos de dados que as variveis escalares, a nica diferena que no fornecemos seus tamanhos. Exemplo: DECLARE -- Declarao de cursores CURSOR cs_representante(pMenorMedia NUMBER, pMaiorMedia NUMBER) is SELECT rep_in_codigo, rep_st_nome FROM representante WHERE rep_vl_mediamensalvendas BETWEEN pMenorMedia AND pMaiorMedia; BEGIN -- Abre cursor para representantes com mdia entre 40000 e 80000 dbms_output.put_line('Representantes com mdia entre 40000 e 80000'); FOR rRep in cs_Representante(40000,80000) LOOP /* Imprime na tela os vendedores cuja mdia de vendas mensal Est no intervalo de 40000 e 80000 */ dbms_output.put_line(rRep.rep_in_codigo||' - '||rRep.rep_st_nome); END LOOP; -- Abre cursor para representantes com mdia entre 80001 e 100000 dbms_output.put_line('Representantes com mdia entre 80001 e 100000'); FOR rRep in cs_Representante(80001,100000) LOOP /* Imprime na tela os vendedores cuja mdia de vendas mensal Est no intervalo de 80001 e 100000 */ dbms_output.put_line(rRep.rep_in_codigo||' - '||rRep.rep_st_nome); END LOOP; END; /
Cursores implcitos
Elaborado por Josinei Barbosa da Silva
Pgina 57 de 154
Treinamento
Como j mencionado antes, o Oracle cria e abre um cursor para cada declarao SQL que no faz parte de um cursor declarado explicitamente. O cursor implcito mais recente pode ser chamado de cursor SQL. Voc no pode usar os comandos OPEN, CLOSE e FETCH com um cursor implcito. Entretanto, voc pode usar os atributos de cursor para acessar as informaes sobre a declarao SQL executada mais recentemente por meio do cursor SQL
de linhas do cursor o mais recente FETCH retornar uma linha. o mais recente FETCH no retornar uma linha. o cursor estiver aberto.
Como os cursores implcitos no tm nome, voc deve substituir o nome do cursor pela palavra SQL junto ao atributo. O cursor implcito contm as informaes sobre o processamento da ltima declarao SQL (INSERT, UPDATE, DELETE e SELECT INTO) que no foi associada a um cursor explcito. Voc pode usar os atributos do cursor implcito nas declaraes PL/SQL, no nas declaraes SQL. Quando estudamos as excees, vimos o uso de um atributo de cursor implcito em um de nossos exemplos, mas vamos relembr-lo agora que entendemos o seu funcionamento por completo: DECLARE -- Declarar exceo eRegistroInexistente exception; BEGIN -- Excluir cliente DELETE cliente c WHERE c.cli_in_codigo = 1000; -- Se cliente no existe, gerar exceo IF SQL%NOTFOUND THEN raise eRegistroInexistente; END IF; -- Validar excluso COMMIT; EXCEPTION -- Se registro no existe, informa usurio WHEN eRegistroInexistente THEN ROLLBACK; dbms_output.put_line('Cliente 1000 no existe!'||chr(10)|| 'Nenhum registro foi excludo.' ); END; / Nesse exemplo utilizamos o atributo %NOTFOUND para verificarmos quantos registros foram afetados
Elaborado por Josinei Barbosa da Silva
Pgina 58 de 154
Treinamento
pela comando DELETE. Note que o atributo precedido pela palavra SQL, que refere-se a instruo SQL mais recente (o DELETE)
Variveis de cursor
Como vimos nos tpicos anteriores, o cursor da PL/SQL uma rea nomeada do banco de dados. Uma varivel de cursor, por definio, uma referncia quela rea nomeada. Uma varivel de cursor como um ponteiro de uma linguagem de programao como C. As variveis de cursor indicam a rea de trabalho de uma consulta, na qual o conjunto de resultados da consulta armazenado. Uma varivel de cursor tambm dinmica por natureza porque ela no est vinculada a uma consulta especfica. A Oracle conserva essa rea de trabalho enquanto um ponteiro de cursor est apontando para ela. Voc pode usar uma varivel de cursor para qualquer consulta de tipo compatvel. Um dos recursos mais significativos da varivel de cursor que a Oracle permite passar uma varivel de cursor como um argumento para um procedimento ou uma chamada de funo. Para criar uma varivel de cursor, voc primeiro deve criar um tipo de cursor referenciado e depois declara uma varivel de cursor daquele tipo. A sintaxe para definir um tipo cursor a seguinte: TYPE cursor_type_name IS REF CURSOR RETURN return_type; Nessa sintaxe, REF quer dizer referncia, cursor_type_name o nome do tipo do cursor e return_type a especificao de dados do tipo de cursor de retorno. A clusula RETURN opcional. As variveis de cursores podem ser muito teis em situao em que o ambiente aplicativo executa um procedimento PL/SQL (Como procedure ou function) e passa como argumento um cursor e/ou recebe um argumento do tipo cursor. Vejamos um exemplo simples de como podemos utilizar uma varivel do tipo cursor: DECLARE -- Declarao de tipos TYPE TCursor IS REF CURSOR; -- Declarao de variveis vCursor TCursor; -- Sub-rotinas PROCEDURE GerarXML(pCursor TCursor) IS BEGIN NULL; /* implementao para gerar o XML a partir do cursor*/ END; BEGIN -- Abre o cursor atribuindo para a varivel criada OPEN vCursor FOR SELECT rep_in_codigo, rep_st_nome FROM representante; -- Executa a rotina que recebe o cursor e converte seu contedo para XML GerarXML(vCursor); -- Fecha o cursor CLOSE vCursor; END; /
Pgina 59 de 154
Treinamento
IMPORTANTE: Nas prximas lies, estudaremos os procedimentos armazenados, ou seja, procedures e functions, mas como voc pode observar no exemplo acima, podemos criar funes e procedures dentro de qualquer bloco PL/SQL. So as chamadas subrotinas.
Pgina 60 de 154
Treinamento
Procedimentos e funes podem ser implementadas de trs formas: 1. Como subrotina de um bloco annimo; 2. Como procedimento independente e armazenado no banco de dados; 3. Como uma rotina de um pacote armazenado no banco de dados. Nesta lio focaremos as subrotinas em blocos annimos. A principal vantagem de usar subrotinas que voc pode modularizar seu programa, centralizando cdigos que seriam repetidos. Desta forma, caso tal cdigo precisasse de manuteno, estaria implementado em apenas um ponto do programa. Abaixo segue um exemplo simples, onde uma procedure e uma funo so subrotinas de bloco annimo. DECLARE -- Declarao de cursores CURSOR cs_cliente is SELECT c.* FROM cliente c;
Pgina 61 de 154
Treinamento
-- declarao de variveis rCli cs_cliente%ROWTYPE; /***********************************************************/ /* Subrotinas */ /***********************************************************/ -- funo definir se o cliente compra o seu potencial mximo FUNCTION ClienteCompraPotencial(pMediaCompraMensal NUMBER, pMaiorCompra NUMBER) RETURN BOOLEAN IS -- Declarao de variveis vPercentualPotencial NUMBER; vResult BOOLEAN; BEGIN -- Definir quanto a mdia mensal de compras representa da maior compra vPercentualPotencial := (pMediaCompraMensal / pMaiorCompra) * 100; -- Resultado s verdadeiro se percentual for igual ou mairo a 80 vResult := vPercentualPotencial >= 80; RETURN(vREsult); END; -- procedimento para exibir mensagem na tela PROCEDURE Exibir(pMensagem VARCHAR2) IS BEGIN dbms_output.put_line(pMensagem); END; BEGIN -- Inicia o loop no conjunto ativo do cursor FOR rCli in cs_Cliente LOOP IF ClienteCompraPotencial(rCli.cli_vl_mediacomprasmensal, rCli.cli_vl_maiorcompra ) THEN -- Imprime dados extrados na tela Exibir(rCli.cli_in_codigo||' - '||rCli.cli_st_nome); END IF; END LOOP; END; / No exemplo acima utilizamos a funo ClienteCompraPotencial para verificar se o cliente est comprando aquilo que tem potencial (em valor) e o procedimento Exibir para imprimir uma mensagem na tela. Imagine que nosso bloco annimo fosse muito maior e que, em vrios pontos precisasse verificar se o Cliente compra o potencial que tem! Ao invs de reescrever a regra para esse clculo em todos esses pontos, basta chamar a funo que passaria a ser o nico ponto de manuteno dessa regra. Outra coisa que deve ser observada que os procedimentos e funes recebem parmetros, como vimos nos cursores . IMPORTANTE: Ao invocar um procedimento que possui parmetros de entrada, procure informar o
Pgina 62 de 154
Treinamento
valor de cada parmetro na ordem definida, pois no h como o Oracle adivinhar os valores se estiverem embaralhados. possvel informar parmetros em ordem diferente da definida, porm mais trabalhoso, pois voc precisar informar alm do valor, o nome do parmetro ao que se refere, como abaixo: DECLARE ... BEGIN ... ClienteCompraPotencial(pMaiorCompra =>100000, pMediaCompraMensal => 80000) END; /
Pgina 63 de 154
Treinamento
Exerccios propostos
1. Crie um bloco annimo com um cursor automatizado que carregue todos os clientes de um determinado ramo de atividade, mediante o uso de parmetros. Abra um loop para cada ramo de atividade possvel (HIPERMERCADO, SUPERMERCADO, MERCADO, MERCEARIA). Antes de iniciar cada loop, imprima na tela o ramo de atividade. Durante o loop, se a data da maior compra do cliente for do ms corrente, imprima na tela a seguinte mensagem 'A maior compra do cliente x foi realizada no ms corrente'. Onde x cdigo do cliente. 2. Crie um bloco annimo com um cursor no automatizado que carregue todos os produtos e imprima na tela:
'Novo record de venda do produto x n', se o valor da ltima venda for maior ou igual ao valor da maior venda, onde x o cdigo do produto e n o valor da maior venda; 'O record de venda do produto x no foi alcanado', se o valor da ltima venda no for maior ou igual ao valor da maior venda; 'A meta mensal de vendas do representante x est abaixo de sua mdia mensal', se a meta mensal de vendas do representante for menor do que sua mdia mensal; 'O representante x tem potencial maior do que sua meta mensal.', se a meta mensal for maior do que sua mdia e menor do que sua maior venda; 'O representante x atingiu todo o potencial de vendas!', se a meta mensal for maior do que sua mdia e maior do que sua maior venda, ou seja, se no atender as duas primeiras condies. (utilize IF...ELSIF...ELSE); onde x o cdigo do represente.
3.
Crie um bloco annimo com um cursor que carregue todos os representantes e imprima na tela:
4. Construa um bloco annimo que: a) Inicialize uma varivel do tipo DATE com a data do primeiro dia do ms atual; b) Execute um loop que simule um REPEAT...UNTIL; c) Incremente a varivel em um dia, a cada ciclo do loop e imprima na tela o valor obtido; d) Abandone o loop quando a data no avanar o ms atual. 5. Reproduza o exerccio anterior declarando no bloco annimo, uma funo para incrementar a data a ser impressa na tela.
Pgina 64 de 154
Treinamento
03 Stored Procedures
1. 2.
Pgina 65 de 154
Treinamento
Os procedimentos so modulares, o que significa que eles deixam voc dividir um programa em unidades gerenciveis e bem definidas; Como os procedimentos so armazenados em um banco de dados, eles podem ser reutilizados. Aps um procedimento ter sido validado, ele pode ser usado repetidamente sem ser recompilado ou distribudo pela rede; Os procedimentos aumentam a segurana do banco de dados. Voc pode restringir o acesso ao banco de dados permitindo que os usurios acessem os dados apenas por meio dos procedimentos armazenados;
Uma das coisas mais teis que voc pode fazer com o seu conhecimento da PL/SQL talvez seja usla para escrever stored procedures. A encapsulao do cdigo que voc escreveu anteriormente em uma funo armazenada permite que voc a compile uma vez e armazene no banco de dados para uso futuro. Da prxima vez que quiser executar aquele bloco PL/SQL (toda stored procedure um bloco PL/SQL), voc s precisa invocar a funo. As duas maiores diferenas entre um bloco annimo e uma stored procedure so: 1. Blocos annimos no so armazenados no banco de dados e stored procedures so. Quando o desenvolvedor cria um bloco annimo, responsabilidade dele guarda o seu cdigo, ou seja, se o bloco for desenvolvido, executado e no for salvo em algum arquivo de sistema operacional (ou objeto de banco de dados criado para isso), seu cdigo ser perdido. Uma stored procedure, para ser usada, deve ser compilada e quando compilada o Oracle armazena seu cdigo no dicionrio de dados, permitindo assim a sua recuperao para posterior manuteno; 2. Stored Procedure possui um cabealho para nome-lo, declarar variveis de input e output e tipo de retorno; blocos annimos no. A estrutura de um procedimento armazenada exatamente igual procedimentos implementados dentro de blocos PL/SQL e o que define que ele ser armazenada o uso do comando CREATE OR REPLACE antes de seu nome.
Pgina 66 de 154
Treinamento
Procedimentos
Uma procedure nada mais do que um bloco PL/SQL nomeado sem retorno definido (somente funes possuem um retorno definido). A grande vantagem sobre um bloco PL/SQL annimo que pode ser compilado e armazenado no banco de dados como um objeto de schema. Graas a essa caracterstica, as procedures so de fcil manuteno, o cdigo reutilizvel e permitem que trabalhemos com mdulos de programa. A sintaxe bsica de uma procedure : CREATE [OR REPLACE] PROCEDURE [schema.]nome_da_procedure [(parmetro1 [modo1] tipodedado1, parmetro2 [modo2] tipodedado2, ...)] IS|AS [Bloco PL/SQL] Onde:
replace indica que caso a procedure exista ela ser eliminada e substituda pela nova verso criada pelo comando;
parmetro indica o nome da varivel PL/SQL que passada na chamada da procedure ou o nome da varivel que retornar os valores da procedure ou ambos. O que ir conter em parmetro depende de modo; modo Indica que o parmetro de entrada (IN), sada (OUT) ou ambos (IN OUT). importante notar que IN o modo default, ou seja, se no dissermos nada o modo do nosso parmetro ser, automaticamente, IN; tipodedadoN indica o tipo de dado do parmetro. Pode ser qualquer tipo de dado do SQL ou do PL/SQL. Pode usar referencias como %TYPE, %ROWTYPE ou qualquer tipo de dado escalar ou composto. Tambm possvel definir um valor default. Ateno: no possvel fazer qualquer restrio ao tamanho do tipo de dado neste ponto. is/as: Por conveno usamos IS na criao de procedures armazenadas e AS quando estivermos criando pacotes. Bloco PL/SQL inicia com uma clusula BEGIN nome_da_procedure onde as aes sero executadas.
termina
com
END
ou
END
Abaixo, temos um exemplo de uma procedure armazenada que recupera a maior vendas de um produto dentro de um perodo e atualiza essa informao no cadastro do produto, se for maior do que o valor j existente : CREATE OR REPLACE PROCEDURE pr_AtualizaMaiorVendaProduto(pProduto produto.pro_in_codigo%TYPE, pDataInicial DATE, pDataFinal DATE ) IS vValorMaiorVenda_old NUMBER := 0; vValorMaiorVenda_new NUMBER := 0; BEGIN -- Recupera o valor da maior venda registrada no produto BEGIN SELECT pro_vl_maiorvenda
Elaborado por Josinei Barbosa da Silva
Pgina 67 de 154
Treinamento
INTO vValorMaiorVenda_old FROM produto WHERE pro_in_codigo = pProduto; EXCEPTION WHEN no_data_found THEN raise_application_error(-20100,'Produto no cadastrado!'); WHEN OTHERS THEN raise_application_error(-20100,'Erro ao tentar recuperar dados do produto!'); END; -- Recupera o valor da maior venda do produto no perodo SELECT MAX(i.infv_vl_total) INTO vValorMaiorVenda_new FROM nota_fiscal_venda n, item_nota_fiscal_venda i WHERE n.nfv_dt_emissao BETWEEN pDataInicial AND pDataFinal AND n.nfv_in_numero = i.nfv_in_numero AND i.pro_in_codigo = pProduto; -- Se a maior venda do perodo for maior do que a j cadastrada, atualiza IF vValorMaiorVenda_new > vValorMaiorVenda_old THEN UPDATE produto SET pro_vl_maiorvenda = vValorMaiorVenda_new WHERE pro_in_codigo = pProduto; END IF; END; / Esse procedimento armazenado pode ser executado diretamente do prompt de uma ferramenta de comandos, como abaixo: SQL> execute pr_AtualizaMaiorVendaProduto(20,SYSDATE,last_day(SYSDATE)); Ou a partir de um bloco PL/SQL, como abaixo: BEGIN pr_AtualizaMaiorVendaProduto(20,SYSDATE,last_day(SYSDATE)); END; /
Funes
Como vimos antes, as funes so semelhantes aos procedimentos da PL/SQL, exceto pelas seguintes diferenas:
A sintaxe bsica de uma funo : CREATE [OR REPLACE] FUNCTION [schema.]nome_da_funcao [(parmetro1 [modo1] tipodedado1,
Pgina 68 de 154
Treinamento
parmetro2 [modo2] tipodedado2, ...)] RETURN TipoRetorno IS|AS [Bloco PL/SQL] Onde:
replace indica que caso a funo exista ela ser eliminada e substituda pela nova verso criada pelo comando;
parmetro indica o nome da varivel PL/SQL que passada na chamada da funo ou o nome da varivel que retornar os valores da funo ou ambos. O que ir conter em parmetro depende de modo;
modo Indica que o parmetro de entrada (IN), sada (OUT) ou ambos (IN OUT). importante notar que IN o modo default, ou seja, se no dissermos nada o modo do nosso parmetro ser, automaticamente, IN;
tipodedadoN indica o tipo de dado do parmetro. Pode ser qualquer tipo de dado do SQL ou do PL/SQL. Pode usar referencias como %TYPE, %ROWTYPE ou qualquer tipo de dado escalar ou composto. Tambm possvel definir um valor default. Ateno: no possvel fazer qualquer restrio ao tamanho do tipo de dado neste ponto.
is/as: Por conveno usamos IS na criao de funes armazenadas e AS quando estivermos criando pacotes.
Bloco PL/SQL inicia com uma clusula BEGIN e termina com END ou END nome_da_funcao onde as aes sero executadas.
Vamos escrever uma funo armazenada que recebe o cdigo do cliente e retorna a quantidade de notas faturadas para ele, dentro de um perodo: CREATE OR REPLACE FUNCTION fn_QtdeNFV(pCodigoCliente NUMBER, pDataInicial DATE, pDataFinal DATE ) RETURN INTEGER IS -- Declarao de variveis vResult INTEGER :=0; BEGIN -- Recupera quantidade de notas do cliente dentro do perodo SELECT COUNT(*) INTO vResult FROM nota_fiscal_venda nfv WHERE nfv.cli_in_codigo = pCodigoCliente AND nfv.nfv_dt_emissao BETWEEN pDataInicial AND pDataFinal; RETURN(vResult); END; / Como a funo acima armazenada, podemos us-la das seguintes formas: 1. Atribuindo seu retorno diretamente para uma varivel: DECLARE
Elaborado por Josinei Barbosa da Silva
Pgina 69 de 154
Treinamento
vQtdeNFV NUMBER; BEGIN vQtdeNFV := fn_QtdeNFV(10, ADD_MONTHS(SYSDATE,-1), SYSDATE); END; / Nesse exemplo, atribumos para a varivel vQtdeNFV, o retorno da funo fQtdeNFV, tendo como perodo os ltimos trinta dias (SYSDATE igual a data atual e ADD_MONTHS adiciona o nmero de meses informados) 2. Recuperando seu valor atravs de uma instruo SQL: SELECT fn_QtdeNFV(10, ADD_MONTHS(SYSDATE,-1), SYSDATE) QtdeNFV FROM dual; A tabela DUAL uma tabela especial do Oracle que sempre existe, sempre tem exatamente uma linha e sempre tem exatamente uma coluna. Ela a tabela perfeita para ser usada quando se experimentam as funes. A seleo da funo na tabela DUAL faz com que o resultado da funo seja exibido. Se a declarao SELECT acima estivesse dentro de um bloco PL/SQL, o retorno da funo fQtdeNFV poderia ser atribudo uma varivel atravs da clusula INTO. IMPORTANTE: Evite utilizar comandos DML (INSERT, UPDATE e DELETE) em funes, pois se elas forem invocadas de uma declarao SELECT voc receber o seguinte erro: ORA-14551: no possvel executar uma operao DML dentro de uma consulta
DROP o comando de excluso; PROCEDURE ou FUNCTION defini o tipo de procedimento a ser excludo; nome_do_procedimento o nome do procedimento armazenado.
Usando os parmetros
Os procedimentos usam os parmetros para passar as informaes. Quando um parmetro est sendo passado para um procedimento, ele conhecido como um parmetro real. Os parmetros declarados
Elaborado por Josinei Barbosa da Silva
Pgina 70 de 154
Treinamento
como internos a um procedimento so conhecidos como parmetros internos ou formais. O parmetro real e seu parmetro formal correspondente devem pertencer a datatypes compatveis. Por exemplo, a PL/SQL no pode converter um parmetro real com um datatype DATE para um parmetro formal com um datatype LONG. Nesse caso, o Oracle retornaria uma mensagem de erro. Essa questo de compatibilidade tambm se aplica aos valores de retorno.
Definies de parmetro
Quando voc invoca um procedimento, deve passar um valor para cada um dos parmetros do procedimento. Se voc passar valores para o parmetro, eles so posicionais e devem aparecer na mesma ordem em que aparecem na declarao do procedimento. Se voc passar nomes de argumentos, eles podem aparecer em qualquer ordem. Voc pode ter uma combinao entre valores e nomes nos valores do argumento. Quando esse for o caso, os valores identificados na ordem devem preceder os nomes de argumento.
Dependncias de procedimentos
Um dos recursos inerentes da PL/SQL que ela verifica o banco com base nos objetos de dados para ter certeza de que as operaes de um procedimento, uma funo ou um pacote so possveis, os quais o usurio tem acesso. Por exemplo, se voc tiver um procedimento que exija acesso a diversas tabelas e vises, a PL/SQL verifica durante o tempo de compilao se aquelas tabelas e vises esto presentes e disponveis para o usurio. Diz-se que o procedimento dependente dessas tabelas e vises. IMPORTANTE: A PL/SQL recompila automaticamente todos os objetos dependentes quando voc recompila explicitamente o objeto pai. Essa recompilao automtica dos objetos dependentes acontece quando o objeto dependente chamado. Assim sendo, no recomendado recompilar um mdulo pai de um sistema de produo, pois isso faz com que todos os objetos dependentes sejam recompilados e, conseqentemente, pode causar problemas de desempenho para o seu sistema de produo.
Pgina 71 de 154
Treinamento
Pacotes
Um pacote uma coleo encapsulada de objetos de esquema relacionados. Esses objetos podem incluir procedimentos, funes, variveis, constantes, cursores, tipos e excees. Um pacote compilado e depois armazenado no dicionrio de dados do banco de dados como um objeto de esquema. Um uso comum dos pacotes para reunir todos os procedimentos, funes e outros objetos relacionados que executam tarefas semelhantes. Por exemplo, voc pode agrupar todos os objetos utilizados para gerar um resumo de vendas em um nico pacote. Os pacotes contm subprogramas armazenados, ou programas isolados, os quais so chamados subprogramas do pacote. Esses subprogramas podem ser chamados de outro programa armazenado, triggers, programas ou de qualquer um dos programas interativos da Oracle, tais como O SQL*Plus. Ao contrrio dos subprogramas armazenados, o pacote em si no pode ser chamado ou aninhado, e os parmetros no podem ser passados para ele.
As variveis public e os cursores de um pacote persistem durante a sesso, ou seja, todos os cursores e procedimentos que so executados nesse ambiente podem compartilhar deles;
Eles melhoram o desempenho carregando vrios objetos ao mesmo tempo. Assim sendo, as chamadas subseqentes a subprogramas relacionados no pacote no requerem entrada/sada; Eles promovem a reutilizao do cdigo por meio do uso das bibliotecas que contm os procedimentos armazenados e as funes, eliminando assim a codificao redundante.
Estrutura de um pacote
Geralmente um pacote tem dois componentes: 1. Especificao: Declara os tipos, as variveis, as constantes, as excees, os cursores e os subprogramas que esto disponveis para uso. 2. Corpo: Define totalmente as funes e os procedimentos e, assim, implementa a especificao.
A especificao do pacote
A especificao do pacote contm declaraes public. Isso significa que os objetos declarados no pacote so acessveis de qualquer parte do pacote e so disponveis programas externos. Assim sendo, todas as informaes que o aplicativo precisa para executar um programa armazenado esto contidas na especificao do pacote. A sintaxe do comando de especificao o seguinte: CREATE [OR REPLACE] PACKAGE package_name [AUTHID {CURRENT_USER |DEFINER}] {IS | AS} [package body object declaration]
Pgina 72 de 154
Treinamento
AUTHID representa o tipo de direitos que voc quer invocar quando o pacote for executado. Os direitos podem ser aquele de CURRENT_USER ou aquele do pacote DEFINER (Criador);
package body object declaration o lugar no qual voc lista os objetos que sero criados dentro do pacote.
Abaixo temos um exemplo de uma declarao de pacote: CREATE OR REPLACE PACKAGE pck_cliente AS -- Author : JOSINEI -- Created : 27/6/2007 17:45:36 -- Purpose : -- atualiza dados cliente /*--------------------------------------------------------------------------*/ /* Public function and procedure declarations */ /*--------------------------------------------------------------------------*/ -- Procedimento para atualizar valor mdio de compras do cliente PROCEDURE AtualizarMediaComprasMensal(pCodigoCliente NUMBER); -- Procedimento para atualizar dados de maior compra do cliente PROCEDURE AtualizarMaiorCompra(pCodigoCliente NUMBER); -- Funo que retorna o endereo do cliente FUNCTION EnderecoCliente(pCodigoCliente NUMBER) RETURN VARCHAR2; END pck_cliente; /
O corpo do pacote
O corpo de um pacote contm a definio dos objetos public que voc declara na especificao. O corpo tambm contm as outras declaraes de que so privadas do pacote e que esto acessveis apenas para os objetos do corpo do pacote. Os objetos privados declarados no corpo do pacote no so acessveis para outros objetos fora do pacote. Ao contrrio da especificao do pacote, a parte de declarao do corpo do pacote pode conter corpos de subrotinas. IMPORTANTE: Se a especificao declarar apenas constantes e variveis, o corpo do pacote no necessrio. A sintaxe para criar o corpo (body) do pacote o seguinte: CREATE [OR REPLACE] PACKAGE BODY package_name {IS | AS} [package body object declaration] END [package_name]; Nessa sintaxe, as palavras-chave e os parmetros so os seguinte:
package_name o nome do pacote definido pelo criador. importante dar-lhe um nome significativo e que represente o contedo do corpo do pacote. Esse nome deve ser igual ao nome
Pgina 73 de 154
Treinamento
Abaixo segue um exemplo de corpo de pacote que implementa as rotinas pblicas que declaramos na especificao do pacote pck_cliente: CREATE OR REPLACE PACKAGE BODY pck_cliente AS -- Declarao de variveis privadas rCli cliente%ROWTYPE; -- Procedimento para atualizar valor mdio de compras do cliente PROCEDURE AtualizarMediaComprasMensal(pCodigoCliente NUMBER) IS BEGIN -- Recupera a mdia a partir da soma mensal SELECT AVG(rmv_vl_total) INTO rCli.cli_vl_mediacomprasmensal FROM (SELECT rmv_in_mes, rmv_in_ano, sum(rmv_vl_total) rmv_vl_total FROM resumo_mensal_venda WHERE cli_in_codigo = pCodigoCliente GROUP BY rmv_in_mes, rmv_in_ano ); -- Atualiza Mdia Compras Mensal UPDATE cliente SET cli_vl_mediacomprasmensal = rCli.cli_vl_mediacomprasmensal WHERE cli_in_codigo = pCodigoCliente; -- Finaliza transao confirmando alteraes COMMIT; END; -- Procedimento para atualizar dados de maior compra do cliente PROCEDURE AtualizarMaiorCompra(pCodigoCliente NUMBER) IS BEGIN -- Recupera a maior compra do cliente SELECT MAX(nfv_vl_total) INTO rCli.cli_vl_maiorcompra FROM nota_fiscal_venda WHERE cli_in_codigo = pCodigoCliente; -- Atualiza Maior Compra do cliente UPDATE cliente SET cli_vl_maiorcompra = rCli.cli_vl_maiorcompra WHERE cli_in_codigo = pCodigoCliente; -- Finaliza transao confirmando alteraes COMMIT; END; -- Funo que retorna o endereo do cliente FUNCTION EnderecoCliente(pCodigoCliente NUMBER) RETURN VARCHAR2 IS vEnderecoCliente VARCHAR2(256);
Elaborado por Josinei Barbosa da Silva
Pgina 74 de 154
Treinamento
BEGIN -- Recupera o endereo concatenado do cliente SELECT cli_st_endereco ||' - '||cli_st_cidade ||' - '||cli_st_uf INTO vEnderecoCliente FROM cliente WHERE cli_in_codigo = pCodigoCliente; -- Retorna o endereo concatenado RETURN(vEnderecoCliente); END; END pck_cliente; / Observando o corpo desse pacote, vemos que todos procedimentos recebem o cdigo do cliente como parmetro (e todos eles so pblicos se observarmos o exemplo de especificao). Poderamos ter definido na especificao, uma varivel para receber esse cdigo e ento, em todos os procedimentos utilizaramos o seu valor, que seria inicializado antes de executar os procedimentos, como veremos a seguir.
Onde:
package_name o nome do pacote; type_name o nome de um tipo; variable_name o nome de uma varivel; subprogram_name o nome de um procedimento interno do pacote.
Poderamos invocar um procedimento do pacote que utilizamos para exemplificar a especificao e o corpo de um pacote, da seguinte maneira: DECLARE
Pgina 75 de 154
Treinamento
Recompilando pacotes
Para recompilar um pacote voc deve usar o comando ALTER PACKAGE com a palavra-chave compile. Essa recompilao explcita elimina a necessidade de qualquer recompilao implcita no runtime e evita todos os erros associados de compilao no aps modificaes no pacote. Quando recompila um pacote, voc tambm recompila todos os objetos definidos dentro do pacote. A recompilao no altera a definio do pacote ou de nenhum de seus objetos. Os seguintes exemplos recompilam apenas o corpo de um pacote. A segunda declarao recompila todo o pacote, incluindo o corpo e a especificao: ALTER PACKAGE pck_cliente COMPILE BODY; ALTER PACKAGE pck_cliente COMPILE PACKAGE; Voc pode compilar todos os pacotes do schema usando o utilitrio dbms_utility da Oracle. SQL> execute dbms_utility.compile_schema(SCHEMA =>'JSILVA');
Dependncia de pacote Durante o processo de recompilao de um pacote, o Oracle invalida todos os objetos dependentes. Esses objetos incluem procedimentos armazenados ou pacotes que chamam ou referenciam os objetos declarados na especificao recompilada. Quando o programa de outro usurio chama ou referencia um objeto dependente antes de ser recompilado, o Oracle o recompila automaticamente no runtime. Durante a recompilao do pacote, o Oracle determina se os objetos dos quais o corpo do pacote depende so vlidos. Se algum desses objetos for invlido, o Oracle os recompila antes de recompilar o corpo do pacote. Se a recompilao for bem-sucedida, ento o corpo d pacote se torna vlido. Se forem detectados erros as mensagens de erro apropriadas so geradas e o corpo do pacote permanece invlido.
Pgina 76 de 154
Treinamento
Triggers
O que um Trigger?
Um trigger um bloco PL/SQL que associado a um evento especfico, armazenado em um banco de dados e executado sempre que o evento ocorrer. No Oracle Database possvel criar triggers de tipos diferentes, como por exemplo:
Triggers DML (Data Manipulation Language): Triggers tradicionais para tratamento de operaes INSERT, UPDATE e DELETE, que o Oracle Database suporta h muitos anos. Instead-of: Introduzidos na verso 8 do Oracle Database como um modo de possibilitar a atualizao de determinados tipos de views. Data Definition Language (DDL): Disponvel a partir do Oracle Database 8i, muito utilizado para auditoria de alteraes nos objetos de um schema. Banco de Dados: A exemplo das triggers DDL, tambm foi disponibilizada na verso 8i e utilizada nos eventos de banco de dados, tais como inicializao e fechamento do banco, logon, logoff e etc..
Embora os triggers sejam classificados em tipos diferentes, a estrutura entre esses tipos so semelhantes. Neste treinamento, estudaremos os triggers DML.
Triggers DML
Os triggers DML so os triggers tradicionais que podem ser definidos em uma tabela e so executados, ou disparados, em resposta aos seguintes eventos:
Incluso de uma linha em uma tabela Atualizao de uma linha em uma tabela Excluso de uma linha em uma tabela
Evento que dispara o trigger Tabela no qual o evento ocorrer Condio opcional que controla a hora em que o trigger executado Bloco PL/SQL contendo o cdigo a ser executado quando o trigger disparado
Assim como functions, procedures e packages, um trigger armazenado no banco de dados, ou seja, um objeto do banco e sempre executado quando o evento para o qual ele definido ocorre. No importa se o evento disparado por alguma digitao em uma declarao SQL usando SQL*Plus, executando um programa client-server ou qualquer outra ferramenta pela qual seja possvel manipular dados da tabela. Por isso, uma lgica para manipular o dado includo, atualizado ou alterado em uma tabela, sempre ser executado se estiver no trigger. Entre os usos mais comuns de triggers DML, podemos citar a gerao de primary key (PK) e o registro de log de alterao. Abaixo segue um exemplo muito comum de trigger que gera um log de alterao:
Pgina 77 de 154
Treinamento
CREATE OR REPLACE TRIGGER trg_log_preco_prod_iu AFTER UPDATE ON preco_produto FOR EACH ROW BEGIN INSERT INTO log_preco_produto (lpp_st_tipopreco ,lpp_in_codigo ,lpp_vl_unitario_anterior ,lpp_vl_unitario_novo ,lpp_dt_alteracao ,lpp_st_usuario) VALUES (:OLD.ppr_st_tipopreco ,:OLD.pro_in_codigo ,:OLD.ppr_vl_unitario ,:NEW.ppr_vl_unitario ,SYSDATE ,USER); END trg_log_preco_prod_iu; /
Nvel de linha: o mais comum e executado uma vez para cada linha afetada pela SQL que a disparou. Apenas triggers em nvel de linha tm acesso aos valores de dados dos registros afetados (novos e antigos Nvel de declarao: Pouco usada, executado apenas uma vez diante da execuo da SQL.
E os eventos do trigger DML? Podemos definir trs eventos para um trigger DML:
Se combinarmos esses eventos aos nveis e o momento de execuo (quando), teremos 12 tipos de triggers DML.
Pgina 78 de 154
Treinamento
IMPORTANTE: Os triggers definidos de forma idntica so executados sem ordem determinada. Se voc escrever diversos triggers que so disparados antes de uma linha ser atualizada, por exemplo, deve garantir que a integridade do banco de dados no dependa da ordem de execuo. Dicas:
Use os triggers no nvel de linha antes da atualizao para a implantao de regras de negcio complexas e clculos complexos, pois provavelmente voc deseja fazer isso antes que a linha seja inserida; Use os triggers no nvel de linha aps a atualizao para logging das alteraes; Use os triggers no nvel de declarao antes da atualizao para implementar regras de segurana que no dependam dos valores dos registros afetados; No implemente regras de negcio diretamente nos triggers. Prefira implementar regras em stored procedures e apenas execut-las a partir das triggers; NUNCA utilize triggers para implementar integridade referencial. Para isso existe as constraints no banco de dados.
Pgina 79 de 154
Treinamento
Exerccios Propostos
1. Desenvolva uma funo armazenada que receba como argumento o nmero de uma nota fiscal e retorne o valor total dos itens (coluna INFV_VL_TOTAL da tabela de itens). Nomeie essa funo como fnValorTotalItensNotaFiscal e teste utilizando uma declarao SQL simples na tabela DUAL. 2. Desenvolva um procedimento armazenado que busque, para cada nota fiscal cadastrada, o valor total de seus itens e valide se o seu valor atual (coluna NFV_VL_TOTAL) corresponde ao total dos itens e, caso no corresponda, atualize esse valor. Utilize a funo criada no primeiro exerccio para recuperar o valor total dos itens. Nomeie esse procedimento com prAtualizarValorNotaFiscal. 3. Crie um pacote chamado pck_NotaFiscalVenda e implemente nele a funo criada no primeiro exerccio e o procedimento criado no segundo exerccio. Tanto a funo quanto o procedimento, devem ser publicados na especificao do pacote. Adicione nesse pacote, um procedimento que valide se o valor total do item est certo e, caso no esteja, corrija. O valor correto do item obtido multiplicando a quantidade (INFV_QT_FATURADA) pelo valor unitrio (INFV_VL_UNITARIO) e subtraindo o desconto (INFV_PE_DESCONTO). Esse novo procedimento no deve ser pblico e o procedimento prAtualizarValorNotaFiscal deve execut-lo antes de atualizar o valor total da nota. 4. Crie uma procedure que receba dois argumentos: um ano e um ms. Esse procedimento deve recuperar as notas fiscais do perodo informado (ano/ms) e atualizar a tabela de resumo mensal de vendas para cada produto/cliente/representante, sendo que, se o perodo j existir, deve ser atualizado. Nomeie a procedure como prAtualizarVendasMensal. 5. Crie um pacote chamado pck_cliente que contenha:
Uma funo pblica que receba como argumento o cdigo do cliente e retorne o cdigo do produto com maior freqncia de compra (atravs dos itens de nota fiscal ou da tabela de resumo mensal); Uma funo pblica que receba como argumento o cdigo do cliente e retorne o cdigo do produto com maior representatividade financeira nas compras do cliente.
Pgina 80 de 154
Treinamento
O valor da maior venda (coluna REP_VL_MAIORVENDA), obtida na tabela de nota fiscal de vendas; A data da maior venda (coluna REP_DT_MAIORVENDA), obtida na tabela de nota fiscal de vendas; A mdia mensal de venda (coluna REP_VL_MEDIAMENSALVENDAS), obtida na tabela de resumo mensal de vendas; Uma funo privada do pacote que calcule a meta mensal de vendas, tirando a mdia de vendas dos ltimos trs meses adicionados de 5%; Um procedimento pblico que atualize a meta mensal do representante (coluna REP_VL_METAMENSAL) utilizando a funo criada.
7. Desenvolva um pacote chamado pck_produto, com procedimentos e funes necessrios para atualizar:
Data e valor de ltima venda de cada produto; Data e valor de maior venda de cada produto.
Pgina 81 de 154
Treinamento
04 Collections
1. 2. 3. 4. 5.
Tabelas por ndice; Tabelas aninhadas; Arrays; Collection como fonte de dados (SELECT no Array); Bulk Binding
Pgina 82 de 154
Treinamento
Colees
As colees da PL/SQL permitem agrupar muitas ocorrncias de um objeto. A PL/SQL disponibiliza trs tipos de colees:
Se voc conhece as outras linguagens de programao, pense em uma coleo como algo semelhante a um array (Um vetor). Um array uma srie de elementos repetidos, todos do mesmo tipo. Voc vai aprender que alguns tipos de coleo da Oracle so mais parecidos com arrays tradicionais do que outros.
Uma tabela por ndice pode ser populada esparsamente; Voc no define um tamanho mximo para uma tabela por ndice.
Onde:
type_name o nome do tipo que voc est declarando (Ele ser usado para definir o tipo das variveis); data_type o tipo de dado da coleo, ou seja, cada elemento da tabela armazena um valor desse tipo;
NOT NULL probe que uma entrada da tabela contenha valor nulo.
Abaixo, segue um exemplo de definio de tipos para coleo e declarao de variveis desses tipos: DECLARE -- Definio de tipos
Elaborado por Josinei Barbosa da Silva
Pgina 83 de 154
Treinamento
TYPE TArrayNumerico IS TABLE OF NUMBER INDEX BY BINARY_INTEGER; TYPE TRepresentante IS TABLE OF representante%ROWTYPE INDEX BY BINARY_INTEGER; -- Declarao de variveis cOrdemRepresentante TArrayNumerico; cRepresentante TRepresentante; BEGIN NULL; END; / No exemplo acima, a varivel cOrdemRepresentante como um array de uma nica coluna do tipo number e a varivel cRepresentante como uma matriz de duas dimenses, ou seja, um array contendo todas as colunas da tabela representante.
index o valor de inteiro que indica o ndice da entrada e pode ser qualquer nmero entre 1 e 2.147.483.647, no precisando ser consecutivos, ou seja, se voc precisasse colocar apenas duas entradas em uma tabela, poderia usar os ndices 1 e 2 ou poderia usar 1 e 2.147.483.647, ocupando em ambas situaes, duas entradas apenas (A PL/SQL no reserva espao para as entradas que no so usadas);
No exemplo abaixo, definimos um tipo de tabela por ndice para conter o nome dos clientes. A partir desse tipo, declaramos uma varivel que conter o nome de cada um dos clientes: DECLARE -- declarao de cursores CURSOR cs_cliente IS SELECT c.* FROM cliente c; -- declarao de tipos TYPE TNomeCliente IS TABLE OF VARCHAR2(1024) INDEX BY BINARY_INTEGER; -- declarao de variveis cCli TNomeCliente; vCount BINARY_INTEGER := 0; BEGIN -- Percorrer todos os clientes do cadastro
Elaborado por Josinei Barbosa da Silva
Pgina 84 de 154
Treinamento
FOR csc IN cs_cliente LOOP -- Incrementa contador vCount := vCount + 1; -- Atribuio de valores cCli(vCount) := csc.cli_st_nome; END LOOP; END; / No exemplo acima, utilizamos um contador para gerar o ndice, mas poderamos utilizar o prprio cdigo do cliente como ndice, uma vez que ele numrico e o ndice no precisa ser utilizado na seqncia, como no exemplo abaixo: DECLARE -- declarao de cursores CURSOR cs_cliente IS SELECT c.* FROM cliente c; -- declarao de tipos TYPE TNomeCliente IS TABLE OF VARCHAR2(1024) INDEX BY BINARY_INTEGER; -- declarao de variveis cCli TNomeCliente; BEGIN -- Percorrer todos os clientes do cadastro FOR csc IN cs_cliente LOOP -- Atribuio de valores cCli(csc.cli_in_codigo) := csc.cli_st_nome; END LOOP; END; / Referenciando valores em uma tabela por ndice Para referenciar uma entrada especfica em uma tabela por ndice, voc especifica um valor de ndice, usando a mesma sintaxe do tipo array que usou ao inserir os dados. Por exemplo, para avaliar se o cliente da entrada 2 novo, basta escrever uma declarao IF como esta: IF cCli(2).Novo THEN ... ... END IF; ou IF cCli(2).Novo = TRUE THEN ... ... END IF;
(Referencie um valor do tipo BOOLEAN como preferir!)
Como vimos anteriormente, as tabelas por ndice so populadas esparsamente e por isso pode haver
Elaborado por Josinei Barbosa da Silva
Pgina 85 de 154
Treinamento
valores de ndices para os quais no h entrada. Se tentar referenciar um deles, voc tem um exceo NO_DATA_FOUND e, conseqentemente, poder tratar essa exceo. Caso no queira utilizar a rea de EXCEPTION para tratar essa exceo, voc pode utilizar o mtodo EXISTS da coleo, como mostrado abaixo: IF cCli.EXISTS(15) THEN ... ... END IF; Um mtodo uma funo ou um procedimento que anexado a um objeto. Neste caso, a tabela cCli o objeto e a funo EXISTS o mtodo. Alterando entradas de tabela Voc atualiza uma tabela PL/SQL de modo semelhante ao que voc faz para inserir. Se voc j inseriu um nmero de linha 10 na tabela cCli (usada no nosso exemplo), ento voc pode atualizar aquela mesma linha com uma declarao como esta: cCli(10).Nome := 'Grupo Carrefour'; Essa declarao atualiza o campo Nome do registro na entrada da tabela de nmero 10, com o texto 'Grupo Carrefour'. Excluindo entradas de tabela Voc pode excluir entradas de uma tabela invocando o mtodo DELETE, que pode excluir uma entrada, um intervalo de entradas ou todas as entradas, da tabela. A sintaxe para o mtodo DELETE a seguinte: table_name.DELETE[(primeira_entrada[,ultima_entrada])]; onde:
primeira_entrada o ndice da entrada que voc quer excluir ou o ndice da primeira entrada de um intervalo de entradas que voc quer excluir;
IMPORTANTE: A invocao do mtodo DELETE sem a passagem de nenhuma argumento, faz com que todos os dados sejam excludos da tabela. Ento:
Para limpar toda tabela cCli, basta executar a declarao: cCli.DELETE; Para apagar apenas a entrada 8 da tabela cCli, basta executar a declarao: cCli.DELETE(8); E para apagar as entradas de 6 a 10 da tabela cCli, execute a declarao: cCli.DELETE(6,10);
Pgina 86 de 154
Treinamento
Tabelas aninhadas
As tabelas aninhadas surgiram no Oracle 8. No banco de dados, uma tabela aninhada uma coleo no ordenada de linhas. Pense em um sistema de notas fiscais, onde cada nota contenha um nmero de itens. Tradicionalmente, os criadores de banco de dados o implementariam usando duas tabelas de banco de dados, uma para as notas e uma segunda para os itens. Cada registro de item conteria um vnculo de chave estrangeira com sua nota me. A partir do Oracle 8, possvel que cada nota tenha sua prpria tabela de item e que aquela tabela seja armazenada como uma coluna. A PL/SQL suporta as tabelas aninhadas porque o banco de dados Oracle as suporta. As tabelas aninhadas so declaradas e usadas de forma semelhante s tabelas por ndice, mas existem algumas diferenas que voc deve conhecer:
Quando voc recupera uma tabela aninhada do banco de dados, as entradas so indexadas consecutivamente. Mesmo ao construir uma tabela aninhada dentro da PL/SQL, voc no pode pular arbitrariamente os valores de ndice como faria com as tabelas por ndice;
As tabelas aninhadas no suportam os datatypes especficos da PL/SQL; Voc precisa usar um mtodo construtor para inicializar uma tabela aninhada;
O intervalo de ndices das tabelas aninhadas de -2.147.483.647 at 2.147.483.647 (Os ndices das tabelas por ndice no podem ser 0 ou nmeros negativos).
IMPORTANTE: Quando as tabelas aninhadas esto no banco de dados, as linhas no tm nenhuma ordem determinada associada a elas. Quando voc l uma tabela aninhada na PL/SQL, cada linha recebe um ndice. Assim, de certa forma, naquele ponto h uma certa ordem envolvida. Entretanto, a ordem no preservada. Se voc selecionar a mesma tabela aninhada duas vezes, voc pode ter uma ordem de linha diferente a cada vez.
CUIDADO: At a verso 8i do Oracle Database, tabela aninhada s estava disponvel na distribuio Enterprise do Oracle Database, por isso, certifique-se que seu cliente possui esse recurso antes de utilizlo.
Se as tabelas aninhadas fossem criadas primeiro, a Oracle nunca teria desenvolvido o tipo por ndice. Entretanto, ambas esto disponveis e voc deve selecionar entre elas. Se voc precisar de um array de um datatype especfico da PL/SQL, tal como BOOLEAN, NATURAL ou INTEGER, ento sua nica escolha uma tabela por ndice. A outra coisa que deve ser considerada que as tabelas aninhadas requerem que os seus ndices sejam consecutivos, tal como 1,2,3 e assim por diante.
type_name o nome do tipo que voc est declarando; data_type o datatype da coleo; NOT NULL probe que uma entrada de tabela seja nula.
Pgina 87 de 154
Treinamento
IMPORTANTE: O datatype de uma coleo NO pode ser BOOLEAN, NCHAR, NCLOB, NVARCHAR2, REF CURSOR, TABLE e VARRAY; Como pode ser observado, a declarao de um tipo de uma tabela aninhada semelhante quela usada para uma tabela por ndice, mas a clusula INDEX BY no usada. A presena de uma clusula INDEX BY que indica para o Oracle que voc est utilizando uma tabela por ndice. Depois de declarar um tipo de tabela aninhada, voc pode usar aquele tipo para declarar as variveis de tabela.
Inicializando uma tabela aninhada Quando voc declara uma varivel para uma tabela aninhada, voc tem uma varivel que no contm nada. Ela considerada nula. Para torn-la uma tabela, voc precisa chamar uma funo construtora para criar aquela tabela e, depois, armazenar o resultado daquela funo na varivel que voc est usando. Por exemplo, digamos que voc tenha usado as seguintes declaraes para criar uma tabela aninhada chamada cRep: TYPE TRepresentante IS TABLE OF representante%ROWTYPE; cRep TRepresentante; Para usar realmente cRep como uma tabela aninhada, voc precisa chamar uma funo construtora para inicializ-la. A funo construtura sempre toma seu nome do nome do tipo usado para declarar a tabela; portanto, nesse caso, a funo construtora se chamaria Trepresentante. Esse conceito vem do mundo orientado a objetos, onde um construtor a funo que realmente aloca memria para um objeto. Voc pode chamar a funo construtora sem passar nenhum argumento e criar uma tabela vazia, como no exemplo seguinte: cRep := TRepresentante(); Voc tambm pode chamar a funo construtora e passar valores para uma ou mais entradas. Entretanto, os valores listados no construtor devem ser do mesmo tipo da tabela. Isso um pouco difcil de fazer com os registros. O exemplo seguinte mostra como declarar cRep como uma tabela de registros de representante e depois a inicializa com dois representantes: DECLARE -- Definio de tipos TYPE TRepresentante IS TABLE OF representante%ROWTYPE; -- Declarao de variveis cRep TRepresentante; rRep1 representante%ROWTYPE; rRep2 representante%ROWTYPE; BEGIN -- Atribuindo dados do primeiro representante rRep1.rep_in_codigo := 10; rRep1.rep_st_nome := 'Seu Madruga'; -- Atribuindo dados do segundo representante rRep2.rep_in_codigo := 20; rRep2.rep_st_nome := 'Chaves';
Pgina 88 de 154
Treinamento
-- Inicializando a coleo de representante com duas entradas: rRep1 e rRep2 cRep := TRepresentante(rRep1, rRep2); END; / O segredo nesse exemplo que os argumentos para o construtor so rRep1 e rRep2. Ambos so registros do tipo representante%ROWTYPE e coincidem com o tipo de elemento da tabela. Obviamente um pouco trabalhoso definir as coisas dessa forma. Para adicionar entradas a uma tabela alm daquelas que voc criou com o construtor, voc precisa estender a tabela como discutimos na seo seguinte. Estendendo uma tabela aninhada Para estender uma tabela aninhada de modo a adicionar mais entradas a ela, use o mtodo extend. O mtodo extend permite que voc adicione uma ou vrias entradas. Ele tambm permite que voc clone uma entrada existente uma ou mais vezes. A sintaxe do mtodo extend a seguinte: colecao.EXTEND[(quantidade_entradas[,entrada_clone])]; onde:
colecao o nome da tabela aninhada; quantidade_entradas o nmero de entradas novas que voc quer adicionar; entrada_clone uma varivel ou constante que indica qual entrada voc quer clonar.
A seguir temos um exemplo que utiliza o mtodo extend para estender a coleo de representantes: DECLARE -- Declarao de cursores CURSOR cs_representante IS SELECT r.* FROM representante r; -- Definio de tipos TYPE TRepresentante IS TABLE OF representante%ROWTYPE; -- Declarao de variveis cRep TRepresentante; vRepCount PLS_INTEGER :=0; BEGIN -- Inicializa a coleo criando uma entrada vazia cRep := TRepresentante(); -- Adiciona uma entrada na coleo para cada representante do cadastro FOR csr IN cs_representante LOOP vRepCount := vRepCount + 1; cRep.EXTEND; cRep(vRepCount).rep_in_codigo := csr.rep_in_codigo; cRep(vRepCount).rep_st_nome := csr.rep_st_nome; cRep(vRepCount).rep_vl_maiorvenda := csr.rep_vl_maiorvenda; END LOOP;
Pgina 89 de 154
Treinamento
-- Clona a primeira entrada 5 vezes cRep.EXTEND(5,1); -- Exibe todos os registros da coleo FOR i IN 1..(vRepCount + 5) LOOP dbms_output.put_line('Representante: '||cRep(i).rep_in_codigo ||chr(13)|| 'Nome.........: '||cRep(i).rep_st_nome ||chr(13)|| 'Maior Venda..: '||cRep(i).rep_vl_maiorvenda||chr(13) ); END LOOP; END; / Removendo entradas de uma tabela aninhada Voc pode remover entradas de uma tabela aninhada usando o mtodo delete, assim como faz com as tabelas por ndice. O exemplo abaixo exclui a entrada 10 da coleo cRep: cRep.DELETE(10); Voc pode usar as entradas depois de exclu-las. As outras entradas da tabela no so renumeradas. Outro mtodo para remover as linhas de uma tabela aninhada invocar o mtodo trim. O mtodo trim remove um nmero especificado de entradas do final da tabela. Sintaxe: colecao.TRIM[(quantidade_entradas)]; Onde:
colecao o nome da tabela aninhada; quantidade_entradas o nmero de entradas a serem removidas do final. O padro 1.
O mtodo trim se aplica apenas s tabelas aninhadas e aos arrays de tamanho variado. Ele no pode ser aplicado s tabelas por ndice. Para exemplificar a remoo de entradas de uma coleo, vamos alterar o exemplo anterior para, aps exibir as entradas, remover as cinco que foram clonadas usando o mtodo trim e apagar a entrada 1 usando o mtodo delete: DECLARE -- Declarao de cursores CURSOR cs_representante IS SELECT r.* FROM representante r; -- Definio de tipos TYPE TRepresentante IS TABLE OF representante%ROWTYPE; -- Declarao de variveis cRep TRepresentante; vRepCount PLS_INTEGER :=0; BEGIN -- Inicializa a coleo criando uma entrada vazia cRep := TRepresentante(); -- Adiciona uma entrada na coleo para cada representante do cadastro FOR csr IN cs_representante LOOP
Pgina 90 de 154
Treinamento
vRepCount := vRepCount + 1; cRep.EXTEND; cRep(vRepCount).rep_in_codigo := csr.rep_in_codigo; cRep(vRepCount).rep_st_nome := csr.rep_st_nome; cRep(vRepCount).rep_vl_maiorvenda := csr.rep_vl_maiorvenda; END LOOP; -- Clona a primeira entrada 5 vezes cRep.EXTEND(5,1); -- Exibe todos os registros da coleo FOR i IN 1..(vRepCount + 5) LOOP dbms_output.put_line('Representante: '||cRep(i).rep_in_codigo ||chr(13)|| 'Nome.........: '||cRep(i).rep_st_nome ||chr(13)|| 'Maior Venda..: '||cRep(i).rep_vl_maiorvenda||chr(13) ); END LOOP; dbms_output.put_line(cRep.COUNT||' entradas encontradas!'); -- Remove as 5 entradas clonadas cRep.TRIM(5); -- Exclui a primeira entrada cRep.DELETE(1); -- Exibe a nova contagem de entradas dbms_output.put_line('Agora restam '||cRep.COUNT||' entradas!'); -- Exibe as entradas que restaram FOR i IN 1..(vRepCount + 5) LOOP IF cRep.EXISTS(i) THEN dbms_output.put_line('Representante: '||cRep(i).rep_in_codigo ||chr(13)|| 'Nome.........: '||cRep(i).rep_st_nome ||chr(13)|| 'Maior Venda..: '||cRep(i).rep_vl_maiorvenda||chr(13) ); END IF; END LOOP; END; / Nesse exemplo, o mtodo trim usado para remover os cinco clones que criamos no exemplo anterior. Logo em seguida, o mtodo delete chamado para excluir tambm a primeira entrada. Depois do uso do mtodo delete, utilizamos o mtodo count para exibir o nmero de entradas. At a verso 8.1.5 a PL/SQL s reconhecia a excluso das entradas aps referenciar a contagem da coleo, ou seja, o uso do mtodo count servia para desviar de um bug do Oracle. Por fim, as entradas restante so exibidas.
Pgina 91 de 154
Treinamento
CUIDADO: At a verso 8i do Oracle Database, array de tamanho varivel s estava disponvel na distribuio Enterprise do Oracle Database, por isso, certifique-se que seu cliente possui esse recurso antes de utiliz-lo.
nome_tipo o nomo do tipo de array; size o nmero de elementos que voc quer que o array contenha; tipo_entrada o tipo de dados dos elementos do array; NOT NULL probe que as entradas do array sejam nulas.
Os varray precisam ser inicializados assim como as tabelas aninhadas. Antes de usar um VARRAY, voc precisa chamar seu construtor. Voc pode passar os valores para o construtor e aqueles valores so usados para criar elementos de array, ou voc pode invocar o construtor sem nenhum parmetro para criar um array vazio. No exemplo abaixo, inicializamos uma varivel VARRAY chamada vStrings, incluindo duas entradas: DECLARE -- Definio de tipos TYPE TStrings IS VARRAY(1000000) OF VARCHAR2(1024); -- Declarao de variveis vStrings TStrings; BEGIN -- Inicializa a coleo criando uma entrada vazia vStrings := TStrings('Linha 1','Linha 2'); -- Exibe todos os registros da coleo FOR i IN 1..2 LOOP dbms_output.put_line(vStrings(i)); END LOOP; END; / Neste exemplo, criamos um tipo VARRAY chamado TStrings e declaramos a varivel vStrings como sendo do tipo TStrings. Na seo de execuo do bloco, inicializamos vStrings com duas linhas, contendo na primeira entrada o texto 'Linha 1' e na segunda entrada, o texto 'Linha 2'. No final, temos um LOOP com dois ciclos que imprimem na tela as duas entradas.
Pgina 92 de 154
Treinamento
Para remover entradas de um VARRAY, basta utilizar os mtodos delete ou trim, como fizemos na manipulao de tabelas aninhadas. O exemplo abaixo mostra o contedo da tabela produto sendo lido em um VARRAY: DECLARE -- Declarao de cursores CURSOR cs_produto IS SELECT p.pro_in_codigo, p.pro_st_nome, p.pro_st_marca, p.pro_dt_ultimavenda FROM produto p; -- Definio de tipos TYPE TProduto IS VARRAY(100) OF cs_produto%ROWTYPE; -- Declarao de variveis cProd TProduto; vIndice PLS_INTEGER := 0; BEGIN -- Inicializar coleo cProd := TProduto(); -- Abrir cursor OPEN cs_produto; -- Para cada produto, atribuir cdigo, nome, marca e data de ultima venda LOOP -- Incrementar indice e estender coleo vIndice := vIndice + 1; cProd.EXTEND; -- Recuperar dados do cursor e atribuir para coleo FETCH cs_produto INTO cProd(vIndice); -- Se no encontrar mais nada no cursor, elimina a ltima entrada -- gerada na coleo, decrementa o ndice da colo e abandona o LOOP IF cs_produto%NOTFOUND THEN cProd.TRIM(1); vIndice := vIndice - 1; EXIT; END IF; END LOOP; -- Fechar cursor CLOSE cs_produto; -- Imprimir cada entrada da coleo na tela FOR i IN 1..vIndice LOOP dbms_output.put_line('Cd.Prod....: '||cProd(i).pro_in_codigo||chr(13)|| 'Nome........: '||cProd(i).pro_st_nome||chr(13)|| 'Marca.......: '||cProd(i).pro_st_marca||chr(13)|| 'ltima Venda: '||cProd(i).pro_dt_ultimavenda||chr(13) );
Pgina 93 de 154
Treinamento
END LOOP; END; / Neste exemplo, para cada produto retornado no cursor, utilizamos o mtodo extend para incluir uma entrada na coleo e, no final, quando o comando FETCH no encontra mais nenhum registro no cursor, utilizamos o mtodo trim para remover a ltima entrada da coleo que foi gerada pelo prprio comando FETCH. Observe que extend no pode ser usado para aumentar o array alm do tamanho mximo especificado de 100 entradas.
limit
first
last next
prior
delete
trim
Descrio O mtodo count retorna o nmero de entradas da tabela. Essa funo recebe o nmero da entrada como argumento e retorna o valor TRUE se a entrada existir. Caso contrrio, ela retorna FALSE. Esse mtodo retorna o nmero mximo de elementos que uma coleo pode conter. Somente arrays de tamanho varivel tm um limite superior. Quando usado com as tabelas aninhadas e com as tabelas por ndice, o mtodo limit retorna NULL. Esse mtodo retorna o valor de ndice mais baixo usado em uma coleo. Essa funo retorna o valor do ndice mais alto usado na tabela. O mtodo next retorna o valor do prximo ndice vlido que seja maior do que um valor que voc fornece. O mtodo prior retorna o valor do ndice vlido mais alto que precede o valor do ndice que voc fornecer. O mtodo delete permite excluir entrada de uma coleo. Voc pode excluir todas as entradas ou uma entrada especfica ou um intervalo de entradas. O mtodos delete um procedimento e no retorna um valor. O mtodo trim permite excluir entradas do final de uma coleo. Voc pode usar trim apenas em uma
IF vIndice > cli_array.limit THEN ... ELSE cli_array(vIndex) := 10; END IF;
vMenorIndiceValido := cCli.first;
vIndiceAnterior := cCli.Prior(vIndiceCorrente)
-- Excluindo todas as entradas cCli.delete; -- Excluindo apenas uma entrada cCli.delete(8); -- Excluindo um intervalo de entradas cCli.delete(6,10); -- cortando uma entrada cli_array.trim;
Pgina 94 de 154
Treinamento
Mtodo
extend
Descrio entrada ou em diversas entradas. O mtodo trim um procedimento e no retorna uma valor. Este mtodo s pode ser usado com arrays de tamanho variado e com as tabelas aninhadas. O mtodo extend permite adicionar entradas no final de uma coleo. Voc pode adicionar uma entrada ou vrias entradas, especificando o nmero a ser adicionado como parmetro. O mtodo extend tambm pode ser usada para clonar entradas existentes. Voc clona uma entrada passando o nmero da entrada como um segundo parmetro para extend. O mtodo extend um procedimento e no retorna um valor. Assim como trim, extend s pode ser usado com os arrays de tamanho varivel e com as tabelas aninhadas.
-- Adicionando uma entrada cli_array.extend; -- Adicionando trs entradas cli_array.extend(3); -- Adicionando uma nova entrada clone da 3 cli_array.extend(1,3);
Pgina 95 de 154
Treinamento
TCliente;
Depois que voc carregar os dados de sua coleo, voc pode executar uma declarao SELECT nela. Para que PL/SQL visualizar a coleo como uma tabela, necessrio moldar a coleo como sendo do seu tipo, atravs da funo CAST e depois, moldar resultado desse CAST como uma tabela. Veja o exemplo abaixo: -- Imprimir total de clientes novos SELECT COUNT(*) INTO vCount FROM TABLE(CAST(cCli AS TCliente)) Onde:
vCount uma varivel do tipo inteira previamente declarada; CAST a funo que informa ao PL/SQL que cCli deve ser visto como um tipo Tcliente; e TABLE faz com que a PL/SQL veja a varivel do tipo TCliente, como uma tabela.
CUIDADO: Quando usamos um tipo tabela aninhada armazenado para manipular colees, nos deparamos com um problema de inicializao da coleo. No basta inicializar a coleo usando o construtor do seu tipo, como vimos na seo de tabelas aninhadas. Alm disso, a cada entrada estendida, necessrio inicializar a entrada com valores nulos antes de manipul-la, usando uma varivel do mesmo tipo do elemento da coleo ou usando o prprio tipo para passar os valores nulos, como no exemplo abaixo: cCli(1):= ObjCliente(NULL,NULL,NULL); Nesse exemplo, a entrada 1 da coleo cliente inicializada com nulo para cada um dos seus trs campos, usando para isso o objeto ObjCliente, usado para definir o tipo de cada elemento do tipo TCliente, que por sua vez foi usado para declarar a varivel cCli.
Para entendermos melhor, vejamos o seguinte exemplo: 1. Criao do objeto armazenado: CREATE OR REPLACE TYPE ObjCliente AS OBJECT (Codigo INTEGER, Nome VARCHAR2(20), Novo CHAR(1) ) / 2. Criao do tipo armazenado, sendo cada elemento do tipo do objeto criado no passoa 1: CREATE OR REPLACE TYPE TCliente IS TABLE OF ObjCliente; / 3. Criao do bloco que recupera todos os clientes, atravs de um cursor, e inclui cada cliente retornado na coleo, alimentando o cdigo do cliente, nome do cliente e, com base na data de
Pgina 96 de 154
Treinamento
incluso, se o cliente novo ou no. Aps alimentar a coleo, imprimimos cada um dos clientes informando se um cliente novo ou antigo. Por fim, executamos uma declarao SELECT para imprimir a quantidade de clientes novos e outra para imprimir a quantidade de clientes antigos: DECLARE -- Declarao de cursores CURSOR cs_cliente IS SELECT c.* FROM cliente c; -- Declarao de variveis cCli TCliente; vIndice PLS_INTEGER := 0; vCount PLS_INTEGER := 0; BEGIN -- Inicializa a coleo criando uma entrada vazia cCli := TCliente(); -- Adiciona uma entrada na coleo para cada representante do cadastro FOR csc IN cs_cliente LOOP -- Estender a coleo em 1 entrada cCli.EXTEND; -- Definir valor do ndice vIndice := cCli.LAST; -- Inicializar a entrada estendida cCli(vIndice):= ObjCliente(NULL,NULL,NULL); -- Atualizar os dados da entrada cCli(vIndice).Codigo := csc.cli_in_codigo; cCli(vIndice).Nome := csc.cli_st_nome; IF csc.cli_dt_inclusao BETWEEN add_months(trunc(SYSDATE),-12) AND SYSDATE THEN cCli(vIndice).Novo := 'S'; ELSE cCli(vIndice).Novo := 'N'; END IF; END LOOP; -- Imprimir cdigo e nome de cada cliente informando se novo ou antigo FOR i IN 1..vIndice LOOP CASE cCli(i).Novo
WHEN 'S' THEN dbms_output.put_line('Cliente '||cCli(i).Nome||' ('||cCli(i).Codigo||') Novo!'); WHEN 'N' THEN dbms_output.put_line('Cliente '||cCli(i).Nome||' ('||cCli(i).Codigo||') Antigo!');
-- Quebrar linha dbms_output.put_line(''); -- Imprimir total de clientes novos SELECT COUNT(*) INTO vCount
Pgina 97 de 154
Treinamento
FROM TABLE(CAST(cCli AS TCliente)) cn WHERE cn.novo = 'S'; dbms_output.put_line('Total de Clientes Novos: '||vCount); -- Imprimir total de clientes antigos SELECT COUNT(*) INTO vCount FROM TABLE(CAST(cCli AS TCliente)) cn WHERE cn.novo = 'N'; dbms_output.put_line('Total de Clientes Antigos: '||vCount); END; / O uso desse recurso pode representar ganhos considerveis em aplicaes crticas, pois os dados so recuperados uma nica vez e pode ser utilizado em memria quantas vezes for necessrio, alm de possibilitar uma manipulao pontual de cada registro atravs dos mtodos de tabela, j estudados.
********/
/******** Declarao de variveis ********/ -- variveis de cursores cs_ClienteNovo TCursor; -- colees cCli TCliente; -- registros rCliente TRegistroCliente; -- esclares vIndice PLS_INTEGER := 0; BEGIN -- Inicializa a coleo criando uma entrada vazia cCli := TCliente();
Elaborado por Josinei Barbosa da Silva
Pgina 98 de 154
Treinamento
-- Adiciona uma entrada na coleo para cada representante do cadastro FOR csc IN cs_cliente LOOP -- Estender a coleo em 1 entrada cCli.EXTEND; -- Definir valor do ndice vIndice := cCli.LAST; -- Inicializar a entrada estendida cCli(vIndice):= ObjCliente(NULL,NULL,NULL); -- Atualizar os dados da entrada cCli(vIndice).Codigo := csc.cli_in_codigo; cCli(vIndice).Nome := csc.cli_st_nome; IF csc.cli_dt_inclusao BETWEEN add_months(trunc(SYSDATE),-12) AND SYSDATE THEN cCli(vIndice).Novo := 'S'; ELSE cCli(vIndice).Novo := 'N'; END IF; END LOOP; -- Abri o cursor com uma declarao SELECT na coleo cCli OPEN cs_ClienteNovo FOR SELECT Codigo,Nome,Novo FROM TABLE(CAST(cCli AS TCliente)) cn WHERE cn.novo = 'S'; -- Imprimir cdigo e nome de cada cliente novo LOOP -- Recupera registro do cursor aberto a partir da coleo FETCH cs_ClienteNovo INTO rCliente; -- Sai do loop quando no encontrar registro no cursor EXIT WHEN cs_ClienteNovo%NOTFOUND; dbms_output.put_line('Cliente '||rCliente.Nome|| ' ('||rCliente.Codigo||') Novo!' ); END LOOP; -- Fecha cursor CLOSE cs_ClienteNovo; END; / IMPORTANTE: Tipos de colees locais no so permitidos em instrues SQL, apenas tipos armazenados.
O bulk binding
O bulk binding da PL/SQL um recurso que surgiu na verso Oracle8i. O bulk binding permite codificar as declaraes SQL que operam em todas as entradas de uma coleo, sem ter de fazer o LOOP em toda a coleo usando o cdigo PL/SQL. Vrios dos exemplos dados at aqui, usaram um LOOP FOR de cursor para carregar os dados de uma tabela de banco de dados para uma tabela ou array da PL/SQL. A mudana da SQL (FETCH) para a PL/SQL (para adicionar os dados ao array) chamada de
Pgina 99 de 154
Treinamento
mudana de contexto e consome muita overhead. Voc pode usar o recurso de bulk binding para evitar grande parte daquela overhead. Duas novas palavras-chave suportam o binding: BULK COLLECT e FORALL., BULK COLLECT usada com as declaraes SELECT para colocar todos os dados em uma coleo. FORALL usada com as declaraes INSERT, UPDATE e DELETE para executar aquelas declaraes uma vez para cada elemento de uma coleo.
Na declarao SELECT: SELECT rep_in_codigo, rep_st_nome BULK COLLECT INTO cRep FROM representante;
No FETCH do cursor: OPEN cs_representante; FETCH cs_representante BULK COLLECT INTO cRep; CLOSE cs_representante;
Para exemplificar melhor, vamos reescrever o exemplo que classifica o cliente como novo ou antigo: DECLARE -- Declarao de cursores CURSOR cs_cliente IS SELECT ObjCliente( c.cli_in_codigo, c.cli_st_nome,
Elaborado por Josinei Barbosa da Silva
Treinamento
) FROM cliente c;
(CASE WHEN (c.cli_dt_inclusao BETWEEN add_months(trunc(SYSDATE),-12) AND SYSDATE) THEN 'S' ELSE 'N' END )
-- Declarao de variveis cCli TCliente; vCount PLS_INTEGER := 0; BEGIN -- Inicializa a coleo criando uma entrada vazia cCli := TCliente(); OPEN cs_cliente; FETCH cs_cliente BULK COLLECT INTO cCli; CLOSE cs_cliente; -- Imprimir cdigo e nome de cada cliente informando se novo ou antigo FOR i IN 1..cCli.COUNT LOOP CASE cCli(i).Novo WHEN 'S' THEN dbms_output.put_line('Cliente '||cCli(i).Nome||' ('||cCli(i).Codigo||') Novo!'); WHEN 'N' THEN dbms_output.put_line('Cliente '||cCli(i).Nome||' ('||cCli(i).Codigo||') Antigo!'); END CASE; END LOOP; -- Quebrar linha dbms_output.put_line(''); -- Imprimir total de clientes novos SELECT COUNT(*) INTO vCount FROM TABLE(CAST(cCli AS TCliente)) cn WHERE cn.novo = 'S'; dbms_output.put_line('Total de Clientes Novos: '||vCount); -- Imprimir total de clientes antigos SELECT COUNT(*) INTO vCount FROM TABLE(CAST(cCli AS TCliente)) cn WHERE cn.novo = 'N'; dbms_output.put_line('Total de Clientes Antigos: '||vCount); END; / Este bloco PL/SQL apresenta apenas duas alteraes em relao ao bloco que usamos para exemplificar o uso de declarao SELECT com colees:
Treinamento
1. A declarao SELECT do cursor cs_cliente, com o uso da instruo CASE, j retorna se o cliente novo ou no e as colunas retornadas na declarao so moldadas para o objeto ObjCliente (uma espcie de CAST); 2. O cdigo que executava um LOOP no cursor e atribua os dados de cada cliente para a coleo, foi substitudo por trs linhas que: a) Abre o cursor (OPEN cs_cliente); b) Alimenta a coleo com todos os dados do cursor (FETCH COLLECT INTO cCli); c) Fecha o cursor (CLOSE cs_cliente). Desta forma, ganhamos em performance e deixamos o cdigo mais enxuto. cs_cliente BULK
Usando FORALL
A palavra-chave FORALL permite que voc baseie uma declarao DML (Data Manipulation Language), ou seja, INSERT, UPDATE ou DELETE, no contedo de uma coleo. Quando FORALL usada, a declarao executada uma vez para cada entrada da coleo, mas apenas uma mudana de contexto feita da PL/SQL para a SQL. O desempenho resultante muito mais rpido do que aquilo que voc teria se codificasse um LOOP na PL/SQL. Para exemplificar o uso do FORALL, vamos alterar o exemplo do BULK COLLECT para passar o nome de todos os clientes para maisculas: DECLARE -- Declarao de cursores CURSOR cs_cliente IS SELECT c.cli_in_codigo, c.cli_st_nome FROM cliente c; -- Definio de tipo TYPE TClienteID IS TABLE OF cliente.cli_in_codigo%TYPE; TYPE TClienteNome IS TABLE OF cliente.cli_st_nome%TYPE; -- Declarao de variveis cCliID TClienteID; cCliNome TClienteNome; vCount PLS_INTEGER := 0; BEGIN -- Carregar colees com dados do cursor OPEN cs_cliente; FETCH cs_cliente BULK COLLECT INTO cCliID, cCliNome; CLOSE cs_cliente; -- Alterar todos dos nomes da tabela de cliente para maiscula FORALL x IN cCliID.FIRST..cCliID.LAST UPDATE cliente SET cli_st_nome = upper(cCliNome(x)) WHERE cli_in_codigo = cCliID(x); -- Capturar quantidade de linhas atualizadas vCount := SQL%ROWCOUNT; -- Informar quantidade de registros atualizados
Elaborado por Josinei Barbosa da Silva
Treinamento
dbms_output.put_line(vCount||' registro(s) atualizado(s).'); -- Efetivar alteraes no banco COMMIT; END; / Observe que neste exemplo, definimos duas colees cujos tipos esto definidos no prprio bloco, ao invs de estarem armazenados no banco de dados. Uma coleo para conter o cdigo do cliente e a outra para conter o nome. Isso necessrio porque na declarao UPDATE do recurso FORALL, no conseguimos referenciar o campo de um registro usado num bulk binding. Logo no conseguiramos utilizar o campo codigo do objeto ObjCliente usado para definir os elementos do tipo TCliente (como vnhamos fazendo at aqui). Se voc tentar referenciar um campo de registro ao utilizar FORALL, receber o seguinte erro de retorno: PLS-00436: restrio de implementao: no possvel fazer referncia a campos da tabela de registros BULK In-BIND
Causa Voc tentou usar a coleo antes de incializ-la com sua funo construtora. Voc tentou acessar o valor de uma entrada em uma coleo e aquela entrada no existe. Voc usou um subscript (o ndice) que excede o nmero de elementos atuais da coleo. Voc usou um subscript (o ndice) com um varray que era maior do que o mximo suportado pela declarao de tipo do array. Voc usou um subscript (o ndice) que no pode ser convertido para um inteiro.
Ao escrever cdigo que lida com colees, voc pode detectar essas excees ou gravar cdigo para evit-las. Voc pode evitar NO_DATA_FOUND, por exemplo, testando a validade de cada entrada com o mtodo exists antes de tentar acessar o valor da entrada. O seguinte trecho de cdigo mostra como isso feito: -- Se o elemento 10 existe IF cCli.EXISTS(10) THEN ... ... -- Se o elemento 10 no existe ELSE ... ... END IF; Voc pode evitar os erros de subscript com cdigo cuidadoso. Se voc est trabalhando com
Elaborado por Josinei Barbosa da Silva
Treinamento
VARRAY, saiba antes o nmero de elementos que voc declarou para aquele VARRAY conter. Se voc est trabalhando com uma tabela aninhada, e no tem mais certeza do tamanho, use o mtodo count para verificar o tamanho da tabela.
Treinamento
Exerccios Propostos
1. Adicione a coluna cli_ch_novo na tabela cliente, usando o seguinte comando:
ALTER TABLE cliente ADD (cli_ch_novo CHAR(1) DEFAULT 'S' NOT NULL CONSTRAINT ck_cli_ch_novo CHECK(cli_ch_novo IN('S','N')) );
Estando a tabela alterada, edite o pacote pck_cliente e crie um procedimento pblico, que:
Recupere os dados dos clientes atravs de um cursor; Monte uma coleo (ou quantas achar necessrio) com os dados do cliente e mais uma coluna que identifique se o cliente novo caso a data de incluso no tiver mais do que um ano ou antigo, se a data de incluso tive mais do que um ano; Atualize o cadastro de cada cliente da coleo, informando 'S' na coluna cli_ch_novo quando o cliente for novo e 'N', quando o cliente for antigo. Receba como argumento o cdigo do representante, a data inicial do perodo e a data
final;
Alimente uma coleo com o total de cada venda realizada pelo representante no perodo informado (tabela nota_fiscal_venda) calculando uma coluna extra com a comisso do representante para cada nota fiscal;
Totalize as comisses do representante e retorne esse total. A comisso deve ser 5% do valor total da nota, mais:
1% se o cliente for novo; 1% se a venda for vista ou 0,5% se a venda for em 3 vezes sem juros.
3. Adicione uma funo na package pck_produto que receba o cdigo do produto e retorne a quantidade de vezes que foi praticado um valor menor do que o preo de vendas cadastrado. Para isso, carregue todas as vendas do produto em uma coleo, registrando para cada elemento da coleo o valor unitrio praticado e o valor de vendas cadastrado. Execute uma declarao SELECT nessa coleo fazendo a contagem de vezes que o valor unitrio praticado maior do que o valor de venda cadastrado. Lembre-se que o valor unitrio praticado est na tabela item_nota_fiscal_venda e o valor de venda cadastrado est na tabela preco_produto, que contm os preos de venda e de compra.
Treinamento
PL/SQL:
SQL Dinmico Stored Procedure com transao autnoma Funo PIPELINED
UTL_FILE: Escrita e leitura de arquivos no servidor
Treinamento
SQL Dinmico
SQL Dinmico a SQL ou PL/SQL que gerada por um programa quando ele executado. Voc usa a SQL dinmica quando precisa escrever software genrico. Por exemplo, se voc desenvolver um gerador de relatrios no tem como saber com antecedncia quais so os relatrios que as pessoas criaro usando esse gerador. Neste caso, voc precisa tornar o seu cdigo flexvel e genrico o suficiente para permitir que os usurios executem qualquer consulta que queiram. O SQL dinmico um recurso para fornecer a flexibilidade no desenvolvimento de cdigo PL/SQ e SQLs genricos. Esse cdigo gerado pelo software no runtime e depois executado.
ATENO: SQL dinmico deve ser utilizado com responsabilidade, pois ele prejudicial para a performance do banco de dados. O SQL dinmico no compartilhado na memria do banco de dados.
Nos exemplos que vimos at aqui, trabalhamos com SQL esttico (tambm chamado de nativo), onde necessrio saber com antecedncia como deve ser seu SQL. Exemplo: Se quisermos que nossa package pck_cliente tenha uma funo que retorne a mdia geral de compra de clientes, permitindo que o usurio decida se quer a mdia de:
Teramos que criar uma cdigo como o que segue abaixo: (Na especificao da package) -- Funo que retorna a mdia de compra (geral, -- por ramo de atividade, cidade ou ramo + cidade) FUNCTION GetMediaCompraMensal (pRamo cliente.cli_st_ramoatividade%TYPE ,pCidade cliente.cli_st_cidade%TYPE) RETURN NUMBER; (No corpo da package) -- Funo que retorna a media de compra (geral, -- por ramo de atividade, cidade ou ramo + cidade) FUNCTION GetMediaCompraMensal (pRamo cliente.cli_st_ramoatividade%TYPE ,pCidade cliente.cli_st_cidade%TYPE) RETURN NUMBER IS vlResult cliente.cli_vl_mediacomprasmensal%TYPE := 0; BEGIN -- Se for por ramo de atividade e cidade IF (pRamo IS NOT NULL) AND (pCidade IS NOT NULL) THEN SELECT AVG(c.cli_vl_mediacomprasmensal) INTO vlResult FROM cliente c
Elaborado por Josinei Barbosa da Silva
Treinamento
WHERE c.cli_st_ramoatividade = pRamo AND c.cli_st_cidade = pCidade; -- Se for apenas por ramo de atividade ELSIF (pRamo IS NOT NULL) AND (pCidade IS NULL) THEN SELECT AVG(c.cli_vl_mediacomprasmensal) INTO vlResult FROM cliente c WHERE c.cli_st_ramoatividade = pRamo; -- Se for apenas por cidade ELSIF (pRamo IS NULL) AND (pCidade IS NOT NULL) THEN SELECT AVG(c.cli_vl_mediacomprasmensal) INTO vlResult FROM cliente c WHERE c.cli_st_cidade = pCidade; -- Se for geral ELSIF (pRamo IS NULL) AND (pCidade IS NULL) THEN SELECT AVG(c.cli_vl_mediacomprasmensal) INTO vlResult FROM cliente c; END IF; RETURN(vlResult); EXCEPTION WHEN no_data_found THEN RETURN(0); WHEN OTHERS THEN raise_application_error (-20100, 'No foi possivel recuperar mdia de compra para: '|| chr(13)||'Ramo: '||pRamo|| chr(13)||'Cidade: '||pCidade|| chr(13)|| chr(13)||'Erro: '||sqlerrm); END GetMediaCompraMensal; A soluo acima atende a necessidade que descrevemos, conforme podemos conferir com a instruo SQL abaixo: SELECT c.cli_in_codigo ,c.cli_st_nome ,c.cli_vl_mediacomprasmensal ,c.cli_st_ramoatividade ,pck_cliente.GetMediaCompraMensal (c.cli_st_ramoatividade ,NULL) ,c.cli_st_cidade ,pck_cliente.GetMediaCompraMensal (NULL , c.cli_st_cidade) ,pck_cliente.GetMediaCompraMensal (c.cli_st_ramoatividade
Elaborado por Josinei Barbosa da Silva
"Cod. Cliente" "Nome Cliente" "Media Mensal Compra" "Ramo Atividade" "Media Ramo" "Cidade" "Media Cidade"
Treinamento
"Media Ramo/Cidade"
Mas observe na funo que, nas quatro possibilidades de retorno da funo, repetimos a mesma SQL trocando apenas suas restries (WHERE AND). A mesma funo poderia ser reescrita utilizando SQL dinmico com um SQL genrico, como vemos abaixo: FUNCTION GetMediaCompraMensal (pRamo cliente.cli_st_ramoatividade%TYPE ,pCidade cliente.cli_st_cidade%TYPE) RETURN NUMBER IS vlResult cliente.cli_vl_mediacomprasmensal%TYPE := 0; vlSQL LONG; vlWhereOrAnd VARCHAR2(6) := 'WHERE '; BEGIN vlSQL := 'SELECT AVG(c.cli_vl_mediacomprasmensal) '|| 'FROM cliente c ';
-- Filtrar Ramo IF (pRamo IS NOT NULL) THEN vlSQL := vlSQL || 'WHERE c.cli_st_ramoatividade = '''||pRamo||''' '; vlWhereOrAnd := 'AND '; END IF; -- Filtrar Cidade IF (pCidade IS NOT NULL) THEN vlSQL := vlSQL || vlWhereOrAnd||'c.cli_st_cidade = '''||pCidade||''' '; END IF; EXECUTE IMMEDIATE vlSQL INTO vlResult; RETURN(vlResult); EXCEPTION WHEN no_data_found THEN RETURN(0); WHEN OTHERS THEN raise_application_error (-20100, 'No foi possivel recuperar mdia de compra para: '|| chr(13)||'Ramo: '||pRamo|| chr(13)||'Cidade: '||pCidade|| chr(13)|| chr(13)||'Erro: '||sqlerrm); END GetMediaCompraMensal; No exemplo acima, ao invs de concatenarmos os parmetros pRamo e pCidade na SQL, poderamos ter usado a clusula USING do comando EXECUTE IMMEDIATE para informar as variveis de
Treinamento
ligao, mas para isso, seria preciso garantir que todas as variveis de ligao (bind variables) informadas estariam presentes na SQL executada (em nosso exemplo, nem sempre isso aconteceria). IMPORTANTE: O 'EXECUTE IMMEDIATE' foi introduzido na verso 8i do Oracle Database. Nas verses anteriores, a nica maneira de gerar SQL Dinmico era atravs do pacote DBMS_SQL e sua utilizao no era das mais fceis.
ATENO: Use o PRAGMA AUTONOMOUS_RANSACTION com responsabilidade, pois ele pode causar comportamentos indesejados no seu sistema e dificultar a manuteno.
Vejamos um exemplo simples de como utilizar esse recurso: Crie as seguintes tabelas:
Elaborado por Josinei Barbosa da Silva
Treinamento
CREATE TABLE atualizacao_resumo_vendas (arv_dt_atualizacao DATE NOT NULL ,arv_st_usuario VARCHAR2(30) NOT NULL ,arv_st_conclusao VARCHAR2(10) DEFAULT 'EXECUTANDO' NOT NULL CONSTRAINT ck_arv_st_conclusao CHECK(arv_st_conclusao IN('EXECUTANDO','OK','FALHOU')) ) / CREATE TABLE temp_atualizacao_resumo (tar_dt_atualizacao DATE NOT NULL ,tar_st_usuario VARCHAR2(30) NOT NULL ,tar_st_conclusao VARCHAR2(10) DEFAULT 'EXECUTANDO' NOT NULL CONSTRAINT ck_tar_st_conclusao CHECK(tar_st_conclusao IN('EXECUTANDO','OK','FALHOU')) );
Implemente a seguinte package: CREATE OR REPLACE PACKAGE pck_vendas IS -- Author : JOSINEIS -- Created : 15/1/2011 14:53:47 -- Purpose : pacote para manipulao de vendas /* Procedure para atualizar o resumo mensal de vendas */ PROCEDURE AtualizaResumoMensal; END pck_vendas; / CREATE OR REPLACE PACKAGE BODY pck_vendas is /* Registrar log de atualizao do resumo mensal de vendas */ PROCEDURE RegistrarAtualizacaoResumo (pSituacaoConclusao atualizacao_resumo_vendas.arv_st_conclusao%TYPE) IS PRAGMA AUTONOMOUS_TRANSACTION; BEGIN BEGIN INSERT INTO atualizacao_resumo_vendas(arv_dt_atualizacao,arv_st_usuario,arv_st_conclusao) VALUES(SYSDATE,USER,pSituacaoConclusao); COMMIT; EXCEPTION WHEN dup_val_on_index THEN NULL; WHEN OTHERS THEN raise_application_error (-20100, 'No foi possvel registrar a atualizao do resumo mensal de vendas'|| chr(13)||'Erro: '||SQLERRM); END;
Treinamento
END RegistrarAtualizacaoResumo; /* Procedure para atualizar o resumo mensal de vendas */ PROCEDURE AtualizaResumoMensal IS -- Declarao de cursores CURSOR cs_vendas IS SELECT to_char(n.nfv_dt_emissao,'yyyy') ano, to_char(n.nfv_dt_emissao,'mm') mes, i.pro_in_codigo, n.cli_in_codigo, n.rep_in_codigo, SUM(i.infv_vl_total) vl_total, COUNT(i.infv_in_numero) numerovendas, SUM(i.infv_qt_faturada) qt_faturada FROM nota_fiscal_venda n, item_nota_fiscal_venda i WHERE n.nfv_in_numero = i.nfv_in_numero GROUP BY to_char(n.nfv_dt_emissao,'yyyy'), to_char(n.nfv_dt_emissao,'mm'), i.pro_in_codigo, n.cli_in_codigo, n.rep_in_codigo; BEGIN RegistrarAtualizacaoResumo(pSituacaoConclusao => 'EXECUTANDO'); -- Inicia Loop nos registros de venda FOR csv IN cs_vendas LOOP BEGIN INSERT INTO resumo_mensal_venda(rmv_in_ano,rmv_in_mes,pro_in_codigo,cli_in_codigo,rep_in_cod igo,rmv_vl_total,rmv_in_numerovendas,rmv_qt_vendida) VALUES(csv.ano,csv.mes,csv.pro_in_codigo,csv.cli_in_codigo,csv.rep_in_ codigo,csv.vl_total,csv.numerovendas,csv.qt_faturada); EXCEPTION WHEN dup_val_on_index THEN UPDATE resumo_mensal_venda r SET r.rmv_vl_total = csv.vl_total, r.rmv_in_numerovendas = csv.numerovendas, r.rmv_qt_vendida = csv.qt_faturada WHERE r.rmv_in_ano = csv.ano AND r.rmv_in_mes = csv.mes AND r.pro_in_codigo = csv.pro_in_codigo AND r.cli_in_codigo = csv.cli_in_codigo AND r.rep_in_codigo = csv.rep_in_codigo; WHEN OTHERS THEN RegistrarAtualizacaoResumo(pSituacaoConclusao => 'FALHOU'); ROLLBACK; raise_application_error (-20100,'No foi possvel atualizar o seguinte resumo mensal de vandas:'|| chr(13)||'Ano..........: '||to_char(csv.ano) || chr(13)||'Ms..........: '||to_char(csv.mes) || chr(13)||'Produto......: '||to_char(csv.pro_in_codigo)|| chr(13)||'Cliente......: '||to_char(csv.cli_in_codigo)||
Elaborado por Josinei Barbosa da Silva
Treinamento
chr(13)||'Representante: '||to_char(csv.rep_in_codigo)|| chr(13)|| chr(13)||'Erro: '||SQLERRM); END; END LOOP; RegistrarAtualizacaoResumo(pSituacaoConclusao => 'OK'); END AtualizaResumoMensal; END pck_vendas; / Abra duas janelas SQL e: 1. Na primeira execute o seguinte bloco (NO EXECUTE COMMIT): BEGIN pck_vendas.AtualizaResumoMensal; INSERT INTO temp_atualizacao_resumo (tar_dt_atualizacao ,tar_st_usuario ,tar_st_conclusao) VALUES (SYSDATE ,USER ,'OK'); END; / 2. Em seguida, execute as seguintes consultas na mesma janela do bloco annimo: SELECT * FROM atualizacao_resumo_vendas / SELECT * FROM temp_atualizacao_resumo / Todos os INSERTs realizados na operao sero vistos, pois como estamos na mesma transao do bloco, tambm visualizamos o que ainda no sofreu COMMIT, ou seja:
Na primeira consulta: Um registro com a situao 'EXECUTANDO' e outro com a situao 'OK'. Esses registros foram includos pela procedure pck_vendas.RegistrarAtualizacaoResumo, que possui transao autnoma; Na segunda consulta: Um registro com a situao 'OK', includo pelo INSERT do nosso bloco annimo e que ainda no sofreu COMMIT; A primeira consulta exibe o mesmo resultado da outra janela SQL (registros includos pela procedure com transao autnoma); A segunda consulta no exibe o registro includo no bloco annimo (ainda sem COMMIT);
Treinamento
5. Na segunda janela de SQL, repita as duas consultas e veja que agora a segunda consulta mostra o registro includo pelo bloco annimo (e finalmente submetido ao COMMIT); Como pode ser observado, desde a execuo do bloco annimo, os registros da tabela ATUALIZACAO_RESUMO_VENDAS, includos e submetidos ao COMMIT pela procedure pck_vendas.RegistrarAtualizacaoResumo (que possui transao autnoma), eram visveis nas duas janelas de SQL. Em contrapartida, os registros da tabela TEMP_ATUALIZACAO_RESUMO, cujo INSERT acontecia no bloco annimo, s passaram a ser visveis na outra janela de SQL aps o COMMIT; IMPORTANTE: Somente a partir da verso 9i do Oracle Database que stored proceures com transao autnoma ganharam suporte para transaes entre bancos de dados distribudos.
Observe que nos meses de janeiro e novembro os valores so zerados, provavelmente porque no foram realizadas vendas. Uma soluo possvel criar uma tabela na base de dados com apenas um
Treinamento
campo, contendo todos os perodos (ms/ano) para relacionar com a tabela que contm o total de vendas. Suponhamos que tivssemos uma tabela chamada perodo_resumo que contivesse todos os perodos de que precisamos, como abaixo:
per_in_mes 01 02 03 04 05 06 07 08 09 10 11 12 per_in_ano 2006 2006 2006 2006 2006 2006 2006 2006 2006 2006 2006 2006
Considerando que o total de vendas por perodo ser obtido pela tabela resumo_mensal_vendas, teramos que relacionar essas duas tabelas da seguinte maneira para obtermos o resultado que desejamos. SELECT p.per_in_mes, p.per_in_ano, SUM(r.rmv_vl_total) rmv_vl_total FROM periodo_resumo p, resumo_mensal_venda r WHERE p.per_in_mes BETWEEN 1 AND 12 AND p.per_in_ano = 2006 AND p.per_in_mes = r.rmv_in_mes(+) AND p.per_in_ano = r.rmv_in_ano(+) GROUP BY p.per_in_mes, p.per_in_ano ORDER BY p.per_in_mes, p.per_in_ano; O resultado esperado alcanado utilizando somente instrues SQL, mas precisamos manter dados persistentes de perodo s para isso. O Oracle um banco de dados Objeto-Relacional e, sendo assim, possui extenses para suporte de classes e objetos sendo esse o recurso necessrio para a criao de uma PL/SQL table function, ou seja, uma funo em PL/SQL que retorne um conjunto de dados complexos que possa ser tratado como uma tabela substituindo assim dados por um objeto no persistente que seja acessvel na linguagem SQL. Com esse recurso, podemos montar a tabela de perodos apenas quando precisarmos utiliz-la, no precisando armazenar esses dados.
Treinamento
2. Criar uma coleo onde cada elemento do tipo do objeto criado no primeiro passo; 3. Criar uma funo do tipo PIPELINED que retorna a coleo criada no segundo passo. Vamos usar o nosso exemplo como perodos para exemplificar esse processo: 1. Criao do objeto armazenado no banco para possibilitar registro de ms e ano: CREATE OR REPLACE TYPE ObjPeriodo AS OBJECT ( mes INTEGER, ano INTEGER ); / 2. Criao da coleo de perodos (ms/ano): CREATE OR REPLACE TYPE TPeriodo AS TABLE OF ObjPeriodo; / 3. Criao da funo PIPELINED que retorna os perodos desejados: CREATE OR REPLACE PACKAGE pck_util IS --Declarao da sua funo FUNCTION fnPeriodo(pDataInicial DATE, pDataFinal DATE) RETURN TPeriodo PARALLEL_ENABLE PIPELINED; END pck_util; / CREATE OR REPLACE PACKAGE BODY pck_util IS -- Funo que retorna coleo de perodo FUNCTION fnPeriodo(pDataInicial DATE, pDataFinal DATE) RETURN TPeriodo PARALLEL_ENABLE PIPELINED IS -- Declarao de variveis vQtdeMeses INTEGER := 0; vDataInicial DATE := pDataInicial; vDataFinal DATE := pDataFinal; vDataAtual DATE := add_months(pDataInicial,-1); vResult ObjPeriodo := ObjPeriodo(NULL,NULL); BEGIN -- Acha a quantidade de meses entre as duas datas, -- incluindo 1 para considerar o ltimo ms vQtdeMeses := months_between(vDataFinal,vDataInicial); -- Inicia uma loop que vai do ms/ano inicial at o ms/ano final FOR i IN 1..vQtdeMeses LOOP -- Incrementa um ms na data atual vDataAtual := add_months(vDataAtual,1); -- Defini os valores do registro atual vResult.Mes := to_number(to_char(vDataAtual,'MM')); vResult.Ano := to_number(to_char(vDataAtual,'YYYY')); -- Adiciona registro na coleo PIPE ROW (vResult); END LOOP; RETURN; END fnPeriodo;
Elaborado por Josinei Barbosa da Silva
Treinamento
END pck_util; / Neste exemplo, primeiramente, foi criado um tipo de objeto chamado ObjPeriodo que contm dois atributos: ms e ano. Depois, criamos uma coleo chamada TPeriodo, que conter linhas do tipo ObjPeriodo. Aps criar o objeto e a coleo, foi criado um pacote chamado pck_util, que contm a funo fnPeriodo, que como podemos observar, retorna um objeto TPeriodo e recebe como parmetros uma data do perodo inicial e uma data do perodo final. A clusula PIPELINED da funo o que permite a utilizao do comando PIPE ROW que o responsvel por incluir o registro na coleo que ser retornada. PARALLEL_ENABLE permite que a funo seja executada em sesses filhas de operaes paralelas, portanto, opcional mas necessria se voc utiliza PARALLEL QUERY.
Treinamento
Mas como podemos utilizar esse recurso para resolver o nosso problema inicial, onde precisvamos recuperar o valor total de venda para cada ms no perodo de 01/2006 a 12/2006? A nossa declarao SELECT que dependia da tabela periodo_resumo, no precisa mais, pois podemos reescrev-la da seguinte forma: SELECT p.mes, p.ano, SUM(nvl(r.rmv_vl_total,0)) rmv_vl_total FROM TABLE(pck_util.fnPeriodo(to_date('01/01/2006','dd/mm/yyyy'), to_date('01/12/2006','dd/mm/yyyy') ) ) p, resumo_mensal_venda r WHERE p.mes BETWEEN 1 AND 12 AND p.ano = 2006 AND p.mes = r.rmv_in_mes(+) AND p.ano = r.rmv_in_ano(+) GROUP BY p.mes, p.ano ORDER BY p.mes, p.ano; Ou se preferir: SELECT p.mes, p.ano, (SELECT SUM(nvl(r.rmv_vl_total,0)) FROM resumo_mensal_venda r WHERE p.mes = r.rmv_in_mes AND p.ano = r.rmv_in_ano ) rmv_vl_total FROM TABLE(pck_util.fnPeriodo(to_date('01/01/2006','dd/mm/yyyy'), to_date('01/12/2006','dd/mm/yyyy') ) ) p
Voc deve ter privilgios de executar o pacote; Seu administrador de banco de dados deve definir um parmetro de inicializao de banco de dados chamado UTL_FILE_DIR ou criar um DIRECTORY no banco de dados;
Treinamento
Usando UTL_FILE, o processo para leitura ou gravao de um arquivo o seguinte: 1. Declarar uma varivel de handle de arquivo para usar na identificao do arquivo quando voc fizer chamadas para as diversas rotinas do UTL_FILE. 2. Declarar uma string do tipo VARCHAR2 para agir como um buffer para ler o arquivo uma linha de cada vez; 3. Abrir o arquivo, especificando se far leitura ou escrita; 4. Manipular a linha, seja uma leitura ou uma escrita (existem sub-rotinas especficas de UTL_FILE para cada caso); 5. Fechar o arquivo.
UTL_FILE.WRITE_ERROR UTL_FILE.INTERNAL_ERROR
FCLOSE_ALL
Fecha todos os arquivos. Excees levantadas As mesmas da funo FCLOSE. Descarrega todos os dados em buffer para serem gravados em disco imediatamente. Excees levantadas
Exceo UTL_FILE.INVALID_FILEHANDLE Descrio Voc usou um handle de arquivo invlido. Provavelmente voc esqueceu de abrir o arquivo. Voc tentou gravar em um arquivo que no estava aberto para gravao (modos W e A). Ocorreu um erro de sistema operacional, tal como um erro de disco cheio, ao tentar gravar em um arquivo Ocorreu um erro interno.
FFLUSH
UTL_FILE.INVALID_OPERATION
UTL_FILE.WRITE_ERROR
UTL_FILE.INTERNAL_ERROR
FOPEN
Treinamento
Proocedure / Function
Descrio
(escrever) ou A (para anexar a um arquivo existente). UTL_FILE.INVALID_OPERATION O arquivo no pode ser aberto por algum outro motivo, como por exemplo, falta de permisso de acesso do proprietrio do software do Oracle. Na maioria das vezes, problema de permisso de acesso no sistema operacional. Um erro interno ocorreu.
UTL_FILE.INTERNAL_ERROR
GET_LINE
UTL_FILE.INVALID_OPERATION
UTL_FILE.VALUE_ERROR
IS_OPEN NEW_LINE
Verifica se um arquivo est aberto. Grava um caractere newline em um arquivo. Excees levantadas As mesmas da funo FFLUSH. Grava uma string de caracteres em um arquivo, mas no coloca uma newline depois dela. Excees levantadas As mesmas da funo FFLUSH. Grava uma linha em um arquivo. Excees levantadas As mesmas da funo FFLUSH. Formata e grava sada. Essa uma imitao bruta do procedimento printf() da linguagem C. Excees levantadas As mesmas da funo FFLUSH.
PUT
PUT_LINE
PUTF
Treinamento
No esquea de declarar a procedure na especificao da package; Certifique-se que tem acesso e as permisses necessrias ao diretrio informado (INTERFACE, em nosso exemplo).
Treinamento
Embora o nosso exemplo no implemente o tratamento de excees, uma procedure para gerao de arquivos bem codificada, deve conter esse tratamento. Para isso, veja a tabela 8, onde as possveis excees de cada sub-rotina de UTL_FILE, esto listadas.
Agora, execute a nova procedure e confira o a arquivo gerado no diretrio que voc especificou.
Certifique-se que o arquivo a ser lido est no diretrio utilizado na procedure (INTERFACE, em nosso exemplo); No esquea de declarar a procedure na especificao da package; Para executar a procedure, ligue a exibio de mensagens em sua sesso de SQL: SET SERVEROUTPUT ON;
TEXT_IO
Embora no faa parte do escopo deste treinamento, vale citar a existncia do pacote TEXT_IO. Esse pacote semelhante ao UTL_FILE, mas ele permite que voc leia e grave arquivos no cliente em vez do servidor. TEXT_IO no faz parte do software do banco de dados da Oracle. Ele vem com o Oracle Developer (Developer 2000).
Treinamento
Treinamento
Exerccios Propostos
1. Crie um pacote chamado pck_vendas que contenha:
Uma funo PIPELINED que receba um intervalo de datas e baseado nessas datas, monte uma tabela virtual que repita todos os perodos mensais para cada representante cadastrado. Nomeie essa funo como fnMensalPorRepresentante; Utilize essa funo em uma declarao SELECT que retorne a venda total mensal por representante em um intervalo de datas; Uma funo PIPELINED que receba um intervalo de datas e baseado nessas datas, monte uma tabela virtual que repita todos os perodos mensais para cada cliente cadastrado. Nomeie essa funo como fnMensalPorCliente; Utilize essa funo em uma declarao SELECT que retorne a venda total mensal por cliente em um intervalo de datas; Uma funo PIPELINED que receba um intervalo de datas e baseado nessas datas, monte uma tabela virtual que repita todos os perodos mensais para cada produto cadastrado. Nomeie essa funo como fnMensalPorProduto; Utilize essa funo em uma declarao SELECT que retorne a venda total mensal por produto em um intervalo de datas;
Uma funo PIPELINED que receba um intervalo de datas e baseado nessas datas, monte uma tabela virtual que repita todos os perodos mensais para cada Cliente/Produto cadastrados. A idia obter quanto cada cliente comprou de cada produto em cada perodo. Nomeie essa funo como fnMensalClienteProduto; Utilize essa funo em uma declarao SELECT que retorne a venda total mensal por produto em um intervalo de datas;
Treinamento
2.
Treinamento
Treinamento
SELECT p.pro_in_codigo ,p.pro_st_nome ,rmv.rmv_in_ano ,rmv.rmv_in_mes ,sum(rmv.rmv_qt_vendida) qt_vendida FROM produto p, resumo_mensal_venda rmv WHERE p.pro_in_codigo = rmv.pro_in_codigo(+) AND p.pro_st_marca = 'Brahma' GROUP BY p.pro_in_codigo ,p.pro_st_nome ,p.pro_st_marca ,rmv.rmv_in_ano ,rmv.rmv_in_mes ORDER BY p.pro_in_codigo ,rmv.rmv_in_ano DESC ,rmv.rmv_in_mes DESC; Entre os profissionais Oracle, a implementao da Oracle a mais usada porque: 1. O padro veio depois e implementou uma lgica contrria a j praticada; 2. A sinalizao do outer join usando apenas (+) ao invs de um texto em lngua inglesa, gera SQLs mais limpos; 3. A popularidade do banco de dados Oracle leva esses profissionais a ignorar esse padro. Dicas:
Se pretende trabalhar com diversos bancos de dados, utilize o padro ANSI92; Se trabalhar exclusivamente com banco de dados Oracle, siga a implementao Oracle, pois do contrrio ter problemas com outros profissionais dedicados a Oracle; Se est desenvolvendo ou evoluindo sistemas de um cliente que j possui implementaes em produo, verifique qual padro utilizam e d sequencia.
ROWNUM
Existem situaes onde voc executa um SELECT que retorna n registros, mas precisa apenas de alguns desses registros, no importando qual ou ainda incluir no retorno um nmero sequencial para cada linha retornada. O Oracle Database disponibiliza a pseudo coluna ROWNUM que pode ser utilizada nesses casos. Veja os exemplos abaixo: ROWNUM sequenciando as linhas retornadas SELECT ROWNUM ,pp.* FROM preco_produto pp; ROWNUM limitando a quantidade de linhas retornadas: O exemplo abaixo retorna as 10 maiores vendas mensais SELECT ROWNUM ,v.*
Treinamento
FROM( SELECT p.pro_in_codigo ,p.pro_st_nome ,p.pro_st_marca ,c.cli_st_nome ,rmv.rmv_in_ano ,rmv.rmv_in_mes ,rmv.rmv_qt_vendida ,rmv.rmv_vl_total FROM produto p ,resumo_mensal_venda rmv ,cliente c WHERE p.pro_in_codigo = rmv.pro_in_codigo AND rmv.cli_in_codigo = c.cli_in_codigo ORDER BY rmv.rmv_qt_vendida DESC ,rmv.rmv_in_ano DESC ,rmv.rmv_in_mes DESC ,c.cli_st_nome ) v WHERE ROWNUM <= 10;
Treinamento
IMPORTANTE: Para executar esse comando, precisar dos privilgios de criao de tabela.
INSERT SELECT
Se voc precisa executar uma sequencia de INSERTs, originados de uma mesma fonte, sem a necessidade de tratar cada linha inclusa, voc pode utilizar a combinao dos comandos INSERT e SELECT. Com essa combinao, em um nico comando, voc conseguir executar um INSERT para todos os registros retornados pelo SELECT, como no exemplo abaixo: INSERT INTO simula_novo_preco_produto (pro_in_codigo,ppr_st_tipopreco,ppr_vl_unitario) SELECT pp.pro_in_codigo ,pp.ppr_st_tipopreco ,decode(pp.ppr_st_tipopreco ,'COMPRAS',pp.ppr_vl_unitario ,'VENDAS' ,pp.ppr_vl_unitario +(pp.ppr_vl_unitario *0.10)) FROM preco_produto pp; COMMIT;
IMPORTANTE: Uma execuo envolvendo um volume muito alto de dados pode resultar em um problema de desempenho e a soluo pode no ser a ideal. Avalie com responsabilidade o uso desse recurso.
MERGE
Esta declarao SQL permite que voc obtenha dados a partir de uma fonte (tabela, view ou consulta SQL) para manipulao em outra tabela, combinar vrias operaes DML em um nico comando, (como por exemplo, um UPDATE e um INSERT). Imagine uma situao onde voc executa uma consulta em uma tabela e, a partir dos dados obtidos precise atualizar um registro em outra tabela, mas se o registro no existir, voc o incluir... Vamos criar essa situao. Inclua novos registros na tabela PRODUTO, conforme SQL abaixo: INSERT INTO produto (pro_st_nome,pro_in_codigo,pro_st_marca,pro_dt_inclusao
Elaborado por Josinei Barbosa da Silva
Treinamento
,pro_st_usuarioinclusao,pro_dt_ultimavenda,pro_vl_ultimavenda ,pro_dt_maiorvenda) VALUES ('Cerveja Malzbier',11,'Brahma',SYSDATE ,USER,NULL,NULL,NULL) / INSERT INTO produto (pro_st_nome,pro_in_codigo,pro_st_marca,pro_dt_inclusao ,pro_st_usuarioinclusao,pro_dt_ultimavenda,pro_vl_ultimavenda ,pro_dt_maiorvenda) VALUES ('Cerveja Malzbier',12,'Schincariol',SYSDATE ,USER,NULL,NULL,NULL) / COMMIT / Agora, precisamos atualizar o preo de venda dos produtos da marca Brahma para R$ 1,22, mas se no existir preo cadastrado para o produto, incluiremos. O cdigo abaixo resolveria isso:
DECLARE eRegistroInexistente EXCEPTION; CURSOR cs_produto IS SELECT p.pro_in_codigo FROM produto p WHERE p.pro_st_marca = 'Brahma'; vNovoValorVenda CONSTANT preco_produto.ppr_vl_unitario%TYPE := 1.22; BEGIN FOR csp IN cs_produto LOOP BEGIN UPDATE preco_produto pp SET pp.ppr_vl_unitario = vNovoValorVenda WHERE pp.pro_in_codigo = csp.pro_in_codigo AND pp.ppr_st_tipopreco = 'VENDAS'; IF SQL%NOTFOUND THEN RAISE eRegistroInexistente; END IF; EXCEPTION WHEN eRegistroInexistente THEN INSERT INTO preco_produto(pro_in_codigo,ppr_st_tipopreco,ppr_vl_unitario) VALUES(csp.pro_in_codigo,'VENDAS',vNovoValorVenda); WHEN OTHERS THEN
raise_application_error (-20100, 'No foi possivel atualizar o preo do produto ' ||to_char(csp.pro_in_codigo));
END; END LOOP; COMMIT; END;
Elaborado por Josinei Barbosa da Silva
Treinamento
Mas possvel executar a mesma alterao utilizando apenas o comando MERGE. No exemplo abaixo repetimos a operao para alterar o preo de venda dos produtos da marca Schincariol (para R$ 1.12) utilizando esse comando: DECLARE vNovoValorVenda CONSTANT preco_produto.ppr_vl_unitario%TYPE := 1.12; BEGIN MERGE INTO preco_produto pp USING (SELECT p.pro_in_codigo FROM produto p WHERE p.pro_st_marca = 'Schincariol') p ON (pp.pro_in_codigo = p.pro_in_codigo) WHEN MATCHED THEN UPDATE SET pp.ppr_vl_unitario = vNovoValorVenda WHERE pp.ppr_st_tipopreco = 'VENDAS' WHEN NOT MATCHED THEN INSERT(pro_in_codigo,ppr_st_tipopreco,ppr_vl_unitario) VALUES(p.pro_in_codigo,'VENDAS',vNovoValorVenda); COMMIT; END; /
Treinamento
do GROUP BY. Ele funciona como uma clusula WHERE, mas em nvel de grupo. Teste o exemplo abaixo: SELECT p.pro_st_marca ,rmv.rmv_in_ano ,sum(rmv.rmv_qt_vendida) rmv_qt_vendida ,sum(rmv.rmv_vl_total) rmv_vl_total FROM produto p ,resumo_mensal_venda rmv WHERE p.pro_in_codigo = rmv.pro_in_codigo GROUP BY p.pro_st_marca ,rmv.rmv_in_ano HAVING sum(rmv.rmv_vl_total) > 50000; Em nosso exemplo restringimos a soma do valor total (SUM()), mas poderia ser qualquer outra funo de grupo, como AVG() ou MAX(), por exemplo.
ROLLUP
Com o ROLLUP voc pode criar sub-totais com base nos valores de grupo. Vamos alterar o nosso exemplo anterior (do GROUP BY com HAVING) para obtermos sub-totais por ms e ano: SELECT rmv.rmv_in_ano ,rmv.rmv_in_mes ,p.pro_st_marca ,sum(rmv.rmv_qt_vendida) rmv_qt_vendida ,sum(rmv.rmv_vl_total) rmv_vl_total FROM produto p ,resumo_mensal_venda rmv WHERE p.pro_in_codigo = rmv.pro_in_codigo GROUP BY ROLLUP(rmv.rmv_in_ano ,rmv.rmv_in_mes ,p.pro_st_marca); Observe que alm dos sub-totais, um total geral foi acrescentado no final. Caso no queira o total geral, deixe a coluna que representa o primeiro nvel (no caso rmv_in_ano) fora do ROLLUP (logo aps a palavra-chave GROUP BY).
CUBE
Outro recurso avanado para manipular resultados agrupados e que tambm comum para sistemas de apoio a deciso o CUBE. O CUBE funciona como o ROLLUP, mas o resultado mais detalhado. Ele apresenta totais para todas as combinaes de totais do seu GROUP BY. Altere sua consulta para aplicar o CUBE ao invs do ROLLUP e observe o resultado:
Elaborado por Josinei Barbosa da Silva
Treinamento
SELECT rmv.rmv_in_ano ,rmv.rmv_in_mes ,p.pro_st_marca ,sum(rmv.rmv_qt_vendida) rmv_qt_vendida ,sum(rmv.rmv_vl_total) rmv_vl_total FROM produto p ,resumo_mensal_venda rmv WHERE p.pro_in_codigo = rmv.pro_in_codigo GROUP BY CUBE(rmv.rmv_in_ano ,rmv.rmv_in_mes ,p.pro_st_marca); SUGESTO DE PESQUISA Existem muitos outros recursos que facilitam a manipulao de dados agrupados garantindo um bom desempenho, entre eles as funes analticas. Pesquise sobre esse recurso e faa seus testes.
Com a execuo desse script, temos dois gerentes: 40 e 50, sendo que o 40 gerente do representante 10 e o 50 gerente dos representantes 20 e 30. Agora, queremos listar os representantes em ordem hierrquica, ou seja, o gerente e em seguida seus subordinados. A SQL abaixo utiliza o CONNECT BY para isso: SELECT r.rep_in_codigo ,r.rep_st_nome ,r.rep_in_codigo_gerente
Elaborado por Josinei Barbosa da Silva
Treinamento
,r.rep_vl_metamensal "Meta Mensal Vendas" ,r.rep_vl_mediamensalvendas "Media Mensal Vendas" ,r.rep_dt_maiorvenda "Valor Maior Venda" ,r.rep_dt_maiorvenda "Data Maior Venda" FROM representante r START WITH r.rep_in_codigo_gerente IS NULL CONNECT BY PRIOR rep_in_codigo = rep_in_codigo_gerente; Entendendo a SQL:
Na clusula START WITH voc defini o incio de sua hierarquia, que em nosso caso so os representantes que esto no topo da pirmide, ou seja, no possuem gerente. Na clusula CONNET BY PRIOR voc indica a associao do nvel mais baixo com o mais alto. Se traduzssemos ao p da letra, seria algo como Conecte este representante abaixo do representante indicado como gerente.
Com o CONNECT BY voc tambm pode visualizar em que nvel da hierarquia cada registro se encontra. Para isso, existe uma pseudo coluna chamada LEVEL. Voc pode utilizar LEVEL na lista de colunas: SELECT LEVEL ,r.rep_in_codigo "Codigo" ,r.rep_st_nome "Nome" ,r.rep_in_codigo_gerente "Cod.Gerente" ,r.rep_vl_metamensal "Meta Mensal Vendas" ,r.rep_vl_mediamensalvendas "Media Mensal Vendas" ,r.rep_dt_maiorvenda "Valor Maior Venda" ,r.rep_dt_maiorvenda "Data Maior Venda" FROM representante r START WITH r.rep_in_codigo_gerente IS NULL CONNECT BY PRIOR rep_in_codigo = rep_in_codigo_gerente; LEVEL tambm pode ser utilizado como restrio da consulta. Veja como fica nossa consulta se quisermos apenas visualizar os gerentes: SELECT LEVEL ,r.rep_in_codigo "Codigo" ,r.rep_st_nome "Nome" ,r.rep_in_codigo_gerente "Cod.Gerente" ,r.rep_vl_metamensal "Meta Mensal Vendas" ,r.rep_vl_mediamensalvendas "Media Mensal Vendas" ,r.rep_dt_maiorvenda "Valor Maior Venda" ,r.rep_dt_maiorvenda "Data Maior Venda" FROM representante r WHERE LEVEL = 1 START WITH r.rep_in_codigo_gerente IS NULL CONNECT BY PRIOR rep_in_codigo = rep_in_codigo_gerente;
Treinamento
mais teis:
Tabela 9: Funes de caractere incorporadas do Oracle
INSTR LENGTH LOWER LPAD LTRIM REPLACE RPAD RTRIM SUBSTR TRIM TRANSLATE UPPER
Descrio Retorna um caractere quando recebe seu valor ASCII. Retorna o cdigo ASCII do caractere. Retorna uma string na qual a primeira letra de cada palavra colocada em maiscula e todos os caracteres restantes, em minsculas. Retorna a localizao de uma string dentro de outra string. Retorna o comprimento de uma string de caracteres. Retorna NULL quando o valor NULL. Converte toda a string de caracteres para minsculas. Preenche uma string no lado esquerdo com qualquer string especificada. Corta uma string de caracteres do lado esquerdo. Substitui toda ocorrncia de uma string por outra string. Preenche uma string no lado direito de toda string especificada. Corta uma string de caracteres no lado direito. Retorna uma parte de uma string de dentro de uma string. Combina a funcionalidade das funes LTRIM e RTRIM. Igual a REPLACE, exceto que opera no nvel de caractere, em vez de operar no nvel de string. Converte toda a string de caracteres para maisculas.
Descrio Retorna o valor absoluto de um nmero Retorna o valor que representa o menor inteiro que maior do que ou igual a um nmero especificado. Muito usado para arrendondar valores para baixo. Retorna a exponenciao de e elevado potncia de algum nmero onde e= 2,7182818... Retorna o logaritmo natural de algum nmero x. Retorna o resto de algum nmero x dividido por algum nmero y. Retorna x arredondado para y casas. Retorna a raiz quadrada de algum nmero x. X nunca deve ser negativo. Retorna algum nmero x, truncado para y casas. No arredonda, apenas corta na localizao especificada.
Funo ADD_MONTHS
LAST_DAY MONTHS_BETWEEN
Descrio Adiciona um ms data especificada. Ela no adiciona 30 ou 31 dias, mas simplesmente adiciona um ao ms. Se o nmero de meses informado for negativo, subtrai os meses. Retorna o ltimo dia de determinado ms. Calcula os meses entre duas datas. Retorna um inteiro quando ambas as datas so os ltimos dias do ms. Caso contrrio, ela retorna a parte fracionria de um ms de 31 dias.
Treinamento
Descrio Retorna a data do primeiro dia da semana especificado em uma string aps a data inicial. Simplesmente retorna a data e hora do sistema no formato tipo DATE. Trunca at o parmetro de data especificado, tal como dia, ms e assim por diante. Normalmente utilizado sem parmetros, trunca na data, retirando hora, minutos e segundos.
Descrio Converte DATEs e NUMBERs em uma string VARCHAR2. Converte uma string CHAR ou VARCHAR2 em um valor DATE. Converte uma string CHAR ou VARCHAR2 em um valor NUMBER.
USER USERENV
Descrio Age como uma declarao IF...THEN...ELSE de uma lista de valores. Toma uma lista de valores ou expresses e retorna o maior valor avaliado. Verifica se o valor passado como primeiro parmetro nulo e sendo, retorna o segundo parmetro como substituto. (simula a atribuio de valor default) Retorna o nome do usurio atual em uma string VARCHAR2. Retorna as informaes sobre o seu ambiente de trabalho atual.
Dicas
Sempre utilize TO_DATE quando estiver referenciando uma data em formato string. Um cdigo bem feito segue essa prtica informando a data (em formato string) e um segundo parmetro com informando em que formato encontra a data a ser convertida (esse parmetro tambm um string); Ao referenciar variveis que no podem conter valor nulo, utilize a funo NVL(); Para exibir uma data armazenada no banco de dados ou retornada por SYSDATE, em um formato especfico, utilize a funo TO_CHAR; Utilize a funo CHR() para formatar suas mensagens de sistema com quebra de linha (chr(13)); Se pretende utilizar um valor alfanumrico em uma expresso matemtica, converta seu valor utilizando TO_NUMBER(); Antes de implementar uma funo ou uma lgica para tratar ou converter um dado, pesquise se j no existe o que precisa entre as funes incorporadas do Oracle Database.
Treinamento
Otimizador do Oracle; Variveis de ligao (Bind Variables); SQL Dinmico; EXPLAIN PLAIN; SQLs Complexas; SORT (Ordenao nas SQLs); etc...
Treinamento
Introduo
Problemas com aplicaes de baixa performance podem estar frequentemente relacionados a consultas SQL mal estruturadas ou a uma modelagem de banco de dados ineficiente. A metodologia de tuning da Oracle, recomenda que, antes de analisar configuraes do banco de dados, seja analisadas e ajustadas as instrues SQL que apresentarem desempenho insatisfatrio. A otimizao de uma instruo SQL constitui em determinar a melhor estratgia para execut-la no banco de dados. O otimizador do Oracle escolhe, por exemplo, se usar um ndice ou no para uma consulta especifica e que tcnicas de join usar na juno de mltiplas tabelas. Estas decises tm um impacto muito grande na performance de um SQL e por isso a otimizao de uma instruo essencial para qualquer aplicao e de extrema importncia para a performance de um banco de dados relacional. muito importante que os desenvolvedores conheam o otimizador do Oracle como tambm os conceitos bsicos relativos tuning. Tal conhecimento ir ajudar a escrever consultas muito mais eficientes e rpidas. Alm de conhecer o otimizador do Oracle, imprescindvel que o desenvolvedor conhea a aplicao e os dados dela. Antes de sair escrevendo uma consulta SQL, procure entender o processo do qual essa instruo far parte. Qual a finalidade dessa instruo? Informaes idnticas podem ser encontradas em diferentes fontes de dados. Se voc estiver familiarizado com estas fontes, poder identificar a fonte que proporcionar uma recuperao mais rpido, ou seja, uma consulta em menor tempo.
O Otimizador Oracle
O otimizador determina a maneira mais eficiente de se rodar uma declarao SQL. Para executar qualquer SQL o Oracle tem que montar um plano de execuo. O plano de execuo de uma consulta uma descrio de como o Oracle ir implementar a recuperao dos dados para satisfazer a uma determinada declarao SQL. At a verso 9i, o Oracle possuia dois otimizadores:
CBO (Cost Based Optimizer): Otimizador baseado em custo, que passou a ser o padro na verso 9i.
A partir da verso 10g do Oracle, o otimizador baseado em regra (RBO) deixou de ser utilizado e o otimizador baseado em custo (CBO) passou a ser o nico existente. Mesmo com o Oracle 10g utilizando apenas o CBO, vale a pena conhecermos os dois e o que veremos a seguir.
Treinamento
Seletividade A seletividade a primeira e mais importante medida do Otimizador Baseado em Custo. Ela representa uma frao de linhas de um conjunto resultante de uma tabela ou o resultado de um join ou um agrupamento. O CBO utiliza estatsticas para determinar a seletividade de um determinado predicado (clausula WHERE ou HAVING). A seletividade diretamente ligada ao predicado da consulta, como por exemplo WHERE PRO_IN_CODIGO = 1245 Ou uma combinao de predicados, como: WHERE PRO_IN_CODIGO = 1245 AND PRO_ST_CESTOQUE='S' O propsito do predicado de uma consulta limitar o escopo dela a um certo nmero de linhas em que estamos interessados. Portanto, a seletividade de um predicado indica quantas linhas de um conjunto vo ser filtradas por uma determinada condio. A seletividade varia numa faixa de valores de 0.0 at 1.0 onde a seletividade de 0 indica que nenhuma linha ser selecionada e 1 que todas as linhas sero selecionadas. A seletividade igual ao numero de valores distintos que uma coluna possui (1/NVD onde NVD significa o Numero de Valores Distintos).
Treinamento
isso: function DescricaoProduto(pCodProduto: integer): string; begin with Query1 do begin SQL.Close; SQL.Clear; SQL.Add('SELECT PRO_ST_NOME'); SQL.Add('FROM PRODUTO'); SQL.Add('WHERE PRO_IN_CODIGO = ' + pCodProduto); SQL.Open; Result := FieldByName('PRO_ST_NOME').AsString; end; end; O problema que toda vez a declarao SELECT dessa funo for executada, o Oracle vai fazer uma anlise da instruo para definir o plano de execuo (o chamado PARSE). Isso acontece porque o cdigo do produto, recebido como argumento, concatenado SQL, ou seja, para o Oracle essa mesma instruo, quando o cdigo for 1 diferente de quando o cdigo for 2. Outro impacto negativo dessa instruo refere-se ao uso de memria. O servidor Oracle possui uma rea de memria compartilhada que aloca as instrues SQLs executadas mais recentemente. Quando uma instruo recebida pelo servidor, antes de fazer o seu PARSE, ele verifica se essa instruo existe nessa rea. Se existir, ele utiliza o plano que j foi definido (e que fica alocado com a instruo). Muito bem e da? Da que se o Oracle acha que a instruo acima com o cdigo 1 diferente da mesma instruo com o cdigo 2, ele vai consumir dois espaos de memria. Agora imagine um sistema com mil usurios conectados e todas as instrues SQLs escritas dessa forma! A SQL do exemplo acima, ficaria alocado na memria da servidor Oracle da seguinte forma: Quando for cdigo 1 SELECT PRO_ST_NOME FROM PRODUTO WHERE PRO_IN_CODIGO = 1 Quando for cdigo 2 SELECT PRO_ST_NOME FROM PRODUTO WHERE PRO_IN_CODIGO = 2 Literalmente, as duas instrues acima so diferentes. Para que o Oracle reaproveite as instrues, eficientemente, IMPORTANTSSIMO que os aplicativos faam uso das chamadas BIND VARIABLES ao executar SQLs. Abaixo, segue a mesma funo Delphi do exemplo anterior, utilizando esse recurso: function DescricaoProduto(pCodProduto: integer): string; begin with Query1 do begin SQL.Close;
Elaborado por Josinei Barbosa da Silva
Treinamento
SQL.Clear; SQL.Add('SELECT PRO_ST_NOME'); SQL.Add('FROM PRODUTO'); SQL.Add('WHERE PRO_IN_CODIGO = :PRO_IN_CODIGO'); ParamByName('PRO_IN_CODIGO').AsInteger := pCodProduto; SQL.Open; Result := FieldByName('PRO_ST_NOME').AsString; end; end; A BIND VARIABLE indicada pelos dois pontos dentro da declarao SQL (:PRO_IN_CODIGO). O nome da variable livre, mas observe que no nosso caso foi utilizado o nome da coluna que est sendo filtrada. Esse apenas um padro. Bem, esta instruo na memria do servidor Oracle ficar semelhante com o demonstrado abaixo: SELECT PRO_ST_NOME FROM PRODUTO WHERE PRO_IN_CODIGO = :1 Este :1 no representa o cdigo de produto 1 e sim uma BIND VARIABLE. Essa varivel assumir como valor qualquer cdigo de produto informado na aplicao, ou seja, sempre ser a mesma instruo, no importando a quantidade de vezes que ela for executada e, consequentemente, o mesmo plano de execuo (j armazenado da primeira vez). Portanto, h grande importncia e vantagens no uso de SQL que usam variveis de ligao (bind) em aplicaes que interagem com bancos de dados, especialmente quando envolvem valores dinmicos e parmetros fornecidos pelo usurio, de forma que este recurso deve ser utilizado sempre, tratando-se de boa prtica de programao.
SQL Dinmico
Tudo que falamos sobre BIND VARIABLE, pode induzir o leitor a achar que o recurso de SQL Dinmico da PL/SQL funciona de forma semelhante. ENGANO! Os SQLs Dinmicos do PL/SQL SEMPRE so submetidos ao PARSE e SEMPRE ocupam lugar exclusivo na memria. Quando desenvolver rotinas PL/SQL, procure escrever SQLs nativos (estticos). Por exemplo, na funo pck_cliente.GetMediaCompraMensal poderia ser reescrita da seguinte forma: -- Funo que retorna a media de compra (geral, -- por ramo de atividade, cidade ou ramo + cidade) FUNCTION GetMediaCompraMensal (pRamo cliente.cli_st_ramoatividade%TYPE ,pCidade cliente.cli_st_cidade%TYPE) RETURN NUMBER IS vlResult cliente.cli_vl_mediacomprasmensal%TYPE := 0; BEGIN SELECT AVG(c.cli_vl_mediacomprasmensal) INTO vlResult
Elaborado por Josinei Barbosa da Silva
Treinamento
FROM cliente c WHERE c.cli_st_ramoatividade = decode(pRamo,NULL,c.cli_st_ramoatividade,pRamo) AND c.cli_st_cidade = decode(pCidade,NULL,c.cli_st_cidade,pCidade); RETURN(vlResult); EXCEPTION WHEN no_data_found THEN RETURN(0); WHEN OTHERS THEN raise_application_error (-20100, 'No foi possivel recuperar mdia de compra para: '|| chr(13)||'Ramo: '||pRamo|| chr(13)||'Cidade: '||pCidade|| chr(13)|| chr(13)||'Erro: '||sqlerrm); END GetMediaCompraMensal; IMPORTANTE: no exemplo acima, aplicamos uma funo do Oracle nas condies da consulta. Esse tipo de implementao deve ser avaliado caso a caso, pois pode afetar a performance (negativamente). Via de regra, os problemas acontecem quando a funo aplicada na coluna e no no valor de restrio.
O uso de ndices
Para tirar vantagem dos ndices, escreva seu SQL de uma maneira que o Oracle faa uso dele. O otimizador do Oracle no usar o acesso atravs de um ndice simplesmente porque ele existe em uma coluna, caso o otimizador seja por custo. Lembre-se tambm que um ndice NO SELETIVO pode ser ignorado pelo otimizador CBO. Tenha certeza de ter criado todos os ndices necessrios nas tabelas, mas tome cuidado com o excesso de ndices, pois eles podem degradar a performance de DMLs na tabela. Ento como escolher que colunas indexar?
Use ndices em colunas que so frequentemente usados na clausula WHERE de consultas da aplicao ou de consultas usadas por usurios finais. Indexe as colunas que so frequentemente usadas para juntar join as tabelas nas diferentes consultas. Prefira fazer join pelas chaves primarias e chaves estrangeiras. Use ndices apenas em colunas que possuem uma baixa porcentagem de linhas com valores iguais (isso est diretamente ligado a seletividade). No use ndices em colunas que so usadas apenas com funes e operadores NO EXATOS (<, <=, >, >=, <>, !=) na clausula WHERE, pois a partir do momento em que voc aplica uma funo coluna ou utiliza um operador que permite um range de valores, o ndice ignorado pois vai apontar para n possveis entradas. No indexe colunas que so frequentemente modificadas ou quando a eficincia ganha atravs da criao de um ndice no valha a pena devido perda de performance em operaes de INSERT, UPDATE e DELETE. Com a criao do ndice, estas operaes perdero em performance devido necessidade de manter o ndice correto. ndices nicos (UNIQUE) so melhores que os no nicos devido a melhor seletividade. Use ndices nicos em chaves primrias e ndices no nicos em chaves estrangeiras (FOREIGN KEY) e colunas muito usadas nas clausulas WHERE;
Elaborado por Josinei Barbosa da Silva
Treinamento
AVALIE SEMPRE a possibilidade de criar um ndice para cada FOREIGN KEY da tabela. Isso pode resultar em grandes ganhos de desempenho em instrues onde ocorre o relacionamento entre as tabelas.
EXPLAIN PLAIN
Se familiarize com a ferramenta EXPLAIN PLAN e use-a para otimizar seu SQL. O EXPLAIN PLAN ir te ajudar a descobrir, atravs do plano de execuo da consulta, os meios de acesso que o Oracle est utilizando para acessar as tabelas do banco de dados. Uma vez que identificamos um SQL com uma performance ruim, podemos usar o comando EXPLAIN PLAN FOR para gerar um plano de execuo para este SQL. Para usar esse comando necessrio criar a tabela PLAN_TABLE, atravs do script utlxplan.sql, que normalmente fica em Oracle_home/rdbms/admin Vejamos um exemplo no SQL*Plus de como usar o EXPLAIN PLAN: EXPLAIN PLAN FOR SELECT n.*, i.* FROM nota_fiscal_venda n, item_nota_fiscal_venda i WHERE n.nfv_in_numero = i.nfv_in_numero; Desta forma estamos populando a tabela PLAN_TABLE com o plano de execuo do SQL. A consulta no executada. Apenas o plano de execuo gerado. Mas como visualizamos o plano? Antes de executar o EXPLAIN PLAN, certifique-se de truncar a tabela PLAN_TABLE ou de usar o comando "SET STATEMENT_ID=" nas execues do comando EXPLAIN PLAN FOR, pois de outra forma o plano de execuo de diversas consultas ser guardado na PLAN TABLE tornando sua interpretao extremamente complicada. Ento, vamos recriar nosso plano de execuo definindo um STATEMENT_ID: EXPLAIN PLAN SET STATEMENT_ID = 'XXX' FOR SELECT n.*, i.* FROM nota_fiscal_venda n, item_nota_fiscal_venda i WHERE n.nfv_in_numero = i.nfv_in_numero; Agora voc pode consultar a PLAN TABLE para ver como a consulta ser executada. A consulta a seguir pode ser usada para extrair a parte importante do plano de execuo da tabela PLAN TABLE. SELECT LPAD(' ',2*(LEVEL-1))|| OPERATION||' '|| OPTIONS||' '||' '|| OBJECT_NAME||' '||
Treinamento
DECODE(ID, 0, 'COST = '||POSITION) "QUERY PLAN" FROM PLAN_TABLE START WITH ID = 0 AND STATEMENT_ID = 'XXX' CONNECT BY PRIOR ID = PARENT_ID AND STATEMENT_ID = 'XXX'; O plano de execuo da consulta ficar como a seguinte amostra:
QUERY PLAN -----------------------------------------------------------------------------SELECT STATEMENT COST = 11 MERGE JOIN SORT JOIN TABLE ACCESS FULL NOTA_FISCAL_VENDA SORT JOIN TABLE ACCESS FULL ITEM_NOTA_FISCAL_VENDA
IMPORTANTE: Para que o o custo da declarao (COST) seja informado, necessrio que estatsticas dos objetos acessados pela declarao estejam devidamente coletados. Essas estatsticas podem ser coletadas atravs do pacote dbms_stats. O procedimento gather_schema_stats, por exemplo, faz a coleta de estatsticas de todos os objetos do squema. NO exemplo abaixo coletada as estatsticas do schema JSILVA: SQL> execute dbms_stats.gather_schema_stats(ownname => 'JSILVA'); Existem boas ferramentas de programao que possuem o EXPLAN, como: Toad, Oracle Sql Developer, PLSQL Developer, etc..
O AUTOTRACE do SQL*Plus
O AUTOTRACE um recurso do SQL*Plus que permite gerar, automaticamente, um relatrio baseado no plano de execuo usado pelo otimizador assim como tambm as estatsticas referentes execuo daquele SQL. O Relatrio gerado aps a execuo com sucesso de comandos DML (SELECT, DELETE, UPDATE e INSERT). Ele usado para monitorar e otimizar a performance dessas declaraes. O AUTOTRACE pode ser utilizado de cinco maneiras: 1. SET AUTOTRACE OFF: Nenhum relatrio de AUTOTRACE gerado. Esta a opo padro (Default). 2. SET AUTOTRACE ON EXPLAIN: O relatrio de AUTOTRACE mostra apenas o Plano de execuo. 3. SET AUTOTRACE ON STATISTICS: O relatrio de AUTOTRACE mostra apenas as estatsticas referentes a execuo do SQL. 4. SET AUTOTRACE ON: O Relatrio de AUTOTRACE inclui tanto o Plano de execuo como as estatsticas referentes execuo do SQL. Tambm requer executar o script plustrce.sql e receber alguns privilgios de DBA. 5. SET AUTOTRACE TRACEONLY: Funciona como o SET AUTOTRACE ON, mas suprime a impresso do resultado da declarao se ela possuir. Tambm requer executar o script plustrce.sql e receber alguns privilgios de DBA. IMPORTANTE: Para utilizar as opes que exibem estatsticas necessrio:
Elaborado por Josinei Barbosa da Silva
Treinamento
Executar o script plustrce.sql, que fica em ORACLE_HOME\sqlplus\admin com o usurio SYS (como SYSDBA);
Ainda com o usurio SYS, atribuir o role PLUSTRACE para o usurio que far uso do recurso, caso no seja o prprio DBA.
Exemplo: 1. Ative o SET AUTOTRACE TRACEONLY para exibir o plano de execuo e as estatsticas, sem as linhas de retorno: SQL> SET AUTOTRACE TRACEONLY; 2. Agora execute a seguinte consulta: SELECT n.nfv_in_numero, i.pro_in_codigo FROM nota_fiscal_venda n, item_nota_fiscal_venda i WHERE n.nfv_in_numero = i.nfv_in_numero; 3. O resultado deve ser parecido com: Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=60 Bytes=780) 1 0 NESTED LOOPS (Cost=4 Card=60 Bytes=780) 2 1 TABLE ACCESS (FULL) OF 'ITEM_NOTA_FISCAL_VENDA' (Cost=4 Card=60 Bytes=480) 3 1 INDEX (UNIQUE SCAN) OF 'PK_NOTA_FISCAL_VENDA' (UNIQUE)
Statistics ---------------------------------------------------------0 recursive calls 0 db block gets 24 consistent gets 0 physical reads 0 redo size 1467 bytes sent via SQL*Net to client 532 bytes received via SQL*Net from client 5 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 60 rows processed
Lendo a sada de um AUTOTRACE No exemplo acima, onde usamos o AUTOTRACE TRACEONLY, temos o plano de execuo e as estatsticas, mas como interpretamos isso? O plano de execuo:
Opitmizer indica o otimizador Oracle usado: CHOOSE ou RULE (Custo ou Regra); Cost indica o custo em cada ponto do plano, ou seja, o custo da SELECT STATEMENT o
Pgina 145 de 154
Treinamento
Nested, como o nome j diz, algo aninhado e no nosso exemplo temos NESTED LOOP, ou seja, um LOOP aninhado que foi gerado devido ao relacionamento de master/detail entre nota fiscal e item nota fiscal; TABLE ACCESS (FULL) indica o objeto em que foi realizado um acesso completo, ou seja, no nosso caso todas as linhas da tabela de item nota fiscal foram verificadas. Isso no bom em tabelas com grande e mdio volumes de dados. O ideal um acesso parcial atravs de um indce, como aconteceu com a tabela de notas; INDEX SCAN indica o ndice utilizado para acessar um conjunto de dados, no nosso caso, na tabela de nota fiscal foi usada a chave primria.
As estatsticas:
recursive calls: Nmero de chamadas recursivas dentro da instruo; db block gets: o nmero de vezes que um bloco foi requisitado para o buffer cache;
consistent gets; o nmero de vezes que uma leitura consistente foi requisitada para um bloco do buffer cache.
physical reads: o nmero total de blocos de dados lidos do disco para o buffer cache. redo size: o tamanho (em bytes) do log redo gerado durante essa operao;
bytes sent via SQL*Net to client: Total de bytes enviados ao cliente pelos processos de segundo plano; bytes received via SQL*Net from client: Total de bytes enviados ao servidor pelo cliente; SQL*Net roundtrips recebidas pelo cliente;
to/from
sorts (disk): Nmero de ordenaes em disco. Isso muito ruim para o desempenho pois executa I/O;
INDEX SCAN versus FULL TABLE SCAN Se voc estiver selecionando mais de 15 % das linhas de uma tabela, um FULL TABLE SCAN geralmente mais rpido do que o acesso pelo ndice. Quando o acesso por ndice causar lentido ao invs de apresentar um ganho de performance, voc pode utilizar algumas tcnicas para eliminar o uso do ndice: SELECT * FROM produto p WHERE p.pro_vl_ultimavenda+0 = 10000; Supondo que existisse um ndice na coluna pro_vl_ultimavenda, ele no seria usado, pois o
Treinamento
ndice seria pela coluna pro_vl_ultimavenda e no pela coluna mais zero. Um ndice tambm no usado se o Oracle tiver que realizar uma converso implcita de dados. No nosso exemplo, pro_vl_ultimavenda do tipo NUMBER, ou seja, se filtrarmos o valor como string, o ndice no ser usado: SELECT * FROM produto p WHERE p.pro_vl_ultimavenda = '10000'; Essa manobra tambm poderia ser realizada aplicando uma funo na coluna filtrada, sem que isso afetasse seu contedo, claro.
CUIDADO: Lembre-se sempre que essa teoria s vale quando estamos selecionando mais do que 15% dos dados e, mesmo assim, deve ser analisada com ateno, pois as excees tambm existem. Tenha responsabilidade no uso dessas tcnicas.
A.COL1 > A.COL2 A.COL1 < A.COL2 A.COL1 >= A.COL2 A.COL1 <= A.COL2 COL1 IS NULL COL1 IS NOT NULL.
Uma entrada de ndice possui ponteiros para valores exatos da tabela e no um range de valores, logo o uso de operadores >, <, >= e <=, inviabilizam o uso de um ndice. Um ndice no guarda o ROWID (que o ponteiro) de colunas que possuem valores nulos. Qualquer consulta em linhas que possuam valores nulos o ndice no pode ser utilizado. COL1 NOT IN (value1, value2 ) COL1 != expression COL1 LIKE '%teste'
Neste caso, o uso do "%" no inicio da string acaba por suprimir a parte por onde coluna indexada e por isso o ndice no usado. Por outro lado, COL1 LIKE 'teste%' ou COL1 LIKE 'teste%teste%' faz uso do ndice resultando em uma busca por faixas limites. UPPER(COL1) = 'TESTE'
Quaisquer expresses, funes ou clculos envolvendo colunas indexadas no faro uso do ndice se
Treinamento
ele existir. No exemplo acima, o uso da funo UPPER vai impedir do ndice ser usado.
Evite a clausula OR
Se um SQL envolve OR na clausula WHERE, ele tambm pode ser reescrito substituindo o OR pelo UNION. Voc deve verificar cuidadosamente os planos de execuo de cada SQL para decidir qual o mais adequado e com melhor desempenho para a aplicao. O uso de OR no recomendado em aplicaes transacionais. Seu uso muito difundido em aplicaes de Datawarehouse.
Treinamento
No nosso caso, as tabelas possuem poucos registros, mas imagine uma situao onde existem 10.000 clientes cadastrados e sejam emitidas 50.000 notas por dia?! Teramos 500.000.000 registros processados no banco de dados e retornados para nossa aplicao. Provavelmente essa consulta travaria o banco de dados. Por isso, muito cuidado para no produzir um produto cartesiano indesejado. Existem situaes em que o produto cartesiano produzido de propsito, mas so raros Muito raros!
SQLs complexas
Comandos SQLs muito complexos podem sobrecarregar o otimizador; As vezes a melhor soluo pode ser escrever vrios SQLs simples com uma boa performance do que um nico SQL complexo. Por exemplo: se uma juno envolve mais de 8 tabelas com uma grande quantidade de dados seria melhor quebrar o SQL em dois ou trs SQLs menores, cada um envolvendo no mximo 4 junes de tabelas, e guardar os resultados em tabelas temporrias criadas previamente ou criar um bloco ou funo para realizar toda a operao, passo a passo. O SQL no uma linguagem procedural. Usar um cdigo SQL para executar diferentes coisas geralmente resulta em baixa performance para cada uma das tarefas. Se voc deseja que seu SQL faa diferentes tarefas, ento escreva vrios SQLs, ao invs de escrever apenas um para fazer as diferentes tarefas dependendo dos parmetros passados para o script. Quando sua declarao SQL estiver se parecendo mais com um programa do que com uma instruo de comandos, analise a possibilidade de quebr-la em pedaos.
Treinamento
tabela de produtos tivesse 1000 linhas e nossa tabela de itens de nota fiscal tambm tivesse 1000 linhas. Executando a consulta a seguir, todas as linhas da tabela de itens seriam lidas para cada linha da tabela de produto. O Resultado final vai ser de 1.000.000 de linhas lidas: SELECT p.pro_in_codigo FROM produto p WHERE p.pro_in_codigo IN(SELECT i.pro_in_codigo FROM item_nota_fiscal_venda i ); Se alterarmos a declarao para usar o EXISTS, no mximo, uma linha ser lida para cada linha correspondente da tabela produto, reduzindo desta forma a sobrecarga de processamento da consulta: SELECT p.pro_in_codigo FROM produto p WHERE EXISTS (SELECT 1 FROM item_nota_fiscal_venda i WHERE i.pro_in_codigo = p.pro_in_codigo );
Evite o SORT
SORT como referenciamos operaes de ordenao realizadas pelo Oracle. No ponto de vista de desempenho, SORT nunca bom. Para resumir, o Oracle executa um SORT quando precisa arrumar os dados antes de envi-los para o solicitante. Ele recupera os dados e executa o SORT para organiz-los da maneira que o solicitante pediu. Por exemplo, quando necessrio devolver o resultado ordenado, agrupado ou distinto, haver um SORT, ou seja, um ORDER BY, um GROUP BY ou um DISTINCT vai gerar SORT. Quando necessrio realizar essa operao, o Oracle tenta faz-lo em memria, mas se a rea reservada para isso no for suficiente para a quantidade de dados recuperada, o Oracle vai utilizar o TABLESPACE TEMPORRIA em disco, ou seja, vai gerar um I/O nada desejvel, pois I/O o que h de pior para desempenho de banco de dados.
Treinamento
sempre melhor escrever SQLs diferentes para tarefas diferentes, mas se voc tem que usar apenas um SQL, voc pode fazer ele parecer menos complexo usando o operado UNION. Toda via, o UNION tem um problema de desempenho. O UNION executa, implicitamente um DISTINCT, ou seja, faz um SORT para eliminar linhas repetidas. Mas possvel executar unio de declaraes SQL evitando o SORT do UNION. Voc pode usar o UNION ALL que retorna todas as linhas, no se preocupando com a distino entre elas. Isso quer dizer que ele no faz um SORT. Se voc tem certeza que os resultados das SELECTs unidas no geraram linhas repetidas, utilize o UNION ALL.
Reciclagem de VIEWs
Esteja atento ao fato de escrever uma VIEW com um propsito e depois utiliz-la para outro onde ela seria mal empregada. Uma consulta VIEW requer que todas as tabelas por ela referenciadas sejam acessadas para que os dados sejam retornados. Antes de usar uma VIEW, verifique se todas as tabelas desta VIEW precisam ser realmente acessadas apara retornar os dados que voc precisa. Seno, no utilize a VIEW. Ao invs disso use as tabelas base ou crie uma nova VIEW (se necessrio) para sua necessidade em especifico.
Database Link
As vezes sua consulta est bem elaborada, o banco possui ndices seletivos e tudo mais que tenda para a boa performance, mas a sua consulta demora para dar retorno. Isso pode ser causado por causa de Database Link. Se a sua consulta acessa um objeto de outro banco de dados (diretamente ou por sinnimo), voc pode ter um problema de performance, pois existe o tempo rede entre um banco de dados e outro (alm do trfego de rede do banco de dados base com a sua aplicao). Como se no bastasse o fato de existir um trfego de rede extra, a performance de Database Link no das melhores. Os Database Links so recomendados apenas para carga de dados em processos agendados (jobs noturnos, por exemplo).
Treinamento
Isso possvel movendo a condio WHERE de cada varredura em um comando CASE, que filtra os dados de cada agregao. Eliminando n-1 varreduras, pode representar um grande ganho de performance. O exemplo a seguir que saber o numero de representantes cuja maior venda menor do que 20000, entre 20001 e 40000, e maior que 40000 todo ms. Isto pode ser feito atravs de 3 consultas: SELECT COUNT(*) FROM representante r WHERE r.rep_vl_maiorvenda <= 20000; SELECT COUNT(*) FROM representante r WHERE r.rep_vl_maiorvenda BETWEEN 20001 AND 40000; SELECT COUNT(*) FROM representante r WHERE r.rep_vl_maiorvenda > 40000; Neste caso executamos trs comandos para conseguir as informaes e conseguimos com sucesso, entretanto, mais eficiente rodar toda consulta como um nico SQL. Cada nmero calculado como uma coluna. O count usa um filtro com o comando CASE para contabilizar apenas as linhas onde a condio valida, como mostrado abaixo: SELECT COUNT (CASE THEN COUNT (CASE THEN COUNT (CASE THEN FROM representante WHEN r.rep_vl_maiorvenda <= 20000 1 ELSE NULL END) menor_20000, WHEN r.rep_vl_maiorvenda BETWEEN 20001 AND 40000 1 ELSE NULL END) entre_20000_40000, WHEN r.rep_vl_maiorvenda > 40000 1 ELSE NULL END) maior_40000 r;
Teremos as mesmas informaes com um nico acesso ao banco e sem prejudicar o plano de execuo!
Utilize blocos PL/SQL para executar operaes SQL repetidas em LOOPs de sua aplicao
Imagine que voc acaba de incluir uma nota fiscal com 200 itens e para cada item, voc precisa validar alguma coisa no cadastro de produto? Voc ter que executar 200 vezes a mesma SELECT mudando apenas o produto, ou seja, duzentas vezes esse comando ser enviado pela rede ao servidor e o resultado ser devolvido. Agora imagine que voc emita 400 notas por dia! Que trfico de rede, no?! Existem maneiras de evitar esse problema! Muitas linguagens possuem recursos para que voc fornea um array como parmetro para o bloco SQL e ento processe as instrues de cada linha do array antes de retornar aplicao. No nosso exemplo, o array conteria os itens da nota e as instrues seriam as SELECTs de verificao. No Delphi, por exemplo, existe um conjunto de componentes especficos para acesso banco de dados Oracle (no gratuito) que disponibiliza um tipo chamado TPLSQLTable. Voc pode definir uma varivel desse tipo, aliment-lo com os itens que desejar e ter um array em Delphi. O seu bloco PL/SQL s precisa ter uma coleo do tipo tabela por ndice e receber o array do Delphi como parmetro, como abaixo: DECLARE
Elaborado por Josinei Barbosa da Silva
Treinamento
-- Definir um vetor para conter itens TYPE TCodigoProduto IS TABLE OF produto.pro_in_codigo%TYPE INDEX BY binary_integer; -- Declarao de variveis cCodigoProduto TCodigoProduto; BEGIN -- Coleo PL/SQL recebendo como parmetro array do Delphi com itens da nota -- (pCodigoProduto o array em Delphi e cCodigoProduto o array em PL/SQL) cCodigoProduto := :pCodigoProduto; -- Executa loop na lista de itens FOR vCount IN cCodigoProduto.FIRST..cCodigoProduto.LAST LOOP /* Processamento de cada linha do array montado no Delphi*/ END LOOP; END; /
Treinamento
Exerccios Propostos
1. Revise todos os procedimentos criados nos pacotes pck_cliente, pck_produto e pck_representant, aplicando as boas prtica de desenvolvimento SQL.