使用 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 的第二大运行值的主要内容,如果未能解决你的问题,请参考以下文章