Вы не можете указать целевую таблицу для обновления в предложении FROM

382

У меня есть простая таблица 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?

CSchulz
источник
1
Возможная копия MySQL Error 1093 - Не
Стив Чамберс

Ответы:

772

Проблема в том, что 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предложение, чтобы также ограничить результаты.

BlueRaja - Дэнни Пфлугхофт
источник
10
Я не думаю, что причина бессмысленна. Подумайте о семантике. Либо MySQL должен сохранять копию таблицы до начала обновления, либо внутренний запрос может использовать данные, которые уже были обновлены запросом в процессе его выполнения. Ни один из этих побочных эффектов не является желательным, поэтому самая безопасная ставка - заставить вас указать, что произойдет, используя дополнительную таблицу.
Сирида
35
@siride: другие базы данных, такие как MSSQL или Oracle, не имеют этого произвольного ограничения
BlueRaja - Дэнни Пфлугхофт
3
@ BlueRaja-DannyPflughoeft: это не произвольно. Это разумное дизайнерское решение, основанное на стоимости альтернатив. Другие системы БД все равно решили справиться с этими расходами. Но эти системы, например, не позволяют включать неагрегированные столбцы в списки SELECT, когда вы используете GROUP BY, а MySQL делает это. Я бы сказал, что MySQL здесь не так, и я мог бы сказать то же самое о других СУБД для операторов UPDATE.
Сирида
33
@siride С точки зрения реляционной алгебры, Tи (SELECT * FROM T)полностью эквивалентны. Это одинаковые отношения. Поэтому это произвольное, бессмысленное ограничение. Более конкретно, это обходной путь, чтобы заставить MySQL сделать что-то, что он может делать, но по какой-то причине он не может разобрать в своей более простой форме.
Тобия
4
В моем случае принятое решение не сработало, потому что мой стол был просто слишком большим. Запрос не завершен. По-видимому, это занимает слишком много внутренних ресурсов. Вместо этого я создал View с внутренним запросом и использовал его для выбора данных, который работал абсолютно нормально. DELETE FROM t WHERE tableID NOT IN (SELECT viewID FROM t_view);Также я рекомендую запустить OPTIMIZE TABLE t;потом, чтобы уменьшить размер таблицы.
CodeX
53

Вы можете сделать это в три этапа:

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утверждениями - я надеюсь, что автору было известно об этом. Однако это единственное решение? Или запрос может быть переписан с помощью подзапросов или объединений? И почему (не) это сделать?
Конерак
Я думаю, у вас есть ошибка капитализации в вашем втором решении. Не должен UPDATE Pers Pчитать UPDATE pers P?
Убиквибакон
2
Пробовал это решение и для большого количества записей во временной / второй таблице запрос может быть очень медленным; попробуйте создать временную / вторую таблицу с индексом / первичным ключом [см. dev.mysql.com/doc/refman/5.1/en/create-table-select.html ]
Алекс
Как утверждает @Konerak, это не самый лучший ответ. Ответ от BlueRaja ниже кажется мне лучшим. Upvotes, кажется, согласны.
ШатЮТ
@Konerak, CREATE TABLE AS SELECTужасного представления не дает?
Pacerier
27

В Mysql вы не можете обновить одну таблицу, выполнив подзапрос этой же таблицы.

Вы можете разделить запрос на две части или сделать

 ОБНОВЛЕНИЕ ТАБЛИЦА_А КАК
 INNER JOIN TABLE_A AS B ON A.field1 = B.field1
 SET field2 =? 
Yuantao
источник
5
SELECT ... SET? Я никогда не слышал об этом.
Серж С.
@grisson Спасибо за разъяснения. Теперь я понимаю, почему мое предложение IN не работает - я нацелился на ту же таблицу.
Энтони
2
... это не похоже на работу. Это все еще дает мне ту же ошибку.
BlueRaja - Дэнни Пфлюгофт
2
этот ответ на самом деле делает более правильную и эффективную вещь, которая используется AS Bво второй ссылке на TABLE_A. ответ в примере с наибольшим количеством голосов можно упростить, используя AS Tвместо потенциально неэффективного FROM (SELECT * FROM myTable) AS something, который, к счастью, оптимизатор запросов обычно устраняет, но не всегда делает это.
natbro
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» для временной таблицы.

Budda
источник
Почему бы не выбрать значения в переменные вместо того, чтобы делать внутренние внутренние внутренние выборки?
Pacerier
SELECT ( SELECT MAX(gehalt * 1.05)..- первый SELECTне выбирает ни одного столбца.
Истак Ахмед
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, медленный, я изменил его, так как использовал для удаления дубликатов из таблицы. В случае, если это помогает кому-либо с большими столами Original Query

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
источник
Добавить комментарий, если вы отрицаете.
Ajak6
3

Если вы пытаетесь прочитать fieldA из tableA и сохранить его в 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));

Код выше копирует значение из fieldA в fieldB, когда условие-поле удовлетворяет вашему условию. это также работает в ADO (например, доступ)

Источник: попробовал сам

Криш
источник
3

MariaDB поднял это, начиная с 10.3.x (и для DELETEи 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);

УДАЛИТЬ - Тот же источник и таблица целей

До 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;
Хари Дас
источник