Những gì bạn đang mô tả được gọi là Hiệp hội đa hình. Nghĩa là, cột "khóa ngoại" chứa một giá trị id phải tồn tại trong một trong các bảng mục tiêu. Thông thường, các bảng đích có liên quan theo một cách nào đó, chẳng hạn như là các thể hiện của một số siêu lớp dữ liệu phổ biến. 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ác ràng buộc SQL. Một ràng buộc khóa ngoại luôn tham chiếu một bảng mục tiêu.
Hiệp hội đa hình được hỗ trợ bởi các khung 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 khung phải làm công việc tương đương để đảm bảo rằng tài liệu tham khảo được thỏa mãn. Đó là, giá trị trong khóa ngoại hiện diện trong một trong các bảng mục tiêu có thể.
Các hiệp hội đa hình là yếu đối với việc thực thi tính nhất quán của cơ sở dữ liệu. Tính toàn vẹn 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 logic logic toàn vẹn tham chiếu được thi hành và việc thực thi phải không có lỗi.
Dưới đây là một số giải pháp thay thế tận dụng tính toàn vẹn tham chiếu được thi hành 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 states
và countries
tương ứng. Mỗi bảng "phổ biến" này cũng tham chiếu 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 không 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 một places
bảng như một supertable. Như Abie đề cập, một lựa chọn thứ hai là các địa điểm phổ biến của bạn tham chiếu một bảng như thế places
, đó là cha mẹ của cả hai states
và countries
. Nghĩa là, cả tiểu bang và quốc gia cũng có khóa ngoại 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 một trong hai bảng đích, hãy sử dụng hai cột. Hai cột này có thể là NULL
; trong thực tế chỉ có một trong số họ là không 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ề mặt lý thuyết quan hệ, các Hiệp hội đa hình vi phạm hình thức bình thường đầu tiên , bởi vì thực popular_place_id
tế, một cột có hai ý nghĩa: đó là một quốc gia hoặc một quốc gia. Bạn sẽ không lưu trữ một người age
và của họ phone_number
trong một cột và vì lý do tương tự, bạn không nên lưu trữ cả hai state_id
và country_id
trong một cột. Thực tế là 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 hiệp hội đa hình cũng vi phạm 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 bảng mà khóa ngoại đề cập đến. Trong Dạng thông thường thứ ba, một thuộc tính trong bảng phải chỉ 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 chắn tôi làm theo mô tả của bạn mà không thấy các định nghĩa bảng hoặc truy vấn mẫu, nhưng có vẻ như bạn chỉ đơn giản có nhiều Filters
bảng, mỗi bảng chứa một khóa ngoại tham chiếu một Products
bảng trung tâm .
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ới một loại bộ lọc cụ thể thật dễ dàng nếu bạn biết loại nào bạn muốn tham gia:
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 để xây dựng truy vấn SQL. SQL yêu cầu bảng được chỉ định và cố định tại thời điểm bạn viết truy vấn. Bạn không thể làm cho 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ẻ Products
.
Tùy chọn duy nhất khác là tham gia vào tất cả các bảng bộ lọc bằng cách sử dụng các phép nối ngoài. Những sản phẩm không có sản phẩm phù hợp sẽ chỉ được trả về dưới dạng một hàng null. Nhưng bạn vẫn phải mã hóa tất cả các bảng đã tham gia và nếu bạn thêm các bảng 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 một cách thanh thản:
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ó xung quanh đó.
join
mục tiêu cũng sẽ thay đổi ...... Tôi có phức tạp quá không? Cứu giúp!