Case When or iif syntax expression in jooq - java

I am trying to reproduce this MySQL query in jooq.
messageTable1, messageTable2 same column
select
msg.index as index,
left(msg.message, if(msg.type = 1, 10, msg.maxLength)) as message
from
messageTable1 as msg
select
msg.index as index,
left(msg.message, if(msg.type = 1, 10, msg.maxLength)) as message
from
messageTable2 as msg
making function java
public Message getMessageTest(String tableName) {
return roDslContext.select(
DSL.field("msg.index").as("index"),
left(DSL.field( "{0}", String.class, "msg.message"),
when(DSL.field("msg.type").eq(1), 10, DSL.field( "{0}", String.class, "msg.maxLength").as("message)
.from(tableName)
.fetchOneInto(Message.class);
}
but....
when(DSL.field("msg.type").eq(1), 10, DSL.field( "{0}", String.class, "msg.maxLength").as("message)
jooq is field msg.maxLength ... not accept
also so iif
how case, when, iif insert field column data??

Using the code generator
I'm assuming you're not using the code generator in this example, because you want to make your column references dynamic, depending on the table name.
There's a possibility of generating common interfaces onto your tables, which would abstract over the two table types. Also, there are Table.rename(String) methods generated on those tables, which allow for renaming a table MESSAGE_TABLE_1.rename("message_table_2"). So your query would look like this:
public Message getMessageTest(String tableName) {
MessageTable1 msg = MESSAGE_TABLE_1.rename(tableName);
return roDslContext
.select(
msg.INDEX,
left(msg.MESSAGE, if_(msg.TYPE.eq(1), val(10), msg.MAX_LENGTH))
)
.from(msg)
.fetchOneInto(Message.class);
}
As always, this is assuming the following static import:
import static org.jooq.impl.DSL.*;
The actual problem
You seem to be attempting to construct field references using plain SQL templating. You did it right for msg.index via DSL.field("msg.index"), but somehow tripped over the templating syntax for msg.message. This doesn't make sense:
field("{0}", String.class, "msg.message");
When using a template, the placeholder {0} needs to be replaced by a QueryPart instance. You can't just pass a String instead (which would be interpreted as a bind value). What you meant to do here is any of these:
// If you really need templating, though here, you don't:
field("{0}", String.class, name("msg", "message"));
// Instead, just do this, if you want quoted names:
field(name("msg", "message"), String.class);
// Or this, if you don't care about quoting the name
field("msg.message", String.class);

Related

postgres jdbc insert array of custom type

I want to insert array of custom type into postgres with jdbc.
my sql schema:
CREATE TYPE element_pk_t AS (
workspace_id BIGINT,
element_id BIGINT,
history_id BIGINT
);
my java class:
public class ElementPK {
public Long workspaceId;
public Long elementId;
public Long historyId;
}
How should I do this in java with jdbc?
I've found tutorial regarding custom type https://docs.oracle.com/javase/tutorial/jdbc/basics/sqlcustommapping.html, but array of custom type is still unclear to me.
One trivial approach is using pure string style SQL statement, but I still prefer using PreparedStatement with setObject or setArray
If I understood you correctly, then, from the Java code, you need to generate query that looks, for example, like this:
INSERT INTO parent_table(elements)
VALUES (
ARRAY[
row(1, 2, 3)::element_pk,
row(4, 5, 6)::element_pk,
row(7, 8, 9)::element_pk
]);
Assuming that you have a table like the following:
CREATE TABLE parent_table(
id SERIAL PRIMARY KEY,
created_at TIMESTAMPTZ DEFAULT NOW(),
elements element_pk[]
)
And the Type like you described. If I were you, I wont implement it via an array. I would have use JSONB instead - in this way you wont lose an ability to use indexing. Of course, there is java.sql.Array out there, and you can still do it via the following:
Array array = connection.createArrayOf("public.element_pk", new ElementPK[]{
new ElementPK(1, 2, 3),
new ElementPK(9, 4, 6)
});
And then set the array, to PgPreparedStatement, but the thing is that internally PgPreparedStatement will enclose your elements within curly braces, which is ok, but each element will be represented by its toString method call result. I mean, assume your ElementPK toString looks like this:
#Override
public String toString() {
return "This is how it is implemented, really?";
}
then you will get the SQL like:
INSERT INTO parent_table (elements) VALUES ('{"This is how it is implemented, really?","This is how it is implemented, really?"}')
Again, it is maybe possible to adopt it, but from my perspective - at least having your logic within toString method is not that great, is it? Spare yourself and do the following:
CREATE TABLE via_jsonb(
elements JSONB
);
and then simply:
INSERT INTO via_jsonb VALUES(
'{
"workspace_id" : 1,
"element_id" : 2,
"history_id" : 3
}'
);
and in the Java code I would have simply create json from your object and set it as a string. Really, there are a lot of functions and cool features on JSONB out of the box.
Hope it helped, have a nice day!)
Follow up to the last part of #misha2045 answer.
If you still refuese to use JSONB for whatever reason you need to add parenthesis at the end and start of your toString method.
#Override
public String toString() {
return "("+this.field1+ ", " + this.field2+")";
}
Then set the array in the prepared statement
ps.setArray(1, con.createArrayOf("yourType", yourClassArray));

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.

How to use JOOQ's parser to extract table names from SQL statements [duplicate]

Using the JOOQ parser API, I'm able to parse the following query and get the parameters map from the resulting Query object. From this, I can tell that there is one parameter, and it's name is "something".
However, I haven't been able to figure out how to determine that the parameter "something" is assigned to a column named "BAZ" and that column is part of the table "BAR".
Does the parser API have a way to get the table/column metadata associated to each parameter?
String sql = "SELECT A.FOO FROM BAR A WHERE A.BAZ = :something";
DSLContext context = DSL.using...
Parser parser = context.parser();
Query query = parser.parseQuery(sql);
Map<String, Param<?>> params = query.getParams();
Starting from jOOQ 3.16
jOOQ 3.16 introduced a new, experimental (as of 3.16) query object model API, which can be traversed, see:
The manual
A blog post about traversing jOOQ expression trees
Specifically, you can write:
List<QueryPart> parts = query.$traverse(
Traversers.findingAll(q -> q instanceof Param)
);
Or, to conveniently produce exactly the type you wanted:
Map<String, Param<?>> params = query.$traverse(Traversers.collecting(
Collectors.filtering(q -> q instanceof Param,
Collectors.toMap(
q -> ((Param<?>) q).getParamName(),
q -> (Param<?>) q
)
)
));
The Collectors.toMap() call could include a mergeFunction, in case you have the same param name twice.
Pre jOOQ 3.16
As of jOOQ 3.11, the SPI that can be used to access the internal expression tree is the VisitListener SPI, which you have to attach to your context.configuration() prior to parsing. It will then be invoked whenever you traverse that expression tree, e.g. on your query.getParams() call.
However, there's quite a bit of manual plumbing that needs to be done. For example, the VisitListener will only see A.BAZ as a column reference without knowing directly that A is the renamed table BAR. You will have to keep track of such renaming yourself when you visit the BAR A expression.

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.

Map attributes not updated properly in Dynamo. New field is added instead.

I am using the AWS Java SDK for communicating with DynamoDB. I am trying to do a table update of some properties stored in a map.
Before the update, I have an object that looks like this:
{
"myMap": {
"innerMap": {}
},
"hashKeyName": "hashKeyValue"
}
My code looks like this:
Table myTable = ...;
myTable.updateItem("hashKeyName", "hashKeyValue",
new AttributeUpdate("myMap.innerMap.myKey").addNumeric(100));
After this update, my Dynamo object looks like this (notice that the map is still empty):
{
"myMap": {
"innerMap": {}
},
"myMap.innerMap.myKey": 100,
"hashKeyName": "hashKeyValue"
}
Why was myMap.innerMap.myKey added as a separate field instead of being correctly set in the map?
The reason for this is because using AttributeUpdate is considered a legacy operation and the usage of map keys as attribute names is not supported. The correct usage to use an update expression:
myTable.updateItem("hashKeyName", "hashKeyValue",
"ADD myMap.innerMap.myKey :val", null /* NameMap, see comment */,
new ValueMap().withNumeric(":val", 100));
Notice that there is no name map in the above expression. One might be tempted to do use ADD #name :val as an update expression and then provide a name map for #name => myMap.innerMap.myKey. In this case, these expressions are not equivalent. When a . appears in the raw expression, it is treated as a path separator. When . appears in a NameMap value, it is not considered a path separator.

Categories

Resources