JPQL: How to write dynamic where conditions - criteria

I am struggling with JPQL dynamic where condition. I tried searching the syntax for the same but coluldn't find one.
in my case if user is passing the name parameter then the select query should be
select * from user where name = 'sanjay'
if user is not passing name parameter then select query should be
select * from user
Below is my jpql query format which fails when name parameter is not passed.
entity_manager.createQuery("select u from user u where u.name = :name").setParameter("name",params[:name]).getResultList()
How can i update above JPQL query to support both the cases i.e when the name parameter is passed and when the name parameter is not passed ??

This is not possible in JPQL. You even cannot do something like
createQuery("select u from user u where u.name = :name OR :name IS NULL")
It is not possible. That simple. Use two queries or use the Criteria API.

This is the answer I get when I tries to do like you it is working with some modification.
In my case I had the problem that my optional parameter was a List<String> and the solution was the following:
#Query(value = "SELECT *
FROM ...
WHERE (COLUMN_X IN :categories OR COALESCE(:categories, null) IS NULL)"
, nativeQuery = true)
List<Archive> findByCustomCriteria1(#Param("categories") List<String> categories);
This way:
If the parameter has one or more values it is selected by the left side of the OR operator
If the parameter categories is null, meaning that i have to select all values for COLUMN_X, will always return TRUE by the right side of the OR operator
Why COALESCE and why a null value inside of it?
Let's explore the WHERE clause in all conditions:
Case 1: categories = null
(COLUMN_X IN null OR COALESCE(null, null) IS NULL)
The left part of the OR will return false, while the right part of the OR will always return true, in fact COALESCE will return the first non-null value if present and returns null if all arguments are null.
Case 2: categories = ()
(COLUMN_X IN null OR COALESCE(null, null) IS NULL)
JPA will automatically identify an empty list as a null value, hence same result of Case 1.
Case 3: categories = ('ONE_VALUE')
(COLUMN_X IN ('ONE_VALUE') OR COALESCE('ONE_VALUE', null) IS NULL)
The left part of the OR will return true only for those values for which COLUMN_X = 'ONE_VALUE' while the right part of the OR will never return true, because it is equals to 'ONE_VALUE' IS NULL (that is false).
Why the null as second parameter? Well, that's because COALESCE needs at least two parameters.
Case 4: categories = ('ONE_VALUE', 'TWO_VALUE')
(COLUMN_X IN ('ONE_VALUE', 'TWO_VALUE') OR COALESCE('ONE_VALUE', 'TWO_VALUE', null) IS NULL)
As in Case 3, the left part of the OR operator will select only the rows for which COLUMN_X is equale to 'ONE_VALUE' or 'TWO_VALUE'.

Related

How to formulate JPQL query with or without parameter?

For example take this jpql query -
#Query("SELECT account.name, account.type From AccountEntity account WHERE account.id=:accountId")
getAccountNameById(#Param(accountId) int accountId); //i know Spring Data Derived Query can handles this automatically - but lets not use this for this discussion.
in the above jpql query, if no accountId is passed, i want to select records for all accountId's. Is it possible. I know i can use another query - getAllAccounts() and call it from service layer based on accountId is present or not. But I have to handle it from repo in this case.
So is it possibel that JPQL returns all records when parm value is missing or null.
A little bit late but...
In this example the 1st WHEN is going to evaluate :accountId
if it's null then the query is going to return all elements,
in the 2nd WHEN if :accountId is not null then is going to return all elements
where account.id = :accountId
SELECT account.name, account.type FROM AccountEntity account
WHERE CASE WHEN :accountId IS NULL THEN TRUE
WHEN :accountId IS NOT NULL AND account.id = :accountId THEN TRUE ELSE FALSE END
test if parameter is null
SELECT account.name, account.type From AccountEntity account WHERE (:accountId is null or account.id=:accountId)

Default return value for JPA query

I have a JPA query
#Query(value = "SELECT SUM(total_price) FROM ... WHERE ...", nativeQuery = true)
which works as expected when there are matching records. But when there are no matching records, the query returns null.
How can I return zero(0) instead of null when no records are found?
You can change return type to be an Optional;
#Query(value = "SELECT SUM(total_price) FROM ... WHERE ...", nativeQuery = true)
Optional<Integer> getSum(...);
Or you can wrap this getSum() with a default method;
#Query(..)
Integer getSum(...);
default Integer safeGetSum(..) {
return Optional.ofNullable(getSum(..)).orElse(0);
}
More info on null handling in repositories
When the return value is not a list, or some wrapper (plus some others check below), & there are no matching records, the return will be null, so there is no slick way to handle this with some defaultValue=0 through #Query
The absence of a query result is then indicated by returning null. Repository methods returning collections, collection alternatives, wrappers, and streams are guaranteed never to return null but rather the corresponding empty representation.
A native SQL option is COALESCE which returns the first non-null expression in the arg list
SELECT COALESCE(SUM(total_price),0) FROM ...

JPA Criteria "IS NULL OR NOT IN"

I am trying to build the CriteriaQuery equivalent of the following SQL where clause, using OpenJPA 2.4.0:
where employee_status is null or employee_status not in ('Inactive','Terminated')
I have created a list of employeeStatuses, and this is how I am adding the predicate:
predicates.add(
criteriaBuilder.or(
criteriaBuilder.isNull(domainEntity.get("employeeStatus")),
criteriaBuilder.not(domainEntity.get("employeeStatus").in(employeeStatuses))
)
);
The generated SQL looks like this:
AND (t0.EMPLOYEE_STATUS IS NULL OR NOT (t0.EMPLOYEE_STATUS = ? OR t0.EMPLOYEE_STATUS = ?) AND t0.EMPLOYEE_STATUS IS NOT NULL)
As you can see, the 'not in' statement is being transformed into multiple comparisons, with 't0.EMPLOYEE_STATUS IS NOT NULL' added at the end. In order for this to work, the translated clause should be contained in another set of parenthesis.
Any ideas about how I can get this to work?

EF Left joining a table on two properties combined with a case statement

I'm trying to write a query for a database that will left join a table to a look up table and the results will be returned based on a case statement.
In normal SQL the query would look like this:
SELECT chis_id, chis_detail, cilt.mhcatID, cilt.mhtID, 'TheFileName' =
CASE
WHEN cilt.mhcatID IS NOT NULL AND cilt.mhtID IS NOT NULL THEN chis_linked_filename
END
FROM chis
LEFT JOIN cilt on cilt.mhcatID = chis.mhcat_id AND cilt.mhtID = chis.mht_id
WHERE cch_id = 50
chis is the table being queried, cilt is a look-up table and does not contain any foreign key relationships to chis as a result (chis has existing FK's to mht and mhcat tables by the mhtID and mhcatID respectively).
The query will be used to return a list of history updates for a record. If the join to the cilt lookup table is successful this means that the caller of the query will have permission to view the filename of any associated files for the history updates.
Whilst during my research I've found various posts on here relating on how to do case statements and left joins in Linq to Entity queries, I've not been able to work out how to join on two different fields. Is this possible?
You need to join on an anonymous type with matching field names like so:
var query = from x in context.Table1
join y in context.Table2
on new { x.Field1, x.Field2 } equals new { y.Field1, y.Field2 }
select {...};
A full working example using the an extra from instead of a join would look something like this:
var query = from chis in context.Chis
from clit in context.Clit
.Where(x => x.mhcatID = chis.mhcat_id)
.Where(x => x.mhtID = chis.mht_id)
.DefaultIfEmpty()
select new
{
chis.id,
chis.detail,
cilt.mhcatID,
cilt.mhtID,
TheFileName = (cilt.mhcatID != null && cilt.mhtID != null) ? chis.linked_filename : null
};
Based on what Aducci suggested, I used a group join and DefaultIsEmpty() to get the results I wanted. For some reason, I couldn't get DefaultIfEmpty() didn't work correctly on its own and the resulting SQL employed an inner join instead of a left.
Here's the final code I used to get the left join working:
var query = (from chis in context.chis
join cilt in context.cilts on new { MHT = chis.mht_id, MHTCAT = chis.mhcat_id } equals new { MHT = cilt.mhtID, MHTCAT = cilt.mhcatID } into tempCilts
from tempCilt in tempCilts.DefaultIfEmpty()
where chis.cch_id == 50
select new {
chisID = chis.chis_id,
detail = chis.chis_detail,
filename = chis.chis_linked_filename,
TheFileName = (tempCilt.mhcatID != null && tempCilt.mhtID != null ? chis.chis_linked_filename : null),
mhtID = chis.mht_id,
mhtcatID = chis.mhcat_id
}).ToList();

Why null and reference to null not the same thing

Why these two methods work differently:
public List<Foo> GetFoos()
{
int? parentId = null;
var l = _dataContext.Foos.Where(x => x.ParentElementId == parentId).ToList();
return l;
}
public List<Foo> GetFoos()
{
var l = _dataContext.Foos.Where(x => x.ParentElementId == null).ToList();
return l;
}
The first one returns nothing. Second returns what was expected. Data comes from EF. ParentElementId is nullable.
That is because you can't compare to null in SQL, it has the special IS NULL operator to check for null values.
The first query will be translated into a comparison, where the parameter is null:
WHERE ParentElementId = #param
This doesn't work, because comparing two null values doesn't yield true.
The second query will be translated into a null check, because the null value is a constant:
WHERE ParentElementId IS NULL
This works because EF is not fooled to translate it into a comparison.
I know, you got your answer but here is some additional insight:
This issue has been discussed on MSDN forums. Some people believe it's a bug, others say this is intentional behaviour due to performance reasons
It's always helps running EFProf or Sql Server Profiler (in case you are working with SQL Server. For example your two examples translate into two following statements respectively:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[ParentElementId] AS [ParentElementId]
FROM [dbo].[Foo] AS [Extent1]
WHERE [Extent1].[ParentElementId] = NULL
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[ParentElementId] AS [ParentElementId]
FROM [dbo].[Foo] AS [Extent1]
WHERE [Extent1].[ParentElementId] IS NULL
This technique (looking at generated SQL) is often very useful when dealing with problems in EF.
Captain Obvious: because parentId is not null, probably.
Response to edit: first one is not compilable. Type cannot be infered for null.
Response to another edit: Because EF query translates nullable types incorrectly probably