Menu

SQLite Recursive CTE: WITH RECURSIVE Kullanımı

SQLite'ta recursive CTE nasıl çalışır? WITH RECURSIVE'in anchor/recursive yapısı, parent-child ağaçlarda gezinme, sayı serisi üretme ve sonsuz döngülerden kaçınma.

Bu sayfada çalıştırılabilir editörler var — düzenle, çalıştır ve sonucu anında gör.

SQL'de Recursion Tuhaf Geliyor — Ta ki Görene Kadar

Çoğu sorgu, zaten var olan veriden satır döndürür. SQLite recursive CTE ise farklı çalışır: kendi ürettiği çıktıyı tekrar girdi olarak besler ve eklenecek yeni bir şey kalmayana dek adım adım satır üretir. Derinliği belli olmayan bir ağaçta gezinmek ya da elinizde sayı tablosu olmadan 1'den 100'e kadar sayı üretmek işte tam böyle yapılır.

Kalıp her zaman aynıdır:

WITH RECURSIVE name(columns) AS (
    -- başlangıç: ilk satırlar
    SELECT ...
    UNION ALL
    -- özyinelemeli: önceki adımdan türetilen satırlar
    SELECT ... FROM name WHERE ...
)
SELECT * FROM name;

En üstte anchor (başlangıç) sorgusu, ardından UNION ALL, en altta da recursive (özyinelemeli) sorgu yer alır. SQLite önce anchor sorgusunu bir kez çalıştırır; sonra recursive kısmı tekrar tekrar işletir — her seferinde bir önceki adımda üretilen satırları girdi olarak kullanır — ve yeni satır gelmediği anda durur.

1'den 10'a Kadar Sayma

En basit sqlite recursive CTE örneği bir sayı serisi üretir. Üstelik hiçbir tabloya bile ihtiyacın yok:

Adım adım takip edelim:

  1. Anchor (başlangıç) tek bir satır üretir: n = 1.
  2. Recursive (özyinelemeli) adım bu satırı alır, n + 1 = 2 hesaplar ve 2 < 10 doğru olduğundan satırı tutar.
  3. Sonraki iterasyon n = 2 değerini alıp n = 3 üretir. Ve bu böyle devam eder.
  4. n, 10 değerine ulaştığında 10 < 10 artık yanlıştır; özyinelemeli adım hiç satır döndürmez ve SQLite durur.

Buradaki WHERE n < 10 koşulu, durma koşuludur. Bu olmazsa sorgu sonsuza kadar çalışır.

SQLite ile tarih serisi üretme

Aynı mantık gerçek raporlarda da çok işe yarar — bir tarih aralığındaki her günü, hiçbir şey olmayan günler dahil, tek tek doldurmak için kullanılır:

Bunu genelde bir olay (events) tablosuyla LEFT JOIN ederek olaysız günleri doğru şekilde sayarsınız. Düz bir GROUP BY date boş günleri tamamen atlar; tarih serisi ise her gün için size bir satır verir, olay olsa da olmasa da.

SQLite ile parent-child ağaç yapısında gezinmek

Klasik kullanım senaryosu. Aşağıda her satırın kendi yöneticisini işaret ettiği bir çalışan tablosu var:

Anchor kısmı kökü seçiyor (yöneticisi olmayan kişiyi). Özyinelemeli adım ise employees tablosunu CTE ile join ederek manager_id değeri CTE'de zaten bulunan bir id ile eşleşen herkesi buluyor. Her iterasyon bir kademe daha derine iniyor. depth ise çıktıyı girintilemek için eklediğimiz basit bir sayaç.

Bu sorgu istediğiniz derinlikteki ağaç yapısı için çalışır. İki seviye olmuş, on seviye olmuş fark etmez; sorgu aynı kalır.

Belirli Bir Kaydın Tüm Atalarını Bulma

Şimdi yönü ters çevirelim. Kökten aşağıya inmek yerine, belirli bir çalışandan yukarıya doğru yürüyüp o kişinin tüm yönetici zincirini bulalım:

Çıkış noktamız (anchor) başlangıçtaki çalışandır. Her özyinelemeli adımda bir üst yöneticiye sıçrarız. SQLite kök kayda ulaşınca durur — yani manager_id IS NULL olduğunda join hiçbir şey bulamaz ve sorgu sonlanır.

Bu desen; ekmek kırıntısı (breadcrumb) menüleri, iç içe yorumlar, kategori yolları ve kısacası "yukarıya doğru tırman" mantığının geçtiği her yerde işe yarar.

Durdurma Koşulları ve Sonsuz Döngüler

En sık karşılaşılan hata, durdurma koşulunu unutmak ya da hiç tetiklenmeyecek bir koşul yazmaktır. Şu ikisini karşılaştıralım:

-- Sonsuza dek çalışır:
WITH RECURSIVE bad(n) AS (
    SELECT 1
    UNION ALL
    SELECT n + 1 FROM bad
)
SELECT n FROM bad;

WHERE koşulu olmadığı için sonucu sıfır satıra düşürecek bir koşul da yok. SQLite de hiç sıkılmadan sonsuza kadar saymaya çalışır.

Kendinizi korumak için iki alışkanlık edinin:

  1. Recursive kısımda büyümeyi sınırlayan bir WHERE koşulu mutlaka bulunsun.
  2. Geliştirme aşamasında dış SELECT'e bir LIMIT ekleyin; bu sizin güvenlik ağınız olsun. Durdurma koşulunda bir hata yapmış olsanız bile sorgu yine de sonlanır.

CTE'nin kendisi sınırsız, ama LIMIT 5 dış sorguyu erkenden durduruyor. SQLite, LIMIT'in ihtiyacından fazlasını rekürsif olarak hesaplamayacak kadar akıllı. Keşif amaçlı işe yarar; ancak production kodunda gerçek bir durdurma koşulunun yerine geçmez.

Graph'larda Döngüler

Ağaçlarda döngü olmaz. Ama genel graph'larda olabilir — ve veride bir döngü varsa, naif yazılmış bir recursive CTE sonsuza kadar döner. Çözüm: o ana kadar yürüdüğünüz yolu takip etmek ve daha önce uğradığınız node'lara tekrar girmemek:

path aslında o ana kadar uğranan node'ları virgülle ayrılmış şekilde tutan bir metin. Yeni bir node eklemeden önce WHERE koşulu, o node'un listede olup olmadığını kontrol ediyor. Bu koruma olmasaydı, 1 → 2 → 3 → 1 döngüsü sonsuza kadar dönerdi.

SQL'de hazır bir "ziyaret edilenler kümesi" yok — bunu kendin kurguluyorsunuz: ya bir string olarak biriktiriyorsunuz ya da o ana kadar oluşan CTE ile JOIN yaparak.

Recursive CTE mi, Self Join mi?

Sadece bir-iki seviye derine inecekseniz self join hem daha basit hem de daha hızlıdır:

Bu, "her bir kişinin doğrudan yöneticisi kim" sorusunu çözer. Ama iş "Ada'ya, kaç kademe üzerinden olursa olsun, dolaylı bağlı olan herkes" sorusuna gelince — derinlik belli değildir — bunu temiz bir şekilde yalnızca recursive CTE çözer. Aracı, ihtiyacın olan derinliğe göre seç:

  • Sabit ve küçük derinlik: self join, belki iki ya da üç tane.
  • Belirsiz veya değişken derinlik: WITH RECURSIVE.

Zihinsel Model

SQLite recursive CTE, aslında deklaratif şekilde yazılmış bir döngüdür:

  • Anchor (başlangıç sorgusu), döngünün ilk değeridir.
  • Recursive sorgu, döngünün gövdesidir — mevcut satırlardan yeni satır kümesini üretir.
  • Durma koşulu, döngünün çıkış testidir — sıfır satır döndürdüğünde döngü biter.
  • UNION ALL ise her şeyi nihai sonuç kümesinde biriktirir.

Bu eşleşme kafanızda oturduğunda, sözdizimi artık tuhaf gelmemeye başlar. Aslında SQL içinde bir for döngüsü yazıyorsunuz.

Sırada: İndeksler

Recursive CTE'ler bol miktarda satır üzerinde gezinir ve recursive adımdaki join her iterasyonda yeniden çalışır. Join'de kullanılan sütun indeksli değilse, performans uçurumdan düşer. İndeksler bir sonraki bölümün konusu ve manager_id tam da indeksten en çok fayda görecek türden bir sütun.

Sıkça Sorulan Sorular

SQLite'ta recursive CTE nedir?

Recursive CTE, kendisine tekrar tekrar başvurarak sonuç kümesi üreten bir WITH RECURSIVE sorgusudur. İki parçası vardır ve bunlar UNION ALL ile birleştirilir: başlangıç satırlarını üreten anchor sorgusu ve bir önceki adımdan yeni satırlar türeten recursive sorgu. SQLite, recursive kısım yeni satır üretmeyi bırakana kadar sorguyu çalıştırmaya devam eder.

WITH RECURSIVE'i ne zaman kullanmalıyım?

Bir ağaç ya da graf üzerinde gezmen gerektiğinde (çalışan-yönetici, kategori-alt kategori, iç içe yorumlar) veya bir dizi üretmen gerektiğinde (bir aralıktaki tüm tarihler, 1'den 100'e kadar sayılar) tam aradığın şey budur. Düz JOIN'ler bir-iki seviyeye kadar iş görür; ama derinliği önceden bilmediğin durumlar için recursive CTE en temiz çözüm.

Recursive CTE'de sonsuz döngüye nasıl düşmem?

Recursive sorgunun mutlaka bir durma koşulu olmalı: ya sonunda sıfır satır döndürecek bir WHERE ifadesi ya da üst sınırını koyduğun bir sayaç. Döngü içerebilen graf yapılarında gezdiğin yolu bir kolonda tutup oradaki düğümleri tekrar ziyaret etmemen gerekir. Güvenlik ağı olarak dış sorguya bir LIMIT koy; böylece kontrolden çıkan bir recursion belleği doldurmadan durur.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA