結論

突合(とつごう)とは、2つ以上のデータや書類を突き合わせて、合っているか・どこが食い違うかを確かめることです。データの突合は、結果を「両方にある」「片方だけにある」「値が違う」の3つに分けて出します。

この記事でわかること

  • 読み方と意味、照合・消込・マッチングとの違い
  • SQL・pandas・コマンドで突合した実行結果
  • キーの揺れ・NULL・重複キーで外れる例と直し方

2026年11月11日更新|PostgreSQL 16.15・SQLite 3.53.1・pandas 3.0.5で実行確認

「突合しておいて」と頼まれて、読み方も手順も分からず検索した、という場面に向けた記事です。前半で言葉の意味を辞書で確かめ、後半で架空の請求データと入金データを実際に突合して、出力をそのまま載せます。この記事で実際に動かしたものは、PostgreSQL と SQLite の SQL、pandas、comm・diff・join の各コマンドです。

flowchart TD
    A[一覧A 請求] --> K[キーをそろえる]
    B[一覧B 入金] --> K
    K --> M{キーで突き合わせる}
    M -->|Aだけ| X[片方だけ]
    M -->|Bだけ| X
    M -->|両方にある| V{値は同じ?}
    V -->|はい| OK[一致]
    V -->|いいえ| NG[値が違う]

よく調べられることの答えを先にまとめます。

知りたいこと答え詳しい節
突合の読み方「とつごう」。辞書の見出しには「つきあわせ」もある突合とは
突合の意味資料やデータを並べて比べ、合っているかを確かめること突合とは
突合と照合の違いほぼ同じ意味。突合は「2つの一覧を全件比べる」場面でよく使う照合などとの違い
突合と消込の違い消込は、突合で一致した分を「済み」にしていく後の処理照合などとの違い
SQL で突合するにはFULL OUTER JOIN で3つの結果を1回で出すSQLでデータ突合
Python で突合するにはpandas の merge(indicator=True)pandasで突合
コマンドで突合するにはsort してから comm・diff・joinコマンドで突合
件数は合うのに不安件数と合計が同じでも中身が違う例がある。チェックサムも見る件数・合計・チェックサム
合うはずなのに合わない全角半角・空白・ゼロ埋め・大文字小文字・NULL・重複キーを疑うキーの揺れ

確認環境:Ubuntu 24.04.5 LTS の上で、PostgreSQL 16.15 を作業用のフォルダーに initdb し、127.0.0.1 のポートだけで起動して SQL を実行しました。SQLite は Python 3.13.18 の sqlite3 モジュール(SQLite 3.53.1)から同じ SQL を実行しています。Python は pandas 3.0.5・NumPy 2.5.3、コマンドは GNU coreutils 9.4(sort・comm・join・sha256sum・md5sum)と GNU diffutils 3.10 です。データはすべてこの記事のために作った架空のもので、実在の会社・取引とは関係がありません。10万行の計測は2コアのマシンで1回ずつ測った値です。言葉の読みと意味は、コトバンクに載っている精選版 日本国語大辞典と、Weblio に載っている実用日本語表現辞典で確かめました。SQL と pandas の決まりは、PostgreSQL・SQLite・pandas の公式ドキュメントで確かめています。表計算ソフトでの突合は扱っていません。図は mermaid-cli 11.16.0 で描画を確かめています(確認日 2026年11月11日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「突合とは|読み方・意味と照合との違い、データ突合のやり方(SQL・Python)」

突合とは(読み方は「とつごう」・意味)

この節では、突合の読み方と意味を、辞書の記載とあわせて確かめられます。

突合は「とつごう」と読み、2つ以上の資料やデータを並べて比べ、合っているかどうかを確かめることを指します。 実用日本語表現辞典(Weblio)は、読み方を「とつごう・つきあわせ」とし、意味を「資料などを並べて比較対照し、異同や整合性などを確認すること」と説明しています。

一方、精選版 日本国語大辞典(コトバンク)では、「突合」は「つきあわせ」の見出しで載っていて、意味の1つに「両方を照らし合わせること」が挙げられています。つまり、もともとの和語は「突き合わせ(つきあわせ)」で、それを漢字2文字で書いた「突合」を音読みしたものが「とつごう」です。仕事の会話や資料では「とつごう」と読むのが一般的で、今回確かめた2つの辞書に「とっこう」「とつあい」という読みは載っていませんでした。

項目内容
読み方とつごう(辞書の見出しには「つきあわせ」もある)
意味資料・データを並べて比べ、同じか違うか、つじつまが合うかを確かめること
動詞の形突合する(とつごうする)/突き合わせる
よく一緒に使う語突合作業・突合結果・突合キー・不一致・差異
英語reconciliation、matching と訳されることが多い

ふだんの事務の言葉としての突合と、IT・データの仕事での突合は、やっていることは同じですが、対象と道具が違います。

事務・経理での突合IT・データでの突合
比べるもの請求書と入金の記録、発注書と納品書と請求書2つの表・CSV・ログ、移行前と移行後のデータ
手がかり請求番号・取引先名・金額キー(主キー・ID・コード)
道具目で見比べる、表計算ソフトSQL・Python・コマンド
出したい結果合わないものの一覧両方にある/片方だけ/値が違う の3つ

1読みは「とつごう」

辞書の見出しは「つきあわせ」。会話では「とつごう」が通じやすい。

2目的は食い違いを見つけること

合っている分を確かめるだけでなく、合わない分を一覧にして初めて終わる。

3キーが要る

どの行とどの行を比べるかを決める列(請求番号など)がなければ、突合は始められない。

突合と照合・突き合わせ・消込・マッチング・検証の違い

この節では、突合と似た言葉をどう使い分けるかが、1つの表で分かります。

突合と照合は、ほぼ同じ意味で使われます。 辞書の説明でも、突合は「照らし合わせること」と書かれていて、はっきりした線引きはありません。そのうえで、仕事でよく見る使い分けを整理すると次のようになります。会社やチームで言い方が決まっているときは、そちらに合わせるのが確実です。

言葉読みよく使う場面突合との関係
突合とつごう2つの一覧を全件比べ、食い違いを洗い出す―
突き合わせつきあわせ突合と同じ。話し言葉でやわらかい同じ意味の和語
照合しょうごう基準になるものと比べて確かめる(本人確認・指紋・パスワードなど)ほぼ同じ。1件ずつ確かめる場面に多い
消込けしこみ入金と請求を突き合わせ、一致した請求を「済み」にする突合のあとに行う処理
マッチング―条件に合う相手どうしを結び付ける。あいまいな一致も含む突合の「結び付ける」部分
検証けんしょう正しいかどうかを確かめること全般突合は検証の方法の1つ

突合と言うことが多い

請求の一覧と入金の一覧を全件比べる

移行前と移行後のデータを比べる

発注書・納品書・請求書の3つを比べる(三点突合)

照合と言うことが多い

入力されたパスワードを登録済みの値と比べる

届いた荷物を伝票と比べる

データベースの文字の比べ方の決まり(照合順序)

三点突合は、発注書・納品書・請求書の3つの書類で、品目・数量・金額が同じかを確かめる作業の呼び名です。2つの一覧の突合を、組み合わせを変えて行うと考えると分かりやすくなります。

突合を使う場面(ビジネスとIT)と例文

この節では、突合という言葉がどんな仕事で出てくるのかと、そのまま使える例文が分かります。

「同じはずの2つ」があるところには、突合があります。 片方は自分たちの記録、もう片方は別の仕組みや相手から来た記録、という組み合わせが典型です。

場面突き合わせるものキーの例見つけたいもの
請求と入金請求の一覧と、銀行の入金の一覧請求番号・金額未入金・金額の違い・身に覚えのない入金
在庫帳簿の在庫数と、数えた在庫数品目コード数の差
データ移行移行前の表と、移行後の表主キー移っていない行・値が変わった行
ログ送った側のログと、受けた側のログ処理番号・時刻届いていない処理・二重の処理
マスタの件数本番と控え、システムAとシステムBコード登録もれ・件数の差

例文です。

  • 請求データと入金データを突合し、差異を一覧にしました。
  • 移行前後で件数を突合したところ、3件の不一致が見つかりました。
  • 商品マスタと注文ログを突合して、マスタに登録のないコードを洗い出します。
  • 突合の結果、片方にしかないデータが2件ありました。原因を調べています。
  • 突合キーは請求番号とし、金額が1円でも違えば不一致として扱います。

例文の最後のように、「何をキーにするか」「どこまで同じなら一致とするか」を先に決めて書いておくと、あとで結果の読み方がぶれません。

データ突合のやり方の全体像(3つの結果と突合キー)

この節では、道具が何であっても共通する、データ突合の手順と出すべき結果が分かります。

データ突合の結果は、「両方にあって値も同じ」「両方にあるが値が違う」「片方だけにある」に分かれます。 片方だけは、どちらにあるかで2つに分かれるので、数えると4通りです。この記事では、次の架空のデータを最後まで使います。請求が6件、入金が5件です。

seikyu(請求)                       nyukin(入金)
invoice_no  customer      amount     invoice_no  amount
INV-001     青葉商事      120000     INV-001     120000
INV-002     北斗工業       88000     INV-002      80000   ← 金額が違う
INV-003     みなと物産     45000     INV-003      45000
INV-004     山吹電機      230000     (なし)             ← 請求だけ
INV-005     東雲システム   66000     INV-005      66000
INV-006     若葉運輸       15000     (なし)             ← 請求だけ
(なし)                             INV-007      30000   ← 入金だけ
3件一致INV-001・003・005
1件値が違うINV-002(88000 と 80000)
3件片方だけ請求だけ2件・入金だけ1件

どの道具でも、この「3・1・2・1」が出れば正しく突合できています。手順は次のとおりです。

1

突合キーを決める

行を1つに決められる列を選ぶ。ここでは請求番号(invoice_no)。1つの列で決まらなければ、複数の列を組み合わせる。

キー

2

キーが重複していないか数える

同じキーが2行あると、突き合わせた結果の行が増える。先に数えて、あれば集計してから比べる。

重複

3

キーの書き方をそろえる

全角と半角、前後の空白、ゼロ埋め、大文字と小文字をそろえる。そろえる前の値も残しておく。

正規化

4

件数と合計を比べる

全体が合っているかを最初に見る。合わなければ、どのくらい違うかの見当がつく。

全体

5

キーで突き合わせ、3つの結果に分ける

一致・値が違う・片方だけを出し、それぞれの件数を数える。件数の合計が、元の行数と合うかも確かめる。

明細

6

合わなかった分の原因を調べる

本当の食い違いか、書き方の揺れかを分ける。揺れなら手順3に戻って決まりを足す。

原因

SQLでデータ突合:FULL OUTER JOIN(SQLiteとPostgreSQL)

この節では、2つの表を SQL で突合して、3つの結果を1回の問い合わせで出す書き方が分かります。

FULL OUTER JOIN(完全外部結合)は、両方の表の行をすべて残して結び付けるので、突合にそのまま使えます。 相手がいない側の列は NULL(値がない印)になるため、NULL かどうかで「片方だけ」を見分けます。

SELECT COALESCE(s.invoice_no, n.invoice_no) AS invoice_no,
       s.amount AS seikyu, n.amount AS nyukin,
       CASE WHEN n.invoice_no IS NULL THEN '請求だけ'
            WHEN s.invoice_no IS NULL THEN '入金だけ'
            WHEN s.amount <> n.amount THEN '値が違う'
            ELSE '一致' END AS kekka
FROM seikyu s FULL OUTER JOIN nyukin n ON s.invoice_no = n.invoice_no
ORDER BY 1;

PostgreSQL 16.15 と SQLite 3.53.1 で同じ SQL を実行し、同じ結果になりました。

invoice_no|seikyu|nyukin|kekka
INV-001|120000|120000|一致
INV-002|88000|80000|値が違う
INV-003|45000|45000|一致
INV-004|230000||請求だけ
INV-005|66000|66000|一致
INV-006|15000||請求だけ
INV-007||30000|入金だけ
(7 rows)

結果ごとの件数は、外側を GROUP BY で包むと出せます。

SELECT kekka, COUNT(*) AS kensu FROM (
SELECT CASE WHEN n.invoice_no IS NULL THEN '請求だけ'
            WHEN s.invoice_no IS NULL THEN '入金だけ'
            WHEN s.amount <> n.amount THEN '値が違う'
            ELSE '一致' END AS kekka
FROM seikyu s FULL OUTER JOIN nyukin n ON s.invoice_no = n.invoice_no) t
GROUP BY kekka ORDER BY kekka;
kekka|kensu
一致|3
値が違う|1
入金だけ|1
請求だけ|2
  • CASE の順番が大切です。 先に「相手がいない」を判定し、そのあとで値を比べます。逆にすると、NULL との比較になって判定がずれます。
  • COALESCE(s.invoice_no, n.invoice_no) は、左から見て最初の NULL でない値を返します。入金だけの行でも請求番号を表示するために使っています。
  • SQLite が FULL OUTER JOIN に対応したのは 3.39.0(2022年6月25日)です。それより前の版の SQLite や、FULL OUTER JOIN のないデータベースでは、LEFT JOIN を2回書いて UNION でつなぐ形にします。

結合の種類ごとの違いは関連記事「SQLのJOINの違い|INNER JOINとLEFT JOINの使い分け」で、FULL OUTER JOIN の使えないデータベースでの書き方は関連記事「SQL CROSS JOIN・FULL OUTER JOINとは|例とMySQL代替」で扱っています。

EXCEPTとNOT EXISTSで「片方だけ」を出す

この節では、「こちらにあって、あちらにない行」だけを出したいときの2つの書き方と、結果の違いが分かります。

EXCEPT は「上の結果から、下の結果にある行を除く」演算です。 行のすべての列を比べるので、キーが同じでも金額が違えば残ります。

SELECT invoice_no, amount FROM seikyu
EXCEPT
SELECT invoice_no, amount FROM nyukin
ORDER BY 1;
invoice_no|amount
INV-002|88000
INV-004|230000
INV-006|15000

上下を入れ替えると、入金の側から見た結果になります。EXCEPT は片方向なので、突合では必ず両方向を実行します。

invoice_no|amount
INV-002|80000
INV-007|30000

「キーが相手にない行」だけを、ほかの列も付けて出したいときは NOT EXISTS を使います。

SELECT s.invoice_no, s.customer, s.amount
FROM seikyu s
WHERE NOT EXISTS (SELECT 1 FROM nyukin n WHERE n.invoice_no = s.invoice_no)
ORDER BY 1;
invoice_no|customer|amount
INV-004|山吹電機|230000
INV-006|若葉運輸|15000
書き方出るもの向いている場面
FULL OUTER JOIN一致・値が違う・片方だけを1回で突合の結果を一覧にしたい
EXCEPT(両方向)行の中身が1列でも違う行列が多く、全列が同じかを確かめたい
NOT EXISTSキーが相手にない行未入金・登録もれだけを出したい

SQLite と PostgreSQL の書き方の違い

同じ SQL を2つのデータベースで実行して、違いが出たところです。

書き方PostgreSQL 16.15SQLite 3.53.1
FULL OUTER JOIN使える使える(3.39.0 から)
EXCEPT使える使える
EXCEPT ALL使えるnear "ALL": syntax error
a IS DISTINCT FROM b使える使える(3.39.0 から)
a IS NOT b(値どうし)syntax error at or near "b"使える
文字の列 = 整数の列operator does not exist: text = integerエラーにならず、00123 と 123 が一致した

EXCEPT は重複した行を1つにまとめてから引きます。同じ行が2回あることも見分けたいときは、PostgreSQL では EXCEPT ALL が使えます。SQLite では、行ごとの件数を GROUP BY で数えてから比べます。

Python(pandas)で突合:merge(indicator=True)とcompare

この節では、CSV などの2つの表を pandas で突合する書き方と、出力の読み方が分かります。

pandas の merge に how="outer" と indicator=True を付けると、行がどちらにあったかを示す _merge 列が付きます。 値は both(両方)・left_only(左だけ)・right_only(右だけ)の3つです。

import pandas as pd
s = pd.read_csv("seikyu.csv", dtype={"invoice_no": "string"})
n = pd.read_csv("nyukin.csv", dtype={"invoice_no": "string"})
m = s.merge(n, on="invoice_no", how="outer", suffixes=("_seikyu", "_nyukin"),
            indicator=True, validate="one_to_one")
print(m[["invoice_no", "amount_seikyu", "amount_nyukin", "_merge"]])
print()
print(m["_merge"].value_counts())
both = m[m["_merge"] == "both"]
print()
print(both[both["amount_seikyu"] != both["amount_nyukin"]][["invoice_no", "amount_seikyu", "amount_nyukin"]])
  invoice_no  amount_seikyu  amount_nyukin      _merge
0    INV-001       120000.0       120000.0        both
1    INV-002        88000.0        80000.0        both
2    INV-003        45000.0        45000.0        both
3    INV-004       230000.0            NaN   left_only
4    INV-005        66000.0        66000.0        both
5    INV-006        15000.0            NaN   left_only
6    INV-007            NaN        30000.0  right_only

_merge
both          4
left_only     2
right_only    1
Name: count, dtype: int64

  invoice_no  amount_seikyu  amount_nyukin
1    INV-002        88000.0        80000.0
  • both の4件のうち、金額が違うのは1件でした。一致3・値が違う1・請求だけ2・入金だけ1で、SQL の結果と同じです。
  • 相手のいない行に NaN(値がない印)が入るため、金額の列が小数(120000.0)に変わっています。整数のまま扱いたいときは、読み込むときに dtype={"amount": "Int64"} を指定します。
  • validate="one_to_one" は、キーが両方の表で重複していないことを確かめる指定です。重複があるとエラーで止まるので、突合では付けておくと安心です。
  • 請求番号を dtype="string" で読んでいるのは、00123 のような値が数値に変わって先頭のゼロが消えるのを防ぐためです。

同じ形の2つの表は compare で比べる

DataFrame.compare は、行と列の並びが同じ2つの表を比べて、違うセルだけを出します。

import pandas as pd
old = pd.DataFrame({"code": ["A1", "A2", "A3"], "name": ["ねじA", "ねじB", "ねじC"], "price": [100, 250, 80]}).set_index("code")
new = pd.DataFrame({"code": ["A1", "A2", "A3"], "name": ["ねじA", "ねじB", "ねじC"], "price": [100, 250, 85]}).set_index("code")
print(old.compare(new, result_names=("old", "new")))
print()
new2 = new.drop(index="A3")
try:
    old.compare(new2)
except ValueError as e:
    print("ValueError:", e)
     name      price
      old  new   old   new
code
A2    ねじB  ねじB   NaN   NaN
A3    NaN  NaN  80.0  85.0

ValueError: Can only compare identically-labeled (both index and columns) DataFrame objects

A2 は名前の「B」が全角に変わったこと、A3 は価格が 80 から 85 に変わったことが分かります。同じところは NaN で表示されます。下のエラーは、片方の表から1行を除いて比べたときのものです。compare は行や列が増減した表には使えないので、先に merge で「片方だけ」を洗い出し、両方にある行どうしを compare で比べます。

10万行を突合したときの時間

10万行の表と、そこから1%の行を抜き、0.5%の値を変え、500行を足した表(99,511行)を突合しました。結果はどの方法でも「片方だけ 989行と500行、値が違う 485行」で一致しています。

37mspandas の mergeouter・indicator・validate つき
83msPostgreSQLFULL OUTER JOIN・索引なし
160msSQLite(索引あり)キーの列に索引を作ったとき

SQLite で、キーの列に索引を作らずに同じ FULL OUTER JOIN を実行すると、25秒待っても終わらなかったため打ち切りました。実行計画は両方の表の全件読み(SCAN)でした。SQLite で大きな表を突合するときは、先にキーの列に索引を作ります。

コマンドで突合:sort+comm・diff・join

この節では、データベースや Python を使わずに、Linux や Mac のコマンドだけでテキストファイルを突合する方法が分かります。

comm は、並べ替え済みの2つのファイルを1行ずつ比べて、「1つ目だけ」「2つ目だけ」「両方」の3列に分けるコマンドです。 -1・-2・-3 は、その列を出さない指定です。まず、CSV からキーの列だけを取り出して並べ替えます。

export LC_ALL=C
tail -n +2 seikyu.csv | cut -d, -f1 | sort > s_keys.txt
tail -n +2 nyukin.csv | cut -d, -f1 | sort > n_keys.txt
$ comm -12 s_keys.txt n_keys.txt     # 両方にある
INV-001
INV-002
INV-003
INV-005
$ comm -23 s_keys.txt n_keys.txt     # 請求だけ
INV-004
INV-006
$ comm -13 s_keys.txt n_keys.txt     # 入金だけ
INV-007

値の違いまで見るには、キーと金額を並べた2つのファイルを diff で比べます。

$ diff -u s_kv.txt n_kv.txt
--- s_kv.txt
+++ n_kv.txt
@@ -1,6 +1,5 @@
 INV-001,120000
-INV-002,88000
+INV-002,80000
 INV-003,45000
-INV-004,230000
 INV-005,66000
-INV-006,15000
+INV-007,30000

diff は行の単位で比べるので、「値が違う」のか「片方だけ」なのかは、出力を見て自分で読み分けることになります。キーで結び付けて横に並べたいときは join を使います。-a1 -a2 は相手のいない行も出す指定、-e は空の欄に入れる文字、-o は出す列の指定です。

$ join -t, -a1 -a2 -e '-' -o 0,1.2,2.2 s_kv.txt n_kv.txt
INV-001,120000,120000
INV-002,88000,80000
INV-003,45000,45000
INV-004,230000,-
INV-005,66000,66000
INV-006,15000,-
INV-007,-,30000

この出力に awk で判定の列を足すと、SQL と同じ一覧になりました。

$ join -t, -a1 -a2 -e '-' -o 0,1.2,2.2 s_kv.txt n_kv.txt \
    | awk -F, '{r=($2=="-")?"入金だけ":($3=="-")?"請求だけ":($2==$3)?"一致":"値が違う"; print $0","r}'
INV-001,120000,120000,一致
INV-002,88000,80000,値が違う
INV-003,45000,45000,一致
INV-004,230000,-,請求だけ
INV-005,66000,66000,一致
INV-006,15000,-,請求だけ
INV-007,-,30000,入金だけ

comm と join は、並べ替えていないファイルを渡すとエラーになります。 並べ替えを逆順にしたファイルで試したときの文言です。

$ comm -12 s_keys.txt n_rev.txt
comm: file 2 is not in sorted order
comm: input is not in sorted order

$ join -t, s_kv.txt n_kv_rev.txt
join: n_kv_rev.txt:2: is not sorted: INV-005,66000
join: input is not in sorted order

sort の並び順は言語の設定(ロケール)で変わるため、sort・comm・join を同じ LC_ALL=C の下で実行すると順序の食い違いを防げます。diff の出力の読み方は関連記事「diffコマンドの使い方|-uの読み方・patch適用・オプション一覧」で扱っています。

件数・合計・チェックサムで突合する

この節では、明細を1行ずつ比べる前に全体が合っているかを確かめる方法と、その限界が分かります。

件数と合計の突合は速くて分かりやすい反面、「合っていれば中身も同じ」とは言えません。 請求と入金の例では、件数も合計も違うので、食い違いがあることがすぐ分かります。

SELECT 'seikyu' AS hyo, COUNT(*) AS kensu, SUM(amount) AS gokei FROM seikyu
UNION ALL
SELECT 'nyukin', COUNT(*), SUM(amount) FROM nyukin;
hyo|kensu|gokei
seikyu|6|564000
nyukin|5|341000

次は、件数も合計も同じなのに中身が違う例です。移行前(old.csv)の ID-1 が 500 多く、ID-2 が 500 少なく移ったと考えてください。

old.csv            new.csv
ID-1,1000          ID-1,1500
ID-2,2000          ID-2,1500
ID-3,3000          ID-3,3000

old.csv 件数=3 合計=6000
new.csv 件数=3 合計=6000

$ sha256sum old.csv new.csv
15c6063c090af1c189bf7b02db25e66999cf85ef1a0d0071074b65455f15d3ae  old.csv
fda37535c0dff1b20d763899c58489f23728f3a1d2553db4670154ad98fc240e  new.csv

件数も合計も 3件・6000 で一致しましたが、チェックサム(中身から計算した短い値。1文字でも違えば変わる)は別の値になりました。増えた分と減った分が打ち消し合うと、合計の突合では見つけられません。

ただし、チェックサムは中身がまったく同じときにしか一致しません。行の順番や改行の種類が違うだけでも変わります。

比べたものチェックサム読み取れること
old.csv と、行の順番だけ入れ替えたファイル一致しない順番が違うだけで変わる
両方を sort してから計算一致した並べ替えてから比べる
old.csv と、改行を CRLF にしたファイル一致しない改行の種類でも変わる
CRLF のファイルから CR を除いて計算一致した改行をそろえてから比べる

データベースの中身とファイルを比べるときも、同じ形の文字列にそろえれば突合できます。PostgreSQL で請求の表を「請求番号,金額」の行にして順に並べ、md5 を計算した値は、CSV から同じ形を作って md5sum で計算した値と一致しました。

-- PostgreSQL
SELECT COUNT(*), SUM(amount),
       md5(string_agg(invoice_no || ',' || amount, E'\n' ORDER BY invoice_no))
FROM seikyu;
6|564000|cbeac45c00176541421ae556e37c2e03

$ tail -n +2 seikyu.csv | cut -d, -f1,3 | sort | head -c -1 | md5sum
cbeac45c00176541421ae556e37c2e03  -
  1. 1件数を比べる行の増減が分かる。いちばん速い
  2. 2合計を比べる数値の列のずれが分かる。打ち消し合うと見えない
  3. 3チェックサムを比べる同じか違うかが確実に分かる。どこが違うかは分からない
  4. 4キーで明細を突合するどの行のどの値が違うかまで分かる

上から順に進め、合わなかった段階で下の段に進むと、手間を小さくできます。移行のときに件数を確かめる SQL の書き方は関連記事「データ移行のマッピング定義書の書き方|項目テンプレートと件数検証SQL」で扱っています。

キーの揺れで突合が外れる例と直し方(全角半角・空白・ゼロ埋め)

この節では、同じはずのキーが一致しない原因と、書き方をそろえて一致させる手順が分かります。

突合で「合うはずなのに合わない」ときは、データの食い違いではなく、キーの書き方の違いであることがよくあります。 入金の側の請求番号を、わざと5通りに崩して突合しました。

入金の側のキー崩し方そのまま突合そろえたあと
inv-001小文字一致しないINV-001
␣INV-003␣前後に半角の空白一致しないINV-003
INV-005+全角の空白後ろに全角の空白一致しないINV-005
INV-004全角の英数字と記号一致しないINV-004
INV-2ゼロ埋めなし一致しないINV-002
import unicodedata, re

def norm(key):
    k = unicodedata.normalize("NFKC", key).strip().upper()   # 全角→半角、前後の空白、大文字
    m = re.fullmatch(r"INV-(\d+)", k)
    return f"INV-{int(m.group(1)):03d}" if m else k           # 数字を3桁にゼロ埋め

n3["invoice_no"] = n3["invoice_no"].map(norm)
'inv-001' -> 'INV-001'
' INV-003 ' -> 'INV-003'
'INV-005\u3000' -> 'INV-005'
'INV-004' -> 'INV-004'
'INV-2' -> 'INV-002'
そのまま: 0 件が一致 / 正規化後: 5 件が一致

そのままでは5件とも一致せず、そろえたあとは5件とも一致しました。NFKC は Unicode の正規化の1つで、全角の英数字や記号、全角の空白を半角にそろえます。

SQL でそろえる場合は、UPPER(TRIM(列)) が手軽ですが、効く範囲に限りがあります。同じデータを PostgreSQL と SQLite で試すと、どちらも次の結果でした。

結合の条件一致した件数残ったもの
s.invoice_no = n.invoice_no0件すべて
s.invoice_no = UPPER(TRIM(n.invoice_no))2件全角の空白・ゼロ埋めなし

TRIM は、PostgreSQL でも SQLite でも全角の空白を取りませんでした。 TRIM('INV-005 ') の長さは8文字のままです。全角が混ざるデータは、取り込む前に Python などでそろえるか、取り除く文字を明示して指定します。

ゼロ埋めと型の違い

移行前はコードが文字(00123)、移行後は整数(123)という表を突合しました。

-- PostgreSQL
ERROR:  operator does not exist: text = integer
HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.

-- SQLite(エラーにならず一致した)
old_code|new_code
00123|123
00456|456

PostgreSQL は型が違うと止まるので、CAST(o.code AS INTEGER) = n.code のように、どちらにそろえるかを自分で書きます。SQLite は自動で数値として比べたため一致しました。便利ですが、00123 と 123 を別のコードとして扱いたいデータでは、意図しない一致になります。

キーをそろえるとき

避ける元の列をそろえた値で上書きする。あとで元の値を確かめられなくなる
→
代わりにそろえた値を別の列に入れて突合する。元の値は残す

そろえた決まり(何を何に変えたか)も結果と一緒に残す

名前で突合したくなったとき

避ける取引先名のような、書き方が人によって変わる列をキーにする
→
代わりに番号やコードをキーにする。名前は結果を目で確かめる補助に使う

番号がないデータは、先に番号を付ける決まりを作る

NULLや重複キーがあるときの突合

この節では、値のない欄(NULL)と、同じキーが複数ある表で、突合の結果がどう狂うかと直し方が分かります。

NULL があると「値が違う」を見落とす

SQL では、NULL との比較の結果は真でも偽でもなく NULL になります。 PostgreSQL の公式ドキュメントも、ふつうの比較演算子はどちらかの入力が NULL のとき NULL を返す、と説明しています。そのため a.v <> b.v だけで比べると、片方が NULL の行が「一致」に入ってしまいます。

SELECT a.k, a.v AS a_v, b.v AS b_v,
       CASE WHEN a.v <> b.v THEN '値が違う' ELSE '一致' END AS futsu,
       CASE WHEN a.v IS DISTINCT FROM b.v THEN '値が違う' ELSE '一致' END AS null_taio
FROM a JOIN b ON a.k = b.k ORDER BY 1;
k|a_v|b_v|futsu|null_taio
K1|100|100|一致|一致
K2||200|一致|値が違う
K3|||一致|一致
K4|300||一致|値が違う

K2 と K4 は片方にしか値がないのに、<> では「一致」になりました。IS DISTINCT FROM は NULL を「比べられる値」として扱う比較で、片方だけ NULL なら違う、両方 NULL なら同じ、と判定します。PostgreSQL と SQLite(3.39.0 から)の両方で同じ結果でした。EXCEPT も NULL どうしを同じとみなすので、同じデータで K2 と K4 の2行が出ました。

pandas は向きが逆です。値がない印の NaN どうしを != で比べると「違う」になるため、今度は両方とも空の行が不一致に入ります。

    k    v_a    v_b     ne  chigau
0  K1  100.0  100.0  False   False
1  K2    NaN  200.0   True    True
2  K3    NaN    NaN   True   False
3  K4  300.0    NaN   True    True

ne は v_a != v_b、chigau は「等しい、または両方とも空」を除いた判定です。K3 の扱いが違います。また、pandas の公式ドキュメントは、キーの列が両方とも空の行どうしは結び付けられ、これは SQL の結合と違う動きだ、と注意しています。手元でも、キーが None の行どうしが結び付きました。キーが空の行は、突合の前に取り除いて別に数えます。 SQL の NULL の決まりは関連記事「SQLのNULLの扱い|COALESCE・NULLIFと落とし穴12選」で扱っています。

重複キーがあると行が増える

請求 INV-001(120000)に対して、入金が 60000 ずつ2回に分かれて届いた、という表で突合しました。

invoice_no|seikyu|nyukin|kekka
INV-001|120000|60000|値が違う
INV-001|120000|60000|値が違う
INV-002|88000|88000|一致
INV-003|45000|45000|一致

請求は3件なのに結果は4行になり、INV-001 は2行とも「値が違う」と出ました。まず重複を数え、キーごとに合計してから突合します。

SELECT invoice_no, COUNT(*) AS kensu FROM nyukin2 GROUP BY invoice_no HAVING COUNT(*) > 1;

SELECT s.invoice_no, s.amount AS seikyu, n.gokei AS nyukin, n.kensu,
       CASE WHEN s.amount <> n.gokei THEN '値が違う' ELSE '一致' END AS kekka
FROM seikyu s
JOIN (SELECT invoice_no, SUM(amount) AS gokei, COUNT(*) AS kensu
      FROM nyukin2 GROUP BY invoice_no) n ON s.invoice_no = n.invoice_no
ORDER BY 1;
invoice_no|kensu
INV-001|2

invoice_no|seikyu|nyukin|kensu|kekka
INV-001|120000|120000|2|一致
INV-002|88000|88000|1|一致
INV-003|45000|45000|1|一致

pandas では、validate="one_to_one" を付けておくと、重複があった時点で次のエラーで止まります。気づかないまま行が増えるのを防げます。

MergeError: Merge keys are not unique in right dataset; not a one-to-one merge
Duplicates in right:
 invoice_no
   INV-001 ...

重複した行の見つけ方と、1件だけ残す書き方は関連記事「SQLで重複データを抽出・削除する方法|1件だけ残す書き方」で扱っています。

突合の方法の選び方(判断表)とつまずき

この節では、手元のデータに合った突合の方法の選び方と、うまくいかないときの原因と直し方が分かります。

データがどこにあるかで選ぶのが、いちばん手間がかかりません。 データベースの中にあるなら SQL、ファイルなら pandas かコマンドです。

手元の状況向いている方法理由
2つの表が同じデータベースにあるFULL OUTER JOIN取り出さずに3つの結果を出せる
列が多く、全部同じかを確かめたいEXCEPT を両方向列を1つずつ書かずに済む
CSV や表計算ソフトから書き出したファイルpandas の mergeキーをそろえる処理と突合を1つのスクリプトに書ける
キーだけのテキスト、サーバーの上でさっと確かめたいsort と comm追加の道具がいらない
別々のデータベース、別々のサーバーにある件数・合計・チェックサムデータを運ばずに全体を比べられる
行と列の並びが同じ2つの表pandas の compare違うセルだけが出る
文章や設定ファイルの違いを見たいdiff行の単位で前後が分かる

つまずきやすい点です。文言は、この記事で実際に出したものです。

症状・文言原因直し方
合うはずの行が「片方だけ」に出るキーの全角半角・空白・ゼロ埋め・大文字小文字の違いそろえた値を別の列に作って突合する
値が違うのに「一致」になるNULL を <> で比べているIS DISTINCT FROM を使う
結果の行数が元より多いキーが重複している重複を数え、キーごとに集計してから突合する
operator does not exist: text = integerPostgreSQL で型の違う列を比べたCAST でどちらかにそろえる
near "ALL": syntax errorSQLite で EXCEPT ALL を書いたGROUP BY で件数を数えてから比べる
comm: file 2 is not in sorted order並べ替えていない同じロケールで sort してから渡す
MergeError: Merge keys are not uniquevalidate を付けた merge でキーが重複重複を集計するか、指定を見直す
Can only compare identically-labeledcompare に行や列の違う表を渡した先に merge で両方にある行だけにする
金額が 120000.0 と表示されるNaN が入って列が小数になったInt64 で読み込む

突合を終える前の確認です。

  • 1突合キーと「一致」の決まりを書き残した何の列をキーにし、どこまで同じなら一致としたか
  • 2キーの重複と空の行を数えた重複は集計し、空の行は別に数える
  • 3結果の件数を足すと元の行数に合う請求6件=一致3+値が違う1+請求だけ2
  • 4両方向で確かめたEXCEPT や NOT EXISTS は片方向。入れ替えてもう一度実行する
  • 5合わなかった分の一覧を残した件数だけでなく、どの行かが分かる形で

よくある質問

Q. 突合の読み方は「とつごう」ですか、「つきあわせ」ですか? A. 仕事の会話では「とつごう」が一般的です。実用日本語表現辞典は読みを「とつごう・つきあわせ」としていて、精選版 日本国語大辞典は「つきあわせ」の見出しで載せています。どちらも誤りではありません。

Q. 突合とは、簡単に言うと何ですか? A. 2つ以上のデータや書類を突き合わせて、合っているか、どこが食い違うかを確かめることです。請求の一覧と入金の一覧を比べる、移行前と移行後のデータを比べる、といった作業を指します。

Q. 突合と照合の違いは何ですか? A. ほぼ同じ意味で、はっきりした線引きはありません。2つの一覧を全件比べて食い違いを洗い出す場面では突合、基準になるものと比べて1件ずつ確かめる場面では照合と言うことが多いです。

Q. 突合と消込の違いは何ですか? A. 消込は、請求と入金を突き合わせて、一致した請求を「済み」にしていく処理です。突合で一致を確かめ、そのあとに消込を行う、という順番になります。

Q. 「突合する」は正しい言い方ですか? A. 業務の用語として広く使われています。相手になじみがなさそうなときは、「突き合わせる」「照らし合わせる」と言い換えると伝わりやすくなります。

Q. SQL で2つのテーブルを突合するには、どう書きますか? A. FULL OUTER JOIN でキーを結び付け、相手の側が NULL なら「片方だけ」、両方にあれば値を比べます。この記事では PostgreSQL と SQLite で同じ SQL を実行し、同じ結果になりました。片方だけを出すなら NOT EXISTS、全列の違いを出すなら EXCEPT が使えます。

Q. pandas で突合するには、どう書きますか? A. merge(how="outer", indicator=True) を使います。_merge 列の both・left_only・right_only で、どちらにあった行かが分かります。both の行について値の列を比べると、値の違いが出せます。

Q. 件数が合っていれば、突合は終わりにしてよいですか? A. 件数と合計が同じでも中身が違う例を、この記事で実際に作りました。件数と合計は最初の確認として使い、大事なデータはチェックサムかキーごとの突合まで進めるのが確実です。

Q. 合うはずのデータが一致しません。何を見ればよいですか? A. キーの全角と半角、前後の空白、ゼロ埋め、大文字と小文字を確かめます。次に、キーの重複と、空の値(NULL)を数えます。この記事の例では、書き方をそろえるだけで 0件から5件の一致に変わりました。

Q. 三点突合とは何ですか? A. 発注書・納品書・請求書の3つの書類で、品目・数量・金額が同じかを確かめる作業です。考え方は2つの一覧の突合と同じで、組み合わせを変えて比べます。

Q. 突合は英語で何と言いますか? A. reconciliation や matching と訳されることが多いです。会計の文脈では reconciliation、データの行を結び付ける意味では matching がよく使われます。

まとめ

  • 突合は「とつごう」と読み、2つ以上のデータや書類を突き合わせて、合っているか・どこが食い違うかを確かめること。辞書の見出しは「つきあわせ」。
  • 照合とはほぼ同じ意味。消込は突合のあとの処理、マッチングは結び付ける部分、検証は突合を含む広い言葉。
  • データ突合の結果は「一致・値が違う・片方だけ」。SQL は FULL OUTER JOIN、pandas は merge(indicator=True)、コマンドは sort と comm・join で、同じ「3・1・2・1」が出た。
  • 件数と合計が同じでも中身が違うことがある。チェックサムは同じか違うかだけが分かり、順番や改行の違いでも変わる。
  • 合わないときは、キーの揺れ・NULL・重複キーを疑う。NULL は IS DISTINCT FROM、重複は集計、揺れはそろえた値を別の列に作って突合する。

次に読むとよい記事です。結合の基本は関連記事「SQLのJOINの違い|INNER JOINとLEFT JOINの使い分け」で、移行のときの件数の確かめ方は関連記事「データ移行のマッピング定義書の書き方|項目テンプレートと件数検証SQL」で扱っています。

参考・出典

確認日はいずれも 2026年11月11日です。

  • コトバンク「突合」(精選版 日本国語大辞典):https://kotobank.jp/word/%E7%AA%81%E5%90%88-342352
  • Weblio 辞書「突合」(実用日本語表現辞典):https://www.weblio.jp/content/%E7%AA%81%E5%90%88
  • PostgreSQL 16 Documentation「9.2. Comparison Functions and Operators」(IS DISTINCT FROM と NULL の比較):https://www.postgresql.org/docs/16/functions-comparison.html
  • PostgreSQL 16 Documentation「7.2. Table Expressions」(FULL OUTER JOIN):https://www.postgresql.org/docs/16/queries-table-expressions.html
  • PostgreSQL 16 Documentation「7.4. Combining Queries」(EXCEPT・EXCEPT ALL):https://www.postgresql.org/docs/16/queries-union.html
  • SQLite「Release 3.39.0」(FULL OUTER JOIN と IS DISTINCT FROM の追加):https://www.sqlite.org/releaselog/3_39_0.html
  • SQLite「Datatypes In SQLite」(型の違う値の比較):https://www.sqlite.org/datatype3.html
  • pandas「pandas.DataFrame.merge」(indicator・validate・空のキーの注意):https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.merge.html
  • pandas「pandas.DataFrame.compare」:https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.compare.html
  • GNU Coreutils「comm」「join」(手元の comm --help・join --help の表示でも確認):https://www.gnu.org/software/coreutils/manual/coreutils.html
  • Python「unicodedata」(NFKC の正規化):https://docs.python.org/ja/3/library/unicodedata.html

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「突合とは|読み方・意味と照合との違い、データ突合のやり方(SQL・Python)」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

成長のヒミツ、 ぜんぶ丸わかり