PGCP-BDA Β· All 22 sessions fully explained Β· Real-world analogies Β· Code with comments Β· Easy language for all levels
Click any session heading to expand. Use the quick-jump links below to navigate.
Before databases existed, every application stored its data in its own flat files (think .txt or .csv files stored on disk). Each program read and wrote its own files, with no coordination between programs.
A Database Management System (DBMS) is software that manages a structured collection of data. It acts as an interface between the user/application and the physical data stored on disk. The DBMS takes care of storage, security, concurrency, recovery, and integrity β so you don't have to.
Structured storage, fast querying, multi-user access without conflicts, role-based security, automatic backups and crash recovery.
Atomicity β all or nothing. Consistency β rules always satisfied. Isolation β transactions don't interfere. Durability β committed data survives crashes.
Relational (MySQL, Oracle), Document (MongoDB), Key-Value (Redis), Columnar (Cassandra, HBase), Graph (Neo4j).
In an RDBMS (Relational Database Management System), all data lives in tables β rows (tuples) and columns (attributes). Tables relate to each other through keys. You query data using SQL. E.F. Codd invented this model in 1970 at IBM.
| Term | Relational name | Simple meaning | Example |
|---|---|---|---|
| Table | Relation | A spreadsheet-like grid | Employees table |
| Row | Tuple | One record / entity | Priya's employee record |
| Column | Attribute | One property of the entity | employee_name, salary |
| Primary Key | Candidate Key | Uniquely identifies each row | employee_id = 101 |
| Foreign Key | Referential constraint | Links one table to another | dept_id in Employees β id in Departments |
E.F. Codd published 12 rules in 1985 to define what a "truly relational" database must do. Think of them as the constitution of RDBMS. Most commercial databases satisfy most but not necessarily all 12 rules.
SELECT * FROM information_schema.tables;UPDATE employees SET dept = 'IT' WHERE dept = 'Computers'; β updates many rows at once.When you install Oracle (or any enterprise RDBMS), data is stored in a hierarchy of logical and physical structures. Understanding this helps you as a DBA (Database Administrator) tune performance and plan storage.
The highest logical unit. A database has multiple tablespaces: SYSTEM (internal Oracle tables), SYSAUX (auxiliary), USERS (user data), TEMP (temporary sorting), UNDO (rollback data). You can create custom tablespaces per application.
Each tablespace is made up of one or more .dbf files on disk. This is where bytes actually get written. If a tablespace runs out of space, you add another datafile to it. Multiple datafiles can be on different disks for performance.
A small but critical binary file that records: database name, creation timestamp, list of all datafiles and redo log files, and current checkpoint information. The DB won't start without it. Oracle recommends keeping 3 copies (multiplexing).
Record every change made to the database. If the DB crashes, Oracle replays the redo log to recover all committed transactions. Works in groups that cycle through (LGWR writes to them continuously).
Inside a datafile: Segment = space for one object (e.g., one table). Extent = a chunk of contiguous blocks. Block (8KB by default) = the smallest unit Oracle reads/writes.
Memory cache shared by all database users. Contains: Buffer Cache (caches data blocks), Shared Pool (caches SQL execution plans), Redo Log Buffer (buffers before writing to disk).
The world generates different types of data. Understanding the difference determines which storage system you should use for each type.
| Type | Definition | Real Example | Stored in | Query with |
|---|---|---|---|---|
| Structured | Predefined schema, fixed rows & columns, strict types | Bank transactions, Employee records, Product catalog | MySQL, Oracle, PostgreSQL | SQL β straightforward |
| Semi-structured | No strict schema, but has self-describing tags or markers | JSON from REST APIs, XML config files, Email with headers, CSV exports | MongoDB, Elasticsearch, XML databases | XPath, XQuery, JSONPath |
| Unstructured | No predefined format, raw bytes, no schema at all | Doctor's handwritten notes, WhatsApp photos, Videos, PDFs, Social media posts | HDFS, S3, Azure Blob, NAS | NLP, image AI, search engines |
Before any analysis or storage, data must be collected. As a data professional, you need to know how data enters your pipeline from various sources.
Google Forms, paper forms digitized. Structured data, collected on demand. Good for customer feedback, research. Tool: Google Forms, SurveyMonkey, ODK.
Automatically extract data from websites using Python (BeautifulSoup, Scrapy). Amazon price tracking, news headlines, social media trends. Semi-structured HTML parsed to JSON/CSV.
Temperature sensors, smart meters, GPS trackers, factory machines. Generate continuous time-series data. Volume: billions of readings/day. Need stream processing (Kafka, Flink).
REST APIs from services: Twitter API, Google Maps API, RBI data feeds, payment gateways. Data arrives as JSON/XML. Most reliable β structured, real-time.
Pulling data from other operational databases: ERP systems (SAP), CRM (Salesforce), accounting software. Done via SQL queries or JDBC connections.
Web server logs (Apache, Nginx), application logs, system event logs. Store every request, error, user action. Massive volume β analyzed with ELK Stack (Elasticsearch + Logstash + Kibana).
SQL (Structured Query Language) is the standard language used to interact with relational databases. It was developed in the 1970s by IBM and standardized by ANSI/ISO. All major RDBMS (MySQL, Oracle, PostgreSQL, SQL Server) support SQL with minor dialect differences.
DDL commands define the structure of the database β they create, modify, or delete database objects like tables, indexes, views. DDL commands are auto-committed (cannot be rolled back in most databases).
ββββββββββββββββββββββββββββββββββββββββββββ CREATE TABLE β Define a new table ββββββββββββββββββββββββββββββββββββββββββββ CREATE TABLE Books ( book_id INT NOT NULL, -- no null allowed name VARCHAR(100) NOT NULL, author VARCHAR(60), -- nullable price DECIMAL(8,2) DEFAULT 0.00, -- default value genre VARCHAR(30), added_on DATE DEFAULT CURDATE(), PRIMARY KEY (book_id), -- uniquely identifies row CHECK (price >= 0) -- price can't be negative ); ββββββββββββββββββββββββββββββββββββββββββββ ALTER TABLE β Modify existing table structure ββββββββββββββββββββββββββββββββββββββββββββ ALTER TABLE Books ADD COLUMN publisher VARCHAR(80); -- add column ALTER TABLE Books MODIFY COLUMN name VARCHAR(150); -- change type ALTER TABLE Books DROP COLUMN publisher; -- remove column ALTER TABLE Books RENAME TO Library_Books; -- rename table ββββββββββββββββββββββββββββββββββββββββββββ DROP vs TRUNCATE β both delete all data ββββββββββββββββββββββββββββββββββββββββββββ TRUNCATE TABLE Books; -- removes all rows, keeps table structure (fast) DROP TABLE Books; -- removes ENTIRE table + structure (gone forever)
DML commands work on the data inside the tables. They can be rolled back (unlike DDL). These are the commands you'll use most often day-to-day.
ββββββββββββββββββββββββββββββββββββββββββββ INSERT β Add new rows ββββββββββββββββββββββββββββββββββββββββββββ -- Method 1: Specify all columns in order INSERT INTO Books VALUES (1, 'Clean Code', 'Robert Martin', 499.00, 'Programming', '2024-01-15'); -- Method 2: Specify column names (recommended β safe against schema changes) INSERT INTO Books (book_id, name, author, price) VALUES (2, 'The Pragmatic Programmer', 'Hunt', 549.00); -- Method 3: Multiple rows at once (faster than individual inserts) INSERT INTO Books (book_id, name, price) VALUES (3, 'Design Patterns', 699.00), (4, 'Refactoring', 599.00), (5, 'SICP', 350.00); ββββββββββββββββββββββββββββββββββββββββββββ SELECT β Query / Read data ββββββββββββββββββββββββββββββββββββββββββββ -- Basic select SELECT * FROM Books; -- all columns, all rows SELECT name, price FROM Books; -- specific columns only SELECT DISTINCT genre FROM Books; -- unique genres only -- Filtering with WHERE SELECT * FROM Books WHERE price > 400; SELECT * FROM Books WHERE genre = 'Programming' AND price < 600; SELECT * FROM Books WHERE name LIKE '%Code%'; -- contains 'Code' SELECT * FROM Books WHERE price BETWEEN 300 AND 600; SELECT * FROM Books WHERE author IN ('Martin', 'Hunt'); SELECT * FROM Books WHERE publisher IS NULL; -- find NULL values ββββββββββββββββββββββββββββββββββββββββββββ UPDATE β Modify existing rows ββββββββββββββββββββββββββββββββββββββββββββ UPDATE Books SET price = 449.00 WHERE book_id = 1; -- update one row UPDATE Books SET price = price * 0.9 -- 10% discount to all WHERE genre = 'Programming'; ββββββββββββββββββββββββββββββββββββββββββββ DELETE β Remove rows (rollbackable unlike TRUNCATE) ββββββββββββββββββββββββββββββββββββββββββββ DELETE FROM Books WHERE book_id = 5; -- delete one specific row DELETE FROM Books WHERE price < 100; -- delete rows matching condition
DCL manages WHO can do WHAT in the database. In organizations, the DBA creates user accounts and assigns only the necessary permissions (principle of least privilege).
-- Step 1: Create a new DB user CREATE USER 'dbda_student'@'localhost' IDENTIFIED BY 'secure_pass_2024'; -- Step 2: Grant specific permissions GRANT SELECT, INSERT ON college_db.* TO 'dbda_student'@'localhost'; GRANT ALL PRIVILEGES ON test_db.* TO 'dbda_student'@'localhost'; -- Step 3: Make permissions take effect FLUSH PRIVILEGES; -- Step 4: Revoke permissions when no longer needed REVOKE DELETE ON college_db.Books FROM 'dbda_student'@'localhost'; -- Check what permissions a user has SHOW GRANTS FOR 'dbda_student'@'localhost';
Constraints are rules applied on table columns to ensure data quality. They are enforced automatically by the DBMS β no need to write application logic for them.
| Constraint | What it enforces | Example | What happens on violation |
|---|---|---|---|
| PRIMARY KEY | Unique + NOT NULL. Only one per table. | employee_id INT PRIMARY KEY | INSERT rejected with duplicate key error |
| FOREIGN KEY | Value must exist in referenced table | dept_id INT REFERENCES dept(id) | INSERT/UPDATE rejected if parent doesn't exist |
| UNIQUE | No duplicate values (NULLs allowed) | email VARCHAR(100) UNIQUE | INSERT rejected with unique constraint error |
| NOT NULL | Value must be provided, NULL forbidden | name VARCHAR(50) NOT NULL | INSERT/UPDATE rejected if NULL passed |
| CHECK | Value must satisfy a logical expression | age INT CHECK (age BETWEEN 18 AND 65) | INSERT/UPDATE rejected if condition is false |
| DEFAULT | Auto-fill when value not provided | status VARCHAR(10) DEFAULT 'active' | Column silently filled with default |
-- Transactions ensure ACID compliance -- BEGIN starts a transaction block START TRANSACTION; UPDATE accounts SET balance = balance - 5000 WHERE acc_no = 'A001'; UPDATE accounts SET balance = balance + 5000 WHERE acc_no = 'B002'; COMMIT; -- both updates succeed β save permanently -- If something goes wrong between the two updates: ROLLBACK; -- undo everything β no money lost, no money gained SAVEPOINT sp1; -- create a checkpoint mid-transaction ROLLBACK TO sp1; -- undo back to checkpoint, not all the way
GROUP BY collapses multiple rows with the same value in a column into a single summary row. It's used with aggregate functions like COUNT, SUM, AVG, MIN, MAX.
ββββββββββββββββββββββββββββββββββββββββββββ EMPLOYEES table (what we're working with) ββββββββββββββββββββββββββββββββββββββββββββ emp_id | name | dept_id | salary | city -------|---------|---------|--------|------- 1 | Priya | 10 | 60000 | Mumbai 2 | Rahul | 10 | 55000 | Pune 3 | Anita | 20 | 70000 | Mumbai 4 | Vikram | 20 | 80000 | Delhi 5 | Sneha | 30 | 45000 | Pune ββββββββββββββββββββββββββββββββββββββββββββ -- Q: How many employees in each department? SELECT dept_id, COUNT(*) AS headcount FROM employees GROUP BY dept_id; -- Result: dept 10β2, dept 20β2, dept 30β1 -- Q: Departments with more than 1 employee AND avg salary above 50000 SELECT dept_id, COUNT(*) AS headcount, AVG(salary) AS avg_salary, MAX(salary) AS highest_sal, MIN(salary) AS lowest_sal, SUM(salary) AS total_payroll FROM employees WHERE city != 'Delhi' -- filter BEFORE grouping (Delhi excluded) GROUP BY dept_id HAVING COUNT(*) > 1 -- filter AFTER grouping (keep dept with 2+ people) AND AVG(salary) > 50000 ORDER BY avg_salary DESC; -- sort final output
| Function | What it calculates | Ignores NULL? | Example use |
|---|---|---|---|
COUNT(*) | Total number of rows (including NULLs) | No | How many orders today? |
COUNT(col) | Count of non-NULL values in a column | Yes | How many orders have a tracking number? |
SUM(col) | Total sum of numeric column | Yes | Total revenue this month |
AVG(col) | Arithmetic mean (sum/count-of-non-nulls) | Yes | Average order value |
MIN(col) | Smallest value (works on dates too) | Yes | Cheapest product, earliest order date |
MAX(col) | Largest value | Yes | Most expensive product, latest login |
STDDEV(col) | Standard deviation β how spread out values are | Yes | Salary consistency analysis |
VARIANCE(col) | Statistical variance (STDDEV squared) | Yes | Data science feature analysis |
A subquery is a complete SELECT statement nested inside another SQL statement. The inner query runs first, its result is passed to the outer query. Think of it as solving a maths problem step by step.
ββββββββββββββββββββββββββββββββββββββββββββ 1. Simple Subquery (runs once, independent) ββββββββββββββββββββββββββββββββββββββββββββ -- Find all employees earning above company average SELECT name, salary FROM employees WHERE salary > ( SELECT AVG(salary) FROM employees β runs once, returns 62000 ); -- The inner query returns 62000, outer becomes: WHERE salary > 62000 ββββββββββββββββββββββββββββββββββββββββββββ 2. Correlated Subquery (runs once PER ROW of outer query) ββββββββββββββββββββββββββββββββββββββββββββ -- Find employees earning MORE than their OWN department's average -- This is correlated because it references e1.dept_id from outer query SELECT e1.name, e1.salary, e1.dept_id FROM employees e1 WHERE e1.salary > ( SELECT AVG(e2.salary) FROM employees e2 WHERE e2.dept_id = e1.dept_id β uses outer query's dept_id! ); -- For Priya (dept 10): inner query calculates avg of dept 10 = 57500 -- For Anita (dept 20): inner query calculates avg of dept 20 = 75000 -- Each row gets its OWN calculation β hence "correlated" ββββββββββββββββββββββββββββββββββββββββββββ 3. Subquery with EXISTS (check if related rows exist) ββββββββββββββββββββββββββββββββββββββββββββ -- Find all departments that HAVE at least one employee SELECT d.dept_name FROM departments d WHERE EXISTS ( SELECT 1 FROM employees e WHERE e.dept_id = d.id );
In a normalised database, related data is split across multiple tables. JOINs bring them back together. Think of it as matching puzzle pieces β you need matching values on both sides.
ββββββββββββββββββββββββββββββββββββββββββββ Sample data we'll use for JOIN examples ββββββββββββββββββββββββββββββββββββββββββββ EMPLOYEES: emp_id, name, dept_id (1,2,3,4,5) DEPARTMENTS: id, dept_name (10,20,30,99 β 99 has no employees) SALESMAN: id, name, customer_id, commission ββββββββββββββββββββββββββββββββββββββββββββ -- INNER JOIN: Only matching rows from BOTH tables SELECT e.name, d.dept_name, e.salary FROM employees e INNER JOIN departments d ON e.dept_id = d.id; -- Returns only employees whose dept_id exists in departments table -- Employees with dept 99 would be excluded -- LEFT OUTER JOIN: All employees + their dept (NULL if dept missing) SELECT e.name, d.dept_name FROM employees e LEFT JOIN departments d ON e.dept_id = d.id; -- Returns ALL employees. If no matching dept, dept_name shows NULL -- RIGHT OUTER JOIN: All departments + their employees (NULL if no emp) SELECT e.name, d.dept_name FROM employees e RIGHT JOIN departments d ON e.dept_id = d.id; -- Returns ALL departments. Dept 99 appears with NULL employee name -- FULL OUTER JOIN: Everything from both sides SELECT e.name, d.dept_name FROM employees e FULL OUTER JOIN departments d ON e.dept_id = d.id; -- SELF JOIN: Employee-Manager hierarchy (same table joined to itself) SELECT e.name AS employee, m.name AS manager FROM employees e LEFT JOIN employees m ON e.manager_id = m.emp_id; -- Salesperson + customer details (from syllabus lab problem) SELECT c.cust_name, c.city, s.name AS salesman, s.commission FROM customers c JOIN salesman s ON c.salesman_id = s.id ORDER BY s.commission DESC;
| JOIN Type | Returns | Use case | Tip |
|---|---|---|---|
| INNER JOIN | Only rows matching in BOTH tables | Most common β get related data | Default JOIN = INNER JOIN |
| LEFT (OUTER) JOIN | All left rows + matched right (NULL if no match) | All customers even if they never ordered | Most used outer join |
| RIGHT (OUTER) JOIN | All right rows + matched left | All products even if never sold | Equivalent to LEFT JOIN with tables swapped |
| FULL OUTER JOIN | All rows from both sides | Audit mismatches on either side | MySQL doesn't support β use UNION of LEFT+RIGHT |
| SELF JOIN | Table matched with itself | Manager-employee, friend-of-friend | Requires aliases (e1, e2) |
| CROSS JOIN | Every row from left Γ every row from right | Generate all combinations (sizes Γ colors) | Can produce huge result sets! |
% = any number of characters (including zero). _ = exactly one character.name LIKE 'A%' β starts with A. name LIKE '%N' β ends with N.name LIKE 'A%N' β starts with A, ends with N (matches "Arjun", "Arun").name LIKE '_a%' β second character is 'a'.WHERE name LIKE 'A%N'Without normalization, databases suffer from three types of anomalies that corrupt data integrity:
You can't add a new department unless it already has an employee. Example: Can't record "IT Department" until someone joins it.
Updating one fact requires changing multiple rows. Miss one row β inconsistent data. Example: Department head changes β must update 50 employee rows.
Deleting the last employee from a dept also deletes the department's info. Example: Last person in IT quits β IT department info vanishes from DB.
Rule: Every column must contain atomic (single, indivisible) values. No repeating groups. Every row must be unique.
Student: Priya | Courses: "DBMS, OS, Networks"
Problem: The "Courses" column has multiple values in one cell β not atomic. Cannot query individual courses.
Priya | DBMS
Priya | OS
Priya | Networks
Each course is in its own row. Now you can query "who takes DBMS?"
Rule: Must be in 1NF PLUS no partial dependency. Every non-key attribute must depend on the entire primary key β not just part of it. This only matters when you have a composite primary key.
Table: (student_id, course_id, student_name, marks)
PK = (student_id + course_id)
But student_name depends only on student_id β not on the full composite PK. That's a partial dependency.
Split into:
Students(student_id, student_name)
Enrollments(student_id, course_id, marks)
Now student_name is in its own table, depending fully on student_id.
Rule: Must be in 2NF PLUS no transitive dependency. A non-key attribute should NOT depend on another non-key attribute. All non-key columns must depend DIRECTLY on the primary key.
Table: (emp_id, dept_id, dept_name)
PK = emp_id
But dept_name depends on dept_id, not on emp_id directly. That's transitive: emp_id β dept_id β dept_name.
Split into:
Employees(emp_id, dept_id)
Departments(dept_id, dept_name)
dept_name is now in its own table, depending directly on dept_id PK.
Rule: Must be in 3NF PLUS every determinant must be a candidate key. It's a stricter version of 3NF that catches edge cases where 3NF still allows anomalies (usually involves multiple overlapping candidate keys). For most practical purposes, 3NF is sufficient.
An ER diagram is a blueprint of your database β drawn before you write a single line of SQL. It shows what entities (things) exist, what attributes they have, and how they relate to each other. Think of it as the architect's floor plan before construction begins.
| ER Symbol | Shape | Represents | Example |
|---|---|---|---|
| Entity | Rectangle | A "thing" that has data β becomes a table | Student, Course, Department |
| Weak Entity | Double Rectangle | Entity that can't exist without another | OrderItem (can't exist without Order) |
| Attribute | Ellipse (oval) | A property of an entity β becomes a column | student_id, name, date_of_birth |
| Key Attribute | Underlined oval | Attribute that uniquely identifies the entity | student_id |
| Multi-valued Attribute | Double Ellipse | Attribute with multiple values | Phone numbers (person can have 2+ phones) |
| Derived Attribute | Dashed Ellipse | Computed from other attributes, not stored | Age (derived from DOB + today's date) |
| Composite Attribute | Ellipse with sub-ovals | Attribute made of multiple parts | Full Name = FirstName + LastName |
| Relationship | Diamond | Association between entities | Enrolls (Student Enrolls in Course) |
| Cardinality (1:1) | Line labels | One entity on each side | Citizen has one Passport |
| Cardinality (1:N) | Line labels | One on left, many on right | Department has many Employees |
| Cardinality (M:N) | Line labels | Many on both sides | Student takes many Courses; Course has many Students |
A stored procedure is a named, pre-compiled block of SQL code stored in the database. Instead of sending a long SQL query from your application every time, you just call the procedure name. Think of it like a function in Python β you define it once and call it many times with different arguments.
ββββββββββββββββββββββββββββββββββββββββββββ Basic Stored Procedure ββββββββββββββββββββββββββββββββββββββββββββ DELIMITER // CREATE PROCEDURE GetDeptSummary( IN p_dept_id INT, -- input parameter (caller provides) OUT p_emp_count INT, -- output parameter (procedure fills) OUT p_avg_salary DECIMAL(10,2) -- another output ) BEGIN -- Use IF-ELSE control structure IF p_dept_id IS NULL THEN SELECT COUNT(*), AVG(salary) INTO p_emp_count, p_avg_salary FROM employees; -- all employees if dept not specified ELSE SELECT COUNT(*), AVG(salary) INTO p_emp_count, p_avg_salary FROM employees WHERE dept_id = p_dept_id; END IF; END // DELIMITER ; -- Call the procedure CALL GetDeptSummary(10, @count, @avg); SELECT @count AS total_employees, @avg AS average_salary; ββββββββββββββββββββββββββββββββββββββββββββ Procedure with CURSOR (process row by row) ββββββββββββββββββββββββββββββββββββββββββββ DELIMITER // CREATE PROCEDURE GiveRaise() BEGIN DECLARE done INT DEFAULT 0; DECLARE v_id INT; DECLARE v_salary DECIMAL(10,2); -- Declare cursor to loop over employees DECLARE emp_cursor CURSOR FOR SELECT emp_id, salary FROM employees; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; OPEN emp_cursor; read_loop: LOOP FETCH emp_cursor INTO v_id, v_salary; IF done THEN LEAVE read_loop; END IF; -- Give 10% raise to those earning below 50000 IF v_salary < 50000 THEN UPDATE employees SET salary = v_salary * 1.10 WHERE emp_id = v_id; END IF; END LOOP; CLOSE emp_cursor; END // DELIMITER ;
A VIEW is a saved SELECT query that looks and behaves like a table. It does NOT store data β it runs the underlying query every time you access it. Views are used to: simplify complex queries, restrict columns/rows users can see, and present data in a specific format.
-- Create a view: employees with dept name + only salary > 50000 CREATE VIEW HighEarnerDetails AS SELECT e.emp_id, e.name, e.salary, e.city, d.dept_name FROM employees e JOIN departments d ON e.dept_id = d.id WHERE e.salary > 50000; -- Use the view exactly like a table SELECT * FROM HighEarnerDetails WHERE city = 'Mumbai'; SELECT dept_name, COUNT(*) FROM HighEarnerDetails GROUP BY dept_name; -- Update or drop a view CREATE OR REPLACE VIEW HighEarnerDetails AS SELECT ... -- modify DROP VIEW HighEarnerDetails; -- remove -- From syllabus: View to find employee with highest salary CREATE VIEW TopEarnerView AS SELECT e.name, e.salary, e.dept_id, d.dept_name FROM employees e JOIN departments d ON e.dept_id = d.id WHERE e.salary = (SELECT MAX(salary) FROM employees);
A TRIGGER is a stored procedure that automatically executes when a specific event (INSERT, UPDATE, DELETE) occurs on a table. You don't call triggers β the database fires them. Used for: audit logging, enforcing complex business rules, maintaining derived data.
| Trigger Type | When it fires | Access to NEW/OLD | Use case |
|---|---|---|---|
| BEFORE INSERT | Before new row is added | NEW (modify it before save) | Auto-format data, validate custom rules |
| AFTER INSERT | After row is successfully added | NEW (read-only) | Log new record, send notification |
| BEFORE UPDATE | Before row is modified | OLD (old values), NEW (new values) | Prevent unauthorized changes, capture old value |
| AFTER UPDATE | After row is successfully modified | OLD and NEW | Audit trail, sync summary tables |
| BEFORE DELETE | Before row is removed | OLD (the row being deleted) | Prevent deletion of important records |
| AFTER DELETE | After row is deleted | OLD (what was deleted) | Archive deleted records, cleanup |
-- AFTER UPDATE trigger: audit log for salary changes DELIMITER // CREATE TRIGGER trg_SalaryAudit AFTER UPDATE ON employees FOR EACH ROW BEGIN IF OLD.salary != NEW.salary THEN -- only log if salary actually changed INSERT INTO salary_audit_log (emp_id, old_salary, new_salary, changed_by, changed_at) VALUES (NEW.emp_id, OLD.salary, NEW.salary, USER(), NOW()); END IF; END // DELIMITER ; -- BEFORE INSERT trigger: auto-uppercase the name DELIMITER // CREATE TRIGGER trg_FormatName BEFORE INSERT ON employees FOR EACH ROW BEGIN SET NEW.name = UPPER(TRIM(NEW.name)); -- trim spaces + uppercase END // DELIMITER ;
Window functions perform calculations across a set of related rows without collapsing them. Unlike GROUP BY (which gives one row per group), window functions give you a result for EVERY row, with that row's "window" context calculated alongside. Extremely powerful for ranking, running totals, and comparisons.
-- The OVER() clause is what makes it a window function -- PARTITION BY = which "window" each row belongs to -- ORDER BY = how to sort within each window SELECT name, dept_id, salary, -- Rank within department (ties get same rank, gap follows) RANK() OVER(PARTITION BY dept_id ORDER BY salary DESC) AS dept_rank, -- Dense rank (ties get same rank, NO gap) DENSE_RANK() OVER(PARTITION BY dept_id ORDER BY salary DESC) AS dense_rank, -- Row number (unique sequential number) ROW_NUMBER() OVER(ORDER BY salary DESC) AS overall_rank, -- Running total of salary within dept SUM(salary) OVER(PARTITION BY dept_id ORDER BY salary) AS running_total, -- Department total (same for all rows in that dept) SUM(salary) OVER(PARTITION BY dept_id) AS dept_total, -- Salary of next higher earner in same dept LEAD(salary, 1) OVER(PARTITION BY dept_id ORDER BY salary) AS next_salary, -- Salary of previous row in same dept LAG(salary, 1) OVER(PARTITION BY dept_id ORDER BY salary) AS prev_salary FROM employees; -- Practical use: Get top earner per department (without subquery) SELECT * FROM ( SELECT name, dept_id, salary, RANK() OVER(PARTITION BY dept_id ORDER BY salary DESC) AS rnk FROM employees ) ranked WHERE rnk = 1; -- only top earner per dept
-- Simple CASE: map specific values SELECT name, salary, CASE dept_id WHEN 10 THEN 'Engineering' WHEN 20 THEN 'HR' WHEN 30 THEN 'Finance' ELSE 'Unknown' END AS dept_name, -- Searched CASE: conditions CASE WHEN salary >= 80000 THEN 'Senior' WHEN salary >= 50000 THEN 'Mid-level' ELSE 'Junior' END AS level FROM employees;
| Aspect | OLTP β Operational | OLAP β Analytical |
|---|---|---|
| Full form | Online Transaction Processing | Online Analytical Processing |
| Purpose | Run daily business operations | Support business intelligence & decisions |
| Query type | Simple INSERT/UPDATE/DELETE + simple SELECTs | Complex multi-table JOINs, aggregations, GROUP BY |
| Data age | Current (minutes old) | Historical (months to years) |
| Data granularity | Individual transactions | Aggregated summaries |
| Schema design | Highly normalized (3NF) | Denormalized (Star / Snowflake schema) |
| DB size | Gigabytes | Terabytes to Petabytes |
| Response time | Milliseconds | Seconds to minutes (complex queries) |
| Concurrent users | Thousands | Tens to hundreds (analysts) |
| Example systems | Banking apps, e-commerce checkout, hospital records | Amazon Redshift, Google BigQuery, Snowflake, Hive |
A Data Warehouse is a central repository that consolidates data from multiple OLTP systems (ERP, CRM, POS) into one place for analysis. Data is cleaned, transformed, and loaded through an ETL process.
Named because the diagram looks like a star β one central Fact table surrounded by Dimension tables. The Fact table contains measurable numbers (sales amount, quantity). Dimension tables provide context (who, what, when, where).
ββββββββββββββββββββββββββββββββββββββββββββ STAR SCHEMA for E-commerce Sales Analysis ββββββββββββββββββββββββββββββββββββββββββββ FACT_Sales (the star's centre) βββ sales_id (PK) βββ amount β measure βββ quantity β measure βββ discount β measure βββ date_id β DIM_Date βββ product_id β DIM_Product βββ customer_id β DIM_Customer βββ store_id β DIM_Store βββ promotion_id β DIM_Promotion DIM_Date DIM_Product DIM_Customer βββ date_id (PK) βββ product_id βββ customer_id βββ day βββ name βββ name βββ month βββ category βββ city βββ quarter βββ brand βββ segment βββ year βββ price βββ age_group -- Sample analytical query on Star Schema SELECT d.year, d.quarter, p.category, c.city, SUM(f.amount) AS total_revenue, COUNT(f.sales_id) AS num_transactions, AVG(f.amount) AS avg_order_value FROM FACT_Sales f JOIN DIM_Date d ON f.date_id = d.date_id JOIN DIM_Product p ON f.product_id = p.product_id JOIN DIM_Customer c ON f.customer_id = c.customer_id WHERE d.year IN (2023, 2024) GROUP BY d.year, d.quarter, p.category, c.city ORDER BY total_revenue DESC;
Pull data from sources: SQL databases (JDBC), flat files (CSV/Excel), APIs (JSON/XML), mainframes. Challenge: different formats, encodings, time zones. Tools: Informatica, Talend, Apache Sqoop.
Clean and reshape: handle missing values, standardize formats (dates, phone numbers, addresses), remove duplicates, apply business rules, create surrogate keys, aggregate data. Most complex step β 70% of ETL effort.
Write to warehouse. Two strategies: Full Load (reload everything β simple but slow), Incremental Load (only new/changed records β fast but complex). Bulk insert tools used for performance.
| Operation | What it does | Example |
|---|---|---|
| Roll-up (aggregation) | Move from detail level to summary level. Reduces data. | Daily sales β Monthly sales β Annual sales |
| Drill-down (disaggregation) | Move from summary to detail. Increases data. | Annual total β Q3 total β September total β Week 3 |
| Slice | Select one value for ONE dimension. Reduces cube to 2D. | "Show all data for year 2024 only" β year dimension fixed |
| Dice | Select ranges on MULTIPLE dimensions. Sub-cube. | "2023β2024, Western region, Electronics & Appliances" |
| Pivot (Rotate) | Swap the row and column axes. Changes perspective. | Products on rows & months on columns β swap them |
Facebook, Google, Amazon have billions of records. Traditional RDBMS scales vertically (bigger machine). But you can't keep buying bigger machines forever. NoSQL scales horizontally (more machines).
Product catalogs vary wildly β a book has ISBN, a shirt has size/color. In SQL, you'd need 50 nullable columns or complex joins. In MongoDB, each document just has the fields it needs.
Redis answers a cache lookup in under 1ms. Cassandra handles 1 million writes/second. These are impossible with a single SQL server under heavy load.
JSON from APIs, sensor readings, user activity logs, social media posts β don't fit neatly into rows and columns. Document and key-value stores are far more natural.
| Type | Data Model | Real-world use case | Examples | Key strength |
|---|---|---|---|---|
| Document Store | JSON/BSON documents, each can have different fields | Product catalogs, user profiles, content management systems, e-commerce | MongoDB, CouchDB, Firestore | Flexible schema, rich queries on nested data |
| Key-Value Store | Simple dictionary: key β value (value is opaque blob) | Session storage, user carts, rate limiting, real-time leaderboards, pub/sub messaging | Redis, DynamoDB, Riak, Memcached | Fastest possible lookup (O(1)), massive scale |
| Column-Oriented (Wide Column) | Rows identified by row key, columns grouped in "column families" | IoT time-series, activity feeds, recommendation engines, write-heavy analytics | Cassandra, HBase, Google Bigtable | Extraordinary write throughput, linear scaling |
| Graph Database | Nodes (entities) connected by edges (relationships) | Social networks, fraud detection, recommendation engines, knowledge graphs | Neo4j, Amazon Neptune, ArangoDB | Traversing relationships is very fast; SQL JOIN chains can't match this |
The CAP Theorem (Brewer's Theorem, 2000) states that in a distributed database system, you can guarantee at most 2 out of 3 properties simultaneously:
Every read receives the most recent write. All nodes show the same data at the same time. If node A writes, node B immediately sees that write.
Every request receives a response (not necessarily the most recent data). The system is always up and returns something, even if a node is down.
The system continues to work even if network communication breaks between nodes (network partition). Essential for any distributed system β network failures WILL happen.
Embed related data directly inside the parent document. Best when data is: always accessed together, doesn't change often, has a 1-to-few relationship, or doesn't need independent queries.
-- Example: Blog post with embedded comments { "_id": "post_001", "title": "Introduction to MongoDB", "author": "Priya Sharma", "published": "2024-03-15", "tags": ["NoSQL", "MongoDB", "Database"], "comments": [ β embedded array of sub-documents { "user": "Rahul", "text": "Great article!", "date": "2024-03-16", "likes": 5 }, { "user": "Anita", "text": "Very helpful for my exam prep", "date": "2024-03-17", "likes": 3 } ] } -- β One query fetches post + all comments -- β If comments grow to 10,000+, document becomes huge (16MB limit in MongoDB)
Store related data in separate collections and reference by ID. Best when: data is accessed independently, there's a 1-to-many or many-to-many relationship, or the related data is frequently updated.
-- User document { "_id": "user_101", "name": "Priya", "email": "priya@example.com" } -- Order documents (separate collection, reference user_id) { "_id": "order_501", "user_id": "user_101", "item": "Laptop", "amount": 65000 } { "_id": "order_502", "user_id": "user_101", "item": "Mouse", "amount": 800 } -- Query with $lookup (like SQL JOIN) db.orders.aggregate([ { $lookup: { from: "users", localField: "user_id", foreignField: "_id", as: "user_details" }} ]) -- β Orders and users can be queried and updated independently -- β Requires multiple queries (or $lookup which is slower than embedded)
One of NoSQL's greatest advantages over SQL: you can add new fields to a collection without ALTER TABLE, without downtime, and without breaking existing applications. Old documents simply don't have the new field.
-- Scenario: You need to add 'loyalty_points' to all users -- In SQL: ALTER TABLE users ADD loyalty_points INT DEFAULT 0; (might lock table) -- In MongoDB: Just start inserting new docs with the field! -- Backfill existing docs that don't have the field: db.users.updateMany( { loyalty_points: { $exists: false } }, -- where field doesn't exist { $set: { loyalty_points: 0 } } -- add it with default 0 ); -- Rename a field across all documents: db.users.updateMany({}, { $rename: { "phone": "mobile_number" } }); -- Remove a field from all documents: db.users.updateMany({}, { $unset: { "old_field": "" } });
| SQL Concept | MongoDB Equivalent | Notes |
|---|---|---|
| Database | Database | Same concept, different internal storage |
| Table | Collection | Collections don't enforce schema |
| Row / Record | Document (JSON / BSON) | Each doc can have different fields |
| Column | Field | Fields exist only if set on each doc |
| Primary Key (auto) | _id (ObjectId) | Auto-generated 12-byte unique ID |
| Index | Index | Same concept, different syntax |
| JOIN | $lookup (or embedded data) | Prefer embedding over $lookup for performance |
| GROUP BY | $group in aggregation pipeline | More powerful β can group on complex expressions |
| WHERE | Query filter document | {field: {$operator: value}} |
| NULL | null or missing field | Absence of field β null value in Mongo |
-- MongoDB creates DB when you first use it (no CREATE DATABASE command) use restaurant_db -- switch to (or create) database -- Insert a single document db.restaurants.insertOne({ name: "The Spice Route", borough: "Manhattan", cuisine: "Indian", address: { street: "74 W 47th St", zipcode: "10036", coord: [-73.9894, 40.7594] β nested document }, grades: [ β array of sub-documents { grade: "A", score: 12, date: new Date("2024-01-15") }, { grade: "B", score: 27, date: new Date("2023-06-20") } ] }) -- Insert multiple documents at once db.restaurants.insertMany([ { name: "Mumbai Magic", borough: "Brooklyn", cuisine: "Indian" }, { name: "Beijing Bites", borough: "Queens", cuisine: "Chinese" }, { name: "Pasta Palace", borough: "Bronx", cuisine: "Italian" } ])
-- Basic queries db.restaurants.find({}) -- all documents db.restaurants.find({ borough: "Manhattan" }) -- exact match db.restaurants.findOne({ cuisine: "Indian" }) -- only first match -- Comparison operators db.restaurants.find({ "grades.score": { $gt: 90 } }) -- score > 90 db.restaurants.find({ "grades.score": { $gte: 70, $lt: 100 } }) -- From syllabus lab: restaurants NOT American, score > 70, longitude < -65.75 db.restaurants.find({ cuisine: { $ne: "American" }, "grades.score": { $gt: 70 }, "address.coord.0": { $lt: -65.754168 } }) -- NOT American, grade=A, NOT Brooklyn, sort by cuisine desc db.restaurants.find({ cuisine: { $ne: "American" }, "grades.grade": "A", borough: { $ne: "Brooklyn" } }).sort({ cuisine: -1 }) -- Logical operators db.restaurants.find({ $or: [ { cuisine: "Indian" }, { cuisine: "Chinese" } ] }) db.restaurants.find({ $and: [ { borough: "Manhattan" }, { "grades.score": { $gt: 80 } } ] }) -- Projection: specify which fields to return (1=include, 0=exclude) db.restaurants.find( { borough: "Manhattan" }, { name: 1, cuisine: 1, _id: 0 } -- show name+cuisine, hide _id ) -- Sort, skip, limit (for pagination) db.restaurants.find({}) .sort({ name: 1 }) -- ascending by name .skip(20) -- skip first 20 (page 2) .limit(10) -- return 10 results
-- $set: add or change specific fields db.restaurants.updateOne( { name: "The Spice Route" }, { $set: { is_open: true, rating: 4.5 } } ) -- $inc: increment a number db.restaurants.updateMany( { cuisine: "Indian" }, { $inc: { review_count: 1 } } -- add 1 to review_count ) -- $push: add element to an array db.restaurants.updateOne( { name: "The Spice Route" }, { $push: { grades: { grade: "A", score: 15, date: new Date() } } } ) -- Upsert: update if exists, insert if not db.restaurants.updateOne( { name: "New Restaurant" }, { $set: { cuisine: "Fusion", borough: "Staten Island" } }, { upsert: true } -- creates new doc if not found )
Without indexes, every query performs a collection scan (COLLSCAN) β examining every single document. As your collection grows to millions of documents, this becomes unbearably slow. Indexes create a separate B-tree data structure that allows O(log n) lookup instead of O(n) scan.
ββββββββββββββββββββββββββββββββββββββββββββ Creating Different Types of Indexes ββββββββββββββββββββββββββββββββββββββββββββ -- 1. Single field index (most common) db.restaurants.createIndex({ borough: 1 }) -- ascending db.restaurants.createIndex({ rating: -1 }) -- descending -- 2. Compound index (multiple fields together) db.restaurants.createIndex({ borough: 1, cuisine: 1, name: 1 }) -- This compound index helps queries on: -- (borough) β yes, prefix -- (borough, cuisine) β yes, prefix -- (borough, cuisine, name) β yes, full index -- (cuisine) β NO! (no borough prefix) -- (cuisine, name) β NO! -- 3. Unique index β prevents duplicates db.users.createIndex({ email: 1 }, { unique: true }) -- 4. Sparse index β only indexes documents WHERE field exists db.users.createIndex({ phone: 1 }, { sparse: true }) -- Documents without 'phone' field are NOT in this index -- Useful when field is optional (avoid indexing many nulls) -- 5. Partial index β only index subset matching a condition db.restaurants.createIndex( { borough: 1 }, { partialFilterExpression: { rating: { $gt: 4.0 } } } ) -- Only indexes restaurants with rating > 4.0 -- Much smaller index, much faster for queries on popular restaurants -- 6. Text index β for full-text search db.articles.createIndex({ content: "text", title: "text" }) db.articles.find({ $text: { $search: "MongoDB indexing performance" } }) ββββββββββββββββββββββββββββββββββββββββββββ Analyzing Query Performance with explain() ββββββββββββββββββββββββββββββββββββββββββββ db.restaurants.find( { borough: "Manhattan", cuisine: "Indian" } ).explain("executionStats") -- Key things to look for in explain() output: -- winningPlan.stage: "IXSCAN" = β using index, "COLLSCAN" = β full scan -- totalDocsExamined: should be close to nReturned (ideally equal) -- executionTimeMillis: how long in milliseconds -- nReturned: how many docs matched -- Index management db.restaurants.getIndexes() -- list all indexes db.restaurants.dropIndex("borough_1") -- drop specific index by name db.restaurants.dropIndexes() -- drop all except _id
| Stage | Purpose | SQL Equivalent | Example |
|---|---|---|---|
| $match | Filter documents (always put early to reduce workload) | WHERE | {$match: {borough:"Manhattan"}} |
| $group | Group documents + compute aggregate values | GROUP BY | {$group: {_id:"$cuisine", count:{$sum:1}}} |
| $project | Include/exclude/rename/compute new fields | SELECT col AS alias | {$project: {name:1, price:1, _id:0}} |
| $sort | Sort documents by field(s) | ORDER BY | {$sort: {count:-1}} |
| $limit | Keep only first N documents | LIMIT | {$limit: 10} |
| $skip | Skip first N documents (pagination) | OFFSET | {$skip: 20} |
| $unwind | Deconstruct array β one doc per array element | (no SQL equivalent) | {$unwind: "$grades"} |
| $lookup | Left outer join with another collection | LEFT JOIN | See example below |
| $addFields | Add computed fields to documents | SELECT ..., expression AS col | {$addFields: {total: {$multiply:["$qty","$price"]}}} |
| $count | Count documents in pipeline | COUNT(*) | {$count: "total_restaurants"} |
| $out / $merge | Write pipeline results to a collection | INSERT INTO ... SELECT | {$out: "summary_collection"} |
ββββββββββββββββββββββββββββββββββββββββββββ Complete aggregation pipeline example From syllabus: restaurants with score > 90 ββββββββββββββββββββββββββββββββββββββββββββ db.restaurants.aggregate([ // Stage 1: filter to Manhattan { $match: { borough: "Manhattan" } }, // Stage 2: explode grades array β 1 doc per grade { $unwind: "$grades" }, // Stage 3: only keep grades with score > 90 { $match: { "grades.score": { $gt: 90 } } }, // Stage 4: group by cuisine, compute stats { $group: { _id: "$cuisine", avgScore: { $avg: "$grades.score" }, maxScore: { $max: "$grades.score" }, restCount: { $addToSet: "$name" }, // unique restaurant names total: { $sum: 1 } }}, // Stage 5: add a field counting unique restaurants { $addFields: { uniqueRestaurants: { $size: "$restCount" } }}, // Stage 6: sort by average score descending { $sort: { avgScore: -1 } }, // Stage 7: only top 5 cuisines { $limit: 5 }, // Stage 8: clean up output { $project: { cuisine: "$_id", avgScore: { $round: ["$avgScore", 2] }, uniqueRestaurants: 1, _id: 0 }} ]) ββββββββββββββββββββββββββββββββββββββββββββ $lookup β Join two collections ββββββββββββββββββββββββββββββββββββββββββββ db.orders.aggregate([ { $lookup: { from: "customers", // collection to join localField: "customer_id",// field in orders foreignField: "_id", // field in customers as: "customer_info"// name for joined array }}, { $unwind: "$customer_info" }, // flatten the joined array { $project: { order_date: 1, amount: 1, "customer_info.name": 1, "customer_info.city": 1 }} ])
XML (eXtensible Markup Language) is a text-based format for representing structured data. Every element has an opening and closing tag. It's self-describing (tags explain the data). Widely used for: configuration files, web services (SOAP), data interchange between systems, and document storage.
<?xml version="1.0" encoding="UTF-8"?> <library> <book id="B001" inStock="true"> <title>Clean Code</title> <author>Robert C. Martin</author> <price currency="INR">499</price> <categories> <category>Programming</category> <category>Software Engineering</category> </categories> </book> <book id="B002" inStock="false"> <title>The Pragmatic Programmer</title> <author>David Thomas</author> <price currency="INR">549</price> </book> </library> ββββββββββββββββββββββββββββββββββββββββββββ XPath β Navigate the XML tree ββββββββββββββββββββββββββββββββββββββββββββ /library β root element /library/book β all book elements /library/book[1] β first book only /library/book/@id β all id attributes /library/book[@id='B001'] β book with id B001 /library/book[@id='B001']/price β price of book B001 //title β all title elements anywhere in doc /library/book[price > 500] β books costing over 500 ββββββββββββββββββββββββββββββββββββββββββββ XQuery β Query language for XML (like SQL for XML) ββββββββββββββββββββββββββββββββββββββββββββ for $b in doc("library.xml")/library/book where $b/price > 400 and $b/@inStock = "true" order by $b/price descending return <result> <bookTitle>{$b/title/text()}</bookTitle> <cost>{$b/price/text()}</cost> </result>
| Aspect | XML | JSON |
|---|---|---|
| Verbosity | Verbose (opening + closing tags) | Compact |
| Attributes support | Yes (on tags) | No native attributes |
| Comments | Supported | Not supported |
| Schema validation | XSD (XML Schema Definition) | JSON Schema |
| Transformations | XSLT (powerful) | JS/Python code |
| Use today | Enterprise systems, SOAP, config files, Office documents | REST APIs, NoSQL, web applications |
A single Cassandra server instance. Typically holds 1-3TB of data. Each node independently handles reads and writes for its data range.
All nodes are arranged in a conceptual ring. Each node "owns" a range of hash values. There is NO master β every node is equal (peer-to-peer).
Cassandra hashes the partition key to determine which node(s) store the data. Moving data when adding/removing nodes is minimal (only neighboring ranges shift).
Every write is first appended to the commit log (sequential disk write β very fast). Then written to MemTable (in-memory). Flushed to SSTable periodically.
= Database in SQL terms. Contains tables. Keyspace-level settings define: replication strategy and replication factor.
How many copies of each data item to keep. RF=3 means 3 nodes each hold a copy. If 1 node dies, 2 copies remain β no data loss.
| Feature | MongoDB | Cassandra |
|---|---|---|
| Architecture | Primary-Secondary (replica sets) | Peer-to-peer (masterless ring) |
| Schema | Dynamic (schemaless) | Static (schema must be defined) |
| Query flexibility | High (rich queries, aggregation) | Limited (must design for query patterns) |
| Joins | $lookup (or embedding) | Not supported β denormalize instead |
| Write performance | Good | Outstanding (millions/second) |
| Best for | CMS, catalogs, varied documents | Time-series, IoT, messaging, logs |
| Consistency model | Strong consistency (CP) | Tunable consistency (AP by default) |
| Used by | Forbes, eBay, Expedia | Netflix, Apple, Instagram, Discord |
ββββββββββββββββββββββββββββββββββββββββββββ Getting Started with cqlsh ββββββββββββββββββββββββββββββββββββββββββββ -- Start the CQL shell $ cqlsh $ cqlsh 127.0.0.1 9042 -- with specific host and port -- Create a keyspace (with replication settings) CREATE KEYSPACE college_db WITH REPLICATION = { 'class': 'SimpleStrategy', -- for single DC 'replication_factor': 3 -- 3 copies of all data }; -- For production (multi-datacenter): CREATE KEYSPACE prod_db WITH REPLICATION = { 'class': 'NetworkTopologyStrategy', 'datacenter1': 3, 'datacenter2': 2 }; USE college_db; -- switch to keyspace DESCRIBE KEYSPACES; -- list all keyspaces DESCRIBE TABLES; -- list tables in current keyspace DESCRIBE TABLE employees; -- see table structure
Unlike SQL where you add indexes after design, Cassandra requires you to design your tables around your queries. The PRIMARY KEY determines: which node stores the data AND how data is sorted on each node. Get this wrong and your queries will either fail or be extremely slow.
| Primary Key Type | Syntax | Effect | Use when |
|---|---|---|---|
| Simple PK | PRIMARY KEY (user_id) | user_id is both partition + sort key | Data accessed by single unique ID |
| Compound PK | PRIMARY KEY (device_id, timestamp) | device_id = partition key; timestamp = clustering key (sorts within partition) | Time-series: "get all readings for device X, ordered by time" |
| Composite Partition | PRIMARY KEY ((country, city), user_id) | country+city together determine which node; user_id sorts within | When single partition key creates "hot spots" (too much data on one node) |
ββββββββββββββββββββββββββββββββββββββββββββ Designing for query: "Get sensor readings for device X in descending time order" ββββββββββββββββββββββββββββββββββββββββββββ CREATE TABLE sensor_readings ( device_id UUID, -- partition key: groups all readings for one device read_time TIMESTAMP, -- clustering key: sorted newest-first temperature FLOAT, humidity FLOAT, location TEXT, PRIMARY KEY (device_id, read_time) ) WITH CLUSTERING ORDER BY (read_time DESC); -- β This query is FAST: SELECT * FROM sensor_readings WHERE device_id = 12345-uuid AND read_time > '2024-01-01' LIMIT 100; -- β This query FAILS (no partition key): SELECT * FROM sensor_readings WHERE temperature > 30; -- Cannot scan all partitions ββββββββββββββββββββββββββββββββββββββββββββ Table Operations ββββββββββββββββββββββββββββββββββββββββββββ -- Create employees table CREATE TABLE employees ( emp_id UUID PRIMARY KEY, first_name TEXT, last_name TEXT, department TEXT, salary DECIMAL, hire_date DATE, is_active BOOLEAN ); -- Add a new column (non-breaking change) ALTER TABLE employees ADD email TEXT; ALTER TABLE employees ADD skills LIST<TEXT>; -- Create secondary index (for non-partition key queries) CREATE INDEX emp_dept_idx ON employees(department); -- Now you can query: WHERE department = 'Engineering' -- But use sparingly β secondary indexes are expensive at scale DROP INDEX emp_dept_idx; TRUNCATE TABLE employees; -- delete all rows, keep structure DROP TABLE employees;
| Category | CQL Type | Description | Example |
|---|---|---|---|
| Numeric | INT | 32-bit signed integer | age, quantity |
| BIGINT | 64-bit signed integer | user_id, timestamp millis | |
| FLOAT | 32-bit floating point | temperature, rating | |
| DOUBLE | 64-bit floating point | latitude, longitude, precise amounts | |
| DECIMAL | Exact decimal (arbitrary precision) | salary, price, financial data | |
| Text | TEXT / VARCHAR | UTF-8 encoded string (no size limit) | name, email, description |
| ASCII | US-ASCII string only | Status codes, old system IDs | |
| BLOB | Binary data, no validation | Images, files, serialized objects | |
| Date/Time | TIMESTAMP | Date + time with millisecond precision | created_at, updated_at |
| DATE | Date only (no time) | birth_date, hire_date | |
| TIME | Time of day only | office_open_time | |
| Special | UUID | Universally unique identifier | Primary keys (use uuid() function) |
| TIMEUUID | Version 1 UUID (time-based) | Sortable by time, for time-series PKs | |
| Other | BOOLEAN | true / false | is_active, is_verified |
| Collections | LIST<T>, SET<T>, MAP<K,V> | Multiple values in one column | See examples below |
ββββββββββββββββββββββββββββββββββββββββββββ Collections β LIST, SET, MAP in action ββββββββββββββββββββββββββββββββββββββββββββ CREATE TABLE student_profile ( student_id UUID PRIMARY KEY, name TEXT, -- LIST: ordered, allows duplicates (exam scores in order) scores LIST<INT>, -- SET: unordered, NO duplicates (enrolled courses) courses SET<TEXT>, -- MAP: key-value (certification β date earned) certs MAP<TEXT, DATE> ); -- Insert with collections INSERT INTO student_profile (student_id, name, scores, courses, certs) VALUES ( uuid(), 'Priya Sharma', [85, 90, 78, 92], -- LIST {'DBMS', 'OS', 'Networks', 'ML'}, -- SET {'AWS_Cloud': '2024-03-15', 'MongoDB': '2024-01-20'} -- MAP ); -- Append to a LIST UPDATE student_profile SET scores = scores + [88] WHERE student_id = some_uuid; -- Add to a SET UPDATE student_profile SET courses = courses + {'Big Data'} WHERE student_id = some_uuid; -- Add to a MAP UPDATE student_profile SET certs['Cassandra'] = '2024-06-01' WHERE student_id = some_uuid; ββββββββββββββββββββββββββββββββββββββββββββ UDT β User Defined Type (custom complex type) ββββββββββββββββββββββββββββββββββββββββββββ -- Define UDT (like a struct in C) CREATE TYPE address_type ( street TEXT, city TEXT, state TEXT, pincode TEXT ); -- Use in a table (must use FROZEN for non-collection UDT columns) CREATE TABLE employees ( emp_id UUID PRIMARY KEY, name TEXT, home_addr FROZEN<address_type>, -- single address past_addrs LIST<FROZEN<address_type>> -- list of addresses ); -- Insert with UDT INSERT INTO employees (emp_id, name, home_addr) VALUES ( uuid(), 'Priya', { street: '101 Andheri East', city: 'Mumbai', state: 'MH', pincode: '400069' } );
A BATCH groups multiple DML statements and executes them atomically within a single partition. In Cassandra, BATCH is NOT a full transaction β it guarantees atomicity only within the same partition. Cross-partition batches should be avoided as they are slow and add coordinator load.
-- Batch: all operations executed atomically BEGIN BATCH -- Insert new employee INSERT INTO employees (emp_id, name, salary) VALUES(uuid(), 'Neha Patil', 45000); -- Update salary where commission = 0 UPDATE employees SET salary = 20000 WHERE emp_id = some_uuid; -- Change names starting with N to uppercase UPDATE employees SET name = toUpperCase(name) WHERE emp_id = another_uuid; APPLY BATCH; -- Logged BATCH (default): safe but slower β coordinator logs before applying -- Unlogged BATCH: faster but not atomic BEGIN UNLOGGED BATCH ... APPLY BATCH;
According to IBM, bad data costs businesses $3.1 trillion annually in the USA alone. In data pipelines, dirty data causes incorrect analytics, wrong business decisions, failed ML models, and regulatory compliance failures.
Are all required values present? NULL rate per column should be monitored. Critical fields like customer_id should have 0% NULL.
Does the data correctly represent reality? Age of 150 = wrong. PIN code "00000" = wrong. Validate against reference data.
Is data the same across systems? Customer address in CRM vs ERP should match. "Mumbai" vs "Bombay" = inconsistency.
Is data fresh enough for its use? Yesterday's stock prices for real-time trading = useless.
Are there duplicate records? Same customer entered twice with slightly different names creates double billing.
Does data conform to defined formats and ranges? Email must have @, phone must be 10 digits, date must be a valid date.
| Problem | Detection Method | Solution Options | SQL/Python example |
|---|---|---|---|
| Missing Values (NULL) | COUNT(*) WHERE col IS NULL | Mean/median imputation, mode for categorical, forward-fill, drop row, flag as "Unknown" | UPDATE t SET salary = (SELECT AVG(salary) FROM t) WHERE salary IS NULL |
| Duplicate Records | GROUP BY all cols + HAVING COUNT > 1 | Keep first occurrence, compare timestamps, fuzzy matching for near-duplicates | SELECT name, COUNT(*) FROM customers GROUP BY email HAVING COUNT(*) > 1 |
| Outliers | Z-score |z| > 3; IQR method; visual box plots | Cap at threshold (winsorization), remove, treat separately, transform (log scale) | Z-score: (value - mean) / std_dev |
| Inconsistent Format | SELECT DISTINCT + manual review | UPPER()/LOWER(), TRIM(), REGEXP_REPLACE(), standardization tables | UPDATE t SET city = UPPER(TRIM(city)) |
| Wrong Data Type | Schema validation, SELECT WHERE | CAST, convert pipeline, reject invalid on ingestion | SELECT * FROM t WHERE age NOT REGEXP '^[0-9]+$' |
| Referential Integrity | LEFT JOIN to reference table, check NULLs | Add foreign key constraints, clean orphan records | SELECT * FROM orders o LEFT JOIN customers c ON ... WHERE c.id IS NULL |
Machine learning algorithms (KNN, SVM, Neural Networks) are sensitive to the scale of features. If "salary" ranges from 10,000 to 1,00,000 and "age" ranges from 22 to 65, the ML model will be dominated by salary. Scaling puts all features on similar ranges.
| Technique | Formula | Result Range | When to use | Sensitive to outliers? |
|---|---|---|---|---|
| Min-Max Normalization | (x β min) / (max β min) | [0, 1] always | When you know min/max, bounded output needed, KNN, Neural networks | Yes β one extreme outlier shifts everything |
| Z-score Standardization | (x β mean) / std_dev | Typically [β3, 3], mean=0, SD=1 | Data has outliers, SVM, Linear/Logistic Regression, PCA | Less sensitive than min-max |
| Robust Scaling | (x β median) / IQR | Centered at 0, no fixed range | Heavy outliers in data, can't remove them | No β uses median and IQR |
Data lineage tracks the journey of data from origin to destination β where did it come from, what transformations were applied, who accessed it. Critical for: debugging data quality issues ("why is this sales figure wrong?"), regulatory compliance (GDPR, HIPAA), and impact analysis ("if I change this ETL rule, what downstream reports are affected?").