【问题标题】:Python SQL query string formattingPython SQL 查询字符串格式化
【发布时间】:2011-07-11 17:52:27
【问题描述】:

我正在尝试找到格式化 sql 查询字符串的最佳方式。当我调试时 我的应用程序我想记录所有的 sql 查询字符串,它是 字符串格式正确很重要。

选项 1

def myquery():
    sql = "select field1, field2, field3, field4 from table where condition1=1 and condition2=2"
    con = mymodule.get_connection()
    ...
  • 这对于打印 sql 字符串很有用。
  • 如果字符串很长并且不符合标准宽度,这不是一个好的解决方案 80 个字符。

选项 2

def query():
    sql = """
        select field1, field2, field3, field4
        from table
        where condition1=1
        and condition2=2"""
    con = mymodule.get_connection()
    ...
  • 这里的代码很清楚,但是当你打印 sql 查询字符串时,你会得到所有这些烦人的空格。

    u'\nselect field1, field2, field3, field4\n_____从表中\n_ ___where condition1=1 \n_____and condition2=2'

注意:我已将空格替换为下划线_,因为它们被编辑器修剪了

选项 3

def query():
    sql = """select field1, field2, field3, field4
from table
where condition1=1
and condition2=2"""
    con = mymodule.get_connection()
    ...
  • 我不喜欢这个选项,因为它破坏了表格代码的清晰性。

选项 4

def query():
    sql = "select field1, field2, field3, field4 " \
          "from table " \
          "where condition1=1 " \
          "and condition2=2 "
    con = mymodule.get_connection()    
    ...
  • 我不喜欢这个选项,因为每行都需要额外输入 并且也很难编辑查询。

对我来说,最好的解决方案是 Option 2,但我不喜欢打印 sql 字符串时的多余空格。

你知道其他选择吗?

【问题讨论】:

  • 这就是 Psycopg 人所说的对查询字符串组合的一种天真的方法,例如使用字符串连接 - initd.org/psycopg/docs/… 。而是使用查询参数来避免 SQL 注入攻击,并自动将 Python 对象与 SQL 文字转换。 stackoverflow.com/questions/3134691/…
  • 这个问题其实并不是专门针对SQL查询的,而是普遍适用于Python中多行字符串的格式化。应该删除 SQL 标记。
  • 选项 2 - 三引号 使用内置的 textwrap.dedent 输出到 sql 文件或调试日志记录。

标签: python sql string-formatting


【解决方案1】:

这是@aandis 答案的略微修改版本。当涉及到原始字符串时,在字符串之前添加前缀 'r' 字符。例如:

sql = r"""
    SELECT field1, field2, field3, field4
      FROM table
     WHERE condition1 = 1
       AND condition2 = 2;
"""

当您的查询有任何特殊字符(如“\”)需要转义并且 flake8 等 lint 工具将其报告为错误时,建议使用此方法。

【讨论】:

    【解决方案2】:

    使用 'sqlparse' 库我们可以格式化 sqls。

    >>> import sqlparse
    >>> raw = 'select * from foo; select * from bar;'
    >>> print(sqlparse.format(raw, reindent=True, keyword_case='upper'))
    SELECT *
    FROM foo;
    
    SELECT *
    FROM bar;
    

    参考:https://pypi.org/project/sqlparse/

    【讨论】:

      【解决方案3】:

      您可以使用inspect.cleandoc 很好地格式化您打印的 SQL 语句。

      这非常适合您的选项 2

      注意:print("-"*40) 仅用于在不使用 cleandoc 时演示多余的空行。

      from inspect import cleandoc
      def query():
          sql = """
              select field1, field2, field3, field4
              from table
              where condition1=1
              and condition2=2
          """
      
          print("-"*40)
          print(sql)
          print("-"*40)
          print(cleandoc(sql))
          print("-"*40)
      
      query()
      

      输出:

      ----------------------------------------
      
              select field1, field2, field3, field4
              from table
              where condition1=1
              and condition2=2
      
      ----------------------------------------
      select field1, field2, field3, field4
      from table
      where condition1=1
      and condition2=2
      ----------------------------------------
      

      来自docs

      inspect.cleandoc(doc)

      从缩进的文档字符串中清除缩进以与代码块对齐。

      从第一行删除所有前导空格。任何可以从第二行开始统一删除的前导空格都会被删除。随后删除开头和结尾处的空行。此外,所有选项卡都扩展为空格。

      【讨论】:

        【解决方案4】:

        很抱歉在这么老的帖子上发帖——但作为一个也对 pythonic 'best' 充满热情的人,我想我会分享我们的解决方案。

        解决方案是使用 python 的 String Literal Concatenation (http://docs.python.org/) 构建 SQL 语句,它可以在选项 2 和选项 4 之间进行限定

        代码示例:

        sql = ("SELECT field1, field2, field3, field4 "
               "FROM table "
               "WHERE condition1=1 "
               "AND condition2=2;")
        

        也适用于 f-strings

        fields = "field1, field2, field3, field4"
        table = "table"
        conditions = "condition1=1 AND condition2=2"
        
        sql = (f"SELECT {fields} "
               f"FROM {table} "
               f"WHERE {conditions};")
        

        优点:

        1. 它保留了 pythonic 的“良好制表”格式,但不添加无关的空格字符(这会污染日志记录)。
        2. 它避免了选项 4 的反斜杠延续丑陋,这使得添加语句变得困难(更不用说空白盲区了)。
        3. 此外,在 VIM 中展开语句非常简单(只需将光标定位到插入点,然后按 SHIFT-O 即可打开新行)。

        【讨论】:

        • 如果这是用于打印,我认为更好的选择是将其编写为带有""" 的多行字符串并在输出前使用textwrap.dedent()
        • 我玩过那个选项,但它也使日志输出多行。在跟踪 db chatty 应用程序时,这会导致大量输出。
        • 这是一个旧线程,但我一直使用这种格式作为最佳实践,但是较长的查询会变得乏味
        • 我们不应该总是使用双引号"sql query" 来避免与SQL 字符串混淆(标准使用单引号)吗?
        • 如何格式化源代码是一个有效的问题,但在 SQL 的上下文中,确实应该突出地提到对变量进行正确转义更为重要。使用字符串格式准备 SQL 语句是不安全
        【解决方案5】:

        我遇到的最干净的方式是受到sql style guide 的启发。

        sql = """
            SELECT field1, field2, field3, field4
              FROM table
             WHERE condition1 = 1
               AND condition2 = 2;
        """
        

        基本上,以子句开头的关键字应该右对齐,字段名称等应该左对齐。这看起来很整洁,也更容易调试。

        【讨论】:

        • 我建议在原始 sql 字符串前加上 'r' 前缀。请在下面的答案中查看详细信息。
        【解决方案6】:

        为了避免完全格式化,我认为一个很好的解决方案是使用procedures

        调用过程会为您提供您想要的任何查询的结果以放入此过程。您实际上可以在一个过程中处理多个查询。该调用将只返回上次调用的查询

        MYSQL

        DROP PROCEDURE IF EXISTS example;
         DELIMITER //
         CREATE PROCEDURE example()
           BEGIN
           SELECT 2+222+2222+222+222+2222+2222 AS this_is_a_really_long_string_test;
           END //
         DELIMITER;
        
        #calling the procedure gives you the result of whatever query you want to put in this procedure. You can actually process multiple queries within a procedure. The call just returns the last query result
         call example;
        

        Python

        sql =('call example;')
        

        【讨论】:

          【解决方案7】:
          sql = ("select field1, field2, field3, field4 "
                 "from table "
                 "where condition1={} "
                 "and condition2={}").format(1, 2)
          
          Output: 'select field1, field2, field3, field4 from table 
                   where condition1=1 and condition2=2'
          

          如果条件的值应该是一个字符串,你可以这样做:

          sql = ("select field1, field2, field3, field4 "
                 "from table "
                 "where condition1='{0}' "
                 "and condition2='{1}'").format('2016-10-12', '2017-10-12')
          
          Output: "select field1, field2, field3, field4 from table where
                   condition1='2016-10-12' and condition2='2017-10-12'"
          

          【讨论】:

          • 请永远不要这样做。这叫做SQL注入,非常危险。几乎每个 Python 数据库库都提供了使用参数的工具。如果您发现自己使用带有 SQL 字符串的 format(),这是一种主要的代码异味。
          • 我认为我们不能使用它,你必须在使用它之前验证参数,并且你应该知道你传递了什么。
          • 验证比仅使用where condition1=:field1 然后将值作为参数传递更容易出错。如果您使用.format(),将有一种方法可以将';DROP TABLE Users 弹出到您的SQL 中。查看 PEP-249 以了解如何正确使用参数。 python.org/dev/peps/pep-0249/#paramstyle
          【解决方案8】:

          对于可以放在一两行的简短查询,我使用上面投票最多的解决方案中的字符串文字解决方案。对于更长的查询,我将它们分解为.sql 文件。然后我使用包装函数来加载文件并执行脚本,例如:

          script_cache = {}
          def execute_script(cursor,script,*args,**kwargs):
              if not script in script_cache:
                  with open(script,'r') as s:
                      script_cache[script] = s
              return cursor.execute(script_cache[script],*args,**kwargs)
          

          当然,这通常存在于类中,因此我通常不必明确传递cursor。我通常也使用codecs.open(),但这可以理解总体思路。然后 SQL 脚本完全自包含在自己的文件中,并带有自己的语法高亮显示。

          【讨论】:

            【解决方案9】:
            sql = """\
            select field1, field2, field3, field4
            from table
            where condition1=1
            and condition2=2
            """
            

            [编辑回复评论]
            在方法中包含 SQL 字符串并不意味着您必须“制表”它:

            >>> class Foo:
            ...     def fubar(self):
            ...         sql = """\
            ... select *
            ... from frobozz
            ... where zorkmids > 10
            ... ;"""
            ...         print sql
            ...
            >>> Foo().fubar()
            select *
            from frobozz
            where zorkmids > 10
            ;
            >>>
            

            【讨论】:

            • IMO 这与 Option_2 相同
            • @ssoler:您的 Option_2 在 all 行上有前导空格;请注意,您的示例省略了 select 之前的前导空格。我的答案没有前导空格。是什么让您认为它们是相同的?
            • 如果你把你的 sql 字符串放在一个方法中,你将不得不把所有的行都制成表格(Option_2)。一种可能的解决方案是 Option_3。
            • @ssoler:对不起,我不明白这句话。请看我更新的答案。
            • 您更新的答案是我的 Option_3,不是吗?我不喜欢这个选项,因为它破坏了表格代码的清晰性。
            【解决方案10】:

            我建议坚持使用选项 2(我总是将其用于比 SELECT * FROM table 更复杂的查询),如果您想以一种好的方式打印它,您可以始终使用 separate module

            【讨论】:

              【解决方案11】:

              您显然已经考虑了很多编写 SQL 的方法,这样它就可以打印出来了,但是如何更改用于调试日志记录的“打印”语句,而不是以您不喜欢的方式编写 SQL?使用上面你最喜欢的选项,这样的日志记录功能怎么样:

              def debugLogSQL(sql):
                   print ' '.join([line.strip() for line in sql.splitlines()]).strip()
              
              sql = """
                  select field1, field2, field3, field4
                  from table"""
              if debug:
                  debugLogSQL(sql)
              

              如果行长于所需长度,添加额外的逻辑以将记录的字符串拆分为多行也变得很简单。

              【讨论】:

                【解决方案12】:

                您可以将字段名称放入数组“fields”中,然后:

                
                sql = 'select %s from table where condition1=1 and condition2=2' % (
                 ', '.join(fields))
                

                【讨论】:

                • 如果您的条件列表增长,您可以使用 ' 和 '.join(conditions) 来做同样的事情
                • 使用您的解决方案,查询将比使用 Option_4 更难编辑,而且也难以阅读。
                • @ssoler,这取决于一个人如何做事。我在我的程序中声明了一些变量,并使用字符串数组代替,这使得上述方法非常有用并且至少对我来说是可维护的。
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-09-09
                • 1970-01-01
                • 2019-11-28
                • 2011-07-28
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多