FROM句で更新のターゲットテーブルを指定することはできません


380

単純なmysqlテーブルがあります。

CREATE TABLE IF NOT EXISTS `pers` (
  `persID` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(35) NOT NULL,
  `gehalt` int(11) NOT NULL,
  `chefID` int(11) DEFAULT NULL,
  PRIMARY KEY (`persID`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;

INSERT INTO `pers` (`persID`, `name`, `gehalt`, `chefID`) VALUES
(1, 'blb', 1000, 3),
(2, 'as', 1000, 3),
(3, 'chef', 1040, NULL);

次の更新を実行しようとしましたが、エラー1093のみが表示されます。

UPDATE pers P 
SET P.gehalt = P.gehalt * 1.05 
WHERE (P.chefID IS NOT NULL 
OR gehalt < 
(SELECT (
    SELECT MAX(gehalt * 1.05) 
    FROM pers MA 
    WHERE MA.chefID = MA.chefID) 
    AS _pers
))

エラーを検索し、mysqlのページhttp://dev.mysql.com/doc/refman/5.1/en/subquery-restrictions.htmlから見つけましたたが、役に立ちません。

SQLクエリを修正するにはどうすればよいですか?


回答:


769

問題は、MySQLがどんな理由であれ、次のようなクエリを記述できないことです。

UPDATE myTable
SET myTable.A =
(
    SELECT B
    FROM myTable
    INNER JOIN ...
)

つまり、テーブルでUPDATE/ INSERT/ DELETEを実行している場合、内部クエリでそのテーブルを参照することはできません(ただし、外部テーブルのフィールドを参照することできます...)


解決策は、のインスタンスを置き換えることであるmyTableと、サブクエリで(SELECT * FROM myTable)、このように、

UPDATE myTable
SET myTable.A =
(
    SELECT B
    FROM (SELECT * FROM myTable) AS something
    INNER JOIN ...
)

これにより、必要なフィールドが一時テーブルに暗黙的にコピーされるため、許可されます。

私はこの解決策をここで見つけまし。その記事からのメモ:

SELECT * FROM table実際のサブクエリだけにしたくない。例を単純にしたかっただけです。実際には、最も内側のクエリで必要な列のみを選択し、適切なWHERE句を追加して結果も制限する必要があります。


10
その理由は正気ではないと思います。セマンティクスについて考えてください。MySQLは、更新が開始する前にテーブルのコピーを保持する必要があります。または、内部クエリは、進行中のクエリによってすでに更新されているデータを使用する場合があります。これらの副作用はどちらも必ずしも望ましいものではないため、最も安全な方法は、追加のテーブルを使用して何が起こるかを指定することを強制することです。
siride 2013年

35
@siride:MSSQLやOracleなどの他のデータベースには、この任意の制限はありません
BlueRaja-Danny Pflughoeft 2013年

3
@ BlueRaja-DannyPflughoeft:それは恣意的ではありません。代替案のコストに基づく合理的な設計決定です。他のDBシステムはとにかくそれらのコストに対処することを選択しました。しかし、これらのシステムでは、たとえば、GROUP BYを使用する場合に非集約列をSELECTリストに含めることはできませんが、MySQLでは行われます。MySQLはここで間違っていると私は主張しますが、UPDATEステートメントの他のDBMSと同じことを言うかもしれません。
siride 2013年

33
ビューのリレーショナル代数の観点から@siride、Tそして(SELECT * FROM T)完全に等価です。彼らは同じ関係です。したがって、これは恣意的な非常に制限的なものです。より具体的には、MySQLが明確に実行できることを実行するように強制する回避策ですが、何らかの理由で、より単純な形式では解析できません。
Tobia

4
私の場合、テーブルが大きすぎるため、受け入れられたソリューションは機能しませんでした。クエリは完了していません。どうやら、これはあまりにも多くの内部リソースを使用しています。代わりに、内部クエリを使用してビューを作成し、それをデータ選択に使用しましたが、これはまったく問題なく機能しました。 DELETE FROM t WHERE tableID NOT IN (SELECT viewID FROM t_view);またOPTIMIZE TABLE t;、後で実行して、テーブルのサイズを小さくすることをお勧めします。
コーデックス

53

これは3つのステップで行うことができます。

CREATE TABLE test2 AS
SELECT PersId 
FROM pers p
WHERE (
  chefID IS NOT NULL 
  OR gehalt < (
    SELECT MAX (
      gehalt * 1.05
    )
    FROM pers MA
    WHERE MA.chefID = p.chefID
  )
)

...

UPDATE pers P
SET P.gehalt = P.gehalt * 1.05
WHERE PersId
IN (
  SELECT PersId
  FROM test2
)
DROP TABLE test2;

または

UPDATE Pers P, (
  SELECT PersId
  FROM pers p
  WHERE (
   chefID IS NOT NULL 
   OR gehalt < (
     SELECT MAX (
       gehalt * 1.05
     )
     FROM pers MA
     WHERE MA.chefID = p.chefID
   )
 )
) t
SET P.gehalt = P.gehalt * 1.05
WHERE p.PersId = t.PersId

16
そうですね、ほとんどのサブクエリはCREATE TABLEステートメントを含む複数のステップとして書き直すことができます-私は作者がこれを知っていれば幸いです。しかし、これが唯一の解決策ですか?または、クエリをサブクエリまたは結合で書き換えることができますか?そして、なぜ(しない)それをしますか?
Konerak

2番目のソリューションで大文字の誤りがあると思います。UPDATE Pers P読むべきではありませんUPDATE pers Pか?
ubiquibacon 2012

2
このソリューションを試したところ、一時/ 2番目のテーブルに多数のエントリがあると、クエリが非常に遅くなる可能性があります。インデックス/主キーを使用して一時/第2テーブルを作成してみてください[ dev.mysql.com/doc/refman/5.1/en/create-table-select.htmlを参照]
Alex

@Konerakが述べているように、これは本当に最良の答えではありません。以下のBlueRajaからの答えが私には最良のようです。賛成票は同意するようです。
ShatyUT 2014

@Konerak、CREATE TABLE AS SELECT恐ろしいパフォーマンスを与えていませんか?
Pacerier、2015

27

Mysqlでは、同じテーブルをサブクエリして1つのテーブルを更新することはできません。

クエリを2つの部分に分けるか、または

 AとしてのTABLE_Aの更新
 INNER JOIN TABLE_A AS B ON A.field1 = B.field1
 SETフィールド2 =? 

5
SELECT ... SET?私はこれについて聞いたことがありません。
Serge S.

@grisson説明をありがとう。IN句が機能しない理由がわかります。同じテーブルをターゲットにしていました。
アンソニー

2
...これは実際には機能していないようです。それでも同じエラーが発生します。
BlueRaja-Danny Pflughoeft 2013年

2
この答えは実際には、より正確で効率的なことをAS B行いTABLE_Aます。これは、への2番目の参照で使用されています。最も有望な例の答えAS Tは、潜在的に非効率的なの代わりに使用して簡略化できますFROM (SELECT * FROM myTable) AS something。幸いなことに、クエリオプティマイザーは通常、これを排除しますが、常にそうするわけではありません。
natbro 2013年

23

サブクエリから一時テーブル(tempP)を作成する

UPDATE pers P 
SET P.gehalt = P.gehalt * 1.05 
WHERE P.persID IN (
    SELECT tempP.tempId
    FROM (
        SELECT persID as tempId
        FROM pers P
        WHERE
            P.chefID IS NOT NULL OR gehalt < 
                (SELECT (
                    SELECT MAX(gehalt * 1.05) 
                    FROM pers MA 
                    WHERE MA.chefID = MA.chefID) 
                    AS _pers
                )
    ) AS tempP
)

別の名前(エイリアス)を導入し、一時テーブルの「persID」列に新しい名前を付けました


内部内部内部選択を行う代わりに、値を変数に選択しないのはなぜですか?
Pacerier 2015

SELECT ( SELECT MAX(gehalt * 1.05)..SELECT-1つ目は列を選択しません。
イスティアケアーメド2017

18

とても簡単です。たとえば、書く代わりに:

INSERT INTO x (id, parent_id, code) VALUES (
    NULL,
    (SELECT id FROM x WHERE code='AAA'),
    'BBB'
);

あなたは書くべきです

INSERT INTO x (id, parent_id, code)
VALUES (
    NULL,
    (SELECT t.id FROM (SELECT id, code FROM x) t WHERE t.code='AAA'),
    'BBB'
);

または類似。


13

BlueRajaによって投稿されたアプローチは遅いです。テーブルから重複を削除するために使用していたので、変更しました。大きなテーブルを持つ人を助ける場合オリジナルのクエリ

delete from table where id not in (select min(id) from table group by field 2)

これには時間がかかります:

DELETE FROM table where ID NOT IN(
  SELECT MIN(t.Id) from (select Id,field2 from table) AS t GROUP BY field2)

より速いソリューション

DELETE FROM table where ID NOT IN(
   SELECT x.Id from (SELECT MIN(Id) as Id from table GROUP BY field2) AS t)

反対票を投じている場合はコメントを追加してください。
Ajak6


3

tableAからfieldAを読み取って同じテーブルのfieldBに保存しようとしている場合、fieldc = fielddの場合、これを検討する必要があります。

UPDATE tableA,
    tableA AS tableA_1 
SET 
    tableA.fieldB= tableA_1.filedA
WHERE
    (((tableA.conditionFild) = 'condition')
        AND ((tableA.fieldc) = tableA_1.fieldd));

上記のコードは、condition-fieldが条件を満たしたときに、fieldAからfieldBに値をコピーします。これはADOでも機能します(例:access)

ソース:自分で試した


3

MariaDBはこれを10.3.xから解除しました(DELETEおよびの両方UPDATE):

UPDATE-ソースとターゲットが同じステートメント

MariaDB 10.3.2以降、UPDATEステートメントのソースとターゲットが同じになる場合があります。

MariaDB 10.3.1までは、次のUPDATEステートメントは機能しませんでした。

UPDATE t1 SET c1=c1+1 WHERE c2=(SELECT MAX(c2) FROM t1);
  ERROR 1093 (HY000): Table 't1' is specified twice, 
  both as a target for 'UPDATE' and as a separate source for data

MariaDB 10.3.2以降、ステートメントは正常に実行されます。

UPDATE t1 SET c1=c1+1 WHERE c2=(SELECT MAX(c2) FROM t1);

DELETE-同じソーステーブルとターゲットテーブル

MariaDB 10.3.1までは、同じソースとターゲットを持つテーブルから削除することはできませんでした。MariaDB 10.3.1から、これが可能になりました。例えば:

DELETE FROM t1 WHERE c1 IN (SELECT b.c1 FROM t1 b WHERE b.c2=0);

DBFiddle MariaDB 10.2-エラー

DBFiddle MariaDB 10.3-成功


0

他の回避策には、サブクエリでのSELECT DISTINCTまたはLIMITの使用が含まれますが、これらは実体化への影響がそれほど明確ではありません。これは私のために働いた

MySql Docで述べたように


0

MySQLでは、テーブルからの選択と、同じテーブルでの更新を同時に行うことはできません。しかし、常に回避策があります:)

これは動作しません>>>>

UPDATE table1 SET col1 = (SELECT MAX(col1) from table1) WHERE col1 IS NULL;

しかし、これは動作します>>>>

UPDATE table1 SET col1 = (SELECT MAX(col1) FROM (SELECT * FROM table1) AS table1_new) WHERE col1 IS NULL;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.