Чи існує "пітонічний" спосіб (я маю на увазі відсутність запиту "чистого 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 з сеансом.