Excel・文書業務

複数の Excel シートを source-sheet 列付きの1つの表にまとめる

同じ列構成を持つ複数のワークシートを1つの Excel 表に統合し、各行がどのシートから来たかを記録します。小さな合成ワークブックを使うことで、行順、件数、合計値を手作業でも確認できます。

目次を表示

この翻訳はAIで作成しました。コード、単位、数値は原文と併せて確認してください。各言語のネイティブ話者による校閲は、まだ完了していません。 English

対象読者同じような構造の Excel シートを複数受け取り、元のワークブックを変更せずに1つの統合データセットを作成したい人向けのガイドです。

準備するもの
  • Python 3.12 と、そのバージョンを起動できるターミナルコマンド。
  • python -m pip install openpyxl で openpyxl をインストール済みであること。
  • スクリプトが outputs の下にフォルダを作成できる作業フォルダ。
  • 統合するシートは、同じ列名を同じ順序で使用している必要があります。

01何を統合するかを正確に定義する

このワークフローでは、1つのワークブックから選択したワークシートを新しいワークブックに統合します。各元シートのヘッダー行は、列順と大文字・小文字を含めて完全に一致している必要があります。出力では最初の列として source_sheet を追加し、各レコードの出所を保持します。

空白行は無視されます。空白でない行は、ワークシートの順序、その後に各シート内の元の行順でコピーされます。元のワークブックは読み取り専用で開かれ、結果は outputs の下にある別フォルダへ書き込まれます。

02合成ワークブックを作成する

以下のワークブックは合成データであり、この記事のために作成したものです。セットアップスクリプトを create_excel_merge_demo.py として保存してください。East, West, Online という3つのシートを作成し、それぞれ order_id, customer, amount の列を使用します。

python
from pathlib import Path
from openpyxl import Workbook

SOURCE_DIR = Path("outputs") / "excel_merge_demo"
SOURCE = SOURCE_DIR / "regional_orders.xlsx"
HEADERS = ["order_id", "customer", "amount"]
DATA = {
    "East": [
        ["E001", "Ana", 120],
        ["E002", "Ben", 80],
    ],
    "West": [
        ["W001", "Cara", 150],
        ["W002", "Dan", 70],
    ],
    "Online": [
        ["O001", "Emi", 90],
        [None, None, None],
        ["O002", "Finn", 110],
    ],
}

SOURCE_DIR.parent.mkdir(parents=True, exist_ok=True)
SOURCE_DIR.mkdir()  # Stop if the synthetic source folder already exists.

workbook = Workbook()
for index, (sheet_name, rows) in enumerate(DATA.items()):
    if index == 0:
        sheet = workbook.active
        sheet.title = sheet_name
    else:
        sheet = workbook.create_sheet(sheet_name)
    sheet.append(HEADERS)
    for row in rows:
        sheet.append(row)

workbook.save(SOURCE)
text
python create_excel_merge_demo.py
シート空白でないデータ行amount 合計
East2200
West2220
Online2200

空白でないデータレコードは合計6件です。amount の合計は 620 です: 120 + 80 + 150 + 70 + 90 + 110。Online には2件のレコードの間に完全な空白行を1行意図的に入れており、統合処理で空白行がスキップされることを確認できます。

03想定される統合後の行順を手作業で確認する

スクリプトではシートを East, West, Online の順で明示的に指定しています。この順序が統合後のレコード順を決定します。各シート内では、元の上から下への順序が維持されます。

source_sheetorder_idcustomeramount
EastE001Ana120
EastE002Ben80
WestW001Cara150
WestW002Dan70
OnlineO001Emi90
OnlineO002Finn110

したがって出力は4列となり、ヘッダーを含めるとワークシート上では7行になります: ヘッダー1行 + データ6行。source_sheet の値は手作業での件数確認にも使えます。East は2回、West は2回、Online は2回出現します。

04シートを新しいワークブックに統合する

次のスクリプトを excel_merge_sheets.py として保存してください。元ワークブックを読み取り専用で開き、選択したシート名とヘッダーを検証し、完全な空白行を無視します。また、数式を元のワークブックの文脈から切り離した状態でコピーしないよう、数式セルを検出した場合は処理を停止します。

python
from pathlib import Path
from openpyxl import Workbook, load_workbook

SOURCE = Path("outputs") / "excel_merge_demo" / "regional_orders.xlsx"
OUTPUT_DIR = Path("outputs") / "excel_merge_result"
OUTPUT = OUTPUT_DIR / "merged_orders.xlsx"
SHEETS = ("East", "West", "Online")
OUTPUT_SHEET = "Combined"
SOURCE_COLUMN = "source_sheet"


def read_sheet_rows(sheet, expected_header=None):
    first_row = next(
        sheet.iter_rows(min_row=1, max_row=1, values_only=True),
        None,
    )
    if first_row is None:
        raise ValueError(f"Sheet is empty: {sheet.title}")

    header = tuple(first_row)
    if any(not isinstance(name, str) or not name.strip() for name in header):
        raise ValueError(f"Invalid header in sheet: {sheet.title}")
    if len(set(header)) != len(header):
        raise ValueError(f"Duplicate header name in sheet: {sheet.title}")
    if SOURCE_COLUMN in header:
        raise ValueError(f"Reserved column already exists: {SOURCE_COLUMN}")
    if expected_header is not None and header != expected_header:
        raise ValueError(f"Header mismatch in sheet: {sheet.title}")

    rows = []
    for excel_row, values in enumerate(
        sheet.iter_rows(min_row=2, max_col=len(header), values_only=True),
        start=2,
    ):
        if all(value is None for value in values):
            continue
        if any(isinstance(value, str) and value.startswith("=") for value in values):
            raise ValueError(
                f"Formula found in {sheet.title} row {excel_row}; "
                "this example merges literal values only."
            )
        rows.append(tuple(values))

    return header, rows


def main() -> None:
    source_path = SOURCE.resolve(strict=True)
    if not source_path.is_file():
        raise ValueError("SOURCE must be an Excel file.")
    if len(set(SHEETS)) != len(SHEETS):
        raise ValueError("SHEETS contains a duplicate sheet name.")

    source_workbook = load_workbook(
        source_path,
        read_only=True,
        data_only=False,
    )
    try:
        missing = [name for name in SHEETS if name not in source_workbook.sheetnames]
        if missing:
            raise ValueError(f"Missing sheets: {missing}")

        expected_header = None
        combined_rows = []
        for sheet_name in SHEETS:
            sheet = source_workbook[sheet_name]
            header, rows = read_sheet_rows(sheet, expected_header)
            if expected_header is None:
                expected_header = header
            for row in rows:
                combined_rows.append((sheet_name, *row))
    finally:
        source_workbook.close()

    OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
    OUTPUT_DIR.mkdir()  # Refuse to reuse an existing output folder.

    output_workbook = Workbook()
    output_sheet = output_workbook.active
    output_sheet.title = OUTPUT_SHEET
    output_sheet.append([SOURCE_COLUMN, *expected_header])
    for row in combined_rows:
        output_sheet.append(row)
    output_workbook.save(OUTPUT)

    check_workbook = load_workbook(OUTPUT, read_only=True, data_only=False)
    try:
        if check_workbook.sheetnames != [OUTPUT_SHEET]:
            raise RuntimeError("Unexpected output worksheet structure.")
        check_sheet = check_workbook[OUTPUT_SHEET]
        written = list(check_sheet.iter_rows(values_only=True))
    finally:
        check_workbook.close()

    expected = [(SOURCE_COLUMN, *expected_header), *combined_rows]
    if written != expected:
        raise RuntimeError("Saved workbook does not match the expected rows.")

    print(f"Merged {len(SHEETS)} sheets into {len(combined_rows)} data rows.")
    print(f"Verified {len(expected_header) + 1} columns and {len(combined_rows)} data rows.")
    print(f"Output: {OUTPUT.as_posix()}")


if __name__ == "__main__":
    main()
text
python excel_merge_sheets.py

05想定される結果を比較する

合成ワークブックでは、出力に Combined という名前のワークシートが正確に1つ含まれるはずです。1行目は source_sheet, order_id, customer, amount となり、その後に前述の6件のレコードが続きます。

以下の想定コンソール出力は、コードと合成データから手作業で導出したものです。実際に取得した実行ログではありません。

text
Merged 3 sheets into 6 data rows.
Verified 4 columns and 6 data rows.
Output: outputs/excel_merge_result/merged_orders.xlsx
確認項目想定結果
選択した元シート3
統合データ行6
出力列4
East 行2
West 行2
Online 行2
amount 合計620

06実データを使う前にワークブックを確認する

  1. merged_orders.xlsx を開き、Combined という名前のワークシートが1つだけあることを確認します。
  2. source_sheet が最初の列で、その後に元の3列が同じ順序で続いていることを確認します。
  3. source_sheet ごとにレコード数を数えます。East, West, Online がそれぞれ正確に2回ずつ出現するはずです。
  4. amount 列を手作業または Excel で合計します。想定値は 620 です。
  5. Online の空白行が、統合結果で空白レコードとして追加されていないことを確認します。
  6. OUTPUT_DIR を変更せずにスクリプトを再実行します。以前の結果を置き換えるのではなく、FileExistsError で停止するはずです。

実務ワークブックでは、すべてのワークシートを自動統合するのではなく、SHEETS を明示的に変更してください。こうすることで、同じファイル内にある参照表、メモ、非表示シート、集計シートなどを誤って含める可能性を減らせます。

07よくあるエラーを確認する

症状確認すること
ModuleNotFoundError: No module named openpyxlpython -m pip install openpyxl を使い、同じ Python 環境に openpyxl をインストールします。
FileNotFoundErrorSOURCE とターミナルの作業ディレクトリを確認します。この例では、先に合成ワークブックのセットアップを実行してください。
Missing sheetsSHEETS の名前を、空白や大文字・小文字も含めて正確に確認します。
Header mismatch選択したすべてのシートで、同一の列名が同一の順序で使われていることを確認します。
Reserved column already existssource_sheet という元列の名前を変更するか、SOURCE_COLUMN を衝突しない名前に変更します。
Formula found統合前に数式の扱い方を決めてください。このチュートリアルでは、文脈のない数式式をコピーする代わりに意図的に停止します。
FileExistsError出力フォルダがすでに存在します。以前の結果を確認し、上書きせず新しい保存先を使用します。

OUTPUT_DIR の作成後に保存や検証が失敗すると、フォルダまたはワークブックが部分的な結果として残る場合があります。未検証の結果として扱い、問題を修正した後は新しい保存先を使用してください。

08制限を理解する

このスクリプトは、ヘッダー行が1行の矩形データシートを想定しています。複数行ヘッダー、結合されたヘッダーセル、ピボットテーブル、グラフ、画像、複数の無関係な表が同じシート内にあるワークシートには対応していません。

統合されるのはセル値だけです。数値書式、フォント、塗りつぶし、条件付き書式、入力規則、ハイパーリンク、コメント、数式、行の高さ、列幅、ワークシートレベルの設定は再現されません。日付値や数値そのものは値として保持できますが、元の表示書式はコピーされません。

また、このスクリプトは出力ワークブックを作成する前に、統合したすべての行タプルを Python のメモリへ読み込みます。この小規模な検証済みワークフローでは扱いやすい方法ですが、非常に大きなワークブックには適さない可能性があります。大規模ファイルでは、write-only 出力と段階的な別検証を組み合わせたストリーミング設計にすると、メモリ使用量を減らせます。

実行・検証の記録

2026-09-20 · 手作業で確認した例 · 対象: Python 3.12 · openpyxl 必須 · 未実行

  • East で2件、West で2件、Online で2件の空白でないレコードを手作業で数え、統合レコードが6件になることを確認しました。
  • 各シートの amount 合計を 200, 220, 200 と手作業で計算し、全体が 620 になることを確認しました。
  • 完全な空白行をスキップし、East の行、次に West、その後 Online の順になる想定出力順を手作業で導出しました。
  • 元ワークブックが読み取り専用で開かれ、結果が別の outputs フォルダに書き込まれることをコードで確認しました。
  • ヘッダーチェック、予約済み source_sheet のチェック、空白行処理、数式拒否、保存後の行比較を確認しました。
  • 出力4列、データ6行、コンソールメッセージの想定値を手作業で導出しました。
検証範囲の限界
  • この回答の作成者はコードを実行しておらず、XLSX ファイルの作成や展開も行っていません。
  • 数式を含むワークブック、結合セル、書式、日付、ハイパーリンク、非表示シート、破損ファイル、非常に大きなワークブックはテストしていません。
  • 合成ワークブックでの openpyxl の動作はコードから推論したもので、Python ランタイムでは確認していません。
  • 公式ドキュメントの URL は既知のドキュメント所在地に基づいて記載していますが、ライブでは確認していません。

サイト全体の執筆・検証方針

参考資料

説明と例は独自に作成しました。関連する動作や概念は、以下の公式資料で確認できます。