DO's&DONT's #2: 絶対にやらなければいけないこと - データ型を一致させる
神谷 雅紀
SQL Server Eascalation Engineer
クエリを書く場合、比較を行うデータのデータ型は一致させておくことが必要です。
なぜ?
データ型が一致していない場合、必ず、どちらかのデータがもう片方のデータのデータ型に変換された後に比較が行われます。つまり、SQL Server は、データ型変換という余分な処理を行わなければならなくなります。また、それだけではなく、データ型を一致させるために、本来読み取る必要のないデータを読み取らなければならなくなることもあります。
どのような影響があるのか?
クエリのパフォーマンス悪化です。比較するデータのデータ型が一致していない場合、データ型の優先順位に従って、優先順位の低いデータ型のデータが、優先順位の高いデータ型に変換されます。
データ型の優先順位 (Transact-SQL)
https://msdn.microsoft.com/ja-jp/library/ms190309.aspx
例えば、以下のクエリを考えます。
CREATE TABLE TableA (C1 varchar(10))
GO
SELECT * FROM TableA WHERE C1=N'XXX'
GO
SELECT の WHERE 句に指定されている C1 は VARCHAR(10) ですので非 Unicode 型ですが、比較対象の N’XXX’は Unicode 型です。
この場合、何が発生するのでしょう?実行プランを見てみます。
|--Table Scan(OBJECT:([testdb1].[dbo].[TableA]), WHERE:(CONVERT_IMPLICIT(nvarchar(10),[testdb1].[dbo].[TableA].[C1],0)=[@1]))
CONVERT_IMPLICIT() が入っています。これは、その名のとおり、暗黙のデータ型変換です。データ型が異なっているため、データ型を一致させる処理が必要になります。
もし、データ型が一致していたら、どうでしょうか?
SELECT * FROM TableA WHERE C1='XXX'
当然、データ型変換は不要になります。
|--Table Scan(OBJECT:([testdb1].[dbo].[TableA]), WHERE:([testdb1].[dbo].[TableA].[C1]=[@1]))
これは単純な例ですが、実際には、データ型を変換するためには、変換対象のデータを一旦読み取り、データ型を変換し、データを比較という処理が必要になるため、インデックスによる絞込みを効率的に行うことができず、著しくパフォーマンスが悪化することがあります。
どのように対応するか?
データ型を一致させます。
陥りがちなパターン 1 – decimal/numeric 定数の表記
日本では decimal/numeric が好まれる傾向がありますが、decimal/numeric 定数には、必ず小数点が必要であることは、見落とされがちです。
以下の例では、列 C1 は decimal(10,0) と定義されています。これに対して、SELECT の WHERE 句では定数 1 が指定されています。
CREATE TABLE TableB (C1 decimal(10,0))
GO
SELECT * FROM TableB WHERE C1=1
GO
列 C1 と定数 1 のデータ型は一致しているでしょうか?実行プランを見てみます。
|--Table Scan(OBJECT:([testdb1].[dbo].[TableB]), WHERE:([testdb1].[dbo].[TableB].[C1]=CONVERT_IMPLICIT(decimal(10,0),[@1],0)))
CONVERT_IMPLICIT() によって、定数 1 は decimal(10,0) に変換されています。つまり、データ型は一致していません。では、どう記述するべきでしょうか?
答えは、1.0 もしくは 1. です。
SELECT * FROM TableB WHERE C1=1.0
|--Table Scan(OBJECT:([testdb1].[dbo].[TableB]), WHERE:([testdb1].[dbo].[TableB].[C1]=[@1]))
Books Online では、decimal 定数は次のように定義されています。
decimal 型定数は、小数点を含む数値文字列で表し、引用符では囲みません。
定数 (Transact-SQL)
https://msdn.microsoft.com/ja-jp/library/ms179899.aspx
陥りがちなパターン 2 – JDBC での接続文字列の指定
JDBC を用いて SQL Server に接続する場合、Prepared Statement の文字列パラメータを Unicode とするか非 Unicode とするかは、接続文字列パラメータsendStringParametersAsUnicode で指定します。このパラメータが適切に設定されていない場合、データ型の不一致が発生します。
接続プロパティの設定
https://technet.microsoft.com/ja-jp/library/ms378988.aspx
陥りがちなパターン 3 – 文字列定数の N プレフィックス忘れ
NCHAR, NVARCHAR などの Unicode 型との比較を行う文字列定数には、N’ABC’ のように、N プレフィックスが必要です。
以上のように、データ型が一致しているかどうかは、クエリの実行プランを見ることで確認できます。実行プランの確認方法は、以下の Books Online トピックに記載されています。
SQL Trace を用いて確認する場合
Showplan All イベント クラス
https://msdn.microsoft.com/ja-jp/library/ms191283.aspx
Showplan Text イベント クラス
https://msdn.microsoft.com/ja-jp/library/ms191314.aspx
Showplan XML イベント クラス
https://msdn.microsoft.com/ja-jp/library/ms189318.aspx
Management Studio 等のクエリ実行ツールからクエリを実行して確認する場合
グラフィカル実行プランの表示 (SQL Server Management Studio)
https://msdn.microsoft.com/ja-jp/library/ms178071.aspx
SET SHOWPLAN_ALL (Transact-SQL)
https://msdn.microsoft.com/ja-jp/library/ms187735.aspx
SET SHOWPLAN_TEXT (Transact-SQL)
https://msdn.microsoft.com/ja-jp/library/ms176058.aspx
SET SHOWPLAN_XML (Transact-SQL)
https://msdn.microsoft.com/ja-jp/library/ms187757.aspx