Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
SQL NULL représente des données manquantes ou inconnues. Le pilote mssql-python mappe SQL NULL en PythonNone. La distinction compte car NULL ne signifie rien, y compris lui-même. En SQL, NULL = NULL évalue en NULL (inconnu), ce n’est pas vrai, donc utilisez-le IS NULL dans les requêtes et is None en Python.
Recevoir des valeurs NULL
NULL dans les résultats de récupération
Le pilote renvoie les valeurs NULL de SQL Server sous forme de Python None:
import mssql_python
conn = mssql_python.connect(connection_string)
cursor = conn.cursor()
cursor.execute(
"SELECT TOP 1 FirstName, MiddleName, LastName "
"FROM Person.Person WHERE MiddleName IS NULL"
)
row = cursor.fetchone()
print(row.FirstName) # First name value
print(row.MiddleName) # None (NULL in database)
print(row.LastName) # Last name value
Vérifier les valeurs NULL
Vérifiez qu’une valeur est None à l’aide de l’opérateur is lors de l’itération des résultats :
cursor.execute(
"SELECT FirstName, MiddleName, LastName FROM Person.Person WHERE BusinessEntityID <= 10"
)
for row in cursor:
if row.MiddleName is None:
print(f"{row.FirstName} {row.LastName}: No middle name")
else:
print(f"{row.FirstName} {row.MiddleName} {row.LastName}")
Utiliser is None au lieu de == None
Utilisez is None toujours pour les tests NULL. L’opérateur is vérifie l’identité (si la valeur est littéralement None), tandis qu’il == appelle __eq__ et peut donner des résultats inattendus avec des objets personnalisés :
# Correct
if row.MiddleName is None:
full_name = f"{row.FirstName} {row.LastName}"
# Avoid (works but not idiomatic)
if row.MiddleName == None:
full_name = f"{row.FirstName} {row.LastName}"
Envoyer des valeurs NULL
Insérer NULL avec Aucun
Pour insérer des valeurs NULL, passez None:
cursor.execute(
"CREATE TABLE #NullInsertDemo "
"(Name NVARCHAR(50), Email NVARCHAR(100), Phone NVARCHAR(20))"
)
cursor.execute(
"INSERT INTO #NullInsertDemo (Name, Email, Phone) "
"VALUES (%(name)s, %(email)s, %(phone)s)",
{"name": "Alice", "email": None, "phone": "555-1234"}
)
conn.commit()
Mise à jour vers NULL
Définissez une colonne sur NULL en passant None dans les paramètres :
cursor.execute(
"CREATE TABLE #UpdateDemo (ID INT, Email NVARCHAR(100))"
)
cursor.execute("INSERT INTO #UpdateDemo VALUES (100, 'old@example.com')")
cursor.execute(
"UPDATE #UpdateDemo SET Email = %(email)s WHERE ID = %(id)s",
{"email": None, "id": 100}
)
conn.commit()
Gestion conditionnelle du NULL
Définissez des fonctions qui gèrent des paramètres optionnels en les définissant à None lorsqu’ils ne sont pas fournis :
def update_record(cursor, record_id: int, name: str, email: str | None = None):
"""Update record, setting email to NULL if not provided."""
cursor.execute(
"UPDATE #Records SET Name = %(name)s, Email = %(email)s "
"WHERE ID = %(id)s",
{"name": name, "email": email, "id": record_id}
)
NULL dans la clause WHERE
IS NULL dans les requêtes
Utilisation IS NULL en SQL pour des comparaisons NULL :
# Find people without a middle name
cursor.execute("SELECT FirstName FROM Person.Person WHERE MiddleName IS NULL")
# Find people with a middle name
cursor.execute("SELECT FirstName FROM Person.Person WHERE MiddleName IS NOT NULL")
Gestion dynamique du NULL
Lorsqu’un paramètre peut être NULL, on utilise la logique conditionnelle pour construire la requête appropriée :
def find_people(cursor, middle_name: str | None = None):
"""Find people, optionally filtering by middle name."""
if middle_name is None:
# Find people with NULL middle name
cursor.execute("SELECT * FROM Person.Person WHERE MiddleName IS NULL")
else:
# Find people with specific middle name
cursor.execute(
"SELECT * FROM Person.Person WHERE MiddleName = %(middle_name)s",
{"middle_name": middle_name},
)
return cursor.fetchall()
COALESCE pour le remplacement de NULL
Utilisez COALESCE pour remplacer NULL par valeurs par défaut au niveau SQL.
COALESCEest plus efficace que de vérifier None en Python car la substitution se fait sur le serveur, réduisant la quantité de logique conditionnelle dans votre application :
cursor.execute("""
SELECT
FirstName,
COALESCE(MiddleName, '(none)') AS MiddleName,
COALESCE(Suffix, 'N/A') AS Suffix
FROM Person.Person
WHERE BusinessEntityID <= 10
""")
for row in cursor:
# MiddleName and Suffix will never be None
print(f"{row.FirstName}: {row.MiddleName}, {row.Suffix}")
Opérations NULL-safe
Valeurs par défaut en Python
cursor.execute("SELECT TOP 10 Name, Color FROM Production.Product")
for row in cursor:
# Use or to provide default
color = row.Color or "No color"
print(f"{row.Name}: {color}")
Mettre en forme les valeurs NULL
def format_address(row):
"""Format address handling NULL components."""
parts = [
row.AddressLine1,
row.AddressLine2,
row.City,
row.PostalCode,
]
# Filter out None values
return ", ".join(str(p) for p in parts if p is not None)
cursor.execute(
"SELECT TOP 10 AddressLine1, AddressLine2, City, PostalCode "
"FROM Person.Address"
)
for row in cursor:
print(format_address(row))
NULL dans les agrégations
Les fonctions agrégées SQL gèrent les valeurs NULL différemment de ce à quoi on pourrait s’attendre.
COUNT(column) ne compte que les valeurs non NULL, tandis qu’il COUNT(*) compte toutes les lignes.
AVG, SUM, MIN, et MAX tous ignorent les valeurs NULL. Si chaque valeur de la colonne est NULL, ces fonctions rendent NULL (et non zéro).
# COUNT excludes NULL values
cursor.execute("SELECT COUNT(Color) FROM Production.Product") # Counts non-NULL colors
color_count = cursor.fetchval()
# COUNT(*) includes all rows
cursor.execute("SELECT COUNT(*) FROM Production.Product") # Counts all products
total_count = cursor.fetchval()
# AVG ignores NULL
cursor.execute("SELECT AVG(Weight) FROM Production.Product") # Average of non-NULL weights
average_weight = cursor.fetchval()
NULL avec types de données
Valeurs numériques NULL
from decimal import Decimal
cursor.execute("SELECT ListPrice FROM Production.Product WHERE ProductID = 1")
row = cursor.fetchone()
# Check before arithmetic
if row.ListPrice is not None:
tax = row.ListPrice * Decimal("0.08")
total = row.ListPrice + tax
else:
total = Decimal("0")
Valeurs de date NULL
Vérifiez si une colonne de date est None présente avant de l’utiliser dans des comparaisons ou des calculs :
from datetime import date
cursor.execute("SELECT Name, SellEndDate FROM Production.Product WHERE ProductID <= 10")
for row in cursor:
if row.SellEndDate is None:
print(f"{row.Name}: Currently selling")
else:
print(f"{row.Name}: Discontinued on {row.SellEndDate}")
Valeurs de chaîne nulles
Traitez les colonnes de chaînes NULL en vérifiant None avant de les concaténer :
cursor.execute(
"SELECT TOP 10 FirstName, MiddleName, LastName FROM Person.Person"
)
for row in cursor:
# Build full name, handling NULL middle name
if row.MiddleName:
full_name = f"{row.FirstName} {row.MiddleName} {row.LastName}"
else:
full_name = f"{row.FirstName} {row.LastName}"
print(full_name)
Opérations en bloc avec NULL
executemany avec des valeurs NULL
Lors de l’utilisation de executemany(), passez None dans les dictionnaires pour les colonnes devant être NULL :
users = [
{"name": "Alice", "title": "Ms.", "suffix": "Jr."},
{"name": "Bob", "title": None, "suffix": "Sr."}, # NULL title
{"name": "Carol", "title": "Dr.", "suffix": None}, # NULL suffix
]
cursor.executemany(
"SELECT FirstName FROM Person.Person WHERE FirstName = %(name)s",
users
)
Copie en masse avec NULL
Les opérations de copie en masse préservent les valeurs NULL de vos structures de données :
cursor = conn.cursor()
cursor.execute("CREATE TABLE ##NullDemo (Name NVARCHAR(50), Email NVARCHAR(100), Phone NVARCHAR(20))")
conn.commit()
data = [
("Alice", "alice@example.com", "555-0001"),
("Bob", None, "555-0002"), # NULL Email
("Carol", "carol@example.com", None), # NULL Phone
]
result = cursor.bulkcopy("##NullDemo", data)
conn.commit()
print(f"Copied {result['rows_copied']} rows")
Modèles courants
Traitement optionnel sur le champ
Utilisez des annotations de type pour préciser quels champs peuvent être NULL lors de la conversion de lignes en dataclasses :
from dataclasses import dataclass
from typing import Optional
@dataclass
class PersonRecord:
business_entity_id: int
first_name: str
middle_name: Optional[str] = None
suffix: Optional[str] = None
def fetch_person(cursor, person_id: int) -> Optional[PersonRecord]:
cursor.execute(
"SELECT BusinessEntityID, FirstName, MiddleName, Suffix "
"FROM Person.Person WHERE BusinessEntityID = %(id)s",
{"id": person_id},
)
row = cursor.fetchone()
if row is None:
return None
return PersonRecord(
business_entity_id=row.BusinessEntityID,
first_name=row.FirstName,
middle_name=row.MiddleName, # Will be None if NULL
suffix=row.Suffix, # Will be None if NULL
)
Sérialisation JSON avec NULL
Les valeurs Python None se convertissent automatiquement en JSON null lors de l’utilisation du json module :
import json
cursor.execute(
"SELECT TOP 5 BusinessEntityID, FirstName, MiddleName FROM Person.Person"
)
rows = cursor.fetchall()
# Convert to JSON-serializable list
people = []
for row in rows:
people.append({
"id": row.BusinessEntityID,
"name": row.FirstName,
"middle_name": row.MiddleName, # None becomes null in JSON
})
json_output = json.dumps(people, indent=2)
print(json_output)
# [
# {"id": 1, "name": "Ken", "middle_name": "J"},
# {"id": 3, "name": "Roberto", "middle_name": null}
# ]
Dictionnaire avec filtrage NULL
Choisissez d’exclure les valeurs NULL lors de la conversion des lignes en dictionnaires :
def row_to_dict(row, cursor) -> dict:
"""Convert row to dict, optionally excluding NULL values."""
columns = [col[0] for col in cursor.description]
return {col: val for col, val in zip(columns, row) if val is not None}
cursor.execute("SELECT * FROM Person.Person WHERE BusinessEntityID = 1")
row = cursor.fetchone()
person_dict = row_to_dict(row, cursor)
# Only includes non-NULL columns
NULL dans DataFrames
Lorsque vous travaillez avec des pandas ou des DataFrames Polars, il faut prêter une attention particulière aux valeurs nulles car ces bibliothèques utilisent leurs propres valeurs sentinelles.
Pandas NaN et NaT
pandas utilise NaN (Not a Number) pour les valeurs numériques et de chaînes manquantes, et NaT (Not a Time) pour les valeurs de date-heure manquantes. Aucune des deux valeurs n’est la même que Python None:
import pandas as pd
import numpy as np
# When reading SQL results into pandas, NULL becomes NaN or NaT
cursor.execute("SELECT Name, Weight, SellEndDate FROM Production.Product")
table = cursor.arrow()
df = table.to_pandas()
# Check for missing values (covers NaN, NaT, and None)
print(df["Weight"].isna().sum()) # Count of NULL weights
print(df["SellEndDate"].isna().sum()) # Count of NULL dates
# Stage the data in a temp table to avoid mutating the source table
cursor.execute("CREATE TABLE #ProductWeights (Name NVARCHAR(100), Weight DECIMAL(8, 2) NULL)")
# Convert NaN back to None so NULL values round-trip correctly
for _, row in df.iterrows():
weight = None if pd.isna(row["Weight"]) else float(row["Weight"])
cursor.execute(
"INSERT INTO #ProductWeights (Name, Weight) VALUES (%(name)s, %(weight)s)",
{"name": row["Name"], "weight": weight}
)
Warning
Ne comparez pas avec == np.nan ou == pd.NaT. Ces comparaisons renvoient toujours False. Utilisez plutôt pd.isna() ou pd.notna().
Gestion des valeurs nulles dans Polars
Polars utilise sa propre null valeur (non NaN) qui correspond directement à Python None:
import polars as pl
cursor.execute("SELECT Name, Weight, Color FROM Production.Product")
table = cursor.arrow()
df = pl.from_arrow(table)
# Filter rows with non-null values
has_weight = df.filter(pl.col("Weight").is_not_null())
# Replace null with a default
df = df.with_columns(pl.col("Color").fill_null("No color"))