计算3个相关表的SUM

时间:2016-10-25 19:44:39

标签: sql-server entity-framework sql-server-2012 entity-framework-6

我有三个一对多关系的表。由于我的业务场景不容易解释,我将使用更熟悉的术语:

客户 - >发票 - > InvoiceDetails

让我们假设存在Customers.Value1,Invoices.Value2,InvoiceDetails.Value3,所有类型为double(real)。

我需要获取包含来自特定国家/地区的客户的所有记录的Value1,Value2和Value3的摘要(实际上我的where子句有更多条件,但所有条件仅与Customers表相关)。

我需要的值的3查询示例如下所示:

SELECT SUM(c.Value1) FROM Customers c WHERE c.Country = <cond>
SELECT SUM(i.Value2) FROM Customers c INNER JOIN Invoices i ON c.Id = i.CustomerId WHERE c.Country = <cond>
SELECT SUM(d.Value2) FROM (Customers c INNER JOIN Invoices i ON c.Id = i.CustomerId) INNER JOIN InvoiceDetails d ON i.Id = d.InvoiceId WHERE c.Country = <cond>

现在想象一下,如果我的WHERE子句非常复杂,那么重复这个WHERE子句三次看起来非常糟糕并且容易出错。此外,在此示例中,我们以相同的方式过滤记录3次

有没有办法避免重复WHERE子句,并在单个查询中执行此操作?

修改:根据建议在联接查询中执行所有三个摘要的答案,让我提供数据来解释为什么这不正确。

Customers from Spain:
Customer1  Value1 = 10
Customer2  Value1 = 20

Invoices for customers from Spain:
Invoice1  Customer1 Value2 = 100
Invoice2  Customer1 Value2 = 200
Invoice3  Customer2 Value2 = 300
Invoice4  Customer2 Value2 = 400

SELECT SUM(c.Value1) FROM Customers c WHERE c.Country = "Spain"
returns 30

SELECT SUM(c.Value1), SUM(i.Value2) FROM Customers c INNER JOIN Invoices i ON c.Id = i.CustomerId WHERE c.Country = "Spain"
returns 60, 1000

如您所见,由于已加入记录,客户摘要的结果不正确。

1 个答案:

答案 0 :(得分:2)

由于您使用的是EF,因此您可以定义和使用导航属性而不是连接。例如:

public class Customer
{
    // ...
    public ICollection<Invoice> Invoices { get; set; }
}

public class Invoice
{
    // ...
    public ICollection<InvoiceDetail> Details { get; set; }
}

现在您可以像这样使用简单的LINQ To Entities查询(因为您需要多个聚合,查询使用 group by constant 技术):

var query = 
    from c in db.Customers
    where c.Country = <cond>
    group c by 1 into g
    selec new
    {
        Value1 = g.Sum(c => (double?)c.Value1) ?? 0,
        Value2 = g.SelectMany(c => c.Invoices).Sum(i => (double?)i.Value2) ?? 0,
        Value3 = g.SelectMany(c => c.Invoices).SelectMany(i => i.Details).Sum(d => (double?)d.Value2) ?? 0,
    };
var result = query.FirstOrDefault();

当相应的集合为空时,需要nullable强制转换才能避免Sum异常。

更新:以上不会产生好的SQL。奇怪的是,编写LINQ查询的方式会影响生成的SQL查询(我有一种感觉,就像我们按照编写查询的方式控制SQL查询执行计划的那些日子一样)。以下是替代LINQ查询:

var query =
    from c in db.Customers
    where c.Country == "BG"
    let Value1 = (double?)c.Value1
    let Value2 = c.Invoices.Sum(i => (double?)i.Value2)
    let Value3 = c.Invoices.SelectMany(i => i.Details).Sum(i => (double?)i.Value2)
    group new { Value1, Value2, Value3 } by 1 into g
    select new
    {
        Value1 = g.Sum(e => e.Value1),
        Value2 = g.Sum(e => e.Value2),
        Value3 = g.Sum(e => e.Value3),
    };
var result = query.FirstOrDefault();

产生更接近预期的东西:

SELECT
    [Limit1].[K1] AS [C1],
    [Limit1].[A1] AS [C2],
    [Limit1].[A2] AS [C3],
    [Limit1].[A3] AS [C4]
    FROM ( SELECT TOP (1)
        [Project2].[K1] AS [K1],
        SUM([Project2].[A1]) AS [A1],
        SUM([Project2].[A2]) AS [A2],
        SUM([Project2].[A3]) AS [A3]
        FROM ( SELECT
            1 AS [K1],
            [Project2].[Value1] AS [A1],
            [Project2].[C1] AS [A2],
            [Project2].[C2] AS [A3]
            FROM ( SELECT
                [Project1].[Value1] AS [Value1],
                [Project1].[C1] AS [C1],
                (SELECT
                    SUM([Extent4].[Value2]) AS [A1]
                    FROM  [dbo].[Invoice] AS [Extent3]
                    INNER JOIN [dbo].[InvoiceDetail] AS [Extent4] ON [Extent3].[Id] = [Extent4].[Invoice_Id]
                    WHERE [Project1].[Id] = [Extent3].[Customer_Id]) AS [C2]
                FROM ( SELECT
                    [Extent1].[Id] AS [Id],
                    [Extent1].[Value1] AS [Value1],
                    (SELECT
                        SUM([Extent2].[Value2]) AS [A1]
                        FROM [dbo].[Invoice] AS [Extent2]
                        WHERE [Extent1].[Id] = [Extent2].[Customer_Id]) AS [C1]
                    FROM [dbo].[Customer] AS [Extent1]
                    WHERE N'BG' = [Extent1].[Country]
                )  AS [Project1]
            )  AS [Project2]
        )  AS [Project2]
        GROUP BY [K1]
    )  AS [Limit1]