根据当前值获取历史值的计数

时间:2016-12-25 12:56:16

标签: sql sql-server tsql subquery

我想确定用户已经在他们的CURRENT排名中已经有多长时间...我在子查询中工作但是它总是让我回到用户在参数之前计算的所有排名。

     DECLARE @T As Table
(
    [User] char(1),
    [Date] date,
    [Rank] varchar(10)
)

INSERT INTO @T VALUES
('A', '2016-10-01', 'Chairman'),
('A', '2016-11-01', 'Bee'),
('A', '2016-12-01', 'Chairman'),
('A', '2017-01-01', 'Chairman'),
('B', '2016-10-01', 'Artist'),
('B', '2016-11-01', 'Artist'),
('B', '2016-12-01', 'Artist'),
('B', '2017-01-01', 'Artist')

哪个SQL语句为我提供了参数2016.12.01以下结果:

UserId - CountCurrentRankLength
A - 1 
B - 3

-- Explanation of A: because the sequence is broken on november (when parameter is december)

出现了2个新问题(我已经更改了原始问题):

  1. 它似乎解决了我原来的问题,并且将在98%的情况下工作,但正如 Gordon Linoff 所提到的,完美的解决方案是仅追溯用户的时间在他本来可以进入一个等级之前,然后升级并降级。

  2. 现在我已尝试将其放入我的桌面功能,但似乎它不喜欢 WITH 声明:

  3. 但是服务器不喜欢它,因为它似乎不允许在函数中使用:

      

    Msg 156,Level 15,State 1,Procedure fn_getRankUserAffiliation,Line   14 [批处理开始第7行]关键字“WITH”附近的语法不正确。消息   319,等级15,状态1,程序fn_getRankUserAffiliation,第14行   [批处理开始第7行]关键字'with'附近的语法不正确。如果这   statement是一个公用表表达式,一个xmlnamespaces子句或一个   更改跟踪上下文子句,必须是前面的语句   以分号结束。

    ALTER function [dbo].[fn_getRankUserAffiliation](@StartDate date)
                returns @RankUserAffiliation table(
                    UserID NVARCHAR(50),
                    MonthsInRank int
                        )
                        as
                        begin
                            declare @CurrentMonth date
                            select @CurrentMonth = dbo.fn_getFirstOfMonth(@StartDate)
                            insert into @RankUserAffiliation
                        ;WITH CTE
                            SELECT *, ROW_NUMBER() OVER(Partition BY [userid], [Rank] ORDER BY DateofValidity) CountCurrentRankAffiliation
                        FROM PDATA 
    
                        SELECT UserID, CountCurrentRankAffiliation
                        FROM CTE
                        WHERE [Dateofvalidity] = @CurrentMonth
                        return
                    end
    

3 个答案:

答案 0 :(得分:1)

很多保留关键字,但我们走了:

select t1.user, count(distinct t1.date)
from
(select 
    user, date, rank
from t t1
where date <= '2016.12.01') t1
inner join
(select
    user, rank
from t t2
where date = '2016.12.01') t2
on t1.user = t2.user
and t1.rank = t2.rank
group by t1.user;

答案 1 :(得分:1)

更新2

使用行号我不能仅计算当前的排名顺序,因此我必须完全更改查询,但我确实设法获得了预期的结果:

SELECT  [User], 
        (
        SELECT COUNT(*)
        FROM @T i
        WHERE o.[User] = i.[User]
        AND o.[Rank] = i.[Rank]
        AND o.[Date] >= i.[Date]
        AND i.[Date] >
        COALESCE 
        (
            (
            SELECT TOP 1 [Date]
            FROM @T ii
            WHERE i.[User] = ii.[User]
            AND i.[Rank] <> ii.[Rank]
            AND ii.[Date] < o.[Date]
            ORDER BY [Date] DESC
            )
        , '1900-01-01')
        ) As CountCurrentRankLength
FROM @T o
WHERE [Date] = '2016-12-01'
ORDER BY [User], [Date]

第一个内部查询(count(*))基本上与行号相同。但是,使用第二个内部查询它只计算rank顺序的行数 - 这是某种东西我无法使用常规的row number函数。

更新

如果CTE导致您出现问题,您可以将其写为派生表:

SELECT [User], CountCurrwntRankLength
FROM (
    SELECT *, ROW_NUMBER() OVER(PARTITION BY [User], [Rank] ORDER BY [Date]) CountCurrwntRankLength
    FROM @T
) CTE
WHERE [Date] = '2016-12-01'

这将为您提供与CTE相同的结果

第一个版本

好吧,让我们玩假设游戏吧。我假设你使用的是sql server 2008或更高版本:

创建并填充样本表(,在将来的问题中保存此步骤)

DECLARE @T As Table
(
    [User] char(1),
    [Date] date,
    [Rank] varchar(10)
)

INSERT INTO @T VALUES
('A', '2016-10-01', 'Bee'),
('A', '2016-11-01', 'Bee'),
('A', '2016-12-01', 'Chairman'),
('A', '2017-01-01', 'Chairman'),
('B', '2016-10-01', 'Artist'),
('B', '2016-11-01', 'Artist'),
('B', '2016-12-01', 'Artist'),
('B', '2017-01-01', 'Artist')

查询:

;WITH CTE AS
(
    SELECT *, ROW_NUMBER() OVER(PARTITION BY [User], [Rank] ORDER BY [Date]) CountCurrwntRankLength
    FROM @T
)

SELECT [User], CountCurrwntRankLength
FROM CTE
WHERE [Date] = '2016-12-01'

结果:

User    CountCurrwntRankLength
A       1
B       3

说明:
通过使用带有ROW_NUMBER子句的PARTITION BY窗口函数,CTE将添加同一用户具有相同排名的次数。

然后你要做的就是查询CTE的特定日期。

答案 2 :(得分:0)

如果我们假设用户不切换回当前排名,您可以使用聚合和窗口函数执行此操作:

select t.*
from (select t.user, t.rank, count(*) as cnt,
             row_number() over (partition by t.user order by max(t.date) desc) as seqnum
      from t
      group by t.user, t.rank
     ) t
where seqnum = 1;

这实际上为两个用户返回“2”和“4”,而不是“1”和“3”。我发现“2”和“4”可以更好地回答你的问题。减去1,如果你更喜欢从零开始计算。