簡體   English   中英

SQL Server按列對查詢

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

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM