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


379

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

35
@siride: Інші бази даних, такі як MSSQL або Oracle, не мають цього довільного обмеження
BlueRaja - Danny Pflughoeft

3
@ BlueRaja-DannyPflughoeft: це не довільно. Це розумне дизайнерське рішення, засноване на витратах на альтернативи. Інші системи БД вирішили вирішити ці витрати. Але ці системи не дозволяють, наприклад, включати неагреговані стовпці до списків SELECT, коли ви використовуєте GROUP BY, і MySQL. Я можу стверджувати, що MySQL помиляється тут, і я можу сказати те саме про інші СУБД для операторів UPDATE.
siride

33
@siride З точки зору реляційної алгебри, Tі (SELECT * FROM T)цілком еквівалентні. Вони однакове відношення. Тому це довільне, неохайне обмеження. Більш конкретно, це вирішити спосіб змусити MySQL робити щось, що він однозначно може зробити, але чомусь він не може розібратися у своїй більш простій формі.
Тобія

4
У моєму випадку прийняте рішення не спрацювало, оскільки мій стіл був просто занадто великий. Запит так і не завершився. Мабуть, це забирає занадто багато внутрішніх ресурсів. Натомість я створив перегляд із внутрішнім запитом і використав його для вибору даних, який працював абсолютно чудово. 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?
ubiquibacon

2
Спробував це рішення і для великої кількості записів у тимчасовій / другій таблиці запит може бути дуже повільним; спробуйте створити тимчасову / другу таблицю з індексом / первинним ключем [див. dev.mysql.com/doc/refman/5.1/uk/create-table-select.html ]
Алекс

Як зазначає @Konerak, це насправді не найкраща відповідь. Відповідь BlueRaja нижче здається мені найкращою. Оголошення, схоже, згодні.
ShatyUT

@Konerak, це не CREATE TABLE AS SELECTдає жахливих показників?
Pacerier

27

У Mysql не можна оновити одну таблицю, підзапросивши ту саму таблицю.

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

 ОНОВЛЕННЯ ТАБЛИЦІ_A ЯК А
 ВНУТРІШНЯ ПРИЄДНАЙТЕСЬ ДО ТАБЛИ_А ЯК Б НА А.field1 = B.field1
 Поле SET2 =? 

5
SELECT ... SET? Я ніколи про це не чув.
Серж С.

@grisson Дякую за пояснення. Тепер я зрозумів, чому мій пункт IN не працює - я орієнтувався на ту саму таблицю.
Ентоні

2
... це, здається, насправді не працює. Це все ще дає мені ту саму помилку.
BlueRaja - Danny Pflughoeft

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" для тимчасової таблиці


Чому б не вибрати значення в змінні, а не зробити внутрішній внутрішній внутрішній вибір?
Пейсьєр

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

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

Якщо ви намагаєтесь прочитати 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));

Наведений вище код копіює значення з поляA в fieldB, коли поле field відповідає вашій умові. це також працює в 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);

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.