SQLAlchemy ile SQL Görünümü nasıl oluşturulur?


Yanıtlar:


69

Güncelleme: Ayrıca burada SQLAlchemy kullanım tarifine bakın

Bildiğim kadarıyla bir (salt okunur materyalleştirilmemiş) görünüm oluşturmak, kutunun dışında desteklenmiyor. Ancak bu işlevselliği SQLAlchemy 0.7'de eklemek basittir ( burada verdiğim örneğe benzer ). Bir derleyici uzantısı yazmanız yeterlidir CreateView. Bu uzantı ile daha sonra yazabilirsiniz (bunun tbir sütun içeren bir tablo nesnesi olduğunu varsayarak 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

İşte çalışan bir örnek:

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

İsterseniz bir lehçe için de uzmanlaşabilirsiniz, örneğin

@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)
         )

Map v'yi orm.mapper ile kullanabilir miyim? v = Table('viewname', metadata, autoload=True) class ViewName(object): def __init__(self, name): self.name = name mapper(ViewName, v) Yukarıdaki gibi mümkün mü? Çünkü View'u oturum ile kullanacağım.
Syed Habib M

1
@SyedHabibM: evet, bu mümkün. Birincil anahtarı manuel olarak ayarlamanız gerekir, ancak sırasıyla birincil anahtarınız (veya anahtarlarınız) ve özellikleriniz orm.mapper(ViewName, v, primary_key=pk, properties=prop)nerede pkve prophangisidir? Docs.sqlalchemy.org/en/latest/orm/… bakın .
stephan

2
@SyedHabibM: ne yapabilirsin Stephan bir PK sütun şartname geçersiz kılarak tabloları otomatik yüklenmesi kullandığınızda da bahsedildiği ve belirtin:v = Table('viewname', metadata, Column('my_id_column', Integer, primary_key=True), autoload=True)
van

@SyedHabibMI, ilgili sorunuzu stackoverflow.com/q/20518521/92092'de çalışan bir örnekle yanıtladı . Van'ın yorumunu da oraya ekleyeceğim.
stephan

27

stephan'ın cevabı iyi bir cevap ve çoğu temeli kapsıyor, ancak beni tatmin etmeyen şey, SQLAlchemy'nin geri kalanıyla (ORM, otomatik bırakma vb.) entegrasyon eksikliğiydi. İnternetin her köşesinden gelen bilgileri saatlerce denedikten ve bir araya getirdikten sonra aşağıdakileri buldum:

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_viewsPaketi sadece işleri basitleştirmek için kullandığımı unutmayın .

Bir görünüm tanımlama (örneğin, genel olarak Masa modelleriniz gibi):

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]

Görünümlerin eşlenmesi (ORM işlevselliğini etkinleştirin):

Uygulamanızı yüklerken, herhangi bir sorudan önce ve DB'yi kurduktan sonra yapın.

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

Görünümlerin oluşturulması:

Veritabanını başlatırken yapın, örneğin bir create_all () çağrısından sonra.

from sqlalchemy import orm


for view in views:
    db.engine.execute(CreateView(view))

Bir görünüm nasıl sorgulanır:

results = db.session.query(SomeModel, SampleView).join(
    SampleView,
    SomeModel.id == SampleView.some_model_id
).all()

Bu, tam olarak beklediğiniz şeyi döndürür (her biri bir SomeModel nesnesi ve bir SampleView nesnesi olan nesnelerin bir listesi).

Bir görünümü düşürmek:

SampleView.__view__.drop(db.engine)

Drop_all () çağrısı sırasında da otomatik olarak düşecektir.

Bu kesinlikle çok karmaşık bir çözüm ama benim gözümde şu anda piyasadaki en iyisi ve en temiz olanı. Geçtiğimiz birkaç gün içinde test ettim ve herhangi bir sorun yaşamadım. İlişkileri nasıl ekleyeceğimi bilmiyorum (orada sorunlarla karşılaştım), ancak yukarıda sorguda gösterildiği gibi gerçekten gerekli değil.

Herhangi birinin herhangi bir girdisi varsa, beklenmedik sorunlar bulursa veya işleri yapmanın daha iyi bir yolunu bilen varsa, lütfen bir yorum bırakın veya bana bildirin.

Bu, SQLAlchemy 1.2.6 ve Python 3.6 üzerinde test edildi.


Çılgın zamanlama, sadece bununla ben ilgileniyorum. Py 2.7 ve SQLa 1.1.2 için (sormayın ...), gerekli olan tek değişiklik super(CreateView, self).__init__ve sahip olmakclass SampleView(object)
Steven Dickinson

1
@Steven Dickinson evet doğru geliyor! Evet, bunun gerçekten yaygın bir görev olduğunu düşündüm, bu yüzden üzerindeki belgelerin çok zayıf / modası geçmiş / sığ olmasına şaşırdım. Ama hey, sanırım her seferinde bir adım.
fgblomqvist

2
Bunu bildirimsel olarak yapmak isteyenler için, görünümlerimi tablolarımdan farklı bir meta veri örneğiyle ayrı bir dosyada tanımladım: Base = declarative_base(metadata=db.MetaData()) class ViewSample(Base): __tablename__ = 'view_sample' yine de __definition__özelliği ekledim ve orijinal gönderide önerildiği gibi oluşturmak için CreateView'ı çağırdım. Son olarak, damla dekore edilmiş yöntemi değiştirmek zorunda kaldım: if element.element.name.startswith('view_'): return compiler.visit_drop_view(element) çünkü özelliği gömülü tabloya eklemenin bir yolunu bulamadım.
Casey

23

Bugünlerde bunun için bir PyPI paketi var: SQLAlchemy Views .

PyPI Sayfasından:

>>> 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

Ancak, "saf SQL" sorgusu istemediniz, bu nedenle muhtemelen definitionyukarıdakinin SQLAlchemy sorgu nesnesiyle oluşturulmasını istiyorsunuz .

Neyse ki, text()yukarıdaki örnekte, to definitionparametresinin CreateViewböyle bir sorgu nesnesi olduğunu açıkça ortaya koymaktadır . Yani bunun gibi bir şey çalışmalı:

>>> 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)
...  )

İşte ilginç olan kısım:

>>> 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 bu işlevselliği 0.33.6'da ekledi (pypi'de mevcuttur). Görüşlere, somutlaşmış görüşlere sahiptir ve ORM ile bütünleşir. Henüz belgelenmedi, ancak görünümleri + ORM'yi başarıyla kullanıyorum.

Sen edebilirsiniz örnek olarak onların testini kullanmak hem normal ve ORM kullanarak görüşlerin kullanımı.

Bir görünüm oluşturmak için, paketi yükledikten sonra, görünümünüz için temel olarak yukarıdaki testte bulunan aşağıdaki kodu kullanın:

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
    )

Nerede Baseolduğunu declarative_base, sabir SQLAlchemypaket ve create_viewbir fonksiyondur sqlalchemy_utils.view.


Onu alembic ile birlikte kullanmanın bir yolunu buldunuz mu?
Jorge Leitao

1

Kısa ve kullanışlı bir cevap bulamadım.

Ekstra Görünüm işlevselliğine ihtiyacım yok (eğer varsa), bu nedenle bir görünümü sıradan bir tablo olarak diğer tablo tanımları gibi ele alıyorum.

Yani temelde a.pytüm tabloları ve görünümleri, sql ile ilgili şeyleri tanımladığım ve main.pybu sınıfı nereden aktardığım a.pyve kullandığım yer var.

İşte eklediğim a.pyve işe yarayan şey:

class A_View_From_Your_DataBase(Base):
    __tablename__ = 'View_Name'
    keyword = Column(String(100), nullable=False, primary_key=True)

Özellikle, primary_keygörünümde birincil anahtar olmasa bile özelliği eklemeniz gerekir .


-9

Saf SQL olmadan SQL Görünümü? Tanımlı bir görünümü uygulamak için bir sınıf veya işlev oluşturabilirsiniz.

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

2
Üzgünüm ama sorduğum bu değil. Benim ingilizce :) sen yanlış eğer Üzgünüm, mükemmel değil
Thibaut D.
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.