2008-12-22 168 views
1

我想在存儲過程的表上進行優先級匹配。這些要求有點棘手的解釋,但希望這是有道理的。假設我們有一張名爲書籍的表格,其中包含ID,作者,標題,日期和頁面字段。SQL優先匹配

我們還有一個存儲過程,它將查詢與表中的一行進行匹配。

這裏是PROC的簽名:

create procedure match 
    @pAuthor varchar(100) 
    ,@pTitle varchar(100) 
    ,@pDate varchar(100) 
    ,@pPages varchar(100) 

as 

... 

的優先規則如下:

  • 首先,嘗試和比賽的所有4個參數。如果我們找到匹配的回報。
  • 接下來嘗試使用任何3個參數進行匹配。第一個參數的優先級最高,第四個最低。如果我們發現任何比賽都會返回比賽。
  • 接下來我們檢查兩個參數是否匹配,最後是否匹配(仍然遵循參數順序的優先規則)。

我已經實施了這種情況。例如:

select @lvId = id 
from books 
where 
    author = @pAuthor 
,title = @pTitle 
,date = @pDate 
,pages = @pPages 

if @@rowCount = 1 begin 
    select @lvId 
    return 
end 

select @lvId = id 
    from books 
where 
    author = @pAuthor 
,title = @pTitle 
,date = @pDate 

if @@rowCount = 1 begin 
    select @lvId 
    return 
end 

.... 

然而,對於表中的每個新列,個人支票數量的增長由2的順序我真的想推廣這列X號;然而,我很難提出一個計劃。

感謝您的閱讀,我可以提供任何所需的附加信息。


補充:

Dave和其他人,我試着執行您的代碼,它是由條,在這裏我們添加所有的罪名窒息的首份訂單。它給了我一個無效的列名錯誤。當我將總數註釋出來,然後按單個別名排序時,proc編譯就會正常。

任何人有任何想法?

這是在Microsoft SQL Server 2005

+0

使用AND連接WHERE子句中的條件時發生了什麼? – 2008-12-22 23:03:25

+0

@JL這是否有趣? – 2008-12-23 19:11:48

回答

1

你不解釋,如果不止一個結果任何給定的參數達到匹配會發生什麼,所以你需要改變這個佔這些業務規則。現在我已經將它設置爲返回與那些未提供參數的參數相匹配的書。例如,作者,標題和頁面上的匹配會出現在作者和標題上的匹配之前。

您的RDBMS可能有不同的方式來處理「TOP」,因此您可能還需要進行調整。

SELECT TOP 1 
    author, 
    title, 
    date, 
    pages 
FROM 
    Books 
WHERE 
    author = @author OR 
    title = @title OR 
    date = @date OR 
    pages = @pages OR 
ORDER BY 
    CASE WHEN author = @author THEN 1 ELSE 0 END + 
    CASE WHEN title = @title THEN 1 ELSE 0 END + 
    CASE WHEN date = @date THEN 1 ELSE 0 END + 
    CASE WHEN pages = @pages THEN 1 ELSE 0 END DESC, 

    CASE WHEN author = @author THEN 8 ELSE 0 END + 
    CASE WHEN title = @title THEN 4 ELSE 0 END + 
    CASE WHEN date = @date THEN 2 ELSE 0 END + 
    CASE WHEN pages = @pages THEN 1 ELSE 0 END DESC 
+0

我認爲這不是他想要的,因爲在作者+標題上的匹配會在標題+日期+頁面上擊敗匹配。我想他想要3場比賽總是擊敗2場比賽。 – 2008-12-22 20:00:16

+0

@Dave,正確我需要3場比賽才能到達2. @湯姆,我們可以假設我們只會找到一場比賽。選擇Top 1會正常工作。 – 2008-12-22 20:15:32

+0

我做了一些更改以解決此問題,但沒有WHERE子句,性能可能會成爲問題。儘管如此,我會給它更多的想法。 – 2008-12-22 20:20:29

1

我沒有時間寫出查詢,但我認爲這個想法可行。

對於您的謂詞,請使用「author = @pAuthor或title = @ptitle ...」,以便獲取所有候選行。

使用CASE表達式或任何你喜歡在結果集中創建虛擬列,如:

SELECT CASE WHEN author = @pAuthor THEN 1 ELSE 0 END author_match, 
     ... 

然後通過添加此訂單,並得到返回的第一行:

ORDER BY (author_match+title_match+date_match+page_match) DESC, 
     author_match DESC, 
     title_match DESC, 
     date_match DESC 
     page_match DESC 

你還是需要爲每個新列擴展它,但只有一點點。

0
 select id, 
       CASE WHEN @pPages = pages 
        THEN 1 ELSE 0 
       END 
      + Case WHEN @pAuthor=author 
        THEN 1 ELSE 0 
       END AS 
      /* + Do this for each attribute. If each of your 
attributes are just as important as the other 
for example matching author is jsut as a good as matching title then 
leave the values alone, if different matches are more 
important then change the values */ as MatchRank 
     from books 

     where author = @pAuthor OR 
       title = @pTitle OR 
       date = @pDate 

    ORDER BY MatchRank DESC 

編輯

當我運行此查詢(只是修改,以適應我自己的一個表),它工作正常SQL2005。

我推薦一個WHERE子句,但你會想玩玩這個,看看性能的影響。你將需要使用一個OR子句,否則你將失去潛在的匹配

0

好吧,讓我重申我對你的問題的理解:你想要一個存儲過程,可以獲取可變數量的參數並傳回匹配的頂行偏好的加權順序參數在SQL Server 2005上傳遞。

理想情況下,它將使用WHERE子句來防止全表掃描以及利用索引,並將「短路」搜索 - 您不想搜索所有可能的組合,如果可以早找到的話。也許我們也可以允許其他比較器比如=,例如> =用於日期,LIKE用於字符串等。

一種可能的方式是像在this article中那樣傳遞XML參數並使用.Net存儲過程,但讓我們保持原樣現在是香草T-SQL。

這看起來對我來說,在參數二進制搜索:搜索所有參數,然後刪除最後一個,然後掛斷第二個最後一個,但包括最後一個,等

讓我們傳遞的參數作爲由於存儲過程不允許將數組作爲參數傳遞,因此使用分隔字符串。這將允許我們在我們的存儲過程中獲取可變數量的參數,而不需要爲每個參數變化存儲過程。

爲了讓任何形式的比較,我們會通過整個WHERE子句列表,就像這樣:標題LIKE「%%的東西」

傳遞多個參數指在一個字符串界定他們。我們將使用代字符〜字符來分隔參數,如下所示:author ='Chris Latta'〜title like'%something%'〜pages> = 100

然後,這只是一個做二進制加權的問題搜索滿足我們的有序參數列表的第一行(希望存儲過程帶有註釋是不言自明的,但如果沒有,請告訴我)。請注意,總是保證結果(假定您的表至少有一行),因爲最後的搜索是無參數的。

這裏是存儲過程的代碼:

CREATE PROCEDURE FirstMatch 
@SearchParams VARCHAR(2000) 
AS 
BEGIN 
    DECLARE @SQLstmt NVARCHAR(2000) 
    DECLARE @WhereClause NVARCHAR(2000) 
    DECLARE @OrderByClause NVARCHAR(500) 
    DECLARE @NumParams INT 
    DECLARE @Pos INT 
    DECLARE @BinarySearch INT 
    DECLARE @Rows INT 

    -- Create a temporary table to store our parameters 
    CREATE TABLE #params 
    (
     BitMask int,    -- Uniquely identifying bit mask 
     FieldName VARCHAR(100), -- The field name for use in the ORDER BY clause 
     WhereClause VARCHAR(100) -- The bit to use in the WHERE clause 
    ) 

    -- Temporary table identical to our result set (the books table) so intermediate results arent output 
    CREATE TABLE #junk 
    (
     id INT, 
     author VARCHAR(50), 
     title VARCHAR(50), 
     printed DATETIME, 
     pages INT 
    ) 

    -- Ill use tilde ~ as the delimiter that separates parameters 
    SET @SearchParams = LTRIM(RTRIM(@SearchParams))+ '~' 
    SET @Pos = CHARINDEX('~', @SearchParams, 1) 
    SET @NumParams = 0 

    -- Populate the #params table with the delimited parameters passed 
    IF REPLACE(@SearchParams, '~', '') <> '' 
    BEGIN 
     WHILE @Pos > 0 
     BEGIN 
      SET @NumParams = @NumParams + 1 
      SET @WhereClause = LTRIM(RTRIM(LEFT(@SearchParams, @Pos - 1))) 
      IF @WhereClause <> '' 
      BEGIN 
       -- This assumes your field names dont have spaces and that you leave a space between the field name and the comparator 
       INSERT INTO #params (BitMask, FieldName, WhereClause) VALUES (POWER(2, @NumParams - 1), LTRIM(RTRIM(LEFT(@WhereClause, CHARINDEX(' ', @WhereClause, 1) - 1))), @WhereClause) 
      END 
      SET @SearchParams = RIGHT(@SearchParams, LEN(@SearchParams) - @Pos) 
      SET @Pos = CHARINDEX('~', @SearchParams, 1) 
     END 
    END 

    -- Set the binary search to search from all parameters down to one in order of preference 
    SET @BinarySearch = POWER(2, @NumParams) 
    SET @Rows = 0 
    WHILE (@BinarySearch > 0) AND (@Rows = 0) 
    BEGIN 
     SET @BinarySearch = @BinarySearch - 1 
     SET @WhereClause = ' WHERE ' 
     SET @OrderByClause = ' ORDER BY ' 
     SELECT @OrderByClause = @OrderByClause + FieldName + ', ' FROM #params WHERE (@BinarySearch & BitMask) = BitMask ORDER BY BitMask 
     SET @OrderByClause = LEFT(@OrderByClause, LEN(@OrderByClause) - 1) -- Remove the trailing comma 
     SELECT @WhereClause = @WhereClause + WhereClause + ' AND ' FROM #params WHERE (@BinarySearch & BitMask) = BitMask ORDER BY BitMask 
     SET @WhereClause = LEFT(@WhereClause, LEN(@WhereClause) - 4) -- Remove the trailing AND 

     IF @BinarySearch = 0 
     BEGIN 
      -- If nothing found so far, return the top row in the order of the parameters fields 
      SET @WhereClause = '' 
      -- Use the full order sequence of fields to return the results 
      SET @OrderByClause = ' ORDER BY ' 
      SELECT @OrderByClause = @OrderByClause + FieldName + ', ' FROM #params ORDER BY BitMask 
      SET @OrderByClause = LEFT(@OrderByClause, LEN(@OrderByClause) - 1) -- Remove the trailing comma 
     END 

     -- Find out if there are any results for this search 
     SET @SQLstmt = 'SELECT TOP 1 id, author, title, printed, pages INTO #junk FROM books' + @WhereClause + @OrderByClause 
     Exec (@SQLstmt) 

     SET @Rows = @@RowCount 
    END 

    -- Stop the result set being eaten by the junk table 
    SET @SQLstmt = REPLACE(@SQLstmt, 'INTO #junk ', '') 

    -- Uncomment the next line to see the SQL you are producing 
    --PRINT @SQLstmt 

    -- This gives the result set 
    Exec (@SQLstmt) 
END 

此存儲過程稱爲像這樣:

FirstMatch 'author = ''Chris Latta''~pages > 100~title like ''%something%''' 

有你有它 - 對頂級結果加權一個完全可擴展,優化搜索優先順序。這是一個有趣的問題,並展示了您可以使用本機T-SQL實現的功能。

跟這個有幾個小問題:

  • 它依賴於調用者知道,他們必須在字段名稱後留出空間爲參數正常工作
  • 你不能有場用空格名字在其中 - 可以解決的一些努力
  • 它假定相關的排序順序總是上升
  • 下一個程序員有來看待這個過程會覺得你瘋了:)
0

試試這個:

ALTER PROCEDURE match 
    @pAuthor varchar(100) 
,@pTitle varchar(100) 
,@pDate varchar(100) 
,@pPages varchar(100) 
-- exec match 'a title', 'b author', '1/1/2007', 15 
AS 

SELECT id, 

     CASE WHEN author = @pAuthor THEN 1 ELSE 0 END 
     + CASE WHEN title = @pTitle THEN 1 ELSE 0 END 
     + CASE WHEN bookdate = @pDate THEN 1 ELSE 0 END 
     + CASE WHEN pages = @pPages THEN 1 ELSE 0 END AS matches, 

     CASE WHEN author = @pAuthor THEN 4 ELSE 0 END 
     + CASE WHEN title = @pTitle THEN 3 ELSE 0 END 
     + CASE WHEN bookdate = @pDate THEN 2 ELSE 0 END 
     + CASE WHEN pages = @pPages THEN 1 ELSE 0 END AS score 
FROM books 
WHERE author = #pAuthor 
    OR title = @pTitle 
    OR bookdate = @PDate 
    OR pages = @pPages 
ORDER BY matches DESC, score DESC 

然而,這當然會導致表掃描。你可以通過將它作爲一個CTE和4個WHERE子句的聯合來避免這種情況,每個屬性都有一個 - 將會有重複,但是無論如何你都可以進入前1名。

編輯:添加了WHERE ... OR子句。我會覺得更舒服,如果它是

SELECT ... FROM books WHERE author = @pAuthor 
UNION 
SELECT ... FROM books WHERE title = @pTitle 
UNION 
... 
0

在問候ORDER BY子句未能編譯:

遞歸說,(在評論),別名可能不被使用的表達式中在Order By子句中。爲了解決這個問題,我使用了一個返回行的子查詢,然後在外部查詢中排序。通過這種方式,我可以在order by子句中使用別名。慢一點,但更清潔。

2

我相信你工作的答案是迄今爲止最簡單的。但我也相信,在SQL服務器中,他們將始終是全表掃描。 (在Oracle中,如果表沒有經過大量的同時DML,則可以使用位圖索引)

更復雜的解決方案,但性能更高的方法是構建自己的索引。不是SQL Server索引,而是您自己的。

創建一個表(哈希索引)3列(查找,哈希,職級,ROWID)

假設你有3列搜索上。 A,B,C

對於添加到書籍的每一行,您都會通過觸發器或CRUD proc將7行插入到hash_index中。

首先,您

insert into hash_index 
SELECT HASH(A & B & C), 7 , ROWID 
FROM Books 

哪裏&是連接運算符和哈希函數

那麼你會插入散列對於A & B,A & C和B & C. 你現在有一些靈活性,你可以給他們所有的相同的等級,或者如果A B & C的優勢匹配,你可以給他們一個更高的等級。

然後插入散列爲A本身和B和C與相同的等級選擇...所有相同的數字或所有不同的...你甚至可以說A上的一個匹配比一個匹配更高的選擇在B & C.這種解決方案給你很大的靈活性。

當然,這會增加很多INSERT開銷,但是如果書上的DML很低或者性能不相關,那就很好。

現在,當您去搜索時,您將創建一個函數,爲您的@A,@B和@C返回一個HASH表。您將有一個包含7個值的小表,您將加入哈希索引表中的查找哈希。這會給你一切可能的匹配,並可能有一些錯誤的匹配(這只是散列的性質)。你會得到這個結果,在秩列上訂購desc。然後將第一個rowid返回到書本表,並確保@A @B @C的所有值實際上都在該行中。如果不是這樣,你就會被誤判,你需要檢查下一個rowid。

這個「滾動你自己」的每一個操作都非常快。

  • 把你的3個值散列成一個小的7行表變量=非常快。
  • 加入他們的指數在Hash_index表=非常快速索引查找
  • 遍歷結果集將導致1個或可能通過ROWID =非常快

當然2次或3臺訪問,所有其中的一些可能會比FTS慢......但是FTS將繼續變得越來越慢。 FTS的規模會比這慢。你必須玩它。