SQLAlchemy 식에서 컴파일 된 원시 SQL 쿼리를 가져 오는 방법은 무엇입니까?


101

SQLAlchemy 쿼리 개체가 있고 모든 매개 변수가 바인딩 된 컴파일 된 SQL 문의 텍스트를 얻고 싶습니다 (예 : %s 문 컴파일러 또는 MySQLdb 언어 엔진에 의해 바인딩되기를 기다리는 변수 없거나 다른 변수 등).

str()쿼리를 호출 하면 다음과 같은 결과가 나타납니다.

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

query._params에서 찾아 봤지만 빈 dict입니다. sqlalchemy.ext.compiler.compiles데코레이터 예제를 사용하여 내 컴파일러를 작성 했지만 거기에있는 문에도 여전히%s 데이터 곳에 .

쿼리를 생성하기 위해 내 매개 변수가 언제 섞여 있는지 알 수 없습니다. 쿼리 개체를 검사 할 때 항상 빈 사전입니다 (쿼리가 제대로 실행되고 에코 로깅을 켜면 엔진이 출력하지만).

SQLAlchemy가 기본 쿼리를 알기를 원하지 않는다는 메시지를 받기 시작했습니다. 식 API 인터페이스의 일반적인 특성이 모든 다른 DB-API를 깨뜨리기 때문입니다. 쿼리가 무엇인지 알아 내기 전에 실행 되더라도 상관 없습니다. 알고 싶어요!

답변:


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

1
게시물에는 mysql 태그가 지정되어 있으므로이 답변의 postgresql 세부 정보는 실제로 관련이 없습니다.
Hannele

3
OP를 올바르게 이해하면 최종 쿼리를 원합니다. 방언 (여기에 포스트 그레스)을 지정하여 인쇄하면 여전히 제공 나에게 대신 리터럴 값의 자리 표시 자. @Matt의 대답이 작업을 수행합니다. as_scalar()-method of를 사용하면 자리 표시자를 사용하여 SQL을 더 간단하게 가져올 수 있습니다 Query.
Patrick B.

1
@PatrickB. 나는 동의한다. Matt의 대답은 "정답"으로 간주되어야합니다. 나는 그냥함으로써 이것과 같은 결과를 얻습니다 str(q).
André C. Andersen

89

문서는 사용 literal_binds하는 쿼리 인쇄 q매개 변수를 포함하여 :

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

위의 접근 방식은 int 및 문자열과 같은 기본 유형에 대해서만 지원되며 사전 설정된 값이없는 bindparam ()을 직접 사용하는 경우에도이를 문자열화할 수 없다는 경고가 있습니다.

문서는 또한 다음 경고를 발행합니다.

웹 양식 또는 기타 사용자 입력 응용 프로그램에서와 같이 신뢰할 수없는 입력에서받은 문자열 콘텐츠에는이 기술을 사용하지 마십시오. Python 값을 직접 SQL 문자열 값으로 강제 변환하는 SQLAlchemy의 기능은 신뢰할 수없는 입력에 대해 안전하지 않으며 전달되는 데이터 유형의 유효성을 검사하지 않습니다. 관계형 데이터베이스에 대해 비 DDL SQL 문을 프로그래밍 방식으로 호출 할 때는 항상 바인딩 된 매개 변수를 사용하십시오.


감사합니다! 이것은 매우 도움이되었고 pandas read_sql 함수를 고통없이 사용할 수있었습니다!
Justin Palmer

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 2011 년

@Alex : psycopg로 적절한 인용을하는 올바른 방법은 무엇입니까? (반환 값에 대해 prepare ()를 호출하는 것 외에도 최적이 아닌 것 같습니다.)
albertov

죄송합니다. obj.prepare (connection)을 호출하는 한 괜찮을 것입니다. 이는 libpq가 인용을 위해 제공하는 "좋은"API에는 연결이 필요하기 때문입니다 (유니 코드 문자열에 대한 인코딩과 같은 기능을 제공함).
Alex Gaynor 2011 년

1
감사. prepare반환 값을 호출하려고 시도했지만 해당 메서드가없는 것 같습니다 AttributeError: 'psycopg2._psycopg.AsIs' object has no attribute 'prepare'.. 나는 psycopg2 2.2.1 BTW를 사용하고 있습니다
albertov 2011 년

18

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)매력처럼 작동했습니다! 당신은 나를 극도로 고통스러운 길로 가야하는 수많은 시간을 절약했습니다.
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 IS 최종 쿼리. 사람들이 %sSQLAlchemy의하여 데이터베이스로 전송됩니다 - SQLAlchemy의이 %의 대신에 실제 데이터를두고 결코
nosklo

@cce : 일부 DBAPI 모듈은 그 중 하나를하지 않는다 - 자주 데이터베이스 자체에 의해 수행되는
nosklo

1
더욱 파고 - 아하 나는 당신이, 감사 무슨 말을하는지보고 sqlalchemy.dialects.mysql.mysqldb, do_executemany()MySQLdb 커서로 별도로 문 및 매개 변수를 전달합니다. yay 간접!
cce

11

먼저 주로 디버깅 목적으로이 작업을 수행한다고 가정합니다. SQLAlchemy 유창한 API 외부에서 문을 수정하는 것은 권장하지 않습니다.

불행히도 쿼리 매개 변수가 포함 된 컴파일 된 문을 표시하는 간단한 방법이없는 것 같습니다. SQLAlchemy는 실제로 매개 변수를 명령문에 넣지 않고 데이터베이스 엔진에 사전으로 전달됩니다 . 이를 통해 데이터베이스 별 라이브러리는 SQL 주입을 피하기 위해 특수 문자 이스케이프와 같은 작업을 처리 할 수 ​​있습니다.

그러나 합리적으로 쉽게 2 단계 프로세스로이를 수행 할 수 있습니다. 명령문을 얻으려면 이미 표시된대로 수행하고 쿼리를 인쇄하면됩니다.

>>> 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 디버거 내에서 다음이 저에게 효과적
Ben

흥미롭게도 SQLAlchemy 가이 답변을 작성한 이후로 약간 변경되었을 수 있습니다.
Hannele

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 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):
    __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 )

이 답변이 2 년 전에 게시 된 AndyBarr의 답변보다 더 좋거나 다른 점은 무엇입니까?
Piotr Dobrogost

AndyBarr의 답변에는 DBSession으로 쿼리 문을 생성하는 예가 포함되어 있지만이 답변에는 선언적 API 및 select 메서드를 사용하는 예제가 포함되어 있습니다. 특정 방언으로 질의 문을 컴파일하는 경우 답변은 동일합니다. SQLAlchemy를 사용하여 원시 쿼리를 생성 한 다음 Twister의 adbapi로 실행합니다. 이 사용 사례의 경우 세션없이 쿼리를 컴파일하고 쿼리 문자열과 매개 변수를 추출하는 방법을 아는 것이 유용합니다.
eric

3

ConnectionEvents 계열의 이벤트를 사용할 수 있습니다 . after_cursor_execute또는 before_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

어떤 종류의 필터를 설정 한 경우 문은 매개 변수가 무엇인지 보여주지 않습니다.
Hannele
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.