Polimorfik bir ilişkilendirmede neden yabancı bir anahtara sahip değilsiniz?


81

Aşağıda Rails modeli olarak gösterilen gibi, polimorfik bir ilişkilendirmede neden bir yabancı anahtara sahip olamıyorsunuz?

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

3
Sırf diğerlerinin netliği için, OP foreign_keyaktarılabilecek bir seçenekten bahsetmiyor belongs_to. OP, yerel veritabanının "Yabancı Anahtar Kısıtlaması" ndan bahsediyor. Bu bir süre kafamı karıştırdı.
Joshua Pinter

Yanıtlar:


178

Bir yabancı anahtar yalnızca bir üst tabloya başvurmalıdır. Bu, hem SQL sözdizimi hem de ilişkisel teori için temeldir.

Polimorfik İlişkilendirme, belirli bir sütunun iki veya daha fazla ana tablodan birine başvurabileceği zamandır. Bu kısıtlamayı SQL'de bildirmenin bir yolu yok.

Polimorfik İlişkiler tasarımı, ilişkisel veritabanı tasarımının kurallarını çiğniyor. Kullanmanızı tavsiye etmiyorum.

Birkaç alternatif var:

  • Exclusive Arcs: Her biri bir ebeveyni referans alan birden çok yabancı anahtar sütunu oluşturun. Bu yabancı anahtarlardan tam olarak birinin NULL olmamasını sağlayın.

  • İlişkiyi Tersine Çevirin: Üç çoktan çoğa tablo kullanın, her biri Açıklamalara ve ilgili bir üst öğeye başvurur.

  • Somut Üstün Değer: Örtük "yorumlanabilir" üst sınıf yerine, ana tablolarınızın her birinin başvurduğu gerçek bir tablo oluşturun. Ardından Yorumlarınızı bu süper verile bağlayın. Sözde raylar kodu aşağıdakine benzer bir şey olacaktır (Ben bir Rails kullanıcısı değilim, bu yüzden bunu gerçek kod olarak değil, bir kılavuz olarak değerlendirin):

    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
    

Ayrıca SQL'de Pratik Nesne Yönelimli Modeller sunumumda ve kitabımda polimorfik ilişkileri de ele alıyorum sunumumda SQL Antipatterns: Veritabanı Programlamanın Tuzaklarından Kaçınma .


Yorumunuz: Evet, yabancı anahtarın işaret ettiği varsayılan tablonun adını belirten başka bir sütun olduğunu biliyorum. Bu tasarım, SQL'deki yabancı anahtarlar tarafından desteklenmez.

Örneğin, bir Yorum eklerseniz ve bunun için üst tablonun adı olarak "Video" olarak adlandırırsanız ne olur? Comment ? "Video" adlı tablo yok. Ek bir hata ile iptal edilmeli mi? Hangi kısıtlama ihlal ediliyor? RDBMS, bu sütunun var olan bir tabloyu adlandırmasının beklendiğini nereden biliyor? Büyük / küçük harfe duyarlı olmayan tablo adlarını nasıl işler?

Aynı şekilde, Eventstabloyu kaldırırsanız, ancak içinde CommentsOlayları üst öğe olarak belirten satırlarınız varsa , sonuç ne olmalıdır? Düşürme tablosu iptal edilmeli mi? Sıralar Commentsöksüz bırakılmalı mı? Gibi başka bir mevcut tabloya başvurmak için değişmeli Articlesmi? Eventsİşaret ederken herhangi bir anlam ifade eden id değerleriArticles mu?

Bu ikilemlerin tümü, Polimorfik İlişkilendirmelerin meta verilere (bir tablo adı) atıfta bulunmak için verileri (yani bir dize değeri) kullanmaya bağlı olduğu gerçeğinden kaynaklanmaktadır. Bu, SQL tarafından desteklenmez. Veriler ve meta veriler ayrıdır.


Kafamı "Beton Üstün Olabilir" teklifinin etrafına dolamakta zorlanıyorum.

  • CommentableRails model tanımınızda yalnızca bir sıfat olarak değil, gerçek bir SQL tablosu olarak tanımlayın . Başka sütun gerekli değildir.

    CREATE TABLE Commentable (
      id INT AUTO_INCREMENT PRIMARY KEY
    ) TYPE=InnoDB;
    
  • Tablolar tanımlayın Articles, Photosve Events"alt sınıflarından" olarak bir Commentablebirincil anahtar aynı zamanda bir yabancı anahtar referans olması yaparak, Commentable.

    CREATE TABLE Articles (
      id INT PRIMARY KEY, -- not auto-increment
      FOREIGN KEY (id) REFERENCES Commentable(id)
    ) TYPE=InnoDB;
    
    -- similar for Photos and Events.
    
  • CommentsTabloyu bir yabancı anahtar ile tanımlayın Commentable.

    CREATE TABLE Comments (
      id INT PRIMARY KEY AUTO_INCREMENT,
      commentable_id INT NOT NULL,
      FOREIGN KEY (commentable_id) REFERENCES Commentable(id)
    ) TYPE=InnoDB;
    
  • Bir Article(örneğin) oluşturmak istediğinizde , içinde Commentablede yeni bir satır oluşturmalısınız . Yani çok için Photosve 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(), ... );
    
  • Bir oluşturmak istediğinizde Comment, içinde bulunan bir değeri kullanın Commentable.

    INSERT INTO Comments (id, commentable_id, ...)
    VALUES (DEFAULT, 2, ...);
    
  • Bir verilenle ilgili yorumları sorgulamak istediğinizde Photo, bazı birleştirmeler yapın:

    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;
    
  • Yalnızca bir yorumun kimliğine sahip olduğunuzda ve yorum yazılabilir kaynağı bulmak istediğinizde. Bunun için, Yorumlanabilir tablonun hangi kaynağı referans verdiğini belirlemesinin yararlı olduğunu görebilirsiniz.

    SELECT commentable_id, commentable_type FROM Commentable t
    JOIN Comments c ON (t.id = c.commentable_id)
    WHERE c.id = 42;
    

    Ardından, commentable_typehangi tablodan katılacağınızı keşfettikten sonra ilgili kaynak tablosundan (Fotoğraflar, Makaleler, vb.) Veri almak için ikinci bir sorgu çalıştırmanız gerekir . Bunu aynı sorgu içinde yapamazsınız, çünkü SQL tabloların açıkça adlandırılmasını gerektirir; aynı sorgudaki veri sonuçlarına göre belirlenen bir tabloya katılamazsınız.

Kuşkusuz, bu adımlardan bazıları Rails tarafından kullanılan kuralları çiğniyor. Ancak Rails kuralları, uygun ilişkisel veritabanı tasarımı açısından yanlıştır.


2
Takip ettiğiniz için teşekkürler. Aynı sayfadayız, Rails polimorfik ilişkilendirmelerinde yabancı anahtar için Yorumumuzda iki sütun kullanılır. Bir sütun hedef satırın kimliğini tutar ve ikinci sütun Active Record'a bu anahtarın hangi modelde olduğunu söyler (Makale, Fotoğraf veya Etkinlik). Bunu bilerek, önerdiğiniz üç alternatifi yine de tavsiye eder misiniz? Kafamı "Beton Üstün Dayanıklı" teklifinin etrafına dolamakta zorlanıyorum. "Yorumlarınızı bu süperable'a bağlayın" (Yorumlanabilir) derken neyi kastediyorsunuz?
eggdrop

1
Açıkladığınız için teşekkürler. Rails kurallarının uygun ilişkisel veritabanı tasarımına ilişkin olarak neden yanlış olduğunu söylediğinizi anlıyorum - bazı açılardan model, çeşitli ilişkisel kısıtlamaları uygulama yeteneğini kaybettiği için bir depolama mekanizması olarak düz dosyaları kullanmaya benzer.
patlıcan

7
Kesinlikle. Polymorphic Associations belgesinin kendisi yabancı anahtar kısıtlamalarını kullanamayacağınızı söylediğinde, bunun doğru ilişkisel veritabanı tasarımı olmadığı güçlü bir "kod kokusu" olmalıdır!
Bill Karwin

1
Concrete Supertable çözümünün bir dezavantajı, çocuk tablosunda bilgi tutarlılığını zorlamamasıdır. Örneğin, bir Etkinlikler satırının ve bir Fotoğraflar satırının aynı commentable_id'ye sahip olması mümkün olabilir. Verildi, commentable_id'yi oluşturmak ve onu bir alt tabloya atamak için iyi bir prosedür kullanmak bu durumu önlemelidir, ancak olasılık hala mevcuttur.
Jason Martens

1
@Mohamad, STI iyi çalışır. Üst tablonuz STI kullanıyorsa, yine de yabancı anahtarları tanımlayabilirsiniz. Veya alt masa STI kullansa bile.
Bill Karwin

3

Bill Karwin, SQL'in gerçekten yerel bir kavram polimorfik ilişkilere sahip olmaması nedeniyle yabancı anahtarların polimorfik ilişkilerle kullanılamayacağı konusunda haklı. Ancak bir yabancı anahtara sahip olma amacınız bilgi tutarlılığını uygulamaksa, tetikleyiciler aracılığıyla bunu simüle edebilirsiniz. Bu, DB'ye özgü olur ancak aşağıda, polimorfik bir ilişkide bir yabancı anahtarın basamaklı silme davranışını simüle etmek için oluşturduğum bazı yeni tetikleyiciler verilmiştir:

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();

Kodumda, brokeragestablodaki bir kayıt veya tablodaki bir kayıt, agentstablodaki bir kayıtla ilgili olabilir subscribers.


Bu harika. Yeni oluşturulan polimorfik ilişkilerin geçerli bir tür ve kimliği gösterdiğinden emin olmak için benzer bir tetikleyicinin nasıl oluşturulabileceğine dair herhangi bir fikriniz var mı?
cayblood
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.