Loading content...
Throughout this module, we've explored the theoretical foundations of query safety: unsafe query problems, domain dependence, safety conditions, and the finite result guarantee. Now it's time to see these concepts in action through comprehensive examples.
This page presents a catalog of safe query patterns, each analyzed in detail to demonstrate how safety conditions are satisfied. These examples serve as templates you can apply to your own query writing and as exercises in safety verification.
By the end of this page, you will be able to recognize safe query patterns, verify safety for complex expressions, transform unsafe queries into safe equivalents, and confidently write relational calculus expressions that translate cleanly to SQL.
All examples in this page use the following database schema, representing a university course management system.
1234567891011121314151617181920212223242526272829303132
-- Student tableStudent(sid, name, major, year) sid: Student ID (primary key) name: Student's full name major: Department/major code year: Year of study (1-4) -- Course table Course(cid, title, department, credits) cid: Course ID (primary key) title: Course name department: Offering department credits: Credit hours (1-4) -- Enrollment table (students taking courses)Enrolled(sid, cid, semester, grade) (sid, cid, semester): Composite primary key grade: Letter grade (A, B, C, D, F, NULL for in-progress) -- Instructor tableInstructor(iid, name, department, rank) iid: Instructor ID (primary key) rank: Professor, Associate, Assistant, Lecturer -- Teaches table (instructors teaching courses)Teaches(iid, cid, semester) (iid, cid, semester): Composite primary key -- Prerequisite tablePrerequisite(cid, prereq_cid) cid: Course requiring prerequisite prereq_cid: Required prerequisite courseThe most basic safe queries are selections—filtering tuples from a single relation based on conditions. These are always safe when the free variable is bound by the relation.
Query: Find all students in their senior year (year = 4).
1234567
{s | Student(s) ∧ s.year = 4} -- Safety Analysis:-- Free variable: s-- Positive predicate on s: Student(s) ✓-- All free variables are range-restricted ✓-- VERDICT: SAFESelection queries follow the pattern {t | R(t) ∧ condition(t)}. The positive predicate R(t) binds t, and conditions filter the bound values. Order matters: bind first, filter second.
Joins combine tuples from multiple relations. In relational calculus, joins emerge from existential quantification with equality conditions linking relations.
Query: Find student names and the courses they're enrolled in.
12345678910
{<s.name, c.title> | Student(s) ∧ Course(c) ∧ (∃e)(Enrolled(e) ∧ e.sid = s.sid ∧ e.cid = c.cid)} -- Safety Analysis:-- Free variables: s, c (from the result tuple attributes)-- Positive predicates:-- s: bound by Student(s) ✓-- c: bound by Course(c) ✓-- Existential variable e: bound by Enrolled(e) ✓-- VERDICT: SAFENegation queries find tuples that DON'T match a condition. The key to safety is ensuring the outer variable is positively bound before applying the negated condition.
Query: Find students who are not enrolled in any course.
1234567891011
{s | Student(s) ∧ ¬(∃e)(Enrolled(e) ∧ e.sid = s.sid)} -- Safety Analysis:-- Free variable: s-- Positive predicate on s: Student(s) ✓-- Negation contains ∃e where e is bound by Enrolled(e) ✓-- The s.sid inside negation refers to already-bound s-- VERDICT: SAFE -- Note the structure: Bind s first, then check -- that no enrollment exists for this sThe pattern {t | R(t) ∧ ¬(∃s)(S(s) ∧ condition(t,s))} is always safe: t is bound by R, s is bound by S inside the negation, and the condition links them. This is SQL's NOT EXISTS pattern.
Universal queries find tuples that satisfy a condition for ALL elements in some set. In calculus, these use universal quantifiers (∀) or, equivalently, double negation (¬∃¬).
Query: Find students enrolled in ALL CS department courses.
123456789
{s | Student(s) ∧ (∀c)(Course(c) ∧ c.department = 'CS' → (∃e)(Enrolled(e) ∧ e.sid = s.sid ∧ e.cid = c.cid))} -- Safety Analysis:-- Free variable: s → bound by Student(s) ✓-- Universal c: appears positively in antecedent Course(c) ✓-- Existential e: bound by Enrolled(e) ✓-- VERDICT: SAFEUniversal queries implement relational division: 'X such that for all Y, XY exists.' The double-negation pattern (¬∃...¬∃...) is the canonical way to express this safely. The outer negation finds X with no unmatched Y; the inner negation checks for absence of matching tuples.
Real-world queries often combine multiple patterns. Let's analyze some complex examples that use joins, negation, and universal conditions together.
Query: Find students who are enrolled in at least one 4-credit course but have never received a grade below 'B'.
1234567891011121314
{s | Student(s) ∧ -- Has at least one 4-credit enrollment (∃e1)(∃c1)(Enrolled(e1) ∧ Course(c1) ∧ e1.sid = s.sid ∧ e1.cid = c1.cid ∧ c1.credits = 4) ∧ -- Has never received below B ¬(∃e2)(Enrolled(e2) ∧ e2.sid = s.sid ∧ e2.grade IN ('C', 'D', 'F'))} -- Safety Analysis:-- s: bound by Student(s) ✓-- e1, c1: bound in first existential clause ✓ -- e2: bound by Enrolled(e2) inside negation ✓-- VERDICT: SAFEOften, an initial query formulation is unsafe, but the user's intent can be expressed safely. Here's how to recognize and fix common unsafe patterns.
| Unsafe Query | Problem | Safe Equivalent | Key Change |
|---|---|---|---|
{x | ¬R(x)} | x unbound | {x | Universe(x) ∧ ¬R(x)} | Define universe table |
{x | x > 100} | x unbound | {t.x | T(t) ∧ t.x > 100} | Source from table |
{x | R(x) ∨ x=5} | x unbound in ∨ | {x | R(x)} ∪ {5} | Separate handling |
{t | (∀x)P(t,x)} | ∀ over domain | {t | T(t) ∧ (∀x)(S(x)→P(t,x))} | Bound t and restrict ∀ |
Detailed Example:
Unsafe intent: "Find all possible course IDs that don't exist yet."
This is fundamentally unsafe—there are infinitely many strings that aren't course IDs.
123456789101112131415161718
-- Original UNSAFE formulation:{cid | ¬(∃c)(Course(c) ∧ c.cid = cid)} -- Problem: cid ranges over infinite domain -- Clarify intent: What does user ACTUALLY want? -- Option A: IDs mentioned in prerequisites but no course exists{p.prereq_cid | Prerequisite(p) ∧ ¬(∃c)(Course(c) ∧ c.cid = p.prereq_cid)}-- SAFE: p.prereq_cid bound by Prerequisite -- Option B: IDs from a candidate list that aren't used{id | CandidateIDs(id) ∧ ¬(∃c)(Course(c) ∧ c.cid = id)}-- SAFE: id bound by CandidateIDs table -- Option C: Next available sequential ID (requires procedural logic)-- Not expressible in pure relational calculusWhen facing an unsafe query, ask: 'What finite set should the result come from?' Usually there's a table defining the 'universe of interest.' Add that table as a positive predicate to bind free variables.
This page demonstrated safe query patterns through comprehensive examples, reinforcing the theoretical concepts with practical applications.
Module Completion:
Congratulations! You have completed Module 4: Safe Queries. You now understand:
This knowledge is foundational for understanding SQL's design, query optimization, and advanced database theory.
You have mastered safe queries in relational calculus. You can now recognize safe patterns, verify safety conditions, transform unsafe queries to safe equivalents, and understand why SQL enforces these principles by design. This knowledge prepares you for advanced topics in query optimization and database theory.