SQL ipuçları ve püf noktaları
(github.com/ben-n93)- Veri analizi işlerinde sık kullanılan SQL yazma alışkanlıkları ve sorgu kalıplarını derleyen bir liste; bunların tüm RDBMS’lere aynı şekilde uygulanmayabileceği varsayımıyla sunuluyor
- Okunabilirlik açısından önden virgül,
WHERE 1=1, girintileme, CTE, yorumlar veUSINGile sorguları okumayı ve değiştirmeyi kolaylaştıran yaklaşımlar öneriliyor - Veri işlemede anti-join,
QUALIFY,GROUP BY ROLLUP,EXCEPTgibi pratikte sonuç filtreleme, toplam üretme ve tablo farklarını kontrol etmede kullanılan söz dizimleri örneklerle ele alınıyor - Performans ve doğruluk açısından
NULLiçerenNOT IN, örtük tür dönüşümü ve hesaplanmış alan alias çakışmaları sorgu sonuçlarını veya hızını bozabilir - Karmaşık sorgularda yürütme sırası, dokümantasyonu kontrol etme, sütun kaynağını belirtme ve kayıtlı sorgu adı gibi temel alışkanlıklar hata ayıklamayı ve yeniden kullanılabilirliği artırmada önemlidir
SQL yazımında okunabilirliği artıran alışkanlıklar
- Bu depo, yıllar içinde öğrenilen SQL ipuçları ve püf noktalarını derleyen bir listedir; veri analistlerinin günlük işlerinde faydalı olanlara ve SQL’e ilk başladığında bilinmesi iyi olacak konulara odaklanır
- Bazı ipuçları tüm RDBMS’lere uygun olmayabilir
-
Önden virgül ve önden
ANDSELECTbölümünde alanları ayırmak için sondan virgül yerine önden virgül kullanılması önerilir- Yeni bir sütun mu yoksa satıra bölünmüş kod mu olduğu daha net görünür
- Satır uzunlukları farklı olsa bile eksik virgül olup olmadığını bulmak kolaylaşır
- Aynı nedenle
WHEREbölümündeki koşulların önüne de öndenANDkonabilir
-
WHERE 1=1ile koşulları kolay test etmeWHEREbölümüne sahte koşul1=1eklemek, test sırasında koşulları yorum satırına alsanız bile sorgunun bozulmamasını sağlar- Tüm koşullar yorum satırına alınsa bile
1=1kaldığı için sorgu çalışmaya devam edebilir
-
Girintileme ve formatter’lar
-
Karmaşık sorgularda CTE’yi değerlendirin
- inline view’lar 2–3 seviyeden fazla iç içe geçtiğinde, birkaç hafta sonra tekrar bakıldığında anlaşılması zor sorgulara dönüşmeye yatkındır
- CTE, uzun sorguları daha düzenli hale getiren, yeniden kullanılabilirliğe ve hata ayıklamaya yardımcı olan bir yöntem olarak sunulur
-
Yorumlar “neden”i açıklasın
- Zaman geçtikten sonra belirli bir işlemin neden yapıldığını hatırlamak zor olabilir
- Yorumlar genellikle kodun “nasıl” çalıştığından çok, bunun neden yapıldığını açıklamalıdır
- Örnekte, yeni CMS archive video formatını işleyemediği için archive içerikleri hariç tutan koşula yorum eklenir
-
Aynı adlı sütunlarla join için
USING- İki tabloda aynı ada sahip sütunla join yapılırken
USINGkullanmak, join’iON’a göre daha sade ifade edebilir USING, ortak sütunu sonuçta tekilleştirir ve yalnızca bir kez döndürürONkullanırken ortak sütun açıkça belirtilmezseambiguous column namehatası oluşabilir
- İki tabloda aynı ada sahip sütunla join yapılırken
Veri işlemede yararlı söz dizimleri
-
anti-join ile başka tabloda bulunmayan satırları bulma
- anti-join, bir tabloda bulunan ama diğer tabloda eşleşmeyen satırları döndürmek için kullanılır
- Örnek, archive edilmemiş içeriklerin yalnızca
video_iddeğerlerini alma durumunu ele alır - Birden fazla uygulama yöntemi vardır
LEFT JOINsonrasında eşleşen tablonun anahtarıNULLolan satırları filtrelemeNOT INve alt sorgu kullanmaNOT EXISTSve ilişkili alt sorgu kullanmaNOT IN,NULLdeğerleri nedeniyle amaçlandığı gibi çalışmayabileceğinden önerilmez
-
QUALIFYile pencere fonksiyonu sonuçlarını filtrelemeQUALIFY, sorgu sonuçlarını pencere fonksiyonu sonuçlarına göre filtrelemeyi sağlar- inline view olmadan filtreleme yapılabildiği için kod satırı sayısını azaltabilir
- Örnekte ürün başına en iyi 10 pazar
DENSE_RANK()ile seçilir ve ardındanQUALIFYile filtrelenir QUALIFYiçin, yalnızca Snowflake, Amazon Redshift, Google BigQuery gibi büyük veri ambarlarında sağlanıyor göründüğü yönünde bir sınırlama vardır
-
Sütun konumuna dayalı
GROUP BYveORDER BY- Sütun adı yerine sütun konumuyla
GROUP BY 1,ORDER BY 2gibi yazılabilir - Geçici veya tek seferlik sorgular için yararlı olabilir
- Prodüksiyon kodunda her zaman doğrudan sütun adlarına referans verilmesi önerilir
- Sütun adı yerine sütun konumuyla
-
GROUP BY ROLLUPile toplam oluşturmaGROUP BY ROLLUP, ara toplam ve genel toplam oluşturmak için kullanılabilir- Örnek, departman bazında maaş toplamını hesaplarken genel maaş toplamı satırını da oluşturur
- Transact-SQL dokümantasyonu,
ROLLUP’ın sütun ifadeleri kombinasyonları için gruplar oluşturduğunu, grupların sayısını sağdan sola azalttığını ve ara toplamlarla genel toplamlar ürettiğini açıklar COALESCEuygulanırsa genel toplam satırıTotalgibi gösterilebilir- Genel toplam satırının sonucun alt kısmında gelmesi için sıralama sütununa dikkat etmek gerekir
-
EXCEPTile iki sonuç kümesi arasındaki farkı bulmaEXCEPT, ilk sorgu sonucunda olup ikinci sorgu sonucunda olmayan satırları döndürürEXCEPTveUNION ALLbirlikte kullanılarak iki tablonun aynı veriye sahip olup olmadığı doğrulanabilir- Dönen satır yoksa iki tablo aynıdır
- Dönen satır varsa farkı oluşturan neden bu satırlardır
Performansı ve doğruluğu bozan kalıplar
-
NULLalabilen sütunlardaNOT EXISTS,NOT IN’den daha iyidir- Karşılaştırılan sütun
NULLkabul ediyorsaNOT INgenellikleNOT EXISTS’ten daha yavaş olabilir - Bu durum Snowflake’te yaşanmış; PostgreSQL Wiki’deki Don’t Do This,
NOT IN (SELECT ...)ifadesinin iyi optimize edilmediğini belirtir NOT IN, karşılaştırılan değerlerdeNULLvarsa amaçlandığı gibi çalışmaz- Bir sütunun
NULLkabul etmesi, gerçektenNULLdeğer bulunduğu anlamına gelmez; ancak değiştiremeyeceğiniz tablolarla çalışırkenNOT EXISTShız iyileştirmesine yardımcı olabilir
- Karşılaştırılan sütun
-
Örtük tür dönüşümü yavaşlayabilir veya başarısız olabilir
- Bir sütundan farklı veri türündeki bir değer koşula konduğunda veritabanı örtük tür dönüşümü denemesi yapabilir
- Örnek, string türündeki
video_idsütununu integer200050ile karşılaştırma durumunu ele alır - Örtük tür dönüşümüne güvenmek sorunlara yol açabilir
- Dönüştürülemeyen değerler varsa hata oluşabilir
- Her değeri belirtilen türe dönüştürmek için ek çalışma gerektiğinden sorgu yavaşlayabilir
- Sütunla aynı veri türünü kullanmak veya hatalardan kaçınmak için Snowflake’in
TRY_TO_NUMBERgibi fonksiyonları kullanılabilir - Hıza etkisi, işlenen veri kümesinin boyutuna göre değişir
Sık yapılan hatalar
-
NOT INveNULLNOT IN, karşılaştırılan değerlerdeNULLvarsa çalışmazNULL, Unknown’ı temsil ettiği için SQL motoru kontrol edilen değerin listede olmadığını doğrulayamaz- Bu durumda
NOT EXISTSkullanmak bir alternatiftir
-
Hesaplanmış alan alias çakışması
- Hesaplanmış alanın adını mevcut bir sütunla aynı yapmak beklenmeyen davranışlara yol açabilir
- Snowflake’in
GROUP BYdokümantasyonu,GROUP BYbölümündeki bir ad hem sütun adıyla hem alias ile eşleşirse sütun adının kullanılacağını belirtir - Örnekte
LEFT(product, 1) AS productile alias oluşturupGROUP BY productyazıldığında, ilk harfe göre değil özgünproductsütununa göre gruplama yapılır ve 3 satır döner - İki çözüm vardır
product_lettergibi benzersiz alias kullanmakGROUP BY LEFT(product, 1)gibi ifadeyi açıkça belirtmek- Pencere fonksiyonlarında da alias sorunu oluşabilir
- Örnekte
CASEileRobotgelirini 0’a çevirir, ancak bu pencere fonksiyonu çalıştıktan sonra uygulandığı için sıralama beklenenden farklı çıkar - Mümkünse benzersiz alias kullanmak veya pencere fonksiyonunun
ORDER BYiçinde hesaplamayı doğrudan yazmak gerekir
-
Sütunun hangi tabloya ait olduğunu belirtin
- Birden fazla join içeren karmaşık sorgularda değer sorunlarını kaynak tabloya kadar izleyebilmek gerekir
- İki tablo aynı sütun adını paylaştığında sütunun ait olduğu yer belirtilmezse RDBMS hata verebilir
- Örnekte
vc.video_id,metadata.seasongibi tablo alias’ları eklenerek sütun kaynağı netleştirilir
Yürütme sırası, dokümantasyon ve kayıt adı
-
SQL yürütme sırasını anlama
- SQL öğrenenler için en önemli tavsiyelerden biri olarak bölümlerin yürütme sırasını anlamak gösterilir
- Yürütme sırasını bilmek sorgu yazma biçimini ciddi ölçüde değiştirebilir
- Referans olarak A beginner’s guide to the true order of SQL operations sunulur
-
Dokümantasyonu sonuna kadar okuyun
- Snowflake’te birden fazla tarih sütunu arasından en güncel tarihi döndürmek için
GREATEST()kullanılan bir örnek vardır GREATEST(), argümanlarından biriNULLiseNULLdöndürür- Dokümantasyon daha fazla okunsaydı
COALESCE(GREATEST(...), ...)yerineGREATEST_IGNORE_NULLS()kullanılabilirdi - Çoğu durumda dokümantasyona göz atmak 1 dakikadan az sürer ve beklenenden farklı davranışın nedenini arama zahmetini azaltabilir
- Snowflake’te birden fazla tarih sütunu arasından en güncel tarihi döndürmek için
-
Kayıtlı sorgular için açıklayıcı adlar kullanın
- Yeniden çalıştırılması veya referans alınması gereken sorguyu bulamama durumundan kaçınmak için açıklayıcı adlarla kaydetmek daha iyidir
- Kayıt adlarında genellikle sorgu konusu, çalıştırma ayı ve talep eden kişinin adı yer alır
- Örnek biçim
Lapsed users analysis - 2023-09-01 - Olivia Robertsşeklindedir
2 yorum
Baştaki virgüller bu gönderide tamamen sondaki virgüller olarak yazılmış. Orijinal metinde başta olacak şekilde girilmiş.
Hacker News yorumları
Benim ekleyeceğim ipucu şu: DB sunucusunu düzgünce öğrenmek ve execution plan’leri sık sık kontrol etmek gerekir. Beklenmedik sonuçlar çıkabileceği için ayarlayıp yeniden kontrol etmek iyi olur.
Genelde
EXISTS,IN’den daha hızlıdır;NOT EXISTSiseNULLişlemedeEXCEPT’ten farklı davranır. Tabloları join ettikten sonra satırlarıDISTINCTgibi şeylerle elemek yerine,SELECTlistesindeki alt sorgu sütunlarını kullanmak bazen çok daha hızlı olabilir. Aynı tablodan 10’dan fazla değer getirseniz bile böyle olabilir; DB sunucusu lateral join desteklese de durum aynı kalabilir. Ancak alt sorgu en fazla tek satır döndürmelidir.Tek seferlik olmayan sorguların tüm tablo taraması yapmamasını sağlamak gerekir. Bugünün tablo taraması yarının kesintisine dönüşebileceğinden indeks eklenmelidir.
GROUP BYifadesinin genellikle indeks kullanımını belirlediğini de hatırlamak gerekir.Bir ifadeye göre filtreleme yapmanız gerekiyorsa, örneğin bir alt dizgenin belirli bir değere eşit olup olmadığını kontrol etmeniz gerekiyorsa, hesaplanmış bir sütun ekleyip ona indeks koyabilirsiniz. Bazı DB’ler ifade indekslerini doğrudan destekler.
ORyerineUNION ALLkullanmak, karmaşık sorgularda veya çok sayıdaORkoşulunda çoğu zaman çok daha hızlı olabilir.DB filtreleme sırasını akıllıca belirleyemediğinde, sırayı zorlamak için alt sorguyu
JOINetmek de işe yarar.Postgres’te ilginç bulduğum şey, başka DB’lerde de böyle olma ihtimali var,
INSERT (SELECT ...)işlemini CPU çekirdeği sayısına göre elle shard ederseniz neredeyse doğrusal şekilde hızlanabilmesiydi. Yaklaşık 10 join olsa bile mümkündü. ÖnceEXPLAINile en içteki ya da en dıştaki join’i bulup, ardından her satır aralığı için ayrı paralel sorgular (id >= start AND id < end) çalıştırmak yeterli. 6 yıl önce yaptığım bir işte tuhaf bir nedenle bu yöntemi çok kullandım. Postgres 10+’a paralellik eklendi, ama bildiğim kadarıyla hâlâ bu kadar ileri düzeyde değil.SELECTlistesinde alt sorgu “sütunu” kullanmakla tam olarak ne kastedildiğini bilmiyorum.Örneğin
SELECT column1, (SELECT column2, column3, ... FROM table_b WHERE table_a.id = table_b.a_id) FROM table_açalıştırınca beklendiği gibi “subquery must return only one column” hatası geliyor. Birden çok sütunu kayıt/kompozit tip olarak döndürmek mi kastediliyor?GROUP BYifadesinin genellikle indeks kullanımını neden belirlediği bana hemen anlaşılır gelmemişti; merak edenler için bu yazı adım adım iyi açıklıyor: https://www.brentozar.com/archive/2015/06/indexing-for-group...EXPLAINkullanmalı ve tercih ettiğiniz araçla onu yorumlamayı öğrenmelisiniz. Sorguları da izlemek gerekir.Önceki startup’ta PgHero kurmuştuk; performans optimizasyonu ve önceliklendirme konusunda gerçekten çok yardımcı oldu.
Tüm tablo taraması bazı sorgular için en verimli erişim stratejisidir. Genelde tüm tabloyu okuyan analiz/toplama sorguları böyledir; bazen tüm satırların yalnızca %50’sini getiren durumlarda bile tablo taraması daha iyidir.
Salt okunur bir tablo taramasının nasıl kesintiye yol açtığını da pek bilmiyorum. Çünkü eşzamanlı erişimi engellemez. Tek dezavantajı I/O yükünün büyük olmasıdır; sunucu bunu kaldıramıyorsa zaten baştan ciddi şekilde düşük özellikli demektir.
“Okunabilirlik” bölümündeki 3 örnek tuhaf. İlk 2’si yazmayı kolaylaştırmak için kelimenin tam anlamıyla okunabilirlikten fedakârlık ediyor; sonuncusu ise girintilemeyle bile pek kurtarılamayan, okunması zor, canavar gibi bir biçim.
Geliştiricilerin gerçek kaynak kod kadar commit geçmişine de baktığını düşünüyorum.
Yeterince sık gördüğüm için artık pek gözüme batmıyor.
Bir sorun da görmüyorum.
Yanlış bir şey var gibi de görünmüyor.
SELECTbloğunda sütunları satır satır ayırırken 150 karakterlik satırı olduğu gibi bırakan kişi kim? Bu okunabilirliğin bozuk bir tanımı. Virgül meselesine hiç giremiyorum bile.Kod incelemesinde uzun satırları kimse doğru düzgün görmez. AngularJS’in en büyük sorunu buydu. Merge yanlış işlendi ve her şey bozuldu; çünkü 90. sütun civarına gelince gözler bulanıklaşıyor. Kod incelemesi olan yarım düzineden fazla ekip gördüm, hep aynıydı. Bu sorunun gayet farkında olup kaçınmaya çalışsam bile, ben de hâlâ başkalarının yaklaşık yarısı sıklığında hata yapıyorum.
Biraz bölerek yazalım. Özellikle başkalarına örnek gösteriyorsanız bunu daha da çok yapmalısınız.
Karmaşık stored procedure’larla çalışırken ipuçları şöyle:
WHEREkoşulunun çalıştırılmasıdır.JOINya da karmaşık işlemler denerseniz timeout alma olasılığınız yüksektir6. madde olmadan 5. maddeyi yaptıysanız, optimal olmayan bir şey yaptığınızı görememe ihtimaliniz yüksek. Benim tavsiyem erken optimizasyondan kaçınmak; önce en sezgisel şekilde yazıp yalnızca gerektiğinde optimize etmek. En önemlisi SQL’i prosedürel yazmamaktır. İstediğiniz veriyi tarif edersiniz; motora onu nasıl getireceğini emretmezsiniz
Öte yandan Microsoft, query planner en iyisini bilir der gibi, onu tune etmeye çalışmamanız için sürekli uyarılar koyuyor
DB sürümü de etkili olabilir
“Ne olur ne olmaz” diye geliştirme yapmayı sevmiyorum. Arayüzler için de böyle,
where 1=1gibi placeholder’lar için deGerektiğinde yapın. Bir gün gelecekte gerekebilir diye yapmayın. Production kodu, geliştirme yardımcılarını geride bırakacağınız yer değildir. Geliştirme sırasında istediğinizi yapabilirsiniz ama production kodunda okunabilirlik ve açık niyet çok daha önemlidir
“Anti join” konusunda bir şey daha. Başka bir büyük tabloda veya alt sorguda koşula uyan satırın var olup olmadığını kontrol etmek istiyorsanız
INya daLEFT JOINyerine EXISTS kullanmak daha iyidirEXISTS, eşleşen bir öğe bulur bulmaz true döndürür.LEFT JOINveINdurumunda motor, değerlendirmeden önce tüm sonuçları toplar(NOT) EXISTS,(LEFT) JOINya da(NOT) IN’den daha iyi bir execution plan üretti ya da aynı planı ürettiÜstelik niyeti de daha net gösteriyor
“Koda yorum yazın” ile ilgili olarak, en azından MSSQL’de yorumlarda
--yerine/**/kullanılması sıkça önerilir. Query Store gibi özellikler sorguları çoğu zaman satır sonları olmadan sakladığından, oradan sorguyu aldığınızda IDE formatter’ını doğrudan kullanmak yerine her şeyi elle düzeltmeniz gerekirOBJECT_DEFINITIONile kullanıyorumselect name,cast((select OBJECT_DEFINITION(object_id) for xml path('')) as xml) from sys.proceduresSatır sonları korunduğu için düzenlemek daha kolay olabilir. Ancak
>işaretinin>hâline gelmesi gibi diğer XML karakterleri bozulur. Bir diğer seçenek deVARBINARYve onu geri açacak bir şey kullanmaktırHerkes virgül önerisine takılmış durumda, ama
WHEREkoşulundaki1=1iyi bir fikir olarak mı görülüyor? Code review’da bunu görsem yazarı hakkında ne düşüneceğimi bilememWHEREifadesindeki değişikliğin başka satırları etkilememesi, yani code review’u kolaylaştırmasıAma gerekçe bu örnekteki gibi dinamik koşullar eklemekse, çalıştığım yerde bu kesinlikle işten kovulma sebebi olurdu
Veritabanı ayarlarıyla hız kazanma konusunda, neredeyse “satın alma”ya yakın bir yöntem ile fiilen elle uygulanan “inşa etme” arasındaki çizginin nereye çekilmesi gerektiğine dair genel yönergeler paylaşabilecek biri var mı? Sınırlı deneyimime göre yetkin DBA’ler çok daha yüksek ücretler alıp başka yerlerde çalıştığı için bu iş çoğu zaman uygulama geliştiricisinin üstüne kalıyor. Yukarıda söylendiği gibi, veritabanını bilmek önemli
Tipik bir örnek, zamanla büyük miktarda biriken ve en güncel verinin en sık erişildiği veriler. Bir DBA, bölümleme ya da kısmi indekslerle erişimi hızlı tutabilir; uygulama geliştiricisi ise kayıtları arka planda ayrı bir arşiv tablosuna taşırken tüm veri kümesinde nihai arama gibi işlevleri desteklemeye devam edebilir. Bulut veritabanının özellik eksikliğiyle kısıtlanma gibi durumlarda, uygun zamanda tek bir tabloyu birden fazlaya bölmeye yönelik ilk işi araçların epey otomatikleştirebileceği hissine de kapılıyorum
Bir başka yönetim seçeneği de büyük blob/dosyaların tamamını ayrı bir veritabanında ya da dosya sisteminde tutup farklı depolama ayarları kullanmak. Bunu da veritabanı karşılayabildiği gibi elle de yönetilebiliyor
Uç noktada, indeksleri doğrudan kendiniz uygulamaya kadar gidilebilir gibi. Otomatik artan birincil anahtarı ve çok sayıda sütunu olan dev bir tablo tutup, o ID ile aranabilir birkaç sütunu içeren ayrı bir tablo oluşturmak gibi. Tam metin arama ya da vektörlere kadar da gidilebilir
MSSQL 2016+’da materialized view pattern’ı elle uygularken işe yarayan bir ipucu, partition switching ile birlikte kullanmak. https://github.com/cajuncoding/SqlBulkHelpers?tab=readme-ov-... adresinde iyi açıklanmış ve uygulanmış. Tesadüfen bulduğum, ticari açıdan en faydalı ama arama sıralaması düşük ve yıldızı az olan küçük bir kütüphaneydi; .NET ile MSSQL’e toplu eklemeye odaklanıyor. Partition switching otomasyonu sayesinde satın alma/inşa etme çizgisini uygun şekilde çizen iyi bir örnek olduğunu düşünüyorum
Eksik kalan bir nokta:
SELECT *kullanmayı bırakmalısınız. Neredeyse kesinlikle tablonun tüm genişliğine ihtiyacınız yoktur; bunu yaptığınızda filtrelenecek ve aktarılacak veri artar, ayrıca harika bir özellik olan semi join’i de engellersinizGeliştiriciyseniz, evet.
SELECT *tuzaklar içerir ve neredeyse her zaman sütunları açıkça belirtmeli ya da bunu sizin yerinize yapan bir query builder kullanmalısınızAma analistseniz hayat kısa; bazen tüm sütunları tek tek yazmak istemeyebilirsiniz.
SELECT *de sorun değilBiraz konu dışına çıkıyor olabilir ama, bir yöneticinin pull request’i hiç yorum veya tartışma olmadan doğrudan kapatması kabul edilebilir bir yöntem mi?
Depoya ara sıra katkı yapmış ya da yapmaya çalışmış biri olarak soruyorum
Örnek: https://github.com/ben-n93/SQL-tips-and-tricks/pulls?q=is%3A...