Як створити SQL View за допомогою SQLAlchemy?


85

Чи існує "пітонічний" спосіб (я маю на увазі відсутність запиту "чистого SQL") для визначення подання SQL за допомогою SQLAlchemy?

Відповіді:


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

Чи можу я використовувати карту v з orm.mapper? як v = Table('viewname', metadata, autoload=True) class ViewName(object): def __init__(self, name): self.name = name mapper(ViewName, v) Вище можливо? Тому що я буду використовувати View з сеансом.
Syed Habib M

1
@SyedHabibM: так, це можливо. Ви повинні вручну встановити первинний ключ, однак, щось на зразок orm.mapper(ViewName, v, primary_key=pk, properties=prop)де pkі propє вашим первинним ключем (або ключами) і властивостями відповідно. Див. Docs.sqlalchemy.org/en/latest/orm/… .
Стефан

2
@SyedHabibM: ви можете робити те , що Stephan згадувалося також , коли ви використовуєте таблиці автоматично завантажуються з допомогою перевизначення специфікації стовпця і вказати PK:v = Table('viewname', metadata, Column('my_id_column', Integer, primary_key=True), autoload=True)
ван

@SyedHabibMI ж відповісти на ваш відповідне питання stackoverflow.com/q/20518521/92092 з робочим прикладом в даний час. Додам туди ж коментар ван.
Стефан

27

Відповідь Стефана є доброю і охоплює більшість основ, але мене не задовольнила відсутність інтеграції з рештою 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):

Це робити під час завантаження програми, перед будь-якими запитами та після налаштування БД.

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.


Божевільний час, я просто мав справу з цим сам. Для py 2.7 та SQLa 1.1.2 (не питайте ...), потрібні були лише зміни super(CreateView, self).__init__та наявністьclass SampleView(object)
Стівен Дікінсон

1
@ Стівен Дікінсон гукає приблизно так! Так, я зрозумів, що це справді поширене завдання, саме тому я був здивований, що документація на нього була настільки бідною / застарілою / неглибокою. Але, гадаю, крок за кроком, я гадаю.
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) оскільки я не міг знайти спосіб додати властивість до вбудованої таблиці.
Кейсі,

24

Сьогодні для цього існує пакет 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" , тож ви, мабуть, хочете, щоб definitionвищезгадане було створено за допомогою об'єкта запиту SQLAlchemy.

На щастя, text()у наведеному вище прикладі чітко видно, що definitionпараметр to 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знаходитьсяdeclarative_base , saє SQLAlchemyпакет і create_viewє функцією від sqlalchemy_utils.view.


Ви знайшли спосіб використовувати його разом з алембіком?
Хорхе Лейтао

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 View без чистого SQL? Ви можете створити клас або функцію для реалізації визначеного подання.

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

2
Вибачте, але це не те, що я просив. Моя англійська не досконала, вибачте, якщо ви неправильно зрозуміли :)
Тібо Д.,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.