Basic SQL Querying Techniques for Data Analysis
Query languages allow data professionals to retrieve, summarize, filter, and organize data stored in databases. SQL, or Structured Query Language, is the most widely used query language for relational databases.
Although individual database systems may implement different functions and syntax, the core analytical techniques are broadly similar. These include:
- Counting records
- Identifying distinct values
- Calculating summary statistics
- Finding minimum and maximum values
- Filtering rows
- Sorting results
- Matching text patterns
- Grouping and aggregating data
This article introduces these techniques using a hypothetical table named used_car_sales.
Example Dataset
Suppose a relational database contains the following table:
used_car_salesIts columns might include:
| Column | Description |
|---|---|
sale_id | Unique identifier for the sale |
customer_id | Unique identifier for the customer |
dealer_name | Name of the automobile dealer |
purchase_date | Date of purchase |
postal_code | Customer’s postal code |
car_price | Price paid for the vehicle |
The exact syntax shown below may vary slightly across database systems.
1. Counting Rows with COUNT()
The COUNT() aggregate function counts rows or non-null values.
To count every row in a table, use:
SELECT COUNT(*) AS total_sales
FROM used_car_sales;COUNT(*) counts all rows, including rows containing null values in individual columns.
To count the number of non-null values in a particular column:
SELECT COUNT(dealer_name) AS sales_with_dealer
FROM used_car_sales;Unlike COUNT(*), this query does not count rows where dealer_name is NULL.
2. Finding Distinct Values
The DISTINCT keyword removes duplicate combinations from a query result.
To list the unique dealers:
SELECT DISTINCT dealer_name
FROM used_car_sales;It is important to note that DISTINCT is normally a SQL keyword rather than a function. Therefore, it is generally written as DISTINCT dealer_name, not DISTINCT(dealer_name).
Counting distinct values
COUNT() and DISTINCT can be combined to count unique dealers:
SELECT COUNT(DISTINCT dealer_name) AS unique_dealers
FROM used_car_sales;Most SQL databases support this form for one column. Support for counting distinct combinations of multiple columns varies by database system.
3. Calculating Aggregate Statistics
Aggregate functions summarize multiple rows and return one value for each group being analyzed.
Common aggregate functions include:
SUM()for totalsAVG()for arithmetic meansMIN()for minimum valuesMAX()for maximum valuesCOUNT()for record counts- Standard-deviation functions for measuring variability
Calculating a total
To calculate the total value of all recorded vehicle sales:
SELECT SUM(car_price) AS total_sales_value
FROM used_car_sales;Calculating an average
To calculate the average vehicle price:
SELECT AVG(car_price) AS average_car_price
FROM used_car_sales;Most aggregate functions ignore NULL values. Therefore, the average is generally calculated only from rows containing a non-null car_price.
This behavior matters because missing prices could cause the result to describe only part of the dataset.
4. Measuring Variability with Standard Deviation
Standard deviation measures how widely values are dispersed around their mean.
A small standard deviation indicates that values tend to remain close to the average. A large standard deviation indicates greater variation.
SQL implementations commonly provide one or both of the following functions:
STDDEV_POP(car_price)and:
STDDEV_SAMP(car_price)STDDEV_POP() calculates the population standard deviation when the available records represent the complete population of interest.
STDDEV_SAMP() calculates the sample standard deviation when the records are treated as a sample from a larger population.
For example:
SELECT
AVG(car_price) AS average_price,
STDDEV_SAMP(car_price) AS sample_standard_deviation
FROM used_car_sales;Function names differ across database products. Some systems use names such as STDEV(), STDEVP(), STDDEV(), or STDDEV_SAMP().
A large standard deviation may indicate substantial variation, skewness, or extreme values. It does not, by itself, identify the reason for that variation.
5. Finding Minimum and Maximum Values
The MIN() and MAX() functions identify the smallest and largest values in a column.
SELECT
MIN(car_price) AS lowest_price,
MAX(car_price) AS highest_price
FROM used_car_sales;These functions are useful for an initial examination of a dataset. An implausibly low or high result may indicate:
- A legitimate extreme observation
- A data-entry error
- A unit mismatch
- A missing digit
- An improperly converted value
Retrieving the row with the highest value
MAX(car_price) returns only the highest price. It does not automatically return information about the corresponding customer or vehicle.
One way to retrieve every row tied for the maximum price is:
SELECT *
FROM used_car_sales
WHERE car_price = (
SELECT MAX(car_price)
FROM used_car_sales
);This approach handles situations where more than one sale has the same maximum price.
6. Filtering Rows with WHERE
Standard SQL does not provide a general SLICE() function for filtering table rows. A subset—or “slice”—of a table is normally selected with a WHERE clause.
To retrieve sales from one postal code:
SELECT *
FROM used_car_sales
WHERE postal_code = '85001';Text values are usually enclosed in single quotation marks.
Filtering numeric ranges
To find cars priced from \$1,000 through \$2,000:
SELECT *
FROM used_car_sales
WHERE car_price BETWEEN 1000 AND 2000;In SQL, BETWEEN normally includes both endpoints. The preceding query is therefore equivalent to:
SELECT *
FROM used_car_sales
WHERE car_price >= 1000
AND car_price <= 2000;Combining multiple conditions
Use AND when every condition must be satisfied:
SELECT *
FROM used_car_sales
WHERE car_price BETWEEN 1000 AND 2000
AND postal_code = '85001';Use OR when at least one condition must be satisfied:
SELECT *
FROM used_car_sales
WHERE postal_code = '85001'
OR postal_code = '85002';Parentheses make the intended logic explicit when AND and OR are used together:
SELECT *
FROM used_car_sales
WHERE car_price <= 5000
AND (
postal_code = '85001'
OR postal_code = '85002'
);7. Filtering from a List with IN
The IN operator tests whether a value appears in a specified list.
SELECT *
FROM used_car_sales
WHERE postal_code IN ('85001', '85002', '85003');This is more concise than writing several conditions joined by OR.
The opposite condition can be expressed with NOT IN:
SELECT *
FROM used_car_sales
WHERE postal_code NOT IN ('85001', '85002');Care is required when NOT IN is used with a subquery that may return NULL, because SQL’s three-valued logic can produce unexpected results.
8. Sorting Results with ORDER BY
The ORDER BY clause arranges query results by one or more columns.
To sort sales from the earliest to the latest date:
SELECT *
FROM used_car_sales
ORDER BY purchase_date ASC;ASC means ascending order and is usually the default.
To display the most recent sales first:
SELECT *
FROM used_car_sales
ORDER BY purchase_date DESC;ORDER BY is a SQL clause, not a function, so parentheses are not used.
Sorting by multiple columns
Results can be sorted by more than one column:
SELECT *
FROM used_car_sales
ORDER BY dealer_name ASC, car_price DESC;This query sorts rows alphabetically by dealer and then places the most expensive sales first within each dealer.
SQL does not guarantee result order unless an ORDER BY clause is provided.
9. Limiting the Number of Results
After sorting data, analysts often retrieve only the first few rows.
In databases that support LIMIT, the five most expensive sales can be obtained with:
SELECT *
FROM used_car_sales
ORDER BY car_price DESC
LIMIT 5;Other database systems use syntax such as:
SELECT TOP 5 *
FROM used_car_sales
ORDER BY car_price DESC;or:
SELECT *
FROM used_car_sales
ORDER BY car_price DESC
FETCH FIRST 5 ROWS ONLY;The correct form depends on the database system.
10. Matching Text Patterns with LIKE
The LIKE operator filters text using pattern matching.
Two commonly supported wildcard characters are:
%for zero or more characters_for exactly one character
Values beginning with a pattern
To retrieve postal codes beginning with 850:
SELECT *
FROM used_car_sales
WHERE postal_code LIKE '850%';Values ending with a pattern
SELECT *
FROM used_car_sales
WHERE dealer_name LIKE '%Motors';Values containing a pattern
SELECT *
FROM used_car_sales
WHERE dealer_name LIKE '%Auto%';Matching a fixed number of characters
If the postal code must begin with 850 and contain exactly two additional characters:
SELECT *
FROM used_car_sales
WHERE postal_code LIKE '850__';The two underscores each represent one character.
Case sensitivity for LIKE depends on the database, collation, and column configuration. Some systems also provide a case-insensitive operator such as ILIKE.
11. Working with NULL Values
A missing database value is represented by NULL. It cannot reliably be tested using ordinary equality:
WHERE dealer_name = NULLInstead, use IS NULL:
SELECT *
FROM used_car_sales
WHERE dealer_name IS NULL;To retrieve non-null values:
SELECT *
FROM used_car_sales
WHERE dealer_name IS NOT NULL;Understanding null behavior is essential because comparisons involving NULL generally evaluate as unknown rather than true or false.
12. Grouping Data with GROUP BY
The GROUP BY clause divides rows into groups so that aggregate functions can be calculated separately for each group.
To calculate total spending by postal code:
SELECT
postal_code,
SUM(car_price) AS total_spending
FROM used_car_sales
GROUP BY postal_code;The query returns one row for each postal code.
Calculating several statistics by group
SELECT
dealer_name,
COUNT(*) AS number_of_sales,
AVG(car_price) AS average_price,
MIN(car_price) AS lowest_price,
MAX(car_price) AS highest_price
FROM used_car_sales
GROUP BY dealer_name;When GROUP BY is used, each selected column generally must either:
- Appear in the
GROUP BYclause, or - Be processed by an aggregate function
Some database systems permit exceptions, but relying on them can produce ambiguous or nonportable queries.
13. Filtering Groups with HAVING
WHERE filters individual rows before grouping. HAVING filters the aggregated groups after GROUP BY has been applied.
To find dealers with at least three sales:
SELECT
dealer_name,
COUNT(*) AS number_of_sales
FROM used_car_sales
GROUP BY dealer_name
HAVING COUNT(*) >= 3;A query may use both clauses:
SELECT
dealer_name,
COUNT(*) AS number_of_sales,
AVG(car_price) AS average_price
FROM used_car_sales
WHERE purchase_date >= '2026-01-01'
GROUP BY dealer_name
HAVING COUNT(*) >= 3
ORDER BY average_price DESC;This query:
- Filters sales by date.
- Groups the remaining records by dealer.
- Calculates the number of sales and average price.
- Retains dealers with at least three qualifying sales.
- Sorts the groups by average price.
14. Using Aliases for Readable Results
Aliases provide clearer names for output columns and tables.
A column alias can be created with AS:
SELECT
COUNT(*) AS total_sales,
AVG(car_price) AS average_car_price
FROM used_car_sales;Table aliases are especially helpful in joins and longer queries:
SELECT
s.dealer_name,
s.car_price
FROM used_car_sales AS s
WHERE s.car_price > 10000;Although AS is optional for aliases in many SQL dialects, including it can improve readability.
SQL and Other Query Languages
The same general analytical objectives appear outside relational SQL:
- Cassandra uses CQL, which resembles SQL but is designed around Cassandra’s distributed data model.
- Neo4j uses Cypher for querying nodes, relationships, and graph patterns.
- Document databases provide query APIs for filtering and aggregating documents.
- Web APIs accept parameters that determine which resources or records are returned.
The concepts of selecting, filtering, grouping, ordering, and aggregating remain relevant, but the syntax and underlying execution models differ.
CQL and Cypher should therefore not be treated as interchangeable versions of SQL. Their capabilities reflect fundamentally different database structures and access patterns.
Common Mistakes to Avoid
Treating keywords as functions
DISTINCT, ORDER BY, GROUP BY, WHERE, and LIKE are SQL language elements, not functions called with parentheses.
Assuming rows have an inherent order
Database rows are not guaranteed to appear in insertion order. Use ORDER BY whenever the order matters.
Ignoring missing values
Most aggregate functions ignore NULL. Always investigate how missing data affects a calculation.
Selecting unrelated columns in grouped queries
A selected column that is neither grouped nor aggregated can make the result ambiguous.
Confusing WHERE and HAVING
Use WHERE for row-level conditions and HAVING for conditions applied to aggregated groups.
Assuming every database uses identical syntax
Standard SQL provides a common foundation, but functions, date syntax, regular expressions, case sensitivity, and result-limiting syntax vary among database systems.
Key Takeaways
COUNT(*)counts table rows, whileCOUNT(column)excludes null values in that column.DISTINCTremoves duplicates and can be combined withCOUNT()to count unique values.SUM(),AVG(),MIN(),MAX(), and standard-deviation functions summarize numerical data.WHEREfilters individual records according to specified conditions.BETWEEN,IN,AND, andORsupport more detailed filtering.ORDER BYsorts query results in ascending or descending order.LIKEperforms basic text-pattern matching with wildcard characters.GROUP BYcalculates summaries for categories or groups.HAVINGfilters groups after aggregation.- SQL syntax and available functions vary among database products.
Conclusion
Basic SQL queries allow analysts to move from raw database records to useful summaries and targeted subsets. Counting and aggregation provide an overview of a dataset, while filtering and sorting expose relevant records and patterns. Grouping extends these operations by generating separate summaries for meaningful categories.
These techniques form the foundation for more advanced work involving joins, subqueries, common table expressions, window functions, and analytical data pipelines.
One-sentence summary: Basic SQL querying techniques use selection, filtering, sorting, pattern matching, grouping, and aggregation to transform stored database records into useful analytical results.
