Gespeicherte Prozeduren mit go-mssqldb

Der Treiber go-mssqldb unterstützt das Aufrufen gespeicherter Prozeduren mit Eingabeparametern, Ausgabeparametern und Rückgabestatuswerten. Dieser Artikel behandelt die häufigsten Muster.

Die meisten Codebeispiele in diesem Artikel setzen voraus, dass das übliche database/sql-Setup bereits vorhanden ist, wobei database/sql als sql importiert wurde, ein ctx-Wert verfügbar ist und db initialisiert wurde. Snippets enthalten Importblöcke nur, wenn sie zusätzliche Pakete wie context, fmt, , log, osoder github.com/microsoft/go-mssqldbeinführen. Wenn ein Codeblock absichtlich dasselbe Beispiel fortsetzt, verwenden = Sie für Werte, die bereits zuvor in diesem Beispiel deklariert wurden; verwenden := Sie für frische Deklarationen in eigenständigen Snippets.

Aufrufen einer gespeicherten Prozedur

Verwenden Sie ExecContext oder QueryContext direkt mit dem Verfahrensnamen:

_, err := db.ExecContext(ctx, "dbo.uspGetEmployeeManagers",
    sql.Named("BusinessEntityID", 6))

Bevorzuge den Prozedurnamen plus Parameter für gewöhnliche Prozeduraufrufe. Verwenden Sie eine explizite EXECUTE ... Zeichenkette nur, wenn Sie den Aufruf in einen größeren Transact-SQL (T-SQL) Batch einbetten oder eine Syntax verwenden müssen, die nicht durch sql.Named Parameter dargestellt wird.

Ausgabeparameter

Verwenden Sie sql.Named mit sql.Out, um die Werte von Ausgabeparametern zu empfangen:

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)

Das entsprechende T-SQL-Verfahren:

CREATE PROCEDURE dbo.GetEmployeeCount
    @count INT OUTPUT
AS
BEGIN
    SELECT @count = COUNT(*) FROM HumanResources.Employee;
END;

Eingabe-/Ausgabeparameter

Für Parameter, die sowohl Eingabe als auch Ausgabe sind, legen Sie In: true in der sql.Out-Struktur fest:

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)

Status zurückgeben

Verwenden Sie, mssql.ReturnStatus um den ganzzahligen Rückgabewert einer gespeicherten Prozedur abzufangen. Dieses Beispiel verwendet das AdventureWorks-Verfahren 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 funktioniert mit ExecContext und QueryContext, aber nicht mit QueryRowContext.

Du kannst sie mit Verfahrensparametern kombinieren ReturnStatus . Dieses Beispiel setzt das vorherige Beispiel fort, sodass der zuvor gezeigte Import mssql wiederverwendet wird und das AdventureWorks-Verfahren dbo.uspGetEmployeeManagers verwendet wird:

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()

Ergebnismengen aus gespeicherten Prozeduren

Wenn die gespeicherte Prozedur Ergebnismengen zurückgibt, verwenden Sie 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
}

Für Prozeduren, die mehrere Ergebnismengen zurückgeben, verwenden Sie rows.NextResultSet(). Weitere Informationen finden Sie unter Anfragen und Aussagen.

Lesen Sie alle Zeilen, bevor Sie Ausgabeparameter oder Rückgabestatus verwenden

SQL Server sendet Ausgabeparameter und Rückgabestatuswerte erst, nachdem die Ergebnismengen abgeschlossen sind. Wenn du eine Ausgabevariable liest, bevor du alle Zeilen verbrauchst, kann der Wert trotzdem unvollständig sein.

Dieses Beispiel setzt das vorherige fort und verwendet den zuvor gezeigten mssql Import wieder.

Die Zeilendaten werden nicht gedruckt. Die Schleife verbraucht nur die Ergebnismenge und returnStatus ist nach Abschluss der Abfrage verfügbar.

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)

Verwenden Sie ExecContext, wenn die Prozedur keine Zeilen zurückgibt. Wenn das Verfahren Zeilen zurückgibt, warten Sie, bis alle Zeilen und Ergebnismengen vollständig verarbeitet wurden, bevor Sie die Ausgabeparameter oder den Rückgabestatus lesen.

Temporäre Tabellen und gespeicherte Prozeduren

Wenn du eine temporäre Tabelle erstellst und sie dann in separaten Aufrufen abfraibst, können die Operationen auf verschiedenen Verbindungen aus dem Pool ausgeführt werden. Da temporäre Tabellen auf eine einzelne Verbindung beschränkt sind, sieht der zweite Anruf möglicherweise die Tabelle nicht.

Um mit temporären Tabellen zu arbeiten, verwenden Sie einen Single-Connection-Ansatz:

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()

Alternativ kann man die Operationen in eine Transaktion einwickeln, die sie automatisch an dieselbe Verbindung anheftet.

Erfassen Sie PRINT- und RAISERROR-Nachrichten

SQL Server-Anweisungen PRINT und RAISERROR mit Schweregrad 0-10 erzeugen Informationsmeldungen, die nicht als Go-Fehler zurückgegeben werden. Um diese Nachrichten zu erfassen, aktivieren Sie den log Verbindungsparameter mit Flag 2 (Nachrichten):

sqlserver://<user>:<password>@<server>?database=AdventureWorks2025&log=2

Bei aktiviertem Logging schreibt der Treiber PRINT Ausgabe und Meldungen mit niedrigem Schweregrad RAISERROR in Gos Standardpaket log. Um sie programmatisch zu erfassen, richten Sie vor Öffnen der Verbindung einen benutzerdefinierten Logger ein:

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")

Dann sendet jede gespeicherte Prozedur, die PRINT oder RAISERROR(..., 0, 1) verwendet, ihre Nachrichten an deinen Logger.

Hinweis

RAISERROR mit Fehlergrad 11 oder höher erzeugt einen Fehler in Go, den du mit der normalen Fehlerprüfung behandeln kannst. Nur Nachrichten mit einem Schweregrad von 0 bis 10 erfordern den Parameter log für die Erfassung.

Weitere Informationen zu Logging-Flags und programmatischer Logermittlung finden Sie unter Logging und Diagnostik.