09 - SQL Functions, Procedures, Triggers & Security
1422 words
7 min read
Visual companion
Python
Type and operator map
Python Week 1: the first filter for runtime behavior
View
Revision summary
What this note is really saying
Short form
# 09 - SQL Functions, Procedures, Triggers & Security ## 🎯 Learning Objectives After reading this topic, you will be able to: - Write SQL functions and procedures - Create row-level and statement-level triggers - Understand transition tables (OLD ROW/NEW ROW) - Use GRANT and REVOKE for access control - Explain SQL...

09 - SQL Functions, Procedures, Triggers & Security
🎯 Learning Objectives
After reading this topic, you will be able to:
- Write SQL functions and procedures
- Create row-level and statement-level triggers
- Understand transition tables (OLD ROW/NEW ROW)
- Use GRANT and REVOKE for access control
- Explain SQL injection and how to prevent it
📋 Prerequisites
- 07 - Advanced SQL — Subqueries, set operations, views
- Understanding of database security concepts
📖 Core Content
9.1 Intuition: Making the Database Do More
SQL isn't just for queries. Modern databases support:
- Functions: Computations that return a value
- Procedures: Sequences of operations that modify data
- Triggers: Automatic actions when data changes
- Security: Controlling who can access what These features push logic closer to the data, improving performance and consistency.
9.2 Functions vs. Procedures
| Feature | Function | Procedure |
|---|---|---|
| Returns | Scalar value or relation | Nothing (or via OUT parameters) |
| Used in | SELECT, WHERE clauses | Standalone CALL / EXECUTE |
| Transaction control | No COMMIT/ROLLBACK | Can have COMMIT/ROLLBACK |
| Side effects | Avoid (should be pure) | Expected |
Functions
sqlCREATE FUNCTION dept_instructor_count(dept_name VARCHAR(20)) RETURNS INTEGER AS $$ DECLARE count INTEGER; BEGIN SELECT COUNT(*) INTO count FROM instructor WHERE instructor.dept_name = dept_name; RETURN count; END; $$ LANGUAGE plpgsql; -- Using the function SELECT dept_instructor_count('Comp. Sci.');
Procedures
sqlCREATE PROCEDURE give_raise( inst_id VARCHAR(5), amount NUMERIC(8,2) ) LANGUAGE SQL AS $$ UPDATE instructor SET salary = salary + amount WHERE ID = inst_id; $$ ; -- Invoking the procedure CALL give_raise('10101', 5000);
9.3 Triggers
A trigger automatically fires when a specified event (INSERT, UPDATE, DELETE) occurs on a table.
(Diagram)
Row-Level Trigger
Fires once for each row affected:
sqlCREATE TRIGGER credits_earned AFTER UPDATE OF grade ON takes REFERENCING NEW ROW AS nrow OLD ROW AS orow FOR EACH ROW WHEN (nrow.grade <> 'F' AND nrow.grade IS NOT NULL AND (orow.grade = 'F' OR orow.grade IS NULL)) BEGIN ATOMIC UPDATE student SET tot_cred = tot_cred + ( SELECT credits FROM course WHERE course.course_id = nrow.course_id ) WHERE student.ID = nrow.ID; END;
This trigger: When a grade is updated to a passing grade, add the course credits to the student's total credits.
Statement-Level Trigger
Fires once per statement, regardless of how many rows are affected:
sqlCREATE TRIGGER log_grade_changes AFTER UPDATE OF grade ON takes REFERENCING OLD TABLE AS ot OLD NEW TABLE AS nt NEW FOR EACH STATEMENT INSERT INTO grade_audit_log (timestamp, affected_rows) VALUES (CURRENT_TIMESTAMP, (SELECT COUNT(*) FROM nt));
| Trigger Type | Fires | Use Case |
|---|---|---|
Row-level (FOR EACH ROW) | Once per affected row | Validating/modifying each row individually |
Statement-level (FOR EACH STATEMENT) | Once per SQL statement | Logging, batch operations |
Transition Tables
| Reference | Meaning | Available In |
|---|---|---|
OLD ROW | The row before modification | DELETE, UPDATE |
NEW ROW | The row after modification | INSERT, UPDATE |
OLD TABLE | All affected rows before | DELETE, UPDATE (statement-level) |
NEW TABLE | All affected rows after | INSERT, UPDATE (statement-level) |
9.4 GRANT and REVOKE
GRANT
sqlGRANT SELECT ON instructor TO user1; GRANT INSERT ON department TO user1, user2; GRANT UPDATE (salary) ON instructor TO manager; -- column-level! GRANT ALL PRIVILEGES ON instructor TO user3;
REVOKE
sqlREVOKE SELECT ON instructor FROM user1; REVOKE INSERT ON department FROM user1, user2; REVOKE ALL PRIVILEGES ON instructor FROM user3;
| Privilege | Meaning |
|---|---|
SELECT | Read data |
INSERT | Insert new rows |
UPDATE | Modify existing rows (can limit to specific columns) |
DELETE | Remove rows |
REFERENCES | Create foreign key references to the table |
ALL PRIVILEGES | All of the above |
Roles
A role is a collection of privileges that can be granted to users:
sqlCREATE ROLE instructor_role; GRANT SELECT, UPDATE ON instructor TO instructor_role; GRANT instructor_role TO user1, user2;
9.5 SQL Injection
SQL injection is a security vulnerability where an attacker inserts malicious SQL into a query:
python# VULNERABLE code def get_user(username): query = f"SELECT * FROM users WHERE name = '{username}'" # If username = "'; DROP TABLE users; --" # The query becomes: # SELECT * FROM users WHERE name = ''; DROP TABLE users; --'
Prevention
- Use parameterized queries (prepared statements):
python# SAFE cursor.execute("SELECT * FROM users WHERE name = %s", (username,))
- Validate input: Reject unexpected characters
- Limit privileges: Use read-only accounts where possible
- Use stored procedures: Pre-compiled SQL with parameters
9.6 Worked Examples
Example 1: Trigger to prevent salary reduction
sqlCREATE TRIGGER prevent_salary_cut BEFORE UPDATE OF salary ON instructor FOR EACH ROW WHEN (NEW.salary < OLD.salary) BEGIN RAISE EXCEPTION 'Salary cannot be reduced'; END;
Example 2: Grant read-only access
sqlCREATE ROLE read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT read_only TO analyst1, analyst2;
📐 Key Formulas / Concepts
| Feature | Syntax | Behavior |
|---|---|---|
| Function | CREATE FUNCTION ... RETURNS ... | Returns a value; can be used in queries |
| Procedure | CREATE PROCEDURE ... | Performs actions; no return value |
| Row Trigger | FOR EACH ROW | Fires per affected row |
| Statement Trigger | FOR EACH STATEMENT | Fires once per SQL statement |
| Transition Table | REFERENCING OLD/NEW ROW/TABLE | Accesses before/after values |
⚠️ Common Pitfalls
Pitfall 1: Triggers Causing Infinite Loops
The Mistake: A trigger on UPDATE performs an UPDATE on the same table, which fires the trigger again, which performs another UPDATE...
Fix: Use
WHEN conditions to avoid re-triggering, or use a different mechanism (CHECK constraint, application logic).Pitfall 2: SQL Injection via String Concatenation
The Mistake: Building SQL queries by concatenating user input.
Fix: Always use parameterized queries (prepared statements). The database separates SQL code from data.
Pitfall 3: Granting Too Many Privileges
The Mistake:
GRANT ALL PRIVILEGES ON ALL TABLES TO public;
Fix: Follow the principle of least privilege — grant only the minimum necessary access. Create specific roles for specific tasks.📝 Practice Questions
Q1. What is the difference between a row-level trigger and a statement-level trigger?
Answer
- Row-level (
FOR EACH ROW): Fires once for each row affected by the triggering statement. Can access old and new values of each row.- Statement-level (
FOR EACH STATEMENT): Fires once for the entire statement, regardless of how many rows are affected.Example: An UPDATE affecting 100 rows fires the row trigger 100 times but the statement trigger once.
Q2. Write a GRANT statement that allows user1 to SELECT and UPDATE on the instructor table.
AnswersqlGRANT SELECT, UPDATE ON instructor TO user1; -- Or limit UPDATE to specific columns: GRANT SELECT, UPDATE (name, dept_name) ON instructor TO user1;
Q3. What is SQL injection and how do you prevent it?
AnswerSQL injection: An attacker inserts malicious SQL code via user input that gets concatenated into a query. Example: Input' OR 1=1; --turns a login query into a query that returns all users.Prevention:
- Use parameterized queries (prepared statements) — always
- Validate/sanitize user input
- Use least-privilege database accounts
- Use stored procedures with parameters
Q4. Write a function that returns the number of courses taught by a given instructor.
>DECLARE>countINTEGER;>BEGIN>SELECTCOUNT(∗)INTOcount>FROMteaches>WHEREID=instructorid;>RETURNcount;>END;>AnswersqlCREATE FUNCTION course_count(instructor_id VARCHAR(5)) RETURNS INTEGER AS
LANGUAGE plpgsql;
-- Usage: SELECT course_count('10101');
Q5. What is the purpose of a role in SQL security?
AnswerA role is a named collection of privileges that can be granted to multiple users. Benefits:
- Centralized management: Change the role's privileges → all users in that role are updated
- Consistency: No need to grant individual permissions
- Organization: Match roles to job functions (manager, analyst, clerk)
Q6. Write a trigger that logs whenever a row is deleted from the instructor table.
AnswersqlCREATE TRIGGER log_instructor_delete AFTER DELETE ON instructor REFERENCING OLD ROW AS o FOR EACH ROW INSERT INTO audit_log (event_type, table_name, record_id, timestamp) VALUES ('DELETE', 'instructor', o.ID, CURRENT_TIMESTAMP);
Q7. What's the difference between a SQL function and a SQL procedure?
Answer
| Feature | Function | Procedure |
|---|---|---|
| Return value | Yes (scalar or table) | No (can have OUT params) |
| Use in SQL | Can appear in SELECT/WHERE | Must be called via CALL |
| Transaction control | No COMMIT/ROLLBACK | Can include COMMIT/ROLLBACK |
| Side effects | Should avoid | Expected |
Q8. What transition table references are available in different trigger events?
Answer
| Event | OLD ROW | NEW ROW | OLD TABLE | NEW TABLE |
|---|---|---|---|---|
| INSERT | No | Yes | No | Yes |
| UPDATE | Yes | Yes | Yes | Yes |
| DELETE | Yes | No | Yes | No |
🔗 Cross-References
- Next Topic: 10 - Relational Algebra
- Previous Topic: 08 - SQL Joins
- Related: BSCS2003 (MAD 1) — Web application security
- Related: BSCS4024 (Networks) — Authentication, access control
- Textbook: Silberschatz, Korth, Sudarshan — Chapter 4 (Intermediate SQL), Chapter 5 (Advanced SQL) Join Discord Previous08 - SQL JoinsNext10 - Relational Algebra