الكيانات الافتراضية (Virtual Entities) — وحدات SQL قابلة لإعادة الاستخدام في التقارير ولوحات المعلومات
ربما مررت بهذا الموقف من قبل: نفس استعلام SQL المعقد يظهر في خمسة سجلات مختلفة من معالج التقارير وثلاثة widgets في لوحة المعلومات. وفي كل مرة تعدّله، عليك أن تتذكر كل مكان حُفظ فيه. علاوة على ذلك، يعرض منتقي الجدول في المعالج الكيانات الحقيقية فحسب، مثل SalesInvoice أو CostInTransLine، وبالتالي فإن أي استعلام يحتاج إلى UNION أو join مكتوب يدويًا أو قلب للإشارة على مستوى الصف لا بد من إعادة كتابته في كل محرر SQL لكل widget.
الكيانات الافتراضية (Virtual Entities) تحل هذه المشكلة. تكتب SELECT واحدة —بقدر ما تشاء من التعقيد: UNION وJOIN وتعبيرات وتجميعات— وتمنحها كودًا واسمًا، فيحولها النظام إلى كيان من الدرجة الأولى. ومن تلك اللحظة، يظهر هذا الكيان في منتقي الجدول الرئيسي في معالج التقارير وفي معالج Widget لوحة المعلومات تمامًا كجدول حقيقي — نفس منتقي الحقول، ونفس الربط التلقائي على الحقول المرجعية، ونفس الترجمات، ونفس لوحة المدخلات.
الفكرة الأساسية في مثال واحد
لنفترض أنك تريد تحليل حركات المخزون الصادرة من نوعين مختلفين من المعاملات: تكلفة بضاعة في الطريق واردة (CostInTransLine، كميات موجبة) وتكلفة بضاعة في الطريق صادرة (CostOutTransLine، كميات سالبة). لا يوجد جدول فيزيائي واحد يعطيك كليهما باتفاقية إشارة موحدة. لذلك تكتب:
select l.totalCost, l.totalQty, l.item_id, l.legalEntity_id, l.valueDate
from CostInTransLine l
union all
select l.totalCost * -1, l.totalQty * -1, l.item_id, l.legalEntity_id, l.valueDate
from CostOutTransLine lتحفظ هذا بوصفه كيانًا افتراضيًا يُسمى InTransitMovements. خلف الكواليس، يُنشئ النظام view في SQL Server باسم vw_InTransitMovements، ويسجل تعريف كيان جديد يشير إلى هذا الـ view، ويضيفه إلى النموذج الذي يستشيره المعالجان.
الآن في معالج التقارير، تختار "الجدول الرئيسي → InTransitMovements"، فتحصل على:
- حقل يُسمى
itemمن النوعReference → InvItem(لأنitem_idفي الجدول المصدر يرتبط بهذه الخاصية). - حقل يُسمى
legalEntityمن النوعReference → LegalEntity. - حقل يُسمى
valueDateمن النوعDate. - حقول رقمية
totalCostوtotalQty.
يمكنك إضافتها إلى تقرير، والتجميع حسب item، وحساب مجموع التكلفة والكمية، وتكون قد انتهيت. لا يوجد SQL خام في أي مكان في التقرير. كما يمكن اختيار نفس الكيان الافتراضي من معالج Widget لوحة المعلومات — نفس سلوك الربط التلقائي، ونفس الترجمات، ونفس دعم التعمق في البيانات (drill-down).
أين تجده
تتوفر شاشة Virtual Entity في قائمة وحدة Basic. افتحها كما تفتح أي ملف رئيسي: انقر جديد، أكمل ترويسة السجل، وستكون جاهزًا.
تحتوي الترويسة على عدد محدود من الحقول:
- Code — معرّف قصير، يتكون من حروف أو أرقام أو شرطة سفلية فقط، ويجب أن يبدأ بحرف. يستخدم النظام هذا الكود لتسمية الـ view الأساسي (
vw_<code>) وبوصفه اسم نوع الكيان. بمجرد الحفظ، لا يمكن تغيير الكود (إذ يستلزم إعادة التسمية حذف الـ view وإعادة إنشائه، وهو ما لا تتيحه المرحلة الأولى — راجع ما القادم لاحقًا). - Arabic Name / English Name — ما سيراه المستخدمون في منتقي الجدول بالمعالج، وفي تسميات الحقول، وعناوين الشاشات.
- SQL Query — جملة SELECT التي تُعرّف الكيان. متعددة الأسطر؛ استخدم
UNIONوJOINوالتعبيرات والتجميعات — كل ما تحتاجه. - Materialization — في الوقت الحالي،
Viewفقط مسموح به. خيارTableمحجوز لمرحلة مستقبلية لتجسيد البيانات في جدول فيزيائي قابل للتحديث.
بعد ملء هذه الحقول، انقر حفظ. ثم لربط أعمدة SELECT بخصائص الكيان، انقر زر Edit Mappings في أعلى الشاشة. يفتح هذا المحرر الذي تحدث فيه المعجزة الحقيقية.
نافذة Edit Mappings
تنقسم النافذة إلى قسمين متراكمين عموديًا.
الأعلى — محرر SQL
محرر Monaco (نفس المحرر المستخدم في VS Code) مع إبراز لغوي لـ SQL وإكمال تلقائي وارتفاع يمتد للشاشة كاملة. عدّل استعلامك بحرية؛ يُحلله النظام أثناء الكتابة لاكتشاف الأعمدة التي يرتبط بها كل خاصية.
فوق المحرر، يقوم زر Bootstrap بالعمل الجوهري: يُحلل SELECT بالكامل، ويمر بكل عمود في مجموعة النتائج، ويحدد الجدول الفيزيائي والعمود الذي أتى منه كل عمود، ويبحث عن الخاصية المطابقة في تعريف كيان ذلك الجدول، ثم يقترح قائمة كاملة من الخصائص. انقره وسيمتلئ القسم السفلي باقتراحات الربط.
TIP
Bootstrap هو نقطة البداية الموصى بها. نادرًا ما ستحتاج إلى كتابة عشرين ربطًا للخصائص يدويًا حين يستطيع المحلل اشتقاقها جميعًا من SQL الخاص بك. فكر فيه كزر "Select Fields" في معالج التقارير — نفس الفكرة، مطبقة على أعمدة الكيانات الافتراضية.
إذا فشل تحليل SQL، يعرض المحرر رسالة خطأ باللون الأحمر مباشرة فوقه. لا يزال بإمكانك النقر على Bootstrap في هذه الحالة، لكن معظم الاقتراحات ستعود بوصفها Unresolved — وستحتاج إلى تعبئة تفاصيل الخصائص يدويًا.
الأسفل — قائمة الخصائص
هذا هو الجدول الذي يُعرّف الحقول التي سيراها المعالج. كل صف يمثل خاصية واحدة بالأعمدة التالية:
| العمود | وظيفته |
|---|---|
| Column Name | اسم العمود في قائمة SELECT (أو الاسم البديل alias). هذا هو الرابط بين الخاصية وعمود الـ view الأساسي. |
| Full Name | الاسم المنطقي للخاصية — مثلًا item أو legalEntity أو valueDate. هذا ما يظهر في معرفات الحقول (InTransitMovements.item.code إلخ). |
| Field Type | Reference أو Decimal أو Text أو Date أو Integer أو Boolean إلخ. أنواع Reference تُتيح الربط التلقائي في المعالج. |
| Reference To | وثيق الصلة فقط حين يكون نوع الحقل Reference — الكيان الذي يشير إليه هذا العمود (مثلًا InvItem لعمود item). |
| Arabic Name / English Name | الترجمات التي سيراها المستخدمون في منتقي الحقول بالمعالج. |
طريقتان لإضافة الصفوف:
- انقر "Add Column". يظهر صف فارغ. عنصر تحكم Column Name في ذلك الصف هو منتقٍ على الأعمدة التي وجدها المحلل في SQL — الأعمدة غير المربوطة أولًا (محددة)، ثم المربوطة مسبقًا مع تعليق "pick to overwrite". اختيار عمود يُشغّل Bootstrap على مستوى عمود واحد ويملأ بقية الصف.
- استخدم Bootstrap. كما هو موضح أعلاه — يستبدل القائمة بالكامل باقتراح المحلل.
إذا نقرت Bootstrap وكانت صفوف موجودة بالفعل، يطلب النظام تأكيدًا أولًا. وكذلك الحال عند اختيار عمود في صف تحتوي Bootstrap-derived mapping — يسأل قبل الكتابة فوق عملك.
الإبلاغ عن الخصائص اليتيمة (Orphan Flagging)
إذا عدّلت SQL بعد ربط الخصائص، ولم يعد Column Name لإحدى الخصائص يطابق أي عمود في قائمة SELECT الجديدة، تُوسَم الصف بتحذير: "هذا العمود لم يعد موجودًا في استعلام SQL الحالي." لا يحذف النظام أي شيء تلقائيًا — أنت تقرر ما إذا كنت ستُعيد تشغيل Bootstrap، أو تختار عمودًا بديلًا، أو تحذف الصف.
تعيد عملية التحليل نفسها التشغيل بعد ~2.5 ثانية من توقفك عن الكتابة، لذا يتحدث الوسم أثناء عملك.
كيف يُحلّل Bootstrap كل عمود
فهم ما يفعله المحلل يُسهّل التنبؤ بمخرجاته كثيرًا.
لمرجع عمود بسيط مثل l.item_id من CostInTransLine l:
- يرى المحلل أن
item_idينتمي إلى الاسم البديلl، الذي هوCostInTransLine. - يبحث عن
CostInTransLineفي نموذج البيانات ويجد الخاصية التي يكون عمودها الأساسي هوitem_id— وهيitem. - ينسخ الاسم الكامل للخاصية (
item) ونوعها (Reference) والكيان المستهدف (InvItem) والتسميات العربية والإنجليزية إلى الاقتراح. - يُعيد تعيين
Column Nameعلى الاقتراح ليكون ما استخدمته في قائمة SELECT، حتى يتوافق اسم عمود الـ view.
النتيجة: صف محلول بالكامل لا يحتاج إلى أي تعديل يدوي.
لتجميع مثل SUM(l.totalCost):
يُصنّف المحلل هذا بوصفه Aggregate Numeric. يحصل الاقتراح على Decimal كنوع حقل، وبلا هدف مرجعي، ويستخدم الاسم البديل في SELECT كاسم العمود. قد ترغب في ملء تسمية أكثر وضوحًا.
لتعبير مثل l.totalCost * -1:
إذا كان التعبير يلف عمودًا أساسيًا واحدًا، يحلّ المحلل بيانات وصف الخاصية لذلك العمود — فـl.totalCost * -1 يُحلَّل بنفس طريقة l.totalCost. إذا جمع التعبير أعمدة متعددة أو استخدم جملة CASE، يرجع المحلل إلى نوع Text بلا مرجع، ويُعلّم الاقتراح بـExpression Partial. أكمل الباقي يدويًا.
لـUNION ALL (أو أي عملية مجموعة):
يمر المحلل بكل فرع ويُحلل الأعمدة موضعًا بموضع. للموضع N، يمر بالفروع بالترتيب ويأخذ أول فرع ينتج اقتراحًا محلولًا بالكامل. ولهذا السبب يعمل هذا:
select 0 as x from a
union all
select x from bالفرع الأول يحتوي على ثابت في الموضع 0 (لا عمود مصدر). الفرع الثاني لديه b.x. يختار الدمج تحليل الفرع الثاني.
ما الذي يحدث عند الحفظ
تنفيذ الحفظ يمر بثلاث خطوات:
- التحقق — شكل الكود، وعدم التعارض في الاسم مع كيان موجود، وكون Materialization هو
View، وأن SQL غير فارغ. - إعادة بناء XML لنموذج البيانات — يُحوَّل JSON الخاص بالربط إلى تعريف
DMEntity(بنفس الشكل الذي يمتلكه كل كيان آخر) ويُخزَّن على السجل. - View DDL — يُشغّل النظام
DROP VIEW IF EXISTS dbo.vw_<code>ثمCREATE VIEW dbo.vw_<code> AS <your SELECT>داخل نفس معاملة قاعدة البيانات لحفظ الكيان. إذا فشل View DDL — خطأ نحوي، أو عمود مفقود، أوORDER BYغير قانوني بدونTOP، أو مشكلة صلاحيات — تُلغى المعاملة بأكملها وترى خطأ SQL Server مباشرةً. لا يُثبَّت أي شيء جزئي.
بعد اكتمال الحفظ، تُعاد بناء ذاكرة التخزين المؤقت لنموذج البيانات حتى يرى الاستدعاء التالي لمعالج التقارير أو معالج Widget لوحة المعلومات الكيان الجديد في منتقي الجدول الرئيسي. لا يلزم إعادة التشغيل ولا مسح يدوي للتخزين المؤقت.
استخدام الكيانات الافتراضية في معالج التقارير
افتح معالج التقارير، انقر الجدول الرئيسي، وسيظهر كيانك الافتراضي جانبًا الكيانات الحقيقية. يعرضها المنتقي تحت نوع الجدول Virtual Entity (بالتوازي مع Entity وDetail Line وSystem Table).
من هنا يعمل كل شيء كما لو كنت على جدول حقيقي:
- اختيار الحقول — انقر Select Fields وسترى قائمة الخصائص التي عرّفتها:
itemوlegalEntityوvalueDateوtotalCostوtotalQty. تتوسع الحقول المرجعية تلقائيًا لمنحكitem.codeوitem.name1وitem.name2إلخ. - المدخلات — أضف
valueDateكمدخل بنوع فلترBetweenوستحصل على موجه نطاق التاريخ القياسي. - التجميعات — أضف
totalCostوtotalQtyكمقاييس، وجمّع حسبitem، واحصل على تقرير مُجمَّع. - الترجمات — تظهر الأسماء العربية والإنجليزية التي حددتها لكل خاصية كعناوين للأعمدة.
راجع دليل معالج التقارير للاطلاع على بقية آليات المعالج — وهي جميعًا تنطبق دون تغيير.
استخدام الكيانات الافتراضية في معالج Widget لوحة المعلومات
في وحدة Dashboard، عند إنشاء widget مدعوم بمصدر بيانات من المعالج، يظهر نفس منتقي الجدول الرئيسي مع كيانك الافتراضي مدرجًا فيه. بمجرد اختياره، تعمل كل ميزات BI التي تحصل عليها الـ widgets المدعومة بالمعالج — التعمق حسب المحدد (dimension drill-by)، وأعمدة cross-filter المستنتجة تلقائيًا، ومنتقيات معرف الحقل — مع الكيان الافتراضي تمامًا كما لو كانت كيانًا حقيقيًا.
هنا تتجلى أكثر فائدة الكيانات الافتراضية: تبنيها مرة واحدة، وبعد ذلك تستطيع إنشاء widgets جديدة للوحة المعلومات بصريًا دون لمس SQL مجددًا.
لمعرفة تفاصيل سلوك الـ widgets المدعومة بالمعالج، راجع دليل وحدة BI ومرجع Wizard Mode.
مثال عملي — تقرير حركات تكاليف البضاعة في الطريق
لنستعرض الخطوات الكاملة من منظور المستخدم باستخدام SQL من بداية الدليل.
الخطوة 1 — إنشاء الكيان الافتراضي
افتح Virtual Entity من قائمة وحدة Basic، انقر جديد، وأدخل:
- Code:
InTransitMovements - Arabic Name:
حركات البضاعة في الطريق - English Name:
In-Transit Movements - Materialization:
View - SQL Query:
select l.totalCost, l.totalQty, l.item_id, l.legalEntity_id, l.valueDate
from CostInTransLine l
union all
select l.totalCost * -1, l.totalQty * -1, l.item_id, l.legalEntity_id, l.valueDate
from CostOutTransLine lاحفظ الترويسة.
الخطوة 2 — ربط الأعمدة
انقر Edit Mappings. تفتح النافذة مع SQL محمّل مسبقًا. انقر Bootstrap. تمتلئ قائمة الخصائص:
| Column Name | Full Name | Field Type | Reference To |
|---|---|---|---|
totalCost | totalCost | Decimal | — |
totalQty | totalQty | Decimal | — |
item_id | item | Reference | InvItem |
legalEntity_id | legalEntity | Reference | LegalEntity |
valueDate | valueDate | Date | — |
انقر OK. تُغلق النافذة؛ في شاشة الكيان، انقر حفظ. يُنشأ الـ view vw_InTransitMovements ويسجّل الكيان نفسه في نموذج البيانات.
الخطوة 3 — بناء التقرير
افتح معالج التقارير، أنشئ معالجًا جديدًا، واختر In-Transit Movements كجدول رئيسي. أضف الحقول:
this— مرجع الكيان (حتى تتمكن من التعمق)item— مربوط تلقائيًا بـInvItemلأن الخاصية مرجعيةvalueDatetotalQty(مع تجميعSum)totalCost(مع تجميعSum)
أضف valueDate كمدخل بنوع فلتر Between. احفظ وشغّل. ستحصل على ملخص لكل صنف يعرض صافي حركة البضاعة في الطريق خلال نطاق التاريخ المحدد، بلا SQL خام في أي مكان بالمعالج.
حالات الحافة والمزالق الشائعة
| الحالة | ما يحدث |
|---|---|
| SQL لديك يحتوي على خطأ نحوي | يعرض المحرر شكوى المحلل مباشرة. لا يزال بإمكانك الحفظ، لكن View DDL سيفشل ويظهر خطأ SQL Server. |
| تشير إلى عمود لم يعد موجودًا في الجدول المصدر | يفشل View DDL عند الحفظ. أصلح SELECT وحاول مجددًا. |
SQL يحتوي على ORDER BY بدون TOP | SQL Server يرفض هذا في الـ views. يفشل الحفظ مع خطأ SQL Server. انقل الترتيب إلى التقرير المستهلك عوضًا عن ذلك. |
| تبني كيانًا افتراضيًا يشير إلى كيان افتراضي آخر | مسموح. قاعدة البيانات تفرض اشتراط عدم وجود تبعيات دائرية — إذا أنشأت دائرة عن طريق الخطأ (A → B → A)، سيفشل الأمر الثاني CREATE VIEW. |
| يحفظ مستخدمان نفس الكيان الافتراضي في الوقت نفسه | تعارض القفل التفاؤلي القياسي؛ يفوز حفظ واحد، والآخر يرى خطأ عدم تطابق الإصدار. |
| تحفظ بدون ربط أي خصائص | مسموح. يُنشأ الـ view لكن الكيان لا يحتوي على حقول قابلة للاستخدام في المعالج. أضف الأعمدة لاحقًا. |
SQL يحتوي على معامل :placeholder | مرفوض عند التحقق. يجب أن يكون SQL للكيان الافتراضي مكتفيًا بذاته. |
| تغير Code بعد الحفظ الأول | مرفوض عند التحقق. أنشئ كيانًا افتراضيًا جديدًا بالكود الجديد إذا أردت إعادة التسمية. |
نموذج الثقة — من يجب أن يتمتع بالوصول
تعريف الكيان الافتراضي يُشغّل SQL داخل اتصال قاعدة بيانات التطبيق. لا توجد بيئة معزولة (sandbox): ما يستطيع مستخدم التطبيق فعله، يستطيع SELECT الكيان الافتراضي فعله. وهذا يعني:
- قيّد قائمة Virtual Entity وصلاحيات تعديل الكيان للمستخدمين المتقدمين / المسؤولين.
- لا تكشف شاشة إنشاء الكيان للأدوار التي لن تثق بها لكتابة SQL قراءة فقط اعتباطي ضد قاعدة البيانات الإنتاجية.
- الـ view المُنشأ من SELECT هو view SQL عادي — أي شخص يستطيع قراءة صفوفه يستطيع الاستعلام عنه بحرية بمجرد وجوده، لكن إنشاء/تعديل التعريف هو ما يحتاج إلى تقييد.
ما القادم لاحقًا
تُوفّر المرحلة الأولى الكيانات الافتراضية بوصفها views فقط. يحمل JSON للإعدادات حقولًا متوافقة مستقبلًا للمرحلة التالية، لذا سيتحمّل أي شيء تحفظه الآن التحولات القادمة بسلاسة:
- Materialization → Table — جداول مُجمَّعة مسبقًا فيزيائيًا للاستعلامات المُكلفة جدًا لتشغيلها عند كل تحميل للوحة المعلومات.
- سياسات التحديث (Refresh policies) — تحديث يدوي أو مُجدول (cron) أو عند الكتابة للجداول المُجسَّدة.
- إدارة الفهارس (Index management) — تعريف فهارس على الجدول المُجسَّد لتحسين أداء الاستعلام.
- إعادة تسمية الكود (Code rename) — حذف الـ view القديم وإنشاء جديد في معاملة واحدة.
- تتبع التبعيات (Dependency tracking) — معرفة التقارير ولوحات المعلومات والكيانات الافتراضية الأخرى التي تعتمد على كيان افتراضي معين قبل تغييره أو حذفه.
في الوقت الحالي، اعتبر Materialization إشارة خارطة طريق: القائمة المنسدلة تعرض Table معطلًا مع تلميح "Coming later".