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;
Related
I have this INSERT query, which purpose is to insert the one row in my database.
Similarly I also have a INSERT query which insert multiple rows.
One of the columns in the table is generated after the values has been generated, since it combines a set of column values to construct a name. The name itself it generated from a Trigger, and its triggered After insert, since the column values has to exist for me to generate the name.
my problem now is when I insert one row or multiple rows, I want to know the the generated column value, but when I return it, it states its null?
#$"INSERT INTO registration_table (id, ...,)
VALUES (1,...,)
RETURNING row_id, name;";
which in return gives me an id the one I inserted, but the not actual name but instead I get null..
The trigger is pretty straight forward
CREATE TRIGGER name_insert_trigger
AFTER INSERT
ON registration_table
REFERENCING NEW TABLE AS new_inserts
FOR EACH STATEMENT
WHEN (pg_trigger_depth() = 0)
EXECUTE PROCEDURE registration_entry_name();
CREATE OR REPLACE FUNCTION registration_entry_name()
RETURNS trigger AS
$$
DECLARE
BEGIN
UPDATE registration_table
SET name = |Pattern| -- This one being the actual name generated..
FROM new_inserts
WHERE new_inserts.row_id = registration_table.row_id;
RETURN null;
END;
$$
LANGUAGE plpgsql;
but the insert query above does not return the name?
why not?
You actually need a BEFORE trigger, your data values will be there. The designation of Before and After very often causes misconceptions especially of row level triggers. The terms do not indicate their timing in relation to the DML. I have found it useful to think of them as "before final data values are set" and "after final data values are set" but both run before the invoking DML completes (for now we will bypass deferred triggers). Lets look at inserts. When the before row trigger fires the NEW row contains the values at that point for every column in the row, any value not specified in the statement will be null or contain the specified default if any. Before row triggers can can change any column. After row triggers cannot change columns, if present any change is ignored.
Your description and code imply you need to combine a couple columns to generate the content of another. Since you did not specify exactly that I will build an example and demo.
create table users ( usr_id integer generated always as identity
, lname text not null
, fname text not null
, full_name text not null
) ;
create or replace
function users_bir()
returns trigger
language plpgsql
as $$
begin
if new.full_name is null
then
new.full_name = trim(new.fname) || ' ' || trim(new.lname);
end if;
return new;
end;
$$;
create trigger users_bir_trg
before insert on users
for each row
execute procedure users_bir();
insert into users(fname, lname)
values ( 'George', 'Henery')
, ( 'Samatha', 'van Horm');
insert into users(fname, lname, full_name)
values ( 'Wacky', 'Warriors','Not so tough guys');
This setup allows the full_name to be specified or generated. If only generation is desired remove the IF leaving only the assignment statement. Even better if you have Postgres 12 or higher just define the the column as a generated column. This is also in the demo.
When inserting or updating data I would like to be able to perform some math on two columns and have that entered as a value for a third column.
Table schema:
CREATE TABLE "public"."subscriptions" (
"id" int4 NOT NULL DEFAULT nextval('subscriptions_id_seq'::regclass),
"item" varchar,
"amount" float4,
"yearly_recurrance" int2,
"annual_cost" float4,
"rank" int2,
PRIMARY KEY ("id")
);
Insert Statement:
INSERT INTO "public"."subscriptions" ("item", "amount", "yearly_recurrance", "rank") VALUES ('test', '19', '7', '0');
I have created a function and trigger that in my mind should take the amount and multiply it by the yearly_recurrance and enter the result in the annual_cost field.
Function:
CREATE OR REPLACE FUNCTION public.calc_cost()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
NEW.annual_cost = NEW.amount * NEW.yearly_recurrance;
RETURN NEW;
END;
$function$
Trigger:
CREATE TRIGGER calc_cost BEFORE INSERT or UPDATE ON subscriptions
FOR EACH STATEMENT
EXECUTE PROCEDURE calc_cost();
It is not working out how I expect. Instead the values from the insert statement are put where they belong, but it is as though the function doesn't run. Not getting any errors that I can see.
I should mention that I believe the trigger is working and calling the function. If I put garbage in the function I get errors. I think the problem is in the function. Given this is my first function however, I am not sure how to work with uncommitted data. Perhaps it's not possible.
Use a generated column instead:
alter table subscriptions
add annual_cost float4 generated always as (amount * yearly_recurrance) stored;
No trigger overhead and it is always accurate.
Note: I don't recommend floats for monetary amounts; rounding errors can be problematic. Use numeric.
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.)
How can one figure out in a BEFORE INSERT Trigger, whether the user has passed the NULL - value for an null-able column explicit, or whether the user hasn't pass any value at all for that column.
In other words, I will be able to do following:
create table t (id, NUMBER, str VARCHAR2(30) );
create Trigger trg
befor INSERT on t
FOR earch row
DECLARE
BEGIN
IF :NEW.str is NULL
THEN
IF <NULL-Value was explicit passed> THEN
dbms_output.put_line( 'user has passed NULL');
ELSE
dbms_output.put_line( 'user has passed nothing');
END IF;
END IF;
END;
/
and then after
INSERT INTO t (id, str) VALUES (1, NULL);
I will see
> user has passed NULL
and after
INSERT INTO t (id) VALUES (1);
I will see
> user has passed nothing
There is no way in Oracle to programmatically distinguish a NULL deliberately passed from a defaulted NULL.
Apart from anything else, it would ascribe meaning to NULL when NULL defiantly remains the absence of meaning. Why would you want to do that?
#OneDayWhen sez:
"Codd himself proposed a second type
of NULL to mean 'inapplicable'"
Yes, a "This page intentionally left blank" for databases. Apparently towards the end of his life Codd had identified four different types of NULL. 8-)
Other flavours of RDBMS distinguish between empty string and NULL, and perhaps some places have a convention that empty string means "inapplicable" and NULL means nothing. However, Oracle treats empty string as NULL and nobody has implemented Codd's NULL2. So I think it is dangerous to ascribe meaning to NULL in Oracle.
There are some database theorists who think NULL was one of Codd's big mistakes, and that goes double for different types of NULL. For an interesting alternative take on the topic try reading Chris Date's How To Handle Missing Information Without Using NULL
Why would you want to use a trigger for this, rather than a default value on the table column? Based on your comments to APC's answer, isn't this what you're trying to achieve?
create table t(id number, str varchar2(32) default 'default value');
create view v as select * from t;
insert into t(id) values (1);
insert into t(id, str) values (2,null);
insert into v(id) values (3);
insert into v(id, str) values (4,null);
select * from t;
ID STR
---------------------- --------------------------------
1 default value
2
3 default value
4
Do the following scheme for my database:
create sequence data_sequence;
create table data_table
{
id integer primary key;
field varchar(100);
};
create view data_view as
select id, field from data_table;
create function data_insert(_new data_view) returns data_view as
$$declare
_id integer;
_result data_view%rowtype;
begin
_id := nextval('data_sequence');
insert into data_table(id, field) values(_id, _new.field);
select * into _result from data_view where id = _id;
return _result;
end;
$$
language plpgsql;
create rule insert as on insert to data_view do instead
select data_insert(new);
Then type in psql:
insert into data_view(field) values('abc');
Would like to see something like:
id | field
----+---------
1 | abc
Instead see:
data_insert
-------------
(1, "abc")
Is it possible to fix this somehow?
Thanks for any ideas.
Ultimate idea is to use this in other functions, so that I could obtain id of just inserted record without selecting for it from scratch. Something like:
insert into data_view(field) values('abc') returning id into my_variable
would be nice but doesn't work with error:
ERROR: cannot perform INSERT RETURNING on relation "data_view"
HINT: You need an unconditional ON INSERT DO INSTEAD rule with a RETURNING clause.
I don't really understand that HINT. I use PostgreSQL 8.4.
What you want to do is already built into postgres. It allows you to include a RETURNING clause on INSERT statements.
CREATE TABLE data_table (
id SERIAL,
field VARCHAR(100),
CONSTRAINT data_table_pkey PRIMARY KEY (id)
);
INSERT INTO data_table (field) VALUES ('testing') RETURNING id, field;
If you feel you must use a view, check this thread on the postgres mailing list before going any further.