Example:
CREATE TABLE IF NOT EXISTS test
(
type character varying,
id integer
);
CREATE UNIQUE INDEX IF NOT EXISTS uniq_id_test
ON test USING btree (type, id)
WHERE (type = 'Test');
PREPARE test (text, int, text) AS
INSERT INTO test (type, id)
VALUES ($1, $2)
ON CONFLICT (type, id)
WHERE type = $3 DO
UPDATE
SET id = EXCLUDED.id;
EXECUTE test('Test', 1, 'Test');
EXECUTE test('Test', 2, 'Test');
EXECUTE test('Test', 3, 'Test');
EXECUTE test('Test', 4, 'Test');
EXECUTE test('Test', 5, 'Test');
EXECUTE test('Test', 6, 'Test');
The last EXECUTE statement always throws an error:
[42P10] ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
If we DEALLOCATE the prepared statement and then re-create it, we will have another 5 successful attempts and on the 6th attempt we again will get the error above.
All of it works fine if we replace $3 parameter with a constant value 'Test' (no errors):
PREPARE test (text, int, text) AS
INSERT INTO test (type, id)
VALUES ($1, $2)
ON CONFLICT (type, id)
WHERE type = 'Test' DO
UPDATE
SET id = EXCLUDED.id;
So, according to this, I can suppose that the root cause is that I used a parameter instead of a constant in INSERT ON CONFLICT WHERE prepared statement, and plancacher or smth works incorrectly with it. But I would like to see a more detailed explanation of the problem above because I'm not a DB specialist and don't get why it works in this way...
PG Version:
PostgreSQL 13.9 (Debian 13.9-1.pgdg110+1) on x86_64-pc-linux-gnu,
compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit
That is because from the sixth execution on, PostgreSQL uses a generic plan for this prepared statement. Such a generic plan doesn't use the parameter values. The idea behind that is to avoid the effort of re-planning the statement for every execution. As a consequence, the value of $3 is not known, and the partial index cannot be inferred.
You may make a case that this is a bug on the mailing list, but I doubt that it will be accepted as such.
The workaround you can use is to avoid generic plans by setting plan_cache_mode = force_custom_plan.
Related
In one of my SQL scripts for PostgreSQL I want to declare a value so that I can use it in several places in the rest of the script.
Here is what I did based on the following thread :
\set cat_id uuid_generate_v4()
insert into categories (id, name) values (:cat_id, 'Category 1')
insert into other_table (id, category_id) values (uuid_generate_v4(), :cat_id)
The problem is that the variable cat_id does not take the value once for all. It just replaces :cat_id by uuid_generate_v4(). Therefore, the value is not the same in the two insert queries.
How can I do to give cat_id the value of the execution of the function ?
Thanks.
When :cat_id contains uuid_generate_v4(), you're using it as a macro. \set variables in psql can be used both as macros and as variables.
Q: How can I do to give cat_id the value of the execution of the function
?
select uuid_generate_v4() as cat_id \gset
Doc:
\gset [ prefix ]
Sends the current query input buffer to the server and stores the query's output into
psql variables (see Variables). The query to be executed must return exactly one row.
Each column of the row is stored into a separate variable, named the same as the column.
Note that afterwards to inject it into a statement, the syntax to use is :'cat_id' so that it gets properly quoted as a literal.
You can use an anonymous DO block with a variable initialized with a new UUID.
DO
$$
DECLARE
cat_id uuid = uuid_generate_v4();
BEGIN
INSERT INTO categories
(id,
name)
VALUES (cat_id,
'Category 1');
INSERT INTO other_table
(id,
category_id)
VALUES (uuid_generate_v4(),
cat_id),
END;
$$
LANGUAGE PLpgSQL;
Given this table:
create table test (
name text primary key
);
I need to write a plpgsql function with a variable name that collides with the primary key name, which I must use in a on conflict clause:
create or replace function func(
name text -- this variable name...
) returns void language plpgsql as
$$
begin
insert into test (name) values (name)
on conflict (name) do update -- ...conflicts with this line
set name = func.name;
end;
$$;
This compiles, but then throws an ambiguous column reference:
select * from func('one');
ERROR: column reference "name" is ambiguous
LINE 2: on conflict (name) do update
^
DETAIL: It could refer to either a PL/pgSQL variable or a table column.
QUERY: insert into test (name) values (name)
on conflict (name) do update
set name = func.name
CONTEXT: PL/pgSQL function func(text) line 3 at SQL statement
I tried specifying the full column name as on conflict (test.name) which does not compile, or ((test.name)) which compiles:
create or replace function func(
name text
) returns void language plpgsql as
$$
begin
insert into test (name) values (name)
on conflict ((test.name)) do -- this fails too
update set name = func.name;
end;
$$;
But it fails as well:
select * from func('two');
ERROR: invalid reference to FROM-clause entry for table "test"
LINE 2: on conflict ((test.name)) do
^
HINT: There is an entry for table "test", but it cannot be referenced from this part of the query.
QUERY: insert into test (name) values (name)
on conflict ((test.name)) do
update set name = func.name
CONTEXT: PL/pgSQL function func(text) line 3 at SQL statement
Is there a solution?
Edit: I found a workaround:
on conflict on constraint test_pkey do update
where test_pkey is the table name plus _pkey. I don't know how reliable this is though. I'd still like to specify the column name instead.
to start with, name is a bad name for both variable and attribute. When you have both, code won't look good. with that in mind, you can "prefix" variable with labeled block (in example below <<fn>>``), and setvariable_conflict` to give preference to column name, see code below:
t=# create or replace function func(
name text
) returns void language plpgsql as
$$
#variable_conflict use_column
<<fn>>
declare name text :='blah';
begin
insert into test (name) values (name)
on conflict (name) do -- this no longer fails
update set name = fn.name;
end;
$$;
t=# insert into test select 'b';
INSERT 0 1
Time: 8.076 ms
t=# select func('b');
func
------
(1 row)
Time: 6.117 ms
t=# select * from test;
name
------
b
blah
(2 rows)
https://www.postgresql.org/docs/current/static/plpgsql-implementation.html#PLPGSQL-VAR-SUBST
By default, PL/pgSQL will report an error if a name in a SQL statement
could refer to either a variable or a table column. You can fix such a
problem by renaming the variable or column, or by qualifying the
ambiguous reference, or by telling PL/pgSQL which interpretation to
prefer.
and further - basically the whole link is about it.
And yet - after demonstrating how particular task this can be easily done with plpgsql, I still quote namual:
The simplest solution is to rename the variable or column. A common
coding rule is to use a different naming convention for PL/pgSQL
variables than you use for column names. For example, if you
consistently name function variables v_something while none of your
column names start with v_, no conflicts will occur.
The ON CONFLICT... syntax (as documented here) uses a unique constraint to determine if the row conflicts. You can specify this unique constraint either by listing the columns it contains (at which point Postgres "infers" the correct index to use) or by naming the constraint directly.
In your case, the unique constraint being used is the primary key constraint implicitly created during your CREATE TABLE statement. This will have a name given to it by the DBMS, unless you specify one directly; so you will need to either look up the name the DBMS has given it (and be aware that this may change if you recreate the schema later), or name it explicitly when you create the table using the syntax CONSTRAINT pk_some_name PRIMARY KEY.
You would then specify the clause as ON CONFLICT ON CONSTRAINT pk_some_name DO ... (note no brackets around the constraint name).
(Alternatively, of course, you could change your function to use an unambiguous parameter name; personally, I think it's good practice to use a prefix like p_ or in_ rather than handling conflicts on a case-by-case basis.)
On PostgreSQL 9.4.
I was surprised to see that error in our server logs, pinpointed to the sole statement inside a pl/pgsql function:
CREATE OR REPLACE FUNCTION my_upsert(
intype text,
invalue text)
RETURNS void AS
$BODY$
BEGIN
WITH upsert AS
(
UPDATE mytable
SET count = count + 1
WHERE type = inType
AND value = inValue
RETURNING *
)
INSERT INTO mytable
(
value,
type
)
SELECT inValue, inType WHERE NOT EXISTS (SELECT * FROM upsert);
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Barring upgrading to 9.5 so we can use built-in upsert functionality, how is it possible that a single statement like that could fail in that way? (And can that be avoided?)
https://www.postgresql.org/message-id/8316.1296788047%40sss.pgh.pa.us
Re: isn't "insert into where not exists" atomic?
No, it isn't: it
will fail in the presence of other transactions doing the same thing... >
AFAIR the basic alternatives are insert -> exception ->
update or taking a lock at the table level
(quoting very inaccurately - reading thread is highly recommended)
If I understand Toms directive right, before 9.5 upsert, the only option would be insert and if exception update or whatever...
I use Oracle DB V10.2.0.1.0 for my project, along with Java as the server. I am trying to add data into a few tables only through the code, but it requires using the same sequence value.
I have a sequence which represents the T_GROUP table's ID named GROUP_SEQ.
(Increment by: 1, Min_Value: 1, Max_Value: 999999999999999999999999, Cache Size: 20, Cycle: No, Order: No).
Said GROUP_SEQ is incremented by a trigger once I enter a new group into the database:
CREATE OR REPLACE TRIGGER GROUP_TRIGGER2
BEFORE INSERT ON T_GROUP for each row
begin
SELECT GROUP_SEQ.nextval
INTO :new.ID
from dual;
END;
In my code, I performed addGroup() function in my code which successfully adds a new group along with the right GROUP_SEQ value, however when I try to get the currval it fails, because I did not use nextval on it's own, and I get this exception:
ORA-08002: sequence GROUP_SEQ.currval is not yet defined in this session
Even though I did define it in the trigger. Happens the same if I run the same commands through the SQLplus cmd.
Thanks in advance!
Solved it!
The problem was indeed the connection I was using - there was a place where I used getConnection again and thus had a different session.
Thanks #krokodilko
If you have data to insert into multiple tables then write a stored procedure to do the DML for all the tables in one go:
CREATE OR REPLACE PROCEDURE add_group(
in_column_a IN T_GROUP.COLUMN_A%TYPE,
in_column_b IN T_GROUP.COLUMN_B%TYPE,
out_id OUT T_GROUP.ID%TYPE
)
AS
BEGIN
INSERT INTO T_GROUP (
id,
column_a,
column_b
) VALUES (
GROUP_SEQ.NEXTVAL,
in_column_a,
in_column_b
)
RETURNING id INTO out_id;
INSERT INTO other_table (
id
) VALUES (
out_id
);
END;
/
In almost all cases, you do not need to use triggers.
Is there any way to write an SQL input file for sqlite that would somehow "throw" an error, eg. exited the transaction with rollback, if a condition isn't met?
I have a script that is supposed to do something, but only if there is a certain row in one table. If it's not there, the execution of the script might have fatal results and corrupt the db.
The script is only started on demand right now, but I would prefer to add a fail-safe which would prevent its execution in case there is some issue.
Basically what I need is something like
/* IF */ SELECT value FROM meta WHERE key = 'version' /* != hardcoded_version_string THROW SOME EXCEPTION */
Is there any way to accomplish that? In Postgre / Oracle this could be done using PLSQL but I am not sure if sqlite support any such a thing?
Triggers can use the RAISE function to generate errors:
CREATE VIEW test AS SELECT NULL AS value;
CREATE TRIGGER test_insert
INSTEAD OF INSERT ON test
BEGIN
SELECT RAISE(FAIL, 'wrong value')
WHERE NEW.value != 'fixed_value';
END;
INSERT INTO test SELECT 'fixed_value';
INSERT INTO test SELECT 'whatever';
Error: wrong value
Is there any way to write an SQL input file for sqlite that would
somehow "throw" an error, eg. exited the transaction with rollback, if
a condition isn't met?
One workaround may be to create dummy table and explicitly violate NULL constraint:
CREATE TABLE meta("key" VARCHAR(100));
INSERT INTO meta("key") VALUES ('version');
CREATE TEMPORARY TABLE dummy(col INT NOT NULL);
Transaction:
BEGIN TRANSACTION;
INSERT INTO dummy(col)
SELECT NULL -- explicit insert of NULL
FROM meta
WHERE "key" = 'version';
-- Error: NOT NULL constraint failed: dummy.col
-- rest code
INSERT INTO meta("key")
VALUES ('val1');
INSERT INTO meta("key")
VALUES ('val2');
-- ...
COMMIT;
SqlFiddleDemo
Keep in mind that SQLite is not procedural language and this solution is a bit ugly.