İçeriğe geç

SQL WITH Kullanımı (CTE) ve Örnekleri

11 defa okundu

Konu: Veri Analizi

SQL WITH Kullanımı (CTE) ve Örnekleri

SQL Server'da WITH ifadesi ile sorgunun başında geçici bir sonuç kümesi tanımlanır ve aynı sorgu içinde bir tablo gibi kullanılır. Bu yapıya CTE (Common Table Expression) denir. İç içe yazılan alt sorguların (subquery) yerine kullanıldığında sorgu daha okunaklı olur, aynı sonuç kümesi sorgu içinde birden fazla yerde kullanılabilir ve kendini çağıran (özyinelemeli, recursive) sorgular yazılabilir. WITH kullanımı SQL Server 2005 ve sonrası versiyonlarda çalışmaktadır.

WITH ifadesinin genel kullanılışı aşağıdaki gibidir.

SQL
  1. WITH CTE_ADI AS
  2. (
  3. SELECT ALAN1, ALAN2 FROM TABLO_ADI
  4. )
  5. SELECT * FROM CTE_ADI

Aşağıdaki örneklerde personel bilgilerinden oluşan geçici bir tablo üzerinden WITH kullanımının farklı örnekleri verilmiştir.

Veri kaynağı için geçici tablo oluşturma

SQL
  1. SELECT 1 AS ID,'AHMET1' AS ADI,'DENEME1' AS SOYADI,N'YÖNETİM' AS BOLUM,90000 AS MAAS,NULL AS YONETICIID
  2. INTO #PERSONEL
  3. UNION
  4. SELECT 2 AS ID,'AHMET2' AS ADI,'DENEME2' AS SOYADI,N'MUHASEBE' AS BOLUM,52000 AS MAAS,1 AS YONETICIID
  5. UNION
  6. SELECT 3 AS ID,'AHMET3' AS ADI,'DENEME3' AS SOYADI,N'MUHASEBE' AS BOLUM,41000 AS MAAS,2 AS YONETICIID
  7. UNION
  8. SELECT 4 AS ID,'AHMET4' AS ADI,'DENEME4' AS SOYADI,N'MUHASEBE' AS BOLUM,38000 AS MAAS,2 AS YONETICIID
  9. UNION
  10. SELECT 5 AS ID,'AHMET5' AS ADI,'DENEME5' AS SOYADI,N'SATIŞ' AS BOLUM,55000 AS MAAS,1 AS YONETICIID
  11. UNION
  12. SELECT 6 AS ID,'AHMET6' AS ADI,'DENEME6' AS SOYADI,N'SATIŞ' AS BOLUM,44000 AS MAAS,5 AS YONETICIID
  13. UNION
  14. SELECT 7 AS ID,'AHMET7' AS ADI,'DENEME7' AS SOYADI,N'SATIŞ' AS BOLUM,47000 AS MAAS,5 AS YONETICIID
  15. UNION
  16. SELECT 8 AS ID,'AHMET8' AS ADI,'DENEME8' AS SOYADI,N'SATIŞ' AS BOLUM,39000 AS MAAS,6 AS YONETICIID
  17. UNION
  18. SELECT 9 AS ID,'AHMET9' AS ADI,'DENEME9' AS SOYADI,N'BİLGİ İŞLEM' AS BOLUM,60000 AS MAAS,1 AS YONETICIID
  19. UNION
  20. SELECT 10 AS ID,'AHMET10' AS ADI,'DENEME10' AS SOYADI,N'BİLGİ İŞLEM' AS BOLUM,48000 AS MAAS,9 AS YONETICIID
  21. UNION
  22. SELECT 11 AS ID,'AHMET11' AS ADI,'DENEME11' AS SOYADI,N'BİLGİ İŞLEM' AS BOLUM,45000 AS MAAS,9 AS YONETICIID

Oluşturduğumuz geçici tablomuzun içeriği aşağıdaki gibidir. YONETICIID alanı personelin bağlı olduğu yöneticinin ID numarasıdır.

SQL
  1. SELECT * FROM #PERSONEL ORDER BY ID
IDADISOYADIBOLUMMAASYONETICIID
1AHMET1DENEME1YÖNETİM90000NULL
2AHMET2DENEME2MUHASEBE520001
3AHMET3DENEME3MUHASEBE410002
4AHMET4DENEME4MUHASEBE380002
5AHMET5DENEME5SATIŞ550001
6AHMET6DENEME6SATIŞ440005
7AHMET7DENEME7SATIŞ470005
8AHMET8DENEME8SATIŞ390006
9AHMET9DENEME9BİLGİ İŞLEM600001
10AHMET10DENEME10BİLGİ İŞLEM480009
11AHMET11DENEME11BİLGİ İŞLEM450009

1. Bölüm ortalamasının üzerinde maaş alan personeller

Önce WITH ile her bölümün ortalama maaşını bulan sonuç kümesini (BOLUM_ORTALAMA) tanımlıyoruz. Sonra bu kümeyi personel tablosuyla birleştirerek maaşı bölüm ortalamasının üzerinde olan personelleri listeliyoruz.

SQL
  1. WITH BOLUM_ORTALAMA AS
  2. (
  3. SELECT BOLUM, AVG(MAAS) AS ORTALAMA
  4. FROM #PERSONEL
  5. GROUP BY BOLUM
  6. )
  7. SELECT P.ADI, P.SOYADI, P.BOLUM, P.MAAS, B.ORTALAMA
  8. FROM #PERSONEL P
  9. INNER JOIN BOLUM_ORTALAMA B ON B.BOLUM = P.BOLUM
  10. WHERE P.MAAS > B.ORTALAMA
  11. ORDER BY P.BOLUM, P.MAAS DESC

Sorgu Sonucu:

ADISOYADIBOLUMMAASORTALAMA
AHMET9DENEME9BİLGİ İŞLEM6000051000
AHMET2DENEME2MUHASEBE5200043666
AHMET5DENEME5SATIŞ5500046250
AHMET7DENEME7SATIŞ4700046250

MAAS alanı tam sayı olduğu için ortalama da tam sayı olarak döner (MUHASEBE bölümünde 43.666,67 yerine 43666). Kuruşlu ortalama için AVG(MAAS * 1.0) yazılabilir.

2. Birden fazla WITH (CTE) kullanımı

Bir sorguda birden fazla CTE tanımlanabilir; WITH bir kez yazılır, sonraki CTE'ler virgül ile ayrılır. İkinci CTE birinciyi kullanabilir. Aşağıdaki sorguda önce bölüm toplamlarını (BOLUM_TOPLAM), sonra bu toplamlardan genel toplamı (GENEL_TOPLAM) buluyoruz ve her bölümün maaş toplamı içindeki payını yüzde olarak hesaplıyoruz.

SQL
  1. WITH BOLUM_TOPLAM AS
  2. (
  3. SELECT BOLUM, COUNT(ID) AS KISI, SUM(MAAS) AS TOPLAM
  4. FROM #PERSONEL
  5. GROUP BY BOLUM
  6. ),
  7. GENEL_TOPLAM AS
  8. (
  9. SELECT SUM(TOPLAM) AS GENEL FROM BOLUM_TOPLAM
  10. )
  11. SELECT B.BOLUM, B.KISI, B.TOPLAM,
  12. CAST(B.TOPLAM * 100.0 / G.GENEL AS DECIMAL(5,2)) AS YUZDE
  13. FROM BOLUM_TOPLAM B
  14. CROSS JOIN GENEL_TOPLAM G
  15. ORDER BY B.TOPLAM DESC

Sorgu Sonucu:

BOLUMKISITOPLAMYUZDE
SATIŞ418500033.09
BİLGİ İŞLEM315300027.37
MUHASEBE313100023.43
YÖNETİM19000016.10

3. Her bölümün en yüksek maaşlı personeli

ROW_NUMBER fonksiyonu ile her bölüm içinde maaşa göre sıra numarası veriyoruz. Sıra numarası WHERE içinde doğrudan kullanılamadığı için önce WITH ile sıralı kümeyi oluşturup sonra SIRA = 1 olan kayıtları seçiyoruz. SIRA <= 3 yazılırsa her bölümün en yüksek maaşlı üç personeli listelenir.

SQL
  1. WITH SIRALI AS
  2. (
  3. SELECT ADI, SOYADI, BOLUM, MAAS,
  4. ROW_NUMBER() OVER(PARTITION BY BOLUM ORDER BY MAAS DESC) AS SIRA
  5. FROM #PERSONEL
  6. )
  7. SELECT BOLUM, ADI, SOYADI, MAAS
  8. FROM SIRALI
  9. WHERE SIRA = 1
  10. ORDER BY BOLUM

Sorgu Sonucu:

BOLUMADISOYADIMAAS
BİLGİ İŞLEMAHMET9DENEME960000
MUHASEBEAHMET2DENEME252000
SATIŞAHMET5DENEME555000
YÖNETİMAHMET1DENEME190000

4. Özyinelemeli (recursive) WITH ile organizasyon şeması

WITH ile tanımlanan küme kendi içinde kendini çağırabilir. Bu yapıda iki sorgu UNION ALL ile birleştirilir: ilk sorgu başlangıç kayıtlarını (yöneticisi olmayan genel müdür), ikinci sorgu ise bir önceki adımda bulunan kişilere bağlı personelleri getirir. Yeni kayıt bulunamayınca sorgu durur. Böylece her personelin şemadaki seviyesini ve yöneticiden kendisine kadar olan yolu buluyoruz.

SQL
  1. WITH SEMA AS
  2. (
  3. SELECT ID, ADI, BOLUM, YONETICIID, 1 AS SEVIYE,
  4. CAST(ADI AS NVARCHAR(200)) AS YOL
  5. FROM #PERSONEL
  6. WHERE YONETICIID IS NULL
  7. UNION ALL
  8. SELECT P.ID, P.ADI, P.BOLUM, P.YONETICIID, S.SEVIYE + 1,
  9. CAST(S.YOL + N' > ' + P.ADI AS NVARCHAR(200))
  10. FROM #PERSONEL P
  11. INNER JOIN SEMA S ON P.YONETICIID = S.ID
  12. )
  13. SELECT SEVIYE, ADI, BOLUM, YOL
  14. FROM SEMA
  15. ORDER BY YOL

Sorgu Sonucu:

SEVIYEADIBOLUMYOL
1AHMET1YÖNETİMAHMET1
2AHMET2MUHASEBEAHMET1 > AHMET2
3AHMET3MUHASEBEAHMET1 > AHMET2 > AHMET3
3AHMET4MUHASEBEAHMET1 > AHMET2 > AHMET4
2AHMET5SATIŞAHMET1 > AHMET5
3AHMET6SATIŞAHMET1 > AHMET5 > AHMET6
4AHMET8SATIŞAHMET1 > AHMET5 > AHMET6 > AHMET8
3AHMET7SATIŞAHMET1 > AHMET5 > AHMET7
2AHMET9BİLGİ İŞLEMAHMET1 > AHMET9
3AHMET10BİLGİ İŞLEMAHMET1 > AHMET9 > AHMET10
3AHMET11BİLGİ İŞLEMAHMET1 > AHMET9 > AHMET11

5. WITH ile ay listesi oluşturma

Özyinelemeli WITH ile tabloya ihtiyaç duymadan sayı veya tarih listesi de üretilebilir. Aşağıdaki sorgu 2026 yılının ayları için ay numarası, ay adı, ayın ilk ve son gününü listeler. Satış olmayan ayların da raporda sıfır olarak görünmesi için bu liste satış tablosuyla LEFT JOIN ile birleştirilebilir.

SQL
  1. WITH AYLAR AS
  2. (
  3. SELECT CAST('2026-01-01' AS DATE) AS TARIH
  4. UNION ALL
  5. SELECT DATEADD(MONTH, 1, TARIH) FROM AYLAR
  6. WHERE TARIH < '2026-12-01'
  7. )
  8. SELECT MONTH(TARIH) AS AY_NO, FORMAT(TARIH, 'MMMM', 'tr-TR') AS AY_ADI, TARIH AS AY_BASI,
  9. EOMONTH(TARIH) AS AY_SONU
  10. FROM AYLAR
  11. OPTION (MAXRECURSION 12)

Sorgu Sonucu:

AY_NOAY_ADIAY_BASIAY_SONU
1Ocak2026-01-012026-01-31
2Şubat2026-02-012026-02-28
3Mart2026-03-012026-03-31
4Nisan2026-04-012026-04-30
5Mayıs2026-05-012026-05-31
6Haziran2026-06-012026-06-30
7Temmuz2026-07-012026-07-31
8Ağustos2026-08-012026-08-31
9Eylül2026-09-012026-09-30
10Ekim2026-10-012026-10-31
11Kasım2026-11-012026-11-30
12Aralık2026-12-012026-12-31

Özyinelemeli sorgular varsayılan olarak en fazla 100 kez kendini çağırabilir; daha uzun listeler için sorgunun sonuna OPTION (MAXRECURSION 1000) gibi bir sınır yazılır (0 sınırsızdır). FORMAT ve EOMONTH fonksiyonları SQL Server 2012 ve sonrasında çalışır.

6. WITH ile mükerrer kayıtları silme

WITH ile tanımlanan küme üzerinden UPDATE ve DELETE de yapılabilir; işlem asıl tabloya uygulanır. Aşağıdaki rehber tablosunda aynı kişi birden fazla kez kayıtlıdır.

SQL
  1. SELECT 1 AS ID,'AHMET1' AS ADI,'DENEME1' AS SOYADI,'5550001111' AS TELEFON
  2. INTO #REHBER
  3. UNION
  4. SELECT 2 AS ID,'AHMET2' AS ADI,'DENEME2' AS SOYADI,'5550002222' AS TELEFON
  5. UNION
  6. SELECT 3 AS ID,'AHMET1' AS ADI,'DENEME1' AS SOYADI,'5550001111' AS TELEFON
  7. UNION
  8. SELECT 4 AS ID,'AHMET3' AS ADI,'DENEME3' AS SOYADI,'5550003333' AS TELEFON
  9. UNION
  10. SELECT 5 AS ID,'AHMET2' AS ADI,'DENEME2' AS SOYADI,'5550002222' AS TELEFON
  11. UNION
  12. SELECT 6 AS ID,'AHMET1' AS ADI,'DENEME1' AS SOYADI,'5550001111' AS TELEFON
SQL
  1. SELECT * FROM #REHBER ORDER BY ID
IDADISOYADITELEFON
1AHMET1DENEME15550001111
2AHMET2DENEME25550002222
3AHMET1DENEME15550001111
4AHMET3DENEME35550003333
5AHMET2DENEME25550002222
6AHMET1DENEME15550001111

Ad, soyad ve telefonu aynı olan kayıtlara ID sırasıyla numara veriyoruz; ilk kayıt kalıyor, SIRA numarası 1'den büyük olan tekrar eden kayıtlar siliniyor.

SQL
  1. WITH MUKERRER AS
  2. (
  3. SELECT ID, ADI, SOYADI, TELEFON,
  4. ROW_NUMBER() OVER(PARTITION BY ADI, SOYADI, TELEFON ORDER BY ID) AS SIRA
  5. FROM #REHBER
  6. )
  7. DELETE FROM MUKERRER WHERE SIRA > 1
SQL
  1. SELECT * FROM #REHBER ORDER BY ID

Sorgu Sonucu:

IDADISOYADITELEFON
1AHMET1DENEME15550001111
2AHMET2DENEME25550002222
4AHMET3DENEME35550003333

DELETE işlemi geri alınamaz. Gerçek tabloda çalıştırmadan önce DELETE yerine SELECT * FROM MUKERRER WHERE SIRA > 1 yazarak silinecek kayıtları kontrol edin.

WITH kullanırken dikkat edilmesi gerekenler

  • WITH'ten önce başka bir komut varsa o komut noktalı virgül (;) ile bitirilmelidir; aksi halde "Incorrect syntax near the keyword 'with'" hatası alınır. Bu yüzden sorguların başına ;WITH yazıldığı da görülür.
  • CTE yalnızca hemen arkasından gelen tek sorguda (SELECT, INSERT, UPDATE, DELETE) geçerlidir. Sonuç birden fazla sorguda kullanılacaksa geçici tablo (#TABLO) veya tablo değişkeni (@TABLO) kullanılmalıdır.
  • CTE içinde ORDER BY, TOP olmadan kullanılamaz; sıralama en sondaki sorguda yapılır.
  • CTE sonuçları saklanmaz; sorgu içinde iki kez kullanılan CTE iki kez çalışır. Büyük veride aynı sonuç defalarca kullanılacaksa geçici tablo daha hızlı olabilir.

İlgili yazılar: MS SQL ile sorguya sıra numarası ekleme · MS SQL'de mükerrer kayıt bulma · SQL döngü kullanımı · MS SQL ile çapraz sorgu (pivot tablo)

Bu yazı işinize yaradı mı?

Veri Analizi konusundaki diğer yazılar

Veri Analizi: 17 yazının tümü →

WhatsApp'ta paylaş

İlgili Yazılar