ActiveRecord regroup method

ActiveRecord’s regroup method is a shorthand for using unscope to remove existing groups.

Sometimes we want to change the query of an existing ActiveRecord::Relation object. Consider this query, which groups user’s by country:

1
query = User.group(:country)

We have a requirement to modify this query to group users by device type, in order to see what percentage use desktop vs mobile. The simplest way to solve the problem is to write a new query from scratch. However, if the query is complex and we only want to adjust the group, it’s easier to use regroup:

1
query.regroup(:device)

How does group work?

In the second half of this post we’ll explain how group works.

GROUP BY

group is an ActiveRecord method for writing SQL GROUP BY, which is used to group rows that have the same value. For example, this query groups results by country and counts the number of customers in each:

1
2
3
SELECT COUNT(CustomerID), Country
FROM Customers
GROUP BY Country;
1
2
3
4
5
6
7
8
+-------------------+-----------+
| COUNT(CustomerID) | Country   |
+-------------------+-----------+
| 3                 | Argentina |
| 2                 | Austria   |
| 2                 | Belgium   |
| 9                 | Brazil    |
+-------------------+-----------+

ActiveRecord to SQL

Now that we understand how GROUP BY works. Let’s look at how group and regroup translate to SQL.

Here’s a query with a single group:

1
2
3
query = User.group(:country)
query.to_sql
=> "SELECT \"users\".* FROM \"users\" GROUP BY \"users\".\"country\""

It generates the following SQL:

1
2
3
SELECT users
FROM users 
GROUP BY country

Let’s see what happens if we add a second group, without using regroup:

1
2
query.group(:device).to_sql
=> "SELECT \"users\".* FROM \"users\" GROUP BY \"users\".\"country\", \"users\".\"device\""

This results in the following SQL:

1
2
3
SELECT users
FROM users 
GROUP BY country, device

This isn’t what we want. We don’t want to group by both country and device. We want the overall device breakdown. Let’s use regroup instead:

1
2
query.regroup(:device).to_sql
=> "SELECT \"users\".* FROM \"users\" GROUP BY \"users\".\"device\""

Now we have a SQL query which will correctly group by device:

1
2
3
SELECT users
FROM users 
GROUP BY device