MySQL’deki ONLY_FULL_GROUP_BY hatasının tercih edilen çözümü modu kapatmak değil, seçilen her toplulaştırılmamış ifadenin GROUP BY sütunlarına işlevsel olarak bağlı olmasını sağlamak veya o ifade için bilinçli bir toplama (aggregate) fonksiyonu kullanmaktır. Böylece her grubun hangi değeri üreteceği açık ve tekrarlanabilir olur.
Karşılaşılan hata çoğunlukla şu biçimdedir:
Expression #2 of SELECT list is not in GROUP BY clause and contains
nonaggregated column ... which is not functionally dependent on columns
in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
Hata neden oluşur?
GROUP BY, birden fazla satırı tek sonuç satırında birleştirir. Kavramsal olarak, anahtar başına tek kayıt tutan bir hash table gibi düşünülebilir: aynı gruplama anahtarına sahip satırlar tek bir girdide toplanır ve o girdide her sütun için yalnızca bir değer yer alabilir. Bir grupta farklı değerler taşıyabilen bir sütunu ne gruplama anahtarına ekler ne de toplama fonksiyonuyla nasıl seçileceğini belirtirseniz, veritabanının hangi değeri döndürmesi gerektiği belirsiz kalır.
MySQL, ONLY_FULL_GROUP_BY etkinken SELECT, HAVING ve ORDER BY bölümlerindeki toplulaştırılmamış ifadeleri denetler. Bir ifade gruplama sütunlarında değilse ve bu sütunlara işlevsel olarak bağlı değilse sorguyu reddeder. Birincil anahtar veya UNIQUE NOT NULL kısıtı gibi şema özellikleri MySQL’in işlevsel bağımlılığı tanımasını sağlayabilir.
Bu mod MySQL 8.4’ün varsayılan SQL modunda yer alır. Modu kapalı bir sunucudan veya eski bir yapılandırmadan taşınan sorgular bu yüzden yükseltme ya da sunucu değişikliğinden sonra hata vermeye başlayabilir.
Uygulama katmanında gruplama farklı çalışır. Örneğin C#‘ta LINQ’in GroupBy işleci her grubu anahtarıyla birlikte bütün elemanlarını taşıyan bir koleksiyon olarak döndürür; SQL’de ise grup tek bir satıra indirgenir. Belirsizlik bu indirgemeden doğar.
Hatayı yeniden üretme
Aşağıdaki geçici tablo, aynı kullanıcı için farklı müşteri adları içeren küçük bir örnek oluşturur:
CREATE TEMPORARY TABLE orders (
id INT PRIMARY KEY,
user_id INT NOT NULL,
customer_name VARCHAR(100) NOT NULL,
amount DECIMAL(10, 2) NOT NULL
);
INSERT INTO orders (id, user_id, customer_name, amount) VALUES
(1, 10, 'Ayşe', 120.00),
(2, 10, 'Ayşe A.', 80.00),
(3, 20, 'Mehmet', 50.00);
Bu sorguda customer_name, ne gruplama anahtarıdır ne de user_id tarafından benzersiz biçimde belirlenir:
SELECT user_id, customer_name, COUNT(*) AS order_count
FROM orders
GROUP BY user_id;
user_id = 10 grubu için hem Ayşe hem de Ayşe A. bulunduğundan tek bir customer_name seçmek belirsizdir.
Sorguyu amaca göre düzeltme
1. Yalnızca grup özetine ihtiyacınız varsa sütunu kaldırın
Müşteri adı sonuç için gerekli değilse en temiz çözüm onu seçmemektir:
SELECT
user_id,
COUNT(*) AS order_count,
SUM(amount) AS total_amount
FROM orders
GROUP BY user_id;
Bu sorgu her kullanıcı için sipariş sayısını ve toplam tutarı deterministik biçimde üretir.
2. Her farklı değer ayrı grup olmalıysa sütunu GROUP BY’a ekleyin
İstenen sonuç kullanıcı ve müşteri adı birleşimine göre bir satırsa bunu açıkça yazın:
SELECT
user_id,
customer_name,
COUNT(*) AS order_count,
SUM(amount) AS total_amount
FROM orders
GROUP BY user_id, customer_name;
Bu değişiklik sonuç kümesinin anlamını değiştirir: aynı kullanıcı birden fazla ad taşıyorsa birden fazla satır döner.
3. Belirli bir seçim kuralınız varsa toplama fonksiyonu kullanın
Gerçek iş kuralı alfabetik olarak en büyük adı seçmekse MAX() bunu açık ve tekrarlanabilir hale getirir:
SELECT
user_id,
MAX(customer_name) AS alphabetically_last_name,
COUNT(*) AS order_count
FROM orders
GROUP BY user_id;
MAX(customer_name) yalnızca gerçekten bu seçim kuralını istiyorsanız doğrudur. Örneğin “en son kullanılan müşteri adı” ihtiyacı için tarih veya artan kimlik üzerinden ilgili satırı bulan ayrı bir sorgu gerekir; MAX() bu anlamı kendiliğinden sağlamaz.
4. Herhangi bir grup değeri kabul edilebiliyorsa ANY_VALUE kullanın
Verinin uygulama kuralı gereği aynı olduğunu biliyor, ancak MySQL bunu şemadan çıkaramıyorsa ANY_VALUE() ile belirli bir değerin önemli olmadığını açıkça belirtebilirsiniz:
SELECT
user_id,
ANY_VALUE(customer_name) AS sample_name,
COUNT(*) AS order_count
FROM orders
GROUP BY user_id;
ANY_VALUE() deterministik bir seçim kuralı değildir. Her gruptan herhangi bir değeri kabul ettiğinizi söyler; “ilk”, “son” veya “en güncel” satırı garanti etmez.
İşlevsel bağımlılık örneği
Müşteri bilgisi ayrı bir tabloda ve id birincil anahtarıyla tutuluyorsa bir müşteri kimliği yalnızca bir adı belirler:
CREATE TEMPORARY TABLE customers (
id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL
);
INSERT INTO customers (id, name) VALUES
(10, 'Ayşe Aksoy'),
(20, 'Mehmet Demir');
SELECT
c.id,
c.name,
COUNT(*) AS order_count,
SUM(o.amount) AS total_amount
FROM customers AS c
JOIN orders AS o ON o.user_id = c.id
GROUP BY c.id;
MySQL desteklenen durumlarda c.name ifadesinin c.id birincil anahtarına işlevsel olarak bağlı olduğunu algılar ve sorguyu kabul eder. Şema bu ilişkiyi gerçekten garanti etmiyorsa eksik kısıtı düzeltmek, sorguya keyfi bir toplama fonksiyonu eklemekten daha doğru olabilir.
sql_mode’u geçici olarak kapatmak
Önce oturumdaki etkin modları inceleyin:
SELECT @@SESSION.sql_mode;
Eski bir sorgunun hata kaynağını doğrulamak için ONLY_FULL_GROUP_BY yalnızca mevcut bağlantıda geçici olarak çıkarılabilir. sys.list_drop(), virgülle ayrılmış listeden tek bir değeri çıkarır ve diğer modlara dokunmaz:
SET SESSION sql_mode = sys.list_drop(
@@SESSION.sql_mode,
'ONLY_FULL_GROUP_BY'
);
Bu ayar yalnızca mevcut oturumu etkiler. Bağlantı kapanınca yeni oturum sunucu varsayılanını kullanır. Global ayarı veya yapılandırma dosyasını değiştirmek uygulamadaki belirsiz sorguları gizleyebileceği için üretim çözümü olarak önerilmez; önce sorgu ve şema düzeltilmelidir.
Kontrol listesi
- Hata mesajındaki ifadeyi ve gruplama sütunlarını belirleyin.
- İfadenin sonuçta gerçekten gerekli olup olmadığını sorun.
- Ayrı grup gerekiyorsa ifadeyi
GROUP BYbölümüne ekleyin. - Tek değer gerekiyorsa iş kuralına uygun
SUM,COUNT,MINveyaMAXgibi bir toplama fonksiyonu seçin. - İşlevsel bağımlılık bekliyorsanız bunu birincil anahtar veya uygun benzersiz kısıtla şemada güvenceye alın.
ANY_VALUE()kullanıyorsanız herhangi bir değerin gerçekten kabul edilebilir olduğunu belgeleyin.
Sonuç
ONLY_FULL_GROUP_BY hatası, sorgunun her grup için hangi değeri döndüreceğini belirtmediğini gösterir. Kalıcı çözüm sorguyu amaca göre düzeltmektir: gereksiz sütunu kaldırmak, sütunu gruplamaya eklemek, iş kuralına uyan bir toplama fonksiyonu seçmek ya da işlevsel bağımlılığı şemada tanımlamak. ANY_VALUE() yalnız değerin gerçekten önemsiz olduğu yerde, oturum düzeyinde modu kapatmak ise yalnız teşhis amacıyla kullanılmalıdır.