Datenbankmigrationen mit mssql-django

Dieser Artikel erklärt, wie Djangos Migrationssystem über das mssql-django Backend mit SQL Server zusammenarbeitet und Randfälle dokumentiert.

Erstellen und Anwenden von Migrationen

Djangos Migrationsworkflow funktioniert auf die gleiche Weise mit SQL Server wie mit anderen Datenbanken:

  1. Generieren Sie Migrationen aus Änderungen am Modell:

    python manage.py makemigrations myapp
    
  2. Überprüfen Sie die generierten Migrationsdateien in <app>/migrations/.

  3. Migrationen auf die Datenbank anwenden:

    python manage.py migrate myapp
    
  4. Migrationsstatus überprüfen:

    python manage.py showmigrations myapp
    

Anfängliches Projektsetup

Wenn Sie ein neues Django-Projekt mit SQL Server einrichten, führen Sie Migrationen aus, um integrierte Django-Tabellen zu erstellen (Authentifizierung, Sitzungen, Administrator):

python manage.py migrate

Mit diesem Befehl werden alle Tabellen erstellt, die für die in INSTALLED_APPS aufgeführten Apps erforderlich sind.

Benutzerdefiniertes SQL in Migrationen

Verwenden Sie migrations.RunSQL, um während der Migrationen rohe SQL-Anweisungen auszuführen. Dieser Ansatz eignet sich zum Erstellen gespeicherter Prozeduren, Trigger oder anderer SQL Server spezifischer Objekte:

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;",
        ),
    ]

Migrations-Randfälle

Die folgenden Migrationsvorgänge erfordern Workarounds, wenn SQL Server als Ziel verwendet wird.

AutoField-Änderung

Das Ändern eines Modellfelds von oder zu AutoField während der Migration wird nicht unterstützt. SQL Server lässt das Hinzufügen oder Entfernen der IDENTITY Eigenschaft aus einer vorhandenen Spalte nicht zu.

Problemumgehung: Erstellen Eines neuen Modells mit dem gewünschten Feldtyp. Migrieren Sie Daten aus der alten Tabelle zur neuen Tabelle, und legen Sie dann die alte Tabelle ab.

Umbenennen von Feld oder Modell mit Fremdschlüsseleinschränkungen

Das Umbenennen eines Felds oder Modells mit Fremdschlüsseleinschränkungen kann fehlschlagen. SQL Server erfordert das Löschen und Neuerstellen von FK-Constraints bei Umbenennungen.

Problemumgehung: Verwenden Sie migrations.SeparateDatabaseAndState, um den FK-Constraint zu löschen, die Spalte umzubenennen und den Constraint neu zu erstellen, und weisen Sie Django dabei an, seinen Modellstatus zu aktualisieren. Im folgenden Beispiel wird der product Fremdschlüssel in einem Order Modell umbenannt in 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",
                ),
            ],
        ),
    ]

Suchen Sie den tatsächlichen Constraint-Namen in Ihrer Datenbank nach, bevor Sie diesen Transact-SQL (T-SQL)-Code ausführen. Django generiert Einschränkungsnamen, die einen kurzen Hash enthalten, sodass der Name in Ihrem Schema nicht mit dem hier gezeigten Platzhalter übereinstimmt.

Referenzielle Aktionen auf Datenbankebene

Django 6.1 fügt datenbankebene referenzielle Aktionen wie DB_CASCADE, DB_SET_NULL, und DB_SET_DEFAULThinzu. SQL Server lehnt Fremdschlüsselgraphen mit mehreren Kaskadenpfaden zur selben Tabelle ab und mssql-django unterstützt diese Werte daher in keiner SQL Server-Version. Mit einem dieser Werte wird die Django-Systemprüfung fields.E324ausgelöst. Nutze stattdessen das Standardverhalten auf Django-Niveau on_delete .

Bitweise Aggregate

Django 6.1 fügt BitOr, BitXor und BitAnd hinzu. SQL Server hat keine native bitweise Aggregatfunktion und mssql-django emuliert diese Aggregate nicht. Sie zu nennen bringt NotSupportedErrorGehaltserhöhungen.

Migrationen zusammenfassen

Nachdem sich viele Migrationen angesammelt haben, können Sie sie in einer kleineren Anzahl von Dateien zusammenfassen:

python manage.py squashmigrations myapp 0001 0010

Tip

Testen Sie zusammengeführte Migrationen immer mit einer neuen Datenbank, um sicherzustellen, dass sie das korrekte Schema erzeugen.

Generierte Spalten (berechnete Spalten)

Das mssql-django-Backend unterstützt die GeneratedField von Django, die SQL Server-berechneten Spalten entsprechen.

Gespeicherte (PERSISTED) generierte Spalten

Eine gespeicherte generierte Spalte wird physisch auf den Datenträger geschrieben und aktualisiert, wenn sich die Quellspalten ändern:

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

Dies generiert: [total_price] AS (([price] * (1 + [tax_rate]))) PERSISTED. SQL Server normalisiert den Ausdruck, wenn er ihn speichert, also sys.computed_columns meldet ([price]*((1)+[tax_rate])).

Virtuelle generierte Spalten

Eine virtuelle generierte Spalte wird zur Abfragezeit berechnet und verbraucht keinen Speicher:

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 schränkt Indizes für nicht persistierte berechnete Spalten ein. Verwenden Sie db_persist=True, wenn Sie die generierte Spalte indizieren müssen.

Tabellen- und Spaltenkommentare

Das mssql-django Backend unterstützt Djangos db_comment Funktion in unterstützten Django-Versionen. Kommentare werden als MS_Description erweiterte Eigenschaften für das SQL Server-Objekt gespeichert.

Tabellenkommentare

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

Spaltenkommentare

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

Kommentare sind in SQL Server Management Studio unter den Spalten- bzw. Tabelleneigenschaften und über sys.extended_properties sichtbar.

Zusammengesetzte Primärschlüssel

Django 5.2 führte CompositePrimaryKey ein. Das mssql-django Back-End unterstützt teilweise zusammengesetzte Primärschlüssel, aber einige Django-Testfälle sind weiterhin ausgeschlossen. Validieren Sie Migrationen mit zusammengesetzten Schlüsseln und Abfragen in Ihrer Anwendung, bevor Sie sie in der Produktion einsetzen.

  • inspectdb generiert zusammengesetzte Primärschlüssel nicht korrekt. Definieren Sie sie manuell nach der Inspektion.
  • Tupelabfragen werden nicht unterstützt. Das Back-End zerlegt zusammengesetzte Schlüsselvergleiche in einzelne Spaltenbedingungen.
  • Tupelvergleiche mit Unterabfragen erfordern Django 5.2.4 und neuere Versionen.
  • Einige Migrationsvorgänge weisen noch bekannte Ausschlüsse auf. Siehe Einschränkungen und nicht unterstützte Features in mssql-django für den aktuellen Status.
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-Behandlung

Wenn Sie explizite Werte in eine AutoField (z. B. das Wiederherstellen von Daten aus einer Sicherung mit bestimmten IDs) einfügen, schließt das Back-End das Einfügen automatisch umSET IDENTITY_INSERT ON / SET IDENTITY_INSERT OFF. Es ist kein manuelles SQL erforderlich.

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

Note

SQL Server lässt zu, dass jeweils nur eine Tabelle pro Sitzung gleichzeitig über IDENTITY_INSERT ON verfügt. Wenn Sie in einem einzigen atomic()-Block explizite IDs in mehrere Tabellen einfügen, behandelt das Back-End das Umschalten für jede Anweisung einzeln. Es kann jedoch zu Konflikten mit gleichzeitigen Sitzungen kommen, die ebenfalls IDENTITY_INSERT in derselben Tabelle verwenden.