web-dev-qa-db-ja.com

タグシステムを実装する方法

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

tagsテーブル、articlesテーブル、tag_to_articles テーブル。

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

84
Saif Bechan

このブログ投稿が興味深いものになると思います: Tags:Database schemas

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

「MySQLicious」ソリューション

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

enter image description hereenter image description here

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

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 は、データを2つのテーブルに編成します。そのテーブル「scCategories」は「タグ」テーブルであり、「ブックマーク」テーブルへの外部キーを持っています。

enter image description here

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

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」のクエリ。つまり、bookmark AND webservice AND NOT 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 は、3つのテーブル構造を考え出しました。テーブル「タグマップ」を介して、ブックマークとタグはn対mに関連しています。各タグは異なるブックマークと一緒に使用でき、逆もまた同様です。このDBスキーマはワードプレスでも使用されます。クエリは、「scuttle」ソリューションとまったく同じです。

enter image description here

交差点(AND)「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
HAVING COUNT( b.id )=3

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

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」のクエリ。つまり、bookmark AND webservice AND NOT 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」のクエリになります。

113

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

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

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

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

8
John

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

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

6
Juha Syrjälä

提案されたソリューションは、タグと記事の間の多対多の関係に対処するために考えることができる最良の方法です(唯一の実行可能なものではないにしても)。私の投票は「はい、まだ最高です」です。しかし、私は他の選択肢に興味があります。

3
David Thomas

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

数百万の質問があり、それぞれに5つのタグがある場合、タグマップテーブルには500万のエントリがあります。そのため、最初にタグ検索に基づいて1万個のタグマップエントリを除外し、次にそれらの1万個の一致する質問を除外する必要があります。したがって、artical idが単純な数値である場合は除外しますが、UUID(32 varchar)の種類である場合は、インデックスを作成しますが、より大きな比較が必要です。

私の解決策:

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

テーブルはこんな感じ

enter image description here

クエリ中に、実際のタグ名ではなくidでクエリします。 [〜#〜] sorted [〜#〜]なので、タグのand条件はより効率的です(LIKE '%|a|%|c|%|f|%)。

LIKE "%sql%"sqlの結果も返すため、単一スペース区切り文字では不十分であり、mysqlmysqlなどのタグを区別するために二重区切り文字が必要であることに注意してください。 LIKE "%|sql|%"である必要があります

検索にはインデックスが付けられていませんが、author/dateTimeなどの記事に関連する他の列にインデックスを付けている場合があります。

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

2

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

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

ノート:

  • これは、最適化を困難にする余分なmany:manyテーブルを通過しないという点で、TOXIよりも優れています。
  • 確かに、タグが冗長であるため、私のアプローチは(TOXIよりも)わずかにかさばるかもしれませんが、それはwholeデータベースのごく一部であり、パフォーマンスの向上が著しい場合があります。
  • 非常にスケーラブルです。
  • サロゲートAUTO_INCREMENT PKはありません(必要ないため)。したがって、Scuttleよりも優れています。
  • MySQLiciousは、インデックスを使用できないため(LIKEleadingワイルドカード、部分文字列での誤ったヒット)
  • MySQLの場合、「クラスタリング」効果を得るためにENGINE = InnoDBを使用してください。

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

0
Rick James