Postgresql插入如果不存在

问题描述 投票:0回答:1

我有以下查询

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不返回行,我找不到插入的示例。

sql postgresql
1个回答
1
投票

而不是嵌套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'取代。我不确定你会发现这种行为是可以接受的,但不幸的是,当发生冲突并且同时返回idcity时,我还没有想出怎么做。


您对上述解决方案可能遇到的另一个问题是我在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