根据给定的声誉获取最接近的特权

时间:2019-03-19 12:16:06

标签: sql sql-server sql-server-2012

我有一个业务案例,其中很少有案例。让我用Stack Overflow Privileges数据来解释它。

请找到特权架构的创建和示例数据:https://rextester.com/KQZS91498

基于给定的信誉输入,我需要找到最近的特权。即最接近的特权,它小于接下来可以实现的给定信誉和特权。

示例:如果我提供7276的声誉,则需要以下特权作为输出:

Id  Reputation  PrivilegeName
------------------------------------------
22  5000        approve tag wiki edits
23  10000       access to moderator tools

我正在使用以下查询来实现它。

DECLARE @MyReputation AS INT = 7276;

DECLARE @FromId AS INT = 0;
DECLARE @ToId AS INT = 0;

SELECT @FromId = MAX(Id) FROM @Privilege
WHERE Reputation > 0 AND Reputation <= @MyReputation;

SELECT @ToId = MIN(Id) FROM @Privilege
WHERE @MyReputation > 0 AND @MyReputation < Reputation;

SELECT Id, Reputation, PrivilegeName
FROM @Privilege
WHERE Id IN (@FromId, @ToId)
ORDER BY Id;

它给了我预期的结果。

请找到相同的可执行查询:https://rextester.com/PDEXOM92503


由于我正在使用聚合函数获取ID,因此,如果信誉具有多个特权,则无法使用。

这里的声誉 10 包含两个特权。并且将来有可能拥有三个或更多特权。

因此,期望是,如果我提供的信誉输入为 10 13 ,则需要以下输出:

Id  Reputation  PrivilegeName
----------------------------------------------
3   10          remove new user restrictions
4   10          create wiki posts
5   15          flag posts
6   15          vote up

对于 945 的信誉输入:

Id  Reputation  PrivilegeName
------------------------------------------
15  500         access review queues
16  1000        established user
17  1000        create gallery chat rooms

即,基于给定信誉输入的最接近的特权。

此案例适用于 100 1000 之类的信誉,该信誉对于同一信誉具有多个特权。

达到上述期望的最佳方法是什么?

3 个答案:

答案 0 :(得分:2)

您可以使用两个ORed NOT EXISTS条件两个过滤相关记录:

SELECT *
FROM @Privilege t
WHERE 
    (t.reputation > @MyReputation AND NOT EXISTS (
        SELECT 1 
        FROM @Privilege t1 
        WHERE t1.reputation > @MyReputation AND t1.reputation < t.reputation
    ))
    OR (t.reputation <= @MyReputation AND  NOT EXISTS (
        SELECT 1 
        FROM @Privilege t2
        WHERE t2.reputation <= @MyReputation AND t2.reputation > t.reputation
    ));

答案 1 :(得分:2)

您也可以像使用UNION ALL一样进行操作。

;WITH cte 
     AS (SELECT *, 
                reputation - @MyReputation DIFF 
         FROM   @Privilege) 
SELECT * 
FROM   cte 
WHERE  diff = (SELECT Max(diff) 
               FROM   cte 
               WHERE  diff <= 0) 
UNION ALL
SELECT * 
FROM   cte 
WHERE  diff = (SELECT Min(diff) 
               FROM   cte 
               WHERE  diff > 0) 

Online Demo

上面的查询也可以使用CASE WHEN来编写,如下所示

;WITH cte 
     AS (SELECT *, 
                reputation - @MyReputation diff, 
                Max(CASE 
                      WHEN reputation - @MyReputation <= 0 THEN 
                      reputation - @MyReputation 
                    END) 
                  OVER()                   mind, 
                Min(CASE 
                      WHEN reputation - @MyReputation > 0 THEN 
                      reputation - @MyReputation 
                    END) 
                  OVER()                   maxd 
         FROM   @Privilege) 

SELECT Id,Reputation,PrivilegeName,DetailedDescription 
FROM   cte 
WHERE  diff in (mind,maxd) 

答案 2 :(得分:2)

我在这里使用TOP(1) WITH TIES。不需要子查询。

您不能将带有ORDER BY的两个查询直接放入UNION ALL中,因此我将它们包装在CTE中。

WITH
CTE1
AS
(
    SELECT TOP(1) WITH TIES
        *
    FROM @Privilege
    WHERE Reputation <= @MyReputation
    ORDER BY Reputation DESC
)
,CTE2
AS
(
    SELECT TOP(1) WITH TIES
        *
    FROM @Privilege
    WHERE Reputation > @MyReputation
    ORDER BY Reputation ASC
)
SELECT *
FROM CTE1

UNION ALL

SELECT *
FROM CTE2
;