スパース列は、多数のnull可能な列を持つテーブルのNULL値に対するMicrosoft SQLストレージ最適化です。 クライアントアプリケーションでは、スパース列を通常の列として認識します。 mssql-pythonドライバーは、特別な処理なしに他の列と同様に読み書きを行います。
| 特徴 | 説明 |
|---|---|
| スパース列 | NULL値はストレージをゼロにします |
| 列セット | すべてのスパース列のXML表現 |
| 横長のテーブル | 最大30,000列に対応 |
Note
スパースカラムはサーバー側の特徴です。 mssql-pythonドライバーは、スパースカラムを扱うために特別な設定やAPIを必要としません。 クライアントが見た唯一の違いは、カラムセットを使うと、スパースカラム値のXML表現を返すことです。
最適な用途:
- NULL値が20~50%以上あるテーブル。
- 変数属性付きのドキュメント保存。
- EAV(エンティティ・属性・値)パターン。
- 多くのオプション読み取り値を備えたセンサーデータ。
スパース列を作成する
テーブルスキーマでスパースカラムを定義し、NULL値が頻繁に保持されるカラムに SPARSE NULL 修飾子を加えます。
基本的なスパースカラムテーブル
オプション属性用にスパース列のテーブルを作成します。
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
);
列セットあり
すべてのスパース列に同時にXMLアクセスを提供する列セットを追加してください。
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
);
スパース列データの挿入
通常の列の場合と同様に、列名を指定してスパース列にデータを挿入します。
個別の列を挿入してください
特定のスパース列の値を設定した製品を挿入します。
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()
列セット (XML) 経由で挿入
XMLを列セットに渡すことで、複数のスパースカラム値を同時に挿入します。
# 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()
動的属性挿入
動的属性を辞書として受け入れ、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()
スパース列のクエリ
スパース列を名前でクエリするか、列セットを通じてすべてのスパース値を一度に取得できます。
個別の列をクエリする
標準的な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}")
カラムセットを通じたクエリ
すべてのスパース列の値を列集合からXMLとして取得します。
# 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}")
Pythonにおける列集合XMLの解析
列セットからXMLを解析してPython辞書に変換し、操作を容易にします。
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'}
SELECT * でクエリを
SELECT *を使用すると、列セットは個々のスパース列ではなく単一のXML列として戻ります。
# 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]}")
個々の列を明示的にクエリしてください
列セットを持つテーブルから個々のスパース列を取得するには、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}")
スパース列の更新
個々のスパースカラムを名前で更新するか、カラムセットXMLを使ってすべてのスパース値を一度に置き換えることができます。
各列を個別に更新
標準的な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()
列集合を通じた更新
すべてのスパース列の値を、列セット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
列集合による部分的な更新
特定のスパース属性のみを更新し、更新に含まれていない他の属性の値は保持します。
# 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()
動的な列パターン
スパースカラム間を動的に検索する柔軟なクエリを構築し、許容リストを使ってカラム名を検証し、SQLインジェクション攻撃を防ぎましょう。
EAVスタイルデータのクエリ
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")
柔軟な属性検索
複数のオプション属性に同時に合致する商品を検索してください。
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")
パフォーマンスに関する考慮事項
スパースカラムがストレージの利点をもたらすかどうかを評価し、オーバーヘッドコストを評価し、パフォーマンスモニタリングを活用してスパースカラム設計を最適化しましょう。
スパースカラムの使用タイミング
スパース列に適しているものには、以下があります:
- NULL値が60~70%以上の列
- 多くのオプションカラムを持つ広いテーブル。
- ストレージ最適化が優先されるワークロードです。
以下の場合、スパースカラムの使用は避けてください:
- ほとんどの行には値があり(非NULL値1行あたり4バイトのオーバーヘッドが発生します)。
- この列はWHERE節で頻繁に使われます。
- この列はクラスタインデックスの一部です。
貯蓄の確認
同じテーブルのスパース版と非スパース版のストレージ容量を比較してください。
-- Compare storage with and without sparse
EXEC sp_spaceused 'ProductAttributes';
EXEC sp_spaceused 'ProductAttributesWithoutSparse';
指数に関する考慮事項
スパースカラムをインデックス化できます。 フィルタリングインデックスはNULL行をスキップするため、スパースデータに適しています。
CREATE INDEX IX_Products_Color
ON ProductAttributes(Color)
WHERE Color IS NOT NULL;
一括操作
Pythonで列セット XML を構築してから行をbulkcopy()に渡すことで、スパース列を持つ複数製品の挿入を最適化します。
まばらなカラムのバルクインサート
一括コピーを使って、スパース属性の複数の商品を効率的に挿入しましょう。
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()
ベスト プラクティス
検証パターンに従い、柔軟性を高めるためにカラムセットを使用し、カラムのスパーネスを監視して、スパースカラム設計がパフォーマンスと保守性の目標を満たしているか確認しましょう。
スパース列の値を検証する
データを挿入する前に、すべてのスパース属性が許可された集合に一致していることを検証してください。
# 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})
柔軟性のためにカラムセットを使いましょう
列セットを使用すると、スパース列の操作が簡単になります:
- コード変更なしで新しいスパースカラムを追加できます。
- 動的属性を保存します。
- XMLを自動的にシリアライズおよびデシリアライズします。
列セットがなければ、明示的な列リストが必要です。 列セットの場合、XML列は動的属性を自動的に処理します。
NULLパーセンテージを監視してください
ある列のNULLパーセンテージを分析し、スパースカラム最適化に適した候補かどうかを判断します。
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