Edit

go-mssqldb data type mappings

The go-mssqldb driver converts Go types to SQL Server types automatically when you pass parameters, and converts SQL Server types back to Go types when scanning results. This article documents the default conversions and the driver-specific types available for explicit control.

Examples in this article run against the AdventureWorks2025 sample database. Read-oriented examples query built-in objects such as Production.Product and Sales.vSalesPerson. Write-oriented examples target HumanResources.Department and Production.ProductCategory.

Go to SQL Server parameter mappings

When you pass values as query parameters, the driver converts Go types to SQL Server types:

Go type SQL Server type Notes
string nvarchar Unicode string. Use mssql.VarChar for non-Unicode varchar.
[]byte varbinary Binary data.
int64 bigint
float64 float 64-bit float.
bool bit
time.Time datetimeoffset Preserves time zone offset. Use mssql.DateTime1 for datetime. Use mssql.DateTimeOffset for explicit offset control.
int32 int
int16 smallint
int8 tinyint
float32 real 32-bit float.
mssql.TVP Table-valued parameter See Table-valued parameters.

Driver-specific types

Use these types from the mssql package when you need explicit control over the SQL Server type:

Driver type SQL Server type Description
mssql.VarChar varchar Non-Unicode string. Wrap a string value.
mssql.NVarCharMax nvarchar(max) Unicode large string.
mssql.VarCharMax varchar(max) Non-Unicode large string.
mssql.NChar nchar Fixed-length Unicode string. Wrap a string value.
mssql.DateTime1 datetime Legacy datetime without offset.
mssql.DateTimeOffset datetimeoffset Explicit offset control.
mssql.UniqueIdentifier uniqueidentifier GUID value.
mssql.TVP User-defined table type Table-valued parameter struct.

Example: varchar vs. nvarchar

By default, string parameters are sent as nvarchar. To use varchar instead:

import "github.com/microsoft/go-mssqldb"

// Sends as nvarchar (default)
db.QueryContext(ctx, "SELECT * FROM Production.Product WHERE Name = @p1", "Adjustable Race")

// Sends as varchar
db.QueryContext(ctx, "SELECT * FROM Production.Product WHERE Name = @p1", mssql.VarChar("Adjustable Race"))

Example: uniqueidentifier

Use the mssql.UniqueIdentifier type to scan uniqueidentifier columns:

import "github.com/microsoft/go-mssqldb"

var id mssql.UniqueIdentifier
err := db.QueryRowContext(ctx, "SELECT rowguid FROM Production.Product WHERE Name = @p1",
    sql.Named("p1", "Adjustable Race")).Scan(&id)

SQL Server to Go result mappings

When you scan query results, the driver converts SQL Server types to Go types:

SQL Server type Go type Notes
int, bigint, smallint, tinyint int64 All integer types scan to int64. Cast as needed.
float, real float64
decimal, numeric, money, smallmoney []byte or string No native Go decimal type. Scan to string or use a third-party decimal library.
bit bool
char, varchar, text string
nchar, nvarchar, ntext string
binary, varbinary, image []byte
date, datetime, datetime2, smalldatetime time.Time
datetimeoffset time.Time Time zone offset is preserved.
time time.Time Date component is 0001-01-01.
uniqueidentifier []byte or mssql.UniqueIdentifier Scans to raw bytes by default. Use mssql.UniqueIdentifier for formatted GUID output.
xml string

Named parameters

Use sql.Named to create named parameters:

rows, err := db.QueryContext(ctx,
    "SELECT * FROM Sales.vSalesPerson WHERE CountryRegionName = @location AND FirstName = @name",
    sql.Named("location", "Australia"),
    sql.Named("name", "Jared"))

Note

Positional parameters (@p1, @p2) also work. The driver assigns ordinal names based on the order of the arguments.

Handle NULL values

SQL Server columns that allow NULL require special handling in Go. The standard database/sql package provides nullable types for this purpose.

sql.Null types

Use sql.NullString, sql.NullInt64, sql.NullFloat64, sql.NullBool, and sql.NullTime to handle columns that can be NULL:

var name sql.NullString
var color sql.NullString
var weight sql.NullFloat64
var sellStartDate sql.NullTime
var sellEndDate sql.NullTime

err := db.QueryRowContext(ctx,
    "SELECT Name, Color, Weight, SellStartDate, SellEndDate FROM Production.Product WHERE ProductID = @id",
    sql.Named("id", 1)).Scan(&name, &color, &weight, &sellStartDate, &sellEndDate)
if err != nil {
    log.Fatal(err)
}

if name.Valid {
    fmt.Println("Name:", name.String)
} else {
    fmt.Println("Name: NULL")
}

if weight.Valid {
    fmt.Printf("Weight: %.2f\n", weight.Float64)
}

Pointer-based NULL handling

As an alternative to sql.Null types, use pointers. A nil pointer represents NULL:

var name *string
var color *string

err := db.QueryRowContext(ctx,
    "SELECT Name, Color FROM Production.Product WHERE ProductID = @id",
    sql.Named("id", 1)).Scan(&name, &color)

if name != nil {
    fmt.Println("Name:", *name)
} else {
    fmt.Println("Name: NULL")
}

Tip

Pointer-based NULL handling is more concise, but sql.Null types make the NULL intent more explicit in struct definitions. Choose whichever pattern your team prefers and use it consistently.

Send NULL values as parameters

Pass nil to send a NULL value as a parameter:

// Insert a row with a NULL Color.
_, err := db.ExecContext(ctx,
    "INSERT INTO Production.ProductCategory (Name) VALUES (@name)",
    sql.Named("name", "Custom Parts"))

Decimal and numeric precision

SQL Server's decimal and numeric types support up to 38 digits of precision. Go's float64 provides only about 15-16 significant digits. Scanning high-precision values into float64 causes silent precision loss.

Scan to string

The safest and simplest approach is to scan decimal/numeric/money/smallmoney columns into string, then convert with a precision-safe library:

var priceStr string
err := db.QueryRowContext(ctx,
    "SELECT ListPrice FROM Production.Product WHERE ProductID = @id",
    sql.Named("id", 1)).Scan(&priceStr)
if err != nil {
    log.Fatal(err)
}
fmt.Println("Price:", priceStr) // "12345.678901234567890"

For performance-sensitive paths, you can scan into []byte and parse directly with your decimal library to reduce string allocations.

Use shopspring/decimal for arithmetic

The shopspring/decimal library provides arbitrary-precision decimal arithmetic:

import "github.com/shopspring/decimal"

var priceStr string
err := db.QueryRowContext(ctx,
    "SELECT ListPrice FROM Production.Product WHERE ProductID = @id",
    sql.Named("id", 1)).Scan(&priceStr)
if err != nil {
    log.Fatal(err)
}

price, err := decimal.NewFromString(priceStr)
if err != nil {
    log.Fatal(err)
}

tax := price.Mul(decimal.NewFromFloat(0.08))
total := price.Add(tax)
fmt.Println("Total:", total.StringFixed(2))

Caution

Never scan money or decimal columns into float64 when exact precision matters (financial calculations, currency, tax). Use exact-value scanning (string or []byte) with shopspring/decimal or cockroachdb/apd instead.

Send decimal values as parameters

When sending decimal values as parameters, use string to avoid float precision loss:

price := "12345.678901234567890"
_, err := db.ExecContext(ctx,
    "UPDATE Production.Product SET ListPrice = @price WHERE ProductID = @id",
    sql.Named("price", price),
    sql.Named("id", 1)) // String is converted to decimal by SQL Server.

Date and time types

time.Time mapping

The driver maps Go's time.Time to datetimeoffset by default, which preserves the time zone offset. Use driver-specific types for other datetime formats:

Go value SQL Server type When to use
time.Time datetimeoffset Default. Use when the time zone matters.
mssql.DateTime1(t) datetime Legacy columns that don't support offset.
mssql.DateTimeOffset(t) datetimeoffset Explicit offset control.

UTC best practice

Store times in UTC and convert to local time in the application layer:

now := time.Now().UTC()
_, err := db.ExecContext(ctx,
    "INSERT INTO Production.ScrapReason (Name, ModifiedDate) VALUES (@name, @modified)",
    sql.Named("name", "Operator error"),
    sql.Named("modified", now))

uniqueidentifier (GUID) patterns

SQL Server stores uniqueidentifier values with the first three groups in little-endian byte order (also known as mixed-endian or GUID byte order). If you scan a uniqueidentifier column into []byte, the raw bytes don't match the standard string representation. Use mssql.UniqueIdentifier instead, which handles the byte-order conversion automatically.

Scan with mssql.UniqueIdentifier

By default, uniqueidentifier columns scan to []byte. Use mssql.UniqueIdentifier for formatted GUID strings:

var id mssql.UniqueIdentifier
err := db.QueryRowContext(ctx,
    "SELECT rowguid FROM Production.Product WHERE Name = @name",
    sql.Named("name", "Adjustable Race")).Scan(&id)
if err != nil {
    log.Fatal(err)
}
fmt.Println("ID:", id) // "6F9619FF-8B86-D011-B42D-00C04FC964FF"

Generate and send GUIDs

Use NEWID() on the server or generate in Go:

// Server-side generation
var newID mssql.UniqueIdentifier
err := db.QueryRowContext(ctx,
    "INSERT INTO Production.ProductCategory (Name) OUTPUT INSERTED.rowguid VALUES (@name)",
    sql.Named("name", "Custom Parts")).Scan(&newID)

Client-side generation with the google/uuid package:

import "github.com/google/uuid"

id := mssql.UniqueIdentifier(uuid.New())
_, err := db.ExecContext(ctx,
    "UPDATE Production.ProductCategory SET rowguid = @id WHERE Name = @name",
    sql.Named("id", id),
    sql.Named("name", "Custom Parts"))

Type mapping checklist

Scenario Recommended approach
Nullable columns Use sql.NullString, sql.NullInt64, sql.NullFloat64, sql.NullBool, sql.NullTime, or pointer types.
Financial/decimal values Scan to string, use shopspring/decimal for arithmetic.
Dates with time zones Use time.Time (maps to datetimeoffset by default).
Legacy datetime columns Use mssql.DateTime1 when sending parameters.
GUIDs Scan with mssql.UniqueIdentifier for formatted output.
Non-Unicode strings Use mssql.VarChar to avoid implicit nvarchar conversion.
JSON columns Scan to string, unmarshal with encoding/json. See JSON and XML data.
XML columns Scan to string, unmarshal with encoding/xml. See JSON and XML data.