【UiPath】データテーブルの操作（3）絞り込み（フィルタ）


# はじめに

この投稿は、RPAツール「UiPath」で Datatable からデータを絞り込む方法について、個人的にまとめたものです。

# データテーブルの操作 シリーズ

DataTable の操作方法について記載した記事が、他にもあります。参考まで。

https://qiita.com/miya_gis/items/6c0dd40378db702bd4b2

https://qiita.com/miya_gis/items/edc3557f3dc7cf6b3cfd

https://qiita.com/miya_gis/items/cde6c1eb3c33378d9718

https://qiita.com/miya_gis/items/78dd08bc7f0b5497b83d

https://qiita.com/miya_gis/items/966113ce9732cb112c49

https://qiita.com/miya_gis/items/f45b67fbc2575f0d6d77

https://qiita.com/miya_gis/items/67eac96071aa7d7d24dd

# 絞り込み方法

UiPath で、データテーブルから条件指定してデータを取得するには、以下の方法があります。

| No | 方法 | 評価 | コメント |
|:-:|:-|:-|:-|
| 1 | 「データ テーブルをフィルター」<br>アクティビティを使用 | UiPath では一般的 | SelectやLinqの方が見やすい<br>比較時に型変換（型揃え）が出来ない|
| 2 | Datatableの「.Select」メソッドを使用 | VB では一般的 | 比較時に型変換（型揃え）が出来る<br>列の加工・削除ができない|
| 3 | 「LINQ to Object（メソッド構文）」 を使用 | .NET では一般的<br>（少し難しい） | 基本何でもできる<br>横に長くなりがち |
| 4 | 「LINQ to SQL（クエリ式）」 を使用 | .NET では一般的<br>（少し難しい） | 基本何でもできる<br>縦横に長くなりがち|

以下で、説明していきます。

# 1）データ テーブルをフィルター アクティビティ

標準アクティビティで提供されている「データ テーブルをフィルター」アクティビティで、絞り込みが出来ます。機能的には十分です。

「フィルタを指定」ボタンを押すと、ウィザードが表示され、条件を設定します。複数の条件の指定も可能で、絞り込み後に「残す／削除する」列の指定も可能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154139/a8a4966b-611b-07d2-6997-c5ff98743130.png)

残念な点としては`「フィルタを指定」ボタンを押さないと、フロー上での絞り込み内容が見れません`。（ボタンを押さないでも見えるようになってほしい）

また、`データの型が「DateTime型」等の「書式によっては値比較が正確にできない」ものは、絞り込みができない事があります。`（値の比較時に、右辺左辺の型変換ができない）

（上記のように「厳格に型を意識する必要がない」場合）基本的には、このアクティビティで絞り込みをすれば良いのですが、`「処理が遅い」`場合あります。
また「`少し凝った操作をしたい`」場合は、以下で紹介する「Selectメソッド」か「LINQ」で操作することになります。

https://docs.uipath.com/activities/lang-ja/docs/filter-data-table

:::note info
要するに「標準アクティビティでは出来ない」ことがあり、その場合「Selectメソッド」か「LINQ」で操作する必要があるということです。
（アクティビティに欲しい機能の追加がされるまでは・・・。）
:::


また、類似アクティビティで「データ テーブルを検索」アクティビティがありますが、検索結果と合致した行番号しか取れないので、絞り込むには少し機能が足りません。


# 2）Datatableの「.Select」メソッド

Datatable が提供する Select メソッドを使用して、DataRow オブジェクトの配列を取得できます。
Select メソッドには、4種類の指定方法があります。

| No | メソッドの指定 | 内容 |
|:-:|:-|:-|
| 1 | .Select() | すべての 行を取得<br>※稀に使う（ほぼ使わない） |
| 2 | .Select("絞込条件") | 絞込条件に合致する 行を取得 |
| 3 | .Select("絞込条件", "並替指定") | 絞込条件に合致する 行を 並替して取得 |
| 4 | .Select("絞込条件", "並替指定", "行の状態指定") | 絞込条件と行状態指定に合致する 行を 並替して取得<br>※UiPathでは使うことは無い |

以下のような式を、代入アクティビティ等にセットして使用します。

``` VB
'// .Select("絞込条件") のサンプル
dt.Select("id='A001'")              '// IDが「A001」
dt.Select("width>20 AND height=10") '// 幅が20より大きく、高さが10
dt.Select("width>20 OR  height=10") '// 幅が20より大きい、または、高さが10

'// .Select("絞込条件", "並替指定")) のサンプル
dt.Select("width>20", "width ASC") '// 幅の昇順で、幅が20より大きい
dt.Select("width>20", "width DESC") '// 幅の降順で、幅が20より大きい
dt.Select("width>20", "width, height DESC") '// 幅の昇順・高さの昇順で、幅が20より大
```

:::note warn
SELECTで、複数列指定で絞り込みをする場合、データ件数によっては処理が遅くなります。
列の指定を 2 回以上に分けて、SELECTで絞り込むと改善します。
:::

https://www.cresco.co.jp/blog/entry/6016/

配列／DataRow ではなく DataTable で取得したい場合は、後ろに「.CopyToDataTable」を付けます。
``` VB
dt.Select("width>20", "width, height DESC").CopyToDataTable
```
ですが、絞り込み結果が0件の場合に「DataRow が含まれていません」エラーになります。「CopyToDataTable」を実行するためには、必ずデータ1件以上必要なためです。
これを回避するには、一度「配列型」に格納したあとで「配列の件数が０で無い」場合に「CopyToDataTable」するようにします。
``` VB
'// 件数は悪用の変数を用意
enum_row As IEnumerable<DataRow>

'// 代入１回目（件数把握）
enum_row = dt.Select("id='A001'") 

'// 代入２回目（データが0件なら、空のDataTableをセット）
dt = If(enum_row.Count = 0, dt.Clone, enum_row.CopyToDataTable)
```

System.Collections.Generic.IEnumerable<System.Data.DataRow>

https://docs.microsoft.com/ja-jp/dotnet/api/system.data.datatable.select?view=net-6.0


# 3）LINQ to Object（メソッド構文）

LINQ to Object は、繰り返し型（IEnumerableインターフェース）のオブジェクトに対して、一括で処理する方法です。以下のように「 . 」で繋ぎながら、配列操作します。

``` vb
'// region が tokyo のデータを取得
dt.AsEnumerable().Where(function(r) r("region").ToString.ToLower = "tokyo")

'// region が t で始まるデータを age が低い順 で取得
dt.AsEnumerable().Where(function(r) r("region").ToString.ToLower.StartsWith("t")
                ).OrderBy(function(r) r("age"))

'// region が Tokyo で age が 31以上 のデータを age が高い順 で取得
dt.AsEnumerable().Where(function(r) r("region").ToString = "Tokyo" _
                      AndAlso CType(r("age"), Int32) > 30
                ).OrderByDescending(function(r) r("age"))

'// region が Tokyo または Osaka のデータを age の高い順、名前順 で取得
dt.AsEnumerable().Where(function(r) r("region").ToString = "Tokyo" _
                             OrElse r("region").ToString = "Osaka"
                ).OrderByDescending(function(r) r("age")
                ).ThenBy(function(r) r("name"))
```

. の後ろは Linq拡張メソッド で、指定できるものがいくつかあります。以下は比較的よく使います。

| 種類 | メソッド | 戻り値 |
|:-:|:-:|:-:|
| 絞り込み | Where | 条件を満たすデータ |
| 並び替え | OrderBy | 昇順データ  |
| 並び替え | OrderByDescending | 降順データ  |
| 並び替え | ThenBy  | 昇順データ（2回目以降の並び替え）  |
| 並び替え | ThenByDescending | 降順データ（2回目以降の並び替え）  |
| 選択／射影 | Select | 指定した要素 |
| 集計 | Max | 最大値 |
| 集計 | Min | 最小値 |
| 集計 | Sum| 合計値 |
| 集計 | Average| 平均値 |
| 集計 | Count| 合致数 |
| 判定 | All| すべて が条件を満たすか |
| 判定 | Any| いずれか が条件を満たすか |
| 判定 | Contains | 含むか |
| 要素取得 | FirstOrDefault | 1件目または初期値 |

取得結果を、列配列ではなく DataTable で取得したい場合は、後ろに「.CopyToDataTable」を付けます。

``` vb
'// region が Tokyo のデータを取得
dt.AsEnumerable().Where(function(r) r("region").ToString = "tokyo").CopyToDataTable
```

:::note warn
横に長くなるため、フローの可読性は落ちます。区切りのいい位置で、改行を挟む等の工夫が必要です。
「.Where・・・」のようにドットが先頭になると構文エラーになるので注意が必要です。
アンダースコア（_）を最後に書くと、エラーを回避できます。
:::


# 4）LINQ to SQL（クエリ式）

LINQ to SQL は SQLクエリのようにデータを絞り込む方法です。「SELECT・FROM・WHERE」などの式を使用します。

SQLクエリとの比較で言うと、以下のようになります。

| SQL式 | LINQ式 |
|:-:|:-:|
| SELECT  | select  |
| FROM  |  from in |
| WHERE  | where  |
| ORDER BY  | orderby  |
| GROUP BY  | group by into  |

書き方の順ですが、LINQ式の場合、SELECT は後ろ（Orderbyの前）に来ます。

``` VB
'// region が Tokyo のデータを取得
From r In dt.AsEnumerable()
Select r
Where r("region").ToString = "tokyo"

'// age が高い順 で取得
From r In dt.AsEnumerable() 
Select r 
Order By r.Field(Of Integer)("age") Descending

'// region が Tokyo で age が 31以上 のデータを age が高い順 で取得
From r In dt.AsEnumerable()
Where r("region").ToString = "Tokyo" AndAlso r.Field(Of Integer)("age") > 30
Select r
Order By r.Field(Of Integer)("age") Descending

'// region が Tokyo または Osaka のデータを age の高い順、名前順 で取得
From r In dt.AsEnumerable()
Where r("region").ToString = "Tokyo" OrElse r("region").ToString = "Osaka"
Select r
order by r.Field(Of Integer)("age") Descending,  r("name")
```

クエリ構文は「遅延バインディング」という仕組みを使用するため「型の明確化」が必要で、列の型「Of XXX」を指定しています。

:::note warn
横に長くなるため、フローの可読性は落ちます。改行を挟む等の工夫が必要です。
:::

# 終わりに

いかがでしたでしょうか。なにかの役に立てば幸いです。
この記事が参考になったら、 LGTMをお願いします。閲覧ありがとうございました。