Using named placeholders with interval fails in PHP and PostgreSQL - postgresql

Okay I've confirmed this works explicitly with PHP.
$ php --version
PHP 5.6.16 (cli) (built: Dec 30 2015 15:09:50) (DEBUG)
<pdo version>
pdo_pgsql
PDO Driver for PostgreSQL enabled
PostgreSQL(libpq) Version 9.4.0
Module version 1.0.2
Revision $Id: fe003f8ab9041c47e97784d215c2488c4bda724d $
I would like to recreate the following SQL in PHP using PDO:
UPDATE relationships SET status = 4 WHERE created > NOW() - interval '2 seconds';
This script is working:
<?php
$db = new PDO('pgsql:dbname=db;host=localhost;user=stevetauber');
$stmt = $db->prepare("UPDATE relationships SET status = 4 WHERE created > NOW() - interval '?'");
$stmt->execute(array("2 seconds"));
Here it is with named placeholders:
<?php
$db = new PDO('pgsql:dbname=db;host=localhost;user=stevetauber');
$stmt = $db->prepare("UPDATE relationships SET status = 4 WHERE created > NOW() - interval ':blah'");
$stmt->execute(array(":blah" => "2 seconds"));
Which gives this error:
Warning: PDOStatement::execute(): SQLSTATE[HY093]: Invalid parameter number: :blah in ... line 5
Now according to PHP documentation,
Example #6 Invalid use of placeholder:
<?php
$stmt = $dbh->prepare("SELECT * FROM REGISTRY where name LIKE '%?%'");
$stmt->execute(array($_GET['name']));
// placeholder must be used in the place of the whole value
$stmt = $dbh->prepare("SELECT * FROM REGISTRY where name LIKE ?");
$stmt->execute(array("%$_GET[name]%"));
?>
Here is the updated code:
<?php
$db = new PDO('pgsql:dbname=db;host=localhost;user=stevetauber');
$stmt = $db->prepare("UPDATE relationships SET status = 4 WHERE created > NOW() - :blah");
$stmt->execute(array(":blah" => "interval '2 seconds'"));
Which yields these DB errors (no script errors):
ERROR: operator does not exist: timestamp with time zone > interval at character 51
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
STATEMENT: UPDATE relationships SET status = 4 WHERE created > NOW() - $1
PDO is doing something weird here though because:
# select NOW() - interval '2 seconds' as a , pg_typeof(NOW() - interval '2 seconds') as b;
a | b
-------------------------------+--------------------------
2015-12-30 18:02:20.956453+00 | timestamp with time zone
(1 row)
So how do I use named placeholders with PostgreSQL and interval?

Placeholders are for pure values, not for values decorated with units (or with anything else).
To express interval '2 seconds' in a placeholder, there are two options:
in the query, write :secs * interval '1 second'
and bind :secs to a number in php
or write: cast(:mystring as interval), and bind :mystring to the string '2 seconds'. It will be interpreted dynamically through the explicit cast.
When experimenting with the psql command line client to compare with the PDO driver, use the PREPARE and EXECUTE SQL statements with postgres native $N placeholders, as opposed to having the parameters values already written literally in the query. This will match what the PHP driver is essentially doing when PDO::ATTR_EMULATE_PREPARES is set to false.
In the last part of you question, when trying this in psql (your query, just simplified to not need a table):
select now() > now() - interval '2 seconds';
it does work and returns 't' (true).
But if you tried that:
prepare p as select now() > now() - $1;
if would fail with
ERROR: operator does not exist: timestamp with time zone > interval
which is the same error as with PDO's prepare/execute.
On the other hand, this does work:
=> prepare p as select now() > now() - interval '1 second'*$1;
PREPARE
=> execute p(2);
?column?
----------
t

Related

PostgreSQL, Npgsql returning 42601: syntax error at or near "$1"

I'm trying to use Npgsql and/or Dapper to query a table and I keep running into Npgsql.PostgresException 42601: syntax error at or near "$1".
Here is what I've got trying it with NpgsqlCommand:
using (var conn = new NpgsqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["postgres"].ConnectionString))
{
conn.Open();
using (NpgsqlCommand command = new NpgsqlCommand("select * from Logs.Logs where Log_Date > current_date - interval #days day;", conn))
{
command.Parameters.AddWithValue("#days", days);
var reader = command.ExecuteReader();
I've also tried it with Dapper(my preferred method) with:
using (var conn = new NpgsqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["postgres"].ConnectionString))
{
conn.Open();
var logs = conn.Query<Log>("select * from Logs.Logs where Log_Date > current_date - interval #days day;", new {days = days});
Either way I get the same Npgsql.PostgresException 42601: syntax error at or near "$1" error. The Statement in the Exception shows: select * from Logs.Logs where Log_Date > current_date - interval $1 day
Note, if I do the following it works fine, but it's not properly parameterized:
var logs = conn.Query<Log>("select * from Logs.Logs where Log_Date > current_date - interval '" + days + "' day;");
What am I doing wrong? I very much appreciate any feedback. Thank you.
PostgreSQL doesn't allow you to stick a parameter anywhere in a query. What you want can be achieved with the following:
var command = new NpgsqlCommand("select * from Logs.Logs where Log_Date > current_date - #days", conn))
command.Parameters.AddWithValue("#days", TimeSpan.FromDays(days));
This way you're passing the interval directly from Npgsql to PostgreSQL, rather than a part of the expression designed to create that interval.
i got this error using DapperExtensions
adding
DapperExtensions.DapperExtensions.SqlDialect = new PostgreSqlDialect();
DapperAsyncExtensions.SqlDialect = new PostgreSqlDialect();
before creating the connection fixed the issue
To subtract days from a date (assuming log_date is data type date), you can simplify:
"SELECT * FROM logs.logs WHERE log_date > CURRENT_DATE - #days;"
And provide #days as unquoted numeric literal (digits only) - which is taken to be an integer. This is even more efficient, since date - integer returns date, while date - interval returns timestamp.
The manual about interval input.

NHibernate Formula based property + PostgreSQL interval

I'm using NHibernate with Mapping by Code and I have a property that is created by this formula.
Property(x => x.IsInOverdue,
mapper => mapper
.Formula("(SELECT (state_ <> 3 AND invoice_uniqueAlias.duedate_ < NOW()) " +
" FROM _invoice AS invoice_uniqueAlias "+
" WHERE invoice_uniqueAlias.invoice_id = issuedinvoice_key)"));
It works perfectly, this sql is inserted as subselect in all queries...
But I would need to add 1 day to invoice_uniqueAlias.duedate_ value. We are using PostgreSQL where the syntax for it is: invoice_uniqueAlias.duedate_ + interval '1 day'
But when I put it in mapper.Formula, NHibernate thinks that interval is a name of column and in all queries tries to add table prefix before interval keyword. The generated SQL then looks like:
... (SELECT (issuedinvo0_.state_ <> 3
AND (invoice_uniqueAlias.duedate_ + (issuedinvo0_.interval '1 day')) < NOW()) ...
I tried to put interval keyword in [, `, put statement interval + '1 day' to brackets, but it didn't help. Any suggestions how to handle it correctly in NHibernate or how it is possible to write it in Postgres without using + interval syntax?
In case, we need NHibernate to treat some words (key words) as part of the underlying DB engine dialect, we have to just extend it.
One way would be the create custom dialect:
public class CustomPostgreDialect : PostgreSQL82Dialect
{
public CustomPostgreDialect()
{
RegisterKeyword("interval");
}
}
And now just use it:
<property name="dialect">My.Namespace.CustomPostgreDialect,My.Data</property>
Some similar issue - Using SQL CONVERT function through nHibernate Criterion (with the similar solution in this answer)

intersystem cache C# query with datetime

When I use cache sql query in C# I'm getting an error:
SQLtext1 = "SELECT top 10 * FROM dbo.DAPPLICATIONSTAT where TIMESTAMP = '2015-02-01 00:00:00'"
I would like to use a where clause with a datetime filter.
I am using InterSystems.Data.CacheClient.dll to execute the query.
Error Messge :
[SQLCODE: <-4>:<A term expected, beginning with one of the following: identifier, constant, aggregate, %ALPHAUP, %EXACT, %MVR, %SQLSTRING, %SQLUPPER, %STRING, %UPPER, $$, :, +, -, (, NOT, EXISTS, or FOR>]
[Cache Error: <<SYNTAX>errdone+2^%qaqqt>] [Details: <Prepare>]
[%msg: < SQL ERROR #4: A term expected, beginning with either of: (, NOT, EXISTS, or FOR^SELECT top :%qpar(1) * FROM dbo . DAPPLICATIONSTAT where TIMESTAMP>
I think that you have reserved word TIMESTAMP and so, you have that error
Try this SQL query, where filedname TIMESTAMP in dobled quotas
SELECT top 10 * FROM dbo.DAPPLICATIONSTAT where "TIMESTAMP" = '2015-02-01 00:00:00'

Convert a string representing a timestamp to an actual timestamp in PostgreSQL?

In PostgreSQL: I convert string to timestamp with to_timestamp():
select * from ms_secondaryhealthcarearea
where to_timestamp((COALESCE(update_datetime, '19900101010101'),'YYYYMMDDHH24MISS')
> to_timestamp('20121128191843','YYYYMMDDHH24MISS')
But I get this error:
ERROR: syntax error at end of input
LINE 1: ...H24MISS') >to_timestamp('20121128191843','YYYYMMDDHH24MISS')
^
********** Error **********
ERROR: syntax error at end of input
SQL state: 42601
Character: 176
Why? How to convert a string to timestamp?
One too many opening brackets. Try this:
select *
from ms_secondaryhealthcarearea
where to_timestamp(COALESCE(update_datetime, '19900101010101'),'YYYYMMDDHH24MISS') >to_timestamp('20121128191843','YYYYMMDDHH24MISS')
You had two opening brackets at to_timestamp:
where to_timestamp((COA.. -- <-- the second one is not needed!
#ppeterka has pointed out the syntax error.
The more pressing question is: Why store timestamp data as string to begin with? If your circumstances allow, consider converting the column to its proper type:
ALTER TABLE ms_secondaryhealthcarearea
ALTER COLUMN update_datetime TYPE timestamp
USING to_timestamp(update_datetime,'YYYYMMDDHH24MISS');
Or use timestamptz - depending on your requirements.
Another way to convert a string to a timestamp type of PostgreSql is the above,
SELECT to_timestamp('23-11-1986 06:30:00', 'DD-MM-YYYY hh24:mi:ss')::timestamp without time zone;
I had the same requirement as how I read the title. How to convert an epoch timestamp as text to a real timestamp. In my case I extracted one from a json object. So I ended up with a timestamp as text with milliseconds
'1528446110978' (GMT: Friday, June 8, 2018 8:21:50.978 AM)
This is what I tried. Just the latter (ts_ok_with_ms) is exactly right.
SELECT
data->>'expiration' AS expiration,
pg_typeof(data->>'expiration'),
-- to_timestamp(data->>'expiration'), < ERROR: function to_timestamp(text) does not exist
to_timestamp(
(data->>'expiration')::int8
) AS ts_wrong,
to_timestamp(
LEFT(
data->>'expiration',
10
)::int8
) AS ts_ok,
to_timestamp(
LEFT(
data->>'expiration',
10
)::int8
) + (
CASE
WHEN LENGTH(data->>'expiration') = 13
THEN RIGHT(data->>'expiration', 3) ELSE '0'
END||' ms')::interval AS ts_ok_with_ms
FROM (
SELECT '{"expiration": 1528446110978}'::json AS data
) dummy
This is the (transposed) record that is returned:
expiration 1528446110978
pg_typeof text
ts_wrong 50404-07-12 12:09:37.999872+00
ts_ok 2018-06-08 08:21:50+00
ts_ok_with_ms 2018-06-08 08:21:50.978+00
I'm sure I overlooked a simpler version of how to get from a timestamp string in a json object to a real timestamp with ms (ts_ok_with_ms), but I hope this helps nonetheless.
Update: Here's a function for your convenience.
CREATE OR REPLACE FUNCTION data.timestamp_from_text(ts text)
RETURNS timestamptz
LANGUAGE SQL AS
$$
SELECT to_timestamp(LEFT(ts, 10)::int8) +
(
CASE
WHEN LENGTH(ts) = 13
THEN RIGHT(ts, 3) ELSE '0'
END||' ms'
)::interval
$$;

Using CURRENT_TIMESTAMP, arithmetic operator and parameter with Firebird

Why doesn't this work (when parameter is set to 1) :
SELECT * FROM TABLE WHERE TIMESTAMPFIELD > (CURRENT_TIMESTAMP - ?)
But this works :
SELECT * FROM TABLE WHERE TIMESTAMPFIELD > (CURRENT_TIMESTAMP - 1)
I get error message: "conversion error from string "39723.991882951" "
I'm using Firebird 2.1
EDIT:
I found the answer myself with a little help:
SELECT * FROM TABLE WHERE TIMESTAMPFIELD > (CURRENT_TIMESTAMP - CAST(? as DECIMAL(18,9))
Works if the parameter is given as float value.
What do you want to do exactly? Maybe I can be more helpfull with more details.
SELECT * FROM TABLE WHERE TIMESTAMPFIELD > (CURRENT_TIMESTAMP - ?)
How do you set your parameter in your code? Which language do you use?
If you use Delphi, then your parameter should be passed as Float. Ie:
MyQuery.ParamByName('delta').asFloat := 0.1;
Try this and tell us if it's working
HTH