【问题标题】:Query to return consecutive record count查询返回连续记录数
【发布时间】:2020-07-07 05:48:10
【问题描述】:

鉴于此表t

  Date          Value
  15-Jan-2020   true 
  14-Jan-2020   true 
  13-Jan-2020   true 
  12-Jan-2020   false
  11-Jan-2020   true 
  10-Jan-2020   false
  09-Jan-2020   false 

如何编写查询以返回值为true 的前连续天数?

在这个例子中,这将是 3。

查询此表时,最新记录将始终为true。在此之前的记录/日期可以是真假。因此,查询实际上会返回 1 到 n 之间的值。

【问题讨论】:

  • 你真的想要一个 PL/SQL 过程/函数,还是你想要一个 SQL 查询? PL/SQL 是 Oracle 的过程语言。
  • 不管怎样都行 :)

标签: sql oracle


【解决方案1】:

这类事情有很多众所周知的 SQL 技巧。我喜欢使用MATCH_RECOGNIZE 功能。我发现它比其他方式更神秘。试试这个:

with input ( d, v ) as
( 
  SELECT to_date('15-Jan-2020','DD-MON-YYYY'),'true' FROM DUAL UNION ALL 
  SELECT to_date('14-Jan-2020','DD-MON-YYYY'),'true' FROM DUAL UNION ALL 
  SELECT to_date('13-Jan-2020','DD-MON-YYYY'),'true' FROM DUAL UNION ALL 
  SELECT to_date('12-Jan-2020','DD-MON-YYYY'),'false' FROM DUAL UNION ALL 
  SELECT to_date('11-Jan-2020','DD-MON-YYYY'),'true' FROM DUAL UNION ALL 
  SELECT to_date('10-Jan-2020','DD-MON-YYYY'),'false' FROM DUAL UNION ALL 
  SELECT to_date('09-Jan-2020','DD-MON-YYYY'),'false ' FROM DUAL)
select d, v, cnt
from input
match_recognize (
  order by d
  measures running count(value_true.d) as cnt
  all rows per match
  pattern (value_true*)
  define 
    value_true AS value_true.v = 'true'
    )
order by d desc    
;
+----------------------+--------+-----+
|          D           |   V    | CNT |
+----------------------+--------+-----+
| 15-JAN-2020 00:00:00 | true   |   3 |
| 14-JAN-2020 00:00:00 | true   |   2 |
| 13-JAN-2020 00:00:00 | true   |   1 |
| 12-JAN-2020 00:00:00 | false  |   0 |
| 11-JAN-2020 00:00:00 | true   |   1 |
| 10-JAN-2020 00:00:00 | false  |   0 |
| 09-JAN-2020 00:00:00 | false  |   0 |
+----------------------+--------+-----+

MATCH_RECOGNIZE 在行中查找模式。在这种情况下,我们给出如下模式:

pattern (value_true*)

找到零个或多个(即星号)“value_true”行。根据我们的定义,一行是“value_true”:

define 
  value_true AS value_true.v = 'true'

然后我们告诉 Oracle,计算与模式匹配的行数 COUNT(),并将结果称为 cnt。因为,在我们的MATCH_RECOGNIZE 中,是按日期按升序 顺序排序的,并且由于我们的MEASURES 子句指定了RUNNING COUNT(而不是FINAL COUNT),所以这是一个“真" 从组的开头到当前行(包括当前行)的行。也就是说,连续的先前“真实”行数(如果计算当前行)。

也许“不那么神秘”不是正确的表达方式。我的意思是,也许,“更明确”。语法不太熟悉,但它说明了你在做什么:即寻找value = true 的连续行的模式并计算它们。

【讨论】:

  • “我发现它比其他方式更不神秘。”请添加说明它在做什么以及它为什么起作用
  • @MT0 我添加了一个演练。我希望这是有道理的。感谢您的建议。
【解决方案2】:

由于您的行是连续的日期,您可以使用带有CASE 表达式的LAG(...) IGNORE NULLS (...) 分析函数来过滤行以找到最近的false 行(对于每个true 行),然后比较日期求两者之间的天数得到计数:

SELECT "date",
       CASE value
       WHEN 'true'
       THEN "date" - LAG( CASE value WHEN 'false' THEN "date" END, 1, "date" )
              IGNORE NULLS OVER ( ORDER BY "date" ASC )
       ELSE 0
       END as cnt
FROM   table_name

LAG 中的CASE 表达式允许LAG 仅考虑false 的行。当您减去两个日期时,这将为您提供它们之间的天数;鉴于行是连续的,这与所需的计数完全匹配。

所以,对于您的测试数据:

CREATE TABLE table_name ( "date", value ) AS
SELECT DATE '2020-01-15', 'true'  FROM DUAL UNION ALL
SELECT DATE '2020-01-14', 'true'  FROM DUAL UNION ALL
SELECT DATE '2020-01-13', 'true'  FROM DUAL UNION ALL
SELECT DATE '2020-01-12', 'false' FROM DUAL UNION ALL
SELECT DATE '2020-01-11', 'true'  FROM DUAL UNION ALL
SELECT DATE '2020-01-10', 'false' FROM DUAL UNION ALL
SELECT DATE '2020-01-09', 'false' FROM DUAL;

这个输出:

日期 |碳纳米管 :-------- | --: 20 年 1 月 9 日 | 0 20 年 1 月 10 日 | 0 20 年 1 月 11 日 | 1 20 年 1 月 12 日 | 0 20 年 1 月 13 日 | 1 20 年 1 月 14 日 | 2 20 年 1 月 15 日 | 3

db小提琴here


如果您在最早日期有 true 值,那么您可以扩展上述查询以解决此问题:

SELECT "date",
       CASE value
       WHEN 'true'
       THEN "date"
            - COALESCE(
                LAG( CASE value WHEN 'false' THEN "date" END, 1, NULL )
                  IGNORE NULLS OVER ( ORDER BY "date" ASC ),
                MIN( "date" ) OVER () - 1
              )
       ELSE 0
       END as cnt
FROM   table_name

db小提琴here

【讨论】:

  • 很好,很紧凑,但我不确定我是否会依赖一天内数据和/或多行中没有任何间隙。无论如何,我认为这需要对最早行为“真”的情况进行微调。 LAG 没有“虚假”记录可供查找。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-31
  • 2021-03-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多