こんにちは、私の名前はドミトリーカルロフスキーです...私は長い間バックエンドをしていませんでしたが、先日、ハリネズミにハリネズミを引っ張ってSbWereWolfの苦痛に突然遭遇しました。
そこで、本日は、パラメータによる商品の検索、フルテキスト検索、ローカリゼーション、ルブリケーターの自動形成、商品を追加するためのウィザードを使用して、オンラインストアのデータベースを調整します。
ここで、このリレーショナル宇宙船を分析します。
そして、ここでそのようなグラフの巣箱を収集するには:
データベーススキーマ
カタログのすべてのエンティティには、次のフィールドがあります。
- slug-人間が読める識別子
- created-エンティティ作成時間
- 検索可能 -エンティティが検索に含まれるかどうか
- title-辞書「language-text」の形式のエンティティの人間が読める名前
- description-辞書「言語テキスト」の形式でのエンティティの人間が読める説明
宇宙船で行われているように、各エンティティでこれらのフィールドを繰り返さないために、残りのエンティティを継承し続ける抽象クラス「オブジェクト」を作成します。
Create class Object abstract Create property Object.slug string ( collate ci , notnull true ) Create property Object.created datetime ( readonly true , default sysdate() ) Create property Object.searchable boolean ( default false ) Create property Object.title embeddedmap string ( collate ci ) Create property Object.description embeddedmap string ( collate ci )
スラッグはエンティティごとに一意である必要があり、名前と説明による検索が必要なので、これらのフィールドに対応するインデックスを追加します。
Create index Object.slug unique Create index Object.title on Object( title by value ) fulltext engine lucene metadata { "analyzer" : "org.apache.lucene.analysis.ru.RussianAnalyzer" } Create index Object.description on Object( description by value ) fulltext engine lucene metadata { "analyzer" : "org.apache.lucene.analysis.ru.RussianAnalyzer" }
全文検索エンジンがすでにDBMSに組み込まれているのは素晴らしいことです。メインデータベースから同じLucene上に構築されたElasticSearchにデータを手動で転送する必要はありません。 DBMSの本質を変更するときは、フルテキストインデックスの更新を処理します。
私たちの店で最も重要なのは商品であり、標準のプロパティに加えて各製品にも価格があります。
Create class Product extends Object Create property Product.price decimal
宇宙船は、ツリー形式の見出しの階層を使用しますが、さらに興味深いことを行います。階層全体を2種類のノードに分割します。
- タグ -特定の製品を特徴付けるフラグ。
- アスペクト -商品の特性を形成する相互に排他的なタグのグループ
階層では、次の2つのタイプのノードが交互に使用されます。
- タグにはアスペクトは含まれますが、タグは含まれません
- タグはアスペクトの中にあるかもしれませんが、アスペクトの中にはないかもしれません
アスペクトの例(および括弧内-タグ):
- 商品の種類(食品、衣類、機器)
- メーカー(Plyushkin Incorporated、Macintosh Limited、Excavator Trading)
- カラー(赤、青、緑)
同時に、「食べ物」ではなく「衣服」と「機器」のタグにのみ「色」を選択することは理にかなっています。
これらの2つのエンティティを追加して、一緒に編成します。
Create class Aspect extends Object Create class Tag extends Object Create property Aspect.tag linkset Tag Create property Aspect.tag_sub linkset Tag Create property Tag.aspect linkset Aspect Create property Tag.aspect_sub linkset Aspect
ご覧のとおり、私たちの関係はすべて、二国間多対多です。 各エンティティには、関連するエンティティへのリンクがあります。 私たちの階層を商品につなぐだけです。 接続は一方向になり、関連する製品のリストでタグが散らばらないようにします。
Create property Product.tag linkset Tag
たとえば、アイテム「Tablecloth-self-assembly」にタグ「technique」を設定できます。これにより、アスペクト「color」が表示され、その結果、「red」を選択できるようになります。
タグによる検索が遅くならないように、それらにインデックスを追加します。
Create index Product.tag notunique
フラグに加えて、エンティティは他のタイプの属性も持っている必要があります:文字列、整数、10進数、一時など。 これらの属性を説明するために、対応するエンティティを紹介します。
Create class Attribute extends Object Create property Attribute.type string ( default "string" )
考えられるように、属性は商品に添付されませんが、タグに添付されます。
Create property Tag.attribute linkset Attribute Create property Attribute.tag linkset Tag
たとえば、タグ「food」が設定されている場合、属性「有効期限」が製品で使用可能になります。 特定の製品の属性の値を製品自体に保存します。 各製品は、どのタグが設定されているかに応じて、実際には独自の属性セットを持つことができるため、これをスキームに入れません。
ユーザースクリプト
全文検索
ユーザーが検索クエリを入力した場合、すぐにユーザーに一致するすべてのオブジェクトを検索します。
Select from Object where searchable = true and ( title lucene "*" or description lucene "*" )
しかし、発行が多すぎることが判明した場合は、問題の商品からのタグのリクエストを詳細に彼に提供するのが合理的です。 これを行うには、実際に見つかったオブジェクトとともに、それらに関連付けられたタグと、後者に関連付けられた属性とアスペクトも要求します。
Select from Object where searchable = true and ( title lucene "*" or description lucene "*" ) fetchplan *:0 slug:0 title:0 tag.slug:0 tag.title:0 tag.aspect.title:0 tag.attribute.title:0 tag.attribute.type:0
したがって、検索結果の横に、適格なルブリケーターを配置することができます。そのアイテムの通過により結果が絞り込まれますが、保証付きでは完全なクリーニングにはつながりません。 したがって、ユーザーが選択したタグと属性値によるフィルタリングを大胆に追加します。
Select from Object where searchable = true and ( title lucene "*" or description lucene "*" ) and ( tag in ( Select from Tag where slug = "tag=tech" and slug="color=red" ) ) and ( weight between 100 and 200 ) fetchplan *:0 slug:0 title:0 tag.slug:0 tag.title:0 tag.aspect.title:0 tag.attribute.title:0 tag.attribute.type:0
カタログナビゲーション
ユーザーがサイトを開いたばかりのとき、すぐにいくつかの移動方向を提供し、範囲の概要を説明するのが妥当です。 したがって、すべてのルートアスペクトとそれらの可能なタグを導出します。
Select from Aspect where ( tag is null ) fetchplan *:0 title:0 tag_sub.slug:0 tag_sub.title:0
ユーザーのさらなる旅は、フルテキスト検索の場合と似ていますが、適切なフルテキスト検索はありません。
Select from Product where searchable = true and ( tag in ( Select from Tag where slug = "tag=tech" and slug="color=red" ) ) and ( weight between 100 and 200 ) fetchplan *:0 slug:0 title:0 tag.slug:0 tag.title:0 tag.aspect.title:0 tag.attribute.title:0 tag.attribute.type:0
ご覧のとおり、ウサギの穴に深く入り込んで、あるカテゴリから別のカテゴリ(ネスト)に移動するのではなく、フィルタリングに追加のタグを追加します。 たとえば、「赤い靴」ページでは「服」、「靴」、「靴」、「赤」のタグで検索し、「赤い靴」ページでは「機器」、「コンピューター」、「ラップトップ」、「赤。」 どちらの場合も、赤は同じタグです。
製品の作成
製品を作成するとき、記入する商品のすべての可能なパラメーターを表示しても意味がありません。 たとえば、S / N比パラメーターは靴にとってまったく意味がありません。 したがって、カタログと同様に、ルートアスペクトのみを表示し、ユーザーがタグを選択して製品を追加する場合にのみ追加のアスペクトが使用可能になります。 利用可能な属性とアスペクトのリストと選択したタグのリスト内のタグは非常に簡単です。
Select from Aspect where ( tag is null ) or ( tag in ( Select from Tag where slug = "tag=tech" and slug="color=red" ) ) fetchplan *:0 title:0 tag_sub.slug:0 tag_sub.title:0 tag_sub.attribute.title:0 tag_sub.attribute.type:0
ここでは、製品を追加するだけでなく、属性、アスペクト、タグのセットを追加する機会をユーザーに提供できます。
たとえば、アスペクト「製品タイプ」を作成しましょう。
Insert into Aspect set slug = "aspect=kind" , title = { "ru" : " " }
ここで、たとえば「Clothes」タグを追加します。
Insert into Tag set slug = "tag=wear" , searchable = true , title = { "ru" : "" } , aspect = ( Select from Aspect where slug = "aspect=kind" ) Update Aspect add tag_sub = ( Select from tag where slug = "tag=wear" ) where slug = "aspect=kind"
他のタグも同様に追加されます。 ネストされたアスペクトをタグに追加することも同様です。 たとえば、「衣料品」および「技術」タグに「色」アスペクトを追加します。
Insert into Aspect set slug = "aspect=color" , title = { "ru" : "" } , tag = ( Select from Tag where slug = "tag=wear" or slug = "tag=tech" ) Update Tag add aspect_sub = ( Select from Aspect where slug = "aspect=color" ) where slug = "tag=wear" Update Tag add aspect_sub = ( Select from Aspect where slug = "aspect=color" ) where slug = "tag=tech"
そして最後に、最も重要なことは商品の追加です。 たとえば、「自己組み立てテーブルクロス」を赤で追加します。
Insert into Product set slug = "product=2" , searchable = true , title = { "ru" : "-" } , price = 999 , tag = ( Select from Tag where slug = "tag=tech" or slug = "tag=red" )
製品の取り外し
製品を削除しても、データベースからこの製品に関するレコードが削除されることはありません。したがって、将来、この製品を復元するか、何らかのログから識別子でデータを検索する必要が生じる場合があります。 はい、404(見つかりません)の代わりに410(なくなった)を提供する場合でも、製品の記録が残っている必要があります。 さらに、削除されたレコードを他のレコードが参照しないようにするなどの難しい問題があります。 したがって、最善の解決策は、特定のプロセスから除外されるようにレコードを変更することです。 たとえば、製品がグローバル検索またはカタログに含まれないようにするには、 searchable
フラグをfalse
変更しfalse
。 これが、すべての検索クエリwhere searchable = true
追加条件を示した理由where searchable = true
。
Update Product set searchable = false where slug = "product=2"
「削除」の別のオプションは、エンティティ自体を削除する代わりに、エンティティ参照を削除することです。 たとえば、持っているアスペクトタグのリストはtag_sub
プロパティに保存されてtag_sub
ます。 「色」アスペクトで「Sulfur-boromoline」タグを選択できなくする場合は、 tag_sub
から削除するだけで、タグからアスペクトへの接続はそのままにしておきます。 したがって、この奇妙な色の製品を見ると、何も壊れません-「色:硫黄-ボロマリン」が表示されますが、新しい製品を作成するとき、この色を選択することはできません。
Update Aspect remove tag_sub = ( Select from Tag where slug = "tag=gray-brown-magenta" ) where slug = "aspect=color"
まとめ
合計で、製品、タグ、アスペクト、属性の4つのエンティティがあります。 それらの間には、8種類の接続があります。 そして、これだけで、たった1晩の忙しい夜に検索、フィルター、魔術師でYandex.Marketを実現するのに十分です。