如何在Python的SQL语句中使用变量?

How to use variables in SQL statement in Python?

提问人:user111606 提问时间:5/24/2009 最后编辑:snakecharmerbuser111606 更新时间:9/8/2023 访问量:474080

问:

我有以下 Python 代码:

cursor.execute("INSERT INTO table VALUES var1, var2, var3,")

其中是整数,是字符串。var1var2var3

如何在没有 Python 的情况下编写变量名称作为查询文本的一部分?

蟒蛇 SQL

评论


答:

158赞 Ayman Hourieh 5/24/2009 #1
cursor.execute("INSERT INTO table VALUES (%s, %s, %s)", (var1, var2, var3))

请注意,参数是作为元组传递的。如果要传递单个参数,则元组需要以逗号结尾。(a, b, c)(a,)

数据库 API 对变量进行适当的转义和引用。注意不要使用字符串格式运算符 (),因为%

  1. 它不会进行任何转义或引用。
  2. 它容易受到不受控制的字符串格式攻击,例如SQL注入

评论

0赞 Andomar 5/24/2009
有趣的是,为什么它单独使用 var 而不是在数组 (var1,var2,var3) 中工作?
0赞 Ayman Hourieh 5/24/2009
根据DB API规范,它看起来可以是以下任何一种方式:python.org/dev/peps/pep-0249
11赞 Ayman Hourieh 2/11/2014
@thekashyap 请仔细阅读。不安全的是使用字符串格式运算符。事实上,我在答案中是这么说的。%
3赞 eric 12/8/2019
投了反对票,因为答案说不使用,但使用了三次。更多的解释会很棒。%
10赞 Mark Ransom 8/8/2020
@eric答案说不要使用运算符来格式化字符串。字符串中的那些被直接使用,并且由于它知道它正在生成 SQL,因此它可以做更多的事情来保护你。%%cursor.execute
28赞 Numlock 5/24/2009 #2

http://www.amk.ca/python/writing/DB-API.html

当您简单地将变量的值附加到语句中时要小心: 想象一下,一个用户给自己起名字—— 这就是为什么你需要使用 SQL 转义,当你以体面的方式使用时,Python 会为你提供转义。URL 中的示例是:';DROP TABLE Users;'cursor.execute

cursor.execute("insert into Attendees values (?, ?, ?)", (name, seminar, paid))

评论

14赞 S.Lott 5/24/2009
实际上,它不是 SQL 转义。它是变量绑定,它更简单、更直接。这些值在解析后绑定到 SQL 语句中,使其不受任何注入攻击。
1赞 Charles Duffy 3/13/2020
好吧,无论是SQL转义还是变量绑定取决于数据库服务器/ DB-API驱动程序的好坏。我见过一些真实世界的、广泛部署的生产数据库,它们的 DB-API 驱动程序只是进行转义,而不是将数据和代码保持在网络上的带外。毋庸置疑,我对那些所谓的“数据库”不太尊重。
89赞 Alex Martelli 5/24/2009 #3

Python DB-API 的不同实现允许使用不同的占位符,因此您需要找出您正在使用的占位符 - 它可能是(例如使用 MySQLdb):

cursor.execute("INSERT INTO table VALUES (%s, %s, %s)", (var1, var2, var3))

或者(例如,使用 Python 标准库中的 sqlite3):

cursor.execute("INSERT INTO table VALUES (?, ?, ?)", (var1, var2, var3))

或其他(在您可能拥有 或“命名样式”之后,或者您将字典而不是映射作为第二个参数传递给 )。检查您正在使用的数据库 API 模块中的字符串常量,并在 http://www.python.org/dev/peps/pep-0249/ 中查找 paramstyle,看看所有参数传递样式是什么!VALUES(:1, :2, :3)(:fee, :fie, :fo)(%(fee)s, %(fie)s, %(fo)s)executeparamstyle

68赞 Kashyap 2/13/2014 #4

很多方式。不要在实际代码中使用最明显的一个(与),它很容易受到攻击%s%

这里是从 sqlite3 的 pydoc 复制粘贴的:

...谨防使用 Python 的字符串操作来组装查询,因为它们容易受到 SQL 注入攻击。例如,攻击者只需关闭单引号并注入 OR TRUE 即可选择所有行:

# Never do this -- insecure!
symbol = input()

sql = "SELECT * FROM stocks WHERE symbol = '%s'" % symbol
print(sql)

cur.execute(sql)

如果需要,请提供更多示例:

# Multiple values single statement/execution
c.execute('SELECT * FROM stocks WHERE symbol=? OR symbol=?', ('RHAT', 'MSO'))
print c.fetchall()
c.execute('SELECT * FROM stocks WHERE symbol IN (?, ?)', ('RHAT', 'MSO'))
print c.fetchall()
# This also works, though ones above are better as a habit as it's inline with syntax of executemany().. but your choice.
c.execute('SELECT * FROM stocks WHERE symbol=? OR symbol=?', 'RHAT', 'MSO')
print c.fetchall()
# Insert a single item
c.execute('INSERT INTO stocks VALUES (?,?,?,?,?)', ('2006-03-28', 'BUY', 'IBM', 1000, 45.00))

评论

12赞 ThatAintWorking 11/25/2014
一些 DB-API 实现实际上使用 %s 作为其变量 - 最明显的是 PostgreSQL 的 psycopg2。不要将这与使用 %s 和 % 运算符进行字符串替换混淆(尽管它很容易混淆)。如果为了便携性,我们可以有一个定义的标准方法来指定 DB-API 的 SQL 参数,我会很高兴。
7赞 snakecharmerb 11/22/2020 #5

对于没有经验的 Python 用户来说,提供单个值的语法可能会令人困惑。

给定查询

INSERT INTO mytable (fruit) VALUES (%s)

通常*,即使值本身是单例,传递给的值也必须包装在有序序列中,例如元组或列表,因此我们必须提供单个元素元组,如下所示: .cursor.execute(value,)

cursor.execute("""INSERT INTO mytable (fruit) VALUES (%s)""", ('apple',))

传递单个字符串

cursor.execute("""INSERT INTO mytable (fruit) VALUES (%s)""", ('apple'))

将导致错误,该错误因 DB-API 连接器而异,例如

  • psycopg2:

    TypeError:并非所有参数在字符串格式设置期间转换

  • SQLITE3的

    sqlite3 中。ProgrammingError:提供的绑定数不正确。当前语句使用 1,并提供了 5

  • mysql.连接器

    mysql.connector.errors.ProgrammingError: 1064 (42000):SQL语法中有错误;


* pymysql 连接器处理单个字符串参数而不会出错。但是,最好将字符串包装在元组中,即使它是单个,因为

  • 如果切换连接器包,则无需更改代码
  • 将查询参数的一致思维模型保持为一系列对象,而不是单个对象。
0赞 AdrianBR 8/16/2023 #6

将您的数据加载为自动规范化表,我建议使用这个库来推断模式、键入数据并具有模式演变 https://pypi.org/project/dlt/

你甚至可以使用这个库在之后对你的结构化数据进行更新插入,下面是一个例子,我们使用 json 中的 id 来更新在目标位置生成的 sql 表

data = [{'id': 1, 'name': 'John'}]

# open connection
pipe = dlt.pipeline(destination='postgres',
                    dataset_name='raw_data')

# Upsert/merge: Update old records, insert new
# Capture the outcome in load info
load_info = pipe.run(data,
                      write_disposition="merge",
                      primary_key="id",
                      table_name="users")