Ескертпе
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Жүйеге кіруді немесе каталогтарды өзгертуді байқап көруге болады.
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Каталогтарды өзгертуді байқап көруге болады.
SQL Server поддерживает встроенную поддержку JSON и XML-данных. В этой статье показано, как использовать драйвер go-mssqldb для запроса, вставки и трансформации данных JSON и XML между структурами Go и SQL Server.
Примеры в этой статье выполняются в образце базы данных AdventureWorks2025. Примеры, ориентированные на чтение, запрашивают встроенные объекты, такие Sales.vSalesPerson как и Sales.SalesOrderHeader. Примеры, ориентированные на запись, используют OPENJSON и XML .nodes() для вставки строк в HumanResources.Department.
Большинство фрагментов предполагают, что ctx и db уже инициализированы. Вы можете использовать этот шаблон настройки:
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()
Данные JSON
Результаты запроса в формате JSON с FOR JSON
Используйте FOR JSON PATH для возврата результатов запроса в виде 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)
Замечание
SQL Server может возвращать вывод FOR JSON в нескольких строках для больших объёмов данных. Если нужно надёжно поддерживать более крупные результаты, читайте все возвращённые строки и объединяйте их. См. Обработка больших результатов JSON.
Обработка больших результатов JSON
Если SQL Server возвращает вывод в формате JSON в нескольких строках, объедините фрагменты перед десериализацией или выводом результата:
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
}
Использование:
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)
}
Десериализация результатов JSON в структуры Go
Используйте FOR JSON вместе с encoding/json, чтобы десериализовать результаты SQL Server напрямую в типы 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 с использованием FOR JSON PATH
Создайте вложенные JSON-структуры с помощью точечной нотации в псевдонимах столбцов:
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)
Результат:
{"Id":43659,"OrderDate":"2022-05-30T00:00:00","Customer":{"Name":"James Hendergart","Email":"james9@adventure-works.com"},"Total":23153.2339}
Парс JSON в SQL Server с OPENJSON
Используйте OPENJSON, чтобы на стороне сервера преобразовать JSON-массив в строки:
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)
}
Вставьте данные JSON в качестве параметра
Отправьте структуру Go в виде строки JSON в сохранённую процедуру или 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
}
Использование:
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)
}
Пакетная вставка из JSON с помощью OPENJSON
Вставьте несколько строк из массива JSON в один оператор:
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()
}
Использование:
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 и JSON_QUERY
Извлекайте значения из столбцов JSON, не перенося весь 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 возвращает скалярное значение (строка, число).
JSON_QUERY возвращает объект или массивы. Используйте правильную функцию для нужного типа данных.
JSON вычислял столбцы и индексы
Для часто запрашиваемых свойств JSON создайте вычисленные столбцы с индексами для повышения производительности:
-- 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);
Затем запросите вычисленный столбец напрямую из 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)
}
XML-данные
Результаты запроса в формате XML с FOR XML
Используйте FOR XML PATH для возврата результатов запросов в формате 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)
Результат:
<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>
Обработка больших результатов XML
Как и в JSON, крупные XML-результаты разбиваются по строкам.
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
}
Разбор результатов XML в Go
Используйте пакет encoding/xml для десериализации результатов в формате 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
}
Передайте параметры XML на SQL Server
Отправьте XML-документ в хранимую процедуру или запрос:
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)
}
Замечание
Параметр @xml по умолчанию отправляется как nvarchar(max). Метод .nodes() требует тип данных xml, поэтому явно приведите параметр с помощью CAST(@xml AS XML).
Вставка строк из XML
Используйте метод .nodes() вместе с оператором INSERT...SELECT для преобразования XML в строки таблицы.
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()
}
Выберите между JSON и XML
| Соображение | JSON | XML |
|---|---|---|
| Поддержка экосистемы Go | Стандарт encoding/json. Теги структуры для сопоставления. |
Стандарт encoding/xml. Более подробные структурные теги. |
| Поддержка SQL Server |
OPENJSON, JSON_VALUE, JSON_QUERY, FOR JSON (SQL Server 2016 и более поздние версии) |
nodes(), value(), query(), FOR XML (все версии) |
| Performance | В целом парсинг выполняется быстрее. Менее многословный формат проводов. | Поддерживает схемы и валидацию. Более многословно. |
| Проверка схемы | Нет встроенной проверки схемы в SQL Server. | Поддерживает коллекции схем XML для серверной валидации. |
| Индексирование | Вычисленные столбцы с индексом JSON_VALUE + | XML-индексы (первичные и вторичные). |
| Моделирование данных | Массивы и вложенные объекты. Хорошо подходит для срезов и отображений в Go. | Иерархические документы с атрибутами и пространствами имён. |
Tip
Для новых разработок JSON обычно лучший выбор. Это требует меньших накладных расходов на парсинг, обеспечивает меньший объём передаваемых данных и естественным образом сопоставляется со структурами Go. Используйте XML, когда нужна проверка схемы или при интеграции с системами, требующими XML.