SQLAlchemy로 SQL보기를 만드는 방법은 무엇입니까?


답변:


69

업데이트 : 여기 에서 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와 함께 map v를 사용할 수 있습니까? v = Table('viewname', metadata, autoload=True) class ViewName(object): def __init__(self, name): self.name = name mapper(ViewName, v) 위와 같이 가능합니까? 세션과 함께보기를 사용하기 때문입니다.
Syed Habib M

1
@SyedHabibM : 네, 가능합니다. 그러나 orm.mapper(ViewName, v, primary_key=pk, properties=prop)where pkand propare your primary key (or keys) and properties 각각과 같이 기본 키를 수동으로 설정해야합니다 . docs.sqlalchemy.org/en/latest/orm/…을 참조하십시오 .
스테판

2
@SyedHabibM : 열 사양을 재정의하고 PK를 지정하여 자동로드 된 테이블을 사용할 때 stephan이 언급 한 것을 수행 할 수 있습니다 .v = Table('viewname', metadata, Column('my_id_column', Integer, primary_key=True), autoload=True)
van

@SyedHabibMI는 현재 작업 예제를 사용 하여 각 질문에 stackoverflow.com/q/20518521/92092에 답변했습니다 . 거기에 밴의 코멘트도 추가하겠습니다.
스테판

27

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에서 테스트되었습니다.


미친 타이밍, 이걸 직접 다루었 어. 평 2.7 SQLA 1.1.2의 경우에만 필요합니다 변화가 있었다 (... 요구하지 않는다)에 super(CreateView, self).__init__와 가진class SampleView(object)
스티븐 디킨슨

1
@Steven Dickinson 맞아요! 네, 이것이 정말 일반적인 작업이라고 생각했습니다. 그래서 그 문서가 너무 형편없고, 구식이고, 얕은 것에 놀랐습니다. 하지만 한 번에 한 걸음 씩.
fgblomqvist

2
이 작업을 선언적으로 수행하려는 사람들을 위해 다른 메타 데이터 인스턴스를 사용하여 내 테이블과 별도의 파일에 내 뷰를 정의했습니다. 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) . 포함 된 테이블에 속성을 추가하는 방법을 찾을 수 없었기 때문입니다.
Casey

23

요즘에는이를위한 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"쿼리아닌 쿼리 를 요청 했으므로 definitionSQLAlchemy 쿼리 개체를 사용 하여 위의 쿼리를 만들고 싶을 것입니다 .

운 좋게도 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

17

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.


alembic과 함께 사용하는 방법을 찾았습니까?
Jorge Leitao

@JorgeLeitao 불행히도 나는 시도하지 않았습니다.
bustawin

1

짧고 편리한 답을 찾지 못했습니다.

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뷰에 기본 키가 없더라도 속성 을 추가해야합니다 .


-9

순수한 SQL이없는 SQL보기? 정의 된 뷰를 구현하는 클래스 또는 함수를 만들 수 있습니다.

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

2
미안하지만 내가 요청한 것이 아닙니다. 내 영어는 : 당신이 오해하는 경우 미안 해요, 완벽하지
의 Thibaut D.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.