使用 SQL 的第二大运行值

Posted

技术标签:

【中文标题】使用 SQL 的第二大运行值【英文标题】:Second greatests running value with SQL 【发布时间】:2021-06-30 12:12:11 【问题描述】:

我有一个table 如:

n value
1 1
2 4
3 4
4 6
5 4
6 8

我可以很容易地找到这个值的运行最大值:

SELECT *, max(value) over (order by n rows unbounded preceding) as mx
FROM table
n value mx
1 1 1
2 4 4
3 4 4
4 6 6
5 4 6
6 8 8

如何获得第二大滑动数?所以输出就是这样的:

n value second_mx
1 1
2 4 1
3 4 4
4 6 4
5 4 4
6 8 6

ps:

SELECT *,nth_value(value,2) over (order by n rows unbounded preceding) as second_mx
FROM table

不工作,因为order by n 描述了如何对第n 个进行排序。

【问题讨论】:

不管你想实现什么逻辑,SQL和Snowflake都有一个非常强大的工具来处理这种情况,叫做MATCH_RECOGNIZE 在 n=2 之前的行是 1 和 4,所以第二个最大值实际上是 1。不是吗? 【参考方案1】:

我提供了一个单独的答案,因为这个答案是如此不同(我可能会删除另一个答案)。我相信这只能通过窗口函数来处理。我认为这提供了一个解决方案。

这从一堆解释开始。您可以跳过查询并链接到 dbfiddle。

有两种情况,第二个最大值非常简单:

如果当前值为最大值且之前出现过,则为第二个最大值。 如果当前值为最大值且从未出现过,则之前的最大值为第二个最大值。

另外一个简单的案例:

如果该值小于或等于前一个第二个最大值,则第二个最大值不会改变。

最后,第二个最大值的一个重要属性:

第二个最大值正在增加。

因此,我们的想法是执行以下操作:

    计算“简单”案例。 在“琐碎”案例中分配第二个,前提是它不会根据简单案例而改变。 在其余部分分配当前值。 求第二个总和的累积最大值。

这会导致:

select t.*, max(imputed_second_max) over (order by n) as second_max
from (select t.*,
             (case when sometimes_mx_2 is not null then sometimes_mx_2
                   when value <= max(sometimes_mx_2) over (order by n) then max(sometimes_mx_2) over (order by n)
                   else value
              end) as imputed_second_max
      from (select t.*,
                   (case when value = mx and nth_value > 1 then value
                         when value = mx and nth_value = 1 then lag(mx) over (order by n)
                    end) as sometimes_mx_2
            from (select t.*, max(value) over (order by n) as mx,
                         row_number() over (partition by value order by n) as nth_value
                  from t
                 ) t
           ) t
      ) t
order by n;

我发现我需要扩充测试用例以获得更好的覆盖率。我发现递减序列特别棘手。

Here 是一个 dbfiddle。

【讨论】:

我给出这个答案是正确的,因为它只使用窗口函数缝合了最干净的答案。 @DavidMabodo 。 . .谢谢你。如果有人想出更简单的方法,我很乐意为您切换答案。 是的我也是,我一直觉得问题比解决方案容易得多【参考方案2】:

这个想法是创建一个排序的累积数组并获取第二个元素。

PostgreSQL(如果需要第3/4/5个元素,可以通过调整数组索引轻松扩展):

SELECT t.*,
     (sort(ARRAY_AGG(t.value) OVER(ORDER BY t.n), 'desc'))[2] AS sec_max
FROM t
ORDER BY n;

db<>fiddle demo

很遗憾,Snowflake 不支持累积 ARRAY_AGG/STRING_AGG。


低于使用递归cte构建累积数组的版本,然后对数组进行排序并取第二个元素。

数据准备:

CREATE OR REPLACE TABLE t
AS
SELECT 1 AS n, 1 AS value
UNION ALL SELECT 2,4
UNION ALL SELECT 3,4
UNION ALL SELECT 4,6
UNION ALL SELECT 5,4
UNION ALL SELECT 6,8;

辅助函数:

CREATE OR REPLACE FUNCTION array_sort_desc(a array)
  RETURNS array
  LANGUAGE javascript
AS
$$
  return A.sort().reverse();
$$
;

主要查询:

WITH src AS (
   SELECT *, ROW_NUMBER() OVER(ORDER BY n) AS rn FROM t
),cte AS (
  SELECT *,  ARRAY_CONSTRUCT(src.value) AS arr
  FROM src
  WHERE rn=1
  UNION ALL
  SELECT src.*, ARRAY_APPEND(arr, src.value)
  FROM src
  JOIN cte
    ON cte.rn=src.rn-1
)
SELECT cte.n, cte.value, arr, array_sort_desc(arr), array_sort_desc(arr)[1] AS sec_max
FROM cte
ORDER BY n;
/*
+---+-------+-----------------------------------+-----------------------------------+---------+
| N | VALUE |                ARR                |       ARRAY_SORT_DESC(ARR)        | sec_max |
+---+-------+-----------------------------------+-----------------------------------+---------+
| 1 |     1 | [   1  ]                          | [   1  ]                          |         |
| 2 |     4 | [   1,   4  ]                     | [   4,   1  ]                     |       1 |
| 3 |     4 | [   1,   4,   4  ]                | [   4,   4,   1  ]                |       4 |
| 4 |     6 | [   1,   4,   4,   6  ]           | [   6,   4,   4,   1  ]           |       4 |
| 5 |     4 | [   1,   4,   4,   6,   4  ]      | [   6,   4,   4,   4,   1  ]      |       4 |
| 6 |     8 | [   1,   4,   4,   6,   4,   8  ] | [   8,   6,   4,   4,   4,   1  ] |       6 |
+---+-------+-----------------------------------+-----------------------------------+---------+
*/

【讨论】:

如果我正确地理解了这一点,您正在将 cte 与自身连接以进行某种递归连接,是这样吗? @DavidMabodo 首先在 src 中进行连续编号,然后在 cte 部分中我有一个锚点 (rn=1) 和递归部分,它一次将下一行添加到数组中。一旦我准备好数组,就会应用排序(请参阅未排序的 ARR 列) 我赞成,但如果你要使用递归 CTE,我认为你不需要数组。只需保留最高的两个值并在查看数据时将它们交换。 @GordonLinoff 没错,递归 cte 是尝试一对一重写 array_agg 逻辑,这在这一点上可能是多余的 :) 另一方面它更通用,取第 3 个/第四元素等等。这是一个很好的谜题。 @LukaszSzozda 。 . .我仍然觉得我错过了一些可以大大简化问题的东西。【参考方案3】:

您可以使用横向连接:

select t.*, t2.value as second_mx
from t cross join lateral
     (select t2.*
      from t t2
      where t2.n <= t.n
      order by t2.value desc
      offset 1 row fetch first 1 row only
     ) t2;

实际上,您也可以将其表示为相关子查询。

也就是说,这在非小型表上不会有很好的性能。

【讨论】:

几乎,“无法评估不支持的子查询类型” 在这种情况下:( 我很久以前有类似的情况:Snowflake (LEFT JOIN) LATERAL: Unsupported subquery type cannot be evaluated @LukaszSzozda 。 . .真可惜。这是我这几天看到的最有趣的问题,而且——除非是自加入——我可以想到几种方法,但 Snowflake 不支持它们。我期待一个答案。 我会选择 MATCH,因为它是一种模式,或者最后使用递归 cte 我找到的最干净的解决方案,但它需要窗口 ARRAY_AGG:db<>fidde demo @LukaszSzozda 。 . .我添加了另一个答案。它不是“更干净”,但它只使用窗口函数。我很少添加多个答案,但它们是如此不同,以至于在这种情况下似乎是有必要的。

以上是关于使用 SQL 的第二大运行值的主要内容,如果未能解决你的问题,请参考以下文章

如何在excel中找出除去最大值以外的第二大值

如何一条sql语句查找表中第二大值

使用分而治之的数组的第二大元素

找出一个整形数组中第二大的数字

有一列数 从中选出第二大的数 sql

markdown 如何查找列表中的第二大数字