【问题标题】:Efficient SQL from LINQ statement with joins and aggregate?来自 LINQ 语句的高效 SQL 与连接和聚合?
【发布时间】:2012-05-02 19:53:46
【问题描述】:

我正在尝试做一个相对基本的 SQL 语句来连接和分组一些表并使用聚合函数进行汇总。我会用 SQL 写成这样:

select
  p.LocationID,
  NumReadings = count(*),
  MinDate = min(t.[DateTime]),
  MaxDate = max(t.[DateTime])
from Station p inner join Data pd on p.LocationID = pd.ReadingLocationID
    inner join ApplicationDateTime t on t.ApplicationDateTimeID = pd.DateTimeID
group by p.LocationID

当我在 EF4 中使用下面的 Linq 语句时,它会创建一些可怕的 SQL(见最底部)。有没有更好的方法来做到这一点?显式地执行连接而不是使用 EF 导航属性会使情况变得更糟。

我不关心美学,但看看查询执行,执行格式不佳的 SQL 需要 3-4 倍的时间。

from s in Station
select new DataSummary
{
   ReadingLocationID = s.ReadingLocationID,
   StationIdentifier = s.StationIdentifier,
   NumReadings = s.Data.Count(),
   MinDateLoaded = s.Data.Min(d => d.ApplicationDateTime.DateTime),
   MaxDateLoaded = s.Data.Max(d => d.ApplicationDateTime.DateTime)
};

这里是 SQL(注意:这里还有一些额外的复杂性,例如表示为另一个连接的继承关系,但这只会导致另一个嵌套级别)。

SELECT 
  [Project3].[LocationTypeID] AS [LocationTypeID], 
  [Project3].[ReadingLocationID] AS [ReadingLocationID], 
  [Project3].[LocationIdentifier] AS [LocationIdentifier], 
  [Project3].[C1] AS [C1], 
  CAST( [Project3].[C2] AS datetime2) AS [C2], 
  CAST( [Project3].[C3] AS datetime2) AS [C3]
FROM ( SELECT 
        [Project2].[ReadingLocationID] AS [ReadingLocationID], 
        [Project2].[LocationTypeID] AS [LocationTypeID], 
        [Project2].[LocationIdentifier] AS [LocationIdentifier], 
        [Project2].[C1] AS [C1], 
        [Project2].[C2] AS [C2], 
        (SELECT 
           MAX([Extent7].[DateTime]) AS [A1]
           FROM  [dbo].[Data] AS [Extent6]
           INNER JOIN [dbo].[ApplicationDateTime] AS [Extent7] ON [Extent6].[DateTimeID] = [Extent7].[ApplicationDateTimeID]
           WHERE [Project2].[ReadingLocationID] = [Extent6].[ReadingLocationID]) AS [C3]
            FROM ( SELECT 
            [Project1].[ReadingLocationID] AS [ReadingLocationID], 
            [Project1].[LocationTypeID] AS [LocationTypeID], 
            [Project1].[LocationIdentifier] AS [LocationIdentifier], 
            [Project1].[C1] AS [C1], 
            (SELECT 
               MIN([Extent5].[DateTime]) AS [A1]
               FROM  [dbo].[Data] AS [Extent4]
               INNER JOIN [dbo].[ApplicationDateTime] AS [Extent5] ON [Extent4].[DateTimeID] = [Extent5].[ApplicationDateTimeID]
               WHERE [Project1].[ReadingLocationID] = [Extent4].[ReadingLocationID]) AS [C2]
               FROM ( SELECT 
                  [Extent1].[ReadingLocationID] AS [ReadingLocationID], 
                  [Extent1].[LocationTypeID] AS [LocationTypeID], 
                  [Extent1].[LocationIdentifier] AS [LocationIdentifier], 
                  (SELECT 
                        COUNT(1) AS [A1]
                        FROM [dbo].[Data] AS [Extent3]
                        WHERE [Extent1].[ReadingLocationID] = [Extent3].[ReadingLocationID]) AS [C1]
                   FROM  [dbo].[ReadingLocation] AS [Extent1]
                   INNER JOIN [dbo].[Station] AS [Extent2] ON [Extent1].[ReadingLocationID] = [Extent2].[LocationID]
                   WHERE ([Extent1].[LocationTypeID] =  CAST( '1' AS int)) AND ([Extent2].[LineID] = 'ACBB3FDF-116C-4E8E-AA80-B925E4922AC8')
                   )  AS [Project1]
               )  AS [Project2]
)

帮助!谢谢。

【问题讨论】:

  • 嗯,看起来确实过于复杂了。没有最小值和最大值会怎样?
  • 你在Linq2Sql中测试过查询吗?根据我的经验,它会生成更好的 SQL。
  • 没有最小值和最大值,它会删除两个“级别”。看起来它确实在为每个聚合函数做一个查询。也可能是我正在连接表中的字段上执行“max()”。我已经在项目中使用 EF,所以我不能真正切换。

标签: .net sql linq entity-framework-4


【解决方案1】:

使用不同的 ORM 怎么样?特别是像 PetaPocoMassive 这样的 MicroOrm 可以让您在 SQL 中编写查询并返回 .NET 对象。

两者都是 Nuget 包:PetaPocoMassive,因此您可以轻松安装它们。

如果您习惯编写 SQL 并且更愿意控制您的查询,那么它们可能是可行的候选者。

【讨论】:

  • 这里肯定有比更改他的ORM 更好的选择吗?没有尝试或任何事情,我只是认为像@JonSkeet 这样的 Linq 大师可以让这个查询变得简短。
  • 感谢您提供的信息——很高兴了解这些 ORM 方法。但是,我已经为这个项目绑定了 EF,所以我想我真的在问是否有替代 Linq 方法可以产生更直接的 SQL。
  • @jrowe88 可能有一种方法,但 EF 的一个权衡是您无法控制生成的 SQL,这就是为什么我建议使用不同的 ORM。我了解项目限制目前不允许您更改,但根据我的经验,我从来没有运气从 EF 调整 SQL。
  • @mattytommo 我确信有一些方法可以调整 LINQ,我正在解决控制 SQL 的能力。在这种情况下,更改 ORM 可能是最好的解决方案,因为 EF 不允许您对生成的 SQL 进行太多调整。
【解决方案2】:

LINQ 可以做很多事情,但它绝对不是魔法。 尝试像编写 SQL 一样编写 LINQ。定义您的联接。

这就是你的 LINQ 语句的样子:

Station
 .Join(Data, s => new { s.LocationID }, d => new { LocationID = d.ReadingLocationID }, (s,d) => new { s.ID, s.LocationID, d.DateTimeID })
 .Join(ApplicationDateTime, j1 => new { j1.DateTimeID }, t => new { DateTimeID = t.ApplicationDateTimeID }, (j1,t) => { j.ID, j.LocationID, t.DateTime })
 .Group(j2 => new { j2.ID, j2.LocationID })
 .Select(g => new DataSummary
   {
      StationIdentifier = g.Key.ID,
      ReadingLocationID = g.Key.LocationID,
      NumReadings = g.Count(),
      MinDateLoaded = g.Min(j2 => j2.DateTime),
      MaxDateLoaded = g.Max(j2 => j2.DateTime)
   });

注意:上面提供的代码未经测试!

请记住,在 LINQ 中创建连接时,您在连接中使用的匿名对象的属性应该具有相同的名称和类型。这花了我一些时间让我的第一次加入工作。 示例:假设 Data 表上的 DateTimeID 列可以为 NULL,而 ApplicationDateTime 上的 ApplicationDateTimeID 列不能为 NULL。然后,您应该将该连接更改为:

.Join(ApplicationDateTime, j1 => new { j1.DateTimeID }, t => new { DateTimeID = (int?)t.ApplicationDateTimeID }, (j1,t) => { j.ID, j.LocationID, t.DateTime })

【讨论】:

  • 谢谢。我会试试看。我确实认为通过不加入第三个表,它正在为每个最小和最大聚合编写单独的子查询,然后加入结果。
猜你喜欢
  • 2015-02-20
  • 2014-08-25
  • 1970-01-01
  • 1970-01-01
  • 2011-03-12
  • 1970-01-01
  • 2014-04-05
  • 1970-01-01
  • 2013-10-09
相关资源
最近更新 更多