Excel VBA 部品表展開
部品表とは
部品表(BOM:bill of materials) は、製造業の基本中の基本の、マスター・データです。
部品表は、活用する場面によって、6つの展開方法を使います。
1.シングル・レベル展開 | 2.シングル・レベル逆展開 |
3.マルチ・レベル展開 | 4.マルチ・レベル逆展開 |
5.集約展開 | 6.集約逆展開 |
コンピュータのデータベースの中には、シングル・レベル展開 の形式でデータを管理して、重複を防いでいます。
ここでは、シングル・レベル展開のデータを、マルチ・レベル展開と、集約展開する Excel マクロ、
および、マルチ・レベル展開のデータから、シングル・レベル展開のデータを抽出する マクロを、紹介します。
[著作権]
このページは、オリジナルのアイデアでマクロを作成しており、渡辺真が、著作権を保有します。
クリエイティブ・コモンズの「表示-非営利-継承 Attribution-NonCommercial-ShareAlike」に準拠して、公開します。
部品表のローレベル・コード設定
部品表では、同一品が、複数の階層に出現する可能性が有ります。このとき、その親品目が使われる、もっとも下のレベルのレベル番号を、ローレベルコード(LLC)と呼びます。
A←レベル1
┏━┻┓
B C←レベル2
┏┻┓┏┻┓
イ ロD B←レベル3
┏┻┓
イ ロ
上の例では、品目Bは、レベル2でも、レベル3でも出現します。このとき、品目Bのロー・レベル・コードは、3となります。
最下位にしかならない子品番には、ローレベル・コードを設定する必要は有りません。
部品表を、バッチ処理するときには、親子関係の情報を、ローレベルコードで並べて使います。
例えば、MRP(Material Requirements Planning)所要量計算をするときには、ローレベルコードの小さいものから大きなものに、順に計算します。これは、上位(親)の所要量が全て求まった時点で、親の所要量から、直下の品目の所要量を計算するので、ローレベル・コード順に処理していけば、一回だけで、全ての構成品の所要量を、計算できるからです。
また、重量累積(個数累積や、原価積み上げも)をする場合は、ローレベル・コードの逆順に並べて計算します。下位の品目の重量の合計が、上位の品目の累積重量になるので、この順に計算すれば、一回の処理で、全ての品目の累積重量を計算できることになります。
ローレベル・コード設定(その1)
下記は、サンプル部品表データ(サンプル:bom0001_2.txt)から、ローレベル・コードを設定して、ローレベルコード順に、表示するマクロです。
このテキスト・ファイルは、部品表を「シングル・レベルで、親子の構成情報を一行ずつのデータ」として並べたものです。
ローレベル・コードを設定する過程で、部品表データの ループ不具合 も見つけることができます。ループとは、自分の子供(子孫)に、自分自身が再登場する、有ってはいけない状況のことです。
適当なフォルダに、bom0001_2.txtを保存して、LowLevelCodeVBA2.xlsを動かしてみて下さい。
★ローレベル・コード設定のアルゴリズム★
読み込んだ、データの1行目から、最後の行まで、
親品目番号について、その品目番号が、子品目番号に存在するかどうかをチェックする。
子品目番号に存在しない場合は、その親品目番号のローレベル・コードは、1(L)となる。
また、1行目に戻って、最後の行まで、ローレベル・コード未設定の親品目番号について、
その品目番号が、ローレベル・コード未設定、もしくは設定中のローレベルの子品目番号に
存在するかどうかをチェックする。
子品目番号に存在しない場合は、その親品目番号のローレベル・コードは、L+1となる。
これを、ローレベル・コードが設定される対象親品目番号が摘出されるかぎり、繰返す。
(通常、繰返し回数は20レベル程度に制限することができる。)
ローレベル・コードが設定されない構成が、ループ構成になります。
配列から、セル範囲に、データを一括登録したり、逆に、セル範囲を、一気に配列に読み込むと、スピードも速く、便利です。
Resize プロパティを使って、セル範囲を指定して、配列を使う方法 の項を、参照下さい。
下の、「部品表のローレベル・コード設定」マクロでは、ソート処理で、この配列操作を使っています。
ローレベル・コード設定(その2)
上の、ローレベル・コード設定(その1)では、構成展開に、Excelの表を使っていました。これだと、Excelのワークシートのサイズ 65,536 行 ( Excel 2007〜 でも、1,048,576 行)の制約を受けます。
ここで紹介するマクロは、親品目番号単位に、1ファイルを作成して、MTFSの機能を使うことにより、高速かつ、品目点数の制約無く、集約展開できるようにしたものです。
FAT 32 では、一つのフォルダに登録できるファイル数は、65,534 に制限されます。しかし、MTFS だと、登録できるファイル数は無制限です。
このため、MTFS ドライブのフォルダにファイルを登録すれば、ファイル名を使ってダイレクトにファイル内容を読み出す事ができるので、ランダム・アクセス・メモリとして使えるのです。
本体メモリではなく、ディスク・ドライブを作業領域として使うので、大量の構成にも対応できます。
ロー・レベル・コードの設定では、シングルレベル逆展開を利用しています。
親品目番号が、子の欄にあるかどうかは、シングルレベル逆展開を使うと、シングルレベル逆展開構成の有無で、瞬時に判断できます。
A←レベル1
┏━┻┓
B C←レベル2
┏┻┓┏┻┓
イ ロD B←レベル3
┏┻┓
イ ロ
| シングルレベル正展開 |
| シングルレベル逆展開 |
|
LLC | 親(キー) | 子 |
| 子(キー) | 親 |
|
1 | A | B |
| B | A | ←A削除 |
| A | C |
| B | C |
|
3 | B | イ |
| C | A | ←A削除 |
| B | ロ |
| D | C |
|
2 | C | B |
| イ | B |
|
| C | D |
| ロ | B |
|
|
|
|
| ↑@Aが存在しない:AのLLCは、1 |
|
|
|
|
|
| ↑BCが存在しない:CのLLCは、2 |
|
|
構成の親品目番号リスト(上の例では、A,B,C)を、シングル・レベル逆展開のキーと照合して、シングル・レベル逆展開に存在しない品目を見つけます。(この場合は、Aのみ)
この結果、LLC 1は、Aだけと確定します。@
LLC 1のメンバが全員分かった時点で、シングル・レベル逆展開から、見つけた親品目(この場合はAのみ)の構成を、削除します。A
残った親品目番号リスト(ここでは、B,C)を、残ったシングル・レベル逆展開のキーと照合して、シングル・レベル逆展開に存在しない品目を見つけます。(この場合は、Cのみ)
この結果、LLC 2は、Cだけと確定します。B
以下同様に、親品目番号を、LLCが決定したものを消し込んでいって、全てLLC が決まるか、もしくは該当が見つからなくなれば、LLC 探索は終わりです。
LLCの最大値は、対象とする部品表環境で、違いますが、それほど大きな数ではありません。
上の二つのファイルを、「専用フォルダ(例えば test )」に保存して下さい。そして、そのフォルダから Excel を開いて、「集約展開」のボタンを押します。
保存したフォルダの中にサブフォルダ「結果」が作られて、そこに、集約展開結果のファイルが作られます。
変数の説明
★前準備
シングル構成ファイルをカウントしながら読み込んで、配列のサイズを決定する。
(配列は、Variant型よりも、String型を使った方が、メモリ消費が少ない。
また、ReDim Preserve で、順次サイズを拡大するよりも、前もってサイズを定義した方が、メモリ消費が少ない。)
★LLC 設定
シングル構成を読み込むための配列
一時配列:親品番30、子品番30、親当たり数量5。ソート順位が狂わないように固定長にして格納する。
シングル逆展開構成を登録するための配列(配列行数は、上のシングル構成を読み込むための配列と同じ)
一時配列temp:子品番30、親品番30、親当たり数量5、親LLC3
親品番とLLCを登録するための配列
親品番LLC配列:親品番、LLC
「親品番LLC配列」で、親品番をキーに、配列の該当行数を検索するための連想配列
親品番索引:親品番、親品番LLC配列での該当行数←★連想配列
★集約展開
シングル構成を読み込むための配列
一時配列:親品番30、子品番30、親当たり数量5。ソート順位が狂わないように固定長にして格納する。
親品番LLC配列:親品番、LLC←上のLLC設定で作った配列そのもの
集約展開構成は巨大(全体の構成行数は、シングル構成の8倍程度になる)になる
このマクロで、某メーカさんの構成データを、シングル→LLC設定→集約展開した結果は、下記のようになりました。
そこそこのパソコンを使えば、6百万件の部品表構成でも、実用レベルの処理時間で集約展開できることが分かりました。(集約展開の出力行数は、7千万行!)
| 区分 | 親品番点数 | 子品番点数 | 品番点数 | 構成(シングル親子関係)数 | 集約展開 処理時間 | 集約展開 構成件数 |
1 | bom0001_3.txt | 268 | 1,008 | 1,036 | 1,841 | (0.2分) | 12,129 |
2 | 客先データ1 | 274,794 | 525,325 | 596,291 | 2,523,033 | 3.5H | 33,223,076 |
3 | 客先データ2 | 802,095 | 1,459,990 | 1,643,284 | 6,038,432 | 12H | 70,676,411 |
部品表は、親品目と、子品目の「関係データ」のため、展開にシーケンシャル処理を使うと、構成点数が増えると、処理に掛け算で時間がかかります。(例えば、構成件数が10倍だと、親10×子10で、100倍)
これに対して、このマクロは、ランダム・アクセスを使っているので、構成点数と、処理時間が、リニアとはいきませんが、爆発的な増加にはなっていないところがミソです。
シングル構成部分は、メモリの配列に登録するので、処理できる構成数は、メモリに依存します。
連想配列を使って、品目番号をキーとして配列の該当部分を参照しています。
解説:
処理しているExcelのパスは、下記で求めています。
現在のパス = ActiveWorkbook.Path
もしくは、
現在のパス = ThisWorkbook.Path
シングル逆展開を出力
以下は、シングル正展開ファイルを読んで、シングル逆展開ファイルを作成する部分です。
ForAppending を使って、追記書き出ししています。
部品表集約展開
LLCの大きいものから(部品表の最下位構成から)、順に子品目の品目番号と、その構成数量を累積していきます。
LLCを使って集約するため、一つの親品目は、検索・展開は一回だけで、高速に処理できます。
親品目の直下の構成は、シングルレベルの構成をそのまま使います。孫は、既に計算済みの集約展開の結果を使います。
通常、集約展開の過程で、同一品目番号が構成子品目と、孫品目との両方から複数行に展開されてきます。ソートすると同一子品目番号が並ぶので、同一子品目番号の数量を合計して、一品目番号について一行だけ、集約展開結果に出力します。
このため、親品目単位の集約構成の検索結果を、いったん Excel のシート「ワーク」に書き出します。
このワークに書き出したものを、Excelの機能を使ってソートします。
(一品目の構成子品目は、さすがに 65,536 行までにはならないので、Excelシートの並び替え機能を使っています。)
マクロとサンプル・データは、ローレベル・コード設定(その2)のところに掲載しています。
連想配列を使って、品目番号をキーに、配列の該当部分を参照しています。
レベル= 17 | 集約展開構成件数= 2 | 当該レベル処理時間= 0時間00分00秒 | 累積= 0時間21分53秒 |
レベル= 16 | 集約展開構成件数= 12 | 当該レベル処理時間= 0時間00分00秒 | 累積= 0時間21分53秒 |
レベル= 15 | 集約展開構成件数= 45 | 当該レベル処理時間= 0時間00分00秒 | 累積= 0時間21分53秒 |
レベル= 14 | 集約展開構成件数= 199 | 当該レベル処理時間= 0時間00分01秒 | 累積= 0時間21分54秒 |
レベル= 13 | 集約展開構成件数= 629 | 当該レベル処理時間= 0時間00分00秒 | 累積= 0時間21分54秒 |
レベル= 12 | 集約展開構成件数= 2,041 | 当該レベル処理時間= 0時間00分02秒 | 累積= 0時間21分56秒 |
レベル= 11 | 集約展開構成件数= 6,309 | 当該レベル処理時間= 0時間00分05秒 | 累積= 0時間22分01秒 |
レベル= 10 | 集約展開構成件数= 17,833 | 当該レベル処理時間= 0時間00分13秒 | 累積= 0時間22分14秒 |
レベル= 9 | 集約展開構成件数= 50,572 | 当該レベル処理時間= 0時間00分38秒 | 累積= 0時間22分52秒 |
レベル= 8 | 集約展開構成件数= 142,366 | 当該レベル処理時間= 0時間01分45秒 | 累積= 0時間24分37秒 |
レベル= 7 | 集約展開構成件数= 376,111 | 当該レベル処理時間= 0時間05分16秒 | 累積= 0時間29分53秒 |
レベル= 6 | 集約展開構成件数= 947,833 | 当該レベル処理時間= 0時間09分15秒 | 累積= 0時間39分08秒 |
レベル= 5 | 集約展開構成件数= 2,220,503 | 当該レベル処理時間= 0時間17分16秒 | 累積= 0時間56分24秒 |
レベル= 4 | 集約展開構成件数= 5,359,284 | 当該レベル処理時間= 0時間29分59秒 | 累積= 1時間26分23秒 |
レベル= 3 | 集約展開構成件数= 12,928,118 | 当該レベル処理時間= 1時間28分13秒 | 累積= 2時間54分36秒 |
レベル= 2 | 集約展開構成件数= 32,437,979 | 当該レベル処理時間= 2時間12分41秒 | 累積= 5時間07分17秒 |
レベル= 1 | 集約展開構成件数= 70,676,411 | 当該レベル処理時間= 2時間50分03秒 | 累積= 7時間57分20秒 |
品番別の集約展開を、品番別のテキスト・ファイルに書き出すよりも、配列に格納したほうが当然高速になります。
しかし、配列の構成数が多くなると、メモリ制限で、「文字列領域が不足しています。」となって集約展開できません。
このため、ここでは大量の構成でも展開できるように、品目番号別の集約展開結果は、MTFS を使って、親品目番号単位のテキスト・ファイルに書き出しています。
品目番号にファイル名として使えない文字は、ユーザ関数で置換しています。
ファイル名として使えない禁止文字を、部品番号の文字列として出現しなくて、ファイル名として使える全角文字に変換する、ユーザ関数。
部品表マルチ・レベル展開・マルチ・レベル逆展開
使い方:
標準BOMファイル(サンプル:bom0001_2.txt)を、適当なフォルダに保存します。
そして、マクロのボタンを押します。マクロから、ファイルの場所を聞いてきます。
(解説はVBAの項)
部品表マルチ・レベル→シングル・レベル抽出
右の図は、部品表のマルチ・レベル展開表示の例です。
右の図で、・の数がレベルを表しています。
マルチ・レベルのデータは、そのレベルの途中に、下位のレベルが挟まっています。
このため、同一レベルをまとめるために、レベル毎に抽出したデータを、ソートして、親品目番号単位にまとめています。
また、マルチ・レベルのデータは、同一構成が、重複して出現する可能性があります。このため、マクロでは、ソート後に、重複データを行削除しています。
下のマクロでは、CSV から読み込んだマルチ・レベルのデータを、一行ごとに、一元配列の「データ配列(28)」に格納しています。
配列データを、セル群に直接書き出すためには、配列は二元配列でなければなりません。このため、一元配列に読みこんだデータは、Transpose を使って、二元配列に格納し直しています。
子品目番号に対応する親品目番号は、「親品番配列(10, 28)」 にレベル毎に格納します。
ここでは、レベルは10までとして、配列の行を上書きすることで、レベル毎に読み込んだ最後の行データのみが親品番配列に残るようにしています。
CSV ファイルの読み込み部分は、Excelでお仕事! の CSV形式テキストデータの読み込み のコードを使わせていただいています。
このマクロとサンプル・データをダウンロードできます。
解説
Input # ステートメント は、シーケンシャル入力モード (Input) で開いたファイルからデータを読み込んで、それを変数に格納するファイル入出力ステートメントです。
構文
Input #filenumber, varlist
Input # ステートメントの構文は、次の指定項目から構成されます。
指定項目 |
内容 |
filenumber |
必ず指定します。任意のファイル番号を指定します。 |
varlist |
必ず指定します。ファイルから読み込んだデータを格納するための変数を、1 つまたは複数指定します。
複数指定するときは、カンマ (,) で区切って指定します。
配列変数、ユーザー定義型の変数、またはオブジェクト変数を指定することはできません。
ただし、配列の要素またはユーザー定義型の要素は、指定できます。 |
通常、Input # ステートメントを使用して読み込んだデータは、Write # ステートメントを使用して書き込みます。
Input # ステートメントは、シーケンシャル入力モード (Input) またはバイナリ モード (Binary) で開いたファイルに対してだけ使用します。
ファイルからデータを読み込む場合、通常、文字列データは文字列型 (String)、数値データは数値データ型として格納されます。
これ以外のデータを読み込んだ場合、次に示すようにデータによって変数に割り当てられる型が異なります。
データ |
変数に格納される値 |
カンマのみ、または空白行 |
Empty 値 (VarType 0) |
#NULL# |
Null 値 (VarType 1) |
#TRUE# または #FALSE# |
真 (True) または偽 (False) |
#yyyy-mm-dd hh:mm:ss# |
式によって表された日付と時刻 |
#ERROR errornumber# |
errornumber (エラー値として格納されたバリアント型 (Variant) |
入力データ内のダブル クォーテーション (""
) は無視されます。
メモ
Input # ステートメントで、"1,2""X"
のようなクォーテーションを含む文字列を記述しないようにしてください。
このような場合、独立した 2 つの文字列として認識されます。
ファイル内のデータ項目の順番は、引数 varlist で指定した変数の順番と一致している必要があります。
また、ファイル内の各データ項目のデータ型は、対応する変数のデータ型と一致していなければなりません。
たとえば、変数が数値データ型で読み込むデータが数値データ型でない場合、変数には 0 が代入されます。
データを入力してきるときにファイルの末尾に達すると、入力が終了し、エラーが発生します。
メモ
Input # ステートメントを使用してファイルから変数へデータを正しく読み込むことができるように、データをファイルに書き込む場合は、Print # ステートメントではなく、必ず Write # ステートメントを使用してください。
Write # ステートメントを使用すると、ファイルにデータを書き込むときに各データ項目の間に正しくカンマ (,) が挿入されます。
使用例
次の例は、Input # ステートメントを使って、ファイルのデータを読み込み、2 つの変数に代入しています。
この例のファイル TESTFILE
には、Write # ステートメントを使って、データが書き込まれているものと仮定します。
データは、"Hello", 234 のように、文字列はダブル クォーテーション (") で囲まれ、数値はカンマ (,) で区切られています。
FreeFile 関数 は、使用可能なファイル番号を整数型 (Integer) の値で返すファイル入出力関数です。
構文
FreeFile[(rangenumber)]
引数 rangenumber には、ファイル番号の範囲をバリアント型 (Variant) で指定します。指定した範囲から次に使用可能なファイル番号を返します。この引数は省略可能です。
0 (既定値)1 〜 255 の範囲のファイル番号が返されます。
1256 〜 511 の範囲のファイル番号が返されます。
使用可能なファイル番号を取得するために FreeFile 関数を使用します。
既に使われているファイル番号を重複して使うのを防ぐことができます。
使用例
次の例は、FreeFile 関数を使って、次に使用可能なファイル番号を返します。
この例では、ループ内で 5 つのファイルをシーケンシャル出力モード (Output) で開いています。
各ファイルには、サンプル データが書き込まれているものと仮定します。