SQL table name as variable to query - sql

I am using the pgx library to populate a Postgres database in Go.
Following e.g. the tutorial here and this question, I construct my query like so:
// this works
tblinsert = `INSERT into tablename (id, body) VALUES ($1, $2) RETURNING id`
var id string
err := Connector.QueryRow(context.Background(), tblinsert, "value1", "value2").Scan(&id)
Question: I would like to supply the tablename as a variable to the query as well, e.g. change tblinsert to INSERT into $1 (id, body) VALUES ($2, $3)
Issue: the above code errors out and returns a "syntax error at or near "$1" (SQLSTATE 42601)" when I run:
//this errors out
err := Connector.QueryRow(context.Background(), tblinsert, "tablename", "value1", "value2").Scan(&id)`.
I do not fully understand why the error message even references the $ placeholder - I expected the query to do the string substitution here, just like for the VALUES.
I found similar questions in pure SQL here and here, so not sure if this is even possible. .
Any pointers on where I am going wrong, or where I can learn more about the $x syntax (I got the above to work using Sprintf, but this appears discouraged) are much appreciated - I am pretty new to both SQL and Go.

Related

Dynamically change project used in a SQL query

I am using BigQuery, Standard SQL, and I want to dynamically change parts of the FROM clause, such as the project id. I have been looking for a solution for this the last 3 years - the problem has been that parameters cannot be used as inputs in the FROM clause. The benefit would be to create a stored procedure, where the project id can be passed in as an argument and can query the appropriate project. The projects would have the same datasets and table names - this would be our way of building a Master query for easy development and implementation. Instead of changing 15 clients' views, we can change the Stored Procedure once and it will push out the changes to all clients' views. However, I have always gotten hung up on dynamically changing the FROM clause!
For example:
DECLARE ProjectId STRING DEFAULT 'test_project';
SELECT col_1 FROM `#ProjectId.Dataset.Table`;
would always error out due to parameters not being able to be used in the FROM clause. However, I saw a related post on using dynamic SQL to overcome this obstacle. I've been looking into the EXECUTE IMMEDIATE function within BigQuery, as this is what has been cited to be a solution. From that post I attempted to implement in several ways:
Attempt #1:
DECLARE ProjectId STRING DEFAULT 'test_project';
EXECUTE IMMEDIATE CONCAT(
"SELECT * FROM ", #ProjectId, ".DataSet.`Table` " )
^ This gives an error "Query error: Undeclared query parameters at [2:19]"
Attempt #2:
EXECUTE IMMEDIATE CONCAT(
"SELECT * FROM ", #ProjectId, ".DataSet.`Table` " )
USING 'my-project' as ProjectId, 'my-dataset' as DataSet;
^ which gives the error "Query error: Undeclared query parameters at [1:19]"
Third and final attempt was to try declaring the parameter within the EXECUTE IMMEDIATE:
EXECUTE IMMEDIATE CONCAT(
"DECLARE ProjectId STRING DEFAULT 'test_project'; ",
"SELECT * FROM ", #ProjectId, ".DataSet.`Table` " )
USING 'my-project' as ProjectId, 'my-dataset' as DataSet;
^ which, you guessed it, results in the same error "Query error: Undeclared query parameters at [1:19]"
I am reaching out to see if anybody has had success with this? I see the value in the Dynamic SQL statements, and have read the documentation and some examples, but it still doesn't seem to work when trying to dynamically change the FROM clause. Any help is much appreciated, willing to try whatever is thrown out - excited to learn what can be done!
Just remove #:
DECLARE ProjectId STRING DEFAULT 'test_project';
EXECUTE IMMEDIATE CONCAT(
"SELECT * FROM ", ProjectId, ".DataSet.`Table` " )

How correctly pass arguments to the SQL request via the sqlx package in Golang?

In my Golang (1.15) application I use sqlx package to work with the PostgreSQL database (PostgreSQL 12.5).
When I try to execute SQL statement with arguments PostgreSQL database it raises an error:
ERROR: could not determine data type of parameter $1 (SQLSTATE 42P18):
PgError null
According to the official documentation, this error means that an INDETERMINATE DATATYPE was passed.
The organizationId has value. It's not null/nil or empty. Also, its data type is a simple built-in data type *string.
Code snippet with Query method:
rows, err := cr.db.Query(`
select
channels.channel_id::text,
channels.channel_name::text
from
channels
left join organizations on
channels.organization_id = organizations.organization_id
where
organizations.tree_organization_id like concat( '%', '\', $1, '%' );`, *organizationId)
if err != nil {
fmt.Println(err)
}
I also tried to use NamedQuery but it also raise error:
ERROR: syntax error at or near ":" (SQLSTATE 42601): PgError null
Code snippet with NamedQuery method:
args := map[string]interface{}{"organization_id": *organizationId}
rows, err := cr.db.NamedQuery(`
select
channels.channel_id::text,
channels.channel_name::text
from
channels
left join organizations on
channels.organization_id = organizations.organization_id
where
organizations.tree_organization_id like concat( '%', '\', :organization_id, '%' );`, args)
if err != nil {
fmt.Println(err)
}
In all likelihood, the arguments is not passed correctly to my request. Can someone explain how to fix this strange behavior?
P.S. I must say right away that I do not want to form an sql query through concatenation, or through the fmt.Sprintf method. It's not safe.
Well, I found the solution of this problem.
I found the discussion in github repository of the sqlx package.
In the first option, we can make concatenation of our search string outside of the query. This should still be safe from injection attacks.
The second choice to try this: concat( '%', '\', $1::text, '%' ). As Bjarni Ragnarsson said in the comment, PostgreSQL cannot deduce the type of $1.

DB2 PL/SQL structure, begin atomic including with query

I'm trying to use db2 pl/sql script giving a template code.
--#set delimiter !
begin atomic
for S as
<query statement that finds quests, "swaps", with a donor/donation & recipient pair>
do
<update statement that fixes Loot with the swap 'S'>;
end for;
-- handle each swap
end!
-- we're done once through
in my query statement i used something like this:
with
t1 (args) as (
...
)
...
select ...
where ... ;
in the update statement
update Loot set ... where ...
but the problem is, when i try to run the full sql code script on the database, I keep getting the message :
"An unexpected token "begin" was found following "<identifier>".
Expected tokens may include: "USER". SQLSTATE=42601 DB21007E End of file
reached while reading the command.
I want to know, how to use the proper syntax or format to include the "with queries" and also update statement to stop giving me the error. I have the "with query" working in a separate file, but when i combine both statements into the template, it would give me this error. Also as well, if I were to include triggers, which part of the code should i put it in. Thank you.
Here is an example that might help.
CREATE TABLE EG(I INT, J INT)!
INSERT INTO EG VALUES (1,1),(2,3)!
begin
for c as with w(i,u) as(values (2,5)) select i,u from w
do
update eg set j = j + c.u where i = c.i;
end for;
end
!
If this does not help your problem, please post a version of your code that we can run and which returns the error message that you are struggling with.

Postgres could not determine data type of parameter $1 in Golang application

I am creating an application in Golang that uses Postgres using the pq driver. I want to make a function that can select a user-determined field from my database, but I get an error:
pq: could not determine data type of parameter $1
Below is the code that generated this error:
var ifc interface{}
if err := conn.QueryRow("SELECT $1 FROM "+db+" WHERE uuid=$3 OR uri=$4 LIMIT 1", field, UUIDOrURI, UUIDOrURI).Scan(&ifc); err != nil {
if err == sql.ErrNoRows {
return http.StatusNotFound
}
log.Println(err)
return http.StatusInternalServerError
}
Why can I not insert the field that I want to SELECT using $1? Is there another way to do this?
You cannot use placeholders for field names. You'll have to build the query directly, as in:
"SELECT `" + field + "` FROM "
To avoid SQL injections, make sure that the field is part of a list of allowed fields beforehand.
IMHO an easier way, but not safe, to create SQL queries is to use fmt.Sprintf:
query := fmt.Sprintf("SELECT %s FROM %s WHERE uuid=%s", field, db, UUIDOrURI)
if err := conn.QueryRow(query).scan(&ifc); err != nil {
}
You can even specify an argument index:
query := fmt.Sprintf("SELECT %[2]s FROM %[1]s", db, field)
In order to ease the development, I recommend to use a package for the postgresql communication, I tried this one and worked great.

How can I get a null field from the Twitter API to play nice with my database?

Forgive me if I could have any sort of fundamental error here. I'd imagine there's something simple I'm missing. I'm looking to store Twitter updates in a database with only a few fields: an auto-increment index, the time posted, the actual status update & the user id the update is in reply to.
I'm simply storing this last field so I can provide a method of filtering out replies.
But it appears that my SQL code is throwing an error. As of this writing, the SQL properly inserts the two most recent updates, which are both replies to another user and, therefore, have data in the in_reply_to_user_id field. But on the third update, which is not in reply to anyone, I get the following error:
Error Number: 1064
You have an error in your SQL syntax;
check the manual that corresponds to
your MySQL server version for the
right syntax to use near ')' at line 1
INSERT IGNORE INTO updates (time,
status, postid, reply) VALUES
(1260070319, 'I guess Johnny Cash knew
what he was talking about in that \"A
Boy Named Suh\" song. That guy is both
fast and mean.', '6389320556', )
Twitter's API states no default value for this parameter. I tried the same query with the "favorited" parameter, and it correctly labeled each row with "false" in my database. So I'm assuming my problem is with inserting an empty string.
For what it's worth, here is my CodeIgniter method:
function insert_tweet($tweet){
foreach($tweet as $t) {
$when = strtotime($t->created_at);
$status = $t->text;
$postid = $t->id;
$reply = $t->in_reply_to_user_id;
$sql = 'INSERT IGNORE INTO updates (time, status, postid, reply) VALUES (?, ?, ?, ?)';
$this->db->query($sql, array($when, $status, $postid, $reply));
}
}
Any help you could give would be great! I hope I've provided enough information!
More info: I should also note that CodeIgniter is throwing the following error:
A PHP Error was encountered
Severity: 4096
Message: Object of class stdClass
could not be converted to string
Filename: database/DB_driver.php
Line Number: 598
In the off chance that this proves some sort of CI idiosyncrasy to be at fault.
I don't know php but in asp.net style pseudo code i would do this...
$this->db->query($sql, array($when, $status, $postid, string.IsNullOrEmpty($reply) ? null : $reply));
Hopefully you can adapt, just need to deal with the empty parameter.
I don't speak codeigniter, but I can tell you how to fix your problem.
You have two options
Check is the string empty, if so, instead of having a blank value, put in the null keyword.
Check is the value empty, if so, leave that column out of the insert statement's cloumns and values.
Hope this helps
I'm not sure about codeigniter (never used it) but I know that you will need to get the null value to appear as NULL
function insert_tweet($tweet){
foreach($tweet as $t) {
$when = strtotime($t->created_at);
$status = $t->text;
$postid = $t->id;
$reply = (is_null($t->in_reply_to_user_id) ? 'NULL' : $t->in_reply_to_user_id);
$sql = 'INSERT IGNORE INTO updates (time, status, postid, reply) VALUES (?, ?, ?, ?)';
$this->db->query($sql, array($when, $status, $postid, $reply));
}
}
I'm not sure if that will produce the desired SQL:
INSERT IGNORE INTO updates (time, status, postid, reply) VALUES (1260070319, 'I guess Johnny Cash knew what he was talking about in that \"A Boy Named Suh\" song. That guy is both fast and mean.', '6389320556', NULL)
edit Also make sure that the field in the database can be null.
Hope this helps..
Why not alter the last column and make it default to NULL?
When you want to insert some record without this field you just ignore it in the 'INSERT' statement.
And be sure not to put a pending comma between the last column value and the right parenthisis.
:)
You're coming from the CodeIgniter forums right? I think the issue is coming from your Twitter library. Can you post that code? For somereason, it's returning a NULL status as an Object, which it shouldn't do.
Hey guys, the answers here seemed to be close, but no cigar. I got some help from the great folks on the CodeIgniter forums, and here's what I came up with:
$reply = (is_null($t->in_reply_to_user_id) || is_object($t->in_reply_to_user_id) ? ‘NULL’ : $t->in_reply_to_user_id);
Essentially, like Zack said, it is returning the empty field from the Twitter API as an empty object instead of null. So I needed to check if this is either null or an object, then return NULL. And if not, return the user_id string.
Thanks a ton for your help!