A solução mais comum é a de gerar o SQL dinamicamente. Mas isso cria mais dois problemas: não é seguro (facilita a injeção de SQL) e obriga o Oracle a compilar um novo comando.
É possível passar um array de elementos como parâmetro de uma funcão ou procedure, mas isso não facilita a vida nem do Java (estou presumindo que a interface com o banco seja JDBC) nem do PL/SQL.
Uma solução que surgiu com o Oracle 9i são as funções PIPELINED. Elas permitem escrever uma função cujos resultados podem ser convertidos em linhas de uma tabela.
Vou descrever isso melhor com código. A declaração abaixo é de uma package que contém funções que vamos usar adiante, diretamente em comandos DML:
CREATE OR REPLACE PACKAGE LIST_PKG AS
TYPE ID_TABLE IS TABLE OF INTEGER;
TYPE NAME_TABLE IS TABLE OF VARCHAR2(32767);
FUNCTION IDS(LIST IN VARCHAR2) RETURN ID_TABLE PIPELINED;
FUNCTION NAMES(LIST IN VARCHAR2) RETURN NAME_TABLE PIPELINED;
END;
Temos aqui dois tipos, uma tabela de inteiros e uma tabela de strings. A função IDS() vai transformar um string com inteiros em uma tabela de inteiros. A função NAMES() fará o mesmo por strings.
Então, se eu quiser pesquisar um grupo de pessoas com determinados cargos, posso fazer o seguinte:
SELECT NOME, CARGO, SALARIO
FROM
PESSOAS P
INNER JOIN (TABLE(LIST_PKG.NAMES('Chefão,Chefe,Chefinho')) T
ON T.COLUMN_VALUE=P.CARGO)
A função TABLE transforma os valores de LIST_PKG.NAMES() em uma tabela.
Se eu quiser encontrar um grupo específico de pessoas, posso escrever uma consulta como esta:
SELECT NOME, CARGO, SALARIO
FROM
PESSOAS P
INNER JOIN (TABLE(LIST_PKG.IDS('34,45,67,1,99')) T
ON T.COLUMN_VALUE=P.MATRICULA)
É claro que o melhor é colocar essas consultas numa procedure ou function:
PROCEDURE BUSCAR_PESSOAS_PROC (
P_MATRICULAS IN VARCHAR2,
P_CURSOR OUT SYS_REFCURSOR
) IS
BEGIN
OPEN P_CURSOR FOR
SELECT NOME, CARGO, SALARIO
FROM
PESSOAS P
INNER JOIN (TABLE(LIST_PKG.IDS(P_MATRICULAS)) T
ON T.COLUMN_VALUE=P.MATRICULA);
END;
E qual a mágica que precisa ocorrer dentro de LIST_PKG? Segue o código:
CREATE OR REPLACE PACKAGE BODY LIST_PKG AS
FUNCTION IDS(LIST IN VARCHAR2) RETURN ID_TABLE PIPELINED IS
LEN PLS_INTEGER;
TAB SYS.DBMS_UTILITY.UNCL_ARRAY;
BEGIN
IF LIST IS NULL THEN
RETURN;
END IF;
SYS.DBMS_UTILITY.COMMA_TO_TABLE(
'"' || REPLACE(LIST, ',', '","') || '"',
LEN, TAB);
FOR I IN 1 .. LEN LOOP
PIPE ROW(TRANSLATE(TAB(I), 'A"', 'A'));
END LOOP;
RETURN;
END IDS;
FUNCTION NAMES(LIST IN VARCHAR2) RETURN NAME_TABLE PIPELINED IS
LEN PLS_INTEGER;
TAB SYS.DBMS_UTILITY.UNCL_ARRAY;
BEGIN
IF LIST IS NULL THEN
RETURN;
END IF;
SYS.DBMS_UTILITY.COMMA_TO_TABLE(
'"' || REPLACE(LIST, ',', '","') || '"',
LEN, TAB);
FOR I IN 1 .. LEN LOOP
PIPE ROW(TRANSLATE(TAB(I), 'A"', 'A'));
END LOOP;
RETURN;
END NAMES;
END LIST_PKG;
Como se pode ver, as funções simplesmente dividem strings, separando os elementos entre vírgulas, e depois repassam os valores através do comando PIPE ROW. Pronto, nunca mais será preciso fazer ginástica com strings; essa package ocupa-se de todo o trabalho.
Bem interessante.
ResponderExcluirMas não daria pra fazer TRANSLATE(TAB(I), '"', '') em vez de TRANSLATE(TAB(I), 'A"', 'A')?
Num teste rápido (sem reproduzir toda a função), funcionou.
(continuando)
ResponderExcluirOu usar REPLACE mesmo.
Salve Marcus,
ResponderExcluiracho que trocar TRANSLATE por REPLACE é uma boa idéia mesmo, mas o TRANSLATE devolve NULL se o último parâmetro for NULL ou '' (que é igual a NULL para o Oracle). Então, aquele A só serve para evitar esse problema.