'LIKE' expression on JSONB column with CriteriaQuery - java

I am trying to use JSONB with CriteriaQuery, and trying to implement this query in terms of criteria builder:
select *
from customer
where exists (
select true
from jsonb_array_elements(customer.contacts::jsonb)
where value::text like '%2%'
);
But seems to be there is no way, in criteria query, to have correlated subquery from function execution result.
Do you know how to implement this query in criteria query or how to make a 'LIKE' expression against JSONB column?
JSONB column has following format: [{"type": "PHONE", "value": "1122"}, {"type": "PHONE", "value": "3344"}];
The query should be a criteria query instance.
Thanks in advance.

Try something like this:
select *
from customer
cross join lateral jsonb_array_elements(contacts) as x(jcontact)
where jcontact->>'value' like '%2%';
Have a look at the results. You will get multiple rows if there is more than one contact that has value like '%2%', but you can eliminate those with distinct and restricting to the columns you actually need.
As for HIbernate or criteriaquery, I cannot help you there. ORMs are a really bad idea.

Related

Spring JPA: Association requirement when joining tables

We have two tables, ORDER and STATUS.
For every change in the orders in ORDER table, we enter the audit into STATUS table with lastUpdatedDateTime.
Sometimes, I just need to fetch order detail and sometimes I need latest status along with order details.
How should I design my entity and how should I join tables when needed?
Do I need to add associations to join table(using criteria builder)?
If I add associations, status data from STATUS table will be pulled even when not needed (e.g. when doing findBy on ORDER where we just need order details)?
Also when I display to the User the orders, they should look like below. Need status and lastUpdatedBy in the Order object
[
{
"OrderId": "1",
"product": "pen",
"status": "IP",
"lastUpdatedBy": "user1"
},
{
"OrderId": "1",
"product": "book",
"status": "CP",
"lastUpdatedBy": "user2"
}
]
I have tried using association and criteria builder to join tables.
Questions:
A right way to design my entites
how to join tables to fetch the top record from status table for a given order.
(P.S i need to use predicates as I have dynamic where clause requirement)
Sample SQL
Select f, o.statusCode, o.lastUpdatedBy
from FX_ORDER f left join ORDER o
ON f.ORDER_ID = o.ORDER_ID
and f.SEQUENCE= o.SEQUENCE
where o.statusCode = 'IP'
and o.lastUpdatedDateTime in (select max(s.lastUpdatedDateTime)
from ORDER s where
s.ORDER_ID = o.ORDER_ID
and s.SEQUENCE= o.SEQUENCE);
Based on the information, it seems like you have a one-to-many relationship between ORDER and STATUS tables, where an ORDER can have multiple STATUS records.
To design your entities, you can create two classes: Order and Status. The Order class will have a collection of Status objects as a member variable to represent the one-to-many relationship. The Order class will also have attributes for OrderId and product, and the Status class will have attributes for statusCode, lastUpdatedBy, and lastUpdatedDateTime. You can then use JPA annotations to map these classes to the corresponding tables in the database.
When you want to fetch order details with the latest status, you can use a query that joins the ORDER and STATUS tables and selects the top record from the STATUS table for each ORDER record.Check below example query
SELECT o.OrderId, o.product, s.statusCode, s.lastUpdatedBy
FROM ORDER o
LEFT JOIN STATUS s ON o.OrderId = s.OrderId
AND s.lastUpdatedDateTime = (
SELECT MAX(s2.lastUpdatedDateTime)
FROM STATUS s2
WHERE s2.OrderId = o.OrderId
)
This query joins the ORDER and STATUS tables on the OrderId column and uses a subquery to select the latest STATUS record for each ORDER record. The LEFT JOIN ensures that all ORDER records are included in the result, even if there are no corresponding STATUS records.
To use this query with dynamic where clause requirements, you can create a JPA CriteriaQuery object and add predicates to it based on the search criteria.Here's an example
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Order> query = cb.createQuery(Order.class);
Root<Order> orderRoot = query.from(Order.class);
Join<Order, Status> statusJoin = orderRoot.join("statuses", JoinType.LEFT);
query.multiselect(
orderRoot.get("OrderId"),
orderRoot.get("product"),
statusJoin.get("statusCode"),
statusJoin.get("lastUpdatedBy")
);
List<Predicate> predicates = new ArrayList<>();
predicates.add(cb.equal(statusJoin.get("statusCode"), "IP"));
query.where(predicates.toArray(new Predicate[0]));
query.orderBy(cb.desc(statusJoin.get("lastUpdatedDateTime")));
List<Order> result = em.createQuery(query).getResultList();

QueryDsl - How create inner join with sorted and grouped table

I have to find Salesmen that have sold some itemType. I created method (see below).
But client told me that he wants to find Salesmen by LAST sold itemType.
DB schema:
My attempts: we have in table ORDERS date column, so in normal SQL query I can do double subquery and it should work.
Double, because first I'm sorting by date, then group by salesman - that returns list with only last sold items.
SELECT *
FROM SALESMEN
JOIN
(SELECT *
FROM
(SELECT *
FROM ORDERS
ORDER BY ORDERS.date)
GROUP BY ORDERS.salesman_id) ON SALESMEN.id = ORDERS.salesman_id
WHERE ORDERS.item_type = "CAR"
Unfortunately, queryDSL can do subquery only in IN clause not in FROM.
I spend many hours to find a solution, and in my opinion, it's simply impossible using queryDSL to get sorted and grouped list and join it with another table in one query.
But maybe someone with grater experience has any idea, maybe a solution is simpler than I think :D
public List<SalesmanEntity> findSalesman(SalesmanSearchCriteriaTo criteria) {
SalesmanEntity salesmanEntity = Alias.alias(SalesmanEntity.class);
EntityPathBase<SalesmanEntity> alias = Alias.$(salesman);
JPAQuery<SalesmanEntity> query = new JPAQuery<SalesmanEntity>(getEntityManager()).from(alias);
... a lot of wird IF's....
if (criteria.getLastSoldItemTyp() != null) {
OrderEntity order = Alias.alias(OrderEntity.class);
EntityPathBase<OrderEntity> aliasOrder = Alias.$(order);
query.join(aliasOrder)
.on(Alias.$(salesman.getId()).eq(Alias.$(order.getSalesmanId())))
.where(Alias.$(order.getItemTyp()).eq(criteria.getLastSoldItemTyp()));
}
return query.fetch();
}
Environment:
Java 1.8
SpringBoot 2.0.9
QueryDSL 4.1.4
This is not a limitation of QueryDSL, rather it is a limitation of JPQL - the query language of JPA. For example, SQL does allow subqueries in the FROM clause, and as such querydsl-sql also allows it. With plain plain JPA, or even Hibernate's proprietary HQL it cannot be done. You would have to write a native SQL query then. For this you can have a look at #NamedNativeQuery.
It is possible to add subqueries on top of JPA using Common Table Expressions (CTE) using Blaze-Persistence. Blaze-Persistence ships with an optional QueryDSL integration as well.
Using that extension library, you can just write the following:
QRecursiveEntity recursiveEntity = new QRecursiveEntity("t");
List<RecursiveEntity> fetch = new BlazeJPAQuery<>(entityManager, cbf)
.select(recursiveEntity)
.from(select(recursiveEntity)
.from(recursiveEntity)
.where(recursiveEntity.parent.name.eq("root1"))
.orderBy(recursiveEntity.name.asc())
.limit(1L), recursiveEntity)
.fetch();
Alternatively, when using Hibernate, you can map a Subquery as an Entity, and then correlate that in your query. Using this you can achieve the same result, but you won't be able to reference any outer variables in the subquery, nor will you be able to parameterize the subquery. Both of these features will however be available with the above approach!
#Entity
#Subselect("SELECT salesman_id, sum(amount) FROM ( SELECT * FROM ORDERS ORDER BY ORDERS.date ) GROUP BY ORDERS.salesman_id")
class OrdersBySalesMan {
#Id #Column(name = "salesman_id") Long salesmanId;
#Basic BigDecimal amount; // or something similarly
}
And then in your query:
.from(QSalesman.salesman)
.innerJoin(QOrdersBySalesMan.ordersBySalesMan)
.on(ordersBySalesMan.salesmanId.eq(salesman.id))

What's the equivalent of mysql RLIKE operator in Hibernate Query?

I have the following query which runs perfectly in mysql.
SELECT * FROM Orders as o, Products as p where o.productinfo RLIKE p.code;
Here I am joining two tables Orders and Products with RLIKE.
I am trying to implement the same in Hibernate.
Query query = session.createQuery("FROM Orders as o, Products as p where o.productinfo RLIKE p.code");
List<Object[]> results = query.getResultList();
When I used RLIKE, the following error is thrown in run time.
{"errormessage":"org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token: RLIKE
I tried to implement the same with LIKE query and matched it with '%p.code%'.
Query query = session.createQuery("FROM Orders as o, Products as p where o.productinfo LIKE '%p.code%'");
But it matches with the string "p.code" rather then the value.
What is the equivalent of RLIKE in HQL?
Is there a different way to join two tables with LIKE in HQL?
Thanks.
Answer by #YCF_L:
For any one trying to join two tables with like operator in Hibernate (mysql) can do it the following way.
SELECT * FROM Orders as o, Products as p where o.productinfo LIKE CONCAT('%',p.code,'%');
What's the equivalent of mysql RLIKE operator in Hibernate Query?
RLIKE is the synonym for REGEXP so you can implement it in hibernate using REGEXP_LIKE, you can take a look about this here : How to search in multiple columns using one like operator in HQL (hibernate sql)
I tried to implement the same with LIKE query and matched it with
'%p.code%'.
..., Products as p where o.productinfo LIKE '%p.code%'");
But it matches with the string "p.code" rather then the value.
This is true, because you don't pass the correct value of p.code, you pass it like a String, instead you have two ways :
Query query = session.createQuery("....Products as p where o.productinfo LIKE '%:code%'");
//------------------------------------------------------------------------------^^^^^^^
query.setParameter("code", p.code);
Or you can concatenate your code with your Query, but the first solution is better.
Query query = session.createQuery(".... where o.productinfo LIKE '%" + p.code + "%'");
EDIT
You can use like with CONCAT without specify the '' like this :
SELECT * FROM Orders as o, Products as p where o.productinfo LIKE CONCAT('%',p.code,'%');
You can check regular expression way something like this way: Restrictions.sqlRestriction(word REGEXP '^[A-Z][A-Za-z]*$')
Please check the link which will be helpful for this case: Regular expression with criteria

Hibernate Criteria filter Entity where ManyToMany relation contains multiple objects

I need help with Hibernate Criteria API.
I have a class Job that contain a list of Skill in ManyToMany relationship.
I need to select jobs based on a skill list given as parameter.
I've tried with Restriction.in("skill.id",ids) but this gives me wrong list.If i've selected 2 skills in search form i want the jobs that contain BOTH of them,so i need somehow to implement AND clause.
I've tried with Conjunction:
Conjunction and = Restrictions.conjunction();
for(Skill skill:job.getSkills()){
and.add(Restrictions.eq("skill.id",skill.getId()));
}
And this:
Criteria crit = criteria.createCriteria("skills",JoinType.LEFT_OUTER_JOIN);
for(Skill skill:job.getSkills()){
crit.add(Restrictions.eq("id", skill.getId()));
}
but it creates same alias for skill and it gives me no result.
sql is and (skill1_.ID=? and skill1_.ID=?)
Can anyone help me with this ?thanks
UPDATE:
HQL Query will be like:
select a from Job a where exists (select skill1.id from Skill skill1 join skill1.jobs r where r.id=a.id and skill1.id=1) and exists (select skill2.id from Skill skill2 join skill2.jobs r where r.id=a.id and skill2.id=4)
I need Criteria based on this.
for(Skill skill:job.getSkills()){
DetachedCriteria subquery = DetachedCriteria.forClass(Skill.class,"skill");
subquery.add(Restrictions.eq("id",skill.getId()));
subquery.setProjection(Projections.property("id"));
subquery.createAlias("jobs", "job");
subquery.add(Restrictions.eqProperty("job.id", "Job.id"));
criteria.add(Subqueries.exists(subquery));
}
I managed to solve it.now it works perfect.

How do I access multiple fields in a JPA query?

I have a JPA query of the form:
SELECT category, count(*) AS c FROM ...
I know that if the query just returns a single column I can do something like:
List<Article> articles = query.getResultList();
However, how do I access the results when there are 2 or more columns as in the example above?
check out section 14.6 here: http://docs.jboss.org/hibernate/core/3.3/reference/en/html/queryhql.html#queryhql-select
it will return a List of Object[] if you select more than one column, but dont get the actual entity.

Categories

Resources