エラーはUPDATEの途中で発生する
いつも通りのUPDATEを実行すると、MySQLが次のエラーを返します:
ERROR 1292 (22007): Truncated incorrect DOUBLE value: 'active'
カラムは存在する。構文も問題ない。明らかな原因も見当たらない。何が起きているかというと、MySQLがクエリ内のどこかで文字列をDOUBLEにキャストしようとしているのです。ストリクトモードが有効な場合、MySQLは不正な値を黙って飲み込まず、代わりにエラーを返します。
なぜこのエラーが発生するか
エラー1292は、MySQLが文字列を数値型(INT、DOUBLE、FLOAT、DECIMAL)に変換しようとして、その文字列が有効な数値でない場合に発生します。MySQL 5.6以前では、この変換は黙って0に切り捨てられていました。MySQL 5.7以降、STRICT_TRANS_TABLESがデフォルトで有効になっています。そのため、切り捨ては静かなデータ破損ではなくエラーとして扱われます。
多くの人が見落とすポイント:問題の値はSET句ではなくWHERE句にあることがよくあります。
デバッグ:実際の原因を特定する
ステップ1 — 現在のsql_modeを確認する
SELECT @@sql_mode;
出力にSTRICT_TRANS_TABLESまたはSTRICT_ALL_TABLESが含まれていますか?それがMySQLが暗黙変換せずにエラーを出す理由です。無効化せず、クエリを修正してください。
ステップ2 — 関連するカラムの型を確認する
DESCRIBE your_table;
-- または
SHOW COLUMNS FROM your_table;
UPDATEで参照しているすべてのカラム(SETとWHEREの両方)を確認してください。INT、DOUBLE、FLOAT、DECIMAL型のカラムに文字列値を比較または代入しているものが候補です。
ステップ3 — 問題の句を特定する
複雑なWHERE句がある場合は、先にSELECTとして実行してみましょう:
SELECT * FROM orders
WHERE status = 'active'
AND category_id = 'electronics';
category_idがINTカラムであれば、このSELECTも同じ1292エラーを発生させます。これが原因です。
よくあるシナリオと修正方法
シナリオ1:数値カラムのWHERE句に文字列値を指定している
ほとんどの場合、これが原因です。数値の外部キーやステータスコードを文字列リテラルでフィルタリングしています。
-- 誤り: category_idはINTなのに文字列を渡している
UPDATE products
SET price = price * 0.9
WHERE category_id = 'electronics';
-- 修正: 実際の整数IDを使用する
UPDATE products
SET price = price * 0.9
WHERE category_id = 5;
シナリオ2:SET句で数値カラムに文字列を代入している
-- 誤り: stockはINT型
UPDATE inventory
SET stock = 'none'
WHERE product_id = 42;
-- 修正: 代わりに0またはNULLを使用する
UPDATE inventory
SET stock = 0
WHERE product_id = 42;
-- またはカラムがテキストを格納すべき場合は型を変更する
ALTER TABLE inventory MODIFY stock VARCHAR(20);
シナリオ3:論理演算子の誤用(&&、||)
MySQLの&&と||演算子は両辺をDOUBLEに強制変換します。文字列のオペランドを渡すと即座に1292エラーが発生します。
-- 誤り: MySQLが'active'をDOUBLEにキャストしようとする
UPDATE orders
SET processed = 1
WHERE is_paid = 1 && status = 'active';
-- 修正: &&の代わりにANDを使用する
UPDATE orders
SET processed = 1
WHERE is_paid = 1 AND status = 'active';
シナリオ4:アプリケーション/ORMから文字列が渡されている
クエリ単体では問題なく見える。問題は実行時にバインドされるパラメータにあります。アプリケーションが実際にその値をどこで生成しているか確認してください。
-- 問題が発生するPython SQLAlchemyの例
db.execute(
"UPDATE users SET role_id = :role WHERE id = :id",
{"role": "admin", "id": user_id} # role_idはVARCHARではなくINT
)
-- 修正: 正しい型を渡す
db.execute(
"UPDATE users SET role_id = :role WHERE id = :id",
{"role": 3, "id": user_id} # 'admin'ロールの整数ID
)
シナリオ5:型が混在するカラムでの算術演算
-- 誤り: discountはVARCHAR('10%')なのでMySQLが文字列を乗算しようとする
UPDATE orders
SET final_price = base_price * (1 - discount)
WHERE id = 101;
-- 修正: '%'を除去して明示的にキャストする
UPDATE orders
SET final_price = base_price * (1 - CAST(REPLACE(discount, '%', '') AS DECIMAL(5,2)) / 100)
WHERE id = 101;
今すぐリリースが必要な場合の一時的な回避策
変換されるデータの内容を正確に把握していて、直後にクリーンアップを行う場合のみ使用してください。
-- このセッションのみストリクトモードを緩和する
SET SESSION sql_mode = REPLACE(@@SESSION.sql_mode, 'STRICT_TRANS_TABLES', '');
-- UPDATEを実行する
UPDATE ...
-- ストリクトモードを元に戻す
SET SESSION sql_mode = @@GLOBAL.sql_mode;
ストリクトモードを無効にすると、MySQLは不正な変換を黙って0に切り捨てます。つまりprice = '10abc'は静かにprice = 10になり、status_code = 'active'はstatus_code = 0になります。UPDATEが完了したら直ちに影響を受けた行を確認してください。
修正が正しく適用されたか確認する
-- 1. エラーなしでUPDATEを実行する
UPDATE orders SET processed = 1 WHERE is_paid = 1 AND status = 'active';
-- Query OK, N rows affected (0.01 sec)
-- 2. データが正しく変更されたか確認する
SELECT id, processed, status FROM orders
WHERE is_paid = 1 AND status = 'active'
LIMIT 10;
-- 3. 誤って0になった行がないか確認する
SELECT COUNT(*) FROM orders WHERE processed = 0 AND is_paid = 1 AND status = 'active';
-- 0が返るはず
長期的な対策:再発を防ぐために
- **ストリクトモードを有効のままにする。**型の不一致をデータが静かに壊される前に検出してくれます。本番環境でこれを無効にして運用するのは、エラーログに何も残らないまま何千行もの
price = 0が生まれる原因になります。 - **アプリケーション層で型を合わせる。**カラムがINTであれば、常に整数をバインドしてください。MySQLの暗黙キャストに頼って差異を埋めようとしないでください。
- 複雑なWHERE句はまずSELECTとしてテストする本番テーブルに対してUPDATEを実行する前に確認しましょう。
- **ORMではカラムマッピングを確認する。**モデル上の文字列フィールドがDB上のINTカラムにマッピングされている場合、アプリケーションコードの型チェックは通っても、DBレイヤーで静かに壊れます。
- 型を混在させる式ではCAST()を明示的に使う変換を可視化して意図的なものにし、後でクエリを読む人にも明確に伝わるようにしましょう。

