Làm cách nào để lấy truy vấn SQL thô, đã biên dịch từ biểu thức SQLAlchemy?


102

Tôi có một đối tượng truy vấn SQLAlchemy và muốn lấy văn bản của câu lệnh SQL đã biên dịch, với tất cả các tham số của nó bị ràng buộc (ví dụ: không %shoặc các biến khác đang chờ bị ràng buộc bởi trình biên dịch câu lệnh hoặc công cụ phương ngữ MySQLdb, v.v.).

Gọi str()trên truy vấn cho thấy một cái gì đó như thế này:

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

Tôi đã thử tìm kiếm trong query._params nhưng đó là một câu lệnh trống. Tôi đã viết trình biên dịch của riêng mình bằng cách sử dụng ví dụ này về trình sqlalchemy.ext.compiler.compilestrang trí nhưng ngay cả câu lệnh ở đó vẫn có%s nơi tôi muốn dữ liệu.

Tôi hoàn toàn không thể tìm ra khi nào các tham số của tôi được trộn vào để tạo truy vấn; khi kiểm tra đối tượng truy vấn, chúng luôn là một từ điển trống (mặc dù truy vấn thực thi tốt và công cụ in ra khi bạn bật ghi nhật ký tiếng vọng).

Tôi bắt đầu nhận được thông báo rằng SQLAlchemy không muốn tôi biết truy vấn cơ bản, vì nó phá vỡ bản chất chung của giao diện API biểu thức tất cả các DB-API khác nhau. Tôi không phiền nếu truy vấn được thực thi trước khi tôi tìm ra nó là gì; Tôi chỉ muốn biết!

Câu trả lời:


106

Blog này cung cấp một câu trả lời cập nhật.

Trích dẫn từ bài đăng trên blog, điều này được đề xuất và làm việc cho tôi.

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

Trong đó q được định nghĩa là:

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

Hoặc chỉ bất kỳ loại session.query () nào.

Cảm ơn Nicolas Cadou về câu trả lời! Tôi hy vọng nó sẽ giúp những người khác tìm kiếm ở đây.


2
Có cách nào dễ dàng để lấy các giá trị làm từ điển không?
Damien

6
@Damien được c = q.statement.compile(...), bạn chỉ có thể nhận đượcc.params
Hannele

1
Bài đăng được gắn thẻ mysql, vì vậy chi tiết postgresql trong câu trả lời này không thực sự liên quan.
Hannele

4
Nếu tôi hiểu OP chính xác, anh ta muốn truy vấn cuối cùng. Việc in với chỉ định một phương ngữ (ở đây là postgres) vẫn cung cấp cho tôi các trình giữ chỗ thay vì các giá trị chữ. Câu trả lời của @ Matt thực hiện công việc. Việc lấy SQL với trình giữ chỗ có thể đạt được đơn giản hơn với as_scalar()-method of Query.
Patrick B.

1
@PatrickB. Tôi đồng ý. Câu trả lời của Matt nên được coi là câu trả lời "chính xác". Tôi nhận được kết quả tương tự như thế này bằng cách làm str(q).
André C. Andersen

90

Các tài liệu hướng dẫn sử dụng literal_bindsđể in một truy vấn qbao gồm các thông số:

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

cách tiếp cận ở trên có lưu ý rằng nó chỉ được hỗ trợ cho các kiểu cơ bản, chẳng hạn như int và chuỗi, và hơn nữa nếu một bindparam () không có giá trị đặt trước được sử dụng trực tiếp, nó cũng sẽ không thể chuỗi ký tự đó.

Tài liệu cũng đưa ra cảnh báo này:

Không bao giờ sử dụng kỹ thuật này với nội dung chuỗi nhận được từ đầu vào không đáng tin cậy, chẳng hạn như từ biểu mẫu web hoặc các ứng dụng do người dùng nhập khác. Các cơ sở của SQLAlchemy để ép buộc các giá trị Python thành các giá trị chuỗi SQL trực tiếp không an toàn trước đầu vào không đáng tin cậy và không xác thực kiểu dữ liệu được truyền. Luôn sử dụng các tham số ràng buộc khi gọi các câu lệnh SQL không phải DDL theo chương trình đối với cơ sở dữ liệu quan hệ.


Cảm ơn bạn! Điều này cực kỳ hữu ích, cho phép tôi sử dụng hàm read_sql của gấu trúc một cách dễ dàng!
Justin Palmer

24

Điều này sẽ hoạt động với 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
Cảm ơn vì điều đó! Đáng buồn là tôi đang sử dụng MySQL nên phương ngữ của tôi là "vị trí" và cần có danh sách tham số hơn là từ điển. Hiện đang cố gắng lấy ví dụ của bạn để giải quyết vấn đề đó ..
cce

Vui lòng không sử dụng adapttheo cách này. Ở lần gọi tối thiểu, hãy chuẩn bị () trên giá trị trả về từ nó mỗi lần, cung cấp kết nối như một đối số, vì vậy nó có thể thực hiện trích dẫn thích hợp.
Alex Gaynor

@Alex: Cách chính xác để thực hiện trích dẫn thích hợp với psycopg là gì? (ngoài kêu gọi chuẩn bị () trên giá trị trả về, mà bạn dường như ngụ ý không phải là tối ưu)
albertov

Xin lỗi, tôi nghĩ rằng cụm từ của tôi không tốt, miễn là bạn gọi obj.prepare (kết nối) thì bạn sẽ ổn. Điều này là do các API "tốt" mà libpq cung cấp để trích dẫn yêu cầu kết nối (và nó cung cấp những thứ như mã hóa cho các chuỗi unicode).
Alex Gaynor

1
Cảm ơn. Tôi đã thử gọi điện thoại preparetrên giá trị trả về nhưng dường như nó không có phương pháp đó: AttributeError: 'psycopg2._psycopg.AsIs' object has no attribute 'prepare'. Tôi đang sử dụng psycopg2 2.2.1 BTW
albertov

18

Đối với phần phụ trợ MySQLdb, tôi đã sửa đổi câu trả lời tuyệt vời của albertov (cảm ơn rất nhiều!) Một chút. Tôi chắc chắn họ có thể được sáp nhập để kiểm tra xem comp.positionalđã Truenhưng đó là hơi vượt quá phạm vi của câu hỏi này.

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)

Tuyệt vời! Tôi chỉ cần danh sách tham số ràng buộc được gửi đến MySQL và sửa đổi ở trên để return tuple(params)hoạt động như một sự quyến rũ! Bạn đã cứu tôi không biết bao nhiêu giờ phải đi xuống một con đường vô cùng đau đớn.
horcle_buzz

15

Điều đó là, sqlalchemy không bao giờ trộn dữ liệu với truy vấn của bạn. Truy vấn và dữ liệu được chuyển riêng đến trình điều khiển cơ sở dữ liệu bên dưới của bạn - việc nội suy dữ liệu xảy ra trong cơ sở dữ liệu của bạn.

Sqlalchemy chuyển truy vấn như bạn đã thấy trong str(myquery) cơ sở dữ liệu và các giá trị sẽ đi trong một bộ dữ liệu riêng biệt.

Bạn có thể sử dụng một số phương pháp mà bạn tự nội suy dữ liệu với truy vấn (như albertov đề xuất bên dưới), nhưng đó không phải là điều mà sqlalchemy đang thực thi.


tại sao nó không giống nhau? Tôi hiểu DB-API đang thực hiện các giao dịch, có thể sắp xếp lại các truy vấn, v.v., nhưng nó có thể sửa đổi truy vấn của tôi nhiều hơn thế này không?
cce

1
@cce: bạn đang cố gắng tìm truy vấn cuối cùng. SELECT id WHERE date_added <= %s AND date_added >= %s ORDER BY count DESC truy vấn cuối cùng. Những %sđược gửi đến các cơ sở dữ liệu bằng cách SQLAlchemy - SQLAlchemy KHÔNG BAO GIỜ đặt dữ liệu thực tế ở vị trí của% s
nosklo

@cce: Một số module dbapi không làm điều đó một trong hai - đó thường được thực hiện bởi các cơ sở dữ liệu riêng của mình
nosklo

1
aha Tôi hiểu những gì bạn đang nói, cảm ơn - đào sâu hơn sqlalchemy.dialects.mysql.mysqldb, do_executemany()chuyển câu lệnh & các tham số riêng biệt đến con trỏ MySQLdb. yay chuyển hướng!
cce

11

Trước tiên, hãy để tôi mở đầu bằng cách nói rằng tôi cho rằng bạn đang làm điều này chủ yếu vì mục đích gỡ lỗi - tôi không khuyên bạn nên cố gắng sửa đổi câu lệnh bên ngoài API thông thạo SQLAlchemy.

Thật không may, dường như không có một cách đơn giản nào để hiển thị câu lệnh đã biên dịch với các tham số truy vấn được bao gồm. SQLAlchemy không thực sự đưa các tham số vào câu lệnh - chúng được chuyển vào cơ sở dữ liệu dưới dạng từ điển . Điều này cho phép thư viện dành riêng cho cơ sở dữ liệu xử lý những thứ như thoát các ký tự đặc biệt để tránh chèn SQL.

Nhưng bạn có thể thực hiện điều này theo quy trình hai bước một cách hợp lý dễ dàng. Để nhận được câu lệnh, bạn có thể làm như bạn đã hiển thị và chỉ cần in truy vấn:

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

Bạn có thể tiến gần hơn một bước với query.statement, để xem tên tham số. Lưu ý :id_1bên dưới so với %sbên trên - không thực sự là một vấn đề trong ví dụ rất đơn giản này, nhưng có thể là chìa khóa trong một câu lệnh phức tạp hơn.

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

Sau đó, bạn có thể nhận các giá trị thực của các tham số bằng cách lấy thuộc paramstính của câu lệnh đã biên dịch:

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

Điều này ít nhất đã hoạt động đối với phần phụ trợ MySQL; Tôi hy vọng nó cũng đủ chung cho PostgreSQL mà không cần sử dụng psycopg2.


Từ bên trong trình gỡ lỗi PyCharm, phần sau hoạt động với tôi ... qry.compile (). Params
Ben

Thật thú vị, có thể SQLAlchemy đã thay đổi một chút kể từ khi tôi viết câu trả lời này.
Hannele

10

Đối với phần phụ trợ postgresql sử dụng psycopg2, bạn có thể lắng nghe do_executesự kiện, sau đó sử dụng con trỏ, câu lệnh và nhập các tham số cưỡng chế cùng với Cursor.mogrify()nội tuyến các tham số. Bạn có thể trả về True để ngăn việc thực thi truy vấn thực tế.

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

Sử dụng mẫu:

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

Giải pháp sau sử dụng Ngôn ngữ biểu thức SQLAlchemy và hoạt động với SQLAlchemy 1.1. Giải pháp này không trộn các tham số với truy vấn (theo yêu cầu của tác giả gốc), nhưng cung cấp cách sử dụng mô hình SQLAlchemy để tạo chuỗi truy vấn SQL và từ điển tham số cho các phương ngữ SQL khác nhau. Ví dụ dựa trên hướng dẫn http://docs.sqlalchemy.org/en/rel_1_0/core/tutorial.html

Cho lớp học,

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

chúng ta có thể tạo một câu lệnh truy vấn bằng cách sử dụng hàm select .

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

Tiếp theo, chúng ta có thể biên dịch câu lệnh thành một đối tượng truy vấn.

query = statement.compile()

Theo mặc định, câu lệnh được biên dịch bằng cách sử dụng triển khai 'được đặt tên' cơ bản tương thích với cơ sở dữ liệu SQL như SQLite và Oracle. Nếu bạn cần chỉ định một phương ngữ chẳng hạn như PostgreSQL, bạn có thể làm

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

Hoặc nếu bạn muốn chỉ định rõ ràng phương ngữ là SQLite, bạn có thể thay đổi kiểu tham số từ 'qmark' thành 'được đặt tên'.

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

Từ đối tượng truy vấn, chúng ta có thể trích xuất chuỗi truy vấn và các tham số truy vấn

query_str = str(query)
query_params = query.params

và cuối cùng thực hiện truy vấn.

conn.execute( query_str, query_params )

Câu trả lời này tốt hơn / khác như thế nào so với câu trả lời của AndyBarr được đăng 2 năm trước đó?
Piotr Dobrogost

Câu trả lời của AndyBarr bao gồm một ví dụ về cách tạo câu lệnh truy vấn với DBSession trong khi câu trả lời này bao gồm một ví dụ sử dụng phương thức chọn và API khai báo. Đối với việc biên dịch câu lệnh truy vấn với một phương ngữ nhất định, các câu trả lời đều giống nhau. Tôi sử dụng SQLAlchemy để tạo các truy vấn thô và sau đó thực thi chúng bằng adbapi của Twister. Đối với trường hợp sử dụng này, biết cách biên dịch truy vấn mà không có phiên và trích xuất chuỗi truy vấn và các tham số là hữu ích.
eric

3

Bạn có thể sử dụng các sự kiện từ họ ConnectionEvents : after_cursor_executehoặc before_cursor_execute.

Trong sqlalchemy UsageRecipes của @zzzeek, ​​bạn có thể tìm thấy ví dụ sau:

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

Ở đây bạn có thể nhận được quyền truy cập vào tuyên bố


2

Vì vậy, khi tập hợp rất nhiều câu trả lời khác nhau, tôi đã nghĩ ra thứ tôi cần: một bộ mã đơn giản để truy cập và thỉnh thoảng nhưng đáng tin cậy (tức là xử lý tất cả các kiểu dữ liệu) lấy chính xác, đã biên dịch SQL được gửi đến Postgres backend bằng cách chỉ thẩm vấn chính truy vấn:

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

Tôi nghĩ .statement có thể sẽ thực hiện thủ thuật: 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

Tuyên bố không hiển thị cho bạn các tham số là gì, nếu bạn đã thiết lập một số loại bộ lọc.
Hannele
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.