2017-01-10 13 views
1

Ich habe ein Problem in SQL Server.Anzahl der Monate in T-SQL zählen

„Whate'er ist gut klar gesagt konzipiert ist, und die Worte es mit Leichtigkeit fließen zu sagen“, Nicolas Boileau

Nun, ich glaube nicht, ich in der Lage sein würde um es klar zu machen, aber ich werde es versuchen! Und ich möchte mich für mein schlechtes Englisch entschuldigen!

Ich habe diese Tabelle bekam:

id ind lvl result date 
1 1 a 3 2017-01-31 
2 1 a 3 2017-02-28 
3 1 a 1 2017-03-31 
4 1 a 1 2017-04-30 
5 1 a 1 2017-05-31 
6 1 b 1 2017-01-31 
7 1 b 3 2017-02-28 
8 1 b 3 2017-03-31 
9 1 b 1 2017-04-30 
10 1 b 1 2017-05-31 
11 2 a 3 2017-01-31 
12 2 a 1 2017-02-28 
13 2 a 3 2017-03-31 
14 2 a 1 2017-04-30 
15 2 a 3 2017-05-31 

Ich möchte die Anzahl der Monate, die Combo zählen {ind, lvl} bleiben im Ergebnis 1 vor, die Anzahl der Monate auf 0 Neuinitialisierung wenn das Ergebnis nicht 1.

klar, ich brauche so etwas zu bekommen:

id ind lvl result date BadResultRemainsFor%Months 
1 1 a 3 2017-01-31 0 
2 1 a 3 2017-02-28 0 
3 1 a 1 2017-03-31 1 
4 1 a 1 2017-04-30 2 
5 1 a 1 2017-05-31 3 
6 1 b 1 2017-01-31 1 
7 1 b 3 2017-02-28 0 
8 1 b 3 2017-03-31 0 
9 1 b 1 2017-04-30 1 
10 1 b 1 2017-05-31 2 
11 2 a 3 2017-01-31 0 
12 2 a 1 2017-02-28 1 
13 2 a 3 2017-03-31 0 
14 2 a 1 2017-04-30 1 
15 2 a 3 2017-05-31 0 

So dass, wenn ich nach der Anzahl der Monate der Suche das Ergebnis war für das Datum 2017-05-31 mit der ID und das lvl ein, ich weiß, dass es 3 Monate gewesen ist.

+2

Sind wir garantiert, dass alle "Datum" -Werte nur Daten sind und immer für den letzten Tag eines jeden Monats? –

+0

@Damien_The_Unbeliever Ja, es wird immer der letzte Tag eines jeden Monats sein! – Flesym

Antwort

2

alle Angenommen, das Datum, die das Ende Tag des Monats:

;WITH tb(id,ind,lvl,result,date) AS(
     select 1,1,'a',3,'2017-01-31' UNION 
     select 2,1,'a',3,'2017-02-28' UNION 
     select 3,1,'a',1,'2017-03-31' UNION 
     select 4,1,'a',1,'2017-04-30' UNION 
     select 5,1,'a',1,'2017-05-31' UNION 
     select 6,1,'b',1,'2017-01-31' UNION 
     select 7,1,'b',3,'2017-02-28' UNION 
     select 8,1,'b',3,'2017-03-31' UNION 
     select 9,1,'b',1,'2017-04-30' UNION 
     select 10,1,'b',1,'2017-05-31' UNION 
     select 11,2,'a',3,'2017-01-31' UNION 
     select 12,2,'a',1,'2017-02-28' UNION 
     select 13,2,'a',3,'2017-03-31' UNION 
     select 14,2,'a',1,'2017-04-30' UNION 
     select 15,2,'a',3,'2017-05-31' 
    ) 
    SELECT t.id,t.ind,t.lvl,t.result,t.date 
    ,CASE WHEN t.isMatched=1 THEN ROW_NUMBER()OVER(PARTITION BY t.ind,t.lvl,t.id-t.rn ORDER BY t.id) ELSE 0 END 
    FROM (
     SELECT t1.*,c.MonthDiff,CASE WHEN c.MonthDiff=t1.result THEN 1 ELSE 0 END AS isMatched 
       ,CASE WHEN c.MonthDiff=t1.result THEN ROW_NUMBER()OVER(PARTITION BY t1.ind,t1.lvl,CASE WHEN c.MonthDiff=t1.result THEN 1 ELSE 0 END ORDER BY t1.id) ELSE null END AS rn 
     FROM tb AS t1 
     LEFT JOIN tb AS t2 ON t1.ind=t2.ind AND t1.lvl=t2.lvl AND t2.id=t1.id-1 
     CROSS APPLY(VALUES(ISNULL(DATEDIFF(MONTH,t2.date,t1.date),1))) c(MonthDiff) 

    ) AS t 
    ORDER BY t.id 
 
id   ind   lvl result  date  
----------- ----------- ---- ----------- ---------- -------------------- 
1   1   a 3   2017-01-31 0 
2   1   a 3   2017-02-28 0 
3   1   a 1   2017-03-31 1 
4   1   a 1   2017-04-30 2 
5   1   a 1   2017-05-31 3 
6   1   b 1   2017-01-31 1 
7   1   b 3   2017-02-28 0 
8   1   b 3   2017-03-31 0 
9   1   b 1   2017-04-30 1 
10   1   b 1   2017-05-31 2 
11   2   a 3   2017-01-31 0 
12   2   a 1   2017-02-28 1 
13   2   a 3   2017-03-31 0 
14   2   a 1   2017-04-30 1 
15   2   a 3   2017-05-31 0 

+0

Danke Nolan Shang für deine Antwort! – Flesym

2

Durch leichtes Anpassen der Eingabedaten und durch geringfügiges Optimieren der Definition der Anforderung wird es recht einfach, die erwarteten Ergebnisse zu erzielen.

Zuerst zwicken wir Ihre date Werte, so dass das einzige, was variiert, der Monat und das Jahr ist - die Tage sind alle gleich. Ich habe gewählt, dass das Hinzufügen von 1 Tag zu jedem Wert . Die Tatsache, dass dies zu Ergebnissen führt, die um einen Monat fortgeschritten sind, spielt hier keine Rolle, da alle Werte in ähnlicher Weise transformiert werden und somit die monatlichen Beziehungen gleich bleiben.

Dann führen wir eine Zahlentabelle ein - hier habe ich angenommen, dass ein kleiner fester Tisch angemessen ist. Wenn es Ihren Anforderungen nicht entspricht, können Sie online problemlos Beispiele finden, um eine große Tabelle mit festen Zahlen zu erstellen, die Sie für diese Abfrage verwenden können.

Und schließlich, wir überarbeiten die Problemstellung. Anstatt zu versuchen, Monate zu zählen, fragen wir stattdessen "Was ist die kleinste Anzahl von Monaten, größer gleich null, dass ich von der aktuellen Zeile zurückgehen muss, um eine Zeile mit einem Nicht-1-Ergebnis zu finden?". Und so produzieren wir diese Anfrage:

declare @t table (id int not null,ind int not null,lvl varchar(13) not null, 
result int not null,date date not null) 
insert into @t(id,ind,lvl,result,date) values 
(1 ,1,'a',3,'20170131'), (2 ,1,'a',3,'20170228'), (3 ,1,'a',1,'20170331'), 
(4 ,1,'a',1,'20170430'), (5 ,1,'a',1,'20170531'), (6 ,1,'b',1,'20170131'), 
(7 ,1,'b',3,'20170228'), (8 ,1,'b',3,'20170331'), (9 ,1,'b',1,'20170430'), 
(10,1,'b',1,'20170531'), (11,2,'a',3,'20170131'), (12,2,'a',1,'20170228'), 
(13,2,'a',3,'20170331'), (14,2,'a',1,'20170430'), (15,2,'a',3,'20170531') 

;With Tweaked as (
    select 
     *, 
     DATEADD(day,1,date) as dp1d 
    from 
     @t 
), Numbers(n) as (
    select 0 union all select 1 union all select 2 union all select 3 union all select 4 
    union all 
    select 5 union all select 6 union all select 7 union all select 8 union all select 9 
) 
select 
    id,  ind,  lvl,  result,  date, 
    COALESCE(
     (select MIN(n) from Numbers n1 
     inner join Tweaked t2 
     on 
      t2.ind = t1.ind and 
      t2.lvl = t1.lvl and 
      t2.dp1d = DATEADD(month,-n,t1.dp1d) 
     where 
      t2.result != 1 
     ), 
     1) as [BadResultRemainsFor%Months] 
from 
    Tweaked t1 

Die COALESCE nur gibt es mit dem Rand Fall zu behandeln, wie für Ihre 1,b Daten, in denen es keine vorherige Zeile mit einem nicht-1 Ergebnis.

Ergebnisse:

id   ind   lvl   result  date  BadResultRemainsFor%Months 
----------- ----------- ------------- ----------- ---------- -------------------------- 
1   1   a    3   2017-01-31 0 
2   1   a    3   2017-02-28 0 
3   1   a    1   2017-03-31 1 
4   1   a    1   2017-04-30 2 
5   1   a    1   2017-05-31 3 
6   1   b    1   2017-01-31 1 
7   1   b    3   2017-02-28 0 
8   1   b    3   2017-03-31 0 
9   1   b    1   2017-04-30 1 
10   1   b    1   2017-05-31 2 
11   2   a    3   2017-01-31 0 
12   2   a    1   2017-02-28 1 
13   2   a    3   2017-03-31 0 
14   2   a    1   2017-04-30 1 
15   2   a    3   2017-05-31 0 

Ein alternativer Weg, um die Anpassung durchzuführen ist, eine einen "Boden" Operation gegen die Tage DATEADD/DATEDIFF Paar zu verwenden, um auszuführen:

DATEADD(month,DATEDIFF(month,0,date),0) as dp1d 

Bei dieser Einstellung werden alle Datumswerte als erste des jeweiligen Monats und nicht als Folgemonat zurückgesetzt.Dies könnte für Sie "natürlicher" sein, oder Sie haben möglicherweise bereits solche Werte in Ihren Originaldaten verfügbar.

+0

Danke Damien_The_Unbeliever für Ihre Antwort! – Flesym

1

Daten Unter der Annahme, im Monat kontinuierlich zu erhöhen, können Sie Fensterfunktion wie so verwenden:

select 
    t.id, ind, lvl, result, dat, 
    case when result = 1 then row_number() over (partition by grp order by id) else 0 end x 
from (
select t.*, 
    dense_rank() over (order by e, result) grp 
from (
select 
    t.*, 
    row_number() over (order by id) - row_number() over (partition by ind, lvl, result order by id) e 
from your_table t 
order by id) t) t; 
+0

Danke GurVfür deine Antwort! – Flesym

Verwandte Themen