Thursday, May 7, 2009

How to Use SQL Query having IN Clause With DB Adapter

When you execute a syntactically correct SQL query having IN clause from SQL prompt, it works as expected.

Let’s assume you have a table employee with the structure given below and the table is populated with few records.

Name Null Type

------------------------------------------------------------------------------------------------

EMP_ID NOT NULL VARCHAR2(10)

EMP_DEPT VARCHAR2(30)

EMP_SALARY NUMBER(8,2)

EMP_DESIGNATION VARCHAR2(30)

EMP_FNAME VARCHAR2(30)

EMP_FNAME VARCHAR2(30)

EMP_ADDREDSS VARCHAR2(60)


You type and execute the query given below at SQL prompt and it returns the data expected.

SELECT EMP_ID, EMP_DEPT, EMP_SALARY, EMP_DESIGNATION, EMP_FNAME, EMP_FNAME
FROM EMPLOYEE
WHERE EMP_DEPT
IN ('SALES','FINANCE','TECHSERVICES','INFRACTURE')

If you use the above query with Oracle DB adapter in Oracle BPEL/ESB it works as expected.

Problem starts when you decide to pass the list of values for in query dynamically. You need to change the above query as the one given below:

SELECT EMP_ID, EMP_DEPT, EMP_SALARY, EMP_DESIGNATION, EMP_FNAME, EMP_FNAME
FROM EMPLOYEE WHERE EMP_DEPT
IN (?)

When you populate the input payload with value list for IN clause and execute the above query with DB adapter, DB adapter wont return anything. DB adapter wraps the parameter value by ' (apostrophe), so even if you pass a comma separated value list, the list would be treated as a single value by the DB adapter and the adapter returns nothing.

You need to write a SQL query like the one given below if you want to use IN clause in SQL query and pass the value list dynamically:

SELECT EMP_ID,EMP_DEPT, EMP_SALARY, EMP_DESIGNATION, EMP_FNAME, EMP_LNAME
FROM EMPLOYEE
WHERE EMP_DEPT IN (WITH VALUE_LIST AS
(SELECT ? val FROM dual)
SELECT SUBSTR(val, (decode(LEVEL, 1, 0, instr(val, ':', 1, LEVEL -1)) + 1), (decode(instr(val, ':', 1, LEVEL) -1, -1, LENGTH(val), instr(val, ':', 1, LEVEL) -1)) -(decode(LEVEL, 1, 0, instr(val, ':', 1, LEVEL -1)) + 1) + 1) a
FROM VALUE_LIST CONNECT BY LEVEL <=
(SELECT(LENGTH(val) -LENGTH(REPLACE(val, ':', NULL)))
FROM VALUE_LIST) + 1)

See the portion of this query highlighted red; you don't need to change it. You need to customize the portion marked green as per your requirement.

While configuring DB adapter you need to the following steps:
  • Select "Execute Custom SQL" as Operation Type and click on Next.
  • Customize the above SQL and paste it in Custom SQL text box and click on Finish.
In this SQL, value list for IN clause should be delimited with a : symbol, If you want to use any other symbol as a delimiter you need to replace : with the symbol you want to use.

Before invoking the DB adapter, you need to create IN value list dynamically. Use XSL transformation to generate the dynamic value list and pass it to the DB adapter.

While generating the value list you don’t need to wrap character data with ' (apostrophe). To query all the employees from SALES, TECHSERVICES, INFRASTRUCTURE, FINANCE departments you need to create an IN clause value list as SALES:TECHSERVICES:INFRASTRUCTURE:FINANCE.

Limitations of DB adapter with this query:
  • If you use SELECT * rather than giving the field list with SELECT, DB adapter configuration adapter won't be able to generate a correct XSD for DB adapter request.
  • If you create a very big value list for IN query (greater than 4000 characters) than you need to break value list and invoke this query multiple times, otherwise you will get ORA-01704: string literal too long error.
Alternative Approach:
  • You can write a PL/SQL stored procedure to implement the same functionality, only issue with PL/SQL is that you need to move the PL/SQL code whenever you move from one system to other i.e. development to system test to UAT to Production environments.

9 comments:

Vaibhav Pandey said...

Excellent !! works wonders :)) thnx buddy.........

Mads said...

Hi,
Nice article about the "Execute Custom SQL".

I have another question, is there a way to execute the DDL comnads using the "Execute Custom SQL"?

Regards,
Madhav

Susan Philip said...

It works beautifully. Thanks for the post. Made my life lot easier

naveen said...

Select NVL(to_char(ow_org_id),'Not Available') organizationId, count(rep_num)
From xxx_table
Where to_date(create_date) > to_date(?)
Group by own_org_id, rep_num

i have created bpel process and trying to get output based on the creation date. the process is based on Httpbinding where parameters are given from WSDL url as

http://hostname:port/httpbinding/default/processname?&repDate=date

i am creating the service with DB adapter(custom sql) output is empty xml
i can get the output if i run the query in sqldeveloper can anyone help me in this regard

created the httpbindings based on this link

http://blogs.oracle.com/reynolds/2005/09/invoking_bpel_from_an_html_for.html

naveen said...

Select NVL(to_char(ow_org_id),'Not Available') organizationId, count(rep_num)
From xxx_table
Where to_date(create_date) > to_date(?)
Group by own_org_id, rep_num

i have created bpel process and trying to get output based on the creation date. the process is based on Httpbinding where parameters are given from WSDL url as

http://hostname:port/httpbinding/default/processname?&repDate=date

i am creating the service with DB adapter(custom sql) output is empty xml
i can get the output if i run the query in sqldeveloper can anyone help me in this regard

created the httpbindings based on this link

http://blogs.oracle.com/reynolds/2005/09/invoking_bpel_from_an_html_for.html

llarafernandes said...

Hello Dharmendra,

I tried to use this query but returns the "ORA-01473: cannot have subqueries in CONNECT BY clause" because I have to use GROUP BY.

My query is:

SELECT ANO, PERIODO, SUM(PAGO_ANO) as PAGO_ANO, SUM(PROCESSADO_ANO) as
PROCESSADO_ANO, PERIODO_NUM, SUM(EXECUTADO_ANO) as EXECUTADO_ANO
FROM C__ACTIVIDADE
WHERE ACTIVIDADE IN (WITH VALUE_LIST AS (
SELECT ? val
FROM dual)
SELECT SUBSTR(val, (decode(LEVEL, 1, 0, instr(val, ':', 1, LEVEL -1)) +
1), (decode(instr(val, ':', 1, LEVEL) -1, -1, LENGTH(val), instr(val,
':', 1, LEVEL) -1)) -(decode(LEVEL, 1, 0, instr(val, ':', 1, LEVEL -1))
+ 1) + 1) a
FROM VALUE_LIST
CONNECT BY LEVEL <= (
SELECT(LENGTH(val) -LENGTH(REPLACE(val, ':', NULL)))
FROM VALUE_LIST) + 1) GROUP BY ANO, PERIODO, PERIODO_NUM

Maybe in this case, is better to create a PL/SQL stored procedure or is there any other way?

Thank you,

Lara Fernandes.

Samir said...

Great Man.....keep up the good work...was hard to get this thing going......

Sam said...

The query doesn't compile even in sqldeveloper.

Appreciate if you could help in solving this issue.

My Query:
SELECT bod.hpod_delivery,
bod.hpod_deliverydetailid
from DBO.BOLORDERDETAIL BOD
where BOD.HPOD_DELIVERYDETAILID in (with VALUE_LIST as (
SELECT ? val
FROM dual)
SELECT SUBSTR(val, (decode(LEVEL, 1, 0, instr(val, ':', 1, LEVEL -1)) +
1), (decode(instr(val, ':', 1, LEVEL) -1, -1, LENGTH(val), instr(val,
':', 1, LEVEL) -1)) -(decode(LEVEL, 1, 0, instr(val, ':', 1, LEVEL -1))
+ 1) + 1) a
FROM VALUE_LIST
CONNECT BY LEVEL <= (
select(length(VAL) -length(replace(VAL, ':', null)))
FROM VALUE_LIST) + 1)

Error Message: Incorrect syntax near the keyword 'with'.

Thanks,
Santosh

Sam said...

My Bad. I was running this against MS SQL Server database. This work perfectly fine with Oracle DB. Can anyone let us know the equivalent query in MS SQL?

Appreciate your help.

Thanks,
Sam