Procédures stockées avec go-mssqldb

Le go-mssqldb pilote prend en charge l’appel de procédures stockées avec des paramètres d’entrée, des paramètres de sortie et des valeurs de statut de retour. Cet article traite des schémas courants.

La plupart des extraits de cet article supposent que la configuration habituelle database/sql est déjà en place, avec database/sql importé comme sql, une ctx valeur disponible, et db initialisé. Les extraits incluent des blocs d’importation uniquement lorsqu’ils introduisent des paquets supplémentaires tels que context, fmt, log, os, ou github.com/microsoft/go-mssqldb. Lorsqu’un bloc de code continue intentionnellement le même exemple, utilisez = pour les valeurs déjà déclarées plus tôt dans cet exemple ; utilisez := pour de nouvelles déclarations dans des extraits autonomes.

Appeler une procédure stockée

Utilisez ExecContext ou QueryContext directement avec le nom de la procédure :

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

Privilégiez le nom de procédure ainsi que les paramètres pour les appels de procédure ordinaires. Utilisez une chaîne explicite EXECUTE ... uniquement lorsque vous devez intégrer l'appel dans un lot Transact-SQL plus grand (T-SQL) ou utilisez une syntaxe qui n'est pas représentée par sql.Named des paramètres.

Paramètres de sortie

Utiliser sql.Named avec sql.Out pour recevoir les valeurs des paramètres de sortie :

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 procédure T-SQL correspondante :

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

Paramètres d’entrée/sortie

Pour les paramètres qui sont à la fois en entrée et en sortie, définissez In: true dans la struct 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)

Statut de retour

À utiliser mssql.ReturnStatus pour capturer la valeur de retour des entiers d’une procédure stockée. Cet exemple utilise la procédure 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 fonctionne avec ExecContext et QueryContext, mais pas avec QueryRowContext.

Vous pouvez combiner ReturnStatus avec les paramètres de la procédure. Cet exemple poursuit l’exemple précédent, il réutilise donc l’importation mssql montrée plus tôt et utilise la procédure 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()

Ensembles de résultats issus des procédures stockées

Si la procédure stockée retourne des ensembles de résultats, utilisez 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
}

Pour les procédures qui retournent plusieurs ensembles de résultats, utilisez rows.NextResultSet(). Pour plus d’informations, voir Requêtes et déclarations.

Lisez toutes les lignes avant d’utiliser les paramètres de sortie ou le statut de retour

SQL Server envoie les paramètres de sortie et les valeurs de statut de retour après la fin des ensembles de résultats. Si vous lisez une variable de sortie avant de consommer toutes les lignes, la valeur peut toujours être incomplète.

Cet exemple poursuit le précédent et réutilise l’importation mssql montrée plus tôt.

Il n’affiche pas les données de ligne. La boucle ne consomme que l’ensemble des résultats, elle est donc returnStatus disponible après la fin de la requête.

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)

À utiliser ExecContext lorsque la procédure ne retourne pas de lignes. Si la procédure retourne des lignes, attendez que toutes les lignes et ensembles de résultats soient consommés avant de lire les paramètres de sortie ou de retourner le statut.

Tables temporaires et procédures stockées

Lorsque vous créez une table temporaire puis l’interrogez dans des appels séparés, les opérations peuvent s’exécuter sur des connexions différentes du pool. Puisque les tables temporaires sont limitées à une seule connexion, le second appel peut ne pas voir la table.

Pour travailler avec des tables temporaires, utilisez une approche à connexion unique :

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

Vous pouvez également regrouper les opérations dans une transaction, ce qui les associe automatiquement à la même connexion.

Capturer les messages PRINT et RAISERROR

Les instructions SQL Server PRINT et RAISERROR avec une gravité de 0 à 10 génèrent des messages d’information qui ne sont pas renvoyés sous forme d’erreurs Go. Pour capturer ces messages, activez le log paramètre de connexion avec le drapeau 2 (messages) :

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

Avec la journalisation activée, le pilote écrit la sortie PRINT et les messages de faible gravité RAISERROR dans le package Go standard log. Pour les capturer programmatiquement, définissez un enregistreur personnalisé avant d’ouvrir la connexion :

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

Ensuite, toute procédure stockée qui utilise PRINT ou RAISERROR(..., 0, 1) envoie ses messages à votre enregistreur.

Note

RAISERROR avec un niveau de gravité de 11 ou plus génère une erreur Go que vous pouvez gérer à l’aide de la vérification d’erreur habituelle. Seuls les messages de gravité 0 à 10 nécessitent le paramètre log pour être capturés.

Pour plus d’informations sur les indicateurs de journalisation et la capture programmatique des journaux, voir Journalisation et diagnostic.