#python #triggers #sqlalchemy
#python #триггеры #sqlalchemy
Вопрос:
У меня есть две таблицы, связанные через внешний ключ, здесь они используют декларативное сопоставление
class Task(DeclarativeBase):
__tablename__ = 'task'
id = Column(Integer, primary_key=True)
state = Column(Integer, default=0)
obs_id = Column(Integer, ForeignKey('obs.id'), nullable=False)
class Obs(DeclarativeBase):
__tablename__ = 'obs'
id = Column(Integer, primary_key=True)
state = Column(Integer, default=0)
Итак, я хотел бы обновить связанный task.state, когда obs.state изменяется на значение 2. В настоящее время я делаю это вручную (используя отношение, называемое task)
obs.state = 2
obs.task.state = 2
Но я бы предпочел сделать это с помощью триггера. Я проверил, что это работает в sqlite
CREATE TRIGGER update_task_state UPDATE OF state ON obs
BEGIN
UPDATE task SET state = 2 WHERE (obs_id = old.id) and (new.state = 2);
END;
Но я не могу найти, как выразить это в sqlalchemy. Я несколько раз прочитал insert update defaults, но не могу найти способ. Я не знаю, возможно ли это вообще.
Ответ №1:
Вы можете создать триггер в базе данных с помощью класса DDL:
update_task_state = DDL('''
CREATE TRIGGER update_task_state UPDATE OF state ON obs
BEGIN
UPDATE task SET state = 2 WHERE (obs_id = old.id) and (new.state = 2);
END;''')
event.listen(Obs.__table__, 'after_create', update_task_state)
Это самый надежный способ: он будет работать для массовых обновлений, когда ORM не используется, и даже для обновлений вне вашего приложения. Однако есть и недостатки:
- Вы должны позаботиться о том, чтобы ваш триггер существовал и обновлялся;
- Он не переносим, поэтому вам придется переписать его, если вы измените базу данных;
- SQLAlchemy не изменит новое состояние уже загруженного объекта, если срок его действия не истечет (например, с помощью некоторого обработчика событий).
Ниже приведено менее надежное (оно будет работать, когда изменения будут внесены только на уровне ORM), но гораздо более простое решение:
from sqlalchemy.orm import validates
class Obs(DeclarativeBase):
__tablename__ = 'obs'
id = Column(Integer, primary_key=True)
state = Column(Integer, default=0)
@validates('state')
def update_state(self, key, value):
self.task.state = value
return value
Оба моих примера работают в одну сторону, т. Е. Они обновляют задачу при изменении obs, но не касаются obs при обновлении задачи. Вам нужно добавить еще один триггер или обработчик событий для поддержки распространения изменений в обоих направлениях.
Комментарии:
1. 1: лично я предпочитаю решение, отличное от DB, с проверкой. Еще одна проверка, которую нужно добавить, заключается в том, что
@validates('task')
поскольку задача может измениться (маловероятно) или быть добавлена (более вероятно) в новый Obs.2. Я думаю, что я собираюсь использовать подход, основанный на ORM. Спасибо!
3. @denis-otkidach Ну, хотя ваш подход работает, я не думаю, что validates запускается для всех событий, у меня есть несколько сеансов.выполняет запуск в некоторых случаях я даже пробовал некоторые функции сопоставления, но даже это, похоже, не сработало никаких предложений
4. @dusual Да, он вызывается только для обновлений уровня ORM.
session.execute()
не является уровнем ORM.5. @DenisOtkidach привет, я знаю, что он слишком старый, но в моем случае он не обновляет какое-либо другое значение, скажем, если у нас есть
name
столбец внутриObs
и внутриvalidates
, я пытаюсь обновитьself.name='any_name'
, он не обновляет значение имени