【问题标题】:Linq 2 SQL query optimizingLinq 2 SQL 查询优化
【发布时间】:2014-12-16 22:46:18
【问题描述】:

我在使用 linq to sql 查询时遇到了真正的问题,这很可能是因为我的技能有点欠缺,但如果有人可以看看我如何将查询优化为 SQL,我将不胜感激正在生成的东西太可怕了

查询的目的是尝试获取加入销售详细信息表的位置的库存概览,以根据部门 ID(即一个部门的所有产品)因此 Max() 和 Sum()

var query1 = (
            from sl in slRepo
            where sl.Product.bsDepartmentId < 90 && sl.Product.SubDepartment.Department.Id == departmentId
            group sl by sl.Location into gbsl
            select new
            {
                ProductId = gbsl.FirstOrDefault().Product.Id,
                LocationId = gbsl.FirstOrDefault().Location.Id,
                Location = gbsl.FirstOrDefault().Location.Name,
                Quantity = gbsl.Sum(x => x.CurrentStock),
                LastInvoice = gbsl.Max(x => x.LastInvoice)
            });

var query2 = (
            from sd in sdRepo
            where sd.Product.SubDepartment.Department.Id == departmentId
            group sd by sd.Location.Id into g
            select new
            {
                LocationId = g.FirstOrDefault().Location.Id,
                LastSale = g.Max(x => x.TransactionDate)
            });

var query3 = (
            from q1 in query1
            join q2 in query2 on q1.LocationId equals q2.LocationId into temp
            from j in temp.DefaultIfEmpty()
            select new XStockOverviewDto
            {
                Location = q1.Location,
                Quantity = q1.Quantity,
                LastInvoice = q1.LastInvoice,
                LastSale = j.LastSale
            });

        return query3;

然后由此生成的 SQL 是... 这似乎是在使用相同的数据集分别查询 MAX 和 SUM 时应该一起完成,这使得查询变得非常复杂,并且实际查询需要很长时间

SELECT 
1 AS [C1], 
[Project10].[Name] AS [Name], 
[Project10].[C2] AS [C2], 
[Project10].[C3] AS [C3], 
 CAST( [Project16].[C2] AS datetime2) AS [C4]
FROM   (SELECT 
    [Project9].[Name] AS [Name], 
    [Project9].[C1] AS [C1], 
    [Project9].[C2] AS [C2], 
    (SELECT 
        MAX([Filter9].[LastInvoice]) AS [A1]
        FROM ( SELECT [Extent18].[LastInvoice] AS [LastInvoice], [Extent20].[Department_Id] AS [Department_Id], [Extent21].[Id] AS [Id1]
            FROM    [dbo].[XStockLevels] AS [Extent18]
            INNER JOIN [dbo].[XProducts] AS [Extent19] ON [Extent18].[Product_Id] = [Extent19].[Id]
            LEFT OUTER JOIN [dbo].[XSubDepartments] AS [Extent20] ON [Extent19].[SubDepartment_Id] = [Extent20].[Id]
            LEFT OUTER JOIN [dbo].[XLocations] AS [Extent21] ON [Extent18].[Location_Id] = [Extent21].[Id]
            WHERE [Extent19].[bsDepartmentId] < 90
        )  AS [Filter9]
        WHERE ([Filter9].[Department_Id] = @p__linq__0) AND (([Project9].[Id] = [Filter9].[Id1]) OR (([Project9].[Id] IS NULL) AND ([Filter9].[Id1] IS NULL)))) AS [C3]
    FROM ( SELECT 
        [Project8].[Id] AS [Id], 
        [Project8].[Name] AS [Name], 
        [Project8].[C1] AS [C1], 
        (SELECT 
            SUM([Filter7].[CurrentStock]) AS [A1]
            FROM ( SELECT [Extent14].[CurrentStock] AS [CurrentStock], [Extent16].[Department_Id] AS [Department_Id], [Extent17].[Id] AS [Id2]
                FROM    [dbo].[XStockLevels] AS [Extent14]
                INNER JOIN [dbo].[XProducts] AS [Extent15] ON [Extent14].[Product_Id] = [Extent15].[Id]
                LEFT OUTER JOIN [dbo].[XSubDepartments] AS [Extent16] ON [Extent15].[SubDepartment_Id] = [Extent16].[Id]
                LEFT OUTER JOIN [dbo].[XLocations] AS [Extent17] ON [Extent14].[Location_Id] = [Extent17].[Id]
                WHERE [Extent15].[bsDepartmentId] < 90
            )  AS [Filter7]
            WHERE ([Filter7].[Department_Id] = @p__linq__0) AND (([Project8].[Id] = [Filter7].[Id2]) OR (([Project8].[Id] IS NULL) AND ([Filter7].[Id2] IS NULL)))) AS [C2]
        FROM ( SELECT 
            [Project7].[Id] AS [Id], 
            [Extent13].[Name] AS [Name], 
            [Project7].[C1] AS [C1]
            FROM   (SELECT 
                [Project5].[Id] AS [Id], 
                [Project5].[C1] AS [C1], 
                (SELECT TOP (1) 
                    [Filter5].[Location_Id] AS [Location_Id]
                    FROM ( SELECT [Extent9].[Location_Id] AS [Location_Id], [Extent11].[Department_Id] AS [Department_Id], [Extent12].[Id] AS [Id3]
                        FROM    [dbo].[XStockLevels] AS [Extent9]
                        INNER JOIN [dbo].[XProducts] AS [Extent10] ON [Extent9].[Product_Id] = [Extent10].[Id]
                        LEFT OUTER JOIN [dbo].[XSubDepartments] AS [Extent11] ON [Extent10].[SubDepartment_Id] = [Extent11].[Id]
                        LEFT OUTER JOIN [dbo].[XLocations] AS [Extent12] ON [Extent9].[Location_Id] = [Extent12].[Id]
                        WHERE [Extent10].[bsDepartmentId] < 90
                    )  AS [Filter5]
                    WHERE ([Filter5].[Department_Id] = @p__linq__0) AND (([Project5].[Id] = [Filter5].[Id3]) OR (([Project5].[Id] IS NULL) AND ([Filter5].[Id3] IS NULL)))) AS [C2]
                FROM ( SELECT 
                    [Project4].[Id] AS [Id], 
                    [Project4].[C1] AS [C1]
                    FROM ( SELECT 
                        [Project2].[Id] AS [Id], 
                        (SELECT TOP (1) 
                            [Filter3].[Location_Id] AS [Location_Id]
                            FROM ( SELECT [Extent5].[Location_Id] AS [Location_Id], [Extent7].[Department_Id] AS [Department_Id], [Extent8].[Id] AS [Id4]
                                FROM    [dbo].[XStockLevels] AS [Extent5]
                                INNER JOIN [dbo].[XProducts] AS [Extent6] ON [Extent5].[Product_Id] = [Extent6].[Id]
                                LEFT OUTER JOIN [dbo].[XSubDepartments] AS [Extent7] ON [Extent6].[SubDepartment_Id] = [Extent7].[Id]
                                LEFT OUTER JOIN [dbo].[XLocations] AS [Extent8] ON [Extent5].[Location_Id] = [Extent8].[Id]
                                WHERE [Extent6].[bsDepartmentId] < 90
                            )  AS [Filter3]
                            WHERE ([Filter3].[Department_Id] = @p__linq__0) AND (([Project2].[Id] = [Filter3].[Id4]) OR (([Project2].[Id] IS NULL) AND ([Filter3].[Id4] IS NULL)))) AS [C1]
                        FROM ( SELECT 
                            [Distinct1].[Id] AS [Id]
                            FROM ( SELECT DISTINCT 
                                [Filter1].[Id5] AS [Id], 
                                [Filter1].[bsLocationId] AS [bsLocationId], 
                                [Filter1].[Name] AS [Name], 
                                [Filter1].[Code1] AS [Code1], 
                                [Filter1].[Code2] AS [Code2], 
                                [Filter1].[Code3] AS [Code3], 
                                [Filter1].[Code4] AS [Code4], 
                                [Filter1].[Code5] AS [Code5], 
                                [Filter1].[Code6] AS [Code6], 
                                [Filter1].[Code7] AS [Code7], 
                                [Filter1].[Code8] AS [Code8], 
                                [Filter1].[bsCompanyId] AS [bsCompanyId], 
                                [Filter1].[Group] AS [Group]
                                FROM ( SELECT [Extent3].[Department_Id] AS [Department_Id], [Extent4].[Id] AS [Id5], [Extent4].[bsLocationId] AS [bsLocationId], [Extent4].[Name] AS [Name], [Extent4].[Code1] AS [Code1], [Extent4].[Code2] AS [Code2], [Extent4].[Code3] AS [Code3], [Extent4].[Code4] AS [Code4], [Extent4].[Code5] AS [Code5], [Extent4].[Code6] AS [Code6], [Extent4].[Code7] AS [Code7], [Extent4].[Code8] AS [Code8], [Extent4].[bsCompanyId] AS [bsCompanyId], [Extent4].[Group] AS [Group]
                                    FROM    [dbo].[XStockLevels] AS [Extent1]
                                    INNER JOIN [dbo].[XProducts] AS [Extent2] ON [Extent1].[Product_Id] = [Extent2].[Id]
                                    LEFT OUTER JOIN [dbo].[XSubDepartments] AS [Extent3] ON [Extent2].[SubDepartment_Id] = [Extent3].[Id]
                                    LEFT OUTER JOIN [dbo].[XLocations] AS [Extent4] ON [Extent1].[Location_Id] = [Extent4].[Id]
                                    WHERE [Extent2].[bsDepartmentId] < 90
                                )  AS [Filter1]
                                WHERE [Filter1].[Department_Id] = @p__linq__0
                            )  AS [Distinct1]
                        )  AS [Project2]
                    )  AS [Project4]
                )  AS [Project5] ) AS [Project7]
            LEFT OUTER JOIN [dbo].[XLocations] AS [Extent13] ON [Project7].[C2] = [Extent13].[Id]
        )  AS [Project8]
    )  AS [Project9] ) AS [Project10]
LEFT OUTER JOIN  (SELECT 
    [Project15].[C1] AS [C1], 
    (SELECT 
        MAX([Extent28].[TransactionDate]) AS [A1]
        FROM   [dbo].[XSalesDetails] AS [Extent28]
        LEFT OUTER JOIN [dbo].[XProducts] AS [Extent29] ON [Extent28].[Product_Id] = [Extent29].[Id]
        INNER JOIN [dbo].[XSubDepartments] AS [Extent30] ON [Extent29].[SubDepartment_Id] = [Extent30].[Id]
        WHERE ([Extent30].[Department_Id] = @p__linq__1) AND (([Project15].[Location_Id] = [Extent28].[Location_Id]) OR (([Project15].[Location_Id] IS NULL) AND ([Extent28].[Location_Id] IS NULL)))) AS [C2]
    FROM ( SELECT 
        [Project14].[Location_Id] AS [Location_Id], 
        [Project14].[C1] AS [C1]
        FROM ( SELECT 
            [Project12].[Location_Id] AS [Location_Id], 
            (SELECT TOP (1) 
                [Extent25].[Location_Id] AS [Location_Id]
                FROM   [dbo].[XSalesDetails] AS [Extent25]
                LEFT OUTER JOIN [dbo].[XProducts] AS [Extent26] ON [Extent25].[Product_Id] = [Extent26].[Id]
                INNER JOIN [dbo].[XSubDepartments] AS [Extent27] ON [Extent26].[SubDepartment_Id] = [Extent27].[Id]
                WHERE ([Extent27].[Department_Id] = @p__linq__1) AND (([Project12].[Location_Id] = [Extent25].[Location_Id]) OR (([Project12].[Location_Id] IS NULL) AND ([Extent25].[Location_Id] IS NULL)))) AS [C1]
            FROM ( SELECT 
                @p__linq__1 AS [p__linq__1], 
                [Distinct2].[Location_Id] AS [Location_Id]
                FROM ( SELECT DISTINCT 
                    [Extent22].[Location_Id] AS [Location_Id]
                    FROM   [dbo].[XSalesDetails] AS [Extent22]
                    LEFT OUTER JOIN [dbo].[XProducts] AS [Extent23] ON [Extent22].[Product_Id] = [Extent23].[Id]
                    INNER JOIN [dbo].[XSubDepartments] AS [Extent24] ON [Extent23].[SubDepartment_Id] = [Extent24].[Id]
                    WHERE [Extent24].[Department_Id] = @p__linq__1
                )  AS [Distinct2]
            )  AS [Project12]
        )  AS [Project14]
    )  AS [Project15] ) AS [Project16] ON ([Project10].[C1] = [Project16].[C1]) OR (([Project10].[C1] IS NULL) AND ([Project16].[C1] IS NULL))

我实际上试图转换为链接的查询是这样的

DECLARE @DeptId INT = 1;

SELECT Location_Id, XLocations.Name, CurrentStock, LastInvoice, LastSale
FROM
(SELECT Location_Id, SUM(CurrentStock) AS CurrentStock, MAX(LastInvoice) AS LastInvoice, MAX(LastSale) AS LastSale
   FROM XStockLevels
LEFT JOIN 
(SELECT XProducts.bsDepartmentId, XProducts.bsSubDepartmentId, XProducts.bsItemId, XProducts.Id, LastSale
   FROM XProducts 
  INNER JOIN XSubDepartments ON XProducts.SubDepartment_Id = XSubDepartments.Id
   LEFT JOIN (SELECT Product_Id, MAX(TransactionDate) AS LastSale FROM XSalesDetails GROUP BY Product_Id) XSD ON XSD.Product_Id = XProducts.Id
  WHERE XProducts.bsDepartmentId<90 AND XSubDepartments.Department_Id = @DeptId) XP
ON XStockLevels.Product_Id = XP.Id
GROUP BY Location_Id) Z 
INNER JOIN XLocations ON Z.Location_Id = XLocations.Id

所以我认为需要进行一些真正的优化,我似乎真的无法弄清楚我在我的 linq 查询中做错了什么。

干杯 乔。

【问题讨论】:

  • 如果你已经有了 SQL,为什么还要把它翻译成 linq。只需针对 db 上下文执行 SQL 语句。
  • 因为我想要一个纯 linq 解决方案,并且没有任何 SQL。

标签: c# linq linq-to-sql


【解决方案1】:

是的,当您的查询变得复杂时,linq to sql 和其他类似的生成器往往会失败。

您的选择是绕过 linq 手动执行查询,或者在数据库上创建一个函数并执行该表单 linq。

【讨论】:

    【解决方案2】:

    SQL 服务器查询优化器通常会对其进行排序。

    首先通过将“生成”的 SQL 放入 SQL 管理工作室并询问查询计划来查看生成的查询计划。如果您不习惯查询计划,则需要一些了解,但值得学习该技能。

    如果你仍然发现生成的 SQL 有问题,请检查你是否有正确的索引等。

    如果一切都失败了,你需要创建一个存储过程。或者对数据库执行多个 Ling to SQL 查询并在 C# 中组合结果。

    【讨论】:

      【解决方案3】:

      首先要改进的是替换所有表达式,例如...

      gbsl.FirstOrDefault().Location.Id
      

      通过...

      gbsl.Key.Id
      

      因为同样的效果,但是FirstOrDefault()被翻译成非常昂贵的SQL。

      此外,在第一个 LINQ 查询中,您应该从投影中删除 ProductIdLocation 属性,因为它们不会在任何地方使用。

      我很确定,如果你做这些事情,你会更接近你最终实现的查询,并且可读性更好。

      有点离题(因为它在这里还没有发挥作用,但经过细微的更改后它可能会)是 LINQ-to-SQL 在分组方面可能非常效率低下。如果你做错了(见下文),它可能会执行GROUP BY 查询,然后执行每个组的查询以填充组。

      这是因为在 SQL 中,group by 是“破坏性的”:它不会从查询的表中返回完整的记录。如果你这样做了

      SELECT a, SUM(b)
      FROM   tableA
      GROUP BY a
      

      您将获得一个包含两个值的结果集,而不是tableA 中的其他值。但是,在 LINQ 中,您通常不仅对聚合感兴趣,而且您可能只想按某些属性对完整实体进行分组,例如以下简单示例:

      from a in As
      group a by a.Type into g
      select new { Type = g.Key, As = g }
      

      这将首先执行一个GROUP BY a.Type 查询,然后执行尽可能多的查询以获取每个组的实体。对于这些类型的分组,最好先获取数据(一个查询)然后在内存中进行分组。

      (这是 Entity Framework 击败 LINQ-to-SQL wrt 查询生成的罕见领域之一)

      【讨论】:

      • 感谢您对分组的深入了解,这更加相关,并且很有意义。我实际上没有注意到该组的 Key 上的 firstOrDefault() 。看来,这些 linq 的东西所花费的时间几乎与 SQL 花费我学习的时间一样多:S
      【解决方案4】:

      前两个答案实际上并没有回答我的问题...但感谢您的回复

      这就是我最终要做的。

      获取 LINQPad

      获取 Linqer 试用版(我认为完整版大约需要 30 英镑)

      在 Linqer 中反编译 SQL 查询,然后查看代码(它真的很乱,需要大量清理,但它给了我可以工作的地方)- 将代码复制到 LINQPad 并开始调整代码以查看更好地检查 SQL 和编译器。

      现在我的代码如下所示:

          var query5 = (
              from sl in (
                  (from xsl in slRepo
                      where
                          xsl.Product.bsDepartmentId < 90 &&
                          xsl.Product.SubDepartment.Department.Id == 1
                      group xsl by new
                      {
                          xsl.Location.Id
                      }
                      into g
                      select new
                      {
                          g.Key,
                          LocationId = g.Key.Id,
                          CurrentStock = (int?) g.Sum(p => p.CurrentStock),
                          LastInvoice = g.Max(p => p.LastInvoice)
                      }))
              join ls in (
                  (from xsd in sdRepo
                      join xp in prodRepo on xsd.Product.Id equals xp.Id into xpJoin
                      from xp in xpJoin.DefaultIfEmpty()
                      join xs in subDepRepo on xp.SubDepartment.Id equals xs.Id into xsJoin
                      from xs in xsJoin.DefaultIfEmpty()
                      where
                          xp.bsDepartmentId < 90 &&
                          xs.Department.Id == 1
                      group xsd by new
                      {
                          xsd.Location.Id
                      }
                      into g
                      select new
                      {
                          g.Key,
                          LastSale = (DateTime?)g.Max(p => p.TransactionDate)
                      })) on sl.Key equals ls.Key into lsJoin
              from ls in lsJoin.DefaultIfEmpty()
              join l in locRepo on sl.LocationId equals l.Id
              select new XStockOverviewDto
              {
                  Location = l.Name,
                  Quantity = sl.CurrentStock,
                  LastInvoice = sl.LastInvoice,
                  LastSale = ls.LastSale
              });
      

      不要误会我的意思,它几乎没有可读性,但我确信这可以进一步分解并清理以提高可读性

      但 SQL 现在看起来像这样,运行速度比它注册时间要快

      SELECT 
          1 AS [C1], 
          [Extent7].[Name] AS [Name], 
          [GroupBy1].[A1] AS [C2], 
          [GroupBy1].[A2] AS [C3], 
          CASE WHEN ([Project1].[C2] IS NULL) THEN CAST(NULL AS datetime2) ELSE  CAST( [Project1].[C1] AS datetime2) END AS [C4]
          FROM    (SELECT 
              [Filter1].[Location_Id] AS [K1], 
              SUM([Filter1].[CurrentStock]) AS [A1], 
              MAX([Filter1].[LastInvoice]) AS [A2]
              FROM ( SELECT [Extent1].[CurrentStock] AS [CurrentStock], [Extent1].[LastInvoice] AS [LastInvoice], [Extent1].[Location_Id] AS [Location_Id], [Extent3].[Department_Id] AS [Department_Id]
                  FROM   [dbo].[XStockLevels] AS [Extent1]
                  INNER JOIN [dbo].[XProducts] AS [Extent2] ON [Extent1].[Product_Id] = [Extent2].[Id]
                  INNER JOIN [dbo].[XSubDepartments] AS [Extent3] ON [Extent2].[SubDepartment_Id] = [Extent3].[Id]
                  WHERE [Extent2].[bsDepartmentId] < 90
              )  AS [Filter1]
              WHERE 1 = [Filter1].[Department_Id]
              GROUP BY [Filter1].[Location_Id] ) AS [GroupBy1]
          LEFT OUTER JOIN  (SELECT 
              [GroupBy2].[K1] AS [Location_Id], 
              [GroupBy2].[A1] AS [C1], 
              1 AS [C2]
              FROM ( SELECT 
                  [Filter3].[Location_Id] AS [K1], 
                  MAX([Filter3].[TransactionDate]) AS [A1]
                  FROM ( SELECT [Extent4].[TransactionDate] AS [TransactionDate], [Extent4].[Location_Id] AS [Location_Id], [Extent6].[Department_Id] AS [Department_Id]
                      FROM   [dbo].[XSalesDetails] AS [Extent4]
                      INNER JOIN [dbo].[XProducts] AS [Extent5] ON [Extent4].[Product_Id] = [Extent5].[Id]
                      INNER JOIN [dbo].[XSubDepartments] AS [Extent6] ON [Extent5].[SubDepartment_Id] = [Extent6].[Id]
                      WHERE [Extent5].[bsDepartmentId] < 90
                  )  AS [Filter3]
                  WHERE 1 = [Filter3].[Department_Id]
                  GROUP BY [Filter3].[Location_Id]
              )  AS [GroupBy2] ) AS [Project1] ON ([GroupBy1].[K1] = [Project1].[Location_Id]) OR (([GroupBy1].[K1] IS NULL) AND ([Project1].[Location_Id] IS NULL))
          INNER JOIN [dbo].[XLocations] AS [Extent7] ON [GroupBy1].[K1] = [Extent7].[Id]
      

      谢谢 乔。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-07-13
        • 1970-01-01
        • 2019-04-15
        相关资源
        最近更新 更多