MariaDB ve MySQL’de Geçici Tablo (TEMPORARY TABLE) Oluşturma ve Kullanma

Veritabanı yönetiminde zaman zaman şöyle bir ihtiyaçla karşılaşırsınız: Karmaşık bir sorgu çalıştıracaksınız, ara sonuçları saklamanız gerekiyor, ama bu verilerin kalıcı olmasını istemiyorsunuz. İşte tam bu noktada geçici tablolar devreye giriyor. MariaDB ve MySQL’de geçici tablolar, oturum süresince yaşayan, oturum kapandığında otomatik olarak yok olan özel tablo yapılarıdır. Doğru kullanıldığında hem performans kazanımı sağlarlar hem de kod karmaşıklığını ciddi ölçüde azaltırlar.

Geçici Tablo Nedir ve Neden Kullanılır?

Geçici tablolar, CREATE TEMPORARY TABLE komutuyla oluşturulan ve yalnızca mevcut veritabanı oturumuna görünür olan tablolardır. Başka bir oturum bu tabloları göremez, onlara erişemez. Oturum sona erdiğinde tablo otomatik olarak silinir.

Peki ne zaman geçici tablo kullanmalısınız?

  • Karmaşık alt sorgular: Aynı alt sorguyu birden fazla yerde kullanıyorsanız, sonucu geçici tabloya alıp referans göstermek daha verimlidir.
  • Adım adım veri işleme: ETL süreçlerinde verileri aşama aşama dönüştürürken ara sonuçları saklamak için idealdir.
  • Raporlama sorguları: Uzun çalışan raporlarda ara hesaplamaları depolamak için kullanılır.
  • Büyük JOIN optimizasyonu: Büyük tablolar üzerinde tekrarlanan JOIN işlemlerini önlemek için filtrelenmiş veri setini geçici tabloya alabilirsiniz.
  • Stored procedure içinde ara veri: Saklı yordamlar içinde adım adım hesaplama yaparken hayat kurtarır.

Temel Söz Dizimi ve İlk Örnek

Geçici tablo oluşturmanın en temel yolu şöyledir:

-- Basit geçici tablo oluşturma
CREATE TEMPORARY TABLE gecici_musteriler (
    id INT AUTO_INCREMENT PRIMARY KEY,
    ad VARCHAR(100),
    soyad VARCHAR(100),
    email VARCHAR(150),
    toplam_siparis DECIMAL(10,2)
);

-- Veri ekleme
INSERT INTO gecici_musteriler (ad, soyad, email, toplam_siparis)
VALUES ('Ahmet', 'Yılmaz', '[email protected]', 1500.00),
       ('Fatma', 'Kaya', '[email protected]', 2750.50),
       ('Mehmet', 'Demir', '[email protected]', 980.75);

-- Sorgulama
SELECT * FROM gecici_musteriler WHERE toplam_siparis > 1000;

Burada dikkat edilmesi gereken önemli bir nokta var: TEMPORARY anahtar kelimesi, aynı isimde kalıcı bir tablo olsa bile sorun çıkarmaz. Geçici tablo, kalıcı tabloyu oturum süresince “gölgeler”. Oturum bitince kalıcı tablo tekrar erişilebilir hale gelir. Bu davranışı bilmeden kullanmak bazen beklenmedik sonuçlara yol açabilir, dikkatli olun.

SELECT Sonucundan Geçici Tablo Oluşturma

Pratikte en sık kullanılan yöntem, mevcut bir sorgunun sonucundan geçici tablo oluşturmaktır. CREATE TEMPORARY TABLE ... AS SELECT söz dizimi bu işi yapıyor:

-- Gerçek tablolardan veri çekip geçici tabloya alma
CREATE TEMPORARY TABLE yuksek_degerli_musteriler AS
SELECT 
    m.musteri_id,
    m.ad,
    m.soyad,
    m.email,
    COUNT(s.siparis_id) AS siparis_sayisi,
    SUM(s.tutar) AS toplam_harcama
FROM musteriler m
INNER JOIN siparisler s ON m.musteri_id = s.musteri_id
WHERE s.tarih >= DATE_SUB(NOW(), INTERVAL 12 MONTH)
GROUP BY m.musteri_id, m.ad, m.soyad, m.email
HAVING toplam_harcama > 5000;

-- Artık bu geçici tabloyu diğer sorgularda kullanabilirsiniz
SELECT * FROM yuksek_degerli_musteriler ORDER BY toplam_harcama DESC LIMIT 10;

Bu yaklaşımın güzelliği şu: Karmaşık sorguyu bir kez çalıştırıyorsunuz, sonucu hafızada (ya da gerekirse diskte) tutuyorsunuz, sonrasında bu veri setini istediğiniz kadar farklı şekillerde sorgulayabiliyorsunuz. Büyük tablolarda bu yaklaşım, aynı alt sorguyu defalarca çalıştırmaktan çok daha verimlidir.

Gerçek Dünya Senaryosu: Aylık Satış Raporu

Bir e-ticaret sisteminde çalıştığınızı düşünelim. Aylık satış raporunu hazırlarken birden fazla tabloya ihtiyacınız var ve aynı veri setini farklı açılardan analiz etmeniz gerekiyor. İşte burada geçici tablolar çok işe yarıyor:

-- Adım 1: Bu ayın siparişlerini geçici tabloya çek
CREATE TEMPORARY TABLE bu_ay_siparisler AS
SELECT 
    s.siparis_id,
    s.musteri_id,
    s.tarih,
    s.durum,
    sd.urun_id,
    sd.miktar,
    sd.birim_fiyat,
    (sd.miktar * sd.birim_fiyat) AS satir_toplami
FROM siparisler s
INNER JOIN siparis_detay sd ON s.siparis_id = sd.siparis_id
WHERE YEAR(s.tarih) = YEAR(NOW()) 
  AND MONTH(s.tarih) = MONTH(NOW())
  AND s.durum != 'iptal';

-- Adım 2: Geçici tabloyu farklı açılardan analiz et
-- Kategori bazlı satış
SELECT 
    u.kategori,
    COUNT(DISTINCT bas.siparis_id) AS siparis_sayisi,
    SUM(bas.miktar) AS toplam_adet,
    SUM(bas.satir_toplami) AS kategori_cirosu
FROM bu_ay_siparisler bas
INNER JOIN urunler u ON bas.urun_id = u.urun_id
GROUP BY u.kategori
ORDER BY kategori_cirosu DESC;

-- Adım 3: En çok satan ürünler
SELECT 
    u.urun_adi,
    SUM(bas.miktar) AS toplam_satis,
    SUM(bas.satir_toplami) AS gelir
FROM bu_ay_siparisler bas
INNER JOIN urunler u ON bas.urun_id = u.urun_id
GROUP BY u.urun_id, u.urun_adi
ORDER BY toplam_satis DESC
LIMIT 20;

Bu örnekte bu_ay_siparisler geçici tablosunu iki farklı analizde kullandık. Normalde her iki sorgu da aynı karmaşık JOIN ve WHERE koşullarını tekrar çalıştırmak zorunda kalırdı. Büyük bir veritabanında bu, ciddi zaman ve kaynak tasarrufu anlamına gelir.

Geçici Tablo Üzerinde İndeks Kullanımı

Geçici tablolar üzerinde de indeks tanımlayabilirsiniz. Özellikle geçici tabloyu birden fazla kez sorgulayacaksanız ve filtre koşulları uygulayacaksanız bu kritik önem taşır:

-- İndeksli geçici tablo oluşturma
CREATE TEMPORARY TABLE musteri_analiz (
    musteri_id INT NOT NULL,
    sehir VARCHAR(100),
    yas_grubu VARCHAR(20),
    toplam_harcama DECIMAL(12,2),
    son_alis_tarihi DATE,
    INDEX idx_sehir (sehir),
    INDEX idx_yas_grubu (yas_grubu),
    INDEX idx_toplam_harcama (toplam_harcama)
);

-- Veriyi doldur
INSERT INTO musteri_analiz
SELECT 
    m.musteri_id,
    a.sehir,
    CASE 
        WHEN TIMESTAMPDIFF(YEAR, m.dogum_tarihi, NOW()) < 25 THEN 'Gen??'
        WHEN TIMESTAMPDIFF(YEAR, m.dogum_tarihi, NOW()) < 40 THEN 'Orta'
        ELSE 'Ust'
    END AS yas_grubu,
    COALESCE(SUM(s.tutar), 0) AS toplam_harcama,
    MAX(s.tarih) AS son_alis_tarihi
FROM musteriler m
LEFT JOIN adresler a ON m.musteri_id = a.musteri_id AND a.varsayilan = 1
LEFT JOIN siparisler s ON m.musteri_id = s.musteri_id
GROUP BY m.musteri_id, a.sehir, yas_grubu;

-- İndeks sayesinde bu sorgu hızlı çalışır
SELECT sehir, yas_grubu, COUNT(*) AS musteri_sayisi, AVG(toplam_harcama) AS ort_harcama
FROM musteri_analiz
WHERE toplam_harcama > 0
GROUP BY sehir, yas_grubu;

CREATE TEMPORARY TABLE ... AS SELECT söz dizimiyle oluşturduğunuzda indeks tanımlamazsınız, tablo otomatik yapıda gelir. İndeks istiyorsanız ya önce yapıyı tanımlayıp sonra INSERT yapmalısınız, ya da sonradan ALTER TABLE ile indeks ekleyebilirsiniz.

Stored Procedure İçinde Geçici Tablo Kullanımı

Saklı yordamlar içinde geçici tablolar çok güçlü bir araç haline gelir. Ancak burada dikkat edilmesi gereken önemli bir nokta var: Stored procedure her çağrıldığında geçici tablo oluşturulmaya çalışılır. Eğer aynı oturumda prosedür birden fazla kez çağrılırsa “tablo zaten mevcut” hatası alırsınız. Bunu önlemek için DROP TEMPORARY TABLE IF EXISTS kullanın:

DELIMITER //

CREATE PROCEDURE musteri_segmentasyon_raporu(IN baslangic_tarihi DATE, IN bitis_tarihi DATE)
BEGIN
    -- Önceki çalışmadan kalan tabloları temizle
    DROP TEMPORARY TABLE IF EXISTS segment_verileri;
    DROP TEMPORARY TABLE IF EXISTS segment_ozeti;
    
    -- Segment verilerini oluştur
    CREATE TEMPORARY TABLE segment_verileri AS
    SELECT 
        m.musteri_id,
        m.ad,
        m.soyad,
        COUNT(s.siparis_id) AS siparis_sayisi,
        SUM(s.tutar) AS toplam_harcama,
        DATEDIFF(bitis_tarihi, MAX(s.tarih)) AS son_alis_gun,
        CASE 
            WHEN COUNT(s.siparis_id) >= 10 AND SUM(s.tutar) >= 10000 THEN 'Platinum'
            WHEN COUNT(s.siparis_id) >= 5 AND SUM(s.tutar) >= 5000 THEN 'Gold'
            WHEN COUNT(s.siparis_id) >= 2 THEN 'Silver'
            ELSE 'Bronze'
        END AS segment
    FROM musteriler m
    LEFT JOIN siparisler s ON m.musteri_id = s.musteri_id
        AND s.tarih BETWEEN baslangic_tarihi AND bitis_tarihi
    GROUP BY m.musteri_id, m.ad, m.soyad;
    
    -- Özet tabloyu oluştur
    CREATE TEMPORARY TABLE segment_ozeti AS
    SELECT 
        segment,
        COUNT(*) AS musteri_sayisi,
        AVG(toplam_harcama) AS ort_harcama,
        SUM(toplam_harcama) AS toplam_ciro
    FROM segment_verileri
    GROUP BY segment;
    
    -- Sonuçları döndür
    SELECT * FROM segment_verileri ORDER BY toplam_harcama DESC;
    SELECT * FROM segment_ozeti ORDER BY toplam_ciro DESC;
    
END //

DELIMITER ;

-- Prosedürü çağırma
CALL musteri_segmentasyon_raporu('2024-01-01', '2024-12-31');

LIKE ile Mevcut Tablodan Yapı Kopyalama

Bazen mevcut bir tablonun yapısını kopyalayarak geçici tablo oluşturmak istersiniz. Veri olmadan sadece yapıyı almak için LIKE kullanabilirsiniz:

-- Mevcut tablonun yapısını kopyala (veri olmadan)
CREATE TEMPORARY TABLE gecici_siparisler LIKE siparisler;

-- Sonra koşullu veri ekle
INSERT INTO gecici_siparisler
SELECT * FROM siparisler
WHERE tarih >= '2024-01-01'
  AND musteri_id IN (SELECT musteri_id FROM vip_musteriler);

-- Geçici tablo üzerinde güvenle işlem yap
UPDATE gecici_siparisler 
SET durum = 'incelendi' 
WHERE tutar > 10000;

-- Sonuçları analiz et
SELECT durum, COUNT(*), SUM(tutar)
FROM gecici_siparisler
GROUP BY durum;

Bu yöntem, özellikle test senaryolarında veya veri doğrulama işlemlerinde çok işe yarar. Gerçek tabloyu etkilemeden veri manipülasyonu yapabilirsiniz.

Geçici Tablo ile Recursive Hesaplamalar

Geçici tablolar, adım adım hesaplama gerektiren durumlarda da kullanışlıdır. Örneğin bir organizasyon hiyerarşisinde veya birikmeli hesaplamalarda:

-- Stok değer hesaplama örneği
CREATE TEMPORARY TABLE stok_hesap (
    islem_no INT AUTO_INCREMENT PRIMARY KEY,
    urun_id INT,
    tarih DATE,
    miktar INT,
    birim_maliyet DECIMAL(10,2),
    kümülatif_miktar INT,
    kümülatif_maliyet DECIMAL(12,2)
);

-- Ham verileri al
INSERT INTO stok_hesap (urun_id, tarih, miktar, birim_maliyet, kümülatif_miktar, kümülatif_maliyet)
SELECT 
    urun_id,
    tarih,
    miktar,
    birim_maliyet,
    0,
    0
FROM stok_hareketleri
WHERE urun_id = 1001
  AND tur = 'giris'
ORDER BY tarih, hareket_id;

-- Değişken kullanarak birikmeli hesapla
SET @kum_miktar = 0;
SET @kum_maliyet = 0;

UPDATE stok_hesap 
SET 
    kümülatif_miktar = (@kum_miktar := @kum_miktar + miktar),
    kümülatif_maliyet = (@kum_maliyet := @kum_maliyet + (miktar * birim_maliyet))
ORDER BY islem_no;

-- Ortalama maliyeti hesapla
SELECT 
    urun_id,
    MAX(kümülatif_miktar) AS toplam_stok,
    MAX(kümülatif_maliyet) AS toplam_maliyet,
    MAX(kümülatif_maliyet) / MAX(kümülatif_miktar) AS ort_maliyet
FROM stok_hesap
GROUP BY urun_id;

Geçici Tabloları Yönetmek: Silme ve Kontrol

Geçici tablolar oturum kapandığında otomatik silinir ama manuel olarak da silebilirsiniz:

-- Tek bir geçici tabloyu sil
DROP TEMPORARY TABLE gecici_musteriler;

-- Hata vermeden sil (tablo yoksa sessizce geç)
DROP TEMPORARY TABLE IF EXISTS gecici_musteriler;

-- Birden fazla geçici tabloyu aynı anda sil
DROP TEMPORARY TABLE IF EXISTS 
    gecici_musteriler, 
    gecici_siparisler, 
    gecici_raporlar;

-- Geçici tabloların varlığını kontrol etmek (doğrudan yol yok, dolaylı)
-- information_schema'dan geçici tablolar görünmez
-- Bunun yerine SHOW CREATE TABLE kullanılabilir
-- Eğer tablo yoksa hata döner, varsa yapısını gösterir
SHOW CREATE TABLE gecici_musteriler;

Önemli bir not: information_schema.tables geçici tabloları listelelemez. Geçici tabloların varlığını kontrol etmenin en pratik yolu, tabloyu sorgulamayı denemek ve hata yakalama bloğu kullanmaktır. Stored procedure içinde bunu şöyle yapabilirsiniz:

DELIMITER //

CREATE PROCEDURE gecici_tablo_kontrol()
BEGIN
    DECLARE tablo_yok CONDITION FOR SQLSTATE '42S02';
    DECLARE CONTINUE HANDLER FOR tablo_yok
    BEGIN
        SELECT 'Geçici tablo mevcut değil' AS durum;
    END;
    
    -- Bu sorgu tablo yoksa CONTINUE HANDLER devreye girer
    SELECT COUNT(*) AS kayit_sayisi FROM gecici_musteriler;
END //

DELIMITER ;

Performans İpuçları ve Dikkat Edilmesi Gerekenler

Geçici tablolar her zaman mükemmel çözüm değildir. İşte bilmeniz gereken önemli noktalar:

  • ENGINE seçimi: Geçici tablolar varsayılan olarak bellek içi (MEMORY engine) oluşturulabilir ama MariaDB ve MySQL’in konfigürasyonuna bağlıdır. Büyük veri setleri için InnoDB tercih edin.
  • tmp_table_size ve max_heap_table_size: Bu iki sistem değişkeni bellekteki geçici tablo boyutunu sınırlar. Bu sınır aşılınca tablo diske yazılır ve performans düşer. SHOW VARIABLES LIKE 'tmp_table_size' ile mevcut değeri görebilirsiniz.
  • Aynı isim çakışması: Aynı oturumda aynı isimde iki geçici tablo oluşturamazsınız. Prosedür çağrılarında DROP IF EXISTS kullanmayı alışkanlık haline getirin.
  • Replikasyon: Statement-based replikasyonda geçici tablolar bazı sorunlara yol açabilir. Row-based replikasyon kullanıyorsanız bu sorun büyük ölçüde ortadan kalkar.
  • İzinler: Geçici tablo oluşturmak için CREATE TEMPORARY TABLES yetkisine ihtiyaç vardır. Bunu şöyle verebilirsiniz:
-- Kullanıcıya geçici tablo oluşturma yetkisi ver
GRANT CREATE TEMPORARY TABLES ON veritabani_adi.* TO 'kullanici'@'localhost';

-- Mevcut yetkileri kontrol et
SHOW GRANTS FOR 'kullanici'@'localhost';

-- Sistem değişkenlerini kontrol et
SHOW VARIABLES LIKE 'tmp_table_size';
SHOW VARIABLES LIKE 'max_heap_table_size';

-- Oturum bazında değiştirme
SET SESSION tmp_table_size = 268435456; -- 256MB
SET SESSION max_heap_table_size = 268435456;

CTE ile Karşılaştırma: Ne Zaman Hangisi?

MariaDB 10.2.1 ve MySQL 8.0 ile birlikte CTE (Common Table Expression) desteği geldi. Geçici tablolar mı, CTE mi sorusu sık sorulan bir soru. Kısa cevap: her ikisinin de yeri var.

CTE’yi tercih edin:

  • Tek bir sorgu içinde kalmak istiyorsanız
  • Okunabilirlik öncelikliyse
  • Veriye bir kez ihtiyaç varsa
  • Recursive sorgu yazacaksanız (MySQL 8.0+, MariaDB 10.2.2+)

Geçici tabloyu tercih edin:

  • Aynı veri setini birden fazla bağımsız sorguda kullanacaksanız
  • İndeks eklemeniz gerekiyorsa
  • Stored procedure içinde çalışıyorsanız
  • Veri setini aşamalı olarak güncelleyecekseniz
  • Performans kritikse ve büyük veri setleriyle çalışıyorsanız

Sonuç

Geçici tablolar, MariaDB ve MySQL’in sunduğu güçlü ama çoğu zaman gereğinden az kullanılan bir özelliktir. Doğru senaryoda kullanıldığında karmaşık raporlama sorgularını sadeleştirir, tekrarlanan hesaplamaları ortadan kaldırır ve bakımı kolay kod yazmanızı sağlar. Özellikle stored procedure içinde adım adım veri işleme, büyük JOIN operasyonlarını optimize etme ve ETL süreçlerinde geçici tablolar neredeyse vazgeçilmez hale gelir.

Ancak her araç gibi geçici tabloların da dikkat gerektiren yönleri var. DROP TEMPORARY TABLE IF EXISTS kullanımını alışkanlık haline getirin, bellek sınırlarını gözetim altında tutun ve replikasyon ortamlarında davranışları test edin. Bu noktalara dikkat ettiğiniz sürece geçici tablolar veritabanı yönetim araç kutunuzun en değerli parçalarından biri olacak.

Bir yanıt yazın

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