Você não pode excluir os registros dessa maneira, o principal problema é que você não pode usar uma subconsulta para especificar o valor de uma cláusula LIMIT.
Isso funciona (testado em 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
);
A subconsulta intermediária é necessária. Sem ele, teríamos dois erros:
- Erro de SQL (1093): Você não pode especificar a tabela de destino 'tabela' para atualização na cláusula FROM - o MySQL não permite que você consulte a tabela que está excluindo de uma subconsulta direta.
- Erro SQL (1235): Esta versão do MySQL ainda não suporta 'subconsulta LIMIT & IN / ALL / ANY / SOME' - Você não pode usar a cláusula LIMIT em uma subconsulta direta de um operador NOT IN.
Felizmente, usar uma subconsulta intermediária nos permite ignorar essas duas limitações.
Nicole apontou que essa consulta pode ser otimizada significativamente para certos casos de uso (como este). Recomendo também a leitura dessa resposta para ver se ela se encaixa na sua.
Sei que estou ressuscitando uma questão bastante antiga, mas recentemente me deparei com esse problema, mas precisava de algo que se dimensionasse bem para grandes números . Não havia nenhum dado de desempenho existente, e como esta questão chamou bastante atenção, pensei em postar o que encontrei.
As soluções que realmente funcionaram foram a subconsulta /
NOT IN
método duplo de Alex Barrett (semelhante ao de Bill Karwin ) e oLEFT JOIN
método de Quassnoi .Infelizmente, os dois métodos acima criam tabelas temporárias intermediárias muito grandes e o desempenho diminui rapidamente à medida que aumenta o número de registros que não estão sendo excluídos.
O que decidi utiliza a subconsulta dupla de Alex Barrett (obrigado!), Mas usa em
<=
vez 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 )
Ele usa
OFFSET
para obter o ID do N º registro e exclui esse registro e todos os registros anteriores.Como ordenar já é uma suposição desse problema (
ORDER BY id DESC
),<=
é um ajuste perfeito.É muito mais rápido, pois a tabela temporária gerada pela subconsulta contém apenas um registro em vez de N registros.
Caso de teste
Testei os três métodos de trabalho e o novo método acima em dois casos de teste.
Ambos os casos de teste usam 10.000 linhas existentes, enquanto o primeiro teste mantém 9.000 (exclui os 1.000 mais antigos) e o segundo teste mantém 50 (exclui os 9950 mais antigos).
+-----------+------------------------+----------------------+ | | 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 | +-----------+------------------------+----------------------+
O que é interessante é que o
<=
método tem melhor desempenho em todas as áreas, mas na verdade fica melhor quanto mais você guarda, em vez de piorar.fonte
ROW_NUMBER()
: stackoverflow.com/questions/603724/…Infelizmente, para todas as respostas dadas por outras pessoas, você não pode
DELETE
eSELECT
de uma determinada tabela na mesma consulta.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
Nem o MySQL oferece suporte
LIMIT
em uma subconsulta. Essas são limitações do 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'
A melhor resposta que posso dar é fazer isso em dois estágios:
SELECT id FROM mytable ORDER BY id DESC LIMIT n;
Colete os ids e transforme-os em uma string separada por vírgulas:
DELETE FROM mytable WHERE id NOT IN ( ...comma-separated string... );
(Normalmente, interpolar uma lista separada por vírgulas em uma instrução SQL apresenta algum risco de injeção de SQL, mas, neste caso, os valores não vêm de uma fonte não confiável, eles são conhecidos como valores inteiros do próprio banco de dados.)
nota: embora isso não conclua o trabalho em uma única consulta, às vezes uma solução mais simples e pronta é a mais eficaz.
fonte
DELETE FROM mytable WHERE id NOT IN (SELECT id FROM mytable ORDER BY id DESC LIMIT 3);
funciona bem.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
fonte
Se o seu id for incremental, use algo como
delete from table where id < (select max(id) from table)-N
fonte
Para excluir todos os registros, exceto o último N, você pode usar a consulta relatada abaixo.
É uma consulta única, mas com muitas instruções, portanto, não é realmente uma única consulta da maneira que se pretendia na pergunta original.
Além disso, você precisa de uma variável e uma instrução preparada embutida (na consulta) devido a um bug no MySQL.
Espero que possa ser útil de qualquer maneira ...
nnn são as linhas a serem mantidas e theTable é a tabela na qual você está trabalhando.
Presumo que você tenha um registro de incremento automático chamado 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;
O bom dessa abordagem é o desempenho : testei a consulta em um banco de dados local com cerca de 13.000 registros, mantendo os últimos 1.000. Ele roda em 0,08 segundos.
O script da resposta aceita ...
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 );
Demora 0,55 segundos. Cerca de 7 vezes mais.
Ambiente de teste: mySQL 5.5.25 em um MacBookPro i7 do final de 2011 com SSD
fonte
DELETE FROM table WHERE ID NOT IN (SELECT MAX(ID) ID FROM table)
fonte
experimente a consulta abaixo:
DELETE FROM tablename WHERE id < (SELECT * FROM (SELECT (MAX(id)-10) FROM tablename ) AS a)
a subconsulta interna retornará os 10 valores principais e a consulta externa excluirá todos os registros, exceto os 10 principais.
fonte
A respeito :
SELECT * FROM table del LEFT JOIN table keep ON del.id < keep.id GROUP BY del.* HAVING count(*) > N;
Ele retorna linhas com mais de N linhas antes. Pode ser útil?
fonte
O uso de id para esta tarefa não é uma opção em muitos casos. Por exemplo - tabela com status do Twitter. Aqui está uma variante com campo de carimbo de data / hora especificado.
delete from table where access_time >= ( select access_time from ( select access_time from table order by access_time limit 150000,1 ) foo )
fonte
Só queria adicionar isso à mistura para qualquer pessoa que use o Microsoft SQL Server em vez do MySQL. A palavra-chave 'Limite' não é compatível com MSSQL, portanto, você precisará usar uma alternativa. Este código funcionou no SQL 2008 e é baseado neste post do 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)
É certo que isso não é elegante. Se você puder otimizar isso para o Microsoft SQL, compartilhe sua solução. Obrigado!
fonte
Se você precisar excluir os registros com base em alguma outra coluna também, aqui está uma solução:
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
fonte
Isso também deve funcionar:
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]
fonte
DELETE FROM table WHERE id NOT IN ( SELECT id FROM table ORDER BY id, desc LIMIT 0, 10 )
fonte
Por que não
DELETE FROM table ORDER BY id DESC LIMIT 1, 123456789
Apenas exclua tudo, exceto a primeira linha (a ordem é DESC!), Usando um número muito grande como segundo argumento LIMIT. Veja aqui
fonte
DELETE
não suporta[offset],
ouOFFSET
: dev.mysql.com/doc/refman/5.0/en/delete.htmlRespondendo isso depois de muito tempo ... Encontrei a mesma situação e em vez de usar as respostas mencionadas, vim com abaixo -
DELETE FROM table_name order by ID limit 10
Isso excluirá os primeiros 10 registros e manterá os registros mais recentes.
fonte