2011-05-19 61 views
1

我在網站中實現了一種搜索機制,並偶然發現了它的SQL方面。如何有條件地加入表格?

用戶可以通過這些過濾器的任意組合搜索故事:故事標題,故事標籤或故事作者的用戶名。如果未提供過濾器,則只返回所有故事。

我立即解決,這是此存儲過程:

(
@TitleFilter varchar(50) = NULL 
,@TagFilter varchar(30) = NULL 
,@UserFilter varchar(30) = NULL 
) 

SELECT 
    story.Title 
    ,story.AddedDTS 

FROM 
    Stories story 
    INNER JOIN FREETEXTTABLE(Stories, Title, @TitleFilter) ft 
     ON ft.[key] = story.ID 
    LEFT JOIN StoryTags st 
     ON st.StoryID = story.ID 
    LEFT JOIN Tags tag 
     ON tag.ID = st.TagID 
    LEFT JOIN StoryUser su 
     ON su.StoryID = story.ID 
    LEFT JOIN Users u 
     ON u.ID = su.UserID 

WHERE 
    1=1 
    AND (
      (@TagFilter IS NULL AND @UserFilter IS NULL) 
      OR (@TagFilter IS NOT NULL AND tag.Name = @TagFilter) 
      OR (@UserFilter IS NOT NULL AND u.Username = @UserFilter) 
     ) 

有一對夫婦的問題,這一點,雖然,我還沒有找到一個更好的方法。

首先,在Stories表啓用了全文搜索,所以我必須使用FREETEXTTABLE機制,強制要求謂語不NULL,所以這個存儲過程不會@TitleFilter組工作NULL。其次,如果我只按標題搜索,那麼加入StoryTagsTagsStoryUsersUsers表格只是一個無用的開銷。

所以問題是我說的兩點:我可以有條件地省略連接來優化執行時間嗎?

如果還有一個完全不同的方法,歡迎您分享它;我主張開箱即用。

回答

1

鑑於當@TitleFilter爲null它不適用於FREETEXTTABLE我會打破它與if語句像這樣。

IF @TitleFilter is not null 

    SELECT 

     story.Title 
     ,story.AddedDTS 

    FROM 
     Stories story 
     INNER JOIN FREETEXTTABLE(Stories, Title, @TitleFilter) ft 
     ON ft.[key] = story.ID 
ELSE 

    SELECT 
     story.Title 
     ,story.AddedDTS 

    FROM 
     Stories story 
     LEFT JOIN StoryTags st 
     ON st.StoryID = story.ID 
     LEFT JOIN Tags tag 
     ON tag.ID = st.TagID 
     LEFT JOIN StoryUser su 
     ON su.StoryID = story.ID 
     LEFT JOIN Users u 
     ON u.ID = su.UserID 
    WHERE 

     (@TagFilter IS NULL AND @UserFilter IS NULL) 
     OR (@TagFilter IS NOT NULL AND tag.Name = @TagFilter) 
     OR (@UserFilter IS NOT NULL AND u.Username = @UserFilter) 

或者如果冒犯了您由於某種原因,總有The Curse and Blessings of Dynamic SQL

+0

LOL其實都得罪我=) 如果我使用'IF'聲明,我必須有一個條件對於每種可能的過濾器組合(例如,您的ELSE不會按照其他過濾器的標題進行過濾)。 而動態SQL是我最後一個解決這個問題的手段;我很想找到一些不那麼邪惡的東西。 – BeemerGuy 2011-05-19 20:59:09

+0

我給你的答案 - 我使用了「IF-ELSE」和參數化動態SQL的組合,指的是鏈接中的樣本。謝謝。 – BeemerGuy 2011-05-20 14:39:27

2

沒有說,你在你的比較聯接和WHERE子句必須真正涉及表列。嘗試這樣的:

declare @useTable1 bit -- set to 0/1 to indicate whether it should be used. 
declare @useTable2 bit -- set to 0/1 to indicate whether it should be used. 
declare @useTable3 bit -- set to 0/1 to indicate whether it should be used. 

select * 
from  requiredTable t 
left join optionalTable_1 t1 on t1.requiredTableID = t.ID and @useTable1 = 1 
left join optionalTable_2 t2 on t2.requiredTableID = t.ID and @useTable2 = 1 
left join optionalTable_3 t3 on t3.requiredTableID = t.ID and @useTable3 = 1 

SQL Server的優化器,至少,是足夠智能的短路東西基於與不變量的比較。

工程就像一個魅力。

+0

這就是我的想法。所以,我執行了執行計劃,並且發現每次加入都需要花費14%的執行時間,即使使用您的短路方法。 – BeemerGuy 2011-05-19 21:10:28

0

據我所看到的,有3個解決方案(至少):

--Solution #1 
DECLARE @TitleFilter varchar(50) = NULL 
     ,@TagFilter varchar(30) = NULL 
     ,@UserFilter varchar(30) = NULL 

IF (@TitleFilter IS NOT NULL) 
    SELECT story.Title 
      ,story.AddedDTS 
    FROM Stories AS story 
      INNER JOIN FREETEXTTABLE(Stories, Title, @TitleFilter) AS ft 
       ON story.ID = ft.[key] 
     LEFT OUTER JOIN StoryTags AS st 
      ON story.ID = st.StoryID AND (@TagFilter IS NOT NULL) 
     LEFT OUTER JOIN Tags AS tag 
      ON st.TagID = tag.ID AND (@TagFilter IS NOT NULL) 
     LEFT OUTER JOIN StoryUser AS su 
      ON story.ID = su.StoryID AND (@UserFilter IS NOT NULL) 
     LEFT OUTER JOIN Users AS u 
      ON su.UserID = u.ID AND (@UserFilter IS NOT NULL) 
    WHERE  (@TagFilter IS NULL OR tag.Name = @TagFilter) 
      AND (@UserFilter IS NULL OR u.Username = @UserFilter) 
    OPTION (RECOMPILE);--Use it in SQL 2008 R2 or later 
ELSE 
    SELECT story.Title 
      ,story.AddedDTS 
    FROM Stories AS story 
     LEFT OUTER JOIN StoryTags AS st 
      ON story.ID = st.StoryID AND (@TagFilter IS NOT NULL) 
     LEFT OUTER JOIN Tags AS tag 
      ON st.TagID = tag.ID AND (@TagFilter IS NOT NULL) 
     LEFT OUTER JOIN StoryUser AS su 
      ON story.ID = su.StoryID AND (@UserFilter IS NOT NULL) 
     LEFT OUTER JOIN Users AS u 
      ON su.UserID = u.ID AND (@UserFilter IS NOT NULL) 
    WHERE  (@TagFilter IS NULL OR tag.Name = @TagFilter) 
      AND (@UserFilter IS NULL OR u.Username = @UserFilter) 
    OPTION (RECOMPILE);--Use it in SQL 2008 R2 or later 
GO 


--Solution #2 
DECLARE @TitleFilter varchar(50) = NULL 
     ,@TagFilter varchar(30) = NULL 
     ,@UserFilter varchar(30) = NULL 

IF (@TitleFilter IS NOT NULL) 
    SELECT story.Title 
      ,story.AddedDTS 
    FROM Stories AS story 
      INNER JOIN FREETEXTTABLE(Stories, Title, @TitleFilter) AS ft 
       ON story.ID = ft.[key] 
    WHERE  (@TagFilter IS NULL OR EXISTS(SELECT 1 FROM StoryTags AS st INNER JOIN Tags AS tag ON st.TagID = tag.ID WHERE tag.Name = @TagFilter)) 
      AND (@UserFilter IS NULL OR EXISTS(SELECT 1 FROM StoryUser AS su INNER JOIN Users AS u ON su.UserID = u.ID WHERE u.Username = @UserFilter)) 
ELSE 
    SELECT story.Title 
      ,story.AddedDTS 
    FROM Stories AS story 
    WHERE  (@TagFilter IS NULL OR EXISTS(SELECT 1 FROM StoryTags AS st INNER JOIN Tags AS tag ON st.TagID = tag.ID WHERE tag.Name = @TagFilter)) 
      AND (@UserFilter IS NULL OR EXISTS(SELECT 1 FROM StoryUser AS su INNER JOIN Users AS u ON su.UserID = u.ID WHERE u.Username = @UserFilter)) 
    --Don't get confused by the execution plan. You will see StoryTags, Tags, StoryUser and Users tables with some persentage. But those tables will be used 
    --only if the corresponding filter will allow to do so (look at the Filter operator). 
    --You can use OPTION (RECOMPILE) if you want to recompile the query every time it runs. 
GO 

--Solution #3 
DECLARE @TitleFilter varchar(50) = NULL 
     ,@TagFilter varchar(30) = NULL 
     ,@UserFilter varchar(30) = NULL 

DECLARE @SqlScript nvarchar(MAX), @ParamDefinition nvarchar(512); 
SET @SqlScript = ' 
    SELECT story.Title 
      ,story.AddedDTS 
    FROM dbo.Stories AS story'; 
IF (@TitleFilter IS NOT NULL) 
    SET @SqlScript += ' 
       INNER JOIN FREETEXTTABLE(Stories, Title, @TitleFilter) AS ft 
        ON story.ID = ft.[key]'; 
IF (@TagFilter IS NOT NULL) 
    SET @SqlScript += ' 
     INNER JOIN dbo.StoryTags AS st 
      ON story.ID = st.StoryID 
     INNER JOIN dbo.Tags AS tag 
      ON st.TagID = tag.ID AND tag.Name = @TagFilter'; 

IF (@UserFilter IS NOT NULL) 
    SET @SqlScript += ' 
     INNER JOIN dbo.StoryUser AS su 
      ON story.ID = su.StoryID 
     INNER JOIN dbo.Users AS u 
      ON su.UserID = u.ID AND u.Username = @UserFilter'; 

SET @ParamDefinition = '@TitleFilter varchar(50) 
         ,@TagFilter varchar(30), 
         ,@UserFilter varchar(30)'; 

EXEC sp_executesql @SqlScript, @ParamDefinition, @TitleFilter, @TagFilter, @UserFilter; 
GO