Replace ID column in LTREE with different column Postgres - postgresql

I have a hierarchical structure in postgres which uses LTREE to calculate path based on a trigger. I am following the examples from here: https://coderwall.com/p/whf3-a/hierarchical-data-in-postgres
Table, trigger and data being inserted look like this:
CREATE TABLE section (
id INTEGER PRIMARY KEY,
asset_name TEXT,
parent_id INTEGER REFERENCES section,
parent_path LTREE
);
CREATE INDEX section_parent_path_idx ON section USING GIST (parent_path);
CREATE INDEX section_parent_id_idx ON section (parent_id);
CREATE OR REPLACE FUNCTION update_section_parent_path() RETURNS TRIGGER AS $$
DECLARE
path ltree;
BEGIN
IF NEW.parent_id IS NULL THEN
NEW.parent_path = '6'::ltree;
ELSEIF TG_OP = 'INSERT' OR OLD.parent_id IS NULL OR OLD.parent_id != NEW.parent_id THEN
SELECT parent_path || id::text FROM section WHERE id = NEW.parent_id INTO path;
IF path IS NULL THEN
RAISE EXCEPTION 'Invalid parent_id %', NEW.parent_id;
END IF;
NEW.parent_path = path;
END IF;
ltree_array = (STRING_TO_ARRAY(path::TEXT,'.'))[2:2147483647];
IF ltree_array IS NOT NULL THEN
FOREACH entry IN ARRAY(ltree_array)
LOOP
v_asset_name = (SELECT asset_name
FROM section AS s
WHERE s.id = entry);
s3path = s3path || v_asset_name || '/';
END LOOP;
END IF;
s3path = NEW.asset_store_id || '/' || s3path || NEW.asset_name;
NEW.s3_path = s3path;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER parent_path_tgr
BEFORE INSERT OR UPDATE ON section
FOR EACH ROW EXECUTE PROCEDURE update_section_parent_path();
INSERT INTO section (id, asset_name, parent_id ) VALUES (1, 'Main Folder', NULL);
INSERT INTO section (id, asset_name, parent_id ) VALUES (2, 'Rejected', 1);
INSERT INTO section (id, asset_name, parent_id ) VALUES (3, 'Records', NULL);
INSERT INTO section (id, asset_name, parent_id ) VALUES (4, 'Expired', 3);
INSERT INTO section (id, asset_name, parent_id ) VALUES (5, 'Selected', 3);
INSERT INTO section (id, asset_name, parent_id ) VALUES (6, 'Useless', 5);
The table output looks like this:
id|asset_name |parent_id|parent_path|
--|-----------|---------|-----------|
1|Main Folder| |6 |
2|Rejected | 1|6.1 |
3|Records | |6 |
4|Expired | 3|6.3 |
5|Selected | 3|6.3 |
6|Useless | 5|6.3.5 |
parent_path is all numerical because it looks like it is concatinating id, but I want to concatinate asset_name column.
I have tried replacing id with asset_name but that throws an syntax error. I can't seem to figure out how to replace id within the trigger.

Related

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

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|

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

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.