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

179 投票
13 回答
193909 浏览
提问于 2025-04-16 09:31

我有一个 SQLAlchemy 的查询对象,想要获取编译后的 SQL 语句的文本,并且希望所有的参数都已经绑定好(也就是说,不想看到像 %s 这样的占位符,或者其他等待被绑定的变量)。

调用 str() 方法后,得到的结果大概是这样的:

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

我试着查看 query._params,但它是一个空字典。我还根据 这个关于 sqlalchemy.ext.compiler.compiles 装饰器的例子 写了自己的编译器,但即使在那个例子中,语句里还是有 %s,而我想要的是数据。

我有点搞不清楚我的参数是什么时候混入的,形成查询;在检查查询对象时,它们总是一个空字典(不过查询执行得很好,当你打开回显日志时,数据库引擎会打印出来)。

我开始觉得 SQLAlchemy 不想让我知道底层的查询,因为这会破坏表达式 API 接口与不同数据库 API 之间的一般性质。我并不介意查询在我知道之前就被执行;我只是想知道!

13 个回答

26

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

这段文档提到,literal_binds可以用来打印一个查询q,包括它的参数:

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

不过,这种方法有一些限制,它只支持基本类型,比如整数和字符串。而且,如果直接使用一个没有预设值的bindparam(),也无法将其转换成字符串。

文档还给出了这样的警告:

绝对不要对来自不可信输入的字符串内容使用这种技术,比如从网页表单或其他用户输入的应用程序中获取的内容。SQLAlchemy将Python值转换为直接SQL字符串的功能对于不可信输入并不安全,也不会验证传递数据的类型。在对关系数据库执行非DDL的SQL语句时,始终要使用绑定参数。

183

Nicolas Cadou的这篇博客提供了一个更新的答案。

从这篇博客中引用,这个方法被推荐并且对我有效:

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()

撰写回答