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 可能有 多个B
s,然后您只想要第一个?
理论上是的,但我可以查询数据并创建一个临时表,其中仅包含第一个事件 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:如何返回特定值及其前面的行?的主要内容,如果未能解决你的问题,请参考以下文章