I'd like to create a Postgres trigger on a table to run when a row gets inserted or updated. The table has many columns, and I'd like the trigger to insert that row into another table. But in that other table, all those columns should be combined into one JSON object (JSONB in newer versions of Postgres).
original table
column1|column2|column3 |
-------|-------|--------|
A |B |C |
new table
combined_column |
---------------------------------------|
{ column1: A, column2: B, column3: C } |
So the table that the trigger is created on would have for example 3 columns, but the table that the trigger inserts into would have only 1 column (a JSON object combining all the columns for the inserted/updated row in the original table).
It would be more efficient to save rows in original form. No transformation needed, occupies less disk space, faster, cleaner.
Just create a log table with identical structure:
CREATE TABLE tbl_log AS TABLE tbl LIMIT 0;
Or use the LIKE keyword to specify more closely what to take from the original with INCLUDING clauses. Example:
CREATE TABLE tbl_log (LIKE tbl INCLUDING STORAGE);
Trigger function:
CREATE OR REPLACE FUNCTION trg_tbl_log()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
INSERT INTO tbl_log VALUES (NEW.*);
RETURN NEW;
END
$func$;
Trigger:
CREATE TRIGGER tbl_log
BEFORE INSERT OR UPDATE ON tbl
FOR EACH ROW EXECUTE PROCEDURE trg_tbl_log();
In Postgres 11 or later, rather use cleaner syntax:
...
FOR EACH ROW EXECUTE FUNCTION trg_tbl_log();
You can easily transform the row into a json value if you need to, with row_to_json(). Or simpler, just to_json(). It might be better to use to_jsonb() and save jsonb instead of json:
...
INSERT INTO generic_js_log (json_column) SELECT to_jsonb(NEW);
...
JSON functions in the manual.
Related
I am creating a trigger in SQL to sum up all the values in a column after a change is made. I am stuck and encountering an error when I try this:
`
CREATE OR REPLACE TRIGGER GET_NUM_ATHLETES
AFTER DELETE OR UPDATE OF NUM_ATHLETES OR INSERT ON DELEGATION
BEGIN
SELECT
SUM("A1"."NUM_") "SUM(NUM_)"
INTO x_1 FROM
"DBF19"."DELEGATION" "A1";
END;
`
My table looks like this:
ID
Num_
ABC
2
XYZ
4
I just used the Oracle SQL Developer GUI to create, but obviously doing something wrong.
You could use a view instead of maintaining the data in a table. That way the view would get the results "live" each time.
And also you wouldn't need to do the extra task of loading data into another table
CREATE VIEW NUM_ATHLETES
AS
SELECT SUM("A1"."NUM_") "SUM(NUM_)"
FROM "DBF19"."DELEGATION" "A1";
Do not create a table, use a VIEW (or if you were doing more complicated calculations a MATERIALIZED VIEW):
DROP TABLE num_athletes;
then:
CREATE VIEW num_athletes (id, num) AS
SELECT id, SUM(num_)
FROM DBF19.DELEGATION
GROUP BY id;
I found the similar question and solution for the SQL server. I want to replace all my null values with zero or empty strings. I can not use the update statement because my table has 255 columns and using the update for all columns will consume lots of time.
Can anyone suggest to me, how to update all the null values from all columns at once in PostgreSQL?
If you want to replace the data on the fly while selecting the rows you need:
SELECT COALESCE(maybe_null_column, 0)
If you want the change to be saved on the table you need to use an UPDATE. If you have a lot of rows you can use a tool like pg-batch
You can also create a new table and then swap the old one and the new one:
# Create new table with updated values
CREATE TABLE new_table AS
SELECT COALESCE(maybe_null_column, 0), COALESCE(maybe_null_column2, '')
FROM my_table;
# Swap table
ALTER TABLE my_table RENAME TO obsolete_table;
ALTER TABLE new_table RENAME TO my_table;
I have an ORDS enabled schema which accepts bulk JSON records and splits them one by one, and inserts them one by one into UEM table.
I've tried to create a trigger which fetches the last inserted row's id and use that value to insert into another table. The problem is, the trigger below only fetches and inserts the last inserted row's id, and it only does 1 insert.
To be more specific:
ORDS gets a bulk JSON payload which consists of 4 records.
POST handler starts a Procedure which splits these 4 records by line break, and immediately inserts these to CLOB columns of UEM table as 4 separate rows by using "connect by level". There is also the ID column which is automatically created and incremented.
In the parallel I also would like to get the ID of these rows and use it in another table insert. I've created the compound trigger below, but this trigger only retrieves the ID of the last record, and inserts only one row.
Why do you think it behaves like this? In the end, the procedure "inserted" 4 records.
CREATE OR REPLACE TRIGGER TEST_TRIGGER5
FOR INSERT ON UEM
COMPOUND TRIGGER
lastid NUMBER;
AFTER STATEMENT IS
BEGIN
SELECT MAX(ID) INTO lastid FROM UEM;
INSERT INTO SPRINT1 (tenantid, usersessionid, newuser, totalerrorcount, userid) VALUES ('deneme', 'testsessionid', 'yes', lastid, 'asdasfqwqwe');
END AFTER STATEMENT;
END TEST_TRIGGER5;
inserts these to CLOB columns of UEM table as 4 separate rows by using "connect by level".
You have 1 INSERT statement that is inserting 4 rows.
In the parallel I also would like to get the ID of these rows and use it in another table insert. I've created the compound trigger below, but this trigger only retrieves the ID of the last record, and inserts only one row.
Why do you think it behaves like this? In the end, the procedure "inserted" 4 records.
It may have inserted 4 ROWS but there was only 1 STATEMENT and you are using an AFTER STATEMENT trigger. If you want it to run for each row then you need to use a row-level trigger.
CREATE OR REPLACE TRIGGER TEST_TRIGGER5
AFTER INSERT ON UEM
FOR EACH ROW
BEGIN
INSERT INTO SPRINT1 (tenantid, usersessionid, newuser, totalerrorcount, userid)
VALUES ('deneme', 'testsessionid', 'yes', :NEW.id, 'asdasfqwqwe');
END TEST_TRIGGER5;
/
db<>fiddle here
Why? Because it is a statement level trigger. If you wanted it to fire for each row, you'd - obviously - use a row level trigger which has the
for each row
clause.
Is there a way to generically copy a row, in particular WITHOUT specifying all the columns.
In my situatoin I have a large table where I would like to copy all the columns except the ID and one other column. In fact data is copied from year to year at the start of the year. The table has 50+ columns so it would be more flexible and robust to change in schema if I did not have to specify all the columns.
This is closely related to the question : copy row while updating one field
In that question Kevin Cline's comment essentially asks my question, but no solution was actually provided for this more general case.
EDIT to provide more detail as requested, here is an example of what is needed:
-- setup test table
create table my_table(pk, v1,v2,v3,v4 ... v50) as
select 17 pk, 1 v1,2 v2,3 v3... 50 v50 from dual;
On the above table copy the row and set pk to 18 and v2 to 10.
An easy way to do this is an anonymous PL/SQL block and the usage of ROWTYPE:
-- setup test table
create table my_table(pk, value) as
select 17 pk, 'abc' value from dual;
declare
l_data my_table%rowtype;
begin
-- fetch the row we want to copy
select * into l_data from my_table tbl where tbl.pk = 17;
-- update all fields that need to change
l_data.pk := 18;
-- note the lack of parens around l_data in the next line
insert into my_table values l_data;
end;
I want a SQL procedure / function to solve the below mentioned problem:
I have 2 tables - Table A and table B.
Table A has 3 columns - name, number and flag.
Table B has 2 columns - name and number.
When a value of flag column changes in table A, a record should be inserted in table B with the same values of name and number from table A.
How can I achieve this?
You can achieve this by using triggers.
Triggers are procedures that are stored in the database and are implicitly run, or fired, when something happens.
You can write triggers that fire whenever and INSERT, UPDATE, or DELETE operation is performed on a particular table or view.
General Syntax:
CREATE TRIGGER WRITE_TRIGGER_NAME_HERE
BEFORE UPDATE ON TABLE_A
FOR EACH ROW
BEGIN
WRITE_INSERT_STATEMENT_HERE_FOR_TABLE_B
END;