web-dev-qa-db-ja.com

Postgres:まだ存在しない場合はINSERT

私はpostgresデータベースに書くためにPythonを使っています:

sql_string = "INSERT INTO hundred (name,name_slug,status) VALUES ("
sql_string += hundred + ", '" + hundred_slug + "', " + status + ");"
cursor.execute(sql_string)

しかし、私の行のいくつかは同一なので、私は以下のエラーを受け取ります:

psycopg2.IntegrityError: duplicate key value  
  violates unique constraint "hundred_pkey"

この行が既に存在しない限りINSERTを書くにはどうすればいいですか?

私はこれが推奨するような複雑なステートメントを見ました:

IF EXISTS (SELECT * FROM invoices WHERE invoiceid = '12345')
UPDATE invoices SET billed = 'TRUE' WHERE invoiceid = '12345'
ELSE
INSERT INTO invoices (invoiceid, billed) VALUES ('12345', 'TRUE')
END IF

しかし、第一に、これは私が必要としていることに対してやり過ぎであり、第二に、どのようにしてそれらを単純な文字列として実行することができるでしょうか。

266
AP257

この行が既に存在しない限りINSERTを書くにはどうすればいいですか?

PostgreSQLに条件付きINSERTを実行するための良い方法があります。

INSERT INTO example_table
    (id, name)
SELECT 1, 'John'
WHERE
    NOT EXISTS (
        SELECT id FROM example_table WHERE id = 1
    );

注意ただし、このアプローチは同時書き込み操作に対して100%信頼できるわけではありません。 NOT EXISTSアンチセミジョインのSELECTINSERTとの間には非常に小さな競合条件があります。そのような条件下では失敗する可能性があります

344
John Doe

Postgres 9.5(2016-01-07以降にリリース)は "upsert" コマンドを提供します。 ONとしても知られていますINSERTのCONFLICT句

INSERT ... ON CONFLICT DO NOTHING/UPDATE

並行操作を使用するときに遭遇する可能性がある微妙な問題の多くを解決します。

328
Arie

1つの方法は、すべてのデータを挿入するための制約のない(固有の索引がない)テーブルを作成し、それとは異なるselectを実行して100のテーブルに挿入することです。

とても高いレベルになります。私の例では3つの列すべてが異なると仮定します。そのため、step3では、NOT EXITS結合を百テーブルの一意の列のみで結合するように変更します。

  1. 一時テーブルを作成します。こちらのドキュメントを参照してください。

    CREATE TEMPORARY TABLE temp_data(name, name_slug, status);
    
  2. データを一時テーブルに挿入します。

    INSERT INTO temp_data(name, name_slug, status); 
    
  3. 一時テーブルにインデックスを追加します。

  4. メインテーブルを挿入します。

    INSERT INTO hundred(name, name_slug, status) 
        SELECT DISTINCT name, name_slug, status
        FROM hundred
        WHERE NOT EXISTS (
            SELECT 'X' 
            FROM temp_data
            WHERE 
                temp_data.name          = hundred.name
                AND temp_data.name_slug = hundred.name_slug
                AND temp_data.status    = status
        );
    
44
Kuberchaun

残念ながら、PostgreSQLMERGEON DUPLICATE KEY UPDATEもサポートしていないので、2つのステートメントでそれを行う必要があります。

UPDATE  invoices
SET     billed = 'TRUE'
WHERE   invoices = '12345'

INSERT
INTO    invoices (invoiceid, billed)
SELECT  '12345', 'TRUE'
WHERE   '12345' NOT IN
        (
        SELECT  invoiceid
        FROM    invoices
        )

それを関数にラップすることができます:

CREATE OR REPLACE FUNCTION fn_upd_invoices(id VARCHAR(32), billed VARCHAR(32))
RETURNS VOID
AS
$$
        UPDATE  invoices
        SET     billed = $2
        WHERE   invoices = $1;

        INSERT
        INTO    invoices (invoiceid, billed)
        SELECT  $1, $2
        WHERE   $1 NOT IN
                (
                SELECT  invoiceid
                FROM    invoices
                );
$$
LANGUAGE 'sql';

それを呼び出すだけです。

SELECT  fn_upd_invoices('12345', 'TRUE')
16
Quassnoi

Postgresで利用可能なVALUESを利用することができます。

INSERT INTO person (name)
    SELECT name FROM person
    UNION 
    VALUES ('Bob')
    EXCEPT
    SELECT name FROM person;
10
crististm

私はこの質問が少し前からのものであることを知っていますが、これは誰かに役立つかもしれないと思いました。これを実行する最も簡単な方法はトリガーを使用することです。例えば。:

Create Function ignore_dups() Returns Trigger
As $$
Begin
    If Exists (
        Select
            *
        From
            hundred h
        Where
            -- Assuming all three fields are primary key
            h.name = NEW.name
            And h.hundred_slug = NEW.hundred_slug
            And h.status = NEW.status
    ) Then
        Return NULL;
    End If;
    Return NEW;
End;
$$ Language plpgsql;

Create Trigger ignore_dups
    Before Insert On hundred
    For Each Row
    Execute Procedure ignore_dups();

このコードをpsqlのプロンプトから実行してください(あるいはデータベース上で直接クエリを実行したい場合もあります)。それから、Pythonから普通に挿入することができます。例えば。:

sql = "Insert Into hundreds (name, name_slug, status) Values (%s, %s, %s)"
cursor.execute(sql, (hundred, hundred_slug, status))

@Thomas_Woutersがすでに述べたように、上記のコードは文字列を連結するのではなくパラメータを利用します。

7
ktr

WITH query:を使用してPostgreSQLで条件付きINSERTを実行するための良い方法があります。

WITH a as(
select 
 id 
from 
 schema.table_name 
where 
 column_name = your_identical_column_value
)
INSERT into 
 schema.table_name
(col_name1, col_name2)
SELECT
    (col_name1, col_name2)
WHERE NOT EXISTS (
     SELECT
         id
     FROM
         a
        )
  RETURNING id 
6
Ritesh Jha

挿入します。存在しない場合は良い方法です。そして、トランザクション "封筒"によって競合状態を回避することができます。

BEGIN;
LOCK TABLE hundred IN SHARE ROW EXCLUSIVE MODE;
INSERT ... ;
COMMIT;
4

ルールを使えば簡単です。

CREATE RULE file_insert_defer AS ON INSERT TO file
WHERE (EXISTS ( SELECT * FROM file WHERE file.id = new.id)) DO INSTEAD NOTHING

しかし、それは同時書き込みで失敗します...

2
user6578371

あなたがあなたの行の多くが同一であると言うならあなたは何度もチェックを終了するでしょう。あなたはそれらを送ることができ、データベースはそれを挿入するか否かを以下のようにON CONFLICT節で決定します。

  INSERT INTO Hundred (name,name_slug,status) VALUES ("sql_string += hundred  
  +",'" + hundred_slug + "', " + status + ") ON CONFLICT ON CONSTRAINT
  hundred_pkey DO NOTHING;" cursor.execute(sql_string);
1
opena

psycopgsカーソルクラスは属性 rowcount を持ちます。

この読み取り専用属性は、最後のexecute *()が生成した(SELECTのようなDQLステートメントの場合)、または影響を受けた(UPDATEやINSERTのようなDMLステートメントの場合)行数を指定します。

そのため、rowcountが0の場合に限り、最初にUPDATEを試行してINSERTを試行できます。

しかし、データベースのアクティビティレベルによっては、UPDATEとINSERTの間で競合状態に陥る可能性があります。この場合、別のプロセスがその間にそのレコードを作成する可能性があります。

1
johnbaum

あなたのコラム「百」は主キーとして定義されているように思われるので一意でなければなりませんが、そうではありません。問題はありません、それはあなたのデータにあります。

主キーを処理するために、シリアルタイプとしてIDを挿入することをお勧めします。

1
Boodoo

(John Doeからの)最も人気のある方法は私にはうまくいくが、私の場合は予想される422行から180行しか得られない。簡単な方法です。

SELECTの後にIF NOT FOUND THENを使うことは私にとっては完璧に働きます。

PostgreSQLドキュメント に記載)

ドキュメントからの例:

SELECT * INTO myrec FROM emp WHERE empname = myname;
IF NOT FOUND THEN
  RAISE EXCEPTION 'employee % not found', myname;
END IF;
1
vchrizz

私は、PostgreSQLおよびHSQLDBで機能するSQLを見つけることを試みて、同様の解決策を探していました。 (HSQLDBがこれを困難にしたのです。)あなたの例を基礎として使うと、これは私が他で見つけたフォーマットです。

sql = "INSERT INTO hundred (name,name_slug,status)"
sql += " ( SELECT " + hundred + ", '" + hundred_slug + "', " + status
sql += " FROM hundred"
sql += " WHERE name = " + hundred + " AND name_slug = '" + hundred_slug + "' AND status = " + status
sql += " HAVING COUNT(*) = 0 );"
0
Jeff Fairley

これはまさに私が直面している問題で、私のバージョンは9.5です。

そして、私は以下のSQLクエリでそれを解決します。

INSERT INTO example_table (id, name)
SELECT 1 AS id, 'John' AS name FROM example_table
WHERE NOT EXISTS(
            SELECT id FROM example_table WHERE id = 1
    )
LIMIT 1;

バージョン9.5以上で同じ問題がある人に役立つことを願っています。

読んでくれてありがとう。

0
NgocLocCoc