SQL Constraints and Triggers
Total Page:16
File Type:pdf, Size:1020Kb
SQL Constraints and Triggers Dr Paolo Guagliardo University of Edinburgh Fall 2016 This page is intentionally left blank Basic SQL constraints We have already seen: UNIQUE to declare keys NOT NULL to disallow null values PRIMARY KEY key + not null FOREIGN KEY to reference attributes in other tables NULL values are ignored when checking constraints except for NOT NULL and PRIMARY KEY Keys CREATE TABLE Account ( accnum VARCHAR(12) UNIQUE, branch VARCHAR(30), custid VARCHAR(10), balance NUMERIC(14,2) ); The following insertion gives an error: INSERT INTO Account VALUES (1, ’London’, ’cust1’, 100), (1, ’Edinburgh’, ’cust3’, 200); The following insertion succeeds: INSERT INTO Account VALUES (NULL, ’London’, ’cust1’, 100), (NULL, ’Edinburgh’, ’cust3’, 200); Compound keys Keys consisting of more than one attribute must be declared using a different syntax CREATE TABLE Movies ( m_title VARCHAR(30), m_director VARCHAR(30), m_year SMALLINT, m_genre VARCHAR(30), UNIQUE (m_title,m_year) ); This declares the set fm title,m yearg as a key for Movies Primary Keys Essentially UNIQUE + NOT NULL CREATE TABLE Account ( accnum VARCHAR(12) PRIMARY KEY, branch VARCHAR(30), custid VARCHAR(10), balance NUMERIC(14,2) ); same as CREATE TABLE Account ( accnum VARCHAR(12) NOT NULL UNIQUE, branch VARCHAR(30), custid VARCHAR(10), balance NUMERIC(14,2) ); Foreign keys in SQL (1) CREATE TABLE Customer ( custid VARCHAR(10) PRIMARY KEY name VARCHAR(20), city VARCHAR(30), address VARCHAR(30) ); CREATE TABLE Account ( accnum VARCHAR(12), branch VARCHAR(30), custid VARCHAR(10) REFERENCES Customer(custid), balance NUMERIC(14,2) ); Every value for attribute custid in Account must appear among the values of the key custid in Customer Foreign keys in SQL (2) General syntax (useful for declaring compound foreign keys) CREATE TABLE <table1> ( <attr> <type>, ... <attr> <type>, FOREIGN KEY (<list1>) REFERENCES <table2>(<list2>) ); where I <list1> and <list2> are lists with the same number of attributes I attributes in <list1> are from table <table1> I attributes in <list2> are unique in <table2> Referential integrity and database modifications (1) Deletion can cause problems with foreign keys Customer ID Name Account Number CustID cust1 John 123456 cust1 cust2 Mary 654321 cust2 where Account.CustID is a foreign key for Customer.ID What happens if one deletes (cust1,John) from Customer? Three approaches are supported in SQL: 1. Reject the deletion operation 2. Propagate it to Account by deleting also (123456,cust1) 3. \Don't know" approach: keep the tuple in Account, but set CustID value to NULL Referential integrity and database modifications (2) All three approaches are supported in SQL CREATE TABLE <table1> ( <attr> <type>, ... FOREIGN KEY <list1> REFERENCES <table2>(<list2>) <approach> ) where <approach> can be: 1. Empty: Reject deletions from <table2> causing the FK to be violated (this is the default when <approach> is not specified) 2. ON DELETE CASCADE: Propagate the deletion to <name> (tuples in <table1> that violate the FK will be deleted) 3. ON DELETE SET NULL: \Don't know" approach (the values of the attributes in <list1>, for tuples in <name> that violate the FK, are set to NULL) Check constraints (1) Syntax: CHECK ( conditional-expression ) Update/insertion is rejected if the condition evaluates to false Example CREATE TABLE Products ( pcode INTEGER PRIMARY KEY, pname VARCHAR(10), pdesc VARCHAR(20), ptype VARCHAR(20), price NUMERIC(6,2) CHECK ( price > 0 ), CHECK ( ptype IN (’BOOK’,’MOVIE’,’MUSIC’) ) ); Check constraints (2) Another example CREATE TABLE Invoices ( invid INTEGER PRIMARY KEY, ordid INTEGER NOT NULL UNIQUE, amount NUMERIC(8,2) CHECK ( amount > 0 ), issued DATE, due DATE, CHECK ( ordid IN SELECT ordid FROM Orders ), CHECK ( due >= issued ) ); The check on ordid is similar to a foreign key, but not the same SQL allows queries in CHECK (not implemented in PostgreSQL) Domain constraints (1) A domain is essentially a data type with optional constraints Syntax CREATE DOMAIN name datatype [ DEFAULT value ][ constraint ] where constraint is NOT NULL j CHECK ( expression ) In CHECK expression, VALUE refers to the value being tested Example CREATE DOMAIN posnumber NUMERIC(10,2) CHECK ( VALUE > 0 ); CREATE DOMAIN category VARCHAR(20) CHECK ( VALUEIN (’BOOK, ’MUSIC’, ’MOVIE’) ); Domain constraints (2) CREATE TABLE Products ( pcode INTEGER PRIMARY KEY, pname VARCHAR(10), pdesc VARCHAR(20), ptype category, price posnumber ); CREATE TABLE Invoices ( invid INTEGER PRIMARY KEY, ordid INTEGER NOT NULL UNIQUE, amount posnumber, issued DATE, due DATE, CHECK ( ordid IN SELECT ordid FROM Orders ), CHECK ( due >= issued ) ); Assertions Essentially a CHECK constraint not bound to a specific table Syntax: CREATE ASSERTION name CHECK ( condition ) Example CREATE ASSERTION too_many_customers CHECK (( SELECT COUNT(*) FROM customers ) <= 1000 ) ; I Standard SQL I Not implemented in any of the currently available DBMSs I The problem is allowing queries in CHECK Triggers Specify an action to execute if certain events took place Event: a change to the database that activates the trigger (an insertion, a deletion, or an update) Condition: a query or test checked when the trigger is activated (for a query: empty is false, non-empty is true) Action: a procedure executed when the condition is true I can refer to old/new values of modified tuples I can examine answers to the condition query I can execute new queries I can make changes to the database (both data and schema) I can be executed before/after the event for each row or for each statement Triggers: Example 1 Suppose we have Products : pcode, pname, price Orders : ordid, odate, ocust, final (bool) Details : ordid, pcode, qty Prices : ordid, pcode, price Whenever a new detail for an order is inserted we want to save the price of the corresponding products Triggers: Example 1 CREATE TRIGGER save_price AFTER INSERTON details REFERENCING NEW TABLEAS inserted FOR EACH STATEMENT WHEN TRUE BEGIN INSERT INTO prices(ordid,pcode,price) SELECT I.ordid, I.pcode, P.price FROM inserted I JOIN products P ON I.pcode = P.pcode END ; Triggers: Example 2 Suppose we have Products : pcode, pname, price Orders : ordid, odate, ocust, final (bool) Details : ordid, pcode, qty Prices : ordid, pcode, price Invoices : invid (serial), ordid, amount, issued, due Whenever an order becomes final we want to generate an invoice for it Triggers: Example 2 CREATE TRIGGER invoice_order AFTER UPDATEOF final ON orders REFERENCING OLD ROWAS oldrow NEW ROWAS newrow FOR EACH ROW WHEN oldrow.final = FALSE AND newrow.final = TRUE BEGIN INSERT INTO invoices(ordid,amount,issued,due) SELECT O.ordid, SUM(D.qty * P.price), O.odate, O.odate+7d FROM orders O, details D, prices P WHERE O.ordid = newrow.ordid AND O.ordid = D.ordid AND D.ordid = P.ordid AND D.pcode = P.pcode END ; Triggers in real systems In PostgreSQL (and similarly for other DBMSs): CREATE TRIGGER name { BEFORE | AFTER } event ON table_name FOR EACH { ROW | STATEMENT } WHEN ( condition ) EXECUTE PROCEDURE function_name ( arguments ) where event can be one of: I INSERT I UPDATE [ OF column [, ... ] ] I DELETE and condition cannot contain queries Triggers for database consistency Constraints Triggers Protection against any statement Activated by specific statement Defined declaratively Defined operationally I easier to understand I effect may be obscure I easier to optimize I more flexibility Other uses of triggers I Alert users I Logging events I Gather statistics I Replication I Workflow management I Business rules enforcement Caution with triggers I An event may activate more than one trigger I Activated triggers are processed in some arbitrary order I Actions can activate other triggers: we get a chain Recursive trigger The action directly/indirectly activates the same trigger =) collections of triggers can have unpredictable effects.