Postgresql 8.4 update query syntax error in plpgsql function - postgresql

I am using PostgreSQL 8.4 and creating a plpgsql function. In the body of this function I have a query to update records.
...
UPDATE device_syncfiles SET
state_code = 1, updated_at = NOW() at time zone 'UTC'
WHERE
((state_code = 2 AND EXTRACT(EPOCH FROM (NOW() at time zone 'UTC' - updated_at::timestamp without time zone)) > 3600) OR
(state_code = 3 AND EXTRACT(EPOCH FROM (NOW() at time zone 'UTC' - updated_at::timestamp without time zone)) > 3600));
...
When I load this function into database, a syntax error turns out
ERROR: syntax error at or near "$1"
LINE 1: UPDATE device_syncfiles SET $1 = 1, $2 = NOW() at time z...
^
QUERY: UPDATE device_syncfiles SET $1 = 1, $2 = NOW() at time zone 'UTC' WHERE (( $1 = 2 AND EXTRACT(EPOCH FROM (NOW() at time zone 'UTC' - $2 ::timestamp without time zone)) > $3 ) OR ( $1 = 3 AND EXTRACT(EPOCH FROM (NOW() at time zone 'UTC' - $2 ::timestamp without time zone)) > $4 ))
CONTEXT: SQL statement in PL/PgSQL function "syncfile_get" near line 19
I cannot find any problem with this query. What's wrong here?
UPDATE: (missing information)
Table: device_syncfiles
id PK integer auto inc
user_id integer FK
file_name character varying(255) NOT NULL,
state_code integer NOT NULL FK,
md5 character varying(255) NOT NULL,
msg character varying(255),
created_at timestamp without time zone,
updated_at timestamp without time zone
Function: syncfile_get()
CREATE OR REPLACE FUNCTION syncfile_get()
RETURNS TABLE(id integer, user_id integer, file_name character varying, state_code integer, md5 character varying, created_at timestamp without time zone, updated_at timestamp without time zone) AS
$BODY$
DECLARE
_device_syncfile_id integer;
_download_timeout integer;
_processing_timeout integer;
BEGIN
-- GET all timeout info
SELECT state_timeout INTO _download_timeout FROM device_syncfile_states
WHERE state_name = 'downloading';
SELECT state_timeout INTO _processing_timeout FROM device_syncfile_states
WHERE state_name = 'processing';
-- GET syncfile id
_device_syncfile_id = NULL;
-- Reset timed out file to idel state
UPDATE device_syncfiles SET
state_code = 1, updated_at = NOW() at time zone 'UTC'
WHERE
((state_code = 2 AND EXTRACT(EPOCH FROM (NOW() at time zone 'UTC' - updated_at::timestamp without time zone)) > _download_timeout) OR
(state_code = 3 AND EXTRACT(EPOCH FROM (NOW() at time zone 'UTC' - updated_at::timestamp without time zone)) > _processing_timeout));
-- GET the id of one idel/timed out file => result could be a integer or NULL
SELECT device_syncfiles.id INTO _device_syncfile_id FROM device_syncfiles
WHERE
device_syncfiles.state_code = 1 OR
(device_syncfiles.state_code = 2 AND EXTRACT(EPOCH FROM (NOW() at time zone 'UTC' - device_syncfiles.updated_at::timestamp without time zone)) > _download_timeout) OR
(device_syncfiles.state_code = 3 AND EXTRACT(EPOCH FROM (NOW() at time zone 'UTC' - device_syncfiles.updated_at::timestamp without time zone)) > _processing_timeout)
LIMIT 1;
-- WHEN NULL skip state update and return empty set of record
-- Otherwise return the set of record with the id found in last step
IF _device_syncfile_id IS NOT NULL THEN
PERFORM syncfile_update(_device_syncfile_id, 2, NULL);
END IF;
RETURN QUERY SELECT
device_syncfiles.id,
device_syncfiles.user_id ,
device_syncfiles.file_name ,
device_syncfiles.state_code ,
device_syncfiles.md5 ,
device_syncfiles.created_at ,
device_syncfiles.updated_at
FROM device_syncfiles WHERE device_syncfiles.id = _device_syncfile_id;
END;
$BODY$
LANGUAGE plpgsql VOLATILE

Many problems.
0.
I am using Postgresql 8.4
Postgres 8.4 reached EOL in July 2014. Consider upgrading to a current version. Urgently.
1.
Your question does not disclose the complete function (at least header and footer) nor any table definition and some sample data to help us help you.
I have to make assumptions, and my educated guess is that you have a function parameter named state_code, which conflicts with the identical column name. In three places. Basics:
Postgresql - INSERT RETURNING INTO ambiguous column reference
How to return result of a SELECT inside a function in PostgreSQL?
You must be aware the all fields declared in a RETURNS TABLE clause are effectively OUT parameters as well. (As stated in the first sentence of the first link.) So your Q update confirmed my assumptions.
2.
Your error message reports the first of those instances here:
UPDATE device_syncfiles SET
state_code = 1 ...
That's a consequence of 0.. You are tripping over your long dead and forgotten version of Postgres, where the superficial syntax check at function creation time used to detect a naming conflict between target columns of UPDATE statements and function parameters. Which is silly and was later removed: those target columns cannot conflict with function parameters on principal.
Your error reproduced in Postgres 8.4: dbfiddle here
The same does not happen in Postgres 9.4: dbfiddle here
To fix, best rename the function parameter to avoid conflicts. Related:
Postgres function returning a row as JSON value
3.
There are two more instances:
WHERE
((state_code = 2 AND EXTRACT(EPOCH FROM (NOW() at time zone 'UTC' - updated_at::timestamp without time zone)) > 3600) OR
(state_code = 3 AND EXTRACT(EPOCH FROM (NOW() at time zone 'UTC' - updated_at::timestamp without time zone)) > 3600));
Would need a fix in any version. Postgres cannot tell whether to resolve to the function parameter or the table column. Best table-qualify all columns to avoid any possible conflicts with parameter names a priori. (Except for UPDATE target columns, which do not need nor allow table qualification.)
4.
That's still lipstick on a pig. Improve the query like this:
UPDATE device_syncfiles d
SET state_code = 1
, updated_at = NOW() AT TIME ZONE 'UTC'
WHERE d.state_code IN (2, 3)
AND d.updated_at < (now() - interval '1 hour') AT TIME ZONE 'UTC';
Shorter, faster, can use an index on updated_at.
5.
Finally consider using timestamp with time zone instead of timestamp without time zone to begin with:
Ignoring timezones altogether in Rails and PostgreSQL

Related

postgresql filter users by age range

Is there a better way of doing this?
Basically, I have a users table, and on of the columns is birth_date (date)
I am supposed to filter by age range, meaning, I will get a range like 18-24.
This will be passed to a function in a jsonb parameter, as an array of 2 integers.
So I have done the following
create or replace function my_filter_function(
p_search_parameters jsonb
)
returns TABLE(
user_id bigint,
birth_date date,
age interval,
years double precision
)
security definer
language plpgsql
as
$$
begin
return query
select u.user_id, u.birth_date, age(u.birth_date), date_part('year', age(u.birth_date))
from users u
where u.birth_date is not null
and ( (p_search_parameters#>>'{age,0}') is null or u.birth_date <= (now() - ((p_search_parameters#>>'{age,0}')::integer * interval '1 year'))::date)
and ( (p_search_parameters#>>'{age,1}') is null or u.birth_date >= (now() - ((p_search_parameters#>>'{age,1}')::integer * interval '1 year'))::date)
;
end;
$$;
-- this is just a aluttle helpder function to better post and explain the question
This seems to be doing the job, but was hoping to find other ways of doing this while still getting a jsonb parameter, array of 2 integers
Any ideas?

Postgres - Pass dynamically generated date to where clause

I need to generate series of date till current_date based on job's last run date
last run date ='2022-10-01'
current date = '2022-10-05'
generate date like
varchar dynamic_date = '2022-10-01','2022-10-02','2022-10-03','2022-10-04','2022-10-05'
and pass to where to clause
select *
from t1
where created_date in (dynamic_date)
this is not allowed as dynamic_date is varchar and created_date is date column
trying to find efficient way to do this
You can use generate_series()
select *
from t1
where created_date in (select g.dt::date
from generate_series(date '2022-10-01',
current_date,
interval '1 day') as g(dt)
)
Or even simpler:
select *
from t1
where created_date >= date '2022-10-01'
and created_date <= current_date

Inserting into Remote Db via Dblink

I'm trying to insert into remote DB via this query
The query works fine When I only select these columns but when I'm trying to insert with a 30-day interval condition it shows that an error in syntax
I don't know What I'm doing wrong in the interval syntax for selecting data of 30 days.
Can anyone help me with this!
Thanks
INSERT INTO tc_new_events
SELECT * FROM dblink ('dev_connec','SELECT id,type,eventtime,
deviceid,positionid,geofenceid,
attributes,maintenanceid FROM tc_events
WHERE eventTime < (NOW() - '30 days' INTERVAL )')
AS DATA (id int ,type varchar , eventtime timestamp without time zone ,
deviceid integer,positionid integer,geofenceid integer ,
attributes varchar,maintenanceid integer);
ERROR: syntax error at or near "30"
LINE 13: ...anceid FROM tc_events WHERE eventTime < (NOW() - '30 days' I...
^
SQL state: 42601
Character: 994

Atomic INSERT WHERE NOT EXISTS (Postgres 9.6.3)

The following statement suffers a race condition which I can reliably demonstrate by executing it concurrent in very quick succession. Is there a way to remove this race condition from the below of do I need to take a different approach altogether?
INSERT INTO scheduled_event_log ("key")
SELECT 'test'
WHERE NOT EXISTS(
SELECT AGE(now() at time zone 'utc', timestamp_utc)
FROM scheduled_event_log
WHERE "key" = 'test'
AND age(now() at time zone 'utc', timestamp_utc) < '6s' FOR UPDATE);
Table as follows:
CREATE TABLE scheduled_event_log (
"key" varchar(64) NOT NULL,
"timestamp_utc" timestamp without time zone default (now() at time zone 'utc')
);

Extracting the number of days from a calculated interval

I am trying to get a query like the following one to work:
SELECT EXTRACT(DAY FROM INTERVAL to_date - from_date) FROM histories;
In the referenced table, to_date and from_date are of type timestamp without time zone. A regular query like
SELECT to_date - from_date FROM histories;
Gives me interval results such as '65 days 04:58:09.99'. But using this expression inside the first query gives me an error: invalid input syntax for type interval. I've tried various quotations and even nesting the query without luck. Can this be done?
SELECT EXTRACT(DAY FROM INTERVAL to_date - from_date) FROM histories;
This makes no sense. INTERVAL xxx is syntax for interval literals. So INTERVAL from_date is a syntax error, since from_date isn't a literal. If your code really looks more like INTERVAL '2012-02-01' then that's going to fail, because 2012-02-01 is not valid syntax for an INTERVAL.
The INTERVAL keyword here is just noise. I suspect you misunderstood an example from the documentation. Remove it and the expression will be fine.
I'm guessing you're trying to get the number of days between two dates represented as timestamp or timestamptz.
If so, either cast both to date:
SELECT to_date::date - from_date::date FROM histories;
or get the interval, then extract the day component:
SELECT extract(day from to_date - from_date) FROM histories;
This example demontrates the creation of a table with trigger which updates the difference between a stop_time and start_time in DDD HH24:MI:SS format where the DDD stands for the amount of dates ...
DROP TABLE IF EXISTS benchmarks ;
SELECT 'create the "benchmarks" table'
;
CREATE TABLE benchmarks (
guid UUID NOT NULL DEFAULT gen_random_uuid()
, id bigint UNIQUE NOT NULL DEFAULT cast (to_char(current_timestamp, 'YYMMDDHH12MISS') as bigint)
, git_hash char (8) NULL DEFAULT 'hash...'
, start_time timestamp NOT NULL DEFAULT DATE_TRUNC('second', NOW())
, stop_time timestamp NOT NULL DEFAULT DATE_TRUNC('second', NOW())
, diff_time varchar (20) NOT NULL DEFAULT 'HH:MI:SS'
, update_time timestamp DEFAULT DATE_TRUNC('second', NOW())
, CONSTRAINT pk_benchmarks_guid PRIMARY KEY (guid)
) WITH (
OIDS=FALSE
);
create unique index idx_uniq_benchmarks_id on benchmarks (id);
-- START trigger trg_benchmarks_upsrt_diff_time
-- hrt = human readable time
CREATE OR REPLACE FUNCTION fnc_benchmarks_upsrt_diff_time()
RETURNS TRIGGER
AS $$
BEGIN
-- NEW.diff_time = age(NEW.stop_time::timestamp-NEW.start_time::timestamp);
NEW.diff_time = to_char(NEW.stop_time-NEW.start_time, 'DDD HH24:MI:SS');
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_benchmarks_upsrt_diff_time
BEFORE INSERT OR UPDATE ON benchmarks
FOR EACH ROW EXECUTE PROCEDURE fnc_benchmarks_upsrt_diff_time();
--
-- STOP trigger trg_benchmarks_upsrt_diff_time
Just remove the keyword INTERVAL:
SELECT EXTRACT(DAY FROM to_date - from_date) FROM histories;