更新
根据Nathan的建议传递execute()行列表后,代码执行得更远,但仍然卡在执行函数上。错误消息显示为:
query = query % db.literal(args)
TypeError: not all arguments converted during string formatting
所以它仍然不起作用。有人知道为什么现在有类型错误吗?
END UPDATE
我有一个.xls格式的大型邮件列表。我使用python和xlrd从xls文件中检索名称和电子邮件到两个列表。现在我想把每个名字和电子邮件放到一个mysql数据库中。我正在使用MySQLdb这部分。显然我不想为每个列表项做一个insert语句 这是我到目前为止所拥有的。
from xlrd import open_workbook, cellname
import MySQLdb
dbname = 'h4h'
host = 'localhost'
pwd = 'P@ssw0rd'
user = 'root'
book = open_workbook('h4hlist.xls')
sheet = book.sheet_by_index(0)
mailing_list = {}
name_list = []
email_list = []
for row in range(sheet.nrows):
"""name is in the 0th col. email is the 4th col."""
name = sheet.cell(row, 0).value
email = sheet.cell(row, 4).value
if name and email:
mailing_list[name] = email
for n, e in sorted(mailing_list.iteritems()):
name_list.append(n)
email_list.append(e)
db = MySQLdb.connect(host=host, user=user, db=dbname, passwd=pwd)
cursor = db.cursor()
cursor.execute("""INSERT INTO mailing_list (name,email) VALUES (%s,%s)""",
(name_list, email_list))
光标执行时的问题。这是错误:_mysql_exceptions.OperationalError: (1241, 'Operand should contain 1 column(s)')
我最初尝试将我的查询放入var中,但之后它只是关于将元组传递给execute()的消息。
我做错了什么?这甚至可能吗?
列表很大,我绝对无法将插入放入循环中。我看着使用LOAD DATA INFILE,但我真的不明白如何格式化文件或查询,当我必须阅读MySQL文档时,我的眼睛流血。我知道我可能可能会使用一些在线xls到mysql转换器,但这对我来说也是一个学习练习。 有更好的方式吗?
答案 0 :(得分:16)
您需要为executemany()
提供行列表。您不需要中断名称并通过电子邮件发送到单独的列表中,只需创建一个包含其中两个值的列表。
rows = []
for row in range(sheet.nrows):
"""name is in the 0th col. email is the 4th col."""
name = sheet.cell(row, 0).value
email = sheet.cell(row, 4).value
rows.append((name, email))
db = MySQLdb.connect(host=host, user=user, db=dbname, passwd=pwd)
cursor = db.cursor()
cursor.executemany("""INSERT INTO mailing_list (name,email) VALUES (%s,%s)""", rows)
更新:正如@JonClements所提到的,它应该是executemany()
而不是execute()
。
答案 1 :(得分:7)
要修复TypeError: not all arguments converted during string formatting
- 您需要使用cursor.executemany(...)
方法,因为它接受可迭代的元组(多行),而cursor.execute(...)
期望参数为单个行值。
执行命令后,您需要确保使用db.commit()
提交事务以使更改在数据库中处于活动状态。
答案 2 :(得分:2)
如果您对代码的高性能感兴趣,这个答案可能会更好。
与excutemany
方法相比,下面的execute
会更快:
INSERT INTO mailing_list (name,email) VALUES ('Jim','jim@yahoo.com'),('Lucy','Lucy@gmail.com')
您可以轻松修改@Nathan Villaescusa的答案并获取新代码。
cursor.execute("""INSERT INTO mailing_list (name,email) VALUES (%s)""".format(",".join(str(i) for i in rows))
这是我自己的测试结果:
excutemany:10000 runs takes 220 seconds
execute:10000 runs takes 12 seconds.
速度差异约为15倍。
答案 3 :(得分:0)
采用@PengjuZhao的想法,应该为所有要传递的值简单地添加一个占位符。与@PengjuZhao答案不同的是,这些值作为第二个参数传递给execute()函数,该函数应该是注入攻击安全的,因为它仅在运行时才被评估(与“ .format()”相反)。>
cursor.execute("""INSERT INTO mailing_list (name,email) VALUES (%s)""", ",".join(str(i) for i in rows))
仅当这无法正常工作时,请尝试以下方法。
####
@PengjuZhao的答案表明, executemany()具有强大的Python开销,或者在不需要的地方使用了多个execute()语句 ,否则executemany()会不会比单个execute()语句慢很多。
这是一个将NathanVillaescusa和@PengjuZhao的答案放在单个execute()方法中的函数。
该解决方案构建了动态数量的占位符,该占位符将添加到sql语句中。这是一个带有多个“%s”占位符的手动构建的execute()语句,它的性能可能优于executemany()语句。
例如,在2列处插入100行:
此解决方案有可能以@PengjuZhao的回答速度很高,而不会冒着注入攻击的危险。
您将把值存储在一维NumPy数组arr_name
和arr_email
中,然后将它们转换成连接值列表,逐行转换。或者,您可以使用@NathanVillaescusa的方法。
from itertools import chain
listAllValues = list(chain([
arr_name.reshape(-1,1), arr_email.reshape(-1,1)
]))
column_names = 'name, email'
table_name = 'mailing_list'
numRows = int((len(listAllValues))/numColumns)
只是避免传递行数。显然,如果在listAllValues的2列中插入6个值,那么将使6/2 = 3行。
def getSqlInsertMultipleRowsInSqlTable(table_name, column_names, listAllValues):
numColumns = len(column_names.split(","))
numRows = int((len(listAllValues))/numColumns)
placeholdersPerRow = "("+', '.join(['%s'] * numColumns)+")"
placeholders = ', '.join([placeholdersPerRow] * numRows)
sqlInsertMultipleRowsInSqlTable = "insert into `{table}` ({columns}) values {values};".format(table=table_name, columns=column_names, values=placeholders)
return sqlInsertMultipleRowsInSqlTable
strSqlQuery = getSqlInsertMultipleRowsInSqlTable(table_name, column_names, listAllValues)
最后一步:
db = MySQLdb.connect(host=host, user=user, db=dbname, passwd=pwd)
cursor = db.cursor()
cursor.execute(strSqlQuery, listAllValues)
该解决方案希望不会像@PengjuZhao的答案那样具有注入攻击的风险,因为它仅使用占位符而不是值来填充sql语句。此时,此处仅在listAllValues
中单独传递值,其中strSqlQuery
仅具有占位符而不是值:
cursor.execute(strSqlQuery, listAllValues)
execute()语句使用占位符%s 和 值列表 中的 sql语句两个单独的参数,就像在@NathanVillaescusa的答案中所做的那样。 我仍然不确定这是否可以避免注入攻击。据我了解,只有将值直接放在sql语句中时,才会发生注入攻击,如果我输入错误,请发表评论。