PostgreSQL: UPDATE implies move across partitions - postgresql

(Note: updated with adopted answer below.)
For a PostgreSQL 8.1 (or later) partitioned table, how does one define an UPDATE trigger and procedure to "move" a record from one partition to the other, if the UPDATE implies a change to the constrained field that defines the partition segregation?
For example, I've a table records partitioned into active and inactive records like so:
create table RECORDS (RECORD varchar(64) not null, ACTIVE boolean default true);
create table ACTIVE_RECORDS ( check (ACTIVE) ) inherits RECORDS;
create table INACTIVE_RECORDS ( check (not ACTIVE) ) inherits RECORDS;
The INSERT trigger and function work well: new active records get put in one table, and new inactive records in another. I would like UPDATEs to the ACTIVE field to "move" a record from one one descendant table to the other, but am encountering an error which suggests that this may not be possible.
Trigger specification and error message:
pg=> CREATE OR REPLACE FUNCTION record_update()
RETURNS TRIGGER AS $$
BEGIN
IF (NEW.active = OLD.active) THEN
RETURN NEW;
ELSIF (NEW.active) THEN
INSERT INTO active_records VALUES (NEW.*);
DELETE FROM inactive_records WHERE record = NEW.record;
ELSE
INSERT INTO inactive_records VALUES (NEW.*);
DELETE FROM active_records WHERE record = NEW.record;
END IF;
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
pg=> CREATE TRIGGER record_update_trigger
BEFORE UPDATE ON records
FOR EACH ROW EXECUTE PROCEDURE record_update();
pg=> select * from RECORDS;
record | active
--------+--------
foo | t -- 'foo' record actually in table ACTIVE_RECORDS
bar | f -- 'bar' record actually in table INACTIVE_RECORDS
(2 rows)
pg=> update RECORDS set ACTIVE = false where RECORD = 'foo';
ERROR: new row for relation "active_records" violates check constraint "active_records_active_check"
Playing with the trigger procedure (returning NULL and so forth) suggests to me that the constraint is checked, and the error raised, before my trigger is invoked, meaning that my current approach won't work. Can this be gotten to work?
ADDITIONAL ANSWER
pg's [list partitioning][2] appears to be the easiest way to accomplish this:
-- untested!
create table RECORDS (..., ACTIVE boolean...)
partition by list(ACTIVE) (
partition ACTIVE_RECORDS values (true),
partition INACTIVE_RECORDS values (false)
)
UPDATE/ANSWER
Below is the UPDATE trigger procedure I ended up using, the same procedure assigned to each of the partitions. Credit is entirely to Bell, whose answer gave me the key insight to trigger on the partitions:
CREATE OR REPLACE FUNCTION record_update()
RETURNS TRIGGER AS $$
BEGIN
IF ( (TG_TABLE_NAME = 'active_records' AND NOT NEW.active)
OR
(TG_TABLE_NAME = 'inactive_records' AND NEW.active) ) THEN
DELETE FROM records WHERE record = NEW.record;
INSERT INTO records VALUES (NEW.*);
RETURN NULL;
END IF;
RETURN NEW;
END;
$$
LANGUAGE plpgsql;

It can be made to work, the trigger that does the move just needs to be defined for each partition, not the whole table. So start as you did for table definitions and the INSERT trigger
CREATE TABLE records (
record varchar(64) NOT NULL,
active boolean default TRUE
);
CREATE TABLE active_records (CHECK (active)) INHERITS (records);
CREATE TABLE inactive_records (CHECK (NOT active)) INHERITS (records);
CREATE OR REPLACE FUNCTION record_insert()
RETURNS TRIGGER AS $$
BEGIN
IF (TRUE = NEW.active) THEN
INSERT INTO active_records VALUES (NEW.*);
ELSE
INSERT INTO inactive_records VALUES (NEW.*);
END IF;
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
CREATE TRIGGER record_insert_trigger
BEFORE INSERT ON records
FOR EACH ROW EXECUTE PROCEDURE record_insert();
... let's have some test data ...
INSERT INTO records VALUES ('FirstLittlePiggy', TRUE);
INSERT INTO records VALUES ('SecondLittlePiggy', FALSE);
INSERT INTO records VALUES ('ThirdLittlePiggy', TRUE);
INSERT INTO records VALUES ('FourthLittlePiggy', FALSE);
INSERT INTO records VALUES ('FifthLittlePiggy', TRUE);
Now the triggers on the partitions. The if NEW.active = OLD.active check is implicit in checking the value of active since we know what's allowed to be in the table in the first place.
CREATE OR REPLACE FUNCTION active_partition_constraint()
RETURNS TRIGGER AS $$
BEGIN
IF NOT (NEW.active) THEN
INSERT INTO inactive_records VALUES (NEW.*);
DELETE FROM active_records WHERE record = NEW.record;
RETURN NULL;
ELSE
RETURN NEW;
END IF;
END;
$$
LANGUAGE plpgsql;
CREATE TRIGGER active_constraint_trigger
BEFORE UPDATE ON active_records
FOR EACH ROW EXECUTE PROCEDURE active_partition_constraint();
CREATE OR REPLACE FUNCTION inactive_partition_constraint()
RETURNS TRIGGER AS $$
BEGIN
IF (NEW.active) THEN
INSERT INTO active_records VALUES (NEW.*);
DELETE FROM inactive_records WHERE record = NEW.record;
RETURN NULL;
ELSE
RETURN NEW;
END IF;
END;
$$
LANGUAGE plpgsql;
CREATE TRIGGER inactive_constraint_trigger
BEFORE UPDATE ON inactive_records
FOR EACH ROW EXECUTE PROCEDURE inactive_partition_constraint();
... and test the results ...
scratch=> SELECT * FROM active_records;
record | active
------------------+--------
FirstLittlePiggy | t
ThirdLittlePiggy | t
FifthLittlePiggy | t
(3 rows)
scratch=> UPDATE records SET active = FALSE WHERE record = 'ThirdLittlePiggy';
UPDATE 0
scratch=> SELECT * FROM active_records;
record | active
------------------+--------
FirstLittlePiggy | t
FifthLittlePiggy | t
(2 rows)
scratch=> SELECT * FROM inactive_records;
record | active
-------------------+--------
SecondLittlePiggy | f
FourthLittlePiggy | f
ThirdLittlePiggy | f
(3 rows)

Beware that you can partition by list and let the database do all the hard work to move rows among partitions.
(untested for 8.4 but most probably working, as for pilcrow comment).
In the following example, a table is created and partitioned by list, using one of the columns in the primary key.
create table t (
-- natural primary key
doc_type varchar not null default 'PRODUCT',
doc_id int not null generated always as identity,
-- content columns
title varchar not null,
-- primary key
primary key (doc_type, doc_id)
)
partition by list(doc_type);
-- partitions of t
create table t_product partition of t for values in ('PRODUCT');
create table t_default partition of t default;
Then we insert some data that should end in t_product or t_default, depending on the value of doc_type.
insert into t (doc_type, title) values
('PRODUCT', 'My first product'), -- 1
('ARTICLE', 'My first article'), -- 2
('TOPIC', 'My first topic'), -- 3
('PRODUCT', 'My second product'), -- 4
('PRODUCT', 'My third product'), -- 5
('ARTICLE', 'My second article'), -- 6
('TOPIC', 'My second topic'), -- 7
('PRODUCT', 'My fourth product'); -- 8
We check rows are automatically moved to the right table
select * from t_product;
doc_type|doc_id|title |
--------+------+-----------------+
PRODUCT | 1|My first product |
PRODUCT | 4|My second product|
PRODUCT | 5|My third product |
PRODUCT | 8|My fourth product|
Now, let us convert a PRODUCT into an ARTICLE to see what happens.
update t
set doc_type = 'ARTICLE'
where doc_type = 'PRODUCT'
and doc_id = 1;
It can be seen the row is not in the t_product partition anymore
select * from t_product;
doc_type|doc_id|title |
--------+------+-----------------+
PRODUCT | 4|My second product|
PRODUCT | 5|My third product |
PRODUCT | 8|My fourth product|
but in the t_default partition.
doc_type|doc_id|title |
--------+------+-----------------+
ARTICLE | 2|My first article |
TOPIC | 3|My first topic |
ARTICLE | 6|My second article|
TOPIC | 7|My second topic |
ARTICLE | 1|My first product |

Related

Is it possible capture all records into table A from table B on or after truncating table B

I am trying to write a trigger with reference to Postgres DOC. But its not even allowing to create a trigger base on truncate, tried different approaches but didn't work.
CREATE TRIGGER delete_after_test
AFTER truncate
ON tableA
FOR EACH ROW
EXECUTE PROCEDURE delete_after_test3();
Function:
CREATE OR REPLACE FUNCTION econnect.delete_after_test3()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare
query text;
begin
insert into econnect.delete_after_test_2 (
"name",
age1,
log_time
)
values
(
old."name",
old.age1,
CURRENT_TIMESTAMP
)
;
return old;
END;
$function$
;
Reference: https://www.postgresql.org/docs/current/sql-createtrigger.html
"TRUNCATE will not fire any ON DELETE triggers that might exist for the tables. But it will fire ON TRUNCATE triggers. If ON TRUNCATE triggers are defined for any of the tables, then all BEFORE TRUNCATE triggers are fired before any truncation happens, and all AFTER TRUNCATE triggers are fired after the last truncation is performed and any sequences are reset. The triggers will fire in the order that the tables are to be processed (first those listed in the command, and then any that were added due to cascading)"
A solution using ON DELETE:
create table delete_test(id integer, fld1 varchar, fld2 boolean);
create table delete_test_save(id integer, fld1 varchar, fld2 boolean);
insert into delete_test values (1, 'test', 't'), (2, 'dog', 'f'), (3, 'cat', 't')
CREATE OR REPLACE FUNCTION public.delete_save()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
INSERT INTO delete_test_save SELECT * FROM del_recs;
RETURN OLD;
END;
$function$
CREATE TRIGGER trg_del_save
AFTER DELETE ON delete_test referencing OLD TABLE AS del_recs FOR EACH statement
EXECUTE FUNCTION delete_save ();
delete from delete_test;
DELETE 3
select * from delete_test;
id | fld1 | fld2
----+------+------
(0 rows)
select * from delete_test_save;
id | fld1 | fld2
----+------+------
1 | test | t
2 | dog | f
3 | cat | t
The example uses a transition relation (referencing OLD TABLE AS del_recs) to collect all the deleted records for use in the function. Then it is possible to do the INSERT INTO delete_test_save SELECT * FROM del_recs; to transfer the records to the other table. No, they will not work with a TRUNCATE trigger.
Transition relations are explained here Create Trigger:
The REFERENCING option enables collection of transition relations, which are row sets that include all of the rows inserted, deleted, or modified by the current SQL statement. This feature lets the trigger see a global view of what the statement did, not just one row at a time. This option is only allowed for an AFTER trigger that is not a constraint trigger; also, if the trigger is an UPDATE trigger, it must not specify a column_name list. OLD TABLE may only be specified once, and only for a trigger that can fire on UPDATE or DELETE; it creates a transition relation containing the before-images of all rows updated or deleted by the statement. Similarly, NEW TABLE may only be specified once, and only for a trigger that can fire on UPDATE or INSERT; it creates a transition relation containing the after-images of all rows updated or inserted by the statement.

user creation dumping using trigger and fucntions

As PostgreSQL doesn't dump object creation date so I want to manually dump user creation date by using trigger and functions. I have created trigger and functions but it's not working.
CREATE TABLE user_audits (
usesysid INT GENERATED ALWAYS AS IDENTITY,
usename varchar NOT NULL,
created_on TIMESTAMP(6) NOT NULL
);
============================
CREATE OR REPLACE FUNCTION user_creation()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$
BEGIN
IF NEW.usename <> OLD.usename THEN
INSERT INTO user_audits(usesysid,usename,created_on)
VALUES(usesysid,usename,now());
END IF;
RETURN NEW;
END;
$$
=================================
CREATE TRIGGER user_creation
BEFORE UPDATE
ON user
FOR EACH ROW
EXECUTE PROCEDURE user_creation();
This is important for audit purpose, for now I am using log file to check creation date but it will rotate after sometime.
Please suggest the better way to dump user creation date in table so that I can retrieve the information anytime.
Thanks
I created a similar excercise with the following tables:
The user_tbl table having only a identity column usersysid and the username
CREATE TABLE user_tbl (
usersysid INT GENERATED ALWAYS AS IDENTITY,
username varchar NOT NULL
);
The user_audits table, slightly modified version of yours: where i added an id identity field. I removed the identity from the usersysid field (since it'll be populated with the one coming from user_tbl)
CREATE TABLE user_audits (
id INT GENERATED ALWAYS AS IDENTITY,
usersysid INT,
username varchar NOT NULL,
created_on TIMESTAMP(6) NOT NULL
);
Now the function, I check if the OLD.username is null, this means that is an insert, if NEW.username <> OLD.username then is an update.
CREATE OR REPLACE FUNCTION user_creation()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$
BEGIN
IF OLD.username is null OR NEW.username <> OLD.username THEN
INSERT INTO user_audits(usersysid,username,created_on)
VALUES(NEW.usersysid,NEW.username,now());
END IF;
RETURN NEW;
END;
$$
;
And finally the trigger, which is fired both on INSERT or UPDATE
CREATE TRIGGER user_creation
BEFORE INSERT OR UPDATE
ON user_tbl
FOR EACH ROW
EXECUTE PROCEDURE user_creation();
Now if I create two new rows and update one with the following
insert into user_tbl (username) values('Carlo');
insert into user_tbl (username) values('Gianni');
update user_tbl set username='Giorgio' where usersysid=1;
I end up with the user_tbl containing the 2 expected rows
defaultdb=> select * from user_tbl;
usersysid | username
-----------+----------
2 | Gianni
1 | Giorgio
(2 rows)
and the user_audits tables containing 3 rows (2 for the insert + 1 for the update)
defaultdb=> select * from user_audits;
id | usersysid | username | created_on
----+-----------+----------+----------------------------
1 | 1 | Carlo | 2021-06-04 13:57:44.810889
2 | 2 | Gianni | 2021-06-04 13:58:14.680878
3 | 1 | Giorgio | 2021-06-04 13:58:44.702364
(3 rows)

PostgreSQL: `NEW.<value>` returning `NULL` when `AFTER TRIGGER` is fired

Description of the problem:
Hi everyone, i have this table:
CREATE TABLE COMPORDINE (
CodProdotto CHAR(5) NOT NULL CHECK(CodProdotto ~* '^[0-9]+$'),
CodOrdine CHAR(5) CHECK(CodOrdine ~* '^[0-9]+$'),
Prezzo REAL NOT NULL, CHECK(Prezzo >= 0.0)
CONSTRAINT OrdineFK FOREIGN KEY(CodOrdine) REFERENCES ORDINE(CodOrdine)
CONSTRAINT ProdottoFK FOREIGN KEY(CodProdotto) REFERENCES PRODOTTO(CodProdotto)
);
on which I declared a TRIGGER:
CREATE TRIGGER updatePrezzoOrdine
AFTER INSERT ON COMPORDINE
FOR EACH ROW
EXECUTE PROCEDURE updatePrezzoOrdine();
and a TRIGGER FUNCTION:
CREATE OR REPLACE FUNCTION updatePrezzoOrdine()
RETURNS TRIGGER
LANGUAGE plpgsql
AS
$$
DECLARE
Totale REAL;
BEGIN
SELECT SUM(Prezzo) INTO Totale
FROM COMPORDINE AS CO
WHERE CO.CodOrdine = NEW.CodOrdine
GROUP BY CodOrdine;
UPDATE ORDINE SET PrezzoTotale = PrezzoTotale + Totale
WHERE CodOrdine = NEW.CodOrdine;
RETURN NEW;
END
$$
Everything works and the trigger just fires fine, but no UPDATE is done on table ORDINE (on line 12).
What I experience (and tried):
I've tried to put some print instructions to see if the query was working:
RAISE NOTICE 'NEW.CodOrdine = %', NEW.CodOrdine;
RAISE NOTICE 'NEW.CodProdotto = %', NEW.CodProdotto;
RAISE NOTICE 'NEW.Prezzo = %', NEW.Prezzo;
but the output returns that new row inserted:
INSERT INTO COMPORDINE VALUES ('12345', '11111', 1.80)
has null values:
NOTICE: NEW.CodOrdine = <NULL>
NOTICE: NEW.CodProdotto = <NULL>
NOTICE: NEW.Prezzo = <NULL>
So the UPDATE "fails" because no row in ORDINE matches NULL values:
CodOrdine
PrezzoTotale
DataAcquisto
CodCliente
CodDipendente
12345
default (0.0)
2020-12-12
00000
99999
Apparently the problem could be related to the inserted row, may it?
What I expect:
After INSERT I would the ORDINE.PrezzoTotale column to be updated with the correct total price of every item in COMPORDINE that matches COMPORDINE.CodOrdine = ORDINE.CodOrdine in this way:
INSERT INTO COMPORDINE VALUES ('12345', '11111', 1.80)
CodOrdine
PrezzoTotale
DataAcquisto
CodCliente
CodDipendente
12345
1.80
2020-12-12
00000
99999
Big thanks to anyone in advance
[EDIT #1]:
As asked, here's the output of \d compordine:
**"public.compordine" table:**
| Colonna | Tipo | Ordinamento | Pu‗ essere null | Default|
|------------|--------------|-------------|-----------------|--------|
|codprodotto | character(5) | | not null | |
|codordine | character(5) | | not null | |
|prezzo | real | | not null | |
**Indexes**:
"compordine_codprodotto_key" UNIQUE CONSTRAINT, btree (codprodotto)
**Check constraints**:
"compordine_codordine_check" CHECK (codordine ~* '^[0-9]+$'::text)
"compordine_codprodotto_check" CHECK (codprodotto ~* '^[0-9]+$'::text)
"compordine_prezzo_check" CHECK (prezzo > 0::double precision)
**Referential integrity constraints**:
"ordinefk" FOREIGN KEY (codordine) REFERENCES ordine(codordine) ON UPDATE CASCADE
"prodottofk" FOREIGN KEY (codprodotto) REFERENCES prodotto(codprodotto) ON UPDATE CASCADE
**Triggers**:
updateprezzoordine AFTER INSERT ON compordine FOR EACH STATEMENT EXECUTE FUNCTION updateprezzoordine()
**Disabled triggers**:
updatescorte BEFORE INSERT ON compordine FOR EACH ROW EXECUTE FUNCTION updatescorte()
[EDIT #2]:
So, I did several other attempts to find a solution (SPOILER ALERT: no solution found); what I did, was to create another function and trigger that simply prints the NEW.<column> value after an INSERT statement on a different table called CLIENTE (my suspect was that all this issue was releated only to COMPORDINE table but I was wrong), here's the code:
CREATE TABLE CLIENTE (
CodCliente CHAR(5) PRIMARY KEY, CHECK(CodCliente ~* '^[0-9]+$'),
Nome VARCHAR(255) NOT NULL, CHECK(Nome ~* '^[A-Za-z ]+$'),
Cognome VARCHAR(255) NOT NULL, CHECK(Cognome ~* '^[A-Za-z ]+$'),
CodiceFiscale CHAR(16) NOT NULL, CHECK(CodiceFiscale ~* '^[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]$'),
Indirizzo VARCHAR(300) NOT NULL,
Email VARCHAR(255) NOT NULL, CHECK(Email ~* '^[A-Za-z0-9._%-]+#[A-Za-z0-9.-]+[.][A-za-z]+$'),
UNIQUE(CodiceFiscale, Email)
);
CREATE TRIGGER print
BEFORE INSERT ON Cliente
EXECUTE PROCEDURE print();
CREATE FUNCTION print()
RETURNS TRIGGER
LANGUAGE plpgsql
AS
$$
BEGIN
RAISE NOTICE 'CodCliente = %', NEW.CodCliente;
RETURN NEW;
END
$$
INSERT INTO CLIENTE VALUES ('34567', 'Gianfranco', 'Rana', 'GNFRNA87F24E123A', 'Via Delle Rane 23', 'franco.rane#ravioli.it')
Output returns:
NEW.CodCliente = <NULL>
While it should be:
NEW.CodCliente = <34567>
I simply can't understand why, shouldn't NEW in an AFTER INSERT TRIGGER returns the value this way? :
Event
OLD
NEW
INSERT
NULL
New Record
DELETE
Old Record
NULL
UPDATE
Original Record
Updated Record
Had the same issue. For me the issue was the way I created the trigger:
create trigger pending_balance_add after insert on pending_balance
execute function update_pending_balance();
I forgot to tell that "for each row" and perhaps "procedure" instead of function:
create trigger pending_balance_add before insert on pending_balance
for each row
execute procedure update_pending_balance();
Now inside of update_pending_balance I have correct new.amount, etc.
Regarding your issue, it shows that
Triggers:
updateprezzoordine AFTER INSERT ON compordine FOR EACH STATEMENT EXECUTE FUNCTION updateprezzoordine()
Disabled triggers:
updatescorte BEFORE INSERT ON compordine FOR EACH ROW EXECUTE FUNCTION updatescorte()
When trigger is executed for statement it has NEW.FIELD values as NULL
PostgreSQL Can't change values in AFTER trigger. Change the trigger to BEFORE.
Does not matter if you change the variable record NEW, the data was already changed in the table record. AFTER triggers ignore NEW changes. You can read and write but only read matters.

Postgres Count records inserted/ updated

I'm trying to keey track of a clients database with which we sync. I need to record records_added (INSERTs) and records_updated (UPDATEs) to our table.
I'm using an UPSERT to handle the sync, and a trigger to update a table keeping track of insert/updates.
The issue is counting records that have are updated. I have 40+ columns to check, do I have to put all these in my check logic? Is there a more elegant way?
Section of code in question:
select
case
when old.uuid = new.uuid
and (
old.another_field != new.another_field,
old.and_another_field != new.and_another_field,
-- many more columns here << This is particularly painful
) then 1
else 0
end into update_count;
Reproducible example:
-- create tables
CREATE TABLE IF NOT EXISTS example (uuid serial primary key, another_field int, and_another_field int);
CREATE TABLE IF NOT EXISTS tracker_table (
records_added integer DEFAULT 0,
records_updated integer DEFAULT 0,
created_at date unique
);
-- create function
CREATE OR REPLACE FUNCTION update_records_inserted () RETURNS TRIGGER AS $body$
DECLARE update_count INT;
DECLARE insert_count INT;
BEGIN
-- ---------------- START OF BLOCK IN QUESTION -----------------
select
case
when old.uuid = new.uuid
and (
old.another_field != new.another_field
-- many more columns here
) then 1
else 0
end into update_count;
-- ------------------ END OF BLOCK IN QUESTION ------------------
-- count INSERTs
select
case
when old.uuid is null
and new.uuid is not null then 1
else 0
end into insert_count;
-- --log the counts
-- raise notice 'update %', update_count;
-- raise notice 'insert %', insert_count;
-- insert or update count to tracker table
insert into
tracker_table(
created_at,
records_added,
records_updated
)
VALUES
(CURRENT_DATE, insert_count, update_count) ON CONFLICT (created_at) DO
UPDATE
SET
records_added = tracker_table.records_added + insert_count,
records_updated = tracker_table.records_updated + update_count;
RETURN NEW;
END;
$body$ LANGUAGE plpgsql;
-- Trigger
DROP TRIGGER IF EXISTS example_trigger ON example;
CREATE TRIGGER example_trigger
AFTER
INSERT
OR
UPDATE
ON example FOR EACH ROW EXECUTE PROCEDURE update_records_inserted ();
-- A query to insert, then update when number of uses > 1
insert into example(whatever) values (2, 3) ON CONFLICT(uuid) DO UPDATE SET another_field=excluded.another_field+1;

Next value of primary key in postgresql

Advise me on how to get the next value of the primary key of an inserting row and use the value.
I am trying to create a table that stores thee paths.
CREATE TABLE comments (
id serial primary key,
path integer[] not null,
content varchar(200) not null
);
insert into meters_location (content, path)
values ('content_1', '{I need to put here the id of the inserted row}');
When I insert the row into the table the id generated value should be put as a path automatically, so if id = 1 then path = {1}.
it should be like:
id | path | content
-------------------------
1 | {1} | content_1
Use the RETURNING clause.
INSERT INTO meters_location (content, path) VALUES ('content_1', '{}') RETURNING id;
You can get any column's value from the affected table this way.
Edit:
If you just want to update an array column with the value of another column, you will need to use a trigger. For example:
CREATE OR REPLACE FUNCTION update_ml_path() RETURNS trigger
AS $body$
BEGIN
IF (TG_OP = 'INSERT') THEN
NEW.path = ARRAY[NEW.id];
END IF;
RETURN NEW;
END;
$body$ LANGUAGE plpgsql;
CREATE TRIGGER set_ml_path BEFORE INSERT ON meters_location
FOR EACH ROW EXECUTE FUNCTION update_ml_path();
postgres=# INSERT INTO meters_location (content, path) VALUES ('content_1', '{}');
INSERT 0 1
postgres=# SELECT * FROM meters_location;
id | path | content
----+------+-----------
1 | {1} | content_1
(1 row)
If it's another table you need to update, you will need to adjust the trigger and function accordingly.
You can use lastval() for that:
insert into meters_location (content, path)
values ('content_1', array[lastval()]);
This only works for single row inserts though.
Online example