Unit II · Database design & protection

DBMS Unit II — Normalization, Security & PL/SQL

Visual, diagram-first learning with handmade flowcharts. Every definition comes with a worked example, every algorithm with a flowchart, and every chapter ends with exam-ready notes.

flowchart TD
  A["Raw table with anomalies"]:::y --> B["Find functional dependencies"]:::m
  B --> C["Normalize: 1NF to 5NF and BCNF"]:::m
  C --> D["Secure with roles, views, audits"]:::g
  D --> E["Automate with PL/SQL"]:::g
The journey of this unit in four steps.
5Learning outcomes
3Core chapters
6Normal forms
20+Mermaid diagrams
Section 1 · Unit overview & syllabus map

What Unit II covers

Unit II: Database Normalization, Security, and PL/SQL Programming. This unit focuses on database design and optimization through Functional Dependencies and Normalization techniques. You learn how to identify candidate keys, compute attribute closures, and perform decomposition while ensuring lossless join and dependency preservation. It also covers advanced normalization concepts such as BCNF, 4NF, and 5NF. Additionally, you learn database security, common threats, and countermeasures to protect organizational data.

Syllabus mindmap
mindmap
  root((Unit II))
    Chapter 2.1 Functional Dependencies
      FD types
        Full
        Partial
        Transitive
        Join
        Multi-valued
      Armstrong axioms
      Attribute closure
      Candidate keys
      Canonical cover
      Decomposition
    Chapter 2.2 Normalization
      Anomalies
      1NF 2NF 3NF
      BCNF
      4NF and 5NF
      Denormalization
    Chapter 2.3 Database Security
      CIA triad
      Threats and vulnerabilities
      Authentication vs authorization
      Access control
      Countermeasures
    PL/SQL Programming
      Blocks
      Cursors
      Procedures and functions
      Triggers
      Exceptions
      Packages
Four branches: three chapters plus PL/SQL programming from the unit title.
Unit learning outcomes

After completing this unit, you will be able to:

  1. Analyze functional dependencies and determine candidate keys using closure techniques.
  2. Apply normalization techniques (1NF to 5NF and BCNF) to eliminate data redundancy and anomalies.
  3. Evaluate database decompositions for lossless join and dependency preservation properties.
  4. Identify database security threats and recommend appropriate security mechanisms and countermeasures.
  5. Design efficient and secure relational database schemas for real-world applications.

Where each outcome is covered

OutcomeWhere to study itSkill you practise
1 · FDs and candidate keysChapter 2.1Attribute closure, key finding, canonical cover
2 · Apply 1NF–5NF and BCNFChapter 2.2Convert schemas step by step, spot anomalies
3 · Evaluate decompositionsSection 4 and 2.1Lossless join test, dependency preservation test
4 · Security threats & countermeasuresChapter 2.3Map threat → vulnerability → control
5 · Design efficient, secure schemasSection 7, PL/SQLNormalize, constrain, grant, audit

How the chapters connect

Chapter 2.1 gives you the mathematics of dependencies. Chapter 2.2 uses those dependencies to restructure tables so redundancy and anomalies disappear. Chapter 2.3 protects the well-structured data. PL/SQL is the programming layer that enforces business rules, constraints, audits and access control inside the database itself.

flowchart LR
  A["2.1 Functional dependencies"]:::g --> B["2.2 Normalization"]:::m
  B --> C["Decomposition: lossless and dependency preserving"]:::m
  C --> D["Normalized schema"]:::g
  D --> E["2.3 Security controls"]:::m
  D --> F["PL/SQL enforcement"]:::m
  E --> G["Secure, efficient database"]:::g
  F --> G
Section 2 · Chapter 2.1

Functional Dependencies

This chapter introduces Functional Dependencies (FDs), Full Functional Dependency, Transitive Dependency, Join Dependency, and Multi-valued Dependency. You learn attribute closure computation, canonical cover generation, candidate key identification, and decomposition techniques.

Chapter 2.1 learning outcomes
  • Define and analyze different types of dependencies in relational databases.
  • Compute attribute closures and identify candidate keys.
  • Derive canonical covers for a set of functional dependencies.
  • Perform decomposition and verify lossless join and dependency preservation properties.
  • Apply dependency concepts in relational database design.

2.1.1 What is a functional dependency?

Definition

Let R be a relation schema and X, Y be sets of attributes of R. The functional dependency X → Y (“X determines Y”) holds if, in every valid instance of R, any two tuples that agree on X also agree on Y. X is the determinant, Y the dependent.

An FD is a constraint on the meaning of the data, not something you read off a single table. A table can only disprove an FD (by showing two rows that agree on X but not on Y). The designer declares FDs from business rules: “a student ID identifies exactly one student name”.

Student(RollNo, Name, Dept, HOD)
RollNoNameDeptHOD
101AshaCSDr. Rao
102RaviCSDr. Rao
103MeenaEEDr. Iyer

Here RollNo → Name, Dept, HOD holds (RollNo is the key) and Dept → HOD holds. But Dept → Name does not: rows 101 and 102 agree on Dept (CS) yet differ on Name.

Trivial FD: X → Y is trivial when Y ⊆ X (e.g. {RollNo, Name} → Name). Non-trivial: Y ⊄ X. Normalization only worries about non-trivial FDs.

2.1.2 Types of dependencies

Full functional dependency

X → Y is full if Y depends on all of X and on no proper subset of X. Example: in Enrollment(StudentID, CourseID, Grade), {StudentID, CourseID} → Grade is full, because neither StudentID alone nor CourseID alone determines Grade.

Partial dependency

Y depends on only part of a composite key. Example: {StudentID, CourseID} → StudentName is partial, since StudentID → StudentName already holds. Violates 2NF.

Transitive dependency

If X → Y and Y → Z, with Y not a key and Y ↛ X, then Z depends on X transitively. Example: RollNo → Dept → HOD. Violates 3NF.

Multi-valued dependency (MVD)

X →→ Y means each value of X is associated with a set of Y values, independent of the other attributes. Example: Emp →→ Skill while Emp →→ Language. Violates 4NF.

Join dependency

A join dependency ⋈(R1, R2, …, Rn) holds on R if R equals the natural join of its projections on R1…Rn in every valid instance. An FD or MVD is a special case. Join dependencies that are not implied by candidate keys violate 5NF.

flowchart TD
  A["Dependency X to Y in relation R"]:::g --> B{"Is Y a subset of X?"}
  B -- Yes --> C["Trivial FD"]:::m
  B -- No --> D["Non-trivial FD"]:::g
  D --> E{"Does Y depend on every attribute of X?"}
  E -- Yes --> F["Full functional dependency"]:::m
  E -- No --> G["Partial dependency on part of the key"]:::y
  D --> H{"Does X to Y and Y to Z hold with Y not a key?"}
  H -- Yes --> I["Transitive dependency X to Z"]:::y
  A --> J["Beyond FDs"]
  J --> K["Multi-valued: X multi-determines Y, independent sets"]:::m
  J --> L["Join dependency: R equals join of its projections"]:::m
Flowchart: classifying a dependency.
Common Mistake

Treating a pattern in today’s data as an FD. If two students happen to have different names for different phone numbers, you still cannot say Phone → Name unless the business rule guarantees it for all future data.

2.1.3 Armstrong’s axioms

Armstrong’s axioms are sound (they derive only true FDs) and complete (they derive every FD implied by F). The set of all implied FDs is the closure of F, written F⁺.

RuleStatementExample
ReflexivityIf Y ⊆ X then X → YAB → A
AugmentationIf X → Y then XZ → YZA → B gives AC → BC
TransitivityIf X → Y and Y → Z then X → ZA → B, B → C gives A → C
Derived rules
UnionX → Y and X → Z give X → YZA → B, A → C gives A → BC
DecompositionX → YZ gives X → Y and X → ZA → BC gives A → B
PseudotransitivityX → Y and WY → Z give WX → ZA → B, CB → D gives CA → D
Proof of the union rule from the axioms

Given X → Y and X → Z. Augment the first with X: X → XY. Augment the second with Y: XY → YZ. By transitivity X → XY and XY → YZ give X → YZ. ∎

2.1.4 Attribute closure

Definition

The closure of an attribute set X under F, written X⁺, is the set of all attributes A such that X → A is implied by F. If X⁺ contains every attribute of R, X is a superkey.

  1. Set result = X.
  2. Look for an FD A → B in F with A ⊆ result and B ⊄ result.
  3. If found, set result = result ∪ B and repeat step 2.
  4. When no FD adds anything new, stop. X⁺ = result.
flowchart TD
  S(["Start: attribute set X and FD set F"]):::g --> I["result = X"]
  I --> L{"Is there an FD A to B in F with A inside result and B not inside result?"}
  L -- Yes --> U["result = result union B"]
  U --> L
  L -- No --> E(["Output: X plus = result"]):::g
Flowchart: attribute closure algorithm.
Worked example: computing closures

R(A, B, C, D, E) with F = { A → B, BC → D, D → E }.

Closure of {A}

Start {A}. A → B adds B: {A,B}. BC → D needs C, which we lack. D → E needs D. So A⁺ = {A, B}. Not a superkey.

Closure of {A, C}
StepFD usedresult
0start{A, C}
1A → B{A, B, C}
2BC → D{A, B, C, D}
3D → E{A, B, C, D, E}

(AC)⁺ = {A, B, C, D, E} = all of R, so AC is a superkey.

Quick Check

R(A,B,C,D), F = {A → B, B → C}. What is (AD)⁺?

Show answer

{A,D} → add B (A→B) → add C (B→C): {A,B,C,D}. AD is a superkey.

Try it: closure and candidate-key calculator

Enter attributes and FDs (one per line) to see closure steps and all candidate keys.

2.1.5 Identifying candidate keys

Definitions

Superkey: attribute set whose closure is all of R. Candidate key: a minimal superkey (no proper subset is a superkey). Prime attribute: belongs to some candidate key. Non-prime attribute: belongs to none.

  1. Find attributes that never appear on any right-hand side. They must be in every candidate key.
  2. Compute the closure of that set. If it covers R, it is the only candidate key.
  3. If not, add other attributes one at a time (then pairs, …) and test closures.
  4. Keep only minimal sets. Repeat to find all keys; the union of their attributes is the set of prime attributes.
flowchart TD
  A["List attributes of R and FDs in F"]:::g --> B["Find attributes never on any right-hand side"]
  B --> C["Compute closure of that set"]
  C --> D{"Closure contains all attributes?"}
  D -- Yes --> E["It is a candidate key and the only one"]:::g
  D -- No --> F["Add one more attribute from the rest"]
  F --> G["Compute closure of each combination"]
  G --> H{"Closure equals all attributes?"}
  H -- No --> F
  H -- Yes --> I["Candidate key if no proper subset is a key"]:::g
  I --> J["Try other combinations of the same size for more keys"]
  J --> K["Prime attributes are all attributes in any candidate key"]:::m
Flowchart: candidate key determination.
Worked example: relation with several candidate keys

R(A, B, C, D) with F = { AB → C, C → D, D → A }.

  • B never appears on a right-hand side, so B is in every key. B⁺ = {B}: not enough.
  • (AB)⁺: AB → C, then C → D: {A,B,C,D} ✔ key.
  • (BC)⁺: C → D, D → A, then AB → C: {A,B,C,D} ✔ key.
  • (BD)⁺: D → A, AB → C, C → D: {A,B,C,D} ✔ key.

Candidate keys: AB, BC, BD. Prime attributes: A, B, C, D (all). A relation whose attributes are all prime is always in 3NF.

Exam Tip

Always run the “never on a right-hand side” shortcut first. It saves most of the search, and attributes that appear only on the right can never be in any key.

2.1.6 Canonical (minimal) cover

Different FD sets can imply the same F⁺. A canonical cover Fc is an equivalent set with no redundancy: every FD has a single attribute on the right (in the textbook form), no extraneous attribute on any left side, and no FD that is implied by the others. It is the starting point for 3NF synthesis.

flowchart TD
  A["Given FD set F"]:::g --> B["Step 1: split right sides to single attributes"]
  B --> C["Step 2: remove extraneous attributes from left sides"]
  C --> D["Step 3: remove redundant FDs"]
  D --> E{"Did anything change in step 2 or 3?"}
  E -- Yes --> C
  E -- No --> F["Canonical cover Fc, optionally merge FDs with same left side"]:::g
Flowchart: canonical cover steps.
Tests

Extraneous attribute A in X → Y (A ∈ X): A is extraneous if (X − A) → Y can be derived, i.e. Y ⊆ (X − A)⁺ computed using F. Redundant FD: X → Y is redundant if Y ⊆ X⁺ computed with that FD removed.

Worked example: canonical cover

F = { A → BC, B → C, A → B, AB → C }

Step 1 · Single attribute on the right

A → B, A → C, B → C, A → B (duplicate, drop), AB → C. Now F = { A → B, A → C, B → C, AB → C }.

Step 2 · Extraneous left-side attributes

Check AB → C. Is A extraneous? Compute B⁺ under F: B → C gives {B, C}, which contains C. So A is extraneous: replace AB → C by B → C (a duplicate). F = { A → B, A → C, B → C }.

Step 3 · Redundant FDs

Test A → C. Without it, A⁺: A → B gives {A,B}; B → C gives {A,B,C}. C is reachable, so A → C is redundant. Remove it.

Fc = { A → B, B → C }. Check equivalence: A⁺ = {A,B,C} and B⁺ = {B,C}, so every original FD is still implied.

Common Mistake

Removing a redundant FD before removing extraneous attributes can give a different (and sometimes wrong) result. Follow the order: split, shrink left sides, then drop redundant FDs. A canonical cover is not always unique, but any valid one is acceptable.

2.1.7 Decomposition techniques

Decomposition replaces R by smaller schemas R1…Rn such that R1 ∪ … ∪ Rn = R. We decompose to remove redundancy and anomalies. A good decomposition must be lossless (the original data can be rebuilt exactly with joins) and preferably dependency preserving (every FD can be checked inside a single table).

Lossless-join test (binary)

Decomposing R into R1, R2 is lossless iff R1 ∩ R2 → R1 − R2 or R1 ∩ R2 → R2 − R1 is in F⁺. Equivalently: the common attributes are a superkey of R1 or R2.

Dependency preservation

Let Fi be the FDs of F⁺ whose attributes lie within Ri. The decomposition preserves dependencies iff (F₁ ∪ … ∪ Fₙ)⁺ = F⁺.

Full tests, tableau (chase) method and worked examples are in Section 4. Below is the verification flow you apply to any proposed decomposition.

flowchart TD
  A["Proposed decomposition of R into R1 ... Rn"]:::g --> B["Check 1: lossless join"]
  B --> C{"Binary case: does R1 intersect R2 determine R1 minus R2 or R2 minus R1?"}
  B --> N["More than 2 tables: use the chase tableau"]
  C -- No --> X["Lossy: reject or add common key attributes"]:::y
  C -- Yes --> D["Check 2: dependency preservation"]
  N --> D
  D --> E{"Is every FD of F implied by the projected FDs?"}
  E -- No --> W["FD lost: enforcing it needs a join"]:::y
  E -- Yes --> OK["Good decomposition: lossless and dependency preserving"]:::g
Flowchart: decomposition verification.

2.1.8 Applying dependency concepts in design

In practice you (1) gather business rules, (2) write them as FDs, MVDs and JDs, (3) compute candidate keys, (4) reduce F to a canonical cover, (5) decompose to the target normal form, (6) verify lossless join and dependency preservation, and (7) implement keys and constraints in SQL.


-- FDs RollNo -> Name, Dept and Dept -> HOD become keys in two tables
CREATE TABLE department (
  dept    VARCHAR2(10) PRIMARY KEY,           -- determinant of HOD
  hod     VARCHAR2(60) NOT NULL
);
CREATE TABLE student (
  roll_no NUMBER PRIMARY KEY,                 -- determinant of name, dept
  name    VARCHAR2(60) NOT NULL,
  dept    VARCHAR2(10) NOT NULL REFERENCES department(dept)
);
-- Rebuild the original relation: the join is lossless
SELECT s.roll_no, s.name, s.dept, d.hod
FROM   student s JOIN department d ON d.dept = s.dept;
Exam Tip

Every FD whose left side becomes a PRIMARY KEY or UNIQUE constraint is enforced for free by the DBMS. FDs that are not preserved need triggers or application checks.

Section 3 · Chapter 2.2

Normalization

This chapter covers the normalization process for improving database design. You learn 1NF, 2NF, 3NF, BCNF, 4NF, 5NF, and denormalization techniques to reduce redundancy and prevent update anomalies.

Chapter 2.2 learning outcomes
  • Explain the need and objectives of normalization.
  • Convert database schemas into 1NF, 2NF, 3NF, and BCNF.
  • Apply higher normal forms (4NF and 5NF) to complex database designs.
  • Identify insertion, deletion, and update anomalies.
  • Evaluate situations where denormalization may improve performance.

3.1 Need and objectives of normalization

Normalization is the systematic process of organizing attributes into relations using dependencies, so that each fact is stored once and only once.

Reduce redundancy

No repeated storage of the same fact, saving space and avoiding disagreement between copies.

Remove anomalies

Insert, update and delete operations work on one fact at a time without side effects.

Preserve meaning

Lossless decomposition keeps all information; keys and FDs enforce integrity.

3.2 Insertion, deletion and update anomalies

Consider this poorly designed table that mixes employees, departments and projects. Primary key: EmpID.

EmpIDEmpNameDeptIDDeptNameDeptHead
E1AshaD1SalesKiran
E2RaviD1SalesKiran
E3MeenaD2HRLatha
AnomalyWhat goes wrong in the table above
InsertionYou cannot add a new department D3 (Finance) until at least one employee is hired, since EmpID cannot be NULL.
DeletionDeleting E3 (Meena) also erases the fact that department D2 (HR, head Latha) exists.
UpdateRenaming Sales to Revenue needs edits in every Sales row; missing one leaves inconsistent data.
flowchart TD
  R["Facts about different entities stored in one table"]:::y --> RD["Redundant data"]
  RD --> I["Insertion anomaly: cannot add one fact without another"]
  RD --> D["Deletion anomaly: deleting one fact loses another"]
  RD --> U["Update anomaly: many rows to change, risk of inconsistency"]
  I --> S["Remedy: decompose using FDs into Employee and Department"]:::g
  D --> S
  U --> S
Flowchart: where anomalies come from and the cure.

3.3 Functional dependency review

Normal forms are defined entirely with FDs and keys. Keep these in mind: a determinant is the left side of an FD; a candidate key is a minimal set whose closure is all of R; a prime attribute lies in some candidate key. Revisit Chapter 2.1 if any term feels shaky.

3.4 The normalization ladder

flowchart TD
  U["Unnormalized: repeating groups, multi-valued cells"]:::y --> N1["1NF: atomic values, key defined"]:::m
  N1 --> N2["2NF: no partial dependency on a composite key"]:::m
  N2 --> N3["3NF: no transitive dependency for non-prime attributes"]:::m
  N3 --> BC["BCNF: every determinant is a superkey"]:::g
  BC --> N4["4NF: no non-trivial multi-valued dependency"]:::g
  N4 --> N5["5NF: every join dependency implied by candidate keys"]:::g
Each form is stricter than the one before (every 5NF relation is also in 4NF, BCNF, 3NF, 2NF and 1NF).

3.5 The normal forms, one by one

Definition · 1NF

A relation is in First Normal Form if every attribute holds a single atomic value (no lists, sets or repeating groups) and every row can be uniquely identified by a key.

Violation: Student(ID, Name, Phones) with Phones = "98100, 98200".

Conversion: (a) put one value per cell by making a separate row or a separate table, (b) define a primary key.

IDNamePhones (not 1NF)
1Asha98100, 98200
2Ravi98300
IDPhone (1NF)
198100
198200
298300

Name stays in Student(ID, Name); phones move to StudentPhone(ID, Phone) with key (ID, Phone).

3.6 Comparison of normal forms

NFCondition (in addition to the previous form)RemovesTypical fix
1NFAtomic values, no repeating groups, key definedMulti-valued cellsSeparate rows or tables
2NFNo partial dependency on a candidate keyPartial dependenciesMove partial dependents with their determinant
3NFFor every X → A: X superkey or A primeTransitive dependenciesSplit the transitive chain
BCNFFor every X → Y: X superkeyAnomalies from overlapping candidate keysDecompose on the violating FD
4NFFor every non-trivial X →→ Y: X superkeyIndependent multi-valued factsOne table per independent fact
5NFEvery join dependency implied by candidate keysCyclic join redundancyProject into the join components

3.7 Worked example: unnormalized → 1NF → 2NF → 3NF → BCNF

Start with one wide table for course registration. Business rules (FDs): StudentID → StudentName; StudentID ↠ many phone numbers; CourseID → CourseTitle, Instructor; Instructor → Room; (StudentID, CourseID) → Grade.

Step 0 · Unnormalized table
StudentIDStudentNamePhonesCourseIDCourseTitleInstructorRoomGrade
S1Asha98100, 98200C1, C2DBMS, OSRao, IyerR10, R20A, B
S2Ravi98300C1DBMSRaoR10B

Cells hold lists, so this violates 1NF.

Step 1 · 1NF: atomic values

Give each (student, course) pair its own row and move phones into their own table.


StudentPhone(StudentID, Phone)                                   key: (StudentID, Phone)
Registration(StudentID, StudentName, CourseID, CourseTitle,
             Instructor, Room, Grade)                            key: (StudentID, CourseID)
Step 2 · 2NF: remove partial dependencies

StudentName depends on StudentID only; CourseTitle, Instructor and Room depend on CourseID only (partial dependencies on the composite key).


Student(StudentID, StudentName)
Course(CourseID, CourseTitle, Instructor, Room)
Enrollment(StudentID, CourseID, Grade)
StudentPhone(StudentID, Phone)
Step 3 · 3NF: remove transitive dependencies

In Course: CourseID → Instructor and Instructor → Room, so CourseID → Room is transitive.


Course(CourseID, CourseTitle, Instructor)      -- Instructor is a foreign key
Instructor(Instructor, Room)
Student(StudentID, StudentName)
Enrollment(StudentID, CourseID, Grade)
StudentPhone(StudentID, Phone)
Step 4 · BCNF check

Check every determinant: StudentID (key of Student), CourseID (key of Course), Instructor (key of Instructor), (StudentID, CourseID) (key of Enrollment). Every determinant is a superkey, so the schema is already in BCNF. (A BCNF failure needs overlapping candidate keys, as in the CourseTeacher example in the BCNF tab.) With no independent multi-valued facts and no cyclic join rule, it is also in 4NF and 5NF.

Quick Check

R(A,B,C) with F = {AB → C, C → B}. Highest normal form?

Show answer

Keys: AB and AC. C → B has a non-superkey determinant, so BCNF fails, but B is prime so 3NF holds. Answer: 3NF.

Common Mistake

Forgetting that BCNF has no prime-attribute exception, and forgetting that a relation with a single-attribute key is automatically in 2NF.

3.8 Denormalization

Denormalization deliberately introduces controlled redundancy to a normalized schema to speed up reads. It is a performance decision made after normalizing, never a replacement for normalization.

TechniqueExampleCost
Derived / stored columnStore order_total in OrdersMust stay in sync with line items
Pre-joined / merged tableCopy customer_name into OrdersUpdate anomalies on rename
Summary table / materialized viewDaily sales per branchRefresh time, storage
Repeating group as columnsphone1, phone2Violates 1NF, limited flexibility
flowchart TD
  A["Schema normalized to 3NF or BCNF"]:::g --> B{"Measured performance problem?"}
  B -- No --> C["Stay normalized"]:::g
  B -- Yes --> D{"Read-heavy workload with costly joins?"}
  D -- No --> E["Tune indexes, queries and caching first"]:::m
  D -- Yes --> F{"Can redundancy be kept consistent by triggers or batch refresh?"}
  F -- No --> E
  F -- Yes --> G["Denormalize selectively"]:::y
  G --> H["Document it and monitor consistency"]
When denormalization is justified.

Use it when: reporting or analytics dominate, joins across many tables are provably the bottleneck, data is mostly read-only (data warehouses, dashboards). Avoid it when: the workload is write-heavy or data accuracy is critical (banking balances).

Exam Tip

Always state the trade-off: faster reads versus redundancy, extra storage and the risk of update anomalies. Mention that triggers or materialized views can keep the redundant copy consistent.

Section 4 · Evaluating decompositions

Decomposition, Lossless Join & Dependency Preservation

Unit outcome 3: evaluate database decompositions for lossless join and dependency preservation properties.

4.1 Why decompose?

To eliminate the redundancy and anomalies identified by normal-form violations. Splitting a relation is safe only when it keeps (1) all the information (lossless join) and ideally (2) all the constraints (dependency preservation).

flowchart TD
  A["Relation R with FD set F"]:::g --> B{"Anomalies or redundancy present?"}
  B -- No --> Z["Keep R as it is"]
  B -- Yes --> C{"R already in BCNF?"}
  C -- Yes --> D["Check 4NF and 5NF, otherwise done"]
  C -- No --> E["Pick violating FD X to Y: split into X union Y and R minus Y"]
  E --> F["Lossless guaranteed since X is a key of X union Y"]:::g
  F --> G{"Are all FDs preserved?"}
  G -- Yes --> H["Accept BCNF decomposition"]:::g
  G -- No --> I{"Is losing the FD acceptable?"}
  I -- Yes --> H
  I -- No --> J["Use 3NF synthesis: lossless and dependency preserving"]:::m
Decomposition decision tree.

4.2 Lossless join

Definition

A decomposition of R into R1, …, Rn is lossless if for every valid instance r of R, r = πR1(r) ⋈ … ⋈ πRn(r). A lossy decomposition generates spurious tuples on joining.

Binary test

R1 ∩ R2 → R1 − R2 or R1 ∩ R2 → R2 − R1 (in F⁺)
Example: lossy versus lossless on R(A, B, C)

Instance r: (1, x, p) and (2, x, q). FD set F = { A → B }.

Decomposition {AB, BC}: common = B

B → A? No. B → C? No. Not lossless. Check with data: πAB = {(1,x),(2,x)}, πBC = {(x,p),(x,q)}. Join gives 4 tuples: (1,x,p), (1,x,q), (2,x,p), (2,x,q). Two are spurious ✘.

Decomposition {AB, AC}: common = A

A → B holds, and B = R1 − R2. Lossless ✔. Check: πAB = {(1,x),(2,x)}, πAC = {(1,p),(2,q)}. Join on A gives exactly (1,x,p), (2,x,q).

flowchart TD
  A["Decomposition D with R1 ... Rn of R"]:::g --> B{"Only two relations?"}
  B -- Yes --> C["common = R1 intersect R2"]
  C --> D["Compute closure of common under F"]
  D --> E{"Closure contains R1 or R2?"}
  E -- Yes --> L["Lossless join"]:::g
  E -- No --> M["Lossy join"]:::y
  B -- No --> F["Build tableau: one row per Ri, distinguished symbol where attribute in Ri"]
  F --> G["Apply each FD: equate symbols, prefer distinguished ones"]
  G --> H{"Any row entirely distinguished?"}
  H -- Yes --> L
  H -- No --> I{"Any further change possible?"}
  I -- Yes --> G
  I -- No --> M
Lossless join test flowchart (binary test and chase tableau).
Example: chase tableau for three relations

R(A,B,C,D), F = { B → C, C → D }, decomposition R1(A,B), R2(B,C), R3(C,D). Distinguished symbols are a1…a4; b-symbols are unknowns.

RowABCD
R1a1a2b13b14
R2b21a2a3b24
R3b31b32a3a4
  1. B → C: rows R1 and R2 agree on B (a2), so set R1.C = a3.
  2. C → D: R1, R2 and R3 now agree on C (a3), so equate their D values to the distinguished a4.
  3. R1 becomes (a1, a2, a3, a4): fully distinguished ⇒ lossless.

4.3 Dependency preservation

Definition

Let Fi be the set of FDs in F⁺ that involve only attributes of Ri. The decomposition is dependency preserving if (F₁ ∪ F₂ ∪ … ∪ Fₙ)⁺ = F⁺, i.e. every FD can be enforced by checking single tables, without joins.

Practical test (avoids computing F⁺): for each FD X → Y in F, set result = X and repeat result = result ∪ ((result ∩ Ri)⁺ ∩ Ri) for every Ri until nothing changes. X → Y is preserved iff Y ⊆ result.

flowchart TD
  A["For each FD X to Y in F"]:::g --> B["result = X"]
  B --> C["For each Ri: result = result union closure of result intersect Ri, restricted to Ri"]
  C --> D{"Did result change in this pass?"}
  D -- Yes --> C
  D -- No --> E{"Is Y inside result?"}
  E -- Yes --> F["This FD is preserved"]:::g
  E -- No --> G["FD lost: decomposition is not dependency preserving"]:::y
Dependency preservation check.
Example: R(A,B,C) with F = { A → B, B → C }
Decomposition D1 = {AB, AC}
  • Lossless: common = A, A → B ✔
  • A → B lies in AB ✔ preserved.
  • B → C: result = {B}. AB: (B ∩ AB)⁺ ∩ AB = {B,C} ∩ AB = {B}. AC: B ∩ AC = ∅, adds nothing. result stays {B}, C ∉ result ✘ lost.

Lossless but not dependency preserving.

Decomposition D2 = {AB, BC}
  • Lossless: common = B, B → C (C = R2 − R1) ✔
  • A → B inside AB ✔; B → C inside BC ✔.

Lossless and dependency preserving. This is the better design (also the 3NF synthesis result).

4.4 Trade-offs

Goal3NF synthesisBCNF decomposition
Lossless joinAlwaysAlways
Dependency preservationAlwaysNot guaranteed
Redundancy removedMostly (some FD-related redundancy may remain)Maximum for FDs
Query costMore joins than the originalPossibly most joins

More decomposition means more tables and more joins. Normalization optimizes write correctness and storage; read performance may need indexes, materialized views or selective denormalization (see 3.8). Aim for a lossless, dependency-preserving 3NF or BCNF design, then measure.

Exam Tip

Remember the headline facts: BCNF is always lossless but may not preserve dependencies; 3NF can always achieve both. A decomposition missing common attributes between its parts is almost always lossy.

Section 5 · Chapter 2.3

Database Security

This chapter introduces database security concepts, common security threats, vulnerabilities, and protection mechanisms. It emphasizes access control, authentication, authorization, and countermeasures against attacks.

Chapter 2.3 learning outcomes
  • Explain the importance of database security in organizations.
  • Identify common database threats and vulnerabilities.
  • Differentiate between authentication and authorization mechanisms.
  • Recommend suitable countermeasures to secure database systems.
  • Apply security principles to protect data confidentiality, integrity, and availability.

5.1 Why database security matters

Databases hold an organization’s most valuable assets: customer identities, financial records, health data, trade secrets. A breach causes financial loss, legal penalties (privacy laws such as GDPR and India’s DPDP Act), loss of customer trust and operational downtime. Security must be built in to the schema, the access rules and the code, not added afterwards.

5.2 The CIA triad

flowchart TD
  C(["CIA triad"]):::g
  C --> CO["Confidentiality: only authorized parties read data. Tools: access control, encryption, masking"]:::m
  C --> IN["Integrity: data stays accurate and changes only in authorized ways. Tools: constraints, hashing, audit, transactions"]:::m
  C --> AV["Availability: data is reachable when needed. Tools: backups, replication, patching, DoS defence"]:::m
The three security goals every control supports.
GoalViolated byProtected with
ConfidentialityData leakage, stolen backups, SQL injection readsAuthentication, GRANT/REVOKE, views, encryption
IntegrityUnauthorized UPDATE, tampered logsConstraints, triggers, hashing, least privilege, audit
AvailabilityDoS, ransomware, disk failureBackups, replication, firewalls, patching

5.3 Threats and vulnerabilities

A threat is a potential cause of harm (an attacker, malware, a careless insider). A vulnerability is a weakness that a threat can exploit (an unpatched server, a weak password). Risk arises when a threat meets a vulnerability and the asset has value. A countermeasure reduces that risk.

flowchart TD
  TA["Threat agent: external attacker, insider, malware"]:::y --> T["Threat: SQL injection, privilege escalation, data leakage"]
  T --> V["Vulnerability: weak password, unpatched server, misconfiguration"]
  V --> AS["Asset at risk: confidentiality, integrity, availability of data"]:::g
  C["Countermeasures: least privilege, encryption, auditing, patching"]:::m -. blocks .-> V
  C -. detects .-> T
Threat → vulnerability → asset, with countermeasures cutting the chain.
Threat / vulnerabilityHow it worksImpactCountermeasures
SQL injectionAttacker input is concatenated into SQL (' OR '1'='1)Data theft, modification, auth bypassBind variables, input validation, least privilege, WAF
Privilege escalationExploiting bugs or excess rights to gain DBA powerFull control of dataLeast privilege, patching, role review, auditing
Insider threatEmployee misuses legitimate accessLeakage, fraudSeparation of duties, auditing, views, monitoring
Malware / ransomwareMalicious code encrypts or steals dataDowntime, data lossEndpoint protection, offline backups, network segmentation
Social engineeringPhishing or impersonation to obtain credentialsAccount takeoverTraining, MFA, restricted admin accounts
Weak authenticationDefault or guessable passwords, no lockoutUnauthorized loginStrong password policy, MFA, account lockout profiles
MisconfigurationOpen ports, default accounts, public cloud bucketsEasy attack surfaceHardening baselines, remove defaults, firewalls
Data leakageOver-broad queries, logs or exports expose sensitive dataPrivacy violationsViews, masking, encryption, DLP, column-level rights
Backup exposureUnencrypted backups stored or shipped carelesslyBypasses all access controlsEncrypt backups, restrict storage access, test restores
SQL injection: vulnerable code versus safe code

-- VULNERABLE: input is concatenated into the statement
v_sql := 'SELECT * FROM customer WHERE email = ''' || p_email || '''';
EXECUTE IMMEDIATE v_sql;
-- p_email = x' OR '1'='1   returns every customer

-- SAFE: bind variable keeps input as data
EXECUTE IMMEDIATE 'SELECT full_name FROM customer WHERE email = :e'
  INTO v_name USING p_email;

5.4 Authentication versus authorization

AuthenticationAuthorization
QuestionWho are you?What are you allowed to do?
HappensFirst, at loginAfter authentication, on every operation
MechanismsPasswords, MFA, certificates, biometrics, SSO/KerberosGRANT/REVOKE, roles, views, row-level security
Failure resultLogin rejected“Insufficient privileges”
ExampleCONNECT asha/secretGRANT SELECT ON account TO asha
sequenceDiagram
  autonumber
  participant U as User
  participant A as Authentication
  participant Z as Authorization
  participant D as Database
  U->>A: Username and password or token
  A->>A: Verify identity
  alt Identity invalid
    A-->>U: Login rejected
  else Identity valid
    A-->>U: Session established
    U->>Z: SELECT salary FROM employee
    Z->>Z: Check privileges and roles
    alt Not permitted
      Z-->>U: Insufficient privileges error
    else Permitted
      Z->>D: Execute query
      D-->>U: Rows returned
    end
  end
Swimlane: authentication happens first, authorization on every request.

5.5 Access control models

Discretionary Access Control: the owner of an object decides who gets which privilege, and may pass the privilege on (WITH GRANT OPTION). Flexible, but risky because users can leak rights.


GRANT SELECT, UPDATE (status) ON account TO asha;
GRANT SELECT ON account TO ravi WITH GRANT OPTION;
REVOKE UPDATE ON account FROM asha;
ModelWho decides?StrengthWeakness
DACObject ownerFlexible, simpleRights can spread, weak against Trojans
MACCentral policy and labelsStrongest control of information flowRigid, administrative cost
RBACAdministrator via rolesScalable, supports least privilegeRole explosion if badly designed

5.6 Countermeasures

ControlWhat it doesExample
Least privilegeGive only the rights needed for the jobReporting user gets SELECT only
EncryptionProtects data at rest (TDE) and in transit (TLS)Encrypted tablespaces and backups
HashingOne-way digest for passwords and integrity checks (salted, slow algorithms)Store bcrypt/PBKDF2 hash, not password
ViewsExpose only selected rows or columnsView without salary column
Stored proceduresControlled, validated access path; users get EXECUTE, not table rightsGRANT EXECUTE ON transfer_funds
AuditingRecord who did what and whenAUDIT SELECT ON account, audit triggers
FirewallsLimit network access to the DB portAllow only app servers
IDS / IPSDetect (IDS) or block (IPS) malicious patternsAlert on mass SELECTs
BackupsRecover from loss or ransomwareEncrypted, offline, tested restore
PatchingCloses known vulnerabilitiesApply vendor critical patch updates

-- Views: hide sensitive columns and rows
CREATE VIEW v_emp_public AS
  SELECT emp_id, name, dept_id FROM employees;
GRANT SELECT ON v_emp_public TO hr_clerk;

-- Authentication hardening: lockout and password lifetime
CREATE PROFILE secure_profile LIMIT
  FAILED_LOGIN_ATTEMPTS 5
  PASSWORD_LIFE_TIME   90;
ALTER USER asha PROFILE secure_profile;

-- Auditing
AUDIT SELECT, UPDATE ON account BY ACCESS;
Applying the CIA triad

Pick the control by the goal: C → encryption, views, GRANT; I → constraints, triggers, hashing, audit; A → backups, replication, patching, DoS protection. Combining several layers is called defence in depth.

Exam Tip

For “recommend a countermeasure” questions, name the threat, the vulnerability it exploits and the control, e.g. SQL injection exploits unvalidated input concatenated into SQL; use bind variables and least privilege.

Common Mistake

Confusing authentication with authorization, or thinking encryption alone solves everything. An authenticated, authorized insider can still leak data, so auditing is essential.

Quick Check

A report user can read the salary column although they only need names. Which principle was violated and how do you fix it?

Show answer

Least privilege. Revoke table access and grant SELECT on a view that omits salary.

Section 6 · PL/SQL programming

PL/SQL Programming

The unit title includes PL/SQL Programming. PL/SQL is Oracle’s procedural extension to SQL: it lets you run logic, loops, error handling and reusable code inside the database, next to the data.

6.1 Block structure

Every PL/SQL program is made of blocks. A block has up to four sections: DECLARE (optional), BEGIN (mandatory), EXCEPTION (optional) and END. Blocks can be nested and can be anonymous or named (procedures, functions, triggers, packages).

flowchart TD
  S(["Block starts"]):::g --> D["DECLARE optional: variables, constants, cursors, user exceptions"]
  D --> B["BEGIN mandatory: executable SQL and PL/SQL statements"]
  B --> Q{"Runtime error raised?"}
  Q -- No --> E(["END"]):::g
  Q -- Yes --> X["EXCEPTION optional: matching WHEN handler runs"]:::y
  X --> E
Flowchart: PL/SQL block structure.

DECLARE
  v_name  employees.name%TYPE;
  c_bonus CONSTANT NUMBER(4,2) := 0.10;
BEGIN
  SELECT name INTO v_name FROM employees WHERE emp_id = 101;
  DBMS_OUTPUT.PUT_LINE('Hello ' || v_name);
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    DBMS_OUTPUT.PUT_LINE('No such employee');
END;
/

6.2 Variables, constants and data types

ItemSyntax / meaning
Variablev_count NUMBER := 0;
Constantc_rate CONSTANT NUMBER := 0.18; (value fixed, must be initialised)
Scalar typesNUMBER, PLS_INTEGER, VARCHAR2, CHAR, DATE, TIMESTAMP, BOOLEAN, CLOB, RAW
%TYPECopies the data type of a column or variable: v_sal employees.salary%TYPE;
%ROWTYPEA record shaped like a table row or cursor row: r employees%ROWTYPE; then use r.name

%TYPE and %ROWTYPE make code adapt automatically when column definitions change, which is valuable when you normalize or restructure tables.


DECLARE
  r employees%ROWTYPE;
BEGIN
  SELECT * INTO r FROM employees WHERE emp_id = 101;
  DBMS_OUTPUT.PUT_LINE(r.name || ' earns ' || r.salary);
END;
/

6.3 Control structures


DECLARE
  v_sal NUMBER := 52000;
  i     PLS_INTEGER := 1;
  v_tot NUMBER := 0;
BEGIN
  -- IF / ELSIF / ELSE
  IF v_sal >= 80000 THEN
    DBMS_OUTPUT.PUT_LINE('Grade A');
  ELSIF v_sal >= 50000 THEN
    DBMS_OUTPUT.PUT_LINE('Grade B');
  ELSE
    DBMS_OUTPUT.PUT_LINE('Grade C');
  END IF;

  -- CASE
  CASE
    WHEN v_sal > 60000 THEN DBMS_OUTPUT.PUT_LINE('High');
    WHEN v_sal > 30000 THEN DBMS_OUTPUT.PUT_LINE('Medium');
    ELSE DBMS_OUTPUT.PUT_LINE('Low');
  END CASE;

  -- Basic LOOP with EXIT WHEN
  LOOP
    v_tot := v_tot + i;
    i := i + 1;
    EXIT WHEN i > 5;
  END LOOP;

  -- WHILE loop
  WHILE i > 0 LOOP
    i := i - 2;
  END LOOP;

  -- Numeric FOR loop (counter is implicit)
  FOR k IN 1..3 LOOP
    DBMS_OUTPUT.PUT_LINE('k = ' || k);
  END LOOP;
END;
/

6.4 Cursors

A cursor is a pointer to the private work area that holds the rows returned by a query. Implicit cursors are created automatically for every DML statement and single-row SELECT INTO; their attributes are read as SQL%FOUND, SQL%NOTFOUND, SQL%ROWCOUNT. Explicit cursors are declared by you to process multi-row results one row at a time.

sequenceDiagram
  participant P as PL/SQL program
  participant C as Cursor
  participant DB as Database
  P->>C: DECLARE CURSOR c IS SELECT ...
  P->>C: OPEN c
  C->>DB: Parse and execute the query
  DB-->>C: Active set of rows
  loop until c%NOTFOUND
    P->>C: FETCH c INTO variables
    C-->>P: Next row
  end
  P->>C: CLOSE c
  C-->>P: Resources released
Cursor lifecycle: declare, open, fetch, close.
Implicit and explicit cursor examples

-- Implicit cursor attribute after DML
BEGIN
  UPDATE employees SET salary = salary * 1.05 WHERE dept_id = 10;
  DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT || ' rows updated');
END;
/

-- Explicit cursor: DECLARE, OPEN, FETCH, CLOSE
DECLARE
  CURSOR c_emp(p_dept NUMBER) IS
    SELECT emp_id, name, salary FROM employees WHERE dept_id = p_dept;
  r c_emp%ROWTYPE;
BEGIN
  OPEN c_emp(10);
  LOOP
    FETCH c_emp INTO r;
    EXIT WHEN c_emp%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE(r.name || ': ' || r.salary);
  END LOOP;
  CLOSE c_emp;
END;
/

-- Cursor FOR loop: opens, fetches and closes automatically
BEGIN
  FOR r IN (SELECT name, salary FROM employees WHERE dept_id = 10) LOOP
    DBMS_OUTPUT.PUT_LINE(r.name || ': ' || r.salary);
  END LOOP;
END;
/
Common Mistake

Forgetting CLOSE leaks cursor resources, and checking %NOTFOUND after processing the row instead of right after FETCH makes the last row run twice.

6.5 Stored procedures and functions

A procedure performs an action and may return values through OUT parameters. A function must RETURN one value and can be used inside SQL. Both are stored in the database, compiled once and reused; they also give a controlled access path for security.


CREATE OR REPLACE PROCEDURE raise_salary (
  p_emp_id IN employees.emp_id%TYPE,
  p_pct    IN NUMBER
) AS
BEGIN
  UPDATE employees
     SET salary = salary * (1 + p_pct / 100)
   WHERE emp_id = p_emp_id;
  IF SQL%ROWCOUNT = 0 THEN
    RAISE_APPLICATION_ERROR(-20001, 'Employee not found');
  END IF;
END raise_salary;
/

CREATE OR REPLACE FUNCTION get_balance (p_acct IN NUMBER)
RETURN NUMBER IS
  v_bal account.balance%TYPE;
BEGIN
  SELECT balance INTO v_bal FROM account WHERE account_id = p_acct;
  RETURN v_bal;
EXCEPTION
  WHEN NO_DATA_FOUND THEN RETURN NULL;
END get_balance;
/

-- Calling them
BEGIN raise_salary(101, 5); END;
/
SELECT get_balance(5001) FROM dual;
ProcedureFunction
ReturnsNothing (or via OUT)Exactly one value (RETURN)
Call from SQLNoYes (if side-effect free)
Typical useActions: transfer, updateComputations: tax, balance

6.6 Triggers

A trigger is a stored block that Oracle runs automatically when a specified event occurs (DML, DDL or system events). Options: BEFORE/AFTER/INSTEAD OF, FOR EACH ROW (row-level, with :OLD and :NEW) or statement-level.

flowchart TD
  A["DML statement: INSERT, UPDATE or DELETE"]:::g --> B["BEFORE STATEMENT trigger runs once"]
  B --> C["For each affected row: BEFORE ROW trigger"]
  C --> D["Row change applied"]
  D --> E["AFTER ROW trigger"]
  E --> F{"More rows?"}
  F -- Yes --> C
  F -- No --> G["AFTER STATEMENT trigger"]
  G --> H{"Any error raised?"}
  H -- No --> I["Statement completes, commit possible"]:::g
  H -- Yes --> J["Statement rolled back with error"]:::y
Trigger execution order for one DML statement.

-- Business rule: salary may never decrease
CREATE OR REPLACE TRIGGER trg_no_salary_cut
BEFORE UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
  IF :NEW.salary < :OLD.salary THEN
    RAISE_APPLICATION_ERROR(-20002, 'Salary cannot be reduced');
  END IF;
END;
/

-- Keep a denormalized summary column consistent
CREATE OR REPLACE TRIGGER trg_order_total
AFTER INSERT OR DELETE ON order_item
FOR EACH ROW
BEGIN
  IF INSERTING THEN
    UPDATE orders SET total = total + :NEW.qty * :NEW.price WHERE order_id = :NEW.order_id;
  ELSIF DELETING THEN
    UPDATE orders SET total = total - :OLD.qty * :OLD.price WHERE order_id = :OLD.order_id;
  END IF;
END;
/

6.7 Exception handling

When a runtime error occurs, control jumps to the EXCEPTION section. Predefined exceptions have names; user-defined ones are declared and raised by you.

Predefined exceptionRaised when
NO_DATA_FOUNDSELECT INTO returns no row
TOO_MANY_ROWSSELECT INTO returns more than one row
ZERO_DIVIDEDivision by zero
DUP_VAL_ON_INDEXDuplicate value violates a unique index or primary key
VALUE_ERRORConversion or size error
INVALID_CURSORIllegal cursor operation
OTHERSCatch-all handler, always last
flowchart TD
  A["A statement raises an exception"]:::y --> B{"Handler for it in the current block?"}
  B -- Yes --> C["Run handler, block ends normally"]:::g
  B -- No --> D{"Enclosing block exists?"}
  D -- Yes --> E["Propagate exception to the enclosing block"]
  E --> B
  D -- No --> F["Unhandled: error returned to the caller"]:::y
  C --> G["Execution continues after the block"]
Exception propagation.

DECLARE
  e_low_balance EXCEPTION;                 -- user-defined
  v_bal NUMBER;
BEGIN
  SELECT balance INTO v_bal FROM account WHERE account_id = 5001;
  IF v_bal < 500 THEN
    RAISE e_low_balance;
  END IF;
  DBMS_OUTPUT.PUT_LINE('OK');
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    DBMS_OUTPUT.PUT_LINE('Account does not exist');
  WHEN e_low_balance THEN
    DBMS_OUTPUT.PUT_LINE('Minimum balance violated');
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('Error ' || SQLCODE || ': ' || SQLERRM);
    RAISE;                                  -- re-raise to the caller
END;
/

Use RAISE_APPLICATION_ERROR(-20000 … -20999, 'message') to send custom errors to client applications, and PRAGMA EXCEPTION_INIT to give a name to an Oracle error number.

6.8 Packages

A package groups related procedures, functions, variables, cursors and types. It has a specification (public interface) and a body (implementation, plus private items). Benefits: modularity, information hiding, load-once performance and a single object to GRANT EXECUTE on.


CREATE OR REPLACE PACKAGE emp_pkg AS
  PROCEDURE hire (p_name VARCHAR2, p_salary NUMBER);
  FUNCTION  dept_total (p_dept NUMBER) RETURN NUMBER;
END emp_pkg;
/

CREATE OR REPLACE PACKAGE BODY emp_pkg AS
  FUNCTION valid_salary (p_salary NUMBER) RETURN BOOLEAN IS   -- private
  BEGIN
    RETURN p_salary BETWEEN 10000 AND 500000;
  END;

  PROCEDURE hire (p_name VARCHAR2, p_salary NUMBER) IS
  BEGIN
    IF NOT valid_salary(p_salary) THEN
      RAISE_APPLICATION_ERROR(-20003, 'Invalid salary');
    END IF;
    INSERT INTO employees(emp_id, name, salary)
    VALUES (emp_seq.NEXTVAL, p_name, p_salary);
  END;

  FUNCTION dept_total (p_dept NUMBER) RETURN NUMBER IS
    v_total NUMBER;
  BEGIN
    SELECT NVL(SUM(salary), 0) INTO v_total FROM employees WHERE dept_id = p_dept;
    RETURN v_total;
  END;
END emp_pkg;
/

6.9 How PL/SQL connects to security, constraints and normalized design

Security
  • Users get EXECUTE on procedures, not rights on tables (controlled access path).
  • Bind variables defeat SQL injection.
  • Audit triggers record who changed what.
  • AUTHID DEFINER (default) runs with the owner’s rights; AUTHID CURRENT_USER uses the caller’s.
Constraints and integrity
  • Triggers enforce rules that declarative constraints cannot (for example, an FD that was not preserved by decomposition).
  • Exceptions give safe, atomic transactions (rollback on failure).
  • Prefer PRIMARY KEY, FOREIGN KEY, CHECK first; use PL/SQL for the rest.
Normalized schema design
  • %TYPE / %ROWTYPE follow column changes after restructuring.
  • Packages hide the multi-table joins that normalization creates.
  • Triggers keep deliberate denormalized copies consistent.
Availability
  • Stored code reduces network round trips.
  • Bulk operations (BULK COLLECT, FORALL) reduce load.
  • Scheduled PL/SQL jobs can refresh summaries and purge audit data.
Exam Tip

Remember: procedure = action, function = value, trigger = automatic event reaction, package = spec + body, cursor = row-by-row query processing. Always list the four block sections in order.

Quick Check

Which attribute tells you how many rows the last UPDATE changed?

Show answer

SQL%ROWCOUNT (implicit cursor attribute).

Section 7 · Real-world secure schema design

Banking Application: Efficient and Secure Schema

Unit outcome 5: design efficient and secure relational schemas. We design a retail bank database, normalize it, secure it and automate rules with PL/SQL.

Step 1 · ER diagram
erDiagram
  CUSTOMER ||--o{ ACCOUNT : holds
  BRANCH ||--o{ ACCOUNT : hosts
  BRANCH ||--o{ EMPLOYEE : employs
  ACCOUNT ||--o{ ACCT_TRANSACTION : records
  EMPLOYEE ||--o{ ACCT_TRANSACTION : processes
  CUSTOMER {
    int customer_id PK
    varchar full_name
    varchar email
    varchar phone
    raw national_id_enc
  }
  BRANCH {
    int branch_id PK
    varchar branch_name
    varchar city
  }
  ACCOUNT {
    int account_id PK
    int customer_id FK
    int branch_id FK
    varchar account_type
    number balance
    varchar status
  }
  EMPLOYEE {
    int emp_id PK
    int branch_id FK
    varchar name
    varchar role
  }
  ACCT_TRANSACTION {
    int txn_id PK
    int account_id FK
    int processed_by FK
    varchar txn_type
    number amount
    timestamp txn_time
  }
Entities and relationships. The AUDIT_LOG table (section 7.4) is deliberately standalone so that it cannot be altered through business relations.

7.1 Normalization steps

A first draft stores everything in one table, BankFlat, key TxnID:


BankFlat(TxnID, AccountNo, CustName, CustEmail, CustPhones, BranchID,
         BranchName, BranchCity, AccType, Balance, TxnType, TxnAmount, TxnTime)

Business rules: TxnID → AccountNo, TxnType, TxnAmount, TxnTime; AccountNo → CustName, CustEmail, BranchID, AccType, Balance; BranchID → BranchName, BranchCity.

StepProblem foundFix
1NFCustPhones holds several numbersSeparate row or table per phone (or keep one primary phone, as in the ER diagram)
2NFKey is the single column TxnID, so there are no partial dependenciesAlready 2NF
3NFTxnID → AccountNo → CustName / Balance (transitive) and AccountNo → BranchID → BranchNameSplit into Transaction, Account, Customer, Branch
BCNFEvery determinant (TxnID, AccountNo, BranchID, CustomerID) is a key of its tableAlready BCNF
4NF / 5NFNo independent multi-valued facts or cyclic join rules in a single tableSatisfied

The decomposition is lossless (each split table shares a foreign key that is the key of its parent) and dependency preserving (each FD is contained in one table).


CREATE TABLE branch (
  branch_id   NUMBER PRIMARY KEY,
  branch_name VARCHAR2(60) NOT NULL,
  city        VARCHAR2(40) NOT NULL
);
CREATE TABLE customer (
  customer_id     NUMBER PRIMARY KEY,
  full_name       VARCHAR2(80) NOT NULL,
  email           VARCHAR2(100) NOT NULL UNIQUE,
  phone           VARCHAR2(15),
  national_id_enc RAW(256)                       -- stored encrypted
);
CREATE TABLE employee (
  emp_id    NUMBER PRIMARY KEY,
  branch_id NUMBER NOT NULL REFERENCES branch(branch_id),
  name      VARCHAR2(80) NOT NULL,
  role      VARCHAR2(20) CHECK (role IN ('TELLER','MANAGER','AUDITOR'))
);
CREATE TABLE account (
  account_id   NUMBER PRIMARY KEY,
  customer_id  NUMBER NOT NULL REFERENCES customer(customer_id),
  branch_id    NUMBER NOT NULL REFERENCES branch(branch_id),
  account_type VARCHAR2(10) CHECK (account_type IN ('SAVINGS','CURRENT')),
  balance      NUMBER(14,2) DEFAULT 0 CHECK (balance >= 0),
  status       VARCHAR2(10) DEFAULT 'ACTIVE' CHECK (status IN ('ACTIVE','FROZEN','CLOSED'))
);
CREATE TABLE acct_transaction (
  txn_id       NUMBER PRIMARY KEY,
  account_id   NUMBER NOT NULL REFERENCES account(account_id),
  processed_by NUMBER REFERENCES employee(emp_id),
  txn_type     VARCHAR2(6) CHECK (txn_type IN ('DEBIT','CREDIT')),
  amount       NUMBER(14,2) NOT NULL CHECK (amount > 0),
  txn_time     TIMESTAMP DEFAULT SYSTIMESTAMP
);
CREATE TABLE audit_log (
  audit_id   NUMBER PRIMARY KEY,
  table_name VARCHAR2(30),
  operation  VARCHAR2(10),
  changed_by VARCHAR2(60),
  changed_at TIMESTAMP,
  old_value  VARCHAR2(200),
  new_value  VARCHAR2(200)
);
CREATE SEQUENCE audit_seq;

7.2 Security controls

ConcernControl in this designCIA goal
Who may log inProfiles with lockout and password lifetime, MFA at the applicationC
Who may do whatRBAC: teller, manager, auditor roles, privileges only via rolesC, I
Sensitive columnsNational ID encrypted; tellers see a view without itC
Money correctnessCHECK constraints, FKs, procedure with transaction and exceptionsI
Tamper evidenceAudit trigger writes to audit_log; auditors have SELECT only, nobody can UPDATE/DELETEI
InjectionOnly bind variables in PL/SQL; users have EXECUTE on procedures onlyC, I
RecoveryEncrypted scheduled backups, tested restore, patched serversA

CREATE ROLE teller_role;
CREATE ROLE manager_role;
CREATE ROLE auditor_role;

-- Tellers see customers through a view without the national ID
CREATE VIEW v_customer_public AS
  SELECT customer_id, full_name, email, phone FROM customer;
GRANT SELECT ON v_customer_public TO teller_role;
GRANT SELECT ON account TO teller_role;

-- Money moves only through the procedure
GRANT EXECUTE ON transfer_funds TO teller_role;

GRANT SELECT ON audit_log TO auditor_role;
GRANT teller_role TO manager_role;           -- role hierarchy
GRANT manager_role TO meena;
GRANT teller_role  TO asha;
GRANT auditor_role TO ravi;

7.3 PL/SQL for secure money transfer


CREATE OR REPLACE PROCEDURE transfer_funds (
  p_from   IN account.account_id%TYPE,
  p_to     IN account.account_id%TYPE,
  p_amount IN NUMBER
) AS
  v_balance account.balance%TYPE;
BEGIN
  IF p_amount IS NULL OR p_amount <= 0 THEN
    RAISE_APPLICATION_ERROR(-20011, 'Amount must be positive');
  END IF;

  -- Lock the source row so that concurrent transfers cannot overdraw it
  SELECT balance INTO v_balance
    FROM account WHERE account_id = p_from AND status = 'ACTIVE' FOR UPDATE;

  IF v_balance < p_amount THEN
    RAISE_APPLICATION_ERROR(-20010, 'Insufficient funds');
  END IF;

  UPDATE account SET balance = balance - p_amount WHERE account_id = p_from;
  UPDATE account SET balance = balance + p_amount WHERE account_id = p_to;

  INSERT INTO acct_transaction (txn_id, account_id, txn_type, amount)
  VALUES (txn_seq.NEXTVAL, p_from, 'DEBIT',  p_amount);
  INSERT INTO acct_transaction (txn_id, account_id, txn_type, amount)
  VALUES (txn_seq.NEXTVAL, p_to,   'CREDIT', p_amount);

  COMMIT;
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    ROLLBACK;
    RAISE_APPLICATION_ERROR(-20012, 'Source account not found or not active');
  WHEN OTHERS THEN
    ROLLBACK;
    RAISE;
END transfer_funds;
/

7.4 Audit and access-control triggers


-- Audit every balance/status change and every delete on ACCOUNT
CREATE OR REPLACE TRIGGER trg_account_audit
AFTER UPDATE OF balance, status OR DELETE ON account
FOR EACH ROW
BEGIN
  INSERT INTO audit_log
    (audit_id, table_name, operation, changed_by, changed_at, old_value, new_value)
  VALUES
    (audit_seq.NEXTVAL, 'ACCOUNT',
     CASE WHEN DELETING THEN 'DELETE' ELSE 'UPDATE' END,
     SYS_CONTEXT('USERENV', 'SESSION_USER'), SYSTIMESTAMP,
     :OLD.balance || '/' || :OLD.status,
     CASE WHEN DELETING THEN NULL ELSE :NEW.balance || '/' || :NEW.status END);
END;
/

-- Access control: block account changes outside business hours
CREATE OR REPLACE TRIGGER trg_block_offhours
BEFORE UPDATE OR DELETE ON account
BEGIN
  IF TO_CHAR(SYSDATE, 'HH24') NOT BETWEEN '09' AND '18' THEN
    RAISE_APPLICATION_ERROR(-20020, 'Account changes allowed only 09:00 to 18:00');
  END IF;
END;
/
Why this design is efficient

3NF/BCNF tables avoid redundancy and anomalies; primary and foreign keys create indexes for fast joins; a summary of daily totals could be a materialized view (a deliberate, documented denormalization) if reporting becomes slow.

Exam Tip

For “design a secure schema” questions, structure the answer as: entities and keys → FDs → normal form reached → constraints → roles and views → audit and triggers → backup and encryption.

Section 8 · Cheatsheet & quick revision

Everything on one page

FD rules

Armstrong’s axioms

Reflexivity: Y ⊆ X ⇒ X → Y
Augmentation: X → Y ⇒ XZ → YZ
Transitivity: X → Y, Y → Z ⇒ X → Z

Derived rules

Union: X → Y, X → Z ⇒ X → YZ
Decomposition: X → YZ ⇒ X → Y
Pseudotransitivity: X → Y, WY → Z ⇒ WX → Z

Closure and key steps

  1. result = X; repeat: if A → B and A ⊆ result then result ∪= B; stop when stable.
  2. Attributes never on a right side are in every key; test their closure first.
  3. Add attributes until closure = R; keep minimal sets; prime = attributes in any key.
  4. Canonical cover: split RHS → drop extraneous LHS attributes → drop redundant FDs.

Normal forms

NFOne-line test
1NFAtomic values only
2NFNo partial dependency on a composite key
3NFFor X → A: X superkey or A prime
BCNFFor X → Y: X superkey
4NFFor non-trivial X →→ Y: X superkey
5NFEvery join dependency implied by candidate keys

Decomposition tests

PropertyTest
Lossless (2 tables)R1 ∩ R2 → R1 − R2 or R1 ∩ R2 → R2 − R1
Lossless (n tables)Chase tableau: some row becomes all distinguished
Dependency preservingFor each X → Y: iterate result ∪= (result ∩ Ri)⁺ ∩ Ri; need Y ⊆ result
Guarantees3NF synthesis: lossless + preserving; BCNF: lossless only

Anomalies

Insertion: cannot add a fact alone. Deletion: removing a fact loses another. Update: many rows to change, inconsistency risk.

Security: threat → countermeasure

ThreatCountermeasure
SQL injectionBind variables, validation, least privilege
Privilege escalationLeast privilege, patching, role review
Insider threatSeparation of duties, auditing, views
Malware / ransomwareOffline backups, segmentation, endpoint protection
Social engineeringTraining and MFA
Weak authenticationStrong policy, MFA, lockout
MisconfigurationHardening, remove defaults, firewall
Data leakageViews, masking, encryption
Backup exposureEncrypt and restrict backups

AuthN = who you are. AuthZ = what you may do. DAC owner decides, MAC labels decide, RBAC roles decide.

PL/SQL syntax quick reference


DECLARE  -- variables, cursors, exceptions
  v x.col%TYPE;  r x%ROWTYPE;  CURSOR c IS SELECT ...;
BEGIN    -- logic
  IF cond THEN ... ELSIF cond THEN ... ELSE ... END IF;
  CASE WHEN cond THEN ... ELSE ... END CASE;
  LOOP ... EXIT WHEN cond; END LOOP;
  WHILE cond LOOP ... END LOOP;
  FOR i IN 1..n LOOP ... END LOOP;
  OPEN c;  FETCH c INTO r;  EXIT WHEN c%NOTFOUND;  CLOSE c;
EXCEPTION
  WHEN NO_DATA_FOUND THEN ...
  WHEN OTHERS THEN ...
END;
/
-- CREATE [OR REPLACE] PROCEDURE p (a IN NUMBER) AS BEGIN ... END;
-- CREATE [OR REPLACE] FUNCTION  f (a IN NUMBER) RETURN NUMBER IS BEGIN ... RETURN x; END;
-- CREATE [OR REPLACE] TRIGGER t BEFORE|AFTER INSERT OR UPDATE ON tbl FOR EACH ROW BEGIN ... END;
-- CREATE [OR REPLACE] PACKAGE pkg AS ... END;  /  PACKAGE BODY pkg AS ... END;
Section 9 · Practice quiz

Test yourself

Twenty questions (17 multiple choice and 3 short answer) covering all five learning outcomes: functional dependencies, closure and keys, normalization, decomposition, security and PL/SQL. You get instant feedback and a final score.