Reading Multiple Cursor output using Spring Batch StoredProcedureItemReader - spring-batch

I have a stored procedure that has 2 Cursors in Output. Is it possible to have 2 RefCursorPosition in the StoredProcedureItemReader?
Sample SP:
create or replace PROCEDURE SP_POC ( p_id IN NUMBER
,p_out_c1 OUT SYS_REFCURSOR
,p_out_c2 OUT SYS_REFCURSOR
)
IS
cnt NUMBER;
BEGIN
cnt := p_id;
OPEN p_out_c1 FOR
SELECT level id ,
'ABC' || level Name
FROM dual
connect by level <5;
OPEN p_out_c2 FOR
select a.id,
b.name,
b.code
from
(SELECT level id , 'ABC' || level Name
FROM dual
connect by level <5 )a,
(SELECT level id , 'ABC' || level Name , 'XYZ-' || level code
FROM dual
connect by level <5)b
order by 2;
END;
Reader:
public StoredProcedureItemReader spReader() {
StoredProcedureItemReader reader = new StoredProcedureItemReader();
SqlParameter[] parameters = {new SqlParameter("#p_id", OracleTypes.NUMBER)
, new SqlOutParameter("#p_out_c1", OracleTypes.CURSOR)
, new SqlOutParameter("#p_out_c2", OracleTypes.CURSOR)
};
parameters.add(new SqlOutParameter("p_out_c2", OracleTypes.CURSOR));*/
reader.setDataSource(dataSource);
reader.setProcedureName("SP_POC");
reader.setRowMapper(new SPRowMapper());
reader.setRefCursorPosition(3);
reader.setPreparedStatementSetter(new MyItemPreparedStatementSetter());
reader.setParameters(parameters);
reader.setSaveState(false);
return reader;
}

Is it possible to have 2 RefCursorPosition in the StoredProcedureItemReader?
No, that is no possible. The StoredProcedureItemReader is not suitable to your case with a procedure that has 2 cursors as output. You need a custom reader for your use case.

Related

Why am I getting error 17410 end of data on my rest api when I add a parameter to the query

I have ORDS 21 installed in a local 19c oracle database. I have created a stored procedure to list all the departments from the dept table with a cursor that lists the employees from the emp table. If I just list all departments and their employees the api works fine, but if I add a parameter to the query to specify which department, I get an error 17410 no data left .
Both queries are backed by plsql stored procedures. I have created many stored procedures using this same format with parameters and nested cursors before without a problem.
MWE for query that works:
create or replace procedure get_dept
as
l_cur sys_refcursor;
begin
open l_cur for
select d.deptno,d.dname,
cursor (select e.empno,e.ename
from emp e
where e.deptno = d.deptno
order by e.deptno,e.empno
) as employees
from dept d
order by d.deptno;
-- return the resultset in json format
apex_json.open_object;
apex_json.write('emps',l_cur);
apex_json.close_object;
end get_dept;
/
MWE for query that does not work:
create or replace procedure get_dept1
(
p_dept_no in varchar2
) as
l_cur sys_refcursor;
begin
open l_cur for
select d.deptno,d.dname,
cursor (select e.empno,e.ename
from emp e
where e.deptno = d.deptno
order by e.deptno,e.empno
) as employees
from dept d
where d.deptno = to_number(p_dept_no)
order by d.deptno;
-- return the resultset in json format
apex_json.open_object;
apex_json.write('emps',l_cur);
apex_json.close_object;
end get_dept1;
/

ColdFusion 2016 and stored proc throwing invalid character error

I am trying existing code in a CF 2016 install... I get this error
"[Macromedia][DB2 JDBC Driver][DB2]ILLEGAL SYMBOL =; VALID SYMBOLS ARE ..."
the line identified is a param of a stored proc call that looks like this:
<cfstoredproc datasource="#application.dsn#" procedure="LIVE.STOPS">
<cfprocparam type="In" cfsqltype="CF_SQL_BIGINT" dbvarname="STOPID" value="#val( variables.procstopid )#" null="no">
<cfprocparam type="In" cfsqltype="CF_SQL_INTEGER" dbvarname="TRIPID" value="#val( url.tripId )#" null="no">
</cfstoredproc>
I cannot find any mention on line of a change in stored proc tag - maybe the DB2 driver? I'm looking for any input. Thanks.
Other info;
Windows10, Apache2.4, connectiong to DB2 v10.
#pendo, Here is the stored proc - it should be noted that I abbreviated some of the sql, but the SP works and has for a long time in the app running CF10.
CREATE OR REPLACE PROCEDURE LIVE.STOP(
IN stopId BIGINT DEFAULT 0,
IN tripId INTEGER DEFAULT 0
) LANGUAGE SQL
BEGIN
DECLARE updateTripId INTEGER DEFAULT 0;
DECLARE minStopId BIGINT DEFAULT 0;
DECLARE maxStopId BIGINT DEFAULT 0;
DECLARE TripSearch_cursor CURSOR FOR
SELECT s1.fkTripsId
FROM live.paymentsTripsStops s1
JOIN live.Trips t ON s1.fkTripsId = t.Id
WHERE s1.fkStopsId = stopId
FETCH FIRST 1 ROWS ONLY;
DECLARE minMaxStop_cursor CURSOR FOR
SELECT
COALESCE(
(
SELECT s.Id
FROM live.Stops s
JOIN live.Trips t ON s.fkTripsId = t.Id
ORDER BY s.Sequence
FETCH FIRST 1 ROWS ONLY
),
0
) AS firstStopId,
COALESCE(
(
SELECT s.Id
FROM live.Stops s
JOIN live.Trips t ON s.fkTripsId = t.Id
ORDER BY s.Sequence DESC
FETCH FIRST 1 ROWS ONLY
),
0
) AS lastStopId
FROM live.Trips t
WHERE t.Id = updateTripId
FETCH FIRST 1 ROWS ONLY;
IF TripId > 0
THEN SET updateTripId = TripId;
ELSE OPEN TripSearch_cursor;
FETCH FROM TripSearch_cursor INTO updateTripId;
CLOSE TripSearch_cursor;
END IF;
IF updateTripId > 0
THEN OPEN minMaxStop_cursor;
FETCH FROM minMaxStop_cursor INTO minStopId, maxStopId;
CLOSE minMaxStop_cursor;
UPDATE live.Trips
SET fkFirstStopId = minStopId,
fkLastStopId = maxStopId
WHERE intId = updateTripId;
END IF;
END

Pl/Sql Storing multiple result of select query in single object in oracle

I have a scenario,a procedure in which there is for loop in which there is a select query which returns different result each time it run. I have to store each and every result in single variable or array or cursor.Please suggest!!.Below provided is the procedure written in a package:
Package Spec:
TYPE t_product_id IS TABLE OF products.productnr%TYPE
INDEX BY PLS_INTEGER;
TYPE t_product_desc IS TABLE OF varchar(100)
INDEX BY PLS_INTEGER;
Type t_product_psnr IS TABLE OF prodsubs.PSNR%TYPE
INDEX BY PLS_INTEGER;
TYPE t_prod_contractperiod IS TABLE OF prodrul.CONTRPEROFPSNR%TYPE
INDEX BY PLS_INTEGER;
Type t_output IS TABLE OF prodrul.ALLOWEDCUSTTYPES%TYPE
INDEX BY PLS_INTEGER;
Package body:
PROCEDURE x_proc_Validprodcusttypeacctyp (
i_prodnr IN t_product_id,
i_invoiceprd IN prodrul.ALLOWEDINVPERIOD%TYPE,
i_psnr IN t_product_psnr,
i_contactprd IN t_prod_contractperiod,
i_result OUT t_output
)
IS
BEGIN
FOR i IN i_prodnr.FIRST .. i_prodnr.LAST
`LOOP
BEGIN
select PRODUCTNR,PRODUCTUSERKEY,ALLOWEDCUSTTYPES,ALLOWEDACCNTTYPES
into i_result (i)`
from prodrul rul join products prods on rul.PRODUCTNROFPSNR = prods.productnr
where prods.productnr = i_prodnr (i) and ALLOWEDINVPERIOD= i_invoiceprd and CONTRPEROFPSNR= i_contactprd (i)
and prods.productnr not in(select productnr from products where SMARTCARDYN = 1)
and rul.PRODUCTNROFPSNR not in (select PSPRODUCTNR from prodsubs join decoders on prodsubs.PSNR = decoders.DECSCPSNR where prodsubs.PSNR= i_psnr (i));
END;
END LOOP;
END x_proc_Validprodcusttypeacctyp;
You can use sys_refcursor as a collection object to return multiple columns instead of using arrays.
E.g.
CREATE OR REPLACE PROCEDURE x_proc_validprodcusttypeacctyp (
i_prodnr IN t_product_id,
i_invoiceprd IN prodrul.allowedinvperiod%TYPE,
i_psnr IN t_product_psnr,
i_contactprd IN t_prod_contractperiod,
i_result OUT t_output,
o_cursor OUT SYS_REFCURSOR
)
IS
BEGIN
OPEN o_cursor FOR
SELECT productnr, productuserkey, allowedcusttypes, allowedaccnttypes
FROM prodrul rul
JOIN
products prods
ON rul.productnrofpsnr = prods.productnr
WHERE prods.productnr = i_prodnr
AND allowedinvperiod = i_invoiceprd
AND contrperofpsnr = i_contactprd
AND prods.productnr NOT IN (SELECT productnr
FROM products
WHERE smartcardyn = 1)
AND rul.productnrofpsnr NOT IN
(SELECT psproductnr
FROM prodsubs
JOIN
decoders
ON prodsubs.psnr =
decoders.decscpsnr
WHERE prodsubs.psnr = i_psnr);
END;
/

Using IndexOf and/Or Substring to parse data into separate columns

I am working on migrating data from one database to another for a hospital. In the old database, the doctor's specialty IDs are all in one column (swvar_specialties), each separated by commas. In the new database, each specialty ID will have it's own column (example: Specialty1_PrimaryID, Specialty2_PrimaryID, Specialty3_PrimaryID, etc). I am trying to export the data out of the old database and separate these into these separate columns. I know I can use indexof and substring to do this - I just need help with the syntax.
So this query:
Select swvar_specialties as Specialty1_PrimaryID
From PhysDirectory
might return results similar to 39,52,16. I need this query to display Specialty1_PrimaryID = 39, Specialty2_PrimaryID = 52, and Specialty3_PrimaryID = 16 in the results. Below is my query so far. I will eventually have a join to pull the specialty names from the specialties table. I just need to get this worked out first.
Select pd.ref as PrimaryID, pd.swvar_name_first as FirstName, pd.swvar_name_middle as MiddleName,
pd.swvar_name_last as LastName, pd.swvar_name_suffix + ' ' + pd.swvar_name_degree as NameSuffix,
pd.swvar_birthdate as DateOfBirth,pd.swvar_notes as AdditionalInformation, 'images/' + '' + pd.swvar_photo as ImageURL,
pd.swvar_philosophy as PhilosophyOfCare, pd.swvar_gender as Gender, pd.swvar_specialties as Specialty1_PrimaryID, pd.swvar_languages as Language1_Name
From PhysDirectory as pd
The article Split function equivalent in T-SQL? provides some details on how to use a split function to split a comma-delimited string.
By modifying the table-valued function in presented in this article to provide an identity column we can target a specific row such as Specialty1_PrimaryID:
/*
Splits string into parts delimitered with specified character.
*/
CREATE FUNCTION [dbo].[SDF_SplitString]
(
#sString nvarchar(2048),
#cDelimiter nchar(1)
)
RETURNS #tParts TABLE (id bigint IDENTITY, part nvarchar(2048) )
AS
BEGIN
if #sString is null return
declare #iStart int,
#iPos int
if substring( #sString, 1, 1 ) = #cDelimiter
begin
set #iStart = 2
insert into #tParts
values( null )
end
else
set #iStart = 1
while 1=1
begin
set #iPos = charindex( #cDelimiter, #sString, #iStart )
if #iPos = 0
set #iPos = len( #sString )+1
if #iPos - #iStart > 0
insert into #tParts
values ( substring( #sString, #iStart, #iPos-#iStart ))
else
insert into #tParts
values( null )
set #iStart = #iPos+1
if #iStart > len( #sString )
break
end
RETURN
END
Your query can the utilise this split function as follows:
Select
pd.ref as PrimaryID,
pd.swvar_name_first as FirstName,
pd.swvar_name_middle as MiddleName,
pd.swvar_name_last as LastName,
pd.swvar_name_suffix + ' ' + pd.swvar_name_degree as LastName,
pd.swvar_birthdate as DateOfBirth,pd.swvar_notes as AdditionalInformation,
'images/' + '' + pd.swvar_photo as ImageURL,
pd.swvar_philosophy as PhilosophyOfCare, pd.swvar_gender as Gender,
(Select part from SDF_SplitString(pd.swvar_specialties, ',') where id=1) as Specialty1_PrimaryID,
(Select part from SDF_SplitString(pd.swvar_specialties, ',') where id=2) as Specialty2_PrimaryID,
pd.swvar_languages as Language1_Name
From PhysDirectory as pd

Update column to be different aggregate values

I am creating a script that for "merging" and deleting duplicate rows from a table. The table contains address information, and uses an integer field for storing information about the email as bit flags (column name lngValue). For example, lngValue & 1 == 1 means its the primary address.
There are instances of the same email being entered twice, but sometimes with different lngValues. To resolve this, I need to take the lngValue from all duplicates and assign them to one surviving record and delete the rest.
My biggest headache so far as been with the "merging" of the records. What I want to do is bitwise or all lngValues of duplicate records together. Here is what I have so far, which only finds the value of all lngValues bitwise or'ed together.
Warning: messy code ahead
declare #duplicates table
(
lngInternetPK int,
lngContactFK int,
lngValue int
)
insert into #duplicates (lngInternetPK, lngContactFK, lngValue)
(
select tblminternet.lngInternetPK, tblminternet.lngContactFK, tblminternet.lngValue from tblminternet inner join
(select strAddress, lngcontactfk, count(*) as count from tblminternet where lngValue & 256 <> 256 group by strAddress, lngcontactfk) secondemail
On tblminternet.strAddress = secondemail.strAddress and
tblminternet.lngcontactfk = secondemail.lngcontactfk
where count > 1 and tblminternet.strAddress is not null and tblminternet.lngValue & 256 <> 256 --order by lngContactFK, strAddress
)
update #duplicates set lngValue = t.val
from
(select (sum(dupes.lngValue) & 65535) as val from
(select here.lngInternetPK, here.lngContactFK, here.lngValue from tblminternet here inner join
(select strAddress, lngcontactfk, count(*) as count from tblminternet where lngValue & 256 <> 256 group by strAddress, lngcontactfk) secondemail
On here.strAddress = secondemail.strAddress and
here.lngcontactfk = secondemail.lngcontactfk
where count > 1 and here.strAddress is not null and here.lngValue & 256 <> 256) dupes, tblminternet this
where this.lngContactFK = dupes.lngContactFK
) t
where lngInternetPK in (select lngInternetPK from #duplicates)
Edit:
As requested here is some sample data:
Table Name: tblminternet
Column Names:
lngInternetPK
lngContactFK
lngValue
strAddress
Example row 1:
lngInternetPK: 1
lngContactFK: 1
lngValue: 33
strAddress: "me#myaddress.com"
Example row 2:
lngInternetPK: 2
lngContactFK: 1
lngValue: 40
strAddress: "me#myaddress.com"
If these two were merged here is the desired result:
lngInternetPK: 1
lngContactFK: 1
lngValue: 41
strAddress: "me#myaddress.com"
Other necessary rules:
Each contact can have multiple emails, but each email row must be distinct ( each email can only appear as one row).
SQL Server lacks native bitwise aggregates, that's why we need to emulate them.
The main idea here is to generate a set of bits from 0 to 15, for each bit apply the bitmask to the value and select MAX (which will give us an OR for a given bit), then select the SUM (which will merge the bit masks).
The we just update the first lngInternetPK for any given (lngContactFK, strValue) with the new value of lngValue, and delete all duplicates.
;WITH bits AS
(
SELECT 0 AS b
UNION ALL
SELECT b + 1
FROM bits
WHERE b < 15
),
v AS
(
SELECT i.*,
(
SELECT SUM(value)
FROM (
SELECT MAX(lngValue & POWER(2, b)) AS value
FROM tblmInternet ii
CROSS JOIN
bits
WHERE ii.lngContactFK = i.lngContactFK
AND ii.strAddress = i.strAddress
GROUP BY
b
) q
) AS lngNewValue
FROM (
SELECT ii.*, ROW_NUMBER() OVER (PARTITION BY lngContactFK, strAddress ORDER BY lngInternetPK) AS rn
FROM tblmInternet ii
) i
WHERE rn = 1
)
UPDATE v
SET lngValue = lngNewValue;
;WITH v AS
(
SELECT ii.*, ROW_NUMBER() OVER (PARTITION BY lngContactFK, strAddress ORDER BY lngInternetPK) AS rn
FROM tblmInternet ii
)
DELETE v
WHERE rn > 1
See this article in my blog for more detailed explanations:
SQL Server: aggregate bitwise OR
I believe the following query gets you what you want. This routine assumes a max of two duplicate addresses per contact. If there's more than one dup per contact, the query will have to be modified. I hope this helps.
Declare #tblminternet
Table
( lngInternetPK int,
lngContactFK int,
lngValue int,
strAddress varchar(255)
)
Insert Into #tblminternet
select 1, 1, 33, 'me#myaddress.com'
union
select 2, 1, 40, 'me#myaddress.com'
union
select 3, 2, 33, 'me#myaddress2.com'
union
select 4, 2, 40, 'me#myaddress2.com'
union
select 5, 3, 2, 'me#myaddress3.com'
--Select * from #tblminternet
Select Distinct
A.lngContactFK ,
A.lngValue | B.lngValue as 'Bitwise OR',
A.strAddress
From #tblminternet A, #tblminternet B
Where A.lngContactFK = B.lngContactFK
And A.strAddress = B.strAddress
And A.lngInternetPK != B.lngInternetPK
You can create SQL Server Aggregate functions in .NET that you can then implement in SQL server inline. I think this requires a minimum of SQL server 2005 and Visual Studio 2010. I did one using Visual Studio 2013 Community Edition (free even for commercial use) for use with .NET 2 and SQL Server 2005.
See the MSDN article: https://msdn.microsoft.com/en-us/library/91e6taax(v=vs.90).aspx
First you'll need to enable the CLR feature in SQL server: https://msdn.microsoft.com/en-us/library/ms131048.aspx
sp_configure 'show advanced options', 1;
GO
RECONFIGURE;
GO
sp_configure 'clr enabled', 1;
GO
RECONFIGURE;
GO
Create a SQL Server -> SQL Server Database Project
Right-click on the new project and select Properties
Configure the targeted SQL Server version under Project Settings
Configure the targeted CLR language under SQL CLR (such as VB)
Right-click on the new project and select Add -> New Item...
When the dialog pops up, select SQL Server -> SQL CLR VB -> SQL CLR VB Aggregate
Now you can write your bitwise code in VB:
Imports System
Imports System.Data
Imports System.Data.SqlClient
Imports System.Data.SqlTypes
Imports Microsoft.SqlServer.Server
<Serializable()> _
<Microsoft.SqlServer.Server.SqlUserDefinedAggregate(Format.Native)> _
Public Structure AggregateBitwiseOR
Private CurrentAggregate As SqlTypes.SqlInt32
Public Sub Init()
CurrentAggregate = 0
End Sub
Public Sub Accumulate(ByVal value As SqlTypes.SqlInt32)
'Perform Bitwise OR against aggregate memory
CurrentAggregate = CurrentAggregate OR value
End Sub
Public Sub Merge(ByVal value as AggregateBitwiseOR)
Accumulate(value.Terminate())
End Sub
Public Function Terminate() As SqlInt32
Return CurrentAggregate
End Function
End Structure
Now deploy it: https://msdn.microsoft.com/en-us/library/dahcx0ww(v=vs.90).aspx
Build the project using the menu bar: Build -> Build ProjectName (if the build fails with error 04018 then download a new version of the data tools # http://msdn.microsoft.com/en-US/data/hh297027 or by going to the menu bar: Tools -> Extensions And Updates, then under updates select update for Microsoft SQL Server Update For Database Tooling)
Copy your compiled DLL to C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Binn and to C:\
Register the DLL:
CREATE ASSEMBLY [CLRTools] FROM ‘c:CLRTools.dll’ WITH PERMISSION_SET = SAFE
Create the aggregate in SQL:
CREATE AGGREGATE [dbo].[AggregateBitwiseOR](#value INT)
RETURNS INT
EXTERNAL NAME [CLRTools].[CLRTools.AggregateBitwiseOR];
If you get the error "Incorrect syntax near 'EXTERNAL'" then change the database compatibility level using following commands:
For SQL Server 2005: EXEC sp_dbcmptlevel 'DatabaseName', 90
For SQL Server 2008: EXEC sp_dbcmptlevel 'DatabaseName', 100
Test your code:
SELECT dbo.AggregateBitwiseOR(Foo) AS Foo FROM Bar
I found this article helpful: http://www.codeproject.com/Articles/37377/SQL-Server-CLR-Functions