İstatistiksel mod, bir veri kümesinde en sık meydana gelen değeri temsil eder. ANSI SQL, AVG, SUM ve COUNT gibi standart toplama işlevlerini tanımlasa da, yerleşik bir MODE toplama işlevini belirgin bir şekilde ihmal eder. Bu eksiklik, ilişkisel modelin skalar sonuçlara odaklanmasından ve bağlama dayalı düğümlerin ortaya çıkardığı belirsizlikten kaynaklanmaktadır. Bu nedenle, uygulayıcılar bu istatistiksel ölçüyü türetilmiş tablolar ve pencere işlevleri kullanarak yeniden oluşturmak zorundadır.
Modu hesaplamak, her parti içinde maksimum sıklık sayısına sahip değeri belirlemeyi gerektirir. Karmaşıklık, iki kısıttan kaynaklanmaktadır: birincisi, toplama işlevleri doğrudan iç içe geçirilemez (örneğin, MAX(COUNT(*))), ikincisi, en yüksek sıklık için kıyaslama düğümleri deterministik bir şekilde çözülmelidir. Bir çözüm, herhangi bir prosedürel döngü veya tedarikçi özel uzantılar olmadan tek bir deklaratif ifade olarak çalışmalıdır.
Yöntem, iki aşamalı bir CTE (Ortak Tablo İfadesi) yapısını kullanır. İlk olarak, GROUP BY ile COUNT(*) kullanarak sıklıkları hesaplayın. İkinci olarak, gruplama anahtarlarıyla bölümlenmiş bir şekilde sıralanan ve kıyaslama için kendi değeri yukarı yönlü sıralanmış RANK() pencere işlevini uygulayın. RANK() = 1 olarak filtreleme yapmak modu verir. Bu yöntem tamamen ANSI SQL:2003 uyumludur ve tek bir tablo taramasında çalışır.
WITH frequency_cte AS ( SELECT group_id, target_value, COUNT(*) AS val_freq FROM observations GROUP BY group_id, target_value ), ranked_cte AS ( SELECT group_id, target_value, RANK() OVER ( PARTITION BY group_id ORDER BY val_freq DESC, target_value ASC ) AS freq_rank FROM frequency_cte ) SELECT group_id, target_value AS mode_value FROM ranked_cte WHERE freq_rank = 1;
Bir e-ticaret analiz ekibi, her giyim kategorisi için en popüler ürün boyutunu (modu) raporlamak zorunda kaldı; bu, depo stok seviyelerini optimize etmek için gerekliydi. sales tablosu, category_id, sale_month ve size_label sütunlarıyla milyonlarca satır içeriyordu. Önemli bir iş kuralı, eğer iki boyut en yüksek satış hacminde eşitlik sağlarsa, sistemin her zaman daha küçük alfanümerik boyutu (örneğin, "M" önce "L") seçmesini gerektiriyordu; bu, deterministik envanter tahminlerini sürdürmek içindi.
Çözüm 1: Korele Alt Sorgu ile Skalar Karşılaştırma.
Bir yaklaşım, her grup için maksimum sayıyı bulmak için korele bir alt sorgu kullanmaktı, ardından eşleşen boyutu bulmak için tekrar bağlanmaktı. Bu yöntem, miras sistemlerde bulunan standart SQL-92 özelliklerine dayanıyordu. Alt sorgu, kategori-aylık çifti başına maksimum sıklığı hesapladı ve dış sorgu bu sıklıkla eşleşen boyutları filtreledi. Her ne kadar evrensel olarak uyumlu olsa da, bu yaklaşım, korelasyondan dolayı O(n²) karmaşıklığına sahipti. Verilerin üzerinde birden fazla geçiş yapılmasını gerektiriyordu ve genellikle ek alt sorgular gerektiren eşik aşamasında zarif bir şekilde başa çıkmakta zorlanıyordu. Sorgu planı, satış hacmi arttıkça önemli ölçüde kötüleşen iç içe döngü bağlantılarına sahipti.
Çözüm 2: Deterministik Sıralama ile Pencere İşlevi.
Seçilen çözüm, yukarıda genel çözümde detaylandırılan ANSI SQL:2003 pencere işlevlerini kullandı. CTE'de sıklıkları malzeme haline getirerek ve RANK() uygulayarak, veritabanı optimizasyonu, sıralama tabanlı işlemleri ve hash toplamayı kullanabildi. Bu yaklaşım, doğru indeksleme ile doğrusal zaman O(n log n) içinde çalıştı, yatay olarak ölçeklendi ve kıyaslama için ikincil sıralama anahtarı aracılığıyla doğal bir şekilde eşitlik çözümlemesi yaptı. Eşit düğüm çözümlemesinin deterministik çözülmesi, envanter algoritmasının tutarlı girişler almasını sağladı ve rapor geçişleri arasında dalgalanan önerileri önledi.
Sonuç.
Uygulama, 50 milyon kayıt içeren bir veri kümesinde rapor oluşturma süresini 12 dakikadan 8 sekize düşürdü. Deterministik eşit düğüm çözümlemesi, otomatik yeniden sipariş sistemlerinde tutarsızlıkları ortadan kaldırdı ve ikincil popüler boyutlar için stokbitimlerini %15 azalttı.
Neden MAX(COUNT(*)) gibi iç içe toplamalar bir sözdizim hatası üretir ve SQL'nin mantıksal işleme sırası neden CTE tabanlı bir yaklaşımı gerektirir?
Birçok aday SELECT group_id, MAX(COUNT(*)) FROM ... yazmayı dener, ANSI SQL'in toplama işlevlerini iç içe geçirmeyi yasakladığını bilmeden. Mantıksal işleme sırası, WHERE, GROUP BY ve HAVING ifadelerinin SELECT'ten önce çalıştığını belirtir; bu, gruplama aşamasında toplamaların mevcut olmadığı anlamına gelir. CTE veya alt sorgu yaklaşımı, ilk aşamada sayıları türetilmiş bir tablo olarak malzeme haline getirir, böylece bunlar ikinci aşamada pencere işlevi sıralama için skalar değerler olarak kullanılabilir. Bu toplama ve pencere aşamalarının ayrımını anlamak, geçerli SQL sorguları oluşturmada kritik öneme sahiptir.
Eşitlikler mevcutken RANK(), DENSE_RANK() ve ROW_NUMBER() arasındaki seçim, mod hesaplamasının doğruluğunu nasıl etkiler ve deterministik eşitlik çözümlemesi neden önemlidir?
Adaylar genellikle ROW_NUMBER()'ı varsayılan olarak kullanır çünkü bu her bölüm için tam olarak bir satır garanti eder. Ancak, ROW_NUMBER() eşit satırlara fiziksel sıralama düzenine göre rastgele tam sayılar atayarak, ikincil sıralama anahtarı atlanırsa her yürütmede farklı bir mod değeri seçilmesine neden olabilir. RANK() tüm eşit değerleri 1. sırada doğru bir şekilde tanır; bu, "tam olarak bir sonuç" gereksinimini deterministik bir şekilde karşılamak için açık bir eşitlik çözümleme mantığı (örneğin, MIN(target_value)) gerektirir. DENSE_RANK() da eşit satırları döndürür ama ardışık numaralandırma ile, bu da ek mantık olmadan basit filtrelemeye uygun hale getirir. Deterministik davranış, analitik uygulamaların ve aşağı akış ETL boru hatlarının tutarlı, tekrarlanabilir sonuçlar almasını sağlar.
Kendi kendine bağlantı kullanımı ile pencere işlevleri arasındaki sıkılık ve bellek etkileri nedir ve bu sorgu planlamayı nasıl etkiler?
Pencere işlevlerinin her zaman bağlantılardan daha iyi performans gösterdiği yönünde yaygın bir yanlış anlama vardır. Mod hesaplamasında, kendi kendine bağlantı yaklaşımı, toplama sıklık tablosunu group_id ve val_freq = max_freq ile kendisiyle birleştirir; bu, birçok eşitlik olduğunda gruplar içinde kartesian bir ürün yaratabilir. Bu, eşitliklerin toplamından, arabellek kullanımını patlatma potansiyeli taşıyan geçici sonuç setleri üretir. Ancak, RANK() gibi pencere işlevleri, sıralama tamponunu korumak için bölüm boyutuna orantılı bellek gerektirir. Adaylar, pencere işlevinin genel olarak daha hızlı olmasının yanı sıra, eğer bölüm boyutları work_mem'i (bu, PostgreSQL teriminde) veya eşdeğer arabellek sınırlarını aşarsa diske taşınabileceğini unutur. Öte yandan, hash tabanlı kendi kendine bağlantılar, az sayıda eşitlik bulunan son derece yüksek sıklık anahtarları için daha iyi performans gösteriyor olabilir. Bu ticaret anlaşmalarını anlamak, geliştiricilerin EXPLAIN planlarını analiz etmelerine ve arabellek ayarlarını optimize etmelerine yardımcı olur.