DBMS (Database Management System)
A comprehensive guide to Database Management Systems covering database fundamentals, ER modeling, relational algebra, normalization, SQL, transactions, indexing, query optimization, recovery, and database security. Designed for strong conceptual understanding and technical interview preparation.

#DBMS Fundamentals#
A friendly, start-to-advanced guide to the foundations of Database Management Systems. Read it top to bottom the first time; use the Quick Revision section at the end before exams or interviews.
π Table of Contents
- Introduction to DBMS
- File System vs DBMS
- Advantages and Disadvantages of DBMS
- Applications of DBMS
- Characteristics of a Database
- Database Users and Database Administrators (DBA)
- Database Languages: DDL, DML, DQL, DCL, TCL
- Database Abstraction
- Three-Schema Architecture
- Data Independence: Logical and Physical
- Database Instances and Schemas
- Metadata and Data Dictionary
- DBMS Components and Architecture
- Types of DBMS
- Centralized and Distributed Databases
- Quick Revision Cheat Sheet
1. Introduction to DBMS
1.1 Data, Information, Database β the basics
| Term | Meaning | Example |
|---|---|---|
| Data | Raw, unprocessed facts | 101, Riya, 87 |
| Information | Data that has been processed to be meaningful | "Riya (Roll 101) scored 87 marks" |
| Database | An organised collection of related data stored so it can be easily accessed, managed and updated | A university's student records |
| DBMS | Software that creates, stores, manages and controls access to a database | MySQL, PostgreSQL, Oracle, SQL Server, MongoDB* |
* MongoDB is a NoSQL DBMS; the concepts in these notes focus on the classic foundations that apply to all of them.
1.2 What is a DBMS?
A Database Management System (DBMS) is software that sits between the users/applications and the actual data. Instead of every program reading and writing files directly, they ask the DBMS, and the DBMS takes care of storing, retrieving, securing and protecting the data.
Think of it like a librarian. You don't walk into the stacks and rearrange books yourself β you ask the librarian, who knows where everything is, who is allowed to borrow what, and how to keep the library consistent.
Users / Applications
β (requests: "give me all students with marks > 80")
βΌ
βββββββββββββββββ
β DBMS β β handles storage, queries, security, concurrency, recovery
βββββββββββββββββ
β
βΌ
Physical Database (files on disk)
1.3 What does a DBMS actually do?
- Define the structure of data (tables, types, constraints)
- Store data efficiently on disk
- Retrieve data quickly using queries
- Update data safely (insert, modify, delete)
- Protect data (security, backup, recovery)
- Allow many users to work at the same time without corrupting data
2. File System vs DBMS
Before DBMSs, applications stored data in plain files (text files, spreadsheets, binary files), and each application managed its own files. That works for tiny setups but breaks down quickly as data and users grow.
2.1 Problems with the traditional file system
- Data redundancy β the same data (e.g., a student's address) is stored in many files.
- Data inconsistency β one copy gets updated, the others don't, so they disagree.
- Difficulty in accessing data β every new type of query needs a brand-new program.
- Data isolation β data is scattered across files with different formats.
- Integrity problems β rules like "marks must be 0β100" are buried inside application code, so they're easy to miss or break.
- Atomicity problems β if the system crashes halfway through a transfer, the data is left half-done.
- Concurrent-access anomalies β two users editing the same file can overwrite each other's changes.
- Security problems β hard to give fine-grained access (e.g., "can see names but not salaries").
2.2 Side-by-side comparison
| Feature | File System | DBMS |
|---|---|---|
| Data redundancy | High | Minimal (controlled) |
| Data consistency | Hard to maintain | Maintained via constraints |
| Data sharing | Difficult, limited | Easy, multi-user |
| Querying | Needs custom programs | Powerful query language (SQL) |
| Security | Basic (OS-level file permissions) | Fine-grained (user/role/object privileges) |
| Backup & recovery | Manual, limited | Built-in mechanisms |
| Concurrency control | Not provided | Provided (locks, isolation levels) |
| Integrity constraints | Coded in each application | Declared once in the database |
| Data independence | None β structure changes break programs | Supported (see Section 10) |
| Relationships between data | Hard to represent | Easily represented (keys, relations) |
| Cost & complexity | Cheap, simple | More costly, more complex |
| Best for | Small, simple, single-user needs | Large, shared, critical data |
π‘ Takeaway: A file system stores data. A DBMS stores data and manages it β with rules, security, relationships and safety nets.
3. Advantages and Disadvantages of DBMS
3.1 Advantages β
| Advantage | What it means |
|---|---|
| Reduced redundancy | Data is stored once and shared, saving space |
| Improved consistency | One source of truth, so no conflicting copies |
| Data sharing | Many users/applications access the same data concurrently |
| Data integrity | Constraints (primary key, foreign key, check, not null) keep data valid |
| Security | Authentication, authorization and access control per user/role |
| Backup and recovery | Logs and checkpoints help restore data after failures |
| Data independence | Change storage or structure without rewriting applications |
| Concurrency control | Multiple transactions run safely at the same time |
| Efficient querying | Query languages, indexes and optimizers make retrieval fast |
| Standards enforcement | Naming, formats and rules are enforced centrally |
| Faster application development | Developers reuse DBMS features instead of re-coding them |
3.2 Disadvantages β
| Disadvantage | What it means |
|---|---|
| High cost | Software licences, powerful hardware, training and skilled staff |
| Complexity | Designing, configuring and tuning a DBMS takes expertise |
| Performance overhead | General-purpose features can be slower than a purpose-built file solution for very simple tasks |
| Single point of failure risk | If a centralized DBMS goes down, everything depending on it is affected |
| Larger size | Needs more memory and disk space |
| Vendor dependence | Migrating from one DBMS to another can be painful |
| Frequent upgrades | Needs maintenance, patches and administration |
π― When is a DBMS overkill? For a small, single-user, rarely-changing dataset, a simple file or spreadsheet may be enough.
4. Applications of DBMS
Almost every system that stores and retrieves data at scale uses a DBMS.
| Domain | How a DBMS is used |
|---|---|
| Banking | Accounts, balances, transactions, loans, ATM records |
| Airlines & Railways | Reservations, schedules, seat availability, ticketing |
| Universities / Education | Student records, courses, enrollment, results, library |
| E-commerce | Products, carts, orders, customers, payments, inventory |
| Telecommunications | Call records, billing, subscriber details |
| Healthcare | Patient records, appointments, prescriptions, lab reports |
| Manufacturing | Inventory, supply chain, orders, production tracking |
| Human Resources | Employee data, payroll, attendance, performance |
| Social Media | Profiles, posts, friendships, messages |
| Finance & Stock Markets | Trades, portfolios, market data |
| Government | Citizen records, tax, census, licences |
| Sales & Retail | Customers, products, purchases, loyalty programs |
5. Characteristics of a Database
The database approach differs from file processing in several key ways.
5.1 Core characteristics
-
Self-describing nature A database stores not only the data but also a description of its own structure (the catalog / metadata): table names, column names, data types, constraints. Programs can learn the structure by reading it.
-
Insulation between programs and data (Data Independence) Structure of data is stored in the catalog, not inside programs, so changing the structure doesn't force rewriting the programs.
-
Data abstraction Users see a simplified, conceptual view and are not bothered with how data is physically stored.
-
Support for multiple views of data Different users can see different customised subsets of the same database (e.g., a student sees their marks; a teacher sees the whole class).
-
Sharing of data and multi-user transaction processing Many users access data at the same time. The DBMS ensures concurrency control (no lost updates) and supports transactions.
-
Controlled redundancy Duplicates are minimised and, where kept deliberately (e.g., for speed), they're controlled and synchronized.
-
Data integrity Rules ensure data stays valid and correct.
-
Security Only authorised users can access or modify specific data.
-
Backup and recovery The system can recover to a consistent state after a crash.
5.2 Transactions and ACID (the heart of reliability)
A transaction is a logical unit of work (e.g., "transfer βΉ500 from A to B"). DBMSs guarantee ACID:
| Letter | Property | Meaning |
|---|---|---|
| A | Atomicity | All steps of the transaction happen, or none do |
| C | Consistency | The database moves from one valid state to another |
| I | Isolation | Concurrent transactions don't interfere with each other |
| D | Durability | Once committed, changes survive crashes |
6. Database Users and Database Administrators (DBA)
6.1 Types of database users
Database Users
βββ Naive / Parametric End Users
βββ Casual (Sophisticated) End Users
βββ Application Programmers
βββ Specialized / Sophisticated Users
βββ Stand-alone Users
| User type | Who they are | How they interact | Example |
|---|---|---|---|
| Naive (Parametric) end users | Non-technical users who use ready-made applications | Through forms and menus; they never write queries | Bank teller, ticket booking clerk, online shopper |
| Casual / Sophisticated end users | Occasional users who need different information each time | Write queries in a query language | Manager running ad-hoc reports |
| Application programmers | Developers who write programs that talk to the database | Embed SQL in programming languages | Backend developer |
| Specialized users | Advanced users building non-traditional applications | Complex custom queries, specialized data models | Data scientists, CAD / GIS system builders |
| Stand-alone users | Maintain personal databases using ready-made packages | Menu/GUI-based software | Someone maintaining a personal contacts or finance DB |
6.2 Database Administrator (DBA)
The DBA is the person (or team) with central control over the database system. Think of the DBA as the city planner and security chief of the database.
Key responsibilities:
- Schema definition β create the original database structure.
- Storage structure and access method definition β decide how data is stored and indexed.
- Schema and physical organization modification β change structure as needs evolve.
- Granting authorization β decide who can access what.
- Routine maintenance β backups, free-space management, performance monitoring.
- Security enforcement β prevent unauthorised access.
- Backup and recovery planning β ensure data can be restored after failures.
- Performance tuning β optimise queries, indexes and resources.
- Monitoring and user support β respond to user problems, track usage.
- Ensuring integrity β maintain data validity and consistency rules.
6.3 Other roles around a database (often mentioned alongside the DBA)
- Database designers β identify the data to be stored and choose structures to represent it (before the database is built).
- System analysts β determine the requirements of end users.
- Tool developers / Operators β build supporting tools and keep the system running.
7. Database Languages: DDL, DML, DQL, DCL, TCL
A DBMS provides different sub-languages for different jobs. In practice, they're all part of SQL (Structured Query Language).
SQL
βββ DDL β Define structure (CREATE, ALTER, DROP, TRUNCATE, RENAME)
βββ DML β Manipulate data (INSERT, UPDATE, DELETE)
βββ DQL β Query data (SELECT)
βββ DCL β Control access (GRANT, REVOKE)
βββ TCL β Control transactions (COMMIT, ROLLBACK, SAVEPOINT)
π Some textbooks include
SELECTinside DML. Treating it separately as DQL is common and clearer, so we do that here.
7.1 DDL β Data Definition Language
Defines or changes the structure (schema) of the database. DDL commands usually auto-commit (they can't be rolled back in most systems).
| Command | Purpose |
|---|---|
CREATE | Create a database object (table, view, indexβ¦) |
ALTER | Modify an existing object's structure |
DROP | Delete an object completely (structure + data) |
TRUNCATE | Remove all rows quickly, keep the table structure |
RENAME | Rename an object |
CREATE TABLE Student (
roll_no INT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
marks INT CHECK (marks BETWEEN 0 AND 100)
);
ALTER TABLE Student ADD email VARCHAR(100);
TRUNCATE TABLE Student;
DROP TABLE Student;
7.2 DML β Data Manipulation Language
Works with the data inside tables: adding, changing, removing rows.
| Command | Purpose |
|---|---|
INSERT | Add new rows |
UPDATE | Modify existing rows |
DELETE | Remove rows (can use WHERE) |
INSERT INTO Student (roll_no, name, marks) VALUES (101, 'Riya', 87);
UPDATE Student SET marks = 90 WHERE roll_no = 101;
DELETE FROM Student WHERE roll_no = 101;
7.3 DQL β Data Query Language
Used to retrieve data. It doesn't change the data.
SELECT name, marks FROM Student WHERE marks > 80 ORDER BY marks DESC;
7.4 DCL β Data Control Language
Controls who can do what (permissions).
| Command | Purpose |
|---|---|
GRANT | Give privileges to a user/role |
REVOKE | Take privileges back |
GRANT SELECT, INSERT ON Student TO teacher_user;
REVOKE INSERT ON Student FROM teacher_user;
7.5 TCL β Transaction Control Language
Manages transactions made by DML statements.
| Command | Purpose |
|---|---|
COMMIT | Permanently save the transaction's changes |
ROLLBACK | Undo changes since the last commit (or savepoint) |
SAVEPOINT | Set a point within a transaction to roll back to |
BEGIN;
UPDATE Account SET balance = balance - 500 WHERE acc_no = 'A';
SAVEPOINT after_debit;
UPDATE Account SET balance = balance + 500 WHERE acc_no = 'B';
COMMIT; -- or ROLLBACK TO after_debit; / ROLLBACK;
7.6 Quick comparison
| Language | Works on | Key commands | Can be rolled back? |
|---|---|---|---|
| DDL | Structure | CREATE, ALTER, DROP, TRUNCATE | Usually no (auto-commit) |
| DML | Data | INSERT, UPDATE, DELETE | Yes (until commit) |
| DQL | Data (read-only) | SELECT | N/A |
| DCL | Permissions | GRANT, REVOKE | Usually no (auto-commit) |
| TCL | Transactions | COMMIT, ROLLBACK, SAVEPOINT | N/A |
7.7 Advanced note: DELETE vs TRUNCATE vs DROP
| DELETE | TRUNCATE | DROP | |
|---|---|---|---|
| Category | DML | DDL | DDL |
| Removes | Selected/all rows | All rows | Entire table (structure + data) |
WHERE clause | β Yes | β No | β No |
| Rollback | β Possible | Usually β | Usually β |
| Speed | Slower (row by row, logged) | Faster | Fast |
8. Database Abstraction
8.1 The idea
A database holds a lot of complex detail (bits on disk, file layouts, indexes). Data abstraction means hiding the unnecessary complexity and showing users only what they need. It's like driving a car: you use the steering wheel and pedals without knowing how the engine's fuel injection works.
8.2 Three levels of abstraction
ββββββββββββββββββββββββββββββββββββββββββββββ
β VIEW LEVEL (what each user sees) β β highest, simplest
ββββββββββββββββββββββββββββββββββββββββββββββ€
β LOGICAL LEVEL (what data is stored, β
β and how it's related) β
ββββββββββββββββββββββββββββββββββββββββββββββ€
β PHYSICAL LEVEL (how data is stored) β β lowest, most detailed
ββββββββββββββββββββββββββββββββββββββββββββββ
| Level | Also called | Describes | Who works here |
|---|---|---|---|
| Physical level | Internal level | How data is actually stored: files, blocks, indexes, record formats | DBMS developers, DBA |
| Logical level | Conceptual level | What data is stored and the relationships among them: tables, columns, types, constraints | DBA, database designers, programmers |
| View level | External level | Only the part of the database relevant to a particular user | End users, applications |
8.3 Example
For a Student table:
- Physical level: a record is stored as a block of bytes with a pointer to the next record, using a B+ tree index on
roll_no. - Logical level:
Student(roll_no INT, name VARCHAR(50), marks INT)withroll_noas the primary key. - View level: A student sees only
nameandmarks; the admin office sees all columns; the accounts team sees fee details.
π‘ This three-level idea is formalised as the Three-Schema Architecture (next section).
9. Three-Schema Architecture
9.1 Purpose
Proposed by ANSI-SPARC, this architecture separates user applications from the physical database, achieving data independence and supporting multiple views.
9.2 The three schemas
External Level (View Level)
ββββββββββββ ββββββββββββ ββββββββββββ
β View 1 β β View 2 β β View n β β External schemas
ββββββ¬ββββββ ββββββ¬ββββββ ββββββ¬ββββββ
ββββββββββββββΌβββββββββββββ
β External/Conceptual Mapping
βΌ
Conceptual Level (Logical)
ββββββββββββββββββββββββ
β Conceptual Schema β β Whole database design
ββββββββββββ¬ββββββββββββ
β Conceptual/Internal Mapping
βΌ
Internal Level (Physical)
ββββββββββββββββββββββββ
β Internal Schema β β Storage details
ββββββββββββββββββββββββ
β
βΌ
Stored Database
| Schema | Level | What it describes |
|---|---|---|
| External schema(s) | External / View | Each user group's view: the part of the DB they care about, hiding the rest |
| Conceptual schema | Conceptual / Logical | The structure of the entire database for the community of users: entities, data types, relationships, constraints. Hides physical details |
| Internal schema | Internal / Physical | Physical storage structure: file organisation, indexes, access paths, record placement |
9.3 Mappings
The DBMS translates requests between levels using mappings:
- External β Conceptual mapping: converts a user's view into the conceptual structure.
- Conceptual β Internal mapping: converts the conceptual structure into physical storage structure.
When a query arrives in terms of a view, the DBMS maps it down: view β conceptual β internal β disk, and brings the result back up the same path.
9.4 Why it matters
- Supports multiple user views of the same data
- Provides data independence (Section 10)
- Gives security (users only see their view)
- Allows each level to change independently
10. Data Independence: Logical and Physical
10.1 Definition
Data independence is the ability to change the schema at one level without having to change the schema at the next higher level. It's the big payoff of the three-schema architecture.
10.2 Physical Data Independence
- The ability to change the internal (physical) schema without changing the conceptual schema (and hence not the external schemas or applications).
- Examples of physical changes:
- Moving data to a different disk or storage device
- Adding or changing an index
- Changing file organisation (e.g., heap to sorted)
- Changing compression or access path
- Only the conceptual/internal mapping needs updating.
- β Easier to achieve and widely supported.
10.3 Logical Data Independence
- The ability to change the conceptual (logical) schema without changing the external schemas or application programs.
- Examples of logical changes:
- Adding a new column or table
- Removing a column not used by existing views
- Splitting or merging tables
- Only the external/conceptual mapping needs updating.
- β οΈ Harder to achieve, because applications depend heavily on the logical structure. Often, only certain kinds of changes (like adding new attributes) are truly transparent.
10.4 Comparison
| Aspect | Physical Data Independence | Logical Data Independence |
|---|---|---|
| Changing | Internal schema | Conceptual schema |
| Should not affect | Conceptual schema (and above) | External schemas / applications |
| Typical change | New index, new storage device | Add/remove attribute or table |
| Mapping updated | ConceptualβInternal | ExternalβConceptual |
| Difficulty | Easier | Harder |
10.5 Real-world analogy
Imagine a restaurant menu (external view). The kitchen can switch suppliers or cooking equipment (physical change) without changing the menu. Adding a new dish to the kitchen's recipe book (logical change) may or may not need the menu reprinted, depending on whether customers need to see it.
11. Database Instances and Schemas
11.1 Database Schema β the blueprint
The schema is the overall design/structure of the database: table names, columns, data types, relationships, constraints. It is defined during design and changes rarely.
- Also called the intension of the database.
- Think of it as the architectural plan of a house.
-- This is a schema (structure):
Student(roll_no INT, name VARCHAR(50), marks INT)
Types of schema (mirroring the three levels):
- Physical schema β how data is physically stored
- Logical schema β logical structure (tables, relationships)
- View / sub-schema β user-specific views
11.2 Database Instance β the snapshot
The instance is the actual data stored in the database at a particular moment in time. It changes frequently as data is inserted, updated or deleted.
- Also called the extension (or database state / snapshot) of the database.
- Think of it as the people currently living in the house.
-- This is an instance (data at this moment):
| roll_no | name | marks |
|---------|-------|-------|
| 101 | Riya | 87 |
| 102 | Aman | 74 |
11.3 Schema vs Instance
| Aspect | Schema | Instance |
|---|---|---|
| Meaning | Structure / design | Data at a moment in time |
| Also called | Intension, database description | Extension, database state, snapshot |
| Changes | Rarely | Frequently |
| Defined by | DDL | DML |
| Analogy | Blueprint of a building | People inside it right now |
| Stored in | Data dictionary / catalog | Database files |
11.4 Related terms
- Empty state: when only the schema exists and no data has been loaded.
- Initial state: when the database is first populated with data.
- Valid state: a state that satisfies all constraints in the schema.
- Schema evolution: changing the schema over time (e.g., adding a column). The DBMS ensures existing data stays consistent.
12. Metadata and Data Dictionary
12.1 What is Metadata?
Metadata means "data about data." It describes the structure, properties and rules of the actual data, rather than being the data itself.
Examples of metadata:
- Table and column names
- Data types and sizes
- Primary and foreign keys
- Constraints and indexes
- User accounts and privileges
- Storage locations and file sizes
- Creation/modification dates
Think of a library: the books are the data; the catalog cards (title, author, shelf location) are the metadata.
12.2 Data Dictionary (System Catalog)
The data dictionary (also called the system catalog) is a repository inside the DBMS that stores all the metadata. The DBMS consults it constantly β for example, to check whether a table exists before running a query.
What it typically contains:
| Category | Information stored |
|---|---|
| Schema info | Tables, views, columns, data types, sizes |
| Constraints | Primary keys, foreign keys, unique, check, not null |
| Indexes | Which indexes exist, on which columns |
| Users & security | User accounts, roles, privileges |
| Storage info | Files, tablespaces, allocation |
| Statistics | Row counts, value distributions (used by the query optimizer) |
| Views, procedures, triggers | Their definitions |
12.3 Types of data dictionary
| Type | Description |
|---|---|
| Active (integrated) data dictionary | Automatically updated by the DBMS whenever the structure changes; always consistent. Most modern DBMSs use this. |
| Passive (non-integrated) data dictionary | A separate tool that must be updated manually; may become out of sync. |
12.4 Example
In many systems you can query the catalog just like normal tables:
-- MySQL / PostgreSQL / SQL Server style
SELECT table_name, column_name, data_type
FROM information_schema.columns
WHERE table_name = 'Student';
12.5 Why it matters
- Lets the DBMS validate and optimise queries
- Supports self-describing nature of the database
- Helps the DBA manage and audit the system
- Provides documentation of the database
13. DBMS Components and Architecture
13.1 Components of a database system
A complete database environment has five major components:
| Component | Description |
|---|---|
| Hardware | Physical devices: servers, storage disks, memory, network |
| Software | The DBMS itself, plus the OS, network software and application programs |
| Data | The actual data and its metadata |
| Users | End users, programmers, DBAs |
| Procedures | Rules and instructions for using and managing the system (backup steps, login, etc.) |
13.2 Internal structure of a DBMS (functional components)
Users / Applications
β (SQL queries, application programs)
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β QUERY PROCESSOR β
β ββββββββββββββββ ββββββββββββββββ βββββββββββββββββ β
β β DDL β β DML Compiler β β Query β β
β β Interpreter β β & Parser β β Optimizer β β
β ββββββββββββββββ ββββββββββββββββ βββββββββββββββββ β
β βββββββββββββββββββββββββ β
β β Query Evaluation β β
β β Engine β β
β βββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββ
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β STORAGE MANAGER β
β Authorization & Integrity Manager β
β Transaction Manager β
β File Manager β
β Buffer Manager β
βββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββ
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β DISK STORAGE β
β Data files β Data dictionary β Indices β Logs β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
13.2.1 Query Processor
Understands and executes user requests.
| Part | Role |
|---|---|
| DDL interpreter | Interprets DDL statements and records definitions in the data dictionary |
| DML compiler | Translates DML/query statements into low-level instructions (an evaluation plan) |
| Query optimizer | Picks the most efficient execution plan among many possible ones |
| Query evaluation engine | Executes the low-level instructions generated by the DML compiler |
13.2.2 Storage Manager
The interface between the data on disk and the query processor.
| Part | Role |
|---|---|
| Authorization & integrity manager | Checks permissions and enforces integrity constraints |
| Transaction manager | Ensures the database stays consistent despite failures and concurrent transactions (ACID) |
| File manager | Manages disk space allocation and the data structures used to store information |
| Buffer manager | Fetches data from disk into main memory (cache) and decides what to keep in memory |
13.2.3 Disk storage (data structures)
- Data files β the database itself
- Data dictionary β metadata
- Indices β structures for fast lookup
- Logs β records of transactions for recovery
13.3 DBMS Architecture (tiers)
How the DBMS is arranged relative to its users and applications.
1-Tier Architecture
- User works directly with the database on the same machine.
- No separate application layer.
- Used for local development, learning, or simple personal databases.
[ User + Application + DBMS + Database ] (all on one machine)
2-Tier Architecture (ClientβServer)
- Client (application/UI) talks directly to the database server.
- Communication via APIs such as ODBC / JDBC.
- Simple and fast, but the client holds business logic and the database is exposed more directly.
Client (Application) ββββββββΊ Database Server (DBMS)
3-Tier Architecture
- Adds a middle application server layer between the client and the database.
- Presentation tier β UI (browser/app)
- Application (business logic) tier β processing rules
- Database tier β DBMS and data
- More secure, scalable and maintainable; the standard for web applications.
Client (Browser/App) ββββΊ Application Server ββββΊ Database Server
(Presentation) (Business Logic) (Data)
| 1-Tier | 2-Tier | 3-Tier | |
|---|---|---|---|
| Layers | 1 | 2 | 3 |
| Client talks to | DB directly (same machine) | DB server directly | Application server only |
| Security | Low | Medium | High |
| Scalability | Poor | Moderate | Good |
| Typical use | Local/personal | Small desktop apps | Web / enterprise apps |
14. Types of DBMS: Hierarchical, Network, Relational, Object-Oriented
DBMSs are categorised by the data model they use to organise data.
14.1 Hierarchical DBMS
Idea: Data is organised as a tree with a single root; each child has exactly one parent (one-to-many relationships).
Department
/ \
Employee Project
|
Dependent
- Relationships: 1 : N only
- Access: navigational, starting from the root and following pointers
- Example: IBM IMS; also seen in file systems and XML structures
| Pros | Cons |
|---|---|
| Simple, easy to understand | Cannot represent many-to-many relationships naturally |
| Very fast for parent-to-child access | Rigid structure; hard to restructure |
| Good integrity (child needs a parent) | Redundancy when a child must belong to many parents |
| Deleting a parent deletes its children |
14.2 Network DBMS
Idea: Data is organised as a graph; a child (member) can have multiple parents (owners), allowing many-to-many relationships.
Student ββββ
ββββΊ Enrollment ββββ Course
Student ββββ
- Model: records connected through sets (ownerβmember) using pointers
- Standard: CODASYL (Conference on Data Systems Languages)
- Example: IDMS, Integrated Data Store
| Pros | Cons |
|---|---|
| Handles many-to-many relationships | Complex structure and pointer management |
| Flexible and efficient access paths | Hard to design and maintain |
| Better than hierarchical for complex relationships | Changing structure requires changing programs (low data independence) |
14.3 Relational DBMS (RDBMS)
Idea: Data is organised into tables (relations) made of rows (tuples) and columns (attributes). Proposed by E. F. Codd (1970). Relationships are made using keys (primary/foreign), not physical pointers.
Student Course
βββββββββββ¬ββββββββ¬ββββββββ βββββββββββ¬βββββββββββββ
β roll_no β name β c_id β βββΊ β c_id β title β
βββββββββββΌββββββββΌββββββββ€ βββββββββββΌβββββββββββββ€
β 101 β Riya β C1 β β C1 β Databases β
β 102 β Aman β C2 β β C2 β Networks β
βββββββββββ΄ββββββββ΄ββββββββ βββββββββββ΄βββββββββββββ
- Language: SQL
- Strong foundation: set theory and relational algebra
- Examples: MySQL, PostgreSQL, Oracle, SQL Server, SQLite
| Pros | Cons |
|---|---|
| Simple, intuitive table structure | Can be slower for very complex/hierarchical data |
| High data independence | Mapping object-oriented application data to tables can be awkward (impedance mismatch) |
| Powerful declarative querying (SQL) | Not ideal for multimedia / highly complex data types |
| Strong integrity, ACID support | Scaling horizontally can be harder |
| Most widely used and standardised |
14.4 Object-Oriented DBMS (OODBMS)
Idea: Data is stored as objects, just like in object-oriented programming β with attributes (data) and methods (behaviour), supporting classes, inheritance, encapsulation and polymorphism.
Class Student
Attributes: roll_no, name, marks
Methods: calculateGrade(), updateMarks()
- Use cases: CAD/CAM, multimedia, engineering, scientific and telecom applications with complex data
- Examples: ObjectDB, db4o, Versant (object-oriented systems)
| Pros | Cons |
|---|---|
| Handles complex data types naturally | Less standardised than relational |
| No impedance mismatch with OO languages | Smaller user community and tooling |
| Supports inheritance and reuse | Steeper learning curve |
| Good for complex relationships | Query power is not as universal as SQL |
14.5 Comparison Summary
| Feature | Hierarchical | Network | Relational | Object-Oriented |
|---|---|---|---|---|
| Structure | Tree | Graph | Tables | Objects/classes |
| Relationships | 1 : N | M : N | Via keys (any) | Via object references |
| Links through | Parentβchild pointers | Ownerβmember sets | Primary/foreign keys | Object identifiers (OIDs) |
| Data independence | Low | Low | High | Moderate |
| Query language | Navigational | Navigational | SQL (declarative) | OQL / method-based |
| Flexibility | Low | Medium | High | High (for complex data) |
| Example | IBM IMS | IDMS | MySQL, Oracle | ObjectDB |
15. Centralized and Distributed Databases
15.1 Centralized Database
Definition: All data is stored and managed at a single location (one central computer/server). Users access it remotely or locally via terminals/clients.
User A ββ
User B ββΌβββΊ [ Central DB Server ]
User C ββ
| Pros | Cons |
|---|---|
| Simple to design, manage and secure | Single point of failure β if it goes down, everything stops |
| Easy to maintain consistency | Heavy load on one server; can become a bottleneck |
| Lower cost for small/medium scale | Slow access for users far from the server |
| Easier backup and administration | Limited scalability |
15.2 Distributed Database
Definition: Data is stored across multiple physical locations (sites/nodes) connected by a network, but appears to users as one logical database. A Distributed DBMS (DDBMS) manages it.
βββ Site 1 (Delhi) βββ
User βββΌββ Site 2 (Mumbai) ββΌββ Network ββ Logical single database
βββ Site 3 (Pune) ββββ
Types
| Type | Description |
|---|---|
| Homogeneous | All sites use the same DBMS software; simpler to manage |
| Heterogeneous | Sites may use different DBMS products, data models or OSs; needs translation/integration |
Key techniques
- Fragmentation β splitting a table into pieces stored at different sites:
- Horizontal (by rows), Vertical (by columns), Mixed/Hybrid
- Replication β keeping copies of data at multiple sites for availability and speed
- Full, partial or no replication
- Allocation β deciding which fragments/copies live at which site
Transparency (users shouldn't notice the distribution)
- Location transparency β users don't need to know where data is
- Fragmentation transparency β users don't know data is split
- Replication transparency β users don't know copies exist
- Failure/Concurrency transparency β system hides failures and conflicts
| Pros | Cons |
|---|---|
| Higher availability and reliability (no single point of failure) | Complex design, management and query processing |
| Faster local access (data close to users) | Higher cost (network, software, administration) |
| Scalable β add more sites easily | Keeping replicas consistent is hard |
| Reflects organisational structure (branches) | Security is harder across many sites |
| Parallel processing improves performance | Distributed transaction handling (e.g., two-phase commit) is complex |
15.3 Centralized vs Distributed
| Aspect | Centralized | Distributed |
|---|---|---|
| Data location | Single site | Multiple sites |
| Failure impact | High (single point of failure) | Lower (other sites keep working) |
| Performance | Can bottleneck | Better via local access and parallelism |
| Complexity | Low | High |
| Cost | Lower | Higher |
| Scalability | Limited | High |
| Consistency management | Simple | Complex |
| Example | A single college's local DB | A bank with branch databases across cities |
β‘ Quick Revision Cheat Sheet
One-liners to remember
- DBMS = software to define, store, retrieve and protect shared data.
- File system problems β redundancy, inconsistency, poor security, no concurrency control.
- ACID β Atomicity, Consistency, Isolation, Durability.
- Users β Naive, Casual, Application programmers, Specialized, Stand-alone. DBA = central controller.
- Languages β DDL (structure), DML (data changes), DQL (SELECT), DCL (permissions), TCL (transactions).
- Abstraction levels β Physical (how stored) β Logical (what stored) β View (what user sees).
- Three-Schema Architecture β External, Conceptual, Internal + mappings between them.
- Physical independence = change storage without touching conceptual schema.
- Logical independence = change conceptual schema without touching views/apps (harder).
- Schema = design (rarely changes); Instance = data right now (changes often).
- Metadata = data about data; stored in the data dictionary / system catalog.
- Query Processor = DDL interpreter + DML compiler + optimizer + evaluation engine.
- Storage Manager = authorization/integrity + transaction + file + buffer managers.
- Tiers β 1-tier (local), 2-tier (clientβserver), 3-tier (client β app server β DB).
- DBMS types β Hierarchical (tree), Network (graph), Relational (tables), Object-oriented (objects).
- Centralized = one site; Distributed = many sites, one logical view (fragmentation + replication).
π€ Commonly asked questions (from these topics)
- What is a DBMS and how is it different from a file system?
- List the advantages and disadvantages of using a DBMS.
- Explain the three levels of data abstraction.
- Explain the Three-Schema Architecture with a diagram.
- Differentiate between logical and physical data independence.
- What is the difference between a schema and an instance?
- What is metadata? What does a data dictionary store?
- Differentiate DDL, DML, DQL, DCL and TCL with examples.
- What is the role of a DBA?
- Compare hierarchical, network, relational and object-oriented DBMS.
- Compare centralized and distributed databases.
- Explain the components of a DBMS (query processor and storage manager).
#Data Models and the Relational Model#
A friendly, start-to-advanced guide to how databases model data, and how the relational model works: tables, keys, NULLs and integrity rules. Read it top to bottom once, then use the Quick Revision section before exams or interviews.
π Table of Contents
- Introduction to Data Models
- Hierarchical Data Model
- Network Data Model
- Relational Data Model
- Object-Oriented Data Model
- Entity-Based and Record-Based Models
- Relation, Tuple, Attribute and Domain
- Degree and Cardinality of a Relation
- Relation Schema and Relation Instance
- Keys: Super, Candidate, Primary, Alternate and Foreign
- Composite and Natural Keys
- Surrogate Keys
- NULL Values and Their Meaning
- Integrity Constraints
- Entity Integrity and Referential Integrity
- Domain and Key Constraints
- Relational Database Properties
- Quick Revision Cheat Sheet
1. Introduction to Data Models
1.1 What is a data model?
A data model is a collection of concepts and rules for describing the structure of a database: what data is stored, how pieces of data relate to each other, and what rules the data must obey.
Think of it as a language for describing data. Just as an architect's blueprint conventions let everyone read a building plan the same way, a data model lets everyone read a database design the same way.
1.2 The three ingredients of a data model
| Ingredient | Meaning | Example (relational model) |
|---|---|---|
| Structure | How data is organised | Tables made of rows and columns |
| Operations | What you can do with the data | Select, project, join, insert, update, delete |
| Constraints | Rules the data must follow | Primary key, foreign key, not null, check |
1.3 Categories of data models
| Category | Level | Describes | Examples |
|---|---|---|---|
| High-level (conceptual) | Close to how users think | Entities, attributes, relationships | ER model |
| Representational (implementation) | Between user view and storage | Records, tables, objects | Hierarchical, Network, Relational |
| Low-level (physical) | Close to how computers store | Files, indexes, access paths | B+ tree, file organisation |
1.4 Roadmap of the models in these notes
Data Models
βββ Entity-based (conceptual) β ER model
βββ Record-based (implementation)
β βββ Hierarchical model (tree)
β βββ Network model (graph)
β βββ Relational model (tables)
βββ Object-based
βββ Object-Oriented model (objects)
2. Hierarchical Data Model
2.1 Idea
Data is organised as a tree. There is one root, and every other record (called a child) has exactly one parent. A parent can have many children.
Department (root)
/ \
Employee Project
| |
Dependent Task
2.2 Key points
- Relationship type: one-to-many (1 : N) only
- Basic unit: a record; related records are linked using parent-child pointers
- Access: navigational β you start at the root and follow pointers down the tree
- Example systems: IBM IMS (Information Management System)
- Real-world look-alikes: folder structure on a computer, XML/JSON-like nesting
2.3 Strengths and weaknesses
| β Strengths | β Weaknesses |
|---|---|
| Simple and easy to understand | Cannot naturally model many-to-many relationships |
| Very fast for parent-to-child lookups | A child with two parents must be duplicated (redundancy) |
| Built-in integrity: a child cannot exist without its parent | Deleting a parent deletes all its children |
| Rigid structure; hard to restructure; programs depend on the structure | |
| Querying "upwards" or across branches is awkward |
3. Network Data Model
3.1 Idea
Data is organised as a graph. A record can have more than one parent, so many-to-many relationships can be represented directly.
Student 1 ββ ββ Course A
βββΊ Enrollment βββ€
Student 2 ββ ββ Course B
3.2 Key terms
| Term | Meaning |
|---|---|
| Record type | A kind of record (like Student) |
| Set | A named one-to-many link between an owner record type and a member record type |
| Owner / Member | The "one" side / the "many" side of a set |
| Pointer / link | A physical connection between records |
- Defined by the CODASYL (Conference on Data Systems Languages) committee, so it is sometimes called the CODASYL model.
- A many-to-many relationship is built using two one-to-many sets and a connecting (junction) record, like
Enrollmentabove. - Example systems: IDMS (Integrated Database Management System)
3.3 Strengths and weaknesses
| β Strengths | β Weaknesses |
|---|---|
| Represents many-to-many relationships naturally | Complex to design and maintain (a tangled web of pointers) |
| Flexible access paths; efficient navigation | Programmer must know the physical structure; low data independence |
| Reduces redundancy compared to the hierarchical model | Structural change usually means changing programs |
| No simple, declarative query language |
3.4 Hierarchical vs Network at a glance
| Hierarchical | Network | |
|---|---|---|
| Shape | Tree | Graph |
| Parents per child | Exactly one | One or more |
| Relationships | 1 : N | M : N (via sets) |
| Complexity | Lower | Higher |
4. Relational Data Model
4.1 Idea
Proposed by E. F. Codd in 1970. Data is organised into relations, which are simply tables made of rows and columns. Links between tables are made by matching values (keys), not by physical pointers.
Student Department
βββββββββββ¬ββββββββ¬ββββββββββ βββββββββββ¬ββββββββββββββ
β roll_no β name β dept_id β ββββββββββΊ β dept_id β dept_name β
βββββββββββΌββββββββΌββββββββββ€ βββββββββββΌββββββββββββββ€
β 101 β Riya β D1 β β D1 β Computer Sc.β
β 102 β Aman β D2 β β D2 β Electronics β
βββββββββββ΄ββββββββ΄ββββββββββ βββββββββββ΄ββββββββββββββ
4.2 Why it won
- Simple and intuitive: everything is a table
- Strong theory: built on set theory and relational algebra / calculus
- Declarative querying: you say what you want (SQL), not how to get it
- High data independence: physical storage can change without breaking queries
- Rich constraints: integrity is declared once and enforced by the DBMS
- Examples: MySQL, PostgreSQL, Oracle, SQL Server, SQLite
4.3 Strengths and weaknesses
| β Strengths | β Weaknesses |
|---|---|
| Simple, uniform structure | Can be slow for deeply connected data (many joins) |
| Powerful, standard language (SQL) | Impedance mismatch with object-oriented programs |
| Strong integrity and ACID transactions | Awkward for complex data like multimedia or nested objects |
| Mature tools and wide adoption | Horizontal scaling can be harder |
5. Object-Oriented Data Model
5.1 Idea
Data is stored as objects, just like in object-oriented programming. An object bundles attributes (data) and methods (behaviour) together.
Class: Student
Attributes: roll_no, name, marks
Methods: calculateGrade(), updateMarks()
5.2 Core concepts
| Concept | Meaning |
|---|---|
| Object | An instance of a class with its own state and an object identifier (OID) |
| Class | Template describing attributes and methods of similar objects |
| Encapsulation | Data and the methods that act on it are packaged together |
| Inheritance | A subclass reuses and extends a superclass (e.g., GradStudent inherits from Student) |
| Polymorphism | The same method name can behave differently for different classes |
| Object identity (OID) | A unique, system-generated identity that does not depend on attribute values |
| Complex objects | Objects can contain other objects or collections |
5.3 Strengths and weaknesses
| β Strengths | β Weaknesses |
|---|---|
| Handles complex data types naturally (CAD, multimedia, scientific data) | Less standardised than relational |
| No impedance mismatch with OO programming languages | Smaller ecosystem and fewer tools |
| Supports reuse through inheritance | Steeper learning curve |
| Direct navigation through object references | Ad-hoc querying is not as universal as SQL |
π Many modern relational systems have borrowed ideas from this model (for example, user-defined types), blending both worlds. These are often called object-relational systems.
6. Entity-Based and Record-Based Models
Data models are also grouped by how they look at the data.
6.1 Entity-based (conceptual) models
- Describe data in terms of entities (real-world things), their attributes and relationships.
- Focus on meaning, close to how humans see the world.
- Used in the design phase, before choosing a particular DBMS.
- Main example: the Entity-Relationship (ER) model.
| ER concept | Example |
|---|---|
| Entity | Student, Course |
| Attribute | name, roll_no |
| Relationship | Student enrolls in Course |
6.2 Record-based (implementation) models
- Describe data in terms of fixed-format records of several types; each record type has a fixed number of fields.
- Closer to how the DBMS organises data.
- Examples: Hierarchical, Network and Relational models.
6.3 Side-by-side
| Aspect | Entity-based | Record-based |
|---|---|---|
| Viewpoint | Real-world things and relationships | Records / tables of data |
| Level | Conceptual (high-level) | Logical / implementation |
| Used for | Designing the database | Implementing the database |
| Examples | ER model | Hierarchical, Network, Relational |
| Typical output | ER diagram | Table definitions / schema |
6.4 How they connect
A typical workflow is to design with an entity-based model (ER diagram) and then convert it into a record-based model (relational tables) for the chosen DBMS.
Real-world requirements β ER diagram β Relational schema β Physical database
(entity-based) (record-based)
7. Relation, Tuple, Attribute and Domain
These are the four building blocks of the relational model. Each has a formal name and an everyday name.
| Formal term | Everyday term | Meaning |
|---|---|---|
| Relation | Table | A set of tuples with the same attributes |
| Tuple | Row / record | One entry in the relation |
| Attribute | Column / field | A named property of the relation |
| Domain | Allowed values | The set of legal values an attribute may take |
7.1 Example
Relation: Student
| roll_no | name | age | dept |
|---|---|---|---|
| 101 | Riya | 20 | CSE |
| 102 | Aman | 21 | ECE |
| 103 | Sara | 20 | CSE |
- Relation: the whole
Studenttable - Tuple:
(102, Aman, 21, ECE) - Attribute:
name,age, β¦ - Domain of
age: for example, whole numbers from 15 to 60 - Domain of
dept: for example,{CSE, ECE, ME, CE}
7.2 More about domains
- A domain is a set of atomic (indivisible) values of the same kind.
- Each attribute draws its values from one domain.
- A domain has a name, data type and format, e.g.,
Phone_Number: 10-digit string. - Two attributes can share a domain (e.g.,
birth_dateandjoin_dateboth use Date) but a domain match doesn't mean the values are meaningfully comparable.
7.3 Atomic values
In the relational model each cell holds one single value (no lists or nested tables). This is the idea behind First Normal Form (1NF).
| β Not atomic | β Atomic |
|---|---|
phones = "98xxx, 97xxx" | One phone number per row (or a separate table) |
8. Degree and Cardinality of a Relation
| Term | Meaning | How to count |
|---|---|---|
| Degree (arity) | Number of attributes in the relation | Count the columns |
| Cardinality | Number of tuples in the relation | Count the rows |
8.1 Example
| roll_no | name | age | dept |
|---|---|---|---|
| 101 | Riya | 20 | CSE |
| 102 | Aman | 21 | ECE |
| 103 | Sara | 20 | CSE |
- Degree = 4 (roll_no, name, age, dept)
- Cardinality = 3 (three rows)
8.2 Things to remember
- Degree is a property of the schema β it stays fixed unless you alter the table.
- Cardinality is a property of the instance β it changes whenever you insert or delete rows.
- Relation types by degree: unary (1), binary (2), ternary (3), n-ary (n).
- β οΈ In database design (ER model), "cardinality" can also mean the relationship ratio (1:1, 1:N, M:N). In this context, it means row count.
9. Relation Schema and Relation Instance
9.1 Relation schema β the blueprint
A relation schema gives the name of the relation and its attributes (with domains). It changes rarely.
Student (roll_no: INT, name: VARCHAR(50), age: INT, dept: VARCHAR(10))
Written in short form: R(A1, A2, β¦, An), and the degree of R is n.
9.2 Relation instance β the snapshot
A relation instance (or relation state) is the set of tuples in the relation at a particular moment. It changes often.
| roll_no | name | age | dept |
|---|---|---|---|
| 101 | Riya | 20 | CSE |
| 102 | Aman | 21 | ECE |
9.3 Database schema vs instance
- Relational database schema = a set of relation schemas plus integrity constraints.
- Relational database instance = the set of all relation instances at a moment in time.
9.4 Comparison
| Aspect | Relation Schema | Relation Instance |
|---|---|---|
| Meaning | Structure (name, attributes, domains) | Actual rows at a point in time |
| Also called | Intension | Extension / state |
| Changes | Rarely | Frequently |
| Defined with | DDL (CREATE TABLE) | DML (INSERT, UPDATE, DELETE) |
| Analogy | Blueprint of a form | Filled-in forms |
9.5 Valid states
Not every set of tuples is allowed. A valid state is an instance that satisfies all constraints in the schema.
10. Keys: Super, Candidate, Primary, Alternate and Foreign
10.1 Why keys?
In a relation, tuples are a set, so no two can be identical. Keys are the attributes that uniquely identify each tuple, and they are also how relations are linked.
We'll use this table throughout:
Student(roll_no, email, name, phone, dept_id)
| roll_no | name | phone | dept_id | |
|---|---|---|---|---|
| 101 | riya@x.com | Riya | 98111 | D1 |
| 102 | aman@x.com | Aman | 98222 | D2 |
| 103 | sara@x.com | Sara | 98333 | D1 |
Assume roll_no and email are each unique for every student, and name can repeat.
10.2 Super key
A set of one or more attributes that uniquely identifies a tuple. It may contain extra, unnecessary attributes.
{roll_no}β{email}β{roll_no, name}β (extra attributename){roll_no, email, name, phone}β{name}β (names can repeat)
Every superset of a key is also a super key.
10.3 Candidate key
A minimal super key: unique, and if you remove any attribute it stops being unique.
{roll_no}β and{email}β are candidate keys.{roll_no, name}β β not minimal, becauseroll_noalone is enough.
A relation can have several candidate keys.
10.4 Primary key
The one candidate key chosen by the designer to identify tuples.
- Must be unique
- Must be NOT NULL
- Only one primary key per table (it may be made of several attributes)
- Usually stable (rarely changes) and compact
Here, we choose roll_no as the primary key.
10.5 Alternate key
Any candidate key that was not chosen as the primary key. They are still unique, just not the "official" identifier.
- Here,
emailis an alternate key. - Enforced in SQL using
UNIQUE(often withNOT NULL).
10.6 Foreign key
An attribute (or set of attributes) in one relation that refers to the primary key (or a candidate key) of another (or the same) relation. It creates the link between tables.
Student Department
βββββββββββ¬βββββββββββββ βββββββββββ¬βββββββββββββββ
β roll_no β dept_id(FK)β βββββΊ β dept_id β dept_name β
β 101 β D1 β β D1 (PK) β Computer Sc. β
β 102 β D2 β β D2 (PK) β Electronics β
βββββββββββ΄βββββββββββββ βββββββββββ΄βββββββββββββββ
- The table holding the foreign key is the referencing (child) table.
- The table being pointed to is the referenced (parent) table.
- A foreign key value must either match an existing value in the parent or be NULL (if allowed).
- A foreign key may be repeated and may be NULL, unlike a primary key.
- A foreign key can refer to the same table (self-reference), e.g.,
manager_idin anEmployeetable.
10.7 All keys in SQL
CREATE TABLE Department (
dept_id VARCHAR(5) PRIMARY KEY,
dept_name VARCHAR(50) NOT NULL
);
CREATE TABLE Student (
roll_no INT PRIMARY KEY, -- primary key
email VARCHAR(100) UNIQUE NOT NULL, -- alternate key
name VARCHAR(50) NOT NULL,
phone VARCHAR(15),
dept_id VARCHAR(5),
FOREIGN KEY (dept_id) REFERENCES Department(dept_id) -- foreign key
);
10.8 Comparison table
| Key | Unique? | Minimal? | NULL allowed? | How many per table? |
|---|---|---|---|---|
| Super key | β | β (not necessarily) | β | Many |
| Candidate key | β | β | Not in the chosen primary key | One or more |
| Primary key | β | β | β No | Exactly one |
| Alternate key | β | β | Depends on definition (usually not null) | Zero or more |
| Foreign key | β (can repeat) | β | β Yes (if allowed) | Zero or more |
10.9 How keys relate to each other
Super keys
βββ Candidate keys (minimal super keys)
βββ Primary key (the chosen one)
βββ Alternate keys (the rest)
Foreign key β points to a primary/candidate key of another table
10.10 A handy counting fact
If a relation with n attributes has a single-attribute candidate key, the number of super keys is 2^(nβ1). For example, a relation with 4 attributes where only one attribute is a candidate key has 2Β³ = 8 super keys.
11. Composite and Natural Keys
11.1 Composite key
A key made of two or more attributes that together identify a tuple uniquely. No single attribute in it is enough on its own.
Example: in Enrollment(roll_no, course_id, grade), a student can enroll in many courses and a course has many students, but each (roll_no, course_id) pair appears once.
| roll_no | course_id | grade |
|---|---|---|
| 101 | C1 | A |
| 101 | C2 | B |
| 102 | C1 | A |
CREATE TABLE Enrollment (
roll_no INT,
course_id VARCHAR(5),
grade CHAR(2),
PRIMARY KEY (roll_no, course_id),
FOREIGN KEY (roll_no) REFERENCES Student(roll_no),
FOREIGN KEY (course_id) REFERENCES Course(course_id)
);
- Each part of this composite key is itself a foreign key.
- Very common in junction (bridge) tables that implement many-to-many relationships.
11.2 Natural key
A key that comes from the real-world data itself, with real business meaning.
Examples: email, passport number, ISBN of a book, vehicle registration number, national ID.
| β Pros | β Cons |
|---|---|
| Meaningful to users | Real-world values can change (email, name) |
| No extra column needed | Might be long (slower joins, larger indexes) |
| Might be reused or missing for some records | |
| May expose sensitive information |
11.3 Composite vs Natural β not opposites
These describe different things: composite is about how many attributes form the key; natural is about where the key comes from. A key can be both, e.g., (country_code, national_id) is a composite natural key.
12. Surrogate Keys
12.1 Definition
A surrogate key is an artificial identifier created by the system with no business meaning. Its only job is to identify the row.
Typical forms:
- Auto-incrementing integers (
1, 2, 3, β¦) - Sequences
- UUID / GUID values
CREATE TABLE Customer (
customer_id INT AUTO_INCREMENT PRIMARY KEY, -- surrogate key
email VARCHAR(100) UNIQUE NOT NULL, -- natural key kept as unique
name VARCHAR(50)
);
(Syntax for auto-increment differs by DBMS: AUTO_INCREMENT, SERIAL, IDENTITY, sequences.)
12.2 Why use one?
- Stability: never changes, even if business data changes
- Compactness: a small integer is faster to index and join than long text
- Uniqueness guaranteed by the system
- Simpler foreign keys, especially compared with composite keys
- Privacy: doesn't reveal real-world information
12.3 Drawbacks
- No meaning: you can't tell what a row is from its key
- Extra column that is not part of the real data
- Duplicates are still possible unless the natural key is also protected with a
UNIQUEconstraint - Values may have gaps (e.g., after rollbacks or deletions) β gaps are normal and harmless
- Random UUIDs can be larger and less index-friendly than sequential integers
β Good practice: use a surrogate primary key and keep a
UNIQUEconstraint on the natural key so the real-world rule is still enforced.
12.4 Natural vs Surrogate
| Aspect | Natural Key | Surrogate Key |
|---|---|---|
| Source | Real-world data | Generated by the system |
| Business meaning | Yes | No |
| Stability | May change | Never changes |
| Size | Can be large | Usually small |
| Example | Email, ISBN | customer_id = 1042 |
| Visible to users | Often | Usually hidden |
13. NULL Values and Their Meaning
13.1 What is NULL?
NULL is a special marker meaning "no value is present." It is not zero, not an empty string, and not a space.
| Value | Means |
|---|---|
0 | The number zero (a real value) |
'' | An empty string (a real value, just empty) |
NULL | No value / unknown / not applicable |
13.2 What can NULL mean? (Why it is tricky)
The same marker is used for different situations:
| Meaning | Example |
|---|---|
| Unknown (value exists but isn't known) | A student's date of birth is not recorded yet |
| Not applicable | spouse_name for an unmarried person |
| Missing / withheld | A person chose not to give their phone number |
| Not yet assigned | A new employee with no manager assigned yet |
Because one marker covers all these, you cannot tell which meaning applies just by seeing NULL.
13.3 NULL behaves differently: three-valued logic
Normal logic has TRUE and FALSE. With NULL, SQL uses three values: TRUE, FALSE, UNKNOWN.
NULL = NULL -- UNKNOWN (not TRUE!)
NULL <> 5 -- UNKNOWN
5 + NULL -- NULL
Truth tables
| A | B | A AND B | A OR B |
|---|---|---|---|
| TRUE | UNKNOWN | UNKNOWN | TRUE |
| FALSE | UNKNOWN | FALSE | UNKNOWN |
| UNKNOWN | UNKNOWN | UNKNOWN | UNKNOWN |
| A | NOT A |
|---|---|
| TRUE | FALSE |
| FALSE | TRUE |
| UNKNOWN | UNKNOWN |
A WHERE clause keeps a row only if the condition is TRUE β rows where it is UNKNOWN are dropped.
13.4 Working with NULL in SQL
-- β Wrong: never matches anything
SELECT * FROM Student WHERE phone = NULL;
-- β
Right
SELECT * FROM Student WHERE phone IS NULL;
SELECT * FROM Student WHERE phone IS NOT NULL;
-- Replace NULL with a default when displaying
SELECT name, COALESCE(phone, 'Not provided') FROM Student;
13.5 NULL and other operations
| Situation | Behaviour |
|---|---|
Aggregates (SUM, AVG, MIN, MAX, COUNT(column)) | NULLs are ignored |
COUNT(*) | Counts all rows, including those with NULLs |
ORDER BY | NULLs are placed first or last depending on the DBMS |
GROUP BY | All NULLs are grouped together |
DISTINCT | NULLs are treated as the same for duplicate removal |
| Joins | NULL = NULL is not true, so NULLs don't match in a normal join condition |
UNIQUE constraint | Behaviour with multiple NULLs varies by DBMS |
13.6 NULL and keys
- Primary key: can never be NULL (entity integrity).
- Foreign key: may be NULL, meaning "this row isn't linked to any parent."
- Alternate keys: commonly declared
NOT NULLas well.
13.7 Controlling NULLs
CREATE TABLE Employee (
emp_id INT PRIMARY KEY,
name VARCHAR(50) NOT NULL, -- must have a value
phone VARCHAR(15), -- NULL allowed
salary INT DEFAULT 0 -- default if none supplied
);
π‘ Tip: use
NOT NULLwherever a value is genuinely required, and useDEFAULTfor sensible fallbacks.
14. Integrity Constraints
14.1 What are they?
Integrity constraints are rules that the data in a database must always satisfy. They protect the accuracy and consistency of the data, preventing invalid entries at the door.
They are like security checks at a gate: only data that follows the rules gets in.
14.2 Why they matter
- Keep the data correct and meaningful
- Enforce business rules inside the database itself (not just in application code)
- Prevent orphan records and duplicates
- Enforced automatically by the DBMS on every insert, update and delete
14.3 Types of integrity constraints
| Type | Purpose | SQL tool |
|---|---|---|
| Domain constraint | Values must come from the attribute's domain | Data type, CHECK, NOT NULL, DEFAULT |
| Key constraint | Key values must be unique | PRIMARY KEY, UNIQUE |
| Entity integrity | Primary key can't be NULL | PRIMARY KEY |
| Referential integrity | Foreign keys must match existing parent keys | FOREIGN KEY ... REFERENCES |
| Semantic / business constraints | Application-specific rules | CHECK, triggers, assertions |
14.4 When are constraints checked?
Constraints are checked on INSERT, UPDATE and DELETE. If an operation would violate a constraint, the DBMS rejects it (or applies a defined action, as described in Section 15).
15. Entity Integrity and Referential Integrity
15.1 Entity integrity
Rule: No attribute of the primary key may be NULL, and primary key values must be unique.
Why? Each tuple represents a real-world entity. If you can't identify it, you can't refer to it.
INSERT INTO Student (roll_no, email, name) VALUES (NULL, 'x@x.com', 'Zed');
-- β Rejected: roll_no is the primary key and cannot be NULL
INSERT INTO Student (roll_no, email, name) VALUES (101, 'y@y.com', 'Yan');
-- β Rejected if 101 already exists: duplicate primary key
For a composite primary key, every part must be non-NULL.
15.2 Referential integrity
Rule: A foreign key value must either match an existing primary (or candidate) key value in the referenced table, or be NULL (if allowed).
Why? It guarantees that links between tables never point to something that doesn't exist (no orphan rows / "dangling references").
INSERT INTO Student (roll_no, email, name, dept_id)
VALUES (104, 'z@z.com', 'Zoya', 'D9');
-- β Rejected: no department with dept_id = 'D9'
15.3 Operations that can violate referential integrity
| Operation | On which table | Possible violation |
|---|---|---|
| Insert | Child (referencing) | New FK value has no matching parent |
| Update | Child | FK changed to a non-existent parent value |
| Delete | Parent (referenced) | Child rows still point to the deleted parent |
| Update | Parent | Parent key changed while children still reference the old value |
| Insert into parent | β | β Never violates referential integrity |
15.4 Referential actions (what to do on parent delete/update)
| Action | Behaviour |
|---|---|
RESTRICT / NO ACTION | Reject the operation if child rows exist (the default in most systems) |
CASCADE | Apply the same change to child rows (delete children / update their FK) |
SET NULL | Set the child's FK to NULL |
SET DEFAULT | Set the child's FK to its default value |
CREATE TABLE Student (
roll_no INT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
dept_id VARCHAR(5),
FOREIGN KEY (dept_id) REFERENCES Department(dept_id)
ON DELETE SET NULL
ON UPDATE CASCADE
);
(Exact support for each option varies slightly between DBMSs.)
15.5 Entity vs Referential integrity
| Entity Integrity | Referential Integrity | |
|---|---|---|
| Applies to | Primary key | Foreign key |
| Rule | Not NULL and unique | Must match a parent key or be NULL |
| Within | A single table | Across two tables (or self-referencing) |
| Prevents | Unidentifiable rows | Orphan / dangling references |
16. Domain and Key Constraints
16.1 Domain constraints
Rule: Every value of an attribute must be an atomic value from that attribute's domain.
The domain is enforced using:
| Tool | What it enforces |
|---|---|
| Data type | INT, VARCHAR(50), DATE, DECIMAL(10,2) β¦ |
NOT NULL | A value must be provided |
DEFAULT | A value used when none is supplied |
CHECK | A condition on allowed values |
| Enumerations / lookup tables | A limited set of legal values |
CREATE TABLE Student (
roll_no INT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
age INT CHECK (age BETWEEN 15 AND 60),
gender CHAR(1) CHECK (gender IN ('M', 'F', 'O')),
join_dt DATE DEFAULT CURRENT_DATE
);
INSERT INTO Student (roll_no, name, age) VALUES (105, 'Neel', 150);
-- β Rejected: age violates the CHECK (domain) constraint
16.2 Key constraints
Rule: The values of the attributes forming a key must be unique across all tuples of the relation; no two tuples may have the same key value.
- Applies to primary keys and candidate/alternate keys (
UNIQUE). - Follows from the fact that a relation is a set of tuples, so duplicates can't exist.
- Every relation has at least one key β in the worst case, the set of all its attributes is a super key.
INSERT INTO Student (roll_no, email, name) VALUES (110, 'riya@x.com', 'Another');
-- β Rejected: email already exists (UNIQUE / alternate key violation)
16.3 Summary of how constraints protect data
| Constraint | Protects against |
|---|---|
| Domain | Invalid or out-of-range values |
| Key | Duplicate tuples |
| Entity integrity | Missing identifiers |
| Referential integrity | Broken links between tables |
17. Relational Database Properties
17.1 Properties of a relation (table)
These properties follow from the mathematical definition of a relation as a set of tuples.
| # | Property | Meaning |
|---|---|---|
| 1 | Unique relation name | Each table has its own name within the database |
| 2 | Unique attribute names | Column names are distinct within a table |
| 3 | Atomic values | Each cell holds a single, indivisible value |
| 4 | Single domain per attribute | All values in a column are of the same kind |
| 5 | No duplicate tuples | Every row is distinct (guaranteed by a key) |
| 6 | Tuples are unordered | The order of rows has no meaning |
| 7 | Attributes are unordered | The order of columns has no meaning (they are identified by name) |
| 8 | Fixed structure | Every tuple has the same attributes as defined by the schema |
β οΈ Practical note: Real SQL tables can technically allow duplicate rows when no key is defined, and
ORDER BYis needed to get a specific row order. Good design (with primary keys) brings SQL tables back in line with the relational model.
17.2 Properties of a relational database
- All data is represented in tables (relations)
- Relationships are expressed through values (keys), not physical pointers
- Integrity is built in through declared constraints
- Declarative language: data is accessed with a high-level language such as SQL
- Data independence: physical storage can change without affecting how users query data
- Set-oriented operations: operations work on whole sets of tuples at a time
- Multiple views of the same data can be created
- Transactions (ACID) keep the data reliable, even with many users
17.3 Putting it together β a complete example
CREATE TABLE Department (
dept_id VARCHAR(5) PRIMARY KEY,
dept_name VARCHAR(50) NOT NULL UNIQUE
);
CREATE TABLE Student (
student_id INT AUTO_INCREMENT PRIMARY KEY, -- surrogate key
roll_no VARCHAR(10) NOT NULL UNIQUE, -- natural / alternate key
name VARCHAR(50) NOT NULL, -- domain: NOT NULL
age INT CHECK (age BETWEEN 15 AND 60), -- domain: CHECK
phone VARCHAR(15), -- NULL allowed
dept_id VARCHAR(5),
FOREIGN KEY (dept_id) REFERENCES Department(dept_id) -- referential integrity
ON DELETE SET NULL
);
| Concept | Where it appears |
|---|---|
| Relation / Attribute | Student, name, age⦠|
| Degree | Student has 6 attributes |
| Primary key | student_id (also surrogate) |
| Alternate / natural key | roll_no |
| Foreign key | dept_id |
| Domain constraint | CHECK (age β¦), NOT NULL |
| Key constraint | PRIMARY KEY, UNIQUE |
| Entity integrity | student_id cannot be NULL |
| Referential integrity | dept_id must exist in Department or be NULL |
| NULL | phone, and dept_id after SET NULL |
β‘ Quick Revision Cheat Sheet
Data models
- A data model = structure + operations + constraints.
- Hierarchical = tree (1 parent per child, 1 : N).
- Network = graph (many parents, M : N via sets, CODASYL).
- Relational = tables, linked by keys (Codd, 1970).
- Object-oriented = objects with attributes + methods, inheritance.
- Entity-based (ER) = conceptual design; record-based = implementation (hierarchical, network, relational).
Relational terms
- Relation = table, Tuple = row, Attribute = column, Domain = allowed values.
- Degree = number of columns; Cardinality = number of rows.
- Schema = structure (rarely changes); Instance = data at this moment.
Keys
- Super key = unique set of attributes (can have extras).
- Candidate key = minimal super key.
- Primary key = chosen candidate key (unique, not NULL, one per table).
- Alternate key = candidate keys not chosen.
- Foreign key = refers to a key in another (or the same) table; can be NULL or repeat.
- Composite key = made of 2+ attributes; Natural key = real-world data; Surrogate key = system-generated, no meaning.
NULL
- NULL = unknown / not applicable / missing β not 0 or empty string.
- Use
IS NULL, never= NULL. Logic is three-valued (TRUE / FALSE / UNKNOWN). - Aggregates ignore NULLs;
COUNT(*)counts all rows.
Integrity
- Domain β valid values; Key β uniqueness; Entity β primary key not NULL; Referential β foreign key matches a parent key or is NULL.
- Referential actions:
RESTRICT/NO ACTION,CASCADE,SET NULL,SET DEFAULT.
Relation properties
- Unique names, atomic values, no duplicate tuples, row and column order doesn't matter.
π€ Commonly asked questions (from these topics)
- What is a data model? What are its components?
- Compare hierarchical, network, relational and object-oriented models.
- Differentiate entity-based and record-based models.
- Define relation, tuple, attribute and domain with an example.
- What are degree and cardinality of a relation?
- Differentiate relation schema and relation instance.
- Differentiate super key, candidate key, primary key and alternate key.
- What is a foreign key? Can it be NULL? Can it have duplicates?
- What is a composite key? Give an example.
- Natural key vs surrogate key β which is better and why?
- What does NULL mean? Why is
NULL = NULLnot true? - What are integrity constraints? Name and explain each type.
- Explain entity integrity and referential integrity with examples.
- What operations can violate referential integrity, and how are they handled?
- What are the properties of a relation?
#Functional Dependencies and Normalization#
A friendly, start-to-advanced guide to designing clean relational tables: why bad design hurts, how functional dependencies describe the data, and how normal forms (1NF to 5NF) fix the mess. Read top to bottom the first time. Use the Quick Revision section before exams or interviews.
π Table of Contents
Part A β The Problem
Part B β Functional Dependency Theory 2. Functional Dependencies (FDs) 3. Trivial and Non-Trivial Dependencies 4. Full and Partial Functional Dependencies 5. Transitive Dependencies 6. Attribute Closure 7. Closure of Functional Dependencies 8. Armstrong's Axioms 9. Candidate Key Identification 10. Minimal Cover / Canonical Cover
Part C β Decomposition 11. Decomposition of Relations 12. Lossless-Join Decomposition 13. Dependency Preservation
Part D β Normal Forms 14. First Normal Form (1NF) 15. Second Normal Form (2NF) 16. Third Normal Form (3NF) 17. Boyce-Codd Normal Form (BCNF) 18. Multivalued Dependencies 19. Fourth Normal Form (4NF) 20. Join Dependencies 21. Fifth Normal Form (5NF) 22. Normal Forms at a Glance
Part E β Going the Other Way 23. Denormalization and Its Trade-offs
Part A β The Problem
1. Data Redundancy and Update Anomalies
1.1 What is data redundancy?
Redundancy means the same piece of information is stored more than once. A little controlled duplication can be useful, but uncontrolled duplication causes trouble.
1.2 A badly designed table
Enrollment(roll_no, student_name, course_id, course_title, instructor, grade)
| roll_no | student_name | course_id | course_title | instructor | grade |
|---|---|---|---|---|---|
| 101 | Riya | C1 | Databases | Dr. Rao | A |
| 101 | Riya | C2 | Networks | Dr. Iyer | B |
| 102 | Aman | C1 | Databases | Dr. Rao | A |
| 103 | Sara | C1 | Databases | Dr. Rao | B |
Notice how "Databases / Dr. Rao" repeats for every student in course C1, and "Riya" repeats for every course she takes.
1.3 Problems caused by redundancy
| Problem | Meaning |
|---|---|
| Wasted storage | The same facts are stored again and again |
| Inconsistency risk | Copies can disagree (e.g., "Dr. Rao" in one row and "Dr. R. Rao" in another) |
| Update anomaly | Changing one fact means updating many rows |
| Insertion anomaly | Can't store one fact without another |
| Deletion anomaly | Deleting one fact accidentally destroys another |
1.4 The three anomalies
πΈ Update (modification) anomaly If Dr. Rao is replaced by Dr. Mehta for Databases, you must change every row with C1. If you miss one, the data becomes inconsistent.
πΈ Insertion anomaly
You want to add a new course C3 β Compilers that nobody has enrolled in yet. You can't β the primary key is (roll_no, course_id) and roll_no would have to be NULL. A fact about courses is trapped behind student data.
πΈ Deletion anomaly If Sara (103) drops out and her row is deleted, nothing is lost for C1 because others still have it. But if Aman were the only student in a course and his row is deleted, the entire course information vanishes too.
1.5 The cure
The cure is normalization: organise data so that each fact is stored in exactly one place. To do that, we first need a precise way to describe relationships between attributes: functional dependencies.
Part B β Functional Dependency Theory
2. Functional Dependencies (FDs)
2.1 Definition
A functional dependency X β Y (read "X determines Y") holds in a relation if whenever two tuples have the same value for X, they must have the same value for Y.
- X is called the determinant (left-hand side, LHS)
- Y is called the dependent (right-hand side, RHS)
Everyday example: A roll number determines a student's name. If two rows have roll_no 101, they must show the same name. So
roll_no β student_name.
2.2 Example
| roll_no | name | dept | hod |
|---|---|---|---|
| 101 | Riya | CSE | Dr. Rao |
| 102 | Aman | ECE | Dr. Iyer |
| 103 | Sara | CSE | Dr. Rao |
roll_no β nameβ (each roll number has one name)roll_no β deptβdept β hodβ (each department has one head)name β roll_noβ (two students could share a name)dept β roll_noβ (CSE has 101 and 103)
2.3 Important points
- An FD is a constraint about the meaning (semantics) of the data, decided by the real-world rules, not by looking at one sample table. A sample can only disprove an FD, never prove it.
- If X is a key, X determines every attribute of the relation.
- Both sides can have multiple attributes, e.g.,
{roll_no, course_id} β grade. X β Ydoes not meanY β X.- Notation:
A β BCmeansA β BandA β C.
2.4 Quick test for an FD in a table
To check X β Y against a table: group rows by X; if any group has more than one distinct Y, the FD is violated.
3. Trivial and Non-Trivial Dependencies
| Type | Condition | Example |
|---|---|---|
| Trivial | Y β X (RHS is part of LHS) | {roll_no, name} β name |
| Non-trivial | Y β X (at least one RHS attribute is not in LHS) | roll_no β name |
| Completely non-trivial | X β© Y = β (nothing in common) | roll_no β name |
- Trivial FDs are always true and give no new information.
- Example of non-trivial but not completely non-trivial:
{roll_no, name} β {name, dept}(name appears on both sides, butdeptis new). - Normal forms and design rules focus on non-trivial FDs.
4. Full and Partial Functional Dependencies
These matter when the determinant is composite (has more than one attribute).
4.1 Full functional dependency
X β Y is a full FD if Y depends on all of X, and removing any attribute from X breaks the dependency.
4.2 Partial functional dependency
X β Y is partial if some proper subset of X already determines Y.
4.3 Example
OrderItem(order_id, product_id, product_name, qty), key = (order_id, product_id)
| FD | Type | Why |
|---|---|---|
{order_id, product_id} β qty | Full | Quantity needs both order and product |
{order_id, product_id} β product_name | Partial | product_id β product_name alone is enough |
4.4 Remember
- A partial dependency can only occur when the key is composite.
- If the determinant is a single attribute, the FD is automatically full.
- Partial dependencies are exactly what 2NF removes.
5. Transitive Dependencies
5.1 Definition
A transitive dependency exists when X β Y and Y β Z (with Y not determining X, and Y not a key), so X β Z holds indirectly through Y.
X ββββΊ Y ββββΊ Z
ββββββββββββββββββββΊ (X β Z is transitive)
5.2 Example
Student(roll_no, name, dept, hod)
roll_no β deptdept β hod- Therefore
roll_no β hod(transitively, viadept)
The head of department is really a fact about the department, not the student, so it gets repeated for every student in that department. This is a source of anomalies, and exactly what 3NF removes.
5.3 Conditions to remember
- Y must be a non-prime / non-key attribute (otherwise
X β YandY β Zis not considered a harmful transitive dependency). - The dependency of a non-key attribute on another non-key attribute is the classic trouble spot.
6. Attribute Closure
6.1 Definition
The closure of an attribute set X under a set of FDs F, written XβΊ, is the set of all attributes that X functionally determines.
6.2 Algorithm (the "keep growing" method)
1. Start with result = X
2. Repeat:
for each FD A β B in F:
if A β result: add B to result
until result stops changing
3. XβΊ = result
6.3 Worked example
Relation R(A, B, C, D, E) with F = { A β B, BC β D, D β E }
Find AβΊ:
- Start:
{A} A β Bβ{A, B}BC β D? Need C, which we don't have. β- Result: AβΊ = {A, B}
Find (AC)βΊ:
- Start:
{A, C} A β Bβ{A, B, C}BC β Dβ{A, B, C, D}D β Eβ{A, B, C, D, E}- Result: (AC)βΊ = {A, B, C, D, E} β AC determines everything.
6.4 What is attribute closure used for?
| Use | How |
|---|---|
| Test if X is a super key | XβΊ contains all attributes of R |
| Find candidate keys | Find minimal X with XβΊ = R |
Check if an FD X β Y follows from F | Compute XβΊ and see whether Y β XβΊ |
| Check equivalence of two FD sets | See if each FD in one set is implied by the other |
| Test dependency preservation | Used in the preservation algorithm (Section 13) |
7. Closure of Functional Dependencies (FβΊ)
7.1 Definition
FβΊ is the set of ALL functional dependencies that are logically implied by F, including trivial ones.
7.2 Example
R(A, B, C), F = { A β B, B β C }
FβΊ includes (among many others):
- Given:
A β B,B β C - Derived:
A β C(transitivity),A β BC(union) - Trivial:
A β A,AB β A,ABC β AB, β¦ - Augmented:
AC β BC,AB β BC, β¦
7.3 Key points
- FβΊ can be very large (it grows exponentially with the number of attributes), so we rarely compute it fully.
- Instead of computing FβΊ, we use attribute closure (XβΊ) to answer questions about it efficiently.
- Two FD sets F and G are equivalent if FβΊ = GβΊ, meaning each can derive the other.
- FβΊ is generated by repeatedly applying Armstrong's axioms.
8. Armstrong's Axioms
A small set of inference rules to derive new FDs from given ones. They are sound (they never produce an invalid FD) and complete (they can produce every FD in FβΊ).
8.1 The three primary axioms
| Axiom | Rule | Meaning |
|---|---|---|
| Reflexivity | If Y β X, then X β Y | A set determines its own subsets (trivial FDs) |
| Augmentation | If X β Y, then XZ β YZ | Adding the same attributes to both sides keeps it true |
| Transitivity | If X β Y and Y β Z, then X β Z | Dependencies chain together |
8.2 Derived (secondary) rules
These can be proved from the three axioms, and are handy shortcuts.
| Rule | Statement | Example |
|---|---|---|
| Union | If X β Y and X β Z, then X β YZ | A β B, A β C β A β BC |
| Decomposition (projectivity) | If X β YZ, then X β Y and X β Z | A β BC β A β B, A β C |
| Pseudotransitivity | If X β Y and WY β Z, then WX β Z | A β B, CB β D β CA β D |
| Composition | If X β Y and Z β W, then XZ β YW | A β B, C β D β AC β BD |
8.3 Proof sketch: Union rule
Given X β Y and X β Z:
- Augment
X β Ywith X:X β XY - Augment
X β Zwith Y:XY β YZ - Transitivity on (1) and (2):
X β YZβ
8.4 β οΈ Common mistakes
- Wrong:
A β BimpliesB β Aβ (FDs are not symmetric) - Wrong:
AB β CimpliesA β Cβ (can't drop from the LHS) - Right:
A β BCimpliesA β Bβ (can split the RHS)
9. Candidate Key Identification
9.1 Quick reminders
- A super key is any set whose closure is the whole relation.
- A candidate key is a minimal super key.
- A relation can have multiple candidate keys.
- Prime attribute = part of at least one candidate key; non-prime attribute = part of none.
9.2 Step-by-step method
- List all attributes of R and all FDs.
- Attributes that never appear on the RHS of any FD must be in every candidate key (nothing else can determine them).
- Attributes that appear only on the RHS (never on the LHS) are in no candidate key.
- Compute the closure of the "must-have" attributes. If it covers everything β that's the (only) candidate key.
- If not, add other attributes one by one (or in small groups) and test closures, keeping sets minimal.
- Look for other keys: if some FD
X β Ahas A inside a key, you can swap A with X to get another key.
9.3 Example 1 β single candidate key
R(A, B, C, D, E), F = { A β B, BC β D, D β E }
- Never on RHS: A, C β they must be in every key.
- (AC)βΊ = {A, B, C, D, E} = everything β
- Check minimality: AβΊ = {A, B} β, CβΊ = {C} β β AC is minimal.
- Candidate key = {AC}; prime attributes: A, C.
9.3 Example 2 β multiple candidate keys
R(A, B, C, D), F = { A β B, B β C, C β D, D β A }
- AβΊ = {A, B, C, D} β β A is a key
- BβΊ = {B, C, D, A} β β B is a key
- CβΊ β , DβΊ β
- Candidate keys = {A}, {B}, {C}, {D} β every attribute is prime.
9.4 Example 3 β a classic
R(Student, Subject, Teacher), F = { {Student, Subject} β Teacher, Teacher β Subject }
- Never on RHS: Student β must be in every key.
- (Student, Subject)βΊ = all β
- (Student, Teacher)βΊ: Teacher β Subject, so = all β
- Candidate keys = {Student, Subject}, {Student, Teacher}; prime attributes: Student, Subject, Teacher.
10. Minimal Cover / Canonical Cover
10.1 What and why
A minimal cover (also called canonical cover), written Fc, is a simplified FD set equivalent to F (same FβΊ) with no redundancy. It is the starting point for 3NF synthesis and for removing needless checking work.
10.2 A set Fc is minimal if:
- Every RHS has a single attribute.
- No extraneous attribute on any LHS (can't remove any attribute from a LHS without changing the closure).
- No redundant FD (can't remove any FD without changing the closure).
10.3 The three steps
| Step | Action |
|---|---|
| 1. Split RHS | Replace X β YZ with X β Y and X β Z |
| 2. Remove extraneous LHS attributes | For XA β B, if B β XβΊ (computed using the full set), then A is extraneous; replace with X β B |
| 3. Remove redundant FDs | For each FD X β B, temporarily remove it; if B β XβΊ using the remaining FDs, it's redundant β delete it |
10.4 Worked example
F = { A β BC, B β C, A β B, AB β C, AC β D }
Step 1 β Split RHS
A β B, A β C, B β C, A β B (duplicate), AB β C, AC β D
β Remove the duplicate: { A β B, A β C, B β C, AB β C, AC β D }
Step 2 β Remove extraneous LHS attributes
AB β C: is B extraneous? Check AβΊ under F: A β B, A β C β AβΊ β {A, B, C}; C β AβΊ β β B extraneous. BecomesA β C(already present).AC β D: is C extraneous? AβΊ includes C, and so with A we reach D? Check: AβΊ = {A, B, C} andAC β Dfires since C β AβΊ β D β AβΊ β β C extraneous. BecomesA β D.
Now: { A β B, A β C, B β C, A β D }
Step 3 β Remove redundant FDs
A β C: without it, A β B and B β C give A β C β β redundant, remove.A β B: without it, AβΊ = {A, C, D}, no B β β needed.B β C: needed (no other way to get C from B).A β D: needed.
Minimal cover: Fc = { A β B, B β C, A β D }
βΉοΈ A minimal cover is not always unique: different orders of removal can give different (but equivalent) covers.
Part C β Decomposition
11. Decomposition of Relations
11.1 What is it?
Decomposition means splitting one relation R into two or more smaller relations (R1, R2, β¦) so that:
- every attribute of R appears in at least one Ri, and
- the pieces are better designed (fewer anomalies).
11.2 Example
Enrollment(roll_no, student_name, course_id, course_title, instructor, grade) could be split into:
Student(roll_no, student_name)Course(course_id, course_title, instructor)Enrollment(roll_no, course_id, grade)
11.3 A good decomposition must have
| Property | Why it matters |
|---|---|
| Lossless join (mandatory) | Joining pieces gives back exactly the original data, with no lost or fake tuples |
| Dependency preservation (desirable) | All original FDs can still be enforced without joining tables |
| Reduced redundancy | Pieces are in a higher normal form |
11.4 Lossy vs lossless in one line
- Lossy decomposition: the join produces extra (spurious) tuples, so information is lost (you can no longer tell which tuples were real).
- Lossless: the join returns exactly the original relation.
12. Lossless-Join Decomposition
12.1 Definition
A decomposition of R into R1 and R2 is lossless if R1 β R2 = R for every valid instance of R.
12.2 The test (binary decomposition)
Decomposition of R into R1 and R2 is lossless if at least one holds in FβΊ:
- (R1 β© R2) β R1, or
- (R1 β© R2) β R2
In words: the common attributes must be a key (super key) of at least one of the two pieces.
12.3 β Lossless example
R(A, B, C), F = { A β B }. Decompose into R1(A, B) and R2(A, C).
- R1 β© R2 = {A}; A β B and AβΊ β {A, B} = R1 β β lossless.
12.4 β Lossy example
R(Student, Course, Teacher) with no helpful FDs. Decompose into R1(Student, Course) and R2(Course, Teacher).
Original:
| Student | Course | Teacher |
|---|---|---|
| Riya | DB | Rao |
| Aman | DB | Iyer |
R1 = {(Riya, DB), (Aman, DB)}, R2 = {(DB, Rao), (DB, Iyer)}
R1 β R2 gives 4 rows, including fake ones like (Riya, DB, Iyer) β. Since Course is not a key of either piece, the decomposition is lossy.
12.5 Notes
- For more than two pieces, a tableau (chase) test is used; for exam problems you can often decompose step by step and apply the binary test each time.
- Decomposing on an FD
X β Yinto (X βͺ Y) and (R β Y) (with X kept in both) is always lossless, which is the foundation of the normalization algorithms. - Lossless-join is mandatory; the other properties are desirable.
13. Dependency Preservation
13.1 Definition
A decomposition of R into R1, β¦, Rn preserves dependencies if every FD in F can be checked within a single smaller table (or derived from FDs that can).
Formally: let Fi = the FDs of FβΊ that involve only attributes of Ri. The decomposition is dependency preserving if
(F1 βͺ F2 βͺ β¦ βͺ Fn)βΊ = FβΊ
13.2 Why does it matter?
If an FD is not preserved, enforcing it requires joining tables on every insert/update, which is slow and error-prone. Otherwise, the DBMS can check each constraint cheaply inside a single table.
13.3 β Preserved example
R(A, B, C), F = { A β B, B β C }. Decompose into R1(A, B) and R2(B, C).
A β Blives in R1 βB β Clives in R2 β- Also lossless (R1 β© R2 = B, and B β C). β Lossless AND dependency preserving.
13.4 β Not preserved example
R(A, B, C), F = { AB β C, C β B }. Decompose into R1(A, C) and R2(B, C).
C β Blives in R2 βAB β C: no table contains A, B and C together β β lost- Lossless? R1 β© R2 = C, and C β B means C is a key of R2 β β so lossless but not dependency preserving.
13.5 Practical test (using attribute closures)
For each FD X β Y in F:
- Set
result = X. - Repeat: for each Ri,
result = result βͺ ((result β© Ri)βΊ β© Ri)(closure under F, restricted to Ri). - Until no change. If
Y β result, the FD is preserved.
13.6 Summary
| Property | Mandatory? | Guarantees |
|---|---|---|
| Lossless join | β Yes | No information lost |
| Dependency preservation | β Desirable | Cheap constraint checking |
| 3NF decomposition (synthesis) | Can always achieve both | |
| BCNF decomposition | Can always achieve lossless, but not always dependency preserving |
Part D β Normal Forms
Normalization is the process of organising tables into progressively stricter "normal forms" to remove redundancy and anomalies. Each form builds on the one before.
Unnormalized β 1NF β 2NF β 3NF β BCNF β 4NF β 5NF
(more restrictions, less redundancy)
Our running example (used for 1NF β 3NF)
OrderDetails(order_id, product_id, product_name, customer_id, customer_name, customer_city, qty)
Business rules (FDs):
{order_id, product_id} β qtyorder_id β customer_idcustomer_id β customer_name, customer_cityproduct_id β product_name
Candidate key: {order_id, product_id}
14. First Normal Form (1NF)
14.1 Rule
A relation is in 1NF if:
- Every attribute holds atomic (single, indivisible) values.
- There are no repeating groups or multivalued attributes (no lists/arrays in a cell).
- Each row is unique (there is a primary key).
14.2 β Violating example
| student_id | name | phones |
|---|---|---|
| 1 | Riya | 98111, 98222 |
| 2 | Aman | 98333 |
phones holds multiple values in one cell.
14.3 β Fix β Option A: one value per row
| student_id | name | phone |
|---|---|---|
| 1 | Riya | 98111 |
| 1 | Riya | 98222 |
| 2 | Aman | 98333 |
14.4 β Fix β Option B (preferred): separate table
Student(student_id, name) StudentPhone(student_id, phone)
14.5 Also violates 1NF
- Repeating columns:
phone1, phone2, phone3 - Composite (non-atomic) values:
address = "12 MG Road, Pune, 411001"when you need to query the city separately
14.6 Notes
- 1NF is the minimum requirement for a relational table.
- It does not remove all redundancy (our running example is in 1NF but still full of anomalies).
15. Second Normal Form (2NF)
15.1 Rule
A relation is in 2NF if:
- It is in 1NF, and
- No non-prime attribute is partially dependent on any candidate key. (Every non-key attribute must depend on the whole key.)
15.2 When does it apply?
- Only matters when a candidate key is composite.
- If all candidate keys are single attributes, a 1NF relation is automatically in 2NF.
15.3 Checking the running example
Key = {order_id, product_id}
| FD | Problem |
|---|---|
product_id β product_name | Partial (part of key determines it) β |
order_id β customer_id, customer_name, customer_city | Partial β |
{order_id, product_id} β qty | Full β |
So the table is not in 2NF.
15.4 Decomposition to 2NF
Move partially dependent attributes into tables keyed by the part of the key that determines them:
- OrderItem(order_id, product_id, qty) β key
{order_id, product_id} - Product(product_id, product_name) β key
product_id - Orders(order_id, customer_id, customer_name, customer_city) β key
order_id
All three are in 2NF now. β
But look at Orders: customer_name and customer_city depend on customer_id, not directly on the key. That's the next problem.
16. Third Normal Form (3NF)
16.1 Rule
A relation is in 3NF if it is in 2NF and no non-prime attribute is transitively dependent on a candidate key.
16.2 Formal definition (very useful in exams)
For every non-trivial FD X β A in the relation, at least one must hold:
- X is a super key, or
- A is a prime attribute (A is part of some candidate key).
16.3 Checking Orders
Orders(order_id, customer_id, customer_name, customer_city)
order_id β customer_idβ (order_id is the key)customer_id β customer_name, customer_cityβ (customer_id is not a super key, and the RHS attributes are non-prime)- So
order_id β customer_nameis transitive (via customer_id) β not in 3NF.
16.4 Decomposition to 3NF
- Orders(order_id, customer_id)
- Customer(customer_id, customer_name, customer_city)
16.5 Final 3NF design
| Table | Key | Contents |
|---|---|---|
| OrderItem | (order_id, product_id) | qty |
| Product | product_id | product_name |
| Orders | order_id | customer_id (FK) |
| Customer | customer_id | customer_name, customer_city |
Each fact now lives in one place; the anomalies from Section 1 are gone. β
16.6 3NF synthesis algorithm (advanced)
A standard method that always gives a lossless, dependency-preserving 3NF decomposition:
- Find a minimal cover Fc of F.
- Group FDs in Fc by the same LHS; for each group
X β A1, A2, β¦create a relation (X, A1, A2, β¦). - If no relation contains a candidate key of R, add one relation made of a candidate key.
- Remove any relation whose attributes are fully contained in another.
16.7 Why 3NF is popular
- It is always achievable with lossless join + dependency preservation.
- It removes most real-world redundancy.
- It may still keep small redundancies where a non-prime FD has a prime attribute on the RHS (the gap BCNF closes).
17. Boyce-Codd Normal Form (BCNF)
17.1 Rule
A relation is in BCNF if, for every non-trivial FD X β Y, X is a super key.
It is a stricter version of 3NF: BCNF removes the "or A is prime" escape clause.
17.2 BCNF vs 3NF
- Every BCNF relation is in 3NF, but not every 3NF is in BCNF.
- The two differ only when a relation has multiple overlapping composite candidate keys.
17.3 Classic example (3NF but not BCNF)
R(Student, Subject, Teacher)
Business rules:
- Each teacher teaches only one subject:
Teacher β Subject - A student learns a subject from one teacher:
{Student, Subject} β Teacher
Candidate keys: {Student, Subject} and {Student, Teacher} β all three attributes are prime.
| Student | Subject | Teacher |
|---|---|---|
| Riya | DB | Rao |
| Aman | DB | Rao |
| Sara | Networks | Iyer |
- 3NF? For
Teacher β Subject, Teacher is not a super key, but Subject is prime β allowed β in 3NF β - BCNF? Teacher is not a super key β β not in BCNF.
Problem (redundancy / anomaly): the fact "Rao teaches DB" is repeated for every student. You can't record that a new teacher teaches a subject until a student enrolls with them.
17.4 Decomposing to BCNF
For each violating FD X β Y, split R into:
- R1 = (X βͺ Y)
- R2 = (R β (Y β X)) (i.e., keep X, drop Y from the rest)
Apply to Teacher β Subject:
- R1(Teacher, Subject) β key
Teacher - R2(Student, Teacher) β key
{Student, Teacher}
Both are in BCNF β
and the decomposition is lossless (common attribute Teacher is a key of R1).
17.5 The catch: dependency preservation
The FD {Student, Subject} β Teacher now spans two tables, so it cannot be checked without a join β.
| 3NF | BCNF | |
|---|---|---|
| Lossless decomposition always possible? | β | β |
| Dependency-preserving always possible? | β | β Not always |
| Redundancy removed | Almost all | All FD-based redundancy |
In practice, designers typically aim for BCNF, and settle for 3NF when BCNF would lose an important dependency.
17.6 BCNF decomposition algorithm
result = {R}
while some relation Ri in result is not in BCNF:
pick a violating FD X β Y in Ri (X is not a super key of Ri)
replace Ri with (X βͺ Y) and (Ri β (Y β X))
18. Multivalued Dependencies (MVDs)
18.1 The situation
Sometimes a table has two or more independent multi-valued facts about the same thing. FDs can't describe this, and BCNF isn't enough to fix the redundancy.
18.2 Example
CourseInfo(Course, Teacher, Book)
Rules: a course can have several teachers and several recommended books, and the teachers and books are independent of each other.
| Course | Teacher | Book |
|---|---|---|
| DB | Rao | Korth |
| DB | Rao | Elmasri |
| DB | Iyer | Korth |
| DB | Iyer | Elmasri |
| OS | Mehta | Galvin |
For DB, every teacher is paired with every book (cross product). To add a new teacher, you must add a row for every book β redundancy and update anomalies!
The key is all three attributes (Course, Teacher, Book), there are no non-trivial FDs, so the table is in BCNF, yet it clearly has a design problem.
18.3 Definition
An MVD X β Y (read "X multidetermines Y") holds if, for each value of X, the set of Y-values is independent of the values of the remaining attributes Z = R β X β Y.
- Here: Course β Teacher and Course β Book.
- Whenever an MVD
X β Yexists with Z remaining, you get the cross-product pattern: if(x, y1, z1)and(x, y2, z2)exist, then(x, y1, z2)and(x, y2, z1)must exist.
18.4 Trivial vs non-trivial MVD
X β Y is trivial if:
- Y β X, or
- X βͺ Y = R (Z is empty)
Otherwise it is non-trivial.
18.5 Important properties
| Property | Statement |
|---|---|
| Replication | Every FD is also an MVD: if X β Y then X β Y |
| Complementation | If X β Y, then X β R β X β Y (so MVDs come in pairs: `Course β Teacher |
| Augmentation | If X β Y and W β Z..., then WX β Y (more inference rules exist) |
| Transitivity | If X β Y and Y β Z, then X β (Z β Y) |
18.6 FD vs MVD
| FD (X β Y) | MVD (X β Y) | |
|---|---|---|
| X determines | Exactly one Y value | A set of Y values |
| Generates | Equality constraint | Tuple-generating constraint |
| Typical cause | Dependent attributes | Independent multi-valued attributes |
| Fixed by | 2NF, 3NF, BCNF | 4NF |
19. Fourth Normal Form (4NF)
19.1 Rule
A relation is in 4NF if it is in BCNF and, for every non-trivial MVD X β Y, X is a super key.
19.2 Fixing the example
CourseInfo(Course, Teacher, Book) has non-trivial MVDs Course β Teacher and Course β Book, but Course is not a super key β not in 4NF.
Decompose (for the MVD X β Y, split into (X βͺ Y) and (X βͺ Z)):
CourseTeacher(Course, Teacher)
| Course | Teacher |
|---|---|
| DB | Rao |
| DB | Iyer |
| OS | Mehta |
CourseBook(Course, Book)
| Course | Book |
|---|---|
| DB | Korth |
| DB | Elmasri |
| OS | Galvin |
Now adding a new teacher is one row, and teachers and books are stored independently. β
19.3 Notes
- Every 4NF relation is in BCNF (and so 3NF, 2NF, 1NF).
- 4NF only matters when a table contains two or more independent multivalued facts.
- A table with only FDs (no non-trivial MVDs) that is in BCNF is automatically in 4NF.
- This decomposition is lossless, since
Courseis the common attribute and Course β Teacher holds.
20. Join Dependencies
20.1 Idea
A join dependency (JD) says that a relation can be split into several projections and rebuilt exactly by joining them, with no loss and no extra rows.
20.2 Definition
A relation R satisfies the join dependency *(R1, R2, β¦, Rn) if
R = Ο_R1(R) β Ο_R2(R) β β¦ β Ο_Rn(R)
where R1 βͺ R2 βͺ β¦ βͺ Rn = R.
- If the JD holds, then decomposing R into R1, β¦, Rn is lossless.
- A JD is trivial if one of the Ri is R itself.
20.3 Relationship with MVD
- An MVD is a special case of a JD with just two components:
X β Yis the same as the JD *(XY, XZ). - General JDs involve three or more components and can't always be expressed using MVDs.
- This makes JDs the most general dependency discussed in these notes (FD β MVD β JD).
20.4 Example
A relation SPJ(Supplier, Part, Project) satisfies the JD *( (Supplier, Part), (Part, Project), (Supplier, Project) ) if it can be rebuilt by joining those three projections. The business rule that makes this true is in the next section.
21. Fifth Normal Form (5NF)
Also called Project-Join Normal Form (PJNF).
21.1 Rule
A relation is in 5NF if it is in 4NF and every non-trivial join dependency in it is implied by its candidate keys.
In simple words: the table can't be split into smaller tables without losing information, unless the split is "obvious" from the keys.
21.2 Example: SupplierβPartβProject
SPJ(Supplier, Part, Project) records which supplier supplies which part to which project.
Business rule (cyclic constraint): If supplier S supplies part P, and project J uses part P, and supplier S supplies to project J, then S supplies part P to project J.
| Supplier | Part | Project |
|---|---|---|
| S1 | P1 | J2 |
| S1 | P2 | J1 |
| S2 | P1 | J1 |
| S1 | P1 | J1 |
Because of the rule, SPJ equals the join of its three binary projections:
- SP(Supplier, Part)
- PJ(Part, Project)
- SJ(Supplier, Project)
SPJ has no non-trivial MVDs (so it is in 4NF) but it has a non-trivial JD that isn't implied by the candidate key (Supplier, Part, Project) β not in 5NF.
5NF design: replace SPJ with SP, PJ and SJ β redundancy is gone, and the original can be reconstructed with a three-way join. β
21.3 β οΈ Important caution
- If the cyclic business rule does not hold, decomposing SPJ into those three tables would be lossy (it would create false tuples). 5NF depends on real-world rules, never on a sample.
- 5NF situations are rare in practice, and often considered more of a theoretical level of design.
22. Normal Forms at a Glance
22.1 The nesting
ββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1NF β
β ββββββββββββββββββββββββββββββββββββββββββββββ β
β β 2NF β β
β β ββββββββββββββββββββββββββββββββββββββββ β β
β β β 3NF β β β
β β β ββββββββββββββββββββββββββββββββββ β β β
β β β β BCNF β β β β
β β β β ββββββββββββββββββββββββββββ β β β β
β β β β β 4NF β 5NF β β β β β
β β β β ββββββββββββββββββββββββββββ β β β β
β β β ββββββββββββββββββββββββββββββββββ β β β
β β ββββββββββββββββββββββββββββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββ
5NF β 4NF β BCNF β 3NF β 2NF β 1NF
22.2 Comparison table
| Normal form | Requirement (in addition to the previous one) | Removes | Dependency involved |
|---|---|---|---|
| 1NF | Atomic values, no repeating groups | Multivalued cells, repeating groups | β |
| 2NF | No partial dependency of non-prime attributes on a candidate key | Partial dependencies | FD |
| 3NF | No transitive dependency of non-prime attributes (for each X β A: X super key or A prime) | Transitive dependencies | FD |
| BCNF | For every non-trivial X β Y, X is a super key | Remaining FD-based anomalies | FD |
| 4NF | For every non-trivial X β Y, X is a super key | Independent multivalued facts | MVD |
| 5NF | Every non-trivial JD is implied by candidate keys | Join-dependency redundancy | JD |
22.3 Decomposition guarantees
| Target | Lossless join | Dependency preserving |
|---|---|---|
| 3NF | β Always | β Always |
| BCNF | β Always | β οΈ Not always |
| 4NF | β Always | β οΈ Not always |
22.4 Quick way to find the highest normal form (exam method)
- Find all candidate keys and the prime attributes.
- Check partial dependencies (non-prime attribute depending on part of a candidate key) β if any, it's only in 1NF.
- Check transitive dependencies: for each
X β A, if X isn't a super key and A isn't prime β it's only in 2NF. - Check BCNF: if every determinant is a super key β BCNF; otherwise it's in 3NF.
- Check non-trivial MVDs β 4NF; check JDs β 5NF.
Part E β Going the Other Way
23. Denormalization and Its Trade-offs
23.1 What is it?
Denormalization is the deliberate introduction of redundancy into a normalized design (by merging tables or adding duplicate/derived data) to improve read performance.
Normalize first to get the design right; denormalize selectively and knowingly only where performance demands it.
23.2 Why would you do it?
Normalized designs spread data across many tables, so common queries need many joins, which can be slow on large data sets, especially in read-heavy systems like reports and dashboards.
23.3 Common techniques
| Technique | Description | Example |
|---|---|---|
| Adding redundant columns | Copy a frequently used column into another table to avoid a join | Store customer_name in Orders |
| Pre-computed / derived columns | Store calculated values | order_total in Orders |
| Merging tables | Combine tables that are always joined (e.g., 1:1 relationships) | Employee + EmployeeProfile |
| Summary / aggregate tables | Store pre-aggregated results | Daily sales totals |
| Storing repeating groups | Keep a few repeated values in columns for speed (violates 1NF) | phone1, phone2 |
| Materialized views | Store the result of a query physically and refresh it | Monthly report view |
23.4 Trade-offs
| β Benefits | β Costs |
|---|---|
| Faster reads (fewer joins) | Redundant data uses more storage |
| Simpler, shorter queries | Slower writes (many copies to update) |
| Faster reporting and analytics | Risk of inconsistency if copies drift apart |
| Fewer tables to scan | Anomalies return (update, insert, delete) |
| More complex code needed to keep copies in sync (triggers, application logic) | |
| Harder to maintain and change the schema |
23.5 When to denormalize (and when not to)
| Consider denormalizing when⦠| Avoid when⦠|
|---|---|
| Workload is read-heavy and write-light | Data changes frequently |
| Joins are a proven bottleneck (measure first!) | Accuracy and consistency are critical (banking, billing) |
| Reporting / data warehouse / analytics systems | A simpler fix like indexing or query tuning would work |
| Derived values are expensive to recompute | You haven't measured a real problem |
23.6 Golden rules
- Normalize first, then denormalize only for a measured performance need.
- Document every denormalization decision.
- Add safeguards (triggers, constraints, scheduled refreshes) to keep redundant copies consistent.
- Try indexing and query optimization before denormalizing.
23.7 Normalization vs Denormalization
| Aspect | Normalization | Denormalization |
|---|---|---|
| Goal | Minimize redundancy, ensure integrity | Maximize read speed |
| Tables | More, smaller | Fewer, larger |
| Joins | More | Fewer |
| Read speed | Slower for complex queries | Faster |
| Write speed | Faster | Slower |
| Storage | Less | More |
| Anomalies | Avoided | Possible |
| Best for | Transactional systems (OLTP) | Reporting / analytics (read-heavy) |
β‘ Quick Revision Cheat Sheet
Why normalize?
- Redundancy β update, insertion, deletion anomalies. Normalization = one fact in one place.
FD basics
X β Y: same X β same Y. Trivial: Y β X.- Partial: part of a composite key determines it. Full: needs the whole key.
- Transitive:
X β Y β Zwith Y non-key.
Closure & rules
- XβΊ = all attributes determined by X. X is a super key iff XβΊ = R.
- Armstrong's axioms: Reflexivity, Augmentation, Transitivity (sound + complete).
- Derived: Union, Decomposition, Pseudotransitivity, Composition.
- FβΊ = all FDs implied by F.
Candidate keys
- Attributes never on RHS β in every key. Attributes only on RHS β in no key.
Minimal cover (3 steps)
- Split RHS to single attributes
- Remove extraneous LHS attributes
- Remove redundant FDs
Decomposition
- Lossless (mandatory):
(R1 β© R2) β R1or(R1 β© R2) β R2. - Dependency preserving (desirable):
(F1 βͺ β¦ βͺ Fn)βΊ = FβΊ. - 3NF β both always possible. BCNF β lossless always, preservation not always.
Normal forms
| NF | One-line rule |
|---|---|
| 1NF | Atomic values, no repeating groups |
| 2NF | 1NF + no partial dependencies |
| 3NF | 2NF + for every X β A: X super key or A prime |
| BCNF | Every non-trivial X β Y has X as super key |
| 4NF | BCNF + no non-trivial MVD with non-super-key LHS |
| 5NF | 4NF + every JD implied by candidate keys |
Other
- MVD
X β Y: independent multi-valued facts; JD*(R1,β¦,Rn): lossless n-way split. - Denormalization: add redundancy for faster reads; costs: slower writes, risk of inconsistency.
π€ Commonly asked questions (from these topics)
- What are update, insertion and deletion anomalies? Give examples.
- Define a functional dependency. What are trivial and non-trivial FDs?
- Differentiate full, partial and transitive dependencies.
- How do you compute the closure of an attribute set? What is it used for?
- State Armstrong's axioms and prove the union rule.
- How do you find all candidate keys of a relation?
- Find the minimal cover of a given FD set.
- What is lossless-join decomposition? How is it tested?
- What is dependency preservation? Give an example where it fails.
- Explain 1NF, 2NF and 3NF with examples.
- Differentiate 3NF and BCNF. Give a relation that is in 3NF but not BCNF.
- What is a multivalued dependency? Explain 4NF with an example.
- What is a join dependency? Explain 5NF.
- What is denormalization? When is it useful and what are its drawbacks?
#SQL Fundamentals#
A friendly, start-to-advanced guide to the core of SQL: creating tables, adding and changing data, reading it back, filtering and sorting, protecting it with constraints, and a few handy tools like
CASE. Read top to bottom the first time. Use the Quick Revision section before exams or interviews.
π Table of Contents
- Introduction to SQL
- SQL Standards and Dialects
- SQL Data Types
- Creating and Selecting Databases
- CREATE, ALTER, DROP and TRUNCATE
- INSERT, UPDATE and DELETE
- SELECT Statements
- WHERE, DISTINCT and ORDER BY
- LIMIT, OFFSET and FETCH
- Aliases and Column Expressions
- Comparison and Logical Operators
- BETWEEN, IN, LIKE and IS NULL
- AND, OR and NOT
- SQL Constraints
- AUTO INCREMENT / Identity Columns
- INSERT INTO SELECT
- CASE WHEN Expressions
- Comments in SQL
- Quick Revision Cheat Sheet
ποΈ Sample data used in these notes
Most examples use these two tables so you can follow along.
Department
| dept_id | dept_name |
|---|---|
| D1 | Computer Science |
| D2 | Electronics |
| D3 | Mechanical |
Student
| student_id | name | age | dept_id | marks | city | |
|---|---|---|---|---|---|---|
| 1 | Riya | 20 | D1 | 87 | Bhopal | riya@x.com |
| 2 | Aman | 21 | D2 | 74 | Indore | aman@x.com |
| 3 | Sara | 20 | D1 | 91 | Bhopal | sara@x.com |
| 4 | Neel | 22 | D3 | NULL | Pune | NULL |
| 5 | Zoya | 19 | D2 | 68 | Delhi | zoya@x.com |
1. Introduction to SQL
1.1 What is SQL?
SQL (Structured Query Language) is the standard language used to talk to relational databases: to create structures, store data, ask questions about it, and control access to it.
- Pronounced "S-Q-L" or "sequel".
- Declarative: you describe what result you want, not how to compute it. The database's optimizer decides the "how".
- Works on tables (relations) made of rows and columns.
Analogy: SQL is like ordering at a restaurant. You say "one margherita pizza" (what you want). You don't tell the chef how to knead the dough.
1.2 What can you do with SQL?
| Category | Purpose | Main commands |
|---|---|---|
| DDL (Data Definition) | Define/modify structure | CREATE, ALTER, DROP, TRUNCATE |
| DML (Data Manipulation) | Add/change/remove data | INSERT, UPDATE, DELETE |
| DQL (Data Query) | Read data | SELECT |
| DCL (Data Control) | Permissions | GRANT, REVOKE |
| TCL (Transaction Control) | Manage transactions | COMMIT, ROLLBACK, SAVEPOINT |
These notes focus on DDL, DML and DQL basics plus constraints and CASE.
1.3 Basic rules of SQL syntax
- A statement usually ends with a semicolon
;. - Keywords are case-insensitive (
SELECT=select); writing keywords in UPPERCASE is a popular style for readability. - Table and column names may be case-sensitive depending on the DBMS and operating system.
- Whitespace and line breaks don't matter; use them to keep queries readable.
- String values go in single quotes:
'Riya'. Double quotes are normally for identifiers (names), not strings. - Dates are written as strings in a standard format, e.g.,
'2026-10-13'.
1.4 Execution flow (big picture)
SQL text β Parser β Optimizer (chooses a plan) β Execution engine β Result
2. SQL Standards and Dialects
2.1 SQL standards
SQL is standardised by ANSI (American National Standards Institute) and ISO. The standard has evolved over time:
| Version | Notable additions (examples) |
|---|---|
| SQL-86 / SQL-89 | First standards |
| SQL-92 | Major release; joins syntax, CASE, CAST, many data types |
| SQL:1999 | Triggers, recursive queries, user-defined types |
| SQL:2003 | Window functions, identity columns (GENERATED ... AS IDENTITY), XML support |
| SQL:2008 | FETCH FIRST ... ROWS ONLY, TRUNCATE |
| SQL:2011 | Temporal data support |
| SQL:2016 | JSON support |
| SQL:2023 | Latest revision (more JSON and property graph features) |
Don't memorise every detail: just remember that a core, common SQL exists and each newer version adds features.
2.2 Dialects
Each database vendor implements the standard plus its own extensions and differences. These variants are called dialects.
| DBMS | Dialect / extension | Notes |
|---|---|---|
| MySQL / MariaDB | MySQL SQL | LIMIT, AUTO_INCREMENT, backtick-quoted names, # comments |
| PostgreSQL | PL/pgSQL, PG SQL | Rich types, SERIAL/identity, ILIKE, RETURNING |
| SQL Server | T-SQL (Transact-SQL) | TOP, IDENTITY, + for string concatenation |
| Oracle | PL/SQL | FETCH FIRST, DUAL table, VARCHAR2, NUMBER |
| SQLite | SQLite SQL | Lightweight, flexible typing, file-based |
2.3 Where dialects differ (things to watch)
| Feature | MySQL | PostgreSQL | SQL Server | Oracle | SQLite |
|---|---|---|---|---|---|
| Limit rows | LIMIT | LIMIT / FETCH | TOP / OFFSET FETCH | FETCH FIRST | LIMIT |
| Auto-number | AUTO_INCREMENT | SERIAL / identity | IDENTITY | identity / sequence | AUTOINCREMENT |
| String concat | CONCAT() | || / CONCAT() | + / CONCAT() | || | || |
| Switch database | USE db; | \c db (psql) | USE db; | Connect as a schema/user | Open a file |
| Comment (line) | -- and # | -- | -- | -- | -- |
β Tip: write standard SQL where possible, and check your DBMS documentation for the dialect-specific parts. The examples here follow the common standard and note differences where they matter.
3. SQL Data Types
A data type tells the database what kind of value a column can hold, and how much space to use. Names and details vary a bit between systems.
3.1 Numeric types
| Type | Description | Example |
|---|---|---|
SMALLINT | Small whole number | 1500 |
INT / INTEGER | Standard whole number (about Β±2.1 billion) | 20 |
BIGINT | Very large whole number | 9000000000 |
DECIMAL(p, s) / NUMERIC(p, s) | Exact number with p total digits, s after the decimal point | DECIMAL(8,2) β 123456.78 |
FLOAT / REAL / DOUBLE | Approximate floating-point numbers | 3.14159 |
β οΈ Use
DECIMALfor money (exact). Floating types can introduce tiny rounding errors.
3.2 Character (string) types
| Type | Description |
|---|---|
CHAR(n) | Fixed-length text; padded with spaces if shorter |
VARCHAR(n) | Variable-length text up to n characters |
TEXT | Long text (size limits and exact name vary by DBMS) |
- Use
CHARfor values of a fixed size (e.g., a 2-letter state code),VARCHARfor names, emails, etc.
3.3 Date and time types
| Type | Stores | Example |
|---|---|---|
DATE | Year-month-day | '2026-10-13' |
TIME | Time of day | '14:30:00' |
TIMESTAMP / DATETIME | Date and time | '2026-10-13 14:30:00' |
INTERVAL | A span of time (standard SQL, e.g., PostgreSQL, Oracle) | '3 days' |
3.4 Other common types
| Type | Description |
|---|---|
BOOLEAN | TRUE / FALSE (MySQL treats it as a small integer; SQL Server uses BIT) |
BLOB / BYTEA / VARBINARY | Binary data like images or files |
JSON | JSON documents (supported by most modern systems) |
UUID | Universally unique identifier (native in PostgreSQL) |
ENUM | A fixed list of allowed text values (MySQL, PostgreSQL) |
3.5 Choosing wisely
- Pick the smallest type that safely fits your data.
- Use proper types (store dates as
DATE, not text) so sorting, comparing and date functions work correctly. - Don't store numbers you don't calculate with (like phone numbers or ZIP codes) as numeric types; use
VARCHAR.
4. Creating and Selecting Databases
A database is a container for tables and other objects.
4.1 Create a database
CREATE DATABASE college;
In MySQL you can avoid an error if it already exists:
CREATE DATABASE IF NOT EXISTS college;
(Support for IF NOT EXISTS on CREATE DATABASE varies by DBMS.)
4.2 List databases
SHOW DATABASES; -- MySQL
\l -- PostgreSQL (psql command)
SELECT name FROM sys.databases; -- SQL Server
4.3 Select (switch to) a database
USE college; -- MySQL, SQL Server
\c college -- PostgreSQL (psql command)
- After this, all table names you write refer to tables inside that database.
- In many systems you can also write the full name:
college.Student.
4.4 Drop a database β οΈ
DROP DATABASE college; -- deletes everything inside it!
DROP DATABASE IF EXISTS college; -- no error if it doesn't exist
4.5 Notes
- SQLite has no
CREATE DATABASE: each database is simply a file you open. - Oracle works with schemas/users rather than a plain
CREATE DATABASEfor everyday work. - Database names should avoid spaces and special characters.
5. CREATE, ALTER, DROP and TRUNCATE
These are DDL commands: they change the structure.
5.1 CREATE TABLE
CREATE TABLE Department (
dept_id VARCHAR(5) PRIMARY KEY,
dept_name VARCHAR(50) NOT NULL
);
CREATE TABLE Student (
student_id INT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
age INT,
dept_id VARCHAR(5),
marks INT,
city VARCHAR(30),
email VARCHAR(100) UNIQUE,
FOREIGN KEY (dept_id) REFERENCES Department(dept_id)
);
Create a table only if it doesn't already exist:
CREATE TABLE IF NOT EXISTS Course (course_id INT PRIMARY KEY, title VARCHAR(60));
5.2 ALTER TABLE β change an existing table
| Task | Common syntax |
|---|---|
| Add a column | ALTER TABLE Student ADD phone VARCHAR(15); |
| Drop a column | ALTER TABLE Student DROP COLUMN phone; |
| Rename a column | ALTER TABLE Student RENAME COLUMN city TO town; |
| Change a data type | MySQL: ALTER TABLE Student MODIFY age SMALLINT; Β· PostgreSQL: ALTER TABLE Student ALTER COLUMN age TYPE SMALLINT; Β· SQL Server: ALTER TABLE Student ALTER COLUMN age SMALLINT; |
| Rename a table | ALTER TABLE Student RENAME TO Learner; (SQL Server uses sp_rename) |
| Add a constraint | ALTER TABLE Student ADD CONSTRAINT chk_age CHECK (age >= 15); |
| Drop a constraint | ALTER TABLE Student DROP CONSTRAINT chk_age; (MySQL uses specific forms like DROP CHECK, DROP FOREIGN KEY) |
β οΈ Changing a type or adding a
NOT NULLconstraint can fail or lose data if existing rows don't fit.
5.3 DROP TABLE β delete the table completely
DROP TABLE Student; -- structure AND data gone
DROP TABLE IF EXISTS Student; -- no error if missing
- You may be blocked if other tables reference it through a foreign key (drop the child tables or constraints first).
5.4 TRUNCATE TABLE β empty the table, keep the structure
TRUNCATE TABLE Student;
- Removes all rows quickly; the table definition stays.
- No
WHEREclause allowed. - Usually resets auto-increment counters (varies by DBMS).
- In most systems it can't be rolled back easily (PostgreSQL and SQL Server allow rollback inside a transaction).
5.5 DELETE vs TRUNCATE vs DROP
DELETE | TRUNCATE | DROP | |
|---|---|---|---|
| Category | DML | DDL | DDL |
| Removes | Chosen/all rows | All rows | Whole table |
WHERE allowed | β | β | β |
| Table structure kept | β | β | β |
| Speed on big tables | Slower | Fast | Fast |
| Rollback | Yes (in a transaction) | Usually no | Usually no |
6. INSERT, UPDATE and DELETE
These are DML commands: they change the data.
6.1 INSERT β add rows
Specify columns (recommended):
INSERT INTO Student (student_id, name, age, dept_id, marks, city, email)
VALUES (1, 'Riya', 20, 'D1', 87, 'Bhopal', 'riya@x.com');
Insert several rows in one statement:
INSERT INTO Student (student_id, name, age, dept_id, marks, city, email)
VALUES
(2, 'Aman', 21, 'D2', 74, 'Indore', 'aman@x.com'),
(3, 'Sara', 20, 'D1', 91, 'Bhopal', 'sara@x.com');
Insert only some columns (others get DEFAULT or NULL):
INSERT INTO Student (student_id, name) VALUES (4, 'Neel');
Without a column list (values must match every column in order; fragile if the table changes):
INSERT INTO Student VALUES (5, 'Zoya', 19, 'D2', 68, 'Delhi', 'zoya@x.com');
β Always list the columns explicitly. It's clearer and safer when the table structure changes.
6.2 UPDATE β change existing rows
UPDATE Student
SET marks = 90
WHERE student_id = 1;
Update several columns:
UPDATE Student
SET city = 'Mumbai', age = age + 1
WHERE student_id = 2;
Update using an expression for many rows:
UPDATE Student SET marks = marks + 5 WHERE dept_id = 'D1';
π¨ Danger:
UPDATE Student SET marks = 0;with noWHEREupdates every row!
6.3 DELETE β remove rows
DELETE FROM Student WHERE student_id = 5;
DELETE FROM Student WHERE marks < 40;
π¨
DELETE FROM Student;with noWHEREdeletes all rows (the table remains).
6.4 Safe-practice checklist
- Test the condition with
SELECTfirst:SELECT * FROM Student WHERE marks < 40; - Always include
WHEREunless you truly mean "all rows". - Run risky changes inside a transaction (
BEGIN; ... ROLLBACK;orCOMMIT;) so you can undo mistakes. - Remember constraints may reject the change (e.g., deleting a department that students still reference).
7. SELECT Statements
SELECT reads data. It never changes the table.
7.1 Basic forms
SELECT * FROM Student; -- all columns, all rows
SELECT name, marks FROM Student; -- specific columns
SELECT 'Hello'; -- no table needed (most DBMSs; Oracle needs FROM DUAL)
SELECT 5 * 4 AS result; -- expressions
7.2 SELECT * β handle with care
- Great for quick exploration.
- In real applications, list the columns you need: it is faster, clearer, and won't break when the table gains new columns.
7.3 Clause order (how you write a query)
SELECT columns
FROM table
WHERE row filter
ORDER BY sort order
LIMIT number of rows; -- (or FETCH / TOP, dialect-dependent)
7.4 Logical order (how the database thinks about it)
1. FROM β pick the table
2. WHERE β filter rows
3. SELECT β choose/compute columns (aliases are created here)
4. DISTINCT β remove duplicates
5. ORDER BY β sort
6. LIMIT / OFFSET / FETCH β trim the result
π‘ This explains why a column alias from
SELECTcan't be used inWHERE(WHERE runs first) but can be used inORDER BY(it runs later).
7.5 Example
SELECT name, marks
FROM Student
WHERE dept_id = 'D1'
ORDER BY marks DESC;
Result:
| name | marks |
|---|---|
| Sara | 91 |
| Riya | 87 |
8. WHERE, DISTINCT and ORDER BY
8.1 WHERE β filter rows
Keeps only rows for which the condition is TRUE.
SELECT * FROM Student WHERE city = 'Bhopal';
SELECT name FROM Student WHERE marks > 80 AND age < 21;
- Rows where the condition is FALSE or UNKNOWN (NULL) are dropped.
- Text comparison is case-sensitive or not depending on the DBMS and collation.
8.2 DISTINCT β remove duplicate rows from the result
SELECT DISTINCT city FROM Student;
| city |
|---|
| Bhopal |
| Indore |
| Pune |
| Delhi |
With multiple columns, the combination must be unique:
SELECT DISTINCT city, dept_id FROM Student;
DISTINCTapplies to the whole row of selected columns, not just the first column.- NULLs are treated as equal to each other for
DISTINCT(one NULL row remains). DISTINCTcan slow queries on big data because the database must compare/sort rows.
8.3 ORDER BY β sort the result
SELECT name, marks FROM Student ORDER BY marks; -- ascending (default)
SELECT name, marks FROM Student ORDER BY marks DESC; -- descending
Sort by multiple columns (second column breaks ties):
SELECT name, city, marks FROM Student
ORDER BY city ASC, marks DESC;
Other handy forms
ORDER BY 2 DESC; -- by the 2nd selected column (position; avoid in production code)
ORDER BY total_marks DESC; -- by an alias (allowed in ORDER BY)
ORDER BY LENGTH(name); -- by an expression
Where do NULLs go?
- Behaviour differs: some systems put NULLs first in ascending order (MySQL, SQL Server), others last (PostgreSQL, Oracle).
- PostgreSQL and Oracle let you control it:
ORDER BY marks DESC NULLS LAST;
β οΈ Without
ORDER BY, the order of rows is not guaranteed. Never rely on "natural" order.
9. LIMIT, OFFSET and FETCH
These restrict how many rows come back, which is useful for "top N" and pagination.
9.1 LIMIT and OFFSET (MySQL, PostgreSQL, SQLite)
SELECT name, marks FROM Student
ORDER BY marks DESC
LIMIT 3; -- top 3 rows
SELECT name, marks FROM Student
ORDER BY marks DESC
LIMIT 3 OFFSET 3; -- skip 3 rows, then take 3
(MySQL also accepts the shorthand LIMIT 3, 3 β LIMIT offset, count.)
9.2 TOP (SQL Server)
SELECT TOP 3 name, marks FROM Student ORDER BY marks DESC;
9.3 OFFSET ... FETCH (SQL standard; Oracle 12c+, PostgreSQL, SQL Server)
SELECT name, marks FROM Student
ORDER BY marks DESC
OFFSET 3 ROWS
FETCH NEXT 3 ROWS ONLY;
SELECT name, marks FROM Student
ORDER BY marks DESC
FETCH FIRST 3 ROWS ONLY; -- top 3
- In SQL Server,
OFFSET ... FETCHrequires anORDER BY. FETCH FIRST n ROWS WITH TIES(where supported) also returns extra rows that tie with the last one.
9.4 Comparison
| Goal | MySQL / PostgreSQL / SQLite | SQL Server | Standard / Oracle |
|---|---|---|---|
| First N rows | LIMIT N | TOP N | FETCH FIRST N ROWS ONLY |
| Skip M, take N | LIMIT N OFFSET M | OFFSET M ROWS FETCH NEXT N ROWS ONLY | OFFSET M ROWS FETCH NEXT N ROWS ONLY |
9.5 Pagination formula
For page number p (starting at 1) with page size s:
OFFSET = (p β 1) Γ s, LIMIT = s
Example: page 3, 10 rows per page β LIMIT 10 OFFSET 20.
9.6 Important cautions
- Always combine with
ORDER BY; otherwise the "first N rows" are arbitrary. - Very large offsets (like
OFFSET 1000000) can be slow, because the database still has to walk past all skipped rows.
10. Aliases and Column Expressions
10.1 Column aliases (AS)
An alias gives a column or expression a temporary, friendlier name in the result.
SELECT name AS student_name, marks AS score FROM Student;
- The word
ASis optional in most systems (marks score), but writing it is clearer. - Use quotes if the alias has spaces:
AS "Student Name"(standard) or backticks in MySQL / brackets in SQL Server. - The alias exists only for that query's output; it doesn't rename anything in the table.
- Aliases can be used in
ORDER BY, but not inWHERE(see Section 7.4).
10.2 Table aliases
A short nickname for a table, especially useful when a query uses several tables or long names.
SELECT s.name, s.marks
FROM Student AS s
WHERE s.marks > 80;
(In Oracle, write the table alias without AS: FROM Student s.)
10.3 Column expressions
You can compute values in the SELECT list using operators and functions.
Arithmetic
SELECT name, marks, marks + 5 AS grace_marks,
marks / 100.0 * 100 AS percentage
FROM Student;
Operators: + - * / % (modulo; MOD() in some systems).
String concatenation (dialect differences!)
SELECT name || ' from ' || city FROM Student; -- standard, PostgreSQL, Oracle, SQLite
SELECT CONCAT(name, ' from ', city) FROM Student; -- MySQL, PostgreSQL, SQL Server
SELECT name + ' from ' + city FROM Student; -- SQL Server
Common built-in functions (names vary slightly by DBMS)
| Purpose | Examples |
|---|---|
| Text | UPPER(name), LOWER(name), LENGTH(name), TRIM(name), SUBSTRING(name, 1, 3) |
| Numeric | ROUND(x, 2), ABS(x), CEIL(x), FLOOR(x) |
| Null handling | COALESCE(marks, 0) (first non-NULL value) |
| Type conversion | CAST(marks AS DECIMAL(5,2)) |
| Date | CURRENT_DATE, CURRENT_TIMESTAMP |
Handling NULL in expressions
SELECT name, marks + 5 AS bumped FROM Student; -- NULL + 5 = NULL
SELECT name, COALESCE(marks, 0) + 5 AS bumped FROM Student; -- NULL treated as 0
Any arithmetic with
NULLgivesNULL.
11. Comparison and Logical Operators
11.1 Comparison operators
| Operator | Meaning | Example |
|---|---|---|
= | Equal to | marks = 90 |
<> or != | Not equal to (<> is the standard) | city <> 'Pune' |
< | Less than | age < 21 |
> | Greater than | marks > 80 |
<= | Less than or equal to | age <= 20 |
>= | Greater than or equal to | marks >= 75 |
- SQL uses a single
=for comparison (not==). - Comparing with
NULLusing these operators gives UNKNOWN. UseIS NULL(Section 12). - Comparisons also work on text (alphabetical order) and dates (chronological order).
11.2 Logical operators (overview)
| Operator | Meaning |
|---|---|
AND | All conditions must be true |
OR | At least one condition must be true |
NOT | Reverses a condition |
BETWEEN, IN, LIKE, IS NULL, EXISTS, ANY/ALL | Special condition operators (the first four are explained in Sections 12β13) |
11.3 Operator groups at a glance
| Group | Operators |
|---|---|
| Arithmetic | + - * / % |
| Comparison | = <> != < > <= >= |
| Logical | AND OR NOT |
| Special | BETWEEN IN LIKE IS NULL |
| String | || (or CONCAT) |
12. BETWEEN, IN, LIKE and IS NULL
12.1 BETWEEN β within a range (inclusive of both ends)
SELECT name, marks FROM Student WHERE marks BETWEEN 70 AND 90;
-- same as: marks >= 70 AND marks <= 90
- Order matters:
BETWEEN low AND high(a reversed range returns nothing). - Works with numbers, text and dates:
WHERE join_date BETWEEN '2026-01-01' AND '2026-03-31'. - Negate with
NOT BETWEEN.
12.2 IN β matches any value in a list
SELECT name, city FROM Student WHERE city IN ('Bhopal', 'Pune');
-- same as: city = 'Bhopal' OR city = 'Pune'
- Negate with
NOT IN. - The list can also come from a subquery:
SELECT name FROM Student WHERE dept_id IN (SELECT dept_id FROM Department WHERE dept_name LIKE 'C%');
β οΈ
NOT INtrap: if the list (or subquery result) contains a NULL,NOT INreturns no rows, because comparisons with NULL are UNKNOWN. Make sure the list has no NULLs, or filter them out.
12.3 LIKE β pattern matching on text
| Wildcard | Meaning |
|---|---|
% | Any number of characters (including none) |
_ | Exactly one character |
SELECT name FROM Student WHERE name LIKE 'S%'; -- starts with S
SELECT name FROM Student WHERE name LIKE '%a'; -- ends with a
SELECT name FROM Student WHERE name LIKE '%ia%'; -- contains "ia"
SELECT name FROM Student WHERE name LIKE '_a%'; -- 2nd letter is 'a'
SELECT name FROM Student WHERE name LIKE '____'; -- exactly 4 characters
SELECT name FROM Student WHERE name NOT LIKE 'A%';
Searching for a literal % or _ β use ESCAPE:
SELECT * FROM Offers WHERE title LIKE '50\%%' ESCAPE '\'; -- titles starting with "50%"
Case sensitivity:
- MySQL, SQL Server: usually case-insensitive (depends on collation)
- PostgreSQL, Oracle:
LIKEis case-sensitive; PostgreSQL offersILIKEfor case-insensitive matching.
π‘ Patterns starting with
%(like'%ia') can't use a normal index well, so they may be slow on big tables.
12.4 IS NULL / IS NOT NULL β test for missing values
SELECT name FROM Student WHERE marks IS NULL;
SELECT name FROM Student WHERE email IS NOT NULL;
- β
WHERE marks = NULLnever works (the result is UNKNOWN, never TRUE). NULLβ0andNULLβ''.- Replace NULLs in output with
COALESCE(marks, 0).
12.5 Quick comparison
| Operator | Tests for | Example |
|---|---|---|
BETWEEN a AND b | Inside a range (inclusive) | age BETWEEN 19 AND 21 |
IN (β¦) | Matches one of several values | city IN ('Pune','Delhi') |
LIKE | Text pattern | name LIKE 'R%' |
IS NULL | Missing value | marks IS NULL |
13. AND, OR and NOT
13.1 What they do
SELECT name FROM Student WHERE city = 'Bhopal' AND marks > 85; -- both true
SELECT name FROM Student WHERE city = 'Pune' OR city = 'Delhi'; -- either true
SELECT name FROM Student WHERE NOT city = 'Bhopal'; -- reverses
13.2 Truth tables (with NULL β UNKNOWN)
AND
| A \ B | TRUE | FALSE | UNKNOWN |
|---|---|---|---|
| TRUE | TRUE | FALSE | UNKNOWN |
| FALSE | FALSE | FALSE | FALSE |
| UNKNOWN | UNKNOWN | FALSE | UNKNOWN |
OR
| A \ B | TRUE | FALSE | UNKNOWN |
|---|---|---|---|
| TRUE | TRUE | TRUE | TRUE |
| FALSE | TRUE | FALSE | UNKNOWN |
| UNKNOWN | TRUE | UNKNOWN | UNKNOWN |
NOT
| A | NOT A |
|---|---|
| TRUE | FALSE |
| FALSE | TRUE |
| UNKNOWN | UNKNOWN |
WHERE keeps only rows whose final result is TRUE.
13.3 Precedence (who goes first?)
NOT(highest)ANDOR(lowest)
13.4 The classic mistake
-- Intended: students from Bhopal or Pune who scored above 85
SELECT name FROM Student
WHERE city = 'Bhopal' OR city = 'Pune' AND marks > 85;
Because AND binds first, this actually means:
city = 'Bhopal' OR (city = 'Pune' AND marks > 85) β
Fix with parentheses:
SELECT name FROM Student
WHERE (city = 'Bhopal' OR city = 'Pune') AND marks > 85; -- β
β Rule of thumb: whenever you mix
ANDandOR, use parentheses, even if you're sure about the precedence.
13.5 Combining with other operators
SELECT name FROM Student
WHERE age BETWEEN 19 AND 21
AND city IN ('Bhopal', 'Delhi')
AND NOT (marks IS NULL);
You can negate special operators directly: NOT BETWEEN, NOT IN, NOT LIKE, IS NOT NULL.
14. SQL Constraints: PRIMARY KEY, FOREIGN KEY, UNIQUE, NOT NULL, CHECK, DEFAULT
14.1 What are constraints?
Constraints are rules enforced by the database on the data in a table. They keep data valid and consistent; any INSERT, UPDATE or DELETE that would break a rule is rejected.
14.2 The six constraints at a glance
| Constraint | Rule | Allows NULL? | Per table |
|---|---|---|---|
| PRIMARY KEY | Uniquely identifies each row | β No | One (can be multi-column) |
| FOREIGN KEY | Value must exist in another table's key (or be NULL) | β Yes | Many |
| UNIQUE | No duplicate values | β Yes (rules for multiple NULLs vary by DBMS) | Many |
| NOT NULL | A value is required | β No | Per column |
| CHECK | Value must satisfy a condition | β (NULL passes unless combined with NOT NULL) | Many |
| DEFAULT | Value used when none is supplied | β | Per column |
14.3 PRIMARY KEY
CREATE TABLE Student (
student_id INT PRIMARY KEY, -- column level
name VARCHAR(50)
);
Composite primary key (table level):
CREATE TABLE Enrollment (
student_id INT,
course_id INT,
grade CHAR(2),
PRIMARY KEY (student_id, course_id)
);
- Automatically unique and NOT NULL.
- The database normally creates an index on it for fast lookup.
14.4 FOREIGN KEY
CREATE TABLE Student (
student_id INT PRIMARY KEY,
dept_id VARCHAR(5),
CONSTRAINT fk_student_dept
FOREIGN KEY (dept_id) REFERENCES Department(dept_id)
ON DELETE SET NULL
ON UPDATE CASCADE
);
- The referenced column must be a primary key or unique column.
- Referential actions:
RESTRICT/NO ACTION(reject; usual default),CASCADE,SET NULL,SET DEFAULT. - Prevents orphan rows (e.g., a student pointing to a department that doesn't exist).
- Create the parent table first, drop it last.
14.5 UNIQUE
CREATE TABLE Student (
student_id INT PRIMARY KEY,
email VARCHAR(100) UNIQUE,
phone VARCHAR(15),
UNIQUE (phone) -- table-level form
);
- A table can have many
UNIQUEconstraints, but only one primary key. - Can span several columns:
UNIQUE (first_name, last_name)β the pair must be unique.
14.6 NOT NULL
name VARCHAR(50) NOT NULL
- A value must be provided on insert/update.
- Column-level only (it isn't written as a table-level constraint in most systems).
14.7 CHECK
CREATE TABLE Student (
student_id INT PRIMARY KEY,
age INT CHECK (age BETWEEN 15 AND 60),
marks INT,
gender CHAR(1),
CONSTRAINT chk_marks CHECK (marks BETWEEN 0 AND 100),
CONSTRAINT chk_gender CHECK (gender IN ('M', 'F', 'O'))
);
- Rejects a row only when the condition is FALSE; if the result is UNKNOWN (because of NULL), the row is accepted. Add
NOT NULLif NULL shouldn't be allowed. - Can compare columns of the same row:
CHECK (end_date >= start_date). - MySQL enforces
CHECKfrom version 8.0.16; older versions parsed but ignored it.
14.8 DEFAULT
CREATE TABLE Student (
student_id INT PRIMARY KEY,
city VARCHAR(30) DEFAULT 'Unknown',
status VARCHAR(10) DEFAULT 'active',
joined_on DATE DEFAULT CURRENT_DATE
);
INSERT INTO Student (student_id) VALUES (10); -- city = 'Unknown', status = 'active'
- Used when a column is omitted in
INSERT(or when you write the keywordDEFAULT). - An explicit
NULLinsert stores NULL; it does not trigger the default.
14.9 Column-level vs table-level; naming constraints
CREATE TABLE Employee (
emp_id INT NOT NULL, -- column-level
email VARCHAR(100) NOT NULL,
salary INT DEFAULT 0 CHECK (salary >= 0),
dept_id VARCHAR(5),
CONSTRAINT pk_employee PRIMARY KEY (emp_id), -- table-level, named
CONSTRAINT uq_emp_email UNIQUE (email),
CONSTRAINT fk_emp_dept FOREIGN KEY (dept_id) REFERENCES Department(dept_id)
);
- Naming constraints (
CONSTRAINT name ...) makes error messages clearer and lets you drop or change them later. - Multi-column constraints must be written at the table level.
14.10 Adding and removing constraints later
ALTER TABLE Student ADD CONSTRAINT uq_email UNIQUE (email);
ALTER TABLE Student ADD CONSTRAINT chk_age CHECK (age >= 15);
ALTER TABLE Student ALTER COLUMN name SET NOT NULL; -- PostgreSQL / Oracle style differs slightly
ALTER TABLE Student DROP CONSTRAINT chk_age;
(Exact syntax for NOT NULL, DEFAULT and dropping constraints differs between MySQL, PostgreSQL, SQL Server and Oracle.)
15. AUTO INCREMENT / Identity Columns
15.1 What and why?
An auto-increment (identity) column automatically generates the next number for each new row, so you don't have to supply the ID yourself. It's most often used for primary keys (these are called surrogate keys).
15.2 Syntax in different systems
| DBMS | Syntax |
|---|---|
| MySQL / MariaDB | id INT AUTO_INCREMENT PRIMARY KEY |
| PostgreSQL | id SERIAL PRIMARY KEY (older style) or id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY |
| SQL Server | id INT IDENTITY(1,1) PRIMARY KEY β (start, step) |
| Oracle (12c+) | id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY (older versions used sequences + triggers) |
| SQLite | id INTEGER PRIMARY KEY (auto-assigns) or id INTEGER PRIMARY KEY AUTOINCREMENT |
| SQL standard | `GENERATED { ALWAYS |
15.3 Example (MySQL)
CREATE TABLE Course (
course_id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(60) NOT NULL
);
INSERT INTO Course (title) VALUES ('Databases'); -- course_id = 1
INSERT INTO Course (title) VALUES ('Networks'); -- course_id = 2
15.4 ALWAYS vs BY DEFAULT (standard identity)
| Option | Behaviour |
|---|---|
GENERATED ALWAYS AS IDENTITY | The system always generates the value; manual values are rejected (unless overridden explicitly) |
GENERATED BY DEFAULT AS IDENTITY | The system generates a value unless you supply one |
15.5 Customising the sequence
-- SQL Server: start at 1000, step 5
id INT IDENTITY(1000, 5) PRIMARY KEY
-- MySQL: change the next value
ALTER TABLE Course AUTO_INCREMENT = 500;
-- PostgreSQL / standard
id INT GENERATED ALWAYS AS IDENTITY (START WITH 100 INCREMENT BY 10)
15.6 Getting the ID of the row you just inserted
| DBMS | How |
|---|---|
| MySQL | SELECT LAST_INSERT_ID(); |
| PostgreSQL | INSERT ... RETURNING course_id; |
| SQL Server | SELECT SCOPE_IDENTITY(); |
| SQLite | SELECT last_insert_rowid(); |
15.7 Things to know
- Gaps are normal. Rolled-back inserts, failed inserts and deleted rows leave holes in the sequence; the counter doesn't go back.
- Deleting rows doesn't reset the counter.
TRUNCATEoften does (depends on the DBMS). - Only one identity column per table in most systems.
- Don't assume IDs are consecutive or carry meaning; treat them as simple unique labels.
16. INSERT INTO SELECT
16.1 What is it?
It copies rows from one query's result into an existing table in a single statement. Instead of VALUES, you supply a SELECT.
16.2 Syntax
INSERT INTO target_table (col1, col2, ...)
SELECT colA, colB, ...
FROM source_table
WHERE condition;
16.3 Examples
Archive old/low-score students:
CREATE TABLE StudentArchive (
student_id INT PRIMARY KEY,
name VARCHAR(50),
marks INT
);
INSERT INTO StudentArchive (student_id, name, marks)
SELECT student_id, name, marks
FROM Student
WHERE marks < 70;
Copy everything (same column structure):
INSERT INTO StudentBackup
SELECT * FROM Student;
Insert with computed values:
INSERT INTO StudentArchive (student_id, name, marks)
SELECT student_id, UPPER(name), COALESCE(marks, 0)
FROM Student
WHERE city = 'Bhopal';
16.4 Rules
- The number of columns in the
SELECTmust match the target column list. - The data types must be compatible, matched by position, not by name.
- Columns not listed get their
DEFAULTorNULL. - Constraints on the target table still apply: duplicate keys, NOT NULL, foreign keys, and so on can make the whole statement fail.
- The
SELECTcan useWHERE,ORDER BY, expressions and more.
16.5 Similar tools (for comparison)
| Command | Target table | Notes |
|---|---|---|
INSERT INTO ... SELECT | Must already exist | Supported almost everywhere |
CREATE TABLE new AS SELECT ... | Created on the fly | MySQL, PostgreSQL, Oracle, SQLite |
SELECT ... INTO new FROM ... | Created on the fly | SQL Server (and PostgreSQL's different meaning in procedural code) |
CREATE TABLE TopStudents AS
SELECT * FROM Student WHERE marks >= 85;
(Such copies usually don't carry over constraints, indexes or defaults.)
17. CASE WHEN Expressions
17.1 What is it?
CASE is SQL's if-then-else. It returns a value based on conditions. It's an expression (it produces a value), so it can be used almost anywhere a value is allowed.
17.2 Searched CASE (most flexible)
SELECT name, marks,
CASE
WHEN marks >= 90 THEN 'A'
WHEN marks >= 75 THEN 'B'
WHEN marks >= 60 THEN 'C'
ELSE 'D'
END AS grade
FROM Student;
| name | marks | grade |
|---|---|---|
| Riya | 87 | B |
| Aman | 74 | C |
| Sara | 91 | A |
| Neel | NULL | D |
| Zoya | 68 | C |
Notice Neel (
marksis NULL): everyWHEN marks >= ...is UNKNOWN, so it falls toELSE. Handle NULL explicitly when it matters:WHEN marks IS NULL THEN 'Absent'.
17.3 Simple CASE (compare one expression to values)
SELECT name,
CASE dept_id
WHEN 'D1' THEN 'Computer Science'
WHEN 'D2' THEN 'Electronics'
ELSE 'Other'
END AS department
FROM Student;
- Simple
CASEonly tests equality. For ranges or complex conditions, use the searched form.
17.4 How it works
- Conditions are checked top to bottom; the first true
WHENwins and the rest are skipped. - If nothing matches and there is no
ELSE, the result isNULL. - All
THEN/ELSEresults should be of compatible types (don't mix numbers and text). - Every
CASEmust end withEND.
17.5 Where you can use CASE
| Clause | Example |
|---|---|
SELECT | Create labels/categories (above) |
ORDER BY | Custom sort order |
UPDATE ... SET | Conditional updates |
WHERE | Conditional filters |
| Inside functions/aggregates | Conditional counting/summing |
Custom sort order
SELECT name, city FROM Student
ORDER BY CASE city
WHEN 'Bhopal' THEN 1
WHEN 'Indore' THEN 2
ELSE 3
END;
Conditional update (different raise per range)
UPDATE Student
SET marks = CASE
WHEN marks < 40 THEN marks + 10
WHEN marks < 60 THEN marks + 5
ELSE marks
END
WHERE marks IS NOT NULL;
Conditional counting
SELECT
COUNT(CASE WHEN marks >= 75 THEN 1 END) AS high_scorers,
COUNT(CASE WHEN marks < 75 THEN 1 END) AS others
FROM Student;
(COUNT ignores NULLs, so rows that don't match return NULL and aren't counted.)
17.6 Related shortcuts
| Function | Meaning |
|---|---|
COALESCE(a, b, c) | First non-NULL value (like a CASE on NULL) |
NULLIF(a, b) | Returns NULL if a = b, else a (handy to avoid divide-by-zero) |
18. Comments in SQL
18.1 Why comment?
Comments are notes ignored by the database. They explain why a query or table exists, document assumptions, or temporarily disable code.
18.2 Single-line comments
-- This selects only active students
SELECT * FROM Student WHERE marks > 80; -- inline comment at the end of a line
- Starts with two hyphens
--; runs to the end of the line. - MySQL requires a space after
--(-- comment) and also supports#:# MySQL-only style comment
18.3 Multi-line (block) comments
/*
Report: top students per city
Author: ...
Purpose: monthly review
*/
SELECT name, marks
FROM Student /* temporarily ignore filter */;
- Starts with
/*, ends with*/. - Can sit in the middle of a statement.
- Support for nested block comments varies (PostgreSQL supports it; MySQL doesn't).
18.4 Commenting out code while debugging
SELECT name, marks
FROM Student
-- WHERE city = 'Pune'
ORDER BY marks DESC;
18.5 Comment support by DBMS
| Style | MySQL | PostgreSQL | SQL Server | Oracle | SQLite |
|---|---|---|---|---|---|
-- text | β (needs space after) | β | β | β | β |
/* text */ | β | β | β | β | β |
# text | β | β | β | β | β |
18.6 Good commenting habits
- Explain the "why", not the obvious "what".
- Keep comments up to date (a wrong comment is worse than none).
- Be careful not to leave a stray comment marker that accidentally comments out part of your query.
- Some systems use special comment forms for optimizer hints or version-specific code, so avoid odd symbols right after
/*.
β‘ Quick Revision Cheat Sheet
Basics
- SQL is declarative; standardised by ANSI/ISO; every DBMS has its own dialect (T-SQL, PL/SQL, MySQL, PostgreSQLβ¦).
- Categories: DDL (CREATE, ALTER, DROP, TRUNCATE) Β· DML (INSERT, UPDATE, DELETE) Β· DQL (SELECT).
- Strings use single quotes; statements end with
;.
Data types
- Numbers:
INT,BIGINT,DECIMAL(p,s)(exact),FLOAT(approximate) - Text:
CHAR(fixed),VARCHAR(variable),TEXT - Date/time:
DATE,TIME,TIMESTAMP/DATETIME - Others:
BOOLEAN,JSON,BLOB,UUID,ENUM
Structure commands
CREATE DATABASE,USE db,CREATE TABLE,ALTER TABLE (ADD / DROP / MODIFY),DROP TABLE,TRUNCATE TABLE- DELETE = some/all rows (rollback-able) Β· TRUNCATE = all rows, keep table Β· DROP = table gone.
Data commands
INSERT INTO t (cols) VALUES (...)Β·UPDATE t SET c = v WHERE ...Β·DELETE FROM t WHERE ...- π¨ No
WHERE= all rows affected.
Querying
- Order written:
SELECT β FROM β WHERE β ORDER BY β LIMIT - Order executed:
FROM β WHERE β SELECT β DISTINCT β ORDER BY β LIMIT DISTINCTremoves duplicate rows Β·ORDER BY col [ASC|DESC](no ORDER BY = no guaranteed order)- Row limiting:
LIMIT n OFFSET mΒ·TOP n(SQL Server) Β·OFFSET m ROWS FETCH NEXT n ROWS ONLY(standard) - Pagination:
OFFSET = (page β 1) Γ size
Operators
- Comparison:
= <> != < > <= >= BETWEEN a AND b(inclusive) Β·IN (...)Β·LIKE(%many,_one) Β·IS [NOT] NULL- Precedence: NOT > AND > OR; use parentheses.
= NULLβ β useIS NULLβ .NOT IN+ NULL in list = no rows.
Aliases & expressions
col AS alias,table AS t; alias usable inORDER BY, not inWHERE.- Concatenation:
||/CONCAT()/+(SQL Server).COALESCE(x, default)handles NULL.
Constraints
PRIMARY KEY(unique + not null, one per table) Β·FOREIGN KEY(references parent key) Β·UNIQUEΒ·NOT NULLΒ·CHECK (condition)Β·DEFAULT value.
Auto numbering
AUTO_INCREMENT(MySQL) Β·SERIAL/IDENTITY(PostgreSQL) Β·IDENTITY(1,1)(SQL Server) Β·GENERATED ... AS IDENTITY(standard/Oracle) Β· gaps are normal.
Other
INSERT INTO t (cols) SELECT ...copies rows into an existing table; columns match by position.CASE WHEN cond THEN x ... ELSE y ENDΒ· first true branch wins Β· noELSEβ NULL.- Comments:
-- line,/* block */,# line(MySQL only).
π€ Commonly asked questions (from these topics)
- What is SQL? What are DDL, DML and DQL?
- What are SQL standards and dialects? Give examples of dialect differences.
- What is the difference between
CHARandVARCHAR? Why useDECIMALfor money? - Differentiate
DELETE,TRUNCATEandDROP. - What happens if you run
UPDATEorDELETEwithoutWHERE? - What is the difference between
WHEREandORDER BY? What doesDISTINCTdo? - How do you get the top 5 rows, or implement pagination, in different databases?
- Why can't a column alias be used in
WHERE? - Explain
BETWEEN,IN,LIKEandIS NULL. Why does= NULLfail? - What is the precedence of
AND,ORandNOT? Why use parentheses? - Explain the six constraints: PRIMARY KEY, FOREIGN KEY, UNIQUE, NOT NULL, CHECK, DEFAULT.
- What is the difference between
PRIMARY KEYandUNIQUE? - How do identity/auto-increment columns work in MySQL, PostgreSQL and SQL Server?
- What does
INSERT INTO ... SELECTdo, and what rules apply? - Explain
CASE WHENwith an example. What happens with noELSE? - How do you write comments in SQL?
#SQL Joins and Set Operations#
Study notes in plain language. Every concept is shown on the same small sample tables, so you can follow the results by eye and compare one join with another. Read it top to bottom the first time; later, jump to the section you need.
Table of Contents
Part A: Joins
- INNER JOIN
- LEFT JOIN
- RIGHT JOIN
- FULL OUTER JOIN
- CROSS JOIN
- SELF JOIN
- Joining Multiple Tables
- Joining Three or More Tables
- Joins Using Multiple Conditions
Part B: Set Operations
Part C: Related Topics
- Join vs Subquery
- Handling Duplicate Rows
- NULL Handling in Joins
- Common Mistakes and Debugging Tips
- Quick Revision Cheat Sheet
1. Why Joins and Set Operations?
A well-designed relational database splits data across several tables to avoid repeating information (for example, employee details in one table and department details in another). To answer real questions such as "Which department does each employee work in?", you need to bring the data back together. That is what these tools do:
| Tool | What it does | Think of it as |
|---|---|---|
| Join | Combines columns from two or more tables, side by side, by matching rows | Widening a table (adding columns) |
| Set operation | Combines rows from two or more queries, one on top of another | Lengthening or comparing result sets (adding, intersecting, subtracting rows) |
JOIN (sideways) SET OPERATION (vertical)
+-------+ +-------+ +-------+
| A | + | B | -> A B side | A |
+-------+ +-------+ by side +-------+
| B | -> A rows then B rows
+-------+
2. Sample Tables Used Throughout
Run these once (syntax is standard SQL; small changes may be needed for your DBMS) and every example in these notes will work.
CREATE TABLE department (
dept_id INT PRIMARY KEY,
dept_name VARCHAR(30)
);
CREATE TABLE employee (
emp_id INT PRIMARY KEY,
emp_name VARCHAR(30),
dept_id INT, -- may be NULL
manager_id INT -- refers to emp_id of the manager, may be NULL
);
CREATE TABLE project (
proj_id INT PRIMARY KEY,
proj_name VARCHAR(30)
);
CREATE TABLE works_on (
emp_id INT,
proj_id INT,
PRIMARY KEY (emp_id, proj_id)
);
INSERT INTO department VALUES (10,'HR'), (20,'IT'), (30,'Sales');
INSERT INTO employee VALUES
(1,'Riya', 10, NULL),
(2,'Arjun', 20, 1),
(3,'Meena', 20, 2),
(4,'Karan', NULL, 2);
INSERT INTO project VALUES (100,'Alpha'), (200,'Beta');
INSERT INTO works_on VALUES (1,100), (2,100), (2,200), (3,200);
The Data
department
| dept_id | dept_name |
|---|---|
| 10 | HR |
| 20 | IT |
| 30 | Sales |
employee
| emp_id | emp_name | dept_id | manager_id |
|---|---|---|---|
| 1 | Riya | 10 | NULL |
| 2 | Arjun | 20 | 1 |
| 3 | Meena | 20 | 2 |
| 4 | Karan | NULL | 2 |
project
| proj_id | proj_name |
|---|---|
| 100 | Alpha |
| 200 | Beta |
works_on
| emp_id | proj_id |
|---|---|
| 1 | 100 |
| 2 | 100 |
| 2 | 200 |
| 3 | 200 |
Two Deliberate "Gaps" in the Data
- Karan has no department (
dept_idis NULL), so he is an employee with no match indepartment. - Sales has no employees, so it is a department with no match in
employee.
These gaps are what make the different join types give different results.
The Basic Join Idea
A join compares rows of one table with rows of another using a join condition (usually table1.key = table2.key, typically a foreign key matched to a primary key). Rows that satisfy the condition are paired up.
PART A: JOINS
3. INNER JOIN
3.1 What It Does
Returns only the rows that have a match in both tables. Rows without a match on either side are dropped.
Table A Table B
+-------+ +-------+
| ####|######|#### | #### = the overlap (matching rows)
| ####|######|#### | INNER JOIN returns only the overlap
+-------+ +-------+
3.2 Syntax
SELECT columns
FROM table1
INNER JOIN table2
ON table1.key = table2.key;
INNER is optional; plain JOIN means INNER JOIN.
3.3 Example
"Show each employee with their department name."
SELECT e.emp_name, d.dept_name
FROM employee e
INNER JOIN department d
ON e.dept_id = d.dept_id;
Result
| emp_name | dept_name |
|---|---|
| Riya | HR |
| Arjun | IT |
| Meena | IT |
- Karan is missing: his
dept_idis NULL, so he matches nothing. - Sales is missing: no employee works there.
3.4 Key Points
- The number of result rows depends on how many pairs satisfy the condition. A row in A that matches 3 rows in B appears 3 times.
- Order of tables does not change the result of an inner join (it is symmetric):
A JOIN Bgives the same rows asB JOIN A. - Use table aliases (
e,d) to keep queries short and to avoid ambiguity. - If a column name exists in both tables, you must qualify it (
e.dept_id), otherwise you get an "ambiguous column" error.
3.5 Other Ways to Write an Inner Join
USING (when the join column has the same name in both tables):
SELECT emp_name, dept_name
FROM employee
JOIN department USING (dept_id);
The join column appears once in the output.
NATURAL JOIN (automatically joins on all columns with the same name):
SELECT * FROM employee NATURAL JOIN department;
Convenient but risky: if someone adds a new same-named column later, the join silently changes. Most professionals avoid it.
Old comma-style join (implicit join)
SELECT e.emp_name, d.dept_name
FROM employee e, department d
WHERE e.dept_id = d.dept_id;
This works, but forgetting the WHERE produces an accidental cross join. Prefer the explicit JOIN ... ON syntax.
3.6 When to Use
Use an inner join when you only want rows that exist on both sides, such as employees who actually have a department.
4. LEFT JOIN
Also called LEFT OUTER JOIN (OUTER is optional).
4.1 What It Does
Returns all rows from the left table, plus the matching rows from the right table. Where there is no match, the right table's columns are filled with NULL.
Left table Right table
+-------+ +-------+
|### |######|#### | ### = ALL of the left table
|### |######|#### | plus the matching part of the right
+-------+ +-------+
4.2 Syntax
SELECT columns
FROM left_table
LEFT JOIN right_table
ON left_table.key = right_table.key;
4.3 Example
"List all employees and their department, even if they have none."
SELECT e.emp_name, d.dept_name
FROM employee e
LEFT JOIN department d
ON e.dept_id = d.dept_id;
Result
| emp_name | dept_name |
|---|---|
| Riya | HR |
| Arjun | IT |
| Meena | IT |
| Karan | NULL |
Karan is now included, with NULL for the department.
4.4 The "Anti-Join" Pattern: Finding Rows With No Match
Add a filter on the right table's key being NULL to find rows in the left table with no partner:
-- Employees that have no department
SELECT e.emp_name
FROM employee e
LEFT JOIN department d
ON e.dept_id = d.dept_id
WHERE d.dept_id IS NULL;
Result: Karan
Test for NULL on a column that is never NULL in the right table (usually its primary key), so you do not confuse "no match" with "matched but the column happened to be NULL".
4.5 ON vs WHERE in a LEFT JOIN (Very Important)
For an inner join, a condition in ON or WHERE gives the same result. For an outer join, they behave differently.
ONcondition: decides which right-side rows match. Left rows are kept regardless.WHEREcondition: filters the final result, and can remove the NULL-extended rows.
Goal: all employees, showing the department name only if it is IT.
-- Condition in ON: all employees stay, only IT gets a name
SELECT e.emp_name, d.dept_name
FROM employee e
LEFT JOIN department d
ON e.dept_id = d.dept_id AND d.dept_name = 'IT';
| emp_name | dept_name |
|---|---|
| Riya | NULL |
| Arjun | IT |
| Meena | IT |
| Karan | NULL |
-- Condition in WHERE: removes everyone who is not in IT (turns into an inner join!)
SELECT e.emp_name, d.dept_name
FROM employee e
LEFT JOIN department d
ON e.dept_id = d.dept_id
WHERE d.dept_name = 'IT';
| emp_name | dept_name |
|---|---|
| Arjun | IT |
| Meena | IT |
Rule of thumb: to keep all left rows, put conditions on the right table in
ON. AWHEREcondition on right-table columns (other thanIS NULLtests) effectively turns a left join into an inner join.
4.6 When to Use
"All of X, with Y if available": all customers with their orders (if any), all employees with their department (if any).
5. RIGHT JOIN
Also called RIGHT OUTER JOIN.
5.1 What It Does
The mirror image of a left join: returns all rows from the right table, plus matching rows from the left. Where there is no match, left-table columns are NULL.
Left table Right table
+-------+ +-------+
| ####|######|### | ### = ALL of the right table
| ####|######|### | plus the matching part of the left
+-------+ +-------+
5.2 Example
"List all departments with their employees, even departments with no employees."
SELECT e.emp_name, d.dept_name
FROM employee e
RIGHT JOIN department d
ON e.dept_id = d.dept_id;
Result
| emp_name | dept_name |
|---|---|
| Riya | HR |
| Arjun | IT |
| Meena | IT |
| NULL | Sales |
Sales appears with NULL for the employee; Karan does not (he has no department, and the right table is the one fully preserved).
5.3 RIGHT JOIN = LEFT JOIN with Tables Swapped
These two queries return the same rows:
FROM employee e RIGHT JOIN department d ON e.dept_id = d.dept_id
FROM department d LEFT JOIN employee e ON e.dept_id = d.dept_id
For this reason, many developers always use LEFT JOIN and just order the tables as needed. It keeps queries consistent and easier to read (the "main" table first).
5.4 When to Use
When the table you want to keep in full is on the right of the join and you do not want to rearrange the query.
6. FULL OUTER JOIN
6.1 What It Does
Returns all rows from both tables. Where rows match, they are combined; where they do not, the missing side is filled with NULL.
Left table Right table
+-------+ +-------+
|### |######| ###| ALL of the left + ALL of the right,
|### |######| ###| matched where possible
+-------+ +-------+
Full outer join = left join results + right join results (without double counting the matches).
6.2 Example
SELECT e.emp_name, d.dept_name
FROM employee e
FULL OUTER JOIN department d
ON e.dept_id = d.dept_id;
Result
| emp_name | dept_name |
|---|---|
| Riya | HR |
| Arjun | IT |
| Meena | IT |
| Karan | NULL |
| NULL | Sales |
Both gaps appear: Karan (no department) and Sales (no employees).
6.3 Finding Rows That Match on Only One Side
SELECT e.emp_name, d.dept_name
FROM employee e
FULL OUTER JOIN department d
ON e.dept_id = d.dept_id
WHERE e.emp_id IS NULL OR d.dept_id IS NULL;
Result: Karan with NULL, and NULL with Sales. This is useful for data reconciliation (finding mismatches between two tables).
6.4 DBMS Support: MySQL Does Not Have FULL OUTER JOIN
MySQL does not support FULL OUTER JOIN. Emulate it by combining a left join and a right join with UNION:
SELECT e.emp_name, d.dept_name
FROM employee e LEFT JOIN department d ON e.dept_id = d.dept_id
UNION
SELECT e.emp_name, d.dept_name
FROM employee e RIGHT JOIN department d ON e.dept_id = d.dept_id;
UNION (not UNION ALL) removes the duplicated matched rows that both halves return.
6.5 When to Use
When you need everything from both sides and want to see what is unmatched on each: comparing two lists, audits, reconciliation.
7. CROSS JOIN
7.1 What It Does
Returns the Cartesian product: every row of the first table paired with every row of the second. There is no join condition.
Rows in result = (rows in A) Γ (rows in B)
7.2 Syntax
SELECT columns
FROM table1
CROSS JOIN table2;
-- Old equivalent (comma with no WHERE):
SELECT columns FROM table1, table2;
7.3 Example
SELECT e.emp_name, d.dept_name
FROM employee e
CROSS JOIN department d;
4 employees Γ 3 departments = 12 rows:
| emp_name | dept_name |
|---|---|
| Riya | HR |
| Riya | IT |
| Riya | Sales |
| Arjun | HR |
| Arjun | IT |
| Arjun | Sales |
| Meena | HR |
| Meena | IT |
| Meena | Sales |
| Karan | HR |
| Karan | IT |
| Karan | Sales |
7.4 Useful Real-World Uses
- Generating all combinations: sizes Γ colours for a product catalogue, dates Γ products for a report grid.
- Building a calendar or test data.
- Creating a "complete grid" and then using a left join to find which combinations are missing.
-- All combinations of size and colour
SELECT s.size, c.colour
FROM size s
CROSS JOIN colour c;
7.5 Warning
Cross joins grow fast: 10,000 Γ 10,000 rows = 100 million rows. An accidental cross join (a missing join condition) is a classic cause of a query that returns far too many rows or runs forever.
8. SELF JOIN
8.1 What It Does
A self join joins a table to itself. It is not a special keyword; it is an ordinary join (inner, left, ...) where the same table appears twice under different aliases.
You need it when rows in a table are related to other rows of the same table: employee β manager, category β parent category, a person β a friend.
8.2 Example: Employee and Manager
The employee table stores each person's manager as manager_id, which is another emp_id in the same table.
SELECT e.emp_name AS employee,
m.emp_name AS manager
FROM employee e
INNER JOIN employee m
ON e.manager_id = m.emp_id;
Think of e as the "employee copy" and m as the "manager copy" of the table.
Result
| employee | manager |
|---|---|
| Arjun | Riya |
| Meena | Arjun |
| Karan | Arjun |
Riya is missing because her manager_id is NULL (she has no manager). To include her, use a left join:
SELECT e.emp_name AS employee,
m.emp_name AS manager
FROM employee e
LEFT JOIN employee m
ON e.manager_id = m.emp_id;
| employee | manager |
|---|---|
| Riya | NULL |
| Arjun | Riya |
| Meena | Arjun |
| Karan | Arjun |
8.3 Example: Finding Pairs in the Same Group
"Pairs of employees who work in the same department."
SELECT a.emp_name AS emp1, b.emp_name AS emp2, a.dept_id
FROM employee a
JOIN employee b
ON a.dept_id = b.dept_id
AND a.emp_id < b.emp_id;
Result
| emp1 | emp2 | dept_id |
|---|---|---|
| Arjun | Meena | 20 |
The condition a.emp_id < b.emp_id does two jobs:
- Stops a row pairing with itself (Arjun with Arjun).
- Avoids mirrored duplicates (it keeps ArjunβMeena but not MeenaβArjun).
8.4 Example: Comparing Rows Within the Same Table
"Employees whose employee ID is higher than their manager's" (any comparison between a row and a related row):
SELECT e.emp_name
FROM employee e
JOIN employee m ON e.manager_id = m.emp_id
WHERE e.emp_id > m.emp_id;
8.5 Key Points
- Aliases are mandatory (otherwise SQL cannot tell the two copies apart).
- Join on a parent-child relationship (
child.parent_id = parent.id). - A self join only looks one level up or down. Multi-level hierarchies need either multiple self joins (one per level) or a recursive query.
- For two levels: join the table three times (employee β manager β manager's manager).
9. Joining Multiple Tables
9.1 The Idea
You can chain several joins in one query. Each JOIN ... ON ... adds one more table. Conceptually, SQL joins the first two tables, then joins that result with the third, and so on.
SELECT ...
FROM t1
JOIN t2 ON t1.k = t2.k
JOIN t3 ON t2.k = t3.k
JOIN t4 ON t3.k = t4.k;
9.2 Walkthrough with Two Joins
"Show each employee's name with their department name and manager's name." This combines an ordinary join and a self join.
SELECT e.emp_name AS employee,
d.dept_name AS department,
m.emp_name AS manager
FROM employee e
LEFT JOIN department d ON e.dept_id = d.dept_id
LEFT JOIN employee m ON e.manager_id = m.emp_id;
Result
| employee | department | manager |
|---|---|---|
| Riya | HR | NULL |
| Arjun | IT | Riya |
| Meena | IT | Arjun |
| Karan | NULL | Arjun |
9.3 Mixing Join Types
You can mix inner and outer joins, but the order matters, especially with outer joins.
A common trap: a left join followed by an inner join on the right-hand table can silently remove the NULL-extended rows.
-- Intention: all employees, with project if any
SELECT e.emp_name, p.proj_name
FROM employee e
LEFT JOIN works_on w ON e.emp_id = w.emp_id
INNER JOIN project p ON w.proj_id = p.proj_id; -- removes employees with no project!
Fix: use left joins all the way along the chain:
SELECT e.emp_name, p.proj_name
FROM employee e
LEFT JOIN works_on w ON e.emp_id = w.emp_id
LEFT JOIN project p ON w.proj_id = p.proj_id;
Result
| emp_name | proj_name |
|---|---|
| Riya | Alpha |
| Arjun | Alpha |
| Arjun | Beta |
| Meena | Beta |
| Karan | NULL |
9.4 Practical Tips
- Join along the relationships (foreign key β primary key). Draw the tables as a chain on paper first.
- Always write an
ONcondition for every join, so you do not create an accidental cross join. - Use meaningful aliases (
e,d,moremp,dept,mgr). - Select only the columns you need instead of
SELECT *. - Build the query one join at a time and check the result after each, so mistakes are easy to catch.
10. Joining Three or More Tables
10.1 A Many-to-Many Relationship Needs Three Tables
Employees and projects have a many-to-many relationship (an employee can work on many projects; a project has many employees). It is stored using a junction (bridge) table, works_on. To list employees with project names, you must join all three:
employee ββ< works_on >ββ project
(emp_id) (emp_id, proj_id) (proj_id)
SELECT e.emp_name, p.proj_name
FROM employee e
JOIN works_on w ON e.emp_id = w.emp_id
JOIN project p ON w.proj_id = p.proj_id;
Result
| emp_name | proj_name |
|---|---|
| Riya | Alpha |
| Arjun | Alpha |
| Arjun | Beta |
| Meena | Beta |
Arjun appears twice because he works on two projects (one row per matching pair). Karan is absent: with inner joins, an employee on no project disappears.
10.2 Four Tables
Add the department as well:
SELECT e.emp_name, d.dept_name, p.proj_name
FROM employee e
JOIN department d ON e.dept_id = d.dept_id
JOIN works_on w ON e.emp_id = w.emp_id
JOIN project p ON w.proj_id = p.proj_id;
Result
| emp_name | dept_name | proj_name |
|---|---|---|
| Riya | HR | Alpha |
| Arjun | IT | Alpha |
| Arjun | IT | Beta |
| Meena | IT | Beta |
10.3 Aggregating Over Multiple Joins
"Number of projects per employee, including employees with none."
SELECT e.emp_name, COUNT(w.proj_id) AS project_count
FROM employee e
LEFT JOIN works_on w ON e.emp_id = w.emp_id
GROUP BY e.emp_id, e.emp_name;
| emp_name | project_count |
|---|---|
| Riya | 1 |
| Arjun | 2 |
| Meena | 1 |
| Karan | 0 |
Note: COUNT(w.proj_id) gives 0 for Karan (NULLs are not counted), whereas COUNT(*) would wrongly give 1 (the NULL-extended row itself counts).
10.4 Watch Out for Row Multiplication (Fan-Out)
Each additional one-to-many join can multiply rows. If one employee has 2 projects and 3 skills, joining both gives 2 Γ 3 = 6 rows for that employee. Summing or counting across such a join gives inflated numbers. (See section 16.)
10.5 Performance Notes
- Make sure join columns are indexed (foreign keys especially).
- The optimizer usually chooses the best join order itself, so the order you write is normally not the execution order; but a clear order helps readers.
- Join only the tables you really need.
11. Joins Using Multiple Conditions
11.1 Why Multiple Conditions?
Sometimes one column is not enough to match rows correctly. You then combine conditions with AND (and sometimes OR) in the ON clause.
11.2 Composite Key Joins (Matching on Two or More Columns)
When a table's key has multiple columns, you must match all of them.
Example tables:
sales(region, year, amount) and target(region, year, target_amount)
SELECT s.region, s.year, s.amount, t.target_amount
FROM sales s
JOIN target t
ON s.region = t.region
AND s.year = t.year;
If you joined only on region, every year of sales would match every year of targets, producing wrong, multiplied rows.
11.3 Extra Filtering Inside ON
You can include other conditions that decide which rows qualify as a match:
SELECT e.emp_name, d.dept_name
FROM employee e
JOIN department d
ON e.dept_id = d.dept_id
AND d.dept_name <> 'HR';
For an inner join this equals putting the condition in WHERE. For outer joins, the difference matters (see section 4.5).
11.4 Non-Equi Joins (Joins Using Other Operators)
Join conditions do not have to use =. You can use <, >, <=, >=, <>, or BETWEEN.
Range join example: a salary_grade table assigns a grade to a salary range.
salary_grade(grade, min_sal, max_sal)
SELECT e.emp_name, e.salary, g.grade
FROM employee e
JOIN salary_grade g
ON e.salary BETWEEN g.min_sal AND g.max_sal;
Self non-equi join: the pair example from section 8.3 used a.emp_id < b.emp_id.
11.5 Combining AND with OR
SELECT e.emp_name, d.dept_name
FROM employee e
JOIN department d
ON e.dept_id = d.dept_id
OR e.emp_id = d.dept_id; -- unusual, for illustration only
Use parentheses to group conditions clearly:
ON a.x = b.x AND (a.y = b.y OR b.y IS NULL). Joins withORoften cannot use indexes well and can be slow; consider rewriting them withUNIONif performance suffers.
11.6 Conditions on More Than Two Tables
In a multi-table join, each ON should relate the new table to a table already in the query:
FROM a
JOIN b ON a.id = b.a_id
JOIN c ON b.id = c.b_id AND c.status = 'ACTIVE'
11.7 Tips
- Check that all key columns are in the
ONclause for composite keys. - Keep join conditions (relating tables) in
ON, and filter conditions (restricting rows) inWHERE, except where outer-join behaviour requires otherwise. - Be careful with NULLs in multi-column joins; a NULL in any compared column means no match with
=(see section 17). - Differences in data type or case or trailing spaces can silently break matches. Make sure the compared columns are compatible.
PART B: SET OPERATIONS
Set operations combine the results of two or more complete SELECT queries, treating each result as a set of rows.
Rules Common to All Set Operations
- Both queries must return the same number of columns.
- Corresponding columns must have compatible data types (in order).
- The column names of the result come from the first query.
ORDER BYgoes once, at the very end, and applies to the whole combined result (it can refer to column names/positions from the first query).- Each individual query can have its own
WHERE,JOIN,GROUP BY, and so on.
Sample Data for This Part
Two small tables of customer names (note that online_customers contains Arjun twice):
online_customers
| name |
|---|
| Riya |
| Arjun |
| Arjun |
| Meena |
store_customers
| name |
|---|
| Arjun |
| Meena |
| Karan |
CREATE TABLE online_customers (name VARCHAR(30));
CREATE TABLE store_customers (name VARCHAR(30));
INSERT INTO online_customers VALUES ('Riya'),('Arjun'),('Arjun'),('Meena');
INSERT INTO store_customers VALUES ('Arjun'),('Meena'),('Karan');
12. UNION and UNION ALL
12.1 UNION
Combines the results of two queries and removes duplicate rows (the result is a true set).
SELECT name FROM online_customers
UNION
SELECT name FROM store_customers;
Result (4 rows):
| name |
|---|
| Riya |
| Arjun |
| Meena |
| Karan |
Duplicates are removed both between the two tables and within the first table (the two Arjuns became one).
12.2 UNION ALL
Combines the results and keeps every row, including duplicates.
SELECT name FROM online_customers
UNION ALL
SELECT name FROM store_customers;
Result (7 rows):
| name |
|---|
| Riya |
| Arjun |
| Arjun |
| Meena |
| Arjun |
| Meena |
| Karan |
12.3 Comparison
| Feature | UNION | UNION ALL |
|---|---|---|
| Duplicates | Removed | Kept |
| Extra work | Must compare rows (sort or hash) to remove duplicates | None |
| Speed | Slower | Faster |
| Result size | Same or smaller | Same or larger |
| Use when | You need a distinct list | Duplicates are acceptable or impossible |
Best practice: use UNION ALL unless you specifically need duplicates removed. It avoids a costly de-duplication step.
12.4 Combining Different Tables Into One List
The columns do not need the same names, only compatible types and positions:
SELECT emp_name AS person_name, 'Employee' AS type FROM employee
UNION ALL
SELECT dept_name, 'Department' FROM department;
The extra constant column (type) is a handy way to label where each row came from.
12.5 Using ORDER BY and Filters
SELECT name FROM online_customers WHERE name <> 'Riya'
UNION
SELECT name FROM store_customers
ORDER BY name; -- once, at the end
12.6 Combining More Than Two Queries
SELECT ... UNION ALL SELECT ... UNION ALL SELECT ...;
If you mix UNION and UNION ALL, they are evaluated left to right; use parentheses to make your intention clear.
12.7 Typical Uses
- Merging similar tables (archive and current data, monthly tables, data from different regions).
- Producing a combined list of values from different sources.
- Emulating a full outer join in MySQL (see section 6.4).
- Building a report with detail rows plus a total row.
12.8 UNION vs JOIN
| UNION | JOIN | |
|---|---|---|
| Combines | Rows (stacks vertically) | Columns (adds side by side) |
| Needs a match condition | No | Yes |
| Column count | Must be equal in both queries | Can differ |
13. INTERSECT
13.1 What It Does
Returns only the rows that appear in both query results (the overlap). Duplicates are removed (set semantics).
Query A Query B
+-------+ +-------+
| ####|######|#### | INTERSECT = only the overlap
+-------+ +-------+
13.2 Example
"Customers who shop both online and in the store."
SELECT name FROM online_customers
INTERSECT
SELECT name FROM store_customers;
Result:
| name |
|---|
| Arjun |
| Meena |
13.3 INTERSECT ALL (Where Supported)
INTERSECT ALL keeps duplicates: if a row appears m times in the first result and n times in the second, it appears min(m, n) times in the output. (Not available in all DBMSs.)
13.4 DBMS Support
| DBMS | INTERSECT support |
|---|---|
| PostgreSQL | Yes (including ALL) |
| Oracle | Yes |
| SQL Server | Yes (no ALL variant) |
| MySQL | Supported in recent versions (8.0.31 onwards); older versions need an alternative |
13.5 Alternatives (Works Everywhere)
Using INNER JOIN (add DISTINCT to match set semantics):
SELECT DISTINCT o.name
FROM online_customers o
INNER JOIN store_customers s ON o.name = s.name;
Using EXISTS:
SELECT DISTINCT o.name
FROM online_customers o
WHERE EXISTS (SELECT 1 FROM store_customers s WHERE s.name = o.name);
Using IN:
SELECT DISTINCT name
FROM online_customers
WHERE name IN (SELECT name FROM store_customers);
13.6 INTERSECT vs INNER JOIN (Important Difference with NULLs)
INTERSECTtreats two NULLs as equal (it compares whole rows as "not distinct"), so a NULL appearing in both results is returned.INNER JOIN ... ON a.x = b.xtreats NULL = NULL as unknown, so NULLs never match.
(See section 17.)
13.7 When to Use
"Customers who appear in both lists": common elements, overlap analysis.
14. EXCEPT / MINUS
14.1 What It Does
Returns the rows from the first query that do not appear in the second. It subtracts the second result from the first. Duplicates are removed.
EXCEPTis the standard SQL keyword (PostgreSQL, SQL Server, and others).MINUSis the Oracle keyword for the same operation.
Query A Query B
+-------+ +-------+
|### |######| | EXCEPT = only the part of A
|### |######| | that is NOT in B
+-------+ +-------+
14.2 Example
"Customers who shop online but never in the store."
SELECT name FROM online_customers
EXCEPT
SELECT name FROM store_customers; -- Oracle: use MINUS
Result:
| name |
|---|
| Riya |
14.3 Order Matters!
Unlike UNION and INTERSECT, EXCEPT is not symmetric.
SELECT name FROM store_customers
EXCEPT
SELECT name FROM online_customers;
Result:
| name |
|---|
| Karan |
| Expression | Result |
|---|---|
online EXCEPT store | Riya |
store EXCEPT online | Karan |
14.4 EXCEPT ALL (Where Supported)
Keeps duplicates by subtracting counts: a row appearing m times in the first result and n times in the second appears max(m β n, 0) times.
SELECT name FROM online_customers
EXCEPT ALL
SELECT name FROM store_customers;
Result: Riya, Arjun (Arjun appears 2 β 1 = 1 time, Meena 1 β 1 = 0 times)
14.5 DBMS Support
| DBMS | Keyword |
|---|---|
| PostgreSQL | EXCEPT (and EXCEPT ALL) |
| SQL Server | EXCEPT |
| Oracle | MINUS (newer versions also accept EXCEPT) |
| MySQL | EXCEPT in recent versions (8.0.31 onwards); older versions need an alternative |
14.6 Alternatives (Works Everywhere)
Using NOT EXISTS (recommended):
SELECT DISTINCT o.name
FROM online_customers o
WHERE NOT EXISTS (SELECT 1 FROM store_customers s WHERE s.name = o.name);
Using LEFT JOIN with IS NULL (anti-join):
SELECT DISTINCT o.name
FROM online_customers o
LEFT JOIN store_customers s ON o.name = s.name
WHERE s.name IS NULL;
Using NOT IN (be careful: if the subquery returns any NULL, the result is empty):
SELECT DISTINCT name
FROM online_customers
WHERE name NOT IN (SELECT name FROM store_customers);
14.7 When to Use
"In A but not in B": customers who have not bought anything, products never ordered, differences between two lists (before and after).
14.8 Set Operations at a Glance
| Operation | Returns | Duplicates | Symmetric? |
|---|---|---|---|
UNION | All rows from either | Removed | Yes |
UNION ALL | All rows from either | Kept | Yes |
INTERSECT | Rows in both | Removed | Yes |
EXCEPT / MINUS | Rows in first, not in second | Removed | No |
Using our sample data:
| Operation | Result |
|---|---|
online UNION store | Riya, Arjun, Meena, Karan |
online UNION ALL store | 7 rows (with duplicates) |
online INTERSECT store | Arjun, Meena |
online EXCEPT store | Riya |
store EXCEPT online | Karan |
14.9 Precedence
In the SQL standard, INTERSECT is evaluated before UNION and EXCEPT when they appear together without parentheses (some DBMSs differ). Use parentheses to be safe:
(SELECT ... UNION SELECT ...)
EXCEPT
SELECT ...;
PART C: RELATED TOPICS
15. Join vs Subquery
15.1 Quick Definitions
- Join: combines tables side by side so you can use columns from all of them.
- Subquery: a query nested inside another query; its result feeds the outer query.
| Subquery type | Description |
|---|---|
| Scalar | Returns one value (one row, one column) |
| Column / list | Returns one column of many rows (used with IN, ANY, ALL) |
| Table (derived table) | Returns a full result set, used in FROM |
| Correlated | References a column of the outer query; evaluated per outer row (conceptually) |
| Non-correlated | Independent; evaluated once |
| EXISTS / NOT EXISTS | Tests whether the subquery returns any row |
15.2 The Same Question, Both Ways
"Employees who work in the IT department."
-- JOIN
SELECT e.emp_name
FROM employee e
JOIN department d ON e.dept_id = d.dept_id
WHERE d.dept_name = 'IT';
-- Subquery (IN)
SELECT emp_name
FROM employee
WHERE dept_id IN (SELECT dept_id FROM department WHERE dept_name = 'IT');
-- Subquery (EXISTS, correlated)
SELECT e.emp_name
FROM employee e
WHERE EXISTS (SELECT 1 FROM department d
WHERE d.dept_id = e.dept_id AND d.dept_name = 'IT');
All three return Arjun and Meena.
15.3 Comparison
| Aspect | Join | Subquery |
|---|---|---|
| Columns from the other table in the output | Yes | Not directly (only if used in FROM) |
| Readability | Clear for combining data | Clear for step-by-step logic ("first find X, then use it") |
| Duplicate rows risk | Yes (one-to-many multiplies rows) | Lower (IN/EXISTS just test for existence) |
| Performance | Usually very good; optimizers are strong with joins | Modern optimizers often rewrite subqueries as joins; correlated subqueries can be slow if not optimized |
| "Not in" logic | LEFT JOIN ... WHERE right.key IS NULL | NOT EXISTS / NOT IN |
| Comparing to an aggregate | Needs a derived table | Natural (salary > (SELECT AVG(...))) |
| Update/Delete based on another table | Possible (syntax varies) | Very common |
15.4 When a Join Is the Better Choice
- You need columns from more than one table in the result.
- You are combining large tables where indexes on join keys are available.
- You want all matches, including multiple matches per row.
15.5 When a Subquery Is the Better Choice
1. Comparing with an aggregate
-- Employees in departments with more than 1 employee
SELECT emp_name
FROM employee
WHERE dept_id IN (
SELECT dept_id FROM employee
GROUP BY dept_id
HAVING COUNT(*) > 1
);
2. Existence tests without duplicates
-- Departments that have at least one employee (each department appears once)
SELECT d.dept_name
FROM department d
WHERE EXISTS (SELECT 1 FROM employee e WHERE e.dept_id = d.dept_id);
A join here would repeat IT twice (two employees) unless you add DISTINCT.
3. "Not present" logic (anti-join)
-- Departments with no employees
SELECT d.dept_name
FROM department d
WHERE NOT EXISTS (SELECT 1 FROM employee e WHERE e.dept_id = d.dept_id);
Result: Sales
4. Scalar comparison
-- Employees whose ID is above the average ID
SELECT emp_name FROM employee
WHERE emp_id > (SELECT AVG(emp_id) FROM employee);
15.6 The Join Equivalent of "Departments With No Employees"
SELECT d.dept_name
FROM department d
LEFT JOIN employee e ON d.dept_id = e.dept_id
WHERE e.emp_id IS NULL;
Same result: Sales.
15.7 IN vs EXISTS (Short Guide)
| IN | EXISTS | |
|---|---|---|
| Works by | Comparing against a list of values | Checking whether any row exists |
| NULL behaviour | NOT IN breaks if the list contains NULL | NOT EXISTS is NULL-safe |
| Typical preference | Small, simple lists | Correlated checks, NOT conditions |
15.8 Subquery in the FROM Clause (Derived Table)
SELECT d.dept_name, t.emp_count
FROM department d
JOIN (
SELECT dept_id, COUNT(*) AS emp_count
FROM employee
GROUP BY dept_id
) t ON d.dept_id = t.dept_id;
This combines both ideas: a subquery produces the summary and a join attaches it.
15.9 Rules of Thumb
- Need columns from the other table? β Join.
- Only need to test whether related data exists (or not)? β EXISTS / NOT EXISTS.
- Need to compare to an aggregate? β Subquery.
- Check the execution plan if performance matters, because the optimizer may treat both forms identically.
16. Handling Duplicate Rows
Duplicates can come from three different sources. First identify which one you have.
| Source | Cause |
|---|---|
| 1. Duplicates already in the table | Data was inserted twice, or no unique constraint exists |
| 2. Join fan-out | A one-to-many or many-to-many join repeats the "one" side rows |
| 3. Set operations | UNION ALL, or sources that overlap |
16.1 Join Fan-Out (Most Common Surprise)
When a row on the "one" side matches n rows on the "many" side, it appears n times in the result.
SELECT d.dept_name, e.emp_name
FROM department d
JOIN employee e ON d.dept_id = e.dept_id;
IT appears twice (once per employee). That is correct for this query, but trouble starts when you then count or sum the repeated side.
Example of the trap: suppose each department has a budget. This query double counts IT's budget:
SELECT SUM(d.budget)
FROM department d
JOIN employee e ON d.dept_id = e.dept_id; -- IT's budget is added twice!
Fix options:
- Aggregate first, join afterwards (pre-aggregate in a derived table).
- Use
EXISTSif you only need to know that a match exists. - Use
DISTINCTcarefully (it can hide problems and may wrongly merge legitimately equal values).
16.2 Removing Duplicates from Query Results
DISTINCT: removes duplicate rows in the output (compares all selected columns together).
SELECT DISTINCT dept_id FROM employee;
GROUP BY: groups identical values, and allows aggregates.
SELECT dept_id, COUNT(*) AS emp_count
FROM employee
GROUP BY dept_id;
UNION (instead of UNION ALL): removes duplicates across combined queries.
COUNT(DISTINCT col): counts unique values only.
SELECT COUNT(DISTINCT dept_id) FROM employee; -- 2 (NULLs are ignored)
16.3 Avoiding Duplicates from Joins Using Semi-Joins
If you only want each parent row once, use EXISTS / IN instead of a join:
-- Each department that has at least one employee, once
SELECT d.dept_name
FROM department d
WHERE EXISTS (SELECT 1 FROM employee e WHERE e.dept_id = d.dept_id);
16.4 Finding Duplicate Rows in a Table
"Which names appear more than once?"
SELECT name, COUNT(*) AS times
FROM online_customers
GROUP BY name
HAVING COUNT(*) > 1;
Result: Arjun, 2
For duplicates over several columns, group by all of them:
SELECT col1, col2, COUNT(*)
FROM some_table
GROUP BY col1, col2
HAVING COUNT(*) > 1;
16.5 Deleting Duplicate Rows (Keeping One)
Assume a table person(id, name, email) with a unique id and duplicate names/emails.
Method 1: Delete using a self join (keep the smallest id)
DELETE p1
FROM person p1
JOIN person p2
ON p1.email = p2.email
AND p1.id > p2.id; -- MySQL multi-table DELETE syntax
Method 2: Delete rows not in the "keep" list (works in many systems)
DELETE FROM person
WHERE id NOT IN (
SELECT MIN(id) FROM person GROUP BY email
);
(In MySQL, wrap the subquery in a derived table, because you cannot select from the table you are deleting from directly.)
Method 3: Numbering duplicates with ROW_NUMBER(), then delete those numbered above 1 (supported by most modern systems; exact syntax varies by DBMS):
-- Identify the extras
SELECT id FROM (
SELECT id,
ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn
FROM person
) t
WHERE rn > 1;
Always run a SELECT first to preview exactly which rows would be deleted. Consider backing up or using a transaction.
16.6 Preventing Duplicates in the Future
- Add a primary key.
- Add a UNIQUE constraint or unique index on columns that must not repeat.
- In junction tables, use a composite primary key (for example
(emp_id, proj_id)inworks_on).
16.7 Duplicates and Set Operations
| Operation | Duplicate behaviour |
|---|---|
UNION | Removes all duplicates |
UNION ALL | Keeps all |
INTERSECT / EXCEPT | Remove duplicates |
INTERSECT ALL / EXCEPT ALL | Keep duplicates using min / subtract counts |
16.8 DISTINCT: When It Helps and When It Hurts
| Helps | Hurts |
|---|---|
| Quick de-duplication of a final list | Hides a wrong join (the real bug is a missing condition) |
| Removing harmless join repeats | Costs extra sorting/hashing on large results |
| Can merge rows that were legitimately separate |
If you are adding DISTINCT just to "make the numbers look right", stop and check the join logic.
17. NULL Handling in Joins
17.1 The Core Problem: NULL Means "Unknown"
In SQL, NULL is not a value; it means "missing or unknown". Comparing anything to NULL, even NULL itself, gives UNKNOWN, not TRUE:
NULL = NULL -> UNKNOWN (not TRUE)
NULL = 5 -> UNKNOWN
NULL <> 5 -> UNKNOWN
NULL IS NULL -> TRUE (the correct test)
A join condition (ON) or WHERE keeps a row only when the condition is TRUE. So rows where the join column is NULL never match with =.
17.2 What This Means for Each Join Type
In our data, Karan's dept_id is NULL.
| Join | Effect of the NULL dept_id |
|---|---|
| INNER JOIN | Karan is dropped (no match possible) |
| LEFT JOIN (employee on the left) | Karan is kept, with NULL in the department columns |
| RIGHT JOIN (department on the right) | Karan is not shown (he matches nothing, and only departments are preserved) |
| FULL OUTER JOIN | Karan is shown with NULL department; he does not match a "NULL department" because NULLs never match |
| CROSS JOIN | NULL is irrelevant (no condition), so Karan is paired with everything |
Important: even if the other table had a row whose key is also NULL, the two NULLs would not match with
=.
17.3 Seeing the Rows Where the Key Is NULL
SELECT * FROM employee WHERE dept_id IS NULL; -- correct
SELECT * FROM employee WHERE dept_id = NULL; -- WRONG: returns nothing
17.4 NULLs Created by Outer Joins
An outer join creates NULLs for unmatched rows. These NULLs have a different meaning: "no matching row exists". They can look identical to NULLs stored in the table, so be careful.
SELECT e.emp_name, d.dept_name
FROM employee e
LEFT JOIN department d ON e.dept_id = d.dept_id;
Karan's dept_name is NULL because there is no matching department (not because a department's name is NULL).
To test for "no match", check a column that cannot be NULL in the right table (its primary key):
WHERE d.dept_id IS NULL
17.5 Filtering After a Left Join (Common Bug)
SELECT e.emp_name, d.dept_name
FROM employee e
LEFT JOIN department d ON e.dept_id = d.dept_id
WHERE d.dept_name <> 'HR';
Karan's dept_name is NULL, so NULL <> 'HR' is UNKNOWN and Karan is dropped, which is probably not what you meant. Fix:
WHERE d.dept_name <> 'HR' OR d.dept_name IS NULL
17.6 Making NULLs Match (Null-Safe Comparison)
If you actually want NULL to match NULL in a join, use one of these:
| Method | Example | Notes |
|---|---|---|
| Standard SQL | a.x IS NOT DISTINCT FROM b.x | Supported in PostgreSQL and some others |
| MySQL operator | a.x <=> b.x | Null-safe equals |
| Portable form | a.x = b.x OR (a.x IS NULL AND b.x IS NULL) | Works everywhere but can be slower |
| COALESCE | COALESCE(a.x, -1) = COALESCE(b.x, -1) | Works if the placeholder value can never be real data; may prevent index use |
17.7 NULLs in Aggregates After Joins
COUNT(*)counts rows (including NULL-extended rows from an outer join).COUNT(column)counts only non-NULL values.
SELECT d.dept_name,
COUNT(*) AS rows_returned,
COUNT(e.emp_id) AS employees
FROM department d
LEFT JOIN employee e ON d.dept_id = e.dept_id
GROUP BY d.dept_name;
| dept_name | rows_returned | employees |
|---|---|---|
| HR | 1 | 1 |
| IT | 2 | 2 |
| Sales | 1 | 0 |
Sales shows COUNT(*) = 1 (the NULL-extended row), but the correct employee count is 0. Use COUNT(e.emp_id).
SUM, AVG, MIN, and MAX ignore NULLs; SUM of only NULLs returns NULL (use COALESCE(SUM(x), 0) for zero).
17.8 Replacing NULLs with Readable Values
SELECT e.emp_name,
COALESCE(d.dept_name, 'No Department') AS department
FROM employee e
LEFT JOIN department d ON e.dept_id = d.dept_id;
| emp_name | department |
|---|---|
| Riya | HR |
| Arjun | IT |
| Meena | IT |
| Karan | No Department |
(COALESCE returns the first non-NULL argument. Some systems also offer IFNULL, NVL, or ISNULL as variants.)
17.9 NULLs and NOT IN (Famous Trap)
-- Which departments have no employee? (looks right, but is dangerous)
SELECT dept_name
FROM department
WHERE dept_id NOT IN (SELECT dept_id FROM employee);
The subquery returns 10, 20, 20, NULL (because of Karan). dept_id NOT IN (10, 20, 20, NULL) is never TRUE for any row, so the result is empty, even though Sales clearly has no employees.
Fixes:
-- 1. NOT EXISTS (safest)
SELECT d.dept_name
FROM department d
WHERE NOT EXISTS (SELECT 1 FROM employee e WHERE e.dept_id = d.dept_id);
-- 2. Remove NULLs from the subquery
WHERE dept_id NOT IN (SELECT dept_id FROM employee WHERE dept_id IS NOT NULL);
-- 3. LEFT JOIN anti-join
SELECT d.dept_name
FROM department d
LEFT JOIN employee e ON d.dept_id = e.dept_id
WHERE e.emp_id IS NULL;
17.10 NULLs and Set Operations
Unlike = in joins, set operations treat NULLs as equal (two NULLs count as the same value):
UNIONkeeps only one NULL row.INTERSECTreturns a NULL row if both sides contain NULL.EXCEPTremoves a NULL row from the first result if the second also contains NULL.
This is a key difference between INTERSECT/EXCEPT and their join-based equivalents.
17.11 Reducing NULL Problems at the Design Level
- Use NOT NULL where a value must exist.
- Decide whether an optional relationship (like "an employee may have no department") is truly needed, and use left joins consciously when it is.
- Document what NULL means in each column.
17.12 Summary Table
| Situation | Behaviour | What to do |
|---|---|---|
NULL = NULL in ON | Not a match | Use null-safe comparison if matching is desired |
| Inner join on a NULL key | Row dropped | Use left join to keep it |
| Left join, no match | Right columns are NULL | Check the right table's primary key IS NULL |
WHERE on right columns after left join | May drop NULL-extended rows | Move the condition into ON, or add OR ... IS NULL |
NOT IN with NULLs | Returns nothing | Use NOT EXISTS |
COUNT(*) vs COUNT(col) | Differ with NULLs | Count a right-table column |
| Set operations | NULLs treated as equal | Remember this when comparing with joins |
18. Common Mistakes and Debugging Tips
| Mistake | Symptom | Fix |
|---|---|---|
| Forgot the join condition | Huge result (cross join) | Add ON ... for every join |
Used INNER JOIN when unmatched rows were needed | Missing rows | Use LEFT/FULL join |
WHERE on right table after a LEFT JOIN | Left join behaves like inner | Move the condition to ON |
| Joined on only part of a composite key | Too many rows | Include all key columns |
| Ambiguous column name | Error | Qualify with the table alias |
| Counting after a one-to-many join | Inflated counts and sums | Pre-aggregate, or count distinct |
NOT IN with a NULL-containing subquery | Empty result | Use NOT EXISTS |
UNION when UNION ALL was enough | Slow, and rows silently disappear | Use UNION ALL if duplicates are fine |
| Different number or order of columns in set operations | Error or wrong data | Match column count, order, and types |
| Joining columns of different types | Slow query or mismatches | Make types consistent |
A Reliable Debugging Routine
- Run each table's
SELECT *(withLIMIT) to understand the data. - Build the join one table at a time; check the row count after each step.
- Compare the row count with what you expect (for a one-to-many join, the count should be at least the "many" side's count).
- Use
COUNT(*)vsCOUNT(DISTINCT key)to detect unexpected fan-out. - Test edge cases: NULL keys, unmatched rows, duplicates.
19. Quick Revision Cheat Sheet
Join Types Summary (Using the Sample Data)
| Join | Keeps | Unmatched rows | Rows in our example |
|---|---|---|---|
| INNER JOIN | Only matches | Dropped from both sides | 3 (Riya, Arjun, Meena) |
| LEFT JOIN | All left + matches | Left kept, right = NULL | 4 (adds Karan) |
| RIGHT JOIN | All right + matches | Right kept, left = NULL | 4 (adds Sales) |
| FULL OUTER JOIN | All of both | Both kept, other side = NULL | 5 (adds Karan and Sales) |
| CROSS JOIN | Every combination | No condition | 12 (4 Γ 3) |
| SELF JOIN | Table joined with itself (any join type) | Depends on join type | e.g. 3 employee-manager pairs |
Visual Memory Aid
INNER: A β© B LEFT: A (all) + matching B
RIGHT: B (all) + matching A FULL: A βͺ B (all, matched where possible)
LEFT anti-join: A where no match in B (LEFT JOIN ... WHERE B.key IS NULL)
Set Operations Summary
| Operation | Result | Duplicates |
|---|---|---|
UNION | Rows from both queries | Removed |
UNION ALL | Rows from both queries | Kept (faster) |
INTERSECT | Rows in both | Removed |
EXCEPT / MINUS | Rows in first not in second (order matters) | Removed |
Rules: same number of columns, compatible types, names from the first query, ORDER BY only at the end.
Join vs Subquery in One Glance
| Need | Use |
|---|---|
| Columns from several tables in the result | Join |
| Just test existence / non-existence | EXISTS / NOT EXISTS |
| Compare with an aggregate | Subquery |
| "In A but not in B" | NOT EXISTS, LEFT JOIN ... IS NULL, or EXCEPT |
Duplicate Handling in One Glance
| Goal | Tool |
|---|---|
| Remove duplicates from output | DISTINCT, GROUP BY, UNION |
| Keep duplicates when combining | UNION ALL |
| Find duplicates | GROUP BY ... HAVING COUNT(*) > 1 |
| Avoid join fan-out | Pre-aggregate, or use EXISTS |
| Prevent duplicates | Primary key, UNIQUE constraint |
NULL Rules in One Glance
NULL = NULLis not true; useIS NULL.- NULL join keys never match with
=. - A
WHEREfilter on a right-table column can cancel a left join. NOT IN+ NULL = empty result; useNOT EXISTS.- Set operations treat NULLs as equal.
COUNT(column)skips NULLs;COUNT(*)does not.
Practice Questions (Using the Sample Tables)
- List all employees with their department name, including those with no department.
Answer:
LEFT JOINfromemployeetodepartment(section 4). - Which departments have no employees?
Answer:
LEFT JOIN ... WHERE e.emp_id IS NULL, orNOT EXISTS(sections 4.4, 15.5). - Show each employee with their manager's name, including the person with no manager.
Answer: self
LEFT JOIN(section 8.2). - List employee names with the names of the projects they work on.
Answer: join
employee,works_on,project(section 10.1). - How many projects does each employee work on, including those with none?
Answer:
LEFT JOIN+COUNT(w.proj_id)+GROUP BY(section 10.3). - Find customers who are in the online list but not the store list.
Answer:
EXCEPT/MINUS, orNOT EXISTS(section 14). - Combine the names of both customer lists into one list without duplicates.
Answer:
UNION(section 12.1). - Why does
WHERE dept_id NOT IN (SELECT dept_id FROM employee)return nothing? Answer: the subquery contains a NULL (section 17.9).
#SQL Functions and Aggregation#
Study notes in plain language. Every concept is shown on the same small sample tables, so you can follow the results by eye. Read top to bottom the first time; later, jump to whichever section you need.
Table of Contents
Part A: Aggregation
- Aggregate Functions: COUNT, SUM, AVG, MIN, MAX
- GROUP BY
- HAVING
- WHERE vs HAVING
- GROUP BY with Multiple Columns
- DISTINCT with Aggregate Functions
- Conditional Aggregation
- FILTER Clauses (Where Supported)
- Aggregate Queries with Joins
- GROUPING SETS, ROLLUP and CUBE
Part B: Scalar Functions
1. The Big Picture
SQL functions come in two families, and the difference is the single most important idea in this topic.
| Family | Works on | Returns | Example |
|---|---|---|---|
| Aggregate functions | A group of rows | One value per group | SUM(salary) β one total |
| Scalar (single-row) functions | One row's value at a time | One value per row | UPPER(name) β one result per row |
Scalar: Riya -> RIYA (rows in = rows out)
Arjun -> ARJUN
Aggregate: 50000
80000 -> 280000 (many rows collapse into one)
70000
...
Part A of these notes covers aggregation (collapsing rows into summaries). Part B covers scalar functions (string, numeric, date, conversion, NULL handling) that transform values row by row, and which you often use inside aggregate queries.
Dialect warning: SQL functions are one of the areas where DBMSs differ most (MySQL, PostgreSQL, SQL Server, Oracle). Where names or syntax differ, the notes show the variants. Always check your DBMS documentation.
2. Sample Tables Used Throughout
CREATE TABLE employee (
emp_id INT PRIMARY KEY,
emp_name VARCHAR(30),
dept VARCHAR(20),
city VARCHAR(20),
salary DECIMAL(10,2), -- may be NULL
bonus DECIMAL(10,2), -- may be NULL
hire_date DATE
);
INSERT INTO employee VALUES
(1,'Riya', 'HR', 'Bhopal', 50000, 5000, '2020-03-15'),
(2,'Arjun', 'IT', 'Indore', 80000, NULL, '2019-07-01'),
(3,'Meena', 'IT', 'Bhopal', 70000, 7000, '2021-01-10'),
(4,'Karan', 'Sales', 'Indore', 40000, NULL, '2022-09-25'),
(5,'Divya', 'Sales', 'Bhopal', 40000, 2000, '2022-11-05'),
(6,'Sana', 'IT', 'Indore', NULL, NULL, '2023-02-14');
employee
| emp_id | emp_name | dept | city | salary | bonus | hire_date |
|---|---|---|---|---|---|---|
| 1 | Riya | HR | Bhopal | 50000 | 5000 | 2020-03-15 |
| 2 | Arjun | IT | Indore | 80000 | NULL | 2019-07-01 |
| 3 | Meena | IT | Bhopal | 70000 | 7000 | 2021-01-10 |
| 4 | Karan | Sales | Indore | 40000 | NULL | 2022-09-25 |
| 5 | Divya | Sales | Bhopal | 40000 | 2000 | 2022-11-05 |
| 6 | Sana | IT | Indore | NULL | NULL | 2023-02-14 |
For the join section, two extra tables:
CREATE TABLE customer (
cust_id INT PRIMARY KEY,
cust_name VARCHAR(30),
city VARCHAR(20),
credit_limit DECIMAL(10,2)
);
CREATE TABLE orders (
order_id INT PRIMARY KEY,
cust_id INT,
amount DECIMAL(10,2),
order_date DATE
);
INSERT INTO customer VALUES
(1,'Riya', 'Bhopal', 5000),
(2,'Arjun', 'Indore', 3000),
(3,'Meena', 'Bhopal', 4000),
(4,'Karan', 'Indore', 2000);
INSERT INTO orders VALUES
(101, 1, 500, '2026-01-10'),
(102, 1, 700, '2026-02-05'),
(103, 2, 300, '2026-02-20'),
(104, 3, 1200, '2026-03-15'),
(105, 3, 800, '2026-03-28');
customer
| cust_id | cust_name | city | credit_limit |
|---|---|---|---|
| 1 | Riya | Bhopal | 5000 |
| 2 | Arjun | Indore | 3000 |
| 3 | Meena | Bhopal | 4000 |
| 4 | Karan | Indore | 2000 |
orders
| order_id | cust_id | amount | order_date |
|---|---|---|---|
| 101 | 1 | 500 | 2026-01-10 |
| 102 | 1 | 700 | 2026-02-05 |
| 103 | 2 | 300 | 2026-02-20 |
| 104 | 3 | 1200 | 2026-03-15 |
| 105 | 3 | 800 | 2026-03-28 |
Deliberate "Gaps" in the Data
- Sana's salary is NULL (and several bonuses are NULL), so we can see how aggregates treat NULLs.
- Karan has no orders, so we can see what aggregates do after an outer join.
- Two employees (Karan and Divya) share the same salary, 40000, so we can see what
DISTINCTdoes.
PART A: AGGREGATION
3. Aggregate Functions: COUNT, SUM, AVG, MIN, MAX
An aggregate function takes many values and returns a single summary value.
| Function | What it returns |
|---|---|
COUNT() | Number of rows (or non-NULL values) |
SUM() | Total of the values |
AVG() | Average (mean) of the values |
MIN() | Smallest value |
MAX() | Largest value |
3.1 Aggregates Over the Whole Table
With no GROUP BY, the entire table is treated as one group, and the query returns exactly one row.
SELECT COUNT(*) AS total_rows,
COUNT(salary) AS salaries_present,
SUM(salary) AS total_salary,
AVG(salary) AS avg_salary,
MIN(salary) AS lowest,
MAX(salary) AS highest
FROM employee;
Result
| total_rows | salaries_present | total_salary | avg_salary | lowest | highest |
|---|---|---|---|---|---|
| 6 | 5 | 280000 | 56000 | 40000 | 80000 |
3.2 COUNT: Three Forms
| Form | Counts | On our data |
|---|---|---|
COUNT(*) | All rows, regardless of NULLs | 6 |
COUNT(column) | Rows where the column is NOT NULL | COUNT(salary) = 5, COUNT(bonus) = 3 |
COUNT(DISTINCT column) | Unique non-NULL values | COUNT(DISTINCT dept) = 3 |
COUNT(1) and COUNT(*) behave the same: both count rows.
3.3 The Golden Rule: Aggregates Ignore NULLs
SUM, AVG, MIN, MAX, and COUNT(column) all skip NULLs. (Only COUNT(*) counts every row.)
Notice what this does to the average:
AVG(salary) = 280000 / 5 = 56000 (NOT 280000 / 6)
Sana's NULL salary is left out of both the sum and the count. If you wanted to treat a missing salary as 0, you would have to say so explicitly:
SELECT AVG(COALESCE(salary, 0)) FROM employee; -- 280000 / 6 = 46666.67
This is a different answer to a different question, so choose deliberately.
3.4 Behaviour on Empty Input
If no rows qualify (for example WHERE removes everything):
| Function | Result on zero rows |
|---|---|
COUNT(...) | 0 |
SUM, AVG, MIN, MAX | NULL |
SELECT COUNT(*), SUM(salary) FROM employee WHERE dept = 'Legal';
-- 0, NULL
Use COALESCE(SUM(salary), 0) if you need 0 instead of NULL.
3.5 Details for Each Function
SUM and AVG
- Work on numeric columns only.
AVG=SUM / COUNT(column)(non-NULL count).- In some systems (for example SQL Server),
AVGof an integer column returns an integer (truncating decimals). Cast to a decimal first if needed:AVG(CAST(x AS DECIMAL(10,2))).
MIN and MAX
- Work on numbers, text, and dates.
- On text they use alphabetical order; on dates, earliest/latest.
SELECT MIN(hire_date) AS first_hire,
MAX(hire_date) AS latest_hire,
MIN(emp_name) AS first_alphabetically
FROM employee;
| first_hire | latest_hire | first_alphabetically |
|---|---|---|
| 2019-07-01 | 2023-02-14 | Arjun |
3.6 Using Expressions Inside Aggregates
SELECT SUM(salary + COALESCE(bonus, 0)) AS total_compensation
FROM employee;
Sana's row contributes nothing (NULL + 0 is NULL, which is skipped), giving 55000 + 80000 + 77000 + 40000 + 42000 = 294000.
3.7 Where Aggregates Can and Cannot Be Used
- Allowed in:
SELECT,HAVING,ORDER BY. - Not allowed in:
WHERE(aggregation happens afterWHERE; see section 6). - Not allowed to nest directly in most systems:
SUM(AVG(x))is an error (it needs a subquery).
3.8 Mixing Aggregates and Plain Columns
SELECT dept, COUNT(*) FROM employee; -- ERROR (or wrong) without GROUP BY
If you use an aggregate and a plain column, the plain column must be in a GROUP BY (next section).
4. GROUP BY
4.1 What It Does
GROUP BY splits the rows into groups that share the same value(s) in the grouping column(s), then applies the aggregate functions to each group separately. You get one result row per group.
employee rows groups result
-------------- ------ ------
Riya HR 50000 HR : 50000 HR 50000
Arjun IT 80000 GROUP BY IT : 80000,70000,NULL IT 150000
Meena IT 70000 dept --> Sales : 40000,40000 Sales 80000
Karan Sales 40000
Divya Sales 40000
Sana IT NULL
4.2 Syntax
SELECT group_column, AGGREGATE(column)
FROM table
[WHERE row_condition]
GROUP BY group_column
[HAVING group_condition]
[ORDER BY ...];
4.3 Example
"Employee count, total salary, and average salary for each department."
SELECT dept,
COUNT(*) AS headcount,
SUM(salary) AS total_salary,
AVG(salary) AS avg_salary
FROM employee
GROUP BY dept;
Result
| dept | headcount | total_salary | avg_salary |
|---|---|---|---|
| HR | 1 | 50000 | 50000 |
| IT | 3 | 150000 | 75000 |
| Sales | 2 | 80000 | 40000 |
Look at IT: headcount is 3 (counts Sana's row), but the average is 75000 = 150000 / 2, because Sana's NULL salary is ignored.
4.4 The Main Rule of GROUP BY
Every column in the
SELECTlist must be either inside an aggregate function or listed in theGROUP BY.
-- WRONG: emp_name is neither aggregated nor grouped
SELECT dept, emp_name, COUNT(*)
FROM employee
GROUP BY dept;
This is ambiguous (which employee name should represent the whole IT group?), so strict systems reject it. MySQL's default mode (ONLY_FULL_GROUP_BY) rejects it too; older or relaxed MySQL settings may return an arbitrary value, which is dangerous.
4.5 Grouping by an Expression
You can group by the result of a function, not just a plain column:
SELECT EXTRACT(YEAR FROM hire_date) AS hire_year,
COUNT(*) AS hires
FROM employee
GROUP BY EXTRACT(YEAR FROM hire_date)
ORDER BY hire_year;
| hire_year | hires |
|---|---|
| 2019 | 1 |
| 2020 | 1 |
| 2021 | 1 |
| 2022 | 2 |
| 2023 | 1 |
(Some systems let you group by the alias or by column position; support varies.)
4.6 How NULLs Are Grouped
All NULLs in the grouping column are placed together into one group (even though NULL β NULL in comparisons). If some employees had a NULL dept, they would appear as a single group with dept = NULL.
4.7 GROUP BY Without Aggregates
GROUP BY can simply return the distinct combinations:
SELECT dept FROM employee GROUP BY dept; -- same as SELECT DISTINCT dept
4.8 Ordering the Output
Groups come out in no guaranteed order. Add ORDER BY to control it, and you may order by an aggregate:
SELECT dept, SUM(salary) AS total_salary
FROM employee
GROUP BY dept
ORDER BY total_salary DESC;
| dept | total_salary |
|---|---|
| IT | 150000 |
| Sales | 80000 |
| HR | 50000 |
4.9 Logical Order of Execution
1. FROM / JOIN get the rows
2. WHERE filter individual rows
3. GROUP BY form groups
4. HAVING filter groups
5. SELECT compute output columns (including aggregates and aliases)
6. DISTINCT
7. ORDER BY sort
8. LIMIT / TOP
This order explains many "why doesn't this work?" errors, such as using a SELECT alias in WHERE.
5. HAVING
5.1 What It Does
HAVING filters groups after GROUP BY and aggregation. It is the "WHERE for groups".
5.2 Example
"Departments whose average salary is above 45000."
SELECT dept, AVG(salary) AS avg_salary
FROM employee
GROUP BY dept
HAVING AVG(salary) > 45000;
Result
| dept | avg_salary |
|---|---|
| HR | 50000 |
| IT | 75000 |
Sales (average 40000) is filtered out.
5.3 Multiple Conditions
SELECT dept, COUNT(*) AS headcount, SUM(salary) AS total_salary
FROM employee
GROUP BY dept
HAVING COUNT(*) > 1 AND SUM(salary) > 100000;
Result: only IT (headcount 3, total 150000).
5.4 HAVING Can Use Aggregates You Do Not Display
SELECT dept
FROM employee
GROUP BY dept
HAVING MAX(salary) >= 70000;
Result: IT. (The MAX is used for filtering only; it is not in the SELECT list.)
5.5 HAVING Without GROUP BY
Allowed: the whole table is treated as one group.
SELECT SUM(salary) FROM employee HAVING SUM(salary) > 100000;
This returns the total if the condition holds, and no row if it does not. It is rarely used.
5.6 Aliases in HAVING
The standard does not allow SELECT aliases in HAVING (because HAVING runs before SELECT). Some systems (MySQL, for example) allow it anyway; others do not. The portable approach is to repeat the aggregate expression.
6. WHERE vs HAVING
Both filter, but at different stages, and on different things.
6.1 Comparison
| Feature | WHERE | HAVING |
|---|---|---|
| Filters | Individual rows | Groups |
| Runs | Before GROUP BY | After GROUP BY |
| Can use aggregate functions | No | Yes |
| Can reference non-aggregated columns | Yes | Only grouped columns (or inside aggregates) |
| Effect on performance | Reduces rows early (cheaper) | Works on already-formed groups |
6.2 Using Both Together
"Among employees hired after 2020, which departments have a total salary above 60000?"
SELECT dept, SUM(salary) AS total_salary
FROM employee
WHERE hire_date > '2020-12-31' -- row filter first
GROUP BY dept
HAVING SUM(salary) > 60000; -- group filter after
Step by step:
WHEREkeeps Meena (IT), Karan (Sales), Divya (Sales), Sana (IT).GROUP BY deptforms IT (Meena 70000, Sana NULL) and Sales (40000, 40000).HAVINGkeeps groups with SUM > 60000: IT = 70000 β, Sales = 80000 β.
| dept | total_salary |
|---|---|
| IT | 70000 |
| Sales | 80000 |
6.3 The Same Condition in the Wrong Place
-- ERROR: aggregate in WHERE
SELECT dept FROM employee WHERE SUM(salary) > 60000 GROUP BY dept;
WHERE runs before groups exist, so there is no SUM yet.
6.4 A Non-Aggregate Condition in HAVING (Works, but Inefficient)
SELECT dept, COUNT(*)
FROM employee
GROUP BY dept
HAVING dept = 'IT'; -- legal, but WHERE is better
Better:
SELECT dept, COUNT(*)
FROM employee
WHERE dept = 'IT' -- filters before grouping
GROUP BY dept;
6.5 Rule of Thumb
- Condition on a single row's value β
WHERE. - Condition on an aggregate result β
HAVING.
7. GROUP BY with Multiple Columns
7.1 What It Does
When you list several columns, a group is a unique combination of those columns' values.
SELECT dept, city,
COUNT(*) AS headcount,
SUM(salary) AS total_salary
FROM employee
GROUP BY dept, city
ORDER BY dept, city;
Result
| dept | city | headcount | total_salary |
|---|---|---|---|
| HR | Bhopal | 1 | 50000 |
| IT | Bhopal | 1 | 70000 |
| IT | Indore | 2 | 80000 |
| Sales | Bhopal | 1 | 40000 |
| Sales | Indore | 1 | 40000 |
Five combinations exist in the data, so five rows. Notice IT + Indore has 2 employees (Arjun and Sana), but only Arjun's salary counts toward the sum.
7.2 Key Points
- The order of columns in
GROUP BYdoes not change the groups (GROUP BY dept, cityforms the same groups asGROUP BY city, dept), only the default row ordering may differ. - Only combinations that actually exist in the data appear. There is no row for "HR + Indore".
- Every non-aggregated
SELECTcolumn must be in theGROUP BYlist. - More grouping columns β finer groups β more result rows.
7.3 Grouping Detail Levels
GROUP BY | Number of groups here | Level |
|---|---|---|
| (nothing) | 1 | Whole table |
dept | 3 | Per department |
dept, city | 5 | Per department and city |
emp_id | 6 | Per employee (no summarising) |
7.4 Combining with HAVING and ORDER BY
SELECT dept, city, COUNT(*) AS headcount
FROM employee
GROUP BY dept, city
HAVING COUNT(*) > 1
ORDER BY headcount DESC;
Result: IT, Indore, 2.
7.5 Multi-Level Summaries Need Extra Tools
A plain multi-column GROUP BY gives only the finest level of detail. To also get subtotals and a grand total in the same query, use ROLLUP, CUBE, or GROUPING SETS (section 12).
8. DISTINCT with Aggregate Functions
8.1 What It Does
Placing DISTINCT inside an aggregate makes it consider only unique non-NULL values, so repeated values are counted or summed once.
SELECT COUNT(salary) AS cnt,
COUNT(DISTINCT salary) AS cnt_distinct,
SUM(salary) AS total,
SUM(DISTINCT salary) AS total_distinct,
AVG(salary) AS avg_all,
AVG(DISTINCT salary) AS avg_distinct
FROM employee;
Result
| cnt | cnt_distinct | total | total_distinct | avg_all | avg_distinct |
|---|---|---|---|---|---|
| 5 | 4 | 280000 | 240000 | 56000 | 60000 |
How it works: the non-NULL salaries are 50000, 80000, 70000, 40000, 40000. The distinct set is 50000, 80000, 70000, 40000, so:
COUNT(DISTINCT salary)= 4SUM(DISTINCT salary)= 50000 + 80000 + 70000 + 40000 = 240000AVG(DISTINCT salary)= 240000 / 4 = 60000
8.2 The Most Useful Case: COUNT(DISTINCT)
SELECT COUNT(DISTINCT dept) AS departments,
COUNT(DISTINCT city) AS cities
FROM employee;
| departments | cities |
|---|---|
| 3 | 2 |
Per group:
SELECT city, COUNT(DISTINCT dept) AS departments_present
FROM employee
GROUP BY city;
| city | departments_present |
|---|---|
| Bhopal | 3 |
| Indore | 2 |
8.3 DISTINCT vs SELECT DISTINCT
| Syntax | Meaning |
|---|---|
SELECT DISTINCT dept, city FROM ... | Removes duplicate rows from the output |
COUNT(DISTINCT dept) | Counts unique values inside an aggregate |
8.4 Cautions
MIN(DISTINCT x)andMAX(DISTINCT x)give the same result as withoutDISTINCT(no effect).SUM(DISTINCT x)andAVG(DISTINCT x)are rarely what you want; they merge different rows that happen to share a value (here Karan's and Divya's equal salaries).DISTINCTadds sorting/hashing work, so it is slower on large data.COUNT(DISTINCT a, b)(two columns at once) works in some systems (MySQL) but not others (PostgreSQL, SQL Server). A portable alternative is a subquery withSELECT DISTINCT a, band thenCOUNT(*).- NULLs are ignored by
COUNT(DISTINCT col).
9. Conditional Aggregation
9.1 The Idea
Conditional aggregation means putting a condition inside an aggregate function, usually with CASE. It lets you compute several differently-filtered summaries in a single pass, in a single row or even as columns (a "pivot").
The trick: CASE returns NULL for rows you want to skip, and aggregates ignore NULLs.
9.2 Counting with a Condition
SELECT dept,
COUNT(*) AS headcount,
SUM(CASE WHEN salary >= 60000 THEN 1 ELSE 0 END) AS high_earners,
COUNT(CASE WHEN salary >= 60000 THEN 1 END) AS high_earners_v2
FROM employee
GROUP BY dept;
Result
| dept | headcount | high_earners | high_earners_v2 |
|---|---|---|---|
| HR | 1 | 0 | 0 |
| IT | 3 | 2 | 2 |
| Sales | 2 | 0 | 0 |
Two equivalent patterns:
SUM(CASE WHEN cond THEN 1 ELSE 0 END): adds 1 for matching rows.COUNT(CASE WHEN cond THEN 1 END): theCASEreturns NULL when the condition is false, andCOUNTskips NULLs.
9.3 Summing with a Condition
SELECT SUM(CASE WHEN city = 'Bhopal' THEN salary END) AS bhopal_payroll,
SUM(CASE WHEN city = 'Indore' THEN salary END) AS indore_payroll
FROM employee;
| bhopal_payroll | indore_payroll |
|---|---|
| 160000 | 120000 |
9.4 Pivot-Style Reports (Rows to Columns)
"For each department, show payroll by city side by side."
SELECT dept,
COALESCE(SUM(CASE WHEN city = 'Bhopal' THEN salary END), 0) AS bhopal,
COALESCE(SUM(CASE WHEN city = 'Indore' THEN salary END), 0) AS indore,
SUM(salary) AS total
FROM employee
GROUP BY dept;
Result
| dept | bhopal | indore | total |
|---|---|---|---|
| HR | 50000 | 0 | 50000 |
| IT | 70000 | 80000 | 150000 |
| Sales | 40000 | 40000 | 80000 |
COALESCE(..., 0) turns HR's Indore result (a sum of nothing, so NULL) into 0.
9.5 Percentages and Ratios
SELECT dept,
ROUND(100.0 * COUNT(CASE WHEN salary >= 60000 THEN 1 END) / COUNT(*), 1)
AS pct_high_earners
FROM employee
GROUP BY dept;
| dept | pct_high_earners |
|---|---|
| HR | 0.0 |
| IT | 66.7 |
| Sales | 0.0 |
Using 100.0 (a decimal) avoids integer division, which would round everything down to 0.
9.6 Other Common Uses
- Yes/No flag counts:
SUM(CASE WHEN status = 'ACTIVE' THEN 1 ELSE 0 END). - Period comparisons:
SUM(CASE WHEN year = 2025 THEN amount END)next to... year = 2026 .... - Categorising into bands (low/medium/high) and counting each band in one query.
9.7 Tips
- Choose
ELSE 0(forSUM) or noELSE(forCOUNT, or when you want NULL for "no data"). - Always think about what a group with no matching rows produces:
SUMgives NULL,COUNTgives 0. - Conditional aggregation is portable across all major databases, which makes it a safe alternative to
FILTER(next section).
10. FILTER Clauses (Where Supported)
10.1 What It Is
FILTER is the standard SQL syntax for conditional aggregation. It attaches a WHERE condition directly to an aggregate, making the query cleaner than using CASE.
AGGREGATE(expression) FILTER (WHERE condition)
10.2 Example
SELECT dept,
COUNT(*) AS headcount,
COUNT(*) FILTER (WHERE salary >= 60000) AS high_earners,
SUM(salary) FILTER (WHERE city = 'Bhopal') AS bhopal_payroll
FROM employee
GROUP BY dept;
Result
| dept | headcount | high_earners | bhopal_payroll |
|---|---|---|---|
| HR | 1 | 0 | 50000 |
| IT | 3 | 2 | 70000 |
| Sales | 2 | 0 | 40000 |
10.3 Equivalent CASE Version
COUNT(CASE WHEN salary >= 60000 THEN 1 END) -- same as COUNT(*) FILTER (WHERE salary >= 60000)
SUM(CASE WHEN city = 'Bhopal' THEN salary END) -- same as SUM(salary) FILTER (WHERE city = 'Bhopal')
10.4 Support
| DBMS | FILTER supported? |
|---|---|
| PostgreSQL | Yes |
| SQLite | Yes (recent versions) |
| MySQL, SQL Server, Oracle | Generally no; use the CASE form |
(Support can change between versions, so check your DBMS documentation.)
10.5 FILTER vs HAVING vs WHERE
| Tool | Filters | Affects other aggregates in the query? |
|---|---|---|
WHERE | Rows, before grouping | Yes (all aggregates see only the remaining rows) |
HAVING | Whole groups, after aggregation | Removes the entire group from the output |
FILTER | Rows for one specific aggregate | No (only that aggregate is affected) |
That last row is the point of FILTER: you can compute COUNT(*) for all rows and COUNT(*) FILTER (...) for some rows in the same query.
11. Aggregate Queries with Joins
Joining tables first and then aggregating is how most real reports are built: "total sales per customer", "orders per city". Two big issues: unmatched rows and row multiplication (fan-out).
11.1 Basic Pattern: Join, Then Group
"Total order amount and order count per customer."
SELECT c.cust_name,
COUNT(o.order_id) AS order_count,
SUM(o.amount) AS total_amount
FROM customer c
JOIN orders o ON c.cust_id = o.cust_id
GROUP BY c.cust_id, c.cust_name;
Result (inner join: Karan, with no orders, is missing)
| cust_name | order_count | total_amount |
|---|---|---|
| Riya | 2 | 1200 |
| Arjun | 1 | 300 |
| Meena | 2 | 2000 |
Group by the primary key (c.cust_id) plus any displayed columns, so two different customers with the same name are never merged.
11.2 Keeping Customers With No Orders (LEFT JOIN)
SELECT c.cust_name,
COUNT(o.order_id) AS order_count,
COALESCE(SUM(o.amount), 0) AS total_amount
FROM customer c
LEFT JOIN orders o ON c.cust_id = o.cust_id
GROUP BY c.cust_id, c.cust_name;
Result
| cust_name | order_count | total_amount |
|---|---|---|
| Riya | 2 | 1200 |
| Arjun | 1 | 300 |
| Meena | 2 | 2000 |
| Karan | 0 | 0 |
11.3 COUNT(*) vs COUNT(column) After a LEFT JOIN
An outer join produces a NULL-filled row for unmatched customers. COUNT(*) counts that row; COUNT(o.order_id) does not.
| Expression | Karan's result | Correct? |
|---|---|---|
COUNT(*) | 1 (counts the NULL-extended row) | Wrong |
COUNT(o.order_id) | 0 | Correct |
Always count a column from the optional (right) table, not
*.
11.4 Grouping by a Column from the Joined Table
"Orders and revenue per customer city."
SELECT c.city,
COUNT(o.order_id) AS orders,
SUM(o.amount) AS revenue
FROM customer c
LEFT JOIN orders o ON c.cust_id = o.cust_id
GROUP BY c.city;
| city | orders | revenue |
|---|---|---|
| Bhopal | 4 | 3200 |
| Indore | 1 | 300 |
11.5 Filtering: WHERE, ON, and HAVING in Join Queries
-- Customers whose total order amount exceeds 1000
SELECT c.cust_name, SUM(o.amount) AS total_amount
FROM customer c
JOIN orders o ON c.cust_id = o.cust_id
GROUP BY c.cust_id, c.cust_name
HAVING SUM(o.amount) > 1000;
| cust_name | total_amount |
|---|---|
| Riya | 1200 |
| Meena | 2000 |
For a LEFT JOIN where you want to restrict which orders count but still keep all customers, put the order condition in the ON clause. A WHERE condition on the right table would remove the customers with no matching orders:
SELECT c.cust_name, COUNT(o.order_id) AS orders_2026_march
FROM customer c
LEFT JOIN orders o
ON c.cust_id = o.cust_id
AND o.order_date >= '2026-03-01' -- in ON: keeps every customer
GROUP BY c.cust_id, c.cust_name;
| cust_name | orders_2026_march |
|---|---|
| Riya | 0 |
| Arjun | 0 |
| Meena | 2 |
| Karan | 0 |
11.6 The Fan-Out Trap (Double Counting)
When you join a "one" table to a "many" table, each "one" row is repeated once per match. Summing a column from the "one" side then counts it multiple times.
-- Wrong: credit_limit is repeated for every order
SELECT SUM(c.credit_limit) AS total_credit
FROM customer c
LEFT JOIN orders o ON c.cust_id = o.cust_id;
The joined rows are: Riya Γ2, Arjun Γ1, Meena Γ2, Karan Γ1. So the sum becomes
5000Γ2 + 3000 + 4000Γ2 + 2000 = 23000, but the true total credit limit is
5000 + 3000 + 4000 + 2000 = 14000.
Fixes
-
Aggregate the "one" table on its own (no join needed):
SELECT SUM(credit_limit) FROM customer; -- 14000 -
Pre-aggregate the "many" side, then join (one row per customer):
SELECT c.cust_name, c.credit_limit, t.total_amount FROM customer c LEFT JOIN ( SELECT cust_id, SUM(amount) AS total_amount FROM orders GROUP BY cust_id ) t ON c.cust_id = t.cust_id;cust_name credit_limit total_amount Riya 5000 1200 Arjun 3000 300 Meena 4000 2000 Karan 2000 NULL -
Use
DISTINCTcarefully (SUM(DISTINCT ...)can wrongly merge equal values from different customers, so it is not a safe fix).
Rule: before summing or counting after a join, ask "Is this value repeated because of the join?"
11.7 Checklist for Aggregate-with-Join Queries
- Right join type? (inner drops unmatched rows; left keeps them)
-
COUNT(optional_table.column), notCOUNT(*), with outer joins -
COALESCEfor sums of unmatched groups - Grouping by the primary key of the entity being summarised
- No fan-out double counting
- Row-level filters in
WHERE(or inONfor outer joins); aggregate filters inHAVING
12. GROUPING SETS, ROLLUP and CUBE
12.1 The Problem
A normal GROUP BY dept, city gives only the detail rows. Reports usually also need subtotals and a grand total. Without these tools, you would write several queries and join them with UNION ALL. These three extensions of GROUP BY produce multiple levels of grouping in one query.
12.2 Reminder: The Detail Level
SELECT dept, city, SUM(salary) AS total_salary
FROM employee
GROUP BY dept, city;
| dept | city | total_salary |
|---|---|---|
| HR | Bhopal | 50000 |
| IT | Bhopal | 70000 |
| IT | Indore | 80000 |
| Sales | Bhopal | 40000 |
| Sales | Indore | 40000 |
12.3 ROLLUP
ROLLUP(a, b) produces a hierarchy of subtotals, rolling up from the right:
(a, b) detail
(a) subtotal per a
() grand total
SELECT dept, city, SUM(salary) AS total_salary
FROM employee
GROUP BY ROLLUP (dept, city)
ORDER BY dept, city;
Result (NULL in a grouping column means "all values", i.e. a subtotal)
| dept | city | total_salary |
|---|---|---|
| HR | Bhopal | 50000 |
| HR | NULL | 50000 (HR subtotal) |
| IT | Bhopal | 70000 |
| IT | Indore | 80000 |
| IT | NULL | 150000 (IT subtotal) |
| Sales | Bhopal | 40000 |
| Sales | Indore | 40000 |
| Sales | NULL | 80000 (Sales subtotal) |
| NULL | NULL | 280000 (grand total) |
- For n columns,
ROLLUPproduces n + 1 grouping levels. - Column order matters:
ROLLUP(dept, city)gives dept subtotals;ROLLUP(city, dept)would give city subtotals. - Use for naturally hierarchical data: year β month β day, country β state β city.
12.4 CUBE
CUBE(a, b) produces subtotals for every possible combination of the columns:
(a, b) detail
(a) subtotal per a
(b) subtotal per b
() grand total
SELECT dept, city, SUM(salary) AS total_salary
FROM employee
GROUP BY CUBE (dept, city);
This returns everything ROLLUP did, plus the per-city subtotals:
| dept | city | total_salary |
|---|---|---|
| NULL | Bhopal | 160000 (city subtotal) |
| NULL | Indore | 120000 (city subtotal) |
- For n columns,
CUBEproduces 2βΏ grouping levels (for 3 columns: 8), so it grows quickly. - Use for cross-tab / multi-dimensional analysis where every dimension needs its own totals.
12.5 GROUPING SETS
GROUPING SETS lets you list exactly the groupings you want. It is the most flexible; ROLLUP and CUBE are shortcuts for common patterns.
SELECT dept, city, SUM(salary) AS total_salary
FROM employee
GROUP BY GROUPING SETS (
(dept), -- totals per department
(city), -- totals per city
() -- grand total
);
Result
| dept | city | total_salary |
|---|---|---|
| HR | NULL | 50000 |
| IT | NULL | 150000 |
| Sales | NULL | 80000 |
| NULL | Bhopal | 160000 |
| NULL | Indore | 120000 |
| NULL | NULL | 280000 |
(Notice: no detail rows by dept, city; only the sets you asked for.)
12.6 Equivalences
ROLLUP (a, b) = GROUPING SETS ( (a,b), (a), () )
CUBE (a, b) = GROUPING SETS ( (a,b), (a), (b), () )
12.7 Telling Subtotal NULLs Apart from Real NULLs: GROUPING()
A NULL in the output might mean "subtotal row" or a real NULL in the data. The GROUPING(column) function returns:
- 1 if the column was aggregated away (this is a subtotal row)
- 0 if the column is a real grouping value
SELECT
CASE WHEN GROUPING(dept) = 1 THEN 'ALL DEPARTMENTS' ELSE dept END AS dept,
CASE WHEN GROUPING(city) = 1 THEN 'ALL CITIES' ELSE city END AS city,
SUM(salary) AS total_salary
FROM employee
GROUP BY ROLLUP (dept, city);
| dept | city | total_salary |
|---|---|---|
| HR | Bhopal | 50000 |
| HR | ALL CITIES | 50000 |
| IT | Bhopal | 70000 |
| IT | Indore | 80000 |
| IT | ALL CITIES | 150000 |
| Sales | Bhopal | 40000 |
| Sales | Indore | 40000 |
| Sales | ALL CITIES | 80000 |
| ALL DEPARTMENTS | ALL CITIES | 280000 |
12.8 Comparison
| Feature | ROLLUP | CUBE | GROUPING SETS |
|---|---|---|---|
| Levels for n columns | n + 1 | 2βΏ | Whatever you list |
| Output | Hierarchical subtotals + grand total | All combinations | Exactly your chosen groupings |
| Order of columns matters | Yes | No | Not applicable |
| Best for | Hierarchies (year β month) | Multi-dimensional cross-tabs | Custom report layouts |
12.9 DBMS Support
| DBMS | Support |
|---|---|
| PostgreSQL | GROUPING SETS, ROLLUP, CUBE, GROUPING() |
| SQL Server | GROUPING SETS, ROLLUP, CUBE, GROUPING() |
| Oracle | GROUPING SETS, ROLLUP, CUBE, GROUPING() |
| MySQL | Only GROUP BY ... WITH ROLLUP syntax (and GROUPING()); no CUBE or GROUPING SETS |
MySQL form:
SELECT dept, city, SUM(salary)
FROM employee
GROUP BY dept, city WITH ROLLUP;
12.10 Emulating Them (Where Not Supported)
Combine separate GROUP BY queries with UNION ALL, giving each the same columns (using NULL as the placeholder):
SELECT dept, city, SUM(salary) FROM employee GROUP BY dept, city
UNION ALL
SELECT dept, NULL, SUM(salary) FROM employee GROUP BY dept
UNION ALL
SELECT NULL, NULL, SUM(salary) FROM employee;
PART B: SCALAR FUNCTIONS
Scalar functions work on one row at a time and return one value per row. They can be used in SELECT, WHERE, GROUP BY, HAVING, and ORDER BY, and inside aggregate functions.
13. String Functions
String (text) functions clean, search, extract, and combine character data.
13.1 Common String Functions
| Task | Standard / common form | Variants |
|---|---|---|
| Convert to upper case | UPPER(s) | |
| Convert to lower case | LOWER(s) | |
| Length in characters | CHAR_LENGTH(s) / LENGTH(s) | SQL Server: LEN(s) (ignores trailing spaces) |
| Part of a string | SUBSTRING(s, start, length) | Oracle, MySQL: SUBSTR(s, start, length) |
| Join strings | CONCAT(a, b, ...) | a || b (standard; PostgreSQL, Oracle); SQL Server: + or CONCAT |
| Remove spaces (both ends) | TRIM(s) | LTRIM(s), RTRIM(s) for one side |
| Replace text | REPLACE(s, old, new) | |
| Find position of text | POSITION(sub IN s) | MySQL, Oracle: INSTR(s, sub); SQL Server: CHARINDEX(sub, s) |
| Leftmost / rightmost characters | LEFT(s, n), RIGHT(s, n) | Oracle: use SUBSTR |
| Pad to a length | LPAD(s, n, pad), RPAD(s, n, pad) | Not in SQL Server |
| Reverse | REVERSE(s) | |
| Capitalise first letters | INITCAP(s) | PostgreSQL, Oracle |
| Concatenate with separator | CONCAT_WS(sep, a, b, ...) | MySQL, PostgreSQL, SQL Server |
SQL indexing: string positions in SQL start at 1, not 0.
13.2 Examples (on the employee table)
SELECT emp_name,
UPPER(emp_name) AS upper_name,
LOWER(emp_name) AS lower_name,
CHAR_LENGTH(emp_name) AS name_length,
SUBSTRING(emp_name, 1, 3) AS first_three,
LEFT(emp_name, 1) AS initial
FROM employee;
| emp_name | upper_name | lower_name | name_length | first_three | initial |
|---|---|---|---|---|---|
| Riya | RIYA | riya | 4 | Riy | R |
| Arjun | ARJUN | arjun | 5 | Arj | A |
| Meena | MEENA | meena | 5 | Mee | M |
| Karan | KARAN | karan | 5 | Kar | K |
| Divya | DIVYA | divya | 5 | Div | D |
| Sana | SANA | sana | 4 | San | S |
13.3 Concatenation
SELECT CONCAT(emp_name, ' (', dept, ')') AS label
FROM employee;
| label |
|---|
| Riya (HR) |
| Arjun (IT) |
| Meena (IT) |
| Karan (Sales) |
| Divya (Sales) |
| Sana (IT) |
NULL behaviour with concatenation:
CONCAT(...)in MySQL returns NULL if any argument is NULL; in PostgreSQL, SQL Server, and Oracle,CONCATtreats NULL as empty text.- The
||operator (standard) gives NULL if either side is NULL (except in Oracle, which treats NULL as an empty string). - Protect yourself with
COALESCE(col, '').
13.4 Cleaning and Replacing
SELECT TRIM(' hello ') AS trimmed, -- 'hello'
REPLACE('2026-10-13', '-', '/') AS replaced, -- '2026/10/13'
LPAD('42', 5, '0') AS padded; -- '00042'
13.5 Finding Text
SELECT POSITION('na' IN 'Sana') AS pos; -- 3
Combine with SUBSTRING to extract part of a string, for example the domain from an email:
SELECT SUBSTRING(email, POSITION('@' IN email) + 1) AS domain
FROM contact; -- illustrative table
13.6 Strings in Aggregation
String functions are often applied before grouping:
SELECT LEFT(emp_name, 1) AS initial, COUNT(*) AS people
FROM employee
GROUP BY LEFT(emp_name, 1);
Also, MIN and MAX work on text, and many systems provide a string aggregate to join values into a list (names vary by DBMS: STRING_AGG, GROUP_CONCAT, LISTAGG).
13.7 Tips
- Case sensitivity of text comparison depends on the DBMS and collation. Use
LOWER()on both sides for case-insensitive matching when in doubt. - Applying a function to a column in
WHERE(for exampleWHERE UPPER(name) = 'RIYA') can prevent index use; compare against the raw column when possible. - Watch out for trailing spaces and different lengths of
CHARcolumns.
14. Numeric Functions
Numeric functions perform maths on values.
14.1 Common Numeric Functions
| Function | Purpose | Example | Result |
|---|---|---|---|
ABS(x) | Absolute value | ABS(-7) | 7 |
ROUND(x, d) | Round to d decimal places | ROUND(4666.6667, 2) | 4666.67 |
CEIL(x) / CEILING(x) | Round up to an integer | CEIL(4.2) | 5 |
FLOOR(x) | Round down to an integer | FLOOR(4.8) | 4 |
TRUNC(x, d) / TRUNCATE(x, d) | Cut off decimals (no rounding) | TRUNCATE(4.789, 1) | 4.7 |
MOD(x, y) or x % y | Remainder | MOD(10, 3) | 1 |
POWER(x, y) | x raised to y | POWER(2, 3) | 8 |
SQRT(x) | Square root | SQRT(16) | 4 |
SIGN(x) | β1, 0, or 1 | SIGN(-5) | -1 |
RAND() / RANDOM() | Random number | varies |
(Names differ: TRUNC is Oracle/PostgreSQL, TRUNCATE is MySQL; SQL Server uses ROUND(x, d, 1) to truncate; RAND vs RANDOM varies.)
14.2 ROUND in Detail
SELECT ROUND(1234.567, 2) AS two_dp, -- 1234.57
ROUND(1234.567, 0) AS whole, -- 1235
ROUND(1234.567, -2) AS hundreds; -- 1200
- A positive digits value rounds after the decimal point.
- Zero rounds to a whole number.
- A negative value rounds to the left of the decimal point (tens, hundreds, ...).
- How exact halves like 2.5 are handled can differ between systems, so do not depend on it.
14.3 CEIL vs FLOOR with Negative Numbers
| x | CEIL(x) | FLOOR(x) |
|---|---|---|
| 4.2 | 5 | 4 |
| 4.8 | 5 | 4 |
| -4.2 | -4 | -5 |
CEIL always moves toward positive infinity and FLOOR toward negative infinity, so "up" and "down" refer to the number line, not to the size of the value.
14.4 Rounding Aggregate Results
SELECT dept,
ROUND(AVG(bonus), 2) AS avg_bonus
FROM employee
GROUP BY dept;
| dept | avg_bonus |
|---|---|
| HR | 5000.00 |
| IT | 7000.00 |
| Sales | 2000.00 |
(IT: only Meena has a non-NULL bonus, so the average is 7000. For the whole table, AVG(bonus) = 14000 / 3 = 4666.67.)
14.5 Percentage of Total
SELECT dept,
SUM(salary) AS dept_total,
ROUND(100.0 * SUM(salary) / (SELECT SUM(salary) FROM employee), 1) AS pct_of_total
FROM employee
GROUP BY dept;
| dept | dept_total | pct_of_total |
|---|---|---|
| HR | 50000 | 17.9 |
| IT | 150000 | 53.6 |
| Sales | 80000 | 28.6 |
(The rounded percentages add to 100.1 because of rounding; that is normal.)
14.6 Integer Division: A Classic Trap
When both operands are integers, many systems return an integer:
| Expression | PostgreSQL, SQL Server | MySQL, Oracle |
|---|---|---|
7 / 2 | 3 | 3.5 |
To be safe, make one side a decimal:
SELECT 7 / 2.0; -- 3.5
SELECT CAST(7 AS DECIMAL) / 2;
14.7 Other Uses
- Even/odd test:
WHERE MOD(emp_id, 2) = 0 - Salary hike:
salary * 1.10 - Bucketing:
FLOOR(salary / 10000) * 10000groups salaries into ranges of 10000.
SELECT FLOOR(salary / 10000) * 10000 AS salary_band, COUNT(*) AS people
FROM employee
WHERE salary IS NOT NULL
GROUP BY FLOOR(salary / 10000) * 10000
ORDER BY salary_band;
| salary_band | people |
|---|---|
| 40000 | 2 |
| 50000 | 1 |
| 70000 | 1 |
| 80000 | 1 |
14.8 NULL and Division by Zero
- Any arithmetic with NULL gives NULL (
salary + NULL= NULL). - Dividing by zero causes an error in most systems (MySQL returns NULL). Guard with
NULLIF(see section 17).
15. Date and Time Functions
Date/time functions get the current time, pull out parts, do date arithmetic, and format dates. This is the area where DBMS syntax differs most.
15.1 Date and Time Data Types
| Type | Holds |
|---|---|
DATE | Date only (2026-10-13) |
TIME | Time only (15:30:00) |
TIMESTAMP / DATETIME | Date and time (2026-10-13 15:30:00) |
INTERVAL | A span of time (PostgreSQL, Oracle, standard SQL) |
15.2 Getting the Current Date and Time
| Need | Standard | Variants |
|---|---|---|
| Current date | CURRENT_DATE | SQL Server: CAST(GETDATE() AS DATE) |
| Current date and time | CURRENT_TIMESTAMP | NOW() (MySQL, PostgreSQL), GETDATE() / SYSDATETIME() (SQL Server), SYSDATE (Oracle) |
15.3 Extracting Parts of a Date
Standard SQL: EXTRACT(part FROM date)
SELECT hire_date,
EXTRACT(YEAR FROM hire_date) AS yr,
EXTRACT(MONTH FROM hire_date) AS mon,
EXTRACT(DAY FROM hire_date) AS dy
FROM employee
WHERE emp_id = 1;
| hire_date | yr | mon | dy |
|---|---|---|---|
| 2020-03-15 | 2020 | 3 | 15 |
Variants
| DBMS | Style |
|---|---|
| MySQL | YEAR(d), MONTH(d), DAY(d), DAYNAME(d) |
| SQL Server | YEAR(d), MONTH(d), DAY(d), DATEPART(part, d) |
| PostgreSQL | EXTRACT(...), DATE_PART('year', d) |
| Oracle | EXTRACT(...), TO_CHAR(d, 'YYYY') |
Common parts: YEAR, QUARTER, MONTH, WEEK, DAY, HOUR, MINUTE, SECOND, and day-of-week.
15.4 Date Arithmetic: Adding and Subtracting
| DBMS | Add 30 days |
|---|---|
| PostgreSQL / standard | hire_date + INTERVAL '30 days' |
| MySQL | DATE_ADD(hire_date, INTERVAL 30 DAY) or hire_date + INTERVAL 30 DAY |
| SQL Server | DATEADD(DAY, 30, hire_date) |
| Oracle | hire_date + 30 (adds days); ADD_MONTHS(d, n) for months |
15.5 Difference Between Two Dates
| DBMS | Days between d1 and d2 |
|---|---|
| MySQL | DATEDIFF(d2, d1) |
| SQL Server | DATEDIFF(DAY, d1, d2) |
| PostgreSQL | d2 - d1 (gives days for DATE values) |
| Oracle | d2 - d1 (days) |
Example: tenure in years (approximate)
-- MySQL style
SELECT emp_name, TIMESTAMPDIFF(YEAR, hire_date, CURRENT_DATE) AS years_of_service
FROM employee;
-- PostgreSQL style
SELECT emp_name, EXTRACT(YEAR FROM AGE(CURRENT_DATE, hire_date)) AS years_of_service
FROM employee;
15.6 Truncating a Date to a Period
Useful for grouping by month, week, and so on.
| DBMS | First day of the month |
|---|---|
| PostgreSQL | DATE_TRUNC('month', d) |
| Oracle | TRUNC(d, 'MM') |
| SQL Server | DATETRUNC(MONTH, d) (newer versions) or DATEFROMPARTS(YEAR(d), MONTH(d), 1) |
| MySQL | DATE_FORMAT(d, '%Y-%m-01') |
15.7 Formatting a Date as Text
| DBMS | Example producing 13-Oct-2026 |
|---|---|
| MySQL | DATE_FORMAT(d, '%d-%b-%Y') |
| PostgreSQL, Oracle | TO_CHAR(d, 'DD-Mon-YYYY') |
| SQL Server | FORMAT(d, 'dd-MMM-yyyy') |
15.8 Grouping by Date Parts (Aggregation Meets Dates)
"Orders and revenue per month." (using orders)
SELECT EXTRACT(YEAR FROM order_date) AS yr,
EXTRACT(MONTH FROM order_date) AS mon,
COUNT(*) AS orders,
SUM(amount) AS revenue
FROM orders
GROUP BY EXTRACT(YEAR FROM order_date), EXTRACT(MONTH FROM order_date)
ORDER BY yr, mon;
| yr | mon | orders | revenue |
|---|---|---|---|
| 2026 | 1 | 1 | 500 |
| 2026 | 2 | 2 | 1000 |
| 2026 | 3 | 2 | 2000 |
15.9 Filtering by Date Ranges
Prefer range conditions over wrapping the column in a function, so indexes can be used:
-- Better: sargable (index-friendly)
WHERE order_date >= '2026-03-01' AND order_date < '2026-04-01'
-- Slower on large tables: function on the column
WHERE EXTRACT(MONTH FROM order_date) = 3 AND EXTRACT(YEAR FROM order_date) = 2026
Use the half-open range (>= start AND < next_start) rather than BETWEEN, which includes both ends and can mishandle timestamps with a time part.
15.10 Tips
- Use the ISO format
'YYYY-MM-DD'for date literals; it is unambiguous. - Be aware of time zones with
TIMESTAMPvalues. DATEvsTIMESTAMPcomparisons: a timestamp of2026-03-15 14:00is not equal to the date2026-03-15(which means midnight).- Month lengths, leap years, and end-of-month dates are handled by the date functions, so prefer them to manual arithmetic.
16. Conversion and Casting Functions
16.1 Why Conversion Is Needed
Data often arrives as the wrong type (numbers stored as text, dates stored as strings), or you need a specific type for a calculation or comparison. Casting converts a value from one data type to another.
There are two kinds:
| Kind | Description |
|---|---|
| Implicit conversion | The DBMS converts automatically (for example comparing a number to a text value) |
| Explicit conversion | You request the conversion with CAST, CONVERT, or a TO_... function |
Prefer explicit conversion. Implicit conversions can give surprising results, hurt index use, and behave differently between systems.
16.2 CAST (Standard SQL)
CAST(expression AS data_type)
SELECT CAST('123' AS INT) AS text_to_int, -- 123
CAST(salary AS DECIMAL(10,1)) AS one_decimal, -- e.g. 50000.0
CAST('2026-10-13' AS DATE) AS text_to_date,
CAST(emp_id AS VARCHAR(10)) AS int_to_text
FROM employee
WHERE emp_id = 1;
CAST is portable and works in all major databases.
16.3 Other Conversion Syntaxes
| DBMS | Functions |
|---|---|
| MySQL | CAST(x AS type), CONVERT(x, type); target types include SIGNED, UNSIGNED, DECIMAL, CHAR, DATE |
| SQL Server | CAST(...), CONVERT(type, x, style) (with a style code for date formats), TRY_CAST, TRY_CONVERT, PARSE, FORMAT |
| PostgreSQL | CAST(...), shorthand x::type (for example '123'::INT), TO_CHAR, TO_DATE, TO_NUMBER, TO_TIMESTAMP |
| Oracle | CAST(...), TO_CHAR(x, format), TO_DATE(text, format), TO_NUMBER(text) |
16.4 Converting Text to Dates (with a Format)
Strings in non-standard formats need a format mask:
| DBMS | Convert '13/10/2026' to a date |
|---|---|
| PostgreSQL, Oracle | TO_DATE('13/10/2026', 'DD/MM/YYYY') |
| MySQL | STR_TO_DATE('13/10/2026', '%d/%m/%Y') |
| SQL Server | CONVERT(DATE, '13/10/2026', 103) |
16.5 Converting Numbers and Dates to Text
-- PostgreSQL / Oracle
SELECT TO_CHAR(hire_date, 'DD-Mon-YYYY') FROM employee;
SELECT TO_CHAR(salary, '999,999.00') FROM employee;
-- MySQL
SELECT DATE_FORMAT(hire_date, '%d-%b-%Y') FROM employee;
16.6 Safe Conversion
A plain CAST raises an error when the value cannot be converted (for example CAST('abc' AS INT)), which can stop an entire query. Some systems provide safe versions that return NULL instead:
| DBMS | Safe cast |
|---|---|
| SQL Server | TRY_CAST(x AS type), TRY_CONVERT(type, x) |
| Others | Often need a validation check first (for example a pattern test), or error-handling logic |
16.7 Typical Uses
1. Avoid integer division
SELECT CAST(7 AS DECIMAL(5,2)) / 2; -- 3.50
2. Convert text columns before maths or sorting
Text sorts alphabetically ('100' comes before '20'), so cast to a number for numeric order:
SELECT * FROM some_table ORDER BY CAST(code AS INT); -- illustrative
3. Compare values of different types explicitly
WHERE CAST(hire_date AS DATE) = '2022-11-05'
4. Build text from numbers
SELECT CONCAT(emp_name, ' earns ', CAST(salary AS CHAR)) FROM employee;
16.8 Things That Can Go Wrong
| Issue | Example / Effect |
|---|---|
| Invalid value | CAST('abc' AS INT) β error |
| Overflow | Value too large for the target type β error |
| Precision loss | CAST(3.99 AS INT) may truncate or round depending on the DBMS |
| Truncated text | CAST('Hello World' AS VARCHAR(5)) may be cut to 'Hello' (or raise an error) |
| Ambiguous date strings | '03/04/2026' might be 3 April or March 4 depending on settings |
| NULL input | Casting NULL gives NULL |
| Index loss | WHERE CAST(col AS ...) = ... can stop an index from being used |
17. NULL Functions: COALESCE, NULLIF
17.1 Quick Reminder: NULL Means "Unknown"
- Any arithmetic or comparison with NULL gives NULL / UNKNOWN:
5 + NULLis NULL,NULL = NULLis not true. - Test with
IS NULL/IS NOT NULL. - Aggregates skip NULLs (see section 3.3).
17.2 COALESCE
COALESCE(a, b, c, ...) returns the first non-NULL value in the list (or NULL if they are all NULL).
SELECT emp_name,
salary,
bonus,
COALESCE(bonus, 0) AS bonus_or_zero,
salary + COALESCE(bonus, 0) AS total_pay
FROM employee;
| emp_name | salary | bonus | bonus_or_zero | total_pay |
|---|---|---|---|---|
| Riya | 50000 | 5000 | 5000 | 55000 |
| Arjun | 80000 | NULL | 0 | 80000 |
| Meena | 70000 | 7000 | 7000 | 77000 |
| Karan | 40000 | NULL | 0 | 40000 |
| Divya | 40000 | 2000 | 2000 | 42000 |
| Sana | NULL | NULL | 0 | NULL |
Without COALESCE(bonus, 0), salary + bonus would be NULL for Arjun and Karan (bonus is NULL). Sana's total stays NULL because her salary is NULL.
Properties
- Part of standard SQL, so it works in all major databases.
- Accepts two or more arguments, evaluated left to right; it stops at the first non-NULL value.
- All arguments should be of compatible types.
Common uses
-- Default text for missing values
SELECT COALESCE(city, 'Unknown') FROM employee;
-- Fallback chain: use mobile, else home phone, else 'none'
SELECT COALESCE(mobile, home_phone, 'none') FROM contact; -- illustrative table
-- Safe totals (sum of nothing is NULL, so replace with 0)
SELECT COALESCE(SUM(salary), 0) FROM employee WHERE dept = 'Legal'; -- 0
17.3 Vendor-Specific Two-Argument Versions
| Function | DBMS | Equivalent to |
|---|---|---|
IFNULL(a, b) | MySQL, SQLite | COALESCE(a, b) |
NVL(a, b) | Oracle | COALESCE(a, b) |
ISNULL(a, b) | SQL Server | COALESCE(a, b) (but takes the data type of the first argument) |
Prefer COALESCE because it is portable.
17.4 NULLIF
NULLIF(a, b) returns NULL if a = b, otherwise returns a. In other words, it creates a NULL on purpose.
NULLIF(10, 10) -- NULL
NULLIF(10, 5) -- 10
NULLIF('', '') -- NULL
Use 1: avoid divide-by-zero errors
SELECT dept,
SUM(salary) / NULLIF(COUNT(salary), 0) AS manual_average
FROM employee
GROUP BY dept;
If a group had no non-NULL salaries, COUNT(salary) would be 0, and NULLIF(0, 0) would turn it into NULL, so the division returns NULL instead of raising an error.
Use 2: turn placeholder values into real NULLs
SELECT NULLIF(city, '') FROM employee; -- empty string becomes NULL
Now COALESCE, COUNT(column), and IS NULL treat these as missing.
Use 3: clean data, then supply a default (combining both)
SELECT COALESCE(NULLIF(TRIM(city), ''), 'Unknown') AS city_clean
FROM employee;
Blank or whitespace-only cities become 'Unknown'.
17.5 COALESCE vs NULLIF (They Are Opposites)
COALESCE | NULLIF | |
|---|---|---|
| Purpose | Replace NULL with something | Create NULL from a specific value |
| Number of arguments | Two or more | Exactly two |
| Returns | First non-NULL argument | NULL if both equal, else the first argument |
| Typical use | Default values, safe totals | Avoid divide-by-zero, clean placeholders |
17.6 Related Conditional Tools
-
CASE: general conditional logic, and the engine behind conditional aggregation:CASE WHEN bonus IS NULL THEN 'No bonus' ELSE 'Has bonus' END -
IS NULL/IS NOT NULL: for filtering.
17.7 NULL Functions with Aggregates (Careful!)
| Expression | Meaning |
|---|---|
AVG(salary) | Average of non-NULL salaries (56000) |
AVG(COALESCE(salary, 0)) | Average treating missing salaries as zero (46666.67) |
COUNT(*) vs COUNT(salary) | 6 vs 5 |
SUM(COALESCE(bonus, 0)) | Same as SUM(bonus) (adding zeros changes nothing), but guards against a NULL total |
The choice between the first two is a business decision, not just a technical one.
18. Common Mistakes and Tips
| Mistake | What happens | Fix |
|---|---|---|
Non-aggregated column not in GROUP BY | Error (or arbitrary value in relaxed MySQL) | Add it to GROUP BY or aggregate it |
Aggregate in WHERE | Error | Move the condition to HAVING |
Row-level condition in HAVING | Works but slower | Use WHERE |
Using COUNT(*) after a LEFT JOIN | Counts NULL-extended rows (count of 1 instead of 0) | COUNT(right_table.column) |
| Summing a "one-side" column after a join | Double counting (fan-out) | Aggregate before joining, or aggregate the table alone |
Assuming AVG includes NULL rows | Average is higher than expected | Remember NULLs are skipped; use COALESCE if zero is intended |
| Integer division | 7/2 gives 3 | Use a decimal (7.0 / 2) or CAST |
| Dividing by a value that can be zero | Error | NULLIF(denominator, 0) |
SUM/AVG of an empty group | NULL | COALESCE(SUM(x), 0) |
| Concatenating a NULL | Whole result becomes NULL (in some systems) | COALESCE(col, '') |
Function on a column in WHERE | Index not used | Rewrite as a range (>= ... AND < ...) |
BETWEEN with timestamps | Misses or double-counts boundaries | Use >= start AND < next_start |
Confusing NULL in ROLLUP output | Cannot tell subtotals from real NULLs | Use GROUPING() |
Using SUM(DISTINCT x) to "fix" duplicates | Merges legitimately equal values | Fix the join logic instead |
| Using vendor-specific functions unknowingly | Query fails on another DBMS | Prefer standard forms (COALESCE, CAST, EXTRACT, CASE) |
19. Quick Revision Cheat Sheet
Aggregate Functions
| Function | Result | NULL handling | Empty input |
|---|---|---|---|
COUNT(*) | Number of rows | Counts all rows | 0 |
COUNT(col) | Non-NULL values | Skips NULLs | 0 |
COUNT(DISTINCT col) | Unique non-NULL values | Skips NULLs | 0 |
SUM(col) | Total | Skips NULLs | NULL |
AVG(col) | Mean of non-NULL values | Skips NULLs | NULL |
MIN(col) / MAX(col) | Smallest / largest | Skips NULLs | NULL |
Clause Summary
SELECT columns / aggregates
FROM tables / joins
WHERE row filter (no aggregates)
GROUP BY grouping columns
HAVING group filter (aggregates allowed)
ORDER BY ...
Execution order: FROM β WHERE β GROUP BY β HAVING β SELECT β DISTINCT β ORDER BY β LIMIT
WHERE vs HAVING in One Line
WHEREfilters rows before grouping (no aggregates).HAVINGfilters groups after aggregation (aggregates allowed).
Multi-Level Grouping
| Tool | Gives | Levels for n columns |
|---|---|---|
ROLLUP(a,b) | Detail + subtotal by a + grand total | n + 1 |
CUBE(a,b) | All combinations | 2βΏ |
GROUPING SETS(...) | Exactly the sets you list | As listed |
GROUPING(col) | 1 if the column is a subtotal placeholder |
Conditional Aggregation Patterns
COUNT(CASE WHEN cond THEN 1 END) -- count matching rows
SUM(CASE WHEN cond THEN col END) -- sum matching rows
SUM(col) FILTER (WHERE cond) -- same, where FILTER is supported
Scalar Function Families (Standard-ish Names)
| Family | Key functions |
|---|---|
| String | UPPER, LOWER, LENGTH/CHAR_LENGTH, SUBSTRING, CONCAT, TRIM, REPLACE, POSITION, LEFT/RIGHT, LPAD/RPAD |
| Numeric | ABS, ROUND, CEIL, FLOOR, TRUNC, MOD, POWER, SQRT, SIGN |
| Date/Time | CURRENT_DATE, CURRENT_TIMESTAMP, EXTRACT, date add/diff, truncate, format (varies by DBMS) |
| Conversion | CAST, CONVERT, TO_CHAR, TO_DATE, TO_NUMBER, TRY_CAST |
| NULL handling | COALESCE, NULLIF, IFNULL, NVL, ISNULL |
Key Rules to Remember
- Aggregates ignore NULLs (except
COUNT(*)). - Every
SELECTcolumn is aggregated or inGROUP BY. - Aggregates are not allowed in
WHERE. - After an outer join, count a column from the optional side.
- Watch for fan-out when summing across a one-to-many join.
- Use
100.0 *orCASTto avoid integer division. COALESCEfills in NULLs;NULLIFcreates them (great for divide-by-zero).- Use range conditions for dates instead of functions on the column.
Practice Questions (Using the Sample Tables)
- How many employees are there, and how many have a salary recorded?
Answer:
COUNT(*)= 6,COUNT(salary)= 5 (section 3.2). - Show total salary per department, highest first.
Answer:
GROUP BY deptwithORDER BY SUM(salary) DESC(section 4.8). - Which departments have more than one employee and an average salary above 45000?
Answer:
HAVING COUNT(*) > 1 AND AVG(salary) > 45000. Only IT qualifies (section 5). - Show headcount per department and per city, with subtotals and a grand total.
Answer:
GROUP BY ROLLUP (dept, city)(section 12.3). - Show, per department, the number of employees earning at least 60000, without using
FILTER. Answer:COUNT(CASE WHEN salary >= 60000 THEN 1 END)(section 9.2). - List every customer with their number of orders, including customers with none.
Answer:
LEFT JOIN+COUNT(o.order_id)+GROUP BY(section 11.2). - Why does
SUM(credit_limit)give 23000 after joining customers to orders? Answer: each customer's limit is repeated once per order (fan-out); the true total is 14000 (section 11.6). - Compute the average bonus per employee safely when a group might have zero bonuses.
Answer:
SUM(bonus) / NULLIF(COUNT(bonus), 0)(section 17.4). - Count how many employees were hired in each year.
Answer:
GROUP BY EXTRACT(YEAR FROM hire_date)(section 4.5). - Show the total pay per employee, treating a missing bonus as zero.
Answer:
salary + COALESCE(bonus, 0)(section 17.2).
#Subqueries and Advanced SQL#
A friendly, start-to-advanced guide to queries inside queries, reusable query building blocks (CTEs, views), window functions for analytics, and programmable database objects (procedures, functions, triggers, cursors, dynamic SQL). Read top to bottom the first time. Use the Quick Revision section before exams or interviews.
π Table of Contents
Part A β Subqueries
- Subquery Basics: Single-Row and Multiple-Row Subqueries
- Scalar Subqueries
- Multi-Column Subqueries
- Nested Subqueries
- Correlated Subqueries
- Subqueries in SELECT, FROM and WHERE
- EXISTS and NOT EXISTS
- IN and NOT IN
- ANY / SOME and ALL
Part B β CTEs 10. Common Table Expressions (CTEs) 11. Recursive CTEs
Part C β Window Functions 12. Window Functions: The Big Picture 13. ROW_NUMBER, RANK and DENSE_RANK 14. LAG and LEAD 15. FIRST_VALUE and LAST_VALUE 16. NTILE 17. Window Aggregates 18. PARTITION BY and Window Frames 19. Running Totals and Moving Averages 20. Top-N Per Group Queries
Part D β Reusable and Temporary Structures 21. Views and Materialized Views 22. Temporary Tables 23. Pivoting and Unpivoting
Part E β Programmable Database Objects 24. Stored Procedures 25. User-Defined Functions 26. Triggers 27. Cursors 28. Dynamic SQL
ποΈ Sample data used in these notes
Department
| dept_id | dept_name | location |
|---|---|---|
| D1 | Engineering | Delhi |
| D2 | Sales | Mumbai |
| D3 | HR | Delhi |
| D4 | Legal | Pune |
Employee
| emp_id | name | dept_id | salary | manager_id |
|---|---|---|---|---|
| 1 | Asha | D1 | 90000 | NULL |
| 2 | Ravi | D1 | 70000 | 1 |
| 3 | Meera | D1 | 70000 | 1 |
| 4 | Kabir | D2 | 60000 | 1 |
| 5 | Sana | D2 | 80000 | 4 |
| 6 | Dev | D2 | 50000 | 4 |
| 7 | Tara | D3 | 65000 | 1 |
| 8 | Omar | D1 | 55000 | 2 |
Sales (monthly sales per region)
| region | month_no | amount |
|---|---|---|
| East | 1 | 100 |
| East | 2 | 120 |
| East | 3 | 90 |
| East | 4 | 150 |
| West | 1 | 80 |
| West | 2 | 85 |
| West | 3 | 110 |
| West | 4 | 95 |
Notes: Department D4 (Legal) has no employees; Asha has no manager (NULL). These details make several examples interesting. Most examples use standard SQL; dialect differences are flagged where they matter.
Part A β Subqueries
1. Subquery Basics: Single-Row and Multiple-Row Subqueries
1.1 What is a subquery?
A subquery (inner query) is a SELECT statement nested inside another statement (the outer query). The inner query produces a result that the outer query uses.
Analogy: "Who earns more than the average salary?" You first need the average (inner question), then compare everyone to it (outer question).
SELECT name, salary
FROM Employee
WHERE salary > (SELECT AVG(salary) FROM Employee); -- inner query in parentheses
1.2 Basic rules
- A subquery is always wrapped in parentheses.
- It can appear in
SELECT,FROM,WHERE(and other places likeHAVING). - The number of rows and columns it returns must suit how it's used.
ORDER BYinside a subquery is usually pointless (and sometimes disallowed) unless combined with a row limit.
1.3 Classification by what the subquery returns
| Type | Rows returned | Columns returned | Typical operators |
|---|---|---|---|
| Single-row (scalar) | Exactly 1 | 1 | = <> > < >= <= |
| Multiple-row | 0 or more | 1 | IN, NOT IN, ANY, ALL, EXISTS |
| Multi-column | 1 or more | 2+ | (a, b) IN (...) |
| Table (derived table) | Any | Any | Used in FROM |
1.4 Single-row subquery
Returns one row β compare with ordinary operators.
-- Who earns the highest salary?
SELECT name, salary
FROM Employee
WHERE salary = (SELECT MAX(salary) FROM Employee);
| name | salary |
|---|---|
| Asha | 90000 |
1.5 Multiple-row subquery
Returns several rows β use IN, ANY, ALL or EXISTS.
-- Employees working in departments located in Delhi
SELECT name
FROM Employee
WHERE dept_id IN (SELECT dept_id FROM Department WHERE location = 'Delhi');
Delhi departments: D1 and D3 β Asha, Ravi, Meera, Omar, Tara.
1.6 β οΈ Common error
Using = with a subquery that returns more than one row:
SELECT name FROM Employee
WHERE dept_id = (SELECT dept_id FROM Department WHERE location = 'Delhi');
-- β Error: subquery returns more than 1 row (D1 and D3)
Fix: use IN instead of =.
1.7 Non-correlated vs correlated (preview)
- Non-correlated: the inner query is independent and runs once. (Everything above.)
- Correlated: the inner query refers to the outer query's columns (Section 5).
2. Scalar Subqueries
2.1 Definition
A scalar subquery returns exactly one row and one column: a single value. It can be used anywhere a single value is allowed: in SELECT, WHERE, ORDER BY, in expressions, and so on.
2.2 Examples
In WHERE:
SELECT name, salary FROM Employee
WHERE salary > (SELECT AVG(salary) FROM Employee); -- average = 67500
| name | salary |
|---|---|
| Asha | 90000 |
| Ravi | 70000 |
| Meera | 70000 |
| Sana | 80000 |
In SELECT (shows the value on every row):
SELECT name, salary,
(SELECT AVG(salary) FROM Employee) AS company_avg,
salary - (SELECT AVG(salary) FROM Employee) AS diff_from_avg
FROM Employee;
2.3 What if it doesn't return exactly one row?
| Situation | Result |
|---|---|
| 0 rows | The scalar subquery evaluates to NULL |
| 1 row | Works β |
| More than 1 row | β Runtime error |
π‘ To guarantee one row, use an aggregate (
MAX,AVGβ¦), a unique key in the filter, or a row limit (LIMIT 1/TOP 1/FETCH FIRST 1 ROW ONLY).
3. Multi-Column Subqueries
3.1 Definition
A subquery that returns more than one column, compared with several columns of the outer query at once, using a row constructor (col1, col2).
3.2 Example: highest-paid employee in each department
SELECT name, dept_id, salary
FROM Employee
WHERE (dept_id, salary) IN (
SELECT dept_id, MAX(salary)
FROM Employee
GROUP BY dept_id
);
| name | dept_id | salary |
|---|---|---|
| Asha | D1 | 90000 |
| Sana | D2 | 80000 |
| Tara | D3 | 65000 |
3.3 Pairwise vs non-pairwise comparison
| Style | Meaning | Example |
|---|---|---|
| Pairwise | Both columns must match together as a pair | WHERE (dept_id, salary) IN (SELECT dept_id, MAX(salary) ...) |
| Non-pairwise | Each column is checked separately with its own subquery | WHERE dept_id IN (...) AND salary IN (...) (may match wrong combinations) |
Pairwise is usually what you mean when matching "this department's highest salary".
3.4 Support
- β
PostgreSQL, MySQL, Oracle, SQLite: support
(a, b) IN (SELECT ...). - β SQL Server doesn't support row-constructor comparison; use
EXISTSor a join instead:
SELECT e.name, e.dept_id, e.salary
FROM Employee e
WHERE EXISTS (
SELECT 1 FROM Employee x
WHERE x.dept_id = e.dept_id
GROUP BY x.dept_id
HAVING MAX(x.salary) = e.salary
);
4. Nested Subqueries
4.1 Definition
A subquery inside another subquery (several levels deep).
4.2 Example
"Names of employees who work in a department located where the highest-paid employee's department is located."
SELECT name
FROM Employee
WHERE dept_id IN (
SELECT dept_id FROM Department
WHERE location = (
SELECT d.location
FROM Department d
WHERE d.dept_id = (
SELECT dept_id FROM Employee
WHERE salary = (SELECT MAX(salary) FROM Employee)
)
)
);
Reading from the inside out:
- Innermost:
MAX(salary)β 90000 - Employee with 90000 β dept
D1 - Location of
D1βDelhi - All departments in Delhi β
D1,D3 - Employees in those departments β Asha, Ravi, Meera, Omar, Tara
4.3 Execution order
Non-correlated nested subqueries run innermost first, each result feeding the next level out.
4.4 Advice
- Deep nesting is hard to read and debug.
- Prefer CTEs (Section 10) or joins to flatten the logic.
- Most databases allow many nesting levels (the limit is high), but readability is the real limit.
5. Correlated Subqueries
5.1 Definition
A correlated subquery uses a column from the outer query, so it can't run on its own. Conceptually, it is re-evaluated for each row of the outer query.
5.2 Example: employees earning more than their own department's average
SELECT e.name, e.dept_id, e.salary
FROM Employee e
WHERE e.salary > (
SELECT AVG(x.salary)
FROM Employee x
WHERE x.dept_id = e.dept_id -- β refers to the outer row (correlation)
);
| name | dept_id | salary |
|---|---|---|
| Asha | D1 | 90000 (D1 avg = 71250) |
| Sana | D2 | 80000 (D2 avg = 63333) |
5.3 How it works (conceptually)
For each row e in Employee:
compute AVG(salary) of employees x where x.dept_id = e.dept_id
keep e if e.salary > that average
5.4 Non-correlated vs correlated
| Non-correlated | Correlated | |
|---|---|---|
| Depends on outer query? | β No | β Yes |
| Runs | Once | Conceptually once per outer row |
| Can run alone? | β | β |
| Typical use | Global values (overall average) | Row-by-row comparison (per-group value) |
5.5 Performance note
Optimizers often rewrite correlated subqueries into joins or other efficient plans, but not always. For large data, consider alternatives:
- A join to a pre-aggregated derived table or CTE
- A window function:
AVG(salary) OVER (PARTITION BY dept_id)(Section 17)
5.6 Correlated subquery in SELECT
SELECT d.dept_name,
(SELECT COUNT(*) FROM Employee e WHERE e.dept_id = d.dept_id) AS emp_count
FROM Department d;
| dept_name | emp_count |
|---|---|
| Engineering | 4 |
| Sales | 3 |
| HR | 1 |
| Legal | 0 |
(Legal appears with 0, which a plain inner join would drop.)
6. Subqueries in SELECT, FROM and WHERE
6.1 In the WHERE clause (filtering)
The most common place. Used with comparison operators, IN, EXISTS, ANY, ALL.
SELECT name FROM Employee
WHERE dept_id IN (SELECT dept_id FROM Department WHERE location = 'Mumbai');
6.2 In the SELECT list (computed column)
Must be a scalar subquery (one value per outer row).
SELECT name, salary,
(SELECT dept_name FROM Department d WHERE d.dept_id = e.dept_id) AS dept
FROM Employee e;
- Good for small lookups, but often a join is clearer and faster.
6.3 In the FROM clause (derived table / inline view)
The subquery's result acts as a temporary table for the outer query.
SELECT d.dept_name, t.avg_sal
FROM Department d
JOIN (
SELECT dept_id, AVG(salary) AS avg_sal
FROM Employee
GROUP BY dept_id
) AS t ON t.dept_id = d.dept_id;
| dept_name | avg_sal |
|---|---|
| Engineering | 71250 |
| Sales | 63333.33 |
| HR | 65000 |
Rules:
- A derived table must have an alias in most databases (MySQL, PostgreSQL, SQL Server require it).
- All its columns need names (use
ASfor computed ones). - It can't normally refer to other tables in the same
FROM(unless usingLATERAL/APPLY, which are database-specific).
6.4 Where each placement fits
| Placement | Subquery must return | Use for |
|---|---|---|
WHERE | Scalar, list, or rows (for EXISTS) | Filtering |
SELECT | Scalar (one value) | Extra computed column |
FROM | A full table (any rows/columns) | Pre-aggregating or reshaping data |
7. EXISTS and NOT EXISTS
7.1 What does EXISTS do?
EXISTS (subquery) returns TRUE if the subquery returns at least one row, otherwise FALSE. It only checks whether rows exist, not what they contain.
7.2 Example: departments that have employees
SELECT d.dept_name
FROM Department d
WHERE EXISTS (
SELECT 1
FROM Employee e
WHERE e.dept_id = d.dept_id
);
β Engineering, Sales, HR.
7.3 NOT EXISTS: departments with no employees
SELECT d.dept_name
FROM Department d
WHERE NOT EXISTS (
SELECT 1 FROM Employee e WHERE e.dept_id = d.dept_id
);
β Legal.
7.4 Key facts
- It's almost always correlated (links inner to outer).
- The
SELECTlist inside doesn't matter (SELECT 1,SELECT *,SELECT NULLall behave the same). - Evaluation stops at the first matching row (short-circuit), which can be very efficient.
EXISTSis never UNKNOWN: it's TRUE or FALSE, even with NULLs involved.
7.5 EXISTS vs IN
EXISTS | IN | |
|---|---|---|
| Checks | Whether any row exists | Whether a value is in a list |
| NULL handling | Safe | NOT IN breaks if the list has NULL |
| Typical best use | Large inner results, correlation, NOT checks | Small, fixed lists |
| Compares columns? | Via the correlation condition | Directly with a single column |
β Rule of thumb: for "does not exist" logic, prefer
NOT EXISTSoverNOT IN.
8. IN and NOT IN
8.1 IN with a subquery
True if the value matches any value returned by the subquery.
SELECT name FROM Employee
WHERE dept_id IN (SELECT dept_id FROM Department WHERE location = 'Delhi');
x IN (a, b, c) means x = a OR x = b OR x = c.
8.2 NOT IN
True if the value matches none of the returned values.
SELECT name FROM Employee
WHERE dept_id NOT IN (SELECT dept_id FROM Department WHERE location = 'Delhi');
β Kabir, Sana, Dev (department D2, Mumbai).
8.3 π¨ The NULL trap with NOT IN
x NOT IN (1, 2, NULL) expands to x <> 1 AND x <> 2 AND x <> NULL, and the last part is UNKNOWN, so the whole condition is never TRUE.
Example: employees who are not managers
SELECT name FROM Employee
WHERE emp_id NOT IN (SELECT manager_id FROM Employee);
manager_id contains a NULL (Asha has no manager) β returns no rows β.
Fixes
-- (a) Remove NULLs from the subquery
SELECT name FROM Employee
WHERE emp_id NOT IN (SELECT manager_id FROM Employee WHERE manager_id IS NOT NULL);
-- (b) Use NOT EXISTS (preferred)
SELECT e.name FROM Employee e
WHERE NOT EXISTS (SELECT 1 FROM Employee m WHERE m.manager_id = e.emp_id);
Both give: Meera, Sana, Dev, Tara, Omar (the non-managers).
8.4 Notes
INcan take a literal list or a subquery; the subquery must return one column (or multiple, with a row constructor).- Duplicates in the subquery result don't matter.
- A very large
INlist can be slow; consider a join orEXISTS.
9. ANY / SOME and ALL
These compare a value to every value returned by a subquery, using a comparison operator.
9.1 ANY (or SOME: they are synonyms)
Condition is TRUE if the comparison is true for at least one returned value.
-- Salary greater than ANY HR salary (HR has only 65000)
SELECT name, salary FROM Employee
WHERE salary > ANY (SELECT salary FROM Employee WHERE dept_id = 'D3');
= salary greater than the minimum of the set (65000) β Asha, Ravi, Meera, Sana.
9.2 ALL
Condition is TRUE if the comparison is true for every returned value.
-- Salary greater than ALL Sales salaries (60000, 80000, 50000)
SELECT name, salary FROM Employee
WHERE salary > ALL (SELECT salary FROM Employee WHERE dept_id = 'D2');
= salary greater than the maximum (80000) β Asha only.
9.3 Handy equivalences
| Expression | Equivalent to |
|---|---|
x = ANY (subquery) | x IN (subquery) |
x <> ALL (subquery) | x NOT IN (subquery) |
x > ANY (subquery) | x > MIN of the subquery |
x > ALL (subquery) | x > MAX of the subquery |
x < ANY (subquery) | x < MAX of the subquery |
x < ALL (subquery) | x < MIN of the subquery |
9.4 Edge cases
| Subquery result | ANY | ALL |
|---|---|---|
| Empty | FALSE | TRUE (vacuously true) |
| Contains NULL | May yield UNKNOWN | May yield UNKNOWN (and <> ALL has the same trap as NOT IN) |
9.5 Support
β
PostgreSQL, MySQL, Oracle, SQL Server. β SQLite doesn't support ANY/ALL: use MIN() / MAX() subqueries instead (salary > (SELECT MAX(salary) ...)).
Part B β CTEs
10. Common Table Expressions (CTEs)
10.1 What is a CTE?
A CTE is a named, temporary result set defined with the WITH keyword at the start of a statement and used in the statement that follows. Think of it as giving a subquery a name so the main query reads like a story.
10.2 Syntax
WITH cte_name (optional_column_list) AS (
SELECT ...
)
SELECT ...
FROM cte_name;
10.3 Example
WITH dept_avg AS (
SELECT dept_id, AVG(salary) AS avg_sal
FROM Employee
GROUP BY dept_id
)
SELECT e.name, e.salary, d.avg_sal
FROM Employee e
JOIN dept_avg d ON d.dept_id = e.dept_id
WHERE e.salary > d.avg_sal;
β Asha (90000 > 71250) and Sana (80000 > 63333).
10.4 Multiple CTEs (comma-separated, can build on each other)
WITH dept_avg AS (
SELECT dept_id, AVG(salary) AS avg_sal
FROM Employee GROUP BY dept_id
),
high_depts AS (
SELECT dept_id FROM dept_avg WHERE avg_sal > 65000
)
SELECT name, dept_id FROM Employee
WHERE dept_id IN (SELECT dept_id FROM high_depts);
(Only one WITH; later CTEs may reference earlier ones.)
10.5 Key points
- Scope: a CTE exists only for the one statement that follows. It's not stored.
- A CTE can be referenced multiple times in the main query, so you write the logic once.
- Works with
SELECT, and in most systems also withINSERT,UPDATE,DELETE. - Column names can be given in the list or come from the inner
SELECT. - SQL Server: the statement before
WITHmust end with a semicolon. - Support: PostgreSQL, SQL Server, Oracle, SQLite, and MySQL 8.0+.
10.6 CTE vs subquery vs view vs temp table
| Feature | Subquery | CTE | View | Temp table |
|---|---|---|---|---|
| Named? | No | β | β | β |
| Lifetime | One place in a query | One statement | Permanent | Session |
| Reusable within the query | β (repeat it) | β | β | β |
| Stores data? | No | No (usually) | No | Yes |
| Can be indexed? | No | No | No (unless materialized) | β |
| Best for | Simple one-off logic | Readability, step-by-step logic, recursion | Sharing logic across queries | Large intermediate results |
π‘ In many databases a CTE is just a readability tool: the optimizer may inline it like a subquery. Some systems may compute it once (materialize) in certain cases (e.g., PostgreSQL has
MATERIALIZED/NOT MATERIALIZEDhints).
11. Recursive CTEs
11.1 What is it?
A recursive CTE is a CTE that refers to itself. It's used to walk hierarchies and graphs (org charts, category trees, bill of materials) or to generate sequences.
11.2 Structure
WITH RECURSIVE cte_name AS (
-- 1. Anchor member: the starting rows (runs once)
SELECT ...
UNION ALL
-- 2. Recursive member: refers to cte_name, runs repeatedly
SELECT ...
FROM table JOIN cte_name ON ...
)
SELECT * FROM cte_name;
(PostgreSQL, MySQL, SQLite require the word RECURSIVE; SQL Server and Oracle omit it and just use WITH.)
11.3 How it executes
- Run the anchor β initial result set.
- Run the recursive member using the rows produced in the previous step.
- Repeat step 2 until it produces no new rows.
- Combine (
UNION ALL) all results.
11.4 Example: organisation hierarchy
WITH RECURSIVE org AS (
SELECT emp_id, name, manager_id, 0 AS level, name AS path
FROM Employee
WHERE manager_id IS NULL -- anchor: the top boss
UNION ALL
SELECT e.emp_id, e.name, e.manager_id, o.level + 1,
o.path || ' > ' || e.name -- use CONCAT() in MySQL, + in SQL Server
FROM Employee e
JOIN org o ON e.manager_id = o.emp_id -- recursive step: find direct reports
)
SELECT name, level, path FROM org ORDER BY path;
| name | level | path |
|---|---|---|
| Asha | 0 | Asha |
| Kabir | 1 | Asha > Kabir |
| Dev | 2 | Asha > Kabir > Dev |
| Sana | 2 | Asha > Kabir > Sana |
| Meera | 1 | Asha > Meera |
| Ravi | 1 | Asha > Ravi |
| Omar | 2 | Asha > Ravi > Omar |
| Tara | 1 | Asha > Tara |
11.5 Example: generating a number sequence
WITH RECURSIVE nums AS (
SELECT 1 AS n
UNION ALL
SELECT n + 1 FROM nums WHERE n < 5 -- the stopping condition
)
SELECT n FROM nums; -- 1, 2, 3, 4, 5
11.6 Safety: avoid infinite loops
- Always ensure the recursion has a stopping condition (the
WHEREor a join that eventually returns nothing). - Cyclic data (A reports to B, B reports to A) can loop forever; track a level/depth or visited path and cap it.
- Databases enforce recursion limits:
- SQL Server: default
MAXRECURSIONis 100 (OPTION (MAXRECURSION n)) - MySQL:
cte_max_recursion_depth(default 1000) - PostgreSQL: none by default, so be careful (
LIMITcan help)
- SQL Server: default
11.7 UNION vs UNION ALL
UNION ALLis standard and faster (keeps duplicates).UNION(removes duplicates) can also stop some cycles, but costs more and isn't allowed everywhere.
Part C β Window Functions
12. Window Functions: The Big Picture
12.1 What are they?
A window function performs a calculation across a set of rows related to the current row (its "window"), without collapsing the rows.
GROUP BY aggregate | Window function | |
|---|---|---|
| Rows in output | One per group (rows collapse) | Same as input (every row kept) |
| Can show detail + aggregate together | β | β |
12.2 Syntax
function_name(args) OVER (
[PARTITION BY col1, col2, ...] -- split rows into groups
[ORDER BY col [ASC|DESC], ...] -- order within each group
[frame_clause] -- which rows around the current row to use
)
- Empty
OVER ()= the window is the whole result set.
12.3 Families of window functions
| Family | Functions |
|---|---|
| Ranking | ROW_NUMBER, RANK, DENSE_RANK, NTILE |
| Value (offset) | LAG, LEAD, FIRST_VALUE, LAST_VALUE |
| Aggregate | SUM, AVG, COUNT, MIN, MAX used with OVER |
12.4 Where they run (logical order)
FROM β WHERE β GROUP BY β HAVING β WINDOW FUNCTIONS β SELECT β DISTINCT β ORDER BY β LIMIT
- Window functions are evaluated after
WHERE,GROUP BYandHAVING. - β οΈ So you can't use them in
WHERE(orGROUP BY/HAVING). To filter on a window result, wrap the query in a CTE or subquery and filter outside (see Top-N, Section 20).
12.5 Support
PostgreSQL, SQL Server (2012+ for the full set), Oracle, MySQL 8.0+, SQLite 3.25+.
13. ROW_NUMBER, RANK and DENSE_RANK
All three assign a position to each row based on ORDER BY. They differ in how ties are treated.
13.1 Example (overall salary ranking)
SELECT name, salary,
ROW_NUMBER() OVER (ORDER BY salary DESC) AS row_num,
RANK() OVER (ORDER BY salary DESC) AS rnk,
DENSE_RANK() OVER (ORDER BY salary DESC) AS dense_rnk
FROM Employee;
| name | salary | row_num | rnk | dense_rnk |
|---|---|---|---|---|
| Asha | 90000 | 1 | 1 | 1 |
| Sana | 80000 | 2 | 2 | 2 |
| Ravi | 70000 | 3 | 3 | 3 |
| Meera | 70000 | 4 | 3 | 3 |
| Tara | 65000 | 5 | 5 | 4 |
| Kabir | 60000 | 6 | 6 | 5 |
| Omar | 55000 | 7 | 7 | 6 |
| Dev | 50000 | 8 | 8 | 7 |
(For the tied pair Ravi/Meera, which one gets row_num 3 vs 4 is arbitrary unless you add a tiebreaker such as , name.)
13.2 How ties behave
| Function | Ties get⦠| After a tie⦠| Pattern |
|---|---|---|---|
| ROW_NUMBER | Different numbers (arbitrary among ties) | Continues 1 by 1 | 1, 2, 3, 4, 5 |
| RANK | The same rank | Skips numbers (gap) | 1, 2, 3, 3, 5 |
| DENSE_RANK | The same rank | No gaps | 1, 2, 3, 3, 4 |
13.3 With PARTITION BY (rank within each department)
SELECT name, dept_id, salary,
RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS dept_rank
FROM Employee;
Ranking restarts at 1 in each department.
13.4 When to use which
| Need | Use |
|---|---|
| Unique sequence number / de-duplicate (keep one row) | ROW_NUMBER |
| Competition-style ranking (ties share, gaps follow) | RANK |
| Ranking without gaps (e.g., "3rd highest distinct salary") | DENSE_RANK |
β οΈ
ORDER BYis required insideOVER()for these ranking functions.
14. LAG and LEAD
14.1 Purpose
Access the value from another row in the same partition without a self-join:
LAGlooks backward (previous rows).LEADlooks forward (next rows).
14.2 Syntax
LAG (column [, offset [, default]]) OVER (PARTITION BY ... ORDER BY ...)
LEAD(column [, offset [, default]]) OVER (PARTITION BY ... ORDER BY ...)
- offset: how many rows away (default 1).
- default: value to return if there is no such row (default
NULL).
14.3 Example: month-over-month change (East region)
SELECT month_no, amount,
LAG(amount) OVER (ORDER BY month_no) AS prev_amount,
LEAD(amount) OVER (ORDER BY month_no) AS next_amount,
amount - LAG(amount) OVER (ORDER BY month_no) AS change_from_prev
FROM Sales
WHERE region = 'East';
| month_no | amount | prev_amount | next_amount | change_from_prev |
|---|---|---|---|---|
| 1 | 100 | NULL | 120 | NULL |
| 2 | 120 | 100 | 90 | 20 |
| 3 | 90 | 120 | 150 | -30 |
| 4 | 150 | 90 | NULL | 60 |
14.4 With a default value and PARTITION BY (all regions)
SELECT region, month_no, amount,
LAG(amount, 1, 0) OVER (PARTITION BY region ORDER BY month_no) AS prev_amount
FROM Sales;
PARTITION BY region makes sure the "previous" row never comes from another region.
14.5 Notes
ORDER BYinsideOVERis essential (otherwise "previous/next" is meaningless).LAG/LEADignore the frame clause; they work by row offset.- Typical uses: growth rates, comparing with the previous period, detecting changes, time gaps between events.
15. FIRST_VALUE and LAST_VALUE
15.1 Purpose
FIRST_VALUE(col): the value from the first row of the window frame.LAST_VALUE(col): the value from the last row of the window frame.
15.2 Example: compare each month to the region's first month
SELECT region, month_no, amount,
FIRST_VALUE(amount) OVER (PARTITION BY region ORDER BY month_no) AS first_amount,
amount - FIRST_VALUE(amount) OVER (PARTITION BY region ORDER BY month_no) AS vs_first
FROM Sales;
East: first_amount = 100 on every row; West: 80.
15.3 β οΈ The LAST_VALUE surprise (the default frame)
When ORDER BY is present, the default frame is
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW: it ends at the current row, so LAST_VALUE simply returns the current row's value!
SELECT month_no, amount,
LAST_VALUE(amount) OVER (ORDER BY month_no) AS last_default
FROM Sales WHERE region = 'East';
-- last_default: 100, 120, 90, 150 (same as amount!) β not what you wanted
Fix: specify the full frame
LAST_VALUE(amount) OVER (
ORDER BY month_no
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS last_amount
-- β 150, 150, 150, 150 β
15.4 Notes
FIRST_VALUEworks as expected with the default frame (the frame always starts at the first row).- Some systems support
IGNORE NULLS/RESPECT NULLSoptions. - Use for: "first purchase date", "latest status", comparing to a baseline.
16. NTILE
16.1 Purpose
NTILE(n) divides the ordered rows of a partition into n roughly equal groups (buckets) and returns the bucket number (1 to n) for each row. Useful for quartiles, deciles, percentile groups.
16.2 Example: salary quartiles
SELECT name, salary,
NTILE(4) OVER (ORDER BY salary DESC) AS quartile
FROM Employee;
| name | salary | quartile |
|---|---|---|
| Asha | 90000 | 1 |
| Sana | 80000 | 1 |
| Ravi | 70000 | 2 |
| Meera | 70000 | 2 |
| Tara | 65000 | 3 |
| Kabir | 60000 | 3 |
| Omar | 55000 | 4 |
| Dev | 50000 | 4 |
8 rows Γ· 4 buckets = 2 rows each.
16.3 When rows don't divide evenly
The earlier buckets get one extra row. For NTILE(3) on 8 rows β bucket sizes 3, 3, 2.
16.4 Notes
ORDER BYdecides which bucket each row falls into;PARTITION BYrestarts the bucketing per group.- Ties can be split across buckets (NTILE doesn't look at values, only row positions).
- If there are fewer rows than buckets, some buckets stay empty.
17. Window Aggregates
17.1 Idea
Use ordinary aggregate functions (SUM, AVG, COUNT, MIN, MAX) as window functions by adding OVER (...). Each row keeps its detail and shows an aggregate.
17.2 Example: each employee vs their department
SELECT name, dept_id, salary,
SUM(salary) OVER (PARTITION BY dept_id) AS dept_total,
AVG(salary) OVER (PARTITION BY dept_id) AS dept_avg,
COUNT(*) OVER (PARTITION BY dept_id) AS dept_size,
salary - AVG(salary) OVER (PARTITION BY dept_id) AS diff_from_dept_avg
FROM Employee;
| name | dept_id | salary | dept_total | dept_avg | dept_size |
|---|---|---|---|---|---|
| Asha | D1 | 90000 | 285000 | 71250 | 4 |
| Ravi | D1 | 70000 | 285000 | 71250 | 4 |
| Meera | D1 | 70000 | 285000 | 71250 | 4 |
| Omar | D1 | 55000 | 285000 | 71250 | 4 |
| Kabir | D2 | 60000 | 190000 | 63333.33 | 3 |
| Sana | D2 | 80000 | 190000 | 63333.33 | 3 |
| Dev | D2 | 50000 | 190000 | 63333.33 | 3 |
| Tara | D3 | 65000 | 65000 | 65000 | 1 |
17.3 Percentage of a total
SELECT name, salary,
ROUND(100.0 * salary / SUM(salary) OVER (), 1) AS pct_of_company
FROM Employee;
OVER () with nothing inside = the entire result set (company total = 540000).
17.4 Behaviour depends on ORDER BY
OVER clause | What the aggregate covers |
|---|---|
OVER () | The whole result set |
OVER (PARTITION BY x) | The whole partition |
OVER (PARTITION BY x ORDER BY y) | Cumulative from the partition start to the current row (default frame) |
17.5 Compared with a correlated subquery
A window aggregate does in one pass what a correlated subquery does with repeated lookups (Section 5), and is usually simpler and faster.
18. PARTITION BY and Window Frames
18.1 PARTITION BY
Splits rows into independent groups; the window function restarts for each group. (It's like GROUP BY for windows, but rows are not collapsed.)
SUM(amount) OVER (PARTITION BY region)
- No
PARTITION BYβ the whole result set is one partition. - You can partition by several columns:
PARTITION BY region, year.
18.2 ORDER BY inside OVER
Defines the order of rows inside each partition: essential for ranking, LAG/LEAD, running totals.
18.3 The frame clause
The frame decides which rows around the current row the function actually uses.
{ROWS | RANGE | GROUPS} BETWEEN <start> AND <end>
Frame boundaries
| Boundary | Meaning |
|---|---|
UNBOUNDED PRECEDING | From the first row of the partition |
n PRECEDING | n rows (or value units) before the current row |
CURRENT ROW | The current row |
n FOLLOWING | n rows after the current row |
UNBOUNDED FOLLOWING | Up to the last row of the partition |
Shorthand: ROWS UNBOUNDED PRECEDING = ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW.
18.4 ROWS vs RANGE (vs GROUPS)
| Mode | Counts by | Ties (equal ORDER BY values) |
|---|---|---|
ROWS | Physical row position | Each row treated separately |
RANGE | Value of the ORDER BY column | Tied rows are included together (peers) |
GROUPS | Groups of peer rows (PostgreSQL, SQLite) | Counts peer groups |
Example with ties (employees ordered by salary ascending; two employees earn 70000):
SELECT name, salary,
SUM(salary) OVER (ORDER BY salary ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS rows_sum,
SUM(salary) OVER (ORDER BY salary RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS range_sum
FROM Employee;
| name | salary | rows_sum | range_sum |
|---|---|---|---|
| Dev | 50000 | 50000 | 50000 |
| Omar | 55000 | 105000 | 105000 |
| Kabir | 60000 | 165000 | 165000 |
| Tara | 65000 | 230000 | 230000 |
| Ravi | 70000 | 300000 | 370000 |
| Meera | 70000 | 370000 | 370000 |
| Sana | 80000 | 450000 | 450000 |
| Asha | 90000 | 540000 | 540000 |
RANGE gives both tied rows the same total (it includes all peers); ROWS adds one row at a time.
18.5 Default frame
OVER has⦠| Default frame |
|---|---|
ORDER BY | RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW |
No ORDER BY | The entire partition |
β For running totals and moving averages, explicitly write
ROWS ...to avoid tie surprises.
18.6 Named windows (reuse the same definition)
SELECT region, month_no,
SUM(amount) OVER w AS running_total,
AVG(amount) OVER w AS running_avg
FROM Sales
WINDOW w AS (PARTITION BY region ORDER BY month_no
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW);
(Supported in PostgreSQL, MySQL 8, SQLite; not in SQL Server.)
18.7 Which functions use frames?
- β
Aggregates (
SUM,AVG, β¦),FIRST_VALUE,LAST_VALUE - β Ranking functions and
LAG/LEAD,NTILE: they ignore the frame clause.
19. Running Totals and Moving Averages
19.1 Running (cumulative) total
Sum of everything up to the current row.
SELECT region, month_no, amount,
SUM(amount) OVER (
PARTITION BY region
ORDER BY month_no
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_total
FROM Sales;
| region | month_no | amount | running_total |
|---|---|---|---|
| East | 1 | 100 | 100 |
| East | 2 | 120 | 220 |
| East | 3 | 90 | 310 |
| East | 4 | 150 | 460 |
| West | 1 | 80 | 80 |
| West | 2 | 85 | 165 |
| West | 3 | 110 | 275 |
| West | 4 | 95 | 370 |
19.2 Moving (rolling) average
Average over a sliding window, such as the last 3 months. This smooths out short-term noise.
SELECT month_no, amount,
AVG(amount) OVER (
ORDER BY month_no
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW -- current + previous 2 rows
) AS moving_avg_3
FROM Sales
WHERE region = 'East';
| month_no | amount | moving_avg_3 | Calculation |
|---|---|---|---|
| 1 | 100 | 100.00 | (100) / 1 |
| 2 | 120 | 110.00 | (100 + 120) / 2 |
| 3 | 90 | 103.33 | (100 + 120 + 90) / 3 |
| 4 | 150 | 120.00 | (120 + 90 + 150) / 3 |
The first rows average fewer than 3 values because the frame has fewer rows available. If you want only complete windows, check the row count (e.g.,
COUNT(*) OVER (same frame) = 3).
19.3 Other useful frames
| Goal | Frame |
|---|---|
| Running total | ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW |
| 3-period moving average (trailing) | ROWS BETWEEN 2 PRECEDING AND CURRENT ROW |
| Centered moving average (prev, current, next) | ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING |
| Reverse running total (remaining) | ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING |
| Whole partition total | ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING (or omit ORDER BY) |
19.4 Tips
- Always include
ORDER BYso "running" has a meaning, andPARTITION BYto restart per group. - Prefer
ROWSover the defaultRANGEto avoid duplicate-value (tie) surprises. - Time-based windows ("last 7 days" regardless of missing days) need
RANGEwith an interval (supported in some systems) or a calendar table.
20. Top-N Per Group Queries
20.1 The problem
"Give me the top 2 highest-paid employees in each department." A plain LIMIT only trims the whole result, not each group.
20.2 Standard solution: window function + CTE
WITH ranked AS (
SELECT name, dept_id, salary,
ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rn
FROM Employee
)
SELECT name, dept_id, salary
FROM ranked
WHERE rn <= 2; -- filter happens OUTSIDE the window computation
| name | dept_id | salary |
|---|---|---|
| Asha | D1 | 90000 |
| Ravi (or Meera) | D1 | 70000 |
| Sana | D2 | 80000 |
| Kabir | D2 | 60000 |
| Tara | D3 | 65000 |
(Ravi and Meera tie at 70000, so ROW_NUMBER keeps only one of them, chosen arbitrarily.)
20.3 Choosing the right ranking function
| Function | Behaviour on ties | Result for "top 2" in Engineering |
|---|---|---|
ROW_NUMBER | Exactly N rows per group, ties broken arbitrarily | Asha + one of Ravi/Meera |
RANK | Tied rows share a rank, so may return more than N | Asha, Ravi, Meera (both rank 2) |
DENSE_RANK | "Top N distinct values" | Asha, Ravi, Meera |
β Add a tiebreaker (e.g.,
ORDER BY salary DESC, emp_id) withROW_NUMBERto make results deterministic.
20.4 Other common variants
Highest in each group (Top-1): ROW_NUMBER() ... WHERE rn = 1 or, using a multi-column subquery (Section 3):
WHERE (dept_id, salary) IN (SELECT dept_id, MAX(salary) FROM Employee GROUP BY dept_id)
Alternative without window functions (correlated subquery): keep an employee if fewer than 2 colleagues in the same department earn more:
SELECT e.name, e.dept_id, e.salary
FROM Employee e
WHERE (SELECT COUNT(*) FROM Employee x
WHERE x.dept_id = e.dept_id AND x.salary > e.salary) < 2;
This works everywhere but is slower and acts like RANK for ties.
20.5 Why the CTE wrapper?
Window functions can't appear in WHERE (Section 12.4), so compute the rank in an inner query/CTE and filter in the outer one.
Part D β Reusable and Temporary Structures
21. Views and Materialized Views
21.1 What is a view?
A view is a saved SELECT query given a name, which you can query like a table. It stores no data of its own (only the query definition); each time you query it, the underlying query runs.
21.2 Creating and using views
CREATE VIEW high_earners AS
SELECT emp_id, name, dept_id, salary
FROM Employee
WHERE salary >= 70000;
SELECT * FROM high_earners WHERE dept_id = 'D1';
CREATE OR REPLACE VIEW high_earners AS ...; -- change definition (PostgreSQL, MySQL, Oracle)
ALTER VIEW high_earners AS ...; -- SQL Server (or CREATE OR ALTER)
DROP VIEW high_earners;
21.3 Why use views?
| Benefit | Explanation |
|---|---|
| Simplicity | Hide complex joins/logic behind a simple name |
| Security | Expose only certain columns/rows (e.g., hide salary) |
| Abstraction / logical data independence | Applications use the view even if tables change underneath |
| Consistency | One central definition of a business rule |
| Reusability | Many queries can share the same logic |
21.4 Updatable views
Simple views can often be used with INSERT/UPDATE/DELETE, which pass through to the base table. This usually requires that the view:
- is based on one table,
- has no
DISTINCT,GROUP BY, aggregates, window functions or set operations, - includes all
NOT NULLcolumns without defaults (for inserts).
WITH CHECK OPTION blocks changes that would make a row disappear from the view:
CREATE VIEW eng_staff AS
SELECT * FROM Employee WHERE dept_id = 'D1'
WITH CHECK OPTION;
-- UPDATE eng_staff SET dept_id = 'D2' ... β rejected (row would leave the view)
21.5 Limitations of regular views
- Performance is only as good as the underlying query (views don't speed things up by themselves).
- Stacked/nested views can become hard to understand and optimise.
- Complex views may not be updatable.
21.6 Materialized views
A materialized view stores the query result physically, like a table, and must be refreshed to pick up new data. It trades freshness for speed.
-- PostgreSQL
CREATE MATERIALIZED VIEW dept_summary AS
SELECT dept_id, COUNT(*) AS emp_count, AVG(salary) AS avg_salary
FROM Employee
GROUP BY dept_id;
REFRESH MATERIALIZED VIEW dept_summary;
REFRESH MATERIALIZED VIEW CONCURRENTLY dept_summary; -- needs a unique index; avoids blocking reads
Refresh options (vary by DBMS)
| DBMS | Materialized view support |
|---|---|
| PostgreSQL | MATERIALIZED VIEW + manual REFRESH |
| Oracle | MATERIALIZED VIEW with refresh modes: COMPLETE / FAST (incremental) and ON DEMAND / ON COMMIT |
| SQL Server | Indexed views (automatically maintained, with many restrictions) |
| MySQL / SQLite | No built-in materialized views (simulate with a summary table refreshed by a job) |
21.7 View vs Materialized view
| View | Materialized view | |
|---|---|---|
| Stores data? | β No (query only) | β Yes (result stored) |
| Data freshness | Always current | As of last refresh (can be stale) |
| Query speed | Same as the underlying query | Fast (precomputed) |
| Storage | None | Uses disk space |
| Maintenance | None | Needs refresh strategy |
| Best for | Simplification, security | Expensive aggregations, reports, dashboards |
22. Temporary Tables
22.1 What are they?
A temporary table is a table that exists only for a session (or transaction) and is automatically dropped (or emptied) afterwards. It's a scratch pad for intermediate results.
22.2 Syntax by DBMS
| DBMS | Create |
|---|---|
| MySQL / MariaDB | CREATE TEMPORARY TABLE t (...); |
| PostgreSQL | CREATE TEMP TABLE t (...); |
| SQLite | CREATE TEMP TABLE t (...); |
| SQL Server | CREATE TABLE #t (...); (local, session) Β· ##t (global, visible to all sessions) |
| Oracle | CREATE GLOBAL TEMPORARY TABLE t (...) ON COMMIT DELETE ROWS; (the definition is permanent, the data is private to each session/transaction) |
22.3 Examples
-- Create empty, then fill
CREATE TEMPORARY TABLE high_paid (emp_id INT, name VARCHAR(50), salary INT);
INSERT INTO high_paid
SELECT emp_id, name, salary FROM Employee WHERE salary >= 70000;
-- Create and fill in one step
CREATE TEMP TABLE dept_stats AS -- PostgreSQL / SQLite / MySQL(AS SELECT)
SELECT dept_id, AVG(salary) AS avg_sal FROM Employee GROUP BY dept_id;
SELECT INTO #dept_stats FROM ... -- SQL Server style: SELECT ... INTO #dept_stats FROM ...
22.4 Lifetime and visibility
- Session-scoped: visible only to the session that created it; two users can use the same name without clashing.
- Dropped automatically when the session ends (or you can
DROP TABLEearlier). - Some systems offer transaction scope: PostgreSQL
ON COMMIT DROPorON COMMIT DELETE ROWS; OracleON COMMIT DELETE ROWS/PRESERVE ROWS. - A temp table can shadow a permanent table with the same name (in MySQL/PostgreSQL), so avoid reusing names.
22.5 Why use them?
- Break complex logic into steps and reuse intermediate results several times.
- Hold large intermediate results and add indexes to speed later joins.
- Stage data during processing (like ETL work).
- Keep session-specific working data without affecting others.
22.6 Temp table vs CTE vs derived table
| Temp table | CTE / derived table | |
|---|---|---|
| Data stored? | β Yes | Usually not |
| Reuse across many statements | β Yes | β One statement only |
| Can add indexes/constraints | β | β |
| Setup cost | Higher (create + fill) | None |
| Best for | Big/repeated intermediate data | Readability within one query |
SQL Server also has table variables (
DECLARE @t TABLE (...)), which are lighter-weight but scoped to a batch and offer fewer optimizer statistics.
23. Pivoting and Unpivoting
23.1 Pivot: rows β columns
Turn row values into column headings, usually with aggregation.
Starting data (long format): Sales(region, month_no, amount).
Portable method: conditional aggregation (CASE + aggregate)
SELECT region,
SUM(CASE WHEN month_no = 1 THEN amount END) AS m1,
SUM(CASE WHEN month_no = 2 THEN amount END) AS m2,
SUM(CASE WHEN month_no = 3 THEN amount END) AS m3,
SUM(CASE WHEN month_no = 4 THEN amount END) AS m4
FROM Sales
GROUP BY region;
| region | m1 | m2 | m3 | m4 |
|---|---|---|---|---|
| East | 100 | 120 | 90 | 150 |
| West | 80 | 85 | 110 | 95 |
- Works in every database.
- Rows that don't match a
CASEreturnNULL, which the aggregate ignores.
23.2 Native PIVOT (SQL Server and Oracle)
-- SQL Server
SELECT region, [1] AS m1, [2] AS m2, [3] AS m3, [4] AS m4
FROM (SELECT region, month_no, amount FROM Sales) AS src
PIVOT (SUM(amount) FOR month_no IN ([1], [2], [3], [4])) AS p;
- PostgreSQL: the
crosstab()function from thetablefuncextension (or conditional aggregation;FILTER (WHERE ...)is a neat alternative toCASE). - MySQL: no
PIVOT; use conditional aggregation.
23.3 Limitation of pivoting
The column list must be known in advance. To pivot on values that change (e.g., all months found in the table), you need dynamic SQL (Section 28) to build the query.
23.4 Unpivot: columns β rows
Turn column headings into row values. Reverse of the pivot.
Starting data (wide): SalesWide(region, m1, m2, m3, m4).
Portable method: UNION ALL
SELECT region, 1 AS month_no, m1 AS amount FROM SalesWide
UNION ALL
SELECT region, 2, m2 FROM SalesWide
UNION ALL
SELECT region, 3, m3 FROM SalesWide
UNION ALL
SELECT region, 4, m4 FROM SalesWide;
Native options
-- SQL Server / Oracle
SELECT region, month_label, amount
FROM SalesWide
UNPIVOT (amount FOR month_label IN (m1, m2, m3, m4)) AS u;
-- PostgreSQL
SELECT region, v.month_no, v.amount
FROM SalesWide
CROSS JOIN LATERAL (VALUES (1, m1), (2, m2), (3, m3), (4, m4)) AS v(month_no, amount);
23.5 Notes
UNPIVOTdrops NULL values by default (SQL Server); theUNION ALLapproach keeps them unless filtered.- Pivoting is mainly for reports and presentation; storing data in the long (unpivoted) form is usually better for the database design.
23.6 Pivot vs Unpivot
| Pivot | Unpivot | |
|---|---|---|
| Direction | Rows β columns | Columns β rows |
| Typical tool | CASE + SUM, PIVOT | UNION ALL, UNPIVOT, LATERAL VALUES |
| Result | Wider, fewer rows | Longer, more rows |
| Use for | Reports, cross-tabs | Normalising wide data for analysis |
Part E β Programmable Database Objects
Procedures, functions, triggers and cursors are written in a procedural extension of SQL: PL/SQL (Oracle), T-SQL (SQL Server), PL/pgSQL (PostgreSQL), and MySQL's stored program language. The ideas are the same; the syntax differs.
24. Stored Procedures
24.1 What is a stored procedure?
A stored procedure is a named, precompiled block of SQL and procedural code stored in the database, which you run with a call. It can accept parameters, run many statements, use variables, conditions and loops, and even manage transactions.
24.2 Example (MySQL)
DELIMITER //
CREATE PROCEDURE give_raise (IN p_dept VARCHAR(5), IN p_pct DECIMAL(5,2))
BEGIN
UPDATE Employee
SET salary = salary * (1 + p_pct / 100)
WHERE dept_id = p_dept;
END //
DELIMITER ;
CALL give_raise('D1', 10); -- 10% raise for Engineering
(DELIMITER changes the statement terminator so the client doesn't stop at the first ; inside the body.)
24.3 Same idea in other systems
| DBMS | Create | Call |
|---|---|---|
| PostgreSQL (11+) | CREATE PROCEDURE give_raise(p_dept VARCHAR, p_pct NUMERIC) LANGUAGE plpgsql AS $$ BEGIN UPDATE ...; END; $$; | CALL give_raise('D1', 10); |
| SQL Server | CREATE PROCEDURE give_raise @dept VARCHAR(5), @pct DECIMAL(5,2) AS BEGIN UPDATE ...; END; | EXEC give_raise @dept = 'D1', @pct = 10; |
| Oracle | CREATE OR REPLACE PROCEDURE give_raise(p_dept IN VARCHAR2, p_pct IN NUMBER) IS BEGIN UPDATE ...; END; | EXEC give_raise('D1', 10); or CALL ... |
| SQLite | β No stored procedures | β |
24.4 Parameter modes
| Mode | Meaning |
|---|---|
IN | Input only (default) |
OUT | Value is returned to the caller |
INOUT | Passed in and returned modified |
-- MySQL: OUT parameter
CREATE PROCEDURE dept_count (IN p_dept VARCHAR(5), OUT p_count INT)
BEGIN
SELECT COUNT(*) INTO p_count FROM Employee WHERE dept_id = p_dept;
END;
CALL dept_count('D1', @c);
SELECT @c; -- 4
24.4b Typical contents
Variables (DECLARE), conditions (IF / ELSE, CASE), loops (WHILE, LOOP), error handling (TRY/CATCH, EXCEPTION, handlers), transactions (COMMIT, ROLLBACK), cursors, and dynamic SQL.
24.5 Advantages and disadvantages
| β Advantages | β Disadvantages |
|---|---|
| Reusability: write once, call from anywhere | Business logic inside the database, which is harder to version-control and test |
| Performance: precompiled/cached plans, fewer network round trips | Portability: syntax is vendor-specific |
Security: grant EXECUTE without table access; parameters reduce injection risk | Harder to debug than application code |
| Consistency: one place for critical logic | Can become a maintenance burden if overused |
| Atomic multi-step operations | Scaling logic is tied to the database server |
25. User-Defined Functions
25.1 What is a UDF?
A user-defined function (UDF) is a stored routine that takes parameters and returns a value (a single value or a table), and can be used inside SQL statements like built-in functions (UPPER(), ROUND()).
25.2 Types
| Type | Returns | Used like |
|---|---|---|
| Scalar function | One value | SELECT bonus(salary) FROM Employee; |
| Table-valued function (inline / set-returning) | A table (set of rows) | SELECT * FROM dept_employees('D1'); |
| Aggregate function (custom) | One value over a group (supported in some systems) | SELECT my_agg(col) ... |
25.3 Scalar function examples
-- MySQL
DELIMITER //
CREATE FUNCTION bonus(p_salary DECIMAL(10,2))
RETURNS DECIMAL(10,2)
DETERMINISTIC
BEGIN
RETURN p_salary * 0.10;
END //
DELIMITER ;
SELECT name, salary, bonus(salary) AS bonus FROM Employee;
-- PostgreSQL
CREATE FUNCTION bonus(p_salary NUMERIC) RETURNS NUMERIC
LANGUAGE sql IMMUTABLE
AS $$ SELECT p_salary * 0.10 $$;
-- SQL Server
CREATE FUNCTION dbo.bonus (@salary DECIMAL(10,2))
RETURNS DECIMAL(10,2)
AS BEGIN
RETURN @salary * 0.10;
END;
25.4 Table-valued function examples
-- SQL Server (inline table-valued function)
CREATE FUNCTION dbo.dept_employees (@dept VARCHAR(5))
RETURNS TABLE
AS RETURN (SELECT emp_id, name, salary FROM Employee WHERE dept_id = @dept);
SELECT * FROM dbo.dept_employees('D1');
-- PostgreSQL
CREATE FUNCTION dept_employees(p_dept VARCHAR)
RETURNS TABLE (emp_id INT, name VARCHAR, salary INT)
LANGUAGE sql AS $$
SELECT emp_id, name, salary FROM Employee WHERE dept_id = p_dept
$$;
SELECT * FROM dept_employees('D1');
(MySQL doesn't support table-valued functions.)
25.5 Function vs procedure
| Function | Stored procedure | |
|---|---|---|
| Returns | A value or table (required) | Optional (via OUT params / result sets) |
Use inside SELECT/WHERE | β Yes | β No (called with CALL/EXEC) |
| Modify data (DML) | Often restricted (especially in SQL Server) | β Allowed |
| Transaction control | Usually β | β Usually allowed |
| Typical use | Calculations, reusable expressions | Actions, multi-step operations |
25.6 Cautions
- A scalar UDF in a
SELECTorWHEREmay run once per row, which can be slow on big tables. - Functions in a
WHEREclause on an indexed column can block index use (non-sargable condition). - Declare determinism (
DETERMINISTIC/IMMUTABLE) correctly: the optimizer may rely on it. (MySQL may require this declaration when binary logging is on.)
26. Triggers
26.1 What is a trigger?
A trigger is a stored routine that the database runs automatically when a specified event (INSERT, UPDATE or DELETE) happens on a table. You never call it directly.
26.2 Trigger anatomy
| Part | Options |
|---|---|
| Event | INSERT, UPDATE, DELETE |
| Timing | BEFORE the change (validate/modify values), AFTER the change (audit, cascade), INSTEAD OF (replace the action, usually on views) |
| Level | Row-level (FOR EACH ROW: runs once per affected row) or statement-level (runs once per statement) |
| Condition | Optional WHEN filter |
| Body | The code to run |
26.3 Accessing old and new values
| DBMS | How |
|---|---|
| MySQL, PostgreSQL | OLD.column (before change) and NEW.column (after) |
| Oracle | :OLD.column, :NEW.column |
| SQL Server | Special tables inserted and deleted (statement-level, may contain several rows) |
INSERT has only NEW, DELETE has only OLD, UPDATE has both.
26.4 Example 1: audit trail (AFTER UPDATE)
CREATE TABLE salary_audit (
audit_id INT AUTO_INCREMENT PRIMARY KEY,
emp_id INT,
old_salary DECIMAL(10,2),
new_salary DECIMAL(10,2),
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- MySQL
DELIMITER //
CREATE TRIGGER trg_salary_audit
AFTER UPDATE ON Employee
FOR EACH ROW
BEGIN
IF OLD.salary <> NEW.salary THEN
INSERT INTO salary_audit (emp_id, old_salary, new_salary)
VALUES (OLD.emp_id, OLD.salary, NEW.salary);
END IF;
END //
DELIMITER ;
26.5 Example 2: validation / defaulting (BEFORE INSERT)
-- MySQL
CREATE TRIGGER trg_check_salary
BEFORE INSERT ON Employee
FOR EACH ROW
BEGIN
IF NEW.salary < 0 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Salary cannot be negative';
END IF;
END;
26.6 PostgreSQL style (function + trigger)
CREATE FUNCTION log_salary_change() RETURNS TRIGGER AS $$
BEGIN
IF OLD.salary <> NEW.salary THEN
INSERT INTO salary_audit (emp_id, old_salary, new_salary)
VALUES (OLD.emp_id, OLD.salary, NEW.salary);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_salary_audit
AFTER UPDATE ON Employee
FOR EACH ROW EXECUTE FUNCTION log_salary_change();
26.7 SQL Server style
CREATE TRIGGER trg_salary_audit ON Employee
AFTER UPDATE
AS
BEGIN
INSERT INTO salary_audit (emp_id, old_salary, new_salary)
SELECT d.emp_id, d.salary, i.salary
FROM deleted d JOIN inserted i ON i.emp_id = d.emp_id
WHERE d.salary <> i.salary;
END;
(SQL Server triggers are statement-level, so always write them to handle multiple rows.)
26.8 Common uses
- Auditing and change history
- Enforcing complex business rules that constraints can't express
- Maintaining derived/summary data (denormalized counters)
- Automatic timestamps (
updated_at) - Synchronising tables or logging
26.9 Cautions
- Hidden logic: behaviour happens "invisibly", surprising developers.
- Performance: row-level triggers on bulk operations run many times.
- Cascading triggers (one trigger fires another) and recursion can be hard to trace.
- Harder to debug and test, and can complicate bulk loads (often temporarily disabled).
- Prefer constraints (
CHECK,FOREIGN KEY) when they can do the job.
26.10 Managing triggers
DROP TRIGGER trg_salary_audit; -- MySQL (PostgreSQL: DROP TRIGGER name ON table)
ALTER TABLE Employee DISABLE TRIGGER trg_salary_audit; -- SQL Server / Oracle style
27. Cursors
27.1 What is a cursor?
A cursor lets you process the rows of a query result one at a time inside procedural code, like a pointer moving through the result set. SQL normally works on whole sets, so cursors are for the cases where you truly need row-by-row logic.
27.2 Lifecycle (explicit cursor)
| Step | Action |
|---|---|
| 1. DECLARE | Define the cursor and its SELECT |
| 2. OPEN | Execute the query and position before the first row |
| 3. FETCH | Retrieve the next row into variables (repeat in a loop) |
| 4. CLOSE | Release the result set |
| 5. DEALLOCATE | (SQL Server) Remove the cursor definition |
27.3 Example: SQL Server (T-SQL)
DECLARE @id INT, @name VARCHAR(50);
DECLARE emp_cur CURSOR FOR
SELECT emp_id, name FROM Employee WHERE dept_id = 'D1';
OPEN emp_cur;
FETCH NEXT FROM emp_cur INTO @id, @name;
WHILE @@FETCH_STATUS = 0 -- 0 means a row was fetched successfully
BEGIN
PRINT CONCAT('Processing ', @name);
FETCH NEXT FROM emp_cur INTO @id, @name;
END
CLOSE emp_cur;
DEALLOCATE emp_cur;
27.4 Example: MySQL (inside a stored program)
DECLARE done INT DEFAULT 0;
DECLARE v_name VARCHAR(50);
DECLARE emp_cur CURSOR FOR SELECT name FROM Employee WHERE dept_id = 'D1';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; -- runs when no more rows
OPEN emp_cur;
read_loop: LOOP
FETCH emp_cur INTO v_name;
IF done = 1 THEN LEAVE read_loop; END IF;
-- process v_name here
END LOOP;
CLOSE emp_cur;
27.5 Example: PostgreSQL / Oracle (loop form)
-- PL/pgSQL: the FOR loop opens, fetches and closes the cursor for you
FOR rec IN SELECT emp_id, name FROM Employee WHERE dept_id = 'D1' LOOP
RAISE NOTICE 'Processing %', rec.name;
END LOOP;
Oracle PL/SQL has a similar FOR rec IN (SELECT ...) LOOP ... END LOOP;, plus cursor attributes such as %FOUND, %NOTFOUND, %ROWCOUNT.
27.6 Implicit vs explicit cursors
- Implicit: the database creates one automatically for each DML statement or single-row
SELECT INTO(Oracle exposes it asSQL%ROWCOUNT). - Explicit: you declare and control it (as above).
27.7 Cursor types (vary by DBMS; clearest in SQL Server)
| Type | Behaviour |
|---|---|
| Forward-only | Moves only forward; fastest and simplest |
| Static | Works on a snapshot copy; doesn't see later changes |
| Dynamic | Reflects changes made by others while open |
| Keyset-driven | Fixed set of rows (keys), but sees updates to their values |
| Scrollable | Can move forward/backward (FETCH PRIOR, FETCH FIRST, FETCH ABSOLUTE n) |
27.8 Performance warning
- Cursors are slow compared with set-based SQL (one row at a time, extra overhead, held resources and locks).
- β
Prefer a single set-based statement (
UPDATE ... SET ... WHERE, window functions, joins). For example, the raise in Section 24 needs no cursor. - Use cursors only when each row needs complex, sequential, or external processing (calling a procedure per row, row-dependent logic).
27.9 Quick comparison
| Set-based SQL | Cursor | |
|---|---|---|
| Processing | Whole set at once | Row by row |
| Speed | β Fast | β Slow |
| Code | Short, declarative | Long, procedural |
| Resource use | Lower | Higher (memory, locks) |
| Use when | Almost always | No set-based way exists |
28. Dynamic SQL
28.1 What is it?
Dynamic SQL means building an SQL statement as a text string at runtime and then executing it. Ordinary ("static") SQL has its structure fixed when you write it; dynamic SQL lets the table name, column names, conditions or whole statement vary.
28.2 Why use it?
- Variable table or column names (identifiers can't be parameters in normal SQL)
- Optional search filters (add a
WHEREcondition only if the user supplied a value) - Dynamic pivots (column list depends on the data, Section 23.3)
- Generic utilities (loop over tables, run maintenance statements)
28.3 How to run it in different systems
| DBMS | Mechanism | Example |
|---|---|---|
| SQL Server | EXEC(@sql) or (better) sp_executesql | EXEC sp_executesql @sql, N'@d VARCHAR(5)', @d = 'D1'; |
| MySQL | PREPARE / EXECUTE / DEALLOCATE PREPARE | see below |
| PostgreSQL (PL/pgSQL) | EXECUTE with format() | EXECUTE format('SELECT ... FROM %I', tbl); |
| Oracle (PL/SQL) | EXECUTE IMMEDIATE | EXECUTE IMMEDIATE 'SELECT ... WHERE dept_id = :1' INTO v USING d; |
28.4 Examples
MySQL: prepared statement with a parameter
SET @sql = 'SELECT name, salary FROM Employee WHERE dept_id = ?';
PREPARE stmt FROM @sql;
SET @d = 'D1';
EXECUTE stmt USING @d;
DEALLOCATE PREPARE stmt;
SQL Server: parameterised dynamic SQL
DECLARE @sql NVARCHAR(MAX) =
N'SELECT name, salary FROM Employee WHERE dept_id = @d';
EXEC sp_executesql @sql, N'@d VARCHAR(5)', @d = 'D1';
SQL Server: dynamic table name (identifier must be quoted safely)
DECLARE @tbl SYSNAME = 'Employee';
DECLARE @sql NVARCHAR(MAX) = N'SELECT COUNT(*) FROM ' + QUOTENAME(@tbl);
EXEC sp_executesql @sql;
PostgreSQL: dynamic table name + parameter
EXECUTE format('SELECT COUNT(*) FROM %I WHERE dept_id = $1', tbl_name)
INTO cnt
USING dept;
Optional filters (pattern)
SET @sql = 'SELECT * FROM Employee WHERE 1 = 1';
IF @dept IS NOT NULL THEN SET @sql = CONCAT(@sql, ' AND dept_id = ?'); END IF;
28.5 π¨ SQL injection danger
If you concatenate user input straight into the SQL text, an attacker can change the meaning of your query.
-- β DANGEROUS
SET @sql = CONCAT('SELECT * FROM Employee WHERE name = ''', user_input, '''');
-- If user_input is: x' OR '1'='1 β returns every row (or worse)
How to defend
- Use bind parameters (
?,@d,$1,:1) for values, never concatenate values. - Safely quote identifiers (table/column names) with
QUOTENAME()(SQL Server),format('%I')/quote_ident()(PostgreSQL),DBMS_ASSERT(Oracle). - Whitelist allowed table/column names rather than trusting input.
- Run with least privilege (the account shouldn't have rights it doesn't need).
28.6 Pros and cons
| β Advantages | β Disadvantages |
|---|---|
| Very flexible; build queries at runtime | SQL injection risk if misused |
| Handles variable identifiers and optional filters | Harder to read, test and debug |
| Enables dynamic pivots and generic utilities | Plans may be re-compiled (less caching) unless parameterised |
| No compile-time syntax checking; errors appear at runtime | |
| Executes with the caller's/owner's permissions, so security needs care |
28.7 When to avoid it
If a normal parameterised query (or a CASE/COALESCE trick like WHERE (@d IS NULL OR dept_id = @d)) can solve the problem, use static SQL instead.
β‘ Quick Revision Cheat Sheet
Subqueries
- Single-row β
= <> > <; multiple-row βIN / ANY / ALL / EXISTS; scalar = exactly 1 row Γ 1 column (0 rows β NULL, >1 β error). - Multi-column:
(a, b) IN (SELECT a, b ...)(not in SQL Server). - Nested = subquery within subquery (inside-out). Correlated = refers to the outer row, re-evaluated per row.
- Placement: WHERE (filter), SELECT (scalar), FROM (derived table, needs alias).
- EXISTS = any row? (short-circuits, NULL-safe). NOT IN + NULL = no rows β use NOT EXISTS.
= ANYβ‘IN;<> ALLβ‘NOT IN;> ANYβ > MIN;> ALLβ > MAX; empty set: ANY false, ALL true.
CTEs
WITH name AS (...) SELECT ...: named, one-statement scope, reusable, readable.- Recursive CTE = anchor
UNION ALLrecursive member; needs a stopping condition (SQL Server default limit 100;RECURSIVEkeyword not used in SQL Server/Oracle).
Window functions β func() OVER (PARTITION BY β¦ ORDER BY β¦ frame)
- Keep all rows (unlike
GROUP BY); evaluated after WHERE/GROUP BY/HAVING β can't filter on them in WHERE (wrap in CTE). - ROW_NUMBER (unique) Β· RANK (ties share, gaps) Β· DENSE_RANK (ties share, no gaps) Β· NTILE(n) (buckets).
- LAG/LEAD (previous/next row) Β· FIRST_VALUE/LAST_VALUE (watch the default frame for LAST_VALUE).
- Window aggregates:
SUM() OVER (PARTITION BY β¦). - Frames:
ROWS(position) vsRANGE(value, includes ties). Default with ORDER BY =RANGE UNBOUNDED PRECEDING β CURRENT ROW. - Running total:
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW; 3-row moving avg:ROWS BETWEEN 2 PRECEDING AND CURRENT ROW. - Top-N per group:
ROW_NUMBER() OVER (PARTITION BY g ORDER BY x DESC)in a CTE, thenWHERE rn <= N.
Structures
- View = saved query, no data stored, always current. Materialized view = stored result, needs refresh, fast reads.
- Temp table = session-scoped scratch table (
#tin SQL Server,TEMP/TEMPORARYelsewhere, Oracle GTT). - Pivot = rows β columns (
CASE+ aggregate, orPIVOT); Unpivot = columns β rows (UNION ALL,UNPIVOT,LATERAL VALUES).
Programmable objects
- Stored procedure: called with
CALL/EXEC, can do DML, transactions,IN/OUT/INOUTparams. - UDF: returns a value/table, usable inside SQL; scalar UDFs may be slow per row.
- Trigger: automatic on INSERT/UPDATE/DELETE; BEFORE / AFTER / INSTEAD OF; row vs statement level;
OLD/NEW(orinserted/deleted). - Cursor: row-by-row processing (DECLARE β OPEN β FETCH β CLOSE); prefer set-based SQL.
- Dynamic SQL: SQL built as a string; use bind parameters and safe identifier quoting to prevent SQL injection.
π€ Commonly asked questions (from these topics)
- What is a subquery? Differentiate single-row, multiple-row, scalar and multi-column subqueries.
- What is a correlated subquery? How does it differ from a non-correlated one?
- Where can subqueries be used (SELECT, FROM, WHERE)? Why does a derived table need an alias?
EXISTSvsIN: which is better and when? Why isNOT INdangerous with NULLs?- Explain
ANY,SOMEandALLwith examples. - What is a CTE? How is it different from a subquery, view and temp table?
- What is a recursive CTE? Write one to traverse an employeeβmanager hierarchy.
- What is a window function, and how does it differ from
GROUP BY? - Differentiate
ROW_NUMBER,RANKandDENSE_RANK. - What do
LAG,LEAD,FIRST_VALUE,LAST_VALUEandNTILEdo? What is theLAST_VALUEdefault-frame pitfall? - Explain
PARTITION BYand window frames (ROWSvsRANGE). - How do you compute a running total and a moving average?
- How do you get the top N rows per group?
- View vs materialized view: differences, pros and cons.
- What are temporary tables, and when would you use one instead of a CTE?
- How do you pivot and unpivot data in SQL?
- Stored procedure vs function: differences and use cases.
- What are triggers? Explain BEFORE/AFTER/INSTEAD OF and row- vs statement-level.
- What is a cursor? Why should cursors be avoided when possible?
- What is dynamic SQL, and how do you protect it from SQL injection?
#Transactions and Concurrency Control#
A friendly, start-to-advanced guide to how a database keeps data correct when many users work at the same time, and when things go wrong. Read top to bottom the first time. Use the Quick Revision section before exams or interviews.
π Table of Contents
Part A β Transaction Basics
Part B β Schedules and Serializability 4. Transaction Schedules 5. Serial and Non-Serial Schedules 6. Serializability 7. Conflict Serializability 8. Precedence Graphs 9. View Serializability
Part C β Recoverability and Problems 10. Recoverable and Non-Recoverable Schedules 11. Cascadeless and Strict Schedules 12. Concurrency Problems
Part D β Concurrency Control Techniques 13. Lock-Based Protocols 14. Shared and Exclusive Locks 15. Two-Phase Locking (2PL) 16. Strict and Rigorous 2PL 17. Deadlocks: Prevention, Detection and Recovery 18. Timestamp-Based Protocols 19. Optimistic Concurrency Control 20. Multi-Version Concurrency Control (MVCC)
Part E β Isolation, Distribution and Commands 21. Isolation Levels 22. Distributed Transactions and Two-Phase Commit 23. Transaction Commands: COMMIT, ROLLBACK, SAVEPOINT
Part A β Transaction Basics
1. Introduction to Transactions
1.1 What is a transaction?
A transaction is a logical unit of work: one or more database operations that must be treated as a single, indivisible action. Either all of it happens, or none of it does.
Analogy: Transferring βΉ500 from Account A to Account B has two steps (subtract from A, add to B). If the system crashes after step 1, βΉ500 vanishes. A transaction guarantees the two steps succeed together or not at all.
1.2 Example: bank transfer
T1: read(A) -- read A's balance
A = A - 500
write(A) -- write new balance of A
read(B)
B = B + 500
write(B)
commit
1.3 Basic operations
| Operation | Meaning |
|---|---|
read(X) | Copy data item X from the database into the transaction's local memory |
write(X) | Write the local value of X back to the database |
begin | Start of the transaction |
commit | Make all changes permanent |
abort / rollback | Undo all changes made by the transaction |
1.4 Why do we need transactions?
- Failures: crashes, power loss, errors halfway through multi-step work.
- Concurrency: many users read and write the same data at once; without control, results become wrong.
- A transaction is the unit for recovery and concurrency control.
1.5 Transaction boundaries
A transaction begins with its first statement (or an explicit BEGIN) and ends with COMMIT (success) or ROLLBACK (failure/undo). See Section 23 for the commands.
2. Transaction States
A transaction passes through several states during its life.
ββββββββββββββββββββββ commit βββββββββββββ
begin βββΊ ACTIVE ββΊβ PARTIALLY COMMITTEDββββββββββββββΊβ COMMITTED ββββ
β βββββββββββ¬βββββββββββ βββββββββββββ β
β failure β failure βββΊ TERMINATED
βΌ βΌ β
ββββββββββββββββββββββ rollback done βββββββββββ β
β FAILED ββββββββββββββββββββΊβ ABORTED βββββββββ
ββββββββββββββββββββββ βββββββββββ
| State | Meaning |
|---|---|
| Active | The initial state; the transaction is executing its operations |
| Partially committed | The last statement has executed, but the changes may still only be in memory/buffers |
| Committed | Changes are safely and permanently stored; the transaction succeeded |
| Failed | Normal execution can't continue (error, crash, or a check failed) |
| Aborted | The transaction has been rolled back and the database restored to its state before it started |
| Terminated | The transaction has left the system (after commit or abort) |
2.1 What happens after an abort?
The system may:
- Restart the transaction (if the failure was due to a hardware/software problem, not a logic error), or
- Kill it (if the cause is an internal logical error or bad input).
2.2 Key point
A transaction is committed only after the changes are durably recorded (for example, the log is flushed to disk). That is why "partially committed" exists as a separate state.
3. ACID Properties
ACID is the set of guarantees a database gives every transaction.
| Letter | Property | One-line meaning |
|---|---|---|
| A | Atomicity | All or nothing |
| C | Consistency | Database moves from one valid state to another valid state |
| I | Isolation | Concurrent transactions don't interfere with each other |
| D | Durability | Committed changes survive crashes |
3.1 Atomicity
- Either every operation takes effect or none does.
- If a transaction fails midway, partial changes are undone.
- Handled by: the recovery manager, using the log (undo information).
- Bank example: if the debit succeeds but the credit fails, the debit is rolled back.
3.2 Consistency
- A transaction preserves all integrity rules (constraints, keys, business rules).
- If the database is consistent before the transaction, it must be consistent after it.
- Handled by: constraints enforced by the DBMS plus correct application logic.
- Bank example: total money (A + B) is the same before and after the transfer.
3.3 Isolation
- Each transaction should behave as if it were running alone, even though many run together.
- Intermediate (uncommitted) changes of one transaction are hidden from others.
- Handled by: the concurrency control manager (locks, timestamps, MVCC).
- The strictness of isolation is configurable through isolation levels (Section 21).
3.4 Durability
- After a
COMMIT, the changes persist even if the system crashes immediately afterwards. - Handled by: write-ahead logging (WAL), which writes log records to stable storage before the commit is acknowledged; after a crash, the log is used to redo committed work.
3.5 Which component protects which property?
| Property | Mainly protected by |
|---|---|
| Atomicity | Recovery manager (undo via log) |
| Consistency | Integrity constraints + application + the other three |
| Isolation | Concurrency control manager |
| Durability | Recovery manager (redo via log / stable storage) |
Part B β Schedules and Serializability
4. Transaction Schedules
4.1 What is a schedule?
A schedule is the chronological order in which the operations of several transactions are executed. The operations of each individual transaction must keep their own internal order.
4.2 Notation
| Symbol | Meaning |
|---|---|
r1(A) | Transaction T1 reads item A |
w1(A) | Transaction T1 writes item A |
c1 | T1 commits |
a1 | T1 aborts |
4.3 Example
Two transactions:
- T1:
r1(A) w1(A) r1(B) w1(B) - T2:
r2(A) w2(A)
One possible schedule:
S: r1(A) w1(A) r2(A) w2(A) r1(B) w1(B)
(T1's and T2's operations are interleaved, but each keeps its own order.)
4.4 Types of schedules (preview)
- By interleaving: serial vs non-serial (Section 5)
- By correctness: serializable (conflict / view) (Sections 6β9)
- By recovery behaviour: recoverable, cascadeless, strict (Sections 10β11)
5. Serial and Non-Serial Schedules
5.1 Serial schedule
Transactions run one after another, with no interleaving: T1 completes entirely, then T2, and so on.
Serial (T1 then T2):
T1: r(A) w(A) r(B) w(B)
T2: r(A) w(A)
- Always correct (consistent), since there is no interference.
- Poor performance: while one transaction waits (e.g., for disk I/O), the CPU sits idle.
- For n transactions there are n! possible serial schedules.
5.2 Non-serial schedule
Operations of different transactions are interleaved.
Non-serial:
T1: r(A) w(A) r(B) w(B)
T2: r(A) w(A)
- Improves throughput and response time (concurrency).
- May be incorrect unless it's equivalent to some serial schedule.
5.3 Comparison
| Serial | Non-serial | |
|---|---|---|
| Interleaving | None | Yes |
| Correctness | Always correct | Only if serializable |
| Concurrency / performance | Low | High |
| Number of schedules | n! | Very many |
π― Goal of concurrency control: allow non-serial schedules, but only those that are equivalent to a serial schedule.
6. Serializability
6.1 Definition
A non-serial schedule is serializable if its effect is equivalent to that of some serial schedule of the same transactions.
6.2 Why it matters
Serializability is the standard definition of correctness for concurrent execution: if a schedule is serializable, it gives the same result as running the transactions one at a time, so isolation is preserved.
6.3 Two flavours
| Type | Based on | Strength |
|---|---|---|
| Conflict serializability | Order of conflicting operations | Stricter; easy to test (graph) |
| View serializability | What each transaction reads and the final writes | More general; hard to test |
Serial schedules β Conflict-serializable β View-serializable β All schedules
7. Conflict Serializability
7.1 Conflicting operations
Two operations conflict if all three hold:
- They belong to different transactions,
- They access the same data item, and
- At least one is a write.
| Pair (on the same item) | Conflict? |
|---|---|
| Read β Read | β No |
| Read β Write (R-W) | β Yes |
| Write β Read (W-R) | β Yes |
| Write β Write (W-W) | β Yes |
The order of conflicting operations changes the result; the order of non-conflicting operations doesn't matter.
7.2 Conflict equivalence
Two schedules are conflict equivalent if they involve the same transactions and operations, and every pair of conflicting operations is ordered the same way in both.
7.3 Conflict serializable
A schedule is conflict serializable if it is conflict equivalent to some serial schedule. Intuitively: you can turn it into a serial schedule by swapping adjacent non-conflicting operations.
7.4 Example
S: r1(A) w1(A) r2(A) w2(A) r1(B) w1(B) r2(B) w2(B)
- Conflicts on A:
w1(A) β r2(A),w1(A) β w2(A),r1(A) β w2(A)(all T1 before T2) - Conflicts on B:
r1(B)/w1(B)beforer2(B)/w2(B)(T1 before T2)
All conflicts say "T1 before T2", so S is conflict equivalent to the serial schedule T1 β T2 β .
7.5 A non-serializable example
S: r1(X) r2(X) w1(X) w2(X)
r2(X) β w1(X)means T2 before T1r1(X) β w2(X)means T1 before T2
These demands contradict each other, so S is not conflict serializable β. (It is also the classic lost update.)
7.6 How to test it
Build a precedence graph (next section): the schedule is conflict serializable if and only if the graph has no cycle.
8. Precedence Graphs
Also called the serialization graph. It's the standard test for conflict serializability.
8.1 How to build it
- Make one node per transaction.
- For each pair of conflicting operations where Ti's operation comes before Tj's, draw a directed edge Ti β Tj.
- Check for cycles.
8.2 The rule
- No cycle β conflict serializable. A topological order of the graph gives an equivalent serial order.
- Cycle β not conflict serializable.
8.3 Example 1 β acyclic
S: r1(A) w1(A) r2(A) w2(A) r1(B) w1(B) r2(B) w2(B)
Edges: only T1 β T2.
(T1) βββββββΊ (T2)
No cycle β β equivalent serial schedule: T1, T2.
8.4 Example 2 β with a cycle
S: r1(X) r2(X) w1(X) w2(X)
Edges: r1(X) β w2(X) gives T1 β T2; r2(X) β w1(X) gives T2 β T1.
(T1) ββββββββΊ (T2) β cycle β
β not conflict serializable.
8.5 Example 3 β three transactions
S: r1(A) w2(A) r3(A) w1(A)
r1(A) β w2(A): T1 β T2w2(A) β r3(A): T2 β T3w2(A) β w1(A): T2 β T1r3(A) β w1(A): T3 β T1
Edges include T1 β T2 and T2 β T1 β cycle β.
8.6 Finding the equivalent serial order
If the graph is acyclic, do a topological sort. Example: edges T1 β T2, T1 β T3, T3 β T2 give the serial order T1, T3, T2.
8.7 Remember
- Precedence graphs test conflict serializability only.
- A schedule with a cycle might still be view serializable (next section).
9. View Serializability
9.1 Idea
Conflict serializability is sufficient but not necessary for correctness. View serializability accepts more schedules by looking at what transactions actually read and what ends up in the database.
9.2 View equivalence
Two schedules S and Sβ² are view equivalent if, for every data item Q:
- Initial read: if Ti reads the initial value of Q in S, it does so in Sβ² too.
- Read-from (updated read): if Ti reads a value of Q written by Tj in S, it reads the value written by the same Tj in Sβ².
- Final write: the transaction that performs the last write of Q in S also does so in Sβ².
A schedule is view serializable if it is view equivalent to some serial schedule.
9.3 Example: view serializable but not conflict serializable
S: r1(A) w2(A) w1(A) w3(A)
Precedence graph: r1(A) β w2(A): T1 β T2; w2(A) β w1(A): T2 β T1 β cycle, so S is not conflict serializable.
View check against the serial schedule T1, T2, T3: r1(A) w1(A) w2(A) w3(A)
- T1 reads the initial value of A in both β
- No transaction reads another's write β
- The final write is by T3 in both β
β View equivalent to a serial schedule, so S is view serializable β .
9.4 The role of blind writes
A blind write is a write without a preceding read (like w2(A) and w3(A) above). Schedules that are view serializable but not conflict serializable always contain blind writes.
9.5 Relationship and cost
- Every conflict-serializable schedule is view serializable, but not vice-versa.
- Testing view serializability is NP-complete, so real systems don't use it; they enforce the simpler conflict serializability (or stronger) via protocols such as 2PL.
9.6 Conflict vs View
| Conflict serializability | View serializability | |
|---|---|---|
| Based on | Order of conflicting operations | Reads-from and final writes |
| Test | Precedence graph (polynomial) | NP-complete |
| Accepts | Fewer schedules | More schedules |
| Used in practice | β Yes | β Mostly theoretical |
| Blind writes needed to differ | β | Yes |
Part C β Recoverability and Problems
10. Recoverable and Non-Recoverable Schedules
Serializability talks about correct results; recoverability talks about what happens when a transaction fails.
10.1 Reading from another transaction
Ti reads from Tj if Ti reads a value that Tj wrote and Tj has not yet aborted.
10.2 Recoverable schedule
A schedule is recoverable if, whenever Ti reads from Tj, Tj commits before Ti commits.
10.3 β Recoverable example
T1: w1(A) c1
T2: r2(A) c2
w1(A) r2(A) c1 c2 β T2 reads from T1, and T1 commits first β
.
10.4 β Non-recoverable example
w1(A) r2(A) c2 a1 (T2 commits, then T1 aborts)
T2 has read T1's dirty value and already committed, but T1 then aborts. T2's result is based on data that never officially existed, and since T2 is committed, it can't be undone (violates Durability). β
10.5 Why it matters
Non-recoverable schedules can leave the database permanently inconsistent. DBMSs must allow only recoverable schedules.
11. Cascadeless and Strict Schedules
Recoverable schedules can still be inefficient or messy to undo. Two stricter classes fix this.
11.1 Cascading rollback (the problem)
If T1 aborts, every transaction that read T1's uncommitted data must also abort, and then those that read from them, and so on.
T1: w1(A)
T2: r2(A) w2(B)
T3: r3(B)
T1 aborts β T2 must abort β T3 must abort (cascading rollback)
This wastes work and is slow.
11.2 Cascadeless schedule (ACA: Avoids Cascading Aborts)
A schedule is cascadeless if every transaction reads only data written by already-committed transactions.
- Ti may read X only after the writer of X has committed.
- Prevents dirty reads, so no cascading rollbacks.
- Every cascadeless schedule is recoverable.
11.3 Strict schedule
A schedule is strict if a transaction can neither read nor overwrite a data item written by another transaction until that transaction commits or aborts.
- Extends cascadeless by also blocking dirty writes (write-write on uncommitted data).
- Makes rollback simple: restore the item's before-image.
- Every strict schedule is cascadeless.
11.4 Hierarchy
βββββββββββββββββββββββββββββββββββββββββββββ
β All schedules β
β βββββββββββββββββββββββββββββββββββββββ β
β β Recoverable β β
β β βββββββββββββββββββββββββββββββββ β β
β β β Cascadeless (ACA) β β β
β β β βββββββββββββββββββββββββββ β β β
β β β β Strict β β β β
β β β β β Serial schedules β β β β
β β β βββββββββββββββββββββββββββ β β β
β β βββββββββββββββββββββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββ
Serial β Strict β Cascadeless β Recoverable
11.5 Comparison
| Property | Recoverable | Cascadeless | Strict |
|---|---|---|---|
| Reads from uncommitted? | Allowed (but commit order enforced) | β Not allowed | β Not allowed |
| Overwrites uncommitted? | Allowed | Allowed | β Not allowed |
| Cascading rollback? | Possible | β Avoided | β Avoided |
| Rollback complexity | Complex | Moderate | Simple (restore old value) |
11.6 Example
w1(A) r2(A) c1 c2 β Recoverable, NOT cascadeless (T2 read uncommitted A)
w1(A) c1 r2(A) c2 β Cascadeless (and strict)
w1(A) w2(A) c1 c2 β Cascadeless, NOT strict (T2 overwrote uncommitted A)
11.7 Recoverability and serializability are independent
A schedule can be serializable but not recoverable, or recoverable but not serializable. A good DBMS guarantees both.
12. Concurrency Problems: Lost Update, Dirty Read, Non-Repeatable Read, Phantom Read
When transactions run together with no control, several anomalies occur.
12.1 Lost Update (WriteβWrite conflict)
Two transactions read the same value, both modify it, and one overwrites the other's change, which is lost.
| Time | T1 | T2 | A (initially 100) |
|---|---|---|---|
| 1 | read(A) β 100 | 100 | |
| 2 | read(A) β 100 | 100 | |
| 3 | A = A + 50 | ||
| 4 | A = A β 30 | ||
| 5 | write(A) β 150 | 150 | |
| 6 | write(A) β 70 | 70 β |
Correct result should be 120; T1's update was lost.
12.2 Dirty Read (WriteβRead conflict)
A transaction reads data that another transaction wrote but hasn't committed. If the writer later aborts, the reader has used data that never existed.
| Time | T1 | T2 |
|---|---|---|
| 1 | write(A) = 500 (uncommitted) | |
| 2 | read(A) β 500 (dirty!) | |
| 3 | rollback (A back to 100) | |
| 4 | uses 500 β |
12.3 Non-Repeatable Read (ReadβWrite conflict)
A transaction reads the same row twice and gets different values, because another transaction modified and committed between the reads.
| Time | T1 | T2 |
|---|---|---|
| 1 | read(A) β 100 | |
| 2 | write(A) = 200, commit | |
| 3 | read(A) β 200 β (changed) |
12.4 Phantom Read
A transaction runs the same query twice (e.g., a range condition) and the set of rows differs, because another transaction inserted or deleted matching rows and committed.
| Time | T1 | T2 |
|---|---|---|
| 1 | SELECT * FROM Student WHERE marks > 80 β 2 rows | |
| 2 | INSERT a student with marks 95, commit | |
| 3 | same SELECT β 3 rows β (a "phantom" appeared) |
12.5 Non-repeatable vs phantom
| Non-repeatable read | Phantom read | |
|---|---|---|
| What changes | Value of an existing row | The set of rows matching a condition |
| Cause | UPDATE (or DELETE) by another transaction | INSERT (or DELETE) by another transaction |
| Prevented by | Locking/snapshotting the rows read | Range/predicate locks, snapshots, or Serializable |
12.6 Summary
| Problem | Conflict type | Caused by |
|---|---|---|
| Lost update | WβW | Overwriting a value based on a stale read |
| Dirty read | WβR | Reading uncommitted data |
| Non-repeatable read | RβW | Row changed between two reads |
| Phantom read | RβW (predicate) | Rows added/removed between two queries |
Part D β Concurrency Control Techniques
Concurrency control ensures that concurrent transactions produce correct, serializable (and recoverable) results. Main approaches: pessimistic (locking, timestamps) and optimistic (validate at the end), plus multi-version techniques.
13. Lock-Based Protocols
13.1 Idea
Before a transaction accesses a data item, it must obtain a lock on it. The lock tells others "I'm using this", so conflicting access is blocked or delayed.
Analogy: library books. If someone has a book checked out, you must wait.
13.2 Components
- Lock manager: a DBMS component that grants, delays and releases locks.
- Lock table: records which transaction holds which lock on which item and who is waiting.
- Lock granularity: a lock can be on a row, page, table or database. Finer locks give more concurrency but more overhead; coarser locks are cheaper but block more.
13.3 Basic flow
lock(X) β read/write X β unlock(X)
- If the lock can't be granted (conflict), the transaction waits.
13.4 Locking alone is not enough
Simply locking an item just before use and unlocking right after does not guarantee serializability. The timing rules (Two-Phase Locking) are what guarantee it.
13.5 Two issues locks can introduce
- Deadlock: transactions wait on each other forever (Section 17).
- Starvation: a transaction waits indefinitely because others keep getting the lock.
14. Shared and Exclusive Locks
14.1 The two lock modes
| Lock | Symbol | Purpose | Allows |
|---|---|---|---|
| Shared lock | S / lock-S(X) | For reading X | Many transactions may hold S on the same item |
| Exclusive lock | X / lock-X(X) | For writing X (and reading) | Only one transaction may hold it; no others |
14.2 Compatibility matrix
| Requested β \ Held β | S | X |
|---|---|---|
| S | β Granted | β Wait |
| X | β Wait | β Wait |
- Read + Read can happen together; anything involving a write is exclusive.
14.3 Example
T1: lock-S(A) read(A) unlock(A)
T2: lock-S(A) read(A) unlock(A) β runs in parallel with T1
T3: lock-X(A) write(A) unlock(A) β must wait until T1 and T2 release A
14.4 Lock conversion
| Operation | Meaning | Rule |
|---|---|---|
| Upgrade | S β X | Allowed only if no other transaction holds a lock on the item (otherwise wait) |
| Downgrade | X β S | Allowed, used to release write power early |
14.5 SQL examples (dialect-dependent)
SELECT * FROM Account WHERE acc_no = 'A' FOR UPDATE; -- takes an exclusive row lock
SELECT * FROM Account WHERE acc_no = 'A' FOR SHARE; -- shared lock (PostgreSQL; MySQL 8: FOR SHARE)
(SQL Server uses table hints such as WITH (UPDLOCK).)
15. Two-Phase Locking (2PL)
15.1 The rule
Every transaction must perform its locking in two phases:
- Growing phase: the transaction may acquire locks, but may not release any.
- Shrinking phase: the transaction may release locks, but may not acquire any new ones.
Locks held
β β±β²
β β± β²
β β± β²
β β± β²
βββββββββββββββββββββΊ time
growing β shrinking
lock point
- The lock point is the moment the transaction holds all its locks (the end of the growing phase).
- Once a transaction releases its first lock, it enters the shrinking phase for good.
15.2 Example
β Follows 2PL
lock-S(A) read(A) lock-X(B) write(B) unlock(A) unlock(B)
β Violates 2PL (acquires a lock after releasing one)
lock-S(A) read(A) unlock(A) lock-X(B) write(B) unlock(B)
15.3 What 2PL guarantees
- β Conflict serializability: every schedule allowed by 2PL is conflict serializable. Transactions are effectively ordered by their lock points.
15.4 What 2PL does NOT guarantee
- β Freedom from deadlock
- β Freedom from cascading rollbacks (a transaction may release a write lock before commit, letting others read uncommitted data)
- β Recoverability by itself (the stricter variants fix this)
- Not every serializable schedule can be produced by 2PL (2PL is sufficient, not necessary).
15.5 Example of a cascading problem under basic 2PL
T1: lock-X(A) write(A) unlock(A) ... (enters shrinking, not yet committed)
T2: lock-S(A) read(A) β reads T1's uncommitted value
T1: abort β T2 must also abort (cascading rollback)
This is why practical systems use strict or rigorous 2PL.
15.6 Variants
| Variant | Description |
|---|---|
| Basic 2PL | Growing then shrinking; releases may occur before commit |
| Conservative (static) 2PL | Acquires all locks before starting; deadlock-free, but needs to know all items in advance and reduces concurrency |
| Strict 2PL | Holds exclusive locks until commit/abort (Section 16) |
| Rigorous 2PL | Holds all locks until commit/abort (Section 16) |
16. Strict and Rigorous 2PL
16.1 Strict 2PL
A transaction holds all its exclusive (write) locks until it commits or aborts. Shared locks may be released earlier.
- Guarantees strict schedules (hence cascadeless and recoverable) β
- Guarantees conflict serializability β
- No other transaction can read or overwrite uncommitted data.
- Most widely used form of locking in practice.
16.2 Rigorous 2PL
A transaction holds all its locks (shared and exclusive) until it commits or aborts.
- Stricter than strict 2PL, and simpler to implement.
- Transactions can be serialized in the order in which they commit.
- Guarantees conflict serializability, strictness, and recoverability β
- Slightly lower concurrency (shared locks are held longer).
16.3 Comparison
| Basic 2PL | Conservative 2PL | Strict 2PL | Rigorous 2PL | |
|---|---|---|---|---|
| Locks acquired | Gradually | All up front | Gradually | Gradually |
| Exclusive locks released | Anytime in shrinking phase | Anytime after | At commit/abort | At commit/abort |
| Shared locks released | Anytime in shrinking phase | Anytime after | Anytime in shrinking phase | At commit/abort |
| Conflict serializable | β | β | β | β |
| Cascadeless / strict | β | β | β | β |
| Deadlock-free | β | β | β | β |
| Concurrency | Highest | Lowest | High | Moderate |
π‘ In most commercial databases, "2PL" in practice means strict (or rigorous) 2PL: locks are released automatically at
COMMITorROLLBACK.
17. Deadlocks: Prevention, Detection and Recovery
17.1 What is a deadlock?
A deadlock occurs when two or more transactions wait for each other to release locks, and none can proceed.
T1: lock-X(A) ...wants B T1 holds A, waits for B
T2: lock-X(B) ...wants A T2 holds B, waits for A
β both wait forever
17.2 Four necessary conditions (all must hold)
- Mutual exclusion: a resource can be held by only one transaction at a time.
- Hold and wait: a transaction holds some locks while waiting for others.
- No preemption: locks can't be forcibly taken away.
- Circular wait: a cycle of transactions each waiting for the next.
Break any one and deadlock can't occur.
17.3 Deadlock Prevention
Design the system so deadlock can never happen.
(a) Conservative 2PL / pre-declaration
- Lock all required items before starting; if any is unavailable, wait without holding anything.
- Breaks hold and wait. Downsides: need to know items in advance; low concurrency.
(b) Ordering of resources
- Impose a global order on data items; every transaction must request locks in that order.
- Breaks circular wait.
(c) Timestamp-based schemes (each transaction gets a timestamp at start; older = smaller timestamp)
| Scheme | If Ti requests a lock held by Tj⦠| Type |
|---|---|---|
| WaitβDie | Ti older than Tj β Ti waits; Ti younger β Ti dies (aborts, restarts later) | Non-preemptive |
| WoundβWait | Ti older than Tj β Ti wounds Tj (Tj aborts); Ti younger β Ti waits | Preemptive |
- In both, an aborted transaction keeps its original timestamp when restarted, so it eventually becomes the oldest and can't starve.
- Both are deadlock-free (waits only go in one age direction, so no cycle).
- Drawback: unnecessary aborts can occur even when no deadlock would have happened.
(d) Lock timeouts
- A transaction waiting longer than a time limit is aborted. Simple, but it may abort innocent, merely slow transactions. (Often considered a lightweight detection technique.)
17.4 Deadlock Detection
Allow deadlocks to happen, then find and fix them.
Wait-for graph
- Nodes: transactions.
- Edge Ti β Tj: Ti is waiting for a lock held by Tj.
- A cycle in the graph β deadlock.
(T1) ββwaits forβββΊ (T2)
β² β
βββββwaits forβββββββ β cycle = deadlock
- The DBMS checks the graph periodically (or whenever a wait occurs).
- Trade-off: checking too often wastes CPU; too rarely lets deadlocked transactions sit idle.
17.5 Deadlock Recovery
Once a deadlock is detected, break the cycle by aborting one or more transactions (the victim).
1. Choosing the victim (try to minimise cost), for example:
- The youngest transaction, or the one that has done the least work
- The one that holds fewest locks or has fewest items left
- The one involved in the fewest rollbacks previously
2. Rollback
- Total rollback: abort the victim entirely and restart it.
- Partial rollback: roll back only far enough to release the needed lock (needs more bookkeeping).
3. Avoiding starvation
- Don't always pick the same victim. Include the number of times already rolled back in the cost so every transaction eventually completes.
17.6 Prevention vs Detection
| Prevention | Detection and recovery | |
|---|---|---|
| Approach | Stop deadlocks from forming | Let them form, then resolve |
| Overhead | Unnecessary aborts / restricted concurrency | Graph maintenance + occasional aborts |
| Best when | Deadlocks are frequent | Deadlocks are rare |
| Used by most DBMSs | Rarely as the main method | β Commonly (plus timeouts) |
18. Timestamp-Based Protocols
18.1 Idea
Instead of locks, give each transaction a unique timestamp when it starts and force the execution to be equivalent to the serial order of timestamps (older first).
- TS(T): the timestamp of transaction T (a smaller value means it started earlier).
- For each data item X, the DBMS stores:
- R_TS(X): largest timestamp of any transaction that read X successfully.
- W_TS(X): largest timestamp of any transaction that wrote X successfully.
18.2 Basic Timestamp Ordering (TO) rules
When T wants to read(X):
- If TS(T) < W_TS(X) β T would read a value written by a younger transaction β abort T and restart with a new timestamp.
- Otherwise β allow the read; set R_TS(X) = max(R_TS(X), TS(T)).
When T wants to write(X):
- If TS(T) < R_TS(X) β a younger transaction has already read the old value β abort T.
- If TS(T) < W_TS(X) β a younger transaction has already written X β abort T (the write is obsolete).
- Otherwise β allow the write; set W_TS(X) = TS(T).
18.3 Summary table
| Operation | Condition | Action |
|---|---|---|
read(X) | TS(T) < W_TS(X) | Abort T |
read(X) | TS(T) β₯ W_TS(X) | Read; R_TS(X) = max(R_TS(X), TS(T)) |
write(X) | TS(T) < R_TS(X) | Abort T |
write(X) | TS(T) < W_TS(X) | Abort T |
write(X) | otherwise | Write; W_TS(X) = TS(T) |
18.4 Thomas' Write Rule (an optimisation)
If TS(T) < W_TS(X) (but TS(T) β₯ R_TS(X)), instead of aborting, simply ignore the write. Reason: a newer value has already overwritten it, so T's write would have been immediately overwritten anyway.
- Allows some schedules that basic TO rejects (it may produce schedules that are view serializable but not conflict serializable).
18.5 Example
Let TS(T1) = 10, TS(T2) = 20, initially R_TS(A) = W_TS(A) = 0.
| Step | Operation | Check | Result |
|---|---|---|---|
| 1 | T2: read(A) | 20 β₯ W_TS(A)=0 β | R_TS(A) = 20 |
| 2 | T1: write(A) | TS(T1)=10 < R_TS(A)=20 β | T1 aborted (T2 already read A "in the future") |
18.6 Properties
| β Advantages | β Disadvantages |
|---|---|
| No locks, so no deadlocks | Transactions may be aborted and restarted often |
| Guarantees conflict serializability (order = timestamp order) | Starvation is possible for long transactions |
| Simple rules | Basic TO may allow cascading rollbacks and non-recoverable schedules |
| Overhead of storing timestamps per item |
A strict timestamp ordering variant delays reads/writes of an item until the transaction that last wrote it commits, giving recoverable, cascadeless schedules.
19. Optimistic Concurrency Control
Also called validation-based concurrency control.
19.1 Idea
Assume conflicts are rare. Let transactions run without locking, and check for conflicts only at the end, just before committing. If a conflict is found, the transaction is aborted and restarted.
Analogy: Several people edit their own copies of a document, and when saving, the system checks whether someone else changed the same parts in the meantime.
19.2 Three phases of a transaction
| Phase | What happens |
|---|---|
| 1. Read phase | Reads data from the database, but all writes go to private local copies (no changes are visible to others yet) |
| 2. Validation phase | The system checks whether committing would violate serializability with other transactions |
| 3. Write phase | If validation passes, local changes are written to the database; otherwise the transaction is aborted and restarted |
19.3 Timestamps used
Each transaction Ti records: Start(Ti), Validation(Ti), Finish(Ti). Serialization order = order of validation timestamps.
19.4 Validation test
For every transaction Tj that validated before Ti (TS(Tj) < TS(Ti)), at least one must hold:
- Tj finished entirely before Ti started (no overlap), or
- Tj's write set doesn't intersect Ti's read set, and Tj finished its write phase before Ti started its write phase, or
- Tj's write set doesn't intersect Ti's read set or write set, and Tj finished its read phase before Ti finished its read phase.
If none holds, Ti is aborted.
19.5 Properties
| β Advantages | β Disadvantages |
|---|---|
| No locking overhead, no deadlocks | Under high contention, many aborts and wasted work |
| Great for read-heavy workloads with few conflicts | Long transactions may be repeatedly aborted (starvation) |
| Readers never block | Conflict is detected late (after work is done) |
19.6 Optimistic locking in applications
The same idea is often used at the application level with a version column:
-- Read the row and remember its version (say version = 7)
SELECT balance, version FROM Account WHERE acc_no = 'A';
-- Update only if nobody changed it in the meantime
UPDATE Account
SET balance = 450, version = version + 1
WHERE acc_no = 'A' AND version = 7;
-- If 0 rows were updated β someone else changed it β retry or report a conflict
19.7 Pessimistic vs Optimistic
| Pessimistic (locking) | Optimistic | |
|---|---|---|
| Assumption | Conflicts are likely | Conflicts are rare |
| When checked | Before access (locks) | At commit (validation) |
| Waiting | Transactions wait for locks | Transactions rarely wait |
| On conflict | Wait (or deadlock) | Abort and retry |
| Best for | Write-heavy, high contention | Read-heavy, low contention |
20. Multi-Version Concurrency Control (MVCC)
20.1 Idea
Instead of overwriting data, the database keeps multiple versions of each item. Each transaction sees a consistent snapshot of the data, as of a particular point in time.
Key slogan: Readers don't block writers, and writers don't block readers.
20.2 How it works (conceptually)
- Every
write(X)creates a new version of X, tagged with the writer's timestamp/transaction ID. - Every
read(X)is directed to the appropriate older/newer version that was committed as of the reader's snapshot. - Old versions that no transaction can see anymore are cleaned up (garbage collection).
Item X: v1 (written by T5) β v2 (written by T9) β v3 (written by T14)
Reader with snapshot at T10 sees v2. Reader at T15 sees v3.
20.3 MVCC with timestamp ordering (rules)
Each version Xk has W_TS(Xk) and R_TS(Xk). For transaction T with timestamp TS(T):
- Read(X): return the version with the largest W_TS β€ TS(T). Update that version's R_TS if needed. Reads never abort (an old enough version always exists).
- Write(X): find the same version Xk.
- If TS(T) < R_TS(Xk) β a younger transaction already read it β abort T.
- Otherwise create a new version with W_TS = TS(T).
20.4 Snapshot Isolation (SI)
A widely implemented MVCC flavour:
- Each transaction reads from a snapshot taken at its start (or per statement in some systems).
- On commit, "first committer wins": if two concurrent transactions wrote the same item, only the first to commit succeeds; the other is aborted.
- Prevents dirty reads, non-repeatable reads, and lost updates.
- Not fully serializable: it allows an anomaly called write skew (two transactions each read overlapping data and write different items, producing a result that no serial order would). Example: two doctors on call each check "at least one other doctor is on call" and both go off call.
20.5 Real-world implementations
| DBMS | How it keeps versions (simplified) |
|---|---|
| PostgreSQL | Each row version is stored in the table with transaction IDs showing when it was created and deleted; VACUUM reclaims dead versions |
| MySQL (InnoDB) | Latest row in the table, older versions rebuilt from undo logs |
| Oracle | Old versions rebuilt from undo (rollback) data |
| SQL Server | Optional row versioning in tempdb (snapshot isolation / read-committed snapshot) |
20.6 Pros and cons
| β Advantages | β Disadvantages |
|---|---|
| Readers and writers don't block each other, giving high concurrency | Extra storage for old versions |
| Consistent, repeatable snapshots for long read queries | Garbage collection / cleanup overhead |
| Fewer lock conflicts and deadlocks among readers | Writeβwrite conflicts still need handling (locks or abort) |
| Snapshot isolation alone can allow write skew |
Part E β Isolation, Distribution and Commands
21. Isolation Levels
21.1 Why levels?
Full serializability is the safest but can reduce performance. SQL lets you trade some isolation for speed by choosing an isolation level.
21.2 The four standard levels (weakest β strongest)
| Level | Idea |
|---|---|
| READ UNCOMMITTED | Can see other transactions' uncommitted changes |
| READ COMMITTED | Sees only committed data (each statement sees the latest committed state) |
| REPEATABLE READ | Rows you've read won't change if read again in the same transaction |
| SERIALIZABLE | Behaves as if transactions ran one at a time |
21.3 Which anomalies each level allows (SQL standard)
| Isolation level | Dirty read | Non-repeatable read | Phantom read |
|---|---|---|---|
| Read Uncommitted | β οΈ Possible | β οΈ Possible | β οΈ Possible |
| Read Committed | β Prevented | β οΈ Possible | β οΈ Possible |
| Repeatable Read | β Prevented | β Prevented | β οΈ Possible |
| Serializable | β Prevented | β Prevented | β Prevented |
Lost updates and dirty writes (overwriting uncommitted data) must be prevented at all levels in a proper implementation.
21.4 How locking implements the levels (classic lock-based view)
| Level | Write locks | Read locks |
|---|---|---|
| Read Uncommitted | Held until commit | None |
| Read Committed | Held until commit | Released immediately after the read |
| Repeatable Read | Held until commit | Held until commit (on rows read) |
| Serializable | Held until commit | Held until commit, plus range/predicate locks to stop phantoms |
21.5 Setting the level (SQL)
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; -- standard; applies to the next transaction
-- Or at the start of a transaction:
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- PostgreSQL style
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; -- MySQL: for the session
(Exact syntax differs between systems.)
21.6 Real systems differ from the textbook
| DBMS | Default level | Notes |
|---|---|---|
| PostgreSQL | Read Committed | Read Uncommitted behaves as Read Committed; Repeatable Read is snapshot isolation (no phantoms, but write skew possible); Serializable uses an extra conflict detection technique |
| MySQL (InnoDB) | Repeatable Read | Uses MVCC plus next-key locks that largely prevent phantoms |
| SQL Server | Read Committed | Locking by default; optional row-versioning modes and a Snapshot level |
| Oracle | Read Committed | Supports Serializable and Read-Only; Read Uncommitted and Repeatable Read are not offered as such |
21.7 Choosing a level
- Higher isolation β safer data, but more blocking/aborts and lower throughput.
- Lower isolation β faster, but you must tolerate anomalies.
- Typical guidance: Read Committed for general apps; Repeatable Read / Serializable for critical logic such as financial calculations; Read Uncommitted only for rough analytics where approximate data is acceptable.
22. Distributed Transactions and Two-Phase Commit
22.1 Distributed transactions
A distributed (global) transaction accesses data stored at multiple sites/databases, yet must still satisfy ACID, especially atomicity: the transaction must commit at all sites or abort at all sites.
Example: booking a trip updates the airline database, the hotel database and the payment database. They must all succeed together or all fail together.
22.2 Roles
| Role | Description |
|---|---|
| Coordinator | The site (or transaction manager) that initiates and controls the global commit decision |
| Participants (cohorts) | The sites executing parts (sub-transactions) of the global transaction |
22.3 The problem
If each site decides independently, one could commit while another fails, leaving the system inconsistent. We need an atomic commit protocol: the most common is Two-Phase Commit (2PC).
22.4 Two-Phase Commit (2PC)
Phase 1 β Prepare (voting phase)
- The coordinator writes
<prepare T>to its log and sends PREPARE to all participants. - Each participant checks whether it can commit:
- If yes: it forces its log to disk (writes
<ready T>, so it can commit even after a crash), then replies VOTE-COMMIT (YES). From now on, it can't unilaterally abort. - If no: it writes
<no T>, replies VOTE-ABORT (NO), and aborts locally.
- If yes: it forces its log to disk (writes
Phase 2 β Commit / Abort (decision phase) 3. The coordinator collects the votes:
- All YES β writes
<commit T>to its log (the commit point), sends GLOBAL-COMMIT to all. - Any NO (or a timeout) β writes
<abort T>, sends GLOBAL-ABORT to all.
- Each participant follows the decision, writes it in its log, releases locks, and sends an ACK.
- After all ACKs, the coordinator writes
<end T>.
Coordinator Participants
β ββ PREPARE βββββββββββββββββββββΊ β
β βββ VOTE-COMMIT / VOTE-ABORT βββ β Phase 1 (voting)
β β
β ββ GLOBAL-COMMIT / GLOBAL-ABORT βΊβ Phase 2 (decision)
β βββ ACK ββββββββββββββββββββββββ β
22.5 Failure handling (simplified)
| Failure | What happens |
|---|---|
| Participant fails before voting | Coordinator times out β treats it as NO β global abort |
| Participant fails after voting YES | On recovery, it sees <ready T> in its log and asks the coordinator for the outcome |
| Coordinator fails before deciding | Participants that haven't voted can abort; those that voted YES must wait (blocked) |
| Coordinator fails after deciding | On recovery, it reads its log and resends the decision |
| Message lost | Timeouts and retransmission |
22.6 The blocking problem
If the coordinator crashes after participants have voted YES but before they learn the decision, those participants are stuck holding locks, unable to commit or abort on their own, until the coordinator recovers. This is 2PC's main weakness. (A three-phase commit protocol adds an extra phase to reduce blocking, at higher cost, and is rarely used in practice.)
22.7 Properties of 2PC
| β Advantages | β Disadvantages |
|---|---|
| Guarantees atomic commit across sites | Blocking if the coordinator fails |
| Simple and widely supported (for example, the XA standard) | Extra messages and log writes (slower) |
| Works with strict 2PL at each site | The coordinator is a single point of failure |
| Locks are held longer, reducing concurrency |
22.8 Related concerns
- Each site also runs its own concurrency control (e.g., local strict 2PL); for global serializability, all sites must be consistent about the order.
- Distributed deadlocks can span sites and require a global wait-for graph or timeouts to detect.
23. Transaction Commands: COMMIT, ROLLBACK, SAVEPOINT
These are TCL (Transaction Control Language) commands.
23.1 Starting a transaction
| DBMS | Explicit start |
|---|---|
| MySQL, PostgreSQL, SQLite | START TRANSACTION; or BEGIN; |
| SQL Server | BEGIN TRANSACTION; |
| Oracle | A transaction starts implicitly with the first DML statement |
23.2 Autocommit
- Many systems run in autocommit mode by default: each statement is its own transaction and is committed automatically.
- Using
BEGIN/START TRANSACTIONturns off autocommit until the transaction ends. - In MySQL:
SET autocommit = 0;disables autocommit for the session.
23.3 COMMIT
Makes all changes of the transaction permanent and visible to others, and releases its locks.
COMMIT;
23.4 ROLLBACK
Undoes all changes since the transaction began (or since the last commit), and releases locks.
ROLLBACK;
23.5 SAVEPOINT
Sets a named point inside a transaction so you can roll back part of the work without abandoning everything.
SAVEPOINT sp1; -- create a savepoint
ROLLBACK TO SAVEPOINT sp1; -- undo everything after sp1 (transaction stays open)
RELEASE SAVEPOINT sp1; -- remove the savepoint (not supported by every system)
- Rolling back to a savepoint does not end the transaction; you still need
COMMITorROLLBACK. - Later savepoints are discarded when you roll back to an earlier one.
- SQL Server syntax:
SAVE TRANSACTION sp1;andROLLBACK TRANSACTION sp1; - Oracle / PostgreSQL / MySQL:
ROLLBACK TO [SAVEPOINT] sp1;
23.6 Full example: bank transfer
START TRANSACTION;
UPDATE Account SET balance = balance - 500 WHERE acc_no = 'A';
SAVEPOINT after_debit;
UPDATE Account SET balance = balance + 500 WHERE acc_no = 'B';
-- Suppose a check shows account B doesn't exist:
-- ROLLBACK TO SAVEPOINT after_debit; -- keep the debit, undo only the credit
-- or simply:
-- ROLLBACK; -- undo everything
COMMIT; -- all done, make it permanent
23.7 Savepoint walkthrough
| Step | Command | Effect |
|---|---|---|
| 1 | START TRANSACTION | Begin |
| 2 | INSERT row 1 | Pending |
| 3 | SAVEPOINT s1 | Marks the position |
| 4 | INSERT row 2 | Pending |
| 5 | ROLLBACK TO s1 | Row 2 undone; row 1 still pending |
| 6 | COMMIT | Row 1 permanently saved |
23.8 Important notes
- DDL and implicit commits: in MySQL and Oracle, DDL statements (
CREATE,ALTER,DROP, β¦) implicitly commit the current transaction. PostgreSQL and SQL Server allow many DDL statements inside a transaction that can be rolled back. - If the connection drops or the system crashes before
COMMIT, uncommitted changes are rolled back automatically. - On errors inside a transaction, behaviour varies: PostgreSQL marks the whole transaction as aborted until you
ROLLBACK(or roll back to a savepoint); other systems may let you continue. - Keep transactions short to reduce lock holding time, contention and deadlocks.
23.9 Quick summary
| Command | Purpose |
|---|---|
START TRANSACTION / BEGIN | Start a transaction |
COMMIT | Save all changes permanently |
ROLLBACK | Undo all changes of the transaction |
SAVEPOINT name | Mark a point to roll back to |
ROLLBACK TO name | Undo back to the savepoint only |
RELEASE SAVEPOINT name | Delete the savepoint |
SET TRANSACTION ISOLATION LEVEL ... | Choose the isolation level |
β‘ Quick Revision Cheat Sheet
Transactions
- A transaction = logical unit of work, all-or-nothing.
- States: Active β Partially committed β Committed (or Failed β Aborted) β Terminated.
- ACID: Atomicity (undo via log) Β· Consistency (valid state β valid state) Β· Isolation (concurrency control) Β· Durability (redo via WAL).
Schedules and serializability
- Serial: one after another (n! options, always correct, slow). Non-serial: interleaved (fast, must be serializable).
- Conflict = different transactions + same item + at least one write (R-W, W-R, W-W).
- Conflict serializable β precedence graph has no cycle; topological order = serial order.
- View serializable: same initial reads, same read-from, same final writes; needs blind writes to differ; testing is NP-complete.
- Serial β Conflict-serializable β View-serializable.
Recoverability
- Recoverable: if Ti reads from Tj, Tj commits first.
- Cascadeless: read only committed data.
- Strict: no read or write of uncommitted data.
- Strict β Cascadeless β Recoverable.
Concurrency problems
- Lost update (W-W) Β· Dirty read (read uncommitted) Β· Non-repeatable read (same row changes) Β· Phantom (set of rows changes).
Locking
- S (shared, read) is compatible with S; X (exclusive, write) is compatible with nothing.
- 2PL: growing phase then shrinking phase; guarantees conflict serializability, but not deadlock-freedom or cascadeless.
- Strict 2PL: hold X locks until commit/abort. Rigorous 2PL: hold all locks until commit/abort. Conservative 2PL: all locks up front β deadlock-free.
Deadlocks
- Four conditions: mutual exclusion, hold & wait, no preemption, circular wait.
- Prevention: conservative 2PL, resource ordering, wait-die (non-preemptive), wound-wait (preemptive).
- Detection: wait-for graph cycle. Recovery: pick a victim, roll back (total/partial), avoid starvation.
Other protocols
- Timestamp ordering: read aborts if TS(T) < W_TS(X); write aborts if TS(T) < R_TS(X) or < W_TS(X); Thomas write rule skips obsolete writes.
- Optimistic (OCC): read β validate β write; abort if validation fails; best when conflicts are rare.
- MVCC: multiple versions; readers don't block writers; snapshot isolation can allow write skew.
Isolation levels (standard)
| Level | Dirty | Non-repeatable | Phantom |
|---|---|---|---|
| Read Uncommitted | β | β | β |
| Read Committed | β | β | β |
| Repeatable Read | β | β | β |
| Serializable | β | β | β |
(β = possible, β = prevented)
Distributed
- 2PC: Phase 1 prepare/vote β Phase 2 commit/abort. Coordinator + participants. Blocking if coordinator fails.
Commands
START TRANSACTION/BEGINΒ·COMMITΒ·ROLLBACKΒ·SAVEPOINT xΒ·ROLLBACK TO xΒ· autocommit is often on by default.
π€ Commonly asked questions (from these topics)
- What is a transaction? Explain its states with a diagram.
- Explain the ACID properties with an example.
- Differentiate serial and non-serial schedules.
- What is serializability? Differentiate conflict and view serializability.
- How do you test conflict serializability using a precedence graph? Give an example.
- What are recoverable, cascadeless and strict schedules?
- Explain lost update, dirty read, non-repeatable read and phantom read with examples.
- What are shared and exclusive locks? Draw the compatibility matrix.
- Explain two-phase locking. Does it prevent deadlocks or cascading rollbacks?
- Differentiate strict 2PL and rigorous 2PL.
- What are the conditions for deadlock? Explain prevention (wait-die, wound-wait), detection and recovery.
- Explain timestamp ordering and Thomas' write rule.
- What is optimistic concurrency control? Describe its phases.
- What is MVCC? How does it let readers and writers work together?
- List the SQL isolation levels and the anomalies each allows.
- Explain the two-phase commit protocol and its drawback.
- What do
COMMIT,ROLLBACKandSAVEPOINTdo?
#Database Recovery and Backup#
Study notes written in plain language. Read top to bottom the first time; later, jump to whichever section you need.
Table of Contents
- Why Recovery Matters
- Types of Database Failures
- Log-Based Recovery
- Write-Ahead Logging (WAL)
- Deferred and Immediate Database Modification
- Checkpoints
- Undo and Redo Operations
- Shadow Paging
- Crash Recovery
- Recovery with Concurrent Transactions
- Backup and Restore
- Full, Incremental and Differential Backups
- Point-in-Time Recovery
- Replication and Failover
- Disaster Recovery Planning
- Quick Revision Cheat Sheet
1. Why Recovery Matters
A database promises that your data stays correct and safe. But hardware breaks, programs crash, and people make mistakes. Recovery is the set of techniques a DBMS uses to bring the database back to a correct, consistent state after something goes wrong.
Recovery exists to protect two properties from the ACID guarantees:
| Property | What recovery must ensure |
|---|---|
| Atomicity | A transaction is all-or-nothing. If it was half done when a failure hit, its partial changes must be undone. |
| Durability | Once a transaction is committed, its effects must survive any failure. If they were not yet saved on disk, they must be redone. |
One-line summary: Recovery = undo what should not have happened + redo what should not have been lost.
There are two big halves to this topic:
- Recovery (sections 2β10): protecting the database from failures while it is running (crashes, aborted transactions).
- Backup and disaster protection (sections 11β15): protecting against bigger losses (disk destroyed, data corrupted, site lost).
2. Types of Database Failures
Not all failures are equal. The recovery method you need depends on what failed.
2.1 Transaction Failure
A single transaction cannot finish normally.
- Logical error: the transaction hits something it cannot handle (bad input, overflow, divide by zero, a constraint violated).
- System error: the DBMS must stop the transaction, for example because of a deadlock.
Effect: only that transaction is affected. The rest of the system keeps running. Fix: roll back (undo) the transaction.
2.2 System Crash
The whole system stops unexpectedly: power cut, operating system crash, DBMS software bug, CPU or memory failure.
- Contents of main memory (RAM), including the buffer pool, are lost.
- Contents of disk are assumed to be safe (this is called the fail-stop assumption).
Effect: many transactions may be in progress, and some committed changes may not have reached disk yet. Fix: crash recovery using the log (sections 3, 9).
2.3 Disk (Media) Failure
The storage device itself is damaged: head crash, controller failure, corrupted disk, accidentally deleted files.
Effect: data on disk is lost or unreadable, so the log alone may not be enough. Fix: restore from a backup, then roll forward using archived logs (sections 11β13), or fail over to a replica (section 14).
2.4 Other Failures Worth Knowing
- Human errors: someone runs
DELETEwithout aWHERE, or drops a table. - Disasters: fire, flood, earthquake, or a whole data centre going offline.
- Communication failures in distributed systems: network breaks between sites.
2.5 Storage Types (Background You Need)
| Storage | Survives a crash? | Example |
|---|---|---|
| Volatile | No | RAM, cache, buffer pool |
| Non-volatile | Yes (but can still fail) | HDD, SSD |
| Stable (idealised) | Practically never lost | Data written to several independent disks/locations |
Recovery techniques try to simulate stable storage by keeping the log safely, often on multiple devices.
2.6 Failure Summary
| Failure | What is lost | Typical remedy |
|---|---|---|
| Transaction failure | One transaction's work | Rollback via log |
| System crash | Contents of memory | Crash recovery (redo + undo) |
| Disk failure | Data on disk | Restore backup + replay logs / failover |
| Disaster | Entire site | Disaster recovery plan |
3. Log-Based Recovery
3.1 The Core Idea
Before (or while) changing data, the DBMS writes a record of what it is doing into a separate file called the log (also called the journal or transaction log). After a failure, the log is read to decide what to undo and what to redo.
The log is sequential and append-only: new records are added at the end, which makes writing it fast.
3.2 Types of Log Records
Every transaction Ti leaves a trail of records:
| Record | Meaning |
|---|---|
<Ti, start> | Transaction Ti has begun |
<Ti, X, V_old, V_new> | Ti changed item X from V_old to V_new |
<Ti, commit> | Ti has committed |
<Ti, abort> | Ti has been rolled back |
V_oldis the before image (needed for undo).V_newis the after image (needed for redo).
Each record also usually has a Log Sequence Number (LSN), a number that grows with time and identifies the record's position in the log.
3.3 A Small Example
Suppose account A has 1000 and B has 2000. Transaction T1 moves 50 from A to B.
<T1, start>
<T1, A, 1000, 950>
<T1, B, 2000, 2050>
<T1, commit>
- If the system crashes before
<T1, commit>is on disk, T1 never completed, so we undo it using the old values (A back to 1000, B back to 2000). - If the crash happens after
<T1, commit>is on disk but the data pages were not written, we redo it using the new values (A = 950, B = 2050).
3.4 Why a Log Works
Writing a small sequential log record is much cheaper than writing full data pages at random places. The DBMS can therefore keep committed work safe quickly, and flush the real data pages to disk later at leisure.
4. Write-Ahead Logging (WAL)
4.1 The Rule
WAL protocol: A change must be recorded in the log on stable storage before the changed data page is written to the database on disk.
Think of it like a diary: write down what you plan to do before you do it, so if you are interrupted you know exactly where you were.
4.2 The Two WAL Conditions
- Undo rule: Before a modified (dirty) data page is written to disk, all log records describing changes to that page (including the old values) must already be on disk. This guarantees we can always undo uncommitted changes.
- Redo rule (commit rule): Before a transaction is declared committed, all of its log records (including the
commitrecord) must be on disk. This guarantees we can always redo committed changes.
4.3 Why Both Are Needed
| Scenario | What WAL guarantees |
|---|---|
| A dirty page of an uncommitted transaction reached disk, then crash | Old value is in the log, so it can be undone |
| A transaction committed but its pages were still only in memory, then crash | New value is in the log, so it can be redone |
4.4 Buffer Management Policies (Helps You Understand WAL)
The buffer pool decides when pages go to disk. Two policy pairs matter:
| Policy | Meaning | Consequence |
|---|---|---|
| Steal | A page with uncommitted changes may be written to disk | Need undo capability |
| No-steal | Uncommitted pages are never written to disk | No undo needed, but needs lots of memory |
| Force | All pages changed by a transaction are written to disk at commit | No redo needed, but commit is slow |
| No-force | Pages need not be written at commit | Need redo capability, but commit is fast |
Most real systems use Steal + No-force. This is fastest, and WAL is what makes it safe.
4.5 Flushing the Log
- Log records are first kept in a log buffer in memory.
- The buffer is flushed to disk when: a transaction commits, the buffer is full, or a data page is about to be written (WAL rule).
- Group commit: several transactions' commit records are flushed together in one disk write, improving throughput.
5. Deferred and Immediate Database Modification
These are two strategies that decide when the actual database is updated relative to commit. Both use the log.
5.1 Deferred Database Modification
All writes are postponed until the transaction commits.
How it works
- Transaction starts:
<T, start>is logged. - Every write is only recorded in the log (new value only is needed). The database is not touched.
- When the transaction is about to commit,
<T, commit>is logged. - Only then are the changes actually applied to the database.
On failure
- Crash before commit record: nothing was written to the database, so simply ignore the transaction. No undo needed.
- Crash after commit record: some changes may not have been applied, so redo them from the log.
Log record contains only the new value: <T, X, V_new>.
Pros: no undo logic; simple. Cons: the transaction must hold all its changes somewhere until commit; reads of its own writes need extra care; poor for long transactions.
5.2 Immediate Database Modification
Writes may be applied to the database while the transaction is still running (before commit), as long as the log record for the change is written first (WAL).
How it works
<T, start>is logged.- For each write, the log record
<T, X, V_old, V_new>is written first, and then the database may be updated, even before commit. - At commit,
<T, commit>is logged.
On failure
- Transaction has a commit record: redo it (some updates may be missing on disk).
- Transaction has no commit record: undo it (some updates may already be on disk).
Log record needs both old and new value.
Pros: more flexible, better memory use, works for long transactions. Cons: needs both undo and redo logic.
5.3 Side-by-Side Comparison
| Feature | Deferred | Immediate |
|---|---|---|
| When DB is updated | Only after commit | Possibly before commit |
| Log record content | New value only | Old and new values |
| Undo needed? | No | Yes |
| Redo needed? | Yes | Yes |
| Complexity | Lower | Higher |
| Used in practice | Rarely | Commonly |
5.4 Worked Example
Log contents at the moment of the crash:
<T1, start>
<T1, A, 1000, 950>
<T1, B, 2000, 2050>
<T1, commit>
<T2, start>
<T2, C, 700, 600>
<-- CRASH
- T1 has a commit record, so REDO T1 (set A = 950, B = 2050).
- T2 has no commit record:
- Deferred: ignore T2 (database never changed).
- Immediate: UNDO T2 (set C back to 700).
6. Checkpoints
6.1 The Problem
After a long time the log becomes huge. If recovery had to read the entire log from the very beginning after every crash, it would be extremely slow. Also, most old transactions were already safely written to disk, so redoing them is wasted work.
6.2 The Solution
A checkpoint is a marker in the log saying: "Everything before this point is safely on disk. Recovery does not need to look further back (except for transactions that were still active)."
6.3 Simple (Quiescent) Checkpoint
Steps:
- Stop accepting new transactions temporarily.
- Wait for running transactions to finish.
- Flush all log records from memory to disk.
- Flush all modified buffer pages to disk.
- Write a
<checkpoint>record to the log and flush it. - Resume normal work.
Downside: the system pauses during the checkpoint.
6.4 Fuzzy (Non-Quiescent) Checkpoint
Transactions keep running while the checkpoint is taken.
- Write a
<begin checkpoint>record that lists all active transactions (and often a dirty page table). - Dirty pages are flushed to disk in the background, gradually.
- When the flushing is complete, write
<end checkpoint>. - Record the location of the last checkpoint in a known place (for example, a "master record").
Benefit: almost no interruption. This is what modern systems use.
6.5 What Recovery Does with a Checkpoint
When a crash occurs, recovery starts from the most recent checkpoint:
- Transactions that committed before the checkpoint: nothing to do (already on disk).
- Transactions active at the checkpoint, or started after it: examine the log from that point and redo/undo accordingly.
|----- old log (ignored) -----|<checkpoint>|---- log scanned in recovery ----> CRASH
6.6 Benefits
- Shorter recovery time
- Smaller portion of the log needs scanning
- Old log portions can be archived or discarded
7. Undo and Redo Operations
These two actions are the building blocks of every recovery algorithm.
7.1 UNDO
Undo reverses the effect of a change by restoring the old value (before image).
- Used for transactions that did not commit before the failure (or are being rolled back).
- Process the log backwards (from newest to oldest) so that the earliest old value ends up in place.
- After undoing, a
<T, abort>record is written.
7.2 REDO
Redo re-applies a change by writing the new value (after image).
- Used for transactions that did commit but whose changes may not have reached disk.
- Process the log forwards (from oldest to newest) so the final value is the latest one.
7.3 Idempotence (Very Important)
Recovery itself can crash! So undo and redo must be idempotent: doing them once or many times gives the same result.
redo(redo(X))=redo(X)andundo(undo(X))=undo(X)
This works because we set a value to a specific number (for example "A = 950"), rather than adding to it.
7.4 Compensation Log Records (CLRs)
When an undo is performed, the undo action itself is also logged, as a compensation log record. This ensures:
- An undo is never undone twice if a crash happens during recovery.
- Recovery can continue where it left off.
Typically a CLR contains a pointer (undoNextLSN) to the next record that still needs to be undone.
7.5 Which Operation for Which Case?
| Transaction state at crash | Action |
|---|---|
| Committed (commit record in log) | Redo |
| Active / no commit record | Undo |
| Already aborted & fully rolled back | Nothing (or redo its history in some algorithms) |
7.6 Quick Visual
Undo: scan log <<<<<< backwards, restore OLD values
Redo: scan log >>>>>> forwards, apply NEW values
8. Shadow Paging
8.1 What Is It?
Shadow paging is an alternative to log-based recovery. It avoids a log by never overwriting old data: instead, it writes updates to new pages, and switches over only on commit.
8.2 Key Structures
- The database is divided into fixed-size pages.
- A page table maps logical page numbers to physical disk locations.
- Two tables are kept:
- Shadow page table: a copy of the page table that points to the original, unchanged pages. Stored safely on disk and never modified during the transaction.
- Current page table: used by the running transaction. It initially equals the shadow table.
8.3 How It Works
- At the start,
current page table= copy ofshadow page table. - When the transaction wants to write to page P:
- If this is the first write to P, make a copy of P in a new free disk block.
- Update the current page table entry for P to point to the new block.
- Apply the change to the new copy.
- The shadow page table and the original pages remain untouched.
8.4 Commit
- Flush all modified pages to disk.
- Atomically make the current page table become the new shadow page table (done by updating a single pointer, the root pointer, held in a fixed disk location).
- Free the old pages.
The atomic switch of one pointer is what makes the commit "all or nothing".
8.5 Recovery
- Crash before commit: just discard the current page table. The shadow page table still points to the old, consistent database. No undo or redo needed.
- Crash after commit: the new table is already the shadow table, so the database is fine.
8.6 Advantages
- No log needed, so no overhead of writing log records.
- Very fast recovery (just use the shadow table).
- No undo or redo operations at all.
8.7 Disadvantages
| Problem | Explanation |
|---|---|
| Copying overhead | Every modified page is copied |
| Data fragmentation | Pages scatter around the disk, hurting sequential access |
| Garbage collection | Old pages must be found and freed |
| Concurrency is hard | Designed for one transaction at a time; supporting many is complicated |
| Page table can be large | Copying it for each transaction is costly |
8.8 Log-Based vs Shadow Paging
| Aspect | Log-based (WAL) | Shadow paging |
|---|---|---|
| Needs a log | Yes | No |
| Undo/redo | Yes | No |
| Recovery speed | Slower (analyse log) | Very fast |
| Normal-operation cost | Low | Higher (copying, fragmentation) |
| Concurrency support | Good | Poor |
| Used in practice | Very widely | Rarely (some small/embedded systems) |
9. Crash Recovery
9.1 What Happens
When the DBMS restarts after a system crash, it must automatically bring the database to a consistent state: all committed work present, all uncommitted work gone.
9.2 Simple Log-Based Procedure
- Find the last checkpoint.
- Build two lists by scanning the log forward:
- Redo-list: transactions with both
startandcommit. - Undo-list: transactions with
startbut nocommit(orabort).
- Redo-list: transactions with both
- Undo all transactions in the undo-list (scan backwards).
- Redo all transactions in the redo-list (scan forwards).
9.3 The ARIES Approach (Standard Industrial Algorithm)
ARIES (Algorithm for Recovery and Isolation Exploiting Semantics) is the best-known recovery algorithm. It relies on WAL, fuzzy checkpoints, and logging of undo actions. It works in three phases:
Phase 1: Analysis
- Start at the last checkpoint and scan forward to the end of the log.
- Rebuild two tables as they were at crash time:
- Transaction table: which transactions were active (and their last LSN).
- Dirty page table: which pages were modified in memory but possibly not on disk, with the recLSN (first log record that dirtied it).
- Output: the list of transactions to undo ("losers") and the starting point for redo.
Phase 2: Redo ("Repeating History")
- Start from the smallest recLSN in the dirty page table and scan forward.
- Re-apply every logged update (even those of transactions that will later be undone) if the page on disk is older than the log record.
- To check this, compare the log record's LSN with the pageLSN stored on the page. Redo only if
pageLSN < record LSN. - Result: the database is exactly as it was at the moment of the crash.
Phase 3: Undo
- Roll back all loser transactions (active at crash, not committed).
- Scan the log backwards, undoing each update and writing a CLR for each undo.
- At the end, write
<T, abort/end>for each loser.
Log: [checkpoint] ... ... ... ... ... CRASH
|--- Analysis -------------->|
|--- Redo ------------->|
|<-------- Undo (losers) ----|
9.4 Key Principle: Repeat History, Then Undo
ARIES first redoes everything to restore the pre-crash state, then undoes the incomplete transactions. This is simple and handles all situations correctly, including crashes during recovery (because of CLRs).
9.5 Crash During Recovery
Because undo and redo are idempotent and every undo is logged with CLRs, recovery can simply be restarted from the beginning and will still arrive at the right result.
9.6 Recovery Time
Recovery time depends mostly on:
- How long ago the last checkpoint was taken
- How many dirty pages were in memory
- Log volume since the checkpoint
10. Recovery with Concurrent Transactions
10.1 The Complication
In real systems many transactions run at the same time, sharing the same log and sometimes touching related data. Recovery must now handle several transactions at once, without breaking isolation.
10.2 Single Shared Log
All concurrent transactions write to one log, interleaved:
<T1, start>
<T2, start>
<T1, A, 10, 20>
<T2, B, 5, 8>
<T1, commit>
<T2, C, 3, 9>
<-- CRASH
On recovery, T1 is redone and T2 is undone, using the same procedures as before.
10.3 Role of Concurrency Control
Recovery depends on the locking protocol.
- Strict two-phase locking (Strict 2PL): a transaction keeps its exclusive (write) locks until it commits or aborts.
- This guarantees that no other transaction reads or overwrites an uncommitted value.
10.4 Cascading Rollback
If transaction T2 reads a value written by uncommitted T1, and T1 aborts, then T2 must also abort. This chain reaction is a cascading rollback, which is expensive.
- Cascadeless schedules: transactions read only committed data, so there is no cascading rollback.
- Strict schedules: a value written by T cannot be read or overwritten until T commits/aborts. This makes undo trivial (just restore the before image).
Rule of thumb: Use strict 2PL to keep recovery simple and safe.
10.5 Why Overwriting Uncommitted Data Is Dangerous
If T2 overwrote a value written by uncommitted T1 and T1 later aborts, restoring T1's old value would erase T2's update. Strict locking prevents this problem entirely.
10.6 Checkpoints with Concurrency
A concurrent checkpoint record lists all transactions active at that moment: <checkpoint, {T1, T2, ...}>. Recovery must then:
- Go back to the most recent checkpoint.
- Consider every transaction in the checkpoint's active list, plus every transaction that started afterwards.
- Redo those that committed; undo those that did not.
Transactions that committed before the checkpoint can be ignored.
10.7 Recovery Steps for Concurrent Transactions (Summary)
- Scan the log backward to find the last checkpoint.
- Start with the undo-list = active transactions in the checkpoint.
- Scan forward from the checkpoint:
- On
<T, start>: add T to the undo-list. - On
<T, commit>: move T from undo-list to redo-list.
- On
- Scan backward and undo every update by transactions in the undo-list.
- Scan forward and redo every update by transactions in the redo-list.
10.8 Logical Undo (Brief Note)
For things like index changes, other transactions may have modified the same structure after your change. A physical undo (restoring bytes) could damage their work, so the system performs a logical undo: it applies the inverse operation (for example, deleting a key that was inserted) rather than restoring old bytes.
11. Backup and Restore
11.1 Why Backups Are Still Needed
The log protects against crashes, but it cannot help if the disk itself is destroyed or if data is wrongly deleted or corrupted. A backup is a separate copy of the database kept somewhere safe, so you can get your data back.
Replication is not a backup. If you delete data by mistake, replication copies the deletion everywhere. A backup is a snapshot in time.
11.2 Backup vs Restore
- Backup: creating a copy of data (and logs) and storing it separately.
- Restore: copying the backup back and making the database usable again.
- Recovery: applying logs after the restore to bring the data up to date.
11.3 Logical vs Physical Backups
| Type | What it contains | Pros | Cons |
|---|---|---|---|
| Logical | SQL statements or exported data (for example a dump of CREATE TABLE and INSERT) | Portable across versions and systems, readable, selective | Slow to restore on large databases |
| Physical | Raw copies of data files, pages, and logs | Fast to back up and restore | Tied to same DBMS/version/platform |
11.4 Hot, Warm and Cold Backups
| Type | Database state during backup | Notes |
|---|---|---|
| Cold (offline) | Database is shut down | Simplest and consistent; but causes downtime |
| Warm | Database is up, but only reads allowed (or limited writes) | Partial availability |
| Hot (online) | Database fully running and being updated | No downtime; needs log support to be made consistent |
For hot backups, the log produced during the backup is also saved, so the copy can be made consistent on restore.
11.5 What Should Be Backed Up?
- Data files
- Transaction / archived logs
- Configuration files
- Schema definitions, users, and permissions
11.6 Backup Best Practices
- 3-2-1 rule: keep 3 copies of your data, on 2 different types of media, with 1 copy stored off-site.
- Automate backups on a schedule.
- Encrypt backups and control who can access them.
- Test restores regularly. A backup that has never been restored is just a hope.
- Keep a retention policy (how long to keep each backup).
- Verify integrity (checksums) of backup files.
11.7 Restore Process (Typical)
- Identify the correct backup.
- Restore the data files from that backup.
- Apply archived logs to bring the data forward (roll forward).
- Undo any uncommitted transactions.
- Verify consistency and reopen the database.
12. Full, Incremental and Differential Backups
These describe how much data each backup copies.
12.1 Full Backup
Copies everything in the database.
- Pros: simplest and fastest restore (only one backup needed).
- Cons: takes the most time and storage space.
12.2 Incremental Backup
Copies only the data that changed since the last backup of any kind (full or incremental).
- Pros: smallest and fastest to take; saves storage.
- Cons: restore is slower and more complex: you need the last full backup plus every incremental in order. If one is missing or damaged, the chain breaks.
12.3 Differential Backup
Copies everything that changed since the last full backup.
- Pros: restore needs only the last full backup plus the latest differential, which is simpler than incrementals.
- Cons: grows bigger each day until the next full backup.
12.4 Example Week
Suppose a full backup runs on Sunday and changes of 10 GB happen each day.
| Day | Incremental size | Differential size |
|---|---|---|
| Mon | 10 GB (since Sun) | 10 GB (since Sun) |
| Tue | 10 GB (since Mon) | 20 GB (since Sun) |
| Wed | 10 GB (since Tue) | 30 GB (since Sun) |
To restore Wednesday's state:
- Incremental: Full (Sun) + Mon + Tue + Wed β 4 backups
- Differential: Full (Sun) + Wed β 2 backups
12.5 Comparison Table
| Feature | Full | Incremental | Differential |
|---|---|---|---|
| Data copied | All | Changed since last backup | Changed since last full |
| Backup speed | Slowest | Fastest | Medium |
| Storage use | Highest | Lowest | Medium (grows) |
| Restore speed | Fastest | Slowest | Medium |
| Restore needs | 1 backup | Full + all incrementals | Full + latest differential |
| Risk | Low | Higher (chain dependency) | Lower |
12.6 Common Strategy
A typical schedule combines them: weekly full + daily differential or incremental + continuous log archiving. This balances storage, speed, and recovery effort.
12.7 Block-Level Tracking (Extra Detail)
Incremental and differential backups often rely on tracking which pages/blocks changed, using a change-tracking bitmap or the log sequence numbers, so the system does not need to compare entire files.
13. Point-in-Time Recovery
13.1 What Is It?
Point-in-Time Recovery (PITR) lets you restore the database to any chosen moment, such as "just before 2:14 PM yesterday, before someone dropped the table".
A plain backup only returns you to when the backup was taken. PITR goes further by combining a backup with the continuous log.
13.2 What You Need
- A base backup (a full physical backup).
- A continuous archive of transaction logs (archived WAL), covering the time from the base backup up to the desired point.
13.3 Steps
Base backup (Sunday) + archived logs (Sun β Wed 2:14 PM) β database as of Wed 2:14 PM
- Restore the base backup taken before the target time.
- Replay (roll forward) the archived log records in order, redoing the committed changes.
- Stop replaying at the chosen target (a timestamp, a transaction ID, or a named restore point / LSN).
- Open the database. Transactions not committed by that moment are rolled back.
13.4 Typical Use Cases
- Accidental
DELETE/DROP TABLEor bad update (human error) - Application bug that corrupted data
- Recovering to just before a malicious or faulty change
- Testing: recreating the exact data state at an earlier moment
13.5 Important Points
- Log archiving must be enabled before the problem occurs; you cannot do PITR without logs.
- The log chain must be unbroken: a missing log file stops recovery at that gap.
- After a PITR, the database follows a new timeline; changes made after the target time in the old timeline are discarded.
- Recovery Point Objective (RPO) for PITR can be near zero because logs are archived continuously.
- The more logs to replay, the longer the restore takes, so take frequent base backups to reduce replay time.
13.6 Backup vs PITR
| Plain backup restore | Point-in-time recovery | |
|---|---|---|
| Restores to | Moment of the backup | Any chosen moment |
| Needs logs | No | Yes |
| Data loss | Everything after backup | Only what you choose to skip |
14. Replication and Failover
14.1 Replication
Replication keeps copies of the same data on multiple servers. If one server fails, another has the data.
- Primary (master/leader): accepts writes.
- Replica (standby/slave/follower): receives changes from the primary and can serve reads or take over.
Replication is mostly based on shipping the log: changes recorded on the primary are sent to replicas, which replay them.
14.2 Synchronous vs Asynchronous Replication
| Type | How it works | Pros | Cons |
|---|---|---|---|
| Synchronous | Primary waits until the replica confirms it has the change before committing | No data loss (RPO β 0) | Slower commits; writes blocked if replica unreachable |
| Asynchronous | Primary commits immediately and sends changes to the replica afterwards | Fast; unaffected by replica delay | Replica can lag, so recent commits may be lost on failure |
| Semi-synchronous | Waits for at least one replica to acknowledge | Compromise between the two | Some added latency |
14.3 Replication Topologies
- PrimaryβReplica (single leader): one writer, many readers. Simple and most common.
- Multi-primary (multi-master): several nodes accept writes; needs conflict resolution.
- Cascading replication: replicas feed other replicas to reduce load on the primary.
14.4 Failover
Failover is the process of switching to a standby server when the primary fails.
Steps:
- Detect the failure (health checks / heartbeats missed).
- Choose the best replica (the most up-to-date one).
- Promote it to be the new primary.
- Redirect clients and applications to the new primary (DNS change, virtual IP, or connection proxy).
- Reconfigure other replicas to follow the new primary.
- Later, repair the old primary and rejoin it as a replica (failback, if desired).
14.5 Automatic vs Manual Failover
| Automatic | Manual | |
|---|---|---|
| Speed | Fast (seconds) | Slower |
| Risk | Possible false alarms | Human control |
| Best for | High availability needs | Cases where careful judgment is needed |
14.6 Split-Brain Problem
If a network issue makes each node think the other is dead, two nodes may both act as primary, accepting conflicting writes. To avoid this:
- Use a quorum/majority vote (an odd number of nodes or a witness).
- Use fencing to shut off the old primary before promoting a new one.
14.7 Related Setups
- Hot standby: replica is running, continuously applying changes, and ready to take over quickly (can often serve read queries).
- Warm standby: replica receives logs but needs some steps before it can serve.
- Cold standby: a machine is available but data must be restored first.
- Log shipping: archived log files are periodically copied to the standby and replayed.
14.8 Replication Is Not a Backup
Replication copies mistakes too. A dropped table drops on the replicas as well. Keep both replication (for availability) and backups (for historical recovery).
15. Disaster Recovery Planning
15.1 What Is a Disaster?
A disaster is a large event that makes a whole site or system unavailable: fire, flood, earthquake, power or network outage at a data centre, major hardware failure, ransomware, or serious human error.
Disaster Recovery (DR) is the plan and set of procedures to restore the database and critical services after such an event.
15.2 Two Key Metrics
| Metric | Stands for | Question it answers |
|---|---|---|
| RPO | Recovery Point Objective | How much data can we afford to lose? (measured in time, e.g. 5 minutes of data) |
| RTO | Recovery Time Objective | How long can we be down? (e.g. back up within 1 hour) |
<---- RPO ---->| disaster |<------- RTO ------->
last good copy service restored
- Lower RPO needs more frequent backups or synchronous replication.
- Lower RTO needs standby systems that are ready to go.
- Both cost more as they get smaller, so they are chosen based on business need.
15.3 Steps in Building a DR Plan
- Risk assessment: list possible threats and their likelihood.
- Business impact analysis: decide which databases and services are critical and what downtime costs.
- Set RPO and RTO for each system.
- Choose strategies: backups, replication, standby sites, off-site storage.
- Document procedures: step-by-step instructions, contacts, roles, and responsibilities.
- Implement and automate where possible.
- Test regularly with drills and simulated failures.
- Review and update as systems change.
15.4 Recovery Site Types
| Site | Description | Recovery time | Cost |
|---|---|---|---|
| Hot site | Fully equipped, running replica kept up to date | Minutes to hours | Highest |
| Warm site | Hardware ready, data somewhat up to date, some setup needed | Hours to a day | Medium |
| Cold site | Space and basic infrastructure only; need to install and restore | Days | Lowest |
15.5 Strategies Commonly Combined
- Regular backups stored off-site or in another region
- Geographic replication to a remote data centre
- Point-in-time recovery capability via archived logs
- Redundant hardware (power, network, storage such as RAID)
- Automated failover for critical systems
15.6 Roles and Communication
A good plan states who does what: who declares a disaster, who performs the recovery, who communicates with users and management, and how to reach them if normal systems are down.
15.7 Testing the Plan
- Backup restore tests: confirm backups actually work.
- Failover drills: switch to the standby deliberately.
- Full DR exercises: simulate loss of the primary site.
- Record results, measure actual RPO/RTO, and fix gaps.
An untested plan is an assumption, not a plan.
15.8 Typical Order of Actions in a Real Disaster
- Detect and confirm the incident.
- Declare a disaster and activate the plan.
- Fail over to the standby site / restore from backups.
- Roll forward logs to the required point.
- Verify data integrity and consistency.
- Redirect applications and users.
- Monitor, communicate, and later document lessons learned.
16. Quick Revision Cheat Sheet
Key Definitions
| Term | One-line meaning |
|---|---|
| Log | Sequential record of all changes, used to undo and redo |
| WAL | Log must reach disk before the data page does |
| Undo | Restore old (before) values for uncommitted work |
| Redo | Re-apply new (after) values for committed work |
| Checkpoint | Marker that limits how far back recovery must read |
| Deferred modification | DB updated only after commit β no undo |
| Immediate modification | DB may be updated before commit β undo + redo |
| Shadow paging | Write to new pages; switch the page table pointer on commit |
| ARIES | Analysis β Redo (repeat history) β Undo |
| CLR | Log record written when an undo is performed |
| Strict 2PL | Hold write locks until commit/abort; avoids cascading rollback |
| Full backup | Copy of everything |
| Incremental | Changes since the last backup |
| Differential | Changes since the last full backup |
| PITR | Base backup + archived logs β restore to any moment |
| Replication | Copies of data on multiple servers |
| Failover | Promote a standby when the primary dies |
| RPO | Maximum acceptable data loss (time) |
| RTO | Maximum acceptable downtime (time) |
Failure β Fix Map
| Failure | Fix |
|---|---|
| Transaction failure | Rollback with log (undo) |
| System crash | Crash recovery: analysis, redo, undo |
| Disk failure | Restore backup + replay logs, or failover |
| Human error | PITR / restore from backup |
| Site disaster | Disaster recovery plan, standby site |
Commonly Asked Exam Points
- Why is WAL needed? To allow undo of stolen pages and redo of unflushed committed changes.
- Deferred vs immediate: deferred needs only redo; immediate needs both.
- Why checkpoints? To reduce recovery time and the log portion to scan.
- Why is undo/redo idempotent? Because recovery may itself crash and be restarted.
- Shadow paging's main weakness: fragmentation, copying, poor concurrency.
- Incremental vs differential: incremental is smaller but restore is longer; differential is larger but restore is simpler.
- PITR needs base backup + continuous log archive.
- Replication β backup.
- RPO is about data loss; RTO is about downtime.
#Database Security#
Study notes in plain language. Read top to bottom the first time; later, jump to whichever section you need.
Table of Contents
- Why Database Security Matters
- Database Authentication
- Authorization and Access Control
- Users, Roles and Privileges
- GRANT and REVOKE
- Role-Based Access Control (RBAC)
- Least-Privilege Principle
- SQL Injection
- Parameterized Queries
- Encryption at Rest and in Transit
- Data Masking and Anonymization
- Auditing and Activity Logging
- Backup Security
- Database Security Best Practices
- Quick Revision Cheat Sheet
1. Why Database Security Matters
A database usually holds the most valuable thing an organisation owns: its data. That might be customer details, passwords, financial records, health information, or business secrets. If the data leaks, is changed, or disappears, the damage can be huge: financial loss, legal trouble, and loss of trust.
1.1 The CIA Triad
Database security is built around three goals:
| Goal | Meaning | Example of failure |
|---|---|---|
| Confidentiality | Only authorised people can see the data | A stranger reads customer records |
| Integrity | Data stays correct and is changed only by authorised people | Someone secretly edits a salary |
| Availability | Authorised users can access data when they need it | A database is wiped or locked by an attack |
1.2 Common Threats
- Weak or stolen passwords
- Users having more access than they need
- SQL injection attacks through application inputs
- Data intercepted while travelling over a network
- Stolen disks or leaked backups
- Insider misuse (a trusted person abusing access)
- Mistakes such as misconfiguration or accidental deletion
1.3 The Big Picture (Layers of Defence)
Security works best in layers, so if one fails, others still protect the data.
Who are you? -> Authentication
What may you do? -> Authorization / Access Control
Is the input safe? -> Parameterized queries (against SQL injection)
Is the data unreadable
to outsiders? -> Encryption (at rest and in transit)
Should you see real data? -> Masking / Anonymization
What happened and who
did it? -> Auditing and logging
Are copies safe too? -> Backup security
The rest of these notes walk through each layer.
2. Database Authentication
2.1 What Is It?
Authentication answers the question: "Who are you?" It is the process of verifying the identity of a user, application, or service before allowing it to connect to the database.
Authentication = proving identity. (It is different from authorization, which is about what you may do after your identity is known.)
2.2 Common Authentication Methods
| Method | How it works | Notes |
|---|---|---|
| Username and password | Database stores credentials and checks them at login | Most common; only as strong as the password |
| Operating-system authentication | Trusts the identity of the logged-in OS user | Convenient for local admin tasks |
| Directory / centralised authentication (LDAP, Active Directory, Kerberos) | An external identity system verifies users | One place to manage users and passwords |
| Single Sign-On (SSO) | One login gives access to many systems | Reduces password sprawl |
| Certificate-based authentication | Client proves identity with a digital certificate | Strong; often used between systems |
| Token / IAM-based authentication | Short-lived tokens issued by an identity service | Common in cloud databases |
| Multi-Factor Authentication (MFA) | Needs two or more proofs (password + code/device) | Greatly reduces risk from stolen passwords |
2.3 The Three Types of "Factors"
- Something you know: password, PIN
- Something you have: phone, hardware token, smart card
- Something you are: fingerprint, face
MFA combines at least two different types.
2.4 How Passwords Should Be Handled
- Databases and applications should never store passwords in plain text.
- Passwords should be stored as a salted hash produced by a slow, purpose-built password-hashing algorithm (such as bcrypt, scrypt, or Argon2).
- Hash: a one-way fingerprint of the password; it cannot be turned back into the original.
- Salt: random extra data added to each password before hashing, so identical passwords produce different hashes and precomputed attack tables become useless.
- Enforce strong password policies: minimum length, complexity or passphrases, expiry or rotation where appropriate, and lockout after repeated failures.
2.5 Good Authentication Habits
- Change default accounts and passwords immediately (many databases ship with well-known defaults).
- Remove or lock accounts that are not needed (sample accounts, former employees).
- Use separate accounts for each person and each application; avoid shared logins so actions can be traced.
- Do not hard-code passwords in source code; keep them in a secrets manager or protected configuration.
- Limit failed login attempts and monitor them.
- Prefer MFA or short-lived tokens for administrators.
3. Authorization and Access Control
3.1 What Is Authorization?
Authorization answers: "What are you allowed to do?" After a user is authenticated, the database decides which objects (tables, views, procedures) they may access and which operations (read, insert, update, delete, create) they may perform.
Authentication -> "You are Riya."
Authorization -> "Riya may read the Orders table but not change it."
3.2 What Is Access Control?
Access control is the set of rules and mechanisms that enforce authorization. It restricts access to data and operations based on defined policies.
3.3 Main Access Control Models
| Model | Who decides access? | Idea |
|---|---|---|
| Discretionary Access Control (DAC) | The owner of an object | Owners grant or revoke privileges to others at their discretion. Used by SQL GRANT/REVOKE. Flexible, but depends on owners behaving well. |
| Mandatory Access Control (MAC) | A central authority / system policy | Data and users get security labels (for example Top Secret, Secret, Confidential). Access depends on comparing labels. Very strict; used in military/government settings. |
| Role-Based Access Control (RBAC) | Roles representing job functions | Permissions are given to roles; users are assigned roles. (Covered in section 6.) |
| Attribute-Based Access Control (ABAC) | Attributes and rules (user, resource, time, location) | Policies like "managers may view records of their own department during office hours." |
3.4 Levels at Which Access Can Be Controlled
- Database/instance level: who can connect and administer.
- Schema level: who can see or create objects in a schema.
- Object level: table, view, procedure, sequence.
- Column level: specific columns (for example hide a
salarycolumn). - Row level: specific rows (for example a salesperson sees only their own customers). Many databases support this as row-level security or policies.
3.5 Using Views for Access Control
A view is a saved query that behaves like a virtual table. You can give users access to a view that shows only certain columns or rows, without giving access to the underlying table.
CREATE VIEW employee_public AS
SELECT emp_id, name, department
FROM employee; -- salary column is hidden
GRANT SELECT ON employee_public TO hr_intern;
The intern can see names and departments but never the salaries.
3.6 Types of Privileges at a High Level
- System (administrative) privileges: actions affecting the whole database, such as creating users or tables.
- Object privileges: actions on a specific object, such as
SELECTon a table.
4. Users, Roles and Privileges
These are the three basic building blocks of database authorization.
4.1 Users
A user (account) is an identity that can connect to the database. It can represent a person or an application/service.
CREATE USER riya IDENTIFIED BY 'StrongPassw0rd!'; -- syntax varies by DBMS
Every user typically has: a name, an authentication method, and a set of privileges (directly or via roles).
4.2 Privileges
A privilege is a permission to perform a specific action.
| Type | Examples |
|---|---|
| Object privileges | SELECT, INSERT, UPDATE, DELETE, REFERENCES, EXECUTE on a particular object |
| System privileges | CREATE TABLE, CREATE USER, ALTER ANY TABLE, BACKUP DATABASE |
4.3 Roles
A role is a named collection of privileges. Instead of giving privileges to every user one by one, you give them to a role and then assign the role to users.
CREATE ROLE sales_analyst;
GRANT SELECT ON orders TO sales_analyst;
GRANT SELECT ON customers TO sales_analyst;
GRANT sales_analyst TO riya;
GRANT sales_analyst TO arjun;
4.4 How They Fit Together
+--------+ assigned +--------+ contains +------------+
| User | -----------------> | Role | ---------------> | Privileges |
+--------+ +--------+ +------------+
(can also hold privileges directly, but roles are tidier)
4.5 Why Roles Are Better Than Direct Privileges
| Direct privileges per user | Roles |
|---|---|
| Hard to manage with many users | Manage permissions in one place |
| Easy to forget something when someone joins/leaves | Add or remove one role |
| Inconsistent access between similar users | Consistent access for the same job |
| Difficult to audit | Easy to review "what can this role do?" |
4.6 Special Accounts
- Administrator accounts (for example
root,sa,sys,postgres): extremely powerful. Protect heavily, use rarely, and never use them for normal application work. - Application accounts (service accounts): used by programs; should have only the permissions the application needs.
- Default / sample accounts: should be changed, locked, or removed.
5. GRANT and REVOKE
These two SQL statements are how privileges are given and taken away. (They are part of SQL's Data Control Language, DCL.)
5.1 GRANT
Syntax
GRANT privilege_list
ON object
TO user_or_role [, ...]
[WITH GRANT OPTION];
Examples
-- Allow reading and inserting on one table
GRANT SELECT, INSERT ON employee TO riya;
-- Allow updating only certain columns
GRANT UPDATE (phone, address) ON employee TO riya;
-- Allow everything on a table (use with caution)
GRANT ALL PRIVILEGES ON employee TO admin_user;
-- Give a role to a user
GRANT sales_analyst TO arjun;
5.2 WITH GRANT OPTION
If a privilege is granted WITH GRANT OPTION, the receiver may in turn grant that same privilege to others.
GRANT SELECT ON employee TO riya WITH GRANT OPTION;
-- riya can now do: GRANT SELECT ON employee TO arjun;
This is powerful but risky, because control spreads. Use sparingly.
(Some systems use WITH ADMIN OPTION for roles and system privileges, with a similar idea.)
5.3 REVOKE
Syntax
REVOKE privilege_list
ON object
FROM user_or_role [, ...]
[CASCADE | RESTRICT];
Examples
REVOKE INSERT ON employee FROM riya;
REVOKE ALL PRIVILEGES ON employee FROM riya;
REVOKE sales_analyst FROM arjun;
5.4 CASCADE vs RESTRICT
When a privilege was passed on using GRANT OPTION, removing it from the first person raises a question about everyone they granted it to.
- CASCADE: also revokes the privilege from users who received it through that person.
- RESTRICT: refuses to revoke if other users depend on it.
5.5 DENY (in Some Systems)
Some systems (for example SQL Server) also have DENY, which explicitly blocks a permission. A denial usually overrides a grant. This differs from REVOKE, which simply removes a previously given permission.
5.6 Privilege Rules to Remember
- Only users with the right (the object owner or someone holding the privilege with grant option) can grant it.
- The object's owner has all privileges on it by default.
- Privileges can be column-level as well as table-level.
- Changes made with
GRANT/REVOKEtypically take effect for new actions soon after (details vary by DBMS).
5.7 Quick Example Scenario
CREATE ROLE report_reader;
GRANT SELECT ON sales TO report_reader;
GRANT report_reader TO priya;
-- Priya leaves the reporting team
REVOKE report_reader FROM priya;
One line takes away all that access.
6. Role-Based Access Control (RBAC)
6.1 The Idea
In RBAC, access is based on job function, not on individuals.
- Define roles that match jobs (Cashier, Manager, Auditor, Developer).
- Give each role only the privileges that job needs.
- Assign users to roles.
When a person changes jobs, you change their role, not dozens of individual permissions.
6.2 The Core Components
| Component | Meaning |
|---|---|
| Users | People or applications |
| Roles | Job functions |
| Permissions | Rights on objects (read, write, execute...) |
| Sessions | A user activating one or more of their roles when connected |
6.3 Example
| Role | Permissions |
|---|---|
cashier | SELECT, INSERT on sales |
store_manager | All of cashier + UPDATE, DELETE on sales, SELECT on staff |
auditor | SELECT only on all tables |
dba | Administrative rights |
CREATE ROLE cashier;
CREATE ROLE store_manager;
GRANT SELECT, INSERT ON sales TO cashier;
GRANT cashier TO store_manager; -- role hierarchy: manager inherits cashier
GRANT UPDATE, DELETE ON sales TO store_manager;
GRANT SELECT ON staff TO store_manager;
GRANT cashier TO ravi;
GRANT store_manager TO meena;
6.4 Role Hierarchies
Roles can inherit from other roles. A senior role includes all permissions of junior roles plus extra ones (as above, store_manager includes cashier). This avoids repeating permissions.
6.5 Separation of Duties (SoD)
RBAC helps enforce separation of duties: no single person should be able to complete a sensitive process alone. For example, the person who creates a payment should not be the one who approves it. This is done by making certain roles mutually exclusive.
6.6 Benefits of RBAC
- Simpler administration: manage a few roles instead of many users.
- Consistency: people in the same job get the same access.
- Faster onboarding and offboarding: assign or remove roles.
- Supports least privilege and separation of duties.
- Easier auditing: you can review what each role can do.
6.7 Possible Problems
- Role explosion: too many highly specific roles become hard to manage.
- Role creep: users collect roles over time and keep access they no longer need.
- Roles must be reviewed regularly to remain accurate.
6.8 RBAC vs DAC (Quick Contrast)
| DAC | RBAC | |
|---|---|---|
| Who controls access | Object owner | Administrators via roles |
| Based on | Individual grants | Job functions |
| Scalability | Becomes messy with many users | Scales well |
7. Least-Privilege Principle
7.1 Definition
Principle of Least Privilege (PoLP): Every user, program, and process should have only the minimum privileges needed to do its job, and only for as long as needed.
7.2 Why It Matters
- Limits the damage if an account is stolen or misused.
- Limits the damage if an application is attacked (for example by SQL injection): the attacker inherits only that account's small set of rights.
- Reduces accidental mistakes (a user cannot delete what they cannot touch).
- Makes auditing and compliance easier.
7.3 Example
A web application only needs to read and add orders.
| Bad (over-privileged) | Good (least privilege) |
|---|---|
App connects as root/sa/admin | App connects as app_user |
| Can drop tables, create users, read everything | Can only SELECT/INSERT on the needed tables |
| One SQL injection could destroy the database | Injection damage is heavily limited |
CREATE USER app_user IDENTIFIED BY '...';
GRANT SELECT, INSERT ON orders TO app_user;
GRANT SELECT ON products TO app_user;
-- no DELETE, no DROP, no access to other tables
7.4 How to Apply It
- Start with no access and add only what is required ("default deny").
- Give column-level or view-based access instead of full-table access where possible.
- Use separate accounts for different tasks (for example an admin uses a normal account day-to-day and an admin account only when needed).
- Use time-limited privileges where possible (temporary access that expires).
- Review privileges regularly, removing what is no longer needed.
- Avoid granting powerful options such as
ALL PRIVILEGES,WITH GRANT OPTION, orPUBLICaccess without strong reason. - Revoke access immediately when people change roles or leave.
7.5 Related Ideas
- Need-to-know: users get access to data only if their work requires it.
- Separation of duties: splits sensitive tasks across people.
- Default deny: anything not explicitly allowed is forbidden.
8. SQL Injection
8.1 What Is It?
SQL injection (SQLi) is an attack where an attacker inserts malicious SQL code into an input (a form field, URL parameter, header, cookie), and the application mixes that input directly into a SQL query. The database then runs the attacker's code as if it were part of the legitimate query.
The root cause: user input is treated as code instead of as data.
8.2 A Simple Example
Vulnerable code that builds a query by joining strings:
query = "SELECT * FROM users WHERE username = '" + name + "' AND password = '" + pwd + "'"
If the attacker types this as the username:
' OR '1'='1' --
the final query becomes:
SELECT * FROM users WHERE username = '' OR '1'='1' --' AND password = ''
'1'='1'is always true.--starts a comment, so the password check is ignored.- Result: the attacker logs in without a valid password.
8.3 What Attackers Can Do
- Bypass login (as above)
- Read sensitive data they should not see
- Modify or delete data (
UPDATE,DELETE,DROP TABLE) - Gain admin control of the database
- In some configurations, run commands on the server
8.4 Types of SQL Injection
| Type | How it works |
|---|---|
| In-band (classic) | Attacker sees results directly in the application's response |
| Error-based | Attacker uses error messages that reveal information about the database |
| UNION-based | Attacker uses UNION SELECT to append results from other tables to the legitimate result |
| Blind (inferential) | No data or errors are shown; attacker learns by observing behaviour |
| Boolean-based blind | Asks true/false questions and watches whether the page changes |
| Time-based blind | Asks questions that cause a delay if true, and measures response time |
| Out-of-band | Data is sent to the attacker through another channel (for example a network request) |
| Second-order | Malicious input is stored safely first, then used unsafely later in another query |
8.5 Example: UNION-Based Idea
/products?id=5 UNION SELECT username, password FROM users
If the application builds the query by simply appending the input, the attacker can pull data from a completely different table.
8.6 Why It Happens
- Building SQL by string concatenation with user input
- Trusting input without validation
- Showing detailed database errors to users
- Applications connecting with excessive privileges
8.7 How to Defend (Overview)
- Parameterized queries / prepared statements (the primary defence; see section 9)
- Stored procedures, written safely, without building dynamic SQL from raw input
- Input validation using allow-lists (type, length, format, range)
- Escaping input only as a last resort when parameters cannot be used
- Least-privilege database accounts, so damage is limited
- Hide detailed error messages from users; log them privately
- Web application firewalls (WAF) and security testing as extra layers
- Regular security testing and code review
Validation and escaping help, but they are not a replacement for parameterized queries.
9. Parameterized Queries
9.1 What Are They?
A parameterized query (also called a prepared statement) separates the SQL code from the data. The SQL structure is written first with placeholders, and the actual values are supplied separately.
Because the database already knows what is code and what is data, anything the user types is treated only as a value, never as SQL commands.
9.2 How It Works
- The application sends the query template with placeholders (
?,%s,:name,@name,$1, depending on the language/DBMS). - The database parses and plans that structure.
- The application sends the values separately.
- The database plugs values into the placeholders as data only.
SQL template: SELECT * FROM users WHERE username = ? AND password = ?
Values: ["' OR '1'='1' --", "anything"]
|
v
The whole text ' OR '1'='1' -- is treated as ONE literal username.
No match found, so no login. The attack fails.
9.3 Code Examples
Python (DB-API style)
cursor.execute(
"SELECT * FROM users WHERE username = %s AND password_hash = %s",
(username, password_hash)
)
Java (JDBC PreparedStatement)
PreparedStatement ps = conn.prepareStatement(
"SELECT * FROM users WHERE username = ? AND password_hash = ?");
ps.setString(1, username);
ps.setString(2, passwordHash);
ResultSet rs = ps.executeQuery();
PHP (PDO)
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :u");
$stmt->execute([':u' => $username]);
SQL-level prepared statement (concept)
PREPARE find_user (text) AS
SELECT * FROM users WHERE username = $1;
EXECUTE find_user('riya');
9.4 Unsafe vs Safe at a Glance
| Unsafe (string concatenation) | Safe (parameterized) |
|---|---|
"... WHERE name = '" + input + "'" | "... WHERE name = ?" with input as a parameter |
| Input can change the query's meaning | Input can only be a value |
| Vulnerable to SQL injection | Protects against SQL injection |
9.5 Extra Benefits
- Performance: the database can reuse a parsed/planned statement when executing it many times with different values.
- Cleaner code: no manual quoting or escaping of values.
- Fewer bugs: data types are handled properly.
9.6 Important Limitations and Tips
- Placeholders work for values only. They generally cannot be used for table names, column names, or keywords (like
ASC/DESC). If these must be dynamic, choose from a fixed allow-list in code; never insert raw user text. - Using parameters is not safe if you still concatenate user input into the template string.
- ORMs (object-relational mappers) usually use parameters internally, but raw-query features can still be unsafe if concatenated.
- Stored procedures are safe only if they do not build dynamic SQL from unsanitised input inside.
- Combine with validation and least privilege for defence in depth.
10. Encryption at Rest and in Transit
Encryption converts readable data (plaintext) into unreadable data (ciphertext) using an algorithm and a key. Only someone with the correct key can turn it back. Even if data is stolen, it is useless without the key.
Data exists in different states, and each needs protection:
| State | Protection |
|---|---|
| At rest (stored on disk, backups) | Encryption at rest |
| In transit (moving across a network) | Encryption in transit |
| In use (being processed in memory) | Harder to protect; covered by access control and specialised techniques |
10.1 Types of Encryption (Background)
- Symmetric encryption: the same key encrypts and decrypts (for example AES). Fast, used for large amounts of data.
- Asymmetric encryption: a public key encrypts and a private key decrypts (for example RSA). Slower, used for secure key exchange and identity verification.
- In practice, systems combine both: asymmetric to exchange a secret safely, symmetric to encrypt the bulk of the data.
10.2 Encryption at Rest
Goal: protect stored data if someone gets hold of the physical disk, files, or backups.
Levels at which it can be applied
| Level | Description | Pros | Cons |
|---|---|---|---|
| Disk / volume encryption | Whole disk or file system is encrypted (for example BitLocker, LUKS, cloud disk encryption) | Easy, covers everything | Does not protect against someone who has logged into the running system |
| Transparent Data Encryption (TDE) | Database encrypts its data files, logs, and backups automatically; the application sees normal data | No application changes; protects files on disk | Data is decrypted for anyone who can query it successfully |
| Column / field-level encryption | Only sensitive columns (for example card numbers) are encrypted | Fine-grained, stronger protection of key fields | More effort; can limit searching/indexing |
| Application-level encryption | Application encrypts data before sending it to the database | Database admins cannot read it | Complex; harder to search or sort |
Typical design: key hierarchy
Master key (protected in a key manager / HSM)
|
+--> encrypts --> Data encryption keys (DEKs)
|
+--> encrypt --> actual data
10.3 Encryption in Transit
Goal: stop attackers from reading or tampering with data as it travels between the application and database (or between database servers, such as for replication).
- Use TLS (Transport Layer Security, the successor to SSL) for database connections.
- Configure the server to require encrypted connections, and clients to verify the server's certificate (prevents "man-in-the-middle" attacks).
- Use modern TLS versions and strong cipher suites; disable old, weak ones.
- Also encrypt traffic for replication links, backup transfers, and admin tools.
- VPNs or private networks can add protection but do not replace TLS.
10.4 Key Management (Critical!)
Encryption is only as strong as the care taken of its keys.
- Never store keys next to the data they protect.
- Use a dedicated key management system (KMS) or hardware security module (HSM).
- Rotate keys regularly and when compromise is suspected.
- Limit who can access keys, and log key usage.
- Have a secure key backup; losing the key means permanently losing the data.
10.5 At Rest vs In Transit
| At rest | In transit | |
|---|---|---|
| Protects | Stored data (files, disks, backups) | Data moving over a network |
| Typical tech | TDE, disk encryption, column encryption | TLS/SSL |
| Threat prevented | Stolen disks, leaked files and backups | Eavesdropping, tampering, man-in-the-middle |
You need both. Encrypting only one leaves the other open.
10.6 Things to Remember
- Encryption does not replace access control; a user with valid permissions still sees decrypted data.
- Encryption can add some performance overhead.
- Hashing is different from encryption: hashing is one-way (used for passwords), encryption is reversible with a key.
11. Data Masking and Anonymization
Sometimes people need to use data but should not see the real sensitive values: developers testing an application, analysts studying trends, support staff viewing a customer. These techniques hide or alter sensitive information while keeping the data usable.
11.1 Data Masking
Masking replaces sensitive data with fictional but realistic-looking data, or hides parts of it.
Example
| Original | Masked |
|---|---|
4111 1111 1111 1234 | XXXX XXXX XXXX 1234 |
riya.sharma@mail.com | r***@mail.com |
9876543210 | 98XXXXXX10 |
Riya Sharma | Anita Verma (substituted) |
11.2 Types of Masking
| Type | Description |
|---|---|
| Static masking | A copy of the database is masked permanently, then used for testing/development. The original stays untouched. |
| Dynamic masking | Data is masked on the fly when queried, based on who is asking. Stored data is unchanged; privileged users see real values, others see masked values. |
| On-the-fly masking | Data is masked while moving from one environment to another (for example production to test). |
11.3 Common Masking Techniques
- Substitution: replace with realistic fake values (names, addresses)
- Shuffling: rearrange real values among rows so they no longer match their owners
- Redaction / partial masking: hide some characters (
XXXX1234) - Nulling out: replace with
NULLor blank - Number/date variance: shift numbers or dates by a random amount
- Tokenization: replace a value with a random token; the real value is kept in a separate secure vault
- Format-preserving masking: keeps the same format (a masked phone number still looks like a phone number)
11.4 Example: Dynamic Masking Concept
-- Concept (syntax varies by DBMS): mask email for normal users
ALTER TABLE customer
ALTER COLUMN email ADD MASKED WITH (FUNCTION = 'email()');
A support agent running SELECT email FROM customer sees rXXX@XXXX.com, while a user with unmask permission sees the real value.
11.5 Anonymization
Anonymization permanently transforms data so that individuals can no longer be identified, directly or indirectly. Once done properly, the result is no longer considered personal data.
- Removes or alters direct identifiers (name, ID number, phone, email)
- Also handles quasi-identifiers (age, postal code, gender), which in combination could still re-identify someone
11.6 Anonymization Techniques
| Technique | Idea |
|---|---|
| Generalization | Replace exact values with ranges (age 34 becomes 30-40; full postcode becomes first digits) |
| Suppression | Remove certain values or records completely |
| Perturbation / noise | Add small random changes so individual values are unreliable but overall statistics stay accurate |
| Aggregation | Share only summaries (averages, counts), not individual records |
| k-anonymity | Make each record indistinguishable from at least k β 1 other records on identifying attributes |
| Differential privacy | Add carefully calibrated noise so the presence or absence of one person barely changes results |
11.7 Pseudonymization (Related, But Different)
Pseudonymization replaces identifiers with pseudonyms (for example Customer_8421), while the mapping back to real identities is kept separately. It is reversible if you have the mapping, so it is not full anonymization, but it reduces risk.
11.8 Masking vs Anonymization vs Encryption
| Reversible? | Main purpose | |
|---|---|---|
| Encryption | Yes, with the key | Protect real data, which is still needed in its true form |
| Masking | Usually no (static) or hidden (dynamic) | Give safe, usable data to those who do not need real values |
| Anonymization | No | Remove the ability to identify individuals, often for analysis or sharing |
| Pseudonymization | Yes, via separate mapping | Reduce exposure while keeping the ability to link records internally |
11.9 Good Practices
- Mask data before it goes to development, testing, or training environments.
- Maintain referential integrity (a masked customer ID should still match across tables).
- Keep the masking rules and mapping tables secure.
- Test whether anonymized data can still be re-identified by combining datasets.
12. Auditing and Activity Logging
12.1 What Is It?
Auditing is the practice of recording and reviewing database activity so you can answer: who did what, when, from where, and was it successful?
It supports accountability, detection of suspicious behaviour, investigation after incidents, and compliance with regulations.
12.2 What Should Be Logged?
| Category | Examples |
|---|---|
| Authentication events | Successful and failed logins, logouts, account lockouts |
| Privilege and account changes | CREATE USER, GRANT, REVOKE, role changes, password changes |
| Schema changes (DDL) | CREATE, ALTER, DROP |
| Data access and changes (DML) | SELECT on sensitive tables, INSERT, UPDATE, DELETE |
| Administrative actions | Backups, restores, configuration changes, starting/stopping the database |
| Errors and failures | Permission denied, failed statements |
12.3 Details to Capture in Each Record
- Who: user name, application, role
- When: exact date and time (with time zone)
- Where: client IP address, host, program
- What: the statement or action, the object affected
- Result: success or failure
- Optionally old and new values for sensitive changes
12.4 Types of Auditing
| Type | Description |
|---|---|
| Statement auditing | Tracks particular types of SQL statements |
| Privilege auditing | Tracks use of specific privileges |
| Object auditing | Tracks access to specific tables or objects |
| Fine-grained auditing | Tracks access based on conditions (for example only access to rows above a salary value) |
| Trigger-based auditing | Custom triggers write changes into an audit table |
| Built-in / native auditing | Features provided by the DBMS itself |
12.5 Example: Trigger-Based Audit Table
CREATE TABLE salary_audit (
audit_id INT PRIMARY KEY AUTO_INCREMENT,
emp_id INT,
old_salary DECIMAL(10,2),
new_salary DECIMAL(10,2),
changed_by VARCHAR(50),
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TRIGGER trg_salary_audit
AFTER UPDATE ON employee
FOR EACH ROW
INSERT INTO salary_audit (emp_id, old_salary, new_salary, changed_by)
VALUES (OLD.emp_id, OLD.salary, NEW.salary, CURRENT_USER());
Every salary change now leaves a permanent trail.
12.6 Protecting the Logs
Audit logs are a target too: attackers may try to erase their tracks.
- Store logs separately from the database being audited, ideally on a different, secured system.
- Make logs tamper-resistant (write-once storage, restricted permissions, integrity checks or hashing).
- Restrict who can read or modify them; DBAs should not be able to silently alter their own audit trail.
- Retain logs for the period required by policy or regulation.
- Make sure logs do not contain secrets such as plain-text passwords.
12.7 Monitoring and Alerting
Logging is useful only if someone looks at it.
- Review logs regularly and automate alerts for suspicious events, such as:
- Many failed logins, or a login from an unusual place/time
- Sudden large data exports
- Unexpected privilege escalation
- Access to sensitive tables by unusual users
- Schema changes outside maintenance windows
- Feed logs into a central monitoring system (such as a SIEM) for correlation.
- Some organisations use database activity monitoring (DAM) tools for real-time inspection.
12.8 Balancing Detail and Performance
Auditing everything can slow the system and create huge logs. Focus on sensitive data, privileged users, and critical actions, and tune the scope.
12.9 Why It Matters for Compliance
Many laws and standards require evidence of who accessed personal, financial, or health data. Audit logs provide this proof.
13. Backup Security
Backups are full copies of your data. If they are not as well protected as the live database, attackers can simply steal the backup instead of attacking the database.
13.1 Why Backup Security Matters
- A stolen backup gives an attacker everything, often with no login needed.
- Backups often sit in less-monitored places (shared drives, tapes, cloud storage).
- Backups are a prime target for ransomware: attackers delete or encrypt backups so you cannot recover.
- Old backups may hold data that was supposed to be deleted.
13.2 Key Protections
| Area | What to do |
|---|---|
| Encryption | Encrypt backups at rest (storage) and in transit (while being copied). Protect and separately store the encryption keys. |
| Access control | Only authorised people and processes may create, read, move, or delete backups. Apply least privilege to backup accounts. |
| Secure storage location | Keep backups in secured locations; for physical media use locked storage; for cloud use private, access-controlled storage. |
| Off-site / separate copy | Keep at least one copy away from the main system, so a single incident cannot destroy both. |
| Immutability | Use write-once or immutable (cannot be changed or deleted for a set time) backups to defend against ransomware and insider deletion. |
| Air-gapped / offline copies | A copy disconnected from the network cannot be reached by network-based attacks. |
| Integrity checks | Use checksums or signatures to detect corruption or tampering. |
| Test restores | Regularly restore backups to confirm they work (and that encryption keys are available). |
| Retention and disposal | Keep backups only as long as needed; securely delete or destroy expired backups and old media. |
| Auditing | Log who accessed, created, restored, or deleted backups. |
| Protect test copies | If a backup is restored into test environments, apply masking so real data is not exposed. |
13.3 The 3-2-1 Rule (with Security in Mind)
- 3 copies of the data
- 2 different storage types
- 1 copy off-site (and ideally one immutable or offline)
13.4 Common Mistakes
- Unencrypted backups left on shared folders or cloud buckets that are publicly accessible
- Storing encryption keys with the backup
- Backup accounts with administrator-level rights everywhere
- Never testing restores
- Forgetting about old backups and decommissioned media
- Leaving backup credentials in scripts
13.5 Quick Checklist
- Backups encrypted (at rest and in transit)
- Keys stored separately and securely
- Access restricted and logged
- Off-site and immutable/offline copy exists
- Restores tested regularly
- Retention and secure disposal rules defined
14. Database Security Best Practices
A consolidated checklist that ties together everything above.
14.1 Identity and Access
- Use strong authentication; enable MFA for administrators.
- Change default passwords and disable or remove unused or sample accounts.
- Give each person and application its own account; avoid shared logins.
- Apply RBAC and the least-privilege principle.
- Limit use of powerful options:
ALL PRIVILEGES,WITH GRANT OPTION, and access forPUBLIC. - Review user access periodically, and remove access promptly when roles change or people leave.
- Enforce separation of duties for sensitive tasks.
14.2 Application Security
- Use parameterized queries / prepared statements everywhere.
- Validate input with allow-lists.
- Do not show raw database errors to end users.
- Applications should connect with low-privilege accounts, never as the administrator.
- Keep credentials out of source code; use a secrets manager.
14.3 Data Protection
- Encrypt data at rest (TDE, column-level encryption for highly sensitive fields) and in transit (TLS).
- Manage keys securely with a KMS/HSM, and rotate them.
- Use masking and anonymization for non-production and analytics use.
- Collect and store only the data you really need, and delete it when no longer required.
14.4 Monitoring and Response
- Enable auditing and activity logging for logins, privilege changes, schema changes, and sensitive data access.
- Protect and retain logs, and send them to a separate, secure location.
- Set up alerts for suspicious activity.
- Have an incident response plan: know how to detect, contain, investigate, and recover.
14.5 System and Network Hardening
- Patch the database software and operating system regularly.
- Install and enable only the features and services you need (reduce the attack surface).
- Place databases in a private network segment; do not expose them directly to the internet.
- Restrict access with firewalls and allow-lists of trusted hosts/IP addresses.
- Change default ports where sensible and secure configuration files.
- Run the database service under a dedicated low-privilege OS account.
- Follow a secure configuration baseline (hardening guidelines).
14.6 Backup and Recovery
- Keep encrypted, access-controlled, off-site, and immutable backups.
- Test restores regularly.
- Securely dispose of old backups.
14.7 People and Process
- Provide security awareness training for developers, administrators, and users.
- Document security policies and keep them up to date.
- Run vulnerability scans and penetration tests periodically.
- Meet relevant compliance requirements (for example regulations on personal, financial, or health data).
- Apply defence in depth: never rely on a single control.
14.8 Summary Table of Threats and Controls
| Threat | Main controls |
|---|---|
| Stolen or guessed passwords | Strong passwords, MFA, lockout, salted hashing |
| Users with too much access | RBAC, least privilege, regular access reviews |
| SQL injection | Parameterized queries, validation, least-privilege accounts |
| Network eavesdropping | TLS encryption in transit |
| Stolen disks or files | Encryption at rest |
| Exposure in test/analytics | Masking and anonymization |
| Undetected misuse | Auditing, logging, alerting |
| Stolen or destroyed backups | Encrypted, immutable, off-site, access-controlled backups |
15. Quick Revision Cheat Sheet
Key Definitions
| Term | One-line meaning |
|---|---|
| Authentication | Proving who you are |
| Authorization | Deciding what you may do |
| Access control | Rules and mechanisms enforcing authorization |
| DAC / MAC / RBAC / ABAC | Owner-based / label-based / role-based / attribute-based access models |
| User | An identity (person or application) that connects to the database |
| Privilege | Permission to perform an action |
| Role | Named bundle of privileges assigned to users |
| GRANT | Give a privilege or role |
| REVOKE | Take a privilege or role away |
| WITH GRANT OPTION | Receiver may pass the privilege on |
| Least privilege | Give only the minimum access needed |
| SQL injection | Attacker input is executed as part of a SQL query |
| Parameterized query | SQL template + separate values; input is only data |
| Encryption at rest | Protect stored data (TDE, disk, column encryption) |
| Encryption in transit | Protect data on the network (TLS) |
| Masking | Hide or replace sensitive values with safe ones |
| Anonymization | Irreversibly remove ability to identify individuals |
| Pseudonymization | Replace identifiers with reversible pseudonyms |
| Audit log | Record of who did what, when, and from where |
| Immutable backup | Backup that cannot be altered or deleted for a set time |
Remember These Pairs
- Authentication = who are you? | Authorization = what can you do?
- Encryption = reversible with key | Hashing = one-way
- Masking = hide for usability | Anonymization = cannot identify anyone
- GRANT = give | REVOKE = remove
- Static masking = permanent copy | Dynamic masking = on-the-fly by user
Commonly Asked Exam Points
- Why is a role better than granting privileges directly? Easier management, consistency, and auditing.
- What is the primary defence against SQL injection? Parameterized queries (prepared statements).
- Why should an application not connect as admin? Least privilege limits damage if it is attacked.
- What does
WITH GRANT OPTIONdo, and why is it risky? Lets the receiver pass the privilege on; control spreads. - Difference between CASCADE and RESTRICT in
REVOKE? Cascade also removes dependent grants; restrict refuses if dependencies exist. - Why encrypt both at rest and in transit? They protect different states: stored data and moving data.
- Why store passwords as salted hashes? Cannot be reversed, and salt makes identical passwords differ and defeats precomputed tables.
- Why protect audit logs and backups? They are valuable targets: attackers erase evidence or steal complete copies.
- Placeholders cannot be used for? Table names, column names, and keywords.
#DBMS Interview Preparation and Practice#
Study notes in plain language. The first half is "X vs Y" comparisons (the classic rapid-fire interview questions). The second half is problem-solving practice (queries, schedules, ER diagrams, schema design). Read it once top to bottom, then use the cheat sheet at the end for last-minute revision.
Table of Contents
Part A: Concept Comparisons
- DBMS vs RDBMS
- Primary Key vs Unique Key
- Primary Key vs Foreign Key
- DELETE vs TRUNCATE vs DROP
- WHERE vs HAVING
- UNION vs UNION ALL
- Joins vs Subqueries
- 2NF vs 3NF vs BCNF
- Normalization vs Denormalization
- Clustered vs Non-Clustered Index
- B-Tree vs B+ Tree
Part B: Transactions and Concurrency Questions
Part C: Practice Problems
How to Answer Interview Questions (Short Tip)
For any "X vs Y" question, use this pattern:
- One-line definition of each.
- 2 to 4 key differences (use a table if you are writing).
- A tiny example.
- When to use which.
Interviewers care more about why than about memorised lists.
PART A: CONCEPT COMPARISONS
1. DBMS vs RDBMS
DBMS (Database Management System)
Software that lets you create, store, retrieve, update, and manage data. Data may be stored as files, hierarchies, or networks, with no strict requirement for relationships between data.
RDBMS (Relational DBMS)
A DBMS that stores data in tables (relations) made of rows and columns, and supports relationships between tables using keys. It is based on Codd's relational model and uses SQL.
Comparison
| Feature | DBMS | RDBMS |
|---|---|---|
| Data storage | Files, hierarchical, or network structures | Tables (rows and columns) |
| Relationships between data | Usually not supported or handled by the programmer | Supported via primary and foreign keys |
| Normalization | Not supported | Supported |
| Data integrity | Limited | Strong (constraints, keys) |
| ACID support | Often partial or none | Generally full |
| Multi-user access | Limited, often single-user | Designed for many concurrent users |
| Security | Basic | Advanced (roles, privileges) |
| Query language | Varies | SQL |
| Handling large data | Weak | Handles large volumes well |
| Examples | File system, XML stores, early hierarchical systems | MySQL, PostgreSQL, Oracle, SQL Server |
Codd's Idea in One Line
In an RDBMS, data is organised into relations; each row is unique and identified by a key; relationships are expressed through values (keys), not physical pointers.
Interview Tip
"Every RDBMS is a DBMS, but not every DBMS is an RDBMS."
2. Primary Key vs Unique Key
Both enforce uniqueness, but they have different roles.
Definitions
- Primary key (PK): the column(s) that uniquely identify each row. It is the table's main identifier.
- Unique key (UK): a constraint that makes sure all values in a column (or combination) are distinct, but it is not the main identifier.
Comparison
| Feature | Primary Key | Unique Key |
|---|---|---|
| Purpose | Main identifier of a row | Prevent duplicate values in a column |
| How many per table | Only one | Many allowed |
| NULL values | Not allowed | Allowed (how many NULLs depends on the DBMS) |
| Uniqueness | Yes | Yes |
| Automatically indexed | Yes (often a clustered index in some DBMSs, such as SQL Server and MySQL InnoDB) | Yes (usually non-clustered/unique index) |
| Can be referenced by a foreign key | Yes | Yes (in most systems) |
| Composite allowed | Yes | Yes |
Example
CREATE TABLE student (
student_id INT PRIMARY KEY, -- identifies the row, no NULLs
email VARCHAR(100) UNIQUE, -- must be distinct, may be NULL
phone VARCHAR(15) UNIQUE
);
When to Use Which
- Use the primary key for the identity of the record.
- Use unique keys for other attributes that must also be unique (email, username, passport number).
Related Terms Interviewers Ask
- Candidate key: any minimal set of columns that can uniquely identify a row. One of them is chosen as the primary key.
- Alternate key: candidate keys not chosen as the primary key.
- Super key: any set of columns that uniquely identifies a row (may include extra columns).
- Composite key: a key made of two or more columns.
- Surrogate key: an artificial key (like auto-increment ID) with no business meaning.
- Natural key: a key based on real-world data (like email or SSN).
Super keys β Candidate keys β Primary key (one chosen)
3. Primary Key vs Foreign Key
Definitions
- Primary key: uniquely identifies a row within its own table.
- Foreign key (FK): a column (or set of columns) in one table that refers to the primary key (or a unique key) of another table, creating a link between them.
Comparison
| Feature | Primary Key | Foreign Key |
|---|---|---|
| Purpose | Identify rows uniquely | Link tables and maintain referential integrity |
| Uniqueness | Must be unique | Can have duplicates |
| NULL values | Not allowed | Allowed (unless defined NOT NULL) |
| Per table | Only one | Can have many |
| Refers to | Nothing (it is the source) | The PK/unique key of another (or the same) table |
| Effect | Enforces entity integrity | Enforces referential integrity |
Example
CREATE TABLE department (
dept_id INT PRIMARY KEY,
dept_name VARCHAR(50)
);
CREATE TABLE employee (
emp_id INT PRIMARY KEY,
name VARCHAR(50),
dept_id INT,
FOREIGN KEY (dept_id) REFERENCES department(dept_id)
ON DELETE SET NULL
ON UPDATE CASCADE
);
Here employee.dept_id must match an existing department.dept_id (or be NULL).
Referential Actions (Very Common Question)
What happens to child rows when the parent row is deleted or updated?
| Action | Meaning |
|---|---|
CASCADE | Apply the same change to child rows (delete or update them too) |
SET NULL | Set the child's foreign key to NULL |
SET DEFAULT | Set the child's foreign key to its default value |
RESTRICT / NO ACTION | Block the change if child rows exist |
Key Points
- A foreign key can reference the same table (self-reference), such as
manager_idpointing toemp_id. - The foreign key and the referenced key must have compatible data types.
- Foreign keys protect against orphan records (child rows with no parent).
4. DELETE vs TRUNCATE vs DROP
All three remove data in some way, but they work at different levels.
Definitions
- DELETE: removes selected rows (or all rows) from a table, one by one. It is DML.
- TRUNCATE: removes all rows quickly, keeping the table structure. It is DDL (in most systems).
- DROP: removes the entire table (data, structure, indexes, constraints). It is DDL.
Comparison
| Feature | DELETE | TRUNCATE | DROP |
|---|---|---|---|
| Category | DML | DDL | DDL |
| What it removes | Chosen rows or all rows | All rows | Entire table object |
WHERE clause | Yes | No | No |
| Table structure after | Stays | Stays | Gone |
| Speed | Slower (row-by-row, logged per row) | Fast (deallocates pages, minimal logging) | Fast |
| Rollback | Yes (inside a transaction) | Depends on DBMS (see note) | Depends on DBMS; usually cannot be rolled back easily |
| Triggers fire | Yes (row-level delete triggers) | No | No |
| Identity / auto-increment counter | Not reset | Usually reset | Gone with the table |
| Locks | Row-level locks | Table-level lock | Table-level |
| Can be used with referenced (FK) tables | Yes (subject to constraints) | Often blocked if referenced by a foreign key | Blocked if referenced (unless constraints dropped or CASCADE) |
Note on rollback: In MySQL and Oracle,
TRUNCATEcauses an implicit commit and cannot be rolled back. In PostgreSQL and SQL Server,TRUNCATEcan be rolled back inside a transaction. Always mention that behaviour varies.
Example
DELETE FROM orders WHERE order_date < '2023-01-01'; -- some rows
DELETE FROM orders; -- all rows, slow, keeps structure
TRUNCATE TABLE orders; -- all rows, fast, keeps structure
DROP TABLE orders; -- table gone completely
One-Line Memory Aid
- DELETE = "remove these rows."
- TRUNCATE = "empty the table."
- DROP = "remove the table itself."
5. WHERE vs HAVING
Both filter data, but at different stages of the query.
Definitions
- WHERE: filters individual rows before grouping.
- HAVING: filters groups after
GROUP BYand aggregation.
Comparison
| Feature | WHERE | HAVING |
|---|---|---|
| Filters | Rows | Groups |
| Applied | Before GROUP BY | After GROUP BY |
Aggregate functions (SUM, COUNT, ...) | Cannot be used | Can be used |
Can be used without GROUP BY | Yes | Rarely (treats whole result as one group) |
| Performance | Reduces rows early (usually faster) | Works on already-grouped data |
Example
Find departments with an average salary above 50000, considering only employees hired after 2020:
SELECT dept_id, AVG(salary) AS avg_sal
FROM employee
WHERE hire_date > '2020-12-31' -- filters rows first
GROUP BY dept_id
HAVING AVG(salary) > 50000; -- filters groups afterwards
Logical Order of Query Execution (Important!)
1. FROM / JOIN (get the tables)
2. WHERE (filter rows)
3. GROUP BY (form groups)
4. HAVING (filter groups)
5. SELECT (choose columns, compute expressions)
6. DISTINCT
7. ORDER BY (sort)
8. LIMIT / OFFSET / TOP
This explains why you cannot use a column alias from SELECT inside WHERE (WHERE runs before SELECT).
Best Practice
Put conditions that do not need aggregates in WHERE, not in HAVING, so fewer rows get grouped.
6. UNION vs UNION ALL
Both combine the results of two or more SELECT queries into one result set.
Rules for Both
- Each query must return the same number of columns.
- Corresponding columns must have compatible data types.
- Column names come from the first query.
Comparison
| Feature | UNION | UNION ALL |
|---|---|---|
| Duplicates | Removed | Kept |
| Sorting / dedup work | Extra work (sort or hash to remove duplicates) | None |
| Speed | Slower | Faster |
| Result size | Smaller or equal | Larger or equal |
Example
-- Table A names: Riya, Arjun, Riya Table B names: Arjun, Meena
SELECT name FROM a
UNION
SELECT name FROM b;
-- Result: Riya, Arjun, Meena (duplicates removed)
SELECT name FROM a
UNION ALL
SELECT name FROM b;
-- Result: Riya, Arjun, Riya, Arjun, Meena (everything kept)
Related Set Operators
| Operator | Returns |
|---|---|
UNION | All distinct rows from both |
UNION ALL | All rows from both |
INTERSECT | Rows present in both |
EXCEPT / MINUS | Rows in the first but not in the second |
When to Use Which
- Use UNION ALL when you know there are no duplicates, or duplicates are fine. It is faster.
- Use UNION only when you actually need duplicates removed.
7. Joins vs Subqueries
Both let you use data from more than one table or query. They are often interchangeable, but not always.
Joins (Recap)
A join combines rows from two tables based on a related column.
| Join | Returns |
|---|---|
INNER JOIN | Only matching rows from both |
LEFT (OUTER) JOIN | All rows from left + matches from right (NULL if none) |
RIGHT (OUTER) JOIN | All rows from right + matches from left |
FULL (OUTER) JOIN | All rows from both; NULL where no match |
CROSS JOIN | Every row of A paired with every row of B (Cartesian product) |
SELF JOIN | A table joined with itself |
Subqueries (Recap)
A subquery is a query nested inside another query.
| Type | Description |
|---|---|
| Scalar subquery | Returns a single value |
| Row / column subquery | Returns one row or one column |
| Table (derived) subquery | Returns a table, used in FROM |
| Correlated subquery | References columns of the outer query; runs once per outer row (conceptually) |
| Non-correlated subquery | Independent; runs once |
Comparison
| Feature | Join | Subquery |
|---|---|---|
| Purpose | Combine columns from multiple tables | Use result of one query as input to another |
| Columns from both tables in output | Yes | Only from the outer query (unless used in FROM) |
| Readability | Good for combining data | Good for stepwise logic ("find X, then use it") |
| Performance | Usually efficient (optimizers handle joins well) | Correlated subqueries can be slow; modern optimizers often rewrite them as joins |
| Use for existence checks | Possible | EXISTS / IN is natural |
| Use for "not present in" | LEFT JOIN ... IS NULL | NOT EXISTS / NOT IN |
Same Question, Two Ways
"Employees who work in the 'Sales' department."
-- Join
SELECT e.name
FROM employee e
JOIN department d ON e.dept_id = d.dept_id
WHERE d.dept_name = 'Sales';
-- Subquery
SELECT name
FROM employee
WHERE dept_id IN (SELECT dept_id FROM department WHERE dept_name = 'Sales');
Cases Where a Subquery Is More Natural
-
Comparing against an aggregate: "Employees earning above the company average"
SELECT name FROM employee WHERE salary > (SELECT AVG(salary) FROM employee); -
Existence checks:
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.cust_id = c.cust_id)
Cases Where a Join Is Better
- You need columns from both tables in the output.
- You are linking large tables and want the optimizer to choose join strategies.
The NOT IN Trap (Famous Interview Question)
If the subquery returns any NULL, NOT IN returns no rows (because comparisons with NULL are unknown). Prefer NOT EXISTS or LEFT JOIN ... WHERE right.key IS NULL.
-- Safer
SELECT c.name
FROM customer c
WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.cust_id = c.cust_id);
8. 2NF vs 3NF vs BCNF
Normal forms reduce redundancy and anomalies (insert, update, delete anomalies) by organising data according to functional dependencies (FDs).
Quick Prerequisites
- Functional dependency
X β Y: if two rows have the same X, they must have the same Y. - Prime attribute: part of some candidate key. Non-prime attribute: not part of any candidate key.
- Partial dependency: a non-prime attribute depends on part of a composite candidate key.
- Transitive dependency:
A β BandB β C(where B is not a key), soA β Cindirectly.
1NF (Base Requirement)
All values are atomic (single-valued; no repeating groups or lists in a cell) and each row is unique.
The Three Forms
| Normal Form | Condition | Removes |
|---|---|---|
| 2NF | In 1NF and no non-prime attribute is partially dependent on a candidate key | Partial dependencies |
| 3NF | In 2NF and no non-prime attribute is transitively dependent on a candidate key. (Formal: for every X β Y, either X is a super key, or Y is a prime attribute.) | Transitive dependencies |
| BCNF | In 3NF and for every FD X β Y, X is a super key | Remaining anomalies caused by dependencies where the determinant is not a super key |
Easy Memory Lines
- 2NF: "Depend on the whole key."
- 3NF: "...and nothing but the key."
- BCNF: "Every determinant must be a super key." (so help me Codd)
Example 1: 2NF Violation
OrderItem(order_id, product_id, product_name, quantity)
Candidate key: (order_id, product_id)
FD: product_id β product_name is a partial dependency (depends on only part of the key).
Fix (2NF):
OrderItem(order_id, product_id, quantity)
Product(product_id, product_name)
Example 2: 3NF Violation
Employee(emp_id, emp_name, dept_id, dept_name)
Key: emp_id
FDs: emp_id β dept_id, dept_id β dept_name, so emp_id β dept_name is transitive.
Fix (3NF):
Employee(emp_id, emp_name, dept_id)
Department(dept_id, dept_name)
Example 3: 3NF but Not BCNF
Teaching(student, subject, teacher)
Rules:
- Each teacher teaches one subject only:
teacher β subject - A student has one teacher per subject:
(student, subject) β teacher
Candidate keys: (student, subject) and (student, teacher).
All attributes are prime, so the table is in 3NF.
But teacher β subject has determinant teacher, which is not a super key, so it is not in BCNF.
Fix (BCNF):
TeacherSubject(teacher, subject) -- teacher β subject
StudentTeacher(student, teacher)
Trade-off: BCNF decomposition can sometimes lose a dependency (you can no longer enforce (student, subject) β teacher with a simple key constraint). 3NF can always be achieved lossless and dependency-preserving; BCNF is always lossless but may not be dependency-preserving.
Comparison Summary
| 2NF | 3NF | BCNF | |
|---|---|---|---|
| Eliminates partial dependency | Yes | Yes | Yes |
| Eliminates transitive dependency | No | Yes | Yes |
| Allows non-super-key determinant when RHS is prime | Yes | Yes | No |
| Strictness | Lowest | Middle | Highest |
| Dependency preserving always possible | Yes | Yes | Not always |
| Lossless decomposition | Yes | Yes | Yes |
How to Solve a Normalization Problem (Steps)
- List all functional dependencies.
- Find the candidate keys (use attribute closure: the set of attributes determined by a given set).
- Identify prime and non-prime attributes.
- Check for partial dependencies (2NF), transitive ones (3NF), and non-super-key determinants (BCNF).
- Decompose, making sure the decomposition is lossless (and ideally dependency-preserving).
Attribute Closure Mini-Example
Relation R(A, B, C, D), FDs: A β B, B β C, C β D.
Closure of A: start {A} β add B β add C β add D β AβΊ = {A, B, C, D} = everything, so A is a candidate key.
9. Normalization vs Denormalization
Normalization
The process of organising tables to reduce redundancy and avoid anomalies by splitting large tables into smaller, related ones (1NF β 2NF β 3NF β BCNF).
Denormalization
The deliberate introduction of redundancy (merging tables, duplicating columns, storing precomputed values) to improve read performance, accepting some cost in storage and update complexity.
Comparison
| Feature | Normalization | Denormalization |
|---|---|---|
| Goal | Remove redundancy, ensure integrity | Speed up reads/queries |
| Redundancy | Minimal | Added on purpose |
| Number of tables | More | Fewer |
| Joins needed | More | Fewer |
| Insert/update/delete | Faster and safer (one place to change) | Slower and riskier (many copies to keep in sync) |
| Read/select speed | Can be slower (many joins) | Faster |
| Data integrity | Strong | Higher risk of inconsistency |
| Storage | Less | More |
| Best for | OLTP (many small transactions, frequent writes) | OLAP / reporting / data warehouses, read-heavy systems |
Common Denormalization Techniques
- Adding redundant columns (store
customer_namein theorderstable to avoid a join) - Precomputed / derived columns (store
order_totalinstead of summing items every time) - Summary tables (daily sales totals)
- Merging tables (combining one-to-one tables)
- Materialized views
Example
Normalized: Orders(order_id, cust_id) + Customer(cust_id, name) needs a join to show the name.
Denormalized: Orders(order_id, cust_id, cust_name) avoids the join but must be updated everywhere if a customer renames.
Interview Answer in One Line
"Normalize first for correctness; denormalize selectively, and only where measured read performance needs it."
10. Clustered vs Non-Clustered Index
An index is a separate data structure (usually a B+ tree) that helps the database find rows faster without scanning the whole table, like the index at the back of a book.
Clustered Index
- Determines the physical (or logical) order of the data rows in the table. The leaf level of the index contains the actual data rows.
- Only one per table (rows can be sorted in only one way).
- In many systems, the primary key automatically becomes the clustered index (for example SQL Server by default, MySQL InnoDB always).
Non-Clustered Index
- A separate structure from the table data. Leaf nodes contain the indexed key values plus pointers to the actual rows (a row locator, or the clustered key if the table is clustered).
- A table can have many non-clustered indexes.
Comparison
| Feature | Clustered | Non-Clustered |
|---|---|---|
| Data storage | Data rows stored in index order | Index stored separately from data |
| Leaf level holds | Actual data pages/rows | Key values + row pointers |
| Number per table | One | Many |
| Lookup speed (range queries) | Very fast (rows are adjacent) | Slower (extra jump to fetch rows) |
| Lookup speed (exact match) | Fast | Fast (needs extra lookup unless covering) |
| Insert/update cost | Higher if it causes rows to move (page splits) | Index maintenance overhead on every write |
| Extra storage | None (the table is the index) | Yes (additional space) |
| Typical choice | Primary key or range-searched column | Frequently filtered/joined columns |
Analogy
- Clustered: a dictionary. The words themselves are in alphabetical order.
- Non-clustered: the index at the back of a textbook. It lists topics and tells you which page to turn to.
Key Terms Often Asked
- Covering index: a non-clustered index that includes all columns a query needs, so the table need not be touched at all.
- Composite index: an index on multiple columns; the column order matters (leftmost-prefix rule).
- Unique index: enforces uniqueness.
- Heap: a table with no clustered index (rows stored in no specific order).
- Index scan vs index seek: a seek jumps directly to matching entries (efficient); a scan reads the whole index.
Trade-Off to Mention
Indexes speed up reads but slow down writes (every insert/update/delete must also update the indexes) and use extra space. Do not index everything.
11. B-Tree vs B+ Tree
Both are balanced, multi-way search trees designed for disk-based storage, keeping the number of disk reads low. Most database indexes use B+ trees.
B-Tree
- Every node (internal and leaf) stores keys and data (or data pointers).
- A search can end at an internal node if the key is found there.
B+ Tree
- Internal nodes store only keys (used for routing).
- All actual data (or record pointers) are stored in the leaf nodes.
- Leaf nodes are linked together in a sequential list (a linked list), enabling fast range scans.
- Keys in internal nodes are duplicated in leaves.
Comparison
| Feature | B-Tree | B+ Tree |
|---|---|---|
| Data stored in | All nodes | Leaf nodes only |
| Internal nodes | Keys + data | Keys only (more keys fit per node) |
| Leaf nodes linked | No | Yes (linked list) |
| Fan-out (children per node) | Lower | Higher (so tree is shorter) |
| Tree height | Possibly taller | Usually shorter |
| Search | Can end at any level | Always goes down to a leaf (predictable) |
| Range queries / full scans | Slower (must traverse tree) | Very fast (follow leaf links) |
| Key duplication | None | Keys repeated in internal nodes and leaves |
| Typical use | Some file systems, in-memory | Database indexes (MySQL InnoDB, PostgreSQL, Oracle, SQL Server) |
Why Databases Prefer B+ Trees
- Higher fan-out: internal nodes hold only keys, so each disk page holds more keys, so the tree is shallower and fewer disk reads are needed.
- Consistent search cost: every lookup reaches a leaf at the same depth.
- Efficient range queries and ordered scans: leaves are linked.
Properties of a B+ Tree of Order m (Typical Rules)
- All leaves are at the same level (perfectly balanced).
- Each node (except the root) is at least half full.
- Node overflow causes a split; underflow causes a merge or redistribution.
- Search, insert, and delete are all O(log n).
Small Visual (B+ Tree)
[ 30 | 60 ] <- internal (keys only)
/ | \
[10|20] [30|40|50] [60|70|80] <- leaves (keys + data pointers)
β-----------β-------------β <- leaves linked for range scans
PART B: TRANSACTIONS AND CONCURRENCY QUESTIONS
12. ACID Properties
A transaction is a logical unit of work (one or more operations) that must be treated as a single whole. ACID are the four guarantees a reliable database provides for transactions.
| Letter | Property | Meaning | Ensured by |
|---|---|---|---|
| A | Atomicity | All operations happen or none do ("all or nothing") | Transaction manager, undo log / rollback |
| C | Consistency | A transaction takes the database from one valid state to another, respecting all rules and constraints | Constraints, triggers, application logic |
| I | Isolation | Concurrent transactions do not interfere; each appears to run alone | Concurrency control (locking, MVCC) |
| D | Durability | Once committed, changes survive crashes | Write-ahead log, redo, flushing to stable storage |
Classic Example: Bank Transfer (A β B, βΉ500)
BEGIN;
UPDATE account SET balance = balance - 500 WHERE acc_id = 'A';
UPDATE account SET balance = balance + 500 WHERE acc_id = 'B';
COMMIT;
- Atomicity: if the credit to B fails, the debit from A is rolled back.
- Consistency: total money in the system stays the same; no negative balance if there is a constraint.
- Isolation: another transaction reading balances mid-transfer does not see a half-done state.
- Durability: after
COMMIT, a power cut does not undo the transfer.
Transaction States
Active β Partially Committed β Committed
| |
+-----β Failed β Aborted (rolled back)
Interview Follow-Ups
- Which property is violated if the system crashes mid-transfer and the debit stays but credit is lost? Atomicity.
- Which property do locks/MVCC provide? Isolation.
- Which property is guaranteed by the log? Atomicity (undo) and Durability (redo).
- Is consistency the DBMS's job alone? No. The DBMS enforces declared constraints; the application must define correct transaction logic.
ACID vs BASE (Brief)
Some distributed/NoSQL systems relax ACID into BASE (Basically Available, Soft state, Eventually consistent) to gain availability and scale. RDBMS emphasise ACID.
13. Deadlock and Serializability Problems
13.1 Deadlock
A deadlock occurs when two or more transactions wait for each other to release locks, so none can proceed.
Classic example
| Time | T1 | T2 |
|---|---|---|
| 1 | Lock(A) | |
| 2 | Lock(B) | |
| 3 | Request Lock(B) β waits for T2 | |
| 4 | Request Lock(A) β waits for T1 |
Both wait forever: deadlock.
13.2 Four Necessary Conditions (Coffman Conditions)
All four must hold for a deadlock:
- Mutual exclusion: a resource can be held by only one transaction at a time (exclusive lock).
- Hold and wait: a transaction holds locks while waiting for more.
- No preemption: locks cannot be forcibly taken away.
- Circular wait: a cycle of transactions each waiting for the next.
13.3 Deadlock Detection: Wait-For Graph
- Nodes = transactions. Edge
Ti β Tjmeans "Ti is waiting for a lock held by Tj". - A cycle in the graph means deadlock.
T1 β T2 β T1 (cycle = deadlock)
The system periodically checks for cycles and chooses a victim to abort (usually the one with the least work done, youngest, or cheapest to roll back). The victim is rolled back and restarted. Watch for starvation (the same transaction repeatedly chosen as victim).
13.4 Deadlock Prevention Strategies
Timestamp-based schemes (older transaction = smaller timestamp = higher priority):
| Scheme | If Ti requests a lock held by Tj | Type |
|---|---|---|
| Wait-Die | If Ti is older β Ti waits. If Ti is younger β Ti dies (aborts, restarts with the same timestamp). | Non-preemptive |
| Wound-Wait | If Ti is older β Ti wounds Tj (Tj aborts). If Ti is younger β Ti waits. | Preemptive |
Other approaches
- Lock all resources upfront (conservative 2PL): avoids hold-and-wait, but reduces concurrency.
- Impose a fixed order for acquiring locks on resources (prevents circular wait).
- Lock timeouts: abort a transaction that waits too long (simple, may abort unnecessarily).
13.5 Avoiding Deadlocks in Practice
- Access tables and rows in a consistent order across all transactions.
- Keep transactions short; avoid user interaction inside them.
- Use the lowest necessary isolation level.
- Use appropriate indexes (fewer rows locked).
- Be ready to retry a transaction chosen as a deadlock victim.
13.6 Serializability
A schedule is the interleaved order of operations from several transactions. A schedule is serializable if its outcome is equivalent to some serial schedule (transactions executed one after another with no interleaving). Serializability is the standard correctness criterion for concurrent execution.
13.7 Conflicting Operations
Two operations conflict if all three hold:
- They belong to different transactions.
- They access the same data item.
- At least one is a write.
| Pair | Conflict? |
|---|---|
| ReadβRead | No |
| ReadβWrite | Yes |
| WriteβRead | Yes |
| WriteβWrite | Yes |
13.8 Conflict Serializability
A schedule is conflict serializable if it can be turned into a serial schedule by swapping adjacent non-conflicting operations.
Test: Precedence (Serialization) Graph
- Make a node for each transaction.
- Add an edge
Ti β Tjif an operation of Ti conflicts with and comes before an operation of Tj on the same item. - If the graph has no cycle, the schedule is conflict serializable. A topological order gives an equivalent serial schedule. If there is a cycle, it is not conflict serializable.
13.9 View Serializability
A schedule is view serializable if it is view equivalent to a serial schedule. Two schedules are view equivalent if:
- The same initial reads occur (each transaction reads the initial value of an item in both or neither).
- Each read reads from the same write in both schedules.
- The final write of each item is by the same transaction.
Relationships:
Serial β Conflict serializable β View serializable β All schedules
Every conflict serializable schedule is view serializable, but not the reverse (the difference involves blind writes, writes without a prior read). Testing view serializability is NP-complete, so databases rely on conflict serializability.
13.10 Ensuring Serializability: Two-Phase Locking (2PL)
- Growing phase: the transaction may acquire locks but not release any.
- Shrinking phase: the transaction may release locks but not acquire any.
- 2PL guarantees conflict serializability, but it does not prevent deadlock.
| Variant | Behaviour |
|---|---|
| Basic 2PL | As above; may cause cascading rollbacks |
| Strict 2PL | Holds exclusive locks until commit/abort β avoids cascading rollbacks |
| Rigorous 2PL | Holds all locks until commit/abort |
| Conservative 2PL | Acquires all locks before starting β no deadlock, but hard to apply |
Other approach: Timestamp ordering and MVCC (multi-version concurrency control).
13.11 Typical Interview Questions
- "Can a serializable schedule have a deadlock?" A schedule that has completed is deadlock-free by definition; but 2PL, which produces serializable schedules, can cause deadlocks.
- "Does 2PL guarantee freedom from deadlock?" No.
- "Difference between deadlock and starvation?" Deadlock: cyclic waiting; nobody progresses. Starvation: one transaction waits indefinitely while others proceed.
14. Isolation Levels and Concurrency Anomalies
14.1 Concurrency Anomalies (Problems)
| Anomaly | What happens | Example |
|---|---|---|
| Dirty read | T1 reads data written by T2 that is not yet committed; T2 then rolls back | T2 sets balance 500 β T1 reads 500 β T2 rolls back β T1 used a value that never existed |
| Non-repeatable read | T1 reads a row twice, and gets different values because T2 updated and committed in between | T1 reads salary 50k; T2 updates to 60k and commits; T1 reads again β 60k |
| Phantom read | T1 runs the same range query twice and gets different sets of rows because T2 inserted/deleted matching rows and committed | T1 counts employees with salary > 50k β 10; T2 inserts a new one; T1 counts again β 11 |
| Lost update | Two transactions read the same value and both write, so one overwrite erases the other | T1 and T2 both read balance 100; T1 writes 150; T2 writes 120; T1's update is lost |
| Write skew | Two transactions each read overlapping data, then write different items, breaking a rule that depends on both | Two doctors each see "2 on call", each goes off call; now 0 on call |
14.2 The Four Standard Isolation Levels (SQL Standard)
| Isolation Level | Dirty Read | Non-Repeatable Read | Phantom Read |
|---|---|---|---|
| Read Uncommitted | Possible | Possible | Possible |
| Read Committed | Prevented | Possible | Possible |
| Repeatable Read | Prevented | Prevented | Possible (in the standard) |
| Serializable | Prevented | Prevented | Prevented |
Higher isolation = more correctness, but less concurrency and more locking/overhead.
14.3 How Each Level Behaves
- Read Uncommitted: can read uncommitted data. Rarely used.
- Read Committed: only reads committed data. Default in PostgreSQL, Oracle, SQL Server (default).
- Repeatable Read: rows read once return the same values again within the transaction. Default in MySQL InnoDB. (InnoDB's gap locking also prevents most phantoms; PostgreSQL's snapshot-based Repeatable Read also prevents phantoms.)
- Serializable: transactions behave as if executed one after another. Safest, slowest.
Exact behaviour varies by DBMS: the standard defines minimum guarantees, and implementations may be stronger.
14.4 Setting the Level
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- or, for a session:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; -- MySQL
14.5 How Isolation Is Implemented
| Approach | Idea |
|---|---|
| Locking (pessimistic) | Shared locks for reads, exclusive locks for writes; readers can block writers |
| MVCC (Multi-Version Concurrency Control) | Keeps multiple versions of rows; readers see a consistent snapshot and do not block writers (and vice versa) |
| Optimistic concurrency control | Transactions run without locks, then validate at commit; conflicting ones are aborted and retried |
14.6 Example: Non-Repeatable Read Timeline
| Time | T1 | T2 |
|---|---|---|
| 1 | SELECT salary WHERE id=1 β 50000 | |
| 2 | UPDATE salary=60000 WHERE id=1; COMMIT; | |
| 3 | SELECT salary WHERE id=1 β 60000 (changed!) |
Prevented at Repeatable Read and above.
14.7 Choosing a Level
| Need | Choose |
|---|---|
| Maximum throughput, can tolerate imperfect reads (rough analytics) | Read Committed |
| Consistent reads within a transaction | Repeatable Read |
| Strict correctness (financial operations, inventory checks) | Serializable |
14.8 Typical Interview Questions
- "Which anomaly does Read Committed still allow?" Non-repeatable reads and phantoms.
- "Difference between non-repeatable read and phantom read?" Non-repeatable: the same row's value changes. Phantom: the set of rows matching a condition changes.
- "How does MVCC help?" Readers read old versions, so reads do not block writes.
- "How do you prevent lost updates?" Use locks (
SELECT ... FOR UPDATE), optimistic version checks, atomic updates likeSET balance = balance - 100, or higher isolation.
PART C: PRACTICE PROBLEMS
15. Indexing and Query Optimization Questions
15.1 What Does the Query Optimizer Do?
For each SQL query, the query optimizer considers many possible execution plans (join order, join methods, which indexes to use) and picks the one with the lowest estimated cost, using statistics about the data (row counts, value distribution).
15.2 How to Read an Execution Plan
Use EXPLAIN (or EXPLAIN ANALYZE, or "Show Execution Plan"):
EXPLAIN SELECT * FROM employee WHERE dept_id = 10;
Things to look for:
| Plan item | Meaning | Good / Bad |
|---|---|---|
| Full table scan | Reads every row | Bad on large tables (OK on tiny ones) |
| Index seek / index lookup | Jumps to matching rows | Good |
| Index scan | Reads the whole index | Moderate |
| Nested loop join | For each outer row, look up inner | Good for small outer set with indexed inner |
| Hash join | Builds a hash table of one input | Good for large unsorted inputs |
| Merge join | Merges two sorted inputs | Good when both sorted/indexed |
| Sort / filesort | Explicit sorting | Costly for large data |
| Estimated vs actual rows | Large differences mean stale statistics | Update statistics |
15.3 Common Questions and Model Answers
Q1. When should you create an index?
On columns frequently used in WHERE, JOIN, ORDER BY, and GROUP BY, especially with high selectivity (many distinct values) on large tables.
Q2. When should you avoid or be careful with indexes? Small tables, columns with very few distinct values (low cardinality, such as gender), tables with heavy writes (each write must update every index), and too many indexes (storage and maintenance cost).
Q3. Why is my index not being used? (Classic "sargability" problems)
| Cause | Bad example | Better |
|---|---|---|
| Function on the indexed column | WHERE YEAR(order_date) = 2025 | WHERE order_date >= '2025-01-01' AND order_date < '2026-01-01' |
| Arithmetic on the column | WHERE salary * 12 > 600000 | WHERE salary > 50000 |
| Leading wildcard | WHERE name LIKE '%kumar' | LIKE 'kumar%' can use an index |
| Type mismatch / implicit conversion | WHERE phone = 9876543210 (phone is VARCHAR) | WHERE phone = '9876543210' |
| Leftmost-prefix rule broken | Index on (a, b), query filters only b | Filter on a, or create an index starting with b |
OR conditions across different columns | WHERE a = 1 OR b = 2 | Consider UNION of two indexed queries |
| Low selectivity | Optimizer decides a scan is cheaper | Fine; this is deliberate |
| Stale statistics | Estimates are wrong | Update statistics |
Q4. What is a covering index? An index containing all columns a query needs, so the table is never accessed (an index-only scan).
CREATE INDEX idx_cover ON orders (cust_id, order_date, amount);
-- Fully covers:
SELECT order_date, amount FROM orders WHERE cust_id = 42;
Q5. Composite index column order?
Place equality conditions first, then range conditions; follow the leftmost-prefix rule. An index on (a, b, c) supports filters on a, a,b, and a,b,c but not on b or c alone.
Q6. Why is SELECT * discouraged?
It fetches unnecessary columns, prevents covering indexes, and adds network and memory cost.
Q7. How to speed up a slow query? (Checklist)
- Look at the execution plan (
EXPLAIN). - Add or adjust indexes for filter/join/sort columns.
- Make predicates sargable (no functions on indexed columns).
- Select only the needed columns and rows (filter early, limit results).
- Rewrite correlated subqueries as joins if needed; use
EXISTSinstead ofINfor large subqueries where appropriate. - Avoid
SELECT DISTINCTorUNIONwhen not needed (useUNION ALL). - Use pagination properly (keyset pagination instead of huge
OFFSET). - Update statistics; rebuild fragmented indexes.
- Consider partitioning, caching, materialized views, or controlled denormalization for heavy reports.
- Keep transactions short to reduce locking.
Q8. What is a query execution order trap?
WHERE filters before GROUP BY; put non-aggregate filters there, not in HAVING.
Q9. What is index selectivity?
distinct values / total rows. The closer to 1, the more selective (good for an index).
Q10. Pagination problem
LIMIT 10 OFFSET 1000000 must still skip 1,000,000 rows. Better:
SELECT * FROM orders
WHERE order_id > :last_seen_id
ORDER BY order_id
LIMIT 10;
15.4 Worked Optimization Example
Slow query
SELECT * FROM orders WHERE YEAR(order_date) = 2025 AND status = 'SHIPPED';
Problems: SELECT *; function on order_date prevents an index seek.
Improved
CREATE INDEX idx_orders_status_date ON orders (status, order_date);
SELECT order_id, cust_id, amount
FROM orders
WHERE status = 'SHIPPED'
AND order_date >= '2025-01-01' AND order_date < '2026-01-01';
15.5 Join Strategy Summary
| Join algorithm | Best when |
|---|---|
| Nested loop | One side small, other side indexed |
| Hash join | Large inputs, equality joins, no useful index |
| Sort-merge | Inputs already sorted, or non-equality range joins |
16. SQL Query-Writing Problems
16.1 Practice Schema
department(dept_id PK, dept_name)
employee(emp_id PK, name, salary, dept_id FK, manager_id FKβemployee.emp_id, hire_date)
project(proj_id PK, proj_name)
works_on(emp_id FK, proj_id FK, hours) -- PK (emp_id, proj_id)
customer(cust_id PK, name, city)
orders(order_id PK, cust_id FK, order_date, amount)
16.2 Classic Problems with Solutions
P1. Second highest salary
-- Using subquery
SELECT MAX(salary) FROM employee
WHERE salary < (SELECT MAX(salary) FROM employee);
-- Using LIMIT/OFFSET (distinct values)
SELECT DISTINCT salary FROM employee
ORDER BY salary DESC LIMIT 1 OFFSET 1;
P2. Nth highest salary (here N = 3, using a window function)
SELECT salary FROM (
SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
FROM employee
) t
WHERE rnk = 3
LIMIT 1;
(Without window functions: WHERE N-1 = (SELECT COUNT(DISTINCT salary) FROM employee e2 WHERE e2.salary > e1.salary).)
P3. Find duplicate values
SELECT name, COUNT(*) AS cnt
FROM employee
GROUP BY name
HAVING COUNT(*) > 1;
P4. Delete duplicate rows (keep one)
DELETE FROM employee
WHERE emp_id NOT IN (
SELECT MIN(emp_id) FROM (SELECT * FROM employee) x GROUP BY name, dept_id
);
(Or use ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) and delete rows where the number is greater than 1.)
P5. Employees earning more than their manager (self join)
SELECT e.name
FROM employee e
JOIN employee m ON e.manager_id = m.emp_id
WHERE e.salary > m.salary;
P6. Departments with no employees
SELECT d.dept_name
FROM department d
LEFT JOIN employee e ON d.dept_id = e.dept_id
WHERE e.emp_id IS NULL;
P7. Department-wise employee count, only departments with more than 5
SELECT d.dept_name, COUNT(e.emp_id) AS emp_count
FROM department d
JOIN employee e ON e.dept_id = d.dept_id
GROUP BY d.dept_name
HAVING COUNT(e.emp_id) > 5;
P8. Highest-paid employee in each department
SELECT e.*
FROM employee e
WHERE e.salary = (
SELECT MAX(salary) FROM employee WHERE dept_id = e.dept_id -- correlated
);
P9. Top 3 salaries in each department (window function)
SELECT * FROM (
SELECT e.*, DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rnk
FROM employee e
) t
WHERE rnk <= 3;
P10. Customers who have never placed an order
SELECT c.name
FROM customer c
WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.cust_id = c.cust_id);
P11. Total order amount per customer, highest first
SELECT c.name, SUM(o.amount) AS total
FROM customer c
JOIN orders o ON o.cust_id = c.cust_id
GROUP BY c.cust_id, c.name
ORDER BY total DESC;
P12. Employees working on more than 2 projects
SELECT e.name, COUNT(w.proj_id) AS projects
FROM employee e
JOIN works_on w ON w.emp_id = e.emp_id
GROUP BY e.emp_id, e.name
HAVING COUNT(w.proj_id) > 2;
P13. Employees who work on all projects (relational division)
SELECT e.name
FROM employee e
JOIN works_on w ON w.emp_id = e.emp_id
GROUP BY e.emp_id, e.name
HAVING COUNT(DISTINCT w.proj_id) = (SELECT COUNT(*) FROM project);
P14. Salary above department average
SELECT e.name, e.salary, e.dept_id
FROM employee e
WHERE e.salary > (SELECT AVG(salary) FROM employee WHERE dept_id = e.dept_id);
P15. Employees hired in the last 30 days (syntax varies by DBMS)
SELECT * FROM employee
WHERE hire_date >= CURRENT_DATE - INTERVAL '30' DAY;
P16. Running total of orders per customer (window function)
SELECT cust_id, order_date, amount,
SUM(amount) OVER (PARTITION BY cust_id ORDER BY order_date) AS running_total
FROM orders;
P17. Customers from the same city (self join)
SELECT a.name AS cust1, b.name AS cust2, a.city
FROM customer a
JOIN customer b ON a.city = b.city AND a.cust_id < b.cust_id;
P18. Count employees by salary band using CASE
SELECT
CASE WHEN salary < 30000 THEN 'Low'
WHEN salary < 70000 THEN 'Medium'
ELSE 'High' END AS band,
COUNT(*) AS cnt
FROM employee
GROUP BY band; -- some DBMSs need the full CASE expression repeated in GROUP BY
P19. Find the median / top percent (concept)
Use window functions such as ROW_NUMBER, NTILE, or PERCENT_RANK, depending on the DBMS.
P20. Swap values with a single UPDATE (example: flip 'M' and 'F' in a column)
UPDATE person
SET gender = CASE gender WHEN 'M' THEN 'F' WHEN 'F' THEN 'M' ELSE gender END;
16.3 Tips for Query-Writing Rounds
- Clarify the schema and ask about NULLs and duplicates.
- Think in order: FROM/JOIN β WHERE β GROUP BY β HAVING β SELECT β ORDER BY.
- Break the problem into small steps; build the query incrementally.
- Prefer
NOT EXISTSoverNOT INwhen NULLs may exist. - Use
COUNT(*)vsCOUNT(col)correctly (COUNT(col)ignores NULLs). - Mention an alternative approach and the performance trade-off.
- Know the difference between
RANK,DENSE_RANK, andROW_NUMBER:
| Function | Ties | Example for values 100, 100, 90 |
|---|---|---|
ROW_NUMBER | Unique numbers | 1, 2, 3 |
RANK | Same rank, gaps after | 1, 1, 3 |
DENSE_RANK | Same rank, no gaps | 1, 1, 2 |
17. ER Diagram Design Problems
17.1 ER Model Building Blocks
| Element | Description | Notation (Chen) |
|---|---|---|
| Entity | A real-world object (Student, Course) | Rectangle |
| Weak entity | Cannot be identified without its owner entity (Dependent of Employee) | Double rectangle |
| Attribute | A property of an entity | Oval |
| Key attribute | Uniquely identifies an entity | Underlined |
| Multivalued attribute | Can have several values (phone numbers) | Double oval |
| Derived attribute | Computed from others (age from birth date) | Dashed oval |
| Composite attribute | Made of sub-parts (address = street + city + zip) | Oval with sub-ovals |
| Relationship | Association between entities (Student enrolls in Course) | Diamond |
| Identifying relationship | Links a weak entity to its owner | Double diamond |
17.2 Cardinality and Participation
Cardinality (how many):
- One-to-One (1:1): Person β Passport
- One-to-Many (1:N): Department β Employees
- Many-to-Many (M:N): Student β Course
Participation (must it exist?):
- Total (mandatory): every entity must take part (double line). Example: every Employee must belong to a Department.
- Partial (optional): some entities may not take part (single line).
17.3 Steps to Solve an ER Design Problem
- Read the requirements and underline nouns (candidate entities) and verbs (candidate relationships).
- Identify entities and their attributes; choose primary keys.
- Identify relationships, their cardinality, and participation.
- Handle special cases: weak entities, multivalued/composite/derived attributes, ternary relationships, generalization/specialization.
- Add attributes on relationships where needed (e.g.,
gradeon Enrolls). - Review for redundancy and missing requirements.
- Convert to relational schema.
17.4 Converting an ER Diagram into Tables (Mapping Rules)
| ER construct | Becomes |
|---|---|
| Strong entity | A table with its PK |
| Weak entity | A table with its partial key + owner's PK as a composite PK (owner's key also a FK) |
| 1:1 relationship | Put the FK on either side (preferably the side with total participation), or merge tables |
| 1:N relationship | Put the FK on the "N" side |
| M:N relationship | A new junction table with the PKs of both entities as FKs (composite PK) plus relationship attributes |
| Multivalued attribute | A separate table with the entity's PK as FK |
| Composite attribute | Flatten into separate columns |
| Derived attribute | Usually not stored (compute when needed) |
| Ternary relationship | A table with FKs to all three entities |
| Specialization/Generalization (ISA) | (a) one table per entity (parent + children), (b) child tables only, or (c) a single table with a type column |
17.5 Problem 1: University
Requirements: Students enroll in courses. Each course is taught by one instructor. Instructors belong to a department. Students get a grade for each course they take.
Entities: Student(student_id, name, email), Course(course_id, title, credits), Instructor(instr_id, name), Department(dept_id, dept_name).
Relationships:
- Student enrolls in Course: M:N, attribute
grade - Instructor teaches Course: 1:N (one instructor, many courses)
- Instructor works in Department: N:1
Relational schema:
department(dept_id PK, dept_name)
instructor(instr_id PK, name, dept_id FKβdepartment)
course(course_id PK, title, credits, instr_id FKβinstructor)
student(student_id PK, name, email)
enrollment(student_id FK, course_id FK, grade, PRIMARY KEY (student_id, course_id))
17.6 Problem 2: Hospital
Requirements: Patients are treated by doctors. A doctor has one specialization. A patient can have many appointments. Each appointment is with one doctor on a date and may produce prescriptions of medicines.
Entities: Patient, Doctor, Appointment, Medicine
Design notes:
Appointmentcan be an entity (it has its own attributes: date, time, notes) linking Patient and Doctor. This resolves the M:N between patient and doctor.Prescriptionlinks Appointment and Medicine (M:N) with attributesdosage,duration.
patient(patient_id PK, name, dob, phone)
doctor(doctor_id PK, name, specialization)
appointment(appt_id PK, patient_id FK, doctor_id FK, appt_date, notes)
medicine(med_id PK, med_name)
prescription(appt_id FK, med_id FK, dosage, duration, PRIMARY KEY (appt_id, med_id))
17.7 Problem 3: Weak Entity Example
Requirement: Employees have dependents (children). A dependent is identified only by name together with the employee.
Dependentis a weak entity, its partial key isdep_name.- Identifying relationship: Employee has Dependent (1:N, total participation on the weak side).
dependent(emp_id FKβemployee, dep_name, relationship,
PRIMARY KEY (emp_id, dep_name))
17.8 Problem 4: Generalization / Specialization
Requirement: A bank has accounts. Some are Savings accounts (interest rate), others Current accounts (overdraft limit).
Account(acc_no, holder, balance) β superclass
βββ Savings(acc_no, interest_rate) β subclass
βββ Current(acc_no, overdraft_limit) β subclass
Constraints: disjoint (an account is one type only) or overlapping; total (every account must be one of the types) or partial.
17.9 Common Pitfalls to Mention
- Modelling an M:N relationship without a junction table
- Storing multiple values in one column
- Treating a relationship attribute as an entity attribute (for example
gradebelongs to the enrollment, not to Student or Course) - Missing participation/cardinality constraints
- Storing derived data unnecessarily
- Using a poor primary key (for example, a name)
17.10 Ternary vs Binary Relationships
If a fact involves three entities together (for example Supplier supplies Part to Project), a ternary relationship may be required, because breaking it into three binary relationships can lose information about which combination occurs together.
18. Schema Design Case Studies
For each case: requirements β entities β tables β keys β constraints β indexes β notes.
18.1 Case Study 1: Online Library System
Requirements: Members borrow books. Books have multiple authors and multiple copies. Track due dates and returns, and fines for late returns.
Tables
author(author_id PK, name)
book(book_id PK, title, isbn UNIQUE, publisher, year)
book_author(book_id FK, author_id FK, PRIMARY KEY (book_id, author_id)) -- M:N
copy(copy_id PK, book_id FK, shelf_location, status) -- physical copies
member(member_id PK, name, email UNIQUE, join_date)
loan(loan_id PK, copy_id FK, member_id FK, issue_date, due_date, return_date NULL)
fine(fine_id PK, loan_id FK, amount, paid_flag)
Design decisions
- Separate book (the title) from copy (the physical item), so one book can have many copies.
- M:N between books and authors needs a junction table.
return_date NULLmeans "still on loan".- Index on
loan(member_id),loan(copy_id), andloan(due_date)for common queries.
Sample queries: books currently overdue; members with unpaid fines; most borrowed books.
18.2 Case Study 2: E-Commerce Platform
Requirements: Customers place orders for products. An order has many items. Products belong to categories. Customers may have multiple addresses. Track payments and shipping status.
customer(cust_id PK, name, email UNIQUE, password_hash, created_at)
address(addr_id PK, cust_id FK, line1, city, state, zip, is_default)
category(cat_id PK, name, parent_cat_id FK NULL) -- self-reference for sub-categories
product(prod_id PK, name, description, price, stock, cat_id FK)
orders(order_id PK, cust_id FK, ship_addr_id FK, order_date, status, total_amount)
order_item(order_id FK, prod_id FK, quantity, unit_price, PRIMARY KEY (order_id, prod_id))
payment(payment_id PK, order_id FK, method, amount, paid_at, status)
Key design points
- Store
unit_priceinorder_item: product prices change later, but old orders must keep the price at purchase time (a deliberate, justified denormalization). total_amountinordersis derived; storing it is a performance choice and must be kept consistent (or computed on demand).- Never store plain-text passwords; store hashes.
- Check constraints:
price >= 0,quantity > 0,stock >= 0. - Indexes:
orders(cust_id, order_date),product(cat_id),order_item(prod_id). - Use transactions when placing an order (reduce stock, create order, create payment).
18.3 Case Study 3: Banking System
Requirements: Customers have accounts; accounts have transactions; transfers move money between accounts.
customer(cust_id PK, name, pan UNIQUE, phone)
account(acc_no PK, cust_id FK, type, balance, opened_on, status,
CHECK (balance >= 0))
txn(txn_id PK, acc_no FK, txn_type, amount, txn_time, balance_after, ref_id)
transfer(transfer_id PK, from_acc FK, to_acc FK, amount, txn_time, status)
Key design points
- Treat each money movement as a transaction with ACID guarantees (debit and credit together).
- Keep the transaction history append-only: never update or delete past entries; correct with reversing entries (an audit trail).
- A joint account (many customers per account) turns CustomerβAccount into M:N, so a junction table
account_holderis needed. - Use Serializable or explicit locking (
SELECT ... FOR UPDATE) for balance updates to avoid lost updates. - Use
DECIMAL/NUMERICfor money, never floating point.
18.4 Case Study 4: Student Result Management
Requirements: Track students, subjects, semesters, exam marks, and compute GPA.
student(student_id PK, name, branch, admission_year)
subject(subject_id PK, name, credits)
semester(sem_id PK, year, term)
result(student_id FK, subject_id FK, sem_id FK, marks, grade,
PRIMARY KEY (student_id, subject_id, sem_id))
Notes: GPA is a derived value, so compute it with a query or view rather than storing it (or store it in a summary table that is refreshed). The composite primary key allows a student to retake a subject in a different semester.
18.5 Case Study 5: Fixing a Bad Design (Normalization Exercise)
Original single table
OrderRecord(order_id, order_date, cust_name, cust_phone, product1, qty1, product2, qty2, ...)
Problems: repeating groups (violates 1NF), customer data duplicated (update anomaly), cannot add more products without altering the table.
Fixed design
customer(cust_id PK, cust_name, cust_phone)
orders(order_id PK, order_date, cust_id FK)
product(prod_id PK, prod_name, price)
order_item(order_id FK, prod_id FK, qty, PRIMARY KEY (order_id, prod_id))
18.6 General Schema-Design Checklist
- Every table has a primary key
- Relationships are expressed with foreign keys, and referential actions are chosen deliberately
- Data is normalized (at least 3NF) unless a justified exception exists
- Appropriate data types (
DECIMALfor money,DATE/TIMESTAMPfor time, right-sized strings) - Constraints:
NOT NULL,UNIQUE,CHECK,DEFAULT - Indexes on foreign keys and frequent filter/sort columns
- Consider audit columns (
created_at,updated_at) - Soft delete (
is_deletedflag) vs hard delete decided - Plan for growth (partitioning, archiving)
- Security considered (no plain-text secrets, least-privilege access)
19. Transaction Schedule Problems
19.1 Notation
R1(A)= Transaction T1 reads item AW2(B)= Transaction T2 writes item BC1= T1 commits,A1= T1 aborts
A schedule is the order in which operations from different transactions are executed.
19.2 Types of Problems You Will Be Asked
- Is the schedule conflict serializable? (precedence graph)
- Is it view serializable?
- Is it recoverable / cascadeless / strict?
- Find an equivalent serial schedule.
- Count the number of possible schedules.
- Is a schedule allowed by 2PL?
- Identify anomalies (dirty read, lost update, ...).
19.3 Problem 1: Conflict Serializability
Schedule S:
R1(A) W1(A) R2(A) W2(A) R1(B) W1(B) R2(B) W2(B)
Step 1: List conflicting pairs (different transactions, same item, at least one write):
- On A:
W1(A)beforeR2(A)β edge T1 β T2 - On A:
W1(A)beforeW2(A)β T1 β T2 - On A:
R1(A)beforeW2(A)β T1 β T2 - On B:
R1(B)/W1(B)beforeR2(B)/W2(B)β T1 β T2
Step 2: Draw the precedence graph: only edge T1 β T2. No cycle.
Conclusion: conflict serializable; equivalent serial order T1, T2.
19.4 Problem 2: Not Conflict Serializable
Schedule S:
R1(A) R2(A) W1(A) W2(A)
Edges:
R2(A)beforeW1(A)β T2 β T1R1(A)beforeW2(A)β T1 β T2W1(A)beforeW2(A)β T1 β T2
Graph has T1 β T2 and T2 β T1: a cycle. Not conflict serializable. (This is also a lost update: both read the old A, and T1's write is overwritten.)
19.5 Problem 3: Equivalent Serial Order with Three Transactions
Schedule:
R1(X) R3(Y) R3(X) R2(Y) R2(Z) W3(Y) W2(Z) R1(Z) W1(X) W1(Z)
Edges (conflicts):
R3(Y)...W3(Y): same transaction, no edge.R2(Y)beforeW3(Y)β T2 β T3R3(Y)before... nothing from T2 writes Y. OK.R2(Z)beforeW1(Z)β T2 β T1;W2(Z)beforeR1(Z)β T2 β T1;W2(Z)beforeW1(Z)β T2 β T1R3(X)beforeW1(X)β T3 β T1;R1(X)before... T3 never writes X.
Graph: T2 β T3, T3 β T1, T2 β T1. No cycle.
Topological order: T2 β T3 β T1. The schedule is conflict serializable and equivalent to the serial schedule T2, T3, T1.
19.6 Recoverability
| Type | Rule | Problem prevented |
|---|---|---|
| Recoverable | If Tj reads from Ti, then Ti must commit before Tj commits | Committing based on data that is later rolled back |
| Cascadeless (ACA) | Transactions read only committed data | Cascading rollbacks |
| Strict | Items written by Ti are not read or overwritten until Ti commits/aborts | Simplifies undo (before-images work) |
Strict β Cascadeless β Recoverable β All schedules
Example: Non-recoverable schedule
W1(A) R2(A) C2 A1
T2 read T1's uncommitted write and committed. When T1 aborts, T2's committed work depends on data that never existed. Non-recoverable (the worst case).
Example: Recoverable but not cascadeless
W1(A) R2(A) C1 C2
T2 reads uncommitted data but commits only after T1 commits (recoverable). But if T1 had aborted before committing, T2 would also need to abort (cascade), so it is not cascadeless.
19.7 Problem: Is This Schedule Allowed by 2PL?
Lock-X(A) R1(A) W1(A) Unlock(A) Lock-X(B) R1(B) ...
T1 unlocks A and then locks B: acquiring a lock after releasing one violates 2PL (no new locks in the shrinking phase). The fix is to lock B before unlocking A.
19.8 Counting Schedules
For transactions with n1, n2, ..., nk operations each, the number of possible interleavings (keeping each transaction's internal order) is
(n1 + n2 + ... + nk)! / (n1! Γ n2! Γ ... Γ nk!)
Example: T1 has 3 operations and T2 has 2 operations β 5! / (3! Γ 2!) = 10 schedules.
The number of serial schedules with k transactions is k!.
19.9 Quick Method to Solve Any Schedule Problem
- Write down all operations in order.
- For each item, list operations in order and find conflicting pairs across transactions.
- Draw the precedence graph.
- Cycle? β not conflict serializable. No cycle? β topological sort gives the serial order.
- For recoverability, check who read from whom and the commit order.
- For anomalies, check for read-write patterns (dirty read, lost update, etc.).
20. Real-World Database Design Scenarios
These are open-ended, "how would you design/solve this?" questions. A structured answer scores well:
Clarify β Model β Normalize β Constrain β Index β Handle concurrency β Plan for scale β Secure β Explain trade-offs.
20.1 Scenario: Ride-Booking App
Needs: riders, drivers, trips, payments, ratings, live driver locations.
Approach
- Tables:
rider,driver,vehicle,trip(rider_id, driver_id, pickup, drop, status, fare),payment,rating. trip.statusfollows a state flow: requested β accepted β ongoing β completed/cancelled.- Frequently changing location data is write-heavy; keep it separate from core tables (or in a specialised store) and keep only a recent snapshot in the main DB.
- Index
trip(rider_id, created_at),trip(driver_id, status). - Concurrency: two riders must not get the same driver, so use a transaction with row locking (
SELECT ... FOR UPDATE) or an atomic status update (UPDATE driver SET status='BUSY' WHERE driver_id=? AND status='AVAILABLE'and check the affected row count).
20.2 Scenario: Ticket / Seat Booking (Movie, Train, Flight)
Problem: many users trying to book the same seat; avoid double booking.
Solutions
- Unique constraint:
UNIQUE (show_id, seat_no)on bookings, so the database itself rejects a second booking. - Use a transaction with
SELECT ... FOR UPDATEon the seat row, then update. - Use a temporary hold with an expiry (
hold_until) during payment, released if unpaid. - Use appropriate isolation (Serializable, or Repeatable Read plus locking).
- Always retry on deadlock or serialization failure.
20.3 Scenario: Bank Balance Transfer Under Heavy Load
Problems: lost updates, deadlocks.
Answers
- Wrap debit and credit in one ACID transaction.
- Update atomically:
UPDATE account SET balance = balance - :amt WHERE acc_no = :a AND balance >= :amt. - Lock accounts in a consistent order (for example, by lower account number first) to avoid deadlocks.
- Keep an append-only ledger for audit.
- Make operations idempotent (use a unique transfer ID so a retry does not double-process).
20.4 Scenario: Social Media Feed / Followers
Needs: users, posts, followers, likes, comments.
follow(follower_id, followee_id, PRIMARY KEY (follower_id, followee_id)): a self-referencing M:N on users.post(post_id, user_id, content, created_at): index(user_id, created_at DESC)for profile pages.like(user_id, post_id)with a composite PK prevents duplicate likes.- Counters (like counts) can be denormalized into
post.like_countfor speed, updated carefully (or via periodic recomputation). - Large feeds β use keyset pagination and consider caching.
20.5 Scenario: Inventory Management (Overselling Problem)
Problem: stock goes negative when many orders arrive simultaneously.
CHECK (stock >= 0)as a final safety net.- Atomic decrement:
UPDATE product SET stock = stock - :q WHERE prod_id = :p AND stock >= :q;then check that 1 row was updated. - Or use optimistic locking with a
versioncolumn:UPDATE ... WHERE prod_id = ? AND version = ?, and retry on failure.
20.6 Scenario: Reporting and Analytics on a Busy OLTP Database
Problem: heavy reports slow down day-to-day transactions.
Options
- Create summary tables or materialized views.
- Denormalize for reporting (star schema in a separate warehouse).
- Run reports on a read replica.
- Add suitable indexes, or partition large tables by date.
- Schedule heavy jobs at off-peak times.
20.7 Scenario: Multi-Tenant Application (SaaS)
Options
| Approach | Description | Pros | Cons |
|---|---|---|---|
Shared schema (tenant_id column everywhere) | All tenants in the same tables | Cheapest, simplest to scale in numbers | Risk of data leaks if filters are missed; noisy neighbours |
| Separate schema per tenant | Each tenant has its own schema in one database | Better isolation | More management |
| Separate database per tenant | Each tenant has its own database | Strongest isolation | Highest cost and operational effort |
Tip: with a shared schema, include tenant_id in every index and every query, and consider row-level security.
20.8 Scenario: Storing Hierarchical Data (Categories, Org Charts)
| Technique | Idea | Notes |
|---|---|---|
| Adjacency list | parent_id column referencing the same table | Simple; recursive queries (WITH RECURSIVE) needed to fetch subtrees |
| Path enumeration | Store the full path (/1/4/9/) | Easy subtree queries; harder to maintain |
| Nested sets | Store left/right numbers | Fast reads; expensive updates |
| Closure table | Store every ancestor-descendant pair | Flexible queries; more storage |
20.9 Scenario: Handling Historical Changes (Audit and History)
- Audit table populated via triggers (old and new values, who, when).
- Slowly changing records: keep
valid_from/valid_tocolumns, so you can see the data as it was at any past date. - Soft delete: an
is_deletedordeleted_atflag instead of removing the row. - Store price at purchase in orders (not just a reference to the product).
20.10 Scenario: Slow Production Query
A reliable troubleshooting answer:
- Reproduce and capture the query.
- Look at the execution plan.
- Check for missing/unused indexes, non-sargable conditions, large scans, bad join order, stale statistics.
- Fix in order of impact: index, rewrite query, schema change (or caching).
- Measure again, and watch the effect on writes.
20.11 Scenario: Handling Duplicate or Dirty Data
- Prevent at the source: constraints (
UNIQUE,NOT NULL,CHECK, FKs). - Detect duplicates:
GROUP BY ... HAVING COUNT(*) > 1. - Clean: use
ROW_NUMBER()to keep one row per group. - Prevent future duplicates with a unique index.
20.12 Final Interview Framework for Design Questions
- Ask clarifying questions: scale, read/write ratio, consistency needs, growth.
- Identify entities and relationships; draw a quick ER sketch.
- Define tables, keys, and constraints.
- Normalize, then justify any denormalization.
- Choose indexes based on the main queries.
- Address concurrency: transactions, isolation level, locking.
- Plan for scale and failure: partitioning, replication, backups.
- Mention security: least privilege, encryption, no plain-text secrets.
- State trade-offs openly. There is rarely one perfect answer.
21. Quick Revision Cheat Sheet
One-Liners for the "vs" Questions
| Topic | The core difference |
|---|---|
| DBMS vs RDBMS | RDBMS stores data in related tables with keys, SQL and ACID; plain DBMS need not |
| Primary vs Unique Key | PK: one per table, no NULL, row identity. UK: many, NULL allowed (varies), just uniqueness |
| Primary vs Foreign Key | PK identifies a row. FK links to another table's key; may repeat and be NULL |
| DELETE vs TRUNCATE vs DROP | Rows (with WHERE, logged, rollbackable) vs all rows fast vs the whole table |
| WHERE vs HAVING | WHERE filters rows before grouping; HAVING filters groups after (can use aggregates) |
| UNION vs UNION ALL | UNION removes duplicates (slower); UNION ALL keeps all (faster) |
| Join vs Subquery | Join combines columns from tables; subquery feeds one query's result to another |
| 2NF / 3NF / BCNF | No partial dependency / no transitive dependency / every determinant is a super key |
| Normalization vs Denormalization | Remove redundancy for integrity vs add redundancy for read speed |
| Clustered vs Non-clustered | Index defines the row order (one) vs separate structure with pointers (many) |
| B-Tree vs B+ Tree | Data in all nodes vs data only in linked leaves (better for ranges, shallower) |
ACID in One Line Each
- Atomicity: all or nothing
- Consistency: valid state to valid state
- Isolation: concurrent transactions don't interfere
- Durability: committed means permanent
Isolation Levels Quick Table
| Level | Dirty | Non-repeatable | Phantom |
|---|---|---|---|
| Read Uncommitted | β | β | β |
| Read Committed | β | β | β |
| Repeatable Read | β | β | β (standard) |
| Serializable | β | β | β |
(β = possible, β = prevented)
Deadlock and Serializability Essentials
- Deadlock = cycle in the wait-for graph.
- Prevent with wait-die / wound-wait, lock ordering, timeouts.
- Conflict serializable β precedence graph has no cycle.
- 2PL β conflict serializable, but not deadlock-free.
- Strict 2PL β also cascadeless.
Query-Writing Reminders
- Execution order: FROM β WHERE β GROUP BY β HAVING β SELECT β ORDER BY β LIMIT
- Use NOT EXISTS instead of NOT IN when NULLs are possible.
RANKhas gaps,DENSE_RANKhas none,ROW_NUMBERis always unique.COUNT(*)counts rows;COUNT(col)skips NULLs.- Keep predicates sargable (no functions on indexed columns).
Design Reminders
- M:N β junction table.
- 1:N β FK on the many side.
- Weak entity β owner's key + partial key.
- Multivalued attribute β separate table.
- Money β DECIMAL, never float.
- Store historical values (like price at purchase) in transactional records.
Final Advice for Interviews
- Clarify before answering.
- Define, then compare, then give an example.
- Think aloud on trade-offs; there is rarely one right design.
- For SQL, write it step by step and test mentally with a tiny sample.
- For schedules, always draw the precedence graph.
- Be honest about DBMS-specific behaviour ("this varies between MySQL and PostgreSQL").
π§ Continue Learning
Related technical blueprints and practical guides in Core CS.

Computer Networks
Learn how computers communicate over networks, from basic networking concepts and protocols to IP addressing, routing, TCP/UDP, HTTP/HTTPS, DNS, and network security fundamentals.

Object-Oriented Programming (OOP)
A comprehensive guide to Object-Oriented Programming (OOP), covering classes, objects, encapsulation, abstraction, inheritance, polymorphism, constructors, destructors, interfaces, exception handling, and object relationships. Includes practical C++ and Java examples, SOLID principles, design patterns fundamentals, and interview-oriented questions to build strong object-oriented design skills.