jOOQ JSON query results in ORA-00979 - java

I'm trying to execute this query to an Oracle 19c database:
Field<JSON> employee = DSL.field("employee", JSON.class);
Table<Record1<JSON>> employees = dsl
.select(jsonObject(jsonEntry("id", EMPLOYEE.ID), jsonEntry("name", EMPLOYEE.NAME), jsonEntry("phones",
jsonArrayAgg(
jsonObject(jsonEntry("number", PHONE.PHONENUMBER), jsonEntry("type", PHONE.TYPE)))
)).as(employee))
.from(EMPLOYEE)
.join(PHONE).on(PHONE.EMPLOYEE_ID.eq(EMPLOYEE.ID))
.groupBy(EMPLOYEE.ID)
.asTable();
String json = dsl
.select(jsonArrayAgg(employees.field(employee)))
.from(employees)
.fetchOneInto(String.class);
But I get
org.springframework.jdbc.BadSqlGrammarException: jOOQ; bad SQL grammar
[select json_arrayagg("alias_113372058".employee) from
(select json_object(key ? value "EMPLOYEE"."ID", key ? value "EMPLOYEE"."NAME", key ? value json_arrayagg(json_object(key ? value "PHONE"."PHONENUMBER", key ? value "PHONE"."TYPE"))) employee from "EMPLOYEE" join "PHONE" on "PHONE"."EMPLOYEE_ID" = "EMPLOYEE"."ID" group by "EMPLOYEE"."ID") "alias_113372058"];
nested exception is java.sql.SQLSyntaxErrorException: ORA-00979: Kein GROUP BY-Ausdruck
Does jOOQs JSON feature not work with Oracle?

This isn't related to your JSON usage. The same thing would have happened if you removed all of it and wrote this query instead:
dsl.select(EMPLOYEE.ID, EMPLOYEE.NAME)
.from(EMPLOYEE)
.join(PHONE).on(PHONE.EMPLOYEE_ID.eq(EMPLOYEE.ID))
.groupBy(EMPLOYEE.ID);
Your query would work in MySQL, PostgreSQL or standard SQL, where you can still project all functionally dependent columns after grouping by a primary key column. But in Oracle, this doesn't work. So, you have to add EMPLOYEE.NAME to your GROUP BY clause.
There's a feature request to transform your SQL accordingly, but jOOQ 3.14 does not support this yet: https://github.com/jOOQ/jOOQ/issues/4725
Note that JSON_ARRAYAGG() aggregates empty sets into NULL, not into an empty []. If that's a problem, use COALESCE()

Related

Spring Data JPA - problem with null parameter for NUMBER column type

I want to make dynamic query in which if particular parameter is sent, the Native query should filter the result based on it. In case it's null, it should not reflect the result.
I am using Spring Data JPA with Native query mechanism + Oracle DB
For String parameters this approach works fine
:email is null or s.email = :email
but for Integer parameters when they have value, the Query works but if the parameter is null the query fails with the error
Caused by: java.sql.SQLSyntaxErrorException: ORA-00932: inconsistent datatypes: expected NUMBER got BINARY
I am using the exactly the same approach for for Integer instead of String
I am wondering whether the problem is on my side or it's some kind of bug?
In Oracle DB it's not worked this way. A workaround is using JPQL like
SELECT s FROM Entity s WHERE :id is null OR s.id = COALESCE(:id, -1)
Or for native query use TO_NUMBER function of oracle
SELECT s FROM Entity s WHERE :id is null OR s.id = TO_NUMBER(:id)

Setting a collection parameter as a list for column values

I'm trying to write a query with a Collection parameter. It is assumed that query would turn this collection to a column of its values for the further operations. I'm using Spring, so now my query looks like:
#Query(value = "SELECT id FROM unnest(array[?1]) id", nativeQuery = true)
List<Object> getIds(Collection<String> ids);
It doesn't work, because Hibernate set collections as parameters in braces, so the real query will be send to DB:
SELECT id FROM unnest(array[('1', '2')]) id
This is not a valid query, the error is raising:
ERROR: a column definition list is required for functions returning
"record" SQL state: 42601
Right version of it must be:
SELECT id FROM unnest(array['1', '2']) id
So please can anybody tell me, if there is a way I could set a collection as query parameter without braces? Or maybe there is an another way to turn collection to column?

jOOQ - error with alias and quotes

I have this query:
Field<String> yearMonth = DSL.field("FORMATDATETIME({0}, 'yyyy-MM')",
String.class, LICENZE.CREATION_DATE).as("anno_mese");
List<Record3<Integer, String, String>> records =
create.select(DSL.count().as("num_licenze"), LICENZE.EDIZIONE, yearMonth).
from(LICENZE).
groupBy(LICENZE.EDIZIONE, yearMonth).
orderBy(yearMonth).
fetch();
this query generates:
select
count(*) "num_licenze",
"PUBLIC"."LICENZE"."EDIZIONE",
FORMATDATETIME("PUBLIC"."LICENZE"."CREATION_DATE", 'yyyy-MM') "anno_mese"
from "PUBLIC"."LICENZE"
group by
"PUBLIC"."LICENZE"."EDIZIONE",
"anno_mese"
order by "anno_mese" asc
executing it i get: Column "anno_mese" not found; SQL statement
Testing the generated query and removing the quotes from anno_mese in every parts of the query make the query works instead.
Is my query wrong or am I using jooq in the wrong way?
The alias in this query is not so important, I can run the query without using it too but just to understand how it works.
I am using h2 as database.
Thanks for the help
I suspect this is a bug in H2, which I've reported here, because the query looks fine to me. Here are some workarounds that you can do from the jOOQ side:
Don't reference the "anno_mese" column by name
While SQL is a bit repetitive otherwise, you won't notice the difference with jOOQ. I simply moved the as("anno_mese") method call into the SELECT clause. You don't really need it in the GROUP BY and ORDER BY clauses.
Field<String> yearMonth = DSL.field("FORMATDATETIME({0}, 'yyyy-MM')",
String.class, LICENZE.CREATION_DATE);
List<Record3<Integer, String, String>> records =
create.select(DSL.count().as("num_licenze"),
LICENZE.EDIZIONE,
yearMonth.as("anno_mese")).
from(LICENZE).
groupBy(LICENZE.EDIZIONE, yearMonth).
orderBy(yearMonth).
fetch();
Disable quoting in jOOQ generated queries
You can use jOOQ's Settings to prevent schema / table / column names from being quoted. Example:
DSLContext create = DSL.using(connection, SQLDialect.H2,
new Settings().withRenderNameStyle(RenderNameStyle.AS_IS);
Use upper case column names
This will probably work: DSL.field(...).as("ANNO_MESE")

How can I get an id for a new record in a generic way? (JOOQ 3.4 with Postgres)

With jooq 3.4 I can't figure out how to do this (with Postgresql):
Query query = dsl.insertInto(TABLE)
.set(TABLE.ID, Sequences.TABLE_ID_SEQ.nextval());
but in a case when I don't know which is the exact table, something like this:
TableImpl<?> tableImpl;
Query query = dsl.insertInto(tableImpl)
.set(tableImpl.getIdentity(), tableImpl.getIdentity().getSequence().nextval());
Is it somehow possible?
I tried this:
dsl.insertInto(tableImpl)
.set(DSL.field("id"),
tableImpl.getSchema().getSequence("table_id_seq").nextval())
This works but I still don't know how to get the sequence name from the TableImpl object.
Is there a solution for this? Or is there a problem with my approach?
In plain SQL I would do this:
insert into table_A (id) VALUES nextval('table_A_id_seq');
insert into table_B (table_A_id, some_val) VALUES (currval('table_A_id_seq'), some_val);
So I need the value or a reference to that id for later use of the id that was generated for the inserted record as default, but I don't want to set any other values.
jOOQ currently doesn't have any means of associating a table with its implicitly used sequence for the identity column. The reason for this is that the sequence is generated when the table is created, but it isn't formally connected to that table.
Usually, you don't have to explicitly set the serial value of a column in a PostgreSQL database. It is generated automatically on insert. In terms of DDL, this means:
CREATE TABLE tablename (
colname SERIAL
);
is equivalent to specifying:
CREATE SEQUENCE tablename_colname_seq;
CREATE TABLE tablename (
colname integer NOT NULL DEFAULT nextval('tablename_colname_seq')
);
ALTER SEQUENCE tablename_colname_seq OWNED BY tablename.colname;
The above is taken from:
http://www.postgresql.org/docs/9.3/static/datatype-numeric.html#DATATYPE-SERIAL
In other words, just leave out the ID values from the INSERT statements.
"Empty" INSERT statements
Note that if you want to create an "empty" INSERT statement, i.e. a statement where you pass no values at all, generating a new column with a generated ID, you can use the DEFAULT VALUES clause.
With SQL
INSERT INTO tablename DEFAULT VALUES
With jOOQ
DSL.using(configuration)
.insertInto(TABLENAME)
.defaultValues()
.execute();
Returning IDs
Note that PostgreSQL has native support for an INSERT .. RETURNING clause, which is also supported by jOOQ:
With SQL
INSERT INTO tablename (...) VALUES (...) RETURNING ID
With jOOQ
DSL.using(configuration)
.insertInto(TABLENAME, ...)
.values(...)
.returning(TABLENAME.ID)
.fetchOne();

coverting sql count aggregate function to hibernate criteria

I have the following sql query which i am trying to covert to a hibernate find:
select column_1, column_2, count(*)
from data_table
group by column_1, column_2
order by column_1, column_2
;
So far this is the code i have:
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(table.class);
detachedCriteria.setProjection(Projections.projectionList()
.add(Projections.groupProperty("column_1"))
.add(Projections.groupProperty("column_2"))
.add(Projections.rowCount()))
.addOrder(Order.asc("column_1"))
.addOrder(Order.asc("column_2"));
For some reason i am getting the following error:
ORA-00979: not a GROUP BY expression
Is this the right way to translate the the sql query using hibernate? if not, what would be a better way
I am also trying to map the returned count column to a transient property in the model object. What would be a good way to implement that?
Thanks
I was able to implement what i was looking for using the code below:
detachedCriteria.setProjection(Projections.projectionList()
.add(Projections.groupProperty("column_1"))
.add(Projections.groupProperty("column_2"))
.add(Projections.sqlProjection(
"count(*) as counter",
new String[] { "counterproperty" },
new Type[] { Hibernate.LONG }
)))
.addOrder(Order.asc("column_1"))
.addOrder(Order.asc("column_2"));
detachedCriteria.setResultTransformer(Transformers.aliasToBean(modelObject.class));
counterProperty is a transient property i added to the model object

Categories