[英]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.