SELECT, Filtering, TOP & OFFSET-FETCH
The SELECT statement is the workhorse of T-SQL. Beyond the ANSI core, T-SQL adds TOP n [PERCENT] [WITH TIES] and the ANSI-standard OFFSET ... FETCH for pagination.
Anatomy of a SELECT
SELECT [DISTINCT | TOP n [PERCENT] [WITH TIES]] columns
FROM table_source
WHERE row_filter
GROUP BY columns
HAVING group_filter
ORDER BY columns [OFFSET n ROWS FETCH NEXT m ROWS ONLY];
Logical processing order: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → OFFSET/FETCH (or TOP).
TOP — Microsoft's Original Limiter
-- Top 5 highest-paid employees
SELECT TOP 5 employee_id, first_name, last_name, salary
FROM employees
ORDER BY salary DESC;
-- TOP without ORDER BY is non-deterministic — DON'T do this in production
SELECT TOP 5 * FROM employees;
-- WITH TIES includes every row that ties with the last one (requires ORDER BY)
SELECT TOP 3 WITH TIES first_name, last_name, salary
FROM employees
ORDER BY salary DESC;
TOP n PERCENT returns the top n% (rounded up):
-- Top 10% of customers by total order amount
SELECT TOP 10 PERCENT customer_id, SUM(total) AS spend
FROM orders
GROUP BY customer_id
ORDER BY spend DESC;
OFFSET-FETCH — ANSI Pagination
OFFSET ... FETCH is the ANSI-standard pagination clause and the right choice for new code. It requires ORDER BY.
DECLARE @page_size INT = 25, @page INT = 3;
SELECT employee_id, first_name, last_name, hire_date
FROM employees
ORDER BY hire_date DESC, employee_id
OFFSET (@page - 1) * @page_size ROWS
FETCH NEXT @page_size ROWS ONLY;
| Feature | TOP | OFFSET-FETCH |
|---|---|---|
| Standardized | No (T-SQL only) | Yes (ANSI SQL:2008) |
| Skip rows | No | Yes (OFFSET) |
| Tie-handling | WITH TIES |
None |
Requires ORDER BY |
Recommended | Required |
WHERE — Row Filtering
-- Comparison and logical operators
SELECT *
FROM employees
WHERE salary >= 5000
AND department_id IN (50, 60, 80)
AND (job_id LIKE 'IT_%' OR commission_pct IS NOT NULL);
-- BETWEEN is inclusive on both ends
SELECT *
FROM orders
WHERE order_date BETWEEN '2024-01-01' AND '2024-12-31';
LIKE Wildcards
| Pattern | Matches |
|---|---|
% |
Zero or more characters |
_ |
Exactly one character |
[abc] |
Any one of a, b, c |
[a-f] |
Range a–f |
[^a-f] |
Anything not a–f |
ESCAPE '\' |
Define an escape character for literal % or _ |
SELECT first_name
FROM employees
WHERE first_name LIKE 'J%n' -- Jan, John, Joaquín
AND email LIKE '%@%.com'
AND phone_number LIKE '[5-7]%'; -- starts 5, 6, or 7
NULL Handling
NULL is "unknown" — comparisons with = or <> always return UNKNOWN. Use IS NULL and IS NOT NULL.
-- WRONG: returns no rows because salary <> NULL is UNKNOWN, not TRUE
SELECT * FROM employees WHERE commission_pct <> NULL;
-- RIGHT
SELECT * FROM employees WHERE commission_pct IS NOT NULL;
ISNULL vs COALESCE
-- ISNULL: T-SQL specific, takes exactly two arguments, returns the type of the first
SELECT ISNULL(commission_pct, 0) AS pct FROM employees;
-- COALESCE: ANSI, n arguments, returns the highest-precedence type
SELECT COALESCE(phone_number, mobile_number, 'no phone') AS contact
FROM employees;
ISNULL |
COALESCE |
|
|---|---|---|
| Standard | T-SQL | ANSI SQL |
| Arguments | 2 | n |
| Result type | First argument's type | Highest-precedence type |
| NULL-on-NULL behaviour | Returns NULL | Returns NULL |
| Performance | Slightly faster | Negligible difference |
ISNULL(col, 'fallback') returns the type/length of col. If col is VARCHAR(3) and the fallback is 'fallback', it gets truncated to 'fal'. COALESCE follows type-precedence rules and is generally safer for mixed types.
HAVING — Filtering Groups
HAVING filters after aggregation; WHERE filters before.
-- Departments with more than 5 employees and avg salary above 8000
SELECT department_id, COUNT(*) AS staff, AVG(salary) AS avg_sal
FROM employees
WHERE hire_date < '2020-01-01' -- per-row filter
GROUP BY department_id
HAVING COUNT(*) > 5 -- group filter
AND AVG(salary) > 8000
ORDER BY avg_sal DESC;
Best Practices
- Always pair
TOPandOFFSET-FETCHwithORDER BY— otherwise the rows you get back are unspecified. - Use
OFFSET-FETCHfor new code; reserveTOPfor "show me top N" use cases. - Never compare to
NULLwith=— useIS NULL. - Watch
ISNULLlength truncation; preferCOALESCEwhen the fallback is wider than the column.
Summary
TOP n [WITH TIES] [PERCENT]is T-SQL's row limiter; useOFFSET ... FETCHfor paginated results.WHEREfilters rows;HAVINGfilters aggregated groups.LIKEsupports%,_, character classes, andESCAPE.NULLis unknown — useIS NULL/IS NOT NULLand pickISNULL(fast, 2 args) orCOALESCE(ANSI, n args) accordingly.