業務システムのコード設計 ── 商品コード・顧客コードの決め方とチェックディジット

· · コード設計, チェックディジット, 業務システム, データベース, C#, .NET, Excel, 判断表, 設計, Windows開発

「新しいシステムに移行するので、商品コードの体系を決めてください」──業務システムの構築やExcel台帳の移行では、必ずこの宿題が出ます。そして、その場の思いつきで決めたコードは、平均するとシステム本体より長生きします。システムは10年で入れ替わっても、取引先に配った顧客番号と、過去の帳票に印字された商品コードは残り続けるからです。

一方で、コード設計には先人の知恵が驚くほど蓄積されています。JANコードやクレジットカード番号、マイナンバーといった「大規模に運用されている番号」は、入力ミスの検出方法から桁の割り当てまで、設計判断の理由込みで公開されています。この記事では、それらを下敷きに、業務システムで商品コード・顧客コード・伝票番号などを決めるときのルールと、使える仕組み(チェックディジット)を整理します。

1. まず結論

  • コードは識別に徹し、意味は属性としてDBに持たせます。部門・分類・年度をコードの桁に埋め込む「有意コード」は、組織改編・分類変更で必ず破綻します。マイナンバーは、意味を持たない番号として設計されています。1
  • 人が入力・転記・読み上げるコードにはチェックディジットを付けます。入力誤りの大半は「1文字の打ち間違い」と「隣どうしの入れ替え」であることが実測で知られており2、チェックディジットはまさにこの2つを狙い撃ちで検出します。
  • 文字種は運用で決めます。電話・FAX・手書きが関わるなら数字のみ。桁を節約したいなら、紛らわしい文字(I・L・O等)を除いた英数字(Crockford Base32など3)。
  • 桁数は「将来の件数の10倍」を目安に確保し、先頭ゼロを使うなら全システムで文字列として扱います。Excelは数値と解釈した瞬間に先頭ゼロを落とし、16桁以降を0に丸めます。4
  • 一度使ったコードは欠番になっても再利用しません。過去の帳票・ログ・取引先のシステムに残った番号が、別の対象を指してしまいます。
  • 商品にバーコードを付けて流通させるなら、自前コードではなくJANコード(GS1標準)に乗ります。5

2. コードに意味を持たせるか ── 最初で最大の分かれ道

コード設計で最初に決めるべきは、桁数でも文字種でもなく、「コードに意味を持たせるか」です。

ありがちな設計はこうです。「商品コードは8桁。先頭2桁が部門、次の3桁が分類、残り3桁が連番」。決めた瞬間は整然として見えますが、数年でこうなります。

  • 組織改編で部門が統合され、旧部門コードを持つ商品が宙に浮く。振り直せば過去帳票と突合できなくなり、放置すれば「コードの部門と実際の部門が違う」例外がマスタに増殖する。
  • 分類をまたぐ商品(食品でもあり雑貨でもある)が現れ、どちらのコードを付けるかの社内ルールが必要になる。
  • ある分類だけ商品が増えて連番3桁が枯渇し、「この分類だけ例外的に別部門の空き番号を借りる」運用が始まる。

原因は明確で、組織や分類は変わるものなのに、変わらないことを前提にコードへ焼き込んだことです。だから原則はその逆になります。コードは対象を一意に指すためだけの記号にして、部門・分類などの属性はデータベースの列に持つ。属性ならいくら変わってもUPDATE一発で、コードは無傷です。

国の番号制度もこの原則で作られています。マイナンバーは住民票コードを「作為が加わらない方法」で変換して生成される、意味を持たない11桁+検査用数字1桁です。1 意味を持たせないのは、番号から個人の属性を推測されないためでもありますが、属性変更(引越しや改姓)で番号が変わらないためでもあります。

とはいえ実務では「先頭を見れば顧客か仕入先かだけは分かってほしい」という要望も根強いので、判断表にすると次のようになります。

方式 強み 破綻するポイント 向く場面
完全有意コード 02-104-317(部門-分類-連番) 読めば属性が分かる 組織改編・分類変更・部分的な桁枯渇で振り直し 対象が増減せず、分類が制度上固定されている場合のみ
無意味連番 10000317 何が変わっても壊れない。採番が単純 人がコードから何も読み取れない 属性を画面・帳票で常に併記できるシステム前提の運用
ハイブリッド C-0317-5(種別1文字+連番+検査数字) 種別の取り違えだけは防げる。破綻要素が最小 種別の定義変更(まれ) 迷ったらこれ。種別は「顧客/仕入先/商品」程度の粗さに留める

推奨は表の下2つです。埋め込む意味は、増やすほど破綻の芽が増えると考えてください。

3. 文字種と桁数 ── 情報効率と可読性はトレードオフ

次に決めるのは文字種と桁数です。これは純粋に算数の問題で、1桁に使える文字の種類が多いほど、同じ桁数で多くの対象を表せます

文字種 6桁で表せる数 読み上げ・手書き耐性
数字のみ(10種) 100万 ◎ 電話・FAX・手書きに強い
英大文字+数字から紛らわしい文字を除いた32種(Crockford Base323) 約10.7億 ○ 誤読対策済みだが読み上げは数字に劣る
英大文字+数字(36種) 約21.8億 0O1Iの誤読が必ず起きる

判断基準は次の2つです。

  • そのコードは電話で読み上げられるか、手書き・FAXされるか。ひとつでも当てはまるなら数字のみを推奨します。英字が混ざった瞬間、「アイ? ワン?」の確認コストと誤読リスクが発生します。
  • 数字だけだと桁数が長くなりすぎるか。対象が数千万件を超えるなど桁数を圧縮したい場合に、初めて英数字を検討します。その場合も素の36種ではなく、ILO(と偶然の卑語を避けるためのU)を除いたCrockford Base32のような設計済みアルファベットを使うべきです。この仕様は誤読対策が徹底していて、デコード時には小文字も受理し、il1o0として解釈します。3

桁数は「現在の件数」ではなく「システムと帳票が生き続ける20〜30年で到達しうる件数」から逆算し、余裕を1桁足します。桁あふれの怖さは第8章で扱いますが、郵便番号が5桁から7桁に変わったとき(1998年)のような全面改修を自社に引き起こさないためのコストは、たかが1桁です。

もうひとつ、地味に効くのが区切りです。クレジットカード番号が4桁ごとに印字されるように、人間は長い数字列を3〜4桁の塊でしか正確に扱えません。8桁以上のコードを帳票や画面に出すなら 1234-5678 のように区切って表示します。ただし区切り記号はデータに含めず、表示時に付けるのが原則です(第6章)。

4. チェックディジット ── 入力ミスをコード自身に検出させる

4.1 入力ミスの正体は2種類

チェックディジット(検査用数字)は、コードの末尾(または先頭)に付ける1桁で、他の桁から計算で導けるようにしておくことで、入力されたコードの誤りをその場で検出する仕組みです。マイナンバーの検査用数字も、政令に「電子計算機に入力するときに誤りのないことを確認することを目的として」と目的が明記されています。1

どんな計算式が良いかは、人間がどんなミスをするかで決まります。オランダの郵便振替システム等の誤記データを分析したVerhoeffの古典的調査によると、誤りの60〜95%は1桁だけの打ち間違い(single error)で、これが圧倒的多数。次いで2桁の誤りが10〜20%を占め、その大部分は隣接する2桁、とりわけ abba 型の入れ替え(転置)です。それ以外の類型(aabb 型や1つ飛びの入れ替えなど)は、それぞれ全体の0.5〜1.5%程度にすぎません。2

つまりチェックディジット方式の性能は、実質的に「1桁誤りをどれだけ検出できるか」と「隣接転置をどれだけ検出できるか」の2点で評価できます。

4.2 実際に使われている方式と検出能力(判断表)

方式 使われている場所 1桁誤り 隣接転置 特徴
モジュラス10 ウェイト3-1 JANコード等のGS1標準、ISBN-136 全て検出 差が5の組(05501661など)を見逃す バーコード運用なら事実上これ一択
Luhn(モジュラス10) クレジットカード番号7 全て検出 0990 の1組だけ見逃す 実装が最も簡単。自社コードの既定候補
モジュラス11(加重) マイナンバー8、旧ISBN ほぼ全て検出 ほぼ全て検出 検出力は高いが「余りの帳尻」問題あり(後述)
モジュラス9(加重) 法人番号9 09 を見逃す 09 の組を見逃す 9で割る都合上、0と9を区別できない
Damm / Verhoeff 学術的方式 全て検出 全て検出 数表の表引き(Damm10)や群演算(Verhoeff2)で実装

いくつか補足します。

  • JAN方式(モジュラス10 ウェイト3-1)は、右端の桁から順に3倍・1倍を交互に掛けて合計し、「10 − (合計を10で割った余り)」の1の位をチェックディジットにします。6 転置で和が変わらないのは3倍と1倍の差×(桁の差)が10の倍数になる場合、つまり2桁の差がちょうど5のときで、このパターンだけは検出できません。
  • Luhnは1954年にIBMのH. P. Luhnが特許出願した方式で7、1つおきの桁を2倍し、9を超えたら9を引いて合計します。隣接転置の見逃しは 0990 のただ1組。実装の簡単さと検出力のバランスが良く、自社コードに新規採用するならまずこれで困りません。
  • マイナンバーのモジュラス11は、11が素数であるおかげで理論上の検出力は高いのですが、余りが0または1のときにチェックディジットを一律0とする畳み込みがあり8、この2クラスの間を移動する誤りだけは検出できません。旧ISBN(ISBN-10)は同じモジュラス11でこの問題を「余り10をXと書く」ことで解決しましたが、今度は「数字のはずの桁にXが現れる」という運用上の面倒を抱えました。モジュラス11系を採用するなら、この「11種類の余りを10種類の数字にどう押し込むか」問題が必ず付いてくることは知っておくべきです。
  • 法人番号のモジュラス9は先頭にチェックディジットを置く珍しい設計ですが11、9で割った余りを使うため 09 が合同になり、この2つの打ち間違い・入れ替えは素通しします。
  • DammやVerhoeffの方式は、1桁誤りと隣接転置の両方を全て検出します。全ての1桁誤りと全ての隣接転置を検出する十進コードはVerhoeffが最初に構成し2、Dammは準群(quasigroup)の演算表を1枚引いていくだけというより簡単な構成を与えました。10 検出力最優先ならこれらですが、業務システムの入力ミス対策としてはLuhnやJAN方式で実用上十分です。

4.3 チェックディジットを付けるべきコード、不要なコード

判断基準は「人の手と目を通るか」です。紙の注文書から打ち込む商品コード、電話口で伝える会員番号、現場で手書きされる伝票番号には付ける価値があります。逆に、システム間連携でしか流れない内部ID、画面からの選択でしか入力されないコードには不要です。ミスの発生源(人)がいなければ、検出する意味もないからです。

5. C#での実装例

主要な方式はどれも数行〜十数行で実装できます。コードは常に文字列として受け取る点にだけ注意してください(理由は第6章)。

まずJAN方式(GTIN-13)。12桁の本体からチェックディジットを計算します。6

public static int Gtin13CheckDigit(string body12)
{
    if (body12.Length != 12 || !body12.All(char.IsAsciiDigit))
        throw new ArgumentException("12桁の数字を指定してください", nameof(body12));

    int sum = 0;
    for (int i = 0; i < 12; i++)
    {
        int digit = body12[11 - i] - '0';        // 右端から数える
        sum += (i % 2 == 0) ? digit * 3 : digit; // 右端が×3、以降交互
    }
    return (10 - sum % 10) % 10;
}

次にLuhn。自社の顧客コード・会員番号に付けるならこの2つで完結します。

public static int LuhnCheckDigit(string body)
{
    int sum = 0;
    for (int i = 0; i < body.Length; i++)
    {
        int digit = body[body.Length - 1 - i] - '0';
        if (i % 2 == 0)              // チェックディジットの隣から1桁おきに2倍
        {
            digit *= 2;
            if (digit > 9) digit -= 9;
        }
        sum += digit;
    }
    return (10 - sum % 10) % 10;
}

public static bool IsValidLuhn(string code) =>
    code.Length >= 2 && code.All(char.IsAsciiDigit) &&
    LuhnCheckDigit(code[..^1]) == code[^1] - '0';

取引先マスタの入力チェックに使える、法人番号の検証も載せておきます。省令の算式9をそのまま書き下したものです(先頭1桁が検査用数字、続く12桁が基礎番号)。

public static bool IsValidCorporateNumber(string code)
{
    if (code.Length != 13 || !code.All(char.IsAsciiDigit)) return false;

    int sum = 0;
    for (int n = 1; n <= 12; n++)
    {
        int p = code[13 - n] - '0';          // 基礎番号の最下位をn=1桁目とする
        sum += p * (n % 2 == 0 ? 2 : 1);     // 奇数桁×1、偶数桁×2
    }
    return 9 - sum % 9 == code[0] - '0';
}

国税庁の資料にある例(会社法人等番号 700110005901 → 法人番号 8700110005901)で検算すると、奇数桁の和11+偶数桁の和13×2=37、37を9で割った余り1、9−1=8で一致します。11

入力画面での使い方にもひと工夫あります。チェックディジット不一致のとき「コードが不正です」とだけ出すのではなく、マスタを引いて名称を復唱表示する(「1000317: 株式会社〇〇 でよろしいですか?」)ところまでやると、チェックディジットをすり抜けた誤入力(実在する別のコードを打ってしまった場合)も人の目で捕捉できます。

6. 現場で必ず踏む罠

コード体系そのものが良くても、実装と運用で台無しになる定番パターンがあります。

  • Excelの0落ちと15桁丸め。Excelはセルの内容を数値と解釈すると先頭ゼロを削除し、さらに数値の有効精度が15桁のため16桁以降を0に置き換えます。4 顧客コード 00123123 になり、クレジットカード番号級の長い番号は末尾が 0 に化けるということです。CSV連携があるシステムのコードは、全経路で文字列として扱う(Power Queryでのテキスト型指定、取り込み側での列型指定)ことを最初から運用手順に含めてください。
  • データベースで数値型に格納してしまう。先頭ゼロが消えるのはExcelと同じですし、「コードの帯で絞り込む」つもりの BETWEEN が桁数の違うコードを巻き込みます。コードは計算対象ではないので、桁数固定の文字列型が原則です。ソート順も文字列として設計します(桁数を固定していれば文字列ソート=数値ソートになります)。
  • ハイフンをデータに含めてしまう。1234-567812345678 が別レコードとして混在し始めたら終わりです。保存は素のコード、区切りは表示時に付与。入力時はハイフン・空白を除去してから検証します。
  • 大文字小文字の揺れ。英字を使うなら保存前に大文字へ正規化し、照合順序(collation)に頼らず自前で統一します。
  • 欠番の再利用。「顧客コード 1000317 は解約済みだから新規顧客に回そう」は禁物です。過去の請求書・ログ・取引先システムには古い対応関係が残っており、監査や障害調査で別人を指してしまいます。コードは永久欠番が原則です。

7. 自前で決めてはいけないコード ── 既存標準に乗る場合

社内で閉じるコードは自由に設計できますが、商品にバーコードを印字して社外(小売・EC・物流)に流すなら、自前コードではなくJANコード(GTIN)を使います。JANコードはGS1事業者コードの貸与を受けて設定するもので、自社で勝手に決めることはできません。5 チェックディジットの計算方法も標準で定められています。6

このとき設計上の要点は、社内コードとJANコードを無理に一本化しないことです。同一商品でも入数違いでJANが分かれる、規格変更でJANが変わる、といった事情があるため、商品マスタには「社内商品コード(主キー相当、自社で採番)」と「JANコード(属性、複数持てる)」を別の列として持たせるのが定石です。ここでも「コードは識別、意味(外部標準との対応)は属性」の原則がそのまま効きます。

8. コード体系の寿命と移行

どれだけ丁寧に設計しても、コード体系はいつか寿命を迎えます。典型は桁あふれです。連番の上限が見えてきたとき、選択肢は「桁を増やす」しかありませんが、桁数はマスタの列定義だけでなく、帳票のレイアウト、バーコードの印字幅、取引先との連携ファイル仕様、そして取引先側のシステムにまで焼き付いています。郵便番号の7桁化(1998年)のような移行を、自社と全取引先で実施することになるわけです。

だからこそ第3章の「余裕を1桁」が効くのですが、それでも移行が必要になったときの原則は次の3つです。

  • 新旧対照マスタを作り、移行期間中は両方のコードで検索できるようにする。取引先からの問い合わせは古いコードで来ます。
  • 内部キーとコードを分離しておいた場合、移行は表示層とマスタの問題に閉じます。データベースの主キーに業務コードそのものを使っていると、全テーブルの外部キーが道連れになります。新規設計では内部キー(自動採番)と表示用コードを分けておくことを勧めます。
  • スキーマ変更はバージョン管理されたマイグレーションで配る。コード列の桁数変更を全客先・全環境に確実に行き渡らせる方法は、DBスキーマのマイグレーション記事で扱ったとおりです。

9. まとめ

  • コード設計の第一原則は「コードは識別、意味は属性」。部門や分類をコードに焼き込むと、組織と分類の変化がそのままコードの破綻になります。マイナンバーが無意味番号なのは偶然ではありません。1
  • 文字種と桁数は情報効率と可読性のトレードオフ。読み上げ・手書きがあるなら数字のみ、桁を圧縮したいなら誤読対策済みのアルファベット(Crockford Base323)。桁数は将来件数の10倍+1桁。
  • 入力ミスの実態は「1桁誤りが6〜9割、隣接転置が大半の残り」。2 人手を通るコードにはチェックディジットを付け、方式は迷ったらLuhn、バーコードに載せるならGS1標準6。モジュラス11系の「余りの帳尻」問題と、法人番号のモジュラス9が0と9を区別しないことは、方式選定時に知っておくべき性質です。
  • 実装は文字列一貫が原則。Excelの0落ち・15桁丸め4、数値型格納、ハイフン混入、欠番再利用が現場の四大事故です。
  • 外に流れる商品コードはJAN(GS1)に乗り、社内コードとは別の列で持つ。桁あふれ移行に備えて内部キーと表示用コードは分離しておく。

コード体系は、一度配ってしまうと後から直すコストが桁違いに大きい「事実上の外部仕様」です。新規システムの要件定義でコードの話が出たら、画面や機能より先に、この記事のチェックリストを一周してみてください。

関連記事

関連する相談領域

合同会社小村ソフトでは、業務システム新規構築・リプレース時のコード体系・マスタ設計、既存コード体系の桁あふれ・重複の調査と移行計画、入力チェック(チェックディジット・マスタ照合)の実装を扱っています。この記事のチェックディジット方式の比較のように、「どの方式がどんなミスをどれだけ検出できるか」を数式で評価して設計判断に落とすご相談は、数理コンサルティングの領域としてもお受けしています。

参考リンク

  1. 行政手続における特定の個人を識別するための番号の利用等に関する法律施行令(平成26年政令第155号) 第六条. 個人番号とすべき番号が、住民票コードを変換して得られ作為が加わらない方法により生成される11桁の番号と、その後に付された1桁の検査用数字(個人番号を電子計算機に入力するときに誤りのないことを確認することを目的として算出される0から9までの整数)により構成されることについて。  2 3 4

  2. J. Verhoeff, Error Detecting Decimal Codes, Mathematical Centre Tracts 29, Mathematisch Centrum, Amsterdam. 実システムの誤記サンプル分析に基づく誤り類型の頻度(1桁誤りが60〜95%で最大の類型、2桁誤りが10〜20%でその大部分が隣接桁の転置、twin error等の少数類型がそれぞれ0.5〜1.5%)、および全ての1桁誤りと全ての隣接転置(同書のtranspositionは隣接桁の入れ替えを指す)を検出する十進コードを著者が構成したことについて。  2 3 4 5

  3. Douglas Crockford, Base 32. 32文字のアルファベットから1と紛らわしいI・L、0と紛らわしいO(および偶然の卑語を避けるためU)を除外していること、デコード時に大文字小文字を受理しi・lを1、oを0として扱うこと、mod 37によるチェック記号の仕組みについて。  2 3 4

  4. Microsoft サポート, 先頭のゼロと大きい数値を保持する. Excelの数値の有効精度が最大15桁であり、クレジットカード番号のような16桁以上の数値では15桁を超える部分が0に変更されること、先頭ゼロが削除されること、および列をテキストとして扱う回避策について。  2 3

  5. GS1 Japan, GS1事業者コード・GTIN(JANコード). JANコードの利用にはGS1事業者コードの貸与を受ける登録手続きが必要であることについて。  2

  6. GS1 Japan, チェックデジットの計算方法. GTIN-13(JANコード標準タイプ)のチェックデジットが、右端の桁から順に3倍・1倍を交互に掛けた合計を用いて「10から(合計を10で割った余り)を引く」ことで算出されることについて。  2 3 4 5

  7. H. P. Luhn, US Patent 2,950,048 “Computer for Verifying Numbers” (1954年出願、1960年登録). 元の番号の右端にチェックディジットを付加し、代替数字(2倍した数字の各桁和)を用いた交差加算で番号を検証する方式について。  2

  8. 行政手続における特定の個人を識別するための番号の利用等に関する法律に規定する個人番号、個人番号カード、特定個人情報の提供等に関する命令(平成26年総務省令第85号) 第五条. 検査用数字の算式(検査用数字以外の11桁の最下位からn桁目の数字Pnに、1≦n≦6のときn+1、7≦n≦11のときn−5の重みQnを掛けた合計を11で除し、11から余りを引く。余りが1以下の場合は0とする)について。  2

  9. 法人番号の指定等に関する省令(平成26年財務省令第70号) 第二条. 法人番号の検査用数字の算式(基礎番号の最下位からn桁目の数字Pnに、nが奇数のとき1、偶数のとき2の重みQnを掛けた合計を9で除し、9から余りを引く)について。  2

  10. H. M. Damm, Totally anti-symmetric quasigroups for all orders n≠2,6, Discrete Mathematics, Vol. 307, 2007. 全ての1桁誤りと隣接転置を検出するチェックディジット方式の基礎となる完全反対称準群が位数2・6を除く全ての位数で存在することについて。  2

  11. 国税庁, チェックデジットの計算. 法人番号が12桁の基礎番号とその前に付された1桁の検査用数字で構成されること、および会社法人等番号700110005901からチェックデジット8を算出する計算例について。  2

同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

この記事は次のサービスページにつながります。近い入口からご覧ください。

よくある質問

この記事のテーマについて、相談時によくある質問をまとめています。

チェックディジットはどんなコードに付けるべきですか?
人が入力・転記・読み上げに関わるコードに付けます。紙の帳票から打ち込む商品コード、電話で伝える会員番号、手書きされる伝票番号などが典型です。逆に、システム間でしか流れない内部ID(データベースの主キーなど)には不要です。人手を経ないコードは打ち間違いが起きないためで、チェックディジットの目的は入力誤りの検出にあります。マイナンバーの検査用数字も、法令上「電子計算機に入力するときに誤りのないことを確認すること」を目的と明記しています。
商品コードに部門や分類の意味を持たせてもよいですか?
原則は「コードは識別に徹し、意味は属性としてデータベースに持つ」です。部門・分類・年度などをコードの桁に埋め込むと、組織改編や分類変更のたびにコードの振り直しが必要になり、過去の帳票・取引先に渡した番号との突合が壊れます。どうしても人が見て区別したい場合は、種別を表す接頭辞1文字程度にとどめ、それ以外は連番にするのが実務的な落とし所です。
既存のコード体系にチェックディジットを後付けできますか?
技術的には可能ですが、桁が1つ増えるため、マスタ・全帳票・取引先とのデータ連携・印刷物のすべてに影響します。実質的にはコード体系の移行プロジェクトになるので、桁あふれ対応などで体系を刷新するタイミングに合わせるのが現実的です。それまでのつなぎとしては、入力画面でマスタ存在チェック(入力されたコードが実在するかの照合)と名称の復唱表示を行うだけでも、誤入力の実害はかなり減らせます。
UUIDやULIDを業務コードに使ってもよいですか?
データベースの内部キーとしては問題ありませんが、人が読み上げ・転記する「表示用コード」には向きません。UUIDは36文字と長すぎ、電話やFAXでの伝達に耐えないためです。内部キー(UUIDや自動連番)と、人に見せる表示用コード(短い連番+チェックディジット)を分ける二層構成にすると、両方の要求を満たせます。表示用コードの体系を将来変えることになっても、内部キーが安定していれば影響範囲を表示層に閉じ込められます。

著者プロフィール

記事の著者プロフィールページです。

小村 豪

合同会社小村ソフト 代表

Windows ソフト開発、技術相談、不具合調査を中心に、既存資産が残る案件や原因が見えにくい障害調査に強みがあります。

ブログ一覧に戻る