使用自身的值更新PostgreSQL表

wwodge7n  于 2022-12-03  发布在  PostgreSQL
关注(0)|答案(3)|浏览(147)

我正尝试使用同一表中另一行的值更新表中的 * 多个列 *:

CREATE TEMP TABLE person (
  pid INT
, name VARCHAR(40)
, dob DATE
, younger_sibling_name VARCHAR(40)
, younger_sibling_dob DATE
);

INSERT INTO person VALUES (pid, name, dob)
  (1, 'John' , '1980-01-05')
, (2, 'Jimmy', '1975-04-25')
, (3, 'Sarah', '2004-02-10')
, (4, 'Frank', '1934-12-12')
;

任务是用年龄与younger_sibling_nameyounger_sibling_dob最接近的人的姓名和生日填充younger_sibling_nameyounger_sibling_dob,但不能比younger_sibling_name大或年龄相同。
我可以很容易地设置较年轻的兄弟dob,因为它是确定与相关子查询一起使用的记录的值(我认为这是一个示例?):

UPDATE person SET younger_sibling_dob = (
SELECT MAX(dob)
FROM person AS sibling
WHERE sibling.dob < person.dob);

我只是看不出有什么办法可以得到name
对于每个MAX选择,真实的查询将运行大约1 M行,每组100-500行,因此性能是一个问题。

编辑

在尝试了许多不同的方法之后,我决定采用这个方法,我认为它能够很好地平衡验证数据与中间结果,显示逻辑的意图,并充分执行:

WITH sibling AS (
  SELECT person.pid, sibling.dob, sibling.name,
         row_number() OVER (PARTITION BY person.pid
                            ORDER BY sibling.dob DESC) AS age_closeness
  FROM person
  JOIN person AS sibling ON sibling.dob < person.dob
)
UPDATE person
  SET younger_sibling_name = sibling.name
     ,younger_sibling_dob  = sibling.dob
FROM sibling
WHERE person.pid = sibling.pid
   AND sibling.age_closeness = 1;

SELECT * FROM person ORDER BY dob;
wqlqzqxt

wqlqzqxt1#

重写2022
我预计您添加的解决方案的性能会很差,因为它做了大量不必要的工作。
当有多个具有相同dob的行时,该问题和添加的解决方案没有定义要选取哪一行。通常,您需要确定性选取。此查询从具有相同dob的每组对等体中选取按字母顺序排列的名字。请根据您的需要进行调整。

UPDATE person p
SET    younger_sibling_name = y.name
     , younger_sibling_dob  = y.dob
FROM  (
   SELECT dob, name, lead(dob) OVER (ORDER BY dob) AS next_dob
   FROM  (
      SELECT DISTINCT ON (dob)
             dob, name
      FROM   person p
      ORDER  BY dob, name  -- ①
      ) sub
   ) y
WHERE  p.dob = y.next_dob;
  • db〈〉fiddle here * -使用扩展测试用例

至少从Postgres 8.4开始工作。
需要在dob上使用索引才能快速,最好是在(dob, name)上使用多列索引。
子查询sub将整个表传递一次,并按dob提取不同得行.
①我把name添加到ORDER BY中作为决胜局,以选择名字按字母顺序排列的行。
在外层的SELECT中,将后面的dobnext_dob)添加到每一行中,其中lead()--简单的,现在有不同的dob,然后连接到next_dob,剩下的就是简单的。
如果 * 没有更年轻的人 * 存在,则没有UPDATE发生,列保持NULL
关于DISTINCT ON和可能更快的 * 许多 * 重复项查询技术:

  • 是否选择每个GROUP BY组中的第一行?
  • 优化GROUP BY查询以检索每个用户的最新行

从同一行取dobname可以保证我们保持同步。多个相关子查询不能提供这种保证,而且代价会更高。

原始答案

仍然有效。

旧查询1
WITH cte AS (
   SELECT *, dense_rank() OVER (ORDER BY dob) AS drk
   FROM   person
    )
UPDATE person p
SET    younger_sibling_name = y.name
     , younger_sibling_dob  = y.dob
FROM   cte x
JOIN   (SELECT DISTINCT ON (drk) * FROM cte) y ON y.drk = x.drk - 1
WHERE  x.pid = p.pid;

古老的SQLFIDLE
在CTE cte中,根据每个人的dop,使用窗函数dense_rank()获得无间隙的排名。
cte与其自身连接,但从第二个示例中删除dob上的重复项。这样每个人都得到一个UPDATE。如果多个人共享同一个dop,则 * 同一个 * 将被选为下一个dob上所有人的弟弟。我使用以下语句执行此操作:

(SELECT DISTINCT ON (rnk) * FROM cte)

ORDER BY rnk, ...添加到此子查询中,以便为每个dob选择一个特定得人.

旧查询2

WITH cte AS (
   SELECT dob, min(name) AS name
        , row_number() OVER (ORDER BY dob) rn
   FROM   person p
   GROUP  BY dob
   )
UPDATE person p
SET    younger_sibling_name = y.name
     , younger_sibling_dob  = y.dob
FROM   cte x
JOIN   cte y ON y.rn = x.rn - 1
WHERE  x.dob = p.dob;

古老的SQLFIDLE
这是可行的,因为聚合函数是在窗口函数之前应用的,而且由于两种操作都遵循排序顺序,所以速度应该很快。
不需要像查询1中那样使用后面的DISTINCT
结果与查询1完全相同。
同样,您可以向ORDER BY添加更多列,以便为每个dob选择一个特定的人。

k10s72fa

k10s72fa2#

1)查找MAX()总是可以用NOT EXISTS(...)重写

UPDATE person dst
SET younger_sibling_name = src.name
        ,younger_sibling_dob = src.dob
FROM person src
WHERE src.dob < dst.dob
   OR src.dob = dst.dob AND src.pid < dst.pid
AND NOT EXISTS (
        SELECT * FROM person nx
        WHERE nx.dob < dst.dob
           OR nx.dob = dst.dob AND nx.pid < dst.pid
        AND nx.dob > src.dob
           OR nx.dob = src.dob AND nx.pid > src.pid
        );

2)除了rank()/ row_number(),您也可以在WINDOW上使用LAG()函数:

UPDATE person dst
SET younger_sibling_name = src.name
        ,younger_sibling_dob = src.dob
FROM    (
        SELECT pid
        , LAG(name) OVER win AS name
        , LAG(dob) OVER win AS dob 
        FROM person
        WINDOW win AS (ORDER BY dob, pid)
        ) src
WHERE src.pid = dst.pid
        ;

这两个版本都需要自联接子查询(或CTE),因为UPDATE不允许窗口函数。

xxls0lw8

xxls0lw83#

要获取dob和名称,您可以执行以下操作:

update person
    set younger_sibling_dob = (select dob
                               from person p2
                               where s.dob < person.dob
                               order by dob desc
                               limit 1),
       younger_sibling_name = (select name
                               from person p2
                               where s.dob < person.dob
                               order by dob desc
                               limit 1)

如果您在dob上有索引,则查询会执行得更快。

相关问题