一、对于不同类型的入参,有不同的占位符,但是用多了会发现,%s才是真理
二、sql拼接可以用%s,也可以用format,如果不考虑sql注入风险问题,个人建议使用format,可以接受dict为入参进行匹配。
三、在考虑sql注入风险的情况下,可以将拼接好的sql跟入参分开,调用cur.execute(sql, params)来规避sql注入风险。
需要注意的是,这里有两种使用方式,建议使用第二种:
1、采用sql拼接的方式,拼接时用 xx= %s的方式,入参格式为元组或者list
如下举例:
sql = "select count(*) as sys_num from test where true "
params = []
if param1:
sql += " and param1 = %s "
params.append(param1)
if param2:
sql += " and (param2=%s) "
params.append(param2)
cur.execute(sql, params)
2、采用sql拼接的方式,拼接时用 xx=%(xx)s的方式, 入参格式为dict
如下举例:
sql = "select count(*) as sys_num from test where true "
params = {"param1": "xx", "param2": "xxx"}
if param1:
sql += " and param1 = %(param1)s "
if param2:
sql += " and (param2=%(param2)s) "
cur.execute(sql, params)
四、需要特别注意的是,当你的sql中包含了date_format(date,'%Y-%m')等带%号的时候,有以下几种情况需要注意:
1、如果你将sql完全拼接好(包括入参)再通过只传入sql调用cur.execute(sql,param)方法执行的时候(这里的param默认为None),这里的'%Y-%m'不需要改成'%%Y-%%m'。
如:
sql = """
SELECT * FROM test
WHERE date_time IN (SELECT max(date_time) FROM test)
and date_format(submit_date,'%Y') = date_format('{}','%Y')
GROUP BY status;
""".format(date_time)
cur.execute(sql)
# cur.execute(sql, {})
result = cur.fetchall()
需要注意:上面的cur.execute(sql)如果改成cur.execute(sql,param)并且param不为None, 这时候就需要改成%%。
2、如果你拼接的sql不包括入参(而是通过%s或者其他占位符),再通过传入sql和param来调用cur.execute(sql,param)执行的时候,这里的'%Y-%m'需要改成'%%Y-%%m'。
sql = """
SELECT * FROM test
WHERE date_time IN (SELECT max(date_time) FROM test)
and date_format(submit_date,'%%Y') = date_format(%(date_time)s,'%%Y')
GROUP BY status;
"""
cur.execute(sql, {"date_time": '2019-08-09'})
result = cur.fetchall()
3、为什么呢?通过查看源码发现,cur.execute(sql,param)方法param默认为None,当param为None时,默认你传入的sql是完整的,不进行预编译,所以不需要两个%%来防止被编译。
当param不为None时,默认你的sql是不完整的,需要通过params对你的sql进行匹配获得完整的sql,这时会对sql进行预编译,预编译会对%x等占位符进行处理,这时你的sql中有%Y,%m等不合法的占位符
就会直接报错,所以这种情况下需要使用%%来防止被编译。
五、需要注意的是,当你的sql想动态接收数据库表名或者字段名时,不能使用sql+params调用cur.execute(sql, {"table_name": 'aaaaa'})这种方式,因为pymysql会判断入参类型,如果是字符串的会补上'',表名加''会导致sql执行报错。
如果一定要动态接收表名或者字段名,需要在调用cur.execute()前提前拼接好,可以使用%(table_name)s或者format()。
六、总结:
1、占位符尽量都使用%s
2、建议使用防止sql注入的方式,通过将分开的sql,param作为入参调用cur.execute(sql,param),这里的param可以为元组、列表和字典。
3、使用%%是为了防止被mysql预编译导致报错,cur.execute(sql,param)方法中params默认为None,不进行编译,当params不为None时,就会对你的sql进行预编译(无论你的sql是否已经拼接完整)
这时就需要使用%%来防止被编译。
4、动态接收表名或者字段名不能sql+params调用cur.execute(sql, params)这种方式,需要在调用cur.execute()前提前拼接好,可以使用%(table_name)s或者format()。