Postgres M 2
Postgres M 2
Basic Syntax
SELECT column1, column2, ...
FROM table_name
WHERE condition
ORDER BY column
LIMIT n;
Module - 2 1
('Bob', 'Marketing', 60000, 'Mumbai', 35, '2021-03
-10', TRUE),
('Charlie', 'Engineering', 90000, 'Bangalore', 32, '2020-06
-01', TRUE),
('Diana', 'HR', 50000, 'Hyderabad', 29, '2023-07
-20', FALSE),
('Eve', 'Marketing', 70000, 'Chennai', 31, '2022-11
-05', TRUE),
('Frank', 'Engineering', 85000, 'Bangalore', 40, '2019-08
-15', TRUE),
('Grace', 'HR', 55000, 'Mumbai', 27, '2023-01
-10', FALSE),
('Henry', 'Finance', 95000, 'Hyderabad', 45, '2018-04
-01', TRUE);
Module - 2 2
name | dept | salary
---------+--------------+----------
Alice | Engineering | 75000.00
Bob | Marketing | 60000.00
Charlie | Engineering | 90000.00
3. Column Aliases — AS
Rename columns in output using AS:
SELECT
name AS employee_name,
dept AS department,
salary AS monthly_salary
FROM employees;
4. Expressions in SELECT
-- Calculate annual salary
SELECT
name,
salary AS monthly_salary,
salary * 12 AS annual_salary,
salary * 0.10 AS bonus
FROM employees;
Module - 2 3
Alice | 75000.00 | 900000.00 | 7500.00
Bob | 60000.00 | 720000.00 | 6000.00
-- Specific city
SELECT * FROM employees WHERE city = 'Hyderabad';
6. Comparison Operators
SELECT * FROM employees WHERE salary = 75000; -- equal
SELECT * FROM employees WHERE salary != 75000; -- not equa
l
SELECT * FROM employees WHERE salary <> 75000; -- not equa
l (same as !=)
SELECT * FROM employees WHERE salary > 70000; -- greater
than
SELECT * FROM employees WHERE salary < 70000; -- less tha
n
SELECT * FROM employees WHERE salary >= 75000; -- greater
or equal
SELECT * FROM employees WHERE salary <= 60000; -- less or
equal
Module - 2 4
-- AND — both conditions must be true
SELECT * FROM employees
WHERE dept = 'Engineering' AND salary > 80000;
-- Combined
SELECT * FROM employees
WHERE (dept = 'Engineering' OR dept = 'Finance')
AND salary > 80000
AND is_active = TRUE;
-- Date range
SELECT name, hire_date FROM employees
WHERE hire_date BETWEEN '2021-01-01' AND '2023-01-01';
-- NOT BETWEEN
SELECT name, salary FROM employees
WHERE salary NOT BETWEEN 60000 AND 80000;
Module - 2 5
9. IN — Match Multiple Values
-- Employees in specific departments
SELECT * FROM employees
WHERE dept IN ('Engineering', 'Finance');
-- NOT IN
SELECT * FROM employees
WHERE dept NOT IN ('HR', 'Marketing');
Module - 2 6
-- Employees with no city
SELECT * FROM employees WHERE city IS NULL;
Module - 2 7
-- Page 2 (page size = 3)
SELECT name, salary FROM employees
ORDER BY emp_id
LIMIT 3 OFFSET 3; -- skip first 3, get next 3
-- Unique cities
SELECT DISTINCT city FROM employees;
-- Sum of salaries
SELECT SUM(salary) FROM employees;
-- Average salary
SELECT AVG(salary) FROM employees;
SELECT ROUND(AVG(salary), 2) AS avg_salary FROM employees;
-- Highest salary
SELECT MAX(salary) FROM employees;
-- Lowest salary
Module - 2 8
SELECT MIN(salary) FROM employees;
-- Multiple aggregates
SELECT
COUNT(*) AS total_employees,
ROUND(AVG(salary),2) AS avg_salary,
MAX(salary) AS highest_salary,
MIN(salary) AS lowest_salary,
SUM(salary) AS total_salary
FROM employees;
Module - 2 9
-- Departments where avg salary > 70000
SELECT dept, ROUND(AVG(salary),2) AS avg_salary
FROM employees
GROUP BY dept
HAVING AVG(salary) > 70000;
WHERE vs HAVING:
WHERE HAVING
Filters Individual rows Grouped results
Used with Any query GROUP BY only
Aggregates ❌ Not allowed ✅ Allowed
18. Full SELECT Clause Order
This is the correct order of clauses in a SELECT statement:
Module - 2 10
ORDER BY avg_salary DESC
LIMIT 5
OFFSET 0;
-- Concatenation
SELECT name || ' works in ' || dept AS description FROM emp
loyees;
-- CONCAT function
SELECT CONCAT(name, ' - ', city) AS info FROM employees;
Module - 2 11
-- Length of name
SELECT name, LENGTH(name) AS name_length FROM employees;
-- Trim whitespace
SELECT TRIM(name) FROM employees;
-- Substring
SELECT SUBSTRING(name, 1, 3) AS short_name FROM employees;
Best Practices
Always specify column names instead of SELECT * in production — it is faster
and more readable.
Use aliases to make output column names clear and readable.
Always use ORDER BY with LIMIT — without ORDER BY, results are
unpredictable.
Use WHERE to filter before grouping and HAVING to filter after grouping.
Use DISTINCT sparingly — it can be slow on large tables. Consider if your data
model needs fixing instead.
Always use IS NULL / IS NOT NULL — never = NULL .
Use ILIKE instead of LIKE for case-insensitive searches in PostgreSQL.
SELECT columns
FROM table
WHERE condition;
Module - 2 12
Think of it like a filter or sieve — data passes through only if it meets the
condition.
Module - 2 13
-- Equal to
SELECT * FROM employees WHERE dept = 'Engineering';
-- Not equal to
SELECT * FROM employees WHERE dept != 'HR';
SELECT * FROM employees WHERE dept <> 'HR'; -- same as !=
-- Greater than
SELECT * FROM employees WHERE salary > 70000;
-- Less than
SELECT * FROM employees WHERE salary < 70000;
2. AND Operator
Both conditions must be TRUE:
Module - 2 14
WHERE city = 'Hyderabad' AND is_active = TRUE;
3. OR Operator
At least one condition must be TRUE:
name | city
---------+-----------
Alice | Hyderabad
Charlie | Bangalore
Diana | Hyderabad
Frank | Bangalore
Henry | Hyderabad
-- HR or Finance employees
SELECT name, dept
FROM employees
WHERE dept = 'HR' OR dept = 'Finance';
4. NOT Operator
Negates a condition:
Module - 2 15
-- Not in Engineering
SELECT name, dept
FROM employees
WHERE NOT dept = 'Engineering';
-- Not active
SELECT name, is_active
FROM employees
WHERE NOT is_active;
Module - 2 16
-- Active employees in Engineering or Finance with salary >
70000
SELECT name, dept, salary, is_active
FROM employees
WHERE is_active = TRUE
AND (dept = 'Engineering' OR dept = 'Finance')
AND salary > 70000;
name | salary
-------+----------
Alice | 75000.00
Bob | 60000.00
Eve | 70000.00
-- NOT BETWEEN
SELECT name, salary
FROM employees
WHERE salary NOT BETWEEN 60000 AND 80000;
Module - 2 17
7. IN — Match a List of Values
-- Employees in specific departments
SELECT name, dept
FROM employees
WHERE dept IN ('Engineering', 'Finance', 'HR');
name | dept
---------+--------------
Alice | Engineering
Charlie | Engineering
Diana | HR
Frank | Engineering
Grace | HR
Henry | Finance
-- NOT IN
SELECT name, dept
FROM employees
WHERE dept NOT IN ('HR', 'Marketing');
-- IN with numbers
SELECT name, emp_id
FROM employees
WHERE emp_id IN (1, 3, 5, 7);
Module - 2 18
-- Names ending with 'e'
SELECT name FROM employees WHERE name LIKE '%e';
-- Exactly 3 characters
SELECT name FROM employees WHERE name LIKE '___';
Wildcard Characters:
Symbol Meaning Example
% Zero or more characters 'A%' → Alice, Anna
_ Exactly one character 'A__' → Amy, Ann
-- NOT LIKE
SELECT * FROM employees WHERE name NOT LIKE 'A%';
Module - 2 19
SELECT name FROM employees WHERE phone IS NULL; -- ✅ corr
ect
-- Inactive employees
SELECT name FROM employees WHERE is_active = FALSE;
SELECT name FROM employees WHERE NOT is_active; -- short
hand
Module - 2 20
12. WHERE with String Functions
-- Case insensitive exact match
SELECT * FROM employees
WHERE LOWER(name) = 'alice';
Module - 2 21
-- Employees in departments that have more than 2 people
SELECT name, dept
FROM employees
WHERE dept IN (
SELECT dept
FROM employees
GROUP BY dept
HAVING COUNT(*) > 2
);
-- This:
WHERE dept = 'HR' OR dept = 'Finance' AND salary > 80000
-- Is evaluated as:
WHERE dept = 'HR' OR (dept = 'Finance' AND salary > 80000)
-- AND is evaluated first!
Module - 2 22
ORDER BY -- 6. sort results
LIMIT -- 7. limit rows
-- CORRECT ✅
— use HAVING for aggregates
SELECT dept, AVG(salary)
FROM employees
GROUP BY dept
HAVING AVG(salary) > 70000;
Quick Reference
Operator Description Example
= Equal salary = 75000
Module - 2 23
Operator Description Example
IS NOT NULL Check not null phone IS NOT NULL
Best Practices
Always use parentheses when combining AND and OR — never rely on
operator precedence.
Use IN instead of multiple OR conditions — it is cleaner and faster.
Use BETWEEN for range conditions — more readable than >= AND <= .
Always use IS NULL and IS NOT NULL — never = NULL .
Use ILIKE for case-insensitive searches instead of converting with
UPPER/LOWER.
Put the most selective condition first in AND chains — PostgreSQL can short-
circuit evaluation.
Never use aggregate functions in WHERE — use HAVING instead.
Module - 2 24
age SMALLINT,
hire_date DATE,
is_active BOOLEAN
);
1. Equal To ( = )
-- Exact match
SELECT * FROM employees WHERE dept = 'Engineering';
Module - 2 25
name | dept | salary
---------+--------------+----------
Alice | Engineering | 75000.00
Charlie | Engineering | 90000.00
Frank | Engineering | 85000.00
name | salary
---------+----------
Charlie | 90000.00
Frank | 85000.00
Henry | 95000.00
Module - 2 26
SELECT name, salary FROM employees WHERE salary < 60000;
-- Age range
SELECT name, age FROM employees
WHERE age BETWEEN 28 AND 35;
-- Date range
SELECT name, hire_date FROM employees
WHERE hire_date BETWEEN '2021-01-01' AND '2023-12-31';
-- NOT BETWEEN
SELECT name, salary FROM employees
WHERE salary NOT BETWEEN 60000 AND 80000;
Module - 2 27
8. IN — List Comparison
-- Match any value in the list
SELECT name, dept FROM employees
WHERE dept IN ('Engineering', 'Finance');
-- City filter
SELECT name, city FROM employees
WHERE city IN ('Hyderabad', 'Bangalore');
-- NOT IN
SELECT name, dept FROM employees
WHERE dept NOT IN ('HR', 'Marketing');
-- IN with numbers
SELECT name, emp_id FROM employees
WHERE emp_id IN (1, 3, 5, 7);
-- Ends with e
SELECT name FROM employees WHERE name LIKE '%e';
-- Contains 'ar'
SELECT name FROM employees WHERE name LIKE '%ar%';
-- NOT LIKE
SELECT name FROM employees WHERE name NOT LIKE 'A%';
Module - 2 28
-- Check for NULL
SELECT * FROM employees WHERE phone IS NULL;
-- IS DISTINCT FROM
SELECT * FROM employees
WHERE dept IS DISTINCT FROM 'Engineering';
-- Returns rows where dept != 'Engineering' AND where dept
IS NULL
Expression Result
1 = 1 TRUE
1 = NULL NULL
NULL = NULL NULL
1 IS NOT DISTINCT FROM 1 TRUE
Module - 2 29
Expression Result
1 IS NOT DISTINCT FROM NULL FALSE
NULL IS NOT DISTINCT FROM NULL TRUE
1. AND Operator
Both conditions must be TRUE:
Module - 2 30
WHERE dept = 'Engineering' AND salary > 80000;
2. OR Operator
At least one condition must be TRUE:
Module - 2 31
WHERE city = 'Hyderabad' OR city = 'Bangalore';
name | city
---------+-----------
Alice | Hyderabad
Charlie | Bangalore
Diana | Hyderabad
Frank | Bangalore
Henry | Hyderabad
-- HR or Finance department
SELECT name, dept FROM employees
WHERE dept = 'HR' OR dept = 'Finance';
OR Truth Table:
A B A OR B
TRUE TRUE TRUE
TRUE FALSE TRUE
FALSE TRUE TRUE
FALSE FALSE FALSE
TRUE NULL TRUE
FALSE NULL NULL
NULL NULL NULL
3. NOT Operator
Negates / reverses a condition:
-- Not in Engineering
SELECT name, dept FROM employees
WHERE NOT dept = 'Engineering';
Module - 2 32
-- Not active
SELECT name, is_active FROM employees
WHERE NOT is_active;
Module - 2 33
-- Active employees NOT in HR with salary >= 70000
SELECT name, dept, salary, is_active FROM employees
WHERE is_active = TRUE
AND NOT dept = 'HR'
AND salary >= 70000;
-- Complex condition
SELECT name, dept, city, salary FROM employees
WHERE (city = 'Hyderabad' OR city = 'Bangalore')
AND (dept = 'Engineering' OR dept = 'Finance')
AND salary > 70000
AND is_active = TRUE;
5. Operator Precedence
When mixing operators PostgreSQL evaluates in this order:
Module - 2 34
6. NULL Behavior with Logical Operators
NULL behaves unexpectedly in logical operations — this is called three-valued
logic (TRUE, FALSE, NULL):
-- NULL in AND
SELECT TRUE AND NULL; -- NULL
SELECT FALSE AND NULL; -- FALSE (short-circuit)
SELECT NULL AND NULL; -- NULL
-- NULL in OR
SELECT TRUE OR NULL; -- TRUE (short-circuit)
SELECT FALSE OR NULL; -- NULL
SELECT NULL OR NULL; -- NULL
-- NULL in NOT
SELECT NOT NULL; -- NULL
-- NULL in comparison
SELECT NULL = NULL; -- NULL (not TRUE!)
SELECT NULL != NULL; -- NULL
SELECT NULL > 5; -- NULL
Module - 2 35
-- 2. Simple OR
SELECT * FROM employees
WHERE dept = 'HR' OR dept = 'Finance';
-- 4. NOT + IN
SELECT * FROM employees
WHERE dept NOT IN ('HR', 'Marketing')
AND is_active = TRUE;
-- 5. LIKE + AND
SELECT * FROM employees
WHERE name LIKE 'A%'
OR name LIKE 'E%'
AND salary > 60000;
-- 6. Date + AND
SELECT * FROM employees
WHERE hire_date >= '2022-01-01'
AND is_active = TRUE
AND salary > 70000;
-- 8. BETWEEN + OR
SELECT * FROM employees
WHERE salary BETWEEN 70000 AND 90000
OR age < 29;
Module - 2 36
SELECT name, dept, salary, city
FROM employees
WHERE is_active = TRUE
AND dept IN ('Engineering', 'Finance')
AND salary >= 75000
AND city != 'Mumbai'
AND hire_date BETWEEN '2018-01-01' AND '2023-01-01'
ORDER BY salary DESC;
Best Practices
Always use parentheses when mixing AND and OR — never rely on
precedence.
Use IN instead of multiple OR conditions — cleaner and more readable.
-- Instead of this
WHERE city = 'Mumbai' OR city = 'Delhi' OR city = 'Chennai'
-- Use this
WHERE city IN ('Mumbai', 'Delhi', 'Chennai')
Module - 2 37
-- Dangerous if dept can be NULL
WHERE dept NOT IN ('HR', NULL) -- returns nothing!
-- Safe approach
WHERE dept NOT IN ('HR') AND dept IS NOT NULL
SELECT columns
FROM table
WHERE condition
ORDER BY column [ASC | DESC];
Module - 2 38
('Alice', 'Engineering', 75000, 'Hyderabad', 28, '2022-01
-15', TRUE),
('Bob', 'Marketing', 60000, 'Mumbai', 35, '2021-03
-10', TRUE),
('Charlie', 'Engineering', 90000, 'Bangalore', 32, '2020-06
-01', TRUE),
('Diana', 'HR', 50000, 'Hyderabad', 29, '2023-07
-20', FALSE),
('Eve', 'Marketing', 70000, 'Chennai', 31, '2022-11
-05', TRUE),
('Frank', 'Engineering', 85000, 'Bangalore', 40, '2019-08
-15', TRUE),
('Grace', 'HR', 55000, 'Mumbai', 27, '2023-01
-10', FALSE),
('Henry', 'Finance', 95000, 'Hyderabad', 45, '2018-04
-01', TRUE);
name | salary
---------+----------
Diana | 50000.00
Grace | 55000.00
Bob | 60000.00
Eve | 70000.00
Alice | 75000.00
Frank | 85000.00
Charlie | 90000.00
Henry | 95000.00
Module - 2 39
-- ASC is default — same result without keyword
SELECT name, salary
FROM employees
ORDER BY salary;
-- Sort by name A to Z
SELECT name, dept
FROM employees
ORDER BY name ASC;
name | salary
---------+----------
Henry | 95000.00
Charlie | 90000.00
Frank | 85000.00
Alice | 75000.00
Eve | 70000.00
Bob | 60000.00
Grace | 55000.00
Diana | 50000.00
Module - 2 40
-- Sort by name Z to A
SELECT name, dept
FROM employees
ORDER BY name DESC;
Module - 2 41
-- Sort by city A-Z, then name A-Z
SELECT name, city, salary
FROM employees
ORDER BY city ASC, name ASC;
5. Sorting by Alias
You can sort by a column alias defined in SELECT:
Module - 2 42
name,
salary * 12 AS annual_salary
FROM employees
ORDER BY annual_salary DESC;
name | annual_salary
---------+--------------
Henry | 1140000.00
Charlie | 1080000.00
Frank | 1020000.00
Alice | 900000.00
Eve | 840000.00
Bob | 720000.00
Grace | 660000.00
Diana | 600000.00
6. Sorting by Expression
Sort directly by a calculated expression:
Module - 2 43
-- Sort by name length (shortest name first)
SELECT name
FROM employees
ORDER BY LENGTH(name) ASC;
name
---------
Bob
Eve
Alice
Diana
Frank
Grace
Henry
Charlie
Module - 2 44
-- Engineering dept sorted by age
SELECT name, dept, age
FROM employees
WHERE dept = 'Engineering'
ORDER BY age ASC;
name | salary
---------+----------
Henry | 95000.00
Charlie | 90000.00
Frank | 85000.00
Module - 2 45
ORDER BY age DESC
LIMIT 3;
Module - 2 46
FROM employees
ORDER BY salary DESC NULLS LAST;
name | dept
---------+--------------
Alice | Engineering
Charlie | Engineering
Frank | Engineering
Henry | Finance
Bob | Marketing
Eve | Marketing
Diana | HR
Grace | HR
Module - 2 47
END ASC;
Module - 2 48
GROUP BY dept, name, salary -- 3. group
HAVING salary > 50000 -- 4. filter groups
ORDER BY dept ASC, salary DESC -- 6. sort ← here
LIMIT 5 -- 7. limit rows
OFFSET 0; -- 8. skip rows
Module - 2 49
FROM employees
WHERE dept = 'Engineering'
ORDER BY age ASC;
Quick Reference
Syntax Meaning
ORDER BY salary Ascending (default)
ORDER BY salary ASC Ascending (A→Z, low→high)
ORDER BY salary DESC Descending (Z→A, high→low)
ORDER BY dept ASC, salary DESC Multi-column sort
ORDER BY 2 DESC Sort by 2nd column
ORDER BY alias DESC Sort by alias
ORDER BY salary DESC NULLS LAST NULLs at bottom
ORDER BY salary ASC NULLS FIRST NULLs at top
Best Practices
Always use ORDER BY when using LIMIT — without it results are unpredictable.
Prefer column names over column positions in ORDER BY — positions break
when SELECT list changes.
Use NULLS LAST with DESC to prevent NULLs from appearing at the top
unexpectedly.
Module - 2 50
Use multi-column sorting for deterministic results when many rows share the
same value in the first sort column.
Use CASE in ORDER BY for custom business-defined sort orders.
For case-insensitive text sorting use ORDER BY LOWER(column) .
Always put ORDER BY after WHERE and GROUP BY — it is the second to last
clause before LIMIT.
What is OFFSET?
OFFSET skips a specified number of rows before starting to return results. It
tells PostgreSQL — "ignore the first N rows, then start returning".
SELECT columns
FROM table
ORDER BY column
LIMIT n
OFFSET n;
Module - 2 51
INSERT INTO employees (name, dept, salary, city, age, hire_
date, is_active)
VALUES
('Alice', 'Engineering', 75000, 'Hyderabad', 28, '2022-01
-15', TRUE),
('Bob', 'Marketing', 60000, 'Mumbai', 35, '2021-03
-10', TRUE),
('Charlie', 'Engineering', 90000, 'Bangalore', 32, '2020-06
-01', TRUE),
('Diana', 'HR', 50000, 'Hyderabad', 29, '2023-07
-20', FALSE),
('Eve', 'Marketing', 70000, 'Chennai', 31, '2022-11
-05', TRUE),
('Frank', 'Engineering', 85000, 'Bangalore', 40, '2019-08
-15', TRUE),
('Grace', 'HR', 55000, 'Mumbai', 27, '2023-01
-10', FALSE),
('Henry', 'Finance', 95000, 'Hyderabad', 45, '2018-04
-01', TRUE);
1. Basic LIMIT
-- Get first 3 rows
SELECT name, salary
FROM employees
LIMIT 3;
name | salary
-------+----------
Alice | 75000.00
Bob | 60000.00
Charlie| 90000.00
Module - 2 52
-- Get only 1 row
SELECT * FROM employees LIMIT 1;
⚠️ Without ORDER BY, which 3 rows you get is unpredictable. Always use
ORDER BY with LIMIT.
name | salary
---------+----------
Henry | 95000.00
Charlie | 90000.00
Frank | 85000.00
Module - 2 53
LIMIT 1;
-- 3 youngest employees
SELECT name, age
FROM employees
ORDER BY age ASC
LIMIT 3;
3. Basic OFFSET
-- Skip first 3 rows, return rest
SELECT name, salary
FROM employees
ORDER BY salary DESC
OFFSET 3;
name | salary
---------+----------
Alice | 75000.00
Eve | 70000.00
Bob | 60000.00
Grace | 55000.00
Diana | 50000.00
Module - 2 54
4. LIMIT + OFFSET Together — Pagination
This is the most common use case — paginating through records:
-- Page 1 — rows 1 to 3
SELECT name, salary
FROM employees
ORDER BY emp_id
LIMIT 3 OFFSET 0;
name | salary
---------+----------
Alice | 75000.00
Bob | 60000.00
Charlie | 90000.00
-- Page 2 — rows 4 to 6
SELECT name, salary
FROM employees
ORDER BY emp_id
LIMIT 3 OFFSET 3;
name | salary
---------+----------
Diana | 50000.00
Eve | 70000.00
Frank | 85000.00
-- Page 3 — rows 7 to 9
SELECT name, salary
FROM employees
ORDER BY emp_id
LIMIT 3 OFFSET 6;
name | salary
-------+----------
Module - 2 55
Grace | 55000.00
Henry | 95000.00
5. Pagination Formula
OFFSET = (page_number - 1) * page_size
LIMIT = page_size
-- Page size = 3
-- Page 1: LIMIT 3 OFFSET 0 → (1-1)*3 = 0
-- Page 2: LIMIT 3 OFFSET 3 → (2-1)*3 = 3
-- Page 3: LIMIT 3 OFFSET 6 → (3-1)*3 = 6
-- Page 4: LIMIT 3 OFFSET 9 → (4-1)*3 = 9
-- Page size = 5
-- Page 1: LIMIT 5 OFFSET 0
-- Page 2: LIMIT 5 OFFSET 5
-- Page 3: LIMIT 5 OFFSET 10
-- Page 4: LIMIT 5 OFFSET 15
Module - 2 56
bquery)
SELECT name, dept, salary
FROM (
SELECT
name, dept, salary,
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salar
y DESC) AS rn
FROM employees
) ranked
WHERE rn <= 2
ORDER BY dept, salary DESC;
Module - 2 57
SELECT name, salary
FROM employees
ORDER BY salary DESC
LIMIT 1 OFFSET 3; -- OFFSET = N - 1
Module - 2 58
FROM employees
ORDER BY salary DESC
OFFSET 5;
-- Page 1 (page_size = 3)
SELECT emp_id, name, dept, salary
FROM employees
ORDER BY name ASC
LIMIT 3 OFFSET 0;
Module - 2 59
-- Page 2
SELECT emp_id, name, dept, salary
FROM employees
ORDER BY name ASC
LIMIT 3 OFFSET 3;
-- Page 3
SELECT emp_id, name, dept, salary
FROM employees
ORDER BY name ASC
LIMIT 3 OFFSET 6;
Performance Consideration
-- Small OFFSET — fast
SELECT * FROM employees ORDER BY emp_id LIMIT 10 OFFSET 10;
Module - 2 60
0000;
-- PostgreSQL must scan and skip 1 million rows!
-- Page 3
SELECT emp_id, name, salary
FROM employees
WHERE emp_id > 6
ORDER BY emp_id ASC
LIMIT 3;
Module - 2 61
Syntax Meaning
LIMIT 5 Return only 5 rows
LIMIT 1 Return only 1 row
LIMIT ALL No limit (all rows)
OFFSET 3 Skip first 3 rows
OFFSET 0 No skip (start from beginning)
LIMIT 3 OFFSET 3 Skip 3, return next 3 (Page 2)
LIMIT 1 OFFSET 1 2nd row (2nd highest salary etc.)
Best Practices
Always use ORDER BY with LIMIT — without it the rows returned are
unpredictable.
Use LIMIT 1 to get a single specific record like highest, lowest, latest, oldest.
Use OFFSET = (page - 1) × page_size for simple pagination in smaller datasets.
For large tables, prefer keyset pagination over OFFSET — it does not degrade
with large offsets.
Always retrieve total count separately when paginating so the frontend knows
total pages.
Use LIMIT 1 OFFSET N-1 to get the Nth highest or lowest value.
Never use LIMIT without testing edge cases — what happens on the last page
when fewer rows remain than the page size.
What is DISTINCT?
DISTINCT removes duplicate rows from query results and returns only unique
values. When you query a column that has repeated values, DISTINCT ensures
each value appears only once.
Module - 2 62
FROM table;
dept
--------------
Engineering
Marketing
Engineering
HR
Marketing
Engineering
HR
Finance
With DISTINCT:
dept
--------------
Engineering
Marketing
HR
Finance
Module - 2 63
salary NUMERIC(10,2),
city VARCHAR(50),
age SMALLINT,
hire_date DATE,
is_active BOOLEAN
);
dept
--------------
Engineering
Finance
Module - 2 64
HR
Marketing
-- Unique cities
SELECT DISTINCT city
FROM employees;
city
-----------
Hyderabad
Mumbai
Bangalore
Chennai
is_active
-----------
t
f
dept | city
--------------+-----------
Engineering | Bangalore
Module - 2 65
Engineering | Hyderabad
Finance | Hyderabad
HR | Hyderabad
HR | Mumbai
Marketing | Chennai
Marketing | Mumbai
Module - 2 66
-- Unique departments of active employees
SELECT DISTINCT dept
FROM employees
WHERE is_active = TRUE;
total_departments
-------------------
4
Module - 2 67
SELECT COUNT(DISTINCT (dept, city)) AS unique_combinations
FROM employees;
6. DISTINCT vs GROUP BY
Both can remove duplicates but work differently:
DISTINCT GROUP BY
Purpose Remove duplicates Group + aggregate
Aggregation ❌ Not directly ✅ Yes
Performance Similar Similar
Use when Just need unique values Need counts/sums too
Module - 2 68
Charlie | Engineering | 90000.00
Henry | Finance | 95000.00
Grace | HR | 55000.00
Eve | Marketing | 70000.00
⚠️ With DISTINCT ON, the ORDER BY must start with the same column(s) as
DISTINCT ON.
phone
-----------
NULL
Module - 2 69
9876543210
9999999999
DISTINCT Summary
Syntax Description
SELECT DISTINCT col Unique values of one column
SELECT DISTINCT col1, col2 Unique combinations
SELECT DISTINCT ON (col) One row per unique value (PostgreSQL)
COUNT(DISTINCT col) Count unique values
name | ?column?
---------+------------
Alice | 900000.00
Module - 2 70
-- With alias (clear output)
SELECT name, salary * 12 AS annual_salary
FROM employees;
name | annual_salary
---------+--------------
Alice | 900000.00
2. AS Keyword — Optional
The AS keyword is optional — both work the same:
Module - 2 71
-----+--------------
1 | Alice | Engineering | 75000.00 | Hyder
abad | t
2 | Bob | Marketing | 60000.00 | Mumba
i | f
-- String expressions
SELECT
name,
dept,
name || ' works in ' || dept AS description,
UPPER(name) AS name_upper,
LENGTH(name) AS name_length
FROM employees;
-- Date expressions
Module - 2 72
SELECT
name,
hire_date,
CURRENT_DATE - hire_date AS days_employed,
AGE(CURRENT_DATE, hire_date) AS employment_duratio
n
FROM employees;
Module - 2 73
6. Alias with Spaces — Double Quotes
If alias contains spaces or special characters, wrap in double quotes:
SELECT
name AS "Employee Name",
salary AS "Monthly Salary",
salary * 12 AS "Annual CTC",
dept AS "Department Name"
FROM employees;
7. Alias in ORDER BY
You can use aliases in ORDER BY — PostgreSQL resolves them:
SELECT
name,
salary * 12 AS annual_salary
FROM employees
ORDER BY annual_salary DESC; -- using alias in ORDER BY
name | annual_salary
---------+--------------
Henry | 1140000.00
Charlie | 1080000.00
Frank | 1020000.00
Module - 2 74
Aliases cannot be used in WHERE clause — WHERE is evaluated before
SELECT:
-- WRONG ❌
SELECT name, salary * 12 AS annual_salary
FROM employees
WHERE annual_salary > 900000;
-- ERROR: column "annual_salary" does not exist
-- OR use subquery
SELECT name, annual_salary
FROM (
SELECT name, salary * 12 AS annual_salary
FROM employees
) AS salary_data
WHERE annual_salary > 900000;
-- WRONG ❌
SELECT dept, AVG(salary) AS avg_salary
FROM employees
GROUP BY dept
HAVING avg_salary > 70000;
-- ERROR: column "avg_salary" does not exist
-- CORRECT ✅
SELECT dept, AVG(salary) AS avg_salary
FROM employees
Module - 2 75
GROUP BY dept
HAVING AVG(salary) > 70000;
-- Table alias
SELECT [Link], [Link], [Link]
FROM employees AS e
WHERE [Link] > 70000;
-- Without AS
SELECT [Link], [Link]
FROM employees e;
Module - 2 76
Clause Can Use Alias?
SELECT ✅ Define alias here
ORDER BY ✅ Yes
GROUP BY ✅ Yes (PostgreSQL allows)
WHERE ❌ No
HAVING ❌ No
FROM ✅ Table alias only
Complete Practical Example
SELECT
e.emp_id AS id,
[Link] AS "Employee Name",
[Link] AS department,
[Link] AS monthly_salary,
[Link] * 12 AS annual_salary,
[Link] * 0.10 AS bonus,
[Link] AS location,
AGE(CURRENT_DATE, e.hire_date) AS experience,
CASE e.is_active
WHEN TRUE THEN 'Active'
WHEN FALSE THEN 'Inactive'
END AS status
FROM employees AS e
WHERE e.is_active = TRUE
ORDER BY annual_salary DESC
LIMIT 5;
Best Practices
Always use AS keyword when defining aliases — it is more readable.
Use meaningful alias names that describe the data — annual_salary not col1 .
Use double quotes for aliases with spaces — "Employee Name" not Employee Name .
Remember aliases cannot be used in WHERE or HAVING — use the original
expression instead.
Module - 2 77
Use table aliases in queries with multiple tables or JOINs to keep code clean.
Use DISTINCT ON in PostgreSQL when you need the top/first record per group
— it is more efficient than subqueries.
Use COUNT(DISTINCT column) to count unique values in a column.
Module - 2 78