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.
Microsoft SQL 2016 y versiones posteriores y Azure SQL proporcionan soporte JSON mediante funciones que operan sobre nvarchar columnas. El controlador mssql-python envía y recibe JSON como cadenas Python normales. Ustedes pueden:
- Guarda JSON como cadenas en
nvarcharcolumnas. - Consulta JSON con expresiones de ruta usando
JSON_VALUE,JSON_QUERY, yOPENJSON. - Transformar datos relacionales a JSON con
FOR JSON. - Analiza JSON en formato relacional con
OPENJSON.
Note
Microsoft SQL almacena los datos JSON en nvarchar columnas, no en un tipo de columna JSON dedicado. El controlador mssql-python envía y recibe JSON como cadenas normales. Utiliza el módulo integrado json de Python para serializar y desserializar en el lado del cliente.
Almacenar datos JSON
Serializa dictados en Python a cadenas con json.dumps() antes de insertarlos en las columnas nvarchar.
Insertar cadena JSON
Almacenar un diccionario de Python como texto JSON en una tabla de bases de datos:
import json
import mssql_python
conn = mssql_python.connect(
"Server=<server>.database.windows.net;"
"Database=<database>;"
"Authentication=ActiveDirectoryDefault;"
"Encrypt=yes"
)
cursor = conn.cursor()
# Create table with JSON column
cursor.execute("""
CREATE TABLE #JsonProducts (
ProductID INT IDENTITY PRIMARY KEY,
Name NVARCHAR(100),
JsonData NVARCHAR(MAX)
)
""")
# Python dict to JSON string
product_data = {
"name": "Widget Pro",
"specs": {
"weight": 2.5,
"dimensions": {"width": 10, "height": 5, "depth": 3}
},
"tags": ["electronics", "gadgets", "bestseller"]
}
cursor.execute("""
INSERT INTO #JsonProducts (Name, JsonData)
VALUES (%(name)s, %(json)s)
""", {"name": "Widget Pro", "json": json.dumps(product_data)})
conn.commit()
Validar JSON en el inserto
Utiliza la ISJSON() función para validar la sintaxis JSON antes de insertar:
data = {"name": "Widget", "specs": {"weight": 1.5}}
cursor.execute("""
CREATE TABLE #JsonValidate (
ProductID INT IDENTITY, Name NVARCHAR(100), JsonData NVARCHAR(MAX)
)
""")
cursor.execute("""
INSERT INTO #JsonValidate (Name, JsonData)
SELECT %(name)s, %(json)s
WHERE ISJSON(%(json)s) = 1
""", {"name": "Widget", "json": json.dumps(data)})
if cursor.rowcount == 0:
raise ValueError("Invalid JSON data")
Consulta de datos JSON
Utiliza funciones de ruta JSON de Microsoft SQL para extraer valores en el servidor antes de devolverlos al cliente.
Extraer valores escalares
Usar JSON_VALUE para extraer valores individuales:
# Create table with sample JSON data
cursor.execute("""
CREATE TABLE #JsonExtract (
ProductID INT IDENTITY, Name NVARCHAR(100), JsonData NVARCHAR(MAX)
)
""")
cursor.execute("""
INSERT INTO #JsonExtract (Name, JsonData) VALUES (
'Widget Pro',
'{"name":"Widget Pro","specs":{"weight":2.5,"dimensions":{"width":10,"height":5,"depth":3}},"tags":["electronics","gadgets"]}'
)
""")
cursor.execute("""
SELECT
Name,
JSON_VALUE(JsonData, '$.specs.weight') AS Weight,
JSON_VALUE(JsonData, '$.specs.dimensions.width') AS Width
FROM #JsonExtract
WHERE JSON_VALUE(JsonData, '$.name') = %(name)s
""", {"name": "Widget Pro"})
row = cursor.fetchone()
print(f"Weight: {row.Weight}, Width: {row.Width}")
Extraer objetos o matrices
Usa JSON_QUERY para objetos y matrices.
cursor.execute("""
CREATE TABLE #JsonQuery (
ProductID INT IDENTITY, Name NVARCHAR(100), JsonData NVARCHAR(MAX)
)
""")
cursor.execute("""
INSERT INTO #JsonQuery (Name, JsonData) VALUES (
'Widget Pro',
'{"specs":{"weight":2.5,"color":"blue"},"tags":["electronics","gadgets"]}'
)
""")
cursor.execute("""
SELECT
Name,
JSON_QUERY(JsonData, '$.specs') AS Specs,
JSON_QUERY(JsonData, '$.tags') AS Tags
FROM #JsonQuery
""")
for row in cursor:
specs = json.loads(row.Specs) if row.Specs else {}
tags = json.loads(row.Tags) if row.Tags else []
print(f"{row.Name}: {specs}, Tags: {tags}")
Analizar un array JSON a filas
Expande un array JSON en filas usando OPENJSON:
cursor.execute("""
CREATE TABLE #JsonArray (
ProductID INT IDENTITY, Name NVARCHAR(100), JsonData NVARCHAR(MAX)
)
""")
cursor.execute("""
INSERT INTO #JsonArray (Name, JsonData) VALUES
('Widget Pro', '{"tags":["electronics","gadgets","bestseller"]}'),
('Gadget X', '{"tags":["tools","gadgets"]}')
""")
cursor.execute("""
SELECT p.Name, t.value AS Tag
FROM #JsonArray p
CROSS APPLY OPENJSON(p.JsonData, '$.tags') t
""")
for row in cursor:
print(f"Product: {row.Name}, Tag: {row.Tag}")
Analizar objeto JSON en columnas
Extrae campos individuales de los objetos JSON usando JSON_VALUE() y convierte los resultados en los tipos SQL apropiados.
cursor.execute("""
CREATE TABLE #JsonCols (
ProductID INT IDENTITY, Name NVARCHAR(100), JsonData NVARCHAR(MAX)
)
""")
cursor.execute("""
INSERT INTO #JsonCols (Name, JsonData) VALUES (
'Widget Pro',
'{"name":"Widget Pro","specs":{"weight":2.5,"dimensions":{"width":10,"height":5}}}'
)
""")
cursor.execute("""
SELECT
p.ProductID,
j.name AS ProductName,
j.weight,
j.width,
j.height
FROM #JsonCols p
CROSS APPLY OPENJSON(p.JsonData)
WITH (
name NVARCHAR(100) '$.name',
weight DECIMAL(5,2) '$.specs.weight',
width INT '$.specs.dimensions.width',
height INT '$.specs.dimensions.height'
) j
""")
Modificar datos JSON
Úsalo JSON_MODIFY para actualizar una ruta específica en un documento JSON sin reescribir todo el valor.
Actualizar valor JSON
Modificar una sola propiedad JSON usando JSON_MODIFY:
cursor.execute("""
CREATE TABLE #JsonMod (
ProductID INT IDENTITY, Name NVARCHAR(100), JsonData NVARCHAR(MAX)
)
""")
cursor.execute("""
INSERT INTO #JsonMod (Name, JsonData) VALUES (
'Widget Pro',
'{"specs":{"weight":2.5,"dimensions":{"width":10}},"tags":["electronics"]}'
)
""")
cursor.execute("""
UPDATE #JsonMod
SET JsonData = JSON_MODIFY(JsonData, '$.specs.weight', %(weight)s)
WHERE ProductID = %(id)s
""", {"weight": 3.0, "id": 1})
conn.commit()
Añadir propiedad JSON
Insertar una nueva propiedad en un objeto JSON existente:
cursor.execute("""
CREATE TABLE #JsonAdd (
ProductID INT IDENTITY, Name NVARCHAR(100), JsonData NVARCHAR(MAX)
)
""")
cursor.execute("""
INSERT INTO #JsonAdd (Name, JsonData) VALUES (
'Widget Pro', '{"specs":{"weight":2.5}}'
)
""")
cursor.execute("""
UPDATE #JsonAdd
SET JsonData = JSON_MODIFY(JsonData, '$.specs.color', %(color)s)
WHERE ProductID = %(id)s
""", {"color": "blue", "id": 1})
Eliminar propiedad JSON
Elimina una propiedad de un objeto JSON configurándola como NULL:
cursor.execute("""
CREATE TABLE #JsonRem (
ProductID INT IDENTITY, Name NVARCHAR(100), JsonData NVARCHAR(MAX)
)
""")
cursor.execute("""
INSERT INTO #JsonRem (Name, JsonData) VALUES (
'Widget Pro', '{"specs":{"weight":2.5,"color":"blue"}}'
)
""")
cursor.execute("""
UPDATE #JsonRem
SET JsonData = JSON_MODIFY(JsonData, '$.specs.color', NULL)
WHERE ProductID = %(id)s
""", {"id": 1})
Agregar a una matriz JSON
Añade un nuevo valor al final de un array JSON usando append directiva en JSON_MODIFY:
cursor.execute("""
CREATE TABLE #JsonAppend (
ProductID INT IDENTITY, Name NVARCHAR(100), JsonData NVARCHAR(MAX)
)
""")
cursor.execute("""
INSERT INTO #JsonAppend (Name, JsonData) VALUES (
'Widget Pro', '{"tags":["electronics","gadgets"]}'
)
""")
cursor.execute("""
UPDATE #JsonAppend
SET JsonData = JSON_MODIFY(
JsonData,
'append $.tags',
%(tag)s
)
WHERE ProductID = %(id)s
""", {"tag": "new-arrival", "id": 1})
Conversión de datos relacionales en JSON
La FOR JSON cláusula transforma los resultados de la consulta en una cadena JSON en el lado del servidor.
PARA JSON AUTO
Generar JSON a partir de los resultados de la consulta:
cursor.execute("""
SELECT TOP 5 o.SalesOrderID, p.LastName AS CustomerName, o.TotalDue
FROM Sales.SalesOrderHeader o
JOIN Sales.Customer c ON o.CustomerID = c.CustomerID
JOIN Person.Person p ON c.PersonID = p.BusinessEntityID
FOR JSON AUTO
""")
# Result is a single string containing JSON
json_result = cursor.fetchval()
orders = json.loads(json_result)
print(json.dumps(orders, indent=2))
PARA EL CAMINO DE JSON
Obtén más control sobre la estructura JSON:
cursor.execute("""
SELECT
o.SalesOrderID AS 'order.id',
o.OrderDate AS 'order.date',
p.LastName AS 'customer.name',
e.EmailAddress AS 'customer.email'
FROM Sales.SalesOrderHeader o
JOIN Sales.Customer c ON o.CustomerID = c.CustomerID
JOIN Person.Person p ON c.PersonID = p.BusinessEntityID
JOIN Person.EmailAddress e ON p.BusinessEntityID = e.BusinessEntityID
WHERE o.SalesOrderID = %(id)s
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
""", {"id": 43659})
json_result = cursor.fetchval()
order = json.loads(json_result)
# Structure: {"order": {"id": 43659, "date": "..."}, "customer": {"name": "...", "email": "..."}}
JSON anidado
Consulta estructuras de datos con arrays JSON anidados y objetos usando subconsultas con FOR JSON para construir una salida JSON jerárquica.
cursor.execute("""
SELECT TOP 3
c.CustomerID,
p.LastName AS CustomerName,
(SELECT TOP 3 o.SalesOrderID, o.TotalDue
FROM Sales.SalesOrderHeader o
WHERE o.CustomerID = c.CustomerID
FOR JSON PATH) AS Orders
FROM Sales.Customer c
JOIN Person.Person p ON c.PersonID = p.BusinessEntityID
WHERE c.PersonID IS NOT NULL
FOR JSON PATH
""")
json_result = cursor.fetchval()
customers = json.loads(json_result)
# Each customer has nested Orders array
Patrones de integración en Python
Estos patrones muestran cómo construir abstracciones en Python sobre tablas respaldadas por JSON.
Patrón de repositorio con JSON
Implementa una capa de acceso a datos que serialize y deserializa objetos Python en columnas JSON, proporcionando una interfaz segura de tipos para la base de datos.
from dataclasses import dataclass, asdict
from typing import Optional
import json
@dataclass
class ProductSpecs:
weight: float
color: str
dimensions: dict
@dataclass
class Product:
id: Optional[int]
name: str
specs: ProductSpecs
class ProductRepository:
def __init__(self, connection):
self.conn = connection
cursor = self.conn.cursor()
cursor.execute("""
IF OBJECT_ID('#JsonRepo') IS NULL
CREATE TABLE #JsonRepo (
ProductID INT IDENTITY PRIMARY KEY,
Name NVARCHAR(100),
JsonData NVARCHAR(MAX)
)
""")
self.conn.commit()
def save(self, product: Product) -> int:
cursor = self.conn.cursor()
specs_json = json.dumps(asdict(product.specs))
if product.id:
cursor.execute("""
UPDATE #JsonRepo SET Name = %(name)s, JsonData = %(json)s
WHERE ProductID = %(id)s
""", {"name": product.name, "json": specs_json, "id": product.id})
else:
cursor.execute("""
INSERT INTO #JsonRepo (Name, JsonData)
OUTPUT INSERTED.ProductID
VALUES (%(name)s, %(json)s)
""", {"name": product.name, "json": specs_json})
product.id = cursor.fetchval()
self.conn.commit()
return product.id
def get(self, product_id: int) -> Optional[Product]:
cursor = self.conn.cursor()
cursor.execute("""
SELECT ProductID, Name, JsonData FROM #JsonRepo WHERE ProductID = %(id)s
""", {"id": product_id})
row = cursor.fetchone()
if row is None:
return None
specs_data = json.loads(row.JsonData)
return Product(
id=row.ProductID,
name=row.Name,
specs=ProductSpecs(**specs_data)
)
Conéctate a la base de datos, luego crea el repositorio y úsalo para guardar y recuperar un producto. El método save() sigue la rama INSERT cuando id es None y la rama UPDATE en caso contrario:
conn = mssql_python.connect(connection_string)
repo = ProductRepository(conn)
# id is None, so save() inserts a new row and returns the generated ProductID.
product = Product(
id=None,
name="Widget Pro",
specs=ProductSpecs(weight=2.5, color="black", dimensions={"width": 10, "height": 5})
)
product_id = repo.save(product)
print(f"Saved product {product_id}")
# Read the product back into a typed Product object.
loaded = repo.get(product_id)
print(loaded)
conn.close()
El repositorio crea #JsonRepo como una tabla temporal local asociada a la conexión que se pasa, así que save() y get() deben compartir esa misma conexión. La tabla se elimina cuando se cierra la conexión.
Gestionar eficientemente los grandes resultados JSON
Cuando los resultados de JSON sean grandes, obténlos en partes a lo largo de varias filas.
def fetch_json_in_parts(cursor, query: str, params: dict) -> list:
"""Handle JSON results that might span multiple rows."""
cursor.execute(query, params)
# FOR JSON might split large results across rows
json_parts = []
for row in cursor:
json_parts.append(row[0])
# Combine parts
json_string = "".join(json_parts)
return json.loads(json_string) if json_string else []
# Usage
data = fetch_json_in_parts(cursor, "SELECT TOP 100 * FROM Production.Product FOR JSON AUTO", {})
Convertir los resultados de la consulta a JSON en Python
Transforma los resultados de consultas relacionales en formato JSON en Python convirtiendo cada fila en un diccionario y luego serializando a JSON.
def query_to_json(cursor, query: str, params: dict = None) -> str:
"""Execute query and return results as JSON string."""
cursor.execute(query, params or {})
columns = [col[0] for col in cursor.description]
rows = []
for row in cursor:
rows.append(dict(zip(columns, row)))
return json.dumps(rows, default=str, indent=2)
# Usage
json_output = query_to_json(cursor, "SELECT TOP 5 ProductID, Name, ListPrice FROM Production.Product WHERE ProductSubcategoryID = %(cat)s", {"cat": 1})
print(json_output)
Indexar datos JSON
Crea una columna calculada respaldada por una expresión de camino JSON para hacer que el camino sea indexable.
Columna calculada con índice
Define una columna calculada que extrae un valor JSON y aplícale un índice para un filtrado eficiente en caminos JSON que se consultan con frecuencia. El siguiente ejemplo crea una tabla permanente, añade una columna computada persistente sobre la ruta JSON $.specs.weight y crea un índice sobre ella.
cursor.execute("""
IF OBJECT_ID('dbo.ProductCatalog', 'U') IS NOT NULL
DROP TABLE dbo.ProductCatalog
""")
cursor.execute("""
CREATE TABLE dbo.ProductCatalog (
ProductID INT IDENTITY PRIMARY KEY,
Name NVARCHAR(100),
JsonData NVARCHAR(MAX)
)
""")
# Insert sample rows with JSON data
rows = [
("Widget Pro", '{"specs":{"weight":2.5,"color":"blue"}}'),
("Gadget X", '{"specs":{"weight":0.8,"color":"red"}}'),
("Heavy Duty", '{"specs":{"weight":9.1,"color":"gray"}}'),
]
cursor.executemany(
"INSERT INTO dbo.ProductCatalog (Name, JsonData) VALUES (%(name)s, %(json)s)",
[{"name": n, "json": j} for n, j in rows]
)
conn.commit()
# Add a persisted computed column that extracts weight from JSON
cursor.execute("""
ALTER TABLE dbo.ProductCatalog
ADD ProductWeight AS CAST(JSON_VALUE(JsonData, '$.specs.weight') AS DECIMAL(5,2)) PERSISTED
""")
# Index the computed column for efficient range queries
cursor.execute("""
CREATE INDEX IX_ProductCatalog_Weight
ON dbo.ProductCatalog (ProductWeight)
""")
conn.commit()
Consulta usando columna computada indexada
Filtra directamente por la columna calculada. El motor de consultas utiliza el índice en lugar de escanear y analizar cada documento JSON.
cursor.execute("""
SELECT Name, ProductWeight
FROM dbo.ProductCatalog
WHERE ProductWeight > %(min_weight)s
ORDER BY ProductWeight
""", {"min_weight": 1.0})
for row in cursor:
print(f"{row.Name}: {row.ProductWeight} kg")
# Cleanup
cursor.execute("DROP TABLE dbo.ProductCatalog")
conn.commit()
Elige entre columnas relacionales y almacenamiento JSON
Utiliza columnas relacionales cuando los datos tienen un esquema fijo, necesitan integridad referencial, participan en JOINs o aparecen frecuentemente en cláusulas WHERE. Utiliza columnas JSON (nvarchar(max)) cuando los datos son escasos, varían entre filas o representan una configuración o metadatos flexibles.
Cuándo usar procesamiento JSON en el lado del servidor frente al del cliente
Utiliza funciones JSON de Microsoft SQL (JSON_VALUE, JSON_QUERY, OPENJSON) cuando necesites filtrar, indexar o agregar entre campos JSON sin tener que transferir cada fila al cliente. Esta elección es correcta cuando solo un subconjunto de filas coincide con tus criterios, o cuando quieres cálculos de índices de columnas en rutas JSON.
Utiliza procesamiento Python en el lado del cliente (json.loads()) cuando recuperes documentos completos y los proceses en lógica de aplicación. Este enfoque funciona bien cuando necesitas el documento completo y no filtras en campos JSON de la base de datos.
Flujos de trabajo de estilo documento
Cuando tu aplicación almacene y recupere documentos completos, usa serialización en Python y trata la columna JSON como almacenamiento opaco. Procesa y consulta los documentos en Python obteniendo y desserializando blobs JSON completos:
import json
# Create the settings table
cursor.execute("""
CREATE TABLE #Settings (
UserID INT PRIMARY KEY,
ConfigJson NVARCHAR(MAX)
)
""")
# Store a configuration document
config = {
"theme": "dark",
"notifications": {"email": True, "sms": False},
"custom_fields": {"department": "Engineering", "cost_center": "CC-100"}
}
cursor.execute(
"INSERT INTO #Settings (UserID, ConfigJson) VALUES (%(uid)s, %(cfg)s)",
{"uid": 1, "cfg": json.dumps(config)}
)
# Retrieve and process in Python
cursor.execute("SELECT ConfigJson FROM #Settings WHERE UserID = %(uid)s", {"uid": 1})
row = cursor.fetchone()
config = json.loads(row.ConfigJson)
print(config["notifications"]["email"]) # True
Consultas JSON en el lado del servidor
Usa funciones JSON de Microsoft SQL cuando necesites filtrar, indexar o agregar entre campos JSON sin tener que buscar cada fila. Este enfoque es más eficiente que cargar todas las filas en Python para filtrar la memoria:
-
JSON_VALUEextrae valores escalares y puede retrocalcular índices de columnas. -
JSON_QUERYextrae objetos y matrices. -
OPENJSONdesglosa JSON en filas para operaciones deJOINy agregación. -
JSON_MODIFYactualiza rutas específicas sin reescribir todo el documento.
# Filter by a JSON field server-side
cursor.execute("""
SELECT UserID, ConfigJson
FROM #Settings
WHERE JSON_VALUE(ConfigJson, '$.custom_fields.department') = %(dept)s
""", {"dept": "Engineering"})
Para rutas JSON consultadas frecuentemente, crea una columna calculada con un índice:
ALTER TABLE Settings
ADD Department AS JSON_VALUE(ConfigJson, '$.custom_fields.department');
CREATE INDEX IX_Settings_Department ON Settings(Department);
procedimientos recomendados
Aplica estas pautas para usar columnas JSON de forma fiable.
Validar JSON antes del almacenamiento
Valida JSON e identificadores de tabla/columna antes de almacenarlos para evitar ataques de inyección.
def store_json_safely(cursor, table: str, json_column: str, data: dict):
"""Store JSON with validation."""
# 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_]*$', json_column):
raise ValueError(f"Invalid column name: {json_column}")
json_str = json.dumps(data)
# Check if valid JSON in Microsoft SQL
cursor.execute("SELECT ISJSON(%(json)s)", {"json": json_str})
if cursor.fetchval() != 1:
raise ValueError("Invalid JSON")
cursor.execute(f"INSERT INTO {table} ({json_column}) VALUES (%(json)s)", {"json": json_str})
No uses JSON en exceso
Utiliza columnas JSON para datos flexibles o dispersos, como preferencias de usuario o campos personalizados. Utiliza columnas relacionales para:
- Datos consultados con frecuencia.
- Datos que requieren integridad referencial.
- Columnas utilizadas en cláusulas
WHERE.
Maneja correctamente a None/NULL
Maneja los campos JSON ausentes u opcionales insertando valores NULL para columnas que no tienen datos.
cursor.execute("""
CREATE TABLE #JsonOpt (
ProductID INT IDENTITY, Name NVARCHAR(100), JsonData NVARCHAR(MAX)
)
""")
cursor.execute("""
INSERT INTO #JsonOpt (Name, JsonData) VALUES (
'Widget Pro', '{"required_field":"value"}'
)
""")
cursor.execute("""
SELECT
Name,
JSON_VALUE(JsonData, '$.optional_field') AS OptionalValue
FROM #JsonOpt
""")
for row in cursor:
# JSON_VALUE returns NULL if path doesn't exist
value = row.OptionalValue or "default"
print(f"{row.Name}: {value}")