MySQL 8.0.19。MySQL 8.0 の「メンテナンスリリース」です。
8.0.19より前のMySQLには、「標準SQLのひとつであるVALUES文が実装されていない」という重大な不具合が含まれていたため、バグ修正として本リリースに含まれたようです(真に受ける人がいると困るので、無粋ながら説明しておくと、これ、思いっきり「新機能」ですからね! )
まだあまりよくわかっていないのですが、個人的にはこれは、「テンポラリテーブルを作らなくても、複数の行のデータを作れる」というものなのかなと理解しています。いまのところ。
これまでは、クエリの中で何かの当て込み用に複数行のデータが欲しかった場合には
SELECT 13, "name1"
UNION
SELECT 17, "name2"
UNION
SELECT 19, "name3"
のようにする方法はありました。全然スマートじゃないので、あんまり使うもんじゃないなというのが私の感想(でも、ここぞという時に役に立ったテクニックではあります)。
VALUESは、 これを
VALUES
ROW(13, "name1"),
ROW(17, "name2"),
ROW(19, "name3")
のように書けるもの。
ということで、以前、緯度や経度1度あたりの長さを求める日記を書いたのですが、それを書き直してみたいと思います。今回は1度ではなく1秒の距離を求めてみることにします。
経度1秒の長さ
SELECT ido,
ST_Distance(
ST_GeomFromText(CONCAT("POINT(", ido, " ", 135, ")"), 6668),
ST_GeomFromText(CONCAT("POINT(", ido, " ", 135+(1/60/60), ")"), 6668)
) dist
FROM (VALUES ROW(10),ROW(20),ROW(30),ROW(40),ROW(50),ROW(60),ROW(70),ROW(80),ROW(89),ROW(35),ROW(0)) as t(ido)
ORDER BY ido;
結果:
+-----+--------------------+
| ido | dist |
+-----+--------------------+
| 0 | 30.922293347363215 |
| 10 | 30.45543707038192 |
| 20 | 29.068816191795264 |
| 30 | 26.801938223437794 |
| 35 | 25.357920979131784 |
| 40 | 23.720694278317538 |
| 50 | 19.91554019501125 |
| 60 | 15.500025396953356 |
| 70 | 10.607016183528014 |
| 80 | 5.387103340492138 |
| 89 | 0.5478026839370822 |
+-----+--------------------+
11 rows in set (0.00 sec)
赤道付近で約30m、北緯35度付近で 25m強となっていることが見て取れます。
テキストエディタでINSERT文を作ったり、作業のためだけにテーブルを作ったりする方法よりもずっとスマートですね。 ST_GeomFromText()に渡すWKTを、CONCATで作っているところがちょっと格好悪いけど。
緯度1秒の長さ
同様に、緯度1秒の長さを求めてみます。
SELECT
ido,
ST_Distance(
ST_GeomFromText(CONCAT("POINT(", ido, " 135)"), 6668),
ST_GeomFromText(CONCAT("POINT(", ido+1/60/60, " 135)"), 6668)
) dist
FROM (VALUES ROW(10),ROW(20),ROW(30),ROW(40),ROW(50),ROW(60),ROW(70),ROW(80),ROW(89),ROW(35),ROW(0)) as t(ido)
ORDER BY ido;
+-----+--------------------+
| ido | dist |
+-----+--------------------+
| 0 | 30.71497472832411 |
| 10 | 30.724353692261502 |
| 20 | 30.75135882757604 |
| 30 | 30.792732916306704 |
| 35 | 30.817301218397915 |
| 40 | 30.843485630117357 |
| 50 | 30.897495447658002 |
| 60 | 30.948247983120382 |
| 70 | 30.989621736109257 |
| 80 | 31.016626415774226 |
| 89 | 31.02591015584767 |
+-----+--------------------+
11 rows in set (0.00 sec)
以前の日記で書いたとおり、極付近に近づくほど、緯度1秒の長さが長くなっていることが見て取れます。
2桁の数字を作る
確かセルコさんあたりが本に書いていた気がするのですが、クエリの中で2桁の整数が欲しくなったときのちょっとしたテクニック。 0~9の数字10件だけを登録しておいたテーブルを使う例だったと記憶していますが、VALUES文を使うと、それを、テーブルを使わずにできるようになります。
WITH t AS (
SELECT * FROM (VALUES ROW(1),ROW(2),ROW(3),ROW(4),ROW(5),ROW(6),ROW(7),ROW(8),ROW(9),ROW(0)) AS t(n)
)
SELECT CONCAT(t1.n, t2.n) num
FROM t t1, t t2
ORDER BY num;
+------+
| num |
+------+
| 00 |
| 01 |
| 02 |
: : :
| 98 |
| 99 |
+------+
今まで使ってきたSQLの感覚に馴染むように、上のように書いてみましたが、実は VALUES 文は「文」ですから、これ自体が値を返すのです。
たとえば、冒頭で UNION との比較で紹介した例の実行結果は、こう。
mysql> VALUES
-> ROW(13, "name1"),
-> ROW(17, "name2"),
-> ROW(19, "name3")
-> ;
+----------+----------+
| column_0 | column_1 |
+----------+----------+
| 13 | name1 |
| 17 | name2 |
| 19 | name3 |
+----------+----------+
3 rows in set (0.00 sec)
ということで、先ほどのCTEの中身から、余計な SELECT 文を取り去ってしまいましょう。ほら、こんなふうに書けるんです。
WITH t(n) AS (
VALUES ROW(1),ROW(2),ROW(3),ROW(4),ROW(5),ROW(6),ROW(7),ROW(8),ROW(9),ROW(0)
)
SELECT CONCAT(t1.n, t2.n) num
FROM t t1, t t2
ORDER BY num;
感想
「テンポラリのテーブルを作らずに行を生やす」ということに漠然と憧れがあったのですが、まさか本当に実装されるとは思いませんでした。そもそもこれが標準SQLだというのも知りませんでした。 ただ、これはうまく使わないと、クエリが見えにくくなってしまうかもしれないなぁ、とも感じました。いくらでも「わかりにくいクエリ」を作ることができそうです。
また、いちいち「ROW」と書かなきゃいけないのも、ちょっと面倒に感じました。
木村明治さんが2019年夏にRDBMSごとのSQLの比較を発表してくれていましたが、その資料の21ページ以降で VALUES 文が紹介されていました。 さすがです!!! しかも、アイデア自体はMySQL発祥(マルチプルインサート時のVALUES)らしい。へぇぇ。
www.slideshare.net