Thiết kế cơ sở dữ liệu - các đối tượng khác nhau với gắn thẻ được chia sẻ


8

Nền tảng của tôi là về lập trình web nhiều hơn là quản trị cơ sở dữ liệu, vì vậy vui lòng sửa cho tôi nếu tôi sử dụng thuật ngữ sai ở đây. Tôi đang cố gắng tìm ra cách tốt nhất để thiết kế cơ sở dữ liệu cho một ứng dụng mà tôi sẽ viết mã.

Tình huống: Tôi đã có Báo cáo trong một bảng và Đề xuất trong một bảng khác. Mỗi báo cáo có thể có nhiều khuyến nghị. Tôi cũng có một bảng riêng cho Từ khóa (để thực hiện gắn thẻ). Tuy nhiên, tôi muốn chỉ có một bộ từ khóa được áp dụng cho cả Báo cáo và Đề xuất để việc tìm kiếm từ khóa mang lại cho bạn Báo cáo và Đề xuất dưới dạng kết quả.

Đây là cấu trúc tôi bắt đầu với:

Reports
----------
ReportID
ReportName


Recommendations
----------
RecommendationID
RecommendationName
ReportID (foreign key)


Keywords
----------
KeywordID
KeywordName


ObjectKeywords
----------
KeywordID (foreign key)
ReportID (foreign key)
RecommendationID (foreign key)

Theo bản năng, tôi cảm thấy như điều này không tối ưu và tôi nên để các đối tượng có thể gắn thẻ của mình được thừa hưởng từ cha mẹ chung và để phụ huynh nhận xét đó được gắn thẻ, sẽ cho cấu trúc sau:

BaseObjects
----------
ObjectID (primary key)
ObjectType


Reports
----------
ObjectID_Report (foreign key)
ReportName


Recommendations
----------
ObjectID_Recommendation (foreign key)
RecommendationName
ObjectID_Report (foreign key)


Keywords
----------
KeywordID (primary key)
KeywordName


ObjectKeywords
----------
ObjectID (foreign key)
KeywordID (foreign key)

Tôi có nên đi với cấu trúc thứ hai này? Tôi có thiếu bất kỳ mối quan tâm quan trọng ở đây? Ngoài ra, nếu tôi đi với cái thứ hai, tôi nên sử dụng cái tên không chung chung nào để thay thế "Object"?

Cập nhật:

Tôi đang sử dụng SQL Server cho dự án này. Đây là một ứng dụng nội bộ với một số lượng nhỏ người dùng không đồng thời, vì vậy tôi không lường trước được tải cao. Về mặt sử dụng, các từ khóa có thể sẽ được sử dụng một cách tiết kiệm. Nó khá nhiều chỉ cho mục đích báo cáo thống kê. Theo nghĩa đó, bất kỳ giải pháp nào tôi đi cùng có lẽ sẽ chỉ ảnh hưởng đến bất kỳ nhà phát triển nào sẽ cần duy trì hệ thống này ... nhưng tôi cho rằng thật tốt khi thực hiện các thực tiễn tốt bất cứ khi nào tôi có thể. Cảm ơn tất cả những cái nhìn sâu sắc!


Có vẻ như bạn không có câu hỏi quan trọng nhất được trả lời - Dữ liệu sẽ được truy cập như thế nào? - Đối với những truy vấn / câu lệnh nào bạn muốn "điều chỉnh" mô hình của mình? - Làm thế nào để bạn có kế hoạch mở rộng chức năng? Tôi nghĩ rằng không có thực tiễn tốt nhất chung - giải pháp phụ thuộc vào câu trả lời của những câu hỏi này. Và nó bắt đầu quan trọng ngay cả trong các mô hình đơn giản như thế này. Hoặc bạn có thể kết thúc với mô hình tuân theo một số nguyên tắc cao hơn nhưng thực sự hấp dẫn trong các tình huống quan trọng nhất - những người dùng hệ thống nhìn thấy.
Štefan Oravec

Điểm tốt! Tôi sẽ phải dành thời gian suy nghĩ về điều này!
matikin9

Câu trả lời:


6

Vấn đề với ví dụ đầu tiên của bạn là bảng tri-link. Có phải điều đó sẽ yêu cầu một trong các khóa ngoại trên báo cáo hoặc đề xuất phải luôn là NULL để từ khóa chỉ liên kết theo cách này hay cách khác?

Trong trường hợp ví dụ thứ hai của bạn, việc nối từ cơ sở đến các bảng dẫn xuất bây giờ có thể yêu cầu sử dụng bộ chọn loại hoặc LEFT THAM GIA tùy thuộc vào cách bạn thực hiện.

Vì vậy, tại sao không làm cho nó rõ ràng và loại bỏ tất cả các NULL và TRÁI PHIẾU?

Reports
----------
ReportID
ReportName


Recommendations
----------
RecommendationID
RecommendationName
ReportID (foreign key)


Keywords
----------
KeywordID
KeywordName


ReportKeywords
----------
KeywordID (foreign key)
ReportID (foreign key)

RecommendationKeywords
----------
KeywordID (foreign key)
RecommendationID (foreign key)

Trong trường hợp này khi bạn thêm một cái gì đó cần được gắn thẻ, bạn chỉ cần thêm bảng thực thể và bảng liên kết.

Sau đó, kết quả tìm kiếm của bạn trông như thế này (xem vẫn còn lựa chọn loại đang diễn ra và biến chúng thành tổng quát ở cấp kết quả đối tượng nếu bạn muốn một danh sách kết quả duy nhất):

SELECT CAST('REPORT' AS VARCHAR(15)) AS ResultType
    ,Reports.ReportID AS ObjectID
    ,Reports.ReportName AS ObjectName
FROM Keywords
INNER JOIN ReportKeywords
    ON ReportKeywords.KeywordID = Keywords.KeywordID
INNER JOIN Reports
    ON Reports.ReportID = ReportKeywords.ReportID
WHERE Keywords.KeywordName LIKE '%' + @SearchCriteria + '%'
UNION ALL
SELECT 'RECOMMENDATION' AS ResultType
    ,Recommendations.RecommendationID AS ObjectID
    ,Recommendations.RecommendationName AS ObjectName
FROM Keywords
INNER JOIN RecommendationKeywords
    ON RecommendationKeywords.KeywordID = Keywords.KeywordID
INNER JOIN Recommendations
    ON Recommendations.RecommendationID = RecommendationKeywords.ReportID
WHERE Keywords.KeywordName LIKE '%' + @SearchCriteria + '%'

Không có vấn đề gì, ở đâu đó sẽ có sự lựa chọn loại và một số loại phân nhánh đang diễn ra.

Nếu bạn nhìn vào cách bạn sẽ làm điều này trong tùy chọn 1 của mình, nó tương tự nhưng với câu lệnh CASE hoặc LEFT THAM GIA và COALESCE. Khi bạn mở rộng tùy chọn 2 của mình với nhiều thứ được liên kết hơn, bạn phải tiếp tục thêm nhiều TRÁI PHIẾU trong đó mọi thứ thường KHÔNG được tìm thấy (một đối tượng được liên kết chỉ có thể có một bảng dẫn xuất hợp lệ).

Tôi không nghĩ có bất cứ điều gì sai về cơ bản với lựa chọn 2 của bạn và bạn thực sự có thể làm cho nó giống như đề xuất này với việc sử dụng các quan điểm.

Trong tùy chọn 1 của bạn, tôi gặp một số khó khăn khi xem lý do tại sao bạn chọn bảng tri-link.


Bảng ba liên kết mà bạn đề cập có lẽ là kết quả của việc tôi lười biếng về tinh thần ...: P Sau khi đọc các câu trả lời khác nhau, tôi nghĩ cả hai lựa chọn ban đầu của mình đều không có ý nghĩa. Có các bảng ReportKeywords và khuyến nghị riêng biệt có ý nghĩa thiết thực hơn. Tôi đã xem xét khả năng mở rộng, về khả năng có nhiều đối tượng cần từ khóa được áp dụng, nhưng thực tế có lẽ chỉ có một loại đối tượng có thể cần từ khóa.
matikin9

4

Đầu tiên, lưu ý rằng giải pháp lý tưởng phụ thuộc vào mức độ RDBMS mà bạn sử dụng. Tôi sẽ đưa ra cả câu trả lời cụ thể và tiêu chuẩn cụ thể của PostgreSQL.

Chuẩn hóa, trả lời chuẩn

Câu trả lời tiêu chuẩn là có hai bảng tham gia.

Giả sử chúng ta có các bảng:

CREATE TABLE keywords (
     kword text
);

CREATE TABLE reports (
     id serial not null unique,
     ...
);

CREATE TABLE recommendations (
     id serial not null unique,
     ...
);

CREATE TABLE report_keywords (
     report_id int not null references reports(id),
     keyword text not null references keyword(kword),
     primary key (report_id, keyword)
);

CREATE TABLE recommendation_keywords (
     recommendation_id int not null references recommendation(id),
     keyword text not null references keyword(kword),
     primary key (recommendation_id, keyword)
);

Cách tiếp cận này tuân theo tất cả các quy tắc chuẩn hóa tiêu chuẩn và không phá vỡ các nguyên tắc chuẩn hóa cơ sở dữ liệu truyền thống. Nó nên hoạt động trên bất kỳ RDBMS nào.

Trả lời dành riêng cho PostgreSQL, thiết kế N1NF

Đầu tiên, một từ về lý do tại sao PostgreSQL là khác nhau. PostgreSQL hỗ trợ một số cách rất hữu ích để sử dụng các chỉ mục trên các mảng, đáng chú ý nhất là sử dụng các chỉ mục GIN. Chúng có thể mang lại lợi ích hiệu quả khá nhiều nếu được sử dụng đúng cách ở đây. Bởi vì PostgreSQL có thể "tiếp cận" các loại dữ liệu theo cách này, giả định cơ bản về tính nguyên tử và chuẩn hóa có phần khó giải quyết khi áp dụng một cách cứng nhắc ở đây. Vì vậy, vì lý do này, khuyến nghị của tôi là phá vỡ quy tắc nguyên tử của mẫu thông thường đầu tiên và dựa vào các chỉ số GIN để có hiệu suất tốt hơn.

Một lưu ý thứ hai ở đây là trong khi điều này mang lại hiệu suất tốt hơn, nó gây thêm một số vấn đề đau đầu vì bạn sẽ có một số công việc thủ công phải làm để có được tính toàn vẹn tham chiếu để hoạt động tốt. Vì vậy, sự đánh đổi ở đây là hiệu suất cho công việc thủ công.

CREATE TABLE keyword (
    kword text primary key
);

CREATE FUNCTION check_keywords(in_kwords text[]) RETURNS BOOL LANGUAGE SQL AS $$

WITH kwords AS ( SELECT array_agg(kword) as kwords FROM keyword),
     empty AS (SELECT count(*) = 0 AS test FROM unnest($1)) 
SELECT bool_and(val = ANY(kwords.kwords))
  FROM unnest($1) val
 UNION
SELECT test FROM empty WHERE test;
$$;

CREATE TABLE reports (
     id serial not null unique,
     ...
     keywords text[]   
);

CREATE TABLE recommendations (
     id serial not null unique,
     ...
     keywords text[]  
);

Bây giờ chúng tôi phải thêm các kích hoạt để đảm bảo rằng các từ khóa được quản lý đúng cách.

CREATE OR REPLACE FUNCTION trigger_keyword_check() RETURNS TRIGGER
LANGUAGE PLPGSQL AS
$$
BEGIN
    IF check_keywords(new.keywords) THEN RETURN NEW
    ELSE RAISE EXCEPTION 'unknown keyword entered'
    END IF;
END;
$$;

CREATE CONSTRAINT TRIGGER check_keywords AFTER INSERT OR UPDATE TO reports
WHEN (old.keywords <> new.keywords)
FOR EACH ROW EXECUTE PROCEDURE trigger_keyword_check();

CREATE CONSTRAINT TRIGGER check_keywords AFTER INSERT OR UPDATE 
TO recommendations
WHEN (old.keywords <> new.keywords)
FOR EACH ROW EXECUTE PROCEDURE trigger_keyword_check();

Thứ hai, chúng ta phải quyết định phải làm gì khi một từ khóa bị xóa. Vì hiện tại, một từ khóa được xóa khỏi bảng từ khóa sẽ không xếp tầng cho các trường từ khóa. Có lẽ đây là mong muốn và có thể không. Điều đơn giản nhất để làm là chỉ cần hạn chế xóa luôn và mong bạn sẽ tự xử lý trường hợp này nếu nó xuất hiện (sử dụng trình kích hoạt để đảm bảo an toàn tại đây). Một tùy chọn khác có thể là viết lại mọi giá trị từ khóa trong đó từ khóa tồn tại để loại bỏ nó. Một lần nữa kích hoạt sẽ là cách để làm điều đó là tốt.

Ưu điểm lớn của giải pháp này là bạn có thể lập chỉ mục cho việc tra cứu rất nhanh theo từ khóa và bạn có thể kéo tất cả các thẻ mà không cần tham gia. Nhược điểm là loại bỏ một từ khóa là một nỗi đau, và sẽ không hoạt động tốt ngay cả vào một ngày tốt. Điều này có thể được chấp nhận vì đây là một sự kiện hiếm gặp và có thể được giao cho một quá trình nền nhưng nó là một sự đánh đổi đáng để hiểu.

Phê bình giải pháp đầu tiên của bạn

Vấn đề thực sự với giải pháp đầu tiên của bạn là bạn không có khóa nào có thể có trên ObjectKeywords. Do đó, bạn có một vấn đề trong đó bạn không thể đảm bảo rằng mỗi từ khóa được áp dụng cho mỗi đối tượng chỉ một lần.

Giải pháp thứ hai của bạn tốt hơn một chút. Nếu bạn không thích các giải pháp khác được cung cấp, tôi khuyên bạn nên đi với nó. Tuy nhiên tôi sẽ đề nghị loại bỏ keyword_id và chỉ tham gia vào văn bản từ khóa. Điều đó giúp loại bỏ một tham gia mà không chuẩn hóa.


Tôi đang sử dụng MS SQL Server cho dự án này, nhưng cảm ơn về thông tin trên PostgreSQL. Các điểm khác mà bạn đưa ra về việc xóa và đảm bảo các cặp từ khóa đối tượng chỉ xảy ra một lần. Ngay cả khi tôi có khóa cho từng cặp từ khóa đối tượng, tôi vẫn không phải kiểm tra trước khi chèn chứ? Đối với việc có một id từ khóa riêng ... Tôi đọc rằng đối với SQL Server, việc có một chuỗi dài có thể làm giảm hiệu suất và có lẽ tôi sẽ phải cho phép người dùng nhập "cụm từ khóa" thay vì chỉ "từ khóa" ".
matikin9

0

Tôi muốn đề xuất hai cấu trúc riêng biệt:

báo cáo
---------------
  ID báo cáo
  ID từ khóa

khuyến nghị_keywords
-----------------------
  đề nghị
  keyword_id

Bằng cách này, bạn không có tất cả các id thực thể có thể có trong cùng một bảng (không có khả năng mở rộng và có thể gây nhầm lẫn) và bạn không có một bảng có "id đối tượng" chung chung mà bạn phải phân tán ở một nơi khác sử dụng base_objectbảng, sẽ làm việc, nhưng tôi nghĩ rằng quá phức tạp trong thiết kế.


Tôi không đồng ý rằng những gì bạn đang đề xuất là một lựa chọn khả thi nhưng tại sao RI không thể được thi hành với thiết kế B của OP? (Tôi cho rằng đó là những gì bạn đang nói).
ypercubeᵀᴹ

@ypercube: Tôi nghĩ rằng tôi đã bỏ lỡ BaseObjectsbảng trong lần đọc đầu tiên của mình và nghĩ rằng tôi đang xem một mô tả cho một bảng object_idcó thể trỏ đến một ID trong bất kỳ bảng nào .
Thất vọngWithFormsDesigner

-1

Theo kinh nghiệm của tôi đây là những gì bạn có thể làm.

Reports
----------
Report_id (primary_key)
Report_name

Recommendations
----------------
Recommendation_id (primary key)
Recommendation_name
Report_id (foreign key)

Keywords
----------
Keyword_id (primary key)
Keyword

Và đối với mối quan hệ giữa các từ khóa, báo cáo và đề xuất, bạn có thể thực hiện một trong hai tùy chọn: Tùy chọn A:

Recommendation_keywords
------------------------
Recommendation_id(foreign_key)
keyword_id (foreign_key)

Điều này cho phép mối quan hệ trực tiếp từ Báo cáo đến Khuyến nghị, với Từ khóa và cuối cùng là Từ khóa. Tùy chọn B:

object_keywords
---------------
Object_id
Object_type
Keyword_id(foreign_key)

Tùy chọn A dễ áp ​​dụng và quản lý hơn vì nó sẽ có các cấu trúc cơ sở dữ liệu để xử lý tính toàn vẹn dữ liệu và sẽ không cho phép chèn dữ liệu không hợp lệ.

Tùy chọn B mặc dù yêu cầu công việc nhiều hơn một chút vì bạn sẽ cần mã hóa nhận dạng mối quan hệ. Về lâu dài sẽ linh hoạt hơn, nếu trong một thời điểm nào đó trong tương lai bạn cần thêm từ khóa vào một mục khác ngoài báo cáo hoặc đề xuất bạn chỉ cần thêm nhận dạng và sử dụng trực tiếp vào bảng.


Hãy để tôi giải thích lý do tại sao tôi đánh giá thấp: 1. Không rõ liệu bạn có ủng hộ lựa chọn A, B hoặc cách tiếp cận thứ 3 không. Dường như (với tôi) rằng bạn nói rằng cả hai đều ít nhiều OK (với điều đó tôi không đồng ý vì A có một số vấn đề mà những người khác đã nêu ra với câu trả lời của họ. 2. Bạn có đề xuất cải tiến thiết kế của A (hoặc B) không ? Điều đó cũng không rõ ràng. Sẽ rất tốt nếu các FK được xác định rõ ràng, hoàn toàn không rõ ràng những gì bạn đang đề xuất. Tổng cộng tôi thích câu trả lời làm rõ mọi thứ và tùy chọn cho bất kỳ khách truy cập nào trong tương lai. Vui lòng thử chỉnh sửa câu trả lời của bạn và Tôi sẽ đảo ngược phiếu bầu của tôi.
ypercubeᵀᴹ
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.