TSVに対するSQLのSELECTもどきを作ってみた

# 概要
TSVなどのちょっとした行列データを絞り込んだり、照合したりという処理を楽にするために[ツールとJavaライブラリ](https://1f9ffe72-262c-49fc-8add-1f00635d5d2d.filesusr.com/archives/9223b7_d5bc6022ccee4cba98a75f059dc3a107.zip?dn=select.zip)を作ってみた。

# 利用の前提
JDK 11 で動作確認しています。多分JRE8あたりから動くのではないかと思います。

# 機能
### コマンドラインツール
- 入出力ファイルのエンコード指定
- ファイル先頭のスキップ行数指定
- 列区切り文字の指定
- 出力列の指定
- ソート条件の指定
- SQLのWHEREもどき
- SQLのJOINもどき

### Javaライブラリ
コマンドラインツールとほぼ同等ですが、SQLのWHEREもどきの表現力が上がり、Javaプログラムで使いやすくなっています。

# 利用例
以下は、全て[ダウンロードパッケージ](https://1f9ffe72-262c-49fc-8add-1f00635d5d2d.filesusr.com/archives/9223b7_d5bc6022ccee4cba98a75f059dc3a107.zip?dn=select.zip)内にサンプルとして付いていますので、色々試したい方はそちらからどうぞ。

### 利用するファイル
```person.tsv
名前	性別	年齢	住所	趣味
佐藤	男	25	東京	旅行
鈴木	女	12	大阪	映画
山本	男	33	名古屋	工作
```

```cities.csv
都市,人口,平均気温
東京,10000000,22.5
大阪,5000000,24.3
名古屋,3000000,23.1
福岡,1500000,25.7
仙台,1200000,19.2
札幌,1000000,15.5
```

```hobbies.ssv
趣味;イベント;場所
旅行;浅草散策;東京
旅行;博多湾クルーズ;福岡
映画;未公開映画試聴;名古屋
```

### シンプルなJOIN
```
java -jar select.jar 0@0;2@1 persons.tsv;1 cities.csv;System1Comma;INNER;3@0=0
```
- 0@0;2@1
    - 0番目ファイル0列目と1番目ファイルの2列目を出力
- persons.tsv;1
    - 0番目ファイルとしてpersions.tsvを使う
    - 先頭1行を読み飛ばす
- cities.csv;System1Comma;INNER;3@0=0
    - 1番目ファイルとしてcities.csvを使う
    - Systemデフォルト（SJIS）エンコードで、先頭1行を読み飛ばし、列区切りはカンマとする
    - 直前のファイルとINNER JOINをする
    - 0番目ファイルの3列目とこのファイルの0列目が一致する事をJOIN条件とする

```text:出力
佐藤	22.5
山本	23.1
鈴木	24.3
```

### シンプルなJOIN（列名テキスト版で出力は同じ）
```
java -jar select.jar -c Text 名前@0;平均気温@1 persons.tsv;0 cities.csv;S0C;INNER;住所@0=都市
```

### シンプルなWHERE
```
java -jar select.jar -c Text @-0 "persons.tsv;0;性別@=@男;年齢@>=@30"
```
- @-0
    - -は反転マークなので、「0番目ファイルの指定列なし」の反転→0番目ファイルの全列
- persons.tsv;0;性別@=@男;年齢@>=@30
    - 0番目ファイルとしてpersons.tsvを使う
    - 先頭0行を読み飛ばす
    - 性別が男の行だけに絞り込む
    - 年齢が30以上の行だけに絞り込む
    - ""で囲わないと、不等号がリダイレクトマークと誤認識されて意図した結果にならない

```text:出力
山本	男	33	名古屋	工作
```

「男」とか「30」とかの絞り込み条件は、だいたい直感と合う様に、テキスト・整数・小数で切り替えてくれます

- 数字だけなら64bit整数（いわゆるlong）とみなす
- それ以外で小数と解釈できれば2倍長浮動小数（いわゆるdouble）とみなす
- それ以外はテキストとみなす
- ファイルの列中で指定の型（整数・小数）に解釈できない行は、絞り込み条件に合致していないとみなす

### 複雑な例
```
java -jar select.jar -c Text @-0;都市@-1;イベント@2 persons.tsv;0;性別@=@男 "cities.csv;S0C;平均気温@<@23.0;LEFT;住所@0=都市" hobbies.ssv;0SC;FULL;都市@1=場所;趣味@0=趣味
```

```text:出力
佐藤	男	25	東京	旅行	10000000	22.5	浅草散策
山本	男	33	名古屋	工作	_NUL_	_NUL_	_NUL_
_NUL_	_NUL_	_NUL_	_NUL_	_NUL_	_NUL_	_NUL_	博多湾クルーズ
_NUL_	_NUL_	_NUL_	_NUL_	_NUL_	_NUL_	_NUL_	未公開映画試聴
```

```java:Javaライブラリとして使って、同等の処理を行うサンプル
try (BufferedReader personReader = Files.newBufferedReader(Path.of("persons.tsv")) ;
        BufferedReader cityReader = Files.newBufferedReader(Path.of("cities.csv"), Charset.defaultCharset()) ;
        BufferedReader hobbyReader = Files.newBufferedReader(Path.of("hobbies.ssv"))) {
    Select.byText().selectAllExcept(0, null).
            selectAllExcept(1, "都市").
            select(2, "イベント").
            from(personReader).where("性別", v -> v.equals("男")).
            leftJoin(cityReader).tokenizer(Select.COMMA_TOKENIZER).
                    whereD("平均気温", v -> v < 23).on(0, "住所", "都市").
            fullJoin(hobbyReader).tokenizer(Select.SEMICOLON_TOKENIZER).
                    on(1, "都市", "場所").on(0, "趣味", "趣味").
            execute().
            map(t -> Arrays.stream(t).collect(Collectors.joining("\t"))).
            forEach(System.out::println);
}
```

# 他ツールとの比較
[SQLite3](https://sfnovicenotes.blogspot.com/2019/10/sqlite3-tsvtsv.html)などのSQLを使う方が高機能だが

- 簡単・定型的な処理なら、簡単で高速

Linuxの[joinコマンド](https://qiita.com/hirohiro77/items/f3847fd4ab1dc653f4ca)の方がJava不要で手軽だが

- 事前のソート処理が不要
- 簡易WHERE句による絞り込みが可能
- 複数ファイルの一括JOINが可能

などの違いがある。

# ご意見をいただけると助かる点
- 似たようなツールがあれば
    - 車輪の再発明を避けるため
- コマンドラインツールの書式の改善案
    - 絞り込みで不等号を使うと、””で囲う必要があり、ちょっとイケてない感