Published on : Jul 24, 2026

SQL for Data Analysts: Essential Skills & Query Guide

The SQL Skills That Actually Land You a Data Analyst Job

8 Minutes Read
Rutvik Acharya, Principal Data Scientist at Atlassian

Rutvik Acharya

Principal Data Scientist Atlassian

SQL for Data Analysts thumbnail with laptop, SQL code, database icons, and analytics dashboard.

SQL Skills Every Data Analyst Needs to Know

The SQL Skills That Actually Land You a Data Analyst Job

If you've spent any time researching how to become a data analyst, you probably already know the answer to what to learn first. It's SQL. A 2026 analysis by 365 Data Science looked at over 1,000 data analyst job postings and found SQL showing up in roughly 53% of them, more than Excel, more than Python, more than anything else on the list.

But "learn SQL" isn't really one skill, it's a whole category of them. Plenty of analysts can write a basic SELECT statement and still freeze up the moment they're asked to join four tables, clean up a column full of NULLs, or explain why a query is taking forever to run. This guide walks through what actually matters, roughly in the order you'll need it, with examples you can run yourself, starting with why SQL is worth the effort in the first place.

Why SQL matters for data analysts

Every data analyst job, no matter the industry, comes down to the same loop. Get the data, shape the data, explain the data. SQL is how most companies store and serve that data, which is why it shows up more consistently in job postings than any visualization or programming tool.

A few reasons SQL stays essential even as AI tools get better at writing queries for you.

  • It's the interface to the data itself. Dashboards and BI tools sit on top of SQL, and someone still has to define the underlying queries.

  • It's tested in interviews. Live SQL rounds, whether whiteboard or shared screen, are standard at most companies hiring analysts.

  • It scales with you. The same core syntax works whether you're pulling 100 rows in MySQL or aggregating a billion rows in BigQuery.

  • It sharpens how you think. Writing a good SQL query forces you to be precise about the question you're actually asking, before you even get to the answer.

The rest of this guide covers the specific skills worth mastering, roughly in the order they tend to come up on the job.

Joins

Joins combine rows from two or more tables based on a related column. This is probably the single most tested SQL skill in analyst interviews, because almost no real business question can be answered from one table alone.

Join Type

What It Returns

Common Use Case

INNER JOIN

Only rows with matches in both tables

Orders that have a matching customer record

LEFT JOIN

All rows from the left table, matched rows from the right (NULL if no match)

All customers, even ones with zero orders

RIGHT JOIN

All rows from the right table, matched rows from the left

Rarely used, usually rewritten as a LEFT JOIN

FULL OUTER JOIN

All rows from both tables, matched where possible

Reconciling two lists that should mostly overlap

SELF JOIN

A table joined to itself

Comparing employees to their managers in the same table

CROSS JOIN

Every row from table A paired with every row from table B

Generating all date x product combinations

Here's an example using customers and their orders.

SELECT
    c.customer_id,
    c.customer_name,
    o.order_id,
    o.order_amount
FROM customers c
LEFT JOIN orders o
    ON c.customer_id = o.customer_id
ORDER BY c.customer_id;

Using LEFT JOIN here instead of INNER JOIN is deliberate. It keeps customers who haven't placed any orders, which matters if you're calculating something like the percentage of customers who never ordered.

A habit worth building. When you're asked to join tables in an interview, ask out loud what should happen to unmatched rows. That one question shows you understand joins conceptually, not just the syntax.

Aggregations

Aggregations collapse many rows into a summary, usually grouped by some category.

Function

Purpose

COUNT()

Number of rows (or non NULL values in a column)

SUM()

Total of a numeric column

AVG()

Average of a numeric column

MIN() / MAX()

Smallest or largest value

GROUP BY

Defines the categories to aggregate within

HAVING

Filters groups after aggregation, unlike WHERE, which filters rows before

Here's an example calculating monthly revenue by region.

SELECT
    region,
    DATE_TRUNC('month', order_date) AS order_month,
    SUM(order_amount) AS total_revenue,
    COUNT(order_id) AS total_orders
FROM orders
GROUP BY region, DATE_TRUNC('month', order_date)
HAVING SUM(order_amount) > 100000
ORDER BY order_month, total_revenue DESC;

A common mistake is putting an aggregate condition in WHERE instead of HAVING. WHERE runs before grouping, so it can't reference an aggregated value like SUM(order_amount).

Subqueries

A subquery is a query nested inside another query. They come in handy when you need an intermediate result before your main logic can run.

A scalar subquery returns a single value.

SELECT
    order_id,
    order_amount
FROM orders
WHERE order_amount > (SELECT AVG(order_amount) FROM orders);

A correlated subquery references the outer query and runs once per row.

SELECT
    c.customer_id,
    c.customer_name
FROM customers c
WHERE EXISTS (
    SELECT 1
    FROM orders o
    WHERE o.customer_id = c.customer_id
      AND o.order_amount > 5000
);

Subqueries and CTEs (the WITH clause) often do the same job, but CTEs are almost always more readable once the logic gets past a single line. Most analysts default to CTEs today and save inline subqueries for short, simple filters.

Window functions

Window functions perform calculations across a set of rows related to the current row, without collapsing the result into a single row per group the way GROUP BY does. This is the skill that tends to separate "knows SQL" from "knows SQL well."

Function

What It Does

ROW_NUMBER()

Assigns a unique sequential number to each row

RANK()

Assigns a rank, with ties sharing a rank and a gap after

DENSE_RANK()

Same as RANK(), but no gap after ties

LAG() / LEAD()

Looks at the previous or next row's value

SUM() OVER()

Running or grouped total without collapsing rows

Here's an example finding the top order per customer.

SELECT *
FROM (
    SELECT
        customer_id,
        order_id,
        order_amount,
        ROW_NUMBER() OVER (
            PARTITION BY customer_id
            ORDER BY order_amount DESC
        ) AS rn
    FROM orders
) ranked
WHERE rn = 1;

And here's one for a running monthly total.

SELECT
    order_month,
    monthly_revenue,
    SUM(monthly_revenue) OVER (ORDER BY order_month) AS running_total
FROM monthly_summary;

Window functions are usually the topic analysts feel least confident about, and also the one that comes up most in mid to senior interviews. It's worth deliberately practicing ten or fifteen problems instead of just reading about them.

Handling NULLs

NULL means unknown or absent, not zero and not an empty string. Mishandling NULLs is one of the most common ways a dashboard ends up quietly wrong.

A few things to keep in mind.

  • Use IS NULL or IS NOT NULL. Never = NULL, since that always evaluates to unknown rather than true or false.

  • COUNT(column) skips NULLs, but COUNT(*) counts every row regardless.

  • Any arithmetic or string operation involving NULL returns NULL. 5 + NULL is NULL, not 5.

  • A NULL in a join key means that row won't match in an INNER JOIN, even if conceptually it should.

COALESCE is probably the single most useful NULL handling function.

SELECT
    customer_id,
    COALESCE(phone_number, 'Not Provided') AS phone_number
FROM customers;

NULLIF turns a specific value into NULL, which is handy for avoiding a divide by zero error.

SELECT
    order_id,
    total_amount / NULLIF(quantity, 0) AS price_per_unit
FROM orders;

Without NULLIF, a quantity of 0 would throw a division error and break the entire query.

CASE WHEN statements

CASE WHEN lets you build conditional, business readable logic directly into a query, turning raw values into categories a stakeholder can actually use.

SELECT
    order_id,
    order_amount,
    CASE
        WHEN order_amount >= 10000 THEN 'High Value'
        WHEN order_amount >= 2000  THEN 'Mid Value'
        ELSE 'Low Value'
    END AS order_segment
FROM orders;

order_id

order_amount

order_segment

101

12,500

High Value

102

3,200

Mid Value

103

850

Low Value

CASE WHEN also pairs well with aggregation to "pivot" data when there's no native PIVOT function available.

SELECT
    region,
    SUM(CASE WHEN order_status = 'Completed' THEN order_amount ELSE 0 END) AS completed_revenue,
    SUM(CASE WHEN order_status = 'Refunded' THEN order_amount ELSE 0 END) AS refunded_revenue
FROM orders
GROUP BY region;

Indexing basics

You don't need to be a database administrator, but understanding indexes helps you explain, and sometimes fix, why a query is slow, which becomes a genuinely useful skill once you're working with large tables.

What an index actually does. It's a separate data structure, usually a B tree, that lets the database find rows without scanning the entire table, similar to an index at the back of a book.

Concept

What It Means

Full table scan

Database reads every row, which is slow on large tables

Index scan

Database uses the index to jump straight to relevant rows

Primary key

Automatically indexed, uniquely identifies each row

Composite index

An index on multiple columns together, useful when queries filter on the same combination repeatedly

EXPLAIN / EXPLAIN ANALYZE

Shows the query execution plan, the first thing to check when a query is slow

Checking a query's execution plan in PostgreSQL or MySQL.

EXPLAIN ANALYZE
SELECT *
FROM orders
WHERE customer_id = 4521;

If customer_id isn't indexed, this query scans the entire orders table. As an analyst, you typically won't be creating indexes on production systems, but knowing how to read an execution plan helps you figure out why a query is timing out and have a more informed conversation with a data engineer about it.

Practising on MySQL, PostgreSQL and BigQuery

Core SQL is portable, but each platform has its own dialect quirks. Knowing the differences saves real debugging time when you switch between tools on the job.

Feature

MySQL

PostgreSQL

BigQuery

Row limiting

LIMIT 10

LIMIT 10

LIMIT 10

String concatenation

CONCAT(a, b)

a || b or CONCAT(a,b)

CONCAT(a, b)

Current date

CURDATE()

CURRENT_DATE

CURRENT_DATE()

Auto increment ID

AUTO_INCREMENT

SERIAL / IDENTITY

Not applicable, no native auto increment

Window functions

Supported (8.0+)

Fully supported

Fully supported

Regex matching

REGEXP

~

REGEXP_CONTAINS()

Pricing model

Free / self hosted

Free / self hosted

Pay per query, based on data scanned

Best for practising

General SQL fundamentals

Advanced SQL (CTEs, window functions, JSON)

Large scale analytics, cloud native workflows

A few ways to actually practice.

  • Start with MySQL or PostgreSQL, either locally or through a free sandbox. The fundamentals transfer everywhere.

  • Move to BigQuery once you're comfortable, since many companies run analytics on cloud warehouses like BigQuery, Snowflake or Redshift rather than traditional databases.

  • Practice on data that's actually messy, with duplicate rows, missing values and inconsistent formatting, not just clean textbook tables. It's a lot closer to what the job actually looks like.

  • Rebuild queries from scratch instead of copying solutions. The goal is recognizing patterns, not memorizing answers.

Putting it all together

Here's a query that pulls together joins, CASE WHEN, NULL handling and a window function. It's the kind of query you might actually end up writing on the job.

Say a stakeholder wants to know each customer's total spend, when they last ordered, and how they rank against other customers in their region.

SELECT
    c.customer_id,
    c.customer_name,
    c.region,
    COALESCE(SUM(o.order_amount), 0) AS total_spend,
    MAX(o.order_date) AS last_order_date,
    CASE
        WHEN COALESCE(SUM(o.order_amount), 0) >= 50000 THEN 'VIP'
        WHEN COALESCE(SUM(o.order_amount), 0) >= 10000 THEN 'Regular'
        ELSE 'New / Low Activity'
    END AS customer_tier,
    RANK() OVER (
        PARTITION BY c.region
        ORDER BY COALESCE(SUM(o.order_amount), 0) DESC
    ) AS regional_rank
FROM customers c
LEFT JOIN orders o
    ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.customer_name, c.region
ORDER BY c.region, regional_rank;

This single query answers a segmentation question, a recency question and a ranking question at once. That's exactly the kind of multi part logic that shows up in both interviews and real dashboards.

Common mistakes

  • Using INNER JOIN by default. It silently drops rows and skews totals when a LEFT JOIN is what the question actually needs.

  • Filtering aggregates in WHERE instead of HAVING.

  • Forgetting that NULLs break equals comparisons. Always use IS NULL instead.

  • Not partitioning window functions correctly. A missing PARTITION BY quietly changes the entire calculation.

  • Ignoring query performance until it becomes a production problem. Get comfortable reading EXPLAIN output early.

  • Memorizing syntax instead of understanding the logic behind it. Dialects change over time, but the reasoning behind joins, aggregation and window functions mostly doesn't.

Where to go from here

You can learn the fundamentals from documentation and practice sites on your own. Getting from "I can write a query" to "I can confidently handle the SQL round in an interview, and the SQL work in the job" usually takes structured practice on real, messy datasets with actual feedback on what you got wrong. If that's the stage you're at, it's worth looking into a structured data analytics program that builds these exact skills through real project work instead of isolated exercises.

FAQ

FREQUENTLY ASKED QUESTIONS

Basic syntax gets you started, but most real analyst work, things like segmentation, cohort analysis, ranking and running totals, depends on joins, aggregations and window functions. Most companies test for this in interviews too.
Start with MySQL or PostgreSQL to build the fundamentals. Both are free, well documented and widely used for learning. Once you're comfortable, add BigQuery or another cloud warehouse, since most analytics teams run on one these days.
Getting comfortable enough for interviews, meaning confident with joins, aggregations, subqueries and basic window functions, usually takes somewhere between six and ten weeks of consistent, hands on practice. Watching videos alone won't get you there.
The core, things like SELECT, JOIN, GROUP BY and window functions, is nearly identical. The differences show up mostly in function names for dates and strings, and in BigQuery's pay per query pricing model, which changes how you think about scanning large tables.