【问题标题】:Suggested way to run multiple sql statements in python?在 python 中运行多个 sql 语句的建议方法?
【发布时间】:2020-10-16 11:01:12
【问题描述】:

在 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

【问题讨论】:

  • 告诉我们大局是什么。您列出的内容很不正常;可能有更好的方法来解决“真正的”问题。
  • 另外,如果这是一次性任务,只需在命令行 mysql 中执行即可,不必担心 python 的限制。
  • @RickJames 是的,同意。这是一个日常脚本,用于备份表以进行审计(我们将它们保留十天)。将其放入 DW 或其他任何东西都不够重要,但我们会保留它以用于一些临时查询,以便跟踪跟踪一周的数据,如果需要用于审计目的。
  • 您使用DROP TABLE ... CREATE TABLE 超过TRUNCATE TABLE 的任何特殊原因?

标签: python mysql mysql-python


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

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

    1. execute() 方法一次可以很好地处理一个查询。因为在执行方法期间会处理状态。

    我知道cursor.execute(operation, params=None, multi=True) 接受多个查询。但是在这种情况下参数不能很好地工作,有时内部错误异常也会破坏所有结果。代码变得庞大而模糊。甚至 docs 也提到了这一点。

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

    建议一:

    制作一个查询列表,例如 -:

    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-:

    用字典设置。 [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
    
    

    您还可以将 split 与脚本一起使用。 Not recommended

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

    注意-:我主要使用list of query 方法,但在一些复杂的地方使用make dictionary 方法。

    【讨论】:

      【解决方案3】:

      查看 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):
      

      【讨论】:

        【解决方案4】:

        import mysql.connector

        您可以执行以下命令,只需将 t1 和 episodes 替换为您自己的 tabaes 即可

        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 是用户可以输入的,您应该考虑一个表名白名单。

        Prepared statemnts 不适用于表名和列名,因此我们必须使用字符串替换来在正确的位置获取正确的表名,这会使您的代码 易受攻击 sql注入

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

        【讨论】:

        • 为什么是 import mysql.connector ?我在代码中没有看到。
        • 三个不同的python mysql连接器,这个使用官方的,其他的使用稍微不同的语法..你没有指定你使用的是哪个库,所以我澄清了,,,
        【解决方案5】:

        所有答案都是完全有效的,所以我将使用静态类型和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 安全的解决方案。

        【讨论】:

          【解决方案6】:
          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 侦探社”,“肖恩·辛普森一家”,1987 年)]

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

          来源:GeeksForGeeks: SQL Using Python 看看这个来源..这有很多很棒的东西给你。

          【讨论】:

            【解决方案7】:

            我不会依赖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 准备语句。

            【讨论】:

            • 这是一个很好的答案,感谢您的撰写和示例。
            【解决方案8】:

            美丽在旁观者的眼中,所以做某事的最佳方法是主观的,除非你明确告诉我们如何衡量。我可以看到三个假设选项:

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

            或者,您还可以更改查询以避免一些不必要的工作。


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

            如果 multi 设置为 True,execute() 能够执行操作字符串中指定的多个语句。它返回一个迭代器,可以处理每个语句的结果。但是,在这种情况下使用参数效果不佳,通常最好单独执行每个语句


            关于选项 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;'
            )
            

            【讨论】:

              【解决方案9】:

              我会创建一个存储过程:

              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)
              

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

              【讨论】:

              • 谢谢,这是一个不错的方法。出于好奇,为什么CopyTable 函数需要两个参数? -- 一个是old_table,另一个是new_table,或者它是如何工作的?
              • 是的,完全正确。我在模拟你在做什么,我什至用了和你一样的名字。 'table_name' 不存在,如果存在,它将被删除; 'mytable' 是我们从 [to 'table_name'] 复制的那个。诀窍,如果我们可以这样称呼它,就在 PREPARE 命令中:)
              • 我仍然不清楚some kind of error checking and you could even have the store procedure return a code to indicate success or failure。能否请您详细说明这一点并给我一些参考?
              • 看看本页底部dev.mysql.com/doc/connector-python/en/… 就是一个完美的例子。
              猜你喜欢
              • 1970-01-01
              • 2020-09-17
              • 1970-01-01
              • 1970-01-01
              • 2018-09-27
              • 1970-01-01
              • 1970-01-01
              • 2018-01-19
              • 1970-01-01
              相关资源
              最近更新 更多