SQL/Teradata:如何返回特定值及其前面的行?

Posted

技术标签:

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

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

我有一组这样的代码:

ID      timestamp         location      event_type
1111    20160601-0112     Detroit       Event A
1111    20160602-0954     ***lyn      Event B
1111    20160602-1123     ***lyn      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     ***lyn      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吗? 没错。 【参考方案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','***lyn','Event B');
INSERT INTO testtable VALUES (1111,'20160602-1123','***lyn','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 | ***lyn    | 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。

以上是关于SQL/Teradata:如何返回特定值及其前面的行?的主要内容,如果未能解决你的问题,请参考以下文章

sql Teradata Sql Scratchpad - 查看Raw Data.sql

sql Teradata的DENSE_RANK

sql Teradata数据库大小

从地图中获取密钥及其特定值

有效的多个返回值[重复]

如何加入表格以返回每个团队符合特定标准的特定人数?