文字列と Unicode を処理する

Microsoft SQLは複数の文字列型を提供し、mssql-pythonドライバがPython strオブジェクトにマッピングします。 重要な決定は、 varchar (非Unicode)を使うか nvarchar (Unicode)を使うかです。

  • 名前、住所、あらゆる言語のユーザー生成コンテンツなど、ASCII 以外の文字が含まれる可能性があるデータには nvarchar を使用してください。
  • データが厳密にASCII(コード、識別子、メールアドレス)で、ストレージを節約したい場合は、varchar を使用します。 varchar 1文字あたり1バイトを使用します。 nvarchar 文字あたり2バイトを使用します。
SQL 型 Unicode 最大長 Python 型
char(n) いいえ 8,000 str
varchar(n) いいえ 8,000 str
varchar(max) いいえ 2 GB str
nchar(n) イエス 4,000 str
nvarchar(n) イエス 4,000 str
nvarchar(max) イエス 2 GB str
text いいえ 2 GB(非推奨) str
ntext イエス 2 GB(非推奨) str

基本的な文字列操作

ドライバーは、Microsoft SQL のすべての文字列型を Python の str オブジェクトにマップします。

文字列の挿入と回収

パラメータ化されたクエリを使って、データベースから文字列データを安全に挿入・取得できます。

import mssql_python

conn = mssql_python.connect(connection_string)
cursor = conn.cursor()

# Create temp table for demo
cursor.execute("""
    CREATE TABLE #StringDemo (
        ID INT IDENTITY(1,1) PRIMARY KEY,
        Name NVARCHAR(100),
        Email NVARCHAR(200)
    )
""")

# Insert string data
cursor.execute(
    "INSERT INTO #StringDemo (Name, Email) VALUES (%(name)s, %(email)s)",
    {"name": "Alice Smith", "email": "alice@example.com"}
)
conn.commit()

# Retrieve string data
cursor.execute("SELECT Name, Email FROM #StringDemo WHERE ID = 1")
row = cursor.fetchone()
print(row.Name)   # 'Alice Smith'
print(row.Email)  # 'alice@example.com'

特殊文字を持つ文字列

引用符、角括弧、その他の文字列中の特殊文字をパラメータ化されたクエリで扱います。

# Quotes and special characters handled automatically
cursor.execute("""
    CREATE TABLE #Notes (
        ID INT IDENTITY(1,1) PRIMARY KEY,
        Title NVARCHAR(200),
        Content NVARCHAR(MAX)
    )
""")
cursor.execute(
    "INSERT INTO #Notes (Title, Content) VALUES (%(title)s, %(content)s)",
    {
        "title": "O'Brien's Report",
        "content": 'Contains "quotes" and special chars: <>&'
    }
)
conn.commit()

Unicode のサポート

nvarcharの列とPython strを使って、任意の言語のテキストを保存・取得できます。

Unicodeテキストを保存します

パラメータ付きクエリにPython文字列を渡してUnicodeコンテンツを挿入します。ドライバーはnvarchar列に対してUTF-16LEとしてエンコードします。

# International characters - use nvarchar columns
cursor.execute("""
    CREATE TABLE #Messages (
        ID INT IDENTITY(1,1) PRIMARY KEY,
        Content NVARCHAR(MAX)
    )
""")
cursor.execute("""
    INSERT INTO #Messages (Content) VALUES (%(msg)s)
""", {"msg": "Hello 你好 مرحبا שלום 🎉"})

cursor.execute("SELECT Content FROM #Messages WHERE ID = 1")
row = cursor.fetchone()
print(row.Content)  # 'Hello 你好 مرحبا שלום 🎉'

異なる文字体系での Unicode

nvarcharの列と一括挿入を用いて、複数の言語とスクリプトを1つのテーブルでサポートします。

messages = [
    {"lang": "English", "text": "Hello, World!"},
    {"lang": "Chinese", "text": "你好,世界!"},
    {"lang": "Japanese", "text": "こんにちは世界!"},
    {"lang": "Korean", "text": "안녕하세요, 세상!"},
    {"lang": "Arabic", "text": "مرحبا بالعالم!"},
    {"lang": "Hebrew", "text": "שלום עולם!"},
    {"lang": "Russian", "text": "Привет мир!"},
    {"lang": "Greek", "text": "Γειά σου Κόσμε!"},
    {"lang": "Emoji", "text": "👋🌍✨🎉"},
]

cursor.execute("""
    CREATE TABLE #Greetings (
        ID INT IDENTITY(1,1) PRIMARY KEY,
        Language NVARCHAR(50),
        Message NVARCHAR(200)
    )
""")
cursor.executemany("""
    INSERT INTO #Greetings (Language, Message) VALUES (%(lang)s, %(text)s)
""", messages)
conn.commit()

Unicode には nvarchar 列を使用してください

データに非ASCII文字が含まれている場合、必ずvarcharではなくnvarcharとして列を定義してください。

-- For Unicode data, always use nvarchar, not varchar
CREATE TABLE #UnicodeDemo (
    ID INT IDENTITY PRIMARY KEY,
    Name NVARCHAR(100),        -- Supports Unicode
    Description NVARCHAR(MAX)  -- Supports large Unicode text
);

弦の長さの考慮事項

データ長の安定性に応じて、固定長タイプと可変長タイプを選びましょう。

固定長と可変長の比較

Microsoft SQLのchar(n)は、宣言された長さに合わせて後ろにスペースを付けて値を補足します。 このパディングは可変長データのストレージを無駄にしますが、国コードなどの固定幅カラムでは性能を向上させることができます。 ほとんどの文字列の列には varchar(n) を使いましょう。

以下の例は、パッド付きカラムと非パッディングカラムがデータ検索をどのように扱うかの違いを示しています。

# char(6) pads to fixed length
cursor.execute(
    "SELECT StateProvinceCode FROM Person.StateProvince WHERE StateProvinceID = 1"
)  # nchar(6) column
row = cursor.fetchone()
print(repr(row.StateProvinceCode))  # 'AB    ' - right-padded with spaces

# nvarchar stores actual length
cursor.execute(
    "SELECT Name FROM Person.StateProvince WHERE StateProvinceID = 1"
)  # nvarchar column
row = cursor.fetchone()
print(repr(row.Name))  # 'Alberta' - no padding

末尾のスペースを処理

固定長のchar列からデータを取得する際は、Microsoft SQL Serverによって追加されたパディングスペースをrstrip()で除去します。

# Strip trailing spaces from char columns
cursor.execute("SELECT ProductNumber FROM Production.Product")
for row in cursor:
    code = row.ProductNumber.rstrip()  # Remove trailing spaces
    print(f"Code: '{code}'")

大型弦(MAXタイプ)

nvarchar(max)型とvarchar(max)型は最大2GBの文字列をサポートしており、大きなテキスト文書、JSON、XMLコンテンツの保存に最適です。

# Large text content
large_content = "x" * 100000  # 100K characters

cursor.execute("""
    CREATE TABLE #Documents (
        ID INT IDENTITY(1,1) PRIMARY KEY,
        Content NVARCHAR(MAX)
    )
""")
cursor.execute("""
    INSERT INTO #Documents (Content) VALUES (%(content)s)
""", {"content": large_content})

cursor.execute("SELECT Content FROM #Documents WHERE ID = 1")
row = cursor.fetchone()
print(len(row.Content))  # 100000

文字列比較と照合

MicrosoftのSQL文字列比較動作は、データベースまたは列の照合セットに依存します。

大文字小文字の区別

MicrosoftのSQL文字列比較は、コレーションに依存します。 デフォルトではほとんどのデータベースは大文字に区別されない照合を使用しますが、 COLLATE 節でこれを上書きできます。

# Case-insensitive collation (default for many databases)
cursor.execute("SELECT * FROM Person.Person WHERE LastName = %(name)s", {"name": "smith"})
# Might match 'Smith', 'SMITH', 'smith' depending on collation

# For case-sensitive comparison
cursor.execute("""
    SELECT * FROM Person.Person 
    WHERE LastName COLLATE Latin1_General_CS_AS = %(name)s
""", {"name": "Smith"})

LIKE パターン照合

LIKE 演算子をワイルドカード文字と組み合わせて使用し、文字列パターンを検索します。特殊文字は、リテラルとして一致させるために角かっこ表記でエスケープします。

# Wildcard searches
search_term = "Road"
cursor.execute("""
    SELECT Name FROM Production.Product WHERE Name LIKE %(pattern)s
""", {"pattern": f"%{search_term}%"})

# Escape special characters in search
def escape_like(value: str) -> str:
    """Escape LIKE wildcards in search value."""
    return value.replace("[", "[[]").replace("%", "[%]").replace("_", "[_]")

search = "100%"
cursor.execute("""
    SELECT Name FROM Production.Product WHERE Name LIKE %(pattern)s
""", {"pattern": f"%{escape_like(search)}%"})

符号化の考慮事項

エンコーディングの動作はMicrosoftのSQLカラムタイプとソースのコレーションに依存します。

符号化の前提とUnicodeのデフォルト

mssql-pythonドライバーはMicrosoft SQL列タイプに基づいて自動的にエンコーディングを処理します。 デフォルトでは、nvarchar列の文字列パラメータはUTF-16LEとして送信され、varchar列のデータベース照合に従って以下の通りです:

列の種類 ワイヤー符号化 Python 結果
nvarcharncharntext UTF-16LE str (ドライバーによって解読)
varcharchartext データベースまたはカラムの照合エンコーディング str (ドライバーがソースエンコーディングを用いてデコード)

Python文字列は常に内部的にUnicodeです。 strパラメータを渡すと、ドライバーはターゲットカラムタイプとしてそれをエンコードします。 デフォルトでは、ドライバーは文字列パラメータを nvarchar (Unicode)として送信し、データベースの照合に関わらず文字が保持されます。 varchar列の場合、UTF-8はデータベースまたは列がUTF-8対応のコレーションを使用している場合にのみ適用されます。

もしカラムが varchar で、カラムタイプと完全に一致する非Unicodeデータを送る必要がある場合(例えば暗黙の変換警告を避けるため)、 setinputsizes() を使ってデフォルトを上書きしてください:

import mssql_python

conn = mssql_python.connect(connection_string)
cursor = conn.cursor()

# Create temp table for demo
cursor.execute("CREATE TABLE #AsciiTable (Code VARCHAR(100))")

cursor.setinputsizes([(mssql_python.SQL_VARCHAR, 100, 0)])
cursor.execute(
    "INSERT INTO #AsciiTable (Code) VALUES (?)",
    ("ABC123",)
)
conn.commit()

ほとんどのアプリケーションでは、デフォルトの挙動は正しいです。 クエリプランに暗黙のコンバージョン警告がある場合や、特定の varchar コレーションにマッチする必要がある場合にのみオーバーライドしてください。

接続エンコーディング

mssql-pythonドライバーは、Microsoft SQL Serverのバージョンと設定に基づいて接続のエンコーディングを自動的に処理します。 Python文字列はUnicodeであるため、ドライバーはターゲットデータ型に合わせて適切に(UTF-8またはUTF-16)エンコードします。 接続エンコーディングを手動で設定する必要はありません。

レガシー照合を含むVARCHAR列

Windows-1252(CP1252)の照合を持つデータベース(Latin1_General_CI_ASなど)は、拡張ラテン文字(例:、アクセント付き文字)をCP1252エンコーディングでvarchar列に保存します。 ドライバーはすべてのプラットフォームでこれらの文字を正しく復号します。

この違いはクロスプラットフォーム展開において重要で、Windows上で正しく読み取れる同じデータvarcharLinuxでも正しく読み込まれ、特別な設定は不要です。

# Create a temp table with a varchar column and insert extended Latin characters
cursor.execute("CREATE TABLE #Products (Name VARCHAR(100))")
cursor.execute("INSERT INTO #Products (Name) VALUES (%(name)s)", {"name": "Café €100 ™"})
conn.commit()

# CP1252 characters in varchar columns are decoded correctly on all platforms
cursor.execute("SELECT Name FROM #Products WHERE Name LIKE '%€%'")
for row in cursor:
    print(row.Name)  # Correct on both Windows and Linux

スキーマが許可しているなら、 varchar 列を nvarchar に移行することで、エンコードを完全に避け、すべてのUnicode文字をサポートします。

ファイルのエンコード

データベースに挿入するファイルを読み取る際は、Unicodeの内容を保存するための適切なエンコーディングを指定します。

# Reading files with explicit encoding
def insert_file_content(cursor, conn, file_path: str, encoding: str = "utf-8"):
    with open(file_path, "r", encoding=encoding) as f:
        content = f.read()
    
    cursor.execute(
        "INSERT INTO #FileContent (Content) VALUES (%(content)s)",
        {"content": content}
    )
    conn.commit()

一般的な文字列操作

これらの例は、PythonとSQLの両方でよく見られる文字列操作パターンをカバーしています。

連結

文字列はPythonで挿入前に連結するか、サーバー上のSQLの文字列演算子を使うことができます。

# Concatenate in Python before insert
first_name = "Alice"
last_name = "Smith"
full_name = f"{first_name} {last_name}"

cursor.execute("""
    CREATE TABLE #ConcatDemo (
        ID INT IDENTITY(1,1) PRIMARY KEY,
        FullName NVARCHAR(200)
    )
""")
cursor.execute(
    "INSERT INTO #ConcatDemo (FullName) VALUES (%(name)s)",
    {"name": full_name}
)

# Or concatenate in SQL
cursor.execute("""
    SELECT FirstName + ' ' + LastName AS FullName FROM Person.Person
""")

文字列の書式設定

ユーザーに表示する前に、Python で書式設定を適用して、文字列を通貨表示、パディング、配置付きで表示できるようにします。

from decimal import Decimal

# Format for display
cursor.execute("SELECT Name, ListPrice FROM Production.Product WHERE ListPrice > 0")
for row in cursor.fetchall()[:5]:
    print(f"{row.Name}: ${row.ListPrice:.2f}")

# Pad strings
cursor.execute("SELECT ProductNumber FROM Production.Product")
for row in cursor.fetchall()[:5]:
    padded = row.ProductNumber.ljust(15)  # Left-justify, pad to 15 chars
    print(f"[{padded}]")

NULLと空文字列の区別

Microsoft SQLはNULLと空文字列('')を異なる値として扱います。 NULLは「未知」を意味し、empty文字列は「空であることが知られている」ことを意味します。応募には一つの慣習を選び、一貫性を保ちましょう。 ほとんどのアプリケーションは、オプションフィールドが欠けている場合にNULLを使用します。

以下の例は、NULL文字列と空文字列の区別方法を示しています。

# NULL is different from empty string
cursor.execute("""
    CREATE TABLE #NullDemo (
        ID INT IDENTITY(1,1) PRIMARY KEY,
        Name NVARCHAR(100),
        MiddleName NVARCHAR(100)
    )
""")
cursor.execute("""
    INSERT INTO #NullDemo (Name, MiddleName) 
    VALUES (%(name)s, %(middle)s)
""", {"name": "Alice", "middle": None})  # NULL

cursor.execute("""
    INSERT INTO #NullDemo (Name, MiddleName) 
    VALUES (%(name)s, %(middle)s)
""", {"name": "Bob", "middle": ""})  # Empty string

# Query differences
cursor.execute("SELECT * FROM #NullDemo WHERE MiddleName IS NULL")
cursor.execute("SELECT * FROM #NullDemo WHERE MiddleName = ''")

トリム操作

Pythonの文字列メソッドを使って、データベースから取得した値からリード、テール、またはその両方の空白を削除してください。

cursor.execute("SELECT Name FROM Production.Product")
for row in cursor:
    # Remove whitespace
    trimmed = row.Name.strip()  # Both ends
    left_trimmed = row.Name.lstrip()
    right_trimmed = row.Name.rstrip()

JSON文字列データ

JSONドキュメントをnvarchar(max)列に保存し、Microsoft SQLのJSON関数でクエリを行ってください。

Store JSON as nvarchar

Python辞書をJSON文字列にシリアル化してnvarchar列に挿入し、それらを取得してPythonオブジェクトに逆シリアル化します。

import json

data = {"name": "Alice", "scores": [95, 87, 91], "active": True}
json_string = json.dumps(data)

cursor.execute("""
    CREATE TABLE #Configs (
        ID INT IDENTITY(1,1) PRIMARY KEY,
        ConfigData NVARCHAR(MAX)
    )
""")
cursor.execute("""
    INSERT INTO #Configs (ConfigData) VALUES (%(data)s)
""", {"data": json_string})

# Retrieve and parse
cursor.execute("SELECT ConfigData FROM #Configs WHERE ID = 1")
row = cursor.fetchone()
config = json.loads(row.ConfigData)
print(config["name"])  # 'Alice'

Microsoft SQL JSON関数を使用

Microsoft SQLのJSON関数を使って、クライアントコードではなくクエリでJSONデータを直接解析・フィルタリングしましょう。

import json

data = {"name": "Alice", "scores": [95, 87, 91], "active": True}

cursor.execute("""
    CREATE TABLE #Configs (
        ID INT IDENTITY(1,1) PRIMARY KEY,
        ConfigData NVARCHAR(MAX)
    )
""")
cursor.execute(
    "INSERT INTO #Configs (ConfigData) VALUES (%(data)s)",
    {"data": json.dumps(data)}
)
conn.commit()

cursor.execute("""
    SELECT JSON_VALUE(ConfigData, '$.name') AS Name
    FROM #Configs
    WHERE JSON_VALUE(ConfigData, '$.active') = 'true'
""")
for row in cursor:
    print(row.Name)  # 'Alice'

パターンマッチングには LIKE を使い、より高度なテキスト検索のために全文索引を有効にしてください。

全文クエリ

ワイルドカードパターン付きの LIKE オペレーターは、全文索引が利用できない場合に全文検索の簡単な代替手段を提供します。

# Using CONTAINS (requires full-text index on the table)
cursor.execute("""
    SELECT JobTitle FROM HumanResources.Employee
    WHERE JobTitle LIKE %(search)s
""", {"search": "%Engineer%"})

# Pattern-based search as an alternative to full-text
cursor.execute("""
    SELECT Name FROM Production.Product
    WHERE Name LIKE %(search)s
""", {"search": "%Mountain%"})

ベスト プラクティス

これらのガイドラインを適用して、言語やエンコーディング間で文字列データを正しく扱うことができます。

国際データにはnvarcharをご利用ください

もしある列にUnicodeが含まれているか分からない場合は、 nvarcharを使いましょう。 ストレージコストは控えめで、文字変換によるデータ損失を防ぐことができます。

以下の例は、UnicodeデータとASCIIのみのデータに対する列定義の違いを示しています:

-- Good: supports any language
CREATE TABLE #UserProfile (
    Name NVARCHAR(100),
    Bio NVARCHAR(MAX)
);

-- Limited: ASCII/Latin only
CREATE TABLE #UserProfileAscii (
    Name VARCHAR(100),
    Bio VARCHAR(MAX)
);

文字列長の検証

挿入前にPythonで文字列長を確認し、切断エラーを防ぎ、ユーザーに意味のあるエラーメッセージを提供してください。

def safe_insert(cursor, name: str, max_length: int = 100):
    """Insert with length validation."""
    if len(name) > max_length:
        raise ValueError(f"Name exceeds {max_length} characters")
    
    cursor.execute(
        "INSERT INTO #UserProfile (Name) VALUES (%(name)s)",
        {"name": name}
    )

バイナリ文字列は別々に扱う

エンコードの問題を避けるために、テキスト文字列(Python str、SQL nvarchar)とバイナリデータ(Python bytes、SQL varbinary)を区別してください。

binary_data = b'\x00\x01\x02'  # bytes - use varbinary
text_data = "Hello"            # str - use nvarchar