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.
SQL Server tiene soporte integrado para datos JSON y XML. Este artículo muestra cómo usar el go-mssqldb controlador para consultar, insertar y transformar datos JSON y XML entre estructuras Go y SQL Server.
Los ejemplos de este artículo se aplican a la base de datos de ejemplo AdventureWorks2025 . Ejemplos orientados a lectura consultan objetos integrados como Sales.vSalesPerson y Sales.SalesOrderHeader. Los ejemplos orientados a escritura utilizan OPENJSON XML .nodes() para insertar filas en HumanResources.Department.
La mayoría de los fragmentos suponen que ctx y db ya se han inicializado. Puedes usar este patrón de configuración:
import (
"context"
"database/sql"
"log"
"time"
_ "github.com/microsoft/go-mssqldb"
)
db, err := sql.Open("sqlserver", "sqlserver://<user>:<password>@<server>?database=AdventureWorks2025&encrypt=true&TrustServerCertificate=true")
if err != nil {
log.Fatal(err)
}
defer db.Close()
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
Datos JSON
Consulta los resultados como JSON con JSON FOR
Úsase FOR JSON PATH para devolver resultados de consultas como cadena JSON:
var jsonResult string
err := db.QueryRowContext(ctx,
"SELECT BusinessEntityID AS Id, FirstName + ' ' + LastName AS Name, CountryRegionName AS Department FROM Sales.vSalesPerson WHERE CountryRegionName = @dept FOR JSON PATH",
sql.Named("dept", "United States")).Scan(&jsonResult)
if err != nil {
log.Fatal(err)
}
fmt.Println(jsonResult)
Note
SQL Server puede devolver la salida FOR JSON en varias filas para cargas de mayor tamaño. Si necesitas soportar resultados más grandes de forma fiable, lee todas las filas devueltas y concatenalas. Consulta Manejar resultados JSON grandes.
Manejar resultados JSON grandes
Cuando SQL Server devuelve la salida JSON en varias filas, concatena los fragmentos antes de desorganizar o imprimir el resultado:
import "strings"
func queryJSON(ctx context.Context, db *sql.DB, query string, args ...any) (string, error) {
rows, err := db.QueryContext(ctx, query, args...)
if err != nil {
return "", err
}
defer rows.Close()
var sb strings.Builder
for rows.Next() {
var chunk string
if err := rows.Scan(&chunk); err != nil {
return "", err
}
sb.WriteString(chunk)
}
if err := rows.Err(); err != nil {
return "", err
}
return sb.String(), nil
}
Uso:
jsonStr, err := queryJSON(ctx, db,
"SELECT BusinessEntityID AS Id, FirstName + ' ' + LastName AS Name, CountryRegionName AS Department FROM Sales.vSalesPerson FOR JSON PATH")
if err != nil {
log.Fatal(err)
}
Deserializar resultados JSON en estructuras de Go
Combina FOR JSON con encoding/json para deserializar los resultados de SQL Server directamente en tipos Go:
import "encoding/json"
type Employee struct {
ID int `json:"Id"`
Name string `json:"Name"`
Department string `json:"Department"`
}
func getEmployees(ctx context.Context, db *sql.DB, dept string) ([]Employee, error) {
var jsonStr string
err := db.QueryRowContext(ctx,
"SELECT BusinessEntityID AS Id, FirstName + ' ' + LastName AS Name, CountryRegionName AS Department FROM Sales.vSalesPerson WHERE CountryRegionName = @dept FOR JSON PATH",
sql.Named("dept", dept)).Scan(&jsonStr)
if err != nil {
return nil, err
}
var employees []Employee
if err := json.Unmarshal([]byte(jsonStr), &employees); err != nil {
return nil, err
}
return employees, nil
}
JSON anidado con FOR JSON PATH
Crea estructuras JSON anidadas usando notación de puntos en alias de columna:
var jsonResult string
err := db.QueryRowContext(ctx, `
SELECT
soh.SalesOrderID AS [Id],
soh.OrderDate AS [OrderDate],
CONCAT(pp.FirstName, ' ', pp.LastName) AS [Customer.Name],
ea.EmailAddress AS [Customer.Email],
soh.TotalDue AS [Total]
FROM Sales.SalesOrderHeader AS soh
JOIN Sales.Customer AS c ON soh.CustomerID = c.CustomerID
LEFT JOIN Person.Person AS pp ON c.PersonID = pp.BusinessEntityID
LEFT JOIN Person.EmailAddress AS ea ON pp.BusinessEntityID = ea.BusinessEntityID
WHERE soh.SalesOrderID = @id
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER`,
sql.Named("id", 43659)).Scan(&jsonResult)
Resultado:
{"Id":43659,"OrderDate":"2022-05-30T00:00:00","Customer":{"Name":"James Hendergart","Email":"james9@adventure-works.com"},"Total":23153.2339}
Parse JSON en SQL Server con OPENJSON
Úsase OPENJSON para fragmentar un array JSON en filas del lado del servidor:
jsonData := `[
{"Name": "Alice", "Department": "Engineering"},
{"Name": "Bob", "Department": "Marketing"}
]`
rows, err := db.QueryContext(ctx, `
SELECT Name, Department
FROM OPENJSON(@json)
WITH (
Name NVARCHAR(100) '$.Name',
Department NVARCHAR(100) '$.Department'
)`, sql.Named("json", jsonData))
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var name, dept string
if err := rows.Scan(&name, &dept); err != nil {
log.Fatal(err)
}
fmt.Printf("%s - %s\n", name, dept)
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
Insertar datos JSON como parámetro
Envía una estructura Go como cadena JSON a un procedimiento almacenado o OPENJSON:
type OrderItem struct {
ProductID int `json:"productId"`
Quantity int `json:"quantity"`
Price float64 `json:"price"`
}
func sendJSONPayload(ctx context.Context, db *sql.DB, metadata map[string]any) error {
jsonBytes, err := json.Marshal(metadata)
if err != nil {
return err
}
// Pass JSON to a stored procedure for server-side processing.
_, err = db.ExecContext(ctx,
"EXECUTE dbo.ProcessDepartmentMetadata @payload",
sql.Named("payload", string(jsonBytes)))
return err
}
Uso:
metadata := map[string]any{
"source": "docs-sample",
"batchId": "b-20260709-01",
"departments": []map[string]any{
{"name": "Data Science", "groupName": "Research and Development"},
{"name": "Cloud Ops", "groupName": "Information Technology"},
},
}
if err := sendJSONPayload(ctx, db, metadata); err != nil {
log.Fatal(err)
}
Inserción por lotes desde JSON con OPENJSON
Inserta varias filas de un array JSON en una sola sentencia:
func bulkInsertFromJSON(ctx context.Context, db *sql.DB, jsonData string) (int64, error) {
result, err := db.ExecContext(ctx, `
INSERT INTO HumanResources.Department (Name, GroupName)
SELECT Name, GroupName
FROM OPENJSON(@json)
WITH (
Name NVARCHAR(50) '$.name',
GroupName NVARCHAR(50) '$.groupName'
)`, sql.Named("json", jsonData))
if err != nil {
return 0, err
}
return result.RowsAffected()
}
Uso:
jsonData := `[
{"name": "Data Science", "groupName": "Research and Development"},
{"name": "Cloud Ops", "groupName": "Information Technology"},
{"name": "Developer Relations", "groupName": "Sales and Marketing"}
]`
rowsInserted, err := bulkInsertFromJSON(ctx, db, jsonData)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Inserted %d rows\n", rowsInserted)
JSON_VALUE y JSON_QUERY
Extrae valores de las columnas JSON sin transferir todo el documento JSON:
// Extract a scalar value.
var productName string
err := db.QueryRowContext(ctx, `
SELECT Name
FROM Production.Product
WHERE ProductID = @id`,
sql.Named("id", 1)).Scan(&productName)
if err != nil {
log.Fatal(err)
}
fmt.Println(productName)
// Extract a JSON array from a FOR JSON subquery.
var itemsJSON string
err = db.QueryRowContext(ctx, `
SELECT (
SELECT ProductID, Name
FROM Production.Product
WHERE ProductSubcategoryID = @subId
FOR JSON PATH
) AS Items`,
sql.Named("subId", 1)).Scan(&itemsJSON)
if err != nil {
log.Fatal(err)
}
fmt.Println(itemsJSON)
Tip
JSON_VALUE devuelve un valor escalar (cadena, número).
JSON_QUERY devuelve un objeto o arreglo. Utiliza la función correcta para el tipo de dato que necesitas.
JSON calculaba columnas e índices
Para propiedades JSON consultadas con frecuencia, crea columnas calculadas con índices para un mejor rendimiento:
-- Demo table with JSON-only rows.
IF OBJECT_ID('dbo.ProductJsonDemo', 'U') IS NOT NULL
DROP TABLE dbo.ProductJsonDemo;
CREATE TABLE dbo.ProductJsonDemo (
ProductDescriptionID INT IDENTITY(1,1) PRIMARY KEY,
Metadata NVARCHAR(MAX) NOT NULL,
CONSTRAINT CK_ProductJsonDemo_Metadata_IsJson CHECK (ISJSON(Metadata) = 1),
-- Cast to a bounded length so the index key stays under SQL Server limits.
LocaleName AS CAST(JSON_VALUE(Metadata, '$.locale') AS NVARCHAR(10)) PERSISTED
);
INSERT INTO dbo.ProductJsonDemo (Metadata)
VALUES
(N'{"locale":"en","title":"Road helmet"}'),
(N'{"locale":"fr","title":"Casque de route"}');
CREATE INDEX IX_ProductJsonDemo_LocaleName ON dbo.ProductJsonDemo(LocaleName);
Luego consulta la columna calculada directamente desde Go:
rows, err := db.QueryContext(ctx,
"SELECT ProductDescriptionID, LocaleName FROM dbo.ProductJsonDemo WHERE LocaleName = @name",
sql.Named("name", "en"))
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var id int
var localeName string
if err := rows.Scan(&id, &localeName); err != nil {
log.Fatal(err)
}
fmt.Printf("%d %s\n", id, localeName)
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
Datos XML
Consulta los resultados como XML con FOR XML
Usar FOR XML PATH para devolver los resultados de la consulta como XML:
var xmlResult string
err := db.QueryRowContext(ctx, `
SELECT BusinessEntityID AS [@id], FirstName + ' ' + LastName AS Name, CountryRegionName AS Department
FROM Sales.vSalesPerson
WHERE CountryRegionName = @dept
FOR XML PATH('Employee'), ROOT('Employees')`,
sql.Named("dept", "United States")).Scan(&xmlResult)
if err != nil {
log.Fatal(err)
}
fmt.Println(xmlResult)
Resultado:
<Employees>
<Employee id="274"><Name>Stephen Jiang</Name><Department>United States</Department></Employee>
<Employee id="275"><Name>Michael Blythe</Name><Department>United States</Department></Employee>
<Employee id="276"><Name>Linda Mitchell</Name><Department>United States</Department></Employee>
<Employee id="277"><Name>Jillian Carson</Name><Department>United States</Department></Employee>
<Employee id="279"><Name>Tsvi Reiter</Name><Department>United States</Department></Employee>
<Employee id="280"><Name>Pamela Ansman-Wolfe</Name><Department>United States</Department></Employee>
<Employee id="281"><Name>Shu Ito</Name><Department>United States</Department></Employee>
<Employee id="283"><Name>David Campbell</Name><Department>United States</Department></Employee>
<Employee id="284"><Name>Tete Mensa-Annan</Name><Department>United States</Department></Employee>
<Employee id="285"><Name>Syed Abbas</Name><Department>United States</Department></Employee>
<Employee id="287"><Name>Amy Alberts</Name><Department>United States</Department></Employee>
</Employees>
Gestionar grandes resultados XML
Al igual que en JSON, los grandes resultados XML se distribuyen en filas.
func queryXML(ctx context.Context, db *sql.DB, query string, args ...any) (string, error) {
rows, err := db.QueryContext(ctx, query, args...)
if err != nil {
return "", err
}
defer rows.Close()
var sb strings.Builder
for rows.Next() {
var chunk string
if err := rows.Scan(&chunk); err != nil {
return "", err
}
sb.WriteString(chunk)
}
if err := rows.Err(); err != nil {
return "", err
}
return sb.String(), nil
}
Resultados del análisis de XML en Go
Usa el paquete encoding/xml para deserializar los resultados XML.
import "encoding/xml"
type EmployeeList struct {
XMLName xml.Name `xml:"Employees"`
Employees []Employee `xml:"Employee"`
}
type Employee struct {
ID int `xml:"id,attr"`
Name string `xml:"Name"`
Department string `xml:"Department"`
}
func getEmployeesXML(ctx context.Context, db *sql.DB, dept string) (*EmployeeList, error) {
xmlStr, err := queryXML(ctx, db, `
SELECT BusinessEntityID AS [@id], FirstName + ' ' + LastName AS Name, CountryRegionName AS Department
FROM Sales.vSalesPerson
WHERE CountryRegionName = @dept
FOR XML PATH('Employee'), ROOT('Employees')`,
sql.Named("dept", dept))
if err != nil {
return nil, err
}
var result EmployeeList
if err := xml.Unmarshal([]byte(xmlStr), &result); err != nil {
return nil, err
}
return &result, nil
}
Pasar parámetros XML a SQL Server
Envía un documento XML a un procedimiento almacenado o consulta:
xmlData := `<Employees>
<Employee><Name>Alice</Name><Department>Engineering</Department></Employee>
<Employee><Name>Bob</Name><Department>Marketing</Department></Employee>
</Employees>`
rows, err := db.QueryContext(ctx, `
DECLARE @xmlDoc XML = CAST(@xml AS XML);
SELECT
e.value('(Name)[1]', 'NVARCHAR(100)') AS Name,
e.value('(Department)[1]', 'NVARCHAR(100)') AS Department
FROM @xmlDoc.nodes('/Employees/Employee') AS t(e)`,
sql.Named("xml", xmlData))
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var name, dept string
if err := rows.Scan(&name, &dept); err != nil {
log.Fatal(err)
}
fmt.Printf("%s - %s\n", name, dept)
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
Note
El @xml parámetro se envía como nvarchar(max ) por defecto. El .nodes() método requiere el tipo de dato xml , por lo que convierte explícitamente el parámetro con CAST(@xml AS XML).
Insertar filas desde XML
Utiliza el método .nodes() con una instrucción INSERT...SELECT para dividir XML en filas de tabla.
func bulkInsertFromXML(ctx context.Context, db *sql.DB, xmlData string) (int64, error) {
result, err := db.ExecContext(ctx, `
DECLARE @xmlDoc XML = CAST(@xml AS XML);
INSERT INTO HumanResources.Department (Name, GroupName)
SELECT
e.value('(Name)[1]', 'NVARCHAR(50)'),
e.value('(GroupName)[1]', 'NVARCHAR(50)')
FROM @xmlDoc.nodes('/Departments/Department') AS t(e)`,
sql.Named("xml", xmlData))
if err != nil {
return 0, err
}
return result.RowsAffected()
}
Elige entre JSON y XML
| Consideración | JSON | XML |
|---|---|---|
| Soporte del ecosistema Go | Estándar encoding/json. Etiquetas de estructura para mapeo. |
Estándar encoding/xml. Más etiquetas de estructura verbosas. |
| Soporte para SQL Server |
OPENJSON, JSON_VALUE, JSON_QUERY, FOR JSON (SQL Server 2016 y versiones posteriores) |
nodes(), value(), query(), FOR XML (todas las versiones) |
| Performance | Generalmente un análisis más rápido. Formato de cable menos extenso. | Soporta esquemas y validación. Más verboso. |
| Validación de esquema | No hay validación de esquema integrada en SQL Server. | Soporta colecciones de esquemas XML para validación en el lado del servidor. |
| Indexación | Calculó columnas con JSON_VALUE + índice. | Índices XML (primario y secundario). |
| Modelado de datos | Matrices y objetos anidados. Se adaptan de forma natural a los slices y mapas de Go. | Documentos jerárquicos con atributos y espacios de nombres. |
Tip
Para desarrollos nuevos, JSON suele ser la mejor opción. Requiere menos sobrecarga de análisis, produce cargas más pequeñas y se adapta de forma natural a las estructuras de Go. Usa XML cuando necesites validación de esquema o al integrar con sistemas que requieren XML.