Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
The mssql-python driver automatically maps Python data types to SQL Server types when sending parameters and converts SQL Server types to Python types when retrieving results.
Python to SQL Server mappings
When you pass Python values as parameters to execute() or executemany(), the driver automatically selects the appropriate SQL Server type. For most applications, the automatic mapping is correct. Use setinputsizes() (described later in this article) only when you need to override the default, for example to force varchar instead of nvarchar or to control parameter metadata for string and integer inputs.
| Python type | SQL Server type | Notes |
|---|---|---|
None |
NULL |
SQL NULL value. |
bool |
bit | True→1, False→0. |
int |
tinyint, smallint, int, bigint | Size selected based on value range. |
float |
float | 64-bit floating point. |
decimal.Decimal |
decimal, numeric | Preserves precision up to 38 digits. |
str |
varchar, nvarchar | Uses nvarchar for Unicode content. |
bytes, bytearray |
varbinary | Binary data. |
datetime.date |
date | Date only. |
datetime.time |
time | Time only. |
datetime.datetime |
datetime2 | Date and time with nanosecond precision. |
uuid.UUID |
uniqueidentifier | 16-byte GUID. |
Integer type selection
The driver automatically selects the smallest integer type that can hold the value:
| Value range | SQL Server type |
|---|---|
| 0 to 255 | tinyint |
| -32,768 to 32,767 | smallint |
| -2,147,483,648 to 2,147,483,647 | int |
| Larger values | bigint |
Large value types
For strings and binary data exceeding standard limits, the driver automatically uses MAX types:
| Condition | SQL Server type |
|---|---|
| String > 8,000 bytes (varchar) | varchar(max) |
| String > 4,000 chars (nvarchar) | nvarchar(max) |
| Binary > 8,000 bytes | varbinary(max) |
The driver streams large values to the server to minimize memory usage.
SQL Server to Python mappings
When the driver retrieves data from SQL Server, the driver converts values to Python types:
| SQL Server type | Python type | Notes |
|---|---|---|
| bit | bool |
True/False. |
| tinyint, smallint, int, bigint | int |
Python integer (arbitrary precision). |
| real | float |
32-bit floating point. |
| float | float |
64-bit floating point. |
| decimal, numeric | decimal.Decimal |
Preserves precision. |
| money, smallmoney | decimal.Decimal |
Fixed precision (four decimal places). |
| char, varchar, text | str |
Decoded as UTF-8. |
| nchar, nvarchar, ntext | str |
Decoded as UTF-16LE. |
| binary, varbinary, image | bytes |
Raw binary. |
| date | datetime.date |
Date only. |
| time | datetime.time |
Time with microsecond precision. |
| datetime, smalldatetime | datetime.datetime |
Legacy datetime types. |
| datetime2 | datetime.datetime |
High precision datetime. |
| datetimeoffset | datetime.datetime |
Time zone-aware datetime. |
| uniqueidentifier | uuid.UUID |
Python UUID objects. |
| xml | str |
XML as text. |
| geography, geometry | bytes |
Spatial types as binary. |
| hierarchyid | bytes |
Hierarchy data as binary. |
| sql_variant | Varies | Resolved to the underlying base type (v1.5.0+). sql_variant columns use a streaming fetch path, which might have a slight performance impact compared to fixed-type columns. |
NULL |
None |
Python None. |
SQL type constants
The driver exports constants that correspond to ODBC SQL type identifiers. You typically use these constants with setinputsizes() to override the driver's default type inference when the automatic mapping doesn't match your schema.
Character types
| Constant | Value | Description |
|---|---|---|
SQL_CHAR |
1 | Fixed-length ANSI character. |
SQL_VARCHAR |
12 | Variable-length ANSI character. |
SQL_LONGVARCHAR |
-1 | Long variable-length ANSI. |
SQL_WCHAR |
-8 | Fixed-length Unicode. |
SQL_WVARCHAR |
-9 | Variable-length Unicode. |
SQL_WLONGVARCHAR |
-10 | Long variable-length Unicode. |
Numeric types
| Constant | Value | Description |
|---|---|---|
SQL_BIT |
-7 | Bit/boolean. |
SQL_TINYINT |
-6 | 8-bit unsigned integer. |
SQL_SMALLINT |
5 | 16-bit signed integer. |
SQL_INTEGER |
4 | 32-bit signed integer. |
SQL_BIGINT |
-5 | 64-bit signed integer. |
SQL_REAL |
7 | 32-bit floating point. |
SQL_FLOAT |
6 | 64-bit floating point. |
SQL_DOUBLE |
8 | 64-bit floating point. |
SQL_DECIMAL |
3 | Fixed precision decimal. |
SQL_NUMERIC |
2 | Fixed precision numeric. |
Date and time types
| Constant | Value | Description |
|---|---|---|
SQL_TYPE_DATE |
91 | Date only. |
SQL_TYPE_TIME |
92 | Time only. |
SQL_TYPE_TIMESTAMP |
93 | Date and time. |
SQL_SS_TIME2 |
-154 | SQL Server time(n). |
SQL_DATETIMEOFFSET |
-155 | SQL Server datetimeoffset. |
Note
SQL_SS_TIME2 and SQL_DATETIMEOFFSET are internal constants not exported as module-level attributes. Use the integer values (-154, -155) directly when calling add_output_converter().
Binary types
| Constant | Value | Description |
|---|---|---|
SQL_BINARY |
-2 | Fixed-length binary. |
SQL_VARBINARY |
-3 | Variable-length binary. |
SQL_LONGVARBINARY |
-4 | Long variable-length binary. |
Other types
| Constant | Value | Description |
|---|---|---|
SQL_GUID |
-11 | Uniqueidentifier. |
SQL_XML |
-152 | XML data. |
SQL_SS_UDT |
-151 | User-defined type (spatial). |
SQL_SS_VARIANT |
-150 | sql_variant data. |
Note
SQL_SS_UDT and SQL_SS_VARIANT are internal constants not exported as module-level attributes. Use the integer values (-151, -150) directly when calling add_output_converter().
Use setinputsizes()
Call setinputsizes() before executemany() to explicitly declare parameter types when the driver's automatic type inference causes unexpected behavior, for example when a column is varchar(100) but the driver sends nvarchar:
Note
For decimal values, rely on the driver's automatic type inference rather than SQL_DECIMAL or SQL_NUMERIC. Those explicit decimal type constants currently have a known issue with setinputsizes(). For more information, see Execute queries.
import mssql_python
conn = mssql_python.connect(connection_string)
cursor = conn.cursor()
# Declare types: (sql_type, precision, scale)
cursor.setinputsizes([
(mssql_python.SQL_WVARCHAR, 100, 0), # nvarchar(100)
(mssql_python.SQL_INTEGER, 0, 0), # int
])
cursor.executemany(
"SELECT ProductID, Name FROM Production.Product WHERE Name LIKE ? AND ProductSubcategoryID = ?",
[
("Road%", 2),
("Mountain%", 1),
]
)
Decimal separator
The driver uses locale-aware decimal separators. To customize:
import mssql_python
# Get current separator
sep = mssql_python.getDecimalSeparator()
print(f"Current separator: {sep}") # Usually "."
# Set custom separator (for locales using comma)
mssql_python.setDecimalSeparator(",")
Custom type converters
Register custom converters for specific SQL types:
import mssql_python
from decimal import Decimal
def money_to_float(value):
"""Convert money values to float instead of Decimal."""
if value is None:
return None
return float(value)
conn.add_output_converter(Decimal, money_to_float)
For more information, see Custom type converters.
Handle special types
UUID handling
The driver maps uuid.UUID to uniqueidentifier automatically. You can insert and retrieve UUIDs without manual string conversion:
import uuid
cursor.execute("CREATE TABLE #Users (UserId UNIQUEIDENTIFIER PRIMARY KEY, Name NVARCHAR(100))")
# Insert a generated UUID
user_id = uuid.uuid4()
cursor.execute("INSERT INTO #Users (UserId, Name) VALUES (%(user_id)s, %(name)s)", {"user_id": user_id, "name": "Alice"})
conn.commit()
# Retrieve as uuid.UUID object (default behavior)
cursor.execute("SELECT UserId, Name FROM #Users WHERE Name = %(name)s", {"name": "Alice"})
row = cursor.fetchone()
print(f"Type: {type(row.UserId)}") # <class 'uuid.UUID'>
print(f"UUID: {row.UserId}") # e.g., 3b4c8f2a-...
# Use the returned UUID directly in subsequent queries
cursor.execute("SELECT Name FROM #Users WHERE UserId = %(user_id)s", {"user_id": row.UserId})
By default, the driver returns UNIQUEIDENTIFIER columns as uuid.UUID objects. To return pyodbc-compatible uppercase strings instead, set native_uuid=False:
import uuid
# Per-connection: return UUIDs as strings
conn2 = mssql_python.connect(connection_string, native_uuid=False)
cursor2 = conn2.cursor()
cursor2.execute("CREATE TABLE #UuidDemo (Id UNIQUEIDENTIFIER DEFAULT NEWID(), Label NVARCHAR(50))")
cursor2.execute("INSERT INTO #UuidDemo (Label) VALUES (%(label)s)", {"label": "test"})
conn2.commit()
cursor2.execute("SELECT Id FROM #UuidDemo")
row = cursor2.fetchone()
print(type(row[0])) # <class 'str'>
print(row[0]) # e.g., 3B4C8F2A-...
conn2.close()
For module-level configuration, see the native_uuid setting in Module configuration.
Datetime with timezone
datetimeoffset columns return time zone-aware datetime objects. For guidance on working with offset-aware versus offset-naive values, see Datetime handling.
cursor.execute("SELECT SYSDATETIMEOFFSET()")
row = cursor.fetchone()
dt = row[0]
print(f"DateTime: {dt}")
print(f"Timezone: {dt.tzinfo}")
Spatial data
The driver returns spatial types (geography, geometry) as bytes. You can use parameter strings in WKT format:
# Insert using WKT
cursor.execute("CREATE TABLE #Locations (Name NVARCHAR(100), Geo GEOGRAPHY)")
cursor.execute(
"INSERT INTO #Locations (Name, Geo) VALUES (%(name)s, geography::STGeomFromText(%(wkt)s, 4326))",
{"name": "Seattle", "wkt": "POINT(-122.33 47.60)"}
)
# Retrieve as bytes
cursor.execute("SELECT Geo.STAsBinary() FROM #Locations")
row = cursor.fetchone()
geo_bytes = row[0]
rowversion/timestamp
SQL Server's rowversion (formerly timestamp) is a binary type, not a datetime:
cursor.execute("""
CREATE TABLE #RowVersionDemo (
ID int PRIMARY KEY,
Version rowversion
)
""")
cursor.execute("INSERT INTO #RowVersionDemo (ID) VALUES (1)")
cursor.execute("SELECT Version FROM #RowVersionDemo WHERE ID = 1")
row = cursor.fetchone()
version = row[0] # bytes, not datetime
Stream large value types
When inserting or retrieving varchar(max), nvarchar(max), or varbinary(max) data, the driver uses Data-at-Execution (DAE) streaming to transmit data in chunks rather than loading the entire value into memory at once.
Streaming activates automatically when input data exceeds size thresholds:
| Data type | Streaming threshold |
|---|---|
| varchar(max) | > 8,000 bytes |
| nvarchar(max) | > 4,000 characters |
| varbinary(max) | > 8,000 bytes |
Streaming works with execute(), executemany(), and all fetch APIs (fetchone(), fetchmany(), fetchall()). The driver requires no special configuration and handles streaming automatically for large values.
Unsupported SQL Server types
The following SQL Server types don't have native Python type mappings. If your application requires these types, use string or binary representations as a workaround, or use pyodbc if it supports the type you need.
| SQL Server type | Status |
|---|---|
| json | Not supported |
| vector | Not supported |
| table | Not supported (table-valued parameters) |
Tip
Although the json SQL Server type isn't supported directly, you can store JSON data in nvarchar(max) columns and query it with SQL Server JSON functions (JSON_VALUE, JSON_QUERY, OPENJSON). For patterns and examples, see JSON data.
The following types return data as bytes but don't have native Python type mappings:
| SQL Server type | Python type | Notes |
|---|---|---|
| geography | bytes |
Use .STAsBinary() for WKB format. |
| geometry | bytes |
Use .STAsBinary() for WKB format. |
| hierarchyid | bytes |
Binary representation. |
| sql_variant | Varies | Resolved to the underlying base type. |
Decimal versus float precision
Use decimal.Decimal when exact precision matters, such as financial calculations, currency, or any value where rounding errors are unacceptable. Use float when approximate values are acceptable, such as scientific measurements or sensor readings.
The driver maps decimal.Decimal to SQL Server decimal/numeric and preserves precision up to 38 digits. float maps to SQL Server float (64-bit IEEE 754), which can introduce rounding artifacts:
from decimal import Decimal
# Exact - use for currency and financial data
price = Decimal("19.99")
tax_rate = Decimal("0.0825")
total = price * (1 + tax_rate) # Decimal("21.6391750")
cursor.execute("""
CREATE TABLE #PrecisionDemo (
Description NVARCHAR(100),
DiscountPct DECIMAL(5,2),
Rating FLOAT
)
""")
cursor.execute(
"INSERT INTO #PrecisionDemo (Description, DiscountPct, Rating) VALUES (%(desc)s, %(pct)s, %(rating)s)",
{"desc": "Summer sale", "pct": Decimal("0.15"), "rating": 4.5}
)
cursor.execute("SELECT DiscountPct, Rating FROM #PrecisionDemo WHERE Description = %(desc)s", {"desc": "Summer sale"})
row = cursor.fetchone()
print(type(row.DiscountPct)) # <class 'decimal.Decimal'>
print(type(row.Rating)) # <class 'float'>
When retrieving decimal/numeric columns, the driver always returns decimal.Decimal objects. When retrieving float/real columns, the driver returns Python float.
Warning
Don't compare float values for equality. Use a tolerance range instead: abs(a - b) < 0.0001.
Numpy type binding
The mssql-python driver uses isinstance() checks to determine SQL types for parameters. Numpy integer types (numpy.int64, numpy.int32, numpy.int8) don't pass isinstance(x, int) in NumPy 2.x, which causes binding failures. Only numpy.float64 works directly because it's a subclass of Python float.
Convert numpy values to native Python types before passing them as parameters:
import numpy as np
row_id = np.int64(42)
score = np.float32(3.14)
# This fails: numpy.int64 is not recognized as int
# cursor.execute("SELECT * FROM Products WHERE ID = ?", (row_id,))
# Convert to native Python types first
cursor.execute(
"SELECT * FROM Production.Product WHERE ProductID = %(product_id)s",
{"product_id": int(row_id)}
)
# For DataFrames, convert the whole column
import pandas as pd
df = pd.DataFrame({"ProductID": [1, 2, 3], "Quantity": [10, 20, 30]})
cursor.execute("CREATE TABLE #Orders (ProductID INT, Quantity INT)")
for _, row in df.iterrows():
cursor.execute(
"INSERT INTO #Orders (ProductID, Quantity) VALUES (%(product_id)s, %(quantity)s)",
{"product_id": int(row["ProductID"]), "quantity": int(row["Quantity"])}
)
If you work heavily with pandas or numpy data, consider using the Arrow integration or pandas integration paths, which handle type conversion internally.