是否有“ Pythonic”方式(我的意思是,没有“ pure SQL”查询)来使用SQLAlchemy定义SQL视图?
Answers:
更新:又见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
这是一个工作示例:
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
如果需要,您也可以专门学习方言,例如
@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)
)
orm.mapper(ViewName, v, primary_key=pk, properties=prop)
wherepk
和prop
分别是主键(或多个键)和属性。参见docs.sqlalchemy.org/en/latest/orm/…。
v = Table('viewname', metadata, Column('my_id_column', Integer, primary_key=True), autoload=True)
斯蒂芬的答案是一个很好的答案,涵盖了大多数基础知识,但是令我感到不满意的是缺乏与其余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'
请注意,我正在使用该sqlalchemy_views
软件包,只是为了简化操作。
定义视图(例如,像Table模型一样全局):
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]
映射视图(启用ORM功能):
在加载应用程序时,进行任何查询之前以及在设置数据库之后执行此操作。
for view in views:
if not hasattr(view, '_sa_class_manager'):
orm.mapper(view, view.__view__)
创建视图:
初始化数据库时执行此操作,例如在create_all()调用之后。
from sqlalchemy import orm
for view in views:
db.engine.execute(CreateView(view))
如何查询视图:
results = db.session.query(SomeModel, SampleView).join(
SampleView,
SomeModel.id == SampleView.some_model_id
).all()
这将完全返回您期望的结果(每个对象都有SomeModel对象和SampleView对象的列表)。
删除视图:
SampleView.__view__.drop(db.engine)
在drop_all()调用期间,它也会自动删除。
这显然是一个非常棘手的解决方案,但在我看来,这是目前最好的解决方案。这几天我已经对其进行了测试,并且没有任何问题。我不确定如何添加关系(在那里遇到问题),但这并不是真正必要的,如上面查询中所示。
如果任何人有任何意见,发现任何意外问题或知道更好的处理方法,请发表评论或让我知道。
这已在SQLAlchemy 1.2.6和Python 3.6上进行了测试。
super(CreateView, self).__init__
class SampleView(object)
Base = declarative_base(metadata=db.MetaData()) class ViewSample(Base): __tablename__ = 'view_sample'
我仍然包括该__definition__
属性,并调用CreateView来按照原始文章中的建议创建它。最后,我不得不修改放置修饰的方法: if element.element.name.startswith('view_'): return compiler.visit_drop_view(element)
因为我找不到将属性添加到嵌入式表的方法。
这些天来有一个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
但是,您要求使用非“纯SQL”查询,因此您可能希望definition
使用SQLAlchemy查询对象创建以上内容。
幸运的是,text()
上面的示例中的清楚地表明,definition
to的参数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)
... )
这是有趣的一点:
>>> 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
SQLAlchemy-utils刚刚在0.33.6中添加了此功能(可在pypi中使用)。它具有视图,实例化视图,并且与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
)
的位置Base
是declarative_base
,sa
是SQLAlchemy
包,并且create_view
是中的函数sqlalchemy_utils.view
。
我找不到一个简短方便的答案。
我不需要View的额外功能(如果有的话),因此我只是将视图视为普通表作为其他表定义。
因此,基本上,我在a.py
哪里定义了所有表和视图,与sql有关的东西,以及从main.py
何处导入a.py
和使用这些类的地方。
这是我添加a.py
并起作用的内容:
class A_View_From_Your_DataBase(Base):
__tablename__ = 'View_Name'
keyword = Column(String(100), nullable=False, primary_key=True)
值得注意的是,primary_key
即使视图中没有主键,也需要添加属性。
没有纯SQL的SQL视图?您可以创建一个类或函数来实现已定义的视图。
function get_view(con):
return Table.query.filter(Table.name==con.name).first()
v = Table('viewname', metadata, autoload=True) class ViewName(object): def __init__(self, name): self.name = name mapper(ViewName, v)
上面是可能的吗?因为我将在会话中使用View。