はじめに
「営業部の報告だと9月の売上は525万円。経理部の数字だと370万円」
「で、どっちが正しいの?」と聞かれて会議が止まる。
調べてみると、実はどちらも間違っていない。
データを扱う現場ではよくある話です。原因のほとんどは計算ミスではなく、「売上」という言葉の意味が部署ごとに違うことにあります。
この記事では、数字がズレる典型的な原因を5つに分けて整理します。そのうえで、SQLを使って定義をそろえ、差を説明できる状態にする方法を紹介します。
結論:数字がズレる5つの原因
| # | 原因 | よくあるズレ方 |
|---|---|---|
| 1 | 基準日が違う | 営業は受注日、経理は出荷日(計上日)で数える |
| 2 | 金額の定義が違う | 税込・税抜、値引きや送料の扱いが違う |
| 3 | キャンセル・返品の扱いが違う | 営業の表には残っていて、経理では除外されている |
| 4 | 期間の区切り方が違う | 締め日、月末日の時刻、タイムゾーンが違う |
| 5 | 集計元・集計方法が違う | 取得した時点のズレ、手作業での修正、JOINによる重複 |
目指すのは数字を1つに統一することではありません。
違う数字には違う名前をつけて、差を説明できるようにすることです。
サンプルデータ
記事中のSQLはこのデータで試せます(MySQL 8.0)。検証用のデータベースを作ってから実行してください。
CREATE DATABASE IF NOT EXISTS sales_demo;
USE sales_demo;
DROP TABLE IF EXISTS orders;
CREATE TABLE orders (
order_id INT PRIMARY KEY,
ordered_at DATETIME NOT NULL, -- 受注日時(日本時間)
shipped_on DATE NULL, -- 出荷日(未出荷・キャンセルはNULL)
customer VARCHAR(50) NOT NULL,
amount INT NOT NULL, -- 商品金額(税抜)
discount INT NOT NULL DEFAULT 0, -- 値引き(税抜)
shipping_fee INT NOT NULL DEFAULT 0, -- 送料(税抜)
status VARCHAR(20) NOT NULL -- active / cancelled
);
INSERT INTO orders VALUES
(1, '2026-08-28 10:00:00', '2026-09-02', 'A社', 1000000, 0, 1000, 'active'), -- 8月受注・9月出荷
(2, '2026-09-03 11:00:00', '2026-09-05', 'B社', 2000000, 100000, 1500, 'active'),
(3, '2026-09-10 14:00:00', '2026-09-12', 'C社', 500000, 0, 1000, 'active'),
(4, '2026-09-15 09:30:00', NULL, 'D社', 800000, 0, 1000, 'cancelled'), -- 出荷前にキャンセル
(5, '2026-09-25 16:00:00', '2026-10-02', 'E社', 1200000, 50000, 1500, 'active'), -- 9月受注・10月出荷
(6, '2026-09-30 18:45:00', '2026-09-30', 'F社', 300000, 0, 500, 'active'), -- 月末の夕方に受注・即日出荷
(7, '2026-09-28 13:00:00', NULL, 'G社', 600000, 0, 1000, 'active'); -- 未出荷
各原因の詳細
1. 基準日が違う
症状
月の途中では近い数字なのに、月をまたぐ案件があると大きくズレます。
なぜ起きるか
売上がいつ発生したと考えるかが、部署ごとに違うからです。
| 部署 | よく使う基準日 | 理由 |
|---|---|---|
| 営業部 | 受注日・契約日 | 自分の成果が確定した日 |
| 物流・現場 | 出荷日・納品日 | 作業が発生した日 |
| 経理部 | 出荷日・検収日(計上日) | 会計のルールに沿って売上を計上する日 |
| 資金繰り | 入金日 | お金が実際に入った日 |
9月25日に受注し、10月2日に出荷した案件(サンプルの order_id=5)は、営業部では9月の売上、経理部では10月の売上になります。
どう揃えるか
どの基準日も間違いではありません。「受注額」「売上高」「入金額」のように別の名前をつけ、1つの「売上」という言葉に複数の意味を持たせないようにします。
2. 金額の定義が違う
症状
件数は合っているのに、金額だけが少しずつズレます。
なぜ起きるか
同じ1件でも、「金額」に何を含めるかが違います。
- 税込か税抜か(見積書・請求書は税込、会計は税抜で見ることが多い)
- 値引きを本体価格から引くか、別の行で持つか
- 送料・手数料・オプション料金を含めるか
- 外貨建ての場合、いつの為替レートで換算するか
どう揃えるか
後述の定義表に「税」「値引き」「送料」の列を作り、1つずつ埋めていきます。ここが曖昧なまま集計ロジックを作ると、あとで誰も説明できなくなります。
3. キャンセル・返品の扱いが違う
症状
過去の月の数字が、あとで見ると変わっていたり、逆に変わっていなかったりします。
なぜ起きるか
同じ「9月の売上」でも、キャンセルや返品をどう反映するかが違います。
- 営業の管理表:受注したときに1行追加し、キャンセルされても消さずに残っている
- システムの受注データ:ステータスが更新されるため、9月分を10月に集計し直すと数字が減る
- 経理:9月に計上した売上の返品は、返品のあった10月にマイナスとして計上し、9月の数字は動かさない
どう揃えるか
過去の月の数字があとから変わってよいかを決めます。経営会議で使う確定値は月次で締めて保存し(スナップショット)、日々の営業管理は最新の状態で見る、というように用途で分けると整理しやすくなります。
4. 期間の区切り方が違う
症状
月末の数件だけが合わない、または月初の数字に前月分が混ざります。
なぜ起きるか
よくある原因は次の3つです。
- 締め日が違う(取引先ごとに20日締めで請求している一方、分析は月末締めで見ている)
- SQLの書き方のせいで、月末日の時刻が抜ける
- タイムゾーンが違う(SaaSやクラウドのDBは時刻をUTCで保存していることが多く、日本時間の1日0時〜9時が前日扱いになる)
2つ目はSQLでとても起きやすいミスです。
-- ❌ 9/30 18:45 の受注(order_id=6)が漏れる
SELECT SUM(amount - discount) AS 受注額
FROM orders
WHERE ordered_at BETWEEN '2026-09-01' AND '2026-09-30';
-- => 4950000
'2026-09-30' は '2026-09-30 00:00:00' と解釈されるため、30日の0時0分より後の受注が対象から外れます。
-- ⭕ 「以上・未満」で書く
SELECT SUM(amount - discount) AS 受注額
FROM orders
WHERE ordered_at >= '2026-09-01'
AND ordered_at < '2026-10-01';
-- => 5250000
時刻をUTCで保存している場合は、CONVERT_TZ(ordered_at, '+00:00', '+09:00') で日本時間に変換してから日付を切り出します。
どう揃えるか
期間の条件は「以上・未満」の書き方に統一します。締め日とタイムゾーンは定義表に書いておきます。
5. 集計元・集計方法が違う
症状
1〜4を揃えたはずなのに、まだ合いません。
なぜ起きるか
- データを取得した時点が違う(営業は月初3日にエクスポートし、経理は5日に締め処理をしている)
- 手元のExcelで誰かが手作業で修正している(「この案件は来月扱いで」と直接書き換えた、など)
- 明細テーブルとJOINした結果、1件の受注が複数行になり、金額が重複している(分納・複数明細)
- 同じ受注が二重に登録されている
どう揃えるか
集計元を1つ(DBか、1つのシート)に決め、各部署はそこから定義済みのビューを通して数字を取るようにします。手作業での修正を禁止する必要はありませんが、修正履歴が残る場所で行います。JOINしたあとは COUNT(*) と COUNT(DISTINCT order_id) を比べて、重複がないか確認します。
元データを1か所に持つ考え方は、データベース設計の基本:正規化とテーブル設計の考え方 でも解説しています。
逆に「揃えなくていい」ケース
数字が違うこと自体は、必ずしも問題ではありません。次のような場合は、無理に揃えなくても大丈夫です。
- 差の理由を全員が説明できる(「営業は受注ベースなので、経理より1か月先行します」と一言で済む)
- 差が毎月ごくわずかで、意思決定に影響しない
- 数字を見る人が1〜2人で、同じ人が両方の数字を把握している
- 差が期ズレだけで、翌月以降に解消されることがわかっている
問題は数字が違うことではなく、なぜ違うのか誰も説明できないことです。
進め方
Step 1:会議で使う数字を3〜5個に絞る
最初から全部の指標を定義しようとすると終わりません。経営会議や部署間のやり取りで実際に使っている数字から始めます。
Step 2:定義表を作る
部署ごとに聞き取りをして、1枚の表にまとめます。
| 指標名 | 担当 | 基準日 | 税 | 値引き | 送料 | キャンセル | 期間の区切り |
|---|---|---|---|---|---|---|---|
| 受注額 | 営業部 | 受注日 | 税抜 | 控除後 | 含まない | 含む(受注時点の金額) | 月末締め・日本時間 |
| 売上高 | 経理部 | 出荷日 | 税抜 | 控除後 | 含む | 含まない | 月末締め・日本時間 |
| 入金額 | 経理部 | 入金日 | 税込 | 控除後 | 含む | 返金はマイナス | 月末締め・日本時間 |
※ この例では営業部の慣習に合わせて、受注額をキャンセル控除前にしています。どちらを選んでも構いません。大事なのは表に書いてあることです。
Step 3:名前を分けてビューにする
定義表の内容をSQLのビューにします。以後、誰がどのツールから集計しても同じ定義の数字になります。
-- 営業部「受注額」:受注日ベース・税抜・値引き後・送料除く・キャンセル含む
CREATE OR REPLACE VIEW v_order_amount AS
SELECT
order_id,
DATE(ordered_at) AS base_date,
amount - discount AS order_amount,
status
FROM orders;
-- 経理部「売上高」:出荷日ベース・税抜・値引き後・送料含む・キャンセル除く
CREATE OR REPLACE VIEW v_revenue AS
SELECT
order_id,
shipped_on AS base_date,
amount - discount + shipping_fee AS revenue
FROM orders
WHERE status = 'active'
AND shipped_on IS NOT NULL;
月ごとの集計です。書き換えるのは冒頭の CONFIG だけです。
-- ===== CONFIG =====
SET @month_start = '2026-09-01'; -- ★集計したい月の1日
SET @month_end = DATE_ADD(@month_start, INTERVAL 1 MONTH); -- 翌月1日(変更不要)
SELECT
(SELECT SUM(order_amount) FROM v_order_amount
WHERE base_date >= @month_start AND base_date < @month_end) AS 受注額,
(SELECT SUM(revenue) FROM v_revenue
WHERE base_date >= @month_start AND base_date < @month_end) AS 売上高;
-- 受注額: 5250000 / 売上高: 3704000
※ SET した変数は、同じ接続(セッション)の中でだけ有効です。phpMyAdminなどで実行するときは、SET と SELECT をまとめて実行してください。
Step 4:差異の内訳を毎月出す
ここが一番大事なステップです。受注額から売上高までの差を、項目ごとに分解します。
-- ===== CONFIG =====
SET @month_start = '2026-09-01'; -- ★集計したい月の1日
SET @month_end = DATE_ADD(@month_start, INTERVAL 1 MONTH); -- 翌月1日(変更不要)
WITH b AS (
SELECT
-- ① 当月受注の受注額
COALESCE(SUM(CASE WHEN ordered_at >= @month_start AND ordered_at < @month_end
THEN amount - discount END), 0) AS order_amount,
-- ② 当月受注のうちキャンセル
COALESCE(SUM(CASE WHEN ordered_at >= @month_start AND ordered_at < @month_end
AND status = 'cancelled'
THEN amount - discount END), 0) AS cancelled,
-- ③ 当月受注のうち、翌月以降に出荷(未出荷を含む)
COALESCE(SUM(CASE WHEN ordered_at >= @month_start AND ordered_at < @month_end
AND status = 'active'
AND (shipped_on IS NULL OR shipped_on >= @month_end)
THEN amount - discount END), 0) AS ship_next,
-- ④ 前月以前の受注で、当月に出荷
COALESCE(SUM(CASE WHEN ordered_at < @month_start
AND status = 'active'
AND shipped_on >= @month_start AND shipped_on < @month_end
THEN amount - discount END), 0) AS ship_from_prev,
-- ⑤ 当月出荷分の送料
COALESCE(SUM(CASE WHEN status = 'active'
AND shipped_on >= @month_start AND shipped_on < @month_end
THEN shipping_fee END), 0) AS shipping,
-- ⑥ 当月の売上高
COALESCE(SUM(CASE WHEN status = 'active'
AND shipped_on >= @month_start AND shipped_on < @month_end
THEN amount - discount + shipping_fee END), 0) AS revenue
FROM orders
)
SELECT '① 受注額(営業部)' AS 項目, order_amount AS 金額 FROM b
UNION ALL SELECT '② キャンセル', -cancelled FROM b
UNION ALL SELECT '③ 当月受注・翌月以降に出荷', -ship_next FROM b
UNION ALL SELECT '④ 前月以前受注・当月に出荷', ship_from_prev FROM b
UNION ALL SELECT '⑤ 送料', shipping FROM b
UNION ALL SELECT '⑥ 売上高(経理部)', revenue FROM b
UNION ALL SELECT '⑦ 説明できない差異',
revenue - (order_amount - cancelled - ship_next + ship_from_prev + shipping) FROM b;
実行結果:
| 項目 | 金額 |
|---|---|
| ① 受注額(営業部) | 5,250,000 |
| ② キャンセル | -800,000 |
| ③ 当月受注・翌月以降に出荷 | -1,750,000 |
| ④ 前月以前受注・当月に出荷 | 1,000,000 |
| ⑤ 送料 | 4,000 |
| ⑥ 売上高(経理部) | 3,704,000 |
| ⑦ 説明できない差異 | 0 |
冒頭の「525万円と370万円、どっちが正しいの?」への答えがこの表です。どちらも正しく、差の内訳はこうなっています、と示せるようになります。
⑦が0でなければ、まだ把握していない原因があります。 原因5(手作業での修正、重複、取得時点のズレ)を疑ってください。返品を別のテーブルで管理している場合は、行を1つ追加します。
運用するなら、月初にこのクエリを定期実行し、⑦が0以外なら通知を出すようにしておくと、ズレに早く気づけます。
Step 5:ダッシュボードに定義を書き添える
グラフのタイトルを「売上」ではなく、「売上高(出荷日ベース・税抜)」 のように定義込みで書きます。これだけで「どっちの数字?」という質問がかなり減ります。
Looker Studioでダッシュボードを作る手順は Looker Studioでスプレッドシートから売上ダッシュボードを作る で解説しています。ビューをデータソースにすれば、定義がそろった数字をそのまま表示できます。
まとめ
- 部署ごとに数字が合わない原因の多くは、計算ミスではなく定義の違い
- 主な原因は、基準日・金額の範囲・キャンセルの扱い・期間の区切り・集計元の5つ
- 目指すのは「1つに統一」ではなく、違う数字に違う名前をつけること
- 定義は表にまとめ、SQLのビューとして実装すると、誰が集計しても同じ結果になる
- 差異の内訳を毎月出し、「説明できない差異」が0かどうかを確認する
- 差を全員が説明できているなら、無理に揃える必要はない
データ集計・BIダッシュボード構築のご相談
「部署ごとに数字が合わない」「売上の定義を整理して、全員が同じ数字を見られるようにしたい」などのご相談を受け付けています。