2010-03-03 39 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 

而且代碼的可讀性,因爲交易的東西不存在。