I have a table where i would like to add a unique constraint on pair of columns.
DummyTable
Id name1 type1 name2 type2
1 hello firstname world lastname
I need to add a unique constraint with the combination of the 4 columns like
add constraint name_type_unique
unique ((name1, type1), (name2, type2));
Which means name and type should be unique either way around. It should not be possible to have values like:
Id name1 type1 name2 type2
1 hello firstname world lastname
2 world lastname hello firstname
Is that somehow possible?
I don't know of a native solution, and I can't promise efficiency, but have you considered a trigger?
create function dummy_table_insert_trigger()
returns trigger
as
$BODY$
DECLARE
existing integer;
BEGIN
select max (id)
into existing
from dummy_table t
where
(t.name1 = NEW.name2 and t.type1 = NEW.type2) or
(t.name2 = NEW.name1 and t.type2 = NEW.type1);
if existing is not null then
raise exception 'Conflicting record id % exists', existing;
end if;
return NEW;
END;
$BODY$
language plpgsql;
create trigger dummy_table_trigger
before insert on dummy_table
for each row
execute function dummy_table_insert_trigger()
Related
I'm using postgres 14 and my invoice table looks something like that:
name
type
location_id
uuid
invoice_id
int
...
...
The location_id is a foreign key from the location table. location_id and invoice_id are a composite primary key.
What I want to achieve now is that the invoice id for each location starts at 1 and increments automatically.
Is there a way to implement this with something like a sequence?
I solved the issue by using a before insert trigger:
create or replace Function private.invoice_before_insert()
returns trigger
language plpgsql
as $$
begin
new.invoice_id = (
select coalesce(max(invoice_id), 0) + 1
from private.invoice
where invoice_location_id = new.invoice_location_id
);
new.invoice_created_at = current_timestamp;
return new;
end;
$$;
create trigger invoice_before_insert
before insert
on private.invoice
for each row
execute function private.invoice_before_insert();
I'm not sure if this aprouch has any drawbacks tho. ¯_(ツ)_/¯
Advise me on how to get the next value of the primary key of an inserting row and use the value.
I am trying to create a table that stores thee paths.
CREATE TABLE comments (
id serial primary key,
path integer[] not null,
content varchar(200) not null
);
insert into meters_location (content, path)
values ('content_1', '{I need to put here the id of the inserted row}');
When I insert the row into the table the id generated value should be put as a path automatically, so if id = 1 then path = {1}.
it should be like:
id | path | content
-------------------------
1 | {1} | content_1
Use the RETURNING clause.
INSERT INTO meters_location (content, path) VALUES ('content_1', '{}') RETURNING id;
You can get any column's value from the affected table this way.
Edit:
If you just want to update an array column with the value of another column, you will need to use a trigger. For example:
CREATE OR REPLACE FUNCTION update_ml_path() RETURNS trigger
AS $body$
BEGIN
IF (TG_OP = 'INSERT') THEN
NEW.path = ARRAY[NEW.id];
END IF;
RETURN NEW;
END;
$body$ LANGUAGE plpgsql;
CREATE TRIGGER set_ml_path BEFORE INSERT ON meters_location
FOR EACH ROW EXECUTE FUNCTION update_ml_path();
postgres=# INSERT INTO meters_location (content, path) VALUES ('content_1', '{}');
INSERT 0 1
postgres=# SELECT * FROM meters_location;
id | path | content
----+------+-----------
1 | {1} | content_1
(1 row)
If it's another table you need to update, you will need to adjust the trigger and function accordingly.
You can use lastval() for that:
insert into meters_location (content, path)
values ('content_1', array[lastval()]);
This only works for single row inserts though.
Online example
I have 2 tables in Postgresql with the same schema, the only difference is that in one of the table id field is of type bigint. Schema of the table I need to fill with data looks like this:
create table test_int_table(
id int,
description text,
hash_code int
);
I need to copy the data from test_table with bigint id to public.test_int_table. And some of the values which are bigger than id range should be filtered out. How can I track those values without hardcoding the range?
I can do something like this, but I would like to build more generic solution:
insert into test_int_table
select * from test_table as test
where test.id not between 2147483647 and 9223372036854775808
By generic I mean without constraints on the columns names and their number. So that in case, I have multiple columns of bigint type in other tables how can I filter all of their columns values generically (without specifying a column name)?
There is no generic solution, as far as I can tell.
But I would write it as
INSERT INTO test_int_table
SELECT *
FROM test_table AS t
WHERE t.id BETWEEN -2147483647 AND 2147483647;
You can do something like this if you want to track :
Create a function like this :
CREATE OR REPLACE FUNCTION convert_to_integer(v_input bigint)
RETURNS INTEGER AS $$
DECLARE v_int_value INTEGER DEFAULT NULL;
BEGIN
BEGIN
v_int_value := v_input::INTEGER;
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'Invalid integer value: "%". Returning NULL.', v_input;
RETURN NULL;
END;
RETURN v_int_value;
END;
$$ LANGUAGE plpgsql;
and write a query like this :
INSERT INTO test_int_table SELECT * FROM test_table AS t WHERE convert_to_integer(t.id) is not null;
Or you can modify a function to return 0.
I have a table given by the source:
create table person (
id serial not null,
name varchar(50) not null unique,
age int not null,
constraint person_pkey primary key (id)
)
And I want to define a function my_func that inserts a new record or updates age with the given one. I implemented it like this:
create or replace function my_func(name varchar(50), age int) returns void as $$
begin
insert into person ("name", "age") values (my_func.name, my_func.age)
on conflict ("name") do update
set age = my_func.age
where person.name = my_func.name;
end
$$ language plpgsql;
It gives me the error:
my_func('Alex', 31);
ERROR: column reference "name" is ambiguous
I can't understand where is this ambiguous "name"? I tried INSERT and UPDATE separately, they work fine. What should I change in my function in order to make it work correctly?
Notice: I would like to keep the same names for the columns and arguments of the function.
It's apparently not a bug, but there is no way to resolve the conflict using the default variable conflict resolution behavior: https://www.postgresql.org/message-id/CAE3TBxyCn9dOF2273ki%3D4NFwsaJdYXiMQ6x2rydsWY_6p8z_zg%40mail.gmail.com
This is not a bug. It's a naming conflict due to having a parameter
and a column with the same name (a).
You can use different name for the parameter or tell Postgres how to
resolve such conflicts:
CREATE OR REPLACE FUNCTION pg_temp.testf(a int)
RETURNS void LANGUAGE plpgsql AS
$body$
#variable_conflict use_column
BEGIN INSERT INTO testt(a) SELECT testf.a ON CONFLICT (a) DO NOTHING; END
$body$;
See the documentation:
https://www.postgresql.org/docs/current/static/plpgsql-implementation.html
The key part is adding #variable_conflict use_column to the function to resolve the conflict without having to change the parameter name or reference the constraint name instead of the column name.
Likewise I see no reason why the current code shouldn't work; yet that seemes to be the case. I got it working through creating a names constraint and referencing it in the ON Conflict clause.
create table person (
id serial not null,
name varchar(50) not null,
age int not null,
constraint person_pkey primary key (id),
constraint person_name_uk unique (name)
);
--
create or replace function my_func(name varchar, age int) returns void as $$
begin
insert into person (name, age) values (my_func.name, my_func.age)
on conflict on constraint person_name_uk
do update
set age = excluded.age
where person.name = excluded.name;
end
$$ language plpgsql;
select my_func('George',35);
select * from person;
select my_func('George',45);
select * from person;
Still, it's strange!
I would like to add a constraint that will check values from related table.
I have 3 tables:
CREATE TABLE somethink_usr_rel (
user_id BIGINT NOT NULL,
stomethink_id BIGINT NOT NULL
);
CREATE TABLE usr (
id BIGINT NOT NULL,
role_id BIGINT NOT NULL
);
CREATE TABLE role (
id BIGINT NOT NULL,
type BIGINT NOT NULL
);
(If you want me to put constraint with FK let me know.)
I want to add a constraint to somethink_usr_rel that checks type in role ("two tables away"), e.g.:
ALTER TABLE somethink_usr_rel
ADD CONSTRAINT CH_sm_usr_type_check
CHECK (usr.role.type = 'SOME_ENUM');
I tried to do this with JOINs but didn't succeed. Any idea how to achieve it?
CHECK constraints cannot currently reference other tables. The manual:
Currently, CHECK expressions cannot contain subqueries nor refer to
variables other than columns of the current row.
One way is to use a trigger like demonstrated by #Wolph.
A clean solution without triggers: add redundant columns and include them in FOREIGN KEY constraints, which are the first choice to enforce referential integrity. Related answer on dba.SE with detailed instructions:
Enforcing constraints “two tables away”
Another option would be to "fake" an IMMUTABLE function doing the check and use that in a CHECK constraint. Postgres will allow this, but be aware of possible caveats. Best make that a NOT VALID constraint. See:
Disable all constraints and table checks while restoring a dump
A CHECK constraint is not an option if you need joins. You can create a trigger which raises an error instead.
Have a look at this example: http://www.postgresql.org/docs/9.1/static/plpgsql-trigger.html#PLPGSQL-TRIGGER-EXAMPLE
CREATE TABLE emp (
empname text,
salary integer,
last_date timestamp,
last_user text
);
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
BEGIN
-- Check that empname and salary are given
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname cannot be null';
END IF;
IF NEW.salary IS NULL THEN
RAISE EXCEPTION '% cannot have null salary', NEW.empname;
END IF;
-- Who works for us when she must pay for it?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;
END IF;
-- Remember who changed the payroll when
NEW.last_date := current_timestamp;
NEW.last_user := current_user;
RETURN NEW;
END;
$emp_stamp$ LANGUAGE plpgsql;
CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp
FOR EACH ROW EXECUTE PROCEDURE emp_stamp();
...i did it so (nazwa=user name, firma = company name) :
CREATE TABLE users
(
id bigserial CONSTRAINT firstkey PRIMARY KEY,
nazwa character varying(20),
firma character varying(50)
);
CREATE TABLE test
(
id bigserial CONSTRAINT firstkey PRIMARY KEY,
firma character varying(50),
towar character varying(20),
nazwisko character varying(20)
);
ALTER TABLE public.test ENABLE ROW LEVEL SECURITY;
CREATE OR REPLACE FUNCTION whoIAM3() RETURNS varchar(50) as $$
declare
result varchar(50);
BEGIN
select into result users.firma from users where users.nazwa = current_user;
return result;
END;
$$ LANGUAGE plpgsql;
CREATE POLICY user_policy ON public.test
USING (firma = whoIAM3());
CREATE FUNCTION test_trigger_function()
RETURNS trigger AS $$
BEGIN
NEW.firma:=whoIam3();
return NEW;
END
$$ LANGUAGE 'plpgsql'
CREATE TRIGGER test_trigger_insert BEFORE INSERT ON test FOR EACH ROW EXECUTE PROCEDURE test_trigger_function();