it-swarm.asia

Cara Paling Efisien untuk Mengambil Kisaran Tanggal

Apa cara paling efisien untuk mengambil rentang tanggal dengan struktur tabel seperti ini?

create table SomeDateTable
(
    id int identity(1, 1) not null,
    StartDate datetime not null,
    EndDate datetime not null
)
go

Katakanlah Anda menginginkan rentang untuk StartDate dan EndDate. Jadi dengan kata lain, jika StartDate berada di antara @StartDateBegin dan @StartDateEnd, dan EndDate berada di antara @EndDateBegin dan @EndDateEnd, lalu lakukan sesuatu.

Saya tahu ada beberapa cara untuk melakukan hal ini, tetapi apa yang paling disarankan?

16
Thomas Stringer

Ini adalah masalah yang sulit untuk dipecahkan secara umum, tetapi ada beberapa hal yang dapat kita lakukan untuk membantu pengoptimal memilih rencana. Script ini membuat tabel dengan 10.000 baris dengan distribusi baris pseudo-acak yang dikenal untuk menggambarkan:

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

Pertanyaan pertama adalah bagaimana cara mengindeks tabel ini. Salah satu opsi adalah menyediakan dua indeks pada kolom DATETIME, sehingga pengoptimal setidaknya dapat memilih apakah akan mencari di StartDate atau EndDate.

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

Tentu saja, ketidaksetaraan pada StartDate dan EndDate berarti bahwa hanya satu kolom di setiap indeks yang dapat mendukung pencarian dalam contoh query, tetapi ini adalah tentang yang terbaik yang bisa kita lakukan. Kami mungkin mempertimbangkan menjadikan kolom kedua di setiap indeks sebagai INCLUDE daripada kunci, tetapi kami mungkin memiliki kueri lain yang dapat melakukan pencarian kesetaraan di kolom utama dan pencarian ketidaksetaraan di kolom kedua. Juga, kita bisa mendapatkan statistik yang lebih baik dengan cara ini. Bagaimanapun...

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

Kueri ini menggunakan variabel, jadi secara umum pengoptimal akan menebak selektivitas dan distribusi, menghasilkan perkiraan kardinalitas tebakan 81 baris . Bahkan, kueri menghasilkan 2076 baris, perbedaan yang mungkin penting dalam contoh yang lebih kompleks.

Pada SQL Server 2008 SP1 CU5 atau yang lebih baru (atau R2 RTM CU1) kita dapat mengambil keuntungan dari Parameter Embedding Optimization untuk mendapatkan perkiraan yang lebih baik, hanya dengan menambahkan fungsi OPTION (RECOMPILE) ke permintaan SELECT di atas. Hal ini menyebabkan kompilasi tepat sebelum batch dijalankan, memungkinkan SQL Server untuk 'melihat' nilai parameter nyata dan mengoptimalkannya. Dengan perubahan ini, perkiraan meningkat menjadi 468 baris (meskipun Anda perlu memeriksa rencana runtime untuk melihat ini). Perkiraan ini lebih baik dari 81 baris, tetapi masih belum terlalu dekat. ekstensi pemodelan diaktifkan oleh jejak bendera 2301 dapat membantu dalam beberapa kasus, tetapi tidak dengan kueri ini.

Masalahnya adalah di mana baris yang dikualifikasikan oleh dua rentang pencarian tumpang tindih. Salah satu asumsi penyederhanaan yang dibuat dalam komponen estimasi biaya dan kardinalitas optimizer adalah bahwa predikat independen (jadi jika keduanya memiliki selektivitas 50%, hasil penerapan keduanya diasumsikan memenuhi syarat 50% dari 50% = 25% dari baris ). Di mana korelasi semacam ini merupakan masalah, kita sering dapat mengatasinya dengan statistik multi-kolom dan/atau difilter. Dengan dua rentang dengan titik awal dan akhir yang tidak diketahui, ini menjadi tidak praktis. Di sinilah kadang-kadang kita harus menggunakan penulisan ulang kueri ke formulir yang menghasilkan perkiraan yang lebih baik:

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)

Bentuk ini terjadi untuk menghasilkan perkiraan runtime 2110 baris (dibandingkan 2076 aktual). Kecuali Anda memiliki TF 2301 pada, dalam hal ini teknik pemodelan yang lebih maju melihat melalui trik dan menghasilkan perkiraan yang persis sama seperti sebelumnya: 468 baris.

Suatu hari SQL Server mungkin mendapatkan dukungan asli untuk interval. Jika itu datang dengan dukungan statistik yang baik, pengembang mungkin takut rencana tuning kueri seperti ini sedikit kurang.

29
Paul White 9

Saya tidak tahu solusi yang cepat untuk semua distribusi data, tetapi jika semua rentang Anda pendek, kami biasanya dapat mempercepatnya. Jika, misalnya, rentang lebih pendek dari satu hari, daripada permintaan ini:

SELECT  TaskId ,    
        TaskDescription ,
        StartedAt ,    
        FinishedAt    
FROM    dbo.Tasks    
WHERE   '20101203' BETWEEN StartedAt AND FinishedAt

kita dapat menambahkan satu syarat lagi:

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

Akibatnya, alih-alih memindai seluruh tabel, kueri hanya akan memindai rentang dua hari, yang lebih cepat. Jika rentang mungkin lebih panjang, kami dapat menyimpannya sebagai urutan yang lebih pendek. Detail di sini: Menyetel Kueri SQL dengan Bantuan Kendala

5
A-K