我正在两个表之间进行数据迁移(拆分相关表).现有的表是reminders,它有一个start列和一个新添加的dateset_id列,指向一个新dateset表,该表也有一start列.对于每一行reminders,我想要INSERT一个新的行,dateset其中start复制的值,以及带有新插入的ID UPDATE的相应行.remindersdateset
这是我试过的SQL:
WITH inserted_datesets AS (
INSERT INTO dateset (start)
SELECT start FROM reminder
RETURNING reminder.id AS reminder_id, id AS dateset_id
)
UPDATE reminder
SET dateset_id = ids.dateset_id
FROM inserted_datesets AS ids
WHERE reminder.id = ids.reminder_id
Run Code Online (Sandbox Code Playgroud)
我收到一个错误missing FROM-clause entry for table "reminder",因为我reminder.id在RETURNING子句中包含了列,但实际上没有为插入选择它.这是有道理的,但我无法弄清楚如何修改查询以执行我需要的操作.我缺少一种完全不同的方法吗?
Vla*_*nov 10
有几种方法可以解决这个问题.
1.暂时添加一列
正如其他人所提到的,直接的方式就是一列临时添加reminder_id到dateset.原装填充它IDs从reminder表.用它来加入reminder与dateset表.删除临时列.
2.当开始是独特的
如果start列的值是唯一的,则可以通过reminder将dateset表与start列上的表连接来在没有额外列的情况下执行此操作.
INSERT INTO dateset (start)
SELECT start FROM reminder;
WITH
CTE_Joined
AS
(
SELECT
reminder.id AS reminder_id
,reminder.dateset_id AS old_dateset_id
,dateset.id AS new_dateset_id
FROM
reminder
INNER JOIN dateset ON dateset.start = reminder.start
)
UPDATE CTE_Joined
SET old_dateset_id = new_dateset_id
;
Run Code Online (Sandbox Code Playgroud)
3.开始时不是唯一的
即使在这种情况下,也可以在没有临时列的情况下进行.主要思想如下.我们来看看这个例子:
我们有两行reminder具有相同的start值和ID 3和7:
reminder
id start dateset_id
3 2015-01-01 NULL
7 2015-01-01 NULL
Run Code Online (Sandbox Code Playgroud)
将它们插入后dateset,将生成新的ID,例如1和2:
dateset
id start
1 2015-01-01
2 2015-01-01
Run Code Online (Sandbox Code Playgroud)
我们如何链接这两行并不重要.最终结果可能是
reminder
id start dateset_id
3 2015-01-01 1
7 2015-01-01 2
Run Code Online (Sandbox Code Playgroud)
要么
reminder
id start dateset_id
3 2015-01-01 2
7 2015-01-01 1
Run Code Online (Sandbox Code Playgroud)
这两种变体都是正确的.这带来了以下解决方案.
只需先插入所有行.
INSERT INTO dateset (start)
SELECT start FROM reminder;
Run Code Online (Sandbox Code Playgroud)
start知道它不是唯一的,在列上匹配/连接两个表.通过添加ROW_NUMBER和连接两列来"使它"独一无二.可以缩短查询时间,但我明确地说明了每一步:
WITH
CTE_reminder_rn
AS
(
SELECT
id
,start
,dateset_id
,ROW_NUMBER() OVER (PARTITION BY start ORDER BY id) AS rn
FROM reminder
)
,CTE_dateset_rn
AS
(
SELECT
id
,start
,ROW_NUMBER() OVER (PARTITION BY start ORDER BY id) AS rn
FROM dateset
)
,CTE_Joined
AS
(
SELECT
CTE_reminder_rn.id AS reminder_id
,CTE_reminder_rn.dateset_id AS old_dateset_id
,CTE_dateset_rn.id AS new_dateset_id
FROM
CTE_reminder_rn
INNER JOIN CTE_dateset_rn ON
CTE_dateset_rn.start = CTE_reminder_rn.start AND
CTE_dateset_rn.rn = CTE_reminder_rn.rn
)
UPDATE CTE_Joined
SET old_dateset_id = new_dateset_id
;
Run Code Online (Sandbox Code Playgroud)
我希望从代码中可以清楚地知道它的作用,特别是当你将它与没有的简单版本进行比较时ROW_NUMBER.显然,复杂的解决方案即使start是独一无二的也能工作,但它并不像一个简单的解决方案那样高效.
此解决方案假定dateset在此过程之前为空.
这是另一种做法,与弗拉基米尔到目前为止提出的3种方式不同.
临时函数将允许您读取创建的新行的ID以及查询中的其他值:
--minimal demonstration schema
CREATE TABLE dateset (
id SERIAL PRIMARY KEY,
start TIMESTAMP
-- other things here...
);
CREATE TABLE reminder (
id SERIAL PRIMARY KEY,
start TIMESTAMP,
dateset_id INTEGER REFERENCES dateset(id)
-- other things here...
);
--pre-migration data
INSERT INTO reminder (start) VALUES ('2014-02-14'), ('2014-09-06'), ('1984-01-01'), ('2014-02-14');
--all at once
BEGIN;
CREATE FUNCTION insertreturning(ts TIMESTAMP) RETURNS INTEGER AS $$
INSERT INTO dateset (start)
VALUES (ts)
RETURNING dateset.id;
$$ LANGUAGE SQL;
UPDATE reminder SET dateset_id = insertreturning(reminder.start);
DROP FUNCTION insertreturning(TIMESTAMP);
ALTER TABLE reminder DROP COLUMN start;
END;
Run Code Online (Sandbox Code Playgroud)
在我意识到写作INSERT ... RETURNING子查询可以解决问题之后,这种解决问题的方法就出现了.虽然INSERTs不允许作为子查询,但函数调用肯定是.
有趣的是,这表明返回值的DML子查询可能广泛有用.如果可能,我们只会写:
UPDATE reminder SET dateset_id = (
INSERT INTO dateset (start)
VALUES (reminder.start)
RETURNING dateset.id));
Run Code Online (Sandbox Code Playgroud)
您只能使用 RETURNING 从 INSERT 部分返回列,而不能从所选表中返回。所以,如果你愿意在你的日期集表中添加一个列提醒_id,
ALTER TABLE dateset ADD COLUMN reminder_id integer;
Run Code Online (Sandbox Code Playgroud)
以下语句将起作用:
WITH inserted_datesets AS (
INSERT INTO dateset (start, reminder_id)
SELECT start, id FROM reminder
RETURNING reminder_id, id AS dateset_id
)
UPDATE reminder
SET dateset_id = ids.dateset_id
FROM inserted_datesets AS ids
WHERE id = reminder_id
Run Code Online (Sandbox Code Playgroud)
只有当该列的值开始在提醒 都是独一无二的,下面的2条语句也能发挥作用:
INSERT INTO dateset(start) SELECT start FROM reminder;
UPDATE reminder SET dateset_id = (SELECT id FROM dateset WHERE start=reminder.start);
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
6268 次 |
| 最近记录: |