2012-11-09 81 views
2

我想插入一條記錄到表中,如果記錄已經存在,請獲取其ID,否則運行插入並獲取新記錄的ID。如果值不存在,則插入新行並獲取id

我將插入數百萬條記錄,不知道如何以有效的方式做到這一點。我現在正在做的是運行select來檢查記錄是否已經存在,如果沒有,插入它並獲取插入記錄的ID。隨着桌子的增長,我想像SELECT會殺了我。

我現在正在做與psycopg2蟒蛇看起來是這樣的:

select = ("SELECT id FROM ... WHERE ...", [...]) 
cur.execute(*select) 
if not cur.rowcount: 
    insert = ("INSERT INTO ... VALUES ... RETURNING id", [...]) 
    cur.execute(*insert) 
rid = cur.fetchone()[0] 

是它也許可以做一些事情在存儲過程是這樣的:

BEGIN 
    EXECUTE sql_insert; 
    RETURN id; 
    EXCEPTION WHEN unique_violation THEN 
     -- return id of already existing record 
     -- from the exception info ? 
END; 

如何優化的任何想法像這樣的情況?

+2

看來你正在試圖實現一個upsert。請參閱http://www.depesz.com/2012/06/10/why-is-upsert-so-complicated/ –

+0

[UPDATE/INSERT基於行是否存在]的可能重複(http://stackoverflow.com/questions/11559420/update-insert-based-on-if-a-row-exists) –

+2

我不相信這是一個upsert。我沒有做任何更新。我有數百萬條重複記錄,如果它已經在數據庫中,我需要記錄的ID。 – Cricri

回答

2

首先,這顯然是不一個作爲UPSERTUPDATE從未提及。不過,類似的併發問題也適用。

總是會有這類任務的競爭狀態,但可以將其減少到極小的時間段,而在同一時間查詢的ID只一次用(介紹修改數據的CTE在PostgreSQL 9.1):

給定一個表tbl

CREATE TABLE tbl(tbl_id serial PRIMARY KEY, some_col text UNIQUE); 

使用此查詢:

WITH x AS (SELECT 'baz'::text AS some_col) -- enter value(s) once 

    , y AS (
    SELECT x.some_col 
     , (SELECT t.tbl_id FROM tbl t WHERE t.some_col = x.some_col) AS tbl_id 
    FROM x  
    ) 

    , z AS (
    INSERT INTO tbl(some_col) 
    SELECT y.some_col 
    FROM y 
    WHERE y.tbl_id IS NULL 
    RETURNING tbl_id 
) 

SELECT COALESCE(
     (SELECT tbl_id FROM z) 
     ,(SELECT tbl_id FROM y) 
     ); 
  • CTE x只是爲了方便:輸入一次值。
  • CTE y檢索tbl_id - 如果它已經存在。
  • CTE z插入新行 - 如果沒有。
  • 最後的SELECT避免在COALESCE結構中對錶格執行另一個查詢。現在

,如果併發事務提交的新行some_col =準確CTE yz之間的「富」,但這是極不可能這可能仍然會失敗。如果發生這種情況,您將得到重複的密鑰違規,並且必須重試。沒有丟失。如果你不面對併發寫入,你可以忘記這一點。

你可以把它放到一個plpgsql函數中,並自動重新運行對重複鍵錯誤的查詢。

不用說,你需要在此設置兩個索引(如上面顯示在我的CREATE TABLE發言):

  • 一個UNIQUEPRIMARY KEY約束上tbl_id(這是serial型的!)
  • 另一個UNIQUEPRIMARY KEY或約束上some_col

兩個自動實現的索引。

+1

我的錯誤; insert-or-get-key不是一個upsert,只是在問題上密切相關。 –

相關問題