除非有很大的差距,如何按时间对行进行分组?

时间:2019-10-31 13:30:44

标签: sql group-by splunk splunk-query

问题域:我(公司)有一个可以接受公共连接的wifi网络。我们想知道每个设备与每个访问点(AP)保持连接的时间。这就是所谓的“停留时间”。问题很复杂,因为设备白天通常可以在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,因此结果将包括该连接的时间

我需要做的简单的英语逻辑是:

对于clientMACapMAC的每个分组,确定所选时间段内的连接持续时间。如果在相同组合的行之间存在15分钟的间隔,请开始新的工期计算并关闭旧的工期。本质上,在给定的clientMAC上看到的给定apMAC的每组应该是单独的“交易”,并以一行报告。

因此所需的输出如下:

clientMAC apMAC Duration
    1      a      ...
    1      b      ...
    2      a      ...
    1      a      ...

2 个答案:

答案 0 :(得分:0)

您可以使用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()的值即可。

答案 1 :(得分:0)

一个不使用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

结果:-

Results

其工作原理的说明。

在实际代码本身中,而不是测试数据准备中,我们要做的第一件事是声明一个变量,以使我们之间的最大间隔仍然被视为同一访问的一部分

-- Store our maximum allowed gap in minuutes into a variable
DECLARE @MaximumGapMinutes INT = 15

然后我们创建一个临时表来保存驻留时间计算,并将其填充

-- 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

内部选择查找该clientMac和apMac的上次查看时间。如果没有看到以前的时间,则使用当前的timeSeen(IsNull(subselect,timeSeen)部分)。

然后,外部选择针对同一clientMac和apMac计算当前timeSeen与上一个(lastSeen)之间的停留时间。因为如果没有以前的访问,我们将当前的timeSeen用作lastSeen,那么如果这是第一次访问,则停留时间将为零。

结果存储在#DwellTimes

Reults stored in #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

此处的内部选择添加了一个字段GapNumber,我们可以将其用作分组字段。这仅是先前记录超过我们的最大值(包括当前记录)的计数。因此,如果当前记录超过最大值,则是新访问的开始。

带空位号的结果

Gap Number

最后,通过对clientMac,apMac和GapNumber进行分组,我们可以将DwellTime的总和用作每次访问的停留时间,前提是我们将GapNumber设置为0(如果大于最大值),这将是访问的开始

SUM(CASE WHEN DwellTime > @MaximumGapMinutes THEN 0 ELSE DwellTime END) AS DwellTime

Results

希望这对某人有用!