Procedure memorizzate con go-mssqldb

Il go-mssqldb driver supporta la chiamata di procedure memorizzate con parametri di input, parametri di output e valori di stato di ritorno. Questo articolo tratta i modelli più comuni.

La maggior parte dei frammenti di codice in questo articolo presuppone che la configurazione standard database/sql sia già presente, con database/sql importato come sql, un valore ctx disponibile e db inizializzato. Gli snippet includono blocchi di importazione solo quando introducono pacchetti aggiuntivi come context, fmt, log, os, o github.com/microsoft/go-mssqldb. Quando un blocco di codice continua intenzionalmente lo stesso esempio, usa = per valori già dichiarati in precedenza in quell'esempio; usa := per dichiarazioni nuove in snippet standalone.

Chiamare una procedura memorizzata

Usa ExecContext o QueryContext direttamente con il nome della procedura:

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

Preferisci il nome della procedura più i parametri per le chiamate ordinarie di procedure. Usa una stringa esplicita EXECUTE ... solo quando devi incorporare la chiamata in un batch più grande di Transact-SQL (T-SQL) oppure usa una sintassi che non è rappresentata da sql.Named parametri.

Parametri di output

Usa sql.Named con sql.Out per ricevere i valori dei parametri di uscita:

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)

La corrispondente procedura T-SQL:

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

Parametri di input/output

Per parametri che sono sia input che output, imposta In: true sulla sql.Out struct:

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)

Stato del reso

Usare mssql.ReturnStatus per catturare il valore di ritorno intero di una procedura memorizzata. Questo esempio utilizza la procedura 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 funziona con ExecContext e QueryContext, ma non con QueryRowContext.

Puoi combinare ReturnStatus con i parametri della procedura. Questo esempio continua quello precedente, quindi riutilizza l'importazione mssql mostrata prima e utilizza la procedura 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()

Set di risultati dalle stored procedure

Se la procedura memorizzata restituisce insiemi di risultati, usa 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
}

Per procedure che restituiscono più set di risultati, si usa rows.NextResultSet(). Per ulteriori informazioni, vedi Query e dichiarazioni.

Leggi tutte le righe prima di utilizzare i parametri di output o lo stato di ritorno

SQL Server invia parametri di output e valori di stato di ritorno dopo la fine dei set di risultati. Se leggi una variabile di output prima di consumare tutte le righe, il valore può comunque essere incompleto.

Questo esempio continua quello precedente e riutilizza l'importazione mssql mostrata prima.

Non stampa i dati della riga. Il ciclo consuma solo il set dei risultati, quindi returnStatus è disponibile dopo la fine della query.

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)

Usalo ExecContext quando la procedura non restituisce righe. Se la procedura restituisce righe, attendi che tutte le righe e i set di risultati siano consumati prima di leggere i parametri di output o lo stato di restituzione.

Tabelle temporanee e procedure memorizzate

Quando crei una tabella temporanea e poi la interroghi in chiamate separate, le operazioni possono essere eseguite su connessioni diverse dal pool. Poiché le tabelle temporanee sono limitate a una singola connessione, la seconda chiamata potrebbe non vedere la tabella.

Per lavorare con tabelle temporanee, si utilizza un approccio a connessione singola:

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

In alternativa, racchiudi le operazioni in una transazione, che le vincola automaticamente alla stessa connessione.

Cattura i messaggi di errore PRINT e RAISERROR

Le istruzioni SQL Server PRINT e RAISERROR con gravità 0-10 producono messaggi informativi che non vengono restituiti come errori Go. Per catturare questi messaggi, abilita il log parametro di connessione con il flag 2 (messaggi):

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

Con la registrazione abilitata, il driver scrive l'output e i messaggi RAISERROR di bassa gravità nel pacchetto standard log di Go. Per catturarli programmaticamente, imposta un logger personalizzato prima di aprire la connessione:

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

Poi qualsiasi procedura memorizzata che utilizza PRINT o RAISERROR(..., 0, 1) invia i suoi messaggi al tuo logger.

Note

RAISERROR con un livello di gravità pari a 11 o superiore produce un errore Go che puoi gestire con i normali controlli degli errori. Solo i messaggi di gravità 0-10 richiedono il log parametro per essere catturati.

Per maggiori informazioni sui flag di logging e sulla cattura programmatica dei log, vedi Logging e diagnostica.