我正在从日志文件中将记录批量插入到数据库中。偶尔(每千行中约有1行)其中一行违反主键并导致事务失败。目前,用户必须手动浏览导致失败的文件,并在尝试重新导入之前删除有问题的行。鉴于要导入数百个这样的文件,这是不切实际的。
我的问题:如何跳过违反主键约束的记录插入,而不必在每行之前执行SELECT
语句以查看它是否已存在?
注意:我知道非常相似的问题#1054695,但它似乎是SQL Server特定的答案,我使用的是PostgreSQL(通过Python / psycopg2导入)。 < / p>
答案 0 :(得分:13)
您还可以在交易中使用SAVEPOINT。
Pythonish伪代码从应用程序方面说明:
database.execute("BEGIN")
foreach data_row in input_data_dictionary:
database.execute("SAVEPOINT bulk_savepoint")
try:
database.execute("INSERT", table, data_row)
except:
database.execute("ROLLBACK TO SAVEPOINT bulk_savepoint")
log_error(data_row)
error_count = error_count + 1
else:
database.execute("RELEASE SAVEPOINT bulk_savepoint")
if error_count > error_threshold:
database.execute("ROLLBACK")
else:
database.execute("COMMIT")
编辑:这是psql中实际操作的一个实际示例,基于文档中示例的略微变化(以“&gt;”为前缀的SQL语句):
> CREATE TABLE table1 (test_field INTEGER NOT NULL PRIMARY KEY);
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "table1_pkey" for table "table1"
CREATE TABLE
> BEGIN;
BEGIN
> INSERT INTO table1 VALUES (1);
INSERT 0 1
> SAVEPOINT my_savepoint;
SAVEPOINT
> INSERT INTO table1 VALUES (1);
ERROR: duplicate key value violates unique constraint "table1_pkey"
> ROLLBACK TO SAVEPOINT my_savepoint;
ROLLBACK
> INSERT INTO table1 VALUES (3);
INSERT 0 1
> COMMIT;
COMMIT
> SELECT * FROM table1;
test_field
------------
1
3
(2 rows)
请注意,错误后插入值3,但仍在同一事务中!
SAVEPOINT的文档位于http://www.postgresql.org/docs/8.4/static/sql-savepoint.html。
答案 1 :(得分:4)
我会使用存储过程捕获您的唯一违规行为的例外情况。例如:
CREATE OR REPLACE FUNCTION my_insert(i_foo text, i_bar text)
RETURNS boolean LANGUAGE plpgsql AS
$BODY$
begin
insert into foo(x, y) values(i_foo, i_bar);
exception
when unique_violation THEN -- nothing
return true;
end;
$BODY$;
SELECT my_insert('value 1','another value');
答案 2 :(得分:1)
您可以对事务执行rollback
或在引发异常的代码(cr是游标)之前回滚到保存点:
name = uuid.uuid1().hex
cr.execute('SAVEPOINT "%s"' % name)
try:
# your failing query goes here
except Exception:
cr.execute('ROLLBACK TO SAVEPOINT "%s"' % name)
# your alternative code goes here
else:
cr.execute('RELEASE SAVEPOINT "%s"' % name)
此代码假定存在正在运行的事务,否则您将不会收到该错误消息。
直接来自creates cursors的Django postgresql后端psycopg。也许将来他们会为Django游标创建一个代理类,类似于cursor of odoo。它们使用following code(self是光标)扩展光标:
@contextmanager
@check
def savepoint(self):
"""context manager entering in a new savepoint"""
name = uuid.uuid1().hex
self.execute('SAVEPOINT "%s"' % name)
try:
yield
except Exception:
self.execute('ROLLBACK TO SAVEPOINT "%s"' % name)
raise
else:
self.execute('RELEASE SAVEPOINT "%s"' % name)
这样上下文使您的代码更容易,它将是:
try:
with cr.savepoint():
# your failing query goes here
except Exception:
# your alternative code goes here
并且代码更具可读性,因为交易内容不存在。
答案 3 :(得分:0)
或者您可以使用SSIS并使失败的行采用与成功路径不同的路径。
您是否正在使用不同的数据库,是否可以将文件批量插入临时表,然后使用SQL代码仅选择那些没有exisitng id的记录?