Czy istnieje sposób „Pythonic” (mam na myśli brak zapytania „czystego SQL”) na zdefiniowanie widoku SQL za pomocą SQLAlchemy?
Czy istnieje sposób „Pythonic” (mam na myśli brak zapytania „czystego SQL”) na zdefiniowanie widoku SQL za pomocą SQLAlchemy?
Odpowiedzi:
Aktualizacja: Zobacz także przepis na użycie SQLAlchemy tutaj
Tworzenie niezmaterializowanego widoku (tylko do odczytu) nie jest obsługiwane po wyjęciu z pudełka, o ile wiem. Ale dodanie tej funkcjonalności w SQLAlchemy 0.7 jest proste (podobnie jak w przykładzie, który tu podałem ). Musisz tylko napisać rozszerzenie kompilatora CreateView
. Dzięki temu rozszerzeniu możesz następnie pisać (zakładając, że t
jest to obiekt tabeli z kolumną 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
Oto działający przykład:
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
Jeśli chcesz, możesz też specjalizować się w dialekcie, np
@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)
gdzie pk
i prop
są odpowiednio klucz podstawowy (lub klucze) i właściwości. Zobacz docs.sqlalchemy.org/en/latest/orm/… .
v = Table('viewname', metadata, Column('my_id_column', Integer, primary_key=True), autoload=True)
Odpowiedź Stephana jest dobra i obejmuje większość baz, ale niezadowolony był brak integracji z resztą SQLAlchemy (ORM, automatyczne usuwanie itp.). Po godzinach eksperymentowania i zbierania wiedzy ze wszystkich zakątków internetu wymyśliłem:
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'
Zwróć uwagę, że korzystam z sqlalchemy_views
pakietu, aby uprościć sprawę.
Definiowanie widoku (np. Globalnie jak modele 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]
Mapowanie widoków (włącz funkcjonalność ORM):
Zrób to podczas ładowania aplikacji, przed zapytaniami i po skonfigurowaniu bazy danych.
for view in views:
if not hasattr(view, '_sa_class_manager'):
orm.mapper(view, view.__view__)
Tworzenie widoków:
Zrób podczas inicjalizacji bazy danych, np. Po wywołaniu create_all ().
from sqlalchemy import orm
for view in views:
db.engine.execute(CreateView(view))
Jak zapytać o widok:
results = db.session.query(SomeModel, SampleView).join(
SampleView,
SomeModel.id == SampleView.some_model_id
).all()
To zwróci dokładnie to, czego oczekujesz (listę obiektów, z których każdy ma obiekt SomeModel i obiekt SampleView).
Upuszczanie widoku:
SampleView.__view__.drop(db.engine)
Zostanie również automatycznie usunięty podczas wywołania drop_all ().
To oczywiście bardzo hakerskie rozwiązanie, ale moim zdaniem jest to obecnie najlepsze i najczystsze rozwiązanie. Testowałem to przez ostatnie kilka dni i nie miałem żadnych problemów. Nie jestem pewien, jak dodać relacje (napotkałem tam problemy), ale nie jest to konieczne, jak pokazano powyżej w zapytaniu.
Jeśli ktoś ma jakieś uwagi, znajdzie jakieś nieoczekiwane problemy lub zna lepszy sposób na zrobienie czegoś, zostaw komentarz lub daj mi znać.
Zostało to przetestowane w SQLAlchemy 1.2.6 i Python 3.6.
super(CreateView, self).__init__
i posiadająceclass SampleView(object)
Base = declarative_base(metadata=db.MetaData()) class ViewSample(Base): __tablename__ = 'view_sample'
nadal uwzględniłem __definition__
właściwość i wywołałem CreateView, aby utworzyć ją zgodnie z sugestią w oryginalnym poście. Na koniec musiałem zmodyfikować metodę dekorowanego upuszczania: if element.element.name.startswith('view_'): return compiler.visit_drop_view(element)
ponieważ nie mogłem znaleźć sposobu na dodanie właściwości do tabeli osadzonej.
Obecnie jest do tego pakiet PyPI: SQLAlchemy Views .
Z jego strony 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
Jednak poprosiłeś o zapytanie „czystego SQL” , więc prawdopodobnie chcesz, aby definition
powyższe zostało utworzone za pomocą obiektu zapytania SQLAlchemy.
Na szczęście text()
w powyższym przykładzie widać, że definition
parametr to CreateView
jest takim obiektem zapytania. Więc coś takiego powinno działać:
>>> 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)
... )
Oto interesujący fragment:
>>> 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 właśnie dodało tę funkcjonalność w wersji 0.33.6 (dostępna w pypi). Ma poglądy, zmaterializowane widoki i integruje się z ORM. Nie jest to jeszcze udokumentowane, ale z powodzeniem używam widoków + ORM.
Możesz użyć ich testu jako przykładu zarówno dla zwykłych, jak i zmaterializowanych widoków przy użyciu ORM.
Aby utworzyć widok, po zainstalowaniu pakietu użyj następującego kodu z powyższego testu jako podstawy widoku:
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
)
Gdzie Base
jest declarative_base
, sa
jest SQLAlchemy
pakietem i create_view
jest funkcją z sqlalchemy_utils.view
.
Nie mogłem znaleźć krótkiej i przydatnej odpowiedzi.
Nie potrzebuję dodatkowej funkcjonalności widoku (jeśli w ogóle), więc po prostu traktuję widok jako zwykłą tabelę, tak jak inne definicje tabel.
Więc w zasadzie mam a.py
gdzie definiuje wszystkie tabele i widoki, rzeczy związane z sql i main.py
gdzie importuję te klasy a.py
i używam ich.
Oto, co dodam a.py
i działa:
class A_View_From_Your_DataBase(Base):
__tablename__ = 'View_Name'
keyword = Column(String(100), nullable=False, primary_key=True)
Warto zauważyć, że musisz dodać primary_key
właściwość, mimo że w widoku nie ma klucza podstawowego.
Widok SQL bez czystego SQL? Możesz utworzyć klasę lub funkcję, aby zaimplementować zdefiniowany widok.
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)
powyżej jest możliwe? Ponieważ użyję View z sesją.