【问题标题】:How do I get a raw, compiled SQL query from a SQLAlchemy expression?如何从 SQLAlchemy 表达式中获取原始的编译 SQL 查询?
【发布时间】:2011-06-04 18:36:32
【问题描述】:

我有一个 SQLAlchemy 查询对象,想要获取已编译的 SQL 语句的文本,并绑定了所有参数(例如,没有 %s 或其他等待语句编译器或 MySQLdb 方言引擎绑定的变量等) .

在查询中调用 str() 会显示如下内容:

SELECT id WHERE date_added <= %s AND date_added >= %s ORDER BY count DESC

我尝试查看 query._params 但它是一个空字典。我使用this example of the sqlalchemy.ext.compiler.compiles decorator 编写了自己的编译器,但即使那里的语句仍然有%s 我想要数据的地方。

我不太清楚我的参数何时混入创建查询;检查查询对象时,它们始终是一个空字典(尽管查询执行得很好,并且当您打开回显日志时引擎会将其打印出来)。

我开始收到 SQLAlchemy 不希望我知道底层查询的消息,因为它破坏了所有不同 DB-API 的表达式 API 接口的一般性质。我不介意查询是否在我发现它是什么之前被执行;我只是想知道!

【问题讨论】:

    标签: python sql mysql sqlalchemy


    【解决方案1】:

    这应该适用于 Sqlalchemy >= 0.6

    from sqlalchemy.sql import compiler
    
    from psycopg2.extensions import adapt as sqlescape
    # or use the appropiate escape function from your db driver
    
    def compile_query(query):
        dialect = query.session.bind.dialect
        statement = query.statement
        comp = compiler.SQLCompiler(dialect, statement)
        comp.compile()
        enc = dialect.encoding
        params = {}
        for k,v in comp.params.iteritems():
            if isinstance(v, unicode):
                v = v.encode(enc)
            params[k] = sqlescape(v)
        return (comp.string.encode(enc) % params).decode(enc)
    

    【讨论】:

    • 谢谢!可悲的是,我使用的是 MySQL,所以我的方言是“定位的”,需要有一个参数列表而不是字典。目前正试图让你的例子与它一起工作..
    • 请不要以这种方式使用adapt。每次至少调用它的返回值的 prepare(),将连接作为参数提供,因此它可以进行正确的引用。
    • @Alex:正确引用 psycopg 的正确方法是什么? (除了在返回值上调用 prepare(),您似乎暗示这不是最佳的)
    • 对不起,我认为我的措辞不好,只要您调用 obj.prepare(connection) 就可以了。这是因为 libpq 为引用提供的“好”API 需要连接(并且它提供了诸如 unicode 字符串编码之类的东西)。
    • 谢谢。我试过在返回值上调用prepare,但似乎它没有那个方法:AttributeError: 'psycopg2._psycopg.AsIs' object has no attribute 'prepare'。我正在使用 psycopg2 2.2.1 BTW
    【解决方案2】:

    问题是,sqlalchemy 从不将数据与您的查询混合。查询和数据分别传递给您的底层数据库驱动程序 - 数据的插值发生在您的数据库中。

    Sqlalchemy 将您在 str(myquery) 中看到的查询传递给数据库,这些值将放在单独的元组中。

    您可以使用某种方法,自己用查询插入数据(如下 albertov 建议的那样),但这与 sqlalchemy 正在执行的事情不同。

    【讨论】:

    • 为什么不是同一个东西?我了解 DB-API 正在执行事务,可能对查询进行重新排序等,但它对我的查询的修改是否不止于此?
    • @cce:您正在尝试查找最终查询。 SELECT id WHERE date_added &lt;= %s AND date_added &gt;= %s ORDER BY count DESC IS 是最终查询。那些%s 由 sqlalchemy 发送到数据库 - sqlalchemy 永远不会将实际数据放在 %s
    • @cce:一些 dbapi 模块也不这样做——这通常由数据库本身完成
    • aha 我明白你在说什么,谢谢 — 进一步挖掘 sqlalchemy.dialects.mysql.mysqldbdo_executemany() 将语句和参数分别传递给 MySQLdb 游标。耶间接!
    【解决方案3】:

    对于 MySQLdb 后端,我稍微修改了 albertov 的绝妙答案(非常感谢!)。我确信它们可以合并以检查 comp.positional 是否为 True 但这稍微超出了这个问题的范围。

    def compile_query(query):
        from sqlalchemy.sql import compiler
        from MySQLdb.converters import conversions, escape
    
        dialect = query.session.bind.dialect
        statement = query.statement
        comp = compiler.SQLCompiler(dialect, statement)
        comp.compile()
        enc = dialect.encoding
        params = []
        for k in comp.positiontup:
            v = comp.params[k]
            if isinstance(v, unicode):
                v = v.encode(enc)
            params.append( escape(v, conversions) )
        return (comp.string.encode(enc) % tuple(params)).decode(enc)
    

    【讨论】:

    • 太棒了!我只需要将绑定参数列表发送到 MySQL 并将上面的修改为 return tuple(params) 就像一个魅力!你为我节省了无数小时不得不走上一条极其痛苦的道路。
    【解决方案4】:

    This 博客提供了更新的答案。

    引用博客文章,这是建议并为我工作。

    >>> from sqlalchemy.dialects import postgresql
    >>> print str(q.statement.compile(dialect=postgresql.dialect()))
    

    其中 q 定义为:

    >>> q = DBSession.query(model.Name).distinct(model.Name.value) \
                 .order_by(model.Name.value)
    

    或者只是任何一种session.query()

    感谢 Nicolas Cadou 的回答!我希望它可以帮助其他来这里搜索的人。

    【讨论】:

    • 有没有一种简单的方法可以将值作为字典获取?
    • @Damien 给定c = q.statement.compile(...),你可以得到c.params
    • 该帖子被标记为 mysql,因此此答案中的 postgresql 详细信息并不真正相关。
    • 如果我正确理解了 OP,他想要最终的查询。使用指定方言(此处为 postgres)进行打印仍会为 me 提供占位符而不是文字值。 @Matt 的回答完成了这项工作。使用as_scalar()-方法Query 可以更简单地获取带有占位符的SQL。
    • @PatrickB。我同意。马特的答案应该被认为是“正确”的答案。我通过 str(q) 得到与此相同的结果。
    【解决方案5】:

    我认为 .statement 可能会解决问题: http://docs.sqlalchemy.org/en/latest/orm/query.html?highlight=query

    >>> local_session.query(sqlalchemy_declarative.SomeTable.text).statement
    <sqlalchemy.sql.annotation.AnnotatedSelect at 0x6c75a20; AnnotatedSelectobject>
    >>> x=local_session.query(sqlalchemy_declarative.SomeTable.text).statement
    >>> print(x)
    SELECT sometable.text 
    FROM sometable
    

    【讨论】:

    • 声明不会显示参数是什么,如果您设置了某些类型的过滤器。
    【解决方案6】:

    您可以使用来自ConnectionEvents 家族的事件:after_cursor_executebefore_cursor_execute

    在 @zzzeek 的 sqlalchemy UsageRecipes 中,您可以找到以下示例:

    Profiling
    
    ...
    @event.listens_for(Engine, "before_cursor_execute")
    def before_cursor_execute(conn, cursor, statement,
                            parameters, context, executemany):
        conn.info.setdefault('query_start_time', []).append(time.time())
        logger.debug("Start Query: %s" % statement % parameters)
    ...
    

    在这里您可以访问您的声明

    【讨论】:

      【解决方案7】:

      documentation 使用 literal_binds 打印包含参数的查询 q

      print(q.statement.compile(compile_kwargs={"literal_binds": True}))
      

      上述方法有一个警告,它仅支持基本类型,例如整数和字符串,此外,如果直接使用没有预设值的 bindparam(),它将无法将其字符串化要么。

      文档也发出此警告:

      永远不要对从不受信任的字符串内容使用此技术 输入,例如来自 Web 表单或其他用户输入应用程序。 SQLAlchemy 将 Python 值强制转换为直接 SQL 字符串的工具 值对于不受信任的输入是不安全的,并且不验证 传递的数据类型。始终使用绑定参数 以编程方式针对关系调用非 DDL SQL 语句 数据库。

      【讨论】:

      • 谢谢!这非常有帮助,让我可以轻松地使用 pandas read_sql 函数!
      • @JustinPalmer 熊猫不接受查询吗?是否接受Core表达式?
      • Core 表达式怎么做?
      • 不幸的是,literal_binds 方法无法将 python bytes 数据类型与已编译的 SQL 语句绑定。在这种情况下,您可能需要提前将bytes 转换为十六进制字符串,然后弄清楚如何使用数据库支持的一些内置函数来传递十六进制字符串
      【解决方案8】:

      以下解决方案使用 SQLAlchemy 表达式语言并适用于 SQLAlchemy 1.1。该解决方案没有将参数与查询混合(按照原作者的要求),而是提供了一种使用 SQLAlchemy 模型为不同 SQL 方言生成 SQL 查询字符串和参数字典的方法。示例基于教程http://docs.sqlalchemy.org/en/rel_1_0/core/tutorial.html

      鉴于班级,

      from sqlalchemy import Column, Integer, String
      from sqlalchemy.ext.declarative import declarative_base
      Base = declarative_base()
      class foo(Base):
          __tablename__ = 'foo'
          id = Column(Integer(), primary_key=True)
          name = Column(String(80), unique=True)
          value = Column(Integer())
      

      我们可以使用 select 函数生成查询语句。

      from sqlalchemy.sql import select    
      statement = select([foo.name, foo.value]).where(foo.value > 0)
      

      接下来,我们可以将语句编译成查询对象。

      query = statement.compile()
      

      默认情况下,该语句使用与 SQLite 和 Oracle 等 SQL 数据库兼容的基本“命名”实现进行编译。如果需要指定PostgreSQL等方言,可以这样做

      from sqlalchemy.dialects import postgresql
      query = statement.compile(dialect=postgresql.dialect())
      

      或者如果你想明确指定方言为 SQLite,你可以将 paramstyle 从 'qmark' 更改为 'named'。

      from sqlalchemy.dialects import sqlite
      query = statement.compile(dialect=sqlite.dialect(paramstyle="named"))
      

      从查询对象中,我们可以提取查询字符串和查询参数

      query_str = str(query)
      query_params = query.params
      

      最后执行查询。

      conn.execute( query_str, query_params )
      

      【讨论】:

      • 这个答案比 AndyBarr 2 年前发布的答案更好/不同吗?
      • AndyBarr 的答案包括使用 DBSession 生成查询语句的示例,而此答案包括使用声明性 API 和 select 方法的示例。关于用某种方言编译查询语句,答案是一样的。我使用 SQLAlchemy 生成原始查询,然后使用 Twister 的 adbapi 执行它们。对于这个用例,了解如何在没有会话的情况下编译查询并提取查询字符串和参数很有用。
      • @eric 你怎么不只使用原始 SQL?
      【解决方案9】:

      对于使用 psycopg2 的 postgresql 后端,您可以侦听 do_execute 事件,然后使用游标、语句和类型强制参数以及 Cursor.mogrify() 来内联参数。您可以返回 True 以防止实际执行查询。

      import sqlalchemy
      
      class QueryDebugger(object):
          def __init__(self, engine, query):
              with engine.connect() as connection:
                  try:
                      sqlalchemy.event.listen(engine, "do_execute", self.receive_do_execute)
                      connection.execute(query)
                  finally:
                      sqlalchemy.event.remove(engine, "do_execute", self.receive_do_execute)
      
          def receive_do_execute(self, cursor, statement, parameters, context):
              self.statement = statement
              self.parameters = parameters
              self.query = cursor.mogrify(statement, parameters)
              # Don't actually execute
              return True
      

      示例用法:

      >>> engine = sqlalchemy.create_engine("postgresql://postgres@localhost/test")
      >>> metadata = sqlalchemy.MetaData()
      >>> users = sqlalchemy.Table('users', metadata, sqlalchemy.Column("_id", sqlalchemy.String, primary_key=True), sqlalchemy.Column("document", sqlalchemy.dialects.postgresql.JSONB))
      >>> s = sqlalchemy.select([users.c.document.label("foobar")]).where(users.c.document.contains({"profile": {"iid": "something"}}))
      >>> q = QueryDebugger(engine, s)
      >>> q.query
      'SELECT users.document AS foobar \nFROM users \nWHERE users.document @> \'{"profile": {"iid": "something"}}\''
      >>> q.statement
      'SELECT users.document AS foobar \nFROM users \nWHERE users.document @> %(document_1)s'
      >>> q.parameters
      {'document_1': '{"profile": {"iid": "something"}}'}
      

      【讨论】:

      • 这是 Postgres 的最佳答案!与使用literal_binds 的方法不同,它适用于任何参数类型。
      【解决方案10】:

      首先让我先说一下,我假设您这样做主要是出于调试目的——我不建议您尝试在 SQLAlchemy fluent API 之外修改语句。

      不幸的是,似乎没有一种简单的方法来显示包含查询参数的编译语句。 SQLAlchemy 实际上并没有将参数放入语句中——它们是passed into the database engine as a dictionary。这让特定于数据库的库可以处理诸如转义特殊字符以避免 SQL 注入之类的事情。

      但是您可以通过两步过程相当容易地做到这一点。要获取语句,您可以按照已经显示的方式进行操作,然后打印查询:

      >>> print(query)
      SELECT field_1, field_2 FROM table WHERE id=%s;
      

      您可以使用 query.statement 更进一步,以查看参数名称。注意下面的:id_1 和上面的%s——在这个非常简单的示例中并不是真正的问题,但可能是更复杂语句的关键。

      >>> print(query.statement)
      >>> print(query.statement.compile()) # seems to be equivalent, you can also
                                           # pass in a dialect if you want
      SELECT field_1, field_2 FROM table WHERE id=:id_1;
      

      然后,可以通过获取编译语句的params属性来获取参数的实际值:

      >>> print(query.statement.compile().params)
      {u'id_1': 1} 
      

      这至少适用于 MySQL 后端;我希望它对于 PostgreSQL 来说也足够通用,而无需使用 psycopg2

      【讨论】:

      • 从 PyCharm 调试器中,以下对我有用... qry.compile().params
      • 有趣,可能是 SQLAlchemy 自从我写了这个答案后发生了一些变化。
      【解决方案11】:

      更新:又遇到了另一种情况,这里以前的解决方案没有正确生成正确的 SQL 语句。在 SQLAlchemy 中进行一些研究之后,很明显您不仅需要针对特定​​方言进行编译,还需要获取编译后的查询并将其初始化为正确的 DBAPI 连接上下文。否则,类型绑定处理器之类的东西不会被执行,并且 JSON.NULL 之类的值不会被正确转换。

      注意,这使得这个解决方案非常适合 Flask + Flask-SQLAlchemy + psycopg2 + PostgreSQL。您可能需要通过更改方言和引用连接的方式将此解决方案转换为您的环境。不过,我非常有信心这会为所有数据类型生成准确的 SQL。

      下面的结果是一个简单的插入方法,偶尔但可靠地抓取精确的编译 SQL,只需询问查询本身,就可以将其发送到我的 PostgreSQL 后端:

      import sqlalchemy.dialects.postgresql.psycopg2
      
      from flask import current_app
      
      def query_to_string(query):
          dialect = sqlalchemy.dialects.postgresql.psycopg2.dialect()
          compiled_query = query.statement.compile(dialect=dialect)
          sqlalchemy_connection = current_app.db.session.connection()
          context = dialect.execution_ctx_cls._init_compiled(
              dialect,
              sqlalchemy_connection,
              sqlalchemy_connection.connection,
              compiled_query,
              None
          )
          mogrified_query = sqlalchemy_connection.connection.cursor().mogrify(
              context.statement,
              context.parameters[0]
          )
          return mogrified_query.decode()
      
      query = [ .... some ORM query .... ]
      
      print(f"compiled SQL = {query_to_string(query)}")
      

      【讨论】:

        【解决方案12】:

        我创建了这个小函数,当我想打印完整的查询时导入它,考虑到当方言已经绑定时我正处于测试的中间:

        import re
        
        def print_query(query):
            regex = re.compile(":(?P<name>\w+)")
            params = query.statement.compile().params
            sql = regex.sub("'{\g<name>}'", str(query.statement)).format(**params)
            print(f"\nPrinting SQLAlchemy query:\n\n")
            print(sql)
            return sql
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-05-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-10-12
          • 1970-01-01
          相关资源
          最近更新 更多