I've got the following types (A row type and a table of that kind of row):
CREATE TYPE t_tf_row AS OBJECT (
id NUMBER,
description VARCHAR2(50)
);
/
CREATE TYPE t_tf_tab IS TABLE OF t_tf_row;
/
And the following pipelined function which returns information for that kind of table (for this example it just prints a basic iteration).
CREATE OR REPLACE FUNCTION get_tab_ptf (p_rows IN NUMBER) RETURN t_tf_tab PIPELINED AS
BEGIN
FOR i IN 1 .. p_rows LOOP
PIPE ROW(t_tf_row(i, 'Description for ' || i));
END LOOP;
RETURN;
END;
/
Instead of using a pipelined function with its definition like above, I want to use a java stored procedure that does the exact same thing. How can I achieve the same output from this?
I want to be able to call the java stored procedure/function just like the following:
SELECT *
FROM TABLE(get_tab_ptf(10))
ORDER BY id DESC;
Thanks
PS: This question is meant for learning purposes only.
Related
I post a similar question previously, but have opened another question to be more specific as the previous one gave me a solution but I have now encountered another problem.
We have an existing Oracle database that had boolean columns defined like so
CREATE TABLE MY_TABLE
(
SOME_TABLE_COLUMN NUMBER(1) DEFAULT 0 NOT NULL,
etc..
And with the corresponding java field defined like private boolean someTableColumn; I've come to understand this is because Oracle does not have a Boolean datatype, but under the hood does the conversion from boolean to integer when inserting data, and the reverse when retriving data.
This has caused an issue when I have been working on migrating our database from Oracle to Postgres. Thanks to answers on my previous question, I have migrated the column type from NUMBER(1) to BOOLEAN. This has solved the issue with inserting data. However, our codebase uses JDBCTemplate and we unfortunately have hundereds of hardcoded queries in the code that make queries like SELECT * FROM MY_TABLE WHERE TABLE_COLUMN=1.
When these run against the Postgres DB, I get the following error ERROR: operator does not exist: boolean = integer. We have a requirement to have backwards compatability with Oracle, so I can't simply update these queries to replace 1 and 0 with TRUE and FALSE respectively.
Is there a way I can configure Postgres so it can do a conversion behind the scenes to resolve this? I have looked at casts but I don't really understand the documentation and the examples given don't seem to match my use case. Any help is appreciated.
Can you try to use '0' and '1' instead of 0 and 1 in your requests ?
I used to work on apps compliant with both Oracle and Postgresql using this syntax. Apps were using JPA but can say with certainty that we were also using this syntax with nativeQuery = true.
Note: I would have posted this as a comment but I don't have the required reputation to do so, hence the post as an answer
Update:
Duh! Brain Freeze. On later thought there is a way to get this conversion in both directions. Process via a View.
Steps:
Rename your table.
Create a view having the same name as the old table. In this view
translate the boolean column to the appropriate integer.
Create an trigger function and an instead of trigger on the view for insert/update dml. In the trigger function translate the column value to boolean as appropriate.
See revised demo.
alter table testb rename to testb_tab;
create or replace view testb (id, name, is_ok)
as
select i,
, name
, is_ok::int
from testb_tab;
create or replace
function testb_act_dml()
returns trigger
language plpgsql
as $$
begin
if tg_op = 'INSERT' then
insert into testb_tab(name,is_ok)
values (new.name, new.is_ok::boolean) ;
else
update testb_tab
set name = new.name
, is_ok = new.is_ok::boolean
where id = old.id;
end if;
return new;
end;
$$;
create trigger testb_biuri -- before insert update row instead of
instead of insert or update on testb
for each row execute function testb_act_dml();
Finally, there is another option which probably has the lease work. Do not change the column description to Boolean. Either leave it as an integer or define it as a smallint. Either way a check constraint may come in useful. So something along the line of:
create table tests( id int generated always as identity primary key
, name text
, is_ok smallint
, constraint is_ok_ck
check ( is_ok in (0,1) or is_ok is null)
);
This is one of the issues I have with the concept of "database independence". It simply does not exist. Vendors implementation often simply vary too much. In this case Postures to the rescue, perhaps but also perhaps extreme: create you own create your own operators. Proceed with caution:
-- function to compare boolean = integer
create or replace
function"__bool=int"( b boolean, i int)
returns boolean
language sql
as $$
select (b=i::boolean);
$$;
-- create the Operator for boolean = integer
create operator = (
leftarg = boolean
, rightarg = int
, function = "__bool=int"
, commutator = =
);
The above will not allow your code: SELECT * FROM MY_TABLE WHERE TABLE_COLUMN=1 (see demo here).
However, this road may lead to unexpected twists, and lots of function/operator pairs. For example the above does not support SELECT * FROM MY_TABLE WHERE TABLE_COLUMN<>1. That requires another function/operator combination. Further I do not see a retrieval function that converts a boolean back to an integer. If you follow this path be sure to massively test your boolean-to-integer (integer-to-boolean) operations. It may just be better to just byte the bullet and updated those few queries (you did say hundreds not thousands) as #mlogario suggests.
I have a SQL function named as IP_ELEARN_PERSON.F_GET_PERSON(int, string, array).
Now I want to run this function directly from sql developer and I am trying to execute this function like this --
select IP_ELEARN_PERSON.F_GET_PERSON(32433,'SOURCED',('ALL')) from dual;
Now the problem is when I am trying to execute this funtion I am getting the following errror --
ORA-06553: PLS-306: wrong number or types of arguments in call to 'F_GET_PERSON'
06553. 00000 - "PLS-%s: %s"
*Cause:
*Action:
Error at Line: 3 Column: 8
We were calling this function from java by using setArray method of the CallableStatment like this--
cstmt.setArray(4, new ObtainSqlArrayFromJava().returnSqlArray(
underlyingConn, roles));
So my doubt is, is the way I used to mention the array in the query right?
I went through many Stack Overflow posts but no where any thing was written for arrays as an argument.
CREATE TYPE string_list IS TABLE OF VARCHAR2(100);
/
CREATE FUNCTION F_GET_PERSON (
id INT,
type VARCHAR2,
array string_list
) RETURN INT
AS
BEGIN
RETURN 0;
END;
/
SELECT F_GET_PERSON( 1, 'SOURCED', string_list( 'ALL' ) )
FROM DUAL;
If you want some java code for passing an array as a bind variable to a PreparedStatement then you can look at my answer here. You should be able to easily adapt it to a CallableStatement.
As stated here:
http://docs.oracle.com/cd/E11882_01/appdev.112/e25519/composites.htm#LNPLS00501
Oracle supports the following collections types: associative array, VARRAY (variable-size array), and nested table.
You'll need to properly initialize the collection before passing it to the function.
I am a beginner PLSQL user, and I have what might be a rather simple question.
I have created the following SQL Function, which returns the created date of the process whose corporate ID matches the corporate ID that I have given it. I have this connected to my JDBC, and it returns values just fine.
However, I just realized I overlooked an important issue--it's entirely possible that more than one process will have a corporate ID that matches the ID value I've inputted, and in cases like that I will need to be able to access all of the created date values where the IDs return a match.
CREATE OR REPLACE FUNCTION FUNCTION_1(
c_id IN INT)
RETURN INT
AS
p_date process.date_created%TYPE;
BEGIN
SELECT process.date_created
FROM PROCESS
WHERE process.corporate_id = c_id
ORDER BY process.corporate_id;
RETURN p_id;
END FUNCTION_1;
/
Is there a way that I can modify my function to return multiple rows from the same column, and then call that function to return some sort of array using JDBC? Or, if that isn't possible, is there a way I can return what I need using PLSQL procedures or just plain SQL combined with JDBC? I've looked through other questions here, but none seemed to be about quite what I need to know.
Thanks to anyone who can help!
you need make some changes in your function. on java side it will be simple select
you need change type of your function from int to collection
read about the table functions here Table Functions
user oracle table() function to convert result of your function to table
it let you use your function in queries. read more about the syntax here: Table Collections: Examples
here the example how to call your function from java:
select t.column_value as process_id
from table(FUNCTION_1(1)) t
--result
PROCESS_ID
1 1
2 2
--we need create new type - table of integers
CREATE OR REPLACE TYPE t_process_ids IS TABLE OF int;
--and make changes in function
CREATE OR REPLACE FUNCTION FUNCTION_1(
c_id IN INT)
RETURN t_process_ids
AS
l_ids t_process_ids := t_process_ids();
BEGIN
--here I populated result of select into the local variables
SELECT process.id
bulk collect into l_ids
FROM PROCESS
WHERE process.corporate_id = c_id
ORDER BY process.corporate_id;
--return the local var
return l_ids;
END FUNCTION_1;
--the script that I used for testing
create table process(id int, corporate_id int, date_created date);
insert into process values(1, 1, sysdate);
insert into process values(2, 1, sysdate);
insert into process values(3, 2, sysdate);
I have a type as:
CREATE TYPE status_record AS
(
id bigint,
status boolean
);
A procedure that does some processing with an array of type as input parameter as:
CREATE OR REPLACE FUNCTION update_status(status_list status_record[])
RETURNS text AS
$BODY$
DECLARE
BEGIN
--does some processing
return 'SUCCESS';
end;$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Finally I queried the procedure as:
select *
from update_status(cast(ARRAY[(385,false),(387,false)] as status_record[]));
Everything works fine in pgadmin. Later when I tried to call the same using Hibernate native SQL Query Ka Boom!!! The following is displayed:
org.postgresql.util.PSQLException:
ERROR: array value must start with "{" or dimension information
Final question: both ARRAY[--something] and {--something} does the same job?
Use an array literal (text representation of the array), since the array constructor ARRAY[...] has to be evaluated by Postgres:
SELECT update_status('{"(1,t)","(2,f)"}'::status_record[]);
Maybe even without the explicit cast:
SELECT update_status('{"(1,t)","(2,f)"}');
There were similar cases on SO before:
Pass array from node-postgres to plpgsql function
How to pass custom type array to Postgres function
Try to put the array and type initialization into a string, maybe you can then get around the problems with the obfuscation layer (aka ORM):
select update_status(cast('{"(1,true)", "(2,false)"}' as status_record[]));
I don't know Hibernate, so I can't tell if that will work.
I'm using hibernate to call the Postgres array_agg function. The problem is that I need to call the function with an order by included. I cannot pass the order by through hibernate in the correct syntax so I need to create a function that does exactly what array_agg does with ordering being automatic. Here's what I need to duplicate in a function:
array_agg(table.TEXT order by table.text asc)
I've tried and tried but I cannot figure it out - and google isn't helping much. Thanks in advance!
You can substitute the missing feature by feeding an ordered set to the aggregate function - which works in Postgres but is not the standard SQL way to do this (as #Richard mentioned in his comment):
SELECT array_agg(sub.text) AS ordered_array
FROM (SELECT text FROM tbl ORDER BY text) sub
Or, for a simple case like this, just use the array constructor instead:
SELECT ARRAY( SELECT text FROM tbl ORDER BY 1 ) AS ordered_array
You should be able to implement that with the limited means of Hibernate.
I figured it out just by creating a custom aggregate function and using the final function (FFUNC)
CREATE OR REPLACE FUNCTION order_func(anyarray) RETURNS anyarray AS $BODY$
BEGIN
return ARRAY(SELECT unnest($1) ORDER BY 1);
END;
$BODY$ LANGUAGE 'plpgsql';
CREATE AGGREGATE array_agg_order_func(anyelement) (
SFUNC=array_append,
STYPE=anyarray,
FFUNC=order_func,
INITCOND='{}'
);
This will then pass the aggregated array to the sorting function.