Skip to content

SQL basics

If you have used SUMIF, FILTER or a pivot table, you already understand what SQL does — it just says it differently. This page covers the parts you need for SQL Cell. It is not a full SQL course.

Every example assumes a file called sales.csv attached in the Files panel, with columns date, region, product, units and total.

SELECT region, total
FROM 'sales.csv'
WHERE units > 10
ORDER BY total DESC
LIMIT 20;

Read it as: take these columns, from this file, keeping only rows that match, sorted this way, stopping after 20 rows.

The clauses always appear in that order. All of them except SELECT and FROM are optional.

SELECT * FROM 'sales.csv'; -- every column
SELECT region, total FROM 'sales.csv'; -- just these two
SELECT total * 0.2 AS vat FROM 'sales.csv';

AS names the result column, which is what ends up as the header in your sheet. Name your calculated columns — it makes the inserted block readable.

SELECT * FROM 'sales.csv'
WHERE region = 'EMEA'
AND units >= 10
AND date >= '2026-01-01';

Text goes in single quotes; numbers do not. Useful comparisons:

= <> equal, not equal
> < >= <= ordering
IN ('EMEA', 'APAC') any of these
BETWEEN 10 AND 50 inclusive range
LIKE '%widget%' contains (% is the wildcard)
IS NULL / IS NOT NULL empty or not

This is the pivot table. GROUP BY says which column to collapse on, and the aggregate functions say what to do with the rows in each group.

SELECT region,
count(*) AS orders,
sum(total) AS revenue,
avg(total) AS average_order,
max(total) AS biggest
FROM 'sales.csv'
GROUP BY region
ORDER BY revenue DESC;

The rule that catches everyone: every column in SELECT must either be in GROUP BY or be wrapped in an aggregate function. product in the query above would be an error — the database has no way to pick which product to show for a whole region.

To filter on an aggregate, use HAVING rather than WHERE:

SELECT region, sum(total) AS revenue
FROM 'sales.csv'
GROUP BY region
HAVING sum(total) > 10000;

WHERE filters rows before grouping; HAVING filters groups after.

JOIN is VLOOKUP, without the fragility.

SELECT c.name, sum(o.total) AS lifetime_value
FROM 'orders.csv' o
JOIN 'customers.csv' c ON o.customer_id = c.id
GROUP BY c.name
ORDER BY lifetime_value DESC;

o and c are aliases — short names for the files so you can write o.total instead of repeating the filename. ON says which columns must match.

A plain JOIN keeps only rows that matched in both files. LEFT JOIN keeps every row from the first file whether or not it matched, filling the rest with nulls — use it when a missing match is itself the thing you are looking for.

SELECT
round(avg(total), 2) AS avg_total,
upper(region) AS region,
coalesce(discount, 0) AS discount, -- null becomes 0
date_trunc('month', date) AS month, -- group by month
strftime(date, '%Y-%m') AS label
FROM 'sales.csv'
GROUP BY region, month, label;
  • Column names are case-sensitive. If your header is Total, then total will not find it. Wrap awkward names in double quotes: "order id".
  • Single quotes are for values, double quotes are for names. 'EMEA' is text; "order id" is a column.
  • A trailing comma before FROM is an error. Easy to leave behind when you delete a column.
  • Numbers that arrived as text will not add up. Check the Columns tab in the preview — if a numeric column says VARCHAR, cast it: CAST(total AS DOUBLE).

SQL Cell runs DuckDB, so anything in the DuckDB SQL documentation works here — including window functions, PIVOT, and reading several files at once with a glob such as 'data/*.csv'.