如何从SQLAlchemy表达式获取原始的编译SQL查询?


101

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

调用str()查询将显示如下内容:

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

我试着在query._params中查找,但这是一个空字典。我使用装饰器的这个示例sqlalchemy.ext.compiler.compiles编写了自己的编译器,但即使那里的语句仍然有%s我想要的数据。

我无法弄清楚何时混入参数来创建查询。在检查查询对象时,它们始终是一个空字典(尽管查询执行得很好,并且当您打开echo记录时引擎会打印出来)。

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

Answers:


105

博客提供了更新的答案。

引用博客文章中的内容,这对我来说是建议和有效的。

>>> 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的回答!希望对其他在这里搜索的人有所帮助。


2
是否有一种简单的方法可以将值作为字典获取?
Damien 2014年

5
@Damien给出c = q.statement.compile(...),您可以得到c.params
Hannele '16

1
该帖子被标记为mysql,因此此答案中的postgresql详细信息并不真正相关。
Hannele '18

3
如果我正确理解OP,则他需要最终查询。指定方言(此处为postgres)进行打印仍会为提供占位符,而不是文字值。@Matt的答案就可以了。使用的as_scalar()-方法可以更轻松地实现使用占位符获取SQL Query
帕特里克·B。

1
@帕特里克·B。我同意。马特的答案应被视为“正确”的答案。我只是通过这样做就得到了相同的结果str(q)
安德烈·C·安徒生

89

文档用于literal_binds打印q包含参数的查询:

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

上面的方法有一个警告:仅基本类型(例如int和字符串)才支持该方法,此外,如果直接使用没有预设值的bindparam(),则也不能将其字符串化。

该文档还发出以下警告:

切勿将此技术与从不受信任的输入(例如从Web表单或其他用户输入应用程序)接收到的字符串内容一起使用。SQLAlchemy的将Python值强制转换为直接SQL字符串值的功能对于不受信任的输入是不安全的,并且无法验证传递的数据类型。以编程方式对关系数据库调用非DDL SQL语句时,请始终使用绑定参数。


谢谢!这非常有帮助,让我可以轻松使用熊猫read_sql函数!
贾斯汀·帕尔默

24

这应该适用于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)

2
谢谢你!可悲的是,我正在使用MySQL,因此我的方言是“位置”字,需要具有参数列表而不是字典。当前正在尝试使用您的示例进行处理
。.– cce

请不要adapt以这种方式使用。至少每次都对返回值调用prepare(),并将连接作为参数提供,因此它可以进行正确的引用。
Alex Gaynor

@Alex:用psycopg正确报价的正确方法是什么?(除了在返回值上调用prepare(),您似乎暗示这不是最优的)
albertov 2011年

对不起,我认为我的措词不好,只要您确实调用obj.prepare(connection),就可以了。这是因为libpq提供的用于引用的“良好” API需要连接(并且提供诸如Unicode字符串编码之类的东西)。
Alex Gaynor

1
谢谢。我尝试调用prepare返回值,但似乎没有该方法:AttributeError: 'psycopg2._psycopg.AsIs' object has no attribute 'prepare'。我正在使用psycopg2 2.2.1 BTW
albertov 2011年

18

对于MySQLdb后端,我稍微修改了albertov的出色答案(非常感谢!)。我敢肯定,可以将它们合并以检查是否存在comp.positionalTrue但这超出了此问题的范围。

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)像个魅力一样!您节省了我无数小时不得不走的痛苦之路。
horcle_buzz 2015年

15

事实是,sqlalchemy永远不会将数据与查询混合在一起。查询和数据分别传递到基础数据库驱动程序-数据插值发生在数据库中。

Sqlalchemy如您所见将查询传递str(myquery)给数据库,并且值将进入一个单独的元组。

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


为什么不一样呢?我了解DB-API正在执行事务,可能对查询进行重新排序等,但是它是否可以对我的查询进行更多修改?
cce

1
@cce:您正在尝试查找最终查询。SELECT id WHERE date_added <= %s AND date_added >= %s ORDER BY count DESC 最终查询。那些%s通过sqlalchemy发送到数据库-sqlalchemy切勿将实际数据替换%s
nosklo 2011年

@cce:有些DBAPI模块不会做,要么-这往往是由数据库本身完成
nosklo

1
啊哈,我明白了你的意思,谢谢—进一步研究sqlalchemy.dialects.mysql.mysqldbdo_executemany()将语句和参数分别传递给MySQLdb游标。是的,间接的!
cce

11

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

不幸的是,似乎没有一种简单的方法可以显示包含查询参数的已编译语句。SQLAlchemy实际上并未将参数放入语句中-它们已作为字典传递给数据库引擎。这使特定于数据库的库可以处理诸如转义特殊字符的操作,以避免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可能发生了一些变化。
汉内利'19

10

对于使用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"}}'}

4

以下解决方案使用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,则可以将参数样式从“ 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发表的答案更好/不同吗?
Piotr Dobrogost

AndyBarr的答案包括使用DBSession生成查询语句的示例,而此答案包括使用声明性API和select方法的示例。关于用某种方言编译查询语句,答案是相同的。我使用SQLAlchemy生成原始查询,然后使用Twister的adbapi执行它们。对于此用例,了解如何在没有会话的情况下编译查询以及提取查询字符串和参数很有用。
eric

3

您可以使用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)
...

在这里您可以访问您的对帐单


2

因此,将这些不同答案的很多点放在一起,我得出了我需要的东西:一组简单的代码可以插入,偶尔但可靠地(即处理所有数据类型)获取发送给我的准确的,已编译的SQL通过查询查询本身的Postgres后端:

from sqlalchemy.dialects import postgresql

query = [ .... some ORM query .... ]

compiled_query = query.statement.compile(
    dialect=postgresql.dialect()
)
mogrified_query = session.connection().connection.cursor().mogrify(
    str(compiled_query),
    compiled_query.params
)

print("compiled SQL = {s}".format(mogrified_query.decode())

0

我认为.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

如果您设置了某些过滤器,则Statement不会显示参数是什么。
Hannele '18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.