Sql 按优先级返回基于可空列的数据的存储过程
我有一个名为Sql 按优先级返回基于可空列的数据的存储过程,sql,sql-server,tsql,stored-procedures,Sql,Sql Server,Tsql,Stored Procedures,我有一个名为ClientUrls的表,其结构如下: +------------+----------------+----------+ | ColumnName | DataType | Nullable | +------------+----------------+----------+ | ClientId | INT | No | | CountryId | INT | Yes | | RegionI
ClientUrls
的表,其结构如下:
+------------+----------------+----------+
| ColumnName | DataType | Nullable |
+------------+----------------+----------+
| ClientId | INT | No |
| CountryId | INT | Yes |
| RegionId | INT | Yes |
| LanguageId | INT | Yes |
| URL | NVARCHAR(2048) | NO |
+------------+----------------+----------+
我有一个存储过程up\u GetClientUrls
,它接受以下参数:
@ClientId INT
@CountryId INT
@RegionId INT
@LanguageId INT
有关程序的信息
+----------+-----------+----------+------------+-------------------------------+
| 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
预期响应(基于示例数据)
返回此行是因为在空RegionId上匹配正确的LanguageId比在空LanguageId上匹配正确的RegionId优先级更高
下面是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
您可以将where子句更改为:
AND (c.CountryID = @CountryID OR c.CountryID IS NULL)
编码方面,它的代码更少。
但调整问题更大。未经测试,但您可以执行以下操作:
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
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
通过为每个匹配项指定一个值并选择最高值,可以减少所需的代码。但您将增加所需案例陈述的数量,而不是工会的数量
这也可以是一个函数,而不是存储过程。因此,它可以更容易地用于其他查询的另一种方法可能是尝试操纵
NULL
值来创建继承权,如下所示:
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
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
这很容易(如果我理解正确的话)。您只需要很少的代码就可以做到这一点。此外,如果需要,它将很容易扩展
下面是一个工作示例
--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语句替换IIF
它是这样工作的
每个匹配项都有一个值(有点像二进制数)
然后根据每个匹配项,我们使用值或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选项获得非null版本
如果编辑结果以返回多于前1的结果,则可以按正确顺序查看项目。即,如果将语言从2更改为1,您将在1,1,1空选项上获得1,1,1行您的客户端id在表中是唯一的吗?我的意思是如果我能得到
[Client\u id 1 Country 1],[Client\u id 1,Country 2]
ClientId,Country id,RegionId和LanguageId都应该是唯一的(包括空值是唯一的)。你使用TOP 1
我还应该在你当前的代码中检查TOP 1和TIES
,因为在不知道索引的情况下,它就不那么明显了。您可以使用具有相同排列的CASE语句(即,当ClientID=@@ClientID和CountryID=@@CountryID等时,第一个将是CASE语句。最后一个将有3个为null)。我认为你会有更好的表现,但我不确定。回答很好+1用于IIF的使用。我还没见过那个函数。这个查询的性能也比我的UNION语句好。我知道IIF函数,但我从来没有使用过它,因为我对case语句已经习以为常了。我真的应该开始使用它了。另外,与将空匹配与精确匹配分开一样,这使得它更易于维护+1感谢您的评论。1,2,4,8。。。在谈论优先事项时,技巧是一件容易记住的事情。如果您有两个优先级相同的项目,它甚至可以工作,并且很容易扩展或收回,因为这些数字只在计算中使用过。