JPA Criteria API and Oracle JSON_TABLE function - java

I have a problem with writing criteria in java based on sql code, we must use JSON_TABLE function and search by parameters:
select tab1.id
from TABLE1 tab1
left join
TABLE2 param_json on tab1.id = param_json.TABLE_1_ID,
json_table(
param_json.PARAMS_JSON, '$[*]'
COLUMNS (
"paramCode" VARCHAR2(4000) PATH '$.paramCode',
"displayValue" VARCHAR2(4000) PATH '$.displayValue'
)
) jt
where jt."paramCode" = 'param1' and lower(jt."displayValue") like '%value%');
CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
CriteriaQuery<Long> query = builder.createQuery(Long.class);
Root<Table1> table1Root = query.from(Table1.class);
Join<Table1, Table2> jsonEJBJoin = table1Root.join(Table1_.table2Elements);
//how add json_table function
List<Predicate> whereConditions = new LinkedList<>();
Predicate[] predicates = new Predicate[whereConditions.size()];
//how add where conditions based on json_table function
query.where(whereConditions.toArray(predicates));
query.select(table1Root.get(Table1_.id));
List<Long> resultList = getEntityManager().createQuery(query)
.setMaxResults(10)
.getResultList();

This is hard because you're using a screwdriver as a hammer. The whole point of the Criteria API is to have platform-independent code which doesn't depend on proprietary functions like json_table.
If you have database access, I think the cleanest way to resolve this is to create a view to hide all the Oracle-specific code from JPA, e.g.:
create or replace view VIEW1 (TABLE_1_ID, PARAMCODE, DISPLAYVALUE) as
select param_json.TABLE_1_ID, jt.paramCode, jt.displayValue
from TABLE2 param_json,
json_table(
param_json.PARAMS_JSON, '$[*]'
COLUMNS (
paramCode VARCHAR2(4000) PATH '$.paramCode',
displayValue VARCHAR2(4000) PATH '$.displayValue'
)
) jt;
Then you replace Table2 in your JPA code with View1.
If you can't modify the database, I think you'll have to abandon the Criteria API and use a NativeQuery with bind variable parameters, something like:
List<Long> resultList = getEntityManager().createNativeQuery("select tab1.id
from TABLE1 tab1
left join
TABLE2 param_json on tab1.id = param_json.TABLE_1_ID,
json_table(
param_json.PARAMS_JSON, '$[*]'
COLUMNS (
paramCode VARCHAR2(4000) PATH '$.paramCode',
displayValue VARCHAR2(4000) PATH '$.displayValue'
)
) jt
where jt.paramCode = :param1 and lower(jt.displayValue) like :param2))" )
.setParameter("param1", param1)
.setParameter("param2", "%" + value + "%" )
.setMaxResults(10)
.getResultList();
As a side note, I recommend never using the case-sensitive Oracle identifiers (with double quotes). Some Oracle tutorials use them, but they only make things more complicated.

Related

Convert SQL Query to Criteria Query in Spring Boot

I'm relatively new to Spring JPA CriteriaQuery. Im trying to convert my old native query in my program to criteria query and haven't been successful on join query for multiple table with conditions. I need help converting native SQL query into Criteria Query for these query below :
select * from student s inner join (
select distinct on (student_id) * from class where status = 'Active' order by
student_id,date_register desc) c on s.id = c.user_id
inner join teacher t on t.subject_id = c.subject_id
where t.status = 'Active' and s.status='Active' order by s.name desc
Update :
Below code is as far as I can go cause I dont really know much. Am i in the right direction? I'm opting for Expression because i dont know how to use Join.
CriteriaQuery<Student> query = cb.createQuery(Student.class);
Root<Student> sRoot= query.from(Student.class);
query.select(sRoot);
Subquery<Integer> subquery = query.subquery(Integer.class);
Root<Class> cRoot= subquery.from(CLass.class);
Expression<Integer> max = cb.max(cRoot.get("dateRegister"));
subquery.select(max);
subquery.groupBy(cRoot.get("student"));
query.where(
cb.and(
cb.in(cRoot.get("dateRegister")).value(subquery)
)
);
Thanks in advance!

JQPL select inside select

just switched to spring boot from .NET Core, in .NET core we can easily nest a select inside a select like this:
var result = from c in context.Cars
join br in context.Brands
on c.BrandId equals br.Id
join col in context.Colors
on c.ColorId equals col.Id
select new CarDetailDto
{
Id = c.Id,
BrandName = br.Name,
CarName = c.Name,
ColorName = col.Name,
DailyPrice = c.DailyPrice,
ModelYear = c.ModelYear,
CarImages = (from cimg in context.CarImages
where cimg.CarId == c.Id
select new CarImage
{
Id = cimg.Id,
ImagePath = cimg.ImagePath,
CarId = c.Id,
Date = cimg.Date
}).ToList()
};
I want to do that in JPQL as well but didnt manage to solve
#Query( select column1, column2, column3 from tablename1 where coluname=(select columname from tablename2 where columnname=abcd) )
Your JPQL query should look like above.
Whatever subquery you write with condition.
If your query is fetching 3 column you need to create a DTO with same column name.
If your query is fetching list of rows then your actual jpql will look like this.
#Query( select column1, column2, column3 from tablename1 where coluname=
(select columname from tablename2 where columnname=abcd) )
List<ResultDTO> findAllResultList(Parameter value);
Above it is mapping the result to list of DTO objects to result rows.
If your query is fetching single row then your actual jpql will look like this.
#Query( select column1, column2, column3 from tablename1 where coluname=
(select columname from tablename2 where columnname=abcd) )
ResultDTO findResult(Parameter value);
The single result is mapped to one DTO object.
Make sure your result column name and DTO column name matches
Using the JPA repository call the names of the method which you used for the particular query.

`SELECT COUNT(*) FROM (SELECT DISTINCT ...)` in Hibernate or JPA

This feels like a trivial use case for Hibernate or JPA, but I've been struggling for a couple of days to get this to work.
I have an position entity class that has latitude, longitude and updateTime fields (among others). I would like to count the number of distinct combinations of those three fields while ignoring the others. In SQL, this is trivial:
SELECT COUNT(*) FROM (SELECT DISTINCT LONGITUDE, LATITUDE, UPDATE_TIME FROM POSITION) AS TEMP;
It is important that I abstract myh database implementation from the rest of my application because different users may wish to use different database engines. (Heck I use h2 for testing and mariadb for local production...)
I have been trying to translate this SQL into Java code using either Hibernate or JPA syntax, but I cannot figure out how.
EDIT - Here is as close as I have been able to get using JPA (ref: https://en.wikibooks.org/wiki/Java_Persistence/Criteria)
public long getCountDistinctInFlightPositions() {
Session session = sessionFactory.openSession();
CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
CriteriaQuery<Tuple> innerQuery = criteriaBuilder.createTupleQuery();
Root<Position> position = innerQuery.from(Position.class);
innerQuery.multiselect(
position.get("longitude"),
position.get("latitude"),
position.get("updateTime")
);
// The method countDistinct(Expression<?>) in the type CriteriaBuilder is not applicable for the arguments (CriteriaQuery<Tuple>)
criteriaBuilder.countDistinct(innerQuery);
return 1;
}
You can do it this way:
CriteriaQuery<Long> countQuery = cb.createQuery( Long.class );
Root<Position> root = countQuery.from( Position.class );
countQuery.select( cb.count( root.get( "id" ) ) );
Subquery<Integer> subQuery = countQuery.subquery( Integer.class );
Root<Position> subRoot = subQuery.from( Position.class );
subQuery.select( cb.min( subRoot.get( "id" ) ) );
subQuery.groupBy( subRoot.get( "longitude" ),
subRoot.get( "latitude" ),
subRoot.get( "updateTime" ) );
countQuery.where( root.get( "id" ).in( subQuery ) );
Long count = entityManager.createQuery( countQuery ).getSingleResult();
This effectively generates the following SQL:
SELECT COUNT( p0.id ) FROM Position p0
WHERE p0.id IN (
SELECT MIN( p1.id )
FROM Position p1
GROUP BY p1.longitude, p1.latitude, p1.updateTime )
In a scenario where I have 3 rows and 2 of them have the same tuple of longitude, latitude, and update time, the query will return a result of 2.
Make sure you maintain a good index on [Longtitude, Latitude, UpdateTime] here so that you can take advantage of faster GROUP BY execution. The PK is already b-tree indexed so the other operations wrt COUNT/MIN should be accounted for easily by that index already.

How to write criteria eqivalent of sql having count [duplicate]

I need to create a query and I need COUNT(*) and HAVING COUNT(*) = x.
I'm using a work around that uses the CustomProjection class, that I downloaded somewhere.
This is the SQL that I try to achieve:
select count(*) as y0_, this_.ensayo_id as y1_ from Repeticiones this_
inner join Lineas linea1_ on this_.linea_id=linea1_.id
where this_.pesoKGHA>0.0 and this_.nroRepeticion=1 and linea1_.id in (18,24)
group by this_.ensayo_id
having count(*) = 2
This is the code, where I use the Projection Hibernate class:
critRepeticion.setProjection(Projections.projectionList()
.add( Projections.groupProperty("ensayo") )
.add( CustomProjections.groupByHaving("ensayo_id",Hibernate.LONG,"COUNT(ensayo_id) = "+String.valueOf(lineas.size()))
.add( Projections.rowCount() )
);
The error is:
!STACK 0
java.lang.NullPointerException
at org.hibernate.criterion.ProjectionList.toSqlString(ProjectionList.java:50)
at org.hibernate.loader.criteria.CriteriaQueryTranslator.getSelect(CriteriaQueryTranslator.java:310)
at org.hibernate.loader.criteria.CriteriaJoinWalker.<init>(CriteriaJoinWalker.java:71)
at org.hibernate.loader.criteria.CriteriaLoader.<init>(CriteriaLoader.java:67)
at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1550)
at org.hibernate.impl.CriteriaImpl.list(CriteriaImpl.java:283)
at ar.com.cse.cseagro.controller.RepeticionController.buscarEnsayo(RepeticionController.java:101)
If I comment the line with CustomProjections class, the query work, but I don't get the HAVING COUNT(*) filter in the SQL ...
Basically the query try to retrieve, in a master - detail schema, all the master records where a list of details are simultaneously present, like if you want tho know "which invoices have both products, A and B".
That why if I got 3 items in the IN clause, I need to use HAVING COUNT = 3 clause.
Any idea or suggestion?
Best regards,
I figured out the problem. I replace CusotmProjections class, with:
.add( Projections.sqlGroupProjection("ensayo_id", groupBy , alias, types));
where groupBy, alias and types are:
String groupBy = "ensayo_id" + " having " + "count(*) = " + String.valueOf(lineas.size());
String[] alias = new String[1];
Alias[0] = "ensayo_id";
Type[] types = new Type[1];
types[0] = Hibernate.INTEGER;
and the magic is on groupby String. –
If someone needs to do it in grails it would be like:
projections {
groupProperty("id")
sqlGroupProjection(...)
rowCount()
}
Where sqlGroupProjection is available since 2.2.0
/**
* Adds a sql projection to the criteria
*
* #param sql SQL projecting
* #param groupBy group by clause
* #param columnAliases List of column aliases for the projected values
* #param types List of types for the projected values
*/
protected void sqlGroupProjection(String sql, String groupBy, List<String> columnAliases, List<Type> types) {
projectionList.add(Projections.sqlGroupProjection(sql, groupBy, columnAliases.toArray(new String[columnAliases.size()]), types.toArray(new Type[types.size()])));
}
http://grepcode.com/file/repo1.maven.org/maven2/org.grails/grails-hibernate/2.2.0/grails/orm/HibernateCriteriaBuilder.java/#267
Here is my sample, it works fine, maybe useful :
My sql query :
select COLUMN1, sum(COLUMN2) from MY_TABLE group by
COLUMN1 having sum(COLUMN2) > 1000;
And Criteria would be :
Criteria criteria = getCurrentSession().createCriteria(MyTable.Class);
ProjectionList projectionList = Projections.projectionList();
projectionList.add(Projections.property("column1"), "column1");
projectionList.add(Projections.sqlGroupProjection("sum(column2) sumColumn2 ", "COLUMN1 having sum(COLUMN2) > 1000" , new String[]{"sumColumn2"}, new org.hibernate.type.Type[]{StandardBasicTypes.STRING}));
criteria.setProjection(projectionList);
criteria.List();
criteria.add(Restrictions.sqlRestriction("1=1 having count(*) = 2"));

complex query to equivalent criteriabuilder query(EntityManager)

My Query is this:
query1 = select a.id from entity1 a where a.id in (:List1)
and not exists (select ex2 from entity2 ex2 where ex2.assignedId = a.id)
union
select ex.assignedId from entity2 ex ,entity3 pi
where ex.entity3Id = pi.id and ex.assignedId in (:List1)
and ex.assignedTypeId = :assignedTypeId and pi.processStatus = :status
and not exists
(select ex1.assignedId from entity2 ex1 , entity3 pi1
where ex1.entity3Id = pi1.id and ex1.assignedId = ex.assignedId
and ex1.assignedTypeId = :assignedTypeId
and pi1.processStatus <> :status);
and while trying to execute query,
Query existingIds=em.createQuery(query1); //With all parameters set
throws NullPointerException in line 87 of org.hibernate.hql.ast.ParameterTranslationsImpl
completely checked all the braces and parameters. The equivalent conversion works in mysql.
Can someone assist me in converting the query with CriteriaBuilder, finding it difficult to make the conversion.
Not sure if JPQL supports union operation at all. Are you putting this as NamedQuery or you are creating on the fly (entityManager.createQuery()) ?

Categories

Resources