Gibt es eine "pythonische" Möglichkeit (ich meine, keine "reine SQL" -Abfrage), eine SQL-Ansicht mit SQLAlchemy zu definieren?
Gibt es eine "pythonische" Möglichkeit (ich meine, keine "reine SQL" -Abfrage), eine SQL-Ansicht mit SQLAlchemy zu definieren?
Antworten:
Update: Siehe auch das SQLAlchemy-Verwendungsrezept hier
Das Erstellen einer (schreibgeschützten, nicht materialisierten) Ansicht wird meines Wissens nicht sofort unterstützt. Aber das Hinzufügen dieser Funktionalität in SQLAlchemy 0.7 ist einfach (ähnlich dem Beispiel gab ich hier ). Sie müssen nur eine Compiler-Erweiterung schreiben CreateView
. Mit dieser Erweiterung können Sie dann schreiben (vorausgesetzt, es t
handelt sich um ein Tabellenobjekt mit einer Spalte 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
Hier ist ein Arbeitsbeispiel:
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
Wenn Sie möchten, können Sie sich auch auf einen Dialekt spezialisieren, z
@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)
wo pk
und prop
sind Ihr Primärschlüssel (oder Ihre Primärschlüssel) bzw. Eigenschaften. Siehe docs.sqlalchemy.org/en/latest/orm/… .
v = Table('viewname', metadata, Column('my_id_column', Integer, primary_key=True), autoload=True)
Die Antwort von stephan ist gut und deckt die meisten Grundlagen ab, aber was mich unzufrieden machte, war die mangelnde Integration mit dem Rest der SQLAlchemy (ORM, automatisches Löschen usw.). Nachdem ich stundenlang experimentiert und Wissen aus allen Ecken des Internets zusammengesetzt hatte, kam ich auf Folgendes:
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'
Beachten Sie, dass ich das sqlalchemy_views
Paket verwende, um die Dinge zu vereinfachen.
Definieren einer Ansicht (z. B. global wie Ihre Tabellenmodelle):
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]
Zuordnen der Ansichten (Aktivieren der ORM-Funktionalität):
Tun Sie dies beim Laden Ihrer App, vor allen Abfragen und nach dem Einrichten der Datenbank.
for view in views:
if not hasattr(view, '_sa_class_manager'):
orm.mapper(view, view.__view__)
Ansichten erstellen:
Tun Sie dies beim Initialisieren der Datenbank, z. B. nach einem Aufruf von create_all ().
from sqlalchemy import orm
for view in views:
db.engine.execute(CreateView(view))
So fragen Sie eine Ansicht ab:
results = db.session.query(SomeModel, SampleView).join(
SampleView,
SomeModel.id == SampleView.some_model_id
).all()
Dies würde genau das zurückgeben, was Sie erwarten (eine Liste von Objekten, die jeweils ein SomeModel-Objekt und ein SampleView-Objekt haben).
Ansicht löschen:
SampleView.__view__.drop(db.engine)
Es wird auch automatisch während eines Aufrufs von drop_all () gelöscht.
Dies ist offensichtlich eine sehr hackige Lösung, aber in meinen Augen ist es die beste und sauberste, die es derzeit gibt. Ich habe es in den letzten Tagen getestet und hatte keine Probleme. Ich bin nicht sicher, wie ich Beziehungen hinzufügen soll (dort sind Probleme aufgetreten), aber es ist nicht wirklich notwendig, wie oben in der Abfrage gezeigt.
Wenn jemand Eingaben hat, unerwartete Probleme findet oder eine bessere Vorgehensweise kennt, hinterlassen Sie bitte einen Kommentar oder lassen Sie es mich wissen.
Dies wurde auf SQLAlchemy 1.2.6 und Python 3.6 getestet.
super(CreateView, self).__init__
und mit demclass SampleView(object)
Base = declarative_base(metadata=db.MetaData()) class ViewSample(Base): __tablename__ = 'view_sample'
Ich habe die __definition__
Eigenschaft weiterhin eingefügt und CreateView aufgerufen, um sie wie im ursprünglichen Beitrag vorgeschlagen zu erstellen. Schließlich musste ich die Drop-Decorated-Methode ändern: if element.element.name.startswith('view_'): return compiler.visit_drop_view(element)
weil ich keine Möglichkeit fand, die Eigenschaft zur eingebetteten Tabelle hinzuzufügen.
Heutzutage gibt es dafür ein PyPI-Paket: SQLAlchemy Views .
Von der PyPI-Seite:
>>> 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
Sie haben jedoch nach einer Abfrage ohne "reines SQL" gefragt , daher möchten Sie wahrscheinlich, dass die definition
oben genannte Abfrage mit dem SQLAlchemy-Abfrageobjekt erstellt wird.
Glücklicherweise text()
macht das obige Beispiel deutlich, dass der definition
Parameter to CreateView
ein solches Abfrageobjekt ist. So etwas sollte funktionieren:
>>> 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)
... )
Hier ist das Interessante:
>>> 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 hat diese Funktionalität gerade hinzugefügt in 0.33.6 hinzugefügt (verfügbar in pypi). Es hat Ansichten, materialisierte Ansichten und ist in das ORM integriert. Es ist noch nicht dokumentiert, aber ich verwende erfolgreich die Ansichten + ORM.
Sie können ihren Test als Beispiel verwenden für reguläre und materialisierte Ansichten mithilfe des ORM verwenden.
Verwenden Sie zum Erstellen einer Ansicht nach der Installation des Pakets den folgenden Code aus dem obigen Test als Grundlage für Ihre Ansicht:
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
)
Wo Base
ist das declarative_base
, sa
ist das SQLAlchemy
Paket und create_view
ist eine Funktion von sqlalchemy_utils.view
.
Ich konnte keine kurze und handliche Antwort finden.
Ich benötige keine zusätzlichen Funktionen von View (falls vorhanden), daher behandle ich eine Ansicht einfach als normale Tabelle wie andere Tabellendefinitionen.
Im Grunde habe ich also, a.py
wo alle Tabellen und Ansichten definiert werden, SQL-bezogene Dinge und main.py
wo ich diese Klasse importierea.py
und verwende.
Folgendes füge ich hinzu a.py
und funktioniert:
class A_View_From_Your_DataBase(Base):
__tablename__ = 'View_Name'
keyword = Column(String(100), nullable=False, primary_key=True)
Insbesondere müssen Sie die primary_key
Eigenschaft hinzufügen , obwohl die Ansicht keinen Primärschlüssel enthält.
SQL View ohne reines SQL? Sie können eine Klasse oder Funktion erstellen, um eine definierte Ansicht zu implementieren.
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)
oben ist möglich? Weil ich View with session verwenden werde.