【问题标题】:Basing OFFSET on another query?基于另一个查询的OFFSET?
【发布时间】:2022-11-14 01:04:52
【问题描述】:

我的表结构大致如in this post所述:

name processed processing updated ref_time
abc t f 27794395 27794160
def t f 27794395 27793440
ghi t f 27794395 27793440
jkl f f 27794395 27794160
mno t f 27794395 27793440
pqr f t 27794395 27794160

我已经基于这个表结构创建了一个dbfiddle(更多内容见下文),因此无需创建您自己的。

基于this answer,我得到了一个ref_time值的列表,用作从status_table删除“旧”条目的基础:

with 
    ref as (
        select ref_time 
        from status_table 
        group by ref_time 
        having bool_and(processed)
        order by ref_time desc
        offset 1
    )
delete from status_table s
using ref r
where s.ref_time = r.ref_time

但是现在我想对我用作offset 的内容更加复杂...我理想情况下希望保留处理所有记录的最新ref_time(根据上面的示例,offset 是@ 987654333@),但是最近的两个ref_time 其中第二个 ref_time 的关联记录比第一个多(即 offset 需要是 2 才能跳过最近的两个 ref_time)。

我认为以下查询(基于this answer)将有助于完成此任务,因为它根据ref_time 计算processed 记录的总数:

select ref_time, 
    count(*) cnt_total,
    count(*) filter(where processed) cnt_processed,
    round(avg(processed::int),2) ratio_processed
from status_table
group by ref_time
order by ratio_processed desc, ref_time desc;

所以在 this dbfiddle 中,我需要保留 ref_time=27794160(而不是像示例中那样将其包含在删除列表中),因为尽管它是第二个,但它的 cnt_total 也比第一个高。

一般来说,规则是我希望将所有 ref_time 保留到(但不包括)与之前(或更少)具有相同 cnt_totalref_time

【问题讨论】:

  • 你可以编辑倒数第二段,让它更明显你已经包含了一个 dbfiddle 吗?我只是在没有意识到的情况下离开并创建了自己的。
  • 在您提供的最后一个示例中,您有 4 个 ref_time 组(27794170、27794160、27793450、27793440)。您的目标是删除 27794170 之后的所有内容,除非之前的记录具有更高的计数(在本例中为 27794160)。如果 27793450 的计数高于 27794160,会发生什么情况?
  • @JimJimson 您问“如果 27793450 的计数高于 27794160 会怎样?”。根据最后一段中的规则,如果是这种情况,那么我们应该保留 27793450... 但请记住,由于 delete 查询中的 having bool_and(processed),我们只有在所有记录都是 @987654349 时才会删除@... 在此示例中,27793450 并非如此...因此,无论它的计数是否高于 27794160,我们都会保留它。

标签: sql postgresql aggregate-functions sql-delete gaps-and-islands


【解决方案1】:
WITH sel AS (
   SELECT ref_time
   FROM  (
      SELECT ref_time
           , count(*) FILTER (WHERE drop) OVER (ORDER BY ref_time DESC) AS drops
      FROM  (
         SELECT ref_time
              , lag(count(*)) OVER (ORDER BY ref_time DESC) >= count(*) IS TRUE AS drop
         FROM   status_table
         GROUP  BY ref_time
         HAVING bool_and(processed)
         ) sub1
      ) sub2
   WHERE drops > 0
   )
DELETE FROM status_table d
USING  sel s
WHERE  d.ref_time = s.ref_time;

fiddle

子查询sub1 主要是您已经拥有的。再加上一些:

我们只需要count(*),因为HAVING bool_and(processed) 无论如何都排除了其他情况。

lag(count(*)) OVER (ORDER BY ref_time DESC) >= count(*) 检查前一行是否相同或更大,在这种情况下,我们将其称为“丢弃”。我们希望保留第一个“下降”之前的所有行。

所以 count(*) FILTER (WHERE drop) OVER (ORDER BY ref_time DESC) AS dropssub2 计算那些“掉落”,并且 WHERE drops > 0 消除我们想要保留的前导行。

关于这种“差距和岛屿”方法:

如果status_table 上可以并发写入,您可能需要添加锁定子句FOR UPDATE 以确保。但是您不能在聚合查询中执行此操作,因此您将在执行此操作的位置添加另一个子查询……请参阅:

【讨论】:

  • 这太棒了……这么多学习点!作为一个小的补充,我运行了一个单独的 DELETE 查询,它根据 updated 时间戳清除“旧”数据(因此是该列的原因)......基本上我想保留任何 ref_time不全是processed(基于它可能仍在“处理中”……因此将它们从现有的删除列表中过滤掉)……但是一段时间后我可以假设ref_time会永远不要都是processed,然后删除与之相关的所有内容...
  • 我创建了一个我认为可行的小提琴,将我的基于updated 的 DELETE 合并到这个中,但我不禁觉得它不是最佳的......你觉得呢? dbfiddle.uk/z9I7qqeV
  • 与您相比,唯一的变化是查询中 max(updated)max_updated 的任何实例。我不喜欢重复阈值27794395(在更新一个而不是另一个时容易出现错误)但由于这可能在 bash 脚本中我可以为此使用一个变量......但也许 SQL 本身可以被整理以避免这种重复?
  • 看起来对我很好。一些建议:dbfiddle.uk/EZOtM6po 或者开始一个新问题。评论不是地方...
  • 我可以问一个新问题,但我不确定要问什么问题,因为它太具体了。我在您的精简版本中看到的问题是“旧”(但不完全是processed)数据可能会弄乱drops 序列,因为它们可能会冒泡到那个序列中......因此在我上面的小提琴中我创建了一个 old 列,并使用 ORDER DESC 将它们下拉到底部,避开 drops 序列(它应该只适用于完全 processed 数据)。在这个小提琴中查看带有ref_time=27794165(不是所有processed)的“旧”条目:dbfiddle.uk/b0xzz-3t
【解决方案2】:

这是windowfunctions的简单翻译:

with 
 count_per_completed_ref_time as
  ( select 
        ref_time, 
        count(*) cnt_total
    from  status_table 
    group by ref_time 
    having bool_and(processed)
    order by ref_time desc )
,windowed_counts as
  ( select 
        ref_time,
        cnt_total,
        row_number() over w1 as ref_time_num,
        lag(cnt_total) over w1 as preceding_cnt_total
    from count_per_completed_ref_time
    window w1 as (order by ref_time desc) )
delete from status_table s
where ref_time in --only delete completed ref_times, found in the first CTE
  ( select ref_time from count_per_completed_ref_time)
and ref_time not in --prevent deleting these
  ( select ref_time
    from windowed_counts
    where ref_time_num = 1 --top, latest completed ref_time
    or (  ref_time_num = 2 --second latest
        and cnt_total>preceding_cnt_total)--has higher total than the latest
   ) 
returning *;
  1. 选择与以前相同的ref_time 行,仅保留已完全处理的行。
  2. 使用窗口显示这些ref_time 中的order,加上前面ref_timecnt_total,在此排名中有一个higher
  3. 始终按此顺序选择顶部的ref_time,如果第二个的cnt_total 更高,则也接受。
  4. 删除在中找到的所有已完成的ref_time1., 除非他们被选中3., returning 全部删掉,供大家查看。

    Demo 以及额外的测试用例。

【讨论】:

    猜你喜欢
    • 2021-10-28
    • 2021-02-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-30
    相关资源
    最近更新 更多