539

Python を使用して postgres データベースに書き込みます。

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」SQL ステートメントを作成するにはどうすればよいですか?

次のような複雑なステートメントが推奨されているのを見てきました。

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

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

4

18 に答える 18

432

「この行が既に存在しない限り INSERT」SQL ステートメントを作成するにはどうすればよいですか?

PostgreSQL で条件付き INSERT を行う良い方法があります。

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

ただし、このアプローチは同時書き込み操作に対して 100% 信頼できるわけではありません。反セミ結合SELECT内のとそれ自体の間には、非常に小さな競合状態があります。このような状況で失敗する可能性があります。NOT EXISTSINSERT

于 2012-11-12T10:27:16.060 に答える
60

1 つのアプローチは、制約のない (一意のインデックスがない) テーブルを作成してすべてのデータを挿入し、それとは異なる選択を行って 100 のテーブルに挿入することです。

そりゃハイレベルでしょう。この例では 3 つの列がすべて異なると想定しているため、ステップ 3 では NOT EXITS 結合を変更して、100 テーブルの一意の列のみを結合します。

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

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

    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
        );
    
于 2010-11-01T15:36:30.103 に答える
19

残念ながら、どちらもPostgreSQLサポートしていないため、次の 2 つのステートメントで実行する必要があります。MERGEON DUPLICATE KEY UPDATE

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')
于 2010-11-01T14:28:27.543 に答える
16

これはまさに私が直面している問題であり、私のバージョンは 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で同じ問題を抱えている人に役立つことを願っています。

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

于 2019-05-16T06:12:39.933 に答える
14

Postgres で利用可能な VALUES を利用できます。

INSERT INTO person (name)
    SELECT name FROM person
    UNION 
    VALUES ('Bob')
    EXCEPT
    SELECT name FROM person;
于 2012-03-30T09:02:24.717 に答える
9

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

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 が既に述べたように、上記のコードは文字列を連結するのではなく、パラメーターを利用していることに注意してください。

于 2012-05-21T15:32:19.400 に答える
5

INSERT .. WHERE NOT EXISTS は良いアプローチです。また、トランザクション「エンベロープ」によって競合状態を回避できます。

BEGIN;
LOCK TABLE hundred IN SHARE ROW EXCLUSIVE MODE;
INSERT ... ;
COMMIT;
于 2015-03-26T12:56:12.163 に答える
2

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

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

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

于 2016-07-12T08:52:16.973 に答える
1

(John Doeからの)最も多くの賛成票を含むアプローチは何とか機能しますが、私の場合、予想される422行から180行しか得られません。間違ったものを見つけることができず、エラーもまったくないので、別のものを探しましたシンプルなアプローチ。

IF NOT FOUND THENの後に使用するSELECTと、私にとっては完璧に機能します。

( PostgreSQL のドキュメントに記載)

ドキュメントの例:

SELECT * INTO myrec FROM emp WHERE empname = myname;
IF NOT FOUND THEN
  RAISE EXCEPTION 'employee % not found', myname;
END IF;
于 2013-11-23T11:29:38.457 に答える
1

psycopgs カーソル クラスには属性rowcountがあります。

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

したがって、行数が 0 の場合にのみ、UPDATE を最初に試行し、INSERT のみを試行できます。

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

于 2010-11-01T14:59:26.887 に答える
1

行の多くが同一であると言うと、何度もチェックを終了します。それらを送信すると、データベースは次のように 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);
于 2019-02-21T03:18:38.643 に答える
1

列「百」は主キーとして定義されているように見えるため、一意である必要がありますが、そうではありません。問題はデータにあるのではなく、データにあります。

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

于 2018-08-30T12:34:05.447 に答える
0

HSQLDBだけでなくPostgreSQLでも機能する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 );"
于 2014-09-26T06:53:00.883 に答える
-1

これは、テーブル名、列、および値を指定して、postgresql に相当する upsert を生成する汎用の python 関数です。

jsonをインポート

def upsert(table_name, id_column, other_columns, values_hash):

    template = """
    WITH new_values ($$ALL_COLUMNS$$) as (
      values
         ($$VALUES_LIST$$)
    ),
    upsert as
    (
        update $$TABLE_NAME$$ m
            set
                $$SET_MAPPINGS$$
        FROM new_values nv
        WHERE m.$$ID_COLUMN$$ = nv.$$ID_COLUMN$$
        RETURNING m.*
    )
    INSERT INTO $$TABLE_NAME$$ ($$ALL_COLUMNS$$)
    SELECT $$ALL_COLUMNS$$
    FROM new_values
    WHERE NOT EXISTS (SELECT 1
                      FROM upsert up
                      WHERE up.$$ID_COLUMN$$ = new_values.$$ID_COLUMN$$)
    """

    all_columns = [id_column] + other_columns
    all_columns_csv = ",".join(all_columns)
    all_values_csv = ','.join([query_value(values_hash[column_name]) for column_name in all_columns])
    set_mappings = ",".join([ c+ " = nv." +c for c in other_columns])

    q = template
    q = q.replace("$$TABLE_NAME$$", table_name)
    q = q.replace("$$ID_COLUMN$$", id_column)
    q = q.replace("$$ALL_COLUMNS$$", all_columns_csv)
    q = q.replace("$$VALUES_LIST$$", all_values_csv)
    q = q.replace("$$SET_MAPPINGS$$", set_mappings)

    return q


def query_value(value):
    if value is None:
        return "NULL"
    if type(value) in [str, unicode]:
        return "'%s'" % value.replace("'", "''")
    if type(value) == dict:
        return "'%s'" % json.dumps(value).replace("'", "''")
    if type(value) == bool:
        return "%s" % value
    if type(value) == int:
        return "%s" % value
    return value


if __name__ == "__main__":

    my_table_name = 'mytable'
    my_id_column = 'id'
    my_other_columns = ['field1', 'field2']
    my_values_hash = {
        'id': 123,
        'field1': "john",
        'field2': "doe"
    }
    print upsert(my_table_name, my_id_column, my_other_columns, my_values_hash)
于 2016-07-25T23:44:37.613 に答える
-13

解決策は簡単ですが、すぐには解決しません。
この命令を使用する場合は、データベースに 1 つの変更を加える必要があります。

ALTER USER user SET search_path to 'name_of_schema';

これらの変更後、「INSERT」は正しく機能します。

于 2016-11-08T11:00:38.770 に答える