Bạn không thể chỉ định bảng mục tiêu để cập nhật trong mệnh đề TỪ


380

Tôi có một bảng mysql đơn giản:

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

Tôi đã cố chạy bản cập nhật sau, nhưng tôi chỉ nhận được lỗi 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
))

Tôi đã tìm kiếm lỗi và tìm thấy từ trang mysql sau http://dev.mysql.com/doc/refman/5.1/en/subquery-restrictions.html , nhưng nó không giúp tôi.

Tôi phải làm gì để sửa truy vấn sql?


Câu trả lời:


769

Vấn đề là MySQL, vì bất kỳ lý do điên rồ nào, không cho phép bạn viết các truy vấn như thế này:

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

Đó là, nếu bạn đang thực hiện UPDATE/ INSERT/ DELETEtrên một bảng, bạn không thể tham chiếu bảng đó trong một truy vấn bên trong ( tuy nhiên bạn có thể tham chiếu một trường từ bảng bên ngoài đó ...)


Giải pháp là thay thế thể hiện của myTabletruy vấn phụ bằng (SELECT * FROM myTable), như thế này

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

Điều này rõ ràng khiến các trường cần thiết được sao chép hoàn toàn vào một bảng tạm thời, vì vậy nó được cho phép.

Tôi tìm thấy giải pháp này ở đây . Một lưu ý từ bài viết đó:

Bạn không muốn chỉ SELECT * FROM tabletrong truy vấn con trong cuộc sống thực; Tôi chỉ muốn giữ các ví dụ đơn giản. Trong thực tế, bạn chỉ nên chọn các cột bạn cần trong truy vấn trong cùng đó và thêm một WHEREmệnh đề tốt để giới hạn kết quả.


10
Tôi không nghĩ lý do là vô tri. Hãy suy nghĩ về ngữ nghĩa. MySQL phải giữ một bản sao của bảng trước khi bắt đầu cập nhật hoặc truy vấn bên trong có thể sử dụng dữ liệu đã được truy vấn cập nhật khi nó đang diễn ra. Cả hai tác dụng phụ này đều không nhất thiết là mong muốn, vì vậy đặt cược an toàn nhất là buộc bạn phải chỉ định điều gì sẽ xảy ra khi sử dụng bảng phụ.
siride

35
@siride: Các cơ sở dữ liệu khác, chẳng hạn như MSSQL hoặc Oracle, không có hạn chế tùy ý này
BlueRaja - Danny Pflughoeft

3
@ BlueRaja-DannyPflughoeft: không độc đoán. Đó là quyết định thiết kế hợp lý dựa trên chi phí thay thế. Các hệ thống DB khác đã chọn xử lý các chi phí đó. Nhưng những hệ thống đó không, ví dụ, cho phép bạn đưa các cột không tổng hợp vào danh sách CHỌN khi bạn sử dụng GROUP BY và MySQL thì có. Tôi tranh luận rằng MySQL sai ở đây và tôi có thể nói giống như các DBMS khác cho các câu lệnh CẬP NHẬT.
siride

33
@siride Từ quan điểm đại số quan hệ, T(SELECT * FROM T)hoàn toàn tương đương. Họ có cùng quan hệ. Do đó đây là một hạn chế tùy ý, inane. Cụ thể hơn, đó là một cách giải quyết để ép buộc MySQL thực hiện một việc mà nó rõ ràng có thể làm, nhưng vì một số lý do, nó không thể phân tích cú pháp ở dạng đơn giản hơn.
Tobia

4
Trong trường hợp của tôi, giải pháp được chấp nhận không hoạt động vì bảng của tôi quá lớn. Các truy vấn không bao giờ hoàn thành. Rõ ràng điều này đang lấy quá nhiều nội lực. Thay vào đó, tôi đã tạo Chế độ xem với truy vấn bên trong và sử dụng nó để chọn dữ liệu, hoạt động hoàn toàn tốt. DELETE FROM t WHERE tableID NOT IN (SELECT viewID FROM t_view);Ngoài ra tôi khuyên bạn nên chạy OPTIMIZE TABLE t;sau đó để giảm kích thước của bảng.
CodeX

53

Bạn có thể thực hiện điều này trong ba bước:

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;

hoặc là

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
Vâng, hầu hết các truy vấn con có thể được viết lại dưới dạng nhiều bước với các CREATE TABLEcâu lệnh - Tôi hy vọng tác giả nhận thức được điều này. Tuy nhiên, đây có phải là giải pháp duy nhất? Hoặc truy vấn có thể được viết lại với các truy vấn con hoặc tham gia? Và tại sao (không) làm điều đó?
Konerak

Tôi nghĩ rằng bạn có một lỗi viết hoa trong giải pháp thứ hai của bạn. Không nên UPDATE Pers Pđọc UPDATE pers P?
ubiquibacon

2
Đã thử giải pháp này và đối với một số lượng lớn các mục trong bảng tạm thời / giây, truy vấn có thể rất chậm; hãy thử tạo bảng tạm thời / thứ hai bằng một chỉ mục / khóa chính [xem dev.mysql.com/doc/refman/5.1/en/create-table-select.html ]
Alex

Như @Konerak tuyên bố, đây thực sự không phải là câu trả lời hay nhất. Câu trả lời từ BlueRaja dưới đây có vẻ tốt nhất với tôi. Upvotes dường như đồng ý.
ShatyUT

@Konerak, Không CREATE TABLE AS SELECTcho hiệu suất khủng khiếp?
Pacerier 24/2/2015

27

Trong Mysql, bạn không thể cập nhật một bảng bằng cách truy vấn cùng một bảng.

Bạn có thể tách truy vấn thành hai phần hoặc làm

 CẬP NHẬT TABLE_A NHƯ A
 THAM GIA TABLE_A NHƯ B TRÊN A.field1 = B.field1
 Trường SET2 =? 

5
SELECT ... SET? Tôi chưa bao giờ nghe về điều này.
Serge S.

@grisson Cảm ơn đã làm rõ. Bây giờ tôi hiểu lý do tại sao mệnh đề IN của tôi không hoạt động - Tôi đã nhắm mục tiêu vào cùng một bảng.
Anthony

2
... điều này dường như không thực sự hoạt động. Nó vẫn cho tôi cùng một lỗi.
BlueRaja - Daniel Pflughoeft

2
câu trả lời này thực sự làm điều đúng đắn và hiệu quả hơn, được sử dụng AS Btrên tài liệu tham khảo thứ hai TABLE_A. câu trả lời trong ví dụ được nâng cấp nhiều nhất có thể được đơn giản hóa bằng cách sử dụng AS Tthay vì tiềm năng không hiệu quả FROM (SELECT * FROM myTable) AS something, điều may mắn là trình tối ưu hóa truy vấn thường loại bỏ nhưng không phải lúc nào cũng có thể làm như vậy.
natbro

23

Tạo một bảng tạm thời (tempP) từ một truy vấn con

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
)

Tôi đã giới thiệu một tên riêng (bí danh) và đặt tên mới cho cột 'PersID' cho bảng tạm thời


Tại sao không chọn các giá trị thành các biến thay vì thực hiện các lựa chọn bên trong bên trong?
Pacerier 24/2/2015

SELECT ( SELECT MAX(gehalt * 1.05)..- đầu tiên SELECTkhông chọn bất kỳ cột.
Istiaque Ahmed

18

Nó khá đơn giản. Ví dụ: thay vì viết:

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

bạn nên viết

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

hoặc tương tự.


13

Cách tiếp cận được đăng bởi BlueRaja rất chậm Tôi đã sửa đổi nó khi tôi đang sử dụng để xóa các bản sao khỏi bảng. Trong trường hợp nó giúp bất cứ ai có bảng lớn Truy vấn gốc

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

Điều này đang mất nhiều thời gian hơn:

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

Giải pháp nhanh hơn

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

Thêm một bình luận nếu bạn đang downvote.
Ajak6


3

Nếu bạn đang cố đọc trườngA từ bảngA và lưu nó vào trườngB trên cùng một bảng, khi trườngc = fieldd bạn có thể muốn xem xét điều này.

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

Mã ở trên sao chép giá trị từ trườngA sang trườngB khi trường điều kiện đáp ứng điều kiện của bạn. điều này cũng hoạt động trong ADO (ví dụ: truy cập)

nguồn: cố gắng bản thân


3

MariaDB đã gỡ bỏ điều này bắt đầu từ 10.3.x (cả cho DELETEUPDATE):

CẬP NHẬT - Báo cáo có cùng nguồn và mục tiêu

Từ MariaDB 10.3.2, các câu lệnh CẬP NHẬT có thể có cùng nguồn và đích.

Cho đến MariaDB 10.3.1, câu lệnh CẬP NHẬT sau sẽ không hoạt động:

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

Từ MariaDB 10.3.2, câu lệnh thực thi thành công:

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

XÓA - Cùng một nguồn và bảng đích

Cho đến MariaDB 10.3.1, việc xóa khỏi một bảng có cùng nguồn và đích là không thể. Từ MariaDB 10.3.1, điều này hiện có thể. Ví dụ:

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

DBFiddle MariaDB 10.2 - Lỗi

DBFiddle MariaDB 10.3 - Thành công


0

Các cách giải quyết khác bao gồm sử dụng CHỌN DISTINCT hoặc GIỚI HẠN trong truy vấn con, mặc dù những điều này không rõ ràng về tác dụng của chúng đối với việc vật chất hóa. cái này làm việc cho tôi

như đã đề cập trong MySql Doc


0

MySQL không cho phép chọn từ một bảng và cập nhật trong cùng một bảng cùng một lúc. Nhưng luôn có một cách giải quyết :)

Điều này không hoạt động >>>>

UPDATE table1 SET col1 = (SELECT MAX(col1) from table1) WHERE col1 IS NULL;

Nhưng điều này hoạt động >>>>

UPDATE table1 SET col1 = (SELECT MAX(col1) FROM (SELECT * FROM table1) AS table1_new) WHERE col1 IS NULL;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.