ユーザーが購入したカスタムタグをトランザクションごとに保存したいと思います。たとえば、ユーザーが靴を購入した場合、タグは_"SPORTS", "NIKE", SHOES, COLOUR_BLACK, SIZE_12,..
_になります。
これらのタグは、売り手に問い合わせて売り上げを理解することに関心のある売り手です。
私の考えは、新しいタグが入ってくると、そのタグの新しいコード(ハッシュコードのようなものですがシーケンシャル)を作成し、コードは_"a-z"
_ 26文字から始まり、次に_"aa, ab, ac...zz"
_が続きます。 _"|"
_で区切って、tag (varchar)
と呼ばれる1つの列に1つのトランザクションで指定されたすべてのタグを保持します。
マッピングが(アプリケーションレベルで)であると仮定しましょう
_"SPORTS" = a
"TENNIS" = b
"CRICKET" = c
...
...
"NIKE" = z //Brands company
"ADIDAS" = aa
"WOODLAND" = ab
...
...
SHOES = ay
...
...
COLOUR_BLACK = bc
COLOUR_RED = bd
COLOUR_BLUE = be
...
SIZE_12 = cq
...
_
したがって、上記の購入トランザクションを保存すると、タグは_tag="|a|z|ay|bc|cq|"
_のようになります。これで、WHERE
条件_tag LIKE %|ay|%
_を追加して、販売者が販売した靴の数を検索できるようになります。ここでの問題は、「LIKEが%で始まる」のインデックス(redshift dbのソートキー)を使用できないことです。では、1億のレコードがある可能性があるため、この問題を解決するにはどうすればよいですか?全表スキャンを望まない.
これを修正する解決策はありますか?
Update_1:指定したタグを検索した後、結果に対してグループ化を実行したいので、_bridge table
_の概念(相互参照テーブル)を使用していません。 1つのトランザクションで2つのタグが一致した場合、私のソリューションでは1行しか表示されませんが、ブリッジテーブルでは2行表示されますか?その後、私のsum()は2倍になります。
以下のような提案を受けました
タグごとに1回、WHERE句でEXISTS(SELECT 1 FROM transaction_tag WHERE tag_id = 'zz' and trans_id = tr.trans_id)(注:trは周囲のクエリのトランザクションテーブルのエイリアスであると想定)
私はこれに従っていません。 ANDとOR条件をタグで実行する必要があるため、例( "SPORTS" AND "ADIDAS")---- "SHOE" AND( "NIKE" OR "ADIDAS")
Update_2:私はビットフィールドをフォローしていません。Redshiftがこのサポートを持っていることを知らないため、システムに最低3500のタグがあり、それぞれに1ビットを割り当てると想定しています。これにより、トランザクションごとに437バイトになりますが、トランザクションには最大5つのタグしか指定できません。ここで何か最適化しますか?
Solution_1:
タグ列とともに最小値(SMALL_INT)と最大値(SMALL_INT)を追加し、それにインデックスを適用することを考えました。
こんな感じ
_"SPORTS" = a = 1
"TENNIS" = b = 2
"CRICKET" = c = 3
...
...
"NIKE" = z = 26
"ADIDAS" = aa = 27
_
だから私の列の値は
_`tag="|a|z|ay|bc|cq|"` //sorted?
`minTag=1`
`maxTag=95` //for cq
_
そして、shoe(ay = 51)を検索するクエリは
_maxTag <= 51 AND tag LIKE %|ay|%
_
そして、shoe(ay = 51)AND SIZE_12(cq = 95)を検索するクエリは
_minTag >= 51 AND maxTag <= 95 AND tag LIKE %|ay|%|cq|%
_
これは何かメリットがありますか?親切に代替案を提案してください。
ここでも、多対多のルックアップテーブル(ブリッジテーブル)を使用するのが最善の方法であると確信しています。複数の行の一致に関する懸念は、適切なクエリ設計によって修正できます。テーブルが次のとおりだとします。
CREATE TABLE purchases(PurchaseID,CustomerID,PurchaseDate,...)
CREATE TABLE tags(TagID,TagType,TagName)
CREATE TABLE purchasetags(PurchaseID,TagID)
したがって、各購入には複数のタグセット(制限なし)を含めることができ、お楽しみのために、タグをTagTypeで分類する機能を追加しました。 Color」、「Sport」、「shoes」は「ProductType」タグ、「Nike」はブランドタグ、「soccer」はsportタグ。
次に、クエリを実行する(そして単一の行のみを返す)場合は、次のようにします。
SELECT *
FROM purchases
WHERE PurchaseID IN (SELECT pt.PurchaseID
FROM purchasetag pt
INNER JOIN tags t ON pt.TagID=t.TagID
WHERE t.TagName IN ('Adidas','Nike'))
GROUP BY whatever...
より洗練されたコンボ検索(Nikeシューズまたはアディダスシューズの購入を見つける)を行う必要がある場合、クエリもより洗練されている必要があります。
SELECT *
FROM purchases
WHERE PurchaseID IN (SELECT pt.PurchaseID
FROM purchasetag pt
INNER JOIN tags t ON pt.TagID=t.TagID
WHERE t.TagName = 'Shoes')
AND PurchaseID IN (SELECT pt.PurchaseID
FROM purchasetag pt
INNER JOIN tags t ON pt.TagID=t.TagID
WHERE t.TagName IN ('Adidas','Nike'))
この場合も、目的のタグの組み合わせに一致する購入ごとに1行が返されます。
このような問題を解決する通常の方法は、 bitfield を使用することです。
したがって、タグテーブルを作成し、それをn:mテーブルを介して売上高または製品にリンクします。次に、タグテーブルで、タグごとに2の累乗として一意のビット値を割り当てます。 1, 2, 4, 8, ..., 1024, 2048, ...
for sports, tennis, cricket, ...
など。
bit_or
を使用すると、これらの値を単一の数値に圧縮し、これを製品または売上の数値と一緒に保存できます。たとえば、1つの製品のタグ「sports」と「cricket」は5になります。
使用可能な数値タイプのビットサイズがすべてのタグを格納するのに十分でない場合は、これらのフィールドの複数を使用して、フィールドの番号または列名とタグ付きのビット値を格納します。
次に、次の形式の使用句をクエリします。
flags & 1024 = 1024
またはflags & 1024 <> 0
= 10番目のフラグセット
これで、フラグに対してブール式を実行できます。すべての色に単一のフィールドを指定すると、Colorタグのある製品のクエリなど、colorflags <> 0
などの他のトリックも実行できます。
列指向のデータベース(redshift)を使用しているため、&
は列の一意の値ごとに1回だけ実行されます。実装に応じて、データベースは&
節を分析し、列値のソート順(無料)を通じてサイズの制約を使用することにより、これをさらに削減します。
そして、最後のパフォーマンスが必要な場合は、フラグとクエリに関する統計を収集し、それらをインテリジェントにグループ化することで、トリックを実行できます。私はあなたが説明しているユースケースでは(フィルタリングの後に合計を実行する...グループ化する)、これによって得られる可能性のあるパフォーマンスは計算のコストと比較して無視できるでしょう。
テーブル
brand
ID
Name
size
ID
name
color
ID
name
customer
ID
Name
purchase
ID
customerID
date
purchaseDetail
purchaseID
brandID
sizeID
colorID
すべてのタグをアイテムに関連付けるには、purchaseDetailが必要です
select *
from purchase
join purchaseDetail
on purchase.ID = purchaseDetail.purchaseID
join brand
on brand.ID = purchaseDetail.brandID
and brand.Name in ('Nike', 'Adidas')
join size
on size.ID = purchaseDetail.sizeID
join color
on color.ID = purchaseDetail.colorID
and color.Name = 'black'