mssql-python のデータ型マッピング

mssql-pythonドライバーはパラメータを送信時にPythonデータ型を自動的にSQL Server型にマッピングし、結果を取得する際にSQL Server型をPython型に変換します。

PythonからSQL Serverへのマッピング

Python値をパラメータとしてexecute()やexecutemany()に渡すと、ドライバーは自動的に適切なSQL Serverタイプを選択します。 ほとんどの用途では、自動マッピングは正確です。 setinputsizes()(この記事の後述で説明します)は、デフォルトを上書きする必要がある場合、例えばnvarcharの代わりにvarcharを強制したり、文字列や整数入力のパラメータメタデータを制御する場合のみ使用してください。

Python 型 SQL Server の種類 Notes
None NULL SQLのNULL値。
bool bit True→1、 False→0。
int ティニート、 スモールリント、 イント、 ビジント サイズは値段に基づいて選択します。
float float 64ビット浮動小数点。
decimal.Decimal 十進法、 数値 最大38桁までの精度を保持します。
str ヴァルチャール、 ンヴァルチャール Unicodeのコンテンツには nvarchar を使用しています。
bytes、bytearray varbinary バイナリ データ。
datetime.date date 日付のみ。
datetime.time time 時間だけだ。
datetime.datetime datetime2 ナノ秒単位の精度で日付と時間。
uuid.UUID uniqueidentifier 16バイトのガイド。

整数型選択

ドライバーは自動的に、次の値を保持できる最小の整数型を選択します。

値の範囲 SQL Server の種類
0 ~ 255 tinyint
-32,768 ~ 32,767 smallint
-2,147,483,648 ~ 2,147,483,647 int
より大きな値 bigint

大きな価値タイプ

標準制限を超える文字列やバイナリデータの場合、ドライバーは自動的にMAX型を使用します:

状態 SQL Server の種類
文字列 > 8,000バイト(varchar) varchar(max)
文字列 > 4,000 chars(nvarchar) nvarchar(max)
バイナリ > 8,000バイト varbinary(max)

ドライバーはメモリ使用を最小限に抑えるために大きな値をサーバーにストリーミングします。

SQL ServerからPythonへのマッピング

ドライバーがSQL Serverからデータを取得する際、値をPython型に変換します:

SQL Server の種類 Python 型 Notes
bit bool 正/誤
ティニート、 スモールリント、 イント、 ビジント int Python整数(任意の精度)。
実数 float 32 ビット浮動小数点。
float float 64ビット浮動小数点。
十進法、 数値 decimal.Decimal 精度を保つ。
お金、 小さなお金 decimal.Decimal 固定精度(小数点以下4桁)です。
char、 varchar、 テキスト str UTF-8としてデコードされました。
nchar、 nvarchar、 ntext str UTF-16LEとしてデコードされました。
binary、varbinary、image bytes 生のバイナリです。
date datetime.date 日付のみ。
time datetime.time マイクロ秒単位の精度で時間。
デートタイム、 スモールデイトタイム datetime.datetime 昔ながらのデートタイムタイプ。
datetime2 datetime.datetime 高精度のデートタイム。
datetimeoffset datetime.datetime タイムゾーンを意識した日付時間。
uniqueidentifier uuid.UUID Python UUIDオブジェクト。
xml str XMLをテキストとして使う。
地理、 幾何学 bytes 空間タイプは二項法として扱います。
hierarchyid bytes 階層データをバイナリとして扱う。
sql_variant 場合により異なる 基礎となるベースタイプに解決します。 sql_variant カラムはストリーミングフェッチパスを使用しており、固定型カラムと比べてわずかなパフォーマンスに影響がある場合があります。
NULL None Python なし。

SQL型定数

ドライバーはODBC SQL型識別子に対応する定数をエクスポートします。 通常、これらの定数を setinputsizes() 使って、自動マッピングがスキーマと一致しない場合にドライバーのデフォルトのタイプ推論を上書きします。

文字型

定数 価値 形容
SQL_CHAR 1 固定長ANSI文字。
SQL_VARCHAR 12 可変長ANSI文字。
SQL_LONGVARCHAR -1 長距離可変長ANSI。
SQL_WCHAR -8 固定長のUnicode。
SQL_WVARCHAR -9 可変長Unicode。
SQL_WLONGVARCHAR -10 長く可変長のUnicode。

数値型

定数 価値 形容
SQL_BIT -7 ビット/ブール数です。
SQL_TINYINT -6 8 ビット符号なし整数。
SQL_SMALLINT 5 16 ビット符号付き整数。
SQL_INTEGER 4 32 ビット符号付き整数。
SQL_BIGINT -5 64ビット符号付き整数。
SQL_REAL 7 32 ビット浮動小数点。
SQL_FLOAT 6 64ビット浮動小数点。
SQL_DOUBLE 8 64ビット浮動小数点。
SQL_DECIMAL 3 固定精度の十進法。
SQL_NUMERIC 2 固定精度の数値。

日付/時刻型

定数 価値 形容
SQL_TYPE_DATE 91 日付のみ。
SQL_TYPE_TIME 92 時間だけだ。
SQL_TYPE_TIMESTAMP 93 日付と時刻。
SQL_SS_TIME2 -154 SQL Server time(n).
SQL_DATETIMEOFFSET -155 SQL Server datetimeoffset.

Note

SQL_SS_TIME2 SQL_DATETIMEOFFSETはモジュールレベルの属性としてエクスポートされない内部定数です。 -154を呼ぶ際には整数の値(-155、add_output_converter())を直接使います。

バイナリ型

定数 価値 形容
SQL_BINARY -2 固定長のバイナリ。
SQL_VARBINARY -3 可変長のバイナリ。
SQL_LONGVARBINARY -4 長可変長のバイナリ。

その他の型

定数 価値 形容
SQL_GUID -11 ユニーク識別子。
SQL_XML -152 XML データ。
SQL_SS_UDT -151 ユーザー定義型(空間型)。
SQL_SS_VARIANT -150 sql_variantデータ。

Note

SQL_SS_UDT SQL_SS_VARIANTはモジュールレベルの属性としてエクスポートされない内部定数です。 -151を呼ぶ際には整数の値(-150、add_output_converter())を直接使います。

setinputsizes()

ドライバの自動型推論が予期せぬ挙動を引き起こす場合、例えばカラムがsetinputsizes()されているがドライバが送信した場合にパラメータ型を明示的に宣言するためにexecutemany()前にvarchar(100)を呼び出しますnvarchar:

Note

小数点については、 SQL_DECIMAL や SQL_NUMERICではなく、ドライバーの自動型推論に頼ってください。 これらの明示的な10進法定数には現在、 setinputsizes()で既知の問題があります。 詳細については、「 クエリ実行」を参照してください。

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),
    ]
)

小数点の区切り文字

ドライバーは位置認識の小数点区切りを使用します。 カスタマイズするには、次のように実行します。

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

カスタムタイプコンバーター

特定のSQLタイプに対応したレジスタカスタムコンバータ:

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)

詳細については、 カスタムタイプコンバーターをご覧ください。

特殊なハンドルタイプ

UUIDの扱い

ドライバーは uuid.UUID を自動的に uniqueidentifier にマッピングします。 手動の文字列変換なしでUUIDの挿入と取得が可能です:

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

デフォルトでは、ドライバーは UNIQUEIDENTIFIER 列をオブジェクト uuid.UUID として返します。 代わりにpyodbc互換の大文字文字列を返すには、 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()

モジュールレベルの構成については、native_uuidの設定を参照してください。

タイムタイム(タイムゾーン付き)

datetimeoffset 列はタイムゾーン対応のオブジェクト datetime 返します。 オフセット認識値とオフセットナイーブ値の扱いに関するガイダンスについては、 Datetimeハンドリングを参照してください。

cursor.execute("SELECT SYSDATETIMEOFFSET()")
row = cursor.fetchone()
dt = row[0]

print(f"DateTime: {dt}")
print(f"Timezone: {dt.tzinfo}")

空間データ

運転手は空間タイプ(geography、 geometry)を bytesとして返します。 パラメータ文字列はWKT形式で使用できます:

# 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]

ロウバージョン/タイムスタンプ

SQL Serverのrowversion(旧timestamp)は二値型であり、日付時間ではありません。

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

大きな価値タイプをストリーミング

varchar(max)、nvarchar(max)、またはvarbinary(max)データの挿入または取得時、ドライバはデータをチャンク単位で送信するためにData-at-Execution(DAE)ストリーミングを使用します。

ストリーミングは入力データのサイズ閾値を超えると自動的に起動します:

データの種類 ストリーミングしきい値
varchar(max) > 8,000バイト
nvarchar(max) > 4,000文字
varbinary(max) > 8,000バイト

ストリーミングは execute()、 executemany()、そしてすべてのフェッチAPI(fetchone()、 fetchmany()、 fetchall())で動作します。 このドライバーは特別な設定を必要とせず、大きな数値に対して自動的にストリーミング処理を行います。

サポートされていないSQL Serverタイプ

以下のSQL ServerタイプはネイティブのPython型マッピングを持っていません。

SQL Server の種類 地位
json サポートしていません
vector サポートしていません
table サポートされていません(テーブル値パラメータ)

ヒント

json SQL Server型は直接サポートされていませんが、JSONデータをnvarchar(max)列に保存し、SQL Server JSON関数(JSON_VALUE、JSON_QUERY、OPENJSON)でクエリすることができます。 パターンや例については JSONデータを参照してください。

以下のタイプは、bytesとしてデータを返しますが、ネイティブのPython型マッピングは持っていません。

SQL Server の種類 Python 型 Notes
geography bytes WKBフォーマットには .STAsBinary() を使いましょう。
geometry bytes WKBフォーマットには .STAsBinary() を使いましょう。
hierarchyid bytes 二元表現。
sql_variant 場合により異なる 基礎となるベースタイプに解決します。

十進数と浮子数の精度の比較

財務計算、通貨、または四捨五入誤差が許容できない数値など、正確な精度が重要な場合に decimal.Decimal 用いてください。 科学的測定値やセンサー測定値など、おおよその値が許容できる場合にのみ float を使用します。

ドライバーはdecimal.DecimalをSQL Server decimal/numericにマッピングし、最大38桁までの精度を保持します。 floatSQL Server float(64ビットIEEE 754)にマッピングされており、これは丸めアーティファクトを導入することがあります。

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'>

decimal / numeric列を取得する際、ドライバーは常にオブジェクトdecimal.Decimal返します。 float / real列を取り戻す際に、運転士はPython floatを戻します。

Warning

平等のために float 価値観を比較しないでください。 代わりに許容範囲を使いましょう: abs(a - b) < 0.0001。

ナンピー型バインディング

mssql-pythonドライバはパラメータのSQL型を決定するためにisinstance()チェックを使用します。 numpy整数型(numpy.int64、 numpy.int32、 numpy.int8)はNumPy 2.xでパス isinstance(x, int) できず、バインディング失敗を引き起こします。 numpy.float64が直接機能するのは、Python floatのサブクラスだからです。

numpyの値をパラメータとして渡す前にネイティブPython型に変換してください:

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

pandasやnumpyデータを多用する場合は、 Arrowの統合 や pandasの統合 パスを内部で扱う方法を検討してください。