ข้อผิดพลาดของ MySQL 1093 - ไม่สามารถระบุตารางเป้าหมายสำหรับการอัปเดตในส่วนคำสั่งได้


593

ฉันมีตาราง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' สำหรับการอัปเดตในส่วนคำสั่งได้

ฉันจะเอาชนะสิ่งนี้ได้อย่างไร



2
ดูเหมือนว่าคำขอคุณลักษณะในตัวติดตามบั๊ก MySQL อยู่ที่นี่: ไม่สามารถอัปเดตตารางและเลือกจากตารางเดียวกันในแบบสอบถามย่อย
Ben Creasy

คำตอบ:


713

อัปเดต: คำตอบนี้ครอบคลุมการจำแนกข้อผิดพลาดทั่วไป สำหรับคำตอบที่เฉพาะเจาะจงมากขึ้นเกี่ยวกับวิธีจัดการกับคำค้นหาที่แน่นอนของ OP ได้ดีที่สุดโปรดดูคำตอบอื่น ๆ ของคำถามนี้

ใน MySQL คุณไม่สามารถแก้ไขตารางเดียวกันกับที่คุณใช้ในส่วนที่เลือกได้
พฤติกรรมนี้มีการบันทึกไว้ที่: http://dev.mysql.com/doc/refman/5.6/th/update.html

บางทีคุณสามารถเข้าร่วมตารางกับตัวเองได้

หากตรรกะนั้นง่ายพอที่จะปรับรูปร่างแบบสอบถามใหม่ให้สูญเสียแบบสอบถามย่อยและเข้าร่วมตารางกับตัวเองโดยใช้เกณฑ์การเลือกที่เหมาะสม สิ่งนี้จะทำให้ MySQL มองเห็นตารางเป็นสองสิ่งที่แตกต่างกันทำให้เกิดการเปลี่ยนแปลงแบบทำลายล้างในอนาคต

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

หรือลองลองทำแบบสอบถามย่อยให้ลึกลงไปจาก a clause ...

หากคุณต้องการเคียวรี่ย่อยอย่างแน่นอนมีวิธีแก้ไข แต่ก็น่าเกลียดด้วยเหตุผลหลายประการรวมถึงประสิทธิภาพ:

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

แบบสอบถามย่อยที่ซ้อนกันในส่วนคำสั่ง FROM จะสร้างตารางชั่วคราวโดยนัยดังนั้นจึงไม่นับว่าเป็นตารางเดียวกับที่คุณกำลังอัปเดต

... แต่ระวังเครื่องมือเพิ่มประสิทธิภาพข้อความค้นหา

อย่างไรก็ตามระวังว่าตั้งแต่MySQL 5.7.6เป็นต้นไปเครื่องมือเพิ่มประสิทธิภาพอาจปรับให้เหมาะสมแบบสอบถามย่อยและยังให้ข้อผิดพลาด โชคดีที่ optimizer_switchตัวแปรสามารถใช้เพื่อปิดการทำงานนี้ แม้ว่าฉันจะไม่สามารถแนะนำให้ทำสิ่งนี้เป็นมากกว่าการแก้ไขปัญหาระยะสั้นหรือสำหรับงานที่ต้องทำเพียงเล็กน้อย

SET optimizer_switch = 'derived_merge=off';

ขอบคุณPeter V. Mørchสำหรับคำแนะนำนี้ในความคิดเห็น

ตัวอย่างเทคนิคมาจากบารอนชวาร์ตษ์เผยแพร่ครั้งแรกที่ Nabbleถอดความและขยายที่นี่


1
อัปเดตคำตอบนี้เพราะฉันต้องลบรายการและไม่สามารถรับข้อมูลจากตารางอื่นได้จึงต้องสืบค้นย่อยจากตารางเดียวกัน เนื่องจากนี่คือสิ่งที่ปรากฏขึ้นด้านบนในขณะที่ googling สำหรับข้อผิดพลาดฉันได้รับนี้จะเป็นคำตอบที่เหมาะสมที่สุดสำหรับฉันและผู้คนจำนวนมากพยายามที่จะอัปเดตในขณะที่ subquerieing จากตารางเดียวกัน
HMR

2
@Cheekysoft ทำไมไม่บันทึกค่าลงในตัวแปรแทน
Pacerier

19
ระวัง, จากMySQL 5.7.6 on , เครื่องมือเพิ่มประสิทธิภาพอาจปรับย่อยแบบสอบถามออกไปและยังคงให้ข้อผิดพลาดเว้นแต่คุณSET optimizer_switch = 'derived_merge=off';:-(
Peter V. Mørch

1
@ PeterV.Mørchกำลังติดตามmysqlserverteam.com/derived-tables-in-mysql-5-7ในกรณีที่มีการดำเนินการบางอย่างการผสานจะไม่เกิดขึ้น เช่นจัดเตรียมตารางดัมมี่ที่ได้รับด้วย LIMIT (ถึง inifity) และข้อผิดพลาดจะไม่เกิดขึ้น นั่นเป็นการแฮ็กที่ค่อนข้างดีและยังมีความเสี่ยงที่ MySQL รุ่นอนาคตจะสนับสนุนการรวมการสืบค้นกับ LIMIT หลังจากทั้งหมด
user2180613

คุณช่วยยกตัวอย่างแบบเต็มเกี่ยวกับวิธีแก้ปัญหานี้ได้ไหม อัปเดต tbl SET col = (เลือก ... จาก (เลือก .... จาก) เป็น 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
อาจเป็นเพราะฉันอยู่ในความผูกมัดในวันนี้ แต่นี่เป็นคำตอบที่ง่ายที่สุดแม้ว่ามันอาจจะไม่ใช่ "ดีที่สุด" ก็ตาม
Tyler V.

4
มันใช้งานได้ดีขอบคุณ! แล้วตรรกะนี่คืออะไร? ถ้ามันซ้อนกันอีกระดับหนึ่งมันจะถูกดำเนินการก่อนส่วนนอกหรือไม่ และถ้ามันไม่ซ้อนกัน mySQL ก็พยายามที่จะเรียกใช้หลังจากลบมีล็อคในตารางหรือไม่
Flat Cat

43
ข้อผิดพลาดและวิธีแก้ปัญหานี้ไม่สมเหตุสมผล ... แต่ใช้งานได้ บางครั้งผมสงสัยว่ายาเสพติด devs MySQL อยู่บน ...
Cerin

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
ฉันต้องบ้าแน่ ๆ คำตอบเป็นเช่นเดียวกับสิ่งที่ระบุไว้ในต้นฉบับ
Jeff Lowery

นี่คือคำตอบสำหรับฉันด้วย ไม่ต้องwhere inอยู่ในคอลัมน์ ID ดังนั้นคุณไม่จำเป็นต้องค้นหาตารางย่อยอีก
Richard

@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
ดูเหมือนว่า overkill @lbybyrdie นั้นถูกต้อง - อาจเป็นUPDATE skills SET type='Development' WHERE type='Programming';ได้ ผมไม่เข้าใจว่าทำไมผู้คนจำนวนมากไม่ได้คิดเกี่ยวกับสิ่งที่พวกเขาทำ ...
shadyyx

1
นี่คือคำตอบที่ดีที่สุดที่นี่ IMHO ไวยากรณ์เป็นเรื่องง่ายที่จะเข้าใจคุณสามารถใช้คำสั่งก่อนหน้านี้อีกครั้งและไม่ จำกัด เฉพาะบางกรณี
Steffen Winkler

2
แม้ว่ากรณีตัวอย่างจะเป็นที่น่าสงสัยหลักการก็คือสิ่งที่ดีที่สุดในการทำความเข้าใจและปรับใช้ในสถานการณ์จริง แบบสอบถามที่แสดงทำงานได้เนื่องจากการเข้าร่วมโดยนัยในรายการตารางสร้างตารางชั่วคราวสำหรับแบบสอบถามย่อยดังนั้นจึงให้ผลลัพธ์ที่มั่นคงและหลีกเลี่ยงการปะทะกันระหว่างการดึงข้อมูลและการปรับเปลี่ยนของตารางเดียวกัน
AnrDaemon

1
ไม่ว่าใครจะพูดอะไรเกี่ยวกับเรื่องนี้มากเกินไปก็ยังคงตอบคำถามในแง่ของชื่อ ข้อผิดพลาดที่ OP ดังกล่าวเกิดขึ้นก็เช่นกันเมื่อมีการพยายามอัปเดตโดยใช้ตารางเดียวกันในแบบสอบถามแบบซ้อน
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 แต่ไม่มีใครตอบ บางทีคุณสามารถช่วย
Akshay Arora

@AkshayArora ผ่านส่วนภายใต้หัวข้อ 'บางทีคุณสามารถเข้าร่วมโต๊ะกับตัวเอง' ในคำตอบของ @ Cheekysoft เขากล่าวว่าUPDATE tbl AS a INNER JOIN tbl AS b ON .... SET a.col = b.col- นี่จะทำงานเป็นนามแฝงที่แตกต่างกันสำหรับตารางเดียวกันที่ใช้ที่นี่ ในทำนองเดียวกันใน @ NexusRex คำตอบของSELECTแบบสอบถามแรกทำหน้าที่เป็นตารางที่ได้รับซึ่งstory_categoryจะใช้เป็นครั้งที่สอง ดังนั้นข้อผิดพลาดที่กล่าวถึงใน OP ไม่ควรเกิดขึ้นที่นี่ใช่ไหม
Istiaque Ahmed

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)

[อัพเดตหรือลบหรืออะไรก็ตาม]


การนำไปใช้ที่เข้าใจได้และเป็นตรรกะของคำตอบทั้งหมดข้างต้น ง่ายและตรงประเด็น
Clain Dsilva

นี่เป็นส่วนหนึ่งของเวิร์กโฟลว์ของฉันทันที ติดกับข้อผิดพลาดตรงไปที่คำตอบนี้และแก้ไขแบบสอบถาม ขอบคุณ
Vaibhav

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
Chris

1
@ Chris ฉันตรวจสอบแล้วใน MySQL และใช้งานได้ดีสำหรับฉันดังนั้นฉันจะขอให้คุณโปรดยืนยันตอนท้ายของคุณแล้วอ้างว่าถูกต้องหรือไม่ถูกต้อง ขอบคุณ !!!
sactiw

1
ที่ด้านล่างของหน้านี้มีข้อความระบุว่า 'ปัจจุบันคุณไม่สามารถอัปเดตตารางและเลือกจากตารางเดียวกันในแบบสอบถามย่อย' - และฉันพบว่าสิ่งนี้เป็นจริงในหลายโอกาส dev.mysql.com/doc/refman/5.0/en/update.html
Chris

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

ตามMysql UPDATE Syntax ที่ลิงก์โดย @CheekySoft ระบุว่าอยู่ที่ด้านล่างสุด

ขณะนี้คุณไม่สามารถอัปเดตตารางและเลือกจากตารางเดียวกันในแบบสอบถามย่อย

ฉันเดาว่าคุณกำลังลบจาก store_category ในขณะที่ยังคงเลือกจากในสหภาพ


3

สำหรับการสืบค้นเฉพาะทาง OP พยายามที่จะบรรลุวิธีที่ดีที่สุดและมีประสิทธิภาพมากที่สุดในการทำเช่นนี้คือไม่ต้องใช้แบบสอบถามย่อยเลย

ต่อไปนี้เป็นLEFT JOINข้อความค้นหาสองข้อความของ OP:

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
และคุณเพิ่งสูญเสียกุญแจต่างประเทศทั้งหมดของคุณและบางทีคุณก็มีการลบซ้อนด้วยเช่นกัน
Walf

2

ลองบันทึกผลลัพธ์ของคำสั่ง Select ในตัวแปรแยกต่างหากจากนั้นใช้สำหรับลบคิวรี



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 ของคุณเพื่อหาไวยากรณ์ที่ถูกต้องเพื่อใช้ใกล้กับ 'ซ้ายเข้าร่วม (หมวดหมู่ที่เลือก. จากหมวดหมู่) cat ON story_category.id = cat' ที่บรรทัด 1 '
Istiaque Ahmed

เมื่อใช้การลบหลายตารางคุณต้องระบุตารางที่ได้รับผลกระทบ 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
fyrye

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

ผู้ใช้งานคนนี้จะทำงานกับ MySQL ทุกรุ่นรวมถึงฐานข้อมูลอื่น ๆ ที่ฉันรู้จัก

การสาธิตเกี่ยวกับ DB Fiddle :

-- 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);
| category_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)
| category_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;
| category_id |
| ----------: |
| 4 |
| 5 |
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.