it-swarm.com.de

So erstellen Sie eine SQL-Ansicht mit SQLAlchemy

Alles steht im Titel. Gibt es eine Pythonic-Methode (keine reine SQL-Abfrage), um eine SQL-Ansicht mit SQLAlchemy zu definieren?

Danke für Ihre Hilfe,

53
Thibaut D.

Update: Siehe auch das SQLAlchemy-Verwendungsrezept hier

Das Erstellen einer (schreibgeschützten, nicht materialisierten) Ansicht wird meines Wissens nach nicht unterstützt. Das Hinzufügen dieser Funktionalität in SQLAlchemy 0.7 ist jedoch unkompliziert (ähnlich dem Beispiel, das ich here gegeben habe). Sie müssen nur eine Compiler-ErweiterungCreateView schreiben. Mit dieser Erweiterung können Sie dann schreiben (vorausgesetzt, t ist 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)
         )
57
stephan

In diesen Tagen gibt es dafür ein PyPI-Paket: SQLAlchemy Views .

Von seiner 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 no "pure SQL" gefragt, sodass die definition oben wahrscheinlich mit dem SQLAlchemy-Abfrageobjekt erstellt werden soll.

Glücklicherweise macht die text() im obigen Beispiel deutlich, dass der definition-Parameter für 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
13
LeoRochael

stephans Antwort ist gut und deckt die meisten Grundlagen ab. Was mich jedoch nicht zufriedenstellte, war die mangelnde Integration in den Rest von SQLAlchemy (ORM, automatisches Löschen usw.). Nach stundenlangem Experimentieren und Zusammensetzen von Wissen aus allen Ecken des Internets kam ich zu folgenden Themen:

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, nur um die Dinge zu vereinfachen.

Definieren einer Ansicht (z. B. global wie Ihre Table-Modelle):

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]

Mappen der Ansichten (ORM-Funktionalität aktivieren):

Wenn Sie Ihre App laden, vor Abfragen und nach dem Einrichten der Datenbank.

for view in views:
    if not hasattr(view, '_sa_class_manager'):
        orm.mapper(view, view.__view__)

Erstellen der Ansichten:

Do beim Initialisieren der Datenbank, z. 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 drop_all () - Aufrufs verworfen.

Dies ist offensichtlich eine sehr harte Lösung, aber in meinen Augen ist es derzeit die beste und sauberste. Ich habe es in den letzten Tagen getestet und hatte keine Probleme. Ich bin nicht sicher, wie man Beziehungen hinzufügt (dort stoßen Probleme), 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 unter SQLAlchemy 1.2.6 und Python 3.6 getestet.

11
fgblomqvist

SQLAlchemy-utils hat gerade diese Funktionalität hinzugefügt in 0.33.6 (verfügbar in pypi). Es hat Ansichten, materialisierte Ansichten und ist mit dem ORM integriert. Es ist noch nicht dokumentiert, aber ich verwende erfolgreich die Ansichten + ORM.

Sie können verwenden Sie ihren Test als Beispiel sowohl für reguläre als auch für materialisierte Ansichten mit dem ORM. 

Um eine Ansicht zu erstellen, verwenden Sie nach der Installation des Pakets den folgenden Code aus dem obigen Test als Basis 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
    )

Wenn Base der declarative_base ist, ist sa das SQLAlchemy-Paket und create_view eine Funktion aus sqlalchemy_utils.view.

3
bustawin

Ich konnte keine kurze und praktische Antwort finden.

Ich brauche keine zusätzlichen Funktionen für View (falls vorhanden), daher behandele ich eine View einfach als gewöhnliche Tabelle als andere Tabellendefinitionen.

Im Grunde habe ich a.py, wo alle Tabellen und Ansichten, das SQL-bezogene Material und main.py definiert werden, wo ich diese Klasse aus a.py importiere und sie verwende.

Folgendes füge ich in a.py hinzu und funktioniert:

class A_View_From_Your_DataBase(Base):
    __table= '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. 

0
Rick