Was ist der effizienteste Weg, um Datumsbereiche mit einer solchen Tabellenstruktur abzurufen?
create table SomeDateTable
(
id int identity(1, 1) not null,
StartDate datetime not null,
EndDate datetime not null
)
go
Angenommen, Sie möchten einen Bereich für StartDate
und EndDate
. Mit anderen Worten, wenn StartDate
zwischen @StartDateBegin
Und @StartDateEnd
Liegt und EndDate
zwischen @EndDateBegin
Und @EndDateEnd
, dann mach etwas.
Ich weiß, dass es wahrscheinlich einige Möglichkeiten gibt, dies zu tun, aber was ist am meisten zu empfehlen?
Dies ist im Allgemeinen ein schwer zu lösendes Problem, aber wir können einige Dinge tun, um dem Optimierer bei der Auswahl eines Plans zu helfen. Dieses Skript erstellt eine Tabelle mit 10.000 Zeilen mit einer bekannten pseudozufälligen Verteilung der Zeilen, um Folgendes zu veranschaulichen:
CREATE TABLE dbo.SomeDateTable
(
Id INTEGER IDENTITY(1, 1) PRIMARY KEY NOT NULL,
StartDate DATETIME NOT NULL,
EndDate DATETIME NOT NULL
);
GO
SET STATISTICS XML OFF
SET NOCOUNT ON;
DECLARE
@i INTEGER = 1,
@s FLOAT = Rand(20120104),
@e FLOAT = Rand();
WHILE @i <= 10000
BEGIN
INSERT dbo.SomeDateTable
(
StartDate,
EndDate
)
VALUES
(
DATEADD(DAY, @s * 365, {d '2009-01-01'}),
DATEADD(DAY, @s * 365 + @e * 14, {d '2009-01-01'})
)
SELECT
@s = Rand(),
@e = Rand(),
@i += 1
END
Die erste Frage ist, wie diese Tabelle indiziert wird. Eine Möglichkeit besteht darin, zwei Indizes für die Spalten DATETIME
bereitzustellen, damit der Optimierer zumindest auswählen kann, ob nach StartDate
oder EndDate
gesucht werden soll.
CREATE INDEX nc1 ON dbo.SomeDateTable (StartDate, EndDate)
CREATE INDEX nc2 ON dbo.SomeDateTable (EndDate, StartDate)
Natürlich bedeuten die Ungleichungen sowohl bei StartDate
als auch bei EndDate
, dass nur eine Spalte in jedem Index eine Suche in der Beispielabfrage unterstützen kann, aber dies ist ungefähr das Beste, was wir tun können. Wir könnten erwägen, die zweite Spalte in jedem Index zu einem INCLUDE
anstatt zu einem Schlüssel zu machen, aber wir könnten andere Abfragen haben, die eine Gleichheitssuche in der führenden Spalte und eine Ungleichheitssuche in der zweiten Spalte durchführen können. Auf diese Weise erhalten wir möglicherweise auch bessere Statistiken. Wie auch immer...
DECLARE
@StartDateBegin DATETIME = {d '2009-08-01'},
@StartDateEnd DATETIME = {d '2009-10-15'},
@EndDateBegin DATETIME = {d '2009-08-05'},
@EndDateEnd DATETIME = {d '2009-10-22'}
SELECT
COUNT_BIG(*)
FROM dbo.SomeDateTable AS sdt
WHERE
sdt.StartDate BETWEEN @StartDateBegin AND @StartDateEnd
AND sdt.EndDate BETWEEN @EndDateBegin AND @EndDateEnd
Diese Abfrage verwendet Variablen, sodass der Optimierer im Allgemeinen die Selektivität und Verteilung errät, was zu einer geschätzten Kardinalitätsschätzung von 81 Zeilen führt. Tatsächlich erzeugt die Abfrage 2076 Zeilen, eine Diskrepanz, die in einem komplexeren Beispiel wichtig sein könnte.
Unter SQL Server 2008 SP1 CU5 oder höher (oder R2 RTM CU1) können wir Parameter Embedding Optimization nutzen, um bessere Schätzungen zu erhalten, indem wir einfach die Funktion OPTION (RECOMPILE)
zur obigen Abfrage SELECT
. Dies führt zu einer Kompilierung unmittelbar vor der Ausführung des Stapels, sodass SQL Server die tatsächlichen Parameterwerte "sehen" und für diese optimieren kann. Mit dieser Änderung verbessert sich die Schätzung auf 468 Zeilen (obwohl Sie den Laufzeitplan überprüfen müssen, um dies zu sehen). Diese Schätzung ist besser als 81 Zeilen, aber immer noch nicht so nah Modellierungserweiterungen, die durch Ablaufverfolgungsflag 2301 aktiviert wurden, können in einigen Fällen hilfreich sein, jedoch nicht bei dieser Abfrage.
Das Problem besteht darin, dass sich die durch die beiden Bereichssuchen qualifizierten Zeilen überlappen. Eine der vereinfachenden Annahmen in der Kosten- und Kardinalitätsschätzungskomponente des Optimierers ist, dass Prädikate unabhängig sind. Wenn also beide eine Selektivität von 50% haben, wird angenommen, dass das Ergebnis der Anwendung beider Prädikate 50% von 50% = 25% der Zeilen qualifiziert ). Wenn diese Art der Korrelation ein Problem darstellt, können wir sie häufig mit mehrspaltigen und/oder gefilterten Statistiken umgehen. Bei zwei Bereichen mit unbekannten Start- und Endpunkten wird dies unpraktisch. Hier müssen wir manchmal darauf zurückgreifen, die Abfrage in ein Formular umzuschreiben, das zufällig eine bessere Schätzung liefert:
SELECT COUNT(*) FROM
(
SELECT
sdt.Id
FROM dbo.SomeDateTable AS sdt
WHERE
sdt.StartDate BETWEEN @StartDateBegin AND @StartDateEnd
INTERSECT
SELECT
sdt.Id
FROM dbo.SomeDateTable AS sdt
WHERE
sdt.EndDate BETWEEN @EndDateBegin AND @EndDateEnd
) AS intersected (id)
OPTION (RECOMPILE)
Dieses Formular erzeugt zufällig eine Laufzeitschätzung von 2110 Zeilen (gegenüber 2076 tatsächlichen). Es sei denn, Sie haben TF 2301 aktiviert. In diesem Fall sehen die fortgeschritteneren Modellierungstechniken den Trick durch und erzeugen genau die gleiche Schätzung wie zuvor: 468 Zeilen.
Eines Tages wird SQL Server möglicherweise native Unterstützung für Intervalle erhalten. Wenn dies mit einer guten statistischen Unterstützung einhergeht, haben Entwickler möglicherweise Angst, solche Abfragepläne etwas weniger zu optimieren.
Ich kenne keine Lösung, die für alle Datenverteilungen schnell ist, aber wenn alle Ihre Bereiche kurz sind, können wir sie normalerweise beschleunigen. Wenn beispielsweise die Bereiche kürzer als ein Tag sind, anstelle dieser Abfrage:
SELECT TaskId ,
TaskDescription ,
StartedAt ,
FinishedAt
FROM dbo.Tasks
WHERE '20101203' BETWEEN StartedAt AND FinishedAt
wir können noch eine Bedingung hinzufügen:
SELECT TaskId ,
TaskDescription ,
StartedAt ,
FinishedAt
FROM dbo.Tasks
WHERE '20101203' BETWEEN StartedAt AND FinishedAt
AND StartedAt >= '20101202'
AND FinishedAt <= '20101204' ;
Anstatt die gesamte Tabelle zu scannen, scannt die Abfrage daher nur den Bereich von zwei Tagen, was schneller ist. Wenn Bereiche länger sein können, können wir sie als Sequenzen kürzerer Bereiche speichern. Details hier: Optimieren von SQL-Abfragen mithilfe von Einschränkungen