何をしたいユーティリティか:「採番」
ここでの「採番」は、配列の要素に「業務で使う番号(連番・管理番号)」を振る処理のことです。
さっきやった「インデックス付与」とよく似ていますが、目的が少し違います。
インデックスは「配列の何番目か」という技術的な情報でした。
採番は「伝票番号」「行番号」「管理 ID」のように、業務上意味を持つ番号を付けるイメージです。
例えば、次のような場面で使います。
画面上の明細行に「行番号」を振りたい。
新規に追加された行に「一意な行 ID」を振りたい。
既存データの最大番号の続きから、新しい番号を振りたい。
ここをユーティリティ化しておくと、「番号の付け方」がコード全体で統一されて、バグが減ります。
インデックス付与との違いを整理する
インデックスは「位置」、採番は「意味のある番号」
インデックス付与は、0, 1, 2, ... のように「配列の位置」をそのままくっつけるものでした。
採番は、次のような特徴を持ちます。
番号の開始値を自由に決めたい(1 から、1000 から、など)。
既存の最大値の続きから振りたい。
桁数を揃えたい(001, 002, …)。
プレフィックスを付けたい(”ORD-001″, “ORD-002″…)。
つまり、「ただの位置」ではなく、「業務ルールに沿った番号」を作るのが採番です。
基本形:単純な連番を振る採番ユーティリティ
シンプルな連番採番
まずは、「配列の要素に 1, 2, 3… のような連番を振る」基本形からいきます。
function assignSequence(array, key = "seq", start = 1, step = 1) {
if (!Array.isArray(array)) {
return [];
}
return array.map((item, i) => {
const n = start + i * step;
if (item != null && typeof item === "object") {
return { ...item, [key]: n };
}
return { value: item, [key]: n };
});
}
JavaScriptここでの重要ポイントは次の 3 つです。
start で「どこから始めるか」を決める(デフォルト 1)。
step で「何刻みで増やすか」を決める(デフォルト 1)。
オブジェクトならスプレッドでプロパティ追加、プリミティブなら { value, seq } で包む。
これで、「1, 2, 3…」「10, 20, 30…」など、基本的な採番は全部書けます。
実際の動き
const rows = [
{ name: "A" },
{ name: "B" },
{ name: "C" },
];
assignSequence(rows);
/*
[
{ name: "A", seq: 1 },
{ name: "B", seq: 2 },
{ name: "C", seq: 3 },
]
*/
assignSequence(rows, "lineNo", 10, 10);
/*
[
{ name: "A", lineNo: 10 },
{ name: "B", lineNo: 20 },
{ name: "C", lineNo: 30 },
]
*/
JavaScript「行番号」「伝票内の明細番号」などは、このパターンで十分なことが多いです。
既存データの「最大値の続き」から採番する
よくある要件:「今ある番号の続きから振ってほしい」
業務で本当によく出るのが、「既に番号が振られているデータがあって、新しく追加されたものにはその続きから番号を振りたい」という要件です。
例えば、既存データがこうなっているとします。
const existing = [
{ name: "A", seq: 1 },
{ name: "B", seq: 2 },
];
JavaScriptここに新規行を 2 件追加したいとき、「3, 4」と振りたいわけです。
最大値を求めてから採番するユーティリティ
function nextSequenceStart(items, key = "seq") {
if (!Array.isArray(items)) {
return 1;
}
let max = 0;
for (const item of items) {
if (!item || typeof item !== "object") continue;
const value = item[key];
if (typeof value === "number" && Number.isFinite(value)) {
if (value > max) {
max = value;
}
}
}
return max + 1;
}
function assignSequenceFromExisting(existingItems, newItems, key = "seq", step = 1) {
const start = nextSequenceStart(existingItems, key);
return assignSequence(newItems, key, start, step);
}
JavaScriptnextSequenceStart は、「既存配列の中で key の最大値を探して、その次の値を返す」関数です。assignSequenceFromExisting は、「既存配列を見て開始値を決め、その値から新規配列に採番する」関数です。
実際の使い方
const existing = [
{ name: "A", seq: 1 },
{ name: "B", seq: 2 },
];
const newRows = [
{ name: "C" },
{ name: "D" },
];
const numberedNew = assignSequenceFromExisting(existing, newRows);
/*
[
{ name: "C", seq: 3 },
{ name: "D", seq: 4 },
]
*/
JavaScriptこのように、「既存データの最大値の続きから採番する」という業務あるあるを、ユーティリティに閉じ込めておくと、
画面でもバッチでも同じルールで番号を振れるようになります。
桁数・プレフィックス付きの採番(”ORD-001″ など)
「見た目のルール」がある番号
実務では、「ただの数字」ではなく、次のようなルールが付くことが多いです。
3 桁ゼロ埋め(001, 002, …)。
プレフィックス付き(”ORD-001″, “ORD-002″…)。
日付+連番(”20240601-001″ など)。
こういうときは、「数値としての連番」と「文字列としてのフォーマット」を分けて考えるとスッキリします。
ゼロ埋め+プレフィックスのフォーマッタ
function formatSequence(n, { width = 3, prefix = "", suffix = "" } = {}) {
const numStr = String(n).padStart(width, "0");
return `${prefix}${numStr}${suffix}`;
}
JavaScriptpadStart で桁数を揃え、前後に文字列を付けるだけです。
採番と組み合わせる
function assignCode(array, key = "code", options = {}) {
if (!Array.isArray(array)) {
return [];
}
const { start = 1, step = 1, width = 3, prefix = "", suffix = "" } = options;
return array.map((item, i) => {
const n = start + i * step;
const code = formatSequence(n, { width, prefix, suffix });
if (item != null && typeof item === "object") {
return { ...item, [key]: code };
}
return { value: item, [key]: code };
});
}
JavaScript実際の動き
const orders = [
{ customer: "Alice" },
{ customer: "Bob" },
];
assignCode(orders, "orderNo", {
start: 1,
width: 4,
prefix: "ORD-",
});
/*
[
{ customer: "Alice", orderNo: "ORD-0001" },
{ customer: "Bob", orderNo: "ORD-0002" },
]
*/
JavaScript「数値としての連番」と「文字列としての見た目」を分けておくと、
途中でフォーマットを変えたくなったときも、採番ロジックをいじらずに済みます。
採番で特に意識してほしい重要ポイント
1. 「一意性」がどこまで必要かを決める
採番は、「一意であること」が求められることが多いです。
伝票内だけ一意ならよいのか。
システム全体で一意である必要があるのか。
日付ごとにリセットしてよいのか。
これによって、採番の設計が大きく変わります。
配列ユーティリティとして扱うのは、「あくまで“その配列の中での採番”」です。
システム全体で一意な ID が必要な場合は、DB のシーケンスや UUID など、別の仕組みと組み合わせる必要があります。
2. 「再採番」するかどうかを決める
行の追加・削除があったとき、「番号を振り直すかどうか」も重要です。
常に 1 から振り直す(見た目の行番号として使う)。
一度振った番号は変えない(業務上の識別子として使う)。
前者なら、毎回 assignSequence で振り直せばよいです。
後者なら、「新規行にだけ採番する」ロジックが必要になります。
例えば、「seq が未定義のものにだけ番号を振る」ユーティリティはこう書けます。
function assignSequenceIfMissing(array, key = "seq") {
if (!Array.isArray(array)) return [];
const start = nextSequenceStart(array, key);
let current = start;
return array.map((item) => {
if (!item || typeof item !== "object") {
return item;
}
if (typeof item[key] === "number") {
return item;
}
return { ...item, [key]: current++ };
});
}
JavaScript「番号を変えたくないのか」「見た目だけ揃えたいのか」を、ユーティリティのレベルで分けておくと、後で混乱しません。
3. 「表示用の番号」と「内部 ID」を混ぜない
採番した番号を、そのまま「内部 ID」として使ってしまうと、
後から仕様変更したときに破綻しやすいです。
例えば、「画面の行番号(1, 2, 3…)」をそのまま DB のキーに使ってしまうと、
行の並び替えや削除で番号が変わったときに、整合性が崩れます。
内部 ID(変わらない識別子)と、表示用の番号(変わってもよい連番)は、
プロパティ名も用途も分けておくのが安全です。
少し手を動かして感覚をつかむ
コンソールで、次のようなコードを実際に打ってみてください。
const rows = [
{ name: "A" },
{ name: "B" },
{ name: "C" },
];
assignSequence(rows);
assignSequence(rows, "lineNo", 10, 10);
const existing = [
{ name: "A", seq: 1 },
{ name: "B", seq: 2 },
];
const newRows = [
{ name: "C" },
{ name: "D" },
];
assignSequenceFromExisting(existing, newRows);
const orders = [
{ customer: "Alice" },
{ customer: "Bob" },
];
assignCode(orders, "orderNo", {
start: 1,
width: 4,
prefix: "ORD-",
});
JavaScript「どんな番号が振られているか」「既存の続きからちゃんと採番されているか」「フォーマット付きの番号がどう作られているか」を、自分の目で確認してみてください。
そのうえで、自分のプロジェクトに
export function assignSequence(...) { ... }
export function nextSequenceStart(...) { ... }
export function assignSequenceFromExisting(...) { ... }
export function assignCode(...) { ... }
JavaScriptのような関数を置き、「業務で番号を振りたくなったら、必ずこの“採番ユーティリティ”を通す」というルールを作ってみてください。
そうすると、「その場しのぎの連番」から、「意図と一貫性を持った採番設計」に一段ステップアップしていきます。
