存储过程以优先级基于可空列返回数据

JBo*_*ond 5 sql t-sql sql-server stored-procedures

我有一个名为ClientUrls具有以下结构的表:

+------------+----------------+----------+
| ColumnName |    DataType    | Nullable |
+------------+----------------+----------+
| ClientId   | INT            | No       |
| CountryId  | INT            | Yes      |
| RegionId   | INT            | Yes      |
| LanguageId | INT            | Yes      |
| URL        | NVARCHAR(2048) | NO       |
+------------+----------------+----------+
Run Code Online (Sandbox Code Playgroud)

我有一个存储过程up_GetClientUrls,它采用以下参数:

@ClientId INT
@CountryId INT
@RegionId INT
@LanguageId INT
Run Code Online (Sandbox Code Playgroud)

关于过程的信息

  1. proc需要所有参数,它们都不是NULL
  2. proc的目的是根据预定义的优先级返回表中的单个匹配行.优先级为ClientId>国家>地区>语言
  3. ClientUrls表中的三个列可以为空.如果一列包含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       |
+---------+----------+-----------+----------+------------+
Run Code Online (Sandbox Code Playgroud)

以下是一些示例数据:

+----------+-----------+----------+------------+-------------------------------+
| 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 |
+----------+-----------+----------+------------+-------------------------------+
Run Code Online (Sandbox Code Playgroud)

示例存储过程调用

EXEC up_GetClientUrls   @ClientId = 1
                        ,@CountryId = 1
                        ,@RegionId = 1
                        ,@LanguageId = 2
Run Code Online (Sandbox Code Playgroud)

预期响应(基于示例数据)

+----------+-----------+----------+------------+-------------------------------+
| ClientId | CountryId | RegionId | LanguageId |             URL               |
+----------+-----------+----------+------------+-------------------------------+
|        1 |         1 |     NULL | 2          | http://www.Anotherwebsite.com |
+----------+-----------+----------+------------+-------------------------------+
Run Code Online (Sandbox Code Playgroud)

返回此行是因为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
Run Code Online (Sandbox Code Playgroud)

Pau*_*ain 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
Run Code Online (Sandbox Code Playgroud)

就是这样。在旧版本的 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
+---------+----------+-----------+----------+------------+
Run Code Online (Sandbox Code Playgroud)

我刚刚更新了此内容,以确保您通过空选项获得非空版本。

如果您编辑结果以返回比前 1 个更多的内容,您可以看到按正确顺序排列的项目。即,如果将语言从 2 更改为 1,您将在 1,1,1,Null 选项上获得 1,1,1,1 行