这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

时序分析

时序分析随时间的推移评估给定变量集的值,并将这些值分组到窗口(基于时间间隔)以进行分析和聚合。使用时序分析的常见场景包括:随着时间的推移不断进行的股市交易、投资组合业绩随时间推移的变动情况以及数据的图表趋势线。

由于时序中的时间和数据状态都是连续性的,因此随时间的推移来评估 SQL 查询具有挑战性。输入记录的间隔通常不均匀,因此会产生空白。为了解决此问题,Vertica 提供了:

  • 空白填充功能,它可以填充缺失的数据点。

  • 插值方案,它可以在已知数据点的离散集范围内推导新数据点。

Vertica 会在数据中插入非时序列(例如随时间片的变化计算得出的分析函数结果),并将缺失数据点添加到输出。此部分详细介绍了空白填充和插值。

您可以使用基于事件的窗口将时序数据拆分为与数据中的重大事件相邻的窗口。这在财务数据中尤其重要,因为分析可以侧重于触发了其他活动的特定事件。

会话化是基于事件的窗口的特殊用例,它通常用于分析点击流,例如根据记录的 Web 点击识别 Web 浏览会话。

Vertica 通过以下 SQL 扩展为时序分析提供了额外支持:

  • SELECT 语句中的 TIMESERIES 子句 支持空白填充和插值 (GFI) 计算。

  • TS_FIRST_VALUETS_LAST_VALUE 是时序聚合函数,用于在时间片开始或结束时返回值,而时间片的开始或结束时间由插值方案分别确定。

  • TIME_SLICE 是(SQL 扩展)日期/时间函数,用于按照不同的固定时间间隔聚合数据,并将向上舍入的输入 TIMESTAMP 值返回到与时间片间隔开始或结束时间相对应的值。

另请参阅

1 - 空白填充和插值 (GFI)

此主题中用于解释概念的示例和图形,均使用以下简单的架构:

CREATE TABLE TickStore (ts TIMESTAMP, symbol VARCHAR(8), bid FLOAT);
INSERT INTO TickStore VALUES ('2009-01-01 03:00:00', 'XYZ', 10.0);
INSERT INTO TickStore VALUES ('2009-01-01 03:00:05', 'XYZ', 10.5);
COMMIT;

在 Vertica 中,时序数据以一系列符合特定表架构的行表示,其中一列用于存储时间信息。

时序中的时间和数据状态都是连续性的。因此,随时间的推移对 SQL 查询进行评估具有挑战性,因为输入记录的间隔通常不均匀,而且会包含空白。

例如,下表中包含的两个输入行相隔五秒钟:3:00:00 和 3:00:05。

=> SELECT * FROM TickStore;
         ts          | symbol | bid
---------------------+--------+------
 2009-01-01 03:00:00 | XYZ    |   10
 2009-01-01 03:00:05 | XYZ    | 10.5
(2 rows)

根据这两个输入,如何确定时间位于这两个时间点之间的出价,例如 3:00:03 PM? TIME_SLICE 函数会将时间戳标准化为对应的时间片;但是,TIME_SLICE 并没有解决数据中缺少输入(时间片)这一问题。不过,Vertica 提供了空白填充和插值 (GFI) 功能,可以填充缺少的数据点并将已知数据点范围内的新(缺少的)数据点添加到输出中。它通过时序聚合函数(TS_FIRST_VALUETS_LAST_VALUE)以及 SQL TIMESERIES 子句完成这些任务。 但首先,我们将从常数插值开始,介绍 Vertica 中构成空白填充和插值的组件。 以下主题中的图像使用如下图例:

  • x 轴表示时间戳 (ts) 列

  • y 轴表示出价列。

  • 垂直的蓝线用来分隔时间片。

  • 红点表示表中的输入记录 $10.0 和 $10.5。

  • 蓝色星形表示输出值,包括内插值。

1.1 - 常数插值

根据示例 TickStore 架构中在 03:00:00 和 03:00:05 时的已知输入时间戳,如何确定 03:00:03 时的出价?

在财务数据中,使用的常见插值方案是将出价设置为到目前为止最后一个见到的值。这种方案称为常数插值,也就是说,Vertica 将根据之前的输入记录计算新值。

回到上面问到的查询,以下是表输出,它显示了在 03:00:00 和 03:00:05 时的出价之间存在 5 秒延迟:

=> SELECT * FROM TickStore;
         ts          | symbol | bid
---------------------+--------+------
 2009-01-01 03:00:00 | XYZ    |   10
 2009-01-01 03:00:05 | XYZ    | 10.5
(2 rows)

使用常数插值时,XYZ 的内插出价在 3:00:03 时仍为 $10.0,而且 3:00:03 介于在两个已知数据输入(3:00:00 PM 和 3:00:05)之间。在 3:00:05 时,值变为 $10.5。已知数据点以红点表示,在 3:00:03 时的内插值以蓝色星形表示。

包含 3 秒时间片的 TickStore 表

要编写查询使输入行更加均匀,您首先需要了解 TIMESERIES 子句和时序聚合函数

1.2 - TIMESERIES 子句和聚合

SELECT..TIMESERIES 子句和时序聚合会对数据进行标准化处理并将其放入 3 秒时间片,然后在发现空白时插入出价,以此来解决输入记录中存在空白的问题。

TIMESERIES 子句

TIMESERIES 子句 是时序分析计算的重要组件。它执行空白填充和插值 (GFI) 以生成输入记录中缺失的时间片。此子句会应用于数据中的时间戳列/表达式,其形式如下:

TIMESERIES slice_time AS 'length_and_time_unit_expression' 
OVER ( ... [ window-partition-clause[ , ... ] ] 
... ORDER BY time_expression )
... [ ORDER BY table_column [ , ... ] ]

时序聚合函数

时序聚合 (TSA) 函数 TS_FIRST_VALUETS_LAST_VALUE 随时间的推移评估给定变量集的值,并将这些值分组到窗口以进行分析和聚合。

TSA 函数负责处理属于每个时间片的数据。每个时间片会产生一行输出;如果存在分区表达式,则每个时间片的每个分区会产生一行输出。

下表显示了 3 秒时间片,其中:

  • 前两行位于第一个时间片,该时间片范围为 3:00:00 到 3:00:02。这些是 TSA 函数对应于 3:00:00 起的时间片的输出所对应的输入行。

  • 后两行位于第二个时间片,该时间片范围为 3:00:03 到 3:00:05。这些是 TSA 函数对应于 3:00:03 起的时间片的输出所对应的输入行。

    结果是每个时间片的开始时间。

    时序聚合输出

示例

以下示例比较了使用和不使用 TS_FIRST_VALUE TSA 函数时的返回值。

此示例显示了不使用 TS_FIRST_VALUE TSA 函数的 TIMESERIES 子句。

=> SELECT slice_time, bid FROM TickStore TIMESERIES slice_time AS '3 seconds' OVER(PARTITION by TickStore.bid ORDER BY ts);

此示例显示了 TIMESERIES 子句和 TS_FIRST_VALUE TSA 函数。该查询返回了 bid 列的值,这些值由指定的常数插值方案确定。

=> SELECT slice_time, TS_FIRST_VALUE(bid, 'CONST') bid FROM TickStore
   TIMESERIES slice_time AS '3 seconds' OVER(PARTITION by symbol ORDER BY ts);

Vertica 插入了最后一个已知值,填充了缺失的数据点,因此返回了对应于 3:00:03 的 10:

1.3 - 时序舍入

Vertica 按照与时间戳 2000-01-01 00:00:00 相关的相同间隔计算所有时序。Vertica 根据需要对时序时间戳进行四舍五入以符合此基线。对于指定的间隔,开始时间也会向下舍入为最接近的整数。

根据此逻辑,TIMESERIES 子句将生成时间戳系列,如以下部分所述。

分钟时序会向下舍入为整数分钟。例如,以下语句指定了 00:00:0300:05:50 这一时间范围:

=> SELECT ts FROM (
    SELECT '2015-01-04 00:00:03'::TIMESTAMP AS tm
      UNION
    SELECT '2015-01-04 00:05:50'::TIMESTAMP AS tm
   ) t
   TIMESERIES ts AS '1 minute' OVER (ORDER BY tm);

Vertica 将时间序列的开始时间和结束时间四舍五入到整分钟,分别为 00:00:0000:05:00


         ts
---------------------
 2015-01-04 00:00:00
 2015-01-04 00:01:00
 2015-01-04 00:02:00
 2015-01-04 00:03:00
 2015-01-04 00:04:00
 2015-01-04 00:05:00
(6 rows)

因为基线时间戳 2000-01-01 00:00:00 为星期六,所有周时序都从星期六开始。Vertica 相应地向下舍入了时序的开始和结束时间戳。例如,以下语句指定了 12/10/99 ‑ 01/10/00 的时间跨度:

=> SELECT ts FROM (
     SELECT '1999-12-10 00:00:00'::TIMESTAMP AS tm
       UNION
     SELECT '2000-01-10 23:59:59'::TIMESTAMP AS tm
     ) t
     TIMESERIES ts AS '1 week' OVER (ORDER BY tm);

指定的时间范围从星期五 (12/10/99) 开始,因此 Vertica 从上一个星期六 (12/04/99) 开始时序。时序结束于时间范围中的最后一个星期六,即 01/08/00:

         ts
---------------------
 1999-12-04 00:00:00
 1999-12-11 00:00:00
 1999-12-18 00:00:00
 1999-12-25 00:00:00
 2000-01-01 00:00:00
 2000-01-08 00:00:00
(6 rows)

月时序会相对于基线时间戳 2000-01-01 00:00:00 分为相等的 30 天间隔。例如,以下语句指定了 09/01/99 ‑ 12/31/00 这一时间范围:

=> SELECT ts FROM (
     SELECT '1999-09-01 00:00:00'::TIMESTAMP AS tm
       UNION
     SELECT '2000-12-31 23:59:59'::TIMESTAMP AS tm
   ) t
   TIMESERIES ts AS '1 month' OVER (ORDER BY tm);

Vertica 生成了一系列 30 天的间隔,其中每个时间戳都相对于基线时间戳进行了向上舍入或向下舍入:

         ts
---------------------
 1999-08-04 00:00:00
 1999-09-03 00:00:00
 1999-10-03 00:00:00
 1999-11-02 00:00:00
 1999-12-02 00:00:00
 2000-01-01 00:00:00
 2000-01-31 00:00:00
 2000-03-01 00:00:00
 2000-03-31 00:00:00
 2000-04-30 00:00:00
 2000-05-30 00:00:00
 2000-06-29 00:00:00
 2000-07-29 00:00:00
 2000-08-28 00:00:00
 2000-09-27 00:00:00
 2000-10-27 00:00:00
 2000-11-26 00:00:00
 2000-12-26 00:00:00
(18 rows)

年时序分为相等的 365 天间隔。如果时间范围重叠了自基线时间戳 2000-01-01 00:00:00 以来或在该基线时间戳以前的闰年,Vertica 将相应地对时序时间戳进行四舍五入。

例如,以下语句指定了 01/01/95 ‑ 05/08/09 这一时间范围,此时间范围重叠了四个闰年,包括基线时间戳:

=> SELECT ts FROM (
      SELECT '1995-01-01 00:00:00'::TIMESTAMP AS tm
        UNION
      SELECT '2009-05-08'::TIMESTAMP AS tm
    ) t timeseries ts AS '1 year' over (ORDER BY tm);

Vertica 生成了一系列时间戳,它们都相对于基线时间戳进行了向上舍入或向下舍入:

         ts
---------------------
 1994-01-02 00:00:00
 1995-01-02 00:00:00
 1996-01-02 00:00:00
 1997-01-01 00:00:00
 1998-01-01 00:00:00
 1999-01-01 00:00:00
 2000-01-01 00:00:00
 2000-12-31 00:00:00
 2001-12-31 00:00:00
 2002-12-31 00:00:00
 2003-12-31 00:00:00
 2004-12-30 00:00:00
 2005-12-30 00:00:00
 2006-12-30 00:00:00
 2007-12-30 00:00:00
 2008-12-29 00:00:00
(16 rows)

1.4 - 线性插值

线性插值是指 Vertica 基于指定的时间片将值内插到线性斜率中,而不是基于最后一个见到的值(常数插值)内插数据点。

后面的查询使用线性插值将输入记录放入 2 秒时间片中,然后返回每个股票代码/时间片组合的第一个出价值(时间片开始时的值):

=> SELECT slice_time, TS_FIRST_VALUE(bid, 'LINEAR') bid FROM Tickstore
   TIMESERIES slice_time AS '2 seconds' OVER(PARTITION BY symbol ORDER BY ts);
     slice_time      | bid
---------------------+------
 2009-01-01 03:00:00 |   10
 2009-01-01 03:00:02 | 10.2
 2009-01-01 03:00:04 | 10.4
(3 rows)

下图显示了前面的查询结果,同时显示了没有产生输入记录的 2 秒时间空白(3:00:02 和 3:00:04)。请注意,XYZ 的内插出价在 3:00:02 时变为 10.2,在 3:00:03 时变为 10.3,在 3:00:04 时变为 10.4,而所有这些都介于两个已知数据输入(3:00:00 和 3:00:05)之间。在 3:00:05 时,值可能会变为 10.5。

使用 ts_first_value 时的线性插值

以下是常数和线性插值方案的横向比较。

1.5 - GFI 示例

此主题介绍了可以使用常数和线性插值方案编写的部分查询。

常数插值

第一个查询使用 TS_FIRST_VALUE()TIMESERIES 子句将输入记录放入时长为 3 秒的时间片中,然后返回每个符号/时间片组合的第一个出价值(时间片开始时的值)。

=> SELECT slice_time, symbol, TS_FIRST_VALUE(bid) AS first_bid FROM TickStore
   TIMESERIES slice_time AS '3 seconds' OVER (PARTITION BY symbol ORDER BY ts);

因为股票 XYZ 的出价在 3:00:03 时为 10.0,即第二个时间片的 first_bid 值。此值从 3:00:03 开始时一直为 10.0,因为 10.5 的输入值直到 3:00:05 时才产生。在这种情况下,根据在股票 XYZ 中见到的最后一个值为时间 3:00:03 推断了内插值:

     slice_time      | symbol | first_bid
---------------------+--------+-----------
 2009-01-01 03:00:00 | XYZ    |        10
 2009-01-01 03:00:03 | XYZ    |        10
(2 rows)

下一个示例将输入记录放入 2 秒时间片以返回每个股票代码/时间片组合的第一个出价值:

=> SELECT slice_time, symbol, TS_FIRST_VALUE(bid) AS first_bid FROM TickStore
   TIMESERIES slice_time AS '2 seconds' OVER (PARTITION BY symbol ORDER BY ts);

此时结果包含 2 秒时间片中的三个记录,所有这些出现在 03:00:00 时的第一个输入行和在 3:00:05 时的第二个输入行之间。请注意,第二个和第三个输出记录与不存在输入记录的时间片对应。

     slice_time      | symbol | first_bid
---------------------+--------+-----------
 2009-01-01 03:00:00 | XYZ    |        10
2009-01-01 03:00:02 | XYZ    |        10
2009-01-01 03:00:04 | XYZ    |        10
(3 rows)

使用同一个表架构时,下一个查询将 TS_LAST_VALUE() 与 TIMESERIES 结合使用以返回每个时间片最后的值(时间片结束时的值)。

=> SELECT slice_time, symbol, TS_LAST_VALUE(bid) AS last_bid FROM TickStore
   TIMESERIES slice_time AS '2 seconds' OVER (PARTITION BY symbol ORDER BY ts);

请注意,最后一个值输出行为 10.5,因为在时间 3:00:05 时的值 10.5 是从 3:00:04 开始的 2 秒时间片中的最后一个点。

     slice_time      | symbol | last_bid
---------------------+--------+----------
 2009-01-01 03:00:00 | XYZ    |       10
 2009-01-01 03:00:02 | XYZ    |       10
 2009-01-01 03:00:04 | XYZ    |     10.5
(3 rows)

请记住,因为常数插值为默认方案,如果按下述方式使用 CONST 参数编写查询,则会返回相同的结果:

=> SELECT slice_time, symbol, TS_LAST_VALUE(bid, 'CONST') AS last_bid FROM TickStore
   TIMESERIES slice_time AS '2 seconds' OVER (PARTITION BY symbol ORDER BY ts);

线性插值

根据常数插值示例(指定了 2 秒时间片)中所列的上述输入记录,通过线性插值得出的 TS_LAST_VALUE 结果如下:

=> SELECT slice_time, symbol, TS_LAST_VALUE(bid, 'linear') AS last_bid   FROM TickStore
   TIMESERIES slice_time AS '2 seconds' OVER (PARTITION BY symbol ORDER BY ts);

在结果中,没有返回最后一行的 last_bid 值,因为查询指定了 TS_LAST_VALUE,并且在 3:00:04 时间片之后没有要插值的任何数据点。

     slice_time      | symbol | last_bid
---------------------+--------+----------
 2009-01-01 03:00:00 | XYZ    |     10.2
 2009-01-01 03:00:02 | XYZ    |     10.4
 2009-01-01 03:00:04 | XYZ    |
(3 rows)

使用多个时序聚合函数

同一个查询中可以使用多个时序聚合函数。按照 TIMESERIES 子句中的定义,它们共享同一个空白填充策略;然而,每个时序聚合函数可以指定自己的插值策略。在以下示例中,有两个常数插值和一个线性插值方案,但这三个函数全部使用三秒时间片。

=> SELECT slice_time, symbol,
       TS_FIRST_VALUE(bid, 'const') fv_c,
       TS_FIRST_VALUE(bid, 'linear') fv_l,
       TS_LAST_VALUE(bid, 'const') lv_c
   FROM TickStore
   TIMESERIES slice_time AS '3 seconds' OVER(PARTITION BY symbol ORDER BY ts);

在以下输出中,比较了原始输出与多个时序聚合函数返回的输出。

使用分析 LAST_VALUE 函数

以下是使用 LAST_VALUE() 的示例,这样您可以了解此函数与 GFI 语法之间的区别。

=> SELECT *, LAST_VALUE(bid) OVER(PARTITION by symbol ORDER BY ts)
   AS "last bid" FROM TickStore;

对于输出值,没有执行任何空白填充和内插。

         ts          | symbol | bid  | last bid
---------------------+--------+------+----------
 2009-01-01 03:00:00 | XYZ    |   10 |       10
 2009-01-01 03:00:05 | XYZ    | 10.5 |     10.5
(2 rows)

使用 slice_time

在 TIMESERIES 查询中,您无法在 WHERE 子句中使用 slice_time 列,因为 WHERE 子句的评估先于 TIMESERIES 子句,并且直到评估 TIMESERIES 之后才会生成 slice_time 列。例如,Vertica 不支持以下查询:

=> SELECT symbol, slice_time, TS_FIRST_VALUE(bid IGNORE NULLS) AS fv
   FROM TickStore
   WHERE slice_time = '2009-01-01 03:00:00'
   TIMESERIES slice_time as '2 seconds' OVER (PARTITION BY symbol ORDER BY ts);
ERROR:  Time Series timestamp alias/Time Series Aggregate Functions not allowed in WHERE clause

但是,您可以编写子查询,将 slice_time 中的谓词放在外部查询中:

=> SELECT * FROM (
      SELECT symbol, slice_time,
        TS_FIRST_VALUE(bid IGNORE NULLS) AS fv
      FROM TickStore
      TIMESERIES slice_time AS '2 seconds'
      OVER (PARTITION BY symbol ORDER BY ts) ) sq
   WHERE slice_time = '2009-01-01 03:00:00';
 symbol |     slice_time      | fv
--------+---------------------+----
 XYZ    | 2009-01-01 03:00:00 | 10
(1 row)

创建稠密时间序列

借助 TIMESERIES 子句,您可以方便地创建稠密时间序列,以便与实际数据一起用于外部联接。结果将表示所有时间点,而不只是存在数据的时间点。

随后的示例将使用空白填充和插值 (GFI)中所述的相同 TickStore 架构,并添加一个新内部表以用于创建联接:

=> CREATE TABLE inner_table (
       ts TIMESTAMP,
       bid FLOAT
   );
=> CREATE PROJECTION inner_p (ts, bid) as SELECT * FROM inner_table
   ORDER BY ts, bid UNSEGMENTED ALL NODES;
=> INSERT INTO inner_table VALUES ('2009-01-01 03:00:02', 1);
=> INSERT INTO inner_table VALUES ('2009-01-01 03:00:04', 2);
=> COMMIT;

为了返回所有时间点,可以在相关时间帧的开始和结束范围之间创建一个简单的并集。在本例中以 1 秒为时间段:

=> SELECT ts FROM (
     SELECT '2009-01-01 03:00:00'::TIMESTAMP AS time FROM TickStore
     UNION
     SELECT '2009-01-01 03:00:05'::TIMESTAMP FROM TickStore) t
   TIMESERIES ts AS '1 seconds' OVER(ORDER BY time);
         ts
---------------------
 2009-01-01 03:00:00
 2009-01-01 03:00:01
 2009-01-01 03:00:02
 2009-01-01 03:00:03
 2009-01-01 03:00:04
 2009-01-01 03:00:05
(6 rows)

下一个查询将以 500 毫秒为时间段在时间帧的开始和结束范围之间创建一个并集:

=> SELECT ts FROM (
     SELECT '2009-01-01 03:00:00'::TIMESTAMP AS time
     FROM TickStore
     UNION
     SELECT '2009-01-01 03:00:05'::TIMESTAMP FROM TickStore) t
   TIMESERIES ts AS '500 milliseconds' OVER(ORDER BY time);
          ts
-----------------------
 2009-01-01 03:00:00
 2009-01-01 03:00:00.5
 2009-01-01 03:00:01
 2009-01-01 03:00:01.5
 2009-01-01 03:00:02
 2009-01-01 03:00:02.5
 2009-01-01 03:00:03
 2009-01-01 03:00:03.5
 2009-01-01 03:00:04
 2009-01-01 03:00:04.5
 2009-01-01 03:00:05
(11 rows)

以下查询以 1 秒为时间段在相关时间帧的开始和结束范围之间创建一个并集:

=> SELECT * FROM (
     SELECT ts FROM (
       SELECT '2009-01-01 03:00:00'::timestamp AS time FROM TickStore
       UNION
       SELECT '2009-01-01 03:00:05'::timestamp FROM TickStore) t
       TIMESERIES ts AS '1 seconds' OVER(ORDER BY time) ) AS outer_table
   LEFT OUTER JOIN inner_table ON outer_table.ts = inner_table.ts;

此并集将从左联接表返回一组与右联接表中记录相匹配的完整记录。如果查询没有找到任何匹配项,它会使用 NULL 值扩展右侧列:

         ts          |         ts          | bid
---------------------+---------------------+-----
 2009-01-01 03:00:00 |                     |
 2009-01-01 03:00:01 |                     |
 2009-01-01 03:00:02 | 2009-01-01 03:00:02 |   1
 2009-01-01 03:00:03 |                     |
 2009-01-01 03:00:04 | 2009-01-01 03:00:04 |   2
 2009-01-01 03:00:05 |                     |
(6 rows)

2 - 时序数据中的 NULL 值

NULL 值是空白填充和插值 (GFI) 计算的不常见输入。当存在 NULL 值时,您可以使用时间序列聚合 (TSA) 函数 TS_FIRST_VALUETS_LAST_VALUEIGNORE NULLS 影响插值的输出。系统处理 TSA 函数的方式与处理其对应的分析函数 FIRST_VALUELAST_VALUE 类似,因此,如果时间戳本身为 NULL,Vertica 将先筛选掉这些行,然后再执行空白填充和插值。

包含 NULL 值时的常数插值

图 1 显示了四个输入行中的输入都不包含 NULL 值时的默认(常数)插值结果。

图 2 显示了相同的输入行,但新增的一个输入记录的出价值为 NULL 且其对应的时间戳值为 3:00:03。

对于常数插值,从 3:00:03 开始的出价值将一直为 NULL,直到时序中出现下一个非 NULL 出价值。在图 2 中,由于存在 NULL 行,以阴影区域表示的时间间隔中的内插出价值为 NULL。如果在以 3:00:02 为起点的时间片中,使用常数插值对 TS_FIRST_VALUE(bid) 进行评估,其输出为非 NULL。但是,TS_FIRST_VALUE(bid) 在下一个时间片中仍会输出 NULL。如果 3:00:02 时间片的最后一个值为 NULL,则下一个时间片 (3:00:04) 的第一个值为 NULL。但是,如果将 TSA 函数与 IGNORE NULLS 结合使用,则在 3:00:04 时的值将与在 3:00:02 时的值相同。

为说明这一情况,请在 TickStore 表的 03:00:03 处插入一个包含 NULL 出价值的新行,此时 Vertica 将为 03:00:02 记录输出一个包含 NULL 值的行,但是没有为 03:00:03 输入输出一个行。

=> INSERT INTO tickstore VALUES('2009-01-01 03:00:03', 'XYZ', NULL);
=> SELECT slice_time, symbol, TS_LAST_VALUE(bid) AS last_bid FROM TickStore
-> TIMESERIES slice_time AS '2 seconds' OVER (PARTITION BY symbol ORDER BY ts);
     slice_time      | symbol | last_bid
---------------------+--------+----------
 2009-01-01 03:00:00 | XYZ    |       10
 2009-01-01 03:00:02 | XYZ    |
 2009-01-01 03:00:04 | XYZ    |     10.5
(3 rows)

如果指定 IGNORE NULLS,Vertica 将使用常数插值方案填充缺失的数据点。在此,03:00:02 时的出价会内插到出价的上一个已知输入记录,即 03:00:00 时的 $10:

=> SELECT slice_time, symbol, TS_LAST_VALUE(bid IGNORE NULLS) AS last_bid FROM TickStore
     TIMESERIES slice_time AS '2 seconds' OVER (PARTITION BY symbol ORDER BY ts);
     slice_time      | symbol | last_bid
---------------------+--------+----------
 2009-01-01 03:00:00 | XYZ    |       10
 2009-01-01 03:00:02 | XYZ    |       10
 2009-01-01 03:00:04 | XYZ    |     10.5
(3 rows)

此时,假如插入一个其时间戳列包含 NULL 值的行,Vertica 将在空白填充和插值开始之前筛选掉此行。

=> INSERT INTO tickstore VALUES(NULL, 'XYZ', 11.2);
=> SELECT slice_time, symbol, TS_LAST_VALUE(bid) AS last_bid FROM TickStore
     TIMESERIES slice_time AS '2 seconds' OVER (PARTITION BY symbol ORDER BY ts);

请注意,11.2 出价行没有对应的输出。

     slice_time      | symbol | last_bid
---------------------+--------+----------
 2009-01-01 03:00:00 | XYZ    |       10
 2009-01-01 03:00:02 | XYZ    |
 2009-01-01 03:00:04 | XYZ    |     10.5
(3 rows)

包含 NULL 值时的线性插值

对于线性插值,在图 3 中以阴影区表示的时间间隔中,内插出价值为 NULL。

如果在 3:00:03 时的输入值为 NULL,Vertica 无法以线性方式在此时间点附近内插出价值。

Vertica 在时间片的任意一侧中选择最近的非 NULL 值,并使用此值。例如,如果您使用线性插值方案,但未指定 IGNORE NULLS,并且您的数据包含一个实值和一个 NULL 值,则结果为 NULL。如果任意一侧中有值为 NULL,则结果为 NULL。因此,如果在以 3:00:02 为起始的时间片中,使用线性插值对 TS_FIRST_VALUE(bid) 进行评估,其输出为 NULL。 TS_FIRST_VALUE(bid) 在下一个时间片中仍为 NULL。

=> SELECT slice_time, symbol, TS_FIRST_VALUE(bid, 'linear') AS fv_l FROM TickStore
     TIMESERIES slice_time AS '2 seconds' OVER (PARTITION BY symbol ORDER BY ts);
     slice_time      | symbol | fv_l
---------------------+--------+------
 2009-01-01 03:00:00 | XYZ    |   10
 2009-01-01 03:00:02 | XYZ    |
 2009-01-01 03:00:04 | XYZ    |
(3 rows)