如何使用SQLAlchemy创建SQL视图?

Thi*_* D. 65 python postgresql sqlalchemy

一切都在标题中.是否有"Pythonic"方式(我的意思是,没有"纯SQL"查询)来使用SQLAlchemy定义SQL视图?

谢谢你的帮助,

ste*_*han 62

更新:另请参阅此处的SQLAlchemy使用方法

据我所知,开箱即用,不支持创建(只读非物化)视图.但是在SQLAlchemy 0.7中添加此功能很简单(类似于我在这里给出的示例).您只需编写编译器扩展即可 CreateView.使用此扩展,您可以编写(假设这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
Run Code Online (Sandbox Code Playgroud)

这是一个工作示例:

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
Run Code Online (Sandbox Code Playgroud)

如果你愿意,你也可以专注于方言,例如

@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)
         )
Run Code Online (Sandbox Code Playgroud)

  • @SyedHabibM:当你使用自动加载的表时,你可以通过覆盖列规范并指定PK来执行*stephan*所提到的:`v = Table('viewname',metadata,Column('my_id_column',Integer,primary_key = True) ,autoload = True)` (2认同)

fgb*_*ist 18

stephan的回答很好,涵盖了大多数基础,但让我不满意的是缺乏与SQLAlchemy的其余部分(ORM,自动删除等)的集成.经过几个小时的实验并将来自互联网各个角落的知识拼凑起来后,我想出了以下内容:

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'
Run Code Online (Sandbox Code Playgroud)

请注意,我正在使用该sqlalchemy_views软件包,只是为了简化操作.

定义视图(例如全局像您的表模型):

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]
Run Code Online (Sandbox Code Playgroud)

映射视图(启用ORM功能):

在任何查询之前和设置数据库之后加载您的应用程序时.

for view in views:
    if not hasattr(view, '_sa_class_manager'):
        orm.mapper(view, view.__view__)
Run Code Online (Sandbox Code Playgroud)

创建视图:

在初始化数据库时执行,例如在create_all()调用之后.

from sqlalchemy import orm


for view in views:
    db.engine.execute(CreateView(view))
Run Code Online (Sandbox Code Playgroud)

如何查询视图:

results = db.session.query(SomeModel, SampleView).join(
    SampleView,
    SomeModel.id == SampleView.some_model_id
).all()
Run Code Online (Sandbox Code Playgroud)

这将完全返回您所期望的(每个对象的列表,每个对象具有SomeModel对象和SampleView对象).

删除视图:

SampleView.__view__.drop(db.engine)
Run Code Online (Sandbox Code Playgroud)

它也会在drop_all()调用期间自动被删除.

这显然是一个非常讨厌的解决方案,但在我看来,它是目前最好的一个,也是最干净的.我过去几天测试了它,没有任何问题.我不确定如何添加关系(遇到问题),但这并不是必需的,如上面的查询所示.

如果有人有任何意见,发现任何意外问题,或知道更好的做事方式,请发表评论或让我知道.

这是在SQLAlchemy 1.2.6和Python 3.6上测试的.

  • 对于那些希望以声明方式执行此操作的人,我使用不同的元数据实例在与我的表不同的文件中定义了我的视图:`Base = declarative_base(metadata=db.MetaData()) class ViewSample(Base): __tablename__ = 'view_sample'`我仍然包含了 `__definition__` 属性,并按照原始帖子中的建议调用 CreateView 来创建它。最后,我不得不修改 drop 装饰方法:`if element.element.name.startswith('view_'): return compiler.visit_drop_view(element)` 因为我找不到将属性添加到嵌入表的方法。 (2认同)

Leo*_*ael 16

这些天有一个PyPI包:SQLAlchemy Views.

从它的PyPI页面:

>>> 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
Run Code Online (Sandbox Code Playgroud)

但是,您要求没有"纯SQL"查询,因此您可能希望definition使用SQLAlchemy查询对象创建上述查询.

幸运的是,text()上面的示例清楚地表明definition参数CreateView是这样的查询对象.所以像这样的东西应该工作:

>>> 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)
...  )
Run Code Online (Sandbox Code Playgroud)

这是有趣的一点:

>>> 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
Run Code Online (Sandbox Code Playgroud)


bus*_*win 6

SQLAlchemy-utils 刚刚在0.33.6中添加了此功能(可在pypi中使用)。它具有视图,实例化视图,并且与ORM集成。它尚未记录,但我已成功使用视图+ ORM。

您可以使用 ORM 将其测试用作常规视图和实例化视图的示例

要创建视图,请在安装软件包后,使用上面测试中的以下代码作为视图的基础:

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
    )
Run Code Online (Sandbox Code Playgroud)

的位置Basedeclarative_basesaSQLAlchemy包,并且create_view是中的函数sqlalchemy_utils.view