Dies ist eine Art triviale Aufgabe in meiner C # -Homeworld, aber ich habe es noch nicht in SQL geschafft und würde es vorziehen, sie satzbasiert (ohne Cursor) zu lösen. Eine Ergebnismenge sollte aus einer Abfrage wie dieser stammen.
SELECT SomeId, MyDate,
dbo.udfLastHitRecursive(param1, param2, MyDate) as 'Qualifying'
FROM T
Wie sollte es funktionieren
Ich schicke diese drei Parameter in eine UDF.
Die UDF verwendet intern params, um verknüpfte <= 90 Tage ältere Zeilen aus einer Ansicht abzurufen.
Die UDF durchläuft 'MyDate' und gibt 1 zurück, wenn sie in eine Gesamtberechnung einbezogen werden soll.
Sollte dies nicht der Fall sein, wird 0 zurückgegeben. Wird hier als "qualifizierend" bezeichnet.
Was die UdF machen wird
Listen Sie die Zeilen in Datumsreihenfolge auf. Berechnen Sie die Tage zwischen den Zeilen. Die erste Zeile in der Ergebnismenge ist standardmäßig Treffer = 1. Wenn die Differenz bis zu 90 beträgt, - gehen Sie zur nächsten Zeile über, bis die Summe der Lücken 90 Tage beträgt (der 90. Tag muss vergehen). Setzen Sie bei Erreichen Treffer auf 1 und Lücke auf 0 zurück Es würde auch funktionieren, stattdessen die Zeile aus dem Ergebnis wegzulassen.
|(column by udf, which not work yet)
Date Calc_date MaxDiff | Qualifying
2014-01-01 11:00 2014-01-01 0 | 1
2014-01-03 10:00 2014-01-01 2 | 0
2014-01-04 09:30 2014-01-03 1 | 0
2014-04-01 10:00 2014-01-04 87 | 0
2014-05-01 11:00 2014-04-01 30 | 1
In der obigen Tabelle ist die MaxDiff-Spalte die Lücke zum Datum in der vorherigen Zeile. Das Problem bei meinen bisherigen Versuchen ist, dass ich die vorletzte Zeile im obigen Beispiel nicht ignorieren kann.
[BEARBEITEN]
Gemäß Kommentar füge ich ein Tag hinzu und füge auch das udf ein, das ich gerade zusammengestellt habe. Dies ist jedoch nur ein Platzhalter und liefert kein nützliches Ergebnis.
;WITH cte (someid, otherkey, mydate, cost) AS
(
SELECT someid, otherkey, mydate, cost
FROM dbo.vGetVisits
WHERE someid = @someid AND VisitCode = 3 AND otherkey = @otherkey
AND CONVERT(Date,mydate) = @VisitDate
UNION ALL
SELECT top 1 e.someid, e.otherkey, e.mydate, e.cost
FROM dbo.vGetVisits AS E
WHERE CONVERT(date, e.mydate)
BETWEEN DateAdd(dd,-90,CONVERT(Date,@VisitDate)) AND CONVERT(Date,@VisitDate)
AND e.someid = @someid AND e.VisitCode = 3 AND e.otherkey = @otherkey
AND CONVERT(Date,e.mydate) = @VisitDate
order by e.mydate
)
Ich habe eine andere Abfrage, die ich separat definiere, die eher dem entspricht, was ich brauche, aber blockiert ist, weil ich nicht mit Fensterspalten rechnen kann. Ich habe auch ein ähnliches Modell ausprobiert, das mehr oder weniger die gleiche Ausgabe liefert, nur mit einer LAG () über MyDate, umgeben von einem Datum.
SELECT
t.Mydate, t.VisitCode, t.Cost, t.SomeId, t.otherkey, t.MaxDiff, t.DateDiff
FROM
(
SELECT *,
MaxDiff = LAST_VALUE(Diff.Diff) OVER (
ORDER BY Diff.Mydate ASC
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
FROM
(
SELECT *,
Diff = ISNULL(DATEDIFF(DAY, LAST_VALUE(r.Mydate) OVER (
ORDER BY r.Mydate ASC
ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING),
r.Mydate),0),
DateDiff = ISNULL(LAST_VALUE(r.Mydate) OVER (
ORDER BY r.Mydate ASC
ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING),
r.Mydate)
FROM dbo.vGetVisits AS r
WHERE r.VisitCode = 3 AND r.SomeId = @SomeID AND r.otherkey = @otherkey
) AS Diff
) AS t
WHERE t.VisitCode = 3 AND t.SomeId = @SomeId AND t.otherkey = @otherkey
AND t.Diff <= 90
ORDER BY
t.Mydate ASC;
quelle
Antworten:
Während ich die Frage lese, ist der grundlegende erforderliche rekursive Algorithmus:
Dies ist mit einem rekursiven allgemeinen Tabellenausdruck relativ einfach zu implementieren.
Verwenden Sie beispielsweise die folgenden Beispieldaten (basierend auf der Frage):
Der rekursive Code lautet:
Die Ergebnisse sind:
Mit einem Index
TheDate
als führendem Schlüssel ist der Ausführungsplan sehr effizient:Sie könnten dies in eine Funktion einschließen und direkt gegen die in der Frage erwähnte Ansicht ausführen, aber meine Instinkte sind dagegen. In der Regel ist die Leistung besser, wenn Sie Zeilen aus einer Ansicht in eine temporäre Tabelle auswählen, den entsprechenden Index für die temporäre Tabelle bereitstellen und dann die obige Logik anwenden. Die Details hängen von den Details der Ansicht ab, aber dies ist meine allgemeine Erfahrung.
Der Vollständigkeit halber (und aufgrund der Antwort von ypercube) sollte ich erwähnen, dass meine andere Lösung für diese Art von Problem (bis T-SQL ordnungsgemäß geordnete Mengenfunktionen erhält) ein SQLCLR-Cursor ist ( siehe meine Antwort hier für ein Beispiel der Technik) ). Dies ist wesentlich leistungsfähiger als ein T-SQL-Cursor und eignet sich für Benutzer mit Kenntnissen in .NET-Sprachen und der Fähigkeit, SQLCLR in ihrer Produktionsumgebung auszuführen. In diesem Szenario bietet es möglicherweise nicht viel mehr als die rekursive Lösung, da der Großteil der Kosten von der Art ist, aber es ist erwähnenswert.
quelle
Da es sich um eine SQL Server 2014-Frage handelt, kann ich auch eine nativ kompilierte gespeicherte Prozedurversion eines "Cursors" hinzufügen.
Quelltabelle mit einigen Daten:
Ein Tabellentyp, der der Parameter für die gespeicherte Prozedur ist. Passen Sie das
bucket_count
entsprechend an .Und eine gespeicherte Prozedur, die den Tabellenwertparameter durchläuft und die Zeilen in sammelt
@R
.Code zum Füllen einer speicheroptimierten Tabellenvariablen, die als Parameter für die nativ kompilierte gespeicherte Prozedur verwendet wird, und Aufrufen der Prozedur.
Ergebnis:
Aktualisieren:
Wenn Sie aus irgendeinem Grund nicht jede Zeile in der Tabelle besuchen müssen, können Sie das Gleiche tun wie die Version "Zum nächsten Datum springen", die in der rekursiven CTE von Paul White implementiert ist.
Der Datentyp benötigt keine ID-Spalte und Sie sollten keinen Hash-Index verwenden.
Und die gespeicherte Prozedur verwendet a
select top(1) ..
, um den nächsten Wert zu finden.quelle
T.TheDate >= dateadd(day, 91, @CurDate)
alle ändere, wäre das in Ordnung, oder?TheDate
inTType
zu OPDate
.Eine Lösung, die einen Cursor verwendet.
(zuerst einige benötigte Tabellen und Variablen) :
Der eigentliche Cursor:
Und die Ergebnisse erhalten:
Getestet bei SQLFiddle
quelle
INSERT @cd
nur zu tun , wenn@Qualify=1
(und somit keine 13 Millionen Zeilen einfügen, wenn Sie nicht alle in der Ausgabe benötigen). Und die Lösung hängt von der Suche nach einem Index abTheDate
. Wenn es keine gibt, wird es nicht effizient sein.Ergebnis
Schauen Sie sich auch an, wie die laufende Summe in SQL Server berechnet wird
Update: siehe unten die Ergebnisse der Leistungstests.
Wegen der unterschiedlichen Logik beim Finden von "90 Tage Lücke" können ypercubes und meine Lösungen, wenn sie intakt bleiben, unterschiedliche Ergebnisse zu Paul Whites Lösung zurückgeben. Dies ist auf die Verwendung der Funktionen DATEDIFF und DATEADD zurückzuführen .
Beispielsweise:
gibt '2014-04-01 00: 00: 00.000' zurück, was bedeutet, dass '2014-04-01 01: 00: 00.000' eine Lücke von mehr als 90 Tagen aufweist
aber
Gibt '90' zurück, was bedeutet, dass es sich noch innerhalb der Lücke befindet.
Betrachten Sie ein Beispiel eines Einzelhändlers. In diesem Fall ist der Verkauf eines verderblichen Produkts mit dem Verkaufsdatum "2014-01-01" am "2014-01-01 23: 59: 59: 999" in Ordnung. Der Wert DATEDIFF (DAY, ...) ist in diesem Fall OK.
Ein anderes Beispiel ist ein Patient, der darauf wartet, gesehen zu werden. Für jemanden, der am '2014-01-01 00: 00: 00: 000' ankommt und am '2014-01-01 23: 59: 59: 999' abreist, sind es 0 (null) Tage, wenn DATEDIFF verwendet wird, obwohl das Die tatsächliche Wartezeit betrug fast 24 Stunden. Wieder wartete ein Patient, der am '2014-01-01 23:59:59' eintrifft und am '2014-01-02 00:00:01' weggeht, auf einen Tag, wenn DATEDIFF verwendet wird.
Aber ich schweife ab.
Ich habe DATEDIFF-Lösungen verlassen und sogar die Leistung getestet, aber sie sollten wirklich in ihrer eigenen Liga sein.
Es wurde auch bemerkt, dass es für die großen Datensätze unmöglich ist, Werte am selben Tag zu vermeiden. Wenn wir also sagen, dass 13 Millionen Datensätze 2 Jahre lang Daten umfassen, werden wir für einige Tage mehr als einen Datensatz haben. Diese Datensätze werden in den DATEDIFF-Lösungen von my und ypercube zum frühestmöglichen Zeitpunkt herausgefiltert. Hoffe, dass ypercube nichts dagegen hat.
Die Lösungen wurden in der folgenden Tabelle getestet
mit zwei verschiedenen Clustered-Indizes (in diesem Fall mein Datum):
Die Tabelle wurde wie folgt ausgefüllt
Für einen Multimillionen-Zeilenfall wurde INSERT so geändert, dass 0-20-Minuten-Einträge zufällig hinzugefügt wurden.
Alle Lösungen wurden sorgfältig in den folgenden Code eingepackt
Getestete tatsächliche Codes (in keiner bestimmten Reihenfolge):
DATEDIFF-Lösung von Ypercube ( YPC, DATEDIFF )
DATEADD-Lösung von Ypercube ( YPC, DATEADD )
Paul Whites Lösung ( PW )
Meine DATEADD-Lösung ( PN, DATEADD )
Meine DATEDIFF-Lösung ( PN, DATEDIFF )
Ich verwende SQL Server 2012, entschuldige mich also bei Mikael Eriksson, aber sein Code wird hier nicht getestet. Ich würde immer noch erwarten, dass seine Lösungen mit DATADIFF und DATEADD bei einigen Datensätzen unterschiedliche Werte zurückgeben.
Und die tatsächlichen Ergebnisse sind:
quelle
Ok, habe ich etwas verpasst oder warum würden Sie nicht einfach die Rekursion überspringen und wieder zu sich selbst zurückkehren? Wenn das Datum der Primärschlüssel ist, muss es eindeutig sein und in chronologischer Reihenfolge, wenn Sie den Versatz zur nächsten Zeile berechnen möchten
Ausbeuten
Es sei denn, ich habe etwas Wichtiges verpasst ...
quelle
WHERE [TheDate] > [T1].[TheDate]
um den 90-Tage-Differenzschwellenwert zu berücksichtigen. Trotzdem ist Ihre Ausgabe nicht die gewünschte.