2つのexcelファイルでの更新:大量データの場合

2つのexcelファイルでの更新をUIPATHで作成しました。
仕様は、B.xlsxのデータにA.xlsxのデータがあれば項目を更新するものです。
Bが4000件でAが1000件で更新をかけたところ処理時間が1Hかかりました。
A,Bともソートして処理を行いました。
UIPATHの内容

A、Bともデータ件数が増えるとともにAのデータ内容も可変的に変更されます。
UIPATHで作成してみましたが処理時間がかかるので他の方法を考えています。
しかし、ロジックが適正なのかまず考えてみたいので問題がないかご教示いただければと思います。

こんにちは

2点ほど

A,Bともソート済みであるなら、両方ともfor eachでまわすのは効率が悪いです。4000x1000の計算が必要になりますので。
例えばAの値をみながら、Bの値をチェックするポインタ(カウンタ)を使うようなロジックとか、テーブルをJoinしちゃうとかが効率が良いと思います。

もうひとつは、上記抜きにしても、1時間はかかりすぎとの印象があります。
画像が切れているのでなんともですが、
ExcelApplicationScopeのAutoSaveがOnになっていて、かつ一回ごとに書き込みに行っていませんか?
書き込み等のIO処理は演算に比べてはるかに遅いので、最後に一括で書き込む(Rangeでまとめて書き込むか、AutoSaveをOffにした上で1回ごとに書き込み、最後にSaveWorkbook)ようにしたほうがパフォーマンスは出ると思います。

1 Like

ありがとうございます。
ExcelApplicationScopeのAutoSaveがOnになっていました。
まずONを外したところB.xlsxに更新はしているようですが保存できません。
SSSUPDATE_DATA_v5.00.xaml (16.2 KB)

テーブルJoinに関してですが教えていただきたく思います。

テーブルのJoinとは別の方法になりますが、

DataTable型変数.Select("キー = '"  + 検索対象キー値  + "'")(0)

のような式で、DataTable型変数の「キーの値が検索対象キー値に該当する行」を持ってくることができます。
画像で貼られているWorkflowの場合だと、dt2だけをFor Each rowで回し、その中でマスター(dt1)から必要な行を持ってくる、というのが簡単だと思います。

(DataTableのJoinは私も考えたのですが、LINQを使うので、若干ハードルが高いかな、とも。定型文にしてしまえば使い回しは効かなくもないのですが・・・)

3 Likes

こんにちは。
Yoichiさんがおっしゃっている「AutoSaveをOffにした上で1回ごとに書き込み、最後にSaveWorkbook」
この一文が抜け落ちているかと思います。
実際にmatrix99999さんが添付している.xamlにSaveWorkbookが無いのが原因で保存できていないと思います。
横から失礼しました。

1 Like

ありがとうございます。
A.xlsxを最初読み込み、datatable DT1に格納
B.xlsxを読み、datatable DT2に格納
DT2をfor each row で回し行単位で処理すること理解しました。

そのあと、if文で
dt1.Select(“キー = '” + 検索対象キー値 + “'”)(0)
であれば検索値をDT1のdatatableに更新を考えています。

改めて、教えていただきたいのは、「検索対象キー値」とは、DT1の項目名になるのでしょうか。
的外れな質問かと思いますが教えてください。

項目名ではなく実際の値です。
なのでFor each rowでの行ごとの変数から、

 row("項目名").ToString()

のような形で値を取り出す必要があります。

1 Like

更新元のデータを読みdatatable dt1に格納した後、
更新データを読みdatatable dt2に格納して
for each row でdt2のデータを1データ単位で処理をして、
dt1.selectでdt1の値を取得しようとしました。
select文ですが
dt1.Select(“ID = '” + row(0).ToString() + “’”)(3).tostring
とした場合、
dt2のキー項目が1列目でdt1のIDと一致したならば dt1の4列目の値を取得すると考えてよいでしょうか。

結果をwrite lineで確認しようとしましたが、エラーとなります。

Source: Write line
Message: The expression contains an invalid string constant: 'DT2の1行目のキー項目の内容’.
Exception Type: SyntaxErrorException
System.Data.SyntaxErrorException: The expression contains an invalid string constant: 'DT2の1行目のキー項目の内容’

フローは下記のようにしてみました。

vb.netで調べましたがうまくいきません。
再度お聞きする次第です。


joinに関してフロー作成したところ処理速度も問題なくできました。

assign
dt1.PrimaryKey = New DataColumn() {dt1.Columns(“ID”)}
dt2.PrimaryKey = New DataColumn() {dt2.Columns(“ID”)}

Invoke method
dt1にmerge

色々ごっちゃになっていそうなので、順を追って説明しますね。

まず、Select文の引数は、文字列でクエリを渡します。ここはSQLのWhere文なんかに近い感じです。

ですので、

ID = 'IDの値'

というのが一番シンプルな形式になります。これを文字列(String型)の引数として渡すので、

"ID = 'IDの値'"

となります。Selectで固定値を持ってくるだけならここまでで対応できます。
ですが、今回のようにループで回す場合、IDの値は行によって変わるので、

"ID = '" + (IDの値の変数) + "'"

とする必要が出てきます。そして、そこに各行のデータを取得していれたいので、データが格納されている行(DataRow変数)を row とし、IDの列名を「ID」とすると、

"ID = '" + row("ID").ToString() + "'"

になります。
ここまでがSelectに渡すべき引数の組み立てです。ですから、

(マスターのDataTable型変数).Select("ID = '" + row("ID").ToString() + "'")

これは正しい式になります。

但し、マスターのデータが入ったDataTableに対してSelectをした場合、注意すべきは「結果は配列で取得される」ことです。
これは何故かというと、.NETの言語使用上はSelectの引数で渡した条件式に該当する行が複数ある可能性もあるからです。
(あるいは、該当する行がなければ「値が1個も入っていない配列」が返されます)

なので、上で作成した式である

(マスターのDataTable型変数).Select("ID = '" + row("ID").ToString() + "'")

これは、DataRowの配列になります。

ですが、今回のような用途では、マスターに同じIDが複数ある、ということは通常考えられないです。
(もしそういったことがあれば、それはマスターとして正規化ができていないので、データの持ち方そのものを検討し直す必要があります)
ですから、取得した配列のなかで用があるのは最初のデータ、つまり0番目だけ、ということになります。故に、配列の値を取得する方法としての「(n)」をつけて

(マスターのDataTable型変数).Select("ID = '" + row("ID").ToString() + "'")(0)

で、マスターの該当行を表すことができます。
繰り返しますが、最後の「(0)」はこの場合、配列の0番目を指しているので、これ全体で「1レコード分のDataRowオブジェクト」になります。(つまり、特定の列を指すわけではありません)

ですから、そのマスターから何か値を取得するのであれば、少しややこしいですが、

(マスターのDataTable型変数).Select("ID = '" + row("ID").ToString() + "'")(0)("列名").ToString()

のような形になると思います。これで「rowで見ている行のID列の値を元に、マスターから検索をかけ、マスターの"列名"に該当する列の値を取得する」ことができます。

1 Like

本当に詳細に教えて頂きありがとうございます。

改めて、
処理は、B.xlsx(dt2)を一行ずつよみA.xlsx(dt1)とキーが一致したら
A.xlsxの項目内容をB.xlsxに更新します。
キーはユニークです。

添付したフローで行ったところ、
Write line : Index was outside the bounds of the array
Index was out of range. Check that the collection has values and the index is less than the size of the collection.

上記エラーが出てしまします。
dt1.select(“dt1のキー a .xlsxのキー = '” + row(“dt2のキー b .xlsxのキー”).ToString() + “'”)(0)(“dt1の項目 a.xlsxの項目)”).ToString
このselect文で大丈夫だと思いましたがエラーとなります。

エラー内容も「配列の個数以上の範囲を指定している」ということで色々調べていますがつまずいています。
vb.netの理解で混乱していることも事実でしてエラーの対応教えて頂ければと思います。

そのときのIDをWrite Line等で出力して、元データと確認してみてください。
Index out of rangeは、たとえば「項目が1個も入っていない配列に対して、0番目を指定した」時にも発生します。

なので、一番考えられるのはSelect文の結果が検索に失敗しているのではないかな、と。

1 Like

ありがとうございます。
原因分かりました。
実は、B.xlsxのデータ数が100件(for each row)で、A.xlsxは、10件でテストしています。
実際のデータの関係は、B > Aであります。
つまり、Bのキーは必ずAのキーにヒットしないことがあります。
A:B=1:1の場合であれば問題ありません。
この場合、Bのキーでデータにヒットしない場合
try catchのtryで
write lineで select文を記載しエラーが出た場合、
cattchesでexceptionの場合は処理しない。
ステータスをassignで取得させる。
assgin:Vexceptin = “1”

finailyでif文でexceptionでない場合処理する。
if Vexception <> “1” thenの場合、
assgin:
Vnum = dt2.Rows.IndexOf(row)+2 (何行目のデータか把握)
write cell
"シート名” 行列の設定: “D” + Vnum (D列でVmun行目)
select文で値を取得

select実行、elseの場合は処理しないにしたいと考えます。

フローは下記のようにしました。for each row 以降

例外は処理コスト的に重いのもあって、不必要に発生しない(させない)に越したことはない、ので。

上のコメントに書いたように、

DataTable型変数.Select("クエリ")

は、DataRow型の配列になるので、

DataTable型変数.Select("クエリ").Length

で、該当する行数を取得できます。(戻り値はInt32型になります)

なので、たとえばSwitchアクティビティを使い、戻り値が1だったら正常系、0だったらマスタに該当行なし、1より大きければマスタデータの不備として処理、といった感じで分岐してはいかがでしょう。

1 Like

ありがとうございます。

for each row以降ですが
row in dt2
assign:処理行を取得
Vnum = dt1.select(“ID = '” + row(“ID”).ToString() + “'”).Length

switch
case: 0 キーマッチングできない
case:1
write cell
"シート名” 行列の設定: “D” + Vnum (D列でVmun行目)
select文で値を取得

default: マスターエラーとする

フロー:

UIPATHの知識とともにvb.netの知識があればマクロよりUIPATHで簡単にできること
改めて分かりました。
.netの文字列操作や時間計算でのメソッドを利用していますが、
select文の利用もと思いました。
それとセレクターの理解が今一つです。
多忙なところ本当にありがとうございました。

ありがとうございます。
for each for で再度、for each rowで処理した場合、1Hかかっていたものが、
for each forからselect文の記載に変更することで1min30secと処理時間が減りました。

VB.netに関してですが、
dt1.select(“ID = '” + row(“ID”).ToString() + “'”).Lengthでの戻り値が0,1,1以上できますが
vb.netの仕様見ると文字列の長さを調べるということでした。
ということは、キー一致の場合のステータスと考えるのですがこの認識で間違いないでしょうか。

行数取得は、dt2.Rows.IndexOf(row)+2 で行うことにしました。

vb.netの仕様見ると文字列の長さを調べるということでした。

~.Length プロパティは用途に応じていくつか意味があります。
英語の意味合い的には「長さ」になるのですが。

System.String型のLengthプロパティだと、書かれているように「文字列の長さ」になります。

System.Array型のLengthプロパティでは、「配列内の要素の数」です。
今回のDataTable.Selectの戻り値の場合はこちらになります。(System.Arrayは少しややこしい概念ですが、配列そのもののデータ型と思ってください)

他の例だと、System.IO.FileInfoクラス(ファイルの情報を保持する)のLengthプロパティは「該当するファイルの容量」だったりもします。

このあたりは少しとっつきにくい部分でもあるのですが、意味の決め打ちではなく、「.NET Framework系の言語では、.Lengthはだいたい長さやサイズをあらわすもの(プロパティ)」ぐらいの漠然とした概念で覚えておくと、色々な場合に役に立つのではないかと思います。

1 Like