質問
SOで使用されているようなタグシステムを実装するのが最善の方法だと思いました。私はこれを考えていましたが、優れたスケーラブルなソリューションを思い付くことができません。
tags
テーブル、 articles
テーブル、 tag_to_articles
テーブルの基本的な3つのテーブルソリューションを考えていました。
これはこの問題の最善の解決策ですか、それとも代替手段はありますか?この方法を使用すると、テーブルは時間とともに非常に大きくなりますが、検索には効率的ではないと思います。一方、クエリが高速に実行されることはそれほど重要ではありません。
解決
このブログ投稿に興味があると思います:タグ:データベーススキーマ
問題:タグ付けできるデータベーススキーマが必要です ブックマーク(またはブログ投稿など)を必要な数だけタグ付けします。 その後、クエリを実行して、ブックマークを タグの結合または交差。また、除外したい(例:マイナス) 検索結果からのいくつかのタグ。
“ MySQLicious”ソリューション
このソリューションでは、スキーマにはテーブルが1つしかなく、非正規化されています。このタイプは“ MySQLiciousソリューション”と呼ばれますMySQLiciousはdel.icio.usデータをこの構造を持つテーブルにインポートするためです。
交差点(AND) “ search + webservice + semweb”のクエリ:
SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags LIKE "%semweb%"
ユニオン(OR) “ search | webservice | semweb”:
のクエリ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” “タグ” -tableであり、“ブックマーク” -tableへの外部キーを持っています。
交差点(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
まず、ブックマークとタグのすべての組み合わせが検索されます。タグは“ブックマーク”、“ウェブサービス”です。または“ semweb” (c.category IN( 'bookmark'、 'webservice'、 'semweb'))、3つのタグをすべて検索したブックマークのみが考慮されます(HAVING COUNT(b.bId)= 3)。
ユニオン(OR) “ bookmark | webservice | semweb”のクエリ: 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”ソリューション
Toxi は、3つのテーブル構造を考案しました。テーブルを介して“タグマップ”ブックマークとタグはn対mに関連しています。各タグを異なるブックマークと一緒に使用したり、その逆を行うことができます。このDBスキーマはワードプレスでも使用されます。 クエリは“ scuttle”とまったく同じです。ソリューション。
交差点(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
ユニオン(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”を照会します。つまり、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”のクエリになります。
他のヒント
3つのテーブルのソリューションに問題はありません。
別のオプションは、記事に適用できるタグの数(SOの5など)を制限し、記事テーブルに直接追加することです。
DBの正規化には利点と欠点があります。1つのテーブルにハードワイヤリングすることには利点と欠点があります。
両方を行うことはできないということはありません。情報を繰り返すことはリレーショナルDBパラダイムに反しますが、目標がパフォーマンスである場合、パラダイムを破る必要があります。
提案された3つのテーブルの実装はタグ付けに有効です。
ただし、スタックオーバーフローは異なる実装を使用します。投稿テーブルのvarchar列にタグをプレーンテキストで保存し、フルテキストインデックスを使用してタグに一致する投稿を取得します。たとえば、 posts.tags =" algorithm system tagting best-practices"
などです。ジェフはこれについてどこかで言及していると確信していますが、どこかを忘れています。 提案された解決策は、タグと記事の間の多対多の関係に対処するために考えられる最良の方法です(唯一の実行可能な方法ではないにしても)。私の投票は「はい、まだ最高です」です。ただし、他の選択肢に興味があります。
データベースがインデックス可能な配列(たとえばPostgreSQLなど)をサポートしている場合、完全に非正規化されたソリューションをお勧めします-タグを同じテーブルに文字列の配列として格納します。そうでない場合は、オブジェクトをタグにマッピングするセカンダリテーブルが最適なソリューションです。タグに対して追加の情報を保存する必要がある場合は、個別のタグテーブルを使用できますが、タグのルックアップごとに2番目の結合を導入しても意味がありません。
パフォーマンスを向上させるためにMySQLiciousを最適化することをお勧めします。 それ以前は、Toxi(3テーブル)ソリューションの欠点は
です数百万の質問があり、それぞれに5つのタグがある場合、タグマップテーブルには500万のエントリがあります。そのため、最初にタグ検索に基づいて1万個のタグマップエントリを除外し、次にそれらの1万個の一致する質問を除外する必要があります。したがって、artical idが単純な数値である場合は除外しますが、UUID(32 varchar)の種類である場合は、インデックスを作成しますが、より大きな比較が必要です。
私の解決策:
新しいタグが作成されるたびに、counter ++(base 10)を取得し、そのカウンターをbase64に変換します。これで、各タグ名にはbase64 idが付きます。このIDを名前とともにUIに渡します。 この方法では、システムで4095個のタグが作成されるまで、最大2つのchar idを使用できます。次に、これらの複数のタグを各質問テーブルのタグ列に連結します。区切り文字も追加して、ソートします。
つまり、テーブルは次のようになります
クエリ中に、実際のタグ名ではなくidでクエリします。
SORTED であるため、タグの and
条件はより効率的です( LIKE '%| a |%| c |%| f |%
)。
単一のスペース区切り文字では不十分であり、 LIKE"%sql%"
は mysql
の結果も返します。 LIKE"%| sql |%"
検索にはインデックスが作成されていないことはわかっていますが、author / dateTimeなどの記事に関連する他の列にインデックスを作成している場合があります。
最後に、このソリューションでは、100万件のレコードを500万件のレコードと結合条件で比較する必要がある内部結合は不要です。
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は、インデックスを使用できないため(
LIKE
と leading ワイルドカード、部分文字列の誤ったヒット) - MySQLの場合、「クラスタリング」効果を得るには、ENGINE = InnoDBを使用してください。
関連する議論(MySQLの場合):
多く:多くのマッピングテーブルの最適化
順序付きリスト