【问题标题】:Skip null values when counting with RANK() OVER使用 RANK() OVER 计数时跳过空值
【发布时间】:2018-08-13 17:32:18
【问题描述】:

给定一组行,有时有一个字段null,有时没有:

SELECT 
   Date, TheThing
FROM MyData
ORDER BY Date


Date                     TheThing
-----------------------  --------
2016-03-09 08:17:29.867  a
2016-03-09 08:18:33.327  a
2016-03-09 14:32:01.240  NULL
2016-10-21 19:53:49.983  NULL
2016-11-12 03:25:21.753  b
2016-11-24 07:43:24.483  NULL
2016-11-28 16:06:23.090  b
2016-11-28 16:09:07.200  c
2016-12-10 11:21:55.807  c

我想要一个计算非空值的排名列:

Date                     TheThing  DesiredTotal
-----------------------  --------  ------------
2016-03-09 08:17:29.867  a         1
2016-03-09 08:18:33.327  a         2
2016-03-09 14:32:01.240  NULL      2 <---notice it's still 2 (good)
2016-10-21 19:53:49.983  NULL      2 <---notice it's still 2 (good)
2016-11-12 03:25:21.753  b         3
2016-11-24 07:43:24.483  NULL      3 <---notice it's still 3 (good)
2016-11-28 16:06:23.090  b         4
2016-11-28 16:09:07.200  c         5
2016-12-10 11:21:55.807  c         6

我尝试显而易见的:

SELECT 
   Date, TheThing, 
   RANK() OVER(ORDER BY Date) AS Total
FROM MyData
ORDER BY Date

但是RANK() 计算空值:

Date                     TheThing  Total
-----------------------  --------  -----
2016-03-09 08:17:29.867  a         1
2016-03-09 08:18:33.327  a         2
2016-03-09 14:32:01.240  NULL      3 <--- notice it is 3 (bad)
2016-10-21 19:53:49.983  NULL      4 <--- notice it is 4 (bad)
2016-11-12 03:25:21.753  b         5 <--- and all the rest are wrong (bad)
2016-11-24 07:43:24.483  NULL      7
2016-11-28 16:06:23.090  b         8
2016-11-28 16:09:07.200  c         9
2016-12-10 11:21:55.807  c         10

如何指示RANK()(或DENSE_RANK())不计算空值?

您是否尝试过使用分区?

为什么是的!更糟糕的是:

SELECT 
   Date, TheThing, 
   RANK() OVER(PARTITION BY(CASE WHEN TheThing IS NOT NULL THEN 1 ELSE 0 END) ORDER BY Date) AS Total
FROM MyData
ORDER BY Date

RANK() 计算空值:

Date                     TheThing  Total
-----------------------  --------  -----
2016-03-09 08:17:29.867  a         1
2016-03-09 08:18:33.327  a         2
2016-03-09 14:32:01.240  NULL      1 <--- reset to 1?
2016-10-21 19:53:49.983  NULL      2 <--- why go up?
2016-11-12 03:25:21.753  b         3 
2016-11-24 07:43:24.483  NULL      3 <--- didn't reset?
2016-11-28 16:06:23.090  b         4 
2016-11-28 16:09:07.200  c         5
2016-12-10 11:21:55.807  c         6

现在我随机输入东西 - 疯狂地挥舞着。

SELECT 
   Date, TheThing, 
   RANK() OVER(PARTITION BY(CASE WHEN TheThing IS NOT NULL THEN 1 ELSE NULL END) ORDER BY Date) AS Total
FROM MyData
ORDER BY Date

SELECT 
   Date, TheThing, 
   DENSE_RANK() OVER(PARTITION BY(CASE WHEN TheThing IS NOT NULL THEN 1 ELSE NULL END) ORDER BY Date) AS Total
FROM MyData
ORDER BY Date

编辑:有了所有答案,我需要进行多次迭代才能找到我想要的所有边缘情况。最后,我在概念上想要的是OVER(),以便计数。我不知道OVER 适用于RANK(和DENSE_RANK)以外的任何东西。

http://sqlfiddle.com/#!18/c6d87/1

阅读奖励

【问题讨论】:

  • 我不认为您是否可以在单个查询中执行此操作。首先,您需要过滤非 Nulls 的记录,然后使用之前创建的排名选择所有记录。
  • TheThing 是不是除了Frobnull 之外的任何东西?这会影响运行总数吗?请显示所有边缘情况。
  • Rank()case when TheThing is NULL then Lag ... else TheThing end 使用前一行的值是否可以让您获得任何帮助?如果发生这种情况,它可能需要一个软糖因子来处理初始 null 值。
  • @HABO 或连续两个 NULL,因为 LAG() 必须知道的不是常量,而是最后一个非 NULL 有多少行。

标签: sql sql-server tsql


【解决方案1】:

我认为您正在寻找累积计数:

SELECT Date, TheThing, 
       COUNT(theThing) OVER (ORDER BY Date) AS Total
FROM MyData
ORDER BY Date;

【讨论】:

  • 当你引入 tie 时,它​​与 RANKDENSE_RANK 的工作方式不同。并且与我的回答中的第一个查询几乎相同:),这也指向我:)
  • 虽然所有其他答案都很好,并且对尝试解决另一个问题的其他人有用,但我真正想要解决的实际问题是 "counting"。我认为RANK OVER 的计数方式。原来COUNT OVER 是新事物。
【解决方案2】:

试试这个:

declare @tbl table (dt datetime, col int);
insert into @tbl values
('2016-03-09 08:17:29.867', 1),
('2016-03-09 08:18:33.327', 1),
('2016-03-09 14:32:01.240', NULL),
('2016-10-21 19:53:49.983', NULL),
('2016-11-12 03:25:21.753', 1),
('2016-11-24 07:43:24.483', NULL),
('2016-11-28 16:06:23.090', 1),
('2016-11-28 16:09:07.200', 1),
('2016-12-10 11:21:55.807', 1);

select dt,
       col,
       sum(case when col is null then 0 else 1 end) over (order by dt) rnk
from @tbl

这个想法非常简单:如果您将 1 分配给非 null 值,并且在列为 null 的情况下分配为零,则按日期排序的累积总和与不包括 null 的排名完全相同。

其他方法是使用RANKROW_NUMBER 结合使用,这将尊重Date 列中的关系,并且与RANK 尊重NULLs 完全相同:

select dt,
       col,
       case when col is not null then 
           rank() over (order by dt)
       else 
           rank() over (order by dt) - row_number() over (partition by rnDiff order by dt)
       end rnk
from (
    select dt,
           col,
           row_number() over (order by dt) -
               row_number() over (partition by coalesce(col, 0) order by dt) rnDiff
    from @tbl
) a
order by dt

【讨论】:

  • 这不会像 RANK 那样处理重复项
  • 尽管他们可能在 RANK 上走错了路。但是,例如,如果存在与最低 dt 相关的多个值,则会看到差异。 RANK 将分配所有 1。这不会。
  • 实际上,它会正确分配排名,但不是 1,而是 2(在两个并列行的情况下),但对于并列行,它是相同的。
  • @MartinSmith 第二个查询正确处理关系,但我认为(从提供的数据)第一个查询可以安全使用。
【解决方案3】:

我的蜥蜴脑把我带到这里...... sum() 与 rank()

Select *
       ,NewCol = sum(sign(TheThing)) over (Order by Date)
       ,OrEven = sum(TheThing/TheThing) over (Order by Date)  
 From  MyData

退货

【讨论】:

    【解决方案4】:

    rank() 中减去NULLs 的当前计数怎么样?

    SELECT date,
           thething,
           rank() OVER (ORDER BY date)
           -
           sum(CASE
                 WHEN thething IS NULL THEN
                   1
                 ELSE
                   0
               END) OVER (ORDER BY date) desiredtotal
           FROM mydata;
    

    db<>fiddle

    这也应该保留rank() 产生的重复和空白,并且不需要子查询。

    【讨论】:

      【解决方案5】:

      我先使用 CTE 获得正确的日期,然后将排名应用于修改日期:

      CREATE TABLE #tmp(dt datetime, TheThing int)
      
      INSERT INTO #tmp VALUES('2016-03-09 08:17:29.867',  1)
      INSERT INTO #tmp VALUES('2016-03-09 08:18:33.327',  1)
      INSERT INTO #tmp VALUES('2016-03-09 14:32:01.240',  NULL)
      INSERT INTO #tmp VALUES('2016-10-21 19:53:49.983',  NULL)
      INSERT INTO #tmp VALUES('2016-11-12 03:25:21.753',  1)
      INSERT INTO #tmp VALUES('2016-11-24 07:43:24.483',  NULL)
      INSERT INTO #tmp VALUES('2016-11-28 16:06:23.090',  1)
      INSERT INTO #tmp VALUES('2016-11-28 16:09:07.200',  1)
      INSERT INTO #tmp VALUES('2016-12-10 11:21:55.807',  1)
      
      
      ;WITH CTE as (
      SELECT 
      CASE WHEN TheThing IS NULL THEN (SELECT MAX(dt) from #tmp OrigTbl where OrigTbl.dt <     SubTbl.dt and OrigTbl.TheThing IS NOT NULL) ELSE dt end dtMod,
      SubTbl.dt,SubTbl.TheThing
         from #tmp SubTbl)
      SELECT dt, TheThing, DENSE_RANK() over(ORDER BY dtMod) from CTE
      

      【讨论】:

        【解决方案6】:

        我会使用subquery

        SELECT [Date], TheThing,
               (SELECT COUNT(*)
                FROM MyData m
                WHERE m.[Date] <= m1.[Date] AND m.TheThing IS NOT NULL
               ) AS DesiredTotal
        FROM MyData m1;
        

        同样,你也可以尝试apply

        SELECT *
        FROM MyData m1 CROSS APPLY
            (SELECT COUNT(*) AS DesiredTotal
             FROM MyData m
             WHERE m.[Date] <= m1.[Date] AND m.TheThing IS NOT NULL
            ) m2;
        

        【讨论】:

        • 这可行,但它对数据的大小非常敏感...要警惕较大数据集上的指数运行时间。
        • @AaronBertrand。 . .我知道这不是生成系列的有效方式。
        猜你喜欢
        • 2016-05-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-12-08
        • 2021-12-06
        • 2020-05-13
        • 2020-12-29
        相关资源
        最近更新 更多