为每个选定的行插入另一行吗?

时间:2019-08-05 21:25:11

标签: sql postgresql

我只有一个表TableA。它具有列idtyperelatedIdanother1another2列。列type可以具有值1, 2 or 3。 我需要的是,对于TableA中的每一行,type = 1,在同一表中插入另一行,并用新插入的行的relatedId更新原始行(列id)。另外,应从原始行中复制新插入的行中某些列的值。

所以对于当前状态:

id|type|relatedId|another1

10| 1  |null|"some text"
11| 2  |null|"somthing"
12| 1  |null|"somthing else"

结果应为:

id|type|relatedId|another1

10| 1  |13  |"some text"      - now has relationship to 13
11| 2  |null|"somthing"
12| 1  |14  |"somthing else"  - now has relationship to 13
13| 3  |null|"some text"      - inserted, "another1" is copied from 10
14| 3  |null|"somthing else"  - inserted, "another1" is copied from 12

2 个答案:

答案 0 :(得分:0)

假设文本是唯一的,您可以这样做:

demo:db<>fiddle

WITH ins AS (
    INSERT INTO tablea(type, related_id, another1)
    SELECT 3, null, another1
    FROM tablea
    WHERE type = 1
    RETURNING id, another1
)
UPDATE tablea t
SET related_id = s.id
FROM (
    SELECT * FROM ins
) s
WHERE s.another1 = t.another1 AND t.type = 1

WITH子句允许顺序执行两个单独的语句。因此,首先插入新数据。使用新生成的ID,您可以随后更新旧数据。因为必须匹配原始数据,所以文本作为标识符很有用。

仅在不必使用(1,'something')的数据集时才有效。这样一来,很难确定两个记录中的哪个是每个副本的原始记录。


另一种方法也可以将type1-id存储在新的type3列中。如果您认为可以,则可以执行以下操作:

demo:db<>fiddle

WITH ins AS (
    INSERT INTO tablea(type, related_id, another1)
    SELECT 3, id, another1
    FROM tablea
    WHERE type = 1
    RETURNING id, related_id, another1
)
UPDATE tablea t
SET related_id = s.id
FROM (
    SELECT * FROM ins
) s
WHERE s.related_id = t.id

这会将原始的type1-id存储在新的type1-id中。因此,在每种情况下都可以在该值上找到原始ID。

不幸的是,您不能在另一个WITH子句中使这些列为NULL,因为WITH子句仅适用于现有数据。目前,查询本身尚未完成。因此,新记录实际上并不存在。


这可以工作...

demo:db<>fiddle

WITH to_be_copied AS (
    SELECT id, another1
    FROM tablea
    WHERE type = 1
), ins AS (
    INSERT INTO tablea(type, related_id, another1)
    SELECT 3, null, another1
    FROM to_be_copied
    ORDER BY id                         -- 1
    RETURNING id, another1
)
UPDATE tablea t
SET related_id = s.type3_id
FROM (
SELECT 
    * 
FROM 
    (SELECT id as type1_id, row_number() OVER (ORDER BY id) FROM to_be_copied) tbc 
    JOIN 
    (SELECT id as type3_id, row_number() OVER (ORDER BY id) FROM ins) i 
    ON tbc.row_number = i.row_number
) s
WHERE t.id = s.type1_id

此解决方案假定(1)中的给定顺序确保了新记录的插入顺序。实际上,我对此不太确定。但是,如果这样:首先查询所有type1记录。之后,将进行复制(以相同的顺序!)。之后,将使用旧记录ID和新记录ID。 row_number()窗口函数将连续的行计数添加到记录。因此,如果两个数据集具有相同的顺序,则旧ID应获得与其对应的新ID相同的行号。在这种情况下,可以进行识别。对于这个小例子,这行得通...

-> 编辑:这似乎是在说:是的,自Postgres 9.6 https://stackoverflow.com/a/50822258/3984221

起,订单将被保留。

答案 1 :(得分:0)

根据this question,从9.6开始,Postgres保留通过SELECT用显式ORDER BY插入的行的顺序。我们可以使用它来将插入的行与使用row_number()来自的行进行连接。

WITH
"cte1"
AS
(
SELECT "id",
       3 "type",
       "related_id",
       "another1",
       row_number() OVER (ORDER BY "id") "rn"
       FROM "tablea"
       WHERE "type" = 1
),
"cte2"
AS
(
INSERT INTO "tablea"
            ("type",
             "another1")
       SELECT "type",
              "another1"
              FROM "cte1"
              ORDER BY "id"
       RETURNING "id"
),
"cte3"
AS
(
SELECT "id",
       row_number() OVER (ORDER BY "id") "rn"
       FROM "cte2"
)
UPDATE "tablea"
       SET "related_id" = "cte3"."id"
       FROM "cte1"
            INNER JOIN "cte3"
                       ON "cte3"."rn" = "cte1"."rn"
       WHERE "cte1"."id" = "tablea"."id";

在第一个CTE中,我们获得了所有行,这些行应与ID排序的row_number()一起插入。在第二个中,我们通过从第一个CTE中选择按ID显式排序来插入它们。我们在第二个CTE中返回插入的ID,以便我们在第三个CTE中选择它,然后再次添加ID排序的row_number()。现在,我们可以通过行号加入第一个和第三个CTE,以获得成对的原始ID和新插入的ID。在此基础上,我们可以更新设置相关ID的表。

db<>fiddle