Trying to automatically insert into a table using triggers in POSTGRESQL - postgresql

I am trying to make a trigger and function that inserts into the table purchases the values which have been inserted into the table customers.
Columns of table customers
1-customer_id serial PK references customer_id in purchases
2-c_name VARCHAR
3-amount DOUBLE PRECISION
Columns of table purchases
1- customer_id serial PK 2- amount DOUBLE PRECISION
The code for the trigger and the function:
CREATE OR REPLACE FUNCTION auto_insert_purchases()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$body$
BEGIN
insert into purchases(customer_id,purchase) values
(NEW.customer_id,NEW.purchase);
END
$body$
CREATE TRIGGER tr_auto_insert_purchases
AFTER INSERT ON customers
EXECUTE PROCEDURE auto_insert_purchases()
As you can see its supposed to take the new row data and insert it into the table but after doing and insertion to customers like this:
insert into customers values(2,'Stewie Griffin',4.99);
I get this error message:
ERROR: null value in column "customer_id" of relation "purchases" violates not-null
constraint
DETAIL: Failing row contains (null, null).
CONTEXT: SQL statement "insert into purchases(customer_id,purchase) values
(NEW.customer_id,NEW.purchase)"
auto_insert_purchases() PL/pgSQL fonksiyonu, 3. satır, SQL ifadesi içinde
SQL state: 23502
Why does the failing row contain null? Am I using the NEW keyword incorrectly?

CREATE TABLE customers (
customer_id int4 NULL,
c_name varchar NULL,
amount float8 NULL
);
CREATE TABLE purchases (
customer_id int4 NULL,
amount float8 NULL
);
CREATE OR REPLACE FUNCTION auto_insert_purchases()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
insert into purchases(customer_id, amount) values
(NEW.customer_id, NEW.amount);
return new;
END;
$function$
;
create trigger tr_auto_insert_purchases
after insert ON customers
for each row
execute procedure auto_insert_purchases();
insert into customers(customer_id, c_name, amount) values (2,'Stewie Griffin', 4.99);
select * from purchases;
-- Result:
customer_id|amount|
-----------+------+
2| 4.99|
May be you just forgot to write for each row statement after CREATE TRIGGER tr_auto_insert_purchases AFTER INSERT ON customers

Related

PostgreSQL -- JOIN UNNEST output with CTE INSERT ID -- INSERT many to many

In a PostgreSQL function, is it possible to join the result of UNNEST, which is an integer array from function input, with an ID returned from a CTE INSERT?
I have PostgreSQL tables like:
CREATE TABLE public.message (
id SERIAL PRIMARY KEY,
content TEXT
);
CREATE TABLE public.message_tag (
id SERIAL PRIMARY KEY,
message_id INTEGER NOT NULL CONSTRAINT message_tag_message_id_fkey REFERENCES public.message(id) ON DELETE CASCADE,
tag_id INTEGER NOT NULL CONSTRAINT message_tag_tag_id_fkey REFERENCES public.tag(id) ON DELETE CASCADE
);
I want to create a PostgreSQL function which takes input of content and an array of tag_id. This is for graphile. I want to do it all in one function, so I get a mutation.
Here's what I got so far. I don't know how to join an UNNEST across an id returned from a CTE.
CREATE FUNCTION public.create_message(content text, tags Int[])
RETURNS public.message
AS $$
-- insert to get primary key of message, for many to many message_id
WITH moved_rows AS (
INSERT INTO public.message (content)
RETURNING *;
)
-- many to many relation
INSERT INTO public.message_tag
SELECT moved_rows.id as message_id, tagInput.tag_id FROM moved_rows, UNNEST(tags) as tagInput;
RETURNING *
$$ LANGUAGE sql VOLATILE STRICT;
You're not that far from your goal:
the semicolon placement in the CTE is wrong
the first INSERT statement lacks a SELECT or VALUES clause to specify what should be inserted
the INSERT into tag_message should specify the columns in which to insert (especially if you have that unnecessary serial id)
you specified a relation alias for the UNNEST call already, but none for the column tag_id
your function was RETURNING a set of message_tag rows but was specified to return a single message row
To fix these:
CREATE FUNCTION public.create_message(content text, tags Int[])
RETURNS public.message
AS $$
-- insert to get primary key of message, for many to many message_id
WITH moved_rows AS (
INSERT INTO public.message (content)
VALUES ($1)
RETURNING *
),
-- many to many relation
_ AS (
INSERT INTO public.message_tag (message_id, tag_id)
SELECT moved_rows.id, tagInput.tag_id
FROM moved_rows, UNNEST($2) as tagInput(tag_id)
)
TABLE moved_rows;
$$ LANGUAGE sql VOLATILE STRICT;
(Online demo)

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?

jsonb_populate_record / jsonb_populate_recordset should return a table

currently I try to make a history table based on postgresql jsonb, currently as a example I have two table's:
CREATE TABLE data (id BIGSERIAL PRIMARY KEY, price NUMERIC(10,4) NOT NULL, article TEXT NOT NULL, quantity BIGINT NOT NULL, lose BIGINT NOT NULL, username TEXT NOT NULL);
CREATE TABLE data_history (id BIGSERIAL PRIMARY KEY, data JSONB NOT NULL, username TEXT NOT NULL);
The history table act's a simple history (the username there could be avoided).
I populate the data of the history with a trigger:
CREATE OR REPLACE FUNCTION insert_history() RETURNS TRIGGER AS $$
BEGIN
INSERT INTO data_history (data, username) VALUES (row_to_json(NEW.*), NEW.username);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Now I try to populate the history back to the data table:
SELECT jsonb_populate_record(NULL::data, data) FROM data_history;
However the result will now be a tuple and not a table:
jsonb_populate_record
-------------------------------------
(1,45.4500,0A45477,100,1,c.schmitt)
(2,5.4500,0A45477,100,1,c.schmitt)
(2 rows)
Is there any way to get the data back as the table data back? I know there is jsonb_populate_recordset, too, however it doesn't accept a query?!
jsonb_populate_record() returns a row-type (or record-type), so if you use it in the SELECT cluase, you'll get a single column, which is a row-type.
To avoid this, use it in the FROM clause instead (with an implicit LATERAL JOIN):
SELECT r.*
FROM data_history,
jsonb_populate_record(NULL::data, data) r
Technically, the statement below could work too
-- DO NOT use, just for illustration
SELECT jsonb_populate_record(NULL::data, data).*
FROM data_history
but it will call jsonb_populate_record() for each column in data (as a result of an engine limitation).

Functions and Triggers in PostgreSql

As I am new to DBs, I am learning PostgreSql and for a sample, I am trying to small scenario on Mobile Recharge Database System. The below is the query I have. I want to know what is the problem with the function I have written which should only return balance amount of an account number which is nothing but the customer id.
And, I also want to add the value in the wallet table when we add some value (that's topup kind of).
Please help me in this. Thanks.
The below is the complete query:
CREATE DATABASE "RECHARGESYS"
WITH OWNER = postgres
ENCODING = 'UTF8'
TABLESPACE = pg_default
LC_COLLATE = 'English_United States.1252'
LC_CTYPE = 'English_United States.1252'
CONNECTION LIMIT = -1;
--SERVICEPROVIDER TABLE:
DROP TABLE SERVICE_PROVIDERS;
CREATE TABLE SERVICE_PROVIDERS
(
SPID VARCHAR(5) PRIMARY KEY CHECK(SPID LIKE 'S%'),
SPNAME VARCHAR(50)
);
--CUSTOMER TABLE:
DROP TABLE CUSTOMER;
CREATE TABLE CUSTOMER
(
CID INT PRIMARY KEY,
CNAME VARCHAR(50)
);
--RECHARGE TABLE:
DROP TABLE RECHARGE;
CREATE TABLE RECHARGE
(
RID INT PRIMARY KEY,
CID INT REFERENCES CUSTOMER(CID),
SPID VARCHAR(5) REFERENCES SERVICE_PROVIDERS(SPID) CHECK(SPID LIKE 'S%'),
RENUMBER BIGINT,
AMOUNT INT
);
--TRANSACTION TABLE:
DROP TABLE TRANSACTION;
CREATE TABLE TRANSACTION
(
TID INT PRIMARY KEY,
SPID VARCHAR(5) REFERENCES SERVICE_PROVIDERS(SPID) CHECK(SPID LIKE('S%')),
RID INT REFERENCES RECHARGE(RID)
);
--WALLET TABLE:
DROP TABLE WALLET;
CREATE TABLE WALLET
(
WID INT PRIMARY KEY,
CID INT REFERENCES CUSTOMER(CID),
WAMOUNT INT
);
INSERT INTO SERVICE_PROVIDERS VALUES ('S1001', 'AIRTEL');
INSERT INTO SERVICE_PROVIDERS VALUES ('S1002', 'AIRCEL');
INSERT INTO SERVICE_PROVIDERS VALUES ('S1003', 'TATA DOCOMO');
INSERT INTO SERVICE_PROVIDERS VALUES ('S1004', 'IDEA');
INSERT INTO SERVICE_PROVIDERS VALUES ('S1005', 'VODAFONE');
SELECT * FROM SERVICE_PROVIDERS;
INSERT INTO CUSTOMER VALUES('20001','AHMED');
INSERT INTO CUSTOMER VALUES('20002','ASIF');
INSERT INTO CUSTOMER VALUES('20003','AHSRAF');
INSERT INTO CUSTOMER VALUES('20004','MAHESH');
INSERT INTO CUSTOMER VALUES('20005','ARUN');
SELECT * FROM CUSTOMER;
INSERT INTO WALLET VALUES('30001','20001','1000');
INSERT INTO WALLET VALUES('30002','20002','1000');
INSERT INTO WALLET VALUES('30003','20003','1000');
INSERT INTO WALLET VALUES('30004','20004','1000');
INSERT INTO WALLET VALUES('30005','20005','1000');
SELECT * FROM WALLET;
--IN THIS FUNCTION I WANT TO CHECK THE BALANCE ONCE I GIVE THE ACCOUNT NUMBER / CUSTOMER ID (ID):
CREATE OR REPLACE FUNCTION BALANCE(ACCNO INT)
RETURNS INT AS $BAL$
BEGIN
SELECT WAMOUNT INTO BAL FROM WALLET WHERE CID=ACCNO;
RETURN(BAL);
END;
$BAL$ LANGUAGE plpgsql;
SELECT BALANCE('20001') FROM WALLET;
--ALSO I WANT TRIGGER THAT CAN ADD AMOUNT TO THE WALLET.
The problem with your function is that you try to use the variable BAL without declaring it:
CREATE OR REPLACE FUNCTION BALANCE(ACCNO INT)
RETURNS INT AS $BAL$
declare BAL integer; --<--- Add this line
.......
But for a simple select query you don't need a plpgsql function. A sql function will do the job:
CREATE OR REPLACE FUNCTION balance(ACCNO INT)
RETURNS INT AS $$
SELECT wamount FROM wallet WHERE cid=ACCNO;
$$ LANGUAGE sql;

Need foreign key as array

CREATE TABLE test ( id int PRIMARY KEY , name );
CREATE TABLE test1 ( id integer[] REFERENCES test , rollid int );
ERROR: foreign key constraint "test3_id_fkey" cannot be implemented
DETAIL: Key columns "id" and "id" are of incompatible types: integer[] and integer.
after that I try to another way also
CREATE TABLE test1 ( id integer[] , rollid int);
ALTER TABLE test1 ADD CONSTRAINT foreignkeyarray FOREIGN KEY (id) REFERENCES test;
ERROR: foreign key constraint "fkarray" cannot be implemented
DETAIL: Key columns "id" and "id" are of incompatible types: integer[] and integer.
so I try create a foreign key array means it say error. please tell me anyone?
postgresql version is 9.1.
What you're trying to do simply can't be done. At all. No ifs, no buts.
Create a new table, test1_test, containing two fields, test1_id, test_id. Put the foreign keys as needed on that one, and make test1's id an integer.
Using arrays with foreign element keys is usually a sign of incorrect design. You need to do separate table with one to many relationship.
But technically it is possible. Example of checking array values without triggers. One reusable function with paramethers and dynamic sql. Tested on PostgreSQL 10.5
create schema if not exists test;
CREATE OR REPLACE FUNCTION test.check_foreign_key_array(data anyarray, ref_schema text, ref_table text, ref_column text)
RETURNS BOOL
RETURNS NULL ON NULL INPUT
LANGUAGE plpgsql
AS
$body$
DECLARE
fake_id text;
sql text default format($$
select id::text
from unnest($1) as x(id)
where id is not null
and id not in (select %3$I
from %1$I.%2$I
where %3$I = any($1))
limit 1;
$$, ref_schema, ref_table, ref_column);
BEGIN
EXECUTE sql USING data INTO fake_id;
IF (fake_id IS NOT NULL) THEN
RAISE NOTICE 'Array element value % does not exist in column %.%.%', fake_id, ref_schema, ref_table, ref_column;
RETURN false;
END IF;
RETURN true;
END
$body$;
drop table if exists test.t1, test.t2;
create table test.t1 (
id integer generated by default as identity primary key
);
create table test.t2 (
id integer generated by default as identity primary key,
t1_ids integer[] not null check (test.check_foreign_key_array(t1_ids, 'test', 't1', 'id'))
);
insert into test.t1 (id) values (default), (default), (default); --ok
insert into test.t2 (id, t1_ids) values (default, array[1,2,3]); --ok
insert into test.t2 (id, t1_ids) values (default, array[1,2,3,555]); --error
If you are able to put there just values from test.id, then you can try this:
CREATE OR REPLACE FUNCTION test_trigger() RETURNS trigger
LANGUAGE plpgsql AS $BODY$
DECLARE
val integer;
BEGIN
SELECT id INTO val
FROM (
SELECT UNNEST(id) AS id
FROM test1
) AS q
WHERE id = OLD.id;
IF val IS NULL THEN RETURN OLD;
ELSE
RAISE 'Integrity Constraint Violation: ID "%" in Test1', val USING ERRCODE = '23000';
RETURN NULL;
END IF;
END; $BODY$;
-- DROP TRIGGER test_delete_trigger ON test;
CREATE TRIGGER test_delete_trigger BEFORE DELETE OR UPDATE OF id ON test
FOR EACH ROW EXECUTE PROCEDURE test_trigger();