JPA use stored procedure in criteria query - postgresql

I'm trying to define a criteria query with a function in select and in where statement.
The SQL query is:
select s.*, contr_topay(s.id) as rest
from spedizionestd s
where contr_topay(s.id) >0
... other conditions
... optional order by
contr_topay is the procedure in the database (Postgresql). I've defined a NamedStoredProcedure:
#NamedStoredProcedureQuery(
name = "MovimentoContrassegno.contr_topay",
procedureName = "contr_topay",
parameters = {
#StoredProcedureParameter(mode = ParameterMode.IN, queryParameter = "idsped", type = Long.class, optional = false),
#StoredProcedureParameter(mode = ParameterMode.OUT, queryParameter="importo", type=Double.class, optional = false),
}
)
and called it with success:
StoredProcedureQuery query = this.em.createNamedStoredProcedureQuery("MovimentoContrassegno.contr_dapagare");
query.setParameter("idsped", myid);
query.execute();
return (Double) query.getOutputParameterValue(2);
Now, how can I put the procedure in the select clause and in the where condition inside a criteria query?
NB: i need criteria query because I build dynamic query with additional where conditions and "order by" choised by the user at runtime
(I'm using eclipselink 2.6.0)

Related

JPA Criteria API: Predicate for Exists in Another Table in Column

I need to run the following SQL in Criteria API where one additional constraint is that the main table's ned_orgunit_t.nihsac must also exist in a separate table, participating_orgs_t.nihsac.
select o.* from ned_orgunit_t o where
o.current_flag = 'Y' and
o.inactive_date is null
o.nihsac like 'HNM%' and
/* -- This is the problem -- */
exists (select nihsac from idp.participating_orgs_t where nihsac = o.nihsac);
Criteria API Java code:
// First the standard stuff, no issues: Core NED_ORGUNIT_T predicates & selection
CriteriaBuilder crbuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<NedOrgUnit> crquery = crbuilder.createQuery(NedOrgUnit.class);
Root<NedOrgUnit> root = crquery.from(NedOrgUnit.class);
crquery.select(root);
List<Predicate> predicates = new ArrayList<Predicate>();
Predicate currentFlag = crbuilder.equal(root.get("currentFlag"), "Y");
predicates.add(currentFlag);
Predicate inactiveDate = crbuilder.isNull(root.get("inactiveDate"));
predicates.add(inactiveDate);
Predicate nihsac = crbuilder.like(root.get("nihsac"), topNihsac + '%');
predicates.add(nihsac);
// Now I need to link an EXISTS match on a separate table, PARTICIPATING_ORGS_T
// ...???
// How to do it?

Rewrite query in JPA

I want to rewrite this SQL query in JPA.
String hql = "SELECT date(created_at) AS cdate, sum(amount) AS amount, count(id) AS nooftransaction "
+ "FROM payment_transactions WHERE date(created_at)>=date(now()- interval 10 DAY) "
+ "AND date(created_at)<date(now()) GROUP BY date(created_at)";
TypedQuery<Merchants> query = entityManager.createQuery(hql, Merchants.class);
List<Merchants> merchants = query.getResultList();
Is there a way to rewrite the queries into JPA or I should use it as it is?
In situations like these, more often than not the best approach is to write a plain SQL view:
CREATE OR REPLACE VIEW payment_transactions_stats AS
SELECT date(created_at) AS cdate, sum(amount) AS amount, count(id) AS nooftransaction
FROM payment_transactions
WHERE date(created_at)>=date(now()- interval 10 DAY)
AND date(created_at)<date(now()) GROUP BY date(created_at);
And map it to an #Immutable entity. This approach works well when:
you have read only data
the view does not need parameters (in this case there are solutions as well which span from hacky to nice)
You provide no details about the classes and entities but it could be something like:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = builder.createTupleQuery();
From<PaymentTransaction> tx = query.from(PaymentTransaction.class);
Expression<Long> sumAmount = builder.sum(tx.get("amount"));
Expression<Long> count = builder.count(tx.get("id"));
Expression<Date> createdAt = tx.get("created_at");
query.multiselect(createdAt, sumAmount, count);
query.where(builder.greaterThanOrEqualTo(createdAt, builder.function("DATEADD", "DAY", new Date(), builder.literal(-10))),
builder.lessThan(createdAt, new Date()));
query.groupBy(createdAt);
entityManager.createQuery(query).getResultList().stream()
.map(t -> new Merchants(t.get(0, Date.class), t.get(1, Long.class), t.get(2, Long.class)))
.collect(Collectors.toList());
It is better not to use JPA for complex queries like this. JPA are usually used for simple queries.
Since the question is tagged with spring-data-jpa, you could try using a Spring CRUDRepository on top of your table. In the CRUDRepository, write a custom method with the #Query annotation.
It's hard for me to formulate the entire query because I don't know the members of your Merchants class.
Alternatively you can set the nativeQuery = true for the #Query annotation and use actual DB query to solve your problem.
You can use below code
CriteriaBuilder qb = entityManager.getCriteriaBuilder();
CriteriaQuery cq = qb.createQuery();
Root paymentInstructionsRoot = cq.from(PaymentInstructions.class);
List<Predicate> predicates = new ArrayList<>();
predicates.add(qb.greaterThanOrEqualTo(path, fromDateRange));
predicates.add(qb.lessThanOrEqualTo(path, toDateRange));
Selection cdate = paymentInstructionsRoot.get(PaymentInstructions_.createdAt).alias("cdate");
Selection amount = qb.sum(paymentInstructionsRoot.get(PaymentInstructions_.amount))).alias("amount");
Selection nooftransaction = qb.count(paymentInstructionsRoot.get(PaymentInstructions_.id))).alias("nooftransaction");
Selection[] selectionExpression = {cdate, amount, nooftransaction};
Expression[] groupByExpression = {paymentInstructionsRoot.get(PaymentInstructions_.createdAt)};
cq.multiselect(selectionExpression).where(predicates.toArray(new Predicate[]{})).groupBy(groupByExpression).where(predicates.toArray(new Predicate[]{}));
List<PaymentInstructions> paymentInstructions = entityManager.createQuery(cq).getResultList();
In your Entity class that represents the 'payment_transactions' table, add the following:
#SqlResultSetMapping(
name = "PaymentTransaction.summaryMapping",
classes = {
#ConstructorResult(targetClass = PaymentTransactionSummary.class,
columns = {
#ColumnResult(name = "cdate")
, #ColumnResult(name = "amount")
, #ColumnResult(name = "nooftransaction")
})
}
)
Create a new pojo class named PaymentTransactionSummary (must match the name used above, or whatever name you choose, with member fields cdate, amount, and nooftransaction. Include a constructor that includes those three fields in the order listed above.
Then in your dao class, write this:
Query q = entityManager.createNativeQuery("your query string from above"
, "PaymentTransaction.summaryMapping");
List<PaymentTransactionSummary> results = q.getResultList();

How to get List of Table Entity with only selected columns in Hibernate using native SQL?

I am trying to execute SQL query using session.createSQLQuery() method of Hibernate.
test table has 3 columns :
col1
col2
col3
Working
String sql = "SELECT * FROM test";
SQLQuery query = session.createSQLQuery(sql);
query.addEntity(Test.class);
List<Test> testEntityList = query.list();
Not Working
String sql = "SELECT col1, col2 FROM test";
SQLQuery query = session.createSQLQuery(sql);
query.addEntity(Test.class);
List<Test> testEntityList = query.list();
Error:
The column col3 was not found in this ResultSet.
I need to retrieve only a few specific columns from the table rather than the whole table.
How can I achieve this?
You can use hibernate projections, see this answer Hibernate Criteria Query to get specific columns or you can do this by changing the return type to
List<Object[]> and parsing it to List<Test>
List<Object[]> testEntityList = query.list();
List<Test> res = new ArrayList<Test>(testEntityList.size());
for (Object[] obj : testEntityList) {
Test test = new Test();
test.setCol1(obj[0]);
test.setCol2(obj[1]);
res.add(test);
}

EclipseLink JPQL CASE Statetment returns NoResultException

I would like to do a check in my PostgreSQL database with Eclipse Link in a named query and return a boolean. However when I change my count statement (which returns a correct value) to a case statement I get a NoResultException. What is the problem?
Following a simplified example:
#NamedQuery(name = "User.isExistent",
query = "SELECT CASE WHEN COUNT(u) > 0 THEN true ELSE false END
FROM User u WHERE u.someField = :someField")
Usage
TypedQuery<Boolean> query = em.createNamedQuery("User.isExistent", Boolean.class);
query.setParameter("someField", "someFieldValue");
Boolean result = query.getSingleResult();

Entity Framework limit length of a returned nvarchar column

I want to limit the length of a column in an EF query, ala:
var query = from ce in entities.ContactEvents
.Include("Person")
.Include("Orders")
where ce.PersonID = personID
orderby ce.DateTimeContact descending
select new ContactEvent
{
ID = ce.ID,
DateTimeContact = ce.DateTimeContact,
Description = ce.Description.Substring(0, 500),
Orders = ce.Orders
};
The query fails because the EF can't project the complex type Orders.
The entity or complex type 'Model.ContactEvent' cannot be constructed in a LINQ to Entities query.
I've tried a few different ways to do the same thing such as use an explicit join in the LINQ expression but so far I always hit a snag populating the Orders collection in the select projection.
Any ideas on how I can construct my query? Ideally I don't even want to use a select projection but I'm assuming I need to in order to be able to limit the length of the description column returned from the database.
You cannot project to entity types. That is the limitation. If you want to return projection (calling select new) you must either return anonymous type or custom non entity type. If you want to return entity type you must always return whole column from linq-to-entities. You can try to trim the column after object is materialized by using:
var data = (from ce in entities.ContactEvents
.Include("Person")
.Include("Orders")
where ce.PersonID = personID
orderby ce.DateTimeContact descending
select ce)
.AsEnumerable()
.Select(e => new ContactEvent
{
ID = e.ID,
DateTimeContact = e.DateTimeContact,
Description = e.Description.Substring(0, 500),
Orders = e.Orders
});