[英]SQL Server query by column pair
我正在开发像亚马逊这样的产品过滤器(分面搜索)。 我有一个包含属性(颜色,ram,屏幕)的表,如下所示:
ArticleID PropertyID Value
--------- ---------- ------------
1 1 Black
1 2 8 GB
1 3 15"
2 1 White
2 2 8 GB
3 3 13"
我必须根据选择的属性选择文章。 您可以为一个属性选择多个值(例如RAM:4 GB和8 GB),您可以选择多个属性(例如RAM和屏幕大小)。
我需要这样的功能:
SELECT ArticleID
FROM ArticlesProperties
WHERE (PropertyID = 2 AND Value IN ('4 GB', '8 GB'))
AND (PropertyID = 3 AND Value IN ('13"'))
我曾经通过创建动态查询然后执行该查询来做到这一点:
SELECT ArticleID
FROM ArticlesProperties
WHERE PropertyID = 2 AND Value IN ('4 GB', '8 GB')
INTERSECT
SELECT ArticleID
FROM ArticlesProperties
WHERE PropertyID = 3 AND Value IN ('13"')
但我不认为这是好方法,必须有一些更好的解决方案。 表中有数百万个属性,因此需要进行优化。
解决方案应该适用于SQL Server 2014 Standard Edition,而不需要一些附加组件或搜索引擎,如solr
等。
我在泡菜中,所以如果有人有一些想法或解决方案,我会非常感激。 谢谢!
intersect
可能会很好。
另一种方法是构造一个where
子句并使用聚合并having
:
SELECT ArticleID
FROM ArticlesProperties
WHERE ( PropertyID = 2 AND Value IN ('4 GB', '8 GB') ) OR
( PropertyID = 3 AND Value IN ('13"') )
GROUP BY ArticleId
HAVING COUNT(DISTINCT PropertyId) = 2;
但是, INTERSECT
方法可能会更好地使用ArticlesProperties(PropertyId, Value)
上的索引,因此首先尝试查看替代方案必须达到的性能。
我制作了一个片段,展示了我将要工作的线条。 良好的指数选择对于加快查询非常重要。 始终检查执行计划以调整索引。
笔记:
该脚本使用临时表,但实质上它们与常规表没有区别。 除#select_properties
外,如果您计划使用脚本中概述的工作方式,则临时表应成为常规表。
存储文章属性,其中包含属性选择值的ID,而不是实际的选择值。 这节省了SQL Server缓存这些表时的磁盘空间和内存。 SQL Server将尽可能多地在内存中缓存表,以便更快地为select语句提供服务。
如果文章属性表太大,则SQL Server可能必须执行磁盘IO才能执行select语句,这肯定会降低语句的速度。
额外的好处是,对于查找,您正在寻找ID(整数)而不是文本( VARCHAR
的)。 查找整数比查找字符串快得多。
在表上提供合适的索引以加速查询。 为此,通过检查实际执行计划来分析查询是一种很好的做法。
我在下面的代码段中包含了几个这样的索引。 根据文章属性表和统计信息中的行数,SQL Server将选择最佳索引来加速查询。
如果SQL Server认为查询缺少SQL语句的正确索引,则实际执行计划将指示您缺少索引。 优秀的做法是,当您的查询变慢时,通过检查SQL Server Management Studio中的实际执行计划来分析这些查询。
该代码段使用临时表来指定您要查找的属性: #select_properties
。 通过插入属性ID和属性选择值ID来提供该表中的条件。 最终选择查询选择至少一个属性选择值适用于每个属性的文章。
您可以在要在其中选择文章的会话中创建此临时表。 然后插入搜索条件,触发select语句,最后删除临时表。
CREATE TABLE #articles(
article_id INT NOT NULL,
article_desc VARCHAR(128) NOT NULL,
CONSTRAINT PK_articles PRIMARY KEY CLUSTERED(article_id)
);
CREATE TABLE #properties(
property_id INT NOT NULL, -- color, size, capacity
property_desc VARCHAR(128) NOT NULL,
CONSTRAINT PK_properties PRIMARY KEY CLUSTERED(property_id)
);
CREATE TABLE #property_values(
property_id INT NOT NULL,
property_choice_id INT NOT NULL, -- eg color -> black, white, red
property_choice_val VARCHAR(128) NOT NULL,
CONSTRAINT PK_property_values PRIMARY KEY CLUSTERED(property_id,property_choice_id),
CONSTRAINT FK_values_to_properties FOREIGN KEY (property_id) REFERENCES #properties(property_id)
);
CREATE TABLE #article_properties(
article_id INT NOT NULL,
property_id INT NOT NULL,
property_choice_id INT NOT NULL
CONSTRAINT PK_article_properties PRIMARY KEY CLUSTERED(article_id,property_id,property_choice_id),
CONSTRAINT FK_ap_to_articles FOREIGN KEY (article_id) REFERENCES #articles(article_id),
CONSTRAINT FK_ap_to_property_values FOREIGN KEY (property_id,property_choice_id) REFERENCES #property_values(property_id,property_choice_id)
);
CREATE NONCLUSTERED INDEX IX_article_properties ON #article_properties(property_id,property_choice_id) INCLUDE(article_id);
INSERT INTO #properties(property_id,property_desc)VALUES
(1,'color'),(2,'capacity'),(3,'size');
INSERT INTO #property_values(property_id,property_choice_id,property_choice_val)VALUES
(1,1,'black'),(1,2,'white'),(1,3,'red'),
(2,1,'4 Gb') ,(2,2,'8 Gb') ,(2,3,'16 Gb'),
(3,1,'13"') ,(3,2,'15"') ,(3,3,'17"');
INSERT INTO #articles(article_id,article_desc)VALUES
(1,'First article'),(2,'Second article'),(3,'Third article');
-- the table you have in your question, slightly modified
INSERT INTO #article_properties(article_id,property_id,property_choice_id)VALUES
(1,1,1),(1,2,2),(1,3,2), -- article 1: color=black, capacity=8gb, size=15"
(2,1,2),(2,2,2),(2,3,1), -- article 2: color=white, capacity=8Gb, size=13"
(3,1,3), (3,3,3); -- article 3: color=red, size=17"
-- The table with the criteria you are selecting on
CREATE TABLE #select_properties(
property_id INT NOT NULL,
property_choice_id INT NOT NULL,
CONSTRAINT PK_select_properties PRIMARY KEY CLUSTERED(property_id,property_choice_id)
);
INSERT INTO #select_properties(property_id,property_choice_id)VALUES
(2,1),(2,2),(3,1); -- looking for '4Gb' or '8Gb', and size 13"
;WITH aid AS (
SELECT ap.article_id
FROM #select_properties AS sp
INNER JOIN #article_properties AS ap ON
ap.property_id=sp.property_id AND
ap.property_choice_id=sp.property_choice_id
GROUP BY ap.article_id
HAVING COUNT(DISTINCT ap.property_id)=(SELECT COUNT(DISTINCT property_id) FROM #select_properties)
-- criteria met when article has a number of properties matching, equal to the distinct number of properties in the selection set
)
SELECT a.article_id,a.article_desc
FROM aid
INNER JOIN #articles AS a ON
a.article_id=aid.article_id
ORDER BY a.article_id;
-- result is the 'Second article' with id 2
DROP TABLE #select_properties;
DROP TABLE #article_properties;
DROP TABLE #property_values;
DROP TABLE #properties;
DROP TABLE #articles;
XML参数
您的过程采用XML参数@criteria XML我用来调试的几件事:drop table #properties drop table #criteria
create table #properties (propertyId int)
insert into #properties values (1), (2) --presuming that you have a list of all the possible properties somewhere
-- This would be passed in by the application
declare @criteria XML = '<criteria>
<property id="1">
<item value="8 GB" />
<item value="4 GB" />
</property>
<property id="2">
<item value="13 in" />
<item value="4 in" />
</property>
</criteria>'
--encode the '"' and replace 'in' as needed
您需要的代码从这里开始:
create table #criteria
(propertyId int, searchvalue nvarchar(20))
insert into #criteria (propertyId, searchvalue)
select
cc.propertyId,
c.value('@value','nvarchar(20)')
from #properties cc
cross apply @criteria.nodes(N'/criteria/property[@id=sql:column("PropertyID")]/item') t(c)
SELECT ArticleID, count(1)
FROM ArticlesProperties ap
join #criteria cc on cc.propertyId = ap.propertyId and cc.searchvalue = ap.value
group by ArticleID
having count(1) = (select count(distinct propertyid from #criteria))
我假设(ArticleID, PropertyID)
是一个关键。
这看起来像实体属性值(EAV)表或“开放模式”设计,因此基本上没有好的方法来查询任何东西。 您甚至可以考虑设置动态PIVOT,但这相当复杂。
一种方法是EXISTS
表达式:
SELECT DISTINCT ArticleID
FROM ArticlesProperties ap
WHERE EXISTS (SELECT 1 FROM ArticlesProperties
WHERE ArticleID = ap.ArticleID AND PropertyID = 2 AND Value IN ('4 GB', '8 GB'))
AND (SELECT 1 FROM ArticlesProperties
WHERE ArticleID = ap.ArticleID AND PropertyID = 3 AND Value IN ('13"'));
或者你可以尝试OR
结合COUNT()
和HAVING
:
SELECT ArticleID
FROM ArticlesProperties
WHERE (PropertyID = 2 AND Value IN ('4 GB', '8 GB'))
OR (PropertyID = 3 AND Value IN ('13"'))
GROUP BY ArticleID
HAVING COUNT(PropertyID) = 2;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.