it-swarm.com.de

Effizienteste Methode zum Abrufen von Datumsbereichen

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?

16
Thomas Stringer

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.

29
Paul White 9

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

5
A-K