| Version 1 (modified by , 12 days ago) ( diff ) |
|---|
Advanced Database Development
Allergy–prescription safety enforcement
A patient should never be prescribed a medication that conflicts with one of their documented allergies. This requirement blocks the unsafe insert outright, at the database level before it can happen: a new prescription_medical_records row is rejected if the prescription is linked, via allergy_prescription_restrictions → prescription_restriction to any allergy already on that patient's medical record.
Custom domains
CREATE DOMAIN non_negative_currency AS DECIMAL(12,2)
CHECK (VALUE >= 0);
Triggers
CREATE TABLE IF NOT EXISTS prescription_allergy_conflicts_log (
log_id BIGSERIAL PRIMARY KEY,
record_id BIGINT NOT NULL REFERENCES medical_records(record_id),
prescription_id BIGINT NOT NULL REFERENCES prescriptions(prescription_id),
allergy_id BIGINT NOT NULL REFERENCES allergies(allergy_id),
conflict_type TEXT CHECK (conflict_type IN ('ACTIVE', 'RESOLVED')),
detected_date TIMESTAMP DEFAULT NOW(),
resolution_notes TEXT,
resolved_date TIMESTAMP
);
CREATE OR REPLACE FUNCTION t1_prescription_allergy_check()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
v_patient_id BIGINT;
v_conflict_allergy_id BIGINT;
v_allergy_name TEXT;
BEGIN
SELECT patient_id INTO v_patient_id
FROM medical_records
WHERE record_id = NEW.record_id;
IF v_patient_id IS NULL THEN
RAISE EXCEPTION 'Medical record % not found', NEW.record_id;
END IF;
SELECT apr.allergy_id, a.name
INTO v_conflict_allergy_id, v_allergy_name
FROM prescription_restriction pr_rest
JOIN allergy_prescription_restrictions apr ON apr.restriction_id = pr_rest.restriction_id
JOIN allergies a ON apr.allergy_id = a.allergy_id
JOIN medical_record_allergies mra ON a.allergy_id = mra.allergy_id
WHERE pr_rest.prescription_id = NEW.prescription_id
AND mra.record_id = NEW.record_id
LIMIT 1;
IF v_conflict_allergy_id IS NOT NULL THEN
RAISE EXCEPTION 'PRESCRIPTION_ALLERGY_CONFLICT: Prescription % conflicts with allergy % (%) in patient''s record %',
NEW.prescription_id, v_conflict_allergy_id, v_allergy_name, NEW.record_id;
END IF;
RETURN NEW;
END;
$$;
DROP TRIGGER IF EXISTS trg_prescription_allergy_check ON prescription_medical_records;
CREATE TRIGGER trg_prescription_allergy_check
BEFORE INSERT
ON prescription_medical_records
FOR EACH ROW
EXECUTE FUNCTION t1_prescription_allergy_check();
Views
CREATE OR REPLACE VIEW v_prescription_allergy_conflicts AS
SELECT
mr.record_id,
p.patient_id,
p.first_name,
p.last_name,
pmr.prescription_id,
pr.medication_name,
a.allergy_id,
a.name AS allergy_name,
a.allergy_severity,
apr.restriction_id,
pr_rest.description AS restriction_description,
CASE
WHEN mra.record_id IS NOT NULL THEN 'ACTIVE_CONFLICT'
ELSE 'ARCHIVED'
END AS conflict_status
FROM prescription_medical_records pmr
JOIN medical_records mr ON pmr.record_id = mr.record_id
JOIN patients p ON mr.patient_id = p.patient_id
JOIN prescriptions pr ON pmr.prescription_id = pr.prescription_id
JOIN prescription_restriction pr_rest ON pr.prescription_id = pr_rest.prescription_id
JOIN allergy_prescription_restrictions apr ON pr_rest.restriction_id = apr.restriction_id
JOIN allergies a ON apr.allergy_id = a.allergy_id
LEFT JOIN medical_record_allergies mra ON mr.record_id = mra.record_id
AND a.allergy_id = mra.allergy_id
WHERE a.allergy_severity IN ('HIGH', 'CRITICAL')
ORDER BY p.patient_id, a.allergy_severity DESC;
Appointment scheduling integrity
Appointments must obey real scheduling constraints so no scheduling in the past, no doublebooking the same doctor or patient in an overlapping time, no marking an appointment COMPLETED before its scheduled time, and valid status transitions only (SCHEDULED → IN_PROGRESS/COMPLETED/CANCELLED, IN_PROGRESS → COMPLETED/CANCELLED, nothing further out of COMPLETED/CANCELLED/NO_SHOW). A background job autotransitions appointments that remain SCHEDULED if they are 45+ minutes past their time to NO_SHOW.
Schema change
ALTER TABLE appointments DROP CONSTRAINT appointments_status_chk;
ALTER TABLE appointments ADD CONSTRAINT appointments_status_chk
CHECK (status IN ('SCHEDULED','COMPLETED','CANCELLED','IN_PROGRESS','NO_SHOW'));
Triggers
CREATE OR REPLACE FUNCTION is_valid_appointment_transition(p_old TEXT, p_new TEXT)
RETURNS BOOLEAN
LANGUAGE sql
IMMUTABLE
AS $$
SELECT CASE
WHEN p_old = p_new THEN TRUE
WHEN p_old = 'SCHEDULED' AND p_new IN ('IN_PROGRESS', 'COMPLETED', 'CANCELLED') THEN TRUE
WHEN p_old = 'IN_PROGRESS' AND p_new IN ('COMPLETED', 'CANCELLED') THEN TRUE
ELSE FALSE
END;
$$;
CREATE OR REPLACE FUNCTION trigger_appointments_enforce()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
v_combined_datetime TIMESTAMP;
BEGIN
v_combined_datetime := NEW.appointment_date::TIMESTAMP + NEW.appointment_time;
IF TG_OP = 'INSERT' AND v_combined_datetime < NOW() THEN
RAISE EXCEPTION 'Cannot schedule appointment in the past (appointment_date=%, appointment_time=%)',
NEW.appointment_date, NEW.appointment_time;
END IF;
IF TG_OP = 'UPDATE' THEN
IF NOT is_valid_appointment_transition(OLD.status, NEW.status) THEN
RAISE EXCEPTION 'Appointment status cannot transition from % to %',
OLD.status, NEW.status;
END IF;
IF NEW.status = 'COMPLETED' AND v_combined_datetime > NOW() THEN
RAISE EXCEPTION 'Cannot mark appointment COMPLETED before its scheduled time (scheduled for %)',
v_combined_datetime;
END IF;
END IF;
RETURN NEW;
END;
$$;
DROP TRIGGER IF EXISTS trigger_appointments_enforce ON appointments;
CREATE TRIGGER trigger_appointments_enforce
BEFORE INSERT OR UPDATE
ON appointments
FOR EACH ROW
EXECUTE FUNCTION trigger_appointments_enforce();
CREATE OR REPLACE FUNCTION t1_appointments_no_overlap()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
v_new_start TIMESTAMP;
v_new_end TIMESTAMP;
BEGIN
IF NEW.status NOT IN ('SCHEDULED', 'IN_PROGRESS', 'COMPLETED') THEN
RETURN NEW;
END IF;
v_new_start := NEW.appointment_date::TIMESTAMP + NEW.appointment_time;
v_new_end := v_new_start + INTERVAL '30 minutes';
IF EXISTS (
SELECT 1 FROM appointments a
WHERE a.doctor_id = NEW.doctor_id
AND a.status IN ('SCHEDULED', 'IN_PROGRESS', 'COMPLETED')
AND (a.appointment_date::TIMESTAMP + a.appointment_time) < v_new_end
AND v_new_start < (a.appointment_date::TIMESTAMP + a.appointment_time + INTERVAL '30 minutes')
AND (TG_OP <> 'UPDATE' OR a.appointment_id <> NEW.appointment_id)
) THEN
RAISE EXCEPTION 'Doctor % has overlapping appointment at %', NEW.doctor_id, NEW.appointment_date;
END IF;
IF EXISTS (
SELECT 1 FROM appointments a
WHERE a.patient_id = NEW.patient_id
AND a.status IN ('SCHEDULED', 'IN_PROGRESS', 'COMPLETED')
AND (a.appointment_date::TIMESTAMP + a.appointment_time) < v_new_end
AND v_new_start < (a.appointment_date::TIMESTAMP + a.appointment_time + INTERVAL '30 minutes')
AND (TG_OP <> 'UPDATE' OR a.appointment_id <> NEW.appointment_id)
) THEN
RAISE EXCEPTION 'Patient % has overlapping appointment at %', NEW.patient_id, NEW.appointment_date;
END IF;
RETURN NEW;
END;
$$;
DROP TRIGGER IF EXISTS trigger_appointments_no_overlap ON appointments;
CREATE TRIGGER trigger_appointments_no_overlap
BEFORE INSERT OR UPDATE
ON appointments
FOR EACH ROW
EXECUTE FUNCTION t1_appointments_no_overlap();
Stored procedures/functions
CREATE OR REPLACE PROCEDURE job_mark_no_show()
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE appointments
SET status = 'NO_SHOW'
WHERE status = 'SCHEDULED'
AND (appointment_date::TIMESTAMP + appointment_time) < (NOW() - INTERVAL '45 minutes');
END;
$$;
Views
CREATE OR REPLACE VIEW v_overdue_appointments AS
SELECT
a.appointment_id, a.patient_id, p.first_name, p.last_name,
a.doctor_id, d.first_name AS doctor_first_name, d.last_name AS doctor_last_name,
a.appointment_date, a.appointment_time, a.status,
NOW() - (a.appointment_date::TIMESTAMP + a.appointment_time) AS time_overdue
FROM appointments a
JOIN patients p ON a.patient_id = p.patient_id
JOIN doctors d ON a.doctor_id = d.doctor_id
WHERE a.status = 'SCHEDULED'
AND (a.appointment_date::TIMESTAMP + a.appointment_time) < (NOW() - INTERVAL '45 minutes')
ORDER BY time_overdue DESC;
Medical record consistency
Diagnoses, procedures, and referrals can be created independently but must stay consistent with the patient they actually serve as a mismatch like a diagnosis for one patient attached to a different patient's record, a doctor referring a patient to themselves, is both an integrity problem and a clinical safety risk.
Triggers
CREATE OR REPLACE FUNCTION t1_diagnosis_record_consistency()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
v_record_patient BIGINT;
v_diagnosis_patient BIGINT;
BEGIN
SELECT patient_id INTO v_record_patient FROM medical_records WHERE record_id = NEW.record_id;
SELECT patient_id INTO v_diagnosis_patient FROM diagnosis WHERE diagnosis_id = NEW.diagnosis_id;
IF v_record_patient IS NULL THEN
RAISE EXCEPTION 'Medical record % not found', NEW.record_id;
END IF;
IF v_diagnosis_patient IS NULL THEN
RAISE EXCEPTION 'Diagnosis % not found', NEW.diagnosis_id;
END IF;
IF v_record_patient <> v_diagnosis_patient THEN
RAISE EXCEPTION 'Diagnosis % belongs to patient %, but medical record % belongs to patient %',
NEW.diagnosis_id, v_diagnosis_patient, NEW.record_id, v_record_patient;
END IF;
RETURN NEW;
END;
$$;
DROP TRIGGER IF EXISTS trg_diagnosis_record_consistency ON diagnosis_medical_records;
CREATE TRIGGER trg_diagnosis_record_consistency
BEFORE INSERT ON diagnosis_medical_records
FOR EACH ROW EXECUTE FUNCTION t1_diagnosis_record_consistency();
CREATE OR REPLACE FUNCTION t2_procedure_diagnosis_consistency()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
v_diagnosis_patient BIGINT;
BEGIN
IF NEW.diagnosis_id IS NOT NULL THEN
SELECT patient_id INTO v_diagnosis_patient FROM diagnosis WHERE diagnosis_id = NEW.diagnosis_id;
IF v_diagnosis_patient IS NULL THEN
RAISE EXCEPTION 'Diagnosis % not found', NEW.diagnosis_id;
END IF;
IF NEW.patient_id <> v_diagnosis_patient THEN
RAISE EXCEPTION 'Procedure belongs to patient %, but diagnosis % belongs to patient %',
NEW.patient_id, NEW.diagnosis_id, v_diagnosis_patient;
END IF;
END IF;
RETURN NEW;
END;
$$;
DROP TRIGGER IF EXISTS trg_procedure_diagnosis_consistency ON performed_procedures;
CREATE TRIGGER trg_procedure_diagnosis_consistency
BEFORE INSERT OR UPDATE ON performed_procedures
FOR EACH ROW EXECUTE FUNCTION t2_procedure_diagnosis_consistency();
CREATE OR REPLACE FUNCTION t3_referral_consistency()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM medical_records WHERE record_id = NEW.record_id) THEN
RAISE EXCEPTION 'Medical record % not found', NEW.record_id;
END IF;
IF NEW.from_doctor_id = NEW.to_doctor_id THEN
RAISE EXCEPTION 'Doctor % cannot refer to themselves', NEW.from_doctor_id;
END IF;
RETURN NEW;
END;
$$;
DROP TRIGGER IF EXISTS trg_referral_consistency ON referrals;
CREATE TRIGGER trg_referral_consistency
BEFORE INSERT ON referrals
FOR EACH ROW EXECUTE FUNCTION t3_referral_consistency();
Views
CREATE OR REPLACE VIEW v_medical_record_overview AS
SELECT
mr.record_id,
p.patient_id, p.first_name, p.last_name, p.embg,
COUNT(DISTINCT dmr.diagnosis_id) AS diagnosis_count,
COUNT(DISTINCT CASE WHEN d.patient_id IS NOT NULL AND d.patient_id <> p.patient_id THEN dmr.diagnosis_id END) AS diagnosis_mismatches,
COUNT(DISTINCT mrp.procedure_id) AS procedures_count,
COUNT(DISTINCT mrl.result_id) AS lab_results_count,
COUNT(DISTINCT ref.referral_id) AS referrals_count,
COUNT(DISTINCT mra.allergy_id) AS allergies_count,
COUNT(DISTINCT CASE WHEN ref.from_doctor_id = ref.to_doctor_id THEN ref.referral_id END) AS self_referrals_detected
FROM medical_records mr
JOIN patients p ON mr.patient_id = p.patient_id
LEFT JOIN diagnosis_medical_records dmr ON dmr.record_id = mr.record_id
LEFT JOIN diagnosis d ON d.diagnosis_id = dmr.diagnosis_id
LEFT JOIN medical_record_procedures mrp ON mrp.record_id = mr.record_id
LEFT JOIN medical_record_lab_results mrl ON mrl.record_id = mr.record_id
LEFT JOIN referrals ref ON mr.record_id = ref.record_id
LEFT JOIN medical_record_allergies mra ON mr.record_id = mra.record_id
GROUP BY mr.record_id, p.patient_id, p.first_name, p.last_name, p.embg;
Billing integrity
Billing records must stay in sync with their line items and payment status must follow a valid state machine. billing.total_cost is automatically recalculated whenever a procedure or lab test is added to or removed from a bill, rather than relying on the application to keep it in sync manually. payment_status may only move PENDING → PAID or PENDING → CANCELLED but never backwards once finalized and every status change is written to an audit log. Since billing originally had no "issued" date , a created_at column was added so outstanding PENDING bills can be tracked for follow-up.
Schema change
ALTER TABLE billing ADD COLUMN IF NOT EXISTS created_at TIMESTAMP DEFAULT NOW();
CREATE TABLE IF NOT EXISTS billing_audit_log (
audit_id BIGSERIAL PRIMARY KEY,
bill_id BIGINT NOT NULL REFERENCES billing(bill_id),
old_amount DECIMAL(12,2),
new_amount DECIMAL(12,2),
old_status TEXT,
new_status TEXT,
change_type TEXT CHECK (change_type IN ('INSERT', 'UPDATE', 'LINE_ITEM_ADD', 'LINE_ITEM_REMOVE')),
changed_at TIMESTAMP DEFAULT NOW()
);
Stored procedures/functions
CREATE OR REPLACE FUNCTION recalculate_billing_total(p_bill_id BIGINT, p_change_type TEXT)
RETURNS VOID
LANGUAGE plpgsql
AS $$
DECLARE
v_procedure_total DECIMAL;
v_lab_total DECIMAL;
v_new_total DECIMAL;
BEGIN
SELECT COALESCE(SUM(p.cost), 0) INTO v_procedure_total
FROM billing_procedures bp
JOIN procedures p ON p.procedure_id = bp.procedure_id
WHERE bp.bill_id = p_bill_id;
SELECT COALESCE(SUM(lt.cost), 0) INTO v_lab_total
FROM billing_lab_tests blt
JOIN lab_tests lt ON lt.test_id = blt.test_id
WHERE blt.bill_id = p_bill_id;
v_new_total := v_procedure_total + v_lab_total;
UPDATE billing SET total_cost = v_new_total WHERE bill_id = p_bill_id;
INSERT INTO billing_audit_log (bill_id, new_amount, change_type)
VALUES (p_bill_id, v_new_total, p_change_type);
END;
$$;
CREATE OR REPLACE FUNCTION is_valid_billing_transition(p_old TEXT, p_new TEXT)
RETURNS BOOLEAN
LANGUAGE sql
IMMUTABLE
AS $$
SELECT CASE
WHEN p_old = p_new THEN TRUE
WHEN p_old = 'PENDING' AND p_new IN ('PAID', 'CANCELLED') THEN TRUE
ELSE FALSE
END;
$$;
CREATE OR REPLACE PROCEDURE job_billing_alerts()
LANGUAGE plpgsql
AS $$
DECLARE
v_overdue_count INT;
BEGIN
SELECT COUNT(*) INTO v_overdue_count FROM v_overdue_billings WHERE days_outstanding > 30;
RAISE NOTICE 'Found % overdue billing records requiring follow-up', v_overdue_count;
END;
$$;
Triggers
-- one shared trigger function, reused for both billing_procedures and billing_lab_tests
-- (both tables have a bill_id column, so NEW.bill_id / OLD.bill_id works either way)
CREATE OR REPLACE FUNCTION t1_billing_line_item_changed()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
IF TG_OP = 'DELETE' THEN
PERFORM recalculate_billing_total(OLD.bill_id, 'LINE_ITEM_REMOVE');
RETURN OLD;
ELSE
PERFORM recalculate_billing_total(NEW.bill_id, 'LINE_ITEM_ADD');
RETURN NEW;
END IF;
END;
$$;
DROP TRIGGER IF EXISTS trg_billing_procedures_update_total ON billing_procedures;
CREATE TRIGGER trg_billing_procedures_update_total
AFTER INSERT OR DELETE ON billing_procedures
FOR EACH ROW
EXECUTE FUNCTION t1_billing_line_item_changed();
DROP TRIGGER IF EXISTS trg_billing_lab_tests_update_total ON billing_lab_tests;
CREATE TRIGGER trg_billing_lab_tests_update_total
AFTER INSERT OR DELETE ON billing_lab_tests
FOR EACH ROW
EXECUTE FUNCTION t1_billing_line_item_changed();
CREATE OR REPLACE FUNCTION t2_billing_status_transition()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
IF NOT is_valid_billing_transition(OLD.payment_status, NEW.payment_status) THEN
RAISE EXCEPTION 'Cannot transition billing status from % to %',
OLD.payment_status, NEW.payment_status;
END IF;
IF OLD.payment_status <> NEW.payment_status THEN
INSERT INTO billing_audit_log (bill_id, old_status, new_status, change_type)
VALUES (NEW.bill_id, OLD.payment_status, NEW.payment_status, 'UPDATE');
END IF;
RETURN NEW;
END;
$$;
DROP TRIGGER IF EXISTS trg_billing_status_transition ON billing;
CREATE TRIGGER trg_billing_status_transition
BEFORE UPDATE ON billing
FOR EACH ROW
EXECUTE FUNCTION t2_billing_status_transition();
Views
CREATE OR REPLACE VIEW v_overdue_billings AS
SELECT
b.bill_id,
p.patient_id, p.first_name, p.last_name,
b.total_cost, b.payment_status, b.created_at,
CURRENT_DATE - b.created_at::DATE AS days_outstanding,
CASE
WHEN CURRENT_DATE - b.created_at::DATE > 60 THEN 'CRITICAL'
WHEN CURRENT_DATE - b.created_at::DATE > 30 THEN 'OVERDUE'
ELSE 'PENDING'
END AS urgency
FROM billing b
JOIN medical_records mr ON b.record_id = mr.record_id
JOIN patients p ON mr.patient_id = p.patient_id
WHERE b.payment_status = 'PENDING'
AND CURRENT_DATE - b.created_at::DATE >= 30
ORDER BY days_outstanding DESC;
Custom domains for EMBG and Phone number formats
Custom domains replace one-off column checks with reusable types. embg_format, email_format, and phone_number_format are new or stricter than before; non_negative_cost consolidates a rule already repeated on three tables. non_negative_cost can be applied immediately, since it matches an existing rule. The other three need existing data checked first, since a stricter domain fails if any row doesn't already conform.
Custom domains
CREATE DOMAIN embg_format AS TEXT
CHECK (VALUE ~ '^\d{13}$');
CREATE DOMAIN email_format AS TEXT
CHECK (
VALUE ~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'
AND LENGTH(VALUE) <= 254
);
CREATE DOMAIN phone_number_format AS TEXT
CHECK (VALUE ~ '^\+?[\d\s\-().]{7,20}$');
CREATE DOMAIN non_negative_cost AS DECIMAL(12,2)
CHECK (VALUE >= 0);
ALTER TABLE procedures ALTER COLUMN cost TYPE non_negative_cost USING cost::non_negative_cost;
ALTER TABLE lab_tests ALTER COLUMN cost TYPE non_negative_cost USING cost::non_negative_cost;
ALTER TABLE billing ALTER COLUMN total_cost TYPE non_negative_cost USING total_cost::non_negative_cost;
-- run these BEFORE altering embg / email / phone columns — every one of these
-- should return ZERO rows, otherwise the corresponding ALTER will fail:
-- SELECT patient_id, embg FROM patients WHERE embg !~ '^\d{13}$';
-- SELECT doctor_id, email_address FROM doctors WHERE email_address !~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$';
-- SELECT patient_id, email_address FROM patients WHERE email_address IS NOT NULL AND email_address !~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$';
-- SELECT patient_id, phone_number FROM patients WHERE phone_number IS NOT NULL AND phone_number !~ '^\+?[\d\s\-().]{7,20}$';
Revenue reporting (materialized view and background refresh job)
Revenue reports need expensive joins across billing, procedures, and lab tests, so computing them fresh on every request is wasteful. This materialized view precomputes monthly revenue, split by procedure vs lab test. Procedure revenue is attributed down to department, since procedures carries a doctor_id directly. Lab revenue can't be: lab_tests has no doctor or department reference, and billing_lab_tests links only to the catalog test_id, not a specific performed instance so lab rows are reported to the clinic only.The refresh procedure was verified by manual invocation.
Views
CREATE MATERIALIZED VIEW mv_revenue_monthly AS
SELECT
DATE_TRUNC('month', b.payment_date)::DATE AS month,
dept.department_id,
dept.department_name,
'PROCEDURE' AS revenue_type,
SUM(p.cost) AS revenue,
COUNT(DISTINCT b.bill_id) AS transaction_count
FROM billing b
JOIN billing_procedures bp ON bp.bill_id = b.bill_id
JOIN procedures p ON p.procedure_id = bp.procedure_id
JOIN doctors doc ON doc.doctor_id = p.doctor_id
JOIN departments dept ON dept.department_id = doc.department_id
WHERE b.payment_status = 'PAID'
GROUP BY DATE_TRUNC('month', b.payment_date), dept.department_id, dept.department_name
UNION ALL
SELECT
DATE_TRUNC('month', b.payment_date)::DATE AS month,
NULL AS department_id,
NULL AS department_name,
'LAB' AS revenue_type,
SUM(lt.cost) AS revenue,
COUNT(DISTINCT b.bill_id) AS transaction_count
FROM billing b
JOIN billing_lab_tests blt ON blt.bill_id = b.bill_id
JOIN lab_tests lt ON lt.test_id = blt.test_id
WHERE b.payment_status = 'PAID'
GROUP BY DATE_TRUNC('month', b.payment_date);
CREATE INDEX idx_mv_revenue_monthly_month ON mv_revenue_monthly (month, revenue_type);
CREATE OR REPLACE VIEW v_current_month_revenue AS
SELECT month, department_id, department_name, revenue_type, revenue, transaction_count
FROM mv_revenue_monthly
WHERE month = DATE_TRUNC('month', CURRENT_DATE)::DATE
ORDER BY revenue_type, revenue DESC;
Stored procedures/functions
CREATE OR REPLACE PROCEDURE medora_job_refresh_revenue_view()
LANGUAGE plpgsql
AS $$
BEGIN
REFRESH MATERIALIZED VIEW mv_revenue_monthly;
END;
$$;
