Jika Anda mendapatkan hasil yang sangat aneh saat menggunakan DATEDIFF()
fungsi di SQL Server, dan Anda yakin fungsi tersebut mengandung bug, jangan sobek dulu. Ini mungkin bukan bug.
Ada skenario di mana hasil yang dihasilkan oleh fungsi ini bisa sangat buruk. Dan jika Anda tidak mengerti bagaimana fungsi sebenarnya bekerja, hasilnya akan terlihat benar-benar salah.
Semoga artikel ini dapat membantu memperjelas bagaimana DATEDIFF()
function dirancang untuk bekerja, dan memberikan beberapa contoh skenario di mana hasil Anda mungkin tidak seperti yang Anda harapkan.
Contoh 1 – 365 Hari Tidak Selalu Setahun
Pertanyaan: Kapan 365 hari tidak setahun?
Jawaban: Saat menggunakan DATEDIFF()
tentu saja!
Berikut adalah contoh di mana saya menggunakan DATEDIFF()
untuk mengembalikan jumlah hari antara dua tanggal, dan kemudian jumlah tahun antara dua tanggal yang sama.
DECLARE @startdate datetime2 = '2016-01-01 00:00:00.0000000', @enddate datetime2 = '2016-12-31 23:59:59.9999999'; SELECT DATEDIFF(day, @startdate, @enddate) Days, DATEDIFF(year, @startdate, @enddate) Years;
Hasil:
+--------+---------+ | Days | Years | |--------+---------| | 365 | 0 | +--------+---------+
Jika menurut Anda hasil ini salah, dan bahwa DATEDIFF()
jelas memiliki bug, baca terus – tidak semuanya seperti yang terlihat.
Percaya atau tidak, ini sebenarnya hasil yang diharapkan. Hasil ini persis sesuai dengan cara DATEDIFF()
dirancang untuk bekerja.
Contoh 2 – 100 Nanodetik =1 Tahun?
Mari kita lakukan sebaliknya.
DECLARE @startdate datetime2 = '2016-12-31 23:59:59.9999999', @enddate datetime2 = '2017-01-01 00:00:00.0000000'; SELECT DATEDIFF(year, @startdate, @enddate) Year, DATEDIFF(quarter, @startdate, @enddate) Quarter, DATEDIFF(month, @startdate, @enddate) Month, DATEDIFF(dayofyear, @startdate, @enddate) DOY, DATEDIFF(day, @startdate, @enddate) Day, DATEDIFF(week, @startdate, @enddate) Week, DATEDIFF(hour, @startdate, @enddate) Hour, DATEDIFF(minute, @startdate, @enddate) Minute, DATEDIFF(second, @startdate, @enddate) Second, DATEDIFF(millisecond, @startdate, @enddate) Millisecond, DATEDIFF(microsecond, @startdate, @enddate) Microsecond, DATEDIFF(nanosecond, @startdate, @enddate) Nanosecond;
Hasil (ditampilkan dengan output vertikal):
Year | 1 Quarter | 1 Month | 1 DOY | 1 Day | 1 Week | 1 Hour | 1 Minute | 1 Second | 1 Millisecond | 1 Microsecond | 1 Nanosecond | 100
Hanya ada perbedaan seratus nanodetik (.0000001 detik) antara dua tanggal/waktu, namun kami mendapatkan hasil yang persis sama untuk setiap bagian tanggal, kecuali nanodetik.
Bagaimana ini bisa terjadi? Bagaimana bisa perbedaan 1 mikrodetik dan perbedaan 1 tahun keduanya pada saat yang bersamaan? Belum lagi semua tanggal di antaranya?
Ini mungkin tampak gila, tetapi ini juga bukan bug. Hasil ini persis sesuai dengan cara DATEDIFF()
seharusnya bekerja.
Dan untuk membuat segalanya lebih membingungkan, kita bisa mendapatkan hasil yang berbeda tergantung pada tipe datanya. Tapi kita akan segera melakukannya. Pertama mari kita lihat bagaimana DATEDIFF()
fungsi benar-benar berfungsi.
Definisi Sebenarnya dari DATEDIFF()
Alasan kami mendapatkan hasil yang kami lakukan adalah karena DATEDIFF()
fungsi didefinisikan sebagai berikut:
Fungsi ini mengembalikan hitungan (sebagai nilai integer yang ditandatangani) dari batas bagian tanggal yang ditentukan di antara tanggal mulai yang ditentukan dan tanggal akhir .
Berikan perhatian khusus pada kata-kata "batas datepart dilintasi". Inilah sebabnya mengapa kami mendapatkan hasil yang kami lakukan pada contoh sebelumnya. Sangat mudah untuk mengasumsikan bahwa DATEDIFF()
menggunakan waktu yang telah berlalu untuk perhitungannya, tetapi tidak. Ini menggunakan jumlah batas datepart yang dilintasi.
Pada contoh pertama, tanggal tidak melewati batas bagian tahun. Tahun kencan pertama persis sama dengan tahun kencan kedua. Tidak ada batas yang dilewati.
Dalam contoh kedua, kami memiliki skenario yang berlawanan. Tanggal melewati setiap batas bagian tanggal setidaknya sekali (100 kali untuk nanodetik).
Contoh 3 – Hasil yang Berbeda untuk Minggu Ini
Sekarang, mari kita berpura-pura satu tahun telah berlalu. Dan di sini kita tepat satu tahun kemudian dengan nilai tanggal/waktu, kecuali bahwa nilai tahun bertambah satu.
Kita harus mendapatkan hasil yang sama, bukan?
DECLARE @startdate datetime2 = '2017-12-31 23:59:59.9999999', @enddate datetime2 = '2018-01-01 00:00:00.0000000'; SELECT DATEDIFF(year, @startdate, @enddate) Year, DATEDIFF(quarter, @startdate, @enddate) Quarter, DATEDIFF(month, @startdate, @enddate) Month, DATEDIFF(dayofyear, @startdate, @enddate) DOY, DATEDIFF(day, @startdate, @enddate) Day, DATEDIFF(week, @startdate, @enddate) Week, DATEDIFF(hour, @startdate, @enddate) Hour, DATEDIFF(minute, @startdate, @enddate) Minute, DATEDIFF(second, @startdate, @enddate) Second, DATEDIFF(millisecond, @startdate, @enddate) Millisecond, DATEDIFF(microsecond, @startdate, @enddate) Microsecond, DATEDIFF(nanosecond, @startdate, @enddate) Nanosecond;
Hasil:
Year | 1 Quarter | 1 Month | 1 DOY | 1 Day | 1 Week | 0 Hour | 1 Minute | 1 Second | 1 Millisecond | 1 Microsecond | 1 Nanosecond | 100
Salah.
Kebanyakan dari mereka adalah sama, tapi kali ini minggu kembali 0
.
Hah?
Ini terjadi karena tanggal input memiliki kalender minggu yang sama nilai-nilai. Kebetulan tanggal yang dipilih misalnya 2 memiliki nilai minggu kalender yang berbeda.
Untuk lebih spesifik, contoh 2 melewati batas bagian minggu mulai dari '2016-12-31' hingga '01-01-2017'. Hal ini karena minggu terakhir tahun 2016 berakhir pada 31-12-2016, dan minggu pertama 2017 dimulai pada 01-01-2017 (Minggu).
Namun dalam contoh 3, minggu pertama tahun 2018 sebenarnya dimulai pada tanggal mulai kami 2017-12-31 (Minggu). Tanggal akhir kami, karena hari berikutnya, jatuh dalam minggu yang sama. Oleh karena itu, tidak ada batas bagian minggu yang dilewati.
Ini jelas mengasumsikan bahwa hari Minggu adalah hari pertama setiap minggu. Ternyata, DATEDIFF()
fungsi tidak menganggap bahwa hari Minggu adalah hari pertama dalam seminggu. Bahkan mengabaikan SET DATEFIRST
pengaturan (pengaturan ini memungkinkan Anda untuk secara eksplisit menentukan hari mana yang dianggap sebagai hari pertama dalam seminggu). Alasan Microsoft untuk mengabaikan SET DATEFIRST
adalah memastikan DATEDIFF()
fungsi bersifat deterministik. Inilah solusi jika ini menjadi masalah bagi Anda.
Jadi singkatnya, hasil Anda bisa terlihat "salah" untuk setiap bagian tanggal tergantung pada tanggal/waktu. Hasil Anda bisa terlihat sangat salah saat menggunakan bagian minggu. Dan mereka bisa terlihat lebih salah jika Anda menggunakan SET DATEFIRST
nilai selain 7 (untuk hari Minggu) dan Anda mengharapkan DATEDIFF()
untuk menghormati itu.
Tetapi hasilnya tidak salah, dan itu bukan bug. Ini lebih dari sekadar "mengerti" bagi mereka yang tidak mengetahui bagaimana fungsi tersebut sebenarnya bekerja.
Semua gotcha ini juga berlaku untuk DATEDIFF_BIG()
fungsi. Ini bekerja sama dengan DATEDIFF()
dengan pengecualian bahwa ia mengembalikan hasilnya sebagai bigint . yang ditandatangani (sebagai lawan dari int untuk DATEDIFF()
).
Contoh 4 – Hasil Tergantung pada Tipe Data
Anda juga bisa mendapatkan hasil yang tidak diharapkan karena tipe data yang Anda gunakan untuk tanggal input Anda. Hasil akan sering berbeda tergantung pada tipe data tanggal input. Tapi Anda tidak bisa menyalahkan DATEDIFF()
untuk ini, karena ini murni karena kemampuan dan keterbatasan berbagai tipe data. Anda tidak dapat mengharapkan untuk mendapatkan hasil presisi tinggi dari nilai input presisi rendah.
Misalnya, setiap kali tanggal mulai atau tanggal akhir memiliki waktu kecil nilai, detik dan milidetik akan selalu mengembalikan 0. Ini karena waktu kecil tipe data hanya akurat dalam hitungan menit.
Inilah yang terjadi jika kita mengganti contoh 2 untuk menggunakan smalldatetime bukannya datetime2 :
DECLARE @startdate smalldatetime = '2016-12-31 23:59:59', @enddate smalldatetime = '2017-01-01 00:00:00'; SELECT DATEDIFF(year, @startdate, @enddate) Year, DATEDIFF(quarter, @startdate, @enddate) Quarter, DATEDIFF(month, @startdate, @enddate) Month, DATEDIFF(dayofyear, @startdate, @enddate) DOY, DATEDIFF(day, @startdate, @enddate) Day, DATEDIFF(week, @startdate, @enddate) Week, DATEDIFF(hour, @startdate, @enddate) Hour, DATEDIFF(minute, @startdate, @enddate) Minute, DATEDIFF(second, @startdate, @enddate) Second, DATEDIFF(millisecond, @startdate, @enddate) Millisecond, DATEDIFF(microsecond, @startdate, @enddate) Microsecond, DATEDIFF(nanosecond, @startdate, @enddate) Nanosecond;
Hasil:
Year | 0 Quarter | 0 Month | 0 DOY | 0 Day | 0 Week | 0 Hour | 0 Minute | 0 Second | 0 Millisecond | 0 Microsecond | 0 Nanosecond | 0
Alasan mengapa ini semua nol adalah karena kedua tanggal input sebenarnya identik:
DECLARE @startdate smalldatetime = '2016-12-31 23:59:59', @enddate smalldatetime = '2017-01-01 00:00:00'; SELECT @startdate 'Start Date', @enddate 'End Date';
Hasil:
+---------------------+---------------------+ | Start Date | End Date | |---------------------+---------------------| | 2017-01-01 00:00:00 | 2017-01-01 00:00:00 | +---------------------+---------------------+
Batasan waktu kecil tipe data menyebabkan detik dibulatkan, yang kemudian menyebabkan efek aliran dan semuanya dibulatkan. Bahkan jika Anda tidak mendapatkan nilai input yang identik, Anda masih bisa mendapatkan hasil yang tidak diharapkan karena tipe data tidak memberikan presisi yang Anda butuhkan.