謎のスプレッド演算子！Snowflakeに突如実装された ** とは！？

小ネタです。

:::note info
2025年4月8日に追記しました。  
公式の紹介ブログなどを追記しています。  
:::

## スプレッド演算子とは？ 

スプレッド演算子(Spread operator)は2025/3/15の[リリース](https://docs.snowflake.com/en/release-notes/2025/9_07)で実装されました。  
「配列を個別の値リストに展開可能」とのことです。  

https://docs.snowflake.com/en/release-notes/2025/9_07

追記：[公式の紹介ブログ](https://www.snowflake.com/en/engineering-blog/sql-spread-operator/)もリリースされました。  

https://www.snowflake.com/en/engineering-blog/sql-spread-operator/

## 配列とスプレッド演算子の使い方

試してみましょう。  
Snowflakeでは配列は以下のような構文になります。  

```sql
SELECT [1, 2, 3] AS ARR;
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2917580/e3e79dbc-2c77-42d5-b9e9-3b3fb510ae1e.png)

スプレッド演算子は以下のような構文で適用します。  

```sql
SELECT **[1, 2, 3] AS ARR;
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2917580/99b5810c-4de2-4a58-94f0-8cffe9e70394.png)

なお、```ARRAY_CONSTRUCT```関数を使っても```[]```で書いたのと同じ挙動だそうです。ふむふむ。  

```sql
SELECT **ARRAY_CONSTRUCT(1, 2, 3) AS ARR;
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2917580/99b5810c-4de2-4a58-94f0-8cffe9e70394.png)

空の配列を渡すと…クラッシュします。さらばSnowsight。  
実行したときのワークシートが(おそらくキャッシュが残っている間はずっと)開けなくなるので絶対に実行してはいけない。  

```sql
-- これは実行してはいけない
SELECT **[];
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2917580/d37ab849-667e-4bf8-b02d-da8156a51460.png)

半構造化データの処理に便利そうなので、ちょっとそういうテーブルを作成して試してみましょう。  

```sql
-- 準備する
CREATE OR REPLACE TEMPORARY TABLE array_example (id INT, array_column ARRAY);
INSERT INTO array_example (id, array_column)SELECT 1, [1, 2, 3];
INSERT INTO array_example (id, array_column)SELECT 2, [4, 5, 6];
INSERT INTO array_example (id, array_column)SELECT 3, [7, 8, 9];
SELECT * FROM array_example;

-- スプレッド演算子を適用！
SELECT id, **array_column FROM array_example;
```

ナムサン！無慈悲な非対応なのだ！  
「非定数な配列へのスプレッド演算子は非対応です」みたいなメッセージが出ていますね。  

> 000002 (0A000): Unsupported feature 'spread argument with non-constant array input'.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2917580/de067c40-7d26-4fc7-ab7f-de5d464d06ad.png)

例えば以下のような「頑張ってコンパイルしたら定数って分かる可能性あるんじゃない？」みたいなSQL文でもNGでした。  

```sql
-- 動かない！
SELECT **ARR FROM (
  SELECT [1, 2, 3] AS ARR
);
```

~~ということで、最初に示したような```[]```を直接展開するケースを対象にしているようです。~~  
~~おそらく、以下のような書き方を省力化できるものを意図しているんじゃないかなーと想像しています。~~  
追記したように、ストアドプロシージャなどを組み合わせるともっと広範な用途があります。  

```sql
SELECT
  ARR[0] AS A1,
  ARR[1] AS A2,
  ARR[2] AS A3
FROM (
  SELECT [1, 2, 3] AS ARR
);
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2917580/207d666d-c42f-428b-b63d-8a81eb14ff5c.png)

## スプレッド演算子の用途

想像だけで書いているのですが…例えばパラメータを配列としてサッと書いて、そこから展開したいときに便利かもしれません。  
プログラムでSQL文を生成するときにも楽できるシーンがあるかも。  
ぜひ「こんな使い方もできるよ！」というアイディアを教えてください！  

こいつ…```ILIKE```とも組み合わせられるぞ！  

```sql
SELECT * ILIKE 'ARR%2%' FROM (
  SELECT **[1, 2, 3] AS ARR
);
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2917580/024df114-4330-44ad-83a2-2a8c4054b581.png)

## 追記：SnowVillageでのディスカッション

SnowVillageは国内のSnowflakeコミュニティです。  
本記事の公開後、このSnowVillageのSlackで相談したところ、色々とコメントをいただきました。  

- Pythonなどにおける記述に近づけるためだろう。動的SQLで```?```プレースホルダを並べるよりシンプルに書ける
- 可変長引数で使うイメージ
- ストアドプロシージャで使えるはず
- ```ARRAY_CONTAINS```で値が配列に含まれているか判定するよりもずっと早くなるはず
- ```IN```句で使うのでは？

公式の紹介ブログがリリースされる前だったのに、この精度のコメントがいただけるのはありがたすぎる…！  

## 追記：シン・スプレッド演算子の用途

### 関数の可変長引数に展開する

実際にはこの用途がちょこちょこありそう。公式の紹介ブログでも「Other use cases」の最初に取り上げています。  

```sql
-- COALESCEはNULLじゃない最初の要素を返す関数。このケースは1が返ってくる
SELECT COALESCE(**[NULL, 1, 2, 3]);

-- CONCATは文字列結合する関数。このケースは'HelloSnowflake!'という文字列になる
SELECT CONCAT(**['Hello', 'Snow', 'flake!']);

-- LEAST/GREATESTは最小/最大を返す関数
SELECT GREATEST(**[10, 100, 1000]);
-- 
```

一連の可変長引数に対して、複数の配列から展開してもOK。これは可能性を感じますね！  

```sql
-- 3つの配列をすべて一連の可変長引数に展開する。結果として20が最大として選ばれる
SELECT GREATEST(**[10, 18, 14], **[13, 2, 16], **[1, 20, 4, 9]);
```

### Snowflake Scriptingと組み合わせる

前述の可変長引数に展開するサンプルコードのように```[]```を直接展開するケースに使うなら、その中身を最初から並べて書いたらいいのでは？と思っていたのですが、Snowflake Scriptingと組み合わせると以下のようなことができます。  

```sql
DECLARE
    -- 配列を定義する
    INPUT_ARRAY ARRAY DEFAULT [1, 2, 3, 4];
    OUTPUT RESULTSET;
BEGIN
    -- 定義した配列を参照する
    OUTPUT := (SELECT COALESCE(**:INPUT_ARRAY));
    RETURN TABLE(OUTPUT);
END;
```

こういう書き方だと```[]```の中身を書く場所が```DECLARE```に集約できるので、SQL文が少し抽象化されていい感じですね！  

### ストアドプロシージャで使う

Snowflake Scriptingで使えるということは、ストアドプロシージャで使えるということ…！  
まずは匿名ストアドプロシージャで試してみましょう！  

```sql
-- WITH句で定義すると匿名ストアドプロシージャを作れる。実装言語はSQLを指定
WITH ANONYMOUS_PROC AS PROCEDURE (INPUT_ARRAY ARRAY)
    RETURNS TABLE(VAL INTEGER)
    LANGUAGE SQL
    AS
    $$
    DECLARE
        OUTPUT RESULTSET;
    BEGIN
        OUTPUT := (SELECT LEAST(**:INPUT_ARRAY));
        RETURN TABLE(OUTPUT);
    END;
    $$
CALL ANONYMOUS_PROC([7, 5, 2, 3]);
-- 結果として2が得られる
```

動いたァァァァ！
そして、もちろん通常のストアドプロシージャでも使えます。  

```sql
-- 実装言語はSQLを指定
CREATE OR REPLACE PROCEDURE ARR_SPREAD_PROC(INPUT_ARRAY ARRAY)
RETURNS TABLE(VAL INTEGER)
LANGUAGE SQL
AS
DECLARE
    OUTPUT RESULTSET;
BEGIN
    OUTPUT := (SELECT LEAST(**:INPUT_ARRAY));
    RETURN TABLE(OUTPUT);
END;

-- 動作確認1：結果として2が得られる
CALL ARR_SPREAD_PROC([2, 3]);
-- 動作確認2：違うサイズの配列でもOK
CALL ARR_SPREAD_PROC([5, 7, 2, 3, 5, 7]);
-- 動作確認3：ストアドプロシージャの引数をSQLから与えても大丈夫
CALL ARR_SPREAD_PROC(SELECT [2, 3, 5, 7, 11]);
```

やったぁ！  
公式の紹介ブログによるとUDF(User Defined Function)でもOKだそうです。  

### ```IN```句でも使える

ストアドプロシージャ内における```IN```句で使うのが最も有用そう。  

```sql
WITH FILTER_CUSTOMER AS PROCEDURE (INPUT_ARRAY ARRAY)
    RETURNS TABLE(C_CUSTKEY INTEGER, C_NAME STRING)
    LANGUAGE SQL
    AS
    $$
    DECLARE
        OUTPUT RESULTSET;
    BEGIN
        OUTPUT := (
            SELECT C_CUSTKEY, C_NAME
            FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.CUSTOMER
            WHERE C_CUSTKEY IN (**:INPUT_ARRAY)
        );
        RETURN TABLE(OUTPUT);
    END;
    $$
CALL FILTER_CUSTOMER([60001, 60035, 60073]);
```

以下のようになります！ちゃんと動いてそう。  

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2917580/c517c53e-bd32-4fde-ab93-aeed33949034.png)

スプレッド演算子がない場合、```FLATTEN```して```TABLE```した結果を```SELECT```する必要があります。かなりシンプルになりますね。  

```sql
-- もし、世界にスプレッド演算子がなかったら
WITH FILTER_CUSTOMER AS PROCEDURE (INPUT_ARRAY ARRAY)
    RETURNS TABLE(C_CUSTKEY INTEGER, C_NAME STRING)
    LANGUAGE SQL
    AS
    $$
    DECLARE
        OUTPUT RESULTSET;
    BEGIN
        OUTPUT := (
            SELECT C_CUSTKEY, C_NAME
            FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.CUSTOMER
            WHERE C_CUSTKEY IN (
                SELECT VALUE FROM TABLE(FLATTEN(:INPUT_ARRAY))
            )
        );
        RETURN TABLE(OUTPUT);
    END;
    $$
CALL FILTER_CUSTOMER([60001, 60035, 60073]);
```

### ストアドプロシージャでもこのパターンはダメ

```sql
-- NULLが含まれていると結果はNULLになる
SELECT LEAST(5, 1, 2, 3, NULL, 7);
-- 上記と同じ挙動
SELECT LEAST(**[5, 1, 2, 3, NULL, 7]);
-- これをストアドプロシージャ＋スプレッド演算子でやろうとすると動かない！
CALL ARR_SPREAD_PROC([5, 1, 2, 3, NULL, 7]);
```

上記のようにNULLを含む配列が渡されると残念ながら動きません。無念。

> 000002 (0A000): Uncaught exception of type 'STATEMENT_ERROR' on line 4 at position 14 : Unsupported feature 'spread argument with non-constant array input'.

## 追記：SnowVillageに参加するには？

SnowVillageは主にSlackで活動しています。日々、Snowflakeの活用方法やベストプラクティス、悩み事についてかなり活発にディスカッションしています。  
下記のサイトに参加規約とSlackへの招待リンクがありますので、興味のある方はぜひ参加してくださいね！  

https://usergroups.snowflake.com/snowvillage/