【问题标题】:Skip rows if subquery returns multiple rows in Postgres如果子查询在 Postgres 中返回多行,则跳过行
【发布时间】:2020-06-22 15:34:07
【问题描述】:

我想根据 Postgres 中的子查询结果更新表 prod_replay_out。但是,子查询返回多行,但我想跳过这些行并根据子查询返回的单行更新表。

我已经引用了链接Subquery returns more than 1 row error,但max() 函数不适用于我的预期结果。你能给我一些修改查询的建议吗?谢谢。

prod_replay_out 有以下列:

卖家、买家、sender_tag、seller_tag、buyer_tag、isin、数量、in_msg_time、msg_type、cdsx_time

prod_replay_in 有以下列:

卖家、买家、sender_tag、seller_tag、buyer_tag、isin、数量、msg_type、cdsx_time

我尝试了什么?

请在下面找到更新sql:

更新sql:

update prod_replay_out O  
   set in_msg_id = 
        (Select id
           From prod_replay_in I
          Where I.msg_type   = 'CDST010'
            and I.seller     = O.seller
            and I.buyer      = O.buyer
            and I.sender_tag = O.sender_tag
            and I.seller_tag = O.seller_tag
            and I.buyer_tag  = O.buyer_tag
            and I.isin       = O.isin
            and I.quantity   = O.quantity
            and I.cdsx_time  = O.in_msg_time
            and I.cdsx_time::text like '2020-05-12%'
         ) 
where O.msg_type = 'CDST01C'
and O.cdsx_time::text like '2020-05-12%';

我尝试了以下解决方案。是正确的做法还是有什么漏洞?

update prod_replay_out O  
   set in_msg_id = 
        (Select id
           From prod_replay_in I
          Where I.msg_type   = 'CDST010'
            and I.seller     = O.seller
            and I.buyer      = O.buyer
            and I.sender_tag = O.sender_tag
            and I.seller_tag = O.seller_tag
            and I.buyer_tag  = O.buyer_tag
            and I.isin       = O.isin
            and I.quantity   = O.quantity
            and I.cdsx_time  = O.in_msg_time
            and I.cdsx_time::text like '2020-05-12%'
            and 1 = (Select count(id)
                       From prod_replay_in I
                      Where I.msg_type   = 'CDST010'
                        and I.seller     = O.seller
                        and I.buyer      = O.buyer
                        and I.sender_tag = O.sender_tag
                        and I.seller_tag = O.seller_tag
                        and I.buyer_tag  = O.buyer_tag
                        and I.isin       = O.isin
                        and I.quantity   = O.quantity
                        and I.cdsx_time  = O.in_msg_time
                        and I.cdsx_time::text like '2020-05-12%'
                    )    
                )
where O.msg_type = 'CDST01C'
  and O.cdsx_time::text like '2020-05-12%';

【问题讨论】:

  • 不清楚你需要什么逻辑。请编辑问题并向我们展示一些示例数据(表中的十几行就足够了)以及您希望从查询中得到什么结果。如果子查询返回多于 1 行,您要使用哪一行?有随机的吗?
  • 您的 Postgres 版本和实际的精确表定义(CREATE TABLE 语句)将有助于获得最佳解决方案。

标签: sql postgresql select sql-update subquery


【解决方案1】:

查询

最重要的是,不要使用相关子查询。这是这项工作的劣质工具。在FROM clause 中使用子查询。

这只会更新在源表中找到单个匹配候选行的地方(既没有也没有多个),并且只更新实际更改值的地方:

UPDATE prod_replay_out o
SET    in_msg_id = i.id
FROM  (
   SELECT i.id, i.seller, i.buyer, i.sender_tag, i.seller_tag, i.buyer_tag, i.isin, i.quantity, i.cdsx_time
   FROM   prod_replay_in i
   WHERE  i.msg_type   = 'CDST010'
   AND    i.cdsx_time >= '2020-05-12'     -- ① "sargable" expression
   AND    i.cdsx_time <  '2020-05-13'     -- ② don't cast to date, it's a valid timestamp literal
   AND    NOT EXISTS (                    -- ③ EXISTS is typically faster than counting
      SELECT FROM prod_replay_in x
      WHERE  x.id <> i.id                 -- ④ unique
      AND   (i.seller, i.buyer, i.sender_tag, i.seller_tag, i.buyer_tag, i.isin, i.quantity, i.cdsx_time)  -- ⑤ short syntax
        =   (x.seller, x.buyer, x.sender_tag, x.seller_tag, x.buyer_tag, x.isin, x.quantity, x.cdsx_time)
      )
   ) i
WHERE  o.msg_type = 'CDST01C'
AND   (i.seller, i.buyer, i.sender_tag, i.seller_tag, i.buyer_tag, i.isin, i.quantity, i.cdsx_time)
  =   (o.seller, o.buyer, o.sender_tag, o.seller_tag, o.buyer_tag, o.isin, o.quantity, o.in_msg_time)  -- ⑥ o.cdsx_time?
-- AND    o.cdsx_time >= '2020-05-12'     -- ⑦ redundant
-- AND    o.cdsx_time <  '2020-05-13'
AND   o.in_msg_id IS DISTINCT FROM i.id   -- ⑧ avoid empty updates
;

① 像 GMB 已经建议的那样,将这个谓词转换为“sargable”表达式。这通常更快,并且可以使用索引支持。

② 但如果cdsx_timetimestamp 列(似乎很可能),则不要转换为date'2020-05-12' 是一个完全有效的时间戳文字,表示当天的第一个实例。见:

如果是timestamptz 列,请考虑timezone 设置的可能影响!见:

EXISTS 通常比计算所有行更有效,因为它可以在找到另一行时立即停止。特别是如果可以有很多对等点,并且可以使用索引支持。见:

④ 假设id 是唯一的(或PK)。否则,使用系统列ctid 进行作业。见:

⑤ 方便、等效的短语法,带有 ROW 值。见:

⑥ 您的查询有:

and I.cdsx_time  = O.in_msg_time         -- !?
and I.cdsx_time::text like '2020-05-12%'

...但是:

O.cdsx_time::text like '2020-05-12%'

你不是故意写and I.cdsx_time = O.cdsx_time

⑦ 会是噪音。该限制已在子查询中实施。 (也无助于索引支持。)

⑧ 如果某些列可能已经具有所需的值,这一点很重要。然后跳过该操作,而不是全额编写相同的行版本。

如果两列都定义为NOT NULL,则简化为o.in_msg_id &lt;&gt; i.id。再次,请参阅:

指数

如果性能是一个问题,或者您反复运行它,请考虑以下索引:

对于识别源行候选的第一步(按预期查询计划的顺序!):

CREATE INDEX foo ON prod_replay_in (msg_type, cdsx_time); 

第二步排除重复:

CREATE INDEX foo ON prod_replay_in (seller, buyer, sender_tag, seller_tag, buyer_tag, isin, quantity, cdsx_time);

或任何具有足够选择性的小子集。 如果在索引扫描中包含相对较少的附加行作为“误报”,则在较少列上的较小索引通常更有效。虽然相对较少,但在接下来的FILTER 步骤中可以轻松消除这些问题。

对于识别目标行的最后一步:

CREATE INDEX foo ON prod_replay_out (msg_type, in_msg_time);

再次:或任何具有足够选择性的小子集。

【讨论】:

    【解决方案2】:

    您只想在子查询返回一行时更新。一种选择在子查询中使用聚合和having

    update prod_replay_out o  
    set in_msg_id = (
        select max(id)
        from prod_replay_in i
        where 
            i.msg_type       = 'cdst010'
            and i.seller     = o.seller
            and i.buyer      = o.buyer
            and i.sender_tag = o.sender_tag
            and i.seller_tag = o.seller_tag
            and i.buyer_tag  = o.buyer_tag
            and i.isin       = o.isin
            and i.quantity   = o.quantity
            and i.cdsx_time  = o.in_msg_time
            and i.cdsx_time  >= '2020-05-12'::date
            and i.cdsx_time  <  '2020-05-13'::date
        having count(*) = 1
    ) 
    where 
        o.msg_type = 'cdst01c'
        and o.cdsx_time  >= '2020-05-12'::date
        and o.cdsx_time  <  '2020-05-13'::date
        
    

    请注意,我重写了日期过滤器以避免转换为文本(您可以使用带有日期文字的半开间隔,这样效率更高)。

    请注意,当子查询将返回多行(或根本没有行)时,这会将 in_msg_id 更新为 null。如果你想避免这种情况,你可以在where 子句中过滤:

    update prod_replay_out o  
    set in_msg_id = (
        select max(id)
        from prod_replay_in i
        where 
            i.msg_type       = 'cdst010'
            and i.seller     = o.seller
            and i.buyer      = o.buyer
            and i.sender_tag = o.sender_tag
            and i.seller_tag = o.seller_tag
            and i.buyer_tag  = o.buyer_tag
            and i.isin       = o.isin
            and i.quantity   = o.quantity
            and i.cdsx_time  = o.in_msg_time
            and i.cdsx_time  >= '2020-05-12'::date
            and i.cdsx_time  <  '2020-05-13'::date
        having count(*) = 1
    ) 
    where 
        o.msg_type = 'cdst01c'
        and o.cdsx_time  >= '2020-05-12'::date
        and o.cdsx_time  <  '2020-05-13'::date
        and (
            select count(*)
            from prod_replay_in i
            where 
                i.msg_type       = 'cdst010'
                and i.seller     = o.seller
                and i.buyer      = o.buyer
                and i.sender_tag = o.sender_tag
                and i.seller_tag = o.seller_tag
                and i.buyer_tag  = o.buyer_tag
                and i.isin       = o.isin
                and i.quantity   = o.quantity
                and i.cdsx_time  = o.in_msg_time
                and i.cdsx_time  >= '2020-05-12'::date
                and i.cdsx_time  <  '2020-05-13'::date
        ) = 1
    

    【讨论】:

      猜你喜欢
      • 2018-08-12
      • 1970-01-01
      • 2021-02-15
      • 2016-08-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多