Having a stored procedure in Oracle which I need to call from a service in Java using Spring Data JPA.
Tested the procedure in sql developer and found to be working fine. It takes some 6 IN parameters and returns a ref_cursor.
Please help me on know how to execute this.
The Procedure:
create or replace PROCEDURE GET_CONF(PROD_NAME IN VARCHAR2, DERIVATIVE IN VARCHAR2, PHY_SEC_SIZE IN VARCHAR2, PROD_CACH IN VARCHAR2, CAPACITY IN VARCHAR2,ENC_TYPE IN VARCHAR2, INTER_VAL IN VARCHAR2, HEADS IN VARCHAR2,DISCS IN VARCHAR2, AREALDENSITY IN VARCHAR2, DESIGNSITE IN VARCHAR2, RSLT OUT SYS_REFCURSOR) AS ....
Here the first two parameters are mandatory fields in screen, so definitely it has value. But the other fields are optional.
The Spring data repository method is as below:
#Procedure(procedureName = "get_conf")
List<Object[]> testjpa(String prodName, String derivative, String phySize,
String prodCache, String capacity, String encType,
String interfaceVal, String heads, String discs,
String arealDensity, String desingSite);
I have tried giving the return type as List and List. But nothing worked out.
If I am returning only one value, everything works fine. Problem comes in when I am returning multiple values and multiple rows.
The error that I am getting is as below
I tried this, but no luck.
Oracle Function
PROCEDURE p_get_region_cur (v_out_cur OUT SYS_REFCURSOR)
AS
BEGIN
OPEN v_out_cur FOR SELECT region_id, region_name FROM regions;
END;
Call from Java by using StoredProcedureQuery
StoredProcedureQuery query = entityManager.createStoredProcedureQuery("p_get_region_cur", Region.class);
query.registerStoredProcedureParameter(1, void.class, ParameterMode.REF_CURSOR);
List<Region> result = query.getResultList();
Related
I have a Stored Procedure that inserts, updates or deletes tablerows. It was working fine while all parameters were used as input. However, I need to return the ID of last inserted row. For that I tried using an INOUT parameter and RETURNING after the INSERT statement to return the ID.
However, I am not sure how to bind the returned ID to the INOUT parameter. Following is the code for stored procedure:
CREATE OR REPLACE PROCEDURE public.spproductinsertupdatedelete(
_ser integer,
_subcategid integer,
_inrprice numeric,
_usdprice numeric,
_colour integer,
_size integer,
_qty integer,
_prodid integer DEFAULT NULL::integer,
inout _pid integer default null
)
LANGUAGE 'plpgsql'
AS $BODY$
BEGIN
if _ser=1 then --- Insert
INSERT INTO product (prod_subcateg_id,prod_inr_price,prod_usd_price,prod_colour,prod_size,prod_qty)
VALUES (_subcategID, _inrprice, _usdprice, _colour, _size, _qty)
RETURNING prod_id;
ELSEIF _ser=2 THEN
UPDATE PRODUCT SET
prod_subcateg_id = _subcategid,
prod_inr_price = _inrprice,
prod_usd_price = _usdprice,
prod_size = _size,
prod_colour = _colour,
prod_qty=_qty
where prod_id = _prodID;
ELSEIF _ser=3 THEN ---- Delete
UPDATE PRODUCT SET prod_datetill = now()
WHERE prod_id = _prodID;
end if;
END
$BODY$;
On executing above stored procedure, I receive this error:
ERROR: query has no destination for result data
Proof of concept
A PROCEDURE can return values, but in a very limited fashion (as of Postgres 13).
The manual on CALL:
CALL executes a procedure.
If the procedure has any output parameters, then a result row will be
returned, containing the values of those parameters.
The manual on CREATE PROCEDURE:
argmode
The mode of an argument: IN, INOUT, or VARIADIC. If omitted, the default is IN. (OUT arguments are currently not supported for procedures. Use INOUT instead.)
So your use of the INOUT mode is correct. But the assignment in the function body is missing. And some other things are wrong / suboptimal. I suggest:
CREATE OR REPLACE PROCEDURE public.spproductinsertupdatedelete(
_ser int
, _subcategid int
, _inrprice numeric
, _usdprice numeric
, _colour int
, _size int
, _qty int
, INOUT _prod_id int DEFAULT NULL
)
LANGUAGE plpgsql AS
$proc$
BEGIN
CASE _ser -- simpler than IF
WHEN 1 THEN -- INSERT
INSERT INTO product
(prod_subcateg_id, prod_inr_price, prod_usd_price, prod_colour, prod_size, prod_qty)
VALUES (_subcategid , _inrprice , _usdprice , _colour , _size , _qty )
RETURNING prod_id
INTO _prod_id; -- !!!
WHEN 2 THEN -- UPDATE
UPDATE product
SET (prod_subcateg_id, prod_inr_price, prod_usd_price, prod_size, prod_colour, prod_qty)
= (_subcategid , _inrprice , _usdprice , _size , _colour , _qty)
WHERE prod_id = _prod_id;
WHEN 3 THEN -- soft-DELETE
UPDATE product
SET prod_datetill = now()
WHERE prod_id = _prod_id;
ELSE
RAISE EXCEPTION 'Unexpected _ser value: %', _ser;
END CASE;
END
$proc$;
db<>fiddle here
Take this as proof of concept. But I see nothing in the question warranting the use of a PROCEDURE in the first place.
You probably want a FUNCTION
A FUNCTION offers more options to return values, doesn't need to be run separately with CALL, and can be integrated in bigger queries. Chances are, that's what you wanted in the first place, and you were just being mislead by the widespread misnomer "stored procedure". See:
How to get result set from PostgreSQL stored procedure?
Moreover, in the current form, you have to provide many noise parameters if you want to update or soft-delete a row. Plain SQL commands might do the job. Or separate functions ...
The rule of thumb: if you don't need to manage transactions from within, you probably want to use a function instead of a procedure. Later, Postgres procedures may be extended to be able and return multiple result sets (per SQL standard), but not yet (pg 13).
See:
In PostgreSQL, what is the difference between a “Stored Procedure” and other types of functions?
Do stored procedures run in database transaction in Postgres?
https://www.2ndquadrant.com/en/blog/postgresql-11-server-side-procedures-part-1/#comment-72
What are the differences between “Stored Procedures” and “Stored Functions”?
I am calling a stored procedure from JPA EclipseLink. Stored Procedure returns three output variables, One cursor type and other two types. I am able to get the value returned from cursor but not the other two values.
Here is the part of stored procedure(example):
create or replace PROCEDURE PROCEDURE1 (
Variable1 IN VARCHAR2,
Variable12 IN VARCHAR2,
Variable13 IN VARCHAR2,
Variable14 IN VARCHAR2,
p_xml_result OUT SYS_REFCURSOR,
p_errorcode OUT NUMBER,
p_errorMessage OUT VARCHAR2)
IS
-- Declare variable
ERRSQL VARCHAR2 (80);
NOSTRAECCEZIONE EXCEPTION;
ERROR_ONE EXCEPTION;
V_AREA VARCHAR2(40);
BEGIN
SELECT AREA
INTO V_AREA
FROM Employee
WHERE Employee_CODE = P_EmployeeCode;
OPEN p_xml_result FOR
<Some query to get data>
p_errorcode := 0;
p_errorMessage := NULL;
EXCEPTION
WHEN NO_DATA_FOUND THEN
p_errorcode := 100;
p_errorMessage := 'generic error';
WHEN OTHERS THEN
p_errorcode := 102;
p_errorMessage := SQLERRM;
Java Code:
StoredProcedureCall procCall = new StoredProcedureCall();
procCall.setProcedureName("getSpecialOffers");
procCall.addNamedArgument("Variable1","Variable1",String.class);
procCall.addNamedArgument("Variable2", "Variable12", String.class);
procCall.addNamedArgument("Variable13", "Variable13", String.class);
procCall.addNamedArgument("Variable14", "Variable14", String.class);
procCall.addNamedOutputArgument("p_xml_result", "p_xml_result", OracleTypes.CURSOR);
procCall.addNamedOutputArgument("p_errorcode", "p_errorcode", Integer.class);
procCall.addNamedOutputArgument("p_errorMessage", "p_errorMessage", String.class);
Session session = ((JpaEntityManager)_em).getActiveSession();
ValueReadQuery query = new ValueReadQuery();
query.setCall(procCall);
query.addArgument("Variable1");
query.addArgument("Variable12");
query.addArgument("Variable13");
query.addArgument("Variable14");
Vector parameters = new Vector();
parameters.addElement(Variable1);
parameters.addElement(Variable12);
parameters.addElement(Variable13);
parameters.addElement(Variable14);
Object qResult = null;
qResult = session.executeQuery(query,parameters);
Vector vec=(Vector) qResult;
ArrayRecord arrayrec =(ArrayRecord) vec.get(0);
p_xml_result returns four field, which I am able to fetch as arrayrec.get("VariableXYZ"). and arrayrec also has only those four variables present which are returned from cursor. But I want to access the value of p_errorcode in my code in all the conditions. Can you please help me with it?
I got the desired output params by changing ValueReadQuery to DataReadQuery.
I have a working stored function on postgresql db
create or replace function sp1(d1 date, d2 date)
returns table(ServiceType varchar, counter bigint) as $$
begin
return query select servicerequesttype, count(*)as counter from events
where creationdate>=d1 and creationdate<=d2
group by servicerequesttype
order by(counter) desc;
end;
$$
language plpgsql;
which returns a table with two columns, varchar and bigint. I execute it like
select * from sp1();
Now I want to use this on hibernate. As I understand I want to execute a raw query on hibernate and create a List for the result.
Is this possible, or I have to rewrite the stored procedures all over?
You can create a view in the database and then in Java code, you can query view
This is a technique used when you have complex queries or aggregated values.
#Entity
#Table(name = "V_SERVICE_VIEW")
public class ServiceView {
..
..
}
I implemented this function in my Postgres database: http://www.cureffi.org/2013/03/19/automatically-creating-pivot-table-column-names-in-postgresql/
Here's the function:
create or replace function xtab (tablename varchar, rowc varchar, colc varchar, cellc varchar, celldatatype varchar) returns varchar language plpgsql as $$
declare
dynsql1 varchar;
dynsql2 varchar;
columnlist varchar;
begin
-- 1. retrieve list of column names.
dynsql1 = 'select string_agg(distinct '||colc||'||'' '||celldatatype||''','','' order by '||colc||'||'' '||celldatatype||''') from '||tablename||';';
execute dynsql1 into columnlist;
-- 2. set up the crosstab query
dynsql2 = 'select * from crosstab (
''select '||rowc||','||colc||','||cellc||' from '||tablename||' group by 1,2 order by 1,2'',
''select distinct '||colc||' from '||tablename||' order by 1''
)
as ct (
'||rowc||' varchar,'||columnlist||'
);';
return dynsql2;
end
$$;
So now I can call the function:
select xtab('globalpayments','month','currency','(sum(total_fees)/sum(txn_amount)*100)::decimal(48,2)','text');
Which returns (because the return type of the function is varchar):
select * from crosstab (
'select month,currency,(sum(total_fees)/sum(txn_amount)*100)::decimal(48,2)
from globalpayments
group by 1,2
order by 1,2'
, 'select distinct currency
from globalpayments
order by 1'
) as ct ( month varchar,CAD text,EUR text,GBP text,USD text );
How can I get this function to not only generate the code for the dynamic crosstab, but also execute the result? I.e., the result when I manually copy/paste/execute is this. But I want it to execute without that extra step: the function shall assemble the dynamic query and execute it:
Edit 1
This function comes close, but I need it to return more than just the first column of the first record
Taken from: Are there any way to execute a query inside the string value (like eval) in PostgreSQL?
create or replace function eval( sql text ) returns text as $$
declare
as_txt text;
begin
if sql is null then return null ; end if ;
execute sql into as_txt ;
return as_txt ;
end;
$$ language plpgsql
usage: select * from eval($$select * from analytics limit 1$$)
However it just returns the first column of the first record :
eval
----
2015
when the actual result looks like this:
Year, Month, Date, TPV_USD
---- ----- ------ --------
2016, 3, 2016-03-31, 100000
What you ask for is impossible. SQL is a strictly typed language. PostgreSQL functions need to declare a return type (RETURNS ..) at the time of creation.
A limited way around this is with polymorphic functions. If you can provide the return type at the time of the function call. But that's not evident from your question.
Refactor a PL/pgSQL function to return the output of various SELECT queries
You can return a completely dynamic result with anonymous records. But then you are required to provide a column definition list with every call. And how do you know about the returned columns? Catch 22.
There are various workarounds, depending on what you need or can work with. Since all your data columns seem to share the same data type, I suggest to return an array: text[]. Or you could return a document type like hstore or json. Related:
Dynamic alternative to pivot with CASE and GROUP BY
Dynamically convert hstore keys into columns for an unknown set of keys
But it might be simpler to just use two calls: 1: Let Postgres build the query. 2: Execute and retrieve returned rows.
Selecting multiple max() values using a single SQL statement
I would not use the function from Eric Minikel as presented in your question at all. It is not safe against SQL injection by way of maliciously malformed identifiers. Use format() to build query strings unless you are running an outdated version older than Postgres 9.1.
A shorter and cleaner implementation could look like this:
CREATE OR REPLACE FUNCTION xtab(_tbl regclass, _row text, _cat text
, _expr text -- still vulnerable to SQL injection!
, _type regtype)
RETURNS text
LANGUAGE plpgsql AS
$func$
DECLARE
_cat_list text;
_col_list text;
BEGIN
-- generate categories for xtab param and col definition list
EXECUTE format(
$$SELECT string_agg(quote_literal(x.cat), '), (')
, string_agg(quote_ident (x.cat), %L)
FROM (SELECT DISTINCT %I AS cat FROM %s ORDER BY 1) x$$
, ' ' || _type || ', ', _cat, _tbl)
INTO _cat_list, _col_list;
-- generate query string
RETURN format(
'SELECT * FROM crosstab(
$q$SELECT %I, %I, %s
FROM %I
GROUP BY 1, 2 -- only works if the 3rd column is an aggregate expression
ORDER BY 1, 2$q$
, $c$VALUES (%5$s)$c$
) ct(%1$I text, %6$s %7$s)'
, _row, _cat, _expr -- expr must be an aggregate expression!
, _tbl, _cat_list, _col_list, _type);
END
$func$;
Same function call as your original version. The function crosstab() is provided by the additional module tablefunc which has to be installed. Basics:
PostgreSQL Crosstab Query
This handles column and table names safely. Note the use of object identifier types regclass and regtype. Also works for schema-qualified names.
Table name as a PostgreSQL function parameter
However, it is not completely safe while you pass a string to be executed as expression (_expr - cellc in your original query). This kind of input is inherently unsafe against SQL injection and should never be exposed to the general public.
SQL injection in Postgres functions vs prepared queries
Scans the table only once for both lists of categories and should be a bit faster.
Still can't return completely dynamic row types since that's strictly not possible.
Not quite impossible, you can still execute it (from a query execute the string and return SETOF RECORD.
Then you have to specify the return record format. The reason in this case is that the planner needs to know the return format before it can make certain decisions (materialization comes to mind).
So in this case you would EXECUTE the query, return the rows and return SETOF RECORD.
For example, we could do something like this with a wrapper function but the same logic could be folded into your function:
CREATE OR REPLACE FUNCTION crosstab_wrapper
(tablename varchar, rowc varchar, colc varchar,
cellc varchar, celldatatype varchar)
returns setof record language plpgsql as $$
DECLARE outrow record;
BEGIN
FOR outrow IN EXECUTE xtab($1, $2, $3, $4, $5)
LOOP
RETURN NEXT outrow
END LOOP;
END;
$$;
Then you supply the record structure on calling the function just like you do with crosstab.
Then when you all the query you would have to supply a record structure (as (col1 type, col2 type, etc) like you do with connectby.
Suppose I have following SP to run a dynamic sql
ALTER PROCEDURE [dbo].[MySP]
AS
BEGIN
declare #sql varchar(4000)
select #sql = 'select cnt = count(*) from Mytable ..... ';
exec (#sql)
END
then in edmx, I add the sp and import function for this sp. the return type is scalars int32.
then I want to use this function in code like:
int? result = context.MySP();
I got error said "cannot implicitly convert type System.Data.Objects.ObjectResults to int?"
If use
var result = context.MySP();
then Single() cann't be applied to context.MySP().
How to get the result for this case?
You may have already resolved this, but ...
I had done the same thing; my procedure was returning an integer so I selected Scalars and Int32, and it was returning System.Data.Objects.ObjectResult
Of course, the problem is the window states "Returns a Collection Of" (emphasis mine).
Durr.
Select None instead of Scalars and the appropriate result (int) will be returned.
Try changing your result datatype to System.Nullable<int>. See this post.