Foto oleh panumas nikhomkhai via Pexels

BETWEEN dan IN adalah dua operator yang mempermudah penulisan filter untuk rentang nilai dan daftar nilai tertentu. Keduanya membuat query lebih ringkas dibanding menulis banyak kondisi OR secara manual, namun masing-masing memiliki perilaku khusus yang perlu dipahami agar tidak menghasilkan bug logika yang tidak disadari.

Artikel ini membahas penggunaan BETWEEN dan IN beserta perbedaan keduanya, termasuk jebakan umum saat menggabungkannya dengan NOT.

Memfilter Rentang Nilai dengan BETWEEN

BETWEEN bersifat inklusif pada kedua batas

-- Transaksi dengan total antara 100000 dan 500000 (inklusif) SELECT * FROM Orders WHERE TotalAmount BETWEEN 100000 AND 500000; -- Setara dengan SELECT * FROM Orders WHERE TotalAmount >= 100000 AND TotalAmount <= 500000; -- Expected: transaksi dengan nilai PERSIS 100000 atau 500000 -- IKUT TERMASUK dalam hasil

Perlu diingat, BETWEEN bersifat inklusif — nilai batas bawah dan batas atas ikut disertakan dalam hasil. Ini penting dipahami terutama saat BETWEEN dipakai pada rentang tanggal, karena bisa menyebabkan data tidak terduga ikut masuk atau justru hilang jika batas waktunya kurang presisi.

Jebakan BETWEEN pada Rentang Tanggal

BETWEEN pada DATETIME rawan melewatkan data di hari terakhir

-- BAHAYA: BETWEEN pada DATETIME bisa melewatkan data SELECT * FROM Orders WHERE OrderDate BETWEEN '2026-01-01' AND '2026-01-31'; -- Expected: order pada '2026-01-31 14:30:00' TIDAK TERMASUK -- karena '2026-01-31' diartikan sebagai '2026-01-31 00:00:00' -- LEBIH AMAN: tentukan batas akhir secara eksplisit SELECT * FROM Orders WHERE OrderDate >= '2026-01-01' AND OrderDate < '2026-02-01';

Memfilter Daftar Nilai dengan IN

IN menggantikan banyak OR

-- Tanpa IN, menulis OR berkali-kali SELECT * FROM Orders WHERE Status = 'Shipped' OR Status = 'Delivered' OR Status = 'Returned'; -- Dengan IN, lebih ringkas SELECT * FROM Orders WHERE Status IN ('Shipped', 'Delivered', 'Returned'); -- Expected: kedua query menghasilkan baris yang identik

Jebakan NOT IN dengan Subquery Mengandung NULL

NOT EXISTS lebih aman dibanding NOT IN dengan subquery

-- BAHAYA: jika subquery mengandung NULL, hasil NOT IN bisa KOSONG SELECT * FROM Customers WHERE CustomerId NOT IN (SELECT CustomerId FROM Orders WHERE CustomerId IS NOT NULL OR 1=1); -- Jika subquery tanpa filter NULL dan ada satu CustomerId NULL, -- SELURUH hasil NOT IN akan kosong meski seharusnya ada data -- LEBIH AMAN: gunakan NOT EXISTS SELECT * FROM Customers c WHERE NOT EXISTS ( SELECT 1 FROM Orders o WHERE o.CustomerId = c.CustomerId );

Menggabungkan IN dengan Subquery

IN tidak harus diisi daftar nilai statis — subquery yang mengembalikan satu kolom juga bisa dipakai langsung sebagai sumber daftar, membuat query lebih dinamis mengikuti data yang sebenarnya ada di database.

IN dengan subquery dinamis

SELECT FullName FROM Customers WHERE CustomerId IN ( SELECT CustomerId FROM Orders WHERE TotalAmount > 1000000 ); -- Expected: hanya pelanggan yang PERNAH melakukan transaksi -- di atas 1 juta yang muncul di hasil

Pendekatan ini jauh lebih fleksibel dibanding menulis daftar ID secara manual, karena daftar tersebut otomatis mengikuti perubahan data tanpa perlu mengubah teks query setiap kali data baru masuk.

Baik BETWEEN, IN, maupun NOT IN pada akhirnya hanyalah bentuk ringkas dari kombinasi operator perbandingan dasar, sehingga memahami cara kerja aslinya di balik layar membantu menghindari jebakan logika yang sudah dibahas di atas, terutama saat bekerja dengan data yang kompleks dan melibatkan banyak subquery bertingkat.

Perilaku BETWEEN pada Tipe Tanggal dan Batas Inklusif

BETWEEN pada kolom datetime dan cara aman mencakup satu hari penuh

-- Jebakan umum: baris tanggal 2024-01-31 dengan jam -- tidak ikut terambil karena BETWEEN bersifat inklusif -- terhadap nilai literal, bukan terhadap keseluruhan hari SELECT OrderID, OrderDate FROM Orders WHERE OrderDate BETWEEN '2024-01-01' AND '2024-01-31'; -- Baris dengan OrderDate '2024-01-31 14:30:00' TIDAK ikut -- Pendekatan yang aman untuk mencakup seluruh hari terakhir SELECT OrderID, OrderDate FROM Orders WHERE OrderDate >= '2024-01-01' AND OrderDate < '2024-02-01';

BETWEEN bersifat inklusif pada kedua batasnya, artinya BETWEEN 1 AND 10 mencakup nilai 1 dan 10 itu sendiri, bukan hanya nilai di antara keduanya. Sifat inklusif ini menjadi sumber bug tersembunyi ketika dipakai pada kolom bertipe DATETIME, karena literal '2024-01-31' sebenarnya direpresentasikan sebagai '2024-01-31 00:00:00.000', sehingga baris dengan jam di atas tengah malam pada tanggal tersebut tidak akan ikut terambil.

Solusi yang lebih aman untuk rentang tanggal adalah menulis kondisi secara eksplisit menggunakan operator lebih besar sama dengan untuk batas bawah dan lebih kecil untuk batas atas hari berikutnya, seperti contoh kedua di atas, sehingga seluruh jam pada tanggal terakhir yang dimaksud tetap tercakup tanpa bergantung pada asumsi jam default tengah malam.

Sementara itu, operator IN berguna untuk memeriksa keanggotaan nilai pada sebuah daftar diskrit, bukan rentang kontinu, misalnya WHERE Status IN ('Pending', 'Processing', 'Shipped'); IN jauh lebih ringkas dibanding menuliskan beberapa kondisi OR yang setara, dan SQL Server mengoptimalkan keduanya dengan cara yang serupa pada level execution plan.

IN dengan Subquery dibandingkan Banyak Kondisi OR

IN dengan daftar statis vs IN dengan subquery

-- Menggunakan OR berulang untuk beberapa nilai statis SELECT * FROM dbo.Pesanan WHERE StatusPesanan = 'Pending' OR StatusPesanan = 'Diproses' OR StatusPesanan = 'Dikirim'; -- Pola yang sama memakai IN, lebih ringkas dan mudah dibaca SELECT * FROM dbo.Pesanan WHERE StatusPesanan IN ('Pending', 'Diproses', 'Dikirim'); -- IN dengan subquery: memfilter berdasarkan hasil query lain SELECT * FROM dbo.Pesanan WHERE PelangganID IN ( SELECT PelangganID FROM dbo.Pelanggan WHERE Kota = 'Surabaya' ); -- Hasil: hanya pesanan milik pelanggan yang berdomisili di Surabaya

Secara fungsional, IN dengan daftar nilai statis dan rangkaian OR yang setara akan menghasilkan rencana eksekusi yang identik, karena SQL Server Query Optimizer menerjemahkan keduanya menjadi bentuk logika yang sama persis sebelum dioptimasi. Perbedaan di antara keduanya murni soal keterbacaan kode; IN jauh lebih ringkas ditulis dan dibaca ketika daftar nilai yang dibandingkan cukup banyak, dibanding menuliskan kondisi OR berulang untuk kolom yang sama.

Situasi menjadi berbeda ketika IN dipakai bersama subquery, seperti mencari PelangganID dari table Pelanggan berdasarkan kota tertentu. Pada kasus ini, SQL Server perlu mengevaluasi subquery tersebut, dan jika subquery mengembalikan baris dalam jumlah besar, optimizer akan mempertimbangkan strategi seperti mengubahnya menjadi semi-join internal yang mirip dengan EXISTS, bukan benar-benar mencocokkan satu per satu seperti pada rangkaian OR statis.

Untuk subquery yang berpotensi mengembalikan banyak baris atau bahkan mengandung nilai NULL, EXISTS sering kali menjadi alternatif yang lebih aman dan performanya lebih stabil dibanding IN, karena EXISTS berhenti mencari begitu menemukan satu baris yang cocok, sementara IN dengan subquery yang mengandung NULL pada kolom pembanding dapat menghasilkan perilaku tidak terduga akibat logika NULL tiga-nilai pada SQL Server.

Jebakan NOT IN dengan Subquery yang Mengandung NULL

NOT IN vs NOT EXISTS dengan NULL

-- Jebakan: jika subquery mengandung satu saja NULL, -- NOT IN tidak mengembalikan baris apa pun SELECT NamaProduk FROM dbo.Produk WHERE ProdukID NOT IN ( SELECT ProdukID FROM dbo.ProdukDiskon -- misal ada 1 baris NULL ); -- Hasil: kosong, padahal seharusnya ada produk yang tidak didiskon -- Solusi aman memakai NOT EXISTS SELECT NamaProduk FROM dbo.Produk p WHERE NOT EXISTS ( SELECT 1 FROM dbo.ProdukDiskon d WHERE d.ProdukID = p.ProdukID );

NOT IN dengan subquery menyimpan jebakan tersembunyi: jika hasil subquery mengandung satu saja baris dengan nilai NULL pada kolom yang dibandingkan, seluruh query NOT IN akan mengembalikan hasil kosong, bukan error, karena logika tiga-nilai SQL Server membuat perbandingan terhadap NULL menghasilkan UNKNOWN yang kemudian membatalkan seluruh kondisi NOT IN secara keseluruhan.

NOT EXISTS tidak memiliki masalah ini karena cara kerjanya berbeda: ia memeriksa keberadaan baris yang cocok lewat korelasi langsung, bukan membandingkan terhadap daftar nilai yang dikembalikan subquery. Karena alasan keamanan ini, NOT EXISTS umumnya lebih direkomendasikan dibanding NOT IN setiap kali subquery yang dipakai berpotensi mengandung nilai NULL pada kolom pembandingnya.

Kesalahan Umum yang Sering Terjadi

Memakai BETWEEN pada rentang DATETIME tanpa mempertimbangkan bagian jam

BETWEEN tanggal akhir yang ditulis tanpa jam (misalnya '2026-01-31') diartikan sebagai tengah malam, sehingga transaksi pada hari itu setelah jam 00:00:00 bisa tidak ikut terhitung.

Memakai NOT IN dengan subquery yang berpotensi mengandung NULL

Jika daftar nilai dari subquery mengandung satu saja nilai NULL, seluruh hasil NOT IN akan kosong secara tidak terduga, sebuah perilaku yang sering membingungkan karena tidak ada pesan error sama sekali.

Kapan Pendekatan Ini Tidak Cocok Dipakai

BETWEEN dan IN membuat query jauh lebih ringkas, namun untuk filter rentang tanggal yang kritikal (seperti laporan keuangan akhir bulan), lebih aman menulis perbandingan eksplisit dengan operator >= dan < daripada mengandalkan BETWEEN yang berisiko salah interpretasi batas waktu.

Ringkasan

BETWEEN dan IN membuat query filtering jauh lebih ringkas dibanding menumpuk banyak kondisi AND/OR secara manual, sekaligus membuat maksud query lebih mudah dipahami oleh developer lain, selama kedua jebakan di atas — rentang tanggal dan NOT IN dengan NULL — selalu diwaspadai.

Seri Belajar SQL Server:

← Sebelumnya: Pencarian Pola dengan LIKE

→ Selanjutnya: Logika Kondisional dengan CASE WHEN