I need help with creating a trigger which forbids user to delete data that is newer than 2 weeks.
My current code:
CREATE OR REPLACE FUNCTION f_delete_data() RETURNS trigger AS $$
BEGIN
RAISE EXCEPTION 'Cant delete data which is newer than 2 weeks.';
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trig_delete_data BEFORE DELETE ON Results
FOR EACH ROW WHEN (OLD.Date < DATE_SUB(NOW(), INTERVAL 14 DAY)) EXECUTE PROCEDURE
f_delete_data();
This code says there's a syntax error at or near 14 ..
Why is the date_sub(..,interval 14 day) not working?
I'm using PostgreSQL 9.3.0.
Why isn't the date_sub(..,intercval 14 day) not working?
$$ LANGUAGE plpgsql; indicates you're using postgres however DATE_SUB is a MySQL specific function not available in postgres.
Try replacing DATE_SUB with this
(OLD.Date < NOW() - INTERVAL '14 DAYS')
Besides the obvious mistake (no DATE_SUB in Postgres), you also have your logic backwards. If you want to protect rows where the value in the date column is less than 2 weeks old: "newer than 2 weeks", then you must revert the comparison operator.
CREATE TRIGGER trig_delete_data
BEFORE DELETE ON results
FOR EACH ROW
WHEN (OLD.date < DATE_SUB(NOW(), INTERVAL 14 DAY))
WHEN (OLD.date < now() - interval '14 days')
WHEN (OLD.date > now() - interval '14 days')
EXECUTE PROCEDURE f_delete_data();
And f_delete_data() really should be name something like f_protect_new_data().
Or, if your columns is an actual date like the ill-chosen column name suggests, further simplify:
WHEN (OLD.date >= CURRENT_DATE - 14)
The manual on CURRENT_DATE & friends.
Use >= in this case, the 14th day back from today is still illegal according to your definition. The bound is logically a bit different from timestamp handling.
Why "ill-chosen"? "date" is a reserved word in standard SQL and a basic type name in Postgres. If the column actually holds a timestamp, not a date, it's misleading on top of that.
Related
I want to see how long a loop iteration takes inside a DO block in postgres. The basic layout is as follows:
DO $$
declare v_time timestamptz;
declare i record;
begin
for i in select generate_series(1, 5) t
loop
select current_timestamp into v_time;
perform pg_sleep(i.t);
-- something done here (pg_sleep to ensure some time passes)
raise notice '%', v_time - (select current_timestamp);
-- expect negative interval from RAISE.
end loop;
end; $$;
However, when I run this (have tried on Postgres 13 and 9), I get an interval of 0S returned:
NOTICE: 00:00:00
NOTICE: 00:00:00
NOTICE: 00:00:00
NOTICE: 00:00:00
NOTICE: 00:00:00
DO
Query returned successfully in 15 secs 389 msec.
I have done this previously and have never run into this issue before, so I guess my question is "what am I doing wrong this time?" instead of "why is postgres behaving unexpectedly?"
current_timestamp is defined to be:
the start time of the current transaction, their values do not change during the transaction
you probably want to use clock_timestamp() instead which returns a value that changes within a transaction, see the link above for a more complete description.
In my program every table has a column last_modified:
last_modified int8 DEFAULT (date_part('epoch'::text, now()::timestamp) * (1000)::double precision) NOT NULL
For update I added a trigger:
CREATE OR REPLACE FUNCTION sync_lastmodified() RETURNS trigger AS $$
BEGIN
NEW.last_modified := (date_part('epoch'::text, now()::timestamp) * (1000)::double precision);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER
sync_lastmodified
BEFORE UPDATE ON
ourtable
FOR EACH ROW EXECUTE PROCEDURE
sync_lastmodified();
They should write current time as a long value into the last_modified column on update/insert.
However, it is not working as I expected.
To reproduce the issue, I did update and got the following:
last_modified value equals 1543576224455 (Friday November 30, 2018 16:10:24 (pm) in time zone Asia/Tashkent (+05))
Almost at the same time I run the function now from pgAdmin:
SELECT now()
and got the result:
2018-11-30 11:10:36.891426+05
To check system time within a few seconds I run timedatectl status from terminal and got the following result:
The question is why the function now() gives a time with 5 hours
difference when I run it from trigger or as a default value when
insert?
epoch will give you the number of seconds since the epoch. As the documentation says:
epoch
For timestamp with time zone values, the number of seconds since 1970-01-01 00:00:00 UTC (can be negative)
Since you are offset by 5 hours from UTC, that explains the difference.
I want to delete records older than a given (as a parameter to the function) number of days.
There are similar questions (e.g. SQL Get all records older than 30 days) which recommend to use interval '30 day' but how to make the 30 a parameter?
I have a column with a timestamp and the delete would be done in a function that's in SQL rather than plpgsql
Such an SQL function could look like this:
CREATE FUNCTION delold(integer) RETURNS void
LANGUAGE sql STRICT AS
'DELETE FROM mytable
WHERE tscol < current_timestamp
- ($1::text || '' days'')::interval';
So, i have a couple of functions inside my db.
One function needs to run when the data in a specific table is older than 5 minutes.
I've tried doing it with:
PERFORM case when now() - '5 minutes'::interval > (select end_time from x order by end_route desc limit 1) then update_x() else null end;
When i run the command as a regular select query, it runs OK. But when i put that inside another function (The one being called, returns updated table that is no older than 5 minutes), it never runs. Also, if i just put update_x(), then it runs OK (but every time the function is called).
Does anyone have any idea on how i could fix this?
One idea is to just set a cron to run the function every 5 mins independently, but i'd rather the server is idle since the function is quite resource intensive, and it doesn't get called that often.
I'm on version 8.4(Due to my ISP, so can't change, though i am considering moving to VPS, so if this is something that would work on 9.5 and newer, i can wait).
The function now() gives the start time of the current transaction and is invariable inside it. Use clock_timestamp(), example:
do $$
begin
for i in 1..3 loop
perform pg_sleep(1);
raise notice 'now(): % clock_timestamp(): %', now(), clock_timestamp();
end loop;
end $$;
NOTICE: now(): 2017-12-06 10:22:40.422683+01 clock_timestamp(): 2017-12-06 10:22:41.437099+01
NOTICE: now(): 2017-12-06 10:22:40.422683+01 clock_timestamp(): 2017-12-06 10:22:42.452456+01
NOTICE: now(): 2017-12-06 10:22:40.422683+01 clock_timestamp(): 2017-12-06 10:22:43.468124+01
Per the documentation:
clock_timestamp() returns the actual current time, and therefore its value changes even within a single SQL command (...)
now() is a traditional PostgreSQL equivalent to transaction_timestamp().
I'm not sure why, but once i moved the boolean up one level it started working.
So now instead of having perform case query inside function, i'm sending boolean to the function, and performing the check in a view above this function.
CREATE VIEW x_view AS select * from get_x((clock_timestamp() - '5 minutes'::interval)::timestamp > (select end_route from gps_skole2 order by end_route desc limit 1));
And inside the function:
PERFORM case when $1 then update_x() else null end;
I m using postgresql 8.2.
I want to be able to create a simple table with year and all the week number upto 52 for that year.
Like this:
There must be an efficient way to do this.
If it needs to scale out to any year, it should dynamically list all the week number for year.
Any help is appreciated.
TIA
From what I know, PostgreSQL does not have a function that returns the number of weeks a year. So I guess the best way to proceed is to create this function:
CREATE OR REPLACE FUNCTION weeks_in_year(aYear integer) RETURNS integer AS
$$
DECLARE
vW integer;
BEGIN
vW := date_part('week', (aYear::text || '-12-31')::date);
-- When the week is the first, the year has 52 weeks.
RETURN CASE WHEN vW = 1 THEN 52 ELSE vW END;
END
$$
language 'plpgsql';
Then you can use this function with generate_series to get your data
select 2013 AS year,generate_series(1, weeks_in_year(2013)) AS week
if you have to create a table, you can use
SELECT 2013 AS year,generate_series(1, weeks_in_year(2013)) AS week INTO my_table