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.
Řídké sloupce jsou optimalizace úložiště v Microsoft SQL pro hodnoty NULL v tabulkách s mnoha sloupci s nulovatelnými hodnotami. Klientské aplikace vidí řídké sloupce jako pravidelné sloupce. Ovladač mssql-python je čte a zapisuje jako jakýkoli jiný sloupec bez speciálního zpracování.
| funkce | Popis |
|---|---|
| Řídké sloupce | NULL hodnoty používají nulovou paměť |
| Sady sloupců | XML reprezentace všech řídkých sloupců |
| Široké tabulky | Podpora až 30 000 sloupců |
Note
Řídké sloupce jsou funkcí na straně serveru. Ovladač mssql-python nevyžaduje žádnou speciální konfiguraci ani API pro práci se řídkými sloupci. Jediný rozdíl viditelný klientem je, že když použijete sady sloupců, vracejí XML reprezentaci řídkých sloupcových hodnot.
Nejlépe se hodí pro:
- Tabulky s 20–50 % a více hodnotami NULL.
- Ukládání dokumentů s proměnnými atributy.
- Vzory návrhu EAV (Entity-Attribute-Value).
- Data ze senzorů s mnoha volitelnými hodnotami.
Vytvořte řídké sloupce
Definujte řídké sloupce ve schématu tabulky přidáním modifikátoru SPARSE NULL do sloupců, které často obsahují hodnoty NULL.
Základní tabulka řídkých sloupců
Vytvořte tabulku s řídkými sloupci pro volitelné atributy.
CREATE TABLE ProductAttributes (
ProductID INT PRIMARY KEY,
ProductName NVARCHAR(100) NOT NULL,
-- Sparse columns for optional attributes
Color NVARCHAR(50) SPARSE NULL,
Size NVARCHAR(20) SPARSE NULL,
Weight DECIMAL(10,2) SPARSE NULL,
Material NVARCHAR(100) SPARSE NULL,
Warranty INT SPARSE NULL,
Manufacturer NVARCHAR(100) SPARSE NULL
);
Se sadou sloupců
Přidejte sadu sloupců, která umožní XML přístup ke všem řídkým sloupcům současně.
CREATE TABLE ProductAttributesWithSet (
ProductID INT PRIMARY KEY,
ProductName NVARCHAR(100) NOT NULL,
-- Column set provides XML access to all sparse columns
SparseAttributes XML COLUMN_SET FOR ALL_SPARSE_COLUMNS,
-- Sparse columns
Color NVARCHAR(50) SPARSE NULL,
Size NVARCHAR(20) SPARSE NULL,
Weight DECIMAL(10,2) SPARSE NULL,
Material NVARCHAR(100) SPARSE NULL,
Warranty INT SPARSE NULL,
Manufacturer NVARCHAR(100) SPARSE NULL
);
Vložte data do řídkého sloupce
Vkládejte do řídkých sloupců podle názvu, stejně jako byste to dělali u běžných sloupců.
Vložte jednotlivé sloupce
Vložte produkty s vyplněnými hodnotami v konkrétních řídkých sloupcích.
import mssql_python
conn = mssql_python.connect(connection_string)
cursor = conn.cursor()
# Create the table with sparse columns
cursor.execute("DROP TABLE IF EXISTS ProductAttributes")
cursor.execute("""
CREATE TABLE ProductAttributes (
ProductID INT PRIMARY KEY,
ProductName NVARCHAR(100) NOT NULL,
Color NVARCHAR(50) SPARSE NULL,
Size NVARCHAR(20) SPARSE NULL,
Weight DECIMAL(10,2) SPARSE NULL,
Material NVARCHAR(100) SPARSE NULL,
Warranty INT SPARSE NULL,
Manufacturer NVARCHAR(100) SPARSE NULL
)
""")
# Insert with some sparse columns populated
cursor.execute("""
INSERT INTO ProductAttributes (ProductID, ProductName, Color, Size)
VALUES (%(id)s, %(name)s, %(color)s, %(size)s)
""", {"id": 1, "name": "T-Shirt", "color": "Blue", "size": "Large"})
# Insert with different sparse columns
cursor.execute("""
INSERT INTO ProductAttributes (ProductID, ProductName, Weight, Material)
VALUES (%(id)s, %(name)s, %(weight)s, %(material)s)
""", {"id": 2, "name": "Coffee Mug", "weight": 0.35, "material": "Ceramic"})
conn.commit()
Vložit prostřednictvím sady sloupců (XML)
Vložte najednou hodnoty více řídkých sloupců předáním XML sadě sloupců.
# Create the table with a column set
cursor.execute("DROP TABLE IF EXISTS ProductAttributesWithSet")
cursor.execute("""
CREATE TABLE ProductAttributesWithSet (
ProductID INT PRIMARY KEY,
ProductName NVARCHAR(100) NOT NULL,
SparseAttributes XML COLUMN_SET FOR ALL_SPARSE_COLUMNS,
Color NVARCHAR(50) SPARSE NULL,
Size NVARCHAR(20) SPARSE NULL,
Weight DECIMAL(10,2) SPARSE NULL,
Material NVARCHAR(100) SPARSE NULL,
Warranty INT SPARSE NULL,
Manufacturer NVARCHAR(100) SPARSE NULL
)
""")
# Insert using column set XML
cursor.execute("""
INSERT INTO ProductAttributesWithSet (ProductID, ProductName, SparseAttributes)
VALUES (%(id)s, %(name)s, %(xml)s)
""", {
"id": 3,
"name": "Laptop Bag",
"xml": "<Color>Black</Color><Size>Medium</Size><Material>Nylon</Material><Warranty>24</Warranty>"
})
conn.commit()
Dynamické vkládání atributů
Vytvořte funkci, která přijímá dynamické atributy jako slovník a automaticky sestavuje XML.
def insert_with_attributes(cursor, product_id: int, name: str, attributes: dict):
"""Insert product with dynamic sparse column attributes."""
# Build XML for column set
xml_parts = [f"<{key}>{value}</{key}>" for key, value in attributes.items()]
attributes_xml = "".join(xml_parts)
cursor.execute("""
INSERT INTO ProductAttributesWithSet (ProductID, ProductName, SparseAttributes)
VALUES (%(id)s, %(name)s, %(xml)s)
""", {"id": product_id, "name": name, "xml": attributes_xml or None})
# Usage
insert_with_attributes(cursor, 4, "Headphones", {
"Color": "Silver",
"Warranty": 12,
"Manufacturer": "AudioTech"
})
conn.commit()
Dotazování na řídké sloupce
Dotazujte řídké sloupce podle jména, nebo získávejte všechny řídké hodnoty najednou přes množinu sloupců.
Dotazujte jednotlivé sloupce
Dotazujte konkrétní řídké sloupce pomocí standardní syntaxe SELECT.
# Query specific sparse columns
cursor.execute("""
SELECT ProductID, ProductName, Color, Size
FROM ProductAttributes
WHERE Color IS NOT NULL
""")
for row in cursor:
print(f"{row.ProductName}: {row.Color}, {row.Size}")
Dotaz přes množinu sloupců
Získejte všechny řídké sloupcové hodnoty jako XML ze sloupcové množiny.
# Get column set XML
cursor.execute("""
SELECT ProductID, ProductName, SparseAttributes
FROM ProductAttributesWithSet
WHERE ProductID = %(id)s
""", {"id": 3})
row = cursor.fetchone()
print(f"Product: {row.ProductName}")
print(f"Attributes XML: {row.SparseAttributes}")
Parsování XML sady sloupců v Pythonu
Rozpracujte XML ze sady sloupců a převeďte ho do Python slovníku pro snadnější manipulaci.
from xml.etree import ElementTree as ET
def get_product_attributes(cursor, product_id: int) -> dict:
"""Get product with parsed sparse attributes."""
cursor.execute("""
SELECT ProductName, SparseAttributes
FROM ProductAttributesWithSet
WHERE ProductID = %(id)s
""", {"id": product_id})
row = cursor.fetchone()
if row is None:
return None
result = {"ProductName": row.ProductName}
# Parse XML column set
if row.SparseAttributes:
# Wrap in root element for parsing
xml_str = f"<root>{row.SparseAttributes}</root>"
root = ET.fromstring(xml_str)
for elem in root:
result[elem.tag] = elem.text
return result
# Usage
product = get_product_attributes(cursor, 3)
print(product)
# {'ProductName': 'Laptop Bag', 'Color': 'Black', 'Size': 'Medium', 'Material': 'Nylon', 'Warranty': '24'}
Dotaz pomocí SELECT *
Když použijete SELECT *, sada sloupců se vrátí jako jeden XML sloupec místo jednotlivých řídkých sloupců.
# SELECT * returns column set instead of individual sparse columns
cursor.execute("""
SELECT * FROM ProductAttributesWithSet WHERE ProductID = %(id)s
""", {"id": 3})
row = cursor.fetchone()
# Returns: ProductID, ProductName, SparseAttributes (not individual columns)
print(f"Columns: {[col[0] for col in cursor.description]}")
Dotazujte se na jednotlivé sloupce explicitně
Pro získání jednotlivých řídkých sloupců z tabulky s množinou sloupců je explicitně uveďte v klauzuli SELECT.
# To get individual sparse columns with column set table, list them explicitly
cursor.execute("""
SELECT ProductID, ProductName, Color, Size, Weight, Material, Warranty, Manufacturer
FROM ProductAttributesWithSet
WHERE ProductID = %(id)s
""", {"id": 3})
# Now each sparse column is available as separate property
row = cursor.fetchone()
print(f"Color: {row.Color}, Material: {row.Material}")
Aktualizovat řídké sloupce
Aktualizujte jednotlivé řídké sloupce podle názvu nebo nahraďte všechny řídké hodnoty najednou pomocí XML pro sadu sloupců.
Aktualizovat jednotlivé sloupce
Aktualizujte specifické hodnoty řídkých sloupců pomocí standardní UPDATE SQL syntaxe.
cursor.execute("""
UPDATE ProductAttributes
SET Color = %(color)s, Weight = %(weight)s
WHERE ProductID = %(id)s
""", {"id": 1, "color": "Red", "weight": 0.2})
conn.commit()
Aktualizace pomocí sady sloupců
Všechny řídké hodnoty sloupců nahraďte přímou aktualizací XML pro sadu sloupců.
# Replace all sparse column values via column set
cursor.execute("""
UPDATE ProductAttributesWithSet
SET SparseAttributes = %(xml)s
WHERE ProductID = %(id)s
""", {
"id": 3,
"xml": "<Color>Navy</Color><Size>Large</Size><Material>Leather</Material>"
})
conn.commit()
# Note: This clears any sparse columns not included in the XML
Částečná aktualizace pomocí množiny sloupců
Aktualizujte pouze specifické řídké atributy a zachovávejte hodnoty ostatních atributů, které nejsou v aktualizaci zahrnuty.
# To update only specific attributes, merge with existing
def update_attributes(cursor, product_id: int, updates: dict):
"""Update specific sparse attributes while preserving others."""
# Get current attributes
cursor.execute("""
SELECT Color, Size, Weight, Material, Warranty, Manufacturer
FROM ProductAttributesWithSet
WHERE ProductID = %(id)s
""", {"id": product_id})
row = cursor.fetchone()
if row is None:
raise ValueError(f"Product {product_id} not found")
# Merge updates
current = {
"Color": row.Color,
"Size": row.Size,
"Weight": row.Weight,
"Material": row.Material,
"Warranty": row.Warranty,
"Manufacturer": row.Manufacturer
}
for key, value in updates.items():
current[key] = value
# Build XML with non-null values
xml_parts = []
for key, value in current.items():
if value is not None:
xml_parts.append(f"<{key}>{value}</{key}>")
cursor.execute("""
UPDATE ProductAttributesWithSet
SET SparseAttributes = %(xml)s
WHERE ProductID = %(id)s
""", {"id": product_id, "xml": "".join(xml_parts) or None})
# Usage
update_attributes(cursor, 3, {"Color": "Brown", "Warranty": 36})
conn.commit()
Dynamické sloupcové vzory
Budujte flexibilní dotazy, které dynamicky prohledávají řídké sloupce, využívají allowlisty k ověřování názvů sloupců a prevenci útoků SQL injection.
Dotazování na data ve stylu EAV
Vyhledávání produktů podle konkrétního názvu atributu a hodnoty pomocí vzorového párování Entity-Attribute-Value (EAV).
def find_products_by_attribute(cursor, attribute_name: str, attribute_value: str) -> list:
"""Find products with specific attribute value."""
# Validate column name against allowed sparse columns to prevent SQL injection
allowed_columns = {"Color", "Size", "Weight", "Material", "Warranty", "Manufacturer"}
if attribute_name not in allowed_columns:
raise ValueError(f"Invalid attribute: {attribute_name}")
cursor.execute(f"""
SELECT ProductID, ProductName, {attribute_name}
FROM ProductAttributesWithSet
WHERE {attribute_name} = %(value)s
""", {"value": attribute_value})
return cursor.fetchall()
# Find all blue products
blue_products = find_products_by_attribute(cursor, "Color", "Blue")
Flexibilní vyhledávání atributů
Hledejte produkty, které odpovídají více volitelným atributům najednou.
def search_by_attributes(cursor, **attributes) -> list:
"""Search products by multiple optional attributes."""
# Validate column names against allowed sparse columns to prevent SQL injection
allowed_columns = {"Color", "Size", "Weight", "Material", "Warranty", "Manufacturer"}
invalid = set(attributes.keys()) - allowed_columns
if invalid:
raise ValueError(f"Invalid attributes: {invalid}")
conditions = ["1=1"] # Always true base condition
params = {}
for i, (key, value) in enumerate(attributes.items()):
if value is not None:
conditions.append(f"{key} = %(attr_{i})s")
params[f"attr_{i}"] = value
query = f"""
SELECT ProductID, ProductName, SparseAttributes
FROM ProductAttributesWithSet
WHERE {' AND '.join(conditions)}
"""
cursor.execute(query, params)
return cursor.fetchall()
# Search by multiple attributes
results = search_by_attributes(cursor, Color="Black", Material="Nylon")
Důležité informace o výkonu
Zhodnoťte, kdy řídké sloupy přinášejí výhody skladování, zhodnoťte režijní náklady a použijte monitorování výkonu k optimalizaci návrhu řídkých sloupů.
Kdy použít řídké sloupce
Mezi dobré kandidáty na řídké sloupky patří:
- Sloupce s více než 60–70 % hodnot NULL.
- Široké stoly s mnoha volitelnými sloupci.
- Pracovní zátěže, kde je prioritou optimalizace úložiště.
Vyhněte se používání řídkých sloupců, když:
- Většina řádků obsahuje hodnoty (každá hodnota jiná než NULL přidává 4 bajty režie).
- Sloupec se často používá v klauzulích WHERE.
- Sloupec je součástí shlukovaného indexu.
Zkontrolujte úsporu místa v úložišti
Porovnejte velikost úložiště řídkých a neřídkých verzí téže tabulky.
-- Compare storage with and without sparse
EXEC sp_spaceused 'ProductAttributes';
EXEC sp_spaceused 'ProductAttributesWithoutSparse';
Úvahy o indexu
Můžete indexovat řídké sloupce. Filtrované indexy dobře fungují pro řídká data, protože přeskakují NULL řádky:
CREATE INDEX IX_Products_Color
ON ProductAttributes(Color)
WHERE Color IS NOT NULL;
Hromadné operace
Optimalizujte vklady více produktů s řídkými sloupci vytvořením XML sady sloupců v Pythonu před předáním řádků do bulkcopy().
Hromadná vložka s řídkými sloupci
Použijte hromadné kopírování k efektivnímu vložení více produktů s řídkými atributy.
def bulk_insert_with_attributes(conn, products: list[dict]):
"""Bulk insert products with sparse attributes."""
rows = []
for product in products:
attrs = product.get("attributes", {})
xml = "".join(f"<{k}>{v}</{k}>" for k, v in attrs.items()) or None
rows.append((product["id"], product["name"], xml))
cursor = conn.cursor()
result = cursor.bulkcopy("ProductAttributesWithSet", rows)
return result["rows_copied"]
# Usage
products = [
{"id": 100, "name": "Widget A", "attributes": {"Color": "Red", "Size": "Small"}},
{"id": 101, "name": "Widget B", "attributes": {"Weight": 1.5, "Material": "Steel"}},
{"id": 102, "name": "Widget C", "attributes": {}}, # No sparse attributes
]
bulk_insert_with_attributes(conn, products)
conn.commit()
Osvědčené postupy
Řiďte se validačními vzory, používejte sady sloupců pro flexibilitu a sledujte řídkost sloupců, abyste zajistili, že váš návrh řídkých sloupců splňuje požadavky výkonu a udržitelnosti.
Ověřte hodnoty sloupce s řídkými hodnotami
Před vložením dat ověřte, že všechny řídké atributy odpovídají povolené množině.
# Sparse columns have the same constraints as regular columns
# The SPARSE keyword only affects storage
def validate_and_insert(cursor, product_id: int, name: str, attributes: dict):
"""Insert with validation."""
allowed_attributes = {"Color", "Size", "Weight", "Material", "Warranty", "Manufacturer"}
invalid = set(attributes.keys()) - allowed_attributes
if invalid:
raise ValueError(f"Unknown attributes: {invalid}")
xml_parts = [f"<{k}>{v}</{k}>" for k, v in attributes.items()]
cursor.execute("""
INSERT INTO ProductAttributesWithSet (ProductID, ProductName, SparseAttributes)
VALUES (%(id)s, %(name)s, %(xml)s)
""", {"id": product_id, "name": name, "xml": "".join(xml_parts) or None})
Používejte sady sloupců pro flexibilitu
Sady sloupců zjednodušují práci se řídkými sloupci:
- Přidávejte nové řídké sloupce bez změn kódu.
- Ukládejte dynamické atributy.
- Automaticky serializujte a deserializujte XML.
Bez množiny sloupců potřebujete explicitní seznamy sloupců. Při použití sady sloupců sloupec XML automaticky zpracovává dynamické atributy.
Monitorování nulových procent
Analyzujte NULL procento pro sloupec, abyste zjistili, zda je vhodný pro optimalizaci řídkých sloupců.
def analyze_sparseness(cursor, table: str, column: str) -> float:
"""Check if column is a good sparse candidate."""
# Validate identifiers to prevent SQL injection
import re
if not re.match(r'^[A-Za-z_][A-Za-z0-9_.]*$', table):
raise ValueError(f"Invalid table name: {table}")
if not re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', column):
raise ValueError(f"Invalid column name: {column}")
cursor.execute(f"""
SELECT
COUNT(*) AS TotalRows,
SUM(CASE WHEN {column} IS NULL THEN 1 ELSE 0 END) AS NullRows
FROM {table}
""")
row = cursor.fetchone()
null_percentage = (row.NullRows / row.TotalRows * 100) if row.TotalRows > 0 else 0
print(f"Column {column}: {null_percentage:.1f}% NULL")
print(f"Recommendation: {'Good sparse candidate' if null_percentage > 60 else 'Keep as regular column'}")
return null_percentage