Как сохранить вычисляемый столбец в SQLAlchemy?

#python #sqlalchemy

#python #sqlalchemy

Вопрос:

Я хотел бы знать, как сохранить вычисленное значение чистым способом в базе данных:

Пример (взят из руководства по SQLAlchemy):

 Base = declarative_base()

class Interval(Base):
    __tablename__ = 'interval'

    id = Column(Integer, primary_key=True)
    start = Column(Integer)
    end = Column(Integer)

    @hybrid_property
    def length(self):
        return self.end - self.start
  

Так length вычисляется. Но для упрощения поиска запросов (в другом инструменте базы данных) я хотел бы, чтобы это length также было сохранено в таблице.

Итак, моим первым грубым тестом было просто добавить: length = Column(Float) . Очевидно, что это не работает (но я подумал, что все же попробую: p), поскольку метод длины перезаписывает столбец длины.

Прямо сейчас мой единственный способ, о котором я могу думать, это вычислить его в init amp; отложить до super (), вот так:

 Base = declarative_base()

class Interval(Base):
    __tablename__ = 'interval'

    id = Column(Integer, primary_key=True)
    start = Column(Integer)
    end = Column(Integer)
    length = Column(Integer)

    def __init__(self, *args, **kwargs):
        kwargs['length'] = kwargs['end'] - kwargs['start']
        super().__init__(*args, **kwargs)
  

Однако я чувствую, что должен быть более чистый способ. Есть ли?

Спасибо!

Ответ №1:

Есть несколько способов справиться с этим.

Один из них с before_update и before_insert триггерами. Это SQLAlchemy, где изменения в модели обнаруживаются автоматически, а события отправляются до того, как строки записываются на сервер. Затем вы можете использовать это, чтобы предотвратить сохранение, потенциально увидеть, какие столбцы изменились, а затем соответствующим образом обновить length .

Другой использует generated или computed column . Они были введены в Postgres 12, и они также присутствуют в MySQL, Oracle и MS SQL Server, хотя я знаю об этом меньше. Они делают по сути то же самое, но вы просто добавляете эту логику в свое определение таблицы и можете забыть об этом. SQLAlchemy поддерживает их с 1.3.11.

Комментарии:

1. Ваш второй не будет работать, к сожалению, достаточно, поскольку я использую SQLite :). Первый дал мне хорошую подсказку. Сначала я попробовал before_insert , но это было не очень хорошо, так как мне тоже нужен был уровень для чтения. Далее был init_scalar , но это не сработало во время вставки. В итоге я использовал «init». Изменение kwargs в обработчике инициализации сделало то, что мне было нужно. Основная проблема, с которой я сталкиваюсь, заключается в том, что она отделяет инициализацию от самого класса. Так что подумайте, действительно ли это стоит того;-).

Ответ №2:

Вы можете использовать функцию column_property в sqlalchemy вместо hybrid_property.

column_property: Функция column_property() может использоваться для сопоставления выражения SQL способом, аналогичным обычному отображению столбца. С помощью этого метода атрибут загружается вместе со всеми другими атрибутами, отображенными в столбцах, во время загрузки.

Примечание: также вы можете query использовать column_property.

 Base = declarative_base()

class Interval(Base):
    __tablename__ = 'interval'

    id = Column(Integer, primary_key=True)
    start = Column(Integer)
    end = Column(Integer)
    length = column_property(end - start)
  

пример запроса:

 session.query(Interval).filter(Interval.length == 10)
  

Комментарии:

1. Большое спасибо за ваш вклад, но length как описано вами, не сохраняется в базе данных.