我正在开发一个店面Web应用程序。当潜在客户在网站上查看产品时,我想自动从数据库中建议一组类似的产品(与要求人员明确输入产品相似性数据/映射相比)。
事实上,当你考虑它时,大多数店面数据库已经有很多可用的相似性数据。在我的情况下,Products
可以是:
Manufacturer
(又名Brand
),Categories
和Tags
(又名Keywords
)。
通过计算产品与所有其他产品之间共享属性的数量,您可以计算“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专家:
和
SimilarProductMappings
表。答案 0 :(得分:2)
你问了很多问题。我会尝试解决每一个问题,而不会涉及太多细节。
CTE与派生表是语法糖。它在性能方面没有任何区别。使用它们的唯一好处是,您可以重复使用它们,而不是再次复制/粘贴/键入派生表。但是,在这种情况下,你不会重复使用它们,所以这取决于你。
索引视图:请记住,视图上的索引就像表上的索引一样,几乎没有异常。想象一下,就像为您的特定查询/视图创建另一个表并将其存储在磁盘上以便更快地检索。当基础数据发生变化时,必须更新这些索引。是的,这可能会产生巨大的资源影响。一般来说,我宁愿看到有人编写一个使用基表上的索引的查询,如果他们需要更多索引用于特定目的,那么在具有多个表的视图上详细查看而不是整体查看。这更容易维护,更容易弄清楚为什么你的CRUD花费的时间比预期的要长。索引视图没有必然出错。但是,要非常小心地在这样的应用程序数据库模型上添加它,因为更新/插入/删除的表的复杂性。索引视图的大多数更合适的用途是在报表数据仓库中。无论如何,不要在不理解它将对CRUD(创建,读取,更新,删除)操作的表做什么的情况下在视图上放置索引。在CRM或应用程序支持类型的数据库中,除非存在静态需求,否则我将远离它们,并且它不会真正影响性能。
阅读这篇文章:http://technet.microsoft.com/en-us/library/ms187864(v=sql.105).aspx
注意关于页面下方3/4的问题,它讨论了不使用的地方,我认为你的情况适合于不应该使用它的4/5场景。
关于保存连接...请记住,FULL OUTER连接是效率最差的攻击者之一。在我看来,你在那里拥有它的唯一原因是因为你没有在你的CTE中包括制造商。您可以将它包含在您的CTE中,然后在最终查询中汇总cat / tag的匹配数量,将它们拉到一起以获得您的分数。这样,您只有两个左外连接(每个CTE一个),然后将两个计数相加并由同一制造商(case语句),productId等分组。
最后......我会考虑将所有这些放在一个非规范化的表格中,或者甚至可能是一个预先计算好的立方体。让我们考虑一下您的要求: 一个。产品的相关性分数是否需要生效?如果是,为什么?添加/删除新产品时,这不是关键任务。任何说它需要活着的人,可能并不真正意味着它。 湾检索速度。我可以使用临时表重写您的查询,确保索引正确等,并在存储过程中提出一个相当快的查询。但是,我仍在聚合来自数据库的数据,以便在每次加载页面时在我的商店前面显示。如果数据被预先计算并存储在productIds的单独表格中,并且每个产品的分数都存储并且由productId索引,那么检索将非常快。您可以在每晚,每小时/任何时间在ETL中中继和重新加载表,并且不必担心每次都重建维护索引。当然,如果您的商店前端是24/7/365,您需要编写一些数据库端代码以担心版本控制,以便您的应用程序永远不必等待数据库正在重新计算。
另外,请确保至少在Web /应用程序服务器上缓存此信息。有一件事是肯定的,如果您使用上面的解决方案,那么您将需要在您的网站中构建一些内容,以便它不会等待数据返回并缓存它。
希望这一切都有所帮助。
答案 1 :(得分: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'