【问题标题】:Is it possible to temporarily duplicate and modify rows on the fly in an SQL SELECT query?是否可以在 SQL SELECT 查询中临时复制和修改行?
【发布时间】:2009-06-15 19:13:56
【问题描述】:

我刚刚为我的应用程序收到了一个新的数据源,它仅在数据发生更改时才将数据插入Derby 数据库。通常,缺少数据很好 - 我正在用数据(随时间变化的值)绘制折线图,​​我只是在两点之间画一条线,在任何给定点推断预期值。问题是,在这种情况下,缺少数据意味着“画一条直线”,如果我这样做,图表就会不正确。

有两种方法可以解决这个问题:我可以创建一个以不同方式处理丢失数据的新类(由于 prefuse、我正在使用的绘图库处理绘图的方式,这可能很困难),或者我可以复制行,保持 y 值相同,同时更改每行中的 x 值。我可以在连接数据库和渲染器的 Java 中执行此操作,或者我可以修改 SQL。

我的问题是,给定如下结果集:

+-------+---------------------+
| value | received            |
+-------+---------------------+
|     7 | 2000-01-01 08:00:00 |
|    10 | 2000-01-01 08:00:05 |
|    11 | 2000-01-01 08:00:07 |
|     2 | 2000-01-01 08:00:13 |
|     4 | 2000-01-01 08:00:16 |
+-------+---------------------+

假设我在 8:00:20 查询它,如何使用 SQL 使它看起来像下面这样?基本上,我每秒都在复制该行,直到它已经被占用。从所有意图和目的来看,received 都是唯一的(它不是,但它是由于查询中的 WHERE 子句造成的)。

+-------+---------------------+
| value | received            |
+-------+---------------------+
|     7 | 2000-01-01 08:00:00 |
|     7 | 2000-01-01 08:00:01 |
|     7 | 2000-01-01 08:00:02 |
|     7 | 2000-01-01 08:00:03 |
|     7 | 2000-01-01 08:00:04 |
|    10 | 2000-01-01 08:00:05 |
|    10 | 2000-01-01 08:00:06 |
|    11 | 2000-01-01 08:00:07 |
|    11 | 2000-01-01 08:00:08 |
|    11 | 2000-01-01 08:00:09 |
|    11 | 2000-01-01 08:00:10 |
|    11 | 2000-01-01 08:00:11 |
|    11 | 2000-01-01 08:00:12 |
|     2 | 2000-01-01 08:00:13 |
|     2 | 2000-01-01 08:00:14 |
|     2 | 2000-01-01 08:00:15 |
|     4 | 2000-01-01 08:00:16 |
|     4 | 2000-01-01 08:00:17 |
|     4 | 2000-01-01 08:00:18 |
|     4 | 2000-01-01 08:00:19 |
|     4 | 2000-01-01 08:00:20 |
+-------+---------------------+

感谢您的帮助。

【问题讨论】:

  • 我实际上正在使用 Apache 的 Derby。我已将信息添加到问题中。

标签: sql derby


【解决方案1】:

由于 SQL 的基于集合的性质,没有简单的方法可以做到这一点。我使用了两种解决策略:

a) 使用一个循环从初始日期时间到结束日期时间,并为每个步骤获取值,并将其插入临时表中

b) 生成一个以 1 分钟为增量的表(正常或临时),将基准日期时间添加到此表中即可生成步骤。

方法 b) 的示例(SQL Server 版本)

假设我们永远不会查询超过 24 小时的数据。我们创建一个表 intervals,其中包含一个 dttm 字段,其中包含每个步骤的分钟数。该表必须事先填充。

select dateadd(minute,stepMinutes,'2000-01-01 08:00') received,
(select top 1 value from table where received <= 
dateadd(minute,dttm,'2000-01-01 08:00') 
order by received desc) value
from intervals

【讨论】:

    【解决方案2】:

    在这种情况下,您似乎真的不需要生成所有这些数据点。生成以下内容是否正确?如果它正在绘制一条直线,则不需要每秒生成一个数据点,每个数据点只需生成两个……当前时间一个,下一次之前一个。此示例从下一次减去 5 毫秒,但如果需要,您可以将其设为整秒。

    +-------+---------------------+
    | value | received            |
    +-------+---------------------+
    |     7 | 2000-01-01 08:00:00 |
    |     7 | 2000-01-01 08:00:04 |
    |    10 | 2000-01-01 08:00:05 |
    |    10 | 2000-01-01 08:00:06 |
    |    11 | 2000-01-01 08:00:07 |
    |    11 | 2000-01-01 08:00:12 |
    |     2 | 2000-01-01 08:00:13 |
    |     2 | 2000-01-01 08:00:15 |
    |     4 | 2000-01-01 08:00:16 |
    |     4 | 2000-01-01 08:00:20D |
    +-------+---------------------+
    

    如果是这种情况,那么您可以执行以下操作:

    SELECT * FROM
    (SELECT * from TimeTable as t1
    UNION
    SELECT t2.value, dateadd(ms, -5, t2.received)
    from ( Select t3.value, (select top 1 t4.received  
                             from TimeTable t4 
                             where t4.received > t3.received
                             order by t4.received asc) as received
    from TimeTable t3) as t2
    UNION
    SELECT top 1 t6.value, GETDATE()
    from TimeTable t6
    order by t6.received desc
    ) as t5
    where received IS NOT NULL
    order by t5.received
    

    这样做的最大优势在于它是基于集合的解决方案,并且比任何迭代方法都快得多。

    【讨论】:

    • 这绝对是一个更好的主意,SQL 乍一看很可靠。有没有办法在不使用 TOP 1 的情况下做到这一点? Derby 非常简单 - 它不支持 TOP 或 LIMIT。
    • 不,你不能。您需要查询的顶部才能仅获取下一项(例如当前时间之后的时间)如果您可以从 Derby 移至 MySQL 或 PostgreSql,我知道这两个都支持这个示例。这是用 MSSQL 编写的,因此需要进行一些调整
    • 该死的。我们正在嵌入 Derby,因此它对我们的软件来说是非常固有的——我们可以使用另一个数据库,但我们也会使用 Derby,这对于应该是一个简单的应用程序来说似乎有点太多了。看来我将在 Java 代码中执行此操作。不过,我接受你的回答,因为你基本上已经回答了任何使用半体面数据库的人的问题。
    【解决方案3】:

    您可以只移动一个游标,将 vars 保留为最后一个值和返回的时间,如果当前值超过一秒,则使用前一个值和新时间一次循环一秒,直到获得当前行的时间。

    尝试在 SQL 中执行此操作会很痛苦,如果您创建了丢失的数据,您可能必须添加一列来跟踪实际/插值数据点。

    【讨论】:

      【解决方案4】:

      最好为您希望在图表上拥有的每个轴向值创建一个表格,然后加入该表格,或者甚至只是将数据字段放在那里并在值到达时/如果值到达时更新该记录。

      “缺失值”问题相当广泛,所以我建议你制定一个可靠的政策。

      会发生的一件事是您将有多个相邻的插槽缺少值。

      如果您可以将其转换为 OLAP 数据,这将容易得多。

      【讨论】:

        【解决方案5】:

        创建一个包含所有分钟的简单表(警告,将运行一段时间):

        Create Table Minutes(Value DateTime Not Null)
        Go
        
        Declare @D DateTime
        Set @D = '1/1/2000'
        
        While (Year(@D) < 2002)
        Begin
          Insert Into Minutes(Value) Values(@D)
          Set @D = DateAdd(Minute, 1, @D)
        End
        Go
        
        
        Create Clustered Index IX_Minutes On Minutes(Value)
        Go
        

        然后你可以像这样使用它:

        Select 
          Received = Minutes.Value,
          Value = (Select Top 1 Data.Value
                   From Data
                   Where Data.Received <= Minutes.Received
                   Order By Data.Received Desc)
        From
          Minutes
        Where
          Minutes.Value Between @Start And @End
        

        【讨论】:

          【解决方案6】:

          我建议不要在 SQL/数据库中解决这个问题,因为它是基于集合的。 此外,您在这里处理的是秒数,所以我猜您最终可能会得到很多行,具有相同的重复数据,必须从数据库传输到您的应用程序。

          【讨论】:

            【解决方案7】:

            处理此问题的一种方法是将您的数据左连接到一个包含所有收到值的表。然后,当该行没有值时,您可以根据您拥有的上一个和下一个实际值计算预计值。

            你没有说你使用的是什么数据库平台。在 SQL Server 中,我将创建一个接受开始日期时间和结束日期时间值的用户定义函数。它会返回一个表值,其中包含您需要的所有收到的值。

            我在下面模拟了它,它在 SQL Server 中运行。子选择别名 r 是用户定义函数实际返回的内容。

            select r.received,
            isnull(d.value,(select top 1 data.value from data where data.received < r.received order by data.received desc)) as x
            from (
                select cast('2000-01-01 08:00:00' as datetime) received
                union all
                select cast('2000-01-01 08:00:01' as datetime)
                union all
                select cast('2000-01-01 08:00:02' as datetime)
                union all
                select cast('2000-01-01 08:00:03' as datetime)
                union all
                select cast('2000-01-01 08:00:04' as datetime)
                union all
                select cast('2000-01-01 08:00:05' as datetime)
                union all
                select cast('2000-01-01 08:00:06' as datetime)
                union all
                select cast('2000-01-01 08:00:07' as datetime)
                union all
                select cast('2000-01-01 08:00:08' as datetime)
                union all
                select cast('2000-01-01 08:00:09' as datetime)
                union all
                select cast('2000-01-01 08:00:10' as datetime)
                union all
                select cast('2000-01-01 08:00:11' as datetime)
                union all
                select cast('2000-01-01 08:00:12' as datetime)
                union all
                select cast('2000-01-01 08:00:13' as datetime)
                union all
                select cast('2000-01-01 08:00:14' as datetime)
                union all
                select cast('2000-01-01 08:00:15' as datetime)
                union all
                select cast('2000-01-01 08:00:16' as datetime)
                union all
                select cast('2000-01-01 08:00:17' as datetime)
                union all
                select cast('2000-01-01 08:00:18' as datetime)
                union all
                select cast('2000-01-01 08:00:19' as datetime)
                union all
                select cast('2000-01-01 08:00:20' as datetime)
            ) r
            left outer join Data d on r.received = d.received
            

            【讨论】:

              【解决方案8】:

              如果您使用的是 SQL Server,那么这将是一个好的开始。我不确定 Apache 的 Derby 与 sql 有多接近。

              Usage: EXEC ElaboratedData '2000-01-01 08:00:00','2000-01-01 08:00:20'
              
              CREATE PROCEDURE [dbo].[ElaboratedData]
                @StartDate DATETIME,
                @EndDate DATETIME
              AS
                --if not a valid interval, just quit
                IF @EndDate<=@StartDate BEGIN
                  SELECT 0;    
                  RETURN;
                END;
              
                /*
                Store the value of 1 second locally, for readability
                --*/
                DECLARE @OneSecond FLOAT;
                SET @OneSecond = (1.00000000/86400.00000000);
              
                /*
                create a temp table w/the same structure as the real table.
                --*/
                CREATE TABLE #SecondIntervals(TSTAMP DATETIME, DATAPT INT);
              
                /*
                For each second in the interval, check to see if we have a known value.
                If we do, then use that.  If not, make one up.
                --*/ 
                DECLARE @CurrentSecond DATETIME; 
                SET @CurrentSecond = @StartDate;
                WHILE @CurrentSecond <= @EndDate BEGIN
                  DECLARE @KnownValue INT;
              
                  SELECT @KnownValue=DATAPT
                  FROM TESTME
                  WHERE TSTAMP = @CurrentSecond;
              
                  IF (0 = ISNULL(@KnownValue,0)) BEGIN
                    --ok, we have to make up a fake value
                    DECLARE @MadeUpValue INT;
                    /*
                    *******Put whatever logic you want to make up a fake value here
                    --*/
                    SET @MadeUpValue = 99;
              
                    INSERT INTO #SecondIntervals(
                      TSTAMP
                     ,DATAPT
                    )
                    VALUES(
                      @CurrentSecond
                     ,@MadeUpValue
                    );
                  END;  --if we had to make up a value
                  SET @CurrentSecond = @CurrentSecond + @OneSecond;
                END;  --while looking thru our values
              
                --finally, return our generated values + real values
                SELECT TSTAMP, DATAPT FROM #SecondIntervals
                UNION ALL
                SELECT TSTAMP, DATAPT FROM TESTME
                ORDER BY TSTAMP;
              GO
              

              【讨论】:

                【解决方案9】:

                只是一个想法,您可能想查看 Anthony Mollinaro 的 SQL Cookbook,第 9 章。他有一个食谱,"Filling in Missing Dates"(查看第 278-281 页),主要讨论您要做什么。它需要某种顺序处理,要么通过帮助表,要么递归地进行查询。虽然他没有直接提供 Derby 的示例,但我怀疑您可能会根据您的问题调整它们(尤其是 PostgreSQL 或 MySQL ,它似乎与平台无关)。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2010-09-24
                  • 2012-08-25
                  • 2023-03-03
                  • 2021-11-03
                  • 1970-01-01
                  • 2011-07-16
                  相关资源
                  最近更新 更多