问题
I have to write a query to update a record if it exists else insert it. I am doing this update/insert into a postgres DB. I have looked into the upsert examples and most of them use maximum of two fields to update. However, I want to update multiple columns. Example:
query="""INSERT INTO table (col1,col2,col3,col4,col5,col6,col7,col8,col9,..col20) VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
ON CONFLICT(col2) DO UPDATE SET (col1,col2,col3,col4,col5,col6,col7,col8,col9,..col20) VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)"""
In the query above assume that col2 is a unique key, I am inserting and updating the same number of columns. I have to execute this query using pymysql(python library). In a simple insert statement, I know how to pass the tuple containing the parameters dynamically.
cursor.execute(insert_query, data_tuple)
But in this case, I have both places(insert and update) input to be dynamic. Considering the above upsert query, the way I pass the parameters to the cursor
cursor.execute(upsert_query,data_tuple,data_tuple)
However, this one throws up an error with the number of arguments being in the execute function. So how do I pass? Moreover, I am trying to use this way to pass parameters because using the assignment(=) would be a laborious thing to do for 20 columns.
Is there any other alternative way to do this? Like a simple "replace into" statement in mysql.
回答1:
The direct answer to your question is, you do a tuple + tuple
to double the tuple.
cursor.execute(upsert_query, data_tuple + data_tuple)
Other Options:
If you have individual values and you are constructing the tuple, you can directly construct a tuple with twice the number of values.
query="""INSERT INTO table (col1,col2,col3,col4,col5,col6,col7,col8,col9,..col20) VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
ON CONFLICT(col2) DO UPDATE SET col1=%s, col3=%s, col4=%s, ..."""
cur.execute(query, (c1, c2, c3, ... c20, c1, c3, c4, ... c20))
You will have to specify the values (except col2) twice.
If you already have the tuple, which is what you originally asked, then you will use a +
to merge the same tuple twice.
If you have the individual values and not the tuple, you could also use named parameters like a dictionary.
query="""INSERT INTO table (col1,col2,col3,col4...) VALUES(%(c1)s, %(c2)s, %(c3)s, %(c4)s...) ON CONFLICT(col2) DO UPDATE SET col1=%(c1)s, col3=%(c3)s, col4=%(c4)s, ..."""
cur.execute(query, {'c1': c1val, 'c2': c2val, 'c3': c3val, 'c4': c4val, ...})
This form is good for readability, passes parameters only once, and is easy to maintain (increase in columns, etc) if the number of columns change in the future.
回答2:
EDIT 2
So, after few exchanges : your problem seems to be how to use the cursor.execute function in pymysql. This is the link for the appropriate documentation : https://pymysql.readthedocs.io/en/latest/modules/cursors.html
I never code in python but the documentation seems quite precise on the execute method usage :
execute(query, args=None)
Execute a query
Parameters:
query (str) – Query to execute.
args (tuple, list or dict) – parameters used with query. (optional)
Returns:
Number of affected rows
Return type:
int
If args is a list or tuple, %s can be used as a placeholder in the query. If args is a dict, %(name)s can be used as a placeholder in the query.
So maybe with a 'dict' type it is possible, but I do not think it is the philosophy of it.
Original post
I am quite not sure of what exactly you want to say by 'both places input to be dynamic', so I will put down some SQL here an do not hesitate if you have any question after :)
First a small initialization
CREATE TABLE test
(
id int,
value_1 varchar,
value_2 bit
);
ALTER TABLE test
ADD CONSTRAINT ck_test UNIQUE(id, value_1, value_2);
INSERT INTO test
VALUES
(1, 'test', cast(1 as bit))
, (2, 'test_2', cast(0 as bit));
Second the error
INSERT INTO test
VALUES
(1, 'test', cast(1 as bit));
Third the UPSERT
INSERT INTO test
VALUES
(1, 'test', cast(1 as bit))
ON CONFLICT ON CONSTRAINT ck_test
DO
UPDATE
SETid = 3, value_1 = 'error';
Is that answering your question? Or is it more a string building problem?
EDIT So, I am not fond of alternative languages so I will put this in plpgsql:
do language plpgsql $$
declare
query varchar;
id_insert int;
value_1_insert varchar;
value_2_insert bit;
id_update int;
value_1_update varchar;
value_2_update bit;
begin
id_insert := 4;
value_1_insert := 'test';
value_2_insert := cast(1 as bit);
id_update := id_insert;
value_1_update := 'error';
value_2_update := cast(0 as bit);
query := 'INSERT INTO test
VALUES
(
cast('||id_insert||' as int)
, '''||value_1_insert||'''
, cast('||value_2_insert||' as bit)
)
ON CONFLICT ON CONSTRAINT ck_test
DO
UPDATE
SET
id = cast('||id_update||' as int)
, value_1 = '''||value_1_update||'''
, value_2 = cast('||value_2_update||' as bit);';
execute query;
end;
$$;
Hope this helps ;)
来源:https://stackoverflow.com/questions/55922003/update-multiple-columns-on-conflict-postgres