แบบสอบถาม SQL: ลบระเบียนทั้งหมดออกจากตารางยกเว้น N ล่าสุด?


91

เป็นไปได้ไหมที่จะสร้างแบบสอบถาม mysql เดียว (โดยไม่มีตัวแปร) เพื่อลบระเบียนทั้งหมดออกจากตารางยกเว้น N ล่าสุด (เรียงตาม id desc)

อะไรทำนองนี้มันใช้ไม่ได้ :)

delete from table order by id ASC limit ((select count(*) from table ) - N)

ขอบคุณ.

คำตอบ:


141

คุณไม่สามารถลบเรกคอร์ดด้วยวิธีนั้นปัญหาหลักคือคุณไม่สามารถใช้คิวรีย่อยเพื่อระบุค่าของประโยค LIMIT

ใช้งานได้ (ทดสอบใน MySQL 5.0.67):

DELETE FROM `table`
WHERE id NOT IN (
  SELECT id
  FROM (
    SELECT id
    FROM `table`
    ORDER BY id DESC
    LIMIT 42 -- keep this many records
  ) foo
);

จำเป็นต้องมีการสืบค้นย่อยระดับกลาง หากไม่มีเราจะพบข้อผิดพลาดสองประการ:

  1. ข้อผิดพลาดของ SQL (1093): คุณไม่สามารถระบุตารางเป้าหมาย 'ตาราง' สำหรับการอัปเดตในส่วนคำสั่ง FROM - MySQL ไม่อนุญาตให้คุณอ้างถึงตารางที่คุณกำลังลบจากภายในแบบสอบถามย่อยโดยตรง
  2. ข้อผิดพลาดของ SQL (1235): MySQL เวอร์ชันนี้ยังไม่รองรับ 'LIMIT & IN / ALL / ANY / SOME subquery' - คุณไม่สามารถใช้ LIMIT clause ภายในแบบสอบถามย่อยโดยตรงของตัวดำเนินการ NOT IN

โชคดีที่การใช้แบบสอบถามย่อยระดับกลางช่วยให้เราสามารถข้ามข้อ จำกัด ทั้งสองนี้ได้


Nicole ได้ชี้ให้เห็นว่าข้อความค้นหานี้สามารถปรับให้เหมาะสมได้อย่างมีนัยสำคัญสำหรับกรณีการใช้งานบางกรณี (เช่นกรณีนี้) ฉันขอแนะนำให้อ่านคำตอบนั้นด้วยเพื่อดูว่าเหมาะกับคุณหรือไม่


4
โอเคใช้งานได้ - แต่สำหรับฉันแล้วมันไม่สง่างามและไม่น่าพอใจที่ต้องใช้กลอุบายลึกลับแบบนั้น +1 อย่างไรก็ตามสำหรับคำตอบ
Bill Karwin

1
ฉันทำเครื่องหมายว่าเป็นคำตอบที่ยอมรับเพราะทำในสิ่งที่ฉันขอ แต่โดยส่วนตัวแล้วฉันอาจจะทำในสองแบบสอบถามเพื่อให้มันง่าย :) ฉันคิดว่าอาจมีวิธีที่ง่ายและรวดเร็ว
serg

1
ขอบคุณอเล็กซ์คำตอบของคุณช่วยฉัน ฉันเห็นว่าจำเป็นต้องมีการสืบค้นย่อยระดับกลาง แต่ฉันไม่เข้าใจว่าทำไม คุณมีคำอธิบายสำหรับสิ่งนั้นหรือไม่?
Sv1

9
คำถาม: "foo" มีไว้ทำอะไร?
Sebastian Breit

9
Perroloco ฉันลองโดยไม่มี foo และได้รับข้อผิดพลาดนี้: ERROR 1248 (42000): ทุกตารางที่ได้มาต้องมีนามแฝงของตัวเองดังนั้นคำตอบของเราทุกตารางที่ได้มาจะต้องมีนามแฝงของตัวเอง!
codygman

109

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

โซลูชันที่ใช้งานได้จริงคือแบบสอบถาม /NOT INวิธีการย่อยคู่ของ Alex Barrett (คล้ายกับBill Karwin's ) และวิธีการของ QuassnoiLEFT JOIN

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

สิ่งที่ฉันตัดสินใช้แบบสอบถามย่อยคู่ของ Alex Barrett (ขอบคุณ!) แต่ใช้<=แทนNOT IN:

DELETE FROM `test_sandbox`
  WHERE id <= (
    SELECT id
    FROM (
      SELECT id
      FROM `test_sandbox`
      ORDER BY id DESC
      LIMIT 1 OFFSET 42 -- keep this many records
    ) foo
  )

ใช้OFFSETเพื่อรับ id ของระเบียนNและลบบันทึกนั้นและระเบียนก่อนหน้าทั้งหมด

เนื่องจากการสั่งซื้อเป็นข้อสันนิษฐานของปัญหานี้อยู่แล้ว ( ORDER BY id DESC) <=จึงเหมาะสมอย่างยิ่ง

เร็วกว่ามากเนื่องจากตารางชั่วคราวที่สร้างโดยแบบสอบถามย่อยมีเพียงระเบียนเดียวแทนที่จะเป็นNระเบียน

กรณีทดสอบ

ฉันทดสอบวิธีการทำงานทั้งสามวิธีและวิธีการใหม่ข้างต้นในสองกรณีการทดสอบ

กรณีทดสอบทั้งสองใช้ 10,000 แถวที่มีอยู่ในขณะที่การทดสอบครั้งแรกเก็บ 9000 (ลบ 1,000 ที่เก่าที่สุด) และการทดสอบครั้งที่สองเก็บ 50 (ลบ 9950 ที่เก่าที่สุด)

+-----------+------------------------+----------------------+
|           | 10000 TOTAL, KEEP 9000 | 10000 TOTAL, KEEP 50 |
+-----------+------------------------+----------------------+
| NOT IN    |         3.2542 seconds |       0.1629 seconds |
| NOT IN v2 |         4.5863 seconds |       0.1650 seconds |
| <=,OFFSET |         0.0204 seconds |       0.1076 seconds |
+-----------+------------------------+----------------------+

สิ่งที่น่าสนใจคือ<=วิธีนี้เห็นประสิทธิภาพที่ดีขึ้นทั่วทั้งกระดาน แต่จริงๆแล้วจะดีขึ้นเมื่อคุณเก็บไว้ได้มากขึ้นแทนที่จะแย่ลง


11
ฉันกำลังอ่านกระทู้นี้อีกครั้งในอีก 4.5 ปีต่อมา นอกจากนี้ยังดี!
Alex Barrett

ว้าวดูดีมาก แต่ใช้ไม่ได้ใน Microsoft SQL 2008 ฉันได้รับข้อความนี้: "ไวยากรณ์ไม่ถูกต้องใกล้" ขีด จำกัด "เป็นเรื่องดีที่มันใช้งานได้ใน MySQL แต่ฉันจะต้องหาทางเลือกอื่น
Ken Palmer

1
@KenPalmer คุณควรจะยังคงสามารถค้นหาการชดเชยแถวเฉพาะโดยใช้ROW_NUMBER(): stackoverflow.com/questions/603724/…
Nicole

3
@KenPalmer ใช้ SELECT TOP แทน LIMIT เมื่อสลับระหว่าง SQL และ mySQL
Alpha G33k

1
ไชโยสำหรับสิ่งนั้น มันลดการสืบค้นในชุดข้อมูล (ใหญ่มาก) ของฉันจาก 12 นาทีเหลือ 3.64 วินาที!
Lieuwe

10

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

DELETE FROM mytable WHERE id NOT IN (SELECT MAX(id) FROM mytable);

ERROR 1093 (HY000): You can't specify target table 'mytable' for update 
in FROM clause

MySQL ไม่สามารถรองรับLIMITในแบบสอบถามย่อยได้ นี่คือข้อ จำกัด ของ MySQL

DELETE FROM mytable WHERE id NOT IN 
  (SELECT id FROM mytable ORDER BY id DESC LIMIT 1);

ERROR 1235 (42000): This version of MySQL doesn't yet support 
'LIMIT & IN/ALL/ANY/SOME subquery'

คำตอบที่ดีที่สุดที่ฉันสามารถทำได้คือทำในสองขั้นตอน:

SELECT id FROM mytable ORDER BY id DESC LIMIT n; 

รวบรวมรหัสและทำให้เป็นสตริงที่คั่นด้วยจุลภาค:

DELETE FROM mytable WHERE id NOT IN ( ...comma-separated string... );

(โดยปกติการแก้ไขรายการที่คั่นด้วยจุลภาคลงในคำสั่ง SQL จะทำให้เกิดความเสี่ยงของการแทรก SQL แต่ในกรณีนี้ค่าไม่ได้มาจากแหล่งที่ไม่น่าเชื่อถือค่าเหล่านี้จะทราบว่าเป็นค่าจำนวนเต็มจากฐานข้อมูลเอง)

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


แต่คุณสามารถทำการรวมภายในระหว่างการลบและเลือก สิ่งที่ฉันทำด้านล่างควรได้ผล
achinda99

คุณต้องใช้เคียวรีย่อยตัวกลางเพื่อให้ LIMIT ทำงานในเคียวรีย่อย
Alex Barrett

@ achinda99: ฉันไม่เห็นคำตอบจากคุณในกระทู้นี้ ... ?
Bill Karwin

ฉันถูกดึงไปประชุม ความผิดฉันเอง. ตอนนี้ฉันไม่มีสภาพแวดล้อมการทดสอบเพื่อทดสอบ sql ที่ฉันเขียน แต่ฉันได้ทำทั้งสิ่งที่ Alex Barret ทำและฉันได้รับมันเพื่อทำงานร่วมกับการรวมภายใน
achinda99

เป็นข้อ จำกัด โง่ ๆ ของ MySQL ด้วย PostgreSQL ใช้DELETE FROM mytable WHERE id NOT IN (SELECT id FROM mytable ORDER BY id DESC LIMIT 3);งานได้ดี
bortzmeyer


5

หากรหัสของคุณเพิ่มขึ้นให้ใช้สิ่งที่ต้องการ

delete from table where id < (select max(id) from table)-N

2
ปัญหาใหญ่อย่างหนึ่งในเคล็ดลับที่ดีนี้: ซีเรียลไม่ได้อยู่ติดกันเสมอไป (เช่นเมื่อมีการย้อนกลับ)
bortzmeyer

5

ในการลบบันทึกทั้งหมดยกเว้น te last Nคุณสามารถใช้แบบสอบถามที่รายงานด้านล่าง

เป็นแบบสอบถามเดียว แต่มีข้อความจำนวนมากดังนั้นจึงไม่ใช่แบบสอบถามเดียวอย่างที่ตั้งใจไว้ในคำถามเดิม

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

หวังว่ามันอาจจะมีประโยชน์ต่อไป ...

nnnคือแถวที่ต้องเก็บและตารางคือตารางที่คุณกำลังทำงานอยู่

ฉันสมมติว่าคุณมีบันทึกการสร้างอัตโนมัติชื่อid

SELECT @ROWS_TO_DELETE := COUNT(*) - nnn FROM `theTable`;
SELECT @ROWS_TO_DELETE := IF(@ROWS_TO_DELETE<0,0,@ROWS_TO_DELETE);
PREPARE STMT FROM "DELETE FROM `theTable` ORDER BY `id` ASC LIMIT ?";
EXECUTE STMT USING @ROWS_TO_DELETE;

สิ่งที่ดีเกี่ยวกับแนวทางนี้คือประสิทธิภาพ : ฉันได้ทดสอบแบบสอบถามบนฐานข้อมูลท้องถิ่นที่มีข้อมูลประมาณ 13,000 รายการโดยเก็บ 1,000 รายการล่าสุดไว้ มันทำงานใน 0.08 วินาที

บทจากคำตอบที่ยอมรับ ...

DELETE FROM `table`
WHERE id NOT IN (
  SELECT id
  FROM (
    SELECT id
    FROM `table`
    ORDER BY id DESC
    LIMIT 42 -- keep this many records
  ) foo
);

ใช้เวลา 0.55 วินาที อีกประมาณ 7 เท่า.

สภาพแวดล้อมการทดสอบ: mySQL 5.5.25 ใน MacBookPro i7 รุ่นปลายปี 2011 พร้อม SSD



1

ลองสอบถามด้านล่าง:

DELETE FROM tablename WHERE id < (SELECT * FROM (SELECT (MAX(id)-10) FROM tablename ) AS a)

แบบสอบถามย่อยภายในจะส่งกลับค่า 10 อันดับแรกและแบบสอบถามภายนอกจะลบระเบียนทั้งหมดยกเว้น 10 อันดับแรก


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

0

สิ่งที่เกี่ยวกับ:

SELECT * FROM table del 
         LEFT JOIN table keep
         ON del.id < keep.id
         GROUP BY del.* HAVING count(*) > N;

ส่งคืนแถวที่มีมากกว่า N แถวก่อนหน้า อาจมีประโยชน์?


0

การใช้ id สำหรับงานนี้ไม่ใช่ทางเลือกในหลาย ๆ กรณี ตัวอย่างเช่น - ตารางที่มีสถานะ Twitter นี่คือตัวแปรที่มีช่องการประทับเวลาที่ระบุ

delete from table 
where access_time >= 
(
    select access_time from  
    (
        select access_time from table 
            order by access_time limit 150000,1
    ) foo    
)

0

แค่อยากจะนำสิ่งนี้ไปผสมผสานสำหรับทุกคนที่ใช้ Microsoft SQL Server แทน MySQL MSSQL ไม่รองรับคีย์เวิร์ด 'Limit' ดังนั้นคุณจะต้องใช้ทางเลือกอื่น รหัสนี้ทำงานใน SQL 2008 และอ้างอิงจากโพสต์ SO นี้ https://stackoverflow.com/a/1104447/993856

-- Keep the last 10 most recent passwords for this user.
DECLARE @UserID int; SET @UserID = 1004
DECLARE @ThresholdID int -- Position of 10th password.
SELECT  @ThresholdID = UserPasswordHistoryID FROM
        (
            SELECT ROW_NUMBER()
            OVER (ORDER BY UserPasswordHistoryID DESC) AS RowNum, UserPasswordHistoryID
            FROM UserPasswordHistory
            WHERE UserID = @UserID
        ) sub
WHERE   (RowNum = 10) -- Keep this many records.

DELETE  UserPasswordHistory
WHERE   (UserID = @UserID)
        AND (UserPasswordHistoryID < @ThresholdID)

เป็นที่ยอมรับว่าสิ่งนี้ไม่สง่างาม หากคุณสามารถปรับให้เหมาะสมกับ Microsoft SQL ได้โปรดแบ่งปันวิธีการแก้ปัญหาของคุณ ขอบคุณ!


0

หากคุณต้องการลบบันทึกตามคอลัมน์อื่นด้วยนี่คือวิธีแก้ปัญหา:

DELETE
FROM articles
WHERE id IN
    (SELECT id
     FROM
       (SELECT id
        FROM articles
        WHERE user_id = :userId
        ORDER BY created_at DESC LIMIT 500, 10000000) abc)
  AND user_id = :userId

0

สิ่งนี้ควรใช้งานได้เช่นกัน:

DELETE FROM [table] 
INNER JOIN (
    SELECT [id] 
    FROM (
        SELECT [id] 
        FROM [table] 
        ORDER BY [id] DESC
        LIMIT N
    ) AS Temp
) AS Temp2 ON [table].[id] = [Temp2].[id]


-1

ทำไมจะไม่ล่ะ

DELETE FROM table ORDER BY id DESC LIMIT 1, 123456789

เพียงแค่ลบทั้งหมดยกเว้นแถวแรก (คำสั่งคือ DESC!) โดยใช้ตัวเลขที่ใหญ่มากเป็น LIMIT-อาร์กิวเมนต์ที่สอง ดูที่นี่


2
DELETEไม่รองรับ[offset],หรือOFFSET: dev.mysql.com/doc/refman/5.0/en/delete.html
Nicole

-1

ตอบคำถามนี้หลังจากผ่านไปนาน ... เจอสถานการณ์เดียวกันและแทนที่จะใช้คำตอบที่กล่าวไปฉันมาพร้อมกับด้านล่าง -

DELETE FROM table_name order by ID limit 10

การดำเนินการนี้จะลบระเบียน 10 อันดับแรกและเก็บบันทึกล่าสุดไว้


คำถามนี้ถามว่า "ทั้งหมด exept the last N records" และ "in a single query" แต่ดูเหมือนว่าคุณยังต้องใช้แบบสอบถามแรกเพื่อนับระเบียนทั้งหมดในตารางจากนั้น จำกัด ให้รวม - N
Paolo

@ Paolo เราไม่ต้องการให้แบบสอบถามนับระเบียนทั้งหมดเนื่องจากข้อความค้นหาด้านบนจะลบทั้งหมดยกเว้น 10 ระเบียนล่าสุด
Nitesh

1
ไม่คำค้นหานั้นลบระเบียนที่เก่าแก่ที่สุด 10 รายการ OP ต้องการลบทุกอย่างยกเว้น n ระเบียนล่าสุด Yours เป็นวิธีแก้ปัญหาพื้นฐานที่จะจับคู่กับคิวรีการนับในขณะที่ OP กำลังถามว่ามีวิธีรวมทุกอย่างเป็นแบบสอบถามเดียวหรือไม่
ChrisMoll

@ChrisMoll ฉันเห็นด้วย ฉันจะแก้ไข / ลบคำตอบนี้ได้หรือไม่เพื่อให้ผู้ใช้ไม่โหวตฉันหรือปล่อยไว้อย่างนั้น?
Nitesh
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.