Truy vấn SQL: Xóa tất cả các bản ghi khỏi bảng ngoại trừ N mới nhất?


90

Có thể xây dựng một truy vấn mysql duy nhất (không có biến) để xóa tất cả các bản ghi khỏi bảng, ngoại trừ N mới nhất (được sắp xếp theo id desc) không?

Chuyện như thế này, chỉ là nó không hoạt động :)

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

Cảm ơn.

Câu trả lời:


139

Bạn không thể xóa các bản ghi theo cách đó, vấn đề chính là bạn không thể sử dụng truy vấn con để chỉ định giá trị của mệnh đề LIMIT.

Điều này hoạt động (được thử nghiệm trong 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
);

Truy vấn con trung gian bắt buộc. Nếu không có nó, chúng tôi sẽ gặp phải hai lỗi:

  1. Lỗi SQL (1093): Bạn không thể chỉ định bảng mục tiêu 'bảng' để cập nhật trong mệnh đề FROM - MySQL không cho phép bạn tham chiếu đến bảng bạn đang xóa trong một truy vấn con trực tiếp.
  2. Lỗi SQL (1235): Phiên bản MySQL này chưa hỗ trợ 'LIMIT & IN / ALL / ANY / SOME' - Bạn không thể sử dụng mệnh đề LIMIT trong truy vấn con trực tiếp của toán tử NOT IN.

May mắn thay, việc sử dụng một truy vấn con trung gian cho phép chúng ta bỏ qua cả hai hạn chế này.


Nicole đã chỉ ra rằng truy vấn này có thể được tối ưu hóa đáng kể cho các trường hợp sử dụng nhất định (chẳng hạn như trường hợp này). Tôi cũng khuyên bạn nên đọc câu trả lời đó để xem nó có phù hợp với bạn không.


4
Được rồi, điều đó hiệu quả - nhưng đối với tôi, thật không phù hợp và không hài lòng khi phải dùng đến những thủ thuật phức tạp như vậy. Tuy nhiên, +1 cho câu trả lời.
Bill Karwin 23/02/09

1
Tôi đánh dấu nó là một câu trả lời được chấp nhận, bởi vì nó thực hiện những gì tôi yêu cầu. Nhưng cá nhân tôi sẽ làm điều đó có lẽ trong hai truy vấn chỉ để giữ cho nó đơn giản :) Tôi nghĩ có lẽ có một số cách nhanh chóng và dễ dàng.
serg 23/02/09

1
Cảm ơn Alex, câu trả lời của bạn đã giúp tôi. Tôi thấy rằng truy vấn con trung gian là bắt buộc nhưng tôi không hiểu tại sao. Bạn có lời giải thích cho điều đó?
Sv1

8
một câu hỏi: "foo" để làm gì?
Sebastian Breit

9
Perroloco, tôi đã thử mà không có foo và gặp lỗi này: ERROR 1248 (42000): Mỗi bảng dẫn xuất phải có bí danh riêng Vì vậy, câu trả lời của chúng tôi, mọi bảng dẫn xuất phải có bí danh riêng!
codygman

106

Tôi biết tôi đang sống lại một câu hỏi khá cũ, nhưng gần đây tôi đã gặp phải vấn đề này, nhưng cần một cái gì đó có quy mô lớn . Không có bất kỳ dữ liệu hiệu suất hiện có nào và vì câu hỏi này đã được khá nhiều người chú ý, tôi nghĩ tôi sẽ đăng những gì tôi tìm thấy.

Các giải pháp thực sự hoạt động là phương pháp / truy vấn phụ kép của Alex BarrettNOT IN (tương tự như của Bill Karwin ) và phương pháp của QuassnoiLEFT JOIN .

Thật không may, cả hai phương pháp trên đều tạo ra các bảng tạm thời trung gian rất lớn và hiệu suất giảm nhanh chóng do số lượng bản ghi không được xóa ngày càng lớn.

Những gì tôi đã giải quyết bằng cách sử dụng truy vấn phụ kép của Alex Barrett (cảm ơn!) Nhưng sử dụng <=thay vì 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
  )

Nó sử dụng OFFSETđể lấy id của bản ghi thứ N và xóa bản ghi đó và tất cả các bản ghi trước đó.

Vì đặt hàng đã là một giả định của vấn đề này ( ORDER BY id DESC), <=là một sự phù hợp hoàn hảo.

Nó nhanh hơn nhiều, vì bảng tạm thời được tạo bởi truy vấn con chỉ chứa một bản ghi thay vì N bản ghi.

Trường hợp thử nghiệm

Tôi đã thử nghiệm ba phương pháp làm việc và phương pháp mới ở trên trong hai trường hợp thử nghiệm.

Cả hai trường hợp thử nghiệm đều sử dụng 10000 hàng hiện có, trong khi thử nghiệm đầu tiên giữ 9000 (xóa 1000 cũ nhất) và thử nghiệm thứ hai giữ 50 (xóa 9950 cũ nhất).

+-----------+------------------------+----------------------+
|           | 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 |
+-----------+------------------------+----------------------+

Điều thú vị là <=phương pháp này cho thấy hiệu suất tốt hơn trên toàn diện, nhưng thực sự sẽ tốt hơn khi bạn giữ được nhiều thứ hơn thay vì tệ hơn.


11
Tôi đang đọc lại chủ đề này 4,5 năm sau. Bổ sung tốt đẹp!
Alex Barrett,

Chà, điều này trông tuyệt vời nhưng không hoạt động trong Microsoft SQL 2008. Tôi nhận được thông báo sau: "Cú pháp không chính xác gần 'Giới hạn'. Thật tuyệt khi nó hoạt động trong MySQL, nhưng tôi sẽ cần tìm một giải pháp thay thế.
Ken Palmer

1
@KenPalmer Bạn vẫn có thể tìm thấy độ lệch hàng cụ thể bằng cách sử dụng ROW_NUMBER(): stackoverflow.com/questions/603724/…
Nicole

3
@KenPalmer sử dụng CHỌN TOP thay vì LIMIT khi chuyển đổi giữa SQL và mySQL
Alpha G33k

1
Chúc mừng cho điều đó. Nó đã giảm truy vấn trên tập dữ liệu (rất lớn) của tôi từ 12 phút xuống 3,64 giây!
Lieuwe

10

Thật không may cho tất cả các câu trả lời được đưa ra bởi những người khác, bạn không thể DELETESELECTtừ một bảng nhất định trong cùng một truy vấn.

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 cũng không thể hỗ trợ LIMITtrong một truy vấn con. Đây là những hạn chế của 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'

Câu trả lời tốt nhất mà tôi có thể đưa ra là thực hiện việc này theo hai giai đoạn:

SELECT id FROM mytable ORDER BY id DESC LIMIT n; 

Thu thập id và biến chúng thành một chuỗi được phân tách bằng dấu phẩy:

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

(Thông thường việc nội suy một danh sách phân tách bằng dấu phẩy vào một câu lệnh SQL dẫn đến một số rủi ro về việc chèn SQL, nhưng trong trường hợp này, các giá trị không đến từ một nguồn không đáng tin cậy, chúng được biết là các giá trị nguyên từ chính cơ sở dữ liệu.)

lưu ý: Mặc dù điều này không hoàn thành công việc trong một truy vấn duy nhất , nhưng đôi khi một giải pháp đơn giản hơn, hoàn thành tốt là hiệu quả nhất.


Nhưng bạn có thể thực hiện các phép nối bên trong giữa xóa và chọn. Những gì tôi đã làm dưới đây sẽ hoạt động.
achinda99 23/02/09

Bạn cần sử dụng truy vấn con trung gian để LIMIT hoạt động trong truy vấn con.
Alex Barrett

@ achinda99: Tôi không thấy câu trả lời từ bạn trên chủ đề này ...?
Bill Karwin 23/02/09

Tôi đã được kéo cho một cuộc họp. Lỗi của tôi. Tôi không có môi trường thử nghiệm ngay bây giờ để kiểm tra sql mà tôi đã viết, nhưng tôi đã làm cả những gì Alex Barret đã làm và tôi đã làm cho nó hoạt động với một kết nối bên trong.
achinda99 23/02/09

Đó là một hạn chế ngu ngốc của MySQL. Với PostgreSQL, DELETE FROM mytable WHERE id NOT IN (SELECT id FROM mytable ORDER BY id DESC LIMIT 3);hoạt động tốt.
bortzmeyer

8
DELETE  i1.*
FROM    items i1
LEFT JOIN
        (
        SELECT  id
        FROM    items ii
        ORDER BY
                id DESC
        LIMIT 20
        ) i2
ON      i1.id = i2.id
WHERE   i2.id IS NULL

5

Nếu id của bạn tăng dần thì hãy sử dụng một cái gì đó như

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

2
Một vấn đề lớn trong thủ thuật hay này: các nối tiếp không phải lúc nào cũng liền nhau (ví dụ: khi có các lần quay lại).
bortzmeyer

5

Để xóa tất cả các bản ghi ngoại trừ te cuối N, bạn có thể sử dụng truy vấn được báo cáo bên dưới.

Đó là một truy vấn duy nhất nhưng có nhiều câu lệnh, vì vậy nó thực sự không phải là một truy vấn duy nhất theo cách mà nó đã dự định trong câu hỏi ban đầu.

Ngoài ra, bạn cần một biến và một câu lệnh chuẩn bị sẵn (trong truy vấn) do một lỗi trong MySQL.

Hy vọng nó có thể hữu ích dù sao ...

nnn là các hàng cần giữBảng là bảng bạn đang làm việc.

Tôi giả sử bạn có một bản ghi tự động bổ sung có tên là 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;

Điều tốt về cách tiếp cận này là hiệu suất : Tôi đã thử nghiệm truy vấn trên một DB cục bộ với khoảng 13.000 bản ghi, giữ lại 1.000 bản ghi cuối cùng. Nó chạy trong 0,08 giây.

Kịch bản từ câu trả lời được chấp nhận ...

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

Mất 0,55 giây. Khoảng hơn 7 lần.

Môi trường thử nghiệm: mySQL 5.5.25 trên MacBookPro i7 cuối 2011 với SSD



1

hãy thử truy vấn bên dưới:

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

truy vấn con bên trong sẽ trả về giá trị 10 trên cùng và truy vấn bên ngoài sẽ xóa tất cả các bản ghi ngoại trừ 10 đầu.


1
Một số giải thích về cách hoạt động của điều này sẽ có lợi cho những người xem câu trả lời này. Việc bán phá giá mã thường không được khuyến khích.
rayryeng

Điều này không đúng với id không nhất quán
Slava Rozhnev

0

Thế còn :

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

Nó trả về các hàng có nhiều hơn N hàng trước đó. Có thể hữu ích?


0

Sử dụng id cho tác vụ này không phải là một tùy chọn trong nhiều trường hợp. Ví dụ - bảng với các trạng thái twitter. Đây là một biến thể với trường dấu thời gian được chỉ định.

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

0

Chỉ muốn đưa điều này vào hỗn hợp cho bất kỳ ai sử dụng Microsoft SQL Server thay vì MySQL. Từ khóa 'Giới hạn' không được MSSQL hỗ trợ, vì vậy bạn sẽ cần sử dụng một từ khóa thay thế. Mã này hoạt động trong SQL 2008 và dựa trên bài đăng SO này. 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)

Phải thừa nhận rằng điều này không thanh lịch. Nếu bạn có thể tối ưu hóa điều này cho Microsoft SQL, vui lòng chia sẻ giải pháp của bạn. Cảm ơn!


0

Nếu bạn cũng cần xóa các bản ghi dựa trên một số cột khác, thì đây là một giải pháp:

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

Điều này cũng sẽ hoạt động:

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]

0
DELETE FROM table WHERE id NOT IN (
    SELECT id FROM table ORDER BY id, desc LIMIT 0, 10
)


-1

Trả lời câu hỏi này sau một thời gian dài ... Gặp phải tình huống tương tự và thay vì sử dụng các câu trả lời được đề cập, tôi đã đưa ra bên dưới -

DELETE FROM table_name order by ID limit 10

Thao tác này sẽ xóa 10 bản ghi đầu tiên và giữ các bản ghi mới nhất.


Câu hỏi yêu cầu "tất cả đều sử dụng N bản ghi cuối cùng" và "trong một truy vấn duy nhất". Nhưng nó có vẻ bạn vẫn cần một truy vấn đầu tiên để đếm tất cả các bản ghi trong bảng giới hạn sau đó đến tổng - N
Paolo

@Paolo Chúng tôi không yêu cầu truy vấn đếm tất cả các bản ghi vì truy vấn trên sẽ xóa tất cả ngoại trừ 10 bản ghi cuối cùng.
Nitesh

1
Không, truy vấn đó sẽ xóa 10 bản ghi cũ nhất. OP muốn xóa mọi thứ ngoại trừ n bản ghi gần đây nhất. Yours là giải pháp cơ bản sẽ được ghép nối với một truy vấn đếm, trong khi OP đang hỏi liệu có cách nào để kết hợp mọi thứ thành một truy vấn duy nhất hay không.
ChrisMoll

@ChrisMoll Tôi đồng ý. Tôi sẽ chỉnh sửa / xóa câu trả lời này ngay bây giờ để người dùng không bỏ phiếu cho tôi hay để nguyên nó?
Nitesh
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.