Làm cách nào để tạo Dạng xem SQL với SQLAlchemy?


82

Có cách nào "Pythonic" (ý tôi là không có truy vấn "thuần SQL") để xác định chế độ xem SQL với SQLAlchemy?

Câu trả lời:


69

Cập nhật: Xem thêm công thức sử dụng SQLAlchemy tại đây

Theo tôi biết, việc tạo chế độ xem (chỉ đọc, không được hiện thực hóa) không được hỗ trợ. Nhưng việc thêm chức năng này trong SQLAlchemy 0.7 rất đơn giản (tương tự như ví dụ tôi đã đưa ra ở đây ). Bạn chỉ cần viết một phần mở rộng trình biên dịch CreateView . Với phần mở rộng này, sau đó bạn có thể viết (giả sử đó tlà một đối tượng bảng có một cột id)

createview = CreateView('viewname', t.select().where(t.c.id>5))
engine.execute(createview)

v = Table('viewname', metadata, autoload=True)
for r in engine.execute(v.select()):
    print r

Đây là một ví dụ hoạt động:

from sqlalchemy import Table
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql.expression import Executable, ClauseElement

class CreateView(Executable, ClauseElement):
    def __init__(self, name, select):
        self.name = name
        self.select = select

@compiles(CreateView)
def visit_create_view(element, compiler, **kw):
    return "CREATE VIEW %s AS %s" % (
         element.name,
         compiler.process(element.select, literal_binds=True)
         )

# test data
from sqlalchemy import MetaData, Column, Integer
from sqlalchemy.engine import create_engine
engine = create_engine('sqlite://')
metadata = MetaData(engine)
t = Table('t',
          metadata,
          Column('id', Integer, primary_key=True),
          Column('number', Integer))
t.create()
engine.execute(t.insert().values(id=1, number=3))
engine.execute(t.insert().values(id=9, number=-3))

# create view
createview = CreateView('viewname', t.select().where(t.c.id>5))
engine.execute(createview)

# reflect view and print result
v = Table('viewname', metadata, autoload=True)
for r in engine.execute(v.select()):
    print r

Nếu bạn muốn, bạn cũng có thể chuyên về một phương ngữ, ví dụ:

@compiles(CreateView, 'sqlite')
def visit_create_view(element, compiler, **kw):
    return "CREATE VIEW IF NOT EXISTS %s AS %s" % (
         element.name,
         compiler.process(element.select, literal_binds=True)
         )

Tôi có thể sử dụng map v với orm.mapper không? như v = Table('viewname', metadata, autoload=True) class ViewName(object): def __init__(self, name): self.name = name mapper(ViewName, v) trên là có thể? Bởi vì tôi sẽ sử dụng Chế độ xem với phiên.
Syed Habib M

1
@SyedHabibM: vâng, điều này là có thể. Tuy nhiên, bạn phải đặt khóa chính theo cách thủ công, chẳng hạn như orm.mapper(ViewName, v, primary_key=pk, properties=prop)vị trí pkpropkhóa chính (hoặc các khóa) và thuộc tính của bạn tương ứng. Xem docs.sqlalchemy.org/en/latest/orm/… .
stephan 12/1213

2
@SyedHabibM: bạn cũng có thể thực hiện những gì stephan đã đề cập khi bạn sử dụng bảng tự động tải bằng cách ghi đè đặc điểm kỹ thuật cột và chỉ định PK:v = Table('viewname', metadata, Column('my_id_column', Integer, primary_key=True), autoload=True)
van

@SyedHabibMI đã trả lời câu hỏi tương ứng của bạn stackoverflow.com/q/20518521/92092 với một ví dụ hoạt động ngay bây giờ. Tôi cũng sẽ thêm bình luận của van ở đó.
stephan 12/1213

27

Câu trả lời của stephan là một câu trả lời hay và bao gồm hầu hết các cơ sở, nhưng điều khiến tôi không hài lòng là thiếu tích hợp với phần còn lại của SQLAlchemy (ORM, tự động thả, v.v.). Sau nhiều giờ thử nghiệm và tổng hợp kiến ​​thức từ tất cả các góc của internet, tôi đã nghĩ ra những điều sau:

import sqlalchemy_views
from sqlalchemy import Table
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql.ddl import DropTable


class View(Table):
    is_view = True


class CreateView(sqlalchemy_views.CreateView):
    def __init__(self, view):
        super().__init__(view.__view__, view.__definition__)


@compiles(DropTable, "postgresql")
def _compile_drop_table(element, compiler, **kwargs):
    if hasattr(element.element, 'is_view') and element.element.is_view:
        return compiler.visit_drop_view(element)

    # cascade seems necessary in case SQLA tries to drop 
    # the table a view depends on, before dropping the view
    return compiler.visit_drop_table(element) + ' CASCADE'

Lưu ý rằng tôi đang sử dụng sqlalchemy_viewsgói, chỉ để đơn giản hóa mọi thứ.

Xác định chế độ xem (ví dụ toàn cầu như các mô hình Bảng của bạn):

from sqlalchemy import MetaData, text, Text, Column


class SampleView:
    __view__ = View(
        'sample_view', MetaData(),
        Column('bar', Text, primary_key=True),
    )

    __definition__ = text('''select 'foo' as bar''')

# keeping track of your defined views makes things easier
views = [SampleView]

Ánh xạ các chế độ xem (bật chức năng ORM):

Thực hiện khi tải ứng dụng của bạn, trước bất kỳ truy vấn nào và sau khi thiết lập DB.

for view in views:
    if not hasattr(view, '_sa_class_manager'):
        orm.mapper(view, view.__view__)

Tạo các khung nhìn:

Thực hiện khi khởi tạo cơ sở dữ liệu, ví dụ sau khi gọi lệnh create_all ().

from sqlalchemy import orm


for view in views:
    db.engine.execute(CreateView(view))

Cách truy vấn một dạng xem:

results = db.session.query(SomeModel, SampleView).join(
    SampleView,
    SomeModel.id == SampleView.some_model_id
).all()

Điều này sẽ trả về chính xác những gì bạn mong đợi (danh sách các đối tượng mà mỗi đối tượng có một đối tượng SomeModel và một đối tượng SampleView).

Bỏ chế độ xem:

SampleView.__view__.drop(db.engine)

Nó cũng sẽ tự động bị loại bỏ trong cuộc gọi drop_all ().

Đây rõ ràng là một giải pháp rất khó hiểu nhưng trong mắt tôi, nó là giải pháp tốt nhất và sạch nhất hiện tại. Tôi đã thử nghiệm nó vài ngày qua và không có bất kỳ vấn đề nào. Tôi không chắc làm thế nào để thêm vào các mối quan hệ (đã gặp sự cố ở đó) nhưng nó không thực sự cần thiết, như đã trình bày ở trên trong truy vấn.

Nếu bất kỳ ai có bất kỳ ý kiến ​​đóng góp nào, phát hiện bất kỳ vấn đề bất ngờ nào hoặc biết cách tốt hơn để thực hiện công việc, vui lòng để lại bình luận hoặc cho tôi biết.

Điều này đã được thử nghiệm trên SQLAlchemy 1.2.6 và Python 3.6.


Đúng lúc điên rồ, chỉ là bản thân tôi đang giải quyết chuyện này. Đối với py 2.7 và SQLa 1.1.2 (đừng hỏi ...), chỉ thay đổi cần thiết là super(CreateView, self).__init__và cóclass SampleView(object)
Steven Dickinson

1
@Steven Dickinson nghe đúng rồi! Vâng, tôi nhận ra đây là một nhiệm vụ thực sự phổ biến, đó là lý do tại sao tôi ngạc nhiên khi tài liệu về nó quá nghèo nàn / lỗi thời / nông cạn. Nhưng này, tôi cho là từng bước một.
fgblomqvist

2
Đối với những người muốn thực hiện việc này một cách khai báo, tôi đã xác định các chế độ xem của mình trong một tệp riêng biệt từ các bảng của tôi với một phiên bản siêu dữ liệu khác: Base = declarative_base(metadata=db.MetaData()) class ViewSample(Base): __tablename__ = 'view_sample' Tôi vẫn bao gồm thuộc __definition__tính và gọi CreateView để tạo nó như được đề xuất trong bài đăng ban đầu. Cuối cùng, tôi đã phải sửa đổi phương pháp thả trang trí: if element.element.name.startswith('view_'): return compiler.visit_drop_view(element) bởi vì tôi không thể tìm cách thêm thuộc tính vào bảng nhúng.
Casey

23

Ngày nay, có một gói PyPI cho điều đó: Chế độ xem SQLAlchemy .

Từ Trang PyPI của nó:

>>> from sqlalchemy import Table, MetaData
>>> from sqlalchemy.sql import text
>>> from sqlalchemy_views import CreateView, DropView

>>> view = Table('my_view', metadata)
>>> definition = text("SELECT * FROM my_table")

>>> create_view = CreateView(view, definition, or_replace=True)
>>> print(str(create_view.compile()).strip())
CREATE OR REPLACE VIEW my_view AS SELECT * FROM my_table

Tuy nhiên, bạn đã yêu cầu không có truy vấn "thuần SQL" , vì vậy có thể bạn muốndefinition ở trên được tạo bằng đối tượng truy vấn SQLAlchemy.

May mắn thay, text()trong ví dụ trên cho thấy rõ rằng definitiontham số tới CreateViewlà một đối tượng truy vấn. Vì vậy, một cái gì đó như thế này sẽ hoạt động:

>>> from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey
>>> from sqlalchemy.sql import select
>>> from sqlalchemy_views import CreateView, DropView

>>> metadata = MetaData()

>>> users = Table('users', metadata,
...     Column('id', Integer, primary_key=True),
...     Column('name', String),
...     Column('fullname', String),
... )

>>> addresses = Table('addresses', metadata,
...   Column('id', Integer, primary_key=True),
...   Column('user_id', None, ForeignKey('users.id')),
...   Column('email_address', String, nullable=False)
...  )

Đây là một chút thú vị:

>>> view = Table('my_view', metadata)
>>> definition = select([users, addresses]).where(
...     users.c.id == addresses.c.user_id
... )
>>> create_view = CreateView(view, definition, or_replace=True)
>>> print(str(create_view.compile()).strip())
CREATE OR REPLACE VIEW my_view AS SELECT users.id, users.name,
users.fullname, addresses.id, addresses.user_id, addresses.email_address 
FROM users, addresses 
WHERE users.id = addresses.user_id

17

SQLAlchemy-utils vừa thêm chức năng này trong 0.33.6 (có sẵn trong pypi). Nó có các khung nhìn, các khung nhìn cụ thể hóa và nó tích hợp với ORM. Nó chưa được lập thành tài liệu, nhưng tôi đang sử dụng thành công lượt xem + ORM.

Bạn có thể sử dụng thử nghiệm của họ làm ví dụ cho cả chế độ xem thông thường và chế độ xem cụ thể hóa bằng ORM.

Để tạo chế độ xem, sau khi bạn cài đặt gói, hãy sử dụng mã sau từ thử nghiệm ở trên làm cơ sở cho chế độ xem của bạn:

class ArticleView(Base):
    __table__ = create_view(
        name='article_view',
        selectable=sa.select(
            [
                Article.id,
                Article.name,
                User.id.label('author_id'),
                User.name.label('author_name')
            ],
            from_obj=(
                Article.__table__
                    .join(User, Article.author_id == User.id)
            )
        ),
        metadata=Base.metadata
    )

Trong trường hợp Basedeclarative_base, saSQLAlchemygói, và create_viewlà một hàm từ sqlalchemy_utils.view.


Bạn đã tìm ra cách sử dụng nó cùng với alembic chưa?
Jorge Leitao

@JorgeLeitao Rất tiếc là tôi chưa thử.
bustawin

1

Tôi không thể tìm thấy câu trả lời ngắn gọn và tiện dụng.

Tôi không cần thêm chức năng của Chế độ xem (nếu có), vì vậy tôi chỉ đơn giản coi chế độ xem như một bảng bình thường như các định nghĩa bảng khác.

Vì vậy, về cơ bản tôi có a.pynơi xác định tất cả các bảng và chế độ xem, những thứ liên quan đến sql và main.pynơi tôi nhập các lớp đó từa.py và sử dụng chúng.

Đây là những gì tôi thêm vào a.pyvà hoạt động:

class A_View_From_Your_DataBase(Base):
    __tablename__ = 'View_Name'
    keyword = Column(String(100), nullable=False, primary_key=True)

Đáng chú ý, bạn cần thêm thuộc primary_keytính mặc dù không có khóa chính trong chế độ xem.


-9

SQL View không có SQL thuần túy? Bạn có thể tạo một lớp hoặc một hàm để triển khai một dạng xem đã xác định.

function get_view(con):
  return Table.query.filter(Table.name==con.name).first()

2
Xin lỗi, nhưng đó không phải là những gì tôi yêu cầu. Tiếng Anh của tôi không hoàn hảo, tôi xin lỗi nếu bạn hiểu nhầm :)
Thibaut D.
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.