SQL özyineleme aslında nasıl çalışır?


19

Diğer programlama dillerinden SQL'e gelince, özyinelemeli bir sorgunun yapısı oldukça garip görünüyor. Adım adım ilerleyin ve parçalanıyor gibi görünüyor.

Aşağıdaki basit örneği düşünün:

CREATE TABLE #NUMS
(N BIGINT);

INSERT INTO #NUMS
VALUES (3), (5), (7);

WITH R AS
(
    SELECT N FROM #NUMS
    UNION ALL
    SELECT N*N AS N FROM R WHERE N*N < 10000000
)
SELECT N FROM R ORDER BY N;

Hadi içinden geçelim.

İlk olarak, bağlantı elemanı yürütülür ve sonuç kümesi R'ye yerleştirilir. Bu nedenle R, {3, 5, 7} olarak başlatılır.

Ardından, yürütme BİRLİĞİ TÜMÜ altına düşer ve özyinelemeli üye ilk kez yürütülür. R üzerinde yürütülür (yani, şu anda elimizde bulunan R üzerinde: {3, 5, 7}). Bu, {9, 25, 49} ile sonuçlanır.

Bu yeni sonuçla ne işe yarıyor? Mevcut {3, 5, 7} 'ye {9, 25, 49} ekliyor, sonuçta ortaya çıkan sendika R'yi etiketliyor ve ardından oradan tekrarlamaya devam ediyor mu? Yoksa R'yi sadece bu yeni sonuç {9, 25, 49} olarak yeniden tanımlıyor ve daha sonra bütün birleşme mi yapıyor?

Her iki seçim de mantıklı değil.

R şimdi {3, 5, 7, 9, 25, 49} ise ve özyinelemenin bir sonraki yinelemesini gerçekleştirirsek, o zaman {9, 25, 49, 81, 625, 2401} ile sonuçlanır ve kayıp {3, 5, 7}.

R artık sadece {9, 25, 49} ise, yanlış etiketleme sorunumuz var. R, sabitleme elemanı sonuç kümesinin ve müteakip tüm özyinelemeli üye sonuç kümelerinin birleşimi olarak anlaşılır. Oysa {9, 25, 49}, R'nin sadece bir bileşenidir. Şimdiye kadar biriktirdiğimiz R'nin tamamı değildir. Bu nedenle, özyinelemeli üyeyi R'den seçim olarak yazmak mantıklı değildir.


@Max Vernon ve @Michael S.'nin aşağıda ayrıntılarıyla neler sunduğunu kesinlikle takdir ediyorum. Yani, (1) tüm bileşenler özyineleme sınırına veya null kümeye kadar oluşturulur ve sonra (2) tüm bileşenler bir araya getirilir. Bu aslında çalışmak için SQL özyineleme anlamak.

SQL'i yeniden tasarlıyor olsaydık, belki daha açık ve açık bir sözdizimi uygulardık, şöyle bir şey:

WITH R AS
(
    SELECT   N
    INTO     R[0]
    FROM     #NUMS
    UNION ALL
    SELECT   N*N AS N
    INTO     R[K+1]
    FROM     R[K]
    WHERE    N*N < 10000000
)
SELECT N FROM R ORDER BY N;

Matematikte endüktif bir kanıt gibi.

Şu anda olduğu gibi SQL özyineleme ile ilgili sorun, kafa karıştırıcı bir şekilde yazılmış olmasıdır. Yazılma şekli, her bileşenin R arasından seçilerek oluşturulduğunu söyler, ancak bu, şimdiye kadar inşa edilmiş (veya inşa edilmiş gibi görünüyor) tam R anlamına gelmez. Sadece önceki bileşen anlamına gelir.


"R şimdi {3, 5, 7, 9, 25, 49} ise ve tekrarlamanın bir sonraki yinelemesini gerçekleştirirsek, o zaman {9, 25, 49, 81, 625, 2401} ile sonuçlanırız ve {3, 5, 7} kaybettim. " Böyle çalışıyorsa {3,5,7} 'i nasıl kaybettiğinizi görmüyorum.
ypercubeᵀᴹ

@ yper-crazyhat-cubeᵀᴹ - Önerdiğim ilk hipotezden devam ediyordum, yani, ara R o noktaya kadar hesaplanan her şeyin birikimi ise? Daha sonra, özyinelemeli elemanın bir sonraki yinelemesinde, R'nin her elemanı kare içine alınır. Böylece, {3, 5, 7} {9, 25, 49} olur ve bir daha asla R'de {3, 5, 7} yoktur. Başka bir deyişle, {3, 5, 7} R'den kaybolur.
UnLogicGuys

Yanıtlar:


26

Özyinelemeli CTE'lerin BOL açıklaması, özyinelemeli yürütmenin anlambilimini aşağıdaki gibi açıklar:

  1. CTE ifadesini çapa ve özyinelemeli üyelere ayırın.
  2. İlk çağırma veya temel sonuç kümesini (T0) oluşturan bağlantı üyelerini çalıştırın.
  3. Yinelemeli üyeleri giriş olarak Ti ve çıkış olarak Ti + 1 ile çalıştırın.
  4. Boş bir set geri dönünceye kadar 3. adımı tekrarlayın.
  5. Sonuç kümesini döndür. Bu T0'dan Tn'ye kadar olan bir BİRLİK.

Bu nedenle, her seviye giriş olarak şu ana kadar biriken sonuç kümesinin tamamında değil.

Yukarıdaki mantıksal olarak nasıl çalışır . Fiziksel olarak özyinelemeli CTE'ler şu anda SQL Server'da iç içe döngüler ve yığın biriktirme ile uygulanmaktadır. Bu burada ve burada açıklanır ve pratikte her özyinelemeli elemanın tüm düzeyle değil, önceki seviyeden ana satırla çalıştığı anlamına gelir . Ancak özyinelemeli CTE'lerde izin verilen sözdizimi üzerindeki çeşitli kısıtlamalar bu yaklaşımın işe yaradığı anlamına gelir.

ORDER BYSorgunuzdan kaldırırsanız sonuçlar aşağıdaki gibi sıralanır

+---------+
|    N    |
+---------+
|       3 |
|       5 |
|       7 |
|      49 |
|    2401 |
| 5764801 |
|      25 |
|     625 |
|  390625 |
|       9 |
|      81 |
|    6561 |
+---------+

Bunun nedeni, yürütme planının aşağıdakilere çok benzer şekilde çalışmasıdır C#

using System;
using System.Collections.Generic;
using System.Diagnostics;

public class Program
{
    private static readonly Stack<dynamic> StackSpool = new Stack<dynamic>();

    private static void Main(string[] args)
    {
        //temp table #NUMS
        var nums = new[] { 3, 5, 7 };

        //Anchor member
        foreach (var number in nums)
            AddToStackSpoolAndEmit(number, 0);

        //Recursive part
        ProcessStackSpool();

        Console.WriteLine("Finished");
        Console.ReadLine();
    }

    private static void AddToStackSpoolAndEmit(long number, int recursionLevel)
    {
        StackSpool.Push(new { N = number, RecursionLevel = recursionLevel });
        Console.WriteLine(number);
    }

    private static void ProcessStackSpool()
    {
        //recursion base case
        if (StackSpool.Count == 0)
            return;

        var row = StackSpool.Pop();

        int thisLevel = row.RecursionLevel + 1;
        long thisN = row.N * row.N;

        Debug.Assert(thisLevel <= 100, "max recursion level exceeded");

        if (thisN < 10000000)
            AddToStackSpoolAndEmit(thisN, thisLevel);

        ProcessStackSpool();
    }
}

NB1: ankraj elemanının ilk çocuk zamanla yukarıda olduğu gibi 3onun kardeşleri, ilgili tüm bilgileri işleniyor 5ve 7ve onların soyundan, zaten makaradan atıldı ve artık erişilebilir olmuştur.

NB2: Yukarıdaki C #, yürütme planıyla aynı genel semantiğe sahiptir, ancak yürütme planındaki akış aynı değildir, çünkü operatörler boru hatlı bir exection tarzında çalışır. Bu, yaklaşımın özünü göstermek için basitleştirilmiş bir örnektir. Planın kendisi hakkında daha fazla ayrıntı için önceki bağlantılara bakın.

NB3: Yığın biriktirmesinin kendisi, özyineleme düzeyinin anahtar sütunu ve gerektiğinde benzersizleştiriciler eklenmiş benzersiz olmayan kümelenmiş bir dizin olarak uygulanır ( kaynak )


6
SQL Server'da özyinelemeli sorgular, ayrıştırma sırasında her zaman özyinelemeden yinelemeye (yığınlama ile) dönüştürülür. Yineleme için uygulama kuralı IterateToDepthFirst- Iterate(seed,rcsv)->PhysIterate(seed,rcsv). Sadece FYI. Mükemmel cevap.
Paul White diyor GoFundMonica

Bu arada, UNION ALL yerine UNION'a da izin verilir, ancak SQL Server bunu yapmaz.
Joshua

5

Bu sadece (yarı) eğitimli bir tahmindir ve muhtemelen tamamen yanlıştır. Bu arada ilginç bir soru.

T-SQL bildirimsel bir dildir; belki özyinelemeli bir CTE, UNION ALL'un sol tarafındaki sonuçların geçici bir tabloya eklendiği imleç tarzı bir işleme dönüştürülür, ardından UNION ALL'un sağ tarafı sol taraftaki değerlere uygulanır.

Bu nedenle, önce UNION ALL'un sol tarafının çıktısını sonuç kümesine ekleriz, daha sonra UNION ALL'un sağ tarafının sonuçlarını sol tarafa uygular ve bunu sonuç kümesine ekleriz. Sol taraf daha sonra sağ taraftaki çıktı ile değiştirilir ve sağ taraf tekrar "yeni" sol tarafa uygulanır. Bunun gibi bir şey:

  1. {3,5,7} -> sonuç kümesi
  2. {3,5,7} 'ye uygulanan {9,25,49} özyinelemeli ifadeler. {9,25,49} sonuç kümesine eklenir ve UNION ALL'un sol tarafının yerini alır.
  3. {9,25,49} için uygulanan {81,625,2401} özyinelemeli ifadeler. {81,625,2401} sonuç kümesine eklenir ve UNION ALL'un sol tarafının yerini alır.
  4. {6561,390625,5764801} olan {81,625,2401} için özyinelemeli ifadeler uygulandı. Sonuç kümesine {6561,390625,5764801} eklendi.
  5. Sonraki yineleme WHERE yan tümcesinin false döndürmesine neden olduğundan imleç tamamlandı.

Bu davranışı, özyinelemeli CTE'nin yürütme planında görebilirsiniz:

resim açıklamasını buraya girin

Bu, yukarıdaki 1. adımdır, burada UNION ALL'un sol tarafının çıktıya eklenmesi:

resim açıklamasını buraya girin

Bu, çıktının sonuç kümesiyle birleştirildiği UNION ALL'un sağ tarafıdır:

resim açıklamasını buraya girin


4

SQL Server belgelerine söz, T ı ve T i + 1 , ne çok anlaşılabilir, ne de gerçek bir uygulamanın doğru bir açıklama.

Temel fikir, sorgunun özyinelemeli bölümünün önceki tüm sonuçlara yalnızca bir kez bakmasıdır .

Diğer veritabanlarının bunu nasıl uyguladığını görmek faydalı olabilir ( aynı sonucu elde etmek için ). Postgres belgelerine diyor ki:

Özyinelemeli Sorgu Değerlendirmesi

  1. Özyinelemesiz terimi değerlendirin. İçin UNION(ancak değil UNION ALL), yinelenen satırları atın. Geri kalan tüm satırları özyinelemeli sorgunun sonucuna dahil edin ve bunları geçici bir çalışma tablosuna yerleştirin .
  2. Çalışma masası boş olmadığı sürece şu adımları tekrarlayın:
    1. Özyinelemeli öz referans yerine çalışma tablosunun geçerli içeriğini değiştirerek özyinelemeli terimi değerlendirin. İçin UNION(ancak UNION ALL), ıskarta yinelenen satırlar ve önceki sonuç satırı çoğaltmak satırlar. Geri kalan tüm satırları özyinelemeli sorgunun sonucuna dahil edin ve bunları geçici bir ara tabloya yerleştirin .
    2. Çalışma tablasının içindekileri ara tablonun içeriği ile değiştirin, ardından ara tablayı boşaltın.

Not
Açıkçası, bu süreç yineleme özyineleme değil RECURSIVE, SQL standartları komitesi tarafından seçilen terminolojidir.

SQLite belgeler biraz farklı uygulanmasını ipuçları ve bu bir-satır-at-a-time algoritması anlamak için en kolay olabilir:

Özyinelemeli tablonun içeriğini hesaplamak için temel algoritma aşağıdaki gibidir:

  1. Çalıştırın initial-selectve sonuçları bir kuyruğa ekleyin.
  2. Kuyruk boş olmasa da:
    1. Kuyruktan tek bir satır ayıklayın.
    2. Bu tek satırı özyinelemeli tabloya ekle
    3. Yeni çıkarılan tek satırın yinelemeli tablodaki tek satır olduğunu varsayalım ve recursive-selecttüm sonuçları kuyruğa ekleyerek çalıştırın .

Yukarıdaki temel prosedür aşağıdaki ek kurallarla değiştirilebilir:

  • Bir UNION operatörü bağlanıyorsa initial-selectile recursive-selectözdeş satır önceden kuyruğa eklenmiş, sonra sadece kuyruğuna satırları ekleyin. Tekrarlanan satırlar, yinelenen satırlar yineleme adımı tarafından kuyruktan zaten çıkarılmış olsa bile kuyruğa eklenmeden önce atılır. İşleç BİRLİĞİ TÜMÜ ise, hem initial-selectve hem de tarafından oluşturulan tüm satırlar recursive-selecttekrar olsalar bile her zaman kuyruğa eklenir.
    [...]

0

Benim bilgim özellikle DB2'de ama açıklama diyagramlarına bakmak SQL Server ile aynı görünüyor.

Plan buradan geliyor:

Planı Yapıştır'da görün

SQL Server Açıklama Planı

Optimize edici, her özyinelemeli sorgu için tam anlamıyla bir birleşim çalıştırmaz. Sorgunun yapısını alır ve birliğin ilk bölümünü bir "çapa üyesine" atar, daha sonra birliğin ikinci yarısında ("özyinelemeli üye" olarak adlandırılır ve tanımlanan sınırlamalara ulaşana kadar özyinelemeli olarak). özyineleme tamamlandığında, optimize edici tüm kayıtları bir araya getirir.

Optimize edici, sadece önceden tanımlanmış bir işlem yapmayı öneri olarak alır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.