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

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

更新履歴(4件・最終更新 2026年08月02日)

この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。

記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
Luhnのチェックディジットを手計算で追える例を追加し、隣接転置や1桁の誤りが実際に検出できることまで示しました。あわせて桁数の指針の言い換え、モジュラスとウェイトの読み方の注、Excel数式とVBAでの実装例を追加しています。
桁数の目安に、チェックディジット分の1桁が含まれていなかったので補いました。
初版公開
この記事を引用する(DOI(登録済みアーカイブ): 10.5281/zenodo.21590182)

以下のDOIは過去に登録されたアーカイブを指しており、現在の本文とは一致しない場合があります。現在の本文を参照するときは、このページのURLを使用してください。

小村 豪(2026)「業務システムのコード設計 ── 商品コード・顧客コードの決め方とチェックディジット」合同会社小村ソフト. https://comcomponent.com/blog/business-code-design-check-digit/

DOI(登録済みアーカイブ)
10.5281/zenodo.21590182
DOI(前回登録した版)
10.5281/zenodo.21733123

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

この記事では、JANコードやクレジットカード番号、マイナンバーなどの設計を手がかりに、商品コード・顧客コード・伝票番号をどう決めるかを整理します。前半はコードに持たせる意味・文字種・桁数の判断、後半は入力ミスを検出するチェックディジットの選び方と実装です。「長く使える番号にすること」と「打ち間違いを見つけること」を分けて考えます。

1. まず結論

コードは識別に徹し、意味は属性としてDBに持たせる。人が扱うコードには、入力ミスを見つける仕組みを足す。 この2つが基本方針です。

コードに、変わる属性を埋め込まない

部門・分類・年度を桁に埋め込む「有意コード」は、組織改編や分類変更がそのままコード変更の問題になります。マイナンバーも、意味を持たない番号として設計されています。1 また、一度使ったコードは欠番になっても再利用しません。過去の帳票・ログ・取引先のシステムに残った番号が、別の対象を指してしまうからです。

人が扱うなら、読みやすさと誤入力検出を優先する

電話・FAX・手書きが関わるなら数字のみ。桁を節約したい場合は、紛らわしい文字を除いた英数字(Crockford Base32など2)を検討します。人が入力・転記・読み上げるコードにはチェックディジットを付けます。実測で多いとされる「1文字の打ち間違い」と「隣どうしの入れ替え」を主な検出対象にします。3

桁数・保存方法・外部標準を、配る前に決める

桁数は、将来件数の10倍程度を表せる本体の桁数に、チェックディジットを使うならさらに1桁を加えて考えます。コードは全経路で文字列として扱います。Excelは数値と解釈した瞬間に先頭ゼロを落とし、15桁を超える部分を0に変えてしまうためです。4 商品にバーコードを付けて社外へ流通させる場合は、自前コードではなくJANコード(GS1標準)に乗ります。5

目的別の読み方

いま決めたいこと・困っていること 読むところ
商品や顧客のコードに、部門・分類を入れるべきか 2章: 意味を持たせるか
数字と英数字のどちらにするか、何桁用意するか 3章: 文字種と桁数
チェックディジットが必要か、どの方式を選ぶか 4章: 検出できるミスと方式
C#へ組み込みたい、Luhnの計算を手で追いたい 5章: 実装例
Excelで既存マスタを点検したい 5章: Excelの数式とVBA
先頭ゼロが消える、表記ゆれや欠番の扱いに困っている 6章: 保存・入力・運用の罠
JANコードと社内商品コードの関係を決めたい 7章: 外部標準との対応
桁あふれや既存コードの変更に備えたい 8章: 寿命と移行

実装例だけを使う場合も、文字列として受け取る前提と、検査数字の一致だけでは分からないことを確認してください。チェックディジットの計算と、マスタ照合・名称確認は別の段階です。

図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全26件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle

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

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

変わる属性を桁に入れると、何が起きるか

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

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

対象の識別と、その対象の属性を分ける

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

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

無意味連番か、種別だけを付けたハイブリッドか

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

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

推奨は表の下2つです。埋め込む意味は、増やすほど破綻の芽が増えると考えてください。表の C-0317-5 は構成を示す模式例で、特定の算式でチェックディジットを計算した結果ではありません。実際の検査数字の計算は4〜5章で扱います。

もう1つ分けておきたいのが、DB内部のキーと、人に見せる表示用コードです。内部キーには自動採番やUUIDなどを使い、人が読む番号は短い連番とチェックディジットにする二層構成を考えます。表示用コードの変更で全テーブルの外部キーまで変えずに済むようにするためです。移行時の扱いは8章で説明します。

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

1桁で表せる量と、人が読みやすいことは別

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

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

この表は、6桁すべてを本体に使った場合の組合せ数です。検査用の桁を確保し、採番しない予約番号を除いた実際の利用可能数とは区別します。文字種の判断基準は次の2つです。

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

本体の余裕と、検査用の1桁を別々に数える

桁数は「現在の件数」ではなく、システムと帳票が生き続ける20〜30年で到達しうる件数から逆算します。目安は、到達しうる件数の10倍程度を表せる本体の桁数を確保することです。数字のコードなら、容量の余裕を1桁分持たせる考え方になります。

たとえば将来10万件を見込むなら、本体は6桁を目安にします。そのうえでチェックディジットを付ける場合は、本体6桁+検査用数字1桁で、合計7桁です。検査用の1桁は本体から計算する桁なので、採番できる件数を増やすための余裕には数えません。

この「本体=10倍程度の容量」と「検査用数字1桁」を混ぜないことが大切です。英数字を使う場合は1桁の容量が違うため、上の表の文字種で計算し直します。

桁あふれの怖さは第8章で扱いますが、郵便番号が5桁から7桁に変わったとき(1998年)のような全面改修を自社に引き起こさないためのコストは、たかが1桁です。

長いコードは区切って見せ、保存値は変えない

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

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

この章では「どんなミスを見つけたいか」から方式を選びます。検査数字が一致することは、そのコードがマスタに存在することや、意図した商品・顧客であることの確認とは別です。後者は5章の入力画面での確認と組み合わせます。

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

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

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

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

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

表を読む前に、方式名の読み方を分けておきます。「モジュラス〇〇」は「〇〇で割った余りを使う方式」という意味です(モジュラス11なら11で割った余り)。「ウェイト」は各桁に掛ける倍率です。JAN方式の「ウェイト3-1」なら、チェックディジットを付ける前の本体の右端から3倍・1倍・3倍・1倍…と交互に掛けます。

JANや法人番号の方式は、重み付きの合計と余りから検査数字を作ります。Luhnは2倍した値が9を超えたときの処理も加わります。一方、DammとVerhoeffは表引きや群演算を使う方式で、単純な「倍率を掛けて余りを取る」計算だけではありません。以下で方式ごとに説明します。

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

表の「全て検出」は、そこに示した誤りの種類に対する性質です。桁の脱落や追加、離れた桁の変更など、あらゆる誤入力を検出するという意味ではありません。隣接転置は、異なる数字が隣どうしで入れ替わる場合を指します。

JAN方式: 外部標準で決まる場合

JAN方式(モジュラス10 ウェイト3-1)は、右端の桁から順に3倍・1倍を交互に掛けて合計し、「10 − (合計を10で割った余り)」の1の位をチェックディジットにします。6 転置で和が変わらないのは3倍と1倍の差×(桁の差)が10の倍数になる場合、つまり2桁の差がちょうど5のときで、このパターンだけは検出できません。

Luhn: 自社の数字コードの既定候補

Luhnは1954年にIBMのH. P. Luhnが特許出願した方式で7、1つおきの桁を2倍し、9を超えたら9を引いて合計します。隣接転置の見逃しは 09↔90 のただ1組。実装の簡単さと検出力のバランスが良く、自社コードに新規採用するならまずこれで困りません。

モジュラス11: 11種類の余りを1桁へ収める難しさ

マイナンバーのモジュラス11は、11が素数であるおかげで理論上の検出力は高いのですが、余りが0または1のときにチェックディジットを一律0とする畳み込みがあり8、この2クラスの間を移動する誤りだけは検出できません。

旧ISBN(ISBN-10)は同じモジュラス11でこの問題を「余り10をXと書く」ことで解決しましたが、今度は「数字のはずの桁にXが現れる」という運用上の面倒を抱えました。モジュラス11系を採用するなら、この「11種類の余りを10種類の数字にどう押し込むか」問題が必ず付いてくることは知っておくべきです。

法人番号のモジュラス9: 0と9を区別できない

法人番号のモジュラス9は先頭にチェックディジットを置く珍しい設計ですが11、9で割った余りを使うため 0 と 9 が合同になり、この2つの打ち間違い・入れ替えは素通しします。

Damm・Verhoeff: 1桁誤りと隣接転置の検出を優先する

DammやVerhoeffの方式は、1桁誤りと隣接転置の両方を全て検出します。全ての1桁誤りと全ての隣接転置を検出する十進コードはVerhoeffが最初に構成し3、Dammは準群(quasigroup)の演算表を1枚引いていくだけというより簡単な構成を与えました。10 検出力最優先ならこれらですが、業務システムの入力ミス対策としてはLuhnやJAN方式で実用上十分です。

自社の誤り方が違うなら、文字や帳票も見直す

この表が「1桁誤り」と「隣接転置」の2列で方式を比べているのは、4.1で見たVerhoeffの調査――オランダの郵便振替システムなど、人が実際に転記・入力していた番号の誤記記録を類型別に集計したもの――で、この2類型が誤りのほとんどを占めていたからです。3 逆に言えば、自社のコードで起きている誤りの傾向が明らかに違う(たとえば手書きの 1 と 7 の読み違いが大半)なら、方式の比較よりも、文字種や帳票の書式を見直すほうが効きます。

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

判断基準は「人の手と目を通るか」です。紙の注文書から打ち込む商品コード、電話口で伝える会員番号、現場で手書きされる伝票番号には付ける価値があります。逆に、システム間連携でしか流れない内部ID、画面からの選択でしか入力されないコードには不要です。ここで狙っているのは、人が文字を打ち間違えたり転記し間違えたりする経路だからです。選択肢から別の商品を選ぶミスは、チェックディジットではなく名称などの確認で対処します。

なお、5章のLuhn・JAN・法人番号の実装は数字のコード用です。3章のCrockford Base32や種別接頭辞付きコードを、そのまま渡す実装ではありません。採用する文字種と、検査の対象範囲・方式は揃えてください。

5. 実装例 ── C#とExcel・VBA

ここでは、JAN方式の検査数字の生成、Luhnの生成と検証、法人番号の検証を示します。本体から1桁を作る処理と、検査数字を含むコードを確かめる処理を分けて読んでください。コードは常に文字列として受け取ります(理由は6章)。

各例は計算部分を示すもので、業務上の桁数・採番範囲・マスタ存在確認までを1つの関数で済ませるものではありません。

C#

以下はクラス内へ置くメソッドの抜粋です。SystemとSystem.Linqの名前空間を利用し、char.IsAsciiDigitを持つ.NET 8などの環境を前提にします。char.IsAsciiDigitの対象はASCIIの0〜9で、全角数字などは含みません。12

どのメソッドもnullを処理する作りにはしていないため、呼び出し側で除外してください。数字の桁数や、区切り文字を取り除く処理も、業務コードの仕様に合わせて先に行います。

JAN方式: 12桁の本体から末尾の1桁を作る

まず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つを使い分ける

LuhnCheckDigitにはチェックディジットを含まない本体を、IsValidLuhnにはチェックディジットを含む完成したコードを渡します。この2つで、検査数字の生成と照合ができます。

生成側のLuhnCheckDigitは入力文字や長さを検査していません。単独で呼ぶ場合も、空でないASCII数字列で、設計した本体の桁数であることを呼び出し側で確認します。検証側のIsValidLuhnは2桁以上のASCII数字列かを調べますが、自社で決めた固定桁数やマスタへの登録有無は別途確認してください。

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';

手計算1: 本体からチェックディジットを作る

このコードが何をしているのかは、1回だけ手で追っておくと頭に残ります。顧客コードの本体を 1000317 として、チェックディジットを計算してみます。本体の右端から数えて1番目・3番目・5番目…の桁を2倍し、2倍した結果が9を超えたら9を引く、それだけです。

本体の桁(右から) 1 2 3 4 5 6 7
数字 7 1 3 0 0 0 1
2倍する ○   ○   ○   ○
計算後 14→5 1 6 0 0 0 2

合計は 5+1+6+0+0+0+2 = 14。チェックディジットは「10 −(合計を10で割った余り)」の1の位なので、10 − 4 = 6。完成したコードは 10003176 です。上のLuhnCheckDigit("1000317")が返す値と一致します。

手計算2: 完成したコードと、打ち間違えたコードを検証する

できあがったコードを検証するときは、チェックディジットも含めて右端から数え、右から2番目・4番目…を2倍して合計します。6+5+1+6+0+0+0+2 = 20 で、10で割り切れるので正しいコードです。試しに、よくある打ち間違いを入れてみます。

  • 隣どうしを入れ替えて 10003716 と打った場合: 6+2+7+6+0+0+0+2 = 23。10で割り切れないので検出できます。
  • 1桁だけ間違えて 10008176 と打った場合: 6+5+1+7+0+0+0+2 = 21。これも検出できます。

この「合計が10の倍数になるか」だけが判定基準なので、電卓しかない現場でも検算できます。第4章で見たとおり、隣接する異なる2桁の転置のうちLuhnが見逃すのは 09↔90 です。これは、ほかの種類の複数桁誤りや桁の脱落・追加まで検出できるという意味ではありません。

法人番号: 先頭の検査数字と、続く12桁を照合する

取引先マスタの入力チェックに使える、法人番号の検証も載せておきます。省令の算式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

この関数のtrueは算式に一致したという意味です。実在する法人に指定された番号かどうかまで、この計算で確認しているわけではありません。

Excelの数式とVBA

数式: 文字列で取り込んだコードを点検する

移行前の既存マスタをExcelで棚卸しする場面や、業務側で配られたリストを情シスが点検する場面では、開発環境を出さずにその場で検証したいことがあります。Luhnの計算上の一致は数式1本でも確認できます。A2には、文字列として取り込み済みで、所定の桁数を満たす2桁以上のASCII数字のコードが入っている前提です。Microsoft 365またはExcel 2021以降のLET・SEQUENCEを使います。

この数式は、C#のIsValidLuhnにある「2桁以上か」「ASCII数字だけか」という入力検査を再現していません。空欄や数字以外を渡すとエラーになり得るほか、1桁の0でも計算上はTRUEになります。式の結果だけで入力形式まで合格とせず、対象セルの形式は別途確認してください。

=LET(s,A2&"", n,LEN(s), i,SEQUENCE(n),
     d,MID(s,i,1)*1,
     x,IF(MOD(n-i,2)=1, d*2, d),
     y,IF(x>9, x-9, x),
     MOD(SUM(y),10)=0)

n-i が奇数の桁、つまり右から2番目・4番目…を2倍し、9を超えたら9を引いて合計し、10で割り切れるかを見ています。前掲の手計算と同じ手順です。コードが数値として入っているセルでは先頭ゼロが失われている点に注意してください(第6章)。文字列の指定は、入力・取り込みの前に行います。後から表示形式を文字列へ変えたり、式のA2&""で文字列化したりしても、既に失われたゼロや丸められた末尾は戻りません。原本から文字列として取り込み直してください。4

VBA: ワークシートから呼ぶ検証関数にする

古いExcelを含めて配りたい場合や、複数の方式を使い分けたい場合は、VBAの関数にしておくと=IsValidLuhn(A2)のようにワークシートから呼べます。

' Luhn方式の検証。先頭ゼロを落とさないよう、必ず文字列で渡すこと。
Public Function IsValidLuhn(ByVal code As String) As Boolean
    Dim i As Long, n As Long, d As Long, total As Long
    Dim c As String

    n = Len(code)
    If n < 2 Then Exit Function          ' 既定値の False を返す

    For i = 1 To n
        c = Mid$(code, n - i + 1, 1)     ' 右端から数える
        If c < "0" Or c > "9" Then Exit Function
        d = CLng(c)
        If i Mod 2 = 0 Then              ' 右から2番目・4番目…を2倍
            d = d * 2
            If d > 9 Then d = d - 9
        End If
        total = total + d
    Next i

    IsValidLuhn = (total Mod 10 = 0)
End Function

同じように桁を走査する関数として、JAN方式や法人番号の検証も実装できます。ただし、Luhnの「2倍して9を超えたら9を引く」処理をそのまま残して倍率だけを変えるのではありません。JANは本体の右端から3倍・1倍、法人番号は本体の奇数桁×1・偶数桁×2という規則に加え、検査数字の位置と算式も、それぞれに合わせます。

入力画面: 計算が通っても、名称を確認する

入力画面では、表記を正規化する → 桁数・文字種を確認する → チェックディジットを検証する → マスタを照合し、名称を表示するという順序で考えます。

チェックディジットが不一致なら、入力を見直してもらいます。一致した場合も、マスタを引いて名称を復唱表示する(「10003176: 株式会社〇〇 でよろしいですか?」)ところまで行います。計算を通過した別の実在コードを入力してしまった場合は、チェックディジットでは見分けられないためです。マスタが見つからない場合と、見つかった対象が意図と違う場合も、別々に確認します。

6. 現場で必ず踏む罠

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

Excelへ渡す前に文字列型を決める

Excelはセルの内容を数値と解釈すると先頭ゼロを削除し、さらに数値の有効精度が15桁のため16桁以降を0に置き換えます。4 顧客コード 00123 が 123 になり、クレジットカード番号級の長い番号は末尾が 0 に化けるということです。CSV連携があるシステムのコードは、全経路で文字列として扱う(Power Queryでのテキスト型指定、取り込み側での列型指定)ことを最初から運用手順に含めてください。

DBでもコードを数値型にしない

先頭ゼロが消えるのはExcelと同じですし、「コードの帯で絞り込む」つもりの BETWEEN が桁数の違うコードを巻き込みます。コードは計算対象ではないので、桁数固定の文字列型が原則です。ソート順も文字列として設計します(ASCII数字だけで桁数を固定していれば、文字列としての桁の比較と数値の大小が揃います)。

保存値と、区切った表示を分ける

1234-5678 と 12345678 が別レコードとして混在し始めたら終わりです。保存は素のコード、区切りは表示時に付与。入力時はハイフン・空白を除去してから検証します。

英字の大文字小文字を保存前に統一する

英字を使うなら保存前に大文字へ正規化し、照合順序(collation)に頼らず自前で統一します。

使い終わったコードを別の対象へ回さない

「顧客コード 1000317 は解約済みだから新規顧客に回そう」は禁物です。過去の請求書・ログ・取引先システムには古い対応関係が残っており、監査や障害調査で別人を指してしまいます。コードは永久欠番が原則です。

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

社外で使う番号は、標準の登録と算式に従う

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

社内コードとJANコードは、対応関係として持つ

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

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

桁を増やす影響は、DBの列だけでは終わらない

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

だからこそ第3章の容量の余裕が効きます。それでも移行が必要になった場合は、旧コードへの対応、内部キー、変更の配布を分けて準備します。

新旧対照マスタを用意し、旧コードでも検索できるようにする

取引先からの問い合わせは古いコードで来ます。新旧対照マスタを作り、移行期間中は両方のコードで検索できるようにすることが必要です。チェックディジットの後付けも桁が増える変更なので、既存体系を刷新するタイミングと合わせて考えます。

内部キーを安定させ、表示用コードの変更を切り離す

内部キーとコードを分離しておいた場合、移行は表示層とマスタの問題に閉じます。データベースの主キーに業務コードそのものを使っていると、全テーブルの外部キーが道連れになります。新規設計では内部キー(自動採番)と表示用コードを分けておくことを勧めます。

スキーマ変更を、全環境へ確実に配る

スキーマ変更はバージョン管理されたマイグレーションで配ります。コード列の桁数変更を全客先・全環境に確実に行き渡らせる方法は、DBスキーマのマイグレーション記事で扱ったとおりです。

9. まとめ

コード設計は、次の順に決めると整理できます。

  1. 識別のルールを決める。 原則は「コードは識別、意味は属性」です。部門・分類をコードへ焼き込まず、無意味連番か種別だけを付けるハイブリッドにします。マイナンバーが意味を持たない番号であることも参考になります。1 一度使ったコードは再利用せず、内部キーと表示用コードも分けておきます。
  2. 人が扱う形式と、誤入力の検出方式を決める。 読み上げ・手書きがあるなら数字のみ、桁を圧縮したいなら誤読対策済みのアルファベット(Crockford Base322)を検討します。将来件数の10倍程度の本体容量と、検査用の1桁は別に数えます。人手を通るコードにはチェックディジットを付け、自社の数字コードならLuhn、JANコードならGS1標準を選びます。6 主に狙うのは、実測で多い1桁誤りと隣接転置です。3 モジュラス11の余りの扱いや、法人番号のモジュラス9が0と9を区別しない性質も、方式ごとに確認します。
  3. 保存・入力・移行を一組で設計する。 Excelの0落ち・15桁丸め4、DBでの数値型格納、ハイフンや大文字小文字の混在を避け、文字列として一貫して扱います。チェックディジットだけで終えず、マスタ照合と名称表示を組み合わせます。社外流通用のJANコードは社内コードとは分けて持ち、桁あふれ移行では新旧対照マスタとスキーマの配布を準備します。

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

関連記事

関連する相談領域

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

参考リンク

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

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

  3. 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 ↩6

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

  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

  12. Microsoft, .NET 8のChar.cs. Char.IsAsciiDigitがASCIIの0〜9の範囲を判定する実装であることについて。 ↩

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

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

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

よくある質問

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

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

著者プロフィール

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

小村 豪

合同会社小村ソフト 代表

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

ブログ一覧に戻る