SQL:根据值的顺序拉取行
SQL: Pull rows based on sequence of values
我需要根据特定序列中存在的某些值来提取数据行。
这是一个数据示例:
Header
EventId
EventDate
67891882
382
2022-01-21 09:29:50.000
67891882
81
2022-01-21 09:03:23.000
67891882
273
2022-01-21 09:03:51.000
67891882
77
2022-01-21 09:05:58.000
67891882
2
2022-01-21 09:29:48.000
我需要的结果是捕获Header 和EventId=81 的EventDate。进一步的标准包括:
- EventID 81 是“开始”,EventID 77 是“结束”
- 除了 (60, 72, 73, 74, 75, 76, 83, 85, 86, 87, 103, 154, 166, 197, 199) 之外,这两者之间可以存在任意数量的其他事件
因此在上面的示例中,EventID 81 和 EventDate 2022-01-21 09:03:23.000
符合我要提取的行,因为 273 不在异常列表中。
ATTEMPT: 我尝试了以下查询
SELECT *
FROM #Table
WHERE EventDate BETWEEN (SELECT EventDate
FROM #Table
WHERE EventId = 81)
AND (SELECT eventdate
FROM #Table
WHERE EventId = 77)
AND EventId NOT IN (60, 72, 73, 74, 75, 76, 83, 85, 86, 87, 103, 154, 166, 197, 199)
ORDER BY 3
但我立即面临这样一个事实,即我的子查询 return 不止一个结果,所以这行不通(我用它来测试一个单独的 Header # 示例,效果很好).所以现在我不太确定如何进行。我不愿意认为我会被迫使用 CURSOR
,主要是因为我的源数据由 2.66 亿行组成。
我之前也曾尝试使用 LAG()
函数来找到我的“起点”,但是一旦请求开始变得越来越复杂(添加排除列表作为以及在 81 和 77 之间可能有 1 或 40 行的事实。
我该如何处理?下面是一些可以使用的示例数据。 Header 可以被认为是父键,与任意数量的 EventID(代表特定操作)和 EventDate 相关联,事件发生时间:
create table #data (header int, eventid int, eventdate datetime)
insert into #data
values
('62252595', '22', '5/23/2021 12:34:02 PM'),
('62252595', '81', '5/23/2021 12:34:03 PM'),
('62252595', '29', '5/23/2021 12:34:12 PM'),
('62252595', '40', '5/23/2021 12:34:27 PM'),
('62252595', '22', '5/23/2021 12:35:02 PM'),
('62252595', '22', '5/23/2021 12:36:12 PM'),
('62252595', '37', '5/23/2021 12:36:36 PM'),
('62252595', '77', '5/23/2021 12:37:04 PM'),
('62252595', '6', '5/23/2021 12:37:52 PM'),
('63252595', '39', '5/23/2021 12:38:01 PM'),
('63252595', '81', '5/23/2021 12:38:04 PM'),
('63252595', '37', '5/23/2021 12:38:06 PM'),
('63252595', '21', '5/23/2021 12:38:09 PM'),
('63252595', '75', '5/23/2021 12:38:10 PM'),
('63252595', '77', '5/23/2021 12:38:12 PM'),
('64252595', '29', '5/23/2021 12:38:15 PM'),
('64252595', '26', '5/23/2021 12:38:18 PM'),
('64252595', '81', '5/23/2021 12:38:20 PM'),
('64252595', '40', '5/23/2021 12:38:21 PM'),
('64252595', '81', '5/23/2021 12:38:24 PM'),
('64252595', '83', '5/23/2021 12:39:06 PM'),
('64252595', '77', '5/23/2021 12:39:07 PM'),
('65252595', '41', '5/23/2021 12:39:12 PM'),
('65252595', '81', '5/23/2021 12:39:16 PM'),
('65252595', '37', '5/23/2021 12:39:20 PM'),
('65252595', '18', '5/23/2021 12:39:56 PM'),
('65252595', '18', '5/23/2021 12:40:03 PM'),
('65252595', '77', '5/23/2021 12:40:15 PM'),
('65252595', '36', '5/23/2021 12:40:46 PM'),
('65252595', '77', '5/23/2021 12:40:53 PM')
预期结果:
从这个 #Data
table,我希望看到的结果是:
Header
EventId
EventDate
62252595
81
5/23/2021 12:34:03 PM
65252595
81
5/23/2021 12:39:16 PM
Header #'s 63252595 和 64252595 不符合条件,因为在 81 的第一个实例和 77 的第一个实例之间(按 EventDate 按 Header 顺序分区),在 5/23/2021 12:38:10 PM
处存在一个 75 和一个 83分别位于 5/23/2021 12:39:06 PM
(均在排除列表中)。我希望这能消除一些困惑。
编辑: 经过一番思考,我想知道是否可以使用 CASE
表达式来简化它。使用上面 #Data
table 中的示例数据,我编写了这个查询:
select *
from (
select * from (
select *, id=case when EventId = 81 then 1
when EventId = 77 then 2
when EventId in (60, 72, 73, 74, 75, 76, 83, 85, 86, 87, 103, 154, 166, 197, 199) then 5 else 0 end
from #data) a
where id <> 0)b
order by 3
它所做的是过滤掉所有 'allowable' 事件并进行过滤,以便我可以过滤以仅查看未受阻的事件,其中 id
=1 然后是 2。什么我还不确定如何让它只显示 id
=1 的条目和后面的 2.
查看示例数据的实际预期结果会很方便,所以我实际上不知道这是否正确,看起来您只需要计算每个 header 的日期范围:
with h as (
select *,
Min(case when eventid=81 then eventdate end) over(partition by header) Sdate,
Max(case when eventid=77 then eventdate end) over(partition by header) Edate
from #data
)
select header, eventId, EventDate
from h
where eventdate between SDate and EDate
and EventId not in(60, 72, 73, 74, 75, 76, 83, 85, 86, 87, 103, 154, 166, 197, 199)
order by eventdate;
我假设开始事件 (81) 总是从该行开始一个新的“帧”,而结束事件 (77) 总是从下一行开始一个新的帧。
我还假设您只对同时存在开始和结束事件的帧感兴趣,并且该帧不包含异常事件(我将只使用 00作为随机允许事件,199 作为唯一例外事件).
例如...
[81,00,81,00,77,00,81,199,77]
=> frame 0 = [81,00]
=> frame 1 = [81,00,77]
=> frame 2 = [00]
=> frame 3 = [81,199,77]
在该示例中,只有第 2 帧的开始事件会被 return 编辑(其他缺少开始 and/or 结束事件,或包含异常事件)。
WITH
frame_start AS
(
SELECT
*,
CASE
WHEN
81 = eventid
OR
77 = LAG(eventid) OVER (PARTITION BY header ORDER BY eventdate)
THEN
1
ELSE
0
END
AS new_frame
FROM
#data
),
framed AS
(
SELECT
*,
SUM(new_frame) OVER (PARTITION BY header ORDER BY eventdate) AS frame_id
FROM
frame_start
)
SELECT
header, MIN(eventdate)
FROM
framed
GROUP BY
header, frame_id
HAVING
SUM(CASE WHEN eventid IN (81,77) THEN 1 ELSE 0 END) = 2
AND
MAX(CASE WHEN eventid IN (199, etc) THEN 1 ELSE 0 END) = 0
演示:https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=d54493c87629e3e59759ac9d119ec6ad
解释:
第一个 CTE 添加了一个名为 new_frame
的列。
1
= 当前行是 81,或者前一行是 77
0
= 其他一切
这标志着每个新帧的开始(如此处顶部所述)。
下一个 CTE 通过按日期时间顺序对 new_frame 进行累加,为每一帧中的每一行分配一个 id。 id 从 0 开始,然后在每一行上增加该行的 new_frame 值(如果 new_frame=0,保持与前一行相同的 id,如果 new_frame=1 增加id 加 1).
此时 header 的行被分解为帧(如此处顶部所述)。
最终查询按帧分组,然后使用 HAVING 子句过滤结果。第一个检查是框架中具有 81 或 77 的行数总计必须为 2。第二个检查是框架中的任何行都不能有异常事件。如果所有检查都通过,return 帧中的最小时间戳,根据定义,它来自帧中的第一行。
我需要根据特定序列中存在的某些值来提取数据行。
这是一个数据示例:
Header | EventId | EventDate |
---|---|---|
67891882 | 382 | 2022-01-21 09:29:50.000 |
67891882 | 81 | 2022-01-21 09:03:23.000 |
67891882 | 273 | 2022-01-21 09:03:51.000 |
67891882 | 77 | 2022-01-21 09:05:58.000 |
67891882 | 2 | 2022-01-21 09:29:48.000 |
我需要的结果是捕获Header 和EventId=81 的EventDate。进一步的标准包括:
- EventID 81 是“开始”,EventID 77 是“结束”
- 除了 (60, 72, 73, 74, 75, 76, 83, 85, 86, 87, 103, 154, 166, 197, 199) 之外,这两者之间可以存在任意数量的其他事件
因此在上面的示例中,EventID 81 和 EventDate 2022-01-21 09:03:23.000
符合我要提取的行,因为 273 不在异常列表中。
ATTEMPT: 我尝试了以下查询
SELECT *
FROM #Table
WHERE EventDate BETWEEN (SELECT EventDate
FROM #Table
WHERE EventId = 81)
AND (SELECT eventdate
FROM #Table
WHERE EventId = 77)
AND EventId NOT IN (60, 72, 73, 74, 75, 76, 83, 85, 86, 87, 103, 154, 166, 197, 199)
ORDER BY 3
但我立即面临这样一个事实,即我的子查询 return 不止一个结果,所以这行不通(我用它来测试一个单独的 Header # 示例,效果很好).所以现在我不太确定如何进行。我不愿意认为我会被迫使用 CURSOR
,主要是因为我的源数据由 2.66 亿行组成。
我之前也曾尝试使用 LAG()
函数来找到我的“起点”,但是一旦请求开始变得越来越复杂(添加排除列表作为以及在 81 和 77 之间可能有 1 或 40 行的事实。
我该如何处理?下面是一些可以使用的示例数据。 Header 可以被认为是父键,与任意数量的 EventID(代表特定操作)和 EventDate 相关联,事件发生时间:
create table #data (header int, eventid int, eventdate datetime)
insert into #data
values
('62252595', '22', '5/23/2021 12:34:02 PM'),
('62252595', '81', '5/23/2021 12:34:03 PM'),
('62252595', '29', '5/23/2021 12:34:12 PM'),
('62252595', '40', '5/23/2021 12:34:27 PM'),
('62252595', '22', '5/23/2021 12:35:02 PM'),
('62252595', '22', '5/23/2021 12:36:12 PM'),
('62252595', '37', '5/23/2021 12:36:36 PM'),
('62252595', '77', '5/23/2021 12:37:04 PM'),
('62252595', '6', '5/23/2021 12:37:52 PM'),
('63252595', '39', '5/23/2021 12:38:01 PM'),
('63252595', '81', '5/23/2021 12:38:04 PM'),
('63252595', '37', '5/23/2021 12:38:06 PM'),
('63252595', '21', '5/23/2021 12:38:09 PM'),
('63252595', '75', '5/23/2021 12:38:10 PM'),
('63252595', '77', '5/23/2021 12:38:12 PM'),
('64252595', '29', '5/23/2021 12:38:15 PM'),
('64252595', '26', '5/23/2021 12:38:18 PM'),
('64252595', '81', '5/23/2021 12:38:20 PM'),
('64252595', '40', '5/23/2021 12:38:21 PM'),
('64252595', '81', '5/23/2021 12:38:24 PM'),
('64252595', '83', '5/23/2021 12:39:06 PM'),
('64252595', '77', '5/23/2021 12:39:07 PM'),
('65252595', '41', '5/23/2021 12:39:12 PM'),
('65252595', '81', '5/23/2021 12:39:16 PM'),
('65252595', '37', '5/23/2021 12:39:20 PM'),
('65252595', '18', '5/23/2021 12:39:56 PM'),
('65252595', '18', '5/23/2021 12:40:03 PM'),
('65252595', '77', '5/23/2021 12:40:15 PM'),
('65252595', '36', '5/23/2021 12:40:46 PM'),
('65252595', '77', '5/23/2021 12:40:53 PM')
预期结果:
从这个 #Data
table,我希望看到的结果是:
Header | EventId | EventDate |
---|---|---|
62252595 | 81 | 5/23/2021 12:34:03 PM |
65252595 | 81 | 5/23/2021 12:39:16 PM |
Header #'s 63252595 和 64252595 不符合条件,因为在 81 的第一个实例和 77 的第一个实例之间(按 EventDate 按 Header 顺序分区),在 5/23/2021 12:38:10 PM
处存在一个 75 和一个 83分别位于 5/23/2021 12:39:06 PM
(均在排除列表中)。我希望这能消除一些困惑。
编辑: 经过一番思考,我想知道是否可以使用 CASE
表达式来简化它。使用上面 #Data
table 中的示例数据,我编写了这个查询:
select *
from (
select * from (
select *, id=case when EventId = 81 then 1
when EventId = 77 then 2
when EventId in (60, 72, 73, 74, 75, 76, 83, 85, 86, 87, 103, 154, 166, 197, 199) then 5 else 0 end
from #data) a
where id <> 0)b
order by 3
它所做的是过滤掉所有 'allowable' 事件并进行过滤,以便我可以过滤以仅查看未受阻的事件,其中 id
=1 然后是 2。什么我还不确定如何让它只显示 id
=1 的条目和后面的 2.
查看示例数据的实际预期结果会很方便,所以我实际上不知道这是否正确,看起来您只需要计算每个 header 的日期范围:
with h as (
select *,
Min(case when eventid=81 then eventdate end) over(partition by header) Sdate,
Max(case when eventid=77 then eventdate end) over(partition by header) Edate
from #data
)
select header, eventId, EventDate
from h
where eventdate between SDate and EDate
and EventId not in(60, 72, 73, 74, 75, 76, 83, 85, 86, 87, 103, 154, 166, 197, 199)
order by eventdate;
我假设开始事件 (81) 总是从该行开始一个新的“帧”,而结束事件 (77) 总是从下一行开始一个新的帧。
我还假设您只对同时存在开始和结束事件的帧感兴趣,并且该帧不包含异常事件(我将只使用 00作为随机允许事件,199 作为唯一例外事件).
例如...
[81,00,81,00,77,00,81,199,77]
=> frame 0 = [81,00]
=> frame 1 = [81,00,77]
=> frame 2 = [00]
=> frame 3 = [81,199,77]
在该示例中,只有第 2 帧的开始事件会被 return 编辑(其他缺少开始 and/or 结束事件,或包含异常事件)。
WITH
frame_start AS
(
SELECT
*,
CASE
WHEN
81 = eventid
OR
77 = LAG(eventid) OVER (PARTITION BY header ORDER BY eventdate)
THEN
1
ELSE
0
END
AS new_frame
FROM
#data
),
framed AS
(
SELECT
*,
SUM(new_frame) OVER (PARTITION BY header ORDER BY eventdate) AS frame_id
FROM
frame_start
)
SELECT
header, MIN(eventdate)
FROM
framed
GROUP BY
header, frame_id
HAVING
SUM(CASE WHEN eventid IN (81,77) THEN 1 ELSE 0 END) = 2
AND
MAX(CASE WHEN eventid IN (199, etc) THEN 1 ELSE 0 END) = 0
演示:https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=d54493c87629e3e59759ac9d119ec6ad
解释:
第一个 CTE 添加了一个名为 new_frame
的列。
1
= 当前行是 81,或者前一行是 770
= 其他一切
这标志着每个新帧的开始(如此处顶部所述)。
下一个 CTE 通过按日期时间顺序对 new_frame 进行累加,为每一帧中的每一行分配一个 id。 id 从 0 开始,然后在每一行上增加该行的 new_frame 值(如果 new_frame=0,保持与前一行相同的 id,如果 new_frame=1 增加id 加 1).
此时 header 的行被分解为帧(如此处顶部所述)。
最终查询按帧分组,然后使用 HAVING 子句过滤结果。第一个检查是框架中具有 81 或 77 的行数总计必须为 2。第二个检查是框架中的任何行都不能有异常事件。如果所有检查都通过,return 帧中的最小时间戳,根据定义,它来自帧中的第一行。