Запрос SQL: удалить все записи из таблицы, кроме последних N? - PullRequest
76 голосов
/ 23 февраля 2009

Можно ли создать один запрос MySQL (без переменных), чтобы удалить все записи из таблицы, кроме последнего N (отсортированного по id desc)?

Как то так, только не работает :) 1003 *

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

Спасибо.

Ответы [ 16 ]

120 голосов
/ 23 февраля 2009

Вы не можете удалить записи таким образом, основная проблема в том, что вы не можете использовать подзапрос для указания значения предложения 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): вы не можете указать целевую таблицу 'table' для обновления в предложении FROM - MySQL не позволяет вам ссылаться на таблицу, которую вы удаляете из прямого подзапроса .
  2. Ошибка SQL (1235): эта версия MySQL еще не поддерживает подзапрос LIMIT & IN / ALL / ANY / SOME ' - Вы не можете использовать предложение LIMIT в прямом подзапросе оператор NOT IN.

К счастью, использование промежуточного подзапроса позволяет нам обойти оба эти ограничения.


Николь указала, что этот запрос может быть значительно оптимизирован для определенных случаев использования (таких как этот). Я рекомендую прочитать этот ответ , а также посмотреть, подходит ли он вам.

93 голосов
/ 29 ноября 2011

Я знаю, что воскрешаю довольно старый вопрос, но недавно я столкнулся с этой проблемой, но мне нужно что-то, что хорошо масштабируется до . Существующих данных о производительности не было, и, поскольку этому вопросу было уделено немало внимания, я решил опубликовать то, что нашел.

На самом деле работали следующие решения: метод двойного подзапроса Алекса Барретта / NOT IN (аналогичный 1009 * Билла Карвина) и 1011 ** 1012 Кассну * метод.

К сожалению, оба приведенных выше метода создают очень большие промежуточные временные таблицы, и производительность быстро снижается, так как количество удаляемых записей не становится большим.

То, на чем я остановился, использует двойной подзапрос Алекса Барретта (спасибо!), Но использует <= вместо 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 для получения идентификатора 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 |
+-----------+------------------------+----------------------+

Что интересно, метод <= обеспечивает лучшую производительность по всем направлениям, но на самом деле, чем больше вы сохраняете, тем лучше.

9 голосов
/ 23 февраля 2009

К сожалению, для всех ответов, данных другими людьми, вы не можете 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, но в этом случае значения не поступают из ненадежного источника, они, как известно, являются значениями id из самой базы данных.)

примечание: Хотя это не позволяет выполнить работу в одном запросе, иногда более простое, готовое решение является наиболее эффективным.

8 голосов
/ 23 февраля 2009
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 голосов
/ 23 февраля 2009

Если ваш идентификатор инкрементный, используйте что-то вроде

delete from table where id < (select max(id) from table)-N
4 голосов
/ 02 октября 2013

Чтобы удалить все записи, кроме последних N , вы можете использовать запрос, указанный ниже.

Это одиночный запрос, но со многими утверждениями, так что на самом деле это не одиночный запрос , как это было задумано в исходном вопросе.

Также вам нужна переменная и встроенный (в запросе) подготовленный оператор из-за ошибки в MySQL.

Надеюсь, это может быть полезно в любом случае ...

nnn - строки в keep , а 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;

Хорошая вещь в этом подходе - производительность : я проверил запрос к локальной БД с около 13 000 записей, сохраняя последние 1000. Работает за 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 на iBook MacBookPro конца 2011 года с SSD

2 голосов
/ 23 февраля 2009
DELETE FROM table WHERE ID NOT IN
(SELECT MAX(ID) ID FROM table)
1 голос
/ 14 мая 2015

попробуйте запрос ниже:

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

внутренний подзапрос возвратит 10 верхних значений, а внешний запрос удалит все записи, кроме 10 верхних.

0 голосов
/ 23 июня 2016

Если вам нужно удалить записи, основанные также на каком-то другом столбце, то вот решение:

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 голосов
/ 02 апреля 2015

Просто хотел добавить это в смесь для тех, кто использует Microsoft SQL Server вместо MySQL. Ключевое слово «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, поделитесь своим решением. Спасибо!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...