لماذا لا يمكنك الحصول على مفتاح أجنبي في جمعية متعددة الأشكال؟
سؤال
لماذا لا يمكنك الحصول على مفتاح أجنبي في جمعية متعددة الأشكال، مثل المرء الممثل أدناه كنموذج قضبان؟
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
end
class Article < ActiveRecord::Base
has_many :comments, :as => :commentable
end
class Photo < ActiveRecord::Base
has_many :comments, :as => :commentable
#...
end
class Event < ActiveRecord::Base
has_many :comments, :as => :commentable
end
المحلول
يجب أن يشير المفتاح الأجنبي إلى جدول واحد فقط. هذا أمر أساسي لكل من بناء جملة SQL، ونظرية العلائقية.
تتمثل جمعية متعددة الأشكال في أنه قد يشير عمود معين إلى الجداول الأمين أو أكثر. لا توجد طريقة يمكنك أن تعلن عن القيد في SQL.
تصميم الجمعيات متعددة الأعمدة يكسر قواعد تصميم قاعدة البيانات العلائقية. أنا لا أوصي باستخدامه.
هناك العديد من البدائل:
أقواس حصرية: قم بإنشاء أعمدة مفاتيح أجنبية متعددة، كل مرجعي أحد الوالدين. إنفاذ أن الشخص بالضبط من هذه المفاتيح الأجنبية يمكن أن يكون غير فارغ.
عكس العلاقة: استخدم ثلاثة جداول متعددة إلى العديد من التعليقات، كل مراجع تعليقات الوالد المعني.
فائق ملموسة: بدلا من "Superclass" الضمني "المعروف"، قم بإنشاء جدول حقيقي يشير كل من الجداول الأصل الخاصة بك. ثم ربط تعليقاتك إلى هذا القسرية. سيكون رمز القضبان الزائفة مثل ما يلي (أنا لست مستخدما من القضبان، لذلك يعامل هذا بمثابة مبادئ توجيهية، وليس الرمز الحرفي):
class Commentable < ActiveRecord::Base has_many :comments end class Comment < ActiveRecord::Base belongs_to :commentable end class Article < ActiveRecord::Base belongs_to :commentable end class Photo < ActiveRecord::Base belongs_to :commentable end class Event < ActiveRecord::Base belongs_to :commentable end
أنا أيضا تغطية الجمعيات متعددة الأعمدة في عرضي النماذج العملية المنحى للكائنات في SQL, وكتابتي SQL antipatterns: تجنب معالم برمجة قاعدة البيانات.
إعادة تعليقك: نعم، أعرف أن هناك عمود آخر يلاحظ اسم الجدول الذي يفترض أن المفتاح الأجنبي يشير إليه. هذا التصميم غير مدعوم من المفاتيح الأجنبية في SQL.
ما يحدث، على سبيل المثال، إذا قمت بإدراج تعليق واسم "الفيديو" كاسم الجدول الرئيسي لذلك Comment
ب لا يوجد جدول يدعى "الفيديو" موجود. يجب إحباط إدراج مع خطأ؟ ما القيود التي تنتهكها؟ كيف تعرف RDBMS أن هذا العمود من المفترض تسمية جدول موجود؟ كيف تتعامل مع أسماء طاولة غير حساسة لحالة الأحرف؟
وبالمثل، إذا كنت تسقط Events
الجدول، ولكن لديك صفوف في Comments
التي تشير إلى الأحداث كوالدهم، ما يجب أن يكون النتيجة؟ يجب إحباط جدول الإسقاط؟ يجب الصفوف في Comments
أن تكون أيتم؟ يجب أن يتغيروا للإشارة إلى جدول آخر موجود مثل Articles
ب قم بقية المعرف التي تستخدم للإشارة إلى Events
جعل أي معنى عند الإشارة إلى Articles
?
تعتمد كل المعضلات هذه على حقيقة أن الجمعيات متعددة الأشكال تعتمد على استخدام البيانات (أي قيمة سلسلة) للإشارة إلى البيانات الوصفية (اسم جدول). هذا غير مدعوم من SQL. البيانات والبيانات البيانات الوصفية منفصلة.
أواجه صعوبة في التفاف رأسي حول اقتراحك "القابل للقدمين الخرساني".
حدد
Commentable
كجدول SQL حقيقي، وليس مجرد صفة في تعريف نموذج القضبان الخاص بك. لا توجد أعمدة أخرى ضرورية.CREATE TABLE Commentable ( id INT AUTO_INCREMENT PRIMARY KEY ) TYPE=InnoDB;
تحديد الجداول
Articles
,Photos
, ، وEvents
كما "الفئات الفرعية"Commentable
, ، من خلال جعل المفتاح الأساسي هو أيضا إشارة مفاتيح أجنبيةCommentable
.CREATE TABLE Articles ( id INT PRIMARY KEY, -- not auto-increment FOREIGN KEY (id) REFERENCES Commentable(id) ) TYPE=InnoDB; -- similar for Photos and Events.
تحديد
Comments
الجدول مع مفتاح أجنبي لCommentable
.CREATE TABLE Comments ( id INT PRIMARY KEY AUTO_INCREMENT, commentable_id INT NOT NULL, FOREIGN KEY (commentable_id) REFERENCES Commentable(id) ) TYPE=InnoDB;
عندما تريد إنشاء
Article
(على سبيل المثال)، يجب عليك إنشاء صف جديد فيCommentable
جدا. كذلك بالنسبة لPhotos
وEvents
.INSERT INTO Commentable (id) VALUES (DEFAULT); -- generate a new id 1 INSERT INTO Articles (id, ...) VALUES ( LAST_INSERT_ID(), ... ); INSERT INTO Commentable (id) VALUES (DEFAULT); -- generate a new id 2 INSERT INTO Photos (id, ...) VALUES ( LAST_INSERT_ID(), ... ); INSERT INTO Commentable (id) VALUES (DEFAULT); -- generate a new id 3 INSERT INTO Events (id, ...) VALUES ( LAST_INSERT_ID(), ... );
عندما تريد إنشاء
Comment
, ، استخدم قيمة موجودة فيCommentable
.INSERT INTO Comments (id, commentable_id, ...) VALUES (DEFAULT, 2, ...);
عندما تريد الاستعلام تعليقات معينة
Photo
, ، هل البعض ينضم:SELECT * FROM Photos p JOIN Commentable t ON (p.id = t.id) LEFT OUTER JOIN Comments c ON (t.id = c.commentable_id) WHERE p.id = 2;
عندما يكون لديك فقط معرف التعليق وتريد العثور على مورد التعليق الذي يعلق عليه تعليق. لذلك، قد تجد أنه مفيد للجدول المعلق لتعيين الموارد التي تشير إليها.
SELECT commentable_id, commentable_type FROM Commentable t JOIN Comments c ON (t.id = c.commentable_id) WHERE c.id = 42;
ثم تحتاج إلى تشغيل استعلام ثان للحصول على بيانات من جدول الموارد المعني (الصور، المقالات، إلخ)، بعد اكتشافه من
commentable_type
أي طاولة للانضمام إليها. لا يمكنك القيام بذلك في نفس الاستعلام، لأن SQL يتطلب تسمية الجداول بشكل صريح؛ لا يمكنك الانضمام إلى جدول يحدده البيانات ينتج عن نفس الاستعلام.
من المسلم به أن بعض هذه الخطوات تقطع الاتفاقيات المستخدمة من قبل القضبان. لكن اتفاقيات القضبان مخطئين فيما يتعلق بتصميم قاعدة البيانات العلائقية المناسبة.
نصائح أخرى
بيل كاروين صحيحة أن المفاتيح الأجنبية لا يمكن استخدامها مع العلاقات متعددة الجنسيات بسبب SQL لا تملك علاقات متعددة الجنسية مفهوم الأصلي. ولكن إذا كان هدفك المتمثل في وجود مفتاح أجنبي هو تطبيق تكامل المرجعية، فيمكنك محاكاة ذلك عن طريق المشغلات. هذا يحصل على DB محددة ولكن أدناه هو بعض المشغلات الأخيرة التي أنشأتها لمحاكاة سلوك حذف المتتالية من مفتاح أجنبي على علاقة متعددة الأشكال:
CREATE FUNCTION delete_related_brokerage_subscribers() RETURNS trigger AS $$
BEGIN
DELETE FROM subscribers
WHERE referrer_type = 'Brokerage' AND referrer_id = OLD.id;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER cascade_brokerage_subscriber_delete
AFTER DELETE ON brokerages
FOR EACH ROW EXECUTE PROCEDURE delete_related_brokerage_subscribers();
CREATE FUNCTION delete_related_agent_subscribers() RETURNS trigger AS $$
BEGIN
DELETE FROM subscribers
WHERE referrer_type = 'Agent' AND referrer_id = OLD.id;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER cascade_agent_subscriber_delete
AFTER DELETE ON agents
FOR EACH ROW EXECUTE PROCEDURE delete_related_agent_subscribers();
في الرمز الخاص بي سجل في brokerages
الجدول أو سجل في agents
يمكن أن تتعلق الجدول بسجل في subscribers
جدول.