【问题标题】:RANK() OVER PARTITION with RANK resettingRANK() OVER PARTITION 与 RANK 重置
【发布时间】:2016-05-14 11:47:17
【问题描述】:

如何获得在分区更改时重新启动的 RANK? 我有这张桌子:

ID    Date        Value  
1     2015-01-01  1  
2     2015-01-02  1 <redundant  
3     2015-01-03  2  
4     2015-01-05  2 <redundant  
5     2015-01-06  1  
6     2015-01-08  1 <redundant  
7     2015-01-09  1 <redundant  
8     2015-01-10  2  
9     2015-01-11  3  
10    2015-01-12  3 <redundant  

我正在尝试删除值未从上一个条目更改的所有行(标有 )。 我尝试过使用游标,但耗时太长,因为该表有大约 5000 万行。

我也尝试过使用 RANK:

SELECT ID, Date, Value,
RANK() over(partition by Value order by Date ASC) Rank,
FROM DataLogging 
ORDER BY Date ASC 

但我明白了:

ID    Date        Value  Rank   (Rank)
1     2015-01-01  1      1      (1)
2     2015-01-02  1      2      (2)
3     2015-01-03  2      1      (1)
4     2015-01-05  2      2      (2)
5     2015-01-06  1      3      (1)
6     2015-01-08  1      4      (2)
7     2015-01-09  1      5      (3)
8     2015-01-10  2      3      (1)
9     2015-01-11  3      1      (1)
10    2015-01-12  3      2      (2)

括号中是我想要的 Rank,这样我就可以过滤掉 Rank = 1 的行并删除其余的行。

编辑:我接受了似乎最容易编写的答案,但不幸的是,没有一个答案运行得足够快,无法删除行。 最后我还是决定使用 CURSOR。我已将数据拆分为大约 250k 行的块,游标在每批 250k 行约 11 分钟内运行并删除行,下面的答案使用 DELETE,每批 250k 行需要约 35 分钟。

【问题讨论】:

  • 在您的示例中,您如何区分第一组value = 1ids 1 和 2)和第二组(ids 5、6 和 7)?
  • 我不太明白这个问题。按时间顺序,两者之间还有其他值。
  • @AlinI:每个组中哪些特定行被认为是多余的,这对您来说很重要吗?例如,保留最早的行与每个组中的最新行是否重要?
  • 是的,重要的是,最早的行应该保留在表中。
  • 您尝试基于RANK() 的窗口函数版本的问题是您想要的分区不是由value 列严格确定的,实际上也不是由any 列组合。它们是相对于特定行顺序的行对的函数。正如 Gordon 在他的回答中所建议的那样,lag()lead() 窗口函数正是处理这个问题的方法。

标签: sql sql-server window-functions


【解决方案1】:
select * 
from  ( select ID, Date, Value, lag(Value, 1, 0) over (order by ID) as ValueLag 
        from table ) tt
where ValueLag is null or ValueLag <> Value  

如果订单是日期然后结束(按日期排序)

这应该告诉你好坏 - 它基于 ID - 你需要日期然后修改
它可能看起来很长,但它应该非常有效

declare @tt table  (id tinyint, val tinyint);
insert into @tt values 
( 1, 1),
( 2, 1),
( 3, 2),
( 4, 2),
( 5, 1),
( 6, 1),
( 7, 1),
( 8, 2),
( 9, 3),
(10, 3);

select id, val, LAG(val) over (order by id) as lagVal
from @tt;

-- find the good
select id, val 
from ( select id, val, LAG(val) over (order by id) as lagVal
       from @tt 
     ) tt
where  lagVal is null or lagVal <> val 

-- select the bad 
select tt.id, tt.val 
  from @tt tt
  left join ( select id, val 
                from ( select id, val, LAG(val) over (order by id) as lagVal
                         from @tt 
                     ) ttt
               where   ttt.lagVal is null or ttt.lagVal <> ttt.val 
            ) tttt 
    on tttt.id = tt.id 
 where tttt.id is null

【讨论】:

  • 嗯,我不熟悉 LEAD 功能。看起来还蛮有用的。我没有投反对票,但不清楚如何将其带到下一步(删除多余的行)。
  • 不是我的反对意见,但它可能是您的答案的第一次修订有语法错误。希望当他们注意到您已修复它时,他们可以收回它
  • 您确实显示了一个选择。当您只想要相邻时,领先/滞后非常有效。
  • 这里的逻辑不太对劲,它返回行2, 4, 7, 8, 10。如果打算显示冗余行,则应返回 2, 4, **6**, 7, 10
  • @mellamokb 我的意图是它会显示好的行。我会看的。
【解决方案2】:

这是一种有点复杂的方法:

WITH CTE AS
(
    SELECT  *, 
            ROW_NUMBER() OVER(ORDER BY [Date]) RN1,
            ROW_NUMBER() OVER(PARTITION BY Value ORDER BY [Date]) RN2
    FROM dbo.YourTable
), CTE2 AS
(
    SELECT *, ROW_NUMBER() OVER(PARTITION BY Value, RN1 - RN2 ORDER BY [Date]) N
    FROM CTE
)
SELECT *
FROM CTE2
ORDER BY ID;

结果是:

╔════╦════════════╦═══════╦═════╦═════╦═══╗
║ ID ║    Date    ║ Value ║ RN1 ║ RN2 ║ N ║
╠════╬════════════╬═══════╬═════╬═════╬═══╣
║  1 ║ 2015-01-01 ║     1 ║   1 ║   1 ║ 1 ║
║  2 ║ 2015-01-02 ║     1 ║   2 ║   2 ║ 2 ║
║  3 ║ 2015-01-03 ║     2 ║   3 ║   1 ║ 1 ║
║  4 ║ 2015-01-05 ║     2 ║   4 ║   2 ║ 2 ║
║  5 ║ 2015-01-06 ║     1 ║   5 ║   3 ║ 1 ║
║  6 ║ 2015-01-08 ║     1 ║   6 ║   4 ║ 2 ║
║  7 ║ 2015-01-09 ║     1 ║   7 ║   5 ║ 3 ║
║  8 ║ 2015-01-10 ║     2 ║   8 ║   3 ║ 1 ║
║  9 ║ 2015-01-11 ║     3 ║   9 ║   1 ║ 1 ║
║ 10 ║ 2015-01-12 ║     3 ║  10 ║   2 ║ 2 ║
╚════╩════════════╩═══════╩═════╩═════╩═══╝

要删除你不想要的行,你只需要这样做:

DELETE FROM CTE2
WHERE N > 1;

【讨论】:

  • 这似乎可行,但如果我使用 SELECT ID FROM CTE2 WHERE N > 1 ORDER BY ID; 我会得到太多行:~237k vs ~175k 这是多余的.
  • @AlinI 我刚刚复制了您问题中发布的预期结果
  • 如果我得到了整张桌子,那是真的,排名如预期。现在,我该如何删除不需要的行?
  • 这不可能。如果我 SELECT FROM CTE2 WHERE N > 1 我得到 237k 行... DELETE 怎么只删除 175k?
  • @AlinI 对不起,我不明白你说什么
【解决方案3】:

如果你想删除行,我建议你使用lag():

with todelete as (
      select t.*, lag(value) over (order by date) as prev_value
      from t
     )
delete from todelete
    where value = prev_value;

我不太确定 rank() 与这个问题有什么关系。

编辑:

要查看删除的行,使用相同的逻辑:

with todelete as (
      select t.*, lag(value) over (order by date) as prev_value
      from t
     )
select *
from todelete
where value <> prev_value or prev_value is null;

where 子句与第一个查询中的 where 子句正好相反,将 NULL 值考虑在内。

【讨论】:

  • 这似乎可行,但如果我使用 SELECT ID FROM todelete where value = prev_value; 我会得到太多行:~237k vs ~175k 这是多余的
  • @AlinI 。 . .你想要where value &lt;&gt; prev_value or prev_value is null。您的逻辑返回被删除的行,而不是被保留的行。
【解决方案4】:

这很有趣,所以我想我会加入。不幸的是,在不首先转换数据的情况下使用RANK()(或者更确切地说,ROW_NUMBER())创建解决方案看起来是无法获得的。为了尝试转换数据,我想出了这个使用 1 ROW_NUMBER() 的解决方案:

;WITH Ordered AS
(
    SELECT ROW_NUMBER() OVER (ORDER BY [Date]) AS [Row], *
    FROM DataLogging
),
Final AS
(
    SELECT
        o1.*, NULLIF(o1.Value - ISNULL(o2.Value, o1.Value - 1), 0) [Change]
    FROM
        Ordered o1
        LEFT JOIN Ordered o2 ON
            o1.[Row] = o2.[Row] + 1
)
SELECT * FROM Final

在最后的Change 列中,如果值没有变化,则值为NULL(但如果有变化,则会有差异)。

所以要删除,请将选择更改为

DELETE FROM DataLogging where Change IS NULL

编辑:滞后也可以在这里工作,但我一直在想象解决方案,而我却完全忘记了这一点。

【讨论】:

    【解决方案5】:

    为我的案子工作!谢谢 我必须获取一个员工的 report_to 相对于之前的 reports_to value 和 effdt 的更改。 换句话说,每个报告的最小生效日期行都为员工更改。

    tocheck 为 ( 选择 T.emplid,T.reports_to,T.effdt, lag(reports_to) over (order by effdt) 作为 prev_value 从 PS_JOB t ) 选择 * 从到检查 其中reports_to prev_value 或prev_value 为空;

    在 p 中进一步添加了更改

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-12-07
      • 1970-01-01
      • 1970-01-01
      • 2012-11-04
      • 2020-10-22
      • 2021-11-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多