Postgresql update multiple rows with same name and id, and update the consecutive rows vrersion - postgresql

I have a table where insertion is in this form.
Table
I want the verion to get update by 1 whenever there is a new row with same name and id.
Required output
I tried using a function and trigger.
CREATE OR REPLACE FUNCTION update_ver()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$
BEGIN
update version
set ver = ver + 1
where new.name = 'A' and new.id ='1';
RETURN new;
END;
$$

-- create table
CREATE TABLE mytable (
"name" varchar NULL,
id int4 NULL,
phone varchar NULL,
email varchar NULL,
ver int4 NULL
);
-- create trigger function
CREATE OR REPLACE FUNCTION before_insert()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$
begin
new.ver = (select coalesce(max(ver), 0) + 1 from mytable where name = new.name and id = new.id);
return new;
end;
$$
-- set trigger function to table
create trigger trg_before_insert before
insert
on
mytable for each row execute function before_insert();
-- inserting sample data
INSERT INTO mytable ("name", id, phone, email) VALUES('A', 1, '123', '123#email.com');
INSERT INTO mytable ("name", id, phone, email) VALUES('B', 2, '345', '345#email.com');
INSERT INTO mytable ("name", id, phone, email) VALUES('A', 1, '456', '456#email.com');
-- select data and view
select * from mytable;
Result:
name|id|phone|email |ver|
----+--+-----+-------------+---+
A | 1|123 |123#email.com| 1|
B | 2|345 |345#email.com| 1|
A | 1|456 |456#email.com| 2|

Related

How to create trigger that calculate delta between two tables?

I have three tables. I need create trigger that calculate price difference between two tables and insert it to result table.
Algorithm is next:
If we inserting value in table2 we should check if table1 have same id, get start_price from it, calculate difference, and insert result in result table.
For example if we are insering (assume table1 is already filled) :
INSERT INTO table2 VALUES (1, 135);
we should get in result table:
1 3.58%
caculation logic: 100-((135/140) * 100) = 3.58%
I can't figure out how to do this math operation inside trigger:
CREATE TABLE table1
(
id integer NOT NULL,
start_price integer NOT NULL,
CONSTRAINT table1_pkey PRIMARY KEY (id)
);
CREATE TABLE table2
(
id integer NOT NULL,
end_price integer NOT NULL
);
CREATE TABLE result
(
id integer NOT NULL,
result_price decimal NOT NULL
);
INSERT INTO table1 VALUES (1, 140);
INSERT INTO table1 VALUES (2, 230);
INSERT INTO table1 VALUES (3, 70);
trigger:
CREATE OR REPLACE FUNCTION delta_percent_calc() RETURNS TRIGGER AS
$BODY$
BEGIN
INSERT INTO
result(id,name)
-- some logic here. seems that INSERT or UPDATE should be here because table1 and table2 price can be changed with update
RETURN new;
END;
$BODY$
language plpgsql;
UPD:. it's seems that I get it work with follow solution:
CREATE OR REPLACE FUNCTION delta_percent_calc() RETURNS TRIGGER AS
$BODY$
DECLARE
s_price integer;
BEGIN
select start_price into s_price from table1 where id = new.id;
INSERT INTO
result (id,result_price)
VALUES(new.id, round(100 - ( (new.end_price::numeric/s_price::numeric) * 100), 4 ) );
ON CONFLICT (id) DO UPDATE
SET result_price = round(100 - ( (new.end_price::numeric/s_price::numeric) * 100), 4 );
RETURN new;
END;
$BODY$
language plpgsql;
CREATE OR REPLACE TRIGGER trig_percent_calc
AFTER INSERT ON table2
FOR EACH ROW
EXECUTE PROCEDURE delta_percent_calc();

snowflake incorporate insert and update to a table

I have three snowflake tables(TEST1, TEST2, CONTROL) as below.
TEST1
create OR REPLACE table TEST1 (
id varchar(100),
name varchar(100),
address VARCHAR(64)
);
INSERT INTO TEST1 values (100, 'ABC', null);
INSERT INTO TEST1 values (200, 'XYZ', null);
INSERT INTO TEST1 values (300, 'VBN', null);
TEST2
create OR REPLACE table TEST2 (
id varchar(100),
name varchar(100),
address VARCHAR(64)
);
INSERT INTO TEST2 values (100, 'ABC', null);
INSERT INTO TEST2 values (200, 'FDS', null);
CONTROL
create OR REPLACE table CONTROL (
KEY_COLUMNS VARCHAR,
TABLE_NAME_SOURCE VARCHAR,
TABLE_NAME_TARGET VARCHAR
);
INSERT INTO CONTROL values ('id,name,address', 'TEST2','TEST1');
I want to incorporate insert and update to 'TEST2' based on 'TEST1' table.If records does not exist in TEST2, then the stored procedure should insert rows into TEST2 from TEST1
If there is any update to TEST1, it should be captured in TEST2.
My stored procedure looks like below. I have declared variables which come from control table. My script works completely fine for insert.
For update, it should update all columns specified in KEY_COLUMNS from TEST1 to TEST2. I can hardcode and update (highlighted in bold), but instead I want to use KEY_COLUMN column from control table as I did for insert something like (WHEN MATCHED THEN update set target_columns = source_columns).
CREATE OR REPLACE PROCEDURE SP()
RETURNS string
LANGUAGE SQL
AS
$$
DECLARE
source_tbl STRING := (select TABLE_NAME_SOURCE from CONTROL);
source_columns STRING;
target_columns STRING;
query1 STRING;
BEGIN
SELECT KEY_COLUMNS INTO :source_columns FROM CONTROL WHERE TABLE_NAME_SOURCE = :source_tbl;
SELECT KEY_COLUMNS INTO :target_columns FROM CONTROL WHERE TABLE_NAME_SOURCE = :source_tbl;
QUERY1 := 'MERGE INTO TEST2 AS A
USING (
select '|| :source_columns ||' from TEST1) AS B
ON A.ID=B.ID
WHEN NOT MATCHED THEN INSERT ('|| :target_columns ||')
values ('|| :source_columns ||')
**WHEN MATCHED THEN update set A.ID=B.ID, A.name=B.name, A.address=B.address**;';
EXECUTE IMMEDIATE :QUERY1;
RETURN :QUERY1;
END;
$$;
call SP();
Expected TEST2 Output
You want a new statement.
SELECT listagg(' A.'||s.value||'=B.'||s.value, ',') FROM CONTROL, TABLE(SPLIT_TO_TABLE(KEY_COLUMNS, ',')) s WHERE TABLE_NAME_SOURCE = 'TEST2';
which gives:
LISTAGG(' A.'||S.VALUE||'=B.'||S.VALUE, ',')
A.id=B.id, A.name=B.name, A.address=B.address
Thus you SP becomes:
CREATE OR REPLACE PROCEDURE SP()
RETURNS string
LANGUAGE SQL
AS
$$
DECLARE
source_tbl STRING := (select TABLE_NAME_SOURCE from CONTROL);
source_columns STRING;
target_columns STRING;
update_sets STRING;
query1 STRING;
BEGIN
SELECT KEY_COLUMNS INTO :source_columns FROM CONTROL WHERE TABLE_NAME_SOURCE = :source_tbl;
SELECT KEY_COLUMNS INTO :target_columns FROM CONTROL WHERE TABLE_NAME_SOURCE = :source_tbl;
SELECT listagg(' A.'||s.value||'=B.'||s.value, ',') INTO :update_sets FROM CONTROL, TABLE(SPLIT_TO_TABLE(KEY_COLUMNS, ',')) s WHERE TABLE_NAME_SOURCE = :source_tbl;;
QUERY1 := 'MERGE INTO TEST2 AS A
USING (
select '|| :source_columns ||' from TEST1) AS B
ON A.ID=B.ID
WHEN NOT MATCHED THEN INSERT ('|| :target_columns ||')
values ('|| :source_columns ||')
WHEN MATCHED THEN update set '|| :update_sets ||';';
EXECUTE IMMEDIATE :QUERY1;
RETURN :QUERY1;
END;
$$;
which returns
SP
MERGE INTO TEST2 AS A USING ( select id,name,address from TEST1) AS B ON A.ID=B.ID WHEN NOT MATCHED THEN INSERT (id,name,address) values (id,name,address) WHEN MATCHED THEN update set A.id=B.id, A.name=B.name, A.address=B.address;
You can try to build your update statement merge string using value fetched from below query -
SNOWFLAKE1#COMPUTE_WH#TEST_DB.PUBLIC>select * from control;
+-----------------+-------------------+-------------------+
| KEY_COLUMNS | TABLE_NAME_SOURCE | TABLE_NAME_TARGET |
|-----------------+-------------------+-------------------|
| id,name,address | TEST2 | TEST1 |
+-----------------+-------------------+-------------------+
1 Row(s) produced. Time Elapsed: 0.156s
SNOWFLAKE1#COMPUTE_WH#TEST_DB.PUBLIC>select listagg(concat('A.',value,'=','B.',value),',') as set_upd from (select * fr
om control, lateral split_to_table(control.key_columns,','));
+---------------------------------------------+
| SET_UPD |
|---------------------------------------------|
| A.id=B.id,A.name=B.name,A.address=B.address |
+---------------------------------------------+
1 Row(s) produced. Time Elapsed: 0.185s

How to condition on a list of parameters in PGSQL

I'm looking to input a list of integers into a function and return rows based on this. In this case, I want to select some attributes based on employer id, e.g. select where employer id = 102,103 and 105.
This is what I have tried.
CREATE OR REPLACE FUNCTION dummy(VARIADIC e_id NUMERIC[])
RETURNS TABLE (
emp_id INT,
first_name VARCHAR,
last_name VARCHAR,
salary INT
)
AS $$
BEGIN
RETURN QUERY SELECT em.emp_id, em.first_name, em.last_name, em.salary
FROM employee em
WHERE em.emp_id IN e_id;
END; $$
LANGUAGE PLPGSQL;
SELECT *
FROM dummy(102, 103, 105);
The error appears to be from the line containing WHERE. How am I able to select based on the list of integers e_id?
WHERE em.emp_id IN e_id
Since you are dealing with a list of numbers, you could just use the any construct instead of in:
CREATE OR REPLACE FUNCTION dummy(VARIADIC e_id NUMERIC[])
RETURNS TABLE (
emp_id INT,
first_name VARCHAR,
last_name VARCHAR,
salary INT
)
AS $$
BEGIN
RETURN QUERY SELECT em.emp_id, em.first_name, em.last_name, em.salary
FROM employee em
WHERE em.emp_id = any (e_id);
END; $$
LANGUAGE PLPGSQL;
Demo on DB Fiddle
create table employee(emp_id int, first_name varchar(50), last_name varchar(50), salary int);
insert into employee values
(1, 'a', 'b', 50),
(2, 'c', 'd', 100),
(3, 'e', 'f', 400);
CREATE OR REPLACE FUNCTION dummy(VARIADIC e_id NUMERIC[])
RETURNS TABLE (
emp_id INT,
first_name VARCHAR,
last_name VARCHAR,
salary INT
)
AS $$
BEGIN
RETURN QUERY SELECT em.emp_id, em.first_name, em.last_name, em.salary
FROM employee em
WHERE em.emp_id = any (e_id);
END; $$
LANGUAGE PLPGSQL;
SELECT * FROM dummy(1, 2);
emp_id | first_name | last_name | salary
-----: | :--------- | :-------- | -----:
1 | a | b | 50
2 | c | d | 100

I'm having an issue with this code when I try to input values into the transactions table

So I'm setting up a schema in which I can input transactions of a journal entry independent of each other but also that rely on each other (mainly to ensure that debits = credits). I set up the tables, function, and trigger. Then, when I try to input values into the transactions table, I get the error below. I'm doing all of this in pgAdmin4.
CREATE TABLE transactions (
transactions_id UUID PRIMARY KEY DEFAULT uuid_generate_v1(),
entry_id INTEGER NOT NULL,
post_date DATE NOT NULL,
account_id INTEGER NOT NULL,
contact_id INTEGER NULL,
description TEXT NOT NULL,
reference_id UUID NULL,
document_id UUID NULL,
amount NUMERIC(12,2) NOT NULL
);
CREATE TABLE entries (
id UUID PRIMARY KEY,
test_date DATE NOT NULL,
balance NUMERIC(12,2)
CHECK (balance = 0.00)
);
CREATE OR REPLACE FUNCTION transactions_biut()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
EXECUTE 'INSERT INTO entries (id,test_date,balance)
SELECT
entry_id,
post_date,
SUM(amount) AS ''balance''
FROM
transactions
GROUP BY
entry_id;';
END;
$$;
CREATE TRIGGER transactions_biut
BEFORE INSERT OR UPDATE ON transactions
FOR EACH ROW EXECUTE PROCEDURE transactions_biut();
INSERT INTO transactions (
entry_id,
post_date,
account_id,
description,
amount
)
VALUES
(
'1',
'2019-10-01',
'101',
'MISC DEBIT: PAID FOR FACEBOOK ADS',
-200.00
),
(
'1',
'2019-10-01',
'505',
'MISC DEBIT: PAID FOR FACEBOOK ADS',
200.00
);
After I execute this input, I get the following error:
ERROR: column "id" of relation "entries" does not exist
LINE 1: INSERT INTO entries (id,test_date,balance)
^
QUERY: INSERT INTO entries (id,test_date,balance)
SELECT
entry_id,
post_date,
SUM(amount) AS "balance"
FROM
transactions
GROUP BY
entry_id;
CONTEXT: PL/pgSQL function transactions_biut() line 2 at EXECUTE
SQL state: 42703
There are a few problems here:
You're not returning anything from the trigger function => should probably be return NEW or return OLD since you're not modifying anything
Since you're executing the trigger before each row, it's bound to fail for any transaction that isn't 0 => maybe you want a deferred constraint trigger?
You're not grouping by post_date, so your select should fail
You've defined entry_id as INTEGER, but entries.id is of type UUID
Also note that this isn't really going to scale (you're summing up all transactions of all days, so this will get slower and slower...)
#chirs I was able to figure out how to create a functioning solution using statement-level triggers:
CREATE TABLE transactions (
transactions_id UUID PRIMARY KEY DEFAULT uuid_generate_v1(),
entry_id INTEGER NOT NULL,
post_date DATE NOT NULL,
account_id INTEGER NOT NULL,
contact_id INTEGER NULL,
description TEXT NOT NULL,
reference_id UUID NULL,
document_id UUID NULL,
amount NUMERIC(12,2) NOT NULL
);
CREATE TABLE entries (
entry_id INTEGER PRIMARY KEY,
post_date DATE NOT NULL,
balance NUMERIC(12,2),
CHECK (balance = 0.00)
);
CREATE OR REPLACE FUNCTION transactions_entries() RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'DELETE') THEN
INSERT INTO entries
SELECT o.entry_id, o.post_date, SUM(o.amount) FROM old_table o GROUP BY o.entry_id, o.post_date;
ELSIF (TG_OP = 'UPDATE') THEN
INSERT INTO entries
SELECT o.entry_id, n.post_date, SUM(n.amount) FROM new_table n, old_table o GROUP BY o.entry_id, n.post_date;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO entries
SELECT n.entry_id,n.post_date, SUM(n.amount) FROM new_table n GROUP BY n.entry_id, n.post_date;
END IF;
RETURN NULL; -- result is ignored since this is an AFTER trigger
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER transactions_ins
AFTER INSERT ON transactions
REFERENCING NEW TABLE AS new_table
FOR EACH STATEMENT EXECUTE PROCEDURE transactions_entries();
CREATE TRIGGER transactions_upd
AFTER UPDATE ON transactions
REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
FOR EACH STATEMENT EXECUTE PROCEDURE transactions_entries();
CREATE TRIGGER transactions_del
AFTER DELETE ON transactions
REFERENCING OLD TABLE AS old_table
FOR EACH STATEMENT EXECUTE PROCEDURE transactions_entries();
Any thoughts on optimization?

Syntax error on postgresql function. impossible syntax

I try to correct this function but is impossible!!
I declare one integer "var_id", and insert in id_val the value of frist query,
if is null the tag name is inserted in a table and var_id = last insert id, otherwise do the last insert...
CREATE OR REPLACE FUNCTION public."InsertVideoTag"
(
IN in_idvideo integer,
IN in_tagname VARCHAR(25)
)
RETURNS bigint AS
$$
DECLARE var_id bigint DEFAULT NULL;
SELECT var_id := IDTag FROM Tag WHERE TagName = in_tagname;
IF var_id IS NULL
THEN
INSERT INTO tag ( TagName )
VALUES( in_tagname );
var_id := SELECT CURRVAL(pg_get_serial_sequence('public.tag','idtag'));
END IF;
INSERT INTO video_has_tag
(
IDVideo,
IDTag
)
VALUES
(
in_idvideo,
var_id
);
SELECT CURRVAL(pg_get_serial_sequence('public.video','idvideo'));
$$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER;
The function can be converted into a pure SQL one, which will make it much better performing one. Also I've noted, that the current functionality will create duplicate entries in the video_has_tag table if called multiple times with the same arguments.
I've changed the function to be idempotent.
First table structure that I've assumed:
CREATE TABLE tag (
idTag serial,
tagName text);
CREATE TABLE video_has_tag (
idVideo integer,
idTag integer);
And then the function itself:
CREATE OR REPLACE FUNCTION insVideoTag(in_idvideo integer, in_tagname text)
RETURNS integer STRICT AS $insVideoTag$
WITH
new_tag AS (
INSERT INTO tag (tagName)
SELECT $2
WHERE NOT EXISTS (SELECT 1 FROM tag WHERE tagName = $2)
RETURNING idTag, tagName
), tag_data AS (
SELECT * FROM new_tag
UNION
SELECT idTag, tagName FROM tag
WHERE tagName = $2
), new_target AS (
INSERT INTO video_has_tag(idVideo, idTag)
SELECT $1, td.idTag
FROM tag_data td
WHERE NOT EXISTS (SELECT 1 FROM video_has_tag
WHERE idVideo=$1 AND idTag=td.idTag)
RETURNING idVideo, idTag
)
SELECT idVideo FROM (
SELECT * FROM new_target
UNION
SELECT * FROM video_has_tag
WHERE idVideo=$1 AND idTag=(SELECT idTag FROM tag_data)
) s;
$insVideoTag$ LANGUAGE sql;
No need for if statements, a where clause is enough:
selecting the current value is not suitable as a return value (or to be entered into an other tables FK) : it could have been bumped after the first insert.
DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;
CREATE TABLE tag
( idtag SERIAL NOT NULL PRIMARY KEY
, tagname varchar
);
CREATE TABLE video_has_tag
( idvideo INTEGER NOT NULL
, idtag INTEGER NOT NULL REFERENCES tag (idtag)
);
CREATE OR REPLACE FUNCTION tmp.insertvideotag
( in_idvideo integer , in_tagname VARCHAR )
RETURNS bigint AS
$$
DECLARE var_id bigint DEFAULT NULL;
BEGIN
INSERT INTO tag (tagname )
SELECT in_tagname
WHERE NOT EXISTS (
SELECT * FROM tag
WHERE tagname = in_tagname
);
INSERT INTO video_has_tag (idvideo,idtag)
SELECT in_idvideo, tg.idtag
FROM tag tg
WHERE tg.tagname = in_tagname
RETURNING idtag
INTO var_id
;
RETURN var_id;
END;
$$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER;
SELECT insertvideotag(11, 'Eleven');
SELECT insertvideotag(12, 'Eleven');
SELECT insertvideotag(13, 'Thirteen');
SELECT tv.idvideo
,tv.idtag, tg.tagname
FROM video_has_tag tv
JOIN tag tg ON tg.idtag = tv.idtag
;
Result:
NOTICE: CREATE TABLE will create implicit sequence "tag_idtag_seq" for serial column "tag.idtag"
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "tag_pkey" for table "tag"
CREATE TABLE
CREATE TABLE
CREATE FUNCTION
insertvideotag
----------------
1
(1 row)
insertvideotag
----------------
2
(1 row)
idvideo | idtag | tagname
---------+-------+----------
11 | 1 | Eleven
12 | 1 | Eleven
13 | 2 | Thirteen
(2 rows)
CREATE OR REPLACE FUNCTION public."InsertVideoTag"
(
IN in_idvideo integer,
IN in_tagname VARCHAR(25)
)
RETURNS bigint AS
$$
DECLARE var_id bigint DEFAULT NULL;
begin
var_id := (select IDTag FROM Tag WHERE TagName = in_tagname);
IF var_id IS NULL
THEN
INSERT INTO tag ( TagName )
VALUES( in_tagname );
var_id := (SELECT CURRVAL(pg_get_serial_sequence('public.tag','idtag')));
END IF;
INSERT INTO video_has_tag
(
IDVideo,
IDTag
)
VALUES
(
in_idvideo,
var_id
);
return (SELECT CURRVAL(pg_get_serial_sequence('public.video','idvideo')));
end;
$$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER;
I've found it's much easier to work with lower case columns & table names, etc. with postgres, so I've made a few changes to your existing code :
CREATE OR REPLACE FUNCTION public."insertvideotag"
(
IN in_idvideo integer,
IN in_tagname VARCHAR(25)
)
RETURNS bigint AS
$$
DECLARE var_id bigint DEFAULT NULL;
BEGIN
--SELECT var_id := IDTag FROM Tag WHERE TagName = in_tagname;
SELECT idtag into var_id FROM tag WHERE tagname = in_tagname;
IF var_id IS NULL
THEN
INSERT INTO tag ( TagName )
VALUES( in_tagname );
--var_id := SELECT CURRVAL(pg_get_serial_sequence('public.tag','idtag'));
SELECT CURRVAL(pg_get_serial_sequence('public.tag','idtag')) into var_id;
END IF;
INSERT INTO video_has_tag
(
idvideo,
idtag
)
VALUES
(
in_idvideo,
var_id
);
SELECT CURRVAL(pg_get_serial_sequence('public.video','idvideo'));
END
$$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER;
The remaining potential issues are the sequence names, and perhaps what value you'd like to return.