AdvancedDatabaseDevelopment: advanced_db.sql

File advanced_db.sql, 23.6 KB (added by 183164, 12 days ago)
Line 
1-- =============================================================
2-- CityFix - advanced_db.sql (фаза P7)
3-- Домени, функции, тригери, процедури, погледи и позадинска задача.
4--
5-- Редослед на извршување:
6-- 1. schema_creation.sql
7-- 2. advanced_db.sql (оваа скрипта)
8-- 3. data_load.sql
9-- Скриптата може да се извршува повеќепати.
10-- =============================================================
11
12SET search_path TO project;
13SET client_min_messages TO warning;
14
15-- Тригерите и погледите зависат од колоните што се менуваат подолу, па прво се бришат.
16DROP TRIGGER IF EXISTS reports_before_insert ON reports;
17DROP TRIGGER IF EXISTS reports_before_status_update ON reports;
18DROP TRIGGER IF EXISTS status_logs_before_insert ON status_logs;
19DROP TRIGGER IF EXISTS status_logs_after_insert ON status_logs;
20DROP TRIGGER IF EXISTS reports_status_consistency ON reports;
21DROP TRIGGER IF EXISTS status_logs_immutable ON status_logs;
22DROP TRIGGER IF EXISTS assignments_before_insert ON assignments;
23DROP TRIGGER IF EXISTS comments_before_insert ON comments;
24DROP MATERIALIZED VIEW IF EXISTS mv_report_times; -- се креира повторно во other_topics.sql (P9)
25DROP VIEW IF EXISTS v_public_reports;
26DROP VIEW IF EXISTS v_report_overview;
27DROP VIEW IF EXISTS v_worker_workload;
28
29
30-- =============================================================
31-- 1. ДОМЕНИ
32-- =============================================================
33DO $$
34BEGIN
35 IF NOT EXISTS (SELECT 1 FROM pg_type t JOIN pg_namespace n ON n.oid = t.typnamespace
36 WHERE n.nspname = 'project' AND t.typname = 'email_address') THEN
37 CREATE DOMAIN project.email_address AS VARCHAR(150)
38 CHECK (VALUE ~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$');
39 END IF;
40 IF NOT EXISTS (SELECT 1 FROM pg_type t JOIN pg_namespace n ON n.oid = t.typnamespace
41 WHERE n.nspname = 'project' AND t.typname = 'phone_number') THEN
42 CREATE DOMAIN project.phone_number AS VARCHAR(20)
43 CHECK (VALUE ~ '^\+?[0-9]{6,19}$');
44 END IF;
45 IF NOT EXISTS (SELECT 1 FROM pg_type t JOIN pg_namespace n ON n.oid = t.typnamespace
46 WHERE n.nspname = 'project' AND t.typname = 'report_status') THEN
47 CREATE DOMAIN project.report_status AS VARCHAR(20)
48 CHECK (VALUE IN ('submitted', 'received', 'in_progress', 'resolved', 'rejected'));
49 END IF;
50 IF NOT EXISTS (SELECT 1 FROM pg_type t JOIN pg_namespace n ON n.oid = t.typnamespace
51 WHERE n.nspname = 'project' AND t.typname = 'priority_level') THEN
52 CREATE DOMAIN project.priority_level AS VARCHAR(10)
53 CHECK (VALUE IN ('low', 'medium', 'high', 'urgent'));
54 END IF;
55END $$;
56
57-- Колоните ги користат домените наместо посебните CHECK ограничувања од P2.
58ALTER TABLE admins DROP CONSTRAINT IF EXISTS ck_admins_email;
59ALTER TABLE workers DROP CONSTRAINT IF EXISTS ck_workers_email;
60ALTER TABLE citizens DROP CONSTRAINT IF EXISTS ck_citizens_email;
61ALTER TABLE citizens DROP CONSTRAINT IF EXISTS ck_citizens_phone;
62ALTER TABLE reports DROP CONSTRAINT IF EXISTS ck_reports_status;
63ALTER TABLE reports DROP CONSTRAINT IF EXISTS ck_reports_priority;
64ALTER TABLE status_logs DROP CONSTRAINT IF EXISTS ck_status_logs_status;
65
66ALTER TABLE admins ALTER COLUMN email TYPE email_address;
67ALTER TABLE workers ALTER COLUMN email TYPE email_address;
68ALTER TABLE citizens ALTER COLUMN email TYPE email_address;
69ALTER TABLE citizens ALTER COLUMN phone TYPE phone_number;
70ALTER TABLE reports ALTER COLUMN status TYPE report_status;
71ALTER TABLE reports ALTER COLUMN priority TYPE priority_level;
72ALTER TABLE status_logs ALTER COLUMN status TYPE report_status;
73
74
75-- =============================================================
76-- 2. ПОМОШНИ ФУНКЦИИ
77-- =============================================================
78
79-- Дозволени премини помеѓу статусите на пријавата.
80CREATE OR REPLACE FUNCTION status_transition_allowed(p_from TEXT, p_to TEXT)
81RETURNS BOOLEAN LANGUAGE sql IMMUTABLE AS $$
82 SELECT (p_from, p_to) IN (('submitted', 'received'),
83 ('submitted', 'rejected'),
84 ('received', 'in_progress'),
85 ('received', 'rejected'),
86 ('in_progress', 'resolved'),
87 ('in_progress', 'rejected'));
88$$;
89
90-- Нумерички ранг на приоритетот (low = 1 ... urgent = 4) и обратно.
91CREATE OR REPLACE FUNCTION priority_rank(p TEXT)
92RETURNS INTEGER LANGUAGE sql IMMUTABLE AS $$
93 SELECT CASE p WHEN 'low' THEN 1 WHEN 'medium' THEN 2
94 WHEN 'high' THEN 3 WHEN 'urgent' THEN 4 END;
95$$;
96
97CREATE OR REPLACE FUNCTION priority_from_rank(r INTEGER)
98RETURNS TEXT LANGUAGE sql IMMUTABLE AS $$
99 SELECT (ARRAY['low', 'medium', 'high', 'urgent'])[GREATEST(1, LEAST(4, r))];
100$$;
101
102-- Рок за реакција (SLA) во денови според приоритетот.
103CREATE OR REPLACE FUNCTION sla_days(p TEXT)
104RETURNS INTEGER LANGUAGE sql IMMUTABLE AS $$
105 SELECT CASE p WHEN 'urgent' THEN 1 WHEN 'high' THEN 3
106 WHEN 'medium' THEN 7 ELSE 14 END;
107$$;
108
109-- Растојание во метри помеѓу две точки (формула на Haversine).
110CREATE OR REPLACE FUNCTION distance_m(lat1 NUMERIC, lon1 NUMERIC, lat2 NUMERIC, lon2 NUMERIC)
111RETURNS DOUBLE PRECISION LANGUAGE sql IMMUTABLE AS $$
112 SELECT 2 * 6371000 * asin(sqrt(
113 power(sin(radians(lat2 - lat1) / 2), 2)
114 + cos(radians(lat1)) * cos(radians(lat2))
115 * power(sin(radians(lon2 - lon1) / 2), 2)));
116$$;
117
118-- Активни пријави од иста категорија во даден радиус (за проверка на дупликати).
119CREATE OR REPLACE FUNCTION find_similar_reports(p_category_id INTEGER,
120 p_latitude NUMERIC,
121 p_longitude NUMERIC,
122 p_radius_m DOUBLE PRECISION DEFAULT 150,
123 p_exclude_report_id INTEGER DEFAULT NULL)
124RETURNS TABLE (report_id INTEGER, description TEXT, status TEXT,
125 created_at TIMESTAMP, distance_m DOUBLE PRECISION)
126LANGUAGE sql STABLE AS $$
127 SELECT r.report_id, r.description, r.status::TEXT, r.created_at,
128 ROUND(distance_m(p_latitude, p_longitude, r.latitude, r.longitude)::NUMERIC, 1)
129 FROM project.reports r
130 WHERE r.category_id = p_category_id
131 AND r.status NOT IN ('resolved', 'rejected')
132 AND r.latitude IS NOT NULL
133 AND r.report_id IS DISTINCT FROM p_exclude_report_id
134 AND distance_m(p_latitude, p_longitude, r.latitude, r.longitude) <= p_radius_m
135 ORDER BY 5;
136$$;
137
138
139-- =============================================================
140-- 3. ТРИГЕРИ - животен циклус и конзистентност на статусот
141-- =============================================================
142
143-- 3.1 Нова пријава: статусот мора да биде 'submitted'. Ако во близина веќе има
144-- барем 2 активни пријави за истиот проблем, приоритетот се крева на најмалку 'high'.
145CREATE OR REPLACE FUNCTION trg_reports_before_insert()
146RETURNS TRIGGER LANGUAGE plpgsql AS $$
147DECLARE
148 v_similar INTEGER;
149BEGIN
150 IF NEW.status IS DISTINCT FROM 'submitted' THEN
151 RAISE EXCEPTION 'Новата пријава мора да има статус submitted (добиено: %)', NEW.status;
152 END IF;
153 IF NEW.latitude IS NOT NULL THEN
154 SELECT COUNT(*) INTO v_similar
155 FROM find_similar_reports(NEW.category_id, NEW.latitude, NEW.longitude, 150);
156 IF v_similar >= 2 AND priority_rank(NEW.priority) < priority_rank('high') THEN
157 NEW.priority := 'high';
158 END IF;
159 END IF;
160 RETURN NEW;
161END $$;
162
163CREATE TRIGGER reports_before_insert
164 BEFORE INSERT ON reports
165 FOR EACH ROW EXECUTE FUNCTION trg_reports_before_insert();
166
167-- 3.2 Промена на статусот директно во reports мора да биде дозволен премин.
168CREATE OR REPLACE FUNCTION trg_reports_before_status_update()
169RETURNS TRIGGER LANGUAGE plpgsql AS $$
170BEGIN
171 IF NEW.status IS DISTINCT FROM OLD.status
172 AND NOT status_transition_allowed(OLD.status, NEW.status) THEN
173 RAISE EXCEPTION 'Недозволена промена на статусот на пријавата % од % во %',
174 OLD.report_id, OLD.status, NEW.status;
175 END IF;
176 RETURN NEW;
177END $$;
178
179CREATE TRIGGER reports_before_status_update
180 BEFORE UPDATE OF status ON reports
181 FOR EACH ROW EXECUTE FUNCTION trg_reports_before_status_update();
182
183-- 3.3 Нов запис во историјата: првиот мора да биде 'submitted' (без работник),
184-- секој следен мора да биде дозволен премин, да не е постар од претходниот
185-- и да го направи работник кој е доделен на пријавата.
186CREATE OR REPLACE FUNCTION trg_status_logs_before_insert()
187RETURNS TRIGGER LANGUAGE plpgsql AS $$
188DECLARE
189 v_last status_logs%ROWTYPE;
190BEGIN
191 SELECT * INTO v_last
192 FROM status_logs
193 WHERE report_id = NEW.report_id
194 ORDER BY changed_at DESC, log_id DESC
195 LIMIT 1;
196
197 IF NOT FOUND THEN
198 IF NEW.status <> 'submitted' OR NEW.worker_id IS NOT NULL THEN
199 RAISE EXCEPTION 'Првиот запис за пријавата % мора да биде submitted, без работник',
200 NEW.report_id;
201 END IF;
202 RETURN NEW;
203 END IF;
204
205 IF NOT status_transition_allowed(v_last.status, NEW.status) THEN
206 RAISE EXCEPTION 'Недозволена промена на статусот на пријавата % од % во %',
207 NEW.report_id, v_last.status, NEW.status;
208 END IF;
209 IF NEW.changed_at < v_last.changed_at THEN
210 RAISE EXCEPTION 'Промената не може да биде постара од претходната (%)', v_last.changed_at;
211 END IF;
212 IF NEW.worker_id IS NULL THEN
213 RAISE EXCEPTION 'Промената на статусот мора да ја направи работник';
214 END IF;
215 IF NOT EXISTS (SELECT 1 FROM assignments a
216 WHERE a.report_id = NEW.report_id AND a.worker_id = NEW.worker_id) THEN
217 RAISE EXCEPTION 'Работникот % не е доделен на пријавата %', NEW.worker_id, NEW.report_id;
218 END IF;
219 RETURN NEW;
220END $$;
221
222CREATE TRIGGER status_logs_before_insert
223 BEFORE INSERT ON status_logs
224 FOR EACH ROW EXECUTE FUNCTION trg_status_logs_before_insert();
225
226-- 3.4 По внесот во историјата, статусот во reports автоматски се усогласува.
227CREATE OR REPLACE FUNCTION trg_status_logs_after_insert()
228RETURNS TRIGGER LANGUAGE plpgsql AS $$
229BEGIN
230 UPDATE reports
231 SET status = NEW.status
232 WHERE report_id = NEW.report_id
233 AND status IS DISTINCT FROM NEW.status;
234 RETURN NULL;
235END $$;
236
237CREATE TRIGGER status_logs_after_insert
238 AFTER INSERT ON status_logs
239 FOR EACH ROW EXECUTE FUNCTION trg_status_logs_after_insert();
240
241-- 3.5 Одложено ограничување: на крајот на трансакцијата статусот во reports
242-- мора да е еднаков на последниот запис во историјата.
243CREATE OR REPLACE FUNCTION trg_reports_status_consistency()
244RETURNS TRIGGER LANGUAGE plpgsql AS $$
245DECLARE
246 v_current TEXT;
247 v_logged TEXT;
248BEGIN
249 SELECT status INTO v_current FROM reports WHERE report_id = NEW.report_id;
250 IF NOT FOUND THEN
251 RETURN NULL; -- пријавата е избришана во истата трансакција
252 END IF;
253 SELECT status INTO v_logged
254 FROM status_logs
255 WHERE report_id = NEW.report_id
256 ORDER BY changed_at DESC, log_id DESC
257 LIMIT 1;
258 IF v_logged IS DISTINCT FROM v_current THEN
259 RAISE EXCEPTION 'Статусот на пријавата % (%) не одговара на историјата (%)',
260 NEW.report_id, v_current, COALESCE(v_logged, 'нема запис');
261 END IF;
262 RETURN NULL;
263END $$;
264
265CREATE CONSTRAINT TRIGGER reports_status_consistency
266 AFTER INSERT OR UPDATE OF status ON reports
267 DEFERRABLE INITIALLY DEFERRED
268 FOR EACH ROW EXECUTE FUNCTION trg_reports_status_consistency();
269
270-- 3.6 Историјата на статуси не смее да се менува ни брише (ревизорска трага).
271-- Дозволено е само каскадно бришење заедно со пријавата.
272CREATE OR REPLACE FUNCTION trg_status_logs_immutable()
273RETURNS TRIGGER LANGUAGE plpgsql AS $$
274BEGIN
275 IF TG_OP = 'DELETE'
276 AND NOT EXISTS (SELECT 1 FROM reports WHERE report_id = OLD.report_id) THEN
277 RETURN OLD; -- каскадно бришење: пријавата веќе е избришана
278 END IF;
279 RAISE EXCEPTION 'Историјата на статусите не може да се менува ни брише';
280END $$;
281
282CREATE TRIGGER status_logs_immutable
283 BEFORE UPDATE OR DELETE ON status_logs
284 FOR EACH ROW EXECUTE FUNCTION trg_status_logs_immutable();
285
286
287-- =============================================================
288-- 4. ТРИГЕРИ - правила за доделувања и коментари
289-- =============================================================
290
291-- 4.1 Затворена пријава не може да се доделува; доделувањето не смее
292-- да биде пред поднесувањето на пријавата.
293CREATE OR REPLACE FUNCTION trg_assignments_before_insert()
294RETURNS TRIGGER LANGUAGE plpgsql AS $$
295DECLARE
296 v_report reports%ROWTYPE;
297BEGIN
298 SELECT * INTO v_report FROM reports WHERE report_id = NEW.report_id;
299 IF v_report.status IN ('resolved', 'rejected') THEN
300 RAISE EXCEPTION 'Пријавата % е затворена (%) и не може да се доделува',
301 NEW.report_id, v_report.status;
302 END IF;
303 IF NEW.assigned_at < v_report.created_at THEN
304 RAISE EXCEPTION 'Доделувањето не може да биде пред поднесувањето на пријавата';
305 END IF;
306 RETURN NEW;
307END $$;
308
309CREATE TRIGGER assignments_before_insert
310 BEFORE INSERT ON assignments
311 FOR EACH ROW EXECUTE FUNCTION trg_assignments_before_insert();
312
313-- 4.2 Коментар може да пишува само работник доделен на пријавата,
314-- и само додека пријавата не е затворена.
315CREATE OR REPLACE FUNCTION trg_comments_before_insert()
316RETURNS TRIGGER LANGUAGE plpgsql AS $$
317DECLARE
318 v_status TEXT;
319BEGIN
320 IF NOT EXISTS (SELECT 1 FROM assignments a
321 WHERE a.report_id = NEW.report_id AND a.worker_id = NEW.worker_id) THEN
322 RAISE EXCEPTION 'Работникот % не е доделен на пријавата %', NEW.worker_id, NEW.report_id;
323 END IF;
324 SELECT status INTO v_status FROM reports WHERE report_id = NEW.report_id;
325 IF v_status IN ('resolved', 'rejected') THEN
326 RAISE EXCEPTION 'Пријавата % е затворена и не прима нови коментари', NEW.report_id;
327 END IF;
328 RETURN NEW;
329END $$;
330
331CREATE TRIGGER comments_before_insert
332 BEFORE INSERT ON comments
333 FOR EACH ROW EXECUTE FUNCTION trg_comments_before_insert();
334
335
336-- =============================================================
337-- 5. СКЛАДИРАНИ ПРОЦЕДУРИ И ФУНКЦИИ ЗА АПЛИКАЦИЈАТА
338-- =============================================================
339
340-- 5.1 Поднесување на пријава со фотографии во една операција.
341CREATE OR REPLACE FUNCTION submit_report(p_citizen_id INTEGER,
342 p_category_id INTEGER,
343 p_description TEXT,
344 p_location_text TEXT,
345 p_latitude NUMERIC,
346 p_longitude NUMERIC,
347 p_photos TEXT[] DEFAULT '{}')
348RETURNS INTEGER LANGUAGE plpgsql AS $$
349DECLARE
350 v_report_id INTEGER;
351 v_created_at TIMESTAMP;
352BEGIN
353 INSERT INTO reports (description, location_text, latitude, longitude, category_id, citizen_id)
354 VALUES (p_description, p_location_text, p_latitude, p_longitude, p_category_id, p_citizen_id)
355 RETURNING report_id, created_at INTO v_report_id, v_created_at;
356
357 INSERT INTO status_logs (status, changed_at, report_id, worker_id)
358 VALUES ('submitted', v_created_at, v_report_id, NULL);
359
360 INSERT INTO photos (image_url, report_id)
361 SELECT '/uploads/reports/' || v_report_id || '/' || f, v_report_id
362 FROM unnest(p_photos) AS f;
363
364 RETURN v_report_id;
365END $$;
366
367-- 5.2 Промена на статусот од страна на работник.
368-- Тригерите ги проверуваат правилата и го усогласуваат статусот во reports.
369CREATE OR REPLACE PROCEDURE change_report_status(p_report_id INTEGER,
370 p_worker_id INTEGER,
371 p_new_status TEXT,
372 p_note TEXT DEFAULT NULL)
373LANGUAGE plpgsql AS $$
374BEGIN
375 INSERT INTO status_logs (status, note, report_id, worker_id)
376 VALUES (p_new_status, p_note, p_report_id, p_worker_id);
377END $$;
378
379-- 5.3 Автоматско зголемување на приоритетот на пријави кои долго чекаат.
380-- Потребен минимален приоритет според деновите од последната промена на статусот:
381-- 3+ дена -> medium, 7+ дена -> high, 14+ дена -> urgent.
382CREATE OR REPLACE FUNCTION escalate_overdue_reports()
383RETURNS TABLE (report_id INTEGER, old_priority TEXT, new_priority TEXT, days_waiting INTEGER)
384LANGUAGE plpgsql AS $$
385BEGIN
386 RETURN QUERY
387 WITH waiting AS (
388 SELECT r.report_id, r.priority::TEXT AS priority,
389 EXTRACT(DAY FROM now()::TIMESTAMP - MAX(l.changed_at))::INTEGER AS days_waiting
390 FROM project.reports r
391 JOIN project.status_logs l ON l.report_id = r.report_id
392 WHERE r.status NOT IN ('resolved', 'rejected')
393 GROUP BY r.report_id
394 ),
395 target AS (
396 SELECT w.report_id, w.priority, w.days_waiting,
397 priority_from_rank(GREATEST(
398 priority_rank(w.priority),
399 CASE WHEN w.days_waiting >= 14 THEN 4
400 WHEN w.days_waiting >= 7 THEN 3
401 WHEN w.days_waiting >= 3 THEN 2
402 ELSE 1 END)) AS new_priority
403 FROM waiting w
404 ),
405 updated AS (
406 UPDATE project.reports r
407 SET priority = t.new_priority
408 FROM target t
409 WHERE r.report_id = t.report_id
410 AND t.new_priority <> t.priority
411 RETURNING r.report_id, t.priority, t.new_priority, t.days_waiting
412 )
413 SELECT * FROM updated ORDER BY 1;
414END $$;
415
416
417-- =============================================================
418-- 6. ПОГЛЕДИ
419-- =============================================================
420
421-- 6.1 Јавен преглед за мапата: без лични податоци за граѓаните.
422CREATE VIEW v_public_reports AS
423SELECT r.report_id, c.name AS category, r.description, r.location_text,
424 r.latitude, r.longitude, r.status, r.priority, r.created_at,
425 (SELECT COUNT(*) FROM photos p WHERE p.report_id = r.report_id) AS photo_count
426FROM reports r
427JOIN categories c ON c.category_id = r.category_id
428WHERE r.status NOT IN ('resolved', 'rejected')
429 OR r.report_id IN (SELECT l.report_id FROM status_logs l
430 WHERE l.status = 'resolved'
431 AND l.changed_at >= now() - INTERVAL '30 days');
432
433-- 6.2 Целосен преглед за администраторите: доделени работници, последна промена и SLA.
434CREATE VIEW v_report_overview AS
435WITH last_change AS (
436 SELECT report_id, MAX(changed_at) AS last_changed_at
437 FROM status_logs
438 GROUP BY report_id
439)
440SELECT r.report_id, c.name AS category, r.description, r.status, r.priority,
441 ci.full_name AS citizen, r.created_at, lc.last_changed_at,
442 (SELECT string_agg(w.full_name, ', ' ORDER BY w.full_name)
443 FROM assignments a JOIN workers w ON w.worker_id = a.worker_id
444 WHERE a.report_id = r.report_id) AS assigned_workers,
445 EXTRACT(DAY FROM now()::TIMESTAMP - lc.last_changed_at)::INTEGER AS days_since_change,
446 sla_days(r.priority) AS sla_days,
447 (r.status NOT IN ('resolved', 'rejected')
448 AND now()::TIMESTAMP - lc.last_changed_at > sla_days(r.priority) * INTERVAL '1 day') AS is_overdue
449FROM reports r
450JOIN categories c ON c.category_id = r.category_id
451JOIN citizens ci ON ci.citizen_id = r.citizen_id
452JOIN last_change lc ON lc.report_id = r.report_id;
453
454-- 6.3 Оптовареност и учинок на работниците.
455CREATE VIEW v_worker_workload AS
456WITH resolved AS (
457 SELECT l.worker_id, l.report_id, l.changed_at AS resolved_at, r.created_at
458 FROM status_logs l
459 JOIN reports r ON r.report_id = l.report_id
460 WHERE l.status = 'resolved'
461)
462SELECT w.worker_id, w.full_name,
463 COUNT(DISTINCT r.report_id) FILTER (WHERE r.status NOT IN ('resolved', 'rejected'))
464 AS active_reports,
465 COUNT(DISTINCT r.report_id) FILTER (WHERE r.status NOT IN ('resolved', 'rejected')
466 AND r.priority IN ('high', 'urgent'))
467 AS active_high_priority,
468 (SELECT COUNT(*) FROM resolved rs
469 WHERE rs.worker_id = w.worker_id
470 AND rs.resolved_at >= now() - INTERVAL '90 days') AS resolved_last_90_days,
471 (SELECT ROUND(AVG(EXTRACT(EPOCH FROM rs.resolved_at - rs.created_at) / 86400)::NUMERIC, 1)
472 FROM resolved rs WHERE rs.worker_id = w.worker_id) AS avg_resolution_days
473FROM workers w
474LEFT JOIN assignments a ON a.worker_id = w.worker_id
475LEFT JOIN reports r ON r.report_id = a.report_id
476GROUP BY w.worker_id, w.full_name;
477
478
479-- =============================================================
480-- 7. ПОЗАДИНСКА ЗАДАЧА
481-- Секој ден во 06:00 се зголемува приоритетот на пријавите кои долго чекаат.
482-- Потребна е екстензијата pg_cron; ако ја нема, задачата се извршува
483-- однадвор (на пример cron на серверот) со:
484-- psql -c "SELECT * FROM project.escalate_overdue_reports();"
485-- =============================================================
486SET client_min_messages TO notice;
487DO $$
488BEGIN
489 IF EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_cron') THEN
490 PERFORM cron.schedule('cityfix-escalate-overdue', '0 6 * * *',
491 'SELECT * FROM project.escalate_overdue_reports()');
492 RAISE NOTICE 'Позадинската задача е закажана со pg_cron.';
493 ELSE
494 RAISE NOTICE 'pg_cron не е достапен: escalate_overdue_reports() закажете ја однадвор.';
495 END IF;
496END $$;