BigQuery のループ制御文の使いどころを自分なりに整理してみた

2024.04.30

こんにちは!エノカワです。

BigQuery にはループ処理を実現する制御文が用意されていますが、「こういうことをしたい場合、どの制御文を使えば良いんだっけ?」と迷った経験はありませんか?
(私だけ?)

ループ制御文の使いどころ

ということで、ループ制御文の使いどころを自分なりに整理してみました。

制御文 使いどころ 特徴
LOOP 条件付きでループを抜ける必要がある場合 - BREAK、LEAVE などでループを制御
- 反復回数が事前に分からない場合に適している
REPEAT 少なくとも1回はループ本体を実行したい場合 - 条件判定は反復後に行われる
- 初期化が不要な単純なループに適している
WHILE 事前条件を満たす間ループを実行したい場合 - 条件判定が最初に行われる
- ループ本体が全く実行されない可能性がある
FOR...IN 配列やクエリの結果セットなどの集合に対してループしたい場合 - 集合の要素数分だけ反復が行われる
- 各反復で要素値が自動的に割り当てられる

それぞれの制御文の特徴を踏まえて、以下のようなポイントで使い分けができそうです。

  • 無条件で反復したい場合は LOOP
  • 初回実行が確実に必要な場合は REPEAT
  • 条件を先に評価したい場合は WHILE
  • 集合に対して反復したい場合は FOR...IN

また、以下の制御文を使うことで条件によってループを制御できるので、よりきめ細かなロジックを実装できます。
IF 文と組み合わせて使用します。LOOP, REPEAT, WHILE, FOR..IN いずれのループ制御でも使用できます。

  • BREAK / LEAVE: 現在のループを抜ける
  • CONTINUE / ITERATE: 現在のループの残りの処理をスキップし、次の反復の先頭に進む

使用例

以下、各制御文の特徴を掴むために構文と使用例を見てみましょう。

LOOP

構文

LOOP
  ループ本体;
  ...
  IF 条件式 THEN
    LEAVE;
  END IF;
END LOOP;

条件式が真になるまでループ本体を繰り返し実行します。

使用例

デフォルト値 0 で宣言した変数xが 10 以上になるまで繰り返します。

DECLARE x INT64 DEFAULT 0;

LOOP
  SET x = x + 1;
  IF x >= 10 THEN
    LEAVE;
  END IF;
END LOOP;

SELECT x;
| x   |
| --- |
| 10  |

REPEAT

構文

REPEAT
  ループ本体;
  UNTIL 条件式
END REPEAT;

ループ本体を1回実行した後、条件式が真になるまでループ本体を繰り返します。

使用例

デフォルト値 0 で宣言した変数x3 以上になるまで繰り返します。

DECLARE x INT64 DEFAULT 0;

REPEAT
  SET x = x + 1;
  SELECT x;
  UNTIL x >= 3
END REPEAT;
| x   |
| --- |
| 1   |

| x   |
| --- |
| 2   |

| x   |
| --- |
| 3   |

BREAKまたはLEAVEを使用して、ループを早めに抜けることもできます。
以下では、変数x偶数 の場合にループを抜けています。

DECLARE x INT64 DEFAULT 0;

REPEAT
  SET x = x + 1;
  SELECT x;
  IF MOD(x, 2) = 0 THEN
    BREAK;
  END IF;
  UNTIL x >= 3
END REPEAT;
| x   |
| --- |
| 1   |

| x   |
| --- |
| 2   |

WHILE

構文

WHILE 条件式 DO
  ループ本体;
END WHILE;

条件式が真の間、ループ本体を繰り返します。

使用例

デフォルト値 TRUE で宣言した変数headsFALSE になるまで繰り返します。

DECLARE heads BOOL DEFAULT TRUE;
DECLARE coin_flip ARRAY<STRING>;

WHILE heads DO
  SET heads = RAND() < 0.5;
  SET coin_flip = ARRAY_CONCAT(coin_flip, [IF(heads, 'Heads!', 'Tails!')]);
END WHILE;

SELECT result FROM UNNEST(coin_flip) AS result;
| result |
| ------ |
| Heads! |
| Heads! |
| Tails! |

条件によっては、ループ本体が全く実行されない可能性もあります。
以下では、変数headsのデフォルト値を FALSE で宣言しているので、ループ本体が実行されません。

DECLARE heads BOOL DEFAULT FALSE;
DECLARE coin_flip ARRAY<STRING>;

WHILE heads DO
  SET heads = RAND() < 0.5;
  SET coin_flip = ARRAY_CONCAT(coin_flip, [IF(heads, 'Heads!', 'Tails!')]);
END WHILE;

SELECT result FROM UNNEST(coin_flip) AS result;

FOR...IN

構文

FOR レコード IN (式)
DO
  ループ本体;
END FOR;

式で生成される行に対してループ本体を実行します。
式は配列やクエリ結果のデータセットにも対応しています。

使用例

bigquery-public-data.samples.shakespeareのクエリ結果の レコードの数 だけ繰り返します。

FOR record IN
  (SELECT word, word_count
   FROM bigquery-public-data.samples.shakespeare
   LIMIT 5)
DO
  SELECT record.word, record.word_count;
END FOR;
| word    | word_count |
| ------- | ---------- |
| LVII    | 1          |

| word    | word_count |
| ------- | ---------- |
| augurs  | 1          |

| word    | word_count |
| ------- | ---------- |
| dimm'd  | 1          |

| word    | word_count |
| ------- | ---------- |
| plagues | 1          |

| word    | word_count |
| ------- | ---------- |
| treason | 1          |

配列の要素の数だけ繰り返すこともできます。
以下では、配列fruitsの数だけ繰り返しています。

DECLARE fruits ARRAY<STRING>;
SET fruits = ['Apple', 'Banana', 'Orange', 'Grapes'];

FOR fruit IN (SELECT * FROM UNNEST(fruits) AS name)
DO
  SELECT fruit.name;
END FOR;
| name   |
| ------ |
| Apple  |

| name   |
| ------ |
| Banana |

| name   |
| ------ |
| Orange |

| name   |
| ------ |
| Grapes |

まとめ

以上、BigQuery のループ制御文の使いどころを自分なりに整理してみました。

自分自身への備忘録的な記事になってしまいましたが、他の方にも参考になれば幸いです!

参考