2015-08-27 64 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