multivalueを分割する

小ネタ

```Splunk:SPL
| makeresults
| eval _raw="time,duration,rollno
2016-08-07 21:13:00,10.07 9.56 7.35 12.32,10 20 30 40"
| multikv forceheader=1
| table time duration rollno
| eval duration=split(duration," "), rollno=split(rollno," ")
`comment("this is sample data")`
```

|time|duration|rollno
|:-:|--:|--:|
|2016-08-07 21:13:00|10.07<br>9.56<br>7.35<br>12.32|10<br>20<br>30<br>40|

こんなデータが在った時の分割方法

期待する結果は

|_time|duration|rollno
|:-:|--:|--:|
2016/08/07 21:13:00|	10.07	|10
2016/08/07 21:13:00|	9.56	|20
2016/08/07 21:13:00|	7.35	|30
2016/08/07 21:13:00|	12.32	|40

#素直な考え方
`mvzip`でmutivalueフィールドをくっつけて、`mvexpand`して、あとは元のフィールド抽出

```perl:SPL
...
| eval zipped=mvzip(duration,rollno)
| mvexpand zipped
| eval duration=mvindex(split(zipped,","),0), rollno=mvindex(split(zipped,","),1)
| table _time duration rollno
```
これの欠点はフィールドがたくさんだとmvzipをネストしないといけないこと

#カウンターを使ってとりだし
一つフィールドを選んで`mvexpnad`。`streamstats`でカウンターを作って、あとは元のフィールド抽出

```Splunk:SPL
...
| mvexpand rollno
| streamstats count
| eval duration=mvindex(duration,count - 1)
| table _time duration rollno
```
multivalueのフィールドは順番が保持されることを利用したクエリー。
`stats value(X) as X`とかでフィールドをたくさん作った場合に使えると思う。

#イベントのカウンター処理
2020/1/19 修正。　

```perl:SPL
| makeresults count=4
| eval fieldA=random() % 50 + 1, fieldB=random() % 50 + 1, fieldC=random() % 50 + 1 ,fieldD=random() % 50 + 1, fieldE=random() % 50 + 1
| stats values(*) as * by _time
`comment("this is sample data")`
| eval counter=mvrange(0,mvcount(fieldA))
| streamstats count as session
| stats list(*) as * by session counter
| foreach field*
    [eval <<FIELD>> = mvindex(<<FIELD>>, counter)]
| table field*
```
こちらは`mvexpand`を使用しないでシングルイベントに分割したクエリー。
`foreach`は単なる`*`は`_`から始まるフィールドをみない。
なので、今回は`field*` で指定したけど
`rename session as _session, counter as _counter`として　`foreach *`とやる手もある。

ここのキモは

1. `streamstats count as session`で各イベントのナンバーを作成
2. `eval counter=mvrange(0,基準フィールドのmultivalue数)`で各イベントのmultivalue展開用カウンターを作成
3. `stats list(*) as * by session counter`で一括シングル化用イベント作成
4. `foreach *`で一括シングルイベント化

これで、複数イベントでフィールドがなんであれ、一括変換可能。
ただし、フィールド名にフィールドで使えない文字が入っていると`foreach *`の処理ができないので、見つけたら`rename`とかしてやってから`foreach`。

#JSON
2019/12/7 追加。　ちょっとテクを見つけた。

[mvexpandメモリ超過@Splunk Answer](https://answers.splunk.com/answers/98620/mvexpand-gives-mvexpand-output-will-be-truncated-due-to-excessive-memory-usage.html)
multivalueを展開してくれる`mvexpand`は_limits.conf_設定のメモリ容量を超過してしまうとイベントが省略されてしまう。
そのために先の記事では_やる前に`_raw`を削りなさい_というコツを教えてくれる。

最近同じような`_raw`を削ってもまだ、mvexpandメモリ超過になってしまう恐ろしい話の解答をみていて、`stats`を利用してmultivalueを展開していることがあった。

**これは使える** :smirk: 

```perl:SPL
| makeresults 
| eval _raw="{\"_data\":{\"services\":[{\"id\":\"FB00000\",\"users\":[100,122]},{\"id\":\"FB11111\",\"users\":[404,797]}],\"socialNetwork\":\"FB\"},\"_timestamp\":\"01-02-02013T01:00:04.582+0100\",\"_type\":\"ServiceReport\"}"
| spath path=_data.services{} output=data
| kv
| rename data.services{}.* as * 
| stats count by data.socialNetwork timestamp type data
| spath input=data
| stats count values(*) as * by users{}
| rename users{} as users, data.socialNetwork as socialNetwork
| table id users socialNetwork timestamp type
```
`mvexpand`を使わなくても展開可。メモリ超過の心配なし。

####ここでのポイント
 1. multivalue になるデータを`spath`で一旦とりだす。
 2. `kv`(`spath`)でフィールド抽出
 3. `stats count by フィールド名`で展開
 4. 一旦取り出しておいたデータを`spath`で抽出
 5. 繰り返し

`rename`は適宜行うと、SPLが書き易い。

なお、JSONは一旦　[JSON Formatter](https://jsonformatter.curiousconcept.com/#)
に通して、テキストエディタで`"`を`\"`に変換してやって貼り付けてます。
だいぶ楽になりました。

#JSON2
```perl:JSON2.spl
| makeresults
| eval _raw="{ \"Items\": [ { \"CN\": \"AccountName\", \"CV\": \"AccountOne\", \"Props\": [ { \"PN\": \"PropOne\", \"PV\": \"5\" }, { \"PN\": \"PropTwo\", \"PV\": \"3\" } ] }, { \"CN\": \"AccountName\", \"CV\": \"AccountOne\", \"Props\": [ { \"PN\": \"PropOne\", \"PV\": \"5\" } ] }, { \"CN\": \"AccountName\", \"CV\": \"AccountTwo\", \"Props\": [ { \"PN\": \"PropOne\", \"PV\": \"5\" }, { \"PN\": \"PropThree\", \"PV\": \"8\" } ] }, { \"CN\": \"PersonName\", \"CV\": \"Bob\", \"Props\": [ { \"PN\": \"PropOne\", \"PV\": \"5\" }, { \"PN\": \"PropThree\", \"PV\": \"8\" } ] }, { \"CN\": \"PersonName\", \"CV\": \"Bob\", \"Props\": [ { \"PN\": \"PropThree\", \"PV\": \"8\" } ] } ] }"
```
この場合、何も考えず`spath`すると

| Items{}.CN | Items{}.CV | Items{}.Props{}.PN | Items{}.Props{}.PV |
|:--|:--|:--|:--|
|AccountName<br>AccountName<br>AccountName<br>PersonName<br>PersonName|AccountOne<br>AccountOne<br>AccountTwo<br>Bob<br>Bob|PropOne<br>PropTwo<br>PropOne<br>PropOne<br>PropThree<br>PropOne<br>PropThree<br>PropThree|5<br>3<br>5<br>5<br>8<br>5<br>8<br>8|
と大変なことになる。

ネストの深さにもよるが、`spath`と`stats count`と`mvexpand`を組み合わせると上手く表にできる。

```perl:spath
...
| spath path=Items{} output=Items
| stats count by Items
| spath input=Items path=Props{} output=Props
| mvexpand Props
| spath input=Props
| spath input=Items
| fields - Items count Props*
```
考え方としては、`by`の引数が重複していない時は`stats count`で分割
重複している、または他の項目を素直に分けたい場合`mvexpand`を使用する。

結果は

|CN	|CV	|PN	|PV|
|:-:|:-:|:-:|:-:|
|AccountName	|AccountOne	|PropOne	|5
|AccountName	|AccountOne	|PropOne	|5
|AccountName	|AccountOne	|PropTwo	|3
|AccountName	|AccountTwo	|PropOne	|5
|AccountName	|AccountTwo	|PropThree	|8
|PersonName	|Bob	|PropOne	|5
|PersonName	|Bob	|PropThree	|8
|PersonName	|Bob	|PropThree	|8

この後こんなことをしていました。

```perl:xyseries
| eval names=CN."_".CV
| stats sum(PV) as PV by names PN
| xyseries names PN PV
| rex field=names "AccountName_(?<AccountName>\w+)|PersonName_(?<PersonName>\w+)"
| table PersonName AccountName PropOne PropTwo PropThree
```
`xyseries`は引数としてはたくさん使用できるがフィールド名が汚くなるので、３つにまとめてあとで分解するといい感じになると思います。

#まとめ
`spath`とかでJSONを展開すると、multivalueなフィールドがたくさんできる。
展開の方法はいろいろあって、フィールドの数やイベントの数に応じてここの方法のどれかを使うといいと思います。