4

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

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);

我只是看不到任何方法来获得name
对于每个 MAX 选择,真正的查询将在 100-500 组中运行大约1M 行,因此性能是一个问题。

编辑

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

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;
4

3 回答 3

6

重写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.
① 我添加nameORDER BY决胜局以选择按字母顺序排列名字的行。适应我们的需要。

在外部SELECT将 next later dob( next_dob) 添加到每一行,使用lead()-simple now 和 distinct 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

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

消除了对DISTINCT查询 1 中的类似操作的需要。

结果与查询 1 完全相同。
同样,您可以添加更多列来ORDER BY为每个dob.

于 2013-03-20T01:26:21.340 回答
2

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 不允许窗口函数。

于 2013-03-20T17:37:08.333 回答
1

要获取出生日期和姓名,您可以执行以下操作:

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,那么查询将运行得更快。

于 2013-03-19T22:56:12.420 回答