Postgres: INSERT, если еще не существует - PullRequest
268 голосов
/ 01 ноября 2010

Я использую 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"

Как я могу написать оператор SQL 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

Но, во-первых, является ли это излишним для того, что мне нужно, а во-вторых, как мне выполнить одну из них в виде простой строки?

Ответы [ 17 ]

1 голос
/ 30 августа 2018

Кажется, ваш столбец "сто" определен как первичный ключ и поэтому должен быть уникальным, что не соответствует действительности. Проблема не в том, а в ваших данных.

Я предлагаю вам ввести идентификатор как серийный тип для обработки первичного ключа

1 голос
/ 23 ноября 2013

Подход с наибольшим количеством голосов (от Джона Доу) как-то работает для меня, но в моем случае из ожидаемых 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;
1 голос
/ 01 ноября 2010

Класс курсора psycopgs имеет атрибут rowcount .

Этот атрибут только для чтения указывает число строк, которые были выполнены последним выполнением * () (для операторов DQL, таких как SELECT)) или затронут (для операторов DML, таких как UPDATE или INSERT).

Таким образом, вы можете сначала попробовать UPDATE и INSERT, только если rowcount равен 0.

Но в зависимости от уровней активности в вашей базе данныхвы можете столкнуться с состоянием гонки между UPDATE и INSERT, когда другой процесс может создать эту запись в промежуточный период.

0 голосов
/ 16 мая 2019

Это как раз та проблема, с которой я сталкиваюсь, и моя версия 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 голосов
/ 08 ноября 2016

Решение простое, но не сразу.
Если вы хотите использовать эту инструкцию, вы должны внести одно изменение в db:

ALTER USER user SET search_path to 'name_of_schema';

после этих изменений «INSERT» будет работать правильно.

0 голосов
/ 26 июля 2016

Вот общая функция Python, которая, учитывая имя таблицы, столбцы и значения, генерирует эквивалент upsert для postgresql.

import 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)
0 голосов
/ 26 сентября 2014

Я искал похожее решение, пытаясь найти SQL, который работает как в PostgreSQL, так и в HSQLDB. (Именно 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 );"
...