【问题标题】:SQL/Teradata: How to return specific values and their preceding rows?SQL/Teradata:如何返回特定值及其前面的行?
【发布时间】:2017-09-21 19:09:27
【问题描述】:

我有一个无法解决的 SQL (Teradata) 问题。我知道答案可能比我想象的要简单得多。

我有一组这样的代码:

ID      timestamp         location      event_type
1111    20160601-0112     Detroit       Event A
1111    20160602-0954     Brooklyn      Event B
1111    20160602-1123     Brooklyn      Event A
1112    20160912-1420     Minneapolis   Event B
1113    20161123-1742     New Orleans   Event A
1113    20161124-1841     New Orleans   Event A
1113    20161124-2100     New Orleans   Event B
1114    20170201-0959     Detroit       Event A
1114    20170201-2350     Detroit       Event A

以下是我需要退货的条件:

我想返回每个 ID 的第一个事件 B,以及在该事件 B 之前发生的最近的事件 A(基于时间戳)。因此,对于上面的数据集,我会得到:

ID      timestamp         location      event_type
1111    20160601-0112     Detroit       Event A
1111    20160602-0954     Brooklyn      Event B
1113    20161124-1841     New Orleans   Event A
1113    20161124-2100     New Orleans   Event B

没有返回 1111 的第三条记录,因为它发生在事件 B 之后。没有返回 ID 1112,因为它前面没有事件 A。没有返回 1113 的第一条记录,因为它之后有更接近的事件 A(到事件 B)。 1114 没有被返回,因为没有事件 B。

我已经为此工作了几个小时,以至于我不再清楚地接近它......任何帮助将不胜感激!

【问题讨论】:

  • 每个 ID 可能有 多个 Bs,然后您只想要第一个?
  • 理论上是的,但我可以查询数据并创建一个临时表,其中仅包含第一个事件 B 记录(以及所有事件 A 记录),如果它更容易的话。
  • 为什么 ID 1112 不在列表中?是因为它缺少一个事件A吗?
  • 没错。

标签: sql teradata


【解决方案1】:

鉴于您的示例数据,我认为以下内容应该可以解决问题。

SELECT *
FROM testtable
QUALIFY

    (
        event_type = 'Event A'
        AND
        min(event_type) OVER (PARTITION BY id ORDER BY "timestamp" ROWS BETWEEN 1 FOLLOWING AND 1 FOLLOWING) = 'Event B'
    ) OR
    (
        event_type = 'Event B'
        AND
        max(event_type) OVER (PARTITION BY id ORDER BY "timestamp" ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) = 'Event A'
    )

这里我们使用Window函数来测试结果集中记录前后的记录。我们在 QUALIFY 子句中执行此操作,它的作用类似于 WHERE 子句,但用于窗口函数。

分解此限定语句:

        event_type = 'Event A'
        AND
        min(event_type) OVER (PARTITION BY id ORDER BY "timestamp" ROWS BETWEEN 1 FOLLOWING AND 1 FOLLOWING) = 'Event B'

是说“如果当前记录是“事件 A”并且下一条记录按此 ID 的时间戳排序时是“事件 B”,则允许该记录”。

        event_type = 'Event B'
        AND
        max(event_type) OVER (PARTITION BY id ORDER BY "timestamp" ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) = 'Event A'

是说“如果当前记录是“事件 B”,并且按此 ID 的时间戳排序的前一个记录是“事件 A”,则允许该记录。

可能有必要在 QUALIFY 子句中进行更多创建以捕捉边缘情况,但是一旦你了解了它的工作原理,你就会在其中获得相当多的创意。


例子:

CREATE MULTISET VOLATILE TABLE testtable
(
 id int,
 ts varchar(20),
 location varchar(20),
 event_type varchar(20)

) PRIMARY INDEX (id) ON COMMIT PRESERVE ROWS;

INSERT INTO testtable VALUES (1111,'20160601-0112','Detroit','Event A');
INSERT INTO testtable VALUES (1111,'20160602-0954','Brooklyn','Event B');
INSERT INTO testtable VALUES (1111,'20160602-1123','Brooklyn','Event A');
INSERT INTO testtable VALUES (1112,'20160912-1420','Minneapolis','Event B');
INSERT INTO testtable VALUES (1113,'20161123-1742','New Orleans','Event A');
INSERT INTO testtable VALUES (1113,'20161124-1841','New Orleans','Event A');
INSERT INTO testtable VALUES (1113,'20161124-2100','New Orleans','Event B');
INSERT INTO testtable VALUES (1114,'20170201-0959','Detroit','Event A');
INSERT INTO testtable VALUES (1114,'20170201-2350','Detroit','Event A');


SELECT *
FROM testtable
QUALIFY

    (
        event_type = 'Event A'
        AND
        min(event_type) OVER (PARTITION BY id ORDER BY ts ROWS BETWEEN 1 FOLLOWING AND 1 FOLLOWING) = 'Event B'
    ) OR
    (
        event_type = 'Event B'
        AND
        max(event_type) OVER (PARTITION BY id ORDER BY ts ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) = 'Event A'
    );

+------+---------------+-------------+------------+
|  id  |      ts       |  location   | event_type |
+------+---------------+-------------+------------+
| 1111 | 20160601-0112 | Detroit     | Event A    |
| 1111 | 20160602-0954 | Brooklyn    | Event B    |
| 1113 | 20161124-1841 | New Orleans | Event A    |
| 1113 | 20161124-2100 | New Orleans | Event B    |
+------+---------------+-------------+------------+

【讨论】:

  • 如果每个 ID 有多个 B,这将全部出现,要获得第一个,您可以添加 AND Sum(CASE WHEN event_type = 'Event B' THEN 1 end) Over (PARTITION BY ID ORDER BY ts ROWS BETWEEN Unbounded Preceding AND 1 Following) = 1。这会将1 分配给第一个B 和前一行。
  • 谢谢!这在我的确切数据集上不太适用,但使用您的回复(JNevill 和 dnoeth)并稍作修改,我就能得到我的结果!
【解决方案2】:

这适用于 MS SQL 2012 - 不确定语法是否相同?

;WITH myData AS
    (
        SELECT
            ID,
            min(timestamp) as timestamp,
            location,
            event_type
        FROM
            tableName
        GROUP BY
            ID,
            location,
            event_type
    )
        SELECT
            *
        FROM
            myData
        WHERE
            (
                myData.timestamp < (SELECT top 1 m2.timestamp FROM myData m2 WHERE m2.ID = myData.ID AND m2.event_type = 'Event B' ORDER BY m2.timestamp ASC)
                OR
                myData.event_type = 'Event B'
            )
            AND (SELECT Count(*) FROM myData m2 WHERE m2.ID = myData.ID AND m2.event_type = 'Event A') > 0
        ORDER BY
            myData.timestamp

【讨论】:

  • 感谢您的回复! -- 非常接近工作,但 Teradata 中的子查询不支持“TOP N” -- 子查询中的工作限制也是我一直面临的一个问题。
  • 可以在 ORDER BY 之后将“TOP N”替换为“LIMIT 1”吗?
  • 不幸的是,在 Teradata 的子查询中也不允许使用 ORDER BY。
猜你喜欢
  • 2021-09-10
  • 1970-01-01
  • 1970-01-01
  • 2018-03-26
  • 1970-01-01
  • 1970-01-01
  • 2020-06-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多