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:

  • WHERE koşulunda CONCAT kullanmaktan kaçının. Örneğin WHERE CONCAT(first_name, ' ', last_name) = 'Ali Yilmaz' yazmak yerine WHERE first_name = 'Ali' AND last_name = 'Yilmaz' kullanın. İlk durumda index kullanılamaz.
  • SELECT içinde CONCAT kullanmak 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_CONCAT ile büyük gruplar oluşturuyorsanız group_concat_max_len değ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 CONCAT ile NULL birleştirme her zaman NULL döndürür.
  • MariaDB’de CONCAT_WS kullanımı MySQL ile aynıdır.
  • MariaDB, || operatörünü bazı sql_mode ayarlarında string birleştirme için kullanabilir. MySQL’de || varsayılan olarak mantıksal OR operatörüdür. Eğer PIPES_AS_CONCAT sql_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: NULL ile boş string '' farklı şeylerdir. COALESCE(column, '') ile NULL değerleri boş string’e çevirirseniz CONCAT davranışı daha öngörülebilir olur.
  • Sayısal değerleri dönüştürün: CONCAT sayısal değerleri otomatik string’e çevirir ama bazen CAST(column AS CHAR) veya CONVERT(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 NULL iç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_len limitine 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.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir