月初の貼り付け地獄 — 部署別ファイルの統合をワンクリックで

月初の貼り付け地獄

月初になると、各部署から「今月の集計です」とスプレッドシートが届きます。営業1課、2課、3課、それぞれ別のファイル。
あなたの仕事は、これを1枚にまとめて全社の集計表にすること。

部署別ファイルの統合例

ファイルを1つずつ開いて、データ範囲を選択して、コピー、統合先に切り替えて、貼り付け、次のファイル……。
3部署ならまだしも、5部署、10部署とあると、月初の半日が消えます。
しかも、貼り付け位置が1行ずれるだけで、後の集計が全部おかしくなります。

指定したフォルダ内のスプレッドシートをワンクリックで統合

それなら、GASで自動化しましょ!

指定したフォルダに各部署からのファイルを入れておけば、ボタン1つでスプレッドシートをすべて読み込み、1枚のシートに統合してくれます。
「元ファイル名」も自動で付くので、後からどの行がどの部署由来か一目でわかります。

コード.gs

// コード.gs
/**
 * 指定フォルダ内の全スプレッドシートを1枚に縦結合する
 * 各ファイルの最初のシートを読み込み、最頻のヘッダーを基準にして揃うものだけ統合
 */

const SETTINGS_SHEET = '設定';
const RESULT_SHEET = '統合結果';
const SOURCE_COL_NAME = '元ファイル名';

function mergeFiles() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const ui = SpreadsheetApp.getUi();

  const settings = ss.getSheetByName(SETTINGS_SHEET);
  const folderId = settings ? String(settings.getRange('B1').getValue() || '').trim() : '';
  if (!folderId) {
    ui.alert('「' + SETTINGS_SHEET + '」シートのB1にフォルダIDを入力してください。');
    return;
  }

  let folder;
  try {
    folder = DriveApp.getFolderById(folderId);
  } catch (e) {
    ui.alert('フォルダが見つかりません。IDを確認してください。');
    return;
  }

  // 1パス目: 全ファイルを読み込んで保持
  const files = folder.getFilesByType(MimeType.GOOGLE_SHEETS);
  const allFiles = [];
  while (files.hasNext()) {
    const file = files.next();
    if (file.getId() === ss.getId()) continue;
    const src = SpreadsheetApp.openById(file.getId());
    const values = src.getSheets()[0].getDataRange().getValues();
    if (values.length < 2) continue;
    allFiles.push({ name: file.getName(), header: values[0], rows: values.slice(1) });
  }

  if (allFiles.length === 0) {
    ui.alert('統合できるファイルが見つかりませんでした。');
    return;
  }

  // ファイル名の昇順でソート(Driveの返却順は不定のため)
  allFiles.sort(function(a, b) {
    return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
  });

  // 最も多く出現するヘッダーを基準にする(少数派のファイルがスキップ対象)
  const counts = {};
  allFiles.forEach(function(f) {
    const key = f.header.join('\t');
    counts[key] = (counts[key] || 0) + 1;
  });
  let baseKey = null;
  let baseCount = 0;
  Object.keys(counts).forEach(function(k) {
    if (counts[k] > baseCount) {
      baseCount = counts[k];
      baseKey = k;
    }
  });
  const baseHeader = baseKey.split('\t');

  // 基準ヘッダーと一致するファイルだけ統合
  const merged = [];
  const skipped = [];
  let fileCount = 0;
  allFiles.forEach(function(f) {
    if (f.header.join('\t') === baseKey) {
      f.rows.forEach(function(row) {
        merged.push([f.name].concat(row));
      });
      fileCount++;
    } else {
      skipped.push(f.name);
    }
  });

  const outHeader = [SOURCE_COL_NAME].concat(baseHeader);
  const resultSheet = ss.getSheetByName(RESULT_SHEET) || ss.insertSheet(RESULT_SHEET);
  resultSheet.clear();
  resultSheet.getRange(1, 1, 1, outHeader.length).setValues([outHeader]);
  if (merged.length > 0) {
    resultSheet.getRange(2, 1, merged.length, outHeader.length).setValues(merged);
  }
  resultSheet.getRange(1, 1, 1, outHeader.length)
    .setBackground('#f3f3f3')
    .setFontWeight('bold')
    .setHorizontalAlignment('center');

  SpreadsheetApp.flush();

  let msg = '統合完了。\n\n' +
    'ファイル数: ' + fileCount + '\n' +
    'データ行数: ' + merged.length;
  if (skipped.length > 0) {
    msg += '\n\nヘッダーが多数派と異なるためスキップ:\n- ' + skipped.join('\n- ');
  }
  ui.alert(msg);
}

function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu('自動ボタン')
    .addItem('フォルダ内ファイルを統合', 'mergeFiles')
    .addToUi();
}

使い方

① 統合先のスプレッドシートを新規作成
統合した結果を書き出すための「マスタ」スプレッドシートを1つ用意します。

② 拡張機能 → Apps Script にコードを貼り付けて保存

③ Driveに統合元ファイルを集めるフォルダを作る
例: `統合元_サンプル` というフォルダを作り、そこに各部署のファイル(営業1課、2課、3課……)を入れておきます。

④ 統合先スプレッドシートに「設定」という名前のシートを用意し、B1セルにフォルダIDを入力

フォルダIDは、DriveでフォルダURLを開いた時の `https://drive.google.com/drive/folders/【ここの部分】` です。

⑤ スプレッドシートをリロード → 「自動ボタン」→「フォルダ内ファイルを統合」を実行
初めて実行すると承認ダイアログが表示されます。手順は1と同じです。

実行すると、フォルダ内の全スプレッドシートが読み込まれ、「統合結果」シートに縦結合された状態で書き出されます。
処理したファイル数とデータ行数がポップアップで表示されます。

ワンポイント・レッスン

DriveApp.getFolderById() — フォルダの中身をGASから操作する

コードの中にあるこの3行が、今回の統合処理の心臓部です。

const folder = DriveApp.getFolderById(folderId);
const files = folder.getFilesByType(MimeType.GOOGLE_SHEETS);
while (files.hasNext()) {
  const file = files.next();
  // ...
}

GASは Google Drive を直接操作できます。スプレッドシートだけでなく、フォルダの中身を一覧したり、ファイルを開いたり、移動したりが可能です。

  • `DriveApp.getFolderById(id)` — フォルダIDを指定してフォルダを取得する

  • `folder.getFilesByType(MimeType.GOOGLE_SHEETS)` — フォルダ内のスプレッドシートだけを取り出す。Excelやドキュメントは自動で除外される

  • `files.hasNext()` / `files.next()` — ファイル一覧は配列ではなく「イテレータ」。`hasNext()` で次があるか確認し、`next()` で1つずつ取り出すパターンで回す

DriveApp                              ← Drive全体への入口
  └ getFolderById('1AbC...')          ← フォルダを指定
      └ getFilesByType(GOOGLE_SHEETS) ← スプレッドシートだけ取り出す
          ├ 営業1課 ← files.next()
          ├ 営業2課 ← files.next()
          └ 営業3課 ← files.next()

スプレッドシートの中だけで完結する処理(第1〜3回)と違い、`DriveApp` を使うと「フォルダの中の全ファイル」を相手にできるようになります。GASならではの強みです。

まとめ

フォルダにファイルを入れておくだけで、ボタン1つで全ファイルを統合。「元ファイル名」が自動で付くので、どの行がどの部署由来か一目瞭然。
月初の貼り付け作業がまるごと消えます。


全ファイルが同じヘッダーとは限らない


ヘッダー揺れ(列名・列順・項目が不一致)の例

部署ごとにシートのヘッダーが一致していないことは珍しくありません。
営業一課は「日付」列から始まるが、営業二課は「担当者」列から始まる。
営業一課は「商品名」「数」だが、営業二課は「商品」「数量」。
営業二課には「備考」があるが、営業一課にはない…

コード.gsはヘッダーの一致が前提で、不一致のファイルはスキップしましたが、拡張コードでは、これらのヘッダー揺れにも対応させましょう。

拡張コードで出来ること

  • ヘッダー揺れ対応統合 — 全ファイルのヘッダーを和集合して列を作り、各行を列名ベースで再配置。列順がバラバラでも、追加列があっても、1枚に統合できる

    この記事の続きはnoteで公開しています。

    noteで続きを読む

GAS,自動化自動化

Posted by botw