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.
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.
Ok, just to cut it short, I've done the actual JPQL without using any parameter first and it looks like this.
SELECT count(dt)
FROM transaction dt
WHERE dt.transactionType = 'TEST'
AND dt.date
BETWEEN FUNC('TO_DATE','01-2019','mm-yyyy')
AND FUNC('TO_DATE','02-2019','mm-yyyy')
This thing work! But the thing is now I need to make the transactionType and date as a parameter and this is how it looks like
SELECT count(dt)
FROM transaction dt
WHERE dt.transactionType = :transType
AND dt.date
BETWEEN FUNC('TO_DATE',:lastMonth,'mm-yyyy')
AND FUNC('TO_DATE',:nextMonth,'mm-yyyy')
So for :transType it's fine, but inside this FUNC() seems like I shouldnt put the parameter just like that and need some workaround. I've been googling and can't find any result.
The error was like this
You have attempted to set a parameter value using a name of
lastMonth,'mm-yyyy') that does not exist in the query string
As you can see, the parameter inside FUNC() take along the parameter behind it that meant for FUNC(). What did I miss? Enlighten me please.
Make sure you're using setString for the parameter type.
I always had difficulty with named parameters within JPA, depending upon how the query was created - try using ordinal parameters, eg: ?1 and set them by index.
I'd avoid FUNC as it can carry some major overhead if you're not extremely careful.
There's a workaround for this problem.
Initially, the simplified SQL as below:
SELECT count(*)
FROM table tb
WHERE tb.date between to_date('01-2020','mm-yyyy') and to_date('02-2020','mm-yyyy');
And by directly convert the simplified SQL to JPQL, it turns out as such:
SELECT count(tb)
FROM table tb
WHERE tb.date BETWEEN FUNC('TO_DATE','01-2020','mm-yyyy') AND FUNC('TO_DATE','02-2020','mm-yyyy')
But, the JPQL need to be dynamic as the date will not be static, so by using JPQL parameter to ensure this JPQL can be used at any date, instinctively I thought to use as such:
SELECT count(tb)
FROM table tb
WHERE tb.date BETWEEN FUNC('TO_DATE',:fromDate,'mm-yyyy') AND FUNC('TO_DATE',:toDate,'mm-yyyy')
But as my initial question when this thread first started, such JPQL will not work. So how did I found a workaround? Relatively quite simple actually.
Instead of using this to get the ranging date (as from sql wise I use to_date)
WHERE tb.date BETWEEN FUNC('TO_DATE','','') AND FUNC('TO_DATE','','')
I used this
WHERE FUNC('TO_CHAR','','') between (--fromDate) and (--toDate)
Which finally resulted in final working JPQL of
SELECT count(tb)
FROM table tb
WHERE FUNC('TO_CHAR',tb.date,'mm-yyyy') BETWEEN (:fromDate) AND (:toDate)
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.
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);
In mysql I want to execute a query like this
SELECT MIN(id) FROM table;
The more I read about JOOQ syntax and the aggregate functions, the confused I get.
I thought something like this would work
select( EVENT.EVENTID , min() ).from( EVENT ).fetch();
or
Result<Integer> er = context.select( EVENT.EVENTID.min()).fetch();
I tried a work around by selecting the whole first record
Result<EventRecord> er2 = context.selectFrom(EVENT).orderBy(EVENT.EVENTID.asc()).limit(1).fetch();
If the result has size 0, a record does not exist, but when it is not 0 I get the right record. I would like to use the min() function but can't get the syntax right.
The query you want to write in SQL is this one:
SELECT MIN(event.eventid) FROM event
This is why your two attempts didn't work
// 1. You cannot combine single columns with aggregate functions in SQL,
// unless you're grouping by those columns
// 2. You didn't pass any cargument column to the MIN() function
context.select( EVENT.EVENTID , min() ).from( EVENT ).fetch();
// 3. This doesn't specify any FROM clause, so your database won't know what
// table you want to select the MIN(eventid) from
context.select( EVENT.EVENTID.min()).fetch();
Note that these thoughts are not specific to jOOQ, they are related to SQL in general. When using jOOQ, always think of the SQL statement you want to express first (the one at the top of my answer). So your jOOQ statement would look like any of these:
// "Postfix notation" for MIN()
context.select(EVENT.EVENTID.min()).from(EVENT).fetch();
// "Prefix notation" for MIN(), where min() is static-imported from
// org.jooq.impl.DSL
context.select(min(EVENT.EVENTID)).from(EVENT).fetch();
It looks like the fetchAny() method will return the record with the first/lowest record id.
EventRecord record = context.selectFrom(EVENT).fetchAny();
As #LukasEder mentioned there are many alternative methods, and he may be generous and follow up on some of those. Thanks Lucas