54 puan yazan GN⁺ 2025-10-19 | 5 yorum | WhatsApp'ta paylaş
  • SQL anti-pattern’leri, sorguların ve veri pipeline’larının bakımını zorlaştırır ve beklenenden daha yavaş performansa yol açar
  • CASE WHEN’in aşırı kullanımı, indeksli kolonlara fonksiyon uygulanması, SELECT *, DISTINCT’in aşırı kullanımı, iç içe view’lar ve alt sorgular, derin bağımlılık yapıları bunun başlıca örnekleridir
  • Bu sorunların çoğu, hız ve teslim tarihi baskısı nedeniyle geliştirilen geçici çözümlerden kaynaklanır; uzun vadede hem veri güvenilirliğine hem de geliştirme hızına zarar verir
  • Çözüm olarak açık join tanımları, boyut tablolarının kullanımı, gereksiz iç içeliğin kaldırılması, view’ların düzenli olarak temizlenmesi gerekir
  • SQL’e basit bir script gibi değil, ekip düzeyinde yönetilen üretim kodu gibi yaklaşmak gerekir; okunabilirliği gözeten ilk tasarım yeniden çalışma ihtiyacını azaltır

Giriş

  • Bugün, sık görülen ve etkisi büyük olan bazı SQL anti-pattern örneklerine odaklanılıyor
  • Bu sorunlar veri güvenilirliğinin düşmesi, sorgu geliştirme hızının yavaşlaması gibi bir kısır döngü yaratır
  • Aşağıdaki liste tüm örnekleri kapsamaz; daha derin bir anlayış için Bill Karwin’in kitabı önerilir

Aşırı karmaşık CASE WHEN ifadeleri

  • Büyük sistemlerde durum kodlarını (ör. 1=stokta yok) insanların okuyabileceği hale getirmek için CASE WHEN ifadeleri sık kullanılır
  • Dashboard veya raporların hızlı geliştirilmesi için bu CASE WHEN mantığını yalnızca tek bir View’a eklemek, uzun vadede bir anti-pattern sayılır
  • Bu yaklaşım, yinelenen mantığın kopyala-yapıştır edilmesine ve yorum farklarına yol açar; tüm sorgu ortamını karmaşıklaştırır
  • Çözüm, durum kodlarını dönüştüren ayrı bir boyut tablosu (dimension table) veya ortak view oluşturarak yeniden kullanılabilirliği sağlamaktır

İndeksli kolonlarda fonksiyon kullanımı

  • WHERE UPPER(name) = 'ABC' örneğinde olduğu gibi indeksli bir kolona fonksiyon uygulandığında indeks verimliliği ortadan kalkar
    • SQL Server gibi sistemlerde bu durum gereksiz bir full table scan oluşmasına neden olabilir
  • Çözüm, fonksiyon uygulanmış kolonu ayrıca indekslemek veya giriş değerini dönüştürerek sorgu koşulunu sadeleştirmektir

View içinde SELECT * kullanımı

  • View geliştirirken SELECT * kullanmak pratik görünebilir, ancak yapı (schema) değiştiğinde view kolayca bozulabilir
  • Gereksiz kolonlar da dahil edildiği için istenmeyen bağımlılıklar ve performans sorunları ortaya çıkar; bu yüzden kolonlar açıkça seçilmelidir

DISTINCT ile tekrarları “çözme” alışkanlığı

  • Hatalı join’ler nedeniyle yinelenen sonuçlar oluştuğunda, bunu SELECT DISTINCT ile geçici olarak çözmek veri bütünlüğü sorununu gizler
    • Asıl neden, join koşullarının eksik olması veya ilişki tanımının (1:1, 1:N vb.) hatalı kurulmasıdır
    • Doğru çözüm, join mantığını güçlendirerek ilişki tanımını netleştirmek ve agregasyon ya da raporlama öncesinde ilişki tutarlılığını sağlamaktır

View katmanlarının iç içe binmesi (Excessive View Layer Stacking)

  • Birden çok ekip mevcut view’ları yeniden kullanarak sürekli yeni view’lar eklediğinde, bağımlılık zinciri karmaşıklaşır ve performans hızla düşer
    • Debugging zorlaşır ve sorguyu genişletmek adeta “arkeolojik kazı” seviyesinde güçleşir
  • Dönüştürme mantığını düzenli olarak flatten etmek ve karmaşık işlemleri açık temel view’lara veya tablolara materialize etmek gerekir

Aşırı derin alt sorgular

  • 3-4 seviyeden fazla derin iç içe alt sorgular, okunabilirliği azaltır ve debugging’i zorlaştırır
    • 5000 satırdan uzun alt sorguların kullanıldığı örnekler bile vardır
  • CTE (Common Table Expression) kullanıldığında mantıksal adımları ayırmak kolaylaşır ve sorgunun okunabilirliği (readability) artar

Sonuç

  • SQL yüzeyde basit görünse de sistem büyüdükçe karmaşıklığı artma eğilimindedir
  • Anti-pattern’lerin çoğu kötü niyetten değil, “hızlı sonuç” alma uğruna verilen tavizlerden (hız, teslim tarihi, geçici çözümler) doğar
  • SQL kod gibi yönetildiğinde (sürüm kontrolü, code review, net tasarım) uzun vadede hem verimlilik hem de güvenilirlik birlikte sağlanabilir
  • İlk tasarıma birkaç dakika ayırıp açıklık ve tutarlılığı düşünmek, ileride oluşacak yeniden çalışma ihtiyacını ve karmaşayı büyük ölçüde azaltır

5 yorum

 
aer0700 2025-10-20

Şu an vaktimiz yok, önce acil sorunu çözüp sonra yeniden yazarız dediğimiz şeyler biriktikçe korkunç bir sorgu cehennemine dönüşüyor. Ben de bunlardan epey yaptım. Onları yeniden yazacağımız o “sonra”nın aslında hiç gelmeyeceğini bile bile.

 
firefoxsaiko123 2025-10-20

Hımk...

 
ilikeall 2025-10-20

"Çoğu sorun, hız ve teslim tarihi baskısından kaynaklanan geçici çözümlerden doğar"
Ah ah..

 
GN⁺ 2025-10-19
Hacker News görüşleri
  • Bir sorguda DISTINCT kullanıldığını gördüğümde, yazarın veri modelini ya da küme teorisini tam olarak anlamadığından, hatta belki ikisini de bilmediğinden şüpheleniyorum
    • Bazen DISTINCT, aşırı normalize edilmemiş bir şemanın işareti de olabilir. Örneğin, yinelenen şehir adlarının kaydedilmesini önlemek için ille de addresses_cities gibi bir tablo oluşturmaya gerek olmadığını düşünüyorum
    • Benim deneyimim de neredeyse aynı. Ama yakın zamanda, tüm join’ler doğru olmasına rağmen CTE içine DISTINCT eklediğimde performansın ciddi biçimde iyileştiği bir durum oldu. Kayıtların benzersizliği garanti edildiğinde sorgu planlayıcısı optimizasyon yapabiliyor gibi görünüyor
    • Sorguda en fazla bir sonuç döneceğini düşündüğüm için LIMIT 1 eklediğimde de benzer şekilde bunun iyi olmadığı yönünde geri bildirim almıştım. Ama büyük tablolarda (sqlite, mysql, postgresql hepsinde) istediği kaydı bulduktan sonra bile DB tüm tabloyu taramayı sürdürme eğiliminde oluyor
    • SELECT x FROM t sorgusunda DISTINCT ifadesini güvenle çıkarıp çıkaramayacağımızı nasıl bilebiliriz diye soruyorum. t şemasında x için PRIMARY ya da UNIQUE kısıtı olduğunu görsek bile, biri çıkıp kısa süre sonra UNIQUE kısıtını kaldırabilir. O zaman tekrarlar oluşur ve nedenini anlamaya çalışırız. SQL bir set dili değil, bir bag dilidir. Çalışma anında rel t ve x niteliğini bulursa sadece geri döndürür. Tekrarlar oluşabilir, tip değişebilir. Eğer bir Set istiyorsanız mutlaka DISTINCT belirtmelisiniz. Sorgu planlayıcısı çalışma anında UNIQUE ya da PRIMARY olduğunu görürse deduplication yapmaz
    • Cypher’da ise tam tersi geçerli. neo4j ile karmaşık verilerle çalışırken, yinelenen düğümler sonuca gerçekten çok kolay karışıyor ve DISTINCT zorunlu hale geliyor. Özellikle değişken uzunluklu ilişkiler kullanıldığında DISTINCT olmadan yavaşlıyor ve çok sayıda tekrar oluşuyor
  • DISTINCT olmadan da doğru sorgu yapısını nasıl tasarlayacağınıza dair yaklaşık 9000 karakterlik iki bölümlü bir eğitim yazdım
    https://kb.databasedesignbook.com/posts/systematic-design-of-join-queries/
    • Güzel bir makale. Yer imlerine ekledim. Bir de bunun gerçekten bir kitap olduğunu fark ettim
  • Sık anılmayan konulardan biri de tam olarak “var olmayan şeyi” arayan sorgular. Örneğin != ya da NOT IN (...) kullanımı çoğu durumda verimsizdir (ama başka koşullar sonuç kümesini yeterince daraltmışsa sorun olmayabilir). Ayrıca DB’nin null değerlerini nasıl ele aldığını anlamak da önemli. null ile boş string aynı şey mi, null == null mı, bunlar DB’ye göre değişebilir
    • null işleme ve indeksleme konusunda, kullandığım DB’ler null değerlerini indekslemediği için WHERE col IS NULL sorguları, col üzerinde indeks olsa bile verimsiz çalışıyor. Gerçekten gerekiyorsa col değerinin null olup olmadığını gösteren char(1) ya da bit sütunu oluşturup o alanı indekslemeyi öneririm
    • != ya da NOT IN (...) kullanımının neredeyse her zaman verimsiz olduğu söylenmişti; nedenini merak ediyorum. Sağ taraftaki değer sabitse hash table lookup ile genelde verimli olması gerekmez mi? Daha verimli bir alternatif olup olmadığını merak ediyorum
  • Sunulan “anti-pattern”lerin hepsinin gerçekten anti-pattern olduğunu düşünmüyorum. Sorgu koşullarının indekslerle uyuşmaması sorunu sonuçta indekslerin nasıl çalıştığını yeterince anlamamaktan kaynaklanıyor. Burada anılan sorunların önemli bir kısmı SQL’in kendisinden çok veritabanı şeması tasarımıyla ilgili. DISTINCT gerekiyorsa, primary key tasarımı doğru yapılmamış olabilir. View’ların aşırı katmanlanması da temel tabloların yanlış tasarlandığını gösterir. İyi bir DB modellemesi tüm bu sorunları en baştan önler
  • Bu tür “anti-pattern”ler aslında SQL dil tasarımının sınırlamalarıyla (ya da eksik tasarımıyla) oluşan basit geçici çözümlerden ibaret. Ben SQL veritabanlarında çalışan yeni bir dil geliştiriyorum ve bu sorunların her biri için daha iyi alternatifler üretmek istiyorum. Henüz tamamlanmadı ve dokümantasyonu da eksik, ama ilgilenirseniz https://lutra-lang.org üzerinden geri bildirim almak isterim
    • “SQL veritabanı” ifadesi belirsiz. SQL sadece ilişkisel değil, ilişkisel olmayan bazı DB’lerde de uygulanmış durumda. Uzmanlar SQL’in sorunlarını çok uzun zaman önce fark etmişti; Chris Date ve Hugh Darwen’in Tutorial D’si gibi alternatifler de vardı. Buna rağmen onlarca yıl birikmiş SQL kodu ve araçları yüzünden alternatifler yerleşemedi. Ben SQL sayesinde onlarca yıl iş güvencesi ve düzenli gelir elde ettim; daha iyi bir dile ihtiyaç var ama bir yandan bu duruma olumlu da bakıyorum
    • Proje iyi görünüyor. Biraz daha olgunlaştığında kesinlikle takip edeceğim
  • SQL’i basit bir sorgu dili olarak değil de gerçek bir programlama dili olarak görmemek en büyük anti-pattern. Kod stilini tutarlı biçimde girintileyip mantıksal olarak ilişkili parçaları birlikte gruplamayı öneriyorum. Alt sorguları CTE’ye çevirmeyi tavsiye ederim. Etkili yorum bırakmak da önemli. Benim stilime bakabilirsiniz: https://bentilly.blogspot.com/2011/02/sql-formatting-style.html
    • Böyle kod stili tartışmalarının, uygun bir linter aracı olmadan neredeyse anlamsız olduğunu düşünüyorum
  • Sorgularımı hızlandırmanın ve sunucu kaynak kullanımını azaltmanın en büyük sırrı, sorguları daha sargable hale getirmek oldu
    https://en.wikipedia.org/wiki/Sargable
    https://www.brentozar.com/blitzcache/non-sargable-predicates/
    • sargable kelimesinin pratikte hangi topluluklarda kullanıldığını merak ediyorum. 20 yılı aşkın süredir SQL ile çalışıyorum ama bunu manual’lerde, Stack Overflow’da ya da HN’de neredeyse hiç görmedim. Hangi DB’de, hangi şirkette ya da hangi açık kaynak topluluğunda daha yaygın kullanılıyor, merak ediyorum
    • sargable sözcüğünün kökenini araştırırken şu StackOverflow yanıtı yardımcı oldu https://dba.stackexchange.com/a/217983
      sargable kelimesi “Search ARGument ABLE” ifadesinden türetilmiş bir portmanteau’dur
  • CASE WHEN ifadelerinin aşırı kullanımıyla ilgili sorunların çoğu, mantığı tek bir yerde toplamak için UDF (User Defined Function) kullanılarak çözülebilir
    İndeksli bir sütun üzerinde fonksiyon kullanmak, sorgunun sargable olmadığının işaretidir
    DISTINCT aşırı kullanımına alternatif olarak, join’den türeyen fan-out içinden tablo grain’ine uygun şekilde de-dupe etmek için şu tür sorgular yararlı olur:
    ROW_NUMBER() OVER (PARTITION BY <grain> ORDER BY <deterministic sort>) = 1
    
    Bazı DB’lerde QUALIFY özelliği desteklenir ve sorgu çok daha temiz hale gelir
    sargable açıklaması
    Redshift'te QUALIFY
    • sargable olmayan sorunlar expression index ile kolayca çözülebilir. En azından sqlite için böyle olduğunu düşünüyorum
  • View’ları iç içe kullanmanın gerçekten gerekli olduğu durumlar da var. Bizim POS yazılımımızda, transaction’ları tek seferde temiz biçimde görebileceğimiz omurga bir view oluşturmak için iç içe view’ları çok kullanıyoruz. Aksi halde her tablo için where koşullarını ayrı ayrı yazmak, void/iade/iptal gibi çeşitli durumları her seferinde ele almak gerekiyor ve bir değişiklik olduğunda onlarca view/procedure’ı tek tek düzeltmek zorunda kalıyorsunuz. Bizim durumda iç içe view kullanımı çok daha pratik
  • İndeksli sütunlarda fonksiyon kullanma sorununun daha açık anlatılması gerekiyor. İndeksli sütuna fonksiyon uygulanınca indeksin verimliliği kayboluyor ve pratikte full scan oluştuğu için yavaşlıyor. Bunu doğrudan yaşayıp öğrendim
    • Bununla ilgili iyi bilinen bir belge var https://use-the-index-luke.com/sql/where-clause/obfuscation
    • Önerilen çözümün (ör. UPPER(name) sütunu üzerine indeks eklemek) en azından MS SQL Server’da en iyi yaklaşım olmadığını düşünüyorum. Diğer DB’lerin bunu destekleyip desteklemediğinden emin değilim ama daha iyi çözüm, doğrudan case-insensitive bir hesaplanmış sütunu COLLATE ile oluşturmaktır
      ALTER TABLE example ADD name_ci AS name COLLATE SQL_Latin1_General_CI_AS;
      
      (İhtiyaca göre uyarlayın)
    • İlgili blog yazısında bir yazım hatası var. İlk satır büyük harfle yazılmalı. Eğer indeks zaten fonksiyon uygulanmış veri üzerine kurulmuşsa, sorgu sırasında bu bir full scan olmaz. Elbette bu örnekte baştan case-insensitive bir collation kullanmak daha iyi olurdu ama genel olarak fikir geçerli
    • “Bunu doğrudan yaşayıp öğrendim” sanki SQL geliştiricilerinin sloganı gibi. Her durumda SQL uzun süredir oldukça istikrarlı biçimde evriliyor, bu yüzden bu tür tuzakları önceden bilmek uzun süre işe yarıyor
 
ahwjdekf 2025-10-21

En önemli şey eksik kalmış.

  • ORM kullanmak