タグシステムの実装方法


90

SOで使用されているようなタグシステムを実装するのが最善の方法は何だろうと思っていました。私はこれを考えていましたが、優れたスケーラブルなソリューションを思い付くことができません。

私は基本的な3つのテーブルソリューションを持つことを考えていました:tagsテーブル、articlesテーブル、そしてテーブルを持つことtag_to_articles

これはこの問題の最善の解決策ですか、それとも代替手段がありますか?この方法を使用すると、テーブルは時間の経過とともに非常に大きくなり、検索にはあまり効率的ではないと思います。一方、クエリが高速に実行されることはそれほど重要ではありません。


回答:


119

このブログ投稿がおもしろいと思います:タグ:データベーススキーマ

問題:ブックマーク(またはブログ投稿など)に必要な数のタグを付けることができるデータベーススキーマが必要です。その後、クエリを実行して、ブックマークをタグの和集合または共通部分に制限します。また、検索結果から一部のタグを除外する(たとえば、マイナスにする)必要があります。

「MySQLicious」ソリューション

このソリューションでは、スキーマにテーブルが1つだけあり、非正規化されています。このタイプは、MySQLiciousがdel.icio.usデータをこの構造のテーブルにインポートするため、「MySQLiciousソリューション」と呼ばれます。

ここに画像の説明を入力してくださいここに画像の説明を入力してください

「search + webservice + semweb」の交差(AND)クエリ:

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags LIKE "%semweb%"

「search | webservice | semweb」のユニオン(OR)クエリ:

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
OR tags LIKE "%webservice%"
OR tags LIKE "%semweb%"

「search + webservice-semweb」のマイナスクエリ

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags NOT LIKE "%semweb%"

「Scuttle」ソリューション

Scuttleは、データを2つのテーブルに整理します。そのテーブル「scCategories」は「タグ」テーブルであり、「ブックマーク」テーブルへの外部キーを持っています。

ここに画像の説明を入力してください

「bookmark + webservice + semweb」の交差点(AND)クエリ:

SELECT b.*
FROM scBookmarks b, scCategories c
WHERE c.bId = b.bId
AND (c.category IN ('bookmark', 'webservice', 'semweb'))
GROUP BY b.bId
HAVING COUNT( b.bId )=3

最初に、すべてのブックマークとタグの組み合わせが検索されます。タグは「bookmark」、「webservice」、または「semweb」(c.category IN( 'bookmark'、 'webservice'、 'semweb'))であり、次にそのブックマークのみが検索されます。検索された3つのタグすべてが考慮されます(HAVING COUNT(b.bId)= 3)。

「bookmark | webservice | semweb」のユニオン(OR)クエリ: HAVING句を省略するだけで、ユニオンができます。

SELECT b.*
FROM scBookmarks b, scCategories c
WHERE c.bId = b.bId
AND (c.category IN ('bookmark', 'webservice', 'semweb'))
GROUP BY b.bId

マイナス(除外)「bookmark + webservice-semweb」のクエリ。つまり、ブックマークとWebサービスであり、semwebではありません。

SELECT b. *
FROM scBookmarks b, scCategories c
WHERE b.bId = c.bId
AND (c.category IN ('bookmark', 'webservice'))
AND b.bId NOT
IN (SELECT b.bId FROM scBookmarks b, scCategories c WHERE b.bId = c.bId AND c.category = 'semweb')
GROUP BY b.bId
HAVING COUNT( b.bId ) =2

HAVING COUNTを省略すると、「bookmark | webservice-semweb」のクエリが表示されます。


「Toxi」ソリューション

Toxiは3つのテーブル構造を考え出しました。テーブル「タグマップ」を介して、ブックマークとタグはn-to-mに関連しています。各タグは、異なるブックマークと一緒に使用でき、その逆も可能です。このDBスキーマはwordpressでも使用されます。クエリは「scuttle」ソリューションの場合とまったく同じです。

ここに画像の説明を入力してください

「bookmark + webservice + semweb」の交差点(AND)クエリ

SELECT b.*
FROM tagmap bt, bookmark b, tag t
WHERE bt.tag_id = t.tag_id
AND (t.name IN ('bookmark', 'webservice', 'semweb'))
AND b.id = bt.bookmark_id
GROUP BY b.id
HAVING COUNT( b.id )=3

Union(OR)「bookmark | webservice | semweb」のクエリ

SELECT b.*
FROM tagmap bt, bookmark b, tag t
WHERE bt.tag_id = t.tag_id
AND (t.name IN ('bookmark', 'webservice', 'semweb'))
AND b.id = bt.bookmark_id
GROUP BY b.id

マイナス(除外)「bookmark + webservice-semweb」のクエリ。つまり、ブックマークとWebサービスであり、semwebではありません。

SELECT b. *
FROM bookmark b, tagmap bt, tag t
WHERE b.id = bt.bookmark_id
AND bt.tag_id = t.tag_id
AND (t.name IN ('Programming', 'Algorithms'))
AND b.id NOT IN (SELECT b.id FROM bookmark b, tagmap bt, tag t WHERE b.id = bt.bookmark_id AND bt.tag_id = t.tag_id AND t.name = 'Python')
GROUP BY b.id
HAVING COUNT( b.id ) =2

HAVING COUNTを省略すると、「bookmark | webservice-semweb」のクエリが表示されます。


3
そのブログ投稿の作者はこちら。ブログはChromeによってブロックされなくなりました(愚かなワードプレスの脆弱性、現在tumblrに移動されました)。それを
マークダウンに

こんにちは@Philipp。OK、私の答えを編集しました。ところで、データベースタグシステムに関する素晴らしい投稿に感謝します。
Nick Dandoulakis 2013年

1
注:「bookmark」と「webservice」を検索したときにToxiソリューションの交差クエリでもブックマークを表示する場合は、「HAVING COUNT(b.id)= 3」をから変更する必要があります。 3から「sizeof(array( 'bookmark'、 'webservice'))」。これを動的タグクエリ関数として使用することを計画している場合は、ほんの少しの詳細です。
toxicate 2013

3
投稿に記載されているさまざまなソリューションのパフォーマンス比較のためのリンクはありますか?
kampta 2016

@kampta、いいえ、リンクはありません。
Nick Dandoulakis 2016

8

3つのテーブルのソリューションに問題はありません。

もう1つのオプションは、記事に適用できるタグの数を制限し(SOの5など)、それらを記事テーブルに直接追加することです。

DBを正規化することには、長所と短所があります。これは、1つのテーブルにハードワイヤリングすることには長所と短所があるのと同じです。

両方ができないとは何も言われていません。情報を繰り返すことはリレーショナルDBパラダイムに反しますが、目標がパフォーマンスである場合は、パラダイムを破らなければならない場合があります。


はい、この方法にはいくつかの欠点がありますが、タグを記事テーブルに直接配置することは確かにオプションです。(tag1,2,3,4)のように5つのタグをコンマ区切りのフィールドに格納する場合、これは簡単な方法です。問題は、検索がさらに速くなるかどうかです。たとえば、誰かがtag1ですべてを見たいと思っている場合は、記事テーブル全体を調べなければなりません。これは、tag_to_articleテーブルを経由するよりも少なくなります。しかし、繰り返しになりますが、tags_to_articleテーブルはよりスリムになっています。もう1つは、phpで毎回爆発する必要があるということです。これに時間がかかるかどうかはわかりません。
Saif Bechan 2009年

両方(記事付きのタグと別のテーブル)を実行すると、ポストセントリック検索とタグセントリック検索の両方のパフォーマンスが得られます。トレードオフは、繰り返される情報を維持する負担です。また、タグの数を制限することで、それぞれを独自の列に配置できます。XXXXXの記事から*を選択してください。爆発する必要はありません。
ジョン

6

提案された3つのテーブルの実装は、タグ付けに使用できます。

ただし、スタックオーバーフローは異なる実装を使用します。タグをプレーンテキストでpostsテーブルのvarchar列に格納し、フルテキストインデックスを使用してタグに一致する投稿をフェッチします。たとえばposts.tags = "algorithm system tagging best-practices"。ジェフがこれについてどこかで言及していると確信していますが、どこか忘れています。


4
これは非常に非効率的なようです。タグの順序はどうですか?または関連するタグ?(「プロセス」が「アルゴリズム」などに類似しているなど)
Richard Duerr

3

提案された解決策は、タグと記事の間の多対多の関係に対処するために私が考えることができる最善の方法です。だから私の投票は「はい、それでも最高です」です。しかし、私はどんな代替案にも興味があります。


同意する。これらのTagsおよびTagMapテーブルのレコードサイズは小さく、適切にインデックスが作成されていれば、パフォーマンスが大幅に低下することはありません。アイテムごとのodタグの数を制限することも良い考えかもしれません。
PanJanek 2009年

2

データベースがインデックス可能な配列(PostgreSQLなど)をサポートしている場合は、完全に非正規化されたソリューションをお勧めします。タグを文字列の配列として同じテーブルに格納します。そうでない場合は、オブジェクトをタグにマッピングするセカンダリテーブルが最善の解決策です。タグに対して追加情報を格納する必要がある場合は、個別のタグテーブルを使用できますが、タグルックアップごとに2番目の結合を導入しても意味がありません。


PostgreSQLは唯一の整数配列のインデックスをサポートしています。postgresql.org/docs/current/static/intarray.htmlを
マイク・チェンバレン

1
Nowadysはテキストもサポートしています:postgresql.org/docs/9.6/static/arrays.html
Luckydonald 2017年

2

パフォーマンスを向上させるために最適化されたMySQLiciousを提案したいと思います。その前に、Toxi(3テーブル)ソリューションの欠点は

何百万もの質問があり、それぞれに5つのタグがある場合、タグマップテーブルには500万のエントリがあります。したがって、最初にタグ検索に基づいて10,000個のタグマップエントリを除外し、次にそれらの10,000個の一致する質問を再度除外する必要があります。したがって、アーティカルIDが単純な数値である場合は除外しますが、それがUUID(32 varchar)のようなものである場合は、インデックスが付けられていますが、除外する場合はより大きな比較が必要です。

私の解決策:

新しいタグが作成されるたびに、counter ++(base 10)を用意し、そのカウンターをbase64に変換します。これで、各タグ名のIDはbase64になります。このIDを名前とともにUIに渡します。このようにして、システムで4095個のタグが作成されるまで、最大2つの文字IDを使用できます。次に、これらの複数のタグを各質問テーブルのタグ列に連結します。区切り文字も追加して、並べ替えます。

だからテーブルはこんな感じ

ここに画像の説明を入力してください

クエリ中に、実際のタグ名ではなくIDでクエリを実行します。それがあるのでSORTEDandタグの条件は、より効率的になります(LIKE '%|a|%|c|%|f|%)。

単一のスペース区切り文字が十分でないと、私たちのような分化タグに二重の区切り必要があることに注意sqlしてmysqlいるためLIKE "%sql%"戻りますmysqlだけでなくその結果を。する必要がありますLIKE "%|sql|%"

検索がインデックスに登録されていないことは知っていますが、author / dateTimeなどの記事に関連する他の列にインデックスを付けている可能性があります。そうしないと、全表スキャンになります。

最後に、このソリューションでは、100万レコードを結合条件で500万レコードと比較する必要がある場合、内部結合は必要ありません。


チーム、コメントでこのソリューションの欠点についてのあなたの意見を提供してください。
Kanagavelu Sugumar 2018年

@Nick Dandoulakis上記の解決策についてコメントを提供してください。
Kanagavelu Sugumar 2018年

@JuhaSyrjälä上記の解決策は問題ありませんか?
Kanagavelu Sugumar 2018年

0
CREATE TABLE Tags (
    tag VARHAR(...) NOT NULL,
    bid INT ... NOT NULL,
    PRIMARY KEY(tag, bid),
    INDEX(bid, tag)
)

ノート:

  • これは、最適化を困難にする余分なmany:manyテーブルを通過しないという点で、TOXIよりも優れています。
  • 確かに、私のアプローチは冗長なタグのために(TOXIよりも)少しかさばるかもしれませんが、それはデータベース全体のわずかな割合であり、パフォーマンスの大幅な向上が見込まれます。
  • 非常にスケーラブルです。
  • (必要がないため)代理AUTO_INCREMENTPKはありません。したがって、Scuttleよりも優れています。
  • (それは、インデックスを使用することはできませんのでMySQLiciousは吸うLIKEリードする。ストリングの偽ヒットワイルドカード)
  • MySQLの場合、「クラスタリング」効果を得るには、必ずENGINE = InnoDBを使用してください。

関連する議論(MySQLの場合):
many:manyマッピングテーブル最適化
順序付きリスト

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