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

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.

Related

HQL / Postgres : EXTRACT(dow from CURRENT_TIMESTAMP) with a parameter

I am trying to implement following query to retrieve users that need to be alerted weekly and send alerts on Monday.
Following query works :
String sql = "SELECT user_id FROM table"
+ " WHERE alert_interval = 7 AND EXTRACT(dow from CURRENT_TIMESTAMP) = 1))";
But if I want to pass a parameter to EXTRACT(dow from CURRENT_TIMESTAMP) like this :
String sql = "SELECT user_id FROM table"
+ " WHERE alert_interval = 7 AND EXTRACT(dow from : currentTs) = 1))";
var args = new MapSqlParameterSource("currentTs", currentTs);
jdbcOperations.query(sql, args, (ResultSet rs) -> ...);
I am getting following error :
nested exception is org.postgresql.util.PSQLException: ERROR: function pg_catalog.date_part(unknown, unknown) is not unique
Hint: Could not choose a best candidate function. You might need to add explicit type casts.
Not sure how I can pass a parameter to EXTRACT(dow from ...) ?
Any pointers would be appreciated.
Thank you.

Tableau Prep: How can I refer to a parameter in the Custom SQL query?

Using Tableau Prep Builder version 2021.4.4 I connected to Postgresql database (version 12) and created a Custom Query as the only input in the Flow.
Then I created a parameter (my_date).
In the custom query I have:
select my_field
from my_table
where date = -- How can I refer to my_date?
I have already tried the following but all failed:
where date = ${my_date}-- syntax error at or near "$"
where date = $my_date -- syntax error at or near "$"
where date = :my_date -- syntax error at or near ":"
where date = (my_date) -- "my_date" does not exist
where date = my_date -- "my_date" does not exist
where date = $1 -- Index 0 out of bounds for length 0
Use < > , for example:
where date = <my_date>
OR
where date = '<my_date>'

postgresql ERROR: syntax error at or near "CONCAT"

i am trying to fetch users list who older than and between 20 to 60 minute randomly. here is my query
SELECT *
FROM t_users
WHERE create_time <= NOW() - INTERVAL CONCAT(floor(random()* (60-20 + 1) + 20),' minutes');
it's giving me error ERROR: syntax error at or near "CONCAT"
You can't use concat() like that to create an interval. The easiest solution is to use make_interval:
WHERE create_time <= NOW() - make_interval(mins => (floor(random()* (60-20 + 1) + 20))::int )

Why does the gorm postgresql throws pq: syntax error at or near ")"?

SELECT_QUERY = `SELECT * FROM events WHERE c_id = ? AND start_time > ? and
end_time < ?`
query := sr.db.Raw(SELECT_QUERY, request.GetCId(), startTime, endTime)
var v = request.GetVIds()
if len(v) > 0 {
query = query.Where(` v_id IN (?) `, v)
} //Only this block introduces first ) after end_time
var c = request.GetStatus().String()
if len(c) > 0 {
query = query.Where( " status = ? ", c) // this introduces the other opening brace //after AND
}
Following is the query generated and found in logs
SELECT * FROM events WHERE c_id = 1 AND start_time > '2020-04-16 18:42:00' and
end_time < '2020-04-16 18:45:50' ) AND ( v_id IN (1,2)) AND ( status = 'STATUS_MIDDLE_CLASS' ORDER BY start_time DESC LIMIT 5 OFFSET 1
The other solution in stackoverflow and internet article doesn't help.
PS: Is it because I mix db.Raw( ) and query.Where() ?
Changing ? to $1 doesn't fix the issue.
Basically a few things fixed the issue.
1) Mixing Raw and query.Where was one issue.
After making the Raw query to sr.db.Where
2)
SELECT_QUERY = `SELECT * FROM events WHERE c_id = ? AND start_time > ? and
end_time < ?`
already has select * from. And then using query := sr.db.Raw(SELECT_QUERY, request.GetCId(), startTime, endTime) introduces nested select *.
So, changed SELECT_QUERY as follows
SELECT_QUERY = `events WHERE c_id = ? AND start_time > ? and
end_time < ?`
solved the issue.
I found a workaround to the error which I received when I tried to add a timestamp filed in Go/Gorm solution with PostgreSQL equivalent to default: now() which is default: time.Now().Format(time.RFC3339)
I received the error because I use AutoMigrate() to create the tables in PostgreSQL. The one problem I found is when trying to use the default value of a function instead of a string (which one can use for a fixed timestamp).
So I had to go into DataGrid (since I use JetBrains, but you can use any PostgreSQL admin tool like pgAdmin) and manually add the timestamp field with default of now() or merely update the existing field to add the default of now(). Then the error goes away when doing your next build in Go.

Using named placeholders with interval fails in PHP and 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