タグ付けのためのデータベース設計


171

次のタグ付け機能をサポートするデータベースをどのように設計しますか?

  • アイテムには多数のタグを付けることができます
  • 特定のタグセットでタグ付けされたすべてのアイテムの検索は高速である必要があります(アイテムにはすべてのタグが必要であるため、OR検索ではなくAND検索です)。
  • アイテムの作成/書き込みは、迅速な検索/読み取りを可能にするために遅くなる可能性があります

理想的には、(少なくとも)n個の特定のタグのセットでタグ付けされたすべてのアイテムのルックアップは、単一のSQLステートメントを使用して実行する必要があります。検索するタグの数やアイテムのタグの数は不明であり、数が多い場合があるため、JOINを使用することは現実的ではありません。

何か案は?


これまでのすべての答えをありがとう。

しかし、私が間違っていない場合、与えられた答えはタグでOR検索を行う方法を示しています。(1つ以上のnタグを持つすべてのアイテムを選択します)。効率的なAND検索を探しています。(すべてn個のタグを持つアイテムをすべて選択します。

回答:


22

ANDingについて:「リレーショナル除算」演算を探しているようです。この記事では、リレーショナルの分割について簡潔かつわかりやすい方法で説明します。

パフォーマンスについて:ビットマップベースのアプローチは、状況に適しているように直感的に聞こえます。ただし、digiguruが示唆するように、ビットマップインデックスを「手動で」実装することは良い考えだとは思いません。新しいタグが追加されるたびに複雑な状況のように聞こえます(?)しかし、一部のDBMS(Oracleを含む)は、何らかの形でビットマップインデックスを提供していますビルトインインデックスシステムがインデックスのメンテナンスの潜在的な複雑さを排除するので、役に立ちます。さらに、ビットマップインデックスを提供するDBMSは、クエリプランを実行するときにそれらを適切に検討できる必要があります。


4
データベースのビットフィールドタイプを使用すると、特定のビット数に制限されるため、答えは少し先見の明があると言わざるを得ません。これは、各アイテムが特定の数のタグに制限されていることを意味するのではなく、システム全体で特定の数の一意のタグしか存在できないことを意味します(通常は32または64まで)。
Mark Renouf、

1
3nf実装(Question、Tag、Question_has_Tag)、およびQuestion_has_TagのTag_idのビットマップインデックスを想定すると、質問にタグが追加または削除されるたびにビットマップインデックスを再構築する必要があります。ようなクエリ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)右Bツリーインデックスが中間テーブルに存在すると仮定外細かいスケールであるべきである
アダムMusch

「この記事」リンクは死んでいます。私はそれを読みたいと思います:(
mpen

3
マーク:これは見栄えがいい:simple-talk.com/sql/t-sql-programming/…これはおそらく、私が言及したものの再発行されたバージョンでしょう。
Troels Arvin

記事のURLが無効になっている
Sebastien H.

77

データベーススキーマのタグ付けに関する優れた記事を次に示します。

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

パフォーマンステストとともに:

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

MySQLに非常に特有の結論があることに注意してください(少なくとも2005年に書かれた時点では)全文索引付けの特性は非常に貧弱でした。


1
また、SOを使用してタグ付けシステムを実装する方法について、より詳細な技術的洞察を得たいと思いますか?ポッドキャストで、すべてのタグをすべての質問の列に保持し、その場でシリアル化/非シリアル化すると言ったと思いますか?私はそれについてもっと知りたいと思います、そしておそらくいくつかのコードスニペットを見てください。私は周りを見回して詳細を見つけましたが、METAで質問する前にすでにこれを行っているリンクはありますか?
Marston A.

5
Metaに関するこの質問には、SOスキーマに関するいくつかの情報があります。meta.stackexchange.com
Barrett

元のリンクは死んでいたが、新しい場所を見つけたと思う。これらがあなたが参照していた記事であることを確認したい場合があります。
ブラッドラーソン

12
@Jeffによって書かれたにもかかわらず、これは依然として本質的にリンクのみの回答です。
curiousdannii

13

簡単な解決策で問題が発生しない:アイテムのテーブル、タグのテーブル、「タグ付け」のクロステーブル

クロステーブルのインデックスは十分な最適化である必要があります。適切なアイテムの選択は

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

ANDタグ付けは

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 ...

これは確かに、多数の比較タグではそれほど効率的ではありません。メモリ内のタグ数を維持する場合は、クエリを頻繁に行わないタグで開始するようにすると、ANDシーケンスがより迅速に評価されます。一致するタグの予想数といずれか1つに一致する可能性に応じて、これは問題のない解決策です。20個のタグを一致させ、一部のランダムアイテムがそれらの15個と一致すると予想される場合、これは依然として重いでしょう。データベース上。


13

@Jeff Atwoodがリンクしている記事(http://howto.philippkeller.com/2005/04/24/Tags-Database-schemas/)が非常に詳細であることを強調したかっただけです(3つの異なるスキーマのメリットについて説明しています)アプローチ)そして、通常これまでにここで述べたものよりもパフォーマンスが優れているANDクエリに対する優れたソリューションがあります(つまり、各用語に相関サブクエリを使用しません)。また、コメントにはたくさんの良いものがあります。

ps-誰もがここで話しているアプローチは、記事では「Toxi」ソリューションと呼ばれています。


3
私はそのすばらしい記事を読んだことを覚えていますが、残念ながらリンクは今死んでいます。:(誰もがそのミラーを知っていますか?
localhost '26

5
リンクは死んでいた:<
アーロン

6

Java Content Repositoryの実装(Apache Jackrabbitなど)のような厳密にはデータベースではないソリューションを試して、Apache Luceneのようにその上に構築された検索エンジンを使用することもできます。

適切なキャッシングメカニズムを備えたこのソリューションは、自社開発のソリューションよりもパフォーマンスが向上する可能性があります。

ただし、小規模または中規模のアプリケーションでは、以前の投稿で説明した正規化されたデータベースよりも高度な実装が必要になるとは思いません。

編集:あなたの説明で、検索エンジンでJCRのようなソリューションを使用することはより説得力があるようです。これにより、長期的にはプログラムが大幅に簡略化されます。


5

最も簡単な方法は、タグテーブルを作成することです。
Target_Type-複数のテーブルにタグを付ける場合-タグ付け
Targetされるレコードのキー
Tag- タグのテキスト

データのクエリは次のようになります。

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

更新
AND条件への要件に基づいて、上記のクエリは次のようになります

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

@Zizzencsの2番目の提案として、完全に(R)DB中心ではない何かが必要になる可能性がある

どういうわけか、私は、プレーンなnvarcharフィールドを使用して、適切なキャッシュ/インデックス付けでそのタグを保存すると、より高速な結果が得られると思います。しかし、それは私だけです。

私は以前に多対多の関係を表すために3つのテーブルを使用してタグ付けシステムを実装しましたが(Item Tags ItemTags)、多くの場所でタグを扱うことになると思いますが、3つのテーブルで常に同時に操作/クエリされると、コードが間違いなく複雑になります。

追加された複雑さがそれに見合うかどうかを検討する必要があります。


0

結合を回避できず、多少正規化されます。

私のアプローチは、タグテーブルを持つことです。

 TagId (PK)| TagName (Indexed)

次に、itemsテーブルにTagXREFID列があります。

このTagXREFID列は、3番目のテーブルへのFKです。これをTagXREFと呼びます。

 TagXrefID | ItemID | TagId

したがって、アイテムのすべてのタグを取得するには、次のようにします。

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

タグのすべてのアイテムを取得するには、次のようなものを使用します。

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

一連のタグをANDで結合するには、上記のステートメントを少し変更して、AND Tags.TagName = @ TagName1 AND Tags.TagName = @ TagName2などを追加し、クエリを動的に作成します。


0

私がやりたいのは、生データを表すテーブルがいくつかあるため、この場合は

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

これは書き込み時間に対して高速に動作し、すべてを正規化しますが、各タグについて、さらにタグを付けるたびにテーブルを2回結合する必要があることにも注意してください。そのため、読み取りが遅くなります。

読み取りを改善するソリューションは、フラット化された形式でデータを表す新しいテーブルを本質的に作成するストアドプロシージャを設定して、コマンドでキャッシングテーブルを作成することです...

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

次に、タグ付けされたアイテムテーブルを最新の状態に保つ必要がある頻度を検討します。すべての挿入時であれば、カーソル挿入イベントでストアドプロシージャを呼び出します。1時間ごとのタスクの場合は、1時間ごとのジョブをセットアップして実行します。

ここで、データ検索を本当に賢くするために、タグからデータを取得するためのストアード・プロシージャーを作成する必要があります。大規模なcaseステートメントでネストされたクエリを使用するのではなく、データベースから選択するタグのリストを含む単一のパラメーターを渡して、アイテムのレコードセットを返します。これは、ビットごとの演算子を使用するバイナリ形式で最適です。

バイナリ形式で説明するのは簡単です。アイテムに割り当てられるタグが4つあるとしましょう。バイナリで表すと、

0000

4つのタグすべてがオブジェクトに割り当てられている場合、オブジェクトは次のようになります...

1111

最初の2つだけの場合...

1100

次に、必要な列で1と0のバイナリ値を見つけるだけの場合です。SQL Serverのビット演算子を使用すると、非常に単純なクエリを使用して、列の最初に1があることを確認できます。

詳細については、このリンクを確認してください


0

他の人が言ったことを言い換えると、トリックはスキーマではなく、クエリにあります

エンティティ/ラベル/タグの素朴なスキーマは正しい方法です。しかし、あなたが見てきたように、多くのタグでANDクエリを実行する方法はすぐにはわかりません。

そのクエリを最適化する最良の方法はプラットフォームに依存するため、RDBSで質問にタグを付け直し、タイトルを「タグ付けデータベースでANDクエリを実行する最適な方法」などに変更することをお勧めします。

MS SQLに関するいくつかの提案がありますが、それが使用しているプラ​​ットフォームでない場合は控えます。


6
この問題の領域で作業しようとする他の人々が実際にその技術を使用していて利益を得る可能性があるため、特定の技術について少しだけ情報提供を控えるべきではないでしょう。
ブライアンレーバイン2008

0

上記の回答のバリエーションは、タグIDを取得し、それらを並べ替え、^で区切られた文字列として結合し、ハッシュすることです。次に、ハッシュをアイテムに関連付けるだけです。タグの組み合わせごとに新しいキーが生成されます。AND検索を行うには、指定されたタグIDでハッシュを再作成して検索します。アイテムのタグを変更すると、ハッシュが再作成されます。同じタグセットを持つアイテムは、同じハッシュキーを共有します。


4
このアプローチでは、まったく同じタグのセットを持つエントリのみを検索できます。これは常に簡単です。私の元の質問では、私がクエリしたすべてのタグ、そしておそらくそれ以上のタグを持つエントリを見つけたいと思っています。
Christian Berg

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.