SQL GROUP BY Multiple Columns
List more than one column to group — a new group forms for every distinct COMBINATION of values, not just each column separately.
What & Why
Grouping by one column answers "one number per channel." Sometimes that's not specific enough — "one number per channel, split further by whether the campaign is still active" needs a second grouping column. List both columns, separated by a comma: GROUP BY channel, status.
The important mental model here: Postgres doesn't group by channel and then separately by status — it groups by the unique combination of both values together. Two rows only land in the same group if they match on every listed column, not just one of them. IT-and-active is a different group from IT-and-inactive, even though both share the same channel value.
See How It Works
Marketing wants campaign counts for every channel and status combination.
| id | name | channel | spend | start_date | end_date | status | target_segment |
|---|---|---|---|---|---|---|---|
| 1 | Spring Launch | google_ads | 55000.00 | 2024-01-15 | 2024-03-31 | active | smb |
| 2 | Retention Webinar | 45000.00 | 2024-02-10 | 2024-04-15 | active | enterprise | |
| 3 | Finance Retargeting | 50000.00 | 2024-03-12 | 2024-05-31 | active | enterprise | |
| 4 | Enterprise Search | google_ads | 60000.00 | 2024-04-01 | 2024-06-30 | active | enterprise |
| id | name | channel | status | group key | state |
|---|---|---|---|---|---|
| 1 | Spring Launch | google_ads | active | google_ads · active | CURRENT |
| 2 | Retention Webinar | active | email · active | WAIT | |
| 3 | Finance Retargeting | active | linkedin · active | WAIT | |
| 4 | Enterprise Search | google_ads | active | google_ads · active | WAIT |
| channel | status | campaign_count |
|---|---|---|
| google_ads | active | 1 |
Spring Launch contributes to the google_ads · active group; the second google_ads row visibly increments the same bucket.
SELECT
channel,
status,
COUNT(*) AS campaign_count
FROM marketing.campaigns
GROUP BY channel, status
ORDER BY channel, status;Practice this concept
Marketing wants campaign counts for every channel and status combination.
marketingPrefix tables with marketing.table_name.
channelstatuscampaign_countmarketing.campaigns| Column | Type |
|---|---|
| id | integer |
| name | text |
| channel | text |
| spend | numeric |
| start_date | date |
| end_date | date |
| status | text |
| target_segment | text |
| legacy_id | text |
Sign up free to try it on a real business scenario