具有多重嵌套表的分组方式和计数作为 LINQ 查询

Posted

技术标签:

【中文标题】具有多重嵌套表的分组方式和计数作为 LINQ 查询【英文标题】:Group By Having and Count as LINQ query with multiply nested tables 【发布时间】:2012-02-28 17:11:56 【问题描述】:

我有以下 SQL 查询来返回所有没有 OrderLines 且没有分配部件的客户 - 即我只想要每个订单的 每个 订单行没有分配部件的客户 - (在我正在处理不同域但已翻译给客户/订单以说明问题的实际问题)

SELECT c.Customer_PK
FROM Customers c
  INNER JOIN Orders o
  ON c.Customer_PK = o.Customer_FK
  LEFT OUTER JOIN OrderLines l
  ON o.Order_PK = l.Order_FK
  LEFT OUTER JOIN Parts p
  ON l.OrderLine_PK = p.OrderLine_FK
GROUP BY c.Customer_PK
HAVING COUNT(p.Part_PK) = 0

我在 LINQ 中想出的最好的方法如下:

Dim qry =     
(From c In context.Customers
 Select New With  c.Customer_PK,
                  .CountParts = 
                      (From o In c.Orders
           From l In o.OrderLines
                       Select l.Parts.Count).DefaultIfEmpty.Sum)

qry = (From grp In qry
       Where grp.CountParts  = 0
       Select grp.Customer_PK)

这可行,但生成的 SQL 不是最优的 - 它正在对客户查询的每一行执行 Count 子查询,而不是使用 Group By 和 Have。我尝试使 LINQ Group By 语法工作,但它一直将过滤器作为 WHERE 而不是 HAVING 子句。

有什么想法吗?

根据以下答案进行编辑:

我接受 JamieSee 的回答,因为它解决了所述问题,即使它没有产生我最初的 GROUP BY HAVING 查询。

感谢 Peter 和 Nick 对此提供意见。我是一名 VB 开发人员,所以我在将您的代码翻译成 VB 时遇到了困难,这是我最接近的,但它并不能完全产生所需的输出:

Dim qry = From c In context.Customers
          Group Join o In context.Orders On c.Customer_PK Equals o.Customer_FK
          Into joinedOrders = Group
          From jo In joinedOrders.DefaultIfEmpty
          Group Join l In context.OrderLines On jo.Order_PK Equals l.Order_FK
          Into joinedLines = Group
          From jl In joinedLines.DefaultIfEmpty
          Group c By Key = New With c.Customer_PK, jl Into grp = Group
          Where Key.jl Is Nothing OrElse Not Key.jl.Parts.Any
          Select c.Customer_PK 

我遇到的问题是我必须按“键”将“jl”推入组中,以便我可以从 Where 子句中引用它,否则编译器无法看到该变量或出现在组之前的任何其他变量按子句。

使用指定的过滤器,我得到所有客户,其中至少一个订单的行没有零件,而不是只有客户没有任何订单的零件。

【问题讨论】:

【参考方案1】:

鉴于您不关心计数,只关心最终客户,请考虑以下重述问题:

识别所有没有包含零件行的订单的所有客户。

这会产生:

var customersWithoutParts = from c in Context.Customers
                            where !(from o in Context.Orders
                                    from l in o.Lines
                                    from p in l.Parts
                                    select o.Customer_FK).Contains(c.Customer_PK)
                            select c.Customer_PK;

这应该会产生大致相当于以下内容的发出的 SQL:

SELECT     c.Customer_PK
FROM         Customers AS c
WHERE     (NOT EXISTS
                          (SELECT     o.Cusomer_FK
                            FROM          Orders AS o INNER JOIN
                                                   OrderLines AS l ON o.Order_PK = l.Order_FK INNER JOIN
                                                   Parts AS p ON l.OrderLine_PK = p.OrderLine_FK
                            WHERE      (o.Customer_FK = c.Customer_PK))) 

要获得您试图重现的 SQL,我将首先尝试以下操作:

var customersWithoutParts = from c in Context.Customers
                            from o in c.Orders.DefaultIfEmpty()
                            from l in o.Lines.DefaultIfEmpty()
                            join part in Context.Parts on part.OrderLine_FK equals l.OrderLine_PK into joinedParts
                            where joinedParts.Count() == 0
                            select c.Customer_PK;

请注意,在 VB 中,此处的 join 将替换为 Group Join

【讨论】:

谢谢你,我将接受这个答案,因为它适用于我给出的示例并给出合理的 SQL。如果我要求的不是 NO 部分,那么我相信这个答案不再有效,因为 NOT EXISTS 必然意味着 COUNT = 0。更广泛的问题仍然是如何说服 LINQ-to-Entities 生成 SQL GROUP BY 和有类似例子的语法。 用我认为应该产生原始 SQL 的东西更新了答案。我没有测试更新的部分。让我知道这对你有什么作用。【参考方案2】:

在没有生成的模型 (C#) 的情况下很难创建查询:

from o in dc.Orders
join jOrderLines in dc.OrderLines on o.Order_PK equals jOrderLines.Order_FK into joinedOrderlines
from l in joinedOrderLines.DefaultIfEmpty() 
group o by o.Customer_FK into g
where l == null || l.Count(x => x.Parts) == 0
select g.Key

【讨论】:

【参考方案3】:

这样的事情怎么样:

var qry = from c in db.Customers
            join o in db.Orders.Where(x => x.Customer_FK == c.Customer_PK)
            join l in db.OrderLines.Where(x => x.Order_FK = o.Order_PK).DefaultIfEmpty()
            join p in db.Parts.Where(x => x.OrderLine_FK = l.OrderLine_PK).DefaultIfEmpty()
            group c by new
            
                c.Customer_PK
             into g
            where g.Count(p => p.Part_PK != null) == 0
            select new
            
                Customer_PK = g.Key.Customer_PK
            ;

【讨论】:

以上是关于具有多重嵌套表的分组方式和计数作为 LINQ 查询的主要内容,如果未能解决你的问题,请参考以下文章

Linq 高级分组 + 计数到新模型

Linq to SQL - 分组和计数

LINQ查询表达式 - LINQ 查询分组

具有内部联接、多个分组依据和最小最大值的 Linq 查询

Linq 按连接表中的项目计数分组

Falcor 查询中的多重嵌套