You are on page 1of 154

Treinamento

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

Use a clausula RETURNING em declaraes DML:..........................................................................153 Exerccios Propostos............................................................................................................................... 154

Elaborado por Josinei Barbosa da Silva

Pgina 4 de 154

Treinamento

01 Introduo ao PL/SQL
1. 2. 3. 4.

Objetivo do curso; Conceitos bsicos; Declarao de variveis; Tratamento de excees;

Elaborado por Josinei Barbosa da Silva

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:

Blocos annimos; Stored procedures; e Packages;

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.

Elaborado por Josinei Barbosa da Silva

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.

Por que aprender a PL/SQL?


Independente da ferramenta front-end que est usando, voc pode usar a PL/SQL para executar o processamento no servidor em vez de execut-lo no cliente. Voc pode usar a PL/SQL para encapsular regras de negcios e outras lgicas complicadas. Ela fornece modularidade e abstrao. Voc pode us-la nos gatilhos (triggers) do banco de dados para codificar restries complexas, as quais reforam a integridade do banco de dados, para registrar alteraes e para replicar os dados. A PL/SQL tambm pode ser usada com os procedimentos armazenados e as funes para fornecer maior segurana ao banco de dados. Finalmente,ela fornece um nvel de independncia da plataforma. O Oracle est disponvel para muitas plataformas de hardware, mas a PL/SQL igual em todas elas.

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

Elaborado por Josinei Barbosa da Silva

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:

Figura 01: Estrutura de bloco annimo do PL/SQL.

No decorrer do curso, veremos um outro tipo de bloco PL/SQL: Stored Procedure.

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:

Colocar um ponto e vrgula(;) no final de cada comando SQL ou PL/SQL;

Quando um bloco executado com sucesso, sem erros de compilao ou erros no tratados, a seguinte mensagem ser mostrada:

PL_SQL procedure successfully completed


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

Elaborado por Josinei Barbosa da Silva

Pgina 9 de 154

Treinamento

Ambiente de Execuo
O ambiente de execuo PL/SQL possui dois componentes essenciais:

Motor PL/SQL; Client do Oracle (com uma Ferramenta de desenvolvimento).

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.

Ferramentas para execuo de declaraes SQL e desenvolvimento PL/SQL


SQL*Plus

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:

Elaborado por Josinei Barbosa da Silva

Pgina 10 de 154

Treinamento

Figura 02: Ambiente grfico do SQL*Plus

Figura 03: Ambiente de prompt do MS-DOS

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

Elaborado por Josinei Barbosa da Silva

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

Figura 04: Debuger do PL/SQL Developer

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

Elaborado por Josinei Barbosa da Silva

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:

Figura 06: SQL Window do PL/SQL Developer

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:

Figura 07: Editor do Command Window do PL/SQL Developer

Figura 08: Janela de execuo do Command Window do PL/SQL Developer

Object Browser: Lista de forma hierrquica os objetos permitidos ao usurio logado,

Elaborado por Josinei Barbosa da Silva

Pgina 14 de 154

Treinamento

disponibilizando atalhos que facilitam a manuteno e edio desses objetos:

Figura 09: Object Browser do PL/SQL Developer

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.

Elaborado por Josinei Barbosa da Silva

Pgina 15 de 154

Treinamento

Figura 10: Edio de stored procedure no TOAD

Elaborado por Josinei Barbosa da Silva

Pgina 16 de 154

Treinamento

Figura 11: O SQL Builder do TOAD

Elaborado por Josinei Barbosa da Silva

Pgina 17 de 154

Treinamento

Figura 12: O SQL Edit Window do TOAD

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

Oracle SQL Developer

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:

Elaborado por Josinei Barbosa da Silva

Pgina 18 de 154

Treinamento

Figura 13: Vrias conexes abertas no Oracle SQL Developer, visualizadas na tela principal

Elaborado por Josinei Barbosa da Silva

Pgina 19 de 154

Treinamento

Figura 14: Alterando a conexo da aba de trabalho ativa no Oracle SQL Developer

Diversas formas de resultados na mesma tela, atravs de abas:

Elaborado por Josinei Barbosa da Silva

Pgina 20 de 154

Treinamento

Figura 15: Resultado da instruo, apresentados de vrias maneiras, atravs de abas

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:

Elaborado por Josinei Barbosa da Silva

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.

Elaborado por Josinei Barbosa da Silva

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:

Identificador: nome da varivel.

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.

Datatypes escalares (tipos de dados simples)


Uma varivel escalar uma varivel que no formada de alguma combinao de outras variveis. As variveis escalares no tm componentes internos que voc pode manipular individualmente. Elas so usadas para construir datatypes mais complexos, tais como os registros e arrays. A tabela 1 abaixo exemplifica alguns datatypes escalares da PL/SQL:
Elaborado por Josinei Barbosa da Silva

Pgina 23 de 154

Treinamento

Tabela 1: Exemplo de datatypes escalares

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

:= := := :=

10; 'ITU'; 3.1415; FALSE;

Elaborado por Josinei Barbosa da Silva

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.

Elaborado por Josinei Barbosa da Silva

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

Elaborado por Josinei Barbosa da Silva

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.

O Escopo de uma varivel


Escopo de uma varivel a regio de um programa no qual podemos referenciar a varivel. Variveis declaradas em blocos PL/SQL so considerados local para o bloco onde est declarado e global para todos seus sub-blocos. Se uma varivel global redeclarada em um sub-bloco, ambos permanecem no escopo. Dentro do sub-bloco, somente a varivel local visvel e para referenciar a varivel global devemos utilizar uma tag, que ser vista a seguir. Entretanto, no podemos declarar uma varivel duas vezes no mesmo bloco, mas podemos declarar a mesma varivel em dois blocos diferentes. As duas variveis so distintas, ou seja, qualquer mudana em uma, no ir afetar a outra. Um bloco no pode referenciar variveis declaradas em outros blocos de mesmo nvel porque estas variveis no so locais e nem globais para este bloco. Uma varivel visvel no bloco no qual ela foi declarada e para todos os sub-blocos e procedures e functions aninhados. Se o bloco no acha a varivel declarada localmente, ele procura na sesso declarativa do bloco pai. Um bloco pai nunca procura por uma varivel declarada no bloco filho. O escopo descrito aqui se aplica a todos objetos declarados, tais como variveis, cursores, excees definidas por usurio e constantes. Veja uma representao do escopo de variveis:

Elaborado por Josinei Barbosa da Silva

Pgina 28 de 154

Treinamento

Figura 17: Escopo de uma varivel

Usando tags para identificar um varivel


Podemos identificar uma varivel global dentro de um sub-bloco usando uma tag como prefixo do bloco PL/SQL. Fazendo isto, conseguimos com que uma varivel declarada no bloco pai e de mesmo nome da varivel declarada no bloco filho, possa ser manipulada dentro do bloco filho. Esta tag definida atravs dos smbolos << tag >>, onde tag pode ser qualquer palavra, no podendo utilizar nmeros, nem outros smbolos. Vejamos um exemplo prtico: <<BlocoPai>> DECLARE x number := 4; BEGIN DECLARE x number := 2; BEGIN dbms_output.put_line(BlocoPai.x); END; dbms_output.put_line(x); END; / No exemplo acima, estamos referenciando a varivel x do bloco pai, dentro da sesso executvel do bloco filho, mesmo o bloco filho tendo uma varivel de mesmo nome. Isto s foi possvel porque utilizamos a tag BlocoPai, que est pr-fixada no bloco pai.

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.

Elaborado por Josinei Barbosa da Silva

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.

Elaborado por Josinei Barbosa da Silva

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:

Erros predefinidos da Oracle; Erros no definidos da Oracle;


Pgina 31 de 154

Elaborado por Josinei Barbosa da Silva

Treinamento

Erros definidos pelo usurio.

Capturando uma exceo


Se a exceo invocada na rea de execuo do bloco e a mesma for manipulada na rea de exceo com sucesso, ento a execuo no propaga para o bloco ou para o ambiente e o bloco termina com sucesso. Podemos capturar qualquer erro incluindo comandos dentro da rea de exceo de um bloco PL/SQL. Cada manipulao consiste de uma clusula WHEN que especifica uma exceo, seguida por uma seqncia de comandos que sero executados quando a exceo for invocada. Sintaxe: EXCEPTION WHEN exceo1 [OR exceo2 ...] THEN Comando1; Comando2; . . . WHEN exceo3 [OR exceo4 ...] THEN Comando1; Comando2; . . . WHEN OTHERS THEN Comando1; Comando2; IMPORTANTE:

A rea de manipulao de excees, inicia-se, sempre, com a palavra chave EXCEPTION.

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 pr-definidas da Oracle


O servidor Oracle define vrios erros com nomes padro. Embora todo erro Oracle tenha um nmero, um erro deve ser referenciado pelo nome. A PL/SQL tem alguns erros e excees comuns da Oracle predefinidos, os quais incluem o seguinte:
Tabela 3: Excees pr-definidas da Oracle

Excees no_data_found

Descrio A SELECT de linha nica no retornou dados.

Elaborado por Josinei Barbosa da Silva

Pgina 32 de 154

Treinamento

Excees too_many_rows invalid_cursor value_error invalid_number zero_divide dup_val_on_index

cursor_already_open not_logged_on transaction_backed_out login_danied program_error storage_error timeout_on_resource rowtype_mismatch others

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

Erros indefinidos da Oracle


Conseguimos capturar erros no definidos pelo Servidor Oracle declarando-os primeiro ou usando o manipulador OTHERS. A exceo declarada invocada implicitamente. No PL/SQL, o PRAGMA EXCEPTION_INIT() diz para o compilador para associar uma exceo com um nmero de erro Oracle. Isto permite referenciar em qualquer exceo interna pelo nome e escrever um manipulador especfico para isto. IMPORTANTE: PRAGMA uma palavra reservada e uma diretiva de compilao que no processada quando um bloco executado. Ele direciona o compilador do PL/SQL para interpretar todas
Elaborado por Josinei Barbosa da Silva

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)

Erros definidos pelo usurio


Um usurio pode levantar explicitamente uma exceo usando o comando RAISE. Esse procedimento deve ser usado apenas quando a Oracle no levanta sua prpria exceo ou quando o processamento no desejado ou impossvel de ser completado. As etapas para levantar e tratar um erro definido pelo usurio, so os seguintes: 1. Declarar o nome para a exceo de usurio dentro da seo de declarao do bloco; 2. Levantar a exceo explicitamente dentro da parte executvel do bloco, usando o comando RAISE; 3. Referenciar a exceo declarada com uma rotina de tratamento de erro. O exemplo seguinte ilustra o uso da exceo definida pelo usurio: 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

Elaborado por Josinei Barbosa da Silva

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;

Elaborado por Josinei Barbosa da Silva

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;

Elaborado por Josinei Barbosa da Silva

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)

Regras de escopo de exceo


Voc precisa conhecer as orientaes abaixo sobre o escopo das declaraes:
Uma exceo no pode ser declarada duas vezes no mesmo bloco, mas a mesma exceo pode ser declarada em dois blocos diferentes; As excees so locais ao bloco onde elas foram declaradas e globais a todos os sub-blocos do bloco. Os blocos includos (bloco pai) no podem referenciar as excees que foram declaradas em nenhum de seus sub-blocos; As declaraes globais podem ser declaradas de novo no nvel do sub-bloco local. Se isso ocorrer, a declarao local tem precedncia sobre a declarao global.

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.

Elaborado por Josinei Barbosa da Silva

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.

Elaborado por Josinei Barbosa da Silva

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.

Elaborado por Josinei Barbosa da Silva

Pgina 39 de 154

Treinamento

Cenrio para prtica


At agora vimos muitas explicaes e exemplos, mas praticamos pouco. Nos prximos captulos precisaremos exercitar o que for visto e, de preferncia, num ambiente de teste que simule algum sistema, mesmo que simplrio. Para facilitar isso, usaremos um modelo de dados de um sistema de vendas bem simples, que no chega a normalizao ideal, porm, atende perfeitamente s nossas necessidades.

Modelo lgico de entidade e relacionamento


Abaixo segue o nosso modelo lgico de entidade e relacionamento:

Figura 18: Modelo Lgico de Entidade Relacionamento do sistema de vendas Elaborado por Josinei Barbosa da Silva

Pgina 40 de 154

Treinamento

Modelo fsico de entidade e relacionamento


Para entendermos como as entidades se relacionam, o modelo lgico a melhor opo, pois fornece uma representao mais limpa, porm, quando precisamos de detalhes de implementao fsica, como o tipo de uma coluna para elaborar uma instruo SQL, a melhor representao o modelo fsico de entidade e relacionamento, como na figura a seguir:

Figura 19: Modelo Fsico de Entidade Relacionamento do sistema de vendas

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.

Elaborado por Josinei Barbosa da Silva

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.

Elaborado por Josinei Barbosa da Silva

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;

Elaborado por Josinei Barbosa da Silva

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.

Elaborado por Josinei Barbosa da Silva

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.

Criando um loop REPEAT...UNTIL


Em PL/SQL no existe um loop do tipo REPET...UNTIL incorporado, entretanto, voc pode simular um usando o loop simples e as declaraes EXIT ou EXIT WHEN. A sintaxe de um loop REPEAT...UNTIL, em PL/SQL, seria da seguinte forma: LOOP; <Declaraes> IF <condicao> THEN EXIT; END IF; END LOOP; Alternativamente voc pode usar o mtodo mais comum, que : LOOP; <Declaraes> EXIT WHEN <condio>; END LOOP;

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

Qual loop devo usar?


Todas essas opes de loops podem causar confuso! Como foi visto nos exemplos, voc pode usar as declaraes FOR, WHILE e LOOP para criar a mesma sada. Entretanto, a Tabela 5 mostra algumas orientaes gerais sobre quando usar qual tipo de loop.
Tabela 4:Tipo de Loop a ser usado

Loop LOOP (Simples)

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

Elaborado por Josinei Barbosa da Silva

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:

Figura 20: 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.

Um cursor pode ser qualquer comando SELECT, incluindo joins e outros.

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;

Elaborado por Josinei Barbosa da Silva

Pgina 49 de 154

Treinamento

Coloca valores nas variveis de input obtendo seus endereos de memria;

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

Posiciona o ponteiro antes da primeira linha do conjunto ativo.

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.

Recuperando dados de um Cursor


O comando FETCH recupera linhas da consulta associada ao cursor. Para cada comando FETCH executado, o cursor avana para a prxima linha no conjunto ativo. Sintaxe: FETCH nome_cursor INTO [varivel1, varivel2, ...] / nome_registro]; Onde:

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:

A clusula INTO deve conter:


uma varivel para cada coluna retornada pelo comando FETCH, na ordem correspondente das colunas retornadas e com o mesmo tipo retornado; Ou um Tipo Registro com o mesmo nmero de colunas do registro retornado (e do mesmo tipo das colunas do cursor;

Ou uma varivel declarada com %ROWTYPE baseada no prprio cursor.

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;

O comando FETCH s pode ser executado se o Cursor estiver aberto.

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

Elaborado por Josinei Barbosa da Silva

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

END LOOP; -- Fechar cursor CLOSE cs_representante; END; /

Atributos de cursores explcitos


A PL/SQL disponibiliza quatro atributos de cursor que so muito teis, como mostrado na tabela a seguir:
Tabela 5: Atributos de cursor explcito

Excees %rowcount %found %notfound %isopen

Descrio Mostra o nmero Retorna TRUE se Retorna TRUE se Retorna TRUE se

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

Elaborado por Josinei Barbosa da Silva

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.

Cursores, registros e o atributo %ROWTYPE


Ao invs de declararmos uma varivel para cada coluna retornada por um cursor, podemos definir registros com a mesma estrutura do cursor e declarar variveis do tipo desses registros. O mais prtico definir essa varivel de registro utilizando o atributo %ROWTYPE, mas claro que
Elaborado por Josinei Barbosa da Silva

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;

Elaborado por Josinei Barbosa da Silva

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.

Cursores explcitos automatizados (LOOP Cursor FOR)


Os LOOPs Cursor FOR so ideais quando voc quer fazer o LOOP em todos os registros retornados pelo cursor. Com o LOOP Cursor FOR voc no deve declarar o registro que controla o LOOP. Da mesma forma, voc no deve usar o LOOP Cursor FOR quando as operaes do cursor precisarem ser tratadas manualmente. Um LOOP Cursor FOR faz as seguintes coisas implicitamente: 1. Declara o ndice do LOOP; 2. Abre o cursor; 3. Faz o FETCH da linha seguinte a partir do cursor para cada iterao do LOOP; 4. Fecha o cursor quando todas as linhas so processadas ou quando o LOOP encerrado. Um LOOP Cursor FOR processa linhas em um cursor explcito. Isto uma facilidade por que o cursor aberto, linhas so carregadas em cada iterao do LOOP, o LOOP finalizado quando a ltima linha processada e o cursor fechado, automaticamente. Sintaxe: FOR nome_registro IN nome_cursor LOOP Comando1; Comando2; Comando3; END LOOP; Onde:
Elaborado por Josinei Barbosa da Silva

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.

Elaborado por Josinei Barbosa da Silva

Pgina 56 de 154

Treinamento

Passando parmetros para cursores


Podemos passar parmetros para um cursor previamente declarado. Isto significa que podemos abrir e fechar um cursor dentro de um bloco PL/SQL, retornando diferentes linhas no cursor em cada ocasio. Para cada execuo, o cursor anterior fechado e reaberto com um novo conjunto de parmetros. Esse recurso pode ser utilizado em cursores manipulados manualmente ou em cursores automatizados. Sintaxe: CURSOR nome_cursor [(nome_parametro tipo, ...)] IS Comando Select; Onde:

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

Atributos do cursor implcito


Assim como os cursores explcitos, os cursores implcitos tambm usam os atributos. Os atributos do cursor implcito so os mesmo do cursor explcito, ou seja:
Tabela 6: Atributos de cursor implcito

Excees %rowcount %found %notfound %isopen

Descrio Mostra o nmero Retorna TRUE se Retorna TRUE se Retorna TRUE se

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

Elaborado por Josinei Barbosa da Silva

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.

Elaborado por Josinei Barbosa da Silva

Pgina 60 de 154

Treinamento

Blocos annimos, procedimentos e funes


Em PL/SQL temos trs tipos de blocos: Blocos Annimos, procedimentos (Procedures) e funes ( Functions). Um bloco annimo, como j vimos (e como o prprio nome sugere), no nomeado e esta a nica diferena entre ele e os procedimentos e funes. Um procedimento um conjunto de declaraes SQL e PL/SQL (como um bloco annimo) agrupadas logicamente, que executam uma tarefa especfica. Ele um programa em miniatura. Abaixo temos uma figura que mostra a diferena de nomeao entre bloco annimo, procedure e function:

Figura 21: Bloco annimo, procedure e function

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;

Elaborado por Josinei Barbosa da Silva

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

Elaborado por Josinei Barbosa da Silva

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

Elaborado por Josinei Barbosa da Silva

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.

Elaborado por Josinei Barbosa da Silva

Pgina 64 de 154

Treinamento

03 Stored Procedures
1. 2.

Procedimentos armazenados; Pacotes;

Elaborado por Josinei Barbosa da Silva

Pgina 65 de 154

Treinamento

Procedimentos armazenados (Stored Procedure)


Um procedimento armazenado um procedimento que foi compilado e armazenado dentro do banco de dados. Depois de armazenado o procedimento um objeto de esquema, ou seja, um objeto especfico do banco de dados.

Por que usar os procedimentos?


Os procedimentos so criados para solucionar um problema ou tarefa especficos. Os procedimentos PL/SQL oferecem as seguintes vantagens:

Na PL/SQL voc pode personalizar um procedimento segundo seus requisitos especficos;

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;

Os procedimentos aproveitam os recursos de memria compartilhados.

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.

Procedimentos versus funes


Os procedimentos e as funes so sub programas PL/SQL que so armazenados no banco de dados. A diferena significativa entre os dois so apenas os tipos de sada dos dois objetos gerados. Uma funo retorna um valor simples, enquanto que um procedimento usado para executar processamento complicado quando voc quer ter de volta uma quantidade substancial de informaes. A seguir veremos um pouco mais sobre procedimentos e funes.

Elaborado por Josinei Barbosa da Silva

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;

nome_da_procedure indica o nome da procedure;

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:

As funes retornam um valor; As funes so usadas como parte de uma expresso.

A sintaxe bsica de uma funo : CREATE [OR REPLACE] FUNCTION [schema.]nome_da_funcao [(parmetro1 [modo1] tipodedado1,

Elaborado por Josinei Barbosa da Silva

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;

nome_da_funcao indica o nome da funo;

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

Manuteno de procedimentos armazenados


Para recompilar explicitamente um PROCEDURE, como no exemplo abaixo. procedimento armazenado, use o comando ALTER ALTER PROCEDURE pr_AtualizaMaiorVendaProduto COMPILE; (ou ALTER FUNCTION para funes armazenadas) O comando acima, como pode ser observado, no altera a declarao ou definio do procedimento. Para isso, voc deve usar o comando CREATE OR REPLACE [PROCEDURE | FUNCTION], junto com todo o cdigo do procedimento (da mesma forma que feito quando se cria o procedimento). Para excluir um procedimento armazenado, voc deve utilizar o comando DROP [PROCEDURE | FUNCTION]. A sintaxe do comando : DROP PROCEDURE | FUNCTION nome_do_procedimento; Onde:

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.

Segurana da invocao de procedimento


Ao executar um procedimento armazenado, o Oracle verifica se voc tem os privilgios necessrios para tanto. Se voc no tiver as permisses de execuo apropriadas, ocorre um erro. Uma vez tendo direito de execuo de um procedimento, ao execut-lo, o Oracle no verifica se voc tem direitos sobre os objetos referenciados pelo procedimento. O Oracle verifica se o proprietrio (OWNER) do procedimento tem permisses e isso durante a compilao do procedimento. Desta forma, a PL/SQL disponibiliza atravs dos procedimentos armazenados, um timo recurso para controle de permisso. Voc pode ter uma aplicao onde, apenas um usurio tem direito de incluir, alterar e excluir registros. Dentro do schema desse usurio voc desenvolve rotinas que executa essas operaes e ento, transmite o direito de executar os procedimentos aos usurio que desejar. Esses usurio s conseguiro alterar os registros atravs da rotina e voc poder aplicar regras internas no procedimento, como consistncias e gerao de logs.

Elaborado por Josinei Barbosa da Silva

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.

Vantagens do uso de pacotes


Os pacotes oferecem as seguintes vantagens:
Eles permitem organizar o desenvolvimento do seu aplicativo de forma mais eficiente com os mdulos. Cada pacote facilmente entendido e as interfaces entre os pacotes so simples, claras e bem definidas;

Eles permitem conceder os privilgios de forma eficiente;

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 permitem executar a sobrecarga nos procedimentos e funes;

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]

Elaborado por Josinei Barbosa da Silva

Pgina 72 de 154

Treinamento

END [package_name]; Nessa sintaxe, as palavras-chave e os parmetros so os seguintes:


package_name o nome do pacote que o criador define. importante dar-lhe um nome que seja significativo e represente o contedo do pacote;

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

Elaborado por Josinei Barbosa da Silva

Pgina 73 de 154

Treinamento

dado a package durante a criao da especificao;


package body object declaration o lugar no qual voc lista os objetos que sero criados dentro do pacote.

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.

Utilizando os subprogramas e variveis de um pacote


Quando um pacote invocado, o Oracle executa trs etapas para execut-lo: 1. Verifica o acesso de usurio Confirma se o usurio tem a concesso do privilgio de sistema EXECUTE; 2. Verifica a validade do pacote Verifica no dicionrio de dados e determina se o subprograma vlido. Se o objeto invlido, ele automaticamente recompilado antes de ser executado; 3. Executa O sub-programa invocado executado. Para referenciar os sub-programas e objetos do pacote, voc deve usar a notao de ponto, como abaixo:
package_name.type_name para referenciar um tipo definido na especificao de um pacote; package_name.variable_name para referenciar uma varivel declarada na especificao de um pacote; package_name.subprogram_name para invocar um procedimento declarado na especificao do pacote e implementado no corpo do pacote.

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

Elaborado por Josinei Barbosa da Silva

Pgina 75 de 154

Treinamento

vEndereco VARCHAR2(256); BEGIN pck_cliente.AtualizarMediaComprasMensal(10); vEndereco := pck_cliente.EnderecoCliente(10); END; /

Manuteno dos pacotes


Estados de um pacote
O pacote considerado invlido se o seu cdigo-fonte ou qualquer objeto que ele referencia foi excludo, alterado ou substitudo desde que a especificao do pacote foi recompilada pela ltima vez. Quando um pacote se torna invlido, o Oracle tambm torna invlido todo objeto que referencia o pacote.

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.

Elaborado por Josinei Barbosa da Silva

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

Uma definio de trigger DML consiste das seguintes partes bsicas:


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:

Elaborado por Josinei Barbosa da Silva

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

Tipos de triggers DML


Os triggers DML podem ser classificados de duas maneiras diferentes: quando so disparados (com relao declarao SQL) ou se eles disparam ou no para cada linha afetada pela declarao SQL de disparo (nvel do disparo). Combinando essas classificaes, temos quatro tipos de triggers DML. Quando os triggers so disparados? Existem duas opes de quando o trigger deve ser disparado: antes e depois. Os before triggers so executados antes do disparo da declarao SQL e os after triggers so executados depois do disparo da declarao SQL. Quais so os nveis do trigger? Existem dois possveis nveis para um trigger DML:

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:

INSERT UPDATE DELETE

Se combinarmos esses eventos aos nveis e o momento de execuo (quando), teremos 12 tipos de triggers DML.

Elaborado por Josinei Barbosa da Silva

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.

Ativando e desativando triggers


Os triggers podem ser temporariamente desativados sem precisar exclu-los e depois recri-los. Isso pode ser til quando voc precisar fazer algum processamento especial, como por exemplo, uma carga de dados. O comando ALTER TRIGGER usado para ativar e desativar triggers. Veja o exemplo abaixo: -- Desabilitar trigger ALTER TRIGGER rep_trg_iu DISABLE; -- Habilitar trigger ALTER TRIGGER rep_trg_iu ENABLE; Dica: Executar carga de dados com triggers desabilitados pode representar um grande ganho de desempenho, mas cada caso deve ser analisado, pois qualquer regra implementada nos triggers desabilitados, sero ignoradas.

Elaborado por Josinei Barbosa da Silva

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:

Um procedimento pblico que receba como argumento o cdigo do cliente e atualize:


O valor da maior compra (coluna CLI_VL_MAIORCOMPRA), obtida na tabela de nota fiscal de vendas; A data da maior compra (coluna CLI_DT_MAIORCOMPRA), obtida na tabela de nota fiscal de vendas; A mdia de compras mensal (coluna CLI_VL_MEDIACOMPRASMENSAL), obtida na tabela de resumo mensal de vendas.

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.

6. Crie um pacote chamado pck_representante que contenha:


Um procedimento pblico que receba como argumento o cdigo do representante e atualize:

Elaborado por Josinei Barbosa da Silva

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.

Elaborado por Josinei Barbosa da Silva

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

Elaborado por Josinei Barbosa da Silva

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:

Tabelas por ndice; Tabelas aninhadas; Arrays de tamanho variado (varray).

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.

Tabelas por ndice


As tabelas por ndice so um dos trs tipos de coleo suportados pela PL/SQL. Na verdade, elas so o tipo de coleo original. Nas primeiras verses da PL/SQL, as tabelas por ndices eram o nico tipo de coleo disponvel. Uma tabela por ndice uma tabela de elementos mantida em memria, onde cada elemento indexado por um valor de inteiro. As tabelas por ndice funcionam da mesma forma que os arrays, com algumas diferenas:

Uma tabela por ndice pode ser populada esparsamente; Voc no define um tamanho mximo para uma tabela por ndice.

Declarando uma tabela por ndice


Para declarar uma tabela por ndice voc deve: 1. Definir um tipo, especificando um datatype para a coleo e outro para o ndice da tabela, sendo que, o datatype da coleo pode ser um tipo escalar, tal como um NUMBER ou VARCHAR2, ou ele pode ser um tipo composto, tal como um registro e o datatype do ndice da tabela deve sempre ser BINARY_INTEGER. 2. Declarar uma varivel do tipo definido. A sintaxe para declarar um tipo para tabela de ndice o seguinte:
TYPE type_name IS TABLE OF data_type [NOT NULL] INDEX BY BINARY_INTEGER;

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.

Manipulando uma tabela por ndice


Inserindo entradas em uma tabela por ndice Os elementos de uma tabela por ndice so identificados exclusivamente por um valor de inteiro, ou ndice. Sempre que referencia um valor da tabela, voc deve fornecer o ndice daquele valor. Para inserir os valores em uma tabela PL/SQL, voc usa uma declarao de atribuio como esta: table_var(index) := value; Onde:

table_var o nome da varivel do tipo tabela por ndice;

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

value o valor a ser atribudo para a entrada.

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:

table_name o nome da varivel de tabela;

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;

ultima_entrada o ltimo ndice 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);

Elaborado por Josinei Barbosa da Silva

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.

Declarando uma tabela aninhada


Voc declara uma tabela aninhada usando as duas mesmas etapas que usa para declarar uma tabela por ndice. Em primeiro lugar, voc declara um tipo, e depois declara uma ou mais variveis daquele tipo. A sintaxe para criar o tipo a seguinte: TYPE type_name IS TABLE OF data_type [NOT NULL]; onde:

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.

Elaborado por Josinei Barbosa da Silva

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.

Manipulando tabelas aninhadas

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

Elaborado por Josinei Barbosa da Silva

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;

Elaborado por Josinei Barbosa da Silva

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

Elaborado por Josinei Barbosa da Silva

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.

Arrays de tamanho varivel


Assim como as tabelas aninhadas, os arrays de tamanho varivel ou varrays tambm comearam a existir a partir da verso Oracle8. Os arrays so semelhantes s tabelas aninhadas, mas eles possuem um tamanho mximo fixo. Eles diferem das tabelas aninhadas porque quando voc armazena um varray em uma coluna de banco de dados, a ordem dos elementos preservada.

Elaborado por Josinei Barbosa da Silva

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.

Declarando e inicializando um VARRAY


Para criar um varray, voc usa a palavra-chave VARRAY em uma declarao de tipo para criar um tipo de array. Depois voc pode usar aquele tipo para declarar uma ou mais variveis. A sintaxe para declarar um tipo varray a seguinte: TYPE nome_tipo IS VARRAY (size) OF tipo_entrada [NOT NULL]; Onde:

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.

Adicionando e removendo dados de um VARRAY


Depois de inicializar um VARRAY, voc pode adicionar e remover dados dele, assim como faz com uma tabela aninhada. Se voc quiser adicionar ao array mais elementos do que voc criou quando o inicializou, pode chamar o mtodo extend. Entretanto, voc s pode estender um array at o tamanho mximo especificado na definio do tipo array.
Elaborado por Josinei Barbosa da Silva

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

Elaborado por Josinei Barbosa da Silva

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.

Mtodos de tabela da PL/SQL


Ns j estudamos alguns mtodos de coleo nas sees anteriores. A PL/SQL fornece vrios outros mtodos incorporados para uso com as tabelas. Veja a tabela a seguir:
Tabela 6: Mtodos de tabela da PL/SQL

Mtodo count exist

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

Exemplo vRecordCount := cCli.count IF cCli.exists(15) THEN ... END IF;

IF vIndice > cli_array.limit THEN ... ELSE cli_array(vIndex) := 10; END IF;

vMenorIndiceValido := cCli.first;

vMaiorIndiceValido := cCli.last; vProximoIndice := cCli.Next(vIndiceCorrente)

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;

Elaborado por Josinei Barbosa da Silva

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.

Exemplo -- cortando 3 emtradas cli_array.trim(3);

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

Executando declaraes SELECT em uma coleo


Em PL/SQL voc consegue executar uma declarao SELECT em uma coleo, como se ela fosse uma tabela. Isso pode ser muito til se voc precisar filtrar registros de uma coleo ou executar loops constantes no mesmo cursor, por exemplo. Mas para realizar uma operao como essa, voc precisa: 1. Criar um objeto armazenado de banco de dados que contenha as colunas da coleo; 2. Criar um uma tipo (type) armazenado no banco de dados que seja uma tabela do objeto criado; A criao de um objeto como esse pode se parecer muito com a definio de tipo registro ( redord), como no exemplo abaixo: CREATE OR REPLACE TYPE ObjCliente AS OBJECT (cli_in_codigo INTEGER, cli_st_nome VARCHAR2(20), cli_ch_novo CHAR(1) ) / Estando o objeto criado, voc deve criar um tipo tabela desse objeto. O comando para isso quase igual a definio de um tipo tabela dentro de um bloco. A nica diferena o uso da palavra CREATE (ou CREATE OR REPLACE). Veja: CREATE OR REPLACE TYPE TCliente IS TABLE OF ObjCliente; / Por fim, dentro do seu bloco, voc s precisa declarar uma varivel do tipo criado e us-la como as colees que j vimos at aqui: DECLARE -- Declarao de variveis

Elaborado por Josinei Barbosa da Silva

Pgina 95 de 154

Treinamento

cCli BEGIN NULL; END; /

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

Elaborado por Josinei Barbosa da Silva

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

END CASE; END LOOP;

-- Quebrar linha dbms_output.put_line(''); -- Imprimir total de clientes novos SELECT COUNT(*) INTO vCount

Elaborado por Josinei Barbosa da Silva

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.

Criando um cursor explcito a partir de uma coleo


Outra maneira de utilizar uma coleo em uma instruo SQL, atribuindo seu retorno para uma varivel do tipo REF CURSOR e manipul-la como um curso explcito. Para isso basta definirmos um tipo REF CURSOR e declararmos uma varivel desse tipo. Depois s abrir o cursor referenciando a varivel definida, usando para isso a declarao SELECT feita na coleo. O exemplo abaixo semelhante ao anterior, porm, aps alimentarmos a coleo, abrimos um cursor baseado nessa coleo restringindo seu retorno aos clientes novos. Em seguido iniciamos um LOOP no cursor e imprimimos cada um dos clientes na tela. DECLARE /******** Declarao de cursores CURSOR cs_cliente IS SELECT c.* FROM cliente c; /******** Definio de tipos TYPE TCursor IS REF CURSOR; TYPE TRegistroCliente IS RECORD( Codigo INTEGER, Nome VARCHAR2(20), Novo CHAR(1) ); ********/

********/

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

Elaborado por Josinei Barbosa da Silva

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.

Usando BULK COLLECT


Voc pode usar as palavras-chave BULK COLLECT para ter os resultados de uma declarao SELECT colocados diretamente em uma coleo. Voc pode usar BULK COLLECT com as declaraes SELECT INTO e tambm com as declaraes FETCH. Por exemplo, se RepCodigos e RepNomes fossem ambas tabelas aninhadas, voc emitiria a seguinte declarao SELECT para gerar nelas uma entrada para cada representante existente na tabela representante: SELECT rep_in_codigo, rep_st_nome BULK COLLECT INTO RepCodigos, RepNomes FROM representante; Se voc tivesse um cursor chamado cs_representante que retornasse os mesmos dados, voc poderia escrever BULK COLLECT na declarao FETCH da seguinte maneira: OPEN cs_representante; FETCH cs_representante BULK COLLECT INTO RepCodigos, RepNomes; CLOSE cs_representante; At o Oracle 8i, por algum motivo, a Oracle no permitia que voc usasse BULK COLLECT em uma coleo de registros. Assim sendo, se voc selecionasse dez colunas, precisaria declarar dez colees, uma para cada coluna. A partir da verso 9r2 do Oracle Database, isso mudou, e passou a ser aceito o uso de colees de registro. Dessa forma, se tivssemos uma varivel chamada cRep que fosse de um tipo registro que contivesse uma coluna para cdigo e outra para nome, poderamos reescrever nossos dois exemplos acima, como abaixo:

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

Pgina 100 de 154

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:

Elaborado por Josinei Barbosa da Silva

Pgina 101 de 154

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

Pgina 102 de 154

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

Tratamento de excees para as colees


Algumas excees da PL/SQL esto relacionadas diretamente s colees. Elas esto listadas na tabelas abaixo:
Tabela 7: Excees relacionadas s colees

Exceo COLLECTION_IS_NULL NO_DATA_FOUND SUBSCRIPT_BEYOND_COUNT SUBSCRIPT_OUTSIDE_LIMIT VALUE_ERROR

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

Pgina 103 de 154

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.

Elaborado por Josinei Barbosa da Silva

Pgina 104 de 154

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

2. Na package pck_representante, crie uma funo pblica que:

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.

Elaborado por Josinei Barbosa da Silva

Pgina 105 de 154

Treinamento

05 Tpicos avanados: PL/SQL


1.

PL/SQL:
SQL Dinmico Stored Procedure com transao autnoma Funo PIPELINED
UTL_FILE: Escrita e leitura de arquivos no servidor

Elaborado por Josinei Barbosa da Silva

Pgina 106 de 154

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:

um ramo de atividade uma cidade de um ramo em uma cidade ou geral,

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

Pgina 107 de 154

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"

Pgina 108 de 154

Treinamento

,c.cli_st_cidade) FROM cliente c;

"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

Elaborado por Josinei Barbosa da Silva

Pgina 109 de 154

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.

SQL Dinmico em cursores


Tambm podemos criar cursores com SQL dinmico, concatenando valores ou usando variveis de ligao, como no exemplo abaixo: DECLARE vlCursor SYS_REFCURSOR; vlRamo cliente.cli_st_ramoatividade%TYPE := 'SUPERMERCADO'; vlCliente cliente%ROWTYPE; BEGIN OPEN vlCursor FOR 'SELECT * '|| 'FROM cliente c '|| 'WHERE c.cli_st_ramoatividade = :1' USING vlRamo; LOOP FETCH vlCursor INTO vlCliente; EXIT WHEN vlCursor%NOTFOUND; dbms_output.put_line('Cliente '||vlCliente.cli_st_nome); END LOOP; CLOSE vlCursor; END; /

Stored Procedure com transao autnoma


Pode existir situaes onde uma programa invoque outro programa e algo do segundo programa precise de um COMMIT independente do que acontea no primeiro. Por sua vez, o primeiro programa no pode ser afetado de forma alguma pelo COMMIT executado no segundo. Pelo que vimos at o momento, isso no seria possvel, pois um COMMIT no segundo programa tambm valeria para tudo que foi executado no primeiro at aquele momento. Resumindo, o que precisamos que o segundo programa tenha uma transao independente (autnoma). Para que isso seja possvel, o Oracle disponibiliza o PRAGMA AUTONOMOUS_TRANSACTION. Com esse recurso, voc poder implementar programas PL/SQL que possuem transao independente, ou seja, qualquer COMMIT ou ROLLBACK que ocorrer dentro desse programa, s valer para ele.

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

Pgina 110 de 154

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;

Elaborado por Josinei Barbosa da Silva

Pgina 111 de 154

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

Pgina 112 de 154

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

3. Na segunda janela de SQL, execute as duas consultas novamente e veja que:


4. Volte na janela do bloco annimo e execute o COMMIT;


Elaborado por Josinei Barbosa da Silva

Pgina 113 de 154

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.

Tabela Funo PL/SQL (PIPELINED)


Uma tabela funo PL/SQL, tambm chamada de funo pipelined, um recurso pouco conhecido do Oracle 9i. Funo pipelined um recurso PL/SQL para gerar registros em memria, como se fosse uma uma tabela virtual. A grande vantagem dessa modalidade a habilidade de devolver linhas dinamicamente a partir da gerao da mesma, ou seja, quando inclumos um registro nessa tabela virtual (atravs do comando PIPE ROW), ele a envia de volta ao cliente, antes de gerar a prxima, ao contrrio de um array PL/SQL, que constri todos os registros em batch antes de retorn-los ao cliente. Isso quer dizer que o recurso de memria pouco utilizado, pois no precisa segurar a informao. Dessa forma voc consegue eliminar a necessidade de armazenar dados que so apenas para auxili-lo na recuperao de outros dados, minimizando o uso do recurso de memria. Um uso comum para esse recurso para fazer carga de uma dimenso de tempo dentro de um Datawarehouse, onde voc precisa gerar vrias linhas de referncias aos anos e isso no tem uma origem exata. Suponha que voc em seu ambiente de trabalho se depare com um problema onde voc precise executar uma declarao SELECT que retorne o valor total de venda para cada ms de um determinado perodo, por exemplo, de 01/2006 a 12/2006:
Perodo 01/2006 02/2006 03/2006 04/2006 05/2006 06/2006 07/2006 08/2006 09/2006 10/2006 11/2006 12/2006 Valor 0 80000 20000 50000 60000 15000 100000 35000 40000 70000 0 87000

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

Elaborado por Josinei Barbosa da Silva

Pgina 114 de 154

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.

Montando uma funo PIPELINED


Se a funo retornar um conjunto de dados, primeiramente precisaremos criar no banco, essa estrutura que ser retornada, ou seja, no basta criar a funo. Para trabalhar com o PIPELINED necessrio: 1. Criar um objeto de banco de dados armazenado, contendo as colunas das quais voc precisar;

Elaborado por Josinei Barbosa da Silva

Pgina 115 de 154

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

Pgina 116 de 154

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.

Utilizando uma funo PIPELINED em uma declarao SQL


Para utilizar uma funo PIPELINED em uma instruo SQL to simples quanto usar as tabelas do banco de dados. A nica diferena, que precisamos fazer um typecasting da funo, como no exemplo abaixo: SELECT * FROM TABLE(pck_util.fnPeriodo(add_months(SYSDATE,-12),SYSDATE)) Neste exemplo, temos como retorno os ltimos 12 perodos mensais:

Elaborado por Josinei Barbosa da Silva

Pgina 117 de 154

Treinamento

Figura 22: Resultado do uso de uma funo PIPELINED

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

UTL_FILE: Escrita e leitura de arquivos no servidor


A linguagem PL/SQL em si no possui mecanismo para executar sada para arquivo ou tela. Entretanto, a Oracle fornece alguns pacotes incorporados que permitem executar a E/S e que podem ser chamados da PL/SQL. O pacote UTL_FILE o mais conhecido e mais simples de se utilizar e veremos as seguir como extrair informaes do banco de dados e gerar um arquivo com esses dados no sistema operacional do servidor, bem como ler um arquivo do sistema operacional e manipular seus dados. Existem dois pr-requisitos para usar o pacote UTL_FILE:

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;

Elaborado por Josinei Barbosa da Silva

Pgina 118 de 154

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.

Procedimentos e funes de UTL_FILE


Na tabela 8 esto os procedimentos e funes que encontramos no pacote UTL_FILE.
Tabela 8: Procedimentos e funes de UTL_FILE

Proocedure / Function FCLOSE

Descrio Fecha um arquivo. Excees levantadas


Exceo UTL_FILE.INVALID_FILEHANDLE Descrio Voc passou um handle de arquivo que no representava um arquivo aberto. O sistema operacional no pode gravar no arquivo. Um erro interno ocorreu.

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

Abre um arquivo. Excees levantadas


Exceo UTL_FILE.INVALID_PATH UTL_FILE.INVALID_MODE Descrio O diretrio no vlido Um modo invlido foi especificado. O modem open pode ser R (ler), W

Elaborado por Josinei Barbosa da Silva

Pgina 119 de 154

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

L uma linha de um arquivo e avana para a prxima. Excees levantadas


Exceo UTL_FILE.INVALID_FILEHANDLE Descrio Voc passou um handle de arquivo invlido. Possivelmente, voc esqueceu de abrir o arquivo primeiro. O arquivo no est aberto para leitura (modo R) ou existem problemas com as permisses do arquivo. O buffer no suficientemente longo para conter a linha que est sendo lida do arquivo. O tamanho do buffer aumentado. O final do arquivo foi atingido. Ocorreu um erro interno do sistema UTL_FILE. Ocorreu um erro do sistema operacional durante a leitura do arquivo.

UTL_FILE.INVALID_OPERATION

UTL_FILE.VALUE_ERROR

UTL_FILE.NO_DATA_FOUND UTL_FILE.INTERNAL_ERROR UTL_FILE.READ_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

Elaborado por Josinei Barbosa da Silva

Pgina 120 de 154

Treinamento

Gerando um arquivo com informaes extradas do banco de dados


Vamos criar na package pck_vendas, uma procedure que recupera o cadastro de produto do banco de dados e gerar um arquivo no servidor no formato CSV. Abaixo segue o cdigo da nova procedure: /* Procedure que exporta o cadastro de produtos para um arquivo CSV*/ PROCEDURE ExpProdutoCSV IS vlFile utl_file.file_type; CURSOR cs_produto IS SELECT p.pro_in_codigo ,p.pro_st_nome ,p.pro_st_marca ,(SELECT pp.ppr_vl_unitario FROM preco_produto pp WHERE pp.pro_in_codigo = p.pro_in_codigo AND pp.ppr_st_tipopreco = 'VENDAS' ) vl_preco_venda ,(SELECT pp.ppr_vl_unitario FROM preco_produto pp WHERE pp.pro_in_codigo = p.pro_in_codigo AND pp.ppr_st_tipopreco = 'COMPRAS' ) vl_preco_compra FROM produto p; BEGIN -- Abrir arquivo vlFile := utl_file.fopen('INTERFACE','CadProd-'||USER||'.csv','W'); -- Gravar cabealho utl_file.put_line(vlFile , '"Codigo",' ||'"Nome",' ||'"Marca",' ||'"Preco de Venda",' ||'"Preco de Compra"'); -- Gravar produtos no arquivo FOR csp IN cs_produto LOOP utl_file.put_line(vlFile , '"'||csp.pro_in_codigo ||'",' ||'"'||csp.pro_st_nome ||'",' ||'"'||csp.pro_st_marca ||'",' ||'"'||csp.vl_preco_venda ||'",' ||'"'||csp.vl_preco_compra||'"'); END LOOP; -- Fechar arquivo utl_file.fclose(vlFile); END ExpProdutoCSV; IMPORTANTE:

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

Elaborado por Josinei Barbosa da Silva

Pgina 121 de 154

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.

Recuperando informaes de um arquivo


Para testarmos a leitura de arquivos com UTL_FILE, implementaremos mais uma procedure na package pck_vendas. Essa procedure abrir o arquivo que geramos no exemplo anterior e exibir cada linha atravs do pacote DBMS_OUTPUT. Abaixo segue o cdigo: /* Procedure que importa o cadastro de produto de um arquivo CSV */ PROCEDURE ImpProdutoCSV IS vlFile utl_file.file_type; vlLinha VARCHAR2(1024); BEGIN -- Abrir arquivo vlFile := utl_file.fopen('INTERFACE','CadProd-'||USER||'.csv','R'); -- Recuperar cada linha do arquivo e exibir linha atravs do dbms_output LOOP BEGIN utl_file.get_line(vlFile,vlLinha); dbms_output.put_line(vlLinha); EXCEPTION WHEN no_data_found THEN EXIT; END; END LOOP; -- Fechar arquivo utl_file.fclose(vlFile); END ImpProdutoCSV; IMPORTANTE:

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;

Agora s executar e ver o resultado na tela.

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

Elaborado por Josinei Barbosa da Silva

Pgina 122 de 154

Treinamento

Elaborado por Josinei Barbosa da Silva

Pgina 123 de 154

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;

Elaborado por Josinei Barbosa da Silva

Pgina 124 de 154

Treinamento

06 Tpicos avanados: SQL e funes


incorporadas do Oracle Database
1.

SQLs avanados para usar com PL/SQL:


O Outer Join do Oracle ROWNUM Comando CASE no SELECT SELECT combinado com CREATE TABLE e INSERT MERGE GROUP BY com HAVING Recursos avanados de agrupamento: ROLLUP e CUBE Consultas hierrquicas com CONNECT BY

2.

Funes teis do Oracle Database

Elaborado por Josinei Barbosa da Silva

Pgina 125 de 154

Treinamento

SQLs avanados para usar com PL/SQL


O outer join do Oracle
Um OUTER JOIN um join simples (chamado de INNER JOIN) com uma "opo estendida " de trazer tambm os dados que no satisfazem a condio do join, ou seja, em um join entre duas tabelas, voc tem a opo de trazer os dados, mesmo que no exista um registro correspondente em uma das tabelas. Para exemplificar, vejamos a seguinte situao: Elaborar uma SQL que traga os produtos da marca Brahma com o resumo mensal de quantidade vendida (ano e ms), existindo ou no o resumo de vendas do produto. A SQL abaixo, que est no padro ANSI92 (tambm chamada de SQL92), nos d essa soluo: 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 LEFT OUTER JOIN resumo_mensal_venda rmv ON (p.pro_in_codigo = rmv.pro_in_codigo) WHERE 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; O LEFT indica que a tabela da esquerda (produto) a base para o OUTER JOIN, ou seja, todos os registros da tabela PRODUTO que atenderem a clusula WHERE devem ser retornados, mesmo que no exista resumo de vendas correspondente. At a, tudo bem! Esse padro de outer join definido pela ANSI92 e, portanto, vale para qualquer banco de dados. O problema que esse padro veio depois que os SGBDs do mercado j haviam implementado suas prprias solues para os outer joins. No SQL Server, por exemplo, para montar o outer join, bastava incluir o caracter * junto do operador = do lado da tabela base da consulta, ou seja, o nosso exemplo teria as clusulas FROM e WHERE um pouco diferente: FROM produto p, resumo_mensal_venda rmv WHERE p.pro_in_codigo *= rmv.pro_in_codigo AND p.pro_st_marca = 'Brahma' A leitura na implementao do SQL Server bem prxima do padro ANSI, pois o * est do lado da tabela base que no nosso exemplo LEFT. No entanto o padro ANSI92 pode gerar algumas confuses para quem se habituou com a implementao de outer join da Oracle. Diferente do seu concorrente, a implementao da Oracle indica a tabela que pode no haver dados correspondentes, ao invs da tabela base. Essa indicao feita pelo operador (+) e adicionado na associao das tabelas, mas direita da coluna da tabela que pode no ter registros, como no exemplo abaixo:

Elaborado por Josinei Barbosa da Silva

Pgina 126 de 154

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

Elaborado por Josinei Barbosa da Silva

Pgina 127 de 154

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;

Comando CASE no SELECT


Por muito tempo, o comando CASE foi exclusividade da linguagem PL/SQL e para reproduz-lo em SQLs utilizmos a funo decode(), mas nas ltimas verses do Oracle Database, passamos a contar com esse comando tambm nas SQLs. O seu uso em SQLs simples e recomendado, pois melhor para o desempenho do SQL do que a funo decode (embora seja um ganho mnimo). Abaixo segue um exemplo de uso do CASE em SQLs: SELECT nfv.nfv_in_numero ,nfv.nfv_dt_emissao ,r.rep_st_nome ,c.cli_st_nome ,c.cli_st_cidade ,(CASE WHEN nfv.nfv_st_condicaopagamento = 'AVISTA' THEN 'A Vista' WHEN nfv.nfv_st_condicaopagamento LIKE '%COMJUROS' THEN 'Parcelado com juros' WHEN nfv.nfv_st_condicaopagamento LIKE '%SEMJUROS' THEN 'Parcelado sem juros' END ) nfv_st_condicaopagamento FROM nota_fiscal_venda nfv ,cliente c ,representante r WHERE nfv.cli_in_codigo = c.cli_in_codigo AND nfv.rep_in_codigo = r.rep_in_codigo;

SELECT combinado com CREATE TABLE e INSERT

Elaborado por Josinei Barbosa da Silva

Pgina 128 de 154

Treinamento

CREATE TABLE AS SELECT


possvel criar uma tabela a partir de outra, combinando os comandos CREATE TABLE e SELECT. Essa combinao um comando DDL (Definition Data Language) e como tal s pode ser executado em um bloco PL/SQL atravs de SQL Dinmico. No uma prtica recomendada, mas pode ser til em alguma situao onde voc precise, por exemplo, criar um backup de uma tabela antes de executar o processamento. Outro uso bem comum quando voc precisa criar uma cpia de uma tabela sem dados, como no exemplo abaixo: CREATE TABLE simula_novo_preco_produto AS SELECT * FROM preco_produto WHERE 1=2;

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

Pgina 129 de 154

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

Pgina 130 de 154

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

GROUP BY com HAVING


Voc j deve saber que o GROUP BY utilizado para aplicar funes de grupo e dessa forma obter totais, mdias, valores mximos e mnimos. Existem situaes onde voc precisa aplicar uma restrio de retorno no valor agrupado ( soma ou mdia, por exemplo). Se voc tentar aplicar essa restrio na clusula WHERE receber um erro, pois nela as restries so em nvel de linha e no de valores agrupados. Se voc quiser recuperar do banco de dados o total de vendas do ano por marca de produto, no poder usar a SQL abaixo (tente): 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 AND sum(rmv.rmv_vl_total) > 50000 GROUP BY p.pro_st_marca ,rmv.rmv_in_ano Se tentou, deve ter recebido o erro ORA-00934: a funo de grupo no permitida aqui do Oracle. Para aplicar uma restrio em valor agrupado, voc deve utilizar a clusula HAVING aps o conjunto

Elaborado por Josinei Barbosa da Silva

Pgina 131 de 154

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.

Recursos avanados de agrupamento: ROLLUP e CUBE


ROLLUP e CUBE so extenses do GROUP BY, normalmente utilizadas em sistemas de apoio a deciso, como Datawarehouse. A seguir veremos como utilizar cada um deles.

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

Pgina 132 de 154

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.

Consultas hierrquicas com CONNECT BY


O CONNECT BY um recurso do Oracle para recuperarmos uma consulta em ordem hierrquica, como por exemplo, em um cadastro de funcionrios onde um funcionrio pode ser gerente de outro. Para estudarmos esse recurso, vamos alterar a nossa tabela de representante para informar o gerente de cada representante. Abaixo segue o script para preparar a tabela: -- incluir coluna ALTER TABLE representante ADD(rep_in_codigo_gerente INTEGER) / -- registrar o gerente de cada representante UPDATE representante r SET r.rep_in_codigo_gerente = 40 WHERE r.rep_in_codigo = 10 / UPDATE representante r SET r.rep_in_codigo_gerente = 50 WHERE r.rep_in_codigo IN(20,30) / COMMIT /

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

"Codigo" "Nome" "Cod.Gerente"


Pgina 133 de 154

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;

Funes incorporadas do Oracle Database


Existem muitas funes incorporadas do Oracle Database que podem otimizar o seu trabalho, tanto em tempo de desenvolvimento quanto desempenho do que voc produzir. Em geral essas funes podem ser usadas em cdigos PL/SQL e declaraes SQL. impossvel memorizar todas essas funes, mas a seguir, listamos algumas das mais comuns e
Elaborado por Josinei Barbosa da Silva

Pgina 134 de 154

Treinamento

mais teis:
Tabela 9: Funes de caractere incorporadas do Oracle

Funo CHR ASCII INITCAP

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.

Tabela 10: Funes numricas incorporadas do Oracle

Funo ABS CEIL

EXP LN MOD ROUND SQRT TRUNC

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.

Tabela 11: Funes de data incorporadas do Oracle

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.

Elaborado por Josinei Barbosa da Silva

Pgina 135 de 154

Treinamento

Funo NEXT_DAY SYSDATE TRUNC

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.

Tabela 12: Funes de converso incorporadas do Oracle

Funo TO_CHAR TO_DATE TO_NUMBER

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.

Tabela 13: Funes diversas incorporadas do Oracle

Funo DECODE GRATEST NVL

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.

Elaborado por Josinei Barbosa da Silva

Pgina 136 de 154

Treinamento

07 Dicas de performance e boas prticas em SQL


1. 2. 3. 4. 5. 6. 7.

Otimizador do Oracle; Variveis de ligao (Bind Variables); SQL Dinmico; EXPLAIN PLAIN; SQLs Complexas; SORT (Ordenao nas SQLs); etc...

Elaborado por Josinei Barbosa da Silva

Pgina 137 de 154

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:

RBO (Ruled Based Optimizer): Otimizador baseado em regra;

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.

Otimizador baseado em regra (RBO)


O RBO utiliza uma srie de regras rgidas para determinar um plano de execuo para cada SQL. Se voc conhecer as regras voc pode construir uma consulta SQL para acessar os dados da maneira desejada. S pra exemplificar, quando voc criava um ndice na sua tabela e monta uma SQL para acess-la, filtrando as colunas do ndice criado, sendo o otimizador RBO, com certeza ele utilizaria esse ndice para acesso. como se obedecesse uma ordem sua. O problema que o simples fato de existir um ndice que corresponde ao seu filtro, no quer dizer que ele seja seletivo e se no for, causa efeito contrrio. Talvez seja mais eficiente acessar toda a tabela (o famosso TABLE ACCESS FULL) do que acessar um ndice a procura de um ponteiro. O RBO deixou de ser aperfeioado na verso 9i do Oracle Database, quando o CBO, que veremos a seguir, passou a ser o otimizador recomendado.

Elaborado por Josinei Barbosa da Silva

Pgina 138 de 154

Treinamento

No Oracle 10g foi descontinuado.

Otimizador baseado em custo (CBO)


Introduzido no Oracle 7, o CBO tentar achar o plano de execuo que possui o menor custo para acessar os dados tanto para uma quantidade de trabalho especifica como para um tempo inicial de resposta mais rpido. Os Custos de diferentes planos so calculados e a opo que apresentar o menor custo de execuo escolhida. So coletadas estatsticas referentes s tabelas do banco de dados e estas so usadas para determinar um plano de execuo timo. A fonte de informaes do otimizador CBO so as estatsticas coletadas no banco de dados e essa uma das razes que levam o Oracle 10g a adotar a coleta de estatsticas automtica, uma vez que s trabalha com o otimizador CBO. IMPORTANTE: Um banco de dados configurado para trabalhar com CBO que no tiver uma poltica de atualizao peridica de estatsticas, provavelmente ser vtima de desempenho ruim.

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

Variveis de ligao (Bind Variables)


As variveis de ligao (bind) permitem que uma instruo SQL seja preparada uma nica vez pelo banco de dados e executada inmeras vezes, mesmo com valores diferentes para estas variveis. Esta economia na fase de preparao a cada execuo representa um ganho de eficincia (tempo e recursos) na aplicao e no servidor de banco de dados. Alm disso, variveis de ligao facilitam a validao de tipo de dados dos valores de entrada fornecidos dinamicamente e evitam os riscos de vulnerabilidade de segurana e integridade existentes quando se constri uma instruo SQL por concatenao de strings. Assim, este recurso trs tambm robustez e segurana execuo de SQL nas aplicaes. Por exemplo, imagine que voc precise buscar de dentro de uma aplicao Delphi a descrio de um determinado produto a partir de um cdigo informado. Uma funo (em Delphi) como abaixo pode resolver
Elaborado por Josinei Barbosa da Silva

Pgina 139 de 154

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

Pgina 140 de 154

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

Pgina 141 de 154

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

Pgina 142 de 154

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.

Colunas indexadas no ORDER BY


O otimizador do Oracle ir utilizar uma varredura por ndice se a clausula ORDER BY possuir uma coluna indexada. Uma consulta usar o ndice criado para uma coluna, mesmo que a coluna no seja especificada na clausula WHERE. A consulta obter o ROWID para cada linha do ndice e acessar a tabela usando o ROWID.

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

Elaborado por Josinei Barbosa da Silva

Pgina 143 de 154

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

Pgina 144 de 154

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

Elaborado por Josinei Barbosa da Silva

Treinamento

custo total da declarao;


Card indica a cardinalidade em cada ponto do plano. Entenda como cardinalidade o nmero de relacionamentos para recuperar os dados;

Bytes indica o nmero de bytes envolvidos em cada ponto do plano;

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

client: Nmero total de mensagens enviadas e

sorts (memory): Nmero de ordenaes em memria;

sorts (disk): Nmero de ordenaes em disco. Isso muito ruim para o desempenho pois executa I/O;

rows processed: Nmero de linhas processadas na operao.

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

Elaborado por Josinei Barbosa da Silva

Pgina 146 de 154

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 clusula WHERE crucial!


Informe o mximo de restries possveis na clausula WHERE, pois isso diminuir o universo dos dados a serem recuperados, mas tome cuidado com algumas situaes onde o uso de restries pode ser prejudicial, principalmente para a utilizao de ndices. As seguintes clusulas no WHERE no faro uso do ndice mesmo que ele esteja disponvel:

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

Elaborado por Josinei Barbosa da Silva

Pgina 147 de 154

Treinamento

ele existir. No exemplo acima, o uso da funo UPPER vai impedir do ndice ser usado.

Use o WHERE ao invs do HAVING para filtrar linhas


Evite o uso da clausula HAVING junto com GROUP BY em uma coluna indexada. Neste caso o ndice no utilizado. Alm disso, exclua as linhas indesejadas na sua consulta utilizando a clausula WHERE ao invs do HAVING. Se a tabela produto possusse um ndice na coluna pro_st_marca, a seguinte consulta no faria uso dele: SELECT p.pro_st_marca, AVG(p.pro_vl_maiorvenda) pro_vl_mediamaiorvenda FROM produto p GROUP BY p.pro_st_marca HAVING p.pro_st_marca = 'Kaiser'; A mesma instruo poderia ser reescrita da seguinte maneira para aproveitar o ndice: SELECT p.pro_st_marca, AVG(p.pro_vl_maiorvenda) pro_vl_mediamaiorvenda FROM produto p WHERE p.pro_st_marca = 'Kaiser' GROUP BY p.pro_st_marca;

Especifique as colunas principais do ndice na clusula WHERE


Em um ndice composto a consulta apenas utilizar o ndice se as principais colunas do ndice estiverem especificada na clausula WHERE. Se voc possui um ndice na tabela de item nota fiscal cujas colunas so NFV_IN_NUMERO e INFV_IN_NUMERO, o uso de NFV_IN_NUMERO na clusula WHERE pode levar ao uso do ndice. O mesmo no acontece se apenas INFV_IN_NUMERO for utilizada. Obviamente que a utilizao das duas colunas (de preferencia na ordem de criao do ndice) levar ao uso do ndice.

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.

Cuidado com Produto Cartesiano


Produto cartesiano a combinao de dois conjuntos, de forma que resulte em um terceiro conjunto constitudo por todos os elementos do primeiro combinados com todos os elementos do segundo. Isso o que acontece quando voc declara em sua SQL duas tabelas ou mais e no implementa o join correto entre elas. Como no exemplo abaixo: SELECT c.cli_in_codigo ,c.cli_st_nome ,nfv.nfv_in_numero ,nfv.nfv_dt_emissao ,nfv.nfv_st_condicaopagamento ,nfv.nfv_vl_total FROM cliente c ,nota_fiscal_venda nfv;

Elaborado por Josinei Barbosa da Silva

Pgina 148 de 154

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.

Quando usar MINUS, IN e EXISTS


Em vrios casos, mais de um SQL pode lhe trazer o resultado desejado. Cada um deles pode ter um plano de execuo diferente e assim se comportar de forma diferente. MINUS O operador MINUS, por exemplo, pode ser muito mais rpido do que usar WHERE NOT IN ou WHERE NOT EXISTS. Vamos dizer que tenhamos um ndice na coluna cli_st_uf e outro ndice na coluna cli_st_ramoatividade. Desconsiderando a disponibilidade de ndices, a consulta a seguir vai requerer um FULL TABLE SCAN devido ao uso do predicado NOT IN: SELECT cli_in_codigo FROM cliente WHERE cli_st_uf IN ('SP', 'RJ') AND cli_st_ramoatividade NOT IN ('SUPERMERCADO','MERCADO'); Entretanto se a mesma query for escrita da seguinte forma vai resultar em uma varredura por ndice (INDEX SCAN): SELECT cli_in_codigo FROM cliente WHERE cli_st_uf IN ('SP', 'RJ') MINUS SELECT cli_in_codigo FROM cliente WHERE cli_st_ramoatividade IN('SUPERMERCADO','MERCADO'); EXISTS A funo EXISTS procura pela presena de uma nica linha que satisfaz o critrio de pesquisa ao contrrio do comando IN que procura por todas as ocorrncias. Por exemplo, vamos supor que nossa
Elaborado por Josinei Barbosa da Silva

Pgina 149 de 154

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.

EXISTS ao invs de DISTINCT


Sempre que puder, Use o EXISTS ao invs do DISTINCT se voc desejar que o resultado contenha apenas valores distintos ao fazer a juno de tabelas. Por exemplo: SELECT DISTINCT r.rep_in_codigo, r.rep_St_nome FROM representante r, nota_fiscal_venda n WHERE r.rep_in_codigo = n.rep_in_codigo; A declarao abaixo uma alternativa melhor: SELECT r.rep_in_codigo, r.rep_St_nome FROM representante r WHERE EXISTS(SELECT n.rep_in_codigo FROM nota_fiscal_venda n WHERE r.rep_in_codigo = n.rep_in_codigo);

UNION e UNION ALL


Elaborado por Josinei Barbosa da Silva

Pgina 150 de 154

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.

Cuidados ao utilizar VIEWs


Joins em VIEWs complexas
Joins em VIEWs complexas no so recomendados e devem ser evitados quando possve,l especialmente em joins entre duas ou mais VIEWs complexas. Quando trabalhamos com joins entre VIEWs complexas, as mesmas tm que ser instanciadas primeiramente em memria e depois a consulta efetuada em cima dos dados resultantes das VIEWs. Deu pra imaginar o custo?

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

Aplicativos versus Banco de Dados


As aplicaes devem tentar acessar os dados apenas uma vez. Isto reduz o trafego de informaes na rede alm da carga no banco de dados. Considere as seguintes tcnicas:

Utilize o comando CASE para combinar mltiplas varreduras:


Elaborado por Josinei Barbosa da Silva

Pgina 151 de 154

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

Pgina 152 de 154

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

Use a clausula RETURNING em declaraes DML:


Quando apropriado, use INSERT, UPDATE ou DELETE com a clusula RETURNING para selecionar e modificar dados com uma nica chamada. Esta tcnica aumenta a performance diminuindo o numero de chamadas ao banco de dados. O RETURNING pode te retornar um dos valores que foram alterados. Por exemplo, voc inclui uma nova nota fiscal em seu sistema e aps a gravao precisa informar o nmero da nota gravada para o usurio. Ao invs de executar uma SELECT depois do INSERT s para recuperar esse nmero, voc inclui no seu INSERT a clausula RETURNING para retornar o valor gravado na coluna de nmero.

Elaborado por Josinei Barbosa da Silva

Pgina 153 de 154

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.

Elaborado por Josinei Barbosa da Silva

Pgina 154 de 154