mysqlpython-3.xpymysql

is there a bug in pymysql.cursor.executemany()


I met an error when I run

import datetime
import json
import pymysql

config={
    'host':...,
    'user':...,
    'password':...,
    'database':...,
    'charset':'utf8mb4',
    'cursorclass':pymysql.cursors.DictCursor,
}
with pymysql.connect(**config) as conn:
    with conn.cursor() as cur:
        sql="""
            create table if not exists test(
                route VARCHAR(8),
                direction INT,
                timestamp DATETIME,
                stations JSON,
                PRIMARY KEY (route, direction)
            )
            """
        cur.execute(sql, )
        now=datetime.datetime.now()
        j=json.dumps({})
        ret1={
            'route': 'N2',
            'direction': 0,
            'timestamp': now,
            'stations' : j,
        }
        ret2={
            'route': 'N1',
            'direction': 0,
            'timestamp': now,
            'stations' : j,
        }
        sql="""
            insert into test
            values ( %(route)s, %(direction)s, %(timestamp)s, %(stations)s )
            on duplicate key update timestamp=%(timestamp)s , stations=%(stations)s
            """

        # These two lines will runs as expected,
        # cur.execute(sql, ret1)         
        # conn.commit()
        # but these two will raise an error.
        cur.executemany(sql, [ret1,ret2]) 
        conn.commit()

The error message is like

pymysql.err.ProgrammingError: (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 '%(timestamp)s , stations=%(stations)s' at line 3")

Is this a bug of pymysql or my mistake?

pymysql version "1.1.1"
python version "3.12.8"
mysql version "8.0.41-0ubuntu0.24.04.1 for Linux on x86_64 ((Ubuntu))"


Solution

  • Don't use placeholders in the ON DUPLICATE KEY UPDATE clause. executemany() gets its benefit in INSERT statements by duplicating the VALUES list with each dictionary in the parameters list, so the query looks like:

    INSERT INTO test
    VALUES (...), (...)
    

    But you can't replicate the ON DUPLICATE UPDATE clause similarly, so it leaves the placeholders in the query, which results in a syntax error.

    Instead, you can tell MySQL to gets the values to update from the VALUES clause automatically.

    sql="""
        insert into test
        values ( %(route)s, %(direction)s, %(timestamp)s, %(stations)s ) AS new
        on duplicate key update timestamp=new.timestamp, stations=new.stations
        """
    

    If you need to support MySQL 5.x, use VALUES(columnName) instead of new.columnName.