SQL sorgusu: En son N hariç tablodaki tüm kayıtlar silinsin mi?


91

En son N (id desc'ye göre sıralı) hariç tablodan tüm kayıtları kaldırmak için tek bir mysql sorgusu (değişkenler olmadan) oluşturmak mümkün müdür?

Bunun gibi bir şey, sadece işe yaramıyor :)

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

Teşekkürler.

Yanıtlar:


141

Kayıtları bu şekilde silemezsiniz, asıl sorun, LIMIT cümlesinin değerini belirtmek için bir alt sorgu kullanamamanızdır.

Bu çalışır (MySQL 5.0.67'de test edilmiştir):

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
);

Ara alt sorgu olan gerekli. Onsuz iki hatayla karşılaşırdık:

  1. SQL Hatası (1093): FROM yan tümcesinde güncelleme için hedef tablo 'tablo' belirtemezsiniz - MySQL, doğrudan bir alt sorgu içinden sildiğiniz tabloya başvurmanıza izin vermez.
  2. SQL Hatası (1235): MySQL'in bu sürümü henüz 'LIMIT & IN / ALL / ANY / SOME alt sorgusunu' desteklemiyor - LIMIT deyimini bir NOT IN operatörünün doğrudan alt sorgusu içinde kullanamazsınız.

Neyse ki, bir ara alt sorgu kullanmak, bu sınırlamaların ikisini de atlamamıza olanak tanır.


Nicole, bu sorgunun belirli kullanım durumları (bunun gibi) için önemli ölçüde optimize edilebileceğini belirtti. Sizinkine uyup uymadığını görmek için bu cevabı da okumanızı tavsiye ederim .


4
Tamam bu işe yarıyor - ama benim için böyle gizli numaralara başvurmak zavallı ve tatmin edici değil. Yine de cevap için +1.
Bill Karwin

1
Bunu kabul edilmiş bir cevap olarak işaretliyorum çünkü istediğimi yapıyor. Ama ben şahsen bunu basitleştirmek için muhtemelen iki sorguda yapacağım :) Hızlı ve kolay bir yol olabileceğini düşündüm.
serg

1
Teşekkürler Alex, cevabın bana yardımcı oldu. Ara alt sorgunun gerekli olduğunu görüyorum ama nedenini anlamıyorum. Bunun için bir açıklaman var mı?
Sv1

9
bir soru: "foo" ne için?
Sebastian Breit

9
Perroloco, foo olmadan denedim ve şu hatayı aldım: ERROR 1248 (42000): Her türetilmiş tablonun kendi takma adı olmalıdır Yani onların cevabımız, türetilmiş her tablonun kendi takma adı olmalıdır!
codygman

109

Oldukça eski bir soruyu yeniden canlandırdığımı biliyorum, ancak son zamanlarda bu sorunla karşılaştım, ancak büyük sayılara iyi ölçeklenebilecek bir şeye ihtiyacım vardı . Mevcut herhangi bir performans verisi yoktu ve bu soru oldukça dikkat çektiğinden, bulduğum şeyi göndereceğimi düşündüm.

Gerçekte işe yarayan çözümler Alex Barrett'in çift alt sorgusu /NOT IN yöntemi ( Bill Karwin'inkineLEFT JOIN benzer ) ve Quassnoi'nin yöntemiydi.

Kayıtların sayısı olarak maalesef yukarıdaki yöntemlerden ikisi hızla çok büyük ara geçici tablolar ve performans düşer oluşturmak değil silinmesini büyük olur.

Karar verdiğim şey Alex Barrett'in ikili alt sorgusunu kullanıyor (teşekkürler!), Ancak <=bunun yerine şunu kullanıyor 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
  )

N. kaydın OFFSETkimliğini almak için kullanır ve bu kaydı ve önceki tüm kayıtları siler.

Sipariş zaten bu sorunun bir varsayımı olduğundan ( ORDER BY id DESC), <=mükemmel bir uyumdur.

Alt sorgu tarafından oluşturulan geçici tablo N kayıt yerine yalnızca bir kayıt içerdiğinden çok daha hızlıdır .

Test durumu

Üç çalışma yöntemini ve yukarıdaki yeni yöntemi iki test durumunda test ettim.

Her iki test durumu da mevcut 10000 satırı kullanırken, ilk test 9000'i (en eski 1000'i siler) ve ikinci test 50'yi saklar (en eski 9950'yi siler).

+-----------+------------------------+----------------------+
|           | 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 |
+-----------+------------------------+----------------------+

İlginç olan, <=yöntemin her alanda daha iyi performans görmesidir, ancak aslında daha kötüsü yerine ne kadar çok tutarsanız daha iyi hale gelir.


11
Bu konuyu 4,5 yıl sonra tekrar okuyorum. Güzel ek!
Alex Barrett

Vay canına, bu harika görünüyor ama Microsoft SQL 2008'de çalışmıyor. Şu mesajı alıyorum: "'Limit'e yakın yanlış sözdizimi. MySQL'de çalışması güzel, ancak alternatif bir çözüm bulmam gerekecek.
Ken Palmer

1
@KenPalmer ROW_NUMBER(): stackoverflow.com/questions/603724/…
Nicole

3
@KenPalmer, SQL ve mySQL arasında geçiş yaparken LIMIT yerine SELECT TOP kullanın
Alpha G33k

1
Bunun için şerefe. (Çok büyük) veri setimdeki sorguyu 12 dakikadan 3,64 saniyeye düşürdü!
Lieuwe

10

Ne yazık ki, diğer kişiler tarafından verilen tüm cevaplar için , aynı sorguda belirli bir tablodan DELETEve yapamazsınız 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 LIMITbir alt sorguda da destekleyemez . Bunlar MySQL'in sınırlamalarıdır.

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'

Bulabileceğim en iyi cevap, bunu iki aşamada yapmaktır:

SELECT id FROM mytable ORDER BY id DESC LIMIT n; 

Kimlikleri toplayın ve virgülle ayrılmış bir dize haline getirin:

DELETE FROM mytable WHERE id NOT IN ( ...comma-separated string... );

(Normalde, virgülle ayrılmış bir listeyi bir SQL deyimine enterpolasyon yapmak bazı SQL enjeksiyonu riskini beraberinde getirir, ancak bu durumda değerler güvenilmeyen bir kaynaktan gelmez, veritabanının kendisinden tam sayı değerleri olarak bilinir.)

not: Bu, işi tek bir sorguda tamamlamasa da, bazen daha basit bir çözüm, en etkili çözümdür.


Ancak silme ve seçme arasında iç birleştirmeler yapabilirsiniz. Aşağıda yaptığım şey işe yaramalı.
achinda99

Alt sorguda LIMIT işlevinin çalışmasını sağlamak için bir ara alt sorgu kullanmanız gerekir.
Alex Barrett

@ achinda99: Bu ileti dizisinde sizden bir yanıt göremiyorum ...?
Bill Karwin

Bir toplantı için çekildim. Benim hatam. Şu anda yazdığım sql'yi test etmek için bir test ortamım yok, ancak hem Alex Barret'in yaptığını yaptım hem de içsel birleştirme ile çalışmasını sağladım.
achinda99

MySQL'in aptalca bir sınırlaması. PostgreSQL ile DELETE FROM mytable WHERE id NOT IN (SELECT id FROM mytable ORDER BY id DESC LIMIT 3);iyi çalışıyor.
bortzmeyer

8
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

Kimliğiniz artımlıysa, aşağıdaki gibi bir şey kullanın

delete from table where id < (select max(id) from table)-N

2
Bu güzel numaradaki büyük bir problem: diziler her zaman bitişik değildir (örneğin geri dönüşler olduğunda).
bortzmeyer

5

Son N hariç tüm kayıtları silmek için aşağıda bildirilen sorguyu kullanabilirsiniz.

Bu tek bir sorgu, ancak birçok ifadeye sahip olduğundan, aslında orijinal soruda amaçlandığı şekilde tek bir sorgu değil .

Ayrıca MySQL'deki bir hata nedeniyle bir değişkene ve yerleşik (sorguda) hazırlanmış bir ifadeye ihtiyacınız var.

Umarım yine de yararlı olabilir ...

nnn için satırlar vardır tutmak ve theTable üzerinde çalıştığınız tablodur.

İd adında bir otomatik artırma kaydınız olduğunu varsayıyorum

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;

Bu yaklaşımla ilgili iyi olan şey performanstır : Sorguyu yerel bir DB'de yaklaşık 13.000 kayıtla test ettim ve son 1.000 kaydı tuttum. 0,08 saniyede çalışır.

Kabul edilen cevaptan senaryo ...

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 saniye sürer. Yaklaşık 7 kat daha fazla.

Test ortamı: SSD'li 2011 sonu i7 MacBookPro'da mySQL 5.5.25



1

aşağıdaki sorguyu deneyin:

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

iç alt sorgu ilk 10 değerini döndürür ve dış sorgu ilk 10 dışındaki tüm kayıtları siler.


1
Bunun nasıl çalıştığına dair bazı açıklamalar, bu yanıta rastlayanlar için faydalı olacaktır. Kod dökümü genellikle tavsiye edilmez.
rayryeng

0

Ne dersin :

SELECT * FROM table del 
         LEFT JOIN table keep
         ON del.id < keep.id
         GROUP BY del.* HAVING count(*) > N;

Önceden N'den fazla satır içeren satırları döndürür. Faydalı olabilir mi?


0

Bu görev için id kullanmak çoğu durumda bir seçenek değildir. Örneğin - twitter durumlarına sahip tablo. Belirtilen zaman damgası alanına sahip bir varyant burada.

delete from table 
where access_time >= 
(
    select access_time from  
    (
        select access_time from table 
            order by access_time limit 150000,1
    ) foo    
)

0

Bunu MySQL yerine Microsoft SQL Server kullanan herkes için bir araya getirmek istedim. 'Limit' anahtar kelimesi MSSQL tarafından desteklenmez, bu nedenle bir alternatif kullanmanız gerekir. Bu kod SQL 2008'de çalıştı ve bu SO gönderisine dayanıyor. 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)

Kuşkusuz, bu zarif değil. Bunu Microsoft SQL için optimize edebiliyorsanız, lütfen çözümünüzü paylaşın. Teşekkürler!


0

Kayıtları başka bir sütuna göre de silmeniz gerekiyorsa, işte bir çözüm:

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

Bu da çalışmalıdır:

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]

0
DELETE FROM table WHERE id NOT IN (
    SELECT id FROM table ORDER BY id, desc LIMIT 0, 10
)


-1

Bunu uzun bir süre sonra cevaplamak ... Aynı durumla karşılaştım ve bahsedilen cevapları kullanmak yerine aşağıdaki ile geldim -

DELETE FROM table_name order by ID limit 10

Bu, ilk 10 kaydı silecek ve en son kayıtları saklayacaktır.


Soru, "tümü son N kayıt hariç" ve "tek bir sorguda" olarak soruldu. Ancak, tablodaki tüm kayıtları saymak ve ardından toplamla sınırlamak için hala bir ilk sorguya ihtiyacınız var gibi görünüyor - H
Paolo

@Paolo Yukarıdaki sorgu son 10 kayıt dışında tümünü sildiği için tüm kayıtları saymak için bir sorguya ihtiyacımız yoktur.
Nitesh

1
Hayır, bu sorgu en eski 10 kaydı siler. OP, en son n kayıt dışında her şeyi silmek istiyor. OP, her şeyi tek bir sorguda birleştirmenin bir yolu olup olmadığını sorarken, sizinki bir sayma sorgusuyla eşleştirilecek temel çözümdür.
ChrisMoll

@ChrisMoll Kabul ediyorum. Kullanıcıların bana olumsuz oy vermemesi veya olduğu gibi bırakmaması için bu yanıtı şimdi düzenleyip silmeli miyim?
Nitesh
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.