Microsoft SQL 提供了一种具有服务器端处理能力的本地xml数据类型:
- XQuery用于查询XML内容。
- 用于提取和修改的XML方法(
value(),query(),exist()nodes()modify())。 - 可选的XML模式验证。
- 用于提高性能的 XML 索引
mssql-python驱动以字符串形式发送和接收XML数据。 所有XML处理(XQuery、XML方法、模式验证)都在Microsoft SQL端执行。 使用Pythonxml.etree.ElementTree或类似库进行客户端XML解析。
何时使用XML与JSON: 当你需要模式验证、命名空间支持或混合内容(文本与元素交错)时,可以使用 XML。 当你的数据以键值对为导向、供 Web API 使用,或不需要强制实施架构约束时,请使用 JSON(配合 nvarchar 列和 Microsoft SQL 的 JSON 函数)。 大多数新应用更倾向于 JSON 格式,除非数据本身是文档结构的。
插入 XML 数据
将 XML 传递为 Python 字符串;驱动程序将其发送到 Microsoft SQL 的原生 XML 列类型。
作为字符串插入
通过参数化查询,直接将XML内容作为字符串插入XML列。
import mssql_python
from xml.etree import ElementTree as ET
conn = mssql_python.connect(
"Server=<server>.database.windows.net;"
"Database=<database>;"
"Authentication=ActiveDirectoryDefault;"
"Encrypt=yes"
)
cursor = conn.cursor()
# Create temp table for XML storage
cursor.execute("CREATE TABLE #XMLOrders (OrderID INT IDENTITY(1,1) PRIMARY KEY, OrderXML XML)")
# Insert XML as string
xml_data = """
<Order OrderID="1001">
<Customer Name="John Doe" Email="john@example.com"/>
<Items>
<Item ProductID="A1" Quantity="2" Price="29.99"/>
<Item ProductID="B2" Quantity="1" Price="49.99"/>
</Items>
</Order>
"""
cursor.execute("""
INSERT INTO #XMLOrders (OrderXML) VALUES (%(xml)s)
""", {"xml": xml_data})
conn.commit()
用 ElementTree 构建 XML
使用Python的ElementTree库程序生成XML文档,然后转换为字符串进行插入。
from xml.etree import ElementTree as ET
# Build XML document
order = ET.Element("Order", OrderID="1002")
customer = ET.SubElement(order, "Customer", Name="Jane Smith", Email="jane@example.com")
items = ET.SubElement(order, "Items")
ET.SubElement(items, "Item", ProductID="C3", Quantity="3", Price="19.99")
ET.SubElement(items, "Item", ProductID="D4", Quantity="2", Price="39.99")
# Convert to string
xml_string = ET.tostring(order, encoding="unicode")
cursor.execute("""
INSERT INTO #XMLOrders (OrderXML) VALUES (%(xml)s)
""", {"xml": xml_string})
conn.commit()
插入前验证 XML
在服务器上使用 SQL 的 TRY/CATCH 和 XML 类型铸造验证 XML 语法,以在存储前拒绝格式错误的文档。
# Validate XML with TRY/CATCH
cursor.execute("""
BEGIN TRY
DECLARE @xml XML = CAST(%(xml)s AS XML);
SELECT @xml.value('(/Order/@OrderID)[1]', 'INT') AS Validated;
END TRY
BEGIN CATCH
THROW;
END CATCH
""", {"xml": xml_data})
查询XML数据
在服务器端使用 XML 方法提取特定值,而无需将完整文档传给客户端。
XML value() 方法
使用 value() XPath表达式的方法,从XML中提取单个标量值或属性。
从 XML 中提取标量值:
cursor.execute("""
SELECT
OrderID,
OrderXML.value('(/Order/@OrderID)[1]', 'INT') AS XmlOrderID,
OrderXML.value('(/Order/Customer/@Name)[1]', 'NVARCHAR(100)') AS CustomerName,
OrderXML.value('(/Order/Customer/@Email)[1]', 'NVARCHAR(100)') AS Email
FROM #XMLOrders
""")
for row in cursor:
print(f"Order {row.XmlOrderID}: {row.CustomerName} ({row.Email})")
XML query() 方法
使用XPath表达式返回文档中的XML片段(非标量值)。
提取XML片段:
cursor.execute("""
SELECT
OrderID,
OrderXML.query('/Order/Items') AS ItemsXML
FROM #XMLOrders
WHERE OrderID = %(id)s
""", {"id": 1})
row = cursor.fetchone()
# row.ItemsXML is an XML string
items_xml = row.ItemsXML
print(items_xml)
XML exist() 方法
测试XPath表达式是否匹配文档中的任何节点,返回1表示true,返回0表示false。
检查XPath是否匹配:
cursor.execute("""
SELECT OrderID, OrderXML
FROM #XMLOrders
WHERE OrderXML.exist('/Order/Items/Item[@ProductID="A1"]') = 1
""")
for row in cursor:
print(f"Order {row.OrderID} contains product A1")
XML nodes() 方法(分解为行)
使用操作 CROSS APPLY 符和 nodes() 方法将嵌套的XML元素转换为关系行集。
将XML转换为关系格式:
cursor.execute("""
SELECT
o.OrderID,
Items.Item.value('@ProductID', 'VARCHAR(10)') AS ProductID,
Items.Item.value('@Quantity', 'INT') AS Quantity,
Items.Item.value('@Price', 'DECIMAL(10,2)') AS Price
FROM #XMLOrders o
CROSS APPLY o.OrderXML.nodes('/Order/Items/Item') AS Items(Item)
WHERE o.OrderID = %(id)s
""", {"id": 1})
for row in cursor:
print(f"Product {row.ProductID}: {row.Quantity} x ${row.Price}")
修改XML数据
使用 XML.modify() XQuery DML表达式来更新现有的XML内容。
XML modify() 并插入
使用 XQuery 的 insert 操作,通过 modify() 方法向 XML 文档中添加新元素。
cursor.execute("""
UPDATE #XMLOrders
SET OrderXML.modify('
insert <Item ProductID="E5" Quantity="1" Price="59.99"/>
into (/Order/Items)[1]
')
WHERE OrderID = %(id)s
""", {"id": 1})
conn.commit()
XML modify() 与 delete 一起使用
使用 modify() 方法和 XQuery 的 delete 操作从 XML 文档中删除元素或节点。
cursor.execute("""
UPDATE #XMLOrders
SET OrderXML.modify('
delete /Order/Items/Item[@ProductID="A1"]
')
WHERE OrderID = %(id)s
""", {"id": 1})
conn.commit()
XML modify() 与 replace
使用 modify() 方法和 XQuery 的 replace value of 操作,更新 XML 文档中的属性值或元素文本。
cursor.execute("""
UPDATE #XMLOrders
SET OrderXML.modify('
replace value of (/Order/Items/Item[@ProductID="B2"]/@Price)[1]
with 54.99
')
WHERE OrderID = %(id)s
""", {"id": 1})
conn.commit()
对于XML查询
在 FOR XML 任意查询中附加以返回单个XML字符串的结果。
适用于XML RAW。
生成 XML,每行都变成一个简单元素,属性为列。
cursor.execute("""
SELECT ProductID, Name, ListPrice
FROM Production.Product
WHERE ProductSubcategoryID = %(cat)s
FOR XML RAW('Product'), ROOT('Products')
""", {"cat": 1})
xml_result = cursor.fetchval()
print(xml_result)
# <Products><Product ProductID="1" Name="..." ListPrice="..."/></Products>
FOR XML AUTO
生成带有嵌套结构的XML,自动反映查询中的连接层级。
cursor.execute("""
SELECT sc.Name AS SubcategoryName, p.Name AS ProductName, p.ListPrice
FROM Production.ProductSubcategory sc
JOIN Production.Product p ON sc.ProductSubcategoryID = p.ProductSubcategoryID
WHERE sc.ProductSubcategoryID = %(cat)s
FOR XML AUTO, ROOT('Catalog')
""", {"cat": 1})
xml_result = cursor.fetchval()
# Nested XML structure based on join hierarchy
对于XML路径
利用显式列别名和子查询构建自定义XML结构,以控制嵌套和元素名称。
对 XML 结构拥有最大控制权:
cursor.execute("""
SELECT
sc.ProductSubcategoryID AS '@ID',
sc.Name AS 'Name',
(
SELECT p.ProductID AS '@ID',
p.Name AS 'Name',
p.ListPrice AS 'Price'
FROM Production.Product p
WHERE p.ProductSubcategoryID = sc.ProductSubcategoryID
FOR XML PATH('Product'), TYPE
) AS 'Products'
FROM Production.ProductSubcategory sc
WHERE sc.ProductSubcategoryID = %(cat)s
FOR XML PATH('Subcategory'), ROOT('Catalog')
""", {"cat": 1})
xml_result = cursor.fetchval()
用 Python 解析 XML
检索 XML 字符串,并用 xml.etree.ElementTree 兼容库解析。
用 ElementTree 解析查询结果
从数据库获取 XML,并用 ElementTree 解析成 Python 对象树,通过 DOM 访问元素和属性。
from xml.etree import ElementTree as ET
cursor.execute("""
SELECT CatalogDescription FROM Production.ProductModel
WHERE CatalogDescription IS NOT NULL AND ProductModelID = 19
""")
row = cursor.fetchone()
root = ET.fromstring(row.CatalogDescription)
# Navigate XML structure using namespace
ns = {'pd': 'http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelDescription'}
summary = root.find('.//pd:Summary', ns)
if summary is not None:
# Get all text content
text = ''.join(summary.itertext()).strip()
print(f"Summary: {text[:100]}")
将XML转换为词典
将XML树结构转换为嵌套的Python字典,便于程序化访问嵌套数据。
def xml_to_dict(element):
"""Convert XML element to dictionary."""
result = {}
# Add attributes
if element.attrib:
result['@attributes'] = element.attrib
# Add children
children = list(element)
if children:
child_dict = {}
for child in children:
child_result = xml_to_dict(child)
if child.tag in child_dict:
# Convert to list if multiple same-named children
if not isinstance(child_dict[child.tag], list):
child_dict[child.tag] = [child_dict[child.tag]]
child_dict[child.tag].append(child_result)
else:
child_dict[child.tag] = child_result
result.update(child_dict)
# Add text content
if element.text and element.text.strip():
result['#text'] = element.text.strip()
return result
# Usage
cursor.execute("""
SELECT CatalogDescription FROM Production.ProductModel
WHERE CatalogDescription IS NOT NULL AND ProductModelID = 19
""")
xml_string = cursor.fetchval()
root = ET.fromstring(xml_string)
data = xml_to_dict(root)
处理大型XML文档
Microsoft SQL 可能会将大量FOR XML结果拆分到多行;在解析前将部分串接起来。
分块获取 XML
当FOR XML返回大量结果集时,Microsoft SQL 可能会将 XML 拆分到多行;将所有部分合并为单一文档。
# FOR XML might split large results across rows
cursor.execute("""
SELECT * FROM LargeTable FOR XML RAW
""")
xml_parts = []
for row in cursor:
xml_parts.append(row[0])
full_xml = "".join(xml_parts)
流式 XML 解析
对于非常大的 XML 文档,可以使用迭代解析,一次处理一个元素,而无需将整棵树加载到内存中。
from xml.etree import ElementTree as ET
import io
cursor.execute("""
SELECT Instructions FROM Production.ProductModel
WHERE Instructions IS NOT NULL AND ProductModelID = 7
""")
xml_string = cursor.fetchval()
# Use iterparse for memory-efficient parsing
xml_stream = io.StringIO(xml_string)
for event, elem in ET.iterparse(xml_stream, events=['end']):
if elem.tag.endswith('step'):
# Process step element
text = ''.join(elem.itertext()).strip()
if text:
print(f"Step: {text[:60]}")
# Free memory
elem.clear()
XML 索引
在 Microsoft SQL 中创建索引以加快 XML 查询速度:
-- Primary XML index (assumes a table with an XML column)
CREATE PRIMARY XML INDEX PIX_XMLOrders_OrderXML
ON #XMLOrders(OrderXML);
-- Secondary indexes for specific access patterns
CREATE XML INDEX SIX_XMLOrders_Path
ON #XMLOrders(OrderXML)
USING XML INDEX PIX_XMLOrders_OrderXML
FOR PATH;
CREATE XML INDEX SIX_XMLOrders_Value
ON #XMLOrders(OrderXML)
USING XML INDEX PIX_XMLOrders_OrderXML
FOR VALUE;
使用命名空间
在 XQuery 表达式中使用 declare namespace 语法以内联方式声明命名空间前缀。
查询带有命名空间的 XML
在XPath表达式中添加命名空间声明,以匹配特定命名空间中的元素。
xml_with_ns = """
<Order xmlns="http://example.com/orders"
xmlns:c="http://example.com/customer">
<c:Customer Name="John Doe"/>
<Items>
<Item ProductID="A1"/>
</Items>
</Order>
"""
cursor.execute("""
SELECT OrderXML.value('
declare namespace o="http://example.com/orders";
declare namespace c="http://example.com/customer";
(/o:Order/c:Customer/@Name)[1]
', 'NVARCHAR(100)') AS CustomerName
FROM #XMLOrders
WHERE OrderID = %(id)s
""", {"id": 1})
用 Python 解析命名空间的 XML
在解析 Python 中带有命名空间的 XML 时,请在你find()和其他元素访问调用中声明命名空间映射。
from xml.etree import ElementTree as ET
cursor.execute("""
SELECT TOP 1
(SELECT SalesOrderID AS [@OrderID],
TotalDue AS [@Total],
CustomerID AS [Customer/@ID]
FROM Sales.SalesOrderHeader
WHERE SalesOrderID = 43659
FOR XML PATH('Order'))
""")
xml_string = cursor.fetchval()
root = ET.fromstring(xml_string)
order_id = root.get('OrderID')
total = root.get('Total')
print(f"Order {order_id}: ${total}")
最佳做法
应用这些指南,高效地处理XML数据。
选择 XML 而非 JSON
根据你的数据结构和处理需求,决定是使用Microsoft SQL的原生xml类型还是存储JSONnvarchar:
此比较帮助您决定是否使用 Microsoft SQL xml 类型,或将 JSON 存储在nvarchar:
| Feature | XML | JSON |
|---|---|---|
| 架构验证 | 原生支持 | 无原生支持 |
| 命名空间 | 完全支持 | 不支持 |
| 特性 | 支持 | 无直接等效项 |
| 混合内容 | 支持 | 不支持 |
| 文档处理 | 更好 | 结构化程度较低 |
避免使用 SELECT *
当你只需要特定值时,不要检索完整的 XML 文档。 使用XML方法提取服务器上的数据。 这种方法减少了网络流量,避免了用 Python 解析大型 XML 文档。
# Create sample XML table for performance demos
cursor.execute("""
CREATE TABLE #XMLPerf (
OrderID INT,
OrderXML XML
)
""")
cursor.execute("""
INSERT INTO #XMLPerf (OrderID, OrderXML) VALUES
(1, '<Order><Customer Name="Alice"/><Item ProductID="1" Qty="2" Price="29.99"/></Order>'),
(2, '<Order><Customer Name="Bob"/><Item ProductID="3" Qty="1" Price="49.99"/></Order>')
""")
# Anti-pattern: Downloads entire XML per row
cursor.execute("SELECT * FROM #XMLPerf")
# Better: Extract only the values you need on the server
cursor.execute("""
SELECT OrderID,
OrderXML.value('(/Order/Customer/@Name)[1]', 'NVARCHAR(100)') AS Customer
FROM #XMLPerf
""")
处理空 XML
查询 XML 数据时,在 XML 方法返回 NULL 时,使用 COALESCE() 提供默认值。
cursor.execute("""
SELECT OrderID,
COALESCE(OrderXML.value('(/Order/@Total)[1]', 'DECIMAL(10,2)'), 0) AS Total
FROM #XMLPerf
""")