Essential SQL Queries for Data Analysts

Essential SQL Queries for Data Analysts
Image by Author | Canva

Data analysts commonly use SQL when creating various reports and calculating business metrics. In this article, we’ll show you several queries that will help you in everyday data analysis tasks. The examples are SQL query interview questions from StrataScratch.

Here’s an overview of examples and SQL concepts we’ll use.

Essential SQL Queries for Data Analysts

Time Binning With DATE_TRUNC()

Timestamps is a common data type you’ll work with. Unfortunately, time periods are not inherent in timestamps. This makes it difficult to analyze trends without first manipulating the data. However, with DATE_TRUNC(), we can perform time binning, i.e., grouping timestamps into a specific time period.

Example: We can create a weekly orders report with these requirements. In the code below, we categorize the data from the week column into weeks using DATE_TRUNC(). In addition, we also use EXTRACT() to find only orders from the first quarter of 2023.

Then, the result of the CTE is called in SELECT, and we use SUM() to calculate the total quantity.

WITH filtered_orders AS (
    SELECT DATE_TRUNC('week', week::DATE) AS week_start,
           quantity 
    FROM orders_analysis
    WHERE EXTRACT(QUARTER FROM week::DATE) = 1 AND EXTRACT(YEAR FROM week::DATE) = 2023
)

SELECT week_start AS week, 
       SUM(quantity) AS total_quantity
FROM filtered_orders
GROUP BY week_start
ORDER BY week_start;

Here’s the output snapshot. You can see that the dates start from 2023-01-02, which was the first Monday (i.e., the start of the week) of 2023. As the rows represent the weeks, the difference between the dates is 7 days.

week total_quantity
2023-01-02 303
2023-01-09 312
2023-01-16 321
… …
2023-03-27 411

Where You Can Use It:

  • Weekly or monthly growth trends
  • Quarterly revenue changes
  • Daily churn and usage drops

Calculating Rates (Conversion, Completion, Activation, Retention, and More)

Calculating rates is helpful when quantifying user behavior over time. They help capture patterns like how frequently users return, how quickly they convert, or how likely they are to churn.

Example: The code below calculates the first-day retention rate of a group of video game players.

The CTE identifies the first login.

The SELECT statement then counts the number of players who logged in the day after the first day (WHERE login_date – first_day = 1) and divides the count of those players by the total count of players to get the rate.

WITH first_days AS
  (SELECT player_id,
          MIN(login_date) AS first_day
   FROM players_logins
   GROUP BY player_id)
   
SELECT CAST(COUNT(DISTINCT first_days.player_id) AS FLOAT)/
  (SELECT COUNT(DISTINCT player_id) AS total_users
   FROM players_logins) AS retention_rate
FROM first_days
JOIN players_logins ON first_days.player_id = players_logins.player_id
WHERE login_date - first_day = 1;

Here’s the output.

retention_rate
0.5

Where You Can Use It:

  • Growth dashboards with D1, D3, D7, and D30 retention rates (with just slightly changing the logic, e.g., login_date – first_day = 7)
  • A/B tests on onboarding flows or welcome screens
  • Cohort reports
  • Funnel metrics
  • Marketing performance (e.g., click-through rates, cost-per-click)
  • Sales analytics
  • Customer support (e.g., resolution rate, first-contact resolution rate)
  • User engagement
  • Product launch

Categorical Breakdowns With Aggregate Functions

As a data analyst, you’ll very often have to perform categorical breakdowns to help you understand the distribution of data.

Example: The code calculates the average number of bathrooms and bedrooms for each city’s property types, utilizing the AVG() aggregate function.

In addition, there are WHERE and HAVING to filter data according to requirements before and after aggregation.

SELECT city,
       property_type,
       AVG(bathrooms) AS n_bathrooms_avg,
       AVG(bedrooms) AS n_bedrooms_avg
FROM airbnb_search_details
GROUP BY city, property_type;

Here’s the partial output.

city property_type n_bathrooms_avg n_bedrooms_avg
Chicago House 2 3
NYC Condominium 1 1.3333333333333333
Boston Condominium 2 2
… … … …
Boston Apartment 1 1

Where You Can Use It:

  • Product
  • Users
  • Content
  • Orders
  • Headcounts breakdowns

Identifying Top and Bottom Performers With Window Functions

Data analysts are commonly asked to deliver a report of the top-n or bottom-n of a certain category, i.e., best and worst performers/cases.. In SQL, this is typically done by using the ranking window functions such as RANK() or DENSE_RANK().

Example: Here we write a query that identifies the bottom two companies by mobile usage.

We use COUNT(*) to count the number of mobile events, then DENSE_RANK() to rank them by event count. The ranking is in ascending order, meaning the lowest number of events will be ranked as 1. The DENSE_RANK() window function will show duplicate ranks without skipping between the ranks. We need that because skipping ranks can potentially mean we miss some users. If you wanted to skip ranks (in other scenarios), then you’d use RANK().

All this is written as a CTE. In SELECT, we leave only the users where the rank is 2 or lower (i.e., 1), which will show the bottom two users.

WITH ranked_companies AS
  (SELECT customer_id,
          COUNT(*) AS events,
          DENSE_RANK() OVER (ORDER BY COUNT(*)) AS rnk
   FROM fact_events
   WHERE client_id = 'mobile'
   GROUP BY customer_id)
   
SELECT customer_id,
       events
FROM ranked_companies
WHERE rnk <= 2
ORDER BY events ASC;

The output shows three users.

customer_id events
Electric Gravity 4
eShop 4
Sendit 7

Where You Can Use It:

  • Top-selling/worst-selling products
  • Highest/lowest paid employees
  • Students with the highest/lowest grades
  • Longest/shortest retained customers
  • Highest/lowest costs

Handling NULLs With COALESCE()

The data you’ll work with is messy, often with some data missing. You need to handle those gaps so your metrics aren’t messed up with NULL values, in which case you have to use COALESCE().

Example: In this example, we’ll calculate the total listening time and the count of unique songs the users have listened to.

In the query, we sum and round the listening duration, then divide by 60 to convert it to seconds. In addition, we use COALESCE() to replace all NULL values with 0; this way, we prevent incorrect results caused by missing data.

We also use COUNT() and DISTINCT to calculate the total number of unique songs each user has listened to.

SELECT user_id, 
       ROUND(SUM(COALESCE(listen_duration, 0)) / 60.0) AS total_listen_duration, 
       COUNT(DISTINCT song_id) AS unique_song_count
FROM listening_habits
GROUP BY user_id;

Here’s the partial output.

user_id total_listen_duration unique_song_count
101 8 2
102 5 2
103 6 1
… … …
109 0 1

Where You Can Use It:

  • Contact info cleanup
  • Missing transaction amounts
  • For missing dates
  • For missing categorical labels

Calculating Running (Cumulative) Totals

Cumulative or running totals is a metric that data analysts calculate often. In SQL, it’s performed using the SUM() window function.

Example: In this example, we will calculate the cumulative total not as a final output, but as a step to it. The goal is to find the minimum number of platforms necessary to accommodate the entire scheduled traffic.

In the CTE, we write two SELECT statements with UNION ALL. The first one finds the train IDs and their arrival times, which are marked as 1. The second SELECT does the same for departure times, which are marked as -1.

The next CTE calculates the cumulative sum of mark values, which gives us the total number of trains present at the station. This is done by using the SUM() window function on the time and mark values descendingly, which cumulates the number of trains from the earliest to the latest time in a day.

In the final SELECT, we apply the MAX() function to the cumulative value to output the minimum number of platforms needed, as all trains that are present at the same time at the train station require, obviously, a platform each.

WITH timetable AS(
    (SELECT train_id,
            arrival_time AS TIME,
            1 AS mark
     FROM train_arrivals
     
     UNION ALL 
     
     SELECT train_id,
            departure_time AS TIME,
            -1 AS mark
     FROM train_departures
     )
    ORDER BY time, mark DESC),
    
cumsum AS
  (SELECT *,
          SUM(mark) OVER (ORDER BY time, mark DESC) AS trains_at_same_time
   FROM timetable
   )
   
SELECT MAX(trains_at_same_time) AS min_platforms
FROM cumsum;

Here’s the partial output.

train_id time mark trains_at_same_time
1 08:00 1 1
2 08:05 1 3
3 08:05 1 3
… … … …
14 20:15 -1 0

Where You Can Use It:

  • Daily or monthly cumulative sales
  • Cumulative number of signups or activations
  • Cumulative costs
  • Cumulative pageviews
  • Cumulative minutes watched/listened

Comparing Current vs Previous Periods With LAG()

Commonly, you’ll need to compare the values from the current and previous periods, for example, when showing monthly revenue changes. The LAG() window function will help you in this task, as it allows you to access the previous row’s value from the current row.

Example: We’ll find returning active users. They are defined as users who made a second purchase within seven days of any previous purchase. We’ll utilize LAG() in this task.

In the CTE, we list the user IDs and the order creation date. Then, we use LAG() to fetch the previous row’s value. We do that for each user separately; that’s why the data is partitioned by the user ID. Since we ordered data by creation date, the previous row equals the previous order.

In SELECT, we select the user IDs and apply filters required by the interview question.

WITH ordered_tx AS (
  SELECT user_id,
         created_at::DATE AS tx_date,
         LAG(created_at::DATE) OVER (PARTITION BY user_id ORDER BY created_at) AS prev_tx_date
  FROM amazon_transactions
)

SELECT DISTINCT user_id
FROM ordered_tx
WHERE prev_tx_date IS NOT NULL AND tx_date - prev_tx_date <= 7;

Here’s the partial output.

user_id
100
103
105
…
150

Where You Can Use It:

  • Daily/monthly/quarterly/annual revenue change
  • Daily user retention
  • Monthly user churn
  • Daily/monthly/quarterly/annual sales change

Labelling Rows Using CASE WHEN

Row labeling is a common data analyst task, where you need to label rows based on business rules. The CASE WHEN statement is used for that. This is a common data analyst task where you need to label rows based on business rules.

Example: We’ll label employees with the highest and the lowest salaries.

To find the highest and the lowest salaries, we use the RANK() window function. We rank employees by salary from the lowest to the highest; those with the lowest salaries will be ranked as 1. To assign rank 1 to employees with the highest salaries, we rank them by salary in descending order.

In the SELECT statement, we select all the required columns. Then, we use CASE WHEN to label the employees. We assign the label ‘Highest Salary’ where the value in the column highest_sal (from the CTE) is 1; otherwise, the label is ‘Lowest Salary’.

In addition, we exclude all the ranks from lowest_sal and highest_sal, except rank 1. That way, we get no other employees; only those with the highest and the lowest salaries.

WITH cte AS
  (SELECT *,
          RANK() OVER(ORDER BY salary) AS lowest_sal,
          RANK() OVER(ORDER BY salary DESC) AS highest_sal
   FROM worker)
   
SELECT worker_id,
       salary,
       department,
       CASE
           WHEN highest_sal = 1 THEN 'Highest Salary'
           ELSE 'Lowest Salary'
       END AS salary_type
FROM cte
WHERE lowest_sal = 1 OR highest_sal = 1;

Here’s the output.

worker_id salary department salary_type
10 65000 HR Lowest Salary
4 500000 Admin Highest Salary
5 500000 Admin Highest Salary

Where You Can Use It:

  • User segmentation
  • Risk scoring
  • Support SLAs
  • Sales pipelines
  • Content platforms
  • Education data

Conclusion

These SQL query examples were selected to demonstrate the business use, not so much a particular SQL concept. Of course, in some cases, the business case is tightly linked to using a particular SQL function or concept. The idea was to give you something useful; with a little bit of tweaking, you can easily adapt those queries to your particular data analysis needs.

Leave a Reply

Your email address will not be published. Required fields are marked *