كيف تتعامل مع تعدد الأشكال في قاعدة البيانات؟

StackOverflow https://stackoverflow.com/questions/45621

  •  09-06-2019
  •  | 
  •  

سؤال

مثال

أملك Person, SpecialPerson, ، و User. Person و SpecialPerson هم مجرد أشخاص - ليس لديهم اسم مستخدم أو كلمة مرور على الموقع، ولكن يتم تخزينهم في قاعدة بيانات لحفظ السجلات.المستخدم لديه كل نفس البيانات Person وربما SpecialPerson, بالإضافة إلى اسم المستخدم وكلمة المرور كما تم تسجيلهم في الموقع.


كيف يمكنك معالجة هذه المشكلة؟هل لديك Person الجدول الذي يخزن جميع البيانات المشتركة لشخص ما ويستخدم مفتاحًا للبحث عن بياناته فيه SpecialPerson (إذا كانوا شخصًا مميزًا) والمستخدم (إذا كانوا مستخدمين) والعكس صحيح؟

هل كانت مفيدة؟

المحلول

توجد بشكل عام ثلاث طرق لتعيين وراثة الكائنات لجداول قاعدة البيانات.

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

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

يمكنك أيضًا إنشاء جدول لكل فصل كما اقترحت.بهذه الطريقة تحتاج إلى الانضمام للحصول على كافة البيانات.لذا فهو أقل أداءً.أعتقد أن هذا هو الحل الأنظف.

ما تريد استخدامه يعتمد بالطبع على حالتك.لا يوجد أي حل مثالي لذا عليك أن تزن الإيجابيات والسلبيات.

نصائح أخرى

ألق نظرة على مارتن فاولر أنماط بنية تطبيقات المؤسسات:

  • وراثة جدول واحد:

    عند التعيين إلى قاعدة بيانات علائقية، نحاول تقليل الصلات التي يمكن أن تتراكم بسرعة عند معالجة بنية الوراثة في جداول متعددة.يقوم برنامج وراثة الجدول الواحد بتعيين كافة الحقول لجميع فئات بنية الوراثة في جدول واحد.

  • وراثة جدول الصف:

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

  • وراثة الجدول الخرساني:

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

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

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

ما سأقوله هنا هو إرسال مهندسي قواعد البيانات إلى conniptions ولكن هنا:

النظر في قاعدة بيانات منظر كمعادل لتعريف الواجهة.والجدول يعادل فئة.

لذا، في مثالك، ستقوم جميع فئات الأشخاص الثلاثة بتطبيق واجهة IPerson.إذن لديك 3 جداول - واحد لكل من "المستخدم" و"الشخص" و"الشخص الخاص".

ثم احصل على عرض "PersonView" أو أي شيء يحدد الخصائص المشتركة (كما هو محدد بواسطة "الواجهة") من جميع الجداول الثلاثة في العرض الفردي.استخدم عمود "PersonType" في طريقة العرض هذه لتخزين النوع الفعلي للشخص الذي يتم تخزينه.

لذا، عندما تقوم بتشغيل استعلام يمكن تشغيله على أي نوع من الأشخاص، ما عليك سوى الاستعلام عن طريقة عرض PersonView.

قد لا يكون هذا ما قصده OP أن يسأله، لكنني اعتقدت أنني قد أطرح هذا هنا.

لقد واجهت مؤخرًا حالة فريدة من تعدد أشكال قاعدة البيانات في أحد المشروعات.كان لدينا ما بين 60 إلى 120 فئة محتملة، لكل منها مجموعتها الخاصة المكونة من 30 إلى 40 سمة فريدة، وحوالي 10 إلى 12 سمة مشتركة في جميع الفئات.قررنا اتباع مسار SQL-XML وانتهى بنا الأمر بجدول واحد.شيء مثل :

PERSON (personid,persontype, name,address, phone, XMLOtherProperties)

تحتوي على جميع الخصائص الشائعة كأعمدة ثم حقيبة خصائص XML كبيرة.كانت طبقة ORM مسؤولة بعد ذلك عن قراءة/كتابة الخصائص المعنية من XMLOtherProperties.قليلا مثل :

 public string StrangeProperty
{
get { return XMLPropertyBag["StrangeProperty"];}
set { XMLPropertyBag["StrangeProperty"]= value;}
}

(لقد انتهى بنا الأمر إلى تعيين عمود xml كمستند Hastable بدلاً من مستند XML، ولكن يمكنك استخدام ما يناسب DAL الخاص بك بشكل أفضل)

لن يفوز بأي جوائز تصميم، لكنه سينجح إذا كان لديك عدد كبير (أو غير معروف) من الفئات المحتملة.وفي SQL2005، لا يزال بإمكانك استخدام XPATH في استعلامات SQL الخاصة بك لتحديد الصفوف بناءً على بعض الخصائص المخزنة بتنسيق XML.إنها مجرد عقوبة أداء صغيرة يجب استيعابها.

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

  • الجدول حسب التسلسل الهرمي للفئة.جدول واحد للتسلسل الهرمي بأكمله.
  • الجدول لكل فئة فرعية.يتم إنشاء جدول منفصل لكل فئة فرعية مع ارتباط 0-1 بين جداول الفئات الفرعية.
  • الجدول لكل فئة ملموسة.يتم إنشاء جدول واحد لكل فئة ملموسة.

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

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

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

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

لذلك سيكون تصميمي شيء من هذا القبيل

PERSON (الشخص، الاسم، العنوان، الهاتف، ...)

شخص خاص (شخص مرجعي، شخص (شخص)، حقول إضافية...)

المستخدم (الشخصية، مراجع الشخص (الشخصية)، اسم المستخدم، كلمة المرور المشفرة، الحقول الإضافية...)

يمكنك أيضًا إنشاء طرق عرض لاحقًا تعمل على تجميع النوع الفائق والنوع الفرعي، إذا كان ذلك ضروريًا.

العيب الوحيد في هذا النهج هو إذا وجدت نفسك تبحث بشكل مكثف عن الأنواع الفرعية المرتبطة بنوع فائق معين.لا توجد إجابة سهلة لهذا السؤال الذي يخطر ببالي، يمكنك تتبعه برمجيًا إذا لزم الأمر، أو تشغيل بعض الاستعلامات العامة وتخزين النتائج مؤقتًا.وسوف يعتمد حقا على التطبيق.

أود أن أقول ذلك، اعتمادًا على ما يميز الشخص والشخص المميز، ربما لا تريد تعدد الأشكال لهذه المهمة.

سأقوم بإنشاء جدول مستخدم، وهو جدول شخص يحتوي على حقل مفتاح خارجي فارغ للمستخدم (على سبيل المثال، يمكن أن يكون الشخص مستخدمًا، ولكن ليس من الضروري أن يكون كذلك).
ثم سأقوم بإنشاء جدول SpecialPerson الذي يرتبط بجدول الشخص مع وجود أي حقول إضافية فيه.إذا كان السجل موجودًا في SpecialPerson لمعرف شخص معين، فهو/هي/هو شخص مميز.

في شركتنا نتعامل مع تعدد الأشكال من خلال الجمع بين جميع الحقول في جدول واحد، وأسوأ ما في الأمر أنه لا يمكن فرض أي تكامل مرجعي، كما أنه من الصعب جدًا فهم النموذج.أود أن أوصي ضد هذا النهج بالتأكيد.

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

نعم، سأفكر أيضًا في استخدام TypeID مع جدول PersonType إذا كان من الممكن أن يكون هناك المزيد من الأنواع.ومع ذلك، إذا كان هناك 3 فقط، فلا ينبغي أن تكون غير محددة.

هذا منشور قديم لكنني أعتقد أنني سأفكر فيه من الناحية المفاهيمية والإجرائية والأداء.

السؤال الأول الذي أود طرحه هو العلاقة بين الشخص والشخص المميز والمستخدم، وما إذا كان من الممكن أن يكون شخص ما كلاهما شخص مميز ومستخدم في نفس الوقت.أو أي مجموعة أخرى من 4 مجموعات محتملة (الفئة أ + ب، الفئة ب + ج، الفئة أ + ج، أو أ + ب + ج).إذا تم تخزين هذه الفئة كقيمة في ملف type الحقل وبالتالي سيؤدي إلى انهيار هذه المجموعات، وهذا الانهيار غير مقبول، فأعتقد أنه سيكون هناك حاجة إلى جدول ثانوي يسمح بعلاقة رأس بأطراف.لقد تعلمت أنك لا تحكم على ذلك حتى تقوم بتقييم الاستخدام وتكلفة فقدان معلومات المجموعة الخاصة بك.

العامل الآخر الذي يجعلني أميل نحو جدول واحد هو وصفك للسيناريو. User هو الكيان الوحيد الذي له اسم مستخدم (على سبيل المثال varchar(30)) وكلمة المرور (على سبيل المثال varchar(32)).إذا كان الطول المحتمل للحقول المشتركة هو متوسط ​​20 حرفًا لكل 20 حقلاً، فإن زيادة حجم العمود الخاص بك هي 62 على 400، أو حوالي 15٪ - منذ 10 سنوات، لكان هذا أكثر تكلفة مما هو عليه مع أنظمة RDBMS الحديثة، خاصة مع نوع الحقل مثل varchar (على سبيل المثاللـ MySQL) متاح.

وإذا كان الأمان يهمك، فقد يكون من المفيد استدعاء جدول رأس برأس ثانوي credentials ( user_id, username, password).سيتم استدعاء هذا الجدول في JOIN سياقيًا في وقت تسجيل الدخول، ولكنه منفصل هيكليًا عن "أي شخص" فقط في الجدول الرئيسي.و أ LEFT JOIN متاح للاستعلامات التي قد ترغب في اعتبارها "مستخدمين مسجلين".

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

في الماضي، قمت بذلك تمامًا كما تقترح - لديك جدول "شخص" للأشياء الشائعة، ثم ربط "SpecialPerson" للفئة المشتقة.ومع ذلك، فأنا أعيد التفكير في ذلك، حيث أن Linq2Sql يريد أن يكون هناك حقل في نفس الجدول يشير إلى الفرق.لم ألقي نظرة على نموذج الكيان كثيرًا، رغم ذلك - متأكد تمامًا أن هذا يسمح بالطريقة الأخرى.

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

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

الطريقة الأولى مدعومة أيضًا بواسطة LINQ-to-SQL إذا قررت السير في هذا الطريق (يسمونها "Table Per Hierarchy" أو "TPH").

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top