【问题标题】:SQL "IN" statement in linq query mistake, how resolve?linq查询中的SQL“IN”语句错误,如何解决?
【发布时间】:2018-07-18 19:21:40
【问题描述】:

我在 SQL 中有这个查询:

SELECT * 
FROM TableName
WHERE myData IN (SELECT MAX(myData) AS DATA_MAX  
                 FROM TableName 
                 GROUP BY id1, id2) 

我想在 Linq (c#) 中复制它 - 我该怎么做?

【问题讨论】:

  • 为什么要将其转换为 LINQ?我从不理解这种痴迷;您可以使用许多出色的工具非常轻松地从 .NET 代码中执行准确的 SQL,这将比解析 LINQ 表达式树效率高得多,并且需要零努力来实施。你有理由需要它作为 LINQ 吗?您对非 LINQ 解决方案感兴趣吗?另外:问题标题中提到的“错误”是什么?
  • @MarcGravell 是非常正确的。 Linq 是一个很棒的东西,但我们不需要将它硬塞到所有东西中,它并不总是最有效的方式。
  • 这个查询 100000% 不适合 LINQ - 它与对象或映射无关。这是一个报告查询。查询本身需要优化——你可以使用MAX() OVER(PARTITION BY id1,id2) 吗?执行计划会更好吗?额外的索引呢?创建一个 View 并映射到它,这样您就可以优化查询而无需重新编译和重新部署
  • 我该怎么做 - 首先选择一个支持 LINQ 的 ORM。

标签: c# sql linq sql-to-linq-conversion


【解决方案1】:

这并不是一个真正的直接答案,因为它不是通过 LINQ 实现的;但它确实解决了问题,并且用最少的麻烦:

您可以使用"Dapper" 之类的工具来执行原始查询,而无需涉及任何 LINQ。如果您使用的是 LINQ-to-SQL 或实体框架之类的东西,那里的数据上下文通常有一个您可以使用的原始查询 API,但我将展示一个“Dapper " 实施:

class SomeType
{
    // not shown: properties that look like the columns
    // of [TableName] in the database - correct names/types
}
...
var data = connection.Query<SomeType>(@"
SELECT * FROM TableName
WHERE myData IN (Select max(myData) as DATA_MAX  from TableName group
by id1, id2)").AsList();

这种方法使得迁移现有 SQL 查询变得非常容易不必将所有内容重写为 LINQ。

如果您使用的是 LINQ-to-SQL,DataContext 有一个类似的 ExecuteQuery&lt;TResult&gt; 方法。 Entity Framework 有一个SqlQuery 方法

【讨论】:

  • 更好的是 - 使用该查询创建视图或函数。像这样的报告查询需要相当多的优化。可以用MAX OVER(partition by id1,id2) 重写吗?那会避免双表扫描吗?不必重新部署应用程序即可应用优化
  • @PanagiotisKanavos 我同意你对优化的看法;我不同意你的结论,即观点是做到这一点的正确方法——这完全取决于上下文;在许多情况下,将查询编码到应用程序中可能更合适 - 特别是如果它是基于服务器的并且可以通过单击部署 - 并且可以减少部署开销同时增加 可测试性。 也有种场景,其中视图是部署它以最小化部署开销的正确方式。这类问题没有“单一解决方案”。
  • 这不是一个结论。如果查询在视图中,通常更容易测试查询。例如,可以重写此查询以在每行添加 ROW_NUMBER 或 RANK,并仅返回 RN=1 的那些。硬编码的查询字符串需要修改和部署与查询本身无关的内容
  • @PanagiotisKanavos 我真的不关心查询的位置;在任何一种配置中开发和测试它都会给我带来完全零挑战。但是通过不是视图,我可以执行以下操作:部署到集群中的 1 台服务器并进行并行实时冒烟测试。这也意味着我可以更改查询的 shape - 可能是多个结果集而不是单个“宽”结果集;你不能用视图或存储过程来做到这一点,因为它会破坏消费者 - 但是如果查询是消费者的part,那么迭代和部署这种重构的更改是微不足道的。跨度>
  • 不同的场景,不同的解决方案。我可以说相反。现在我写了很多 ETL 和报告代码。对于 ETL,视图提供了一个抽象层,将使用者与查询更改或表重构隔离开来。只要字段相同,消费者不知道区别。这使得重构、冒烟测试、比较选项之间的执行计划变得更容易。我不必重新部署应用程序/ETL 脚本来修复查询。
【解决方案2】:

长话短说 - 不要使用 LINQ,优化查询并使用像 Dapper 这样的 microORM 将结果映射到类:

var query = "Select * "
            "from ( select *, " +
            "       ROW_NUMBER() OVER (partition by id1,id2 order by mydata desc) AS RN " +
            "       From TableName ) T " +
            "where RN=1";
var data = connection.Query<SomeType>(query);

LINQ 不能替代 SQL。通常,ORM 并不打算编写像这样的报告查询。

报告查询需要大量优化,并且通常必须在生产中进行更改。您希望每次查询更改时都必须重新部署您的应用程序。在这种情况下,最好使用像 Dapper 这样的 microOMR 创建一个视图并映射到它。

此特定查询可能需要 两次 表扫描,一次用于计算每个 id1,id2 的最大值,一次用于查找与 mydata 匹配的行。中间数据也必须假脱机到 tempdb 中。如果mydata 被索引覆盖,它可能不会是一个昂贵的查询。如果不是,所有数据将被扫描两次。

另一种方法是根据 id1、id2 通过mydata 计算每一行的排名。您可以使用 ROW_NUMBER、RANK、NTILE 等排名函数之一来完成此操作。

Select * 
from ( select *,
              ROW_NUMBER() OVER (partition by id1,id2 order by mydata desc) AS RN
       From TableName) T 
where RN=1

您可以直接将该查询与 Dapper 一起使用,或者创建一个视图并将您的实体映射到视图,而不是表本身。

一种选择是创建一个MyTableRanked 视图:

CREATE VIEW MyTableRanked AS
select *,
       ROW_NUMBER() OVER (partition by id1,id2 order by mydata desc) AS RN
From TableName

这将允许您编写:

var query="Select * from MyTableRanked where RN=@rank";
var data = connection.Query<SomeType>(query,new {rank=2});

允许您返回每个 ID1、ID2 组合的前 N ​​条记录

【讨论】:

    【解决方案3】:

    你可以试试这个。也许它会起作用。

    var myData = (from c in _context.TableName
        group c by new
        {
            c.id1,
            c.id2
        } into gcs
        select new
        {
           gcs.Max(p=>p.myData)
        }).AsQueryable();
    
    var result = (from t in _context.TableName
                  where myData.Contains(t.myData)
                  select t).ToList();
    

    【讨论】:

    • 我个人认为第一个查询上的 ToList() 是一个错误 - 它强制将其分成两个查询而不是一个组合查询,据我们所知,第一个查询的数据量可能很重要。您可能可以简单地删除ToList() 并通过IQueryable&lt;T&gt; 使其全部工作。另外:每次添加答案时,您都不需要添加“检查我的答案”(现在已删除,而不是由我删除):OP 会自动收到答案通知。
    • @MarcGravell IQueryable 是不错的选择
    • 你不需要添加 AsQueryable();如果_context.Table 一个查询(IQueryable&lt;T&gt;),那么结果已经是一个查询(对于不同的T)。如果它不是一个查询:添加AsQueryable() 不会成为一个。
    • @jigneshpatel 它不是 - 查询已经是可查询的。但是,无论生成什么 SQL 查询,它都不会像所要求的那样。可能会更糟
    • @PanagiotisKanavos(分享了一个会意的点头)-哦,我从 LINQ 查询和 SQL 生成中看到的恐怖 :) 不要误会我的意思:如果你不太擅长 SQL , LINQ 可能会比你更擅长 SQL - 但对于任何具有良好 SQL 技能的人来说......至少说得更好:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-18
    • 2010-10-28
    相关资源
    最近更新 更多