lesson 20 Joins & sets · intermediate
Ratios, ROUND & NULLIF
Calculate safe ratios and round your results.
the idea
A ratio needs a defined numerator and denominator. Multiply by 100.0 before dividing so SQLite does not use integer division, ROUND the result for display, and wrap the denominator in NULLIF(…, 0) so an empty group gives NULL instead of a misleading number.
Dataset: Product / SaaS Analytics
The tables the challenges use, with their first 6 rows. Under each column name: the kind of value it holds, and which table it links to.
invitations7 columns · 6,450 rows
| invitation_idnumber · unique id | account_idnumber · links to accounts | invited_by_user_idnumber · links to users | invitee_emailtext | sent_atdate & time | accepted_atdate & time | accepted_user_idnumber · links to users |
|---|---|---|---|---|---|---|
| 1 | 1 | 1 | deepa.bell@ironcapital1.example | 2024-01-21 11:24:15 | 2024-01-24 03:43:14 | 2 |
| 2 | 1 | 1 | sofia.wilson@mail.example | 2024-02-02 07:45:53 | 2024-02-02 21:34:25 | 3 |
| 3 | 2 | 6 | nadia.flores@vectordata2.example | 2024-01-25 09:18:58 | NULL | NULL |
| 4 | 2 | 6 | rosa.jones@vectordata2.example | 2024-02-03 06:27:24 | 2024-02-03 07:03:46 | 7 |
| 5 | 3 | 10 | raj.li@truefoods3.example | 2024-01-20 15:16:56 | NULL | NULL |
| 6 | 3 | 10 | ayesha.silva@truefoods3.example | 2024-01-24 19:32:04 | 2024-01-26 05:58:03 | 11 |
- invitation_idnumber · unique id
- 1
- account_idnumber · links to accounts
- 1
- invited_by_user_idnumber · links to users
- 1
- invitee_emailtext
- deepa.bell@ironcapital1.example
- sent_atdate & time
- 2024-01-21 11:24:15
- accepted_atdate & time
- 2024-01-24 03:43:14
- accepted_user_idnumber · links to users
- 2
- invitation_idnumber · unique id
- 2
- account_idnumber · links to accounts
- 1
- invited_by_user_idnumber · links to users
- 1
- invitee_emailtext
- sofia.wilson@mail.example
- sent_atdate & time
- 2024-02-02 07:45:53
- accepted_atdate & time
- 2024-02-02 21:34:25
- accepted_user_idnumber · links to users
- 3
- invitation_idnumber · unique id
- 3
- account_idnumber · links to accounts
- 2
- invited_by_user_idnumber · links to users
- 6
- invitee_emailtext
- nadia.flores@vectordata2.example
- sent_atdate & time
- 2024-01-25 09:18:58
- accepted_atdate & time
- NULL
- accepted_user_idnumber · links to users
- NULL
- invitation_idnumber · unique id
- 4
- account_idnumber · links to accounts
- 2
- invited_by_user_idnumber · links to users
- 6
- invitee_emailtext
- rosa.jones@vectordata2.example
- sent_atdate & time
- 2024-02-03 06:27:24
- accepted_atdate & time
- 2024-02-03 07:03:46
- accepted_user_idnumber · links to users
- 7
- invitation_idnumber · unique id
- 5
- account_idnumber · links to accounts
- 3
- invited_by_user_idnumber · links to users
- 10
- invitee_emailtext
- raj.li@truefoods3.example
- sent_atdate & time
- 2024-01-20 15:16:56
- accepted_atdate & time
- NULL
- accepted_user_idnumber · links to users
- NULL
- invitation_idnumber · unique id
- 6
- account_idnumber · links to accounts
- 3
- invited_by_user_idnumber · links to users
- 10
- invitee_emailtext
- ayesha.silva@truefoods3.example
- sent_atdate & time
- 2024-01-24 19:32:04
- accepted_atdate & time
- 2024-01-26 05:58:03
- accepted_user_idnumber · links to users
- 11
first 6 of 6,450 rows · your queries use all of them
accounts7 columns · 3,200 rows
| account_idnumber · unique id | account_nametext | industrytext | company_sizetext | countrytext | acquisition_channeltext | created_atdate & time |
|---|---|---|---|---|---|---|
| 1 | Iron Capital Inc | Logistics | 11-50 | United States | organic | 2024-01-14 18:52:14 |
| 2 | Vector Data Co | Nonprofit | 51-200 | United Kingdom | social | 2024-01-15 13:21:52 |
| 3 | True Foods Inc | Healthcare | 201-1000 | United States | outbound_sales | 2024-01-19 11:22:02 |
| 4 | Meridian Works | Software | 11-50 | United States | organic | 2024-01-23 23:42:36 |
| 5 | Prism Health Group | Software | 1-10 | Germany | referral | 2024-01-25 11:43:43 |
| 6 | Beacon Supply Ltd | Media | 1000+ | United Kingdom | organic | 2024-01-27 20:19:28 |
- account_idnumber · unique id
- 1
- account_nametext
- Iron Capital Inc
- industrytext
- Logistics
- company_sizetext
- 11-50
- countrytext
- United States
- acquisition_channeltext
- organic
- created_atdate & time
- 2024-01-14 18:52:14
- account_idnumber · unique id
- 2
- account_nametext
- Vector Data Co
- industrytext
- Nonprofit
- company_sizetext
- 51-200
- countrytext
- United Kingdom
- acquisition_channeltext
- social
- created_atdate & time
- 2024-01-15 13:21:52
- account_idnumber · unique id
- 3
- account_nametext
- True Foods Inc
- industrytext
- Healthcare
- company_sizetext
- 201-1000
- countrytext
- United States
- acquisition_channeltext
- outbound_sales
- created_atdate & time
- 2024-01-19 11:22:02
- account_idnumber · unique id
- 4
- account_nametext
- Meridian Works
- industrytext
- Software
- company_sizetext
- 11-50
- countrytext
- United States
- acquisition_channeltext
- organic
- created_atdate & time
- 2024-01-23 23:42:36
- account_idnumber · unique id
- 5
- account_nametext
- Prism Health Group
- industrytext
- Software
- company_sizetext
- 1-10
- countrytext
- Germany
- acquisition_channeltext
- referral
- created_atdate & time
- 2024-01-25 11:43:43
- account_idnumber · unique id
- 6
- account_nametext
- Beacon Supply Ltd
- industrytext
- Media
- company_sizetext
- 1000+
- countrytext
- United Kingdom
- acquisition_channeltext
- organic
- created_atdate & time
- 2024-01-27 20:19:28
first 6 of 3,200 rows · your queries use all of them
worked example
Run these steps in order to see the idea before you write your own query. The step marked “the pattern” is what the challenges ask for. Running the example does not count toward progress.
Count sent and accepted per workspace. Run this. COUNT(*) is invitations sent; COUNT(accepted_at) counts those with an acceptance timestamp.
step-1.sqlSELECT account_id, COUNT(*) AS sent, COUNT(accepted_at) AS accepted FROM invitations GROUP BY account_id LIMIT 15;
Divide without breaking on zero. Run this. NULLIF turns a zero denominator into NULL so you do not divide by zero; 100.0 avoids integer division.
step-2.sqlSELECT 100.0 * 3 / NULLIF(0, 0) AS undefined_rate, ROUND(100.0 * 3 / 7, 2) AS rate;
Put it together. The lesson's pattern in one query.the pattern
example.sqlSELECT account_id, COUNT(*) AS sent, COUNT(accepted_at) AS accepted FROM invitations GROUP BY account_id;
common mistake
COUNT(accepted_at) / COUNT(*) is integer division in SQLite and returns 0 for most accounts.