All topics
Database · Learning hub

Oracle Database notes for developers

Master Oracle Database with a curated set of 3 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore Database notes
Oracle Database

SQL & PL/SQL Fundamentals

Oracle Database: SQL & PL/SQL Fundamentals Oracle Database is a relational database management system with a rich SQL dialect and PL/SQL — a procedural extensio

Oracle Database: SQL & PL/SQL Fundamentals

Oracle Database is a relational database management system with a rich SQL dialect and PL/SQL — a procedural extension of SQL for writing stored procedures, functions, triggers, and packages.

Oracle SQL Essentials

-- DUAL: Oracle's built-in one-row table for expressions
SELECT SYSDATE FROM DUAL;
SELECT 1 + 1 FROM DUAL;
SELECT USER FROM DUAL;

-- String functions
SELECT UPPER('hello'), LOWER('WORLD'), LENGTH('abc') FROM DUAL;
SELECT SUBSTR('Oracle Database', 1, 6) FROM DUAL;  -- 'Oracle' (1-indexed)
SELECT INSTR('Oracle Database', 'a') FROM DUAL;    -- first 'a' position
SELECT TRIM('  hello  ') FROM DUAL;
SELECT LPAD('42', 5, '0') FROM DUAL;               -- '00042'

-- Number functions
SELECT ROUND(3.145, 2) FROM DUAL;   -- 3.15
SELECT TRUNC(3.9) FROM DUAL;        -- 3 (truncate, not round)
SELECT MOD(10, 3) FROM DUAL;        -- 1
SELECT POWER(2, 10) FROM DUAL;      -- 1024
SELECT ABS(-5), CEIL(3.1), FLOOR(3.9) FROM DUAL;

-- Date functions
SELECT SYSDATE FROM DUAL;                     -- current date+time
SELECT SYSTIMESTAMP FROM DUAL;                -- with timezone
SELECT ADD_MONTHS(SYSDATE, 3) FROM DUAL;
SELECT MONTHS_BETWEEN(DATE '2024-12-31', DATE '2024-01-01') FROM DUAL;
SELECT LAST_DAY(SYSDATE) FROM DUAL;
SELECT TRUNC(SYSDATE, 'MM') FROM DUAL;        -- first day of month
SELECT TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS') FROM DUAL;
SELECT TO_DATE('2024-03-15', 'YYYY-MM-DD') FROM DUAL;

-- NVL / COALESCE (NULL handling)
SELECT NVL(commission, 0) FROM employees;
SELECT COALESCE(col1, col2, 'default') FROM table1;
SELECT NVL2(commission, 'Has commission', 'No commission') FROM employees;

Advanced SQL

-- Analytic (Window) functions
SELECT
    employee_id,
    salary,
    department_id,
    RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS dept_rank,
    ROW_NUMBER() OVER (ORDER BY salary DESC) AS overall_rank,
    LAG(salary, 1) OVER (ORDER BY hire_date) AS prev_salary,
    LEAD(salary, 1) OVER (ORDER BY hire_date) AS next_salary,
    SUM(salary) OVER (PARTITION BY department_id) AS dept_total,
    AVG(salary) OVER (ORDER BY hire_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg
FROM employees;

-- CONNECT BY (hierarchical queries)
SELECT
    LEVEL,
    LPAD(' ', (LEVEL-1)*2) || last_name AS name_tree,
    employee_id,
    manager_id
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR employee_id = manager_id
ORDER SIBLINGS BY last_name;

-- MERGE (upsert)
MERGE INTO target_table t
USING source_table s ON (t.id = s.id)
WHEN MATCHED THEN
    UPDATE SET t.value = s.value, t.updated_at = SYSDATE
WHEN NOT MATCHED THEN
    INSERT (id, value, created_at) VALUES (s.id, s.value, SYSDATE);

-- Pivot
SELECT * FROM (
    SELECT department_id, job_id, salary FROM employees
)
PIVOT (
    SUM(salary) FOR job_id IN ('IT_PROG' AS it_prog, 'SA_REP' AS sa_rep)
);

PL/SQL

-- Anonymous block
DECLARE
    v_name   employees.last_name%TYPE;
    v_salary NUMBER(10, 2);
BEGIN
    SELECT last_name, salary
    INTO v_name, v_salary
    FROM employees
    WHERE employee_id = 100;

    DBMS_OUTPUT.PUT_LINE('Name: ' || v_name || ', Salary: ' || v_salary);
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE('Employee not found');
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('Error: ' || SQLERRM);
END;
/

-- Stored procedure
CREATE OR REPLACE PROCEDURE give_raise(
    p_emp_id IN  employees.employee_id%TYPE,
    p_pct    IN  NUMBER,
    p_new_sal OUT employees.salary%TYPE
) AS
BEGIN
    UPDATE employees
    SET salary = salary * (1 + p_pct / 100)
    WHERE employee_id = p_emp_id
    RETURNING salary INTO p_new_sal;
    COMMIT;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        RAISE_APPLICATION_ERROR(-20001, 'Employee ' || p_emp_id || ' not found');
END;
/

-- Function
CREATE OR REPLACE FUNCTION get_annual_salary(p_emp_id IN NUMBER)
RETURN NUMBER AS
    v_sal NUMBER;
BEGIN
    SELECT salary * 12 INTO v_sal FROM employees WHERE employee_id = p_emp_id;
    RETURN v_sal;
END;
/

-- Cursor
DECLARE
    CURSOR c_emp IS
        SELECT employee_id, last_name, salary FROM employees WHERE department_id = 60;
BEGIN
    FOR rec IN c_emp LOOP
        DBMS_OUTPUT.PUT_LINE(rec.last_name || ': ' || rec.salary);
    END LOOP;
END;
/
Oracle Database

Performance: Indexing, Query Plans & Hints

Oracle Database: Performance Tuning Indexes -- B-tree index (default) CREATE INDEX idx_emp_dept ON employees(department_id); CREATE INDEX idx_emp_name ON employ

Oracle Database: Performance Tuning

Indexes

-- B-tree index (default)
CREATE INDEX idx_emp_dept ON employees(department_id);
CREATE INDEX idx_emp_name ON employees(last_name, first_name);  -- composite

-- Unique index
CREATE UNIQUE INDEX idx_emp_email ON employees(email);

-- Function-based index (for queries on expressions)
CREATE INDEX idx_upper_last ON employees(UPPER(last_name));
-- Now this uses the index:
SELECT * FROM employees WHERE UPPER(last_name) = 'KING';

-- Bitmap index (low-cardinality columns, data warehouse)
CREATE BITMAP INDEX idx_emp_job ON employees(job_id);

-- Partial index (only index certain rows)
CREATE INDEX idx_active_orders ON orders(order_date)
WHERE status = 'ACTIVE';

-- Index on partitioned table
CREATE INDEX idx_sales_date ON sales(sale_date) LOCAL;  -- local = per partition

-- Check index usage
SELECT index_name, table_name, uniqueness, status
FROM user_indexes
WHERE table_name = 'EMPLOYEES';

-- Drop index
DROP INDEX idx_emp_dept;

-- Rebuild fragmented index
ALTER INDEX idx_emp_dept REBUILD ONLINE;

Execution Plans & EXPLAIN PLAN

-- View execution plan
EXPLAIN PLAN FOR
SELECT e.last_name, d.department_name
FROM employees e JOIN departments d ON e.department_id = d.department_id
WHERE e.salary > 5000;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

-- Or use AUTOTRACE in SQL*Plus
SET AUTOTRACE ON;
SELECT * FROM employees WHERE department_id = 60;

-- From v$sql_plan (for already-executed queries)
SELECT *
FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(sql_id => 'abc123xyz'));

-- Key operations to look for:
-- TABLE ACCESS FULL   — no index used (bad for large tables)
-- INDEX RANGE SCAN    — good, uses index
-- INDEX UNIQUE SCAN   — best, exact match on unique index
-- HASH JOIN           — large table joins (good)
-- NESTED LOOPS        — small table joins (good when driving table is small)
-- SORT MERGE JOIN     — sorted inputs

-- Check statistics age (stale stats = bad plans)
SELECT table_name, last_analyzed, num_rows
FROM user_tables
ORDER BY last_analyzed;

-- Gather fresh stats
EXEC DBMS_STATS.GATHER_TABLE_STATS('HR', 'EMPLOYEES', cascade => TRUE);

Hints & Query Optimization

-- Hints: override optimizer decisions (use sparingly, as a last resort)

-- Force index use
SELECT /*+ INDEX(e idx_emp_dept) */ * FROM employees e WHERE department_id = 60;

-- Force full table scan (when FTS is actually faster for large result sets)
SELECT /*+ FULL(e) */ * FROM employees e WHERE salary > 1000;

-- Join order hints
SELECT /*+ LEADING(d e) USE_NL(e) */ *
FROM departments d JOIN employees e ON d.department_id = e.department_id;

-- Parallel query
SELECT /*+ PARALLEL(employees, 4) */ * FROM employees;

-- Result cache (cache query result in SGA)
SELECT /*+ RESULT_CACHE */ * FROM expensive_view;

-- Common performance tips
-- 1. Avoid functions on indexed columns in WHERE clause:
--    Bad:  WHERE TO_CHAR(hire_date, 'YYYY') = '2020'
--    Good: WHERE hire_date >= DATE '2020-01-01' AND hire_date < DATE '2021-01-01'

-- 2. Use bind variables (not literals) to share cursor cache
--    Bad:  WHERE id = 100
--    Good: WHERE id = :emp_id

-- 3. Use EXISTS instead of IN for correlated subqueries
SELECT * FROM departments d
WHERE EXISTS (SELECT 1 FROM employees e WHERE e.department_id = d.department_id);
Oracle Database

Administration: Users, Tablespaces & Backup

Oracle Database: Administration Users & Privileges -- Create user CREATE USER myapp IDENTIFIED BY "SecurePass123" DEFAULT TABLESPACE users TEMPORARY TABLESPACE

Oracle Database: Administration

Users & Privileges

-- Create user
CREATE USER myapp IDENTIFIED BY "SecurePass123"
    DEFAULT TABLESPACE users
    TEMPORARY TABLESPACE temp
    QUOTA 500M ON users;

-- Grant privileges
GRANT CONNECT, RESOURCE TO myapp;
GRANT CREATE SESSION TO myapp;
GRANT SELECT, INSERT, UPDATE, DELETE ON hr.employees TO myapp;
GRANT ALL ON hr.departments TO myapp;
GRANT CREATE VIEW, CREATE PROCEDURE TO myapp;

-- Roles
CREATE ROLE app_read_only;
GRANT SELECT ON hr.employees TO app_read_only;
GRANT SELECT ON hr.departments TO app_read_only;
GRANT app_read_only TO myapp;

-- Revoke
REVOKE DELETE ON hr.employees FROM myapp;

-- View user privileges
SELECT * FROM dba_sys_privs WHERE grantee = 'MYAPP';
SELECT * FROM dba_tab_privs WHERE grantee = 'MYAPP';
SELECT * FROM dba_role_privs WHERE grantee = 'MYAPP';

-- Switch user (DBA)
ALTER SESSION SET CURRENT_SCHEMA = hr;

-- Lock/unlock user
ALTER USER myapp ACCOUNT LOCK;
ALTER USER myapp ACCOUNT UNLOCK;

-- Change password
ALTER USER myapp IDENTIFIED BY "NewPass456";

Tablespaces

-- Create tablespace
CREATE TABLESPACE myapp_data
    DATAFILE '/u01/oradata/mydb/myapp_data01.dbf' SIZE 1G
    AUTOEXTEND ON NEXT 100M MAXSIZE 10G
    EXTENT MANAGEMENT LOCAL
    SEGMENT SPACE MANAGEMENT AUTO;

-- Add datafile to existing tablespace
ALTER TABLESPACE myapp_data
    ADD DATAFILE '/u01/oradata/mydb/myapp_data02.dbf' SIZE 1G AUTOEXTEND ON;

-- Resize datafile
ALTER DATABASE DATAFILE '/u01/oradata/mydb/myapp_data01.dbf' RESIZE 2G;

-- Check tablespace usage
SELECT
    tablespace_name,
    ROUND(used_space * 8192 / 1024 / 1024, 2) AS used_mb,
    ROUND(tablespace_size * 8192 / 1024 / 1024, 2) AS total_mb,
    ROUND(used_percent, 2) AS pct_used
FROM dba_tablespace_usage_metrics
ORDER BY used_percent DESC;

-- Drop tablespace (with data!)
DROP TABLESPACE myapp_data INCLUDING CONTENTS AND DATAFILES;

RMAN Backup

# RMAN (Recovery Manager) — Oracle's built-in backup tool

# Connect to RMAN
rman target /
rman target sys/password@mydb

# Full database backup
RMAN> BACKUP DATABASE PLUS ARCHIVELOG;

# Incremental backup (level 0 = full, level 1 = changes since level 0)
RMAN> BACKUP INCREMENTAL LEVEL 0 DATABASE;
RMAN> BACKUP INCREMENTAL LEVEL 1 DATABASE;

# Configure backup retention
RMAN> CONFIGURE RETENTION POLICY TO RECOVERY WINDOW OF 7 DAYS;
RMAN> CONFIGURE BACKUP OPTIMIZATION ON;

# List backups
RMAN> LIST BACKUP SUMMARY;
RMAN> LIST BACKUP OF DATABASE;

# Validate backup (check for corruption without restoring)
RMAN> VALIDATE DATABASE;

# Restore and recover
RMAN> RESTORE DATABASE;
RMAN> RECOVER DATABASE;
RMAN> ALTER DATABASE OPEN;

# Point-in-time recovery
RMAN> RUN {
    SET UNTIL TIME "TO_DATE('2024-03-15 14:00:00', 'YYYY-MM-DD HH24:MI:SS')";
    RESTORE DATABASE;
    RECOVER DATABASE;
    ALTER DATABASE OPEN RESETLOGS;
}

Key Data Dictionary Views

  • DBA_TABLES, DBA_INDEXES, DBA_COLUMNS — all objects in database (DBA access required).

  • ALL_TABLES, ALL_INDEXES — objects accessible to current user.

  • USER_TABLES, USER_INDEXES — objects owned by current user.

  • V$SESSION — active sessions; V$SQL — cached SQL statements.

  • V$LOCK, V$LOCKED_OBJECT — lock contention debugging.

  • V$DATABASE — database info; V$INSTANCE — instance info.

  • DBA_SEGMENTS — storage used per segment (table, index).

  • DBA_JOBS / DBA_SCHEDULER_JOBS — scheduled jobs.

Keep your Oracle Database knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever