Cast to date itu pantas, tetapi apakah itu ide yang bagus?


47

Dalam SQL Server 2008 datatype tanggal telah ditambahkan.

Casting datetimekolom dateadalah sargable dan dapat menggunakan indeks pada datetimekolom.

select *
from T
where cast(DateTimeCol as date) = '20130101';

Opsi lain yang Anda miliki adalah menggunakan rentang.

select *
from T
where DateTimeCol >= '20130101' and
      DateTimeCol < '20130102'

Apakah pertanyaan ini sama baiknya atau haruskah satu lebih disukai daripada yang lain?


4
Apa yang dikatakan rencana eksekusi?
a_horse_with_no_name

3
Saya tidak bisa tidak memperhatikan bahwa LINQ2SQL menghasilkan SQL where cast(date_column as date) = 'value'ketika disajikan dengan C # mirip dengan where obj.date_column.Date == date_variable.
GSerg

6
Itu item Connect yang sangat baik. :)
Rob Farley

1
Situs Connect telah dihapus serta Sargable di Wikipedia
Ivanzinho

Jawaban:


59

Mekanisme di balik sargability casting hingga saat ini disebut pencarian dinamis .

SQL Server memanggil fungsi internal GetRangeThroughConvertuntuk mendapatkan awal dan akhir kisaran.

Agak mengherankan ini tidak kisaran yang sama dengan nilai literal Anda.

Membuat tabel dengan baris per halaman dan 1440 baris per hari

CREATE TABLE T
  (
     DateTimeCol DATETIME PRIMARY KEY,
     Filler      CHAR(8000) DEFAULT 'X'
  );

WITH Nums(Num)
     AS (SELECT number
         FROM   spt_values
         WHERE  type = 'P'
                AND number BETWEEN 1 AND 1440),
     Dates(Date)
     AS (SELECT {d '2012-12-30'} UNION ALL
         SELECT {d '2012-12-31'} UNION ALL
         SELECT {d '2013-01-01'} UNION ALL
         SELECT {d '2013-01-02'} UNION ALL
         SELECT {d '2013-01-03'})
INSERT INTO T
            (DateTimeCol)
SELECT DISTINCT DATEADD(MINUTE, Num, Date)
FROM   Nums,
       Dates 

Lalu berlari

SET STATISTICS IO ON;
SET STATISTICS TIME ON;

SELECT *
FROM   T
WHERE  DateTimeCol >= '20130101'
       AND DateTimeCol < '20130102'

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'; 

Kueri pertama telah 1443membaca dan yang kedua 2883sehingga membaca seluruh hari tambahan kemudian membuangnya terhadap predikat residual.

Rencana tersebut menunjukkan predikat pencarian tersebut

Seek Keys[1]: Start: DateTimeCol > Scalar Operator([Expr1006]), 
               End: DateTimeCol < Scalar Operator([Expr1007])

Jadi alih-alih >= '20130101' ... < '20130102'membacanya, > '20121231' ... < '20130102'buang semua 2012-12-31baris.

Kerugian lain dari mengandalkannya adalah bahwa perkiraan kardinalitas mungkin tidak seakurat dengan kueri rentang tradisional. Ini dapat dilihat dalam versi amandemen SQL Fiddle Anda .

Semua 100 baris dalam tabel sekarang cocok dengan predikat (dengan datetimes terpisah 1 menit pada hari yang sama).

Kueri (rentang) kedua dengan benar memperkirakan bahwa 100 akan cocok dan menggunakan pemindaian indeks berkerumun. The CAST( AS DATE)permintaan salah memperkirakan bahwa hanya satu baris akan cocok dan menghasilkan rencana dengan pencarian kunci.

Statistik tidak diabaikan sepenuhnya. Jika semua baris dalam tabel memiliki yang sama datetimedan cocok dengan predikat (misalnya 20130101 00:00:00atau 20130101 01:00:00) maka rencana menunjukkan pemindaian indeks berkerumun dengan perkiraan 31,6228 baris.

100 ^ 0.75 = 31.6228

Jadi dalam hal ini tampaknya perkiraan tersebut berasal dari rumus di sini .

Jika semua baris dalam tabel memiliki yang sama datetimedan tidak cocok dengan predikat (mis. 20130102 01:00:00) Maka jatuh kembali ke perkiraan jumlah baris 1 dan rencana dengan pencarian.

Untuk kasus di mana tabel memiliki lebih dari satu DISTINCTnilai, baris yang diestimasi tampaknya sama seperti jika kueri mencari dengan tepat 20130101 00:00:00.

Jika histogram statistik memiliki langkah pada 2013-01-01 00:00:00.000maka estimasi akan didasarkan pada EQ_ROWS(yaitu tidak memperhitungkan waktu lain pada tanggal itu). Kalau tidak, jika tidak ada langkah sepertinya menggunakan AVG_RANGE_ROWSdari langkah-langkah sekitarnya.

Karena datetimememiliki presisi sekitar 3ms dalam banyak sistem akan ada sangat sedikit nilai duplikat aktual dan angka ini akan menjadi 1.


1
Hai Martin, dapatkah Anda menambahkan TL;DRbagian dengan beberapa poin dengan kasus yang berbeda menambahkan apakah dalam kasus itu, para pemain sampai saat ini apakah itu ide yang baik atau tidak?
TT.

6
@TT. Saya pikir intinya adalah bahwa itu bukan ide yang baik. Mengapa Anda ingin menggunakan metode yang membutuhkan lembar contekan?
Aaron Bertrand

10

Saya tahu bahwa ini memiliki Great Answer® lama dari Martin, tapi saya ingin menambahkan beberapa perubahan pada perilaku di sini dalam versi SQL Server yang lebih baru. Ini tampaknya hanya telah diuji hingga 2008R2.

Dengan PETUNJUK PENGGUNAAN baru yang memungkinkan dilakukannya beberapa perkiraan waktu perjalanan kardinalitas, kita dapat melihat ketika segala sesuatu berubah.

Menggunakan pengaturan yang sama seperti pada SQL Fiddle.

CREATE TABLE T ( ID INT IDENTITY PRIMARY KEY, DateTimeCol DATETIME, Filler CHAR(8000) NULL );

CREATE INDEX IX_T_DateTimeCol ON T ( DateTimeCol );


WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1),
     E02(N) AS (SELECT 1 FROM E00 a, E00 b),
     E04(N) AS (SELECT 1 FROM E02 a, E02 b),
     E08(N) AS (SELECT 1 FROM E04 a, E04 b),
     Num(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY E08.N) FROM E08)
INSERT INTO T(DateTimeCol)
SELECT TOP 100 DATEADD(MINUTE, Num.N, '20130101')
FROM Num;

Kami dapat menguji level yang berbeda seperti:

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_100' ));
GO

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_110' ));
GO 

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_120' ));
GO 

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_130' ));
GO 

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_140' ));
GO 

Rencana untuk semua ini tersedia di sini . Level Compat 100 dan 110 keduanya memberikan rencana pencarian kunci, tetapi mulai dengan level compat 120, kita mulai mendapatkan rencana pemindaian yang sama dengan estimasi 100 baris. Ini berlaku hingga level 140.

GILA

GILA

GILA

Estimasi kardinalitas untuk >= '20130101', < '20130102'rencana tetap di 100, yang diharapkan.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.