The Error Message
複雑なレポートの実行中やレコードの更新中に、MySQL が突然停止することがあります。ターミナルやアプリケーションのログには、次のメッセージが表示されます:
ERROR 1242 (21000): Subquery returns more than 1 row
Why This Happens
本質的に、これは logic mismatch です。MySQL は scalar value(5 という数値や 'Active' という文字列のような単一のデータ)を期待していますが、subquery がリストを返しています。これは通常、以下の 3 つの特定のシナリオで発生します:
- 1 つの値でしか機能しない comparison operator(
=、<、>)を使用した。 - 単一の列を作成するために、
SELECTlist 内に subquery が配置されている。 - subquery が、arrays や sets をサポートしていない関数の argument として機能している。
データベースの logic が「ユーザーは 1 つの active subscription しか持たない」と想定しているのに、実際のデータに 3 つ含まれている場合、query は即座にエラーになります。
Step-by-Step Fixes
Method 1: Use the LIMIT 1 Clause
最新の record や特定の代表値が 1 つだけ必要な場合は、LIMIT 1 が最も直接的な解決策です。これにより、subquery は最初の一致を見つけた時点で停止し、scalar requirement を満たします。
失敗する query の例:
SELECT name,
(SELECT order_date FROM orders WHERE orders.user_id = users.id) as last_order
FROM users;
ユーザー #402 が 12 件の orders を出している場合、subquery は 12 個の dates を返し、query はクラッシュします。修正方法:
SELECT name,
(SELECT order_date FROM orders
WHERE orders.user_id = users.id
ORDER BY order_date DESC LIMIT 1) as last_order
FROM users;
Method 2: Switch from = to IN
equals operator(=)は厳密であり、単一の一致を要求します。subquery が IDs の collection を返す可能性がある場合は、等号を IN operator に置き換えてください。IN は、値のリストを処理するために特別に設計されています。
失敗する query の例:
SELECT * FROM products
WHERE category_id = (SELECT id FROM categories WHERE name = 'Electronics');
店舗に 'Retail' と 'Wholesale' の両方の 'Electronics' categories(IDs 5 と 12)がある場合、subquery は両方を返します。修正方法:
SELECT * FROM products
WHERE category_id IN (SELECT id FROM categories WHERE name = 'Electronics');
Method 3: Use Aggregate Functions
ランダムな row ではなく、summary が必要な場合があります。MAX()、MIN()、SUM() などの aggregate functions は、複数の rows を自然に 1 つの scalar value に圧縮します。これは、財務データや時系列データに最適です。
失敗する query の例:
UPDATE employees
SET salary = (SELECT amount FROM bonuses WHERE employee_id = 101)
WHERE id = 101;
従業員 101 が今月 3 つの異なる bonuses を受け取った場合、update は失敗します。修正方法(SUM を使用):
UPDATE employees
SET salary = salary + (SELECT SUM(amount) FROM bonuses WHERE employee_id = 101)
WHERE id = 101;
Method 4: Use EXISTS for Presence Checks
関連する record が存在するかどうかだけを確認する場合は、subquery の比較を完全に避けてください。代わりに EXISTS を使用します。一致するものが 1 つ見つかるとすぐに検索を停止するため、パフォーマンスが向上します。また、データではなく boolean(true/false)を返すため、row count errors を回避できます。
失敗する query の例:
SELECT * FROM articles
WHERE id = (SELECT article_id FROM comments WHERE status = 'pending');
修正方法:
SELECT * FROM articles a
WHERE EXISTS (SELECT 1 FROM comments c WHERE c.article_id = a.id AND c.status = 'pending');
Method 5: Audit for Duplicate Data
SQL が完璧でも、データが整理されていないことがあります。一意であるべきテーブル(user_profiles など)に同じ user_id の rows が 2 つ含まれている場合、Error 1242 が発生します。これは多くの場合、database constraint が欠落している兆候です。
次のパターンを使用して重複を検索します:
SELECT user_id, COUNT(*)
FROM profiles
GROUP BY user_id
HAVING COUNT(*) > 1;
重複をクリーンアップした後、その列に UNIQUE constraint を適用して、将来エラーが再発するのを防ぎます。
Verification
デプロイする前に、MySQL Workbench や TablePlus などの安全な環境で修正をテストしてください。次の 3 点を確認します:
- エラーメッセージが表示されなくなったことを確認する。
- row count を確認する。subquery の範囲が広すぎる場合、
INを使用すると意図したよりも多くの rows が返されることがあります。 LIMIT 1を使用した場合は、ORDER BYlogic を再確認する。単にランダムな record ではなく、「最新」や「最安」の record が取得されていることを確認してください。
Pro Tips for Prevention
- デフォルトで LIMIT 1 を使用する: scalar subqueries では、
LIMIT 1を追加することで、予期しないデータ増加に対するセーフティネットとして機能します。 - JOIN を選択する: 可能な限り、
LEFT JOINまたはINNER JOINを使用してください。これらは一般的に高速で、nested subqueries よりもデバッグがはるかに簡単です。 - Enforce Uniqueness: 1対1の関係を表す列には
UNIQUEindexes を使用します。これにより、エラーの発生源であるデータ入力時点でエラーを阻止できます。 - Scale Testing: 5 行のテストデータで動作する query でも、50,000 行の本番データでは壊れる可能性があります。常に現実的な datasets でテストしてください。

