Linq to SQL:左连接到MAX聚合

时间:2010-02-24 04:13:10

标签: linq-to-sql

我正在尝试编写一个Linq to SQL语句,该语句显示所有客户记录,并且只显示发票表的匹配最大值(InvoiceId);基本上是客户的最新发票。左连接是必需的,因为客户可能没有任何发票,但需要在结果集中。

两个基本表,其外键为Customer.CustomerID = Invoice.CustomerId

CREATE TABLE [dbo].[Customer](
    [CusomerId] [int] IDENTITY(1,1) NOT NULL,
    [CustomerName] [int] NOT NULL
    CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED 
(
    [CustomerId] ASC
)
) ON [PRIMARY]

CREATE TABLE [dbo].[Invoice](
    [InvoiceId] [int] IDENTITY(1,1) NOT NULL,
    [CustomerId] [int] NOT NULL,
    [InvoiceTotal] [float] NOT NULL
    CONSTRAINT [PK_Invoice] PRIMARY KEY CLUSTERED 
(
    [InvoiceId] ASC
)
) ON [PRIMARY]

所需结果集的SQL如下:

SELECT *
FROM Customers c
  LEFT JOIN 
    (Invoice i 
      INNER JOIN (SELECT CustomerId, MAX(InvoiceId) as InvId FROM Invoice GROUP BY CustomerId) as InvList 
      ON i.InvoiceNo = InvList.InvoiceNo) ON c.CustomerId = i.CustomerId

根据我的发现,我不认为这可以在一个声明中完成;需要首先创建MAX(InvoiceId)产品并在主语句中使用。由于我不能让它工作,也许我也错了。

2 个答案:

答案 0 :(得分:3)

您可以在LINQ中编写此特定查询,如下所示 - 尽管这会产生相关的子查询:

var query = 
    from c in ctx.Customer
    select new
    {
        Customer = c,
        LatestInvoice = ctx.Invoice
            .Where(i => i.CustomerId == c.CustomerId)
            .OrderByDescending(i => i.InvoiceId)
            .FirstOrDefault();
    };

如果您想以另一种方式执行此操作,LINQ语法的可读性较差,但由于延迟执行,可以将查询拆分一点:

var latestInvoicesPerCustomerQuery = 
    from inv in ctx.Invoice
    group inv by inv.CustomerId into g
    select new { CustomerId = g.Key, InvoiceId = g.Max(inv => inv.InvoiceId) };

var customersAndLatestInvoicesQuery = 
    from customer in ctx.Customer
    join linv in latestInvoicesPerCustomer 
         on customer.CustomerId equals linv.CustomerId
         into latestInvoiceJoin
    from latestInvoice in latestInvoiceJoin.DefaultIfEmpty() // left join
    join invoice in ctx.Invoice 
         on latestInvoice.InvoiceId equals invoice.InvoiceId
    select new
    {
        Customer = customer,
        LatestInvoice = invoice
    };

第一个查询(latestInvoicesPerCustomerQuery)在您枚举它或第二个查询引用第一个查询之前不会执行。就运行时而言,最终查询是一个表达式树 - 因此您可以将第一个查询视为已吸收到第二个查询中。

如果你真的想在一个查询中使用它,你也可以这样做:

var customersAndLatestInvoicesQuery = 
    from customer in ctx.Customer
    join linv in (
            from inv in ctx.Invoice
            group inv by inv.CustomerId into g
            select new 
            { 
                CustomerId = g.Key, 
                InvoiceId = g.Max(inv => inv.InvoiceId) 
            }
        ) 
        on customer.CustomerId equals linv.CustomerId
        into latestInvoiceJoin
    from latestInvoice in latestInvoiceJoin.DefaultIfEmpty() // left join
    join invoice in ctx.Invoice 
         on latestInvoice.InvoiceId equals invoice.InvoiceId
    select new
    {
        Customer = customer,
        LatestInvoice = invoice
    };

customersAndLatestInvoicesQuery的任何一种变体都应该大致转换为您在帖子中列出的SQL。

答案 1 :(得分:0)

我无法让Ben M的例子工作,但我能够将其用于以下方面:

var latestInvoicesPerCustomerQuery = 
    from inv in ctx.Invoice
    group inv by inv.CustomerId into g
    join invj in ctx.Invoice on g.Max(inv => inv.InvoiceId) equals invj.InvoiceId
    select invj;

var customersAndLatestInvoicesQuery = 
    from customer in ctx.Customer
    join linv in latestInvoicesPerCustomer 
         on customer.CustomerId equals linv.CustomerId
         into latestInvoiceJoin
    from latestInvoice in latestInvoiceJoin.DefaultIfEmpty() // left join
    select new
    {
        Customer = customer,
        LatestInvoice = invoice
    };

在第一个声明中,我将发票表加入了结果,故意不使用select new。

//If I had done:
select new {LatestInvoice=invj}
// then I would have included the name LatestInvoice in the second statement:
join linv in latestInvoicesPerCustomer 
             on customer.CustomerId equals linv.LatestInvoice.CustomerId
             into latestInvoiceJoin
// Not desirable to me, and it seems it may be troublesome when used.

这实际上简化了第二个语句,只需要对第一个语句对象进行简单的左连接。我现在正在为拥有这些客户的客户提供所有客户和最新发票的结果集。

我不确定为什么Ben M的解决方案不起作用,但是当删除以下行时,我只会得到一个左连接产品:

join invoice in ctx.Invoice 
         on latestInvoice.InvoiceId equals invoice.InvoiceId

包含此行,产品是内部联接。