Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Драйвер go-mssqldb поддерживает вызов сохранённых процедур с входными параметрами, выходными параметрами и значениями статуса возврата. В этой статье рассматриваются распространённые закономерности.
Большинство фрагментов в этой статье предполагают, что обычная настройка database/sql уже выполнена, database/sql импортирован как sql, значение ctx доступно, а db инициализирован. Фрагменты включают блоки импорта только тогда, когда они вводят дополнительные пакеты, такие как context, fmt, log, osили github.com/microsoft/go-mssqldb. Когда блок кода намеренно продолжает тот же пример, используйте = для значений, которые уже были объявлены ранее в этом примере; := используйте для новых объявлений в отдельных фрагментах.
Вызов хранимой процедуры
Используйте ExecContext или QueryContext непосредственно с названием процедуры:
_, err := db.ExecContext(ctx, "dbo.uspGetEmployeeManagers",
sql.Named("BusinessEntityID", 6))
Для обычных вызовов процедур предпочтительно использовать имя процедуры с параметрами. Используйте явную строку EXECUTE ... только в том случае, если вам нужно встроить вызов в более крупный пакет Transact-SQL (T-SQL) или использовать синтаксис, который не поддерживается параметрами sql.Named.
Выходные параметры
Используйте sql.Named с sql.Out для получения значений выходных параметров:
import (
"context"
"database/sql"
"fmt"
"log"
)
ctx := context.Background()
var employeeCount int64
_, err := db.ExecContext(ctx, "dbo.GetEmployeeCount",
sql.Named("count", sql.Out{Dest: &employeeCount}))
if err != nil {
log.Fatal(err)
}
fmt.Println("Employee count:", employeeCount)
Соответствующая процедура T-SQL:
CREATE PROCEDURE dbo.GetEmployeeCount
@count INT OUTPUT
AS
BEGIN
SELECT @count = COUNT(*) FROM HumanResources.Employee;
END;
Параметры ввода/вывода
Для параметров, которые одновременно входные и выходные, задаём In: true на sql.Out структуре:
var result int64 = 10
_, err := db.ExecContext(ctx, "dbo.DoubleValue",
sql.Named("value", sql.Out{Dest: &result, In: true}))
if err != nil {
log.Fatal(err)
}
fmt.Println("Doubled:", result)
Статус возврата
Используйте mssql.ReturnStatus для захвата целочисленного возвращаемого значения сохранённой процедуры. В этом примере используется процедура AdventureWorks dbo.uspGetEmployeeManagers :
import (
"database/sql"
"fmt"
"log"
mssql "github.com/microsoft/go-mssqldb"
)
var returnStatus mssql.ReturnStatus
rows, err := db.QueryContext(ctx, "dbo.uspGetEmployeeManagers",
sql.Named("BusinessEntityID", 6),
&returnStatus)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
fmt.Println("Return status:", returnStatus)
ReturnStatus работает с ExecContext и QueryContext, но не с QueryRowContext.
Вы можете комбинировать ReturnStatus с параметрами процедуры. Этот пример продолжает предыдущий пример, поэтому он повторно использует mssql импорт, показанный ранее, и применяет процедуру AdventureWorks dbo.uspGetEmployeeManagers :
var returnStatus mssql.ReturnStatus
rows, err := db.QueryContext(ctx, "dbo.uspGetEmployeeManagers",
sql.Named("BusinessEntityID", 6),
&returnStatus)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
Наборы результатов из хранящихся процедур
Если сохранённая процедура возвращает наборы результатов, используйте QueryContext:
rows, err := db.QueryContext(ctx, "dbo.uspGetEmployeeManagers", sql.Named("BusinessEntityID", 6))
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var reportingLevel int
var employeeID int
var employeeFirstName string
var employeeLastName string
var organizationNode string
var managerFirstName string
var managerLastName string
if err := rows.Scan(&reportingLevel, &employeeID, &employeeFirstName, &employeeLastName, &organizationNode, &managerFirstName, &managerLastName); err != nil {
log.Fatal(err)
}
fmt.Printf("Level %d: %d %s %s (manager: %s %s)\n",
reportingLevel, employeeID, employeeFirstName, employeeLastName, managerFirstName, managerLastName)
_ = organizationNode
}
Для процедур, возвращающих несколько наборов результатов, используйте rows.NextResultSet(). Для получения дополнительной информации смотрите в разделе Запросы и выписки.
Прочитайте все строки перед использованием параметров выхода или статуса возврата
SQL Server отправляет параметры вывода и возвращает значения статуса после завершения наборов результатов. Если вы прочитаете выходную переменную до того, как поглотите все строки, значение всё равно может быть неполным.
Этот пример продолжает предыдущий и повторно использует mssql импорт, показанный ранее.
Он не печатает данные строк. Цикл потребляет только набор результатов, поэтому returnStatus становится доступен после завершения запроса.
var returnStatus mssql.ReturnStatus
rows, err := db.QueryContext(ctx, "dbo.uspGetEmployeeManagers",
sql.Named("BusinessEntityID", 6),
&returnStatus)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var reportingLevel int
var employeeID int
var employeeFirstName string
var employeeLastName string
var organizationNode string
var managerFirstName string
var managerLastName string
if err := rows.Scan(&reportingLevel, &employeeID, &employeeFirstName, &employeeLastName, &organizationNode, &managerFirstName, &managerLastName); err != nil {
log.Fatal(err)
}
_ = organizationNode
_ = managerFirstName
_ = managerLastName
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
fmt.Println("Return status:", returnStatus)
Используйте ExecContext тогда, когда процедура не возвращает строки. Если процедура возвращает строки, дождитесь, пока все строки и наборы результатов не будут обработаны, прежде чем считывать выходные параметры или статус возврата.
Временные таблицы и хранящиеся процедуры
Когда вы создаёте временную таблицу и затем задаваете к ней запросы в отдельных вызовах, операции могут выполняться на разных соединениях из пула. Поскольку временные таблицы ограничены одним соединением, второй вызов может не видеть таблицу.
Для работы с временными таблицами используйте подход с единым соединением:
conn, err := db.Conn(ctx)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
_, err = conn.ExecContext(ctx, "CREATE TABLE #TempItems (Id INT, Name NVARCHAR(50))")
if err != nil {
log.Fatal(err)
}
_, err = conn.ExecContext(ctx, "INSERT INTO #TempItems VALUES (1, N'Item A')")
if err != nil {
log.Fatal(err)
}
rows, err := conn.QueryContext(ctx, "SELECT * FROM #TempItems")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
В качестве альтернативы оберните операции в транзакцию, которая автоматически закрепляет их к одному и тому же соединению.
Перехват сообщений PRINT и RAISERROR
Операторы SQL Server PRINT и RAISERROR с степенью серьёзности 0-10 создают информационные сообщения, которые не возвращаются в виде ошибок Go. Чтобы зафиксировать эти сообщения, включите параметр соединения log с флагом 2 (сообщения):
sqlserver://<user>:<password>@<server>?database=AdventureWorks2025&log=2
С включенным логированием драйвер записывает PRINT выводные и RAISERROR низкоинтенсивные сообщения в стандартный log пакет Go. Чтобы зафиксировать их программно, перед открытием соединения установите пользовательский логгер:
import (
"database/sql"
"log"
"os"
mssql "github.com/microsoft/go-mssqldb"
)
// Direct driver messages to a custom logger.
mssql.SetLogger(log.New(os.Stdout, "mssql: ", log.LstdFlags))
db, err := sql.Open("sqlserver",
"sqlserver://<user>:<password>@<server>?database=AdventureWorks2025&log=2")
Затем любая сохранённая процедура, которая использует PRINT или RAISERROR(..., 0, 1) отправляет свои сообщения в ваш логер.
Note
RAISERROR при уровне серьёзности 11 и выше приводит к ошибке Go, которую можно обработать с помощью стандартной проверки ошибок. Параметр log требуется захватывать только для сообщений с уровнем серьёзности 0–10.
Для получения дополнительной информации о логировании флагов и программном захвате журналов см. раздел «Логирование и диагностика».