Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Tento článek je podrobný průvodce migrací pro aplikace Django, které se přesouvají z PostgreSQL (psycopg2nebopsycopg) na SQL Server (mssql-django). Obecný přehled migrace z jakékoli databáze najdete v tématu Migrace aplikací Django z jiných databází do SQL Server.
Předpoklady
- Python 3.8 nebo novější
- Microsoft ovladač ODBC 17 nebo 18 pro SQL Server. Vizte Nainstalujte mssql-django.
- SQL Server 2016 nebo novější nebo Azure SQL Database
Přepnutí back-endu databáze
Nahraďte konfiguraci PostgreSQL v 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",
},
},
}
Aktualizace requirements.txt:
# Remove
# psycopg2-binary>=2.9
# or psycopg[binary]>=3.1
# Add
mssql-django>=1.5
Nahrazení funkcí django.contrib.postgres
Modul django.contrib.postgres poskytuje pole, funkce a vyhledávání specifická pro PostgreSQL. Nefungují s SQL Server. Následující části ukazují, jak nahradit jednotlivé funkce.
Pole
PostgreSQL ArrayField ukládá pole nativně. SQL Server nemá datový typ sloupce pro pole.
Možnost 1: JSONField (pracuje s Django 3.2 a novějšími verzemi)
# 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)
Dotazování na změny:
# 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"],
)
)
Možnost 2: Související tabulka (normalizovaná, lepší pro velká pole nebo časté filtrování)
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
Nahradit s 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 podporuje stejnou syntaxi vyhledávání klíčů:
# Both backends support this
Profile.objects.filter(metadata__theme="dark")
Pole rozsahu hodnot
Typy rozsahů PostgreSQL (IntegerRangeField, BigIntegerRangeField, DateRangeField, DateTimeRangeField, DecimalRangeField) nemají žádný SQL Server ekvivalent. Použijte dvě samostatná pole:
# 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()
Aktualizujte dotazy tak, aby používaly samostatná porovnání polí. Předtím s PostgreSQL DateRangeField:
from django.contrib.postgres.fields import DateRangeField
from psycopg2.extras import DateRange
Event.objects.filter(dates__contains=DateRange(start, end))
Poté se dvěma DateField sloupci v SQL Serveru:
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 a CIEmailField
Typy textu nerozlišující velká a malá písmena PostgreSQL používají příponu citext . Výchozí kolace SQL Serveru (SQL_Latin1_General_CP1_CI_AS) už nerozlišuje malá a velká písmena, takže standardní CharField a EmailField se chovají stejně:
# 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
Fulltextové vyhledávání PostgreSQL je hluboce integrované s Django. SQL Server má vlastní fulltextový vyhledávací modul, ale žádnou integraci Django ORM. Podívejte se na migraci fulltextového vyhledávání dále v tomto článku.
Agregační funkce
Nahraďte agregace specifické pro PostgreSQL:
# 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_AGGvyžaduje SQL Server 2017 nebo novější nebo Azure SQL Database.
Migrace fulltextového vyhledávání
Fulltextové vyhledávání PostgreSQL používá tsvectortsquerya GIN indexy. SQL Server má samostatný fulltextový vyhledávací modul.
Povolení fulltextového vyhledávání v 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;
Dotazování fulltextového vyhledávání z Django
K přístupu k funkcím CONTAINS a FREETEXT serveru SQL Server použijte nezpracované příkazy SQL:
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],
)
)
Pro seřazené výsledky (ekvivalentní k 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],
)
Provozní příručka pro údržbu fulltextového indexu
Plánování údržby SQL Server fulltextových indexů po migraci:
- Používá se
CHANGE_TRACKING AUTOpro aktualizace téměř v reálném čase. - Použijte
CHANGE_TRACKING MANUALpro okna hromadného načítání a pak spusťte celou populaci. - Sledujte stav procházení a nevyřízené položky pomocí
sys.fulltext_indexesasys.dm_fts_index_population.
Kontrola stavu:
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;
Po rozsáhlém načtení dat při ručním sledování:
ALTER FULLTEXT INDEX ON [products_product] START FULL POPULATION;
Tip
Znovu sestavte nebo znovu naplňte fulltextové indexy v době nízkého provozu. U velkých tabulek může být jejich úplné naplnění nákladné.
Vytvoření správce vyhledávání
Zabalení nezpracovaného SQL ve správci pro čistý přístup:
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 a prostorová data
mssql-django neobsahuje GIS backend GeoDjango. Pokud vaše aplikace PostgreSQL používá PostGIS prostřednictvím django.contrib.gis, nemůžete migrovat prostorové dotazy přímo do Django ORM na SQL Server.
SQL Server nativně podporuje datové typy geografie a geometrie. Práce s prostorovými daty po migraci:
- Ukládejte prostorová data pomocí nezpracovaného SQL nebo vlastních polí modelu, která se mapují na sloupce geography nebo geometry v SQL Serveru.
- Dotazování prostorových dat pomocí nezpracovaného SQL pomocí předdefinovaných prostorových funkcí 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],
)
- Zvažte knihovny třetích stran, které přidávají SQL Server prostorovou podporu do Django, nebo zachovat prostorové dotazy jako nezpracované SQL při použití ORM pro všechno ostatní.
Note
Pokud vaše aplikace silně závisí na prostorových vyhledáváních GeoDjango, pečlivě vyhodnoťte náklady na migraci. Přesun prostorových dotazů do nezpracovaného SQL vyžaduje přepsání každého prostorového filtru GeoDjango.
Migrace sdružování připojení
Pokud vaše aplikace PostgreSQL používá pgbouncer pro sdružování připojení, nahraďte ji integrovanou správou připojení Django nebo sdružováním připojení ODBC.
Znovupoužití připojení v Django
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+
},
}
Další podrobnosti najdete v tématu Sdružování připojení v mssql-django.
Náhrada za DISTINCT ON
PostgreSQL podporuje DISTINCT ON, které umožňuje získat jeden řádek z každé skupiny. SQL Server tuto syntaxi nepodporuje. Místo toho použijte funkce okna:
# 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
"""
)
Dotazy nad JSONB
Typ PostgreSQL jsonb podporuje bohaté operátory dotazů. SQL Server ukládá JSON jako nvarchar(max) s dostupnými funkcemi dotazů od SQL Server 2016.
Syntaxe vyhledávání Django JSONField funguje na obou back-endech pro základní operace:
# Works on both PostgreSQL and SQL Server
Config.objects.filter(data__settings__theme="dark")
Config.objects.filter(data__has_key="settings")
Pro pokročilé dotazy nad JSON, které ORM frameworku Django nepodporuje, použijte funkce SQL Serveru JSON_VALUE a OPENJSON:
from django.db.models.expressions import RawSQL
# Query nested JSON values
Config.objects.annotate(
theme=RawSQL("JSON_VALUE(data, '$.settings.theme')", [])
).filter(theme="dark")
Odebrání závislostí PostgreSQL
Po migraci odeberte z projektu balíčky PostgreSQL:
pip uninstall psycopg2-binary psycopg2 psycopg
Odebrat django.contrib.postgres z INSTALLED_APPS :settings.py
INSTALLED_APPS = [
# Remove this line:
# "django.contrib.postgres",
"django.contrib.admin",
"django.contrib.auth",
# ...
]
Kontrolní seznam pro migraci
| Krok | Podrobnosti |
|---|---|
| Přepnutí back-endu | Nahraďte django.db.backends.postgresql za mssql v settings.py. |
| Nahradit contrib.postgres | Prohoďte ArrayField, HStoreField, pole rozsahu a pole CI. |
| Aktualizace fulltextového vyhledávání | Migrujte z tsvector/tsquery do SQL Server . CONTAINS/FREETEXT |
| Aktualizace prostorových dotazů | Přepište vyhledávání GeoDjango jako nezpracované SQL pomocí SQL Server prostorových funkcí. |
Nahradit DISTINCT ON |
Používejte ROW_NUMBER() funkce oken. |
| Aktualizace nezpracovaných SQL | Změňte syntaxi PostgreSQL (LIMIT, ||, NOW()) na SQL Server syntaxi. Viz Aktualizace vlastního SQL. |
| Povolení RCSI | Nastaví se READ_COMMITTED_SNAPSHOT ON tak, aby odpovídalo chování MVCC PostgreSQL. Viz Rozdíly v izolaci transakcí. |
| Kolace testů | Ověřte, že chování rozlišování velkých a malých písmen odpovídá vašim očekáváním. Viz rozdíly v řazení. |
| Odebrat psycopg2 | Odinstalujte psycopg2-binary nebo psycopg. Odeberte django.contrib.postgres. |
| Opětovné generování migrací | Odstraňte staré soubory migrace, spusťte makemigrations a migrate znovu. |
| Migrujte data | Použijte dumpdata/loaddata nebo nástroj ETL pro velké datové sady. |