So I´m fairly new to Postgresql and started working with it by testing out some stuff with pgadmin4 on Postgres9.6.
The problem:
I have a table: test(id, text)
In this table I have 10 rows of data.
Now I want to import a CSV which has 12 rows to update the test table. Some text changed for the first 10 rows AND I want to insert the 2 additional rows from the CSV.
I know that you can truncate all the data from a table and just import everything again from the CSV, but that´s not a nice way to do this. I want to Update my existing data & Insert the new data with one query.
I already found a function which should solve this by using a temporary table. This updates the existing rows correct, but the 2 additional rows do not get inserted
CREATE OR REPLACE FUNCTION upsert_test(integer,varchar) RETURNS VOID AS $$
DECLARE
BEGIN
UPDATE test
SET id = tmp_table.id,
text = tmp_table.text
FROM tmp_table
WHERE test.id = tmp_table.id;
IF NOT FOUND THEN
INSERT INTO test(id,text) values
(tmp_table.id,tmp_table.text);
END IF;
END;
$$ Language 'plpgsql';
DO $$ BEGIN
PERFORM upsert_test(id,text) FROM test;
END $$;
So what do I need to change to also get the insert to work?
Assuming you have a primary or unique constraint on the id column you can do this with a single statement, no function required:
insert into test (id, text)
select id, text
from tmp_table
on conflict (id)
do update set text = excluded.text;
Related
I have issue with creating PostgreSQL triggers
I have rigger executing amendments to table on FOR EACH ROW basis, but i need to process all columns in these rows except one.
I tried to find solution how prevent passing one of columns to procedure, but as i understand it's done automatically and can not be interfered.
OLD and NEW variables with old and new rows passed to procedure and i was searching how remove columns from OLD/NEW for further processing and failed so far
I have simple table1 with columns:
name
number
description
I have following trigger
CREATE TRIGGER table1_trigger
AFTER INSERT OR UPDATE OR DELETE ON table1
FOR EACH ROW EXECUTE PROCEDURE trigger_proc();
and following procedure
CREATE OR REPLACE FUNCTION trigger_proc() RETURNS TRIGGER AS
$$
DECLARE
old_v jsonb;
new_v jsonb;
BEGIN
old_v = json_strip_nulls(row_to_json(OLD));
new_v = json_strip_nulls(row_to_json(NEW));
END;
$$ LANGUAGE plpgsql;
After executing for example:
insert into table1 (name, number, description)
values ('name', 1, 'useless text to vanish')
I need column "description" either to be excluded
at stage passing to procedure "FOR EACH ROW EXECUTE PROCEDURE trigger_proc()"
or to be removed from OLD/NEW in procedure
or OLD/NEW data to be copied to some new variables with RECORD type, but of course without "description" column
or any other ways where old_v and new_v will not contain data from this column!
Thanks in advance for any help!
I am using Microsoft Access as a front-end to my PostgreSQL database. My workflow is pretty simple:
Create a linked table using the ODBC driver
Build a form using form wizard for data entry
Inserting data works really well if i submit the data directly on the table or using the form. However, i can update the data that was submitted directly to the table, but i cannot update the data submitted through the form as i get a write.conflict error.
I checked many previous answers and one of the issues was to do with the timestamp precision. This helped with updating the data submitted directly on the table as it didn't work before.
Now i just need to resolve updating data that was submitted using the form. I tried using Me.Dirty as follows:
Private Sub Form_Dirty(Cancel As Integer)
Me.Dirty = False
End Sub
That didn't work for me unfortunately. It really does look like something to do with the form as entering data using the table works perfectly. Is it how the form saves the data? How can i get it off editing mode? I really don't know and i tried various things.
I would really appreciate a hand on this as i have been on it for days and i can't resolve it.
Thank you.
Here is a simplified version of the code i used to create my table. I just have more VARCHAR and numeric columns in the table i am using.
I also created a logs table that would update if any changes are made to the main table. This logs table is populate via a trigger as shown in the code.
CREATE TABLE table_1 (
id INT PRIMARY KEY DEFAULT to_char(now(), 'YYMMDDHH24MI') :: INT,
column_1 VARCHAR(50),
column_2 VARCHAR(100),
column_3 BOOLEAN,
last_updated timestamp(0));
CREATE TABLE logs_table
(like table_1 EXCLUDING CONSTRAINTS,
operation char(10) not null,
date_operated timestamp(0) default current_timestamp
);
create function logs_function()
returns trigger as $$
BEGIN
insert into logs_table (id, column_1, column_2, column_3, last_updated, operation)
values (old.id, old.column_1, old.column_2, old.column_3, old.last_updated, TG_OP);
IF TG_OP = 'UPDATE'
THEN
new.last_updated := current_timestamp;
RETURN NEW;
ELSIF TG_OP = 'DELETE'
THEN
RETURN OLD;
END IF;
end;
$$ LANGUAGE plpgsql;
CREATE TRIGGER logs_trigger
BEFORE UPDATE OR DELETE ON table_1
FOR EACH ROW EXECUTE PROCEDURE logs_function();
I am trying to write a trigger which gets data from the table attribute in which multiple rows are inserted corresponding to one actionId at one time and group all that data into the one object:
Table Schema
actionId
key
value
I am firing trigger on rows insertion,SO how can I handle this multiple row insertion and how can I collect all the data.
CREATE TRIGGER attribute_changes
AFTER INSERT
ON attributes
FOR EACH ROW
EXECUTE PROCEDURE log_attribute_changes();
and the function,
CREATE OR REPLACE FUNCTION wflowr222.log_task_extendedattribute_changes()
RETURNS trigger AS
$BODY$
DECLARE
_message json;
_extendedAttributes jsonb;
BEGIN
SELECT json_agg(tmp)
INTO _extendedAttributes
FROM (
-- your subquery goes here, for example:
SELECT attributes.key, attributes.value
FROM attributes
WHERE attributes.actionId=NEW.actionId
) tmp;
_message :=json_build_object('actionId',NEW.actionId,'extendedAttributes',_extendedAttributes);
INSERT INTO wflowr222.irisevents(message)
VALUES(_message );
RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
and data format is,
actionId key value
2 flag true
2 image http:test.com/image
2 status New
I tried to do it via Insert trigger, but it is firing on each row inserted.
If anyone has any idea about this?
I expect that the problem is that you're using a FOR EACH ROW trigger; what you likely want is a FOR EACH STATEMENT trigger - ie. which only fires once for your multi-line INSERT statement. See the description at https://www.postgresql.org/docs/current/sql-createtrigger.html for a more through explanation.
AFAICT, you will also need to add REFERENCING NEW TABLE AS NEW in this mode to make the NEW reference available to the trigger function. So your CREATE TRIGGER syntax would need to be:
CREATE TRIGGER attribute_changes
AFTER INSERT
ON attributes
REFERENCING NEW TABLE AS NEW
FOR EACH STATEMENT
EXECUTE PROCEDURE log_attribute_changes();
I've read elsewhere that the required REFERENCING NEW TABLE ... syntax is only supported in PostgreSQL 10 and later.
Considering the version of postgres you have, and therefore keeping in mind that you can't use a trigger defined FOR EACH STATEMENT for your purpose, the only alternative I see is
using a trigger after insert in order to collect some information about changes in a utility table
using a unix cron that execute a pl/sql that do the job on data set
For example:
Your utility table
CREATE TABLE utility (
actionid integer,
createtime timestamp
);
You can define a trigger FOR EACH ROW with a body that do something like this
INSERT INTO utilty values(NEW.actionid, curent_timestamp);
And, finally, have a crontab UNIX that execute a file or a procedure that to something like this:
SELECT a.* FROM utility u JOIN yourtable a ON a.actionid = u.actionid WHERE u.createtime < current_timestamp;
// do something here with records selected above
TRUNCATE table utility;
If you had postgres 9.5 you could have used pg_cron instead of unix cron...
I am new to PostgreSQL and found a trigger which serves my purpose completely except for one little thing. The trigger is quite generic and runs across different tables and logs different field changes. I found here.
What I now need to do is test for a specific field which changes as the tables change on which the trigger fires. I thought of using substr as all the column will have the same name format e.g. XXX_cust_no but the XXX can change to 2 or 4 characters. I need to log the value in theXXX_cust_no field with every record that is written to the history_ / audit table. Using a bunch of IF / ELSE statements to accomplish this is not something I would like to do.
The trigger as it now works logs the table_name, column_name, old_value, new_value. I however need to log the XXX_cust_no of the record that was changed as well.
Basically you need dynamic SQL for dynamic column names. format helps to format the DML command. Pass values from NEW and OLD with the USING clause.
Given these tables:
CREATE TABLE tbl (
t_id serial PRIMARY KEY
,abc_cust_no text
);
CREATE TABLE log (
id int
,table_name text
,column_name text
,old_value text
,new_value text
);
It could work like this:
CREATE OR REPLACE FUNCTION trg_demo()
RETURNS TRIGGER AS
$func$
BEGIN
EXECUTE format('
INSERT INTO log(id, table_name, column_name, old_value, new_value)
SELECT ($2).t_id
, $3
, $4
,($1).%1$I
,($2).%1$I', TG_ARGV[0])
USING OLD, NEW, TG_RELNAME, TG_ARGV[0];
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
CREATE TRIGGER demo
BEFORE UPDATE ON tbl
FOR EACH ROW EXECUTE PROCEDURE trg_demo('abc_cust_no'); -- col name here.
SQL Fiddle.
Related answer on dba.SE:
How to access NEW or OLD field given only the field's name?
List of special variables visible in plpgsql trigger functions in the manual.
TRIGEER-->To get a column value from one table to other table when i insert values?
I am having two tables(customer_details and loan_balance).
What i need is, I must get the column (custid)of customer_details table to the loan_balance table when i insert the data into the loan_balance table.
This is the full set up of my query : SQL FIDDLE
So i need a trigger to be raised and the data should be updated automatically without dynamic insertion of custid.
Postgres has an unconventional way of creating triggers:
create a function that returns type trigger and return the NEW row record
create a trigger that executes the function
Here's the code you need:
CREATE FUNCTION synch_custid_proc()
RETURNS trigger AS $$
BEGIN
NEW.custid = (
select max(custid)
from customer_details
where creditid = NEW.creditid
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql
CREATE TRIGGER synch_custid_trig
BEFORE INSERT ON loan_amount
FOR EACH ROW
EXECUTE PROCEDURE synch_custid_proc();
I chosen to select max(custid) rather than simply custid when finding the value in case there are multiple rows that match. You might have to adjust this logic to suit your data.
See a live demo on SQLFiddle