Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Las columnas dispersas son una optimización de almacenamiento SQL de Microsoft para valores NULL en tablas con muchas columnas anulables. Las aplicaciones cliente ven las columnas dispersas como columnas regulares. El controlador mssql-python los lee y escribe como cualquier otra columna sin un manejo especial.
| Feature | Descripción |
|---|---|
| Columnas dispersas | Los valores NULL utilizan almacenamiento cero |
| Conjuntos de columnas | Representación XML de todas las columnas dispersas |
| Tablas anchas | Soporte para hasta 30.000 columnas |
Note
Las columnas dispersas son una característica del servidor. El controlador mssql-python no requiere ninguna configuración especial ni API para funcionar con columnas dispersas. La única diferencia visible para el cliente: cuando usas conjuntos de columnas, devuelven una representación XML de valores de columna dispersos.
Más adecuado para:
- Tablas con entre un 20 % y un 50 % o más de valores NULL.
- Almacenamiento de documentos con atributos variables.
- Patrones EAV (Entidad-Atributo-Valor).
- Datos de sensores con muchas lecturas opcionales.
Crear columnas dispersas
Define columnas dispersas en tu esquema de tabla añadiendo el SPARSE NULL modificador a columnas que frecuentemente contendrán valores NULL.
Tabla básica de columnas dispersas
Crea una tabla con columnas dispersas para atributos opcionales.
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
);
Con conjunto de columnas
Añadir un conjunto de columnas para proporcionar acceso XML a todas las columnas dispersas simultáneamente.
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
);
Insertar datos de columna dispersos
Inserta datos en columnas dispersas por nombre, igual que lo harías con las columnas normales.
Insertar columnas individuales
Insertar productos con valores específicos rellenados en columnas dispersas.
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()
Insertar mediante conjunto de columnas (XML)
Inserta varios valores de columna dispersos a la vez pasando XML al conjunto de columnas.
# 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()
Inserción dinámica de atributos
Crea una función que acepte atributos dinámicos como diccionario y construya el XML automáticamente.
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()
Consulta columnas dispersas
Consulta las columnas dispersas por nombre, o recupera todos los valores dispersos a la vez a través del conjunto de columnas.
Consulta columnas individuales
Consulta columnas dispersas específicas usando la sintaxis estándar 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}")
Consulta mediante el conjunto de columnas
Recupera todos los valores dispersos de columnas como XML del conjunto de columnas.
# 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}")
Analizar el XML del conjunto de columnas en Python
Analiza el XML del conjunto de columnas para convertirlo en un diccionario de Python y facilitar la manipulación.
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'}
Consulta con SELECT *
Cuando usas SELECT *, el conjunto de columnas devuelve como una sola columna XML en lugar de columnas dispersas individuales.
# 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]}")
Consulta las columnas individuales explícitamente
Para recuperar columnas dispersas individuales de una tabla con un conjunto de columnas, enumérelas explícitamente en la cláusula 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}")
Actualizar columnas dispersas
Actualizar las columnas dispersas individuales por nombre o reemplazar todos los valores dispersos de una vez mediante el conjunto de columnas XML.
Actualizar columnas individuales
Actualizar valores específicos de columnas dispersas usando la sintaxis estándar de SQL UPDATE .
cursor.execute("""
UPDATE ProductAttributes
SET Color = %(color)s, Weight = %(weight)s
WHERE ProductID = %(id)s
""", {"id": 1, "color": "Red", "weight": 0.2})
conn.commit()
Actualización mediante conjunto de columnas
Sustituye todos los valores de columna dispersos actualizando directamente el conjunto de columnas en XML.
# 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
Actualización parcial mediante conjunto de columnas
Actualizar solo atributos dispersos específicos, manteniendo los valores de otros atributos que no se incluyen en la actualización.
# 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()
Patrones dinámicos de columnas
Construye consultas flexibles que busquen dinámicamente en columnas dispersas, usando listas de permisos para validar los nombres de las columnas y prevenir ataques de inyección SQL.
Consulta de datos al estilo EAV
Buscar productos por un nombre y un valor de atributo específicos mediante coincidencia de patrones de Entidad-Atributo-Valor (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")
Búsqueda flexible de atributos
Busca productos que tengan múltiples atributos opcionales a la vez.
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")
Consideraciones sobre el rendimiento
Evalúa cuándo las columnas dispersas proporcionan beneficios de almacenamiento, evalúa los costes generales y utiliza la monitorización del rendimiento para optimizar el diseño de tu columna escasa.
Cuándo usar columnas dispersas
Buenos candidatos para columnas escasas incluyen:
- Columnas con más del 60-70 % de valores NULL.
- Tablas anchas con muchas columnas opcionales.
- Cargas de trabajo donde la optimización del almacenamiento es prioritaria.
Evitar usar columnas dispersas cuando:
- La mayoría de las filas tienen valores (cada valor no NULL añade 4 bytes de sobrecarga).
- La columna se usa frecuentemente en las cláusulas WHERE.
- La columna forma parte del índice agrupado.
Comprobar el ahorro de almacenamiento
Compara el tamaño de almacenamiento de versiones dispersas y no dispersas de la misma tabla.
-- Compare storage with and without sparse
EXEC sp_spaceused 'ProductAttributes';
EXEC sp_spaceused 'ProductAttributesWithoutSparse';
Consideraciones sobre el índice
Puedes indexar columnas dispersas. Los índices filtrados funcionan bien para datos dispersos porque se saltan filas NULL:
CREATE INDEX IX_Products_Color
ON ProductAttributes(Color)
WHERE Color IS NOT NULL;
Operaciones masivas
Optimiza las inserciones de varios productos con columnas dispersas mediante la creación en Python del XML del conjunto de columnas antes de pasar las filas a bulkcopy().
Inserción masiva con columnas dispersas
Utiliza copia masiva para insertar eficazmente múltiples productos con atributos dispersos.
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()
procedimientos recomendados
Sigue patrones de validación, utiliza conjuntos de columnas para mayor flexibilidad y monitoriza la escasez de columnas para asegurarte de que tu diseño de columna escasa cumpla con los objetivos de rendimiento y mantenimiento.
Validar los valores de una columna dispersa
Valida que todos los atributos dispersos coincidan con el conjunto permitido antes de insertar datos.
# 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})
Utiliza conjuntos de columnas para mayor flexibilidad
Los conjuntos de columnas simplifican el trabajo con columnas dispersas:
- Añade nuevas columnas dispersas sin cambios en el código.
- Guardar atributos dinámicos.
- Serializar y deserializar XML automáticamente.
Sin un conjunto de columnas, necesitas listas explícitas de columnas. Con un conjunto de columnas, la columna XML gestiona automáticamente los atributos dinámicos.
Supervisar los porcentajes de valores NULL
Analiza el porcentaje de NULL de una columna para determinar si es un buen candidato para la optimización de columnas dispersas.
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