| | 1 | = Advanced Database Development (SQL DDL) = |
| | 2 | |
| | 3 | == 1. Data constraints requirements: Booking Overlapping and Sitter Availability == |
| | 4 | '''Data requirements description''' |
| | 5 | |
| | 6 | Bookings must adhere to real world time and scheduling: |
| | 7 | * A Pet Sitter cannot have overlapping confirmed bookings - they cant be in two places at once. |
| | 8 | * A Pet Owner cannot book the same pet twice on overlapping dates/times. |
| | 9 | * A booking cannot be created if the {{{date_from}}} is in the past. |
| | 10 | |
| | 11 | === '''Implementation''' |
| | 12 | '''Triggers''' |
| | 13 | {{{ |
| | 14 | #!sql |
| | 15 | CREATE OR REPLACE FUNCTION petsitter_trg_booking_validation() |
| | 16 | RETURNS trigger |
| | 17 | LANGUAGE plpgsql |
| | 18 | AS $$ |
| | 19 | BEGIN |
| | 20 | IF NEW.date_from < now() THEN |
| | 21 | RAISE EXCEPTION 'Cannot create a booking in the past (date_from=%)', NEW.date_from; |
| | 22 | END IF; |
| | 23 | |
| | 24 | IF NEW.date_to <= NEW.date_from THEN |
| | 25 | RAISE EXCEPTION 'Booking end date (%) must be after start date (%)', NEW.date_to, NEW.date_from; |
| | 26 | END IF; |
| | 27 | |
| | 28 | IF NEW.status = 'Confirmed' THEN |
| | 29 | IF EXISTS ( |
| | 30 | SELECT 1 FROM bookings b |
| | 31 | WHERE b.sitter_id = NEW.sitter_id |
| | 32 | AND b.status = 'Confirmed' |
| | 33 | AND b.date_from < NEW.date_to |
| | 34 | AND b.date_to > NEW.date_from |
| | 35 | AND (TG_OP <> 'UPDATE' OR b.booking_id <> NEW.booking_id) |
| | 36 | ) THEN |
| | 37 | RAISE EXCEPTION 'Sitter % is already booked for these dates.', NEW.sitter_id; |
| | 38 | END IF; |
| | 39 | END IF; |
| | 40 | |
| | 41 | RETURN NEW; |
| | 42 | END; |
| | 43 | $$; |
| | 44 | |
| | 45 | DROP TRIGGER IF EXISTS trg_booking_validation ON bookings; |
| | 46 | CREATE TRIGGER trg_booking_validation |
| | 47 | BEFORE INSERT OR UPDATE ON bookings |
| | 48 | FOR EACH ROW |
| | 49 | EXECUTE FUNCTION petsitter_trg_booking_validation(); |
| | 50 | }}} |
| | 51 | |
| | 52 | ---- |
| | 53 | |
| | 54 | == 2. Data constraints requirements: Review/Transaction Integrity == |
| | 55 | '''Data requirements description''' |
| | 56 | |
| | 57 | Reviews are associated with the completion of a booking: |
| | 58 | * A review can only be inserted if the relevant booking has the status 'Completed'. |
| | 59 | * A booking can have a maximum of ONE (1) review. |
| | 60 | * Once a review is created, the rating cannot be edited. |
| | 61 | |
| | 62 | === '''Implementation''' |
| | 63 | '''Triggers''' |
| | 64 | {{{ |
| | 65 | #!sql |
| | 66 | CREATE OR REPLACE FUNCTION petsitter_trg_review_integrity() |
| | 67 | RETURNS trigger |
| | 68 | LANGUAGE plpgsql |
| | 69 | AS $$ |
| | 70 | DECLARE |
| | 71 | v_booking_status varchar; |
| | 72 | BEGIN |
| | 73 | -- Get booking status |
| | 74 | SELECT status INTO v_booking_status |
| | 75 | FROM bookings |
| | 76 | WHERE booking_id = NEW.booking_id; |
| | 77 | |
| | 78 | IF v_booking_status IS NULL THEN |
| | 79 | RAISE EXCEPTION 'Booking % not found.', NEW.booking_id; |
| | 80 | END IF; |
| | 81 | |
| | 82 | -- Block reviews for bookings that arent completed |
| | 83 | IF v_booking_status <> 'Completed' THEN |
| | 84 | RAISE EXCEPTION 'Cannot leave a review for a booking that is % (Must be Completed)', v_booking_status; |
| | 85 | END IF; |
| | 86 | |
| | 87 | RETURN NEW; |
| | 88 | END; |
| | 89 | $$; |
| | 90 | |
| | 91 | DROP TRIGGER IF EXISTS trg_review_integrity ON reviews; |
| | 92 | CREATE TRIGGER trg_review_integrity |
| | 93 | BEFORE INSERT ON reviews |
| | 94 | FOR EACH ROW |
| | 95 | EXECUTE FUNCTION petsitter_trg_review_integrity(); |
| | 96 | |
| | 97 | -- Make reviews non-updateable |
| | 98 | CREATE OR REPLACE FUNCTION petsitter_trg_reviews_no_update() |
| | 99 | RETURNS trigger |
| | 100 | LANGUAGE plpgsql |
| | 101 | AS $$ |
| | 102 | BEGIN |
| | 103 | RAISE EXCEPTION 'Reviews are permanent. Updates are not allowed for review_id %.', OLD.review_id; |
| | 104 | END; |
| | 105 | $$; |
| | 106 | |
| | 107 | DROP TRIGGER IF EXISTS trg_reviews_no_update ON reviews; |
| | 108 | CREATE TRIGGER trg_reviews_no_update |
| | 109 | BEFORE UPDATE ON reviews |
| | 110 | FOR EACH ROW |
| | 111 | EXECUTE FUNCTION petsitter_trg_reviews_no_update(); |
| | 112 | }}} |
| | 113 | |
| | 114 | ---- |
| | 115 | |
| | 116 | == 3. Data constraints requirements: Platform Analytics and Reporting == |
| | 117 | '''Data requirements description''' |
| | 118 | |
| | 119 | The system can create reports of long term business operations: |
| | 120 | * '''Monthly reports:''' Calculates total revenue and booking counts per sitter, per month. |
| | 121 | * '''Top Rated Sitters:''' Calculates average ratings and total completed jobs. |
| | 122 | |
| | 123 | === '''Implementation''' |
| | 124 | '''Views''' |
| | 125 | {{{ |
| | 126 | #!sql |
| | 127 | CREATE OR REPLACE VIEW v_monthly_sitter_reports AS |
| | 128 | SELECT |
| | 129 | u.user_id AS sitter_id, |
| | 130 | u.first_name, |
| | 131 | u.last_name, |
| | 132 | date_trunc('month', b.date_to) AS month, |
| | 133 | COUNT(b.booking_id) AS total_completed_jobs, |
| | 134 | SUM(p.amount) AS total_revenue |
| | 135 | FROM users u |
| | 136 | JOIN bookings b ON u.user_id = b.sitter_id |
| | 137 | JOIN payments p ON b.booking_id = p.booking_id |
| | 138 | WHERE b.status = 'Completed' |
| | 139 | GROUP BY u.user_id, u.first_name, u.last_name, date_trunc('month', b.date_to) |
| | 140 | ORDER BY month DESC, total_revenue DESC; |
| | 141 | |
| | 142 | CREATE OR REPLACE VIEW v_top_rated_sitters AS |
| | 143 | SELECT |
| | 144 | u.user_id AS sitter_id, |
| | 145 | u.first_name, |
| | 146 | u.last_name, |
| | 147 | COUNT(r.review_id) AS total_reviews, |
| | 148 | ROUND(AVG(r.rating)::numeric, 2) AS average_rating |
| | 149 | FROM users u |
| | 150 | JOIN bookings b ON u.user_id = b.sitter_id |
| | 151 | JOIN reviews r ON b.booking_id = r.booking_id |
| | 152 | GROUP BY u.user_id, u.first_name, u.last_name |
| | 153 | HAVING COUNT(r.review_id) > 0 |
| | 154 | ORDER BY average_rating DESC, total_reviews DESC; |
| | 155 | }}} |
| | 156 | |
| | 157 | ---- |
| | 158 | |
| | 159 | == 4. Data constraints requirements: Background Jobs (Automated System Cleanup) == |
| | 160 | '''Data requirements description''' |
| | 161 | |
| | 162 | Rules for pending bookings that must be enforced asynchronously by the database server to keep the platform clean: |
| | 163 | * If a booking request remains 'Pending' and the {{{date_from}}} passes, it should automatically be marked as 'Expired'. |
| | 164 | * Runs automatically every hour |
| | 165 | |
| | 166 | === '''Implementation''' |
| | 167 | '''Stored procedures''' |
| | 168 | {{{ |
| | 169 | #!sql |
| | 170 | -- Enable pg cronjob |
| | 171 | CREATE EXTENSION IF NOT EXISTS pg_cron; |
| | 172 | |
| | 173 | -- Stored procedure for clean up expired bookings |
| | 174 | CREATE OR REPLACE PROCEDURE petsitter_job_expire_pending_bookings() |
| | 175 | LANGUAGE plpgsql |
| | 176 | AS $$ |
| | 177 | BEGIN |
| | 178 | UPDATE bookings |
| | 179 | SET status = 'Expired' |
| | 180 | WHERE status = 'Pending' |
| | 181 | AND date_from < now(); |
| | 182 | END; |
| | 183 | $$; |
| | 184 | }}} |
| | 185 | |
| | 186 | '''Views''' |
| | 187 | {{{ |
| | 188 | CREATE OR REPLACE VIEW v_expired_pending_bookings AS |
| | 189 | SELECT * |
| | 190 | FROM bookings |
| | 191 | WHERE status = 'Expired'; |
| | 192 | }}} |
| | 193 | |
| | 194 | '''Cronjob''' |
| | 195 | {{{ |
| | 196 | -- Schedule the job to run every hour |
| | 197 | SELECT cron.schedule('petsitter_expire_bookings', '0 * * * *', $$CALL petsitter_job_expire_pending_bookings();$$); |
| | 198 | }}} |