LoginSignup
7
5

More than 3 years have passed since last update.

multivalueを分割する

Last updated at Posted at 2019-11-24

小ネタ

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
9.56
7.35
12.32
10
20
30
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して、あとは元のフィールド抽出

SPL
...
| eval zipped=mvzip(duration,rollno)
| mvexpand zipped
| eval duration=mvindex(split(zipped,","),0), rollno=mvindex(split(zipped,","),1)
| table _time duration rollno

これの欠点はフィールドがたくさんだとmvzipをネストしないといけないこと

カウンターを使ってとりだし

一つフィールドを選んでmvexpnadstreamstatsでカウンターを作って、あとは元のフィールド抽出

SPL
...
| mvexpand rollno
| streamstats count
| eval duration=mvindex(duration,count - 1)
| table _time duration rollno

multivalueのフィールドは順番が保持されることを利用したクエリー。
stats value(X) as Xとかでフィールドをたくさん作った場合に使えると思う。

イベントのカウンター処理

2020/1/19 修正。 

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
multivalueを展開してくれるmvexpandlimits.conf設定のメモリ容量を超過してしまうとイベントが省略されてしまう。
そのために先の記事ではやる前に_rawを削りなさいというコツを教えてくれる。

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

これは使える :smirk:

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
に通して、テキストエディタで"\"に変換してやって貼り付けてます。
だいぶ楽になりました。

JSON2

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
AccountName
AccountName
PersonName
PersonName
AccountOne
AccountOne
AccountTwo
Bob
Bob
PropOne
PropTwo
PropOne
PropOne
PropThree
PropOne
PropThree
PropThree
5
3
5
5
8
5
8
8

と大変なことになる。

ネストの深さにもよるが、spathstats countmvexpandを組み合わせると上手く表にできる。

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

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

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は引数としてはたくさん使用できるがフィールド名が汚くなるので、3つにまとめてあとで分解するといい感じになると思います。

まとめ

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

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5