180 questions โ Grouped by Concept ยท Click to expand ยท + for code & details
SQL, or Structured Query Language, is a standard programming language specifically designed for managing and manipulating relational databases.
SQL enables users to query, update, and manage data within a database system.
SQL operates through simple, declarative statements, allowing for efficient data retrieval and manipulation.
This language forms the backbone of all relational database operations, playing a crucial role in data storage, retrieval, and analysis in various software applications and database systems.
Its widespread use and standardization make SQL an essential skill for database management and data analysis.
SQL ensures consistency and integrity of data across different database systems, providing a universal language for database interaction.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT employee_id, employee_name, salary
FROM employees
WHERE salary > 50000
ORDER BY salary DESC;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMPLOYEE_ID | EMPLOYEE_NAME | SALARY |
|---|---|---|
| 104 | Dave Brown | 70,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
3 rows โ Alice Johnson excluded by WHERE clause โ ordered by salary DESC
SQL, or Structured Query Language, is a standard programming language specifically designed for managing and manipulating relational databases.
The different types of SQL commands include Data Definition Language (DDL), Data Manipulation Language (DML), Data Control Language (DCL), and Transaction Control Language (TCL).
DDL commands create, modify, and remove database structures.
DML commands handle data within the database, such as inserting, updating, or deleting records.
DCL commands manage access to database objects, including granting and revoking user permissions.
TCL commands deal with transaction management, ensuring data integrity by managing transactional processes like commit and rollback.
Each command type plays a crucial role in database management and operation.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| CATEGORY | COMMANDS | EXAMPLE |
|---|---|---|
| DDL | CREATE, ALTER, DROP, TRUNCATE | CREATE TABLE employees(...) |
| DML | INSERT, UPDATE, DELETE, MERGE | UPDATE employees SET salary=60000 |
| DCL | GRANT, REVOKE | GRANT SELECT ON employees TO user1 |
| TCL | COMMIT, ROLLBACK, SAVEPOINT | COMMIT |
DDL changes structure; DML changes data; DCL controls permissions; TCL manages transactions
The different types of SQL commands include Data Definition Language (DDL), Data Manipulation Language (DML), Data Control Language (DCL), and Transaction Control Language (TCL).
The SELECT statement retrieves data from one or more tables in a database.
Its clauses are written in a fixed order: SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY.
However, Oracle evaluates them in a different logical order internally: FROM first, then WHERE, then GROUP BY, then HAVING, then SELECT, and finally ORDER BY.
Understanding this evaluation order explains why you cannot reference a column alias defined in SELECT inside the WHERE clause, but you can reference it in ORDER BY.
Each clause is optional except SELECT and FROM, though FROM can be omitted in Oracle when selecting from DUAL or a function result.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
SELECT emp_name, salary
FROM employees
WHERE dept_id = 10
ORDER BY salary DESC;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
Data state before the statement executes.
| EMP_NAME | SALARY |
|---|---|
| Carol White | 55,000 |
| Alice Johnson | 50,000 |
2 rows โ only dept_id 10 rows, ordered by salary descending
SQL clauses are written as SELECT-FROM-WHERE-GROUP BY-HAVING-ORDER BY, but Oracle logically evaluates FROM and WHERE before SELECT, which is why column aliases cannot be used in WHERE.
DISTINCT removes duplicate rows from a query's result set, returning only unique combinations of the selected columns.
When applied to multiple columns, DISTINCT considers the combination of all listed columns together, not each column independently.
DISTINCT is applied after the WHERE clause but before ORDER BY in the logical processing order.
Using DISTINCT on large datasets can be expensive because Oracle must sort or hash the result set to identify duplicates, so it should be used only when duplicates are actually possible.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
SELECT DISTINCT dept_id
FROM employees;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
Data state before the statement executes.
| DEPT_ID |
|---|
| 10 |
| 20 |
2 distinct department IDs returned instead of 3 rows
DISTINCT eliminates duplicate rows based on the combination of all selected columns, but can add sorting overhead on large result sets.
A primary key is a unique identifier for each record in a SQL database table.
Primary key ensures that no two rows have the same key value, maintaining data integrity and enabling efficient data retrieval.
The primary key is a single column or a combination of columns, known as a composite key.
It does not allow null values, guaranteeing that every record is uniquely identified.
Selecting an appropriate primary key is crucial for optimizing query performance and data relationships.
The primary key also serves as the target for foreign keys in other tables, forming the basis of relational database structure.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
CREATE TABLE employees (
employee_id NUMBER PRIMARY KEY,
employee_name VARCHAR2(100) NOT NULL,
email VARCHAR2(100) UNIQUE
);| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | DEPT_ID | SALARY |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | 20 | 70,000 |
All 4 rows returned from employees table
A primary key is a unique identifier for each record in a SQL database table.
Foreign key in SQL is a column or a set of columns in a table that establishes a link between data in two tables.
Foreign key acts as a cross-reference between tables because it references the primary key of another table, thereby establishing a relationship between them.
This key ensures referential integrity of the data, meaning it enforces constraints that ensure the validity of connections among related tables.
The foreign key in a table represents a field or collection of fields in another table, creating a relationship between the two tables.
This key is essential for maintaining data accuracy and consistency, as it restricts input to values that exist in the referenced table.
The use of a foreign key corresponds with the use of primary keys in related tables, facilitating the seamless integration and manipulation of data within relational databases.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
CREATE TABLE employees (
employee_id NUMBER PRIMARY KEY,
emp_name VARCHAR2(100),
dept_id NUMBER,
CONSTRAINT fk_emp_dept FOREIGN KEY (dept_id)
REFERENCES departments(dept_id)
);| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_ID | EMP_NAME | DEPT_ID | SALARY |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | 20 | 70,000 |
All 4 rows returned from employees table
Foreign key in SQL is a column or a set of columns in a table that establishes a link between data in two tables.
Unique key in SQL is a constraint that ensures all values in a column are distinct.
It is similar to a primary key but allows one null value.
This key uniquely identifies each record in a database table.
Unlike a primary key, a database table has multiple unique keys.
The unique key prevents duplicate entries in the specified column, ensuring data integrity.
When a unique key constraint is enforced, the database system checks for uniqueness of the column's values before performing insert or update operations.
This key plays a crucial role in relational database design, facilitating efficient data retrieval and maintaining the uniqueness of records.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | STATUS | |
|---|---|---|
| 101 | alice@company.com | โ Inserted OK |
| 102 | alice@company.com | โ ORA-00001: unique constraint (emp_email) violated |
| 103 | carol@company.com | โ Inserted OK โ different email |
UNIQUE KEY: each value must be unique โ NULL is allowed (unlike PRIMARY KEY)
Unique key in SQL is a constraint that ensures all values in a column are distinct.
Constraints in SQL database design are indispensable tools that enforce data accuracy, maintain relationships, and uphold data integrity, ensuring that the database remains reliable and consistent.
Constraints play a vital role in database design within the realm of SQL.
They serve as crucial rules and conditions that govern the structure and integrity of a database.Constraints ensure data accuracy and consistency.
Primary key constraints, for example, guarantee the uniqueness of each record in a table, preventing duplicate entries.
Foreign key constraints establish relationships between tables, maintaining referential integrity.Not-null constraints mandate that a specific column must always contain a value, eliminating the possibility of storing NULL data.
Check constraints allow you to define custom rules to validate data before insertion or modification.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
Constraints in SQL database design are indispensable tools that enforce data accuracy, maintain relationships, and uphold data integrity, ensuring that the database remains reliable and consistent.
Both enforce uniqueness, but a table can have only one PRIMARY KEY while it can have multiple UNIQUE constraints.
PRIMARY KEY automatically creates a unique index and cannot contain NULL values.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- PRIMARY KEY characteristics
CREATE TABLE employees_pk_demo (
employee_id NUMBER PRIMARY KEY, -- Only one PRIMARY KEY per table
ssn NUMBER UNIQUE, -- Multiple UNIQUE constraints allowed
email VARCHAR2(100) UNIQUE,
phone VARCHAR2(20) UNIQUE,
first_name VARCHAR2(50) NOT NULL,
last_name VARCHAR2(50) NOT NULL
);
-- PRIMARY KEY cannot be NULL
-- This will fail:
-- INSERT INTO employees_pk_demo VALUES (NULL, '123456789', 'john@email.com', '555-1234', 'John', 'Doe');
-- UNIQUE constraint can have NULL values (but only one NULL per column)
INSERT INTO employees_pk_demo VALUES (1, NULL, 'john@email.com', '555-1234', 'John', 'Doe');
INSERT INTO employees_pk_demo VALUES (2, NULL, 'jane@email.com', '555-5678', 'Jane', 'Smith');
-- Composite PRIMARY KEY
CREATE TABLE order_items (
order_id NUMBER,
product_id NUMBER,
quantity NUMBER,
unit_price NUMBER,
PRIMARY KEY (order_id, product_id) -- Composite primary key
);
-- Composite UNIQUE constraint
CREATE TABLE employees_unique_demo (
employee_id NUMBER PRIMARY KEY,
first_name VARCHAR2(50),
last_name VARCHAR2(50),
department_id NUMBER,
UNIQUE (first_name, last_name, department_id) -- Composite unique constraint
);
-- Adding constraints after table creation
ALTER TABLE employees_pk_demo
ADD CONSTRAINT uk_emp_ssn UNIQUE (ssn);
-- Check constraint information
SELECT constraint_name, constraint_type, search_condition, status
FROM user_constraints
WHERE table_name = 'EMPLOYEES_PK_DEMO';
-- Index creation behavior
-- PRIMARY KEY automatically creates a unique index
-- UNIQUE constraint also creates a unique index
SELECT index_name, table_name, uniqueness, column_name
FROM user_ind_columns
WHERE table_name = 'EMPLOYEES_PK_DEMO'
ORDER BY index_name, column_position;
-- Differences summary:
-- 1. PRIMARY KEY: One per table, cannot be NULL, automatically indexed
-- 2. UNIQUE: Multiple per table, can have one NULL value, automatically indexed
-- 3. Both prevent duplicate values
-- 4. PRIMARY KEY is used for referential integrity (foreign keys)
-- 5. Both support composite (multi-column) constraints
-- Foreign key references PRIMARY KEY
CREATE TABLE departments (
department_id NUMBER PRIMARY KEY,
department_name VARCHAR2(100) UNIQUE NOT NULL
);
CREATE TABLE employees_fk_demo (
employee_id NUMBER PRIMARY KEY,
department_id NUMBER,
FOREIGN KEY (department_id) REFERENCES departments(department_id)
);
-- Foreign key can also reference UNIQUE constraint
CREATE TABLE employees_fk_unique (
employee_id NUMBER PRIMARY KEY,
department_name VARCHAR2(100),
FOREIGN KEY (department_name) REFERENCES departments(department_name)
);
-- Performance comparison
-- Both PRIMARY KEY and UNIQUE constraints use B-tree indexes
-- Performance is similar for lookups and joins
-- PRIMARY KEY is slightly faster for foreign key lookups
-- Disable/Enable constraints
ALTER TABLE employees_pk_demo DISABLE CONSTRAINT uk_emp_ssn;
ALTER TABLE employees_pk_demo ENABLE CONSTRAINT uk_emp_ssn;
-- Drop constraints
ALTER TABLE employees_pk_demo DROP CONSTRAINT uk_emp_ssn;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_ID | EMP_NAME | DEPT_ID | SALARY |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | 20 | 70,000 |
All 4 rows returned โ ordered by salary ASC
Both enforce uniqueness, but a table can have only one PRIMARY KEY while it can have multiple UNIQUE constraints.
A NOT NULL constraint ensures that a column cannot contain a NULL value, forcing every row to supply a value for that column.
It is commonly applied to columns that are mandatory for business logic, such as employee_name or hire_date.
Unlike most other constraints, NOT NULL is defined directly on the column rather than as a separate named constraint, though Oracle does allow naming it explicitly.
Attempting to insert or update a row with a NULL value in a NOT NULL column raises an ORA-01400 error.
CREATE TABLE employees (
employee_id NUMBER PRIMARY KEY,
employee_name VARCHAR2(100) NOT NULL,
hire_date DATE NOT NULL
);
-- This insert fails because employee_name is NULL
INSERT INTO employees (employee_id, employee_name, hire_date)
VALUES (201, NULL, SYSDATE);| ERROR_CODE | MESSAGE |
|---|---|
| ORA-01400 | cannot insert NULL into ("EMPLOYEE_NAME") |
Insert is rejected before any row is written to the table
NOT NULL forces a column to always have a value, and Oracle rejects any insert or update that would leave it NULL by raising ORA-01400.
A CHECK constraint restricts the values that can be stored in a column by enforcing a Boolean condition that must evaluate to TRUE or UNKNOWN for every row.
It is used to enforce business rules directly at the database level, such as ensuring a salary is positive or that a status column only holds specific values.
Oracle evaluates the CHECK condition on every INSERT and UPDATE, rejecting the statement if the condition evaluates to FALSE.
Multiple CHECK constraints can be applied to the same table, and a single CHECK constraint can reference multiple columns.
CREATE TABLE employees (
employee_id NUMBER PRIMARY KEY,
salary NUMBER CHECK (salary > 0),
status VARCHAR2(10) CHECK (status IN ('ACTIVE','INACTIVE'))
);
-- This insert fails the CHECK constraint on salary
INSERT INTO employees (employee_id, salary, status)
VALUES (301, -500, 'ACTIVE');| ERROR_CODE | MESSAGE |
|---|---|
| ORA-02290 | check constraint violated |
Negative salary violates the CHECK constraint, so the row is never inserted
A CHECK constraint enforces a column-level or table-level rule, such as salary > 0, and Oracle blocks any DML that would violate it.
A DEFAULT constraint supplies a predefined value for a column automatically when no explicit value is provided during an INSERT.
It is useful for columns like created_date or status, where most rows should follow a standard value unless overridden.
The default value can be a literal, a SQL expression, or a built-in function such as SYSDATE.
A DEFAULT constraint only applies at insert time; it does not change existing rows or prevent a NULL from being explicitly inserted unless combined with a NOT NULL constraint.
CREATE TABLE orders (
order_id NUMBER PRIMARY KEY,
order_date DATE DEFAULT SYSDATE,
status VARCHAR2(10) DEFAULT 'PENDING'
);
INSERT INTO orders (order_id) VALUES (1001);| ORDER_ID | ORDER_DATE | STATUS |
|---|---|---|
| 1001 | (today's date) | PENDING |
order_date and status are filled automatically since no value was given for them
DEFAULT automatically fills a column with a specified value when no value is supplied in an INSERT, but it does not by itself block an explicit NULL.
A composite key is a primary key or unique key made up of two or more columns combined, where the combination of values must be unique even if individual columns repeat.
It is typically used for junction or association tables in many-to-many relationships, such as a table linking students to courses.
Oracle enforces uniqueness across the entire combination of columns, not each column independently.
Composite keys can make foreign key relationships more complex since the referencing table must also include all the composite columns.
| student_id | course_id | enrolled_date |
|---|---|---|
| 1 | 101 | 2026-01-10 |
| 1 | 102 | 2026-01-12 |
| 2 | 101 | 2026-01-15 |
CREATE TABLE enrollments (
student_id NUMBER,
course_id NUMBER,
enrolled_date DATE,
CONSTRAINT pk_enrollments PRIMARY KEY (student_id, course_id)
);| CONSTRAINT_NAME | CONSTRAINT_TYPE | COLUMNS |
|---|---|---|
| PK_ENROLLMENTS | P | STUDENT_ID, COURSE_ID |
student_id 1 can enroll in multiple courses, and course_id 101 can have multiple students โ only the pair must be unique
A composite key enforces uniqueness across a combination of columns together, commonly used in junction tables that model many-to-many relationships.
A join in SQL is a method to combine rows from two or more tables based on a related column between them.
It primarily serves to merge data from different tables in a relational database, enabling more complex queries and data analysis.
The most common types of joins are INNER JOIN, LEFT JOIN, RIGHT JOIN, and FULL JOIN.
INNER JOIN selects records with matching values in both tables.
LEFT JOIN returns all records from the left table and matched records from the right table, while RIGHT JOIN does the opposite.
FULL JOIN combines the results of both LEFT and RIGHT joins, including all records when there's a match in either table.
Joins are fundamental in SQL for data manipulation and retrieval, ensuring efficient access to related data spread across multiple tables.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
3 rows โ INNER JOIN on employees.dept_id = departments.dept_id (Dave Brown excluded, no dept match)
A join in SQL is a method to combine rows from two or more tables based on a related column between them.
In SQL, different types of joins enable the combination of rows from two or more tables.
The most common type is the INNER JOIN, which returns rows when there is a match in both tables.
The LEFT JOIN, also known as LEFT OUTER JOIN, returns all rows from the left table and the matched rows from the right table, filling in with NULLs if there is no match.
Similarly, the RIGHT JOIN, or RIGHT OUTER JOIN, includes all rows from the right table and the matched rows from the left table, using NULLs where no match exists.
The FULL OUTER JOIN combines the results of both LEFT and RIGHT JOINS, displaying all rows from both tables with NULLs in places where there is no match.
Another type, the CROSS JOIN, produces a Cartesian product of the two tables, joining every row of the first table with every row of the second table.
Lastly, the SELF JOIN is a regular join but the table is joined with itself.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
SELECT e.emp_name, d.dept_name
FROM employees e
INNER JOIN departments d ON e.dept_id = d.dept_id;
-- Only rows matching on BOTH sides| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
3 rows โ only rows with matching dept_id in both tables
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
SELECT e.emp_name, d.dept_name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.dept_id;
-- ALL employees returned| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
| Dave Brown | NULL |
4 rows โ Dave included with NULL dept ยท Finance excluded
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
SELECT e.emp_name, d.dept_name
FROM employees e
RIGHT JOIN departments d ON e.dept_id = d.dept_id;
-- ALL departments returned| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Carol White | Engineering |
| Bob Smith | Marketing |
| NULL | Finance |
4 rows โ Finance included with NULL emp ยท Dave excluded
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
SELECT e.emp_name, d.dept_name
FROM employees e
FULL OUTER JOIN departments d ON e.dept_id = d.dept_id;
-- ALL rows both sides| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
| Dave Brown | NULL |
| NULL | Finance |
5 rows โ everyone included ยท NULLs where no match
| Join Type | Dave? | Finance? | Rows |
|---|---|---|---|
| INNER JOIN | โ No | โ No | 3 |
| LEFT JOIN | โ Yes (NULL dept) | โ No | 4 |
| RIGHT JOIN | โ No | โ Yes (NULL emp) | 4 |
| FULL OUTER JOIN | โ Yes | โ Yes | 5 |
In SQL, different types of joins enable the combination of rows from two or more tables.
The difference between INNER JOIN and OUTER JOIN in SQL is that INNER JOIN returns only matching rows, while OUTER JOIN returns both matching and non-matching rows with NULL values for unmatched columns.INNER JOIN combines rows from two or more tables based on a related column, returning only the rows with matching values in both tables.
OUTER JOIN retrieves rows from two or more tables, including unmatched rows from one table while pairing them with corresponding rows from the other table.
NULL values are returned for columns from the missing table, If no match is found.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
The difference between INNER JOIN and OUTER JOIN in SQL is that INNER JOIN returns only matching rows, while OUTER JOIN returns both matching and non-matching rows with NULL values for unmatched columns.
A self-join in SQL is a technique where a table joins with itself to compare rows within that same table.
Self-join is particularly useful when the table has a foreign key that references its own primary key, effectively creating a relationship within the table.
For example, consider a table named 'Employees' with columns for 'EmployeeID', 'Name', and 'ManagerID', where 'ManagerID' is a foreign key to the 'EmployeeID' of the manager.
To list each employee along with their manager's name, a self-join is necessary.The SQL query would join the 'Employees' table to itself.
It would compare the 'ManagerID' column of one instance of the 'Employees' table to the 'EmployeeID' column of another instance.
The query effectively creates two versions of the 'Employees' table: one representing the employees and the other representing their managers.
This allows for the retrieval of both the employee's and the manager's names in the same query result.
This example illustrates how self-joins are essential for querying hierarchical data stored in a single table.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMPLOYEE | MANAGER_NAME |
|---|---|
| Bob Smith | Alice Johnson |
| Carol White | Alice Johnson |
| Dave Brown | Bob Smith |
3 rows โ employees table joined to itself to get manager name
A self-join in SQL is a technique where a table joins with itself to compare rows within that same table.
Lateral joins in SQL enable a subquery in the FROM clause to refer to columns of preceding tables.
Lateral joins are particularly useful when working with array elements or set-returning functions.
A common use case of lateral joins is when dealing with JSON data or complex nested structures.
Lateral joins allow for an efficient unpacking and querying of these structures in such cases.For example, in a database storing product information in a JSON format, a lateral join can extract and work with specific elements from the JSON arrays.
This proves essential in transforming JSON data into a relational format for further analysis.
Lateral joins thus offer a powerful tool for handling advanced data structures in SQL, making them indispensable in modern database operations where complex data types are prevalent.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
Lateral joins in SQL enable a subquery in the FROM clause to refer to columns of preceding tables.
JOIN is used to combine rows from two or more tables based on a related column between them.
Oracle supports multiple JOIN types.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
SELECT e.emp_name, d.dept_name
FROM employees e
INNER JOIN departments d ON e.dept_id = d.dept_id;
-- Only rows matching on BOTH sides| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
3 rows โ only rows with matching dept_id in both tables
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
SELECT e.emp_name, d.dept_name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.dept_id;
-- ALL employees returned| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
| Dave Brown | NULL |
4 rows โ Dave included with NULL dept ยท Finance excluded
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
SELECT e.emp_name, d.dept_name
FROM employees e
RIGHT JOIN departments d ON e.dept_id = d.dept_id;
-- ALL departments returned| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Carol White | Engineering |
| Bob Smith | Marketing |
| NULL | Finance |
4 rows โ Finance included with NULL emp ยท Dave excluded
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
SELECT e.emp_name, d.dept_name
FROM employees e
FULL OUTER JOIN departments d ON e.dept_id = d.dept_id;
-- ALL rows both sides| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
| Dave Brown | NULL |
| NULL | Finance |
5 rows โ everyone included ยท NULLs where no match
| Join Type | Dave? | Finance? | Rows |
|---|---|---|---|
| INNER JOIN | โ No | โ No | 3 |
| LEFT JOIN | โ Yes (NULL dept) | โ No | 4 |
| RIGHT JOIN | โ No | โ Yes (NULL emp) | 4 |
| FULL OUTER JOIN | โ Yes | โ Yes | 5 |
JOIN is used to combine rows from two or more tables based on a related column between them.
A CROSS JOIN returns the Cartesian product of two tables, pairing every row from the first table with every row from the second table.
Unlike INNER or OUTER joins, a CROSS JOIN has no join condition and does not filter rows based on matching column values.
The number of rows returned equals the number of rows in the first table multiplied by the number of rows in the second table.
CROSS JOIN is rarely used directly in business queries but is useful for generating combinations, such as pairing every product with every region for a report.
SELECT e.emp_name, d.dept_name
FROM employees e
CROSS JOIN departments d;| emp_name |
|---|
| Alice Johnson |
| Bob Smith |
Data state before the statement executes.
| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Alice Johnson | Marketing |
| Bob Smith | Engineering |
| Bob Smith | Marketing |
2 employees ร 2 departments = 4 result rows, with no matching condition applied
CROSS JOIN produces every possible row combination between two tables with no matching condition, resulting in rows equal to the product of both row counts.
A FULL OUTER JOIN returns all rows from both tables, matching rows where the join condition is satisfied and filling in NULLs for columns where no match exists on either side.
It combines the behavior of a LEFT OUTER JOIN and a RIGHT OUTER JOIN into a single result set.
FULL OUTER JOIN is useful for reconciliation tasks, such as comparing two datasets to find records that exist in one but not the other.
In Oracle, FULL OUTER JOIN can be written with ANSI syntax using the FULL JOIN keywords, or simulated using a UNION of LEFT and RIGHT joins in older syntax.
| emp_id | emp_name | dept_id |
|---|---|---|
| 101 | Alice Johnson | 10 |
| 102 | Bob Smith | 99 |
SELECT e.emp_name, d.dept_name
FROM employees e
FULL OUTER JOIN departments d
ON e.dept_id = d.dept_id;| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
Data state before the statement executes.
| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Bob Smith | NULL |
| NULL | Marketing |
Bob Smith's dept_id 99 has no matching department, and Marketing has no matching employee โ both still appear with NULLs
FULL OUTER JOIN returns all matched and unmatched rows from both tables, making it useful for finding mismatches between two datasets.
The use of the GROUP BY clause in SQL is to aggregate data into groups based on one or more columns.
This clause works in conjunction with aggregate functions like COUNT, SUM, AVG, MAX, and MIN.
It groups the result set into subsets that have matching values in specified columns, enabling efficient organization and analysis of data.
The GROUP BY clause is essential when users need to calculate aggregate data across different categories or groups within a database.
For example, it aggregates sales data by region to calculate total sales per region.
The clause effectively summarizes large datasets by common attributes, facilitating clearer insights and decision-making in database management and analysis.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT department_id,
COUNT(*) AS emp_count,
AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
HAVING COUNT(*) > 1
ORDER BY avg_salary DESC;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| DEPT_ID | EMP_COUNT | AVG_SALARY |
|---|---|---|
| 20 | 2 | 66,000 |
1 group โ only dept 20 has AVG salary > 60,000 (dept 10 avg = 52,500)
The use of the GROUP BY clause in SQL is to aggregate data into groups based on one or more columns.
The difference between the HAVING and WHERE clause in SQL lies in their distinct functions and stages of data filtering in a query.
The WHERE clause filters rows based on a specified condition before any groupings are made.
It applies to individual rows and is used before data is grouped using the GROUP BY clause.
On the other hand, the HAVING clause filters grouped data.
It operates on aggregated data, meaning it is used after the GROUP BY clause has been applied.
This makes the HAVING clause suitable for conditions that involve aggregate functions, like SUM, AVG, COUNT, etc., filtering groups based on the result of these functions.
In essence, the WHERE clause eliminates rows that do not meet the condition before grouping, while the HAVING clause eliminates groups that do not meet the condition after grouping.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- WHERE filters rows BEFORE grouping
SELECT department_id, AVG(salary) AS avg_sal
FROM employees
WHERE salary > 30000
GROUP BY department_id
HAVING AVG(salary) > 55000;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| DEPT_ID | EMP_COUNT | AVG_SALARY |
|---|---|---|
| 20 | 2 | 66,000 |
1 group โ only dept 20 has AVG salary > 60,000 (dept 10 avg = 52,500)
The difference between the HAVING and WHERE clause in SQL lies in their distinct functions and stages of data filtering in a query.
Aggregate functions in SQL are essential operations used to perform calculations on groups of rows in a database table.
Aggregate functions include commonly used operations such as calculating sums, averages, counts, and finding minimum or maximum values within a specific dataset.
They are crucial for summarizing and deriving meaningful insights from large datasets in SQL.
Aggregate functions are applied using SELECT statements in SQL queries to obtain valuable statistics and metrics from the database.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
Aggregate functions in SQL are essential operations used to perform calculations on groups of rows in a database table.
IN checks whether a column's value matches any value in a specified list, acting as a shorthand for multiple OR conditions.
BETWEEN checks whether a value falls within an inclusive range, equivalent to combining a greater-than-or-equal and less-than-or-equal condition.
LIKE performs pattern matching on string values using wildcard characters, where percent sign matches any sequence of characters and underscore matches exactly one character.
All three operators can be negated using NOT IN, NOT BETWEEN, and NOT LIKE respectively, and all are commonly used inside a WHERE clause.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 30 | 55,000 |
SELECT emp_name, salary
FROM employees
WHERE dept_id IN (10, 20)
AND salary BETWEEN 45000 AND 60000
AND emp_name LIKE 'A%';| EMP_NAME | SALARY |
|---|---|
| Alice Johnson | 50,000 |
Only Alice Johnson matches all three conditions: dept_id in (10,20), salary in range, and name starting with 'A'
IN matches against a list of values, BETWEEN matches an inclusive range, and LIKE matches a string pattern using percent and underscore wildcards.
ROLLUP generates subtotal rows in addition to the normal grouped rows, producing a hierarchy of subtotals that culminates in a grand total row.
CUBE generates subtotals for every possible combination of the grouping columns, not just a hierarchical rollup, which produces more summary rows than ROLLUP.
Both are extensions to the GROUP BY clause and are commonly used in reporting queries that need subtotals alongside detail rows.
The resulting NULL values in subtotal rows can be labeled meaningfully using Oracle's GROUPING function or the GROUPING_ID function.
| region | product | amount |
|---|---|---|
| East | Widget | 1000 |
| East | Gadget | 1500 |
| West | Widget | 800 |
SELECT region, product, SUM(amount) AS total
FROM sales
GROUP BY ROLLUP(region, product);| REGION | PRODUCT | TOTAL |
|---|---|---|
| East | Widget | 1000 |
| East | Gadget | 1500 |
| East | NULL | 2500 |
| West | Widget | 800 |
| West | NULL | 800 |
| NULL | NULL | 3300 |
Rows with NULL product show a region subtotal, and the final NULL/NULL row is the grand total
ROLLUP produces hierarchical subtotals ending in a grand total, while CUBE produces subtotals for every combination of the grouping columns.
A subquery in SQL is a query nested inside a larger query.
A subquery operates within the context of the main query, returning data used for further operations by the outer query.
Subqueries enhance the flexibility of SQL queries, allowing for more complex data retrieval.
They typically appear in the WHERE, HAVING, or SELECT clauses.
Subqueries return a single value, multiple values, or a table, depending on their structure and the context in which they are used.
The execution of a subquery happens before the main query, providing the necessary data for the main query to process.
If a subquery returns multiple values, it often pairs with operators like IN, EXISTS, ANY, or ALL to evaluate conditions in the main query.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- Scalar subquery
SELECT employee_name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_NAME | SALARY |
|---|---|
| Bob Smith | 62,000 |
| Carol White | 55,000 |
| Dave Brown | 70,000 |
3 rows โ salary > average (59,250). Alice Johnson (50,000) excluded โ equals avg, not greater
A subquery in SQL is a query nested inside a larger query.
Common Table Expressions (CTEs) are temporary result sets that simplify complex joins and subqueries in SQL.
CTEs provide a way to create a temporary named result set, which is available only within the execution scope of a single SELECT, INSERT, UPDATE, or DELETE statement.
CTEs improve readability and maintenance of complex queries by breaking them into simpler, reusable parts.
You would use CTEs in situations where you need to reference the result set multiple times within a single query, or when dealing with hierarchical data structures.
They are particularly useful in recursive queries, which are essential for dealing with hierarchical or tree-structured data.
CTEs offer a more readable and organized approach compared to derived tables or subqueries, especially in queries that involve multiple levels of data extraction and manipulation.
They ensure better performance and readability in complex SQL queries, making them a preferred choice for SQL developers and database administrators.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
WITH dept_avg AS (
SELECT department_id, AVG(salary) AS avg_sal
FROM employees GROUP BY department_id
)
SELECT e.employee_name, e.salary
FROM employees e JOIN dept_avg d ON e.department_id = d.department_id
WHERE e.salary > d.avg_sal;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
3 rows โ only rows with matching dept_id in both tables (Dave Brown excluded โ no dept)
Common Table Expressions (CTEs) are temporary result sets that simplify complex joins and subqueries in SQL.
Correlated subqueries in SQL are subqueries that reference columns from the outer query.
They differ from regular subqueries because regular subqueries are independent of the outer query and are executed first, returning a single result to be used by the outer query.
While correlated subqueries are executed once for each row processed by the outer query, making them dependent on the outer query's context.
This allows correlated subqueries to filter results based on conditions from the outer query, making them particularly useful for complex filtering and comparisons in SQL queries.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_NAME | SALARY | DEPT_AVG |
|---|---|---|
| Dave Brown | 70,000 | 66,000 |
| Bob Smith | 62,000 | 66,000 |
2 rows โ employees earning above their own department average
Correlated subqueries in SQL are subqueries that reference columns from the outer query.
Recursive queries in SQL enable the exploration and retrieval of hierarchical data structures, making it a powerful tool in database management.
Recursive queries allow SQL to work with data organized in a tree-like or graph-like manner, such as organizational hierarchies, file systems, or social networks.Traverse and manipulate these hierarchical structures efficiently with recursive queries.
They facilitate tasks like finding all descendants of a given node, determining the path from one node to another, and calculating aggregated values along paths, such as summing up budgets for all child departments within an organization.The primary benefit of using recursive queries is their ability to handle complex relationships within the data.
Instead of relying on multiple queries and application-level logic, SQL recursively navigates through the data, simplifying operations that involve hierarchical structures.
This not only streamlines the code but also improves query performance by minimizing round-trips between the application and the database.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- Oracle CONNECT BY
SELECT LEVEL, employee_name, manager_id
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR employee_id = manager_id;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| LVL | EMP_ID | EMP_NAME | MANAGER_ID |
|---|---|---|---|
| 1 | 105 | CEO | NULL |
| 2 | 101 | Alice Johnson | 105 |
| 2 | 102 | Bob Smith | 105 |
| 3 | 103 | Carol White | 101 |
Recursive CTE/CONNECT BY traverses hierarchy โ LEVEL = depth from root
Recursive queries in SQL enable the exploration and retrieval of hierarchical data structures, making it a powerful tool in database management.
The 'WITH clause' in SQL, also known as a Common Table Expression (CTE), is a powerful feature that allows you to define temporary result sets within a SQL query.
This temporary result set is referenced within the main query, making complex SQL queries more manageable and readable.You start by specifying a CTE name and then define the query that generates the result set.
This result set includes filtering, joining, or aggregating data as needed.
Reference this CTE within the main query once defined, as if it were a table or subquery.The benefits of using the 'WITH clause' are twofold.
It enhances the readability of SQL queries by breaking them down into logical, named parts.
This makes it easier for developers to understand and maintain complex queries.
It also improves query performance since the database engine optimizes the execution plan based on the CTE definition.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
WITH dept_avg AS (
SELECT department_id, AVG(salary) AS avg_sal
FROM employees GROUP BY department_id
)
SELECT e.employee_name, e.salary
FROM employees e JOIN dept_avg d ON e.department_id = d.department_id
WHERE e.salary > d.avg_sal;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
3 rows โ only rows with matching dept_id in both tables (Dave Brown excluded โ no dept)
The 'WITH clause' in SQL, also known as a Common Table Expression (CTE), is a powerful feature that allows you to define temporary result sets within a SQL query.
Use correlated subqueries or self-joins to compare employee salaries with their manager's salaries.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Method 1: Using correlated subquery
SELECT e.employee_id, e.first_name, e.last_name, e.salary, e.manager_id
FROM employees e
WHERE e.salary > (
SELECT m.salary
FROM employees m
WHERE m.employee_id = e.manager_id
);
-- Method 2: Using self-join
SELECT e.employee_id, e.first_name, e.last_name, e.salary as emp_salary,
m.employee_id as mgr_id, m.first_name as mgr_name, m.salary as mgr_salary
FROM employees e
JOIN employees m ON e.manager_id = m.employee_id
WHERE e.salary > m.salary;
-- Method 3: Using window functions (more efficient for large datasets)
SELECT employee_id, first_name, last_name, salary, manager_salary
FROM (
SELECT employee_id, first_name, last_name, salary, manager_id,
LAG(salary) OVER (PARTITION BY manager_id ORDER BY employee_id) as manager_salary
FROM employees
)
WHERE salary > manager_salary;
-- Method 4: Include additional analysis
SELECT e.employee_id, e.first_name, e.salary as emp_salary,
m.salary as mgr_salary,
e.salary - m.salary as salary_difference,
ROUND((e.salary - m.salary) / m.salary * 100, 2) as pct_higher
FROM employees e
JOIN employees m ON e.manager_id = m.employee_id
WHERE e.salary > m.salary
ORDER BY salary_difference DESC;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_NAME | SALARY | DEPT_AVG |
|---|---|---|
| Dave Brown | 70,000 | 66,000 |
| Bob Smith | 62,000 | 66,000 |
2 rows โ employees earning above their own department average
Use correlated subqueries or self-joins to compare employee salaries with their manager's salaries.
EXISTS checks only whether a subquery returns at least one row, stopping as soon as a match is found, and is generally efficient for correlated checks.
IN compares a value against a full list of values returned by a subquery, which means the entire subquery result is typically materialized first.
EXISTS handles NULLs more predictably than NOT IN, since NOT IN can unexpectedly return no rows at all if the subquery result contains any NULL value.
For large subquery result sets, EXISTS often performs better because the optimizer can short-circuit, while IN may require comparing against every value.
| emp_id | emp_name | dept_id |
|---|---|---|
| 101 | Alice Johnson | 10 |
| 102 | Bob Smith | 20 |
SELECT e.emp_name
FROM employees e
WHERE EXISTS (
SELECT 1 FROM departments d WHERE d.dept_id = e.dept_id AND d.dept_name = 'Engineering'
);| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
Data state before the statement executes.
| EMP_NAME |
|---|
| Alice Johnson |
Only Alice Johnson's department (10) matches the EXISTS condition for 'Engineering'
EXISTS only checks for row existence and short-circuits on the first match, while IN compares against a full list of values and can behave unexpectedly with NULLs in NOT IN.
The difference between UNION and UNION ALL commands in SQL lies in how they handle duplicates.
UNION performs a distinct operation, automatically eliminating duplicate rows from the result set.
This means UNION combines the result sets of two or more SELECT statements and removes duplicate rows, providing a distinct list of rows.UNION ALL does not remove duplicates; it simply concatenates the result sets of the SELECT statements.
As a result, UNION ALL is faster than UNION because it does not have to perform the additional step of removing duplicates.
Use UNION when you need a distinct result set, and UNION ALL if duplicates are acceptable or desired in the output.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- UNION: removes duplicates
SELECT emp_id, emp_name FROM current_employees
UNION
SELECT emp_id, emp_name FROM former_employees;
-- UNION ALL: keeps duplicates (faster)
SELECT emp_id, emp_name FROM current_employees
UNION ALL
SELECT emp_id, emp_name FROM former_employees;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SOURCE |
|---|---|---|
| 101 | Alice Johnson | CURRENT |
| 102 | Bob Smith | CURRENT |
| 101 | Alice Johnson | ARCHIVE |
| 102 | Bob Smith | ARCHIVE |
4 rows โ UNION ALL keeps duplicates, faster than UNION
The difference between UNION and UNION ALL commands in SQL lies in how they handle duplicates.
UNION combines the result sets of two or more queries and removes duplicates.
UNION ALL combines result sets but does not remove duplicates.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- UNION: Removes duplicates and sorts results (slower)
SELECT employee_id, first_name FROM employees WHERE department_id = 10
UNION
SELECT employee_id, first_name FROM employees WHERE department_id = 20;
-- UNION ALL: Keeps all rows including duplicates (faster)
SELECT employee_id, first_name FROM employees WHERE department_id = 10
UNION ALL
SELECT employee_id, first_name FROM employees WHERE department_id = 20;
-- Performance comparison
-- UNION performs sorting and duplicate removal
-- UNION ALL is faster as it doesn't sort or remove duplicates
-- Example with different data types (must be compatible)
SELECT employee_id, first_name, 'EMPLOYEE' as record_type FROM employees
UNION ALL
SELECT department_id, department_name, 'DEPARTMENT' as record_type FROM departments;
-- Real-world example: Combining current and archived data
SELECT order_id, customer_id, order_date, 'ACTIVE' as status
FROM current_orders
UNION ALL
SELECT order_id, customer_id, order_date, 'ARCHIVED' as status
FROM archived_orders
ORDER BY order_date DESC;
-- When to use UNION vs UNION ALL:
-- Use UNION when you need unique records across sets
-- Use UNION ALL when you want all records including duplicates
-- UNION ALL is generally preferred for performance unless duplicates must be removed| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SOURCE |
|---|---|---|
| 101 | Alice Johnson | CURRENT |
| 102 | Bob Smith | CURRENT |
| 101 | Alice Johnson | ARCHIVE |
| 102 | Bob Smith | ARCHIVE |
4 rows โ UNION ALL keeps duplicates, faster than UNION
UNION combines the result sets of two or more queries and removes duplicates.
INTERSECT returns only the rows that appear in the result sets of both queries, effectively finding the common rows between two SELECT statements.
MINUS returns rows from the first query's result set that do not appear in the second query's result set, similar to a set subtraction.
Both operators automatically remove duplicate rows from the final result, similar to UNION rather than UNION ALL.
The columns and data types in both SELECT statements must match in number and be compatible in type, just as with UNION.
| emp_id |
|---|
| 101 |
| 102 |
| 103 |
SELECT emp_id FROM current_employees
INTERSECT
SELECT emp_id FROM project_assignments;
SELECT emp_id FROM current_employees
MINUS
SELECT emp_id FROM project_assignments;| emp_id |
|---|
| 102 |
| 103 |
| 104 |
Data state before the statement executes.
| OPERATION | RESULT_EMP_IDS |
|---|---|
| INTERSECT | 102, 103 |
| MINUS | 101 |
INTERSECT finds employees in both lists; MINUS finds employees only in current_employees
INTERSECT returns rows common to both queries, while MINUS returns rows present in the first query but absent from the second, and both deduplicate results automatically.
A view in SQL is a virtual table representing the result of a database query.
It consists of rows and columns, just like a real table.
The fields in a view are fields from one or more real tables in the database.
Views are used to simplify complex queries, encapsulate the complexity of data, and provide a level of security by restricting access to the underlying base tables.
They present data without storing it in a physical format, offering a dynamic approach to querying and manipulating data.
When a user queries a view, the database engine recreates data using the view's SQL statement, thus always presenting the latest data from the base tables.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
CREATE OR REPLACE VIEW v_dept_employees AS
SELECT e.employee_name, e.salary, d.dept_name
FROM employees e JOIN departments d ON e.dept_id = d.dept_id
WHERE e.salary > 40000;
SELECT * FROM v_dept_employees ORDER BY salary DESC;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
3 rows โ only rows with matching dept_id in both tables (Dave Brown excluded โ no dept)
A view in SQL is a virtual table representing the result of a database query.
Materialized views are database objects in SQL that store the results of a query as a physical table.
Materialized views precompute and store the data, making them faster to retrieve, unlike standard views, which are virtual and execute the underlying query each time they are accessed.The key difference between materialized views and standard views is that materialized views contain precomputed data, while standard views are virtual representations of data from underlying tables.
The database engine executes the underlying query in real-time to retrieve the data, when you query a standard view.
A materialized view stores the data in a physical table, so querying it is faster because it avoids the need for real-time computation.Materialized views offer benefits in terms of query performance optimization, especially when dealing with complex and resource-intensive queries.
They reduce the overhead of recalculating the same result set repeatedly by maintaining a snapshot of the data.It's important to note that materialized views come with trade-offs.
They consume storage space and need to be refreshed periodically to ensure that the stored data remains up-to-date with the source data.
This refresh process introduces some latency in accessing the latest data.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
CREATE OR REPLACE VIEW v_dept_employees AS
SELECT e.employee_name, e.salary, d.dept_name
FROM employees e JOIN departments d ON e.dept_id = d.dept_id
WHERE e.salary > 40000;
SELECT * FROM v_dept_employees ORDER BY salary DESC;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
3 rows โ only rows with matching dept_id in both tables (Dave Brown excluded โ no dept)
Materialized views are database objects in SQL that store the results of a query as a physical table.
A view is a virtual table based on the result set of an SQL statement.
It contains rows and columns from one or more tables but doesn't store data physically.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- 1. Simple View
CREATE VIEW v_employee_basic AS
SELECT employee_id, first_name, last_name, email, hire_date
FROM employees;
-- Query the view
SELECT * FROM v_employee_basic WHERE employee_id = 101;
-- 2. Complex View with Joins
CREATE VIEW v_employee_details AS
SELECT e.employee_id,
e.first_name,
e.last_name,
e.email,
e.salary,
d.department_name,
j.job_title,
m.first_name || ' ' || m.last_name as manager_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
LEFT JOIN jobs j ON e.job_id = j.job_id
LEFT JOIN employees m ON e.manager_id = m.employee_id;
-- 3. View with Calculations
CREATE VIEW v_employee_summary AS
SELECT department_id,
COUNT(*) as employee_count,
AVG(salary) as avg_salary,
MIN(salary) as min_salary,
MAX(salary) as max_salary,
SUM(salary) as total_salary
FROM employees
GROUP BY department_id;
-- 4. Updatable View
CREATE VIEW v_emp_updatable AS
SELECT employee_id, first_name, last_name, email, salary
FROM employees
WHERE department_id = 10
WITH CHECK OPTION;
-- Update through view
UPDATE v_emp_updatable
SET salary = salary * 1.1
WHERE employee_id = 101;
-- 5. View with Column Aliases
CREATE VIEW v_emp_formatted (emp_id, full_name, annual_salary, hire_year) AS
SELECT employee_id,
first_name || ' ' || last_name,
salary * 12,
EXTRACT(YEAR FROM hire_date)
FROM employees;
-- 6. Materialized View (for performance)
CREATE MATERIALIZED VIEW mv_dept_summary
BUILD IMMEDIATE
REFRESH FAST ON COMMIT
AS
SELECT d.department_id,
d.department_name,
COUNT(e.employee_id) as emp_count,
AVG(e.salary) as avg_salary
FROM departments d
LEFT JOIN employees e ON d.department_id = e.department_id
GROUP BY d.department_id, d.department_name;
-- 7. View with Security (Row-Level Security)
CREATE VIEW v_emp_security AS
SELECT employee_id, first_name, last_name, salary
FROM employees
WHERE department_id = (SELECT department_id
FROM employees
WHERE employee_id = SYS_CONTEXT('APEX$SESSION', 'APP_USER'));
-- 8. Instead-Of Trigger for Complex Views
CREATE OR REPLACE VIEW v_emp_dept_complex AS
SELECT e.employee_id,
e.first_name,
e.last_name,
d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id;
CREATE OR REPLACE TRIGGER trg_v_emp_dept_insert
INSTEAD OF INSERT ON v_emp_dept_complex
FOR EACH ROW
BEGIN
INSERT INTO employees (employee_id, first_name, last_name, department_id)
VALUES (:NEW.employee_id, :NEW.first_name, :NEW.last_name,
(SELECT department_id FROM departments WHERE department_name = :NEW.department_name));
END;
/
-- View Management
-- Drop view
DROP VIEW v_employee_basic;
-- Replace view
CREATE OR REPLACE VIEW v_employee_basic AS
SELECT employee_id, first_name, last_name, email, phone_number
FROM employees;
-- View metadata
SELECT view_name, text
FROM user_views
WHERE view_name LIKE 'V_EMP%';
-- Check if view is updatable
SELECT table_name, column_name, updatable, insertable, deletable
FROM user_updatable_columns
WHERE table_name = 'V_EMP_UPDATABLE';| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMPLOYEE | MANAGER |
|---|---|
| Bob Smith | Alice Johnson |
| Carol White | Alice Johnson |
| Dave Brown | Bob Smith |
3 rows โ self join pairs each employee with their manager's name
A view is a virtual table based on the result set of an SQL statement.
An index in SQL is a database structure that improves the speed of data retrieval operations on a database table.
It functions similarly to an index in a book, allowing quick access to specific information without scanning every page.
The creation of an index involves using one or more columns of a table, known as keys.
This process sorts data within the table, making data retrieval more efficient for queries using these keys.Indexes are particularly beneficial for enhancing performance in large tables.
Indexes facilitate faster query results, especially for operations involving searching, sorting, and joining data.
They do require additional storage space and impact the performance of data modification operations like INSERT, UPDATE, and DELETE.
The appropriate use of indexes is crucial in database optimization, ensuring a balance between retrieval speed and data modification efficiency.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Create index
CREATE INDEX idx_emp_dept ON employees(department_id);
-- Check execution plan
EXPLAIN PLAN FOR
SELECT * FROM employees WHERE department_id = 10;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_ID | EMP_NAME | DEPT_ID | SALARY |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 103 | Carol White | 10 | 55,000 |
2 rows โ Bob Smith, Dave Brown excluded by WHERE clause
An index in SQL is a database structure that improves the speed of data retrieval operations on a database table.
The difference between clustered and non-clustered indexes in SQL lies in their structure and data storage method.
Clustered indexes sort and store data rows in the table based on their key values.
Each table has only one clustered index, as the data rows themselves are sorted and stored in order of the clustered index key.
On the other hand, non-clustered indexes create a separate structure within the table.
They contain pointers to the data rows, which are stored in a different order.
A table is able to have multiple non-clustered indexes, allowing for more flexible data retrieval.
Non-clustered indexes improve performance for queries that do not modify the data, as they provide rapid access to data rows without rearranging the table itself.
They require additional storage space and slow down data modification operations, such as insert, update, or delete, due to the need to update the index pointers.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Create index
CREATE INDEX idx_emp_dept ON employees(department_id);
-- Check execution plan
EXPLAIN PLAN FOR
SELECT * FROM employees WHERE department_id = 10;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_ID | EMP_NAME | DEPT_ID | SALARY |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 103 | Carol White | 10 | 55,000 |
2 rows โ Bob Smith, Dave Brown excluded by WHERE clause
The difference between clustered and non-clustered indexes in SQL lies in their structure and data storage method.
Implementing indexing in a large database involves creating indexes on columns that are frequently used in search conditions, JOIN operations, or as part of WHERE clauses.
Indexing significantly enhances query performance by reducing the amount of data the database engine needs to scan.
Consider the frequency of the query types and the specific columns involved in these queries, when determining which columns to index.
Opt for composite indexes if multiple columns are often used together in queries.Ensure the database maintains index efficiency by periodically reviewing and updating the indexes based on query patterns and data changes.
Avoid over-indexing as it leads to increased storage requirements and slower data modification operations.
Index maintenance is crucial, especially in dynamic databases where data insertion, updating, and deletion are frequent.
Implement indexing carefully in large databases, ensuring it aligns with the database's usage patterns and query requirements.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Create index
CREATE INDEX idx_emp_dept ON employees(department_id);
-- Check execution plan
EXPLAIN PLAN FOR
SELECT * FROM employees WHERE department_id = 10;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_ID | EMP_NAME | DEPT_ID | SALARY |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 103 | Carol White | 10 | 55,000 |
2 rows โ Bob Smith, Dave Brown excluded by WHERE clause
Implementing indexing in a large database involves creating indexes on columns that are frequently used in search conditions, JOIN operations, or as part of WHERE clauses.
Advanced indexing techniques such as bitmap indexes and partial indexes optimize query performance.
Bitmap indexes are ideal for columns with a low cardinality, where the number of distinct values is small compared to the number of rows in the table.
They store the existence of a value in a compact bitmap form, which speeds up operations like equality joins, count, and group by on these columns.
Partial indexes, on the other hand, index only a subset of rows in a table.
They are particularly useful for large tables where queries frequently target a specific subset of rows.
By indexing only the relevant rows, partial indexes reduce storage requirements and improve query performance.
Implement these indexes to enhance efficiency in scenarios where the query pattern is consistent and predictable.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Create index
CREATE INDEX idx_emp_dept ON employees(department_id);
-- Check execution plan
EXPLAIN PLAN FOR
SELECT * FROM employees WHERE department_id = 10;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_ID | EMP_NAME | DEPT_ID | SALARY |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 103 | Carol White | 10 | 55,000 |
2 rows โ Bob Smith, Dave Brown excluded by WHERE clause
Advanced indexing techniques such as bitmap indexes and partial indexes optimize query performance.
Use the CREATE INDEX statement to create an index on one or more columns to improve query performance.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Create a simple index on a single column
CREATE INDEX idx_employee_last_name ON employees(last_name);
-- Create a composite index on multiple columns
CREATE INDEX idx_emp_dept_salary ON employees(department_id, salary);
-- Create a unique index
CREATE UNIQUE INDEX idx_employee_email ON employees(email);
-- Create a function-based index
CREATE INDEX idx_emp_upper_name ON employees(UPPER(last_name));
-- Create a partial index with WHERE clause (Oracle 12c+)
CREATE INDEX idx_active_employees ON employees(employee_id)
WHERE status = 'ACTIVE';
-- Create index with specific options
CREATE INDEX idx_emp_hire_date ON employees(hire_date)
TABLESPACE users
PCTFREE 10
STORAGE (INITIAL 1M NEXT 1M);
-- Check index usage
SELECT index_name, table_name, column_name, column_position
FROM user_ind_columns
WHERE table_name = 'EMPLOYEES'
ORDER BY index_name, column_position;
-- Monitor index usage
SELECT index_name, table_name, monitoring, used
FROM v$object_usage
WHERE table_name = 'EMPLOYEES';
-- Enable monitoring for an index
ALTER INDEX idx_employee_last_name MONITORING USAGE;
-- Drop an index
DROP INDEX idx_employee_last_name;
-- Rebuild an index
ALTER INDEX idx_emp_dept_salary REBUILD;
-- Create bitmap index (for low cardinality columns)
CREATE BITMAP INDEX idx_emp_gender ON employees(gender);| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_ID | EMP_NAME | DEPT_ID | SALARY |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 103 | Carol White | 10 | 55,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 104 | Dave Brown | 20 | 70,000 |
All 4 rows returned โ ordered by salary ASC
Use the CREATE INDEX statement to create an index on one or more columns to improve query performance.
A composite index is built on two or more columns together, stored as a single index structure ordered by the combination of those columns.
It is most effective when queries frequently filter or sort using the same combination of columns, especially when the leading column is highly selective.
Column order in a composite index matters: Oracle can use the index efficiently for queries that filter on the leading column, but not necessarily for queries that only filter on a trailing column.
Composite indexes can reduce the need for multiple single-column indexes and can sometimes allow Oracle to satisfy a query directly from the index without touching the table, known as an index-only access.
| emp_id | dept_id | hire_date |
|---|---|---|
| 101 | 10 | 2021-03-01 |
| 102 | 10 | 2022-06-15 |
| 103 | 20 | 2020-11-20 |
CREATE INDEX idx_emp_dept_hire
ON employees (dept_id, hire_date);
SELECT emp_id
FROM employees
WHERE dept_id = 10
AND hire_date > DATE '2021-01-01';| EMP_ID |
|---|
| 101 |
| 102 |
The composite index on (dept_id, hire_date) supports this exact filter combination efficiently
A composite index covers multiple columns in a fixed order, and is most useful when queries consistently filter or sort by that same column combination, with the leading column being the most selective.
A function-based index stores the precomputed result of a function or expression applied to one or more columns, rather than the raw column values.
It allows Oracle to use an index efficiently even when a query applies a function to a column in the WHERE clause, such as UPPER(last_name).
Without a function-based index, applying a function to an indexed column in a WHERE clause typically prevents Oracle from using a regular index on that column.
Function-based indexes require the QUERY_REWRITE_ENABLED and appropriate optimizer settings, and the query must use the exact same expression for the index to be considered.
| emp_id | emp_name |
|---|---|
| 101 | Alice Johnson |
| 102 | bob smith |
CREATE INDEX idx_emp_name_upper
ON employees (UPPER(emp_name));
SELECT emp_id
FROM employees
WHERE UPPER(emp_name) = 'BOB SMITH';| EMP_ID |
|---|
| 102 |
The function-based index on UPPER(emp_name) allows this case-insensitive lookup to use an index instead of a full table scan
A function-based index precomputes an expression like UPPER(column) so Oracle can still use an index even when that exact expression appears in a WHERE clause.
The difference between DELETE and TRUNCATE commands is that DELETE removes rows from a table based on a specified condition, while TRUNCATE is a Data Definition Language (DDL) command that quickly removes all rows from a table without logging the deletion of individual rows.
DELETE command logs individual row deletions, which makes it a slower process, especially in large tables.
DELETE operation consumes more transaction log space and permits the use of WHERE clause to specify which rows to remove.TRUNCATE resets the table to its empty state, providing a faster method for deleting all records, especially in large tables.
TRUNCATE also deallocates the data pages used by the table, resulting in the freeing of space for the database but It does not allow for conditional deletions as it does not support the WHERE clause.
Use TRUNCATE when the entire table needs to be emptied without the need for a transaction log for each row deletion.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| COMMAND | CONDITION | ROWS_AFFECTED | ROLLBACK | SPEED |
|---|---|---|---|---|
| DELETE FROM employees WHERE dept_id=10 | dept=10 | 2 rows | โ Yes | Slow |
| DELETE FROM employees | no filter | 4 rows | โ Yes | Slow |
| TRUNCATE TABLE employees | N/A | 4 rows | โ No | Fast |
TRUNCATE is fastest but irreversible โ DELETE is flexible and rollbackable
The difference between DELETE and TRUNCATE commands is that DELETE removes rows from a table based on a specified condition, while TRUNCATE is a Data Definition Language (DDL) command that quickly removes all rows from a table without logging the deletion of individual rows.
DROP removes the entire table structure and data, DELETE removes specific rows with WHERE conditions and can be rolled back, TRUNCATE removes all rows quickly but cannot be rolled back.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- CREATE sample table for demonstration
CREATE TABLE employees_demo (
employee_id NUMBER PRIMARY KEY,
first_name VARCHAR2(50),
last_name VARCHAR2(50),
salary NUMBER,
department_id NUMBER
);
-- Insert sample data
INSERT ALL
INTO employees_demo VALUES (1, 'John', 'Doe', 50000, 10)
INTO employees_demo VALUES (2, 'Jane', 'Smith', 60000, 20)
INTO employees_demo VALUES (3, 'Bob', 'Johnson', 55000, 10)
INTO employees_demo VALUES (4, 'Alice', 'Brown', 65000, 30)
INTO employees_demo VALUES (5, 'Tom', 'Wilson', 52000, 20)
SELECT * FROM dual;
-- Verify data
SELECT * FROM employees_demo;
-- ========================================
-- DELETE COMMAND
-- ========================================
-- Removes specific rows based on WHERE condition
-- Can also remove ALL rows if no WHERE clause is used
-- Can be rolled back
-- Slower for large datasets
-- Triggers fire for each deleted row
-- Delete specific employees
DELETE FROM employees_demo WHERE department_id = 10;
-- Removes employees with department_id = 10
-- Check remaining data
SELECT * FROM employees_demo;
-- Rollback is possible
ROLLBACK;
-- Data is restored
SELECT * FROM employees_demo;
-- Delete with more complex conditions
DELETE FROM employees_demo
WHERE salary < 55000 AND department_id = 20;
-- Delete ALL rows from table (without WHERE clause)
DELETE FROM employees_demo;
-- This removes ALL data but keeps table structure
-- Same result as TRUNCATE but slower and can be rolled back
-- ========================================
-- TRUNCATE COMMAND
-- ========================================
-- Removes ALL rows from table
-- Cannot be rolled back (DDL command)
-- Much faster than DELETE for large tables
-- Resets high water mark
-- Does not fire triggers
-- Restore data first
ROLLBACK;
INSERT ALL
INTO employees_demo VALUES (1, 'John', 'Doe', 50000, 10)
INTO employees_demo VALUES (2, 'Jane', 'Smith', 60000, 20)
INTO employees_demo VALUES (3, 'Bob', 'Johnson', 55000, 10)
SELECT * FROM dual;
-- Truncate table (removes all data)
TRUNCATE TABLE employees_demo;
-- Check table - no data but structure exists
SELECT * FROM employees_demo;
DESC employees_demo;
-- Cannot rollback TRUNCATE
ROLLBACK; -- This won't restore data
-- ========================================
-- DROP COMMAND
-- ========================================
-- Removes entire table structure and data
-- Cannot be rolled back
-- Frees up storage space completely
-- Removes indexes, triggers, constraints
-- Recreate table with data
CREATE TABLE employees_demo (
employee_id NUMBER PRIMARY KEY,
first_name VARCHAR2(50),
last_name VARCHAR2(50),
salary NUMBER,
department_id NUMBER
);
INSERT INTO employees_demo VALUES (1, 'John', 'Doe', 50000, 10);
-- Drop the entire table
DROP TABLE employees_demo;
-- Table no longer exists
-- SELECT * FROM employees_demo; -- This would cause error
-- Check if table exists
SELECT table_name FROM user_tables WHERE table_name = 'EMPLOYEES_DEMO';
-- ========================================
-- COMPARISON TABLE
-- ========================================
/*
+------------------+------------------+------------------+------------------+
| ASPECT | DELETE | TRUNCATE | DROP |
+------------------+------------------+------------------+------------------+
| Purpose | Remove specific | Remove all rows | Remove entire |
| | rows OR all rows | | table |
+------------------+------------------+------------------+------------------+
| WHERE Clause | Yes (optional) | No | No |
| | Without WHERE = | | |
| | deletes all rows | | |
+------------------+------------------+------------------+------------------+
| Rollback | Yes (DML) | No (DDL) | No (DDL) |
+------------------+------------------+------------------+------------------+
| Speed | Slower | Very Fast | Fast |
| | (even for all | | |
| | rows) | | |
+------------------+------------------+------------------+------------------+
| Table Structure | Preserved | Preserved | Removed |
+------------------+------------------+------------------+------------------+
| Triggers | Fire | Don't Fire | Removed |
+------------------+------------------+------------------+------------------+
| Indexes | Preserved | Preserved | Removed |
+------------------+------------------+------------------+------------------+
| Storage Space | Not released | Released | Completely freed |
+------------------+------------------+------------------+------------------+
| Transaction Log | Fully logged | Minimally logged | Minimally logged |
+------------------+------------------+------------------+------------------+
| Identity/Sequence| Continues | Resets | Removed |
+------------------+------------------+------------------+------------------+
*/
-- ========================================
-- PRACTICAL EXAMPLES
-- ========================================
-- Create table for examples
CREATE TABLE sales_data (
sale_id NUMBER,
product_name VARCHAR2(100),
sale_amount NUMBER,
sale_date DATE
);
-- Create sequence
CREATE SEQUENCE sales_seq START WITH 1;
-- Create trigger
CREATE OR REPLACE TRIGGER sales_audit_trg
AFTER DELETE ON sales_data
FOR EACH ROW
BEGIN
INSERT INTO audit_log VALUES (:OLD.sale_id, 'DELETED', SYSDATE);
END;
/
-- Insert test data
INSERT INTO sales_data VALUES (sales_seq.NEXTVAL, 'Laptop', 1200, SYSDATE-10);
INSERT INTO sales_data VALUES (sales_seq.NEXTVAL, 'Mouse', 25, SYSDATE-5);
INSERT INTO sales_data VALUES (sales_seq.NEXTVAL, 'Keyboard', 75, SYSDATE-2);
-- Example 1: DELETE with condition
DELETE FROM sales_data WHERE sale_amount < 50;
-- Trigger fires, specific rows removed, can rollback
-- Example 2: TRUNCATE all data
TRUNCATE TABLE sales_data;
-- All data gone, structure remains, triggers don't fire, cannot rollback
-- Example 3: DROP entire table
DROP TABLE sales_data;
-- Everything gone including structure, triggers, indexes
-- ========================================
-- WHEN TO USE EACH COMMAND
-- ========================================
/*
USE DELETE WHEN:
- Need to remove specific rows based on conditions
- Need to remove ALL rows but maintain rollback capability
- Need ability to rollback the operation
- Working with small to medium datasets
- Need triggers to fire for audit/logging
- Need to maintain referential integrity
- Want to delete all data but slower than TRUNCATE (with rollback option)
USE TRUNCATE WHEN:
- Need to remove ALL rows from a table (faster than DELETE)
- Working with large datasets (performance critical)
- Don't need to rollback the operation
- Don't need triggers to fire
- Want to reset table to empty state quickly
- Want to reset sequences/identity columns
USE DROP WHEN:
- Need to completely remove the table
- Table is no longer needed
- Want to free up all storage space
- Need to remove all associated objects (indexes, triggers, etc.)
- Restructuring database schema
*/
-- Clean up demonstration
-- DROP TABLE employees_demo; -- Already dropped above| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| COMMAND | CONDITION | ROWS_AFFECTED | ROLLBACK | SPEED |
|---|---|---|---|---|
| DELETE FROM employees WHERE dept_id=10 | dept=10 | 2 rows | โ Yes | Slow |
| DELETE FROM employees | no filter | 4 rows | โ Yes | Slow |
| TRUNCATE TABLE employees | N/A | 4 rows | โ No | Fast |
TRUNCATE is fastest but irreversible โ DELETE is flexible and rollbackable
DROP removes the entire table structure and data, DELETE removes specific rows with WHERE conditions and can be rolled back, TRUNCATE removes all rows quickly but cannot be rolled back.
ALTER TABLE ADD is used to add a new column to an existing table, optionally with a default value and constraints.
ALTER TABLE MODIFY is used to change a column's data type, size, or constraints, though changes are restricted if the column already contains incompatible data.
ALTER TABLE DROP COLUMN removes a column and its data permanently, which cannot be rolled back once committed in most Oracle versions.
All three operations change the table's structure rather than its data directly, and they require sufficient privileges and can briefly lock the table during execution.
ALTER TABLE employees ADD phone_number VARCHAR2(20);
ALTER TABLE employees MODIFY phone_number VARCHAR2(30);
ALTER TABLE employees DROP COLUMN phone_number;| STATEMENT | RESULT |
|---|---|
| ADD phone_number | Column added, NULL for existing rows |
| MODIFY phone_number | Column size increased to 30 characters |
| DROP COLUMN phone_number | Column and its data permanently removed |
Each ALTER TABLE statement changes the table structure without requiring a full table rebuild from scratch
ALTER TABLE ADD, MODIFY, and DROP COLUMN let you add, change, or remove a column's definition, with DROP COLUMN being a destructive, generally non-reversible structural change.
Use TRUNCATE TABLE for fast deletion of all records, or DELETE FROM for conditional deletion with transaction control.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Method 1: TRUNCATE TABLE (fastest, cannot be rolled back)
TRUNCATE TABLE employees_temp;
-- Method 2: DELETE FROM (slower, can be rolled back)
DELETE FROM employees_temp;
-- Method 3: DELETE with WHERE clause (delete specific records)
DELETE FROM employees_temp WHERE department_id = 10;
-- Method 4: Conditional deletion
DELETE FROM employees_temp
WHERE hire_date < ADD_MONTHS(SYSDATE, -60);
-- Comparison: TRUNCATE vs DELETE
-- Create test table
CREATE TABLE test_employees AS SELECT * FROM employees;
-- Check record count
SELECT COUNT(*) FROM test_employees;
-- TRUNCATE characteristics:
-- 1. Faster than DELETE
-- 2. Resets high water mark
-- 3. Cannot be rolled back
-- 4. Resets SEQUENCE values
-- 5. Removes all rows without logging individual row deletions
TRUNCATE TABLE test_employees;
-- Recreate for DELETE example
INSERT INTO test_employees SELECT * FROM employees;
-- DELETE characteristics:
-- 1. Slower than TRUNCATE
-- 2. Can be rolled back
-- 3. Triggers fire for each row
-- 4. Generates undo information
-- 5. Can have WHERE clause
BEGIN
DELETE FROM test_employees;
-- Can rollback if needed
ROLLBACK;
END;
/
-- Check count after rollback
SELECT COUNT(*) FROM test_employees;
-- Complete deletion with commit
DELETE FROM test_employees;
COMMIT;
-- Method 5: Using MERGE for conditional deletion
CREATE TABLE employees_to_delete (employee_id NUMBER);
INSERT INTO employees_to_delete VALUES (101);
INSERT INTO employees_to_delete VALUES (102);
MERGE INTO test_employees t
USING employees_to_delete d ON (t.employee_id = d.employee_id)
WHEN MATCHED THEN
UPDATE SET first_name = NULL
DELETE WHERE first_name IS NULL;
-- Method 6: Bulk delete with FORALL (PL/SQL)
DECLARE
TYPE emp_id_array IS TABLE OF NUMBER;
emp_ids emp_id_array;
BEGIN
SELECT employee_id BULK COLLECT INTO emp_ids
FROM employees
WHERE department_id = 10;
FORALL i IN emp_ids.FIRST..emp_ids.LAST
DELETE FROM test_employees WHERE employee_id = emp_ids(i);
END;
/
-- Method 7: Delete with EXISTS subquery
DELETE FROM test_employees e1
WHERE EXISTS (
SELECT 1 FROM departments d
WHERE d.department_id = e1.department_id
AND d.department_name = 'Sales'
);
-- Method 8: Delete with JOIN (using EXISTS)
DELETE FROM test_employees e
WHERE EXISTS (
SELECT 1 FROM job_history jh
WHERE jh.employee_id = e.employee_id
AND jh.end_date IS NOT NULL
);
-- Method 9: Partitioned table truncation
-- TRUNCATE PARTITION (for partitioned tables)
-- ALTER TABLE sales_partitioned TRUNCATE PARTITION p_2023;
-- Method 10: Using ROWID for efficient deletion
DELETE FROM test_employees
WHERE ROWID IN (
SELECT ROWID FROM test_employees
WHERE department_id = 20
AND ROWNUM <= 10
);
-- Performance monitoring
-- Check table statistics before and after
SELECT table_name, num_rows, blocks, empty_blocks
FROM user_tables
WHERE table_name = 'TEST_EMPLOYEES';
-- Update statistics after major deletions
BEGIN
DBMS_STATS.GATHER_TABLE_STATS(USER, 'TEST_EMPLOYEES');
END;
/
-- Reclaim space after large deletions
-- ALTER TABLE test_employees SHRINK SPACE;
-- Clean up
DROP TABLE test_employees;
DROP TABLE employees_to_delete;
-- Best practices:
-- 1. Use TRUNCATE for complete table cleanup (faster)
-- 2. Use DELETE for conditional removal or when rollback needed
-- 3. Consider partitioning for large tables with regular cleanup needs
-- 4. Update statistics after major deletions
-- 5. Monitor space usage and consider shrinking tables when needed| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_ID | EMP_NAME | ACTION | RESULT |
|---|---|---|---|
| 101 | Alice Johnson | UPDATED (matched) | Salary โ 55,000 |
| 102 | Bob Smith | UPDATED (matched) | Salary โ 68,200 |
| 999 | New Hire | INSERTED (not matched) | New row added |
MERGE: 2 rows updated + 1 inserted in one atomic statement
Use TRUNCATE TABLE for fast deletion of all records, or DELETE FROM for conditional deletion with transaction control.
INSERT adds new rows into a table, either by specifying literal values or by selecting data from another query.
UPDATE modifies existing rows that match a WHERE condition, changing one or more column values in place.
MERGE combines insert and update logic into a single statement, updating a row if a matching key already exists in the target table, or inserting a new row if it does not โ commonly called an 'upsert'.
All three are Data Manipulation Language statements, meaning they change data rather than the table's structure, and all participate in transactions that can be committed or rolled back.
| emp_id | emp_name | salary |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
INSERT INTO employees (emp_id, emp_name, salary)
VALUES (103, 'Carol White', 55000);
UPDATE employees
SET salary = salary * 1.10
WHERE emp_id = 102;
MERGE INTO employees e
USING (SELECT 101 AS emp_id, 53000 AS salary FROM dual) s
ON (e.emp_id = s.emp_id)
WHEN MATCHED THEN UPDATE SET e.salary = s.salary
WHEN NOT MATCHED THEN INSERT (emp_id, salary) VALUES (s.emp_id, s.salary);| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 53,000 |
| 102 | Bob Smith | 68,200 |
| 103 | Carol White | 55,000 |
Carol White is inserted, Bob Smith's salary is updated by 10%, and the MERGE updates Alice Johnson since emp_id 101 already exists
INSERT adds rows, UPDATE modifies existing rows, and MERGE performs an upsert that inserts new rows or updates matching ones in a single statement.
A stored procedure is a predefined set of SQL commands stored in the database.
Stored procedure allows for complex operations to be executed with a single call, enhancing efficiency and security.
Stored procedures encapsulate logic, which means they separate the complexity of operations from their execution.
They often include conditional statements and loops, enabling dynamic SQL execution based on input parameters.
Stored procedures reduce network traffic and improve performance, as the execution happens entirely on the database server.
They also provide an additional layer of security by restricting direct access and manipulation of tables.
Use stored procedures to automate and standardize database operations, ensuring consistency and reliability in data handling.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
CREATE OR REPLACE PROCEDURE prc_give_raise(
p_dept_id IN NUMBER, p_pct IN NUMBER, p_rows OUT NUMBER
) IS
BEGIN
UPDATE employees SET salary = salary * (1 + p_pct/100)
WHERE department_id = p_dept_id;
p_rows := SQL%ROWCOUNT; COMMIT;
END;
/| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_ID | EMP_NAME | OLD_SALARY | NEW_SALARY |
|---|---|---|---|
| 101 | Alice Johnson | 50,000 | 55,000 |
| 103 | Carol White | 55,000 | 60,500 |
2 rows updated by stored procedure โ 10% raise applied to dept 10
A stored procedure is a predefined set of SQL commands stored in the database.
A trigger is a special type of stored procedure that automatically executes in response to certain events on a particular table or view.
Triggers help in maintaining the integrity of the database by ensuring that certain actions are performed automatically, such as updating or auditing records, when data modification operations like INSERT, UPDATE, or DELETE are performed.
They are particularly useful for enforcing complex business rules and data validation.
Triggers operate invisibly from the client applications, providing a seamless way to implement and enforce business rules at the database level.
The activation of a trigger occurs when specified conditions are met, such as a specific change in a table's data.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
CREATE OR REPLACE TRIGGER trg_emp_before_insert
BEFORE INSERT ON employees FOR EACH ROW
BEGIN
IF :NEW.hire_date IS NULL THEN
:NEW.hire_date := SYSDATE;
END IF;
IF :NEW.salary < 0 THEN
RAISE_APPLICATION_ERROR(-20001,'Salary cannot be negative');
END IF;
END;
/| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY | HIRE_DATE |
|---|---|---|---|
| 205 | New Employee | 50,000 | 20-APR-2026 |
Trigger auto-fired on INSERT โ hire_date set to SYSDATE by BEFORE INSERT trigger
A trigger is a special type of stored procedure that automatically executes in response to certain events on a particular table or view.
A cursor in SQL is a database object used to retrieve and manipulate data from a result set.
Cursor is particularly useful when you need to process rows of data one at a time, sequentially.
Cursors are typically employed in scenarios where you want to perform operations on individual records within a query result.You would use a cursor when you need to iterate through a set of records, row by row, and perform specific actions or calculations on each row.
This is often required in situations where standard SQL statements, like SELECT, UPDATE, or DELETE, are insufficient to accomplish the desired tasks.
Cursors provide a mechanism for fine-grained control over record processing, making them valuable in complex data manipulation scenarios.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
A cursor in SQL is a database object used to retrieve and manipulate data from a result set.
Stored procedures offer improved performance, as they are precompiled and optimized by the database engine.
This optimization reduces the execution time of queries.
Stored procedures enhance security by allowing controlled access to data.
They ensure data integrity by enforcing business rules and constraints within the database.
Stored procedures also promote code reusability, as they are called from various parts of an application.
They simplify maintenance, as changes to the logic are made centrally within the procedure, affecting all dependent code.
Using stored procedures in SQL leads to better performance, security, data integrity, code reusability, and easier maintenance of the database system.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
CREATE OR REPLACE PROCEDURE prc_give_raise(
p_dept_id IN NUMBER, p_pct IN NUMBER, p_rows OUT NUMBER
) IS
BEGIN
UPDATE employees SET salary = salary * (1 + p_pct/100)
WHERE department_id = p_dept_id;
p_rows := SQL%ROWCOUNT; COMMIT;
END;
/| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_ID | EMP_NAME | OLD_SALARY | NEW_SALARY |
|---|---|---|---|
| 101 | Alice Johnson | 50,000 | 55,000 |
| 103 | Carol White | 55,000 | 60,500 |
2 rows updated by stored procedure โ 10% raise applied to dept 10
Stored procedures offer improved performance, as they are precompiled and optimized by the database engine.
Triggers play a crucial role in maintaining database integrity in SQL.
They are special types of stored procedures that automatically execute or 'fire' in response to certain events on a particular table or view in a database.
Triggers help in enforcing business rules and data integrity by automatically checking for certain conditions or changes in database data.
For example, a trigger prevents invalid data entry into a database by automatically reverting changes if the new data violates business rules.Triggers enhance the reliability of a database by maintaining a consistent state.
They ensure that all necessary changes occur as a result of data modifications.
This functionality becomes particularly valuable in complex databases where multiple interrelated actions need to occur in response to a single event.
Execute complex cascading actions seamlessly with triggers, provided the triggering event occurs.
This approach allows for the automation of routine tasks, thereby reducing the likelihood of human error and maintaining the overall integrity and reliability of the database.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
CREATE OR REPLACE TRIGGER trg_emp_before_insert
BEFORE INSERT ON employees FOR EACH ROW
BEGIN
IF :NEW.hire_date IS NULL THEN
:NEW.hire_date := SYSDATE;
END IF;
IF :NEW.salary < 0 THEN
RAISE_APPLICATION_ERROR(-20001,'Salary cannot be negative');
END IF;
END;
/| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY | HIRE_DATE |
|---|---|---|---|
| 205 | New Employee | 50,000 | 20-APR-2026 |
Trigger auto-fired on INSERT โ hire_date set to SYSDATE by BEFORE INSERT trigger
Triggers play a crucial role in maintaining database integrity in SQL.
Approaching error handling in SQL stored procedures involves several key strategies.
Utilize the TRY...CATCH block to capture and manage errors.
This structure allows the execution of code in the TRY block, and if an error occurs, control is passed to the CATCH block where the error can be handled gracefully.
Implementing error logging is crucial, typically by inserting error details into a dedicated table.
This practice enables the tracking and analysis of errors over time.Another important aspect is the use of RAISERROR or THROW statements to generate custom error messages, allowing for more specific and informative feedback about issues encountered in the stored procedure.
Ensure proper transaction management by using COMMIT and ROLLBACK within the TRY...CATCH block.
This ensures that the database remains consistent and any changes made during the procedure are either fully committed or rolled back, depending on the success or failure of the procedure.
Employing these methods ensures robust error handling in SQL stored procedures, enhancing reliability and maintainability of the database application.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
CREATE OR REPLACE PROCEDURE prc_give_raise(
p_dept_id IN NUMBER, p_pct IN NUMBER, p_rows OUT NUMBER
) IS
BEGIN
UPDATE employees SET salary = salary * (1 + p_pct/100)
WHERE department_id = p_dept_id;
p_rows := SQL%ROWCOUNT; COMMIT;
END;
/| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_ID | EMP_NAME | OLD_SALARY | NEW_SALARY |
|---|---|---|---|
| 101 | Alice Johnson | 50,000 | 55,000 |
| 103 | Carol White | 55,000 | 60,500 |
2 rows updated by stored procedure โ 10% raise applied to dept 10
Approaching error handling in SQL stored procedures involves several key strategies.
A stored procedure is a precompiled group of SQL and PL/SQL statements stored in the database.
It can be executed multiple times and can accept parameters.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Create a simple stored procedure
CREATE OR REPLACE PROCEDURE update_employee_salary(
p_employee_id IN NUMBER,
p_percentage IN NUMBER
) IS
v_current_salary NUMBER;
v_new_salary NUMBER;
BEGIN
-- Get current salary
SELECT salary INTO v_current_salary
FROM employees
WHERE employee_id = p_employee_id;
-- Calculate new salary
v_new_salary := v_current_salary * (1 + p_percentage/100);
-- Update salary
UPDATE employees
SET salary = v_new_salary
WHERE employee_id = p_employee_id;
COMMIT;
DBMS_OUTPUT.PUT_LINE('Salary updated from ' || v_current_salary || ' to ' || v_new_salary);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Employee not found');
WHEN OTHERS THEN
ROLLBACK;
DBMS_OUTPUT.PUT_LINE('Error: ' || SQLERRM);
END;
/
-- Procedure with OUT parameter
CREATE OR REPLACE PROCEDURE get_employee_details(
p_employee_id IN NUMBER,
p_name OUT VARCHAR2,
p_salary OUT NUMBER,
p_department OUT VARCHAR2
) IS
BEGIN
SELECT e.first_name || ' ' || e.last_name,
e.salary,
d.department_name
INTO p_name, p_salary, p_department
FROM employees e
JOIN departments d ON e.department_id = d.department_id
WHERE e.employee_id = p_employee_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
p_name := 'Not Found';
p_salary := 0;
p_department := 'N/A';
END;
/
-- Execute stored procedures
BEGIN
update_employee_salary(101, 10); -- Give 10% raise to employee 101
END;
/
-- Execute procedure with OUT parameters
DECLARE
v_name VARCHAR2(100);
v_salary NUMBER;
v_dept VARCHAR2(100);
BEGIN
get_employee_details(101, v_name, v_salary, v_dept);
DBMS_OUTPUT.PUT_LINE('Employee: ' || v_name);
DBMS_OUTPUT.PUT_LINE('Salary: ' || v_salary);
DBMS_OUTPUT.PUT_LINE('Department: ' || v_dept);
END;
/| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EXCEPTION | WHEN_RAISED | HANDLED_BY | OUTCOME |
|---|---|---|---|
| NO_DATA_FOUND | SELECT INTO = 0 rows | WHEN NO_DATA_FOUND | Continue after message |
| TOO_MANY_ROWS | SELECT INTO > 1 row | WHEN OTHERS | Rollback + log |
Exceptions prevent unhandled errors from crashing the PL/SQL block
A stored procedure is a precompiled group of SQL and PL/SQL statements stored in the database.
A trigger is a special type of PL/SQL block that automatically executes (fires) in response to specific database events such as INSERT, UPDATE, or DELETE operations.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- 1. BEFORE INSERT TRIGGER
CREATE OR REPLACE TRIGGER trg_emp_before_insert
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
-- Auto-generate employee ID if not provided
IF :NEW.employee_id IS NULL THEN
SELECT emp_seq.NEXTVAL INTO :NEW.employee_id FROM dual;
END IF;
-- Set hire date to current date if not provided
IF :NEW.hire_date IS NULL THEN
:NEW.hire_date := SYSDATE;
END IF;
-- Convert email to uppercase
:NEW.email := UPPER(:NEW.email);
END;
/
-- 2. AFTER UPDATE TRIGGER
CREATE OR REPLACE TRIGGER trg_emp_after_update
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
-- Log salary changes
IF :OLD.salary != :NEW.salary THEN
INSERT INTO salary_audit_log (
employee_id,
old_salary,
new_salary,
change_date,
changed_by
) VALUES (
:NEW.employee_id,
:OLD.salary,
:NEW.salary,
SYSDATE,
USER
);
END IF;
END;
/
-- 3. BEFORE DELETE TRIGGER
CREATE OR REPLACE TRIGGER trg_emp_before_delete
BEFORE DELETE ON employees
FOR EACH ROW
BEGIN
-- Prevent deletion of employees with high salary
IF :OLD.salary > 100000 THEN
RAISE_APPLICATION_ERROR(-20001,
'Cannot delete high-salary employee: ' || :OLD.first_name || ' ' || :OLD.last_name);
END IF;
-- Archive employee data before deletion
INSERT INTO employees_archive VALUES :OLD;
END;
/
-- 4. STATEMENT-LEVEL TRIGGER
CREATE OR REPLACE TRIGGER trg_emp_statement_audit
AFTER INSERT OR UPDATE OR DELETE ON employees
BEGIN
INSERT INTO table_audit_log (
table_name,
operation,
timestamp,
user_name
) VALUES (
'EMPLOYEES',
CASE
WHEN INSERTING THEN 'INSERT'
WHEN UPDATING THEN 'UPDATE'
WHEN DELETING THEN 'DELETE'
END,
SYSDATE,
USER
);
END;
/
-- 5. COMPOUND TRIGGER (Oracle 11g+)
CREATE OR REPLACE TRIGGER trg_emp_compound
FOR INSERT OR UPDATE OR DELETE ON employees
COMPOUND TRIGGER
-- Declaration section
TYPE emp_id_list_t IS TABLE OF employees.employee_id%TYPE;
l_emp_ids emp_id_list_t := emp_id_list_t();
-- BEFORE STATEMENT
BEFORE STATEMENT IS
BEGIN
DBMS_OUTPUT.PUT_LINE('Starting DML operation on employees table');
END BEFORE STATEMENT;
-- BEFORE EACH ROW
BEFORE EACH ROW IS
BEGIN
IF INSERTING THEN
:NEW.created_date := SYSDATE;
ELSIF UPDATING THEN
:NEW.modified_date := SYSDATE;
END IF;
END BEFORE EACH ROW;
-- AFTER EACH ROW
AFTER EACH ROW IS
BEGIN
l_emp_ids.EXTEND;
l_emp_ids(l_emp_ids.COUNT) := :NEW.employee_id;
END AFTER EACH ROW;
-- AFTER STATEMENT
AFTER STATEMENT IS
BEGIN
DBMS_OUTPUT.PUT_LINE('Processed ' || l_emp_ids.COUNT || ' employees');
-- Bulk processing of collected employee IDs
FOR i IN 1..l_emp_ids.COUNT LOOP
-- Update related tables or perform batch operations
NULL;
END LOOP;
END AFTER STATEMENT;
END trg_emp_compound;
/
-- 6. DDL TRIGGER
CREATE OR REPLACE TRIGGER trg_ddl_audit
AFTER DDL ON SCHEMA
BEGIN
INSERT INTO ddl_audit_log (
username,
ddl_date,
ddl_type,
object_type,
object_name,
sql_text
) VALUES (
USER,
SYSDATE,
SYS.DICTIONARY_OBJ_TYPE,
SYS.DICTIONARY_OBJ_OWNER,
SYS.DICTIONARY_OBJ_NAME,
SYS.LOGIN_USER
);
END;
/
-- Trigger Management Commands
-- Enable/Disable trigger
ALTER TRIGGER trg_emp_before_insert DISABLE;
ALTER TRIGGER trg_emp_before_insert ENABLE;
-- Drop trigger
DROP TRIGGER trg_emp_before_insert;
-- View trigger information
SELECT trigger_name, table_name, triggering_event, status
FROM user_triggers
WHERE table_name = 'EMPLOYEES';| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | DEPT_ID | SALARY |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | 20 | 70,000 |
All 4 rows returned
A trigger is a special type of PL/SQL block that automatically executes (fires) in response to specific database events such as INSERT, UPDATE, or DELETE operations.
Create triggers that automatically capture DML operations (INSERT, UPDATE, DELETE) and store audit information in separate audit tables.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- Create audit table
CREATE TABLE employee_audit (
audit_id NUMBER GENERATED ALWAYS AS IDENTITY,
table_name VARCHAR2(30),
operation VARCHAR2(10),
employee_id NUMBER,
old_values CLOB,
new_values CLOB,
changed_by VARCHAR2(100),
change_date TIMESTAMP DEFAULT SYSTIMESTAMP,
session_id NUMBER,
terminal VARCHAR2(100),
program VARCHAR2(100)
);
-- Create audit trigger for employees table
CREATE OR REPLACE TRIGGER trg_employee_audit
AFTER INSERT OR UPDATE OR DELETE ON employees
FOR EACH ROW
DECLARE
v_operation VARCHAR2(10);
v_old_values CLOB;
v_new_values CLOB;
BEGIN
-- Determine operation type
IF INSERTING THEN
v_operation := 'INSERT';
v_old_values := NULL;
v_new_values := 'employee_id:' || :NEW.employee_id ||
',first_name:' || :NEW.first_name ||
',last_name:' || :NEW.last_name ||
',email:' || :NEW.email ||
',salary:' || :NEW.salary ||
',department_id:' || :NEW.department_id;
ELSIF UPDATING THEN
v_operation := 'UPDATE';
v_old_values := 'employee_id:' || :OLD.employee_id ||
',first_name:' || :OLD.first_name ||
',last_name:' || :OLD.last_name ||
',email:' || :OLD.email ||
',salary:' || :OLD.salary ||
',department_id:' || :OLD.department_id;
v_new_values := 'employee_id:' || :NEW.employee_id ||
',first_name:' || :NEW.first_name ||
',last_name:' || :NEW.last_name ||
',email:' || :NEW.email ||
',salary:' || :NEW.salary ||
',department_id:' || :NEW.department_id;
ELSIF DELETING THEN
v_operation := 'DELETE';
v_old_values := 'employee_id:' || :OLD.employee_id ||
',first_name:' || :OLD.first_name ||
',last_name:' || :OLD.last_name ||
',email:' || :OLD.email ||
',salary:' || :OLD.salary ||
',department_id:' || :OLD.department_id;
v_new_values := NULL;
END IF;
-- Insert audit record
INSERT INTO employee_audit (
table_name, operation, employee_id, old_values, new_values,
changed_by, session_id, terminal, program
) VALUES (
'EMPLOYEES', v_operation,
COALESCE(:NEW.employee_id, :OLD.employee_id),
v_old_values, v_new_values,
USER, SYS_CONTEXT('USERENV', 'SESSIONID'),
SYS_CONTEXT('USERENV', 'TERMINAL'),
SYS_CONTEXT('USERENV', 'MODULE')
);
END;
/
-- Advanced audit trigger with JSON format
CREATE OR REPLACE TRIGGER trg_employee_audit_json
AFTER INSERT OR UPDATE OR DELETE ON employees
FOR EACH ROW
DECLARE
v_operation VARCHAR2(10);
v_old_json CLOB;
v_new_json CLOB;
BEGIN
IF INSERTING THEN
v_operation := 'INSERT';
v_new_json := JSON_OBJECT(
'employee_id' VALUE :NEW.employee_id,
'first_name' VALUE :NEW.first_name,
'last_name' VALUE :NEW.last_name,
'email' VALUE :NEW.email,
'salary' VALUE :NEW.salary,
'department_id' VALUE :NEW.department_id,
'hire_date' VALUE TO_CHAR(:NEW.hire_date, 'YYYY-MM-DD'),
'manager_id' VALUE :NEW.manager_id
);
ELSIF UPDATING THEN
v_operation := 'UPDATE';
v_old_json := JSON_OBJECT(
'employee_id' VALUE :OLD.employee_id,
'first_name' VALUE :OLD.first_name,
'last_name' VALUE :OLD.last_name,
'email' VALUE :OLD.email,
'salary' VALUE :OLD.salary,
'department_id' VALUE :OLD.department_id,
'hire_date' VALUE TO_CHAR(:OLD.hire_date, 'YYYY-MM-DD'),
'manager_id' VALUE :OLD.manager_id
);
v_new_json := JSON_OBJECT(
'employee_id' VALUE :NEW.employee_id,
'first_name' VALUE :NEW.first_name,
'last_name' VALUE :NEW.last_name,
'email' VALUE :NEW.email,
'salary' VALUE :NEW.salary,
'department_id' VALUE :NEW.department_id,
'hire_date' VALUE TO_CHAR(:NEW.hire_date, 'YYYY-MM-DD'),
'manager_id' VALUE :NEW.manager_id
);
ELSIF DELETING THEN
v_operation := 'DELETE';
v_old_json := JSON_OBJECT(
'employee_id' VALUE :OLD.employee_id,
'first_name' VALUE :OLD.first_name,
'last_name' VALUE :OLD.last_name,
'email' VALUE :OLD.email,
'salary' VALUE :OLD.salary,
'department_id' VALUE :OLD.department_id,
'hire_date' VALUE TO_CHAR(:OLD.hire_date, 'YYYY-MM-DD'),
'manager_id' VALUE :OLD.manager_id
);
END IF;
INSERT INTO employee_audit (
table_name, operation, employee_id, old_values, new_values,
changed_by, session_id, terminal, program
) VALUES (
'EMPLOYEES', v_operation,
COALESCE(:NEW.employee_id, :OLD.employee_id),
v_old_json, v_new_json,
USER, SYS_CONTEXT('USERENV', 'SESSIONID'),
SYS_CONTEXT('USERENV', 'TERMINAL'),
SYS_CONTEXT('USERENV', 'MODULE')
);
END;
/
-- Selective column audit trigger (only track specific changes)
CREATE OR REPLACE TRIGGER trg_employee_salary_audit
AFTER UPDATE OF salary, department_id ON employees
FOR EACH ROW
WHEN (OLD.salary != NEW.salary OR OLD.department_id != NEW.department_id)
BEGIN
INSERT INTO employee_audit (
table_name, operation, employee_id, old_values, new_values, changed_by
) VALUES (
'EMPLOYEES', 'UPDATE', :NEW.employee_id,
'salary:' || :OLD.salary || ',department_id:' || :OLD.department_id,
'salary:' || :NEW.salary || ',department_id:' || :NEW.department_id,
USER
);
END;
/
-- Query audit trail
SELECT audit_id, operation, employee_id, changed_by, change_date,
old_values, new_values
FROM employee_audit
WHERE employee_id = 101
ORDER BY change_date DESC;
-- Audit trail analysis queries
-- Most active users
SELECT changed_by, COUNT(*) as change_count,
COUNT(CASE WHEN operation = 'INSERT' THEN 1 END) as inserts,
COUNT(CASE WHEN operation = 'UPDATE' THEN 1 END) as updates,
COUNT(CASE WHEN operation = 'DELETE' THEN 1 END) as deletes
FROM employee_audit
WHERE change_date >= TRUNC(SYSDATE) - 30
GROUP BY changed_by
ORDER BY change_count DESC;
-- Changes by time period
SELECT TRUNC(change_date) as change_day,
COUNT(*) as total_changes,
COUNT(DISTINCT employee_id) as employees_affected
FROM employee_audit
WHERE change_date >= TRUNC(SYSDATE) - 7
GROUP BY TRUNC(change_date)
ORDER BY change_day;
-- Salary change history for specific employee
SELECT change_date, changed_by,
JSON_VALUE(old_values, '$.salary') as old_salary,
JSON_VALUE(new_values, '$.salary') as new_salary,
JSON_VALUE(new_values, '$.salary') - JSON_VALUE(old_values, '$.salary') as salary_increase
FROM employee_audit
WHERE employee_id = 101
AND operation = 'UPDATE'
AND JSON_EXISTS(old_values, '$.salary')
AND JSON_VALUE(old_values, '$.salary') != JSON_VALUE(new_values, '$.salary')
ORDER BY change_date;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMPLOYEE | MANAGER |
|---|---|
| Bob Smith | Alice Johnson |
| Carol White | Alice Johnson |
| Dave Brown | Bob Smith |
3 rows โ self join pairs each employee with their manager's name
Create triggers that automatically capture DML operations (INSERT, UPDATE, DELETE) and store audit information in separate audit tables.
Create custom aggregate functions using PL/SQL object types with ODCIAggregate interface methods for complex aggregation logic.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- Create object type for custom aggregate
CREATE OR REPLACE TYPE weighted_avg_type AS OBJECT (
total_weighted_value NUMBER,
total_weight NUMBER,
-- Required methods for aggregate interface
STATIC FUNCTION ODCIAggregateInitialize(
ctx IN OUT weighted_avg_type
) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateIterate(
self IN OUT weighted_avg_type,
value IN NUMBER,
weight IN NUMBER
) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateMerge(
self IN OUT weighted_avg_type,
ctx IN weighted_avg_type
) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateTerminate(
self IN weighted_avg_type,
returnValue OUT NUMBER,
flags IN NUMBER
) RETURN NUMBER
);
/
-- Implement the object type body
CREATE OR REPLACE TYPE BODY weighted_avg_type IS
STATIC FUNCTION ODCIAggregateInitialize(
ctx IN OUT weighted_avg_type
) RETURN NUMBER IS
BEGIN
ctx := weighted_avg_type(0, 0);
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateIterate(
self IN OUT weighted_avg_type,
value IN NUMBER,
weight IN NUMBER
) RETURN NUMBER IS
BEGIN
IF value IS NOT NULL AND weight IS NOT NULL THEN
self.total_weighted_value := self.total_weighted_value + (value * weight);
self.total_weight := self.total_weight + weight;
END IF;
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateMerge(
self IN OUT weighted_avg_type,
ctx IN weighted_avg_type
) RETURN NUMBER IS
BEGIN
self.total_weighted_value := self.total_weighted_value + ctx.total_weighted_value;
self.total_weight := self.total_weight + ctx.total_weight;
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateTerminate(
self IN weighted_avg_type,
returnValue OUT NUMBER,
flags IN NUMBER
) RETURN NUMBER IS
BEGIN
IF self.total_weight > 0 THEN
returnValue := self.total_weighted_value / self.total_weight;
ELSE
returnValue := NULL;
END IF;
RETURN ODCIConst.Success;
END;
END;
/
-- Create the aggregate function
CREATE OR REPLACE FUNCTION weighted_average(
value NUMBER,
weight NUMBER
) RETURN NUMBER
AGGREGATE USING weighted_avg_type;
/
-- Test the custom aggregate function
SELECT department_id,
AVG(salary) as simple_average,
weighted_average(salary, years_of_service) as weighted_avg_salary
FROM (
SELECT department_id, salary,
EXTRACT(YEAR FROM SYSDATE) - EXTRACT(YEAR FROM hire_date) as years_of_service
FROM employees
)
GROUP BY department_id;
-- Advanced custom aggregate: String concatenation with separator
CREATE OR REPLACE TYPE concat_type AS OBJECT (
result_string CLOB,
separator VARCHAR2(10),
STATIC FUNCTION ODCIAggregateInitialize(
ctx IN OUT concat_type
) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateIterate(
self IN OUT concat_type,
input_string IN VARCHAR2,
sep IN VARCHAR2 DEFAULT ','
) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateMerge(
self IN OUT concat_type,
ctx IN concat_type
) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateTerminate(
self IN concat_type,
returnValue OUT CLOB,
flags IN NUMBER
) RETURN NUMBER
);
/
CREATE OR REPLACE TYPE BODY concat_type IS
STATIC FUNCTION ODCIAggregateInitialize(
ctx IN OUT concat_type
) RETURN NUMBER IS
BEGIN
ctx := concat_type(EMPTY_CLOB(), ',');
DBMS_LOB.CREATETEMPORARY(ctx.result_string, TRUE);
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateIterate(
self IN OUT concat_type,
input_string IN VARCHAR2,
sep IN VARCHAR2 DEFAULT ','
) RETURN NUMBER IS
BEGIN
IF input_string IS NOT NULL THEN
IF DBMS_LOB.GETLENGTH(self.result_string) > 0 THEN
DBMS_LOB.APPEND(self.result_string, NVL(sep, self.separator));
END IF;
DBMS_LOB.APPEND(self.result_string, input_string);
self.separator := NVL(sep, self.separator);
END IF;
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateMerge(
self IN OUT concat_type,
ctx IN concat_type
) RETURN NUMBER IS
BEGIN
IF DBMS_LOB.GETLENGTH(ctx.result_string) > 0 THEN
IF DBMS_LOB.GETLENGTH(self.result_string) > 0 THEN
DBMS_LOB.APPEND(self.result_string, self.separator);
END IF;
DBMS_LOB.APPEND(self.result_string, ctx.result_string);
END IF;
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateTerminate(
self IN concat_type,
returnValue OUT CLOB,
flags IN NUMBER
) RETURN NUMBER IS
BEGIN
returnValue := self.result_string;
RETURN ODCIConst.Success;
END;
END;
/
-- Create custom concatenate function
CREATE OR REPLACE FUNCTION custom_concat(
input_string VARCHAR2,
separator VARCHAR2 DEFAULT ','
) RETURN CLOB
AGGREGATE USING concat_type;
/
-- Test custom concatenation
SELECT department_id,
custom_concat(first_name || ' ' || last_name, '; ') as employee_list
FROM employees
GROUP BY department_id;
-- Geometric mean aggregate function
CREATE OR REPLACE TYPE geom_mean_type AS OBJECT (
product_log NUMBER,
count_values NUMBER,
STATIC FUNCTION ODCIAggregateInitialize(
ctx IN OUT geom_mean_type
) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateIterate(
self IN OUT geom_mean_type,
value IN NUMBER
) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateMerge(
self IN OUT geom_mean_type,
ctx IN geom_mean_type
) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateTerminate(
self IN geom_mean_type,
returnValue OUT NUMBER,
flags IN NUMBER
) RETURN NUMBER
);
/
CREATE OR REPLACE TYPE BODY geom_mean_type IS
STATIC FUNCTION ODCIAggregateInitialize(
ctx IN OUT geom_mean_type
) RETURN NUMBER IS
BEGIN
ctx := geom_mean_type(0, 0);
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateIterate(
self IN OUT geom_mean_type,
value IN NUMBER
) RETURN NUMBER IS
BEGIN
IF value IS NOT NULL AND value > 0 THEN
self.product_log := self.product_log + LN(value);
self.count_values := self.count_values + 1;
END IF;
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateMerge(
self IN OUT geom_mean_type,
ctx IN geom_mean_type
) RETURN NUMBER IS
BEGIN
self.product_log := self.product_log + ctx.product_log;
self.count_values := self.count_values + ctx.count_values;
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateTerminate(
self IN geom_mean_type,
returnValue OUT NUMBER,
flags IN NUMBER
) RETURN NUMBER IS
BEGIN
IF self.count_values > 0 THEN
returnValue := EXP(self.product_log / self.count_values);
ELSE
returnValue := NULL;
END IF;
RETURN ODCIConst.Success;
END;
END;
/
CREATE OR REPLACE FUNCTION geometric_mean(value NUMBER)
RETURN NUMBER AGGREGATE USING geom_mean_type;
/
-- Test geometric mean
SELECT department_id,
ROUND(AVG(salary), 2) as arithmetic_mean,
ROUND(geometric_mean(salary), 2) as geometric_mean_salary
FROM employees
WHERE salary > 0
GROUP BY department_id;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| DEPT_ID | EMP_COUNT | AVG_SALARY | MAX_SALARY |
|---|---|---|---|
| 10 | 2 | 52,500 | 55,000 |
| 20 | 2 | 66,000 | 70,000 |
2 groups โ Alice+Carol in dept 10, Bob+Dave in dept 20
Create custom aggregate functions using PL/SQL object types with ODCIAggregate interface methods for complex aggregation logic.
A function must return exactly one value using a RETURN statement and can be used directly inside a SQL expression, such as in a SELECT list.
A procedure does not have to return a value, and instead can use IN, OUT, and IN OUT parameters to pass data both into and out of the call.
Functions are typically used for computing and returning a single result, while procedures are used for performing an action or a sequence of operations.
A function called from SQL must follow certain restrictions, such as not performing DML if called directly within a query in older Oracle versions, while a procedure has no such restriction when called via PL/SQL.
CREATE OR REPLACE FUNCTION get_bonus (p_salary NUMBER)
RETURN NUMBER IS
BEGIN
RETURN p_salary * 0.10;
END;
/
CREATE OR REPLACE PROCEDURE give_raise (
p_emp_id IN NUMBER,
p_percent IN NUMBER
) IS
BEGIN
UPDATE employees
SET salary = salary * (1 + p_percent/100)
WHERE emp_id = p_emp_id;
END;
/| OBJECT_TYPE | RETURNS_VALUE | CALLABLE_FROM_SQL |
|---|---|---|
| FUNCTION | Yes โ single value via RETURN | Yes, directly in SELECT |
| PROCEDURE | No โ uses OUT parameters instead | No, only via PL/SQL block or CALL |
get_bonus can be used inline in a SELECT statement; give_raise must be invoked as a standalone call
A function always returns a single value and can be used inside SQL expressions, while a procedure performs an action and communicates results through IN/OUT parameters instead of a return value.
Triggers can fire BEFORE or AFTER the triggering DML event, controlling whether the trigger logic runs before or after the actual INSERT, UPDATE, or DELETE takes effect.
Triggers can also be defined at the row level, firing once for every affected row, or at the statement level, firing only once per triggering statement regardless of how many rows are affected.
A row-level trigger can use the special :NEW and :OLD qualifiers to access the new and previous values of the row being changed.
Combining timing and level gives four common trigger types: BEFORE row-level, AFTER row-level, BEFORE statement-level, and AFTER statement-level, each suited to different validation or auditing needs.
CREATE OR REPLACE TRIGGER trg_salary_check
BEFORE UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
IF :NEW.salary < :OLD.salary THEN
RAISE_APPLICATION_ERROR(-20001, 'Salary cannot decrease');
END IF;
END;
/| TRIGGER_TYPE | FIRES |
|---|---|
| BEFORE, row-level | Once per row, before the row is changed |
| AFTER, row-level | Once per row, after the row is changed |
| BEFORE, statement-level | Once before the statement runs |
| AFTER, statement-level | Once after the statement completes |
trg_salary_check is a BEFORE row-level trigger, since it inspects :OLD and :NEW for every row before the update is applied
Oracle triggers combine timing (BEFORE or AFTER) with level (row or statement), giving four trigger types, with row-level triggers able to access :NEW and :OLD column values.
A transaction in SQL is a fundamental operation that represents a sequence of one or more SQL statements executed as a single unit.
Transaction ensures the integrity and consistency of a database.
A transaction starts with the execution of an SQL statement and concludes when the changes made by the statement are either permanently saved to the database (committed) or undone (rolled back) if an error or issue occurs during execution.
Transactions provide the "ACID" properties in SQL, which stand for Atomicity, Consistency, Isolation, and Durability.
These properties guarantee that database operations are reliable, maintain data integrity, and are recoverable in case of failures.
| account_id | owner | balance |
|---|---|---|
| 1001 | Alice | 5,000 |
| 1002 | Bob | 3,200 |
| 1003 | Carol | 8,750 |
BEGIN
UPDATE accounts SET balance = balance - 500 WHERE account_id = 1001;
UPDATE accounts SET balance = balance + 500 WHERE account_id = 1002;
COMMIT;
EXCEPTION
WHEN OTHERS THEN ROLLBACK;
END;
/| account_id | owner | balance |
|---|---|---|
| 1001 | Alice | 5,000 |
| 1002 | Bob | 3,200 |
| 1003 | Carol | 8,750 |
Data state before query executes.
| EMP_ID | EMP_NAME | OLD_SALARY | NEW_SALARY |
|---|---|---|---|
| 101 | Alice Johnson | 50,000 | 55,000 |
| 103 | Carol White | 55,000 | 60,500 |
2 rows updated โ COMMIT to make permanent
A transaction in SQL is a fundamental operation that represents a sequence of one or more SQL statements executed as a single unit.
ACID properties in a database are fundamental principles that ensure the reliability and consistency of data in SQL systems.
These properties, which stand for Atomicity, Consistency, Isolation, and Durability, are crucial for maintaining data integrity.Atomicity guarantees that database transactions are treated as a single unit, and they either fully succeed or fail.
This means that all the operations within a transaction are executed entirely, or none of them are.Consistency ensures that a database remains in a valid state before and after a transaction.
It means that data changes made by a transaction must adhere to predefined rules and constraints.Isolation prevents concurrent transactions from interfering with each other.
SQL databases use locking mechanisms to isolate transactions and maintain their independence.Durability guarantees that once a transaction is committed, its changes are permanent and will survive any system failures.
In SQL, this means that the data remains intact even in the face of power outages or crashes.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| PROPERTY | GUARANTEED_BY | EXAMPLE |
|---|---|---|
| Atomicity | Transaction rollback | Transfer: both debit+credit or neither |
| Consistency | Constraints + triggers | Balance never goes negative |
| Isolation | Locking / MVCC | Session 2 sees no partial changes |
| Durability | Redo logs | Committed data survives power failure |
ACID properties are the foundation of reliable relational database transactions
ACID properties in a database are fundamental principles that ensure the reliability and consistency of data in SQL systems.
ACID compliance, in the context of distributed databases, ensures that database transactions are executed reliably and consistently.
ACID stands for Atomicity, Consistency, Isolation, and Durability.Atomicity guarantees that a transaction is treated as a single, indivisible unit.
All its operations are either completed or aborted as a whole, ensuring data integrity.
Consistency ensures that a transaction takes the database from one consistent state to another.
It maintains data validity and integrity throughout the process.Isolation guarantees that multiple concurrent transactions do not interfere with each other.
Each transaction is executed in isolation, as if it were the only one running.
Durability ensures that once a transaction is committed, its changes are permanent and survive system failures.ACID compliance in distributed databases ensures data reliability, consistency, and integrity, even in a distributed and potentially unreliable environment.
It's crucial for maintaining data quality and correctness in SQL-based distributed systems.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| PROPERTY | GUARANTEED_BY | EXAMPLE |
|---|---|---|
| Atomicity | Transaction rollback | Transfer: both debit+credit or neither |
| Consistency | Constraints + triggers | Balance never goes negative |
| Isolation | Locking / MVCC | Session 2 sees no partial changes |
| Durability | Redo logs | Committed data survives power failure |
ACID properties are the foundation of reliable relational database transactions
ACID compliance, in the context of distributed databases, ensures that database transactions are executed reliably and consistently.
Handling deadlock situations in SQL involves detecting and resolving conflicts that occur when multiple transactions are competing for the same resources simultaneously.SQL provides mechanisms to handle deadlock situations efficiently.
One approach is to use locks, such as shared locks and exclusive locks, to control access to data.
A transaction acquires a lock, when it requests access to a resource, preventing other transactions from accessing the same resource simultaneously.
A deadlock occurs, If two or more transactions are blocked waiting for each other's resources.To address deadlock situations one common method is to implement a timeout mechanism, where transactions are set to automatically release their locks after a predefined period.
This helps prevent indefinite blocking.
SQL supports deadlock detection algorithms that periodically check for deadlock conditions.
The system automatically terminates one of the conflicting transactions to resolve the issue, If a deadlock is detected.SQL allows for the definition of deadlock priorities, where certain transactions are given higher precedence in case of conflicts, ensuring that critical processes continue execution while less critical ones may be rolled back.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| SESSION | HOLDS_LOCK | WAITING_FOR | ORACLE_ACTION |
|---|---|---|---|
| Session 1 | Acc 1001 | Acc 1002 | โ Allowed to commit |
| Session 2 | Acc 1002 | Acc 1001 | โ ORA-00060: deadlock โ rolled back |
Oracle detects the cycle automatically and rolls back the victim session
Handling deadlock situations in SQL involves detecting and resolving conflicts that occur when multiple transactions are competing for the same resources simultaneously.
SQL commands like BEGIN TRANSACTION, COMMIT, and ROLLBACK are used to manage transactions, in a distributed database system.
These commands ensure the ACID principles (Atomicity, Consistency, Isolation, Durability) are followed.BEGIN TRANSACTION initiates a transaction, executing SQL statements.
COMMIT confirms the transaction, making changes permanent, while ROLLBACK cancels it in case of errors or issues.Concurrency and isolation are maintained using locking mechanisms and isolation levels, preventing interference between transactions and ensuring data integrity and consistency.
| account_id | owner | balance |
|---|---|---|
| 1001 | Alice | 5,000 |
| 1002 | Bob | 3,200 |
| 1003 | Carol | 8,750 |
BEGIN
UPDATE accounts SET balance = balance - 500 WHERE account_id = 1001;
UPDATE accounts SET balance = balance + 500 WHERE account_id = 1002;
COMMIT;
EXCEPTION
WHEN OTHERS THEN ROLLBACK;
END;
/| account_id | owner | balance |
|---|---|---|
| 1001 | Alice | 5,000 |
| 1002 | Bob | 3,200 |
| 1003 | Carol | 8,750 |
Data state before query executes.
| EMP_ID | EMP_NAME | OLD_SALARY | NEW_SALARY |
|---|---|---|---|
| 101 | Alice Johnson | 50,000 | 55,000 |
| 103 | Carol White | 55,000 | 60,500 |
2 rows updated โ COMMIT to make permanent
SQL commands like BEGIN TRANSACTION, COMMIT, and ROLLBACK are used to manage transactions, in a distributed database system.
SQL handles data integrity and consistency through its inherent design and feature set.
The language uses constraints, transactions, and atomic operations to ensure that data remains accurate and reliable.
Constraints such as primary keys, foreign keys, and unique keys enforce data integrity by ensuring that each record is unique and correctly related to other data.
For instance, a primary key constraint prevents duplicate entries in a table.Transactions in SQL play a pivotal role in maintaining data consistency.
A transaction groups several operations into a single unit, which either completes entirely or not at all, thereby preserving the consistency of the database.
SQL employs the ACID properties (Atomicity, Consistency, Isolation, Durability) to ensure that transactions are processed reliably.
This means changes made by a transaction are permanent and survive system failures, and concurrent transactions do not interfere with each other.
Additionally, SQL uses locking mechanisms and isolation levels to manage concurrent access, ensuring that data remains consistent even when multiple users are accessing and modifying it simultaneously.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
SQL handles data integrity and consistency through its inherent design and feature set.
SQL handles concurrency through a set of mechanisms that ensure data integrity and consistency when multiple transactions occur simultaneously.
The primary challenge in managing concurrency in SQL is to balance the need for simultaneous access to data with the necessity of maintaining data accuracy and consistency.
Transactions, which are sequences of operations performed as a single logical unit, play a crucial role in this balance.
SQL databases use locking and isolation levels as key tools to manage concurrent access.
Locking prevents multiple transactions from accessing the same data concurrently, thus avoiding conflicts and data corruption.
However, excessive locking can lead to performance bottlenecks, as it restricts access to data.Isolation levels in SQL determine how much a transaction is isolated from other transactions.
Higher isolation levels provide greater data integrity but at the cost of reduced concurrency and potential performance issues like deadlocks, where two or more transactions are waiting indefinitely for one another to release locks.
SQL databases offer different isolation levels such as Read Uncommitted, Read Committed, Repeatable Read, and Serializable, each providing a different balance between data integrity and concurrency.
It is essential to choose the appropriate isolation level, considering the specific requirements of the application.
This choice impacts how transactions interact, for optimal concurrency management, ensuring that data remains consistent and reliable even when accessed by multiple users or processes simultaneously.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
SQL handles concurrency through a set of mechanisms that ensure data integrity and consistency when multiple transactions occur simultaneously.
ACID stands for Atomicity, Consistency, Isolation, and Durability.
These properties ensure that database transactions are processed reliably in Oracle.
| account_id | owner | balance |
|---|---|---|
| 1001 | Alice | 5,000 |
| 1002 | Bob | 3,200 |
| 1003 | Carol | 8,750 |
-- ACID Properties Demonstration in Oracle
-- 1. ATOMICITY - All or nothing principle
BEGIN
-- Transfer money between accounts (atomic transaction)
UPDATE accounts SET balance = balance - 1000 WHERE account_id = 'ACC001';
UPDATE accounts SET balance = balance + 1000 WHERE account_id = 'ACC002';
-- If any statement fails, entire transaction is rolled back
COMMIT; -- All changes saved together
EXCEPTION
WHEN OTHERS THEN
ROLLBACK; -- All changes undone together
RAISE;
END;
/
-- 2. CONSISTENCY - Database remains in valid state
CREATE OR REPLACE TRIGGER trg_account_balance_check
BEFORE UPDATE ON accounts
FOR EACH ROW
BEGIN
-- Ensure balance never goes negative (business rule)
IF :NEW.balance < 0 THEN
RAISE_APPLICATION_ERROR(-20001, 'Balance cannot be negative');
END IF;
END;
/
-- 3. ISOLATION - Transactions don't interfere with each other
-- Session 1:
BEGIN
UPDATE employees SET salary = salary * 1.1 WHERE department_id = 10;
-- Transaction not yet committed, changes not visible to other sessions
COMMIT;
END;
/
-- Different isolation levels in Oracle
-- READ COMMITTED (default)
ALTER SESSION SET ISOLATION_LEVEL = READ COMMITTED;
-- SERIALIZABLE
ALTER SESSION SET ISOLATION_LEVEL = SERIALIZABLE;
-- Demonstrate isolation with locks
SELECT object_name, object_type, session_id, oracle_username, locked_mode
FROM v$locked_object lo
JOIN dba_objects do ON lo.object_id = do.object_id;
-- 4. DURABILITY - Committed changes survive system failures
-- Oracle ensures durability through:
-- - Redo logs
-- - Database checkpoints
-- - Archive logs
-- Check redo log status
SELECT group#, status, archived, bytes/1024/1024 as size_mb
FROM v$log;
-- Force log switch for durability
ALTER SYSTEM SWITCH LOGFILE;
-- Practical ACID example: Bank transfer
CREATE OR REPLACE PROCEDURE transfer_money(
p_from_account VARCHAR2,
p_to_account VARCHAR2,
p_amount NUMBER
) IS
v_from_balance NUMBER;
BEGIN
-- Start transaction (implicit)
-- Check sufficient funds
SELECT balance INTO v_from_balance
FROM accounts
WHERE account_id = p_from_account
FOR UPDATE; -- Lock the row
IF v_from_balance < p_amount THEN
RAISE_APPLICATION_ERROR(-20002, 'Insufficient funds');
END IF;
-- Perform transfer (atomicity)
UPDATE accounts
SET balance = balance - p_amount
WHERE account_id = p_from_account;
UPDATE accounts
SET balance = balance + p_amount
WHERE account_id = p_to_account;
-- Log transaction
INSERT INTO transaction_log (from_account, to_account, amount, trans_date)
VALUES (p_from_account, p_to_account, p_amount, SYSDATE);
COMMIT; -- Ensure durability
EXCEPTION
WHEN OTHERS THEN
ROLLBACK; -- Ensure atomicity
RAISE;
END;
/| account_id | owner | balance |
|---|---|---|
| 1001 | Alice | 5,000 |
| 1002 | Bob | 3,200 |
| 1003 | Carol | 8,750 |
Data state before query executes.
| PROPERTY | GUARANTEED_BY | EXAMPLE |
|---|---|---|
| Atomicity | Transaction rollback | Transfer: both debit+credit or neither |
| Consistency | Constraints + triggers | Balance never goes negative |
| Isolation | Locking / MVCC | Session 2 sees no partial changes |
| Durability | Redo logs | Committed data survives power failure |
ACID properties are the foundation of reliable relational database transactions
ACID stands for Atomicity, Consistency, Isolation, and Durability.
Oracle uses automatic transaction management with COMMIT/ROLLBACK and provides various locking mechanisms to ensure data consistency.
| account_id | owner | balance |
|---|---|---|
| 1001 | Alice | 5,000 |
| 1002 | Bob | 3,200 |
| 1003 | Carol | 8,750 |
-- Basic transaction control
BEGIN
-- Start implicit transaction
UPDATE employees SET salary = salary * 1.1 WHERE department_id = 10;
INSERT INTO salary_audit (employee_id, old_salary, new_salary, change_date)
SELECT employee_id, salary/1.1, salary, SYSDATE
FROM employees WHERE department_id = 10;
-- Commit all changes
COMMIT;
EXCEPTION
WHEN OTHERS THEN
-- Rollback on error
ROLLBACK;
RAISE;
END;
/
-- Savepoints for partial rollback
BEGIN
UPDATE employees SET salary = salary + 1000 WHERE employee_id = 101;
SAVEPOINT after_first_update;
UPDATE employees SET salary = salary + 500 WHERE employee_id = 102;
SAVEPOINT after_second_update;
-- Some error condition
IF SQL%ROWCOUNT = 0 THEN
ROLLBACK TO after_first_update; -- Partial rollback
END IF;
COMMIT;
END;
/
-- Explicit locking with SELECT FOR UPDATE
-- Lock specific rows for update
SELECT employee_id, salary
FROM employees
WHERE department_id = 10
FOR UPDATE;
-- Update the locked rows
UPDATE employees
SET salary = salary * 1.1
WHERE department_id = 10;
COMMIT; -- Releases locks
-- Row-level locking with NOWAIT
BEGIN
SELECT employee_id, salary
FROM employees
WHERE employee_id = 101
FOR UPDATE NOWAIT; -- Don't wait if row is locked
-- Perform updates
UPDATE employees SET salary = 75000 WHERE employee_id = 101;
COMMIT;
EXCEPTION
WHEN RESOURCE_BUSY THEN
DBMS_OUTPUT.PUT_LINE('Row is locked by another session');
END;
/
-- Lock specific columns
SELECT employee_id, salary, commission_pct
FROM employees
WHERE department_id = 20
FOR UPDATE OF salary; -- Only lock salary column
-- Check current locks
SELECT s.sid, s.serial#, s.username, s.status,
l.type, l.mode_held, l.mode_requested,
o.object_name
FROM v$session s
JOIN v$lock l ON s.sid = l.sid
JOIN dba_objects o ON l.id1 = o.object_id
WHERE s.username IS NOT NULL;
-- Deadlock handling
CREATE OR REPLACE PROCEDURE transfer_funds(
p_from_account NUMBER,
p_to_account NUMBER,
p_amount NUMBER
) IS
deadlock_detected EXCEPTION;
PRAGMA EXCEPTION_INIT(deadlock_detected, -60);
BEGIN
-- Lock accounts in consistent order to avoid deadlocks
IF p_from_account < p_to_account THEN
SELECT balance INTO l_from_balance
FROM accounts WHERE account_id = p_from_account FOR UPDATE;
SELECT balance INTO l_to_balance
FROM accounts WHERE account_id = p_to_account FOR UPDATE;
ELSE
SELECT balance INTO l_to_balance
FROM accounts WHERE account_id = p_to_account FOR UPDATE;
SELECT balance INTO l_from_balance
FROM accounts WHERE account_id = p_from_account FOR UPDATE;
END IF;
-- Perform transfer
UPDATE accounts SET balance = balance - p_amount WHERE account_id = p_from_account;
UPDATE accounts SET balance = balance + p_amount WHERE account_id = p_to_account;
COMMIT;
EXCEPTION
WHEN deadlock_detected THEN
ROLLBACK;
DBMS_OUTPUT.PUT_LINE('Deadlock detected, transaction rolled back');
RAISE;
END;
/
-- Set transaction isolation level
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- or
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- Read-only transactions
SET TRANSACTION READ ONLY;
SELECT * FROM employees WHERE hire_date >= DATE '2023-01-01';
COMMIT;
-- Check transaction status
SELECT s.sid, s.serial#, s.username,
t.status, t.start_time, t.used_ublk
FROM v$session s
JOIN v$transaction t ON s.saddr = t.ses_addr
WHERE s.username = USER;| account_id | owner | balance |
|---|---|---|
| 1001 | Alice | 5,000 |
| 1002 | Bob | 3,200 |
| 1003 | Carol | 8,750 |
Data state before query executes.
| EXCEPTION | WHEN_RAISED | HANDLED_BY | OUTCOME |
|---|---|---|---|
| NO_DATA_FOUND | SELECT INTO = 0 rows | WHEN NO_DATA_FOUND | Continue after message |
| TOO_MANY_ROWS | SELECT INTO > 1 row | WHEN OTHERS | Rollback + log |
Exceptions prevent unhandled errors from crashing the PL/SQL block
Oracle uses automatic transaction management with COMMIT/ROLLBACK and provides various locking mechanisms to ensure data consistency.
COMMIT permanently saves all changes made during the current transaction, making them visible to other sessions and ending the transaction.
ROLLBACK undoes all changes made since the last COMMIT, returning the data to its previous state, and also ends the transaction unless a savepoint is specified.
SAVEPOINT marks a named point within a transaction that you can later roll back to, without undoing the entire transaction.
ROLLBACK TO a savepoint undoes only the changes made after that savepoint, while keeping earlier changes in the transaction intact and uncommitted.
| emp_id | emp_name | salary |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
UPDATE employees SET salary = 55000 WHERE emp_id = 101;
SAVEPOINT after_raise;
UPDATE employees SET salary = 60000 WHERE emp_id = 101;
ROLLBACK TO after_raise;
COMMIT;| EMP_ID | SALARY |
|---|---|
| 101 | 55,000 |
The second update to 60,000 is undone by ROLLBACK TO after_raise, so only the 55,000 raise is committed
COMMIT makes transaction changes permanent, ROLLBACK undoes them, and SAVEPOINT lets you roll back to a specific point within a transaction without discarding everything.
Window functions in SQL perform calculations across a set of table rows related to the current row.
These functions allow users to carry out tasks like calculating running totals, ranking results, or computing averages within a specific window of data.
Window functions operate within a frame or window of data specified by the OVER clause.One common example is the ROW_NUMBER() function, which assigns a unique sequential integer to rows within a partition of a result set.
For exampleSELECT ROW_NUMBER() OVER (ORDER BY column_name) FROM table_name;assigns a unique number to each row ordered bycolumn_name.
Another example is the SUM() function used as a window function.
It calculates the cumulative sum of a column.SELECT SUM(column_name) OVER (ORDER BY column_name) FROM table_name;computes the running total ofcolumn_name.Window functions also include ranking functions like RANK() and DENSE_RANK().
These functions assign a rank to each row within a partition, with gaps in the rank values for ties in the case of RANK() and without gaps for DENSE_RANK().
For example,SELECT RANK() OVER (ORDER BY column_name) FROM table_name;ranks rows based oncolumn_name.These functions enhance the ability to perform complex calculations and data analysis directly within SQL queries, without requiring additional processing outside the database environment.
Their use is essential in scenarios where relational data needs advanced analytical processing like reporting, data analytics, or business intelligence applications.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT employee_name, salary,
ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS row_num,
RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rnk,
DENSE_RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS d_rnk
FROM employees;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_NAME | SALARY | ROW_NUM | RANK | DENSE_RANK |
|---|---|---|---|---|
| Dave Brown | 70,000 | 1 | 1 | 1 |
| Bob Smith | 62,000 | 2 | 2 | 2 |
| Carol White | 55,000 | 3 | 3 | 3 |
| Alice Johnson | 50,000 | 4 | 4 | 4 |
All 4 employees ranked by salary DESC โ no ties so all methods agree
Window functions in SQL perform calculations across a set of table rows related to the current row.
rank(), dense_rank(), and row_number() are SQL functions that enable you to assign ranks and numbers to rows within a result set, each with its unique behavior.
They are powerful tools for data analysis and reporting in SQL queries.Therank()function is used to assign a unique rank to each row within the result set based on the values in one or more columns.
It assigns the same rank to rows with equal values and leaves gaps in ranking for tied rows.
For example, rows with the same score will have the same rank, if you use rank() to rank a set of scores, and the next rank will be skipped.Thedense_rank()function is similar to rank() but does not leave gaps in ranking for tied rows.
It assigns the same rank to rows with equal values and continues with the next rank without gaps.
This is useful when you want a dense ranking without skipped ranks.Therow_number()function assigns a unique number to each row within the result set, without considering ties.
It simply numbers the rows sequentially, starting from 1.
This function is often used to generate a unique identifier for each row in a result set.These functions are valuable in various scenarios.
For example, in a competition where you want to rank participants based on their scores, use rank() to assign unique ranks while considering ties.row_number() is the choice, if you need a simple numbering of rows.
Dense_rank() is handy when you want dense ranking, such as in percentile calculations.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT employee_name, salary,
ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS row_num,
RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rnk,
DENSE_RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS d_rnk
FROM employees;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_NAME | SALARY | ROW_NUM | RANK | DENSE_RANK |
|---|---|---|---|---|
| Dave Brown | 70,000 | 1 | 1 | 1 |
| Bob Smith | 62,000 | 2 | 2 | 2 |
| Carol White | 55,000 | 3 | 3 | 3 |
| Alice Johnson | 50,000 | 4 | 4 | 4 |
All 4 employees ranked by salary DESC โ no ties so all methods agree
rank(), dense_rank(), and row_number() are SQL functions that enable you to assign ranks and numbers to rows within a result set, each with its unique behavior.
Pivot and unpivot operations in SQL are powerful tools for restructuring data within a database.
The pivot operation allows you to transform rows of data into columns, making it easier to analyze and present information.
The unpivot operation does the opposite, converting columns into rows.
These operations are particularly useful when dealing with complex datasets and when you need to transpose data to meet specific reporting or analysis requirements.Pivot operations are employed when you want to aggregate and summarize data by grouping it based on a particular column's values.
For example, A table is pivoted to display sales data by different product categories as separate columns, simplifying the process of comparing sales across categories.
This operation is especially valuable in scenarios where you need to generate cross-tabular reports or create visualizations that rely on data in a columnar format.Unpivot operations are beneficial when you have data stored in a wide format (columns) and need to convert it into a long format (rows).
This is commonly used in scenarios where data needs to be normalized for further analysis.
For example, unpivot it to transform it into a format where each row contains the month and corresponding sales value, if you have a table with columns representing different months of the year and sales data.
This makes it easier to perform time-series analysis and other operations.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM (
SELECT department_id, EXTRACT(YEAR FROM hire_date) yr, salary
FROM employees
)
PIVOT (COUNT(*) FOR yr IN (2020, 2021, 2022, 2023));| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| DEPT_ID | 2021_HIRES | 2022_HIRES | 2023_HIRES |
|---|---|---|---|
| 10 | 1 | 1 | 0 |
| 20 | 1 | 0 | 1 |
PIVOT rotates row values into column headers โ one column per year
Pivot and unpivot operations in SQL are powerful tools for restructuring data within a database.
Window functions are utilized for calculating running totals and moving averages.
TheSUM()function combined with theOVER()clause effectively computes running totals.
This approach involves specifying a window frame using theROWS BETWEENstatement, which defines the range of rows used in each calculation.
For example, to calculate a running total, useSUM(column_name) OVER (ORDER BY column_name).Similarly, for moving averages, theAVG()function is employed alongside theOVER()clause.
This function calculates the average over a specified range of rows, making it ideal for moving averages.
The syntaxAVG(column_name) OVER (ORDER BY column_name ROWS BETWEEN x PRECEDING AND y FOLLOWING)provides the average of values in a window frame defined by x preceding and y following rows.
This method is crucial in time series analysis and trend detection in datasets.
Employ these functions to accurately perform complex calculations in SQL, ensuring efficient and precise data analysis.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT employee_name, salary,
ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS row_num,
RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rnk,
DENSE_RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS d_rnk
FROM employees;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_NAME | SALARY | ROW_NUM | RANK | DENSE_RANK |
|---|---|---|---|---|
| Dave Brown | 70,000 | 1 | 1 | 1 |
| Bob Smith | 62,000 | 2 | 2 | 2 |
| Carol White | 55,000 | 3 | 3 | 3 |
| Alice Johnson | 50,000 | 4 | 4 | 4 |
All 4 employees ranked by salary DESC โ no ties so all methods agree
Window functions are utilized for calculating running totals and moving averages.
These ranking functions handle ties differently: ROW_NUMBER() assigns unique sequential numbers, RANK() skips ranks after ties, DENSE_RANK() assigns consecutive ranks without gaps.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- Sample data with ties
WITH sample_scores AS (
SELECT 'Alice' as name, 95 as score FROM dual UNION ALL
SELECT 'Bob', 87 FROM dual UNION ALL
SELECT 'Carol', 95 FROM dual UNION ALL
SELECT 'David', 82 FROM dual UNION ALL
SELECT 'Eve', 87 FROM dual
)
SELECT name, score,
ROW_NUMBER() OVER (ORDER BY score DESC) as row_num,
RANK() OVER (ORDER BY score DESC) as rank_val,
DENSE_RANK() OVER (ORDER BY score DESC) as dense_rank_val
FROM sample_scores;
-- Results:
-- Alice 95 1 1 1
-- Carol 95 2 1 1 (tie handling difference)
-- Bob 87 3 3 2 (RANK skips 2, DENSE_RANK doesn't)
-- Eve 87 4 3 2
-- David 82 5 5 3
-- Practical use case: Top N per group
SELECT department_id, employee_id, salary, salary_rank
FROM (
SELECT department_id, employee_id, salary,
DENSE_RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) as salary_rank
FROM employees
)
WHERE salary_rank <= 3;
-- When to use which:
-- ROW_NUMBER(): When you need unique ranking (pagination, unique identifiers)
-- RANK(): When gaps in ranking are acceptable (sports rankings)
-- DENSE_RANK(): When you need continuous ranking (grade classifications)| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_NAME | SALARY | ROW_NUM | RANK | DENSE_RANK |
|---|---|---|---|---|
| Dave Brown | 70,000 | 1 | 1 | 1 |
| Bob Smith | 62,000 | 2 | 2 | 2 |
| Carol White | 55,000 | 3 | 3 | 3 |
| Alice Johnson | 50,000 | 4 | 4 | 4 |
All 4 employees ranked by salary DESC โ no ties so all methods agree
These ranking functions handle ties differently: ROW_NUMBER() assigns unique sequential numbers, RANK() skips ranks after ties, DENSE_RANK() assigns consecutive ranks without gaps.
Use window functions with frame specifications (ROWS/RANGE BETWEEN) to calculate cumulative values and moving averages over ordered datasets.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- Running totals and moving averages
SELECT month_date, sales_amount,
-- Running total (cumulative sum)
SUM(sales_amount) OVER (ORDER BY month_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as running_total,
-- 3-month moving average
AVG(sales_amount) OVER (ORDER BY month_date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) as moving_avg_3m,
-- 6-month moving total
SUM(sales_amount) OVER (ORDER BY month_date
ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) as moving_total_6m,
-- Year-to-date calculation
SUM(sales_amount) OVER (PARTITION BY EXTRACT(YEAR FROM month_date)
ORDER BY month_date) as ytd_sales,
-- Percentage of running total
ROUND(sales_amount / SUM(sales_amount) OVER (ORDER BY month_date
ROWS UNBOUNDED PRECEDING) * 100, 2) as pct_running_total
FROM monthly_sales
ORDER BY month_date;
-- Advanced frame specifications
SELECT employee_id, salary, hire_date,
-- Centered moving average (previous, current, next)
AVG(salary) OVER (ORDER BY hire_date
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) as centered_avg,
-- Range-based frame (salary within 5000 range)
AVG(salary) OVER (ORDER BY salary
RANGE BETWEEN 2500 PRECEDING AND 2500 FOLLOWING) as range_avg
FROM employees;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_NAME | SALARY | RUNNING_TOTAL |
|---|---|---|
| Alice Johnson | 50,000 | 50,000 |
| Carol White | 55,000 | 105,000 |
| Bob Smith | 62,000 | 167,000 |
| Dave Brown | 70,000 | 237,000 |
Running total โ cumulative SUM() OVER (ORDER BY salary ASC)
Use window functions with frame specifications (ROWS/RANGE BETWEEN) to calculate cumulative values and moving averages over ordered datasets.
Oracle provides PIVOT and UNPIVOT operators to transform rows to columns and vice versa, useful for reporting and data analysis.
-- Sample data for pivot example
CREATE TABLE quarterly_sales (
year NUMBER,
quarter VARCHAR2(2),
sales_amount NUMBER
);
-- PIVOT: Transform rows to columns
SELECT year, Q1, Q2, Q3, Q4
FROM (
SELECT year, quarter, sales_amount
FROM quarterly_sales
)
PIVOT (
SUM(sales_amount)
FOR quarter IN ('Q1' as Q1, 'Q2' as Q2, 'Q3' as Q3, 'Q4' as Q4)
);
-- Dynamic PIVOT with XML
SELECT year,
EXTRACTVALUE(xml_data, '/PivotSet/item[1]/@val') as Q1,
EXTRACTVALUE(xml_data, '/PivotSet/item[2]/@val') as Q2,
EXTRACTVALUE(xml_data, '/PivotSet/item[3]/@val') as Q3,
EXTRACTVALUE(xml_data, '/PivotSet/item[4]/@val') as Q4
FROM (
SELECT year,
XMLAGG(XMLELEMENT("item", XMLATTRIBUTES(quarter as "key", sales_amount as "val"))) as xml_data
FROM quarterly_sales
GROUP BY year
);
-- UNPIVOT: Transform columns to rows
CREATE TABLE yearly_summary (
year NUMBER,
Q1 NUMBER,
Q2 NUMBER,
Q3 NUMBER,
Q4 NUMBER
);
SELECT year, quarter, sales_amount
FROM yearly_summary
UNPIVOT (
sales_amount FOR quarter IN (Q1, Q2, Q3, Q4)
);
-- Alternative method using UNION ALL
SELECT year, 'Q1' as quarter, Q1 as sales_amount FROM yearly_summary
UNION ALL
SELECT year, 'Q2' as quarter, Q2 as sales_amount FROM yearly_summary
UNION ALL
SELECT year, 'Q3' as quarter, Q3 as sales_amount FROM yearly_summary
UNION ALL
SELECT year, 'Q4' as quarter, Q4 as sales_amount FROM yearly_summary;| DEPT_ID | 2021_HIRES | 2022_HIRES | 2023_HIRES |
|---|---|---|---|
| 10 | 1 | 1 | 0 |
| 20 | 1 | 0 | 1 |
PIVOT rotates row values into column headers โ one column per year
Oracle provides PIVOT and UNPIVOT operators to transform rows to columns and vice versa, useful for reporting and data analysis.
LAG() returns the value of a column from a previous row within the same result set, based on a specified ordering, useful for comparing a row to the one before it.
LEAD() returns the value of a column from a following row, allowing comparison with the next row in the ordering.
Both functions accept an optional offset parameter to look back or ahead by more than one row, and an optional default value to use when no such row exists.
LAG and LEAD are commonly used to calculate period-over-period differences, such as month-over-month sales changes, without needing a self-join.
| sales_month | amount |
|---|---|
| 2026-01 | 10000 |
| 2026-02 | 12000 |
| 2026-03 | 9000 |
SELECT sales_month,
amount,
LAG(amount) OVER (ORDER BY sales_month) AS prev_amount,
LEAD(amount) OVER (ORDER BY sales_month) AS next_amount
FROM monthly_sales;| SALES_MONTH | AMOUNT | PREV_AMOUNT | NEXT_AMOUNT |
|---|---|---|---|
| 2026-01 | 10000 | NULL | 12000 |
| 2026-02 | 12000 | 10000 | 9000 |
| 2026-03 | 9000 | 12000 | NULL |
The first row has no previous value and the last row has no next value, so both show NULL where no such row exists
LAG() looks at a previous row's value and LEAD() looks at a following row's value within an ordered result set, commonly used for period-over-period comparisons.
Identify the most efficient execution plan using the EXPLAIN statement, to optimize an SQL query.
The use of indexes enhances performance, especially for large datasets, by reducing the amount of data scanned.
Writing precise queries with only necessary columns in the SELECT statement minimizes data processing.
Avoiding unnecessary joins and subqueries streamlines query execution.Regularly updating statistics on database tables ensures the query optimizer has accurate information for decision-making.
Implementing query caching stores the results of frequently executed queries for faster retrieval.
Splitting complex queries into simpler subqueries, if they become too cumbersome, maintains efficiency.
Use of temporary tables for intermediate results reduces the complexity of a query.
Adjusting database configurations, like buffer sizes, optimizes the overall performance of SQL queries.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
Identify the most efficient execution plan using the EXPLAIN statement, to optimize an SQL query.
To approach performance tuning in a SQL database, start by analyzing query execution plans.
Identify slow-performing queries and optimize them by using appropriate indexes and query design.
Monitor database metrics like CPU usage, memory usage, and disk I/O to identify bottlenecks.
Adjust server configuration parameters, such as buffer sizes and connection limits, to optimize resource usage.
Consider partitioning large tables and archiving old data to improve query performance.
Regularly maintain and update statistics to ensure the query optimizer makes efficient choices.
Consider caching mechanisms like query caching and database caching to reduce query execution times.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
To approach performance tuning in a SQL database, start by analyzing query execution plans.
Primary function of a database optimizer is to analyze the structure of SQL queries and the database schema to determine the most efficient execution plan.The database optimizer examines various factors such as table sizes, available indexes, and statistics on the data distribution, when a SQL query is submitted.
It then uses this information to formulate an execution plan that minimizes the query's response time.
This plan involves selecting the most appropriate tables to access, the order in which they should be accessed, and which indexes to utilize.One of the key benefits of a database optimizer is its ability to significantly reduce the query execution time.
It ensures that the database engine retrieves and processes the necessary data in the most efficient manner, by selecting the optimal execution plan, leading to faster query results.A well-functioning optimizer contributes to better resource management within the database system.
It helps avoid unnecessary overhead by preventing full table scans when more efficient index-based access is possible.
This, in turn, leads to reduced server load and better utilization of hardware resources.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
Primary function of a database optimizer is to analyze the structure of SQL queries and the database schema to determine the most efficient execution plan.
Understanding the data structure and indexing is crucial, in SQL query optimization for large datasets.
Efficient indexing enhances query performance by reducing the search space.
Analyze query execution plans to identify bottlenecks, as they reveal the steps the database takes to execute a query.
Use joins appropriately; overusing them can lead to slow performance.
Optimize joins by ensuring that the join conditions are on indexed columns.
Aggregate functions should be used judiciously.
They can slow down queries if applied to large data sets without proper filtering.
Ensure that WHERE clauses are selective, as this filters out unnecessary data early in the query execution process.Proper use of subqueries and temporary tables can significantly improve performance.
Subqueries should be used for complex filtering and aggregation, but avoid them in cases where joins can be more efficient.
Temporary tables are useful for breaking down complex queries into simpler steps.
This approach can improve readability and performance.
Regularly update statistics on the database to help the query optimizer make better decisions.
This is especially important in dynamic environments where data changes frequently.
Lastly, avoid using functions on indexed columns in the WHERE clause.
This practice can prevent the database from using the index, leading to full table scans and slower performance.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
Understanding the data structure and indexing is crucial, in SQL query optimization for large datasets.
Handling large-scale data deletion without impacting database performance requires strategic approaches.
One effective method is to use batch deletion, where data is deleted in smaller chunks rather than in a single large query.
This reduces the load on the transaction log and minimizes the locking and blocking of other operations.
Implementing an indexing strategy on the columns used in the deletion criteria can significantly speed up the deletion process.
Proper indexing ensures that the database quickly locates the data to be deleted, reducing the time and resources needed for the operation.Partitioning the table is another practical approach.
You can delete data from each partition independently, by dividing a large table into smaller, more manageable segments.
This method is especially useful when the data to be deleted is concentrated in specific partitions.
Utilize the TRUNCATE TABLE command for partitions that require complete data removal, as it performs faster than a DELETE operation by deallocating entire data pages.
Employ a maintenance window for deletion activities if the data size is exceptionally large, ensuring minimal interference with regular database operations.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
Handling large-scale data deletion without impacting database performance requires strategic approaches.
To approach capacity planning for a SQL database, it is essential to analyze current usage and growth patterns.
This involves reviewing historical data trends, query volumes, and peak usage times to predict future requirements.
Accurate estimation of data growth over time ensures the allocation of sufficient resources.
It is important to factor in both the storage needs and the processing power required to handle increasing data volumes and user queries efficiently.The next step involves testing the database's performance under various loads.
This includes simulating different user scenarios and data volumes to evaluate how the database manages increased stress.
Performance metrics like query response time, throughput, and resource utilization provide insights into the scalability of the database.
Adjust the database configuration and resource allocation accordingly, if the test results indicate potential bottlenecks or performance issues.
Regular monitoring and periodic re-evaluation of the database's capacity plan ensure its continued efficiency and scalability in line with evolving data demands.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
To approach capacity planning for a SQL database, it is essential to analyze current usage and growth patterns.
Advanced techniques for SQL query performance tuning involve various strategies to optimize query execution.
Indexing is a critical technique where indexes are created on columns to speed up data retrieval.
Proper indexing reduces the amount of data the server needs to scan, leading to faster query execution.
Another technique is query rewriting, where SQL queries are restructured for efficiency without altering their output.
For example, replacing subqueries with joins can significantly reduce execution time.Using proper data types ensures that database operations consume less memory and processing power.
For example, using INT for integer data types rather than VARCHAR can improve performance.
Understanding and using the database's execution plan helps in identifying performance bottlenecks.
This involves analyzing how a database executes a query and adjusting the query or the database environment accordingly.
Regularly updating statistics and maintaining the database also plays a key role in performance tuning.
This ensures that the database has current information about data distribution in tables, which is crucial for query optimization.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
Advanced techniques for SQL query performance tuning involve various strategies to optimize query execution.
Query optimization in Oracle involves proper indexing, execution plan analysis, hint usage, and understanding the cost-based optimizer.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- 1. Use proper indexes
CREATE INDEX idx_emp_dept_salary ON employees(department_id, salary);
-- 2. Check execution plan
EXPLAIN PLAN FOR
SELECT * FROM employees WHERE department_id = 10 AND salary > 50000;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
-- 3. Use bind variables instead of literals
SELECT * FROM employees WHERE employee_id = :emp_id;
-- 4. Avoid SELECT *
SELECT employee_id, first_name, salary
FROM employees
WHERE department_id = 10;
-- 5. Use EXISTS instead of IN for subqueries
SELECT * FROM departments d
WHERE EXISTS (SELECT 1 FROM employees e WHERE e.department_id = d.department_id);
-- 6. Use analytical functions instead of self-joins
SELECT employee_id, salary,
RANK() OVER (ORDER BY salary DESC) as salary_rank
FROM employees;
-- 7. Use ROWNUM for limiting results (Oracle-specific)
SELECT * FROM (
SELECT * FROM employees ORDER BY salary DESC
) WHERE ROWNUM <= 10;
-- 8. Oracle 12c+ OFFSET FETCH syntax
SELECT * FROM employees
ORDER BY salary DESC
OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_NAME | SALARY | ROW_NUM | RANK | DENSE_RANK |
|---|---|---|---|---|
| Dave Brown | 70,000 | 1 | 1 | 1 |
| Bob Smith | 62,000 | 2 | 2 | 2 |
| Carol White | 55,000 | 3 | 3 | 3 |
| Alice Johnson | 50,000 | 4 | 4 | 4 |
All 4 employees ranked by salary DESC โ no ties so all methods agree
Query optimization in Oracle involves proper indexing, execution plan analysis, hint usage, and understanding the cost-based optimizer.
Oracle database performance can be improved through proper indexing, query optimization, database tuning, memory management, and hardware optimization.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- 1. INDEXING STRATEGIES
-- Create appropriate indexes
CREATE INDEX idx_emp_dept_join ON employees(department_id);
CREATE INDEX idx_emp_salary_range ON employees(salary);
-- Composite indexes for multi-column queries
CREATE INDEX idx_emp_dept_salary_name ON employees(department_id, salary, last_name);
-- Function-based indexes for case-insensitive searches
CREATE INDEX idx_emp_upper_name ON employees(UPPER(last_name));
-- 2. QUERY OPTIMIZATION
-- Use bind variables
SELECT * FROM employees WHERE employee_id = :emp_id;
-- Avoid SELECT *
SELECT employee_id, first_name, salary FROM employees;
-- Use EXISTS instead of IN
SELECT * FROM departments d
WHERE EXISTS (SELECT 1 FROM employees e WHERE e.department_id = d.department_id);
-- 3. STATISTICS COLLECTION
-- Gather table statistics
BEGIN
DBMS_STATS.GATHER_TABLE_STATS('HR', 'EMPLOYEES');
END;
/
-- Gather schema statistics
BEGIN
DBMS_STATS.GATHER_SCHEMA_STATS('HR');
END;
/
-- 4. MEMORY TUNING
-- Check SGA components
SELECT component, current_size/1024/1024 as size_mb
FROM v$sga_dynamic_components;
-- Tune buffer cache hit ratio
SELECT name, value
FROM v$sysstat
WHERE name IN ('db block gets', 'consistent gets', 'physical reads');
-- 5. SQL TUNING ADVISOR
-- Create tuning task
DECLARE
task_name VARCHAR2(30);
BEGIN
task_name := DBMS_SQLTUNE.CREATE_TUNING_TASK(
sql_text => 'SELECT * FROM employees WHERE department_id = 10'
);
DBMS_SQLTUNE.EXECUTE_TUNING_TASK(task_name);
END;
/| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_ID | EMP_NAME | DEPT_ID | SALARY |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | 20 | 70,000 |
All 4 rows returned
Oracle database performance can be improved through proper indexing, query optimization, database tuning, memory management, and hardware optimization.
EXPLAIN PLAN is a statement that shows the execution plan the Oracle optimizer would use to run a given SQL statement, without actually executing it.
The plan reveals details such as which access method is used per table โ for example a full table scan versus an index range scan โ along with the estimated cost, cardinality, and join order.
Plan output is stored in the PLAN_TABLE and is typically viewed using the DBMS_XPLAN.DISPLAY function for readable formatting.
Reviewing the execution plan helps identify expensive operations, such as unnecessary full table scans, that may benefit from a new index or a rewritten query.
EXPLAIN PLAN FOR
SELECT emp_name FROM employees WHERE dept_id = 10;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);| OPERATION | OBJECT | COST |
|---|---|---|
| SELECT STATEMENT | 3 | |
| TABLE ACCESS FULL | EMPLOYEES | 3 |
A full table scan on EMPLOYEES indicates no index is being used for the dept_id filter โ a candidate for adding an index
EXPLAIN PLAN reveals how Oracle intends to execute a query โ including access paths, join order, and estimated cost โ without running it, helping pinpoint expensive operations before they happen.
Normalization in SQL is the process of organizing and structuring a relational database to minimize data redundancy and ensure data integrity.
Normalization involves breaking down large tables into smaller, related tables and establishing relationships between them using keys, such as primary and foreign keys.
This helps in optimizing data storage and retrieval, reducing anomalies, and maintaining consistency in the database.
Normalization is essential to avoid update anomalies, insertion anomalies, and deletion anomalies, ensuring that data remains accurate and reliable throughout its lifecycle in the database.
It follows a set of rules or forms, such as First Normal Form (1NF), Second Normal Form (2NF), Third Normal Form (3NF), and so on, to achieve these objectives.
-- Before (redundant): orders has customer_name repeated
-- After normalization:
CREATE TABLE customers (customer_id NUMBER PK, name VARCHAR2(100));
CREATE TABLE orders (order_id NUMBER PK, customer_id NUMBER REFERENCES customers);| NORMAL_FORM | RULE_VIOLATED | FIX |
|---|---|---|
| 0NF โ 1NF | Repeating groups: phone='123,456' | Separate into individual rows |
| 1NF โ 2NF | Partial dependency on composite PK | Move to separate table |
| 2NF โ 3NF | Transitive: empโdept_idโdept_name | Move dept_name to departments table |
| 3NF โ BCNF | Determinant not a candidate key | Decompose further |
Normalize step by step โ each form removes a specific type of redundancy
Normalization in SQL is the process of organizing and structuring a relational database to minimize data redundancy and ensure data integrity.
Different normal forms in SQL are levels of organizing and structuring relational databases to minimize data redundancy and improve data integrity.
These normal forms, denoted as 1NF, 2NF, 3NF, BCNF, and 4NF, each have specific rules and conditions that a database table must satisfy.1NF, or First Normal Form, ensures that each column in a table contains atomic (indivisible) values, and there are no repeating groups or arrays.2NF, or Second Normal Form, builds upon 1NF and requires that non-key attributes are functionally dependent on the entire primary key.3NF, or Third Normal Form, extends the normalization process by eliminating transitive dependencies between non-key attributes.BCNF, or Boyce-Codd Normal Form, deals with situations where a table has multiple candidate keys and ensures that non-prime attributes are functionally dependent only on the candidate keys.4NF, or Fourth Normal Form, addresses multivalued dependencies in a table, ensuring that they are properly handled.These normal forms help in designing efficient and well-structured databases, minimizing data anomalies, and ensuring data integrity, which are essential principles in SQL database design.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| NORMAL_FORM | RULE_VIOLATED | FIX |
|---|---|---|
| 0NF โ 1NF | Repeating groups: phone='123,456' | Separate into individual rows |
| 1NF โ 2NF | Partial dependency on composite PK | Move to separate table |
| 2NF โ 3NF | Transitive: empโdept_idโdept_name | Move dept_name to departments table |
| 3NF โ BCNF | Determinant not a candidate key | Decompose further |
Normalize step by step โ each form removes a specific type of redundancy
Different normal forms in SQL are levels of organizing and structuring relational databases to minimize data redundancy and improve data integrity.
Denormalization in SQL involves intentionally introducing redundancy into a database by incorporating data from related tables into a single table.
This process is used to optimize query performance, particularly in scenarios where frequent read operations are prioritized over write operations.
Denormalization enhances query speed by reducing the need for complex joins, as it consolidates data.
It is employed when data retrieval efficiency is critical, such as in data warehousing and reporting applications, or when real-time data consistency is not a primary concern.
However, it should be approached with caution, as it leads to data integrity issues if not managed properly.
-- Before (redundant): orders has customer_name repeated
-- After normalization:
CREATE TABLE customers (customer_id NUMBER PK, name VARCHAR2(100));
CREATE TABLE orders (order_id NUMBER PK, customer_id NUMBER REFERENCES customers);| NORMAL_FORM | RULE_VIOLATED | FIX |
|---|---|---|
| 0NF โ 1NF | Repeating groups: phone='123,456' | Separate into individual rows |
| 1NF โ 2NF | Partial dependency on composite PK | Move to separate table |
| 2NF โ 3NF | Transitive: empโdept_idโdept_name | Move dept_name to departments table |
| 3NF โ BCNF | Determinant not a candidate key | Decompose further |
Normalize step by step โ each form removes a specific type of redundancy
Denormalization in SQL involves intentionally introducing redundancy into a database by incorporating data from related tables into a single table.
Beyond the third normal form, further normalization is achieved by breaking down complex tables into smaller, more focused tables, reducing redundancy, and improving data integrity.In higher normal forms, such as the Boyce-Codd Normal Form (BCNF) and Fourth Normal Form (4NF), the emphasis is on eliminating partial and transitive dependencies.
BCNF ensures that there are no non-trivial functional dependencies of attributes on a candidate key.
This helps maintain data accuracy and prevents anomalies.4NF takes normalization a step further by addressing multi-valued dependencies within a table.
It ensures that there are no non-trivial multivalued dependencies between attributes, leading to a more robust and efficient database structure.SQL databases become more efficient in terms of storage, by achieving these higher normal forms, as data redundancy is minimized, and data integrity is enhanced.
Queries are executed more effectively, and updates are less prone to anomalies, resulting in a well-structured and high-performance database.
-- Before (redundant): orders has customer_name repeated
-- After normalization:
CREATE TABLE customers (customer_id NUMBER PK, name VARCHAR2(100));
CREATE TABLE orders (order_id NUMBER PK, customer_id NUMBER REFERENCES customers);| NORMAL_FORM | RULE_VIOLATED | FIX |
|---|---|---|
| 0NF โ 1NF | Repeating groups: phone='123,456' | Separate into individual rows |
| 1NF โ 2NF | Partial dependency on composite PK | Move to separate table |
| 2NF โ 3NF | Transitive: empโdept_idโdept_name | Move dept_name to departments table |
| 3NF โ BCNF | Determinant not a candidate key | Decompose further |
Normalize step by step โ each form removes a specific type of redundancy
Beyond the third normal form, further normalization is achieved by breaking down complex tables into smaller, more focused tables, reducing redundancy, and improving data integrity.
The process of data modeling for a complex business process involves several key steps.
It requires understanding and defining the business requirements, which involves identifying the data entities and their relationships.
This step often includes extensive discussions with stakeholders to ensure all business needs are captured.
The next phase is the creation of an Entity-Relationship (ER) diagram.
This diagram visually represents the data entities, attributes, and the relationships between them, providing a clear structure for the database.Once the ER diagram is complete, the next step is to translate it into a database schema using SQL.
This involves defining tables, primary and secondary keys, and establishing relationships through foreign keys.
The schema must be normalized to eliminate data redundancy and improve data integrity.
Normalization typically involves organizing the data into tables and establishing relationships between these tables based on rules designed to protect the data and make the database more efficient.
The database is ready after normalization ,for implementation and further optimization, if necessary, to support complex business queries and operations.
This final step ensures the database is not only structured according to business requirements but also performs efficiently under various data operations.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
The process of data modeling for a complex business process involves several key steps.
Data denormalization in SQL is the process of restructuring a relational database to reduce the complexity of relationships and joins.
This process involves combining tables and incorporating redundancy into a database.
Data denormalization optimizes read performance by reducing the number of joins needed in queries.
This strategy is particularly effective for read-heavy databases where query speed is a priority.The impact of data denormalization on performance and scalability is significant.
It enhances query performance by simplifying data structures, which leads to faster data retrieval.
It also increases the database size due to redundant data, impacting storage requirements.
Denormalization also simplifies the database design, making it more scalable in handling large volumes of data.
This approach is ideal for systems where high performance is more critical than data redundancy and storage efficiency.
-- Before (redundant): orders has customer_name repeated
-- After normalization:
CREATE TABLE customers (customer_id NUMBER PK, name VARCHAR2(100));
CREATE TABLE orders (order_id NUMBER PK, customer_id NUMBER REFERENCES customers);| NORMAL_FORM | RULE_VIOLATED | FIX |
|---|---|---|
| 0NF โ 1NF | Repeating groups: phone='123,456' | Separate into individual rows |
| 1NF โ 2NF | Partial dependency on composite PK | Move to separate table |
| 2NF โ 3NF | Transitive: empโdept_idโdept_name | Move dept_name to departments table |
| 3NF โ BCNF | Determinant not a candidate key | Decompose further |
Normalize step by step โ each form removes a specific type of redundancy
Data denormalization in SQL is the process of restructuring a relational database to reduce the complexity of relationships and joins.
Unlike MySQL with MyISAM/InnoDB, Oracle uses a unified architecture with tablespaces, data files, and advanced features like Real Application Clusters (RAC).
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- Oracle Database Architecture Components
-- 1. TABLESPACES - Logical storage units
CREATE TABLESPACE hr_data
DATAFILE '/u01/app/oracle/oradata/hr_data01.dbf' SIZE 100M
AUTOEXTEND ON NEXT 10M MAXSIZE 1G;
-- Create table in specific tablespace
CREATE TABLE employees (
employee_id NUMBER PRIMARY KEY,
first_name VARCHAR2(50),
last_name VARCHAR2(50),
salary NUMBER
) TABLESPACE hr_data;
-- 2. SCHEMAS - Logical container for database objects
CREATE USER hr_user IDENTIFIED BY password
DEFAULT TABLESPACE hr_data
TEMPORARY TABLESPACE temp;
GRANT CREATE SESSION, CREATE TABLE TO hr_user;
-- 3. SEGMENTS, EXTENTS, AND BLOCKS
-- Check segment information
SELECT segment_name, segment_type, tablespace_name, bytes/1024/1024 as size_mb
FROM user_segments
WHERE segment_name = 'EMPLOYEES';
-- Check extent information
SELECT extent_id, bytes/1024 as size_kb, blocks
FROM user_extents
WHERE segment_name = 'EMPLOYEES'
ORDER BY extent_id;
-- 4. ORACLE MEMORY STRUCTURES
-- System Global Area (SGA)
SELECT component, current_size/1024/1024 as size_mb
FROM v$sga_dynamic_components;
-- Buffer Cache
SELECT name, value
FROM v$sysstat
WHERE name IN ('db block gets', 'consistent gets', 'physical reads');
-- 5. ORACLE PROCESSES
-- Check background processes
SELECT pname, description
FROM v$bgprocess
WHERE pname IS NOT NULL;
-- 6. REDO LOGS AND ARCHIVE LOGS
SELECT group#, status, archived, bytes/1024/1024 as size_mb
FROM v$log;
-- Archive log mode check
SELECT log_mode FROM v$database;
-- 7. DATA DICTIONARY VIEWS
-- Table information
SELECT table_name, tablespace_name, num_rows, blocks
FROM user_tables
WHERE table_name = 'EMPLOYEES';
-- Index information
SELECT index_name, table_name, uniqueness, status
FROM user_indexes
WHERE table_name = 'EMPLOYEES';
-- 8. ORACLE ADVANCED FEATURES
-- Partitioning
CREATE TABLE sales_partitioned (
sale_id NUMBER,
sale_date DATE,
amount NUMBER
)
PARTITION BY RANGE (sale_date) (
PARTITION p_2023 VALUES LESS THAN (DATE '2024-01-01'),
PARTITION p_2024 VALUES LESS THAN (DATE '2025-01-01')
);
-- Materialized Views
CREATE MATERIALIZED VIEW mv_sales_summary
BUILD IMMEDIATE
REFRESH FAST ON COMMIT
AS
SELECT TRUNC(sale_date, 'MM') as sale_month,
SUM(amount) as total_sales
FROM sales
GROUP BY TRUNC(sale_date, 'MM');
-- 9. ORACLE RAC (Real Application Clusters)
-- Check RAC configuration
SELECT instance_name, host_name, status
FROM gv$instance;
-- 10. AUTOMATIC STORAGE MANAGEMENT (ASM)
-- Check ASM disk groups
SELECT name, state, type, total_mb, free_mb
FROM v$asm_diskgroup;
-- Oracle vs Other Databases:
-- - Unified architecture (no separate storage engines)
-- - Advanced clustering with RAC
-- - Automatic Storage Management (ASM)
-- - Advanced partitioning options
-- - Flashback technology
-- - Advanced security features| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| DEPT_ID | EMP_COUNT | AVG_SALARY | MAX_SALARY |
|---|---|---|---|
| 10 | 2 | 52,500 | 55,000 |
| 20 | 2 | 66,000 | 70,000 |
2 groups โ Alice+Carol in dept 10, Bob+Dave in dept 20
Unlike MySQL with MyISAM/InnoDB, Oracle uses a unified architecture with tablespaces, data files, and advanced features like Real Application Clusters (RAC).
A data warehouse is a centralized repository designed to store, manage, and retrieve large volumes of data from various sources.
Data warehouse supports analytical reporting, structured and/or ad hoc queries, and decision making.
This system integrates data from multiple databases, applications, and systems, ensuring that the information stored is consistent, reliable, and accessible.
The data in a warehouse is typically structured in a way that simplifies querying and analysis, using SQL as a primary language for data manipulation and retrieval.
Data warehouses are essential in scenarios where complex data analysis, data mining, and reporting are required.
They provide historical intelligence for business analysis, enabling companies to make data-driven decisions.
The effectiveness of a data warehouse relies on its ability to handle large-scale queries and aggregate data from diverse sources.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
A data warehouse is a centralized repository designed to store, manage, and retrieve large volumes of data from various sources.
Data mining is the process of analyzing large sets of data to discover patterns, trends, and relationships.
Data mining involves using SQL queries to extract and manipulate data from databases.
This process enables organizations to make informed decisions by identifying significant patterns and correlations in their data.
Data mining tools often integrate with SQL databases, allowing for efficient data retrieval and analysis.
The effectiveness of data mining depends on the quality of the data and thealgorithmsused.
Properly implemented, data mining provides valuable insights that guide business strategy and operational improvements.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
Data mining is the process of analyzing large sets of data to discover patterns, trends, and relationships.
OLAP databases often use a star schema or snowflake schema for organizing data, which aids in complex querying and analysis.
OLTP databases typically utilize a normalized schema to optimize for transactional speed and data integrity.
While OLAP databases facilitate strategic business decisions through data analysis, OLTP systems focus on the efficient processing of day-to-day transactions.The difference between OLAP and OLTP databases lies in their primary functions and design structures.
OLAP (Online Analytical Processing) databases are optimized for complex query processing and data analysis.
They support decision-making processes and data discovery in business environments.
OLAP systems work efficiently with large data sets, enabling multidimensional queries, often in a data warehouse setting.
These databases are designed for read-intensive operations and facilitate data aggregation, trend analysis, and complex calculations.OLTP (Online Transaction Processing) databases are tailored for managing transaction-oriented applications.
OLTP is adept at handling a large number of short, atomic transactions such as insert, update, and delete operations.
OLTP systems are characterized by their ability to maintain data integrity in multi-access environments.
They are optimized for speed and efficiency in transaction processing, ensuring rapid response times and high throughput.
OLTP databases are commonly used in retail, banking, and other industries where transactional data is constantly processed.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
OLAP databases often use a star schema or snowflake schema for organizing data, which aids in complex querying and analysis.
Data warehousing is the process of collecting, storing, and organizing large volumes of structured data from various sources into a centralized repository.
This repository, known as a data warehouse, is specifically designed for efficient querying and reporting.Data is extracted from operational databases using SQL queries, transformed to meet the desired format and structure, and then loaded into the data warehouse.
The data is typically denormalized to improve query performance, allowing for complex analytical queries to be executed quickly.One of the key benefits of data warehousing is that it provides a single source of truth for an organization's data.
SQL queries are used to extract valuable insights from this data, enabling informed decision-making.
Data warehousing also supports historical data storage, allowing organizations to analyze trends and patterns over time.Data warehousing facilitates the integration of data from multiple sources, making it easier to perform cross-functional analysis.
It also enhances data security and provides a structured environment for data governance and compliance.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
Data warehousing is the process of collecting, storing, and organizing large volumes of structured data from various sources into a centralized repository.
ETL (Extract, Transform, Load) plays a crucial role in data processing within the realm of SQL and databases.
ETL serves as the backbone for moving and manipulating data to ensure it is ready for analysis and reporting."Extract," data is gathered from various sources, such as databases, spreadsheets, or external systems, in the first phase.
SQL queries are commonly used to extract relevant data subsets.
This step ensures that the required data is accessible for further processing.SQL is employed to clean, reshape, and enrich the extracted data, in the "Transform" phase.
This involves tasks like data cleansing, validation, and aggregation.
SQL's powerful querying capabilities enable data engineers to perform complex transformations to meet specific business requirements.The transformed data is loaded into a target database or data warehouse, in the "Load" phase.
SQL's data loading capabilities facilitate this process, ensuring that the data is stored efficiently and is ready for analysis through SQL queries.The benefits of ETL in data processing are manifold.
It enables organizations to maintain clean and structured data, ensuring data accuracy.
ETL also allows for the integration of data from disparate sources into a unified format, enabling comprehensive analysis.
SQL's role in this process is pivotal, as it provides a robust language for data manipulation and transformation, making ETL processes efficient and effective.
Overall, ETL, driven by SQL, streamlines data processing, making it a fundamental component of modern data-driven organizations.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
ETL (Extract, Transform, Load) plays a crucial role in data processing within the realm of SQL and databases.
The use of SQL in business intelligence and data visualization involves querying and manipulating large datasets to extract valuable insights.
SQL, being a powerful language for managing and querying relational databases, plays a crucial role in business intelligence.
SQL allows analysts to retrieve specific data from vast databases quickly and efficiently.
This data retrieval is essential for creating reports, dashboards, and other visualization tools that aid in decision-making.SQL facilitates the aggregation, sorting, and filtering of data.
This process ensures that only relevant data is presented in visual formats such as charts, graphs, and tables.
Efficient SQL queries optimize the performance of business intelligence tools, ensuring that real-time data analysis is possible.
SQL also supports complex analytics operations like joins, window functions, and subqueries, which are indispensable for advanced data analysis.
SQL enables organizations to derive actionable insights from their data, when used effectively, driving better business strategies and outcomes.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
The use of SQL in business intelligence and data visualization involves querying and manipulating large datasets to extract valuable insights.
SQL Injection is a malicious technique where an attacker injects malicious SQL queries into input fields or parameters of a SQL query, potentially allowing unauthorized access to a database or data manipulation.
Always use parameterized queries, validate user input, and sanitize data to ensure that user-provided values are not executed as SQL code.
Limit database permissions to the minimum required for each user, and keep your database software up to date with security patches.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| APPROACH | INPUT | SAFE | RESULT |
|---|---|---|---|
| String concat | ' OR '1'='1 | โ No | Returns ALL 4 rows โ security breach |
| Bind variable :p1 | ' OR '1'='1 | โ Yes | 0 rows โ treated as literal data |
Bind variables prevent SQL injection โ the #1 Oracle security best practice
SQL Injection is a malicious technique where an attacker injects malicious SQL queries into input fields or parameters of a SQL query, potentially allowing unauthorized access to a database or data manipulation.
SQL injection is a security vulnerability in SQL-based applications.
SQL injection occurs when untrusted data is included in SQL queries, allowing attackers to manipulate the database.
You must validate and sanitize input data thoroughly to prevent SQL injection in stored procedures.
This involves using parameterized queries or prepared statements to separate SQL code from user input.Input validation is applied to ensure that only valid and expected data is processed.
Properly configuring access controls and permissions for stored procedures is also crucial in preventing unauthorized access and data manipulation.
Regular security audits and updates are essential to stay protected against evolving threats.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT employee_id, employee_name, salary
FROM employees
WHERE salary > 50000
ORDER BY salary DESC;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| APPROACH | INPUT | SAFE | RESULT |
|---|---|---|---|
| String concat | ' OR '1'='1 | โ No | Returns ALL 4 rows โ security breach |
| Bind variable :p1 | ' OR '1'='1 | โ Yes | 0 rows โ treated as literal data |
Bind variables prevent SQL injection โ the #1 Oracle security best practice
SQL injection is a security vulnerability in SQL-based applications.
The best practices for securing a SQL database involve restricting access, encryption, regular updates, and a strong backup and recovery plan to protect data and maintain the database's integrity and availability.It is essential to restrict access to the SQL database.
Ensure that only authorized users have permissions to access and modify the data.
Use strong authentication mechanisms, such as multi-factor authentication, to enhance security.
Encryption plays a crucial role in securing data.
Employ encryption at rest and in transit to safeguard data both in storage and during transmission.
This prevents unauthorized access to sensitive information.Regularly update and patch the database management system to address known vulnerabilities.
Keeping the system up to date is vital in mitigating potential security risks.
Implement a robust backup and disaster recovery strategy.
Regularly backup the database to ensure data is restored in case of unexpected events or data corruption.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
The best practices for securing a SQL database involve restricting access, encryption, regular updates, and a strong backup and recovery plan to protect data and maintain the database's integrity and availability.
The best practices for database encryption and securing sensitive data in SQL involve several key strategies.
Encrypting data at rest and in transit ensures that sensitive information remains protected from unauthorized access.
Implementing Transparent Data Encryption (TDE) encrypts the database at the file level, adding a layer of security without altering the existing applications.
Using column-level encryption protects specific sensitive data, such as credit card numbers or personal identifiers, by encrypting individual columns in a table.It is essential to manage encryption keys securely.
Store keys in a secure, centralized key management solution, separate from the data they protect.
Regularly update and rotate encryption keys to maintain security.
Implement strong access controls and audit trails to monitor who accesses the data and how it is used.
Employ SQL injection prevention techniques to protect against attacks that can exploit vulnerabilities in SQL databases.
Encrypt backup data to ensure that copies of the database remain secure.
Implement these practices to effectively secure sensitive data in SQL databases.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
The best practices for database encryption and securing sensitive data in SQL involve several key strategies.
The role of SQL in data governance and compliance is pivotal.
SQL serves as the backbone for managing and querying databases, which are crucial for enforcing data governance policies.
It ensures that data remains consistent, accurate, and accessible, which is essential for regulatory compliance.
SQL facilitates the implementation of data governance frameworks by enabling the creation, manipulation, and retrieval of data in a structured and secure manner.SQL provides tools for auditing and monitoring data usage.
SQL allows for the tracking of data access and modifications, ensuring that data handling aligns with legal and organizational standards.
This capability is fundamental for organizations to adhere to regulations like GDPR, HIPAA, and others.
SQL supports data integrity and security measures, a necessity for maintaining the confidentiality and reliability of sensitive information.
This support is critical in preventing data breaches and ensuring compliance with data protection laws.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
The role of SQL in data governance and compliance is pivotal.
Use bind variables (parameterized queries), validate input data, avoid dynamic SQL when possible, and use proper escaping techniques.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- 1. Use Bind Variables (Parameterized Queries)
-- BAD - Vulnerable to SQL injection
CREATE OR REPLACE PROCEDURE bad_login(p_username VARCHAR2, p_password VARCHAR2) IS
v_sql VARCHAR2(1000);
v_count NUMBER;
BEGIN
v_sql := 'SELECT COUNT(*) FROM users WHERE username = ''' ||
p_username || ''' AND password = ''' || p_password || '''';
EXECUTE IMMEDIATE v_sql INTO v_count;
END;
-- GOOD - Using bind variables
CREATE OR REPLACE PROCEDURE safe_login(p_username VARCHAR2, p_password VARCHAR2) IS
v_count NUMBER;
BEGIN
SELECT COUNT(*) INTO v_count
FROM users
WHERE username = p_username
AND password = p_password;
END;
-- 2. For dynamic SQL, use bind variables
CREATE OR REPLACE PROCEDURE safe_dynamic_query(
p_table_name VARCHAR2,
p_employee_id NUMBER
) IS
v_sql VARCHAR2(1000);
v_count NUMBER;
BEGIN
-- Validate table name against whitelist
IF p_table_name NOT IN ('employees', 'departments', 'jobs') THEN
RAISE_APPLICATION_ERROR(-20001, 'Invalid table name');
END IF;
v_sql := 'SELECT COUNT(*) FROM ' || p_table_name ||
' WHERE employee_id = :1';
EXECUTE IMMEDIATE v_sql INTO v_count USING p_employee_id;
END;
-- 3. Input validation function
CREATE OR REPLACE FUNCTION validate_input(p_input VARCHAR2) RETURN VARCHAR2 IS
BEGIN
-- Remove dangerous characters
RETURN REGEXP_REPLACE(p_input, '[''";\\-]', '');
END;
-- 4. Use DBMS_ASSERT for validation
CREATE OR REPLACE PROCEDURE validate_object_name(p_table_name VARCHAR2) IS
v_valid_name VARCHAR2(128);
BEGIN
-- This will raise an exception if not a valid SQL name
v_valid_name := DBMS_ASSERT.SQL_OBJECT_NAME(p_table_name);
-- Additional whitelist check
IF v_valid_name NOT IN ('EMPLOYEES', 'DEPARTMENTS') THEN
RAISE_APPLICATION_ERROR(-20002, 'Table not allowed');
END IF;
END;
-- 5. Safe cursor with bind variables
CREATE OR REPLACE PROCEDURE search_employees(
p_search_term VARCHAR2,
p_cursor OUT SYS_REFCURSOR
) IS
BEGIN
OPEN p_cursor FOR
SELECT employee_id, first_name, last_name
FROM employees
WHERE UPPER(first_name) LIKE UPPER('%' || p_search_term || '%')
OR UPPER(last_name) LIKE UPPER('%' || p_search_term || '%');
END;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| APPROACH | INPUT | SAFE | RESULT |
|---|---|---|---|
| String concat | ' OR '1'='1 | โ No | Returns ALL 4 rows โ security breach |
| Bind variable :p1 | ' OR '1'='1 | โ Yes | 0 rows โ treated as literal data |
Bind variables prevent SQL injection โ the #1 Oracle security best practice
Use bind variables (parameterized queries), validate input data, avoid dynamic SQL when possible, and use proper escaping techniques.
Use Oracle Virtual Private Database (VPD) policies to implement row-level security that automatically filters data based on user context.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Create policy function for row-level security
CREATE OR REPLACE FUNCTION employee_security_policy(
schema_var IN VARCHAR2,
table_var IN VARCHAR2
) RETURN VARCHAR2 IS
v_user VARCHAR2(100);
v_predicate VARCHAR2(4000);
BEGIN
v_user := SYS_CONTEXT('USERENV', 'SESSION_USER');
-- Different access levels based on user
CASE
WHEN v_user IN ('HR_ADMIN', 'SYSTEM') THEN
-- Full access
v_predicate := '1=1';
WHEN v_user LIKE 'MGR_%' THEN
-- Managers can see their department employees
v_predicate := 'department_id IN (
SELECT department_id FROM departments
WHERE manager_id = (
SELECT employee_id FROM employees
WHERE user_id = ''' || v_user || '''
)
)';
WHEN v_user LIKE 'EMP_%' THEN
-- Employees can only see their own record
v_predicate := 'employee_id = (
SELECT employee_id FROM employees
WHERE user_id = ''' || v_user || '''
)';
ELSE
-- Default: no access
v_predicate := '1=0';
END CASE;
RETURN v_predicate;
END;
/
-- Apply the policy to employees table
BEGIN
DBMS_RLS.ADD_POLICY(
object_schema => 'HR',
object_name => 'EMPLOYEES',
policy_name => 'EMPLOYEE_ACCESS_POLICY',
function_schema => 'HR',
policy_function => 'EMPLOYEE_SECURITY_POLICY',
statement_types => 'SELECT, INSERT, UPDATE, DELETE',
update_check => TRUE
);
END;
/
-- Context-based security policy
CREATE OR REPLACE FUNCTION dept_security_policy(
schema_var IN VARCHAR2,
table_var IN VARCHAR2
) RETURN VARCHAR2 IS
v_dept_id NUMBER;
v_role VARCHAR2(100);
BEGIN
-- Get user's department and role from application context
v_dept_id := SYS_CONTEXT('USER_CONTEXT', 'DEPARTMENT_ID');
v_role := SYS_CONTEXT('USER_CONTEXT', 'USER_ROLE');
IF v_role = 'ADMIN' THEN
RETURN '1=1'; -- Full access
ELSIF v_role = 'MANAGER' THEN
RETURN 'department_id = ' || v_dept_id;
ELSIF v_role = 'EMPLOYEE' THEN
RETURN 'employee_id = ' || SYS_CONTEXT('USER_CONTEXT', 'EMPLOYEE_ID');
ELSE
RETURN '1=0'; -- No access
END IF;
END;
/
-- Create application context
CREATE OR REPLACE CONTEXT user_context USING security_pkg;
-- Package to set context
CREATE OR REPLACE PACKAGE security_pkg IS
PROCEDURE set_user_context(
p_user_id VARCHAR2,
p_department_id NUMBER,
p_employee_id NUMBER,
p_role VARCHAR2
);
END;
/
CREATE OR REPLACE PACKAGE BODY security_pkg IS
PROCEDURE set_user_context(
p_user_id VARCHAR2,
p_department_id NUMBER,
p_employee_id NUMBER,
p_role VARCHAR2
) IS
BEGIN
DBMS_SESSION.SET_CONTEXT('USER_CONTEXT', 'USER_ID', p_user_id);
DBMS_SESSION.SET_CONTEXT('USER_CONTEXT', 'DEPARTMENT_ID', p_department_id);
DBMS_SESSION.SET_CONTEXT('USER_CONTEXT', 'EMPLOYEE_ID', p_employee_id);
DBMS_SESSION.SET_CONTEXT('USER_CONTEXT', 'USER_ROLE', p_role);
END;
END;
/
-- Column-level security policy
CREATE OR REPLACE FUNCTION salary_security_policy(
schema_var IN VARCHAR2,
table_var IN VARCHAR2
) RETURN VARCHAR2 IS
v_role VARCHAR2(100);
BEGIN
v_role := SYS_CONTEXT('USER_CONTEXT', 'USER_ROLE');
-- Only managers and HR can see salaries
IF v_role IN ('MANAGER', 'HR_ADMIN') THEN
RETURN '1=1';
ELSE
RETURN '1=0';
END IF;
END;
/
-- Apply column-level policy
BEGIN
DBMS_RLS.ADD_POLICY(
object_schema => 'HR',
object_name => 'EMPLOYEES',
policy_name => 'SALARY_COLUMN_POLICY',
function_schema => 'HR',
policy_function => 'SALARY_SECURITY_POLICY',
statement_types => 'SELECT',
sec_relevant_cols => 'SALARY,COMMISSION_PCT',
sec_relevant_cols_opt => DBMS_RLS.ALL_ROWS
);
END;
/
-- Dynamic policy based on time and IP
CREATE OR REPLACE FUNCTION time_ip_security_policy(
schema_var IN VARCHAR2,
table_var IN VARCHAR2
) RETURN VARCHAR2 IS
v_current_hour NUMBER;
v_ip_address VARCHAR2(100);
v_allowed_networks SYS.ODCIVARCHAR2LIST;
BEGIN
v_current_hour := EXTRACT(HOUR FROM SYSTIMESTAMP);
v_ip_address := SYS_CONTEXT('USERENV', 'IP_ADDRESS');
v_allowed_networks := SYS.ODCIVARCHAR2LIST('192.168.1.%', '10.0.0.%');
-- Business hours check (9 AM to 6 PM)
IF v_current_hour < 9 OR v_current_hour > 18 THEN
RETURN '1=0'; -- No access outside business hours
END IF;
-- IP address check
FOR i IN 1..v_allowed_networks.COUNT LOOP
IF v_ip_address LIKE v_allowed_networks(i) THEN
RETURN '1=1'; -- Allow access from trusted networks
END IF;
END LOOP;
RETURN '1=0'; -- Default deny
END;
/
-- View current policies
SELECT object_owner, object_name, policy_name, function,
enable_flag, sel, ins, upd, del
FROM dba_policies
WHERE object_name = 'EMPLOYEES';
-- Test the security policy
-- Set context for a manager user
BEGIN
security_pkg.set_user_context('MGR_001', 10, 100, 'MANAGER');
END;
/
-- This query will only return employees from department 10
SELECT employee_id, first_name, last_name, department_id
FROM employees;
-- Drop policy if needed
BEGIN
DBMS_RLS.DROP_POLICY(
object_schema => 'HR',
object_name => 'EMPLOYEES',
policy_name => 'EMPLOYEE_ACCESS_POLICY'
);
END;
/
-- Audit policy usage
SELECT policy_name, sql_text, timestamp, username
FROM dba_fga_audit_trail
WHERE object_name = 'EMPLOYEES'
ORDER BY timestamp DESC;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMPLOYEE | MANAGER |
|---|---|
| Bob Smith | Alice Johnson |
| Carol White | Alice Johnson |
| Dave Brown | Bob Smith |
3 rows โ self join pairs each employee with their manager's name
Use Oracle Virtual Private Database (VPD) policies to implement row-level security that automatically filters data based on user context.
GRANT gives a user or role specific privileges, such as the ability to SELECT, INSERT, UPDATE, or DELETE on a table, or system-level privileges like CREATE SESSION.
REVOKE removes previously granted privileges from a user or role, taking away their ability to perform those actions.
Privileges can be granted directly to a user or bundled into a role, which is then granted to multiple users, simplifying privilege management.
The WITH GRANT OPTION clause allows a user who received a privilege to also grant that same privilege to others, which should be used carefully for security reasons.
GRANT SELECT, INSERT ON employees TO hr_clerk;
REVOKE INSERT ON employees FROM hr_clerk;| GRANTEE | TABLE_NAME | PRIVILEGE |
|---|---|---|
| HR_CLERK | EMPLOYEES | SELECT |
After the REVOKE, hr_clerk retains SELECT but no longer has INSERT on the employees table
GRANT assigns privileges to users or roles and REVOKE removes them, forming Oracle's Data Control Language for managing who can do what within the database.
Database sharding is a method of distributing data across multiple servers or databases to enhance performance and manage large datasets effectively.
Database sharding involves partitioning a database into smaller, more manageable pieces, known as shards, each of which are hosted on different servers.
This technique is particularly useful in scenarios where the data volume is large and growing, necessitating distributed storage and processing to maintain performance.
Sharding optimizes query response times and resource utilization, ensuring that a single server is not overwhelmed with too many requests.
It enhances data management and scalability in distributed database environments.
Sharding becomes essential when a database grows beyond the capacity of a single server or when application demands exceed the performance capabilities of a centralized database.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
Database sharding is a method of distributing data across multiple servers or databases to enhance performance and manage large datasets effectively.
Database partitioning involves dividing a large database into smaller, more manageable segments, enhancing performance and simplifying maintenance.
Database partitioning is essential in SQL for handling large volumes of data efficiently.
There are primarily three types of database partitioning: range partitioning, list partitioning, and hash partitioning.Range partitioning divides data based on predefined ranges of values, typically used for chronological data like dates or numbers.
Range partitioning is effective for queries filtering data within specific value ranges.
List partitioning categorizes data into partitions based on a set list of values.
List partitioning is particularly useful when dealing with categorical data, allowing for quick data retrieval based on specified criteria.Hash partitioning employs a hash function to distribute data across partitions uniformly.
Hash partitioning ensures balanced data distribution, optimizing query performance by minimizing data skewness.
Use hash partitioning when evenly distributed data is crucial for system performance.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
Database partitioning involves dividing a large database into smaller, more manageable segments, enhancing performance and simplifying maintenance.
Database replication in SQL ensures data availability, load balancing, disaster recovery, and scalability, making it a valuable strategy for maintaining robust and high-performing database systems.
Database replication in the context of SQL serves as a vital mechanism for duplicating and maintaining database content across multiple servers.
This process ensures data consistency, availability, and fault tolerance, benefiting SQL systems in several ways.Replication enhances data availability by creating redundant copies of the database on separate servers.
This redundancy enables continued access to data even in the event of server failures or maintenance, minimizing downtime.
It contributes to load balancing by distributing read queries among replicated servers, reducing the workload on the primary server.
This optimizes query performance, resulting in faster response times for users.Database replication supports disaster recovery.
In case of data corruption or loss, the replicated copies are used to restore the database to a previous state, safeguarding critical information.
Replication aids in scaling SQL systems horizontally.
Additional servers are added to the replication setup as data grows, accommodating increased data demands without significantly affecting performance.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
Database replication in SQL ensures data availability, load balancing, disaster recovery, and scalability, making it a valuable strategy for maintaining robust and high-performing database systems.
Ensuring data consistency across distributed databases in SQL involves key techniques are distributed transactions, replication, conflict resolution and data versioning.
Use of distributed transactions guarantees that operations across multiple databases either all succeed or all fail, maintaining data integrity.
This is achieved through two-phase commit protocols, where the first phase prepares all databases for a transaction and the second phase either commits or rolls back the transaction based on a unanimous agreement among the databases.Another important technique is the implementation of replication, where changes made in one database are automatically mirrored in others.
This ensures that all distributed databases hold consistent and up-to-date data.
Conflict resolution strategies are crucial when simultaneous updates occur in different databases; these strategies determine which version of the data is considered authoritative.
Replication can be synchronous, where transactions must be confirmed across all databases before completion, or asynchronous, where transactions are replicated after being committed in the primary database.
The choice between synchronous and asynchronous replication depends on the specific requirements of data consistency and system performance.Data versioning is also employed to manage changes over time, allowing systems to track and manage updates to data sets.
This approach helps in resolving conflicts in distributed environments by maintaining a history of changes.
Lastly, regular synchronization checks are essential to ensure that data remains consistent across all nodes in the distributed system.
These checks identify and resolve discrepancies, maintaining the overall integrity and consistency of the distributed database system.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
Ensuring data consistency across distributed databases in SQL involves key techniques are distributed transactions, replication, conflict resolution and data versioning.
The choice between SQL and NoSQL databases hinges on your specific project requirements.
SQL databases are ideal for structured data, complex queries, and data integrity, while NoSQL databases offer flexibility, scalability, and adaptability to changing data needs.
Careful evaluation of your project's characteristics and goals will guide you to the most suitable database solution.SQL databases, known for their structured and relational data storage, excel in scenarios where data integrity, consistency, and complex queries are paramount.
They are well-suited for applications that demand ACID (Atomicity, Consistency, Isolation, Durability) compliance, such as financial systems and traditional relational data models.
SQL databases offer a robust schema that ensures data conformity and enforces relationships between tables.NoSQL databases provide greater flexibility and scalability, making them ideal for scenarios where rapid growth and diverse data types are anticipated.
They shine in applications requiring high throughput and low-latency data access, such as social media platforms and IoT (Internet of Things) applications.
NoSQL databases handle semi-structured and unstructured data effectively, making them suitable for dynamic data environments.Another crucial consideration is the need for horizontal scalability.
SQL databases typically scale vertically by adding more powerful hardware, while NoSQL databases excel at horizontal scaling by distributing data across multiple servers or nodes, enabling seamless expansion as data volume increases.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
The choice between SQL and NoSQL databases hinges on your specific project requirements.
Emerging trends and technologies in SQL database management focus on enhancing efficiency, scalability, and data integration.
SQL databases increasingly adopt cloud-based solutions, ensuring high availability and flexible scaling options to accommodate varying data loads.
Automation in database management is a notable trend, reducing manual overhead and improving accuracy in routine tasks like data backup, recovery, and performance tuning.Integration with big data technologies and machine learning is transforming SQL database management.
This integration enables sophisticated data analytics and predictive modeling, directly within the database environment.
Real-time data processing capabilities are being integrated into SQL databases, allowing businesses to make data-driven decisions swiftly.
Advancements in security features ensure robust protection of sensitive data, a crucial aspect in an era of heightened data privacy concerns.
These trends collectively contribute to more powerful, secure, and efficient SQL database systems.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
Emerging trends and technologies in SQL database management focus on enhancing efficiency, scalability, and data integration.
SQL's XML and JSON data types are essential tools for managing structured data within relational databases.
They enable the storage and manipulation of XML and JSON data, streamlining data integration and enhancing the capabilities of SQL databases in handling diverse data formats.XML data types allow SQL databases to store data in the Extensible Markup Language (XML) format.
XML is a hierarchical and self-descriptive format that is particularly useful for representing complex data structures.
SQL developers store, by using XML data types, query, and manipulate XML data directly within the database.
This capability is especially beneficial when dealing with data from web services or applications that communicate using XML.JSON data types enable SQL databases to work with data in JavaScript Object Notation (JSON) format.
JSON is a lightweight and flexible format that is widely used for data interchange in web applications.
SQL's support for JSON data types allows for the storage and querying of JSON documents, making it easier to work with data generated by web APIs or NoSQL databases.The use of XML and JSON data types in SQL provides several advantages.
It allows for better integration of data from diverse sources, as XML and JSON are common formats for data exchange on the web.
It also simplifies data manipulation and querying, enabling developers to extract specific information from complex documents efficiently.
It enhances the versatility of SQL databases, making them more adaptable to modern data-driven applications.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
SQL's XML and JSON data types are essential tools for managing structured data within relational databases.
Non-relational features in SQL, like JSON support, have become increasingly relevant in modern database management.
SQL databases traditionally focus on structured data, but with the advent of JSON support, they now efficiently handle unstructured data as well.
JSON, or JavaScript Object Notation, is a lightweight format for storing and transporting data, often used when data is sent from a server to a web page.SQL integrates JSON support by allowing JSON data to be queried and manipulated using standard SQL queries.
This integration enhances the flexibility of SQL databases, enabling them to store and process both structured and unstructured data seamlessly.
SQL commands extract and manipulate JSON data, making it possible to combine the strengths of relational data handling with the flexibility of JSON.
This feature is vital in applications where data comes in various formats and needs to be integrated within a single database system.
JSON support in SQL signifies a significant step towards more versatile and adaptable database solutions, catering to the diverse data handling needs of modern applications.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
Non-relational features in SQL, like JSON support, have become increasingly relevant in modern database management.
Oracle provides JSON functions and operators to store, query, and manipulate JSON data efficiently using SQL.
-- Create table with JSON column
CREATE TABLE customer_data (
customer_id NUMBER,
customer_info JSON
);
-- Insert JSON data
INSERT INTO customer_data VALUES (
1,
JSON_OBJECT(
'name' VALUE 'John Doe',
'age' VALUE 30,
'email' VALUE 'john@email.com',
'address' VALUE JSON_OBJECT(
'street' VALUE '123 Main St',
'city' VALUE 'New York',
'zipcode' VALUE '10001'
),
'orders' VALUE JSON_ARRAY(101, 102, 103)
)
);
-- Query JSON data using dot notation
SELECT customer_id,
customer_info.name as customer_name,
customer_info.age as age,
customer_info.address.city as city
FROM customer_data;
-- Query JSON arrays
SELECT customer_id,
customer_info.name as customer_name,
jo.order_id
FROM customer_data c,
JSON_TABLE(c.customer_info, '$.orders[*]'
COLUMNS (order_id NUMBER PATH '$')) jo;
-- Complex JSON queries with JSON_TABLE
SELECT customer_id, name, age, street, city, order_id
FROM customer_data,
JSON_TABLE(customer_info, '$'
COLUMNS (
name VARCHAR2(100) PATH '$.name',
age NUMBER PATH '$.age',
street VARCHAR2(200) PATH '$.address.street',
city VARCHAR2(100) PATH '$.address.city',
NESTED PATH '$.orders[*]' COLUMNS (
order_id NUMBER PATH '$'
)
));
-- JSON aggregation
SELECT JSON_OBJECT(
'total_customers' VALUE COUNT(*),
'avg_age' VALUE AVG(customer_info.age.number()),
'cities' VALUE JSON_ARRAYAGG(DISTINCT customer_info.address.city.string())
) as summary
FROM customer_data;
-- Update JSON data
UPDATE customer_data
SET customer_info = JSON_MERGEPATCH(
customer_info,
JSON_OBJECT('phone' VALUE '555-1234')
)
WHERE customer_id = 1;
-- Search within JSON
SELECT customer_id, customer_info.name
FROM customer_data
WHERE JSON_EXISTS(customer_info, '$.address.city?(@ == "New York")');
-- Create indexes on JSON data
CREATE INDEX idx_customer_name ON customer_data (JSON_VALUE(customer_info, '$.name'));
CREATE INDEX idx_customer_city ON customer_data (JSON_VALUE(customer_info, '$.address.city'));| COUNT | AVG_SALARY | MIN_SALARY | MAX_SALARY | SUM_SALARY |
|---|---|---|---|---|
| 4 | 59,250 | 50,000 | 70,000 | 237,000 |
Aggregate over all 4 employees โ AVG=(50+62+55+70)/4=59,250
Oracle provides JSON functions and operators to store, query, and manipulate JSON data efficiently using SQL.
Handling large-scale data migrations in SQL requires careful planning and execution to ensure the integrity of the data and the efficiency of the process.
One fundamental approach is to leverage SQL's built-in features and best practices.It's crucial to utilize SQL's transactional capabilities.
Transactions allow you to group multiple SQL statements into a single unit of work.
Ensure that either all changes are applied successfully or none at all, maintaining data consistency, by wrapping your data migration operations in transactions.SQL provides powerful tools for data transformation.
SQL's SELECT INTO and INSERT INTO statements allow you to extract and load data efficiently between tables.
SQL's JOIN operations enable you to combine data from multiple sources into a single coherent dataset, facilitating complex migrations.Consider using ETL (Extract, Transform, Load) tools that are designed specifically for large-scale data migrations.
These tools streamline the process by automating data extraction, transformation, and loading tasks, reducing the risk of errors and optimizing performance.Handling large-scale data migrations in SQL involves leveraging SQL's transactional capabilities, utilizing data transformation operations, and considering specialized ETL tools.
This approach ensures a smooth and reliable migration process while maintaining data integrity and performance.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
Handling large-scale data migrations in SQL requires careful planning and execution to ensure the integrity of the data and the efficiency of the process.
Managing backup and recovery in SQL databases involves several key strategies to ensure data integrity and availability.
The core approach includes regularly scheduled backups, which consist of full backups, differential backups, and transaction log backups.
Full backups capture the entire database at a given point in time, providing a comprehensive data snapshot.
Differential backups record changes made since the last full backup, offering a balance between storage efficiency and recovery speed.
Transaction log backups, essential for databases in full recovery mode, capture every transaction in the database, allowing for point-in-time recovery and minimal data loss in case of a failure.Recovery in SQL databases utilizes these backups to restore the database to a specific state.
The recovery process typically starts with the most recent full backup, In the event of data loss or corruption, followed by the application of the latest differential backup and then the relevant transaction log backups up to the point of failure or a specific point in time.
This method ensures data recovery with minimal loss.
The effectiveness of the backup and recovery process depends on regular testing and validation of backups, ensuring they are complete and restorable.
Automating the backup process and monitoring backup health are also crucial steps in maintaining a robust backup and recovery strategy for SQL databases.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
Managing backup and recovery in SQL databases involves several key strategies to ensure data integrity and availability.
Database auditing stands as a crucial process in managing and securing SQL databases.
Database auditing involves the monitoring and recording of database activities, ensuring that data integrity and security are maintained.
This process is essential for detecting any unauthorized or suspicious activities that could compromise the database's integrity.
Through auditing, database administrators gain visibility into database operations, enabling them to track changes, access patterns, and potential security breaches.Auditing in SQL databases typically employs two main methods: using built-in SQL Server audit capabilities and implementing custom auditing solutions with triggers.
SQL Server's built-in audit features provide a comprehensive and easy-to-use framework for tracking and logging a variety of activities at different levels, such as the database or server level.
This method offers a straightforward approach for administrators to enforce security policies and comply with regulatory requirements.
Custom triggers allow for more granular control and flexibility.
They enable the execution of specific actions in response to certain events in the database, allowing administrators to tailor the auditing process to specific needs of the organization.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
Database auditing stands as a crucial process in managing and securing SQL databases.
Approaching error handling in SQL stored procedures involves several key strategies.
First, utilize the TRY...CATCH block to capture and manage errors.
This structure allows the execution of code in the TRY block, and if an error occurs, control is passed to the CATCH block where the error can be handled gracefully.
Implementing error logging is crucial, typically by inserting error details into a dedicated table.
This practice enables the tracking and analysis of errors over time.Another important aspect is the use of RAISERROR or THROW statements to generate custom error messages, allowing for more specific and informative feedback about issues encountered in the stored procedure.
Ensure proper transaction management by using COMMIT and ROLLBACK within the TRY...CATCH block.
This ensures that the database remains consistent and any changes made during the procedure are either fully committed or rolled back, depending on the success or failure of the procedure.
Employing these methods ensures robust error handling in SQL stored procedures, enhancing reliability and maintainability of the database application.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
Approaching error handling in SQL stored procedures involves several key strategies.
Oracle provides multiple backup and recovery methods including RMAN (Recovery Manager), Data Pump, and traditional export/import utilities.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- 1. RMAN (Recovery Manager) - Recommended method
-- Connect to RMAN
-- rman target /
-- Full database backup
BACKUP DATABASE;
-- Incremental backup
BACKUP INCREMENTAL LEVEL 1 DATABASE;
-- Backup specific tablespace
BACKUP TABLESPACE users;
-- Backup archive logs
BACKUP ARCHIVELOG ALL;
-- 2. Data Pump Export/Import
-- Full database export (as SYSDBA)
-- expdp system/password FULL=Y DIRECTORY=DATA_PUMP_DIR DUMPFILE=fulldb.dmp
-- Schema export
-- expdp hr/password SCHEMAS=hr DIRECTORY=DATA_PUMP_DIR DUMPFILE=hr_schema.dmp
-- Table export
-- expdp hr/password TABLES=employees,departments DIRECTORY=DATA_PUMP_DIR DUMPFILE=hr_tables.dmp
-- Import examples
-- impdp system/password FULL=Y DIRECTORY=DATA_PUMP_DIR DUMPFILE=fulldb.dmp
-- Schema import
-- impdp system/password SCHEMAS=hr DIRECTORY=DATA_PUMP_DIR DUMPFILE=hr_schema.dmp
-- 3. SQL*Plus Export (deprecated but still used)
-- Create directory object
CREATE OR REPLACE DIRECTORY backup_dir AS '/u01/backup';
-- Traditional export/import (exp/imp utilities)
-- exp hr/password file=hr_backup.dmp owner=hr
-- imp hr/password file=hr_backup.dmp fromuser=hr touser=hr_new
-- 4. Logical backup using SQL*Plus
SPOOL /u01/backup/employees_backup.sql
SELECT 'INSERT INTO employees VALUES (' ||
employee_id || ',' ||
'''' || first_name || ''',' ||
'''' || last_name || ''',' ||
'''' || email || ''',' ||
'DATE ''' || TO_CHAR(hire_date, 'YYYY-MM-DD') || ''');'
FROM employees;
SPOOL OFF;
-- 5. Flashback Database (Oracle 10g+)
-- Enable flashback database
ALTER DATABASE FLASHBACK ON;
-- Create restore point
CREATE RESTORE POINT before_maintenance;
-- Flashback database to restore point
FLASHBACK DATABASE TO RESTORE POINT before_maintenance;
-- Flashback table to specific time
FLASHBACK TABLE employees TO TIMESTAMP
(SYSTIMESTAMP - INTERVAL '1' HOUR);
-- 6. RMAN Recovery scenarios
-- Recover database
RECOVER DATABASE;
-- Point-in-time recovery
RECOVER DATABASE UNTIL TIME '2024-01-15 14:30:00';
-- Recover specific tablespace
RECOVER TABLESPACE users;
-- 7. Cold backup (database shutdown)
-- Shutdown database
SHUTDOWN IMMEDIATE;
-- Copy all datafiles, control files, and redo logs
-- cp /u01/app/oracle/oradata/ORCL/*.dbf /backup/
-- cp /u01/app/oracle/oradata/ORCL/*.ctl /backup/
-- cp /u01/app/oracle/oradata/ORCL/*.log /backup/
-- Startup database
STARTUP;
-- 8. Check backup status
SELECT session_key, input_type, status,
start_time, end_time,
input_bytes/1024/1024 as input_mb
FROM v$rman_backup_job_details
ORDER BY start_time DESC;
-- 9. Validate backups
VALIDATE BACKUPSET ALL;
-- Cross-check backups
CROSSCHECK BACKUP;
-- 10. Archive log management
-- Enable archive log mode
SHUTDOWN IMMEDIATE;
STARTUP MOUNT;
ALTER DATABASE ARCHIVELOG;
ALTER DATABASE OPEN;
-- Check archive log status
SELECT log_mode FROM v$database;
-- Backup and delete archive logs
BACKUP ARCHIVELOG ALL DELETE INPUT;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | DEPT_ID | SALARY |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | 20 | 70,000 |
All 4 rows returned โ ordered by salary DESC
Oracle provides multiple backup and recovery methods including RMAN (Recovery Manager), Data Pump, and traditional export/import utilities.
Flashback Query lets you view the state of data as it existed at a previous point in time, using the AS OF clause with either a timestamp or an SCN (System Change Number).
It relies on undo data retained by the database, so it is only available for as far back as the configured undo retention period allows.
Flashback Query is commonly used to recover from accidental data changes by comparing current data to its earlier state, or to restore specific rows without a full database restore.
Related features include Flashback Table, which can rewind an entire table to a previous point in time, and Flashback Drop, which can recover a dropped table from the recycle bin.
| emp_id | emp_name | salary |
|---|---|---|
| 101 | Alice Johnson | 55,000 |
SELECT emp_name, salary
FROM employees AS OF TIMESTAMP (SYSTIMESTAMP - INTERVAL '1' HOUR)
WHERE emp_id = 101;| EMP_NAME | SALARY_ONE_HOUR_AGO |
|---|---|
| Alice Johnson | 50,000 |
The query reveals the salary value as it existed one hour ago, before a recent update changed it to 55,000
Flashback Query uses the AS OF clause to view data as it existed at an earlier timestamp or SCN, relying on retained undo data rather than requiring a restore from backup.
There are multiple approaches to find the Nth highest salary in Oracle using ranking functions, subqueries, and analytical functions.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- Method 1: Using ROW_NUMBER() - Most efficient for unique values
SELECT salary
FROM (
SELECT salary, ROW_NUMBER() OVER (ORDER BY salary DESC) as rn
FROM employee
WHERE salary IS NOT NULL
)
WHERE rn = :N;
-- Method 2: Using DENSE_RANK() - Handles duplicates better
SELECT DISTINCT salary
FROM (
SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) as rnk
FROM employee
WHERE salary IS NOT NULL
)
WHERE rnk = :N;
-- Method 3: Using RANK() - Standard ranking with gaps
SELECT DISTINCT salary
FROM (
SELECT salary, RANK() OVER (ORDER BY salary DESC) as rank_num
FROM employee
WHERE salary IS NOT NULL
)
WHERE rank_num = :N;
-- Method 4: Using ROWNUM (Oracle-specific)
SELECT salary
FROM (
SELECT DISTINCT salary
FROM employee
WHERE salary IS NOT NULL
ORDER BY salary DESC
)
WHERE ROWNUM = :N;
-- Method 5: Using OFFSET and FETCH (Oracle 12c+)
SELECT DISTINCT salary
FROM employee
WHERE salary IS NOT NULL
ORDER BY salary DESC
OFFSET (:N - 1) ROWS FETCH NEXT 1 ROWS ONLY;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_NAME | SALARY | ROW_NUM | RANK | DENSE_RANK |
|---|---|---|---|---|
| Dave Brown | 70,000 | 1 | 1 | 1 |
| Bob Smith | 62,000 | 2 | 2 | 2 |
| Carol White | 55,000 | 3 | 3 | 3 |
| Alice Johnson | 50,000 | 4 | 4 | 4 |
All 4 employees ranked by salary DESC โ no ties so all methods agree
There are multiple approaches to find the Nth highest salary in Oracle using ranking functions, subqueries, and analytical functions.
CHAR has a fixed length whereas VARCHAR2 has a variable length.
CHAR always uses the same amount of storage space per entry, while VARCHAR2 uses only the space required.
-- CHAR vs VARCHAR2 comparison
CREATE TABLE data_types_demo (
id NUMBER,
fixed_char CHAR(10), -- Always uses 10 bytes
variable_char VARCHAR2(10) -- Uses 1-10 bytes as needed
);
-- Insert examples
INSERT INTO data_types_demo VALUES (1, 'ABC', 'ABC');
INSERT INTO data_types_demo VALUES (2, 'HELLO', 'HELLO');
-- Check actual lengths
SELECT id,
fixed_char,
variable_char,
LENGTH(fixed_char) as char_length,
LENGTH(variable_char) as varchar_length,
DUMP(fixed_char) as char_dump,
DUMP(variable_char) as varchar_dump
FROM data_types_demo;
-- CHAR pads with spaces, VARCHAR2 doesn't
SELECT * FROM data_types_demo
WHERE fixed_char = 'ABC'; -- May not match due to padding
SELECT * FROM data_types_demo
WHERE variable_char = 'ABC'; -- Exact match
-- Best practices
-- Use CHAR for fixed-length data like codes, flags
-- Use VARCHAR2 for variable-length data like names, descriptions| EMP_ID | EMP_NAME | DEPT_ID | SALARY |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | 20 | 70,000 |
All 4 rows returned
CHAR has a fixed length whereas VARCHAR2 has a variable length.
Use GROUP BY with HAVING clause to find duplicate records based on specific columns.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Find duplicate records using GROUP BY and HAVING
SELECT column_name, COUNT(column_name) as duplicate_count
FROM table_name
GROUP BY column_name
HAVING COUNT(column_name) > 1;
-- Example: Find employees with duplicate email addresses
SELECT email, COUNT(email) as email_count
FROM employees
GROUP BY email
HAVING COUNT(email) > 1;
-- Find duplicates based on multiple columns
SELECT first_name, last_name, department_id, COUNT(*) as duplicate_count
FROM employees
GROUP BY first_name, last_name, department_id
HAVING COUNT(*) > 1;
-- Show all duplicate records with details
SELECT e.*
FROM employees e
INNER JOIN (
SELECT email
FROM employees
GROUP BY email
HAVING COUNT(email) > 1
) duplicates ON e.email = duplicates.email
ORDER BY e.email;
-- Using analytical functions to identify duplicates
SELECT employee_id, first_name, last_name, email,
COUNT(*) OVER (PARTITION BY email) as email_count
FROM employees
WHERE COUNT(*) OVER (PARTITION BY email) > 1;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
3 rows โ only rows with matching dept_id in both tables (Dave Brown excluded โ no dept)
Use GROUP BY with HAVING clause to find duplicate records based on specific columns.
Use ROW_NUMBER() analytical function with MOD function to fetch odd or even positioned records.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- Fetch odd positioned records (1st, 3rd, 5th, etc.)
SELECT * FROM (
SELECT employee_id, first_name, last_name,
ROW_NUMBER() OVER (ORDER BY employee_id) as rn
FROM employees
)
WHERE MOD(rn, 2) = 1;
-- Fetch even positioned records (2nd, 4th, 6th, etc.)
SELECT * FROM (
SELECT employee_id, first_name, last_name,
ROW_NUMBER() OVER (ORDER BY employee_id) as rn
FROM employees
)
WHERE MOD(rn, 2) = 0;
-- Alternative using ROWNUM (less flexible)
-- Odd records using ROWNUM
SELECT * FROM (
SELECT employee_id, first_name, ROWNUM as rn
FROM employees
)
WHERE MOD(rn, 2) = 1;
-- Fetch every 3rd record
SELECT * FROM (
SELECT employee_id, first_name, last_name,
ROW_NUMBER() OVER (ORDER BY employee_id) as rn
FROM employees
)
WHERE MOD(rn, 3) = 0;
-- Fetch alternate records within each department
SELECT * FROM (
SELECT employee_id, first_name, department_id,
ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY employee_id) as rn
FROM employees
)
WHERE MOD(rn, 2) = 1;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_NAME | SALARY | ROW_NUM | RANK | DENSE_RANK |
|---|---|---|---|---|
| Dave Brown | 70,000 | 1 | 1 | 1 |
| Bob Smith | 62,000 | 2 | 2 | 2 |
| Carol White | 55,000 | 3 | 3 | 3 |
| Alice Johnson | 50,000 | 4 | 4 | 4 |
All 4 employees ranked by salary DESC โ no ties so all methods agree
Use ROW_NUMBER() analytical function with MOD function to fetch odd or even positioned records.
Use ranking functions or subqueries to find the second highest salary, handling potential duplicates appropriately.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- Method 1: Using ROW_NUMBER() (most common)
SELECT salary
FROM (
SELECT salary, ROW_NUMBER() OVER (ORDER BY salary DESC) as rn
FROM employee
WHERE salary IS NOT NULL
)
WHERE rn = 2;
-- Method 2: Using DENSE_RANK() (handles duplicates better)
SELECT DISTINCT salary
FROM (
SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) as rnk
FROM employee
WHERE salary IS NOT NULL
)
WHERE rnk = 2;
-- Method 3: Using MAX with subquery
SELECT MAX(salary) as second_highest_salary
FROM employee
WHERE salary < (SELECT MAX(salary) FROM employee);
-- Method 4: Using ROWNUM (Oracle specific)
SELECT salary
FROM (
SELECT DISTINCT salary
FROM employee
WHERE salary IS NOT NULL
ORDER BY salary DESC
)
WHERE ROWNUM <= 2
MINUS
SELECT salary
FROM (
SELECT DISTINCT salary
FROM employee
WHERE salary IS NOT NULL
ORDER BY salary DESC
)
WHERE ROWNUM <= 1;
-- Method 5: Using OFFSET FETCH (Oracle 12c+)
SELECT salary
FROM (
SELECT DISTINCT salary
FROM employee
WHERE salary IS NOT NULL
ORDER BY salary DESC
)
OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY;
-- Method 6: With employee details
SELECT employee_id, first_name, last_name, salary
FROM (
SELECT employee_id, first_name, last_name, salary,
RANK() OVER (ORDER BY salary DESC) as salary_rank
FROM employee
WHERE salary IS NOT NULL
)
WHERE salary_rank = 2;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_NAME | SALARY | ROW_NUM | RANK | DENSE_RANK |
|---|---|---|---|---|
| Dave Brown | 70,000 | 1 | 1 | 1 |
| Bob Smith | 62,000 | 2 | 2 | 2 |
| Carol White | 55,000 | 3 | 3 | 3 |
| Alice Johnson | 50,000 | 4 | 4 | 4 |
All 4 employees ranked by salary DESC โ no ties so all methods agree
Use ranking functions or subqueries to find the second highest salary, handling potential duplicates appropriately.
Use date comparison with proper Oracle date formatting and BETWEEN clause or comparison operators.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- Method 1: Using BETWEEN clause
SELECT employee_id, first_name, last_name, hire_date
FROM employees
WHERE hire_date BETWEEN DATE '2020-01-01' AND DATE '2022-12-31';
-- Method 2: Using comparison operators
SELECT employee_id, first_name, last_name, hire_date
FROM employees
WHERE hire_date >= DATE '2020-01-01'
AND hire_date < DATE '2023-01-01';
-- Method 3: Using TO_DATE function
SELECT employee_id, first_name, last_name, hire_date
FROM employees
WHERE hire_date >= TO_DATE('01-JAN-2020', 'DD-MON-YYYY')
AND hire_date < TO_DATE('01-JAN-2023', 'DD-MON-YYYY');
-- Method 4: With formatted output
SELECT employee_id,
first_name,
last_name,
TO_CHAR(hire_date, 'DD-MON-YYYY') as formatted_hire_date,
ROUND(MONTHS_BETWEEN(SYSDATE, hire_date)/12, 1) as years_of_service
FROM employees
WHERE hire_date BETWEEN DATE '2020-01-01' AND DATE '2022-12-31'
ORDER BY hire_date;
-- Method 5: Using EXTRACT function
SELECT employee_id, first_name, last_name, hire_date
FROM employees
WHERE EXTRACT(YEAR FROM hire_date) BETWEEN 2020 AND 2022
AND NOT (EXTRACT(YEAR FROM hire_date) = 2023 AND EXTRACT(MONTH FROM hire_date) = 1 AND EXTRACT(DAY FROM hire_date) = 1);
-- Method 6: With additional date calculations
SELECT employee_id,
first_name,
last_name,
hire_date,
CASE
WHEN EXTRACT(YEAR FROM hire_date) = 2020 THEN 'Hired in 2020'
WHEN EXTRACT(YEAR FROM hire_date) = 2021 THEN 'Hired in 2021'
WHEN EXTRACT(YEAR FROM hire_date) = 2022 THEN 'Hired in 2022'
END as hire_year_group
FROM employees
WHERE hire_date >= DATE '2020-01-01'
AND hire_date < DATE '2023-01-01'
ORDER BY hire_date;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | DEPT_ID | SALARY |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | 20 | 70,000 |
All 4 rows returned โ ordered by salary ASC
Use date comparison with proper Oracle date formatting and BETWEEN clause or comparison operators.
Oracle provides multiple methods for pagination including ROWNUM, ROW_NUMBER(), and the modern OFFSET FETCH syntax (Oracle 12c+).
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- Method 1: Using ROWNUM (Oracle 11g and earlier)
-- Page 1 (records 1-10)
SELECT * FROM (
SELECT e.*, ROWNUM rn FROM (
SELECT employee_id, first_name, last_name, salary
FROM employees
ORDER BY salary DESC
) e
WHERE ROWNUM <= 10
) WHERE rn >= 1;
-- Page 2 (records 11-20)
SELECT * FROM (
SELECT e.*, ROWNUM rn FROM (
SELECT employee_id, first_name, last_name, salary
FROM employees
ORDER BY salary DESC
) e
WHERE ROWNUM <= 20
) WHERE rn >= 11;
-- Method 2: Using ROW_NUMBER() Analytical Function
-- Page 1 (records 1-10)
SELECT * FROM (
SELECT employee_id, first_name, last_name, salary,
ROW_NUMBER() OVER (ORDER BY salary DESC) as rn
FROM employees
)
WHERE rn BETWEEN 1 AND 10;
-- Page 2 (records 11-20)
SELECT * FROM (
SELECT employee_id, first_name, last_name, salary,
ROW_NUMBER() OVER (ORDER BY salary DESC) as rn
FROM employees
)
WHERE rn BETWEEN 11 AND 20;
-- Method 3: OFFSET FETCH (Oracle 12c+) - Recommended
-- Page 1 (first 10 records)
SELECT employee_id, first_name, last_name, salary
FROM employees
ORDER BY salary DESC
OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY;
-- Page 2 (next 10 records)
SELECT employee_id, first_name, last_name, salary
FROM employees
ORDER BY salary DESC
OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;
-- Page 3 (next 10 records)
SELECT employee_id, first_name, last_name, salary
FROM employees
ORDER BY salary DESC
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
-- Parameterized Pagination Function
CREATE OR REPLACE FUNCTION get_employees_page(
p_page_number NUMBER,
p_page_size NUMBER DEFAULT 10
) RETURN SYS_REFCURSOR IS
v_cursor SYS_REFCURSOR;
v_offset NUMBER;
BEGIN
v_offset := (p_page_number - 1) * p_page_size;
OPEN v_cursor FOR
SELECT employee_id, first_name, last_name, salary
FROM employees
ORDER BY employee_id
OFFSET v_offset ROWS FETCH NEXT p_page_size ROWS ONLY;
RETURN v_cursor;
END;
/
-- Pagination with Total Count
SELECT * FROM (
SELECT employee_id, first_name, last_name, salary,
ROW_NUMBER() OVER (ORDER BY salary DESC) as rn,
COUNT(*) OVER () as total_count
FROM employees
)
WHERE rn BETWEEN 11 AND 20;
-- Dynamic Pagination PL/SQL Block
DECLARE
v_page_number NUMBER := 2;
v_page_size NUMBER := 5;
v_offset NUMBER;
v_sql VARCHAR2(1000);
TYPE emp_cursor_type IS REF CURSOR;
emp_cursor emp_cursor_type;
v_emp_id NUMBER;
v_name VARCHAR2(100);
v_salary NUMBER;
BEGIN
v_offset := (v_page_number - 1) * v_page_size;
v_sql := 'SELECT employee_id, first_name || '' '' || last_name, salary
FROM employees
ORDER BY salary DESC
OFFSET :1 ROWS FETCH NEXT :2 ROWS ONLY';
OPEN emp_cursor FOR v_sql USING v_offset, v_page_size;
DBMS_OUTPUT.PUT_LINE('Page ' || v_page_number || ' (Page Size: ' || v_page_size || ')');
DBMS_OUTPUT.PUT_LINE('----------------------------------------');
LOOP
FETCH emp_cursor INTO v_emp_id, v_name, v_salary;
EXIT WHEN emp_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_emp_id || ' - ' || v_name || ' - || v_salary);
END LOOP;
CLOSE emp_cursor;
END;
/| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_NAME | SALARY | ROW_NUM | RANK | DENSE_RANK |
|---|---|---|---|---|
| Dave Brown | 70,000 | 1 | 1 | 1 |
| Bob Smith | 62,000 | 2 | 2 | 2 |
| Carol White | 55,000 | 3 | 3 | 3 |
| Alice Johnson | 50,000 | 4 | 4 | 4 |
All 4 employees ranked by salary DESC โ no ties so all methods agree
Oracle provides multiple methods for pagination including ROWNUM, ROW_NUMBER(), and the modern OFFSET FETCH syntax (Oracle 12c+).
Use Oracle data dictionary views like ALL_TAB_COLUMNS or USER_TAB_COLUMNS to search for tables containing specific column names.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Find tables with specific column name
SELECT table_name, owner
FROM all_tab_columns
WHERE column_name = 'EMPLOYEE_ID'
ORDER BY owner, table_name;
-- Find tables in current schema with specific column
SELECT table_name, column_name, data_type, data_length
FROM user_tab_columns
WHERE column_name = 'SALARY'
ORDER BY table_name;
-- Find tables with multiple specific columns
SELECT table_name
FROM user_tab_columns
WHERE column_name IN ('FIRST_NAME', 'LAST_NAME')
GROUP BY table_name
HAVING COUNT(DISTINCT column_name) = 2;
-- Find tables with column name pattern
SELECT table_name, column_name
FROM user_tab_columns
WHERE column_name LIKE '%_DATE'
ORDER BY table_name, column_name;
-- Find tables with columns containing specific text
SELECT table_name, column_name, data_type
FROM user_tab_columns
WHERE column_name LIKE '%EMAIL%'
OR column_name LIKE '%PHONE%'
ORDER BY table_name, column_name;
-- More detailed column information
SELECT table_name,
column_name,
data_type,
CASE
WHEN data_type = 'VARCHAR2' THEN data_type || '(' || data_length || ')'
WHEN data_type = 'NUMBER' THEN
CASE
WHEN data_scale IS NULL THEN data_type || '(' || data_precision || ')'
ELSE data_type || '(' || data_precision || ',' || data_scale || ')'
END
ELSE data_type
END as full_data_type,
nullable,
data_default
FROM user_tab_columns
WHERE column_name = 'DEPARTMENT_ID'
ORDER BY table_name;
-- Find foreign key relationships
SELECT a.table_name as child_table,
a.column_name as child_column,
a.constraint_name,
c_pk.table_name as parent_table,
c_pk.column_name as parent_column
FROM user_cons_columns a
JOIN user_constraints c ON a.owner = c.owner AND a.constraint_name = c.constraint_name
JOIN user_constraints c_pk ON c.r_owner = c_pk.owner AND c.r_constraint_name = c_pk.constraint_name
JOIN user_cons_columns c_pk ON c_pk.owner = c_pk.owner AND c_pk.constraint_name = c_pk.constraint_name
WHERE c.constraint_type = 'R'
AND a.column_name = 'DEPARTMENT_ID'
ORDER BY a.table_name;
-- Find all columns for a specific table
SELECT column_id,
column_name,
data_type,
CASE
WHEN data_type = 'VARCHAR2' THEN data_type || '(' || data_length || ')'
WHEN data_type = 'NUMBER' THEN
CASE
WHEN data_scale IS NULL THEN data_type
WHEN data_scale = 0 THEN data_type || '(' || data_precision || ')'
ELSE data_type || '(' || data_precision || ',' || data_scale || ')'
END
ELSE data_type
END as formatted_type,
nullable,
data_default
FROM user_tab_columns
WHERE table_name = 'EMPLOYEES'
ORDER BY column_id;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
3 rows โ only rows with matching dept_id in both tables (Dave Brown excluded โ no dept)
Use Oracle data dictionary views like ALL_TAB_COLUMNS or USER_TAB_COLUMNS to search for tables containing specific column names.
Oracle provides multiple methods for string concatenation including the || operator, CONCAT function, and LISTAGG for multiple values.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- Method 1: Using || operator (most common)
SELECT first_name || ' ' || last_name AS full_name
FROM employees;
-- Method 2: Using CONCAT function (only 2 arguments)
SELECT CONCAT(first_name, last_name) AS full_name
FROM employees;
-- Method 3: Nested CONCAT for multiple strings
SELECT CONCAT(CONCAT(first_name, ' '), last_name) AS full_name
FROM employees;
-- Method 4: Combining || with other functions
SELECT first_name || ' ' || last_name || ' (ID: ' || employee_id || ')' AS employee_info
FROM employees;
-- Method 5: Handling NULL values
SELECT first_name || ' ' || NVL(middle_name || ' ', '') || last_name AS full_name
FROM employees;
-- Method 6: Using LISTAGG for multiple rows
SELECT department_id,
LISTAGG(first_name, ', ') WITHIN GROUP (ORDER BY first_name) AS employee_names
FROM employees
GROUP BY department_id;
-- Method 7: Advanced concatenation with formatting
SELECT 'Employee: ' || INITCAP(first_name) || ' ' || UPPER(last_name) ||
' earns || TO_CHAR(salary, '999,999.99') || ' per year' AS employee_summary
FROM employees;
-- Method 8: Conditional concatenation
SELECT first_name ||
CASE
WHEN middle_name IS NOT NULL THEN ' ' || middle_name || ' '
ELSE ' '
END || last_name AS full_name
FROM employees;
-- Method 9: XML concatenation for complex scenarios
SELECT employee_id,
XMLAGG(XMLELEMENT("skill", skill_name || '; ')).getClobVal() AS skills
FROM employee_skills
GROUP BY employee_id;
-- Method 10: Using REPLACE for clean concatenation
SELECT REPLACE(first_name || ' ' || middle_name || ' ' || last_name, ' ', ' ') AS clean_name
FROM employees;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| DEPT_ID | EMPLOYEE_NAMES |
|---|---|
| 10 | Alice Johnson, Carol White |
| 20 | Bob Smith, Dave Brown |
LISTAGG aggregates names into comma-separated string per group
Oracle provides multiple methods for string concatenation including the || operator, CONCAT function, and LISTAGG for multiple values.
Use GROUP BY clause to retrieve unique values from a column, which can also provide additional aggregation information.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Method 1: Using GROUP BY
SELECT department_id
FROM employees
GROUP BY department_id
ORDER BY department_id;
-- Method 2: GROUP BY with COUNT
SELECT department_id, COUNT(*) as employee_count
FROM employees
GROUP BY department_id
ORDER BY department_id;
-- Method 3: Using UNION (removes duplicates by default)
SELECT department_id FROM employees WHERE department_id <= 50
UNION
SELECT department_id FROM employees WHERE department_id > 50;
-- Method 4: Using analytical functions with ROW_NUMBER
SELECT department_id
FROM (
SELECT department_id,
ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY department_id) as rn
FROM employees
)
WHERE rn = 1;
-- Method 5: Using EXISTS subquery
SELECT DISTINCT e1.department_id
FROM employees e1
WHERE EXISTS (
SELECT 1 FROM employees e2
WHERE e2.department_id = e1.department_id
);
-- Method 6: Using IN with subquery
SELECT department_id
FROM employees
WHERE department_id IN (SELECT department_id FROM employees)
GROUP BY department_id;
-- Method 7: Using MINUS to find unique values
SELECT department_id FROM employees
MINUS
SELECT department_id FROM employees e1
WHERE EXISTS (
SELECT 1 FROM employees e2
WHERE e2.department_id = e1.department_id
AND e2.ROWID < e1.ROWID
);
-- Method 8: Using LISTAGG for unique concatenated values
SELECT LISTAGG(DISTINCT department_id, ',') WITHIN GROUP (ORDER BY department_id) as unique_departments
FROM employees;
-- Method 9: Using COLLECT for unique values as array
SELECT CAST(COLLECT(DISTINCT department_id) AS sys.odcinumberlist) as unique_dept_list
FROM employees;
-- Method 10: Multiple column uniqueness using GROUP BY
SELECT first_name, last_name, COUNT(*) as occurrence_count
FROM employees
GROUP BY first_name, last_name
ORDER BY first_name, last_name;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_NAME | SALARY | ROW_NUM | RANK | DENSE_RANK |
|---|---|---|---|---|
| Dave Brown | 70,000 | 1 | 1 | 1 |
| Bob Smith | 62,000 | 2 | 2 | 2 |
| Carol White | 55,000 | 3 | 3 | 3 |
| Alice Johnson | 50,000 | 4 | 4 | 4 |
All 4 employees ranked by salary DESC โ no ties so all methods agree
Use GROUP BY clause to retrieve unique values from a column, which can also provide additional aggregation information.
Use CREATE TABLE AS SELECT (CTAS) statement to create a copy of a table with both structure and data in Oracle.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Method 1: Create complete copy with data
CREATE TABLE employees_copy AS SELECT * FROM employees;
-- Method 2: Create copy with specific columns
CREATE TABLE employees_basic AS
SELECT employee_id, first_name, last_name, salary
FROM employees;
-- Method 3: Create copy with filtered data
CREATE TABLE high_salary_employees AS
SELECT * FROM employees
WHERE salary > 50000;
-- Method 4: Create copy with additional computed columns
CREATE TABLE employees_enhanced AS
SELECT employee_id,
first_name,
last_name,
salary,
salary * 12 as annual_salary,
CASE
WHEN salary > 75000 THEN 'High'
WHEN salary > 50000 THEN 'Medium'
ELSE 'Low'
END as salary_grade
FROM employees;
-- Method 5: Create empty copy (structure only)
CREATE TABLE employees_structure AS
SELECT * FROM employees WHERE 1=0;
-- Method 6: Create copy with joins
CREATE TABLE employee_department_copy AS
SELECT e.employee_id,
e.first_name,
e.last_name,
e.salary,
d.department_name,
d.location_id
FROM employees e
JOIN departments d ON e.department_id = d.department_id;
-- Method 7: Create copy with specific tablespace
CREATE TABLE employees_copy_ts
TABLESPACE users
AS SELECT * FROM employees;
-- Method 8: Create copy with storage parameters
CREATE TABLE employees_copy_storage (
INITIAL 1M
NEXT 1M
PCTINCREASE 0
)
AS SELECT * FROM employees;
-- Note: CTAS limitations
-- 1. Does not copy indexes
-- 2. Does not copy constraints (except NOT NULL)
-- 3. Does not copy triggers
-- 4. Does not copy comments
-- Copy indexes separately
CREATE INDEX idx_emp_copy_name ON employees_copy(last_name);
-- Copy constraints separately
ALTER TABLE employees_copy ADD CONSTRAINT pk_emp_copy PRIMARY KEY (employee_id);
-- Copy comments separately
COMMENT ON TABLE employees_copy IS 'Copy of employees table';
COMMENT ON COLUMN employees_copy.employee_id IS 'Unique employee identifier';
-- Alternative: Use DBMS_METADATA for complete structure
-- Generate DDL for original table
SELECT DBMS_METADATA.GET_DDL('TABLE', 'EMPLOYEES') FROM dual;
-- Method 9: Create copy with specific conditions and ordering
CREATE TABLE recent_employees AS
SELECT * FROM employees
WHERE hire_date >= ADD_MONTHS(SYSDATE, -24)
ORDER BY hire_date DESC;
-- Method 10: Create partitioned copy
CREATE TABLE employees_partitioned_copy
PARTITION BY RANGE (hire_date) (
PARTITION p_old VALUES LESS THAN (DATE '2020-01-01'),
PARTITION p_recent VALUES LESS THAN (DATE '2025-01-01')
)
AS SELECT * FROM employees;
-- Verify copy
SELECT COUNT(*) FROM employees;
SELECT COUNT(*) FROM employees_copy;
-- Compare structures
DESC employees;
DESC employees_copy;
-- Check if data matches
SELECT COUNT(*) FROM employees
MINUS
SELECT COUNT(*) FROM employees_copy;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
3 rows โ only rows with matching dept_id in both tables (Dave Brown excluded โ no dept)
Use CREATE TABLE AS SELECT (CTAS) statement to create a copy of a table with both structure and data in Oracle.
Use FROM_UNIXTIME equivalent functions or date arithmetic to convert UNIX timestamps to Oracle DATE format.
-- Method 1: Convert UNIX timestamp to DATE
SELECT TO_DATE('1970-01-01', 'YYYY-MM-DD') + (1609459200 / 86400) as converted_date
FROM dual;
-- Method 2: Using TIMESTAMP conversion
SELECT TIMESTAMP '1970-01-01 00:00:00' + NUMTODSINTERVAL(1609459200, 'SECOND') as converted_timestamp
FROM dual;
-- Method 3: Create a reusable function
CREATE OR REPLACE FUNCTION unix_to_date(p_unix_timestamp NUMBER)
RETURN DATE IS
BEGIN
RETURN DATE '1970-01-01' + (p_unix_timestamp / 86400);
END;
/
-- Usage of the function
SELECT unix_to_date(1609459200) as readable_date FROM dual;
-- Method 4: Handle milliseconds UNIX timestamp
SELECT DATE '1970-01-01' + (1609459200000 / 86400000) as converted_date
FROM dual;
-- Method 5: Format the converted date
SELECT TO_CHAR(
DATE '1970-01-01' + (1609459200 / 86400),
'YYYY-MM-DD HH24:MI:SS'
) as formatted_date
FROM dual;
-- Method 6: Convert multiple timestamps in a table
CREATE TABLE unix_timestamps (
id NUMBER,
unix_time NUMBER,
description VARCHAR2(100)
);
INSERT INTO unix_timestamps VALUES (1, 1609459200, 'New Year 2021');
INSERT INTO unix_timestamps VALUES (2, 1640995200, 'New Year 2022');
INSERT INTO unix_timestamps VALUES (3, 1672531200, 'New Year 2023');
SELECT id,
unix_time,
DATE '1970-01-01' + (unix_time / 86400) as converted_date,
TO_CHAR(DATE '1970-01-01' + (unix_time / 86400), 'DD-MON-YYYY HH24:MI:SS') as formatted_date,
description
FROM unix_timestamps;
-- Method 7: Handle time zones
SELECT FROM_TZ(
TIMESTAMP '1970-01-01 00:00:00' + NUMTODSINTERVAL(1609459200, 'SECOND'),
'UTC'
) AT TIME ZONE 'America/New_York' as est_time
FROM dual;
-- Method 8: Reverse conversion (DATE to UNIX timestamp)
SELECT (SYSDATE - DATE '1970-01-01') * 86400 as unix_timestamp
FROM dual;
-- Method 9: Using EXTRACT for components
SELECT EXTRACT(YEAR FROM DATE '1970-01-01' + (1609459200 / 86400)) as year,
EXTRACT(MONTH FROM DATE '1970-01-01' + (1609459200 / 86400)) as month,
EXTRACT(DAY FROM DATE '1970-01-01' + (1609459200 / 86400)) as day
FROM dual;
-- Method 10: Create a comprehensive conversion function
CREATE OR REPLACE FUNCTION unix_to_formatted_date(
p_unix_timestamp NUMBER,
p_format VARCHAR2 DEFAULT 'YYYY-MM-DD HH24:MI:SS'
) RETURN VARCHAR2 IS
BEGIN
RETURN TO_CHAR(
DATE '1970-01-01' + (p_unix_timestamp / 86400),
p_format
);
END;
/
-- Usage examples
SELECT unix_to_formatted_date(1609459200) as default_format,
unix_to_formatted_date(1609459200, 'DD-MON-YYYY') as custom_format,
unix_to_formatted_date(1609459200, 'Day, DD Month YYYY') as verbose_format
FROM dual;
-- Clean up
DROP TABLE unix_timestamps;
DROP FUNCTION unix_to_date;
DROP FUNCTION unix_to_formatted_date;| EMP_ID | EMP_NAME | DEPT_ID | SALARY |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | 20 | 70,000 |
All 4 rows returned
Use FROM_UNIXTIME equivalent functions or date arithmetic to convert UNIX timestamps to Oracle DATE format.
SYSDATE returns the current date and time of the database server, while CURRENT_DATE returns the current date and time in the session time zone.
-- SYSDATE: Database server date/time
SELECT SYSDATE as server_date_time FROM dual;
-- CURRENT_DATE: Session time zone date/time
SELECT CURRENT_DATE as session_date_time FROM dual;
-- CURRENT_TIMESTAMP: More precise session timestamp
SELECT CURRENT_TIMESTAMP as session_timestamp FROM dual;
-- SYSTIMESTAMP: Database server timestamp with time zone
SELECT SYSTIMESTAMP as server_timestamp FROM dual;
-- Show time zone information
SELECT SESSIONTIMEZONE as session_tz,
DBTIMEZONE as database_tz
FROM dual;
-- Demonstrate difference with time zone change
ALTER SESSION SET TIME_ZONE = 'America/New_York';
SELECT SYSDATE as server_date,
CURRENT_DATE as session_date,
CURRENT_TIMESTAMP as session_timestamp
FROM dual;
-- Change to different time zone
ALTER SESSION SET TIME_ZONE = 'Asia/Tokyo';
SELECT SYSDATE as server_date,
CURRENT_DATE as session_date,
CURRENT_TIMESTAMP as session_timestamp
FROM dual;
-- Reset to default time zone
ALTER SESSION SET TIME_ZONE = DBTIMEZONE;
-- Date arithmetic examples
SELECT SYSDATE as today,
SYSDATE + 1 as tomorrow,
SYSDATE - 1 as yesterday,
SYSDATE + 1/24 as one_hour_later,
SYSDATE + 1/24/60 as one_minute_later
FROM dual;
-- Extract components
SELECT EXTRACT(YEAR FROM SYSDATE) as current_year,
EXTRACT(MONTH FROM SYSDATE) as current_month,
EXTRACT(DAY FROM SYSDATE) as current_day,
EXTRACT(HOUR FROM CURRENT_TIMESTAMP) as current_hour,
EXTRACT(MINUTE FROM CURRENT_TIMESTAMP) as current_minute
FROM dual;
-- Formatting dates
SELECT TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS') as formatted_sysdate,
TO_CHAR(CURRENT_DATE, 'YYYY-MM-DD HH24:MI:SS') as formatted_current_date,
TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.FF3') as formatted_timestamp
FROM dual;
-- Practical usage in applications
CREATE TABLE event_log (
event_id NUMBER,
event_description VARCHAR2(200),
server_time DATE DEFAULT SYSDATE,
user_time DATE DEFAULT CURRENT_DATE,
event_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO event_log (event_id, event_description)
VALUES (1, 'User login event');
SELECT * FROM event_log;
-- Date comparisons
SELECT COUNT(*) as today_events
FROM event_log
WHERE TRUNC(server_time) = TRUNC(SYSDATE);
-- Time zone conversions
SELECT server_time,
FROM_TZ(CAST(server_time AS TIMESTAMP), DBTIMEZONE)
AT TIME ZONE 'UTC' as utc_time,
FROM_TZ(CAST(server_time AS TIMESTAMP), DBTIMEZONE)
AT TIME ZONE 'America/Los_Angeles' as pst_time
FROM event_log;
-- Performance consideration: SYSDATE vs CURRENT_DATE
-- SYSDATE is faster as it doesn't need time zone conversion
-- Use SYSDATE for logging and timestamps unless time zone matters
-- Use CURRENT_DATE when user's time zone is important
-- Clean up
DROP TABLE event_log;| COUNT | AVG_SALARY | MIN_SALARY | MAX_SALARY | SUM_SALARY |
|---|---|---|---|---|
| 4 | 59,250 | 50,000 | 70,000 | 237,000 |
Aggregate over all 4 employees โ AVG=(50+62+55+70)/4=59,250
SYSDATE returns the current date and time of the database server, while CURRENT_DATE returns the current date and time in the session time zone.
Use the LENGTH function to get the number of characters in a string, or LENGTHB for byte length.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- Basic LENGTH function
SELECT first_name,
LENGTH(first_name) as name_length
FROM employees;
-- LENGTH vs LENGTHB (character vs byte length)
SELECT first_name,
LENGTH(first_name) as char_length,
LENGTHB(first_name) as byte_length
FROM employees;
-- Find employees with names longer than 8 characters
SELECT first_name, last_name,
LENGTH(first_name) as first_name_length,
LENGTH(last_name) as last_name_length
FROM employees
WHERE LENGTH(first_name) > 8;
-- Combined string length
SELECT first_name,
last_name,
LENGTH(first_name || ' ' || last_name) as full_name_length
FROM employees;
-- Handle NULL values
SELECT first_name,
middle_name,
LENGTH(first_name) as first_length,
LENGTH(middle_name) as middle_length,
NVL(LENGTH(middle_name), 0) as middle_length_safe
FROM employees;
-- Statistical analysis of string lengths
SELECT MIN(LENGTH(first_name)) as min_length,
MAX(LENGTH(first_name)) as max_length,
AVG(LENGTH(first_name)) as avg_length,
STDDEV(LENGTH(first_name)) as std_dev
FROM employees;
-- Group by string length
SELECT LENGTH(first_name) as name_length,
COUNT(*) as count_of_names
FROM employees
GROUP BY LENGTH(first_name)
ORDER BY name_length;
-- Find longest and shortest names
SELECT first_name,
LENGTH(first_name) as name_length
FROM employees
WHERE LENGTH(first_name) = (SELECT MAX(LENGTH(first_name)) FROM employees)
OR LENGTH(first_name) = (SELECT MIN(LENGTH(first_name)) FROM employees);
-- Length-based filtering and sorting
SELECT first_name,
last_name,
LENGTH(first_name) as first_length,
LENGTH(last_name) as last_length
FROM employees
WHERE LENGTH(first_name) BETWEEN 4 AND 8
ORDER BY LENGTH(first_name) DESC, first_name;
-- Character vs byte length for multibyte characters
-- (Important for Unicode/UTF-8 data)
SELECT 'Hello' as text,
LENGTH('Hello') as char_length,
LENGTHB('Hello') as byte_length
FROM dual
UNION ALL
SELECT 'Hรฉllo' as text,
LENGTH('Hรฉllo') as char_length,
LENGTHB('Hรฉllo') as byte_length
FROM dual;
-- Practical example: Data validation
SELECT email,
LENGTH(email) as email_length,
CASE
WHEN LENGTH(email) > 100 THEN 'Too Long'
WHEN LENGTH(email) < 5 THEN 'Too Short'
WHEN email NOT LIKE '%@%' THEN 'Invalid Format'
ELSE 'Valid'
END as validation_status
FROM employees;
-- Find records with empty or very short values
SELECT employee_id,
first_name,
LENGTH(first_name) as length
FROM employees
WHERE LENGTH(first_name) <= 2 OR first_name IS NULL;
-- Length comparison across columns
SELECT employee_id,
first_name,
last_name,
LENGTH(first_name) as first_len,
LENGTH(last_name) as last_len,
CASE
WHEN LENGTH(first_name) > LENGTH(last_name) THEN 'First name longer'
WHEN LENGTH(first_name) < LENGTH(last_name) THEN 'Last name longer'
ELSE 'Same length'
END as comparison
FROM employees;
-- Substring and length operations
SELECT first_name,
SUBSTR(first_name, 1, 3) as first_three,
LENGTH(SUBSTR(first_name, 1, 3)) as substr_length,
LENGTH(TRIM(first_name)) as trimmed_length
FROM employees;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SOURCE |
|---|---|---|
| 101 | Alice Johnson | CURRENT |
| 102 | Bob Smith | CURRENT |
| 101 | Alice Johnson | ARCHIVE |
| 102 | Bob Smith | ARCHIVE |
4 rows โ UNION ALL keeps duplicates, faster than UNION
Use the LENGTH function to get the number of characters in a string, or LENGTHB for byte length.
LISTAGG is an analytical function that concatenates values from multiple rows into a single string, with optional ordering and separator characters.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Basic LISTAGG usage
SELECT department_id,
LISTAGG(first_name, ', ') WITHIN GROUP (ORDER BY first_name) as employee_names
FROM employees
GROUP BY department_id;
-- LISTAGG with custom separator
SELECT department_id,
LISTAGG(first_name, ' | ') WITHIN GROUP (ORDER BY hire_date) as employees_by_hire_date
FROM employees
GROUP BY department_id;
-- LISTAGG with complex expressions
SELECT department_id,
LISTAGG(first_name || ' (' || job_id || ')', '; ')
WITHIN GROUP (ORDER BY salary DESC) as employee_details
FROM employees
GROUP BY department_id;
-- LISTAGG as window function (Oracle 12c+)
SELECT employee_id,
first_name,
department_id,
LISTAGG(first_name, ', ') WITHIN GROUP (ORDER BY first_name)
OVER (PARTITION BY department_id) as dept_colleagues
FROM employees;
-- Handle potential overflow with ON OVERFLOW TRUNCATE (Oracle 12c+)
SELECT department_id,
LISTAGG(first_name, ', ' ON OVERFLOW TRUNCATE '...' WITH COUNT)
WITHIN GROUP (ORDER BY first_name) as employee_names
FROM employees
GROUP BY department_id;
-- LISTAGG with DISTINCT (Oracle 19c+)
SELECT department_id,
LISTAGG(DISTINCT job_id, ', ') WITHIN GROUP (ORDER BY job_id) as unique_jobs
FROM employees
GROUP BY department_id;
-- Practical example: Create employee directory
SELECT d.department_name,
COUNT(e.employee_id) as employee_count,
LISTAGG(e.first_name || ' ' || e.last_name, CHR(10))
WITHIN GROUP (ORDER BY e.last_name) as employee_list
FROM departments d
LEFT JOIN employees e ON d.department_id = e.department_id
GROUP BY d.department_name
ORDER BY d.department_name;
-- LISTAGG with conditional logic
SELECT department_id,
LISTAGG(
CASE
WHEN salary > 50000 THEN first_name || ' (High)'
ELSE first_name || ' (Standard)'
END,
', '
) WITHIN GROUP (ORDER BY salary DESC) as employee_salary_categories
FROM employees
GROUP BY department_id;
-- Alternative to LISTAGG for older Oracle versions
SELECT department_id,
SUBSTR(
SYS_CONNECT_BY_PATH(first_name, ','), 2
) as employee_names
FROM (
SELECT department_id, first_name,
ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY first_name) as rn,
COUNT(*) OVER (PARTITION BY department_id) as cnt
FROM employees
)
WHERE rn = cnt
START WITH rn = 1
CONNECT BY PRIOR rn = rn - 1 AND PRIOR department_id = department_id;
-- Using XMLAGG as alternative
SELECT department_id,
RTRIM(
XMLAGG(
XMLELEMENT("x", first_name || ', ')
).getClobVal(),
', '
) as employee_names
FROM employees
GROUP BY department_id;
-- LISTAGG with hierarchical data
SELECT LEVEL,
LPAD(' ', (LEVEL-1)*2) || first_name as hierarchy_display,
SYS_CONNECT_BY_PATH(first_name, ' -> ') as management_chain
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR employee_id = manager_id;
-- LISTAGG for creating dynamic SQL
SELECT 'SELECT ' ||
LISTAGG(column_name, ', ') WITHIN GROUP (ORDER BY column_id) ||
' FROM ' || table_name as dynamic_select
FROM user_tab_columns
WHERE table_name = 'EMPLOYEES'
GROUP BY table_name;
-- Performance considerations and limitations
-- LISTAGG has a 4000 character limit in older versions
-- Use CLOB version for larger strings (Oracle 12c+)
SELECT department_id,
LISTAGG(first_name || ' ' || last_name, ', ')
WITHIN GROUP (ORDER BY first_name) as employee_names_clob
FROM employees
GROUP BY department_id;
-- Clean up demonstration tables
-- (No tables created in this example)| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMPLOYEE | MANAGER |
|---|---|
| Bob Smith | Alice Johnson |
| Carol White | Alice Johnson |
| Dave Brown | Bob Smith |
3 rows โ self join pairs each employee with their manager's name
LISTAGG is an analytical function that concatenates values from multiple rows into a single string, with optional ordering and separator characters.
Use ALTER TABLE MODIFY statement to change the data type of a column, with considerations for data compatibility and potential data loss.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- Basic syntax for modifying column data type
ALTER TABLE employees MODIFY (salary NUMBER(10,2));
-- Create test table for demonstration
CREATE TABLE data_type_demo (
id NUMBER,
text_col VARCHAR2(50),
number_col NUMBER,
date_col DATE
);
INSERT INTO data_type_demo VALUES (1, '12345', 100, SYSDATE);
INSERT INTO data_type_demo VALUES (2, 'Hello', 200, SYSDATE-1);
-- Example 1: Increase VARCHAR2 size
ALTER TABLE data_type_demo MODIFY (text_col VARCHAR2(100));
-- Example 2: Change NUMBER precision
ALTER TABLE data_type_demo MODIFY (number_col NUMBER(10,2));
-- Example 3: Converting compatible types
-- VARCHAR2 to NUMBER (data must be numeric)
UPDATE data_type_demo SET text_col = '999' WHERE id = 2;
ALTER TABLE data_type_demo MODIFY (text_col NUMBER);
-- Reverting back for next example
ALTER TABLE data_type_demo MODIFY (text_col VARCHAR2(100));
UPDATE data_type_demo SET text_col = 'Hello' WHERE id = 2;
-- Example 4: Converting with data transformation
-- Step 1: Add new column with desired type
ALTER TABLE data_type_demo ADD (new_text_col CLOB);
-- Step 2: Copy and transform data
UPDATE data_type_demo SET new_text_col = text_col;
-- Step 3: Drop old column
ALTER TABLE data_type_demo DROP COLUMN text_col;
-- Step 4: Rename new column
ALTER TABLE data_type_demo RENAME COLUMN new_text_col TO text_col;
-- Example 5: Safe conversion process for complex changes
-- Create backup table
CREATE TABLE data_type_demo_backup AS SELECT * FROM data_type_demo;
-- Complex conversion: VARCHAR2 to DATE
ALTER TABLE data_type_demo ADD (temp_date_col DATE);
-- Convert data with proper handling
UPDATE data_type_demo
SET temp_date_col = CASE
WHEN REGEXP_LIKE(text_col, '^\d{4}-\d{2}-\d{2})
THEN TO_DATE(text_col, 'YYYY-MM-DD')
ELSE NULL
END;
-- Check conversion results
SELECT text_col, temp_date_col FROM data_type_demo;
-- Complete the conversion
ALTER TABLE data_type_demo DROP COLUMN text_col;
ALTER TABLE data_type_demo RENAME COLUMN temp_date_col TO text_col;
-- Example 6: Multiple column modifications
ALTER TABLE data_type_demo MODIFY (
id NUMBER(10),
number_col NUMBER(15,3),
date_col TIMESTAMP
);
-- Example 7: Adding constraints during modification
ALTER TABLE data_type_demo MODIFY (
id NUMBER(10) NOT NULL,
number_col NUMBER(15,3) DEFAULT 0
);
-- Example 8: Handling constraints during type changes
-- View existing constraints
SELECT constraint_name, constraint_type, column_name
FROM user_cons_columns
WHERE table_name = 'DATA_TYPE_DEMO';
-- Disable constraints if needed
-- ALTER TABLE data_type_demo DISABLE CONSTRAINT constraint_name;
-- Modify column
-- ALTER TABLE data_type_demo MODIFY (column_name NEW_TYPE);
-- Re-enable constraints
-- ALTER TABLE data_type_demo ENABLE CONSTRAINT constraint_name;
-- Example 9: Check data compatibility before conversion
-- Check if all values in number_col can be converted to INTEGER
SELECT COUNT(*) as total_rows,
COUNT(CASE WHEN MOD(number_col, 1) = 0 THEN 1 END) as integer_compatible
FROM data_type_demo;
-- Example 10: Using CAST for validation
SELECT text_col,
CASE
WHEN text_col IS NULL THEN 'NULL'
WHEN REGEXP_LIKE(text_col, '^\d+) THEN 'NUMERIC'
WHEN REGEXP_LIKE(text_col, '^\d{4}-\d{2}-\d{2}) THEN 'DATE_FORMAT'
ELSE 'TEXT'
END as data_type_category
FROM data_type_demo;
-- View current column definitions
SELECT column_name, data_type, data_length, data_precision, data_scale, nullable
FROM user_tab_columns
WHERE table_name = 'DATA_TYPE_DEMO'
ORDER BY column_id;
-- Common conversion scenarios and best practices:
-- 1. VARCHAR2 size increase: Always safe
-- ALTER TABLE table_name MODIFY (column_name VARCHAR2(new_larger_size));
-- 2. VARCHAR2 size decrease: Check data first
-- SELECT MAX(LENGTH(column_name)) FROM table_name;
-- 3. NUMBER precision increase: Usually safe
-- ALTER TABLE table_name MODIFY (column_name NUMBER(larger_precision, scale));
-- 4. NUMBER to VARCHAR2: Usually safe
-- ALTER TABLE table_name MODIFY (column_name VARCHAR2(sufficient_size));
-- 5. VARCHAR2 to NUMBER: Validate data first
-- SELECT column_name FROM table_name WHERE NOT REGEXP_LIKE(column_name, '^-?\d+(\.\d+)?);
-- 6. DATE to VARCHAR2: Safe with proper formatting
-- ALTER TABLE table_name ADD (temp_col VARCHAR2(19));
-- UPDATE table_name SET temp_col = TO_CHAR(date_col, 'YYYY-MM-DD HH24:MI:SS');
-- 7. VARCHAR2 to DATE: Validate format first
-- SELECT COUNT(*) FROM table_name WHERE column_name IS NOT NULL
-- AND NOT REGEXP_LIKE(column_name, '^\d{4}-\d{2}-\d{2}');
-- Clean up
DROP TABLE data_type_demo;
DROP TABLE data_type_demo_backup;
-- Important notes:
-- 1. Always backup data before major conversions
-- 2. Test conversions on development environment first
-- 3. Check for data loss during precision reduction
-- 4. Consider impact on indexes and constraints
-- 5. Update application code if data types change
-- 6. Some conversions may require table recreation| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| COUNT | AVG_SALARY | MIN_SALARY | MAX_SALARY | SUM_SALARY |
|---|---|---|---|---|
| 4 | 59,250 | 50,000 | 70,000 | 237,000 |
Aggregate over all 4 employees โ AVG=(50+62+55+70)/4=59,250
Use ALTER TABLE MODIFY statement to change the data type of a column, with considerations for data compatibility and potential data loss.
Use COUNT(DISTINCT column_name) to count unique values, or combine COUNT with DISTINCT in subqueries for more complex scenarios.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Basic COUNT DISTINCT
SELECT COUNT(DISTINCT department_id) as unique_departments
FROM employees;
-- Count distinct values across multiple columns
SELECT COUNT(DISTINCT department_id) as unique_departments,
COUNT(DISTINCT job_id) as unique_jobs,
COUNT(DISTINCT manager_id) as unique_managers
FROM employees;
-- Count distinct combinations of multiple columns
SELECT COUNT(DISTINCT department_id || '_' || job_id) as unique_dept_job_combinations
FROM employees;
-- Alternative using GROUP BY for distinct count
SELECT COUNT(*) as unique_departments
FROM (
SELECT DISTINCT department_id
FROM employees
);
-- Count distinct with conditions
SELECT COUNT(DISTINCT CASE WHEN salary > 50000 THEN department_id END) as high_salary_departments
FROM employees;
-- Compare total vs distinct counts
SELECT COUNT(*) as total_employees,
COUNT(DISTINCT department_id) as unique_departments,
COUNT(DISTINCT job_id) as unique_jobs,
COUNT(*) / COUNT(DISTINCT department_id) as avg_employees_per_dept
FROM employees;
-- Distinct count by group
SELECT department_id,
COUNT(*) as total_employees,
COUNT(DISTINCT job_id) as unique_jobs_in_dept
FROM employees
GROUP BY department_id;
-- Multiple distinct counts with filtering
SELECT COUNT(DISTINCT employee_id) as unique_employees,
COUNT(DISTINCT CASE WHEN salary > 50000 THEN employee_id END) as high_earners,
COUNT(DISTINCT CASE WHEN hire_date >= ADD_MONTHS(SYSDATE, -12) THEN employee_id END) as recent_hires
FROM employees;
-- Using analytical functions for distinct counts
SELECT department_id,
employee_id,
COUNT(DISTINCT department_id) OVER () as total_unique_departments,
COUNT(DISTINCT job_id) OVER (PARTITION BY department_id) as unique_jobs_in_current_dept
FROM employees;
-- Distinct count with NULL handling
SELECT COUNT(DISTINCT NVL(commission_pct, -1)) as unique_commission_rates,
COUNT(DISTINCT commission_pct) as unique_non_null_commission_rates
FROM employees;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMPLOYEE | MANAGER |
|---|---|
| Bob Smith | Alice Johnson |
| Carol White | Alice Johnson |
| Dave Brown | Bob Smith |
3 rows โ self join pairs each employee with their manager's name
Use COUNT(DISTINCT column_name) to count unique values, or combine COUNT with DISTINCT in subqueries for more complex scenarios.
Use GROUP BY with COUNT and ORDER BY, combined with ROWNUM or analytical functions to limit results to top 3.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- Method 1: Using ROWNUM (Oracle 11g and earlier)
SELECT * FROM (
SELECT job_id, COUNT(*) as frequency
FROM employees
GROUP BY job_id
ORDER BY COUNT(*) DESC
)
WHERE ROWNUM <= 3;
-- Method 2: Using ROW_NUMBER() analytical function
SELECT job_id, frequency FROM (
SELECT job_id,
COUNT(*) as frequency,
ROW_NUMBER() OVER (ORDER BY COUNT(*) DESC) as rn
FROM employees
GROUP BY job_id
)
WHERE rn <= 3;
-- Method 3: Using RANK() to handle ties
SELECT job_id, frequency FROM (
SELECT job_id,
COUNT(*) as frequency,
RANK() OVER (ORDER BY COUNT(*) DESC) as rank_num
FROM employees
GROUP BY job_id
)
WHERE rank_num <= 3;
-- Method 4: Using FETCH FIRST (Oracle 12c+)
SELECT job_id, COUNT(*) as frequency
FROM employees
GROUP BY job_id
ORDER BY COUNT(*) DESC
FETCH FIRST 3 ROWS ONLY;
-- Method 5: Including percentage and cumulative statistics
SELECT job_id,
frequency,
ROUND(frequency * 100.0 / total_count, 2) as percentage,
ROUND(SUM(frequency) OVER (ORDER BY frequency DESC) * 100.0 / total_count, 2) as cumulative_percentage
FROM (
SELECT job_id,
COUNT(*) as frequency,
SUM(COUNT(*)) OVER () as total_count,
ROW_NUMBER() OVER (ORDER BY COUNT(*) DESC) as rn
FROM employees
GROUP BY job_id
)
WHERE rn <= 3;
-- Method 6: Most frequent values by department
SELECT department_id, job_id, frequency FROM (
SELECT department_id,
job_id,
COUNT(*) as frequency,
ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY COUNT(*) DESC) as rn
FROM employees
GROUP BY department_id, job_id
)
WHERE rn <= 3
ORDER BY department_id, frequency DESC;
-- Method 7: Using DENSE_RANK for better tie handling
SELECT job_id, frequency, rank_position FROM (
SELECT job_id,
COUNT(*) as frequency,
DENSE_RANK() OVER (ORDER BY COUNT(*) DESC) as rank_position
FROM employees
GROUP BY job_id
)
WHERE rank_position <= 3
ORDER BY rank_position, job_id;
-- Method 8: Most frequent salary ranges
SELECT salary_range, frequency FROM (
SELECT CASE
WHEN salary < 30000 THEN 'Low (< 30K)'
WHEN salary < 60000 THEN 'Medium (30K-60K)'
WHEN salary < 100000 THEN 'High (60K-100K)'
ELSE 'Very High (> 100K)'
END as salary_range,
COUNT(*) as frequency,
ROW_NUMBER() OVER (ORDER BY COUNT(*) DESC) as rn
FROM employees
GROUP BY CASE
WHEN salary < 30000 THEN 'Low (< 30K)'
WHEN salary < 60000 THEN 'Medium (30K-60K)'
WHEN salary < 100000 THEN 'High (60K-100K)'
ELSE 'Very High (> 100K)'
END
)
WHERE rn <= 3;
-- Method 9: Including additional statistics
SELECT job_id,
frequency,
ROUND(frequency * 100.0 / SUM(frequency) OVER (), 2) as percentage,
ROUND(AVG(frequency) OVER (), 2) as avg_frequency,
frequency - ROUND(AVG(frequency) OVER (), 2) as deviation_from_avg
FROM (
SELECT job_id, COUNT(*) as frequency
FROM employees
GROUP BY job_id
ORDER BY COUNT(*) DESC
)
WHERE ROWNUM <= 3;
-- Method 10: Dynamic top N using PL/SQL
DECLARE
v_top_n NUMBER := 3;
v_sql VARCHAR2(1000);
TYPE freq_record IS RECORD (
job_id VARCHAR2(10),
frequency NUMBER
);
TYPE freq_table IS TABLE OF freq_record;
v_results freq_table;
BEGIN
v_sql := 'SELECT job_id, frequency FROM (
SELECT job_id, COUNT(*) as frequency,
ROW_NUMBER() OVER (ORDER BY COUNT(*) DESC) as rn
FROM employees
GROUP BY job_id
) WHERE rn <= :1';
EXECUTE IMMEDIATE v_sql BULK COLLECT INTO v_results USING v_top_n;
DBMS_OUTPUT.PUT_LINE('Top ' || v_top_n || ' most frequent job IDs:');
FOR i IN 1..v_results.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(v_results(i).job_id || ': ' || v_results(i).frequency);
END LOOP;
END;
/| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| OPERATION | ROWS | CONTEXT_SWITCHES |
|---|---|---|
| Row-by-row | 4 | 4 (slow) |
| BULK COLLECT | 4 | 1 (fast) |
BULK COLLECT fetches all rows at once โ reduces SQLโPL/SQL round trips
Use GROUP BY with COUNT and ORDER BY, combined with ROWNUM or analytical functions to limit results to top 3.
Use Oracle date functions, GROUP BY with date formatting, and window functions for comprehensive monthly analysis.
-- Basic monthly sales for last 12 months
SELECT
TO_CHAR(sale_date, 'YYYY-MM') AS sales_month,
TO_CHAR(sale_date, 'Mon YYYY') AS month_name,
SUM(amount) AS monthly_sales,
COUNT(*) AS total_transactions,
ROUND(AVG(amount), 2) AS avg_transaction_amount
FROM sales
WHERE sale_date >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -12)
AND sale_date < TRUNC(SYSDATE, 'MM')
GROUP BY
TO_CHAR(sale_date, 'YYYY-MM'),
TO_CHAR(sale_date, 'Mon YYYY'),
EXTRACT(YEAR FROM sale_date),
EXTRACT(MONTH FROM sale_date)
ORDER BY
EXTRACT(YEAR FROM sale_date) DESC,
EXTRACT(MONTH FROM sale_date) DESC;
-- Advanced version with analytics
WITH monthly_sales AS (
SELECT
TRUNC(sale_date, 'MM') AS month_start,
SUM(amount) AS monthly_sales
FROM sales
WHERE sale_date >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -12)
GROUP BY TRUNC(sale_date, 'MM')
)
SELECT
TO_CHAR(month_start, 'Mon YYYY') AS month_name,
monthly_sales,
LAG(monthly_sales, 1) OVER (ORDER BY month_start) AS prev_month_sales,
ROUND((monthly_sales - LAG(monthly_sales, 1) OVER (ORDER BY month_start)) /
LAG(monthly_sales, 1) OVER (ORDER BY month_start) * 100, 2) AS mom_growth_pct
FROM monthly_sales
ORDER BY month_start DESC;| EMP_NAME | SALARY | PREV_SALARY | NEXT_SALARY |
|---|---|---|---|
| Alice Johnson | 50,000 | NULL | 55,000 |
| Carol White | 55,000 | 50,000 | 62,000 |
| Bob Smith | 62,000 | 55,000 | 70,000 |
| Dave Brown | 70,000 | 62,000 | NULL |
LAG = previous row value, LEAD = next row value in ordered result
Use Oracle date functions, GROUP BY with date formatting, and window functions for comprehensive monthly analysis.
Calculate median using window functions and percentile logic by finding the middle value(s) in ordered datasets.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- Method 1: Using PERCENTILE_CONT (if allowed)
SELECT department_id,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) as median_salary
FROM employees
GROUP BY department_id;
-- Method 2: Manual median calculation using ROW_NUMBER
WITH ranked_salaries AS (
SELECT department_id, salary,
ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary) as rn,
COUNT(*) OVER (PARTITION BY department_id) as total_count
FROM employees
),
median_positions AS (
SELECT department_id, salary, rn, total_count,
CASE
WHEN MOD(total_count, 2) = 1 THEN (total_count + 1) / 2
ELSE total_count / 2
END as median_pos1,
CASE
WHEN MOD(total_count, 2) = 1 THEN (total_count + 1) / 2
ELSE (total_count / 2) + 1
END as median_pos2
FROM ranked_salaries
)
SELECT department_id,
CASE
WHEN total_count = 1 THEN
MAX(CASE WHEN rn = median_pos1 THEN salary END)
WHEN MOD(total_count, 2) = 1 THEN
MAX(CASE WHEN rn = median_pos1 THEN salary END)
ELSE
(MAX(CASE WHEN rn = median_pos1 THEN salary END) +
MAX(CASE WHEN rn = median_pos2 THEN salary END)) / 2
END as median_salary
FROM median_positions
GROUP BY department_id, total_count;
-- Method 3: Using NTILE for approximate median
WITH salary_quartiles AS (
SELECT department_id, salary,
NTILE(2) OVER (PARTITION BY department_id ORDER BY salary) as half
FROM employees
)
SELECT department_id,
AVG(salary) as approximate_median
FROM salary_quartiles
WHERE half = 1
GROUP BY department_id;
-- Method 4: Using analytical functions with proper median logic
WITH ordered_salaries AS (
SELECT department_id, salary,
ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary ASC) as rn_asc,
ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) as rn_desc,
COUNT(*) OVER (PARTITION BY department_id) as cnt
FROM employees
)
SELECT department_id,
AVG(salary) as median_salary
FROM ordered_salaries
WHERE rn_asc IN (FLOOR((cnt + 1) / 2), CEIL((cnt + 1) / 2))
GROUP BY department_id;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_NAME | SALARY | ROW_NUM | RANK | DENSE_RANK |
|---|---|---|---|---|
| Dave Brown | 70,000 | 1 | 1 | 1 |
| Bob Smith | 62,000 | 2 | 2 | 2 |
| Carol White | 55,000 | 3 | 3 | 3 |
| Alice Johnson | 50,000 | 4 | 4 | 4 |
All 4 employees ranked by salary DESC โ no ties so all methods agree
Calculate median using window functions and percentile logic by finding the middle value(s) in ordered datasets.
Use window functions to identify duplicates and remove older versions while keeping the most recent record based on timestamp or other criteria.
-- Sample table with duplicates
CREATE TABLE customer_records (
record_id NUMBER,
customer_id NUMBER,
customer_name VARCHAR2(100),
email VARCHAR2(100),
created_date DATE,
last_updated DATE
);
-- Method 1: Identify duplicates using ROW_NUMBER()
WITH duplicate_analysis AS (
SELECT record_id, customer_id, customer_name, email,
created_date, last_updated,
ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY last_updated DESC, record_id DESC
) as rn,
COUNT(*) OVER (PARTITION BY customer_id) as duplicate_count
FROM customer_records
)
SELECT record_id, customer_id, customer_name,
CASE WHEN rn = 1 THEN 'KEEP' ELSE 'DELETE' END as action,
duplicate_count
FROM duplicate_analysis
WHERE duplicate_count > 1
ORDER BY customer_id, rn;
-- Method 2: Delete duplicates keeping most recent
DELETE FROM customer_records
WHERE record_id IN (
SELECT record_id
FROM (
SELECT record_id,
ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY last_updated DESC, record_id DESC
) as rn
FROM customer_records
)
WHERE rn > 1
);
-- Method 3: Using MERGE to handle duplicates during insert
MERGE INTO customer_master cm
USING (
SELECT customer_id, customer_name, email, last_updated,
ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY last_updated DESC
) as rn
FROM customer_staging
) cs ON (cm.customer_id = cs.customer_id AND cs.rn = 1)
WHEN MATCHED THEN
UPDATE SET
customer_name = cs.customer_name,
email = cs.email,
last_updated = cs.last_updated
WHERE cs.last_updated > cm.last_updated
WHEN NOT MATCHED THEN
INSERT (customer_id, customer_name, email, last_updated)
VALUES (cs.customer_id, cs.customer_name, cs.email, cs.last_updated);
-- Method 4: Create deduplicated table
CREATE TABLE customer_clean AS
SELECT record_id, customer_id, customer_name, email,
created_date, last_updated
FROM (
SELECT record_id, customer_id, customer_name, email,
created_date, last_updated,
ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY last_updated DESC, record_id DESC
) as rn
FROM customer_records
)
WHERE rn = 1;
-- Method 5: Complex deduplication with business rules
WITH advanced_dedup AS (
SELECT record_id, customer_id, customer_name, email,
created_date, last_updated,
-- Priority: most recent update, then most complete record, then highest ID
ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY
last_updated DESC,
CASE WHEN email IS NOT NULL THEN 1 ELSE 2 END,
LENGTH(customer_name) DESC,
record_id DESC
) as priority_rank,
COUNT(*) OVER (PARTITION BY customer_id) as total_duplicates
FROM customer_records
)
SELECT customer_id, customer_name, email, last_updated,
'KEEP - Priority: ' || priority_rank as status
FROM advanced_dedup
WHERE priority_rank = 1;
-- Method 6: Duplicate detection with EXCEPTION handling
CREATE OR REPLACE PROCEDURE clean_customer_duplicates IS
v_deleted_count NUMBER := 0;
BEGIN
-- Create backup first
EXECUTE IMMEDIATE 'CREATE TABLE customer_backup_' ||
TO_CHAR(SYSDATE, 'YYYYMMDDHH24MISS') ||
' AS SELECT * FROM customer_records';
-- Delete duplicates
DELETE FROM customer_records
WHERE record_id IN (
SELECT record_id
FROM (
SELECT record_id,
ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY last_updated DESC, record_id DESC
) as rn
FROM customer_records
)
WHERE rn > 1
);
v_deleted_count := SQL%ROWCOUNT;
DBMS_OUTPUT.PUT_LINE('Deleted ' || v_deleted_count || ' duplicate records');
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
DBMS_OUTPUT.PUT_LINE('Error during deduplication: ' || SQLERRM);
RAISE;
END;
/
-- Method 7: Report duplicate statistics before cleanup
SELECT customer_id,
COUNT(*) as total_records,
COUNT(*) - 1 as duplicates_to_remove,
MIN(created_date) as first_created,
MAX(last_updated) as most_recent_update,
LISTAGG(record_id, ',') WITHIN GROUP (ORDER BY last_updated DESC) as all_record_ids
FROM customer_records
GROUP BY customer_id
HAVING COUNT(*) > 1
ORDER BY COUNT(*) DESC;| EMP_ID | EMP_NAME | ACTION | RESULT |
|---|---|---|---|
| 101 | Alice Johnson | UPDATED (matched) | Salary โ 55,000 |
| 102 | Bob Smith | UPDATED (matched) | Salary โ 68,200 |
| 999 | New Hire | INSERTED (not matched) | New row added |
MERGE: 2 rows updated + 1 inserted in one atomic statement
Use window functions to identify duplicates and remove older versions while keeping the most recent record based on timestamp or other criteria.
Use LAG window function or self-joins to compare current period values with previous period values and calculate growth percentages.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Sample sales data setup
CREATE TABLE monthly_sales (
year_month DATE,
sales_amount NUMBER,
region VARCHAR2(50)
);
-- Method 1: Using LAG window function
SELECT year_month,
sales_amount,
LAG(sales_amount, 12) OVER (ORDER BY year_month) as previous_year_sales,
sales_amount - LAG(sales_amount, 12) OVER (ORDER BY year_month) as absolute_growth,
ROUND(
((sales_amount - LAG(sales_amount, 12) OVER (ORDER BY year_month)) /
LAG(sales_amount, 12) OVER (ORDER BY year_month)) * 100, 2
) as yoy_growth_percentage
FROM monthly_sales
ORDER BY year_month;
-- Method 2: YoY growth by region
SELECT region, year_month, sales_amount,
LAG(sales_amount, 12) OVER (PARTITION BY region ORDER BY year_month) as prev_year_sales,
CASE
WHEN LAG(sales_amount, 12) OVER (PARTITION BY region ORDER BY year_month) IS NOT NULL
THEN ROUND(
((sales_amount - LAG(sales_amount, 12) OVER (PARTITION BY region ORDER BY year_month)) /
LAG(sales_amount, 12) OVER (PARTITION BY region ORDER BY year_month)) * 100, 2
)
ELSE NULL
END as yoy_growth_pct
FROM monthly_sales
ORDER BY region, year_month;
-- Method 3: Annual aggregated YoY growth
WITH annual_sales AS (
SELECT EXTRACT(YEAR FROM year_month) as sales_year,
SUM(sales_amount) as total_sales
FROM monthly_sales
GROUP BY EXTRACT(YEAR FROM year_month)
)
SELECT sales_year,
total_sales,
LAG(total_sales) OVER (ORDER BY sales_year) as previous_year_total,
total_sales - LAG(total_sales) OVER (ORDER BY sales_year) as absolute_growth,
ROUND(
((total_sales - LAG(total_sales) OVER (ORDER BY sales_year)) /
LAG(total_sales) OVER (ORDER BY sales_year)) * 100, 2
) as yoy_growth_percentage
FROM annual_sales
ORDER BY sales_year;
-- Method 4: Quarterly YoY comparison
WITH quarterly_sales AS (
SELECT EXTRACT(YEAR FROM year_month) as sales_year,
CEIL(EXTRACT(MONTH FROM year_month) / 3) as quarter,
SUM(sales_amount) as quarterly_sales
FROM monthly_sales
GROUP BY EXTRACT(YEAR FROM year_month), CEIL(EXTRACT(MONTH FROM year_month) / 3)
)
SELECT sales_year, quarter, quarterly_sales,
LAG(quarterly_sales, 4) OVER (PARTITION BY quarter ORDER BY sales_year) as prev_year_quarter,
ROUND(
CASE
WHEN LAG(quarterly_sales, 4) OVER (PARTITION BY quarter ORDER BY sales_year) > 0
THEN ((quarterly_sales - LAG(quarterly_sales, 4) OVER (PARTITION BY quarter ORDER BY sales_year)) /
LAG(quarterly_sales, 4) OVER (PARTITION BY quarter ORDER BY sales_year)) * 100
ELSE NULL
END, 2
) as yoy_quarter_growth_pct
FROM quarterly_sales
ORDER BY quarter, sales_year;
-- Method 5: Self-join approach (alternative to LAG)
SELECT this_year.year_month,
this_year.sales_amount as current_sales,
last_year.sales_amount as previous_year_sales,
ROUND(
((this_year.sales_amount - last_year.sales_amount) / last_year.sales_amount) * 100, 2
) as yoy_growth_percentage
FROM monthly_sales this_year
LEFT JOIN monthly_sales last_year
ON ADD_MONTHS(last_year.year_month, 12) = this_year.year_month
AND this_year.region = last_year.region
ORDER BY this_year.year_month;
-- Method 6: Multi-period comparison (YoY, QoQ, MoM)
SELECT year_month, sales_amount,
-- Year over Year
LAG(sales_amount, 12) OVER (ORDER BY year_month) as yoy_prev,
ROUND(((sales_amount - LAG(sales_amount, 12) OVER (ORDER BY year_month)) /
NULLIF(LAG(sales_amount, 12) OVER (ORDER BY year_month), 0)) * 100, 2) as yoy_growth,
-- Quarter over Quarter (3 months)
LAG(sales_amount, 3) OVER (ORDER BY year_month) as qoq_prev,
ROUND(((sales_amount - LAG(sales_amount, 3) OVER (ORDER BY year_month)) /
NULLIF(LAG(sales_amount, 3) OVER (ORDER BY year_month), 0)) * 100, 2) as qoq_growth,
-- Month over Month
LAG(sales_amount, 1) OVER (ORDER BY year_month) as mom_prev,
ROUND(((sales_amount - LAG(sales_amount, 1) OVER (ORDER BY year_month)) /
NULLIF(LAG(sales_amount, 1) OVER (ORDER BY year_month), 0)) * 100, 2) as mom_growth
FROM monthly_sales
ORDER BY year_month;
-- Method 7: Growth categorization
WITH growth_analysis AS (
SELECT year_month, sales_amount, region,
LAG(sales_amount, 12) OVER (PARTITION BY region ORDER BY year_month) as prev_year_sales,
ROUND(
((sales_amount - LAG(sales_amount, 12) OVER (PARTITION BY region ORDER BY year_month)) /
NULLIF(LAG(sales_amount, 12) OVER (PARTITION BY region ORDER BY year_month), 0)) * 100, 2
) as yoy_growth_pct
FROM monthly_sales
)
SELECT year_month, region, sales_amount, prev_year_sales, yoy_growth_pct,
CASE
WHEN yoy_growth_pct IS NULL THEN 'No Prior Data'
WHEN yoy_growth_pct >= 20 THEN 'High Growth'
WHEN yoy_growth_pct >= 10 THEN 'Moderate Growth'
WHEN yoy_growth_pct >= 0 THEN 'Low Growth'
WHEN yoy_growth_pct >= -10 THEN 'Slight Decline'
ELSE 'Significant Decline'
END as growth_category
FROM growth_analysis
WHERE year_month >= ADD_MONTHS(SYSDATE, -24) -- Last 2 years
ORDER BY region, year_month;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
| Dave Brown | NULL |
4 rows โ all employees kept, NULL where no matching department
Use LAG window function or self-joins to compare current period values with previous period values and calculate growth percentages.
Use window functions like ROW_NUMBER() or analytical functions to identify duplicate records without GROUP BY clause.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- Method 1: Using ROW_NUMBER() to find duplicates
SELECT employee_id, first_name, last_name, email
FROM (
SELECT employee_id, first_name, last_name, email,
ROW_NUMBER() OVER (PARTITION BY first_name, last_name, email ORDER BY employee_id) as rn
FROM employees
)
WHERE rn > 1;
-- Method 2: Using COUNT() OVER() window function
SELECT employee_id, first_name, last_name, email, duplicate_count
FROM (
SELECT employee_id, first_name, last_name, email,
COUNT(*) OVER (PARTITION BY first_name, last_name, email) as duplicate_count
FROM employees
)
WHERE duplicate_count > 1;
-- Method 3: Using EXISTS with correlation
SELECT DISTINCT e1.employee_id, e1.first_name, e1.last_name, e1.email
FROM employees e1
WHERE EXISTS (
SELECT 1 FROM employees e2
WHERE e2.first_name = e1.first_name
AND e2.last_name = e1.last_name
AND e2.email = e1.email
AND e2.ROWID > e1.ROWID
);
-- Method 4: Using MINUS operation
SELECT * FROM employees
MINUS
SELECT * FROM (
SELECT DISTINCT * FROM employees
);
-- Method 5: Using analytical function DENSE_RANK
SELECT employee_id, first_name, last_name, email
FROM (
SELECT employee_id, first_name, last_name, email,
DENSE_RANK() OVER (PARTITION BY first_name, last_name, email ORDER BY employee_id) as rnk
FROM employees
)
WHERE rnk > 1;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_NAME | SALARY | ROW_NUM | RANK | DENSE_RANK |
|---|---|---|---|---|
| Dave Brown | 70,000 | 1 | 1 | 1 |
| Bob Smith | 62,000 | 2 | 2 | 2 |
| Carol White | 55,000 | 3 | 3 | 3 |
| Alice Johnson | 50,000 | 4 | 4 | 4 |
All 4 employees ranked by salary DESC โ no ties so all methods agree
Use window functions like ROW_NUMBER() or analytical functions to identify duplicate records without GROUP BY clause.
Use PERCENTILE_CONT, NTILE, or calculate percentage-based cutoffs to identify the top 10% salary earners.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- Method 1: Using PERCENTILE_CONT to find 90th percentile
WITH salary_percentile AS (
SELECT PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY salary) as top_10_percent_cutoff
FROM employees
)
SELECT e.employee_id, e.first_name, e.last_name, e.salary
FROM employees e, salary_percentile sp
WHERE e.salary >= sp.top_10_percent_cutoff
ORDER BY e.salary DESC;
-- Method 2: Using NTILE to divide into 10 groups
SELECT employee_id, first_name, last_name, salary, salary_decile
FROM (
SELECT employee_id, first_name, last_name, salary,
NTILE(10) OVER (ORDER BY salary DESC) as salary_decile
FROM employees
)
WHERE salary_decile = 1;
-- Method 3: Using PERCENT_RANK function
SELECT employee_id, first_name, last_name, salary, salary_percentile
FROM (
SELECT employee_id, first_name, last_name, salary,
PERCENT_RANK() OVER (ORDER BY salary DESC) as salary_percentile
FROM employees
)
WHERE salary_percentile <= 0.1;
-- Method 4: Calculate top 10% using COUNT and ROWNUM
SELECT employee_id, first_name, last_name, salary
FROM (
SELECT employee_id, first_name, last_name, salary,
ROWNUM as rn,
ROUND((SELECT COUNT(*) FROM employees) * 0.1) as top_10_percent_count
FROM (
SELECT employee_id, first_name, last_name, salary
FROM employees
ORDER BY salary DESC
)
)
WHERE rn <= top_10_percent_count;
-- Method 5: Using analytical function with percentage calculation
WITH ranked_salaries AS (
SELECT employee_id, first_name, last_name, salary,
ROW_NUMBER() OVER (ORDER BY salary DESC) as salary_rank,
COUNT(*) OVER () as total_employees
FROM employees
)
SELECT employee_id, first_name, last_name, salary,
ROUND((salary_rank / total_employees) * 100, 2) as percentile
FROM ranked_salaries
WHERE salary_rank <= (total_employees * 0.1)
ORDER BY salary DESC;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_NAME | SALARY | ROW_NUM | RANK | DENSE_RANK |
|---|---|---|---|---|
| Dave Brown | 70,000 | 1 | 1 | 1 |
| Bob Smith | 62,000 | 2 | 2 | 2 |
| Carol White | 55,000 | 3 | 3 | 3 |
| Alice Johnson | 50,000 | 4 | 4 | 4 |
All 4 employees ranked by salary DESC โ no ties so all methods agree
Use PERCENTILE_CONT, NTILE, or calculate percentage-based cutoffs to identify the top 10% salary earners.
Use NOT EXISTS, LEFT JOIN with NULL check, or NOT IN to find employees without any leave records.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Assuming we have an employee_leaves table
-- CREATE TABLE employee_leaves (leave_id NUMBER, employee_id NUMBER, leave_date DATE, leave_type VARCHAR2(20));
-- Method 1: Using NOT EXISTS (most efficient)
SELECT e.employee_id, e.first_name, e.last_name, e.department_id
FROM employees e
WHERE NOT EXISTS (
SELECT 1
FROM employee_leaves el
WHERE el.employee_id = e.employee_id
);
-- Method 2: Using LEFT JOIN with NULL check
SELECT e.employee_id, e.first_name, e.last_name, e.department_id
FROM employees e
LEFT JOIN employee_leaves el ON e.employee_id = el.employee_id
WHERE el.employee_id IS NULL;
-- Method 3: Using NOT IN (be careful with NULLs)
SELECT employee_id, first_name, last_name, department_id
FROM employees
WHERE employee_id NOT IN (
SELECT employee_id
FROM employee_leaves
WHERE employee_id IS NOT NULL
);
-- Method 4: Using MINUS operation
SELECT employee_id, first_name, last_name, department_id
FROM employees
MINUS
SELECT e.employee_id, e.first_name, e.last_name, e.department_id
FROM employees e
JOIN employee_leaves el ON e.employee_id = el.employee_id;
-- Method 5: Using COUNT with HAVING
SELECT e.employee_id, e.first_name, e.last_name, e.department_id,
COUNT(el.leave_id) as leave_count
FROM employees e
LEFT JOIN employee_leaves el ON e.employee_id = el.employee_id
GROUP BY e.employee_id, e.first_name, e.last_name, e.department_id
HAVING COUNT(el.leave_id) = 0;
-- Method 6: Find employees with no leaves in specific time period
SELECT e.employee_id, e.first_name, e.last_name
FROM employees e
WHERE NOT EXISTS (
SELECT 1
FROM employee_leaves el
WHERE el.employee_id = e.employee_id
AND el.leave_date >= DATE '2024-01-01'
AND el.leave_date <= DATE '2024-12-31'
);| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
| Dave Brown | NULL |
4 rows โ all employees kept, NULL where no matching department
Use NOT EXISTS, LEFT JOIN with NULL check, or NOT IN to find employees without any leave records.
Use subqueries, window functions, or EXISTS to find departments with multiple employees without using GROUP BY in the main query.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Method 1: Using subquery with COUNT
SELECT DISTINCT d.department_id, d.department_name
FROM departments d
WHERE (
SELECT COUNT(*)
FROM employees e
WHERE e.department_id = d.department_id
) > 1;
-- Method 2: Using EXISTS with correlated subquery
SELECT d.department_id, d.department_name
FROM departments d
WHERE EXISTS (
SELECT e1.employee_id
FROM employees e1
WHERE e1.department_id = d.department_id
AND EXISTS (
SELECT 1
FROM employees e2
WHERE e2.department_id = e1.department_id
AND e2.employee_id != e1.employee_id
)
);
-- Method 3: Using window function COUNT() OVER()
SELECT DISTINCT department_id,
(SELECT department_name FROM departments WHERE department_id = e.department_id) as department_name
FROM (
SELECT department_id,
COUNT(*) OVER (PARTITION BY department_id) as emp_count
FROM employees
)
WHERE emp_count > 1;
-- Method 4: Using MINUS to exclude single-employee departments
SELECT department_id, department_name
FROM departments
MINUS
SELECT d.department_id, d.department_name
FROM departments d
WHERE (
SELECT COUNT(*)
FROM employees e
WHERE e.department_id = d.department_id
) <= 1;
-- Method 5: Using self-join
SELECT DISTINCT d.department_id, d.department_name
FROM departments d
WHERE EXISTS (
SELECT 1
FROM employees e1, employees e2
WHERE e1.department_id = d.department_id
AND e2.department_id = d.department_id
AND e1.employee_id != e2.employee_id
);
-- Method 6: Show department details with employee count
SELECT department_id, department_name, employee_count
FROM (
SELECT d.department_id, d.department_name,
(SELECT COUNT(*) FROM employees e WHERE e.department_id = d.department_id) as employee_count
FROM departments d
)
WHERE employee_count > 1
ORDER BY employee_count DESC;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_NAME | SALARY | ROW_NUM | RANK | DENSE_RANK |
|---|---|---|---|---|
| Dave Brown | 70,000 | 1 | 1 | 1 |
| Bob Smith | 62,000 | 2 | 2 | 2 |
| Carol White | 55,000 | 3 | 3 | 3 |
| Alice Johnson | 50,000 | 4 | 4 | 4 |
All 4 employees ranked by salary DESC โ no ties so all methods agree
Use subqueries, window functions, or EXISTS to find departments with multiple employees without using GROUP BY in the main query.
Use window functions like MAX() OVER() with PARTITION BY to find maximum values per group without GROUP BY clause.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Method 1: Using MAX() OVER() window function
SELECT DISTINCT department_id,
MAX(salary) OVER (PARTITION BY department_id) as max_salary_in_dept
FROM employees
ORDER BY department_id;
-- Method 2: Using correlated subquery
SELECT DISTINCT e1.department_id,
(SELECT MAX(e2.salary)
FROM employees e2
WHERE e2.department_id = e1.department_id) as max_salary
FROM employees e1
ORDER BY e1.department_id;
-- Method 3: Show employees with their department's maximum salary
SELECT employee_id, first_name, last_name, department_id, salary,
MAX(salary) OVER (PARTITION BY department_id) as dept_max_salary,
CASE WHEN salary = MAX(salary) OVER (PARTITION BY department_id)
THEN 'HIGHEST PAID IN DEPT'
ELSE 'NOT HIGHEST'
END as salary_status
FROM employees
ORDER BY department_id, salary DESC;
-- Method 4: Using FIRST_VALUE with window function
SELECT DISTINCT department_id,
FIRST_VALUE(salary) OVER (
PARTITION BY department_id
ORDER BY salary DESC
ROWS UNBOUNDED PRECEDING
) as max_salary
FROM employees;
-- Method 5: Find employees who earn the maximum in their department
SELECT employee_id, first_name, last_name, department_id, salary
FROM (
SELECT employee_id, first_name, last_name, department_id, salary,
MAX(salary) OVER (PARTITION BY department_id) as dept_max_salary
FROM employees
)
WHERE salary = dept_max_salary;
-- Method 6: Multiple column maximums per group
SELECT DISTINCT department_id,
MAX(salary) OVER (PARTITION BY department_id) as max_salary,
MAX(hire_date) OVER (PARTITION BY department_id) as latest_hire_date,
MIN(salary) OVER (PARTITION BY department_id) as min_salary,
COUNT(*) OVER (PARTITION BY department_id) as employee_count
FROM employees
ORDER BY department_id;
-- Method 7: Using RANK() to identify maximum values
SELECT department_id, salary as max_salary
FROM (
SELECT department_id, salary,
RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) as salary_rank
FROM employees
)
WHERE salary_rank = 1;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_NAME | SALARY | ROW_NUM | RANK | DENSE_RANK |
|---|---|---|---|---|
| Dave Brown | 70,000 | 1 | 1 | 1 |
| Bob Smith | 62,000 | 2 | 2 | 2 |
| Carol White | 55,000 | 3 | 3 | 3 |
| Alice Johnson | 50,000 | 4 | 4 | 4 |
All 4 employees ranked by salary DESC โ no ties so all methods agree
Use window functions like MAX() OVER() with PARTITION BY to find maximum values per group without GROUP BY clause.
Use window functions, subqueries with date functions, or analytical queries to count leaves per employee per month.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Assuming employee_leaves table with columns: employee_id, leave_date, leave_type
-- Method 1: Using window function COUNT() OVER()
SELECT employee_id, year_month, monthly_leave_count
FROM (
SELECT el.employee_id,
TO_CHAR(el.leave_date, 'YYYY-MM') as year_month,
COUNT(*) OVER (
PARTITION BY el.employee_id, TO_CHAR(el.leave_date, 'YYYY-MM')
) as monthly_leave_count
FROM employee_leaves el
)
WHERE monthly_leave_count > 3
ORDER BY employee_id, year_month;
-- Method 2: Using subquery with date functions
SELECT DISTINCT e.employee_id, e.first_name, e.last_name,
leave_month, monthly_leaves
FROM employees e
JOIN (
SELECT employee_id,
TO_CHAR(leave_date, 'YYYY-MM') as leave_month,
COUNT(*) as monthly_leaves
FROM employee_leaves
WHERE leave_date IS NOT NULL
GROUP BY employee_id, TO_CHAR(leave_date, 'YYYY-MM')
HAVING COUNT(*) > 3
) monthly_counts ON e.employee_id = monthly_counts.employee_id;
-- Method 3: Using EXTRACT functions for year/month
SELECT el.employee_id,
EXTRACT(YEAR FROM el.leave_date) as leave_year,
EXTRACT(MONTH FROM el.leave_date) as leave_month,
COUNT(*) as leaves_in_month
FROM employee_leaves el
WHERE el.leave_date >= ADD_MONTHS(SYSDATE, -12) -- Last 12 months
GROUP BY el.employee_id, EXTRACT(YEAR FROM el.leave_date), EXTRACT(MONTH FROM el.leave_date)
HAVING COUNT(*) > 3
ORDER BY el.employee_id, leave_year, leave_month;
-- Method 4: Detailed analysis with employee information
SELECT e.employee_id, e.first_name, e.last_name, e.department_id,
leave_analysis.leave_month,
leave_analysis.leave_count,
leave_analysis.leave_types
FROM employees e
JOIN (
SELECT employee_id,
TO_CHAR(leave_date, 'Mon YYYY') as leave_month,
COUNT(*) as leave_count,
LISTAGG(leave_type, ', ') WITHIN GROUP (ORDER BY leave_date) as leave_types
FROM employee_leaves
GROUP BY employee_id, TO_CHAR(leave_date, 'Mon YYYY'), TO_CHAR(leave_date, 'YYYY-MM')
HAVING COUNT(*) > 3
) leave_analysis ON e.employee_id = leave_analysis.employee_id
ORDER BY e.employee_id, leave_analysis.leave_month;
-- Method 5: Using TRUNC function for month boundaries
WITH monthly_leaves AS (
SELECT employee_id,
TRUNC(leave_date, 'MM') as month_start,
COUNT(*) as leave_count,
MIN(leave_date) as first_leave,
MAX(leave_date) as last_leave
FROM employee_leaves
GROUP BY employee_id, TRUNC(leave_date, 'MM')
HAVING COUNT(*) > 3
)
SELECT e.employee_id, e.first_name, e.last_name,
TO_CHAR(ml.month_start, 'Month YYYY') as leave_month,
ml.leave_count,
ml.first_leave,
ml.last_leave
FROM employees e
JOIN monthly_leaves ml ON e.employee_id = ml.employee_id
ORDER BY e.employee_id, ml.month_start DESC;
-- Method 6: Current month analysis
SELECT e.employee_id, e.first_name, e.last_name,
COUNT(el.leave_id) as current_month_leaves
FROM employees e
JOIN employee_leaves el ON e.employee_id = el.employee_id
WHERE TRUNC(el.leave_date, 'MM') = TRUNC(SYSDATE, 'MM')
GROUP BY e.employee_id, e.first_name, e.last_name
HAVING COUNT(el.leave_id) > 3;
-- Method 7: Trending analysis - employees with pattern of high leaves
SELECT employee_id,
COUNT(CASE WHEN leave_count > 3 THEN 1 END) as months_with_high_leaves,
AVG(leave_count) as avg_monthly_leaves,
MAX(leave_count) as max_monthly_leaves
FROM (
SELECT employee_id,
TO_CHAR(leave_date, 'YYYY-MM') as month_year,
COUNT(*) as leave_count
FROM employee_leaves
WHERE leave_date >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -6)
GROUP BY employee_id, TO_CHAR(leave_date, 'YYYY-MM')
) monthly_summary
GROUP BY employee_id
HAVING COUNT(CASE WHEN leave_count > 3 THEN 1 END) >= 2
ORDER BY months_with_high_leaves DESC;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
3 rows โ only rows with matching dept_id in both tables (Dave Brown excluded โ no dept)
Use window functions, subqueries with date functions, or analytical queries to count leaves per employee per month.
NVL replaces a NULL value with a specified replacement value, taking exactly two arguments.
NVL2 evaluates an expression and returns one value if it is not NULL, and a different value if it is NULL, taking three arguments total.
COALESCE returns the first non-NULL value from a list of expressions, and can accept more than two arguments, making it more flexible than NVL.
COALESCE is part of the ANSI SQL standard and is generally preferred for portability, while NVL and NVL2 are Oracle-specific functions.
| emp_id | emp_name | commission |
|---|---|---|
| 101 | Alice Johnson | NULL |
| 102 | Bob Smith | 500 |
SELECT emp_name,
NVL(commission, 0) AS nvl_result,
NVL2(commission, 'Has Comm', 'No Comm') AS nvl2_result,
COALESCE(commission, 0) AS coalesce_result
FROM employees;| EMP_NAME | NVL_RESULT | NVL2_RESULT | COALESCE_RESULT |
|---|---|---|---|
| Alice Johnson | 0 | No Comm | 0 |
| Bob Smith | 500 | Has Comm | 500 |
Alice Johnson's NULL commission is handled differently by each function, while Bob Smith's non-NULL commission passes through unchanged
NVL substitutes a single replacement for NULL, NVL2 branches between two values based on whether an expression is NULL, and COALESCE returns the first non-NULL value from any number of expressions.
TO_DATE converts a string into a DATE value by parsing it according to a specified format model, such as 'YYYY-MM-DD'.
TO_CHAR converts a DATE, NUMBER, or other data type into a formatted string, also using a format model to control the output appearance.
Format models use case-sensitive elements like YYYY for a 4-digit year, MM for month, DD for day, and HH24:MI:SS for 24-hour time.
Using TO_DATE and TO_CHAR explicitly with a defined format avoids ambiguity that can arise from implicit conversions, which depend on the session's NLS_DATE_FORMAT setting.
| emp_id | emp_name | hire_date |
|---|---|---|
| 101 | Alice Johnson | 2021-03-15 |
SELECT emp_name,
TO_CHAR(hire_date, 'DD-Mon-YYYY') AS formatted_date
FROM employees
WHERE hire_date = TO_DATE('2021-03-15', 'YYYY-MM-DD');| EMP_NAME | FORMATTED_DATE |
|---|---|
| Alice Johnson | 15-Mar-2021 |
TO_DATE parses the literal string for the WHERE comparison, and TO_CHAR formats hire_date for display
TO_DATE parses a string into a DATE using a format model, and TO_CHAR formats a DATE or number into a string, with explicit format models avoiding ambiguity from session-level NLS settings.
Use hierarchical queries with CONNECT BY and analytical functions to compare subordinate counts across management levels.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Find employees with more direct reports than their manager
WITH employee_reports AS (
SELECT
e.employee_id,
e.first_name,
e.last_name,
e.manager_id,
COUNT(subordinate.employee_id) as direct_reports
FROM employees e
LEFT JOIN employees subordinate ON e.employee_id = subordinate.manager_id
GROUP BY e.employee_id, e.first_name, e.last_name, e.manager_id
),
manager_reports AS (
SELECT
m.employee_id as manager_id,
COUNT(emp.employee_id) as manager_direct_reports
FROM employees m
LEFT JOIN employees emp ON m.employee_id = emp.manager_id
GROUP BY m.employee_id
)
SELECT
er.employee_id,
er.first_name,
er.last_name,
er.direct_reports as employee_direct_reports,
mr.manager_direct_reports,
er.direct_reports - mr.manager_direct_reports as difference
FROM employee_reports er
JOIN manager_reports mr ON er.manager_id = mr.manager_id
WHERE er.direct_reports > mr.manager_direct_reports
ORDER BY difference DESC;
-- Alternative using hierarchical query
SELECT
emp.employee_id,
emp.first_name || ' ' || emp.last_name as employee_name,
emp_reports.direct_reports as employee_subordinates,
mgr_reports.direct_reports as manager_subordinates
FROM employees emp
JOIN (
SELECT manager_id, COUNT(*) as direct_reports
FROM employees
WHERE manager_id IS NOT NULL
GROUP BY manager_id
) emp_reports ON emp.employee_id = emp_reports.manager_id
JOIN (
SELECT manager_id, COUNT(*) as direct_reports
FROM employees
WHERE manager_id IS NOT NULL
GROUP BY manager_id
) mgr_reports ON emp.manager_id = mgr_reports.manager_id
WHERE emp_reports.direct_reports > mgr_reports.direct_reports;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMPLOYEE | MANAGER |
|---|---|
| Bob Smith | Alice Johnson |
| Carol White | Alice Johnson |
| Dave Brown | Bob Smith |
3 rows โ self join pairs each employee with their manager's name
Use hierarchical queries with CONNECT BY and analytical functions to compare subordinate counts across management levels.
Recursive CTEs allow you to query hierarchical data by defining an anchor member (base case) and recursive member that references itself until a termination condition is met.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Basic recursive CTE structure
WITH RECURSIVE hierarchy_cte AS (
-- Anchor member (base case)
SELECT employee_id, name, manager_id, 1 as level
FROM employees
WHERE manager_id IS NULL
UNION ALL
-- Recursive member
SELECT e.employee_id, e.name, e.manager_id, h.level + 1
FROM employees e
INNER JOIN hierarchy_cte h ON e.manager_id = h.employee_id
)
SELECT * FROM hierarchy_cte;
-- Employee hierarchy with path
WITH RECURSIVE emp_hierarchy AS (
SELECT employee_id, name, manager_id, name as path, 1 as level
FROM employees
WHERE manager_id IS NULL
UNION ALL
SELECT e.employee_id, e.name, e.manager_id,
h.path || ' -> ' || e.name as path, h.level + 1
FROM employees e
JOIN emp_hierarchy h ON e.manager_id = h.employee_id
WHERE h.level < 10 -- Prevent infinite recursion
)
SELECT employee_id, name, level, path
FROM emp_hierarchy
ORDER BY level, name;
-- Alternative Oracle CONNECT BY syntax
SELECT employee_id, name, manager_id, LEVEL,
SYS_CONNECT_BY_PATH(name, ' -> ') as hierarchy_path
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR employee_id = manager_id
ORDER SIBLINGS BY name;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| LVL | EMP_ID | EMP_NAME | MANAGER_ID |
|---|---|---|---|
| 1 | 105 | CEO | NULL |
| 2 | 101 | Alice Johnson | 105 |
| 2 | 102 | Bob Smith | 105 |
| 3 | 103 | Carol White | 101 |
Recursive CTE/CONNECT BY traverses hierarchy โ LEVEL = depth from root
Recursive CTEs allow you to query hierarchical data by defining an anchor member (base case) and recursive member that references itself until a termination condition is met.
Use window functions and analytical techniques to identify missing sequences (gaps) and continuous sequences (islands) in data.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- Sample data: employee badge access logs
CREATE TABLE badge_access (
employee_id NUMBER,
access_date DATE
);
-- Finding gaps in sequential dates
WITH date_sequence AS (
SELECT employee_id, access_date,
LAG(access_date) OVER (PARTITION BY employee_id ORDER BY access_date) as prev_date,
access_date - LAG(access_date) OVER (PARTITION BY employee_id ORDER BY access_date) as day_diff
FROM badge_access
),
gaps AS (
SELECT employee_id, prev_date + 1 as gap_start, access_date - 1 as gap_end
FROM date_sequence
WHERE day_diff > 1
)
SELECT employee_id, gap_start, gap_end, gap_end - gap_start + 1 as gap_days
FROM gaps;
-- Finding islands (consecutive sequences)
WITH numbered_sequence AS (
SELECT employee_id, access_date,
ROW_NUMBER() OVER (PARTITION BY employee_id ORDER BY access_date) as rn,
access_date - ROW_NUMBER() OVER (PARTITION BY employee_id ORDER BY access_date) as island_group
FROM badge_access
),
islands AS (
SELECT employee_id, island_group,
MIN(access_date) as island_start,
MAX(access_date) as island_end,
COUNT(*) as consecutive_days
FROM numbered_sequence
GROUP BY employee_id, island_group
)
SELECT employee_id, island_start, island_end, consecutive_days
FROM islands
WHERE consecutive_days >= 5 -- Islands of 5+ consecutive days
ORDER BY employee_id, island_start;
-- Finding gaps in numeric sequences (like order numbers)
WITH order_gaps AS (
SELECT order_id,
LAG(order_id) OVER (ORDER BY order_id) as prev_order_id,
order_id - LAG(order_id) OVER (ORDER BY order_id) - 1 as gap_size
FROM orders
)
SELECT prev_order_id + 1 as missing_start,
order_id - 1 as missing_end,
gap_size as missing_count
FROM order_gaps
WHERE gap_size > 0;
-- Generate missing sequence numbers
WITH RECURSIVE missing_orders AS (
SELECT prev_order_id + 1 as missing_id, order_id - 1 as max_missing
FROM order_gaps
WHERE gap_size > 0
UNION ALL
SELECT missing_id + 1, max_missing
FROM missing_orders
WHERE missing_id < max_missing
)
SELECT missing_id as missing_order_id
FROM missing_orders
ORDER BY missing_id;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_NAME | SALARY | PREV_SALARY | NEXT_SALARY |
|---|---|---|---|
| Alice Johnson | 50,000 | NULL | 55,000 |
| Carol White | 55,000 | 50,000 | 62,000 |
| Bob Smith | 62,000 | 55,000 | 70,000 |
| Dave Brown | 70,000 | 62,000 | NULL |
LAG = previous row value, LEAD = next row value in ordered result
Use window functions and analytical techniques to identify missing sequences (gaps) and continuous sequences (islands) in data.
Use self-joins or window functions to identify records with overlapping start and end dates, commonly used for scheduling and booking systems.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Sample table for bookings with date ranges
CREATE TABLE room_bookings (
booking_id NUMBER,
room_id NUMBER,
guest_name VARCHAR2(100),
start_date DATE,
end_date DATE
);
-- Insert sample data
INSERT ALL
INTO room_bookings VALUES (1, 101, 'John Doe', DATE '2024-01-10', DATE '2024-01-15')
INTO room_bookings VALUES (2, 101, 'Jane Smith', DATE '2024-01-12', DATE '2024-01-18')
INTO room_bookings VALUES (3, 101, 'Bob Wilson', DATE '2024-01-20', DATE '2024-01-25')
INTO room_bookings VALUES (4, 102, 'Alice Brown', DATE '2024-01-10', DATE '2024-01-15')
INTO room_bookings VALUES (5, 101, 'Tom Green', DATE '2024-01-14', DATE '2024-01-16')
SELECT * FROM dual;
-- Method 1: Self-join to find overlapping bookings
SELECT b1.booking_id as booking1_id,
b1.guest_name as guest1,
b1.start_date as start1,
b1.end_date as end1,
b2.booking_id as booking2_id,
b2.guest_name as guest2,
b2.start_date as start2,
b2.end_date as end2,
-- Calculate overlap period
GREATEST(b1.start_date, b2.start_date) as overlap_start,
LEAST(b1.end_date, b2.end_date) as overlap_end,
LEAST(b1.end_date, b2.end_date) - GREATEST(b1.start_date, b2.start_date) + 1 as overlap_days
FROM room_bookings b1
JOIN room_bookings b2 ON b1.room_id = b2.room_id
AND b1.booking_id < b2.booking_id
WHERE b1.start_date <= b2.end_date
AND b1.end_date >= b2.start_date;
-- Method 2: Using window functions to detect overlaps
WITH booking_analysis AS (
SELECT booking_id, room_id, guest_name, start_date, end_date,
LAG(end_date) OVER (PARTITION BY room_id ORDER BY start_date) as prev_end_date,
LAG(guest_name) OVER (PARTITION BY room_id ORDER BY start_date) as prev_guest,
LAG(booking_id) OVER (PARTITION BY room_id ORDER BY start_date) as prev_booking_id
FROM room_bookings
)
SELECT booking_id, room_id, guest_name, start_date, end_date,
prev_booking_id, prev_guest, prev_end_date,
'OVERLAP DETECTED' as conflict_status
FROM booking_analysis
WHERE start_date <= prev_end_date;
-- Method 3: Find all conflicts for a specific room
SELECT DISTINCT b1.booking_id, b1.guest_name, b1.start_date, b1.end_date
FROM room_bookings b1
WHERE b1.room_id = 101
AND EXISTS (
SELECT 1 FROM room_bookings b2
WHERE b2.room_id = b1.room_id
AND b2.booking_id != b1.booking_id
AND b2.start_date <= b1.end_date
AND b2.end_date >= b1.start_date
)
ORDER BY b1.start_date;
-- Method 4: Advanced overlap analysis with conflict resolution
WITH overlapping_groups AS (
SELECT booking_id, room_id, guest_name, start_date, end_date,
-- Create groups of overlapping bookings
SUM(CASE WHEN start_date <= LAG(end_date) OVER (PARTITION BY room_id ORDER BY start_date)
THEN 0 ELSE 1 END)
OVER (PARTITION BY room_id ORDER BY start_date
ROWS UNBOUNDED PRECEDING) as group_id
FROM room_bookings
),
conflict_summary AS (
SELECT room_id, group_id,
COUNT(*) as conflicting_bookings,
MIN(start_date) as group_start,
MAX(end_date) as group_end,
LISTAGG(guest_name, ', ') WITHIN GROUP (ORDER BY start_date) as affected_guests
FROM overlapping_groups
GROUP BY room_id, group_id
HAVING COUNT(*) > 1
)
SELECT room_id,
'Room ' || room_id || ' has ' || conflicting_bookings || ' overlapping bookings' as conflict_description,
group_start, group_end,
affected_guests,
group_end - group_start + 1 as total_conflict_days
FROM conflict_summary;
-- Method 5: Check for gaps between bookings (opposite of overlaps)
WITH booking_gaps AS (
SELECT room_id, booking_id, guest_name, start_date, end_date,
LAG(end_date) OVER (PARTITION BY room_id ORDER BY start_date) as prev_end,
start_date - LAG(end_date) OVER (PARTITION BY room_id ORDER BY start_date) - 1 as gap_days
FROM room_bookings
)
SELECT room_id, booking_id, guest_name,
prev_end + 1 as gap_start,
start_date - 1 as gap_end,
gap_days
FROM booking_gaps
WHERE gap_days > 0;
-- Method 6: Prevent overlaps with validation function
CREATE OR REPLACE FUNCTION check_booking_overlap(
p_room_id NUMBER,
p_start_date DATE,
p_end_date DATE,
p_exclude_booking_id NUMBER DEFAULT NULL
) RETURN VARCHAR2 IS
v_overlap_count NUMBER;
v_conflicting_guest VARCHAR2(100);
BEGIN
SELECT COUNT(*), MAX(guest_name)
INTO v_overlap_count, v_conflicting_guest
FROM room_bookings
WHERE room_id = p_room_id
AND (booking_id != p_exclude_booking_id OR p_exclude_booking_id IS NULL)
AND start_date <= p_end_date
AND end_date >= p_start_date;
IF v_overlap_count > 0 THEN
RETURN 'CONFLICT: Overlaps with booking for ' || v_conflicting_guest;
ELSE
RETURN 'OK: No conflicts detected';
END IF;
END;
/
-- Test the validation function
SELECT check_booking_overlap(101, DATE '2024-01-13', DATE '2024-01-17') as validation_result
FROM dual;
-- Method 7: Timeline view of bookings
SELECT room_id,
start_date,
end_date,
guest_name,
-- Show booking duration
end_date - start_date + 1 as booking_days,
-- Show overlapping periods
CASE
WHEN start_date <= LAG(end_date) OVER (PARTITION BY room_id ORDER BY start_date)
THEN 'OVERLAPS WITH PREVIOUS'
ELSE 'NO CONFLICT'
END as overlap_status
FROM room_bookings
ORDER BY room_id, start_date;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
3 rows โ only rows with matching dept_id in both tables (Dave Brown excluded โ no dept)
Use self-joins or window functions to identify records with overlapping start and end dates, commonly used for scheduling and booking systems.
CONNECT BY is Oracle's native syntax for querying hierarchical data, such as an organizational chart, without needing a recursive CTE.
START WITH defines the root row or rows of the hierarchy, while CONNECT BY defines the relationship between a parent row and its child rows, typically using PRIOR to reference the parent's key.
The pseudo-column LEVEL indicates the depth of each row in the hierarchy, with the root row at LEVEL 1.
CONNECT BY predates the ANSI recursive WITH clause and remains widely used in Oracle applications because it is often more concise for simple parent-child hierarchies.
| emp_id | emp_name | manager_id |
|---|---|---|
| 1 | CEO Smith | NULL |
| 2 | VP Jones | 1 |
| 3 | Manager Lee | 2 |
SELECT LEVEL, emp_name, manager_id
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id
ORDER BY LEVEL;| LEVEL | EMP_NAME | MANAGER_ID |
|---|---|---|
| 1 | CEO Smith | NULL |
| 2 | VP Jones | 1 |
| 3 | Manager Lee | 2 |
LEVEL 1 is the root (no manager), and each subsequent level follows the PRIOR emp_id = manager_id relationship
CONNECT BY with START WITH is Oracle's native hierarchical query syntax, using PRIOR to link parent and child rows and LEVEL to indicate depth, as an alternative to recursive CTEs.
Execution plans show how Oracle processes SQL statements.
Use EXPLAIN PLAN, AUTOTRACE, and SQL tuning techniques to optimize performance.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Generate and view execution plan
EXPLAIN PLAN FOR
SELECT e.first_name, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id
WHERE e.salary > 50000;
-- View the execution plan
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
-- Alternative: Use AUTOTRACE
SET AUTOTRACE ON EXPLAIN;
SELECT COUNT(*) FROM employees WHERE salary > 50000;
SET AUTOTRACE OFF;
-- Analyze execution plan components
SELECT operation, options, object_name, cost, cardinality, bytes
FROM plan_table
WHERE plan_id = (SELECT MAX(plan_id) FROM plan_table);
-- Common optimization techniques
-- 1. Add appropriate indexes
CREATE INDEX idx_emp_salary ON employees(salary);
CREATE INDEX idx_emp_dept_join ON employees(department_id, salary);
-- 2. Use hints when necessary (use sparingly)
SELECT /*+ USE_INDEX(e, idx_emp_salary) */
e.first_name, e.salary
FROM employees e
WHERE e.salary > 50000;
-- 3. Rewrite inefficient queries
-- Bad: Using functions in WHERE clause
SELECT * FROM employees WHERE UPPER(first_name) = 'JOHN';
-- Good: Use function-based index or avoid function
CREATE INDEX idx_emp_upper_name ON employees(UPPER(first_name));
-- or
SELECT * FROM employees WHERE first_name = 'John';
-- 4. Optimize JOIN order and conditions
-- Ensure proper JOIN conditions and consider driving table
SELECT /*+ LEADING(d) USE_NL(e) */
e.first_name, d.department_name
FROM departments d
JOIN employees e ON d.department_id = e.department_id
WHERE d.department_name = 'Sales';
-- 5. Use EXISTS instead of IN for better performance
-- Less efficient
SELECT * FROM departments
WHERE department_id IN (SELECT department_id FROM employees);
-- More efficient
SELECT * FROM departments d
WHERE EXISTS (SELECT 1 FROM employees e WHERE e.department_id = d.department_id);| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
3 rows โ only rows with matching dept_id in both tables (Dave Brown excluded โ no dept)
Execution plans show how Oracle processes SQL statements.
Use ROWNUM, ROW_NUMBER(), or OFFSET/FETCH for pagination.
Consider performance implications and use appropriate indexing strategies.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- Method 1: OFFSET/FETCH (Oracle 12c+) - Recommended
SELECT employee_id, first_name, last_name, salary
FROM employees
ORDER BY salary DESC
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
-- Method 2: ROW_NUMBER() with subquery
SELECT employee_id, first_name, last_name, salary
FROM (
SELECT employee_id, first_name, last_name, salary,
ROW_NUMBER() OVER (ORDER BY salary DESC) as rn
FROM employees
)
WHERE rn BETWEEN 21 AND 30;
-- Method 3: ROWNUM (Oracle 11g and earlier)
SELECT employee_id, first_name, last_name, salary
FROM (
SELECT employee_id, first_name, last_name, salary, ROWNUM rn
FROM (
SELECT employee_id, first_name, last_name, salary
FROM employees
ORDER BY salary DESC
)
WHERE ROWNUM <= 30
)
WHERE rn >= 21;
-- Efficient pagination with seek method (for large datasets)
-- First page
SELECT employee_id, first_name, last_name, salary
FROM employees
WHERE salary IS NOT NULL
ORDER BY salary DESC, employee_id
FETCH NEXT 10 ROWS ONLY;
-- Subsequent pages (using last row from previous page)
SELECT employee_id, first_name, last_name, salary
FROM employees
WHERE (salary < :last_salary OR (salary = :last_salary AND employee_id > :last_employee_id))
AND salary IS NOT NULL
ORDER BY salary DESC, employee_id
FETCH NEXT 10 ROWS ONLY;
-- Performance optimization for pagination
-- Create appropriate indexes
CREATE INDEX idx_emp_salary_id ON employees(salary DESC, employee_id);
-- Count total rows efficiently (for pagination info)
SELECT COUNT(*) OVER() as total_count,
employee_id, first_name, salary
FROM employees
WHERE salary > 50000
ORDER BY salary DESC
OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY;
-- Batch processing for large datasets
DECLARE
CURSOR emp_cursor IS
SELECT employee_id, salary
FROM employees
ORDER BY employee_id;
TYPE emp_array IS TABLE OF emp_cursor%ROWTYPE;
emp_batch emp_array;
batch_size CONSTANT NUMBER := 1000;
BEGIN
OPEN emp_cursor;
LOOP
FETCH emp_cursor BULK COLLECT INTO emp_batch LIMIT batch_size;
-- Process batch
FOR i IN 1..emp_batch.COUNT LOOP
-- Update salary logic here
UPDATE employees
SET salary = emp_batch(i).salary * 1.1
WHERE employee_id = emp_batch(i).employee_id;
END LOOP;
COMMIT; -- Commit each batch
EXIT WHEN emp_cursor%NOTFOUND;
END LOOP;
CLOSE emp_cursor;
END;
/| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| OPERATION | ROWS | CONTEXT_SWITCHES |
|---|---|---|
| Row-by-row | 4 | 4 (slow) |
| BULK COLLECT | 4 | 1 (fast) |
BULK COLLECT fetches all rows at once โ reduces SQLโPL/SQL round trips
Use ROWNUM, ROW_NUMBER(), or OFFSET/FETCH for pagination.
Oracle partitioning divides large tables into smaller, manageable pieces based on partition keys, improving performance and maintenance.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Range partitioning by date
CREATE TABLE sales_data (
sale_id NUMBER,
sale_date DATE,
customer_id NUMBER,
amount NUMBER
)
PARTITION BY RANGE (sale_date) (
PARTITION p_2022 VALUES LESS THAN (DATE '2023-01-01'),
PARTITION p_2023 VALUES LESS THAN (DATE '2024-01-01'),
PARTITION p_2024 VALUES LESS THAN (DATE '2025-01-01'),
PARTITION p_future VALUES LESS THAN (MAXVALUE)
);
-- List partitioning by region
CREATE TABLE customer_regional (
customer_id NUMBER,
name VARCHAR2(100),
region VARCHAR2(50),
signup_date DATE
)
PARTITION BY LIST (region) (
PARTITION p_north VALUES ('North', 'Northeast'),
PARTITION p_south VALUES ('South', 'Southeast'),
PARTITION p_west VALUES ('West', 'Northwest'),
PARTITION p_other VALUES (DEFAULT)
);
-- Hash partitioning for even distribution
CREATE TABLE user_sessions (
session_id VARCHAR2(50),
user_id NUMBER,
start_time TIMESTAMP,
end_time TIMESTAMP
)
PARTITION BY HASH (user_id) PARTITIONS 8;
-- Composite partitioning (Range-Hash)
CREATE TABLE order_details (
order_id NUMBER,
order_date DATE,
customer_id NUMBER,
product_id NUMBER,
quantity NUMBER
)
PARTITION BY RANGE (order_date)
SUBPARTITION BY HASH (customer_id) SUBPARTITIONS 4 (
PARTITION p_2023_q1 VALUES LESS THAN (DATE '2023-04-01'),
PARTITION p_2023_q2 VALUES LESS THAN (DATE '2023-07-01'),
PARTITION p_2023_q3 VALUES LESS THAN (DATE '2023-10-01'),
PARTITION p_2023_q4 VALUES LESS THAN (DATE '2024-01-01')
);
-- Query specific partitions
SELECT * FROM sales_data PARTITION (p_2023)
WHERE amount > 1000;
-- Cross-partition queries
SELECT EXTRACT(YEAR FROM sale_date) as year,
SUM(amount) as total_sales
FROM sales_data
WHERE sale_date BETWEEN DATE '2022-01-01' AND DATE '2024-12-31'
GROUP BY EXTRACT(YEAR FROM sale_date);
-- Partition maintenance operations
-- Add new partition
ALTER TABLE sales_data ADD PARTITION p_2025 VALUES LESS THAN (DATE '2026-01-01');
-- Drop old partition
ALTER TABLE sales_data DROP PARTITION p_2022;
-- Split partition
ALTER TABLE sales_data SPLIT PARTITION p_future
AT (DATE '2026-01-01') INTO (PARTITION p_2025, PARTITION p_future);
-- Partition-wise joins (both tables partitioned on same key)
SELECT s.sale_date, s.amount, c.name
FROM sales_data s
JOIN customer_regional c ON s.customer_id = c.customer_id
WHERE s.sale_date >= DATE '2023-01-01';
-- Check partition information
SELECT table_name, partition_name, high_value, num_rows
FROM user_tab_partitions
WHERE table_name = 'SALES_DATA'
ORDER BY partition_position;
-- Partition pruning examples
-- This query will only scan p_2023 partition
SELECT COUNT(*) FROM sales_data
WHERE sale_date BETWEEN DATE '2023-06-01' AND DATE '2023-06-30';
-- Parallel operations on partitions
SELECT /*+ PARALLEL(s, 4) */
EXTRACT(MONTH FROM sale_date) as month,
SUM(amount) as monthly_sales
FROM sales_data s
WHERE EXTRACT(YEAR FROM sale_date) = 2023
GROUP BY EXTRACT(MONTH FROM sale_date);| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
3 rows โ only rows with matching dept_id in both tables (Dave Brown excluded โ no dept)
Oracle partitioning divides large tables into smaller, manageable pieces based on partition keys, improving performance and maintenance.
Use proper indexing strategies, join order optimization, hints, and query rewriting techniques to improve performance of complex joins.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Problem: Slow query with multiple large table joins
-- Original inefficient query
SELECT o.order_id, c.customer_name, p.product_name, od.quantity, od.price
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
JOIN order_details od ON o.order_id = od.order_id
JOIN products p ON od.product_id = p.product_id
WHERE o.order_date BETWEEN DATE '2023-01-01' AND DATE '2023-12-31'
AND c.country = 'USA'
AND p.category = 'Electronics';
-- Solution 1: Create appropriate indexes
CREATE INDEX idx_orders_date_customer ON orders(order_date, customer_id);
CREATE INDEX idx_customers_country ON customers(country, customer_id);
CREATE INDEX idx_products_category ON products(category, product_id);
CREATE INDEX idx_order_details_composite ON order_details(order_id, product_id);
-- Solution 2: Optimized query with hints and better structure
SELECT /*+ LEADING(c) USE_NL(o) USE_HASH(od p) */
o.order_id, c.customer_name, p.product_name, od.quantity, od.price
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
JOIN order_details od ON o.order_id = od.order_id
JOIN products p ON od.product_id = p.product_id
WHERE c.country = 'USA'
AND o.order_date BETWEEN DATE '2023-01-01' AND DATE '2023-12-31'
AND p.category = 'Electronics';
-- Solution 3: Break into smaller queries with CTEs
WITH filtered_customers AS (
SELECT customer_id, customer_name
FROM customers
WHERE country = 'USA'
),
filtered_orders AS (
SELECT o.order_id, o.customer_id
FROM orders o
JOIN filtered_customers c ON o.customer_id = c.customer_id
WHERE o.order_date BETWEEN DATE '2023-01-01' AND DATE '2023-12-31'
),
filtered_products AS (
SELECT product_id, product_name
FROM products
WHERE category = 'Electronics'
)
SELECT fo.order_id, fc.customer_name, fp.product_name, od.quantity, od.price
FROM filtered_orders fo
JOIN filtered_customers fc ON fo.customer_id = fc.customer_id
JOIN order_details od ON fo.order_id = od.order_id
JOIN filtered_products fp ON od.product_id = fp.product_id;
-- Solution 4: Use EXISTS instead of JOINs when appropriate
SELECT o.order_id,
(SELECT customer_name FROM customers WHERE customer_id = o.customer_id) as customer_name
FROM orders o
WHERE o.order_date BETWEEN DATE '2023-01-01' AND DATE '2023-12-31'
AND EXISTS (SELECT 1 FROM customers c WHERE c.customer_id = o.customer_id AND c.country = 'USA')
AND EXISTS (
SELECT 1 FROM order_details od
JOIN products p ON od.product_id = p.product_id
WHERE od.order_id = o.order_id AND p.category = 'Electronics'
);
-- Solution 5: Materialized views for frequently joined data
CREATE MATERIALIZED VIEW mv_customer_orders
BUILD IMMEDIATE
REFRESH FAST ON COMMIT
AS
SELECT o.order_id, o.order_date, o.customer_id,
c.customer_name, c.country, c.city
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id;
-- Create indexes on materialized view
CREATE INDEX idx_mv_country_date ON mv_customer_orders(country, order_date);
-- Use materialized view in queries
SELECT mv.order_id, mv.customer_name, p.product_name, od.quantity
FROM mv_customer_orders mv
JOIN order_details od ON mv.order_id = od.order_id
JOIN products p ON od.product_id = p.product_id
WHERE mv.country = 'USA'
AND mv.order_date BETWEEN DATE '2023-01-01' AND DATE '2023-12-31'
AND p.category = 'Electronics';
-- Solution 6: Parallel processing for large datasets
SELECT /*+ PARALLEL(o, 4) PARALLEL(c, 4) PARALLEL(od, 4) PARALLEL(p, 4) */
o.order_id, c.customer_name, p.product_name, od.quantity
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
JOIN order_details od ON o.order_id = od.order_id
JOIN products p ON od.product_id = p.product_id
WHERE o.order_date BETWEEN DATE '2023-01-01' AND DATE '2023-12-31';
-- Solution 7: Partitioning large tables
-- Partition orders by year
ALTER TABLE orders MODIFY
PARTITION BY RANGE (order_date) (
PARTITION p_2022 VALUES LESS THAN (DATE '2023-01-01'),
PARTITION p_2023 VALUES LESS THAN (DATE '2024-01-01'),
PARTITION p_2024 VALUES LESS THAN (DATE '2025-01-01')
);
-- Solution 8: Query rewriting with aggregation pushdown
-- Instead of joining then aggregating
SELECT c.country, SUM(od.quantity * od.price) as total_sales
FROM (
-- Pre-aggregate at order level
SELECT o.customer_id, SUM(od.quantity * od.price) as order_total
FROM orders o
JOIN order_details od ON o.order_id = od.order_id
WHERE o.order_date BETWEEN DATE '2023-01-01' AND DATE '2023-12-31'
GROUP BY o.customer_id
) order_totals
JOIN customers c ON order_totals.customer_id = c.customer_id
GROUP BY c.country;
-- Performance monitoring query
SELECT sql_id, child_number, executions, elapsed_time/executions as avg_elapsed,
buffer_gets/executions as avg_buffer_gets,
sql_text
FROM v$sql
WHERE sql_text LIKE '%orders%customers%'
AND executions > 0
ORDER BY elapsed_time/executions DESC;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
3 rows โ only rows with matching dept_id in both tables (Dave Brown excluded โ no dept)
Use proper indexing strategies, join order optimization, hints, and query rewriting techniques to improve performance of complex joins.
Use Oracle connection pooling features, DRCP (Database Resident Connection Pooling), and proper session management techniques for optimal performance.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Enable Database Resident Connection Pooling (DRCP)
-- Run as SYSDBA
EXECUTE DBMS_CONNECTION_POOL.START_POOL();
-- Configure pool parameters
EXECUTE DBMS_CONNECTION_POOL.CONFIGURE_POOL(
pool_name => 'SYS_DEFAULT_CONNECTION_POOL',
minpool_size => 4,
maxpool_size => 40,
incrpool_size => 2,
session_cached_cursors => 20,
inactivity_timeout => 300,
max_think_time => 600,
max_use_session => 500000,
max_lifetime_session => 86400
);
-- Check pool status
SELECT connection_pool, status, minsize, maxsize, poolsize, num_requests
FROM v$cpool_info;
-- Monitor pool statistics
SELECT * FROM v$cpool_stats;
-- Session management queries
-- Check current sessions
SELECT s.sid, s.serial#, s.username, s.status, s.program, s.machine,
s.logon_time, s.last_call_et
FROM v$session s
WHERE s.username IS NOT NULL
ORDER BY s.last_call_et DESC;
-- Session resource usage
SELECT s.sid, s.username, s.program,
st.value as cpu_used,
sm.value as memory_used,
si.value as logical_reads
FROM v$session s
LEFT JOIN v$sesstat st ON s.sid = st.sid AND st.statistic# = (SELECT statistic# FROM v$statname WHERE name = 'CPU used by this session')
LEFT JOIN v$sesstat sm ON s.sid = sm.sid AND sm.statistic# = (SELECT statistic# FROM v$statname WHERE name = 'session memory')
LEFT JOIN v$sesstat si ON s.sid = si.sid AND si.statistic# = (SELECT statistic# FROM v$statname WHERE name = 'session logical reads')
WHERE s.username IS NOT NULL;
-- Kill idle sessions
CREATE OR REPLACE PROCEDURE kill_idle_sessions(
p_idle_minutes NUMBER DEFAULT 60,
p_exclude_users VARCHAR2 DEFAULT 'SYS,SYSTEM'
) IS
CURSOR idle_sessions_cur IS
SELECT sid, serial#, username
FROM v$session
WHERE status = 'INACTIVE'
AND last_call_et > (p_idle_minutes * 60)
AND username IS NOT NULL
AND username NOT IN (SELECT TRIM(REGEXP_SUBSTR(p_exclude_users, '[^,]+', 1, LEVEL))
FROM dual
CONNECT BY LEVEL <= REGEXP_COUNT(p_exclude_users, ',') + 1);
v_sql VARCHAR2(200);
BEGIN
FOR rec IN idle_sessions_cur LOOP
v_sql := 'ALTER SYSTEM KILL SESSION ''' || rec.sid || ',' || rec.serial# || '''';
EXECUTE IMMEDIATE v_sql;
DBMS_OUTPUT.PUT_LINE('Killed session: ' || rec.username || ' (SID=' || rec.sid || ')');
END LOOP;
END;
/
-- Connection pool monitoring procedure
CREATE OR REPLACE PROCEDURE monitor_connection_pool IS
v_active_sessions NUMBER;
v_inactive_sessions NUMBER;
v_total_sessions NUMBER;
v_cpu_usage NUMBER;
BEGIN
-- Get session counts
SELECT COUNT(CASE WHEN status = 'ACTIVE' THEN 1 END),
COUNT(CASE WHEN status = 'INACTIVE' THEN 1 END),
COUNT(*)
INTO v_active_sessions, v_inactive_sessions, v_total_sessions
FROM v$session
WHERE username IS NOT NULL;
-- Get CPU usage
SELECT value INTO v_cpu_usage
FROM v$sysstat
WHERE name = 'CPU used by this session';
-- Log statistics
INSERT INTO connection_pool_stats (
timestamp, active_sessions, inactive_sessions,
total_sessions, cpu_usage
) VALUES (
SYSTIMESTAMP, v_active_sessions, v_inactive_sessions,
v_total_sessions, v_cpu_usage
);
-- Alert if thresholds exceeded
IF v_total_sessions > 100 THEN
DBMS_OUTPUT.PUT_LINE('WARNING: High session count: ' || v_total_sessions);
END IF;
COMMIT;
END;
/
-- Create monitoring table
CREATE TABLE connection_pool_stats (
timestamp TIMESTAMP,
active_sessions NUMBER,
inactive_sessions NUMBER,
total_sessions NUMBER,
cpu_usage NUMBER
);
-- Application context for session management
CREATE OR REPLACE CONTEXT app_context USING app_context_pkg;
CREATE OR REPLACE PACKAGE app_context_pkg IS
PROCEDURE set_session_info(
p_user_id VARCHAR2,
p_app_name VARCHAR2,
p_module VARCHAR2
);
PROCEDURE clear_session_info;
END;
/
CREATE OR REPLACE PACKAGE BODY app_context_pkg IS
PROCEDURE set_session_info(
p_user_id VARCHAR2,
p_app_name VARCHAR2,
p_module VARCHAR2
) IS
BEGIN
DBMS_SESSION.SET_CONTEXT('APP_CONTEXT', 'USER_ID', p_user_id);
DBMS_SESSION.SET_CONTEXT('APP_CONTEXT', 'APP_NAME', p_app_name);
DBMS_SESSION.SET_CONTEXT('APP_CONTEXT', 'MODULE', p_module);
DBMS_APPLICATION_INFO.SET_MODULE(p_app_name, p_module);
END;
PROCEDURE clear_session_info IS
BEGIN
DBMS_SESSION.CLEAR_CONTEXT('APP_CONTEXT');
DBMS_APPLICATION_INFO.SET_MODULE(NULL, NULL);
END;
END;
/
-- Session pooling configuration for applications
-- JDBC connection pooling example configuration
/*
-- HikariCP configuration
maximumPoolSize=20
minimumIdle=5
connectionTimeout=30000
idleTimeout=600000
maxLifetime=1800000
leakDetectionThreshold=60000
-- Oracle UCP configuration
oracle.ucp.ConnectionPoolName=MyPool
oracle.ucp.InitialPoolSize=5
oracle.ucp.MinPoolSize=5
oracle.ucp.MaxPoolSize=20
oracle.ucp.ConnectionWaitTimeout=3
oracle.ucp.InactiveConnectionTimeout=30
oracle.ucp.AbandonedConnectionTimeout=30
*/
-- Session resource limits
ALTER PROFILE DEFAULT LIMIT
SESSIONS_PER_USER 10
CONNECT_TIME 480
IDLE_TIME 30
CPU_PER_SESSION UNLIMITED
LOGICAL_READS_PER_SESSION UNLIMITED;
-- Monitor session waits
SELECT s.sid, s.username, s.status, w.event, w.wait_time, w.seconds_in_wait
FROM v$session s
LEFT JOIN v$session_wait w ON s.sid = w.sid
WHERE s.username IS NOT NULL
AND w.event NOT LIKE '%message%'
ORDER BY w.seconds_in_wait DESC;
-- Session cleanup procedure
CREATE OR REPLACE PROCEDURE cleanup_sessions IS
BEGIN
-- Kill sessions that have been inactive for more than 2 hours
FOR rec IN (
SELECT sid, serial#, username
FROM v$session
WHERE status = 'INACTIVE'
AND last_call_et > 7200
AND username NOT IN ('SYS', 'SYSTEM', 'DBSNMP')
) LOOP
EXECUTE IMMEDIATE 'ALTER SYSTEM KILL SESSION ''' || rec.sid || ',' || rec.serial# || '''';
END LOOP;
-- Log cleanup activity
INSERT INTO session_cleanup_log (cleanup_date, sessions_killed)
SELECT SYSDATE, SQL%ROWCOUNT FROM dual;
COMMIT;
END;
/
-- Create cleanup log table
CREATE TABLE session_cleanup_log (
cleanup_date DATE,
sessions_killed NUMBER
);
-- Schedule session cleanup job
BEGIN
DBMS_SCHEDULER.CREATE_JOB(
job_name => 'SESSION_CLEANUP_JOB',
job_type => 'PLSQL_BLOCK',
job_action => 'BEGIN cleanup_sessions; END;',
start_date => SYSTIMESTAMP,
repeat_interval => 'FREQ=HOURLY; INTERVAL=2',
enabled => TRUE,
comments => 'Cleanup idle sessions every 2 hours'
);
END;
/
-- Connection pool health check
CREATE OR REPLACE FUNCTION connection_pool_health_check RETURN VARCHAR2 IS
v_total_sessions NUMBER;
v_active_sessions NUMBER;
v_blocked_sessions NUMBER;
v_health_status VARCHAR2(100);
BEGIN
SELECT COUNT(*) INTO v_total_sessions
FROM v$session WHERE username IS NOT NULL;
SELECT COUNT(*) INTO v_active_sessions
FROM v$session WHERE status = 'ACTIVE' AND username IS NOT NULL;
SELECT COUNT(*) INTO v_blocked_sessions
FROM v$session WHERE blocking_session IS NOT NULL;
IF v_blocked_sessions > 5 THEN
v_health_status := 'CRITICAL: ' || v_blocked_sessions || ' blocked sessions';
ELSIF v_total_sessions > 80 THEN
v_health_status := 'WARNING: High session count ' || v_total_sessions;
ELSIF v_active_sessions < 5 THEN
v_health_status := 'INFO: Low activity ' || v_active_sessions || ' active';
ELSE
v_health_status := 'HEALTHY: ' || v_total_sessions || ' total, ' || v_active_sessions || ' active';
END IF;
RETURN v_health_status;
END;
/
-- Test health check
SELECT connection_pool_health_check() as pool_status FROM dual;
-- Advanced session monitoring query
SELECT s.sid, s.serial#, s.username, s.program, s.machine,
s.status, s.last_call_et/60 as idle_minutes,
ss.value as logical_reads,
ROUND(ss.value / GREATEST(s.last_call_et, 1), 2) as reads_per_second
FROM v$session s
LEFT JOIN v$sesstat ss ON s.sid = ss.sid
AND ss.statistic# = (SELECT statistic# FROM v$statname WHERE name = 'session logical reads')
WHERE s.username IS NOT NULL
ORDER BY ss.value DESC;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| DYNAMIC_SQL | EXECUTED | ROWS_AFFECTED |
|---|---|---|
| UPDATE employees SET... | โ | 2 rows |
| DROP TABLE temp_tbl | โ | Table removed |
EXECUTE IMMEDIATE runs SQL assembled as a string at runtime
Use Oracle connection pooling features, DRCP (Database Resident Connection Pooling), and proper session management techniques for optimal performance.
Use Oracle Streams, GoldenGate, or custom triggers and database links to implement real-time data replication and synchronization.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
-- Method 1: Database Link and Trigger-based Synchronization
-- Create database link to target database
CREATE DATABASE LINK target_db
CONNECT TO sync_user IDENTIFIED BY password
USING 'target_database_tns';
-- Test database link
SELECT * FROM dual@target_db;
-- Create synchronization log table
CREATE TABLE sync_log (
log_id NUMBER GENERATED ALWAYS AS IDENTITY,
table_name VARCHAR2(50),
operation VARCHAR2(10),
record_id VARCHAR2(100),
sync_status VARCHAR2(20) DEFAULT 'PENDING',
error_message VARCHAR2(4000),
created_date TIMESTAMP DEFAULT SYSTIMESTAMP,
processed_date TIMESTAMP
);
-- Synchronization trigger for employees table
CREATE OR REPLACE TRIGGER trg_employee_sync
AFTER INSERT OR UPDATE OR DELETE ON employees
FOR EACH ROW
DECLARE
v_operation VARCHAR2(10);
v_employee_id NUMBER;
v_sql VARCHAR2(4000);
BEGIN
-- Determine operation and employee ID
IF INSERTING THEN
v_operation := 'INSERT';
v_employee_id := :NEW.employee_id;
v_sql := 'INSERT INTO employees@target_db (employee_id, first_name, last_name, email, salary, department_id, hire_date) ' ||
'VALUES (' || :NEW.employee_id || ', ''' || :NEW.first_name || ''', ''' || :NEW.last_name || ''', ''' ||
:NEW.email || ''', ' || :NEW.salary || ', ' || :NEW.department_id || ', DATE ''' ||
TO_CHAR(:NEW.hire_date, 'YYYY-MM-DD') || ''')';
ELSIF UPDATING THEN
v_operation := 'UPDATE';
v_employee_id := :NEW.employee_id;
v_sql := 'UPDATE employees@target_db SET first_name = ''' || :NEW.first_name ||
''', last_name = ''' || :NEW.last_name || ''', email = ''' || :NEW.email ||
''', salary = ' || :NEW.salary || ', department_id = ' || :NEW.department_id ||
', hire_date = DATE ''' || TO_CHAR(:NEW.hire_date, 'YYYY-MM-DD') ||
''' WHERE employee_id = ' || :NEW.employee_id;
ELSIF DELETING THEN
v_operation := 'DELETE';
v_employee_id := :OLD.employee_id;
v_sql := 'DELETE FROM employees@target_db WHERE employee_id = ' || :OLD.employee_id;
END IF;
-- Log synchronization request
INSERT INTO sync_log (table_name, operation, record_id, sync_status)
VALUES ('EMPLOYEES', v_operation, v_employee_id, 'PENDING');
-- Execute synchronization
BEGIN
EXECUTE IMMEDIATE v_sql;
-- Update log as successful
UPDATE sync_log
SET sync_status = 'SUCCESS', processed_date = SYSTIMESTAMP
WHERE log_id = (SELECT MAX(log_id) FROM sync_log WHERE record_id = v_employee_id);
EXCEPTION
WHEN OTHERS THEN
-- Log error
UPDATE sync_log
SET sync_status = 'ERROR',
error_message = SQLERRM,
processed_date = SYSTIMESTAMP
WHERE log_id = (SELECT MAX(log_id) FROM sync_log WHERE record_id = v_employee_id);
END;
END;
/
-- Method 2: Batch Synchronization Procedure
CREATE OR REPLACE PROCEDURE sync_employees_batch IS
CURSOR unsync_records IS
SELECT log_id, table_name, operation, record_id
FROM sync_log
WHERE sync_status = 'PENDING'
AND table_name = 'EMPLOYEES'
ORDER BY created_date;
v_sql VARCHAR2(4000);
v_employee_rec employees%ROWTYPE;
BEGIN
FOR rec IN unsync_records LOOP
BEGIN
-- Get current employee data
SELECT * INTO v_employee_rec
FROM employees
WHERE employee_id = rec.record_id;
-- Build sync SQL based on operation
CASE rec.operation
WHEN 'INSERT' THEN
v_sql := 'INSERT INTO employees@target_db VALUES (' ||
v_employee_rec.employee_id || ', ''' ||
v_employee_rec.first_name || ''', ''' ||
v_employee_rec.last_name || ''', ''' ||
v_employee_rec.email || ''', ' ||
v_employee_rec.salary || ', ' ||
v_employee_rec.department_id || ', DATE ''' ||
TO_CHAR(v_employee_rec.hire_date, 'YYYY-MM-DD') || ''')';
WHEN 'UPDATE' THEN
v_sql := 'UPDATE employees@target_db SET ' ||
'first_name = ''' || v_employee_rec.first_name || ''', ' ||
'last_name = ''' || v_employee_rec.last_name || ''', ' ||
'email = ''' || v_employee_rec.email || ''', ' ||
'salary = ' || v_employee_rec.salary || ', ' ||
'department_id = ' || v_employee_rec.department_id || ', ' ||
'hire_date = DATE ''' || TO_CHAR(v_employee_rec.hire_date, 'YYYY-MM-DD') ||
''' WHERE employee_id = ' || v_employee_rec.employee_id;
WHEN 'DELETE' THEN
v_sql := 'DELETE FROM employees@target_db WHERE employee_id = ' || rec.record_id;
END CASE;
-- Execute synchronization
EXECUTE IMMEDIATE v_sql;
-- Update sync status
UPDATE sync_log
SET sync_status = 'SUCCESS', processed_date = SYSTIMESTAMP
WHERE log_id = rec.log_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- Handle deleted records
IF rec.operation != 'DELETE' THEN
UPDATE sync_log
SET sync_status = 'ERROR',
error_message = 'Record not found in source table',
processed_date = SYSTIMESTAMP
WHERE log_id = rec.log_id;
END IF;
WHEN OTHERS THEN
UPDATE sync_log
SET sync_status = 'ERROR',
error_message = SQLERRM,
processed_date = SYSTIMESTAMP
WHERE log_id = rec.log_id;
END;
COMMIT;
END LOOP;
END;
/
-- Method 3: Conflict Resolution for Bi-directional Sync
CREATE TABLE sync_conflicts (
conflict_id NUMBER GENERATED ALWAYS AS IDENTITY,
table_name VARCHAR2(50),
record_id VARCHAR2(100),
source_database VARCHAR2(50),
target_database VARCHAR2(50),
conflict_type VARCHAR2(50),
source_data CLOB,
target_data CLOB,
resolution_status VARCHAR2(20) DEFAULT 'UNRESOLVED',
created_date TIMESTAMP DEFAULT SYSTIMESTAMP,
resolved_date TIMESTAMP
);
-- Conflict detection procedure
CREATE OR REPLACE PROCEDURE detect_sync_conflicts IS
CURSOR source_records IS
SELECT employee_id, first_name, last_name, email, salary,
department_id, hire_date
FROM employees;
v_target_rec employees%ROWTYPE;
v_conflict_detected BOOLEAN;
BEGIN
FOR src_rec IN source_records LOOP
v_conflict_detected := FALSE;
BEGIN
-- Get corresponding target record
SELECT * INTO v_target_rec
FROM employees@target_db
WHERE employee_id = src_rec.employee_id;
-- Check for data conflicts
IF src_rec.first_name != v_target_rec.first_name OR
src_rec.last_name != v_target_rec.last_name OR
src_rec.email != v_target_rec.email OR
src_rec.salary != v_target_rec.salary OR
src_rec.department_id != v_target_rec.department_id THEN
v_conflict_detected := TRUE;
-- Log conflict
INSERT INTO sync_conflicts (
table_name, record_id, source_database, target_database,
conflict_type, source_data, target_data
) VALUES (
'EMPLOYEES', src_rec.employee_id, 'SOURCE_DB', 'TARGET_DB',
'DATA_MISMATCH',
JSON_OBJECT(
'first_name' VALUE src_rec.first_name,
'last_name' VALUE src_rec.last_name,
'email' VALUE src_rec.email,
'salary' VALUE src_rec.salary,
'department_id' VALUE src_rec.department_id
),
JSON_OBJECT(
'first_name' VALUE v_target_rec.first_name,
'last_name' VALUE v_target_rec.last_name,
'email' VALUE v_target_rec.email,
'salary' VALUE v_target_rec.salary,
'department_id' VALUE v_target_rec.department_id
)
);
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- Record exists in source but not in target
INSERT INTO sync_conflicts (
table_name, record_id, source_database, target_database,
conflict_type, source_data
) VALUES (
'EMPLOYEES', src_rec.employee_id, 'SOURCE_DB', 'TARGET_DB',
'MISSING_IN_TARGET',
JSON_OBJECT(
'first_name' VALUE src_rec.first_name,
'last_name' VALUE src_rec.last_name,
'email' VALUE src_rec.email,
'salary' VALUE src_rec.salary,
'department_id' VALUE src_rec.department_id
)
);
END;
END LOOP;
COMMIT;
END;
/
-- Method 4: Monitoring and Alerting
CREATE OR REPLACE PROCEDURE monitor_sync_health IS
v_pending_count NUMBER;
v_error_count NUMBER;
v_old_errors NUMBER;
v_alert_message VARCHAR2(4000);
BEGIN
-- Count pending synchronizations
SELECT COUNT(*) INTO v_pending_count
FROM sync_log
WHERE sync_status = 'PENDING'
AND created_date >= SYSTIMESTAMP - INTERVAL '1' HOUR;
-- Count recent errors
SELECT COUNT(*) INTO v_error_count
FROM sync_log
WHERE sync_status = 'ERROR'
AND created_date >= SYSTIMESTAMP - INTERVAL '1' HOUR;
-- Count old unresolved errors
SELECT COUNT(*) INTO v_old_errors
FROM sync_log
WHERE sync_status = 'ERROR'
AND created_date < SYSTIMESTAMP - INTERVAL '24' HOUR;
-- Generate alerts
IF v_pending_count > 100 THEN
v_alert_message := 'HIGH: ' || v_pending_count || ' pending sync operations';
-- Send alert (implement your alerting mechanism)
DBMS_OUTPUT.PUT_LINE(v_alert_message);
END IF;
IF v_error_count > 10 THEN
v_alert_message := 'CRITICAL: ' || v_error_count || ' sync errors in last hour';
DBMS_OUTPUT.PUT_LINE(v_alert_message);
END IF;
IF v_old_errors > 0 THEN
v_alert_message := 'WARNING: ' || v_old_errors || ' unresolved sync errors older than 24 hours';
DBMS_OUTPUT.PUT_LINE(v_alert_message);
END IF;
-- Log health check
INSERT INTO sync_health_log (
check_date, pending_count, error_count, old_error_count
) VALUES (
SYSTIMESTAMP, v_pending_count, v_error_count, v_old_errors
);
COMMIT;
END;
/
-- Create health monitoring table
CREATE TABLE sync_health_log (
check_date TIMESTAMP,
pending_count NUMBER,
error_count NUMBER,
old_error_count NUMBER
);
-- Schedule health monitoring job
BEGIN
DBMS_SCHEDULER.CREATE_JOB(
job_name => 'SYNC_HEALTH_MONITOR',
job_type => 'PLSQL_BLOCK',
job_action => 'BEGIN monitor_sync_health; END;',
start_date => SYSTIMESTAMP,
repeat_interval => 'FREQ=MINUTELY; INTERVAL=15',
enabled => TRUE,
comments => 'Monitor synchronization health every 15 minutes'
);
END;
/
-- Sync status queries
-- Overall sync performance
SELECT sync_status, COUNT(*) as count,
MIN(created_date) as oldest_request,
MAX(created_date) as newest_request
FROM sync_log
WHERE created_date >= TRUNC(SYSDATE)
GROUP BY sync_status;
-- Error analysis
SELECT error_message, COUNT(*) as frequency
FROM sync_log
WHERE sync_status = 'ERROR'
AND created_date >= TRUNC(SYSDATE) - 7
GROUP BY error_message
ORDER BY COUNT(*) DESC;
-- Sync latency analysis
SELECT table_name,
AVG(EXTRACT(SECOND FROM (processed_date - created_date))) as avg_latency_seconds,
MAX(EXTRACT(SECOND FROM (processed_date - created_date))) as max_latency_seconds,
COUNT(*) as total_operations
FROM sync_log
WHERE sync_status = 'SUCCESS'
AND created_date >= TRUNC(SYSDATE) - 1
GROUP BY table_name;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| DYNAMIC_SQL | EXECUTED | ROWS_AFFECTED |
|---|---|---|
| UPDATE employees SET... | โ | 2 rows |
| DROP TABLE temp_tbl | โ | Table removed |
EXECUTE IMMEDIATE runs SQL assembled as a string at runtime
Use Oracle Streams, GoldenGate, or custom triggers and database links to implement real-time data replication and synchronization.
SCD Type 2 maintains historical data by creating new records for changes, using effective dates and flags to track current vs historical records.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Create SCD Type 2 dimension table
CREATE TABLE dim_customer (
customer_key NUMBER GENERATED ALWAYS AS IDENTITY,
customer_id NUMBER NOT NULL,
customer_name VARCHAR2(100),
email VARCHAR2(100),
phone VARCHAR2(20),
address VARCHAR2(200),
effective_start_date DATE DEFAULT SYSDATE,
effective_end_date DATE DEFAULT DATE '9999-12-31',
is_current_flag CHAR(1) DEFAULT 'Y',
created_date DATE DEFAULT SYSDATE,
PRIMARY KEY (customer_key)
);
-- Create indexes for performance
CREATE INDEX idx_dim_customer_id ON dim_customer(customer_id, is_current_flag);
CREATE INDEX idx_dim_customer_dates ON dim_customer(effective_start_date, effective_end_date);
-- SCD Type 2 Merge procedure
CREATE OR REPLACE PROCEDURE update_customer_scd(
p_customer_id NUMBER,
p_customer_name VARCHAR2,
p_email VARCHAR2,
p_phone VARCHAR2,
p_address VARCHAR2
) IS
v_current_record dim_customer%ROWTYPE;
v_record_changed BOOLEAN := FALSE;
BEGIN
-- Get current record
SELECT * INTO v_current_record
FROM dim_customer
WHERE customer_id = p_customer_id
AND is_current_flag = 'Y';
-- Check if any tracked attributes changed
IF NVL(v_current_record.customer_name, 'NULL') != NVL(p_customer_name, 'NULL') OR
NVL(v_current_record.email, 'NULL') != NVL(p_email, 'NULL') OR
NVL(v_current_record.phone, 'NULL') != NVL(p_phone, 'NULL') OR
NVL(v_current_record.address, 'NULL') != NVL(p_address, 'NULL') THEN
v_record_changed := TRUE;
END IF;
IF v_record_changed THEN
-- Expire current record
UPDATE dim_customer
SET effective_end_date = SYSDATE - INTERVAL '1' SECOND,
is_current_flag = 'N'
WHERE customer_key = v_current_record.customer_key;
-- Insert new current record
INSERT INTO dim_customer (
customer_id, customer_name, email, phone, address,
effective_start_date, effective_end_date, is_current_flag
) VALUES (
p_customer_id, p_customer_name, p_email, p_phone, p_address,
SYSDATE, DATE '9999-12-31', 'Y'
);
END IF;
COMMIT;
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- Insert new customer
INSERT INTO dim_customer (
customer_id, customer_name, email, phone, address
) VALUES (
p_customer_id, p_customer_name, p_email, p_phone, p_address
);
COMMIT;
END;
/
-- Query current records only
SELECT customer_id, customer_name, email, phone
FROM dim_customer
WHERE is_current_flag = 'Y';
-- Query historical changes for a customer
SELECT customer_id, customer_name, email,
effective_start_date, effective_end_date, is_current_flag
FROM dim_customer
WHERE customer_id = 12345
ORDER BY effective_start_date;
-- Point-in-time query (what did customer look like on specific date)
SELECT customer_id, customer_name, email, phone, address
FROM dim_customer
WHERE customer_id = 12345
AND DATE '2023-06-15' BETWEEN effective_start_date AND effective_end_date;
-- Bulk SCD processing using MERGE
MERGE INTO dim_customer d
USING (
SELECT customer_id, customer_name, email, phone, address
FROM staging_customer_updates
) s ON (d.customer_id = s.customer_id AND d.is_current_flag = 'Y')
WHEN MATCHED THEN
UPDATE SET
effective_end_date = CASE
WHEN (NVL(d.customer_name,'X') != NVL(s.customer_name,'X') OR
NVL(d.email,'X') != NVL(s.email,'X') OR
NVL(d.phone,'X') != NVL(s.phone,'X') OR
NVL(d.address,'X') != NVL(s.address,'X'))
THEN SYSDATE - INTERVAL '1' SECOND
ELSE d.effective_end_date
END,
is_current_flag = CASE
WHEN (NVL(d.customer_name,'X') != NVL(s.customer_name,'X') OR
NVL(d.email,'X') != NVL(s.email,'X') OR
NVL(d.phone,'X') != NVL(s.phone,'X') OR
NVL(d.address,'X') != NVL(s.address,'X'))
THEN 'N'
ELSE 'Y'
END;
-- Insert new versions for changed records
INSERT INTO dim_customer (customer_id, customer_name, email, phone, address)
SELECT s.customer_id, s.customer_name, s.email, s.phone, s.address
FROM staging_customer_updates s
JOIN dim_customer d ON s.customer_id = d.customer_id
WHERE d.is_current_flag = 'N'
AND d.effective_end_date = SYSDATE - INTERVAL '1' SECOND;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_ID | EMP_NAME | ACTION | RESULT |
|---|---|---|---|
| 101 | Alice Johnson | UPDATED (matched) | Salary โ 55,000 |
| 102 | Bob Smith | UPDATED (matched) | Salary โ 68,200 |
| 999 | New Hire | INSERTED (not matched) | New row added |
MERGE: 2 rows updated + 1 inserted in one atomic statement
SCD Type 2 maintains historical data by creating new records for changes, using effective dates and flags to track current vs historical records.
Design and query a star schema with fact tables containing measures and foreign keys to dimension tables containing descriptive attributes.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Create dimension tables for star schema
-- Time Dimension
CREATE TABLE dim_time (
time_key NUMBER PRIMARY KEY,
date_value DATE NOT NULL,
year NUMBER,
quarter NUMBER,
month NUMBER,
month_name VARCHAR2(20),
day_of_month NUMBER,
day_of_week NUMBER,
day_name VARCHAR2(20),
week_of_year NUMBER,
is_weekend CHAR(1),
is_holiday CHAR(1),
fiscal_year NUMBER,
fiscal_quarter NUMBER
);
-- Product Dimension
CREATE TABLE dim_product (
product_key NUMBER PRIMARY KEY,
product_id VARCHAR2(20) UNIQUE,
product_name VARCHAR2(100),
brand VARCHAR2(50),
category VARCHAR2(50),
subcategory VARCHAR2(50),
unit_cost NUMBER(10,2),
unit_price NUMBER(10,2),
product_status VARCHAR2(20),
launch_date DATE
);
-- Customer Dimension
CREATE TABLE dim_customer (
customer_key NUMBER PRIMARY KEY,
customer_id VARCHAR2(20) UNIQUE,
customer_name VARCHAR2(100),
customer_type VARCHAR2(20),
segment VARCHAR2(30),
region VARCHAR2(50),
country VARCHAR2(50),
city VARCHAR2(50),
zip_code VARCHAR2(10),
registration_date DATE,
credit_rating VARCHAR2(10)
);
-- Store Dimension
CREATE TABLE dim_store (
store_key NUMBER PRIMARY KEY,
store_id VARCHAR2(20) UNIQUE,
store_name VARCHAR2(100),
store_type VARCHAR2(30),
store_size VARCHAR2(20),
region VARCHAR2(50),
district VARCHAR2(50),
city VARCHAR2(50),
state VARCHAR2(50),
country VARCHAR2(50),
open_date DATE,
manager_name VARCHAR2(100)
);
-- Fact Table - Sales Facts
CREATE TABLE fact_sales (
sales_key NUMBER PRIMARY KEY,
time_key NUMBER REFERENCES dim_time(time_key),
product_key NUMBER REFERENCES dim_product(product_key),
customer_key NUMBER REFERENCES dim_customer(customer_key),
store_key NUMBER REFERENCES dim_store(store_key),
-- Measures
quantity_sold NUMBER,
unit_price NUMBER(10,2),
unit_cost NUMBER(10,2),
sales_amount NUMBER(12,2),
discount_amount NUMBER(10,2),
profit_amount NUMBER(12,2),
tax_amount NUMBER(10,2)
);
-- Create indexes for performance
CREATE INDEX idx_fact_sales_time ON fact_sales(time_key);
CREATE INDEX idx_fact_sales_product ON fact_sales(product_key);
CREATE INDEX idx_fact_sales_customer ON fact_sales(customer_key);
CREATE INDEX idx_fact_sales_store ON fact_sales(store_key);
-- Composite index for common query patterns
CREATE INDEX idx_fact_sales_time_store ON fact_sales(time_key, store_key);
CREATE INDEX idx_fact_sales_time_product ON fact_sales(time_key, product_key);
-- Populate dimension tables with sample data
-- Time dimension population
INSERT INTO dim_time (time_key, date_value, year, quarter, month, month_name,
day_of_month, day_of_week, day_name, week_of_year,
is_weekend, is_holiday, fiscal_year, fiscal_quarter)
SELECT ROWNUM as time_key,
date_value,
EXTRACT(YEAR FROM date_value) as year,
CEIL(EXTRACT(MONTH FROM date_value) / 3) as quarter,
EXTRACT(MONTH FROM date_value) as month,
TO_CHAR(date_value, 'Month') as month_name,
EXTRACT(DAY FROM date_value) as day_of_month,
TO_NUMBER(TO_CHAR(date_value, 'D')) as day_of_week,
TO_CHAR(date_value, 'Day') as day_name,
TO_NUMBER(TO_CHAR(date_value, 'WW')) as week_of_year,
CASE WHEN TO_CHAR(date_value, 'D') IN ('1', '7') THEN 'Y' ELSE 'N' END as is_weekend,
'N' as is_holiday, -- Simplified
CASE WHEN EXTRACT(MONTH FROM date_value) >= 4
THEN EXTRACT(YEAR FROM date_value)
ELSE EXTRACT(YEAR FROM date_value) - 1 END as fiscal_year,
CASE WHEN EXTRACT(MONTH FROM date_value) IN (4,5,6) THEN 1
WHEN EXTRACT(MONTH FROM date_value) IN (7,8,9) THEN 2
WHEN EXTRACT(MONTH FROM date_value) IN (10,11,12) THEN 3
ELSE 4 END as fiscal_quarter
FROM (
SELECT DATE '2022-01-01' + LEVEL - 1 as date_value
FROM dual
CONNECT BY LEVEL <= 1095 -- 3 years of data
);
-- Star Schema Analytical Queries
-- 1. Sales by Year and Quarter
SELECT dt.year, dt.quarter,
SUM(fs.sales_amount) as total_sales,
SUM(fs.profit_amount) as total_profit,
COUNT(*) as transaction_count,
ROUND(AVG(fs.sales_amount), 2) as avg_transaction_value
FROM fact_sales fs
JOIN dim_time dt ON fs.time_key = dt.time_key
GROUP BY dt.year, dt.quarter
ORDER BY dt.year, dt.quarter;
-- 2. Top Products by Sales in Each Category
WITH product_sales AS (
SELECT dp.category, dp.product_name, dp.brand,
SUM(fs.sales_amount) as total_sales,
SUM(fs.quantity_sold) as total_quantity,
ROW_NUMBER() OVER (PARTITION BY dp.category ORDER BY SUM(fs.sales_amount) DESC) as rn
FROM fact_sales fs
JOIN dim_product dp ON fs.product_key = dp.product_key
JOIN dim_time dt ON fs.time_key = dt.time_key
WHERE dt.year = 2023
GROUP BY dp.category, dp.product_name, dp.brand
)
SELECT category, product_name, brand, total_sales, total_quantity
FROM product_sales
WHERE rn <= 3
ORDER BY category, rn;
-- 3. Customer Segmentation Analysis
SELECT dc.segment, dc.region,
COUNT(DISTINCT dc.customer_key) as customer_count,
SUM(fs.sales_amount) as total_sales,
ROUND(AVG(fs.sales_amount), 2) as avg_order_value,
SUM(fs.quantity_sold) as total_units,
ROUND(SUM(fs.sales_amount) / COUNT(DISTINCT dc.customer_key), 2) as sales_per_customer
FROM fact_sales fs
JOIN dim_customer dc ON fs.customer_key = dc.customer_key
JOIN dim_time dt ON fs.time_key = dt.time_key
WHERE dt.year = 2023
GROUP BY dc.segment, dc.region
ORDER BY total_sales DESC;
-- 4. Store Performance Analysis
SELECT ds.region, ds.store_name, ds.store_type,
SUM(fs.sales_amount) as total_sales,
SUM(fs.profit_amount) as total_profit,
ROUND(SUM(fs.profit_amount) / SUM(fs.sales_amount) * 100, 2) as profit_margin_pct,
COUNT(DISTINCT fs.customer_key) as unique_customers,
COUNT(*) as transaction_count
FROM fact_sales fs
JOIN dim_store ds ON fs.store_key = ds.store_key
JOIN dim_time dt ON fs.time_key = dt.time_key
WHERE dt.year = 2023
GROUP BY ds.region, ds.store_name, ds.store_type
ORDER BY total_sales DESC;
-- 5. Time Series Analysis - Monthly Trends
SELECT dt.year, dt.month, dt.month_name,
SUM(fs.sales_amount) as monthly_sales,
LAG(SUM(fs.sales_amount)) OVER (ORDER BY dt.year, dt.month) as prev_month_sales,
ROUND(
(SUM(fs.sales_amount) - LAG(SUM(fs.sales_amount)) OVER (ORDER BY dt.year, dt.month)) /
LAG(SUM(fs.sales_amount)) OVER (ORDER BY dt.year, dt.month) * 100, 2
) as mom_growth_pct,
SUM(SUM(fs.sales_amount)) OVER (PARTITION BY dt.year ORDER BY dt.month) as ytd_sales
FROM fact_sales fs
JOIN dim_time dt ON fs.time_key = dt.time_key
GROUP BY dt.year, dt.month, dt.month_name
ORDER BY dt.year, dt.month;
-- 6. Complex Drill-Down Query
SELECT dt.year, dt.quarter, dc.region, dp.category,
SUM(fs.sales_amount) as sales,
SUM(fs.profit_amount) as profit,
COUNT(*) as transactions,
-- Ranking within each time period
RANK() OVER (PARTITION BY dt.year, dt.quarter ORDER BY SUM(fs.sales_amount) DESC) as sales_rank,
-- Percentage of total sales
ROUND(SUM(fs.sales_amount) / SUM(SUM(fs.sales_amount)) OVER (PARTITION BY dt.year, dt.quarter) * 100, 2) as pct_of_period_sales
FROM fact_sales fs
JOIN dim_time dt ON fs.time_key = dt.time_key
JOIN dim_customer dc ON fs.customer_key = dc.customer_key
JOIN dim_product dp ON fs.product_key = dp.product_key
WHERE dt.year BETWEEN 2022 AND 2023
GROUP BY dt.year, dt.quarter, dc.region, dp.category
HAVING SUM(fs.sales_amount) > 10000
ORDER BY dt.year, dt.quarter, sales DESC;
-- 7. Cube and Rollup Operations
SELECT dt.year, dp.category, dc.segment,
SUM(fs.sales_amount) as total_sales,
COUNT(*) as transaction_count,
GROUPING_ID(dt.year, dp.category, dc.segment) as grouping_level
FROM fact_sales fs
JOIN dim_time dt ON fs.time_key = dt.time_key
JOIN dim_product dp ON fs.product_key = dp.product_key
JOIN dim_customer dc ON fs.customer_key = dc.customer_key
WHERE dt.year = 2023
GROUP BY CUBE(dt.year, dp.category, dc.segment)
ORDER BY grouping_level, dt.year, dp.category, dc.segment;
-- 8. Performance Optimization with Materialized Views
CREATE MATERIALIZED VIEW mv_monthly_sales
BUILD IMMEDIATE
REFRESH FAST ON COMMIT
AS
SELECT dt.year, dt.month, dp.category, dc.region,
SUM(fs.sales_amount) as total_sales,
SUM(fs.profit_amount) as total_profit,
COUNT(*) as transaction_count,
COUNT(DISTINCT fs.customer_key) as unique_customers
FROM fact_sales fs
JOIN dim_time dt ON fs.time_key = dt.time_key
JOIN dim_product dp ON fs.product_key = dp.product_key
JOIN dim_customer dc ON fs.customer_key = dc.customer_key
GROUP BY dt.year, dt.month, dp.category, dc.region;
-- Create index on materialized view
CREATE INDEX idx_mv_monthly_sales ON mv_monthly_sales(year, month, category, region);| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
3 rows โ only rows with matching dept_id in both tables (Dave Brown excluded โ no dept)
Design and query a star schema with fact tables containing measures and foreign keys to dimension tables containing descriptive attributes.
dynamic SQL is a valuable tool in SQL programming that facilitates the creation and execution of SQL statements based on runtime conditions.
It offers flexibility, code efficiency, and the potential for improved query performance, making it a beneficial feature for SQL developers and applications.The use of dynamic SQL provides flexibility in query execution, as it enables the generation of SQL statements on the fly.
This is particularly useful when dealing with dynamic criteria or user inputs.
For example, in a web application, dynamic SQL is employed to construct queries based on user-selected filters, such as date ranges or search keywords.One of the key benefits of dynamic SQL is its ability to reduce code redundancy.
Instead of writing multiple similar SQL statements for different scenarios, dynamic SQL allows developers to write a single, adaptable query that handles various situations.
This not only simplifies code maintenance but also enhances code reusability.Dynamic SQL improves query performance by optimizing execution plans based on the specific conditions provided.
It also aids in preventing SQL injection attacks by properly parameterizing user inputs.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
dynamic SQL is a valuable tool in SQL programming that facilitates the creation and execution of SQL statements based on runtime conditions.
Implementing full-text search in SQL databases involves utilizing SQL functions like "CONTAINS" or "FREETEXT," creating full-text indexes, and considering the integration of third-party search engines for more advanced and efficient text searching capabilities.
These techniques enable SQL developers to enhance the search functionality of their database applications, making it easier to retrieve relevant information from large volumes of text data.One fundamental approach is to use the "CONTAINS" or "FREETEXT" functions, depending on the database system being used, such as SQL Server or PostgreSQL.
These functions enable users to perform text searches on designated columns, returning results that match the specified keywords or phrases.
Database systems optimize these search operations for improved performance, by creating full-text indexes on the columns of interest.SQL offers the "LIKE" operator, which allows for pattern matching within text data. "LIKE" is used for basic text searching by specifying wildcard characters to match partial text strings, which allows for pattern matching within text data.SQL databases support the integration of third-party search engines like Elasticsearch or Solr, which are specifically designed for advanced full-text search capabilities.
These engines offer powerful features such as relevance ranking, stemming, and faceted search, enhancing the accuracy and flexibility of text searches within SQL databases.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
Implementing full-text search in SQL databases involves utilizing SQL functions like "CONTAINS" or "FREETEXT," creating full-text indexes, and considering the integration of third-party search engines for more advanced and efficient text searching capabilities.
Temporal tables in SQL are special types of tables that capture the state of data at any given point in time.
They serve as a means to track the complete history of data changes, allowing users to query past states of the database effectively.
Temporal tables consist of two main types: system-versioned and user-defined.
System-versioned temporal tables automatically record the time period during which a data row was valid in the database.
This feature is invaluable for auditing purposes, as it provides a historical record of data changes, who made them, and when they were made.User-defined temporal tables, on the other hand, require manual input to track the temporal aspects of data.
They are typically used in scenarios where specific business rules dictate the temporal nature of the data.
The use of temporal tables enhances data integrity and provides a robust framework for complex data analysis, historical data recovery, and auditing.
Implementing temporal tables in SQL databases ensures compliance with data retention policies and regulations, making them an essential tool in modern database management.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
Temporal tables in SQL are special types of tables that capture the state of data at any given point in time.
Partitioned tables are a method to divide a large table into smaller, more manageable pieces, each known as a partition.
This division is based on specific criteria, such as date ranges or geographic locations, which allows for improved query performance and data management.
Implementing partitioned tables involves defining the partition scheme and function.
The partition function defines how the data is distributed across the partitions, usually based on column values.
The partition scheme then maps these partitions to different physical locations in the database.SQL Server automatically accesses only the relevant partitions, when querying partitioned tables, leading to faster query execution times.
This is particularly useful in scenarios with large datasets where queries need to be optimized for performance.
Ensure that the partition key is chosen wisely to balance the data distribution across partitions.
Partition maintenance, such as adding or merging partitions, requires ALTER TABLE commands, allowing for dynamic and efficient management of large datasets.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
Partitioned tables are a method to divide a large table into smaller, more manageable pieces, each known as a partition.
Managing temporal data in SQL involves handling information with time-related aspects, such as timestamps and historical records.
SQL offers various features to support temporal data management, including temporal tables and date-time functions.
Temporal tables allow users to track changes over time within the database, providing a historical perspective of data changes.
Date-time functions enable manipulation and querying of dates and times, facilitating time-based data analysis.One challenge in managing temporal data is ensuring data consistency and integrity, especially when dealing with historical data.
Complex queries involving temporal data require careful design to maintain accuracy and performance.
Another challenge is the efficient storage and retrieval of large volumes of temporal data.
Optimizing database design and query execution becomes crucial to handle the increased storage requirements and to ensure quick access to relevant temporal information.
Use efficient indexing strategies and partitioning to manage these challenges effectively.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
Managing temporal data in SQL involves handling information with time-related aspects, such as timestamps and historical records.
To implement and manage table inheritance in SQL, you create a parent table and then define child tables that inherit from this parent table.
The parent table contains common columns that are shared across the child tables.
You use the INHERITS keyword followed by the name of the parent table, when you create a child table.
This approach allows the child tables to automatically include all columns from the parent table, in addition to any specific columns they define.Managing table inheritance involves performing operations on both parent and child tables as needed.
Data inserted into a child table includes both the specific columns of the child table and the inherited columns from the parent table.
The results include records from the parent itself and all its child tables, when querying a parent table, unless you specify otherwise.
Update and delete operations on parent and child tables require careful consideration to maintain data integrity, especially if the operation affects inherited columns.
Use constraints and triggers to enforce data consistency and integrity across the hierarchy of tables.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
To implement and manage table inheritance in SQL, you create a parent table and then define child tables that inherit from this parent table.
The challenges in managing multilingual data in SQL involve handling various character sets, ensuring proper collation for sorting and comparing data, and dealing with issues related to case sensitivity and accent marks.
SQL databases use specific character sets like UTF-8 to support a wide range of languages.
SQL allows the setting of collation at the database, table, or column level for accurate sorting and comparison of multilingual data.
This ensures that data is sorted and compared according to the linguistic rules of the relevant language.To handle case sensitivity and accent differences in multilingual data, SQL provides collation settings that can be fine-tuned for case-insensitive and accent-insensitive comparisons.
Indexes should be created on columns containing multilingual data to improve query performance.
Proper normalization of the database schema is crucial to avoid redundancy and ensure efficient storage of multilingual data.
Utilize appropriate data types like NVARCHAR for storing multilingual content to handle the extended character sets effectively.
Regular database maintenance and updates ensure the continued compatibility and performance of multilingual data handling in SQL environments.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
The challenges in managing multilingual data in SQL involve handling various character sets, ensuring proper collation for sorting and comparing data, and dealing with issues related to case sensitivity and accent marks.
The concept of virtual columns in SQL refers to columns that are not physically stored in the table but are computed from other columns.
Virtual columns are created using expressions or functions based on other columns in the table.
These expressions are evaluated whenever the column is queried, ensuring that the data in a virtual column is always current and consistent with the underlying base columns.Virtual columns serve various purposes in SQL databases.
They enhance query performance by eliminating the need for complex calculations in queries, as the computation is handled within the table structure itself.
This feature is particularly useful for generating formatted data, calculating summaries, or transforming data for easier access and analysis.
Virtual columns also ensure data integrity by maintaining consistency across different queries and applications, as the expression defining the virtual column remains constant regardless of how the data is accessed or used.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
The concept of virtual columns in SQL refers to columns that are not physically stored in the table but are computed from other columns.
To manage version control for database schema changes, specialized tools like .
These tools, like Liquibase or Flyway are used.
These tools track and manage database schema changes using a version control system.
This approach ensures that schema changes are consistent across different environments and team members.
The version control system acts like a repository for SQL scripts, which are written to modify the database schema.
Developers write SQL migration scripts for each change, and the version control tool applies these scripts in the correct order.The process involves maintaining an up-to-date schema version in the database, in a dedicated table.
This table logs the history of applied migrations, allowing for rollback of changes if necessary.
The version control tool compares the database's current schema version with the available migration scripts, when deploying new changes.
It then executes the pending scripts to update the schema.
This method guarantees that schema changes are applied in a controlled and predictable manner, reducing the risk of conflicts or inconsistencies.
Integrating database version control with an overall code version control system aligns database changes with corresponding application code changes, ensuring that they are in sync.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
SELECT * FROM employees WHERE ROWNUM <= 5;| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
Data state before query executes.
| EMP_ID | EMP_NAME | SALARY |
|---|---|---|
| 101 | Alice Johnson | 50,000 |
| 102 | Bob Smith | 62,000 |
| 103 | Carol White | 55,000 |
Top 3 rows โ ROWNUM/FETCH FIRST limits result set
To manage version control for database schema changes, specialized tools like .
Use Oracle Temporal Validity and Flashback Data Archive features to track historical changes and implement temporal queries for time-based data analysis.
| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
-- Method 1: Using Temporal Validity (Oracle 12c+)
CREATE TABLE employee_temporal (
employee_id NUMBER,
first_name VARCHAR2(50),
last_name VARCHAR2(50),
salary NUMBER,
department_id NUMBER,
-- Temporal validity columns
valid_from DATE,
valid_to DATE,
PERIOD FOR valid_time (valid_from, valid_to)
);
-- Enable temporal validity
ALTER TABLE employee_temporal ADD (
CONSTRAINT emp_temporal_pk PRIMARY KEY (employee_id, valid_time WITHOUT OVERLAPS)
);
-- Insert temporal data
INSERT INTO employee_temporal (employee_id, first_name, last_name, salary, department_id, valid_from, valid_to)
VALUES (101, 'John', 'Doe', 50000, 10, DATE '2023-01-01', DATE '2023-06-30');
INSERT INTO employee_temporal (employee_id, first_name, last_name, salary, department_id, valid_from, valid_to)
VALUES (101, 'John', 'Doe', 55000, 10, DATE '2023-07-01', DATE '2023-12-31');
INSERT INTO employee_temporal (employee_id, first_name, last_name, salary, department_id, valid_from, valid_to)
VALUES (101, 'John', 'Doe', 60000, 20, DATE '2024-01-01', DATE '9999-12-31');
-- Query temporal data for specific time point
SELECT employee_id, first_name, last_name, salary, department_id
FROM employee_temporal
WHERE employee_id = 101
AND DATE '2023-08-15' BETWEEN valid_from AND valid_to;
-- Query all changes for an employee
SELECT employee_id, salary, department_id, valid_from, valid_to,
valid_to - valid_from + 1 as period_days
FROM employee_temporal
WHERE employee_id = 101
ORDER BY valid_from;
-- Method 2: Manual Temporal Implementation with Triggers
CREATE TABLE employee_history (
history_id NUMBER GENERATED ALWAYS AS IDENTITY,
employee_id NUMBER,
first_name VARCHAR2(50),
last_name VARCHAR2(50),
salary NUMBER,
department_id NUMBER,
effective_date DATE DEFAULT SYSDATE,
end_date DATE DEFAULT DATE '9999-12-31',
operation_type VARCHAR2(10),
changed_by VARCHAR2(100) DEFAULT USER,
changed_timestamp TIMESTAMP DEFAULT SYSTIMESTAMP
);
-- Current employee table
CREATE TABLE employees_current (
employee_id NUMBER PRIMARY KEY,
first_name VARCHAR2(50),
last_name VARCHAR2(50),
salary NUMBER,
department_id NUMBER,
last_updated TIMESTAMP DEFAULT SYSTIMESTAMP
);
-- Temporal trigger for history tracking
CREATE OR REPLACE TRIGGER trg_employee_temporal
AFTER INSERT OR UPDATE OR DELETE ON employees_current
FOR EACH ROW
BEGIN
IF INSERTING THEN
INSERT INTO employee_history (
employee_id, first_name, last_name, salary, department_id, operation_type
) VALUES (
:NEW.employee_id, :NEW.first_name, :NEW.last_name,
:NEW.salary, :NEW.department_id, 'INSERT'
);
ELSIF UPDATING THEN
-- End previous record
UPDATE employee_history
SET end_date = SYSDATE - INTERVAL '1' SECOND
WHERE employee_id = :OLD.employee_id
AND end_date = DATE '9999-12-31';
-- Insert new record
INSERT INTO employee_history (
employee_id, first_name, last_name, salary, department_id, operation_type
) VALUES (
:NEW.employee_id, :NEW.first_name, :NEW.last_name,
:NEW.salary, :NEW.department_id, 'UPDATE'
);
ELSIF DELETING THEN
-- End current record
UPDATE employee_history
SET end_date = SYSDATE,
operation_type = 'DELETE'
WHERE employee_id = :OLD.employee_id
AND end_date = DATE '9999-12-31';
END IF;
END;
/
-- Method 3: Using Flashback Data Archive (Total Recall)
-- Enable Flashback Data Archive (requires DBA privileges)
/*
CREATE FLASHBACK ARCHIVE fla_employee
TABLESPACE flashback_ts
RETENTION 5 YEAR;
ALTER TABLE employees_current FLASHBACK ARCHIVE fla_employee;
*/
-- Query historical data using Flashback
SELECT employee_id, first_name, last_name, salary, department_id
FROM employees_current
AS OF TIMESTAMP TIMESTAMP '2023-06-15 10:00:00';
-- Query all versions between two timestamps
SELECT employee_id, salary, department_id,
versions_starttime, versions_endtime, versions_operation
FROM employees_current
VERSIONS BETWEEN TIMESTAMP
TIMESTAMP '2023-01-01 00:00:00' AND TIMESTAMP '2023-12-31 23:59:59'
WHERE employee_id = 101
ORDER BY versions_starttime;
-- Method 4: Temporal Analytics Functions
-- Create view for easy temporal queries
CREATE OR REPLACE VIEW v_employee_temporal_analysis AS
SELECT h.*,
LAG(salary) OVER (PARTITION BY employee_id ORDER BY effective_date) as prev_salary,
salary - LAG(salary) OVER (PARTITION BY employee_id ORDER BY effective_date) as salary_change,
LAG(department_id) OVER (PARTITION BY employee_id ORDER BY effective_date) as prev_department,
end_date - effective_date + 1 as days_in_position,
ROW_NUMBER() OVER (PARTITION BY employee_id ORDER BY effective_date) as position_sequence
FROM employee_history h;
-- Salary progression analysis
SELECT employee_id, first_name, last_name,
position_sequence, salary, prev_salary, salary_change,
ROUND((salary_change / prev_salary) * 100, 2) as salary_increase_pct,
days_in_position
FROM v_employee_temporal_analysis
WHERE employee_id = 101
AND prev_salary IS NOT NULL
ORDER BY effective_date;
-- Method 5: Temporal Queries for Business Analysis
-- Employees by salary range over time
WITH temporal_snapshot AS (
SELECT DATE '2023-01-01' + (LEVEL - 1) * 30 as snapshot_date
FROM dual
CONNECT BY LEVEL <= 12 -- Monthly snapshots
),
salary_ranges AS (
SELECT ts.snapshot_date,
COUNT(CASE WHEN h.salary < 40000 THEN 1 END) as low_salary,
COUNT(CASE WHEN h.salary BETWEEN 40000 AND 60000 THEN 1 END) as mid_salary,
COUNT(CASE WHEN h.salary > 60000 THEN 1 END) as high_salary,
COUNT(*) as total_employees
FROM temporal_snapshot ts
LEFT JOIN employee_history h ON ts.snapshot_date BETWEEN h.effective_date AND h.end_date
GROUP BY ts.snapshot_date
)
SELECT snapshot_date,
low_salary, mid_salary, high_salary, total_employees,
ROUND(low_salary / total_employees * 100, 1) as low_salary_pct,
ROUND(mid_salary / total_employees * 100, 1) as mid_salary_pct,
ROUND(high_salary / total_employees * 100, 1) as high_salary_pct
FROM salary_ranges
ORDER BY snapshot_date;
-- Department movement analysis
SELECT h1.employee_id, h1.first_name, h1.last_name,
h1.department_id as from_dept, h2.department_id as to_dept,
h1.end_date as move_date,
h2.effective_date - h1.end_date as gap_days
FROM employee_history h1
JOIN employee_history h2 ON h1.employee_id = h2.employee_id
AND h2.effective_date = h1.end_date + 1
WHERE h1.department_id != h2.department_id
ORDER BY h1.employee_id, h1.end_date;
-- Method 6: Temporal Data Validation
CREATE OR REPLACE FUNCTION validate_temporal_data(
p_employee_id NUMBER
) RETURN VARCHAR2 IS
v_overlaps NUMBER;
v_gaps NUMBER;
BEGIN
-- Check for overlapping periods
SELECT COUNT(*)
INTO v_overlaps
FROM employee_history h1
JOIN employee_history h2 ON h1.employee_id = h2.employee_id
AND h1.history_id != h2.history_id
WHERE h1.employee_id = p_employee_id
AND h1.effective_date <= h2.end_date
AND h1.end_date >= h2.effective_date;
-- Check for gaps in timeline
SELECT COUNT(*)
INTO v_gaps
FROM employee_history h1
JOIN employee_history h2 ON h1.employee_id = h2.employee_id
AND h2.effective_date = (
SELECT MIN(effective_date)
FROM employee_history
WHERE employee_id = h1.employee_id
AND effective_date > h1.end_date
)
WHERE h1.employee_id = p_employee_id
AND h1.end_date + 1 != h2.effective_date;
IF v_overlaps > 0 THEN
RETURN 'ERROR: Overlapping periods detected';
ELSIF v_gaps > 0 THEN
RETURN 'WARNING: Gaps in timeline detected';
ELSE
RETURN 'OK: Timeline is valid';
END IF;
END;
/
-- Test temporal validation
SELECT validate_temporal_data(101) as validation_result FROM dual;
-- Point-in-time reconstruction
CREATE OR REPLACE FUNCTION get_employee_at_date(p_employee_id NUMBER,
p_date DATE
) RETURN SYS_REFCURSOR IS
v_cursor SYS_REFCURSOR;
BEGIN
OPEN v_cursor FOR
SELECT employee_id, first_name, last_name, salary, department_id,
effective_date, end_date
FROM employee_history
WHERE employee_id = p_employee_id
AND p_date BETWEEN effective_date AND end_date;
RETURN v_cursor;
END;
/
-- Usage example
DECLARE
v_cursor SYS_REFCURSOR;
v_emp_id NUMBER;
v_name VARCHAR2(100);
v_salary NUMBER;
v_dept NUMBER;
v_start DATE;
v_end DATE;
BEGIN
v_cursor := get_employee_at_date(101, DATE '2023-08-15');
FETCH v_cursor INTO v_emp_id, v_name, v_name, v_salary, v_dept, v_start, v_end;
IF v_cursor%FOUND THEN
DBMS_OUTPUT.PUT_LINE('Employee ' || v_emp_id || ' on 2023-08-15:');
DBMS_OUTPUT.PUT_LINE('Salary: ' || v_salary);
DBMS_OUTPUT.PUT_LINE('Department: ' || v_dept);
ELSE
DBMS_OUTPUT.PUT_LINE('No data found for that date');
END IF;
CLOSE v_cursor;
END;
/| emp_id | emp_name | dept_id | salary |
|---|---|---|---|
| 101 | Alice Johnson | 10 | 50,000 |
| 102 | Bob Smith | 20 | 62,000 |
| 103 | Carol White | 10 | 55,000 |
| 104 | Dave Brown | NULL | 70,000 |
| dept_id | dept_name |
|---|---|
| 10 | Engineering |
| 20 | Marketing |
| 30 | Finance |
Data state before query executes.
| EMP_NAME | DEPT_NAME |
|---|---|
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
| Dave Brown | NULL |
4 rows โ all employees kept, NULL where no matching department
Use Oracle Temporal Validity and Flashback Data Archive features to track historical changes and implement temporal queries for time-based data analysis.