Splunk: サブサーチを使わないSPLの置き換え例あれこれ


###### 実施環境： Splunk Free 8.2.2

### 0. 概要

 Splunk では複数の検索データを組み合わせるのに、しばしばサブサーチを使用します。
 join コマンドや append コマンドでサブサーチを組み合わせるのは直感的にわかりやすいため、ついつい頼ってしまいがちです。

ですが、サブサーチというのは内部的には結局サーチをもう1つ実行しているわけですので、性能的にみるとあまり好ましい選択肢ではありません。
また、 join コマンドや append コマンドはその仕様として結合できるサブサーチのデータ数に上限があるため、巨大なデータを扱う場合には思っていた結果が得られない場合もあります。

そこで、「サブサーチを使わない SPL 」というものが求められてきます。
今回はそのような、「サブサーチを使わない SPL 」の例をいくつか挙げてみました。

なお、こういった「サブサーチを使わない SPL 」はぱっと見でわかりにくいものも多いので、データ量の少ない環境などではサブサーチを用いた読みやすい SPL の方が望ましい場合もあります。
また、 join コマンドなどをフルに活用して非常に複雑な結合をしているような場合は、「サブサーチを使わない SPL 」への置き換えが困難なパターンもありえます。
どのような SPL が最善かは環境や用途などにより変わってきますので、サブサーチの有無に固執せず、臨機応変に使い分けられるようになるとベストです。

### 1. append コマンドで単純にデータを並べているパターン

まずは、単純にデータを並べているパターンを考えます。

#### 1.1. 通常の検索

普通に search コマンドで検索した結果を並べているパターンを考えます。

![WS000842.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/8ec35d5e-a4f7-34e8-9165-cbe9c444b55e.jpeg "image_1_1")

```Splunk:Splunk
index="main" type="A"
| rename num_1 AS num_0, name_1 AS name_0
| append
  [ search index="main" type="B" 
    | rename num_2 AS num_0, name_2 AS name_0 ]
| table type, num_0, name_0
| sort name_0
```

![WS000845.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/0d7b6f7f-19c8-cc28-5aba-de62f1858be1.jpeg "search_1_1_1")

この場合、素直に最初の検索で両方検索すればサブサーチを使わずに済みます。
異なるフィールド名を統一したい場合は、 eval コマンドの if 関数や case 関数、 coalesce 関数などを使うとよいです。
 if 関数や case 関数は、指定した条件に合致するかどうかで返す値を変える関数です。
 coalesce 関数は、指定した複数のフィールドについて空かどうかを先頭から確認し、最初に空でなかったフィールドの値を返す関数です。

```Splunk:Splunk
index="main" type IN ("A", "B")
| eval num_0  = coalesce(num_1,  num_2),
       name_0 = coalesce(name_1, name_2)
| table type, num_0, name_0
| sort name_0
```

![WS000846.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/6500c84f-904f-62e4-5340-4603bf0a9ad0.jpeg "search_1_1_2")

ちなみに、フィールド名の変更といえば rename コマンドもありますが、今回のように異なるフィールドを同じフィールド名に統合するのには使えません。
以下の例では num_1 , num_2 を num_0 に、 name_1 , name_2 を name_0 にリネームしていますが、後で適用した num_2 , name_2 の値しか残っていません。
これは、 num_1 , name_1 をリネームした後 num_2 , name_2 を同じフィールド名にリネームしたことで、結果が上書きされてしまっているためです。

```Splunk:Splunk
index="main" type IN ("A", "B")
| rename num_1  AS num_0,  num_2  AS num_0,
         name_1 AS name_0, name_2 AS name_0
| table type, num_0, name_0
| sort name_0
```

![WS000847.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/1b4669ec-befc-bd7d-adb3-cc1908ebedcb.jpeg "search_1_1_3")

#### 1.2. 特殊な検索

 where コマンドなどで特殊なデータの絞り込み方をしているパターンも考えてみます。

![WS000843.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/85e8feac-cb2e-55eb-5128-03c1377197a2.jpeg "image_1_2")

```Splunk:Splunk
index="main" type="A"
| where match(num_1, "^[1-3]01$")
| rename num_1 AS num_0, name_1 AS name_0
| append
  [ search index="main" type="B" 
    | where match(num_2, "^1[0-9]*$")
    | rename num_2 AS num_0, name_2 AS name_0 ]
| table type, num_0, name_0
| sort name_0
```

![WS000848.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/ecca9326-1888-26a3-884f-7ccd1aa93ee2.jpeg "search_1_2_1")

このようなパターンでも、 if 関数や case 関数などをうまく用いればサブサーチなしにできる場合があります。

```Splunk:Splunk
index="main" type IN ("A", "B")
| where (type="A" and match(num_1, "^[1-3]01$")) or 
        (type="B" and match(num_2, "^1[0-9]*$"))
| eval num_0  = coalesce(num_1,  num_2),
       name_0 = coalesce(name_1, name_2)
| table type, num_0, name_0
| sort name_0
```

![WS000849.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/ffde9cba-fef6-6107-8b8d-184920d975fc.jpeg "search_1_2_2")

よほど特殊なパターンでもない限り、 append コマンドは使わず単にすべて最初に検索してしまったほうが、変にデータ上限に引っかかることもなくなるのでベターかと思います。

### 2. join コマンドで異なるデータを組み合わせているパターン

次に、特定のフィールドをキーにして join コマンドで異なるデータを組み合わせているパターンを考えます。

#### 2.1. 1 対 1 の組み合わせ

2つのデータが完全に 1 対 1 で対応しているパターンを考えます。

![WS000852.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/9ed1f7df-579f-2b56-cc29-8372c8138e2a.jpeg "image_2_1")

```Splunk:Splunk
index="main" type="A"
| rename num_1 AS num_0
| join num_0
  [ search index="main" type="C"
    | rename num_2 AS num_0 ]
| table num_0, name_1, name_2
| sort num_0
```

![WS000853.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/a24e8c31-3088-7485-2682-5676a5a0246a.jpeg "search_2_1_1")

このようなパターンでは、 stats コマンドの list 関数や values 関数を用いるとサブサーチをなくせます。
この2つの関数はフィールドに格納されている値をそのまま並べて出力する関数で、 list 関数は重複があってもそのまま、 values 関数は重複を排除して値を返します。
抽出するフィールドをキーとなるフィールドごとにこの関数で集計することで、 join コマンドと同様の結果を得ることができます。

```Splunk:Splunk
index="main" type IN ("A", "C")
| eval num_0  = coalesce(num_1,  num_2)
| stats values(name_1) AS name_1, values(name_2) AS name_2 BY num_0
| sort num_0
```

![WS000864.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/bfc0540d-2bbd-2d19-9639-bb92c79f23c1.jpeg "search_2_1_2")

#### 2.2. N 対 N の組み合わせ

2つのデータが 1 対 1 対応でないパターンも考えてみます。

![WS000858.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/48efa511-fe50-0134-792a-9a1d0e2dfc23.jpeg "image_2_2")

```Splunk:Splunk
index="main" type="D"
| rename num_1 AS num_0
| join num_0
  [ search index="main" type="E"
    | rename num_2 AS num_0 ]
| table num_0, name_1, name_2
| sort num_0
```

![WS000856.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/4cab3f87-5e7b-ce08-29f4-85b8e327d8c5.jpeg "search_2_2_1")

 NULL 値はそこまで考慮する必要がないことも多いですが、サブサーチ側から抽出される値が複数となるようなパターンでは、 mvexpand コマンドなどでマルチバリューを分解する必要があります。

```Splunk:Splunk
index="main" type IN ("D", "E")
| eval num_0  = coalesce(num_1,  num_2)
| stats values(name_1) AS name_1, values(name_2) AS name_2 BY num_0
| mvexpand name_2
| sort num_0
```

![WS000863.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/8831d19f-a357-d2a7-12ea-b17b53bde6fb.jpeg "search_2_2_2")

もっとも、このような N 対 N の結合処理では、そもそも join コマンドを使用した場合においても想定通りに結果が出せないケースも多いです。
内部結合か外部結合か、 join コマンドか stats コマンドか、データの量や更新頻度などによってはルックアップを利用するという選択もあるかもしれません。
データの性質や処理の目的などによって、慎重に SPL を検討するようにしたほうがよいでしょう。

### 3. join コマンドで同一のデータを組み合わせているパターン

最後に、 join コマンドを利用したパターンの中でも特殊な、同一のデータを結合しているパターンを考えます。

![WS000859.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/1dc917c0-fd5a-0e00-5a2b-908e4c722897.jpeg "image_3")

```Splunk:Splunk
index="main" type="X"
| eval num = num_b
| join num
  [ search index="main" type="X"
    | fields num_a, name_a
    | rename num_a AS num, name_a AS name_b ]
| table num_a, num_b, name_a, name_b
| sort num_a, num_b
```

![WS000861.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/8c7fbaa4-6197-cf48-74ff-5f7df87ec953.jpeg "search_3_1")

上の例では、 stats コマンドを用いてもなかなかうまくいきません。
ではどうするかというと、 eventstats コマンドを用います。
 eventstats コマンドは集計結果を各データに付与していくコマンドです。
以下の例ではこれで num_a と name_a のセットのリストを各データに付与し、 num_b と比較して name_b を導出しています。

```Splunk:Splunk
index="main" type="X"
| eval num_name = num_a . "/" . name_a
| eventstats values(num_name) AS num_name_list
| mvexpand num_name_list
| where num_name_list LIKE num_b . "/%"
| eval name_b = replace(num_name_list, "^[^/]*/", "")
| table num_a, num_b, name_a, name_b
| sort num_a, num_b
```

![WS000862.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/c9ccc4b7-986a-f3bf-f606-2501a74be740.jpeg "search_3_2")

かなり複雑な処理にはなってしまいましたが、それでも一応サブサーチを使わないでもやりようはあることがわかります。
やろうと思えば意外とサブサーチはなくせるので、すぐに無理と決めつけずにいろいろ考えてみるのもよいかと思います。