Effizienteste Methode zum Abrufen von Datumsbereichen

16

Was ist die effizienteste Methode zum Abrufen von Datumsbereichen mit einer solchen Tabellenstruktur?

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 beide StartDateund EndDate. Mit anderen Worten, wenn StartDatezwischen @StartDateBeginund @StartDateEndund EndDatezwischen @EndDateBeginund fällt @EndDateEnd, dann tun Sie etwas.

Ich weiß, dass es wahrscheinlich ein paar Möglichkeiten gibt, dies zu tun, aber was ist am ratsamsten?

Thomas Stringer
quelle

Antworten:

29

Dieses Problem ist im Allgemeinen schwer zu lösen, aber wir können dem Optimierer einige Maßnahmen bei der Auswahl eines Plans empfehlen. Dieses Skript erstellt eine Tabelle mit 10.000 Zeilen mit einer bekannten Pseudozufallsverteilung von 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 Option besteht darin, zwei Indizes für die DATETIMESpalten bereitzustellen , sodass der Optimierer mindestens auswählen kann, ob nach StartDateoder gesucht werden soll EndDate.

CREATE INDEX nc1 ON dbo.SomeDateTable (StartDate, EndDate)
CREATE INDEX nc2 ON dbo.SomeDateTable (EndDate, StartDate)

Natürlich bedeuten die Ungleichungen in beiden StartDateund 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 in Betracht ziehen, die zweite Spalte in jedem Index zu INCLUDEeinem Schlüssel zu machen, aber wir könnten andere Abfragen haben, die eine Gleichheitssuche für die führende Spalte und eine Ungleichheitssuche für die zweite 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

Bei dieser Abfrage werden Variablen verwendet, sodass der Optimierer im Allgemeinen die Selektivität und Verteilung errät, was zu einer geschätzten Kardinalität von 81 Zeilen führt . Tatsächlich erzeugt die Abfrage 2076 Zeilen, eine Diskrepanz, die in einem komplexeren Beispiel wichtig sein könnte.

In SQL Server 2008 SP1 CU5 oder höher (oder R2 RTM CU1) können wir die Parametereinbettungsoptimierung nutzen , um bessere Schätzungen zu erhalten, indem wir sie einfach OPTION (RECOMPILE)zur SELECTobigen Abfrage hinzufügen . Dies führt zu einer Kompilierung kurz vor der Ausführung des Stapels, sodass SQL Server die tatsächlichen Parameterwerte "sehen" und für diese optimieren kann. Mit dieser Änderung wird die Schätzung auf 468 Zeilen verbessert (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. Die durch das Ablaufverfolgungsflag 2301 aktivierten Modellierungserweiterungen 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 Kalkulations- 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 50% von 50% = 25% der Zeilen ergibt ). Wenn diese Art von 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 zu einer besseren Schätzung führt:

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 führt zu einer 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 liefern genau die gleiche Schätzung wie zuvor: 468 Zeilen.

Eines Tages erhält SQL Server möglicherweise native Unterstützung für Intervalle. Wenn dies mit einer guten statistischen Unterstützung einhergeht, haben Entwickler möglicherweise Angst davor, solche Abfragepläne etwas weniger zu optimieren.

Paul White sagt GoFundMonica
quelle
5

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 zum Beispiel 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 eine weitere Bedingung hinzufügen:

SELECT  TaskId ,    
        TaskDescription ,
        StartedAt ,    
        FinishedAt    
FROM    dbo.Tasks    
WHERE   '20101203' BETWEEN StartedAt AND FinishedAt
    AND StartedAt >= '20101202'
    AND FinishedAt <= '20101204' ;

Infolgedessen durchsucht die Abfrage nicht die gesamte Tabelle, sondern nur den Bereich von zwei Tagen, was schneller ist. Wenn Bereiche länger sein können, können wir sie als Sequenzen von kürzeren speichern. Details hier: SQL-Abfragen mit Hilfe von Constraints optimieren

AK
quelle