Progressive SQL Activity — Leveling Up
This activity was made by Robinson Andres Cortes
In this activity, you’ll explore SQL queries through a set of research-based exercises. Try to complete the activity using only W3Schools, w3resource or SQLhabit as a reference.
Copy and paste the provided code into your preferred SQL DBMS. While the exercises work with any SQL engine, the example solution uses MySQL.
We don’t repeat queries that do exactly the same thing.
Each step adds a new layer of reasoning.
When someone finishes Level 5 entirely on their own, they are no longer just learning SQL
they are learning to think in data.
Level 1 — Fundamentals (Basic Exploration)
-
List all users.
Check if I’m right
In this exercise, we need to select all the data from the table in the database we created. In this case, the table is called
users.
In real-world SQL, this solution does not differ much from what you’ll see in practice. The key concept here is that the*operator means all columns.With that in mind, here is the solution:
SELECT * FROM users -
Show only
first_name,last_name, andemail.Reveal the truth
In this exercise, we continue with the same logic as the previous one. The only change is the selected columns.
Instead of selecting all columns (*), we now only need to retrievefirst_name,last_name, andemail.With that in mind, here is the solution:
SELECT first_name, last_name, email FROM users -
Filter users whose
roleis'admin'.Show me the magic
In this exercise, we introduce another SQL concept called
WHERE.
This is a conditional statement that filters data: only rows that match the given condition will be shown:SELECT * FROM users WHERE role = 'admin' -
Filter users with
document_type = 'CC'.Moment of truth
This one follows the last exercise, now we are not asking for the
role.
Instead we are searching for thedocument_type:SELECT * FROM users WHERE document_type = 'CC' -
Show users older than 18 years (calculate age from
birth_date).Did I nail it?
To solve this challenge, we need to introduce the
TIMESTAMPDIFF()function.
In MySQL,TIMESTAMPDIFF()is a powerful function used to calculate the difference between twoDATEorDATETIMEvalues.In this case we convert the
birth_dateinto years by comparing it with current date, and the check whether the user is older than 18 years.The exercise might look like this:
SELECT * FROM users WHERE TIMESTAMPDIFF(YEAR, birth_date , CURDATE()) > 18More information about
TIMESTAMPDIFF()in here. -
Show users whose income is greater than 5,000,000.
Click for spoilers
Following the same login in the last exercise, we can use
>greater than symbol.
We have to make sure thatmonthly_incomeis grater than5000000.With that in mind, here is the solution:
SELECT * FROM users WHERE monthly_income > 5000000 -
Show users whose name starts with "A".
Let’s see…
In order to solve this exercise, I have to introduce you to a new concept call
LIKE operator.
TheLIKEoperator is used in aWHEREclause to search for a specified pattern in a column.
To return records that starts with a specific letter or phrase, add the%at the end of the letter or phrase.The exercise might look like this:
SELECT * FROM users WHERE first_name like 'a%'More information about
LIKE operatorin here. -
Show users who do not have a
company.Risky click
To solve this challenge, we first need to understand that a user without a company has
company = ǸULL.For this, we introduce a new SQL concept: the
ISoperator.
TheISoperator is used to compare a value withNULLor with boolean values (TRUE,FALSE). It is especially important becauseNULLcannot be compared using the=operator..With that in mind, here is the solution:
SELECT * FROM users WHERE company IS NULLMore information about
IS operatorin here.
Here you already learned SELECT, WHERE, logical operators, and NULL.
Level 2 — Combining Conditions
-
Users older than 25 years who are
'employee'.Answer, please
In this exercise, we introduce some new SQL operators. The first one is
AND.The
ANDoperator is a logical operator that returnsTRUEonly if both conditions areTRUE.
In this case, the condition will returnTRUEif the user is older than 25 and is an employee.Another important concept is the
NOToperator.
TheNOToperator is also a logical operator that returnsTRUEwhen a condition is not met.
In this case, ifcompanyis notNULL, the condition will returnTRUE.With that in mind, here is the solution:
SELECT * FROM users WHERE TIMESTAMPDIFF(YEAR, birth_date , CURDATE()) > 25 AND company IS NOT NULLMore information about the
AND operatorin here.
More information about theNOT operatorin here. -
Users with
'CC'who are active.Am I a genius?
Following the last exercise we only need to check if the
document_type='CC'andis_active=1:SELECT * FROM users WHERE document_type = 'CC' AND is_active = 1 -
Users of legal age without employment.
Or… not?
This exercise is the same as Exercise 9, but it filters users who are over 18 years old, which is the legal age in my country.
SELECT * FROM users WHERE TIMESTAMPDIFF(YEAR, birth_date , CURDATE()) > 17 and company IS NULL -
Users with a job and income greater than 3,000,000.
Time to confess
First, we check that the user has a job.
This means the company column must not be NULL.Then, we filter users whose income is greater than 3,000,000 using the
>comparison operator.
Both conditions must be true for a user to appear in the result.Taking that into count, here is the solution:
SELECT * FROM users WHERE company IS NOT NULL AND monthly_income > 3000000 -
Married users with at least 1 child.
Show the answer
First, we check the marital status by comparing the
marital_statuscolumn with the value'Casado'.
Then, we make sure the user has at least one child by checking thatchildren_countis greater than or equal to1.Both conditions must be true for the user to be included in the result.
SELECT * FROM users WHERE marital_status = 'Casado' AND children_count >= 1 -
Users between 30 and 40 years old.
Proof or pain
In this one we first check that the age of the user is equal or more than 30 and equal or less than 40.
This makes the result to be between 30 and 40.SELECT * FROM users WHERE TIMESTAMPDIFF(YEAR, birth_date , CURDATE()) >= 30 AND TIMESTAMPDIFF(YEAR, birth_date , CURDATE()) <= 40 -
Verified
'admin'users older than 25 years.Judge me
We need first make sure that the role of the user is
adminand he is older that 25.SELECT * FROM users WHERE role = 'admin' AND TIMESTAMPDIFF(YEAR, birth_date , CURDATE()) > 25
Here we combine multiple conditions and boolean logic.
Level 3 — Introduction to Analysis (Aggregations)
-
Count users by
role.Reality check
To start this section, we need to introduce some new concepts that will help us solve this challenge.
The first concept is the
COUNT()function.
TheCOUNT()function is used to count the number of rows that match a specified condition.The second concept is the
GROUP BYstatement.
It groups rows that have the same values into summary rows.The last concept is the
ASkeyword, which is used to rename a column or table using an alias.Enough theory — let’s put our hands on the keyboard.
First, we need to
SELECTthe data we want to analyze. In this case, we select therolecolumn and the number of times each role appears usingCOUNT(role).
Instead of showing the default column nameCOUNT(role), we use theASkeyword to give it a more readable alias.Next, we specify the table where the data is stored, and finally we use
GROUP BYto group the results by role.SELECT role, COUNT(role) AS Counting FROM users GROUP BY roleMore information about the
COUNT() functionin here.
More information about theGROUP BY statementin here.
More information about theAS keywordin here. -
Count users by
document_type.Don’t laugh
This exercise is really similar to the last one. Instead we should use the
document_typeas the search field.SELECT document_type, COUNT(document_type) AS Counting FROM users GROUP BY document_type -
Count how many users are unemployed.
Be gentle
In this case we take the
'Unemployed'as a fixed value andCOUNT(*)for all the users in theuserstable.
Then we only take the usersWHEREitscompany IS NULL, that means they are unemployed.SELECT 'Unemployed', COUNT(*) AS Counting FROM users WHERE company is NULL -
Calculate the overall average income.
Click wisely
For this exercise, we need to introduce a couple of new SQL concepts.
The first one is the
AVG()function.
TheAVG()function returns the average value of a numeric column.The second and final concept in this section is the
ROUND()function.
TheROUND()function rounds a number to a specified number of decimal places.In this case, we want to calculate the average monthly income for all users.
We use theAVG()function to get the average value and theROUND()function to round the result to 0 decimal places.This is the final result:
SELECT 'Average Income', ROUND(AVG(monthly_income), 0) FROM usersMore information about the
AVG() functionin here.
More information about theROUND() functionin here. -
Calculate the average income by
role.The big reveal
This exercise is really similar to the last one, but in this case we
GROUP BY rolethe results.SELECT role, ROUND(AVG(monthly_income ), 0) as average_income FROM users GROUP BY role
Now you’re no longer querying individuals—you’re reading patterns.
Level 4 — Analytical Thinking
-
Show professions with more than 10 people.
Was I close?
In this exercise, we bring together several concepts. First, we introduce a new SQL topic called
HAVING.The
HAVINGclause works in a similar way toWHERE, but with an important difference:
theWHEREclause cannot be used with aggregate functions, whileHAVINGcan.Our goal is to display all user professions, count how many users belong to each profession, assign an alias to that count, group the results by profession, and finally filter the professions that have more than 10 users.
With that in mind, here is the solution:
SELECT profession, COUNT(*) AS profession_count FROM users GROUP BY profession HAVING profession_count > 10More information about the
HAVINGin here. -
Show the city with the most users.
Answer unlocked
In this section the fun starts, because now we have to user some concepts that are really useful in the field.
The first concept we are going to see is call the
ORDER BYkeyword. This keyword is made to order the elements in a specific way, it could be ascendance (ASC) or descendase (DESC), in this case we are going to user theDESC.Onother important tool is the
LIMIT. This clause limits the results by just the amount of result that we specify, in this case we just need the first result.Taking all this in count, this is the solution:
SELECT city, COUNT(*) AS city_count FROM users GROUP BY city ORDER BY city_count DESC LIMIT 1More information about the
ORDER BY keywordin here.
More information about theLIMITin here. -
Compare the number of minors vs adults.
No cheating
It's time to introduce y'all to a new amazing concept: The
SUM()Function.
TheSUM()function is going to return the sum of a numeric column. If a boolean stament is inside the function, and it returnTRUE, then it's going to sum1.
If it returnsFALSE, then it's going to sum0.In this case it's going to look like this:
SELECT
SUM(TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) > 17) AS adults,
SUM(TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) < 18) AS minors
FROM usersMore information about the
SUM() functionin here. -
Average income by city, ordered from highest to lowest.
Let’s find out
Another great exercise to practice multiple SQL concepts together.
In this exercise, we first calculate the average
monthly_incomefor each city.
Then, we round that value and assign it an alias.
Next, we group the results by city and finally order them bymonthly_incomein descending order.With all this in mind, here is the solution:
SELECT city, ROUND(AVG(monthly_income )) AS income_city FROM users GROUP BY city ORDER BY income_city DESC -
Show the top 5 people with the highest income.
Drumroll…
If you’ve completed the previous exercises in order, this one should be extremely easy.
First, we select the
first_nameandmonthly_incomecolumns.
Then, we order the results bymonthly_incomein descending order and limit the output to the top 5 entries.With that in mind, here is the solution:
SELECT first_name, monthly_income FROM `users` ORDER BY monthly_income DESC LIMIT 5
Here you’re already using GROUP BY, ORDER BY, LIMIT, and HAVING.
Level 5 — Engineer Level
-
Classify users as "Minor", "Adult" or "Senior".
Math gods decide
Now it's time to explore a new conditional call the
CASEexpression. 7The
CASEexpression goes through conditions and returns a value when the first condition is met (like an if-then-else statement). So, once a condition is true, it will stop reading and return the result. If no conditions are true, it returns the value in theELSEclause.If there is no
ELSEpart and no conditions are true, it returnsNULL.With that in mind, let's continue:
SELECT first_name,
CASE
WHEN TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) < 18 THEN 'Minor'
WHEN TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) >= 18 AND TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) < 65 THEN 'Adult'
ELSE 'Senior'
END AS age_group
FROM usersMore information about the
CASE expressionin here. -
Show how many users fall into each of the classifications above.
Truth hurts
Let's group a few concepts to get this exercise done.
First, we need to calculate the total number of users that fall into a specific age range:
'Minor','Adult', or'Senior'.If a user matches a given category, we add
1to that group using theSUM()function, and then display the final totals for each category.SELECT
SUM(TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) < 18) as 'Minor',
SUM(TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) >= 18 AND TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) < 65) as 'Adult',
SUM(TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) >= 65) as 'Senior'
FROM users -
Income ranking by city.
Verify my genius
In this case we have to
SUMthemonthly_incomeof the tableusersandGROUP BY citythe result.
Then weORDER BY incomein a descendance order to create the ranking.This is the result:
SELECT city, SUM(monthly_income) AS income FROM users GROUP BY city ORDER BY income DESC -
Profession with the highest average income.
Hope mode ON
Let's get out hands dirty.
First we have to create an
AVGof themonthly_incomeof all users andGROUP BY professionthe result.
Then weORDER BY incometo get the information in a descendance order.
And finallyLIMITthe result to1to get the highest average income:SELECT profession,
ROUND(AVG(monthly_income),0) AS income
FROM users
GROUP BY profession
ORDER BY income DESC
LIMIT 1 -
Show users whose income is above the overall average.
Final answer?
And the last exercise, I imagine how tired you could be, but no problem, let's do it.
First of all we need to gather the information that we need, in this case we need the
first_nameand themonthly_incomefrom the usersWHEREitsincomeis higher that theAVG monthly_income.With that, let's finish with:
SELECT first_name, monthly_income as income
FROM users
WHERE income > (SELECT AVG(monthly_income) FROM users)