我正在尝试使用同一表中另一行的值更新表中的多个列:
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_name
和 younger_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
以及针对许多 重复项的可能更快的查询技术:
从同一行获取 dob
和 name
保证我们保持同步。多个相关子查询不会提供这种保证,而且无论如何都会更加昂贵。
原始答案
仍然有效。
旧查询 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;
在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;
这是有效的,因为聚合函数在窗口函数之前应用。而且它应该非常快,因为两个操作都同意排序顺序。
避免像查询 1 中那样需要稍后的 DISTINCT
。
结果与查询 1 完全相同。
同样,您可以向 ORDER BY
添加更多列,以便为每个 dob
选择一个特定的人。
关于sql - 使用来自自身的值更新 PostgreSQL 表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15512015/