معايير تصميم قواعد البيانات


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

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

إذا كان لديك قاعدة بيانات عاملة ، فأنت بحاجة إلى إجابة السؤال: "ما هي المعايير التي يمكن تطبيقها لتسهيل استخدام قاعدة البيانات هذه؟". إذا تم استخدام هذه المعايير على نطاق واسع ، فسيكون من السهل عليك استخدام قاعدة البيانات ، لأنه لا يتعين عليك دراسة وتذكر مجموعات جديدة من المعايير في كل مرة تبدأ فيها العمل مع قاعدة بيانات جديدة.

CamelCase تسمية أو تسطير سفلي؟


جئت باستمرار عبر قواعد البيانات التي يتم فيها تسمية الجداول بأسلوب CustomerOrders أو customer_orders . أيهما أفضل للاستخدام؟ ربما تريد تطبيق معيار محدد بالفعل ، ولكن إذا كنت تقوم بإنشاء قاعدة بيانات جديدة ، فنوصيك باستخدام الشرطة السفلية لزيادة إمكانية الوصول إليها. إن عبارة "under value" لها معنى مختلف مقارنة بـ "undervalue" ، ولكن مع وجود تسطير under_value ، ستكون الأولى دائماً under_value ، والثاني سيكون undervalue . وعند استخدام CamelCase ، نحصل على Undervalue و UnderValue ، وهما متطابقان من حيث SQL غير متحسسة لحالة الأحرف. علاوة على ذلك ، إذا كنت تعاني من مشاكل في الرؤية وكنت تقوم باستمرار بتجربة السماعات والدبابيس للتأكيد على الكلمات ، فالتسطير أسهل بكثير في القراءة.

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

الجمع أو المفرد في أسماء الجداول؟


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

هل لديك مستخدمين - users ؟ مزود لديه الكلمة الأساسية user . هل تحتاج إلى جدول القيود؟ constraint هو كلمة محجوزة. كلمة audit
محفوظة ، ولكن هل تحتاج إلى جدول audit ؟ ما عليك سوى استخدام صيغة الجمع للأسماء ، ثم لن تزعجك معظم الكلمات المحجوزة في SQL. حتى PostgreSQL ، الذي يحتوي على محلل SQL ممتاز ، تعثر على طاولة user .

مجرد استخدام صيغة الجمع ، واحتمال الصراع سيكون أقل بكثير.

لا تقم بتسمية العمود بالكود "id"


أنا نفسي أخطأت على مر السنين. بمجرد أن عملت مع عميل في باريس ، واشتكى DBA مني عندما أعطيت id العمود id الاسم. اعتقدت انه كان مجرد المتحذلق. بالفعل ، اسم العمود customers.id فريد من نوعه ، و customers.customer_id هو تكرار للمعلومات.

وبعد ذلك اضطررت لتصحيح هذا:

 SELECT thread.* FROM email thread JOIN email selected ON selected.id = thread.id JOIN character recipient ON recipient.id = thread.recipient_id JOIN station_area sa ON sa.id = recipient.id JOIN station st ON st.id = sa.id JOIN star origin ON origin.id = thread.id JOIN star destination ON destination.id = st.id LEFT JOIN route ON ( route.from_id = origin.id AND route.to_id = destination.id ) WHERE selected.id = ? AND ( thread.sender_id = ? OR ( thread.recipient_id = ? AND ( origin.id = destination.id OR ( route.distance IS NOT NULL AND now() >= thread.datesent + ( route.distance * interval '30 seconds' ) )))) ORDER BY datesent ASC, thread.parent_id ASC 

لاحظ المشكلة؟ إذا استخدمت SQL أسماء email_id الكاملة ، مثل star_id أو star_id أو station_id ، star_id الأخطاء فورًا كما كتبت هذا الرمز ، وليس بعد ذلك ، عندما حاولت أن أفهم ما الخطأ الذي ارتكبته.

تفضل لنفسك واستخدم الأسماء الكاملة للمعرف. شكرا لاحقا

أسماء الأعمدة


إعطاء الأعمدة كأسماء وصفية ممكن. لنفترض أن عمود temperature لا علاقة له بهذا:

 SELECT name, 'too cold' FROM areas WHERE temperature < 32; 

أعيش في فرنسا ، وبالنسبة لنا ستكون درجة الحرارة 32 درجة "شديدة البرودة". لذلك ، من الأفضل تسمية عمود fahrenheit .

 SELECT name, 'too cold' FROM areas WHERE fahrenheit < 32; 

الآن كل شيء واضح تماما.

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

 SELECT * FROM some_table s JOIN some_other_table o ON o.owner = s.person_id; 

هذا الرمز هو حقا كل الحق. ولكن عندما تنظر إلى تعريف الجدول ، سترى أن some_other_table.owner لديه قيد مفتاح خارجي مع companies.company_id . لذلك أساسا هذا SQL هو خطأ. كان من الضروري استخدام أسماء متطابقة:

 SELECT * FROM some_table s JOIN some_other_table o ON o.company_id = s.person_id; 

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

ومع ذلك ، أريد أن أشير إلى أن هذا لا يمكن القيام به دائما. إذا كان لديك جدول به مستودع مصدر ووجهة ، فقد ترغب في مقارنة source_id مع destination_id مع warehouse_id . في هذه الحالة ، من الأفضل إعطاء الأسماء source_warehouse_id و destination_warehouse_id .

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

تجنب القيم الخالية


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

أنواع قواعد البيانات


يمكن أن تحتوي قاعدة البيانات على بيانات من أنواع مختلفة: INTEGER ، JSON ، DATETIME ، وما إلى ذلك. يرتبط النوع بعمود وأي قيمة مضافة إليه يجب أن تتوافق مع هذا النوع.

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

 CustomerAccount.java:5: error: bad operand types for binary operator '>' if ( current > threshold ) { ^ first type: String second type: int 

حتى إذا لم تلاحظ أن current > threshold يقارن الأنواع التي لا تضاهى ، فإن المحول البرمجي سيحقق لك هذا.

ومن المفارقات أن قواعد البيانات التي تخزن بياناتك - وهي خط دفاعك الأخير ضد تلف البيانات - تعمل بشكل رهيب مع الأنواع! فقط مثير للاشمئزاز. على سبيل المثال ، إذا كان جدول customers يحتوي على مفتاح بديل ، يمكنك القيام بذلك:

 SELECT name, birthdate FROM customers WHERE customer_id > weight; 

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

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

ثم تظهر القيم NULL على الساحة. تطبيق SQL القياسي بشكل صحيح في مكان واحد فقط ، في IS NULL و IS NOT NULL . نظرًا لأن القيمة NULL غير معروفة بحكم التعريف ، لا يمكن أن يكون لديك عوامل تشغيل مصممة لها. وبالتالي هناك IS NULL IS NOT NULL بدلاً من = NULL و != NULL . وأي مقارنة بين القيم الفارغة تؤدي إلى ظهور قيمة فارغة جديدة.

إذا بدا هذا غريباً عليك ، فسيكون من الأسهل بكثير أن تكتب "غير معروف" بدلاً من NULL:

مقارنة NULL قيم غير معروفة ينتج عنها NULL قيم غير معروفة.

نعم ، الآن أرى!

ماذا تعني القيمة الخالية؟


مسلحين بفتات النظرية ، نعتبر عواقبها العملية.

يتعين عليك دفع مكافأة قدرها 500 دولار لجميع الموظفين الذين بلغ رواتبهم خلال العام أكثر من 50 ألف دولار.

 SELECT employee_number, name FROM employees WHERE salary > 50000; 

لقد طُردت للتو لأن رئيسك حصل على أكثر من 50 ألف دولار ، لكن راتبه غير موجود في قاعدة البيانات (في عمود employees.salary NULL) ، ولا يمكن لمشغل المقارنة مقارنة NULL بـ 50 000.

لماذا هناك NULL في هذا العمود؟ ربما الراتب سري. ربما المعلومات لم تصل بعد. ربما هذا هو مستشار ولا يتقاضون رواتبهم. ربما يكون لديه أجر بالساعة ، وليس براتب. هناك العديد من الأسباب وراء فقد البيانات.

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

ونعم ، لقد كان مثالًا غبيًا ، لكنه كان القشة الأخيرة.

تؤدي القيم الفارغة إلى مواقف مستحيلة منطقيا


قد يبدو لك أنني متحذلق فيما يتعلق بالقيم الفارغة. ومع ذلك ، دعونا ننظر إلى مثال آخر أقرب بكثير إلى الواقع.

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

  • اعتدت الانضمام الخارجي.
  • يمكنهم بسهولة إنشاء قيم فارغة.
  • يمكن أن تتسبب القيم الفارغة في إعطاء SQL إجابة غير صحيحة.

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

suppliers
supplier_id
مدينة
S1
لندن

parts

part_id
مدينة
P1
NULL

من الصعب إيجاد مثال أبسط.

إرجاع هذا الرمز p1 .

 SELECT part_id FROM parts; 

ماذا سيفعل هذا الكود؟

 SELECT part_id FROM parts WHERE city = city; 

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

 SELECT s.supplier_id, p.part_id FROM suppliers s, parts p WHERE p.city <> s.city OR p.city <> 'Paris'; 

لم نحصل على سلسلة استجابة ، لأننا لا نستطيع مقارنة المدينة NULL ( p.city ) ، وبالتالي لن يؤدي أي من فروع WHERE إلى true .

ومع ذلك ، فنحن نعرف أن المدينة غير المعروفة هي باريس أو لا باريس. إذا كانت باريس ، فسيكون الشرط الأول صحيحًا ( <> 'London' ). إذا لم تكن باريس ، فسيكون الشرط الثاني صحيحًا ( <> 'Paris' ). وبالتالي ، يجب أن يكون WHERE true ، لكنه ليس كذلك ، ونتيجة لذلك ، تنشئ SQL نتيجة مستحيلة منطقياً.

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

يؤدي


  • استخدم __ بدلاً من CamelCase .
  • يجب أن تكون أسماء الجداول في صيغة الجمع.
  • أعط أسماء موسعة للحقول ذات المعرفات ( item_id بدلاً من id ).
  • تجنب أسماء الأعمدة الغامضة.
  • إذا كان ذلك ممكنًا ، فقم بتسمية الأعمدة التي تحتوي على مفاتيح خارجية بنفس طريقة الأعمدة التي تشير إليها.
  • عند الإمكان ، أضف NOT NULL إلى جميع تعريفات الأعمدة.
  • كلما كان ذلك ممكنًا ، تجنب كتابة SQL التي يمكنها إنشاء قيم فارغة.

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

Source: https://habr.com/ru/post/ar484188/


All Articles