2017-01-18 36 views
5

Mam dane zawierające daty. Próbuję pogrupować dane według kolejnych dat, jednak daty nie są dokładnie następujące po sobie. Oto przykład:Zapisywanie grup według kolejnych dat, gdy daty nie są dokładnie następujące po sobie

DateColumn    | Value 
------------------------+------- 
2017-01-18 01:12:34.107 | 215426 <- batch no. 1 
2017-01-18 01:12:34.113 | 215636 
2017-01-18 01:12:34.623 | 123516 
2017-01-18 01:12:34.633 | 289926 
2017-01-18 04:58:42.660 | 259063 <- batch no. 2 
2017-01-18 04:58:42.663 | 261830 
2017-01-18 04:58:42.893 | 219835 
2017-01-18 04:58:42.907 | 250165 
2017-01-18 05:18:14.660 | 134253 <- batch no. 3 
2017-01-18 05:18:14.663 | 134257 
2017-01-18 05:18:14.667 | 134372 
2017-01-18 05:18:15.040 | 181679 
2017-01-18 05:18:15.043 | 226368 
2017-01-18 05:18:15.043 | 227070 

Dane są generowane w partiach, a każdy rząd wewnątrz wsadu trwa kilka milisekund do wytworzenia. Próbuję grupie wyniki następująco:

Date1     | Date2     | Count 
------------------------+-------------------------+------ 
2017-01-18 01:12:34.107 | 2017-01-18 01:12:34.633 | 4 
2017-01-18 04:58:42.660 | 2017-01-18 04:58:42.907 | 4 
2017-01-18 05:18:14.660 | 2017-01-18 05:18:15.043 | 6 

Jest bezpiecznie założyć, że jeśli dwa kolejne rzędy są więcej niż 1 minuta od siebie wtedy, że należą do innej partii.

Próbowałem rozwiązań obejmujących funkcję ROW_NUMBER, ale działają one z kolejnymi datami (różnica daty między dwoma wierszami jest stała). Jak osiągnąć pożądany rezultat, gdy różnica jest niewyraźna?


Należy pamiętać, że partia może być dłuższa niż minuta. Na przykład partia może składać się z rzędów rozpoczynających się od 2017-01-01 00:00:00 i kończących się w dniu 2017-01-01 00:05:00, składających się z ~ 3000 wierszy i każdego rzędu oddalonych od siebie o kilkadziesiąt lub sto milisekund. Pewne jest to, że partie są oddalone o co najmniej 1 minutę.

+0

ponownie "czy to jest bezpieczne ..." nie możemy powiedzieć - firma lub inni eksperci domeny będą jedyni, którzy mogą powiedzieć. Jeśli w partiach potrzebujesz identyfikatora dla każdej partii i używasz tego – Mark

+0

, czy dwa ostatnie wiersze mają tę samą wartość datetime lub czy jest to literówka? –

+0

@vkp To dziwne, ale nie literówka. Być może wstawiono dwa wiersze w ciągu 1 milisekundy lub faktyczny czas został zaokrąglony do najbliższej wartości 'datetime'. –

Odpowiedz

8

Spróbuj tego:

select min(t.dateColumn) date1, max(t.dateColumn) date2, count(*) 
from (
    select t.*, sum(val) over (
      order by t.dateColumn 
      ) grp 
    from (
     select t.*, case 
       when datediff(ms, lag(t.dateColumn, 1, t.dateColumn) over (
          order by t.dateColumn 
          ), t.dateColumn) > 60000 
        then 1 
       else 0 
       end val 
     from your_table t 
     ) t 
    ) t 
group by grp; 

Produkuje:

enter image description here

wykorzystuje funkcję analityczną lag() oznaczyć uruchomienie kolejnej partii na podstawie różnicy datecolumn od ostatniego, a następnie użyć analityczne sum() na to, aby utworzyć grupę partii, a następnie pogrupować według niej, aby znaleźć wymagane agregaty.

W grupach mogą występować błędy w klasyfikacji z powodu problemów z zaokrąglaniem z DATETIME. Z MSDN,

wartości daty są zaokrąglone przyrostach, 000, 0,003, lub .007 sekundy, jak to pokazano w poniższej tabeli.

enter image description here


Oto samo zapytanie zapisane w CTE:

WITH cte1(DateColumn, ValueColumn) AS (
    -- Insert your query that returns a datetime column and any other column 
    SELECT 
     SomeDate, 
     SomeValue 
    FROM SomeTable 
    WHERE SomeColumn IS NOT NULL 
), cte2 AS (
    -- This query adds a column called "val" that contains 
    -- 1 when current row date - previous row date > 1 minute 
    -- 0 otherwise 
    SELECT 
     cte1.*, 
     CASE WHEN DATEDIFF(MS, LAG(DateColumn, 1, DateColumn) OVER (ORDER BY DateColumn), DateColumn) > 60000 THEN 1 ELSE 0 END AS val 
    FROM cte1 
), cte3 AS (
    -- This query adds a column called "grp" that numbers 
    -- the groups using running sum over the "val" column 
    SELECT 
     cte2.*, 
     SUM(val) OVER (ORDER BY DateColumn) AS grp 
    FROM cte2 
) 
SELECT 
    MIN(DateColumn) Date1, 
    MAX(DateColumn) Date2, 
    COUNT(ValueColumn) [Count] 
FROM cte3 
GROUP BY grp 
+1

Świetny sposób na tworzenie grup – Aquillo

+0

Czy nie powinno to być 'datediff (second ...)> 60'? – Serg

+0

Rozważ "wybierz datę (mi," 2017-01-18 01:12:20 "," 2017-01-18 01:13:31 "), datediff (drugi," 2017-01-18 01:12:20 ',' 2017-01-18 01:13:31 ') ', rozumiem, że powinny to być różne grupy. – Serg

0

to nie działa, jeśli jesteś porównywania różnic między datami (60.).Ale możesz to wypróbować, jeśli potrzebujesz nagrań, które należą do tej samej minuty X.

SELECT 
    [Date1] = MIN([DateColumn]) 
    ,[Date2] = MAX([DateColumn]) 
    ,[Count] = COUNT([DateColumn]) 
FROM 
    [my_table] 
GROUP BY 
    DATEADD(mi, DATEDIFF(mi, 0, [DateColumn]), 0);