Bir SQLAlchemy ifadesinden ham, derlenmiş bir SQL sorgusunu nasıl edinebilirim?


106

Bir SQLAlchemy sorgu nesnem var ve derlenmiş SQL ifadesinin metnini tüm parametreleri bağlı olarak almak istiyorum (örneğin %s, ifade derleyicisi veya MySQLdb diyalekt motoru, vb. Tarafından bağlanmayı bekleyen veya diğer değişkenler yok ).

str()Sorguyu çağırmak şuna benzer bir şeyi ortaya çıkarır:

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

Sorgu._params'a bakmayı denedim ama bu boş bir söz. Bu sqlalchemy.ext.compiler.compilesdekoratör örneğini kullanarak kendi derleyicimi yazdım , ancak oradaki ifade bile hala %sveri istediğim yere sahip .

Sorguyu oluşturmak için parametrelerimin ne zaman karıştırıldığını tam olarak anlayamıyorum; sorgu nesnesini incelerken her zaman boş bir sözlüktürler (ancak sorgu iyi çalışır ve yankı günlüğünü açtığınızda motor onu yazdırır).

SQLAlchemy'nin tüm farklı DB-API'leri ifade API arayüzünün genel yapısını bozduğu için temeldeki sorguyu bilmemi istemediği mesajını almaya başladım. Ne olduğunu bulmadan önce sorgunun çalıştırılması umurumda değil; Sadece bilmek istiyorum!

Yanıtlar:


108

Bu blog, güncellenmiş bir cevap sağlar.

Blog gönderisinden alıntı yaparak, bu önerildi ve benim için çalıştı.

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

Q şu şekilde tanımlanır:

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

Veya herhangi bir tür session.query ().

Cevap için Nicolas Cadou'ya teşekkürler! Umarım burayı aramaya gelenlere yardımcı olur.


2
Değerleri sözlük olarak almanın kolay bir yolu var mı?
Damien

6
Verilen @Damien c = q.statement.compile(...), sadece alabilirsinizc.params
Hannele

1
Gönderi mysql ile etiketlendi, bu nedenle bu yanıttaki postgresql ayrıntıları gerçekten alakalı değil.
Hannele

4
OP'yi doğru anlarsam, son sorguyu istiyor. Bir lehçe belirleyerek baskı yapmak (burada postgres) bana hala gerçek değerler yerine yer tutucular veriyor . @ Matt'in cevabı işi yapar. Yer tutucularla SQL elde etmek, as_scalar()-metodu ile daha basit hale getirilebilir Query.
Patrick B.

1
@Hayalhanemersin Katılıyorum. Matt'in cevabı "doğru" cevap olarak görülmelidir. Bununla aynı sonucu sadece yaparak elde ederim str(q).
André C. Andersen

97

Dokümantasyon kullanan literal_bindsbir sorgu yazdırmak qparametrelerini içeren:

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

Yukarıdaki yaklaşım, yalnızca ints ve dizeler gibi temel türler için desteklendiği konusunda uyarılara sahiptir ve ayrıca önceden ayarlanmış bir değeri olmayan bir bindparam () doğrudan kullanılırsa, bunu da telafi edemez.

Belgeler ayrıca şu uyarıyı verir:

Bu tekniği, web formları veya diğer kullanıcı girişi uygulamaları gibi güvenilmeyen girdilerden alınan dizi içeriği ile asla kullanmayın. SQLAlchemy'nin Python değerlerini doğrudan SQL dize değerlerine zorlama olanakları, güvenilmeyen girdilere karşı güvenli değildir ve aktarılan verilerin türünü doğrulamaz. İlişkisel bir veritabanına karşı DDL olmayan SQL ifadelerini programlı olarak çağırırken her zaman bağlı parametreleri kullanın.


Teşekkür ederim! Bu son derece yardımcı oldu, pandaların read_sql işlevini acısız bir şekilde kullanmamı sağladı!
Justin Palmer

24

Bu, Sqlalchemy> = 0.6 ile çalışmalıdır

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
Bunun için teşekkürler! Ne yazık ki MySQL kullanıyorum, bu yüzden lehçem "konumsal" ve sözlükten ziyade bir parametreler listesine sahip olması gerekiyor. Şu anda bu .. ile işinize örnek almaya çalışırken
CCE

Lütfen adaptbu şekilde kullanmayın . Minimum çağrıda, her seferinde ondan dönüş değeri üzerine hazırlayın (), bağlantıyı bir argüman olarak sağlayın, böylece uygun alıntı yapabilsin.
Alex Gaynor

@Alex: psycopg ile doğru şekilde alıntı yapmanın doğru yolu nedir? (optimal olmadığını ima ettiğiniz dönüş değeri için hazırla () çağırmanın yanı sıra)
albertov

Özür dilerim, cümlenin kötü olduğunu düşünüyorum, obj.prepare (bağlantı) aradığın sürece sorun olmayacak. Bunun nedeni, libpq'in alıntı yapmak için sağladığı "iyi" API'lerin bağlantı gerektirmesidir (ve unicode dizeleri için kodlama gibi şeyler sağlar).
Alex Gaynor

1
Teşekkürler. Ben çağrı denedim preparedönüş değeri ancak bu yöntemi yok gibi görünüyor: AttributeError: 'psycopg2._psycopg.AsIs' object has no attribute 'prepare'. Psycopg2 2.2.1 BTW kullanıyorum
albertov

18

MySQLdb arka ucu için albertov'un harika cevabını biraz değiştirdim (çok teşekkürler!). Eminim olup olmadığını kontrol etmek için birleştirilebilirler comp.positional, Trueancak bu, bu sorunun kapsamının biraz dışında.

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)

Harika! Sadece MySQL'e gönderilen bağlı parametre listesinin return tuple(params)bir cazibe gibi çalışacak şekilde yukarıdakileri değiştirmesine ihtiyacım vardı ! Son derece acı verici bir yoldan geçmek zorunda kaldığım sayısız saatten kurtuldun.
horcle_buzz

17

Mesele şu ki, sqlalchemy asla verileri sorgunuzla karıştırmaz. Sorgu ve veriler, temeldeki veritabanı sürücünüze ayrı ayrı aktarılır - verilerin enterpolasyonu veritabanınızda gerçekleşir.

Sqlalchemy, sorguyu str(myquery)veritabanında gördüğünüz gibi iletir ve değerler ayrı bir dizide gider .

Verileri sorguyla kendiniz enterpolasyon yaptığınız bir yaklaşım kullanabilirsiniz (aşağıda albertov'un önerdiği gibi), ancak bu, sqlalchemy'nin yürüttüğü şey değildir.


neden aynı şey değil? DB-API'nin işlemler yaptığını, muhtemelen sorgularını yeniden sıraladığını, vb. Anlıyorum, ancak sorgumu bundan daha fazla değiştiriyor olabilir mi?
cce

1
@cce: son sorguyu bulmaya çalışıyorsunuz. Son sorgu SELECT id WHERE date_added <= %s AND date_added >= %s ORDER BY count DESC IS . Bunlar %sveritabanına sqlalchemy tarafından gönderilir - sqlalchemy ASLA gerçek verileri% s
nosklo yerine koymaz

@cce: Bazı dbapi modülleri bunu da yapmaz - bu genellikle veritabanının kendisi tarafından yapılır
nosklo

1
daha da kazma - aha ben, teşekkür neler söylediğini görmek sqlalchemy.dialects.mysql.mysqldb, do_executemany()MySQLdb imlecin ayrı ayrı açıklama ve parametreleri geçirir. yaşasın yöneltme!
cce

12

Öncelikle, bunu esas olarak hata ayıklama amacıyla yaptığınızı varsaydığımı söyleyerek önsöz yapmama izin verin - ifadeyi SQLAlchemy akıcı API'sinin dışında değiştirmeyi denemeyi önermem.

Maalesef, derlenmiş ifadeyi dahil edilen sorgu parametreleriyle göstermenin basit bir yolu yok gibi görünüyor. SQLAlchemy aslında parametreleri ifadeye koymaz - bunlar veritabanı motoruna sözlük olarak aktarılır . Bu, veritabanına özgü kitaplığın, SQL enjeksiyonunu önlemek için özel karakterlerden kaçış gibi şeyleri işlemesini sağlar.

Ancak bunu iki aşamalı bir süreçte makul ölçüde kolayca yapabilirsiniz. İfadeyi almak için, daha önce gösterdiğiniz gibi yapabilir ve sadece sorguyu yazdırabilirsiniz:

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

Parametre adlarını görmek için query.statement ile bir adım daha yaklaşabilirsiniz. Not :id_1vs aşağıda %syukarıda - Bu çok basit örnekte gerçekten bir sorun, ama daha karmaşık açıklamada anahtarı olabilir.

>>> 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;

Ardından, paramsderlenen ifadenin özelliğini alarak parametrelerin gerçek değerlerini elde edebilirsiniz :

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

Bu, en azından bir MySQL arka uç için çalıştı; Kullanmaya gerek kalmadan PostgreSQL için yeterince genel olmasını bekliyorum psycopg2.


PyCharm hata ayıklayıcısından, aşağıdakiler benim için çalıştı ... qry.compile (). Params
Ben

İlginç, bu cevabı yazdığımdan beri SQLAlchemy biraz değişmiş olabilir.
Hannele

10

Psycopg2 kullanan postgresql arka uç için, do_executeolayı dinleyebilir , ardından imleci, ifadeyi kullanabilir ve parametreleri Cursor.mogrify()satır içi yapmak için zorlanmış parametreleri yazabilirsiniz . Sorgunun gerçekten yürütülmesini önlemek için True döndürebilirsiniz.

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

Örnek kullanım:

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

Aşağıdaki çözüm SQLAlchemy Expression Language kullanır ve SQLAlchemy 1.1 ile çalışır. Bu çözüm, parametreleri sorguyla karıştırmaz (orijinal yazarın istediği gibi), ancak farklı SQL lehçeleri için SQL sorgu dizeleri ve parametre sözlükleri oluşturmak için SQLAlchemy modellerini kullanmanın bir yolunu sağlar. Örnek, http://docs.sqlalchemy.org/en/rel_1_0/core/tutorial.html eğiticisine dayanmaktadır.

Sınıf verildiğinde,

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 işlevini kullanarak bir sorgu ifadesi üretebiliriz .

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

Daha sonra, ifadeyi bir sorgu nesnesi olarak derleyebiliriz.

query = statement.compile()

Varsayılan olarak ifade, SQLite ve Oracle gibi SQL veritabanları ile uyumlu temel bir 'adlandırılmış' uygulama kullanılarak derlenir. PostgreSQL gibi bir lehçe belirtmeniz gerekiyorsa, şunları yapabilirsiniz:

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

Ya da, diyalekti açıkça SQLite olarak belirtmek isterseniz, param tarzını 'qmark' yerine 'adlandırılmış' olarak değiştirebilirsiniz.

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

Sorgu nesnesinden sorgu dizesini ve sorgu parametrelerini çıkarabiliriz

query_str = str(query)
query_params = query.params

ve son olarak sorguyu yürütün.

conn.execute( query_str, query_params )

Bu cevap nasıl AndyBarr'ın 2 yıl önceki cevabından daha iyi / farklı?
Piotr Dobrogost

AndyBarr'ın cevabı, bir DBSession ile bir sorgu ifadesi oluşturmanın bir örneğini içerirken, bu cevap, bildirim temelli API ve seçme yöntemini kullanan bir örnek içerir. Sorgu ifadesini belirli bir lehçe ile derlemekle ilgili olarak, cevaplar aynıdır. Ham sorgular oluşturmak için SQLAlchemy kullanıyorum ve ardından bunları Twister'ın adbapi ile çalıştırıyorum. Bu kullanım örneği için, sorgunun bir oturum olmadan nasıl derleneceğini ve sorgu dizesi ve parametrelerinin nasıl çıkarılacağını bilmek yararlıdır.
eric

3

ConnectionEvents ailesindeki olayları kullanabilirsiniz : after_cursor_executeveya before_cursor_execute.

Sqlalchemy UsageRecipes'da @zzzeek şu örneği bulabilirsiniz:

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

Burada erişebilirsiniz açıklamada


3

Bu yüzden, bu farklı yanıtların birçok küçük parçasını bir araya getirerek, ihtiyacım olan şeyi buldum: basit bir kod seti ve ara sıra ancak güvenilir bir şekilde (yani, tüm veri türlerini işler) tam olarak, derlenmiş SQL'ime gönderilen Postgres arka ucu, yalnızca sorgunun kendisini sorgulayarak:

from sqlalchemy.dialects import postgresql

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

compiled_query = query.statement.compile(
    dialect=postgresql.dialect(),
    compile_kwargs={"literal_binds": True}
)
mogrified_query = session.connection().connection.cursor().mogrify(
    str(compiled_query),
    compiled_query.params
)

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

0

Bence .statement hile yapabilir: 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

Bazı tür filtreler ayarladıysanız, ifade size parametrelerin ne olduğunu göstermez.
Hannele

0

Diyalekt zaten bağlıyken bir testin ortasında olduğumu düşünerek, tam sorguyu yazdırmak istediğimde içe aktardığım bu küçük işlevi oluşturdum:

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.