使用Python进行多个SQL插入

时间:2012-10-22 22:33:50

标签: python mysql mysql-python

更新
根据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转换器,但这对我来说也是一个学习练习。 有更好的方式吗?

4 个答案:

答案 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行:

  • execute():“%s”的200倍(=取决于行数)
  • executemany():仅是“%s”的2倍(=与行数无关)。

此解决方案有可能以@PengjuZhao的回答速度很高,而不会冒着注入攻击的危险。

  1. 准备函数的参数:

您将把值存储在一维NumPy数组arr_namearr_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'
  1. 获取带有占位符的sql查询:

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)
  1. 执行strSqlQuery

最后一步:

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语句中时,才会发生注入攻击,如果我输入错误,请发表评论。