Миграция баз данных с помощью mssql-django

В этой статье объясняется, как работает система миграций Django с SQL Server через серверную часть mssql-django, а также документируются известные особые случаи.

Создание и применение миграций

Процесс миграции в Django работает с SQL Server так же, как и с другими базами данных:

  1. Создайте миграции на основе изменений в модели:

    python manage.py makemigrations myapp
    
  2. Просмотрите созданные файлы миграции в <app>/migrations/.

  3. Примените миграции к базе данных:

    python manage.py migrate myapp
    
  4. Проверьте состояние миграции:

    python manage.py showmigrations myapp
    

Начальная настройка проекта

При настройке нового проекта Django с SQL Server выполните миграцию для создания встроенных таблиц Django (аутентификация, сеансы, администратор):

python manage.py migrate

Эта команда создает все таблицы, необходимые для приложений, перечисленных в INSTALLED_APPS.

Настраиваемый SQL в миграциях

Используется migrations.RunSQL для выполнения необработанных инструкций SQL во время миграции. Этот подход полезен для создания хранимых процедур, триггеров или других объектов SQL Server.

from django.db import migrations

class Migration(migrations.Migration):

    dependencies = [
        ("myapp", "0001_initial"),
    ]

    operations = [
        migrations.RunSQL(
            sql="CREATE INDEX IX_myapp_product_name ON myapp_product (name);",
            reverse_sql="DROP INDEX IX_myapp_product_name ON myapp_product;",
        ),
    ]

Известные пограничные случаи миграции

Для следующих операций миграции требуются обходные пути при выборе SQL Server.

Изменение автофилда

Изменение типа поля модели с AutoField или на AutoField во время миграции не поддерживается. SQL Server не позволяет добавлять или удалять IDENTITY свойство из существующего столбца.

Обходное решение. Создайте новую модель с нужным типом поля. Перенесите данные из старой таблицы в новую таблицу, а затем удалите старую таблицу.

Переименование поля или модели с ограничениями внешнего ключа

Переименование поля или модели с ограничениями внешнего ключа может завершиться ошибкой. SQL Server требует удаления и повторного восстановления ограничений FK во время операций переименования.

Обходное решение: Используйте migrations.SeparateDatabaseAndState для удаления ограничения внешнего ключа, переименования столбца и повторного создания ограничения, одновременно указав Django обновить состояние модели. В следующем примере внешний ключ product в модели Order переименовывается в item:

from django.db import migrations

class Migration(migrations.Migration):

    dependencies = [
        ("myapp", "0002_previous"),
    ]

    operations = [
        migrations.SeparateDatabaseAndState(
            database_operations=[
                migrations.RunSQL(
                    sql="ALTER TABLE myapp_order DROP CONSTRAINT FK_order_product;",
                    reverse_sql="ALTER TABLE myapp_order ADD CONSTRAINT FK_order_product FOREIGN KEY (product_id) REFERENCES myapp_product(id);",
                ),
                migrations.RunSQL(
                    sql="EXECUTE sp_rename 'myapp_order.product_id', 'item_id', 'COLUMN';",
                    reverse_sql="EXECUTE sp_rename 'myapp_order.item_id', 'product_id', 'COLUMN';",
                ),
                migrations.RunSQL(
                    sql="ALTER TABLE myapp_order ADD CONSTRAINT FK_order_item FOREIGN KEY (item_id) REFERENCES myapp_product(id);",
                    reverse_sql="ALTER TABLE myapp_order DROP CONSTRAINT FK_order_item;",
                ),
            ],
            state_operations=[
                migrations.RenameField(
                    model_name="order",
                    old_name="product",
                    new_name="item",
                ),
            ],
        ),
    ]

Найдите фактическое имя ограничения в базе данных перед выполнением этого кода T-SQL. Django генерирует имена ограничений, включающие короткий хэш, поэтому имя в вашей схеме не совпадает с подстановочным именем, показанным здесь.

Миграции Squash

После того как многие миграции накапливаются, их можно разбить на меньшее количество файлов:

python manage.py squashmigrations myapp 0001 0010

Tip

Всегда тестируйте объединённые миграции на чистой базе данных, чтобы убедиться, что в результате создаётся корректная схема.

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

Серверная часть mssql-django поддерживает GeneratedField Django (Django 5.0 и более поздних версий), которое сопоставляется с вычисляемыми столбцами SQL Server.

Сохраненные (PERSISTED) созданные столбцы

Сохраненный созданный столбец физически записывается на диск и обновляется при изменении исходных столбцов:

from django.db import models
from django.db.models import F

class Product(models.Model):
    price = models.DecimalField(max_digits=10, decimal_places=2)
    tax_rate = models.DecimalField(max_digits=5, decimal_places=4)
    total_price = models.GeneratedField(
        expression=F("price") * (1 + F("tax_rate")),
        output_field=models.DecimalField(max_digits=10, decimal_places=2),
        db_persist=True,
    )

Это приводит к следующему: total_price AS ([price] * (1 + [tax_rate])) PERSISTED.

Виртуальные созданные столбцы

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

from django.db import models
from django.db.models import F, Value
from django.db.models.functions import Concat

class Employee(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    full_name = models.GeneratedField(
        expression=Concat(F("first_name"), Value(" "), F("last_name")),
        output_field=models.CharField(max_length=101),
        db_persist=False,
    )

Note

SQL Server ограничивает создание индексов для непостоянных вычисляемых столбцов. Используйте, db_persist=True если необходимо индексировать созданный столбец.

Примечания к таблицам и столбцам

Бэкенд mssql-django поддерживает функцию db_comment в Django (в Django 4.2 и более поздних версиях). Примечания хранятся в виде MS_Description расширенных свойств объекта SQL Server.

Примечания к таблицам

class AuditLog(models.Model):
    action = models.CharField(max_length=50)
    timestamp = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table_comment = "Tracks user actions for compliance auditing."

Комментарии к столбцам

class Measurement(models.Model):
    value = models.FloatField(db_comment="Sensor reading in Celsius")
    recorded_at = models.DateTimeField(db_comment="UTC timestamp from the data logger")

Комментарии отображаются в SQL Server Management Studio в свойствах столбца/таблицы и через sys.extended_properties.

Составные первичные ключи

Django 5.2 представил CompositePrimaryKey. Серверная mssql-django часть имеет частичную поддержку составных первичных ключей, но некоторые тестовые случаи Django по-прежнему исключены. Проверьте миграции составных ключей и запросы к приложению перед их внедрением в рабочей среде.

  • inspectdb некорректно создает составные первичные ключи. Определите их вручную после проверки.
  • Поиск по кортежам не поддерживается. Серверная часть разлагает составные ключевые сравнения в отдельные условия столбца.
  • Сравнение кортежей с подзапросами требует Django версии 5.2.4 и выше.
  • Некоторые операции миграции по-прежнему имеют известные исключения. Сведения о текущем состоянии см. в разделе "Ограничения" и неподдерживаемые функции в mssql-django .
from django.db import models
from django.db.models import CompositePrimaryKey

class OrderItem(models.Model):
    pk = CompositePrimaryKey("order_id", "product_id")
    order = models.ForeignKey("Order", on_delete=models.CASCADE)
    product = models.ForeignKey("Product", on_delete=models.CASCADE)
    quantity = models.IntegerField()

IDENTITY_INSERT обработка

При вставке явных значений в объект AutoField (например, восстановление данных из резервной копии с определенными идентификаторами) серверная часть автоматически упаковывает вставкуSET IDENTITY_INSERT ON / SET IDENTITY_INSERT OFF. Ручное написание SQL-запросов не требуется.

# The backend handles IDENTITY_INSERT automatically
Product.objects.create(id=42, name="Restored Widget", price=9.99)

Note

SQL Server позволяет, чтобы в одном сеансе одновременно только одна таблица имела IDENTITY_INSERT ON. Если вставить явные идентификаторы в несколько таблиц в одном блоке, серверная часть обрабатывает переключатель для каждой atomic() инструкции. Однако одновременные сеансы, также использующие IDENTITY_INSERT в той же таблице, могут конфликтовать.