Splunk: 条件付きカウントの方法、またはcount関数内におけるevalの使用について

時々使うのでメモ。

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

###### 目的

 Splunk の stats コマンドでは、 count 関数を使用することでデータの個数を集計することができます。
また、 BY 句を指定することによって指定のフィールドの値ごとに分けた個数を取得することもできます。

```Splunk:Splunk
| makeresults count=10000
| eval NUM = random() % 10
| stats count BY NUM
```

![スクリーンショット 2021-12-12 22.02.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/5843868f-531a-fc8d-f283-a90f75edf2d2.png "stats")

では、「あるフィールドが特定の値であるデータの個数」が欲しい場合はどうすればよいでしょうか。
「特定の値」が単一の値である場合は、以下のように stats コマンドの前か後で where コマンドを使用して対象データを抜き出せば取得できます。

```Splunk:Splunk
| makeresults count=10000
| eval NUM = random() % 10
| where NUM = 0
| stats count BY NUM
```

![スクリーンショット 2021-12-12 22.01.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/285fbbb4-677b-da20-bada-7063870f2fe1.png "where1")

```Splunk:Splunk
| makeresults count=10000
| eval NUM = random() % 10
| stats count BY NUM
| where NUM = 0
```

![スクリーンショット 2021-12-12 21.43.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/e45b7658-b91b-a3ea-c3c5-bd555f81af74.png "where2")

ただ、少々冗長な気がしますし、何よりこれだと複雑な条件には対応できません。

###### 基本

では、どうすればよいでしょうか。
実は、 stats コマンドの count 関数では、「eval(フィールド名=値)」という表現を用いることで、フィールドが特定の値であるデータのカウントを行うことが可能です。

```Splunk:Splunk
| makeresults count=10000
| eval NUM = random() % 10
| stats count(eval(NUM = 0)) AS NUM0
```

![スクリーンショット 2021-12-12 21.45.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/df5cff61-88c3-2e5c-ace3-33a084d52c8c.png "eval1")

なお、この条件付きカウントで得られる集計値は、必ず AS 句を用いてリネームする必要があります。

```Splunk:Splunk
| makeresults count=10000
| eval NUM = random() % 10
| stats count(eval(NUM = 0))
```

![スクリーンショット 2021-12-12 21.46.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/99ed96be-5582-b4a0-5523-99d1a80d30f2.png "no_rename")

###### 応用

この条件付きカウントの条件には、 AND や OR も使用できます。

```Splunk:Splunk
| makeresults count=10000
| eval NUM = random() % 10
| stats count(eval(NUM = 1))            AS NUM1,
        count(eval(NUM = 2))            AS NUM2,
        count(eval(NUM = 1 OR NUM = 2)) AS NUM12
```

![スクリーンショット 2021-12-12 21.48.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/95cd683f-6a3d-8660-ed51-0ec883c75237.png "eval2")

また、 eval コマンドで使用できる様々な関数も使用可能です。

```Splunk:Splunk
| makeresults count=10000
| eval NUM = if(random() % 2 = 0, null(), 3)
| stats count(eval(isnull(NUM)))       AS NULL,
        count(eval(NUM = round(pi()))) AS NUM3
```

![スクリーンショット 2021-12-12 21.54.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/a07da94f-a5f9-181a-4c0a-b0d357cf0918.png "function")

複数のフィールドを使用した条件も作れます。

```Splunk:Splunk
| makeresults count=10000
| eval NUM1 = random() % 10,
       NUM2 = random() % 10
| stats count(eval(NUM1 = NUM2)) AS NUM12
```

![スクリーンショット 2021-12-12 21.56.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/56d5b7f6-ebf8-e08f-f161-65437ff1ab78.png "fields")

なお、あまり複雑な条件を入れてしまうと、非常に読みにくい SPL 文となる可能性があります。
そのような際は以下のように、 stats コマンドの前で条件が成立するなら 1 、それ以外は 0 を示すフラグフィールドを作り、その合計をとるようにすると整理しやすいです。

```Splunk:Splunk
| makeresults count=10000
| eval NUM = random() % 10
| eval FLG = if(NUM = 0, 1, 0)
| stats count(eval(NUM = 0)) AS NUM0,
        sum(FLG)             AS FLG0
```

![スクリーンショット 2021-12-12 21.59.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/b36aa616-0df7-3409-713f-720127956ae6.png "flag")

###### 補足

この条件付きカウントの方法は、 stats コマンド以外でも使用できます。

```Splunk:Splunk
| makeresults count=10000
| eval NUM = random() % 10
| timechart span=1h count(eval(NUM = 0)) AS NUM0
```

![スクリーンショット 2021-12-19 14.24.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/6333a543-8f2f-7a6f-eece-f2c891989e7d.png "timechart")

また、 count 以外の関数でも使用できます。
ただし、 count 以外の関数では eval 単体だとうまく動作しません。
 eval は「新しいフィールドを作成するコマンド」のため、 if 等を使用して「新しいフィールドに格納する値」をしっかり定義する必要があります。
以下の例では、「3で割り切れる値=0,3,6,9」の時は元の値、それ以外では NULL を格納したフィールドの平均を取得しています。
0ではなく NULL なのは、平均計算時の総データ数を「3で割り切れる値=0,3,6,9」のデータのみに限定するためです。

```Splunk:Splunk
| makeresults count=10000
| eval NUM = random() % 10
| stats avg(eval(   NUM % 3 = 0              )) AS AVG,
        avg(eval(if(NUM % 3 = 0, NUM, 0     ))) AS AVG0,
        avg(eval(if(NUM % 3 = 0, NUM, null()))) AS AVGnull
```

![スクリーンショット 2021-12-19 14.34.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/29b014ff-c4ac-c088-2f0c-ec38c96a0e91.png "avg1")

無論、先に記載したフラグフィールドを作成する方法を応用して使うことも可能です。
何なら、こちらのほうがわかりやすいかもしれません。

```Splunk:Splunk
| makeresults count=10000
| eval NUM = random() % 10
| eval FLG = if(NUM % 3 = 0, NUM, null())
| stats avg(FLG) AS AVG
```

![スクリーンショット 2021-12-19 14.46.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/41458421-6584-da1e-f1bd-8bac6c7ab1c8.png "avg2")