Kann eine T-SQL-Lösung für Lücken und Inseln schneller ausgeführt werden als eine C # -Lösung, die auf dem Client ausgeführt wird?
Um genau zu sein, geben wir einige Testdaten an:
CREATE TABLE dbo.Numbers
(
n INT NOT NULL
PRIMARY KEY
) ;
GO
INSERT INTO dbo.Numbers
( n )
VALUES ( 1 ) ;
GO
DECLARE @i INT ;
SET @i = 0 ;
WHILE @i < 21
BEGIN
INSERT INTO dbo.Numbers
( n
)
SELECT n + POWER(2, @i)
FROM dbo.Numbers ;
SET @i = @i + 1 ;
END ;
GO
CREATE TABLE dbo.Tasks
(
StartedAt SMALLDATETIME NOT NULL ,
FinishedAt SMALLDATETIME NOT NULL ,
CONSTRAINT PK_Tasks PRIMARY KEY ( StartedAt, FinishedAt ) ,
CONSTRAINT UNQ_Tasks UNIQUE ( FinishedAt, StartedAt )
) ;
GO
INSERT INTO dbo.Tasks
( StartedAt ,
FinishedAt
)
SELECT DATEADD(MINUTE, n, '20100101') AS StartedAt ,
DATEADD(MINUTE, n + 2, '20100101') AS FinishedAt
FROM dbo.Numbers
WHERE ( n < 500000
OR n > 500005
)
GO
Dieser erste Satz von Testdaten weist genau eine Lücke auf:
SELECT StartedAt ,
FinishedAt
FROM dbo.Tasks
WHERE StartedAt BETWEEN DATEADD(MINUTE, 499999, '20100101')
AND DATEADD(MINUTE, 500006, '20100101')
Der zweite Satz von Testdaten weist 2M -1 Lücken auf, eine Lücke zwischen jeweils zwei benachbarten Intervallen:
TRUNCATE TABLE dbo.Tasks;
GO
INSERT INTO dbo.Tasks
( StartedAt ,
FinishedAt
)
SELECT DATEADD(MINUTE, 3*n, '20100101') AS StartedAt ,
DATEADD(MINUTE, 3*n + 2, '20100101') AS FinishedAt
FROM dbo.Numbers
WHERE ( n < 500000
OR n > 500005
)
GO
Derzeit verwende ich 2008 R2, aber 2012-Lösungen sind sehr willkommen. Ich habe meine C # -Lösung als Antwort veröffentlicht.
Der folgende C # -Code löst das Problem:
Dieser Code ruft diese gespeicherte Prozedur auf:
Es findet und druckt eine Lücke in 2M-Intervallen in den folgenden Zeiten, warmer Cache:
Es findet und druckt 2M-1 Lücken in 2M Intervallen in den folgenden Zeiten, warmer Cache:
Dies ist eine sehr einfache Lösung - ich habe 10 Minuten gebraucht, um sie zu entwickeln. Ein neuer Hochschulabsolvent kann sich das einfallen lassen. Auf der Datenbankseite ist der Ausführungsplan ein trivialer Zusammenführungs-Join, der nur sehr wenig CPU und Speicher benötigt.
Bearbeiten: Um realistisch zu sein, führe ich Client und Server auf separaten Boxen aus.
quelle
Ich glaube, ich habe die Grenzen meines Wissens in SQL Server in diesem Fall ausgeschöpft ...
Um eine Lücke in SQL Server zu finden (was der C # -Code tut) und sich nicht darum zu kümmern, Lücken zu starten oder zu beenden (vor dem ersten Start oder nach dem letzten Ende), ist die folgende Abfrage (oder Varianten) die am schnellsten konnte ich finden:
Dies funktioniert zwar geringfügig, aber für jeden Start-Ziel-Satz können Sie Start und Ziel als separate Sequenzen behandeln, das Ziel um eins versetzen und Lücken werden angezeigt.
Nehmen Sie z. B. (S1, F1), (S2, F2), (S3, F3) und ordnen Sie wie folgt: {S1, S2, S3, null} und {null, F1, F2, F3}. Vergleichen Sie dann Zeile n mit Zeile n In jedem Satz und in Lücken ist der F-Satzwert kleiner als der S-Satzwert. Ich denke, das Problem ist, dass es in SQL Server keine Möglichkeit gibt, zwei separate Sätze nur in der Reihenfolge der Werte in zu verbinden oder zu vergleichen the set ... daher die Verwendung der row_number-Funktion, um das Zusammenführen nur anhand der Zeilennummer zu ermöglichen ... aber es gibt keine Möglichkeit, SQL Server mitzuteilen, dass diese Werte eindeutig sind (ohne sie in eine Tabelle var mit einem Index einzufügen darauf - was länger dauert - ich habe es versucht), also denke ich, dass der Merge-Join nicht optimal ist? (obwohl schwer zu beweisen, wenn es schneller ist als alles andere, was ich tun könnte)
Mit den LAG / LEAD-Funktionen konnte ich Lösungen finden:
(was ich übrigens nicht garantiere, die Ergebnisse - es scheint zu funktionieren, aber ich denke, dass StartedAt in der Aufgabentabelle in Ordnung ist ... und es war langsamer)
Verwenden der Summenänderung:
(keine Überraschung, auch langsamer)
Ich habe sogar versucht, eine CLR-Aggregatfunktion (um die Summe zu ersetzen - sie war langsamer als die Summe und stützte sich auf row_number (), um die Reihenfolge der Daten beizubehalten) und CLR eine Tabellenwertfunktion (um zwei Ergebnismengen zu öffnen und Werte zu vergleichen, die ausschließlich darauf basieren auf Sequenz) ... und es war auch langsamer. Ich habe mich so oft mit SQL- und CLR-Einschränkungen beschäftigt und viele andere Methoden ausprobiert ...
Und wofür?
Wenn Sie auf demselben Computer ausgeführt werden und sowohl die C # -Daten als auch die SQL-gefilterten Daten in eine Datei (gemäß dem ursprünglichen C # -Code) spucken, sind die Zeiten praktisch gleich ... ungefähr 2 Sekunden für die 1-Lücken-Daten (C # normalerweise schneller ), 8-10 Sekunden für den Multi-Gap-Datensatz (SQL normalerweise schneller).
ANMERKUNG : Verwenden Sie die SQL Server-Entwicklungsumgebung nicht für den Zeitvergleich, da die Anzeige im Raster einige Zeit in Anspruch nimmt. Wie mit SQL 2012, VS2010, .net 4.0-Clientprofil getestet
Ich werde darauf hinweisen, dass beide Lösungen fast die gleiche Sortierung von Daten auf dem SQL Server durchführen, sodass die Serverlast für die Abrufsortierung ähnlich ist, je nachdem, welche Lösung Sie verwenden. Der einzige Unterschied besteht in der Verarbeitung auf dem Client (und nicht auf dem Server). und die Übertragung über das Netzwerk.
Ich weiß nicht, was der Unterschied sein könnte, wenn Sie möglicherweise nach verschiedenen Mitarbeitern partitionieren oder wenn Sie zusätzliche Daten mit den Lückeninformationen benötigen (obwohl mir nicht viel anderes als eine Mitarbeiter-ID einfällt), oder natürlich, wenn Es besteht eine langsame Datenverbindung zwischen dem SQL Server und dem Clientcomputer (oder einem langsamen Client) ... Ich habe auch keine Vergleichszeiten, Probleme mit Konflikten oder Probleme mit CPU / NETWORK für mehrere Benutzer verglichen ... Also ich Ich weiß nicht, welches in diesem Fall eher ein Engpass ist.
Was ich weiß, ist ja, SQL Server ist nicht gut in dieser Art von Set-Vergleichen, und wenn Sie die Abfrage nicht richtig schreiben, werden Sie teuer dafür bezahlen.
Ist es einfacher oder schwieriger als die C # -Version zu schreiben? Ich bin mir nicht ganz sicher, ob die Änderung +/- 1, die Gesamtlösung ausführt, auch nicht ganz intuitiv ist, und ich, aber es ist nicht die erste Lösung, zu der ein durchschnittlicher Absolvent kommen würde ... wenn sie fertig ist, ist es einfach genug, sie zu kopieren, aber Es braucht Einsicht, um überhaupt zu schreiben ... das Gleiche gilt für die SQL-Version. Welches ist schwieriger? Welches ist robuster gegenüber unerwünschten Daten? Welches hat mehr Potenzial für Parallelbetrieb? Ist es wirklich wichtig, wenn der Unterschied im Vergleich zum Programmieraufwand so gering ist?
Eine letzte Anmerkung; Es gibt eine nicht angegebene Einschränkung für die Daten - StartedAt muss kleiner als FinishedAt sein, sonst erhalten Sie schlechte Ergebnisse.
quelle
Hier ist eine Lösung, die in 4 Sekunden läuft.
quelle