私は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
しかし、第一に、これは私が必要としていることに対してやり過ぎであり、第二に、どのようにしてそれらを単純な文字列として実行することができるでしょうか。
この行が既に存在しない限り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
アンチセミジョインのSELECT
とINSERT
との間には非常に小さな競合条件があります。そのような条件下では失敗する可能性があります。
Postgres 9.5(2016-01-07以降にリリース)は "upsert" コマンドを提供します。 ONとしても知られていますINSERTのCONFLICT句 :
INSERT ... ON CONFLICT DO NOTHING/UPDATE
並行操作を使用するときに遭遇する可能性がある微妙な問題の多くを解決します。
1つの方法は、すべてのデータを挿入するための制約のない(固有の索引がない)テーブルを作成し、それとは異なるselectを実行して100のテーブルに挿入することです。
とても高いレベルになります。私の例では3つの列すべてが異なると仮定します。そのため、step3では、NOT EXITS結合を百テーブルの一意の列のみで結合するように変更します。
一時テーブルを作成します。こちらのドキュメントを参照してください。
CREATE TEMPORARY TABLE temp_data(name, name_slug, status);
データを一時テーブルに挿入します。
INSERT INTO temp_data(name, name_slug, status);
一時テーブルにインデックスを追加します。
メインテーブルを挿入します。
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
);
残念ながら、PostgreSQL
はMERGE
もON 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')
Postgresで利用可能なVALUESを利用することができます。
INSERT INTO person (name)
SELECT name FROM person
UNION
VALUES ('Bob')
EXCEPT
SELECT name FROM person;
私はこの質問が少し前からのものであることを知っていますが、これは誰かに役立つかもしれないと思いました。これを実行する最も簡単な方法はトリガーを使用することです。例えば。:
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がすでに述べたように、上記のコードは文字列を連結するのではなくパラメータを利用します。
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
挿入します。存在しない場合は良い方法です。そして、トランザクション "封筒"によって競合状態を回避することができます。
BEGIN;
LOCK TABLE hundred IN SHARE ROW EXCLUSIVE MODE;
INSERT ... ;
COMMIT;
ルールを使えば簡単です。
CREATE RULE file_insert_defer AS ON INSERT TO file
WHERE (EXISTS ( SELECT * FROM file WHERE file.id = new.id)) DO INSTEAD NOTHING
しかし、それは同時書き込みで失敗します...
あなたがあなたの行の多くが同一であると言うならあなたは何度もチェックを終了するでしょう。あなたはそれらを送ることができ、データベースはそれを挿入するか否かを以下のように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);
psycopgsカーソルクラスは属性 rowcount を持ちます。
この読み取り専用属性は、最後のexecute *()が生成した(SELECTのようなDQLステートメントの場合)、または影響を受けた(UPDATEやINSERTのようなDMLステートメントの場合)行数を指定します。
そのため、rowcountが0の場合に限り、最初にUPDATEを試行してINSERTを試行できます。
しかし、データベースのアクティビティレベルによっては、UPDATEとINSERTの間で競合状態に陥る可能性があります。この場合、別のプロセスがその間にそのレコードを作成する可能性があります。
あなたのコラム「百」は主キーとして定義されているように思われるので一意でなければなりませんが、そうではありません。問題はありません、それはあなたのデータにあります。
主キーを処理するために、シリアルタイプとしてIDを挿入することをお勧めします。
(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;
私は、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 );"
これはまさに私が直面している問題で、私のバージョンは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以上で同じ問題がある人に役立つことを願っています。
読んでくれてありがとう。