在 python 中 运行 多个 sql 语句的建议方法?

Suggested way to run multiple sql statements in python?

运行 python 中类似以下内容的建议方法是什么:

self.cursor.execute('SET FOREIGN_KEY_CHECKS=0; DROP TABLE IF EXISTS %s; SET FOREIGN_KEY_CHECKS=1' % (table_name,))

例如,这应该是三个单独的 self.cursor.execute(...) 语句吗?除了 cursor.execute(...) 之外,是否应该使用特定的方法来执行此类操作,或者建议的做法是什么?目前我的代码如下:

self.cursor.execute('SET FOREIGN_KEY_CHECKS=0;')
self.cursor.execute('DROP TABLE IF EXISTS %s;' % (table_name,))
self.cursor.execute('SET FOREIGN_KEY_CHECKS=1;')
self.cursor.execute('CREATE TABLE %s select * from mytable;' % (table_name,))

如您所见,所有内容都是 运行 单独...所以我不确定这是否是个好主意(或者更确切地说 - 执行上述操作的最佳方法是什么) .也许 BEGIN...END ?

MySQLCursor.execute()的文档中,他们建议使用multi=True参数:

operation = 'SELECT 1; INSERT INTO t1 VALUES (); SELECT 2'
for result in cursor.execute(operation, multi=True):
    ...

您可以在模块的源代码中找到 another example

我在项目中多次遇到这类问题。经过大量研究,我发现了一些要点和建议。

  1. execute() 方法适用于一次一个查询。因为在执行方法期间会处理状态。

I know cursor.execute(operation, params=None, multi=True) take multiple query. But parameters does not work well in this case and sometimes internal error exception spoil all results too. And code become massive and ambiguous. Even docs also mention this.

  1. executemany(operation, seq_of_params) 不是每次都实施的好习惯。因为产生一个或多个结果集的操作构成未定义的行为,并且允许(但不是必需)实现在检测到已通过调用该操作创建结果集时引发异常。 [来源 - docs]

建议 1-:

Make a list of queries like -:

table_name = 'test'
quries = [
          'SET FOREIGN_KEY_CHECKS=0;',
          'DROP TABLE IF EXISTS {};'.format(table_name),
          'SET FOREIGN_KEY_CHECKS=1;',
          'CREATE TABLE {} select * from mytable;'.format(table_name),
         ]
for query in quries:
    result = self.cursor.execute(query)
    # Do operation with result

建议 2-:

Set with dict. [you can also make this by executemany for recursive parameters for some special cases.]

quries = [
          {'DROP TABLE IF EXISTS %(table_name);':{'table_name': 'student'}},
          {'CREATE TABLE %(table_name) select * from mytable;': 
           {'table_name':'teacher'}},
          {'SET FOREIGN_KEY_CHECKS=0;': ''}
         ]
for data in quries:
    for query, parameter in data.iteritems():
        if parameter == '':
            result = self.cursor.execute(query)
            # Do something with result
        else:
            result = self.cursor.execute(query, parameter)
            # Do something with result

You can also use split with script. Not recommended

with connection.cursor() as cursor:
    for statement in script.split(';'):
        if len(statement) > 0:
             cursor.execute(statement + ';')

Note -: I use mostly list of query approach but in some complex place use make dictionary approach.

查看 MySQLCursor.execute() 的文档。

它声称您可以传入一个 multi 参数,允许您在一个字符串中 运行 多个查询。

如果 multi 设置为 True,execute() 可以执行操作字符串中指定的多个语句。

multi 是 execute() 调用的可选第二个参数:


operation = 'SELECT 1; INSERT INTO t1 VALUES (); SELECT 2'
for result in cursor.execute(operation, multi=True):

import mysql.connector

你可以执行以下命令,只需要用你自己的标签替换 t1 和剧集

tablename= "t1"
 mycursor.execute("SET FOREIGN_KEY_CHECKS=0; DROP TABLE IF EXISTS {}; SET FOREIGN_KEY_CHECKS=1;CREATE TABLE {} select * from episodes;".format(tablename, tablename),multi=True)

虽然这会 运行,但您必须确保启用后生效的外键限制不会造成问题。

如果 tablename 是用户可以输入的东西,你应该考虑 table 名字的白名单。

准备好的语句不适用于 table 和列名,因此我们必须使用字符串替换来在正确的位置获得正确的 table 名称,这将使您的代码 漏洞sql注入

multi=True 是连接器中 运行 4 个命令所必需的,当我测试它时,调试器要求它。

所有答案都是完全有效的,所以我会添加静态类型和 closing 上下文管理器的解决方案。

from contextlib import closing
from typing import List

import mysql.connector
import logging

logger = logging.getLogger(__name__)


def execute(stmts: List[str]) -> None:
    logger.info("Starting daily execution")

    with closing(mysql.connector.connect()) as connection:
        try:
            with closing(connection.cursor()) as cursor:
                cursor.execute(' ; '.join(stmts), multi=True)
        except Exception:
            logger.exception("Rollbacking changes")
            connection.rollback()
            raise
        else:
            logger.info("Finished successfully")

如果我没记错的话,连接或游标可能不是上下文管理器,这取决于您使用的 mysql 驱动程序的版本,所以这是一个 pythonic 安全解决方案。

  1. 执行脚本() 这是一次执行多个 SQL 语句的便捷方法。它执行作为参数获取的 SQL 脚本。 语法:

    sqlite3.connect.executescript(script)

示例代码:

import sqlite3 

# Connection with the DataBase 
# 'library.db' 
connection = sqlite3.connect("library.db") 
cursor = connection.cursor() 

# SQL piece of code Executed 
# SQL piece of code Executed 
cursor.executescript(""" 
    CREATE TABLE people( 
        firstname, 
        lastname, 
        age 
    ); 

    CREATE TABLE book( 
        title, 
        author, 
        published 
    ); 

    INSERT INTO 
    book(title, author, published) 
    VALUES ( 
        'Dan Clarke''s GFG Detective Agency', 
        'Sean Simpsons', 
        1987 
    ); 
    """) 

sql = """ 
SELECT COUNT(*) FROM book;"""

cursor.execute(sql) 

# The output in fetched and returned 
# as a List by fetchall() 
result = cursor.fetchall() 
print(result) 

sql = """ 
SELECT * FROM book;"""

cursor.execute(sql) 

result = cursor.fetchall() 
print(result) 

# Changes saved into database 
connection.commit() 

# Connection closed(broken) 
# with DataBase 
connection.close() 

输出:

[(1,)] [("丹克拉克的 GFG 侦探社", 'Sean Simpsons', 1987)]

  1. 执行多次() 通常情况下,必须将大量数据从数据文件插入数据库(对于更简单的情况,采用列表、数组)。多次迭代代码比每次都将每一行都写入数据库要简单。但是在这种情况下不适合使用循环,下面的例子说明了原因。下面解释了 executemany() 的语法和用法,以及如何像循环一样使用它:

来源:GeeksForGeeks: SQL Using Python 查看此来源..这有很多适合您的好东西。

我不会依赖 execute 函数的任何 multi=True 参数,它非常依赖于驱动程序,也不会尝试尝试在 ; 字符上拆分字符串,这可能嵌入到字符串文字中。最直接的方法是创建一个函数 execute_multiple,它采用要执行的语句列表和一个 rollback_on_error 参数来确定在任何语句导致异常时要执行的操作.

我对 MySQLdb 和 PyMySQL 的体验是,默认情况下它们从 autocommit=0 开始,换句话说,就好像您已经在一个事务中并且需要显式提交。无论如何,该假设适用于下面的代码。如果不是这种情况,那么您应该 1. 在连接后显式设置 autocommit=0 或 2. 修改此代码以在 try 语句

之后启动事务
def execute_multiple(conn, statements, rollback_on_error=True):
    """
    Execute multiple SQL statements and returns the cursor from the last executed statement.

    :param conn: The connection to the database
    :type conn: Database connection

    :param statements: The statements to be executed
    :type statements: A list of strings

    :param: rollback_on_error: Flag to indicate action to be taken on an exception
    :type rollback_on_error: bool

    :returns cursor from the last statement executed
    :rtype cursor
    """

    try:
        cursor = conn.cursor()
        for statement in statements:
            cursor.execute(statement)
            if not rollback_on_error:
                conn.commit() # commit on each statement
    except Exception as e:
        if rollback_on_error:
            conn.rollback()
        raise
    else:
        if rollback_on_error:
            conn.commit() # then commit only after all statements have completed successfully

您还可以使用一个版本来处理带有参数列表的准备好的语句:

def execute_multiple_prepared(conn, statements_and_values, rollback_on_error=True):
    """
    Execute multiple SQL statements and returns the cursor from the last executed statement.

    :param conn: The connection to the database
    :type conn: Database connection

    :param statements_and_values: The statements and values to be executed
    :type statements_and_values: A list of lists. Each sublist consists of a string, the SQL prepared statement with %s placeholders, and a list or tuple of its parameters

    :param: rollback_on_error: Flag to indicate action to be taken on an exception
    :type rollback_on_error: bool

    :returns cursor from the last statement executed
    :rtype cursor
    """

    try:
        cursor = conn.cursor()
        for s_v in statements_and_values:
            cursor.execute(s_v[0], s_v[1])
            if not rollback_on_error:
                conn.commit() # commit on each statement
    except Exception as e:
        if rollback_on_error:
            conn.rollback()
        raise
    else:
        if rollback_on_error:
            conn.commit() # then commit only after all statements have completed successfully
        return cursor # return the cursor in case there are results to be processed

例如:

cursor = execute_multiple_prepared(conn, [('select * from test_table where count = %s', (2000,))], False)

虽然,诚然,上面的调用只有一个 SQL 带参数的准备语句。

情人见仁见智,因此 最佳 做某事的方法是主观的,除非您明确告诉我们如何衡量。我可以看到三个假设选项:

  1. 使用 MySQLCursor 的 multi 选项(不理想)
  2. 将查询保留在多行中
  3. 将查询保留在一行中

您也可以选择更改查询以避免一些不必要的工作。


关于 multi 选项,MySQL documentation 对此非常清楚

If multi is set to True, execute() is able to execute multiple statements specified in the operation string. It returns an iterator that enables processing the result of each statement. However, using parameters does not work well in this case, and it is usually a good idea to execute each statement on its own.


关于选项 2. 和 3. 这纯粹是您希望如何查看代码的偏好。回想一下,默认情况下,连接对象具有 autocommit=FALSE,因此游标实际上将 cursor.execute(...) 调用分批处理到单个事务中。换句话说,下面的两个版本是等价的。

self.cursor.execute('SET FOREIGN_KEY_CHECKS=0;')
self.cursor.execute('DROP TABLE IF EXISTS %s;' % (table_name,))
self.cursor.execute('SET FOREIGN_KEY_CHECKS=1;')
self.cursor.execute('CREATE TABLE %s select * from mytable;' % (table_name,))

self.cursor.execute(
    'SET FOREIGN_KEY_CHECKS=0;'
    'DROP TABLE IF EXISTS %s;' % (table_name,)
    'SET FOREIGN_KEY_CHECKS=1;'
    'CREATE TABLE %s select * from mytable;' % (table_name,)
)

Python 3.6 引入了超级优雅的 f 弦,如果可以的话你应该使用它们。 :)

self.cursor.execute(
    'SET FOREIGN_KEY_CHECKS=0;'
    f'DROP TABLE IF EXISTS {table_name};'
    'SET FOREIGN_KEY_CHECKS=1;'
    f'CREATE TABLE {table_name} select * from mytable;'
)

请注意,当您开始操作行时,这不再成立;在这种情况下,它变成了特定于查询的,如果相关,您应该进行分析。一个相关的 SO 问题是 What is faster, one big query or many small queries?


最后,使用 TRUNCATE 可能比 DROP TABLE 更优雅,除非您有特殊原因不这样做。

self.cursor.execute(
    f'CREATE TABLE IF NOT EXISTS {table_name};'
    'SET FOREIGN_KEY_CHECKS=0;'
    f'TRUNCATE TABLE {table_name};'
    'SET FOREIGN_KEY_CHECKS=1;'
    f'INSERT INTO {table_name} SELECT * FROM mytable;'
)

我要创建一个存储过程:

DROP PROCEDURE IF EXISTS CopyTable;
DELIMITER $$
CREATE PROCEDURE CopyTable(IN _mytable VARCHAR(64), _table_name VARCHAR(64))
BEGIN
    SET FOREIGN_KEY_CHECKS=0;
    SET @stmt = CONCAT('DROP TABLE IF EXISTS ',_table_name);
    PREPARE stmt1 FROM @stmt;
    EXECUTE stmt1;
    SET FOREIGN_KEY_CHECKS=1;
    SET @stmt = CONCAT('CREATE TABLE ',_table_name,' as select * from ', _mytable);
    PREPARE stmt1 FROM @stmt;
    EXECUTE stmt1;
    DEALLOCATE PREPARE stmt1;
END$$
DELIMITER ;

然后 运行:

args = ['mytable', 'table_name']
cursor.callproc('CopyTable', args)

保持简单和模块化。当然你应该做一些错误检查,你甚至可以让存储过程return一个代码来指示成功或失败。