我有一个用于电子商务解决方案的现有MSSQL数据库。有许多产品属性组和许多属性。客户需要一个过滤解决方案,我觉得当前的实施效率不高,所以我想联系DBA社区,提出一些关于如何最好地改进的建议。
数据结构(简单的多对多关系):
ProductAttribute Table
------
ProductId
AttributeId
Attribute Table
-------
AttributeId
AttributeGroupId
AttributeGroup Table
-------
AttributeGroupId
这里的主要问题是,在客户端,他们可以根据需要选择尽可能多的过滤器。但是,对于组中的每个属性,它都是"或"选择,而其他组中的属性是"和"选择。
实施例: AttributeGroup颜色:红色,绿色 AttributeGroup长度:长 AttributeGroup材质:Silk,亚麻
所以基本上我需要退回任何符合红/长/丝绸,红/长/亚麻,绿/长/丝绸,绿/长/亚麻的产品。
现在,存储过程以苏联风格单独检查这些组中的每个组,超长过程完成工作。首先构建一个与第一组(Color)匹配的productIds的临时表,然后删除不与连续的AttributeGroup过滤器匹配的ID。示例:给我所有的红色和绿色产品。然后删除任何不是" Long"的产品。现在删除任何不属于亚麻或丝绸的剩余产品。
这个过程相当不错,但既然这个网站的繁忙季节已经到来,它似乎并没有很好地扩展,而且我希望提高效率。最多可以有12个不同的组,每个组中有许多不同的属性。
数据可以以任何方式传递,但是当前是一个多分隔的字符串,使用sql函数将其解析为表。 AttributeGroupId-属性ID,属性ID | AttributeGroupId-属性ID,属性Id 例如:1-104,114 | 2-125,140 | 3-215,317
编辑:解析输入数据的示例
AttributeGroupId| Attribute Id
------------------------------
1 | 104
1 | 114
2 | 125
2 | 140
3 | 215
3 | 317
没有深入了解当前的行动,有关如何最好地解决这个问题的任何建议吗?
答案 0 :(得分:0)
根据您拥有的属性数量,您可以使用junk dimension策略。单列中会有重复项,但所有列的组合都是唯一的。换句话说,你有红色的多个记录,但你只有一个红/长/丝。
记录数是所有列中唯一组合的数量 所有过滤器都将针对同一个表,只有一个连接到ProductAttribute表。在您的示例中,您可以使每个组成为颜色等列。在单个列中,他们可以使用多值列表进行“或”选择。跨列将是“AND”选择。
Select AttributesID, Color, Length, Material From Attributes
Where Color in (@Color) and Length in (@Length) AND Material in (@Material)
答案 1 :(得分:0)
在您的存储过程中,您可以将过滤器逻辑绑定在重 where语句中,该语句将每个组的属性与OR匹配,然后将AND与其他组匹配。
注意:像这样的查询很可能将查询优化器发送到轨道,并且会发生可怕的事情。如果您确实实现了类似的功能,则需要设置WITH RECOMPILE
选项或使用OPTION (OPTIMIZE FOR UNKNOWN)
基本上,在where子句中,您希望排除不在组中的每个项目,或者它在组中,并且存在过滤器并且过滤器匹配。
DECLARE @Attributes TABLE(AttributeGroupID INT,AttributeID INT)
INSERT @Attributes SELECT 1,104--COLOR
INSERT @Attributes SELECT 1,114--COLOR
INSERT @Attributes SELECT 2,125--LENGTH
INSERT @Attributes SELECT 2,140--LENGTH
INSERT @Attributes SELECT 3,215--MATERIAL
INSERT @Attributes SELECT 3,317--MATERIAL
DECLARE @AttributeGroup TABLE(AttributeGroupID INT)
INSERT @AttributeGroup SELECT 1--COLOR
INSERT @AttributeGroup SELECT 2--LENGTH
INSERT @AttributeGroup SELECT 3--MATERIAL
DECLARE @Product TABLE(ProductID INT)
INSERT @Product SELECT 1
INSERT @Product SELECT 2
DECLARE @ProductAttribute TABLE(ProductID INT,AttributeID INT)
INSERT @ProductAttribute SELECT 1,104
INSERT @ProductAttribute SELECT 1,125
INSERT @ProductAttribute SELECT 1,317
INSERT @ProductAttribute SELECT 2,114
INSERT @ProductAttribute SELECT 2,125
INSERT @ProductAttribute SELECT 2,215
DECLARE @Filter TABLE(AttributeGroupID INT,AttributeID INT)
INSERT @Filter SELECT 1,104
INSERT @Filter SELECT 2,125
--INSERT @Filter SELECT 3,317
SELECT
P.ProductID,
MatchingAttrinuteCount=COUNT(*)
FROM
@Product P
INNER JOIN @ProductAttribute PA ON PA.ProductID=P.ProductID
INNER JOIN @Attributes A ON A.AttributeID=PA.AttributeID
WHERE
(A.AttributeGroupID<>1 OR(A.AttributeGroupID=1 AND ( (NOT(EXISTS(SELECT * FROM @Filter WHERE AttributeGroupID=1))) OR PA.AttributeID IN(SELECT F.AttributeID FROM @Filter F WHERE F.AttributeGroupID=1))))
AND
(A.AttributeGroupID<>2 OR(A.AttributeGroupID=2 AND ( (NOT(EXISTS(SELECT * FROM @Filter WHERE AttributeGroupID=2))) OR PA.AttributeID IN(SELECT F.AttributeID FROM @Filter F WHERE F.AttributeGroupID=2))))
AND
(A.AttributeGroupID<>3 OR(A.AttributeGroupID=3 AND ( (NOT(EXISTS(SELECT * FROM @Filter WHERE AttributeGroupID=3))) OR PA.AttributeID IN(SELECT F.AttributeID FROM @Filter F WHERE F.AttributeGroupID=3))))
GROUP BY
P.ProductID
上面的查询将产生:
ProductID MatchingAttrinuteCount
1 3
2 2
第二种可能更高效的方法......
回避游标通常是一个好主意,但是,这是游标证明有用的时代之一。注意:您将把满足遇到的第一个条件的所有产品加载到内存中作为最大集合。如果你认为这个值不会很大,那么下面的光标逻辑应该更加紧凑和高效。
DECLARE @Filter TABLE(AttributeGroupID INT,AttributeID INT)
INSERT @Filter SELECT 1,104
INSERT @Filter SELECT 2,125
INSERT @Filter SELECT 1,114
SET NOCOUNT ON
DECLARE @Matches TABLE(ProductID INT,AttributeID INT)
DECLARE @AttributeID INT, @AttributeGroupID INT, @HasLooped BIT=0
--GROUPS TO LOOP FOR
DECLARE ATTRIBUTE_GROUP_CURSOR CURSOR FOR SELECT DISTINCT AttributeGroupID FROM @Filter
OPEN ATTRIBUTE_GROUP_CURSOR
FETCH NEXT FROM ATTRIBUTE_GROUP_CURSOR INTO @AttributeGroupID
--FOR EACH GROUP [COLOR..MATERIAL] IN THE FILTER
WHILE(@@FETCH_STATUS=0)BEGIN
INSERT @Matches
--INSERT ALL PRODUCTS WITH MATCHING ATTRIBUTES IN THIS GROUP
--IF FIRST LOOP EXAMINE ALL PRODUCTS IF NOT EXAMINE ONLY PRODUCTS ALREADY IN MATCH SET
SELECT P.ProductID, PA.AttributeID
FROM @Product P
INNER JOIN @ProductAttribute PA ON PA.ProductID=P.ProductID
WHERE
AttributeID IN (SELECT AttributeID FROM @Filter F WHERE F.AttributeGroupID=@AttributeGroupID )
AND
(@HasLooped=0 OR P.ProductID IN (SELECT ProductID FROM @Matches)) --Only match products if one filter has been applied
SET @HasLooped=1
FETCH NEXT FROM ATTRIBUTE_GROUP_CURSOR INTO @AttributeGroupID
END
CLOSE ATTRIBUTE_GROUP_CURSOR
DEALLOCATE ATTRIBUTE_GROUP_CURSOR
SELECT * FROM @Matches
...屈服
ProductID AttributeID
1 104
2 114
1 125
2 125