MySQLで同じ行ごとに結合する
SQLで同じ行ごとに結合することはできない。INNER JOIN
, LEFT JOIN
なら結合カラムを指定するし、指定しないでCROSS JOIN
になると左表 * 右表
になってしまう。
しかし、テストデータを作成するときなど、行ごとに結合したくなる時がある。
テストデータの作成例
テストデータ作成の例として以下を考える。
persons
テーブルにはid
とage
カラムが存在する。改修によってheight
列を追加することになった。160, 170, 180の値を既存の行に設定したい。
現在、select * from persons;
とすると以下のようになっている。
id | age |
---|---|
1 | 20 |
2 | 30 |
3 | 35 |
row_number() over()
MySQLではrow_number() over()
でSELECTした結果の行番号が取れる。
select row_number() over() as rn, id, age from persons order by id;
結果
rn | id | age |
---|---|---|
1 | 1 | 20 |
2 | 2 | 30 |
3 | 3 | 35 |
これを利用して結合する。
結合する際の左表と右表をそれぞれサブクエリにしてrow_number() over()
を列に追加し、これを結合カラムとすれば行番号同士の結合ができる。
select *
from (select row_number() over() as rn, id, age from persons order by id) a
join (select row_number() over() as rn, id, age from persons order by id) b
using(rn);
結果
rn | id | age | id | age |
---|---|---|---|---|
1 | 1 | 20 | 1 | 20 |
2 | 2 | 30 | 2 | 30 |
3 | 3 | 35 | 3 | 35 |
つまり、160, 170, 180が入ったテーブルがあるのであれば、row_number() over()
を利用してid: 1
には160をid: 2
には170をid: 3
には180を設定することができる。
ただテストデータを作るためだけにそのようなテーブルを1から作るのは面倒臭い。
テストデータはJSON配列で用意し、JSON_TABLEでテーブルにする
テストデータはJSON配列で用意して、JSON_TABLE
でテーブルにしてあげると、実テーブルを作成するよりも楽。
今回の例でいうと、[160, 170, 180]
というJSON配列をベタ書きし、JSON_TABLE
を使ってテーブルに変換する。
select
id,
age,
height
from
(select
row_number() over() as rn,
id,
age
from persons
order by id
) a
join
(select
row_number() over() as rn,
height
from json_table(
'[160, 170, 180]',
'$[*]' columns (height int path '$')) inb
) b
using(rn);
結果
id | age | height |
---|---|---|
1 | 20 | 160 |
2 | 30 | 170 |
3 | 35 | 180 |
JSON配列のindexをrow_number()で指定し、JSON_EXTRACTで取り出す
先ほどの方法でJSON配列を用いたが、JSON配列なら結合しない方法もある。
JSON_EXTRACT
を使えばJSONからJSONのパスを指定して値を取り出せるので、以下のように$[0]
とすることで、全員height: 160
で取得できる。
select id, age, json_extract('[160, 170, 180]', '$[0]') height from persons order by id;
$[0]
の部分が0
固定ではなくrow_number() over() - 1
になっていれば160, 170, 180の順で取り出せるため、$[
とrow_number() over() - 1
と]
をconcat
で結合すればいい。
select
id,
age,
json_extract(
'[160, 170, 180]',
concat('$[',
row_number() over() - 1,
']')
) height
from persons
order by id;
pasteコマンドでSQLを組み立てる
SQLでpersons
テーブルをSELECTするだけにして、シェルなどを使ってSQLを組み立てることも考えられる。paste
コマンドを使ってみる。
## selectした結果をファイル名selectedに貼り付け
$ cat << EOF > selected
1,20
2,30
3,35
EOF
## selectedの行数分、insert ~ values(までを出力し、ファイル名insertに書き出す
$ for i in $(seq 1 $(wc -l selected | awk '{print $1}'))
do
echo "insert into persons(id, age, height) values("
done > insert
## ファイル名dataに設定したいデータを記載
$ cat <<EOF > data
,160
,170
,180
EOF
## selectedの行数分、);を出力し、ファイル名insertendに書き出す
$ for i in $(seq 1 $(wc -l b | awk '{print $1}'))
do
echo ");"
done > insertend
## pasteで行ごとに連結してSQLを出力する
$ paste insert selected data insertend
以下のSQL文が生成できる。
insert into persons(id, age, height) values( 1,20 ,160 );
insert into persons(id, age, height) values( 2,30 ,170 );
insert into persons(id, age, height) values( 3,35 ,180 );
列数が少ないなどシンプルな場合は簡単に作れる。
環境
MySQL 8.0