- Antipola SQL membuat query dan pipeline data lebih sulit dipelihara, serta menyebabkan performa yang lebih lambat dari perkiraan
- Contoh yang umum meliputi penggunaan berlebihan CASE WHEN, menerapkan fungsi pada kolom berindeks, SELECT *, penggunaan DISTINCT berlebihan, view dan subquery bertingkat, serta struktur dependensi yang dalam
- Sebagian besar masalah berasal dari solusi sementara karena tekanan kecepatan dan tenggat waktu, yang dalam jangka panjang merusak keandalan data sekaligus kecepatan pengembangan
- Solusinya mencakup definisi join yang jelas, pemanfaatan dimension table, penghapusan nesting yang tidak perlu, dan pembersihan view secara berkala
- SQL harus diperlakukan bukan sebagai sekadar skrip, melainkan sebagai kode produksi yang dikelola pada tingkat tim, dan desain awal yang mempertimbangkan keterbacaan dapat mengurangi pengerjaan ulang
Pengenalan
- Hari ini akan dibahas beberapa contoh antipola SQL yang sering muncul dan berdampak besar
- Masalah-masalah ini memicu lingkaran setan seperti menurunnya keandalan data dan melambatnya kecepatan pengembangan query
- Daftar di bawah ini tidak mencakup semua kasus, dan jika ingin pemahaman yang lebih mendalam, buku Bill Karwin sangat direkomendasikan
CASE WHEN yang Terlalu Kompleks
- Dalam sistem berskala besar, klausa CASE WHEN sering digunakan untuk mengubah kode status (misalnya 1=stok habis) agar dapat dibaca manusia
- Menambahkan logika CASE WHEN tersebut hanya ke satu View demi pengembangan dashboard atau laporan yang cepat merupakan sebuah antipola dalam jangka panjang
- Ini memicu duplikasi logika lewat salin-tempel, masalah ketidaksesuaian interpretasi, dan membuat keseluruhan lingkungan query menjadi berantakan
- Solusinya adalah membuat dimension table atau view bersama secara terpisah untuk mengubah kode status sehingga reusability dapat terjaga
Menggunakan Fungsi pada Kolom Berindeks
- Jika fungsi diterapkan pada kolom yang memiliki indeks, seperti dalam “WHERE UPPER(name) = 'ABC'”, efisiensi indeks akan hilang
- Pada SQL Server dan lain-lain, hal ini dapat menyebabkan full table scan yang tidak perlu
- Solusinya adalah mengindeks kolom hasil penerapan fungsi secara terpisah atau mengubah nilai input agar kondisi query menjadi lebih sederhana
Menggunakan SELECT * di View
- Saat mengembangkan view, penggunaan SELECT * memang tampak praktis, tetapi jika struktur (skema) berubah, view dapat dengan mudah rusak
- Karena kolom yang tidak perlu ikut terbawa, ini bisa menimbulkan dependensi yang tidak disengaja serta masalah performa, sehingga pemilihan kolom harus dilakukan secara eksplisit
Menyelesaikan Duplikasi dengan DISTINCT secara Berlebihan
- Ketika hasil duplikat muncul karena join yang salah, menyelesaikannya sementara dengan SELECT DISTINCT justru menyembunyikan masalah integritas data
- Akar masalahnya adalah ketidaklengkapan kondisi join atau kesalahan dalam definisi relasi (1:1, 1:N, dan sebagainya)
- Solusi yang benar adalah memperjelas definisi relasi dengan memperkuat logika join dan memastikan konsistensi relasi sebelum agregasi atau pelaporan
View Bertingkat Berlebihan (Excessive View Layer Stacking)
- Jika beberapa tim terus menambahkan view baru sambil menggunakan ulang view yang sudah ada, rantai dependensi menjadi kompleks dan performa menurun tajam
- Debugging menjadi sulit, dan perluasan query bisa terasa sesulit “penggalian arkeologis”
- Diperlukan strategi untuk secara berkala meratakan (flatten) logika transformasi, dan untuk operasi yang kompleks, mematerialisasikannya (materialize) ke base view atau tabel yang jelas
Subquery yang Terlalu Dalam
- Subquery yang bersarang terlalu dalam, lebih dari 3–4 tingkat, menurunkan keterbacaan dan menyulitkan debugging
- Bahkan ada kasus yang menangani subquery dengan panjang lebih dari 5000 baris
- Dengan memanfaatkan CTE(Common Table Expression), pemisahan tahap logis menjadi lebih mudah dan kejelasan (readability) query meningkat
Kesimpulan
- SQL di permukaan memang tampak sederhana, tetapi seiring membesarnya sistem, kompleksitasnya juga meningkat
- Sebagian besar antipola muncul bukan karena niat buruk, melainkan sebagai hasil kompromi demi ‘hasil cepat’ (kecepatan, tenggat, solusi sementara)
- Jika SQL dikelola seperti kode (version control, code review, desain yang jelas), dalam jangka panjang produktivitas dan keandalan dapat diperoleh sekaligus
- Dengan meluangkan beberapa menit untuk desain awal dan memikirkan kejelasan serta konsistensi, pengerjaan ulang dan kebingungan di masa depan dapat sangat dikurangi
5 komentar
Karena sekarang tidak ada waktu, kita bilang cukup padamkan dulu masalah yang mendesak lalu tulis ulang nanti, tapi kalau itu menumpuk akhirnya jadi neraka kueri yang mengerikan. Saya juga sudah membuat banyak yang seperti itu. Meski tahu bahwa kata "nanti" untuk menulis ulang itu tidak akan pernah benar-benar datang.
Duh...
“Sebagian besar masalah adalah solusi sementara yang muncul karena tekanan kecepatan dan tenggat waktu”
Hiks..
Komentar Hacker News
DISTINCTdipakai dalam query, saya curiga penulisnya tidak benar-benar paham model data atau teori himpunan, atau mungkin keduanyaDISTINCTjuga bisa menjadi sinyal skema yang terlalu dinormalisasi. Misalnya, saya rasa tidak perlu sampai membuat tabel sepertiaddresses_citieshanya untuk mencegah nama kota ganda tercatatDISTINCTdimasukkan ke dalam CTE, performanya meningkat drastis. Sepertinya query planner bisa melakukan optimisasi jika keunikan record terjaminLIMIT 1dalam query karena mengira hasilnya paling banyak hanya satu, lalu mendapat umpan balik bahwa itu juga kurang baik. Namun pada tabel besar, setelah menemukan record yang diinginkan pun DB cenderung tetap terus menelusuri seluruh tabel (sqlite,mysql,postgresqlsemua begitu)DISTINCTaman dihilangkan pada querySELECT x FROM t. Meski dari skematterlihat bahwaxpunya constraintPRIMARYatauUNIQUE, seseorang bisa saja segera menghapus constraintUNIQUEitu. Lalu duplikasi muncul dan kita jadi bingung kenapa. SQL bukan bahasa himpunan (set), melainkan bahasa bag. Saatrel tdan atributxditemukan di runtime, SQL tinggal mengembalikannya apa adanya. Bisa saja ada duplikasi, atau tipenya berubah. Jika yang diinginkan adalahSet, makaDISTINCTharus dinyatakan secara eksplisit. Query planner lalu tidak akan melakukan deduplikasi saat runtime jika memangUNIQUEatauPRIMARYDISTINCTitu wajib. Terutama saat memakai relasi panjang variabel, tanpaDISTINCThasilnya jadi lambat dan penuh duplikasiDISTINCThttps://kb.databasedesignbook.com/posts/systematic-design-of-join-queries/
!=atauNOT IN (...)hampir selalu tidak efisien (meski bisa saja oke jika himpunan hasil sudah cukup dipersempit oleh kondisi lain). Dan penting juga memahami bagaimana DB menanganinull. Apakahnullsama dengan string kosong, apakahnull == null, itu bisa berbeda antar-DBnulldan indexing, DB yang pernah saya pakai tidak mengindeks nilainull, jadi queryWHERE col IS NULLtetap berjalan tidak efisien walau ada indeks padacol. Kalau memang sangat perlu, saya sarankan membuat kolomchar(1)ataubityang menandai apakahcolbernilai null, lalu mengindeks field itu!=atauNOT IN (...)hampir selalu tidak efisien, saya penasaran kenapanya. Jika nilai di sisi kanan berupa konstanta, bukankah itu bisa jadi lookup hash table dan biasanya efisien? Saya ingin tahu apakah ada alternatif yang lebih efisienDISTINCTdiperlukan, bisa jadi desain primary key-nya tidak tepat. Jikaviewmenumpuk terlalu banyak, berarti desain tabel dasarnya keliru. Pemodelan DB yang baik mencegah semua masalah ini sejak awalhttps://en.wikipedia.org/wiki/Sargable
https://www.brentozar.com/blitzcache/non-sargable-predicates/
sargable, jawaban StackOverflow ini membantu https://dba.stackexchange.com/a/217983Kata "sargable" adalah portmanteau dari "Search ARGument ABLE"
CASE WHENbisa diatasi dengan menyatukan logika di satu tempat memakai UDF (User Defined Function)Menggunakan fungsi pada kolom yang diindeks adalah sinyal bahwa query tersebut tidak sargable
Sebagai pengganti penyalahgunaan
DISTINCT, query untuk menghapus duplikasi sesuai grain tabel di tengah fan-out turunan dari join seperti ini bisa sangat berguna. Beberapa DB mendukung fiturQUALIFY, sehingga query menjadi jauh lebih rapipenjelasan sargable
QUALIFY in Redshift
sqlite, menurut saya begituviewbertingkat justru memang diperlukan. Di software POS kami, kami banyak memakai nestedviewuntuk membuat backboneviewyang menampilkan transaksi secara bersih sekaligus. Kalau tidak begitu, kami harus menulis klausawhereberbeda untuk tiap tabel, dan menangani kondisi seperti void/retur/pembatalan setiap kali, sehingga jika ada perubahan kami harus memperbaiki puluhanview/procedure. Dalam kasus kami, memanfaatkan nestedviewjauh lebih praktisUPPER(name)) setidaknya bukan yang terbaik di MS SQL Server. Saya kurang tahu dukungan di DB lain, tetapi solusi yang lebih baik adalah membuat computed column yang memang case-insensitive lewatCOLLATE(sesuaikan menurut preferensi)Yang paling penting malah terlewat.