Как сгруппировать строки по времени, если нет существенного разрыва? - PullRequest
1 голос
/ 31 октября 2019

Проблемная область: у меня (ну, бизнес) есть сеть Wi-Fi, которая принимает публичные соединения. Мы хотим знать, как долго каждое устройство остается подключенным к каждой из точек доступа (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 в середине, поэтому результат будет включать в себя и время этого соединения.

Простая английская логика того, что мне нужно сделатьis:

Для каждой группировки clientMAC и apMAC, определить длительность соединения в течение выбранного периода времени. Если есть разрыв, скажем, 15 минут между строками, которые имеют одинаковую комбинацию, запустите новый расчет продолжительности и закройте старый. По сути, каждый набор данного clientMAC, видимый в данном apMAC, должен быть отдельной «транзакцией» и сообщаться в виде одной строки.

Таким образом, желаемый результат выглядит примерно так:

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

Ответы [ 2 ]

0 голосов
/ 02 ноября 2019

Версия, которая не использует 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

Затем мы создаемвременная таблица для хранения расчетов Dwell Time и ее заполнения

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

Внутренний выбор находит предыдущий timeSeen для этого clientMac и apMac. Если предыдущий раз не виден, он использует текущий timeSeen (раздел IsNull (subselect, timeSeen)).

Затем внешний выбор вычисляет время задержки между текущим timeSeen и предыдущим (lastSeen)для одного и того же clientMac и apMac. Поскольку мы используем текущий 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

Надеюсь, это кому-нибудь пригодится!

0 голосов
/ 31 октября 2019

Вы можете назначать группы, используя 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().

...