【问题标题】:Using linq to get min and max of a particular field in one query使用 linq 在一个查询中获取特定字段的最小值和最大值
【发布时间】:2015-06-25 00:34:55
【问题描述】:

假设您有这样的课程:

public class Section {
   public DateTime  StartDate;
   public DateTime? EndDate;
}

我有这些对象的列表,我想获得最小开始日期和最大结束日期,但我想使用一个 linq 查询,所以我知道我只迭代列表一次。

例如,如果我在没有 linq 的情况下执行此操作,我的代码将看起来有点像这样(不检查空值):

DateTime? minStartDate;
DateTime? maxEndDate;
foreach(var s in sections) {
     if(s.StartDate < minStartDate) minStartDate = s.StartDate;
     if(s.EndDate > maxEndDate) maxEndDate = s.EndDate;
}

我可以有两个 linq 查询来获取最小值和最大值,但我知道在幕后,它需要对所有值进行两次迭代。

我以前见过这样的最小和最大查询,但需要分组。如果没有分组,并且在单个 linq 查询中,您将如何做到这一点?

【问题讨论】:

  • 可以,但是会慢一些,你还想要吗? :)
  • 您是否正在寻找一个 linq 查询,该查询将返回两个日期的集合 - 最小值和最大值 - 并且忽略空值?

标签: c# linq


【解决方案1】:

在没有分组的情况下,您将如何在单个 linq 查询中执行此操作?

如果我必须这样做,那么我会这样做:

var minMax = (from s0 in sections
  from s1 in sections
  orderby s0.StartDate, s1.EndDate descending
  select new {s0.StartDate, s1.EndDate}).FirstOrDefault();

但我也会根据相关提供商考虑性能影响。

在数据库上,我希望这会变成这样:

SELECT s0.StartDate, s1.EndDate
FROM Sections AS s0
CROSS JOIN Sections AS s1
ORDER BY created ASC, EndDate DESC 
LIMIT 1

SELECT TOP 1 s0.StartDate, s1.EndDate
FROM Sections AS s0, Sections AS s1
ORDER BY created ASC, EndDate DESC 

取决于数据库类型。反过来如何执行很可能是两个表扫描,但如果我要关心这些日期,我会在这些列上有索引,所以它应该是每个索引末尾的两个索引查找扫描,所以我预计它会很快。

我有这些对象的列表

如果我很在意性能,我就不会使用 Linq。

但我想使用一个 linq 查询,所以我知道我只迭代列表一次

这就是我不使用 linq 的原因。由于 linq 中没有任何东西旨在处理这种特殊情况,因此它会遇到更糟糕的组合。实际上它会比 2 次迭代更糟糕,它会是 N +1 次迭代,其中 N 是 Sections 中的元素数。 Linq 提供程序很好,但它们并不神奇。

如果我真的希望能够在 Linq 中执行此操作,例如我有时针对内存中的列表执行此操作,有时针对数据库等执行此操作,我会添加我自己的方法来尽可能以最好的方式执行每个操作:

public static Tuple<DateTime, DateTime?> MinStartMaxEnd(this IQueryable<Section> source)
{
  if(source == null)
    return null;
  var minMax = (from s0 in source
  from s1 in source
  orderby s0.StartDate, s1.EndDate descending
  select new {s0.StartDate, s1.EndDate}).FirstOrDefault();
  return minMax == null ? null : Tuple.Create(minMax.StartDate, minMax.EndDate);
}
public static Tuple<DateTime, DateTime?> MinStartMaxEnd(this IEnumerable<Section> source)
{
  if(source != null)
    using(var en = source.GetEnumerator())
      if(en.MoveNext())
      {
        var cur = en.Current;
        var start = cur.StartDate;
        var end = cur.EndDate;
        while(en.MoveNext())
        {
          cur = en.Current;
          if(cur.StartDate < start)
            start = cur.StartDate;
          if(cur.EndDate.HasValue && (!end.HasValue || cur.EndDate > end))
            end = cur.EndDate;
        }
        return Tuple.Create(start, end);
      }
  return null;
}

但我想使用一个 linq 查询,所以我知道我只迭代列表一次

回到这个话题。 Linq 不承诺对列表进行一次迭代。它有时可以这样做(或根本不迭代)。它可以调用数据库查询,进而将概念上的几次迭代转换为一两次(在 CTE 中很常见)。它可以生成对各种类似但不完全相同的查询非常有效的代码,而手工编码的替代方法是要么遭受大量浪费,要么编写大量类似但 -不太一样的方法。

但是,如果您假设 Linq 给您一次通过,它也可以隐藏一些 N+1 或 N*N 行为,看起来要少得多。如果您需要特定的单通行为,请添加到 Linq;它是可扩展的。

【讨论】:

  • SQL 中的查询不必有两个表引用。它只是: SELECT min(start_date), max(end_date) FROM Sections
【解决方案2】:

您可以使用MinMax

List<Section> test = new List<Section>();

minStartDate = test.Min(o => o.StartDate);
maxEndDate = test.Max(o => o.EndDate);

【讨论】:

  • 我明确表示我想在一个 linq 查询中完成。您的代码需要对数据进行两次迭代。
猜你喜欢
  • 1970-01-01
  • 2019-03-13
  • 2018-10-22
  • 1970-01-01
  • 1970-01-01
  • 2018-11-26
  • 1970-01-01
  • 2018-12-27
  • 1970-01-01
相关资源
最近更新 更多