คุณไม่สามารถระบุตารางเป้าหมายสำหรับการอัปเดตในข้อ FROM


380

ฉันมีตาราง 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: มันไม่ได้เป็นอะไร การตัดสินใจออกแบบที่สมเหตุสมผลนั้นขึ้นอยู่กับต้นทุนของทางเลือก ระบบฐานข้อมูลอื่น ๆ เลือกที่จะจัดการกับค่าใช้จ่ายเหล่านั้นอยู่ดี แต่ระบบเหล่านั้นไม่มีเช่นให้คุณรวมคอลัมน์ที่ไม่รวมในรายการที่เลือกเมื่อคุณใช้ GROUP BY และ MySQL ทำ ฉันยืนยันว่า MySQL ผิดที่นี่และฉันอาจพูดเช่นเดียวกับ DBMS อื่น ๆ สำหรับงบ 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แถลงการณ์ - ฉันหวังว่าผู้เขียนจะได้รับรู้ถึงสิ่งนี้ อย่างไรก็ตามนี่เป็นทางออกเดียวหรือไม่ หรือแบบสอบถามสามารถเขียนใหม่ด้วยแบบสอบถามย่อยหรือรวม? และทำไม (ไม่) ทำอย่างนั้น?
Konerak

ฉันคิดว่าคุณมีข้อผิดพลาดการใช้อักษรตัวพิมพ์ใหญ่ในโซลูชันที่สองของคุณ ไม่ควรUPDATE Pers Pอ่านUPDATE pers P?
ubiquibacon

2
พยายามแก้ปัญหานี้และสำหรับรายการจำนวนมากในตารางชั่วคราว / วินาทีแบบสอบถามอาจช้ามาก พยายามสร้างตารางชั่วคราว / วินาทีด้วยดัชนี / คีย์หลัก [ดูdev.mysql.com/doc/refman/5.1/en/create-table-select.html ]
อเล็กซ์

@Konerak กล่าวว่านี่ไม่ใช่คำตอบที่ดีที่สุด คำตอบจาก BlueRaja ด้านล่างนั้นดีที่สุดสำหรับฉัน โหวตขึ้นดูเหมือนจะเห็นด้วย
ShatyUT

@ Konerak ไม่ได้CREATE TABLE AS SELECTให้ประสิทธิภาพที่น่ากลัว?
Pacerier

27

ใน Mysql คุณไม่สามารถอัปเดตหนึ่งตารางโดยการสืบค้นย่อยในตารางเดียวกัน

คุณสามารถแยกคิวรีออกเป็นสองส่วนหรือทำ

 อัปเดต TABLE_A เป็น A
 เข้าร่วม Table_A เป็น B ใน A.field1 = B.field1
 SET field2 =? 

5
SELECT ... SET? ฉันไม่เคยได้ยินเกี่ยวกับเรื่องนี้
Serge S.

@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' สำหรับตารางชั่วคราว


ทำไมไม่เลือกค่าเป็นตัวแปรแทนที่จะเลือกด้านในด้านใน?
Pacerier

SELECT ( SELECT MAX(gehalt * 1.05)..- SELECTคอลัมน์แรกไม่ได้เลือกคอลัมน์ใด ๆ
Istiaque Ahmed

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 นั้นช้าฉันก็แก้ไขเพราะฉันใช้เพื่อลบข้อมูลที่ซ้ำกันออกจากตาราง ในกรณีที่มันช่วยใครก็ได้ที่มี Query Original Query

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)

เพิ่มความคิดเห็นหากคุณกำลัง downvoting
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));

โค้ดด้านบนจะคัดลอกค่าจาก fieldA ไปยัง fieldB เมื่อ condition-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


0

MySQL ไม่อนุญาตให้เลือกจากตารางและอัปเดตในตารางเดียวกันพร้อมกัน แต่มีวิธีแก้ปัญหาเสมอ :)

สิ่งนี้ใช้ไม่ได้ >>>>

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

แต่สิ่งนี้ใช้ได้ผล >>>>

UPDATE table1 SET col1 = (SELECT MAX(col1) FROM (SELECT * FROM table1) AS table1_new) WHERE col1 IS NULL;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.