Thiết kế cơ sở dữ liệu để gắn thẻ


171

Làm thế nào bạn sẽ thiết kế một cơ sở dữ liệu để hỗ trợ các tính năng gắn thẻ sau:

  • các mục có thể có một số lượng lớn các thẻ
  • tìm kiếm cho tất cả các mục được gắn thẻ với một bộ thẻ nhất định phải nhanh chóng (các mục phải có TẤT CẢ các thẻ, vì vậy đó là tìm kiếm AND, không phải tìm kiếm OR)
  • việc tạo / ghi các mục có thể chậm hơn để cho phép tra cứu / đọc nhanh

Lý tưởng nhất là việc tra cứu tất cả các mục được gắn thẻ (ít nhất) một bộ n thẻ đã cho nên được thực hiện bằng một câu lệnh SQL. Vì số lượng thẻ để tìm kiếm cũng như số lượng thẻ trên bất kỳ mục nào là không xác định và có thể cao, sử dụng THAM GIA là không thực tế.

Có ý kiến ​​gì không?


Cảm ơn tất cả các câu trả lời cho đến nay.

Tuy nhiên, nếu tôi không nhầm, các câu trả lời đã cho biết cách thực hiện tìm kiếm OR trên thẻ. (Chọn tất cả các mục có một hoặc nhiều thẻ n). Tôi đang tìm kiếm một tìm kiếm VÀ hiệu quả. (Chọn tất cả các mục có TẤT CẢ các thẻ n - và có thể hơn thế nữa.)

Câu trả lời:


22

Về ANDing: Có vẻ như bạn đang tìm kiếm hoạt động "phân chia quan hệ". Bài viết này bao gồm phân chia quan hệ theo cách ngắn gọn và dễ hiểu.

Về hiệu suất: Một cách tiếp cận dựa trên bitmap nghe có vẻ trực quan sẽ phù hợp với tình huống này. Tuy nhiên, tôi không tin rằng nên thực hiện lập chỉ mục bitmap một cách "thủ công", như digiguru gợi ý: Nghe có vẻ như là một tình huống phức tạp mỗi khi các thẻ mới được thêm vào (?) Nhưng một số DBMS (bao gồm cả Oracle) cung cấp các chỉ mục bitmap có thể bằng cách nào đó được sử dụng, bởi vì một hệ thống lập chỉ mục tích hợp sẽ loại bỏ sự phức tạp tiềm tàng của bảo trì chỉ mục; ngoài ra, một DBMS cung cấp các chỉ mục bitmap sẽ có thể xem xét chúng một cách phù hợp khi thực hiện kế hoạch truy vấn.


4
Tôi phải nói rằng câu trả lời hơi ngắn gọn, bởi vì việc sử dụng một loại trường bit của cơ sở dữ liệu giới hạn bạn đến một số bit cụ thể. Điều này không có nghĩa là mỗi mục bị giới hạn ở một số lượng thẻ nhất định, nhưng chỉ có thể có một số lượng thẻ duy nhất trong toàn hệ thống (thường lên tới 32 hoặc 64).
Đánh dấu Renouf

1
Giả sử triển khai 3nf (Câu hỏi, Thẻ, Câu hỏi_has_Tag) và chỉ mục bitmap trên Tag_id trong Câu hỏi_has_Tag, chỉ mục bitmap phải được xây dựng lại mỗi khi câu hỏi có thẻ được thêm hoặc xóa. Một truy vấn như thế select * from question q inner join question_has_tag qt where tag_id in (select tag_id from tags where (what we want) minus select tag_id from tags where (what we don't)sẽ ổn và mở rộng ra giả sử các chỉ số cây b phải tồn tại trên bảng giữa
Adam Musch

Liên kết "Bài viết này" đã chết. Tôi rất muốn đọc rằng :(
mpen

3
Mark: Cái này có vẻ tốt: Simple-talk.com/sql/t-sql-programming/ Khăn Đây có lẽ là phiên bản được xuất bản lại của cái tôi đã đề cập.
Quân đội Arvin

URL của bài viết không còn hợp lệ nữa
Sebastien H.

77

Đây là một bài viết tốt về gắn thẻ lược đồ cơ sở dữ liệu:

http://howto.philippkeller.com/2005/04/24/Tags-Database-schemas/

cùng với các bài kiểm tra hiệu suất:

http://howto.philippkeller.com/2005/06/19/Tagsystems-performance-tests/

Lưu ý rằng các kết luận có rất cụ thể đối với MySQL, mà (ít nhất là vào năm 2005 tại thời điểm được viết) có các đặc điểm lập chỉ mục toàn văn bản rất kém.


1
Tôi cũng muốn có cái nhìn sâu sắc kỹ thuật chi tiết hơn về cách bạn triển khai hệ thống gắn thẻ với SO? Tôi nghĩ trên một podcast bạn nói rằng bạn giữ tất cả các thẻ trong một cột với mỗi câu hỏi và sau đó tuần tự hóa / khử nối tiếp chúng một cách nhanh chóng? Tôi rất muốn biết thêm về nó và có thể thấy một số đoạn mã. Tôi đã tìm kiếm xung quanh và tìm thấy bất kỳ chi tiết nào, có liên kết nào bạn đã thực hiện việc này trước khi tôi đặt câu hỏi trên META không?
Marston A.

5
Câu hỏi này trên Meta có một số thông tin về lược đồ SO: meta.stackexchange.com/questions/1863/so-database-schema
Barrett

Các liên kết ban đầu đã chết, nhưng tôi nghĩ rằng tôi đã tìm thấy vị trí mới của họ. Bạn có thể muốn xác minh rằng đây là những bài viết mà bạn đang đề cập đến.
Brad Larson

12
Mặc dù được viết bởi @Jeff, đây thực chất vẫn chỉ là một câu trả lời liên kết.
tò mò

13

Tôi không thấy vấn đề với một giải pháp đơn giản: Bảng cho các mục, bảng cho các thẻ, ổn định cho "gắn thẻ"

Các chỉ số trên bảng chéo phải đủ tối ưu hóa. Chọn các mục thích hợp sẽ là

SELECT * FROM items WHERE id IN  
    (SELECT DISTINCT item_id FROM item_tag WHERE  
    tag_id = tag1 OR tag_id = tag2 OR ...)  

VÀ gắn thẻ sẽ là

SELECT * FROM items WHERE  
    EXISTS (SELECT 1 FROM item_tag WHERE id = item_id AND tag_id = tag1)  
    AND EXISTS (SELECT 1 FROM item_tag WHERE id = item_id AND tag_id = tag2)  
    AND ...

được thừa nhận, không hiệu quả đối với số lượng lớn các thẻ so sánh. Nếu bạn muốn duy trì số lượng thẻ trong bộ nhớ, bạn có thể thực hiện truy vấn để bắt đầu với các thẻ không thường xuyên, do đó trình tự AND sẽ được đánh giá nhanh hơn. Tùy thuộc vào số lượng thẻ dự kiến ​​được so khớp và kỳ vọng phù hợp với bất kỳ thẻ nào trong số chúng, đây có thể là giải pháp OK, nếu bạn khớp 20 thẻ và hy vọng rằng một số mục ngẫu nhiên sẽ khớp với 15 thẻ, thì điều này vẫn còn nặng trên cơ sở dữ liệu.


13

Tôi chỉ muốn nhấn mạnh rằng bài viết mà @Jeff Atwood liên kết đến ( http://howto.philippkeller.com/2005/04/24/Tags-Database-schemas/ ) là rất kỹ lưỡng (Nó thảo luận về giá trị của 3 lược đồ khác nhau cách tiếp cận) và có một giải pháp tốt cho các truy vấn AND thường sẽ hoạt động tốt hơn những gì đã được đề cập ở đây cho đến nay (nghĩa là nó không sử dụng truy vấn con tương quan cho mỗi thuật ngữ). Cũng rất nhiều thứ tốt trong các ý kiến.

ps - Cách tiếp cận mà mọi người đang nói đến ở đây được gọi là giải pháp "Toxi" trong bài viết.


3
Tôi nhớ đã đọc bài viết tuyệt vời đó, nhưng tiếc là bây giờ liên kết đã chết. :( Có ai biết về một tấm gương của nó không?
localhost

5
liên kết đã chết: <
Aaron

6

Bạn có thể muốn thử nghiệm một giải pháp cơ sở dữ liệu không nghiêm ngặt như triển khai Kho lưu trữ nội dung Java (ví dụ: Jackrabbit Apache ) và sử dụng một công cụ tìm kiếm được xây dựng trên đó như Apache Lucene .

Giải pháp này với các cơ chế lưu trữ phù hợp có thể mang lại hiệu suất tốt hơn so với giải pháp trồng tại nhà.

Tuy nhiên, tôi thực sự không nghĩ rằng trong một ứng dụng nhỏ hoặc vừa, bạn sẽ yêu cầu triển khai tinh vi hơn cơ sở dữ liệu chuẩn hóa được đề cập trong các bài đăng trước đó.

EDIT: với sự làm rõ của bạn, có vẻ hấp dẫn hơn khi sử dụng giải pháp giống như JCR với công cụ tìm kiếm. Điều đó sẽ đơn giản hóa rất nhiều chương trình của bạn về lâu dài.


5

Phương pháp đơn giản nhất là tạo bảng thẻ .
Target_Type- trong trường hợp bạn đang gắn thẻ nhiều bảng
Target- Khóa cho bản ghi được gắn thẻ
Tag- Văn bản của thẻ

Truy vấn dữ liệu sẽ giống như:

Select distinct target from tags   
where tag in ([your list of tags to search for here])  
and target_type = [the table you're searching]

CẬP NHẬT
Dựa trên yêu cầu VÀ điều kiện của bạn, truy vấn trên sẽ biến thành một cái gì đó như thế này

select target
from (
  select target, count(*) cnt 
  from tags   
  where tag in ([your list of tags to search for here])
    and target_type = [the table you're searching]
)
where cnt = [number of tags being searched]

1

Tôi muốn đề xuất @Zizzencs thứ hai rằng bạn có thể muốn một cái gì đó không hoàn toàn (R) DB-centric

Bằng cách nào đó, tôi tin rằng việc sử dụng các trường nvarchar đơn giản để lưu trữ các thẻ đó với một số bộ đệm / lập chỉ mục phù hợp có thể mang lại kết quả nhanh hơn. Nhưng đó chỉ là tôi.

Tôi đã triển khai hệ thống gắn thẻ bằng cách sử dụng 3 bảng để thể hiện mối quan hệ Nhiều-nhiều trước đó (Thẻ Mục ItemTags), nhưng tôi cho rằng bạn sẽ xử lý các thẻ ở nhiều nơi, tôi có thể nói với bạn rằng với 3 bảng phải được thao tác / truy vấn đồng thời mọi lúc chắc chắn sẽ làm cho mã của bạn phức tạp hơn.

Bạn có thể muốn xem xét nếu sự phức tạp thêm vào là xứng đáng.


0

Bạn sẽ không thể tránh tham gia và vẫn được bình thường hóa.

Cách tiếp cận của tôi là có Bảng Tag.

 TagId (PK)| TagName (Indexed)

Sau đó, bạn có một cột TagXREFID trong bảng mục của bạn.

Cột TagXREFID này là FK đến bảng thứ 3, tôi sẽ gọi nó là TagXREF:

 TagXrefID | ItemID | TagId

Vì vậy, để có được tất cả các thẻ cho một mục sẽ là một cái gì đó như:

SELECT Tags.TagId,Tags.TagName 
     FROM Tags,TagXref 
     WHERE TagXref.TagId = Tags.TagId 
         AND TagXref.ItemID = @ItemID

Và để có được tất cả các mục cho một thẻ, tôi sẽ sử dụng một cái gì đó như thế này:

SELECT * FROM Items, TagXref
     WHERE TagXref.TagId IN 
          ( SELECT Tags.TagId FROM Tags
                WHERE Tags.TagName = @TagName; )
     AND Items.ItemId = TagXref.ItemId;

Để VÀ một loạt các thẻ cùng nhau, Bạn sẽ sửa đổi câu lệnh trên một chút để thêm AND Tags.TagName = @ TagName1 AND Tags.TagName = @ TagName2, v.v ... và tự động xây dựng truy vấn.


0

Những gì tôi muốn làm là có một số bảng biểu thị dữ liệu thô, vì vậy trong trường hợp này bạn có

Items (ID pk, Name, <properties>)
Tags (ID pk, Name)
TagItems (TagID fk, ItemID fk)

Điều này hoạt động nhanh trong thời gian viết và giữ mọi thứ được bình thường hóa, nhưng bạn cũng có thể lưu ý rằng với mỗi thẻ, bạn sẽ cần tham gia các bảng hai lần cho mỗi thẻ khác mà bạn muốn VÀ, vì vậy nó sẽ bị đọc chậm.

Một giải pháp để cải thiện khả năng đọc là tạo một bảng bộ đệm theo lệnh bằng cách thiết lập một thủ tục được lưu trữ về cơ bản tạo ra bảng mới biểu thị dữ liệu theo định dạng phẳng ...

CachedTagItems(ID, Name, <properties>, tag1, tag2, ... tagN)

Sau đó, bạn có thể xem xét mức độ thường xuyên mà bảng Mục được gắn thẻ cần được cập nhật, nếu nó trên mỗi lần chèn, sau đó gọi thủ tục được lưu trữ trong một sự kiện chèn con trỏ. Nếu đó là một nhiệm vụ hàng giờ, thì hãy thiết lập một công việc hàng giờ để chạy nó.

Bây giờ để thực sự thông minh trong việc truy xuất dữ liệu, bạn sẽ muốn tạo một quy trình được lưu trữ để lấy dữ liệu từ các thẻ. Thay vì sử dụng các truy vấn lồng nhau trong một câu lệnh tình huống lớn, bạn muốn chuyển vào một tham số duy nhất chứa danh sách các thẻ bạn muốn chọn từ cơ sở dữ liệu và trả về một tập hợp các mục. Điều này sẽ là tốt nhất trong định dạng nhị phân, sử dụng các toán tử bitwise.

Trong định dạng nhị phân, nó rất dễ để giải thích. Giả sử có bốn thẻ được gán cho một mục, trong nhị phân, chúng ta có thể đại diện cho điều đó

0000

Nếu tất cả bốn thẻ được gán cho một đối tượng, đối tượng sẽ trông như thế này ...

1111

Nếu chỉ là hai ...

1100

Sau đó, đây chỉ là trường hợp tìm các giá trị nhị phân có 1 và số 0 trong cột bạn muốn. Sử dụng các toán tử Bitwise của SQL Server, bạn có thể kiểm tra xem có 1 trong các cột đầu tiên sử dụng các truy vấn rất đơn giản.

Kiểm tra liên kết này để tìm hiểu thêm .


0

Để diễn giải những gì người khác đã nói: mẹo không nằm trong lược đồ , đó là trong truy vấn .

Lược đồ ngây thơ của Thực thể / Nhãn / Thẻ là cách phù hợp. Nhưng như bạn đã thấy, không rõ ngay cách thực hiện truy vấn AND với nhiều thẻ.

Cách tốt nhất để tối ưu hóa truy vấn đó sẽ phụ thuộc vào nền tảng, vì vậy tôi khuyên bạn nên gắn thẻ lại câu hỏi của mình với RDBS và thay đổi tiêu đề thành một cái gì đó như "Cách tối ưu để thực hiện AND truy vấn trên cơ sở dữ liệu gắn thẻ".

Tôi có một vài gợi ý cho MS SQL, nhưng sẽ kiềm chế trong trường hợp đó không phải là nền tảng bạn đang sử dụng.


6
Có lẽ bạn không nên tiết lộ thông tin về một công nghệ nhất định vì những người khác đang cố gắng làm việc trong lĩnh vực có vấn đề này thực sự có thể đang sử dụng công nghệ đó và sẽ có lợi.
Bryan Rehbein

0

Một biến thể cho câu trả lời ở trên là lấy id thẻ, sắp xếp chúng, kết hợp thành một chuỗi ^ tách và băm chúng. Sau đó, chỉ cần liên kết băm với các mục. Mỗi sự kết hợp của các thẻ tạo ra một khóa mới. Để thực hiện tìm kiếm AND chỉ cần tạo lại hàm băm với id thẻ đã cho và tìm kiếm. Thay đổi thẻ trên một mục sẽ khiến băm được tạo lại. Các mục có cùng bộ thẻ chia sẻ cùng một khóa băm.


4
Với phương pháp này, bạn chỉ có thể tìm kiếm các mục có cùng một bộ thẻ chính xác - điều đó luôn tầm thường. Trong câu hỏi ban đầu của tôi, tôi muốn tìm các mục có tất cả các thẻ tôi truy vấn và có thể nhiều hơn.
Christian Berg

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.