2015-03-29 8 views
6

Ho una tabella in cui i messaggi vengono memorizzati nel momento in cui si verificano. Di solito c'è un messaggio 'A' e talvolta gli A sono separati da un singolo messaggio 'B'. Ora voglio raggruppare i valori in modo da essere in grado di analizzarli, ad esempio trovando la più lunga 'striscia A' o la distribuzione di 'A'-streaks.Raggruppamento e conteggio delle righe per valore fino a quando non cambia

Ho già provato una query COUNT-OVER ma continuo a contare per ogni messaggio.

SELECT message, COUNT(*) OVER (ORDER BY Timestamp RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) 

Questo è il mio dati esempio:

Timestamp  Message 
20150329 00:00 A 
20150329 00:01 A 
20150329 00:02 B 
20150329 00:03 A 
20150329 00:04 A 
20150329 00:05 A 
20150329 00:06 B 

io voglio seguente output

Message COUNT 
A   2 
B   1 
A   3 
B   1 
+0

Quindi ci sono due colonne coinvolte qui, messaggio e data/ora? – jarlh

+0

C'è una colonna timestamp ma i dati vengono comunque memorizzati in ordine. – dwonisch

+0

Considera sempre i dati come non ordinati! (Anche se sembra essere ordinato in questo momento, potrebbe cambiare in futuro.) Mai scrivere mai query a seconda di un ordine implicito !!! – jarlh

risposta

7

che era interessante :)

;WITH cte as (
SELECT Messages.Message, Timestamp, 
ROW_NUMBER() OVER(PARTITION BY Message ORDER BY Timestamp) AS gn, 
ROW_NUMBER() OVER (ORDER BY Timestamp) AS rn 
FROM Messages 
), cte2 AS (
SELECT Message, Timestamp, gn, rn, gn - rn as gb 
FROM cte 
), cte3 AS (
SELECT Message, MIN(Timestamp) As Ts, COUNT(1) as Cnt 
FROM cte2 
GROUP BY Message, gb) 
SELECT Message, Cnt FROM cte3 
ORDER BY Ts 

Ecco il set di risultati:

Message Cnt 
    A 2 
    B 1 
    A 3 
    B 1 

La query può essere più breve ma la post in questo modo in modo da poter vedere cosa sta succedendo. Il risultato è esattamente come richiesto. Questa è la parte più importante gn - rn l'idea è di numerare le righe in ogni partizione e allo stesso tempo di numerare le righe nell'intero set, quindi se si sottrae l'una dall'altra si otterrà il 'rank' di ogni gruppo.

;WITH cte as (
SELECT Messages.Message, Timestamp, 
ROW_NUMBER() OVER(PARTITION BY Message ORDER BY Timestamp) AS gn, 
ROW_NUMBER() OVER (ORDER BY Timestamp) AS rn 
FROM Messages 
), cte2 AS (
SELECT Message, Timestamp, gn, rn, gn - rn as gb 
FROM cte 
) 
SELECT * FROM cte2 

Message Timestamp   gn rn gb 
A 2015-03-29 00:00:00.000 1 1 0 
A 2015-03-29 00:01:00.000 2 2 0 
B 2015-03-29 00:02:00.000 1 3 -2 
A 2015-03-29 00:03:00.000 3 4 -1 
A 2015-03-29 00:04:00.000 4 5 -1 
A 2015-03-29 00:05:00.000 5 6 -1 
B 2015-03-29 00:06:00.000 2 7 -5 
+0

È rn dal primo CTE realmente disponibile nel secondo CTE? – Mihai

+0

Funziona lentamente ma perfettamente (ma ho un sacco di tempo per quella query). Quindi sì, è disponibile. – dwonisch

+0

@Mihai scusa non capisco la tua domanda. –

3

Qui è un po 'la soluzione più piccola:

DECLARE @t TABLE (d DATE, m CHAR(1)) 

INSERT INTO @t 
VALUES ('20150301', 'A'), 
     ('20150302', 'A'), 
     ('20150303', 'B'), 
     ('20150304', 'A'), 
     ('20150305', 'A'), 
     ('20150306', 'A'), 
     ('20150307', 'B'); 

WITH 
c1 AS(SELECT d, m, IIF(LAG(m, 1, m) OVER(ORDER BY d) = m, 0, 1) AS n FROM @t), 
c2 AS(SELECT m, SUM(n) OVER(ORDER BY d) AS n FROM c1) 
    SELECT m, COUNT(*) AS c 
    FROM c2 
    GROUP BY m, n 

uscita:

m c 
A 2 
B 1 
A 3 
B 1 

L'idea è quella di ottenere il valore 1 a righe in cui viene modificato un messaggio:

2015-03-01 A 0 
2015-03-02 A 0 
2015-03-03 B 1 
2015-03-04 A 1 
2015-03-05 A 0 
2015-03-06 A 0 
2015-03-07 B 1 

Th e secondo passo è solo somma del valore di riga corrente + tutti i valori precedenti:

2015-03-01 A 0 
2015-03-02 A 0 
2015-03-03 B 1 
2015-03-04 A 2 
2015-03-05 A 2 
2015-03-06 A 2 
2015-03-07 B 3 

questo modo si ottiene il raggruppamento scene di colonna messaggio e colonna calcolata.