SQL aliases are a small syntax feature with enormous impact on query readability. Learn how to name columns and tables effectively, understand where aliases do and don't work, and build the habits that make your queries maintainable by anyone on your team.

You've written a query that works. It returns the right numbers. But when you come back to it three days later — or hand it to a colleague — nobody can tell what t1.col3 means, or why a column in the output is labeled AVG(DATEDIFF(day, o.created_at, o.shipped_at)). The query is technically correct and practically unreadable.
This is where SQL aliases come in. An alias is simply a temporary name you assign to a column or a table reference within a query. It doesn't change the database at all — it only affects how things are labeled inside that one query. But used well, aliases are the difference between a query that anyone can read and maintain, and one that only the original author can decode (and even then, only right after writing it).
By the end of this lesson, you'll be writing clean, professional queries where every column has a meaningful name, every table reference is concise and clear, and multi-table queries are easy to navigate. You'll also understand the rules that trip people up — like when you can and can't reference an alias — so you won't be blindsided by errors in production.
What you'll learn:
You should be comfortable with basic SELECT queries before diving in. If you're newer to SQL, start with SQL Basics: Master SELECT, FROM, WHERE Clauses and Build Your First Queries and then come back here. Familiarity with JOINs will help for the later sections, but isn't strictly required.
Think of an alias like a name tag at a conference. Your legal name might be "Jonathan Michael Pemberton III," but for the day, your name tag says "Jon." Everyone refers to you as Jon. When the conference ends, you're still Jonathan Michael Pemberton III — nothing changed permanently. The alias only existed for that event.
SQL aliases work the same way. You give a column or table a temporary name that exists only for the duration of that query. The underlying table structure, column names, and data are untouched.
The syntax uses the keyword AS, though in most databases the keyword is optional:
-- With AS (recommended for clarity)
SELECT first_name AS name FROM customers;
-- Without AS (also valid in most databases)
SELECT first_name name FROM customers;
Tip
Always use the AS keyword explicitly. The version without AS is legal but easy to misread — SELECT first_name name looks like a typo or an accident. AS makes your intent unambiguous.
The most common use of aliases is renaming columns in your SELECT output. There are three situations where this is especially valuable.
When you calculate something in a SELECT clause, the result column usually gets a messy auto-generated name. Here's a query that calculates average order fulfillment time:
-- Without alias — the column header is a mess
SELECT
AVG(DATEDIFF(day, created_at, shipped_at))
FROM orders;
The output column header will literally be AVG(DATEDIFF(day, created_at, shipped_at)) — which is accurate but terrible for any downstream use. Add an alias:
-- With alias — clean, self-documenting output
SELECT
AVG(DATEDIFF(day, created_at, shipped_at)) AS avg_fulfillment_days
FROM orders;
Now the output column is labeled avg_fulfillment_days. If you're feeding this query into a dashboard, a report, or another application, that label shows up there too.
Databases often have columns named things like id, name, status, or type that exist across many tables. When you're joining multiple tables together, it becomes critical to clarify which name or id you're referring to:
SELECT
c.id AS customer_id,
c.name AS customer_name,
c.email AS customer_email,
o.id AS order_id,
o.created_at AS order_date,
o.total AS order_total
FROM customers c
JOIN orders o ON c.id = o.customer_id;
Without the aliases, you'd have two columns named id in the result — one for the customer, one for the order. Most databases will include both, but downstream tools might only show one, or handle the naming inconsistently. Aliasing makes both explicit and removes the ambiguity entirely.
Sometimes column names in the database use technical shorthand that made sense to the person who designed the schema, but means nothing to the people reading reports:
-- The database stores it as 'cust_acq_dt' and 'ltv_usd_cents'
-- Your report should show something understandable
SELECT
cust_acq_dt AS acquisition_date,
ltv_usd_cents / 100 AS lifetime_value_usd
FROM customer_metrics;
This kind of alias does double duty: it renames the column to something meaningful, and it also handles a unit conversion in one place.
Aliases can contain spaces if you quote them, but this creates more problems than it solves. Here's what that looks like in different databases:
-- SQL Server / PostgreSQL: double quotes
SELECT total_revenue AS "Total Revenue (USD)" FROM sales_summary;
-- MySQL: backticks
SELECT total_revenue AS `Total Revenue (USD)` FROM sales_summary;
Warning
Aliases with spaces require quoting everywhere they're referenced downstream. They're also harder to use programmatically. Use underscores instead: total_revenue_usd is just as readable as Total Revenue (USD) and far less fragile.
Table aliases assign a short name to a table reference so you don't have to type the full table name every time you reference a column. This is especially important in queries that touch three, four, or more tables.
The syntax follows the same pattern as column aliases:
FROM customers AS c
FROM orders AS o
FROM order_line_items AS li
Here's a realistic example without table aliases versus with them. Imagine you're pulling together a customer order report:
-- Without table aliases: exhausting to read and write
SELECT
customers.first_name,
customers.last_name,
orders.id,
orders.created_at,
order_line_items.product_id,
order_line_items.quantity,
order_line_items.unit_price
FROM customers
JOIN orders ON customers.id = orders.customer_id
JOIN order_line_items ON orders.id = order_line_items.order_id;
-- With table aliases: the same query, much cleaner
SELECT
c.first_name,
c.last_name,
o.id AS order_id,
o.created_at AS order_date,
li.product_id,
li.quantity,
li.unit_price
FROM customers AS c
JOIN orders AS o ON c.id = o.customer_id
JOIN order_line_items AS li ON o.id = li.order_id;
The second version requires about half the keystrokes and is dramatically easier to scan. When you see li.quantity, you immediately know you're looking at a line item field. When you see c.first_name, you know it's the customer.
Key insight
Table aliases aren't just a convenience — they're a communication tool. They establish a vocabulary for your query. Once you define c as customers, every subsequent c. prefix is a signal to the reader about where that data lives.
The most common convention is to use the first letter of each word in the table name:
| Table Name | Alias |
|---|---|
| customers | c |
| orders | o |
| order_line_items | li or oli |
| products | p |
| product_categories | pc |
When two tables start with the same letter (say, customers and coupons), be more descriptive:
FROM customers AS cust
JOIN coupons AS coup ON cust.id = coup.customer_id
Avoid meaningless aliases like t1, t2, t3. These save a few keystrokes but destroy readability — you'll forget which is which within five lines.
Tip
If you're working on a team, agree on alias conventions for your most common tables and stick to them. When everyone uses c for customers and o for orders, queries become readable across the entire codebase, not just to the person who wrote them.
This is where many beginners hit unexpected errors. SQL processes clauses in a specific logical order, and aliases aren't always visible when you might expect them to be. Understanding this saves you real debugging time.
You can reference a column alias in ORDER BY, and it's a good habit:
SELECT
c.name AS customer_name,
SUM(o.total) AS total_spent
FROM customers AS c
JOIN orders AS o ON c.id = o.customer_id
GROUP BY c.id, c.name
ORDER BY total_spent DESC;
total_spent is the alias defined in SELECT, and ORDER BY can see it. This is clean and readable.
This is the most common alias-related mistake. You cannot use a column alias in a WHERE clause:
-- This will FAIL with an error like "column 'total_spent' does not exist"
SELECT
c.name AS customer_name,
SUM(o.total) AS total_spent
FROM customers AS c
JOIN orders AS o ON c.id = o.customer_id
WHERE total_spent > 1000 -- ERROR: alias not visible here
GROUP BY c.id, c.name;
Why? Because SQL processes WHERE before it evaluates the SELECT clause. The alias total_spent doesn't exist yet when WHERE runs.
The fix is to use HAVING for aggregate conditions, or to wrap your query in a subquery:
-- Correct: use HAVING for conditions on aggregates
SELECT
c.name AS customer_name,
SUM(o.total) AS total_spent
FROM customers AS c
JOIN orders AS o ON c.id = o.customer_id
GROUP BY c.id, c.name
HAVING SUM(o.total) > 1000;
When you need to filter on a computed column that isn't an aggregate, wrap the query in a subquery or CTE. You'll learn more about that pattern in Writing SQL FROM Scratch: Structuring Multi-Step Analytical Queries with Derived Tables and Inline Views.
Most databases (SQL Server, PostgreSQL older versions) require you to repeat the full expression in GROUP BY rather than using the alias:
-- Safe across all databases: repeat the expression
SELECT
YEAR(o.created_at) AS order_year,
COUNT(*) AS num_orders
FROM orders AS o
GROUP BY YEAR(o.created_at);
PostgreSQL and some other modern databases actually allow GROUP BY to reference aliases defined in SELECT, but SQL Server and MySQL have different rules. To write portable SQL, repeat the expression rather than relying on alias references in GROUP BY. You can read more about aggregate behavior in Master SQL Aggregate Functions: GROUP BY, HAVING, COUNT, SUM, AVG.
Warning
Never assume your GROUP BY alias behavior will transfer between database systems. A query that works in PostgreSQL might fail in SQL Server and vice versa. When in doubt, repeat the expression — it's always safe.
When you use a subquery in a FROM clause (sometimes called a derived table or inline view), you must give it an alias. Without one, the database has no way to reference it. This is one case where aliases aren't optional — they're required.
-- This will FAIL: subquery in FROM needs an alias
SELECT *
FROM (
SELECT customer_id, SUM(total) AS total_spent
FROM orders
GROUP BY customer_id
);
-- Correct: give the subquery an alias
SELECT *
FROM (
SELECT customer_id, SUM(total) AS total_spent
FROM orders
GROUP BY customer_id
) AS customer_totals
WHERE total_spent > 500;
Now you can reference customer_totals just like any other table, including using dot notation to reference its columns: customer_totals.total_spent.
This pattern — wrapping a query in a subquery and aliasing the result — lets you filter on computed values that you couldn't filter on directly. Understanding SQL subqueries for filtering and lookups builds on this foundation.
Key insight
When you alias a subquery, you're essentially saying "treat the result of this inner query as a named table for the purposes of the outer query." This is a powerful pattern for breaking complex problems into steps.
One scenario where table aliases aren't just helpful but completely required is the self-join — when you join a table to itself. Without aliases, the database can't distinguish which copy of the table you mean.
Imagine an employees table with a manager_id column that references another employee's id:
-- Join employees to itself to get each employee's manager name
SELECT
emp.first_name AS employee_name,
mgr.first_name AS manager_name
FROM employees AS emp
JOIN employees AS mgr ON emp.manager_id = mgr.id;
Here, emp and mgr both refer to the same employees table — but the aliases give us two distinct "copies" to work with. Without aliases, this query is impossible to write. You can explore this pattern further in Writing SQL Self-Joins: Query the Same Table Twice to Compare Rows and Find Relationships.
Let's put everything together in a realistic business scenario. You're a data analyst at a retail company and your manager asks: "Show me the top 10 customers by revenue for this year, along with how many orders they placed and their average order size."
Here's the query, built with careful aliasing throughout:
SELECT
c.id AS customer_id,
c.first_name AS first_name,
c.last_name AS last_name,
c.email AS email,
COUNT(o.id) AS total_orders,
SUM(o.total) AS total_revenue,
AVG(o.total) AS avg_order_value
FROM customers AS c
JOIN orders AS o
ON c.id = o.customer_id
WHERE YEAR(o.created_at) = YEAR(GETDATE())
GROUP BY
c.id,
c.first_name,
c.last_name,
c.email
HAVING SUM(o.total) > 0
ORDER BY total_revenue DESC
LIMIT 10;
Notice what's happening:
c, o)total_orders, total_revenue, avg_order_value)total_revenue — this is readable and validSUM(o.total) rather than the alias — safe and portableThe query is dense with information but easy to read because every name tells you something.
Open any SQL environment connected to a database with at least two related tables. If you don't have one handy, most cloud SQL sandboxes (like db-fiddle.com or SQLFiddle) let you create tables and run queries in the browser.
Setup (if you need sample data):
CREATE TABLE products (
id INT,
name VARCHAR(100),
category VARCHAR(50),
cost_cents INT
);
CREATE TABLE sales (
id INT,
product_id INT,
quantity INT,
sale_date DATE
);
INSERT INTO products VALUES
(1, 'Wireless Headphones', 'Electronics', 7999),
(2, 'Standing Desk Mat', 'Office', 3499),
(3, 'Laptop Sleeve', 'Electronics', 2999),
(4, 'Ergonomic Chair', 'Office', 29999),
(5, 'USB-C Hub', 'Electronics', 5499);
INSERT INTO sales VALUES
(1, 1, 3, '2024-03-01'),
(2, 2, 1, '2024-03-05'),
(3, 1, 2, '2024-03-10'),
(4, 3, 5, '2024-03-12'),
(5, 4, 1, '2024-03-15'),
(6, 5, 4, '2024-03-20'),
(7, 2, 2, '2024-03-22');
Your tasks:
Write a query that shows each product's name and its cost in dollars (not cents). Alias the computed column clearly.
Write a JOIN query that shows each product name, category, and total units sold. Use table aliases throughout and give the aggregated column a clear name.
Extend that query to show only categories with more than 5 total units sold. (Hint: use HAVING, not WHERE.)
Add an ORDER BY to sort by total units sold, descending. Use the column alias in ORDER BY.
Expected column names in your final result: product_name, category, total_units_sold
"Column not found" in WHERE using an alias You've referenced a column alias in a WHERE clause. SQL evaluates WHERE before SELECT, so the alias doesn't exist yet. Move aggregate conditions to HAVING, or wrap the query in a subquery.
Forgetting to alias a subquery in FROM
If you get an error like "Every derived table must have its own alias" (MySQL) or "subquery in FROM must have an alias" (PostgreSQL), you've put a subquery in the FROM clause without naming it. Add AS some_name right after the closing parenthesis.
Ambiguous column names in JOINs
If your query errors with "column reference is ambiguous," you have a column with the same name in multiple joined tables and haven't specified which one. Add table aliases and use dot notation: c.id instead of just id.
Overusing the same aliases across complex queries
In long queries with many subqueries, reusing aliases like t or x in different scopes is confusing. Give each subquery and table a unique, descriptive alias even if it's a few extra characters.
Aliases in CREATE VIEW vs. regular queries Aliases work the same way inside view definitions as in regular queries. However, if you alias a column in a view, that alias becomes the permanent column name of the view. Choose view column aliases with extra care — changing them later means updating everything that queries the view.
Aliases are a small feature with outsized impact on the quality of your SQL. A well-aliased query communicates intent, prevents ambiguity in multi-table queries, produces clean output column names, and makes maintenance dramatically easier — for you and for everyone who touches that query after you.
The core rules to carry forward:
AS explicitly for both column and table aliasest1/t2 for tablesFrom here, your natural next steps are to apply aliasing in the context of more complex query structures. When you start working with GROUP BY and aggregate functions, you'll use column aliases constantly to name computed metrics. As you move into advanced subqueries and CTEs, clear aliasing becomes even more critical — nested queries with ambiguous names become genuinely impossible to debug. Build the habit now, and it pays dividends every time you write SQL.