Postgres: Python: TypeError: SQL.__init__() takes 2 positional arguments but 3 were given - postgresql

Hello I am getting error from my code, can someone help me please?
def query_builder(self, field_name, table_name, pkey, id):
queryx=sql.SQL("select {field} from {table} where {pkey} = %s",(id)).format(
field=sql.Identifier(field_name),
table=sql.Identifier(table_name),
pkey=sql.Identifier(pkey))
self.cur.execute(queryx.as_string(self.conn))

I'm going to assume you are using psycopg2.
If so the issues are, first:
"select {field} from {table} where {pkey} = %s",(id) ..."
Do not include the argument (id) in the string. Also this is not proper form for a single value in a tuple. Python requires it be (id,), note the comma.
Second:
self.cur.execute(queryx.as_string(self.conn))
Should be:
self.cur.execute(queryx, (id,))
The execute is where you supply the argument. Also the composable sql.SQL(...) can be passed directly to execute without being run through as_string. See here sql for more examples.
UPDATE
To use "*" there are two ways:
cur.execute(sql.SQL("select * from {table} where {pkey} = %s).format(table.sql.Identifier(table_name), pkey=sql.Identifier(pkey))
--OR
cur.execute(sql.SQL("select {field} from {table} where {pkey} = %s).format(field=sql.SQL("*"), table=sql.Identifier(table_name), pkey=sql.Identifier(pkey))
Warning, the second does allow for SQL injection as sql.SQL() does not escape values.
As to multiple fields the sql section of the docs has multiple examples. For instance:
If part of your query is a variable sequence of arguments, such as a comma-separated list of field names, you can use the SQL.join() method to pass them to the query:
query = sql.SQL("select {fields} from {table}").format(
fields=sql.SQL(',').join([
sql.Identifier('field1'),
sql.Identifier('field2'),
sql.Identifier('field3'),
]),
table=sql.Identifier('some_table'))

Related

How to pick up data from row and put it into tPostgresqlInput?

I have a requets which giving me an ids. I need to iterate them into another request, so I have a sheme like this: scheme
In tPostgresqlInput I have this code rc.id = upper('18ce317b-bf69-4150-b880-2ab739eab0fe') , but instead of id I need to put smthn like globalMap.get(row4.id). How did I do this?
Apparently this is a syntax issue
Try with :
"select * FROM table LEFT JOIN table on parameter JOIN table on parameter
WHERE 1=1 AND
column = 'content'
AND upper(rc.id) = upper('"+((String)globalMap.get("row4.id")) +"')"
Expressions in tDBInput should always begin and end with double quotes.
Don't forget to cast globalMap.get() with the type of your element (here I put String)
.equals is not a DB function but a java function. I have replaced it with '='
Let me know if it's better

How to pass array as a parameter for rowMode="array" in pg-promise

I would like to get the result of a query using rowMode="array" (as this is a potentially very large table and I don't want it formatted to object format) but I couldn't figure out how to pass in a array/list parameter for use in an IN operator.
const events = await t.manyOrNone({text: `select * from svc.events where user_id in ($1:list);`, rowMode: "array"}, [[1,2]]);
However, the above gives an error: syntax error at or near ":"
Removing the :list did not work either:
const events = await t.manyOrNone({text: `select * from svc.events where user_id in ($1);`, rowMode: "array"}, [[1,2]]);
Error: invalid input syntax for integer: "{"1","2"}"
I understand that this might be because I'm forced to use ParameterizedQuery format for rowMode="array" which does not allow those snazzy modifiers like :list, but this then leads to the question, if I were to use ParameterizedQuery format, then how do I natively pass in a Javascript array so that it is acceptable to the driver?
I guess an alternative formulation to this question is: how do I use arrays as parameters for ParameterizedQuery or PreparedStatements...
Answering my own question as I eventually found an answer to this issue: how to pass in arrays as params for use in the IN operator when using rowMode="array" | ParameterizedQuery | PreparedStatements.
Because this query is being parameterized in the server, we cannot use the IN operator, because the IN operator parameterize items using IN ($1, $2, $3...). Instead we need to use the ANY operator, where ANY($1) where for $1 an array is expected.
So the query that will work is:
const events = await t.manyOrNone({text: `select * from svc.events where user_id=ANY($1);`, rowMode: "array"}, [[1,2]]);

Is there a way to make Delphi's FireDAC recognize PostgreSQL positional parameters that FireDAC generated?

I am executing queries with named parameters from FireDAC to PostgreSQL 11 using the native FireDAC Postgres driver. During the prepare statement FireDAC converts the named parameters to positional parameters, which is correct. However, if I then attempt to assign values to those parameters FireDAC throws an "Argument out of range" exception. It appears that FireDAC does not recognize the positional parameters that it generated. For example, if the original SQL text looked something like this:
SELECT * FROM account WHERE accountid = :accid;
upon calling the Prepare method of the FDQuery, FireDAC converted this query into this:
SELECT * FROM account WHERE accountid = $1;
But when I try to assign the value to the parameter, I get the error. The assignment looks something like this:
FDQuery1.Params[0].AsString = strID;
where strID is a string value and accountid is a text field. Furthermore, if I use something like the following, it returns 0.
ShowMessage( IntToStr( FDQuery1.Params.Count ) );
I've simplified this code significantly, but the issues are the same. How do I get FireDAC to recognize the positional parameters that it generated?
Update : As I mentioned, the above code is greatly simplified. What is actually happening is that in our framework we have one set of routines that assign values to FireDAC macros, and then we generate the SQL statement by preparing the query and then reading the FDQuery's Text property. That SQL statement then gets assigned to the SQL.Text property of another FDQuery (also dynamically created), and it is there that the query fails. So, here is a very simple example of what is happening internally in the code:
var
Query: TFDQuery;
begin
Query := TFDQuery.Create( nil );
Query.Connection := PGConnection;
// In reality, the SQL statement below was generated earlier,
// from a function call where the SQL was created by the FireDAC
// SQL preprocessor, as opposed to being a literal expression
Query.SQL.Text := 'SELECT * FROM tablename WHERE field2 = $1;';
Query.Params[0].AsString := '4'; // BANG! Argument out of range
I thought it might be due to FireDAC macro expansion, so I added the following two lines after instantiating the FDQuery:
Query.ResourceOptions.MacroCreate := False;
Query.ResourceOptions.MacroExpand := False;
Nope. That did help either. I am guessing that FireDAC simply doesn't recognize that $1 is a valid positional parameter in PostgreSQL.
You need to put the symbolic parameter identifier (in your case :accid) in the SQL.Text string, not the positional code ($1)
I just tested these two variations. The first works, the second doesn't.
var
MyQ : tfdquery;
begin
MyQ := Tfdquery.Create(nil);
MyQ.Connection := dm1.dbMain;
MyQ.SQL.Text := 'Select * from person where lastname = :lname;';
MyQ.Params[0].AsString := 'Brodzinsky';
MyQ.Open();
ShowMessage('Records found = '+MyQ.RecordCount.ToString);
MyQ.Close;
MyQ.Free;
end;
The next tries to use positional. Of course, FireDac doesn't see a colon, so doesn't know there is a parameter to create
var
MyQ : tfdquery;
begin
MyQ := Tfdquery.Create(nil);
MyQ.Connection := dm1.dbMain;
MyQ.SQL.Text := 'Select * from person where lastname = $1;';
MyQ.Params[0].AsString := 'Brodzinsky';
MyQ.Open();
ShowMessage('Records found = '+MyQ.RecordCount.ToString);
MyQ.Close;
MyQ.Free;
end;
In the first, 11 records in the person table are returned with my last name; in the second, the Argument Out Of Range error is generated, since there is no parameter specified in the SQL text. Note: I am accessing a MySQL database, but the issue here is Delphi & FireDac preprocessing of the code to send to the db server.
Ok, there may be a way to fix this issue using FireDAC properties, but I haven't found it yet. However, for this particular situation, where the SQL is prepared in one method, and then assigned to a different FDQuery from within another method, I have found an answer. Since PostgreSQL permits named parameters to use numerals, such as :1, what I did was to replace the $ characters with : characters. As in
var
SQLStmt: String;
FDQuery: TFDQuery;
begin
SQLStmt := 'SELECT * FROM tablename WHERE field2 = $1;';
FDQuery := TFDQuery.Create( nil );
FDQuery.Connection := FDConnection1;
FDQuery.SQL.Text := SQLStmt.Replace( '$', ':' );
FDQuery.Params[0].AsString := 'SomeValue'); // Works!
...
And, yes, if your query includes more than one instance of a named parameter, FireDAC replaces it with the same numeral.
If I find another solution I will post it. If someone else comes up with a solution using FireDAC properties, I will select that answer as the correct answer.

How to get only specific rows on DB, when date range fits SQL condition on a 'tsrange' datatype? [duplicate]

I have this query:
some_id = 1
cursor.execute('
SELECT "Indicator"."indicator"
FROM "Indicator"
WHERE "Indicator"."some_id" = %s;', some_id)
I get the following error:
TypeError: 'int' object does not support indexing
some_id is an int but I'd like to select indicators that have some_id = 1 (or whatever # I decide to put in the variable).
cursor.execute('
SELECT "Indicator"."indicator"
FROM "Indicator"
WHERE "Indicator"."some_id" = %s;', [some_id])
This turns the some_id parameter into a list, which is indexable. Assuming your method works like i think it does, this should work.
The error is happening because somewhere in that method, it is probably trying to iterate over that input, or index directly into it. Possibly like this: some_id[0]
By making it a list (or iterable), you allow it to index into the first element like that.
You could also make it into a tuple by doing this: (some_id,) which has the advantage of being immutable.
You should pass query parameters to execute() as a tuple (an iterable, strictly speaking), (some_id,) instead of some_id:
cursor.execute('
SELECT "Indicator"."indicator"
FROM "Indicator"
WHERE "Indicator"."some_id" = %s;', (some_id,))
Your id needs to be some sort of iterable for mogrify to understand the input, here's the relevant quote from the frequently asked questions documentation:
>>> cur.execute("INSERT INTO foo VALUES (%s)", "bar") # WRONG
>>> cur.execute("INSERT INTO foo VALUES (%s)", ("bar")) # WRONG
>>> cur.execute("INSERT INTO foo VALUES (%s)", ("bar",)) # correct
>>> cur.execute("INSERT INTO foo VALUES (%s)", ["bar"]) # correct
This should work:
some_id = 1
cursor.execute('
SELECT "Indicator"."indicator"
FROM "Indicator"
WHERE "Indicator"."some_id" = %s;', (some_id, ))
Slightly similar error when using Django:
TypeError: 'RelatedManager' object does not support indexing
This doesn't work
mystery_obj[0].id
This works:
mystery_obj.all()[0].id
Basically, the error reads Some type xyz doesn't have an __ iter __ or __next__ or next function, so it's not next(), or itsnot[indexable], or iter(itsnot), in this case the arguments to cursor.execute would need to implement iteration, most commonly a List, Tuple, or less commonly an Array, or some custom iterator implementation.
In this specific case the error happens when the classic string interpolation goes to fill the %s, %d, %b string formatters.
Related:
How to implement __iter__(self) for a container object (Python)
Pass parameter into a list, which is indexable.
cur.execute("select * from tableA where id =%s",[parameter])
I had the same problem and it worked when I used normal formatting.
cursor.execute(f'
SELECT "Indicator"."indicator"
FROM "Indicator"
WHERE "Indicator"."some_id" ={some_id};')
Typecasting some_id to string also works.
cursor.execute(""" SELECT * FROM posts WHERE id = %s """, (str(id), ))

Psycopg2 functions in parameters

I'm wonder how I can pass a Postgres function to a field in an insert statement in Psycopg2. Looked everywhere in the doc but can't find the answer.
The code:
From the DBconn class:
def insert(self, table, data):
fields = data.keys()
sql = "INSERT INTO " + table + "("
sql += ",".join(fields) + ") VALUES (" + ",".join(["%s"]*len(fields)) + ");"
self.cur.execute(sql, data.values())
And when calling this function:
db.insert("tblpeople", {"person_name":"john", "org_id":"currval('tblorganisations_org_id_seq')"})
So the question is how can I tell Psycopg2 that the org_id value is a Postgres function?
Tnx!
Your code is producing the statement:
INSERT INTO tblpeople (org_id,person_name) VALUES (%s,%s);
and you are passing 2 arguments. That won't work. You want to produce this code:
INSERT INTO tblpeople (org_id,person_name)
VALUES (currval('tblorganisations_org_id_seq'),%s);
and then pass a single argument. I don't see any way of doing that with your current data structure. If you add another piece of data to your dictionary, like:
{
"text":{"person_name":"john"},
"function":{"org_id":"currval('tblorganisations_org_id_seq')"}
}
and change your constructor to produce the proper query based upon the 'function' key, or, you could 'hard code' your query builder to recognize the 'currval(' being at the beginning of the value. Anyway, the basic problem is you are passing the data as a string argument which won't be handled correctly.
-g