Ескертпе
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Жүйеге кіруді немесе каталогтарды өзгертуді байқап көруге болады.
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Каталогтарды өзгертуді байқап көруге болады.
SQL NULL представляет отсутствующие или неизвестные данные. Драйвер mssql-python сопоставляет SQL NULL со значением Python None. Это различие имеет значение, потому что NULL не равен ничему, включая самого себя. В SQL выражение NULL = NULL вычисляется как NULL (неизвестно), а не TRUE, поэтому в запросах используйте IS NULL, а в Python — is None.
Получать NULL-значения
NULL в результатах получения
Драйвер возвращает значения NULL из SQL Server в формате PythonNone:
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
Проверьте значения NULL
Проверьте, является ли значение None с помощью оператора is при переборе результатов:
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}")
Используйте is None вместо == None
Всегда используйте is None для проверок NULL. Оператор is проверяет идентичность (является ли значение буквально None), а == вызывает __eq__ и может давать неожиданные результаты при работе с пользовательскими объектами:
# 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}"
Отправить значения NULL
Вставьте NULL с отсутствием
Чтобы вставить значения NULL, передайте 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()
Обновление до NULL
Установить для столбца значение NULL, передав None в параметрах:
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()
Условная обработка NULL
Определите функции, которые обрабатывают необязательные параметры, устанавливая для них значение 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 в предложениях WHERE
IS NULL в запросах
Используйте IS NULL в SQL для сравнения с 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")
Динамическая обработка NULL
Когда параметр может быть NULL, используйте условную логику для построения соответствующего запроса:
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 для подстановки NULL
Используйте COALESCE, чтобы подставлять значения по умолчанию вместо NULL на уровне SQL.
COALESCEэффективнее, чем проверка None в Python, потому что подстановка происходит на сервере, уменьшая количество условной логики в вашем приложении:
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}")
NULL-безопасные операции
Значения по умолчанию в 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}")
Формат значений 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 в агрегациях
Агрегированные функции SQL обрабатывают значения NULL иначе, чем можно было бы ожидать.
COUNT(column) считает только значения, не соответствующие NULL, а COUNT(*) все строки.
AVG, SUM, MIN, и MAX все игнорируют значения NULL. Если каждое значение в столбце равно NULL, эти функции возвращают NULL (а не ноль).
# 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 с типами данных
Числовые значения 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")
Значения даты NULL
Проверьте, есть ли столбец None с датой, прежде чем использовать его в сравнениях или вычислениях:
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}")
Значения строк NULL
Обрабатывайте строковые столбцы со значением NULL, проверяя 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)
Крупные операции с NULL
executemany при использовании значений NULL
При использовании executemany() передавайте None в словарях для столбцов, значения которых должны быть 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
)
Пакетное копирование с NULL
Операции массового копирования сохраняют значения NULL из ваших структур данных:
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")
Распространенные шаблоны
Опциональная обработка в полевых условиях
Используйте аннотации типов, чтобы указать, какие поля могут иметь значение NULL при сопоставлении строк с классами данных:
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 с NULL
Значения Python None автоматически конвертируются в JSON null при использовании модуляjson:
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}
# ]
Словарь с фильтрацией NULL
Выберите исключение значений NULL при преобразовании строк в словари:
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 в таблицах данных
Когда вы работаете с pandas или Polars DataFrames, нужно уделять особое внимание нулевым значениям, потому что эти библиотеки используют собственные значения sentinel.
панды NaN и NaT
pandas использует NaN (не число) для пропущенных числовых и строковых значений и NaT (не время) для пропущенных значений даты и времени. Ни одно из этих значений не совпадает с 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}
)
Предупреждение
Не сравнивайте с == np.nan или == pd.NaT. Эти сравнения всегда возвращают False. Вместо этого используются типы pd.isna() или pd.notna().
Обработка null-значений в Polars
Polars использует значение null (а не NaN), которое напрямую соответствует None в Python:
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"))