Những gì bạn đang mô tả được gọi là Liên kết đa hình. Tức là, cột "khóa ngoại" chứa một giá trị id phải tồn tại trong một tập hợp các bảng mục tiêu. Thông thường, các bảng mục tiêu có liên quan với nhau theo một cách nào đó, chẳng hạn như là các thể hiện của một số lớp dữ liệu thông thường. Bạn cũng cần một cột khác dọc theo cột khóa ngoại, để trên mỗi hàng, bạn có thể chỉ định bảng mục tiêu nào được tham chiếu.
CREATE TABLE popular_places (
user_id INT NOT NULL,
place_id INT NOT NULL,
place_type VARCHAR(10) -- either 'states' or 'countries'
-- foreign key is not possible
);
Không có cách nào để mô hình hóa các Hiệp hội đa hình bằng cách sử dụng các ràng buộc SQL. Ràng buộc khóa ngoại luôn tham chiếu đến một bảng mục tiêu.
Các hiệp hội đa hình được hỗ trợ bởi các khung công tác như Rails và Hibernate. Nhưng họ nói rõ ràng rằng bạn phải vô hiệu hóa các ràng buộc SQL để sử dụng tính năng này. Thay vào đó, ứng dụng hoặc khuôn khổ phải thực hiện công việc tương đương để đảm bảo rằng tham chiếu được thỏa mãn. Nghĩa là, giá trị trong khóa ngoại có trong một trong các bảng mục tiêu có thể có.
Các hiệp hội đa hình rất yếu trong việc thực thi tính nhất quán của cơ sở dữ liệu. Tính toàn vẹn của dữ liệu phụ thuộc vào tất cả các máy khách truy cập cơ sở dữ liệu với cùng một logic toàn vẹn tham chiếu được thực thi và việc thực thi cũng phải không có lỗi.
Dưới đây là một số giải pháp thay thế tận dụng được tính toàn vẹn tham chiếu được thực thi bởi cơ sở dữ liệu:
Tạo thêm một bảng cho mỗi mục tiêu. Ví dụ:popular_states
và popular_countries
, tham chiếu nào states
và countries
tương ứng. Mỗi bảng "phổ biến" này cũng tham chiếu đến hồ sơ của người dùng.
CREATE TABLE popular_states (
state_id INT NOT NULL,
user_id INT NOT NULL,
PRIMARY KEY(state_id, user_id),
FOREIGN KEY (state_id) REFERENCES states(state_id),
FOREIGN KEY (user_id) REFERENCES users(user_id),
);
CREATE TABLE popular_countries (
country_id INT NOT NULL,
user_id INT NOT NULL,
PRIMARY KEY(country_id, user_id),
FOREIGN KEY (country_id) REFERENCES countries(country_id),
FOREIGN KEY (user_id) REFERENCES users(user_id),
);
Điều này có nghĩa là để có được tất cả các địa điểm yêu thích phổ biến của người dùng, bạn cần truy vấn cả hai bảng này. Nhưng nó có nghĩa là bạn có thể dựa vào cơ sở dữ liệu để thực thi tính nhất quán.
Tạo places
bảng như một bảng xếp hạng. Như Abie đã đề cập, một giải pháp thay thế thứ hai là các địa điểm phổ biến của bạn tham chiếu một bảng như places
, là cha mẹ của cả hai trạng thái states
và countries
. Nghĩa là, cả tiểu bang và quốc gia cũng có khóa ngoại cho places
(bạn thậm chí có thể đặt khóa ngoại này cũng là khóa chính của states
và countries
).
CREATE TABLE popular_areas (
user_id INT NOT NULL,
place_id INT NOT NULL,
PRIMARY KEY (user_id, place_id),
FOREIGN KEY (place_id) REFERENCES places(place_id)
);
CREATE TABLE states (
state_id INT NOT NULL PRIMARY KEY,
FOREIGN KEY (state_id) REFERENCES places(place_id)
);
CREATE TABLE countries (
country_id INT NOT NULL PRIMARY KEY,
FOREIGN KEY (country_id) REFERENCES places(place_id)
);
Sử dụng hai cột. Thay vì một cột có thể tham chiếu đến một trong hai bảng mục tiêu, hãy sử dụng hai cột. Hai cột này có thể là NULL
; trên thực tế, chỉ một trong số chúng không được NULL
.
CREATE TABLE popular_areas (
place_id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
state_id INT,
country_id INT,
CONSTRAINT UNIQUE (user_id, state_id, country_id), -- UNIQUE permits NULLs
CONSTRAINT CHECK (state_id IS NOT NULL OR country_id IS NOT NULL),
FOREIGN KEY (state_id) REFERENCES places(place_id),
FOREIGN KEY (country_id) REFERENCES places(place_id)
);
Về lý thuyết quan hệ, Liên kết đa hình vi phạm Dạng chuẩn đầu tiên
, bởi vì popular_place_id
thực tế là một cột có hai nghĩa:đó là một tiểu bang hoặc một quốc gia. Bạn sẽ không lưu trữ age
của một người và phone_number
của họ trong một cột duy nhất và vì lý do tương tự, bạn không nên lưu trữ cả state_id
và country_id
trong một cột duy nhất. Việc hai thuộc tính này có kiểu dữ liệu tương thích là ngẫu nhiên; chúng vẫn biểu thị các thực thể logic khác nhau.
Các liên kết đa hình cũng vi phạm Biểu mẫu thông thường thứ ba , bởi vì ý nghĩa của cột phụ thuộc vào cột phụ đặt tên cho bảng mà khóa ngoại tham chiếu đến. Trong Biểu mẫu chuẩn thứ ba, một thuộc tính trong bảng chỉ được phụ thuộc vào khóa chính của bảng đó.
Nhận xét lại từ @SavasVedova:
Tôi không chắc mình theo dõi mô tả của bạn mà không thấy định nghĩa bảng hoặc truy vấn mẫu, nhưng có vẻ như bạn chỉ có nhiều Filters
các bảng, mỗi bảng chứa một khóa ngoại tham chiếu đến một Products
trung tâm bàn.
CREATE TABLE Products (
product_id INT PRIMARY KEY
);
CREATE TABLE FiltersType1 (
filter_id INT PRIMARY KEY,
product_id INT NOT NULL,
FOREIGN KEY (product_id) REFERENCES Products(product_id)
);
CREATE TABLE FiltersType2 (
filter_id INT PRIMARY KEY,
product_id INT NOT NULL,
FOREIGN KEY (product_id) REFERENCES Products(product_id)
);
...and other filter tables...
Việc kết hợp các sản phẩm vào một loại bộ lọc cụ thể rất dễ dàng nếu bạn biết mình muốn tham gia vào loại bộ lọc nào:
SELECT * FROM Products
INNER JOIN FiltersType2 USING (product_id)
Nếu bạn muốn loại bộ lọc là động, bạn phải viết mã ứng dụng để tạo truy vấn SQL. SQL yêu cầu bảng phải được chỉ định và cố định tại thời điểm bạn viết truy vấn. Bạn không thể đặt bảng đã tham gia được chọn động dựa trên các giá trị được tìm thấy trong các hàng riêng lẻ của Products
.
Tùy chọn khác duy nhất là tham gia vào tất cả lọc các bảng bằng cách sử dụng các phép nối bên ngoài. Những sản phẩm không có product_id phù hợp sẽ chỉ được trả về dưới dạng một hàng rỗng. Nhưng bạn vẫn phải mã hóa cứng tất cả các bảng đã tham gia và nếu bạn thêm các bảng bộ lọc mới, bạn phải cập nhật mã của mình.
SELECT * FROM Products
LEFT OUTER JOIN FiltersType1 USING (product_id)
LEFT OUTER JOIN FiltersType2 USING (product_id)
LEFT OUTER JOIN FiltersType3 USING (product_id)
...
Một cách khác để tham gia vào tất cả các bảng bộ lọc là thực hiện nối tiếp:
SELECT * FROM Product
INNER JOIN FiltersType1 USING (product_id)
UNION ALL
SELECT * FROM Products
INNER JOIN FiltersType2 USING (product_id)
UNION ALL
SELECT * FROM Products
INNER JOIN FiltersType3 USING (product_id)
...
Nhưng định dạng này vẫn yêu cầu bạn viết tham chiếu đến tất cả các bảng. Không có gì phải lo lắng.