2018年5月24日木曜日

"「7の倍数」を表す正規表現" をMySQL 8.0で試す

1年半くらい前に書かれたらしいけれど、ふと今日 「7の倍数」を表す正規表現 - Qiita を見つけて読んだ。
(取り敢えず今の俺の中で)正規表現といえばMySQL 8.0。
そして(取り敢えず今の俺の中で)forループ的に数値をテストするといえばCTE、CTEといえばMySQL 8.0。
やってみます。

さすがに元の正規表現は長くて直接クエリーに記述してるとめげるのでストアドファンクションにラップする。
mysql80 26> CREATE FUNCTION regexp_7(n BIGINT UNSIGNED) RETURNS INT DETERMINISTIC RETURN n RLIKE '\\A(((((([07]|(6[29]*3) <snip> )))))))))+\\z';
Query OK, 0 rows affected (0.03 sec)
バックスラッシュは二重にしなければならない、くらいで意外とすんなりストアドファンクションにできた。動くかどうかはわからない。
mysql80 26> WITH RECURSIVE seq AS(
    -> SELECT 1 AS n
    -> UNION ALL
    -> SELECT n + 1 FROM seq WHERE n < 10
    -> )
    -> SELECT n FROM seq;
+------+
| n    |
+------+
|    1 |
|    2 |
|    3 |
|    4 |
|    5 |
|    6 |
|    7 |
|    8 |
|    9 |
|   10 |
+------+
10 rows in set (0.00 sec)
そしてまあ簡単な再起CTE。
組み合わせるとこうなる。
mysql80 26> WITH RECURSIVE seq AS( SELECT 1 AS n UNION ALL SELECT n + 1 FROM seq WHERE n < 10 ) SELECT n, regexp_7(n) AS r FROM seq;
+------+------+
| n    | r    |
+------+------+
|    1 |    0 |
|    2 |    0 |
|    3 |    0 |
|    4 |    0 |
|    5 |    0 |
|    6 |    0 |
|    7 |    1 |
|    8 |    0 |
|    9 |    0 |
|   10 |    0 |
+------+------+
10 rows in set (0.12 sec)
nが正規表現にマッチした時はrが1、マッチしなければ0。
取り敢えず1~10の範囲では問題なく(MySQLの正規表現エンジンが)動いている様子。
mysql80 26>  WITH RECURSIVE seq AS(
    -> SELECT 1 AS n, 0 AS r
    -> UNION ALL
    -> SELECT n + 1, regexp_7(n + 1) FROM seq WHERE n < 49
    -> )
    -> SELECT n, r FROM seq WHERE r = 1;
+------+------+
| n    | r    |
+------+------+
|    7 |    1 |
|   14 |    1 |
|   21 |    1 |
|   28 |    1 |
|   35 |    1 |
|   42 |    1 |
|   49 |    1 |
+------+------+
7 rows in set (0.56 sec)
いい感じに見やすくなったのでnを1~49まで増やしてる。重い。
ついでにnと前回のn(LAG(n))の差を取れば常に7になるはずだと思ってWindow関数にも手を出してみる。
mysql80 26>  WITH RECURSIVE seq AS(
    -> SELECT 1 AS n, 0 AS r
    -> UNION ALL
    -> SELECT n + 1, regexp_7(n + 1) FROM seq WHERE n < 49
    -> )
    -> SELECT n, r, n - LAG(n) OVER (ORDER BY n) AS diff FROM seq WHERE r = 1;
+------+------+------+
| n    | r    | diff |
+------+------+------+
|    7 |    1 | NULL |
|   14 |    1 |    7 |
|   21 |    1 |    7 |
|   28 |    1 |    7 |
|   35 |    1 |    7 |
|   42 |    1 |    7 |
|   49 |    1 |    7 |
+------+------+------+
7 rows in set (0.62 sec)
じゃあここまでをもう一段CTEに閉じ込めて、diff <> 7のものを探してみる。
mysql80 26> WITH RECURSIVE seq AS( SELECT 1 AS n, 0 AS r UNION ALL SELECT n + 1, regexp_7(n + 1) FROM seq WHERE n < 1000 ),
    ->                     ret AS( SELECT n, r, n - LAG(n) OVER (ORDER BY n) AS diff FROM seq WHERE r = 1)
    -> SELECT * FROM ret WHERE diff <> 7;
Empty set (12.60 sec)
1~1000で12秒なら、1万件で2分くらいかしらん、と思ったら↓に当たった。
mysql80 26> SET cte_max_recursion_depth = 100000;
Query OK, 0 rows affected (0.00 sec)

mysql80 26> WITH RECURSIVE seq AS( SELECT 1 AS n, 0 AS r UNION ALL SELECT n + 1, regexp_7(n + 1) FROM seq WHERE n < 10000 ),
    ->                     ret AS( SELECT n, r, n - LAG(n) OVER (ORDER BY n) AS diff FROM seq WHERE r = 1)
    -> SELECT * FROM ret WHERE diff <> 7;
Empty set (2 min 19.87 sec)
うん、楽しい、と、思う。

0 件のコメント :

コメントを投稿