我有以下查询
INSERT INTO address (house_number, street, city_id)
values(11, 'test st', (select id from city where LOWER(city) = LOWER('somecity')))
无论如何在city
表中插入“somecity”如果city
中不存在“somecity”,那么在插入之后,它会返回插入行的ID吗?
我确实找到了这个答案,说upsert可以用来实现这一点
https://stackoverflow.com/a/31742830/492015
但是如果select不返回行,我找不到插入的示例。
而不是嵌套INSERT,你可以use a CTE to perform the INSERTs one after the other but as a single statement:
WITH tmp AS (
INSERT INTO test_city (city) VALUES ('somecity')
ON CONFLICT (lower(city)) DO UPDATE SET city = excluded.city
RETURNING id, city
)
INSERT INTO test_address (house_number, street, city_id)
SELECT house_number, street, id
FROM (VALUES (11, 'test st', 'somecity')) val (house_number, street, city)
LEFT JOIN tmp USING (city)
RETURNING *
使用此设置:
DROP TABLE IF EXISTS test_address;
DROP TABLE IF EXISTS test_city;
CREATE TABLE test_address (
house_number int
, street text
, city_id int
);
CREATE TABLE test_city (
id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
, city text
);
CREATE UNIQUE INDEX test_city_uniq_idx ON test_city USING btree (lower(city));
INSERT INTO test_city (city) VALUES ('Somecity');
并使用上面的INSERT查询
SELECT * FROM test_address;
产量
| house_number | street | city_id |
|--------------+---------+---------|
| 11 | test st | 1 |
和
SELECT * FROM test_city;
产量
| id | city |
|----+----------|
| 1 | somecity |
请注意,CTE取代了
(select id from city where LOWER(city) = LOWER('somecity'))
INSERT .. ON CONFLICT .. DO UPDATE声明:
INSERT INTO test_city (city) VALUES ('somecity')
ON CONFLICT (lower(city)) DO UPDATE SET city = excluded.city
RETURNING id, city
我使用DO UPDATE
而不是DO NOTHING
,以便RETURNING id, city
将永远返回一些东西。如果您使用DO NOTHING
,则在发生冲突时不会返回任何内容。
但请注意,使用city = excluded.city
的结果是原来的'Somecity'
被'somecity'
取代。我不确定你会发现这种行为是可以接受的,但不幸的是,当发生冲突并且同时返回id
和city
时,我还没有想出怎么做。
您对上述解决方案可能遇到的另一个问题是我在lower(city)
上使用了一个唯一索引:
CREATE UNIQUE INDEX test_city_uniq_idx ON test_city USING btree (lower(city));
这允许您在INSERT语句中使用相同的条件:
INSERT ... ON CONFLICT (lower(city))
作为SELECT语句中出现的条件LOWER(city) = LOWER('somecity')
的替代。它产生了预期的效果,但权衡的是,现在你在(lower(city))
上有一个独特的索引。
关于如何插入2个以上表的followup question:
你可以chain together more than one CTE,随后的CTE甚至可以参考先前的CTE。例如,
CREATE UNIQUE INDEX city_uniq_idx ON city USING btree (lower(city));
CREATE UNIQUE INDEX state_uniq_idx ON state USING btree (lower(state_code));
WITH tmpcity AS
(
INSERT INTO
city (city)
VALUES
(
'Miami'
)
ON CONFLICT (lower(city)) DO
UPDATE
SET
city = excluded.city RETURNING id, city
)
, tmpstate as
(
INSERT INTO
state (state_code)
VALUES
(
'FL'
)
ON CONFLICT (lower(state_code)) DO
UPDATE
SET
state_code = excluded.state_code RETURNING id, state_code
)
INSERT INTO
address (house_number, street, city_id, state_id)
SELECT
house_number,
street,
tmpcity.id,
tmpstate.id
FROM
(
VALUES
(
12,
'fake st.',
'Miami',
'FL'
)
)
val (house_number, street, city, state_code)
LEFT JOIN
tmpcity USING (city)
LEFT JOIN
tmpstate USING (state_code)
ON CONFLICT (street) DO NOTHING