Gérer les chaînes de caractères et Unicode

Microsoft SQL propose plusieurs types de chaînes que le pilote mssql-python mappe sur des objets Pythonstr. La décision clé est d’utiliser varchar (non-Unicode) ou nvarchar (Unicode) :

  • Utilisez nvarchar lorsque vos données peuvent contenir des caractères hors ASCII, tels que des noms, des adresses ou du contenu généré par les utilisateurs dans n’importe quelle langue.
  • Utilisez varchar lorsque les données sont strictement ASCII (codes, identifiants, adresses e-mail) et que vous souhaitez économiser de l’espace. varchar utilise 1 octet par caractère ; nvarchar utilise 2 octets par caractère.
Type SQL Unicode Longueur maximale type de Python
char(n) Non 8,000 str
varchar(n) Non 8,000 str
varchar(max) Non 2 Go str
nchar(n) Oui 4,000 str
nvarchar(n) Oui 4,000 str
nvarchar(max) Oui 2 Go str
text Non 2 Go (déprécié) str
ntext Oui 2 Go (obsolète) str

Opérations de chaînes de base

Le pilote associe tous les types de chaînes SQL Microsoft à des objets Pythonstr.

Insérer et récupérer des cordes

Utilisez des requêtes paramétrées pour insérer et récupérer en toute sécurité des données de chaînes de données depuis la base de données.

import mssql_python

conn = mssql_python.connect(connection_string)
cursor = conn.cursor()

# Create temp table for demo
cursor.execute("""
    CREATE TABLE #StringDemo (
        ID INT IDENTITY(1,1) PRIMARY KEY,
        Name NVARCHAR(100),
        Email NVARCHAR(200)
    )
""")

# Insert string data
cursor.execute(
    "INSERT INTO #StringDemo (Name, Email) VALUES (%(name)s, %(email)s)",
    {"name": "Alice Smith", "email": "alice@example.com"}
)
conn.commit()

# Retrieve string data
cursor.execute("SELECT Name, Email FROM #StringDemo WHERE ID = 1")
row = cursor.fetchone()
print(row.Name)   # 'Alice Smith'
print(row.Email)  # 'alice@example.com'

Chaînes avec caractères spéciaux

Traitez les guillemets, les crochets d’angle et d’autres caractères spéciaux dans des chaînes de caractères à l’aide de requêtes paramétrées.

# Quotes and special characters handled automatically
cursor.execute("""
    CREATE TABLE #Notes (
        ID INT IDENTITY(1,1) PRIMARY KEY,
        Title NVARCHAR(200),
        Content NVARCHAR(MAX)
    )
""")
cursor.execute(
    "INSERT INTO #Notes (Title, Content) VALUES (%(title)s, %(content)s)",
    {
        "title": "O'Brien's Report",
        "content": 'Contains "quotes" and special chars: <>&'
    }
)
conn.commit()

Prise en charge d’Unicode

Utilisez les colonnes nvarchar et Python str pour stocker et récupérer du texte dans n’importe quel langage.

Stocker le texte Unicode

Insérer du contenu Unicode en passant des chaînes Python à des requêtes paramétrées ; le pilote les encode en UTF-16LE pour les colonnes nvarchar.

# International characters - use nvarchar columns
cursor.execute("""
    CREATE TABLE #Messages (
        ID INT IDENTITY(1,1) PRIMARY KEY,
        Content NVARCHAR(MAX)
    )
""")
cursor.execute("""
    INSERT INTO #Messages (Content) VALUES (%(msg)s)
""", {"msg": "Hello 你好 مرحبا שלום 🎉"})

cursor.execute("SELECT Content FROM #Messages WHERE ID = 1")
row = cursor.fetchone()
print(row.Content)  # 'Hello 你好 مرحبا שלום 🎉'

Unicode dans différents scripts

Supporter plusieurs langages et scripts dans une seule table en utilisant des colonnes nvarchar et des inserts en masse.

messages = [
    {"lang": "English", "text": "Hello, World!"},
    {"lang": "Chinese", "text": "你好,世界!"},
    {"lang": "Japanese", "text": "こんにちは世界!"},
    {"lang": "Korean", "text": "안녕하세요, 세상!"},
    {"lang": "Arabic", "text": "مرحبا بالعالم!"},
    {"lang": "Hebrew", "text": "שלום עולם!"},
    {"lang": "Russian", "text": "Привет мир!"},
    {"lang": "Greek", "text": "Γειά σου Κόσμε!"},
    {"lang": "Emoji", "text": "👋🌍✨🎉"},
]

cursor.execute("""
    CREATE TABLE #Greetings (
        ID INT IDENTITY(1,1) PRIMARY KEY,
        Language NVARCHAR(50),
        Message NVARCHAR(200)
    )
""")
cursor.executemany("""
    INSERT INTO #Greetings (Language, Message) VALUES (%(lang)s, %(text)s)
""", messages)
conn.commit()

Vérifiez que les colonnes Unicode sont de type nvarchar

Définissez toujours les colonnes comme nvarchar au lieu de varchar lorsque vos données peuvent contenir des caractères non ASCII.

-- For Unicode data, always use nvarchar, not varchar
CREATE TABLE #UnicodeDemo (
    ID INT IDENTITY PRIMARY KEY,
    Name NVARCHAR(100),        -- Supports Unicode
    Description NVARCHAR(MAX)  -- Supports large Unicode text
);

Considérations sur la longueur des cordes

Choisissez entre des types à longueur fixe et variable en fonction de la cohérence de vos longueurs de données.

Longueur fixe versus variable

char(n) de Microsoft SQL complète les valeurs avec des espaces de fin jusqu’à la longueur déclarée. Ce bourrage gaspille du stockage pour des données de longueur variable mais peut améliorer les performances pour les colonnes à largeur fixe, comme les codes pays. Utilisez varchar(n) pour la plupart des colonnes de chaînes de caractères.

L’exemple suivant montre la différence entre la manière dont les colonnes rembourrées et non rembourrées gèrent la récupération des données :

# char(6) pads to fixed length
cursor.execute(
    "SELECT StateProvinceCode FROM Person.StateProvince WHERE StateProvinceID = 1"
)  # nchar(6) column
row = cursor.fetchone()
print(repr(row.StateProvinceCode))  # 'AB    ' - right-padded with spaces

# nvarchar stores actual length
cursor.execute(
    "SELECT Name FROM Person.StateProvince WHERE StateProvinceID = 1"
)  # nvarchar column
row = cursor.fetchone()
print(repr(row.Name))  # 'Alberta' - no padding

Gérer les espaces de fin

Lors de la récupération de données à partir de colonnes de caractères de longueur fixe, utilisez rstrip() pour supprimer les espaces de remplissage ajoutés par Microsoft SQL Server.

# Strip trailing spaces from char columns
cursor.execute("SELECT ProductNumber FROM Production.Product")
for row in cursor:
    code = row.ProductNumber.rstrip()  # Remove trailing spaces
    print(f"Code: '{code}'")

Chaînes de caractères volumineuses (types MAX)

Les nvarchar(max) types et varchar(max) prennent en charge des chaînes allant jusqu’à 2 Go, idéales pour stocker de gros documents texte, JSON ou contenu XML.

# Large text content
large_content = "x" * 100000  # 100K characters

cursor.execute("""
    CREATE TABLE #Documents (
        ID INT IDENTITY(1,1) PRIMARY KEY,
        Content NVARCHAR(MAX)
    )
""")
cursor.execute("""
    INSERT INTO #Documents (Content) VALUES (%(content)s)
""", {"content": large_content})

cursor.execute("SELECT Content FROM #Documents WHERE ID = 1")
row = cursor.fetchone()
print(len(row.Content))  # 100000

Comparaison et collation de chaînes

Le comportement de comparaison des chaînes de caractères dans Microsoft SQL dépend de la collation définie pour la base de données ou la colonne.

Respect de la casse

La comparaison de chaînes de Microsoft SQL dépend de la collation. Par défaut, la plupart des bases de données utilisent une compilation insensible aux majuscules, mais vous pouvez la remplacer avec la COLLATE clause.

# Case-insensitive collation (default for many databases)
cursor.execute("SELECT * FROM Person.Person WHERE LastName = %(name)s", {"name": "smith"})
# Might match 'Smith', 'SMITH', 'smith' depending on collation

# For case-sensitive comparison
cursor.execute("""
    SELECT * FROM Person.Person 
    WHERE LastName COLLATE Latin1_General_CS_AS = %(name)s
""", {"name": "Smith"})

Correspondance de modèles avec LIKE

Utilisez l’opérateur LIKE avec des caractères génériques pour rechercher des modèles de chaînes ; utilisez la notation entre crochets pour échapper aux caractères spéciaux afin de faire correspondre des littéraux.

# Wildcard searches
search_term = "Road"
cursor.execute("""
    SELECT Name FROM Production.Product WHERE Name LIKE %(pattern)s
""", {"pattern": f"%{search_term}%"})

# Escape special characters in search
def escape_like(value: str) -> str:
    """Escape LIKE wildcards in search value."""
    return value.replace("[", "[[]").replace("%", "[%]").replace("_", "[_]")

search = "100%"
cursor.execute("""
    SELECT Name FROM Production.Product WHERE Name LIKE %(pattern)s
""", {"pattern": f"%{escape_like(search)}%"})

Considérations d’encodage

Le comportement d’encodage dépend du type de colonne SQL Microsoft et de la classification source.

Hypothèses d’encodage et valeurs par défaut Unicode

Le mssql-python pilote gère l’encodage automatiquement en fonction du type de colonne Microsoft SQL. Par défaut, les paramètres de chaîne sont envoyés en UTF-16LE pour les colonnes nvarchar et selon la compilation de base de données pour les colonnes varchar :

Type de colonne Encodage filaire Résultat Python
nvarchar, nchar, ntext UTF-16LE str (décodé par le conducteur)
varchar, char, text Codage par base de données ou collation de colonnes str (décodé par le pilote à l’aide de l’encodage source)

Les chaînes Python sont toujours Unicode en interne. Lorsque vous passez un str paramètre, le pilote l’encode pour le type de colonne cible. Par défaut, le pilote envoie les paramètres de chaîne sous forme nvarchar (Unicode), ce qui garantit la préservation des caractères indépendamment de la compilation de la base de données. Pour les varchar colonnes, UTF-8 ne s’applique que lorsque la base de données ou la colonne utilise une collation compatible UTF-8.

Si votre colonne est varchar et que vous devez envoyer des données non Unicode pour correspondre exactement au type de colonne (par exemple, pour éviter les avertissements de conversion implicites), utilisez setinputsizes() pour écraser le type par défaut :

import mssql_python

conn = mssql_python.connect(connection_string)
cursor = conn.cursor()

# Create temp table for demo
cursor.execute("CREATE TABLE #AsciiTable (Code VARCHAR(100))")

cursor.setinputsizes([(mssql_python.SQL_VARCHAR, 100, 0)])
cursor.execute(
    "INSERT INTO #AsciiTable (Code) VALUES (?)",
    ("ABC123",)
)
conn.commit()

Pour la plupart des applications, le comportement par défaut est correct. Annulez uniquement lorsque vous voyez des avertissements de conversion implicites dans les plans de requête ou que vous devez correspondre à une collation spécifique varchar .

Encodage de connexion

Le pilote mssql-python gère automatiquement l’encodage de la connexion en fonction de la version et de la configuration de Microsoft SQL Server. Comme les chaînes Python sont Unicode, le pilote les encode de manière appropriée (UTF-8 ou UTF-16) pour le type de données cible. Vous n’avez pas besoin de configurer manuellement l’encodage de connexion.

Colonnes VARCHAR avec collations héritées

Les bases de données avec des collations Windows-1252 (CP1252), telles que Latin1_General_CI_AS, stockent des caractères latins étendus (par exemple, , et des caractères accentués) dans des colonnes varchar en utilisant l’encodage CP1252. Le pilote décode correctement ces caractères sur toutes les plateformes.

Cette différence est importante pour les déploiements multiplateformes : les mêmes varchar données qui se lisent correctement sur Windows se lisent aussi correctement sous Linux, sans configuration particulière requise.

# Create a temp table with a varchar column and insert extended Latin characters
cursor.execute("CREATE TABLE #Products (Name VARCHAR(100))")
cursor.execute("INSERT INTO #Products (Name) VALUES (%(name)s)", {"name": "Café €100 ™"})
conn.commit()

# CP1252 characters in varchar columns are decoded correctly on all platforms
cursor.execute("SELECT Name FROM #Products WHERE Name LIKE '%€%'")
for row in cursor:
    print(row.Name)  # Correct on both Windows and Linux

Si votre schéma le permet, migrer les colonnes varchar vers nvarchar évite entièrement toute ambiguïté d’encodage et permet de prendre en charge tous les caractères Unicode.

Encodage de fichier

Lors de la lecture de fichiers à insérer dans la base de données, spécifiez l’encodage approprié pour préserver le contenu Unicode.

# Reading files with explicit encoding
def insert_file_content(cursor, conn, file_path: str, encoding: str = "utf-8"):
    with open(file_path, "r", encoding=encoding) as f:
        content = f.read()
    
    cursor.execute(
        "INSERT INTO #FileContent (Content) VALUES (%(content)s)",
        {"content": content}
    )
    conn.commit()

Opérations de chaîne courantes

Ces exemples couvrent des modèles courants de manipulation de chaînes en Python et SQL.

Concaténation

Vous pouvez concaténer des chaînes soit en Python avant d'insérer, soit en utilisant les opérateurs de chaînes SQL sur le serveur.

# Concatenate in Python before insert
first_name = "Alice"
last_name = "Smith"
full_name = f"{first_name} {last_name}"

cursor.execute("""
    CREATE TABLE #ConcatDemo (
        ID INT IDENTITY(1,1) PRIMARY KEY,
        FullName NVARCHAR(200)
    )
""")
cursor.execute(
    "INSERT INTO #ConcatDemo (FullName) VALUES (%(name)s)",
    {"name": full_name}
)

# Or concatenate in SQL
cursor.execute("""
    SELECT FirstName + ' ' + LastName AS FullName FROM Person.Person
""")

Mise en forme de chaîne

Appliquez la mise en forme en Python pour afficher les chaînes avec la monnaie, le remplissage ou l’alignement avant de les montrer aux utilisateurs.

from decimal import Decimal

# Format for display
cursor.execute("SELECT Name, ListPrice FROM Production.Product WHERE ListPrice > 0")
for row in cursor.fetchall()[:5]:
    print(f"{row.Name}: ${row.ListPrice:.2f}")

# Pad strings
cursor.execute("SELECT ProductNumber FROM Production.Product")
for row in cursor.fetchall()[:5]:
    padded = row.ProductNumber.ljust(15)  # Left-justify, pad to 15 chars
    print(f"[{padded}]")

NULL versus chaîne vide

Microsoft SQL considère NULL et chaîne vide ('') comme des valeurs différentes. NULL signifie « inconnu » tandis que chaîne vide signifie « connue pour être vide ». Choisissez une convention pour votre candidature et soyez cohérent. La plupart des applications utilisent NULL pour les champs optionnels manquants.

L’exemple suivant montre comment distinguer entre NULL et chaîne vide :

# NULL is different from empty string
cursor.execute("""
    CREATE TABLE #NullDemo (
        ID INT IDENTITY(1,1) PRIMARY KEY,
        Name NVARCHAR(100),
        MiddleName NVARCHAR(100)
    )
""")
cursor.execute("""
    INSERT INTO #NullDemo (Name, MiddleName) 
    VALUES (%(name)s, %(middle)s)
""", {"name": "Alice", "middle": None})  # NULL

cursor.execute("""
    INSERT INTO #NullDemo (Name, MiddleName) 
    VALUES (%(name)s, %(middle)s)
""", {"name": "Bob", "middle": ""})  # Empty string

# Query differences
cursor.execute("SELECT * FROM #NullDemo WHERE MiddleName IS NULL")
cursor.execute("SELECT * FROM #NullDemo WHERE MiddleName = ''")

Opérations de découpage

Utilisez les méthodes de chaîne de Python pour supprimer les espaces blancs en tête, en fin ou les deux des valeurs récupérées dans la base de données.

cursor.execute("SELECT Name FROM Production.Product")
for row in cursor:
    # Remove whitespace
    trimmed = row.Name.strip()  # Both ends
    left_trimmed = row.Name.lstrip()
    right_trimmed = row.Name.rstrip()

Données de chaîne JSON

Stockez les documents JSON dans les colonnes nvarchar(max) et interrogez-les avec les fonctions JSON de Microsoft SQL.

Stockez le JSON sous nvarchar

Sérialiser des dictionnaires Python en chaînes JSON et les insérer dans des colonnes nvarchar ; les récupérer et les désérialiser à nouveau en objets Python.

import json

data = {"name": "Alice", "scores": [95, 87, 91], "active": True}
json_string = json.dumps(data)

cursor.execute("""
    CREATE TABLE #Configs (
        ID INT IDENTITY(1,1) PRIMARY KEY,
        ConfigData NVARCHAR(MAX)
    )
""")
cursor.execute("""
    INSERT INTO #Configs (ConfigData) VALUES (%(data)s)
""", {"data": json_string})

# Retrieve and parse
cursor.execute("SELECT ConfigData FROM #Configs WHERE ID = 1")
row = cursor.fetchone()
config = json.loads(row.ConfigData)
print(config["name"])  # 'Alice'

Utilisez les fonctions JSON SQL de Microsoft

Utilisez les fonctions JSON de Microsoft SQL pour analyser et filtrer les données JSON directement dans les requêtes plutôt que dans le code client.

import json

data = {"name": "Alice", "scores": [95, 87, 91], "active": True}

cursor.execute("""
    CREATE TABLE #Configs (
        ID INT IDENTITY(1,1) PRIMARY KEY,
        ConfigData NVARCHAR(MAX)
    )
""")
cursor.execute(
    "INSERT INTO #Configs (ConfigData) VALUES (%(data)s)",
    {"data": json.dumps(data)}
)
conn.commit()

cursor.execute("""
    SELECT JSON_VALUE(ConfigData, '$.name') AS Name
    FROM #Configs
    WHERE JSON_VALUE(ConfigData, '$.active') = 'true'
""")
for row in cursor:
    print(row.Name)  # 'Alice'

Utilisez LIKE pour la correspondance de motifs, ou activez un index texte intégral pour une recherche textuelle plus avancée.

Requêtes en texte intégral

L’opérateur LIKE avec des motifs de jokers propose une alternative simple à la recherche en texte intégral lorsque l’index en texte intégral n’est pas disponible.

# Using CONTAINS (requires full-text index on the table)
cursor.execute("""
    SELECT JobTitle FROM HumanResources.Employee
    WHERE JobTitle LIKE %(search)s
""", {"search": "%Engineer%"})

# Pattern-based search as an alternative to full-text
cursor.execute("""
    SELECT Name FROM Production.Product
    WHERE Name LIKE %(search)s
""", {"search": "%Mountain%"})

Bonnes pratiques

Appliquez ces directives pour gérer correctement les données de chaînes à travers les langues et les codages.

Utilisez nvarchar pour les données internationales

Si vous n’êtes pas sûr qu’une colonne puisse contenir Unicode, utilisez nvarchar. Le coût de stockage est modeste et il évite la perte de données due à la conversion de caractères.

L’exemple suivant montre la différence entre définir des colonnes pour les données Unicode et ASCII uniquement :

-- Good: supports any language
CREATE TABLE #UserProfile (
    Name NVARCHAR(100),
    Bio NVARCHAR(MAX)
);

-- Limited: ASCII/Latin only
CREATE TABLE #UserProfileAscii (
    Name VARCHAR(100),
    Bio VARCHAR(MAX)
);

Valider la longueur de la chaîne

Vérifiez la longueur de la chaîne en Python avant d’insérer pour éviter les erreurs de troncature et fournir des messages d’erreur significatifs aux utilisateurs.

def safe_insert(cursor, name: str, max_length: int = 100):
    """Insert with length validation."""
    if len(name) > max_length:
        raise ValueError(f"Name exceeds {max_length} characters")
    
    cursor.execute(
        "INSERT INTO #UserProfile (Name) VALUES (%(name)s)",
        {"name": name}
    )

Gérer les chaînes binaires séparément

Distinguez les chaînes de texte (Pythonstr, SQLnvarchar) et les données binaires (Pythonbytes, SQLvarbinary) pour éviter les problèmes d’encodage.

binary_data = b'\x00\x01\x02'  # bytes - use varbinary
text_data = "Hello"            # str - use nvarchar