dbschema.sql (15310B)
1 DROP DATABASE IF EXISTS a22willi; 2 CREATE DATABASE a22willi; 3 USE a22willi; 4 5 /* --- TABLE DEFINITIONS --- */ 6 7 CREATE TABLE Agent ( 8 CodeName CHAR(3) UNIQUE NOT NULL, 9 FirstName VARCHAR(32) NOT NULL, 10 LastName VARCHAR(32) NOT NULL, 11 Salary DECIMAL, 12 IsGroupLeader BOOL DEFAULT 0, 13 IsManager BOOL DEFAULT 0, 14 IsFieldAgent BOOL DEFAULT 1, 15 CHECK (CodeName RLIKE '^[a-zA-Z][0-9]{1,2}$'), -- Only accept ex: 'B23' or 'x5'. 16 CHECK (LENGTH(FirstName) > 0 AND LENGTH(LastName) > 0), 17 CHECK (Salary >= 0), 18 PRIMARY KEY (CodeName) 19 ) ENGINE = INNODB; 20 21 CREATE INDEX Name ON Agent(FirstName, LastName) USING BTREE; 22 23 -- Denormalization (Horizontal split) 24 CREATE TABLE FieldAgentAttributes ( 25 AgentCodeName CHAR(3) UNIQUE NOT NULL, -- Prevent multiple FieldAgentAttributes per Agent using unique. 26 Specialty VARCHAR(256), 27 Competence VARCHAR(256), 28 CHECK (Specialty IS NULL OR LENGTH(Specialty) > 0), 29 CHECK (Competence IS NULL OR LENGTH(Competence) > 0), 30 PRIMARY KEY (AgentCodeName), 31 FOREIGN KEY (AgentCodeName) REFERENCES Agent(CodeName) 32 ) ENGINE = INNODB; 33 34 -- Denormalization (Codes) 35 CREATE TABLE Terrain ( 36 TerrainCode TINYINT UNSIGNED UNIQUE NOT NULL, 37 TerrainName VARCHAR(64), 38 PRIMARY KEY (TerrainCode) 39 ) ENGINE = INNODB; 40 41 CREATE INDEX TerrainTypes ON Terrain(TerrainName); 42 43 -- Denormalization (Merge) 44 CREATE TABLE Incident ( 45 RegionName VARCHAR(32) NOT NULL, 46 Terrain TINYINT UNSIGNED NOT NULL, 47 IncidentName VARCHAR(64) NOT NULL, 48 IncidentNumber INT UNIQUE NOT NULL, 49 Location VARCHAR(128), 50 PRIMARY KEY (IncidentName, IncidentNumber), 51 FOREIGN KEY (Terrain) REFERENCES Terrain(TerrainCode) 52 ) ENGINE = INNODB; 53 54 -- Denormalization (Vertical split) 55 CREATE TABLE Report ( 56 DateCreated DATETIME NOT NULL, 57 Author CHAR(3) NOT NULL, 58 Title VARCHAR(64) NOT NULL, 59 Content VARCHAR(1024), 60 IncidentName VARCHAR(64), 61 IncidentNumber INT, 62 PRIMARY KEY (DateCreated, Title), 63 FOREIGN KEY (Author) REFERENCES Agent(CodeName), 64 FOREIGN KEY (IncidentName, IncidentNumber) REFERENCES Incident(IncidentName, IncidentNumber) 65 ) ENGINE = INNODB; 66 67 -- Denormalization (Vertical split) 68 CREATE TABLE ArchivedReport ( 69 DateCreated DATETIME NOT NULL, 70 Author CHAR(3) NOT NULL, 71 Title VARCHAR(64) NOT NULL DEFAULT 'nameless_report', 72 Content VARCHAR(1024), 73 IncidentName VARCHAR(64), 74 IncidentNumber INT, 75 PRIMARY KEY (DateCreated, Title), 76 FOREIGN KEY (Author) REFERENCES Agent(CodeName), 77 FOREIGN KEY (IncidentName, IncidentNumber) REFERENCES Incident(IncidentName, IncidentNumber) 78 ) ENGINE = INNODB; 79 80 CREATE TABLE Operation ( 81 OperationName VARCHAR(128) NOT NULL, 82 StartDate DATE NOT NULL, 83 EndDate DATE, 84 SuccessRate BIT, 85 GroupLeader CHAR(3), 86 IncidentName VARCHAR(64) NOT NULL, 87 IncidentNumber INT NOT NULL, 88 CHECK (EndDate IS NULL OR EndDate >= StartDate), 89 PRIMARY KEY (OperationName, StartDate, IncidentName, IncidentNumber), 90 FOREIGN KEY (IncidentName, IncidentNumber) REFERENCES Incident(IncidentName, IncidentNumber), 91 FOREIGN KEY (GroupLeader) REFERENCES Agent(CodeName) 92 ) ENGINE = INNODB; 93 94 -- N - M relation (Agent, Operation) 95 CREATE TABLE OperatesIn ( 96 IncidentName VARCHAR(64) NOT NULL, 97 IncidentNumber INT NOT NULL, 98 OperationName VARCHAR(128) NOT NULL, 99 StartDate DATE NOT NULL, 100 CodeName CHAR(3) NOT NULL, 101 PRIMARY KEY (IncidentName, IncidentNumber, OperationName, StartDate, CodeName), 102 FOREIGN KEY (IncidentName, IncidentNumber) REFERENCES Incident(IncidentName, IncidentNumber), 103 FOREIGN KEY (OperationName, StartDate) REFERENCES Operation(OperationName, StartDate), 104 FOREIGN KEY (CodeName) REFERENCES Agent(CodeName) 105 ) ENGINE = INNODB; 106 107 /* --- LOG TABLES --- */ 108 109 CREATE TABLE AgentLog ( 110 Id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, 111 Operation VARCHAR(10), 112 UserName VARCHAR(32), 113 CodeName CHAR(3), 114 OperationTime DATETIME, 115 PRIMARY KEY (Id) 116 ) ENGINE = INNODB; 117 118 /* --- STORED PROCEDURES --- */ 119 120 DELIMITER // 121 122 -- Get the number of active operations an agent has. 123 -- Conditional procedure with error handling 124 CREATE PROCEDURE GetNumberOfActiveOperations(IN _Agent CHAR(3), OUT _NumOfActiveOperations SMALLINT UNSIGNED) 125 BEGIN 126 IF (SELECT COUNT(*) FROM Agent WHERE CodeName = _Agent) = 0 127 THEN SET @message = CONCAT('Agent: ', _Agent, ' does not exist.'); 128 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = @message; 129 END IF; 130 131 SELECT COUNT(OpIn.OperationName) AS SumOfOperations INTO _NumOfActiveOperations 132 FROM OperatesIn OpIn 133 JOIN Operation Op ON OpIn.OperationName = Op.OperationName AND OpIn.StartDate = Op.StartDate 134 WHERE OpIn.CodeName = _Agent AND (Op.EndDate IS NULL OR Op.EndDate > CURDATE()); 135 END// 136 137 -- Simple procedure 138 CREATE PROCEDURE GetAgentNamesAlphabetically() 139 BEGIN 140 SELECT FirstName, LastName, CodeName 141 FROM Agent 142 ORDER BY LastName, FirstName; 143 END// 144 145 CREATE PROCEDURE GetOperationRegion(IN _OpName VARCHAR(128), OUT _Region VARCHAR(32)) 146 BEGIN 147 SELECT Inc.RegionName INTO _Region 148 FROM Incident Inc 149 JOIN Operation Op ON Op.IncidentName = Inc.IncidentName AND Op.IncidentNumber = Inc.IncidentNumber 150 WHERE Op.OperationName = _OpName; 151 END// 152 153 -- Move report from Report to ArchivedReport 154 -- Procedure that moves tuple in vertical split 155 CREATE PROCEDURE ArchiveReport(IN _DateCreated DATETIME, IN _Title VARCHAR(64)) 156 BEGIN 157 INSERT INTO ArchivedReport (DateCreated, Author, Title, Content, IncidentName, IncidentNumber) 158 SELECT R.DateCreated, R.Author, R.Title, R.Content, R.IncidentName, R.IncidentNumber 159 FROM Report R 160 WHERE R.DateCreated = _DateCreated AND R.Title = _Title; 161 162 DELETE FROM Report 163 WHERE Report.DateCreated = _DateCreated AND Report.Title = _Title; 164 END // 165 166 -- get Operations between two dates 167 -- procedure that selects data 168 CREATE PROCEDURE GetOperationsInRange(IN _StartDate DATE, IN _EndDate DATE) 169 BEGIN 170 SELECT * 171 FROM Operation Op 172 WHERE Op.StartDate > _StartDate 173 AND Op.EndDate IS NOT NULL AND Op.EndDate < _EndDate; 174 END// 175 176 /* --- TRIGGERS --- */ 177 178 -- Log Agent operations 179 CREATE TRIGGER AgentLogInsert AFTER INSERT ON Agent 180 FOR EACH ROW BEGIN 181 INSERT INTO AgentLog (Operation, UserName, CodeName, OperationTime) VALUES 182 ('INSERT', USER(), NEW.CodeName, NOW()); 183 END// 184 185 CREATE TRIGGER AgentLogUpdate AFTER UPDATE ON Agent 186 FOR EACH ROW BEGIN 187 INSERT INTO AgentLog (Operation, UserName, CodeName, OperationTime) VALUES 188 ('UPDATE', USER(), NEW.CodeName, NOW()); 189 END// 190 191 CREATE TRIGGER AgentLogDelete AFTER DELETE ON Agent 192 FOR EACH ROW BEGIN 193 INSERT INTO AgentLog (Operation, UserName, CodeName, OperationTime) VALUES 194 ('DELETE', USER(), OLD.CodeName, NOW()); 195 END// 196 197 -- Validate that an agent is part of at least one group, fieldagents, managers or groupleaders. 198 CREATE TRIGGER ValidateAgent BEFORE INSERT ON Agent 199 FOR EACH ROW BEGIN 200 IF NEW.IsGroupLeader = 0 AND NEW.IsManager = 0 AND NEW.IsFieldAgent = 0 201 THEN 202 SIGNAL SQLSTATE '45000' 203 SET MESSAGE_TEXT = 'Agent missing type (eg: FieldAgent).'; 204 END IF; 205 END// 206 207 -- Before deleting an incident, also remove all related operations and reports. 208 CREATE TRIGGER IncidentCleanup BEFORE DELETE ON Incident 209 FOR EACH ROW BEGIN 210 DELETE FROM Report 211 WHERE IncidentName = OLD.IncidentName AND IncidentNumber = OLD.IncidentNumber; 212 213 DELETE FROM ArchivedReport 214 WHERE IncidentName = OLD.IncidentName AND IncidentNumber = OLD.IncidentNumber; 215 216 DELETE FROM OperatesIn 217 WHERE IncidentName = OLD.IncidentName AND IncidentNumber = OLD.IncidentNumber; 218 219 DELETE FROM Operation 220 WHERE IncidentName = OLD.IncidentName AND IncidentNumber = OLD.IncidentNumber; 221 END// 222 223 -- Validate that an agent can only be part of Operations in the same region, 224 -- And at most 3 operations in the same region. 225 CREATE TRIGGER ValidateOperatesIn BEFORE INSERT ON OperatesIn 226 FOR EACH ROW BEGIN 227 DECLARE MaxAllowedOperations INT DEFAULT 3; 228 DECLARE ExistingRegion VARCHAR(32) DEFAULT NULL; 229 DECLARE NewRegion VARCHAR(32) DEFAULT NULL; 230 DECLARE NumOfOperations SMALLINT UNSIGNED DEFAULT NULL; 231 232 -- Get the region of the new operation the agent is being added to 233 CALL GetOperationRegion(NEW.OperationName, NewRegion); 234 235 -- Get the current number of active operations an agent has 236 CALL GetNumberOfActiveOperations(NEW.CodeName, NumOfOperations); 237 238 -- Get the region of an existing operation the agent is part of 239 SELECT Inc.RegionName INTO ExistingRegion 240 FROM Incident Inc, Operation Op, OperatesIn OpIn 241 WHERE Op.OperationName = OpIn.OperationName AND Op.StartDate = OpIn.StartDate AND Op.IncidentName = Inc.IncidentName AND Op.IncidentNumber = Inc.IncidentNumber 242 AND OpIn.CodeName = NEW.CodeName AND (Op.EndDate IS NULL OR Op.EndDate > CURDATE()) 243 LIMIT 1; 244 245 -- If the agent is part of an operation in a different region, raise an error 246 IF ExistingRegion IS NOT NULL AND ExistingRegion != NewRegion THEN 247 SET @message = CONCAT('Agent already has ongoing operation in region:', ExistingRegion); 248 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = @message; 249 END IF; 250 251 -- If the agent is part already part of 3 operations, raise an error 252 IF NumOfOperations >= MaxAllowedOperations THEN 253 SIGNAL SQLSTATE '45000' 254 SET MESSAGE_TEXT = 'Agent can be part of no more than 3 operations in the same region.'; 255 END IF; 256 END// 257 258 CREATE TRIGGER ValidateOperation BEFORE INSERT ON Operation 259 FOR EACH ROW 260 BEGIN 261 IF NEW.EndDate > DATE_ADD(NEW.StartDate, INTERVAL 5 WEEK) THEN 262 SET NEW.EndDate = DATE_ADD(NEW.StartDate, INTERVAL 5 WEEK); 263 END IF; 264 END// 265 266 267 DELIMITER ; 268 269 /* --- VIEWS --- */ 270 271 -- Specialization 272 -- Get the average successrate for all agents 273 CREATE VIEW AgentRates AS 274 SELECT 275 OpIn.CodeName, 276 AVG(Op.SuccessRate) AS AvgSuccessRate 277 FROM OperatesIn OpIn, Operation Op 278 WHERE 279 OpIn.OperationName = Op.OperationName 280 AND OpIn.StartDate = Op.StartDate 281 GROUP BY OpIn.CodeName; 282 283 -- Simplification (and specialization) 284 -- Get all field agents, including their competence, specialization and average successrate. 285 CREATE VIEW FieldAgents AS 286 SELECT Agent.CodeName, Firstname, LastName, Salary, Specialty, Competence, AvgSuccessRate 287 FROM Agent, FieldAgentAttributes, AgentRates 288 WHERE Agent.IsFieldAgent 289 AND Agent.CodeName = FieldAgentAttributes.AgentCodeName 290 AND Agent.CodeName = AgentRates.CodeName; 291 292 -- Simplification 293 -- Get all GroupLeaders 294 CREATE VIEW GroupLeaders AS 295 SELECT Agent.CodeName, FirstName, LastName, Salary, AvgSuccessRate 296 FROM Agent, AgentRates 297 WHERE IsGroupLeader 298 AND Agent.CodeName = AgentRates.CodeName; 299 300 -- Simplification 301 -- Get all managers 302 CREATE VIEW Managers AS 303 SELECT Agent.CodeName, FirstName, LastName, Salary, AvgSuccessRate 304 FROM Agent, AgentRates 305 WHERE IsManager 306 AND Agent.CodeName = AgentRates.CodeName; 307 308 -- Simplification 309 CREATE VIEW AllReports AS 310 SELECT * 311 FROM Report 312 UNION ALL 313 SELECT * 314 FROM ArchivedReport; 315 316 CREATE VIEW AgentOperations AS 317 SELECT A.CodeName, OpIn.OperationName 318 FROM Agent A, OperatesIn OpIn 319 WHERE A.CodeName = OpIn.CodeName; 320 321 /* --- GENERATE MOCK DATA --- */ 322 323 INSERT INTO Terrain (TerrainName, TerrainCode) VALUES 324 ('Mountainous', 1), 325 ('Plains', 2), 326 ('Hills', 3), 327 ('Desert', 4), 328 ('Forest', 5); 329 330 INSERT INTO Agent (CodeName, FirstName, LastName, Salary, IsGroupLeader, IsManager, IsFieldAgent) VALUES 331 ('A1', 'John', 'Doe', 50000, TRUE, FALSE, TRUE), 332 ('B23', 'Jane', 'Smith', 52000, FALSE, TRUE, FALSE), 333 ('C45', 'Alex', 'Johnson', 51000, FALSE, FALSE, TRUE), 334 ('D56', 'Emily', 'Brown', 50500, TRUE, FALSE, FALSE), 335 ('E78', 'Michael', 'Davis', 53000, FALSE, TRUE, TRUE); 336 337 INSERT INTO Incident (RegionName, Terrain, IncidentName, IncidentNumber, Location) VALUES 338 ('North', 1, 'Heist', 101, 'North Town'), 339 ('West', 2, 'Kidnapping', 102, 'South City'), 340 ('North', 3, 'Sabotage', 103, 'East Village'), 341 ('South', 4, 'Espionage', 104, 'West Point'), 342 ('NorthEast', 3, 'Breach', 105, 'Central Hub'), 343 ('SouthWest', 2, 'Ambush', 106, 'Mountain Pass'), 344 ('MidWest', 4, 'Hijacking', 107, 'River Crossing'), 345 ('Central', 1, 'Arson', 108, 'Forest Edge'), 346 ('OuterWest', 3, 'Assault', 109, 'Desert Base'), 347 ('DeepSouth', 2, 'Theft', 110, 'Island Shore'); 348 349 INSERT INTO Operation (OperationName, StartDate, EndDate, SuccessRate, GroupLeader, IncidentName, IncidentNumber) VALUES 350 ('Operation Thunder', '2023-01-01', '2023-10-18', 1, 'A1', 'Sabotage', 103), 351 ('Operation QuickSilver1', '2023-02-05', '2023-02-18', 0, 'D56', 'Heist', 101), 352 ('Operation QuickSilver2', '2023-02-05', '2025-10-18', 0, 'D56', 'Heist', 101), 353 ('Operation QuickSilver3', '2023-02-05', '2023-11-18', 0, 'D56', 'Heist', 101), 354 ('Operation NightFall', '2023-03-10', '2023-03-30', 1, 'A1', 'Sabotage', 103), 355 ('Operation DesertStorm', '2023-04-01', NULL, 1, 'E78', 'Espionage', 104), 356 ('Operation ForestGuard', '2023-05-01', '2023-05-10', NULL, 'D56', 'Breach', 105); 357 358 INSERT INTO OperatesIn (IncidentName, IncidentNumber, OperationName, StartDate, CodeName) VALUES 359 ('Heist', 101, 'Operation Thunder', '2023-01-01', 'A1'), 360 ('Heist', 101, 'Operation Thunder', '2023-01-01', 'B23'), 361 ('Kidnapping', 102, 'Operation QuickSilver1', '2023-02-05', 'B23'), 362 ('Kidnapping', 102, 'Operation QuickSilver2', '2023-02-05', 'B23'), 363 ('Kidnapping', 102, 'Operation QuickSilver3', '2023-02-05', 'B23'), 364 ('Kidnapping', 102, 'Operation QuickSilver1', '2023-02-05', 'C45'), 365 ('Espionage', 104, 'Operation DesertStorm', '2023-04-01', 'D56'), 366 ('Breach', 105, 'Operation ForestGuard', '2023-05-01', 'E78'); 367 368 INSERT INTO FieldAgentAttributes (AgentCodeName, Specialty, Competence) VALUES 369 ('A1', 'Explosives Expert', 'Advanced explosives handling and defusal'), 370 ('C45', 'Surveillance Specialist', 'Expert in covert surveillance and intelligence gathering'), 371 ('E78', 'Combat Specialist', 'Skilled hand-to-hand combat and small arms use'); 372 373 INSERT INTO Report (DateCreated, Author, Title, Content, IncidentName, IncidentNumber) VALUES 374 ('2023-04-15', 'A1', 'Update on Heist', 'Latest details on the heist operation. Successful with minor hitches.', 'Heist', 101), 375 ('2023-06-10', 'B23', 'Kidnapping in West', 'The kidnapping incident at South City has been contained.', 'Kidnapping', 102), 376 ('2023-06-20', 'D56', 'Sabotage Alert', 'Sabotage in East Village has caused significant infrastructure damage.', 'Sabotage', 103), 377 ('2023-07-01', 'A1', 'Espionage in South', 'Espionage activities spotted in West Point. Suspects are being tracked.', 'Espionage', 104), 378 ('2023-08-15', 'C45','Breach in NorthEast', 'A major breach in Central Hub. Cybersecurity teams are on it.', 'Breach', 105); 379 380 INSERT INTO ArchivedReport (DateCreated, Author, Title, Content, IncidentName, IncidentNumber) VALUES 381 ('2020-03-05', 'D56', 'Old Heist Report', 'Details on an old heist operation.', 'Heist', 101), 382 ('2019-12-10', 'A1', 'Old Kidnapping Details', 'Report on a kidnapping that happened two years back.', 'Kidnapping', 102), 383 ('2021-01-20', 'B23', 'Sabotage in East - Historical', 'An old sabotage report detailing the damage.', 'Sabotage', 103), 384 ('2021-02-15', 'A1', 'Espionage History', 'Previous espionage activities in West Point.', 'Espionage', 104), 385 ('2021-04-05', 'E78', 'Breach Report - Historical', 'A significant breach happened two years back in Central Hub.', 'Breach', 105);