【问题标题】:A more refined version of this LINQ to SQL query此 LINQ to SQL 查询的更完善的版本
【发布时间】:2017-03-02 16:45:15
【问题描述】:

我的难题是尝试将以下 T-SQL 查询转换为几乎等效的(性能方面)LINQ to SQL 查询:

SELECT 
    j1.JOB,   
    max(CASE WHEN ISNULL(logs.statcategory, ' ') = 'PREP' THEN 'X' ELSE ' ' END) AS prep,  
    max(CASE WHEN ISNULL(logs.statcategory, ' ') = 'PRINT' THEN 'X' ELSE ' ' END) AS press,  
    max(CASE WHEN ISNULL(logs.statcategory, ' ') = 'BIND' THEN 'X' ELSE ' ' END) AS bind, 
    max(CASE WHEN ISNULL(logs.statcategory, ' ') = 'SHIP' THEN 'X' ELSE ' ' END) AS ship  
from 
    job j1  
left outer join 
( 
    select 
        j.job,
        l.statcategory,
        cnt=count(*)  
    from 
        job j  
    join 
        jobloc jl 
            join location l 
            on 
                l.code = jl.location and 
                l.site = jl.site 
        on j.job = jl.job  
    WHERE
        j.stat = 'O'  
    group by 
        j.job,l.statcategory  
) logs 
on 
    j1.job = logs.job  
WHERE
    j1.stat = 'O'  
group by
    j1.job

此查询目前在 MS SQL Server 上运行不到 0.2 秒。以下 LINQ 查询是我想出的,它返回完全相同的记录,但运行速度慢了近 30 倍:

from a0 in Jobs
join a1 in 
(
    from a0 in Jobs
    join a1 in JobLocs on a0.Content equals a1.Job
    join a2 in Locations on new {Code = a1.Location, a1.Site} equals new {a2.Code, a2.Site}
    where a0.Stat == 'O'    
    select new {a0.Content, a2.StatCategory}
) on a0.Content equals a1.Content into a1
from a2 in a1.DefaultIfEmpty()
where a0.Stat == 'O'
group a2 by a0.Content into a0
orderby a0.Key
select new 
{
    Job = a0.Key,
    Prep = (bool?)a0.Max(a1 => a1.StatCategory == "PREP" ? true : false),
    Print = (bool?)a0.Max(a1 => a1.StatCategory == "PRINT" ? true : false),
    BIND = (bool?)a0.Max(a1 => a1.StatCategory == "BIND" ? true : false),
    SHIP = (bool?)a0.Max(a1 => a1.StatCategory == "SHIP" ? true : false),
}

这是从 LINQ 查询生成的 SQL(使用 LINQPad):

-- Region Parameters
DECLARE @p0 Int = 79
DECLARE @p1 Int = 79
DECLARE @p2 VarChar(1000) = 'PREP'
DECLARE @p3 VarChar(1000) = 'PRINT'
DECLARE @p4 VarChar(1000) = 'BIND'
DECLARE @p5 VarChar(1000) = 'SHIP'
-- EndRegion
SELECT [t4].[Job], [t4].[value] AS [Prep], [t4].[value2] AS [Print], [t4].[value3] AS [BIND], [t4].[value4] AS [SHIP]
FROM (
    SELECT MAX(
        (CASE 
            WHEN [t3].[StatCategory] = @p2 THEN 1
            WHEN NOT ([t3].[StatCategory] = @p2) THEN 0
            ELSE NULL
         END)) AS [value], MAX(
        (CASE 
            WHEN [t3].[StatCategory] = @p3 THEN 1
            WHEN NOT ([t3].[StatCategory] = @p3) THEN 0
            ELSE NULL
         END)) AS [value2], MAX(
        (CASE 
            WHEN [t3].[StatCategory] = @p4 THEN 1
            WHEN NOT ([t3].[StatCategory] = @p4) THEN 0
            ELSE NULL
         END)) AS [value3], MAX(
        (CASE 
            WHEN [t3].[StatCategory] = @p5 THEN 1
            WHEN NOT ([t3].[StatCategory] = @p5) THEN 0
            ELSE NULL
         END)) AS [value4], [t0].[Job]
    FROM [Job] AS [t0]
    LEFT OUTER JOIN ([Job] AS [t1]
        INNER JOIN [JobLoc] AS [t2] ON [t1].[Job] = [t2].[Job]
        INNER JOIN [Location] AS [t3] ON ([t2].[Location] = [t3].[Code]) AND ([t2].[Site] = [t3].[Site])) ON ([t0].[Job] = [t1].[Job]) AND (UNICODE([t1].[Stat]) = @p0)
    WHERE UNICODE([t0].[Stat]) = @p1
    GROUP BY [t0].[Job]
    ) AS [t4]
ORDER BY [t4].[Job]

突出的一点是,从 LINQ 查询生成的 SQL 为子查询中返回的每一列运行聚合,而在原始 SQL 中,它是外部 SELECT 的一部分。我可以想象部分性能下降是存在的。

我(暂时)愿意接受没有更好的方法来编写此代码,只需使用 LINQ API 中的DataContext.ExecuteQuery() 方法(直接运行和调整第一条 SQL 语句)。但是,我试图在我目前正在处理的项目中尽可能不包含嵌入式 SQL,所以如果它可以接近原始查询的性能,那将是理想的。我已经研究了一段时间(部分是作为学术练习,也是为了实际使用这个或类似的查询),这是我想出的最好的(我没有写原始查询顺便说一句——它是一个旧项目的一部分,正在迁移到一个新项目)。

感谢您的帮助。

【问题讨论】:

  • 你能显示两者的执行计划吗?
  • 而你的类的定义也是如此......
  • stat 上有索引吗?我怀疑这是 UNICODE 转换。
  • 是的--Stat 它实际上是一些索引的一部分。我明白你所说的关于 Unicode 转换的内容——如果服务器必须这样做,它就不能使用索引。如果我将Job 类中Stat 属性的SQL 数据类型设置为nchar,这可能会有所帮助(目前定义为char)。
  • 我刚刚将生成的查询更改为不使用 Unicode 转换,它似乎已经解决了这个问题。我检查了执行计划,有转换的那个给出了关于转换的警告。第二个(无转换)没有。问题LINQ to SQL will not generate sargable query 和随后的回复也适用于这个问题。

标签: c# sql-server linq


【解决方案1】:

根据我们在 cmets 中的讨论,

问题是 linq-to-entities 由于某种未知原因而添加的 UNICODE 转换。 由于(不必要的)转换,数据库无法使用索引。

您可以使用.Equals 代替==,它不会使用UNICODE 或在db 中将类型更改为varchar(1)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多