【问题标题】:yet another date gap-fill SQL puzzle又一个日期填空 SQL 谜题
【发布时间】:2011-12-27 02:20:54
【问题描述】:

不幸的是,我正在使用 Vertica,这使我无法使用 CROSS APPLY。显然 Vertica 中没有 CTE 这样的东西。

这是我得到的:

t:
    day    | id | metric | d_metric
-----------+----+--------+----------
2011-12-01 |  1 | 10     | 10
2011-12-03 |  1 | 12     | 2
2011-12-04 |  1 | 15     | 3

请注意,在第一天,增量等于指标值。 我想填补空白,如下所示:

t_fill:
    day    | id | metric | d_metric
-----------+----+--------+----------
2011-12-01 |  1 | 10     | 10
2011-12-02 |  1 | 10     | 0 -- a delta of 0
2011-12-03 |  1 | 12     | 2
2011-12-04 |  1 | 15     | 3

我每天都在想办法,但我真正想要的是一个一次性的解决方案。

我认为我可以使用 LAST_VALUE 获得一些东西,但我无法提出正确的 JOIN 语句,让我能够正确地对每个 id 的日常历史进行分区和排序。

编辑: 假设我有一个这样的表:

calendar:
    day 
------------
2011-01-01
2011-01-02
   ...

可以与连接有关。我的意图是保持 日历 中的日期范围以匹配 t 中的日期范围。

编辑: 关于我正在寻找的内容的更多说明,只是为了具体一点:

在生成 t_fill 时,我想准确涵盖 t 中的日期范围,以及介于两者之间的任何日期。因此,正确的 t_fill 将与 t 在同一日期开始并在同一日期结束。 t_fill 有两个属性:

1) 一旦某个 id 出现在某个日期,它总是会在以后的每个日期都有一行。这是原始问题中隐含的填补空白。

2) 如果某个 id 的行在某个日期之后不再出现,t_fill 解决方案应该从最后一个数据点的日期开始生成具有相同度量值(和 0 增量)的行截至t的结束日期。

解决方案可能会回填较早的日期,直到 t 中日期范围的开始。也就是说,对于出现在 t 中的第一个日期之后的任何 id,t 中的第一个日期和该 id 的第一个日期之间的行将用 metric=0 填充并且 d_metric=0。我不喜欢这种解决方案,因为它对进入系统的每个 id 都有更高的增长因子。但我可以通过仅在新表中选择 metric!=0 和 d_metric!=0 的行来轻松处理它。

【问题讨论】:

  • 我对 Vertica 一无所知,但是,正如他们在 their site 上所说的那样,“Vertica 高级分析的一些功能包括:原生空白填充……”。所以,如果我是你,我可能应该开始朝那个方向调查。
  • 我们过去曾尝试过他们的 GFI 功能,但它有一个非常特殊的倾向,不适合我们这里的需求。
  • 什么具体不能满足您的需求?似乎使用恒定插值来填补空白将是可行的方法。
  • @geoff 我澄清了为什么我认为专注于插值数据点之间的间隙填充在我的 cmets 中是不够的:stackoverflow.com/a/8714804/1116963

标签: sql date fill vertica gaps-in-data


【解决方案1】:

这与 Jonathan Leffler 的提议有关,但使用了老式的低级 SQL(没有花哨的 CTE 或窗口函数或聚合子查询):

SET search_path='tmp'
DROP TABLE ttable CASCADE;
CREATE TABLE ttable
        ( zday date NOT NULL
        , id INTEGER NOT NULL
        , metric INTEGER NOT NULL
        , d_metric INTEGER NOT NULL
        , PRIMARY KEY (id,zday)
        );
INSERT INTO ttable(zday,id,metric,d_metric) VALUES
 ('2011-12-01',1,10,10)
,('2011-12-03',1,12,2)
,('2011-12-04',1,15,3)
        ;

DROP TABLE ctable CASCADE;
CREATE TABLE ctable
        ( zday date NOT NULL
        , PRIMARY KEY (zday)
        );
INSERT INTO ctable(zday) VALUES
 ('2011-12-01')
,('2011-12-02')
,('2011-12-03')
,('2011-12-04')
        ;

CREATE VIEW v_cte AS (
        SELECT t.zday,t.id,t.metric,t.d_metric
        FROM ttable t
        JOIN ctable c ON c.zday = t.zday
        UNION
        SELECT c.zday,t.id,t.metric, 0
        FROM ctable c, ttable t
        WHERE t.zday < c.zday
        AND NOT EXISTS ( SELECT *
                FROM ttable nx
                WHERE nx.id = t.id
                AND nx.zday = c.zday
                )
        AND NOT EXISTS ( SELECT *
                FROM ttable nx
                WHERE nx.id = t.id
                AND nx.zday < c.zday
                AND nx.zday > t.zday
                )
        )
        ;
SELECT * FROM v_cte;

结果:

    zday    | id | metric | d_metric 
------------+----+--------+----------
 2011-12-01 |  1 |     10 |       10
 2011-12-02 |  1 |     10 |        0
 2011-12-03 |  1 |     12 |        2
 2011-12-04 |  1 |     15 |        3
(4 rows)

【讨论】:

  • 我终于有机会实现这个了。我相信它有效,但规模并不大。源表 t 中有大约 5 亿行,所有那些在 NOT EXIST 中的 SELECT 都是一个很大的痛苦。
  • 不要说得太早:not exists 子查询的处理效率通常出奇的高(可能是因为你的构造相当陈旧)。但我不知道您的平台/实现。
  • 我将采用 Vertica 支持提供给我的查询计划。
【解决方案2】:

我不是 Vertica 用户,但如果您不想使用他们对 GAP 填充的原生支持,here 您可以找到更通用的仅限 SQL 的解决方案。

【讨论】:

  • 我看到了那个并试图在开始这个答案之前对其进行调整。我遇到的问题是 LEFT JOIN 不能帮助我填写 id 值。该链接中的表基本上是对单个 id 进行硬编码,因此 LEFT JOIN 仍然可行。
【解决方案3】:

如果您想使用 CTE 之类的东西,那么使用临时表怎么样?本质上,CTE 是特定查询的视图。

根据您的需要,您可以使临时表事务或会话范围。

我仍然很想知道为什么用常量插值填充间隙在这里不起作用。

【讨论】:

  • 我想我会先回答这个问题,作为回答乔纳森问题的第一步。我们关心的日期范围有两种。
  • e(把评论弄糊涂了):第一个是针对我们正在寻求构建的新表的查询的日期范围。第二个是原始指标表 t 中每个 id 的日期范围。 id 的度量历史可以在我们有任何数据的第一天之后开始。有时,它也可以停止。当然,正如我们所见,它之间可能会缺少几天。查询的日期范围可能会完全错过某些 ID。发生这种情况时,在 Vertica 中作为 TIMESERIES 实现的间隙填充不会做任何事情。由于在捕获的日期范围内没有可使用的数据。
【解决方案4】:

考虑到完整的日历表,它是可行的,尽管并非微不足道。如果没有日历表,那就更难了。

您的查询需要适度准确地陈述,这通常是“如何编写查询”的任何问题的一半。我想你正在寻找:

  • 对于日历中的每个日期,在 T(或其他规定范围)中表示的最小和最大日期之间,
  • 对于 T 中表示的每个不同的 ID,
  • 为给定 ID 查找 T 中在该日期或之前的最新记录的指标。

这会为您提供包含指标的完整日期列表。

然后,您需要自行加入该列表的两个副本,日期相隔一天以形成增量。

请注意,如果某些 ID 值未出现在日期范围的开头,它们将不会显示。

有了这个指导,我相信你应该可以开始了。

【讨论】:

  • > 请注意,如果某些 ID 值未出现在日期范围的开头,它们将不会显示。这正是我们希望从 t 创建新表的原因。
  • 问题是,对于一个在开始后 3 天首次出现的 ID,您没有任何工作可做。您希望此类条目为零吗?
  • e:该死。在没有进入 cmets 方面非常糟糕。这种填补空白的目标不仅是填补空白,而且还填充每个 id 的历史记录,从它有数据的最后一行/日期到查询的最后一个日期:stackoverflow.com/questions/8640626/… 如您所见,使用对 t 的简单查询,任何历史在查询开始日期之前结束的 id 都不会显示。这就是使用 Vertica TIMESERIES 解决方案的问题,以及我上面引用的警告(我的解释)的问题。
  • 当然,并不是所有的 id 都出现在 t 中的第一个日期。不过,我无法将我引用的内容与您的程序相提并论 :(。我只是不具备用 SQL 思考的能力。
  • 是的,我肯定需要更精确。让我试试:在生成 t_fill 时,我想复制 t 中的所有可用日期。因此 t_fill 将在同一日期开始并在同一日期结束。 t_fill 有两个属性。 1)一旦某个 id 出现在某个日期,它总是会在以后的每个日期都有一行。这是原始问题中隐含的填补空白。 2) 如果某个 id 的行在某个日期之后不再出现,t_fill 应该愉快地生成从最后一个数据点的日期到结束日期具有相同度量值(和 0 增量)的行t。继续...
猜你喜欢
  • 1970-01-01
  • 2022-01-05
  • 2020-10-11
  • 1970-01-01
  • 1970-01-01
  • 2012-06-22
  • 1970-01-01
  • 1970-01-01
  • 2013-07-01
相关资源
最近更新 更多