Nie można określić tabeli docelowej do aktualizacji w klauzuli FROM

379

Mam prostą tabelę 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);

Próbowałem uruchomić następującą aktualizację, ale pojawia się tylko błąd 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
))

Szukałem błędu i znalazłem na stronie mysql http://dev.mysql.com/doc/refman/5.1/en/subquery-restrictions.html , ale to mi nie pomaga.

Co mam zrobić, aby poprawić zapytanie SQL?

CSchulz
źródło

Odpowiedzi:

769

Problem polega na tym, że MySQL, z jakiegokolwiek bezsensownego powodu, nie pozwala ci pisać takich zapytań:

UPDATE myTable
SET myTable.A =
(
    SELECT B
    FROM myTable
    INNER JOIN ...
)

Oznacza to, że jeśli robisz UPDATE/ INSERT/ DELETEna stole, nie można odwołać się w tej tabeli zapytania wewnętrznej (ty może jednak odwołać się od tego zewnętrznego pola tabeli ...)


Rozwiązaniem jest zastąpienie wystąpienia myTablezapytania podrzędnego (SELECT * FROM myTable)takim, jak to

UPDATE myTable
SET myTable.A =
(
    SELECT B
    FROM (SELECT * FROM myTable) AS something
    INNER JOIN ...
)

To najwyraźniej powoduje, że niezbędne pola są domyślnie kopiowane do tabeli tymczasowej, więc jest to dozwolone.

Znalazłem to rozwiązanie tutaj . Uwaga z tego artykułu:

W SELECT * FROM tableprawdziwym życiu nie chcesz tylko w podzapytaniu; Chciałem tylko, aby przykłady były proste. W rzeczywistości powinieneś wybierać tylko kolumny, których potrzebujesz w tym najbardziej wewnętrznym zapytaniu, i dodawać dobrą WHEREklauzulę, aby ograniczyć również wyniki.

BlueRaja - Danny Pflughoeft
źródło
10
Nie sądzę, żeby powód był głupi. Pomyśl o semantyce. Albo MySQL musi zachować kopię tabeli przed rozpoczęciem aktualizacji, albo wewnętrzne zapytanie może wykorzystywać dane, które zostały już zaktualizowane przez zapytanie w toku. Żaden z tych efektów ubocznych nie jest koniecznie pożądany, więc najbezpieczniejszym zakładem jest zmuszenie cię do określenia, co się stanie przy użyciu dodatkowej tabeli.
Sir
35
@siride: Inne bazy danych, takie jak MSSQL lub Oracle, nie mają tego arbitralnego ograniczenia
BlueRaja - Danny Pflughoeft
3
@ BlueRaja-DannyPflughoeft: to nie jest arbitralne. Jest to rozsądna decyzja projektowa oparta na kosztach alternatyw. Pozostałe systemy DB i tak zdecydowały się poradzić sobie z tymi kosztami. Ale systemy te nie pozwalają np. Uwzględniać niezagregowanych kolumn na listach SELECT, gdy używasz GROUP BY, a MySQL tak. Twierdziłbym, że MySQL jest tutaj błędny i mógłbym powiedzieć to samo o innych DBMS dla instrukcji UPDATE.
siride
33
@siride Z punktu widzenia algebry relacyjnej T i (SELECT * FROM T)są całkowicie równoważne. Są tą samą relacją. Dlatego jest to arbitralne, bezmyślne ograniczenie. Mówiąc dokładniej, obejściem tego problemu jest zmuszenie MySQL do zrobienia czegoś, co wyraźnie można zrobić, ale z jakiegoś powodu nie może on przeanalizować w swojej prostszej formie.
Tobia,
4
W moim przypadku przyjęte rozwiązanie nie zadziałało, ponieważ mój stół był po prostu zbyt duży. Zapytanie nigdy się nie zakończyło. Najwyraźniej zajmuje to zbyt wiele zasobów wewnętrznych. Zamiast tego utworzyłem widok z wewnętrznym zapytaniem i użyłem go do wyboru danych, co działało absolutnie dobrze. DELETE FROM t WHERE tableID NOT IN (SELECT viewID FROM t_view);Polecam także biegać OPTIMIZE TABLE t;później, aby zmniejszyć rozmiar stołu.
CodeX
53

Możesz to zrobić w trzech krokach:

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;

lub

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
Michael Pakhantsov
źródło
16
Cóż, tak, większość podkwerend może być przepisana jako wiele kroków z CREATE TABLEinstrukcjami - mam nadzieję, że autor był tego świadomy. Czy to jednak jedyne rozwiązanie? Czy zapytanie może zostać przepisane z podzapytaniami lub złączeniami? A dlaczego (nie) to robić?
Konerak,
Myślę, że w drugim rozwiązaniu występuje błąd wielkich liter. Nie powinieneś UPDATE Pers Pczytać UPDATE pers P?
ubiquibacon
2
Wypróbowałem to rozwiązanie i dla dużej liczby wpisów w tabeli tymczasowej / drugiej zapytanie może być bardzo wolne; spróbuj utworzyć tymczasową / drugą tabelę z indeksem / kluczem podstawowym [patrz dev.mysql.com/doc/refman/5.1/en/create-table-select.html ]
Alex
Jak stwierdza @Konerak, to nie jest najlepsza odpowiedź. Odpowiedź z BlueRaja poniżej wydaje mi się najlepsza. Wydaje się, że entuzjaści się zgadzają.
ShatyUT
@Konerak, Nie CREATE TABLE AS SELECTdaje okropnej wydajności?
Pacerier
27

W Mysql nie można aktualizować jednej tabeli, subquery tej samej tabeli.

Możesz podzielić zapytanie na dwie części lub zrobić

 AKTUALIZACJA TABELI_A JAKO A
 WEWNĘTRZNA DOŁĄCZ TABELA_A AS B NA A.field1 = B.field1
 SET pole 2 =? 
Yuantao
źródło
5
SELECT ... SET? Nigdy o tym nie słyszałem.
Serge S.
@grisson Dzięki za wyjaśnienie. Teraz rozumiem, dlaczego moja klauzula IN nie działa - celowałem w tę samą tabelę.
Anthony
2
... to chyba nie działa. Nadal daje mi ten sam błąd.
BlueRaja - Danny Pflughoeft
2
ta odpowiedź faktycznie robi bardziej poprawną i wydajną rzecz, której używa AS Bdrugie odniesienie do TABLE_A. odpowiedź w najbardziej uprzywilejowanym przykładzie można uprościć, używając AS Tpotencjalnie nieefektywnego FROM (SELECT * FROM myTable) AS something, co na szczęście optymalizator zapytań zazwyczaj eliminuje, ale nie zawsze to robi.
natbro
23

Utwórz tabelę tymczasową (tempP) z podzapytania

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
)

Wprowadziłem osobną nazwę (alias) i nadałem nową nazwę kolumnie „persID” dla tabeli tymczasowej

Budda
źródło
Dlaczego nie wybrać wartości do zmiennych zamiast robić wewnętrzne wewnętrzne wewnętrzne selekcje?
Pacerier
SELECT ( SELECT MAX(gehalt * 1.05)..- pierwszy SELECTnie wybiera żadnej kolumny.
Istiaque Ahmed,
18

To całkiem proste. Na przykład zamiast pisać:

INSERT INTO x (id, parent_id, code) VALUES (
    NULL,
    (SELECT id FROM x WHERE code='AAA'),
    'BBB'
);

powinieneś pisać

INSERT INTO x (id, parent_id, code)
VALUES (
    NULL,
    (SELECT t.id FROM (SELECT id, code FROM x) t WHERE t.code='AAA'),
    'BBB'
);

lub podobne.

Ciemna strona
źródło
13

Podejście opublikowane przez BlueRaja jest powolne. Zmodyfikowałem je, ponieważ używałem do usuwania duplikatów z tabeli. W przypadku, gdy pomaga każdemu z dużymi tabelami Original Query

delete from table where id not in (select min(id) from table group by field 2)

Zajmuje to więcej czasu:

DELETE FROM table where ID NOT IN(
  SELECT MIN(t.Id) from (select Id,field2 from table) AS t GROUP BY field2)

Szybsze rozwiązanie

DELETE FROM table where ID NOT IN(
   SELECT x.Id from (SELECT MIN(Id) as Id from table GROUP BY field2) AS t)
Ajak6
źródło
Dodaj komentarz, jeśli oddajesz głos.
Ajak6
3

Jeśli próbujesz odczytać pole A z tabeli A i zapisać je w polu B w tej samej tabeli, gdy fieldc = fieldd możesz to rozważyć.

UPDATE tableA,
    tableA AS tableA_1 
SET 
    tableA.fieldB= tableA_1.filedA
WHERE
    (((tableA.conditionFild) = 'condition')
        AND ((tableA.fieldc) = tableA_1.fieldd));

Powyższy kod kopiuje wartość z pola A do pola B, gdy pole-warunek spełnia warunek. działa to również w ADO (np. dostęp)

źródło: próbowałem siebie

Krish
źródło
3

MariaDB zniosła to, zaczynając od 10.3.x (zarówno dla, jak DELETEi UPDATE):

AKTUALIZACJA - wyciągi z tym samym źródłem i celem

Od MariaDB 10.3.2 instrukcje UPDATE mogą mieć to samo źródło i cel.

Do MariaDB 10.3.1 następująca instrukcja UPDATE nie działała:

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

Od MariaDB 10.3.2 instrukcja wykonuje się pomyślnie:

UPDATE t1 SET c1=c1+1 WHERE c2=(SELECT MAX(c2) FROM t1);

DELETE - ta sama tabela źródłowa i docelowa

Do czasu wydania MariaDB 10.3.1 usunięcie tabeli z tym samym źródłem i celem nie było możliwe. Od MariaDB 10.3.1 jest to teraz możliwe. Na przykład:

DELETE FROM t1 WHERE c1 IN (SELECT b.c1 FROM t1 b WHERE b.c2=0);

DBFiddle MariaDB 10.2 - Błąd

DBFiddle MariaDB 10.3 - Sukces

Łukasz Szozda
źródło
0

Inne obejścia obejmują użycie SELECT DISTINCT lub LIMIT w podzapytaniu, chociaż nie mają one tak jednoznacznego wpływu na materializację. to zadziałało dla mnie

jak wspomniano w MySql Doc

PITU
źródło