JPA Criteria "IS NULL OR NOT IN" - jpa

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?

Related

EF Core completely ignores my selected properties in select

As I understand it, the following code should generate a query containing only the RouteId, RouteNo, and ShipId
var tow = (from t in _context.AllTowData
where t.RouteId == id
orderby t.RouteNo descending
select new TowDefaults {
Id = t.RouteId,
TowNo = t.RouteNo,
ShipId = t.ShipId,
LastTow = t.RouteNo
})
.FirstOrDefault();
However, I get:
SELECT v.route_id, v.route_no, v.tow_id, v.analysis_complete, v.checks_complete, v.cpr_id, v.date_created, v.date_last_modified, v.factor, v.fromportname, v.instrument_data_file, v.instrument_id, v.internal_number, v.mastername, v.message, v.miles_per_division, v.month, v.number_of_samples, v.number_of_samples_analysed_fully, v.prop_setting, v.route_status, v.sampled_mileage, v.serial_no_per_calendar_month, v.ship_speed, v.silk_reading_end, v.silk_reading_start, v.toportname, v.tow_mileage, v.validity, v.year
FROM view_all_tow_data AS v
WHERE v.route_id = '#__id_0'
ORDER BY v.route_no DESC
LIMIT 1
That's every column except the explicitly requested ShipId! What am I doing wrong?
This happens using both a SQL Server and a PostGres database
The property ShipIdis not mapped, either by a [NotMapped] annotation or a mapping instruction. As far as EF is concerned, the property doesn't exist. This has two effects:
EF "notices" that there's an unknown part the final Select and it switches to client-side evaluation (because it's a final Select). Which means: it translates the query before the Select into SQL which doesn't contain the ShipId column, executes it, and materializes full AllTowData entities.
It evaluates the Select client-side and returns the requested TowDefaults objects in which ShipId has its default value, or any value you initialize in C# code, but nothing from the database.
You can verify this by checking _context.AllTowData.Local after the query: it will contain all AllTowData entities that pass the filter.
From your question it's impossible to tell what you should do. Maybe you can map the property to a column in the view. If not, you should remove it from the LINQ query. Using it in LINQ anywhere but in a final Select will cause a runtime exception.

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 ...

JPQL: How to write dynamic where conditions

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'.

Performance: LinqPad and Entity Framework generate different SQL for same Linq request

I think I understand from LinqPad doc that it uses a different linq to tsql translator than Entity Framework. LinqPad's is actually more efficient in at least one case! Here's the details:
LinqPad generates the following simple sql query:
SELECT [t0].[personId]
FROM [Person] AS [t0]
WHERE (NOT (([t0].[deleted]) = 1)) AND ([t0].[masterPersonId] = #p0)
ORDER BY [t0].[personId] DESC
For this C# linq code:
int? state = null;
string realmId = null;
int? reviewerId = null;
bool? deleted = false;
long? parentPersonId = 1275660779659;
var query = from person in Persons
where
(!deleted.HasValue || person.Deleted == deleted) &&
(!state.HasValue || person.personState == state) &&
(!parentPersonId.HasValue || person.masterPersonId == parentPersonId) &&
(realmId == null || person.realmId == realmId) &&
(reviewerId == null ||(person.reviewerId == reviewerId ))
orderby person.personId descending
select person.personId;
So you can see LinqPad translates the linq statement above and removes extraneous sql when a parameter value is null. Nice!
EF however always generates this regardless of null parameters:
SELECT
[Extent1].[personId] AS [personId]
FROM [dbo].[Person] AS [Extent1]
WHERE (#p__linq__0 IS NULL OR [Extent1].[deleted] = #p__linq__1) AND
(#p__linq__2 IS NULL OR [Extent1].[personState] = #p__linq__3) AND
(#p__linq__4 IS NULL OR [Extent1].[masterPersonId] = #p__linq__5) AND
(#p__linq__6 IS NULL OR [Extent1].[realmId] = #p__linq__7) AND
((#p__linq__8 IS NULL) OR ([Extent1].[reviewerId] = #p__linq__9))
It makes for a slower query. We were hoping to use LinqPad to evaluate generated sql for EF, but obviously not if the results will be different. It looks like we can target a custom assembly for an EF connection in LinqPad. I'll play around with that to see if we can at least bring the sql queries together.
Anyone ever travel this road or know of an EF setting we can take advantage of? We're running EF4.
Thanks in advance.
I found this excellent webcast from the author of LinqPad where he mentions this very issue. http://oreilly.com/pub/e/1295
I was using Linq To SQL translation not EF's.

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