Problems with parameterizing timestamp references in PostgreSQL in Perl - sql

I am trying to use a parameterized query in a Perl script to get some timestamps back from a Postgres database. Here's a cut-and-dried example, solely for pedagogical purposes.
I've defined $start_date and $end_date as timestamps and intervals:
my $start_date = "current_timestamp - interval '6 hours'";
my $end_date = "current_timestamp";
I use the following to submit to the database, with $dbh defined earlier:
my $sql = "SELECT cast(? as timestamp), cast(? as timestamp)";
my $sth = $dbh->prepare($sql);
$sth->execute($start_date, $end_date);
When I do this, I get a somewhat confusing error.
DBD::Pg::st execute failed: ERROR: date/time value "current" is no longer supported
I understand that current hasn't been supported in PG since 7.2, but I'm not using that. I'm using current_timestamp, which is supported, AFACT. To wit, if I enter into psql:
select (cast(current_timestamp - interval '6 hours' as timestamp), cast(current_timestamp as timestamp));
the result is what I expect (two timestamps, the former six hours previous to the latter).
I could also use now() rather than current_timestamp. I can use it in the following way:
my $start_date = "now() - interval '6 hours'";
my $end_date = "now()";
When I try to run the query in perl, I get the following error:
DBD::Pg::st execute failed: ERROR: invalid input syntax for type timestamp: "now() - interval '6 hours'"
Yet, the query:
select (cast(now() - interval '6 hours' as timestamp), cast(now() as timestamp));
gives me the expected result.
I am quite flummoxed.

The problem is that a SQL placeholder doesn't represent an expression, but a single value. And that value can't be a function. You could do something like:
my $start_date = "6 hours";
my $sql = "SELECT current_timestamp - cast(? as interval), current_timestamp";
my $sth = $dbh->prepare($sql);
$sth->execute($start_date);
What you're doing in Perl is equivalent to doing this in psql:
select (cast('current_timestamp - interval ''6 hours''' as timestamp), cast('current_timestamp' as timestamp));

To make the windows of your queries a bit more flexible:
$sth = $dbh->prepare(<<__eosql);
SELECT * FROM tbl
WHERE ts BETWEEN current_timestamp - ? * CAST('1 ' || ? AS INTERVAL)
AND
current_timestamp;
__eosql
$sth->execute(6, 'hour');
$sth->execute(10, 'day');
$sth->execute(1, 'week');
# etc.
When you introduce fixed time points, you could do something too clever like ... WHERE COALESCE(?, current_timestamp) ... and remember that an undef parameter defaults to the current time. However, I'd probably write and prepare a separate query.

Related

How to save temporary session variables in PostgreSQL

Say I have a simple SQL statement like this:
SELECT * from birthday WHERE date < now() + INTERVAL '1 day' AND date > now() - INTERVAL '1 day';
but the equations for the "From" and "To" variables would be something more sophisticated than that. And I will be reusing the results of these equations in multiple queries. So, can I store them temporarily as we do in any programming language? I imagine something like:
$from := now() - INTERVAL '1 day';
$to:= now() + INTERVAL '1 day';
SELECT * from birthday WHERE date < $from AND date > $to;
I tried using SELECT INTO as suggested in this question but that's not what I want because it creates a whole database table just to save the variable, which also causes an error when reusing it in a later session even when using the TEMP parameter. It says "relationship already exists"!
I also tried some dollar sign $ syntax and some colon+equal := syntax and none works
SQL is not any programming language. If you want to store the values so you can re-use them in one query, then you can use a CTE:
WITH params as (
SELECT now() - INTERVAL '1 day' as _from,
now() + INTERVAL '1 day' as _to
)
SELECT *
FROM params CROSS JOIN
birthday b
WHERE date < params._to AND date > params._from;
If you want to repeat this across multiple queries, then I would recommend a temporary table:
CREATE TEMPORARY TABLE params AS
SELECT now() - INTERVAL '1 day' as _from,
now() + INTERVAL '1 day' as _to;
SELECT *
FROM params CROSS JOIN
birthday b
WHERE date < params._to AND date > params._from;
You can also encapsulate the code in a procedure/function. Or use some sort of scripting language or language such as Python.

ERROR: invalid input syntax for type interval

I have a query like the following
CAST(kokyaku1Information2.mail_jyushin as integer) as information2_mail_jyushin,
(date '$mytime' - INTERVAL 'information2_mail_jyushin' day) AS modified_date,
When run the query i get an error like 'invalid input syntax for type interval'. I used another select field named information2_mail_jyushin before day.
In Postgres, you would use interval arithmetics like this:
kyaku1Information2.mail_jyushin::int AS information2_mail_jyushin,
date '$mytime'
- kokyaku1Information2.mail_jyushin::int * interval '1 day'
AS modified_date
Note that concatenating variables in a SQL statement is bad practice, and opens up your code to SQL injection attacks. Instead, use parameters, as in:
$1::date
- kokyaku1Information2.mail_jyushin::int * interval '1 day'
AS modified_date

Read & evaluate cell as keyword in PostgreSQL

I have a table called providers where the column notice_period takes values such as interval '1 month' - interval '2 days'. If I do the following:
select to_date('11.11.2020', 'dd-mm-yyyyy') + notice_period from providers
I get the following error:
42883] ERROR: operator does not exist: ` text
How can I avoid this error and use the value of notice_period to directly calculate a valid date?
Cheers
Postgres readily converts intervals to strings. I would recommend removing the interval key word and just using ::interval:
select date_trunc('year', now()) + notice_period::interval
from (values ('1 day'), ( '3 month 1 day')) v(notice_period)
If you insist on the interval in the string, then remove it:
+ replace(notice_period, 'interval ', '')::interval
or:
+ substr(notice_period, 10)::interval
Another option is to use dynamic SQL in a function:
CREATE FUNCTION add_period(timestamp with time zone) RETURNS timestamp with time zone
LANGUAGE plpgsql STRICT AS
$$DECLARE
result timestamp with time zone;
BEGIN
EXECUTE format(
'SELECT TIMESTAMP WITH TIME ZONE %L + %s',
$1,
(SELECT notice_period FROM providers)
) INTO result;
RETURN result;
END;$$;
The problem with that is that the whole thing is vulnerable to SQL injection by bad values in the table providers, so use that only if you can trust the source.
This problem cannot be avoided with SQL strings supplied from outside.

how to manage Date interval HOUR in hive

how to manage hour interval in hive, I try this code:
select DATE_SUB(current_timestamp(),INTERVAL '1' HOUR);
error return : Error while compiling statement: FAILED: ParseException line 1:124 cannot recognize input near 'INTERVAL' ''1'' 'HOUR' in select expression
Unfortunately, that is not how date_sub() works in Hive. And unfortunately, it does not simply support interval arithmetic.
So, unix formats to the rescue!
select from_unixtime(unix_timestamp(current_timestamp()) - 3600)
Of course, you don't need to do the conversion to unix time for the current date/time:
select from_unixtime(unix_timestamp() - 3600)

How to run PostgreSQL Query every day with updated values?

New to SQL, but trying to learn/do a job for a friend. I have a query set up that returns the number of bookings for the day. Example snippet:
...
WHERE be.event IN ('instant_approve', 'approve') AND TO_CHAR(be.created_at, 'yyyy-mm-dd') BETWEEN '2017-06-26' AND '2017-06-26';
Now, this query is set up for just today. How can I set this up so that tomorrow the query is executed for '2017-06-27' and so on? Is this possible?
Built-in function now() gives you a timestamp of the beginning of your transaction (CURRENT_TIMESTAMP pseudo-constant is its "alias", a part of SQL standard, but I prefer using the function).
Another function, date_trunc() gives you a "truncated" timestamp and you can choose, how to truncate it. E.g., date_trunc('day', now()) will give you the date/time of beginning of the current day.
Also, you can add or subtract intervals easily, so here is an example that gives you the beginning of the current and the next days:
select
date_trunc('day', now()) cur_day_start,
date_trunc('day', now() + interval '1 day') as next_day_start
;
Also, I would not use to_char() or anything else on top of created_at column -- this will not allow Postgres planner use index on top of this field. If you do want to use to_char(), then consider adding a functional index over to_char(created_at, 'yyyy-mm-dd').
Here is how I would retrieve records generated at July 26, 2017:
where
created_at between '2017-06-26' and '2017-06-27'
(implicit type casting from text to timestamps here)
This is equivalent to
where
created_at >= '2017-06-26'
and created_at <= '2017-06-27'
-- so all timestamps generated for July 26, 2017 will match. And for such query Postgres will use a regular index created for created_at column (if any).
"Dynamic" version:
where
created_at between
date_trunc('day', now())
and date_trunc('day', now()) + interval '1 day'
Use current_date built-in function in the between condition and it will work only for today's bookings.
.........
WHERE be.event IN ('instant_approve', 'approve') AND TO_CHAR(be.created_at, 'yyyy-mm-dd') =current_date;