J'utilise Python pour écrire dans une base de données postgres:
sql_string = "INSERT INTO hundred (name,name_slug,status) VALUES ("
sql_string += hundred + ", '" + hundred_slug + "', " + status + ");"
cursor.execute(sql_string)
Mais parce que certaines de mes lignes sont identiques, j'obtiens l'erreur suivante:
psycopg2.IntegrityError: duplicate key value
violates unique constraint "hundred_pkey"
Comment puis-je écrire une instruction SQL 'INSERT sauf si cette ligne existe déjà'?
J'ai vu des déclarations complexes comme celle-ci recommandées:
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
Mais d’une part, c’est excessif pour ce dont j’ai besoin, et d’autre part, comment puis-je exécuter l’une d’elles comme une simple chaîne?
Postgres 9.5 (publié depuis le 07/01/2016) propose une commande "upsert" , également appelée clause ON CONFLICT à INSERT :
INSERT ... ON CONFLICT DO NOTHING/UPDATE
Il résout bon nombre des problèmes subtils que vous pouvez rencontrer lorsque vous utilisez une opération simultanée, ce que proposent d’autres réponses.
Comment puis-je écrire une instruction SQL 'INSERT sauf si cette ligne existe déjà'?
Il existe un bon moyen de faire des INSERT conditionnels dans PostgreSQL:
INSERT INTO example_table
(id, name)
SELECT 1, 'John'
WHERE
NOT EXISTS (
SELECT id FROM example_table WHERE id = 1
);
MISE EN GARDE Cette approche n'est toutefois pas fiable à 100% pour les opérations d'écriture concurrentes . Il existe une très petite condition de concurrence entre le SELECT
du NOT EXISTS
anti-semi-join et le INSERT
lui-même. Il peut échouer dans de telles conditions.
Une approche consisterait à créer une table non contrainte (aucun index unique) dans laquelle insérer toutes vos données et à effectuer une sélection distincte de celle nécessaire pour effectuer votre insertion dans votre table cent.
Si haut niveau serait. Je suppose que les trois colonnes sont distinctes dans mon exemple, donc pour l'étape 3, modifiez la jointure NOT EXITS pour ne rejoindre que les colonnes uniques de la table cent.
Créer une table temporaire. Voir docs ici .
CREATE TEMPORARY TABLE temp_data(name, name_slug, status);
INSERT Data dans la table temporaire.
INSERT INTO temp_data(name, name_slug, status);
Ajoutez tous les index à la table temporaire.
Faire l'insertion de la table principale.
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
);
Malheureusement, PostgreSQL
ne prend en charge ni MERGE
ni ON DUPLICATE KEY UPDATE
, vous devrez donc le faire en deux étapes:
UPDATE invoices
SET billed = 'TRUE'
WHERE invoices = '12345'
INSERT
INTO invoices (invoiceid, billed)
SELECT '12345', 'TRUE'
WHERE '12345' NOT IN
(
SELECT invoiceid
FROM invoices
)
Vous pouvez l'envelopper dans une fonction:
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';
et juste l'appeler:
SELECT fn_upd_invoices('12345', 'TRUE')
Vous pouvez utiliser VALUES - disponible dans Postgres:
INSERT INTO person (name)
SELECT name FROM person
UNION
VALUES ('Bob')
EXCEPT
SELECT name FROM person;
Je sais que cette question remonte à un moment donné, mais je pensais que cela pourrait aider quelqu'un. Je pense que le moyen le plus simple de procéder consiste à utiliser un déclencheur. Par exemple.:
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();
Exécutez ce code à partir d'une invite psql (ou comme vous souhaitez exécuter des requêtes directement sur la base de données). Ensuite, vous pouvez insérer comme d'habitude à partir de Python. Par exemple.:
sql = "Insert Into hundreds (name, name_slug, status) Values (%s, %s, %s)"
cursor.execute(sql, (hundred, hundred_slug, status))
Notez que, comme @Thomas_Wouters l'a déjà mentionné, le code ci-dessus tire parti des paramètres plutôt que de concaténer la chaîne.
Il existe un bon moyen de faire un INSERT conditionnel dans PostgreSQL en utilisant la requête WITH: Like:
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
INSÉRER .. LORSQUE N'EXISTE PAS est une bonne approche. Et les conditions de concurrence peuvent être évitées par transaction "enveloppe":
BEGIN;
LOCK TABLE hundred IN SHARE ROW EXCLUSIVE MODE;
INSERT ... ;
COMMIT;
C'est facile avec les règles:
CREATE RULE file_insert_defer AS ON INSERT TO file
WHERE (EXISTS ( SELECT * FROM file WHERE file.id = new.id)) DO INSTEAD NOTHING
Mais cela échoue avec des écritures concurrentes ...
Si vous dites que plusieurs de vos lignes sont identiques, vous finirez par vérifier plusieurs fois. Vous pouvez les envoyer et la base de données déterminera si vous l'insérez ou non avec la clause ON CONFLICT comme suit
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);
la classe de curseur psycopgs a l'attribut nombre de lignes .
Cet attribut en lecture seule spécifie le nombre de lignes que la dernière execute * () a produites (pour les instructions DQL telles que SELECT) ou affectées (pour les instructions DML telles que UPDATE ou INSERT).
Vous pouvez donc essayer d'abord UPDATE et INSERT uniquement si rowcount est à 0.
Mais selon les niveaux d'activité de votre base de données, vous pouvez rencontrer une situation critique entre UPDATE et INSERT, un autre processus pouvant créer cet enregistrement dans l'intervalle.
Votre colonne "cent" semble être définie comme clé primaire et doit donc être unique, ce qui n'est pas le cas. Le problème n'est pas avec, mais avec vos données.
Je vous suggère d'insérer un identifiant de type série pour transférer manuellement la clé primaire
L’approche avec la plupart des votes positifs (de John Doe) fonctionne d’une manière ou d’une autre pour moi, mais dans mon cas, sur 422 lignes attendues, je n’en ai que 180. Je n’ai rien trouvé de mal et il n’ya aucune erreur, alors j’ai cherché une solution différente. approche simple.
Utiliser IF NOT FOUND THEN
après une SELECT
fonctionne parfaitement pour moi.
(décrit dans Documentation PostgreSQL) )
Exemple de documentation:
SELECT * INTO myrec FROM emp WHERE empname = myname;
IF NOT FOUND THEN
RAISE EXCEPTION 'employee % not found', myname;
END IF;
Je recherchais une solution similaire, en essayant de trouver du SQL fonctionnant à la fois dans PostgreSQL et HSQLDB. (HSQLDB était ce qui rendait cela difficile.) En vous basant sur votre exemple, voici le format que j'ai trouvé ailleurs.
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 );"
C'est exactement le problème que je rencontre et ma version est 9.5
Et je le résous avec la requête SQL ci-dessous.
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;
J'espère que cela aidera quelqu'un qui a le même problème avec la version> = 9.5.
Merci d'avoir lu.