SQL geliştiricilerinin karmaşık sorguları daha anlaşılır, yönetilebilir ve modüler hale getirmek için kullandığı güçlü bir özelliktir CTE (Common Table Expressions). Geçici, adlandırılmış bir sonuç kümesi tanımlayarak, aynı sorgu içinde birden fazla kez başvurulabilen mantıksal birimler oluşturulmasına olanak tanır. Bu sayede, iç içe geçmiş alt sorguların yarattığı karmaşa azalırken, kodun genel kalitesi ve performansı önemli ölçüde artırılır. CTE’ler özellikle hiyerarşik verilerin işlenmesinde vazgeçilmez bir rol oynar.

CTE Nedir?

Common Table Expression (CTE), SQL Server, PostgreSQL, MySQL (8.0 ve üzeri), Oracle gibi modern ilişkisel veritabanı yönetim sistemlerinde desteklenen, geçici ve adlandırılmış bir sonuç kümesidir. Bu sonuç kümesi, bir SELECT, INSERT, UPDATE veya DELETE deyiminin yürütme kapsamı içinde tanımlanır ve yalnızca o deyimin süresince varlığını sürdürür. CTE’ler, sorguyu daha küçük, daha okunabilir mantıksal parçalara ayırarak karmaşıklığı azaltmayı amaçlar. İç içe geçmiş alt sorguların yerine daha düz bir yapı sunarak sorgu akışını takip etmeyi kolaylaştırır.

CTE’leri Neden Kullanmalıyız?

CTE’ler, sadece karmaşık sorguları basitleştirmekle kalmaz, aynı zamanda SQL kodunuzun genel kalitesini artıran birçok avantaj sunar:

  • Okunabilirlik ve Anlaşılabilirlik: Büyük ve karmaşık sorguları daha küçük, adlandırılmış mantıksal birimlere böler. Her bir CTE, belirli bir alt görevi yerine getirdiği için sorgunun genel akışı ve amacı daha kolay anlaşılır hale gelir. Bu, kod incelemelerini ve ekip içi işbirliğini kolaylaştırır.
  • Sürdürülebilirlik ve Hata Ayıklama: Modüler yapı sayesinde, bir hata durumunda veya mantık değişikliğinde ilgili CTE’yi bulmak ve üzerinde değişiklik yapmak çok daha hızlı ve güvenlidir. Tek bir büyük sorguda hata ayıklamak yerine, her bir CTE’yi bağımsız olarak test edebilirsiniz.
  • Modülerlik ve Tekrar Kullanım: Bir CTE, tanımlandığı sorgu içinde birden fazla kez kullanılabilir. Bu, aynı alt sorgunun birden fazla yerde tekrarlanmasını önler, böylece kod tekrarını azaltır ve tutarlılığı artırır.
  • Özyinelemeli Sorgular: CTE’lerin en güçlü yönlerinden biri, özyinelemeli (recursive) sorgular yazma yeteneğidir. Bu özellik, hiyerarşik verileri (organizasyon şemaları, ağaç yapıları, malzeme listeleri gibi) işlemek için vazgeçilmezdir. Bir CTE kendi kendisine başvurarak bir hiyerarşiyi adım adım gezebilir.
  • Performans İyileştirmeleri (Dolaylı Olarak): Doğrudan performans artışı garanti etmese de, daha okunabilir ve optimize edilebilir sorgular yazmaya teşvik eder. Veritabanı optimizasyoncusu, iyi yapılandırılmış CTE’leri genellikle verimli bir şekilde işleyebilir.

CTE Tanımlama Yöntemi ve Yapısı

Bir CTE, WITH anahtar kelimesi kullanılarak tanımlanır. Temel sözdizimi aşağıdaki gibidir:

WITH CTE_Adi (Kolon1, Kolon2, ...) AS (
    SELECT Kolon1, Kolon2, ...
    FROM TabloAdi
    WHERE Sart
)
SELECT *
FROM CTE_Adi
WHERE BaskaSart;
  • WITH: CTE tanımlamaya başladığımızı gösterir.
  • CTE_Adi: Oluşturduğunuz geçici sonuç kümesine verdiğiniz isimdir. Bu isim, aynı ana sorgu içinde başka yerlerde kullanılabilir.
  • (Kolon1, Kolon2, ...) (İsteğe bağlı): CTE’nin döndüreceği sütunlar için takma adlar (alias) tanımlanabilir. Bu tanımlanmazsa, içindeki SELECT deyiminin sütun adları kullanılır.
  • AS: CTE’nin tanımının başladığını belirtir.
  • (SELECT ...): Bu parantez içindeki SELECT deyimi, CTE’nin temelini oluşturan ve döndürülecek geçici sonuç kümesini oluşturan sorgudur.

Bir CTE tanımlandıktan sonra, ana SELECT, INSERT, UPDATE veya DELETE deyiminde bir tablo gibi kullanılabilir.

Basit Bir CTE Örneği

Aşağıdaki örnekte, ortalama maaşın üzerinde kazanan çalışanları bulan basit bir CTE kullanımını inceleyelim. Önce CTE kullanmadan, sonra CTE ile karşılaştıralım.

Örnek Veri Seti:

CREATE TABLE Calisanlar (
    CalisanID INT PRIMARY KEY,
    Ad NVARCHAR(50),
    Soyad NVARCHAR(50),
    Departman NVARCHAR(50),
    Maas DECIMAL(10, 2)
);

INSERT INTO Calisanlar (CalisanID, Ad, Soyad, Departman, Maas) VALUES
(1, 'Ali', 'Can', 'IT', 60000),
(2, 'Ayşe', 'Demir', 'İK', 55000),
(3, 'Mehmet', 'Yılmaz', 'IT', 75000),
(4, 'Elif', 'Kaya', 'Muhasebe', 50000),
(5, 'Deniz', 'Tunç', 'IT', 62000),
(6, 'Kemal', 'Uslu', 'Muhasebe', 58000),
(7, 'Zeynep', 'Ateş', 'İK', 70000);

CTE Kullanmadan Sorgu:

SELECT Ad, Soyad, Maas
FROM Calisanlar
WHERE Maas > (SELECT AVG(Maas) FROM Calisanlar);

CTE Kullanarak Sorgu:

WITH OrtalamaMaas AS (
    SELECT AVG(Maas) AS GenelOrtalamaMaas
    FROM Calisanlar
)
SELECT c.Ad, c.Soyad, c.Maas
FROM Calisanlar c, OrtalamaMaas om
WHERE c.Maas > om.GenelOrtalamaMaas;

Yukarıdaki CTE örneğinde, OrtalamaMaas adında geçici bir sonuç kümesi oluşturduk. Bu CTE, tüm çalışanların ortalama maaşını hesaplar. Daha sonra ana sorgu, bu OrtalamaMaas CTE’sini kullanarak, maaşı genel ortalamanın üzerinde olan çalışanları listeler. Bu yaklaşım, sorgunun ne yaptığını daha açık bir şekilde ifade eder ve iç içe geçmiş alt sorguların karmaşıklığını ortadan kaldırır.

Birden Fazla CTE Kullanımı

Bir WITH deyimi içinde birden fazla CTE tanımlayabilir ve bunları virgülle ayırabilirsiniz. Ayrıca, sonraki bir CTE, kendisinden önce tanımlanmış bir CTE’ye başvurabilir.

WITH DepartmanOrtalamaMaaslari AS (
    SELECT Departman, AVG(Maas) AS OrtalamaDepartmanMaasi
    FROM Calisanlar
    GROUP BY Departman
),
YuksekMaasliCalisanlar AS (
    SELECT c.Ad, c.Soyad, c.Departman, c.Maas, dom.OrtalamaDepartmanMaasi
    FROM Calisanlar c
    INNER JOIN DepartmanOrtalamaMaaslari dom ON c.Departman = dom.Departman
    WHERE c.Maas > dom.OrtalamaDepartmanMaasi
)
SELECT Ad, Soyad, Departman, Maas, OrtalamaDepartmanMaasi
FROM YuksekMaasliCalisanlar
ORDER BY Departman, Maas DESC;

Bu örnekte, önce her departmanın ortalama maaşını hesaplayan DepartmanOrtalamaMaaslari CTE’si oluşturulur. Ardından, bu CTE’ye başvurarak, kendi departman ortalamasının üzerinde maaş alan çalışanları bulan YuksekMaasliCalisanlar CTE’si tanımlanır. Son olarak, ana sorgu bu ikinci CTE’den veri çeker. Bu zincirleme yapı, daha karmaşık iş mantıklarını adım adım uygulamak için idealdir.

Özyinelemeli (Recursive) CTE’ler

Özyinelemeli CTE’ler, hiyerarşik veya ağaç yapısındaki verileri (örneğin, bir organizasyon şeması, ürün bileşenleri listesi veya sosyal ağ bağlantıları) sorgulamak için kullanılır. Bir özyinelemeli CTE, kendi kendisine başvurarak belirli bir koşul karşılanana kadar tekrar tekrar çalışır.

Özyinelemeli bir CTE iki ana bölümden oluşur:

  1. Anchor Member (Çapa Üyesi): Özyinelemenin başlangıç noktasını belirler. Genellikle bir SELECT deyimidir ve ilk veri kümesini döndürür. Bu kısım tek başına çalışabilir.
  2. Recursive Member (Özyinelemeli Üye): Çapa üyesi tarafından döndürülen ve daha önce özyinelemeli üye tarafından döndürülmüş sonuçlara atıfta bulunur. Bu kısım, bir UNION ALL veya UNION operatörü ile çapa üyesiyle birleştirilir.

Bir özyinelemeli CTE’nin sonlanmasını sağlamak için bir bitiş koşulu (genellikle bir WHERE maddesi içinde) tanımlamak çok önemlidir, aksi takdirde sonsuz döngüye girilebilir.

Özyinelemeli CTE Örneği: Çalışan Hiyerarşisi

Bir organizasyon şemasındaki çalışanların yöneticilerini takip ederek tam hiyerarşiyi bulalım.

CREATE TABLE Personel (
    PersonelID INT PRIMARY KEY,
    Ad NVARCHAR(50),
    YoneticisiID INT NULL REFERENCES Personel(PersonelID)
);

INSERT INTO Personel (PersonelID, Ad, YoneticisiID) VALUES
(1, 'CEO Ayşe', NULL),
(2, 'Genel Md. Ali', 1),
(3, 'Departman Md. Can', 2),
(4, 'Takım Lideri Elif', 3),
(5, 'Çalışan Mehmet', 4),
(6, 'Çalışan Zeynep', 4),
(7, 'Departman Md. Deniz', 2),
(8, 'Takım Lideri Murat', 7),
(9, 'Çalışan Pınar', 8);
WITH Hiyerarsi AS (
    -- Çapa Üyesi: En üst yöneticiyi bul
    SELECT
        PersonelID,
        Ad,
        YoneticisiID,
        1 AS Seviye,
        CAST(Ad AS NVARCHAR(MAX)) AS Yol
    FROM Personel
    WHERE YoneticisiID IS NULL

    UNION ALL

    -- Özyinelemeli Üye: Altındaki çalışanları bul
    SELECT
        p.PersonelID,
        p.Ad,
        p.YoneticisiID,
        h.Seviye + 1 AS Seviye,
        CAST(h.Yol + ' -> ' + p.Ad AS NVARCHAR(MAX)) AS Yol
    FROM Personel p
    INNER JOIN Hiyerarsi h ON p.YoneticisiID = h.PersonelID
)
SELECT PersonelID, Ad, YoneticisiID, Seviye, Yol
FROM Hiyerarsi
ORDER BY Seviye, PersonelID;

Bu özyinelemeli CTE’de, Hiyerarsi CTE’si iki bölümden oluşur: Çapa üyesi, yöneticisi olmayan (NULL) personeli (CEO) bulur. Özyinelemeli üye ise, önceki seviyedeki yöneticilere bağlı olan personeli bulur ve her yinelemede Seviye‘yi artırır ve Yol‘u günceller. Bu süreç, hiyerarşide daha fazla alt seviye kalmayana kadar devam eder ve tam organizasyon şemasını ortaya çıkarır.

CTE’leri Ne Zaman Kullanmamalıyız?

CTE’ler güçlü bir araç olsa da, her durumda en iyi çözüm olmayabilir:

  • Basit Sorgular: Çok basit, tek satırlık bir alt sorgunun yeterli olduğu durumlarda CTE kullanmak gereksiz karmaşıklık yaratabilir. Bir CTE’nin getirdiği fayda, sorgunun karmaşıklığıyla doğru orantılıdır.
  • Performans Odaklı Büyük Geçici Veri Kümeleri: Eğer ara sonuç kümeniz çok büyükse ve bu sonuç kümesi üzerinde indeksleme veya istatistik toplama gibi işlemler gerekiyorsa, geçici tablolar (#temp_table) veya tablo değişkenleri (@table_variable) daha iyi performans gösterebilir. CTE’ler genellikle optimize edici tarafından türetilmiş tablolar (derived tables) gibi işlenir ve kendi başlarına indeksleme veya istatistik tutmazlar. Ancak modern veritabanı motorları, CTE’leri akıllıca optimize edebilir.
  • Birden Fazla Sorguda Kullanım: Eğer aynı geçici sonuç kümesini farklı, birbirinden bağımsız sorgularda kullanmanız gerekiyorsa, CTE yerine bir View (Görünüm) veya kalıcı bir tablo oluşturmak daha uygun olabilir. CTE’ler yalnızca tanımlandıkları ana sorgunun yaşam döngüsü boyunca var olurlar.

Sonuç

SQL’de CTE’ler (Common Table Expressions), karmaşık sorgu yazımını basitleştiren ve kod kalitesini artıran vazgeçilmez bir özelliktir. Okunabilirliği, sürdürülebilirliği ve modülerliği sayesinde geliştiricilere büyük kolaylıklar sunar. Özellikle özyinelemeli CTE’ler, hiyerarşik verilerin etkili bir şekilde işlenmesi için kritik bir araçtır. Doğru kullanıldığında, SQL sorgularınızın daha şeffaf, yönetilebilir ve güçlü olmasını sağlar, böylece genel veritabanı etkileşiminizi geliştirir. CTE’leri benimsemek, daha iyi SQL kodu yazmanın önemli bir adımıdır.