【问题标题】:TSQL: How to apply Condition to sub groupingTSQL:如何将条件应用于子分组
【发布时间】:2012-12-10 09:22:14
【问题描述】:

图片我有下表,一个人不同时期的多个代码(id是主键)

id   code   Name  Start                     Finish
325  1353   Bob   NULL                      2012-07-03 16:21:16.067
1742 1353   Bob   2012-07-03 16:21:16.067   2012-08-03 15:56:29.897
1803 1353   Bob   2012-08-03 15:56:29.897   NULL
17   575    Bob   NULL                      NULL
270  834    Bob   NULL                      2012-07-20 15:51:19.913
1780 834    Bob   2012-07-20 15:51:19.913   2012-07-26 16:26:54.413
1789 834    Bob   2012-07-26 16:26:54.413   2012-08-21 15:36:58.940
1830 834    Bob   2012-08-21 15:36:58.940   2012-08-24 14:26:05.890
1835 834    Bob   2012-08-24 14:26:05.890   2012-08-30 12:01:05.313
1838 123    Bob   2012-08-30 12:01:05.313   2012-09-05 09:29:02.497
1844 900    Bob   2012-09-05 09:29:02.497   NULL

我想要更新表格以使代码取自最新的人。

id   code   Name Start                      Finish
325  900    Bob  NULL                       2012-07-03 16:21:16.067
1742 900    Bob  2012-07-03 16:21:16.067    2012-08-03 15:56:29.897
1803 900    Bob  2012-08-03 15:56:29.897    NULL
17   900    Bob  NULL                       NULL
270  900    Bob  NULL                       2012-07-20 15:51:19.913
1780 900    Bob  2012-07-20 15:51:19.913    2012-07-26 16:26:54.413
1789 900    Bob  2012-07-26 16:26:54.413    2012-08-21 15:36:58.940
1830 900    Bob  2012-08-21 15:36:58.940    2012-08-24 14:26:05.890
1835 900    Bob  2012-08-24 14:26:05.890    2012-08-30 12:01:05.313
1838 900    Bob  2012-08-30 12:01:05.313    2012-09-05 09:29:02.497
1844 900    Bob  2012-09-05 09:29:02.497    NULL

最新的人被定义为latest (max?) Start AND (Finish IS NULL or Finish >= GetDate()) WITHIN the Group of people of same Name AND Code的人

在上面的示例中,id = 1844 (对于 Bob 的组,它有最新的 Start 和 Finish 为 Null)

我很确定这可以通过单个语句实现,但我可以看到如何定义“最新人”,以便我可以将其加入以获取我想要更新的行

编辑:请注意,我不能仅依赖日期列的 Id 列的顺序。

【问题讨论】:

    标签: tsql group-by


    【解决方案1】:

    这样就可以了:

    update this set code = (
       select top (1) that.code from table1 that
       where that.name = this.name -- match on name
         and (that.Finish is null or that.Finish >= getdate()) -- filter for current rows only
       order by that.Start desc, that.id desc -- rank by start, break ties with id
       )
    from table1 this
    

    我希望您的表被很好地索引,并且/或者不是太大,因为这一步完成很昂贵。

    替代形式,使用OUTER APPLY,更容易扩展:

    update this set code = that.code
    from table1 this
    outer apply (
       select top (1) that.code from table1 that
       where that.name = this.name -- match on name
         and (that.Finish is null or that.Finish >= getdate()) -- filter for current rows
       order by that.Start desc, that.id desc -- rank by start, break ties with id
       ) that
    

    使用窗口函数的替代方法,没有连接:

    update this set code = _latest_code
    from (
      -- identify the latest code per name
      select *, _latest_code = max(
        case 
          when (finish is null or finish >= getdate()) 
           and _row_number = 1
          then code else null 
        end
        ) over (partition by name)
      from (
        -- identify the latest row per name
        select *, _row_number = row_number() over (
          partition by name order by 
            case when finish is null or finish >= getdate() then 0 else 1 end
          , start desc, id desc)
        from table1
        ) this
      ) this
    

    【讨论】:

    • 谢谢。加入回来会更有效率吗?
    • 已经是自连接了,that.name = this.name makes 就这样。我将使用聚合 + 连接编写一个替代版本,然后您可以自己测试和比较哪个在您的表上更有效。
    • 从头开始,使用聚合会更糟。 MAX(Start) 仍然模棱两可,您需要第二次聚合和自加入来打破关系。
    • 你需要一些东西来消除歧义。它首先依赖于 Start,然后 id 仅用于确定性。我使用窗口函数添加了另一个版本,需要更多的排序,更少的加入。
    • 感谢您的解释。这很有意义。有很多值得深思的地方。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-12-23
    • 1970-01-01
    • 2013-05-01
    • 2013-11-12
    • 2022-11-13
    • 2023-03-23
    • 2019-12-07
    相关资源
    最近更新 更多