MySQL8.0.19で加わった VALUES を試してみる

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