プロフィール

arismmn timeline blog

← ブログ一覧に戻る

SQLのLIKEパターンで曖昧マッチが起きた話:祝電検出バグの修正

バグ修正データベースSQL

🐛 問題の発見

軍師(Cloudflare Worker)が、当日に祝電を送っていないのに「軍師の感服」というメッセージを送信してくるバグが発生。

期待される動作:

  • 当日に祝電を送った → 2回目以降のスクショで「軍師の感服」を表示
  • まだ祝電を送っていない → 「祝電を打つ」ボタンを表示

実際の動作:

  • 当日に祝電を送っていないのに「軍師の感服」が表示される
  • 過去の祝電を誤検出している可能性

🔍 原因の調査

祝電の存在チェックを行うコードを確認:

// ❌ 問題のあったコード
const searchPattern = `${jst.y}%${jst.m}%${jst.d}%`;
// 例: "2026%02%21%"

const existingTelegram = await env.DB.prepare(
  `SELECT id FROM logs WHERE type='telegram' AND created_at LIKE ?`
).bind(searchPattern).first();

何が起きていたか

2026%02%21% というパターンは、以下のような意味になります:

  • 2026 で始まる
  • その後に 02 が含まれる
  • さらにその後に 21 が含まれる

つまり、「2026」「02」「21」が順に含まれていれば何でもマッチしてしまいます。

誤検出の例

日付文字列 マッチ 理由
2026/03/02 21:00:00 月の「02」と時刻の「21」が引っかかる
2026/02/10 21:30:00 月「02」と時刻の「21」が引っかかる
2026/12/21 10:00:00 年「2026」と日「21」が引っかかる
2026/02/21 14:00:00 正しく当日を検出(これは期待通り)
2026/02/20 21:00:00 月「02」と時刻の「21」でマッチしてしまう!

2月20日の21時に送った祝電が、2月21日の判定でもマッチしてしまうという致命的な問題でした。


✅ 修正内容

1. 日付境界を含むパターンに変更

// ✅ 修正後のコード
const searchPatternSlash = `${jst.y}/${jst.m}/${jst.d}%`;    // "2026/02/21%"
const searchPatternHyphen = `${jst.y}-${jst.m}-${jst.d}%`;  // "2026-02-21%"

const existingTelegram = await env.DB.prepare(
  `SELECT id FROM logs WHERE type='telegram' AND (created_at LIKE ? OR created_at LIKE ?)`
).bind(searchPatternSlash, searchPatternHyphen).first();

改善点:

  • / または - という明確な日付境界を含める
  • スラッシュ形式とハイフン形式の両方に対応

正しく検出されるパターン

日付文字列 マッチ 理由
2026/02/21 10:30:00 2026/02/21% に完全一致
2026-02-21 15:45:00 2026-02-21% に完全一致
2026/02/20 21:00:00 日付が 21 ではなく 20
2026/03/02 21:00:00 月が 02 だが日が 0221ではない)
2026/12/21 10:00:00 日は 21 だが月が 1202ではない)

📅 日付形式の統一作業

この修正に伴い、データベース内の日付形式も統一することになりました。

背景

祝電記録時に使っていた jst.full は以下のような形式でした:

jst.full  // 例: "2026/2/21 14:30:45" (スラッシュ区切り、1桁月日)

この形式には以下の問題がありました:

  • スラッシュ形式(/)とハイフン形式(-)が混在
  • 月日が1桁の場合ゼロ埋めされない(2/21 vs 02/21
  • タイムライン機能の無限スクロールでカーソル値の不一致問題を引き起こす

解決策: fullHyphen プロパティの追加

getJSTInfo() 関数に新しいプロパティを追加:

const getJSTInfo = (date = new Date()) => {
  // 既存の処理...
  
  // 時刻部分もゼロ埋めで正規化
  const timeObj = new Date(date.toLocaleString('en-US', { timeZone: 'Asia/Tokyo' }));
  const hh = String(timeObj.getHours()).padStart(2, '0');
  const mm = String(timeObj.getMinutes()).padStart(2, '0');
  const ss = String(timeObj.getSeconds()).padStart(2, '0');
  
  return {
    full: jstFull,  // 元の形式(表示用): "2026/2/21 14:30:45"
    fullHyphen: `${y}-${m}-${d} ${hh}:${mm}:${ss}`,  // DB保存用: "2026-02-21 14:30:45"
    dateSlash: `${y}/${m}/${d}`,   // "2026/02/21"
    dateHyphen: `${y}-${m}-${d}`,  // "2026-02-21"
    // ...
  };
};

統一形式: YYYY-MM-DD HH:mm:ss(ハイフン区切り、ゼロ埋め必須)

祝電記録の更新

// 修正前
const jst = getJSTInfo();
await env.DB.prepare("INSERT INTO logs (...) VALUES (..., ?, ...)")
  .bind(..., jst.full, ...).run();

// 修正後
const jst = getJSTInfo();
await env.DB.prepare("INSERT INTO logs (...) VALUES (..., ?, ...)")
  .bind(..., jst.fullHyphen, ...).run();

🚨 SQLでの失敗と緊急復旧

日付形式を統一する過程で、既存の祝電データも一括変換しようと試みました。

失敗したSQL

UPDATE logs 
SET created_at = CASE 
  WHEN created_at LIKE '%/%' 
  THEN strftime('%Y-%m-%d %H:%M:%S', REPLACE(REPLACE(created_at, '/', '-'), '年', '-')) 
  ELSE created_at 
END 
WHERE type='telegram'

結果: SQLiteの strftime() が日本語形式を解析できず、created_atNULL に!

緊急対応

サイト全体が500エラーになったため、以下の手順で緊急復旧:

  1. 被害状況の確認
SELECT id, type, created_at 
FROM logs 
WHERE created_at IS NULL;
-- → ID 6 が NULL
  1. 前後のレコードから日付を推測
SELECT id, created_at 
FROM logs 
ORDER BY id;
-- ID 5: 2026-02-19 23:24:08
-- ID 6: null
-- → 2月20日と推定
  1. 修復
UPDATE logs 
SET created_at = '2026-02-20 12:00:00' 
WHERE id = 6;

サイトが復旧しました。


📝 学んだ教訓

1. LIKEパターンは境界を意識する

-- ❌ 曖昧すぎる
WHERE created_at LIKE '2026%02%21%'

-- ✅ 日付境界を含める
WHERE created_at LIKE '2026-02-21%'
   OR created_at LIKE '2026/02/21%'

% は「何でもマッチ」するため、意図しない文字列も含めてマッチしてしまいます。日付検索では、スラッシュやハイフンといった明確な区切り文字を含めることが重要です。

2. SQLiteの strftime() は万能ではない

日本語形式(2026/2/21)のような非標準フォーマットは、strftime() で解析できません。シンプルな REPLACE() で十分な場合も多いです:

-- ✅ シンプルな変換
UPDATE logs 
SET created_at = REPLACE(created_at, '/', '-') 
WHERE type='telegram' AND created_at LIKE '%/%'

3. 日付形式は最初から統一する

新規データから統一形式を使うようにしたため、今後は同様の問題は発生しません:

jst.fullHyphen  // "2026-02-21 14:30:45" (ハイフン形式、ゼロ埋め)

データベース設計時に日付形式のルールを決めておくことの重要性を再認識しました。


🎯 まとめ

問題:

  • 祝電検出の LIKE パターンが曖昧で、別の日付も誤検出していた
  • 日付形式が複数混在し、一貫性がなかった

解決策:

  • 日付境界(/ または -)を含む正確なパターンに修正
  • データベース保存用に fullHyphen 形式を導入し、全体を統一

結果:

  • 当日の祝電のみ正確に検出できるようになった
  • 今後は日付形式の不一致問題が発生しない

SQLのパターンマッチは便利ですが、意図しない結果を招くことがあります。特に日付のような構造化されたデータは、境界を明確にすることで精度が大幅に向上します。

Gemini
Powered by
Gemini
← ブログ一覧に戻る