Lightning Fast Sql Selects Via RailsI’ve got this situation. And it calls for lightning fast selects across multiple That’s fine. I’ll show you how you can leverage ActiveRecord to do the syntax for you. You know ruby right? Here’s the gist of what I want:
If you’re squirming at all, it means you know this looks terrible. Here’s the resulting log.
So, we’re instantiating all characters into ruby objects, as well as looping through each one and creating ruby objects for all the empires just to compare the number of cities? What will your 199,990 active users (who didn’t get on the list) think when their game is interrupted for 10 minutes to give nods to the biggest, go-getting jerks on the system? Alright. We’ll make it quicker, okay?
This looks a lot better – prettier too. Find takes several useful options. Hopefully, you’re already aware of the :limit and :order options. If not take some time to glance at find in the api. This page will become very familiar to you… Gloriously, no sql is needed for simple queries! Find’s :joins key takes symbols (or an array of symbols if you’re joining more than one table) for table names and by default inner joins the tables. Inner joins are usually what you’re after. In this case, an inner join would give me all the characters who already have an empire associated with them. You may be tempted to use an :include instead of a :joins in this situation. Stick with the :joins for now, and I will exlain later on why the :include is slower. ActiveRecord also allows for different types of joins, but you need to use a longer route. Say you wanted a list of all characters without the limit and ordered by total cities. You also want all the characters without empires and without cities in the list. This would be a left outer join and would look a lot like:
So you have to spell out some basic sql. Not too hard. For more info on other types of joins, check out The Basic of JOINs from Pinal Dave. As you can see, he’s an authority in this area. Anyhow, lets look at the log of the previous select.
As the numbers (the 0.007788, etc) indicate, this entire operation runs in about a third of the time as the first trial’s character select. Beyond that, the first trial also has to load all of the empires. Believe it or not, we can get this faster still. The only data shown on the page, the only data we need, is the characters’ names and city counts. ActiveRecord, was being nice and assumed you wanted all the data columns. Well, we don’t. It’s easy to change that behavior.
Don’t forget the join needs the character ids from each table to be in the select as well. I’ll take a moment to clear up the :joins versus :include issue now. Includes are for eager loading. If we eager loaded (through an include) an empire with a character find,
when we called that character.empire, rails would magically already have it loaded. No database calls. This sounds good, but the problem is that rails does not apply select statements to eagerly loaded columns. In other words, we’d be loading up the entire empire, not just the city count. The solution is to select columns on joined column as methods (ie: as total_cities above). This way you can call the method on the character and have it return the value from the joined table. Here, character_instance.total_cities would return the correct number. You now have the 3 values you needed for a ranking: the order (index in the character array), the name (character.name), and the count (character.total_cities). Let’s see how the database handled it.
Good God! We’ve brought the request down to under 3 milliseconds. This should give you enough to time to do your ranking by population |
|