How can I write a JOOQ query to join on a field from a "with" clause?
For example, I've tried:
create.with("a").as(select(
val(1).as("x"),
val("a").as("y")
))
.select()
.from(tableByName("a")
.join(ANOTHER_TABLE)
.on(ANOTHER_TABLE.ID.eq(tableByName("a").field("x")))
.fetch();
However, as the compiler doesn't know the type of tableByName("a").field("x") it cannot resolve which eq() method to use. Given that I know the type, is there a way I can provide it explicitly? Or is there another approach I should take to join on a field from a "with" clause?
While I certainly agree with flutter's answer being a more desireable path to a solution here, I'll just quickly add a response that answers your specific compilation error question.
There are three things that are wrong with your current join predicate:
ANOTHER_TABLE.ID.eq(tableByName("a").field("x"))
DSL.tableByName() is deprecated. It is generally recommended to use table(Name) instead.
Such a dynamically constructed Table does not know of any of its field() references, thus table(name("a")).field("x") will return null
The compilation error is due to your ID reference being of type Field<Integer> (probably), and thus the Field.eq() method expects a Field<Integer> argument as well. Without any knowledge about the type of your field "x", the jOOQ API / Java compiler infers Field<Object>, which is invalid.
So, the solution would be to write:
// field(Name, Class)
ANOTHER_TABLE.ID.eq(field(name("a", "x"), Integer.class))
// field(Name, DataType)
ANOTHER_TABLE.ID.eq(field(name("a", "x"), ANOTHER_TABLE.ID.getDataType()))
I.e. to use DSL.field(Name, Class<T>), or DSL.field(Name, DataType<T>) if you're using custom data type bindings / converters.
What about declaring the CTE first?
Explicit common table expressions
CommonTableExpression<Record2<Integer, String>> a =
name("a").fields("x", "y").as(select(val(1), val("a")));
create.with(a)
.select()
.from(a)
.join(ANOTHER_TABLE)
.on(ANOTHER_TABLE.ID.eq(a.field("x")))
.fetch();
If this does not work, you can always get the DataType<?> or the Class<?> via the Field, which you can get via the Table.
Related
currently I have a converter to work with value objects in JOOQ with Kotlin.
My database has a table called transactions with a field type decimal(10, 2) called billing _amount and on my Kotlin code I have a simple ValueObject from DDD to wrap it up, called BillingAmount defined as following
data class BillingAmount(val value: BigDecimal)
As for my jooq custom converter I have the following code:
public final TableField<TransactionsRecord, BillingAmount> BILLING_AMOUNT =
createField(
DSL.name("billing_amount"),
org.jooq.impl.SQLDataType.NUMERIC(10, 2).nullable(false),
this,
"",
org.jooq.Converter.ofNullable(
java.math.BigDecimal.class,
okano.dev.jooqtesting.BillingAmount.class,
t -> new okano.dev.jooqtesting.BillingAmount(t),
u -> u.getValue()
)
);
On my repository, I'm just trying to retrieve a sum a billing amounts, but jooq is complaining that BillingAmount doesn't extend a Number class. I know that is a Java generic validation that is preventing my code from working, but there's any way around, except by extending the Number class, for solving this problem? I thought that the converter should be enough, but for sure I'm wrong about this.
Here's the simple query I'm trying to achieve:
// jooq is an instance of DSLContext
return jooq.select(sum(TABLE.BILLING_AMOUNT))
.from(TABLE)
.fetchSingle()
Any thoughts on this question? Thanks in advance.
I assume that problems you're experiencing is just due to Java's type system.
If so, you can simply coerce the field to a different type for a query, like so (admittedly, muddying the query defining code):
BillingAmount sum = jooq
.select(sum(TABLE.BILLING_AMOUNT.coerce(BigDecimal.class)))
.from(TABLE)
.fetchSingle(TABLE.BILLING_AMOUNT);
It's not quite the same type of result as in original query, though, because it materializes BillingAmount directly, and not a Record<BillingAmount> (because after coercion the query return type would be Record<BigDecimal>.
As a workaround, you can always resort to using plain SQL templating in jOOQ
fun alternativeSum(field: Field<*>) = DSL.field("{0}", field.getDataType(), field)
I've been trying to write some queries (for MySQL) and I'm not sure how to write this particular one that involves Integer and UInteger types:
DSL.position(T1.FIELD, ":", -1).eq(T2.UINTFIELD)
position returns a Field<Integer>, so I can't simply compare this result to my Field<UInteger>. Is there a simple way to achieve it? I'd like to keep the correct field types for further validation.
You can use DSL.cast() or DSL.coerce() for this. I recommend coerce.
DSL.cast() produces a SQL cast expression and a jOOQ field reference of the desired type
DSL.coerce() does not affect the generated SQL but still produces a jOOQ filed reference of the desired type.
For example:
position(T1.FIELD, ":", -1).eq(coerce(T2.UINTFIELD, SQLDataType.INTEGER))
Let's say I want to find out who wrote CLRS in a book db (tables BOOK, AUTHOR with a junction table BOOK_AUTHOR).
SelectConditionStep<Record1<String>> query = create
.select(AUTHOR.LASTNAME.as("AuthorName"))
.from(
(
BOOK.leftOuterJoin(BOOK_AUTHOR).on(BOOK.ID.eq(BOOK_AUTHOR.BOOKID))
).leftOuterJoin(AUTHOR).on(AUTHOR.ID.eq(BOOK_AUTHOR.AUTHORID))
)
.where(BOOK.TITLE.eq(CLRS_title))
;
A bit inefficient to match the entire table, just to select a single book. I now want to select that book prior to the match.
The jOOQ doc on this matter led me to believe that could look something like this:
Table<Record1<Integer>> clrs = create
.select(BOOK.ID.as("bookID"))
.from(BOOK)
.where(BOOK.TITLE.eq(CLRS_title))
.asTable()
;
SelectJoinStep<Record1<String>> query = create
.select(AUTHOR.LASTNAME.as("AuthorName"))
.from(
(
clrs.leftOuterJoin(BOOK_AUTHOR).on(clrs.field("bookID").eq(BOOK_AUTHOR.BOOKID))
).leftOuterJoin(AUTHOR).on(AUTHOR.ID.eq(BOOK_AUTHOR.AUTHORID))
)
;
However, that fails to compile because
Cannot resolve method 'eq(org.jooq.TableField<ch.cypherk.bookdb.public_.tables.records.BookAuthorRecord,java.lang.Integer>)'
in the join condition.
What's the correct way to write this join?
The problem you're having
You're dereferencing a column from your derived table using Table.field(String):
clrs.field("bookID")
The type you're getting back is Field<?>, with a wildcard. Like with any generic type, once you have a wild card, a lot of operations (but not all) will no longer be possible on that type. Take List<?>, for example. You can still call List<?>.get() to retrieve an Object, but not List<?>.add(? element). In Field<?>, you can no longer call eq(), unless you cast the argument to a raw type.
You can also coerce your field's <T> type to the type you already know, e.g. by using Table.field(String, DataType<T>)
clrs.field("bookID", BOOK.ID.getDataType())
Study your various options and you might discover the one(s) you might find most useful
A better solution to your query
You don't really need to
Assign your subquery to a local variable
Use a derived table for your problem
Often with jOOQ, if you're having issues with derived tables as above, ask yourself is there really not an easier query I could write instead?
What you really need here is a semi join. Write:
// Assuming this static import
import static org.jooq.impl.DSL.*;
ctx.select(AUTHOR.LASTNAME)
.from(AUTHOR)
.where(AUTHOR.ID.in(
select(BOOK_AUTHOR.AUTHORID)
.from(BOOK_AUTHOR)
.join(BOOK).on(BOOK.ID.eq(BOOK_AUTHOR.BOOKID))
.where(BOOK.TITLE.eq(clrsTitle))
)
.fetch();
I have a situation where I need to return only few fields of a POJO.
Here is a SOF Question: retrieve-single-field-rather-than-whole-pojo-in-hibernate question regarding the same, but few things still seems to be obscure.
1) The answer suggests to use -
String employeeName = session.createQuery("select empMaster.name from EmployeeMaster empMaster where empMaster.id = :id").setInteger("id",10).uniqueResult();
So, here is my concern - Every pojo field is normally private, so "empMaster.name" will simply not work. And am not sure if empMaster.getName() is the solution for this. Will calling the getter methods work?
2) If i am querying multiple fields, (which is my case) (assuming getter methods work) the query will be some thing like -
List<String> employeeDetails = session.createQuery("select empMaster.getName(), empMaster.getDesignation() from EmployeeMaster empMaster where empMaster.id = :id").setInteger("id",10).uniqueResult();
Note the return type has changed from String to List<String>.
2(a) Hope this is right?
2(b) what if i am interested in age/salary of employee which will be of int type. I think the return type will be List<String> or List<Object>. Well any how in the application i can recast the String or Object to the proper type int or float etc. So this should not be a problem.
3) Now what if I am querying multiple employee details (no where clause), so the query will be something like - (not sure if the part of query after from is correct)
List<List<<String>> employeesDetails = session.createQuery("select empMaster.getName(), empMaster.getDesignation() from EmployeeMaster;
Anyway, point here is to emphasise the change in the return type to : List<List<<String>> employeesDetails. Does it work this way ???.
(The question quoted above also has answers pointing to use Projections. I have questions about it but will post them on another question, don't want to mixup.)
I will list the points in the order you mentioned them:
The query has nothing to do with the POJO's field visibility. You are doing a simple query to the database, as if you were doing a query using SQL, and columns in a table have nothing to do with the fact that their mapped POJOs' fields in an application are public or private. The difference is only the language that you're using: now you're using the Hibernate Query Language (HQL), which allows you to express your query with respect to the POJOs' definitions instead of the database's tables' definitions. In fact, doing
session.createQuery("select empMaster.getName() from EmployeeMaster...");
will throw a syntax error: there can be no parenthesis in an object's field name.
By the way, you have to parse your query result to a String, otherwise there would be a compiler semantics error.
String name = (String) session.createQuery("select empMaster.name from EmployeeMaster...").setYourParameters().uniqueResult();
When you do a query whose SELECT clause contains more than one field, and you call uniqueResult(), you're obtaining an array of Objects (Object[]). You iterate through each element in the array and cast it to the corresponding type.
First of all, you probably forgot to add a .list() method call at the end of your query.
session.createQuery("select empMaster.name, empMaster.designation from EmployeeMaster").list();
Otherwise, the query will not be executed, and you would just have a QueryImpl object waiting to be triggered.
Now, when you're expecting multiple results in your query and selecting several fields, you're getting back a List<Object[]>. The elements of the list will be the array of fields for a specific record, and the array per se will be the fields.
I read JOOQ API documentation about following APIs, but still couldn't understand what is fieldIndex about.
fetchAny(int fieldIndex, java.lang.Class<? extends T> type)
For example, in the following code I already knows which column to select, why do we need filedIndex of 0? What does 0 mean?
String name = getDslContext().select(TESTB.STU_NAME)
.from(TESTB)
.where(TESTB.ID.eq(studentId))
.fetchAny(0, String.class);
jOOQ mostly operates on Record types. For instance, when you create the following query:
.select(TESTB.STU_NAME)
.from(TESTB)
.where(TESTB.ID.eq(studentId));
You're really operating on a ResultQuery<Record1<String>>. By calling fetchAny(0, String.class) on that type, you're telling jOOQ that you want to fetch any record, and from that record, you want to get only the value at index 0, converted to String.
This may feel like repeating the same information twice (using TESTB.STU_NAME, and fetching the column at index 0). This is because fetchign a single value is quite a special case in the jOOQ API. Unfortunately, the ResultQuery API doesn't really "know" that you're selecting only one column even if that type information is present via generics.
One alternative would be to use DSLContext.fetchValue(ResultQuery):
String name = getDslContext().fetchValue(
DSL.select(TESTB.STU_NAME)
.from(TESTB)
.where(TESTB.ID.eq(studentId))
);