Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Build a dynamic update query in psycopg2

I have to construct a dynamic update query for postgresql. Its dynamic, because beforehand I have to determine which columns to update.

Given a sample table:

create table foo (id int, a int, b int, c int)

Then I will construct programmatically the "set" clause

_set = {}
_set['a'] = 10
_set['c'] = NULL

After that I have to build the update query. And here I'm stuck. I have to construct this sql Update command:

update foo set a = 10, b = NULL where id = 1

How to do this with the psycopg2 parametrized command? (i.e. looping through the dict if it is not empty and build the set clause) ?

UPDATE

While I was sleeping I have found the solution by myself. It is dynamic, exactly how I wanted to be :-)

create table foo (id integer, a integer, b integer, c varchar)

updates = {}
updates['a'] = 10
updates['b'] = None
updates['c'] = 'blah blah blah'
sql = "upgrade foo set %s where id = %s" % (', '.join("%s = %%s" % u for u in updates.keys()), 10)
params = updates.values()
print cur.mogrify(sql, params)
cur.execute(sql, params)

And the result is what and how I needed (especially the nullable and quotable columns):

"upgrade foo set a = 10, c = 'blah blah blah', b = NULL where id = 10"
like image 423
Gabor Avatar asked Jan 13 '16 18:01

Gabor


3 Answers

Using psycopg2.sql – SQL string composition module

The module contains objects and functions useful to generate SQL dynamically, in a convenient and safe way.

from psycopg2 import connect, sql

conn = connect("dbname=test user=postgres")

upd = {'name': 'Peter', 'age': 35, 'city': 'London'}
ref_id = 12

sql_query = sql.SQL("UPDATE people SET {data} WHERE id = {id}").format(
    data=sql.SQL(', ').join(
        sql.Composed([sql.Identifier(k), sql.SQL(" = "), sql.Placeholder(k)]) for k in upd.keys()
    ),
    id=sql.Placeholder('id')
)
upd.update(id=ref_id)
with conn:
    with conn.cursor() as cur:
        cur.execute(sql_query, upd)
conn.close()

Running print(sql_query.as_string(conn)) before closing connection will reveal this output:

UPDATE people SET "name" = %(name)s, "age" = %(age)s, "city" = %(city)s WHERE id = %(id)s
like image 155
Sergius Flavius Avatar answered Oct 02 '22 06:10

Sergius Flavius


There is actually a slightly cleaner way to make it, using the alternative column-list syntax:

sql_template = "UPDATE foo SET ({}) = %s WHERE id = {}"
sql = sql_template.format(', '.join(updates.keys()), 10)
params = (tuple(addr_dict.values()),)
print cur.mogrify(sql, params)
cur.execute(sql, params)
like image 31
Rmatt Avatar answered Oct 02 '22 07:10

Rmatt


No need for dynamic SQL. Supposing a is not nullable and b is nullable.

If you want to update both a and b:

_set = dict(
    id = 1,
    a = 10,
    b = 20, b_update = 1
)
update = """
    update foo
    set
        a = coalesce(%(a)s, a), -- a is not nullable
        b = (array[b, %(b)s])[%(b_update)s + 1] -- b is nullable
    where id = %(id)s
"""
print cur.mogrify(update, _set)
cur.execute(update, _set)

Output:

update foo
set
    a = coalesce(10, a), -- a is not nullable
    b = (array[b, 20])[1 + 1] -- b is nullable
where id = 1

If you want to update none:

_set = dict(
    id = 1,
    a = None,
    b = 20, b_update = 0
)

Output:

update foo
set
    a = coalesce(NULL, a), -- a is not nullable
    b = (array[b, 20])[0 + 1] -- b is nullable
where id = 1
like image 43
Clodoaldo Neto Avatar answered Oct 02 '22 06:10

Clodoaldo Neto