Mengapa waktu pencarian pencarian saya tidak cocok?

20
select * 
from A 
where posted_date >= '2015-07-27 00:00:00.000' 
  and posted_date  <= '2015-07-27 23:59:59.999'

Tetapi hasilnya berisi catatan yang telah diposkan tanggal pada hari ini: 2015-07-28. Server basis data saya tidak ada di negara saya. Apa masalahnya ?

lecuong92
sumber

Jawaban:

16

Karena Anda menggunakan datetimedatatype, Anda perlu memahami bagaimana sql server melakukan putaran data datetime.

╔═══════════╦═════╦═════════════════════════════╦═════════════════════════════╦══════════╦═══════════╗
   Name     sn          Minimum value                Maximum value         Accuracy   Storage  
╠═══════════╬═════╬═════════════════════════════╬═════════════════════════════╬══════════╬═══════════╣
 datetime   dt   1753-01-01 00:00:00.000      9999-12-31 23:59:59.997      3.33 ms   8 bytes   
 datetime2  dt2  0001-01-01 00:00:00.0000000  9999-12-31 23:59:59.9999999  100ns     6-8 bytes 
╚═══════════╩═════╩═════════════════════════════╩═════════════════════════════╩══════════╩═══════════╝

masukkan deskripsi gambar di sini

Menggunakan kueri di bawah ini, Anda dapat dengan mudah melihat masalah pembulatan yang dilakukan sql server saat Anda menggunakan DATETIMEtipe data.

select  '2015-07-27 00:00:00.000'                       as Original_startDateTime,
        convert(datetime ,'2015-07-27 00:00:00.000')    as startDateTime,
        '2015-07-27 23:59:59.999'                       as Original_endDateTime,
        convert(datetime ,'2015-07-27 23:59:59.999')    as endDateTime,
        '2015-07-27 00:00:00.000'                       as Original_startDateTime2,
        convert(datetime2 ,'2015-07-27 00:00:00.000')   as startDateTime2,  -- default precision is 7
        '2015-07-27 23:59:59.999'                       as Original_endDateTime2,
        convert(datetime2 ,'2015-07-27 23:59:59.999')   as endDateTime2     -- default precision is 7

masukkan deskripsi gambar di sini klik untuk memperbesar

DATETIME2sudah ada sejak SQL Server 2008, jadi mulailah menggunakannya DATETIME. Untuk situasi Anda, Anda dapat menggunakan datetime2dengan 3 desimal misalnya datetime2(3).

Manfaat Menggunakan datetime2:

  • Mendukung hingga 7 tempat desimal untuk komponen waktu vs datetimehanya mendukung 3 tempat desimal .. dan karenanya Anda melihat masalah pembulatan karena secara default membulatkan datetimeyang terdekat .003 secondsdengan selisih .000, .003atau .007detik.
  • datetime2jauh lebih tepat daripada datetimedan datetime2memberi Anda kontrol DATEdan TIMEsebagai lawan datetime.

Referensi:

Kin Shah
sumber
1
gives you control of DATE and TIME as opposed to datetime.apa artinya?
nurettin
Kembali. menggunakan DateTime2vs DateTime.: a. Untuk - kasus - mayoritas - dari - penggunaan - dunia - nyata - kasus, manfaat dari DateTime2Banyak biaya. Lihat: stackoverflow.com/questions/1334143/... b. Itu bukan akar masalah di sini. Lihat komentar selanjutnya.
Tom
Akar masalah di sini (seperti yang saya bertaruh, kebanyakan devs senior akan setuju) bukanlah ketepatan yang tidak mencukupi dalam waktu-tanggal akhir inklusif dari perbandingan rentang tanggal-waktu, melainkan penggunaan satu periode inklusif (vs. eksklusif). Ini seperti memeriksa persamaan untuk Pi, selalu ada kemungkinan bahwa salah satu dari # memiliki> atau <presisi (yaitu Bagaimana jika datetime3dengan 70 (vs 7) digit presisi ditambahkan?). Praktik terbaik adalah menggunakan nilai di mana presisi tidak menjadi masalah, yaitu < awal detik, menit, jam atau hari berikutnya vs. <= akhir detik, menit, jam atau hari sebelumnya.
Tom
18

Seperti beberapa orang lain telah disebutkan dalam komentar dan jawaban lain untuk pertanyaan Anda masalah inti 2015-07-27 23:59:59.999sedang dibulatkan 2015-07-28 00:00:00.000oleh SQL Server. Per dokumentasi untuk DATETIME:

Rentang waktu - 00:00:00 hingga 23: 59: 59,997

Perhatikan bahwa rentang waktu tidak akan pernah bisa .999. Lebih jauh ke bawah dalam dokumentasi itu menentukan aturan pembulatan yang menggunakan SQL Server untuk digit paling signifikan.

Tabel yang menunjukkan aturan pembulatan

Perhatikan bahwa digit paling signifikan hanya dapat memiliki satu dari tiga nilai potensial: "0", "3", atau "7".

Ada beberapa solusi / solusi untuk ini yang dapat Anda gunakan.

-- Option 1
SELECT 
    * 
FROM A 
WHERE posted_date >= '2015-07-27 00:00:00.000' 
  AND posted_date <  '2015-07-28 00:00:00.000' --Round up and remove equality

-- Option 2
SELECT 
    * 
FROM A 
WHERE posted_date >= '2015-07-27 00:00:00.000' 
  AND posted_date <=  '2015-07-27 23:59:59.997' --Round down and keep equality

-- Option 3
SELECT 
    * 
FROM A 
WHERE CAST(posted_date AS DATE) = '2015-07-27' -- Use different data type

-- Option 4
SELECT 
    * 
FROM A 
WHERE CONVERT(CHAR(8), DateColumn, 112) = '20150727' -- Cast to string stripping off time

-- Option 5
SELECT 
    * 
FROM A 
WHERE posted_date BETWEEN '2015-07-27 00:00:00.000' 
  AND '2015-07-27 23:59:59.997' --Use between

Dari lima opsi yang saya sajikan di atas, saya akan mempertimbangkan opsi 1 dan 3 satu-satunya opsi yang layak. Mereka menyampaikan maksud Anda dengan jelas, dan tidak akan rusak jika Anda memperbarui tipe data. Jika Anda menggunakan SQL Server 2008 atau yang lebih baru, saya pikir opsi 3 harus menjadi pendekatan pilihan Anda. Itu terutama benar jika Anda bisa berubah dari menggunakan DATETIMEtipe data ke tipe DATEdata untuk posted_datekolom Anda .

Mengenai opsi 3, penjelasan yang sangat baik tentang beberapa masalah dapat ditemukan di sini: Cast to date itu pantas, tetapi apakah itu ide yang bagus?

Saya tidak suka opsi 2 dan 5 karena .997detik pecahan akan menjadi angka ajaib lain yang orang ingin "perbaiki." Untuk beberapa alasan mengapa BETWEENtidak dipeluk secara luas, Anda mungkin ingin memeriksa kiriman ini .

Saya tidak suka opsi 4 karena mengubah tipe data menjadi string untuk tujuan perbandingan terasa kotor bagi saya. Alasan yang lebih kualitatif untuk menghindarinya dalam SQL Server adalah dampak sargability alias Anda tidak dapat melakukan pencarian indeks dan itu akan sering mengakibatkan kinerja yang lebih buruk.

Untuk informasi lebih lanjut tentang cara yang benar dan cara yang salah untuk menangani pertanyaan rentang tanggal checkout posting ini oleh Aaron Bertrand .

Pada saat berpisah Anda akan dapat menjaga permintaan asli Anda dan itu akan berperilaku seperti yang diinginkan jika Anda mengubah posted_datekolom Anda dari a DATETIMEke DATETIME2(3). Itu akan menghemat ruang penyimpanan di server, memberi Anda akurasi yang lebih besar pada presisi yang sama, lebih memenuhi standar / portable, dan memungkinkan Anda untuk dengan mudah menyesuaikan akurasi / presisi jika kebutuhan Anda berubah di masa depan. Namun, ini hanya opsi jika Anda menggunakan SQL Server 2008 atau yang lebih baru.

Sebagai sedikit hal-hal sepele 1/300akurasi kedua dengan DATETIMEtampaknya menjadi pegangan dari UNIX per jawaban StackOverflow ini . Sybase yang memiliki warisan bersama memiliki kesamaan1/300 keakuratan yang sama dalam jenis DATETIMEdanTIME datanya, tetapi digit paling tidak signifikan mereka adalah sentuhan yang berbeda pada "0", "3", dan "6". Menurut pendapat saya 1/300akurasi kedua dan / atau 3.33ms adalah keputusan arsitektur yang tidak menguntungkan karena blok 4 byte untuk waktu dalam DATETIMEtipe data SQL Server dapat dengan mudah mendukung akurasi 1 ms.

Erik
sumber
Ya, tapi inti "inti masalah" tidak menggunakan opsi 1 (misalnya menggunakan setiap inklusif (vs eksklusif) rentang nilai akhir di mana presisi dari tipe data masa lalu atau potensi masa depan dapat mempengaruhi hasil). Ini seperti memeriksa kesetaraan untuk Pi, selalu mungkin satu # memiliki> atau <ketepatan (kecuali keduanya dibulatkan ke ketepatan umum terendah). Bagaimana jika datetime3dengan 70 (vs 7) digit presisi ditambahkan? Praktik terbaik adalah menggunakan nilai di mana ketepatan tidak penting, yaitu <awal detik, menit, jam atau hari berikutnya vs. <= akhir detik, menit, jam atau hari sebelumnya.
Tom
9

Konversi Tersirat

Saya kira tipe data yang diposting adalah tanggal. Namun tidak masalah apakah tipe di sisi lain adalah Datetime, Datetime2 atau just Time karena string (Varchar) akan secara implisit dikonversi ke Datetime.

Dengan diposting_date yang dinyatakan sebagai Datetime2 (atau Waktu), posted_date <= '2015-07-27 23:59:59.99999'klausa gagal di mana karena meskipun 23:59:59.99999merupakan nilai Datetime2 yang valid, ini bukan nilai Datetime yang valid:

 Conversion failed when converting date and/or time from character string.

Rentang Waktu untuk Datetime

Rentang waktu Datetime adalah 00:00:00 hingga 23: 59: 59,997. Karenanya 23: 59: 59.999 berada di luar jangkauan dan harus dibulatkan ke atas atau ke bawah ke nilai terdekat.

Ketepatan

Selain itu nilai Datetime dibulatkan dengan peningkatan 0,000, 0,003, atau 0,007 detik. (mis. 000, 003, 007, 010, 013, 017, 020, ..., 997)

Ini tidak terjadi dengan nilai 2015-07-27 23:59:59.999yang berada dalam kisaran ini: 2015-07-27 23:59:59.997dan2015-07-28 0:00:00.000 .

Rentang ini sesuai dengan opsi sebelumnya dan berikut yang terdekat, keduanya berakhir dengan, 000, 0,003 atau 0,007.

Membulatkan ke atas atau ke bawah ?

Karena lebih dekat ke 2015-07-28 0:00:00.000(1 vs -2) dari 2015-07-27 23:59:59.997, string dibulatkan dan menjadi nilai Datetime ini: 2015-07-28 0:00:00.000.

Dengan batas atas seperti 2015-07-27 23:59:59.998(atau .995, .996, .997, .998), batas itu akan dibulatkan ke bawah 2015-07-27 23:59:59.997dan kueri Anda akan berfungsi seperti yang diharapkan. Namun itu tidak akan menjadi solusi tetapi hanya nilai keberuntungan.

Jenis datetime2 atau Waktu

Datetime2 dan Rentang waktu waktu 00:00:00.0000000dilewati23:59:59.9999999 dengan akurasi 100ns (digit terakhir saat digunakan dengan ketepatan 7 digit).

Namun rentang Datetime (3) tidak sama dengan rentang Datetime:

  • Tanggal Waktu 0:0:00.000 ke23:59:59.997
  • Datetime2 0:0:00.000000000ke23:59:59.999

Larutan

Pada akhirnya lebih aman untuk mencari tanggal di bawah pada hari berikutnya daripada tanggal di bawah atau sama dengan apa yang Anda anggap sebagai fragmen terakhir dari waktu dalam sehari. Ini terutama karena Anda tahu bahwa hari berikutnya selalu dimulai pada 0: 00: 00.000 tetapi tipe data yang berbeda mungkin tidak memiliki waktu yang sama di akhir hari:

Datetime `0:0:00.000` to `23:59:59.997`
Datetime2 `0:0:00.000000000` to `23:59:59.999-999-900`
Time2 `0:0:00.000000000` to `23:59:59.999-999-900`
  • < 2015-07-28 0:00:00.000akan memberi Anda akurat hasil yang dan merupakan pilihan terbaik
  • <= 2015-07-27 23:59:59.xxx dapat mengembalikan nilai yang tidak terduga ketika tidak dibulatkan ke apa yang Anda pikirkan seharusnya.
  • Konversi ke Tanggal dan penggunaan fungsi harus dihindari karena membatasi penggunaan indeks

Kita dapat berpikir bahwa mengubah [posted_date] ke Datetime2 dan presisi yang lebih tinggi dapat memperbaiki masalah ini, tetapi itu tidak akan membantu karena string masih dikonversi ke Datetime. Namun, jika gips ditambahkan cast(2015-07-27 23:59:59.999' as datetime2), ini berfungsi dengan baik

Transmisikan dan Konversi

Pemain dapat mengonversi nilai hingga 3 digit ke Datetime atau dengan hingga 9 digit ke Datetime2 atau Waktu dan membulatkannya ke presisi yang benar.

Perlu dicatat bahwa pemeran Datetime2 dan Time2 dapat memberikan hasil yang berbeda:

  • select cast('20150101 23:59:59.999999999' as datetime2(7)) adalah pembulatan 2015-05-03 00: 00: 00.0000000 (untuk nilai lebih dari 999999949)
  • select cast('23:59:59.999999999' as time(7)) => 23: 59: 59.9999999

Ini semacam memperbaiki masalah yang dialami datetime dengan kenaikan 0, 3 dan 7 meskipun masih selalu lebih baik untuk mencari tanggal sebelum nano 1 detik di hari berikutnya (selalu 0: 00: 00.000).

Sumber MSDN: datetime (Transact-SQL)

Julien Vavasseur
sumber
6

Itu pembulatan

 select cast('2015-07-27 23:59:59.999' as datetime) 
 returns 2015-07-28 00:00:00.000

.998, .997, .996, .995 semuanya dilemparkan ke .997

Harus menggunakan

select * 
from A 
where posted_date >= '2015-07-27 00:00:00.000' 
  and posted_date <  '2015-07-28 00:00:00.000'

atau

where cast(posted_date as date) = '2015-07-27'

Lihat keakuratan dalam tautan ini
Selalu dilaporkan sebagai .000, .003, .007

paparazzo
sumber
-2
select * from A where date(posted_date) = '2015-07-27' 
Jaime A. Arvizu
sumber
1
'DATE' is not a recognized built-in function name.
Michael Green