Làm thế nào bạn có thể đại diện thừa kế trong cơ sở dữ liệu?


235

Tôi đang suy nghĩ về cách biểu diễn một cấu trúc phức tạp trong cơ sở dữ liệu SQL Server.

Hãy xem xét một ứng dụng cần lưu trữ chi tiết của một họ các đối tượng, có chung một số thuộc tính, nhưng có nhiều thuộc tính khác không phổ biến. Ví dụ, gói bảo hiểm thương mại có thể bao gồm trách nhiệm pháp lý, động cơ, tài sản và bồi thường trong cùng một hồ sơ chính sách.

Việc triển khai điều này trong C # là không quan trọng, vì bạn có thể tạo Chính sách với một bộ Phần, trong đó Phần được kế thừa theo yêu cầu cho các loại bìa khác nhau. Tuy nhiên, cơ sở dữ liệu quan hệ dường như không cho phép điều này dễ dàng.

Tôi có thể thấy rằng có hai lựa chọn chính:

  1. Tạo bảng Chính sách, sau đó là bảng Phần, với tất cả các trường bắt buộc, cho tất cả các biến thể có thể, hầu hết trong số đó sẽ là null.

  2. Tạo bảng Chính sách và nhiều bảng Mục, mỗi bảng cho mỗi loại bìa.

Cả hai lựa chọn thay thế này có vẻ không đạt yêu cầu, đặc biệt là cần phải viết các truy vấn trên tất cả các Phần, có liên quan đến nhiều liên kết hoặc nhiều kiểm tra null.

Thực tiễn tốt nhất cho kịch bản này là gì?


Câu trả lời:


430

@Bill Karwin mô tả ba mô hình thừa kế trong cuốn sách SQL Antipotypes của mình , khi đề xuất các giải pháp cho Thực thể-Thuộc tính-Giá trị SQL . Đây là một tổng quan ngắn gọn:

Kế thừa bảng đơn (hay còn gọi là Bảng kế thừa phân cấp):

Sử dụng một bảng duy nhất như trong tùy chọn đầu tiên của bạn có lẽ là thiết kế đơn giản nhất. Như bạn đã đề cập, nhiều thuộc tính dành riêng cho kiểu con sẽ phải được cung cấp một NULLgiá trị trên các hàng nơi các thuộc tính này không áp dụng. Với mô hình này, bạn sẽ có một bảng chính sách, trông giống như thế này:

+------+---------------------+----------+----------------+------------------+
| id   | date_issued         | type     | vehicle_reg_no | property_address |
+------+---------------------+----------+----------------+------------------+
|    1 | 2010-08-20 12:00:00 | MOTOR    | 01-A-04004     | NULL             |
|    2 | 2010-08-20 13:00:00 | MOTOR    | 02-B-01010     | NULL             |
|    3 | 2010-08-20 14:00:00 | PROPERTY | NULL           | Oxford Street    |
|    4 | 2010-08-20 15:00:00 | MOTOR    | 03-C-02020     | NULL             |
+------+---------------------+----------+----------------+------------------+

\------ COMMON FIELDS -------/          \----- SUBTYPE SPECIFIC FIELDS -----/

Giữ cho thiết kế đơn giản là một lợi thế, nhưng vấn đề chính của phương pháp này là:

  • Khi nói đến việc thêm các kiểu con mới, bạn sẽ phải thay đổi bảng để chứa các thuộc tính mô tả các đối tượng mới này. Điều này có thể nhanh chóng trở thành vấn đề khi bạn có nhiều kiểu con hoặc nếu bạn có kế hoạch thêm các kiểu con một cách thường xuyên.

  • Cơ sở dữ liệu sẽ không thể thực thi các thuộc tính nào được áp dụng và không thuộc tính nào, vì không có siêu dữ liệu để xác định thuộc tính nào thuộc về các kiểu con.

  • Bạn cũng không thể thực thi các NOT NULLthuộc tính của một kiểu con nên là bắt buộc. Bạn sẽ phải xử lý điều này trong ứng dụng của bạn, nói chung là không lý tưởng.

Kế thừa bảng bê tông:

Một cách tiếp cận khác để giải quyết sự kế thừa là tạo một bảng mới cho mỗi kiểu con, lặp lại tất cả các thuộc tính phổ biến trong mỗi bảng. Ví dụ:

--// Table: policies_motor
+------+---------------------+----------------+
| id   | date_issued         | vehicle_reg_no |
+------+---------------------+----------------+
|    1 | 2010-08-20 12:00:00 | 01-A-04004     |
|    2 | 2010-08-20 13:00:00 | 02-B-01010     |
|    3 | 2010-08-20 15:00:00 | 03-C-02020     |
+------+---------------------+----------------+
                          
--// Table: policies_property    
+------+---------------------+------------------+
| id   | date_issued         | property_address |
+------+---------------------+------------------+
|    1 | 2010-08-20 14:00:00 | Oxford Street    |   
+------+---------------------+------------------+

Thiết kế này về cơ bản sẽ giải quyết các vấn đề được xác định cho phương pháp bảng đơn:

  • Các thuộc tính bắt buộc có thể được thi hành bằng NOT NULL .

  • Thêm một kiểu con mới yêu cầu thêm một bảng mới thay vì thêm các cột vào một bảng hiện có.

  • Cũng không có rủi ro rằng một thuộc tính không phù hợp được đặt cho một kiểu con cụ thể, chẳng hạn như vehicle_reg_notrường cho chính sách thuộc tính.

  • Không cần typethuộc tính như trong phương thức bảng đơn. Loại hiện được xác định bởi siêu dữ liệu: tên bảng.

Tuy nhiên mô hình này cũng đi kèm với một vài nhược điểm:

  • Các thuộc tính phổ biến được trộn lẫn với các thuộc tính cụ thể của kiểu con và không có cách nào dễ dàng để xác định chúng. Cơ sở dữ liệu cũng sẽ không biết.

  • Khi xác định các bảng, bạn sẽ phải lặp lại các thuộc tính chung cho mỗi bảng phụ. Đó chắc chắn không phải là KHÔ .

  • Tìm kiếm tất cả các chính sách bất kể loại phụ trở nên khó khăn và sẽ cần một loạt các chính sách UNION.

Đây là cách bạn sẽ phải truy vấn tất cả các chính sách bất kể loại:

SELECT     date_issued, other_common_fields, 'MOTOR' AS type
FROM       policies_motor
UNION ALL
SELECT     date_issued, other_common_fields, 'PROPERTY' AS type
FROM       policies_property;

Lưu ý cách thêm các kiểu con mới sẽ yêu cầu sửa đổi truy vấn ở trên với phần bổ sung UNION ALLcho mỗi kiểu con. Điều này có thể dễ dàng dẫn đến lỗi trong ứng dụng của bạn nếu thao tác này bị quên.

Kế thừa bảng lớp (hay còn gọi là kế thừa bảng theo loại):

Đây là giải pháp mà @David đề cập trong câu trả lời khác . Bạn tạo một bảng duy nhất cho lớp cơ sở của bạn, bao gồm tất cả các thuộc tính phổ biến. Sau đó, bạn sẽ tạo các bảng cụ thể cho từng kiểu con, có khóa chính cũng đóng vai trò là khóa ngoại đối với bảng cơ sở. Thí dụ:

CREATE TABLE policies (
   policy_id          int,
   date_issued        datetime,

   -- // other common attributes ...
);

CREATE TABLE policy_motor (
    policy_id         int,
    vehicle_reg_no    varchar(20),

   -- // other attributes specific to motor insurance ...

   FOREIGN KEY (policy_id) REFERENCES policies (policy_id)
);

CREATE TABLE policy_property (
    policy_id         int,
    property_address  varchar(20),

   -- // other attributes specific to property insurance ...

   FOREIGN KEY (policy_id) REFERENCES policies (policy_id)
);

Giải pháp này giải quyết các vấn đề được xác định trong hai thiết kế khác:

  • Các thuộc tính bắt buộc có thể được thi hành với NOT NULL.

  • Thêm một kiểu con mới yêu cầu thêm một bảng mới thay vì thêm các cột vào một bảng hiện có.

  • Không có rủi ro rằng một thuộc tính không phù hợp được đặt cho một kiểu con cụ thể.

  • Không cần typethuộc tính.

  • Bây giờ các thuộc tính phổ biến không được trộn lẫn với các thuộc tính cụ thể của kiểu con nữa.

  • Chúng ta có thể ở lại KHÔ, cuối cùng. Không cần lặp lại các thuộc tính chung cho mỗi bảng phụ khi tạo các bảng.

  • Việc quản lý tăng tự động idcho các chính sách trở nên dễ dàng hơn, bởi vì điều này có thể được xử lý bởi bảng cơ sở, thay vì mỗi bảng phụ tạo ra chúng một cách độc lập.

  • Tìm kiếm tất cả các chính sách bất kể loại phụ bây giờ trở nên rất dễ dàng: Không UNIONcần thiết - chỉ cần a SELECT * FROM policies.

Tôi coi cách tiếp cận bảng lớp là phù hợp nhất trong hầu hết các tình huống.


Tên của ba mô hình này xuất phát từ cuốn sách Các mô hình kiến ​​trúc ứng dụng doanh nghiệp của Martin Fowler .


97
Tôi cũng đang sử dụng thiết kế này, nhưng bạn không đề cập đến những nhược điểm. Cụ thể: 1) bạn nói rằng bạn không cần loại; đúng nhưng bạn không thể xác định loại thực tế của một hàng trừ khi bạn nhìn vào tất cả các bảng phụ để tìm kết quả khớp. 2) Thật khó để giữ cho bảng chính và các bảng phụ được đồng bộ hóa (ví dụ: người ta có thể xóa hàng trong bảng phụ và không nằm trong bảng chính). 3) Bạn có thể có nhiều hơn một kiểu con cho mỗi hàng chính. Tôi sử dụng các kích hoạt để làm việc khoảng 1, nhưng 2 và 3 là những vấn đề rất khó khăn. Trên thực tế 3 không phải là vấn đề nếu bạn mô hình thành phần, nhưng là để kế thừa nghiêm ngặt.

19
+1 cho nhận xét của @ Tibo, đó là một vấn đề nghiêm trọng. Kế thừa bảng lớp thực sự mang lại một lược đồ không chuẩn hóa. Trường hợp như thừa kế Bảng bê tông không có, và tôi không đồng ý với lập luận rằng Kế thừa bảng cụ thể cản trở DRY. SQL cản trở DRY, vì nó không có phương tiện siêu lập trình. Giải pháp là sử dụng Bộ công cụ cơ sở dữ liệu (hoặc tự viết) để thực hiện công việc nặng nhọc, thay vì viết SQL trực tiếp (hãy nhớ rằng, đây thực sự chỉ là ngôn ngữ giao diện DB). Rốt cuộc, bạn cũng không viết đơn đăng ký doanh nghiệp của mình.
Jo So

18
@Tibo, về điểm 3, bạn có thể sử dụng cách tiếp cận được giải thích tại đây: sqlteam.com/article/iêu , Kiểm tra phần Ràng buộc mô hình một-một trong hai .
Andrew

4
@DanielVassallo Đầu tiên cảm ơn vì câu trả lời tuyệt vời, 1 nghi ngờ nếu một người có chính sách. Làm thế nào để biết chính sách của mình_motor hay chính sách_property? Một cách là tìm kiếm chính sách trong tất cả các Bảng phụ nhưng tôi đoán đây là cách xấu phải không, Cách tiếp cận chính xác là gì?
ThomasBecker

11
Tôi thực sự thích lựa chọn thứ ba của bạn. Tuy nhiên, tôi bối rối cách CHỌN sẽ hoạt động. Nếu bạn CHỌN * TỪ các chính sách, bạn sẽ nhận lại id chính sách nhưng bạn vẫn không biết chính sách thuộc về bảng phụ nào. Bạn vẫn không phải thực hiện THAM GIA với tất cả các kiểu con để có được tất cả các chi tiết chính sách chứ?
Adam

14

Tùy chọn thứ 3 là tạo bảng "Chính sách", sau đó là bảng "SectionsMain" lưu trữ tất cả các trường phổ biến trên các loại phần. Sau đó tạo các bảng khác cho từng loại phần chỉ chứa các trường không chung.

Quyết định nào là tốt nhất phụ thuộc chủ yếu vào số lượng trường bạn có và cách bạn muốn viết SQL của mình. Họ sẽ làm việc tất cả. Nếu bạn chỉ có một vài lĩnh vực thì có lẽ tôi sẽ đi với # 1. Với "rất nhiều" các lĩnh vực, tôi sẽ nghiêng về # 2 hoặc # 3.


+1: Tùy chọn thứ 3 là gần nhất với mô hình thừa kế và IMO được chuẩn hóa nhất
RedFilter

Tùy chọn # 3 của bạn thực sự chỉ là những gì tôi muốn nói bởi tùy chọn # 2. Có nhiều lĩnh vực và một số Phần cũng sẽ có các thực thể con.
Steve Jones

9

Với thông tin được cung cấp, tôi sẽ lập mô hình cơ sở dữ liệu như sau:

CHÍNH SÁCH

  • POLICY_ID (khóa chính)

TRÁCH NHIỆM

  • LIABILITY_ID (khóa chính)
  • POLICY_ID (khóa ngoại)

TÍNH CHẤT

  • PROPERTY_ID (khóa chính)
  • POLICY_ID (khóa ngoại)

... và v.v., vì tôi hy vọng sẽ có các thuộc tính khác nhau được liên kết với từng phần của chính sách. Nếu không, có thể có một SECTIONSbảng duy nhất và ngoài ra policy_id, còn có một section_type_code...

Dù bằng cách nào, điều này sẽ cho phép bạn hỗ trợ các phần tùy chọn cho mỗi chính sách ...

Tôi không hiểu những gì bạn thấy không thỏa đáng về phương pháp này - đây là cách bạn lưu trữ dữ liệu trong khi duy trì tính toàn vẹn tham chiếu và không trùng lặp dữ liệu. Thuật ngữ này là "bình thường hóa" ...

Vì SQL dựa trên SET, nên nó khá xa lạ với các khái niệm lập trình / OO theo thủ tục & yêu cầu mã để chuyển từ cõi này sang cõi khác. Các ORM thường được xem xét, nhưng chúng không hoạt động tốt trong các hệ thống phức tạp, khối lượng lớn.


Vâng, tôi có được điều bình thường hóa
Steve Jones

6

Ngoài ra, tại giải pháp Daniel Vassallo, nếu bạn sử dụng SQL Server 2016+, có một giải pháp khác mà tôi đã sử dụng trong một số trường hợp mà không bị mất hiệu suất đáng kể.

Bạn chỉ có thể tạo một bảng chỉ có trường chung và thêm một cột bằng JSON chuỗi chứa tất cả các trường cụ thể của kiểu con.

Tôi đã thử nghiệm thiết kế này để quản lý thừa kế và tôi rất vui vì tính linh hoạt mà tôi có thể sử dụng trong ứng dụng tương đối.


1
Đó là một ý tưởng thú vị. Tôi chưa sử dụng JSON trong SQL Server, nhưng sử dụng nó rất nhiều ở nơi khác. Cảm ơn cho những người đứng đầu lên.
Steve Jones

5

Một cách khác để làm điều đó, là sử dụng INHERITSthành phần. Ví dụ:

CREATE TABLE person (
    id int ,
    name varchar(20),
    CONSTRAINT pessoa_pkey PRIMARY KEY (id)
);

CREATE TABLE natural_person (
    social_security_number varchar(11),
    CONSTRAINT pessoaf_pkey PRIMARY KEY (id)
) INHERITS (person);


CREATE TABLE juridical_person (
    tin_number varchar(14),
    CONSTRAINT pessoaj_pkey PRIMARY KEY (id)
) INHERITS (person);

Do đó, có thể định nghĩa một kế thừa giữa các bảng.


Các DB khác có hỗ trợ INHERITSngoài PostgreSQL không? MySQL chẳng hạn?
gianni christofakis

1
@giannischristofakis: MySQL chỉ là một cơ sở dữ liệu quan hệ, trong khi Postgres là một cơ sở dữ liệu quan hệ đối tượng. Vì vậy, không có MySQL không hỗ trợ này. Trên thực tế, tôi nghĩ rằng Postgres là DBMS hiện tại duy nhất hỗ trợ kiểu thừa kế này.
a_horse_with_no_name

2
@ marco-paulo-ollivier, câu hỏi của OP là về SQL Server, vì vậy tôi không hiểu tại sao bạn cung cấp giải pháp chỉ hoạt động với Postgres. Rõ ràng, không giải quyết vấn đề.
ánh xạ đến

@mapto câu hỏi này đã trở thành một cái gì đó của mục tiêu dupe "làm thế nào để thực hiện kiểu OO trong cơ sở dữ liệu"; rằng ban đầu nó là về máy chủ sql có vẻ không liên quan
Caius Jard

0

Tôi nghiêng về phương pháp số 1 (bảng Mục thống nhất), vì mục đích lấy hiệu quả toàn bộ chính sách với tất cả các phần của chúng (mà tôi cho rằng hệ thống của bạn sẽ hoạt động rất nhiều).

Hơn nữa, tôi không biết phiên bản SQL Server nào bạn đang sử dụng, nhưng trong năm 2008+ Cột thưa thớt giúp tối ưu hóa hiệu suất trong các tình huống trong đó nhiều giá trị trong một cột sẽ là NULL.

Cuối cùng, bạn sẽ phải quyết định các phần chính sách "tương tự" như thế nào. Trừ khi chúng khác nhau đáng kể, tôi nghĩ rằng một giải pháp bình thường hóa có thể rắc rối hơn giá trị của nó ... nhưng chỉ bạn mới có thể thực hiện cuộc gọi đó. :)


Sẽ có quá nhiều thông tin để trình bày toàn bộ Chính sách trong một lần, do đó không cần phải lấy lại toàn bộ hồ sơ. Tôi nghĩ rằng đó là năm 2005, mặc dù tôi đã sử dụng năm 2008 thưa thớt trong các dự án khác.
Steve Jones

Thuật ngữ "bảng mục thống nhất" đến từ đâu? Google cho thấy hầu như không có kết quả nào cho nó và đã có đủ các điều khoản khó hiểu ở đây.
Stephan-v

-1

Ngoài ra, hãy xem xét sử dụng cơ sở dữ liệu tài liệu (như MongoDB) vốn hỗ trợ cấu trúc dữ liệu phong phú và lồng nhau.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.