मैं SQLAlchemy अभिव्यक्ति से एक कच्चा, संकलित SQL क्वेरी कैसे प्राप्त करूं?


103

मेरे पास SQLAlchemy क्वेरी ऑब्जेक्ट है और संकलित SQL कथन का पाठ प्राप्त करना चाहते हैं, जिसके सभी पैरामीटर बाध्य हैं (उदाहरण %sकथन कंपाइलर या MySQLdb बोली इंजन, आदि द्वारा बाध्य होने की प्रतीक्षा कर रहे हैं।

str()क्वेरी पर कॉल करने से कुछ इस तरह का पता चलता है:

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

मैंने क्वेरी में देखने की कोशिश की है ।_परम्स लेकिन यह एक खाली तानाशाही है। मैंने डेकोरेटर के इस उदाहरण काsqlalchemy.ext.compiler.compiles उपयोग करके अपना खुद का कंपाइलर लिखा था , लेकिन यहां तक ​​कि बयान में अभी भी वह %sजगह है जहां मुझे डेटा चाहिए।

जब मेरा पैरामीटर क्वेरी बनाने के लिए मिलाया जाता है, तो मैं इसका पता नहीं लगा सकता; क्वेरी ऑब्जेक्ट की जांच करते समय वे हमेशा एक खाली शब्दकोश होते हैं (हालांकि क्वेरी ठीक निष्पादित होती है और जब आप ईको लॉगिंग चालू करते हैं तो इंजन इसे प्रिंट करता है)।

मुझे यह संदेश मिलना शुरू हो गया है कि SQLAlchemy मुझे अंतर्निहित क्वेरी को जानना नहीं चाहता है, क्योंकि यह अभिव्यक्ति एपीआई के इंटरफ़ेस के सभी अलग-अलग DB-APIs की सामान्य प्रकृति को तोड़ता है। इससे पहले कि मुझे पता चले कि क्या हुआ था, मुझे इस पर कोई आपत्ति नहीं है; मै सिर्फ जानना चाहता हूँ!

जवाबों:


107

यह ब्लॉग एक अद्यतन उत्तर प्रदान करता है।

ब्लॉग पोस्ट से उद्धृत, यह मेरे लिए सुझाव दिया गया है और काम कर रहा है।

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

या सिर्फ किसी भी तरह का सेशन।क्वेरी ()।

उत्तर के लिए निकोलस कैडू को धन्यवाद! मुझे उम्मीद है कि यह दूसरों को मदद करता है जो यहां खोजते हैं।


2
क्या शब्दकोश के रूप में मूल्यों को प्राप्त करने का एक आसान तरीका है?
डेमियन

6
दिए गए @Damien c = q.statement.compile(...), तो आप सिर्फ प्राप्त कर सकते हैंc.params
Hannele

1
पोस्ट को mysql के साथ टैग किया गया है, इसलिए इस उत्तर में पोस्टग्रैक्स्ल विवरण वास्तव में प्रासंगिक नहीं हैं।
हैनले

4
यदि मैं ओपी को सही ढंग से समझता हूं, तो वह अंतिम प्रश्न चाहता है। एक बोली (यहां पोस्टग्रेज) निर्दिष्ट करने के साथ मुद्रण अभी भी मुझे शाब्दिक मूल्यों के बजाय प्लेसहोल्डर देता है । @ मैट का जवाब काम करता है। प्लेसहोल्डर्स के साथ SQL प्राप्त करना सरल हो सकता है as_scalar()-method के साथ Query
पैट्रिक बी।

1
@PatrickB। मैं सहमत हूँ। मैट के उत्तर को "सही" उत्तर माना जाना चाहिए। मुझे बस यही करने से परिणाम मिलता है str(q)
एंड्रे सी। एंडरसन

94

प्रलेखन का उपयोग करता है literal_bindsएक प्रश्न मुद्रित करने के लिए qमापदंडों सहित:

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

उपर्युक्त दृष्टिकोण में यह चेतावनी दी गई है कि यह केवल बुनियादी प्रकारों के लिए समर्थित है, जैसे कि इन्ट्स और स्ट्रिंग्स, और इसके अलावा यदि बिना पूर्व-निर्धारित मूल्य के बिना एक bindparam () को सीधे उपयोग किया जाता है, तो यह या तो स्ट्रिंग करने में सक्षम नहीं होगा।

प्रलेखन यह चेतावनी भी जारी करता है:

इस तकनीक का उपयोग कभी भी अविश्वसनीय सामग्री से प्राप्त स्ट्रिंग सामग्री के साथ न करें, जैसे कि वेब फ़ॉर्म या अन्य उपयोगकर्ता-इनपुट अनुप्रयोगों से। सीधे SQL स्ट्रिंग मानों में पायथन मूल्यों के साथ तालमेल करने के लिए SQLAlchemy की सुविधाएं अविश्वसनीय इनपुट के खिलाफ सुरक्षित नहीं हैं और पारित होने वाले डेटा के प्रकार को मान्य नहीं करती हैं। प्रोग्राम से गैर-डीडीएल एसक्यूएल स्टेटमेंट को एक रिलेशनल डेटाबेस के विरुद्ध लागू करते समय हमेशा बाउंड पैरामीटर का उपयोग करें।


धन्यवाद! यह बेहद मददगार था, जिसने मुझे पंडों को पढने में मदद की।
जस्टिन पामर

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इस तरीके का उपयोग न करें । प्रत्येक बार रिटर्न मूल्य पर एक न्यूनतम कॉल तैयार () पर, एक तर्क के रूप में कनेक्शन प्रदान करता है, इसलिए यह उचित उद्धरण कर सकता है।
एलेक्स गेन्नोर

@ एलेक्स: मानसोपग के साथ उचित उद्धरण देने का सही तरीका क्या होगा? (रिटर्न वैल्यू पर कॉलिंग तैयार करने के अलावा), जो कि आपको लगता है कि यह अनुकूलतम नहीं है)
अल्बर्टोव

क्षमा करें, मुझे लगता है कि जब तक आप obj.prepare (कनेक्शन) को कॉल करते हैं, तब तक आप ठीक होना चाहिए। ऐसा इसलिए है क्योंकि "अच्छा" API जो libpq को उद्धृत करने के लिए प्रदान करता है कनेक्शन की आवश्यकता होती है (और यह यूनिकोड स्ट्रिंग्स के लिए एन्कोडिंग जैसी चीजें प्रदान करता है)।
एलेक्स गेन्नोर

1
धन्यवाद। मैंने prepareरिटर्न वैल्यू पर कॉल करने की कोशिश की है, लेकिन ऐसा लगता है कि इसमें वह तरीका नहीं है AttributeError: 'psycopg2._psycopg.AsIs' object has no attribute 'prepare':। मैं
psycopg2

18

MySQLdb बैकएंड के लिए मैंने अल्बर्टोव के भयानक उत्तर (बहुत बहुत धन्यवाद!) को थोड़ा संशोधित किया। मुझे यकीन है कि वे जांच करने के लिए विलय कर दिया कर रहा हूँ किया जा सकता है, तो 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)एक आकर्षण की तरह काम किया! आपने मुझे एक बेहद दर्दनाक सड़क पर जाने के अनगिनत घंटे बचाए।
भयावह_बज 3

16

बात यह है, sqlalchemy आपकी क्वेरी के साथ डेटा को कभी नहीं मिलाता है। क्वेरी और डेटा को आपके अंतर्निहित डेटाबेस ड्राइवर को अलग से पास किया जाता है - डेटा का प्रक्षेप आपके डेटाबेस में होता है।

Sqlalchemy str(myquery)डेटाबेस में देखा है के रूप में क्वेरी से गुजरता है , और मान एक अलग tuple में जाएगा।

आप कुछ दृष्टिकोण का उपयोग कर सकते हैं जहां आप डेटा को स्वयं क्वेरी के साथ प्रक्षेपित करते हैं (जैसा कि अल्बर्टोव ने नीचे सुझाया है), लेकिन यह वही बात नहीं है जिसे sqlalchemy निष्पादित कर रहा है।


यह एक ही बात क्यों नहीं है? मैं समझता हूं कि DB-API लेन-देन कर रहा है, संभवतः प्रश्नों को फिर से आदेश दे रहा है, आदि, लेकिन क्या यह मेरी क्वेरी को इससे अधिक संशोधित कर सकता है?
cce

1
@cce: आप अंतिम क्वेरी खोजने की कोशिश कर रहे हैं। SELECT id WHERE date_added <= %s AND date_added >= %s ORDER BY count DESC IS अंतिम क्वेरी। उन %sSQLAlchemy से डेटाबेस के लिए भेजा जाता - SQLAlchemy कभी वास्तविक डेटा% s के स्थान पर डालता है
nosklo

@ cce: कुछ dbapi मॉड्यूल ऐसा नहीं करते हैं - जो कि अक्सर डेटाबेस द्वारा ही किया जाता है
nosklo

1
में आगे खुदाई - अहा मैं तुम्हें क्या कह रहे हैं, धन्यवाद देख sqlalchemy.dialects.mysql.mysqldb, do_executemany()MySQLdb कर्सर के लिए अलग से बयान और मानकों से गुजरता है। याय अप्रत्यक्ष!
cce

11

पहले मुझे यह कहते हुए प्रस्तावना दें कि मुझे लगता है कि आप मुख्य रूप से डिबगिंग उद्देश्यों के लिए ऐसा कर रहे हैं - मैं SQLAlchemy धाराप्रवाह एपीआई के बाहर बयान को संशोधित करने की कोशिश नहीं करूंगा।

दुर्भाग्य से इसमें शामिल किए गए क्वेरी मापदंडों के साथ संकलित विवरण दिखाने का एक सरल तरीका प्रतीत नहीं होता है। SQLAlchemy वास्तव में बयान में मापदंडों को नहीं डालता है - वे एक शब्दकोश के रूप में डेटाबेस इंजन में पारित हो जाते हैं । यह डेटाबेस-विशिष्ट लाइब्रेरी SQL इंजेक्शन से बचने के लिए विशेष वर्णों से बचने जैसी चीजों को संभालने देता है।

लेकिन आप इसे दो-चरणीय प्रक्रिया में यथोचित आसानी से कर सकते हैं। बयान प्राप्त करने के लिए, आप वैसा कर सकते हैं जैसा आपने पहले ही दिखाया है, और बस क्वेरी प्रिंट करें:

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

पैरामीटर नामों को देखने के लिए आप क्वेरी.स्टेटमेंट के साथ एक कदम और पास ले सकते हैं। ऊपर :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 थोड़ा बदल गया है क्योंकि मैंने यह जवाब लिखा है।
हनीले

10

Psycopg2 का उपयोग करके पोस्टग्रैसकल बैकएंड के लिए, आप 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 के साथ काम करता है। यह समाधान पैरामीटर को क्वेरी के साथ नहीं मिलाता है (जैसा कि मूल लेखक द्वारा अनुरोध किया गया है), लेकिन SQL क्वेरी स्ट्रिंग और विभिन्न SQL बोलियों के लिए पैरामीटर शब्दकोशों उत्पन्न करने के लिए SQLAlchemy मॉडल का उपयोग करने का एक तरीका प्रदान करता है। उदाहरण ट्यूटोरियल पर आधारित है http://docs.sqlalchemy.org/en/rel_1_0/core/t.html.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())

हम चुनिंदा फ़ंक्शन का उपयोग करके क्वेरी स्टेटमेंट का उत्पादन कर सकते हैं ।

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

अगला, हम कथन को क्वेरी ऑब्जेक्ट में संकलित कर सकते हैं।

query = statement.compile()

डिफ़ॉल्ट रूप से, यह कथन मूल 'नाम' कार्यान्वयन का उपयोग करके संकलित किया गया है जो SQL डेटाबेस जैसे SQLite और Oracle के साथ संगत है। यदि आपको एक बोली निर्दिष्ट करने की आवश्यकता है जैसे PostgreSQL, तो आप कर सकते हैं

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

या यदि आप स्पष्ट रूप से बोली को SQLite के रूप में निर्दिष्ट करना चाहते हैं, तो आप 'qmark' से 'नाम' के लिए paramstyle बदल सकते हैं।

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 साल पहले के एंडीबेर के पोस्ट से बेहतर / अलग कैसे है?
पायोत्र डोब्रोगोस्ट

एंडबियर के उत्तर में DBSession के साथ एक क्वेरी स्टेटमेंट जेनरेट करने का एक उदाहरण शामिल है जबकि इस उत्तर में डिक्लेरेशन API और सेलेक्ट विधि का उपयोग करके एक उदाहरण शामिल है। एक निश्चित बोली के साथ क्वेरी स्टेटमेंट को संकलित करने के संबंध में, उत्तर समान हैं। मैं कच्चे प्रश्नों को उत्पन्न करने के लिए SQLAlchemy का उपयोग करता हूं और फिर उन्हें ट्विस्टर की अदबीपी के साथ निष्पादित करता हूं। इस उपयोग के मामले के लिए, बिना सत्र के क्वेरी को संकलित करने और क्वेरी स्ट्रिंग और मापदंडों को निकालने का तरीका जानना उपयोगी है।
एरिक

3

आप कनेक्शनईवेंट परिवार से घटनाओं का उपयोग कर सकते हैं : after_cursor_executeयाbefore_cursor_execute

SQLAlchemy में UsageRecipes @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

इसलिए, इन अलग-अलग उत्तरों के बहुत कम बिट्स को एक साथ डालते हुए, मुझे जो चाहिए वह आया: कोड को छोड़ने का एक सरल सेट और कभी-कभी लेकिन मज़बूती से (यानी सभी डेटा प्रकारों को संभालता है) सटीक, संकलित एसक्यूएल को मेरे पास भेजा केवल क्वेरी से पूछताछ करके बैकएंड को पोस्ट करता है:

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

स्टेटमेंट आपको यह नहीं दिखाता है कि पैरामीटर क्या हैं, यदि आपके पास कुछ प्रकार के फ़िल्टर सेट हैं।
हैनले
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.