join array field with ANY - java

Trying to join based on the ID of A that has at least one of the IDSs of table B. A.ID is of type varchar, B.IDS(which is converted as set) is of type varchar[]. I tried like below, but I get an error.
create.select()
.from(A)
.join(B)
.on(A.ID.equal(any(B.IDS))) // Cannot resolve method 'equal(QuantifiedSelect<Record1<T>>)'
.where(other conditions)
Correct jooq code matching below query.
select A.ID, B.ID from A a, B b where a.id = ANY(b.ids) and (other conditions)

The problem seems to be in this part of your question:
B.IDS(which is converted as set) is of type varchar[]
I'm assuming, you used a data type converter to turn your Field<T[]> into a Field<Set<T>>? While that is useful for projecting "better" types than the out of the box types jOOQ supports, it will prevent you from using some data type specific API, in this case array specific API, including the DSL.any(Field<T[]>) operator.
You have at least three options to work around this in this specific case:
Coerce the type back to String[] using DSL.coerce()
Use plain SQL templating, which is always a useful workaround when running into some limitation
Use rawtype casts to remove type safety

Would this do the trick?
(I assume the two table are SQL tables)
SELECT a.ID
FROM A AS a
WHERE a.ID IN (SELECT ID FROM B)

Related

jOOQ - join with nested subquery

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();

JOOQ query to JOIN ON WITH clause

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.

Could any one tell me the real reason of spring-data projection in my case?

The case is:
I have a repository and two methods, one of them looks like:
//There is no warning about: Activity domain type or valid projection interface expected here...
#Query("select distinct a.creatorId from Activity a")
Optional<List<String>> findAllCreatorIds();
The second method looks like:
//There i have warning about: Activity domain type or valid projection interface expected here
#Query("select distinct a.creatorId from Activity a join a.categories c where c.name in ?1")
Optional<List<String>> findAllCreatorIdsByCategoryNames(Set<String> categoryNames);
It looks workable and tests are passed and the generated query is the following:
SELECT DISTINCT activity0_.created_by AS col_0_0_
FROM activities activity0_
INNER JOIN category_item categories1_ ON
activity0_.id=categories1_.activity_id
INNER JOIN categories category2_ ON
categories1_.category_id=category2_.id
WHERE category2_.name IN (?)
I have made the changes to use projection interface. The simple projection interface is:
public interface CreatorIdProjection {
String getCreatorId();
}
The query was changed:
#Query("select a from Activity a join a.categories c where c.name in ?1")
Optional<List<CreatorIdProjection>> findAllCreatorIdsByCategoryNames(Set<String> categoryNames);
Works too. And here is the generated query:
SELECT activity0_.id AS id1_0_,
activity0_.price AS price2_0_,
activity0_.created_by AS created_3_0_,
activity0_.expiration_date AS expirati4_0_,
activity0_.insertts AS insertts5_0_,
activity0_.name AS name6_0_,
activity0_.start_date AS start_da7_0_,
activity0_.updatets AS updatets8_0_
FROM activities activity0_
INNER JOIN category_item categories1_ ON activity0_.id=categories1_.activity_id
INNER JOIN categories category2_ ON categories1_.category_id=category2_.id
WHERE category2_.name IN (?)
I have a few questions related to this case:
Why there is no warning for the first method ?
Why the query have a lot of fields after SELECT when we use
projection? (to be more precise - why we cannot use "select
a.creatorId from Activity a...")
What is the reason of the warning "...domain type or valid
projection interface expected here" if as a result we have a query
that querying the table data instead of what we need.
The warning comes because you're using 'By' in your method name which triggers Spring Data to use (magic) Query methods.
Remove 'By' or replace it with 'Using' or something else and the warning will disappear.
See also https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.details
About question #1:
Why there is no warning for the first method?
There is no Spring Data Projection involved here. You just execute a query returning a list of scalar values, that then is past on (and wrapped in an Optional) by Spring Data. Therefore you should not get such a warning. As a matter of fact I couldn't reproduce the warning at all, nor could I find it in the source code and in any case, the same argument holds for the second method. If it actually produces such a warning please submit a bug.
For question #2a
Why the query have a lot of fields after SELECT when we use projection?
You specify the query exactly using the #Query annotation, and it gets executed as such. Spring Data does not parse your query and removes unnecessary parts, which would be a huge amount of effort for little effect since you provided the query in the first place you might as well provide one that fits your needs.
For question #2b
why we cannot use select a.creatorId from Activity a...?
You can (almost). You just have to specify aliases because JPA will otherwise mangle the column names and Spring Data wouldn't know what columns it is looking at. It actually tells you so when you don't specify aliases. You should get an exception No aliases found in result tuple! Make sure your query defines aliases!. So this should work:
select a.creatorId as creatorId from Activity a...
An alternative is to actually invoke the constructor of a class in the statement:
#Query("select new my.super.cool.projection.CreatorId(a.creatorId)")
Both variants will only query the columns specified.
For question #3
What is the reason for the warning ...domain type or valid projection interface expected here if as a result we have a query that querying the table data instead of what we need.
I was not able to reproduce the warning. I also searched the source code and couldn't find it, so I can't answer this one.

How do I write hql query with cast?

I need to combine 2 tables using hql, both are having common column, but table1 common column is integer and table2 common column is String
For example,
select a.id as id,a.name as name,b.address as address
from Personal as a,Home as b
where a.id=b.studid
Here a.id is an integer while b.stduid is a string, but Data of both columns is the same.
How can I get the result of the query using hql query?
HQL supports CAST (if underlying database supports it), you can use it:
select a.id as id,a.name as name,b.address as address
from Personal as a,Home as b
where cast(a.id as string) = b.studid
See also:
16.10. Expressions
You really need to think why have you got a need to join two entities by properties of different types. Most likely it suggests that some of the entities need to be refactored, which could include changing data types for columns of the underlying db tables. If the model is correct there will be no need to twist Hibernate.
I had to cast it to String like so :
#Query( value = "select new com.api.models.DResultStatus("+
"cast(ds.demoId as java.lang.String),cast(ds.comp as java.lang.String),cast(ds.dc as java.lang.String),cast(be.buildUrl as java.lang.String)")
Just noticed that you are using JPA, there you can not cast or convert datatpes. In the query language, only values of the same type can be compared! read in http://download.oracle.com/javaee/5/tutorial/doc/bnbuf.html#bnbvu

JPQL: What kind of objects contains a result list when querying multiple columns?

I'm trying to do something which is easy as pie in PHP & Co:
SELECT COUNT(x) as numItems, AVG(y) as average, ... FROM Z
In PHP I would get a simple array like [{ numItems: 0, average: 0 }] which I could use like this:
echo "Number of Items: " . $result[0]['numItems'];
Usually in JPQL you only query single objects or single columns and get Lists types, for example List<SomeEntity> or List<Long>. But what do you get, when querying multiple columns?
You get an Object[] (or a List<Object[]>). From the section 4.8.1 Result Type of the SELECT Clause of the JPA 1.0 specification:
The result type of the SELECT clause
is defined by the the result types of
the select_expressions contained in
it. When multiple select_expressions
are used in the SELECT clause, the
result of the query is of type
Object[], and the elements in this
result correspond in order to the
order of their specification in the
SELECT clause and in type to the
result types of each of the
select_expressions.
If you want strong typing, you can use a constructor expression in the SELECT clause. From the section 4.8.2 Constructor Expressions in the SELECT Clause:
A constructor may be used in the
SELECT list to return one or more Java
instances. The specified class is not
required to be an entity or to be
mapped to the database. The
constructor name must be fully
qualified.
If an entity class name is specified
in the SELECT NEW clause, the
resulting entity instances are in the
new state.
SELECT NEW com.acme.example.CustomerDetails(c.id, c.status, o.count)
FROM Customer c JOIN c.orders o
WHERE o.count > 100
You can also use Tuple and return a list of Tuple (List<Tuple>) which you can use as a list of Map.

Categories

Resources