ActiveRecord - Get the last n records and delete them in one command?

To do it in one SQL query use delete_all:

Model.order(created_at: :desc).limit(n).delete_all

But delete_all won't execute any model callbacks or validations

To run callbacks and validations use destroy_all:

Model.order(created_at: :desc).limit(n).destroy_all

Unfortunately destroy_all will execute n + 1 SQL queries: 1 query to retrieve records and n queries to delete each record.


You can achieve this by:

Model.last(n).each(&:destroy)

@IvanDenisov points out another way to do this:

Model.order('created_at DESC').limit(n).destroy_all

It's basically doing the same thing according to Rails API Doc, but a little bit verbose. Besides, it doesn't do all things in one SQL query.


Detailed comparison of SQL queries

I tried to run both codes in rails console under Ruby 2.0.0p253 && Rails 4.0.4, here are the results:

2.0.0p353 :002 > Role.last(3).each(&:destroy)
  Role Load (1.0ms)  SELECT "roles".* FROM "roles" ORDER BY "roles"."id" DESC LIMIT 3
   (0.3ms)  BEGIN
  SQL (3.5ms)  DELETE FROM "roles" WHERE "roles"."id" = $1  [["id", 5487]]
   (11.8ms)  COMMIT
   (0.1ms)  BEGIN
  SQL (0.2ms)  DELETE FROM "roles" WHERE "roles"."id" = $1  [["id", 5488]]
   (5.4ms)  COMMIT
   (0.1ms)  BEGIN
  SQL (0.2ms)  DELETE FROM "roles" WHERE "roles"."id" = $1  [["id", 5489]]
   (4.6ms)  COMMIT

2.0.0p353 :004 > Role.order('created_at DESC').limit(3).destroy_all
  Role Load (0.9ms)  SELECT "roles".* FROM "roles" ORDER BY created_at DESC LIMIT 3
   (0.2ms)  BEGIN
  SQL (0.2ms)  DELETE FROM "roles" WHERE "roles"."id" = $1  [["id", 5492]]
   (6.6ms)  COMMIT
   (0.2ms)  BEGIN
  SQL (0.2ms)  DELETE FROM "roles" WHERE "roles"."id" = $1  [["id", 5491]]
   (0.4ms)  COMMIT
   (0.1ms)  BEGIN
  SQL (0.1ms)  DELETE FROM "roles" WHERE "roles"."id" = $1  [["id", 5490]]
   (0.2ms)  COMMIT

The DELETE parts are exactly the same. They both took multiple SQL queries.

The only difference is SELECT part, if we change 'created_at DESC' to 'id DESC', they will be exactly the same too.