Splunk: 表の結合方法あれこれ

しばしば必要となるのでメモ。

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

以下の2つの表を、様々な形式で結合してみます。

| NUM1 | STR1 |
|-----:|:-----|
| 1    | a1   |
| 2    | a2   |
| 3    | a3   |

```Splunk:Splunk
| makeresults count=3
| streamstats count AS NUM1
| eval STR1 = "a" + NUM1
| table NUM1, STR1
```

![スクリーンショット 2022-01-09 12.59.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/b10f88b8-ddc0-5877-a431-60c5e881c7e7.png "table1")

| NUM2 | STR2 |
|-----:|:-----|
| 3    | b3   |
| 4    | b4   |

```Splunk:Splunk
| makeresults count=2
| streamstats count AS NUM
| eval NUM2 = NUM + 2
| eval STR2 = "b" + NUM2
| table NUM2, STR2
```

![スクリーンショット 2022-01-09 12.59.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/6ead4bc4-cc9d-6069-903e-5f7d9c3ad78d.png "table2")

###### 縦に結合

単純に縦に繋げるには、 append コマンドを使用します。
無論、フィールド名は統一するようにしてください。

| NUM1 | STR1 |
|-----:|:-----|
| 1    | a1   |
| 2    | a2   |
| 3    | a3   |
| 3    | b3   |
| 4    | b4   |

```Splunk:Splunk
| makeresults count=3
| streamstats count AS NUM1
| eval STR1 = "a" + NUM1
| fields NUM1, STR1
| append
    [
      | makeresults count=2
      | streamstats count AS NUM
      | eval NUM2 = NUM + 2
      | eval STR2 = "b" + NUM2
      | fields NUM2, STR2
      | rename NUM2 AS NUM1, STR2 AS STR1
    ]
| table NUM1, STR1
```

![スクリーンショット 2022-01-09 13.01.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/6c993716-09e8-c346-8492-8a0e8f615cc6.png "append")

###### 横に結合

横に繋げる場合は、 appendcols コマンドを使用します。

| NUM1 | STR1 | NUM2 | STR2 |
|-----:|:-----|-----:|:-----|
| 1    | a1   | 3    | b3   |
| 2    | a2   | 4    | b4   |
| 3    | a3   |      |      |

```Splunk:Splunk
| makeresults count=3
| streamstats count AS NUM1
| eval STR1 = "a" + NUM1
| fields NUM1, STR1
| appendcols
    [
      | makeresults count=2
      | streamstats count AS NUM
      | eval NUM2 = NUM + 2
      | eval STR2 = "b" + NUM2
      | fields NUM2, STR2
    ]
| table NUM1, STR1, NUM2, STR2
```

![スクリーンショット 2022-01-09 13.03.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/d54a3d1f-b8f2-8874-618b-42e08a2c7fed.png "appendcols")

###### 斜めに結合

フィールド名を統一せずに append コマンドを使用すると、以下のような形で結合されます。

| NUM1 | STR1 | NUM2 | STR2 |
|-----:|:-----|-----:|:-----|
| 1    | a1   |      |      |
| 2    | a2   |      |      |
| 3    | a3   |      |      |
|      |      | 3    | b3   |
|      |      | 4    | b4   |


```Splunk:Splunk
| makeresults count=3
| streamstats count AS NUM1
| eval STR1 = "a" + NUM1
| fields NUM1, STR1
| append
    [
      | makeresults count=2
      | streamstats count AS NUM
      | eval NUM2 = NUM + 2
      | eval STR2 = "b" + NUM2
      | fields NUM2, STR2
    ]
| table NUM1, STR1, NUM2, STR2
```

![スクリーンショット 2022-01-09 13.04.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/ea56d5e3-a761-2b6f-9162-d1275a9d204d.png "append_nomatch")

###### 交差結合

全ての組み合わせを網羅する形での結合(交差結合)を実現したい場合は、 join コマンドを使用します。
 join コマンドは通常メインサーチとサブサーチで指定したフィールドを比較して一致した行を結合しますが、フィールドを何も指定しない場合は単純にメインサーチ1行毎にサブサーチを結合していきます。
また、 join コマンドの max というパラメータはメインサーチ1行に対し結合できるサブサーチの行数の最大値を指定しており、デフォルトは1です。
これを0(無制限)にした上でフィールドを指定しなければ、以下のように交差結合を作ることができます。

| NUM1 | STR1 | NUM2 | STR2 |
|-----:|:-----|-----:|:-----|
| 1    | a1   | 3    | b3   |
| 1    | a1   | 4    | b4   |
| 2    | a2   | 3    | b3   |
| 2    | a2   | 4    | b4   |
| 3    | a3   | 3    | b3   |
| 3    | a3   | 4    | b4   |

```Splunk:Splunk
| makeresults count=3
| streamstats count AS NUM1
| eval STR1 = "a" + NUM1
| fields NUM1, STR1
| join max=0
    [
      | makeresults count=2
      | streamstats count AS NUM
      | eval NUM2 = NUM + 2
      | eval STR2 = "b" + NUM2
      | fields NUM2, STR2
    ]
| table NUM1, STR1, NUM2, STR2
```

![スクリーンショット 2022-01-09 13.06.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/3a38f4fa-a919-d873-37b2-b036e5eb5bed.png "join_max")

###### 内部結合

上に書いた通り、 join コマンドは通常メインサーチとサブサーチで指定の項目が一致した行を結合する目的で使用されます。
デフォルトでは、 join コマンドはメインサーチとサブサーチの両方に指定の値が存在する行のみを抽出します(内部結合)。

![スクリーンショット 2022-01-09 13.41.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/ad183e80-7b33-e662-055e-2813c8e9e82c.png "figure1")

| NUM1 | STR1 | STR2 |
|-----:|:-----|:-----|
| 3    | a3   | b3   |

以下の例では、メインサーチの NUM1 とサブサーチの NUM2 を比較し、両方に存在する「3」の行のみを結合、抽出しています。

```Splunk:Splunk
| makeresults count=3
| streamstats count AS NUM1
| eval STR1 = "a" + NUM1
| fields NUM1, STR1
| join NUM1
    [
      | makeresults count=2
      | streamstats count AS NUM
      | eval NUM2 = NUM + 2
      | eval STR2 = "b" + NUM2
      | fields NUM2, STR2
      | rename NUM2 AS NUM1
    ]
| table NUM1, STR1, STR2
```

![スクリーンショット 2022-01-09 13.43.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/555ad814-f5f4-a9df-3210-a1ddb6d74e2f.png "join_in")

###### 左外部結合

 join コマンドの type パラメータに outer を指定した場合、メインサーチに含まれる値全てを出力します(左外部結合)。

![スクリーンショット 2022-01-09 13.43.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/fa9f140f-7965-6767-c575-f5331446cf78.png "figure2")

| NUM1 | STR1 | STR2 |
|-----:|:-----|:-----|
| 1    | a1   |      |
| 2    | a2   |      |
| 3    | a3   | b3   |

以下の例では、メインサーチとサブサーチ両方に存在する「3」だけでなく、メインサーチのみに存在する「1」及び「2」も出力されています。

```Splunk:Splunk
| makeresults count=3
| streamstats count AS NUM1
| eval STR1 = "a" + NUM1
| fields NUM1, STR1
| join type=outer NUM1
    [
      | makeresults count=2
      | streamstats count AS NUM
      | eval NUM2 = NUM + 2
      | eval STR2 = "b" + NUM2
      | fields NUM2, STR2
      | rename NUM2 AS NUM1
    ]
| table NUM1, STR1, STR2
```

![スクリーンショット 2022-01-09 13.45.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/71f5e3f4-af59-7974-d5ef-8a4bde9dc223.png "join_left")

###### 右外部結合

サブサーチに含まれる値全てを出力する(右外部結合)ことは、 join コマンド単体では実現できません。

![スクリーンショット 2022-01-09 13.43.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/60c7f1a1-b59f-eed9-dd1a-bf59e8b3f5d5.png "figure3")

| NUM1 | STR1 | STR2 |
|-----:|:-----|:-----|
| 3    | a3   | b3   |
| 4    |      | b4   |

単純にメインサーチとサブサーチを入れ替えるのが最も簡単ですが、それが難しい場合は他のコマンドを合わせて使用するなどして上手く作るしかありません。
以下の例では元の表にNULL行を追加した上で一旦交差結合を作成して、メインサーチに存在しない値( NUM1 が NULL )の場合はサブサーチの値( NUM2 )をメインサーチの値に移し、メインサーチの値とサブサーチの値が等しくかつその値が NULL でない行を抽出した後、キーの値の重複を削除してヒットした値における NULL 行を削除しています。

```Splunk:Splunk
| makeresults count=3
| streamstats count AS NUM1
| eval STR1 = "a" + NUM1
| append
    [
      | makeresults count=1
    ]
| fields NUM1, STR1
| join max=0
    [
      | makeresults count=2
      | streamstats count AS NUM
      | eval NUM2 = NUM + 2
      | eval STR2 = "b" + NUM2
      | fields NUM2, STR2
    ]
| eval NUM1 = if(isnull(NUM1), NUM2, NUM1)
| where isnotnull(NUM1) AND (NUM1 = NUM2)
| table NUM1, STR1, STR2
| dedup NUM1
```

![スクリーンショット 2022-01-09 13.52.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/37741f46-f880-b052-32d4-c182cd8882e5.png "join_right")

ただ、上記の例だとキーとなる値( NUM1 )に対してメインサーチの値( STR1 )とサブサーチの値( STR2 )が一意とならない場合、重複削除で必要な行も削られてしまい上手く動作しません。

性能は非常に悪化しますが、以下のように「メインサーチに含まれる部分(内部結合)」と「メインサーチに含まれない部分」を分割して処理する方法もあります。

```Splunk:Splunk
| makeresults count=3
| streamstats count AS NUM1
| eval STR1 = "a" + NUM1
| fields NUM1, STR1
| join NUM1
    [
      | makeresults count=2
      | streamstats count AS NUM
      | eval NUM2 = NUM + 2
      | eval STR2 = "b" + NUM2
      | fields NUM2, STR2
      | rename NUM2 AS NUM1
    ]
| append
    [
      | makeresults count=2
      | streamstats count AS NUM
      | eval NUM2 = NUM + 2
      | eval STR2 = "b" + NUM2
      | fields NUM2, STR2
      | rename NUM2 AS NUM1
      | where NOT (
          [
            | makeresults count=3
            | streamstats count AS NUM1
            | fields NUM1
            | format
          ]
              )
    ]
| table NUM1, STR1, STR2
```

![スクリーンショット 2022-01-09 14.07.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/68980ae6-a952-52b4-63ba-1df6fee320d5.png "join_in_right")

こちらの例だとキーとなる値( NUM1 )に対してメインサーチの値( STR1 )とサブサーチの値( STR2 )が一意とならない場合も動作できます。

###### 完全外部結合

メインサーチとサブサーチに含まれる値全てを出力する(完全外部結合)場合も、 join コマンド単体では作れません。

![スクリーンショット 2022-01-09 14.15.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/a376a065-c147-1cbf-d015-f56de7a0046e.png "figure4")

| NUM1 | STR1 | STR2 |
|-----:|:-----|:-----|
| 1    | a1   |      |
| 2    | a2   |      |
| 3    | a3   | b3   |
| 4    |      | b4   |

右外部結合と同様、他のコマンドと上手く組み合わせる必要があります。
以下の例は先程の右外部結合とよく似ていますが、メインサーチのみに存在する行が追加されています。

```Splunk:Splunk
| makeresults count=3
| streamstats count AS NUM1
| eval STR1 = "a" + NUM1
| append
    [
      | makeresults count=1
    ]
| fields NUM1, STR1
| join max=0
    [
      | makeresults count=2
      | streamstats count AS NUM
      | eval NUM2 = NUM + 2
      | eval STR2 = "b" + NUM2
      | append
          [
            | makeresults count=1
          ]
      | fields NUM2, STR2
    ]
| eval NUM1 = if(isnull(NUM1), NUM2, NUM1)
| where isnotnull(NUM1) AND
        ((NUM1 = NUM2) OR isnull(NUM2))
| table NUM1, STR1, STR2
| dedup NUM1
```

![スクリーンショット 2022-01-09 14.13.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/0237e16a-8f55-2c0f-2dd4-d728a3b2f42b.png "join_out")

より簡潔な書き方としては、以下のように stats コマンドの values 関数を使用する方法があります。

```Splunk:Splunk
| makeresults count=3
| streamstats count AS NUM1
| eval STR1 = "a" + NUM1
| fields NUM1, STR1
| append
    [
      | makeresults count=2
      | streamstats count AS NUM
      | eval NUM2 = NUM + 2
      | eval STR2 = "b" + NUM2
      | fields NUM2, STR2
      | rename NUM2 AS NUM1
    ]
| stats values(STR1) AS STR1,
        values(STR2) AS STR2 BY NUM1
```

![スクリーンショット 2022-01-09 14.19.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/2d547f77-0e20-6675-6732-6a4843b69bac.png "join_stats")

上記2例は、キーとなる値( NUM1 )に対してメインサーチの値( STR1 )とサブサーチの値( STR2 )が一意となる場合に有効です。

右外部結合の際と同様、以下のように「メインサーチに含まれる部分(左外部結合)」と「メインサーチに含まれない部分」を分割して処理する方法もあります。

```Splunk:Splunk
| makeresults count=3
| streamstats count AS NUM1
| eval STR1 = "a" + NUM1
| fields NUM1, STR1
| join type=outer NUM1
    [
      | makeresults count=2
      | streamstats count AS NUM
      | eval NUM2 = NUM + 2
      | eval STR2 = "b" + NUM2
      | fields NUM2, STR2
      | rename NUM2 AS NUM1
    ]
| append
    [
      | makeresults count=2
      | streamstats count AS NUM
      | eval NUM2 = NUM + 2
      | eval STR2 = "b" + NUM2
      | fields NUM2, STR2
      | rename NUM2 AS NUM1
      | where NOT (
          [
            | makeresults count=3
            | streamstats count AS NUM1
            | fields NUM1
            | format
          ]
              )
    ]
| table NUM1, STR1, STR2
```

![スクリーンショット 2022-01-09 14.22.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1465018/dc9381bc-369f-5d82-52c6-a473f2c6c441.png "join_left_right")

こちらの例だとキーとなる値( NUM1 )に対してメインサーチの値( STR1 )とサブサーチの値( STR2 )が一意とならない場合も動作できます。