2015-08-27 62 views
5

我有一個叫ClientUrls表具有以下結構:存儲過程通過優先返回基於空列數據

+------------+----------------+----------+ 
| ColumnName | DataType | Nullable | 
+------------+----------------+----------+ 
| ClientId | INT   | No  | 
| CountryId | INT   | Yes  | 
| RegionId | INT   | Yes  | 
| LanguageId | INT   | Yes  | 
| URL  | NVARCHAR(2048) | NO  | 
+------------+----------------+----------+ 

我有采用下列參數的存儲過程up_GetClientUrls

@ClientId INT 
@CountryId INT 
@RegionId INT 
@LanguageId INT 

有關PROC信息

  1. 所有參數都是proc所需要的,它們都不會爲NULL
  2. proc的目標是根據預定義的優先級在表中返回單個匹配行。優先級爲ClientId> Country> Region>語言
  3. ClientUrls表中的三個colums可以爲空。如果一列包含NULL,則表示「全部」。例如如果LanguageId爲NULL,則它引用「AllLanguages」。所以如果我們發送一個5的LanguageId給proc,我們首先查找它,否則我們試着找到一個NULL。

矩陣優先的(1爲第一)

+---------+----------+-----------+----------+------------+ 
| Ranking | ClientId | CountryId | RegionId | LanguageId | 
+---------+----------+-----------+----------+------------+ 
|  1 | NOT NULL | NOT NULL | NOT NULL | NOT NULL | 
|  2 | NOT NULL | NULL  | NOT NULL | NOT NULL | 
|  3 | NOT NULL | NOT NULL | NULL  | NOT NULL | 
|  4 | NOT NULL | NULL  | NULL  | NOT NULL | 
|  5 | NOT NULL | NOT NULL | NOT NULL | NULL  | 
|  6 | NOT NULL | NULL  | NOT NULL | NULL  | 
|  7 | NOT NULL | NULL  | NULL  | NULL  | 
+---------+----------+-----------+----------+------------+ 

下面是一些例子數據

+----------+-----------+----------+------------+-------------------------------+ 
| ClientId | CountryId | RegionId | LanguageId |    URL    | 
+----------+-----------+----------+------------+-------------------------------+ 
|  1 |   1 | 1  | 1   | http://www.Website.com  | 
|  1 |   1 | 1  | NULL  | http://www.Otherwebsite.com | 
|  1 |   1 | NULL  | 2   | http://www.Anotherwebsite.com | 
+----------+-----------+----------+------------+-------------------------------+ 

實施例存儲的過程呼叫

EXEC up_GetClientUrls @ClientId = 1 
         ,@CountryId = 1 
         ,@RegionId = 1 
         ,@LanguageId = 2 

預期響應(基於示例的數據)

+----------+-----------+----------+------------+-------------------------------+ 
| ClientId | CountryId | RegionId | LanguageId |    URL    | 
+----------+-----------+----------+------------+-------------------------------+ 
|  1 |   1 |  NULL | 2   | http://www.Anotherwebsite.com | 
+----------+-----------+----------+------------+-------------------------------+ 

返回該行因爲上一個NULL RegionId匹配與正確LanguageId比上具有正確RegionId一個NULL LanguageId匹配更高的優先級。

這裏是proc(它工作)的代碼。爲了真正解決我的問題,有沒有更好的方法來寫這個?如果我在將來擴展這個表,我將繼續乘以UNION語句的數量,因此它不是真正可擴展的。

實際的存儲過程

CREATE PROC up_GetClientUrls 
    (
     @ClientId  INT 
     ,@CountryId  INT 
     ,@RegionId  INT 
     ,@LanguageId INT 
    ) 
AS 
    BEGIN 

     SELECT TOP 1 
      prioritised.ClientId 
      ,prioritised.CountryId 
      ,prioritised.RegionId 
      ,prioritised.LanguageId 
      ,prioritised.URL 
     FROM 
     (
      SELECT 
       c.ClientId 
       ,c.CountryId 
       ,c.RegionId 
       ,c.LanguageId 
       ,c.URL 
       ,1 [priority] 
      FROM ClientUrls c 
      WHERE c.ClientId = @ClientId 
      AND c.CountryId = @CountryId 
      AND c.RegionId = @RegionId 
      AND c.LanguageId = @LanguageId 
      UNION 
       SELECT 
        c.ClientId 
        ,c.CountryId 
        ,c.RegionId 
        ,c.LanguageId 
        ,c.URL 
        ,2 [priority] 
       FROM ClientUrls c 
       WHERE c.ClientId = @ClientId 
       AND c.CountryId IS NULL 
       AND c.RegionId = @RegionId 
       AND c.LanguageId = @LanguageId 
      UNION 
       SELECT 
        c.ClientId 
        ,c.CountryId 
        ,c.RegionId 
        ,c.LanguageId 
        ,c.URL 
        ,3 [priority] 
       FROM ClientUrls c 
       WHERE c.ClientId = @ClientId 
       AND c.CountryId = @CountryId 
       AND c.RegionId IS NULL 
       AND c.LanguageId = @LanguageId 
      UNION 
       SELECT 
        c.ClientId 
        ,c.CountryId 
        ,c.RegionId 
        ,c.LanguageId 
        ,c.URL 
        ,4 [priority] 
       FROM ClientUrls c 
       WHERE c.ClientId = @ClientId 
       AND c.CountryId IS NULL 
       AND c.RegionId IS NULL 
       AND c.LanguageId = @LanguageId 
      UNION 
       SELECT 
        c.ClientId 
        ,c.CountryId 
        ,c.RegionId 
        ,c.LanguageId 
        ,c.URL 
        ,5 [priority] 
       FROM ClientUrls c 
       WHERE c.ClientId = @ClientId 
       AND c.CountryId = @CountryId 
       AND c.RegionId = @RegionId 
       AND c.LanguageId IS NULL 
      UNION 
       SELECT 
        c.ClientId 
        ,c.CountryId 
        ,c.RegionId 
        ,c.LanguageId 
        ,c.URL 
        ,6 [priority] 
       FROM ClientUrls c 
       WHERE c.ClientId = @ClientId 
       AND c.CountryId IS NULL 
       AND c.RegionId = @RegionId 
       AND c.LanguageId IS NULL 
      UNION 
       SELECT 
        c.ClientId 
        ,c.CountryId 
        ,c.RegionId 
        ,c.LanguageId 
        ,c.URL 
        ,7 [priority] 
       FROM ClientUrls c 
       WHERE c.ClientId = @ClientId 
       AND c.CountryId IS NULL 
       AND c.RegionId IS NULL 
       AND c.LanguageId IS NULL 
     ) prioritised 
     ORDER BY prioritised.[Priority] 

    END 
+0

您的客戶端ID是否在表中是唯一的?我的意思是如果我可以得到'[Client_id 1 Country 1],[client_id 1,country 2]' – lad2025

+0

ClientId,CountryId,RegionId和LanguageId應該都是唯一的(包括NULLS唯一)。 – JBond

+0

您使用'TOP 1'我應該檢查當前代碼中的'TOP 1 WITH TIES',因爲不知道您的索引是不是很明顯。 – lad2025

回答

2

這是容易的(如果我理解正確的話)。你可以用很少的代碼來完成。另外,如果需要,它很容易擴展。

這裏是一個工作示例

--Make a table 
CREATE TABLE #ClientUrls (ClientId INT NOT NULL,CountryId INT NULL,RegionId INT NULL,LanguageId INT NULL,URL NVARCHAR(2048) NOT NULL) 

--Put some data into it 
INSERT INTO #ClientUrls (ClientId, CountryId, RegionId, LanguageId, URL) 
VALUES 
(1,1,1,1,'http://www.Website.com'), 
(1,1,1,NULL,'http://www.Otherwebsite.com'), 
(1,1,NULL,2,'http://www.Anotherwebsite.com') 

--This would all be in your proc 
---------------------------------------------- 
DECLARE @ClientId  INT = 1 
DECLARE @CountryId  INT = 1 
DECLARE @RegionId  INT = 1 
DECLARE @LanguageId  INT = 2 

--This is the interesting bit 
---------------------------------------------- 
SELECT TOP 1 C.* 

FROM #ClientUrls AS C 
ORDER BY 
    --Order the ones with the best hit count near the top 
    IIF(ISNULL(C.ClientId, @ClientId) = @ClientId ,1,0) +    
    IIF(ISNULL(C.CountryId, @CountryId) = @CountryId ,2,0) + 
    IIF(ISNULL(C.RegionId, @RegionId) = @RegionId ,4,0) + 
    IIF(ISNULL(C.LanguageId,@LanguageId) = @LanguageId,8,0) DESC, 

    --Order the ones with the least nulls of each hit count near the top 
    IIF(C.ClientId IS NULL,0,1) +            
    IIF(C.CountryId IS NULL,0,2) + 
    IIF(C.RegionId IS NULL,0,4) + 
    IIF(C.LanguageId IS NULL,0,8) DESC 

DROP TABLE #ClientUrls 

完蛋了。在較舊版本的SQL中,不能使用IIF,但如果需要,可以用case語句替換它。

它的工作原理是這樣的。

給每個匹配項目一個值(有點像二進制數字) 然後基於每個匹配項目我們使用的值或0如果它不匹配 加起來我們總會選擇最好的火柴的組合。

value   1   2   4   8   Total value 
+---------+----------+-----------+----------+------------+ 
| Ranking | ClientId | CountryId | RegionId | LanguageId | 
+---------+----------+-----------+----------+------------+ 
|  1 | NOT NULL | NOT NULL | NOT NULL | NOT NULL |  15 
|  2 | NOT NULL | NULL  | NOT NULL | NOT NULL |  13 
|  3 | NOT NULL | NOT NULL | NULL  | NOT NULL |  11 
|  4 | NOT NULL | NULL  | NULL  | NOT NULL |  9 
|  5 | NOT NULL | NOT NULL | NOT NULL | NULL  |  7 
|  6 | NOT NULL | NULL  | NOT NULL | NULL  |  5 
|  7 | NOT NULL | NULL  | NULL  | NULL  |  1 
+---------+----------+-----------+----------+------------+ 

我剛剛更新了這個以確保您通過null選項獲得Non null版本。

如果您編輯結果以返回更多,然後返回頂部1,您可以按正確的順序查看項目。也就是說,如果您將語言從2更改爲1,您將獲得1,1,1,1行1,1,1,1空選項

+0

絕妙的答案! +1也可用於IIF。我還沒有遇到過這個功能。這個查詢也比我的UNION語句執行得更好。 – JBond

+0

我知道IIF的功能,但我從來沒有使用它,因爲我已經習慣了case case。我真的應該開始使用它。也就像從完全匹配中分離NULL匹配,使得它更易於維護。 +1 –

+0

感謝您的評論。在談論優先事項時,1,2,4,8 ...竅門是一個方便的事情。如果您有兩個具有相同優先級的項目,它甚至可以工作,並且由於這些數字只用於計算,所以很容易擴展或收縮。 –

0

您可以更改where子句:

AND (c.CountryID = @CountryID OR c.CountryID IS NULL) 

編碼的角度來看,它的代碼更少。 但調諧更麻煩。

1

未經測試,但你可以做這樣的事情:

SELECT TOP 1 c.ClientId, 
     c.CountryId, 
     c.RegionId, 
     c.LanguageId, 
     c.URL 
FROM ClientUrls c 
ORDER BY CASE 
      WHEN c.ClientId = @ClientId 
       THEN 1000 
      ELSE 0 
      END + 
     CASE 
      WHEN c.CountryId = @CountryId 
       THEN 200 
      WHEN c.CountryId IS NULL 
       THEN 100 
      ELSE 0 
      END + 
     CASE 
      WHEN c.RegionId = @RegionId 
       THEN 20 
      WHEN c.CountryId IS NULL 
       THEN 10 
      ELSE 0 
      END + 
     CASE 
      WHEN c.LanguageId = @LanguageId 
       THEN 2 
      WHEN c.CountryId IS NULL 
       THEN 1 
      ELSE 0 
      END DESC 

通過提供一個值,以每場比賽和選擇最高值,你可以減少所需的代碼。但是你會增加所需的案例陳述的數量,而不是工會的數量。

這也可能是一個函數而不是存儲過程。因此,它可以在其他查​​詢的

0

另一種使用更容易可能是試圖操縱NULL值來創建一個層次結構,這樣的事情:

WITH priorities as  (SELECT 
       c.ClientId 
       ,c.CountryId 
       ,c.RegionId 
       ,c.LanguageId 
       ,c.URL 
       ,COALESCE(
          NULLIF(c.CountryId,@CountryId), 
          NULLIF(c.RegionId,@RegionId), 
          NULLIF(c.LanguageId,@LanguageId), 
          1000000) 
       + ISNULL(c.CountryId,200000) 
       + ISNULL(c.RegionId,100000) 
       + COALESCE(c.CountryId,RegionId,40000) 
       + ISNULL(c.LanguageId,10000) 
       + COALESCE(c.CountryId,c.LanguageId,4000) 
       + COALESCE(c.CountryId,c.RegionId,c.LanguageId,1000) 
       [priority] 
      FROM ClientUrls c 
      WHERE c.ClientId = @ClientId 
      AND (c.CountryId = @CountryId 
      OR c.RegionId = @RegionId 
      OR c.LanguageId = @LanguageId) 
      ) 
SELECT TOP 1 ClientId,CountryId,RegionId,LanguageId,URL FROM priorities ORDER BY priority DESC