【问题标题】:Get values from database with specific time interval以特定时间间隔从数据库中获取值
【发布时间】:2012-11-29 05:29:35
【问题描述】:

我想在 C# 中以特定间隔从数据库中获取值,并且需要一个查询。 这就是我的数据库的样子

Id SensorId 值 CreatedOn ------------------------------------------------ 1 8 33.5 15-11-2012 下午 5:48 2 5 49.2 15-11-2012 下午 5:48 3 8 33.2 15-11-2012 下午 5:49 4 5 48.5 2012 年 11 月 15 日下午 5:49 5 8 31.8 15-11-2012 下午 5:50 6 5 42.5 15-11-2012 下午 5:50 7 8 36.5 15-11-2012 下午 5:51 8 5 46.5 15-11-2012 下午 5:51 9 8 39.2 15-11-2012 下午 5:52 10 5 44.4 15-11-2012 下午 5:52 11 8 36.5 15-11-2012 下午 5:53 12 5 46.5 15-11-2012 下午 5:53 13 8 39.2 15-11-2012 下午 5:54 14 5 44.4 15-11-2012 下午 5:54 ………………………………………………………………………………………………………………

时间间隔以分钟为单位。 所以,如果间隔是 10 分钟,那么我们需要 5:48、5:58、6:08 等的值...

我尝试使用 while 循环来执行此操作,但是当我向数据库发送多个查询时会花费很多时间。

有没有办法在单个查询中获取数据?

【问题讨论】:

    标签: c# .net sql sql-server linq


    【解决方案1】:

    您可以使用datepart 和一个模数来获得匹配的行(例如,@interval = 10,@offset = 8):

    SELECT * FROM table
    WHERE datepart(minute, CreatedOn) % @interval = @offset
    

    编辑

    请注意,以上不是按间隔选择的通用解决方案。它将跨 小时(因此跨天)工作,间隔为 2、3、4、5 ... 任何一分为 60 的分钟间隔。

    如果您想使用一个奇怪的间隔,比如 7 分钟,那么您必须定义间隔的开始时间并计算每行的总分钟数,包括小时/天。此时,您最好根据计算相关间隔的用户定义函数在表上创建一个索引计算列。

    【讨论】:

    • 请不要忘记,如果这个查询很重要并且表很大,应该在这个表达式上创建一个索引以避免顺序扫描。
    • @MarceloZabani 是的,好点......甚至可能值得添加一个计算列和索引
    • 然而,应该注意的是,要使用索引,要么interval 应该是常量并且对 mod 操作进行索引,或者必须将查询更改为包含 6 个相等比较:datepart(minute, CreatedOn) = 08 OR datepart(minute, CreatedOn) = 18 OR datepart(minute, CreatedOn) = 28 .. 以此类推,仅在 datepart(minute, CreatedOn) 上有一个索引。这可能会产生一些索引活动,但仍然比顺序扫描好。
    • 但我想要 3 天的值,间隔 2 分钟。所以在那种情况下,这个东西是行不通的。
    • @MayurDhingra 实际上,对于 2 分钟 的间隔,这仍然有效,因为 mod 值将在多天内选择正确的值。但我接受你的一般观点——如果间隔是 7 分钟,即使跨小时也行不通。
    【解决方案2】:

    您可以这样做,解释包含在代码中的 cmets 中:

    /*We want 10-minute intervals starting 
      from minimum date to next day same time*/
    DECLARE @startDateTime DATETIME = (
        SELECT  MIN(CreatedOn)
        FROM    #yourTable
    )
    DECLARE @endDateTime DATETIME = DATEADD(DAY, 1, @startDateTime)
    
    DECLARE @startDateTimeTable TABLE (dt DATETIME)
    INSERT @startDateTimeTable VALUES (@startDateTime)
    
    /*Create a table that contains relevant datetimes (10-minute 
      intervals from starting date to end date)*/
    ;WITH a AS (
        SELECT  dt
        FROM    @startDateTimeTable
        UNION   ALL
        SELECT  DATEADD(MINUTE, 10, a.dt)
        FROM    a
        JOIN    @startDateTimeTable b ON a.dt <= @endDateTime
    )
    SELECT  *
    INTO    #requiredDateTimes
    FROM    a
    OPTION  (MAXRECURSION 32767)
    
    /*Now join data table to datetime table to 
      filter out only records with datetimes that we want*/
    SELECT  *
    FROM    #yourTable a
    JOIN    #requiredDateTimes b ON
            a.CreatedOn = b.dt
    

    这是一个SQL Fiddle

    【讨论】:

    • 这是正确的方法。其他依赖模数的样本意味着在那个特定时间总会有一个值。必须首先投影每个间隔日期,然后找到最接近的匹配 - 可能是该日期或之前的最后一个值。但是,OP 确实要求提供 LINQ 解决方案。像这个答案一样复杂的东西会要求存储过程。可能有一种方法可以在单个 LINQ 查询中表达这个概念。
    • @MattJohnson 谢谢马特,我同意,我还没有读过关于 C# 的那部分。 OP 实际上发布了另一个与此类似的问题,但强调他需要 LINQ 答案,所以我将这个想法转码为 LINQ 并将其发布为他的另一个问题的答案。
    【解决方案3】:

    任何建议使用模数 (%) 的答案都做了几个假设:

    1. 您将始终在有问题的确切时间获得每个传感器的读数
    2. 每个传感器一分钟内的读数永远不会超过一个。
    3. 您永远不必处理小于一分钟的间隔。

    这些可能是错误的假设,因此您需要一种不同的方法。首先,制作您要查询的所有时间点的地图。然后在该点或之前从每个传感器获取最后一个读数。

    这是一个完整的单元测试,展示了如何在纯 linq-to-objects 中完成它。您可能需要对查询进行一些小的更改才能使其在 linq-to-sql 中工作,但这是正确的方法。我使用了您提供的确切样本数据。

    顺便说一句 - 我希望您在 UTC 中记录您的 CreatedOn 日期,否则在夏令时“回退”转换期间您的传感器读数会不明确。您需要以 UTC 格式记录为 DateTime,或使用 DateTimeOffset。两者都是瞬时时间的适当表示。带有 .Kind 的 Local 或 Unspecified 的 DateTime 只是 日历时间 的有效表示,不适用于传感器读数。

    [TestClass]
    public class LinqIntervalQueryTest
    {
        public class Item
        {
            public int Id { get; set; }
            public int SensorId { get; set; }
            public double Value { get; set; }
            public DateTime CreatedOn { get; set; }
        }
    
        [TestMethod]
        public void Test()
        {
            var data = new[]
                {
                    new Item { Id = 1, SensorId = 8, Value = 33.5, CreatedOn = new DateTime(2012, 11, 15, 17, 48, 0, DateTimeKind.Utc) },
                    new Item { Id = 2, SensorId = 5, Value = 49.2, CreatedOn = new DateTime(2012, 11, 15, 17, 48, 0, DateTimeKind.Utc) },
                    new Item { Id = 3, SensorId = 8, Value = 33.2, CreatedOn = new DateTime(2012, 11, 15, 17, 49, 0, DateTimeKind.Utc) },
                    new Item { Id = 4, SensorId = 5, Value = 48.5, CreatedOn = new DateTime(2012, 11, 15, 17, 49, 0, DateTimeKind.Utc) },
                    new Item { Id = 5, SensorId = 8, Value = 31.8, CreatedOn = new DateTime(2012, 11, 15, 17, 50, 0, DateTimeKind.Utc) },
                    new Item { Id = 6, SensorId = 5, Value = 42.5, CreatedOn = new DateTime(2012, 11, 15, 17, 50, 0, DateTimeKind.Utc) },
                    new Item { Id = 7, SensorId = 8, Value = 36.5, CreatedOn = new DateTime(2012, 11, 15, 17, 51, 0, DateTimeKind.Utc) },
                    new Item { Id = 8, SensorId = 5, Value = 46.5, CreatedOn = new DateTime(2012, 11, 15, 17, 51, 0, DateTimeKind.Utc) },
                    new Item { Id = 9, SensorId = 8, Value = 39.2, CreatedOn = new DateTime(2012, 11, 15, 17, 52, 0, DateTimeKind.Utc) },
                    new Item { Id = 10, SensorId = 5, Value = 44.4, CreatedOn = new DateTime(2012, 11, 15, 17, 52, 0, DateTimeKind.Utc) },
                    new Item { Id = 11, SensorId = 8, Value = 36.5, CreatedOn = new DateTime(2012, 11, 15, 17, 53, 0, DateTimeKind.Utc) },
                    new Item { Id = 12, SensorId = 5, Value = 46.5, CreatedOn = new DateTime(2012, 11, 15, 17, 53, 0, DateTimeKind.Utc) },
                    new Item { Id = 13, SensorId = 8, Value = 39.2, CreatedOn = new DateTime(2012, 11, 15, 17, 54, 0, DateTimeKind.Utc) },
                    new Item { Id = 14, SensorId = 5, Value = 44.4, CreatedOn = new DateTime(2012, 11, 15, 17, 54, 0, DateTimeKind.Utc) },
                };
    
            var interval = TimeSpan.FromMinutes(3);
            var startDate = data.First().CreatedOn;
            var endDate = data.Last().CreatedOn;
    
            var numberOfPoints = (int)((endDate - startDate + interval).Ticks / interval.Ticks);
            var points = Enumerable.Range(0, numberOfPoints).Select(x => startDate.AddTicks(interval.Ticks * x));
    
            var query = from item in data
                        group item by item.SensorId
                        into g
                        from point in points
                        let itemToUse = g.LastOrDefault(x => x.CreatedOn <= point)
                        orderby itemToUse.CreatedOn, g.Key
                        select new
                            {
                                itemToUse.CreatedOn,
                                itemToUse.Value,
                                SensorId = g.Key
                            };
    
            var results = query.ToList();
    
            Assert.AreEqual(6, results.Count);
    
            Assert.AreEqual(data[1].CreatedOn, results[0].CreatedOn);
            Assert.AreEqual(data[1].Value, results[0].Value);
            Assert.AreEqual(data[1].SensorId, results[0].SensorId);
    
            Assert.AreEqual(data[0].CreatedOn, results[1].CreatedOn);
            Assert.AreEqual(data[0].Value, results[1].Value);
            Assert.AreEqual(data[0].SensorId, results[1].SensorId);
    
            Assert.AreEqual(data[7].CreatedOn, results[2].CreatedOn);
            Assert.AreEqual(data[7].Value, results[2].Value);
            Assert.AreEqual(data[7].SensorId, results[2].SensorId);
    
            Assert.AreEqual(data[6].CreatedOn, results[3].CreatedOn);
            Assert.AreEqual(data[6].Value, results[3].Value);
            Assert.AreEqual(data[6].SensorId, results[3].SensorId);
    
            Assert.AreEqual(data[13].CreatedOn, results[4].CreatedOn);
            Assert.AreEqual(data[13].Value, results[4].Value);
            Assert.AreEqual(data[13].SensorId, results[4].SensorId);
    
            Assert.AreEqual(data[12].CreatedOn, results[5].CreatedOn);
            Assert.AreEqual(data[12].Value, results[5].Value);
            Assert.AreEqual(data[12].SensorId, results[5].SensorId);
        }
    }
    

    【讨论】:

      【解决方案4】:

      您可以通过以下方式在两次调用数据库中执行此操作(未经测试):

      int interval = 10;
      DateTime firstDate = db.Items.Select(x => x.CreatedOn).Min();
      var items = db.Items.Where(x => (x.CreatedOn - firstDate).TotalMinutes % interval == 0).ToList();
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-01-31
        • 2020-10-21
        • 2022-10-21
        相关资源
        最近更新 更多