Сохранить только две последние строки для сгруппированных столбцов в таблице

#mysql #group-by #sql-delete #sqlperformance

#mysql #группировать по #sql-удалить #производительность sql

Вопрос:

У меня есть таблица «История» с примерно 300.000 строками, которая ежедневно заполняется новыми данными. Я хочу сохранить только последние две строки каждой комбинации refSchema / refId.

На самом деле я иду по этому пути:

Первый шаг:

 SELECT refSchema,refId FROM History GROUP BY refSchema,refId
  

С помощью этого оператора я получаю все комбинации (которых около 40.000).

Второй шаг:

Я запускаю foreach, который ищет существующие строки для приведенного выше запроса следующим образом:

 SELECT id
FROM History
WHERE refSchema = ? AND refId = ? AND state = 'done'
ORDER BY importedAt 
DESC LIMIT 2,2000
  

Пожалуйста, имейте в виду, что я хочу сохранить последние две строки в своей таблице, поэтому я ограничиваю 2,2000. Если я нахожу совпадающие строки, я помещаю идентификаторы в массив с именем idList .

Заключительный шаг

Таким образом я удаляю все идентификаторы из массива:

 DELETE FROM History WHERE id in ($idList)
  

Все это, похоже, не самая лучшая производительность, потому что я должен проверять каждую комбинацию с помощью дополнительного запроса. Есть ли способ иметь один оператор delete, который делает волшебство, чтобы избежать дополнительных запросов 40.000?

Редактировать Обновление: я использую AWS Aurora DB

Ответ №1:

Если вы используете MySQL 8 , то один концептуально простой способ продолжить здесь — использовать CTE для определения двух верхних строк для каждой группы, которые вы хотите сохранить. Затем удалите любую запись, пара схема / идентификатор которой не отображается в этом белом списке:

 WITH cte AS (
    SELECT refSchema, refId
    FROM
    (
        SELECT *, ROW_NUMBER() OVER (PARTITION BY refSchema, refId ORDER BY importedAt DESC) rn
        FROM History
    ) t
    WHERE rn IN (1, 2)
)

DELETE
FROM History
WHERE (refSchema, refId) NOT IN (SELECT refSchema, refId FROM cte);
  

Если вы не можете использовать CTE, попробуйте встроить вышеупомянутый CTE:

 DELETE
FROM History
WHERE (refSchema, refId) NOT IN (
    SELECT refSchema, refId
    FROM
    (
        SELECT *, ROW_NUMBER() OVER (PARTITION BY refSchema, refId ORDER BY importedAt DESC) rn
        FROM History
    ) t
    WHERE rn IN (1, 2)
);
  

Комментарии:

1. К сожалению, здесь мы используем AWS Aurora… так что там невозможно использовать CTE

2. @Marco Тогда просто попробуйте встроить CTE непосредственно в запрос на удаление.

3. Не разрешается выполнять УДАЛЕНИЕ в подзапросе в той же таблице, например, УДАЛИТЬ ИЗ истории, ГДЕ указан идентификатор (ВЫБЕРИТЕ идентификатор из истории, ГДЕ …)

4. @Marco Да, если вы обернете подзапрос в другой подзапрос, как я сделал выше. Если вы действительно получаете сообщение об ошибке, вставьте его сюда, и я могу попробовать отредактировать свой ответ.

5. Я получаю следующую ошибку: SQL Fehler (1064): У вас ошибка в синтаксисе SQL; проверьте руководство, соответствующее вашей версии сервера MySQL, на предмет правильного синтаксиса для использования рядом с ‘(РАЗДЕЛ ПО refSchema, ПОРЯДОК изменения ПО importedAt DESC) rn ИЗ истории’ в строке 7