I am using PyGreSQL to execute queries against my DB. I need to insert the values to be matched at runtime, but the parameters are not being into the query correctly. If my query is:
SELECT *
FROM Database
WHERE job_level = ANY(:job_level)
AND job_family = ANY(:job_family)
AND cost_center = ANY(:cost_center)
And I wish to use parameters
{'cost_center': '*',
'job_family': 'SDE',
'job_level': ['5', '6', '4', '7'],
}
How do I correctly insert these values via
cursor.execute(query, parameters)
I have tried:
Not using the ANY operator, but just using IN. This fails with
ERROR: syntax error at or near ":"
LINE 4: WHERE job_level IN :job_level
Using %(job_level)s syntax instead of the :job_level. This fails with
ProgrammingError: ERROR: syntax error at or near "ANY"
LINE 4: WHERE job_level IN ANY(ARRAY['5','6','4','7'...
So how do I properly combine the placeholders in the query with the parameters to match all three cases here, a single value, a list of values, and a wildcard matching an entire column?
2
Answers
Through much trial and error, I have found the correct combination of placeholders and parameter formats to match all my use cases: single values, wildcards for an entire column, and lists of individual values. This may not be the only solution, but it works.
Placeholders in the query should be formatted using %()s syntax, inside the ANY operator:
Single and multi-value parameters need to be formatted as lists (for single values, that's probably a side effect of using ANY). For wildcard matches, you can use the name of the column itself, just as with the IN operator, but this column name must be wrapped in the
pgdb.Literal()
function. Thus, to match my example selection criteria:The complete parameter dictionary for my example should be formatted as:
Which can be safely executed with
cursor.execute(query, parameters)
This will allow matching of single values, multiple values, and wildcard matches.
The following example code shows how to do this with PyGreSQL.
Some remarks upfront:
database
. You should rename it, becausedatabase
is a keyword in SQL, and database objects are different from table objects, so it can be confusing to read such SQL. In the example I called the tablejobs
.*
as wildcard, but SQL uses%
instead. You can also use regular expressions in PostgreSQL, but then you need to write.*
instead of*
. They also have a different syntax. See Pattern Matching for the available options. In the example I used%
as pattern withLIKE
.*
(%
) matches any value, you can also simply remove that comparison from the WHERE clause in this case.Example using the DB API 2 of PyGreSQL
Example using the classic API of PyGreSQL
Using a parameter tuple instead of a dict
Both APIs also allow passing parameters as tuples instead of dicts. But dicts are more descriptive, allow reusing parameters in several places in the query, and you do not need to care about the order of the parameters. So I recommend using tuples only for very simple queries.
When using tuples, the
query_string
and theparameters
should loke like this:Using bind parameters directly
The classic API also allows using unformatted (raw) queries with bind parameters
$1
,$2
etc. directly as they are written in PostgresQL.For simple data types this works just like passing tuples as above, but other data types like arrays need to be adapted manually. This can be done as follows:
However, the recommended usage is with formatted querys, where PyGreSQL automatically adapts input and typecasts output.
In most cases, like when passing a list, PyGreSQL can guess the corresponding type in the database, like array in this case. For more exotic data types, you must specify the database type of the parameters explicitly in the
adapt
orquery_formatted
methods. Another way to adapt parameters is wrapping them as type objects, which is also supported in the DB API 2.