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

594

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

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

Серхио дель Амо
источник
3
Связанный: stackoverflow.com/a/14302701/238419
BlueRaja - Дэнни Пфлугхофт
2
Похоже, что запрос функции в трекере ошибок MySQL находится здесь: невозможно обновить таблицу и выбрать из этой же таблицы в подзапросе
Бен

Ответы:

714

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

В 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 за этот совет в комментариях.

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

Cheekysoft
источник
1
Проголосовал этот ответ, потому что я должен был удалить элементы и не мог получить информацию из другой таблицы, пришлось подзапрос из той же таблицы. Поскольку это то, что всплывает наверху, когда я ищу ошибку, которую я получил, это было бы наилучшим ответом для меня и многих людей, пытающихся обновиться, выполняя запросы из одной и той же таблицы.
HMR
2
@Cheekysoft, почему бы не сохранить значения в переменных вместо этого?
Pacerier
19
Имейте в виду , что начиная с MySQL 5.7.6 оптимизатор может оптимизировать подзапрос и по-прежнему выдавать ошибку, если только вы SET optimizer_switch = 'derived_merge=off';:-(
Peter V. Mørch
1
@ PeterV.Mørch Следуя mysqlserverteam.com/derived-tables-in-mysql-5-7 , в случае выполнения определенных операций объединение не может произойти. Например, предоставьте производной фиктивной таблице LIMIT (в единицу), и ошибка никогда не произойдет. Это довольно хакерски, хотя есть риск, что будущие версии MySQL будут поддерживать объединение запросов с LIMIT.
user2180613
Можете ли вы привести полный пример этого обходного пути? ОБНОВЛЕНИЕ tbl SET col = (SELECT ... FROM (SELECT .... FROM) AS x); я все еще получаю ошибки
JoelBonetR
310

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
)

это сделало бы правильную вещь !!

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

Ekonoval
источник
6
Может быть, это потому, что я в безвыходном положении сегодня, но это был самый простой ответ, даже если, возможно, он не самый лучший.
Тайлер В.
4
Это сработало отлично, спасибо! Так в чем тут логика? Если он вложен еще на один уровень, то он будет выполнен перед внешней частью? И если он не является вложенным, то MySQL пытается запустить его после блокировки удаления таблицы?
Плоская кошка
43
Эта ошибка и решение не имеют никакого логического смысла ... но это работает. Иногда я задаюсь вопросом, какие наркотики есть у разработчиков MySQL ...
Cerin
1
Согласитесь с @Cerin .. это абсолютно абсурдно, но это работает.
FastTrack
@ekonoval Спасибо за решение, но оно не имеет для меня минимального смысла, похоже, что вы обманываете MySQL, и он это принимает, лол
deFreitas
106

В 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);
shA.t
источник
5
Это должен быть главный ответ! Может быть, удалить первый «вместо».
Хойхой
1
Я думаю, что DISTINCTздесь нет необходимости - для лучшей производительности;).
SHA.T
1
Я должен быть сумасшедшим. Ответ такой же, как было заявлено в оригинале.
Джефф Лоури
Это ответ и для меня тоже. Не указывайте where inв столбце ID, поэтому вам не нужно запрашивать первичную таблицу.
Ричард
@JeffLowery - первый блок кода является ответом здесь; это второй блок кода из вопроса.
ToolmakerSteve
95

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

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';? Это, кажется, не отвечает на оригинальный вопрос.
lilbyrdie
1
Похоже, излишне, @lilbyrdie правильно - это может быть только так UPDATE skills SET type='Development' WHERE type='Programming';. Я не понимаю, почему так много людей не думают о том, что они делают ...
shadyyx
1
это лучший ответ здесь, ИМХО. Его синтаксис легко понять, вы можете повторно использовать свое предыдущее утверждение, и оно не ограничивается каким-то супер специфическим случаем.
Штеффен Винклер,
2
Даже если данный пример сомнителен, этот принцип лучше всего понять и применить в реальных сценариях. Показанный запрос работает, потому что неявное соединение в списке таблиц создает временную таблицу для подзапроса, таким образом обеспечивая стабильные результаты и избегая столкновения между извлечением и изменением одной и той же таблицы.
AnrDaemon
1
независимо от того, что кто-то может сказать об этом излишестве, он все равно отвечает на вопрос с точки зрения названия. Ошибка, о которой упоминал OP, также встречается при попытке обновления с использованием той же таблицы во вложенном запросе
smac89
35
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
)
NexusRex
источник
3
Можете ли вы объяснить, почему это работает, почему просто вкладывает еще один уровень? Этот вопрос уже задавался как комментарий к вопросу @ EkoNoval, но никто не ответил. Может быть, вы можете помочь.
Акшай Арора
@AkshayArora, в ответе @ Cheekysoft просмотрите часть под заголовком «Может быть, вы можете просто присоединиться к столу». Он сказал UPDATE tbl AS a INNER JOIN tbl AS b ON .... SET a.col = b.col- это будет работать, так как здесь используется другой псевдоним для той же таблицы. Аналогично в ответе @ NexusRex первый SELECTзапрос действует как производная таблица, в которую story_categoryиспользуется второй раз. Таким образом, ошибка, упомянутая в ОП, не должна иметь место здесь, верно?
Истак Ахмед
31

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

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)

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

Sequoya
источник
Наиболее понятна и логична реализация всех вышеперечисленных ответов. Просто и по существу.
Клин Дсилва
Теперь это стало частью моего рабочего процесса. Застряли на этой ошибке, перейдите к этому ответу и исправьте запрос. Спасибо.
Вайбхав
13

Это то, что я сделал для обновления значения столбца Приоритет на 1, если оно равно> = 1 в таблице и в его предложении WHERE, используя подзапрос к той же таблице, чтобы убедиться, что хотя бы одна строка содержит Приоритет = 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);

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

sactiw
источник
1
@anonymous_reviewer: В случае предоставления [-1] или даже [+1] чьему-либо комментарию, пожалуйста, также укажите, почему вы его дали. Спасибо!!!
День святого Валентина
1
-1 потому что это неверно. Вы не можете изменить ту же таблицу, которую используете в операторе SELECT.
Крис
1
@Chris Я проверил его на MySQL, и он отлично работает для меня, поэтому я прошу вас проверить его с вашей стороны, а затем заявить, что он правильный или неправильный. Спасибо!!!
Сакхив
1
Внизу этой страницы написано: «В настоящее время вы не можете обновить таблицу и выбрать одну и ту же таблицу в подзапросе». - и я испытал это, чтобы быть правдой во многих случаях. dev.mysql.com/doc/refman/5.0/ru/update.html
Крис,
19
@ Крис, я знаю это, но для этого есть обходной путь, и именно это я и попытался показать с помощью своего запроса «ОБНОВЛЕНИЕ», и, поверьте, он работает просто отлично. Я не думаю, что вы действительно пытались проверить мой запрос вообще.
Саквояж
6

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

Пример :

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));
S.Roshanth
источник
5

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

что может быть то, что @Cheekysoft имел в виду, делая это в два шага.

YonahW
источник
3

Согласно синтаксису ОБНОВЛЕНИЯ Mysql, связанному с @CheekySoft, он написан прямо внизу.

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

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

briankip
источник
3

Для конкретного запроса, который пытается выполнить OP, идеальный и наиболее эффективный способ сделать это - НЕ использовать подзапрос вообще.

Вот LEFT JOINверсии двух запросов ОП:

SELECT s.* 
FROM story_category s 
LEFT JOIN category c 
ON c.id=s.category_id 
WHERE c.id IS NULL;

Примечание: DELETE sограничивает операции удаления story_categoryтаблицей.
Документация

DELETE s 
FROM story_category s 
LEFT JOIN category c 
ON c.id=s.category_id 
WHERE c.id IS NULL;
Doin
источник
1
Удивлен, что это не имеет больше голосов за. Следует также отметить, что синтаксис нескольких таблиц также работает с UPDATEоператорами и присоединенными подзапросами. Позволяя вам выполнять LEFT JOIN ( SELECT ... )в противоположность WHERE IN( SELECT ... ), делая реализацию полезной во многих случаях использования.
fyrye
2

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

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;

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

Том Шефер
источник
11
И вы только что потеряли все свои внешние ключи, и, возможно, у вас также есть несколько каскадных удалений.
Walf
2

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

липика чакраборты
источник
2

попробуй это

DELETE FROM story_category 
WHERE category_id NOT IN (
SELECT DISTINCT category.id 
FROM (SELECT * FROM STORY_CATEGORY) sc;
Самир Ханал
источник
1

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

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

Что касается, вы хотите удалить строки, story_categoryкоторые не существуют в 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
);

В сочетании NOT INс подзапросом JOINисходная таблица выглядит излишне запутанной. Это может быть выражено более простым способом с not existsпомощью коррелированного подзапроса:

select sc.*
from story_category sc
where not exists (select 1 from category c where c.id = sc.category_id);

Теперь легко превратить это в deleteутверждение:

delete from story_category
where not exists (select 1 from category c where c.id = story_category.category_id);    

Этот запрос будет работать на любой версии MySQL, а также на большинстве других баз данных, которые я знаю.

Демо на БД Fiddle :

-- set-up
create table story_category(category_id int);
create table category (id int);
insert into story_category values (1), (2), (3), (4), (5);
insert into category values (4), (5), (6), (7);

-- your original query to identify offending rows
SELECT * 
FROM  story_category 
WHERE category_id NOT IN (
    SELECT DISTINCT category.id 
    FROM category INNER JOIN 
       story_category ON category_id=category.id);
| category_id |
| ----------: |
| 1 |
| 2 |
| 3 |
-- a functionally-equivalent, simpler query for this
select sc.*
from story_category sc
where not exists (select 1 from category c where c.id = sc.category_id)
| category_id |
| ----------: |
| 1 |
| 2 |
| 3 |
-- the delete query
delete from story_category
where not exists (select 1 from category c where c.id = story_category.category_id);

-- outcome
select * from story_category;
| category_id |
| ----------: |
| 4 |
| 5 |
GMB
источник