web-dev-qa-db-ja.com

SQLAlchemy式から生のコンパイル済みSQLクエリを取得するにはどうすればよいですか?

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_が残っています。

クエリを作成するためにパラメーターがいつ混合されるかはわかりません。クエリオブジェクトを調べるとき、それらは常に空のディクショナリです(ただし、クエリは正常に実行され、エコーロギングをオンにするとエンジンはそれを出力します)。

SQLAlchemyは、基になるクエリを知りたくないというメッセージを受け取り始めています。これは、すべての異なるDB-APIの式APIインターフェースの一般的な性質を破壊するからです。クエリを実行する前に、クエリが実行されるかどうかは気にしません。ただ知りたいだけです!

81
cce

これ ブログは更新された回答を提供します。

ブログ投稿から引用すると、これが提案され、私のために働いた。

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

答えてくれたニコラス・カドゥに感謝します!ここで検索してくる他の人に役立つことを願っています。

79
AndyBarr

ドキュメント は、literal_bindsを使用して、パラメータを含むクエリqを出力します。

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

上記のアプローチには、intや文字列などの基本型でのみサポートされているという警告があります。さらに、事前に設定された値のないbindparam()が直接使用される場合、それも文字列化できません。

65
Matt

これは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)
24
albertov

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)
18
cce

つまり、sqlalchemyはデータとクエリを混合することはありません。クエリとデータは、基になるデータベースドライバーに個別に渡されます。データの補間はデータベースで行われます。

Sqlalchemyは、str(myquery)で見たようにクエリをデータベースに渡し、値は別のTupleに入れられます。

クエリを使用してデータを補間するアプローチを使用することもできます(以下でalbertovが提案したように)が、それはsqlalchemyが実行しているのと同じものではありません。

13
nosklo

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"}}'}
_
8
rectalogic

最初に、主にデバッグ目的でこれを行っていると仮定して、序文を述べましょう-SQLAlchemy fluent APIの外部でステートメントを変更することはお勧めしません。

残念ながら、コンパイルされたステートメントを照会パラメーターを含めて表示する簡単な方法はないようです。 SQLAlchemyは、実際にはパラメーターをステートメントに入れません-それらは 辞書としてデータベースエンジンに渡されます です。これにより、データベース固有のライブラリが特殊文字のエスケープなどを処理して、SQLインジェクションを回避できます。

ただし、2段階のプロセスでこれを合理的に簡単に行うことができます。ステートメントを取得するには、既に示したとおりに実行し、クエリを印刷するだけです。

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

Query.statementを使用すると、パラメーターの名前を確認するために1ステップ近づくことができます。 (注::id_1以下と%s上記-この非常に単純な例では実際には問題ではありませんが、より複雑なステートメントのキーになる可能性があります。)

>>> print(query.statement)
>>> print(query.statement.compile()) # reasonably 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バックエンドで機能しました。 psycopg2を使用しなくてもPostgreSQLに十分な汎用性があることを期待します。

6
Hannele

次のソリューションでは、SQLAlchemy Expression Languageを使用し、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):
    __table= '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 )
3
eric

ConnectionEvents familyのイベントを使用できます: after_cursor_execute または before_cursor_execute

Sqlalchemy sageRecipes by @zzzeekで、この例を見つけることができます:

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
Alex Bender

.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
0
user2757902