404 Not Found

404 Not Found


nginx

PostgreSQL SELECT Query Basics

1. What You'll Learn


2. The Story

Bob is a database engineer at an e-commerce platform. The platform generates about 10,000 orders per day, accumulating 100,000 records over the past 30 days. The operations team asks him to:

  1. Query all orders from the last 30 days, sorted by amount from high to low
  2. Show 50 per page, with paging support
  3. Filter out cancelled orders
  4. Drop duplicate customer IDs from the display

Bob needs SELECT, WHERE, ORDER BY, LIMIT/OFFSET, and DISTINCT to get the job done.


3. Concept: SELECT Basics

(1) SELECT Syntax Overview

SQL
SELECT column1, column2, ...
FROM table_name
[WHERE condition]
[ORDER BY column [ASC|DESC] [NULLS FIRST|NULLS LAST]]
[LIMIT count [OFFSET start]];

SELECT is the most commonly used SQL statement, used to retrieve data from one or more tables.

(2) All Columns vs Specific Columns

Style Description Performance Recommended for
SELECT * Returns all columns Worse—transmits redundant data Quickly exploring table structure
SELECT col1, col2 Returns only specified columns Better—less I/O Production queries

▶ Example: Query All Columns

SQL
SELECT * FROM orders;

Output:

TEXT
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

▶ Example: Query Specific Columns

SQL
SELECT order_id, customer_id, total_amount, order_date
FROM orders;

Output:

TEXT
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(3) Column Aliases

Use the AS keyword (optional) to give a column or expression an alias for more readable results.

Usage Example Description
Explicit AS price * qty AS subtotal Recommended—clear
Omit AS price * qty subtotal Valid but less readable
Quoted alias total_amount AS "Order Total" Required when alias has spaces / case

▶ Example: Using Column Aliases

SQL
SELECT
  order_id,
  total_amount AS amount,
  order_date AS "Order Date",
  total_amount * 0.08 AS tax
FROM orders;

Output:

TEXT
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(4) Expressions and Computed Columns

SELECT can do more than read columns—it can also write arithmetic expressions, function calls, etc., to produce computed columns.

▶ Example: Computed Column

SQL
SELECT
  product_name,
  unit_price,
  unit_price * 1.1 AS price_with_tax,
  ROUND(unit_price * 1.1, 2) AS rounded_price
FROM products;

Output:

TEXT
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

4. Concept: DISTINCT De-duplication

(1) Basic De-duplication

DISTINCT removes duplicate rows from the result set, commonly used to count unique values.

▶ Example: Query Distinct Customer IDs

SQL
SELECT DISTINCT customer_id
FROM orders;

Output:

TEXT
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(2) Multi-column De-duplication

DISTINCT applies to the combination of all specified columns, not a single column.

Statement De-dup scope
SELECT DISTINCT a De-dup by a
SELECT DISTINCT a, b De-dup by (a, b) combination
SELECT DISTINCT ON (a) a, b PostgreSQL-only: de-dup by a, keep first row per group

▶ Example: Multi-column Combination De-dup

SQL
SELECT DISTINCT customer_id, order_status
FROM orders;

Output:

TEXT
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(3) DISTINCT ON: PostgreSQL-only Syntax

DISTINCT ON (expr) de-duplicates by a given expression and returns the first row in each group (pair with ORDER BY to decide which row).

▶ Example: Each Customer's Latest Order

SQL
SELECT DISTINCT ON (customer_id)
  customer_id, order_id, order_date, total_amount
FROM orders
ORDER BY customer_id, order_date DESC;

Output:

TEXT
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

5. Concept: WHERE Filtering

(1) Basic Comparison Operators

Operator Meaning Example
= Equal order_status = 'completed'
<> or != Not equal total_amount <> 0
> Greater than total_amount > 100
< Less than total_amount < 50
>= Greater than or equal total_amount >= 100
<= Less than or equal total_amount <= 500

▶ Example: Filter Completed Orders

SQL
SELECT order_id, customer_id, total_amount
FROM orders
WHERE order_status = 'completed';

Output:

TEXT
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(2) Date Filtering

Date comparison is one of the most common filtering needs in e-commerce.

▶ Example: Orders in the Last 30 Days

SQL
SELECT order_id, customer_id, total_amount, order_date
FROM orders
WHERE order_date >= CURRENT_DATE - INTERVAL '30 days';

Output:

TEXT
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(3) Combining Multiple Conditions

Use AND, OR, NOT to combine conditions. Precedence: NOT > AND > OR; use parentheses to control explicitly.

▶ Example: Combined Condition Filter

SQL
SELECT order_id, total_amount, order_status
FROM orders
WHERE order_status = 'completed'
  AND total_amount > 500
  AND order_date >= '2025-01-01';

Output:

TEXT
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

6. Concept: ORDER BY Sorting

(1) Basic Sorting

ORDER BY defaults to ascending (ASC); you can specify descending (DESC). Supports sorting by column name, alias, or column position.

Sort style Syntax Description
Ascending ORDER BY col ASC Default, ASC optional
Descending ORDER BY col DESC High to low
By alias ORDER BY amount DESC Use alias from SELECT
By position ORDER BY 3 DESC By 3rd column in SELECT list (not recommended)

▶ Example: Sort by Amount Descending

SQL
SELECT order_id, customer_id, total_amount
FROM orders
ORDER BY total_amount DESC;

Output:

TEXT
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(2) Multi-column Sorting

With multi-column sorting, rows are sorted by the first column; ties are broken by the second column, and so on.

▶ Example: Sort by Status, then by Amount

SQL
SELECT order_id, order_status, total_amount
FROM orders
ORDER BY order_status ASC, total_amount DESC;

Output:

TEXT
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(3) NULLS FIRST / NULLS LAST: PostgreSQL Feature

PostgreSQL treats NULL as the largest value by default (last in ascending, first in descending). You can explicitly control NULL placement with NULLS FIRST and NULLS LAST.

Sort NULL default position Adjustable to
ASC NULL at end NULLS FIRST moves NULL to front
DESC NULL at front NULLS LAST moves NULL to end

▶ Example: NULLS FIRST/LAST Control

SQL
SELECT product_name, discount_rate
FROM products
ORDER BY discount_rate DESC NULLS LAST;

Output:

TEXT
 count 
-------
     5
(1 row)

7. Concept: LIMIT and FETCH FIRST Pagination

(1) LIMIT / OFFSET

LIMIT caps the number of returned rows; OFFSET skips a given number of rows. Together they implement pagination.

▶ Example: 50 per page, page 3

SQL
SELECT order_id, customer_id, total_amount, order_date
FROM orders
WHERE order_status = 'completed'
ORDER BY total_amount DESC
LIMIT 50 OFFSET 100;

Output:

TEXT
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(2) FETCH FIRST: SQL Standard Syntax

PostgreSQL also supports the SQL-standard FETCH FIRST syntax, semantically identical to LIMIT.

Syntax Equivalent Description
LIMIT 10 FETCH FIRST 10 ROWS ONLY Standard form
LIMIT 10 OFFSET 20 OFFSET 20 ROWS FETCH FIRST 10 ROWS ONLY Standard pagination form

▶ Example: FETCH FIRST Pagination

SQL
SELECT order_id, customer_id, total_amount
FROM orders
ORDER BY total_amount DESC
OFFSET 100 ROWS
FETCH FIRST 50 ROWS ONLY;

Output:

TEXT
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(3) Pagination Formula

Parameter Calculation
Page page Starts at 1
Page size size e.g., 50
OFFSET (page - 1) * size
LIMIT size

▶ Example: Dynamic Pagination (pseudo-code)

BASH
page=3
size=50
offset=$(( (page - 1) * size ))
psql -c "SELECT order_id, total_amount FROM orders ORDER BY total_amount DESC LIMIT $size OFFSET $offset;"

Output:

TEXT
# psql command executed successfully

8. Flowchart: SELECT Query Execution Order

100%
flowchart TD
    A[FROM orders] --> B[WHERE filter]
    B --> C[SELECT columns / expressions]
    C --> D[DISTINCT]
    D --> E[ORDER BY sort]
    E --> F[LIMIT / OFFSET paginate]
    F --> G[Result Set]

    style A fill:#e1f5fe
    style B fill:#fff3e0
    style C fill:#e8f5e9
    style D fill:#f3e5f5
    style E fill:#fce4ec
    style F fill:#e0f2f1
    style G fill:#c8e6c9

9. Comprehensive Example

Bob completed the operations team's requirements: query completed orders from the last 30 days, paginate by amount descending, and report distinct customer counts.

SQL
-- Step 1: Recent 30-day completed orders, paginated by amount DESC
SELECT
  order_id,
  customer_id,
  total_amount,
  total_amount * 0.08 AS tax_amount,
  order_date AS "Order Date",
  order_status
FROM orders
WHERE order_status = 'completed'
  AND order_date >= CURRENT_DATE - INTERVAL '30 days'
ORDER BY total_amount DESC NULLS LAST
LIMIT 50 OFFSET 0;

-- Step 2: Count distinct customers in those orders
SELECT COUNT(DISTINCT customer_id) AS unique_customers
FROM orders
WHERE order_status = 'completed'
  AND order_date >= CURRENT_DATE - INTERVAL '30 days';

-- Step 3: Each customer's latest order (DISTINCT ON)
SELECT DISTINCT ON (customer_id)
  customer_id,
  order_id,
  total_amount,
  order_date
FROM orders
WHERE order_status = 'completed'
  AND order_date >= CURRENT_DATE - INTERVAL '30 days'
ORDER BY customer_id, total_amount DESC;

-- Step 4: Top 10 orders with computed total (price + tax)
SELECT
  order_id,
  customer_id,
  total_amount,
  ROUND(total_amount * 1.08, 2) AS total_with_tax,
  order_date
FROM orders
WHERE order_status = 'completed'
  AND order_date >= CURRENT_DATE - INTERVAL '30 days'
ORDER BY total_with_tax DESC
FETCH FIRST 10 ROWS ONLY;

❓ FAQ

Q Can I use SELECT * in production?
A Not recommended. SELECT * returns all columns, adding network transfer and memory overhead, and may break application logic when the table schema changes. Always list the columns you need explicitly.
Q What's the difference between DISTINCT and GROUP BY for de-duplication?
A DISTINCT only removes duplicate rows; GROUP BY can also do aggregation. If you just de-dup without aggregation, DISTINCT is more intuitive; use GROUP BY when you need aggregation.
Q How does LIMIT OFFSET paging perform at large offsets?
A OFFSET must skip the first N rows, so the larger the offset, the slower it gets. For large datasets, keyset pagination (WHERE filtering on a sort key) is recommended, e.g., WHERE order_id > last_seen_id LIMIT 50.
Q Is NULLS FIRST/LAST standard SQL?
A No, it's a PostgreSQL extension. MySQL and SQL Server don't support it; you'd emulate it with CASE or COALESCE.
Q Can DISTINCT ON be used without ORDER BY?
A Syntactically yes, but without ORDER BY, which row is kept per group is undefined. You must pair it with ORDER BY for predictable results.
Q Can a column alias be used in WHERE?
A No. In SQL execution order, WHERE runs before SELECT, so the alias doesn't exist yet. But aliases can be used in ORDER BY, since ORDER BY runs after SELECT.
Q Are FETCH FIRST and LIMIT completely equivalent?
A Functionally identical. FETCH FIRST is the SQL:2008 standard syntax; LIMIT is PostgreSQL's traditional syntax. For new code, FETCH FIRST improves portability.
Q Can ORDER BY reference a column not in the SELECT list?
A Yes. PostgreSQL allows ORDER BY to reference a column not in SELECT, but if DISTINCT or GROUP BY is used, the sort column must appear in SELECT or GROUP BY.

📖 Summary


📝 Exercises

  1. ⭐ Write a query selecting product_name and unit_price from products, sorted ascending by price, returning only the first 10 rows.

  2. ⭐⭐ Write a query selecting orders from the last 7 days from orders, filtering for total_amount > 200, sorted by order_date DESC, using FETCH FIRST 20 ROWS ONLY.

  3. ⭐⭐⭐ Use DISTINCT ON to query each customer_id's highest-amount order (hint: ORDER BY customer_id, total_amount DESC), and count how many of those customers have a max single order over 1,000 USD.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏