10
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Oracle DatabaseでエンジンBOMをProperty Graph化(基礎編)

10
Posted at

Oracle DatabaseでエンジンBOMをProperty Graph化(基礎編)

この記事を試す前に、Autonomous Database をまだ作成していない場合は、先に次の Oracle Japan OCI チュートリアルを参照してください。
ADBインスタンスを作成してみよう | OCIチュートリアル

この記事では、ADB上でSQLを実行し、Property Graph を作成し、最後に APEX で可視化する 流れを前提にしています。

engine_bom_oracle.sql を題材に、テーブル設計 → データ投入 → Graph化 → Graph Query → APEX可視化 の順で、解説します。

SQLファイルそのものを読み解く記事 として整理しました。
Graph Query と可視化 まで扱います。


はじめに

BOM(部品表)は、表で管理していても本質は 「親品目と子品目のつながり」 です。
つまり、RDBで正しく設計したうえで、必要な場面だけ Graph として扱えるようにしておくと、次のようなことが一気に分かりやすくなります。

  • 多階層の部品展開
  • 改訂付きBOMの追跡
  • 影響範囲の把握
  • 可視化ツールでの直感的な理解

今回の engine_bom_oracle.sql は、

  • 品目マスタ
  • BOMヘッダ
  • BOM明細
  • 業務意味を埋め込んだ COMMENT
  • MERGE ベースの投入処理
  • CREATE PROPERTY GRAPH

までをひとまとめにした、SQLです。

engine_bom_oracle.sql
-- ============================================================================
-- 1) DDL: 製造業向けアセンブリBOM
-- ============================================================================

CREATE TABLE mfg_item (
    item_id            NUMBER GENERATED ALWAYS AS IDENTITY,
    item_code          VARCHAR2(50 CHAR)   NOT NULL,
    item_name          VARCHAR2(200 CHAR)  NOT NULL,
    item_type          VARCHAR2(30 CHAR)   NOT NULL,
    procurement_type   VARCHAR2(20 CHAR)   NOT NULL,
    uom_code           VARCHAR2(10 CHAR)   NOT NULL,
    revision_code      VARCHAR2(20 CHAR)   DEFAULT 'A'      NOT NULL,
    lifecycle_status   VARCHAR2(20 CHAR)   DEFAULT 'ACTIVE' NOT NULL,
    drawing_no         VARCHAR2(100 CHAR),
    description_text   VARCHAR2(1000 CHAR),
    CONSTRAINT pk_mfg_item PRIMARY KEY (item_id),
    CONSTRAINT uk_mfg_item_code UNIQUE (item_code),
    CONSTRAINT ck_mfg_item_type CHECK (item_type IN ('FG','ASM','PART','RM','CONSUMABLE')),
    CONSTRAINT ck_mfg_item_proc CHECK (procurement_type IN ('MAKE','BUY','BOTH')),
    CONSTRAINT ck_mfg_item_lifecycle CHECK (lifecycle_status IN ('ACTIVE','INACTIVE','OBSOLETE'))
);

CREATE TABLE asm_bom_header (
    bom_id                 NUMBER GENERATED ALWAYS AS IDENTITY,
    bom_code               VARCHAR2(50 CHAR)   NOT NULL,
    assembly_item_id       NUMBER              NOT NULL,
    bom_name               VARCHAR2(200 CHAR)  NOT NULL,
    bom_revision           VARCHAR2(20 CHAR)   NOT NULL,
    alternate_bom_code     VARCHAR2(20 CHAR)   DEFAULT 'PRIMARY'  NOT NULL,
    effectivity_start_date DATE                NOT NULL,
    effectivity_end_date   DATE,
    status_code            VARCHAR2(20 CHAR)   DEFAULT 'RELEASED' NOT NULL,
    description_text       VARCHAR2(1000 CHAR),
    CONSTRAINT pk_asm_bom_header PRIMARY KEY (bom_id),
    CONSTRAINT uk_asm_bom_header_code UNIQUE (bom_code),
    CONSTRAINT uk_asm_bom_header_item_rev_alt UNIQUE (assembly_item_id, bom_revision, alternate_bom_code),
    CONSTRAINT fk_asm_bom_header_item FOREIGN KEY (assembly_item_id)
        REFERENCES mfg_item (item_id),
    CONSTRAINT ck_asm_bom_header_status CHECK (status_code IN ('DRAFT','RELEASED','OBSOLETE')),
    CONSTRAINT ck_asm_bom_header_effective CHECK (
        effectivity_end_date IS NULL OR effectivity_end_date >= effectivity_start_date
    )
);

CREATE TABLE asm_bom_component (
    bom_line_id        NUMBER GENERATED ALWAYS AS IDENTITY,
    bom_id             NUMBER             NOT NULL,
    parent_item_id     NUMBER             NOT NULL,
    component_item_id  NUMBER             NOT NULL,
    line_no            NUMBER(6)          NOT NULL,
    find_no            VARCHAR2(30 CHAR),
    component_qty      NUMBER(18,6)       NOT NULL,
    component_uom      VARCHAR2(10 CHAR)  NOT NULL,
    scrap_factor       NUMBER(9,6)        DEFAULT 0 NOT NULL,
    yield_factor       NUMBER(9,6)        DEFAULT 1 NOT NULL,
    operation_seq      NUMBER(6),
    supply_type        VARCHAR2(20 CHAR)  DEFAULT 'PULL' NOT NULL,
    optional_yn        CHAR(1)            DEFAULT 'N' NOT NULL,
    remarks            VARCHAR2(1000 CHAR),
    CONSTRAINT pk_asm_bom_component PRIMARY KEY (bom_line_id),
    CONSTRAINT uk_asm_bom_component_bom_line UNIQUE (bom_id, line_no),
    CONSTRAINT fk_asm_bom_component_bom FOREIGN KEY (bom_id)
        REFERENCES asm_bom_header (bom_id) ON DELETE CASCADE,
    CONSTRAINT fk_asm_bom_component_parent FOREIGN KEY (parent_item_id)
        REFERENCES mfg_item (item_id),
    CONSTRAINT fk_asm_bom_component_comp FOREIGN KEY (component_item_id)
        REFERENCES mfg_item (item_id),
    CONSTRAINT ck_asm_bom_component_qty CHECK (component_qty > 0),
    CONSTRAINT ck_asm_bom_component_scrap CHECK (scrap_factor >= 0 AND scrap_factor < 1),
    CONSTRAINT ck_asm_bom_component_yield CHECK (yield_factor > 0 AND yield_factor <= 1),
    CONSTRAINT ck_asm_bom_component_supply CHECK (supply_type IN ('PUSH','PULL','KIT')),
    CONSTRAINT ck_asm_bom_component_optional CHECK (optional_yn IN ('Y','N')),
    CONSTRAINT ck_asm_bom_component_no_self CHECK (parent_item_id <> component_item_id)
);

CREATE INDEX ix_asm_bom_header_item      ON asm_bom_header (assembly_item_id);
CREATE INDEX ix_asm_bom_component_bom    ON asm_bom_component (bom_id);
CREATE INDEX ix_asm_bom_component_parent ON asm_bom_component (parent_item_id);
CREATE INDEX ix_asm_bom_component_comp   ON asm_bom_component (component_item_id);

COMMENT ON TABLE mfg_item IS q'~{"表示名":"製造品目マスタ","説明":"BOMで参照される完成品、中間アセンブリ、購入部品、原材料、消耗品を一意に管理する中核マスタ。1行が1品目であり、BOMヘッダの assembly_item_id と BOM明細の parent_item_id / component_item_id の参照先になる。AIは item_type で構成上の役割、procurement_type で調達方式、lifecycle_status で使用可否を解釈する。"}~';
COMMENT ON TABLE asm_bom_header IS q'~{"表示名":"アセンブリBOMヘッダ","説明":"1つの親品目に対する1改訂・1代替BOMの定義を表すテーブル。どの品目に対するBOMか、どの改訂か、いつ有効か、利用状態が何かを管理する。多階層BOMではサブアセンブリごとにもヘッダを持たせ、再帰展開や改訂管理を安定して行う。"}~';
COMMENT ON TABLE asm_bom_component IS q'~{"表示名":"アセンブリBOM明細","説明":"BOMヘッダ配下の1明細行を表すテーブル。親品目1単位あたりに必要な子品目、所要数量、工程、歩留、支給方式などを保持する。parent_item_id は asm_bom_header.assembly_item_id を冗長保持した列で、再帰SQLとグラフ変換の起点キーとして使う。"}~';
COMMENT ON COLUMN mfg_item.item_id IS q'~{"表示名":"品目ID","説明":"システム内部で使用するサロゲート主キー。人間可読な品目コードとは分離し、名称変更やコード体系変更が起きても参照整合性を維持する。"}~';
COMMENT ON COLUMN mfg_item.item_code IS q'~{"表示名":"品目コード","説明":"ERP/MES/PLMなど複数システムで同一品目を突合するための業務キー。接頭辞や語尾から製品群や用途を推測できるが、厳密な一意性は item_id で担保する。"}~';
COMMENT ON COLUMN mfg_item.item_name IS q'~{"表示名":"品目名称","説明":"画面表示、帳票、検索に使う日本語の代表名称。AIが自然言語で品目意味を理解する際の主要手掛かりになる。"}~';
COMMENT ON COLUMN mfg_item.item_type IS q'~{"表示名":"品目区分","説明":"完成品(FG)、中間アセンブリ(ASM)、部品(PART)、原材料(RM)、消耗品(CONSUMABLE)などの役割区分。BOM展開時にノードの意味を判定する基礎属性。"}~';
COMMENT ON COLUMN mfg_item.procurement_type IS q'~{"表示名":"調達区分","説明":"自社製造(MAKE)、購入(BUY)、両方可(BOTH)を示す属性。生産計画、原価計算、代替調達判断で重要な分類。"}~';
COMMENT ON COLUMN mfg_item.uom_code IS q'~{"表示名":"在庫単位","説明":"品目を在庫・購買・生産で数える基本単位。通常は EA、SET、KG などを保持し、BOM明細の component_uom の基準にもなる。"}~';
COMMENT ON COLUMN mfg_item.revision_code IS q'~{"表示名":"品目改訂","説明":"品目自体の設計改訂や仕様版数。BOM改訂とは別概念であり、部品単体の変更履歴を識別する。"}~';
COMMENT ON COLUMN mfg_item.lifecycle_status IS q'~{"表示名":"ライフサイクル状態","説明":"ACTIVE、INACTIVE、OBSOLETE などの状態。BOMで新規採用できるか、既存保守のみか、使用停止かを判断する。"}~';
COMMENT ON COLUMN mfg_item.drawing_no IS q'~{"表示名":"図面番号","説明":"図面・CAD・PLM上の正式な参照番号。存在する場合は設計文書と品目を結び付ける主キー候補として扱う。"}~';
COMMENT ON COLUMN mfg_item.description_text IS q'~{"表示名":"品目説明","説明":"自由記述の仕様補足、用途、材質、注意事項を保持する説明文。AIによる検索・分類・要約の入力情報として活用できる。"}~';
COMMENT ON COLUMN asm_bom_header.bom_id IS q'~{"表示名":"BOM ID","説明":"システム内部で使用するBOMヘッダのサロゲート主キー。BOM明細が参照する親キーであり、改訂単位の構成を一意に識別する。"}~';
COMMENT ON COLUMN asm_bom_header.bom_code IS q'~{"表示名":"BOMコード","説明":"人間可読なBOM識別子。外部システムや帳票で特定のBOM改訂を識別する業務キーとして利用する。"}~';
COMMENT ON COLUMN asm_bom_header.assembly_item_id IS q'~{"表示名":"親品目ID","説明":"このBOMが属する親アセンブリ品目の ID。1つのBOMヘッダは必ず1つの親品目に対応し、サブアセンブリも同じ仕組みで表現する。"}~';
COMMENT ON COLUMN asm_bom_header.bom_name IS q'~{"表示名":"BOM名称","説明":"画面・帳票向けのBOM表示名。通常は親品目名に「BOM」を付けた業務名称を保持する。"}~';
COMMENT ON COLUMN asm_bom_header.bom_revision IS q'~{"表示名":"BOM改訂","説明":"構成表そのものの改訂番号。品目改訂と独立して管理でき、構成変更のみを追跡したい場合に重要となる。"}~';
COMMENT ON COLUMN asm_bom_header.alternate_bom_code IS q'~{"表示名":"代替BOM区分","説明":"PRIMARY、ALT1 などの代替構成識別子。仕向地差、工場差、代替工程差など複数BOMを並行管理するときに使う。"}~';
COMMENT ON COLUMN asm_bom_header.effectivity_start_date IS q'~{"表示名":"有効開始日","説明":"このBOM改訂を使用開始できる日付。製造日や計画日を基準に適用BOMを判定する際の下限となる。"}~';
COMMENT ON COLUMN asm_bom_header.effectivity_end_date IS q'~{"表示名":"有効終了日","説明":"このBOM改訂の使用終了日。NULL の場合は終了未定で継続有効と解釈する。"}~';
COMMENT ON COLUMN asm_bom_header.status_code IS q'~{"表示名":"BOM状態","説明":"DRAFT、RELEASED、OBSOLETE などの状態。生産で使用可能か、設計中か、廃止済みかを示す。"}~';
COMMENT ON COLUMN asm_bom_header.description_text IS q'~{"表示名":"BOM説明","説明":"当該BOM改訂の適用条件、変更理由、注意事項などを記述する自由文。AIが構成変更理由を理解する補足情報になる。"}~';
COMMENT ON COLUMN asm_bom_component.bom_line_id IS q'~{"表示名":"BOM明細ID","説明":"BOM明細行のサロゲート主キー。1行1構成関係を一意に識別し、グラフ変換時にはエッジキーとして利用できる。"}~';
COMMENT ON COLUMN asm_bom_component.bom_id IS q'~{"表示名":"BOM ID","説明":"どのBOMヘッダに属する明細かを示す外部キー。改訂、有効期間、代替BOM区分などの文脈を明細に結び付ける。"}~';
COMMENT ON COLUMN asm_bom_component.parent_item_id IS q'~{"表示名":"親品目ID","説明":"親アセンブリ品目のID。asm_bom_header.assembly_item_id を冗長保持した列で、再帰SQLとプロパティグラフの SOURCE KEY を単純化するために持つ。"}~';
COMMENT ON COLUMN asm_bom_component.component_item_id IS q'~{"表示名":"子品目ID","説明":"親品目にぶら下がる構成品目のID。購入部品、中間アセンブリ、原材料のいずれも取り得る。"}~';
COMMENT ON COLUMN asm_bom_component.line_no IS q'~{"表示名":"行番号","説明":"BOM内での表示順・論理順を表す番号。通常は 10,20,30 のように採番し、途中挿入をしやすくする。"}~';
COMMENT ON COLUMN asm_bom_component.find_no IS q'~{"表示名":"図示番号","説明":"組図のバルーン番号や部品表参照番号。図面上の部品位置とBOM行を対応付ける。"}~';
COMMENT ON COLUMN asm_bom_component.component_qty IS q'~{"表示名":"所要数量","説明":"親品目1単位あたりに必要な子品目数量。歩留やスクラップを加味する前の基準数量として扱う。"}~';
COMMENT ON COLUMN asm_bom_component.component_uom IS q'~{"表示名":"明細単位","説明":"当該BOM行で数量を解釈する単位。品目の在庫単位と一致することが多いが、必要に応じて異なる表現も許容する。"}~';
COMMENT ON COLUMN asm_bom_component.scrap_factor IS q'~{"表示名":"スクラップ率","説明":"組付け時の損耗・廃棄・切り捨てを見込む追加率。0.02 なら基準数量に対して 2% のロスを見込む。"}~';
COMMENT ON COLUMN asm_bom_component.yield_factor IS q'~{"表示名":"歩留係数","説明":"工程を通過して良品として残る比率。1 に近いほど損失が少なく、必要投入量計算の補正に使う。"}~';
COMMENT ON COLUMN asm_bom_component.operation_seq IS q'~{"表示名":"工程順序","説明":"ルーティングや組立工程のどの工程で投入・組付けするかを示す番号。工程別ピッキングや投入タイミング制御に使う。"}~';
COMMENT ON COLUMN asm_bom_component.supply_type IS q'~{"表示名":"支給方式","説明":"PUSH、PULL、KIT などの供給方式。工程への払い出し方法やバックフラッシュ処理を判定する。"}~';
COMMENT ON COLUMN asm_bom_component.optional_yn IS q'~{"表示名":"任意部品フラグ","説明":"Y の場合は選択品・オプション品・条件付き採用品を表す。N は必須構成品を表す。"}~';
COMMENT ON COLUMN asm_bom_component.remarks IS q'~{"表示名":"明細備考","説明":"組付け注意、代替条件、締付トルク参照など、明細単位の補足指示を記録する自由文。"}~';



DECLARE
  FUNCTION get_item_id(p_item_code IN VARCHAR2) RETURN NUMBER IS
    v_item_id mfg_item.item_id%TYPE;
  BEGIN
    SELECT item_id
      INTO v_item_id
      FROM mfg_item
     WHERE item_code = p_item_code;
    RETURN v_item_id;
  END;

  FUNCTION get_bom_id(p_bom_code IN VARCHAR2) RETURN NUMBER IS
    v_bom_id asm_bom_header.bom_id%TYPE;
  BEGIN
    SELECT bom_id
      INTO v_bom_id
      FROM asm_bom_header
     WHERE bom_code = p_bom_code;
    RETURN v_bom_id;
  END;

  PROCEDURE upsert_item(
      p_item_code         IN VARCHAR2,
      p_item_name         IN VARCHAR2,
      p_item_type         IN VARCHAR2,
      p_procurement_type  IN VARCHAR2,
      p_uom_code          IN VARCHAR2 DEFAULT 'EA',
      p_revision_code     IN VARCHAR2 DEFAULT 'A',
      p_lifecycle_status  IN VARCHAR2 DEFAULT 'ACTIVE',
      p_drawing_no        IN VARCHAR2 DEFAULT NULL,
      p_description_text  IN VARCHAR2 DEFAULT NULL
  ) IS
  BEGIN
    MERGE INTO mfg_item t
    USING (
      SELECT p_item_code AS item_code,
             p_item_name AS item_name,
             p_item_type AS item_type,
             p_procurement_type AS procurement_type,
             p_uom_code AS uom_code,
             p_revision_code AS revision_code,
             p_lifecycle_status AS lifecycle_status,
             p_drawing_no AS drawing_no,
             COALESCE(
               p_description_text,
               p_item_name || '。2.0L直列4気筒ガソリンエンジンASSYのサンプルBOMで使用する品目。'
             ) AS description_text
        FROM dual
    ) s
       ON (t.item_code = s.item_code)
     WHEN MATCHED THEN UPDATE
       SET t.item_name        = s.item_name,
           t.item_type        = s.item_type,
           t.procurement_type = s.procurement_type,
           t.uom_code         = s.uom_code,
           t.revision_code    = s.revision_code,
           t.lifecycle_status = s.lifecycle_status,
           t.drawing_no       = COALESCE(s.drawing_no, t.drawing_no),
           t.description_text = COALESCE(s.description_text, t.description_text)
     WHEN NOT MATCHED THEN INSERT (
           item_code,
           item_name,
           item_type,
           procurement_type,
           uom_code,
           revision_code,
           lifecycle_status,
           drawing_no,
           description_text
       ) VALUES (
           s.item_code,
           s.item_name,
           s.item_type,
           s.procurement_type,
           s.uom_code,
           s.revision_code,
           s.lifecycle_status,
           s.drawing_no,
           s.description_text
       );
  END;

  PROCEDURE upsert_bom_header(
      p_bom_code               IN VARCHAR2,
      p_assembly_item_code     IN VARCHAR2,
      p_bom_name               IN VARCHAR2,
      p_bom_revision           IN VARCHAR2,
      p_alternate_bom_code     IN VARCHAR2,
      p_effectivity_start_date IN DATE,
      p_effectivity_end_date   IN DATE DEFAULT NULL,
      p_status_code            IN VARCHAR2 DEFAULT 'RELEASED',
      p_description_text       IN VARCHAR2 DEFAULT NULL
  ) IS
    v_assembly_item_id mfg_item.item_id%TYPE;
  BEGIN
    v_assembly_item_id := get_item_id(p_assembly_item_code);

    MERGE INTO asm_bom_header t
    USING (
      SELECT p_bom_code AS bom_code,
             v_assembly_item_id AS assembly_item_id,
             p_bom_name AS bom_name,
             p_bom_revision AS bom_revision,
             p_alternate_bom_code AS alternate_bom_code,
             p_effectivity_start_date AS effectivity_start_date,
             p_effectivity_end_date AS effectivity_end_date,
             p_status_code AS status_code,
             COALESCE(
               p_description_text,
               p_bom_name || '。有効開始日=' || TO_CHAR(p_effectivity_start_date, 'YYYY-MM-DD')
             ) AS description_text
        FROM dual
    ) s
       ON (t.bom_code = s.bom_code)
     WHEN MATCHED THEN UPDATE
       SET t.assembly_item_id       = s.assembly_item_id,
           t.bom_name               = s.bom_name,
           t.bom_revision           = s.bom_revision,
           t.alternate_bom_code     = s.alternate_bom_code,
           t.effectivity_start_date = s.effectivity_start_date,
           t.effectivity_end_date   = s.effectivity_end_date,
           t.status_code            = s.status_code,
           t.description_text       = COALESCE(s.description_text, t.description_text)
     WHEN NOT MATCHED THEN INSERT (
           bom_code,
           assembly_item_id,
           bom_name,
           bom_revision,
           alternate_bom_code,
           effectivity_start_date,
           effectivity_end_date,
           status_code,
           description_text
       ) VALUES (
           s.bom_code,
           s.assembly_item_id,
           s.bom_name,
           s.bom_revision,
           s.alternate_bom_code,
           s.effectivity_start_date,
           s.effectivity_end_date,
           s.status_code,
           s.description_text
       );
  END;

  PROCEDURE upsert_bom_line(
      p_bom_code         IN VARCHAR2,
      p_component_code   IN VARCHAR2,
      p_line_no          IN NUMBER,
      p_component_qty    IN NUMBER,
      p_component_uom    IN VARCHAR2 DEFAULT 'EA',
      p_operation_seq    IN NUMBER   DEFAULT NULL,
      p_scrap_factor     IN NUMBER   DEFAULT 0,
      p_yield_factor     IN NUMBER   DEFAULT 1,
      p_supply_type      IN VARCHAR2 DEFAULT 'PULL',
      p_optional_yn      IN CHAR     DEFAULT 'N',
      p_remarks          IN VARCHAR2 DEFAULT NULL
  ) IS
    v_bom_id            asm_bom_header.bom_id%TYPE;
    v_parent_item_id    asm_bom_header.assembly_item_id%TYPE;
    v_component_item_id mfg_item.item_id%TYPE;
  BEGIN
    v_bom_id            := get_bom_id(p_bom_code);
    v_component_item_id := get_item_id(p_component_code);

    SELECT assembly_item_id
      INTO v_parent_item_id
      FROM asm_bom_header
     WHERE bom_id = v_bom_id;

    MERGE INTO asm_bom_component t
    USING (
      SELECT v_bom_id AS bom_id,
             v_parent_item_id AS parent_item_id,
             v_component_item_id AS component_item_id,
             p_line_no AS line_no,
             LPAD(p_line_no, 4, '0') AS find_no,
             p_component_qty AS component_qty,
             p_component_uom AS component_uom,
             p_scrap_factor AS scrap_factor,
             p_yield_factor AS yield_factor,
             p_operation_seq AS operation_seq,
             p_supply_type AS supply_type,
             p_optional_yn AS optional_yn,
             p_remarks AS remarks
        FROM dual
    ) s
       ON (t.bom_id = s.bom_id AND t.line_no = s.line_no)
     WHEN MATCHED THEN UPDATE
       SET t.parent_item_id    = s.parent_item_id,
           t.component_item_id = s.component_item_id,
           t.find_no           = s.find_no,
           t.component_qty     = s.component_qty,
           t.component_uom     = s.component_uom,
           t.scrap_factor      = s.scrap_factor,
           t.yield_factor      = s.yield_factor,
           t.operation_seq     = s.operation_seq,
           t.supply_type       = s.supply_type,
           t.optional_yn       = s.optional_yn,
           t.remarks           = COALESCE(s.remarks, t.remarks)
     WHEN NOT MATCHED THEN INSERT (
           bom_id,
           parent_item_id,
           component_item_id,
           line_no,
           find_no,
           component_qty,
           component_uom,
           scrap_factor,
           yield_factor,
           operation_seq,
           supply_type,
           optional_yn,
           remarks
       ) VALUES (
           s.bom_id,
           s.parent_item_id,
           s.component_item_id,
           s.line_no,
           s.find_no,
           s.component_qty,
           s.component_uom,
           s.scrap_factor,
           s.yield_factor,
           s.operation_seq,
           s.supply_type,
           s.optional_yn,
           s.remarks
       );
  END;
BEGIN
  -- 品目マスタ: 100件
  upsert_item('ENG-ASM-I4-2000-NA', '2.0L直列4気筒ガソリンエンジンASSY', 'FG', 'MAKE');
  upsert_item('SB-ASM-I4-2000', 'ショートブロックASSY', 'ASM', 'MAKE');
  upsert_item('HEAD-ASM-I4-2000', 'シリンダヘッドASSY', 'ASM', 'MAKE');
  upsert_item('TIMING-ASM-I4-2000', 'タイミングドライブASSY', 'ASM', 'MAKE');
  upsert_item('LUBE-ASM-I4-2000', '潤滑システムASSY', 'ASM', 'MAKE');
  upsert_item('COOL-ASM-I4-2000', '冷却システムASSY', 'ASM', 'MAKE');
  upsert_item('INTAKE-ASM-I4-2000', '吸気システムASSY', 'ASM', 'MAKE');
  upsert_item('EXH-ASM-I4-2000', '排気システムASSY', 'ASM', 'MAKE');
  upsert_item('FUEL-ASM-I4-2000', '燃料システムASSY', 'ASM', 'MAKE');
  upsert_item('IGNCTRL-ASM-I4-2000', '点火制御システムASSY', 'ASM', 'MAKE');
  upsert_item('ACCDRV-ASM-I4-2000', '補機駆動ASSY', 'ASM', 'MAKE');
  upsert_item('INSTALL-KIT-I4-2000', '搭載取付キットASSY', 'ASM', 'MAKE');
  upsert_item('CYL-BLOCK-AL-I4', 'アルミシリンダブロック', 'PART', 'MAKE');
  upsert_item('CRANKSHAFT-FO-STD', '鍛造クランクシャフト', 'PART', 'MAKE');
  upsert_item('THRUST-BEARING-SET', 'スラストベアリングセット', 'PART', 'BUY');
  upsert_item('MAIN-BEARING-SET', 'メインベアリングセット', 'PART', 'BUY');
  upsert_item('CONROD-SET-I4', 'コネクティングロッドセット', 'PART', 'MAKE');
  upsert_item('PISTON-PIN-SET-86MM', 'ピストン・ピンセット 86mm', 'PART', 'BUY');
  upsert_item('PISTON-RING-SET-86MM', 'ピストンリングセット 86mm', 'PART', 'BUY');
  upsert_item('BED-PLATE-I4', 'ベッドプレート', 'PART', 'MAKE');
  upsert_item('BED-PLATE-BOLT-SET', 'ベッドプレートボルトセット', 'PART', 'BUY');
  upsert_item('CONROD-BOLT-SET', 'コンロッドボルトセット', 'PART', 'BUY');
  upsert_item('CRANK-SEAL-FRONT', 'フロントクランクオイルシール', 'PART', 'BUY');
  upsert_item('CRANK-SEAL-REAR', 'リアクランクオイルシール', 'PART', 'BUY');
  upsert_item('CORE-PLUG-SET', 'コアプラグセット', 'PART', 'BUY');
  upsert_item('OIL-SQUIRTER-SET', 'オイルジェットノズルセット', 'PART', 'BUY');
  upsert_item('BALANCE-SHAFT-MODULE', 'バランスシャフトモジュール', 'PART', 'BUY');
  upsert_item('CYL-HEAD-AL-DOHC', 'アルミシリンダヘッド DOHC', 'PART', 'MAKE');
  upsert_item('INTAKE-VALVE-SET-4', '吸気バルブセット 4気筒分', 'PART', 'BUY');
  upsert_item('EXHAUST-VALVE-SET-4', '排気バルブセット 4気筒分', 'PART', 'BUY');
  upsert_item('VALVE-SPRING-SET-16', 'バルブスプリングセット 16本', 'PART', 'BUY');
  upsert_item('VALVE-RETAINER-SET-16', 'バルブリテーナ・コッタセット 16本', 'PART', 'BUY');
  upsert_item('VALVE-STEM-SEAL-SET-16', 'バルブステムシールセット 16本', 'PART', 'BUY');
  upsert_item('CAMSHAFT-INTAKE-VVT', '吸気カムシャフト VVT', 'PART', 'MAKE');
  upsert_item('CAMSHAFT-EXH-VVT', '排気カムシャフト VVT', 'PART', 'MAKE');
  upsert_item('ROCKER-ARM-SET-16', 'ロッカーアームセット 16本', 'PART', 'BUY');
  upsert_item('LASH-ADJUSTER-SET-16', 'ラッシュアジャスタセット 16個', 'PART', 'BUY');
  upsert_item('HEAD-GASKET-MLS', 'メタルヘッドガスケット', 'PART', 'BUY');
  upsert_item('HEAD-BOLT-SET', 'シリンダヘッドボルトセット', 'PART', 'BUY');
  upsert_item('TIMING-CHAIN-PRIMARY', 'タイミングチェーン', 'PART', 'BUY');
  upsert_item('CRANK-SPROCKET', 'クランクスプロケット', 'PART', 'BUY');
  upsert_item('CAM-SPROCKET-INTAKE', '吸気カムスプロケット', 'PART', 'BUY');
  upsert_item('CAM-SPROCKET-EXH', '排気カムスプロケット', 'PART', 'BUY');
  upsert_item('CHAIN-GUIDE-FIXED', 'タイミングチェーン固定ガイド', 'PART', 'BUY');
  upsert_item('CHAIN-GUIDE-TENSION', 'タイミングチェーンテンションガイド', 'PART', 'BUY');
  upsert_item('CHAIN-TENSIONER-HYD', '油圧チェーンテンショナ', 'PART', 'BUY');
  upsert_item('OIL-PUMP-ASM', 'オイルポンプASSY', 'PART', 'BUY');
  upsert_item('OIL-PICKUP-STRNR', 'オイルストレーナ', 'PART', 'BUY');
  upsert_item('OIL-PAN-UPPER', '上部オイルパン', 'PART', 'MAKE');
  upsert_item('OIL-PAN-LOWER', '下部オイルパン', 'PART', 'MAKE');
  upsert_item('OIL-PAN-SEALANT', 'オイルパン液状ガスケット', 'PART', 'BUY');
  upsert_item('OIL-FILTER-MODULE', 'オイルフィルタモジュール', 'PART', 'BUY');
  upsert_item('OIL-PRESS-SENSOR', '油圧センサ', 'PART', 'BUY');
  upsert_item('WATER-PUMP', 'ウォータポンプ', 'PART', 'BUY');
  upsert_item('THERMOSTAT-ASM', 'サーモスタットASSY', 'PART', 'BUY');
  upsert_item('COOLANT-OUTLET-HOUSING', 'クーラントアウトレットハウジング', 'PART', 'BUY');
  upsert_item('COOLANT-INLET-PIPE', 'クーラントインレットパイプ', 'PART', 'BUY');
  upsert_item('WATER-JACKET-GASKET-SET', '冷却水路ガスケットセット', 'PART', 'BUY');
  upsert_item('ECT-SENSOR', '水温センサ', 'PART', 'BUY');
  upsert_item('BYPASS-HOSE-SET', 'バイパスホースセット', 'PART', 'BUY');
  upsert_item('INTAKE-MANIFOLD', '吸気マニホールド', 'PART', 'MAKE');
  upsert_item('THROTTLE-BODY-ETC', '電子スロットルボディ', 'PART', 'BUY');
  upsert_item('MAP-SENSOR', '吸気圧センサ', 'PART', 'BUY');
  upsert_item('IAT-SENSOR', '吸気温センサ', 'PART', 'BUY');
  upsert_item('PCV-VALVE', 'PCVバルブ', 'PART', 'BUY');
  upsert_item('INTAKE-MANI-GASKET-SET', '吸気マニホールドガスケットセット', 'PART', 'BUY');
  upsert_item('THROTTLE-GASKET', 'スロットルガスケット', 'PART', 'BUY');
  upsert_item('EXH-MANIFOLD', '排気マニホールド', 'PART', 'MAKE');
  upsert_item('EXH-HEAT-SHIELD', '排気マニホールドヒートシールド', 'PART', 'BUY');
  upsert_item('EXH-MANI-GASKET', '排気マニホールドガスケット', 'PART', 'BUY');
  upsert_item('O2-SENSOR-UP', '上流O2センサ', 'PART', 'BUY');
  upsert_item('FUEL-RAIL', 'フューエルレール', 'PART', 'BUY');
  upsert_item('FUEL-INJECTOR-SET-4', 'フューエルインジェクタセット 4本', 'PART', 'BUY');
  upsert_item('FUEL-PRESS-REG', '燃圧レギュレータ', 'PART', 'BUY');
  upsert_item('FUEL-FEED-PIPE', '燃料供給パイプ', 'PART', 'BUY');
  upsert_item('INJECTOR-SEAL-KIT', 'インジェクタシールキット', 'PART', 'BUY');
  upsert_item('ENGINE-WIRE-HARNESS', 'エンジンワイヤハーネス', 'PART', 'BUY');
  upsert_item('ENGINE-CONTROL-MODULE', 'エンジン制御ECU', 'PART', 'BUY');
  upsert_item('CRANK-POS-SENSOR', 'クランク角センサ', 'PART', 'BUY');
  upsert_item('CAM-POS-SENSOR', 'カム角センサ', 'PART', 'BUY');
  upsert_item('KNOCK-SENSOR', 'ノックセンサ', 'PART', 'BUY');
  upsert_item('IGNITION-COIL-SET-4', 'イグニッションコイルセット 4本', 'PART', 'BUY');
  upsert_item('SPARK-PLUG-SET-4', 'スパークプラグセット 4本', 'PART', 'BUY');
  upsert_item('GROUND-CABLE-SET', 'エンジンアースケーブルセット', 'PART', 'BUY');
  upsert_item('HARMONIC-BALANCER', 'ハーモニックバランサ', 'PART', 'BUY');
  upsert_item('SERPENTINE-BELT-6PK', 'サーペンタインベルト 6PK', 'PART', 'BUY');
  upsert_item('BELT-TENSIONER-ASM', 'ベルトテンショナASSY', 'PART', 'BUY');
  upsert_item('IDLER-PULLEY', 'アイドラプーリ', 'PART', 'BUY');
  upsert_item('ALTERNATOR-150A', 'オルタネータ 150A', 'PART', 'BUY');
  upsert_item('STARTER-MOTOR', 'スタータモータ', 'PART', 'BUY');
  upsert_item('ACCESSORY-BRACKET', '補機ブラケット', 'PART', 'MAKE');
  upsert_item('ALTERNATOR-BRACKET', 'オルタネータブラケット', 'PART', 'MAKE');
  upsert_item('FLYWHEEL', 'フライホイール', 'PART', 'MAKE');
  upsert_item('FLYWHEEL-BOLT-SET', 'フライホイールボルトセット', 'PART', 'BUY');
  upsert_item('PILOT-BEARING', 'パイロットベアリング', 'PART', 'BUY');
  upsert_item('ENG-MOUNT-BRKT-RH', '右エンジンマウントブラケット', 'PART', 'MAKE');
  upsert_item('ENG-MOUNT-BRKT-LH', '左エンジンマウントブラケット', 'PART', 'MAKE');
  upsert_item('LIFTING-HOOK-FR', '前側吊りフック', 'PART', 'MAKE');
  upsert_item('LIFTING-HOOK-RR', '後側吊りフック', 'PART', 'MAKE');
  upsert_item('ENGINE-NAMEPLATE', 'エンジン銘板', 'PART', 'BUY');

  -- BOMヘッダ: 12件
  upsert_bom_header('BOM-ENG-ASM-I4-2000-NA', 'ENG-ASM-I4-2000-NA', '2.0L直列4気筒ガソリンエンジンBOM', 'A', 'PRIMARY', DATE '2026-01-01');
  upsert_bom_header('BOM-SB-ASM-I4-2000', 'SB-ASM-I4-2000', 'ショートブロックBOM', 'A', 'PRIMARY', DATE '2026-01-01');
  upsert_bom_header('BOM-HEAD-ASM-I4-2000', 'HEAD-ASM-I4-2000', 'シリンダヘッドBOM', 'A', 'PRIMARY', DATE '2026-01-01');
  upsert_bom_header('BOM-TIMING-ASM-I4-2000', 'TIMING-ASM-I4-2000', 'タイミングドライブBOM', 'A', 'PRIMARY', DATE '2026-01-01');
  upsert_bom_header('BOM-LUBE-ASM-I4-2000', 'LUBE-ASM-I4-2000', '潤滑システムBOM', 'A', 'PRIMARY', DATE '2026-01-01');
  upsert_bom_header('BOM-COOL-ASM-I4-2000', 'COOL-ASM-I4-2000', '冷却システムBOM', 'A', 'PRIMARY', DATE '2026-01-01');
  upsert_bom_header('BOM-INTAKE-ASM-I4-2000', 'INTAKE-ASM-I4-2000', '吸気システムBOM', 'A', 'PRIMARY', DATE '2026-01-01');
  upsert_bom_header('BOM-EXH-ASM-I4-2000', 'EXH-ASM-I4-2000', '排気システムBOM', 'A', 'PRIMARY', DATE '2026-01-01');
  upsert_bom_header('BOM-FUEL-ASM-I4-2000', 'FUEL-ASM-I4-2000', '燃料システムBOM', 'A', 'PRIMARY', DATE '2026-01-01');
  upsert_bom_header('BOM-IGNCTRL-ASM-I4-2000', 'IGNCTRL-ASM-I4-2000', '点火制御システムBOM', 'A', 'PRIMARY', DATE '2026-01-01');
  upsert_bom_header('BOM-ACCDRV-ASM-I4-2000', 'ACCDRV-ASM-I4-2000', '補機駆動BOM', 'A', 'PRIMARY', DATE '2026-01-01');
  upsert_bom_header('BOM-INSTALL-KIT-I4-2000', 'INSTALL-KIT-I4-2000', '搭載取付キットBOM', 'A', 'PRIMARY', DATE '2026-01-01');

  -- BOM明細: 99件
  upsert_bom_line('BOM-ENG-ASM-I4-2000-NA', 'SB-ASM-I4-2000', 10, 1, 'EA', 1000);
  upsert_bom_line('BOM-ENG-ASM-I4-2000-NA', 'HEAD-ASM-I4-2000', 20, 1, 'EA', 1000);
  upsert_bom_line('BOM-ENG-ASM-I4-2000-NA', 'TIMING-ASM-I4-2000', 30, 1, 'EA', 1000);
  upsert_bom_line('BOM-ENG-ASM-I4-2000-NA', 'LUBE-ASM-I4-2000', 40, 1, 'EA', 1000);
  upsert_bom_line('BOM-ENG-ASM-I4-2000-NA', 'COOL-ASM-I4-2000', 50, 1, 'EA', 1000);
  upsert_bom_line('BOM-ENG-ASM-I4-2000-NA', 'INTAKE-ASM-I4-2000', 60, 1, 'EA', 1000);
  upsert_bom_line('BOM-ENG-ASM-I4-2000-NA', 'EXH-ASM-I4-2000', 70, 1, 'EA', 1000);
  upsert_bom_line('BOM-ENG-ASM-I4-2000-NA', 'FUEL-ASM-I4-2000', 80, 1, 'EA', 1000);
  upsert_bom_line('BOM-ENG-ASM-I4-2000-NA', 'IGNCTRL-ASM-I4-2000', 90, 1, 'EA', 1000);
  upsert_bom_line('BOM-ENG-ASM-I4-2000-NA', 'ACCDRV-ASM-I4-2000', 100, 1, 'EA', 1000);
  upsert_bom_line('BOM-ENG-ASM-I4-2000-NA', 'INSTALL-KIT-I4-2000', 110, 1, 'EA', 1000);
  upsert_bom_line('BOM-SB-ASM-I4-2000', 'CYL-BLOCK-AL-I4', 10, 1, 'EA', 1100);
  upsert_bom_line('BOM-SB-ASM-I4-2000', 'CRANKSHAFT-FO-STD', 20, 1, 'EA', 1100);
  upsert_bom_line('BOM-SB-ASM-I4-2000', 'THRUST-BEARING-SET', 30, 1, 'EA', 1100);
  upsert_bom_line('BOM-SB-ASM-I4-2000', 'MAIN-BEARING-SET', 40, 1, 'EA', 1100);
  upsert_bom_line('BOM-SB-ASM-I4-2000', 'CONROD-SET-I4', 50, 1, 'EA', 1100);
  upsert_bom_line('BOM-SB-ASM-I4-2000', 'PISTON-PIN-SET-86MM', 60, 1, 'EA', 1100);
  upsert_bom_line('BOM-SB-ASM-I4-2000', 'PISTON-RING-SET-86MM', 70, 1, 'EA', 1100);
  upsert_bom_line('BOM-SB-ASM-I4-2000', 'BED-PLATE-I4', 80, 1, 'EA', 1100);
  upsert_bom_line('BOM-SB-ASM-I4-2000', 'BED-PLATE-BOLT-SET', 90, 1, 'EA', 1100);
  upsert_bom_line('BOM-SB-ASM-I4-2000', 'CONROD-BOLT-SET', 100, 1, 'EA', 1100);
  upsert_bom_line('BOM-SB-ASM-I4-2000', 'CRANK-SEAL-FRONT', 110, 1, 'EA', 1100);
  upsert_bom_line('BOM-SB-ASM-I4-2000', 'CRANK-SEAL-REAR', 120, 1, 'EA', 1100);
  upsert_bom_line('BOM-SB-ASM-I4-2000', 'CORE-PLUG-SET', 130, 1, 'EA', 1100);
  upsert_bom_line('BOM-SB-ASM-I4-2000', 'OIL-SQUIRTER-SET', 140, 1, 'EA', 1100);
  upsert_bom_line('BOM-SB-ASM-I4-2000', 'BALANCE-SHAFT-MODULE', 150, 1, 'EA', 1100);
  upsert_bom_line('BOM-HEAD-ASM-I4-2000', 'CYL-HEAD-AL-DOHC', 10, 1, 'EA', 1200);
  upsert_bom_line('BOM-HEAD-ASM-I4-2000', 'INTAKE-VALVE-SET-4', 20, 1, 'EA', 1200);
  upsert_bom_line('BOM-HEAD-ASM-I4-2000', 'EXHAUST-VALVE-SET-4', 30, 1, 'EA', 1200);
  upsert_bom_line('BOM-HEAD-ASM-I4-2000', 'VALVE-SPRING-SET-16', 40, 1, 'EA', 1200);
  upsert_bom_line('BOM-HEAD-ASM-I4-2000', 'VALVE-RETAINER-SET-16', 50, 1, 'EA', 1200);
  upsert_bom_line('BOM-HEAD-ASM-I4-2000', 'VALVE-STEM-SEAL-SET-16', 60, 1, 'EA', 1200);
  upsert_bom_line('BOM-HEAD-ASM-I4-2000', 'CAMSHAFT-INTAKE-VVT', 70, 1, 'EA', 1200);
  upsert_bom_line('BOM-HEAD-ASM-I4-2000', 'CAMSHAFT-EXH-VVT', 80, 1, 'EA', 1200);
  upsert_bom_line('BOM-HEAD-ASM-I4-2000', 'ROCKER-ARM-SET-16', 90, 1, 'EA', 1200);
  upsert_bom_line('BOM-HEAD-ASM-I4-2000', 'LASH-ADJUSTER-SET-16', 100, 1, 'EA', 1200);
  upsert_bom_line('BOM-HEAD-ASM-I4-2000', 'HEAD-GASKET-MLS', 110, 1, 'EA', 1200);
  upsert_bom_line('BOM-HEAD-ASM-I4-2000', 'HEAD-BOLT-SET', 120, 1, 'EA', 1200);
  upsert_bom_line('BOM-TIMING-ASM-I4-2000', 'TIMING-CHAIN-PRIMARY', 10, 1, 'EA', 1300);
  upsert_bom_line('BOM-TIMING-ASM-I4-2000', 'CRANK-SPROCKET', 20, 1, 'EA', 1300);
  upsert_bom_line('BOM-TIMING-ASM-I4-2000', 'CAM-SPROCKET-INTAKE', 30, 1, 'EA', 1300);
  upsert_bom_line('BOM-TIMING-ASM-I4-2000', 'CAM-SPROCKET-EXH', 40, 1, 'EA', 1300);
  upsert_bom_line('BOM-TIMING-ASM-I4-2000', 'CHAIN-GUIDE-FIXED', 50, 1, 'EA', 1300);
  upsert_bom_line('BOM-TIMING-ASM-I4-2000', 'CHAIN-GUIDE-TENSION', 60, 1, 'EA', 1300);
  upsert_bom_line('BOM-TIMING-ASM-I4-2000', 'CHAIN-TENSIONER-HYD', 70, 1, 'EA', 1300);
  upsert_bom_line('BOM-LUBE-ASM-I4-2000', 'OIL-PUMP-ASM', 10, 1, 'EA', 1400);
  upsert_bom_line('BOM-LUBE-ASM-I4-2000', 'OIL-PICKUP-STRNR', 20, 1, 'EA', 1400);
  upsert_bom_line('BOM-LUBE-ASM-I4-2000', 'OIL-PAN-UPPER', 30, 1, 'EA', 1400);
  upsert_bom_line('BOM-LUBE-ASM-I4-2000', 'OIL-PAN-LOWER', 40, 1, 'EA', 1400);
  upsert_bom_line('BOM-LUBE-ASM-I4-2000', 'OIL-PAN-SEALANT', 50, 1, 'EA', 1400);
  upsert_bom_line('BOM-LUBE-ASM-I4-2000', 'OIL-FILTER-MODULE', 60, 1, 'EA', 1400);
  upsert_bom_line('BOM-LUBE-ASM-I4-2000', 'OIL-PRESS-SENSOR', 70, 1, 'EA', 1400);
  upsert_bom_line('BOM-COOL-ASM-I4-2000', 'WATER-PUMP', 10, 1, 'EA', 1500);
  upsert_bom_line('BOM-COOL-ASM-I4-2000', 'THERMOSTAT-ASM', 20, 1, 'EA', 1500);
  upsert_bom_line('BOM-COOL-ASM-I4-2000', 'COOLANT-OUTLET-HOUSING', 30, 1, 'EA', 1500);
  upsert_bom_line('BOM-COOL-ASM-I4-2000', 'COOLANT-INLET-PIPE', 40, 1, 'EA', 1500);
  upsert_bom_line('BOM-COOL-ASM-I4-2000', 'WATER-JACKET-GASKET-SET', 50, 1, 'EA', 1500);
  upsert_bom_line('BOM-COOL-ASM-I4-2000', 'ECT-SENSOR', 60, 1, 'EA', 1500);
  upsert_bom_line('BOM-COOL-ASM-I4-2000', 'BYPASS-HOSE-SET', 70, 1, 'EA', 1500);
  upsert_bom_line('BOM-INTAKE-ASM-I4-2000', 'INTAKE-MANIFOLD', 10, 1, 'EA', 1600);
  upsert_bom_line('BOM-INTAKE-ASM-I4-2000', 'THROTTLE-BODY-ETC', 20, 1, 'EA', 1600);
  upsert_bom_line('BOM-INTAKE-ASM-I4-2000', 'MAP-SENSOR', 30, 1, 'EA', 1600);
  upsert_bom_line('BOM-INTAKE-ASM-I4-2000', 'IAT-SENSOR', 40, 1, 'EA', 1600);
  upsert_bom_line('BOM-INTAKE-ASM-I4-2000', 'PCV-VALVE', 50, 1, 'EA', 1600);
  upsert_bom_line('BOM-INTAKE-ASM-I4-2000', 'INTAKE-MANI-GASKET-SET', 60, 1, 'EA', 1600);
  upsert_bom_line('BOM-INTAKE-ASM-I4-2000', 'THROTTLE-GASKET', 70, 1, 'EA', 1600);
  upsert_bom_line('BOM-EXH-ASM-I4-2000', 'EXH-MANIFOLD', 10, 1, 'EA', 1700);
  upsert_bom_line('BOM-EXH-ASM-I4-2000', 'EXH-HEAT-SHIELD', 20, 1, 'EA', 1700);
  upsert_bom_line('BOM-EXH-ASM-I4-2000', 'EXH-MANI-GASKET', 30, 1, 'EA', 1700);
  upsert_bom_line('BOM-EXH-ASM-I4-2000', 'O2-SENSOR-UP', 40, 1, 'EA', 1700);
  upsert_bom_line('BOM-FUEL-ASM-I4-2000', 'FUEL-RAIL', 10, 1, 'EA', 1800);
  upsert_bom_line('BOM-FUEL-ASM-I4-2000', 'FUEL-INJECTOR-SET-4', 20, 1, 'EA', 1800);
  upsert_bom_line('BOM-FUEL-ASM-I4-2000', 'FUEL-PRESS-REG', 30, 1, 'EA', 1800);
  upsert_bom_line('BOM-FUEL-ASM-I4-2000', 'FUEL-FEED-PIPE', 40, 1, 'EA', 1800);
  upsert_bom_line('BOM-FUEL-ASM-I4-2000', 'INJECTOR-SEAL-KIT', 50, 1, 'EA', 1800);
  upsert_bom_line('BOM-IGNCTRL-ASM-I4-2000', 'ENGINE-WIRE-HARNESS', 10, 1, 'EA', 1900);
  upsert_bom_line('BOM-IGNCTRL-ASM-I4-2000', 'ENGINE-CONTROL-MODULE', 20, 1, 'EA', 1900);
  upsert_bom_line('BOM-IGNCTRL-ASM-I4-2000', 'CRANK-POS-SENSOR', 30, 1, 'EA', 1900);
  upsert_bom_line('BOM-IGNCTRL-ASM-I4-2000', 'CAM-POS-SENSOR', 40, 1, 'EA', 1900);
  upsert_bom_line('BOM-IGNCTRL-ASM-I4-2000', 'KNOCK-SENSOR', 50, 1, 'EA', 1900);
  upsert_bom_line('BOM-IGNCTRL-ASM-I4-2000', 'IGNITION-COIL-SET-4', 60, 1, 'EA', 1900);
  upsert_bom_line('BOM-IGNCTRL-ASM-I4-2000', 'SPARK-PLUG-SET-4', 70, 1, 'EA', 1900);
  upsert_bom_line('BOM-IGNCTRL-ASM-I4-2000', 'GROUND-CABLE-SET', 80, 1, 'EA', 1900);
  upsert_bom_line('BOM-ACCDRV-ASM-I4-2000', 'HARMONIC-BALANCER', 10, 1, 'EA', 2000);
  upsert_bom_line('BOM-ACCDRV-ASM-I4-2000', 'SERPENTINE-BELT-6PK', 20, 1, 'EA', 2000);
  upsert_bom_line('BOM-ACCDRV-ASM-I4-2000', 'BELT-TENSIONER-ASM', 30, 1, 'EA', 2000);
  upsert_bom_line('BOM-ACCDRV-ASM-I4-2000', 'IDLER-PULLEY', 40, 1, 'EA', 2000);
  upsert_bom_line('BOM-ACCDRV-ASM-I4-2000', 'ALTERNATOR-150A', 50, 1, 'EA', 2000);
  upsert_bom_line('BOM-ACCDRV-ASM-I4-2000', 'STARTER-MOTOR', 60, 1, 'EA', 2000);
  upsert_bom_line('BOM-ACCDRV-ASM-I4-2000', 'ACCESSORY-BRACKET', 70, 1, 'EA', 2000);
  upsert_bom_line('BOM-ACCDRV-ASM-I4-2000', 'ALTERNATOR-BRACKET', 80, 1, 'EA', 2000);
  upsert_bom_line('BOM-INSTALL-KIT-I4-2000', 'FLYWHEEL', 10, 1, 'EA', 2100);
  upsert_bom_line('BOM-INSTALL-KIT-I4-2000', 'FLYWHEEL-BOLT-SET', 20, 1, 'EA', 2100);
  upsert_bom_line('BOM-INSTALL-KIT-I4-2000', 'PILOT-BEARING', 30, 1, 'EA', 2100);
  upsert_bom_line('BOM-INSTALL-KIT-I4-2000', 'ENG-MOUNT-BRKT-RH', 40, 1, 'EA', 2100);
  upsert_bom_line('BOM-INSTALL-KIT-I4-2000', 'ENG-MOUNT-BRKT-LH', 50, 1, 'EA', 2100);
  upsert_bom_line('BOM-INSTALL-KIT-I4-2000', 'LIFTING-HOOK-FR', 60, 1, 'EA', 2100);
  upsert_bom_line('BOM-INSTALL-KIT-I4-2000', 'LIFTING-HOOK-RR', 70, 1, 'EA', 2100);
  upsert_bom_line('BOM-INSTALL-KIT-I4-2000', 'ENGINE-NAMEPLATE', 80, 1, 'EA', 2100);

  COMMIT;
END;
/



-- ============================================================================
-- 3) PGQL / Oracle Graph
--    A. 品目直接展開グラフ
--    B. BOM改訂認識グラフ
-- ============================================================================

CREATE PROPERTY GRAPH mfg_engine_bom_item_pg
  VERTEX TABLES (
    mfg_item AS items
      KEY (item_id)
      LABEL item
      PROPERTIES ARE ALL COLUMNS
  )
  EDGE TABLES (
    asm_bom_component AS uses_edges
      KEY (bom_line_id)
      SOURCE KEY (parent_item_id) REFERENCES items (item_id)
      DESTINATION KEY (component_item_id) REFERENCES items (item_id)
      LABEL uses
      PROPERTIES ARE ALL COLUMNS
  )

CREATE PROPERTY GRAPH mfg_engine_bom_rev_pg
  VERTEX TABLES (
    mfg_item AS items
      KEY (item_id)
      LABEL item
      PROPERTIES ARE ALL COLUMNS,
    asm_bom_header AS bom_versions
      KEY (bom_id)
      LABEL bom_version
      PROPERTIES ARE ALL COLUMNS
  )
  EDGE TABLES (
    asm_bom_header AS has_bom_edges
      KEY (bom_id)
      SOURCE KEY (assembly_item_id) REFERENCES items (item_id)
      DESTINATION KEY (bom_id) REFERENCES bom_versions (bom_id)
      LABEL has_bom
      PROPERTIES ARE ALL COLUMNS,
    asm_bom_component AS bom_line_edges
      KEY (bom_line_id)
      SOURCE KEY (bom_id) REFERENCES bom_versions (bom_id)
      DESTINATION KEY (component_item_id) REFERENCES items (item_id)
      LABEL bom_line
      PROPERTIES ARE ALL COLUMNS
  )

この記事でわかること

  • BOM向けのRDB設計
  • 主キー・外部キー・CHECK制約
  • CREATE PROPERTY GRAPH
  • Oracleの GRAPH_TABLE
  • APEX Graph Visualization Plugin

このSQLの全体像

このSQLは、大きく分けると次の5層です。

レイヤー 役割
mfg_item 品目そのものを管理するマスタ
asm_bom_header どの親品目に対する、どの改訂のBOMかを管理
asm_bom_component 親子関係・数量・工程・歩留などの明細
mfg_engine_bom_item_pg 品目 → 品目 の直接展開Graph
mfg_engine_bom_rev_pg 品目 → BOM改訂 → 品目 の改訂認識Graph

サンプルデータも含まれており、最上位の
ENG-ASM-I4-2000-NA(2.0L直列4気筒ガソリンエンジンASSY)を起点に、ショートブロック、シリンダヘッド、タイミング系、潤滑系などへ展開できる構成になっています。

SQLファイル上では、投入件数のコメントとして次の値が示されています。

  • 品目マスタ: 100件
  • BOMヘッダ: 12件
  • BOM明細: 99件

全体の流れ


1. テーブル作成を読む

このSQLの前半は、BOMを安定して管理するためのRDB設計です。
主キー・一意制約・外部キー・CHECK制約・索引の組み合わせで、BOM を表現しています。

1-1. mfg_item:品目マスタ

まずは登場人物一覧です。
完成品、中間アセンブリ、購入部品まで、全部ここに載せます。

主な列

列名 意味
item_id システム内部の主キー
item_code 業務で使う品目コード
item_name 品目名称
item_type FG / ASM / PART / RM / CONSUMABLE
procurement_type MAKE / BUY / BOTH
uom_code 単位
revision_code 品目自体の改訂
lifecycle_status ACTIVE / INACTIVE / OBSOLETE

ポイント

  • item_id はサロゲートキー
    • 人が見るコードは item_code
    • 参照整合性は item_id で守る
  • item_code は業務キー
    • 外部システム連携や検索にはこちらを使う
  • item_typeprocurement_type が重要
    • どのノードが完成品か
    • どれが購入品か
    • 自社製造か購入か
      を区別できる

代表的な制約

  • pk_mfg_item:主キー
  • uk_mfg_item_code:品目コードの一意制約
  • ck_mfg_item_type:品目区分の値域制約
  • ck_mfg_item_proc:調達区分の値域制約
  • ck_mfg_item_lifecycle:ライフサイクル状態の値域制約

1-2. asm_bom_header:BOMヘッダ

これは 「どの親品目の、どの版のBOMか」 を表すテーブルです。

主な列

列名 意味
bom_id BOMヘッダの主キー
bom_code BOM識別コード
assembly_item_id 親品目
bom_name BOM名称
bom_revision BOM改訂
alternate_bom_code PRIMARY / ALT1 など
effectivity_start_date 有効開始日
effectivity_end_date 有効終了日
status_code DRAFT / RELEASED / OBSOLETE

なぜヘッダを分けるのか?

BOM明細だけで親子を持つこともできますが、現実の製造業では次の情報が必要になります。

  • 改訂管理
  • 代替BOM管理
  • 有効期間管理
  • ステータス管理

つまり、「何が入るか」だけでなく、「いつの、どの版か」も管理したい わけです。
そのため、ヘッダと明細を分けています。

ここが効いている制約

  • uk_asm_bom_header_code
  • uk_asm_bom_header_item_rev_alt
  • fk_asm_bom_header_item
  • ck_asm_bom_header_status
  • ck_asm_bom_header_effective

特に uk_asm_bom_header_item_rev_alt によって、
同じ親品目に対して、同じ改訂・同じ代替BOMを重複登録させない ようになっています。


1-3. asm_bom_component:BOM明細

ここが、実際の親子関係の本体です。

主な列

列名 意味
bom_line_id 明細行の主キー
bom_id どのBOMヘッダに属するか
parent_item_id 親品目
component_item_id 子品目
line_no 行番号
find_no 図示番号
component_qty 所要数量
component_uom 明細単位
scrap_factor スクラップ率
yield_factor 歩留係数
operation_seq 工程順序
supply_type PUSH / PULL / KIT
optional_yn 任意部品かどうか

ポイント

  • component_qty
    • 親1つあたり子が何個必要か
  • scrap_factor
    • 作業ロスをどれだけ見込むか
  • yield_factor
    • 良品として残る割合
  • optional_yn='Y'
    • 選択部品・オプション部品
  • parent_item_id
    • ヘッダの assembly_item_id と重なるように見えるが、
    • 再帰SQLやGraph変換をシンプルにするための冗長保持

ここが効いている制約

  • fk_asm_bom_component_bom
  • fk_asm_bom_component_parent
  • fk_asm_bom_component_comp
  • ck_asm_bom_component_qty
  • ck_asm_bom_component_scrap
  • ck_asm_bom_component_yield
  • ck_asm_bom_component_supply
  • ck_asm_bom_component_optional
  • ck_asm_bom_component_no_self

削除時のポイント

fk_asm_bom_component_bom には ON DELETE CASCADE が付いています。
つまり、BOMヘッダを消したら、その配下の明細もまとめて消えます。


1-4. ER図で見る

ADBのデータモデラーで見たER図

engine_bom_oracle.sql のDDLを ADB のデータモデラーで可視化すると、テーブル間の参照関係をさらに直感的に把握できます。
特に、mfg_item を基点に asm_bom_headerasm_bom_component がどうつながるか、主キー・外部キー・一意制約がどこにあるかを、DDLと見比べながら確認しやすくなります。

ADB上のデータモデラーで作成したER図

まとめ

  • mfg_item は「品目辞書」
  • asm_bom_header は「BOMの表紙」
  • asm_bom_component は「BOMの中身」

この3つで、製造BOMの基本構造を表現しています。


2. SQL制約(業務ルール)

SQLの制約は、制約 = 業務ルールの宣言 です。

制約の種類 守っていること
主キー pk_mfg_item 行を一意に識別する
一意制約 uk_mfg_item_code 同じ品目コードを重複登録しない
外部キー fk_asm_bom_header_item 存在しない品目を親にできない
CHECK ck_asm_bom_component_no_self 自己循環を防ぐ
CHECK ck_asm_bom_header_effective 有効期間の矛盾を防ぐ
CHECK ck_asm_bom_component_qty 0やマイナス数量を防ぐ

つまりこのSQLは、
業務として成立するBOMデータを担保する設計 になっています。


3. COMMENTで業務意味をSQLに埋め込む

このSQLでは、COMMENT ON TABLE / COMMENT ON COLUMN を丁寧に書いています。
これらのコメントは将来的に、テーブルやカラムを生成AI(LLM)などから参照させる時にコンテキスト理解にも役立ちます。

COMMENT ON TABLE mfg_item IS q'~{
  "表示名":"製造品目マスタ",
  "説明":"BOMで参照される完成品、中間アセンブリ、購入部品、原材料、消耗品を一意に管理する中核マスタ。"
}~';

これが何に効くのか?

  • DB設計書として読める
  • 画面表示名に流用しやすい
  • AIや検索基盤に「この列は何者か」を伝えやすい
  • 後から見た人が、列の業務意味を誤解しにくい

4. MERGEベースのサンプルデータ投入

このSQLは、データ投入も単純な INSERT ではなく、PL/SQLの補助関数と MERGE を使った upsert になっています。

  • get_item_id
  • get_bom_id
  • upsert_item
  • upsert_bom_header
  • upsert_bom_line

という形で、既存データは更新、未登録データは挿入、という振る舞いになります。

なぜ MERGE を使うのか?

  • すでに存在するデータは更新
  • まだ無いデータは挿入
  • スクリプトを再実行しやすい
  • デモや検証環境で扱いやすい

ルートの構成イメージ

この時点ですでに、
「最上位ASSYの下に、さらにサブASSYがぶら下がる」
という多階層BOMの形ができています。


5. CREATE PROPERTY GRAPH でRDBをGraphに変える

このSQLは、最後に 2種類の Property Graph を作ります。

  1. 品目から品目へ直接たどるGraph
  2. 品目 → BOM改訂 → 品目 という、改訂ノードを残すGraph

どちらも PROPERTIES ARE ALL COLUMNS を使っているため、元テーブル列をそのままGraph属性として扱えます。


5-1. まず、ノードとエッジを言葉で整理する

Graphでは、まず次の2つを意識します。

  • ノード(Vertex)
    • モノや概念そのもの
    • 例: 品目、BOM改訂
  • エッジ(Edge)
    • ノード同士の関係
    • 例: 使っている、BOMを持つ、BOM明細でつながる

5-2. mfg_engine_bom_item_pg:直接展開Graph

これは一番シンプルなGraphです。

  • ノード: mfg_item
  • エッジ: asm_bom_component
  • 関係ラベル: uses

イメージ

何が嬉しい?

このGraphでは、
「親品目から子品目へそのまま辿る」
というBOM展開が非常に分かりやすくなります。

短縮版の定義イメージ

CREATE PROPERTY GRAPH mfg_engine_bom_item_pg
  VERTEX TABLES (
    mfg_item AS items
      KEY (item_id)
      LABEL item
      PROPERTIES ARE ALL COLUMNS
  )
  EDGE TABLES (
    asm_bom_component AS uses_edges
      KEY (bom_line_id)
      SOURCE KEY (parent_item_id) REFERENCES items (item_id)
      DESTINATION KEY (component_item_id) REFERENCES items (item_id)
      LABEL uses
      PROPERTIES ARE ALL COLUMNS
  );

5-3. mfg_engine_bom_rev_pg:改訂認識Graph

こちらは、BOMヘッダをノードとして残す版です。

  • ノード1: mfg_itemitem
  • ノード2: asm_bom_headerbom_version
  • エッジ1: has_bom
  • エッジ2: bom_line

イメージ

何が嬉しい?

こちらは、単なる親子関係だけでなく

  • どのBOM改訂か
  • どの代替BOMか
  • いつ有効か
  • RELEASEDDRAFT

まで含めて追跡できます。


5-4. どちらのGraphを使うべきか?

Graph 向いている問い
mfg_engine_bom_item_pg このASSYの部品をサッと展開したい
mfg_engine_bom_rev_pg 改訂や有効期間込みで正しく見たい

6. SQLでGraph Queryを書く

Oracleの SQL Property Graph Query は、GRAPH_TABLE を起点に
入力グラフ名 → MATCH → 外側の WHERECOLUMNS で書きます。

公式ドキュメントでは、基本要素として次が説明されています。

  • FROM GRAPH_TABLE(...)
  • MATCH
  • 外側の WHERE
  • COLUMNS
  • 必要に応じて ONE ROW PER

また、VERTEX_ID / EDGE_ID による識別子取得もサポートされています。


6-1. 基本の形

SELECT *
FROM GRAPH_TABLE (
  <graph_name>
  MATCH (<vertex>) -[<edge>]-> (<vertex>)
  WHERE <condition>
  COLUMNS (...)
);

最低限おさえる4要素

  1. GRAPH_TABLE(<graph_name>)
  2. MATCH
  3. WHERE
  4. COLUMNS

6-2. まずは「直下の子部品」を取る

最初の一歩として、
エンジンASSYの直下にぶら下がる子部品 を取ってみます。

SELECT *
FROM GRAPH_TABLE (
  mfg_engine_bom_item_pg
  MATCH (root IS item) -[e IS uses]-> (comp IS item)
  WHERE root.item_code = 'ENG-ASM-I4-2000-NA'
  COLUMNS (
    root.item_code    AS root_code,
    root.item_name    AS root_name,
    comp.item_code    AS component_code,
    comp.item_name    AS component_name,
    comp.item_type    AS component_type,
    e.line_no         AS line_no,
    e.component_qty   AS component_qty,
    e.supply_type     AS supply_type
  )
)
ORDER BY line_no;

読み方

  • root IS item
    • 親側のノード
  • e IS uses
    • 親子関係を表すエッジ
  • comp IS item
    • 子側のノード
  • root.item_code = 'ENG-ASM-I4-2000-NA'
    • 起点をエンジンASSYに絞る

この書き方に慣れると、
「どのノードから、どの関係で、どこへ行くか」
をそのままSQLにできます。


6-3. 多階層BOMを 1〜3 レベル辿る

Graphの気持ちよさが出るのはここです。
多階層BOMを、可変長パス で辿れます。

公式ドキュメントで説明されている数量詞は次の3種類です。

  • {n}
  • {n,m}
  • {,m}

たとえば 1〜3 ホップを辿るなら、次のように書けます。

SELECT *
FROM GRAPH_TABLE (
  mfg_engine_bom_item_pg
  MATCH (root IS item) -[IS uses]->{1,3} (comp IS item)
  WHERE root.item_code = 'ENG-ASM-I4-2000-NA'
  COLUMNS (
    root.item_code   AS root_code,
    comp.item_code   AS component_code,
    comp.item_name   AS component_name,
    comp.item_type   AS component_type
  )
)
ORDER BY component_code;

->{1,3} の意味

  • 1ホップ
  • 2ホップ
  • 3ホップ

のいずれか、という意味です。

RDBの自己結合だけでこれをやると、
階層が増えるたびにSQLが長くなりがちです。
Graph Queryでは、「何段辿るか」 を量指定子で書けるのが大きな差です。


6-4. 改訂付きBOMを取りたいなら rev_pg

改訂や代替BOM、有効期間も見たいときは、
mfg_engine_bom_rev_pg を使います。

SELECT *
FROM GRAPH_TABLE (
  mfg_engine_bom_rev_pg
  MATCH (a IS item) -[hb IS has_bom]-> (b IS bom_version) -[bl IS bom_line]-> (c IS item)
  WHERE a.item_code = 'ENG-ASM-I4-2000-NA'
    AND b.alternate_bom_code = 'PRIMARY'
    AND b.status_code = 'RELEASED'
  COLUMNS (
    a.item_code              AS assembly_code,
    b.bom_code               AS bom_code,
    b.bom_revision           AS bom_revision,
    b.alternate_bom_code     AS alt_bom_code,
    b.effectivity_start_date AS effectivity_start_date,
    c.item_code              AS component_code,
    c.item_name              AS component_name,
    bl.line_no               AS line_no,
    bl.component_qty         AS component_qty
  )
)
ORDER BY line_no;

このQueryで分かること

  • どの親品目の
  • どのBOM改訂を通って
  • どの子品目へつながるか

つまり、
「親 → 改訂BOM → 子」
という業務の意味を保ったまま引けます。


6-5. 可視化向けに VERTEX_ID / EDGE_ID を返す

グラフ可視化に載せるときは、要素の識別子も欲しくなります。

SELECT *
FROM GRAPH_TABLE (
  mfg_engine_bom_item_pg
  MATCH (p IS item) -[e IS uses]-> (c IS item)
  WHERE p.item_code = 'SB-ASM-I4-2000'
  COLUMNS (
    VERTEX_ID(p) AS parent_vertex_id,
    EDGE_ID(e)   AS edge_id,
    VERTEX_ID(c) AS component_vertex_id,
    p.item_code  AS parent_code,
    c.item_code  AS component_code
  )
);

VERTEX_ID / EDGE_ID は、
「どのノード/どのエッジか」 を一意に扱いたいときに便利です。


6-6. Graph Queryを書くときのコツ

コツ1. 最初は 1ホップから書く

いきなり多階層にしないで、

  1. 起点ノードを置く
  2. 1本のエッジでつなぐ
  3. 返す列を確認する

この順で作ると壊れにくいです。

コツ2. まず「どのGraphを使うか」を決める

  • 単純な親子展開 → mfg_engine_bom_item_pg
  • 改訂文脈込み → mfg_engine_bom_rev_pg

コツ3. COLUMNS は最小限から増やす

いきなり全部返すと見づらいので、

  • 起点コード
  • 子コード
  • 数量
  • 行番号

あたりから始めると読みやすいです。

コツ4. 基礎表に索引を作る

Oracleの公式ドキュメントでも、SQLグラフ問合せのパフォーマンス改善のため、
基礎表に索引を作成すること が推奨されています。

コツ5. 権限も確認する

SQLプロパティ・グラフを問い合せるには、
グラフまたは基礎オブジェクトへの READ または SELECT 権限 が必要です。


7. APEX Graph Visualization Plugin のインストールと使い方

最後に、Oracle公式ドキュメントの
APEXグラフ・ビジュアライゼーション・プラグイン の内容をもとに、
Graphの可視化まで整理します。


7-1. 事前準備

公式ドキュメントでは、最初の前提として次が挙げられています。

  • ターゲットとなる APEX アプリケーションが存在すること
  • そのアプリが Oracle Database 26ai に接続していること
  • 可視化対象の SQL Property Graph がデフォルトスキーマに存在すること

今回の engine_bom_oracle.sql は、

  • item_id
  • bom_id
  • bom_line_id

7-2. インストール手順

Oracle Docsベースで整理すると、流れは次の通りです。

  1. Oracle APEX GitHub から
    region_type_plugin_graphviz.sql をダウンロードする
  2. APEXワークスペースにサインインする
  3. APEXワークスペースに DBMS_GVT パッケージと APEX_SQLGRAPH_JSON 関数を作成する
    • gvt_sqlgraph_to_json.sql
    • apex_sqlgraph_to_json.sql
  4. ダウンロードしたプラグインをターゲットAPEXアプリにインポートする
  5. アプリのページにリージョンとして配置する

補足

Oracle Docs では、GitHub上のこのプラグイン版は APEX 24.2 でのみサポート と説明されています。
導入前に、対象のAPEXバージョンを確認しておくと安心です。


7-3. ページ・デザイナでの基本設定

ページ・デザイナでは、新しいリージョンを作成し、次のように設定します。

  • タイプ: Graph Visualization (Preview)
  • 場所: Local Database
  • タイプ値:
    • SQL Query
    • または Property Graph

ここで指定するのが、Graphをどう取得するかの方法です。

設定画面のイメージ

以下は、APEX のページ・デザイナで Graph リージョンを作成し、でグラフ名や MATCH / COLUMNS を指定している画面イメージです。
記事中の説明と、実際の画面上の入力欄を対応付けながら読むと理解しやすくなります。

APEXページ・デザイナでGraphリージョンを設定する画面


7-4. SQL Query モード

これは、GRAPH_TABLE を丸ごと書く方式です。

SELECT *
FROM GRAPH_TABLE (
  mfg_engine_bom_item_pg
  MATCH (root IS item) -[e IS uses]-> (comp IS item)
  WHERE root.item_code = 'ENG-ASM-I4-2000-NA'
  COLUMNS (
    VERTEX_ID(root) AS root_id,
    EDGE_ID(e)      AS edge_id,
    VERTEX_ID(comp) AS comp_id
  )
);

向いているケース

  • SQLをそのまま管理したい
  • 複雑な条件を1本のSQLで表現したい
  • SQLベースでレビューしやすくしたい

親子リージョンを分けた設定例

親ノード用のリージョンと子ノード用のリージョンを分けると、選択中の頂点を起点に段階的に展開するUIも作りやすくなります。
次の画面では、GRAPH_PARENT / GRAPH_CHILD のようにリージョンを分けつつ、ページアイテムを WHERE 句へ渡して絞り込む構成が確認できます。

APEXページ・デザイナで親子のGraphリージョンを設定する例


7-5. 表示ラベル・Expand・表示サイズの調整

APEX の Graph Visualization では、どのラベルをノード名として見せるか何ホップまで展開するか一度に何要素を表示するか を組み合わせて調整できます。

たとえば次のような設定が使えます。

  • Vertex Caption: ITEM_NAME
  • Show full label text on hover: ON
  • Enable Expand: ON
  • Expand Hops: 1
  • Max Number Of Hop: 5

APEXのGraph表示設定画面(Caption / Expand)

特に Vertex CaptionITEM_NAME にすると、ITEM_CODE だけの表示より意味が分かりやすくなります。
また Expand Hops を小さく始めると、巨大グラフでも画面が崩れにくく、段階的に探索できます。

ドキュメントでは、表示サイズコントロールを有効にすることで、
表示する頂点数・エッジ数・要素数を動的に変更できる と説明されています。

多階層BOMは一気に可視化すると密になりやすいので、

  • まず 1ホップ
  • 次に 1〜2ホップ
  • 特定サブASSYだけ
  • その後に 1〜3ホップ以上

というように、段階的に広げるのがおすすめです。


7-6. 実際の表示結果

設定ができたら、APEX 上では次のようにグラフが表示されます。

全体を表示した例

全体を表示すると、エンジンASSYを中心に多階層の部品関係がネットワークとして描かれます。
表形式では見えにくい「どの品目がどの品目にぶら下がっているか」が、ノードとエッジのつながりとして直感的に把握できます。

Graph Visualization による全体表示結果

ノードを起点に段階展開した例

特定のノードを選び、ホップ数を指定して展開すると、対象ノード周辺だけをフォーカスして追えます。
全体俯瞰と詳細確認を行き来できるのが、Graph可視化の大きな利点です。

Graph Visualization でノード周辺を展開した結果

Sample Graph Visualizations アプリ

公式ドキュメントでは、Sample Graph Visualizations アプリのインポート手順も案内されています。

もし完成例を先に見たいなら、このサンプルを使うのも有効です。


まとめ

今回の engine_bom_oracle.sql は、
「RDBで正しく設計したBOMを、必要に応じてGraphとして扱う」
という流れを示しています。

ポイントを整理すると、次の通りです。

  • mfg_item / asm_bom_header / asm_bom_component の3表でBOMの基本を表す
  • 主キー・外部キー・CHECK制約で業務ルールを守る
  • COMMENT に業務意味を埋め込むことで、設計書・UI・AI利用にも効く
  • mfg_engine_bom_item_pgmfg_engine_bom_rev_pg を使い分ける
  • GRAPH_TABLE を使えば、SQLのままGraph Queryが書ける
  • APEX Graph Visualization Plugin を使えば、SQL Query モードまたは Property Graph モードで可視化できる

参考リンク


10
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?