Selecting fields to join on when joining nested queries - java

I've recently been migrating my database and have been moving our remaining SQL queries over to jooq. Having some fun with this one:
private SelectQuery<?> getIdeasQuery() {
Select<?> numComments = dslContext.select(DSL.count().as("comment_count"), COMMENT.IDEA_ID).from(COMMENT).groupBy(COMMENT.IDEA_ID);
Select<?> numLikes = dslContext.select(DSL.count().as("like_count"), USER_LIKES_IDEA.IDEA_ID).from(USER_LIKES_IDEA).groupBy(USER_LIKES_IDEA.IDEA_ID);
return dslContext
.select(DSL.field("comment_count").as("num_comments"))
.select(DSL.field("like_count").as("num_likes"))
.select(USER.DISPLAY_NAME)
.select(USER.AUTHORITY)
.select(IDEA.fields())
.from(IDEA.leftOuterJoin(numComments).on(COMMENT.IDEA_ID.eq(IDEA.ID))
.leftOuterJoin(numLikes).on(USER_LIKES_IDEA.IDEA_ID.eq(IDEA.ID))
.leftOuterJoin(USER).on(IDEA.USER_ID.eq(USER.ID)))
.getQuery();
}
The returned query is then used to append additional modifiers (using addConditions(), addOrderBy(), addLimit() etc.) depending on the context, and then executed.
The issue I'm having is that the two sub-select queries are not named as their original names for the joins. This is correct behaviour! However, within jOOQ I'm having a hard time finding how I can specify the sub-query's fields to join on. I've tried going down the route of renaming the sub-queries as described in this StackOverflow answer, but the types don't line up for me.
Any ideas?

So I've found what I think is a solution for now. However, it seems like a workaround. I've given the sub-queries names when I do the join, and using that name to point to the field. If my schema changes in future, I don't think this will flag up a compile time error.
Here it is for anyone interested:
private SelectQuery<?> getIdeasQuery() {
Select<?> numComments = dslContext.select(DSL.count().as("comment_count"), COMMENT.IDEA_ID).from(COMMENT).groupBy(COMMENT.IDEA_ID);
Select<?> numLikes = dslContext.select(DSL.count().as("like_count"), USER_LIKES_IDEA.IDEA_ID).from(USER_LIKES_IDEA).groupBy(USER_LIKES_IDEA.IDEA_ID);
return dslContext
.select(DSL.field("comment_count").as("num_comments"))
.select(DSL.field("like_count").as("num_likes"))
.select(USER.DISPLAY_NAME)
.select(USER.AUTHORITY)
.select(IDEA.fields())
.from(IDEA.leftOuterJoin(numComments.asTable("com")).on(DSL.field("com.idea_id").eq(IDEA.ID))
.leftOuterJoin(numLikes.asTable("like")).on(DSL.field("like.idea_id").eq(IDEA.ID))
.leftOuterJoin(USER).on(IDEA.USER_ID.eq(USER.ID)))
.getQuery();
}

Related

How to use a 'Hibernate Types' library type in a single native query that selects Postgres arrays?

I have a query that returns a Postgres array of UUIDs:
SELECT e.id, e.date,
ARRAY
(
SELECT cs.api_id FROM event_condition_set ecs
JOIN condition_set cs on cs.id = ecs.condition_set_id
WHERE ecs.event_id = e.id
) AS condition_set_ids,
...
And then create and run this query as a native query: Query query = entityManager.createNativeQuery(queryString);
Since Hibernate can normally not deal with these Postgres arrays, I use Vlad's Hibernate Types library.
However, currently I need to register this UUIDArrayType globally in my application:
public class PostgreSQL95CustomDialect extends PostgreSQL95Dialect {
public PostgreSQL95CustomDialect() {
super();
this.registerHibernateType(Types.ARRAY, UUIDArrayType.class.getName());
}
}
Aside from the fact this is a bit ugly, it also leaves no room for other types of arrays.
(Note I also tried registering a generic ListArrayType but this throws a NullPointerException during execution of the query.)
I have also tried registering it as a scalar type:
query.unwrap(org.hibernate.query.NativeQuery.class)
.addScalar("condition_set_ids", UUIDArrayType.INSTANCE);
But this makes the entire query only return a single UUID, which is very strange and seems bugged.
Is there a way to ONLY use this UUIDArrayType specifically in this query?
(Please don't suggest using array_agg because the performance is terrible for this case)
you can call native queries using custom Hibernate types as follows:
String myJsonbData = ...;
String[] myStringArr = ...;
final String queryStr = "select your_function(?, ?, ...)"; // ? for each param
entityManager
.createNativeQuery(queryStr)
.setParameter(1, new TypedParameterValue(JsonBinaryType.INSTANCE, myJsonbData))
.setParameter(2, new TypedParameterValue(StringArrayType.INSTANCE, myStringArr));
This is just an example, but as a rule of thumb, you need to instantiate a new TypedParameterValue.
Answering my own question here. After waiting for a while and updating to the most recent library version (I'm on 2.19.2 right now) I don't have any issues anymore with the scalar types registration as I mentioned in my question, i.e.:
query.unwrap(org.hibernate.query.NativeQuery.class)
.addScalar("condition_set_ids", UUIDArrayType.INSTANCE);
So it appears to just have been a bug and I can now avoid the global registration in favor of using scalars.

Introducing a named parameter breaks jOOQ query

To query a PostgreSQL 10.11 database, I am using jOOQ 3.12.4, which comes bundled with Spring Boot 2.2.
Let's assume I have built a query using jOOQ like this:
final String[] ids = ...;
final var query = dslContext.selectFrom(MY_TABLE).where(MY_TABLE.ID.in(ids));
final Map<String, List<MyTable>> changeDomains = query.fetch().intoGroups(MY_TABLE.ID, MyTable.class);
This code runs fine and produces the expected results. But when I refactor my query and introduce a named parameter (to reuse the query in multiple parts of my code), like this:
final String[] ids = ...;
final var query = dslContext.selectFrom(MY_TABLE).where(MY_TABLE.ID.in(param("ids")));
final Map<String, List<MyTable>> changeDomains = query.bind("ids", ids).fetch().intoGroups(MY_TABLE.ID, MyTable.class);
I suddenly start to get the following error:
org.springframework.jdbc.BadSqlGrammarException: jOOQ; bad SQL grammar ...; nested exception is org.postgresql.util.PSQLException: ERROR: operator does not exist: text = character varying[]
Hinweis: No operator matches the given name and argument type(s). You might need to add explicit type casts.
Edit: I get the same error when I use
MY_TABLE.ID.in(param("ids", String[].class))
instead.
How can I solve or work around this problem?
A better solution to your code reuse approach
But when I refactor my query and introduce a named parameter (to reuse the query in multiple parts of my code)
While you could use jOOQ this way (be careful, when mutating and reusing jOOQ queries in a non-threadsafe way!), it is generally recommended to use jOOQ in a more functional way, see e.g.:
https://blog.jooq.org/2017/01/16/a-functional-programming-approach-to-dynamic-sql-with-jooq/
https://www.jooq.org/doc/latest/manual/sql-building/dynamic-sql/
You don't gain much by re-using a jOOQ query, specifically, there's hardly any performance gain.
So, instead of this:
final var query = dslContext.selectFrom(MY_TABLE)
.where(MY_TABLE.ID.in(param("ids")));
final Map<String, List<MyTable>> changeDomains = query
.bind("ids", ids).fetch().intoGroups(MY_TABLE.ID, MyTable.class);
Write this:
public ResultQuery<MyTableRecord> query(String[] ids) {
return dslContext.selectFrom(MY_TABLE).where(MY_TABLE.ID.in(ids));
}
// And then:
final Map<String, List<MyTable>> changeDomains = query(ids)
.fetch().intoGroups(MY_TABLE.ID, MyTable.class);
The actual problem you ran into:
jOOQ, JDBC, and SQL don't support single bind value IN lists. While it seems useful to write this:
SELECT * FROM t WHERE c IN (:bind_value)
And passing an array or list as a single bind value, this is not supported in SQL. Some APIs might pretend that this is supported (but behind the scenes replace the single bind value by multiple ?, ?, ..., ?
PostgreSQL supports the = ANY (:bind_value) operator with arrays
SELECT * FROM t WHERE c = ANY (:bind_value)
You could use it in jOOQ using
dslContext.selectFrom(MY_TABLE).where(MY_TABLE.ID.eq(any(ids)));
That way, you could call the bind() method to replace the array prior to execution. However, I still recommend you write functions returning queries dynamically.

Alias with two Fields using JOOQ

i have a nasty SQl that i want transform in JOOQ
Here are the Query:
SELECT
SUM(dpr.dpr_bruttopraemie_prt + dpr.dpr_sofortrabatt_prt)
, MAX(TO_NUMBER(DP1.dp_wert))
FROM deckungen deck, deckungspraemien dpr,
(SELECT dp.dp_id, dp.dp_wert
FROM textbausteine txb, druckparameter dp
WHERE dp.dp_txb_id = txb.txb_id
) DP1
WHERE DP1.dp_id = :druckparameter_id;
As you can see, i need to make alias from a select with two Fields.
dp.dp_id, dp.dp_wert
that im going to used it on some other parts.
How i can i get it done?
i've seen
.asField()
Funktion but it only make alias for one column.
PS: The actual Query are a lot more complicated. So i wrote a simpler one.
With hoping that it's satisfied the SQL ORACLE Dialect.
I'm assuming that you're using the code generator, so you have generated objects available for your tables like DECKUNGEN. I'm also assuming you're using these static imports:
import static org.jooq.impl.DSL.*; // The jOOQ API
import static com.example.myapp.generated.Tables.*; // Your generated tables
You can then write:
Deckungen deck = DECKUNGEN.as("deck");
Deckungspraemien dpr = DECKUNGSPRAEMIEN.as("dpr");
Textbausteine txb = TEXTBAUSTEINE.as("txb");
Druckparameter dp = DRUCKPARAMETER.as("dp");
Table<?> dp1 = table(
select(dp.DP_ID, dp.DP_WERT)
.from(txb, dp)
.where(dp.DP_TXB_ID.eq(txb.TXB_ID))
).as("dp1");
Record2<BigDecimal, BigDecimal> result =
using(configuration)
.select(
sum(dpr.DPR_BRUTTOPRAEMIE_PRT.plus(dpr.DPR_SOFORTRABATT_PRT)),
max(field("to_number({0})", BigDecimal.class, dp1.field(dp.DP_WERT))))
.from(deck, dpr, dp1)
.where(dp1.field(dp.DP_ID).eq(druckparameterId))
.fetchOne();
Some explanations
jOOQ currently doesn't have a built-in TO_NUMBER() function, but you can easily roll your own using DSL.field(String) and similar overloads. For more detail, refer to the manual's section about plain SQL
A derived table can be created most easily by using the DSL.table(Select) operator.
Columns from a derived table can be dereferenced using Table.field() methods, in particular the Table.field(Field), which tries to find a field by the same name as the argument field, retaining the argument field's type information.
Side-note
I don't think your query is correct as you're simply creating cartesian products between your tables deck, dpr, and dp1. Specifically, the SUM() is quite likely to be wrong, whereas MAX() is simply calculated inefficiently.

How to escape the keyword "on" in HQL

Here is my Entity which I want to query with Hibernate 4.3.4:
#Entity
#DynamicInsert
#DynamicUpdate
public class Device extends OrmEntity implements Serializable {
#Column(name = "is_on") // was necessary to get Hibernate to create the table at all
private boolean on;
...
}
This is the HQL I want to execute (btw the database is PostgreSQL):
Query query = session.createQuery("from Device where ... and on = :a");
query.setBoolean("a", true);
This gives me org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token: on near line 1.
I tried many escape characters like backticks, \", [ ], single-quotes, without success.
What would be the appropriate way to escape the keyword "on" in this HQL-query?
Edit:
I tried "from Device d where d.on = :a" which results in QuerySyntaxException: unexpected token: d near line 1.
I did not try renameAlias as this seems to be overkill for just escaping a keyword...
Edit2:
I am trying out AliasToMapTransformer but I can not seem to get it working for a where clause ... ? It seems to be made for column aliases.
Edit3:
Generally, this code is around for some time already and strangely it worked with Hibernate < 4.3.$
Edit4:
I accepted NimChimpsky's answer, and implemented the following change: Rename the boolean member from on to isOn, but keep the getter and setter as they were: setOn() and isOn().
No further change in my code necessary, and Hibernate is happy now.
#Column
private boolean deviceIsOn;
annotate the field with a renamed variable, and then my method would be isOn.
Bit hacky ... but you keep your nice friendly OO api, with out too much hassle.
Or even simpler, just call it off which isn't reserved

IntelliJ IDEA code inspection: HQL custom dialect & registered functions

My question is about
using registered functions for date/time manipulations in Hibernate Query Language and
IntelliJ IDEA's code inspection for these registered functions in HQL.
I'm using Hibernate 4.2.5 with Java 7, and SQL Server 2008 R2 as the database, and IntelliJ IDEA 12.1.6.
In an HQL query I need to perform the TSQL DATEADD function - or the equivalent HQL date operation. This doesn't seem to exist.
Here's what I'd like to achieve:
update MyTable set startTime = GETDATE(), targetTime = DATEADD(HOUR, allocatedTime, GETDATE()), endTime = null where faultReport.faultReportId = :faultReportId and slaTypeId = :slaTypeId
Searching for answers online has been disappointingly no help, and the most common advice (like the comment seen here: https://stackoverflow.com/a/18150333/2753571) seems to be "don't use date manipulation in hql." I don't see how I can get around performing the operation in the SQL statement in the general case (e.g. when you want to update one column based on the value in another column in multiple rows).
In a similar fashion to the advice in this post: Date operations in HQL, I've subclassed a SQLServerDialect implementation and registered new functions:
registerFunction("get_date", new NoArgSQLFunction("GETDATE", StandardBasicTypes.TIMESTAMP)); // this function is a duplication of "current_timestamp" but is here for testing / illustration
registerFunction("add_hours", new VarArgsSQLFunction(TimestampType.INSTANCE, "DATEADD(HOUR,", ",", ")"));
and added this property to my persistence.xml:
<property name="hibernate.dialect" value="my.project.dialect.SqlServerDialectExtended" />
and then I'm testing with a simple (meaningless, admitted) query like this:
select x, get_date(), add_hours(1, get_date()) from MyTable x
The functions appear to be successfully registered, and that query seems to be working because the following SQL is generated and the results are correct:
select
faultrepor0_.FaultReportSLATrackingId as col_0_0_,
GETDATE() as col_1_0_,
DATEADD(HOUR,
1,
GETDATE()) as col_2_0_,
... etc.
But I now have this problem with IntelliJ IDEA: where get_date() is used in the HQL, the code inspection complains "<expression> expected, got ')'". This is marked as an error and the file is marked in red as a compilation failure.
Can someone can explain how to deal with this, please, or explain what a better approach is? Am I using the incorrect SQLFunction template (VarArgsSQLFunction)? If yes, which is the best one to use?
I'd like the usage of the registered function to not be marked as invalid in my IDE. Ideally, if someone can suggest a better way altogether than creating a new dialect subclass, that would be awesome.

Categories

Resources