Vous ne pouvez pas supprimer les enregistrements de cette façon, le principal problème é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
) foo
);
La sous - requête intermédiaire est requise. Sans cela, nous nous heurterions à deux erreurs:
- Erreur SQL (1093): vous ne pouvez pas spécifier la table cible 'table' pour la mise à jour dans la clause FROM - MySQL ne vous permet pas de faire référence à la table que vous supprimez dans une sous-requête directe.
- Erreur SQL (1235): Cette version de MySQL ne prend pas encore en charge «LIMIT & IN / ALL / ANY / SOME sous-requête» - Vous ne pouvez pas utiliser la clause LIMIT dans une sous-requête directe d'un opérateur NOT IN.
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 question assez ancienne, mais j'ai récemment rencontré ce problème, mais j'avais besoin de quelque chose qui s'adapte bien à de grands nombres . Il n'y avait pas de données de performance 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 la double sous-requête /
NOT IN
méthode d' Alex Barrett (similaire à celle de Bill Karwin ) et laLEFT JOIN
méthode de Quassnoi .Malheureusement, les deux méthodes ci-dessus créent de très grandes tables temporaires intermédiaires et les performances se dégradent rapidement à mesure que le nombre d'enregistrements non supprimés augmente.
Ce sur quoi j'ai choisi utilise la double sous-requête d'Alex Barrett (merci!) Mais utilise à la
<=
place deNOT 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 N ème enregistrement et supprime cet enregistrement et tous les enregistrements précédents.Puisque 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 ne contient qu'un seul enregistrement au lieu de N enregistrements.
Cas de test
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 en conserve 9000 (supprime les 1000 plus anciens) et le deuxième test en conserve 50 (supprime les 9950 les 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 la
<=
méthode voit de meilleures performances dans tous les domaines, mais s'améliore à mesure que vous en conservez, au lieu de pire.la source
ROW_NUMBER()
: stackoverflow.com/questions/603724/...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
LIMIT
en charge 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... );
(Normalement, l'interpolation d'une liste séparée par des virgules dans une instruction SQL présente un certain risque d'injection SQL, mais dans ce cas, les valeurs ne proviennent pas d'une source non approuvée, elles sont connues pour être des valeurs entières de la base de données elle-même.)
Remarque: bien que cela ne fasse pas le travail en une seule requête, une solution plus simple et plus efficace est parfois la plus efficace.
la source
DELETE FROM mytable WHERE id NOT IN (SELECT id FROM mytable ORDER BY id DESC LIMIT 3);
fonctionne très bien.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
la source
Si votre identifiant est incrémentiel, utilisez quelque chose comme
delete from table where id < (select max(id) from table)-N
la source
Pour supprimer tous les enregistrements à l'exception du 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 comme prévu 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 peut être utile de toute façon ...
nnn sont les lignes à garder et theTable est la table sur laquelle vous travaillez.
Je suppose que vous avez un enregistrement à auto-incrémentation 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 la performance : j'ai testé la requête sur une base de données locale avec environ 13000 enregistrements, en conservant les 1000 derniers. Il fonctionne 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
la source
DELETE FROM table WHERE ID NOT IN (SELECT MAX(ID) ID FROM table)
la source
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 des 10 premiers et la requête externe supprimera tous les enregistrements sauf les 10 premiers.
la source
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 avec plus de N lignes auparavant. Cela pourrait être utile?
la source
L'utilisation de id pour cette tâche n'est pas une option dans de nombreux cas. Par exemple - table 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 )
la source
Je voulais juste ajouter cela à tous ceux qui utilisent Microsoft SQL Server au lieu de MySQL. Le mot-clé «Limite» 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 cette publication 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)
Certes, ce n'est pas élégant. Si vous pouvez optimiser cela pour Microsoft SQL, veuillez partager votre solution. Merci!
la source
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
la source
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]
la source
DELETE FROM table WHERE id NOT IN ( SELECT id FROM table ORDER BY id, desc LIMIT 0, 10 )
la source
Pourquoi pas
DELETE FROM table ORDER BY id DESC LIMIT 1, 123456789
Supprimez tout sauf la première ligne (l'ordre est DESC!), En utilisant un très très grand nombre comme deuxième argument LIMIT. Vois ici
la source
DELETE
ne prend pas en charge[offset],
ouOFFSET
: dev.mysql.com/doc/refman/5.0/en/delete.htmlRépondre à cela après un long moment ... Je suis tombé sur la même situation et au lieu d'utiliser les réponses mentionnées, je suis venu avec ci-dessous -
DELETE FROM table_name order by ID limit 10
Cela supprimera les 10 premiers enregistrements et conservera les derniers enregistrements.
la source