【问题标题】:Select last value in a month for all given IDs using LINQ使用 LINQ 为所有给定 ID 选择一个月中的最后一个值
【发布时间】:2010-10-21 23:39:16
【问题描述】:

这个问题是我another question的第二部分,但这次重点是LINQ-to-SQL。

我有 2 个表,一个包含仪表 ID,另一个包含第一个表中某些仪表的测量值。这是表结构:

MeterConfig

  • MeterID(整数)
  • MeterNumber (char[16])
  • 类型 (char[25])

读数

  • MeterID(整数)
  • 日期(日期时间)
  • 值(数字(18,6))

我需要获取每个仪表在给定期间的最后读数(及其日期),以及仪表编号。最后一次读取仪表的日期可能会有所不同。在此期间,某些仪表可能没有任何读数;在这种情况下,它们必须被忽略。

我设法做到了,但我对自己的做法并不十分满意。有没有更有效的方法,无需两次执行几乎相同的子查询?

(from cfg in MeterConfigs
    join r in Readings on cfg.MeterID equals r.MeterID
where
    r.Date >= startDate && r.Date <= endDate
    select new
    {
        Number = cfg.MeterNumber,
        ReadingDate = 
        (
            from r in Readings
            where cfg.MeterID == r.MeterID && r.Date >= startDate && r.Date <= endDate
            orderby r.Date descending
            select r.Date
        ).Take(1),
        Value = 
        (
            from r in Readings
            where cfg.MeterID == r.MeterID && r.Date >= startDate && r.Date <= endDate
            orderby r.Date descending
            select r.Value
        ).Take(1)   
})
.GroupBy(x => x.Number)
.Select(x => x.First());

【问题讨论】:

    标签: linq linq-to-sql


    【解决方案1】:

    这是一种可能的解决方案:

    MeterConfigs
        .Join(
            Readings.Where(x => x.Date >= startDate && x.Date <= endDate),
            x => x.MeterID,
            x => x.MeterID,
            (o,i) => new { MeterNumber = o.MeterNumber, Date = i.Date, Value = i.Value }
        ).GroupBy(x => x.MeterNumber)
        .Select(x => {
            var lastReading = x.OrderByDescending(y => y.Date).FirstOrDefault();
            return new {
                    Number = x.Key,
                    ReadingDate = lastReading.Date,
                    Value = lastReading.Value
                };
            }
        )
    

    如果您安装了 linqpad,您可以使用我创建的一些虚拟数据将此查询的结果与您当前的解决方案进行比较:

    var MeterConfigs = new [] {
    new { MeterID = 1, MeterNumber = "123", Type = "foo" },
    new { MeterID = 2, MeterNumber = "456", Type = "bar" },
    new { MeterID = 3, MeterNumber = "789", Type = "foo" },
    new { MeterID = 4, MeterNumber = "101", Type = "bar" },
    };
    
    var Readings = new [] {
    new { MeterID = 1, Date = new DateTime(2010, 10, 21), Value = 12.3 },
    new { MeterID = 1, Date = new DateTime(2010, 10, 20), Value = 4.3 },
    new { MeterID = 1, Date = new DateTime(2010, 10, 19), Value = 56.2 },
    new { MeterID = 1, Date = new DateTime(2010, 10, 5), Value = 1.4 },
    new { MeterID = 2, Date = new DateTime(2010, 10, 20), Value = 8.2 },
    new { MeterID = 3, Date = new DateTime(2010, 10, 21), Value = 34.7 },
    new { MeterID = 3, Date = new DateTime(2010, 10, 20), Value = 2.9 },
    };
    
    var startDate = new DateTime(2010, 10, 1);
    var endDate = new DateTime(2010, 10, 21);
    
    MeterConfigs
        .Join(
            Readings.Where(x => x.Date >= startDate && x.Date <= endDate),
            x => x.MeterID,
            x => x.MeterID,
            (o,i) => new { MeterNumber = o.MeterNumber, Date = i.Date, Value = i.Value }
        ).GroupBy(x => x.MeterNumber)
        .Select(x => {
            var lastReading = x.OrderByDescending(y => y.Date).FirstOrDefault();
            return new {
                    Number = x.Key,
                    ReadingDate = lastReading.Date,
                    Value = lastReading.Value
                };
            }
        ).Dump();
    
    (from cfg in MeterConfigs
        join r in Readings on cfg.MeterID equals r.MeterID
    where
        r.Date >= startDate && r.Date <= endDate
        select new
        {
            Number = cfg.MeterNumber,
            ReadingDate = 
            (
                from r2 in Readings
                where cfg.MeterID == r2.MeterID && r2.Date >= startDate && r2.Date <= endDate
                orderby r2.Date descending
                select r2.Date
            ).Take(1),
            Value = 
            (
                from r2 in Readings
                where cfg.MeterID == r2.MeterID && r2.Date >= startDate && r2.Date <= endDate
                orderby r2.Date descending
                select r2.Value
            ).Take(1)   
    })
    .GroupBy(x => x.Number)
    .Select(x => x.First()).Dump();
    

    返回:

    Number ReadingDate             Value 
    123    10/21/2010 12:00:00 AM  12.3
    456    10/20/2010 12:00:00 AM  8.2
    789    10/21/2010 12:00:00 AM  34.7
    

    对比:

    Number ReadingDate                       Value 
    123    IEnumerable<DateTime> (1 item)    IEnumerable<Double> (1 item)
           10/21/2010 12:00:00 AM            12.3
    456    IEnumerable<DateTime> (1 item)    IEnumerable<Double> (1 item)
           10/20/2010 12:00:00 AM            8.2
    789    IEnumerable<DateTime> (1 item)    IEnumerable<Double> (1 item)
           10/21/2010 12:00:00 AM            34.7
    

    【讨论】:

    • +1。谢谢,这是一个非常彻底的答案!明天我将启动 LINQPad,当我回到工作岗位时,我会看看它与我已经拥有的相比如何。别担心,我有很多数据:)。
    • 哦,我刚刚意识到可以通过在连接发生之前过滤日期来提高此解决方案的性能。代码已编辑。
    • 抱歉拖了这么久;今天终于有时间测试了。它按预期工作。谢谢!
    猜你喜欢
    • 2021-01-23
    • 1970-01-01
    • 1970-01-01
    • 2021-09-10
    • 2021-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多