MariaDB ve MySQL’de CONCAT ile Metin Birleştirme
Veritabanı sorgularında en sık ihtiyaç duyduğumuz işlemlerden biri, farklı sütunlardaki verileri tek bir metin halinde birleştirmektir. Bir kullanıcının adını ve soyadını tek sütunda göstermek, adres bilgilerini birleştirmek ya da dinamik mesajlar üretmek istediğinizde CONCAT fonksiyonu devreye girer. Basit görünse de CONCAT ve onun gelişmiş versiyonları olan CONCAT_WS, GROUP_CONCAT gibi fonksiyonlar, gerçek dünya senaryolarında son derece güçlü araçlara dönüşebilir. Bu yazıda MariaDB ve MySQL üzerinde metin birleştirme işlemlerini tüm detaylarıyla ele alacağız.
CONCAT Fonksiyonu Nedir?
CONCAT, “concatenate” kelimesinden gelir ve Türkçe karşılığı birleştirme demektir. MySQL ve MariaDB’de bu fonksiyon, verilen iki veya daha fazla string ifadeyi arka arkaya ekleyerek tek bir string döndürür. Söz dizimi son derece basittir:
CONCAT(string1, string2, string3, ...)
Fonksiyon, argüman olarak verilen ifadelerden herhangi biri NULL değer içeriyorsa doğrudan NULL döndürür. Bu davranış önemli bir detaydır ve ilerleyen bölümlerde nasıl yönetileceğini göreceğiz.
Temel CONCAT Kullanımı
En basit senaryodan başlayalım. Diyelim ki bir customers tablonuz var ve müşterilerin adı ile soyadı ayrı sütunlarda tutuluyor. Bu iki alanı birleştirip tam adı tek sütun olarak döndürmek istiyorsunuz:
SELECT CONCAT(first_name, ' ', last_name) AS full_name
FROM customers;
Bu sorgu, first_name ve last_name sütunları arasına bir boşluk koyarak birleştirme işlemi yapar. AS full_name ifadesiyle sonuç sütununa anlamlı bir takma ad veriyoruz. Uygulama katmanında bu sütunu full_name olarak çekip kullanabilirsiniz.
Birden fazla sütunu aynı anda birleştirmek de mümkün. Örneğin bir adres tablosunda sokak, mahalle, ilçe ve şehir bilgileri ayrı tutuluyorsa:
SELECT
customer_id,
CONCAT(street, ', ', district, ', ', city, ' ', postal_code) AS full_address
FROM addresses
WHERE is_active = 1;
Bu örnekte virgül ve boşlukları birleştirmenin içine gömerek okunabilir bir adres formatı elde ediyoruz.
NULL Değerleriyle Başa Çıkmak
CONCAT fonksiyonunun en can sıkıcı özelliği, herhangi bir argümanın NULL olması durumunda tüm sonucun NULL dönmesidir. Gerçek dünya verilerinde NULL kaçınılmazdır. Örneğin müşterilerin ikinci adı her zaman girilmemiş olabilir:
-- Sorunlu sorgu: middle_name NULL ise tüm sonuç NULL döner
SELECT CONCAT(first_name, ' ', middle_name, ' ', last_name) AS full_name
FROM customers;
Bu sorunu aşmak için IFNULL veya COALESCE fonksiyonlarını kullanıyoruz:
-- IFNULL ile NULL değerleri boş string'e dönüştürme
SELECT CONCAT(
first_name,
IFNULL(CONCAT(' ', middle_name), ''),
' ',
last_name
) AS full_name
FROM customers;
Burada middle_name NULL ise boş string döndürüyoruz, değilse başına boşluk koyarak birleştiriyoruz. Bu şekilde ismin ortasında gereksiz boşluk oluşmasını da engellemiş oluyoruz. Alternatif olarak COALESCE kullanımı:
SELECT CONCAT(
COALESCE(first_name, ''),
' ',
COALESCE(last_name, 'Bilinmeyen')
) AS full_name
FROM customers;
COALESCE, verilen argümanlar arasından NULL olmayan ilk değeri döndürür. last_name boş olan kayıtlarda “Bilinmeyen” yazmak gibi varsayılan değer mantığını kolayca uygulayabilirsiniz.
CONCAT_WS: Ayraçlı Birleştirme
Pratik sysadmin işlerinde en çok işe yarayan fonksiyon CONCAT_WS‘dir. “With Separator” anlamına gelir. İlk argüman olarak ayraç karakterini alır, sonraki argümanlar ise birleştirilecek değerlerdir. Asıl avantajı şu: NULL değerleri otomatik olarak atlar, NULL değer yüzünden tüm sonuç NULL olmaz.
-- CONCAT_WS kullanımı
SELECT CONCAT_WS(' ', first_name, middle_name, last_name) AS full_name
FROM customers;
middle_name NULL ise CONCAT_WS onu atlayarak first_name ile last_name arasına tek boşluk koyar. Bu davranış çoğu zaman tam istediğimiz şeydir.
CONCAT_WS ile adres birleştirme örneği:
SELECT
customer_id,
CONCAT_WS(', ', street, neighborhood, district, city) AS formatted_address
FROM addresses
WHERE customer_id = 1001;
Bu sorguda herhangi bir alan boş olsa bile gereksiz virgül oluşmaz, fonksiyon NULL değerleri zaten atlar.
Gerçek Dünya Senaryosu: E-posta Şablonları
Bir e-ticaret sistemi yönetiyorsunuz ve toplu e-posta göndermek için dinamik içerik üretmeniz gerekiyor. Müşterilere kişiselleştirilmiş konu satırları ve mesaj başlıkları oluşturabilirsiniz:
SELECT
customer_id,
email,
CONCAT('Sayın ', CONCAT_WS(' ', first_name, last_name), ',') AS salutation,
CONCAT(
'Sipariş numaranız: #',
order_id,
' - Toplam tutar: ',
FORMAT(total_amount, 2),
' TL'
) AS order_summary
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
WHERE o.order_date >= DATE_SUB(NOW(), INTERVAL 7 DAY)
AND o.status = 'completed';
Bu sorgu, son 7 günde tamamlanan siparişler için her müşteriye özel bir selamlama ve sipariş özeti üretiyor. FORMAT fonksiyonunu CONCAT ile birlikte kullanarak sayısal değerleri de anlamlı biçimde metin içine gömebilirsiniz.
GROUP_CONCAT: Satırları Birleştirmek
Bazen sütunları değil, farklı satırlardaki değerleri birleştirmek istersiniz. GROUP_CONCAT tam bu iş için var. Bir kategorideki tüm ürün isimlerini tek satırda görmek, bir kullanıcının tüm rollerini virgülle ayırarak listelemek gibi durumlarda kullanılır.
Örneğin bir kullanıcı rol sisteminiz var:
SELECT
u.user_id,
u.username,
GROUP_CONCAT(r.role_name ORDER BY r.role_name SEPARATOR ', ') AS user_roles
FROM users u
JOIN user_roles ur ON u.user_id = ur.user_id
JOIN roles r ON ur.role_id = r.role_id
GROUP BY u.user_id, u.username;
Bu sorgu her kullanıcı için sahip olduğu rolleri alfabetik sıraya göre, virgülle ayrılmış tek bir sütunda gösterir. Çıktı şöyle görünür: “admin, editor, viewer”
GROUP_CONCAT ile bazı önemli parametreler:
- SEPARATOR: Varsayılan ayraç virgüldür, bunu değiştirebilirsiniz
- ORDER BY: Birleştirilen değerlerin sıralanma düzenini belirler
- DISTINCT: Tekrarlayan değerleri filtreler
SELECT
product_category,
GROUP_CONCAT(
DISTINCT product_name
ORDER BY product_name ASC
SEPARATOR ' | '
) AS products_list
FROM products
WHERE is_active = 1
GROUP BY product_category;
Bu örnekte her kategorideki benzersiz aktif ürün isimlerini pipe karakteriyle ayırarak listeliyoruz.
Önemli bir uyarı: GROUP_CONCAT sonucunun maksimum uzunluğu group_concat_max_len sistem değişkeniyle kontrol edilir. Varsayılan değer 1024 byte’tır. Uzun string’ler üretiyorsanız bu değeri artırmanız gerekebilir:
SET SESSION group_concat_max_len = 1000000;
-- Ya da kalıcı olarak my.cnf/mariadb.conf dosyasına:
-- group_concat_max_len = 1000000
INSERT ve UPDATE Sorgularında CONCAT
CONCAT sadece SELECT sorgularına özgü değil. Mevcut sütun değerini okuyarak üzerine ekleme yapma ihtiyacınız olduğunda UPDATE ile de kullanabilirsiniz.
Örneğin bir log tablosuna mevcut log değerinin sonuna yeni satır eklemek istiyorsunuz:
UPDATE system_logs
SET log_content = CONCAT(log_content, 'n', NOW(), ' - ', 'Config updated by admin')
WHERE log_id = 5001;
Ya da ürün açıklamalarına toplu ek yapmak:
UPDATE products
SET description = CONCAT(description, ' [Güncellendi: ', DATE_FORMAT(NOW(), '%d.%m.%Y'), ']')
WHERE updated_at >= '2024-01-01'
AND description IS NOT NULL;
Bu sorguda description IS NOT NULL kontrolü kritik. Çünkü CONCAT ile NULL değerler birleştirilirse sonuç NULL olur ve mevcut description değeri kaybolur.
Dinamik SQL ve Stored Procedure İçinde CONCAT
Stored procedure yazarken dinamik sorgular oluşturmak için CONCAT sıkça kullanılır. Tablo adını veya koşulları çalışma zamanında belirlemek istediğinizde bu yönteme başvuruyoruz:
DELIMITER //
CREATE PROCEDURE GetTableStats(IN table_name VARCHAR(100))
BEGIN
SET @query = CONCAT(
'SELECT COUNT(*) as total_rows, ',
'MIN(created_at) as first_record, ',
'MAX(created_at) as last_record ',
'FROM ', table_name,
' WHERE is_deleted = 0'
);
PREPARE stmt FROM @query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END //
DELIMITER ;
-- Kullanımı:
CALL GetTableStats('customers');
CALL GetTableStats('orders');
Bu stored procedure, istediğiniz tablonun temel istatistiklerini dinamik olarak çekiyor. Dikkat edin, dinamik SQL kullanırken SQL injection riskine karşı input validation yapmayı unutmayın.
Performans Notları
CONCAT fonksiyonu genel olarak hafif bir operasyondur ama büyük veri setlerinde dikkat edilmesi gereken noktalar var:
WHEREkoşulundaCONCATkullanmaktan kaçının. ÖrneğinWHERE CONCAT(first_name, ' ', last_name) = 'Ali Yilmaz'yazmak yerineWHERE first_name = 'Ali' AND last_name = 'Yilmaz'kullanın. İlk durumda index kullanılamaz.SELECTiçindeCONCATkullanmak index performansını etkilemez, sadece presentation katmanında birleştirme yapar.- Çok büyük string’ler üretiyorsanız, bu string’lerin bellek tüketimini göz önünde bulundurun.
GROUP_CONCATile büyük gruplar oluşturuyorsanızgroup_concat_max_lendeğerini ayarlayın, yoksa verileriniz sessizce kesilir.
Karmaşık Senaryo: Rapor Sorgularında CONCAT
Gerçek bir üretim ortamında kullandığım bir örneği paylaşayım. Bir ERP sisteminde her gün yöneticilere e-posta ile gönderilen özet rapor için SQL tarafında formatlanmış metin üretmemiz gerekiyordu:
SELECT
CONCAT(
'Departman: ', d.department_name, 'n',
'Sorumlu: ', CONCAT_WS(' ', e.first_name, e.last_name), 'n',
'Bu ay açılan talepler: ', COUNT(t.ticket_id), 'n',
'Ortalama çözüm süresi: ',
ROUND(AVG(TIMESTAMPDIFF(HOUR, t.created_at, t.resolved_at)), 1),
' saatn',
'Açık talepler: ', SUM(CASE WHEN t.status = 'open' THEN 1 ELSE 0 END)
) AS department_report
FROM departments d
JOIN employees e ON d.manager_id = e.employee_id
JOIN tickets t ON d.department_id = t.department_id
WHERE t.created_at >= DATE_FORMAT(NOW(), '%Y-%m-01')
GROUP BY d.department_id, d.department_name, e.first_name, e.last_name
ORDER BY d.department_name;
Bu sorgu, her departman için dönemlik özet bilgilerini formatlı metin olarak döndürüyor. Python veya PHP tarafında bu çıktıyı doğrudan e-posta gövdesine yapıştırabiliyorduk.
MariaDB ile MySQL Arasındaki Farklar
CONCAT davranışı açısından MariaDB ve MySQL büyük ölçüde uyumludur. Ancak bir farka dikkat etmek gerekir:
- MySQL’de
CONCATileNULLbirleştirme her zamanNULLdöndürür. - MariaDB’de
CONCAT_WSkullanımı MySQL ile aynıdır. - MariaDB,
||operatörünü bazısql_modeayarlarında string birleştirme için kullanabilir. MySQL’de||varsayılan olarak mantıksal OR operatörüdür. EğerPIPES_AS_CONCATsql_mode aktifse her iki veritabanında da||birleştirme için çalışır.
-- sql_mode kontrolü
SELECT @@sql_mode;
-- PIPES_AS_CONCAT aktifse bu çalışır:
SELECT first_name || ' ' || last_name FROM customers;
Üretim ortamında bu belirsizliği yaşamamak için her zaman açık CONCAT fonksiyonunu kullanmanızı öneririm.
Pratik İpuçları
Yıllar içinde öğrendiğim ve işleri kolaylaştıran birkaç ipucu:
- Boş string kontrolü yapın:
NULLile boş string''farklı şeylerdir.COALESCE(column, '')ileNULLdeğerleri boş string’e çevirirsenizCONCATdavranışı daha öngörülebilir olur. - Sayısal değerleri dönüştürün:
CONCATsayısal değerleri otomatik string’e çevirir ama bazenCAST(column AS CHAR)veyaCONVERT(column, CHAR)kullanmak daha güvenlidir. - Uzun CONCAT zincirlerinde okunabilirliğe dikkat edin: Sorgunuzu dikey olarak yazın, her argümanı yeni satıra alın, bu bakımı çok kolaylaştırır.
- Test verisiyle deneyin: Özellikle
NULLiçeren satırlarla sonucun nasıl göründüğünü mutlaka test edin. - GROUP_CONCAT sonucunu her zaman kontrol edin: Beklediğinizden uzun listeler için
group_concat_max_lenlimitine takılıp takılmadığınızıCHAR_LENGTH()ile kontrol edebilirsiniz.
Sonuç
CONCAT ve türevleri, veritabanı sorgularında presentation katmanına iş yükü aktarmadan doğrudan SQL içinde zengin metin çıktıları üretmenizi sağlar. Özellikle raporlama sorgularında, toplu veri işlemlerinde ve dinamik içerik üretiminde bu fonksiyonlar gerçekten hayat kurtarır. CONCAT_WS ile NULL değerler sorununu aşabilir, GROUP_CONCAT ile satırları birleştirerek pivot benzeri çıktılar üretebilirsiniz. Performans açısından WHERE koşullarında CONCAT kullanmaktan kaçınmak ve group_concat_max_len değerini düzenlemek en önemli iki pratik kuraldır. Bu araçları doğru kullandığınızda hem uygulama katmanınızın kodunu basitleştirir hem de veritabanından çok daha kullanışlı veriler çekebilirsiniz.
