除非有明显的差距,否则如何按时间对行进行分组?
How do I group rows by time unless there is a significant gap?
问题域:我(好吧,公司)有一个接受 public 连接的 wifi 网络。我们想知道每个设备与每个接入点 (AP) 保持连接的时间。这被称为 'dwell time'。这个问题很复杂,因为设备可以而且通常会在白天在 AP 之间来回移动,并且经常会不止一次地返回到其中的许多 AP。
我们目前使用 Splunk 作为我们的数据捕获和报告工具,它自动神奇地执行此操作,但我们正在考虑迁移到 AWS,因此需要结合使用 ETL 和 SQL.
我的数据如下所示:
rowID clientMAC apMAC timeSeen
100 1 a 12:01
101 1 a 12:03
102 1 a 12:05
103 1 b 12:10
104 1 b 12:20
105 2 a 12:20
106 2 a 12:22
107 1 a 13:00
108 1 a 13:02
109 1 a 13:06
110 1 a 13:12
我的挑战是报告每个 clientAP+macAP 示例的持续时间,例如,clientMAC=1
连接到 apMAC=a
多长时间。
我不能从最初的 timeSeen
中取出最后的 timeSeen
,因为 clientMAC=1
连接到中间的 apMAC=b
,所以结果将包括时间那个连接也是。
我需要做的简单的英语逻辑是:
对于 clientMAC
和 apMAC
的每个分组,确定所选时间段内的连接持续时间。如果相同组合的行之间存在间隔,比如 15 分钟,则开始新的持续时间计算并关闭旧的。本质上,在给定 apMAC
处看到的给定 clientMAC
的每组应该是单独的 'transaction' 并报告为单行。
因此所需的输出类似于:
clientMAC apMAC Duration
1 a ...
1 b ...
2 a ...
1 a ...
您可以使用 lag()
和累计总和来分配组。因为你提到了 AWS,我将使用与该数据库兼容的语法:
select clientmac, apmac, min(timeseen), max(timeseen)
from (select t.*,
sum( case when prev_timeseen > timeseen - interval '15 minute'
then 0 else 1
end) over (partition by clientmac, apmac order by timeseen) as grouping
from (select t.*,
lag(timeseen) over (partition by clientmac, apmac order by timeseen) as prev_timeseen
from t
) t
) t
group by clientmac, apmac, grouping
order by min(timeseen);
实际上计算时间差取决于数据类型。您可以只减去 MIN()
和 MAX()
值。
一个不使用 LAG() 的版本,因此可以在 SQL 的旧版本上运行(LAG 是 SQL Server 2012 之后的版本),以防万一。我有很多客户仍在使用 SQL Server 2008,并且经常需要适用于旧版本的解决方案,因此其他人可能也需要同样的解决方案!
此示例包括创建一些测试数据,因此您可以看到它的工作原理和结果
-- Create a temp table to hold the test data
CREATE TABLE #TestData
(
rowID INT NOT NULL PRIMARY KEY,
clientMAC INT NOT NULL,
apMAC VARCHAR(1) NOT NULL,
timeSeen DATETIME NOT NULL
)
-- Create some test data
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (100, 1, 'a', '2019-Nov-01 12:01:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (101, 1, 'a', '2019-Nov-01 12:02:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (102, 1, 'a', '2019-Nov-01 12:05:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (103, 1, 'b', '2019-Nov-01 12:10:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (104, 1, 'b', '2019-Nov-01 12:20:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (105, 2, 'a', '2019-Nov-01 12:20:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (106, 2, 'a', '2019-Nov-01 12:22:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (107, 1, 'a', '2019-Nov-01 13:00:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (108, 1, 'a', '2019-Nov-01 13:02:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (109, 1, 'a', '2019-Nov-01 13:06:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (110, 1, 'a', '2019-Nov-01 13:12:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (111, 1, 'a', '2019-Nov-01 14:00:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (112, 1, 'a', '2019-Nov-01 14:12:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (113, 1, 'a', '2019-Nov-01 14:14:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (114, 1, 'a', '2019-Nov-01 14:30:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (115, 1, 'a', '2019-Nov-01 14:35:00')
-- Start of Actual Code
-- Store our maximum allowed gap in minuutes into a variable
DECLARE @MaximumGapMinutes INT = 15
-- Create temp table for primary calculated data
CREATE TABLE #DwellTimes (
rowID INT NOT NULL PRIMARY KEY,
clientMAC INT NOT NULL,
apMAC VARCHAR(1) NOT NULL,
timeSeen DATETIME NOT NULL,
lastSeen DATETIME NOT NULL,
DwellTime INT NOT NULL
)
-- Populate temp table
INSERT INTO #DwellTimes
SELECT *, DateDiff(MINUTE, lastSeen, timeSeen) AS DwellTime
FROM (
SELECT *, IsNull((SELECT TOP 1 timeSeen
FROM #TestData TDInner
WHERE TDInner.clientMac = TDMain.clientMac AND TDInner.apMac = TDMain.apMac
AND TDInner.timeSeen < TDMain.timeSeen
ORDER BY timeSeen DESC
), timeSeen) AS lastSeen
FROM #TestData TDMain
) InnerTable
-- Calculate the Dwell Time for visits, counting gaps longer than @MaximumGapMinutes as a new visit
SELECT Min(timeSeen) AS StartTime, clientMac, apMac,
SUM(CASE WHEN DwellTime > @MaximumGapMinutes THEN 0 ELSE DwellTime END) AS DwellTime
FROM (
SELECT *, (SELECT COUNT(*)
FROM #DwellTimes DSub
WHERE DSub.clientMac = DMain.clientMac AND DSub.apMac = DMain.apMac
AND DSub.timeSeen <= DMain.timeSeen AND DSub.DwellTime > 15) AS GapNumber
FROM #DwellTimes DMain
) InnerTable
GROUP BY clientMac, apMac, GapNumber
ORDER BY StartTime, clientMAC, apMAC, DwellTime
-- Clean up after ourselves
DROP TABLE #DwellTimes
-- End of Actual Code
-- Clean up after ourselves
DROP TABLE #TestData
结果:-
其工作原理的解释。
在实际代码本身中,而不是测试数据准备中,我们做的第一件事是为我们的最大时间间隔声明一个变量,该变量仍被视为同一访问的一部分
-- Store our maximum allowed gap in minuutes into a variable
DECLARE @MaximumGapMinutes INT = 15
然后我们创建一个临时 table 来保存停留时间计算,并填充它
-- Populate temp table
INSERT INTO #DwellTimes
SELECT *, DateDiff(MINUTE, lastSeen, timeSeen) AS DwellTime
FROM (
SELECT *, IsNull((SELECT TOP 1 timeSeen
FROM #TestData TDInner
WHERE TDInner.clientMac = TDMain.clientMac AND TDInner.apMac = TDMain.apMac
AND TDInner.timeSeen < TDMain.timeSeen
ORDER BY timeSeen DESC
), timeSeen) AS lastSeen
FROM #TestData TDMain
) InnerTable
内部select 查找该clientMac 和apMac 的前一次Seen。如果没有看到以前的时间,则它使用当前的 timeSeen(IsNull(subselect, timeSeen) 部分)。
然后,外部select 计算同一客户端Mac 和apMac 的当前timeSeen 和前一个(lastSeen) 之间的Dwell Time。因为如果没有以前的访问,我们将当前的 timeSeen 用作 lastSeen,如果这是第一次访问,则停留时间将为零。
结果存储在#DwellTimes
最后,我们计算实际访问次数和停留时间,将超过最大值的间隔作为新访问。
-- Calculate the Dwell Time for visits, counting gaps longer than @MaximumGapMinutes as a new visit
SELECT Min(timeSeen) AS StartTime, clientMac, apMac,
SUM(CASE WHEN DwellTime > @MaximumGapMinutes THEN 0 ELSE DwellTime END) AS DwellTime
FROM (
SELECT *, (SELECT COUNT(*)
FROM #DwellTimes DSub
WHERE DSub.clientMac = DMain.clientMac AND DSub.apMac = DMain.apMac
AND DSub.timeSeen <= DMain.timeSeen AND DSub.DwellTime > 15) AS GapNumber
FROM #DwellTimes DMain
) InnerTable
GROUP BY clientMac, apMac, GapNumber
ORDER BY StartTime, clientMAC, apMAC, DwellTime
里面的select这里增加了一个字段GapNumber,我们可以把它作为分组字段。这只是对有多少以前的记录超过我们的最大值的计数,包括当前记录。因此,如果当前记录超过最大值,则为新访问的开始。
有间隙数的结果
最后,按 clientMac、apMac 和 GapNumber 分组允许我们使用 DwellTime 的总和作为每次访问的停留时间,前提是如果 GapNumber 大于我们的最大值,我们将其设置为 0,因为这将是访问
SUM(CASE WHEN DwellTime > @MaximumGapMinutes THEN 0 ELSE DwellTime END) AS DwellTime
希望这对某人有用!
问题域:我(好吧,公司)有一个接受 public 连接的 wifi 网络。我们想知道每个设备与每个接入点 (AP) 保持连接的时间。这被称为 'dwell time'。这个问题很复杂,因为设备可以而且通常会在白天在 AP 之间来回移动,并且经常会不止一次地返回到其中的许多 AP。
我们目前使用 Splunk 作为我们的数据捕获和报告工具,它自动神奇地执行此操作,但我们正在考虑迁移到 AWS,因此需要结合使用 ETL 和 SQL.
我的数据如下所示:
rowID clientMAC apMAC timeSeen
100 1 a 12:01
101 1 a 12:03
102 1 a 12:05
103 1 b 12:10
104 1 b 12:20
105 2 a 12:20
106 2 a 12:22
107 1 a 13:00
108 1 a 13:02
109 1 a 13:06
110 1 a 13:12
我的挑战是报告每个 clientAP+macAP 示例的持续时间,例如,clientMAC=1
连接到 apMAC=a
多长时间。
我不能从最初的 timeSeen
中取出最后的 timeSeen
,因为 clientMAC=1
连接到中间的 apMAC=b
,所以结果将包括时间那个连接也是。
我需要做的简单的英语逻辑是:
对于 clientMAC
和 apMAC
的每个分组,确定所选时间段内的连接持续时间。如果相同组合的行之间存在间隔,比如 15 分钟,则开始新的持续时间计算并关闭旧的。本质上,在给定 apMAC
处看到的给定 clientMAC
的每组应该是单独的 'transaction' 并报告为单行。
因此所需的输出类似于:
clientMAC apMAC Duration
1 a ...
1 b ...
2 a ...
1 a ...
您可以使用 lag()
和累计总和来分配组。因为你提到了 AWS,我将使用与该数据库兼容的语法:
select clientmac, apmac, min(timeseen), max(timeseen)
from (select t.*,
sum( case when prev_timeseen > timeseen - interval '15 minute'
then 0 else 1
end) over (partition by clientmac, apmac order by timeseen) as grouping
from (select t.*,
lag(timeseen) over (partition by clientmac, apmac order by timeseen) as prev_timeseen
from t
) t
) t
group by clientmac, apmac, grouping
order by min(timeseen);
实际上计算时间差取决于数据类型。您可以只减去 MIN()
和 MAX()
值。
一个不使用 LAG() 的版本,因此可以在 SQL 的旧版本上运行(LAG 是 SQL Server 2012 之后的版本),以防万一。我有很多客户仍在使用 SQL Server 2008,并且经常需要适用于旧版本的解决方案,因此其他人可能也需要同样的解决方案!
此示例包括创建一些测试数据,因此您可以看到它的工作原理和结果
-- Create a temp table to hold the test data
CREATE TABLE #TestData
(
rowID INT NOT NULL PRIMARY KEY,
clientMAC INT NOT NULL,
apMAC VARCHAR(1) NOT NULL,
timeSeen DATETIME NOT NULL
)
-- Create some test data
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (100, 1, 'a', '2019-Nov-01 12:01:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (101, 1, 'a', '2019-Nov-01 12:02:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (102, 1, 'a', '2019-Nov-01 12:05:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (103, 1, 'b', '2019-Nov-01 12:10:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (104, 1, 'b', '2019-Nov-01 12:20:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (105, 2, 'a', '2019-Nov-01 12:20:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (106, 2, 'a', '2019-Nov-01 12:22:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (107, 1, 'a', '2019-Nov-01 13:00:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (108, 1, 'a', '2019-Nov-01 13:02:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (109, 1, 'a', '2019-Nov-01 13:06:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (110, 1, 'a', '2019-Nov-01 13:12:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (111, 1, 'a', '2019-Nov-01 14:00:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (112, 1, 'a', '2019-Nov-01 14:12:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (113, 1, 'a', '2019-Nov-01 14:14:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (114, 1, 'a', '2019-Nov-01 14:30:00')
INSERT INTO #TestData (rowID, clientMAC, apMAC, timeSeen) VALUES (115, 1, 'a', '2019-Nov-01 14:35:00')
-- Start of Actual Code
-- Store our maximum allowed gap in minuutes into a variable
DECLARE @MaximumGapMinutes INT = 15
-- Create temp table for primary calculated data
CREATE TABLE #DwellTimes (
rowID INT NOT NULL PRIMARY KEY,
clientMAC INT NOT NULL,
apMAC VARCHAR(1) NOT NULL,
timeSeen DATETIME NOT NULL,
lastSeen DATETIME NOT NULL,
DwellTime INT NOT NULL
)
-- Populate temp table
INSERT INTO #DwellTimes
SELECT *, DateDiff(MINUTE, lastSeen, timeSeen) AS DwellTime
FROM (
SELECT *, IsNull((SELECT TOP 1 timeSeen
FROM #TestData TDInner
WHERE TDInner.clientMac = TDMain.clientMac AND TDInner.apMac = TDMain.apMac
AND TDInner.timeSeen < TDMain.timeSeen
ORDER BY timeSeen DESC
), timeSeen) AS lastSeen
FROM #TestData TDMain
) InnerTable
-- Calculate the Dwell Time for visits, counting gaps longer than @MaximumGapMinutes as a new visit
SELECT Min(timeSeen) AS StartTime, clientMac, apMac,
SUM(CASE WHEN DwellTime > @MaximumGapMinutes THEN 0 ELSE DwellTime END) AS DwellTime
FROM (
SELECT *, (SELECT COUNT(*)
FROM #DwellTimes DSub
WHERE DSub.clientMac = DMain.clientMac AND DSub.apMac = DMain.apMac
AND DSub.timeSeen <= DMain.timeSeen AND DSub.DwellTime > 15) AS GapNumber
FROM #DwellTimes DMain
) InnerTable
GROUP BY clientMac, apMac, GapNumber
ORDER BY StartTime, clientMAC, apMAC, DwellTime
-- Clean up after ourselves
DROP TABLE #DwellTimes
-- End of Actual Code
-- Clean up after ourselves
DROP TABLE #TestData
结果:-
其工作原理的解释。
在实际代码本身中,而不是测试数据准备中,我们做的第一件事是为我们的最大时间间隔声明一个变量,该变量仍被视为同一访问的一部分
-- Store our maximum allowed gap in minuutes into a variable
DECLARE @MaximumGapMinutes INT = 15
然后我们创建一个临时 table 来保存停留时间计算,并填充它
-- Populate temp table
INSERT INTO #DwellTimes
SELECT *, DateDiff(MINUTE, lastSeen, timeSeen) AS DwellTime
FROM (
SELECT *, IsNull((SELECT TOP 1 timeSeen
FROM #TestData TDInner
WHERE TDInner.clientMac = TDMain.clientMac AND TDInner.apMac = TDMain.apMac
AND TDInner.timeSeen < TDMain.timeSeen
ORDER BY timeSeen DESC
), timeSeen) AS lastSeen
FROM #TestData TDMain
) InnerTable
内部select 查找该clientMac 和apMac 的前一次Seen。如果没有看到以前的时间,则它使用当前的 timeSeen(IsNull(subselect, timeSeen) 部分)。
然后,外部select 计算同一客户端Mac 和apMac 的当前timeSeen 和前一个(lastSeen) 之间的Dwell Time。因为如果没有以前的访问,我们将当前的 timeSeen 用作 lastSeen,如果这是第一次访问,则停留时间将为零。
结果存储在#DwellTimes
最后,我们计算实际访问次数和停留时间,将超过最大值的间隔作为新访问。
-- Calculate the Dwell Time for visits, counting gaps longer than @MaximumGapMinutes as a new visit
SELECT Min(timeSeen) AS StartTime, clientMac, apMac,
SUM(CASE WHEN DwellTime > @MaximumGapMinutes THEN 0 ELSE DwellTime END) AS DwellTime
FROM (
SELECT *, (SELECT COUNT(*)
FROM #DwellTimes DSub
WHERE DSub.clientMac = DMain.clientMac AND DSub.apMac = DMain.apMac
AND DSub.timeSeen <= DMain.timeSeen AND DSub.DwellTime > 15) AS GapNumber
FROM #DwellTimes DMain
) InnerTable
GROUP BY clientMac, apMac, GapNumber
ORDER BY StartTime, clientMAC, apMAC, DwellTime
里面的select这里增加了一个字段GapNumber,我们可以把它作为分组字段。这只是对有多少以前的记录超过我们的最大值的计数,包括当前记录。因此,如果当前记录超过最大值,则为新访问的开始。
有间隙数的结果
最后,按 clientMac、apMac 和 GapNumber 分组允许我们使用 DwellTime 的总和作为每次访问的停留时间,前提是如果 GapNumber 大于我们的最大值,我们将其设置为 0,因为这将是访问
SUM(CASE WHEN DwellTime > @MaximumGapMinutes THEN 0 ELSE DwellTime END) AS DwellTime
希望这对某人有用!