sql - 使用来自自身的值更新 PostgreSQL 表

标签 sql postgresql sql-update correlated-subquery window-functions

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

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,但不是年龄更大或同龄。

我可以很容易地设置小兄弟 dob,因为这是确定要与相关子查询一起使用的记录的值(我认为这是一个例子?):

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

我只是看不到任何获取名称的方法?
对于每个 MAX 选择,真正的查询将以 100-500 为一组运行大约 100 万行,因此性能是一个问题。

编辑

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

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;

最佳答案

重写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 中,使用 lead() 将下一个 dob (next_dob) 添加到每一行- 现在使用不同的 dob 很简单。然后加入那个 next_dob,剩下的就很简单了。

如果 没有年轻人 存在,则不会发生 UPDATE 并且列保持为 NULL

关于 DISTINCT ON 以及针对许多 重复项的可能更快的查询技术:

从同一行获取 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;

sqlfiddle

CTE cte 使用窗口函数 dense_rank()根据每个人的 dop 获得一个没有差距的排名。

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;

sqlfiddle

这是有效的,因为聚合函数在窗口函数之前应用。而且它应该非常快,因为两个操作都同意排序顺序。

避免像查询 1 中那样需要稍后的 DISTINCT

结果与查询 1 完全相同。
同样,您可以向 ORDER BY 添加更多列,以便为每个 dob 选择一个特定的人。

关于sql - 使用来自自身的值更新 PostgreSQL 表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15512015/

相关文章:

string - 在字符串postgresql中查找空间

sql - 用行号更新 Oracle 表列

sql-server - SQL Server - 将更新限制到特定列

sql - 数据库事务是否会阻止竞争条件?

php - 使用存储过程,得到错误: ERROR 1066: Not unique table/alias: 'users'

android - sqlite android select col(x) where (col1 like '%key%' ) or (col2 like '%key%' ) - x 是包含键的列

postgresql - 使用带有 pgbouncer 的事务池有什么优势?

ruby-on-rails - Postgresql 重复主键

MySQL错误代码: 1175 during UPDATE in MySQL Workbench

SQL匹配并删除多分隔值