マキモノ
Web開発無料✅ 公式検証済みv1.0.0 / 更新

一覧が表記ゆれで割れるのを、DBを書き換えずに直す(集計キー正規化+付け替えUI)

大小文字・全半角の違いだけで同じグループが複数カードに割れる問題を、DBの生値を保ったまま表示側の正規化キーで統合する。集計だけ直してフィルタを直さず「クリックしたら0件」になる最頻出の落とし穴、ページング・候補取得上限の罠、間違ったグループに入った行を後から移す付け替えUIまで含む。

出品者: kim@orgiast.jp📖 読込 約2,386トークン (約4円)💰 コスパ 48倍
トークン節約メーター82%節約
ゼロからAIに作らせた場合約14万トークン
このMDを読ませた場合約2.5万トークン

約11.5万トークンの節約 (API料金換算で約170円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。

この巻物について

「一覧が表記ゆれで割れるのを、DBを書き換えずに直す(集計キー正規化+付け替えUI)」は、Web開発カテゴリのAI指示書(MDファイル)です。大小文字・全半角の違いだけで同じグループが複数カードに割れる問題を、DBの生値を保ったまま表示側の正規化キーで統合する。集計だけ直してフィルタを直さず「クリックしたら0件」になる最頻出の落とし穴、ページング・候補取得上限の罠、間違ったグループに入った行を後から移す付け替えUIまで含む。この巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約11.5万トークン(API料金換算で約170円)・82%のトークンを節約できます。

カテゴリ
Web開発
対応AI
claude-code、cursor、codex-cli
ライセンス
商用利用可 (再販不可)
価格
無料
ゼロから開発時
約14万トークン
この巻物使用時
約2.5万トークン
節約量
約11.5万トークン (約170円)
更新日
2026-09-25

使い方 (AIに渡す3つの方法)

いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。

⬇ .md をダウンロード
claude "https://makimono-md.vercel.app/api/v1/files/db-ui/raw を読み込んで、この指示書どおりに実装して"
claude-codecursorcodex-cliライセンス: 商用利用可 (再販不可)

中身

一覧が表記ゆれで割れるのを、DBを書き換えずに直す(集計キー正規化 + 付け替えUI)

この指示書で直る症状

一覧画面のカードやグループが、同じものなのに表記の違いだけで複数に割れている。

サステナブル経営WEEK   1件
サステナブル経営week   8件     ← 本当は同じ。合計9件で1枚のはず

原因は、グループ化のキーにユーザーが入力した生の文字列をそのまま使っていること。 大文字小文字・全角半角・前後の空白・連続スペースが違うだけで別グループになる。

「DB を一括 UPDATE して表記を揃える」で直したくなるが、それはやらない。 元の入力を失うと、誤変換したときに戻せないし、外部システムと突き合わせる時に困る。 表示側の正規化だけで解決する。


設計(これをそのまま実装させてよい)

1. 正規化キーの関数を1つだけ作る

<lib>/grouping-key.ts のような共有モジュールに置く。 複数箇所にコピペすると、片方だけ直して壊れる(後述の落とし穴1がまさにそれ)。

/** グループ化のキー。表記ゆれ(大小文字・全半角・空白)を吸収する。 */
export function groupingKey(raw: string | null | undefined): string {
  return raw == null
    ? ""
    : String(raw).normalize("NFKC").trim().replace(/\s+/g, " ").toLowerCase();
}
  • normalize("NFKC") が全角英数を半角にする(WEEK → WEEK)。これを入れ忘れると全角が別グループに残る。
  • \s+ は全角スペースにもマッチする(JS の \s は U+3000 を含む)。
  • toLowerCase() は最後。NFKC の後にやること。

2. 代表表示名を決める

統合したカードに何と表示するか。件数が最も多い生値を採用する(同数なら新しい方)。 少数派の表記に寄せると「見慣れない名前になった」と現場が混乱する。

export function pickDisplayName(
  variants: { value: string; count: number; latest: string }[],
): string {
  return [...variants]
    .sort((a, b) => b.count - a.count || b.latest.localeCompare(a.latest))[0]?.value ?? "";
}

3. 集計する

export type Row = { groupField: string | null; createdAt: string | null };
export type Group = { key: string; name: string; count: number; latest: string };

export function groupRows(rows: Row[]): Group[] {
  const groups = new Map<string, Map<string, { value: string; count: number; latest: string }>>();
  for (const row of rows) {
    const key = groupingKey(row.groupField);
    if (!key) continue;                      // 空欄は「未分類」として別扱い
    const value = row.groupField!;
    const variants = groups.get(key) ?? new Map();
    const v = variants.get(value) ?? { value, count: 0, latest: "" };
    v.count++;
    if ((row.createdAt ?? "") > v.latest) v.latest = row.createdAt ?? "";
    variants.set(value, v);
    groups.set(key, variants);
  }
  return [...groups].map(([key, byValue]) => {
    const variants = [...byValue.values()];
    return {
      key,
      name: pickDisplayName(variants),
      count: variants.reduce((s, v) => s + v.count, 0),
      latest: variants.reduce((m, v) => (v.latest > m ? v.latest : m), ""),
    };
  }).sort((a, b) => b.latest.localeCompare(a.latest));
}

4. 取込側でも既存表記に寄せる

外部フォーム・CSV・API から新しい行が入るなら、入る時点で既存の代表表記に揃える。 既存の値一覧を引いて groupingKey が一致するものがあれば、その代表表記を使う。 これをやらないと、直した先からまた新しいゆれが増える。


落とし穴(ここで必ず1回壊れる)

落とし穴1: 集計だけ直してフィルタを直さない ← 最頻出

一覧カードは /list?group=<名前> のようなリンクになっていることが多い。 集計側だけ正規化すると、カードをクリックした先が 0 件になる。

カードの表示名は代表表記(例 サステナブル経営week)だが、 絞り込み先の行には少数派表記(サステナブル経営WEEK)が混ざっているため、 生値の完全一致では拾えない。

→ 絞り込み側も同じ groupingKey に通す。クエリ文字列は生の名前のままでよく、 サーバ側で groupingKey(query) === groupingKey(row.groupField) で突き合わせる。 行数が数千程度なら DB の ilike に頼らず取得後に JS で比較してよい。

落とし穴2: ページングの前に絞り込む

「1ページ目を取得してから正規化で絞る」と、2ページ目以降の該当行が落ちる。 正規化での絞り込みを済ませてからページ分割する。

落とし穴3: 候補取得が上限で切れる

多くの DB クライアントは1回のクエリで 1000 行など上限がある。 既存の値一覧を引くところは 必ずページングで全件読む。 途中で切れると「既存表記に寄せる」が効かず、ゆれが増え続ける。 取得に失敗した時は空リストとして続行しない(黙ってゆれを増やすため)。エラーで止める。

落とし穴4: 生値を書き換えたくなる

「一度きれいにすれば終わり」と一括 UPDATE したくなるが、やらない。 表示側の正規化はいつでも戻せるのが最大の利点。


セットで作る「付け替えUI」

表記ゆれが直っても、そもそも間違ったグループに入った行は残る。 グループ項目を画面から編集できるようにする。

  1. 詳細画面でグループ項目を編集可能にする。 入力は 既存の代表表示名を候補に出す combobox(HTML の datalist で十分)。自由入力も許可。 候補を出さないと、また新しい表記ゆれが増える。
  2. 一覧にチェックボックスによる複数選択と一括付け替えを付ける。 「間違ったグループに入った行をまとめて移す」が1操作で終わる。
  3. 保存後は一覧と集計ページの両方のキャッシュを無効化する。

テスト(必ず書かせる)

groupingKey / pickDisplayName は純粋関数なのでユニットテストが安い。最低限これだけ通す。

  • A経営WEEK / A経営week / A経営week が同じキーになる
  • 前後の空白・連続空白・全角スペースを吸収する
  • null / undefined / 空文字 / 空白のみ が空キーになる
  • 別物のグループを誤って統合しない
  • 代表名は件数優先、同数なら新しい方(入力配列を破壊しない)
  • 1件 + 8件 が 9件 に統合される
  • 候補取得が1000件の上限を越えて2ページ目まで読む
  • 候補取得の失敗が空リストとして握り潰されない

さらに、本番の実データで一度再現する。 本番と同じ集計関数に実データを流し、「どのカードが何件になるか」と 「DB の生値が両方残っていること」を目で確認してからデプロイ完了と言う。 ユニットテストが通っただけでは「実データに想定外の表記がない」ことの証明にならない。


完了条件

  • 割れていたグループが1枚に統合され、件数が合計値になっている
  • そのカードをクリックした先の一覧が0件ではない(落とし穴1の確認)
  • DB の生値が両方とも残っている(書き換えていない)
  • 詳細画面から別グループへ付け替えでき、候補が既存名から選べる
  • 一覧から複数選択して一括付け替えできる
  • ユニットテストが通り、本番実データでの再現結果を目視した
  • 型チェック・ビルドが通る(変更前のエラー数と比較し、新規増加0)

よくある質問

+「一覧が表記ゆれで割れるのを、DBを書き換えずに直す(集計キー正規化+付け替えUI)」とは何ですか?

大小文字・全半角の違いだけで同じグループが複数カードに割れる問題を、DBの生値を保ったまま表示側の正規化キーで統合する。集計だけ直してフィルタを直さず「クリックしたら0件」になる最頻出の落とし穴、ページング・候補取得上限の罠、間違ったグループに入った行を後から移す付け替えUIまで含む。

+どれくらいトークン(費用)を節約できますか?

ゼロから開発すると約14万トークンかかりますが、この巻物を使えば約2.5万トークンで済みます。差し引き約11.5万トークン(API料金換算で約170円)・82%の節約です。

+どうやって使いますか?

無料です。MDファイルを Claude Code などのAIに読み込ませるだけ。ワンライナーをターミナルに貼れば実装が始まります。要件定義や技術調査を省いて実装だけにトークンを使えます。

+どのAIツールに対応していますか?

claude-code、cursor、codex-cli に対応しています。

+商用利用できますか?

ライセンスは「商用利用可 (再販不可)」です。

🤝 自分でAIを動かすのは、まだ不安…という方へ

この巻物の内容を、AIを使うプロに丸ごと任せることもできます。姉妹サービスAI代行堂なら「LINEで頼むだけで、仕事が完成」。

AI代行堂を見る →

関連する巻物

この巻物、誰かのトークンも救えます

𝕏 で節約レシートをシェア