لماذا لا يمكنك الحصول على مفتاح أجنبي في جمعية متعددة الأشكال؟

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

سؤال

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

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 جدول.

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