简体   繁体   English

PostgreSQL插入如果不存在

[英]Postgresql insert if does not exist

I have the following query 我有以下查询

INSERT INTO address (house_number, street, city_id)
    values(11, 'test st', (select id from city where LOWER(city) = LOWER('somecity')))

Is there anyway to insert "somecity" in the city table if "somecity" does not exist in city then after inserting, it would return the ID for the inserted row? 无论如何,如果在city中不存在“ somecity”,那么是否在city表中插入“ somecity”,然后在插入之后,它将返回插入行的ID?

I did find this answer that says upsert can be used to achieve this 我确实找到了这个答案,说可以使用upsert实现此目的

https://stackoverflow.com/a/31742830/492015 https://stackoverflow.com/a/31742830/492015

but I can't find an example that inserts if select does not return the row. 但是我找不到一个如果select不返回该行插入的示例。

Instead of nesting the INSERTs, you could use a CTE to perform the INSERTs one after the other but as a single statement : 除了嵌套INSERT,您还可以使用CTE一个接一个地执行INSERT,一个语句即可

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 *

Using this setup: 使用此设置:

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

and with the INSERT above, the query 和上面的INSERT一起,查询

SELECT * FROM test_address;

yields 产量

| house_number | street  | city_id |
|--------------+---------+---------|
|           11 | test st |       1 |

and

SELECT * FROM test_city;

yields 产量

| id | city     |
|----+----------|
|  1 | somecity |

Note that the CTE replaces 请注意,CTE取代了

(select id from city where LOWER(city) = LOWER('somecity'))

with an INSERT .. ON CONFLICT .. DO UPDATE statement: 用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

I used DO UPDATE instead of DO NOTHING so that RETURNING id, city will always return something. 我使用DO UPDATE而不是DO NOTHING以便RETURNING id, city将始终返回某些内容。 If you use DO NOTHING , then nothing is returned when there is a conflict. 如果使用DO NOTHING ,则在发生冲突时不返回任何内容。

Note however that a consequence of using city = excluded.city is that the original 'Somecity' gets replaced by 'somecity' . 但是请注意,使用的结果, city = excluded.city是,原来的'Somecity'被替换为'somecity' I'm not sure you'll find that behavior acceptable, but unfortunately I haven't figured out how to do nothing when there is a conflict and yet return id and city at the same time. 我不确定您是否会接受这种行为,但是不幸的是,在发生冲突时,我还没有弄清楚如何执行任何操作,但同时返回了idcity


Another issue you may have with the above solution is that I used a unique index on lower(city) : 您可能会遇到上述解决方案的另一个问题是,我在lower(city)上使用了唯一索引:

CREATE UNIQUE INDEX test_city_uniq_idx ON test_city USING btree (lower(city));

This allows you to use the identical condition in the INSERT statement: 这使您可以在INSERT语句中使用相同的条件:

INSERT ... ON CONFLICT (lower(city))

as a substitute for the condition LOWER(city) = LOWER('somecity') which appeared in your SELECT statement. 替换出现在SELECT语句中的条件LOWER(city) = LOWER('somecity') It produces the desired effect, but the trade-off is that now you have a unique index on (lower(city)) . 它产生了预期的效果,但是要权衡的是,现在您在(lower(city))上具有唯一索引。


Regarding the followup question of how to insert into more than 2 tables: 关于如何插入两个以上表的后续问题

You can chain together more than one CTE , and the subsequent CTEs can even reference the prior CTEs. 您可以将多个CTE链接在一起 ,后续的CTE甚至可以引用先前的CTE。 For example, 例如,

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM