Lesson 23

SELECT Statement in SQL: Querying Data from Tables

Write SELECT statements to retrieve exactly the columns and rows you need from any table in a relational database.

Every report, dashboard, and analysis starts with the same question: what data do you need and where does it live? The SELECT statement is how you answer that. Whether you are pulling a customer list, checking product stock, or reviewing recent orders, it always begins here.

Basic SELECT Syntax

A SELECT statement has two required parts: the column list and the table name.

SELECT column1, column2
FROM table_name;

Start with the columns you want, then tell SQL where to find them with FROM.

-- Retrieve basic customer information
SELECT c.customer_id, c.first_name, c.last_name, c.email
FROM customers c;
-- Retrieve product names, prices, and stock
SELECT p.product_name, p.price, p.stock_qty
FROM products p;

The alias after the table name (c, p) is a shorthand you can use to prefix column names. It keeps queries readable and becomes essential once you start joining multiple tables.

Selecting All Columns with SELECT *

SELECT * returns every column in the table. It is useful for quick exploration but avoid it in production queries.

-- See everything in the categories table
SELECT *
FROM categories cat;
-- Quick look at a few order rows
SELECT *
FROM orders o
LIMIT 5;
Key idea: SELECT * is fine when you are exploring data interactively. In reports and application queries, always list the columns you actually need. It prevents broken queries when a column is added or removed, and it makes your intent clear to anyone reading the code.

Column Aliases with AS

You can rename any column in the output using AS. The alias only affects the result set label, not the actual column name in the table.

-- Rename columns for cleaner output
SELECT
    c.customer_id   AS id,
    c.first_name    AS first,
    c.last_name     AS last,
    c.email         AS contact_email
FROM customers c;
-- Compute a value and give it a meaningful name
SELECT
    p.product_name,
    p.price,
    p.cost_price,
    p.price - p.cost_price AS gross_profit
FROM products p;

Aliases with spaces in them need to be wrapped in double quotes or backticks depending on the database. In SQLite, double quotes work. Stick to underscores to keep it simple.

Selecting Distinct Values

DISTINCT removes duplicate rows from the result. It applies to the entire row, not just one column.

-- See all unique countries in the customer base
SELECT DISTINCT c.country
FROM customers c
ORDER BY c.country;
-- See all unique order statuses
SELECT DISTINCT o.status
FROM orders o;
-- Unique combinations of loyalty tier and country
SELECT DISTINCT c.loyalty_tier, c.country
FROM customers c
ORDER BY c.loyalty_tier, c.country;

Computed Columns and Expressions

You can include expressions, string concatenation, and function calls directly in your SELECT list.

-- Concatenate first and last name
SELECT
    c.customer_id,
    c.first_name || ' ' || c.last_name AS full_name,
    c.email
FROM customers c;
-- Calculate margin percentage per product
SELECT
    p.product_name,
    p.price,
    p.cost_price,
    ROUND((p.price - p.cost_price) / p.price * 100, 2) AS margin_pct
FROM products p
WHERE p.price > 0;
-- Show order totals with a formatted label
SELECT
    o.order_id,
    o.order_date,
    o.status,
    o.total_amount,
    'Order #' || o.order_id AS order_label
FROM orders o
LIMIT 10;

Limiting Rows with LIMIT

Use LIMIT to restrict how many rows come back. This is especially useful when previewing large tables.

-- Preview the first 10 products
SELECT p.product_id, p.product_name, p.price, p.is_active
FROM products p
LIMIT 10;
-- Get the most recently created customers
SELECT c.customer_id, c.first_name, c.last_name, c.created_at
FROM customers c
ORDER BY c.created_at DESC
LIMIT 5;

Common Mistakes

-- wrong
SELECT customer_id, firstname, lastname
FROM customers;
-- correct
SELECT c.customer_id, c.first_name, c.last_name
FROM customers c;

Column names must match exactly what is defined in the table schema. firstname is not the same as first_name. A typo in a column name raises an error immediately.

---

-- wrong
SELECT c.first_name, c.last_name
total_amount
FROM customers c;
-- correct
SELECT c.first_name, c.last_name,
       o.total_amount
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id;

A missing comma between column names causes SQL to interpret the next item as an alias rather than a separate column, producing either a syntax error or a silently wrong result.

---

-- wrong
SELECT DISTINCT c.first_name, COUNT(*) AS total
FROM customers c;
-- correct
SELECT c.first_name, COUNT(*) AS total
FROM customers c
GROUP BY c.first_name;

Mixing DISTINCT with aggregate functions like COUNT() does not work as expected. Use GROUP BY when you want counts per unique value.

Quick Recap

  • A SELECT statement needs at minimum a column list and a FROM clause. List only the columns you need rather than using SELECT * in production queries.
  • Use AS to rename columns in the output without changing anything in the table. Use computed expressions in the SELECT list to derive new values on the fly.
  • DISTINCT removes duplicate rows from the result. LIMIT caps how many rows are returned, which is useful for exploration and pagination.
Exercise 1 Easy
Write a query that returns product_name, price, and stock_qty from the products table for all rows. Give stock_qty the alias units_available. Sort by price descending.
Hint: Use SELECT p.product_name, p.price, p.stock_qty AS units_available FROM products p. Add ORDER BY p.price DESC.
Solution
SELECT p.product_name, p.price, p.stock_qty AS units_available FROM products p ORDER BY p.price DESC;
Exercise 2 Medium
Write a query that returns each customer's full name as a single column called full_name using string concatenation of first_name and last_name, along with email and loyalty_tier. Also calculate a column called name_length that shows the total character length of full_name using LENGTH(). Sort by name_length descending. Limit to 10 rows.
Hint: Use c.first_name || ' ' || c.last_name AS full_name for the name. Wrap the same expression in LENGTH() for name_length. You can repeat the expression or reference it in ORDER BY.
Solution
SELECT c.first_name || ' ' || c.last_name AS full_name, c.email, c.loyalty_tier, LENGTH(c.first_name || ' ' || c.last_name) AS name_length FROM customers c ORDER BY name_length DESC LIMIT 10;
Exercise 3 Hard
Write a query that returns each unique combination of status and method from the payments table, along with the total number of payments for each combination as payment_count and the total amount as total_amount rounded to 2 decimal places. Sort by total_amount descending.
Hint: Use GROUP BY pay.status, pay.method to get unique combinations. Use COUNT(*) and ROUND(SUM(pay.amount), 2) for the aggregates.
Solution
SELECT pay.status, pay.method, COUNT(*) AS payment_count, ROUND(SUM(pay.amount), 2) AS total_amount FROM payments pay GROUP BY pay.status, pay.method ORDER BY total_amount DESC;
Frequently Asked Questions

SELECT is the command you use to retrieve data from one or more tables. It tells the database which columns you want and which table to pull them from.

SELECT * returns every column in the table. SELECT column_name returns only the columns you specify. In production queries, listing specific columns is preferred because it is faster and more explicit.

Yes, in most databases you can run SELECT with just an expression, like SELECT 1 + 1 or SELECT DATETIME('now'). But for retrieving table data you always need FROM.

Use an alias with the AS keyword, like SELECT first_name AS name. The alias only affects the output column label and does not change the underlying table.