Migrace databází pomocí mssql-django

Tento článek vysvětluje, jak systém migrací v Django funguje se serverem SQL Server prostřednictvím backendu mssql-django, a dokumentuje známé okrajové případy.

Vytvoření a použití migrací

Pracovní postup migrace Django funguje stejně s SQL Server jako s jinými databázemi:

  1. Generování migrací ze změn modelu:

    python manage.py makemigrations myapp
    
  2. Zkontrolujte vygenerované soubory migrace v souboru <app>/migrations/.

  3. Aplikujte migrace na databázi:

    python manage.py migrate myapp
    
  4. Kontrola stavu migrace:

    python manage.py showmigrations myapp
    

Počáteční nastavení projektu

Když nastavíte nový projekt Django s SQL Server, spusťte migrace a vytvořte předdefinované tabulky Django (ověřování, relace, správce):

python manage.py migrate

Tento příkaz vytvoří všechny tabulky vyžadované aplikacemi uvedenými v seznamu INSTALLED_APPS.

Vlastní SQL v migracích

Slouží migrations.RunSQL ke spouštění nezpracovaných příkazů SQL během migrací. Tento přístup je užitečný při vytváření uložených procedur, triggerů nebo jiných objektů specifických pro 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;",
        ),
    ]

Známé okrajové případy při migraci

Následující operace migrace vyžadují alternativní řešení při cílení na SQL Server.

Změna AutoField

Změna pole modelu z nebo do AutoField okamžiku migrace se nepodporuje. SQL Server nepovoluje přidání nebo odebrání IDENTITY vlastnosti z existujícího sloupce.

Alternativní řešení: Vytvoření nového modelu s požadovaným typem pole Migrujte data ze staré tabulky do nové tabulky a potom zahoďte starou tabulku.

Přejmenovat pole nebo model s omezeními cizího klíče

Přejmenování pole nebo modelu s omezeními cizího klíče může selhat. SQL Server během operací přejmenování vyžaduje vyřazení a opětovné vytvoření omezení FK.

Alternativní řešení: Slouží migrations.SeparateDatabaseAndState k vyřazení omezení FK, přejmenování sloupce a opětovnému vytvoření omezení a zobrazení upozornění Django, aby aktualizoval stav modelu. Následující příklad přejmenuje cizí klíč product u modelu Order na 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",
                ),
            ],
        ),
    ]

Před spuštěním tohoto kódu T-SQL vyhledejte ve své databázi skutečný název omezení. Django generuje názvy omezení obsahující krátký hash, takže název ve vašem schématu neodpovídá zástupnému symbolu uvedenému zde.

Migrace squashů

Po mnoha migracích je můžete zmáčknout do menšího počtu souborů:

python manage.py squashmigrations myapp 0001 0010

Tip

Vždy otestujte chycené migrace proti nové databázi, abyste měli jistotu, že vytvoří správné schéma.

Generované sloupce (počítané sloupce)

Backend mssql-django podporuje funkci Django GeneratedField (Django 5.0 a novější), která odpovídá vypočítaným sloupcům v SQL Serveru.

Uložené (PERSISTED) generované sloupce

Uložený vygenerovaný sloupec se fyzicky zapisuje na disk a aktualizuje se při změně zdrojových sloupců:

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,
    )

Tím se vygeneruje: total_price AS ([price] * (1 + [tax_rate])) PERSISTED.

Virtuální generované sloupce

Virtuální generovaný sloupec se vypočítává při dotazu a nezabírá úložný prostor:

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 omezuje vytváření indexů na nepersistovaných vypočítaných sloupcích. Použijte db_persist=True , pokud potřebujete indexovat vygenerovaný sloupec.

Komentáře k tabulce a sloupcům

Backend mssql-django podporuje funkci db_comment frameworku Django (Django 4.2 a novější). Komentáře jsou uloženy jako MS_Description rozšířené vlastnosti v objektu SQL Server.

Komentáře k tabulce

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."

Komentáře ke sloupcům

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")

Komentáře jsou viditelné v SQL Server Management Studio ve vlastnostech sloupce nebo tabulky a prostřednictvím sys.extended_properties.

Složené primární klíče

Django 5.2 představil CompositePrimaryKey. Back-end mssql-django má částečnou podporu složených primárních klíčů, ale některé testovací případy Django jsou stále vyloučené. Před nasazením do produkčního prostředí ověřte ve své aplikaci migrace a dotazy používající složené klíče.

  • inspectdb negeneruje správně složené primární klíče. Definujte je ručně po kontrole.
  • Vyhledávání ntic nejsou podporována. Backend rozkládá porovnání složených klíčů na podmínky jednotlivých sloupců.
  • Porovnání n-tic s poddotazy vyžaduje Django 5.2.4 a novější.
  • Některé operace migrace mají stále známá vyloučení. Aktuální stav najdete v tématu Omezení a nepodporované funkce v 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 Zpracování

Když vložíte explicitní hodnoty do AutoField (například při obnově dat ze zálohy s konkrétními ID), backend automaticky obalí příkaz INSERT do SET IDENTITY_INSERT ON / SET IDENTITY_INSERT OFF. Není potřeba žádný ruční SQL.

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

Note

SQL Server umožňuje, aby v rámci jedné relace měla IDENTITY_INSERT ON současně jen jedna tabulka. Pokud v jednom bloku atomic() vložíte explicitní ID do více tabulek, backend zpracovává toto přepnutí pro každý příkaz zvlášť. Avšak souběžné relace, které také používají IDENTITY_INSERT nad stejnou tabulkou, mohou být v konfliktu.