2015-07-29 82 views
5

Questo è un esercizio abbastanza nuovo per me, ma ho bisogno di trovare un modo per identificare sequenze di pattern all'interno di un tavolo. Così, per esempio, consente di dire che ho una semplice tabella simile al seguente:Come si identificano le sequenze dei pattern di registrazione nei record usando TSQL?

enter image description here

Ora quello che vorrei fare è identificare e raggruppare tutti i record che hanno il modello sequenziato di valori 5, 9 e 6 presentandoli in una query. Come realizzeresti questo compito usando T-SQL?

Il risultato dovrebbe essere simile a questo:

enter image description here

Ho guardato per alcuni potenziali esempi di come questo potrebbe essere realizzato, ma non ho trovato nulla che aiuta veramente.

+0

Puoi aggiungere un piccolo testo sul modello: quanto può essere grande? –

+0

Quindi si dovrebbe avere un modello fornito come 'declare @Pattern come tabella (Seq Int, Val Int); inserire in valori @Pattern (Seq, Val) (1, 5), (2, 9), (3, 6); '? Sembra un join con alcuni fantastici numeri, raggruppamenti e conteggi di Row_Number.Una strana variante "lacune e isole" problema a quel punto. – HABO

+0

@BogdanBogdanov Il modello non sarà mai più di 3 numeri sequenziali. In questo caso 5,9 e 6. Idealmente, la soluzione dovrebbe essere in grado di accogliere una sequenza più grande, se necessario, con alcune modifiche. Il valore è di tipo intero. Spero di aver interpretato correttamente la tua domanda. In caso contrario, fatemelo sapere – Mark

risposta

7

È possibile utilizzare la seguente query avvolto in un CTE al fine di assegnare i numeri di sequenza ai valori contenuti nella sequenza:

;WITH Seq AS (
    SELECT v, ROW_NUMBER() OVER(ORDER BY k) AS rn 
    FROM (VALUES(1, 5), (2, 9), (3, 6)) x(k,v) 
) 

uscita:

v rn 
------- 
5 1 
9 2 
6 3 

Utilizzando quanto sopra CTE puoi identificare le isole, cioè le sezioni di righe sequenziali contenenti l'intera sequenza:

;WITH Seq AS (
    SELECT v, ROW_NUMBER() OVER(ORDER BY k) AS rn 
    FROM (VALUES(1, 5), (2, 9), (3, 6)) x(k,v) 
), Grp AS (
SELECT [Key], [Value], 
     ROW_NUMBER() OVER (ORDER BY [Key]) - rn AS grp    
FROM mytable AS m 
LEFT JOIN Seq AS s ON m.Value = s.v 
) 
SELECT * 
FROM Grp 

uscita:

Key Value grp 
    ----------------- 
    1 5  0 
    2 9  0 
    3 6  0 
    6 5  3 
    7 9  3 
    8 6  3 

grp campo consente di identificare esattamente queste isole.

Tutto quello che dovete fare ora è quello di filtrare appena fuori gruppi parziali:

;WITH Seq AS (
    SELECT v, ROW_NUMBER() OVER(ORDER BY k) AS rn 
    FROM (VALUES(1, 5), (2, 9), (3, 6)) x(k,v) 
), Grp AS (
SELECT [Key], [Value], 
     ROW_NUMBER() OVER (ORDER BY [Key]) - rn AS grp    
FROM mytable AS m 
LEFT JOIN Seq AS s ON m.Value = s.v 
) 
SELECT g1.[Key], g1.[Value] 
FROM Grp AS g1 
INNER JOIN (
    SELECT grp 
    FROM Grp 
    GROUP BY grp 
    HAVING COUNT(*) = 3) AS g2 
ON g1.grp = g2.grp 

Demo here

Nota: La versione iniziale di questa risposta utilizzato un INNER JOIN a Seq. Ciò non funzionerà se la tabella contiene valori come 5, 42, 9, 6, poiché 42 verrà filtrato dallo INNER JOIN e questa sequenza identificata erroneamente come valida. Il credito va a @HABO per questa modifica.

+0

Il tuo primo 'inner join 'non farà cadere alcun valore' mytable' che non corrisponda affatto al pattern, ignorando efficacemente i valori non abbinati piuttosto che fallire il pattern match? – HABO

+0

@HABO Sì, il primo 'INNER JOIN' fa esattamente questo, cioè filtra tutti i valori non corrispondenti, come' 8', '3'. –

+1

Se si usa 'LEFT OUTER JOIN' a' Seq' quando si forma 'Grp', allora si avranno più righe in un'isola se, ad esempio,' mytable' contiene 5, 42, 9, 6. Il controllo finale di ' COUNT' eliminerebbe quel gruppo come ineguagliato. – HABO

1

Non molto ottimizzato, ma credo che la risposta esatta:

CREATE TABLE pattern (
    rowID INT IDENTITY(1,1) PRIMARY KEY, 
    rowValue INT NOT NULL 
); 

INSERT INTO pattern (rowValue) VALUES (5); 
INSERT INTO pattern (rowValue) VALUES (9); 
INSERT INTO pattern (rowValue) VALUES (6); 

SELECT * FROM pattern; 

SELECT Trg.* FROM Keys Trg 
INNER JOIN pattern Pt ON (Trg.fValue = Pt.rowValue) 
INNER JOIN (
    SELECT K.fKey - P.rowID AS X, COUNT(*) AS Xc FROM Keys K 
     LEFT JOIN pattern P ON (K.fValue = P.rowValue) 
    WHERE 
     (P.rowID IS NOT NULL) 
    GROUP BY K.fKey - P.rowID 
    HAVING COUNT(*) = (SELECT COUNT(*) FROM pattern) 
) Z ON (Trg.fKey - Pt.rowID = Z.X); 

Io uso un tavolo per il modello di unirsi al tavolo principale. Calcolo la differenza tra lo Key e il modello Key e mostro solo le righe corrispondenti alla differenza (e il numero di righe per la corrispondenza delle righe corrispondenti all'interno della tabella del modello).

+0

Ho intenzione di optare per la prima risposta, credo che la seconda risposta potrebbe essere una soluzione molto valida, ma in termini di un approccio molto semplicistico ed elegante (soprattutto per me) la soluzione Giorgos sembra quello Sto cercando. Bogdan, ti darò un voto per l'utilità della tua risposta. Grazie! – Mark

+0

10x @ Mark. Sono d'accordo con te che la risposta 1 è molto meglio (l'ho votata :)). –