10

ストアフロント Web アプリケーションを開発しています。潜在的な顧客が Web サイトで製品を閲覧している場合、データベースから一連の類似製品を自動的に提案したいと考えています (製品類似データ/マッピングを人間が明示的に入力する必要はありません)。

実際、考えてみると、ほとんどのストアフロント データベースには、すでに多くの類似データが用意されています。私の場合Productsは次のようになります。

  • Manufacturer(別名Brand)にマッピングされ、
  • 1 つ以上Categoriesの にマップされ、
  • 1 つ以上Tags(別名Keywords) にマップされます。

Storefront データモデル スニペット

製品と他のすべての製品との間で共有される属性の数を数えることで、顧客が閲覧している製品と他の製品を比較するための「SimilarityScore」を計算できます。これが私の最初のプロトタイプの実装です。

;WITH ProductsRelatedByTags (ProductId, NumberOfRelations)
AS
(
    SELECT  t2.ProductId, COUNT(t2.TagId)
    FROM    ProductTagMappings AS t1 INNER JOIN
                ProductTagMappings AS t2 ON t1.TagId = t2.TagId AND t2.ProductId != t1.ProductId
    WHERE   t1.ProductId = '22D6059C-D981-4A97-8F7B-A25A0138B3F4'
    GROUP BY t2.ProductId
), ProductsRelatedByCategories (ProductId, NumberOfRelations)
AS
(
    SELECT  t2.ProductId, COUNT(t2.CategoryId)
    FROM    ProductCategoryMappings AS t1 INNER JOIN
                ProductCategoryMappings AS t2 ON t1.CategoryId = t2.CategoryId AND t2.ProductId != t1.ProductId
    WHERE   t1.ProductId = '22D6059C-D981-4A97-8F7B-A25A0138B3F4'
    GROUP BY t2.ProductId
)
SELECT  prbt.ProductId AS ProductId
        ,IsNull(prbt.NumberOfRelations, 0) AS TagsInCommon
        ,IsNull(prbc.NumberOfRelations, 0) AS CategoriesInCommon
        ,CASE WHEN SimilarProduct.ManufacturerId = SourceProduct.ManufacturerId THEN 1 ELSE 0 END as SameManufacturer
        ,CASE WHEN SimilarProduct.ManufacturerId = SourceProduct.ManufacturerId 
            THEN IsNull(prbt.NumberOfRelations, 0) + IsNull(prbc.NumberOfRelations, 0) + 1
            ELSE IsNull(prbt.NumberOfRelations, 0) + IsNull(prbc.NumberOfRelations, 0)
        END as SimilarityScore
FROM    Products AS SourceProduct, 
        Products AS SimilarProduct INNER JOIN
        ProductsRelatedByTags prbt ON prbt.ProductId = SimilarProduct.Id FULL OUTER JOIN
        ProductsRelatedByCategories prbc ON prbt.ProductId = prbc.ProductId
WHERE SourceProduct.Id = '22D6059C-D981-4A97-8F7B-A25A0138B3F4'

次のようなデータが得られます。

ProductId                            TagsInCommon CategoriesInCommon SameManufacturer SimilarityScore
------------------------------------ ------------ ------------------ ---------------- ---------------
6416C19D-BA4F-4AE6-AB75-A25A0138B3A5 1            0                  0                1
77B2ECC0-E2EB-4C1B-A1E1-A25A0138BA19 1            0                  0                1
2D83276E-40CC-44D0-9DDF-A25A0138BE14 2            1                  1                4
E036BFE0-BBB5-450C-858C-A25A0138C21C 3            0                  0                3

私は SQL パフォーマンスの達人ではないので、SQL の達人に次の質問をします。

  • (CTE) は適切/最適ですか? (それらは確かにSQLを読みやすく/従うのを容易にするようです)。上記のモデルのどこかに結合を保存する方法はありますか?

  • これは、インデックス付きビュー (永続化のため) の良い候補になるでしょうか?それとも、ソース データの変更に過度のコストがかかるでしょうか? SimilarProductMappingsその場合、これを任意の製品の物理テーブルを更新するストアド プロシージャにします。
4

2 に答える 2

2

あなたはたくさんの質問をします。あまり詳しく説明することなく、それぞれに対処しようと思います。

  1. CTE と派生テーブルは構文糖衣です。パフォーマンス的には違いはありません。それらを使用する唯一の利点は、派生テーブルを再度コピー/貼り付け/入力する代わりに、それらを再利用できることです。ただし、この場合はそれらを再利用していないため、あなた次第です。

  2. インデックス付きビュー: ビューのインデックスはテーブルのインデックスのように機能しますが、ほとんど例外はありません。特定のクエリ/ビュー用に別のテーブルが作成され、より高速に取得できるようにディスクに格納されているように想像してください。基になるデータが変更されると、これらのインデックスを更新する必要があります。はい、これはリソースに大きな影響を与える可能性があります。一般に、誰かがベース テーブルのインデックスを使用するクエリを作成し、特定の目的でさらにインデックスが必要な場合は、複数のテーブルを含むビューを全体的に見るのではなく、詳細に調べることを望みます。これは維持するのがはるかに簡単で、CRUD が予想よりも長くかかっている理由を理解するのがはるかに簡単です。インデックス付きビューに必ずしも問題はありません。しかし、更新/挿入/削除されるテーブルが複雑になるため、このようなアプリケーション データベース モデルにこれを追加する場合は十分に注意してください。インデックス付きビューのより適切な用途のほとんどは、レポート データ ウェアハウスです。とにかく、CRUD (作成、読み取り、更新、削除) 操作でテーブルに何を行うかを理解せずに、ビューにインデックスを配置しないでください。また、CRM またはアプリケーション サポート タイプのデータベースでは、静的な必要性があり、パフォーマンスに実際に影響を与えない限り、ほとんどの場合、それらを使用しません。

この記事を読む: http://technet.microsoft.com/en-us/library/ms187864(v=sql.105).aspx

ページの約 3/4 で、どこで使用しないかについて説明されていることに注意してください。あなたのケースは、使用してはならないシナリオの 4/5 に当てはまると思います。

  1. 結合の保存に関して... FULL OUTER結合は、効率の最悪の違反者の1つであることに注意してください。あなたがそこにいる唯一の理由は、CTEにメーカーを含めていないからだと思います。それを CTE に含めるだけで、最終的なクエリで cat/tag ごとに一致数を集計してスコアを取得できます。このようにして、左外部結合を 2 つ (各 CTE に 1 つ) だけにして、2 つのカウントを合計し、同じメーカー (case ステートメント)、productId などでグループ化します。

  2. 最後に...これらすべてを、非正規化されたテーブル、または事前に計算されたキューブに配置することを検討します。要件についていくつか考えてみましょう。製品の相関スコアはライブである必要がありますか? はいの場合、なぜですか? これは、新しい製品が追加または削除された場合にミッション クリティカルではありません。ライブが必要だと言っている人は、おそらく本当の意味ではないでしょう。b. 取得速度。一時テーブルを使用してクエリを書き直したり、インデックスが正しいことを確認したりして、ストアド プロシージャでかなり高速なクエリを作成できます。しかし、ページが読み込まれるたびに、DB からデータを収集して店頭全体に表示しています。データが事前に計算され、すべての製品の productId とスコアの個別のテーブルに格納され、productId によってインデックス付けされている場合、取得は非常に高速になります。毎晩、毎時など、ETL でテーブルをトランクしてリロードすることができ、毎回再構築されるインデックスの維持について心配する必要はありません。もちろん、店舗が 24 時間 365 日稼働している場合は、データベース側のコードを記述して、データベースが再計算中の場合にアプリケーションが待機する必要がないように、バージョン管理を考慮する必要があります。

また、他に何もない場合でも、少なくともこの情報を Web/アプリケーション サーバーにキャッシュするようにしてください。1つ確かなことは、上記のソリューションを使用する場合、データが返されるのを待たずに代わりにキャッシュするように、サイトに何かを構築する必要があるということです.

それがすべて役立つことを願っています。

于 2013-11-13T20:52:47.807 に答える
1

少し違うアプローチはいかがですか?

;WITH ProductFindings (ProductId, NbrTags, NbrCategories)
AS
(
    SELECT  t2.ProductId, COUNT(t2.TagId), 0
    FROM    ProductTagMappings AS t1 
    INNER JOIN
            ProductTagMappings AS t2  ON t1.TagId      = t2.TagId
                                     AND t1.ProductId != t2.ProductId
    WHERE   t1.ProductId = '22D6059C-D981-4A97-8F7B-A25A0138B3F4'
    GROUP BY t2.ProductId

  UNION ALL

    SELECT  c2.ProductId, 0, COUNT(c2.CategoryId)
    FROM    ProductCategoryMappings AS c1 
    INNER JOIN
            ProductCategoryMappings AS c2  ON c1.CategoryId = c2.CategoryId
                                          AND c1.ProductId != c2.ProductId
    WHERE c1.ProductId = '22D6059C-D981-4A97-8F7B-A25A0138B3F4'
    GROUP BY c2.ProductId

), ProductTally (ProductId, TotTags, TotCategories) as
(
   SELECT ProductID, sum(NbrTags), sum(NbrCategories)
   FROM     ProductFindings
   GROUP BY ProductID
)
SELECT  Tot.ProductId      AS ProductId
       ,Tot.TotTags        AS TagsInCommon
       ,Tot.TotCategories  AS CategoriesInCommon
       ,CASE WHEN SimilarProduct.ManufacturerId = SourceProduct.ManufacturerId
               THEN 1
               ELSE 0 
         END               as SameManufacturer
       ,CASE WHEN SimilarProduct.ManufacturerId = SourceProduct.ManufacturerId 
               THEN 1
               ELSE 0
         END + Tot.TotTags + Tot.TotCategories
                           as SimilarityScore
FROM    ProductTally as Tot
INNER JOIN  Products     AS SimilarProduct   ON Tot.ProductID = SimilarProduct.Id 
INNER JOIN  Products     AS SourceProduct    ON SourceProduct.Id = '22D6059C-D981-4A97-8F7B-A25A0138B3F4'
于 2013-11-16T01:48:22.967 に答える