以锯齿模式选择最大值(局部最大值)

Posted

技术标签:

【中文标题】以锯齿模式选择最大值(局部最大值)【英文标题】:Selecting max values in a sawtooth pattern (local maximum) 【发布时间】:2018-09-13 11:19:45 【问题描述】:

我在 postgres 中有一个名为“ts”的表,如下所示:

dev -- numeric device id
ts -- Unix epoch timestamp
key -- string (only interested in records where key is 'u')
val -- int representing uptime in ms

我将进程正常运行时间记录到此表中,并且每隔 30 秒左右进行一次。结果是一个具有不断增加的 val 的表,当进程重新启动时,它会定期重置为 0,从而创建类似锯齿的模式。请注意,记录的实际值可能不是 0,因为重新启动后可能不会立即发生记录。

我想做的是通过定期剔除不代表预重置最大正常运行时间的值来稍微清理一下表格。有多个设备混合到同一个表中,因此最大正常运行时间必须按 dev 分组。

例子:

dev ts      key  val
1   100000  'u'  50      -- boring
1   130100  'u'  30050   -- delete this
1   160100  'u'  60050   -- >> keep this one
1   190200  'u'  100     -- this record dies
1   220200  'u'  30100   -- >> keep this one too
1   250200  'u'  300    

我想要一个查询来挑选除我上面标记的那些记录之外的所有记录,这些记录无趣并且可以删除。

剔除将在批处理过程中定期运行。

【问题讨论】:

【参考方案1】:

如果只想保持局部最大值,可以使用lead()lag()

select t.*
from (select t.*,
             lead(val) over (partition by dev order by ts) as next_val,
             lag(val) over (partition by dev order by ts) as prev_val
      from t
      where key = 'u'
     ) t
where val > prev_val and val > next_val;

【讨论】:

我不知道lead() 和lag()。这些很酷。 注意:如果你有 2 次相同的(最大值)值,那么这将不起作用。也许 50 30050 60050 60050 100 ... 很高兴您指出了这一点,尽管这与我的特定情况无关,因为我使用的时间戳不断增加。我可以获得重复值的唯一方法是,如果进程在登录后不久重新启动两次,及时以相同(短)的正常运行时间被捕获两次。如果发生这种情况,我不希望删除重复记录。但您的观点对于遇到类似问题但情况不同的人可能非常重要。【参考方案2】:

相反,为什么不更新相关记录本身呢?并且只在进程重新启动时插入一条新记录。

【讨论】:

如果我真正控制了系统的底层机制,我会这样做。 在我看来,情况似乎就是这样,因为“我正在将进程正常运行时间记录到此表”。 是的,我可以看到。你的建议很好,只是不适用于我的具体情况。感谢您提供!【参考方案3】:

因为它很有趣:使用新的 PostgreSQL 11 功能“GROUPS in window functions”来解决绑定局部最大值的问题。

问题:

dev key ts      val
1   u   100000  50
1   u   130100  30050
1   u   160100  60050 -- really single local maximum
1   u   190200  100
1   u   220200  30100 -- local maximum together with next value
1   u   250200  30100 
1   u   300000  300
1   u   500000  100
1   u   550000  1000  -- a (tied) local maximum if only 1 before and 1 after is used, which is wrong
1   u   600000  1000
1   u   650000  2000  -- real local maximum together with 2 next rows
1   u   700000  2000
1   u   720000  2000
1   u   750000  300

PostgreSQL 11 新功能:

JOOQ Blog Post explains the feature

The Postgres 11 documentation

demo: db<>fiddle

SELECT 
    dev, key, ts, val 
FROM (
     SELECT
        *, 
        -- B:
        max(val) over (order by sum, val GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) as local_max 
     FROM (
        SELECT -- A
            *, 
            sum(is_diff) over (order by ts) 
        FROM (
            SELECT 
                *,
                CASE WHEN val = lag(val) over (order by ts) THEN 0 ELSE 1 END as is_diff
            FROM test t
            WHERE key = 'u'
        )s
    )s
)s 
WHERE val = local_max

A:这只是一个准备部分。窗口函数需要一定的顺序。如果您要执行PARTITION BY val,则该表将首先由val 排序。但在本例中,您希望通过ts 持有订单。然后你想要val 的窗口函数魔法。因此,在这一部分中,我通过保持ts 的顺序来计算直接后续行中相同值的组数。 (Maybe this could be done in a better way?)

结果是这样的:

dev key ts      val     is_diff  sum
1   u   100000  50      1        1
1   u   130100  30050   1        2
1   u   160100  60050   1        3
1   u   190200  100     1        4
1   u   220200  30100   1        5     \ same group
1   u   250200  30100   0        5     /
1   u   300000  300     1        6
1   u   500000  100     1        7
1   u   550000  1000    1        8     \ same group
1   u   600000  1000    0        8     /
1   u   650000  2000    1        9     \
1   u   700000  2000    0        9     | same group
1   u   720000  2000    0        9     /
1   u   750000  300     1        10

B:这是 Postgres 11 的新功能。现在可以检查组的值。过去可以查找固定的行号。但是现在您可以检查下一组的值。说:如果你有 3 个三行具有相同的值,你可以检查下一个或上一个没有绑定的值,无论你绑定多少行。这以一种非常酷的方式解决了这个问题:

对于具有两个1000 值的示例:现在我们可以检查:下一个值是否大于当前值?不,是一样的。所以是同一组。因此,让我们看一下以下行。那是2000,而且更大。所以当前行不能是局部最大值。

使用此组窗口,您可以获得被包围组的最大值,即使存在并列值,也可以为您提供局部值。

【讨论】:

以上是关于以锯齿模式选择最大值(局部最大值)的主要内容,如果未能解决你的问题,请参考以下文章

如何在核密度估计中找到局部最大值?

UE4常用快捷键

求数组的局部极小值和极大值

Swiftui Multi Picker 以最小最大值更改选择

寻找局部最大值和最小值

SQL - 查询帮助:查找局部最大值