Performing a SQL UPDATE in Rails 14

Posted by jeff Monday, July 23, 2007 20:54:00 GMT

I saw this question on the Ruby on Rails mailing list:

...If you had yes/no fields stored as 1’s and 0’s could you write a simple statement to iterate though all of them and change the 1 to a “Yes”?

I know you can do it individually but how about all of them?

This is an excellent question. Indeed, there are many ways to update a single object:


@customer.update_attribute :zip, '60620'

# or

@customer.update_attributes :zip => '60620'

# or

@customer.zip = '60620'
@customer.save
But what if you need to update lots of rows at once? Doing this:

@customer.find(:all).each { |customer| customer.update_attribute :zip, '60623' }

is not advisable, unless you’re sure that the number of customers is relatively small. If you have 100,000 customers, you will loop through each one, generating a lot of traffic to the database – and it will probably take a very long time.

Instead, you can use the update_all class method of ActiveRecord::Base. You can pass two string parameters (the ‘set’ part and the ‘where’ part):


Customer.update_all 'zip = 60620', 'zip IS NULL'

The SQL statement you get will be equivalent to:

UPDATE Customers SET zip = '60620' WHERE (zip is NULL)

The second parameter is optional, so you can leave it off if you want to update every row:


Customer.update_all 'wants_email = 0'

Even better, the first parameter can be a hash instead of a SQL fragment:


Customer.update_all :zip => '60620', :wants_email => 0, 'zip IS NULL'

But beware! Using update_all bypasses all of your model validations, so you need to make sure you know what you’re doing.


Essential Rails: Up to speed with Rails in just two days.

Comments

Leave a response

  1. cbt   July 24, 2007 @ 06:20 AM

    Nice article. It didn't occur to me that I can do this via Active record.

  2. Steve Tooke   July 24, 2007 @ 08:19 AM

    Excellent thanks. I came across this the other day, but it didn't quite do what I wanted to do.

    Is there any way to put any logic into the set? So you can do a calculation on the original value, for example to add 5 to every price or something?

  3. Pratik   July 24, 2007 @ 09:48 AM

    You might want to add somewhere that even @customer.update_attribute ( notice the singalr form ) bypasses all the model validations. It's overridden in validations.rb file of activerecord where it calls save without validations.

    Thanks, Pratik

  4. Jeff   July 24, 2007 @ 02:29 PM

    @Steve: If you pass string arguments (instead of a hash like I showed near the end), you can supply any valid SQL fragment that will fit into the "SET" and "WHERE" clauses of the UPDATE statement.

    @Pratik: Good point, thanks for that.

  5. Syrups   July 24, 2007 @ 08:33 PM

    Nice article. Thanks!

  6. andy   July 24, 2007 @ 09:51 PM

    I just recently subscribed to your blog, and this post is hits an issue I've just worked through.

    It should be noted that when the first param to update_all method is a hash of update values the SQL syntax generated is...

    update table set colx = valx AND coly = valy;

    The issue being the "AND" where usual SQL syntax is for this to be a "," (comma). While this "AND" syntax apparently works in MySQL, it does not work in Firebird. Other DB problems may exist, but I only tested those two.

    Just a word of caution for those not using MySQL.

    Regards

  7. Jeff   July 25, 2007 @ 12:04 AM

    Andy: Another good point, I should have explicitly said that the values get ANDed together. If a comma is better SQL syntax in this case, you should definitely consider submitting a patch to the Rails trac (or if you don't have time, let me know and I'll whip one up). (It worked for me on Postgres, too, by the way).

  8. Brian   December 18, 2007 @ 06:31 PM

    Is there any way to give custom sql statement that will update it table? I am asking since I have complicated SQL statement that I would like to execute.

    Thanks

  9. Los Angeles Electricians   December 20, 2007 @ 06:59 AM

    Brian,

    You can use: ModelName.update_all ["new value"], ["condition"]

    Thanks

  10. Henry   April 04, 2008 @ 06:11 PM

    Management.updateall( :accountcode =>@accounttoid, :accountcode => @accountfrom_id )

    I'm trying to set the :accountcode in the db to the value in @accountto_id , where db field ":accountcode" equals the value @accountfrom_id. What happens is that all the values change to the value in @accountfromid

  11. henry   April 04, 2008 @ 07:20 PM

    I figured it out , here the code if it help anyone

    Management.updateall( "accountcode = #{@accounttoid}","accountcode = #{@accountfrom_id}")

  12. abcteach.com   May 14, 2008 @ 03:06 PM

    thanks for the help…i’m testing it out and will share the results here too

  13. kino   May 24, 2008 @ 01:11 AM

    It must not be supposed that our experience would be falsified.

  14. Ellroy   May 30, 2008 @ 11:19 PM

    By virtue of my free epoche with respect to the being of the experienced world, the momentous fact is that, by reconciling with experiences, only in reflection do we “direct” ourselves to the Objective world and to its perceptual directedness to separated modes of consciousness.

Comment


(won't be published)