2016-10-29 25 views
9

我有一種情況,我經常需要從具有唯一約束的表中獲取一行,如果不存在,則創建它並返回。 例如我的表可能是:從INSERT以ON CONFLICT返回行而無需更新

CREATE TABLE names(
    id SERIAL PRIMARY KEY, 
    name TEXT, 
    CONSTRAINT names_name_key UNIQUE (name) 
); 

而且它包含:

id | name 
1 | bob 
2 | alice 

然後我想:

INSERT INTO names(name) VALUES ('bob') 
ON CONFLICT DO NOTHING RETURNING id; 

或許:

INSERT INTO names(name) VALUES ('bob') 
ON CONFLICT (name) DO NOTHING RETURNING id 

並讓它返回鮑勃的身份證1。但是,RETURNING只返回插入或更新的行。所以,在上面的例子中,它不會返回任何東西。爲了讓它的功能如我所願,我實際上需要:

INSERT INTO names(name) VALUES ('bob') 
ON CONFLICT ON CONSTRAINT names_name_key DO UPDATE 
SET name = 'bob' 
RETURNING id; 

這似乎有點麻煩。我想我的問題是:

  1. 什麼是不允許(我)所需的行爲的原因是什麼?

  2. 有沒有更好的方法來做到這一點?

回答

9

它的SELECT or INSERT重複的問題,涉及(但不同於)的UPSERT。 Postgres 9.5中的新的UPSERT功能仍然是有用的。

WITH ins AS (
    INSERT INTO names(name) 
    VALUES ('bob') 
    ON  CONFLICT ON CONSTRAINT names_name_key DO UPDATE 
    SET name = NULL 
    WHERE FALSE  -- never executed, but locks the row 
    RETURNING id 
    ) 
SELECT id FROM ins 
UNION ALL 
SELECT id FROM names 
WHERE name = 'bob' -- only executed if no INSERT 
LIMIT 1; 

這樣,您實際上不需要編寫新的行版本。

我想大家都知道,在每一個Postgres的寫入UPDATE該行的新版本,因爲它MVCC model - 即使name如前設置爲相同的值。這會使操作更昂貴,增加可能的併發問題/在某些情況下鎖定爭用並且額外膨脹表。

詳細解釋和如何包裝成一個功能是:

爲什麼 「排除」 不包括RETURNING子句中的列嗎?

如果併發UPDATEDELETE(從不同的會話)是不可能的,你並不需要鎖定該行並可以簡化:

WITH ins AS (
    INSERT INTO names(name) 
    VALUES ('bob') 
    ON  CONFLICT ON CONSTRAINT names_name_key DO NOTHING -- no lock needed 
    RETURNING id 
    ) 
SELECT id FROM ins 
UNION ALL 
SELECT id FROM names 
WHERE name = 'bob' -- only executed if no INSERT 
LIMIT 1; 
+0

謝謝你的回答!我認爲這似乎是一種「更好」的方式,但我不確定我描述的方法與實際的區別是什麼? – ira

+0

@ira:我在上面添加了更多解釋。 –

+0

@ErwinBrandstetter - 這是否總是返回一個ID?我試過了,它似乎沒有工作 - https://stackoverflow.com/q/46586793/435563 - 也許我做錯了什麼? – shaunc

相關問題