简体   繁体   中英

Linq query that does left join and group by having count()

If I have a Rule which has many Criteria and a user selects multiple criteria, how do I write a linq query giving me all the rules where EVERY criteria within that rule exists with the user's selected criteria?

Here is how I'm doing it in SQL currently:

Tables

rule:
  ruleId int
  categoryId int

ruleCriteria:
  ruleId int,
  criteriaId int

@userCriteria:
  criteriaId int
  categoryId int

Query

SELECT
    r.ruleId
FROM dbo.rule r
INNER JOIN dbo.ruleCriteria rc ON r.ruleId= rc.ruleId
LEFT OUTER JOIN @userCriteria uc
        ON rc.criteriaId = uc.criteriaId
        AND r.categoryId = uc.categoryId
GROUP BY r.ruleId
HAVING COUNT(rc.criteriaId) = COUNT(uc.criteriaId)

For the linq query, I'll have these objects (more denormalized, but I could put them in the same structure as the tables if that helps):

userCriteria:
  criteriaId int
  categoryId int

ruleCriteria:
  ruleId int
  categoryId int
  criteriaId int

My goal is the same as the SQL query which is to get a distinct list of matching rules. How would you do it?

I played around with some test data in LINQPad and this is what I came up with.

from rc in ruleCriteria
group new { rc.criteriaId, rc.categoryId } by rc.ruleId into rules
where rules.Count() == userCriteria.Count(uc =>
    rules.Contains(new { uc.criteriaId, uc.categoryId }))
select rules.Key

Here's the explanation, line-by-line:

  1. Get the rules from ruleCriteria
  2. Group the criteria like { criteriaId, categoryId } by the ruleId and save it in rules
  3. For each whole rule, check to see if the number of matching userCriteria is the same as the total number of rules, meaning the user has all the criteria.
  4. Select just the key for this group, which is the ruleId .

This query generates better SQL than the other one, and it's more direct:

from rc in ruleCriterias
group rc by rc.ruleId into rules
where rules.All(rc => userCriterias.Any(uc =>
    uc.criteriaId == rc.criteriaId && uc.categoryId == rc.categoryId))
select rules.Key

You should make sure all the columns are NOT NULL , because if they aren't, this has to generate extra SQL to check to see if things are null.

Unless I am misunderstanding (and therefore over simplyfying the problem) maybe the all .All() method is what you need.

var userCriteria = ; //TODO: Build user criteria
var result = context.Rules.Where(r => r.CategoryId == userCriteria.CategoryId
                                   && r.RuleCriteria.All(rc => rc.id == userCriteria.CriteriaId));

This will only return rulls where all the containing criteria match your criteria. I can't guarantee your sql though

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM