การเรียกใช้ SQL ซ้ำใช้งานได้จริงอย่างไร


19

เมื่อมาถึง SQL จากภาษาการเขียนโปรแกรมอื่นโครงสร้างของเคียวรีแบบเรียกซ้ำจะค่อนข้างแปลก เดินผ่านทีละขั้นตอนและดูเหมือนว่าจะกระจุย

ลองพิจารณาตัวอย่างง่ายๆดังต่อไปนี้:

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;

ลองเดินดูกัน

ขั้นแรกสมาชิกจุดยึดดำเนินการและชุดผลลัพธ์ถูกใส่ใน R ดังนั้น R จะเริ่มต้นได้ที่ {3, 5, 7}

จากนั้นการดำเนินการจะลดลงต่ำกว่า UNION ALL และสมาชิกแบบเรียกซ้ำจะถูกดำเนินการเป็นครั้งแรก มันรันบน R (นั่นคือบน R ที่เรามีอยู่ในมือ: {3, 5, 7}) ผลลัพธ์นี้ใน {9, 25, 49}

ผลลัพธ์ใหม่นี้ทำอะไรได้บ้าง มันผนวก {9, 25, 49} เข้ากับ {3, 5, 7} ที่มีอยู่แล้ว, ติดฉลาก union R ที่เป็นผลลัพธ์แล้วดำเนินการต่อด้วยการเรียกซ้ำจากที่นั่นหรือไม่? หรือว่าจะกำหนด R เป็นเฉพาะผลลัพธ์ใหม่นี้ {9, 25, 49} และทำสหภาพทั้งหมดในภายหลังหรือไม่

ตัวเลือกทั้งสองไม่สมเหตุสมผล

หาก R คือตอนนี้ {3, 5, 7, 9, 25, 49} และเราดำเนินการวนรอบการเรียกซ้ำครั้งต่อไปเราจะสิ้นสุดด้วย {9, 25, 49, 81, 625, 2401} และเราได้ หายไป {3, 5, 7}

หาก R คือตอนนี้เพียง {9, 25, 49} แสดงว่าเรามีปัญหาการติดฉลากผิด เข้าใจว่าเป็นสหภาพของชุดผลลัพธ์สมาชิกสมอและชุดผลลัพธ์สมาชิกซ้ำทั้งหมดที่ตามมา ในขณะที่ {9, 25, 49} เป็นเพียงส่วนประกอบของ R มันไม่ได้เป็น R เต็มที่เราได้เกิดขึ้น ดังนั้นการเขียนสมาชิกแบบเรียกซ้ำเนื่องจากการเลือกจาก R ไม่สมเหตุสมผล


ฉันขอขอบคุณสิ่งที่ @ Max Vernon และ @Michael S. มีรายละเอียดด้านล่าง กล่าวคือ (1) ส่วนประกอบทั้งหมดถูกสร้างขึ้นจนถึงขีด จำกัด การเรียกซ้ำหรือการตั้งค่าว่างจากนั้น (2) ส่วนประกอบทั้งหมดจะรวมเข้าด้วยกัน นี่คือวิธีที่ฉันเข้าใจการเรียกซ้ำ SQL เพื่อใช้งานได้จริง

ถ้าเราออกแบบ SQL บางทีเราอาจบังคับใช้ไวยากรณ์ที่ชัดเจนและชัดเจนมากขึ้นดังนี้:

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;

ประเภทของการพิสูจน์อุปนัยในคณิตศาสตร์

ปัญหาเกี่ยวกับการเรียกซ้ำ SQL ตามที่เป็นอยู่ในปัจจุบันคือมันถูกเขียนในลักษณะที่สับสน วิธีที่เขียนนั้นบอกว่าส่วนประกอบแต่ละอย่างเกิดจากการเลือกจาก R แต่มันไม่ได้หมายความว่า R แบบเต็มที่ถูกสร้างขึ้น (หรือดูเหมือนจะถูกสร้างขึ้นมา) มันหมายถึงองค์ประกอบก่อนหน้า


"ถ้าตอนนี้ R คือ {3, 5, 7, 9, 25, 49} และเราดำเนินการวนรอบการเรียกซ้ำครั้งต่อไปเราจะสิ้นสุดด้วย {9, 25, 49, 81, 625, 2401} และเรา ' ทำหายไป {3, 5, 7} " ฉันไม่เห็นว่าคุณเสีย {3,5,7} อย่างไรถ้ามันทำงานอย่างนั้น
ypercubeᵀᴹ

@ yper-crazyhat-cubeᵀᴹ - ฉันได้ติดตามจากสมมติฐานแรกที่ฉันเสนอคือจะเกิดอะไรขึ้นถ้าค่ากลาง R คือการสะสมของทุกสิ่งที่คำนวณมาจนถึงจุดนั้น? จากนั้นในการวนรอบถัดไปของสมาชิกแบบเรียกซ้ำทุกองค์ประกอบของ R จะถูกยกกำลังสอง ดังนั้น {3, 5, 7} กลายเป็น {9, 25, 49} และเราไม่เคยมี {3, 5, 7} ใน R กล่าวอีกนัยหนึ่งคือ {3, 5, 7} หายไปจาก R.
UnLogicGuys

คำตอบ:


26

คำอธิบาย BOL ของ CTE แบบเรียกซ้ำจะอธิบายความหมายของการเรียกใช้แบบเรียกซ้ำดังนี้:

  1. แยกการแสดงออกของ CTE ออกเป็นจุดยึดและสมาชิกแบบเรียกซ้ำ
  2. เรียกใช้สมาชิกจุดยึดสร้างการเรียกครั้งแรกหรือชุดผลลัพธ์พื้นฐาน (T0)
  3. รันสมาชิกแบบเรียกซ้ำโดยใช้ Ti เป็นอินพุตและ Ti + 1 เป็นเอาต์พุต
  4. ทำซ้ำขั้นตอนที่ 3 จนกว่าจะส่งคืนชุดเปล่า
  5. ส่งคืนชุดผลลัพธ์ นี่คือยูเนี่ยนทั้งหมดของ T0 ถึง Tn

ดังนั้นแต่ละระดับมีเพียงอินพุตเป็นระดับที่สูงกว่าไม่ใช่ชุดผลลัพธ์ทั้งหมดที่สะสมไว้

ข้างต้นเป็นวิธีการทำงานในเชิงตรรกะ ปัจจุบัน CTE แบบเรียกซ้ำทางกายภาพนั้นจะถูกนำไปใช้กับลูปซ้อนและสปูลสพูลใน SQL Server เสมอ สิ่งนี้อธิบายไว้ที่นี่และที่นี่และหมายความว่าในทางปฏิบัติแต่ละองค์ประกอบแบบเรียกซ้ำก็แค่ทำงานกับแถวพาเรนต์จากระดับก่อนหน้าไม่ใช่ระดับทั้งหมด แต่ข้อ จำกัด ต่างๆเกี่ยวกับไวยากรณ์ที่อนุญาตใน CTE แบบเรียกซ้ำหมายความว่าวิธีการนี้ใช้งานได้

หากคุณลบออกORDER BYจากแบบสอบถามของคุณผลลัพธ์จะถูกจัดเรียงดังนี้

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

นี่เป็นเพราะแผนการดำเนินการทำงานคล้ายกับต่อไปนี้ 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: ตามเวลาที่ลูกคนแรกของสมาชิกสมอ3กำลังประมวลผลข้อมูลทั้งหมดเกี่ยวกับพี่น้องของตน5และ7และลูกหลานของพวกเขาได้ถูกละทิ้งจากสปูลแล้วและไม่สามารถเข้าถึงได้อีกต่อไป

NB2: C # ด้านบนมีความหมายโดยรวมเหมือนกับแผนการดำเนินการ แต่การไหลในแผนการดำเนินการไม่เหมือนกันเนื่องจากมีผู้ปฏิบัติงานทำงานในรูปแบบการส่งผ่าน นี่เป็นตัวอย่างที่ง่ายเพื่อแสดงให้เห็นถึงส่วนสำคัญของวิธีการ ดูลิงค์ก่อนหน้านี้สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับแผนนั้น

NB3: สปูลสพูลนั้นถูกนำมาใช้อย่างชัดเจนว่าเป็นดัชนีแบบคลัสเตอร์ที่ไม่ซ้ำพร้อมคอลัมน์หลักของระดับการเรียกซ้ำและการเพิ่มตัวระบุที่ไม่ซ้ำ (ที่มา )


6
แบบสอบถามแบบเรียกซ้ำใน SQL Server จะถูกแปลงจากการเรียกซ้ำเป็นการวนซ้ำเสมอ (ด้วยการซ้อน) ในระหว่างการแยกวิเคราะห์ กฎการใช้งานสำหรับการทำซ้ำคือIterateToDepthFirst- Iterate(seed,rcsv)->PhysIterate(seed,rcsv). เพียงแค่ FYI คำตอบที่ยอดเยี่ยม
พอลไวท์พูดว่า GoFundMonica

อนึ่งอนุญาตให้ใช้ UNION แทน UNION ALL แต่ SQL Server จะไม่ทำเช่นนั้น
Joshua

5

นี่เป็นเพียงการคาดเดาการศึกษา (กึ่ง) และอาจผิดอย่างสมบูรณ์ คำถามที่น่าสนใจโดยวิธีการ

T-SQL เป็นภาษาที่ประกาศ บางที CTE แบบเรียกซ้ำอาจถูกแปลเป็นการดำเนินการแบบเคอร์เซอร์ซึ่งผลลัพธ์จากด้านซ้ายของ UNION ALL จะถูกผนวกเข้ากับตารางชั่วคราวจากนั้นทางด้านขวาของ UNION ALL จะถูกนำไปใช้กับค่าทางด้านซ้าย

ดังนั้นก่อนอื่นเราใส่ผลลัพธ์ของด้านซ้ายของ UNION ALL ลงในชุดผลลัพธ์จากนั้นเราแทรกผลลัพธ์ของด้านขวาของ UNION ALL ที่ใช้กับด้านซ้ายแล้วใส่เข้าไปในชุดผลลัพธ์ ด้านซ้ายจะถูกแทนที่ด้วยเอาต์พุตจากด้านขวาและด้านขวาจะถูกใช้อีกครั้งกับด้านซ้าย "ใหม่" บางสิ่งเช่นนี้

  1. {3,5,7} -> ชุดผลลัพธ์
  2. ข้อความเรียกซ้ำที่ใช้กับ {3,5,7} ซึ่งคือ {9,25,49} {9,25,49} ถูกเพิ่มเข้าไปในชุดผลลัพธ์และแทนที่ด้านซ้ายของ UNION ALL
  3. ข้อความเรียกซ้ำที่ใช้กับ {9,25,49} ซึ่งคือ {81,625,2401} {81,625,2401} ถูกเพิ่มเข้าไปในชุดผลลัพธ์และแทนที่ด้านซ้ายของ UNION ALL
  4. ข้อความเรียกซ้ำที่ใช้กับ {81,625,2401} ซึ่งคือ {6561,390625,5764801} เพิ่ม {6561,390625,5764801} ชุดผลลัพธ์
  5. เคอร์เซอร์เสร็จสมบูรณ์เนื่องจากการวนซ้ำครั้งถัดไปส่งผลให้ WHERE clause ส่งคืนเท็จ

คุณสามารถเห็นพฤติกรรมนี้ในแผนการดำเนินการสำหรับ CTE แบบเรียกซ้ำ:

ป้อนคำอธิบายรูปภาพที่นี่

นี่คือขั้นตอนที่ 1 ด้านบนซึ่งด้านซ้ายของ UNION ALL ถูกเพิ่มไปยังเอาต์พุต:

ป้อนคำอธิบายรูปภาพที่นี่

นี่คือด้านขวาของยูเนี่ยนทั้งหมดที่เอาท์พุทเชื่อมต่อกับชุดผลลัพธ์:

ป้อนคำอธิบายรูปภาพที่นี่


4

เอกสาร SQL Serverซึ่งกล่าวถึงT ฉันและT i + 1เป็นค่าที่เข้าใจมากหรือคำอธิบายที่ถูกต้องของการดำเนินงานที่เกิดขึ้นจริง

แนวคิดพื้นฐานคือว่าส่วน recursive ของแบบสอบถามมีลักษณะที่ผลก่อนหน้านี้ทั้งหมด แต่เพียงครั้งเดียว

อาจเป็นประโยชน์หากคุณดูว่าฐานข้อมูลอื่น ๆ ใช้สิ่งนี้อย่างไร (เพื่อให้ได้ผลลัพธ์เดียวกัน ) เอกสาร Postgresพูดว่า:

การประเมินการเรียกซ้ำ

  1. ประเมินคำศัพท์ที่ไม่เกิดซ้ำ สำหรับUNION(แต่ไม่ใช่UNION ALL) ให้ทิ้งแถวที่ซ้ำกัน รวมถึงแถวที่เหลือทั้งหมดในผลของแบบสอบถาม recursive และยังวางไว้ในชั่วคราวตารางการทำงาน
  2. ตราบใดที่โต๊ะทำงานไม่ว่างให้ทำซ้ำขั้นตอนเหล่านี้:
    1. ประเมินคำแบบเรียกซ้ำโดยแทนที่เนื้อหาปัจจุบันของตารางการทำงานสำหรับการอ้างอิงตัวเองแบบเรียกซ้ำ สำหรับUNION(แต่ไม่ใช่UNION ALL) ให้ทิ้งแถวและแถวที่ซ้ำกันซึ่งทำซ้ำแถวผลลัพธ์ก่อนหน้าใด ๆ รวมถึงแถวที่เหลือทั้งหมดในผลของแบบสอบถาม recursive และยังวางไว้ในชั่วคราวโต๊ะกลาง
    2. แทนที่เนื้อหาของตารางการทำงานด้วยเนื้อหาของตารางกลางจากนั้นล้างตารางกลาง

หมายเหตุการ
พูดอย่างเคร่งครัดกระบวนการนี้คือการวนซ้ำไม่ใช่การเรียกซ้ำ แต่RECURSIVEเป็นคำศัพท์ที่เลือกโดยคณะกรรมการมาตรฐาน SQL

เอกสาร SQLiteคำแนะนำในการดำเนินงานที่แตกต่างกันเล็กน้อยและขั้นตอนวิธีการหนึ่งแถวที่เวลาอาจจะง่ายที่จะเข้าใจ:

อัลกอริทึมพื้นฐานสำหรับการคำนวณเนื้อหาของตารางแบบเรียกซ้ำมีดังนี้:

  1. เรียกใช้initial-selectและเพิ่มผลลัพธ์ลงในคิว
  2. ในขณะที่คิวไม่ว่างเปล่า:
    1. แยกแถวเดียวออกจากคิว
    2. แทรกแถวเดี่ยวนั้นลงในตารางแบบเรียกซ้ำ
    3. สมมติว่าแถวเดียวที่เพิ่งแยกออกมานั้นเป็นแถวเดียวในตารางแบบเรียกซ้ำและเรียกใช้recursive-selectโดยเพิ่มผลลัพธ์ทั้งหมดลงในคิว

ขั้นตอนพื้นฐานด้านบนอาจแก้ไขได้โดยกฎเพิ่มเติมดังต่อไปนี้:

  • หากโอเปอเรเตอร์ของ UNION เชื่อมต่อinitial-selectกับrecursive-selectดังนั้นให้เพิ่มแถวในคิวเท่านั้นหากไม่มีการเพิ่มแถวที่เหมือนกันในคิวก่อนหน้านี้ แถวที่ถูกทำซ้ำจะถูกยกเลิกก่อนที่จะถูกเพิ่มลงในคิวแม้ว่าแถวที่ทำซ้ำได้ถูกแยกออกจากคิวแล้วโดยขั้นตอนการเรียกซ้ำ หากผู้ประกอบการที่เป็นยูเนี่ยนทั้งหมดแล้วแถวทั้งหมดที่สร้างขึ้นโดยทั้งสองinitial-selectและrecursive-selectมีการเพิ่มเสมอคิวแม้ว่าพวกเขาจะซ้ำ
    [ ... ]

0

ความรู้ของฉันอยู่ใน DB2 เป็นพิเศษ แต่การดูไดอะแกรมอธิบายดูเหมือนจะเหมือนกับ SQL Server

แผนมาจากที่นี่:

ดูในการวางแผน

SQL Server อธิบายแผน

เครื่องมือเพิ่มประสิทธิภาพไม่ได้เรียกใช้ยูเนี่ยนทั้งหมดสำหรับการสืบค้นซ้ำแต่ละครั้ง จะใช้โครงสร้างของแบบสอบถามและกำหนดส่วนแรกของสหภาพทั้งหมดให้กับ "สมาชิกสมอ" แล้วมันจะทำงานในช่วงครึ่งหลังของสหภาพทั้งหมด (เรียกว่า "สมาชิก recursive" ซ้ำจนกว่าจะถึงข้อ จำกัด ที่กำหนดไว้ การเรียกซ้ำเสร็จสมบูรณ์เครื่องมือเพิ่มประสิทธิภาพจะรวมระเบียนทั้งหมดเข้าด้วยกัน

เครื่องมือเพิ่มประสิทธิภาพใช้เป็นข้อเสนอแนะเพื่อทำการดำเนินการที่กำหนดไว้ล่วงหน้า

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.