问题
I am trying to test the new PostgreSQL upsert syntax with the following test code, but get the syntax error:
test=> CREATE TABLE test1 (
test(> key1 integer PRIMARY KEY check (key1 > 0),
test(> key2 integer check (key2 > 0)
test(> );
CREATE TABLE
test=> CREATE OR REPLACE FUNCTION upsert(IN in_json_array jsonb)
test-> RETURNS void AS
test-> $func$
test$> UPDATE test1 t SET
test$> t.key1 = (obj->>'key1')::int,
test$> t.key2 = (obj->>'key2')::int
test$> FROM JSONB_ARRAY_ELEMENTS(in_json_array) obj
test$> WHERE t.key1 = obj->'key1'
test$> ON CONFLICT DO UPDATE SET
test$> key1 = excluded.key1,
test$> key2 = excluded.key2;
test$>
test$> $func$ LANGUAGE sql;
ERROR: syntax error at or near "ON"
LINE 9: ON CONFLICT DO UPDATE SET
^
Why does the above code fail please?
Also, the test1
table has several constraints (non-negative values and the unique primary key). How to address the uniqueness constraint only?
UPDATE 2: I have switched from UPDATE
to INSERT
(sorry for the silly mistake!), but am still struggling with the syntax:
test=> CREATE OR REPLACE FUNCTION upsert(IN in_json_array jsonb)
test-> RETURNS void AS
test-> $func$
test$> INSERT into test1 AS t (t.key1, t.key2)
test$> VALUES ((obj->>'key1')::int, (obj->>'key2')::int)
test$> FROM JSONB_ARRAY_ELEMENTS(in_json_array) obj
test$> WHERE t.key1 = obj->'key1'
test$> ON CONFLICT DO UPDATE SET
test$> t.key1 = excluded.key1,
test$> t.key2 = excluded.key2;
test$> $func$ LANGUAGE sql;
ERROR: syntax error at or near "FROM"
LINE 6: FROM JSONB_ARRAY_ELEMENTS(in_json_array) obj
^
I have also tried to change the JSON-line to:
SELECT obj FROM JSONB_ARRAY_ELEMENTS(in_json_array)
but that fails too...
Here is my test code for your convenience:
select upsert('[{"key1":1,"key2":2},{"key1":3,"key2":4}]'::jsonb);
select upsert('[{"key1":1,"key2":2},{"key1":1,"key2":4}]'::jsonb);
回答1:
If you are using a from
clause you can't use values
. It's either insert into .. values (..)
or insert into .. select ... from ...
but not both.
You also can't reference the target table from the insert in the select
clause that feeds the insert. I am not sure what you are trying to achieve with that.
You also need to qualify which "conflict" should be trapped by specifying the PK columns or the name of the constraint that should be handled. In your case it should be the pk column:
All that put together, the function should look like this:
CREATE OR REPLACE FUNCTION upsert(IN in_json_array jsonb)
RETURNS void AS
$func$
INSERT into test1 (key1, key2)
select distinct on ((obj->>'key1')::int)
(obj->>'key1')::int,
(obj->>'key2')::int
FROM JSONB_ARRAY_ELEMENTS(in_json_array) obj
ON CONFLICT (key1) DO
UPDATE SET key1 = excluded.key1,
key2 = excluded.key2;
$func$
LANGUAGE sql;
The distinct on ()
will make sure that only one value for key1 will be returned by the select to avoid the "ON CONFLICT DO UPDATE command cannot affect row a second time" error. Note that without an order by
in the select it is basically "random" which key is being chosen. If you want to pick a specific one, you need to add an order by
to the select
回答2:
As this is the top Google result for error:
ON CONFLICT DO UPDATE command cannot affect row a second time
I will add that It may be caused by a duplicate conflict VALUES, e.g.
INSERT INTO distributors (did, dname)
VALUES
(5, 'Gizmo Transglobal'),
(5, 'Associated Computing, Inc')
ON CONFLICT (did) DO UPDATE SET dname = EXCLUDED.dname;
In this case we try to insert two values with dim
set to 5
. As dim
is the index it cannot have conflict in query itself.
I encountered this error when implementing microservice and processing requests, some of them having duplicate records.
来源:https://stackoverflow.com/questions/35393252/syntax-error-in-upsert-test-code