Spring data repository with empty IN clause - mysql

I'm using Spring data JPA like this:
public interface SampleRepository extends CrudRepository<Sample, Integer>{
#Query("SELECT s FROM Sample s WHERE ((:ids) IS NULL OR s.id IN (:ids))")
List<Sample> queryIn(#Param("ids") List<Integer> ids);
}
But when ids is not empty, I got (1,2,3) is null which is incorrect in mysql.
If I write like this:
public interface SampleRepository extends CrudRepository<Sample, Integer>{
#Query("SELECT s FROM Sample s WHERE s.id IN (:ids)")
List<Sample> queryIn(#Param("ids") List<Integer> ids);
}
When ids is empty, I got s.id in (null)
If I must use native query to do this. Any suggestions?

I guess you want to use ISNULL function to see if :ids has a value or not
SELECT s FROM Sample s WHERE s.id IN (:ids)) or ISNULL(:ids) = 1

Related

Spring Data Rest : What should be the return type of Query

I want to execute the follwoing query but when I used the object[] as return type it gives the error of "PersistentEntity must not be null!" What should be the return type of this query ?
public interface PropertyRepository extends JpaRepository<Property, Long>,JpaSpecificationExecutor {
#Query(value="select p.city.name, count(p) from Property p join p.city group by p.city.id")
Object[] countPropertyInCities();
}
Your query will return a List<Object[]>.
Try this
#Query(value="select p.city.name as name, count(p) as pCount from Property p join p.city c group by c.id")
List<Object[]> countPropertyInCities();
I suggest you try JPA Projections to only fetch entity properties that your actually need.

JPQL join translation policy

I am having trouble searching in my application database due to the translated query resulting from my JPQL query.
Consider the following Structure:
#Entity
#Table(name="pc")
public class PC {
#Id
#GeneratedValue
private Long id;
#Column
private String name;
#ManyToOne
#JoinColumn(name="DEFAULTOS")
private Software defaultOS;
...}
#Entity
#Table(name="software")
public class Software {
#Id
#GeneratedValue
private Long id;
#Column
private String name;
...}
Here is the search query I'm doing:
public List<PC> getPcsInstalled(Software soft){
TypedQuery<PC> lQuery = em.createQuery("SELECT lPC FROM PC lPC"
+ " WHERE lPC.defaultOS IS NULL"
+ " OR lPC.defaultOS.name NOT LIKE :param", PC.class);
lQuery.setParameter("param", soft.getName());
return lQuery.getResultList();
}
Which translates to:
SELECT t1.ID, t1.NAME, t1.DEFAULTOS FROM software t0, pc t1
WHERE (((t1.DEFAULTOS IS NULL) OR NOT (t0.NAME LIKE "Linux"))
AND (t0.ID = t1.DEFAULTOS))
The tables are "joined" with the latest condition (t0.ID = t1.DEFAULTOS). Which automatically filters any PC having DEFAULTOS = NULL (which is not what I want obviously).
The query I expected was:
SELECT t1.ID, t1.NAME, t1.DEFAULTOS FROM pc t1
LEFT JOIN software t0 ON t1.DEFAULTOS = t0.ID
WHERE (((t1.DEFAULTOS IS NULL) OR NOT (t0.NAME LIKE "Linux")))
Of course I can rewrite the JPQL query to specify the LEFT JOIN explicitely, but I was wondering : Is there a way to have the LEFT JOIN as the default policy to join tables?
In fact this is just a simple example, in my project I build much more complex queries and adding the needed LEFT JOIN would be very complex in the actual code.
For information, I'm using Glassfish 4.0 with EclipseLink and a MySQL database.

Write this SQL query in LINQ to Entity format

I have this sql query need to convert to linq to entity
select kv.KeyID, kv.KeyVotID, v.VotID, v.FullName
from E_KeyVot kv
join E_Vot v
on kv.VotID = v.VotID
where kv.KeyID=2
order by v.FullName
This what I have tried but I'm sure it is not correct:
Public Function GetKeyVot() As IQueryable(Of Ems_KeyVot)
Try
Dim _objQuery As IQueryable(Of Ems_KeyVot, Ems_Vot) = _
From a In Context.Ems_KeyVot
Join b In Context.Ems_Vot On a.votid Equals b.votid
Where a.keyid = pub_KeyID
Order By b.FullName
Return _objQuery
Catch ex As Exception
End Try
End Function
Based on your query, the issue you have is that your return types don't agree with the query. The query returns an IQueryable(Of Ems_KeyVot, Ems_Vot), but your function is expecting an IQueryable(Of Ems_KeyVot).
In your query, you are projecting values from both tables. Since you can't return an anonymous type from your function, you need to create another class to return the results:
Public Class Votes
Public Property KeyID As Integer
Public Property KeyVotID As Integer
Public Property VotID As Integer
Public Property FullName As String
End Class
With that, you can modify your query as follows:
Dim _objQuery As IQueryable(Of Ems_KeyVot, Ems_Vot) = _
From a In Context.Ems_KeyVot
Join b In Context.Ems_Vot On a.votid Equals b.votid
Where a.keyid = pub_KeyID
Order By b.FullName
Select New Votes With { .KeyID = a.KeyID, .KeyVotID = a.KeyVotID, .VotID = b.VotID, .FullName = b.FullName }
Also change your function definition to
Public Function GetKeyVot() As IQueryable(Of Votes)
One other note, I see that you have a try catch block, but you aren't doing anything with the exception. If you can't handle an exception, remove the block and let it bubble up to something that can.

Translating a complex SQL query into LINQ

I have plenty of experience with SQL but am fairly new to LINQ and am struggling to convert the following MySQL query into LINQ.
Can anyone help convert the following to LINQ for use in an ASP.net MVC project with Entity framework?
SELECT
S.Submission_ID,
P.Photo_ID,
C2.Contract_Name,
J.Job_Number,
D.Device_Name,
A.`Display_Name`,
S.Submission_Status,
S.Submission_JobRef,
S.Created,
TRUE
FROM
Submission S
LEFT JOIN Job J ON S.`Job_ID` = J.`Job_ID`
LEFT JOIN Contract C2 ON J.`Contract_ID` = C2.`Contract_ID`
INNER JOIN Submission_Status SS ON S.`Submission_Status` = SS.`ID`
INNER JOIN Device D ON S.`Device_ID` = D.`Device_ID`
INNER JOIN ACTION A ON S.`Action_ID` = A.`Action_ID`
INNER JOIN (
SELECT
MIN(P.Photo_ID) AS Photo_ID,
P.Submission_ID
FROM
Photo P
GROUP BY
P.`Submission_ID`) P ON S.`Submission_ID` = P.Submission_ID
WHERE
S.`Submission_Status` <> 3 AND
(LOCATE(#Criteria, C2.`Contract_Name`) > 0 OR
LOCATE(#Criteria, J.`Job_Number`) > 0 OR
LOCATE(#Criteria, D.`Device_Name`) > 0 OR
LOCATE(#Criteria, A.`Display_Name`) > 0 OR
LOCATE(#Criteria, SS.`Value`) > 0 OR
LOCATE(#Criteria, S.`Submission_JobRef`) > 0)
ORDER BY
S.`Submission_ID` DESC
I have tried to get my head around the multiple joins and subquery but have since got stuck. This is what I have so far...Obviously, it is not working or complete!!
Dim results = From S In db.Submissions
Join P In db.Photos On S.Submission_ID Equals P.Submission_ID
Group Join J In db.Jobs On S.Job_ID Equals J.Job_ID
Into Job = Group
Join J In db.Jobs On S.Job_ID Equals J.Job_ID
Group By P.Submission_ID
Into SubmissionPhotoID = Min(P.Photo_ID)
Select New With {.Submission_ID = Submission_ID,
.Photo_ID = SubmissionPhotoID,
.Contract_Name = If(IsNothing(S.Job), "", S.Job.Contract.Contract_Name),
.Job_Number = If(IsNothing(S.Job), "", S.Job.Job_Number),
.Device_Name = S.Device.Device_Name,
.Action_Name = S.Action.Display_Name,
.Submission_Status = S.Submission_Status1.ID,
.Submission_JobRef = S.Submission_JobRef,
.Created = S.Created,
.CanEdit = bolCanEdit}
Order By S.Submission_ID
Skip param.iDisplayStart
Take param.iDisplayLength
Any help or guidance with the above would be greatly appreciated!
Edit
To aid things, here are the classes from the model defining the entities used in the above query. (I have omitted some field which have no relevance to the question).
Partial Public Class Submission
Public Property Submission_ID As Integer
Public Property Job_ID As Nullable(Of Integer)
Public Property Device_ID As Integer
Public Property Action_ID As Integer
Public Property Submission_Status As Nullable(Of Integer)
Public Property Submission_JobRef As String
Public Property Created As Nullable(Of Date)
Public Overridable Property Action As Action
Public Overridable Property Device As Device
Public Overridable Property Job As Job
Public Overridable Property Photos As ICollection(Of Photo) = New HashSet(Of Photo)
Public Overridable Property Submission_Status1 As Submission_Status
End Class
Partial Public Class Job
Public Property Job_ID As Integer
Public Property Contract_ID As Nullable(Of Integer)
Public Property Job_Number As String
Public Overridable Property Contract As Contract
Public Overridable Property Submissions As ICollection(Of Submission) = New HashSet(Of Submission)
End Class
Partial Public Class Contract
Public Property Contract_ID As Integer
Public Property Contract_Name As String
Public Overridable Property Jobs As ICollection(Of Job) = New HashSet(Of Job)
End Class
Partial Public Class Submission_Status
Public Property ID As Integer
Public Property Value As String
Public Overridable Property Submissions As ICollection(Of Submission) = New HashSet(Of Submission)
End Class
Partial Public Class Device
Public Property Device_ID As Integer
Public Property Device_Name As String
Public Overridable Property Submissions As ICollection(Of Submission) = New HashSet(Of Submission)
End Class
Partial Public Class Action
Public Property Action_ID As Integer
Public Property Display_Name As String
Public Overridable Property Submissions As ICollection(Of Submission) = New HashSet(Of Submission)
End Class
Partial Public Class Photo
Public Property Photo_ID As Integer
Public Property Submission_ID As Integer
Public Overridable Property Submission As Submission
End Class
That's a fairly complex piece of SQL, with a sub-select and mixture of left and inner joins.
Some quick suggestions:
Break it down into a sequence of linq statements, starting with your core objects and adding the related pieces in subsequent steps. If you keep the results as IQueryable, the compiler will put it all together for you and send as one query to the db (i.e. don't ToList() until the last step).
Personally, I do joins using two from's and a where extension method than using the join operator. I makes it easier to know that you're getting a left join or an inner join, for one thing.
For example:
FROM Submission S LEFT JOIN Job J ON S.`Job_ID` = J.`Job_ID`
I would do this as (sorry I'm c# so the syntax may not be quite correct for VB)
Dim results = from s in db.Submissions
from j in db.Jobs.Where(j=> j.Job_Id == s.Job_Id).DefaultIfEmpty()
So, the join criteria is inside the .Where() on Jobs and .DefaultIfEmpty() tells it to left-join (essentially, Job will be a default if the join fails).
FURTHER EDIT:
After experimenting, I got this code to return a result (is it the correct result is another question). Again, sorry for the c# syntax.
[TestMethod]
public void Query()
{
const string conStr = "Data Source=(local);Initial Catalog=ComplexSqlToLinq; Integrated Security=True";
var db = new MyDbContext(conStr);
const string criteria = "Contract1";
var minPhotos = from p in db.Photos
group p by p.SubmissionId
into g
select new {SubmissionId = g.Key, PhotoId = g.Min(p=>p.PhotoId)};
var query = from s in db.Submissions
from j in db.Jobs.Where(j => j.JobId == s.JobId).DefaultIfEmpty()
from c in db.Contracts.Where(c => c.ContractId == j.ContractId).DefaultIfEmpty()
from ss in db.SubmissionStatuses.Where(ss => ss.Id == s.SubmissionStatus)
from d in db.Devices.Where(d => d.DeviceId == s.DeviceId)
from a in db.Actions.Where(a => a.ActionId == s.ActionId)
from p in minPhotos.Where(p => p.SubmissionId == s.SubmissionId)
where s.SubmissionStatus != 3 &&
( c.ContractName.Contains(criteria) ||
j.JobNumber.Contains(criteria) ||
d.DeviceName.Contains(criteria) ||
a.DisplayName.Contains(criteria) ||
ss.Value.Contains(criteria) ||
s.SubmissionJobRef.Contains(criteria))
select new
{
s.SubmissionId,
p.PhotoId,
c.ContractName,
j.JobNumber,
d.DeviceName,
a.DisplayName,
s.SubmissionStatus,
s.SubmissionJobRef,
s.Created,
SomeBool = true
};
var result = query.ToList();
Assert.IsTrue(result.Any());
}
Obviously, you can vary the criteria constant in the test to apply to different items, I chose to match the Contract - I assume that only one of the tables will strike a match.
This query generates the following SQL, looks a bit hokey but is pretty similar in function to your original.
SELECT
[Filter1].[SubmissionId] AS [SubmissionId],
[GroupBy1].[A1] AS [C1],
[Filter1].[ContractName] AS [ContractName],
[Filter1].[JobNumber] AS [JobNumber],
[Filter1].[DeviceName] AS [DeviceName],
[Filter1].[DisplayName] AS [DisplayName],
[Filter1].[SubmissionStatus] AS [SubmissionStatus],
[Filter1].[SubmissionJobRef] AS [SubmissionJobRef],
[Filter1].[Created] AS [Created],
cast(1 as bit) AS [C2]
FROM
(
SELECT
[Extent1].[SubmissionId] AS [SubmissionId],
[Extent1].[SubmissionStatus] AS [SubmissionStatus],
[Extent1].[SubmissionJobRef] AS [SubmissionJobRef],
[Extent1].[Created] AS [Created],
[Extent2].[JobNumber] AS [JobNumber],
[Extent3].[ContractName] AS [ContractName],
[Extent4].[Value] AS [Value],
[Extent5].[DeviceName] AS [DeviceName],
[Extent6].[DisplayName] AS [DisplayName]
FROM
[dbo].[Submissions] AS [Extent1]
LEFT OUTER JOIN [dbo].[Jobs] AS [Extent2] ON [Extent2].[JobId] = [Extent1].[JobId]
LEFT OUTER JOIN [dbo].[Contracts] AS [Extent3] ON [Extent3].[ContractId] = [Extent2].[ContractId]
INNER JOIN [dbo].[SubmissionStatus] AS [Extent4] ON [Extent4].[Id] = [Extent1].[SubmissionStatus]
INNER JOIN [dbo].[Devices] AS [Extent5] ON [Extent5].[DeviceId] = [Extent1].[DeviceId]
INNER JOIN [dbo].[Actions] AS [Extent6] ON [Extent6].[ActionId] = [Extent1].[ActionId]
WHERE
3 <> [Extent1].[SubmissionStatus]
) AS [Filter1]
INNER JOIN (
SELECT
[Extent7].[SubmissionId] AS [K1],
MIN([Extent7].[PhotoId]) AS [A1]
FROM
[dbo].[Photos] AS [Extent7]
GROUP BY
[Extent7].[SubmissionId] ) AS [GroupBy1]
ON [GroupBy1].[K1] = [Filter1].[SubmissionId]
WHERE
(
[Filter1].[ContractName] LIKE #p__linq__0 ESCAPE N'~') OR
([Filter1].[JobNumber] LIKE #p__linq__1 ESCAPE N'~') OR
([Filter1].[DeviceName] LIKE #p__linq__2 ESCAPE N'~') OR
([Filter1].[DisplayName] LIKE #p__linq__3 ESCAPE N'~') OR
([Filter1].[Value] LIKE #p__linq__4 ESCAPE N'~') OR
([Filter1].[SubmissionJobRef] LIKE #p__linq__5 ESCAPE N'~')
)
To respond to Dave Johnson's comment in a word - scalability.
Recently I was trying to improve performance of an application and my first thought was to add some SQL similar in complexity to John Henry's sample - multiple joins and filters. After all, it performed like a rocket on my dev machine.
The architect flatly prohibited the use of complex SQL on the database server, on the basis that several large applications with 100's of users were hooked in to it. Much as I like building snappy SQL that rocks, I had to agree. Shifting the logic to to machine that consumes it is good architecture.
So for those of us proficient in declarative SQL, learning translation to linq skills is important.
Of course, the solution I gave earlier doesn't achieve this as the same SQL is sent to the server. But having a linq equivalent is a start that can be be further optimised.
After an awful lot of searching and reading various articles I have given up trying to write this query in LINQ query syntax and gone with method syntax instead.
A big thank you to Ackroydd for your suggestions and support with converting complex SQL to LINQ. When you know you can accomplish something in SQL in a matter of minutes but need to use LINQ for scalability and to keep with existing code, it can get rather frustrating!
Here is what I ended up with as I'm sure it will be useful to someone else:
Dim query As IQueryable(Of Submission)
' Initialise the new query
query = db.Submissions.Include(Function(s) s.Action) _
.Include(Function(s) s.Photos) _
.Include(Function(s) s.Device) _
.Include(Function(s) s.Job) _
.Include(Function(s) s.Submission_Status1) _
.Include(Function(s) s.Job.Contract) _
.Include(Function(s) s.Comments) _
.AsNoTracking
' Apply initial filters
query = query.Where(Function(S) Not S.Submission_Status1.ID.Equals(3))
' Apply search criteria if passed
If Not String.IsNullOrEmpty(param.sSearch) Then
query = query.Where(Function(S) S.Job.Contract.Contract_Name.Contains(param.sSearch) OrElse
S.Job.Job_Number.Contains(param.sSearch) OrElse
S.Device.Device_Name.Contains(param.sSearch) OrElse
S.Action.Display_Name.Contains(param.sSearch) OrElse
S.Submission_Status1.Value.Contains(param.sSearch) OrElse
S.Submission_JobRef.Contains(param.sSearch))
End If
' Order the results
query = query.OrderByDescending(Function(S) S.Submission_ID)
' Paginate the results
query = query.Skip(param.iDisplayStart).Take(param.iDisplayLength)
' Return only the required columns
Dim resultData = query.AsEnumerable.Select(Function(S) New AjaxSubmissionOverview With { _
.Submission_ID = S.Submission_ID,
.Photo_ID = S.Photos.First.Photo_ID,
.Contract_Name = If(IsNothing(S.Job), "", S.Job.Contract.Contract_Name),
.Job_Number = If(IsNothing(S.Job), "", S.Job.Job_Number),
.Device_Name = S.Device.Device_Name,
.Action_Name = S.Action.Display_Name,
.Submission_Status = S.Submission_Status,
.Submission_JobRef = S.Submission_JobRef,
.Latest_Comment = If(S.Comments.Count = 0, "", HtmlHelpers.Truncate(S.Comments.Last.Comment1, 100)),
.Created = S.Created,
.CanEdit = bolCanEdit})

Comparing two collections of enums with Hibernate or SQL

I have an entity 'Parent' which has a Set of enums
private Set<MyEnum> myEnums = EnumSet.noneOf(MyEnum.class);
#CollectionOfElements(targetElement=MyEnum.class)
#JoinTable
(name="PARENT_MY_ENUM",
joinColumns=#JoinColumn(name="PARENT_ID"))
#Enumerated(EnumType.STRING)
#Column (name="MY_ENUM", nullable=false)
public Set<MyEnum> getMyEnums(){
return myEnums;
}
public MyEnum {
ENUM_A,
ENUM_B,
ENUM_C,
ENUM_D;
}
Now I want to search for this entity with a collection of MyEnums. Only entities where all enums are set as in the search collection should be returned.
So if entity A has ENUM_A, ENUM_B and ENUM_C and entity B has ENUM_B, ENUM_C, ENUM_D a search with the search collectoin ENUM_A, ENUM_B, ENUM_C should only return entity A. A search for ENUM_B and ENUM_C should return nothing.
How would I do that in Hibernate?
if I do
select p from Parent p where p.myEnums IN (:searchCollection) and size(p.myEnums) = size(:searchCollection)
then this would return both entities for the first search.
Any ideas?
Update: I got a step further by figuring out how to do it in MySQL but applying this to Hibernate generates invalid SQL.
You would use a subquery with EXISTS for it like:
WHERE EXISTS(
SELECT pa.PARENT_ID, count(pme.MY_ENUM) FROM PARENT pa, PARENT_MY_ENUM pme
where pa.PARENT_ID = pme.PARENT_ID
AND pme.MY_ENUM IN ('ENUM_A','ENUM_B')
GROUP BY pa.PARENT_ID HAVING count(pme.MY_ENUM) = 2
)
But when I try to do the same in Hibernate:
select pa.ParentId, count(pa.myEnums) from Parent pa
WHERE pa.myEnums IN ('ENUM_A','ENUM_B')
GROUP BY pa.ParentId HAVING count(pa.myEnums) = 2
Hiberante creates this SQL statement:
select pa.CONTAINER_RELEASE_REFERENCE_ID as col_0_0_, count(.) as col_1_0_ from PARENT pa, PARENT_MY_ENUM enum1, PARENT_MY_ENUM enum2, PARENT_MY_ENUM enum3
where pa.PARENT_ID=enum1.PARENT_ID and pa.PARENT_ID=enum2.PARENT_ID and pa.PARENT_ID=enum3.PARENT_ID
and (. in ('ENUM_A' , 'ENUM_B'))
group by pa.PARENT_ID having count(.)=2
MySQL complains about the '.', Where is that coming from and why is Hibernate using 3 joins to PARENT_MY_ENUM?
Is this a Hibernate bug or what am I doing wrong?
Give the following a try for you exists subselect
select pa.ParentId, count(en) from Parent pa join pa.myEnums as en
WHERE en IN ('ENUM_A','ENUM_B')
GROUP BY pa.ParentId HAVING count(en) = 2
Otherwise, I wonder if something like that might not do the job
select p from Parent p join p.myEnums em
where (:results) = elements(em)
or
select p from Parent p join p.myEnums em
where (:results) in elements(em)
I think you can do that in java. Execute your initially proposed query, iterate the result and exclude (iterator.remove()) the false positives. It should be O(n), and I believe MySQL will require the same time to filter your result.