🔍 Module 5: SELECT Queries & JOINs

WHERE, ORDER BY, INNER/OUTER/CROSS JOINs, Table Joins

SELECT
Query Basics
WHERE
Filtering
JOIN
Table Joins
ORDER BY
Sorting

📋 SELECT Statement Basics

Basic SELECT Syntax

-- Simple SELECT statement
SELECT 
  employee_id,
  first_name,
  last_name,
  salary
FROM employees;

-- SELECT all columns
SELECT * FROM employees;

-- SELECT with aliases
SELECT 
  employee_id AS emp_id,
  first_name || ' ' || last_name AS full_name,
  salary AS monthly_salary
FROM employees;

-- SELECT with expressions
SELECT 
  employee_id,
  salary,
  salary * 12 AS annual_salary,
  salary * 1.1 AS salary_with_10_pct_raise
FROM employees;

DISTINCT and LIMIT

-- Get unique departments
SELECT DISTINCT department_id
FROM employees;

-- ROWNUM - Get first N rows (Oracle)
SELECT *
FROM employees
WHERE ROWNUM <= 10;

-- FETCH NEXT - Modern approach (12c+)
SELECT *
FROM employees
ORDER BY employee_id
FETCH NEXT 10 ROWS ONLY;

-- OFFSET with FETCH
SELECT *
FROM employees
ORDER BY employee_id
OFFSET 5 ROWS 
FETCH NEXT 10 ROWS ONLY;

🔎 WHERE Clause & Filtering

WHERE Operators

-- Comparison operators
SELECT * FROM employees WHERE salary > 50000;
SELECT * FROM employees WHERE salary <> 50000;

-- IN operator
SELECT * FROM employees 
WHERE department_id IN (10, 20, 30);

-- BETWEEN operator
SELECT * FROM employees 
WHERE salary BETWEEN 50000 AND 100000;

-- LIKE operator (pattern matching)
SELECT * FROM employees 
WHERE first_name LIKE 'J%';  -- Starts with J
SELECT * FROM employees 
WHERE first_name LIKE '%n';  -- Ends with n
SELECT * FROM employees 
WHERE first_name LIKE 'J_hn';  -- Exactly 4 chars, J and hn fixed

-- IS NULL / IS NOT NULL
SELECT * FROM employees WHERE manager_id IS NULL;
SELECT * FROM employees WHERE commission IS NOT NULL;

-- Logical operators (AND, OR, NOT)
SELECT * FROM employees 
WHERE salary > 50000 AND department_id = 20;

SELECT * FROM employees 
WHERE salary > 100000 OR department_id = 10;

SELECT * FROM employees 
WHERE NOT department_id = 20;

WHERE Best Practices

Clause Purpose Example
WHERE Filter rows before aggregation WHERE salary > 50000
HAVING Filter groups after aggregation HAVING COUNT(*) > 5
AND All conditions must be true salary > 50000 AND dept=20
OR Any condition can be true dept=10 OR dept=20
IN Match multiple values dept IN (10,20,30)

📊 ORDER BY - Sorting Results

Sorting Syntax

-- Sort ascending (default)
SELECT * FROM employees 
ORDER BY salary ASC;

-- Sort descending
SELECT * FROM employees 
ORDER BY salary DESC;

-- Multi-column sort
SELECT * FROM employees 
ORDER BY department_id ASC, salary DESC;

-- Sort by column position
SELECT department_id, COUNT(*), AVG(salary)
FROM employees 
GROUP BY department_id
ORDER BY 3 DESC;  -- Order by 3rd column (AVG salary)

-- Sort with NULLS FIRST/LAST
SELECT * FROM employees 
ORDER BY commission ASC NULLS LAST;

SELECT * FROM employees 
ORDER BY manager_id ASC NULLS FIRST;

🔗 JOIN Operations

INNER JOIN

Returns rows where there's a match in both tables.

-- INNER JOIN - explicit
SELECT 
  e.employee_id,
  e.first_name,
  e.salary,
  d.department_name
FROM employees e
INNER JOIN departments d 
  ON e.department_id = d.department_id;

-- INNER JOIN - implicit (old style, comma notation)
SELECT 
  e.employee_id,
  e.first_name,
  d.department_name
FROM employees e, departments d
WHERE e.department_id = d.department_id;

-- INNER JOIN with multiple tables
SELECT 
  e.employee_id,
  e.first_name,
  d.department_name,
  l.city
FROM employees e
INNER JOIN departments d ON e.department_id = d.department_id
INNER JOIN locations l ON d.location_id = l.location_id;

LEFT OUTER JOIN

Returns all rows from left table + matching rows from right table.

-- LEFT OUTER JOIN
SELECT 
  e.employee_id,
  e.first_name,
  d.department_name
FROM employees e
LEFT OUTER JOIN departments d 
  ON e.department_id = d.department_id;

-- SHORT FORM: LEFT JOIN
SELECT 
  e.employee_id,
  e.first_name,
  d.department_name
FROM employees e
LEFT JOIN departments d 
  ON e.department_id = d.department_id;

RIGHT OUTER JOIN

Returns all rows from right table + matching rows from left table.

-- RIGHT OUTER JOIN
SELECT 
  e.employee_id,
  e.first_name,
  d.department_name
FROM employees e
RIGHT OUTER JOIN departments d 
  ON e.department_id = d.department_id;

-- Get departments with no employees
SELECT 
  d.department_id,
  d.department_name,
  COUNT(e.employee_id) AS emp_count
FROM employees e
RIGHT JOIN departments d 
  ON e.department_id = d.department_id
WHERE e.employee_id IS NULL
GROUP BY d.department_id, d.department_name;

FULL OUTER JOIN

Returns all rows from both tables, matching where possible.

-- FULL OUTER JOIN
SELECT 
  e.employee_id,
  e.first_name,
  d.department_name
FROM employees e
FULL OUTER JOIN departments d 
  ON e.department_id = d.department_id;

CROSS JOIN

Cartesian product - returns all combinations of rows.

-- CROSS JOIN - all combinations
SELECT 
  e.employee_id,
  e.first_name,
  d.department_name
FROM employees e
CROSS JOIN departments d;

-- Use case: Generate all dates in range
SELECT d.report_date, s.shift_name
FROM (
  SELECT TRUNC(SYSDATE) + ROWNUM - 1 AS report_date
  FROM DUAL 
  CONNECT BY ROWNUM <= 30
) d
CROSS JOIN shifts s;

SELF JOIN

Join a table to itself (typically for hierarchical data).

-- SELF JOIN - employee and their manager
SELECT 
  e.employee_id,
  e.first_name AS employee_name,
  m.first_name AS manager_name,
  m.employee_id AS manager_id
FROM employees e
LEFT JOIN employees m 
  ON e.manager_id = m.employee_id;

JOIN Comparison

JOIN Type Left Table Right Table Match Use Case
INNER ✓ Matched ✓ Matched Required Only related data
LEFT ✓ All ✓ Matched Optional Primary table complete
RIGHT ✓ Matched ✓ All Optional Secondary table complete
FULL ✓ All ✓ All Optional All data from both
CROSS ✓ All ✓ All None All combinations

⚡ Advanced JOIN Techniques

Multiple JOINs with Conditions

-- Complex multi-table JOIN with filtering
SELECT 
  e.employee_id,
  e.first_name,
  d.department_name,
  l.city,
  COUNT(p.project_id) AS project_count
FROM employees e
INNER JOIN departments d ON e.department_id = d.department_id
INNER JOIN locations l ON d.location_id = l.location_id
LEFT JOIN projects p ON e.employee_id = p.lead_id
WHERE e.hire_date > '01-JAN-2020'
GROUP BY e.employee_id, e.first_name, d.department_name, l.city
ORDER BY project_count DESC;

Anti-JOIN (Find Non-Matching)

-- Find employees without projects
SELECT 
  e.employee_id,
  e.first_name
FROM employees e
LEFT JOIN projects p ON e.employee_id = p.lead_id
WHERE p.project_id IS NULL;

-- Alternative using NOT IN
SELECT 
  e.employee_id,
  e.first_name
FROM employees e
WHERE e.employee_id NOT IN (
  SELECT DISTINCT lead_id FROM projects
);
💡 Tip: Use table aliases (e, d, l) for readability. Use INNER JOIN when you need matches in both tables. Use LEFT/RIGHT for optional relationships.
✓ Learning Checklist - Module 5