驅動程式 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))
一般程序呼叫時,偏好使用程序名稱加上參數。 只有當你需要將呼叫嵌入較大的 Transact-SQL(T-SQL)批次或使用非EXECUTE ...參數語法時,才使用明確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 陳述式,以及嚴重度為 0-10 的 RAISERROR,會產生不會以 Go 錯誤形式傳回的資訊性訊息。 要擷取這些訊息,請啟用 log 帶有標誌 2 (訊息)的連線參數:
sqlserver://<user>:<password>@<server>?database=AdventureWorks2025&log=2
啟用日誌時,驅動程式會將 PRINT 輸出和低嚴重性 RAISERROR 訊息寫入 Go 的標準 log 套件。 若要程式化擷取,請在開啟連線前設定自訂記錄器:
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
嚴重度為 11 或以上的 RAISERROR 會產生 Go 錯誤,您可以透過一般的錯誤檢查來處理。 只有 0-10 的嚴重性訊息才需要 log 該參數來捕捉。
欲了解更多關於日誌旗標與程式式日誌擷取的資訊,請參閱 日誌與診斷。