JOOQ How to do UNION and WHERE IN - java

I'm using org.jooq.impl.Executor to create a query that I want to pass to JdbcOperations. I'm passing parameter values by using .where(Condition...).
After chaining methods for the executor, I get a Query object. The problem is when I call query.getSQL(), the returned query string contains parameters ?,?,? instead of my inserted values.
This is the code I'm using to build the SQL query.
Note that TableA has three foreign keys to TableB. I tried to use JOIN ON with OR to join TableA and TableB but the performance was too slow.
Query query = executor.select(fieldsToSelect)
.from("TableA")
.join("TableB").on("TableA.FirstForeignKey = TableB.TableBID")
.join("TableC")
.on("TableC.TableCID = TableB.TableCForeignKey")
.where(condition)
.union(executor.select(fieldsToSelect)
.from("TableA")
.join("TableB").on("TableA.SecondForeignKey = TableB.TableBID")
.join("TableC")
.on("TableC.TableCID = TableB.TableCForeignKey")
.where(condition)
.union(executor.select(fieldsToSelect)
.from("TableA")
.join("TableB").on("TableA.ThirdForeignKey = TableB.TableBID")
.join("TableC")
.on("TableC.TableCID = TableB.TableCForeignKey")
.where(condition)));
This is how I create Condition object for the executor:
MySQLFactory.fieldByName(Integer.class, TABLE_NAME_TABLEA, "TableAID")
.in(ArrayUtils.toObject(ids));
This is how I perform the query:
jdbcOperations.query(query.getSQL(),query.getBindValues().toArray(), myMapper);
How can I correctly map parameters with values and use the query with JdbcOperations?

By default, the Query.getSQL() method returns a query with bind variables (?). You'll then pass that SQL string to a PreparedStatement and bind the variables individually, extracting them first via Query.getBindValue()
An alternative version of the Query.getSQL(boolean) or Query.getSQL(ParamType) method will indicate to jOOQ that the bind variables should be inlined into the SQL string, in order to form a static SQL statement, not a prepared statement.
You can also tell jOOQ to generate all SQL statements as static statements by using StatementType.STATIC_STATEMENT on the Settings that you provide the jOOQ Configuration with.
This is all documented here:
http://www.jooq.org/doc/latest/manual/sql-building/bind-values/inlined-parameters/

Related

Performance comparison between batchching merge and table-valued parameters

I'd like to optimize the current merge batch for about hundreds of thousands entities, using JDBC 7 and SQL Server 2014.
The prepared statement syntax is somewhat like this:
BEGIN
DECLARE #field1 type1 = ?;
DECLARE #field2 type2 = ?;
MERGE INTO dbo.target_table AS TARGET
USING (SELECT __field1 = #field1, __field2 = #field2) AS SOURCE
ON (TARGET.field1 = SOURCE.__field1)
WHEN MATCHED THEN UPDATE SET field1 = #field1, field2 = #field2
WHEN NOT MATCHED BY TARGET THEN INSERT (field1, field2) VALUES (#field1, #field2);
END
The program then binds POJOs values into the declared above variables.
After reading the documentation of SQL Server, it seems the batched statement will be executed separately on server side, furthermore each merge requires a full table search which could lead to long waiting time.
I wonder whether i can optimize the process by using DECLARE #my_table_var table(field1 type1, field2 type2), inserting data into this #my_table_var then performing merge like :
MERGE INTO dbo.target_table AS TARGET
USING #my_table_var AS SOURCE
ON (TARGET.field1 = #my_table_var.field1)
WHEN MATCHED THEN UPDATE SET TARGET.field1 = #my_table_var.field1, TARGET.field2 = #my_table_var.field2
WHEN NOT MATCHED BY TARGET THEN INSERT (field1, field2) VALUES (#my_table_var.field1, #my_table_var.field2);

How to generate SQL from template with order by parameter using jOOQ?

I generate the SQL template like this with jOOQ 3.11.11.
DSLContext context = new DefaultDSLContext(conf);
Query query = context.select()
.from("table1")
.where(DSL.field("report_date").eq(DSL.param("bizdate")))
.orderBy(DSL.param("sort"));
String sqlTemp = context.renderNamedParams(query);
SQL template:
select *
from table1
where report_date = :bizdate
order by :sort
The SQL template is stored and the params are decided at realtime query condition.
ResultQuery resultQuery = context.resultQuery(sqlTemp, DSL.param("bizdate", "20190801"), DSL.param("sort", "id desc"));
The realtime SQL:
select *
from table1
where report_date = '20190801'
order by 'id desc'
There is something wrong with the order by clause.
So. How to replace the order by param sort with "id desc" or "name asc" and eliminate the quotes?
DSL.param() creates a bind variable, which is generated as ? in SQL, or :bizdate if you choose to use named parameters, or '20190801' if you choose to inline the bind variables. More about bind variables can be seen here.
You cannot use DSL.param() to generate column references or keywords. A column expression (e.g. a reference) is described in the jOOQ expression tree by the Field type. Keywords are described by the Keyword type, but you probably do not want to go this low level. Instead you want to handle some of the logic in your query expression. For example:
String sortField = "id";
SortOrder sortOrder = SortOrder.ASC;
Query query = context.select()
.from("table1")
.where(DSL.field("report_date").eq(DSL.param("bizdate")))
.orderBy(DSL.field(sortField).sort(sortOrder));
The mistake you're making is to think that you can use a single SQL template for all sorts of different dynamic SQL queries, but what if you're dynamically adding another predicate? Or another join? Or another column? You'd have to build a different jOOQ expression tree anyway. Just like here. You could store two SQL strings (one for each sort order), and repeat that for each sort column.
But, instead of pre-generating a single SQL string, I recommend you extract a function that takes the input parameters and generates the query every time afresh, e.g.:
ResultQuery<?> query(String bizDate, Field<?> sortField, SortOrder sortOrder) {
return context.selectFrom("table1")
.where(field("report_date").eq(bizDate))
.orderBy(sortField.sort(sortOrder));
}
Here is some further reading about using jOOQ for dynamic SQL:
https://www.jooq.org/doc/latest/manual/sql-building/dynamic-sql
https://blog.jooq.org/2017/01/16/a-functional-programming-approach-to-dynamic-sql-with-jooq

How to bind variables to conditional statement when using jOOQ to build SQL?

I'm using jOOQ-3.11.9 to build SQL. The following is my code:
String sql = DSL.using(SQLDialect.MYSQL)
.select(DSL.asterisk())
.from(table("service"))
.where("name = ?", "service1")
.getSQL();
What I expect is
select * from service where (name = "service1")
But the result is
select * from service where (name = ?)
Is there anything wrong with my code?
This works as expected. The default Settings.statementType value is StatementType.PREPARED_STATEMENT, so jOOQ by default generates bind value placeholders in your SQL string, which can be used for execution in other tools, such as JDBC, Spring, etc.
You can pass the ParamType.INLINE value to the getSQL() method, or specify Settings.withStatementType(StatementType.STATIC_STATEMENT)
Please consider the explanation in the Javadoc for more details:
https://www.jooq.org/javadoc/latest/org/jooq/Query.html#getSQL--
You should use the field name from jOOQ-generated classes:
String sql = DSL.using(SQLDialect.MYSQL)
.select(DSL.asterisk())
.from(YOURENTITY)
.where(YOURENTITY.NAME.eq(nameParam))
.getSQL();
YOURENTITY should be the jOOQ-generated class in your project. nameParam would then be an argument passed to the method wrapping the above query.
jOOQ has pretty good docs with lots of examples:
https://www.jooq.org/doc/3.11/manual/sql-building/sql-statements/select-statement/where-clause/

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")

Get dynamic SQL column names from Hibernate

I have an Oracle table that has a CLOB in it. Inside this CLOB can be a SQL statement. This can be changed at any time.
I am currently trying to dynamically run these SQL statements and return the column names and data back. This is to be used to dynamically create a table on the web page.
Using Hibernate, I create the query and get the data like so:
List<Object[]> queryResults = null;
SQLQuery q = session.createSQLQuery(sqlText);
queryResults = q.list();
This gets the data I need, but not the column names. I have tried using the getReturnAliases() method, but it throws an error that the "java.lang.UnsupportedOperationException: SQL queries do not currently support returning aliases"
So my question is: Is there a way through Hibernate to get these values dynamically?
You can use :
q.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
List<Map<String,Object>> aliasToValueMapList=query.list();
to get column names in createSQLQuery.
For more details please refer to this question.
You can use the addScalar method to define the columns.
Look at 16.1.1
https://docs.jboss.org/hibernate/orm/3.3/reference/en-US/html/querysql.html
You could implement a ResultTransformer ( http://docs.jboss.org/hibernate/orm/4.3/javadocs/org/hibernate/transform/ResultTransformer.html ) and set it on the native query. I think with a native SQL query you get the aliases as specified in the SQL as alias parameter in the callback method.
In 2018 I would suggest using NativeQueryTupleTransformer with native queries.
query.setResultTransformer(new NativeQueryTupleTransformer());
The result format is List<Tuple>. This format is very convenient to work with native SQL queries.

Categories