Создайте вычисляемый столбец в основной таблице SQLAlchemy

#python #database #orm #sqlalchemy

#python #База данных #orm #sqlalchemy

Вопрос:

У меня есть таблица SQLAlchemy, определенная следующим образом:

 user = Table('user', MetaData(),
             Column('id', Integer),
             Column('first_name', String),
             Column('last_name', String))
  

Мне часто нужно ссылаться на полное имя пользователя во время запроса, например:

 sql = (select([
          user.c.id,
          (user.c.first_name   user.c.last_name).label('full_name')
       ]).where(user.c.id == 123))
  

Поскольку full_name используется во многих местах, поэтому такой код имеет много копий.

Интересно, есть ли в SQLAlchemy способ, которым я могу создать вычисляемый столбец, чтобы я мог удобно использовать его так же, как и другой обычный столбец, SQLAlchemy автоматически преобразует его в (user.c.first_name user.c.last_name).label('full_name') всякий раз, когда я ссылаюсь user.c.full_name

 sql = (select([
          user.c.id,
          user.c.full_name
       ]).where(user.c.id == 123))
  

Я искал и нашел там какое-то решение в SQLAlchemy ORM, используя column_property или hybrid_property. Разница в моем случае заключается в том, что я могу использовать только ядро SQLAlchemy.

Ответ №1:

Невозможно создать вычисляемые столбцы в ядре sqlalchemy. Однако вам не нужно этого делать, все, что требуется, это сохранить ваше выражение в переменной, а затем использовать это в ваших операторах выбора. Если у вас их много для хранения, вы можете использовать для них пространство имен, сохранив их все в коллекции. В приведенном ниже примере я использовал для этой цели объект свойств SQLAlchemy, поэтому он будет вести себя аналогично коллекции columns .

 class Table(sqlalchemy.Table):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.d = sqlalchemy.util._collections.Properties({})

user = Table('user', MetaData(),
             Column('id', Integer),
             Column('first_name', String),
             Column('last_name', String))
user.d.full_name = (user.c.first_name   user.c.last_name).label('full_name')
user.d.backwards_name = (user.c.last_name   user.c.first_name).label('backwards_name')

sql = (select([
          user.c.id,
          user.d.full_name
       ]).where(user.c.id == 123))
  

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

1. Может возникнуть конфликт имен, если я создам производный столбец непосредственно в экземпляре таблицы, full_name это просто пример, на самом деле это могут быть любые слова. Мой проект в значительной степени зависит от table.c выполнения большого количества проверок, связанных с столбцами, поэтому следуйте вашему предложению, я могу переместить full_name экземпляр в table.c ColumnCollection . Но я проверил исходный код, ColumnCollection принимает только экземпляр столбца. Если есть способ, которым я могу обойти и внедрить его в ColumnCollection, это приемлемо для меня.

2. Я отредактировал свой ответ на namespace выражения sql, сохранив их в новой коллекции. Было бы просто использовать monkeypatch для коллекции столбцов, чтобы принять их, но это, вероятно, нарушило бы все остальные фрагменты кода, которые ожидали найти там только столбцы. Я бы предположил, что имеет смысл хранить эти коллекции отдельно, поскольку они содержат разные вещи. Если вы действительно хотите получить к ним доступ вместе, вы можете перейти self.d = Properties({}) на self.d = Properties(ChainMap({}, self.c._data)) . и user.d вместо этого получите доступ ко всему.

3. Спасибо @EAW, хотя ваша идея не решает всех моих проблем, для меня это все еще очень хорошее направление. Вся исходная Table функциональность не будет нарушена при использовании свойства extend d