Khóa ngoại chỉ được tham chiếu đến một bảng cha. Đây là điều cơ bản cho cả cú pháp SQL và lý thuyết quan hệ.
Kết hợp đa hình là khi một cột nhất định có thể tham chiếu đến một trong hai hoặc nhiều bảng cha. Không có cách nào bạn có thể khai báo ràng buộc đó trong SQL.
Thiết kế Liên kết đa hình phá vỡ các quy tắc của thiết kế cơ sở dữ liệu quan hệ. Tôi không khuyên bạn nên sử dụng nó.
Có một số lựa chọn thay thế:
Vòng cung riêng: Tạo nhiều cột khóa ngoại, mỗi cột tham chiếu đến một cột gốc. Thực thi rằng chính xác một trong các khóa ngoại này có thể không phải là NULL.
Đảo ngược mối quan hệ: Sử dụng ba bảng nhiều-nhiều, mỗi bảng tham chiếu Nhận xét và một bảng cha tương ứng.
Concrete Supertable: Thay vì lớp cha "có thể nhận xét" ngầm, hãy tạo một bảng thực mà mỗi bảng cha của bạn tham chiếu. Sau đó liên kết Bình luận của bạn với bảng xếp hạng cao đó. Mã Pseudo-rails sẽ giống như sau (Tôi không phải là người dùng Rails, vì vậy hãy coi đây như một hướng dẫn, không phải mã theo nghĩa đen):
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
Tôi cũng đề cập đến các liên kết đa hình trong bài thuyết trình của tôi Mô hình hướng đối tượng thực tế trong SQL và cuốn sách của tôi Phản vật chất SQL: Tránh cạm bẫy của lập trình cơ sở dữ liệu .
Nhận xét của bạn: Có, tôi biết rằng có một cột khác ghi tên của bảng mà khóa ngoại được cho là trỏ đến. Thiết kế này không được hỗ trợ bởi các khóa ngoại trong SQL.
Ví dụ: điều gì sẽ xảy ra nếu bạn chèn một Nhận xét và đặt tên "Video" làm tên của bảng mẹ cho điều đó Comment
? Không tồn tại bảng có tên "Video". Có nên hủy bỏ chèn khi có lỗi không? Ràng buộc nào đang bị vi phạm? Làm thế nào để RDBMS biết rằng cột này phải đặt tên cho một bảng hiện có? Làm thế nào nó xử lý các tên bảng không phân biệt chữ hoa chữ thường?
Tương tự như vậy, nếu bạn bỏ Events
bảng, nhưng bạn có các hàng trong Comments
đó biểu thị Sự kiện là cha của chúng, thì kết quả sẽ là gì? Có nên hủy bỏ drop table? Các hàng trong Comments
có nên bỏ trống không? Họ có nên thay đổi để tham chiếu đến một bảng hiện có khác, chẳng hạn như Articles
? Các giá trị id được sử dụng để trỏ đến Events
có ý nghĩa gì khi trỏ tới Articles
không?
Những tình huống khó xử này đều là do các Hiệp hội đa hình phụ thuộc vào việc sử dụng dữ liệu (tức là giá trị chuỗi) để tham chiếu đến siêu dữ liệu (tên bảng). Điều này không được hỗ trợ bởi SQL. Dữ liệu và siêu dữ liệu là riêng biệt.
Tôi đang gặp khó khăn khi xoay quanh đề xuất "Concrete Supertable" của bạn.
Định nghĩa Commentable
như một bảng SQL thực, không chỉ là một tính từ trong định nghĩa mô hình Rails của bạn. Không có cột nào khác là cần thiết.
CREATE TABLE Commentable (
id INT AUTO_INCREMENT PRIMARY KEY
) TYPE=InnoDB;
Xác định các bảng Articles
, Photos
và Events
là "lớp con" của Commentable
, bằng cách làm khóa chính của họ cũng là một tham khảo chính nước ngoài Commentable
.
CREATE TABLE Articles (
id INT PRIMARY KEY,
FOREIGN KEY (id) REFERENCES Commentable(id)
) TYPE=InnoDB;
Xác định Comments
bảng với một khóa ngoại để Commentable
.
CREATE TABLE Comments (
id INT PRIMARY KEY AUTO_INCREMENT,
commentable_id INT NOT NULL,
FOREIGN KEY (commentable_id) REFERENCES Commentable(id)
) TYPE=InnoDB;
Khi bạn muốn tạo một Article
(ví dụ), bạn cũng phải tạo một hàng mới Commentable
. Vì vậy, quá cho Photos
và Events
.
INSERT INTO Commentable (id) VALUES (DEFAULT);
INSERT INTO Articles (id, ...) VALUES ( LAST_INSERT_ID(), ... );
INSERT INTO Commentable (id) VALUES (DEFAULT);
INSERT INTO Photos (id, ...) VALUES ( LAST_INSERT_ID(), ... );
INSERT INTO Commentable (id) VALUES (DEFAULT);
INSERT INTO Events (id, ...) VALUES ( LAST_INSERT_ID(), ... );
Khi bạn muốn tạo một Comment
, hãy sử dụng một giá trị tồn tại trong Commentable
.
INSERT INTO Comments (id, commentable_id, ...)
VALUES (DEFAULT, 2, ...);
Khi bạn muốn truy vấn nhận xét của một đối tượng nhất định Photo
, hãy thực hiện một số phép nối:
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;
Khi bạn chỉ có id của một bình luận và bạn muốn tìm tài nguyên có thể bình luận đó là bình luận. Đối với điều này, bạn có thể thấy rằng bảng có thể nhận xét hữu ích để chỉ định tài nguyên nào mà nó tham chiếu.
SELECT commentable_id, commentable_type FROM Commentable t
JOIN Comments c ON (t.id = c.commentable_id)
WHERE c.id = 42;
Sau đó, bạn cần chạy một truy vấn thứ hai để lấy dữ liệu từ bảng tài nguyên tương ứng (Ảnh, Bài viết, v.v.), sau khi khám phá ra commentable_type
bảng nào để tham gia. Bạn không thể làm điều đó trong cùng một truy vấn, vì SQL yêu cầu các bảng phải được đặt tên rõ ràng; bạn không thể tham gia vào một bảng được xác định bởi kết quả dữ liệu trong cùng một truy vấn.
Phải thừa nhận rằng một số bước này phá vỡ các quy ước được sử dụng bởi Rails. Nhưng các quy ước Rails là sai đối với thiết kế cơ sở dữ liệu quan hệ thích hợp.
foreign_key
tùy chọn có thể được chuyển chobelongs_to
. OP đang nói về "Ràng buộc khóa ngoại" của cơ sở dữ liệu gốc. Điều đó khiến tôi bối rối trong một thời gian.