2010-03-03 36 views
8

我正在从日志文件中将记录批量插入到数据库中。偶尔(每千行中有1行)其中一行违反了主键,导致事务失败。目前,用户必须手动检查导致失败的文件,并在尝试重新导入之前删除违规的行。鉴于有数百个这些文件需要导入,这是不切实际的。在主键违规错误后继续交易

我的问题:如何跳过插入违反主键约束的记录,而不必在每行之前执行SELECT语句以查看它是否已存在?

注:我知道类似的问题#1054695,但它似乎是一个SQL Server特定的答案,我正在使用PostgreSQL(通过Python/psycopg2导入)。

回答

12

您也可以在交易中使用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动作(由前缀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

+0

这不会起作用,当发生错误时,事务被中止并回滚。数据库中需要一个异常处理程序。 查询失败:错误:当前事务中止,忽略命令直到事务块结束 – 2010-03-03 18:38:11

+0

是的。这就是SAVEPOINTs的重点。为了给出一个具体的例子,我编辑了我的答案。 – 2010-03-03 19:02:02

+1

----编辑---- 对不起,我错了...对我感到羞耻;)它工作正常,你说得对。 – 2010-03-03 19:06:07

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

完美,谢谢。 – John 2010-03-03 11:08:24

+0

记录您的例外情况总是会更好..您可以修改例外博客以记录它并继续。 – Guru 2010-03-03 18:18:58

+0

你可以让函数登录异常,没问题。 – 2010-03-03 18:38:50

0

或者你可以使用SSIS,并有失败的行采取比成功的一个不同的充路径。

即使您正在使用不同的数据库,您是否可以批量将文件插入到临时表中,然后使用SQL代码仅选择那些没有存在ID的记录?

+0

你能详细说明你的意思吗? – John 2010-03-03 15:57:40

+0

SSIS是SQL Server自带的数据导入工具。我没有注意到你使用的是Postgre。它仍然可以完成postgre的工作,但我不确定你会如何得到它,因为我不认为它带有免费版本的SQL Server。 – HLGEM 2010-03-03 18:02:29

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) 

此代码假定存在正在运行的事务,否则你不会收到该错误信息。

Django的PostgreSQL后端creates cursors直接从psycopg。也许在将来他们会为Django光标创建一个代理类,类似于cursor of odoo。他们延长光标与following code(自我是光标):

@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 

而且代码的可读性,因为交易的东西不存在。