連番の生成~どうしてもループが嫌と言うならしかたない~
これまで、あらかじめ複数行を保持するテーブルを対象に連番を割り当てていました。今度は動的に連番を生成する方法を考えます。
ループを使わずに1行のデータをn行に増やす
表7のような1行だけデータを持つテーブルがあります。データの中身は重要ではないので気にしないでください。
みなさんに考えてほしいのは、この1行のデータを3行に増やしてもらうことです。しかもその際、図6のように連番を付与します。
ここで条件が1つあります。それは、3行に限らずn行に簡単に一般化できる方法であることです。もしこの問題を手続き型言語で解くなら、3回ループして、カウンタの変数を連番列に使えばよいでしょう。問題とも呼べないぐらい簡単な話です。しかしSQLにおいては、基本的に「ループ」という手続きを使いません。代わりにSQLは、集合同士の演算によって集合を次々と組替え、求める集合にたどり着きます。
効率的な演算は何か
おそらく誰もが最初に思いつく方法は、UNIONで3つの行を「足し算」することでしょう(リスト11)。重複行は発生しないので、UNION ALLが利用できます。確かにこれでも求める結果は得られます。しかしお世辞にも「拡張性の高い」コードとは呼べません。「1000万行生成したい」と言われたら、この方法を使う猛者はいないでしょう。
ではどうするか? 数を増やしたいなら、足し算よりもっと効率的な演算があるではありませんか。そう、「掛け算」です。これを利用しない手はありません。SQLにおける掛け算に相当するのは結合です。したがってこのケースならば、表8のような補助テーブルを用意してクロス結合すれば、「1×3=3」という演算のできあがりです(リスト12)。
連番ビューSequenceを作る
あとはSeqテーブルの行数を増減させることができれば、連番つきで何行でも行数を増やすことが可能になります。そのためには、Seqテーブルをビューにしておくことが最も簡単でしょう。まずは十分な大きさを持つ連番テーブルSequenceを作る必要がありますが、これは伝統的にのように各桁の数字を組み合わせることで可能なことが知られています(表9、リスト13)。
表9 Digits
digit(数文字) |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
Sequenceビューを作るクエリは、各桁の数字0~9についてクロス結合ですべての組み合わせを求めています。D1が1の位、D2が10の位、D3が100の位を表します。あとは同様にDn集合を追加することで、どんなに大きな連番テーブルでも思うままに作れます。そうして作られたSequenceビューから、BETWEEN述語によって適当な範囲を切り出しているわけです。SQLらしい、集合演算を駆使した方法です。
なお、パフォーマンス上の注意を促しておくと、クロス結合はSQLの演算の中で最も高コストなため、できる限りビューを使わないようSequenceをテーブルとして保持しておくのが現実的です。そうすればseq列の主キーのインデックスが利用できるため、Seqビューを作るクエリが高速化されます。
また、連番生成の方法は、実装依存のものも含めれば上記以外にも何通りかあります。興味深いものが多いので、みなさんも考えて/探してみてください[4]。