Django-apps migreren van PostgreSQL naar SQL Server

Dit artikel is een gedetailleerde migratiehandleiding voor Django-toepassingen die overstappen van PostgreSQL (psycopg2of psycopg) naar SQL Server (mssql-django). Zie Django-apps migreren van andere databases naar SQL Server voor een algemeen overzicht van de migratie vanuit een database.

Prerequisites

  • Python 3.8 of hoger
  • Microsoft ODBC-stuurprogramma 17 of 18 voor SQL Server. Zie Mssql-django installeren.
  • SQL Server 2016 of hoger of Azure SQL Database

De back-end van de database wisselen

Vervang uw PostgreSQL-configuratie in settings.py:

# Before (PostgreSQL)
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": "mydb",
        "USER": "myuser",
        "PASSWORD": "mypassword",
        "HOST": "localhost",
        "PORT": "5432",
    },
}

# After (SQL Server)
DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": "mydb",
        "USER": "myuser",
        "PASSWORD": "mypassword",
        "HOST": "localhost",
        "PORT": "1433",
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
        },
    },
}

requirements.txtbijwerken:

# Remove
# psycopg2-binary>=2.9
# or psycopg[binary]>=3.1

# Add
mssql-django>=1.5

Django.contrib.postgres-functies vervangen

De django.contrib.postgres module biedt postgreSQL-specifieke velden, functies en zoekacties. Deze werken niet met SQL Server. In de volgende secties ziet u hoe u elke functie vervangt.

ArrayField

PostgreSQL ArrayField slaat matrices systeemeigen op. SQL Server heeft geen matrixkolomtype.

Optie 1: JSONField (werkt met Django 3.2 en latere versies)

# Before
from django.contrib.postgres.fields import ArrayField

class Product(models.Model):
    tags = ArrayField(models.CharField(max_length=50), default=list)

# After
class Product(models.Model):
    tags = models.JSONField(default=list)

Wijzigingen opvragen:

# Before (PostgreSQL)
Product.objects.filter(tags__contains=["sale"])
Product.objects.filter(tags__overlap=["sale", "new"])
Product.objects.filter(tags__len=3)

# After (SQL Server with JSONField)
# Use __contains for exact list matching
Product.objects.filter(tags__contains=["sale"])

# For overlap-style queries, use raw SQL
from django.db.models.expressions import RawSQL
Product.objects.filter(
    pk__in=RawSQL(
        """
        SELECT p.id FROM products_product p
        CROSS APPLY OPENJSON(p.tags) t
        WHERE t.value IN (%s, %s)
        """,
        ["sale", "new"],
    )
)

Optie 2: Gerelateerde tabel (genormaliseerd, beter voor grote matrices of frequent filteren)

class Product(models.Model):
    name = models.CharField(max_length=200)

class ProductTag(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name="tags")
    tag = models.CharField(max_length=50, db_index=True)

    class Meta:
        unique_together = [("product", "tag")]

HStoreField

Vervangen door JSONField:

# Before
from django.contrib.postgres.fields import HStoreField

class Profile(models.Model):
    metadata = HStoreField(default=dict)

# After
class Profile(models.Model):
    metadata = models.JSONField(default=dict)

JSONField ondersteunt dezelfde syntaxis voor sleutelzoekacties:

# Both backends support this
Profile.objects.filter(metadata__theme="dark")

Bereikvelden

PostgreSQL-bereiktypen (IntegerRangeField, BigIntegerRangeField, DateRangeField, DateTimeRangeField) DecimalRangeFieldhebben geen SQL Server equivalent. Gebruik twee afzonderlijke velden:

# Before
from django.contrib.postgres.fields import DateRangeField

class Event(models.Model):
    dates = DateRangeField()

# After
class Event(models.Model):
    start_date = models.DateField()
    end_date = models.DateField()

Werk query's bij om afzonderlijke veldvergelijkingen te gebruiken. Vóór, met PostgreSQL's DateRangeField:

from django.contrib.postgres.fields import DateRangeField
from psycopg2.extras import DateRange

Event.objects.filter(dates__contains=DateRange(start, end))

Na, met twee DateField kolommen op SQL Server:

from datetime import date

start = date(2026, 1, 1)
end = date(2026, 12, 31)

Event.objects.filter(start_date__lte=start, end_date__gte=end)

CITextField en CIEmailField

De hoofdlettergevoelige teksttypen van PostgreSQL gebruiken de citext extensie. De standaardsortering van SQL Server (SQL_Latin1_General_CP1_CI_AS) is al hoofdletterongevoelig, dus standaard CharField en EmailField gedragen zich hetzelfde:

# Before
from django.contrib.postgres.fields import CITextField

class Tag(models.Model):
    name = CITextField(max_length=100)

# After - already case-insensitive with default SQL Server collation
class Tag(models.Model):
    name = models.CharField(max_length=100)

SearchVector, SearchQuery, SearchRank

Zoeken in volledige tekst in PostgreSQL is diep geïntegreerd met Django. SQL Server heeft een eigen zoekprogramma voor volledige tekst, maar geen Django ORM-integratie. Zie de migratie voor zoeken in volledige tekst verderop in dit artikel.

Gecompileerde functies

Vervang PostgreSQL-specifieke aggregaties:

# Before
from django.contrib.postgres.aggregates import ArrayAgg, StringAgg

Product.objects.values("category").annotate(
    all_names=ArrayAgg("name"),
    name_list=StringAgg("name", delimiter=", "),
)

# After - use SQL Server equivalents via RawSQL
from django.db.models.expressions import RawSQL

Product.objects.values("category").annotate(
    name_list=RawSQL(
        "STRING_AGG(name, ', ') WITHIN GROUP (ORDER BY name)",
        [],
    ),
)

Note

STRING_AGGvereist SQL Server 2017 of hoger of Azure SQL Database.

Migratie van zoeken in volledige tekst

PostgreSQL-zoekopdrachten in volledige tekst gebruiken tsvector, tsqueryen GIN indexen. SQL Server heeft een afzonderlijke zoekmachine voor volledige tekst.

Zoeken in volledige tekst inschakelen in SQL Server

-- Create a full-text catalog
CREATE FULLTEXT CATALOG [MyAppCatalog] AS DEFAULT;

-- Create a full-text index (table must have a unique index)
CREATE FULLTEXT INDEX ON [products_product]([name], [description])
KEY INDEX [PK_products_product]
WITH CHANGE_TRACKING AUTO;

Query's uitvoeren voor volledige-tekstzoekopdrachten in Django

Gebruik onbewerkte SQL voor toegang tot SQL Server CONTAINS en FREETEXT functies:

from django.db.models.expressions import RawSQL

# Equivalent of PostgreSQL SearchVector + SearchQuery
def search_products(query):
    return Product.objects.filter(
        pk__in=RawSQL(
            """
            SELECT p.id FROM products_product p
            WHERE CONTAINS((p.name, p.description), %s)
            """,
            [query],
        )
    )

Voor gerangschikte resultaten (gelijk aan SearchRank):

def search_products_ranked(query):
    return Product.objects.raw(
        """
        SELECT p.*, ft.[RANK]
        FROM products_product p
        INNER JOIN CONTAINSTABLE(products_product, (name, description), %s) ft
            ON p.id = ft.[KEY]
        ORDER BY ft.[RANK] DESC
        """,
        [query],
    )

Onderhoudsrunbook voor volledige tekstindex

Plan het onderhoud voor SQL Server full-text-indexen na migratie:

  • Gebruik CHANGE_TRACKING AUTO voor bijna realtime-updates.
  • Gebruik CHANGE_TRACKING MANUAL voor vensters voor bulksgewijs laden en voer vervolgens een volledige populatie uit.
  • De crawlstatus en achterstand volgen via sys.fulltext_indexes en sys.dm_fts_index_population.

Status controleren:

SELECT
    OBJECT_NAME(i.object_id) AS table_name,
    i.change_tracking_state_desc,
    i.has_crawl_completed,
    i.crawl_type_desc
FROM sys.fulltext_indexes AS i;

Na grote dataladingen met handmatige tracering:

ALTER FULLTEXT INDEX ON [products_product] START FULL POPULATION;

Tip

Bouw indexen in volledige tekst opnieuw of vul deze opnieuw in tijdens vensters met weinig verkeer. Volledige populaties kunnen duur zijn voor grote tabellen.

Een search manager maken

Verpakt de onbewerkte SQL in een manager voor schone toegang:

class ProductSearchManager(models.Manager):
    def search(self, query):
        if not query:
            return self.none()
        return self.filter(
            pk__in=RawSQL(
                """
                SELECT p.id FROM products_product p
                WHERE CONTAINS((p.name, p.description), %s)
                """,
                [query],
            )
        )

class Product(models.Model):
    name = models.CharField(max_length=200)
    description = models.TextField()

    objects = ProductSearchManager()
    # Usage: Product.objects.search("mountain bike")

PostGIS en ruimtelijke gegevens

mssql-django bevat geen GeoDjango GIS-back-end. Als uw PostgreSQL-toepassing Gebruikmaakt van PostGIS viadjango.contrib.gis, kunt u ruimtelijke query's niet rechtstreeks migreren naar de Django ORM op SQL Server.

SQL Server biedt systeemeigen ondersteuning voor geografie- en geometriegegevenstypen. Werken met ruimtelijke gegevens na migratie:

  • Sla ruimtelijke gegevens op met behulp van onbewerkte SQL- of aangepaste modelvelden die zijn toegewezen aan SQL Server geografie- of geometriekolommen.
  • Query's uitvoeren op ruimtelijke gegevens met behulp van onbewerkte SQL met ingebouwde ruimtelijke functies van SQL Server:
from django.db import connection

with connection.cursor() as cursor:
    cursor.execute(
        """
        SELECT id, name
        FROM stores
        WHERE location.STDistance(geography::Point(%s, %s, 4326)) <= %s
        """,
        [latitude, longitude, radius_meters],
    )
  • Overweeg bibliotheken van derden die SQL Server ruimtelijke ondersteuning toevoegen aan Django of ruimtelijke query's als onbewerkte SQL bewaren terwijl u de ORM voor alles gebruikt.

Note

Als uw toepassing sterk afhankelijk is van ruimtelijke geodjango-zoekacties, evalueert u de migratiekosten zorgvuldig. Voor het verplaatsen van ruimtelijke query's naar onbewerkte SQL moet elk ruimtelijk GeoDjango-filter opnieuw worden geschreven.

Migratie van groepsgewijze verbindingen

Als uw PostgreSQL-toepassing pgbouncer gebruikt voor connection pooling, vervangt u dit door Django's ingebouwde verbindingsbeheer of ODBC-verbindingspooling.

Django-verbinding hergebruiken

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": "<your-database>",
        "HOST": "<your-server>",
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
        },
        "CONN_MAX_AGE": 600,  # Reuse connections for 10 minutes
        "CONN_HEALTH_CHECKS": True,  # Django 4.1+
    },
}

Zie Verbindingspooling in mssql-django voor meer informatie.

alternatief voor DISTINCT ON

PostgreSQL biedt ondersteuning DISTINCT ON voor het ophalen van één rij per groep. SQL Server biedt geen ondersteuning voor deze syntaxis. Gebruik in plaats daarvan vensterfuncties:

# Before (PostgreSQL)
Entry.objects.order_by("blog_id", "-pub_date").distinct("blog_id")

# After (SQL Server) - use raw SQL with ROW_NUMBER
Entry.objects.raw(
    """
    SELECT * FROM (
        SELECT *, ROW_NUMBER() OVER (PARTITION BY blog_id ORDER BY pub_date DESC) AS rn
        FROM blog_entry
    ) sub
    WHERE rn = 1
    """
)

JSONB-query’s

Het type van jsonb PostgreSQL ondersteunt uitgebreide queryoperators. SQL Server slaat JSON op als nvarchar(max) met queryfuncties die beschikbaar zijn sinds SQL Server 2016.

De zoeksyntaxis van JSONField Django werkt op beide back-ends voor basisbewerkingen:

# Works on both PostgreSQL and SQL Server
Config.objects.filter(data__settings__theme="dark")
Config.objects.filter(data__has_key="settings")

Voor geavanceerde JSON-query's die niet worden ondersteund door Django's ORM, gebruikt u SQL Server JSON_VALUE en OPENJSON functies:

from django.db.models.expressions import RawSQL

# Query nested JSON values
Config.objects.annotate(
    theme=RawSQL("JSON_VALUE(data, '$.settings.theme')", [])
).filter(theme="dark")

PostgreSQL-afhankelijkheden verwijderen

Verwijder na de migratie PostgreSQL-pakketten uit uw project:

pip uninstall psycopg2-binary psycopg2 psycopg

Verwijder django.contrib.postgres uit INSTALLED_APPS in settings.py:

INSTALLED_APPS = [
    # Remove this line:
    # "django.contrib.postgres",
    "django.contrib.admin",
    "django.contrib.auth",
    # ...
]

Controlelijst voor migratie

Stap Details
Backend wijzigen Vervang django.db.backends.postgresql door mssql in settings.py.
Vervang contrib.postgres Verwissel ArrayField, HStoreField, bereikvelden en CI-velden.
Zoeken in volledige tekst bijwerken Migreren van tsvector/tsquery naar SQL Server CONTAINS/FREETEXT.
Ruimtelijke query's bijwerken GeoDjango-zoekopdrachten opnieuw schrijven als onbewerkte SQL met behulp van SQL Server ruimtelijke functies.
Vervangen DISTINCT ON Gebruik ROW_NUMBER() vensterfuncties.
Onbewerkte SQL bijwerken Wijzig de Syntaxis van PostgreSQL (LIMIT, ||, NOW()) in SQL Server syntaxis. Zie Aangepaste SQL bijwerken.
RCSI inschakelen Ingesteld READ_COMMITTED_SNAPSHOT ON op overeenstemming met het gedrag van PostgreSQL MVCC. Zie verschillen in transactieisolatie.
Sortering testen Controleer of het gedrag van hoofdlettergevoeligheid overeenkomt met uw verwachtingen. Zie Sorteringsverschillen.
Psycopg2 verwijderen Verwijderen psycopg2-binary of psycopg. Verwijder django.contrib.postgres.
Migraties opnieuw genereren Verwijder oude migratiebestanden en voer makemigrations en migrate opnieuw uit.
Gegevens migreren Gebruik dumpdata/loaddata of een ETL-hulpprogramma voor grote gegevenssets.