【问题标题】:SQL Server 2014 - Use previous value when date not presentSQL Server 2014 - 当日期不存在时使用以前的值
【发布时间】:2017-01-06 14:42:23
【问题描述】:

我昨天问了一个类似的问题,但我对我想要的东西的描述不是很好。这会更清楚。

领先/落后没有让我得到我需要的东西。它接近,但还不够。 客户端使用 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

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

我需要的是在我有空值的地方,它将拉入以前不为空的团队名称。因此,在 User1 的情况下,7 月和 8 月的这 4 个空值会显示 Team1,因为那是他最后一次加入的团队。 Team2 之后的空值也一样,应该是 Team2。

Lead/Lag 接近或我没有正确使用它。希望通过所有这些代码,这可以让某人的工作变得更轻松。

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

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

【问题讨论】:

  • 请添加您使用 Lead/Lag 的尝试,以便我们进行调试。
  • 在原帖底部添加更新。
  • 如果您的第一行的团队字段为空怎么办?

标签: sql sql-server


【解决方案1】:

(将更新说明移至末尾)

我认为最简单的解决方案(从概念上讲)是加入到 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()... 你可以这样做,但如果你这样做了,那么你必须在分区键中包含来自 b 表的任何数据在连接期间导致 a 表中的行重复。使用 rank 可确保所有此类重复项共享其应有的排名。

更新 - 在我最初写这篇文章后,我删除了它,因为我认为我误读了你的问题;但是当我写一个替代品时,我意识到我可能一开始就做对了。所以在这里,有一个警告:

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

更新 2 - 根据您在以下 cmets 中对数据模型的描述,我正在更改查询以显示内部联接,因为这听起来可行(一旦您扩大联接条件) 并且应该更有效。

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

【讨论】:

  • 我实际上需要那些空值所在的行。我只需要他们有正确的团队。我们的旧表 - 不遵循任何正常形式,每个月都有每个员工和他们的团队。这是大量冗余数据,因为大多数人不会经常更换团队。我想把我们的一些表带入 3nf,这是我想打的。我们现有的报告需要能够提取大量数据(一次最多一年,跨所有团队)并列出我输出的数据。我使用了左外连接来展示我需要如何结束数据。
  • 你是说你认为你不会得到我发布的答案的那些行吗?因为除非我错过了什么,否则你会的。 FWIW,虽然您提到的旧表结构可能效率较低,但使用 (emp,month) 作为键并包括所有条目仍然是 3nf(再次除非我遗漏了什么)。
  • 附加说明 - 您可以在团队记录上放置一个(可以为空的)月底,以便简单的范围检查可以驱动连接,获得紧凑的表和更有效的查询。就像我说的“更好的东西”可能意味着不同的数据模型。但这确实会引入可能的不一致(日期间隔或重叠)
  • 马克,我在我的项目中复制了你的代码,该项目仍然打开,我只得到 2 个结果。 n 将其缩小到两个结果,我需要所有数据都在结果中并填充空值。原始表更像是一个平面文件而不是任何东西。它的所有数据都集中在一个地方。如果我需要获取唯一用户列表,我必须做一些工作以返回唯一用户列表。我不想有一个只有用户的表格,然后是我所拥有的表格,但如果这被证明太难了,我可能会这样做。
  • 那我错过了什么;等一下,我去看看。
【解决方案2】:

您可以使用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

【讨论】:

    【解决方案3】:

    在这个版本中,我首先在 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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-21
      • 1970-01-01
      • 1970-01-01
      • 2012-08-23
      • 1970-01-01
      相关资源
      最近更新 更多