SQL Server 2014 - 在日期不存在时使用以前的值

时间:2017-01-06 14:42:24

标签: sql sql-server

昨天我问了一个类似的问题,但我对自己想要的内容并不是很了解。这将更加清晰。

领先/滞后并没有让我得到我需要的东西。它很接近,但还不够。 将SQL Server 2014用于客户端,构建在SQL 2012上的实际服务器。

这是我的代码: 创建团队表

CREATE TABLE ##TeamTable
    ([UserID] varchar(50), [CurrentTeam] varchar(5), [ChangeDate] datetime)
;

INSERT INTO ##TeamTable
    ([UserID], [CurrentTeam], [ChangeDate])
VALUES
    ('User1', 'Team1', '6/1/2016'),
    ('User1', 'Team2', '9/1/2016'),
    ('User1', 'Team3', '12/1/2016'),
    ('User2', 'Team1', '4/1/2016'),
    ('User2', 'Team2', '10/1/2016'),
    ('User2', 'Team3', '11/1/2016');

现在创建数据表我需要加入

CREATE TABLE ##DataTable
    ([UserID] varchar(50), Month_sk datetime, Media varchar(50), NCO int)
INSERT INTO ##DataTable
    ([UserID] , Month_sk , Media , NCO )
VALUES
    ('User1', '2016-06-01 00:00:00', 'Fax', 100),
    ('User1', '2016-06-01 00:00:00', 'Voice', 120),
    ('User1', '2016-07-01 00:00:00', 'Voice', 90),
    ('User1', '2016-07-01 00:00:00', 'Email', 100),
    ('User1', '2016-08-01 00:00:00', 'Voice', 150),
    ('User1', '2016-08-01 00:00:00', 'Email', 100),
    ('User1', '2016-09-01 00:00:00', 'Voice', 100),
    ('User1', '2016-09-01 00:00:00', 'Email', 120),
    ('User1', '2016-10-01 00:00:00', 'Voice', 90),
    ('User1', '2016-10-01 00:00:00', 'Email', 100),
    ('User1', '2016-11-01 00:00:00', 'Voice', 150),
    ('User1', '2016-11-01 00:00:00', 'Email', 100),
    ('User1', '2016-12-01 00:00:00', 'Voice', 150),
    ('User1', '2016-12-01 00:00:00', 'Email', 100),
    ('User2', '2016-04-01 00:00:00', 'Fax', 100),
    ('User2', '2016-04-01 00:00:00', 'Voice', 120),
    ('User2', '2016-05-01 00:00:00', 'Fax', 100),
    ('User2', '2016-05-01 00:00:00', 'Voice', 120),
    ('User2', '2016-06-01 00:00:00', 'Fax', 100),
    ('User2', '2016-06-01 00:00:00', 'Voice', 120),
    ('User2', '2016-07-01 00:00:00', 'Voice', 90),
    ('User2', '2016-07-01 00:00:00', 'Email', 100),
    ('User2', '2016-08-01 00:00:00', 'Voice', 150),
    ('User2', '2016-08-01 00:00:00', 'Email', 100),
    ('User2', '2016-09-01 00:00:00', 'Voice', 100),
    ('User2', '2016-09-01 00:00:00', 'Email', 120),
    ('User2', '2016-10-01 00:00:00', 'Voice', 90),
    ('User2', '2016-10-01 00:00:00', 'Email', 100),
    ('User2', '2016-11-01 00:00:00', 'Voice', 150),
    ('User2', '2016-11-01 00:00:00', 'Email', 100),
    ('User2', '2016-12-01 00:00:00', 'Voice', 150),
    ('User2', '2016-12-01 00:00:00', 'Email', 100);

这是一个基本的选择,以显示最新情况:

SELECT  b.UserID
        ,b.Media
        ,b.NCO
        ,Month_sk
        ,CurrentTeam

FROM    ##DataTable b

LEFT OUTER JOIN ##TeamTable a on b.UserID = a.UserID and b.Month_sk = a.ChangeDate

order by UserID, Month_sk, media

这给了我一个如下所示的结果集:

Click for data output

我需要的是我有空的地方,它会吸引以前的团队名称,这不是空的。因此,在User1案例中,7月和8月的4个空值将说Team1,因为那是他最后一个团队。对于Team2之后的空值相同,那些应该是Team2。

领先/滞后已接近或我没有正确使用它。希望通过所有这些代码,这可以使某人的工作更轻松。

更新: 滞后/导致给出相同的结果。仍然需要空值来填写

SELECT  b.UserID
        ,b.Media
        ,b.NCO
        ,Month_sk
        ,CurrentTeam
        ,LAG(CurrentTeam,1, currentteam) OVER(PARTITION BY a.userid, changedate ORDER BY ChangeDate) as Lag

FROM    ##DataTable b

LEFT OUTER JOIN ##TeamTable a on b.UserID = a.UserID and b.Month_sk = a.ChangeDate

order by UserID, Month_sk, media

3 个答案:

答案 0 :(得分:2)

(将更新说明移至结束)

我认为最简单的解决方案(概念上)是连接所有月份到month_sk,然后过滤以仅获得最后一场比赛。这种“感觉”可能效率低下,因此您需要使用真实的数据量进行测试,如果出现问题,请寻找更好的方法。 (但“更好的东西”可能涉及改变物理数据模型......)

所以:

select userid, media, nco, month_sk, currentteam
  from (SELECT b.UserID
             , b.Media
             , b.NCO
             , Month_sk
             , CurrentTeam
             , rank() over(partition by b.userID
                               order by a.changeDate desc) n
        FROM            ##DataTable b
             INNER JOIN ##TeamTable a
                     on b.UserID = a.UserID
                    and b.Month_sk >= a.ChangeDate
       ) x
 where n = 1
 order by UserID, Month_sk, media

请注意,在以前的版本中,我使用的是row_number() over()而不是rank() over() ...您可以这样做,但如果您这样做,则必须在分区键中包含{{{ 1}}表在连接期间可能导致b表中的行重复。使用a可确保所有此类重复项共享其应有的排名。

更新 - 在我最初写这篇文章之后,我删除了它,因为我以为我误读了你的问题;但是当我写一个替代品时,我可能已经把它放在第一位了。所以这里有一个警告:

这假设您获得NULL值的唯一原因是外连接。如果“右手”表有一行并且其中只有一列的值为NULL,那么获取该列的先前值将需要进一步使用子查询或分析函数。但即使这样,领先/滞后也可能不起作用,因为它们是基于位置的。 (我认为使用LAST_VALUE的东西可能更合适,但除非需要,否则会留下详细信息。)

UPDATE 2 - 根据您对以下评论中的数据模型的描述,我正在更改查询以显示内部联接,因为它听起来会起作用(一旦您扩大了连接条件)并且应该更有效率。

更新3 - 我误读了您的示例数据并获得了用于计算rank错误的分区表达式。假设n表中的值是唯一的,则应该修复。如果不是它仍然可以修复但需要更多的诡计......

答案 1 :(得分:1)

您可以使用APPLY和这样的子查询执行此操作。

SELECT 
    userid, 
    media,
    nco,
    month_sk,
    currentteam
FROM
    ##DataTable td
    OUTER APPLY (
        SELECT TOP (1) 
            CurrentTeam,
            ChangeDate
        FROM 
            ##TeamTable tt
        WHERE 
            tt.UserID = td.UserID
            and tt.ChangeDate <= td.Month_sk
        ORDER BY
            tt.ChangeDate desc
    ) dataTableWithTeam
ORDER BY
    td.UserID,
    td.Month_sk,
    td.media

答案 2 :(得分:0)

在这个版本中,我首先在CTE中确定适当的“链接”月份,然后将其用作最终连接中的查找。 (一旦我意识到MediaNCO在联接中没有真正的作用,它会变得容易得多。)

WITH cteDateLookup
 as (
    --  Get the ChangeDate for this User/Month
    SELECT
       b.UserID
      ,b.Month_sk
      ,max(a.ChangeDate) ChangeDate
     from ##DataTable b
      left outer join ##TeamTable a
       on b.UserID = a.UserID
        and b.Month_sk >= a.ChangeDate
    group by 
       b.UserID
      ,b.Month_sk
  )
--  Use the cte as a "lookup" for the appropriate date
SELECT
   b.UserID
  ,b.Media
  ,b.NCO
  ,b.Month_sk
  ,a.CurrentTeam
 from ##DataTable  b
  left outer join cteDateLookup  cte
   on cte.UserId = b.UserId
    and b.Month_sk = cte.Month_sk
  left outer join ##TeamTable  a
   on a.UserId = cte.UserId
    and a.ChangeDate = cte.ChangeDate
 order by
   b.UserID
  ,b.Month_sk
  ,b.media