Добавление индексов в модели SQLAlchemy после создания таблицы

У меня есть модель кол-sqlalchemy:

class MyModel(db.Model):
__tablename__ = 'targets'
id = db.Column(db.Integer, primary_key=True)
url = db.Column(db.String(2048))

Таблица уже создана и используется. Я хочу создать индекс для атрибута url, поэтому я передаю ему индекс = True:

url = db.Column(db.String(2048), index=True)

Как я могу заставить этот индекс вступить в силу, не удаляя и не воссоздавая таблицу?

Ответы

Ответ 1

Приведен модельный класс из оригинального вопроса.

class MyModel(db.Model):
    __tablename__ = 'targets'
    id = db.Column(db.Integer, primary_key=True)
    url = db.Column(db.String(2048))

Вы не можете просто добавить index=True потому что даже если вы вызвали db.Model.metadata.create_all() индекс не будет создан для уже созданной таблицы.

Вместо этого вам нужно создать независимый объект Index, а затем создать его. Это будет выглядеть примерно так:

class MyModel(db.Model):
    __tablename__ = 'targets'
    id = db.Column(db.Integer, primary_key=True)
    url = db.Column(db.String(2048))

mymodel_url_index = Index('mymodel_url_idx', MyModel.url)

if __name__ == '__main__':
    mymodel_url_index.create(bind=engine)

Теперь, откуда engine будет зависеть от вашей конфигурации sqlalchemy, но этот код должен передать суть того, что должно произойти.

Ответ 2

Если вы используете Alembic, вы можете создать миграцию для этого и не добавлять его в модель вообще:

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_index('table1_id', 'table1', ['id'], unique=True)
    op.create_index('table2_id', 'table2', ['id'], unique=True)
    # ### end Alembic commands ###

def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_index('table1_id', table_name='table1')
    op.drop_index('table2_id', table_name='table2')
    # ### end Alembic commands ###

Ответ 4

Обратите внимание, что это неверный и чрезмерно сложный ответ

Правильный способ - использовать index.create, как было сказано здесь.


Прежде всего, убедитесь, что у вас есть последний снимок вашей базы данных и вы можете восстановить базу данных из этого моментального снимка.

Для проектов среднего и большого размера (те, которые могут потребоваться для одновременной поддержки нескольких версий и установлены в нескольких средах), существует специальная процедура, которая является частью жизненного цикла управления базой данных, называемой "миграция базы данных". Переходы DB включают в себя изменения в существующей схеме. SQLAlchemy не поддерживает миграцию из коробки.

Доступны два средства миграции базы данных, совместимые с SQLAlchemy:

См. дополнительную информацию и ссылки на эти инструменты на странице документации SQLAlchemy: Изменение схем через миграции.

Но если вы работаете над небольшим проектом, я бы предложил вручную запустить запрос ALTER TABLE DDL из служебной программы командной строки базы данных или через connection.execute() в python script.

В рабочем приложении, над которым я сейчас работаю, мы поддерживаем только одну последнюю версию приложения. Для каждого изменения схемы базы данных мы делаем следующие шаги:

  • сделать снимок производственной базы данных
  • загрузить этот снимок в среду разработки
  • обновить модуль модели данных sqlalchemy.
  • подготовьте и запустите запрос альтернативной таблицы и сохраните этот запрос позже
  • внести другие изменения в код
  • выполнить тесты в среде dev
  • развертывание последней версии кода для производства
  • изменить таблицу при производстве

Также я использую следующий трюк для генерации запросов create/index: Я указываю свое приложение на новую базу данных, разрешаю ведение журнала запросов sqlalchemy и запускать metadata.create_all() - поэтому в журналах (или STDOUT) я вижу создание запроса, созданного sqlalchemy

В зависимости от системы базы данных вы используете запрос создания индекса, будет немного отличаться. Общий запрос будет выглядеть так:

create index targets_i on targets(url);

Ответ 5

Поскольку вопрос был задан, для этого была добавлена поддержка.

Теперь вы можете просто добавить index=True в существующий столбец и автоматически сгенерировать миграцию.

Проверено на следующих версиях пакета:

alembic==1.0.10
SQLAlchemy==1.3.4
SQLAlchemy-Utils==0.34.0
Flask-SQLAlchemy==2.4.0
Flask-Migrate==2.5.2

Ответ 6

Я не уверен, соответствует ли это лучшим практикам, но я обнаружил, что Alembic уведомит меня об индексах в __table_args__, но не сделает их для меня во время миграции. Я сделал этот небольшой script, который может генерировать новые индексы, найденные в свойстве __table_args__. Он использует Index.create(), как упоминалось выше, но будет генерировать новые индексы, если они не существуют.

def create_indexes(db, drop_index=False):
    """
    Creates all indexes on models in project if they do not exists already. Assumes all models
    inherit from RequiredFields class, otherwise will need to adjust search for subclasses. If the index
    exists SQLAlchemy throws an error and we assume everything is ok. 
    :param db: The app db object, acts as the engine param for the Index.create()
    :param drop_index: specifies whether the indexes should be dropped or created
    :return:
    """
    from application.base_models import RequiredFields
    from sqlalchemy import Index
    from sqlalchemy.exc import ProgrammingError
    for klass in RequiredFields.__subclasses__():
        if hasattr(klass, '__table_args__'):
            for item in getattr(klass, '__table_args__'):
                if isinstance(item, Index):
                    try:
                        if not drop_index:
                            item.create(db.engine)
                        else:
                            item.drop(db.engine)
                    except ProgrammingError:  # If index exists, creation fails on error
                        pass
    return

Вот пример класса, показывающего индексы.

class MyModel(RequiredFields):

    __table_args__ = (
         db.Index( ... ),
         db.Index( ... ),
    )

Ответ 7

Используйте флакон-мигрет. Это круто. После добавления индекса просто используйте эту команду:

python manage.py db migrate

Все работает отлично