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

عمليات الربط والاستعلامات الفرعية

تتناول هذه الفئة بالدرجة الأولى مفهومًا أساسيًا في نظم قواعد البيانات العلائقية: الربط (joining). فالربط يتيح لك دمج معلومات مترابطة من جداول متعددة للإجابة عن سؤال. وهذا ليس مفيدًا لسهولة الاستعلام (query) فحسب: فغياب قدرة الربط يشجّع على تفكيك تسوية البيانات (denormalisation)، وهو ما يزيد تعقيد الحفاظ على اتساق بياناتك داخليًا. ويغطي هذا الموضوع الربط الداخلي والخارجي والذاتي، مع قضاء بعض الوقت في الاستعلامات الفرعية (subqueries) — أي الاستعلامات داخل الاستعلامات. وإن واجهت صعوبة في هذه الأسئلة، فأنصح بشدة بكتاب Learning SQL لـ Alan Beaulieu، فهو كتاب موجز وجيد الكتابة في الموضوع.

1. استرجاع أوقات بدء حجوزات الأعضاء

السؤال

كيف يمكنك إنتاج قائمة بأوقات البدء لحجوزات الأعضاء المسمّين 'David Farrell'؟

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

starttime
2012-09-18 09:00:00
2012-09-18 17:30:00
2012-09-18 13:30:00
2012-09-18 20:00:00
2012-09-19 09:30:00
2012-09-19 15:00:00
2012-09-19 12:00:00
2012-09-20 15:30:00
2012-09-20 11:30:00
2012-09-20 14:00:00
2012-09-21 10:30:00
2012-09-21 14:00:00
2012-09-22 08:30:00
2012-09-22 17:00:00
2012-09-23 08:30:00
2012-09-23 17:30:00
2012-09-23 19:00:00
2012-09-24 08:00:00
2012-09-24 16:30:00
2012-09-24 12:30:00
2012-09-25 15:30:00
2012-09-25 17:00:00
2012-09-26 13:00:00
2012-09-26 17:00:00
2012-09-27 08:00:00
2012-09-28 11:30:00
2012-09-28 09:30:00
2012-09-28 13:00:00
2012-09-29 16:00:00
2012-09-29 10:30:00
2012-09-29 13:30:00
2012-09-29 14:30:00
2012-09-29 17:30:00
2012-09-30 14:30:00

الإجابة

select bks.starttime 
	from 
		cd.bookings bks
		inner join cd.members mems
			on mems.memid = bks.memid
	where 
		mems.firstname='David' 
		and mems.surname='Farrell';

أكثر أنواع الربط استخدامًا هو INNER JOIN. وهو يدمج جدولين بناءً على تعبير ربط — في هذه الحالة، لكل معرّف عضو في جدول members، نبحث عن قيم مطابقة في جدول bookings. وحيث نجد تطابقًا، يُعاد صف يجمع قيم الجدولين. ولاحظ أننا أعطينا كل جدول اسمًا مستعارًا (bks وmems). ويُستخدم ذلك لسببين: أولًا لأنه ملائم، وثانيًا لأننا قد نربط الجدول نفسه عدة مرات، فيلزمنا التمييز بين الأعمدة الواردة من كل مرة رُبط فيها الجدول.

ولنتجاهل الآن عبارتي select وwhere، ونركّز على ما ينتجه بيان FROM. في كل أمثلتنا السابقة، كان FROM مجرد جدول بسيط. فما هو الآن؟ جدول آخر! وهذه المرة، ينتج كتركيب من bookings وmembers. ويمكنك أن ترى مجموعة فرعية من ناتج الربط أدناه:

ناتج عبارة FROM لعملية ربط

لكل عضو في جدول members، وجد الربط كل معرّفات الأعضاء المطابقة في جدول bookings. ولكل تطابق، أنتج صفًا يجمع الصف من جدول members والصف من جدول bookings.

من الواضح أن هذه معلومات أكثر ممّا يلزم في حد ذاتها، وأي سؤال مفيد سيريد تصفيتها. وفي استعلامنا، نستخدم بداية عبارة SELECT لاختيار الأعمدة، وعبارة WHERE لاختيار الصفوف، كما هو موضح أدناه:

هذا كل ما نحتاج إليه لإيجاد حجوزات David! وبوجه عام، أشجعك على تذكّر أن ناتج عبارة FROM هو في جوهره جدول كبير واحد تُصفّي منه المعلومات بعد ذلك. وقد يبدو هذا غير فعّال — لكن لا تقلق، فقاعدة البيانات (DB) ستتصرف في الخفاء بذكاء أكبر بكثير :-).

وملاحظة أخيرة: هناك صيغتان مختلفتان للربط الداخلي. وقد عرضت عليك الصيغة التي أفضّلها، والتي أجدها أكثر اتساقًا مع أنواع الربط الأخرى. وسترى كثيرًا صيغة مختلفة، معروضة أدناه:

select bks.starttime
        from
                cd.bookings bks,
                cd.members mems
        where
                mems.firstname='David'
                and mems.surname='Farrell'
                and mems.memid = bks.memid;

هذه مكافئة وظيفيًا تمامًا للإجابة المعتمدة. وإن كنت أكثر ارتياحًا لهذه الصيغة، فلا تتردد في استخدامها!

تلميح: ألقِ نظرة على الوثائق الخاصة بـ INNER JOIN.

2. تحديد أوقات بدء حجوزات ملاعب التنس

السؤال

كيف يمكنك إنتاج قائمة بأوقات البدء لحجوزات ملاعب التنس، في التاريخ '2012-09-21'؟ أرجع قائمة بأزواج من وقت البدء واسم المرفق، مرتّبة بحسب الوقت.

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

start name
2012-09-21 08:00:00 Tennis Court 1
2012-09-21 08:00:00 Tennis Court 2
2012-09-21 09:30:00 Tennis Court 1
2012-09-21 10:00:00 Tennis Court 2
2012-09-21 11:30:00 Tennis Court 2
2012-09-21 12:00:00 Tennis Court 1
2012-09-21 13:30:00 Tennis Court 1
2012-09-21 14:00:00 Tennis Court 2
2012-09-21 15:30:00 Tennis Court 1
2012-09-21 16:00:00 Tennis Court 2
2012-09-21 17:00:00 Tennis Court 1
2012-09-21 18:00:00 Tennis Court 2

الإجابة

select bks.starttime as start, facs.name as name
	from 
		cd.facilities facs
		inner join cd.bookings bks
			on facs.facid = bks.facid
	where 
		facs.name in ('Tennis Court 2','Tennis Court 1') and
		bks.starttime >= '2012-09-21' and
		bks.starttime < '2012-09-22'
order by bks.starttime;

هذا استعلام آخر بـ INNER JOIN، وإن كان فيه قدر أكبر بكثير من التعقيد! فجزء FROM من الاستعلام سهل — فنحن ببساطة نربط جدولَي facilities وbookings على facid. وينتج عن ذلك جدول أرفقنا فيه، لكل صف في bookings، معلومات مفصّلة عن المرفق المحجوز.

وننتقل إلى مكوّن WHERE في الاستعلام. والفحوص على starttime واضحة إلى حد كبير بذاتها — فنحن نتأكد من أن كل الحجوزات تبدأ بين التاريخين المحددين. ولأننا لا يهمّنا إلا ملاعب التنس، نستخدم أيضًا مُعامل IN لنخبر نظام قاعدة البيانات بأن يعيد لنا معرّفات المرافق 0 أو 1 فقط — وهما معرّفا الملعبين. وهناك طرق أخرى للتعبير عن ذلك: كان يمكننا استخدام where facs.facid = 0 or facs.facid = 1، أو حتى where facs.name like 'Tennis%'.

والباقي بسيط إلى حد كبير: نستخدم SELECT لاختيار الأعمدة التي تهمنا، وORDER BY لترتيب النتائج بحسب وقت البدء.

تلميح: هذا ربط داخلي (INNER JOIN) آخر. وقد تريد أيضًا التفكير في استخدام مُعاملَي IN أو LIKE لتحديد النتائج التي تحصل عليها.

3. إنتاج قائمة بكل الأعضاء الذين أوصوا بعضو آخر

السؤال

كيف يمكنك إخراج قائمة بكل الأعضاء الذين أوصوا بعضو آخر؟ تأكد من عدم وجود تكرارات في القائمة، ومن أن النتائج مرتّبة بحسب (surname، firstname).

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

firstname surname
Florence Bader
Timothy Baker
Gerald Butters
Jemima Farrell
Matthew Genting
David Jones
Janice Joplette
Millicent Purview
Tim Rownam
Darren Smith
Tracy Smith
Ponder Stibbons
Burton Tracy

الإجابة

select distinct recs.firstname as firstname, recs.surname as surname
	from 
		cd.members mems
		inner join cd.members recs
			on recs.memid = mems.recommendedby
order by surname, firstname;

وهنا مفهوم يجد بعض الناس فيه لبسًا: يمكنك ربط جدول بنفسه! وهذا مفيد حقًا إذا كانت لديك أعمدة تشير إلى بيانات في الجدول نفسه، كما هي الحال مع recommendedby في cd.members.

وإن واجهت صعوبة في تصوّر ذلك، فتذكّر أن هذا يعمل تمامًا كأي ربط داخلي آخر. فيأخذ ربطنا كل صف في members له قيمة recommendedby، ويبحث في members مرة أخرى عن الصف الذي له معرّف عضو مطابق. ثم يولّد صف إخراج يجمع مُدخلَي العضوين. وهذا يشبه المخطط أدناه:

لاحظ أنه قد يكون لدينا عمودا 'surname' في مجموعة الإخراج، لكن يمكن تمييزهما بالأسماء المستعارة للجدولين. وبعد أن نختار الأعمدة التي نريدها، نستخدم ببساطة DISTINCT لضمان عدم وجود تكرارات.

تلميح: هذا ربط داخلي (INNER JOIN)، تمامًا كما في التمارين السابقة.

4. إنتاج قائمة بكل الأعضاء مع من أوصى بهم

السؤال

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

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

memfname memsname recfname recsname
Florence Bader Ponder Stibbons
Anne Baker Ponder Stibbons
Timothy Baker Jemima Farrell
Tim Boothe Tim Rownam
Gerald Butters Darren Smith
Joan Coplin Timothy Baker
Erica Crumpet Tracy Smith
Nancy Dare Janice Joplette
David Farrell
Jemima Farrell
GUEST GUEST
Matthew Genting Gerald Butters
John Hunt Millicent Purview
David Jones Janice Joplette
Douglas Jones David Jones
Janice Joplette Darren Smith
Anna Mackenzie Darren Smith
Charles Owen Darren Smith
David Pinker Jemima Farrell
Millicent Purview Tracy Smith
Tim Rownam
Henrietta Rumney Matthew Genting
Ramnaresh Sarwin Florence Bader
Darren Smith
Darren Smith
Jack Smith Darren Smith
Tracy Smith
Ponder Stibbons Burton Tracy
Burton Tracy
Hyacinth Tupperware
Henry Worthington-Smyth Tracy Smith

الإجابة

select mems.firstname as memfname, mems.surname as memsname, recs.firstname as recfname, recs.surname as recsname
	from 
		cd.members mems
		left outer join cd.members recs
			on recs.memid = mems.recommendedby
order by memsname, memfname;

ولنُقدّم مفهومًا جديدًا آخر: LEFT OUTER JOIN. وأفضل تفسير له هو بيان كيف يختلف عن عمليات الربط الداخلي. فالربط الداخلي يأخذ جدولًا أيسر وجدولًا أيمن، ويبحث عن صفوف متطابقة بناءً على شرط ربط (ON). وعندما يتحقق الشرط، يُنتج صف مربوط. ويعمل LEFT OUTER JOIN على نحو مشابه، إلا أنه إذا لم يطابق صف معيّن في الجدول الأيسر أي شيء، فإنه يُنتج صف إخراج رغم ذلك. ويتكوّن صف الإخراج هذا من صف الجدول الأيسر، ومجموعة من قيم NULL مكان صف الجدول الأيمن.

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

وكما قد خمّنت، هناك عمليات ربط خارجي أخرى أيضًا. فـ RIGHT OUTER JOIN يشبه كثيرًا LEFT OUTER JOIN، إلا أن الجانب الأيسر من التعبير هو الذي يحتوي البيانات الاختيارية. أما FULL OUTER JOIN، قليل الاستخدام، فيتعامل مع جانبي التعبير كليهما كبيانات اختيارية.

تلميح: جرّب البحث في LEFT OUTER JOIN.

5. إنتاج قائمة بكل الأعضاء الذين استخدموا ملعب تنس

السؤال

كيف يمكنك إنتاج قائمة بكل الأعضاء الذين استخدموا ملعب تنس؟ أدرج في مخرجاتك اسم الملعب، واسم العضو منسّقًا في عمود واحد. تأكد من عدم تكرار البيانات، ورتّب النتائج بحسب اسم العضو ثم اسم المرفق.

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

member facility
Anne Baker Tennis Court 1
Anne Baker Tennis Court 2
Burton Tracy Tennis Court 1
Burton Tracy Tennis Court 2
Charles Owen Tennis Court 1
Charles Owen Tennis Court 2
Darren Smith Tennis Court 2
David Farrell Tennis Court 1
David Farrell Tennis Court 2
David Jones Tennis Court 1
David Jones Tennis Court 2
David Pinker Tennis Court 1
Douglas Jones Tennis Court 1
Erica Crumpet Tennis Court 1
Florence Bader Tennis Court 1
Florence Bader Tennis Court 2
GUEST GUEST Tennis Court 1
GUEST GUEST Tennis Court 2
Gerald Butters Tennis Court 1
Gerald Butters Tennis Court 2
Henrietta Rumney Tennis Court 2
Jack Smith Tennis Court 1
Jack Smith Tennis Court 2
Janice Joplette Tennis Court 1
Janice Joplette Tennis Court 2
Jemima Farrell Tennis Court 1
Jemima Farrell Tennis Court 2
Joan Coplin Tennis Court 1
John Hunt Tennis Court 1
John Hunt Tennis Court 2
Matthew Genting Tennis Court 1
Millicent Purview Tennis Court 2
Nancy Dare Tennis Court 1
Nancy Dare Tennis Court 2
Ponder Stibbons Tennis Court 1
Ponder Stibbons Tennis Court 2
Ramnaresh Sarwin Tennis Court 1
Ramnaresh Sarwin Tennis Court 2
Tim Boothe Tennis Court 1
Tim Boothe Tennis Court 2
Tim Rownam Tennis Court 1
Tim Rownam Tennis Court 2
Timothy Baker Tennis Court 1
Timothy Baker Tennis Court 2
Tracy Smith Tennis Court 1
Tracy Smith Tennis Court 2

الإجابة

select distinct mems.firstname || ' ' || mems.surname as member, facs.name as facility
	from 
		cd.members mems
		inner join cd.bookings bks
			on mems.memid = bks.memid
		inner join cd.facilities facs
			on bks.facid = facs.facid
	where
		facs.name in ('Tennis Court 2','Tennis Court 1')
order by member, facility

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

عملية الربط الداخلي الثانية في هذا الاستعلام جانبها الأيمن هو cd.facilities. وهذا سهل الفهم. أما جانبه الأيسر فهو الجدول الذي يعيده ربط cd.members بـ cd.bookings. ومن المهم التأكيد على هذا: النموذج العلائقي كله يقوم على الجداول. فناتج أي عملية ربط هو جدول آخر. وناتج أي استعلام هو جدول. والقوائم ذات العمود الواحد جداول. ومتى استوعبت ذلك، فقد استوعبت الجمال الجوهري للنموذج.

وملاحظة أخيرة: نُقدّم هنا شيئًا جديدًا واحدًا فعلًا: يُستخدم المُعامل || لدمج النصوص (concatenate).

تلميح: تتطلب هذه الإجابة عمليات ربط متعددة. ولدمج النصوص يمكنك استخدام المُعامل ||.

6. إنتاج قائمة بالحجوزات المكلفة

السؤال

كيف يمكنك إنتاج قائمة بالحجوزات في يوم 2012-09-14 التي ستكلف العضو (أو الضيف) أكثر من 30 دولارًا؟ تذكّر أن تكاليف الضيوف تختلف عن تكاليف الأعضاء (والتكاليف المذكورة لكل "شريحة" مدتها نصف ساعة)، وأن مستخدم الضيوف معرّفه دائمًا 0. أدرج في مخرجاتك اسم المرفق، واسم العضو منسّقًا في عمود واحد، والتكلفة. ورتّب النتائج تنازليًا بحسب التكلفة، ولا تستخدم أي استعلامات فرعية.

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

member facility cost
GUEST GUEST Massage Room 2 320
GUEST GUEST Massage Room 1 160
GUEST GUEST Massage Room 1 160
GUEST GUEST Massage Room 1 160
GUEST GUEST Tennis Court 2 150
Jemima Farrell Massage Room 1 140
GUEST GUEST Tennis Court 1 75
GUEST GUEST Tennis Court 2 75
GUEST GUEST Tennis Court 1 75
Matthew Genting Massage Room 1 70
Florence Bader Massage Room 2 70
GUEST GUEST Squash Court 70.0
Jemima Farrell Massage Room 1 70
Ponder Stibbons Massage Room 1 70
Burton Tracy Massage Room 1 70
Jack Smith Massage Room 1 70
GUEST GUEST Squash Court 35.0
GUEST GUEST Squash Court 35.0

الإجابة

select mems.firstname || ' ' || mems.surname as member, 
	facs.name as facility, 
	case 
		when mems.memid = 0 then
			bks.slots*facs.guestcost
		else
			bks.slots*facs.membercost
	end as cost
        from
                cd.members mems                
                inner join cd.bookings bks
                        on mems.memid = bks.memid
                inner join cd.facilities facs
                        on bks.facid = facs.facid
        where
		bks.starttime >= '2012-09-14' and 
		bks.starttime  30) or
			(mems.memid != 0 and bks.slots*facs.membercost > 30)
		)
order by cost desc;

هذا تمرين معقّد بعض الشيء! ورغم أن منطقه أكثر تعقيدًا ممّا استخدمناه سابقًا، فلا يوجد الكثير جدًا مما يستحق التعليق. فعبارة WHERE تحصر مخرجاتنا في الصفوف المكلفة بدرجة كافية في 2012-09-14، مع تذكّر التمييز بين الضيوف وغيرهم. ثم نستخدم عبارة CASE في اختيارات الأعمدة لإخراج التكلفة الصحيحة للعضو أو الضيف.

تلميح: كما في السابق، تتطلب هذه الإجابة عمليات ربط متعددة. ومنطق WHERE فيها أكثر تعقيدًا مما اعتدت عليه، وسيحتاج إلى عبارة CASE في اختيارات الأعمدة!

7. إنتاج قائمة بكل الأعضاء مع من أوصى بهم، دون استخدام أي عمليات ربط

السؤال

كيف يمكنك إخراج قائمة بكل الأعضاء، مع الشخص الذي أوصى بهم (إن وُجد)، دون استخدام أي عمليات ربط؟ تأكد من عدم وجود تكرارات في القائمة، ومن أن كل زوج من firstname + surname منسّق في عمود ومرتّب.

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

member recommender
Anna Mackenzie Darren Smith
Anne Baker Ponder Stibbons
Burton Tracy
Charles Owen Darren Smith
Darren Smith
David Farrell
David Jones Janice Joplette
David Pinker Jemima Farrell
Douglas Jones David Jones
Erica Crumpet Tracy Smith
Florence Bader Ponder Stibbons
GUEST GUEST
Gerald Butters Darren Smith
Henrietta Rumney Matthew Genting
Henry Worthington-Smyth Tracy Smith
Hyacinth Tupperware
Jack Smith Darren Smith
Janice Joplette Darren Smith
Jemima Farrell
Joan Coplin Timothy Baker
John Hunt Millicent Purview
Matthew Genting Gerald Butters
Millicent Purview Tracy Smith
Nancy Dare Janice Joplette
Ponder Stibbons Burton Tracy
Ramnaresh Sarwin Florence Bader
Tim Boothe Tim Rownam
Tim Rownam
Timothy Baker Jemima Farrell
Tracy Smith

الإجابة

select distinct mems.firstname || ' ' ||  mems.surname as member,
	(select recs.firstname || ' ' || recs.surname as recommender 
		from cd.members recs 
		where recs.memid = mems.recommendedby
	)
	from 
		cd.members mems
order by member;

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

وفي هذه الحالة، نستخدم الاستعلام الفرعي ببساطة لمحاكاة ربط خارجي. فلكل قيمة من قيم member، يُشغَّل الاستعلام الفرعي مرة واحدة للعثور على اسم الشخص الذي أوصى به (إن وُجد). ويُعرف الاستعلام الفرعي الذي يستخدم معلومات من الاستعلام الخارجي بهذه الطريقة (ومن ثم يلزم تشغيله لكل صف في مجموعة النتائج) بأنه استعلام فرعي مترابط (correlated subquery).

تلميح: تتطلب الإجابة الصحيحة عن هذا السؤال استخدام استعلام فرعي

8. إنتاج قائمة بالحجوزات المكلفة باستخدام استعلام فرعي

السؤال

احتوى تمرين إنتاج قائمة بالحجوزات المكلفة على منطق غير مرتب: إذ اضطررنا إلى حساب تكلفة الحجز في كل من عبارة WHERE وعبارة CASE. حاول تبسيط هذا الحساب باستخدام الاستعلامات الفرعية. وللعلم، كان السؤال: كيف يمكنك إنتاج قائمة بالحجوزات في يوم 2012-09-14 التي ستكلف العضو (أو الضيف) أكثر من 30 دولارًا؟ تذكّر أن تكاليف الضيوف تختلف عن تكاليف الأعضاء (والتكاليف المذكورة لكل "شريحة" مدتها نصف ساعة)، وأن مستخدم الضيوف معرّفه دائمًا 0. أدرج في مخرجاتك اسم المرفق، واسم العضو منسّقًا في عمود واحد، والتكلفة. ورتّب النتائج تنازليًا بحسب التكلفة.

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

member facility cost
GUEST GUEST Massage Room 2 320
GUEST GUEST Massage Room 1 160
GUEST GUEST Massage Room 1 160
GUEST GUEST Massage Room 1 160
GUEST GUEST Tennis Court 2 150
Jemima Farrell Massage Room 1 140
GUEST GUEST Tennis Court 1 75
GUEST GUEST Tennis Court 2 75
GUEST GUEST Tennis Court 1 75
Matthew Genting Massage Room 1 70
Florence Bader Massage Room 2 70
GUEST GUEST Squash Court 70.0
Jemima Farrell Massage Room 1 70
Ponder Stibbons Massage Room 1 70
Burton Tracy Massage Room 1 70
Jack Smith Massage Room 1 70
GUEST GUEST Squash Court 35.0
GUEST GUEST Squash Court 35.0

الإجابة

select member, facility, cost from (
	select 
		mems.firstname || ' ' || mems.surname as member,
		facs.name as facility,
		case
			when mems.memid = 0 then
				bks.slots*facs.guestcost
			else
				bks.slots*facs.membercost
		end as cost
		from
			cd.members mems
			inner join cd.bookings bks
				on mems.memid = bks.memid
			inner join cd.facilities facs
				on bks.facid = facs.facid
		where
			bks.starttime >= '2012-09-14' and
			bks.starttime  30
order by cost desc;

تقدّم هذه الإجابة تبسيطًا طفيفًا للنسخة السابقة: فقد كان علينا في نسخة دون استعلام فرعي حساب تكلفة العضو أو الضيف في كل من عبارة WHERE وعبارة CASE. وفي نسختنا الجديدة، ننتج استعلامًا مضمّنًا يحسب لنا التكلفة الإجمالية للحجز، ما يتيح للاستعلام الخارجي أن يختار ببساطة الحجوزات التي يبحث عنها. وللعلم، قد ترى أيضًا الاستعلامات الفرعية في عبارة FROM تُسمّى عروضًا مضمّنة (inline views).

تلميح: ستكون إجابتك مشابهة للتمرين المشار إليه. استخدم استعلامًا فرعيًا في عبارة FROM لتوليد مجموعة نتائج تحسب التكلفة الإجمالية لكل حجز. ثم يمكن للاستعلام الخارجي أن يختار الحجوزات التي تهمه.