Postgres:INSERT如果不存在

我正在使用Python写入一个postgres数据库:

sql_string = "INSERT INTO hundred (name,name_slug,status) VALUES (" sql_string += hundred + ", '" + hundred_slug + "', " + status + ");" cursor.execute(sql_string) 

但是,因为我的一些行是相同的,我得到以下错误:

 psycopg2.IntegrityError: duplicate key value violates unique constraint "hundred_pkey" 

我怎样才能写一个“INSERT,除非这行已经存在”SQL语句?

我已经看到了像这样推荐的复杂的语句:

 IF EXISTS (SELECT * FROM invoices WHERE invoiceid = '12345') UPDATE invoices SET billed = 'TRUE' WHERE invoiceid = '12345' ELSE INSERT INTO invoices (invoiceid, billed) VALUES ('12345', 'TRUE') END IF 

但首先,这是为我所需要的矫枉过正,其次,我怎么能执行其中的一个作为一个简单的string?

我怎样才能写一个“INSERT,除非这行已经存在”SQL语句?

在PostgreSQL中有一个很好的方法来执行条件INSERT:

 INSERT INTO example_table (id, name) SELECT 1, 'John' WHERE NOT EXISTS ( SELECT id FROM example_table WHERE id = 1 ); 

CAVEAT虽然这种方法对于并发写操作不是100%可靠的。 NOT EXISTS反半连接中的SELECTINSERT本身之间有一个非常小的竞争条件。 在这种情况下可能会失败。

Postgres 9.5(自2016-01-07发布)提供了一个“upsert”命令:

 INSERT ... ON CONFLICT DO NOTHING/UPDATE 

它解决了在使用并发操作时可能遇到的许多微妙的问题,其他一些答案提出。

一种方法是创build一个非约束(没有唯一索引)表来插入所有的数据,并做一个select不同,做你的插入到你的一百表。

如此高的水平将是。 我假设在我的例子中,所有三列都是不同的,所以对于step3,将NOT EXITS连接更改为只连接百个表中的唯一列。

  1. 创build临时表。 在这里看到文档。

     CREATE TEMPORARY TABLE temp_data(name, name_slug, status); 
  2. 将数据插入临时表。

     INSERT INTO temp_data(name, name_slug, status); 
  3. 将任何索引添加到临时表中。

  4. 做主表插入。

     INSERT INTO hundred(name, name_slug, status) SELECT DISTINCT name, name_slug, status FROM hundred WHERE NOT EXISTS ( SELECT 'X' FROM temp_data WHERE temp_data.name = hundred.name AND temp_data.name_slug = hundred.name_slug AND temp_data.status = status ); 

不幸的是, PostgreSQL既不支持MERGE也不支持ON DUPLICATE KEY UPDATE ,所以你必须用两条语句来完成:

 UPDATE invoices SET billed = 'TRUE' WHERE invoices = '12345' INSERT INTO invoices (invoiceid, billed) SELECT '12345', 'TRUE' WHERE '12345' NOT IN ( SELECT invoiceid FROM invoices ) 

你可以把它包装成一个函数:

 CREATE OR REPLACE FUNCTION fn_upd_invoices(id VARCHAR(32), billed VARCHAR(32)) RETURNS VOID AS $$ UPDATE invoices SET billed = $2 WHERE invoices = $1; INSERT INTO invoices (invoiceid, billed) SELECT $1, $2 WHERE $1 NOT IN ( SELECT invoiceid FROM invoices ); $$ LANGUAGE 'sql'; 

只是叫它:

 SELECT fn_upd_invoices('12345', 'TRUE') 

如果你只是想插入或不插入(而不是更新),你可以这样做(使用发票的例子):

 INSERT INTO invoices (invoiceid, billed) SELECT '12345', 'TRUE' WHERE NOT EXISTS (SELECT 1 FROM invoices WHERE invoiceid = '12345') 

你可以使用VALUES – 在Postgres中可用:

 INSERT INTO person (name) SELECT name FROM person UNION VALUES ('Bob') EXCEPT SELECT name FROM person; 

我知道这个问题是来自不久前,但认为这可能有助于某人。 我认为最简单的方法是通过触发器。 例如:

 Create Function ignore_dups() Returns Trigger As $$ Begin If Exists ( Select * From hundred h Where -- Assuming all three fields are primary key h.name = NEW.name And h.hundred_slug = NEW.hundred_slug And h.status = NEW.status ) Then Return NULL; End If; Return NEW; End; $$ Language plpgsql; Create Trigger ignore_dups Before Insert On hundred For Each Row Execute Procedure ignore_dups(); 

从psql提示符执行此代码(或者您希望直接在数据库上执行查询)。 然后你可以像Python一样正常插入。 例如:

 sql = "Insert Into hundreds (name, name_slug, status) Values (%s, %s, %s)" cursor.execute(sql, (hundred, hundred_slug, status)) 

请注意,正如@Thomas_Wouters已经提到的,上面的代码利用了参数,而不是连接string。

INSERT .. WHERE不存在是很好的方法。 交易“信封”可以避免竞争条件:

 BEGIN; LOCK TABLE hundred IN SHARE ROW EXCLUSIVE MODE; INSERT ... ; COMMIT; 

(John Doe)最有帮助的方法对我来说确实有效,但在我的情况下,从预期的422行我只得到180.我找不到任何错误,没有任何错误,所以我寻找一个不同的简单的方法。

SELECT之后使用IF NOT FOUND THEN完全适合我。

(在PostgreSQL文档中描述)

文档示例:

 SELECT * INTO myrec FROM emp WHERE empname = myname; IF NOT FOUND THEN RAISE EXCEPTION 'employee % not found', myname; END IF; 

psycopgs游标类有属性rowcount 。

此只读属性指定最后一次执行*()所产生(对于DQL语句(如SELECT))或受影响(对于DML语句(如UPDATE或INSERT))的行数。

所以你可以先尝试更新和INSERT只有当rowcount是0。

但根据数据库中的活动级别,您可能会遇到UPDATE和INSERT之间的争用情况,在这种情况下,另一个进程可能会在此期间创build该logging。

规则很简单:

 CREATE RULE file_insert_defer AS ON INSERT TO file WHERE (EXISTS ( SELECT * FROM file WHERE file.id = new.id)) DO INSTEAD NOTHING 

但它并发写入失败…

我正在寻找一个类似的解决scheme,试图find在PostgreSQL中工作的SQL以及HSQLDB。 (HSQLDB是这样做的困难。)以您的示例为基础,这是我在其他地方find的格式。

 sql = "INSERT INTO hundred (name,name_slug,status)" sql += " ( SELECT " + hundred + ", '" + hundred_slug + "', " + status sql += " FROM hundred" sql += " WHERE name = " + hundred + " AND name_slug = '" + hundred_slug + "' AND status = " + status sql += " HAVING COUNT(*) = 0 );" 

这是一个通用的python函数,它给出了一个表名,列和值,生成了postgresql的upsert等价物。

导入json

 def upsert(table_name, id_column, other_columns, values_hash): template = """ WITH new_values ($$ALL_COLUMNS$$) as ( values ($$VALUES_LIST$$) ), upsert as ( update $$TABLE_NAME$$ m set $$SET_MAPPINGS$$ FROM new_values nv WHERE m.$$ID_COLUMN$$ = nv.$$ID_COLUMN$$ RETURNING m.* ) INSERT INTO $$TABLE_NAME$$ ($$ALL_COLUMNS$$) SELECT $$ALL_COLUMNS$$ FROM new_values WHERE NOT EXISTS (SELECT 1 FROM upsert up WHERE up.$$ID_COLUMN$$ = new_values.$$ID_COLUMN$$) """ all_columns = [id_column] + other_columns all_columns_csv = ",".join(all_columns) all_values_csv = ','.join([query_value(values_hash[column_name]) for column_name in all_columns]) set_mappings = ",".join([ c+ " = nv." +c for c in other_columns]) q = template q = q.replace("$$TABLE_NAME$$", table_name) q = q.replace("$$ID_COLUMN$$", id_column) q = q.replace("$$ALL_COLUMNS$$", all_columns_csv) q = q.replace("$$VALUES_LIST$$", all_values_csv) q = q.replace("$$SET_MAPPINGS$$", set_mappings) return q def query_value(value): if value is None: return "NULL" if type(value) in [str, unicode]: return "'%s'" % value.replace("'", "''") if type(value) == dict: return "'%s'" % json.dumps(value).replace("'", "''") if type(value) == bool: return "%s" % value if type(value) == int: return "%s" % value return value if __name__ == "__main__": my_table_name = 'mytable' my_id_column = 'id' my_other_columns = ['field1', 'field2'] my_values_hash = { 'id': 123, 'field1': "john", 'field2': "doe" } print upsert(my_table_name, my_id_column, my_other_columns, my_values_hash) 

有一个很好的方式来使用WITH查询在PostgreSQL中执行条件INSERT:Like:

 WITH a as( select id from schema.table_name where column_name = your_identical_column_value ) INSERT into schema.table_name (col_name1, col_name2) SELECT (col_name1, col_name2) WHERE NOT EXISTS ( SELECT id FROM a ) RETURNING id 

简单的解决scheme,但不立即。
如果你想使用这个指令,你必须对db进行一次修改:

 ALTER USER user SET search_path to 'name_of_schema'; 

经过这些更改后,“INSERT”将正常工作。