| title | go-mssqldb Table-Valued Parameters |
|---|---|
| description | Pass structured data to stored procedures using table-valued parameters (TVP) with the go-mssqldb driver. |
| author | dlevy-msft |
| ms.author | dlevy |
| ms.date | 04/30/2026 |
| ms.service | sql |
| ms.subservice | connectivity |
| ms.topic | how-to |
| ai-usage | ai-assisted |
Table-valued parameters (TVP) enable you to pass multiple rows of structured data to a stored procedure or parameterized query. The go-mssqldb driver supports TVP through the mssql.TVP type.
Use TVPs when you need to pass a strongly typed rowset into a stored procedure or command and preserve column-level structure. For the highest-throughput path into a destination table, use Bulk operations. If your payload is nested or already JSON-shaped in the application layer, use JSON and XML data.
Create a user-defined table type and a stored procedure that accepts it:
CREATE TYPE dbo.DepartmentType AS TABLE (
Name NVARCHAR(50),
GroupName NVARCHAR(50)
);
GO
CREATE PROCEDURE dbo.InsertDepartments
@departments dbo.DepartmentType READONLY
AS
BEGIN
INSERT INTO HumanResources.Department (Name, GroupName)
SELECT Name, GroupName FROM @departments;
END;
GOCreate a Go struct type that matches the columns in the user-defined table type:
type DepartmentRow struct {
Name string
GroupName string
}Create an mssql.TVP value with the type name and a slice of structs, then pass it as a parameter:
import (
"context"
"database/sql"
"github.com/microsoft/go-mssqldb"
)
func insertDepartments(ctx context.Context, db *sql.DB, departments []DepartmentRow) error {
tvp := mssql.TVP{
TypeName: "dbo.DepartmentType",
Value: departments,
}
_, err := db.ExecContext(ctx, "dbo.InsertDepartments",
sql.Named("departments", tvp))
return err
}Call the function:
departments := []DepartmentRow{
{Name: "Data Science", GroupName: "Research and Development"},
{Name: "Cloud Ops", GroupName: "Information Technology"},
{Name: "Developer Relations", GroupName: "Sales and Marketing"},
}
err := insertDepartments(ctx, db, departments)The driver maps TVP columns to struct fields by position (not by name). The first struct field maps to the first column in the table type, the second field maps to the second column, the third field maps to the third column, and so on for all columns in the table type.
To skip a column, you can't use struct tags. Reorder or restructure your Go type to match the column order in the user-defined table type.
TVP struct fields support the same Go types as regular parameters:
| Go field type | SQL Server column type |
|---|---|
string |
nvarchar |
mssql.VarChar |
varchar |
int64, int32, int16, int8 |
bigint, int, smallint, tinyint |
float64, float32 |
float, real |
bool |
bit |
time.Time |
datetimeoffset |
[]byte |
varbinary |
mssql.UniqueIdentifier |
uniqueidentifier |
You can pass an empty slice. The stored procedure receives a table with zero rows:
tvp := mssql.TVP{
TypeName: "dbo.DepartmentType",
Value: []DepartmentRow{},
}TypeName must be the user-defined table type name (not the destination table name or stored procedure name). If the table type is in a non-default schema, include the schema in TypeName:
tvp := mssql.TVP{
TypeName: "HumanResources.DepartmentType",
Value: departments,
}