SQLAlchemy로 SQL 뷰를 정의하는 "Pythonic"방법 ( "순수 SQL"쿼리 없음)이 있습니까?
답변:
업데이트 : 여기 에서 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)
where pk
and prop
are your primary key (or keys) and properties 각각과 같이 기본 키를 수동으로 설정해야합니다 . docs.sqlalchemy.org/en/latest/orm/…을 참조하십시오 .
v = Table('viewname', metadata, Column('my_id_column', Integer, primary_key=True), autoload=True)
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'
나는 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]
뷰 매핑 (ORM 기능 활성화) :
앱을로드 할 때, 쿼리 전 및 DB 설정 후에 수행하십시오.
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
매개 변수 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을 성공적으로 사용하고 있습니다.
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
는 IS declarative_base
, sa
은 IS 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)
위와 같이 가능합니까? 세션과 함께보기를 사용하기 때문입니다.