MySQL Error 1093 - Не удается указать целевую таблицу для обновления в предложении FROM

473

У меня есть таблица story_category в моей базе данных с коррумпированными записями. Следующий запрос возвращает коррумпированные записи:

SELECT * 
FROM  story_category 
WHERE category_id NOT IN (
    SELECT DISTINCT category.id 
    FROM category INNER JOIN 
       story_category ON category_id=category.id);

Я попытался удалить их, выполнив:

DELETE FROM story_category 
WHERE category_id NOT IN (
    SELECT DISTINCT category.id 
    FROM category 
      INNER JOIN story_category ON category_id=category.id);

Но я получаю следующую ошибку:

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

Как я могу это преодолеть?

Показать ещё 1 комментарий
Теги:
sql-delete
subquery
mysql-error-1093

14 ответов

612
Лучший ответ

Обновление: этот ответ охватывает общую классификацию ошибок. Для более конкретного ответа о том, как лучше всего обрабатывать точный запрос OP, см. Другие ответы на этот вопрос.

В MySQL вы не можете изменить ту же таблицу, которую используете в части SELECT.
Это задокументировано по адресу: http://dev.mysql.com/doc/refman/5.6/en/update.html

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

Если логика достаточно проста, чтобы переформатировать запрос, потеряйте подзапрос и присоединитесь к таблице для себя, используя соответствующие критерии выбора. Это заставит MySQL видеть таблицу как две разные вещи, что позволяет разрушительным изменениям идти вперед.

UPDATE tbl AS a
INNER JOIN tbl AS b ON ....
SET a.col = b.col

В качестве альтернативы, попробуйте вложить подзапрос глубже в предложение from...

Если вам абсолютно необходим подзапрос, есть обходное решение, но это уродливый по нескольким причинам, включая производительность:

UPDATE tbl SET col = (
  SELECT ... FROM (SELECT.... FROM) AS x);

Вложенный подзапрос в предложении FROM создает неявный временный таблицу, поэтому она не считается той же таблицей, которую вы обновляете.

... но обратите внимание на оптимизатор запросов

Однако будьте осторожны, что из MySQL 5.7.6 и далее оптимизатор может оптимизировать подзапрос и все равно дать вам ошибку. К счастью, переменную optimizer_switch можно использовать для отключения этого поведения; хотя я не мог рекомендовать делать это как нечто большее, чем краткосрочное исправление, или для небольших одноразовых задач.

SET optimizer_switch = 'derived_merge=off';

Благодаря Peter V. Mørch за этот совет в комментариях.

Пример техники был от Барона Шварца, изначально опубликован в Nabble, перефразированный и расширенный здесь.

  • 1
    Проголосовал этот ответ, потому что я должен был удалить элементы и не мог получить информацию из другой таблицы, пришлось подзапрос из той же таблицы. Поскольку это то, что всплывает наверху, когда я ищу ошибку, которую я получил, это будет наилучшим ответом для меня и многих людей, пытающихся обновить данные, выполняя запросы из одной и той же таблицы.
  • 2
    @Cheekysoft, почему бы не сохранить значения в переменных вместо этого?
Показать ещё 5 комментариев
207

NexusRex предоставлено очень хорошее решение для удаления с помощью соединения из той же таблицы.

Если вы это сделаете:

DELETE FROM story_category
WHERE category_id NOT IN (
        SELECT DISTINCT category.id AS cid FROM category 
        INNER JOIN story_category ON category_id=category.id
)

вы получите сообщение об ошибке.

Но если вы завернете условие еще одним выбором

DELETE FROM story_category
WHERE category_id NOT IN (
    SELECT cid FROM (
        SELECT DISTINCT category.id AS cid FROM category 
        INNER JOIN story_category ON category_id=category.id
    ) AS c
)

он поступил бы правильно!

Объяснение: Оптимизатор запросов выполняет производную оптимизацию слияния для первого запроса (что приводит к сбою с ошибка), но второй запрос не подходит для производной оптимизации слияния, поэтому оптимизатор вынужден сначала выполнить подзапрос.

  • 2
    Может быть, это потому, что я в безвыходном положении сегодня, но это был самый простой ответ, даже если, возможно, он не самый лучший.
  • 4
    Это сработало отлично, спасибо! Так в чем тут логика? Если он вложен еще на один уровень, то он будет выполнен перед внешней частью? И если он не является вложенным, то MySQL пытается запустить его после блокировки удаления таблицы?
Показать ещё 8 комментариев
103

inner join в вашем подзапросе нет необходимости. Похоже, вы хотите удалить записи в story_category, где category_id не находится в таблице category.

Сделайте это:

DELETE FROM story_category 
WHERE category_id NOT IN (
    SELECT DISTINCT category.id 
    FROM category);

Вместо этого:

DELETE FROM story_category 
WHERE category_id NOT IN (
    SELECT DISTINCT category.id 
    FROM category INNER JOIN
         story_category ON category_id=category.id);
  • 5
    Это должен быть главный ответ! Может быть, удалить первый «вместо».
  • 1
    и исправить блок кода
Показать ещё 3 комментария
82

Недавно мне пришлось обновлять записи в той же таблице, я сделал это, как показано ниже:

UPDATE skills AS s, (SELECT id  FROM skills WHERE type = 'Programming') AS p
SET s.type = 'Development' 
WHERE s.id = p.id;
  • 11
    Разве это не может быть просто записано как UPDATE skills SET type='Development' WHERE type='Programming'; ? Это, кажется, не отвечает на оригинальный вопрос.
  • 1
    Похоже на излишество, @lilbyrdie верен - это могут быть только UPDATE skills SET type='Development' WHERE type='Programming'; , Я не понимаю, почему так много людей не задумываются о том, что они делают ...
Показать ещё 6 комментариев
28
DELETE FROM story_category
WHERE category_id NOT IN (
    SELECT cid FROM (
        SELECT DISTINCT category.id AS cid FROM category INNER JOIN story_category ON category_id=category.id
    ) AS c
)
  • 3
    Можете ли вы объяснить, почему это работает, почему просто вкладывает еще один уровень? Этот вопрос уже задавался как комментарий к вопросу @ EkoNoval, но никто не ответил. Может быть, вы можете помочь.
  • 0
    @AkshayArora, в ответе @ Cheekysoft просмотрите часть под заголовком «Может быть, вы можете просто присоединиться к столу». Он сказал UPDATE tbl AS a INNER JOIN tbl AS b ON .... SET a.col = b.col - это будет работать, так как здесь используется другой псевдоним для той же таблицы. Аналогично, в ответе @ NexusRex первый запрос SELECT действует как производная таблица, в которую story_category используется во второй раз. Таким образом, ошибка, упомянутая в ОП, не должна иметь место, верно?
11

Это то, что я сделал для обновления значения столбца Priority на 1, если он равен >= 1 в таблице и в его предложении WHERE, используя подзапрос в той же таблице, чтобы удостовериться, что хотя бы одна строка содержит Priority = 1 (потому что это условие должно быть проверено при выполнении обновления):


UPDATE My_Table
SET Priority=Priority + 1
WHERE Priority >= 1
AND (SELECT TRUE FROM (SELECT * FROM My_Table WHERE Priority=1 LIMIT 1) as t);

Я знаю, это немного уродливо, но все работает нормально.

  • 1
    @anonymous_reviewer: В случае предоставления [-1] или даже [+1] чьему-либо комментарию, пожалуйста, также укажите, почему вы его дали. Спасибо!!!
  • 1
    -1 потому что это неверно. Вы не можете изменить ту же таблицу, которую используете в операторе SELECT.
Показать ещё 6 комментариев
10

Если вы не можете сделать

UPDATE table SET a=value WHERE x IN
    (SELECT x FROM table WHERE condition);

потому что это та же таблица, вы можете обмануть и сделать:

UPDATE table SET a=value WHERE x IN
    (SELECT * FROM (SELECT x FROM table WHERE condition) as t)

[обновить или удалить или что-то еще]

4

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

который может быть тем, что @Cheekysoft подразумевал, делая это в два этапа.

3

В соответствии с синтаксисом Mysql UPDATE, связанным с @CheekySoft, он говорит прямо внизу.

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

Я думаю, что вы удаляете из store_category, все еще выбираете из него в объединении.

1

Попробуйте сохранить результат оператора Select в отдельной переменной, а затем используйте это для запроса на удаление.

1

Если что-то не сработает, когда выходите через входную дверь, тогда возьмите заднюю дверь:

drop table if exists apples;
create table if not exists apples(variety char(10) primary key, price int);

insert into apples values('fuji', 5), ('gala', 6);

drop table if exists apples_new;
create table if not exists apples_new like apples;
insert into apples_new select * from apples;

update apples_new
    set price = (select price from apples where variety = 'gala')
    where variety = 'fuji';
rename table apples to apples_orig;
rename table apples_new to apples;
drop table apples_orig;

Это быстро. Чем больше данных, тем лучше.

  • 9
    И вы только что потеряли все свои внешние ключи, и, возможно, у вас также есть несколько каскадных удалений.
0

попробуйте это

DELETE FROM story_category 
WHERE category_id NOT IN (
SELECT DISTINCT category.id 
FROM (SELECT * FROM STORY_CATEGORY) sc;
0

Самый простой способ сделать это - использовать псевдоним таблицы, когда вы ссылаетесь на родительскую таблицу запросов внутри подзапроса.

Пример:

insert into xxx_tab (trans_id) values ((select max(trans_id)+1 from xxx_tab));

Измените его на:

insert into xxx_tab (trans_id) values ((select max(P.trans_id)+1 from xxx_tab P));
0

как насчет этого запроса надеюсь, что он поможет

DELETE FROM story_category LEFT JOIN (SELECT category.id FROM category) cat ON story_category.id = cat.id WHERE cat.id IS NULL
  • 0
    Результат показывает: «# 1064 - у вас ошибка в синтаксисе SQL; проверьте руководство, соответствующее вашей версии сервера MariaDB, на предмет правильного синтаксиса, который можно использовать рядом с «LEFT JOIN (SELECT Categories.id ИЗ категорий) cat ON story_category.id = cat.» в строке 1 '

Ещё вопросы

Сообщество Overcoder
Наверх
Меню