複数の列で in 句を使いたい

タイトルのような質問をもらったのでちょっと書いてみる。

テーブルはこんなのを用意しました。

create table 受注 (
  受注番号 char(6) not null, 
  受注枝番 char(2) not null, 
  金額 int not null, 
  constraint PK_受注 primary key (受注番号, 受注枝番)
)

複数の受注番号、受注枝番の組み合わせでデータを引っ張りたいという質問です。

Oracle 等の、行式をサポートしている DB はこんな風に書けます。

select * from 受注
where (受注番号, 受注枝番) in ( ('151201', '01'), ('151202', '02'), ...)

しかし、SQL Server は行式をサポートしていません。

というわけで、代替案はこんな感じ。

select * from 受注
where 受注番号 + 受注枝番 in ( '15120101', '15120202', ...)

今回は受注番号、受注枝番が桁数固定の前提でクエリ書いてます。
列の定義が varchar で桁数が不定の場合は、区切り文字を使わないと正しく取れないケースがあります。

受注番号 受注枝番
151201 01
15 01
150 1

こういうデータがあるとき、受注番号と受注枝番を単に結合すると、2行目と3行目の値が同じになってしまいます。

受注番号 受注枝番 単に結合
151201 01 15120101
15 01 1501
150 1 1501

こういう場合は、絶対に存在しない文字列を区切り文字に入れる等の工夫が要りそう。

受注番号 受注枝番 単に結合 "@@@"を区切り文字
151201 01 15120101 151201@@@01
15 01 1501 15@@@01
150 1 1501 150@@@1

これなら 2行目と3行目の区別がつきます。

select * from 受注
where 受注番号 + '@@@' + 受注枝番 in ( '151201@@@01', '15@@@01', '150@@@1', ...)

注意点がいくつかあって、

  • 列が null 許容の場合は、null のデータが取れないように注意する
    • SQL Server で + で文字列結合している場合は null になるので良いが、CONCAT (Transact-SQL) を使ったり null を空文字として扱うDBの場合は注意が必要
  • 結合する列が文字列型以外の場合は、型変換気を付ける
  • 区切り文字は必ずデータ内に含まれない文字列にすること
  • 結合対象の列に指定しているインデックスは効かない
    • あらかじめ結合した結果の列を計算列として追加し、計算列にインデックスを付与するという方法もある。計算列のインデックス

代替案はありますが、行式を使えないとパフォーマンス面で不利になることも多いと思いますので、行式使えるようになるといいですね。

行式を使うクエリの解説はここが良いかな?
第5回 時代錯誤症候群~進化し続けるSQLに取り残されるな!:SQL緊急救命室|gihyo.jp … 技術評論社