سلسلة sql للمحترفين التعابير الجدولية الشائعة Common Table Expressions في SQL


محمد الميداوي

توليد قيم

لا توفّر معظم قواعد البيانات طريقة أصلية لإنشاء سلاسل الأرقام؛ بيْد أنّه يمكن استخدام تعبيرات الجدول الشائعة أو التعبيرات الجدولية (common table expressions) مع العوديّة (recursion) لمحاكاة هذا النوع من الوظائف.

يولّد المثال التالي تعبيرًا جدوليًا يُسمّى ‎Numbers‎، واسم عموده ‎i‎، ويحتوي أرقام الصفوف (1-5):

-- لتخزين الأعداد `i` واسم العمود `Numbers"إعطاء اسم الجدول
WITH Numbers(i) AS (
 -- البداية
 SELECT 1
 -- ضروري لأجل العودية UNION ALL المعامل
 UNION ALL
 -- تعبير التكرار
 SELECT i + 1
 -- التعبير الجدولي الذي أعلنا عنه والمُستخدم كمصدر للعودية
 FROM Numbers
 -- عبارة إنهاء العودية
 WHERE i < 5
)
-- استخدام التعبير الجدولي المُنشأ كما لو كان جدولا عاديا
SELECT i FROM Numbers;

الخرج الناتج:

i
1
2
3
4
5

يمكن استخدام هذه الطريقة مع أي مجال من الأعداد، وكذلك مع أنواع أخرى من البيانات.

الترقيم العودي لشجيرة recursively enumerating a subtree

المثال التالي يوضّح كيفية ترقيم شجيرة (subtree) عوديًا:

WITH RECURSIVE ManagedByJames(Level, ID, FName, LName) AS (
 -- البدء بهذا الصف
 SELECT 1, ID, FName, LName
 FROM Employees
 WHERE ID = 1
 UNION ALL
 -- الحصول على الموظفين الذين يعملون تحت إمرة أيّ من المدراء المُختارين سابقا
 SELECT ManagedByJames.Level + 1,
           Employees.ID,
           Employees.FName,
           Employees.LName
 FROM Employees
 JOIN ManagedByJames
 ON Employees.ManagerID = ManagedByJames.ID
 ORDER BY 1 DESC -- depth-first search البحث الأولي-العميق
)
SELECT * FROM ManagedByJames;

الخرج الناتج:

Level ID FName LName
1 1 James Smith
2 2 John Johnson
3 4 Johnathon Smith
2 3 Michael Williams

الاستعلامات المؤقتة Temporary query

تتصرف الاستعلامات المؤقتة (Temporary query) مثل الاستعلامات المتشعّبة (nested subqueries)، إلّا أنّ صياغتها مختلفة.

WITH ReadyCars AS (
   SELECT *
    FROM Cars
    WHERE Status = 'READY'
)
SELECT ID, Model, TotalCost
FROM ReadyCars
ORDER BY TotalCost;

الخرج الناتج:

ID Model TotalCost
1 Ford F-150 200
2 Ford F-150 230

هذا استعلام فرعي مكافئ:

SELECT ID, Model, TotalCost
FROM (
    SELECT *
    FROM Cars
    WHERE Status = 'READY'
) AS ReadyCars
ORDER BY TotalCost

التسلّق العوديّ لشجرة recursively going up in a tree

المثال التالي يوضّح كيفية تسلق شجرة عوديًا:

WITH RECURSIVE ManagersOfJonathon AS (
 -- البدء بهذا الصف
 SELECT *
 FROM Employees
 WHERE ID = 4
 UNION ALL
 -- الحصول على مدراء كل الصفوف المُختارة سابقا
 SELECT Employees.*
 FROM Employees
 JOIN ManagersOfJonathon
 ON Employees.ID = ManagersOfJonathon.ManagerID
)
SELECT * FROM ManagersOfJonathon;

الخرج الناتج:

Id FName LName PhoneNumber ManagerId DepartmentId
4 Johnathon Smith 1212121212 2 1
2 John Johnson 2468101214 1 1
1 James Smith 1234567890 NULL 1

التوليد العودي للتواريخ

المثال التالي يولّد تواريخ مع تضمين الجداول الزمنية لفِرَق العمل:

DECLARE @DateFrom DATETIME = '2016-06-01 06:00'
DECLARE @DateTo DATETIME = '2016-07-01 06:00'
DECLARE @IntervalDays INT = 7
-- Transition Sequence = وقت الاستراحة في المناوبات الليلية والنهارية
-- RR (Rest & Relax) = 1
-- DS (Day Shift) = 2
-- NS (Night Shift) = 3
;WITH roster AS
(
    SELECT @DateFrom AS RosterStart, 1 AS TeamA, 2 AS TeamB, 3 AS TeamC
    UNION ALL
    SELECT DATEADD(d, @IntervalDays, RosterStart),
       CASE TeamA WHEN 1 THEN 2 WHEN 2 THEN 3 WHEN 3 THEN 1 END AS TeamA,
       CASE TeamB WHEN 1 THEN 2 WHEN 2 THEN 3 WHEN 3 THEN 1 END AS TeamB,
       CASE TeamC WHEN 1 THEN 2 WHEN 2 THEN 3 WHEN 3 THEN 1 END AS TeamC
    FROM roster WHERE RosterStart < DATEADD(d, -@IntervalDays, @DateTo)
)
SELECT RosterStart,
       ISNULL(LEAD(RosterStart) OVER (ORDER BY RosterStart), RosterStart + @IntervalDays) AS RosterEnd,
       CASE TeamA WHEN 1 THEN 'RR' WHEN 2 THEN 'DS' WHEN 3 THEN 'NS' END AS TeamA,
       CASE TeamB WHEN 1 THEN 'RR' WHEN 2 THEN 'DS' WHEN 3 THEN 'NS' END AS TeamB,
       CASE TeamC WHEN 1 THEN 'RR' WHEN 2 THEN 'DS' WHEN 3 THEN 'NS' END AS TeamC
FROM roster

النتيجة المُعادة:

rm2xk.jpg

استخدام CONNECT BY في Oracle مع تعبير جدولي عودي

توفّر الوظيفة CONNECT BY المُستخدمة في Oracle العديد من الميزات المفيدة التي لا يوجد لها مثيل في التعبيرات الجدولية العودية القياسية في SQL.

يحاول هذا المثال محاكاة هذه الميزات (مع بعض الإضافات التكميلية) باستخدام صياغة SQL Server. هذه الوظائف مفيدة للغاية لمطوّري Oracle - إذ توفّر لهم العديد من الميزات في الاستعلامات المتشعبة (hierarchical queries) غير الموجودة في قواعد البيانات الأخرى، كما أنّها مفيدة أيضًا في توضيح استخدامات الاستعلامات المتشعبة عمومًا.

 WITH tbl AS (
     SELECT id, name, parent_id
         FROM mytable)
 ,     tbl_hierarchy AS (
     /* Anchor */
     SELECT 1 AS "LEVEL"
         --, 1 AS CONNECT_BY_ISROOT
         --, 0 AS CONNECT_BY_ISBRANCH
         , CASE WHEN t.id IN (SELECT parent_id FROM tbl) THEN 0 ELSE 1 END AS CONNECT_BY_ISLEAF
         , 0 AS CONNECT_BY_ISCYCLE
         , '/' + CAST(t.id AS VARCHAR(MAX)) + '/' AS SYS_CONNECT_BY_PATH_id
         , '/' + CAST(t.name AS VARCHAR(MAX)) + '/' AS SYS_CONNECT_BY_PATH_name
         , t.id AS root_id
         , t.*
     FROM tbl t
   WHERE t.parent_id IS NULL -- START WITH parent_id IS NULL
 UNION ALL
 /* العودية*/
 SELECT th."LEVEL" + 1 AS "LEVEL"
         --, 0 AS CONNECT_BY_ISROOT
         --, CASE WHEN t.id IN (SELECT parent_id FROM tbl) THEN 1 ELSE 0 END AS CONNECT_BY_ISBRANCH
         , CASE WHEN t.id IN (SELECT parent_id FROM tbl) THEN 0 ELSE 1 END AS CONNECT_BY_ISLEAF
         , CASE WHEN th.SYS_CONNECT_BY_PATH_id LIKE '%/' + CAST(t.id AS VARCHAR(MAX)) + '/%'
        THEN 1 ELSE 0 END AS CONNECT_BY_ISCYCLE
         , th.SYS_CONNECT_BY_PATH_id + CAST(t.id AS VARCHAR(MAX)) + '/' AS SYS_CONNECT_BY_PATH_id
         , th.SYS_CONNECT_BY_PATH_name + CAST(t.name AS VARCHAR(MAX)) + '/' AS     SYS_CONNECT_BY_PATH_name
         , th.root_id
         , t.*
     FROM tbl t
         JOIN tbl_hierarchy th ON (th.id = t.parent_id) -- CONNECT BY PRIOR id = parent_id
     WHERE th.CONNECT_BY_ISCYCLE = 0) -- NOCYCLE
SELECT th.*
 --, REPLICATE(' ', (th."LEVEL" - 1) * 3) + th.name AS tbl_hierarchy
     FROM tbl_hierarchy th
     JOIN tbl CONNECT_BY_ROOT ON (CONNECT_BY_ROOT.id = th.root_id)
  ORDER BY th.SYS_CONNECT_BY_PATH_name; -- ORDER SIBLINGS BY name

هذا شرح لميزات CONNECT BY الموضّحة أعلاه:

  • العبارات
    • CONNECT BY: تحدّد العلاقة التي تعرّف التشعّب
    • START WITH: تحدّد العقدة الجذرية (root nodes).
    • ORDER SIBLINGS BY: تحدّد ترتيب النتائج
  • المعاملات
    • NOCYCLE: توقِف معالجة فرع معيّن عند رصد شعبة دورية (loop). لأنّ الشعب الصالحة هي الشعب غير الدورية (Directed Acyclic)، أي الشعب التي لا يمكن العودة عبرها إلى العقدة نفسها.
  • العمليات
    • PRIOR: تحصل على البيانات من العقدة الأب (node's parent).
    • CONNECT_BY_ROOT: تحصل على البيانات من العقدة الجذرية.
  • أشباه الأعمدة Pseudocolumns
    • LEVEL: تشير إلى مسافة العقدة من جذرها.
    • CONNECT_BY_ISLEAF: تشير إلى عقدة بدون فروعها.
    • CONNECT_BY_ISCYCLE: تشير إلى عقدة ذات مرجع دائري (circular reference).
  • الدوال
    • SYS_CONNECT_BY_PATH: تعيد سلسلة نصية تمثّل المسار من الجذر إلى العقدة.

ترجمة -وبتصرّف- للفصل 46 من الكتاب SQL Notes for Professionals





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن