3

我刚刚为我的应用程序收到了一个新的数据源,它仅在数据发生更改时才将数据插入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 |
+-------+---------------------+

谢谢你的帮助。

4

9 回答 9

3

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

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

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

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

假设我们永远不会查询超过 24 小时的数据。我们创建一个表间隔,其中有一个 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
于 2009-06-15T19:27:47.247 回答
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

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

于 2009-06-15T19:59:42.253 回答
1

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

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

于 2009-06-15T20:06:32.597 回答
0

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

你没有说你使用的是什么数据库平台。在 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
于 2009-06-15T19:22:58.037 回答
0

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

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

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

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

于 2009-06-15T19:28:07.167 回答
0

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

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
于 2009-06-15T19:29:17.227 回答
0

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

于 2009-06-15T19:40:15.223 回答
0

如果您使用的是 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
于 2009-06-15T20:46:13.273 回答
0

作为一个想法,您可能想查看 Anthony Mollinaro 的 SQL Cookbook,第 9 章。他有一个食谱,“填写缺失的日期”(查看第 278-281 页),主要讨论您要做什么。它需要某种顺序处理,要么通过帮助表,要么递归地进行查询。虽然他没有直接针对 Derby 的示例,但我怀疑您可能会根据您的问题调整它们(特别是 PostgreSQL 或 MySQL 的示例,它似乎与平台无关)。

于 2009-06-15T21:09:34.963 回答