SQL 쿼리 : 최신 N을 제외한 모든 레코드를 테이블에서 삭제 하시겠습니까?


90

최신 N (id desc로 정렬 됨)을 제외하고 테이블에서 모든 레코드를 제거하기 위해 단일 mysql 쿼리 (변수 없음)를 작성할 수 있습니까?

이런 식으로 작동하지 않습니다. :)

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

감사.

답변:


139

그런 식으로 레코드를 삭제할 수 없습니다. 주요 문제는 하위 쿼리를 사용하여 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 절에서 업데이트 할 대상 테이블 'table'을 지정할 수 없습니다. MySQL은 직접 하위 쿼리 내에서 삭제중인 테이블을 참조 할 수 없습니다.
  2. SQL 오류 (1235) :이 버전의 MySQL은 아직 'LIMIT & IN / ALL / ANY / SOME 하위 쿼리'를 지원하지 않습니다 .-NOT IN 연산자의 직접 하위 쿼리 내에서 LIMIT 절을 사용할 수 없습니다.

다행히 중간 하위 쿼리를 사용하면 이러한 제한 사항을 모두 우회 할 수 있습니다.


Nicole은이 쿼리가 특정 사용 사례 (예 :이 사례)에 대해 상당히 최적화 될 수 있다고 지적했습니다. 귀하의 답변에 맞는지 확인하기 위해 그 답변 을 읽는 것이 좋습니다 .


4
좋아요.하지만 저에게는 그런 신비한 속임수에 의지하는 것이 우아하지 않고 불만족 스럽습니다. 그럼에도 불구하고 답은 +1.
Bill Karwin

1
내가 요청한 것을 수행하기 때문에 수락 된 답변으로 표시합니다. 그러나 나는 개인적으로 그것을 간단하게 유지하기 위해 아마도 두 가지 쿼리로 할 것입니다. :) 아마도 빠르고 쉬운 방법이 있다고 생각했습니다.
serg

1
감사합니다 Alex, 귀하의 답변이 도움이되었습니다. 중간 하위 쿼리가 필요하다는 것을 알지만 이유를 이해할 수 없습니다. 그것에 대한 설명이 있습니까?
Sv1

8
질문 : "foo"는 무엇을위한 것입니까?
Sebastian Breit 2013

9
Perroloco, foo없이 시도했는데이 오류가 발생했습니다. ERROR 1248 (42000) : 모든 파생 테이블에는 고유 한 별칭이 있어야합니다. 따라서 우리의 대답은 모든 파생 테이블에 고유 한 별칭이 있어야합니다!
codygman 2013-06-13

106

나는 꽤 오래된 질문을 부활시키고 있다는 것을 알고 있지만 최근 에이 문제에 직면했지만 많은 수로 확장되는 것이 필요했습니다 . 기존 성능 데이터가 없었고,이 질문에 상당한 관심이 있었기 때문에 내가 찾은 것을 게시 할 것이라고 생각했습니다.

실제로 작동하는 솔루션은 Alex Barrett의 이중 하위 쿼리 /NOT IN 메서드 ( Bill Karwin의 )와 Quassnoi의LEFT 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 대신 하나의 레코드 만 포함되므로 훨씬 빠릅니다. .

테스트 케이스

위의 세 가지 작업 방법과 두 가지 테스트 사례에서 새로운 방법을 테스트했습니다.

두 테스트 사례 모두 10000 개의 기존 행을 사용하는 반면 첫 번째 테스트는 9000 개 (가장 오래된 1000 개 삭제)를 유지하고 두 번째 테스트는 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에서는 작동하지 않습니다. " 'Limit'근처에있는 잘못된 구문입니다. MySQL에서 작동하는 것은 좋지만 대체 솔루션을 찾아야합니다.
Ken Palmer

1
@KenPalmer 여전히 ROW_NUMBER()다음을 사용하여 특정 행 오프셋을 찾을 수 있습니다 . stackoverflow.com/questions/603724/…
Nicole

3
대신 LIMIT @KenPalmer의 사용은 SQL SELECT TOP와 MySQL 전환 할 때
알파 G33k

1
건배. 내 (매우 큰) 데이터 세트에 대한 쿼리를 12 분에서 3.64 초로 줄였습니다!
Lieuwe

10

불행하게도, 당신은 할 수 없습니다 다른 사람에 의해 주어진 모든 답변 DELETESELECT같은 쿼리에서 특정 테이블에서.

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

LIMIT하위 쿼리에서 MySQL을 지원할 수도 없습니다 . 이것은 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; 

ID를 수집하여 쉼표로 구분 된 문자열로 만듭니다.

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

(일반적으로 쉼표로 구분 된 목록을 SQL 문에 삽입하면 SQL 삽입 위험이 있지만이 경우 값은 신뢰할 수없는 소스에서 가져온 것이 아니며 데이터베이스 자체의 정수 값으로 알려져 있습니다.)

참고 : 이렇게해도 단일 쿼리로 작업이 완료되지는 않지만 때로는 더 간단한 get-it-done 솔루션이 가장 효과적입니다.


그러나 삭제와 선택간에 내부 조인을 수행 할 수 있습니다. 아래에서 한 일은 작동합니다.
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 2009

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

ID가 증분이면 다음과 같은 것을 사용하십시오.

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

2
이 멋진 트릭의 한 가지 큰 문제 : 직렬이 항상 연속적이지는 않습니다 (예 : 롤백이있을 때).
bortzmeyer 2009

5

마지막 N 을 제외한 모든 레코드를 삭제하려면 아래보고 된 쿼리를 사용할 수 있습니다.

단일 쿼리이지만 많은 문이 있으므로 원래 질문에서 의도 한 방식대로 실제로 단일 쿼리 가 아닙니다 .

또한 MySQL의 버그로 인해 변수와 내장 (쿼리에) 준비된 문이 필요합니다.

어쨌든 유용 할 수 있기를 바랍니다 ...

nnn보관할 행 이고 theTable 은 작업중인 테이블입니다.

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;

이 방법의 좋은 점은 성능입니다 . 마지막 1,000 개를 유지하면서 약 13,000 개의 레코드가있는 로컬 DB에서 쿼리를 테스트했습니다. 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 배 더.

테스트 환경 : SSD가있는 2011 년 후반 i7 MacBookPro의 mySQL 5.5.25



1

아래 쿼리를 시도하십시오.

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

내부 하위 쿼리는 상위 10 개 값을 반환하고 외부 쿼리는 상위 10 개를 제외한 모든 레코드를 삭제합니다.


1
이것이 어떻게 작동하는지에 대한 설명은이 답변을 접하는 사람들에게 도움이 될 것입니다. 일반적으로 코드 덤프는 권장되지 않습니다.
rayryeng

일관되지 않은 ID
로는

0

는 어때 :

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

이전에 N 개 이상의 행이있는 행을 반환합니다. 유용 할 수 있습니까?


0

이 작업에 id를 사용하는 것은 대부분의 경우 옵션이 아닙니다. 예 : 트위터 상태가있는 테이블. 다음은 지정된 타임 스탬프 필드가있는 변형입니다.

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

0

MySQL 대신 Microsoft SQL Server를 사용하는 모든 사람들을 위해 이것을 혼합하고 싶었습니다. 'Limit'키워드는 MSSQL에서 지원되지 않으므로 대안을 사용해야합니다. 이 코드는 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]

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


-1

오랜 시간이 지난 후 대답 ... 같은 상황에 이르렀고 언급 된 대답을 사용하는 대신 아래에 왔습니다.

DELETE FROM table_name order by ID limit 10

이렇게하면 처음 10 개의 기록이 삭제되고 최신 기록이 유지됩니다.


질문은 "모두 마지막 N 개 레코드 제외"및 "단일 쿼리에서"라고 물었습니다. N - 그러나 당신은 여전히 총 테이블에 다음 제한을 모든 레코드를 계산하기 위해 첫 번째 쿼리를해야 할 것 같다
파올로

@Paolo 위의 쿼리는 마지막 10 개의 레코드를 제외한 모든 레코드를 삭제하므로 모든 레코드를 계산하는 쿼리가 필요하지 않습니다.
Nitesh

1
아니요, 해당 쿼리는 가장 오래된 10 개의 레코드를 삭제합니다. OP는 n 개의 가장 최근 레코드를 제외한 모든 것을 삭제하려고합니다. Yours는 카운트 쿼리와 쌍을 이루는 기본 솔루션이며 OP는 모든 것을 단일 쿼리로 결합하는 방법이 있는지 묻습니다.
ChrisMoll

@ChrisMoll 동의합니다. 사용자가 나에게 반대 투표를하지 않거나 그대로 두지 않도록 지금이 답변을 편집 / 삭제해야합니까?
Nitesh
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.