Excelで2つの台帳を照合する方法|差分・値違い・要確認を分ける

2つの台帳を照合するときは、同じ行の位置を比べる前に「何を同じ対象とするか」を決めます。共通のIDで対応を取り、Aだけの候補、Bだけの候補、同じIDの値違い、要確認行を分けると、元の台帳へ戻って確認できます。

重複IDや型違いが残っているのに、最初に見つかった値だけを使って一致・不一致を確定しないことが大切です。この記事では、Excelの小さな架空例を作り、元行と件数を確認して保存するところまで進めます。要確認が1件でも残る間は、照合全体の完了や相手台帳へのデータ不存在を断定しません。

この手順の対象と、照合後に作るもの

対象はWindows版Excel for Microsoft 365で扱う、小さな2台帳・単一の文字列ID・比較する列1つです。完成物は「差分候補と値違いの一覧」「要確認行の一覧」「元件数と対応する検算」です。重複行を削除する手順は含みません。

本文の数式と架空例は、Windows 64bit、Microsoft 365系Excel 16.0.20430.20092で計算・条件変更・保存再読込を確認しました。キーボード入力を初心者が通しで行う利用試験、他のExcel版、MacやWeb版での操作確認は行っていません。

LET・XLOOKUPを使います。これらの関数が認識されない環境では、この手順をそのまま進めないでください。MicrosoftはXLOOKUPをExcel 2016・2019では使用できないと説明しています。 Microsoft:XLOOKUP関数

1.比較条件を先に決める

決めること この例の条件
対象 同じ対象を管理する台帳Aと台帳B
対応を取るキー ID列。空欄でない文字列、各台帳で1IDにつき1行
比較する値 Amount列の1つだけ。State列は今回は比較しない
文字の扱い 大文字小文字を区別。WILD*?の*と?もIDの文字として比較
値の扱い 数値・文字列・空欄を分ける。空欄同士は一致、空欄と0は要確認
空白・重複・エラー 勝手に補正・削除・先頭採用せず、要確認に残す

IDを商品名や表示順で代用すると、同じ名前の別対象や並び順の変化で誤ることがあります。実務の台帳を使う前に、IDが同じ対象を表す根拠と、どの時点の2台帳を比べるかを確認してください。日付や金額の丸め、許容差、複数列の総合判定はこの例の条件に含みません。

2.架空の2台帳を同じブックに用意する

実データを上書きせず、新しいブックでシートを Rules、A、B、Results、Checks の5つにします。綴りを変えると後の式の参照が合いません。以下の表の「元行」は入力場所の案内です。表全体をA1に貼り付けるのではなく、指定行のA列にID、B列にAmountを入力します。

A・Bの1行目は、A1=ID、B1=Amount、C1=State、D1=KeyCheck、E1=ValueCheck、F1=SourceRow とします。入力中のセルは、先に列全体を文字列へ一括変換せず、新しいブックの通常のセルを使います。

入力欄の先頭にアポストロフィ ' があるものは、先頭も含めて入力します。A10の '700 は文字列700、B9の 700 は数値700です。B11の '100 は文字列100。先頭アポストロフィは文字列入力の指定で、IDや値そのものの文字には含まれません。入力後の型は後のKeyCheck/ValueCheckで確認します。

入力前に空のセルを「文字列」形式にしてから数字を入力する方法もあります。既に入った値の見た目だけを変更して型まで直ったと思わないようにします。この例では入力欄の指定と確認結果を優先してください。 Microsoft:数値を文字列として扱う

台帳A:A2:B17へ入力

元行 A列:IDの入力 B列:Amountの入力
2 SAME 100
3 ONLY_A 200
4 DIFF 300
5 DUP_A 400
6 DUP_A 401
7 DUP_B 500
8 空欄(何も入力しない) 600
9 EMPTY 空欄(何も入力しない)
10 '700 700
11 ' SPACE 800
12 TYPE 100
13 VALUE_SPACE ' x
14 WILD*? 900
15 ERR =NA()
16 CASE abc
17 BLANK_ZERO 空欄(何も入力しない)

台帳B:A2:B16へ入力

元行 A列:IDの入力 B列:Amountの入力
2 SAME 100
3 ONLY_B 250
4 DIFF 350
5 DUP_A 400
6 DUP_B 500
7 DUP_B 501
8 EMPTY 空欄(何も入力しない)
9 700 700
10 SPACE 800
11 TYPE '100
12 VALUE_SPACE x
13 WILD*? 900
14 ERR 1400
15 CASE ABC
16 BLANK_ZERO 0

A11のIDは、アポストロフィの直後に半角スペース1個、その後にSPACEを入力します。A13のAmountは、アポストロフィの後に「半角スペース1個・x・半角スペース1個」です。空白を見せる記号を追加しないでください。空欄指定のセルには「空欄」という文字や0を入れません。A15のAmountは =NA() を数式として入れ、意図的に#N/Aを作ります。

AのC2:C17、BのC2:C16には、すべて open を入力します。F列には元のシート行番号を値として記録し、AのF2:F17は2〜17、BのF2:F16は2〜16にします。後でResultsへこの番号を写すため、ここまでの行を入れ替えないでください。

RulesではA2に「比較列」、B2に数値 2 を入れます。2はAmount列、3はState列です。今回は2のまま進めます。日付・取得時点や「ID文字列、大小文字を区別、要確認は自動補正しない」という条件もRulesにメモしておくと、保存後に条件を取り違えにくくなります。

3.型・空白・空欄を先に確認する

A・BのD列でIDの条件、E列で選択した比較値の条件を確認します。数式は先頭の=を含む1行全体を、指定セルの数式バーへ貼り付けて確定します。式を途中で別セルに分割しないでください。

A!D2・B!D2:キーの事前確認

=IF(ISERROR(A2),"KEY_ERROR",IF(A2="","KEY_BLANK",IF(NOT(ISTEXT(A2)),"KEY_TYPE",IF(OR(A2<>TRIM(A2),ISNUMBER(SEARCH(CHAR(160),A2)),ISNUMBER(SEARCH(CHAR(9),A2)),ISNUMBER(SEARCH(CHAR(10),A2))),"KEY_SPACE","OK"))))

空欄・文字列以外・空白等を分け、条件を満たすIDだけOKにします。ISERRORはキーのエラー診断、ISTEXTは文字列かの確認です。A2とTRIM(A2)が違うと、前後または連続した半角スペースを疑います。CHAR(160)・CHAR(9)・CHAR(10)はIDに含まれるNBSP・タブ・改行の診断ですが、この記事の例で特殊空白すべての相手候補判別まで実証したわけではありません。

A!E2・B!E2:比較する値の事前確認

=LET(v,INDEX(A2:C2,1,Rules!$B$2),IF(ISERROR(v),"VALUE_ERROR",IF(ISBLANK(INDEX(A2:C2,1,Rules!$B$2)),"BLANK",IF(ISNUMBER(v),"NUMBER",IF(ISTEXT(v),IF(v<>TRIM(v),"VALUE_SPACE","TEXT"),"VALUE_UNSUPPORTED")))))

INDEXでRules!B2が指定する列を選び、LETのvでその値を扱います。BLANK・NUMBER・TEXTを分け、エラーやTRIMで変わる空白はVALUE_で始まる要確認コードにします。空欄を0へ置き換えず、元セルが空欄かをISBLANKで調べます。

AはD2:E2をD2:E17へ、BはD2:E2をD2:E16へコピーします。コピー先を含む範囲を選んで下方向にコピーするか、フィルハンドルを使います。最終行の式がA17/B16を参照していることを数式バーで確認してください。コピーによる相対参照の結果は、今回の実Excelの局所確認でも元の検証式と一致しました。

KeyCheck 意味
OK この例のキー条件を満たす
KEY_BLANK キー空欄
KEY_TYPE キーが文字列でない
KEY_SPACE 空白等の確認が必要
KEY_ERROR キーがエラー
ValueCheck 意味
BLANK 比較値は空欄
NUMBER 比較値は数値
TEXT 比較値は文字列
VALUE_SPACE TRIMで変わる空白がある
VALUE_ERROR 比較値がエラー
VALUE_UNSUPPORTED この手順で扱わない値型

TRIMで元データを一括修正する手順ではありません。TRIMは半角スペースの扱いに注意があり、NBSPを単独では取り除きません。ValueCheckも特殊空白を網羅する仕組みではないため、データ元に応じた確認が必要です。 Microsoft:TRIM関数

補助列ができたら、AのA1:F17、BのA1:F16をそれぞれテーブルにします。範囲を選び、[挿入]→[テーブル]で「先頭行をテーブルの見出しとして使用する」を選びます。テーブル内を選んで[テーブルデザイン]のテーブル名をA側は TableA、B側は TableB にします。シート名A/Bとテーブル名TableA/TableBは別です。

TableA[ID]のような参照で列全体を指定するため、本文どおりのテーブル名・見出しが必要です。範囲に空白の追加入力行を含めず、上記の行数で作ってください。 Microsoft:テーブルの作成

4.A側から、安全な1対1だけを照合する

Resultsは最初は通常のセル範囲のまま作り、A側・B側の式をコピーし終えてからテーブルにします。まず1行目を次の見出しにします。A側結果は2〜17行、B側結果は18〜32行です。

列 見出し 役割
A Side 元台帳A/B
B OriginRow 自分側の元行
C ID 元のID
D KeyCheck 元台帳のキー確認
E ACount 適格なAキーの件数
F BCount 適格なBキーの件数
G UnsafeCandidateCount 型や空白のため保留された相手候補数
H MatchedOriginRow 安全に対応を取れた相手の元行
I AValue Aの比較値
J BValue Bの比較値
K Result 判定コード

A2:A17はすべて A。B2:B17へAシートのF2:F17を「値」として写します。数式や見た目だけで元行を推測せず、台帳名と元行をセットにします。

Results!C2:元のID

=IF(ISBLANK(A!A2),"",A!A2)

Results!D2:元のキー確認

=A!D2

ID空欄が0のように見えないようにISBLANKで分けています。D列は事前確認の結果をそのまま参照します。次のE・F列で、適格なキーが左右に何件あるかを数えます。

Results!E2:A側の適格な同ID件数

=SUMPRODUCT(--IFERROR(EXACT(C2,TableA[ID]),FALSE),--(TableA[KeyCheck]="OK"))

Results!F2:B側の適格な同ID件数

=SUMPRODUCT(--IFERROR(EXACT(C2,TableB[ID]),FALSE),--(TableB[KeyCheck]="OK"))

EXACTでIDの文字を比べ、KeyCheckがOKの行だけをSUMPRODUCTで数えます。大文字小文字を区別し、ID内の*や?を検索用のワイルドカードとして使いません。見た目の書式が同じだけでは型確認の代わりになりません。 Microsoft:EXACT関数

Results!G2:不適格キーの相手候補を確認

=SUMPRODUCT(--IFERROR(EXACT(C2,TRIM(TableB[ID]&"")),FALSE),--(TableB[KeyCheck]<>"OK"))

この列だけでは一致としません。不適格な相手キーを文字列化・TRIMした場合に同じに見える候補を診断します。Aの文字列700とBの数値700、Aの端空白付きSPACEとBのSPACEを、片側だけのデータとして即断しないためです。元IDの型や空白は変えません。この診断は全種類の表記ゆれを網羅しません。

Results!H2:安全な相手の元行

=IF(AND(D2="OK",E2=1,F2=1,G2=0),XLOOKUP(TRUE,IFERROR(EXACT(C2,TableB[ID]),FALSE),TableB[SourceRow],"",0),"")

自分のキーがOK、AとBが各1件、不適格な相手候補が0件のときだけ相手の元行を採用します。XLOOKUPの検索対象はEXACTのTRUE/FALSE配列です。重複しているのに最初の1行を相手として採用することを避けます。

Results!I2:自分側Aの比較値

=IF(ISBLANK(INDEX(A!A2:C2,1,Rules!$B$2)),"",INDEX(A!A2:C2,1,Rules!$B$2))

Results!J2:安全に対応するBの比較値

=IF(H2="","",IF(XLOOKUP(TRUE,IFERROR(EXACT(C2,TableB[ID]),FALSE),ISBLANK(INDEX(TableB,0,Rules!$B$2)),FALSE,0),"",XLOOKUP(TRUE,IFERROR(EXACT(C2,TableB[ID]),FALSE),INDEX(TableB,0,Rules!$B$2),"",0)))

I列は選択したAの値。J列はH列で対応を採用した場合だけBの値を表示します。元が空欄なら空文字の表示にし、0と混同しないための事前確認はValueCheckで維持します。比較値の#N/Aは、元にある問題がわかるよう表示に残ります。I/Jの見た目だけで一致を決めず、次のK列の判定を見ます。

Results!K2:A側の判定

=IFERROR(IF(D2<>"OK","HOLD_"&D2,IF(OR(E2>1,F2>1),"HOLD_DUPLICATE",IF(G2>0,"HOLD_UNSAFE_KEY_CANDIDATE",IF(F2=0,"ONLY_A",LET(ownkind,A!E2,otherkind,XLOOKUP(TRUE,IFERROR(EXACT(C2,TableB[ID]),FALSE),TableB[ValueCheck],"",0),IF(OR(LEFT(ownkind,6)="VALUE_",LEFT(otherkind,6)="VALUE_"),"HOLD_VALUE",IF(ownkind<>otherkind,"HOLD_VALUE_TYPE",IF(ownkind="BLANK","SAME",IF(ownkind="NUMBER",IF(I2=J2,"SAME","DIFFERENT"),IF(EXACT(I2,J2),"SAME","DIFFERENT")))))))))),"HOLD_FORMULA_ERROR")

長い式ですが、判断の順序は「キー不適格→左右どちらか重複→不適格な相手候補→適格なBにない→値の要確認→値型違い→値比較」です。ownkindは自分のValueCheck、otherkindは相手のValueCheckです。両方BLANKならSAME、両方NUMBERなら数値比較、それ以外の適格な文字列はEXACTで比較します。式自体のエラーもHOLD_FORMULA_ERRORへ残します。

C2:K2をC2:K17へコピーします。コピー後、17行目の自分側参照がA17であることを確認します。E・F列の式はA/Bのどちらにも使うので、続けてE2:F2をE2:F32までコピーしておきます。他のA側の式をB側の18〜32行までそのまま延長しないでください。

5.B側からも確認し、Bだけの候補を拾う

A側からの検索だけでは、BにしかないIDを拾えません。ResultsのA18:A32を B にし、B18:B32へBシートのF2:F16を値として写します。E/F列は前の手順の共通式を使用します。残りは、B側の先頭である18行目に以下の式を入れます。

Results!C18:元のBのID

=IF(ISBLANK(B!A2),"",B!A2)

Results!D18:元のBのキー確認

=B!D2

Results!G18:不適格なA側候補

=SUMPRODUCT(--IFERROR(EXACT(C18,TRIM(TableA[ID]&"")),FALSE),--(TableA[KeyCheck]<>"OK"))

Results!H18:安全なA側の元行

=IF(AND(D18="OK",F18=1,E18=1,G18=0),XLOOKUP(TRUE,IFERROR(EXACT(C18,TableA[ID]),FALSE),TableA[SourceRow],"",0),"")

Results!I18:安全に対応するAの比較値

=IF(H18="","",IF(XLOOKUP(TRUE,IFERROR(EXACT(C18,TableA[ID]),FALSE),ISBLANK(INDEX(TableA,0,Rules!$B$2)),FALSE,0),"",XLOOKUP(TRUE,IFERROR(EXACT(C18,TableA[ID]),FALSE),INDEX(TableA,0,Rules!$B$2),"",0)))

Results!J18:自分側Bの比較値

=IF(ISBLANK(INDEX(B!A2:C2,1,Rules!$B$2)),"",INDEX(B!A2:C2,1,Rules!$B$2))

Results!K18:B側の判定

=IFERROR(IF(D18<>"OK","HOLD_"&D18,IF(OR(E18>1,F18>1),"HOLD_DUPLICATE",IF(G18>0,"HOLD_UNSAFE_KEY_CANDIDATE",IF(E18=0,"ONLY_B",LET(ownkind,B!E2,otherkind,XLOOKUP(TRUE,IFERROR(EXACT(C18,TableA[ID]),FALSE),TableA[ValueCheck],"",0),IF(OR(LEFT(ownkind,6)="VALUE_",LEFT(otherkind,6)="VALUE_"),"HOLD_VALUE",IF(ownkind<>otherkind,"HOLD_VALUE_TYPE",IF(ownkind="BLANK","MATCHED_SAME_IN_A",IF(ownkind="NUMBER",IF(I18=J18,"MATCHED_SAME_IN_A","MATCHED_DIFFERENT_IN_A"),IF(EXACT(I18,J18),"MATCHED_SAME_IN_A","MATCHED_DIFFERENT_IN_A")))))))))),"HOLD_FORMULA_ERROR")

A側と同じ順序で保留を先に判断します。適格なAキーが0件ならONLY_B。Aにも安全に対応する行がある場合はMATCHED_SAME_IN_AまたはMATCHED_DIFFERENT_IN_Aと表示します。これはA側で扱った組の参照で、追加の一致組・値違い組として二重計上しません。

C18:K18をC18:K32へコピーします。32行目の自分側がB16を参照することを確認します。最後にResultsのA1:K32をテーブルにして、名前を ResultTable にします。列名とテーブル名が揃った後に、次の件数式を作ります。

6.差分と要確認行を読み、元の台帳へ戻る

Resultの表示 読み方・次の確認
SAME A側で扱う一致組。空欄同士の一致も含む
DIFFERENT 同じ適格IDで比較値が違う。A/Bの元行を確認
ONLY_A / ONLY_B 適格キー条件での片側候補。保留が残る間は不存在を確定しない
MATCHED_SAME_IN_A / MATCHED_DIFFERENT_IN_A B側から見た、Aで既に扱った組
HOLD_DUPLICATE どちらかの台帳でIDが重複。代表行を勝手に選ばない
HOLD_KEY_BLANK / HOLD_KEY_TYPE / HOLD_KEY_SPACE / HOLD_KEY_ERROR 自分側キーの問題を確認
HOLD_UNSAFE_KEY_CANDIDATE 型や空白で不適格になった相手候補を確認
HOLD_VALUE / HOLD_VALUE_TYPE 値のエラー・空白等、または型の違いを確認
HOLD_FORMULA_ERROR 式・参照・入力条件を確認。未判定のまま残す

Result列のフィルターでDIFFERENT、ONLY_A、ONLY_B、各HOLDを順に確認します。保留はHOLDで始まる値を表示する条件にします。値を見るときはSide+OriginRowで自分の元行、MatchedOriginRowで相手の元行へ戻ります。結果シートの行番号と元台帳の行番号は別です。

たとえばDIFFはA元4行とB元4行、300と350です。Bのみの候補ONLY_BはB元3行。DUP_AはA元5・6行とB元5行を全部確認します。重複中はMatchedOriginRowを採用しないので、元台帳でIDの該当行をすべて見ます。

型違いを確認したら、元の管理ルールと資料を確認し、文字列と数値のどちらに揃えるかを決めます。空白も、除いてよい文字か、IDの一部かを確かめます。同じに見えるからという理由だけでTRIMや数値化を一括適用しません。修正後は同じ条件で再計算し、保留・差分・件数を確認し直します。

7.件数とサンプルを検算する

ChecksのA1をMetric、B1をCountにします。A2:A9へ下表のMetricを順に入力し、対応する式をB列へ入れます。ここでのHOLD*は判定コードを数える前方一致です。IDのWILD*?を文字通りに比較するEXACTとは用途が違います。

Checks!B2:SAME

=COUNTIF(ResultTable[Result],"SAME")

Checks!B3:DIFFERENT

=COUNTIF(ResultTable[Result],"DIFFERENT")

Checks!B4:ONLY_A

=COUNTIF(ResultTable[Result],"ONLY_A")

Checks!B5:ONLY_B

=COUNTIF(ResultTable[Result],"ONLY_B")

Checks!B6:A_HOLD

=COUNTIFS(ResultTable[Side],"A",ResultTable[Result],"HOLD*")

Checks!B7:B_HOLD

=COUNTIFS(ResultTable[Side],"B",ResultTable[Result],"HOLD*")

Checks!B8:A_ROWS

=ROWS(TableA[ID])

Checks!B9:B_ROWS

=ROWS(TableB[ID])
Metric この例の値
SAME 3
DIFFERENT 2
ONLY_A 1
ONLY_B 1
A_HOLD 10
B_HOLD 9
A_ROWS 16
B_ROWS 15

元行数と分類行数を照合します。Aは16=一致3+値違い2+Aのみ1+保留10。Bは15=一致参照3+値違い参照2+Bのみ1+保留9です。保留は行数なので、重複IDのユニーク件数とは違います。合計だけ合ってもキーや比較列の選択が正しいとは限りません。

抜き取り確認 期待する読み方
SAME:A2/B2 100と100、一致
EMPTY:A9/B8 両方空欄、一致
DIFF:A4/B4 300と350、値違い
CASE:A16/B15 abcとABC、値違い
DUP_A・DUP_B 左右の該当6行をすべて重複保留
700・SPACE 型/空白で保留。自動で一致にしない
TYPE・BLANK_ZERO 100数値と文字列100、空欄と0を保留
VALUE_SPACE・ERR 値の空白や#N/Aを保留
WILD*?:A14/B13 文字通りの同IDで900と900、一致

この例は保留19行が残るため、照合全体は未完了です。要確認一覧へ元行・問題・確認先を残し、解消するか、対象外とする根拠を決める工程が続きます。結果を作ったことと、すべての対象を確認し終えたことを分けて記録してください。

8.保存して閉じ、開き直して確認する

  1. 比較条件、元台帳の取得時点、未解消の要確認をRulesに残す。
  2. 名前を付けて.xlsxで保存する。CSVへ変えると、このブックの数式や複数シートを保存できないため、この完成物の保存形式には使わない。
  3. この作業用ブックを閉じ、保存した同じファイルを開き直す。
  4. Rulesの比較列、TableA/TableB/ResultTableの名前・範囲、数式バーの参照、元行、件数、要確認が保持されているか確認する。
  5. フィルターが残っていたら条件を確認する。要確認19行→Bのみ1行→全31行を確認し、必要な条件へ戻す。隠れている行を消えたデータと扱わない。

今回の実機確認では、保存前後の元表・結果・件数表の値と数式・参照が一致し、3テーブルと要確認行を保持しました。テーブルの要確認フィルターも保存再読込後に19行を保持しています。自分のブックでも同じ確認を行い、件数や条件が変わっていれば原因がわかるまで判定を進めないでください。

誤判定を避けるための確認

誤りやすい進め方 この手順での対応
同じ行番号同士を比較する IDで対応し、元行へ戻る
検索の先頭一致を重複の代表にする 左右件数を先に確認し重複は保留
Aから検索して終わる Bからも調べ、Bのみの候補を拾う
100と文字列100、空欄と0を同じにする ValueCheckで値型を分ける
空白を全部削除して照合する 診断と修正を分け、元の意味を確認
合計件数一致だけで完了する 元行の抜き取りと要確認の解消を確認
要確認が残っていても不存在を確定する 片側候補として残し、照合全体は未完了と記録する

対象外と、実データへ広げる前の注意

本文の行位置・コピー範囲はA16行/B15行の例に合わせています。行数が違う実データにそのまま貼ると、結果側へ行が反映されないなどの取りこぼしにつながります。テーブルがあることだけで、結果行の追加や性能まで自動で保証されるわけではありません。

複合キー、多対多、曖昧一致、複数比較列の同時判定、大規模データ、行追加への自動追随、元台帳を並べ替えた全パターン、日付の業務的意味や金額の許容差は対象外です。特殊空白・表記ゆれも網羅的な照合ではありません。これらを含む場合は比較条件と方法を別に設計し、小さい正解例で確認してから使います。

元台帳を並べ替えず、この固定例のまま進めてください。Resultsの元行メモと参照先がずれるような編集、行・列の挿入削除を途中で行った場合は、元行対応を確認し直す必要があります。実データを扱う前に複製・バックアップを取り、元の台帳への上書きは避けます。

まとめ

2台帳の照合は、キーと比較条件を決め、型・空白・重複を確認してから安全な1対1の値を比べます。Aだけ・Bだけ・値違い・要確認を分け、元行と件数を検算し、保存後に同じ結果を読み戻します。

要確認が残る場合の次の仕事は、元資料に戻って判断を解消することです。差分候補を得た時点で照合全体の完了を宣言せず、何を確認済みで何が未解決かを残してください。

タイトルとURLをコピーしました