This is the SQL statement that I have.
SELECT USER_PROFILE.FIRST_NAME, USER_PROFILE.LAST_NAME, USER_PROFILE.USER_TYPE
FROM USER_PROFILE
INNER JOIN USER_LOGIN_STATUS
ON USER_PROFILE.USER_ID=USER_LOGIN_STATUS.USER_ID
ORDER BY USER_PROFILE.FIRST_NAME
And I'm trying to execute the code below that I thought the equivalent to hibernate DetachedCriteria and expected to only have two data as a result.
DetachedCriteria dc = getDetachedCriteria();
DetachedCriteria userLoginCriteria = DetachedCriteria.forClass(UserLoginStatus.class);
userLoginCriteria.setProjection(Projections.distinct(Projections.property("userId")));
dc.add(Subqueries.propertyIn(UserField.id.name(), userLoginCriteria));
DetachedCriteria profileCriteria = dc.createCriteria("profile");
profileCriteria.addOrder(Order.asc("firstName"));
return getAll(dc, pageSetting);
But unfortunately this is the unexpected result: I am having a multiple data result.
Name | Type |
Ben Jones | User |
Ben Jones | User |
Tom Homer | Guest |
Tom Homer | Guest |
Is anyone there knows the exact equivalent DetachedCriteria or a solution for this?
First of all, your SQL looks incorrect. The reason it is returning multiple rows is because you're joining against the USER_LOGIN_STATUS table which may have multiple rows per USER_PROFILE. Because you are not selecting any fields from the USER_LOGIN_STATUS table, you cannot see why there are multiple rows. Why are you joining on this table in the first place?
Secondly, the detached criteria you are performing is not equivalent to the SQL you have provided since you are doing a sub-query which you are not in the SQL.
You don't need this sub-select and since I don't understand why you are doing the join I will assume some points to give you the following example:
DetachedCriteria dc = getDetachedCriteria();
dc.createAlias("userLoginStatus", "uls");
dc.add(Projections.property("firstName"));
dc.add(Projections.property("lastName"));
dc.add(Projections.property("userType"));
dc.addOrder(Order.asc("firstName"));
return getAll(dc, pageSetting);
This is now roughly equivalent but I am assuming:
You have the correct mappings for your relationship between UserField and UserLoginStatus.
That getDetachedCriteria() is effectively returning DetachedCriteria.forClass(UserField.class).
You can also now refer to a field in UserLoginStatus as so:
dc.add(Projections.property("uls.my_user_login_field"));
And as well, if you get your query sorted out and you still return multiple entities, then dinukadev's answer will then come into play with:
dc.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
I suspect the reason this isn't working for you is because of your sub-select.
Sorry I cannot help you more.
Please try to set the result transformer on your root detached criteria as follows. This will eliminate duplicates.
dc.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
Related
I am have a problem where i need to join two tables using the LEAST and GREATEST functions, but using JPA CriteriaQuery. Here is the SQL that i am trying to duplicate...
select * from TABLE_A a
inner join TABLE_X x on
(
a.COL_1 = least(x.COL_Y, x.COL_Z)
and
a.COL_2 = greatest(x.COL_Y, x.COL_Z)
);
I have looked at CriteriaBuilder.least(..) and greatest(..), but am having a difficult time trying to understand how to create the Expression<T> to pass to either function.
The simplest way to compare two columns and get the least/greatest value is to use the CASE statement.
In JPQL, the query would look like
select a from EntityA a join a.entityXList x
where a.numValueA=CASE WHEN x.numValueY <= x.numValueZ THEN x.numValueY ELSE x.numValueZ END
and a.numValueB=CASE WHEN x.numValueY >= x.numValueZ THEN x.numValueY ELSE x.numValueZ END
You can code the equivalent using CriteriaBuilder.selectCase() but I've never been a big fan of CriteriaBuilder. If requirements forces you to use CriteriaBuilder then please let me know and I can try to code the equivalent.
CriteriaBuilder least/greatest is meant to get the min/max value of all the entries in one column. Let's say you want to get the Entity that had the alphabetically greatest String name. The code would look like
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery query = cb.createQuery(EntityX.class);
Root<EntityX> root = query.from(EntityX.class);
Subquery<String> maxSubQuery = query.subquery(String.class);
Root<EntityX> fromEntityX = maxSubQuery.from(EntityX.class);
maxSubQuery.select(cb.greatest(fromEntityX.get(EntityX_.nameX)));
query.where(cb.equal(root.get(EntityX_.nameX), maxSubQuery));
I created a sample Spring Data JPA app that demonstrates these JPA examples at
https://github.com/juttayaya/stackoverflow/tree/master/JpaQueryTest
It turns out that CriteriaBuilder does support calling LEAST and GREATEST as non-aggregate functions, and can be accessed by using the CriteriaBuilder.function(..), as shown here:
Predicate greatestPred = cb.equal(pathA.get(TableA_.col2),
cb.function("greatest", String.class,
pathX.get(TableX_.colY), pathX.get(TableX_.colZ)));
I use the Vlad Mihalcea's library in order to map SQL arrays (Postgresql in my case) to JPA. Then let's imagine I have an Entity, ex.
#TypeDefs(
{#TypeDef(name = "string-array", typeClass =
StringArrayType.class)}
)
#Entity
public class Entity {
#Type(type = "string-array")
#Column(columnDefinition = "text[]")
private String[] tags;
}
The appropriate SQL is:
CREATE TABLE entity (
tags text[]
);
Using QueryDSL I'd like to fetch rows which tags contains all the given ones. The raw SQL could be:
SELECT * FROM entity WHERE tags #> '{"someTag","anotherTag"}'::text[];
(taken from: https://www.postgresql.org/docs/9.1/static/functions-array.html)
Is it possible to do it with QueryDSL? Something like the code bellow ?
predicate.and(entity.tags.eqAll(<whatever>));
1st step is to generate proper sql: WHERE tags #> '{"someTag","anotherTag"}'::text[];
2nd step is described by coladict (thanks a lot!): figure out the functions which are called: #> is arraycontains and ::text[] is string_to_array
3rd step is to call them properly. After hours of debug I figured out that HQL doesn't treat functions as functions unless I added an expression sign (in my case: ...=true), so the final solution looks like this:
predicate.and(
Expressions.booleanTemplate("arraycontains({0}, string_to_array({1}, ',')) = true",
entity.tags,
tagsStr)
);
where tagsStr - is a String with values separated by ,
Since you can't use custom operators, you will have to use their functional equivalents. You can look them up in the psql console with \doS+. For \doS+ #> we get several results, but this is the one you want:
List of operators
Schema | Name | Left arg type | Right arg type | Result type | Function | Description
------------+------+---------------+----------------+-------------+---------------------+-------------
pg_catalog | #> | anyarray | anyarray | boolean | arraycontains | contains
It tells us the function used is called arraycontains, so now we look-up that function to see it's parameters using \df arraycontains
List of functions
Schema | Name | Result data type | Argument data types | Type
------------+---------------+------------------+---------------------+--------
pg_catalog | arraycontains | boolean | anyarray, anyarray | normal
From here, we transform the target query you're aiming for into:
SELECT * FROM entity WHERE arraycontains(tags, '{"someTag","anotherTag"}'::text[]);
You should then be able to use the builder's function call to create this condition.
ParameterExpression<String[]> tags = cb.parameter(String[].class);
Expression<Boolean> tagcheck = cb.function("Flight_.id", Boolean.class, Entity_.tags, tags);
Though I use a different array solution (might publish soon), I believe it should work, unless there are bugs in the underlying implementation.
An alternative to method would be to compile the escaped string format of the array and pass it on as the second parameter. It's easier to print if you don't treat the double-quotes as optional. In that event, you have to replace String[] with String in the ParameterExpression row above
For EclipseLink I created a function
CREATE OR REPLACE FUNCTION check_array(array_val text[], string_comma character varying ) RETURNS bool AS $$
BEGIN
RETURN arraycontains(array_val, string_to_array(string_comma, ','));
END;
$$ LANGUAGE plpgsql;
As pointed out by Serhii, then you can useExpressions.booleanTemplate("FUNCTION('check_array', {0}, {1}) = true", entity.tags, tagsStr)
Example:
|studentname | maths | computers |
++++++++++++++++++++++++++++++++++
|s1 |78 |90 |
==================================
|s2 |56 |75 |
==================================
|s3 |45 |50 |
==================================
The above table represents data that is present in Elasticsearch.
Consider that the user enters 60 and above then Elasticsearch should be able to display only s1, because he is the only student who has scored more than 60 in both subjects.
How do I solve it using Java API?
NOTE: I was able to find out for individual subjects by:
QueryBuilder query = QueryBuilders.boolQuery()
.should(
QueryBuilders.rangeQuery(maths)
.from(50)
.to(100)
)
You can have multiple .should() clauses in the the bool query. So in your case:
QueryBuilder query = QueryBuilders.boolQuery()
.should(
QueryBuilders.rangeQuery(maths)
.from(61)
.to(100)
)
.should(
QueryBuilders.rangeQuery(computers)
.from(61)
.to(100)
)
Note:
RangeQueryBuilder (returned from QueryBuilders.rangeQuery()) also has the method #gte().
According to the docs: "In a boolean query with no must or filter clauses, one or more should clauses must match a document." So if you have have no filter or must, you might not get the desired behavior. The above answer assumes you are using some other clauses. The point is you can combine clauses in the bool query. With the Java API this just means using the clause repeatedly.
You might want to use filter instead of should. This will lead to faster execution and caching (https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html)
I'm currently using JPQL (latest version I think) with a postgresql database and I'm trying to get the latests objects grouped by some (not all columns).
Let's say I have a table MyTable like this:
id | name | color | shape | date_of_creation | other_stuff|
---+------+-------+-------+------------------+------------+
01 | apple| red| circle| 30/12/2015| stuff|
02 | apple| red| circle| 15/12/2015| somestuff|
03 | apple| green| circle| 01/12/2015|anotherstuff|
04 | box| orange| cubic| 13/12/2015| blahblah1|
05 | box| orange| cubic| 25/12/2015| blahblah2|
And I only want the latest grouped by name, color and shape like this:
id | name | color | shape | date_of_creation | other_stuff|
---+------+-------+-------+------------------+------------+
01 | apple| red| circle| 30/12/2015| stuff|
05 | box| orange| cubic| 25/12/2015| blahblah2|
Let's assume that the java equivalent is MyClass with the same elements.
My query in JPQL would look like something like:
SELECT m FROM MyClass m GROUP BY m.name, m.color, m.shape ORDER BY m.date_of_creation;
But this does not work because "date_of_creation" must be in the "GROUP BY" clause, which does not make sense because I don't want to group by date_of_creation. I tried using nested select but I have the same problem with the id now..
I found out about the greatest-n-per-group problem ( SQL Select only rows with Max Value on a Column ) on SO's title suggestions after orginaly typing the question, so I created a named query like this:
SELECT m FROM Myclass LEFT JOIN m.id mleft WHERE
m.name = mleft.name AND
m.color = mleft.color AND
m.shape = mleft.shape AND
m.date_of_creation < mleft.date_of_creation AND
mleft.id = null
Which makes a java.lang.NullPointerException at org.hibernate.hql.internal.ast.HqlSqlWalker.createFromJoinElement(HqlSqlWalker.java:395) or does not even work (no error, nothing). I think it's because the field "id" does not have a relationship with itself. And using an inner join with a select inside does not seem to work in JPQL:
SELECT m FROM MyClass INNER JOIN (SELECT mInner from Myclass GROUP BY m.name etc.)
Maybe I'm missing something and I'll have to build a much more complicated query, but JPQL seems not to be powerfull enough, or I don't know enough of it.
Also, I don't really feel like using the query.getResultList() and doing the work in java...
Any tips are welcome, thanks in advance.
You can't use subqueries in FROM block in JPQL, only SELECT and WHERE blocks allowed. Try someting like this:
SELECT m
FROM Myclass m
WHERE m.date_of_creation IN ( SELECT MAX(mm.date_of_creation)
FROM Myclass mm
WHERE mm.id = m.id)
P.S. Code not tested
I have a SQLite table content with following columns:
-----------------------------------------------
|id|book_name|chapter_nr|verse_nr|word_nr|word|
-----------------------------------------------
the sql query
select count(*) from content where book_name = 'John'
group by book_name, chapter_nr
in DB Browser returns 21 rows (which is the count of chapters)
the equivalent with ORMLite android:
long count = getHelper().getWordDao().queryBuilder()
.groupByRaw("book_name, chapter_nr")
.where()
.eq("book_name", book_name)
.countOf();
returns 828 rows (which is the count of verse numbers)
as far as I know the above code is translated to:
select count(*) from content
where book_name = 'John'
group by book_name, chapter_nr
result of this in DB Browser:
| count(*)
------------
1 | 828
2 | 430
3 | 653
...
21| 542
---------
21 Rows returned from: select count(*)...
so it seems to me that ORMLite returns the first row of the query as the result of countOf().
I've searched stackoverflow and google a lot. I found this question (and more interestingly the answer)
You can also count the number of rows in a custom query by calling the > countOf() method on the Where or QueryBuilder object.
// count the number of lines in this custom query
int numRows = dao.queryBuilder().where().eq("name", "Joe Smith").countOf();
this is (correct me if I'm wrong) exactly what I'm doing, but somehow I just get the wrong number of rows.
So... either I'm doing something wrong here or countOf() is not working the way it is supposed to.
Note: It's the same with groupBy instead of groupByRaw (according to ORMLite documentation joining groupBy's should work)
...
.groupBy("book_name")
.groupBy("chapter_nr")
.where(...)
.countOf()
EDIT: getWordDao returns from class Word:
#DatabaseTable(tableName = "content")
public class Word { ... }
returns 828 rows (which is the count of verse numbers)
This seems to be a limitation of the QueryBuilder.countOf() mechanism. It is expecting a single value and does not understand the addition of GROUP BY to the count query. You can tell that it doesn't because that method returns a single long.
If you want to extract the counts for each of the groups it looks like you will need to do a raw query check out the docs.