Zpracovat hodnoty NULL

SQL NULL reprezentuje chybějící nebo neznámá data. Ovladač mssql-python mapuje SQL NULL na PythonNone. Rozlišení je důležité, protože NULL neznamená nic, včetně sebe sama. V SQL se NULL = NULL vyhodnocuje na NULL (neznámé), což není pravda, takže to používám IS NULL v dotazech a is None v Pythonu.

Přijímání NULL hodnot

NULL ve výsledcích načítání

Ovladač vrací hodnoty NULL ze SQL Server jako 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

Zkontrolujte hodnoty NULL

Zkontrolujte, zda je hodnota None pomocí operátoru is při procházení výsledků:

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

Místo is None použijte == None

Vždy používejte is None pro NULL kontroly. Operátor is ověřuje identitu (zda je hodnota doslova None), zatímco == volá __eq__ a může u vlastních objektů vést k neočekávaným výsledkům:

# 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}"

Odeslat hodnoty NULL

Vložit hodnotu NULL jako None

Chcete-li vložit hodnoty NULL, předejte 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()

Aktualizace na NULL

Nastavte sloupec na NULL předáním None parametrů:

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

Podmíněné zpracování hodnoty NULL

Definujte funkce, které zpracovávají volitelné parametry tak, že je při nezadání nastaví na None:

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 v podmínkách WHERE

JE NULOVÁ v dotazech

Použití IS NULL v SQL pro srovnání 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")

Dynamické zpracování hodnoty NULL

Když může být parametr NULL, použijte podmíněnou logiku k vytvoření příslušného dotazu:

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 pro nahrazení hodnoty NULL

Použijte COALESCE k nahrazení výchozích hodnot NULL na úrovni SQL. COALESCEje efektivnější než kontrola v None Python, protože substituce probíhá na serveru, což snižuje množství podmíněné logiky ve vaší aplikaci:

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

Operace bezpečné vůči hodnotě NULL

Výchozí hodnoty v Pythonu

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

Formátovat hodnoty 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 v agregacích

SQL agregační funkce zpracovávají hodnoty NULL jinak, než byste čekali. COUNT(column) počítá pouze hodnoty ne-NULL, zatímco COUNT(*) počítá všechny řádky. AVG, SUM, MIN, a MAX všechny ignorují NULOVÉ hodnoty. Pokud je každá hodnota ve sloupci NULL, tyto funkce vrátí NULL (nikoli nulu).

# 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 u datových typů

Číselné hodnoty s hodnotou 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")

Hodnoty data NULL

Zkontrolujte, zda sloupec s datem je None, než ho použijete při porovnávání nebo výpočtech:

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

Řetězcové hodnoty NULL

Vyřiďte sloupce řetězců NULL tak, že před spojováním zkontrolujete :None

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)

Hromadné operace s NULL

executemany s hodnotami NULL

Při použití executemany() předejte ve slovnících pro sloupce, které mají být NULL, None:

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
)

Hromadné kopírování s NULL

Hromadné kopírování uchovává hodnoty NULL z vašich datových struktur:

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

Obvyklé scénáře

Volitelné terénní zpracování

Použijte tipové nápovědy k objasnění, která pole mohou být NULL při mapování řádků na datové třídy:

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
    )

JSON serializace pomocí NULL

Python None hodnoty se automaticky převádějí do JSON null při použití modulujson:

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}
# ]

Slovník s NULL filtrováním

Zvolte vyloučení NULL hodnot při převodu řádků na slovníky:

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 v DataFrames

Když pracujete s pandas nebo Polars DataFrames, musíte věnovat zvláštní pozornost nulovým hodnotám, protože tyto knihovny používají své vlastní sentinelové hodnoty.

pandas NaN a NaT

Pandas používá NaN (Not a Number) pro chybějící číselné a řetězcové hodnoty a NaT (Not a Time) pro chybějící hodnoty date-time. Ani jedna z těchto hodnot není stejná jako v PythonuNone:

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

Nesrovnávejte s == np.nan nebo == pd.NaT. Tato srovnání vždy vrátí False. Použijte pd.isna() nebo pd.notna() místo toho.

Zpracování hodnot null v Polars

Polars používá vlastní hodnotu null (nikoli NaN), která přímo odpovídá hodnotě None v Pythonu:

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