تمارين PostgreSQL التفاعلية › الاستعلامات التعاودية 5 / 7 السابق التالي

الاستعلامات التعاودية

تتيح لنا التعبيرات الجدولية الشائعة (Common Table Expressions، أو CTEs) أن ننشئ فعليًا جداول مؤقتة خاصة بنا طوال مدة الاستعلام (query) — وهي إلى حد كبير وسيلة للراحة تساعدنا على كتابة SQL أوضح قراءةً. لكن باستخدام المعدّل WITH RECURSIVE يصبح في إمكاننا إنشاء استعلامات تعاودية (recursive queries). وهذا مفيد جدًا للعمل مع البيانات ذات البنية الشجرية والبيانات الرسومية (graph) — تخيّل، على سبيل المثال، استرجاع كل علاقات عقدة في رسم بياني حتى عمق معيّن.

تعرض لك هذه الفئة بعض الاستعلامات التعاودية الأساسية الممكنة باستخدام مجموعة بياناتنا.

1. إيجاد سلسلة التوصيات الصاعدة للعضو ذي المعرّف 27

السؤال

أوجد سلسلة التوصيات الصاعدة للعضو ذي المعرّف 27: أي العضو الذي أوصى به، ثم العضو الذي أوصى بذلك العضو، وهكذا. أرجع معرّف العضو والاسم الأول والاسم العائلي. ورتّب تنازليًا بحسب معرّف العضو.

النتائج المتوقعة

recommender firstname surname
20 Matthew Genting
5 Gerald Butters
1 Darren Smith

الإجابة

with recursive recommenders(recommender) as (
	select recommendedby from cd.members where memid = 27
	union all
	select mems.recommendedby
		from recommenders recs
		inner join cd.members mems
			on mems.memid = recs.recommender
)
select recs.recommender, mems.firstname, mems.surname
	from recommenders recs
	inner join cd.members mems
		on recs.recommender = mems.memid
order by memid desc

تمثّل WITH RECURSIVE وظيفة مفيدة بشكل مذهل ولا يعرفها كثير من المطوّرين. فهي تتيح لك تنفيذ استعلامات على تسلسلات هرمية من البيانات، وهو أمر يصعب جدًا بوسائل أخرى في SQL. وغالبًا ما تدفع هذه الحالات المطوّرين إلى اللجوء إلى رحلات ذهاب وإياب متعددة إلى نظام قاعدة البيانات.

لقد رأيت WITH من قبل. فالتعبيرات الجدولية الشائعة (CTEs) التي يعرّفها WITH تمنحك القدرة على إنتاج عروض مضمّنة على بياناتك. وهذا في العادة مجرد تسهيل نحوي، لكن المعدّل RECURSIVE يضيف القدرة على الربط مقابل نتائج أُنتجت بالفعل لإنتاج المزيد. ويتخذ WITH التعاودي الصورة الأساسية التالية:

WITH RECURSIVE NAME(columns) as (
	<initial statement>
	UNION ALL 
	<recursive statement>
)

يملأ البيان الأولي البيانات الأولية، ثم يُشغَّل البيان التعاودي مرارًا لإنتاج المزيد. ويستطيع كل خطوة من خطوات التعاود الوصول إلى تعبير CTE، لكنه لا يرى داخله إلا البيانات التي أنتجتها التكرارية السابقة. ويتكرر ذلك حتى لا تُنتج أي تكرارية بيانات إضافية. وقد يبدو أبسط مثال على WITH تعاودي على شيء مثل هذا:

with recursive increment(num) as (
	select 1
	union all
	select increment.num + 1 from increment where increment.num < 5
)
select * from increment;

يُنتج البيان الأولي '1'. وترى التكرارية الأولى للبيان التعاودي هذا محتوىً لـ increment، فتُنتج '2'. وترى التكرارية التالية محتوى increment على أنه '2'، وهكذا. وينتهي التنفيذ عندما لا يُنتج البيان التعاودي أي بيانات إضافية.

وبعد الانتهاء من الأساسيات، يسهّل إلى حد كبير شرح إجابتنا هنا. فيأتي البيان الأولي بمعرّف الشخص الذي أوصى بالعضو الذي يهمنا. ويأخذ البيان التعاودي نتائج البيان الأولي، ويجد معرّف الشخص الذي أوصى به. ثم تُمرَّر هذه القيمة إلى التكرارية التالية، وهكذا.

وبعد أن أنشأنا تعبير CTE المسمى recommenders، كل ما على بيان SELECT الرئيسي فعله هو جلب معرّفات الأعضاء من recommenders، وربطها بجدول members لمعرفة أسمائهم.

تلميح: اقرأ عن WITH RECURSIVE.

2. إيجاد سلسلة التوصيات النازلة للعضو ذي المعرّف 1

السؤال

أوجد سلسلة التوصيات النازلة للعضو ذي المعرّف 1: أي الأعضاء الذين أوصى بهم، ثم الأعضاء الذين أوصى بهم هؤلاء، وهكذا. أرجع معرّف العضو واسمه، ورتّب تصاعديًا بحسب معرّف العضو.

النتائج المتوقعة

memid firstname surname
4 Janice Joplette
5 Gerald Butters
7 Nancy Dare
10 Charles Owen
11 David Jones
14 Jack Smith
20 Matthew Genting
21 Anna Mackenzie
26 Douglas Jones
27 Henrietta Rumney

الإجابة

with recursive recommendeds(memid) as (
	select memid from cd.members where recommendedby = 1
	union all
	select mems.memid
		from recommendeds recs
		inner join cd.members mems
			on mems.recommendedby = recs.memid
)
select recs.memid, mems.firstname, mems.surname
	from recommendeds recs
	inner join cd.members mems
		on recs.memid = mems.memid
order by memid

هذا تنويع طفيف جدًا على السؤال السابق. والفرق الجوهري أننا نتجه الآن في الاتجاه المعاكس. ومن النقاط المثيرة للانتباه أن هذا التعبير CTE، بخلاف المثال السابق، ينتج صفوفًا متعددة في كل تكرارية، بحكم أننا ننزل في شجرة التوصيات (باتباع كل الفروع) بدلًا من الصعود فيها.

تلميح: اقرأ عن WITH RECURSIVE.

3. إنتاج تعبير CTE يمكنه إرجاع سلسلة التوصيات الصاعدة لأي عضو

السؤال

أنتج تعبير CTE يمكنه إرجاع سلسلة التوصيات الصاعدة لأي عضو. وينبغي أن تستطيع تنفيذ select recommender from recommenders where member=x. وأثبت ذلك بجلب السلسلتين للعضوين 12 و22. وينبغي أن يحتوي جدول النتائج على member وrecommender، مرتّبين تصاعديًا بحسب member وتنازليًا بحسب recommender.

النتائج المتوقعة

member recommender firstname surname
12 9 Ponder Stibbons
12 6 Burton Tracy
22 16 Timothy Baker
22 13 Jemima Farrell

الإجابة

with recursive recommenders(recommender, member) as (
	select recommendedby, memid
		from cd.members
	union all
	select mems.recommendedby, recs.member
		from recommenders recs
		inner join cd.members mems
			on mems.memid = recs.recommender
)
select recs.member member, recs.recommender, mems.firstname, mems.surname
	from recommenders recs
	inner join cd.members mems		
		on recs.recommender = mems.memid
	where recs.member = 22 or recs.member = 12
order by recs.member asc, recs.recommender desc

يتطلب هذا السؤال منا إنتاج تعبير CTE يمكنه حساب سلسلة التوصيات الصاعدة لأي مستخدم. ومعظم تعقيد الوصول إلى الجواب يكمن في إدراك أننا نحتاج الآن إلى أن ينتج تعبيرنا CTE عمودين: عمودًا يحتوي العضو الذي نسأل عنه، وآخر يحتوي الأعضاء في شجرة توصياته. وما نفعله في جوهره إنتاج جدول يبسّط التسلسل الهرمي للتوصيات ويسطّحه.

ولأننا نريد إنتاج السلسلة لكل مستخدم، يحتاج بياننا الأولي إلى اختيار بيانات كل مستخدم: معرّفه ومن أوصى به. وبعد ذلك، نريد تمرير حقل member عبر كل تكرارية دون تغييره، مع جلب المُوصي التالي. ويمكنك أن ترى أن الجزء التعاودي من بياننا لم يتغير فعليًا، إلا بتمرير حقل 'member'.

تلميح: ينبغي أن يُعيد بيانك الأولي كل حقول recommendedby وmemid في جدول members.