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


592

У мене 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

Як я можу це подолати?



2
Схоже, запит на функцію в трекері помилок MySQL знаходиться тут: не вдається оновити таблицю та вибрати з тієї ж таблиці в підзапиті
Ben Creasy

Відповіді:


713

Оновлення: Ця відповідь стосується загальної класифікації помилок. Для отримання більш конкретної відповіді про те, як найкраще впоратися з точним запитом ОП, будь ласка, дивіться інші відповіді на це питання

У MySQL ви не можете змінювати ту саму таблицю, яку ви використовуєте в частині SELECT.
Така поведінка задокументована на веб-сайті: http://dev.mysql.com/doc/refman/5.6/uk/update.html

Можливо, ви можете просто приєднати стіл до себе

Якщо логіка досить проста, щоб переформувати запит, втрачайте підзапит та приєднуйтесь до таблиці до себе, використовуючи відповідні критерії вибору. Це призведе до того, що MySQL побачить таблицю як дві різні речі, що дозволить руйнувати деструктивні зміни.

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

Крім того, спробуйте вкласти підзапит глибше в пункт з ...

Якщо вам абсолютно потрібен підзапит, є рішення, але це некрасиво з кількох причин, включаючи продуктивність:

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

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

... але стежте за оптимізатором запитів

Однак майте на увазі, що починаючи з MySQL 5.7.6 і далі, оптимізатор може оптимізувати підзапит і все-таки подати помилку. На щастя, optimizer_switchзмінна може бути використана для вимкнення такої поведінки; хоча я не міг рекомендувати робити це як щось більше, ніж короткострокове виправлення або для невеликих разових завдань.

SET optimizer_switch = 'derived_merge=off';

Дякую Петру В. Морчу за цю пораду в коментарях.

Приклад техніки був від барона Шварца, спочатку опублікованого в Nabble , перефразованого та поширеного тут.


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 , якщо певні операції проводяться, злиття не може відбутися. Наприклад, надайте похідній макетній таблиці обмеження (до inifity), і помилка ніколи не відбудеться. Це досить хакерно, і все ж є ризик, що майбутні версії MySQL підтримуватимуть запити, що об’єднуються з LIMIT.
користувач2180613

Чи можете ви, будь ласка, надати повний приклад цього рішення? ОНОВЛЕННЯ tbl SET col = (ВИБРАТИ ... ВІД (ВИБРАТИ .... ВІД) 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
)

це зробить правильно !!

Пояснення: Оптимізатор запитів виконує похідну оптимізацію злиття для першого запиту (що спричиняє його помилку з помилкою), але другий запит не відповідає вимогам похідної оптимізації злиття . Отже, оптимізатор змушений спочатку виконати запит.


6
Можливо, це тому, що я сьогодні зв'язаний, але це було найпростішою відповіддю, навіть якщо це, можливо, не "найкраще".
Тайлер В.

4
Це спрацювало чудово, дякую! То яка логіка тут? Якщо він вкладений ще на один рівень, то він буде виконаний перед зовнішньою частиною? І якщо він не вкладений, то mySQL намагається запустити його після того, як у видалення є блокування на столі?
Плоский кіт

43
Ця помилка та рішення не мають логічного сенсу ... але це працює. Іноді мені цікаво, на які наркотики діють
розробки

1
Погодьтеся з @Cerin .. це абсолютно абсурдно, але це працює.
FastTrack

@ekonoval Дякую за рішення, але це не має для мене мінімального сенсу, схоже, що ти обманюєш MySQL, і він приймає це, lol
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);

5
Це має бути головна відповідь! Можливо, видаліть перше "замість".
hoyhoy

1
Я вважаю DISTINCT, що тут непотрібно - для кращої роботи;).
shA.t

1
Я, мабуть, божевільний. Відповідь така ж, як було сказано в оригіналі.
Джефф Лоурі

Це відповідь і для мене. Не where inзнаходитесь у стовпці ідентифікатора, тож вам не потрібно підпитувати первинну таблицю.
Річард

@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
незважаючи на те, що хтось може сказати про це надмірності, він все ще відповідає на питання з точки зору назви. Помилка, про яку згадується в ОП, також виникає при спробі оновлення за допомогою тієї ж таблиці вкладеного запиту
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
)

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)

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


Найбільш зрозуміла і логічна реалізація всіх вищезазначених відповідей. Просте і суттєве.
Клін Дільва

Зараз це стало частиною мого робочого процесу. Зупиніться на цій помилці, перейдіть до цієї відповіді та виправте запит. Дякую.
Вайбхав

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);

Я знаю, що це трохи некрасиво, але це працює чудово.


1
@anonymous_reviewer: У разі надання [-1] або навіть [+1] комусь коментаря, будь ласка, зазначте, чому ви це дали. Дякую!!!
sactiw

1
-1 тому, що це неправильно. Ви не можете змінити ту саму таблицю, яку ви використовуєте в операторі SELECT.
Кріс

1
@Chris Я перевірив це на MySQL, і він працює для мене добре, тому я просив би вас підтвердити його в кінці, а потім стверджувати, що він правильний чи неправильний. Дякую!!!
sactiw

1
Внизу цієї сторінки написано: "Наразі ви не можете оновити таблицю та вибрати з тієї ж таблиці в підзапиті." - і я багато разів переживав це, як правда. dev.mysql.com/doc/refman/5.0/uk/update.html
Кріс,

19
@Chris Я це знаю, але для цього є вирішення, і саме це я намагався показати своїм запитом "UPDATE" і повірте, що він працює чудово. Я не думаю, що ви взагалі справді намагалися перевірити мій запит.
sactiw

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));

5

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

що може бути те, що мав на увазі @Cheekysoft, роблячи це в два етапи.


3

Відповідно до синтаксису UPDATE Mysql, пов’язаного @CheekySoft, він говорить прямо внизу.

Наразі не можна оновити таблицю та вибрати з тієї ж таблиці в підзапиті.

Я думаю, ви видаляєте з store_category, поки все ще вибираєте його в союзі.


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;

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
І ви щойно втратили всі ваші закордонні ключі, і, можливо, у вас теж є кілька каскадних делетів.
Вальф

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 (ВИБІР категорії.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
зауваживши

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);    

Цей quer запускається у будь-якій версії MySQL, а також у більшості інших баз даних, які я знаю.

Демонстрація у скрипті DB :

-- 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);
| категорія_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)
| категорія_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;
| категорія_id |
| ----------: |
| 4 |
| 5 |
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.