TL;DR
Date functions di SQL itu buat manipulasi data tanggal dan waktu. Yang paling sering dipake: CURRENT_DATE, EXTRACT, DATE_TRUNC, DATE_ADD/SUB, DATEDIFF, dan DATE_FORMAT. Penting banget buat time-series analysis dan reporting.
Sebagai Data Analyst, kamu pasti sering banget deal dengan data tanggal dan waktu. Laporan bulanan? Umur customer? Trend mingguan? Semuanya butuh date functions.
Masalahnya, date handling di SQL itu bisa tricky. Format tanggal beda-beda, timezone bikin pusing, dan tiap database punya fungsi yang agak berbeda.
Contoh-contoh di sini pake format Indonesia dan timezone WIB, jadi bisa langsung kamu pake buat kerjaan.
Sebelum masuk ke fungsi-fungsinya, kamu perlu tau dulu tipe data tanggal yang ada di SQL.
Cuma nyimpen tanggal (tahun, bulan, hari). Ga ada informasi waktu.
-- Format: YYYY-MM-DD
-- Contoh: 2024-12-23
Cuma nyimpen waktu (jam, menit, detik). Ga ada informasi tanggal.
-- Format: HH:MM:SS
-- Contoh: 14:30:45
Nyimpen tanggal DAN waktu sekaligus.
-- Format: YYYY-MM-DD HH:MM:SS
-- Contoh: 2024-12-23 14:30:45
Sama kayak TIMESTAMP, tapi include informasi timezone.
-- Format: YYYY-MM-DD HH:MM:SS+TZ
-- Contoh: 2024-12-23 14:30:45+07 (WIB)
Buat tutorial ini, kita pakai dataset transaksi e-commerce Indonesia.
| id | customer_id | tanggal_order | tanggal_kirim | tanggal_selesai | total |
|---|---|---|---|---|---|
| 1 | 101 | 2024-01-15 09:30:00 | 2024-01-16 14:00:00 | 2024-01-18 10:15:00 | 500000 |
| 2 | 102 | 2024-01-20 14:45:00 | 2024-01-21 09:30:00 | 2024-01-23 16:00:00 | 750000 |
| 3 | 101 | 2024-02-05 11:00:00 | 2024-02-06 10:00:00 | 2024-02-08 09:45:00 | 320000 |
| 4 | 103 | 2024-02-14 08:15:00 | 2024-02-15 15:30:00 | 2024-02-17 11:00:00 | 1200000 |
| 5 | 102 | 2024-03-01 16:20:00 | 2024-03-02 11:45:00 | 2024-03-04 14:30:00 | 450000 |
| 6 | 104 | 2024-03-10 10:00:00 | 2024-03-11 09:00:00 | 2024-03-13 17:00:00 | 890000 |
| 7 | 101 | 2024-03-22 13:30:00 | 2024-03-23 16:15:00 | 2024-03-26 10:00:00 | 670000 |
| customer_id | nama | tanggal_lahir | tanggal_daftar | kota |
|---|---|---|---|---|
| 101 | Budi Santoso | 1990-05-15 | 2022-01-10 08:30:00 | Jakarta |
| 102 | Siti Rahayu | 1995-08-22 | 2023-03-20 14:15:00 | Bandung |
| 103 | Andi Wijaya | 1988-12-03 | 2021-11-05 10:00:00 | Surabaya |
| 104 | Dewi Lestari | 1992-02-28 | 2024-01-15 09:45:00 | Jakarta |
Dapetin tanggal hari ini (tanpa waktu).
SELECT CURRENT_DATE AS hari_ini;
Hasil:
| hari_ini |
|---|
| 2024-12-23 |
Dapetin waktu saat ini (tanpa tanggal).
SELECT CURRENT_TIME AS waktu_sekarang;
Hasil:
| waktu_sekarang |
|---|
| 14:30:45 |
Dapetin tanggal DAN waktu saat ini.
SELECT
CURRENT_TIMESTAMP AS timestamp_now,
NOW() AS now_function; -- PostgreSQL, MySQL
Hasil:
| timestamp_now | now_function |
|---|---|
| 2024-12-23 14:30:45 | 2024-12-23 14:30:45 |
Filter transaksi hari ini:
SELECT *
FROM transaksi
WHERE DATE(tanggal_order) = CURRENT_DATE;
Filter transaksi 7 hari terakhir:
SELECT *
FROM transaksi
WHERE tanggal_order >= CURRENT_DATE - INTERVAL '7 days';
EXTRACT itu fungsi buat ngambil bagian tertentu dari tanggal. Super useful!
EXTRACT(part FROM date_column)
SELECT
tanggal_order,
EXTRACT(YEAR FROM tanggal_order) AS tahun,
EXTRACT(MONTH FROM tanggal_order) AS bulan,
EXTRACT(DAY FROM tanggal_order) AS hari,
EXTRACT(HOUR FROM tanggal_order) AS jam,
EXTRACT(MINUTE FROM tanggal_order) AS menit,
EXTRACT(SECOND FROM tanggal_order) AS detik,
EXTRACT(DOW FROM tanggal_order) AS hari_minggu, -- 0=Sunday, 6=Saturday
EXTRACT(DOY FROM tanggal_order) AS hari_ke, -- Day of year (1-366)
EXTRACT(WEEK FROM tanggal_order) AS minggu_ke, -- Week of year
EXTRACT(QUARTER FROM tanggal_order) AS kuartal
FROM transaksi
LIMIT 3;
Hasil:
| tanggal_order | tahun | bulan | hari | jam | menit | detik | hari_minggu | hari_ke | minggu_ke | kuartal |
|---|---|---|---|---|---|---|---|---|---|---|
| 2024-01-15 09:30:00 | 2024 | 1 | 15 | 9 | 30 | 0 | 1 | 15 | 3 | 1 |
| 2024-01-20 14:45:00 | 2024 | 1 | 20 | 14 | 45 | 0 | 6 | 20 | 3 | 1 |
| 2024-02-05 11:00:00 | 2024 | 2 | 5 | 11 | 0 | 0 | 1 | 36 | 6 | 1 |
SELECT
EXTRACT(YEAR FROM tanggal_order) AS tahun,
EXTRACT(MONTH FROM tanggal_order) AS bulan,
COUNT(*) AS jumlah_transaksi,
SUM(total) AS total_revenue
FROM transaksi
GROUP BY
EXTRACT(YEAR FROM tanggal_order),
EXTRACT(MONTH FROM tanggal_order)
ORDER BY tahun, bulan;
Hasil:
| tahun | bulan | jumlah_transaksi | total_revenue |
|---|---|---|---|
| 2024 | 1 | 2 | 1250000 |
| 2024 | 2 | 2 | 1520000 |
| 2024 | 3 | 3 | 2010000 |
SELECT
CASE EXTRACT(DOW FROM tanggal_order)
WHEN 0 THEN 'Minggu'
WHEN 1 THEN 'Senin'
WHEN 2 THEN 'Selasa'
WHEN 3 THEN 'Rabu'
WHEN 4 THEN 'Kamis'
WHEN 5 THEN 'Jumat'
WHEN 6 THEN 'Sabtu'
END AS hari,
COUNT(*) AS jumlah_order
FROM transaksi
GROUP BY EXTRACT(DOW FROM tanggal_order)
ORDER BY EXTRACT(DOW FROM tanggal_order);
Dari sini kamu bisa tau hari apa yang paling banyak order. Berguna buat planning promo!
DATE_TRUNC itu kayak "membulatkan" tanggal ke awal periode tertentu. Berguna banget buat grouping.
DATE_TRUNC('period', date_column)
SELECT
tanggal_order,
DATE_TRUNC('year', tanggal_order) AS awal_tahun,
DATE_TRUNC('quarter', tanggal_order) AS awal_kuartal,
DATE_TRUNC('month', tanggal_order) AS awal_bulan,
DATE_TRUNC('week', tanggal_order) AS awal_minggu,
DATE_TRUNC('day', tanggal_order) AS awal_hari,
DATE_TRUNC('hour', tanggal_order) AS awal_jam
FROM transaksi
LIMIT 3;
Hasil:
| tanggal_order | awal_tahun | awal_kuartal | awal_bulan | awal_minggu | awal_hari | awal_jam |
|---|---|---|---|---|---|---|
| 2024-01-15 09:30:00 | 2024-01-01 | 2024-01-01 | 2024-01-01 | 2024-01-15 | 2024-01-15 | 2024-01-15 09:00:00 |
| 2024-01-20 14:45:00 | 2024-01-01 | 2024-01-01 | 2024-01-01 | 2024-01-15 | 2024-01-20 | 2024-01-20 14:00:00 |
| 2024-02-05 11:00:00 | 2024-01-01 | 2024-01-01 | 2024-02-01 | 2024-02-05 | 2024-02-05 | 2024-02-05 11:00:00 |
SELECT
DATE_TRUNC('month', tanggal_order) AS bulan,
COUNT(*) AS jumlah_order,
SUM(total) AS revenue
FROM transaksi
GROUP BY DATE_TRUNC('month', tanggal_order)
ORDER BY bulan;
Lebih clean daripada pake EXTRACT untuk year dan month terpisah.
Fungsi buat nambah atau ngurangin waktu dari tanggal.
SELECT
tanggal_order,
tanggal_order + INTERVAL '7 days' AS seminggu_kemudian,
tanggal_order - INTERVAL '1 month' AS sebulan_sebelum,
tanggal_order + INTERVAL '2 hours' AS dua_jam_kemudian
FROM transaksi
LIMIT 3;
SELECT
tanggal_order,
DATE_ADD(tanggal_order, INTERVAL 7 DAY) AS seminggu_kemudian,
DATE_SUB(tanggal_order, INTERVAL 1 MONTH) AS sebulan_sebelum,
DATE_ADD(tanggal_order, INTERVAL 2 HOUR) AS dua_jam_kemudian
FROM transaksi
LIMIT 3;
SELECT
id,
tanggal_order,
tanggal_order + INTERVAL '1 day' AS estimasi_kirim,
tanggal_order + INTERVAL '3 days' AS estimasi_sampai
FROM transaksi;
SELECT *
FROM transaksi
WHERE tanggal_order >= CURRENT_DATE - INTERVAL '30 days';
Di PostgreSQL, kamu bisa langsung kurangin tanggal.
SELECT
id,
tanggal_order,
tanggal_selesai,
tanggal_selesai - tanggal_order AS selisih_interval,
EXTRACT(DAY FROM (tanggal_selesai - tanggal_order)) AS selisih_hari,
-- Atau pake DATE biar dapet integer
DATE(tanggal_selesai) - DATE(tanggal_order) AS selisih_hari_int
FROM transaksi;
Hasil:
| id | tanggal_order | tanggal_selesai | selisih_interval | selisih_hari | selisih_hari_int |
|---|---|---|---|---|---|
| 1 | 2024-01-15 09:30:00 | 2024-01-18 10:15:00 | 3 days 00:45:00 | 3 | 3 |
| 2 | 2024-01-20 14:45:00 | 2024-01-23 16:00:00 | 3 days 01:15:00 | 3 | 3 |
MySQL punya fungsi DATEDIFF khusus.
SELECT
id,
tanggal_order,
tanggal_selesai,
DATEDIFF(tanggal_selesai, tanggal_order) AS selisih_hari
FROM transaksi;
SELECT
nama,
tanggal_lahir,
EXTRACT(YEAR FROM AGE(CURRENT_DATE, tanggal_lahir)) AS umur
FROM customers;
Hasil:
| nama | tanggal_lahir | umur |
|---|---|---|
| Budi Santoso | 1990-05-15 | 34 |
| Siti Rahayu | 1995-08-22 | 29 |
| Andi Wijaya | 1988-12-03 | 36 |
| Dewi Lestari | 1992-02-28 | 32 |
SELECT
AVG(DATE(tanggal_selesai) - DATE(tanggal_order)) AS avg_delivery_days,
MIN(DATE(tanggal_selesai) - DATE(tanggal_order)) AS min_delivery_days,
MAX(DATE(tanggal_selesai) - DATE(tanggal_order)) AS max_delivery_days
FROM transaksi;
SELECT
tanggal_order,
TO_CHAR(tanggal_order, 'DD-MM-YYYY') AS format_indonesia,
TO_CHAR(tanggal_order, 'DD Mon YYYY') AS format_singkat,
TO_CHAR(tanggal_order, 'Day, DD Month YYYY') AS format_lengkap,
TO_CHAR(tanggal_order, 'HH24:MI:SS') AS waktu_24jam,
TO_CHAR(tanggal_order, 'HH12:MI AM') AS waktu_12jam
FROM transaksi
LIMIT 3;
Hasil:
| tanggal_order | format_indonesia | format_singkat | format_lengkap | waktu_24jam | waktu_12jam |
|---|---|---|---|---|---|
| 2024-01-15 09:30:00 | 15-01-2024 | 15 Jan 2024 | Monday, 15 January 2024 | 09:30:00 | 09:30 AM |
SELECT
tanggal_order,
DATE_FORMAT(tanggal_order, '%d-%m-%Y') AS format_indonesia,
DATE_FORMAT(tanggal_order, '%d %b %Y') AS format_singkat,
DATE_FORMAT(tanggal_order, '%W, %d %M %Y') AS format_lengkap
FROM transaksi
LIMIT 3;
| Code | Deskripsi | Contoh |
|---|---|---|
| YYYY / %Y | Tahun 4 digit | 2024 |
| YY / %y | Tahun 2 digit | 24 |
| MM / %m | Bulan 2 digit | 01 |
| Mon / %b | Bulan singkat | Jan |
| Month / %M | Bulan lengkap | January |
| DD / %d | Hari 2 digit | 15 |
| Day / %W | Nama hari | Monday |
| HH24 / %H | Jam 24 format | 14 |
| HH12 / %h | Jam 12 format | 02 |
| MI / %i | Menit | 30 |
| SS / %s | Detik | 45 |
Kadang data tanggal dateng dalam bentuk string. Kamu perlu convert dulu.
SELECT
TO_DATE('15-01-2024', 'DD-MM-YYYY') AS dari_string,
TO_TIMESTAMP('15-01-2024 09:30:00', 'DD-MM-YYYY HH24:MI:SS') AS dengan_waktu;
SELECT
STR_TO_DATE('15-01-2024', '%d-%m-%Y') AS dari_string,
STR_TO_DATE('15-01-2024 09:30:00', '%d-%m-%Y %H:%i:%s') AS dengan_waktu;
SELECT
CAST('2024-01-15' AS DATE) AS tanggal,
CAST('2024-01-15 09:30:00' AS TIMESTAMP) AS timestamp;
Ini yang sering bikin pusing. Indonesia punya 3 timezone: WIB, WITA, WIT.
SELECT
tanggal_order,
tanggal_order AT TIME ZONE 'UTC' AS utc,
tanggal_order AT TIME ZONE 'Asia/Jakarta' AS wib,
tanggal_order AT TIME ZONE 'Asia/Makassar' AS wita,
tanggal_order AT TIME ZONE 'Asia/Jayapura' AS wit
FROM transaksi
LIMIT 2;
-- PostgreSQL
SET timezone = 'Asia/Jakarta';
-- MySQL
SET time_zone = '+07:00';
Kalau data kamu disimpan dalam UTC (common practice), konversi ke WIB buat reporting:
SELECT
id,
tanggal_order AS utc_time,
tanggal_order AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Jakarta' AS wib_time
FROM transaksi;
SELECT
DATE(tanggal_order) AS tanggal,
COUNT(*) AS jumlah_order,
SUM(total) AS revenue
FROM transaksi
GROUP BY DATE(tanggal_order)
ORDER BY tanggal;
WITH weekly_data AS (
SELECT
DATE_TRUNC('week', tanggal_order) AS minggu,
SUM(total) AS revenue
FROM transaksi
GROUP BY DATE_TRUNC('week', tanggal_order)
)
SELECT
minggu,
revenue,
LAG(revenue) OVER (ORDER BY minggu) AS revenue_minggu_lalu,
revenue - LAG(revenue) OVER (ORDER BY minggu) AS selisih
FROM weekly_data
ORDER BY minggu;
SELECT
SUM(total) AS mtd_revenue
FROM transaksi
WHERE tanggal_order >= DATE_TRUNC('month', CURRENT_DATE)
AND tanggal_order < CURRENT_DATE + INTERVAL '1 day';
SELECT
EXTRACT(MONTH FROM tanggal_order) AS bulan,
SUM(CASE WHEN EXTRACT(YEAR FROM tanggal_order) = 2024 THEN total ELSE 0 END) AS revenue_2024,
SUM(CASE WHEN EXTRACT(YEAR FROM tanggal_order) = 2023 THEN total ELSE 0 END) AS revenue_2023
FROM transaksi
WHERE EXTRACT(YEAR FROM tanggal_order) IN (2023, 2024)
GROUP BY EXTRACT(MONTH FROM tanggal_order)
ORDER BY bulan;
WITH first_order AS (
SELECT
customer_id,
MIN(DATE_TRUNC('month', tanggal_order)) AS cohort_month
FROM transaksi
GROUP BY customer_id
)
SELECT
cohort_month,
COUNT(*) AS new_customers
FROM first_order
GROUP BY cohort_month
ORDER BY cohort_month;
-- POTENSI MASALAH
SELECT * FROM transaksi
WHERE tanggal_order = '2024-01-15'; -- Ini '2024-01-15 00:00:00'
-- LEBIH AMAN
SELECT * FROM transaksi
WHERE DATE(tanggal_order) = '2024-01-15';
-- Atau pake range
SELECT * FROM transaksi
WHERE tanggal_order >= '2024-01-15'
AND tanggal_order < '2024-01-16';
-- DATA BISA SALAH kalau timezone ga konsisten
SELECT * FROM transaksi
WHERE tanggal_order >= '2024-01-15';
-- LEBIH BAIK, eksplisit timezone
SELECT * FROM transaksi
WHERE tanggal_order >= '2024-01-15 00:00:00+07';
-- ERROR
SELECT TO_DATE('15/01/2024', 'DD-MM-YYYY'); -- Format ga match!
-- BENAR
SELECT TO_DATE('15/01/2024', 'DD/MM/YYYY');
Best practice: simpan semua timestamp dalam UTC, konversi ke local timezone saat display.
Lebih clean daripada EXTRACT year dan month terpisah.
Kalau sering filter berdasarkan tanggal, pastikan ada index.
Pilih satu format dan stick dengan itu di seluruh aplikasi.
SELECT COALESCE(tanggal_selesai, CURRENT_TIMESTAMP) AS tanggal_final
FROM transaksi;
Coba kerjain query ini:
Soal: Hitung rata-rata waktu dari order sampai selesai (dalam hari dan jam) per bulan. Hasil diurutkan dari yang tercepat.
SELECT
DATE_TRUNC('month', tanggal_order) AS bulan,
COUNT(*) AS total_order,
ROUND(AVG(EXTRACT(EPOCH FROM (tanggal_selesai - tanggal_order)) / 86400), 2) AS avg_hari,
ROUND(AVG(EXTRACT(EPOCH FROM (tanggal_selesai - tanggal_order)) / 3600), 2) AS avg_jam
FROM transaksi
GROUP BY DATE_TRUNC('month', tanggal_order)
ORDER BY avg_hari;
**Hasil:**
| bulan | total_order | avg_hari | avg_jam |
|-------|-------------|----------|---------|
| 2024-02-01 | 2 | 3.03 | 72.75 |
| 2024-01-01 | 2 | 3.04 | 73.00 |
| 2024-03-01 | 3 | 3.36 | 80.56 |
DATE cuma nyimpen tanggal, contohnya 2024-12-23. DATETIME nyimpen tanggal plus waktu sekaligus. TIMESTAMP mirip DATETIME, tapi ada varian TIMESTAMP WITH TIME ZONE yang nyimpen info timezone juga. Buat data transaksi, pake TIMESTAMP biar jam kejadiannya kerekam. Kalau cuma butuh tanggal lahir atau tanggal libur, DATE udah cukup kok.
EXTRACT buat ngambil satu bagian dari tanggal. Bulan doang atau tahun doang. DATE_TRUNC buat "membulatkan" tanggal ke awal periode, contohnya 2024-01-15 jadi 2024-01-01. Buat grouping laporan bulanan, DATE_TRUNC lebih clean soalnya hasilnya tetap tanggal yang bisa di-sort langsung. EXTRACT lebih cocok buat analisis kayak jumlah order per hari dalam seminggu.
Best practice-nya simpan semua timestamp dalam UTC, lalu konversi ke WIB pas bikin report. Di PostgreSQL pake AT TIME ZONE 'Asia/Jakarta', di MySQL set time_zone = '+07:00'. Indonesia punya 3 timezone: WIB, WITA, WIT. Kalau user kamu tersebar, konversi di layer display aja biar datanya konsisten.
PostgreSQL emang nggak punya fungsi DATEDIFF kayak MySQL. Gantinya, kurangin langsung dua tanggalnya: DATE(tanggal_selesai) - DATE(tanggal_order) hasilnya integer jumlah hari. Kalau butuh selisih dalam jam atau menit, pake EXTRACT(EPOCH FROM ...) terus bagi 3600 buat jam. Konsepnya sama, cuma syntax-nya beda dikit.
Date functions itu skill wajib buat Data Analyst. Inget poin-poin ini:
Latihan yang banyak ya, karena setiap database punya sedikit perbedaan syntax. Yang penting kamu paham konsepnya, tinggal adjust syntax-nya aja.
Sekarang giliran kamu praktekin query-query di atas pake database kamu sendiri.
Kalau kamu udah paham date functions, next step-nya:
- Cohort Analysis - analisis retention berbasis waktu
- Window Functions - analisis time-series yang lebih advanced
- GROUP BY dan HAVING - agregasi data per periode
Mau praktek langsung? Mulai latihan SQL gratis
Latihan interaktif, langsung di browser.
Analisa per kuartal, hari kerja, atau musim jadi ribet kalau tiap query ngitung ulang atribut tanggal. Tabel kalender nyimpen semua atribut itu sekali, biar tinggal di-JOIN.
Laporan penjualan harian sering bolong di hari tanpa transaksi. Ini cara bikin deret tanggal lengkap di SQL biar tiap hari muncul, walau nilainya nol.
Struktur organisasi tersimpan sebagai kolom id_atasan yang saling nunjuk. Ini cara narik rantai jabatan, hitung total bawahan, dan span of control cuma pakai SQL.