Baixe o app para aproveitar ainda mais
Prévia do material em texto
USING COLLECTIONS AND RECORDS (PARTE 02) – NESTED TABLES Do que se trata este artigo Trata-se de um artigo de como utilizar as coleções e as variáveis de registros do PL/SQL. Em que situação o tema é útil: Esta é uma técnica de programação muito utilizada nos casos em que o desenvolvedor necessita armazenar uma coleção de dados, como uma tabela virtual, por exemplo. Alocando um espaço na memória do servidor e acessando-o posteriormente, podendo tornar o próprio desenvolvimento mais eficiente e mais eficaz. E tratando-se de um espaço na memória e não no disco (seja ele rígido ou sólido), dispensando a necessidade de gravação, leitura e limpeza do espaço no disco, onde é muito mais ineficiente (mesmo sendo um disco SSD) comparando com o desempenho que se obtém pela memória. No artigo anterior iniciamos uma sequência de três artigos, onde iremos abordar os tipos de coleções do PL/SQL, sendo eles: VARRAY – matrizes e vetores -, NESTED TABLES - Tabelas Aninhadas - e Arrays associativos. Nesta continuação da série de artigos, iremos abordar o conceito de NESTED TABLES - Tabelas Aninhadas - no PL/SQL. Uma tabela aninhada nada mais é do que uma tabela incorporada dentro de outra, onde você pode inserir, atualizar e excluir elementos (e não registros, lembre-se sempre disto) de forma individual em uma tabela aninhada, isto é, as tabelas aninhadas são flexíveis, ao contrário de um VARRAY, onde os elementos ali contidos podem apenas serem modificados como um todo e devem conter um número fixo de elementos. Uma tabela aninhada não possuí um tamanho máximo de elementos e o desenvolvedor pode armazenar um número de elementos conforme a sua necessidade. (PRICE, 2009) As linhas de uma tabela aninhada são armazenadas sem nenhuma ordem específica, mas quando você recupera as informações de uma tabela aninhada em uma variável PL/SQL, as linhas recebem subscritos consecutivos começando em 1, isto torna a tabela aninhada uma matriz a linhas individuais. Inicialmente as tabelas aninhadas são densas, assim como as matrizes, que não é possível excluir elementos individuais de uma matriz, mas no caso das tabelas aninhadas é possível efetuar esta exclusão de elementos de forma individual, isto torna a tabela aninhada dinâmica, onde, claro que deixará lacunas nos seus índices, mas o Oracle entenderá que o próximo elemento da tabela estará no próximo índice preenchido com algum elemento, ou seja, mesmo estando nulo (null) o elemento o Oracle percorrerá o índice para próximo, e assim sucessivamente até encontrar o próximo índice preenchido por algum elemento que não esteja nulo (null). (MOORE, 2009) Conforme a documentação do Oracle, existem algumas vantagens e desvantagens em se utilizar uma tabela aninhada, comparado com as VARRAYS e Matrizes Associativas, quando: · Uma tabela aninhada pode ser armazenada em uma coluna do banco de dados, ou seja, você pode usar a tabela aninhada para simplificar as operações em SQL que ingressar em uma tabela de coluna única com uma tabela maior. · Os valores do índice não são consecutivos, ou possa possuir valores nulos dentro dos elementos dos índices; · Não há um número definido de valores de índice, ou seja, não possuí limite de tamanho e não há a necessidade de fixar o tamanho da tabela; · Deve-se excluir ou atualizar alguns elementos, mas não todos os elementos ao mesmo tempo, ao contrário do VARRAY; · Poderia ser criado uma tabela de pesquisa separada, com várias entradas para cada linha da tabela principal, e acessá-lo através de consultas de associação; · Os dados de uma tabela aninhada são armazenados em uma tabela de armazenamento, uma tabela gerada pelo próprio sistema do banco de dados. Quando é efetuado o acesso há uma tabela aninhada, o banco de dados junta a tabela aninhada com sua tabela de armazenamento, tornando-as próprias para as consultas e atualizações que afetam apenas alguns elementos da coleção; · A desvantagem de uma tabela aninhada é que, não se pode confiar na ordem dos índices de uma tabela aninhada, isto porque, pode-se excluir e incluir novos índices de forma dinâmica, e o banco de dados não preserva a ordem dos elementos. (MOORE, 2009) Os comandos que podem ser utilizados para as NESTED TABLES são os mesmos que para os VARRAYS, veja abaixo a mesma tabela que foi mostrada no artigo anterior (apenas para recordar): (BURLESON, 2008) COUNT Retorna o número de elementos de um VARRAY. EXISTS Retorna um valor booleano verdadeiro se o elemento no índice especificado exista, caso contrário retornará falso. EXTEND Aumenta o tamanho da matriz por 1 ou pelo número especificado. Não pode ser usado em matrizes associativas. FIRST Navega para o primeiro elemento da matriz. LAST Navega para o último elemento da matriz. NEXT Navega para o próximo elemento da matriz. PRIOR Navega para o elemento anterior da matriz. TRIM Remove o último elemento da matriz. Não pode ser usado em matrizes associativas. DELETE Remove todos os elementos de uma matriz. LIMIT Mostra o limite de tamanho de uma matriz. O comando abaixo, é criado uma NESTED TABLE do tipo VARCHAR2 com o nome de TABLE_NAMES, e acrescentado alguns nomes para efetuarmos os testes e mostrar o exemplo dos nomes listados dentro de uma tabela aninhada, e mostrará como se fosse uma matriz mesmo, com um índice para o acesso. EXEMPLO 01: SET SERVEROUTPUT ON; DECLARE TYPE TABLE_NAMES IS TABLE OF VARCHAR2(30); vNome TABLE_NAMES := TABLE_NAMES('LEANDRO MIOZZO BONATO', 'JOÃO DA SILVA SAURO', 'FULANO', 'BELTRANO'); BEGIN DBMS_OUTPUT.PUT_LINE('NOME É: ' || vNome(1)); END; RESULTADO 01: bloco anônimo concluído NOME É: LEANDRO MIOZZO BONATO A variável “vNome” recebe uma serie de nomes de uma tabela aninhada TABLE_NAMES e o comando acima apenas imprimiu a qual é o nome posicionado no índice 1, lembrando que os índices em tabelas aninhadas se iniciam sempre na posição 1 e nunca em 0. Se executarmos este mesmo comando, porém substituindo a sua posição de 1 para 0, ocorrerá o seguinte erro: RESULTADO 02: ORA-06532: Subscrito além do limite Isto porque o limite de quantidade de valores não pode ser menor que 1. Mas há outro tipo de erro de índices quando utilizamos tabelas aninhadas, que é quando o índice ultrapassa a quantidade de elementos disponíveis na tabela, e ocorre o seguinte erro: RESULTADO 03: ORA-06533: Subscrito acima da contagem Mas quando o comando DELETE, para deletamos um dos elementos, também não possuiremos acesso ao mesmo, pois o registro não será encontrado, ocorrerá a famosa exceção de NO_DATA_FOUND. Abaixo o comando executado seguido de seu resultado: EXEMPLO 02: SET SERVEROUTPUT ON; DECLARE TYPE TABLE_NAMES IS TABLE OF VARCHAR2(30); vNome TABLE_NAMES := TABLE_NAMES('LEANDRO MIOZZO BONATO', 'JOÃO DA SILVA SAURO', 'FULANO', 'BELTRANO'); BEGIN vNome.DELETE(1); DBMS_OUTPUT.PUT_LINE('NOME É: ' || vNome(1)); END; RESULTADO 04: ORA-01403: dados não encontrados Nos próximos exemplos, implantaremos os nossos exemplos com as funções de repetição. EXEMPLO 03: SET SERVEROUTPUT ON; DECLARE TYPE TABLE_NAMES IS TABLE OF VARCHAR2(30); vNome TABLE_NAMES := TABLE_NAMES('LEANDRO MIOZZO BONATO','JOÃO DA SILVA SAURO', 'FULANO','BELTRANO'); i integer := vNome.FIRST; BEGIN WHILE i IS NOT NULL LOOP DBMS_OUTPUT.PUT_LINE('NOME É: ' || vNome(I)); I := vNome.NEXT(I); END LOOP; END; RESULTADO 05: bloco anônimo concluído NOME É: LEANDRO MIOZZO BONATO NOME É: JOÃO DA SILVA SAURO NOME É: FULANO NOME É: BELTRANO No caso acima, executamos apenas todos os elementos contidos na tabela aninhada, observe que precisamos criar uma variável para receber a posição do índice, atribuir a ela a primeira posição, e utilizá-la para percorrer por toda a tabela, também precisamos “sustenta-la”, ou atribuir novamente um valor para o seu índice dentro do WHILE, para que ela possa prosseguir com a leitura dos elementos. No exemplo utilizamos o comando FIRST, para buscar a primeira posição e o comando NEXT, para buscara próxima posição da tabela aninhada. Mas se por exemplo, um destes elementos durante a repetição deixasse de existir, ou fosse deletado, então o erro de dados não encontrados (NO_DATA_FOUND) apareceria para nos assombrar. Com isso precisamos sempre nos ater a criar um tratamento para caso este tipo de situação venha surgir, abaixo o exemplo com o tratamento: EXEMPLO 04: SET SERVEROUTPUT ON; DECLARE TYPE TABLE_NAMES IS TABLE OF VARCHAR2(30); vNome TABLE_NAMES := TABLE_NAMES('LEANDRO MIOZZO BONATO','JOÃO DA SILVA SAURO', 'FULANO','BELTRANO'); i integer := vNome.FIRST; BEGIN vNome.delete(i); WHILE i IS NOT NULL LOOP IF vNome.EXISTS(I) THEN DBMS_OUTPUT.PUT_LINE('NOME É: ' || vNome(I)); I := vNome.NEXT(I); ELSE DBMS_OUTPUT.PUT_LINE('NÃO EXISTE A POSIÇÃO ' || I); I := vNome.NEXT(I); END IF; END LOOP; END; RESULTADO 06: bloco anônimo concluído NÃO EXISTE A POSIÇÃO 1 NOME É: JOÃO DA SILVA SAURO NOME É: FULANO NOME É: BELTRANO É possível também utilizar o comando TRIM, que efetua a exclusão da quantidade de elementos em seu parâmetro de forma decrescente, por exemplo, a tabela aninhada possuí 10 posições e você resolve utilizar o comando TRIM para 5 elementos, então apenas sobrará os 5 primeiros elementos e os últimos 5 elementos serão apagados da tabela aninhada. EXEMPLO 05: SET SERVEROUTPUT ON; DECLARE TYPE TABLE_NAMES IS TABLE OF VARCHAR2(30); vNome TABLE_NAMES := TABLE_NAMES('LEANDRO MIOZZO BONATO','JOÃO DA SILVA SAURO', 'FULANO','BELTRANO'); i integer := vNome.FIRST; BEGIN vNome.TRIM(3); WHILE i IS NOT NULL LOOP DBMS_OUTPUT.PUT_LINE('NOME É: ' || vNome(I)); i := vNome.NEXT(I); END LOOP; END; RESULTADO 07: bloco anônimo concluído NOME É: LEANDRO MIOZZO BONATO Ao contrário do comando DELETE, o comando TRIM, elimina a quantidade de elementos contidos em seu parâmetro de entrada, no exemplo acima, tínhamos 4 elementos utilizamos o comando TRIM e sobraram apenas 1 elemento, sendo apenas o primeiro elemento. O comando COUNT em uma NESTED TABLE, nada mais é do que o comando LAST, apenas na exceção de algum dos elementos ser deletados durante a execução do script. (MOORE, 2014) EXEMPLO 06: SET SERVEROUTPUT ON; DECLARE TYPE TABLE_NAMES IS TABLE OF VARCHAR2(30); vNome TABLE_NAMES := TABLE_NAMES('LEANDRO MIOZZO BONATO','JOÃO DA SILVA SAURO', 'FULANO','BELTRANO'); i integer := vNome.FIRST; BEGIN DBMS_OUTPUT.PUT_LINE('Quantidade antes: ' || vNome.COUNT); DBMS_OUTPUT.PUT_LINE('Último elemento antes: ' || vNome.LAST); vNome.DELETE(1); DBMS_OUTPUT.PUT_LINE('Quantidade depois: ' || vNome.COUNT); DBMS_OUTPUT.PUT_LINE('Último elemento depois: ' || vNome.LAST); END; RESULTADO 08: bloco anônimo concluído Quantidade antes: 4 Último elemento antes: 4 Quantidade depois: 3 Último elemento depois: 4 No caso das tabelas aninhadas podemos também criar uma coleção de TYPES, definindo e declarando variáveis para posteriormente poder acessá-las e efetuar as transações no banco de dados necessárias. O exemplo abaixo, mostra que deve-se declarar a variável com seu respectivo nome o seu tipo, se é tabela aninhada, VARRAY ou uma matriz associativa e então identificar qual o tipo do dado que irá ser utilizado (VARCHAR2, NUMBER, DATE, etc.), também no exemplo, deixamos os valores “fictícios” e partimos para algo que podemos utilizar na vida real, como uma tabela aninhada recebendo os valores de uma tabela com um cursor. EXEMPLO 07: SET SERVEROUTPUT ON; DECLARE CURSOR curNomLst is SELECT FIRST_NAME FROM EMPLOYEES WHERE ROWNUM <= 10; TYPE tNomLst IS TABLE of EMPLOYEES.FIRST_NAME%TYPE; refNomLst tNomLst := tNomLst(); vCount INTEGER :=0; BEGIN FOR N IN curNomLst LOOP vCount := vCount +1; refNomLst.extend; refNomLst(vCount) := N.FIRST_NAME; DBMS_OUTPUT.PUT_LINE('EMPLOYEE FIRST_NAME('||VCOUNT||'):'||REFNOMLST(VCOUNT)); END LOOP; END; RESULTADO 09: bloco anônimo concluído EMPLOYEE FIRST_NAME(1):Ellen EMPLOYEE FIRST_NAME(2):Sundar EMPLOYEE FIRST_NAME(3):Mozhe EMPLOYEE FIRST_NAME(4):David EMPLOYEE FIRST_NAME(5):Hermann EMPLOYEE FIRST_NAME(6):Shelli EMPLOYEE FIRST_NAME(7):Amit EMPLOYEE FIRST_NAME(8):Elizabeth EMPLOYEE FIRST_NAME(9):Sarah EMPLOYEE FIRST_NAME(10):David No exemplo acima, é executado um cursor de uma consulta na tabela EMPLOYEES (funcionários) para o campo de FIRST_NAME (primeiro nome), apenas limitamos a quantidade de registros para 10 para utilizarmos em nosso exemplo, então em seguida definimos uma variável do tipo tabela com um campo do tipo da coluna FIRST_NAME da tabela EMPLOYEES (seria o tipo VARCHAR2, em resumo). Adiante definimos uma variável do tipo da nossa tabela criada anteriormente, recebendo os valores de nossa tabela, e em seguida definimos uma variável contadora do tipo inteiro. Para atribuirmos os valores do cursor à nossa tabela aninhada e em seguida poder acessar os dados, devemos criar um LOOP onde defino como N tendo os registros do cursor CURNOMLST, então para que seja atribuído o valor à nossa tabela aninhada, propriamente dito, deve-se estender o tamanho da tabela para que caiba os registros, pois a mesma não possuí nenhum elemento e está vazia. Em seguida é atribuído o valor do cursor em um elemento no respectivo índice da variável VCOUNT. Também podemos atribuir à uma NESTED TABLE mais de uma coluna no cursor, que o Oracle irá interpretar normalmente. Conforme o exemplo abaixo, queremos agora saber qual é o primeiro nome, o último nome e o salário do funcionário, e com isso mostrar em nossa consulta como é simples transformar um campo de tabela aninhada em vários campos. EXEMPLO 08: SET SERVEROUTPUT ON; DECLARE CURSOR curNomLst is SELECT FIRST_NAME, last_name, SALARY FROM EMPLOYEES WHERE ROWNUM <= 10; TYPE tNomLst IS TABLE of EMPLOYEES%ROWTYPE; refNomLst tNomLst := tNomLst(); vCount INTEGER :=0; BEGIN FOR N IN curNomLst LOOP vCount := vCount +1; refNomLst.extend; refNomLst(vCount).first_name := N.FIRST_NAME; refNomLst(vCount).LAST_name := N.LAST_NAME; refNomLst(vCount).SALARY := N.SALARY; DBMS_OUTPUT.PUT_LINE('EMPLOYEE FIRST_NAME('||VCOUNT||'):'||REFNOMLST(VCOUNT).FIRST_NAME || ' - LAST_NAME: ' || REFNOMLST(VCOUNT).LAST_NAME || ' - SALARY: ' || REFNOMLST(VCOUNT).SALARY); END LOOP; END; RESULTADO: 10 bloco anônimo concluído EMPLOYEE FIRST_NAME(1):Steven - LAST_NAME: King - SALARY: 15000 EMPLOYEE FIRST_NAME(2):Neena - LAST_NAME: Kochhar - SALARY: 17000 EMPLOYEE FIRST_NAME(3):Lex - LAST_NAME: De Haan - SALARY: 17000 EMPLOYEE FIRST_NAME(4):Alexander - LAST_NAME: Hunold - SALARY: 9000 EMPLOYEE FIRST_NAME(5):Bruce - LAST_NAME: Ernst - SALARY: 6000 EMPLOYEE FIRST_NAME(6):David - LAST_NAME: Austin - SALARY: 4800 EMPLOYEE FIRST_NAME(7):Valli - LAST_NAME: Pataballa - SALARY: 4800 EMPLOYEE FIRST_NAME(8):Diana - LAST_NAME: Lorentz - SALARY: 4200 EMPLOYEE FIRST_NAME(9):Nancy - LAST_NAME: Greenberg - SALARY: 12008 EMPLOYEE FIRST_NAME(10):Daniel - LAST_NAME: Faviet - SALARY: 9000 No exemplo acima, adicionamos as colunas no nosso SELECT normalmente, apenas mudamos a referência da tabela aninhada que era feita antes diretamente para a coluna de FIRST_NAME para um ROWTYPE, que nada mais é do que a referência da linha toda. Consequentemente com isso tivemos que acrescentar na referência das colunas da tabela aninhada, os campos que receberão os valores, e também uma grande mudança no momento de referencia-los é acrescentar após o índice da tabela aninhada o nome do campo, ao contrário de quando se tem apenas uma coluna não há a necessidade de referencia-lo após o índice. Contando as NESTED TABLES possuem recursos mais poderosos do que apenas programação “pura” em PL/SQL, elas permitem se criar colunas em tabelas físicas, em resumo, é uma tabela de dados inteira, com suas respectivas propriedades, dentro de apenas uma coluna de tabela física. Abaixo os exemplos mostram como poderá ser utilizadotal recurso, e também como poderemos manipular os dados dentro desta coluna/tabela. EXEMPLO 09: CREATE TYPE EMPLOYEES_ELEMENTS AS OBJECT ( EMPLOYEE_ID NUMBER(8,0), FIRST_NAME VARCHAR2(20), LAST_NAME VARCHAR2(25), EMAIL VARCHAR2(25), PHONE_NUMBER VARCHAR2(20), HIRE_DATE DATE, JOB_ID2 VARCHAR2(10), SALARY NUMBER(8,2), COMMISSION_PCT NUMBER(2,2), MANAGER_ID NUMBER(6), DEPARTMENT_ID NUMBER(4) ); CREATE TYPE EMPLOYEES_TABLE AS TABLE OF EMPLOYEES_ELEMENTS; CREATE TABLE EMPLOYEE_COPY ( SEQ_EMPLOYEE NUMBER(8,0) NOT NULL , EMPLOYEE_INFO EMPLOYEES_TABLE, CONSTRAINT EMPLOYEE_COPY_PK PRIMARY KEY (SEQ_EMPLOYEE) ENABLE ) NESTED TABLE EMPLOYEE_INFO STORE AS EMPLOYEES_INFOS; CREATE SEQUENCE SEQ_EMPLOYEE_COPY INCREMENT BY 1 START WITH 1 MAXVALUE 999999999999 MINVALUE 1 NOCACHE ORDER; RESULTADO 11: TYPE EMPLOYEES_ELEMENTS compilado TYPE EMPLOYEES_TABLE compilado table EMPLOYEE_COPY criado. sequence SEQ_EMPLOYEE_COPY criado. No exemplo acima, primeiramente criamos um TYPE de variável do tipo OBJECT, que criará o objeto da tabela aninhada com todas as colunas necessárias, para este exemplo, estarei replicando a tabela EMPLOYEES, com todos os tipos de dados e nomes de campos. Em um segundo momento, é efetuada a criação de um novo TYPE de variável do tipo TABLE do TYPE criado anteriormente (EMPLOYEES_ELEMENTS), este TYPE estará referenciando e buscando todas as informações de propriedades criados no primeiro TYPE. Então, para finalizar, criamos uma tabela cópia (EMPLOYEE_COPY) da tabela de EMPLOYEES, porém criamos um campo da sequência e referenciamos apenas a coluna do nosso TYPE (EMPLOYEES_TABLE), em seguida “dizemos” para o Oracle que esta tabela possuí uma NESTED TABLE e o campo que está recebendo tal recurso é o EMPLOYEE_INFO e será armazenado os dados no TYPE EMPLOYEES_INFOS. Criamos também uma sequência para nos auxiliar no momento de inserir os registros da tabela, para a primeira coluna SEQ_EMPLOYEE, chamada de SEQ_EMPLOYEE_COPY. Para entendermos melhor o que estamos fazendo, vamos ver na prática como estará funcionando, segue o exemplo abaixo, onde iremos inserir alguns registros, para efetuar tal operação fizemos uma pequena automação para o comando INSERT. EXEMPLO 10: DECLARE CURSOR curEmployees is SELECT * FROM EMPLOYEES; BEGIN FOR N IN curEmployees LOOP INSERT INTO EMPLOYEE_COPY (SEQ_EMPLOYEE, EMPLOYEE_INFO) VALUES (SEQ_EMPLOYEE_COPY.NEXTVAL, EMPLOYEES_TABLE( EMPLOYEES_ELEMENTS ( N.EMPLOYEE_ID, N.FIRST_NAME, N.LAST_NAME, N.EMAIL, N.PHONE_NUMBER, N.HIRE_DATE, N.JOB_ID2, N.SALARY, N.COMMISSION_PCT, N.MANAGER_ID, N.DEPARTMENT_ID))); END LOOP; END; Se efetuarmos uma consulta comum na tabela de EMPLOYEES_COPY, os registros que retornarão serão tudo (com exceção do SEQ_EMPLOYEE) em uma coluna de um tipo de dado de NESTED TABLE, segue abaixo o resultado da consulta comum na tabela em questão, o exemplo abaixo está limitado a 5 registros apenas, para que o mesmo não se estenda muito, mas o correto são 107 registros. EXEMPLO 11: SELECT * FROM EMPLOYEE_COPY; RESULTADO 12: SEQ_EMPLOYEE EMPLOYEE_INFO ------------ -------------------------------------------------- 1 HR.EMPLOYEES_TABLE(HR.EMPLOYEES_ELEMENTS(100,'Steven','King','SKING','515.123.4567','2003-06-17 00:00:00.0','AD_PRES',15000,NULL,100,90)) 2 HR.EMPLOYEES_TABLE(HR.EMPLOYEES_ELEMENTS(101,'Neena','Kochhar','NKOCHHAR','515.123.4568','2005-09-21 00:00:00.0','AD_VP',17000,NULL,100,90))3 HR.EMPLOYEES_TABLE(HR.EMPLOYEES_ELEMENTS(102,'Lex','De Haan','LDEHAAN','515.123.4569','2001-01-13 00:00:00.0','AD_VP',17000,NULL,100,90)) 4 HR.EMPLOYEES_TABLE(HR.EMPLOYEES_ELEMENTS(103,'Alexander','Hunold','AHUNOLD','590.423.4567','2006-01-03 00:00:00.0','IT_PROG',9000,NULL,102,60)) 5 HR.EMPLOYEES_TABLE(HR.EMPLOYEES_ELEMENTS(104,'Bruce','Ernst','BERNST','590.423.4568','2007-05-21 00:00:00.0','IT_PROG',6000,NULL,103,60)) Para selecionar apenas um campo, ou apenas os campos desejados de uma NESTED TABLE pelo comando SELECT, deve-se apenas inserir na clausula do FROM a tabela que contém o campo, neste caso a EMPLOYEE_COPY, e então declarar uma nova tabela com o comando TABLE e indicar qual é a coluna NESTED TABLE da tabela referenciada da EMPLOYEE_COPY. Recomendo sempre criar “Alias” para as colunas referenciadas, para que fique mais fácil de poder acessá-las depois. Vejamos no exemplo abaixo como ficará a consulta no banco de dados, selecionando apenas alguns campos do objeto, o resultado é apenas o 5 primeiros registros. EXEMPLO 12: SELECT B.FIRST_NAME, B.LAST_NAME FROM EMPLOYEE_COPY A, TABLE (A.EMPLOYEE_INFO) B; RESULTADO 13: FIRST_NAME LAST_NAME -------------------- ------------------------- Tayler Fox Pat Fay Mattea Marvins Nandita Sarchand Martha Sullivan Também pode-se utilizar um comando de consulta normalmente para a coluna, onde poderá ser utilizado os comandos no WHERE para efetuar filtros, de forma em que se utiliza a mesma lógica do que para um SELECT comum, porém deve-se acrescentar o campo que deseja comparar, o tipo da variável de tabela e a tabela referenciada, um pouco mais complicado, mas funcional da mesma forma. O exemplo abaixo mostra com detalhes como poderá ser feita esta consulta, o resultado é apenas o 5 primeiros registros. EXEMPLO 13: SELECT B.FIRST_NAME, B.LAST_NAME FROM EMPLOYEE_COPY A, TABLE (A.EMPLOYEE_INFO) B WHERE B.SALARY >= 10000 RESULTADO 14: FIRST_NAME LAST_NAME -------------------- ------------------------- Pat Fay Nancy Greenberg Neena Kochhar Lex De Haan Harrison Bloom Podemos, além do comando INSERT, utilizar outros comandos de manipulação em SQL (DML), como por exemplo, o UPDATE e o DELETE, de forma normal e como de forma como os conhecemos, sem segredo algum. O exemplo abaixo mostra como pode-se ser executado o comando de atualização de registros. EXEMPLO 14: UPDATE TABLE(SELECT A.EMPLOYEE_INFO FROM EMPLOYEE_COPY A, TABLE (A.EMPLOYEE_INFO) B WHERE B.EMPLOYEE_ID = 100 ) B SET B.FIRST_NAME = 'LEANDRO BONATO' EXEMPLO 15: SELECT B.FIRST_NAME FROM EMPLOYEE_COPY A, TABLE (A.EMPLOYEE_INFO) B WHERE B.EMPLOYEE_ID = 100 RESULTADO 15: 1 linhas atualizado. FIRST_NAME -------------------- LEANDRO BONATO EXEMPLO 16: DELETE TABLE (SELECT A.EMPLOYEE_INFO FROM EMPLOYEE_COPY A WHERE A.SEQ_EMPLOYEE = 2 ) A WHERE A.FIRST_NAME = 'LEANDRO BONATO' RESULTADO 16: 1 linhas deletado. Quanto ao desempenho das NESTED´s TABLE´s iremos testar da seguinte forma, primeiramente inserimos 1 000 000 (um milhão) de registros, utilizando o INSERT do exemplo 10, onde apenas iremos apenas acrecentar quantas vezes de 100 registros deverá ser inserido, no caso são 10 000 vezes que deverá ser repetido a instrução do exemplo 10, pois a tabela contém aproximadamente 100 registros. Então com isso também iremos inserir 1 000 000 de registros na tabela EMPLOYEES e verificar qual foi mais rápido para inserir os dados, e em seguida mostraremos a quantidade de tempo para listar e consultar estes registros no banco de dados, em cada e os compararemos. Abaixo está o exemplo de como deverá ficar cada um dos comandos de inserção. EXEMPLO 17: SET SERVEROUTPUT ON; DECLARE CURSOR curEmployees is SELECT * FROM EMPLOYEES; BEGIN FOR I IN 1..10000 LOOP FOR N IN curEmployeesLOOP INSERT INTO EMPLOYEE_COPY (SEQ_EMPLOYEE, EMPLOYEE_INFO) VALUES (SEQ_EMPLOYEE_COPY.NEXTVAL, EMPLOYEES_TABLE( EMPLOYEES_ELEMENTS(N.EMPLOYEE_ID, N.FIRST_NAME, N.LAST_NAME, N.EMAIL, N.PHONE_NUMBER, N.HIRE_DATE, N.JOB_ID2, N.SALARY, N.COMMISSION_PCT, N.MANAGER_ID, N.DEPARTMENT_ID ) ) ); END LOOP; END LOOP; END; EXEMPLO 18: SELECT COUNT(1) FROM EMPLOYEE_COPY RESULTADO 17: bloco anônimo concluído. Tarefa concluída em 612,671 segundos. 1070107 Registros Para o próximo exemplo faremos praticamente o mesmo INSERT, onde as mudanças serão na consulta que deverá ser EMPLOYEE_ID <= 206, para buscar apenas os mesmos que tinham na tabela EMPLOYEE_COPY, e criar uma variável contadora, já que não possuímos uma sequência para utilizar do comando NEXTVAL. Também deve-se alterar algumas propriedades da tabela, no campo EMPLOYEE_ID deverá ser aumentado o seu limite para no mínimo 7 (eu coloquei 8 por boas práticas) e desativar a chave única do campo email (EMP_EMAIL_UK). EXEMPLO 19: SET SERVEROUTPUT ON; DECLARE CURSOR curEmployees is SELECT * FROM EMPLOYEES where EMPLOYEE_ID <= 206; vCOUNT INTEGER := 207; BEGIN FOR I IN 1..10000 LOOP FOR N IN curEmployees LOOP INSERT INTO EMPLOYEES VALUES (vCOUNT, N.FIRST_NAME, N.LAST_NAME, N.EMAIL, N.PHONE_NUMBER, N.HIRE_DATE, N.JOB_ID2, N.SALARY, N.COMMISSION_PCT, N.MANAGER_ID, N.DEPARTMENT_ID); vCOUNT := vCOUNT + 1; END LOOP; END LOOP; END; EXEMPLO 20: SELECT COUNT(1) FROM EMPLOYEES; RESULTADO 18: bloco anônimo concluído. Tarefa concluída em 272,359 segundos. 1070107 Registros A ideia era fazer um comparativo entre dois INSERT´s, um normal entre a tabela de forma comum como conhecemos, e outro com a NESTED TABLE, com uma quantidade aproximada de registros, com isso teríamos um comparativo entre ambos para ver qual se comporta melhor. Vimos que no resultado as duas formas de inserção são muito parecidas em seu tempo de execução, testaremos agora uma consulta com aproximadamente 10% a 20% das tabelas para termos o tempo entre as duas e então mostrarei as vantagens e desvantagens de cada uma em cada situação. EXEMPLO 21: SELECT A.EMPLOYEE_ID, A.FIRST_NAME, A.LAST_NAME, A.SALARY FROM EMPLOYEE_COPY A, TABLE(EMPLOYEE_INFO) B WHERE A.SALARY >= 10000; EXEMPLO 22: SELECT COUNT(1) FROM EMPLOYEE_COPY WHERE SALARY >= 10000; RESULTADO 19: 200000 EXTRAÍDAS EM 10 SEGUNDOS Faremos agora a mesma consulta apenas trocamos as tabelas para vermos quanto tempo demora e quantos registros retornam. EXEMPLO 23: SELECT A.EMPLOYEE_ID, A.FIRST_NAME, A.LAST_NAME, A.SALARY FROM EMPLOYEES A WHERE A.SALARY >= 10000; EXEMPLO 24: SELECT COUNT(1) FROM EMPLOYEES WHERE SALARY >= 10000; RESULTADO 20: 200020 LINHAS EXTRAÍDAS E, 12,063 SEGUNDOS Teoricamente a consulta na NESTED TABLE é mais rápida, mas vimos que um comando de INSERT é muito mais demorado, praticamente 225% de diferença entre uma tabela simples e uma tabela aninhada. Para comprovarmos a diferença de cada um iremos agora executar o plano de explicação e o rastreamento automático de cada uma das consultas. RESULTADO 21: (NESTED TABLE) RESULTADO 22: (TABELA FÍSICA) Reparamos que NESTED TABLE há muito mais cardinalidade de registros do que na tabela física, isto porque, todos os registros devem ser lidos e interpretados pela tabela aninhada e posteriormente interpretados pela tabela física A desvantagem de uma NESTED TABLE é que não podemos criar um índice na coluna de EMPLOYEE_INFO, até porque não faria sentido, pois se fosse indexada as colunas, seriam todas as colunas pertencentes a um índice, pois todas as colunas da tabela EMPLOYEE estão dentro da NESTED TABLE e isso não seria “uma boa prática”. Mas, se alterarmos a tabela EMPLOYEES e acrescentar na coluna SALARY um índice, o desempenho de nossa consulta não irá melhorar (devido à quantidade de registros), mas observem no exemplo que a mesma agora está utilizando um novo índice criado e utilizando outras formas de efetuar a própria consulta, incluindo as da própria chave primária. EXEMPLO 25: CREATE INDEX EMP_ARTIGO_IDX ON EMPLOYEES (SALARY ASC); RESULTADO 23: Isto conclui-se que as NESTED TABLES quando se trata de operações mais comuns não se comportam muito bem, pois podem se tornar mais trabalhosa para se criar e também mais lentas no momento de retornar os dados. Mas são muito uteis quando utilizadas em PACKAGES, por exemplo, pois em uma PACKAGE o desenvolvedor poderá inserir vários tipos de variáveis e trabalhar com o PL/SQL sem problemas. Isto poderá trazer uma série de benefícios, como uma organização e algo mais próximo da para orientação à objetos, pois também não uma forma densa de programação, ou seja, o desenvolvedor pode optar por algo dinâmico, ao contrário dos VARRAY´s. No próximo artigo, será o último desta série de artigos sobre registros e coleções no ORACLE, onde mostrarei mais exemplos sobre o último tipo de matrizes chamado de Tabelas Associativas ou tabela de índice (Associative array or index-by-table). REFERÊNCIAS PRICE, Jason. ORACLE DATABASE 11G SQL: DOMINE SQL E PL/SQL NO BANCO DE DADOS ORACLE. 2009. MOORE, Sheila. ORACLE® DATABASE PL/SQL LANGUAGE REFERENCE 11g RELEASE 1 (11.1) – B28370-05. 2009. _____________. ORACLE® DATABASE PL/SQL LANGUAGE REFERENCE 11g RELEASE 2 (11.2) – E25519-13. 2014. BURLESON, Don. ORACLE VARRAY EXAMPLES. 2008. AGRADECIMENTOS FABIO PELICER ALEXANDRE PIETROBELLI Abraços.
Compartilhar