Selecting either in array or null in Hibernate query language - spring

I would like to select Users that has group that has name in a given array or has null group.
This is my attempted query:
#Query("SELECT u FROM User u WHERE (u.group is null OR u.group.name IN :groups)")
Optional<Page<User>> findUsersByKeywordAndGroupIncludingNullGroup(Pageable pageable, #Param("groups") String... groups); but when I run I have an error in SQL syntax.
Therefore, is there a correct way to do this?
Thanks in advance.

Problem with your query is
If you will pass (null or empty) groups, then the resultant query will become
select * from users where users.group is null or users.group in ()
Thus results in Syntax Error near '('
You can do a hack using sPEL
#Query("SELECT U FROM User U " +
"WHERE U.group IS NULL " +
"OR (1=:#{ #groups == null || #groups.size() == 0 ? 1 : 0} OR U.group.name IN :#{#groups})")
List findUserWithoutGroupOrInGroup(#Param("groups") List<String> groups);
Here, I have used a proxy condition
1=:#{ #groups == null || #groups.size() == 0 ? 1 : 0}
Second part of this condition returns 1 if the parameter groups is null or empty, results in condition 1=1 being TRUE and thereby skipping U.group.name IN :#{#groups}
This approach is not a recommended one, and also not scalable.
In my opinion, you should fetch the Users without any Group and Users in given group separately. You can even fire there queries in parallel and control not to fire Users in given group if groups is empty.

Related

Joins Using QueryDsl and Boolean Expression

Need a help regarding QueryDsl using BooleanExpression. Find the requirement below.
I have Enquiry and Followup table. OneToMany mapping in between them. 1 Enquiry having many followup.
Suppose i have 5 enquiries. for each enquiry i have 3 followup.
Expectation is ..i need last record of followup for each enquiry.
Please find the different ways i have tried.
QBuyerFollowUp qBuyerfollowup = QBuyerFollowUp.buyerFollowUp;
ListPath<BuyerFollowUp, QBuyerFollowUp> followUpList = qBuyerEnquiry.followUpList;
BooleanExpression expression = commonExpression.and(new JPAQuery<>().select(qBuyerfollowup.nextDate).from(qBuyerfollowup)
.where(qBuyerfollowup.eq(followUpList.any()))
.orderBy(qBuyerfollowup.followUpId.desc()).limit(1).eq(new Date());
please ignore commonExpression which is an another expression.
Error: Subquery returns more than 1 row
BooleanExpression expression = followUpList.any().followUpId.in(new JPAQuery<QBuyerFollowUp>().select(qBuyerfollowup.followUpId.max())
.from(qBuyerfollowup).groupBy(qBuyerfollowup.buyerEnquiry.id)
.having(qBuyerfollowup.nextDate.eq(dateFormat.parse(dateFormat.format(new Date())))));
Error: unknown column nextDate.
Here generated query is:
select count(buyerenqui0_.id) as col_0_0_ from buyer_enquiry buyerenqui0_ where
buyerenqui0_.assigned_to= 79 and
(buyerenqui0_.created_at between "2020-05-01 00:00:00" and "2020-05-31 23:59:59") and buyerenqui0_.enq_status<> "Dropped" and
not ( not (exists (select followupli1_.id from buyer_followup followupli1_ where buyerenqui0_.id=followupli1_.buyer_enquiry_id)))
and (exists (select 1 from buyer_followup followupli2_ where buyerenqui0_.id=followupli2_.buyer_enquiry_id and
(followupli2_.id in (select max(a.id) from buyer_followup a group by a.buyer_enquiry_id having date(a.next_date)=date(now())))));
solution: If will remove alias name 'a' from this query
select max(a.id) from buyer_followup a group by a.buyer_enquiry_id having date(a.next_date)=date(now())))
it is working fine.
But we don't have any control on this because alias name is generated by Spring.
You can't join subqueries in JPQL, but you can project them, which should be sufficient in this case:
QInquiry inquiry = QInquiry.inquiry;
QFollowup followup = QFollowup.followup;
Map<Inquiry, Followup> results = query().from(inquiry)
.transform(GroupBy.groupBy(inquiry).as(query().from(followup)
.where(followup.inquiry.eq(inquiry)
.orderBy(followup.id.desc())
.limit(1)))

Unable to create a constant value of type 'System.DBNull'. Only primitive types or enumeration types are supported in this context

I try to apply left outer join on the basis of two ids one in the primary key of one table while the foreign key of another table also nullable
var yarnPOFilter_Grid = (from yrq in _context.Yarn_Requisition_Details
//join ypo in
_context.Yarn_PurchaseOrder_Details on yrq.YarnRequsitionDetailID
equals
ypo.YarnRequsitionDetailID into t
join ypo in
_context.Yarn_PurchaseOrder_Details on yrq.YarnRequsitionDetailID
equals
DBNull.Value.Equals(ypo.YarnRequsitionDetailID) ? 0 :
ypo.YarnRequsitionDetailID into t
from rt in t.DefaultIfEmpty() //
DefaultIfEmpty preserves left-hand elements that have no matches on the
right side
select new
{
YarnRequsitionDetailID =
(rt.YarnRequsitionDetailID == null ? long.MinValue :
rt.YarnRequsitionDetailID),
yrq.YarnID,
yrq.Yarn.YarnName,
yrq.YarnFellowID,
yrq.Yarn_FellowCodes.YarnFellowCode,
yrq.QuantityRequired,
rt.QuantityOrdered,
QuantityBalance_Custom =
yrq.QuantityRequired - rt.QuantityOrdered
}).ToList();
return yarnPOFilter_Grid;
I get this error message when I deal with null in joining condition
Unable to create a constant value of type 'System.DBNull'. Only primitive types or enumeration types are supported in this context.
Execute ToList() First before Select as there is no sql equivalent to the conditions you added in the YarnRequsitionDetailID = (rt.YarnRequsitionDetailID == null ? long.MinValue : rt.YarnRequsitionDetailID)
so it will be
from rt in t.DefaultIfEmpty()).ToList().Select(c => new
{
YarnRequsitionDetailID =
(c.rt.YarnRequsitionDetailID == null ? long.MinValue :
c.rt.YarnRequsitionDetailID),
c.yrq.YarnID,
c.yrq.Yarn.YarnName,
c.yrq.YarnFellowID,
c.yrq.Yarn_FellowCodes.YarnFellowCode,
c.yrq.QuantityRequired,
c.rt.QuantityOrdered,
QuantityBalance_Custom =
c.yrq.QuantityRequired - c.rt.QuantityOrdered
}).ToList();
i hope this solved your previous problem

How can I merge two outputs of two Linq queries?

I'm trying to merge these two object but not totally sure how.. Can you help me merge these two result objects?
//
// Create Linq Query for all segments in "CognosSecurity"
//
var userListAuthoritative = (from c in ctx.CognosSecurities
where (c.SecurityType == 1 || c.SecurityType == 2)
select new {c.SecurityType, c.LoginName , c.SecurityName}).Distinct();
//
// Create Linq Query for all segments in "CognosSecurity"
//
var userListAuthoritative3 = (from c in ctx.CognosSecurities
where c.SecurityType == 3 || c.SecurityType == 0
select new {c.SecurityType , c.LoginName }).Distinct();
I think I see where to go with this... but to answer the question the types of the objects are int, string, string for SecurityType, LoginName , and SecurityName respectively
If you're wondering why I have them broken like this is because I want to ignore one column when doing a distinct. Here are the SQL queries that I'm converting to SQL.
select distinct SecurityType, LoginName, 'Segment'+'-'+SecurityName
FROM [NFPDW].[dbo].[CognosSecurity]
where SecurityType =1
select distinct SecurityType, LoginName, 'Business Line'+'-'+SecurityName
FROM [NFPDW].[dbo].[CognosSecurity]
where SecurityType =2
select distinct SecurityType, LoginName, SecurityName
FROM [NFPDW].[dbo].[CognosSecurity]
where SecurityType in (1,2)
You can't join these because the types are different (first has 3 properties in the resulting type, second has two).
If you can tolerate putting a null value in for the 3rd result of the second query this will help. I would then suggest you just do a userListAuthoritative.concat(userListAuthoritative3 ) BUT I think this will not work as the anonymous types generated by the linq will not be of the same class, even tho the structure is the same. To solve that you can either define a CustomType to encapsulate the tuple and do select new CustomType{ ... } in both queries or postprocess the results using select() in a similar fashion.
Acutally the latter select() approach will also allow you to solve the parameter count mismatch by implementing the select with a null in the post-process to CustomType.
EDIT: According to the comment below once the structures are the same the anonymous types will be the same.
I assume that you want to keep the results distinct:
var merged = userListAuthoritative.Concat(userListAuthoritative3).Distinct();
And, as Mike Q pointed out, you need to make sure that your types match, either by giving the anonymous types the same signature, or by creating your own POCO class specifically for this purpose.
Edit
If I understand your edit, you want your Distinct to ignore the SecurityName column. Is that correct?
var userListAuthoritative = from c in ctx.CognosSecurities
where new[]{0,1,2,3}.Contains(c.SecurityType)
group new {c.SecurityType, c.LoginName, c.SecurityName}
by new {c.SecurityType, c.LoginName}
select g.FirstOrDefault();
I'm not exactly sure what you mean by merge, since you're returning different (anonymous) types from each one. Is there a reason the following doesn't work for you?
var userListAuthoritative = (from c in ctx.CognosSecurities
where (c.SecurityType == 1 || c.SecurityType == 2 || c.SecurityType == 3 || c.SecurityType == 0)
select new {c.SecurityType, c.LoginName , c.SecurityName}).Distinct();
Edit: This assumed they were of the same type -- but they're not.
userListAuthoritative.Concat(userListAuthoritative3);
Try below code, you might need to implement IEqualityComparer<T> in your ctx type.
var merged = userListAuthoritative.Union(userListAuthoritative3);

HQL Query with multiple Criteria

I am trying to write a HQL Query which selectes rows from a table based on multiple criteria.
firstName,lastName
the catch is that the query should be flexible to ignore any empty or null values
so
select t from table t where (:firstname = '' or t.firstName = :firstName) AND
(:lastName = '' OR t.lastName = :lastName)
I would have thought this would work? But it doesnt - it never returns any rows? Any ideas what could be wrong here? I am very new to HQL thats why this question.
If I am understanding correctly you want a way to allow the user to search by firstName, lastName or both. So you should be checking if the parameter passed in is empty then don't make it a condition. If they supply all blank parameters it would return the whole table. Try:
select t from table t
where (:firstname IS NULL or t.firstName = :firstName) AND
(:lastName IS NULL OR t.lastName = :lastName)
(:firstname = '' or t.firstName = :firstName)
Your criteria is strange. If :firstname = '' and if a firstname (t.firstName) is equal '' in the database, the criteria t.firstName = :firstName is good ('' = '')
You don't need :firstname = ''
But If you want to check null value, you need to do:
t.firstName IS NULL or t.firstName = :firstname
What happens if you run following hql with firstname parameter set to empty string?
select t from table t where (:firstname = '')
and following with firstname parameter set to null:
select t from table t where (:firstname is null)
If any of the above returns the whole table then the HQLs named parameter might support what you are trying to do.
Otherwise you must use different queries for the null parameter cases. You can do this by generating the query dynamically.
I had a similar requirement. I want dynamic but I'm using a tool that just gives an HQL editor, so no Java.
The query below allows for optional parameters. Essentially a pseudo-quazi XOR of sorts . . . wish there was real XOR :/
With this query you just put NA into a param instead of leaving it empty if it is not needed.
Yeah, yeah, yeah . . . it's ugly, but it works and it's easy to alter to any other scenario needing optional params in pure HQL.
SELECT t AS table
FROM Table t
WHERE (t.valSet = :valSet
AND (:category= 'NA' AND :subCategory= 'NA'))
OR (:category != 'NA'
AND (t.valSet = :valSet
AND t.category= :category))
OR (:subCategory != 'NA'
AND (t.valSet = :valSet
AND t.subCategory = :subCategory ))

Help required to optimize LINQ query

I am looking to optimize my LINQ query because although it works right, the SQL it generates is convoluted and inefficient...
Basically, I am looking to select customers (as CustomerDisplay objects) who ordered the required product (reqdProdId), and are registered with a credit card number (stored as a row in RegisteredCustomer table with a foreign key CustId)
var q = from cust in db.Customers
join regCust in db.RegisteredCustomers on cust.ID equals regCust.CustId
where cust.CustomerProducts.Any(co => co.ProductID == reqdProdId)
where regCust.CreditCardNumber != null && regCust.Authorized == true
select new CustomerDisplay
{
Id = cust.Id,
Name = cust.Person.DisplayName,
RegNumber = cust.RegNumber
};
As an overview, a Customer has a corresponding Person which has the Name; PersonID is a foreign key in Customer table.
If I look at the SQL generated, I see all columns being selected from the Person table. Fyi, DisplayName is an extension method which uses Customer.FirstName and LastName. Any ideas how I can limit the columns from Person?
Secondly, I want to get rid of the Any clause (and use a sub-query) to select all other CustomerIds who have the required ProductID, because it (understandably) generates an Exists clause.
As you may know, LINQ has a known issue with junction tables, so I cannot just do a cust.CustomerProducts.Products.
How can I select all Customers in the junction table with the required ProductID?
Any help/advice is appreciated.
The first step is to start your query from CustomerProducts (as Alex Said):
IQueryable<CustomerDisplay> myCustDisplay =
from custProd in db.CustomerProducts
join regCust in db.RegisteredCustomers
on custProd.Customer.ID equals regCust.CustId
where
custProd.ProductID == reqProdId
&& regCust.CreditCardNumber != null
&& regCust.Authorized == true
select new CustomerDisplay
{
Id = cust.Id,
Name = cust.Person.Name,
RegNumber = cust.RegNumber
};
This will simplify your syntax and hopefully result in a better execution plan.
Next, you should consider creating a foreign key relationship between Customers and RegisteredCustomers. This would result in a query that looked like this:
IQueryable<CustomerDisplay> myCustDisplay =
from custProd in db.CustomerProducts
where
custProd.ProductID == reqProdId
&& custProd.Customer.RegisteredCustomer.CreditCardNumber != null
&& custProd.Customer.RegisteredCustomer.Authorized == true
select new CustomerDisplay
{
Id = cust.Id,
Name = cust.Person.Name,
RegNumber = cust.RegNumber
};
Finally, for optimum speed, have LINQ compile your query at compile time, rather than run time by using a compiled query:
Func<MyDataContext, SearchParameters, IQueryable<CustomerDisplay>>
GetCustWithProd =
System.Data.Linq.CompiledQuery.Compile(
(MyDataContext db, SearchParameters myParams) =>
from custProd in db.CustomerProducts
where
custProd.ProductID == myParams.reqProdId
&& custProd.Customer.RegisteredCustomer.CreditCardNumber != null
&& custProd.Customer.RegisteredCustomer.Authorized == true
select new CustomerDisplay
{
Id = cust.Id,
Name = cust.Person.Name,
RegNumber = cust.RegNumber
};
);
You can call the compiled query like this:
IQueryable<CustomerDisplay> myCustDisplay = GetCustWithProd(db, myParams);
I'd suggest starting your query from the product in question, e.g. something like:
from cp in db.CustomerProducts
join .....
where cp.ProductID == reqdProdID
As you have found, using a property defined as an extension function or in a partial class will require that the entire object is hydrated first and then the select projection is done on the client side because the server has no knowledge of these additional properties. Be glad that your code ran at all. If you were to use the non-mapped value elsewhere in your query (other than in the projection), you would likely see a run-time exception. You can see this if you try to use the Customer.Person.DisplayName property in a Where clause. As you have found, the fix is to do the string concatenation in the projection clause directly.
Lame Duck, I think there is a bug in your code as the cust variable used in your select clause isn't declared elsewhere as a source local variable (in the from clauses).

Resources