Est-il possible de construire une seule requête mysql (sans variables) pour supprimer tous les enregistrements de la table, à l'exception du dernier N (trié par id desc)?
Quelque chose comme ça, mais ça ne marche pas :)
delete from table order by id ASC limit ((select count(*) from table ) - N)
Merci.
Vous ne pouvez pas supprimer les enregistrements de cette façon, le problème principal étant que vous ne pouvez pas utiliser une sous-requête pour spécifier la valeur d'une clause LIMIT.
Cela fonctionne (testé dans 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
);
La sous-requête intermédiaire est obligatoire. Sans cela, nous aurions rencontré deux erreurs:
Heureusement, l'utilisation d'une sous-requête intermédiaire nous permet de contourner ces deux limitations.
Nicole a souligné que cette requête peut être optimisée de manière significative pour certains cas d'utilisation (comme celui-ci). Je recommande également de lire cette réponse pour voir si elle correspond à la vôtre.
Je sais que je ressuscite une assez vieille question, mais j'ai récemment rencontré ce problème, mais j'avais besoin de quelque chose qui s'adapte bien aux grands nombres . Il n'y avait pas de données de performances existantes, et comme cette question a suscité beaucoup d'attention, j'ai pensé publier ce que j'ai trouvé.
Les solutions qui ont réellement fonctionné étaient les double sous-requête d'Alex Barrett/NOT IN
méthode (similaire à Bill Karwin's ), et Quassnoi's LEFT JOIN
méthode.
Malheureusement, les deux méthodes ci-dessus créent de très grandes tables temporaires intermédiaires et les performances se dégradent rapidement lorsque le nombre d'enregistrements non en cours de suppression devient important.
Ce que j'ai décidé utilise la double sous-requête d'Alex Barrett (merci!) Mais utilise <=
au lieu de 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
)
Il utilise OFFSET
pour obtenir l'id du Ne enregistrement et supprime cet enregistrement et tous les enregistrements précédents.
Étant donné que la commande est déjà une hypothèse de ce problème (ORDER BY id DESC
), <=
est un ajustement parfait.
C'est beaucoup plus rapide, car la table temporaire générée par la sous-requête contient un seul enregistrement au lieu des enregistrements [~ # ~] n [~ # ~] .
J'ai testé les trois méthodes de travail et la nouvelle méthode ci-dessus dans deux cas de test.
Les deux cas de test utilisent 10000 lignes existantes, tandis que le premier test conserve 9000 (supprime les 1000 plus anciens) et le second test en conserve 50 (supprime les 9950 plus anciens).
+-----------+------------------------+----------------------+
| | 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 |
+-----------+------------------------+----------------------+
Ce qui est intéressant, c'est que le <=
la méthode permet d'obtenir de meilleures performances dans tous les domaines, mais elle s'améliore en fait plus vous en gardez, au lieu de pire.
Malheureusement pour toutes les réponses données par d'autres personnes, vous ne pouvez pas DELETE
et SELECT
à partir d'une table donnée dans la même requête.
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 ne peut pas non plus prendre en charge LIMIT
dans une sous-requête. Ce sont des limitations de 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'
La meilleure réponse que je puisse trouver est de le faire en deux étapes:
SELECT id FROM mytable ORDER BY id DESC LIMIT n;
Collectez les identifiants et transformez-les en une chaîne séparée par des virgules:
DELETE FROM mytable WHERE id NOT IN ( ...comma-separated string... );
(L'interpolation normale d'une liste séparée par des virgules dans une instruction SQL présente un risque d'injection SQL, mais dans ce cas, les valeurs ne proviennent pas d'une source non fiable, elles sont connues pour être des valeurs id de la base de données elle-même.)
note: Bien que cela ne fasse pas le travail dans une seule requête , parfois plus simple, get-it- solution faite est la plus efficace.
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
Si votre identifiant est incrémentiel, utilisez quelque chose comme
delete from table where id < (select max(id) from table)-N
Pour supprimer tous les enregistrements sauf le dernier [~ # ~] n [~ # ~] , vous pouvez utiliser la requête indiquée ci-dessous.
Il s'agit d'une seule requête, mais avec de nombreuses instructions, ce n'est donc pas une seule requête telle qu'elle était prévue dans la question d'origine.
Vous avez également besoin d'une variable et d'une instruction préparée intégrée (dans la requête) en raison d'un bogue dans MySQL.
J'espère que cela pourra être utile de toute façon ...
nnn sont les lignes de garder et theTable est la table sur laquelle vous travaillez.
Je suppose que vous avez un enregistrement à incrémentation automatique nommé 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;
La bonne chose à propos de cette approche est performance: J'ai testé la requête sur une base de données locale avec environ 13 000 enregistrements, en conservant les 1 000 derniers. Il s'exécute en 0,08 seconde.
Le script de la réponse acceptée ...
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
);
Prend 0,55 seconde. Environ 7 fois plus.
Environnement de test: mySQL 5.5.25 sur un MacBookPro i7 fin 2011 avec SSD
DELETE FROM table WHERE ID NOT IN
(SELECT MAX(ID) ID FROM table)
essayez ci-dessous la requête:
DELETE FROM tablename WHERE id < (SELECT * FROM (SELECT (MAX(id)-10) FROM tablename ) AS a)
la sous-requête interne renverra la valeur du top 10 et la requête externe supprimera tous les enregistrements sauf le top 10.
Je voulais juste mettre cela dans le mélange pour toute personne utilisant Microsoft SQL Server au lieu de MySQL. Le mot clé "Limit" n'est pas pris en charge par MSSQL, vous devrez donc utiliser une alternative. Ce code a fonctionné dans SQL 2008 et est basé sur ce SO post. 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)
Certes, ce n'est pas élégant. Si vous êtes en mesure d'optimiser cela pour Microsoft SQL, veuillez partager votre solution. Merci!
DELETE FROM table WHERE id NOT IN (SELECT id FROM table ORDER BY id, desc LIMIT 0, 10)
Cela devrait également fonctionner:
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]
Utiliser id pour cette tâche n'est pas une option dans de nombreux cas. Par exemple - tableau avec les statuts Twitter. Voici une variante avec un champ d'horodatage spécifié.
delete from table
where access_time >=
(
select access_time from
(
select access_time from table
order by access_time limit 150000,1
) foo
)
Qu'en est-il de :
SELECT * FROM table del
LEFT JOIN table keep
ON del.id < keep.id
GROUP BY del.* HAVING count(*) > N;
Il renvoie des lignes contenant plus de N lignes auparavant. Pourrait être utile?
Si vous devez également supprimer les enregistrements basés sur une autre colonne, voici une solution:
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