46

優先順位列を持つ「タスク」テーブルがありますが、これには一意の制約があります。

2行の優先度の値を入れ替えようとしていますが、制約に違反し続けています。私はこの声明を同様の状況のどこかで見ましたが、MySQLではありませんでした。

UPDATE tasks 
SET priority = 
CASE
    WHEN priority=2 THEN 3 
    WHEN priority=3 THEN 2 
END 

WHERE priority IN (2,3);

これにより、エラーが発生します。

Error Code: 1062. Duplicate entry '3' for key 'priority_UNIQUE'

偽の値や複数のクエリを使用せずにMySQLでこれを実現することは可能ですか?

編集:

テーブルの構造は次のとおりです。

CREATE TABLE `tasks` (
  `id` int(11) NOT NULL,
  `name` varchar(200) DEFAULT NULL,
  `priority` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `priority_UNIQUE` (`priority`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
4

6 に答える 6

38

偽の値と複数のクエリを使用せずに、MySQL でこれを達成することは可能ですか?

いいえ(私が考えることができるものはありません)。

問題は、MySQL が更新を処理する方法です。MySQL は (適切に実装されている他の DBMS とは異なりUPDATE)、更新を壊れた方法で処理します。ステートメントUNIQUE全体が完了した後ではなく、行が更新されるたびに (およびその他の) 制約のチェックが強制されます。UPDATEそのため、(ほとんどの) 他の DBMS ではこの問題は発生しません。

一部の更新 (すべてまたは一部の ID を増やすなどid=id+1) では、更新で - 別の非標準機能 - を使用することでこれを解決できますORDER BY

2 つの行の値を交換する場合、そのトリックは役に立ちません。NULLまたは偽の値 (存在しないが、列で許可されている) と 2 つまたは 3 つのステートメントを使用する必要があります。

一意の制約を一時的に削除することもできますが、それは本当に良い考えではないと思います。


したがって、一意の列が符号付き整数で、負の値がない場合は、トランザクションにラップされた 2 つのステートメントを使用できます。

START TRANSACTION ;
    UPDATE tasks 
    SET priority = 
      CASE
        WHEN priority = 2 THEN -3 
        WHEN priority = 3 THEN -2 
      END 
    WHERE priority IN (2,3) ;

    UPDATE tasks 
    SET priority = - priority
    WHERE priority IN (-2,-3) ;
COMMIT ;
于 2012-06-26T13:05:46.747 に答える
2

私は同じ問題にぶつかりました。CASE WHEN と TRANSACTION を使用して、考えられるすべての単一ステートメントクエリを試しましたが、まったくうまくいきませんでした。私は3つの代替ソリューションを思いつきました。どちらが自分の状況に適しているかを判断する必要があります。私の場合、フロントエンドから返された小さなオブジェクトの再編成されたコレクション (配列) を処理しています。新しい順序は予測できません (これはスワップ 2 アイテムの取引ではありません)。注文 (通常は英語版で行われます) は、他の 15 の言語に伝播する必要があります。

  1. 1 番目の方法: 既存のレコードを完全に削除し、新しいデータを使用してコレクション全体を再入力します。明らかに、これは、削除したばかりのものを復元するために必要なすべてをフロントエンドから受信している場合にのみ機能します。

  2. 2 番目の方法: この解決策は、偽の値を使用する場合と似ています。私の状況では、再注文したコレクションには、移動前の元のアイテムの位置も含まれています。また、UPDATE の実行中に何らかの方法で元のインデックス値を保持する必要がありました。トリックは、私の場合は UNSIGNED SMALLINT であるインデックス列のビット 15 を操作することでした。(符号付き) INT/SMALLINT データ型を使用している場合は、ビット単位の操作の代わりにインデックスの値を反転できます。

最初の UPDATE は、呼び出しごとに 1 回だけ実行する必要があります。このクエリは、現在のindexフィールドの 15 番目のビットを発生させます (私は unsigned smallint を持っています)。前の 14 ビットは元のインデックス値を反映しており、32K の範囲に近づくことはありません。

UPDATE *table* SET `index`=(`index` | 32768) WHERE *condition*;

次に、元のインデックス値と新しいインデックス値を抽出してコレクションを繰り返し、各レコードを個別に更新します。

foreach( ... ) {
    UPDATE *table* SET `index`=$newIndex WHERE *same_condition* AND `index`=($originalIndex | 32768);
}

この最後の UPDATE も、呼び出しごとに 1 回だけ実行する必要があります。このクエリは、フィールドの 15 番目のビットをクリアしindex、変更されていないレコードの元のインデックス値を効果的に復元します。

UPDATE *table* SET `index`=(`index` & 32767) WHERE *same_condition* AND `index` > 32767;
  1. 3番目の方法は、関連するレコードを主キーを持たない一時テーブルに移動し、すべてのインデックスを更新しから、すべてのレコードを最初のテーブルに戻すことです
于 2016-05-05T03:54:04.313 に答える
1

偽の値のオプション:

さて、私のクエリは似ていて、「1 つの」クエリで更新する方法を見つけました。私のid列は PRIMARYpositionで、UNIQUE グループの一部です。これは、スワッピングでは機能しない私の元のクエリです。

INSERT INTO `table` (`id`, `position`)
  VALUES (1, 2), (2, 1)
  ON DUPLICATE KEY UPDATE `position` = VALUES(`position`);

..しかし、位置は符号なし整数であり、0 になることはないため、クエリを次のように変更しました。

INSERT INTO `table` (`id`, `position`)
  VALUES (2, 0), (1, 2), (2, 1)
  ON DUPLICATE KEY UPDATE `position` = VALUES(`position`);

..そして今、それは動作します! どうやら、MYSQL は値グループを順番に処理します。

おそらくこれでうまくいくでしょう(テストされておらず、MYSQLについてほとんど何も知りません):

UPDATE tasks 
SET priority = 
CASE
    WHEN priority=3 THEN 0 
    WHEN priority=2 THEN 3 
    WHEN priority=0 THEN 2 
END 

WHERE priority IN (2,3,0);

幸運を。

于 2017-03-10T09:53:43.197 に答える
-2

これが制約に違反するかどうかはわかりませんが、似たようなことをしようとしていて、最終的に見つけたいくつかの答えを組み合わせてこのクエリを思いつきました:

UPDATE tasks as T1,tasks as T2 SET T1.priority=T2.priority,T2.priority=T1.priority WHERE (T1.task_id,T2.task_id)=($T1_id, $T2_id)

私が交換していた列はユニークを使用していなかったので、これが役立つかどうかはわかりません...

于 2014-12-17T13:30:38.517 に答える
-3

キーインデックスをわずかに変更して、上記の更新ステートメントで値を交換できます。

CREATE TABLE `tasks` (   `id` int(11) NOT NULL,   `name` varchar(200) DEFAULT NULL,   `priority` varchar(45) DEFAULT NULL,   PRIMARY KEY (`id`,`priority`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

これには、id と優先順位の組み合わせとして主キー インデックスが含まれます。その後、値を交換できます。

UPDATE tasks 
SET priority = 
CASE
    WHEN priority=2 THEN 3 
    WHEN priority=3 THEN 2 
END 

WHERE priority IN (2,3);

ここでは、ユーザー変数や一時変数は必要ありません。これで問題が解決することを願っています:)

于 2012-06-26T13:51:12.667 に答える