Hibernate: Pass array of Entities to PostgreSql function - postgresql

Hibernate: 5.4.0.Final
PostgreSql: 9.6.6 (It would be an option to upgrade to latest version)
I have tables A, B and C. And the following postgresql function:
CREATE OR REPLACE FUNCTION myFunction(param1 "A"[], param2 "B") RETURNS SETOF "C" AS $$
BEGIN
......
END;
$$ LANGUAGE plpgsql;
So basicly, I want to pass an array of records from table A and a single record of B. As result, I want a list of records from table C. I want to call this function from hibernate. Note that the passed entities from table A are not persistent so it is not an option to pass an array of ids. Instead I want to pass the whole (not persistent) entities.
My current hibernate code looks like this:
ProcedureCall call = session.createStoredProcedureCall("myFunction");
call.registerParameter(1, <Type?>, ParameterMode.IN).bindValue(listOfEntities);
call.registerParameter(2, EntityForTableB.class, ParameterMode.IN).bindValue(entityForTableB);
((ResultSetOutput)call.getOutputs().getCurrent()).getResultList();
I cannot identify the right way to pass the function parameters. Currently, I get this error:
org.postgresql.util.PSQLException: ERROR: Function myFunction(bytea, bytea) does not exist
What would be the correct way to handle this? I found solutions where the entities are transformed to json format but it looked complicated. Would appreciate any advice.

Related

Postgres: How to transform data in two cursors before comparison?

I am replacing a legacy function get_data in our database which takes some entity_id and returns a refcursor.
I am writing a new function get_data_new which is using different data sources, but the outputs are expected to be the same as get_data for the same input.
I'd like to verify this with pgtap, and am doing so as follows within a test (with _expected and _actual being the names of the returned cursors):
SELECT schema.get_data('_expected', 123);
SELECT schema.get_data_new('_actual', 123);
SELECT results_eq(
'FETCH ALL FROM _actual',
'FETCH ALL FROM _expected',
'get_data_new should return identical results to the legacy version'
);
This works as expected for other functions, but the query in get_data happens to return some json columns meaning that comparison expectedly fails with ERROR: could not identify an equality operator for type json.
I'd rather leave the legacy function untouched so refactoring to jsonb isn't possible. I'm imagining a workaround to be transforming the data before comparison, hypothetically with something like SELECT entity_id, json_column::jsonb FROM (FETCH ALL FROM _actual), but this specific attempt obviously isn't valid.
What would be a suggested approach here? Write a helper function to insert data from the cursors into a couple of temporary tables? I'm hoping there's a cleaner solution I haven't discovered.
Using postgres 11.14, pgtap11
Have solved it by creating a function to loop over the cursor and return the results as a table. Unfortunately this isn't a generic solution - it only works for the cursors with specific data.
In this specific case, json_column can be implicitly converted to type jsonb so this is all that is needed. However, we can now SELECT * FROM cursor_to_table('_actual') meaning we can do whatever transformations we require on the result.
CREATE OR REPLACE FUNCTION cursor_to_table(_cursor refcursor)
RETURNS TABLE (entity_id bigint, json_column jsonb)
AS $func$
BEGIN
LOOP
FETCH _cursor INTO entity_id, json_column
EXIT WHEN NOT FOUND;
RETURN NEXT;
END LOOP;
RETURN;
END
$func$ LANGUAGE plpgsql;
SELECT results_eq(
'SELECT * FROM cursor_to_table(''_actual'')',
'SELECT * FROM cursor_to_table(''_expected'')',
'get_data_new should return identical results to the legacy version'
);

Accessing columns inside a record using table alias in Postgresql

I'm trying to iterate over the result of a query using a record data type. Nevertheless, if I try to access one column using the table alias defined in the query, I get the following error:
ERRO: schema "inv_row" does not exist
CONTEXT: SQL command "SELECT inv_row.s.processor <> inv_row.d.processor"
PL/pgSQL function "teste" line 7 at IF
Here is the code that throws this error:
CREATE OR REPLACE FUNCTION teste() returns void as $$
DECLARE
inv_row record;
BEGIN
FOR inv_row in SELECT * FROM sa_inventory s LEFT JOIN dim_inventory d ON s.macaddr = d.macaddr LOOP
IF inv_row.s.processor <> inv_row.d.processor THEN
<do something>;
END IF;
END LOOP;
END;
$$ language plpgsql;
Is there another way to access a column of a particular table inside a record data type?
Fortunately the answer here is relatively simple. You have to use parentheses to indicate tuples:
IF (inv_row.s).processor <> (inv_row.d).processor THEN
This is because SQL specifies meaning to the depth of the namespaces and therefore without this PostgreSQL cannot safely determine what this means. So inv_row.s.processor means the processor column of the s table in the inv_row schema. However (inv_row.s).processor means take the s column of the inf_row table, treat it as a tuple, and take the processor column of that.

Simple PostgreSQL function to return rows

How do I convert a simple select query like select * from customers into a stored procedure / function in pg?
I'm new to Postgres and create function customers() as returns table/setof just didn't feel right and thus the question here.
I understand procs are called "functions" in pg land. Thus create procedure does not exist and my only options are to either create a view or a function. The issue is create function x() returns setof y returns a paren'd comma separated row of values which can't be used without further processing (at least that's what I'm seeing in pgAdmin and Ruby/Sequel).
create function x() returns table(...) requires I embed the row definition which I don't want to.
I'm sure there's a reason behind all this but I'm surprised that the most common use case is this tricky.
Untested but should be about right:
CREATE OR REPLACE FUNCTION getcustomers() RETURNS SETOF customers AS $$
SELECT * FROM customers;
$$ LANGUAGE sql;
The issue is "create function x() returns setof y" returns a paren'd
comma separated row values which can't be used without further processing
The function returns a row. To decompose into individual columns, call it with:
SELECT * FROM getcustomers();
That's assuming the function defines a proper return type. See:
How to return multiple rows from PL/pgSQL function?
The manual on CREATE FUNCTION should be a good starting point. The example section covers this topic.

Passing a record as function argument PL/pgSQL

First I am really new to pl/pgsql. Need it for a project.
I am stuck with this (simplified) problem.
My db schema has a n to m relationship (author, books, author_books)
Now I want to have a pl/psgsql function insert_book. (I do know that all authors are definitely already in the author table, so I just want to pass their primary keys).
This function outline is what I have in mind.
create or replace function insert_book(book_to_insert book, authors integer[])
returns void as $$
begin
-- insert book into table books
-- for each author add an entry to author_books table
end;
$$ language plpgsql;
As arguments I thought to pass a record of type book and the authors that wrote it. But how exactly would this work? I googled quite a bit and can't seem to figure this out...
Question 1: Is the function outline "correct"/does it make sense?
Question 2: How to insert record book into table book? Do I have to go over all fields of book (title, isbn, publisher,...) and add them to an INSERT INTO statement or is there a "smarter" way?
Question 3: How would I call my function insert_book? I found this example here (http://dbaspot.com/postgresql/206142-passing-record-function-argument-pl-pgsql.html), but that doesn't really help me. For testing purposes I am using the shell, but later on we will use Java with JDBC.
Thank you very much for your help.
Using unnest() and a data-modifying CTE (requires Postgres 9.1 or later), this can be a simple SQL query:
WITH x AS (SELECT '(1,foo_book)'::book AS _book
, '{1,2,3}'::int[] AS _authors)
, y AS (
INSERT INTO book -- no column list, correct due to composite type
SELECT (x._book).*
FROM x
RETURNING book_id
)
INSERT INTO author_book (book_id, author_id)
SELECT y.book_id, unnest(x._authors)
FROM x,y; -- CROSS JOIN ok, only 1 row for x and y
The first CTE x is just for simplified data input and not strictly needed.
SQL Fiddle.
As to your questions:
Question 1: Is the function outline "correct"/does it make sense?
Might be easier to pass base types instead of the composite type book, but it is a perfectly valid approach. You have to know your way around the syntax for complex types, though. For instance, note the parenthesis around the name in my example: (x._book).*.
A plpgsql function could look like this:
CREATE OR REPLACE FUNCTION f_insert_book(_book book, _authors integer[])
RETURNS void AS
$func$
BEGIN
WITH y AS (
INSERT INTO book b
SELECT (_book).*
RETURNING b.book_id
)
INSERT INTO author_book (book_id, author_id)
SELECT y.book_id, unnest(_authors)
FROM y;
END
$func$ LANGUAGE plpgsql;
Question 2: How to insert record book into table book? (...) or is there a "smarter" way?
The smarter way is to decompose the composite type with (variable_name).*.
As the type is guaranteed to match the table (being derived from it), this is one of the rare cases, where it is perfectly ok, not to provide a column list for the INSERT command in persisted code.
Question 3: How would I call my function insert_book? ...
SELECT f_insert_book('(1,foo_book)'::book, '{1,2,3}'::int[]);
Within other plpgsql functions, use PERFORM instead of SELECT if you don't provide a target (INTO foo) for the (non-existing) results.
Passing JSON datatype (Postgresql 9.2 or higher):
CREATE OR REPLACE FUNCTION f_insert_book(_book json, _authors json)
RETURNS void AS
$$
BEGIN
-- insert book into table books
Insert into books values select * from json_populate_recordset(null:book, _book);
-- for each author add an entry to author_books table
Insert into authors values select * from json_populate_recordset(null:authors, _authors);
end;
$$ language plpgsql;

Oracle error ORA-22905: cannot access rows from a non-nested table item

here's the stored procedure i wrote.In this proc "p_subjectid" is an array of numbers passed from the front end.
PROCEDURE getsubjects(p_subjectid subjectid_tab,p_subjects out refCursor)
as
BEGIN
open p_subjects for select * from empsubject where subject_id in
(select column_value from table(p_subjectid));
--select * from table(cast(p_subjectid as packg.subjectid_tab))
END getsubjects;
This is the error i am getting.
Oracle error ORA-22905: cannot access rows from a non-nested table item OR
as i have seen in different post,i tried casting "cast(p_subjectid as packg.subjectid_tab)" inside table function as given in the comment below.But i am getting another error: ORA-00902: invalid datatype.
And this is the definition of the "subjectid_tab".
type subjectid_tab is table of number index by binary_integer;
Can anyone please tell me what's the error.Is anything wrong with the my procedure.
You have to declare the type on "the database level" as ammoQ suggested:
CREATE TYPE subjectid_tab AS TABLE OF NUMBER INDEX BY binary_integer;
instead of declaring the type within PL/SQL. If you declare the type just in the PL/SQL block, it won't be available to the SQL "engine".
Oracle has two execution scopes: SQL and PL/SQL. When you use a SELECT/INSERT/UPDATE (etc) statement you are working in the SQL scope and, in Oracle 11g and below, you cannot reference types that are defined in the PL/SQL scope. (Note: Oracle 12 changed this so you can reference PL/SQL types.)
TYPE subjectid_tab IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
Is an associative array and can only be defined in the PL/SQL scope so cannot be used in SQL statements.
What you want is to define a collection (not an associative array) in the SQL scope using:
CREATE TYPE subjectid_tab IS TABLE OF NUMBER;
(Note: you do not need the INDEX BY clause for a collection.)
Then you can do:
OPEN p_subjects FOR
SELECT *
FROM empsubject
WHERE subject_id MEMBER OF p_subjectid;
or
OPEN p_subjects FOR
SELECT *
FROM empsubject
WHERE subject_id IN ( SELECT COLUMN_VALUE FROM TABLE( p_subjectid ) );
This is the good solution.
You cannot use a table(cast()) if the type that you cast is in the DECLARE part of the pl/sql block.
You REALLY need to use CREATE TYPE my_type [...]. Otherwise, it will throw the "cannot fetch row[...]" exception.
I just had this problem yesterday.
DECLARE
TYPE number_table IS TABLE OF NUMBER;
result_ids number_table := number_table();
BEGIN
/* .. bunch of code that uses my type successfully */
OPEN ? AS
SELECT *
FROM TABLE(CAST(result_ids AS number_table)); /* BOOM! */
END;
This fails in both of the ways you described earlier when called from a java routine. I discovered this was due to the fact that the type number_table is not defined in an exportable manner than can be shipped off the database. The type works great internally to the routine. But as soon as you try to execute a returnable recordset that references it in any way (including IN clauses?!?) you get a datatype not defined.
So the solution really is CREATE TYPE myschema.number_table IS TABLE OF NUMBER; Then drop the type declaration from your block and use the schema level declaration. Use the schema qualifier to reference the type just to be sure you are using the right one.
you have to cast the results of the pipelined query so:
If your pipelined function returns a rowtype of varchar2 then define a type (for example )
CREATE OR REPLACE TYPE char_array_t is VARRAY(32) of varchar2(255);
select * from table(cast(fn(x) as user_type_t ) );
will now work.