Чи існує "пітонічний" спосіб (я маю на увазі відсутність запиту "чистого SQL") для визначення подання SQL за допомогою SQLAlchemy?
Відповіді:
Оновлення: Дивіться також тут рецепт використання 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)
)
orm.mapper(ViewName, v, primary_key=pk, properties=prop)
де pk
і prop
є вашим первинним ключем (або ключами) і властивостями відповідно. Див. Docs.sqlalchemy.org/en/latest/orm/… .
v = Table('viewname', metadata, Column('my_id_column', Integer, primary_key=True), autoload=True)
Відповідь Стефана є доброю і охоплює більшість основ, але мене не задовольнила відсутність інтеграції з рештою 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.
super(CreateView, self).__init__
та наявністьclass SampleView(object)
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)
оскільки я не міг знайти спосіб додати властивість до вбудованої таблиці.
Сьогодні для цього існує пакет 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
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
.
Я не зміг знайти коротку та зручну відповідь.
Мені не потрібна додаткова функціональність 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
властивість, хоча у поданні немає первинного ключа.
SQL View без чистого SQL? Ви можете створити клас або функцію для реалізації визначеного подання.
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)
Вище можливо? Тому що я буду використовувати View з сеансом.