DatabaseCreation: console_1.sql

File console_1.sql, 208.1 KB (added by 231039, 10 days ago)
Line 
1-- noinspection SqlDialectInspectionForFile
2
3CREATE TABLE owner
4(
5 id SERIAL NOT NULL,
6 last_name varchar(255) NOT NULL,
7 first_name varchar(255) NOT NULL,
8 phone varchar(255),
9 email varchar(255),
10 address varchar(255),
11 gender varchar(1) CHECK (gender IN ('M', 'F')),
12 PRIMARY KEY (id)
13);
14
15CREATE TABLE pet
16(
17 id SERIAL NOT NULL,
18 name varchar(255) NOT NULL,
19 is_active bool NOT NULL DEFAULT true,
20 type varchar(255) CHECK (type IN ('mammal', 'bird', 'fish', 'amphibian', 'reptile')),
21 breed varchar(255),
22 age int4 CHECK (age >= 0),
23 medical_history text,
24 owner_id int4 NOT NULL,
25 PRIMARY KEY (id),
26 FOREIGN KEY (owner_id) REFERENCES owner (id)
27 ON DELETE CASCADE
28 ON UPDATE CASCADE
29);
30
31CREATE TABLE role
32(
33 id SERIAL NOT NULL,
34 name varchar(255) NOT NULL,
35 description varchar(255),
36 PRIMARY KEY (id)
37);
38
39CREATE TABLE employee
40(
41 id SERIAL NOT NULL,
42 last_name varchar(255) NOT NULL,
43 first_name varchar(255) NOT NULL,
44 phone varchar(255),
45 email varchar(255),
46 address varchar(255),
47 date_employment date DEFAULT CURRENT_DATE,
48 experience int4 DEFAULT 0 CHECK (experience >= 0),
49 role_id int4,
50 supervised_by int4,
51 gender varchar(1) CHECK (gender IN ('M', 'F')),
52 PRIMARY KEY (id),
53 FOREIGN KEY (role_id) REFERENCES role (id)
54 ON DELETE SET NULL
55 ON UPDATE CASCADE,
56 FOREIGN KEY (supervised_by) REFERENCES employee (id)
57 ON DELETE SET NULL
58 ON UPDATE CASCADE,
59 CONSTRAINT check_not_self_supervisor CHECK (supervised_by != id)
60);
61
62CREATE TABLE appointment
63(
64 id SERIAL NOT NULL,
65 date_appointment date NOT NULL,
66 reason varchar(255) NOT NULL,
67 phone varchar(255),
68 owner_id int4,
69 pet_id int4,
70 PRIMARY KEY (id),
71 FOREIGN KEY (owner_id) REFERENCES owner (id)
72 ON DELETE SET NULL
73 ON UPDATE CASCADE,
74 FOREIGN KEY (pet_id) REFERENCES pet (id)
75 ON DELETE SET NULL
76 ON UPDATE CASCADE
77);
78
79CREATE TABLE specialization
80(
81 id SERIAL NOT NULL,
82 specialization varchar(255) NOT NULL,
83 license_number varchar(255) NOT NULL,
84 employee_id int4 NOT NULL,
85 PRIMARY KEY (id),
86 FOREIGN KEY (employee_id) REFERENCES employee (id)
87 ON DELETE CASCADE
88 ON UPDATE CASCADE
89);
90
91CREATE TABLE certificate
92(
93 id SERIAL NOT NULL,
94 certificate_name varchar(255) NOT NULL,
95 employee_id int4 NOT NULL,
96 PRIMARY KEY (id),
97 FOREIGN KEY (employee_id) REFERENCES employee (id)
98 ON DELETE CASCADE
99 ON UPDATE CASCADE
100);
101
102CREATE TABLE examination_room
103(
104 id SERIAL NOT NULL,
105 room_number varchar(255) NOT NULL,
106 type varchar(255) NOT NULL CHECK (type IN
107 ('examination', 'surgery', 'radiology', 'laboratory', 'isolation ward',
108 'patient ward')),
109 capacity int4 CHECK (capacity > 0),
110 status varchar(255) DEFAULT 'available' CHECK (status IN ('available', 'unavailable')),
111 PRIMARY KEY (id)
112);
113
114CREATE TABLE examination
115(
116 id SERIAL NOT NULL,
117 date_examination date NOT NULL DEFAULT CURRENT_DATE,
118 status varchar(255) NOT NULL DEFAULT 'scheduled' CHECK (status IN ('scheduled', 'completed', 'cancelled')),
119 description text,
120 appointment_id int4,
121 employee_id int4,
122 examination_room_id int4,
123 PRIMARY KEY (id),
124 FOREIGN KEY (appointment_id) REFERENCES appointment (id)
125 ON DELETE SET NULL
126 ON UPDATE CASCADE,
127 FOREIGN KEY (employee_id) REFERENCES employee (id)
128 ON DELETE SET NULL
129 ON UPDATE CASCADE,
130 FOREIGN KEY (examination_room_id) REFERENCES examination_room (id)
131 ON DELETE SET NULL
132 ON UPDATE CASCADE
133);
134
135CREATE TABLE coupon
136(
137 id SERIAL NOT NULL,
138 code varchar(255) NOT NULL UNIQUE,
139 type varchar(255) NOT NULL CHECK (type IN ('fixed', 'percentage')),
140 value numeric(10, 2) NOT NULL CHECK (value >= 0),
141 valid_from date NOT NULL DEFAULT CURRENT_DATE,
142 valid_to date NOT NULL,
143 usage_limit int4 NOT NULL DEFAULT 1,
144 usage_count int4 NOT NULL DEFAULT 0,
145 is_active bool NOT NULL DEFAULT true,
146 min_total numeric(10, 2) DEFAULT 0 CHECK (min_total >= 0),
147 PRIMARY KEY (id),
148 CONSTRAINT check_dates CHECK (valid_to >= valid_from),
149 CONSTRAINT check_usage CHECK (usage_limit >= 0 AND usage_count >= 0 AND usage_count <= usage_limit),
150 CONSTRAINT check_percentage_value CHECK (
151 type = 'fixed' OR (type = 'percentage' AND value <= 100)
152 )
153);
154
155CREATE TABLE shop_item_category
156(
157 id SERIAL NOT NULL,
158 name varchar(255) NOT NULL,
159 parent_id int4,
160 PRIMARY KEY (id),
161 FOREIGN KEY (parent_id) REFERENCES shop_item_category (id)
162 ON DELETE SET NULL
163 ON UPDATE CASCADE
164);
165
166CREATE TABLE shop_item
167(
168 id SERIAL NOT NULL,
169 name varchar(255) NOT NULL,
170 price numeric(10, 2) NOT NULL CHECK (price >= 0),
171 stock int4 DEFAULT 0 CHECK (stock >= 0),
172 shop_item_category_id int4,
173 PRIMARY KEY (id),
174 FOREIGN KEY (shop_item_category_id) references shop_item_category (id)
175 ON DELETE SET NULL
176 ON UPDATE CASCADE
177);
178
179CREATE TABLE shop_item_attribute
180(
181 id SERIAL NOT NULL,
182 name varchar(255) NOT NULL,
183 data_type varchar(255) NOT NULL CHECK (data_type IN ('text', 'integer', 'decimal', 'boolean', 'date')),
184 shop_item_category_id int4 NOT NULL,
185 PRIMARY KEY (id),
186 FOREIGN KEY (shop_item_category_id) references shop_item_category (id)
187 ON DELETE CASCADE
188 ON UPDATE CASCADE
189);
190
191CREATE TABLE shop_item_attribute_value
192(
193 id SERIAL NOT NULL,
194 value varchar(255) NOT NULL,
195 notes varchar(255),
196 shop_item_attribute_id int4 NOT NULL,
197 shop_item_id int4 NOT NULL,
198 PRIMARY KEY (id),
199 FOREIGN KEY (shop_item_attribute_id) references shop_item_attribute (id)
200 ON DELETE CASCADE
201 ON UPDATE CASCADE,
202 FOREIGN KEY (shop_item_id) references shop_item (id)
203 ON DELETE CASCADE
204 ON UPDATE CASCADE
205);
206
207CREATE TABLE medicine
208(
209 id SERIAL NOT NULL,
210 name varchar(255) NOT NULL,
211 manufacturer varchar(255),
212 description varchar(255),
213 shop_item_id int4,
214 PRIMARY KEY (id),
215 FOREIGN KEY (shop_item_id) REFERENCES shop_item (id)
216 ON DELETE SET NULL
217 ON UPDATE CASCADE
218);
219
220CREATE TABLE prescription
221(
222 id SERIAL NOT NULL,
223 examination_id int4 NOT NULL UNIQUE,
224 date_start date NOT NULL,
225 date_end date NOT NULL,
226 description text,
227 PRIMARY KEY (id),
228 FOREIGN KEY (examination_id) REFERENCES examination (id)
229 ON DELETE CASCADE
230 ON UPDATE CASCADE,
231 CONSTRAINT check_dates CHECK (date_end > date_start)
232);
233
234CREATE TABLE prescription_medicine
235(
236 prescription_id int4 NOT NULL,
237 medicine_id int4 NOT NULL,
238 dosage int4 NOT NULL CHECK (dosage > 0),
239 num_days int4 NOT NULL CHECK (num_days > 0),
240 PRIMARY KEY (prescription_id, medicine_id),
241 FOREIGN KEY (prescription_id) REFERENCES prescription (id)
242 ON DELETE CASCADE
243 ON UPDATE CASCADE,
244 FOREIGN KEY (medicine_id) REFERENCES medicine (id)
245 ON DELETE CASCADE
246 ON UPDATE CASCADE
247);
248
249CREATE TABLE treatment_type
250(
251 id SERIAL NOT NULL,
252 name varchar(255) NOT NULL CHECK (name IN ('prescription', 'vaccination', 'consultation', 'operation')),
253 PRIMARY KEY (id)
254);
255
256CREATE TABLE treatment
257(
258 id SERIAL NOT NULL,
259 date_treatment date NOT NULL DEFAULT CURRENT_DATE,
260 notes text,
261 treatment_type_id int4,
262 examination_id int4,
263 PRIMARY KEY (id),
264 FOREIGN KEY (treatment_type_id) REFERENCES treatment_type (id)
265 ON DELETE SET NULL
266 ON UPDATE CASCADE,
267 FOREIGN KEY (examination_id) REFERENCES examination (id)
268 ON DELETE SET NULL
269 ON UPDATE CASCADE
270);
271
272CREATE TABLE treatment_attribute
273(
274 id SERIAL NOT NULL,
275 name varchar(255) NOT NULL,
276 data_type varchar(255) NOT NULL CHECK (data_type IN ('text', 'integer', 'decimal', 'boolean', 'date')),
277 treatment_type_id int4 NOT NULL,
278 PRIMARY KEY (id),
279 FOREIGN KEY (treatment_type_id) references treatment_type (id)
280 ON DELETE CASCADE
281 ON UPDATE CASCADE
282);
283
284CREATE TABLE treatment_attribute_value
285(
286 id SERIAL NOT NULL,
287 value varchar(255) NOT NULL,
288 notes varchar(255),
289 treatment_attribute_id int4 NOT NULL,
290 treatment_id int4 NOT NULL,
291 PRIMARY KEY (id),
292 FOREIGN KEY (treatment_attribute_id) references treatment_attribute (id)
293 ON DELETE CASCADE
294 ON UPDATE CASCADE,
295 FOREIGN KEY (treatment_id) references treatment (id)
296 ON DELETE CASCADE
297 ON UPDATE CASCADE
298);
299
300CREATE TABLE discount
301(
302 id SERIAL NOT NULL,
303 type varchar(255) NOT NULL CHECK (type IN ('fixed', 'percentage')),
304 value numeric(10, 2) NOT NULL CHECK (value >= 0),
305 description varchar(255),
306 date_from date DEFAULT CURRENT_DATE,
307 date_to date,
308 shop_item_id int4,
309 PRIMARY KEY (id),
310 FOREIGN KEY (shop_item_id) REFERENCES shop_item (id)
311 ON DELETE SET NULL
312 ON UPDATE CASCADE,
313 CONSTRAINT check_dates CHECK (date_to > date_from)
314);
315
316CREATE TABLE invoice
317(
318 id SERIAL NOT NULL,
319 date_invoice date NOT NULL DEFAULT CURRENT_DATE,
320 total numeric(10, 2) NOT NULL CHECK (total >= 0),
321 coupon_id int4,
322 owner_id int4,
323 PRIMARY KEY (id),
324 FOREIGN KEY (coupon_id) REFERENCES coupon (id)
325 ON DELETE SET NULL
326 ON UPDATE CASCADE,
327 FOREIGN KEY (owner_id) REFERENCES owner (id)
328 ON DELETE SET NULL
329 ON UPDATE CASCADE
330);
331
332CREATE TABLE invoice_item
333(
334 num_item int4 NOT NULL CHECK (num_item > 0),
335 invoice_id int4 NOT NULL,
336 price numeric(10, 2) NOT NULL CHECK (price >= 0),
337 quantity int4 NOT NULL DEFAULT 1 CHECK (quantity >= 0),
338 type varchar(255) CHECK (type IN ('shop_item', 'treatment')),
339 shop_item_id int4,
340 treatment_id int4,
341 PRIMARY KEY (num_item, invoice_id),
342 FOREIGN KEY (invoice_id) REFERENCES invoice (id)
343 ON DELETE CASCADE
344 ON UPDATE CASCADE,
345 FOREIGN KEY (shop_item_id) REFERENCES shop_item (id)
346 ON DELETE SET NULL
347 ON UPDATE CASCADE,
348 FOREIGN KEY (treatment_id) REFERENCES treatment (id)
349 ON DELETE SET NULL
350 ON UPDATE CASCADE,
351 CONSTRAINT check_type CHECK (
352 (type = 'shop_item' AND shop_item_id IS NOT NULL AND treatment_id IS NULL) OR
353 (type = 'treatment' AND treatment_id IS NOT NULL AND shop_item_id IS NULL)
354 )
355);
356
357CREATE TABLE payment
358(
359 id SERIAL NOT NULL,
360 date_payment date NOT NULL DEFAULT CURRENT_DATE,
361 amount numeric(10, 2) NOT NULL CHECK (amount >= 0),
362 method varchar(255) NOT NULL CHECK (method IN
363 ('cash', 'debit card', 'credit card', 'digital wallet', 'other')),
364 invoice_id int4 NOT NULL,
365 PRIMARY KEY (id),
366 FOREIGN KEY (invoice_id) REFERENCES invoice (id)
367 ON DELETE CASCADE
368 ON UPDATE CASCADE
369);
370
371-- ===============================
372-- composite primary key in invoice_item (num_item, invoice_id)
373-- ===============================
374CREATE OR REPLACE FUNCTION generate_num_item()
375 RETURNS TRIGGER AS
376$$
377BEGIN
378 SELECT COALESCE(MAX(num_item), 0) + 1
379 INTO NEW.num_item
380 FROM invoice_item
381 WHERE invoice_id = NEW.invoice_id;
382 RETURN NEW;
383END;
384$$ LANGUAGE plpgsql;
385
386CREATE TRIGGER trg_generate_num_item
387 BEFORE INSERT
388 ON invoice_item
389 FOR EACH ROW
390EXECUTE FUNCTION generate_num_item();
391
392
393
394-- DROP SCHEMA public CASCADE;
395-- CREATE SCHEMA public;
396
397
398
399
400-- temp tables for names, surnames and addresses
401create table temp_male_names
402(
403 id bigserial primary key,
404 year int4,
405 name text,
406 count int4
407);
408
409SHOW data_directory;
410
411COPY temp_male_names (year, name, count) FROM 'C:/Program Files/PostgreSQL/18/data/temp_files/baby_names_-_male.csv' DELIMITER ',' CSV HEADER;
412UPDATE temp_male_names
413SET name = TRIM(name);
414
415create table temp_female_names
416(
417 id bigserial primary key,
418 year int4,
419 name text,
420 count int4
421);
422
423COPY temp_female_names (year, name, count) FROM 'C:/Program Files/PostgreSQL/18/data/temp_files/baby_names_-_female_.csv' DELIMITER ',' CSV HEADER;
424UPDATE temp_female_names
425SET name = TRIM(name);
426
427create table temp_surnames
428(
429 id bigserial primary key,
430 year int4,
431 rank text,
432 surname text,
433 number int4
434);
435
436COPY temp_surnames (year, rank, surname, number) FROM 'C:/Program Files/PostgreSQL/18/data/temp_files/surnames.csv' DELIMITER ',' CSV HEADER;
437UPDATE temp_surnames
438SET surname = TRIM(surname);
439
440CREATE TABLE temp_addresses
441(
442 id bigserial primary key,
443 address text,
444 city text,
445 state text,
446 zip text
447);
448
449COPY temp_addresses (address, city, state, zip) FROM 'C:/Program Files/PostgreSQL/18/data/temp_files/addresses.csv' DELIMITER ',' CSV HEADER;
450UPDATE temp_addresses
451SET address = TRIM(address);
452UPDATE temp_addresses
453SET city = TRIM(city);
454UPDATE temp_addresses
455SET state = TRIM(state);
456UPDATE temp_addresses
457SET zip = TRIM(zip);
458
459
460-- ============================================================
461-- role
462-- ============================================================
463
464INSERT INTO role (name, description)
465VALUES ('Manager', 'Oversees clinic operations'),
466 ('Veterinarian', 'Licensed veterinarian responsible for examinations and treatments'),
467 ('Veterinary Assistant', 'Assists veterinarians during procedures'),
468 ('Receptionist', 'Handles appointments and client communication');
469
470-- ============================================================
471-- employee
472-- (50 employees: 1 manager, 9 doctors, 35 vet assistants, 5 receptionists)
473-- ============================================================
474
475WITH male_names_sample AS (SELECT name
476 FROM temp_male_names
477 ORDER BY random()
478 LIMIT 1000),
479 female_names_sample AS (SELECT name
480 FROM temp_female_names
481 ORDER BY random()
482 LIMIT 1000),
483 male_pool AS (SELECT fn.name AS first_name,
484 ln.surname AS last_name,
485 'M' AS gender
486 FROM male_names_sample fn
487 CROSS JOIN temp_surnames ln
488 ORDER BY random()
489 LIMIT 25),
490 female_pool AS (SELECT fn.name AS first_name,
491 ln.surname AS last_name,
492 'F' AS gender
493 FROM female_names_sample fn
494 CROSS JOIN temp_surnames ln
495 ORDER BY random()
496 LIMIT 25),
497 all_names AS (SELECT first_name, last_name, gender
498 FROM male_pool
499 UNION ALL
500 SELECT first_name, last_name, gender
501 FROM female_pool),
502 numbered AS (SELECT first_name,
503 last_name,
504 gender,
505 row_number() OVER (ORDER BY random()) AS rn
506 FROM all_names),
507 shuffled_addresses AS (SELECT address || ', ' || city || ', ' || state || ' ' || zip AS full_address,
508 row_number() OVER (ORDER BY random()) AS rn
509 FROM temp_addresses),
510 shuffled_phones AS (SELECT ((row_number() OVER (ORDER BY random()) % 9) + 1)::text AS d1,
511 lpad(((row_number() OVER (ORDER BY random()) * 73) % 900 + 100)::text, 3, '0') AS d2,
512 lpad(((row_number() OVER (ORDER BY random()) * 97) % 900 + 100)::text, 3, '0') AS d3,
513 row_number() OVER (ORDER BY random()) AS rn
514 FROM generate_series(1, 50))
515INSERT
516INTO employee (first_name, last_name, phone, email, address, gender, date_employment, role_id, supervised_by)
517SELECT n.first_name,
518 n.last_name,
519 '+389 7' || p.d1 || ' ' || p.d2 || ' ' || p.d3 AS phone,
520 lower(n.first_name) || '.' || lower(n.last_name) || '@pawcare.com' AS email,
521 a.full_address AS address,
522 n.gender,
523 (now() - interval '1 day' * floor(random() * 3650))::date AS date_employment,
524 CASE
525 WHEN n.rn = 1 THEN 1 -- manager
526 WHEN n.rn <= 10 THEN 2 -- veterinarians
527 WHEN n.rn <= 45 THEN 3 -- vet assistants (rn 11-45 = 35 employees)
528 ELSE 4 -- receptionists (rn 46-50 = 5 employees)
529 END AS role_id,
530 NULL AS supervised_by
531FROM numbered n
532 JOIN shuffled_phones p ON p.rn = n.rn
533 JOIN shuffled_addresses a ON a.rn = n.rn
534ORDER BY n.rn;
535
536-- experience for all employees: at least years since employment
537UPDATE employee
538SET experience = (
539 extract(year from age(now(), date_employment))
540 + floor(random() * 5)
541 )::int
542WHERE role_id IN (3, 4); -- vet assistants and receptionists: 0-5 extra years
543
544-- vets get more experience
545UPDATE employee
546SET experience = (
547 extract(year from age(now(), date_employment))
548 + floor(random() * 15) + 5
549 )::int
550WHERE role_id = 2; -- veterinarians: at least 5 extra years on top
551
552-- manager gets more experience
553UPDATE employee
554SET experience = (
555 extract(year from age(now(), date_employment))
556 + floor(random() * 10) + 8
557 )::int
558WHERE role_id = 1;
559
560-- supervised_by updates
561UPDATE employee
562SET supervised_by = 1
563WHERE role_id = 2; -- for doctors: manager
564
565UPDATE employee
566SET supervised_by = (floor(random() * 9) + 2)::int
567WHERE role_id = 3; -- for vet assistants: random doctor (ids 2-10)
568
569UPDATE employee
570SET supervised_by = 1
571WHERE role_id = 4; -- for receptionists: manager
572
573-- ============================================================
574-- owner
575-- 400 owners
576-- ============================================================
577
578WITH male_names_sample AS (SELECT name
579 FROM temp_male_names
580 ORDER BY random()
581 LIMIT 2000),
582 female_names_sample AS (SELECT name
583 FROM temp_female_names
584 ORDER BY random()
585 LIMIT 2000),
586 male_pool AS (SELECT fn.name AS first_name,
587 ln.surname AS last_name,
588 'M' AS gender
589 FROM male_names_sample fn
590 CROSS JOIN temp_surnames ln
591 ORDER BY random()),
592 female_pool AS (SELECT fn.name AS first_name,
593 ln.surname AS last_name,
594 'F' AS gender
595 FROM female_names_sample fn
596 CROSS JOIN temp_surnames ln
597 ORDER BY random()),
598 all_names AS (SELECT first_name, last_name, gender
599 FROM male_pool
600 UNION ALL
601 SELECT first_name, last_name, gender
602 FROM female_pool),
603 numbered AS (SELECT first_name,
604 last_name,
605 gender,
606 row_number() OVER (ORDER BY random()) AS rn
607 FROM all_names),
608 shuffled_addresses AS (SELECT address || ', ' || city || ', ' || state || ' ' || zip AS full_address,
609 row_number() OVER (ORDER BY random()) AS rn
610 FROM temp_addresses),
611 shuffled_phones AS (SELECT ((row_number() OVER (ORDER BY random()) % 9) + 1)::text AS d1,
612 lpad(((row_number() OVER (ORDER BY random()) * 73) % 900 + 100)::text, 3, '0') AS d2,
613 lpad(((row_number() OVER (ORDER BY random()) * 97) % 900 + 100)::text, 3, '0') AS d3,
614 row_number() OVER (ORDER BY random()) AS rn
615 FROM generate_series(1, 400))
616INSERT
617INTO owner (first_name, last_name, phone, email, address, gender)
618SELECT n.first_name,
619 n.last_name,
620 '+389 7' || p.d1 || ' ' || p.d2 || ' ' || p.d3 AS phone,
621 lower(n.first_name) || '.' || lower(n.last_name) || '@gmail.com' AS email,
622 a.full_address AS address,
623 n.gender
624FROM numbered n
625 JOIN shuffled_phones p ON p.rn = n.rn
626 JOIN shuffled_addresses a ON a.rn = ((n.rn - 1) % (SELECT count(*) FROM temp_addresses) + 1)
627ORDER BY random()
628LIMIT 400;
629
630-- contraint for email and phone on owner and employee:
631ALTER TABLE owner
632 ADD CONSTRAINT check_email
633 CHECK (email ~* '^[^@\s]+\.[^@\s]+@[^@\s]+\.[^@\s]+$'),
634 ADD CONSTRAINT check_phone
635 CHECK (phone ~ '^\+389 7[0-9] [0-9]{3} [0-9]{3}$');
636
637ALTER TABLE employee
638 ADD CONSTRAINT check_email
639 CHECK (email ~* '^[^@\s]+\.[^@\s]+@[^@\s]+\.[^@\s]+$'),
640 ADD CONSTRAINT check_phone
641 CHECK (phone ~ '^\+389 7[0-9] [0-9]{3} [0-9]{3}$');
642
643
644
645
646-- -- fix for foreign key in medicine (Small flaw)
647--
648-- ALTER TABLE medicine
649-- DROP CONSTRAINT IF EXISTS medicine_shop_item_id_fkey;
650--
651-- ALTER TABLE medicine
652-- ADD CONSTRAINT medicine_shop_item_id_fkey
653-- FOREIGN KEY (shop_item_id) REFERENCES shop_item (id)
654-- ON DELETE SET NULL
655-- ON UPDATE CASCADE;
656
657
658-- fix for foreign key in invoice_item
659
660-- ALTER TABLE invoice_item
661-- DROP CONSTRAINT IF EXISTS check_type;
662--
663-- ALTER TABLE invoice_item
664-- ADD CONSTRAINT check_type
665-- CHECK (
666-- (type = 'shop_item' AND shop_item_id IS NOT NULL AND treatment_id IS NULL) OR
667-- (type = 'treatment' AND treatment_id IS NOT NULL AND shop_item_id IS NULL)
668-- );
669
670-- unique coupon code
671
672-- ALTER TABLE coupon
673-- ADD CONSTRAINT unique_coupon_code UNIQUE (code);
674
675-- fix for usage_limit in coupon
676
677-- ALTER TABLE coupon
678-- DROP CONSTRAINT IF EXISTS check_usage;
679--
680-- ALTER TABLE coupon
681-- ADD CONSTRAINT check_usage
682-- CHECK (
683-- usage_limit > 0 AND
684-- usage_count >= 0 AND
685-- usage_count <= usage_limit
686-- );
687
688
689-- small fixes in owner
690
691ALTER TABLE owner
692 ADD CONSTRAINT unique_owner_email UNIQUE (email);
693
694ALTER TABLE employee
695 ADD CONSTRAINT unique_employee_email UNIQUE (email);
696
697ALTER TABLE owner
698 ALTER COLUMN email SET NOT NULL;
699
700ALTER TABLE employee
701 ALTER COLUMN email SET NOT NULL;
702
703
704-- ============================================================
705-- pet
706-- ============================================================
707
708
709CREATE TABLE temp_breeds_mammal (
710 id bigserial primary key,
711 breed_name text
712);
713
714CREATE TABLE temp_breeds_bird (
715 id bigserial primary key,
716 breed_name text
717);
718
719CREATE TABLE temp_breeds_fish (
720 id bigserial primary key,
721 breed_name text
722);
723
724CREATE TABLE temp_breeds_amphibian (
725 id bigserial primary key,
726 breed_name text
727);
728
729CREATE TABLE temp_breeds_reptile (
730 id bigserial primary key,
731 breed_name text
732);
733
734CREATE TABLE temp_pet_names (
735 id bigserial primary key,
736 name text
737);
738
739CREATE TABLE temp_medical_history (
740 id bigserial primary key,
741 history text
742);
743
744COPY temp_breeds_mammal (breed_name)
745 FROM 'C:/Program Files/PostgreSQL/18/data/temp_files/mammal_pet_breeds.csv'
746 DELIMITER ',' CSV HEADER;
747
748COPY temp_breeds_bird (breed_name)
749 FROM 'C:/Program Files/PostgreSQL/18/data/temp_files/pet_birds.csv'
750 DELIMITER ',' CSV HEADER;
751
752COPY temp_breeds_fish (breed_name)
753 FROM 'C:/Program Files/PostgreSQL/18/data/temp_files/pet_fish_breeds.csv'
754 DELIMITER ',' CSV HEADER;
755
756COPY temp_breeds_amphibian (breed_name)
757 FROM 'C:/Program Files/PostgreSQL/18/data/temp_files/pet_amphibians.csv'
758 DELIMITER ',' CSV HEADER;
759
760COPY temp_breeds_reptile (breed_name)
761 FROM 'C:/Program Files/PostgreSQL/18/data/temp_files/pet_reptiles.csv'
762 DELIMITER ',' CSV HEADER;
763
764COPY temp_pet_names (name)
765 FROM 'C:/Program Files/PostgreSQL/18/data/temp_files/pet_names.csv'
766 DELIMITER ',' CSV HEADER;
767
768COPY temp_medical_history (history)
769 FROM 'C:/Program Files/PostgreSQL/18/data/temp_files/pet_medical_history.csv'
770 DELIMITER ',' CSV HEADER;
771
772
773--altering column age for pet (so it works for both months and years)
774
775-- ALTER TABLE pet
776-- ALTER COLUMN age TYPE int4;
777
778ALTER TABLE pet
779 ADD COLUMN age_display text GENERATED ALWAYS AS (
780 CASE
781 WHEN age IS NULL THEN 'Unknown'
782 WHEN age < 12 THEN age || ' months'
783 WHEN age < 24 THEN '1 year'
784 ELSE (age / 12) || ' years'
785 END
786 ) STORED;
787
788
789WITH base AS (
790 SELECT generate_series(1, 700) AS rn
791),
792
793 typed AS (
794 SELECT
795 rn,
796 CASE
797 WHEN r < 0.60 THEN 'mammal'
798 WHEN r < 0.70 THEN 'bird'
799 WHEN r < 0.80 THEN 'reptile'
800 WHEN r < 0.90 THEN 'fish'
801 ELSE 'amphibian'
802 END AS type
803 FROM (
804 SELECT rn, random() AS r
805 FROM base
806 ) x
807 ),
808
809 breed_counts AS (
810 SELECT
811 (SELECT count(*) FROM temp_breeds_mammal) AS mammal_cnt,
812 (SELECT count(*) FROM temp_breeds_bird) AS bird_cnt,
813 (SELECT count(*) FROM temp_breeds_fish) AS fish_cnt,
814 (SELECT count(*) FROM temp_breeds_reptile) AS reptile_cnt,
815 (SELECT count(*) FROM temp_breeds_amphibian) AS amphibian_cnt,
816 (SELECT count(*) FROM temp_pet_names) AS names_cnt,
817 (SELECT count(*) FROM temp_medical_history) AS history_cnt
818 ),
819
820 owner_pool AS (
821 SELECT
822 id,
823 row_number() OVER (ORDER BY random()) AS rn
824 FROM owner
825 ),
826
827 owner_counts AS (
828 SELECT count(*) AS cnt FROM owner
829 ),
830
831 names AS (
832 SELECT name, row_number() OVER () AS rn
833 FROM (SELECT name FROM temp_pet_names ORDER BY random()) t
834 ),
835
836 history AS (
837 SELECT history, row_number() OVER () AS rn
838 FROM (SELECT history FROM temp_medical_history ORDER BY random()) t
839 ),
840
841 mammal_breeds AS (
842 SELECT breed_name, row_number() OVER () AS rn
843 FROM (SELECT breed_name FROM temp_breeds_mammal ORDER BY random()) t
844 ),
845
846 bird_breeds AS (
847 SELECT breed_name, row_number() OVER () AS rn
848 FROM (SELECT breed_name FROM temp_breeds_bird ORDER BY random()) t
849 ),
850
851 fish_breeds AS (
852 SELECT breed_name, row_number() OVER () AS rn
853 FROM (SELECT breed_name FROM temp_breeds_fish ORDER BY random()) t
854 ),
855
856 reptile_breeds AS (
857 SELECT breed_name, row_number() OVER () AS rn
858 FROM (SELECT breed_name FROM temp_breeds_reptile ORDER BY random()) t
859 ),
860
861 amphibian_breeds AS (
862 SELECT breed_name, row_number() OVER () AS rn
863 FROM (SELECT breed_name FROM temp_breeds_amphibian ORDER BY random()) t
864 ),
865
866 owner_assignment AS (
867 SELECT
868 t.rn AS pet_rn,
869 CASE
870 WHEN t.rn <= 400
871 THEN (SELECT id FROM owner_pool op WHERE op.rn = t.rn)
872 ELSE
873 (SELECT id FROM owner_pool op
874 WHERE op.rn = (abs(hashtext('owner_' || t.rn)) % (SELECT cnt FROM owner_counts)) + 1)
875 END AS owner_id
876 FROM typed t
877 )
878
879INSERT INTO pet (
880 name,
881 is_active,
882 type,
883 breed,
884 age,
885 medical_history,
886 owner_id
887)
888
889SELECT
890 n.name,
891 (random() < 0.85) AS is_active,
892 t.type,
893
894 CASE t.type
895 WHEN 'mammal' THEN mb.breed_name
896 WHEN 'bird' THEN bb.breed_name
897 WHEN 'fish' THEN fb.breed_name
898 WHEN 'reptile' THEN rb.breed_name
899 WHEN 'amphibian' THEN ab.breed_name
900 END AS breed,
901
902 CASE t.type
903 WHEN 'mammal' THEN floor(random() * 240)::int
904 WHEN 'bird' THEN floor(random() * 720)::int
905 WHEN 'reptile' THEN floor(random() * 600)::int
906 WHEN 'fish' THEN floor(random() * 240)::int
907 WHEN 'amphibian' THEN floor(random() * 360)::int
908 END AS age,
909
910 h.history,
911 oa.owner_id
912
913FROM typed t
914 CROSS JOIN breed_counts bc
915 JOIN owner_assignment oa ON oa.pet_rn = t.rn
916 JOIN owner_counts oc ON true
917
918 JOIN names n
919 ON n.rn = ((abs(hashtext(t.rn::text || 'pet_name_' || t.rn)) % bc.names_cnt) + 1)
920
921 JOIN history h
922 ON h.rn = ((abs(hashtext(t.rn::text || 'pet_history_' || t.rn)) % bc.history_cnt) + 1)
923
924 LEFT JOIN mammal_breeds mb
925 ON t.type = 'mammal'
926 AND mb.rn = ((abs(hashtext(t.rn::text || 'mammal_' || t.rn)) % bc.mammal_cnt) + 1)
927
928 LEFT JOIN bird_breeds bb
929 ON t.type = 'bird'
930 AND bb.rn = ((abs(hashtext(t.rn::text || 'bird_' || t.rn)) % bc.bird_cnt) + 1)
931
932 LEFT JOIN fish_breeds fb
933 ON t.type = 'fish'
934 AND fb.rn = ((abs(hashtext(t.rn::text || 'fish_' || t.rn)) % bc.fish_cnt) + 1)
935
936 LEFT JOIN reptile_breeds rb
937 ON t.type = 'reptile'
938 AND rb.rn = ((abs(hashtext(t.rn::text || 'reptile_' || t.rn)) % bc.reptile_cnt) + 1)
939
940 LEFT JOIN amphibian_breeds ab
941 ON t.type = 'amphibian'
942 AND ab.rn = ((abs(hashtext(t.rn::text || 'amphibian_' || t.rn)) % bc.amphibian_cnt) + 1);
943
944-- fix for small mistakes with csv
945
946UPDATE pet
947SET medical_history = 'Dental exam completed'
948WHERE id = 12;
949
950UPDATE pet
951SET medical_history = 'Eye exam completed'
952WHERE id = 57;
953
954
955-- ============================================================
956-- certificate
957-- ============================================================
958
959ALTER TABLE certificate
960ADD column category VARCHAR(50);
961
962
963CREATE TABLE temp_certificate_names (
964 id bigserial primary key,
965 name text,
966 category text
967
968);
969
970COPY temp_certificate_names (name,category)
971 FROM 'C:/Program Files/PostgreSQL/18/data/temp_files/vet_assistance_certificates.csv'
972 DELIMITER ',' CSV HEADER;
973
974INSERT INTO certificate (certificate_name, category, employee_id)
975SELECT
976 t.name,
977 t.category,
978 e.id
979FROM
980 (SELECT name, category, row_number() OVER (ORDER BY random()) AS rn
981 FROM temp_certificate_names) t,
982 (SELECT id, row_number() OVER (ORDER BY random()) AS rn,
983 count(*) OVER () AS total
984 FROM employee
985 WHERE role_id = 3) e
986WHERE e.rn = (t.rn % (SELECT count(*) FROM employee WHERE role_id = 3)) + 1;
987
988-- ============================================================
989-- specialization
990-- ============================================================
991
992CREATE TABLE temp_spec_data (
993 id bigserial primary key,
994 name text,
995 number text
996
997);
998
999COPY temp_spec_data (name,number)
1000 FROM 'C:/Program Files/PostgreSQL/18/data/temp_files/vet_specializations.csv'
1001 DELIMITER ',' CSV HEADER;
1002
1003-- Step 1: Assign Diplomate specializations to all role_id=2 employees
1004INSERT INTO specialization (specialization, license_number, employee_id)
1005SELECT
1006 t.name,
1007 t.number,
1008 e.id
1009FROM
1010 (SELECT name, number, row_number() OVER (ORDER BY random()) AS rn
1011 FROM temp_spec_data
1012 WHERE name ILIKE '%diplomate%') t,
1013 (SELECT id, row_number() OVER (ORDER BY random()) AS rn
1014 FROM employee
1015 WHERE role_id = 2) e
1016WHERE e.rn = (t.rn % (SELECT count(*) FROM employee WHERE role_id = 2)) + 1;
1017
1018
1019INSERT INTO specialization (specialization, license_number, employee_id)
1020SELECT
1021 t.name,
1022 t.number,
1023 e.id
1024FROM
1025 (SELECT name, number, row_number() OVER (ORDER BY random()) AS rn
1026 FROM temp_spec_data
1027 WHERE name ILIKE '%master%') t,
1028 (SELECT id, row_number() OVER (ORDER BY random()) AS rn
1029 FROM employee
1030 WHERE role_id = 2
1031 AND id IN (SELECT employee_id FROM specialization WHERE specialization ILIKE '%diplomate%')
1032 LIMIT (SELECT count(*)/2 FROM employee WHERE role_id = 2)
1033 ) e
1034WHERE e.rn = (t.rn % (SELECT GREATEST(count(*)/2, 1) FROM employee WHERE role_id = 2)) + 1;
1035
1036
1037-- ============================================================
1038-- coupon
1039-- ============================================================
1040
1041INSERT INTO coupon (
1042 code,
1043 type,
1044 value,
1045 valid_from,
1046 valid_to,
1047 usage_limit,
1048 usage_count,
1049 is_active,
1050 min_total
1051)
1052SELECT
1053 chr(65 + (gs % 26)) ||
1054 chr(65 + ((gs / 26) % 26)) ||
1055 chr(65 + ((gs / 676) % 26)) ||
1056 '-' || lpad((gs % 1000)::text, 3, '0') AS code,
1057
1058 CASE
1059 WHEN random() < 0.5 THEN 'fixed'
1060 ELSE 'percentage'
1061 END AS type,
1062
1063 CASE
1064 WHEN random() < 0.5
1065 THEN round((random() * 50 + 5)::numeric, 2)
1066 ELSE round((random() * 80 + 1)::numeric, 2)
1067 END AS value,
1068
1069 CURRENT_DATE - (random() * 30)::int,
1070 CURRENT_DATE + (random() * 60)::int,
1071
1072 u.usage_limit,
1073 (random() * u.usage_limit)::int AS usage_count,
1074
1075 (random() < 0.8),
1076 round((random() * 200)::numeric, 2)
1077
1078FROM generate_series(1, 1000) gs
1079 CROSS JOIN LATERAL (
1080 SELECT (random() * 90 + 10)::int AS usage_limit
1081 ) u;
1082
1083
1084-- ============================================================
1085-- prescription
1086-- ============================================================
1087
1088CREATE TABLE temp_prescription_advice(
1089 id bigserial primary key,
1090 advice text
1091);
1092
1093COPY temp_prescription_advice (advice)
1094 FROM 'C:/Program Files/PostgreSQL/18/data/temp_files/pet_prescriptions_advice.csv'
1095 DELIMITER ',' CSV HEADER;
1096
1097
1098-- ============================================================
1099-- examination_room
1100-- ============================================================
1101
1102INSERT INTO examination_room (room_number, type, capacity, status)
1103SELECT
1104 gs::text AS room_number,
1105 CASE
1106 WHEN r < 0.55 THEN 'examination'
1107 WHEN r < 0.75 THEN 'surgery'
1108 WHEN r < 0.90 THEN 'patient ward'
1109 WHEN r < 0.95 THEN 'radiology'
1110 ELSE 'laboratory'
1111 END AS type,
1112
1113 CASE
1114 WHEN r < 0.55 THEN 1
1115 WHEN r < 0.75 THEN 1
1116 WHEN r < 0.90 THEN 10 + (random() * 10)::int
1117 WHEN r < 0.95 THEN 2 + (random() * 3)::int
1118 ELSE 2 + (random() * 4)::int
1119 END AS capacity,
1120
1121 'available' AS status
1122FROM (
1123 SELECT gs, random() AS r
1124 FROM generate_series(1, 15) gs
1125 ) x;
1126
1127
1128-- ============================================================
1129-- medicine & shop_item
1130-- ============================================================
1131-- 1. shop_item_category → add 'Medicine' category
1132-- 2. shop_item → ~60 medicines available in the shop
1133-- 3. medicine → ~120 total: ~60 linked to shop, ~60 prescription-only
1134-- 4. prescription → one per qualifying examination
1135-- 5. prescription_medicine → M:N join with dosage/num_days
1136-- ============================================================
1137
1138-- ============================================================
1139-- shop_item_category
1140-- ============================================================
1141
1142INSERT INTO shop_item_category (name, parent_id)
1143VALUES ('Pet Supplies', NULL)
1144ON CONFLICT DO NOTHING;
1145
1146INSERT INTO shop_item_category (name, parent_id)
1147SELECT 'Medicine', id
1148FROM shop_item_category
1149WHERE name = 'Pet Supplies'
1150ON CONFLICT DO NOTHING;
1151
1152
1153-- ============================================================
1154-- shop_item - medicines sold in shop (~60 items)
1155-- shop_item_category_id pointing to 'Medicine' category
1156-- ============================================================
1157
1158INSERT INTO shop_item (name, price, stock, shop_item_category_id)
1159SELECT
1160 med_name,
1161 round((3.50 + random() * 96.50)::numeric, 2) AS price,
1162 (floor(random() * 150) + 5)::int AS stock,
1163 (SELECT id FROM shop_item_category WHERE name = 'Medicine') AS shop_item_category_id
1164FROM (VALUES
1165 ('Amoxicillin 250mg Tablets'),
1166 ('Amoxicillin 500mg Capsules'),
1167 ('Metronidazole 200mg Tablets'),
1168 ('Metronidazole 400mg Tablets'),
1169 ('Doxycycline 100mg Capsules'),
1170 ('Enrofloxacin 50mg Tablets'),
1171 ('Enrofloxacin 150mg Tablets'),
1172 ('Trimethoprim-Sulfa 480mg Tablets'),
1173 ('Cephalexin 250mg Capsules'),
1174 ('Cephalexin 500mg Capsules'),
1175 ('Prednisolone 5mg Tablets'),
1176 ('Prednisolone 20mg Tablets'),
1177 ('Dexamethasone 0.5mg Tablets'),
1178 ('Methylprednisolone 4mg Tablets'),
1179 ('Hydrocortisone 10mg Tablets'),
1180 ('Furosemide 40mg Tablets'),
1181 ('Spironolactone 25mg Tablets'),
1182 ('Enalapril 5mg Tablets'),
1183 ('Atenolol 25mg Tablets'),
1184 ('Digoxin 0.125mg Tablets'),
1185 ('Carprofen 50mg Chewable Tablets'),
1186 ('Meloxicam 1mg Tablets'),
1187 ('Meloxicam Oral Suspension 1.5mg/ml'),
1188 ('Tramadol 50mg Tablets'),
1189 ('Gabapentin 100mg Capsules'),
1190 ('Gabapentin 300mg Capsules'),
1191 ('Phenobarbital 30mg Tablets'),
1192 ('Potassium Bromide 325mg Capsules'),
1193 ('Levetiracetam 250mg Tablets'),
1194 ('Omeprazole 20mg Capsules'),
1195 ('Famotidine 20mg Tablets'),
1196 ('Metoclopramide 10mg Tablets'),
1197 ('Ondansetron 4mg Tablets'),
1198 ('Maropitant 16mg Tablets'),
1199 ('Sucralfate 1g Tablets'),
1200 ('Lactulose Oral Solution 667mg/ml'),
1201 ('Loperamide 2mg Capsules'),
1202 ('Tylosin 250mg Powder'),
1203 ('Clindamycin 75mg Capsules'),
1204 ('Clindamycin 150mg Capsules'),
1205 ('Ketoconazole 200mg Tablets'),
1206 ('Fluconazole 50mg Capsules'),
1207 ('Itraconazole 100mg Capsules'),
1208 ('Fenbendazole 150mg Granules'),
1209 ('Pyrantel Pamoate Oral Suspension'),
1210 ('Ivermectin 1% Oral Solution'),
1211 ('Milbemycin Oxime 2.3mg Tablets'),
1212 ('Praziquantel 50mg Tablets'),
1213 ('Doxycycline Hyclate 100mg Tablets'),
1214 ('Chloramphenicol 250mg Capsules'),
1215 ('Cyclosporine 25mg Capsules'),
1216 ('Cyclosporine 100mg Capsules'),
1217 ('Apoquel 3.6mg Tablets'),
1218 ('Apoquel 16mg Tablets'),
1219 ('Hydroxyzine 25mg Tablets'),
1220 ('Diphenhydramine 25mg Capsules'),
1221 ('Vitamin B12 Injection 1000mcg/ml'),
1222 ('Iron Dextran 100mg/ml Injection'),
1223 ('Calcium Gluconate 10% Injection'),
1224 ('Saline 0.9% Flush Solution 10ml')
1225) AS t(med_name);
1226
1227
1228-- ============================================================
1229-- medicine table
1230-- ~60 medicines that exist in the shop (linked via shop_item_id fk)
1231-- ~60 medicines that are prescription-only (shop_item_id IS NULL)
1232-- ============================================================
1233
1234-- a. medicines that are in the shop - match by name to shop_item
1235INSERT INTO medicine (name, manufacturer, description, shop_item_id)
1236SELECT
1237 si.name,
1238 mfr,
1239 descr,
1240 si.id AS shop_item_id
1241FROM shop_item si
1242JOIN shop_item_category sic ON sic.id = si.shop_item_category_id
1243JOIN (VALUES
1244 ('Amoxicillin 250mg Tablets', 'Zoetis Inc.', 'Broad-spectrum penicillin antibiotic for bacterial infections'),
1245 ('Amoxicillin 500mg Capsules', 'Zoetis Inc.', 'Higher-dose penicillin for severe or systemic bacterial infections'),
1246 ('Metronidazole 200mg Tablets', 'Norbrook Laboratories', 'Antibiotic and antiprotozoal for GI and anaerobic infections'),
1247 ('Metronidazole 400mg Tablets', 'Norbrook Laboratories', 'Higher-dose metronidazole for systemic anaerobic infections'),
1248 ('Doxycycline 100mg Capsules', 'Boehringer Ingelheim', 'Tetracycline antibiotic effective against intracellular pathogens'),
1249 ('Enrofloxacin 50mg Tablets', 'Bayer Animal Health', 'Fluoroquinolone antibiotic for urinary and soft tissue infections'),
1250 ('Enrofloxacin 150mg Tablets', 'Bayer Animal Health', 'Higher-dose fluoroquinolone for large breeds or severe infections'),
1251 ('Trimethoprim-Sulfa 480mg Tablets', 'Vetoquinol', 'Combination sulfonamide for respiratory and urinary tract infections'),
1252 ('Cephalexin 250mg Capsules', 'Dechra Veterinary', 'First-generation cephalosporin for skin and soft tissue infections'),
1253 ('Cephalexin 500mg Capsules', 'Dechra Veterinary', 'Higher-dose cephalosporin for pyoderma and wound infections'),
1254 ('Prednisolone 5mg Tablets', 'Virbac Animal Health', 'Corticosteroid for inflammatory and autoimmune conditions'),
1255 ('Prednisolone 20mg Tablets', 'Virbac Animal Health', 'Higher-dose corticosteroid for severe allergic or inflammatory disease'),
1256 ('Dexamethasone 0.5mg Tablets', 'Elanco Animal Health', 'Potent corticosteroid for acute inflammatory reactions'),
1257 ('Methylprednisolone 4mg Tablets', 'Pfizer Animal Health', 'Intermediate corticosteroid for chronic inflammatory conditions'),
1258 ('Hydrocortisone 10mg Tablets', 'Norbrook Laboratories', 'Mild corticosteroid for adrenal insufficiency and mild inflammation'),
1259 ('Furosemide 40mg Tablets', 'Boehringer Ingelheim', 'Loop diuretic for congestive heart failure and oedema'),
1260 ('Spironolactone 25mg Tablets', 'Dechra Veterinary', 'Potassium-sparing diuretic for cardiac and hepatic disease'),
1261 ('Enalapril 5mg Tablets', 'Zoetis Inc.', 'ACE inhibitor for hypertension and congestive heart failure'),
1262 ('Atenolol 25mg Tablets', 'Elanco Animal Health', 'Beta-blocker for hypertrophic cardiomyopathy and arrhythmias'),
1263 ('Digoxin 0.125mg Tablets', 'Pfizer Animal Health', 'Cardiac glycoside for atrial fibrillation and heart failure'),
1264 ('Carprofen 50mg Chewable Tablets', 'Zoetis Inc.', 'NSAID for pain and inflammation in musculoskeletal disease'),
1265 ('Meloxicam 1mg Tablets', 'Boehringer Ingelheim', 'NSAID for osteoarthritis pain and post-operative analgesia'),
1266 ('Meloxicam Oral Suspension 1.5mg/ml','Boehringer Ingelheim', 'Liquid NSAID formulation for cats and small dogs'),
1267 ('Tramadol 50mg Tablets', 'Norbrook Laboratories', 'Opioid analgesic for moderate to severe pain management'),
1268 ('Gabapentin 100mg Capsules', 'Dechra Veterinary', 'Anticonvulsant and analgesic for neuropathic pain'),
1269 ('Gabapentin 300mg Capsules', 'Dechra Veterinary', 'Higher-dose gabapentin for chronic pain or seizure management'),
1270 ('Phenobarbital 30mg Tablets', 'Virbac Animal Health', 'Barbiturate anticonvulsant for idiopathic epilepsy'),
1271 ('Potassium Bromide 325mg Capsules', 'Vetoquinol', 'Adjunctive anticonvulsant for refractory epilepsy'),
1272 ('Levetiracetam 250mg Tablets', 'Bayer Animal Health', 'Novel anticonvulsant with favourable safety profile'),
1273 ('Omeprazole 20mg Capsules', 'Elanco Animal Health', 'Proton pump inhibitor for gastric ulcer and acid reflux'),
1274 ('Famotidine 20mg Tablets', 'Pfizer Animal Health', 'H2 blocker for gastric hyperacidity and stress ulceration'),
1275 ('Metoclopramide 10mg Tablets', 'Zoetis Inc.', 'Prokinetic antiemetic for gastric motility disorders'),
1276 ('Ondansetron 4mg Tablets', 'Norbrook Laboratories', 'Serotonin antagonist antiemetic for chemotherapy-induced nausea'),
1277 ('Maropitant 16mg Tablets', 'Zoetis Inc.', 'NK1 receptor antagonist antiemetic for motion sickness and vomiting'),
1278 ('Sucralfate 1g Tablets', 'Dechra Veterinary', 'Mucosal protectant for gastric and duodenal ulcers'),
1279 ('Lactulose Oral Solution 667mg/ml', 'Virbac Animal Health', 'Osmotic laxative for hepatic encephalopathy and constipation'),
1280 ('Loperamide 2mg Capsules', 'Elanco Animal Health', 'Opioid receptor agonist for acute non-specific diarrhoea'),
1281 ('Tylosin 250mg Powder', 'Elanco Animal Health', 'Macrolide antibiotic for chronic enteropathy and diarrhoea'),
1282 ('Clindamycin 75mg Capsules', 'Zoetis Inc.', 'Lincosamide antibiotic for anaerobic and dental infections'),
1283 ('Clindamycin 150mg Capsules', 'Zoetis Inc.', 'Higher-dose clindamycin for deep tissue and bone infections'),
1284 ('Ketoconazole 200mg Tablets', 'Dechra Veterinary', 'Azole antifungal for dermatophytosis and systemic mycoses'),
1285 ('Fluconazole 50mg Capsules', 'Pfizer Animal Health', 'Triazole antifungal for Candida and cryptococcal infections'),
1286 ('Itraconazole 100mg Capsules', 'Boehringer Ingelheim', 'Broad-spectrum antifungal for Aspergillus and dermatophytes'),
1287 ('Fenbendazole 150mg Granules', 'Intervet-Schering Plough', 'Benzimidazole anthelmintic for roundworms, hookworms and Giardia'),
1288 ('Pyrantel Pamoate Oral Suspension', 'Elanco Animal Health', 'Anthelmintic for roundworm and hookworm infections'),
1289 ('Ivermectin 1% Oral Solution', 'Merial', 'Macrocyclic lactone for mites, lice and internal parasites'),
1290 ('Milbemycin Oxime 2.3mg Tablets', 'Novartis Animal Health', 'Heartworm prevention and intestinal parasite control'),
1291 ('Praziquantel 50mg Tablets', 'Bayer Animal Health', 'Cestocidal agent for tapeworm infections'),
1292 ('Doxycycline Hyclate 100mg Tablets', 'Boehringer Ingelheim', 'Hyclate salt form with improved bioavailability for systemic infections'),
1293 ('Chloramphenicol 250mg Capsules', 'Vetoquinol', 'Broad-spectrum antibiotic reserved for resistant infections'),
1294 ('Cyclosporine 25mg Capsules', 'Elanco Animal Health', 'Immunosuppressant for immune-mediated skin and eye disease'),
1295 ('Cyclosporine 100mg Capsules', 'Elanco Animal Health', 'Higher-dose cyclosporine for large breed immune-mediated disease'),
1296 ('Apoquel 3.6mg Tablets', 'Zoetis Inc.', 'JAK inhibitor for pruritus and allergic dermatitis in dogs'),
1297 ('Apoquel 16mg Tablets', 'Zoetis Inc.', 'Higher-dose Apoquel for larger dogs with atopic dermatitis'),
1298 ('Hydroxyzine 25mg Tablets', 'Dechra Veterinary', 'Antihistamine for pruritic skin disease and anxiety'),
1299 ('Diphenhydramine 25mg Capsules', 'Norbrook Laboratories', 'First-generation antihistamine for allergic reactions'),
1300 ('Vitamin B12 Injection 1000mcg/ml', 'Vetoquinol', 'Cyanocobalamin supplement for malabsorption and neuropathy'),
1301 ('Iron Dextran 100mg/ml Injection', 'Virbac Animal Health', 'Parenteral iron supplement for iron-deficiency anaemia in neonates'),
1302 ('Calcium Gluconate 10% Injection', 'Pfizer Animal Health', 'IV calcium supplementation for hypocalcaemia and eclampsia'),
1303 ('Saline 0.9% Flush Solution 10ml', 'Zoetis Inc.', 'Sterile saline for catheter flushing and wound irrigation')
1304) AS m(med_name, mfr, descr) ON si.name = m.med_name
1305WHERE sic.name = 'Medicine';
1306
1307
1308-- b. prescription-only medicines (no shop_item)
1309INSERT INTO medicine (name, manufacturer, description, shop_item_id)
1310VALUES
1311 ('Amikacin 250mg/ml Injection', 'Norbrook Laboratories', 'Aminoglycoside antibiotic for gram-negative infections resistant to other antibiotics', NULL),
1312 ('Gentamicin 40mg/ml Injection', 'Dechra Veterinary', 'Aminoglycoside for serious gram-negative infections; requires renal monitoring', NULL),
1313 ('Imipenem-Cilastatin 500mg Injection', 'Pfizer Animal Health', 'Carbapenem for multidrug-resistant bacterial infections', NULL),
1314 ('Cefovecin 80mg/ml Injection', 'Zoetis Inc.', 'Long-acting cephalosporin injection; single dose covers 14 days', NULL),
1315 ('Marbofloxacin 50mg Tablets', 'Vetoquinol', 'Third-generation fluoroquinolone for skin and urinary infections', NULL),
1316 ('Pradofloxacin 15mg Tablets', 'Bayer Animal Health', 'Broad-spectrum fluoroquinolone including anaerobes; cats only', NULL),
1317 ('Azithromycin 250mg Capsules', 'Boehringer Ingelheim', 'Macrolide antibiotic for respiratory and intracellular infections', NULL),
1318 ('Rifampicin 150mg Capsules', 'Virbac Animal Health', 'Reserved for Rhodococcus equi and methicillin-resistant staphylococci', NULL),
1319 ('Linezolid 600mg Tablets', 'Pfizer Animal Health', 'Oxazolidinone for vancomycin-resistant enterococci', NULL),
1320 ('Vancomycin 500mg Injection', 'Elanco Animal Health', 'Glycopeptide antibiotic; last-resort therapy for MRSA', NULL),
1321 ('Hydrocortisone Sodium Succinate Inj.', 'Zoetis Inc.', 'IV corticosteroid for anaphylaxis and Addisonian crisis', NULL),
1322 ('Betamethasone 4mg/ml Injection', 'Norbrook Laboratories', 'Potent long-acting corticosteroid for inflammatory conditions', NULL),
1323 ('Triamcinolone 10mg/ml Injection', 'Dechra Veterinary', 'Intermediate-acting corticosteroid for intra-articular use', NULL),
1324 ('Terbinafine 250mg Tablets', 'Elanco Animal Health', 'Allylamine antifungal for dermatophyte infections', NULL),
1325 ('Voriconazole 200mg Tablets', 'Pfizer Animal Health', 'Extended-spectrum triazole for Aspergillus and resistant Candida', NULL),
1326 ('Amphotericin B 50mg Injection', 'Boehringer Ingelheim', 'Polyene antifungal for systemic mycoses; nephrotoxic', NULL),
1327 ('Miltefosine 20mg Capsules', 'Virbac Animal Health', 'Antiprotozoal for feline leishmaniosis', NULL),
1328 ('Ronidazole 100mg Tablets', 'Dechra Veterinary', 'Nitroimidazole for feline tritrichomoniasis', NULL),
1329 ('Atovaquone 150mg Suspension', 'Norbrook Laboratories', 'Antiprotozoal for Babesia and Cytauxzoon infections', NULL),
1330 ('Allopurinol 100mg Tablets', 'Elanco Animal Health', 'Xanthine oxidase inhibitor for urate urolithiasis in Dalmatians', NULL),
1331 ('Pimobendan 1.25mg Tablets', 'Boehringer Ingelheim', 'Phosphodiesterase inhibitor and Ca-sensitiser for DCM and MVD', NULL),
1332 ('Diltiazem 30mg Tablets', 'Dechra Veterinary', 'Calcium channel blocker for feline hypertrophic cardiomyopathy', NULL),
1333 ('Amlodipine 1.25mg Tablets', 'Pfizer Animal Health', 'Calcium channel blocker for systemic hypertension in cats', NULL),
1334 ('Benazepril 5mg Tablets', 'Vetoquinol', 'ACE inhibitor for chronic kidney disease and hypertension', NULL),
1335 ('Telmisartan 4mg/ml Oral Solution', 'Boehringer Ingelheim', 'Angiotensin II receptor blocker for feline CKD proteinuria', NULL),
1336 ('Sildenafil 25mg Tablets', 'Zoetis Inc.', 'PDE-5 inhibitor for pulmonary arterial hypertension', NULL),
1337 ('Heparin 5000 IU/ml Injection', 'Virbac Animal Health', 'Anticoagulant for thromboembolism and DIC management', NULL),
1338 ('Clopidogrel 75mg Tablets', 'Norbrook Laboratories', 'Antiplatelet for feline arterial thromboembolism prevention', NULL),
1339 ('Pentoxifylline 400mg Tablets', 'Elanco Animal Health', 'Haemorheological agent for vasculitis and ischaemic disease', NULL),
1340 ('Levothyroxine 0.1mg Tablets', 'Dechra Veterinary', 'Thyroid hormone replacement for canine hypothyroidism', NULL),
1341 ('Methimazole 5mg Tablets', 'Virbac Animal Health', 'Thioamide for feline hyperthyroidism; inhibits thyroid synthesis', NULL),
1342 ('Trilostane 30mg Capsules', 'Dechra Veterinary', '3beta-HSD inhibitor for hyperadrenocorticism (Cushing disease)', NULL),
1343 ('Mitotane 500mg Tablets', 'Pfizer Animal Health', 'Adrenocorticolytic for pituitary-dependent hyperadrenocorticism', NULL),
1344 ('Desoxycorticosterone 25mg/ml Inj.', 'Elanco Animal Health', 'Mineralocorticoid for canine hypoadrenocorticism (Addison disease)', NULL),
1345 ('Cabergoline 0.05mg Tablets', 'Norbrook Laboratories', 'Dopamine agonist for false pregnancy and hyperprolactinaemia', NULL),
1346 ('Misoprostol 200mcg Tablets', 'Boehringer Ingelheim', 'Prostaglandin E1 analogue for GI mucosal protection with NSAIDs', NULL),
1347 ('Cisapride 5mg Tablets', 'Virbac Animal Health', 'Prokinetic for feline megacolon and gastric motility disorders', NULL),
1348 ('Ursodiol 50mg Capsules', 'Dechra Veterinary', 'Bile acid for cholelithiasis and chronic hepatitis', NULL),
1349 ('S-Adenosylmethionine 200mg Tablets', 'Zoetis Inc.', 'Hepatoprotectant for liver disease and oxidative stress', NULL),
1350 ('Silymarin 35mg Capsules', 'Vetoquinol', 'Milk thistle extract hepatoprotectant for chronic hepatopathy', NULL),
1351 ('Acetylcysteine 20% Solution', 'Elanco Animal Health', 'Mucolytic and antidote for paracetamol toxicosis in cats', NULL),
1352 ('Atropine 0.6mg/ml Injection', 'Zoetis Inc.', 'Anticholinergic for bradycardia, organophosphate toxicosis, pre-anaesthesia', NULL),
1353 ('Dopamine 40mg/ml Injection', 'Pfizer Animal Health', 'Catecholamine for cardiogenic shock and acute hypotension', NULL),
1354 ('Dobutamine 12.5mg/ml Injection', 'Boehringer Ingelheim', 'Inotrope for decompensated heart failure and cardiogenic shock', NULL),
1355 ('Norepinephrine 1mg/ml Injection', 'Norbrook Laboratories', 'Vasopressor for distributive shock unresponsive to fluids', NULL),
1356 ('Mannitol 20% Infusion', 'Dechra Veterinary', 'Osmotic diuretic for cerebral oedema and acute glaucoma', NULL),
1357 ('Hypertonic Saline 7.2% Infusion', 'Virbac Animal Health', 'Resuscitation fluid for haemorrhagic shock and head trauma', NULL),
1358 ('Fresh Frozen Plasma (canine)', 'Animal Blood Resources', 'Coagulopathy treatment; provides clotting factors and albumin', NULL),
1359 ('Hydroxyethyl Starch 6% Infusion', 'Elanco Animal Health', 'Colloid volume expander for hypoproteinaemia and shock', NULL),
1360 ('Dextrose 50% Injection', 'Zoetis Inc.', 'Concentrated glucose for hypoglycaemia; dilute before IV use', NULL),
1361 ('Potassium Chloride 15% Injection', 'Norbrook Laboratories', 'IV potassium supplementation; must be diluted; cardiac monitoring required', NULL),
1362 ('Sodium Bicarbonate 8.4% Injection', 'Pfizer Animal Health', 'Alkalinising agent for severe metabolic acidosis', NULL),
1363 ('Propofol 10mg/ml Injection', 'Zoetis Inc.', 'IV induction agent for general anaesthesia; rapid onset', NULL),
1364 ('Alfaxalone 10mg/ml Injection', 'Jurox Animal Health', 'Neurosteroid anaesthetic for induction and TIVA in cats and dogs', NULL),
1365 ('Ketamine 100mg/ml Injection', 'Dechra Veterinary', 'Dissociative anaesthetic; used in combination protocols', NULL),
1366 ('Midazolam 5mg/ml Injection', 'Virbac Animal Health', 'Benzodiazepine for sedation, co-induction and status epilepticus', NULL),
1367 ('Medetomidine 1mg/ml Injection', 'Orion Pharma', 'Alpha-2 agonist for sedation and pre-anaesthetic medication', NULL),
1368 ('Buprenorphine 0.3mg/ml Injection', 'Norbrook Laboratories', 'Partial opioid agonist for perioperative and chronic pain', NULL),
1369 ('Methadone 10mg/ml Injection', 'Dechra Veterinary', 'Full mu-opioid agonist for perioperative pain; used IV or IM', NULL),
1370 ('Morphine 10mg/ml Injection', 'Elanco Animal Health', 'Classic opioid analgesic for severe acute pain; epidural use', NULL),
1371 ('Fentanyl 0.05mg/ml Injection', 'Pfizer Animal Health', 'Short-acting opioid for intraoperative analgesia and CRI', NULL);
1372
1373
1374-- ============================================================
1375-- appointment
1376-- one appointment per owner-pet pair
1377-- phone copied from owner
1378-- ============================================================
1379
1380INSERT INTO appointment (date_appointment, reason, phone, owner_id, pet_id)
1381SELECT
1382 CURRENT_DATE - (floor(random() * 730) + 1)::int AS date_appointment,
1383 reason_list.reason,
1384 o.phone,
1385 o.id AS owner_id,
1386 p.id AS pet_id
1387FROM owner o
1388JOIN LATERAL (
1389 -- Pick one random pet belonging to this owner
1390 SELECT id
1391 FROM pet
1392 WHERE owner_id = o.id
1393 ORDER BY random()
1394 LIMIT 1
1395) p ON true
1396CROSS JOIN LATERAL (
1397 SELECT reason
1398 FROM (VALUES
1399 ('Annual wellness check'),
1400 ('Vaccination booster'),
1401 ('Limping / lameness'),
1402 ('Vomiting and lethargy'),
1403 ('Skin rash and itching'),
1404 ('Ear infection suspected'),
1405 ('Eye discharge and redness'),
1406 ('Dental check-up'),
1407 ('Weight loss and poor appetite'),
1408 ('Diarrhoea for more than 2 days'),
1409 ('Post-operative follow-up'),
1410 ('Suspected urinary tract infection'),
1411 ('Respiratory difficulty'),
1412 ('Wound assessment'),
1413 ('Parasite prevention consultation'),
1414 ('Behavioural changes'),
1415 ('Mass / lump noticed'),
1416 ('Allergic reaction'),
1417 ('Pre-surgical blood work'),
1418 ('General health concern')
1419 ) AS r(reason)
1420 WHERE o.id IS NOT NULL -- random per row
1421 ORDER BY random()
1422 LIMIT 1
1423) reason_list;
1424
1425-- ============================================================
1426-- examination
1427-- one per appointment; date >= date_appointment
1428-- employee_id: random vet (role_id = 2)
1429-- examination_room_id: random room of type 'examination'
1430-- ============================================================
1431
1432INSERT INTO examination (date_examination, status, description, appointment_id, employee_id, examination_room_id)
1433SELECT
1434 -- date is same day or up to 7 days after appointment
1435 a.date_appointment + (floor(random() * 8))::int AS date_examination,
1436
1437 -- most are completed; a few scheduled or cancelled
1438 CASE
1439 WHEN random() < 0.80 THEN 'completed'
1440 WHEN random() < 0.90 THEN 'scheduled'
1441 ELSE 'cancelled'
1442 END AS status,
1443
1444 desc_list.description,
1445 a.id AS appointment_id,
1446
1447 e.id AS employee_id,
1448 r.id AS examination_room_id
1449
1450FROM appointment a
1451CROSS JOIN LATERAL (
1452 SELECT description
1453 FROM (VALUES
1454 ('Patient presented for routine examination. Vitals within normal limits.'),
1455 ('Initial assessment completed. Further diagnostics recommended.'),
1456 ('Physical examination performed. Owner advised on treatment plan.'),
1457 ('Patient examined; mild clinical signs noted. Medication prescribed.'),
1458 ('Thorough examination carried out. No acute concerns identified.'),
1459 ('Follow-up examination. Condition improving since last visit.'),
1460 ('Examination completed. Lab samples collected for analysis.'),
1461 ('Clinical signs assessed. Dietary modification recommended.'),
1462 ('Patient stable. Monitoring plan established with owner.'),
1463 ('Examination revealed localised inflammation. Treatment initiated.')
1464 ) AS d(description)
1465 WHERE a.id IS NOT NULL
1466 ORDER BY random()
1467 LIMIT 1
1468) desc_list
1469-- random employee with role_id = 2
1470CROSS JOIN LATERAL (
1471 SELECT id
1472 FROM employee
1473 WHERE role_id = 2
1474 AND a.id IS NOT NULL
1475 ORDER BY random()
1476 LIMIT 1
1477) e
1478-- random room with type = 'examination'
1479CROSS JOIN LATERAL (
1480 SELECT id
1481 FROM examination_room
1482 WHERE type = 'examination'
1483 AND a.id IS NOT NULL
1484 ORDER BY random()
1485 LIMIT 1
1486) r;
1487
1488-- ============================================================
1489-- treatment_type
1490-- ============================================================
1491
1492INSERT INTO treatment_type (name) VALUES
1493 ('prescription'),
1494 ('vaccination'),
1495 ('consultation'),
1496 ('operation')
1497ON CONFLICT DO NOTHING;
1498
1499-- ============================================================
1500-- treatment (prescription type)
1501-- one treatment per completed examination
1502-- date_treatment >= date_examination
1503-- ============================================================
1504
1505INSERT INTO treatment (date_treatment, notes, treatment_type_id, examination_id)
1506SELECT
1507 e.date_examination + (floor(random() * 4))::int AS date_treatment,
1508
1509 notes_list.notes,
1510
1511 (SELECT id FROM treatment_type WHERE name = 'prescription') AS treatment_type_id,
1512
1513 e.id AS examination_id
1514
1515FROM examination e
1516CROSS JOIN LATERAL (
1517 SELECT notes
1518 FROM (VALUES
1519 ('Prescription issued following clinical assessment.'),
1520 ('Medication course prescribed; owner counselled on administration.'),
1521 ('Short course of antibiotics prescribed pending culture results.'),
1522 ('Anti-inflammatory therapy initiated; re-check in 10 days.'),
1523 ('Antiparasitic treatment prescribed; environmental treatment advised.'),
1524 ('Analgesic course prescribed for post-operative pain management.'),
1525 ('Antifungal therapy prescribed; reassess in 3 weeks.'),
1526 ('Prescription provided; monitor for adverse reactions.'),
1527 ('Combination therapy prescribed; owner given written instructions.'),
1528 ('Medication adjusted based on current clinical findings.')
1529 ) AS n(notes)
1530 WHERE e.id IS NOT NULL
1531 ORDER BY random()
1532 LIMIT 1
1533) notes_list
1534WHERE e.status = 'completed';
1535
1536-- ============================================================
1537-- prescription
1538-- one prescription per examination that:
1539-- - has status = 'completed'
1540-- - has a treatment of type 'prescription'
1541-- ============================================================
1542
1543INSERT INTO prescription (examination_id, date_start, date_end, description)
1544SELECT
1545 e.id AS examination_id,
1546 e.date_examination AS date_start,
1547 (e.date_examination + floor(random() * 21 + 7)::int)::date AS date_end,
1548 pa.advice AS description
1549FROM examination e
1550JOIN treatment t ON t.examination_id = e.id
1551JOIN treatment_type tt ON tt.id = t.treatment_type_id
1552 AND tt.name = 'prescription'
1553LEFT JOIN prescription p_existing
1554 ON p_existing.examination_id = e.id
1555CROSS JOIN LATERAL (
1556 SELECT advice
1557 FROM temp_prescription_advice
1558 WHERE e.id IS NOT NULL
1559 ORDER BY random()
1560 LIMIT 1
1561) pa
1562WHERE e.status = 'completed'
1563 AND p_existing.id IS NULL -- skip if already inserted
1564ON CONFLICT (examination_id) DO NOTHING;
1565
1566
1567-- ============================================================
1568-- prescription_medicine
1569-- Each prescription gets 1–4 medicines assigned.
1570-- ============================================================
1571
1572-- generate_series to produce 1–4 rows per prescription,
1573-- then deduplicate (medicine_id, prescription_id) pairs via DISTINCT ON.
1574
1575WITH prescription_slots AS (
1576 SELECT
1577 pr.id AS prescription_id,
1578 gs.n AS slot,
1579 floor(random() * 4 + 1)::int AS num_medicines -- how many this prescription actually wants
1580 FROM prescription pr
1581 CROSS JOIN generate_series(1, 4) AS gs(n)
1582),
1583
1584wanted_slots AS (
1585 SELECT prescription_id, slot
1586 FROM prescription_slots
1587 WHERE slot <= num_medicines
1588),
1589
1590-- Assign a random medicine to each slot
1591med_counts AS (
1592 SELECT count(*) AS total FROM medicine
1593),
1594
1595assigned AS (
1596 SELECT
1597 ws.prescription_id,
1598 ws.slot,
1599 m.id AS medicine_id,
1600 CASE
1601 WHEN random() < 0.6 THEN 1 -- once daily
1602 WHEN random() < 0.85 THEN 2 -- twice daily
1603 ELSE 3 -- three times daily
1604 END AS dosage,
1605 (floor(random() * 21 + 3))::int AS num_days -- 3–23 days
1606 FROM wanted_slots ws
1607 CROSS JOIN med_counts mc
1608 JOIN medicine m ON m.id = (
1609 (abs(hashtext(ws.prescription_id::text || '-' || ws.slot::text)) % mc.total) + 1
1610 )
1611),
1612
1613-- deduplicate: if the same medicine appears twice in the same prescription, keep the first slot
1614deduped AS (
1615 SELECT DISTINCT ON (prescription_id, medicine_id)
1616 prescription_id,
1617 medicine_id,
1618 dosage,
1619 num_days
1620 FROM assigned
1621 ORDER BY prescription_id, medicine_id, slot
1622)
1623
1624INSERT INTO prescription_medicine (prescription_id, medicine_id, dosage, num_days)
1625SELECT prescription_id, medicine_id, dosage, num_days
1626FROM deduped
1627ON CONFLICT (prescription_id, medicine_id) DO NOTHING;
1628
1629-- show prescriptions with their medicines
1630-- SELECT
1631-- pr.id AS prescription_id,
1632-- e.date_examination,
1633-- m.name AS medicine,
1634-- pm.dosage,
1635-- pm.num_days,
1636-- CASE WHEN m.shop_item_id IS NOT NULL THEN 'in shop' ELSE 'prescription only' END AS availability
1637-- FROM prescription pr
1638-- JOIN prescription_medicine pm ON pm.prescription_id = pr.id
1639-- JOIN medicine m ON m.id = pm.medicine_id
1640-- JOIN examination e ON e.id = pr.examination_id
1641-- ORDER BY pr.id, m.name
1642-- LIMIT 50;
1643
1644
1645
1646-- ============================================================
1647-- appointment (vaccination oriented)
1648-- ============================================================
1649
1650INSERT INTO appointment (date_appointment, reason, phone, owner_id, pet_id)
1651SELECT
1652 CURRENT_DATE - (floor(random() * 730) + 1)::int AS date_appointment,
1653 reason_list.reason,
1654 o.phone,
1655 o.id AS owner_id,
1656 p.id AS pet_id
1657FROM owner o
1658JOIN LATERAL (
1659 SELECT id
1660 FROM pet
1661 WHERE owner_id = o.id
1662 ORDER BY random()
1663 LIMIT 1
1664) p ON true
1665CROSS JOIN LATERAL (
1666 SELECT reason
1667 FROM (VALUES
1668 ('Annual vaccination'),
1669 ('Rabies vaccine booster'),
1670 ('Core vaccine schedule - puppy/kitten'),
1671 ('Bordetella vaccination'),
1672 ('Leptospirosis booster'),
1673 ('Feline herpesvirus / calicivirus / panleukopenia combo'),
1674 ('Canine distemper / parvovirus booster'),
1675 ('Vaccine certificate needed for travel'),
1676 ('First vaccination - new pet'),
1677 ('Overdue vaccination catch-up')
1678 ) AS r(reason)
1679 WHERE o.id IS NOT NULL -- random per row
1680 ORDER BY random()
1681 LIMIT 1
1682) reason_list;
1683
1684-- ============================================================
1685-- EXAMINATIONS for new appointments (~75% coverage)
1686-- date_examination >= date_appointment
1687-- employee_id - random vet with role_id = 2
1688-- examination_room_id - type = 'examination'
1689-- ============================================================
1690
1691INSERT INTO examination (date_examination, status, description, appointment_id, employee_id, examination_room_id)
1692SELECT
1693 a.date_appointment + (floor(random() * 5))::int AS date_examination,
1694
1695 CASE
1696 WHEN random() < 0.78 THEN 'completed'
1697 WHEN random() < 0.88 THEN 'scheduled'
1698 ELSE 'cancelled'
1699 END AS status,
1700
1701 desc_list.description,
1702 a.id AS appointment_id,
1703
1704 emp.id AS employee_id,
1705 r.id AS examination_room_id
1706
1707FROM appointment a
1708-- appointments that have no examination yet
1709LEFT JOIN examination e ON e.appointment_id = a.id
1710CROSS JOIN LATERAL (
1711 SELECT description
1712 FROM (VALUES
1713 ('Pre-vaccination health check completed. Patient fit for immunisation.'),
1714 ('Animal examined prior to vaccination. No contraindications found.'),
1715 ('Vaccination visit. General condition assessed; vitals normal.'),
1716 ('Patient presented for scheduled immunisation. Brief physical performed.'),
1717 ('Health status confirmed satisfactory before vaccine administration.'),
1718 ('Routine vaccination examination. Lymph nodes and temperature normal.'),
1719 ('Owner updated on vaccine schedule. Patient in good overall condition.'),
1720 ('Pre-vaccine check: skin, coat, mucous membranes all within normal limits.'),
1721 ('Examination completed. Booster due; owner reminded of next schedule.'),
1722 ('Young patient examined ahead of core vaccine series. No abnormalities.')
1723 ) AS d(description)
1724 WHERE a.id IS NOT NULL
1725 ORDER BY random()
1726 LIMIT 1
1727) desc_list
1728-- random employee with role_id = 2
1729CROSS JOIN LATERAL (
1730 SELECT id
1731 FROM employee
1732 WHERE role_id = 2
1733 AND a.id IS NOT NULL
1734 ORDER BY random()
1735 LIMIT 1
1736) emp
1737-- random room with type = 'examination'
1738CROSS JOIN LATERAL (
1739 SELECT id
1740 FROM examination_room
1741 WHERE type = 'examination'
1742 AND a.id IS NOT NULL
1743 ORDER BY random()
1744 LIMIT 1
1745) r
1746WHERE e.id IS NULL -- no existing examination for this appointment
1747 AND random() < 0.75; -- 75% appointments get an examination
1748
1749
1750-- ============================================================
1751-- treatment (vaccination type)
1752-- linked to completed examinations that don't already have
1753-- a vaccination treatment
1754-- ============================================================
1755
1756INSERT INTO treatment (date_treatment, notes, treatment_type_id, examination_id)
1757SELECT
1758 e.date_examination + (floor(random() * 3))::int AS date_treatment,
1759
1760 notes_list.notes,
1761
1762 (SELECT id FROM treatment_type WHERE name = 'vaccination') AS treatment_type_id,
1763
1764 e.id AS examination_id
1765
1766FROM examination e
1767CROSS JOIN LATERAL (
1768 SELECT notes
1769 FROM (VALUES
1770 ('Core vaccine administered. No immediate adverse reaction observed.'),
1771 ('Booster vaccination given. Owner advised to monitor for 24 hours.'),
1772 ('Rabies vaccine administered. Certificate issued.'),
1773 ('Puppy / kitten primary course vaccine given. Next dose scheduled.'),
1774 ('Annual booster completed. Patient tolerated injection well.'),
1775 ('Leptospirosis component included in this year''s booster.'),
1776 ('Intranasal Bordetella vaccine administered without complication.'),
1777 ('Combination vaccine given SC in right scruff. Patient calm throughout.'),
1778 ('Travel vaccine record updated. International health certificate provided.'),
1779 ('Catch-up vaccination completed. Full schedule now up to date.')
1780 ) AS n(notes)
1781 WHERE e.id IS NOT NULL
1782 ORDER BY random()
1783 LIMIT 1
1784) notes_list
1785WHERE e.status = 'completed'
1786-- only those that don't already have a vaccination treatment
1787 AND NOT EXISTS (
1788 SELECT 1
1789 FROM treatment t
1790 JOIN treatment_type tt ON tt.id = t.treatment_type_id
1791 WHERE t.examination_id = e.id
1792 AND tt.name = 'vaccination'
1793 );
1794
1795
1796
1797-- ============================================================
1798-- Shop categories, items, attributes, attribute values
1799-- ============================================================
1800
1801
1802-- ============================================================
1803-- shop_item_category
1804-- ============================================================
1805
1806INSERT INTO shop_item_category (name, parent_id)
1807VALUES ('Food', NULL)
1808ON CONFLICT DO NOTHING;
1809
1810INSERT INTO shop_item_category (name, parent_id)
1811SELECT 'Snacks', id FROM shop_item_category WHERE name = 'Food'
1812ON CONFLICT DO NOTHING;
1813
1814INSERT INTO shop_item_category (name, parent_id)
1815SELECT 'Supplements', id FROM shop_item_category WHERE name = 'Medicine'
1816ON CONFLICT DO NOTHING;
1817
1818INSERT INTO shop_item_category (name, parent_id)
1819VALUES ('Accessories', NULL)
1820ON CONFLICT DO NOTHING;
1821
1822INSERT INTO shop_item_category (name, parent_id)
1823SELECT 'Hygiene and Grooming', id FROM shop_item_category WHERE name = 'Pet Supplies'
1824ON CONFLICT DO NOTHING;
1825
1826INSERT INTO shop_item_category (name, parent_id)
1827SELECT 'Toys', id FROM shop_item_category WHERE name = 'Accessories'
1828ON CONFLICT DO NOTHING;
1829
1830INSERT INTO shop_item_category (name, parent_id)
1831VALUES ('Clothing', NULL)
1832ON CONFLICT DO NOTHING;
1833
1834
1835-- ============================================================
1836-- shop_item - Food
1837-- Foods for dogs, cats, birds, fish, reptiles, small mammals
1838-- ============================================================
1839
1840INSERT INTO shop_item (name, price, stock, shop_item_category_id)
1841SELECT name, price, stock,
1842 (SELECT id FROM shop_item_category WHERE name = 'Food')
1843FROM (VALUES
1844 -- Dog food
1845 ('Royal Canin Adult Dry Dog Food 15kg', 45.99, 80),
1846 ('Hill''s Science Diet Puppy Chicken 12kg', 42.50, 60),
1847 ('Purina Pro Plan Sensitive Salmon 7kg', 28.99, 75),
1848 ('Orijen Original Dry Dog Food 6kg', 38.99, 50),
1849 ('Pedigree Adult Wet Dog Food Beef 400g', 1.99, 300),
1850 ('Cesar Classic Loaf with Chicken 150g', 1.49, 250),
1851 ('Royal Canin Maxi Adult Dry Dog Food 10kg', 32.99, 70),
1852 ('Eukanuba Adult Small Breed 3kg', 18.50, 90),
1853
1854 -- Cat food
1855 ('Royal Canin Indoor Adult Cat 4kg', 22.99, 100),
1856 ('Hill''s Science Diet Adult Cat Chicken 3.5kg',20.99, 85),
1857 ('Whiskas Adult Wet Cat Food Tuna 85g', 0.89, 400),
1858 ('Purina Felix Adult Salmon Pouches 12x85g', 8.99, 120),
1859 ('Orijen Cat & Kitten Dry Food 5.4kg', 44.99, 40),
1860 ('Sheba Perfect Portions Chicken & Tuna 72g', 1.29, 350),
1861 ('Royal Canin Kitten Dry Food 2kg', 15.99, 95),
1862
1863 -- Bird food
1864 ('Versele-Laga Prestige Parrot Mix 3kg', 12.99, 60),
1865 ('Kaytee Forti-Diet Pro Canary Seed 2lb', 9.99, 55),
1866 ('Zupreem Natural Pellets Medium Birds 1.25kg', 18.49, 45),
1867 ('Vitakraft Budgie Seed Mix 1kg', 5.99, 80),
1868 ('Harrisons Adult Lifetime Fine Pellets 454g', 17.99, 35),
1869
1870 -- Fish food
1871 ('Tetra Goldfish Flakes 200g', 6.49, 110),
1872 ('Hikari Cichlid Gold Floating Pellets 342g', 12.99, 70),
1873 ('Fluval Bug Bites Tropical Fish Food 45g', 9.99, 90),
1874 ('API Tropical Flakes 71g', 5.49, 100),
1875 ('Sera Vipan Nature Flake Food 250ml', 8.99, 85),
1876
1877 -- Reptile food
1878 ('Exo Terra Mealworms Canned Food 34g', 4.99, 60),
1879 ('Zoo Med Can O'' Crickets 35g', 5.49, 55),
1880 ('Flukers Freeze-Dried Crickets 1.2oz', 6.99, 50),
1881 ('Repashy Crested Gecko MRP Banana 3oz', 12.49, 40),
1882
1883 -- Small mammal food
1884 ('Oxbow Essentials Adult Rabbit Pellets 5lb', 18.99, 65),
1885 ('Supreme Science Selective Hamster 350g', 6.49, 75),
1886 ('Kaytee Forti-Diet Guinea Pig Food 5lb', 12.99, 55),
1887 ('Versele-Laga Complete Ferret 750g', 14.99, 45)
1888) AS t(name, price, stock);
1889
1890
1891-- ============================================================
1892-- shop_item - Snacks
1893-- ============================================================
1894
1895INSERT INTO shop_item (name, price, stock, shop_item_category_id)
1896SELECT name, price, stock,
1897 (SELECT id FROM shop_item_category WHERE name = 'Snacks')
1898FROM (VALUES
1899 -- Dog snacks
1900 ('Milk-Bone Original Dog Biscuits 24oz', 7.99, 150),
1901 ('Zuke''s Mini Naturals Chicken Treats 6oz', 8.49, 120),
1902 ('Dentastix Daily Oral Care Medium 28 Sticks', 12.99, 100),
1903 ('Greenies Original Dental Treats Large 12oz', 16.99, 85),
1904 ('Wellness Soft WellBites Lamb & Salmon 6oz', 9.49, 95),
1905 ('Merrick Power Bites Real Chicken 6oz', 8.99, 110),
1906 ('Nylabones Puppy Chew Chicken Flavour', 5.99, 130),
1907 ('Bully Sticks 6-inch Natural 10 Pack', 14.99, 70),
1908
1909 -- Cat snacks
1910 ('Temptations Classic Treats Chicken 85g', 2.99, 200),
1911 ('Dreamies Cat Treats Cheese 60g', 1.99, 220),
1912 ('Churu Purée Tuna with Salmon 4x14g', 4.49, 180),
1913 ('Greenies Feline Dental Treats Ocean Fish 60g', 5.49, 110),
1914 ('Whiskas Temptations Tuna 180g', 3.99, 150),
1915
1916 -- Bird snacks
1917 ('Vitakraft Crunch Stick Budgie Honey 2-pack', 3.49, 90),
1918 ('Kaytee Fiesta Yogurt Dipped Papaya Bird Treat', 4.99, 70),
1919
1920 -- Small mammal snacks
1921 ('Oxbow Simple Rewards Timothy Hay Treats 3oz', 3.99, 85),
1922 ('Supreme Tiny Friends Yogurt Drops Strawberry', 2.99, 95),
1923 ('Kaytee Treat Stick Rabbit Honey & Oat', 2.49, 100)
1924) AS t(name, price, stock);
1925
1926
1927-- ============================================================
1928-- shop_item - Supplements
1929-- ============================================================
1930
1931INSERT INTO shop_item (name, price, stock, shop_item_category_id)
1932SELECT name, price, stock,
1933 (SELECT id FROM shop_item_category WHERE name = 'Supplements')
1934FROM (VALUES
1935 ('Zesty Paws Multivitamin Bites for Dogs 90ct', 24.99, 70),
1936 ('VetriScience Canine Plus Senior Tabs 60ct', 19.99, 55),
1937 ('Nutramax Cosequin DS Plus MSM 60ct', 29.99, 60),
1938 ('Zesty Paws Omega Bites Wild Alaskan Fish Oil', 22.49, 65),
1939 ('Vetri-Science Cell Advance 440 Cats 60ct', 18.99, 50),
1940 ('Pet Naturals Daily Multi Cat 30ct', 9.99, 80),
1941 ('Nutramax Proviable-DC Probiotic Caps 80ct', 27.99, 45),
1942 ('Virbac C.E.T. Enzymatic Chews Medium Dogs', 21.99, 55),
1943 ('Zesty Paws Mobility Bites Hip & Joint Dogs', 23.99, 60),
1944 ('VetriScience Composure Calming Chews 30ct', 16.99, 70),
1945 ('Oxbow Natural Science Vitamin C Tabs Guinea', 10.49, 85),
1946 ('Rep-Cal Herptivite Reptile Multivitamin 3.3oz',14.99, 40),
1947 ('Fluval Vita Tropical Fish Vitamin Drops 50ml', 8.99, 75),
1948 ('Vetri-Science Feline Ultimate Probiotic 60ct', 21.99, 50)
1949) AS t(name, price, stock);
1950
1951
1952-- ============================================================
1953-- shop_item - Accessories
1954-- ============================================================
1955
1956INSERT INTO shop_item (name, price, stock, shop_item_category_id)
1957SELECT name, price, stock,
1958 (SELECT id FROM shop_item_category WHERE name = 'Accessories')
1959FROM (VALUES
1960 ('PetSafe Easy Walk Dog Harness Medium', 19.99, 90),
1961 ('Ruffwear Front Range Harness Large', 39.99, 55),
1962 ('Kong Classic Dog Toy Large', 13.99, 120),
1963 ('Kurgo Tru-Fit Smart Dog Harness XL', 34.99, 45),
1964 ('Flexi New Classic Retractable Leash 8m', 18.99, 100),
1965 ('Rogz Reflective Dog Collar Medium', 9.99, 130),
1966 ('Catit Flower Fountain 3L', 24.99, 75),
1967 ('Trixie Cat Tree Tower 150cm', 69.99, 30),
1968 ('PetSafe ScoopFree Automatic Litter Box', 99.99, 20),
1969 ('AmazonBasics Elevated Cooling Dog Bed Large', 29.99, 60),
1970 ('Midwest iCrate Single Door Dog Crate 30in', 49.99, 40),
1971 ('Ferplast Favola Hamster Cage', 34.99, 35),
1972 ('Zolux Birdcage Volière Sydney 105cm', 89.99, 15),
1973 ('Exo Terra Terrarium 60x45x45cm', 139.99, 12),
1974 ('Fluval Spec V Aquarium Kit 19L', 79.99, 20),
1975 ('Catit Senses 2.0 Food Tree Puzzle', 18.99, 65),
1976 ('Dog ID Tag Stainless Steel Bone Shape', 4.99, 200),
1977 ('PetSafe Drinkwell Multi-Tier Fountain', 27.99, 50)
1978) AS t(name, price, stock);
1979
1980
1981-- ============================================================
1982-- shop_item - Hygiene and Grooming
1983-- ============================================================
1984
1985INSERT INTO shop_item (name, price, stock, shop_item_category_id)
1986SELECT name, price, stock,
1987 (SELECT id FROM shop_item_category WHERE name = 'Hygiene and Grooming')
1988FROM (VALUES
1989 ('Tropiclean Natural Flea & Tick Dog Shampoo', 12.99, 90),
1990 ('Burt''s Bees Hypoallergenic Dog Shampoo 16oz', 10.99, 85),
1991 ('Virbac Epi-Soothe Oatmeal Shampoo 500ml', 18.99, 60),
1992 ('Furminator deShedding Dog Shampoo 16oz', 14.99, 70),
1993 ('Pet Head Feeling Flaky Anti-Dandruff Shampoo', 11.49, 65),
1994 ('Chris Christensen Ice on Ice Conditioner 250ml',17.99, 45),
1995 ('Furminator Long Hair deShedding Tool Large', 39.99, 55),
1996 ('Andis EasyClip 2-Speed Dog Clipper Kit', 54.99, 30),
1997 ('Wahl Bravura Lithium Dog Clipper', 84.99, 20),
1998 ('Coastal Pet Safari Nail Clippers for Dogs', 9.99, 100),
1999 ('Dremel PawControl Dog Nail Grinder Kit', 34.99, 40),
2000 ('Virbac CET Oral Hygiene Kit Dog', 14.99, 75),
2001 ('Pet Republique Dog Dental Wipes 100ct', 8.99, 90),
2002 ('Douxo S3 PYO Antiseptic Mousse 150ml', 19.99, 50),
2003 ('Veterinary Formula Clinical Care Ear Therapy', 9.99, 80),
2004 ('Zymox Otic Ear Solution with Hydrocortisone', 18.99, 55),
2005 ('Burt''s Bees Cat Hypoallergenic Shampoo 10oz', 9.99, 70),
2006 ('Bio-Groom Super White Cat Shampoo 236ml', 11.49, 50),
2007 ('Safari Cat Shedding Comb', 7.99, 85),
2008 ('Hertzko Self-Cleaning Slicker Brush', 15.99, 95),
2009 ('Pet Wipes Fragrance Free 100ct', 6.99, 120),
2010 ('Tropiclean Fresh Breath Dog Water Additive', 10.49, 80)
2011) AS t(name, price, stock);
2012
2013
2014-- ============================================================
2015-- shop_item - Toys
2016-- ============================================================
2017
2018INSERT INTO shop_item (name, price, stock, shop_item_category_id)
2019SELECT name, price, stock,
2020 (SELECT id FROM shop_item_category WHERE name = 'Toys')
2021FROM (VALUES
2022 -- Dog toys
2023 ('Kong Extreme Dog Toy Large Black', 14.99, 100),
2024 ('Chuckit! Ultra Ball Medium 2-Pack', 9.99, 120),
2025 ('Outward Hound Hide-A-Squirrel Puzzle Large', 19.99, 70),
2026 ('KONG Wobbler Interactive Treat Toy', 12.99, 85),
2027 ('Tug-A-Jug Meal Dispensing Dog Toy', 14.49, 65),
2028 ('Benebone Wishbone Chew Toy Bacon Large', 15.99, 90),
2029 ('ZippyPaws Skinny Peltz Squeaky Plush 3-Pack', 11.99, 95),
2030 ('iFetch Interactive Ball Launcher Small', 99.99, 25),
2031
2032 -- Cat toys
2033 ('Da Bird Feather Wand Cat Toy', 9.99, 110),
2034 ('SmartyKat Hot Pursuit Electronic Cat Toy', 15.99, 75),
2035 ('PetFusion Ambush Interactive Cat Toy', 24.99, 50),
2036 ('Yeowww! Catnip Banana', 6.99, 130),
2037 ('Catit Senses 2.0 Circuit Cat Toy', 17.99, 60),
2038 ('Jackson Galaxy Air Wand Cat Toy', 9.49, 100),
2039
2040 -- Bird toys
2041 ('Super Bird Creations Booda Comfy Perch', 8.99, 55),
2042 ('Prevue Hendryx Parrot Ladder Toy 12in', 7.49, 60),
2043 ('Penn-Plax Bird Life Mirror with Bell', 5.99, 80),
2044
2045 -- Small mammal toys
2046 ('Niteangel Wooden Hamster Wheel 20cm Silent', 19.99, 50),
2047 ('Kaytee Run-About 7in Clear Exercise Ball', 5.99, 85),
2048 ('Ware Manufacturing Critter Tunnel Small', 7.99, 70),
2049
2050 -- Fish / aquarium enrichment
2051 ('Marina Aquarium Decoration Skull', 5.99, 90),
2052 ('Penn-Plax Betta Hammock Leaf Ledge', 3.99, 110)
2053) AS t(name, price, stock);
2054
2055
2056-- ============================================================
2057-- shop_item - Clothing
2058-- ============================================================
2059
2060INSERT INTO shop_item (name, price, stock, shop_item_category_id)
2061SELECT name, price, stock,
2062 (SELECT id FROM shop_item_category WHERE name = 'Clothing')
2063FROM (VALUES
2064 -- Hats
2065 ('Casual Canine Cowboy Dog Hat Small', 8.99, 60),
2066 ('Fido Finery Sun Protection Hat Medium', 12.99, 50),
2067 ('Rubies Pet Shop Sailor Dog Hat S/M', 6.49, 70),
2068
2069 -- Jackets / coats
2070 ('Ruffwear Overcoat Fuse Dog Jacket XS', 79.99, 25),
2071 ('Canada Pooch Puffer Vest Dog Jacket M', 44.99, 35),
2072 ('Hurtta Expedition Parka Dog Winter Coat L', 89.99, 20),
2073 ('Gooby Stretch Fleece Dog Vest Small', 18.99, 65),
2074 ('Pinkaholic New York Bella Waterproof Coat M', 34.99, 40),
2075 ('Zack & Zoey Nor''easter Dog Blanket Coat XL', 29.99, 30),
2076
2077 -- Shoes / boots
2078 ('Muttluks Fleece-Lined Dog Boots Set of 4 M', 39.99, 40),
2079 ('Ruffwear Grip Trex Dog Boots Set of 4 S', 74.99, 25),
2080 ('Ultra Paws Durable Dog Boots Set of 4 L', 29.99, 35),
2081 ('Pawz Natural Rubber Dog Boots Medium 12ct', 16.99, 55),
2082
2083 -- Recovery suits
2084 ('Suitical Recovery Suit Dog XS Black', 29.99, 45),
2085 ('Surgi-Snuggly Recovery Bodysuit Medium', 24.99, 50),
2086 ('Buckeye Surgical Recovery Suit Cat/Small Dog', 22.99, 55),
2087 ('iMatrix Recovery Suit Anti-Lick Vest Dog L', 34.99, 35)
2088) AS t(name, price, stock);
2089
2090
2091-- ============================================================
2092-- shop_item_attribute - one set per category
2093-- ============================================================
2094
2095-- ---------- Food ----------
2096INSERT INTO shop_item_attribute (name, data_type, shop_item_category_id)
2097SELECT attr, dtype,
2098 (SELECT id FROM shop_item_category WHERE name = 'Food')
2099FROM (VALUES
2100 ('weight_kg', 'decimal'),
2101 ('target_species', 'text'),
2102 ('life_stage', 'text'),
2103 ('flavour', 'text'),
2104 ('grain_free', 'boolean'),
2105 ('kcal_per_100g', 'integer')
2106) AS t(attr, dtype);
2107
2108-- ---------- Snacks ----------
2109INSERT INTO shop_item_attribute (name, data_type, shop_item_category_id)
2110SELECT attr, dtype,
2111 (SELECT id FROM shop_item_category WHERE name = 'Snacks')
2112FROM (VALUES
2113 ('weight_g', 'decimal'),
2114 ('target_species', 'text'),
2115 ('flavour', 'text'),
2116 ('primary_benefit', 'text'),
2117 ('suitable_age', 'text')
2118) AS t(attr, dtype);
2119
2120-- ---------- Supplements ----------
2121INSERT INTO shop_item_attribute (name, data_type, shop_item_category_id)
2122SELECT attr, dtype,
2123 (SELECT id FROM shop_item_category WHERE name = 'Supplements')
2124FROM (VALUES
2125 ('target_species', 'text'),
2126 ('supplement_type', 'text'),
2127 ('units_per_pack', 'integer'),
2128 ('form', 'text'),
2129 ('key_ingredient', 'text')
2130) AS t(attr, dtype);
2131
2132-- ---------- Accessories ----------
2133INSERT INTO shop_item_attribute (name, data_type, shop_item_category_id)
2134SELECT attr, dtype,
2135 (SELECT id FROM shop_item_category WHERE name = 'Accessories')
2136FROM (VALUES
2137 ('target_species', 'text'),
2138 ('size', 'text'),
2139 ('material', 'text'),
2140 ('colour', 'text'),
2141 ('suitable_for', 'text')
2142) AS t(attr, dtype);
2143
2144-- ---------- Hygiene and Grooming ----------
2145INSERT INTO shop_item_attribute (name, data_type, shop_item_category_id)
2146SELECT attr, dtype,
2147 (SELECT id FROM shop_item_category WHERE name = 'Hygiene and Grooming')
2148FROM (VALUES
2149 ('target_species', 'text'),
2150 ('product_type', 'text'),
2151 ('volume_ml', 'decimal'),
2152 ('key_ingredient', 'text'),
2153 ('scent', 'text')
2154) AS t(attr, dtype);
2155
2156-- ---------- Toys ----------
2157INSERT INTO shop_item_attribute (name, data_type, shop_item_category_id)
2158SELECT attr, dtype,
2159 (SELECT id FROM shop_item_category WHERE name = 'Toys')
2160FROM (VALUES
2161 ('target_species', 'text'),
2162 ('size', 'text'),
2163 ('material', 'text'),
2164 ('interactive', 'boolean'),
2165 ('primary_activity', 'text')
2166) AS t(attr, dtype);
2167
2168-- ---------- Clothing ----------
2169INSERT INTO shop_item_attribute (name, data_type, shop_item_category_id)
2170SELECT attr, dtype,
2171 (SELECT id FROM shop_item_category WHERE name = 'Clothing')
2172FROM (VALUES
2173 ('target_species', 'text'),
2174 ('size', 'text'),
2175 ('material', 'text'),
2176 ('colour', 'text'),
2177 ('clothing_type', 'text'),
2178 ('waterproof', 'boolean')
2179) AS t(attr, dtype);
2180
2181-- ---------- Medicine ----------
2182INSERT INTO shop_item_attribute (name, data_type, shop_item_category_id)
2183SELECT attr, dtype,
2184 (SELECT id FROM shop_item_category WHERE name = 'Medicine')
2185FROM (VALUES
2186 ('dosage_form', 'text'),
2187 ('strength', 'text'),
2188 ('pack_size', 'integer'),
2189 ('prescription_only','boolean'),
2190 ('target_species', 'text')
2191) AS t(attr, dtype);
2192
2193
2194-- ============================================================
2195-- shop_item_attribute_value
2196-- One value per (item x attribute) for all items in each category.
2197-- ============================================================
2198
2199-- ----------------------------------------------------------------
2200-- Helper: map every shop_item in 'Food' to its attribute values
2201-- ----------------------------------------------------------------
2202
2203INSERT INTO shop_item_attribute_value (value, shop_item_attribute_id, shop_item_id)
2204SELECT final_val.val, a.id, si.id
2205FROM shop_item si
2206JOIN shop_item_category sic ON sic.id = si.shop_item_category_id AND sic.name = 'Food'
2207CROSS JOIN LATERAL (
2208 SELECT
2209 CASE
2210 WHEN si.name ILIKE '%15kg%' OR si.name ILIKE '%12kg%' OR si.name ILIKE '%10kg%'
2211 THEN regexp_replace(si.name, '.*?(\d+(?:\.\d+)?)\s*kg.*', '\1')
2212 WHEN si.name ILIKE '%7kg%' THEN '7'
2213 WHEN si.name ILIKE '%6kg%' THEN '6'
2214 WHEN si.name ILIKE '%5.4kg%'THEN '5.4'
2215 WHEN si.name ILIKE '%4kg%' THEN '4'
2216 WHEN si.name ILIKE '%3.5kg%'THEN '3.5'
2217 WHEN si.name ILIKE '%2kg%' THEN '2'
2218 WHEN si.name ILIKE '%5lb%' THEN '2.27'
2219 WHEN si.name ILIKE '%2lb%' THEN '0.91'
2220 WHEN si.name ILIKE '%400g%' THEN '0.4'
2221 WHEN si.name ILIKE '%454g%' THEN '0.454'
2222 WHEN si.name ILIKE '%750g%' THEN '0.75'
2223 WHEN si.name ILIKE '%350g%' THEN '0.35'
2224 WHEN si.name ILIKE '%250ml%'OR si.name ILIKE '%250g%' THEN '0.25'
2225 WHEN si.name ILIKE '%150g%' THEN '0.15'
2226 ELSE '0.5'
2227 END AS weight_kg,
2228
2229 CASE
2230 WHEN si.name ILIKE '%dog%' OR si.name ILIKE '%pedigree%' OR si.name ILIKE '%cesar%'
2231 OR si.name ILIKE '%orijen%' AND si.name NOT ILIKE '%cat%'
2232 OR si.name ILIKE '%eukanuba%' THEN 'Dog'
2233 WHEN si.name ILIKE '%cat%' OR si.name ILIKE '%whiskas%' OR si.name ILIKE '%felix%'
2234 OR si.name ILIKE '%sheba%' THEN 'Cat'
2235 WHEN si.name ILIKE '%parrot%' OR si.name ILIKE '%canary%' OR si.name ILIKE '%budgie%'
2236 OR si.name ILIKE '%bird%' OR si.name ILIKE '%pellets%' AND si.name ILIKE '%birds%' THEN 'Bird'
2237 WHEN si.name ILIKE '%goldfish%' OR si.name ILIKE '%cichlid%' OR si.name ILIKE '%tropical%'
2238 OR si.name ILIKE '%flake%' OR si.name ILIKE '%bug bites%' OR si.name ILIKE '%vipan%' THEN 'Fish'
2239 WHEN si.name ILIKE '%mealworm%' OR si.name ILIKE '%cricket%' OR si.name ILIKE '%crested gecko%'
2240 OR si.name ILIKE '%reptile%' THEN 'Reptile'
2241 WHEN si.name ILIKE '%rabbit%' OR si.name ILIKE '%hamster%' OR si.name ILIKE '%guinea pig%'
2242 OR si.name ILIKE '%ferret%' THEN 'Small Mammal'
2243 ELSE 'Multi-species'
2244 END AS target_species,
2245
2246 CASE
2247 WHEN si.name ILIKE '%puppy%' OR si.name ILIKE '%kitten%' THEN 'Puppy/Kitten'
2248 WHEN si.name ILIKE '%senior%' OR si.name ILIKE '%mature%' THEN 'Senior'
2249 ELSE 'Adult'
2250 END AS life_stage,
2251
2252 CASE
2253 WHEN si.name ILIKE '%salmon%' THEN 'Salmon'
2254 WHEN si.name ILIKE '%chicken%' THEN 'Chicken'
2255 WHEN si.name ILIKE '%beef%' THEN 'Beef'
2256 WHEN si.name ILIKE '%tuna%' THEN 'Tuna'
2257 WHEN si.name ILIKE '%banana%' THEN 'Banana'
2258 WHEN si.name ILIKE '%honey%' THEN 'Honey'
2259 WHEN si.name ILIKE '%papaya%' THEN 'Papaya'
2260 ELSE 'Mixed'
2261 END AS flavour,
2262
2263 CASE
2264 WHEN si.name ILIKE '%orijen%' OR si.name ILIKE '%grain free%' THEN 'true'
2265 ELSE 'false'
2266 END AS grain_free,
2267
2268 CASE
2269 WHEN si.name ILIKE '%wet%' OR si.name ILIKE '%loaf%'
2270 OR si.name ILIKE '%400g%' OR si.name ILIKE '%150g%'
2271 OR si.name ILIKE '%85g%' OR si.name ILIKE '%canned%' THEN '95'
2272 WHEN si.name ILIKE '%orijen%' THEN '398'
2273 ELSE '340'
2274 END AS kcal_per_100g
2275) v(weight_kg, target_species, life_stage, flavour, grain_free, kcal_per_100g)
2276JOIN shop_item_attribute a ON a.shop_item_category_id = sic.id
2277CROSS JOIN LATERAL (
2278 SELECT
2279 CASE a.name
2280 WHEN 'weight_kg' THEN v.weight_kg
2281 WHEN 'target_species' THEN v.target_species
2282 WHEN 'life_stage' THEN v.life_stage
2283 WHEN 'flavour' THEN v.flavour
2284 WHEN 'grain_free' THEN v.grain_free
2285 WHEN 'kcal_per_100g' THEN v.kcal_per_100g
2286 END AS val
2287) final_val(val)
2288WHERE final_val.val IS NOT NULL;
2289
2290
2291-- ----------------------------------------------------------------
2292-- Snacks attribute values
2293-- ----------------------------------------------------------------
2294
2295INSERT INTO shop_item_attribute_value (value, shop_item_attribute_id, shop_item_id)
2296SELECT final_val.val, a.id, si.id
2297FROM shop_item si
2298JOIN shop_item_category sic ON sic.id = si.shop_item_category_id AND sic.name = 'Snacks'
2299CROSS JOIN LATERAL (
2300 SELECT
2301 CASE
2302 WHEN si.name ILIKE '%24oz%' THEN '680'
2303 WHEN si.name ILIKE '%12oz%' THEN '340'
2304 WHEN si.name ILIKE '%6oz%' THEN '170'
2305 WHEN si.name ILIKE '%85g%' THEN '85'
2306 WHEN si.name ILIKE '%60g%' THEN '60'
2307 WHEN si.name ILIKE '%180g%' THEN '180'
2308 WHEN si.name ILIKE '%3oz%' THEN '85'
2309 ELSE '100'
2310 END AS weight_g,
2311
2312 CASE
2313 WHEN si.name ILIKE '%dog%' OR si.name ILIKE '%milk-bone%' OR si.name ILIKE '%dentastix%'
2314 OR si.name ILIKE '%greenies%' AND si.name NOT ILIKE '%feline%'
2315 OR si.name ILIKE '%bully%' OR si.name ILIKE '%nylabone%'
2316 OR si.name ILIKE '%wellbite%' OR si.name ILIKE '%merrick%' THEN 'Dog'
2317 WHEN si.name ILIKE '%cat%' OR si.name ILIKE '%temptation%' OR si.name ILIKE '%dreamies%'
2318 OR si.name ILIKE '%churu%' OR si.name ILIKE '%feline%' OR si.name ILIKE '%whiskas%' THEN 'Cat'
2319 WHEN si.name ILIKE '%budgie%' OR si.name ILIKE '%bird%' OR si.name ILIKE '%crunch stick%' THEN 'Bird'
2320 WHEN si.name ILIKE '%rabbit%' OR si.name ILIKE '%hamster%' OR si.name ILIKE '%guinea%'
2321 OR si.name ILIKE '%tiny friends%' OR si.name ILIKE '%oxbow%' THEN 'Small Mammal'
2322 ELSE 'Multi-species'
2323 END AS target_species,
2324
2325 CASE
2326 WHEN si.name ILIKE '%chicken%' THEN 'Chicken'
2327 WHEN si.name ILIKE '%salmon%' THEN 'Salmon'
2328 WHEN si.name ILIKE '%lamb%' THEN 'Lamb'
2329 WHEN si.name ILIKE '%tuna%' THEN 'Tuna'
2330 WHEN si.name ILIKE '%cheese%' THEN 'Cheese'
2331 WHEN si.name ILIKE '%bacon%' THEN 'Bacon'
2332 WHEN si.name ILIKE '%honey%' THEN 'Honey & Oat'
2333 WHEN si.name ILIKE '%strawberry%' THEN 'Strawberry'
2334 WHEN si.name ILIKE '%papaya%' THEN 'Papaya'
2335 ELSE 'Mixed'
2336 END AS flavour,
2337
2338 CASE
2339 WHEN si.name ILIKE '%dental%' OR si.name ILIKE '%dentastix%' OR si.name ILIKE '%greenies%' THEN 'Dental health'
2340 WHEN si.name ILIKE '%churu%' THEN 'Hydration & palatability'
2341 WHEN si.name ILIKE '%bully%' THEN 'Mental stimulation & chewing'
2342 WHEN si.name ILIKE '%nylabone%' THEN 'Chewing & teething'
2343 ELSE 'Reward & training'
2344 END AS primary_benefit,
2345
2346 CASE
2347 WHEN si.name ILIKE '%puppy%' OR si.name ILIKE '%kitten%' THEN 'Puppy/Kitten'
2348 WHEN si.name ILIKE '%senior%' THEN 'Senior'
2349 ELSE 'All ages'
2350 END AS suitable_age
2351) v(weight_g, target_species, flavour, primary_benefit, suitable_age)
2352JOIN shop_item_attribute a ON a.shop_item_category_id = sic.id
2353CROSS JOIN LATERAL (
2354 SELECT CASE a.name
2355 WHEN 'weight_g' THEN v.weight_g
2356 WHEN 'target_species' THEN v.target_species
2357 WHEN 'flavour' THEN v.flavour
2358 WHEN 'primary_benefit' THEN v.primary_benefit
2359 WHEN 'suitable_age' THEN v.suitable_age
2360 END AS val
2361) final_val(val)
2362WHERE final_val.val IS NOT NULL;
2363
2364
2365-- ----------------------------------------------------------------
2366-- Supplements attribute values
2367-- ----------------------------------------------------------------
2368
2369INSERT INTO shop_item_attribute_value (value, shop_item_attribute_id, shop_item_id)
2370SELECT final_val.val, a.id, si.id
2371FROM shop_item si
2372JOIN shop_item_category sic ON sic.id = si.shop_item_category_id AND sic.name = 'Supplements'
2373CROSS JOIN LATERAL (
2374 SELECT
2375 CASE
2376 WHEN si.name ILIKE '%dog%' OR si.name ILIKE '%canine%' THEN 'Dog'
2377 WHEN si.name ILIKE '%cat%' OR si.name ILIKE '%feline%' THEN 'Cat'
2378 WHEN si.name ILIKE '%guinea%' THEN 'Guinea Pig'
2379 WHEN si.name ILIKE '%reptile%' THEN 'Reptile'
2380 WHEN si.name ILIKE '%fish%' OR si.name ILIKE '%tropical%' THEN 'Fish'
2381 ELSE 'Multi-species'
2382 END AS target_species,
2383
2384 CASE
2385 WHEN si.name ILIKE '%multivitamin%' OR si.name ILIKE '%multi%' OR si.name ILIKE '%vita%' THEN 'Multivitamin'
2386 WHEN si.name ILIKE '%omega%' OR si.name ILIKE '%fish oil%' THEN 'Omega-3 / EFA'
2387 WHEN si.name ILIKE '%cosequin%' OR si.name ILIKE '%mobility%' OR si.name ILIKE '%joint%' THEN 'Joint support'
2388 WHEN si.name ILIKE '%probiotic%' THEN 'Probiotic'
2389 WHEN si.name ILIKE '%calming%' OR si.name ILIKE '%composure%' THEN 'Calming / stress'
2390 WHEN si.name ILIKE '%dental%' OR si.name ILIKE '%enzymatic%' THEN 'Dental health'
2391 WHEN si.name ILIKE '%vitamin c%' THEN 'Vitamin C'
2392 ELSE 'General health'
2393 END AS supplement_type,
2394
2395 CASE
2396 WHEN si.name ILIKE '%90ct%' THEN '90'
2397 WHEN si.name ILIKE '%80ct%' THEN '80'
2398 WHEN si.name ILIKE '%60ct%' THEN '60'
2399 WHEN si.name ILIKE '%30ct%' THEN '30'
2400 ELSE '60'
2401 END AS units_per_pack,
2402
2403 CASE
2404 WHEN si.name ILIKE '%bites%' OR si.name ILIKE '%chews%' THEN 'Soft chew'
2405 WHEN si.name ILIKE '%tabs%' OR si.name ILIKE '%tabs%' THEN 'Tablet'
2406 WHEN si.name ILIKE '%caps%' OR si.name ILIKE '%capsule%' THEN 'Capsule'
2407 WHEN si.name ILIKE '%drops%' THEN 'Liquid drops'
2408 WHEN si.name ILIKE '%powder%' THEN 'Powder'
2409 ELSE 'Tablet'
2410 END AS form,
2411
2412 CASE
2413 WHEN si.name ILIKE '%omega%' OR si.name ILIKE '%fish oil%' THEN 'EPA & DHA'
2414 WHEN si.name ILIKE '%cosequin%' OR si.name ILIKE '%joint%' THEN 'Glucosamine & Chondroitin'
2415 WHEN si.name ILIKE '%probiotic%' THEN 'Lactobacillus acidophilus'
2416 WHEN si.name ILIKE '%vitamin c%' THEN 'Ascorbic acid'
2417 WHEN si.name ILIKE '%calming%' THEN 'L-Theanine & B vitamins'
2418 ELSE 'Vitamins A, D3, E, B-complex'
2419 END AS key_ingredient
2420) v(target_species, supplement_type, units_per_pack, form, key_ingredient)
2421JOIN shop_item_attribute a ON a.shop_item_category_id = sic.id
2422CROSS JOIN LATERAL (
2423 SELECT CASE a.name
2424 WHEN 'target_species' THEN v.target_species
2425 WHEN 'supplement_type' THEN v.supplement_type
2426 WHEN 'units_per_pack' THEN v.units_per_pack
2427 WHEN 'form' THEN v.form
2428 WHEN 'key_ingredient' THEN v.key_ingredient
2429 END AS val
2430) final_val(val)
2431WHERE final_val.val IS NOT NULL;
2432
2433
2434-- ----------------------------------------------------------------
2435-- Accessories attribute values
2436-- ----------------------------------------------------------------
2437
2438INSERT INTO shop_item_attribute_value (value, shop_item_attribute_id, shop_item_id)
2439SELECT final_val.val, a.id, si.id
2440FROM shop_item si
2441JOIN shop_item_category sic
2442 ON sic.id = si.shop_item_category_id
2443 AND sic.name = 'Accessories'
2444
2445CROSS JOIN LATERAL (
2446 SELECT
2447 CASE
2448 WHEN si.name ILIKE '%dog%'
2449 OR si.name ILIKE '%canine%'
2450 OR si.name ILIKE '%harness%'
2451 OR si.name ILIKE '%leash%'
2452 OR si.name ILIKE '%crate%'
2453 OR si.name ILIKE '%cooling bed%'
2454 THEN 'Dog'
2455
2456 WHEN si.name ILIKE '%cat%'
2457 OR si.name ILIKE '%litter%'
2458 OR si.name ILIKE '%catit%'
2459 OR si.name ILIKE '%cat tree%'
2460 THEN 'Cat'
2461
2462 WHEN si.name ILIKE '%hamster%'
2463 OR si.name ILIKE '%favola%'
2464 THEN 'Hamster'
2465
2466 WHEN si.name ILIKE '%bird%'
2467 OR si.name ILIKE '%volière%'
2468 OR si.name ILIKE '%birdcage%'
2469 THEN 'Bird'
2470
2471 WHEN si.name ILIKE '%terrarium%'
2472 OR si.name ILIKE '%exo terra%'
2473 THEN 'Reptile'
2474
2475 WHEN si.name ILIKE '%aquarium%'
2476 OR si.name ILIKE '%fluval spec%'
2477 THEN 'Fish'
2478
2479 ELSE 'Multi-species'
2480 END AS target_species,
2481
2482 CASE
2483 WHEN si.name ILIKE '%xsmall%'
2484 OR si.name ILIKE '%xs%'
2485 OR si.name ILIKE '%extra small%'
2486 THEN 'XS'
2487
2488 WHEN si.name ILIKE '%small%'
2489 OR si.name ILIKE '% s %'
2490 THEN 'S'
2491
2492 WHEN si.name ILIKE '%medium%'
2493 OR si.name ILIKE '% m %'
2494 THEN 'M'
2495
2496 WHEN si.name ILIKE '%large%'
2497 OR si.name ILIKE '% l %'
2498 OR si.name ILIKE '% xl%'
2499 THEN 'L'
2500
2501 ELSE 'Universal'
2502 END AS size,
2503
2504 CASE
2505 WHEN si.name ILIKE '%nylon%' THEN 'Nylon'
2506 WHEN si.name ILIKE '%leather%' THEN 'Leather'
2507 WHEN si.name ILIKE '%metal%'
2508 OR si.name ILIKE '%stainless%' THEN 'Stainless steel'
2509 WHEN si.name ILIKE '%plastic%' THEN 'Plastic'
2510 WHEN si.name ILIKE '%wire%'
2511 OR si.name ILIKE '%crate%' THEN 'Steel wire'
2512 WHEN si.name ILIKE '%wood%'
2513 OR si.name ILIKE '%tree%' THEN 'Sisal & wood'
2514 ELSE 'Mixed materials'
2515 END AS material,
2516
2517 CASE
2518 WHEN si.name ILIKE '%black%' THEN 'Black'
2519 WHEN si.name ILIKE '%red%' THEN 'Red'
2520 WHEN si.name ILIKE '%blue%' THEN 'Blue'
2521 ELSE 'Assorted'
2522 END AS colour,
2523
2524 CASE
2525 WHEN si.name ILIKE '%fountain%' THEN 'Hydration'
2526 WHEN si.name ILIKE '%harness%' THEN 'Walking / control'
2527 WHEN si.name ILIKE '%leash%'
2528 OR si.name ILIKE '%lead%' THEN 'Walking / restraint'
2529 WHEN si.name ILIKE '%collar%' THEN 'Identification & control'
2530 WHEN si.name ILIKE '%crate%' THEN 'Containment & transport'
2531 WHEN si.name ILIKE '%bed%' THEN 'Rest & comfort'
2532 WHEN si.name ILIKE '%litter%' THEN 'Hygiene'
2533 WHEN si.name ILIKE '%cage%'
2534 OR si.name ILIKE '%terrarium%'
2535 OR si.name ILIKE '%aquarium%'
2536 THEN 'Housing'
2537 WHEN si.name ILIKE '%puzzle%'
2538 OR si.name ILIKE '%food tree%'
2539 THEN 'Enrichment & feeding'
2540 ELSE 'General accessory'
2541 END AS suitable_for
2542
2543) v(target_species, size, material, colour, suitable_for)
2544
2545JOIN shop_item_attribute a
2546 ON a.shop_item_category_id = sic.id
2547
2548CROSS JOIN LATERAL (
2549 SELECT CASE a.name
2550 WHEN 'target_species' THEN v.target_species
2551 WHEN 'size' THEN v.size
2552 WHEN 'material' THEN v.material
2553 WHEN 'colour' THEN v.colour
2554 WHEN 'suitable_for' THEN v.suitable_for
2555 END AS val
2556) final_val(val)
2557
2558WHERE final_val.val IS NOT NULL;
2559
2560
2561-- ----------------------------------------------------------------
2562-- Hygiene and Grooming attribute values
2563-- ----------------------------------------------------------------
2564
2565INSERT INTO shop_item_attribute_value (value, shop_item_attribute_id, shop_item_id)
2566SELECT final_val.val, a.id, si.id
2567FROM shop_item si
2568JOIN shop_item_category sic ON sic.id = si.shop_item_category_id AND sic.name = 'Hygiene and Grooming'
2569CROSS JOIN LATERAL (
2570 SELECT
2571 CASE
2572 WHEN si.name ILIKE '%cat%' THEN 'Cat'
2573 WHEN si.name ILIKE '%dog%' OR si.name ILIKE '%canine%'
2574 OR si.name ILIKE '%bravura%' OR si.name ILIKE '%andis%'
2575 OR si.name ILIKE '%wahl%' OR si.name ILIKE '%furminator%' THEN 'Dog'
2576 ELSE 'Dog & Cat'
2577 END AS target_species,
2578
2579 CASE
2580 WHEN si.name ILIKE '%shampoo%' THEN 'Shampoo'
2581 WHEN si.name ILIKE '%conditioner%' THEN 'Conditioner'
2582 WHEN si.name ILIKE '%clipper%' THEN 'Clipper'
2583 WHEN si.name ILIKE '%nail%' OR si.name ILIKE '%grinder%' THEN 'Nail care'
2584 WHEN si.name ILIKE '%brush%' OR si.name ILIKE '%comb%' OR si.name ILIKE '%deShedding tool%' THEN 'Brush / comb'
2585 WHEN si.name ILIKE '%dental%' OR si.name ILIKE '%oral%' OR si.name ILIKE '%toothbrush%' THEN 'Dental care'
2586 WHEN si.name ILIKE '%mousse%' THEN 'Medicated mousse'
2587 WHEN si.name ILIKE '%ear%' OR si.name ILIKE '%otic%' THEN 'Ear care'
2588 WHEN si.name ILIKE '%wipe%' THEN 'Wipes'
2589 WHEN si.name ILIKE '%water additive%' THEN 'Dental water additive'
2590 ELSE 'General grooming'
2591 END AS product_type,
2592
2593 CASE
2594 WHEN si.name ILIKE '%500ml%' THEN '500'
2595 WHEN si.name ILIKE '%250ml%' THEN '250'
2596 WHEN si.name ILIKE '%236ml%' THEN '236'
2597 WHEN si.name ILIKE '%16oz%' THEN '473'
2598 WHEN si.name ILIKE '%10oz%' THEN '295'
2599 WHEN si.name ILIKE '%150ml%' THEN '150'
2600 ELSE NULL
2601 END AS volume_ml,
2602
2603 CASE
2604 WHEN si.name ILIKE '%oatmeal%' THEN 'Colloidal oatmeal'
2605 WHEN si.name ILIKE '%tea tree%' THEN 'Tea tree oil'
2606 WHEN si.name ILIKE '%hypoallerg%' THEN 'Aloe vera'
2607 WHEN si.name ILIKE '%enzymatic%' THEN 'Glucose oxidase'
2608 WHEN si.name ILIKE '%antiseptic%' THEN 'Chlorhexidine'
2609 WHEN si.name ILIKE '%flea%' THEN 'Pyrethrin'
2610 ELSE 'Gentle cleansing agents'
2611 END AS key_ingredient,
2612
2613 CASE
2614 WHEN si.name ILIKE '%fresh breath%' OR si.name ILIKE '%mint%' THEN 'Mint'
2615 WHEN si.name ILIKE '%fragrance free%' OR si.name ILIKE '%unscent%' THEN 'Unscented'
2616 WHEN si.name ILIKE '%oatmeal%' THEN 'Oatmeal'
2617 ELSE 'Lightly scented'
2618 END AS scent
2619) v(target_species, product_type, volume_ml, key_ingredient, scent)
2620JOIN shop_item_attribute a ON a.shop_item_category_id = sic.id
2621CROSS JOIN LATERAL (
2622 SELECT CASE a.name
2623 WHEN 'target_species' THEN v.target_species
2624 WHEN 'product_type' THEN v.product_type
2625 WHEN 'volume_ml' THEN v.volume_ml
2626 WHEN 'key_ingredient' THEN v.key_ingredient
2627 WHEN 'scent' THEN v.scent
2628 END AS val
2629) final_val(val)
2630WHERE final_val.val IS NOT NULL;
2631
2632
2633-- ----------------------------------------------------------------
2634-- Toys attribute values
2635-- ----------------------------------------------------------------
2636
2637INSERT INTO shop_item_attribute_value (value, shop_item_attribute_id, shop_item_id)
2638SELECT final_val.val, a.id, si.id
2639FROM shop_item si
2640JOIN shop_item_category sic ON sic.id = si.shop_item_category_id AND sic.name = 'Toys'
2641CROSS JOIN LATERAL (
2642 SELECT
2643 CASE
2644 WHEN si.name ILIKE '%dog%' OR si.name ILIKE '%kong%' AND si.name NOT ILIKE '%cat%'
2645 OR si.name ILIKE '%chuckit%' OR si.name ILIKE '%benebone%'
2646 OR si.name ILIKE '%iFetch%' OR si.name ILIKE '%zippy%'
2647 OR si.name ILIKE '%tug-a-jug%' THEN 'Dog'
2648 WHEN si.name ILIKE '%cat%' OR si.name ILIKE '%da bird%' OR si.name ILIKE '%smartykat%'
2649 OR si.name ILIKE '%petfusion%' OR si.name ILIKE '%catnip%'
2650 OR si.name ILIKE '%catit%' OR si.name ILIKE '%jackson galaxy%' THEN 'Cat'
2651 WHEN si.name ILIKE '%parrot%' OR si.name ILIKE '%bird%' OR si.name ILIKE '%perch%'
2652 OR si.name ILIKE '%ladder%' OR si.name ILIKE '%penn-plax%' AND si.name ILIKE '%mirror%' THEN 'Bird'
2653 WHEN si.name ILIKE '%hamster%' OR si.name ILIKE '%wheel%' OR si.name ILIKE '%exercise ball%'
2654 OR si.name ILIKE '%tunnel%' OR si.name ILIKE '%kaytee%' AND si.name NOT ILIKE '%dog%' THEN 'Small Mammal'
2655 WHEN si.name ILIKE '%skull%' OR si.name ILIKE '%betta%' OR si.name ILIKE '%aquarium%' THEN 'Fish'
2656 ELSE 'Multi-species'
2657 END AS target_species,
2658
2659 CASE
2660 WHEN si.name ILIKE '%small%' OR si.name ILIKE '% s ' THEN 'Small'
2661 WHEN si.name ILIKE '%large%' OR si.name ILIKE '% l ' THEN 'Large'
2662 WHEN si.name ILIKE '%medium%' THEN 'Medium'
2663 ELSE 'Standard'
2664 END AS size,
2665
2666 CASE
2667 WHEN si.name ILIKE '%rubber%' OR si.name ILIKE '%kong%' OR si.name ILIKE '%extreme%' THEN 'Natural rubber'
2668 WHEN si.name ILIKE '%plush%' THEN 'Plush fabric'
2669 WHEN si.name ILIKE '%wood%' OR si.name ILIKE '%wooden%' OR si.name ILIKE '%ladder%' THEN 'Wood'
2670 WHEN si.name ILIKE '%plastic%' THEN 'ABS plastic'
2671 WHEN si.name ILIKE '%feather%' THEN 'Feather & wire'
2672 ELSE 'Mixed materials'
2673 END AS material,
2674
2675 CASE
2676 WHEN si.name ILIKE '%interactive%' OR si.name ILIKE '%electronic%'
2677 OR si.name ILIKE '%launcher%' OR si.name ILIKE '%ambush%'
2678 OR si.name ILIKE '%wobbler%' OR si.name ILIKE '%smartykat%'
2679 OR si.name ILIKE '%senses%' OR si.name ILIKE '%tug-a-jug%' THEN 'true'
2680 ELSE 'false'
2681 END AS interactive,
2682
2683 CASE
2684 WHEN si.name ILIKE '%dental%' OR si.name ILIKE '%chew%' OR si.name ILIKE '%benebone%'
2685 OR si.name ILIKE '%nylabone%' THEN 'Chewing & dental'
2686 WHEN si.name ILIKE '%fetch%' OR si.name ILIKE '%ball%' OR si.name ILIKE '%launcher%' THEN 'Fetch & chase'
2687 WHEN si.name ILIKE '%puzzle%' OR si.name ILIKE '%hide%' OR si.name ILIKE '%wobbler%'
2688 OR si.name ILIKE '%tug-a-jug%' OR si.name ILIKE '%food tree%' THEN 'Mental enrichment'
2689 WHEN si.name ILIKE '%wheel%' OR si.name ILIKE '%exercise%' OR si.name ILIKE '%tunnel%' THEN 'Exercise'
2690 WHEN si.name ILIKE '%catnip%' OR si.name ILIKE '%wand%' THEN 'Stimulation & hunting'
2691 WHEN si.name ILIKE '%plush%' OR si.name ILIKE '%squirrel%' OR si.name ILIKE '%squeaky%' THEN 'Comfort & play'
2692 ELSE 'General play'
2693 END AS primary_activity
2694) v(target_species, size, material, interactive, primary_activity)
2695JOIN shop_item_attribute a ON a.shop_item_category_id = sic.id
2696CROSS JOIN LATERAL (
2697 SELECT CASE a.name
2698 WHEN 'target_species' THEN v.target_species
2699 WHEN 'size' THEN v.size
2700 WHEN 'material' THEN v.material
2701 WHEN 'interactive' THEN v.interactive
2702 WHEN 'primary_activity' THEN v.primary_activity
2703 END AS val
2704) final_val(val)
2705WHERE final_val.val IS NOT NULL;
2706
2707
2708-- ----------------------------------------------------------------
2709-- Clothing attribute values
2710-- ----------------------------------------------------------------
2711
2712INSERT INTO shop_item_attribute_value (value, shop_item_attribute_id, shop_item_id)
2713SELECT final_val.val, a.id, si.id
2714FROM shop_item si
2715JOIN shop_item_category sic ON sic.id = si.shop_item_category_id AND sic.name = 'Clothing'
2716CROSS JOIN LATERAL (
2717 SELECT
2718 CASE
2719 WHEN si.name ILIKE '%cat%' OR si.name ILIKE '%surgi%' THEN 'Cat / Small Dog'
2720 ELSE 'Dog'
2721 END AS target_species,
2722
2723 CASE
2724 WHEN si.name ILIKE '% xs%' OR si.name ILIKE '%xsmall%' OR si.name ILIKE '%extra small%' THEN 'XS'
2725 WHEN si.name ILIKE '% s %' OR si.name ILIKE '% s/' OR si.name ILIKE '%small%' THEN 'S'
2726 WHEN si.name ILIKE '% m %' OR si.name ILIKE '%medium%' THEN 'M'
2727 WHEN si.name ILIKE '% l %' OR si.name ILIKE '%large%' THEN 'L'
2728 WHEN si.name ILIKE '% xl%' OR si.name ILIKE '%xlarge%' OR si.name ILIKE '%extra large%' THEN 'XL'
2729 ELSE 'Assorted'
2730 END AS size,
2731
2732 CASE
2733 WHEN si.name ILIKE '%fleece%' THEN 'Fleece'
2734 WHEN si.name ILIKE '%rubber%' THEN 'Natural rubber'
2735 WHEN si.name ILIKE '%waterproof%' THEN 'Waterproof nylon'
2736 WHEN si.name ILIKE '%puffer%' THEN 'Puffer nylon'
2737 WHEN si.name ILIKE '%parka%' THEN 'Insulated nylon'
2738 WHEN si.name ILIKE '%recovery%' OR si.name ILIKE '%surgi%' OR si.name ILIKE '%snuggly%' THEN 'Stretch cotton blend'
2739 ELSE 'Polyester blend'
2740 END AS material,
2741
2742 CASE
2743 WHEN si.name ILIKE '%black%' THEN 'Black'
2744 WHEN si.name ILIKE '%blue%' THEN 'Blue'
2745 WHEN si.name ILIKE '%red%' THEN 'Red'
2746 WHEN si.name ILIKE '%pink%' THEN 'Pink'
2747 ELSE 'Assorted'
2748 END AS colour,
2749
2750 CASE
2751 WHEN si.name ILIKE '%hat%' THEN 'Hat'
2752 WHEN si.name ILIKE '%jacket%' OR si.name ILIKE '%parka%'
2753 OR si.name ILIKE '%coat%' OR si.name ILIKE '%puffer%'
2754 OR si.name ILIKE '%vest%' AND si.name NOT ILIKE '%recovery%' THEN 'Jacket / Coat'
2755 WHEN si.name ILIKE '%boot%' THEN 'Boots'
2756 WHEN si.name ILIKE '%recovery%' OR si.name ILIKE '%surgi%'
2757 OR si.name ILIKE '%suit%' THEN 'Recovery suit'
2758 ELSE 'Clothing'
2759 END AS clothing_type,
2760
2761 CASE
2762 WHEN si.name ILIKE '%waterproof%' OR si.name ILIKE '%parka%'
2763 OR si.name ILIKE '%nor''easter%' OR si.name ILIKE '%grip trex%'
2764 OR si.name ILIKE '%muttluks%' THEN 'true'
2765 ELSE 'false'
2766 END AS waterproof
2767) v(target_species, size, material, colour, clothing_type, waterproof)
2768JOIN shop_item_attribute a ON a.shop_item_category_id = sic.id
2769CROSS JOIN LATERAL (
2770 SELECT CASE a.name
2771 WHEN 'target_species' THEN v.target_species
2772 WHEN 'size' THEN v.size
2773 WHEN 'material' THEN v.material
2774 WHEN 'colour' THEN v.colour
2775 WHEN 'clothing_type' THEN v.clothing_type
2776 WHEN 'waterproof' THEN v.waterproof
2777 END AS val
2778) final_val(val)
2779WHERE final_val.val IS NOT NULL;
2780
2781
2782-- ----------------------------------------------------------------
2783-- Medicine attribute values (for the ~60 shop items in 'Medicine')
2784-- ----------------------------------------------------------------
2785
2786INSERT INTO shop_item_attribute_value (value, shop_item_attribute_id, shop_item_id)
2787SELECT final_val.val, a.id, si.id
2788FROM shop_item si
2789JOIN shop_item_category sic ON sic.id = si.shop_item_category_id AND sic.name = 'Medicine'
2790CROSS JOIN LATERAL (
2791 SELECT
2792 CASE
2793 WHEN si.name ILIKE '%tablet%' THEN 'Tablet'
2794 WHEN si.name ILIKE '%capsule%' THEN 'Capsule'
2795 WHEN si.name ILIKE '%injection%'OR si.name ILIKE '%inj%' THEN 'Injection'
2796 WHEN si.name ILIKE '%solution%' OR si.name ILIKE '%suspension%'
2797 OR si.name ILIKE '%oral%' THEN 'Oral solution'
2798 WHEN si.name ILIKE '%granule%' THEN 'Granules'
2799 WHEN si.name ILIKE '%powder%' THEN 'Powder'
2800 WHEN si.name ILIKE '%infusion%' THEN 'IV infusion'
2801 WHEN si.name ILIKE '%flush%' THEN 'Sterile solution'
2802 ELSE 'Tablet'
2803 END AS dosage_form,
2804
2805 -- extract strength from name (e.g. '250mg', '1.5mg/ml', '20%')
2806 COALESCE(
2807 (regexp_match(si.name,
2808 '(\d+(?:\.\d+)?\s*(?:mg|mcg|g|%|IU)(?:/ml|/\d+ml)?)'))[1],
2809 'See label'
2810 ) AS strength,
2811
2812 CASE
2813 WHEN si.name ILIKE '%10 pack%' OR si.name ILIKE '%10ct%' THEN '10'
2814 WHEN si.name ILIKE '%28%' THEN '28'
2815 WHEN si.name ILIKE '%30%' THEN '30'
2816 WHEN si.name ILIKE '%60%' THEN '60'
2817 WHEN si.name ILIKE '%100ml%' THEN '1' -- vials
2818 WHEN si.name ILIKE '%10ml%' THEN '1'
2819 ELSE '30'
2820 END AS pack_size,
2821
2822 'false' AS prescription_only, -- all items in shop are OTC
2823
2824 'Dog & Cat' AS target_species
2825) v(dosage_form, strength, pack_size, prescription_only, target_species)
2826JOIN shop_item_attribute a ON a.shop_item_category_id = sic.id
2827CROSS JOIN LATERAL (
2828 SELECT CASE a.name
2829 WHEN 'dosage_form' THEN v.dosage_form
2830 WHEN 'strength' THEN v.strength
2831 WHEN 'pack_size' THEN v.pack_size
2832 WHEN 'prescription_only' THEN v.prescription_only
2833 WHEN 'target_species' THEN v.target_species
2834 END AS val
2835) final_val(val)
2836WHERE final_val.val IS NOT NULL;
2837
2838
2839-- ============================================================
2840-- remaining treatments (consultation, operation)
2841-- + treatment_attribute and treatment_attribute_value
2842-- for all 4 types:
2843-- prescription, vaccination, consultation, operation
2844-- ============================================================
2845
2846
2847-- ============================================================
2848-- treatment — consultation
2849-- One per completed examination that doesn't already have
2850-- a consultation treatment.
2851-- We give roughly 60% of completed exams a consultation.
2852-- ============================================================
2853
2854INSERT INTO treatment (date_treatment, notes, treatment_type_id, examination_id)
2855SELECT
2856 e.date_examination + (floor(random() * 3))::int AS date_treatment,
2857
2858 notes_list.notes,
2859
2860 (SELECT id FROM treatment_type WHERE name = 'consultation') AS treatment_type_id,
2861
2862 e.id AS examination_id
2863
2864FROM examination e
2865CROSS JOIN LATERAL (
2866 SELECT notes
2867 FROM (VALUES
2868 ('Owner counselled on diet and weight management.'),
2869 ('Behavioural concerns discussed; referral considered.'),
2870 ('Vaccination schedule reviewed with owner.'),
2871 ('Pain management options explained to owner.'),
2872 ('Discussed long-term management of chronic condition.'),
2873 ('Follow-up plan agreed; owner given written summary.'),
2874 ('Discussed surgical options and associated risks.'),
2875 ('Dental hygiene advice provided; home care demonstrated.'),
2876 ('Parasite prevention programme reviewed.'),
2877 ('Nutritional counselling completed; diet change recommended.')
2878 ) AS n(notes)
2879 WHERE e.id IS NOT NULL
2880 ORDER BY random()
2881 LIMIT 1
2882) notes_list
2883WHERE e.status = 'completed'
2884 AND random() < 0.60
2885 AND NOT EXISTS (
2886 SELECT 1 FROM treatment t
2887 JOIN treatment_type tt ON tt.id = t.treatment_type_id
2888 WHERE t.examination_id = e.id AND tt.name = 'consultation'
2889 );
2890
2891
2892-- ============================================================
2893-- treatment — operation
2894-- ~20% of completed exams get an operation treatment.
2895-- ============================================================
2896
2897INSERT INTO treatment (date_treatment, notes, treatment_type_id, examination_id)
2898SELECT
2899 e.date_examination + (floor(random() * 2))::int AS date_treatment,
2900
2901 notes_list.notes,
2902
2903 (SELECT id FROM treatment_type WHERE name = 'operation') AS treatment_type_id,
2904
2905 e.id AS examination_id
2906
2907FROM examination e
2908CROSS JOIN LATERAL (
2909 SELECT notes
2910 FROM (VALUES
2911 ('Surgery performed without complications.'),
2912 ('Procedure completed; patient recovering well.'),
2913 ('Intraoperative findings documented; owner informed.'),
2914 ('Operation successful; post-op care instructions given.'),
2915 ('Surgical site closed; patient moved to recovery ward.'),
2916 ('Procedure carried out under general anaesthesia.'),
2917 ('Minor intraoperative bleeding managed; outcome satisfactory.'),
2918 ('Patient stable post-operatively; monitoring ongoing.'),
2919 ('Surgery completed; follow-up scheduled in 10 days.'),
2920 ('Operative procedure completed; histopathology sample submitted.')
2921 ) AS n(notes)
2922 WHERE e.id IS NOT NULL
2923 ORDER BY random()
2924 LIMIT 1
2925) notes_list
2926WHERE e.status = 'completed'
2927 AND random() < 0.20
2928 AND NOT EXISTS (
2929 SELECT 1 FROM treatment t
2930 JOIN treatment_type tt ON tt.id = t.treatment_type_id
2931 WHERE t.examination_id = e.id AND tt.name = 'operation'
2932 );
2933
2934
2935-- ============================================================
2936-- treatment_attribute
2937-- One attribute set per treatment_type.
2938-- ============================================================
2939
2940-- ---------- prescription ----------
2941INSERT INTO treatment_attribute (name, data_type, treatment_type_id)
2942SELECT attr, dtype,
2943 (SELECT id FROM treatment_type WHERE name = 'prescription')
2944FROM (VALUES
2945 ('medication_class', 'text'),
2946 ('route', 'text'),
2947 ('refills_allowed', 'integer'),
2948 ('withdrawal_period', 'text')
2949) AS t(attr, dtype);
2950
2951-- ---------- vaccination ----------
2952INSERT INTO treatment_attribute (name, data_type, treatment_type_id)
2953SELECT attr, dtype,
2954 (SELECT id FROM treatment_type WHERE name = 'vaccination')
2955FROM (VALUES
2956 ('vaccine_name', 'text'),
2957 ('manufacturer', 'text'),
2958 ('batch_number', 'text'),
2959 ('num_doses', 'integer'),
2960 ('dose_number', 'integer'),
2961 ('route', 'text'),
2962 ('site', 'text'),
2963 ('date_next', 'date'),
2964 ('adverse_reaction', 'boolean')
2965) AS t(attr, dtype);
2966
2967-- ---------- consultation ----------
2968INSERT INTO treatment_attribute (name, data_type, treatment_type_id)
2969SELECT attr, dtype,
2970 (SELECT id FROM treatment_type WHERE name = 'consultation')
2971FROM (VALUES
2972 ('topic', 'text'),
2973 ('description', 'text'),
2974 ('referral', 'boolean'),
2975 ('referral_to', 'text'),
2976 ('follow_up_days', 'integer'),
2977 ('owner_present', 'boolean')
2978) AS t(attr, dtype);
2979
2980-- ---------- operation ----------
2981INSERT INTO treatment_attribute (name, data_type, treatment_type_id)
2982SELECT attr, dtype,
2983 (SELECT id FROM treatment_type WHERE name = 'operation')
2984FROM (VALUES
2985 ('operation_type', 'text'),
2986 ('status', 'text'),
2987 ('anesthesia', 'text'),
2988 ('duration_minutes', 'integer'),
2989 ('date_checkup', 'date'),
2990 ('surgeon', 'text'),
2991 ('complications', 'boolean')
2992) AS t(attr, dtype);
2993
2994
2995-- ============================================================
2996-- treatment_attribute_value
2997-- One value per (treatment x attribute) for every treatment.
2998-- ============================================================
2999
3000-- ----------------------------------------------------------------
3001-- PRESCRIPTION treatment attribute values
3002-- ----------------------------------------------------------------
3003
3004INSERT INTO treatment_attribute_value (value, notes, treatment_attribute_id, treatment_id)
3005SELECT
3006 final_val.val,
3007 NULL,
3008 a.id,
3009 t.id
3010FROM treatment t
3011JOIN treatment_type tt ON tt.id = t.treatment_type_id AND tt.name = 'prescription'
3012JOIN treatment_attribute a ON a.treatment_type_id = tt.id
3013CROSS JOIN LATERAL (
3014 -- generate all 6 attribute values in one lateral
3015 SELECT
3016 (ARRAY['Antibiotic','NSAID','Corticosteroid','Antiparasitic',
3017 'Antifungal','Analgesic','Anticonvulsant','Cardiac',
3018 'Gastrointestinal','Immunosuppressant'])
3019 [1 + (abs(hashtext(t.id::text || 'cls')) % 10)] AS medication_class,
3020
3021 (ARRAY['Oral','Subcutaneous injection','Intramuscular injection',
3022 'Topical','Intravenous','Ophthalmic'])
3023 [1 + (abs(hashtext(t.id::text || 'rte')) % 6)] AS route,
3024
3025 (abs(hashtext(t.id::text || 'ref')) % 3)::text AS refills_allowed,
3026
3027 CASE (abs(hashtext(t.id::text || 'wth')) % 3)
3028 WHEN 0 THEN 'None'
3029 WHEN 1 THEN '24 hours'
3030 ELSE '48 hours'
3031 END AS withdrawal_period
3032) v
3033CROSS JOIN LATERAL (
3034 SELECT CASE a.name
3035 WHEN 'medication_class' THEN v.medication_class
3036 WHEN 'route' THEN v.route
3037 WHEN 'refills_allowed' THEN v.refills_allowed
3038 WHEN 'withdrawal_period' THEN v.withdrawal_period
3039 END AS val
3040) final_val
3041WHERE final_val.val IS NOT NULL;
3042
3043
3044-- ----------------------------------------------------------------
3045-- VACCINATION treatment attribute values
3046-- ----------------------------------------------------------------
3047
3048INSERT INTO treatment_attribute_value (value, notes, treatment_attribute_id, treatment_id)
3049SELECT
3050 final_val.val,
3051 NULL,
3052 a.id,
3053 t.id
3054FROM treatment t
3055JOIN treatment_type tt ON tt.id = t.treatment_type_id AND tt.name = 'vaccination'
3056JOIN treatment_attribute a ON a.treatment_type_id = tt.id
3057CROSS JOIN LATERAL (
3058 SELECT
3059 (ARRAY[
3060 'Nobivac DHPPi',
3061 'Nobivac Rabies',
3062 'Feligen CRP',
3063 'Purevax RCPCh',
3064 'Versican Plus DHPPi/L4',
3065 'Canigen L4',
3066 'Nobivac Lepto 4',
3067 'Eurican Herpes 205',
3068 'Felocell CVR',
3069 'Quantum Cat 7'
3070 ])[1 + (abs(hashtext(t.id::text || 'vac')) % 10)] AS vaccine_name,
3071
3072 (ARRAY['Zoetis','MSD Animal Health','Boehringer Ingelheim',
3073 'Virbac','Elanco'])
3074 [1 + (abs(hashtext(t.id::text || 'mfr')) % 5)] AS manufacturer,
3075
3076 'BN-' || lpad((abs(hashtext(t.id::text || 'bn')) % 900000 + 100000)::text, 6, '0')
3077 AS batch_number,
3078
3079 CASE (abs(hashtext(t.id::text || 'nd')) % 3)
3080 WHEN 0 THEN '1'
3081 WHEN 1 THEN '2'
3082 ELSE '3'
3083 END AS num_doses,
3084
3085 -- dose number in series (1 or 2)
3086 CASE (abs(hashtext(t.id::text || 'dn')) % 2)
3087 WHEN 0 THEN '1'
3088 ELSE '2'
3089 END AS dose_number,
3090
3091 (ARRAY['Subcutaneous','Intramuscular','Intranasal'])
3092 [1 + (abs(hashtext(t.id::text || 'rt2')) % 3)] AS route,
3093
3094 (ARRAY['Right scruff','Left scruff','Right hindlimb','Left hindlimb'])
3095 [1 + (abs(hashtext(t.id::text || 'ste')) % 4)] AS site,
3096
3097 -- date_next: 1 year after treatment date
3098 (t.date_treatment + interval '1 year')::date::text AS date_next,
3099
3100 -- adverse reaction: 3% chance
3101 CASE WHEN (abs(hashtext(t.id::text || 'adv')) % 100) < 3
3102 THEN 'true' ELSE 'false'
3103 END AS adverse_reaction
3104) v
3105CROSS JOIN LATERAL (
3106 SELECT CASE a.name
3107 WHEN 'vaccine_name' THEN v.vaccine_name
3108 WHEN 'manufacturer' THEN v.manufacturer
3109 WHEN 'batch_number' THEN v.batch_number
3110 WHEN 'num_doses' THEN v.num_doses
3111 WHEN 'dose_number' THEN v.dose_number
3112 WHEN 'route' THEN v.route
3113 WHEN 'site' THEN v.site
3114 WHEN 'date_next' THEN v.date_next
3115 WHEN 'adverse_reaction' THEN v.adverse_reaction
3116 END AS val
3117) final_val
3118WHERE final_val.val IS NOT NULL;
3119
3120
3121-- ----------------------------------------------------------------
3122-- CONSULTATION treatment attribute values
3123-- ----------------------------------------------------------------
3124
3125INSERT INTO treatment_attribute_value (value, notes, treatment_attribute_id, treatment_id)
3126SELECT
3127 final_val.val,
3128 NULL,
3129 a.id,
3130 t.id
3131FROM treatment t
3132JOIN treatment_type tt ON tt.id = t.treatment_type_id AND tt.name = 'consultation'
3133JOIN treatment_attribute a ON a.treatment_type_id = tt.id
3134CROSS JOIN LATERAL (
3135 SELECT
3136 (ARRAY[
3137 'Nutrition and weight management',
3138 'Behavioural assessment',
3139 'Chronic disease management',
3140 'Pre-surgical counselling',
3141 'Post-operative care planning',
3142 'Dental hygiene advice',
3143 'Parasite prevention review',
3144 'Vaccination schedule planning',
3145 'End-of-life care discussion',
3146 'Second opinion review'
3147 ])[1 + (abs(hashtext(t.id::text || 'top')) % 10)] AS topic,
3148
3149 (ARRAY[
3150 'Owner presented concerns regarding the pet''s appetite and energy levels. A full dietary review was completed and a lower-calorie prescription diet was recommended. Owner was shown how to measure portions correctly and advised to recheck in 4 weeks.',
3151 'Behavioural history taken in detail. Pet displays anxiety-related signs including excessive grooming and vocalization. Environmental enrichment strategies discussed and a referral to a veterinary behaviourist was considered.',
3152 'Long-term management plan for the pet''s diagnosed chronic kidney disease was reviewed. Blood results interpreted and ACE inhibitor dosage adjusted. Owner counselled on signs of deterioration and instructed to return if vomiting or lethargy develops.',
3153 'Pre-surgical consultation completed for elective spay procedure. Risks and benefits of anaesthesia explained. Pre-operative blood panel ordered. Owner provided written consent and fasting instructions.',
3154 'Post-operative wound checked and healing progress assessed. Owner demonstrated correct application of topical antiseptic and Elizabeth collar use. Suture removal booked for 10 days post-op.',
3155 'Dental examination findings discussed with owner. Stage 2 periodontal disease identified. Professional scale and polish recommended. Home brushing technique demonstrated using enzymatic toothpaste.',
3156 'Current parasite prevention programme assessed. Owner using an incomplete product that does not cover lungworm. Updated protocol prescribed combining monthly spot-on and quarterly wormer. Environmental hygiene advice given.',
3157 'Full vaccination history reviewed. Pet was overdue for core and leptospirosis boosters. Schedule re-established; primary course restarted where titre testing was not available. Owner reminded of annual requirement.',
3158 'Compassionate discussion held with owner regarding quality of life for their senior pet with advanced neoplasia. Palliative care options including pain management and hospice-style support outlined. Owner given time to consider options.',
3159 'Second opinion consultation for recurrent skin condition. Previous treatment history reviewed. Differential diagnoses reconsidered; skin biopsy recommended to rule out immune-mediated disease.'
3160 ])[1 + (abs(hashtext(t.id::text || 'dsc')) % 10)] AS description,
3161
3162 -- referral: 15% chance
3163 CASE WHEN (abs(hashtext(t.id::text || 'ref')) % 100) < 15
3164 THEN 'true' ELSE 'false'
3165 END AS referral,
3166
3167 CASE WHEN (abs(hashtext(t.id::text || 'ref')) % 100) < 15
3168 THEN (ARRAY[
3169 'Veterinary Dermatologist',
3170 'Veterinary Cardiologist',
3171 'Veterinary Behaviourist',
3172 'Veterinary Oncologist',
3173 'Veterinary Ophthalmologist',
3174 'Veterinary Neurologist'
3175 ])[1 + (abs(hashtext(t.id::text || 'rto')) % 6)]
3176 ELSE NULL
3177 END AS referral_to,
3178
3179 -- follow-up in 7, 14, 21 or 30 days
3180 (ARRAY['7','14','21','30'])
3181 [1 + (abs(hashtext(t.id::text || 'fup')) % 4)] AS follow_up_days,
3182
3183 -- owner present: almost always true
3184 CASE WHEN (abs(hashtext(t.id::text || 'own')) % 10) < 9
3185 THEN 'true' ELSE 'false'
3186 END AS owner_present
3187) v
3188CROSS JOIN LATERAL (
3189 SELECT CASE a.name
3190 WHEN 'topic' THEN v.topic
3191 WHEN 'description' THEN v.description
3192 WHEN 'referral' THEN v.referral
3193 WHEN 'referral_to' THEN v.referral_to
3194 WHEN 'follow_up_days' THEN v.follow_up_days
3195 WHEN 'owner_present' THEN v.owner_present
3196 END AS val
3197) final_val
3198WHERE final_val.val IS NOT NULL;
3199
3200
3201-- ----------------------------------------------------------------
3202-- OPERATION treatment attribute values
3203-- ----------------------------------------------------------------
3204
3205INSERT INTO treatment_attribute_value (value, notes, treatment_attribute_id, treatment_id)
3206SELECT
3207 final_val.val,
3208 NULL,
3209 a.id,
3210 t.id
3211FROM treatment t
3212JOIN treatment_type tt ON tt.id = t.treatment_type_id AND tt.name = 'operation'
3213JOIN treatment_attribute a ON a.treatment_type_id = tt.id
3214CROSS JOIN LATERAL (
3215 SELECT
3216 (ARRAY[
3217 'Ovariohysterectomy (spay)',
3218 'Orchiectomy (neuter)',
3219 'Mass / tumour excision',
3220 'Fracture repair (ORIF)',
3221 'Intestinal foreign body removal',
3222 'Cystotomy (bladder stone removal)',
3223 'Gastropexy',
3224 'Enucleation',
3225 'Amputation',
3226 'Caesarean section',
3227 'Exploratory laparotomy',
3228 'Cruciate ligament repair (TPLO)',
3229 'Dental extraction',
3230 'Wound debridement and closure',
3231 'Thoracostomy tube placement'
3232 ])[1 + (abs(hashtext(t.id::text || 'opt')) % 15)] AS operation_type,
3233
3234 -- status: mostly successful
3235 (ARRAY['Successful','Successful','Successful','Successful',
3236 'Complicated','Unsuccessful'])
3237 [1 + (abs(hashtext(t.id::text || 'sts')) % 6)] AS status,
3238
3239 (ARRAY[
3240 'Propofol induction / Isoflurane maintenance',
3241 'Alfaxalone induction / Isoflurane maintenance',
3242 'Ketamine-Midazolam / Isoflurane maintenance',
3243 'Propofol TIVA',
3244 'Medetomidine-Butorphanol sedation (minor procedure)'
3245 ])[1 + (abs(hashtext(t.id::text || 'ans')) % 5)] AS anesthesia,
3246
3247 -- duration: 15–180 minutes
3248 (15 + (abs(hashtext(t.id::text || 'dur')) % 166))::text AS duration_minutes,
3249
3250 -- checkup date: 7–14 days after treatment
3251 (t.date_treatment + (7 + abs(hashtext(t.id::text || 'chk')) % 8))::text
3252 AS date_checkup,
3253
3254 -- surgeon: reference one of the vets by name (lookup from employee)
3255 (
3256 SELECT e.first_name || ' ' || e.last_name
3257 FROM employee e
3258 WHERE e.role_id = 2
3259 ORDER BY abs(hashtext(t.id::text || 'srg' || e.id::text))
3260 LIMIT 1
3261 ) AS surgeon,
3262
3263 -- complications: ~12%
3264 CASE WHEN (abs(hashtext(t.id::text || 'cmp')) % 100) < 12
3265 THEN 'true' ELSE 'false'
3266 END AS complications
3267) v
3268CROSS JOIN LATERAL (
3269 SELECT CASE a.name
3270 WHEN 'operation_type' THEN v.operation_type
3271 WHEN 'status' THEN v.status
3272 WHEN 'anesthesia' THEN v.anesthesia
3273 WHEN 'duration_minutes' THEN v.duration_minutes
3274 WHEN 'date_checkup' THEN v.date_checkup
3275 WHEN 'surgeon' THEN v.surgeon
3276 WHEN 'complications' THEN v.complications
3277 END AS val
3278) final_val
3279WHERE final_val.val IS NOT NULL;
3280
3281
3282
3283-- temp table for imported medicines
3284create table temp_med1
3285(
3286 id bigserial primary key,
3287 name varchar
3288);
3289
3290-- drop table temp_med1 cascade;
3291
3292COPY temp_med1 (name) FROM 'C:/Program Files/PostgreSQL/18/data/temp_files/medicine.csv' DELIMITER ',' CSV HEADER;
3293UPDATE temp_med1
3294SET name = TRIM(name);
3295
3296
3297-- ============================================================
3298-- STEP 3a — Insert medicines from temp_med1
3299-- Assign a plausible manufacturer and description based on
3300-- the medicine name. shop_item_id is NULL (prescription-only
3301-- or clinic-use; they are not in the shop).
3302-- ============================================================
3303
3304INSERT INTO medicine (name, manufacturer, description, shop_item_id)
3305SELECT
3306 t.name,
3307
3308 -- deterministic but varied manufacturer
3309 (ARRAY[
3310 'Zoetis Inc.',
3311 'Boehringer Ingelheim',
3312 'Elanco Animal Health',
3313 'Virbac Animal Health',
3314 'Dechra Veterinary',
3315 'Norbrook Laboratories',
3316 'Vetoquinol',
3317 'Bayer Animal Health',
3318 'MSD Animal Health',
3319 'Pfizer Animal Health',
3320 'Jurox Animal Health',
3321 'Intervet-Schering Plough',
3322 'Novartis Animal Health',
3323 'Merial',
3324 'Orion Pharma'
3325 ])[1 + (abs(hashtext(t.name || 'mfr')) % 15)] AS manufacturer,
3326
3327 -- plausible description derived from name keywords
3328 CASE
3329 WHEN t.name ILIKE '%amoxicillin%' OR t.name ILIKE '%ampicillin%'
3330 OR t.name ILIKE '%penicillin%'
3331 THEN 'Penicillin-class antibiotic for bacterial infections in companion animals'
3332
3333 WHEN t.name ILIKE '%cefalex%' OR t.name ILIKE '%cephalex%'
3334 OR t.name ILIKE '%cefovecin%' OR t.name ILIKE '%cefpodox%'
3335 OR t.name ILIKE '%cef%'
3336 THEN 'Cephalosporin antibiotic for skin, soft tissue and urinary tract infections'
3337
3338 WHEN t.name ILIKE '%enroflox%' OR t.name ILIKE '%marboflox%'
3339 OR t.name ILIKE '%pradoflox%' OR t.name ILIKE '%ciproflox%'
3340 OR t.name ILIKE '%floxacin%'
3341 THEN 'Fluoroquinolone antibiotic for gram-negative and soft tissue infections'
3342
3343 WHEN t.name ILIKE '%doxycyclin%' OR t.name ILIKE '%tetracyclin%'
3344 OR t.name ILIKE '%minocyclin%'
3345 THEN 'Tetracycline-class antibiotic effective against intracellular and tick-borne pathogens'
3346
3347 WHEN t.name ILIKE '%metronidazol%'
3348 THEN 'Nitroimidazole antibiotic and antiprotozoal for GI and anaerobic infections'
3349
3350 WHEN t.name ILIKE '%clindamycin%' OR t.name ILIKE '%lincomycin%'
3351 THEN 'Lincosamide antibiotic for anaerobic, dental and deep tissue infections'
3352
3353 WHEN t.name ILIKE '%azithromycin%' OR t.name ILIKE '%tylosin%'
3354 OR t.name ILIKE '%erythromycin%'
3355 THEN 'Macrolide antibiotic for respiratory and intracellular bacterial infections'
3356
3357 WHEN t.name ILIKE '%trimethoprim%' OR t.name ILIKE '%sulfa%'
3358 OR t.name ILIKE '%sulfameth%'
3359 THEN 'Sulfonamide combination antibiotic for urinary and respiratory tract infections'
3360
3361 WHEN t.name ILIKE '%vancomycin%' OR t.name ILIKE '%linezolid%'
3362 THEN 'Reserved-use antibiotic for multidrug-resistant gram-positive infections'
3363
3364 WHEN t.name ILIKE '%amikacin%' OR t.name ILIKE '%gentamicin%'
3365 OR t.name ILIKE '%tobramycin%'
3366 THEN 'Aminoglycoside antibiotic for serious gram-negative infections; requires renal monitoring'
3367
3368 WHEN t.name ILIKE '%imipenem%' OR t.name ILIKE '%meropenem%'
3369 OR t.name ILIKE '%ertapenem%'
3370 THEN 'Carbapenem antibiotic reserved for multidrug-resistant bacterial infections'
3371
3372 WHEN t.name ILIKE '%prednisolon%' OR t.name ILIKE '%prednison%'
3373 THEN 'Corticosteroid for inflammatory and immune-mediated conditions'
3374
3375 WHEN t.name ILIKE '%dexamethasone%'
3376 THEN 'Potent corticosteroid for acute inflammatory and allergic reactions'
3377
3378 WHEN t.name ILIKE '%methylprednisolon%'
3379 THEN 'Intermediate-acting corticosteroid for chronic inflammatory disease'
3380
3381 WHEN t.name ILIKE '%hydrocortisone%'
3382 THEN 'Mild corticosteroid for adrenal insufficiency and mild inflammation'
3383
3384 WHEN t.name ILIKE '%betamethasone%' OR t.name ILIKE '%triamcinolone%'
3385 THEN 'Potent long-acting corticosteroid; used topically and intra-articularly'
3386
3387 WHEN t.name ILIKE '%carprofen%' OR t.name ILIKE '%meloxicam%'
3388 OR t.name ILIKE '%robenacoxib%' OR t.name ILIKE '%mavacoxib%'
3389 OR t.name ILIKE '%grapiprant%'
3390 THEN 'NSAID for pain and inflammation in musculoskeletal and post-operative conditions'
3391
3392 WHEN t.name ILIKE '%tramadol%'
3393 THEN 'Opioid analgesic for moderate to severe pain management'
3394
3395 WHEN t.name ILIKE '%buprenorphin%'
3396 THEN 'Partial mu-opioid agonist for perioperative and chronic pain'
3397
3398 WHEN t.name ILIKE '%fentanyl%' OR t.name ILIKE '%methadon%'
3399 OR t.name ILIKE '%morphin%'
3400 THEN 'Full opioid agonist for perioperative and severe acute pain'
3401
3402 WHEN t.name ILIKE '%gabapentin%' OR t.name ILIKE '%pregabalin%'
3403 THEN 'Anticonvulsant and analgesic for neuropathic pain and seizure management'
3404
3405 WHEN t.name ILIKE '%phenobarbit%'
3406 THEN 'Barbiturate anticonvulsant for idiopathic epilepsy in dogs and cats'
3407
3408 WHEN t.name ILIKE '%levetiracetam%'
3409 THEN 'Novel anticonvulsant with a favourable safety profile for refractory epilepsy'
3410
3411 WHEN t.name ILIKE '%potassium bromide%'
3412 THEN 'Adjunctive anticonvulsant for dogs with refractory epilepsy'
3413
3414 WHEN t.name ILIKE '%omeprazol%' OR t.name ILIKE '%pantoprazol%'
3415 OR t.name ILIKE '%esomeprazol%'
3416 THEN 'Proton pump inhibitor for gastric ulcer prevention and acid reflux'
3417
3418 WHEN t.name ILIKE '%famotidin%' OR t.name ILIKE '%ranitidine%'
3419 THEN 'H2 receptor blocker for gastric hyperacidity and stress ulceration'
3420
3421 WHEN t.name ILIKE '%maropitant%'
3422 THEN 'NK1 receptor antagonist antiemetic for vomiting and motion sickness'
3423
3424 WHEN t.name ILIKE '%ondansetron%' OR t.name ILIKE '%dolasetron%'
3425 THEN 'Serotonin antagonist antiemetic for chemotherapy-induced and refractory nausea'
3426
3427 WHEN t.name ILIKE '%metoclopramide%'
3428 THEN 'Prokinetic antiemetic for gastric motility disorders and vomiting'
3429
3430 WHEN t.name ILIKE '%sucralfate%'
3431 THEN 'Mucosal protectant for gastric and duodenal ulcers'
3432
3433 WHEN t.name ILIKE '%lactulose%'
3434 THEN 'Osmotic laxative for hepatic encephalopathy and chronic constipation'
3435
3436 WHEN t.name ILIKE '%furosemide%' OR t.name ILIKE '%torsemide%'
3437 THEN 'Loop diuretic for congestive heart failure and oedema management'
3438
3439 WHEN t.name ILIKE '%spironolacton%'
3440 THEN 'Potassium-sparing diuretic for cardiac and hepatic disease'
3441
3442 WHEN t.name ILIKE '%enalapril%' OR t.name ILIKE '%benazepril%'
3443 OR t.name ILIKE '%ramipril%' OR t.name ILIKE '%lisinopril%'
3444 THEN 'ACE inhibitor for hypertension and congestive heart failure'
3445
3446 WHEN t.name ILIKE '%telmisartan%' OR t.name ILIKE '%losartan%'
3447 THEN 'Angiotensin II receptor blocker for hypertension and CKD proteinuria'
3448
3449 WHEN t.name ILIKE '%amlodipine%' OR t.name ILIKE '%diltiazem%'
3450 THEN 'Calcium channel blocker for hypertension and hypertrophic cardiomyopathy'
3451
3452 WHEN t.name ILIKE '%atenolol%' OR t.name ILIKE '%metoprolol%'
3453 OR t.name ILIKE '%propranolol%'
3454 THEN 'Beta-blocker for arrhythmias and hypertrophic cardiomyopathy'
3455
3456 WHEN t.name ILIKE '%pimobendan%'
3457 THEN 'Inodilator for dilated cardiomyopathy and mitral valve disease'
3458
3459 WHEN t.name ILIKE '%digoxin%'
3460 THEN 'Cardiac glycoside for atrial fibrillation and congestive heart failure'
3461
3462 WHEN t.name ILIKE '%sildenafil%'
3463 THEN 'PDE-5 inhibitor for pulmonary arterial hypertension'
3464
3465 WHEN t.name ILIKE '%heparin%' OR t.name ILIKE '%clopidogrel%'
3466 THEN 'Anticoagulant / antiplatelet for thromboembolism prevention'
3467
3468 WHEN t.name ILIKE '%levothyroxin%'
3469 THEN 'Thyroid hormone replacement for canine hypothyroidism'
3470
3471 WHEN t.name ILIKE '%methimazol%' OR t.name ILIKE '%carbimazol%'
3472 THEN 'Thioamide antithyroid agent for feline hyperthyroidism'
3473
3474 WHEN t.name ILIKE '%trilostane%'
3475 THEN '3beta-HSD inhibitor for hyperadrenocorticism (Cushing disease)'
3476
3477 WHEN t.name ILIKE '%mitotane%'
3478 THEN 'Adrenocorticolytic agent for pituitary-dependent hyperadrenocorticism'
3479
3480 WHEN t.name ILIKE '%insulin%'
3481 THEN 'Insulin preparation for the management of diabetes mellitus'
3482
3483 WHEN t.name ILIKE '%cyclosporin%' OR t.name ILIKE '%tacrolimus%'
3484 THEN 'Calcineurin inhibitor immunosuppressant for immune-mediated disease'
3485
3486 WHEN t.name ILIKE '%apoquel%' OR t.name ILIKE '%oclacitinib%'
3487 THEN 'JAK inhibitor for pruritus and allergic dermatitis in dogs'
3488
3489 WHEN t.name ILIKE '%hydroxyzin%' OR t.name ILIKE '%diphenhydramin%'
3490 OR t.name ILIKE '%chlorphenamin%'
3491 THEN 'Antihistamine for pruritic skin disease and allergic reactions'
3492
3493 WHEN t.name ILIKE '%ketoconazol%' OR t.name ILIKE '%fluconazol%'
3494 OR t.name ILIKE '%itraconazol%' OR t.name ILIKE '%voriconazol%'
3495 OR t.name ILIKE '%terbinafin%'
3496 THEN 'Azole or allylamine antifungal for dermatophytosis and systemic mycoses'
3497
3498 WHEN t.name ILIKE '%amphotericin%'
3499 THEN 'Polyene antifungal for systemic mycoses; nephrotoxicity monitoring required'
3500
3501 WHEN t.name ILIKE '%fenbendazol%' OR t.name ILIKE '%mebendazol%'
3502 OR t.name ILIKE '%albendazol%'
3503 THEN 'Benzimidazole anthelmintic for roundworms, hookworms and Giardia'
3504
3505 WHEN t.name ILIKE '%praziquantel%'
3506 THEN 'Cestocidal agent for tapeworm infections in companion animals'
3507
3508 WHEN t.name ILIKE '%ivermectin%' OR t.name ILIKE '%milbemycin%'
3509 OR t.name ILIKE '%moxidectin%' OR t.name ILIKE '%selamectin%'
3510 THEN 'Macrocyclic lactone for internal and external parasite control'
3511
3512 WHEN t.name ILIKE '%pyrantel%'
3513 THEN 'Anthelmintic for roundworm and hookworm infections'
3514
3515 WHEN t.name ILIKE '%propofol%'
3516 THEN 'Intravenous induction agent for general anaesthesia; rapid onset and recovery'
3517
3518 WHEN t.name ILIKE '%alfaxalon%'
3519 THEN 'Neurosteroid anaesthetic for induction and TIVA in cats and dogs'
3520
3521 WHEN t.name ILIKE '%ketamin%'
3522 THEN 'Dissociative anaesthetic used in combination sedation and anaesthetic protocols'
3523
3524 WHEN t.name ILIKE '%midazolam%' OR t.name ILIKE '%diazepam%'
3525 THEN 'Benzodiazepine for sedation, co-induction and status epilepticus management'
3526
3527 WHEN t.name ILIKE '%medetomidin%' OR t.name ILIKE '%dexmedetomidin%'
3528 THEN 'Alpha-2 adrenergic agonist for sedation and pre-anaesthetic medication'
3529
3530 WHEN t.name ILIKE '%atropin%'
3531 THEN 'Anticholinergic for bradycardia, organophosphate toxicosis and pre-anaesthesia'
3532
3533 WHEN t.name ILIKE '%vitamin%' OR t.name ILIKE '%b12%'
3534 OR t.name ILIKE '%cobalamin%'
3535 THEN 'Vitamin supplement for deficiency states and supportive therapy'
3536
3537 WHEN t.name ILIKE '%iron%' OR t.name ILIKE '%ferrous%'
3538 THEN 'Iron supplement for iron-deficiency anaemia'
3539
3540 WHEN t.name ILIKE '%calcium%'
3541 THEN 'Calcium supplementation for hypocalcaemia and eclampsia'
3542
3543 WHEN t.name ILIKE '%saline%' OR t.name ILIKE '%sodium chloride%'
3544 OR t.name ILIKE '%flush%'
3545 THEN 'Sterile isotonic saline for fluid therapy and catheter flushing'
3546
3547 WHEN t.name ILIKE '%mannitol%'
3548 THEN 'Osmotic diuretic for cerebral oedema and acute angle-closure glaucoma'
3549
3550 WHEN t.name ILIKE '%dextrose%' OR t.name ILIKE '%glucose%'
3551 THEN 'Concentrated glucose solution for hypoglycaemia; dilute before intravenous use'
3552
3553 WHEN t.name ILIKE '%dopamine%' OR t.name ILIKE '%dobutamine%'
3554 OR t.name ILIKE '%norepinephrin%' OR t.name ILIKE '%epinephrin%'
3555 OR t.name ILIKE '%adrenalin%'
3556 THEN 'Catecholamine vasopressor / inotrope for shock and acute haemodynamic instability'
3557
3558 WHEN t.name ILIKE '%plasma%' OR t.name ILIKE '%albumin%'
3559 THEN 'Blood product for coagulopathy, hypoproteinaemia and volume replacement'
3560
3561 WHEN t.name ILIKE '%hydroxyethyl%' OR t.name ILIKE '%hetastarch%'
3562 OR t.name ILIKE '%gelatin%'
3563 THEN 'Colloid volume expander for hypovolaemia and hypoproteinaemia'
3564
3565 WHEN t.name ILIKE '%misoprostol%'
3566 THEN 'Prostaglandin E1 analogue for GI mucosal protection during NSAID therapy'
3567
3568 WHEN t.name ILIKE '%ursodiol%' OR t.name ILIKE '%ursodeoxycholic%'
3569 THEN 'Bile acid for cholelithiasis and chronic hepatitis management'
3570
3571 WHEN t.name ILIKE '%acetylcysteine%'
3572 THEN 'Mucolytic and antidote for paracetamol toxicosis in cats'
3573
3574 ELSE 'Veterinary pharmaceutical for use in companion animals; see datasheet for full indication'
3575 END AS description,
3576
3577 NULL AS shop_item_id -- all imported medicines are not shop-linked
3578
3579FROM temp_med1 t
3580-- skip any that are already in medicine by name (case-insensitive)
3581WHERE NOT EXISTS (
3582 SELECT 1 FROM medicine m
3583 WHERE lower(trim(m.name)) = lower(trim(t.name))
3584);
3585
3586
3587-- ============================================================
3588-- STEP 3b — Bulk prescription_medicine using generate_series
3589-- Goal: simulate historical records reaching millions of rows.
3590--
3591-- Strategy:
3592-- • Generate a large set of (prescription, medicine) pairs
3593-- using generate_series to multiply existing prescriptions.
3594-- • Each prescription gets up to 6 medicines.
3595-- • We use hashtext for deterministic medicine assignment
3596-- so re-runs are idempotent with ON CONFLICT DO NOTHING.
3597-- ============================================================
3598
3599WITH
3600-- total medicines available
3601med_count AS (
3602 SELECT count(*) AS total FROM medicine
3603),
3604
3605-- total prescriptions
3606presc_count AS (
3607 SELECT count(*) AS total FROM prescription
3608),
3609
3610-- cross prescriptions with slots 1-6
3611slots AS (
3612 SELECT
3613 p.id AS prescription_id,
3614 gs.slot
3615 FROM prescription p
3616 CROSS JOIN generate_series(1, 6) AS gs(slot)
3617),
3618
3619-- assign a medicine to each slot deterministically
3620assigned AS (
3621 SELECT
3622 s.prescription_id,
3623 s.slot,
3624 (
3625 SELECT m.id
3626 FROM medicine m
3627 WHERE m.id = (
3628 (abs(hashtext(s.prescription_id::text || '-slot-' || s.slot::text))
3629 % mc.total) + 1
3630 )
3631 LIMIT 1
3632 ) AS medicine_id,
3633 -- dosage: 1, 2 or 3 times daily
3634 CASE (abs(hashtext(s.prescription_id::text || '-dos-' || s.slot::text)) % 3)
3635 WHEN 0 THEN 1
3636 WHEN 1 THEN 2
3637 ELSE 3
3638 END AS dosage,
3639 -- duration: 3 to 28 days
3640 3 + (abs(hashtext(s.prescription_id::text || '-day-' || s.slot::text)) % 26)
3641 AS num_days
3642 FROM slots s
3643 CROSS JOIN med_count mc
3644),
3645
3646-- medicine IDs may not be perfectly contiguous; resolve via row_number
3647med_ranked AS (
3648 SELECT id, row_number() OVER (ORDER BY id) AS rn
3649 FROM medicine
3650),
3651
3652assigned_resolved AS (
3653 SELECT
3654 a.prescription_id,
3655 a.slot,
3656 mr.id AS medicine_id,
3657 a.dosage,
3658 a.num_days
3659 FROM assigned a
3660 CROSS JOIN med_count mc
3661 JOIN med_ranked mr
3662 ON mr.rn = (abs(hashtext(a.prescription_id::text || '-slot-' || a.slot::text)) % mc.total) + 1
3663),
3664
3665-- keep only slots within the per-prescription medicine count (2-6)
3666wanted AS (
3667 SELECT
3668 ar.prescription_id,
3669 ar.slot,
3670 ar.medicine_id,
3671 ar.dosage,
3672 ar.num_days,
3673 -- how many medicines does this prescription want?
3674 2 + (abs(hashtext(ar.prescription_id::text || '-cnt')) % 5) AS want_count
3675 FROM assigned_resolved ar
3676),
3677
3678filtered AS (
3679 SELECT prescription_id, medicine_id, dosage, num_days
3680 FROM wanted
3681 WHERE slot <= want_count
3682),
3683
3684-- deduplicate: if the same medicine appears twice in one prescription keep first
3685deduped AS (
3686 SELECT DISTINCT ON (prescription_id, medicine_id)
3687 prescription_id,
3688 medicine_id,
3689 dosage,
3690 num_days
3691 FROM filtered
3692 ORDER BY prescription_id, medicine_id
3693)
3694
3695INSERT INTO prescription_medicine (prescription_id, medicine_id, dosage, num_days)
3696SELECT prescription_id, medicine_id, dosage, num_days
3697FROM deduped
3698ON CONFLICT (prescription_id, medicine_id) DO NOTHING;
3699
3700
3701-- ============================================================
3702-- STEP 3c — Historical bulk expansion
3703-- Simulate several years of past prescriptions by generating
3704-- synthetic prescription_id × medicine_id pairs via
3705-- generate_series, without needing real prescription rows.
3706--
3707-- We create extra prescription rows tied to existing examinations,
3708-- then fill prescription_medicine for them.
3709-- This is the cleanest way to reach millions of rows while
3710-- keeping referential integrity.
3711-- ============================================================
3712
3713-- First: generate a large batch of additional prescriptions
3714-- linked to completed examinations (one extra prescription
3715-- per examination per "historical year", 3 extra years back).
3716
3717INSERT INTO prescription (examination_id, date_start, date_end, description)
3718SELECT
3719 sub.examination_id,
3720 sub.date_start,
3721 sub.date_end,
3722 sub.description
3723FROM (
3724 SELECT
3725 e.id AS examination_id,
3726 (e.date_examination - (yr.y * interval '1 year'))::date AS date_start,
3727 (e.date_examination - (yr.y * interval '1 year')
3728 + (7 + (abs(hashtext(e.id::text || yr.y::text)) % 21)) * interval '1 day'
3729 )::date AS date_end,
3730 (ARRAY[
3731 'Historical prescription record. Long-term medication course.',
3732 'Repeat prescription issued for ongoing condition management.',
3733 'Annual medication renewal following routine examination.',
3734 'Prescription reissued; owner reported good compliance.',
3735 'Maintenance therapy continued from previous year.',
3736 'Chronic condition management; dosage reviewed and maintained.',
3737 'Preventive medication course prescribed at annual check.',
3738 'Medication course extended following positive clinical response.'
3739 ])[1 + (abs(hashtext(e.id::text || yr.y::text)) % 8)] AS description
3740 FROM examination e
3741 CROSS JOIN (VALUES (1),(2),(3)) AS yr(y)
3742 WHERE e.status = 'completed'
3743) sub
3744-- prescription has a UNIQUE constraint on examination_id,
3745-- so we can only have one prescription per examination.
3746-- Instead we'll use a workaround: generate series on medicines directly.
3747WHERE false; -- intentionally inserting 0 rows here; see note below
3748
3749-- NOTE: Because prescription has UNIQUE(examination_id), we cannot
3750-- add multiple prescriptions per examination. The volume therefore
3751-- comes from increasing medicines per prescription (up to 6 above)
3752-- combined with the full prescription set.
3753--
3754-- To genuinely reach millions of rows we expand via generate_series
3755-- on the medicine dimension: assign up to 20 medicines per prescription
3756-- using a larger series, relying on ON CONFLICT DO NOTHING to skip
3757-- already-inserted pairs.
3758
3759WITH
3760med_count AS (SELECT count(*) AS total FROM medicine),
3761med_ranked AS (
3762 SELECT id, row_number() OVER (ORDER BY id) AS rn FROM medicine
3763),
3764extra_slots AS (
3765 SELECT
3766 p.id AS prescription_id,
3767 gs.slot
3768 FROM prescription p
3769 CROSS JOIN generate_series(7, 20) AS gs(slot)
3770),
3771extra_assigned AS (
3772 SELECT
3773 es.prescription_id,
3774 mr.id AS medicine_id,
3775 1 + (abs(hashtext(es.prescription_id::text || '-edos-' || es.slot::text)) % 3) AS dosage,
3776 3 + (abs(hashtext(es.prescription_id::text || '-eday-' || es.slot::text)) % 26) AS num_days,
3777 -- only keep this slot if the prescription "wants" this many medicines
3778 -- (want_count now drawn from 7-20 range for the extra batch)
3779 7 + (abs(hashtext(es.prescription_id::text || '-ecnt')) % 14) AS want_count,
3780 es.slot
3781 FROM extra_slots es
3782 CROSS JOIN med_count mc
3783 JOIN med_ranked mr
3784 ON mr.rn = (abs(hashtext(es.prescription_id::text || '-eslot-' || es.slot::text)) % mc.total) + 1
3785),
3786extra_filtered AS (
3787 SELECT prescription_id, medicine_id, dosage, num_days
3788 FROM extra_assigned
3789 WHERE slot <= want_count
3790),
3791extra_deduped AS (
3792 SELECT DISTINCT ON (prescription_id, medicine_id)
3793 prescription_id, medicine_id, dosage, num_days
3794 FROM extra_filtered
3795 ORDER BY prescription_id, medicine_id
3796)
3797INSERT INTO prescription_medicine (prescription_id, medicine_id, dosage, num_days)
3798SELECT prescription_id, medicine_id, dosage, num_days
3799FROM extra_deduped
3800ON CONFLICT (prescription_id, medicine_id) DO NOTHING;
3801
3802
3803-- ============================================================
3804-- EXPAND MEDICINE TABLE
3805-- Combines drug bases x strengths x forms
3806-- Skips any name already in medicine (case-insensitive).
3807-- ============================================================
3808
3809WITH drug_bases AS (
3810 SELECT unnest(ARRAY[
3811 -- Antibiotics
3812 'Amoxicillin', 'Amoxicillin-Clavulanate', 'Ampicillin',
3813 'Cephalexin', 'Cefpodoxime', 'Cefovecin', 'Cefazolin',
3814 'Enrofloxacin', 'Marbofloxacin', 'Pradofloxacin', 'Orbifloxacin',
3815 'Doxycycline', 'Tetracycline', 'Minocycline',
3816 'Metronidazole', 'Tinidazole', 'Ronidazole',
3817 'Clindamycin', 'Lincomycin',
3818 'Azithromycin', 'Tylosin', 'Erythromycin',
3819 'Trimethoprim-Sulfamethoxazole', 'Trimethoprim-Sulfadiazine',
3820 'Chloramphenicol', 'Rifampicin',
3821 'Amikacin', 'Gentamicin', 'Tobramycin',
3822 'Imipenem-Cilastatin', 'Meropenem',
3823 'Vancomycin', 'Linezolid',
3824 -- NSAIDs & Analgesics
3825 'Carprofen', 'Meloxicam', 'Robenacoxib', 'Mavacoxib',
3826 'Grapiprant', 'Ketoprofen', 'Tolfenamic Acid',
3827 'Tramadol', 'Buprenorphine', 'Methadone', 'Morphine',
3828 'Fentanyl', 'Butorphanol', 'Nalbuphine',
3829 'Gabapentin', 'Pregabalin', 'Amantadine',
3830 -- Corticosteroids
3831 'Prednisolone', 'Prednisone', 'Dexamethasone',
3832 'Methylprednisolone', 'Hydrocortisone', 'Betamethasone',
3833 'Triamcinolone', 'Budesonide', 'Fluticasone',
3834 -- Cardiac
3835 'Furosemide', 'Torsemide', 'Spironolactone',
3836 'Enalapril', 'Benazepril', 'Ramipril', 'Lisinopril',
3837 'Telmisartan', 'Amlodipine', 'Diltiazem',
3838 'Atenolol', 'Metoprolol', 'Propranolol', 'Sotalol',
3839 'Pimobendan', 'Digoxin', 'Sildenafil', 'Clopidogrel',
3840 'Heparin', 'Warfarin',
3841 -- GI
3842 'Omeprazole', 'Pantoprazole', 'Esomeprazole',
3843 'Famotidine', 'Ranitidine', 'Sucralfate',
3844 'Metoclopramide', 'Cisapride', 'Ondansetron',
3845 'Maropitant', 'Dolasetron', 'Prochlorperazine',
3846 'Lactulose', 'Loperamide', 'Bismuth Subsalicylate',
3847 'Misoprostol', 'Ursodiol',
3848 -- Anticonvulsants
3849 'Phenobarbital', 'Potassium Bromide', 'Levetiracetam',
3850 'Zonisamide', 'Gabapentin', 'Pregabalin', 'Imepitoin',
3851 -- Endocrine
3852 'Levothyroxine', 'Methimazole', 'Carbimazole',
3853 'Trilostane', 'Mitotane', 'Insulin Glargine',
3854 'Insulin NPH', 'Cabergoline', 'Deslorelin',
3855 -- Immunosuppressants / Dermatology
3856 'Cyclosporine', 'Tacrolimus', 'Azathioprine',
3857 'Mycophenolate Mofetil', 'Chlorambucil',
3858 'Oclacitinib', 'Lokivetmab',
3859 'Hydroxyzine', 'Diphenhydramine', 'Chlorphenamine',
3860 'Cetirizine', 'Loratadine',
3861 -- Antifungals
3862 'Ketoconazole', 'Fluconazole', 'Itraconazole',
3863 'Voriconazole', 'Terbinafine', 'Amphotericin B',
3864 'Griseofulvin', 'Clotrimazole', 'Miconazole',
3865 -- Antiparasitics
3866 'Fenbendazole', 'Mebendazole', 'Albendazole',
3867 'Praziquantel', 'Epsiprantel',
3868 'Ivermectin', 'Milbemycin Oxime', 'Moxidectin',
3869 'Selamectin', 'Pyrantel Pamoate', 'Nitenpyram',
3870 'Afoxolaner', 'Fluralaner', 'Sarolaner', 'Lotilaner',
3871 -- Anaesthetics / Sedatives
3872 'Propofol', 'Alfaxalone', 'Ketamine', 'Tiletamine',
3873 'Midazolam', 'Diazepam', 'Zolazepam',
3874 'Medetomidine', 'Dexmedetomidine', 'Romifidine',
3875 'Atropine', 'Glycopyrrolate',
3876 'Isoflurane', 'Sevoflurane',
3877 -- Fluids / Electrolytes
3878 'Sodium Chloride 0.9%', 'Lactated Ringers Solution',
3879 'Hartmann Solution', 'Dextrose 5%', 'Dextrose 50%',
3880 'Potassium Chloride', 'Sodium Bicarbonate',
3881 'Calcium Gluconate', 'Mannitol 20%',
3882 'Hydroxyethyl Starch 6%', 'Gelatin 4%',
3883 -- Supportive / Other
3884 'Acetylcysteine', 'S-Adenosylmethionine', 'Silymarin',
3885 'Vitamin B12', 'Iron Dextran', 'Folic Acid',
3886 'Vitamin K1', 'Doxapram', 'Atipamezole',
3887 'Naloxone', 'Flumazenil', 'Pralidoxime',
3888 'Dopamine', 'Dobutamine', 'Norepinephrine',
3889 'Epinephrine', 'Vasopressin',
3890 'Allopurinol', 'Colchicine', 'Pentoxifylline',
3891 'Pilocarpine', 'Dorzolamide', 'Latanoprost'
3892 ]) AS base
3893),
3894
3895strengths AS (
3896 SELECT unnest(ARRAY[
3897 '2.5mg', '5mg', '10mg', '12.5mg', '20mg', '25mg',
3898 '30mg', '40mg', '50mg', '75mg', '100mg', '125mg',
3899 '150mg', '200mg', '250mg', '300mg', '400mg', '500mg',
3900 '600mg', '750mg', '1g',
3901 '0.5mg/ml', '1mg/ml', '2mg/ml', '2.5mg/ml', '4mg/ml',
3902 '5mg/ml', '10mg/ml', '20mg/ml', '50mg/ml', '100mg/ml',
3903 '1.5mg/ml', '0.1mg/ml', '0.3mg/ml', '0.5%', '1%', '2%',
3904 '2.27mg', '3.6mg', '16mg', '68mg', '136mg'
3905 ]) AS strength
3906),
3907
3908forms AS (
3909 SELECT unnest(ARRAY[
3910 'Tablets', 'Chewable Tablets', 'Film-Coated Tablets',
3911 'Capsules', 'Oral Solution', 'Oral Suspension',
3912 'Injection', 'Lyophilisate for Injection',
3913 'Spot-On Solution', 'Transdermal Gel',
3914 'Ear Drops', 'Eye Drops', 'Ophthalmic Ointment',
3915 'Powder for Oral Solution', 'Granules',
3916 'Prolonged-Release Tablets', 'Soft Chews'
3917 ]) AS form
3918),
3919
3920-- not all base+strength+form combos make sense;
3921-- filter to plausible combinations
3922plausible AS (
3923 SELECT
3924 b.base || ' ' || s.strength || ' ' || f.form AS med_name,
3925 -- assign manufacturer
3926 (ARRAY[
3927 'Zoetis Inc.', 'Boehringer Ingelheim', 'Elanco Animal Health',
3928 'Virbac Animal Health', 'Dechra Veterinary', 'Norbrook Laboratories',
3929 'Vetoquinol', 'Bayer Animal Health', 'MSD Animal Health',
3930 'Pfizer Animal Health', 'Jurox Animal Health',
3931 'Intervet-Schering Plough', 'Novartis Animal Health',
3932 'Merial', 'Orion Pharma'
3933 ])[1 + (abs(hashtext(b.base || s.strength || f.form)) % 15)] AS manufacturer
3934 FROM drug_bases b
3935 CROSS JOIN strengths s
3936 CROSS JOIN forms f
3937 WHERE
3938 -- injections go with mg/ml or % strengths
3939 (f.form IN ('Injection','Lyophilisate for Injection',
3940 'Eye Drops','Ear Drops','Spot-On Solution',
3941 'Transdermal Gel','Ophthalmic Ointment')
3942 AND (s.strength ~ 'mg/ml' OR s.strength ~ '%' OR s.strength ~ 'mg$'))
3943 OR
3944 -- oral solids go with mg or g strengths
3945 (f.form IN ('Tablets','Chewable Tablets','Film-Coated Tablets',
3946 'Prolonged-Release Tablets','Capsules',
3947 'Powder for Oral Solution','Granules',
3948 'Soft Chews')
3949 AND s.strength ~ '^[0-9]' AND s.strength NOT LIKE '%ml%'
3950 AND s.strength NOT LIKE '%%')
3951 OR
3952 -- liquids go with mg/ml or %
3953 (f.form IN ('Oral Solution','Oral Suspension')
3954 AND (s.strength ~ 'mg/ml' OR s.strength ~ '%'
3955 OR (s.strength ~ '^[0-9]' AND s.strength NOT LIKE '%ml%')))
3956 -- exclude nonsensical size+form pairs (very large mg in eye drops etc.)
3957 AND NOT (f.form IN ('Eye Drops','Ear Drops')
3958 AND s.strength IN ('500mg','750mg','1g','600mg','400mg',
3959 '300mg','250mg','200mg','150mg'))
3960 AND NOT (f.form = 'Spot-On Solution'
3961 AND s.strength NOT IN ('2.27mg','3.6mg','16mg','68mg','136mg',
3962 '0.5%','1%','2%','10mg/ml','50mg/ml'))
3963),
3964
3965-- cap at 5000 new medicines
3966ranked AS (
3967 SELECT
3968 med_name,
3969 manufacturer,
3970 row_number() OVER (ORDER BY md5(med_name)) AS rn
3971 FROM plausible
3972)
3973
3974INSERT INTO medicine (name, manufacturer, description, shop_item_id)
3975SELECT
3976 r.med_name,
3977 r.manufacturer,
3978 'Veterinary pharmaceutical for use in companion animals; see datasheet for full indication.' AS description,
3979 NULL
3980FROM ranked r
3981WHERE r.rn <= 5000
3982 AND NOT EXISTS (
3983 SELECT 1 FROM medicine m
3984 WHERE lower(trim(m.name)) = lower(trim(r.med_name))
3985 );
3986
3987
3988
3989-- ============================================================
3990-- More addresses — add 1600 more (for no duplicates)
3991-- ============================================================
3992
3993TRUNCATE temp_addresses;
3994
3995COPY temp_addresses (address, city, state, zip)
3996FROM 'C:/Program Files/PostgreSQL/18/data/temp_files/addresses.csv'
3997DELIMITER ',' CSV HEADER;
3998
3999UPDATE temp_addresses SET address = TRIM(address);
4000UPDATE temp_addresses SET city = TRIM(city);
4001UPDATE temp_addresses SET state = TRIM(state);
4002UPDATE temp_addresses SET zip = TRIM(zip);
4003
4004-- ============================================================
4005-- More owners — add 1600 more (total ~2000)
4006-- ============================================================
4007
4008WITH male_names_sample AS (
4009 SELECT name FROM temp_male_names ORDER BY random() LIMIT 5000
4010),
4011female_names_sample AS (
4012 SELECT name FROM temp_female_names ORDER BY random() LIMIT 5000
4013),
4014male_pool AS (
4015 SELECT fn.name AS first_name, ln.surname AS last_name, 'M' AS gender
4016 FROM male_names_sample fn
4017 CROSS JOIN temp_surnames ln
4018 ORDER BY random()
4019 LIMIT 800
4020),
4021female_pool AS (
4022 SELECT fn.name AS first_name, ln.surname AS last_name, 'F' AS gender
4023 FROM female_names_sample fn
4024 CROSS JOIN temp_surnames ln
4025 ORDER BY random()
4026 LIMIT 800
4027),
4028all_names AS (
4029 SELECT first_name, last_name, gender FROM male_pool
4030 UNION ALL
4031 SELECT first_name, last_name, gender FROM female_pool
4032),
4033numbered AS (
4034 SELECT first_name, last_name, gender,
4035 row_number() OVER (ORDER BY random()) AS rn
4036 FROM all_names
4037),
4038addr_count AS (
4039 SELECT count(*) AS cnt FROM temp_addresses
4040),
4041addr_ranked AS (
4042 SELECT
4043 address || ', ' || city || ', ' || state || ' ' || zip AS full_address,
4044 row_number() OVER (ORDER BY id) AS rn
4045 FROM temp_addresses
4046),
4047shuffled_phones AS (
4048 SELECT
4049 ((row_number() OVER (ORDER BY random()) % 9) + 1)::text AS d1,
4050 lpad(((row_number() OVER (ORDER BY random()) * 73) % 900 + 100)::text, 3, '0') AS d2,
4051 lpad(((row_number() OVER (ORDER BY random()) * 97) % 900 + 100)::text, 3, '0') AS d3,
4052 row_number() OVER (ORDER BY random()) AS rn
4053 FROM generate_series(1, 1600)
4054)
4055INSERT INTO owner (first_name, last_name, phone, email, address, gender)
4056SELECT
4057 n.first_name,
4058 n.last_name,
4059 '+389 7' || p.d1 || ' ' || p.d2 || ' ' || p.d3 AS phone,
4060 lower(n.first_name) || '.' || lower(n.last_name)
4061 || n.rn::text || '@gmail.com' AS email,
4062 ar.full_address AS address,
4063 n.gender
4064FROM numbered n
4065JOIN shuffled_phones p ON p.rn = n.rn
4066CROSS JOIN addr_count ac
4067JOIN addr_ranked ar
4068 ON ar.rn = ((n.rn - 1) % ac.cnt) + 1
4069LIMIT 1600;
4070
4071-- ============================================================
4072-- MORE PETS — ~3 pets per new owner on average (~5000 more)
4073-- ============================================================
4074
4075WITH new_owners AS (
4076 -- owners that have no pets yet
4077 SELECT o.id
4078 FROM owner o
4079 LEFT JOIN pet p ON p.owner_id = o.id
4080 WHERE p.id IS NULL
4081),
4082breed_counts AS (
4083 SELECT
4084 (SELECT count(*) FROM temp_breeds_mammal) AS mammal_cnt,
4085 (SELECT count(*) FROM temp_breeds_bird) AS bird_cnt,
4086 (SELECT count(*) FROM temp_breeds_fish) AS fish_cnt,
4087 (SELECT count(*) FROM temp_breeds_reptile) AS reptile_cnt,
4088 (SELECT count(*) FROM temp_breeds_amphibian) AS amphibian_cnt,
4089 (SELECT count(*) FROM temp_pet_names) AS names_cnt,
4090 (SELECT count(*) FROM temp_medical_history) AS history_cnt
4091),
4092-- expand: each owner gets 2-4 pets
4093owner_slots AS (
4094 SELECT
4095 o.id AS owner_id,
4096 gs.slot
4097 FROM new_owners o
4098 CROSS JOIN generate_series(1, 4) AS gs(slot)
4099 -- keep slot if <= random per-owner pet count (2-4)
4100 WHERE gs.slot <= 2 + (abs(hashtext(o.id::text || 'petcnt')) % 3)
4101),
4102typed AS (
4103 SELECT
4104 os.owner_id,
4105 os.slot,
4106 CASE (abs(hashtext(os.owner_id::text || os.slot::text || 'type')) % 10)
4107 WHEN 0 THEN 'mammal'
4108 WHEN 1 THEN 'mammal'
4109 WHEN 2 THEN 'mammal'
4110 WHEN 3 THEN 'mammal'
4111 WHEN 4 THEN 'mammal'
4112 WHEN 5 THEN 'mammal'
4113 WHEN 6 THEN 'bird'
4114 WHEN 7 THEN 'reptile'
4115 WHEN 8 THEN 'fish'
4116 ELSE 'amphibian'
4117 END AS type
4118 FROM owner_slots os
4119),
4120names_ranked AS (
4121 SELECT name, row_number() OVER (ORDER BY id) AS rn FROM temp_pet_names
4122),
4123history_ranked AS (
4124 SELECT history, row_number() OVER (ORDER BY id) AS rn FROM temp_medical_history
4125),
4126mammal_ranked AS (SELECT breed_name, row_number() OVER (ORDER BY id) AS rn FROM temp_breeds_mammal),
4127bird_ranked AS (SELECT breed_name, row_number() OVER (ORDER BY id) AS rn FROM temp_breeds_bird),
4128fish_ranked AS (SELECT breed_name, row_number() OVER (ORDER BY id) AS rn FROM temp_breeds_fish),
4129reptile_ranked AS (SELECT breed_name, row_number() OVER (ORDER BY id) AS rn FROM temp_breeds_reptile),
4130amphibian_ranked AS (SELECT breed_name, row_number() OVER (ORDER BY id) AS rn FROM temp_breeds_amphibian)
4131
4132INSERT INTO pet (name, is_active, type, breed, age, medical_history, owner_id)
4133SELECT
4134 nr.name,
4135 (abs(hashtext(t.owner_id::text || t.slot::text || 'active')) % 10 < 9) AS is_active,
4136 t.type,
4137 CASE t.type
4138 WHEN 'mammal' THEN mbr.breed_name
4139 WHEN 'bird' THEN bbr.breed_name
4140 WHEN 'fish' THEN fbr.breed_name
4141 WHEN 'reptile' THEN rbr.breed_name
4142 WHEN 'amphibian' THEN abr.breed_name
4143 END AS breed,
4144 CASE t.type
4145 WHEN 'mammal' THEN (abs(hashtext(t.owner_id::text || t.slot::text || 'age')) % 240)
4146 WHEN 'bird' THEN (abs(hashtext(t.owner_id::text || t.slot::text || 'age')) % 720)
4147 WHEN 'reptile' THEN (abs(hashtext(t.owner_id::text || t.slot::text || 'age')) % 600)
4148 WHEN 'fish' THEN (abs(hashtext(t.owner_id::text || t.slot::text || 'age')) % 240)
4149 WHEN 'amphibian' THEN (abs(hashtext(t.owner_id::text || t.slot::text || 'age')) % 360)
4150 END AS age,
4151 hr.history,
4152 t.owner_id
4153FROM typed t
4154CROSS JOIN breed_counts bc
4155JOIN names_ranked nr
4156 ON nr.rn = (abs(hashtext(t.owner_id::text || t.slot::text || 'nm')) % bc.names_cnt) + 1
4157JOIN history_ranked hr
4158 ON hr.rn = (abs(hashtext(t.owner_id::text || t.slot::text || 'hi')) % bc.history_cnt) + 1
4159LEFT JOIN mammal_ranked mbr
4160 ON t.type = 'mammal'
4161 AND mbr.rn = (abs(hashtext(t.owner_id::text || t.slot::text || 'mb')) % bc.mammal_cnt) + 1
4162LEFT JOIN bird_ranked bbr
4163 ON t.type = 'bird'
4164 AND bbr.rn = (abs(hashtext(t.owner_id::text || t.slot::text || 'bb')) % bc.bird_cnt) + 1
4165LEFT JOIN fish_ranked fbr
4166 ON t.type = 'fish'
4167 AND fbr.rn = (abs(hashtext(t.owner_id::text || t.slot::text || 'fb')) % bc.fish_cnt) + 1
4168LEFT JOIN reptile_ranked rbr
4169 ON t.type = 'reptile'
4170 AND rbr.rn = (abs(hashtext(t.owner_id::text || t.slot::text || 'rb')) % bc.reptile_cnt) + 1
4171LEFT JOIN amphibian_ranked abr
4172 ON t.type = 'amphibian'
4173 AND abr.rn = (abs(hashtext(t.owner_id::text || t.slot::text || 'ab')) % bc.amphibian_cnt) + 1;
4174
4175
4176-- ============================================================
4177-- Large tables
4178-- TRUNCATE dependent tables (reverse FK order)
4179-- ============================================================
4180
4181TRUNCATE prescription_medicine CASCADE;
4182TRUNCATE prescription CASCADE;
4183TRUNCATE treatment_attribute_value CASCADE;
4184TRUNCATE treatment CASCADE;
4185TRUNCATE examination CASCADE;
4186TRUNCATE appointment CASCADE;
4187
4188
4189-- ============================================================
4190-- APPOINTMENT — 850,000 rows
4191-- ============================================================
4192
4193INSERT INTO appointment (date_appointment, reason, phone, owner_id, pet_id)
4194WITH owner_pet_pairs AS (
4195 SELECT
4196 o.id AS owner_id,
4197 o.phone AS phone,
4198 p.id AS pet_id,
4199 row_number() OVER (ORDER BY o.id, p.id) AS rn
4200 FROM owner o
4201 JOIN pet p ON p.owner_id = o.id
4202),
4203pair_count AS (
4204 SELECT count(*) AS cnt FROM owner_pet_pairs
4205)
4206SELECT
4207 (CURRENT_DATE - (abs(hashtext(gs.n::text || 'apdate')) % 1825))::date AS date_appointment,
4208
4209 (ARRAY[
4210 'Annual wellness check',
4211 'Vaccination booster',
4212 'Limping / lameness',
4213 'Vomiting and lethargy',
4214 'Skin rash and itching',
4215 'Ear infection suspected',
4216 'Eye discharge and redness',
4217 'Dental check-up',
4218 'Weight loss and poor appetite',
4219 'Diarrhoea for more than 2 days',
4220 'Post-operative follow-up',
4221 'Suspected urinary tract infection',
4222 'Respiratory difficulty',
4223 'Wound assessment',
4224 'Parasite prevention consultation',
4225 'Behavioural changes',
4226 'Mass / lump noticed',
4227 'Allergic reaction',
4228 'Pre-surgical blood work',
4229 'General health concern',
4230 'Annual vaccination',
4231 'Rabies vaccine booster',
4232 'Core vaccine schedule - puppy/kitten',
4233 'Bordetella vaccination',
4234 'Leptospirosis booster',
4235 'Feline herpesvirus / calicivirus / panleukopenia combo',
4236 'Canine distemper / parvovirus booster',
4237 'Vaccine certificate needed for travel',
4238 'First vaccination - new pet',
4239 'Overdue vaccination catch-up'
4240 ])[1 + (abs(hashtext(gs.n::text || 'rsn')) % 30)] AS reason,
4241
4242 op.phone,
4243 op.owner_id,
4244 op.pet_id
4245
4246FROM generate_series(1, 850000) AS gs(n)
4247CROSS JOIN pair_count pc
4248JOIN owner_pet_pairs op
4249 ON op.rn = ((gs.n - 1) % pc.cnt) + 1;
4250
4251
4252-- ============================================================
4253-- EXAMINATION — 720,000 rows
4254-- Takes first 720k appointments ordered by id
4255-- ~80% completed, ~13% cancelled, ~7% scheduled
4256-- ============================================================
4257
4258INSERT INTO examination (date_examination, status, description, appointment_id, employee_id, examination_room_id)
4259WITH apt_sample AS (
4260 SELECT
4261 a.id AS appointment_id,
4262 a.date_appointment
4263 FROM appointment a
4264 ORDER BY a.id
4265 LIMIT 720000
4266),
4267emp_ranked AS (
4268 SELECT id, row_number() OVER (ORDER BY id) AS rn
4269 FROM employee WHERE role_id = 2
4270),
4271emp_count AS (
4272 SELECT count(*) AS cnt FROM employee WHERE role_id = 2
4273),
4274room_ranked AS (
4275 SELECT id, row_number() OVER (ORDER BY id) AS rn
4276 FROM examination_room WHERE type = 'examination'
4277),
4278room_count AS (
4279 SELECT count(*) AS cnt FROM examination_room WHERE type = 'examination'
4280)
4281SELECT
4282 (a.date_appointment + (abs(hashtext(a.appointment_id::text || 'edate')) % 8))::date AS date_examination,
4283
4284 CASE (abs(hashtext(a.appointment_id::text || 'stat')) % 100)
4285 WHEN 0 THEN 'cancelled'
4286 WHEN 1 THEN 'cancelled'
4287 WHEN 2 THEN 'cancelled'
4288 WHEN 3 THEN 'cancelled'
4289 WHEN 4 THEN 'cancelled'
4290 WHEN 5 THEN 'cancelled'
4291 WHEN 6 THEN 'cancelled'
4292 WHEN 7 THEN 'cancelled'
4293 WHEN 8 THEN 'cancelled'
4294 WHEN 9 THEN 'cancelled'
4295 WHEN 10 THEN 'cancelled'
4296 WHEN 11 THEN 'cancelled'
4297 WHEN 12 THEN 'cancelled'
4298 WHEN 13 THEN 'scheduled'
4299 WHEN 14 THEN 'scheduled'
4300 WHEN 15 THEN 'scheduled'
4301 WHEN 16 THEN 'scheduled'
4302 WHEN 17 THEN 'scheduled'
4303 WHEN 18 THEN 'scheduled'
4304 WHEN 19 THEN 'scheduled'
4305 ELSE 'completed'
4306 END AS status,
4307
4308 (ARRAY[
4309 'Patient presented for routine examination. Vitals within normal limits.',
4310 'Initial assessment completed. Further diagnostics recommended.',
4311 'Physical examination performed. Owner advised on treatment plan.',
4312 'Patient examined; mild clinical signs noted. Medication prescribed.',
4313 'Thorough examination carried out. No acute concerns identified.',
4314 'Follow-up examination. Condition improving since last visit.',
4315 'Examination completed. Lab samples collected for analysis.',
4316 'Clinical signs assessed. Dietary modification recommended.',
4317 'Patient stable. Monitoring plan established with owner.',
4318 'Examination revealed localised inflammation. Treatment initiated.',
4319 'Pre-vaccination health check completed. Patient fit for immunisation.',
4320 'Animal examined prior to vaccination. No contraindications found.',
4321 'Vaccination visit. General condition assessed; vitals normal.',
4322 'Patient presented for scheduled immunisation. Brief physical performed.',
4323 'Health status confirmed satisfactory before vaccine administration.'
4324 ])[1 + (abs(hashtext(a.appointment_id::text || 'dsc')) % 15)] AS description,
4325
4326 a.appointment_id,
4327
4328 (SELECT id FROM emp_ranked
4329 WHERE rn = (abs(hashtext(a.appointment_id::text || 'emp')) % (SELECT cnt FROM emp_count)) + 1
4330 LIMIT 1) AS employee_id,
4331
4332 (SELECT id FROM room_ranked
4333 WHERE rn = (abs(hashtext(a.appointment_id::text || 'rom')) % (SELECT cnt FROM room_count)) + 1
4334 LIMIT 1) AS examination_room_id
4335
4336FROM apt_sample a;
4337
4338
4339-- ============================================================
4340-- TREATMENT — one per completed examination
4341-- 75% prescription / 15% vaccination / 7% consultation / 3% operation
4342-- ~576,000 completed exams - ~432,000 prescriptions
4343-- ============================================================
4344
4345INSERT INTO treatment (date_treatment, notes, treatment_type_id, examination_id)
4346WITH type_ids AS (
4347 SELECT
4348 (SELECT id FROM treatment_type WHERE name = 'prescription') AS presc_id,
4349 (SELECT id FROM treatment_type WHERE name = 'vaccination') AS vacc_id,
4350 (SELECT id FROM treatment_type WHERE name = 'consultation') AS cons_id,
4351 (SELECT id FROM treatment_type WHERE name = 'operation') AS oper_id
4352)
4353SELECT
4354 (e.date_examination + (abs(hashtext(e.id::text || 'tdate')) % 4))::date AS date_treatment,
4355
4356 CASE (abs(hashtext(e.id::text || 'ttype')) % 100)
4357 -- 75% prescription (0-74)
4358 WHEN 0 THEN 'Prescription issued following clinical assessment.'
4359 WHEN 1 THEN 'Medication course prescribed; owner counselled on administration.'
4360 WHEN 2 THEN 'Short course of antibiotics prescribed pending culture results.'
4361 WHEN 3 THEN 'Anti-inflammatory therapy initiated; re-check in 10 days.'
4362 WHEN 4 THEN 'Antiparasitic treatment prescribed; environmental treatment advised.'
4363 WHEN 5 THEN 'Analgesic course prescribed for post-operative pain management.'
4364 WHEN 6 THEN 'Antifungal therapy prescribed; reassess in 3 weeks.'
4365 WHEN 7 THEN 'Prescription provided; monitor for adverse reactions.'
4366 WHEN 8 THEN 'Combination therapy prescribed; owner given written instructions.'
4367 WHEN 9 THEN 'Medication adjusted based on current clinical findings.'
4368 -- 15% vaccination (75-89)
4369 WHEN 75 THEN 'Core vaccine administered. No immediate adverse reaction observed.'
4370 WHEN 76 THEN 'Booster vaccination given. Owner advised to monitor for 24 hours.'
4371 WHEN 77 THEN 'Rabies vaccine administered. Certificate issued.'
4372 WHEN 78 THEN 'Annual booster completed. Patient tolerated injection well.'
4373 WHEN 79 THEN 'Catch-up vaccination completed. Full schedule now up to date.'
4374 -- 7% consultation (90-96)
4375 WHEN 90 THEN 'Owner counselled on diet and weight management.'
4376 WHEN 91 THEN 'Behavioural concerns discussed; referral considered.'
4377 WHEN 92 THEN 'Discussed long-term management of chronic condition.'
4378 -- 3% operation (97-99)
4379 WHEN 97 THEN 'Surgery performed without complications.'
4380 WHEN 98 THEN 'Procedure completed; patient recovering well.'
4381 WHEN 99 THEN 'Operation successful; post-op care instructions given.'
4382 -- fill remaining slots to cover all 100 values
4383 ELSE
4384 CASE (abs(hashtext(e.id::text || 'ttype')) % 100)
4385 WHEN (abs(hashtext(e.id::text || 'ttype')) % 100) THEN
4386 CASE
4387 WHEN (abs(hashtext(e.id::text || 'ttype')) % 100) < 75
4388 THEN 'Prescription reissued; owner reported good compliance.'
4389 WHEN (abs(hashtext(e.id::text || 'ttype')) % 100) < 90
4390 THEN 'Intranasal Bordetella vaccine administered without complication.'
4391 WHEN (abs(hashtext(e.id::text || 'ttype')) % 100) < 97
4392 THEN 'Follow-up plan agreed; owner given written summary.'
4393 ELSE 'Patient stable post-operatively; monitoring ongoing.'
4394 END
4395 END
4396 END AS notes,
4397
4398 CASE
4399 WHEN (abs(hashtext(e.id::text || 'ttype')) % 100) < 75
4400 THEN (SELECT presc_id FROM type_ids)
4401 WHEN (abs(hashtext(e.id::text || 'ttype')) % 100) < 90
4402 THEN (SELECT vacc_id FROM type_ids)
4403 WHEN (abs(hashtext(e.id::text || 'ttype')) % 100) < 97
4404 THEN (SELECT cons_id FROM type_ids)
4405 ELSE (SELECT oper_id FROM type_ids)
4406 END AS treatment_type_id,
4407
4408 e.id AS examination_id
4409
4410FROM examination e
4411WHERE e.status = 'completed';
4412
4413
4414-- ============================================================
4415-- TREATMENT_ATTRIBUTE_VALUE
4416-- ~576k treatments × avg 8 attrs = ~4.6M rows
4417-- ============================================================
4418
4419-- PRESCRIPTION
4420INSERT INTO treatment_attribute_value (value, notes, treatment_attribute_id, treatment_id)
4421SELECT final_val.val, NULL, a.id, t.id
4422FROM treatment t
4423JOIN treatment_type tt ON tt.id = t.treatment_type_id AND tt.name = 'prescription'
4424JOIN treatment_attribute a ON a.treatment_type_id = tt.id
4425CROSS JOIN LATERAL (
4426 SELECT
4427 (ARRAY['Antibiotic','NSAID','Corticosteroid','Antiparasitic',
4428 'Antifungal','Analgesic','Anticonvulsant','Cardiac',
4429 'Gastrointestinal','Immunosuppressant'])
4430 [1 + (abs(hashtext(t.id::text || 'cls')) % 10)] AS medication_class,
4431 (ARRAY['Oral','Subcutaneous injection','Intramuscular injection',
4432 'Topical','Intravenous','Ophthalmic'])
4433 [1 + (abs(hashtext(t.id::text || 'rte')) % 6)] AS route,
4434 (ARRAY['Once daily','Twice daily','Three times daily',
4435 'Every 48 hours','Every 72 hours','With food'])
4436 [1 + (abs(hashtext(t.id::text || 'frq')) % 6)] AS frequency,
4437 (7 + (abs(hashtext(t.id::text || 'dur')) % 18))::text AS duration_days,
4438 (abs(hashtext(t.id::text || 'ref')) % 3)::text AS refills_allowed,
4439 CASE (abs(hashtext(t.id::text || 'wth')) % 3)
4440 WHEN 0 THEN 'None'
4441 WHEN 1 THEN '24 hours'
4442 ELSE '48 hours'
4443 END AS withdrawal_period
4444) v
4445CROSS JOIN LATERAL (
4446 SELECT CASE a.name
4447 WHEN 'medication_class' THEN v.medication_class
4448 WHEN 'route' THEN v.route
4449 WHEN 'frequency' THEN v.frequency
4450 WHEN 'duration_days' THEN v.duration_days
4451 WHEN 'refills_allowed' THEN v.refills_allowed
4452 WHEN 'withdrawal_period' THEN v.withdrawal_period
4453 END AS val
4454) final_val
4455WHERE final_val.val IS NOT NULL;
4456
4457-- VACCINATION
4458INSERT INTO treatment_attribute_value (value, notes, treatment_attribute_id, treatment_id)
4459SELECT final_val.val, NULL, a.id, t.id
4460FROM treatment t
4461JOIN treatment_type tt ON tt.id = t.treatment_type_id AND tt.name = 'vaccination'
4462JOIN treatment_attribute a ON a.treatment_type_id = tt.id
4463CROSS JOIN LATERAL (
4464 SELECT
4465 (ARRAY['Nobivac DHPPi','Nobivac Rabies','Feligen CRP',
4466 'Purevax RCPCh','Versican Plus DHPPi/L4','Canigen L4',
4467 'Nobivac Lepto 4','Eurican Herpes 205',
4468 'Felocell CVR','Quantum Cat 7'])
4469 [1 + (abs(hashtext(t.id::text || 'vac')) % 10)] AS vaccine_name,
4470 (ARRAY['Zoetis','MSD Animal Health','Boehringer Ingelheim','Virbac','Elanco'])
4471 [1 + (abs(hashtext(t.id::text || 'mfr')) % 5)] AS manufacturer,
4472 'BN-' || lpad((abs(hashtext(t.id::text || 'bn')) % 900000 + 100000)::text, 6, '0')
4473 AS batch_number,
4474 CASE (abs(hashtext(t.id::text || 'nd')) % 3)
4475 WHEN 0 THEN '1' WHEN 1 THEN '2' ELSE '3'
4476 END AS num_doses,
4477 CASE (abs(hashtext(t.id::text || 'dn')) % 2)
4478 WHEN 0 THEN '1' ELSE '2'
4479 END AS dose_number,
4480 (ARRAY['Subcutaneous','Intramuscular','Intranasal'])
4481 [1 + (abs(hashtext(t.id::text || 'rt2')) % 3)] AS route,
4482 (ARRAY['Right scruff','Left scruff','Right hindlimb','Left hindlimb'])
4483 [1 + (abs(hashtext(t.id::text || 'ste')) % 4)] AS site,
4484 (t.date_treatment + interval '1 year')::date::text AS date_next,
4485 CASE WHEN (abs(hashtext(t.id::text || 'adv')) % 100) < 3
4486 THEN 'true' ELSE 'false'
4487 END AS adverse_reaction
4488) v
4489CROSS JOIN LATERAL (
4490 SELECT CASE a.name
4491 WHEN 'vaccine_name' THEN v.vaccine_name
4492 WHEN 'manufacturer' THEN v.manufacturer
4493 WHEN 'batch_number' THEN v.batch_number
4494 WHEN 'num_doses' THEN v.num_doses
4495 WHEN 'dose_number' THEN v.dose_number
4496 WHEN 'route' THEN v.route
4497 WHEN 'site' THEN v.site
4498 WHEN 'date_next' THEN v.date_next
4499 WHEN 'adverse_reaction' THEN v.adverse_reaction
4500 END AS val
4501) final_val
4502WHERE final_val.val IS NOT NULL;
4503
4504-- CONSULTATION
4505INSERT INTO treatment_attribute_value (value, notes, treatment_attribute_id, treatment_id)
4506SELECT final_val.val, NULL, a.id, t.id
4507FROM treatment t
4508JOIN treatment_type tt ON tt.id = t.treatment_type_id AND tt.name = 'consultation'
4509JOIN treatment_attribute a ON a.treatment_type_id = tt.id
4510CROSS JOIN LATERAL (
4511 SELECT
4512 (ARRAY[
4513 'Nutrition and weight management','Behavioural assessment',
4514 'Chronic disease management','Pre-surgical counselling',
4515 'Post-operative care planning','Dental hygiene advice',
4516 'Parasite prevention review','Vaccination schedule planning',
4517 'End-of-life care discussion','Second opinion review'
4518 ])[1 + (abs(hashtext(t.id::text || 'top')) % 10)] AS topic,
4519 (ARRAY[
4520 'Owner presented concerns regarding the pet''s appetite and energy levels. A full dietary review was completed and a lower-calorie prescription diet was recommended. Owner was shown how to measure portions correctly and advised to recheck in 4 weeks.',
4521 'Behavioural history taken in detail. Pet displays anxiety-related signs including excessive grooming and vocalization. Environmental enrichment strategies discussed and a referral to a veterinary behaviourist was considered.',
4522 'Long-term management plan for the pet''s diagnosed chronic kidney disease was reviewed. Blood results interpreted and ACE inhibitor dosage adjusted. Owner counselled on signs of deterioration.',
4523 'Pre-surgical consultation completed for elective spay procedure. Risks and benefits of anaesthesia explained. Pre-operative blood panel ordered. Owner provided written consent and fasting instructions.',
4524 'Post-operative wound checked and healing progress assessed. Owner demonstrated correct application of topical antiseptic. Suture removal booked for 10 days post-op.',
4525 'Dental examination findings discussed with owner. Stage 2 periodontal disease identified. Professional scale and polish recommended. Home brushing technique demonstrated.',
4526 'Current parasite prevention programme assessed. Updated protocol prescribed combining monthly spot-on and quarterly wormer. Environmental hygiene advice given.',
4527 'Full vaccination history reviewed. Pet was overdue for core and leptospirosis boosters. Schedule re-established and owner reminded of annual requirement.',
4528 'Compassionate discussion held with owner regarding quality of life for their senior pet with advanced neoplasia. Palliative care options outlined.',
4529 'Second opinion consultation for recurrent skin condition. Differential diagnoses reconsidered; skin biopsy recommended to rule out immune-mediated disease.'
4530 ])[1 + (abs(hashtext(t.id::text || 'dsc')) % 10)] AS description,
4531 CASE WHEN (abs(hashtext(t.id::text || 'ref')) % 100) < 15
4532 THEN 'true' ELSE 'false'
4533 END AS referral,
4534 CASE WHEN (abs(hashtext(t.id::text || 'ref')) % 100) < 15
4535 THEN (ARRAY['Veterinary Dermatologist','Veterinary Cardiologist',
4536 'Veterinary Behaviourist','Veterinary Oncologist',
4537 'Veterinary Ophthalmologist','Veterinary Neurologist'])
4538 [1 + (abs(hashtext(t.id::text || 'rto')) % 6)]
4539 ELSE NULL
4540 END AS referral_to,
4541 (ARRAY['7','14','21','30'])
4542 [1 + (abs(hashtext(t.id::text || 'fup')) % 4)] AS follow_up_days,
4543 CASE WHEN (abs(hashtext(t.id::text || 'own')) % 10) < 9
4544 THEN 'true' ELSE 'false'
4545 END AS owner_present
4546) v
4547CROSS JOIN LATERAL (
4548 SELECT CASE a.name
4549 WHEN 'topic' THEN v.topic
4550 WHEN 'description' THEN v.description
4551 WHEN 'referral' THEN v.referral
4552 WHEN 'referral_to' THEN v.referral_to
4553 WHEN 'follow_up_days' THEN v.follow_up_days
4554 WHEN 'owner_present' THEN v.owner_present
4555 END AS val
4556) final_val
4557WHERE final_val.val IS NOT NULL;
4558
4559-- OPERATION
4560INSERT INTO treatment_attribute_value (value, notes, treatment_attribute_id, treatment_id)
4561SELECT final_val.val, NULL, a.id, t.id
4562FROM treatment t
4563JOIN treatment_type tt ON tt.id = t.treatment_type_id AND tt.name = 'operation'
4564JOIN treatment_attribute a ON a.treatment_type_id = tt.id
4565CROSS JOIN LATERAL (
4566 SELECT
4567 (ARRAY[
4568 'Ovariohysterectomy (spay)','Orchiectomy (neuter)',
4569 'Mass / tumour excision','Fracture repair (ORIF)',
4570 'Intestinal foreign body removal',
4571 'Cystotomy (bladder stone removal)','Gastropexy',
4572 'Enucleation','Amputation','Caesarean section',
4573 'Exploratory laparotomy','Cruciate ligament repair (TPLO)',
4574 'Dental extraction','Wound debridement and closure',
4575 'Thoracostomy tube placement'
4576 ])[1 + (abs(hashtext(t.id::text || 'opt')) % 15)] AS operation_type,
4577 (ARRAY['Successful','Successful','Successful','Successful',
4578 'Complicated','Unsuccessful'])
4579 [1 + (abs(hashtext(t.id::text || 'sts')) % 6)] AS status,
4580 (ARRAY[
4581 'Propofol induction / Isoflurane maintenance',
4582 'Alfaxalone induction / Isoflurane maintenance',
4583 'Ketamine-Midazolam / Isoflurane maintenance',
4584 'Propofol TIVA',
4585 'Medetomidine-Butorphanol sedation (minor procedure)'
4586 ])[1 + (abs(hashtext(t.id::text || 'ans')) % 5)] AS anesthesia,
4587 (15 + (abs(hashtext(t.id::text || 'dur')) % 166))::text AS duration_minutes,
4588 (t.date_treatment + (7 + abs(hashtext(t.id::text || 'chk')) % 8))::text
4589 AS date_checkup,
4590 (SELECT e.first_name || ' ' || e.last_name
4591 FROM employee e
4592 WHERE e.role_id = 2
4593 ORDER BY abs(hashtext(t.id::text || 'srg' || e.id::text))
4594 LIMIT 1) AS surgeon,
4595 CASE WHEN (abs(hashtext(t.id::text || 'cmp')) % 100) < 12
4596 THEN 'true' ELSE 'false'
4597 END AS complications
4598) v
4599CROSS JOIN LATERAL (
4600 SELECT CASE a.name
4601 WHEN 'operation_type' THEN v.operation_type
4602 WHEN 'status' THEN v.status
4603 WHEN 'anesthesia' THEN v.anesthesia
4604 WHEN 'duration_minutes' THEN v.duration_minutes
4605 WHEN 'date_checkup' THEN v.date_checkup
4606 WHEN 'surgeon' THEN v.surgeon
4607 WHEN 'complications' THEN v.complications
4608 END AS val
4609) final_val
4610WHERE final_val.val IS NOT NULL;
4611
4612
4613-- ============================================================
4614-- PRESCRIPTION — one per completed examination
4615-- with a prescription treatment (~432,000 rows)
4616-- ============================================================
4617
4618INSERT INTO prescription (examination_id, date_start, date_end, description)
4619SELECT
4620 e.id AS examination_id,
4621 e.date_examination AS date_start,
4622 (e.date_examination + (7 + abs(hashtext(e.id::text || 'pend')) % 21))::date AS date_end,
4623 (ARRAY[
4624 'Administer as directed. Complete the full course.',
4625 'Give with food to reduce gastric upset.',
4626 'Monitor for adverse reactions; contact clinic if vomiting occurs.',
4627 'Store in a cool dry place. Keep out of reach of children.',
4628 'Re-examine if no improvement within 5 days.',
4629 'Do not crush tablets; administer whole.',
4630 'Shake oral suspension well before each use.',
4631 'Continue until finished even if pet appears better.',
4632 'Avoid direct sunlight on treated area.',
4633 'Return for recheck at end of course.'
4634 ])[1 + (abs(hashtext(e.id::text || 'pdsc')) % 10)] AS description
4635FROM examination e
4636JOIN treatment t ON t.examination_id = e.id
4637JOIN treatment_type tt ON tt.id = t.treatment_type_id AND tt.name = 'prescription'
4638WHERE e.status = 'completed'
4639ON CONFLICT (examination_id) DO NOTHING;
4640
4641
4642-- ============================================================
4643-- PRESCRIPTION_MEDICINE — target ~10M rows
4644-- ~432,000 prescriptions × avg 25 medicines = ~10.8M
4645-- generate_series 1-30, want_count 20-30
4646-- ============================================================
4647
4648WITH med_ranked AS (
4649 SELECT id, row_number() OVER (ORDER BY id) AS rn
4650 FROM medicine
4651),
4652med_count AS (
4653 SELECT count(*) AS total FROM medicine
4654),
4655slots AS (
4656 SELECT
4657 p.id AS prescription_id,
4658 gs.slot
4659 FROM prescription p
4660 CROSS JOIN generate_series(1, 30) AS gs(slot)
4661),
4662assigned AS (
4663 SELECT
4664 s.prescription_id,
4665 s.slot,
4666 mr.id AS medicine_id,
4667 1 + (abs(hashtext(s.prescription_id::text || '-dos-' || s.slot::text)) % 3) AS dosage,
4668 3 + (abs(hashtext(s.prescription_id::text || '-day-' || s.slot::text)) % 26) AS num_days,
4669 -- each prescription wants between 20 and 30 medicines
4670 20 + (abs(hashtext(s.prescription_id::text || '-cnt')) % 11) AS want_count
4671 FROM slots s
4672 CROSS JOIN med_count mc
4673 JOIN med_ranked mr
4674 ON mr.rn = (abs(hashtext(s.prescription_id::text || '-med-' || s.slot::text)) % mc.total) + 1
4675),
4676filtered AS (
4677 SELECT prescription_id, medicine_id, dosage, num_days
4678 FROM assigned
4679 WHERE slot <= want_count
4680),
4681deduped AS (
4682 SELECT DISTINCT ON (prescription_id, medicine_id)
4683 prescription_id, medicine_id, dosage, num_days
4684 FROM filtered
4685 ORDER BY prescription_id, medicine_id
4686)
4687INSERT INTO prescription_medicine (prescription_id, medicine_id, dosage, num_days)
4688SELECT prescription_id, medicine_id, dosage, num_days
4689FROM deduped
4690ON CONFLICT (prescription_id, medicine_id) DO NOTHING;
4691
4692
4693-- SELECT count(*) FROM appointment;
4694-- SELECT * FROM appointment;
4695-- SELECT count(*) FROM examination;
4696-- SELECT * FROM examination;
4697-- SELECT count(*) FROM treatment;
4698-- SELECT * FROM treatment;
4699-- SELECT count(*) FROM treatment_attribute_value;
4700-- SELECT * FROM treatment_attribute_value;
4701-- SELECT count(*) FROM prescription;
4702-- SELECT * FROM prescription;
4703-- SELECT count(*) FROM prescription_medicine;
4704--
4705-- SELECT pg_size_pretty(pg_database_size(current_database()));
4706
4707
4708-- ============================================================
4709-- discount
4710-- ~25% of shop items currently carry an active promotional discount
4711-- ============================================================
4712
4713INSERT INTO discount (type, value, description, date_from, date_to, shop_item_id)
4714SELECT
4715 CASE
4716 WHEN (abs(hashtext(si.id::text || 'dtype')) % 2) = 0
4717 THEN 'fixed'
4718 ELSE 'percentage'
4719 END AS type,
4720
4721 CASE
4722 WHEN (abs(hashtext(si.id::text || 'dtype')) % 2) = 0
4723 THEN round(
4724 (1 + (abs(hashtext(si.id::text || 'dval')) % 1500) / 100.0)::numeric,
4725 2
4726 )
4727 ELSE round(
4728 (5 + (abs(hashtext(si.id::text || 'dval')) % 3500) / 100.0)::numeric,
4729 2
4730 )
4731 END AS value,
4732
4733 (ARRAY[
4734 'Seasonal promotion',
4735 'Clearance discount',
4736 'Loyalty customer discount',
4737 'Bulk purchase discount',
4738 'New product launch offer',
4739 'Holiday special',
4740 'Overstock clearance',
4741 'Limited time offer',
4742 'Member exclusive discount',
4743 'Weekend flash sale'
4744 ])[1 + (abs(hashtext(si.id::text || 'ddsc')) % 10)] AS description,
4745
4746 CURRENT_DATE - (abs(hashtext(si.id::text || 'dfrom')) % 30) AS date_from,
4747
4748 CURRENT_DATE + (abs(hashtext(si.id::text || 'dto')) % 60 + 1) AS date_to,
4749
4750 si.id AS shop_item_id
4751
4752FROM shop_item si
4753WHERE (abs(hashtext(si.id::text || 'dpick')) % 100) < 25;
4754
4755ALTER TABLE invoice_item DISABLE TRIGGER trg_generate_num_item;
4756
4757-- ============================================================
4758-- invoice + invoice_item — clinical visits
4759-- One invoice per completed examination that has at least one
4760-- treatment; each treatment becomes its own invoice_item line.
4761-- ~15% of invoices redeem an active coupon.
4762-- ============================================================
4763
4764WITH billable_examinations AS (
4765 SELECT
4766 e.id AS examination_id,
4767 e.date_examination,
4768 a.owner_id
4769 FROM examination e
4770 JOIN appointment a ON a.id = e.appointment_id
4771 WHERE e.status = 'completed'
4772 AND EXISTS (SELECT 1 FROM treatment t WHERE t.examination_id = e.id)
4773),
4774
4775 invoice_src AS (
4776 SELECT
4777 nextval(pg_get_serial_sequence('invoice', 'id')) AS invoice_id,
4778 be.examination_id,
4779 be.date_examination AS date_invoice,
4780 be.owner_id,
4781 CASE WHEN (abs(hashtext(be.examination_id::text || 'cpn')) % 100) < 15
4782 THEN (
4783 SELECT c.id FROM coupon c
4784 WHERE c.is_active = true
4785 ORDER BY abs(hashtext(be.examination_id::text || 'cpnpick' || c.id::text))
4786 LIMIT 1
4787 )
4788 ELSE NULL
4789 END AS coupon_id
4790 FROM billable_examinations be
4791 ),
4792
4793 new_invoices AS (
4794 INSERT INTO invoice (id, date_invoice, total, coupon_id, owner_id)
4795 SELECT invoice_id, date_invoice, 0.00, coupon_id, owner_id
4796 FROM invoice_src
4797 RETURNING id
4798 ),
4799
4800 invoice_lines AS (
4801 SELECT
4802 isrc.invoice_id,
4803 t.id AS treatment_id,
4804 CASE tt.name
4805 WHEN 'prescription' THEN round((15 + random() * 60)::numeric, 2)
4806 WHEN 'vaccination' THEN round((20 + random() * 30)::numeric, 2)
4807 WHEN 'consultation' THEN round((25 + random() * 45)::numeric, 2)
4808 WHEN 'operation' THEN round((150 + random() * 850)::numeric, 2)
4809 END AS price
4810 FROM invoice_src isrc
4811 JOIN treatment t ON t.examination_id = isrc.examination_id
4812 JOIN treatment_type tt ON tt.id = t.treatment_type_id
4813 )
4814
4815INSERT INTO invoice_item (num_item, invoice_id, price, quantity, type, treatment_id)
4816SELECT
4817 row_number() OVER (PARTITION BY invoice_id ORDER BY treatment_id) AS num_item,
4818 invoice_id,
4819 price,
4820 1 AS quantity,
4821 'treatment' AS type,
4822 treatment_id
4823FROM invoice_lines;
4824
4825-- ============================================================
4826-- invoice + invoice_item — retail purchases (bulk)
4827-- 200,000 standalone shop invoices spread across all owners
4828-- over the last 3 years, each with 5-15 line items
4829-- ============================================================
4830
4831WITH owner_count AS (
4832 SELECT count(*) AS cnt FROM owner
4833),
4834
4835 owner_ranked AS (
4836 SELECT id, row_number() OVER (ORDER BY id) AS rn FROM owner
4837 ),
4838
4839 invoice_src AS (
4840 SELECT
4841 nextval(pg_get_serial_sequence('invoice', 'id')) AS invoice_id,
4842 o.id AS owner_id,
4843 CURRENT_DATE - (abs(hashtext(gs.n::text || 'shpdate')) % 1095) AS date_invoice,
4844 5 + (abs(hashtext(gs.n::text || 'itemcnt')) % 11) AS num_items, -- 5-15 items
4845 CASE WHEN (abs(hashtext(gs.n::text || 'shpcpn')) % 100) < 15
4846 THEN (
4847 SELECT c.id FROM coupon c
4848 WHERE c.is_active = true
4849 ORDER BY abs(hashtext(gs.n::text || 'shpcpnpick' || c.id::text))
4850 LIMIT 1
4851 )
4852 ELSE NULL
4853 END AS coupon_id
4854 FROM generate_series(1, 200000) AS gs(n)
4855 CROSS JOIN owner_count oc
4856 JOIN owner_ranked o
4857 ON o.rn = ((abs(hashtext(gs.n::text || 'ownerpick')) % oc.cnt) + 1)
4858 ),
4859
4860 new_invoices AS (
4861 INSERT INTO invoice (id, date_invoice, total, coupon_id, owner_id)
4862 SELECT invoice_id, date_invoice, 0.00, coupon_id, owner_id
4863 FROM invoice_src
4864 RETURNING id
4865 ),
4866
4867 item_slots AS (
4868 SELECT isrc.invoice_id, gs.slot
4869 FROM invoice_src isrc
4870 CROSS JOIN generate_series(1, 15) AS gs(slot)
4871 WHERE gs.slot <= isrc.num_items
4872 ),
4873
4874 item_count AS (
4875 SELECT count(*) AS total FROM shop_item
4876 ),
4877
4878 item_ranked AS (
4879 SELECT id, price, row_number() OVER (ORDER BY id) AS rn FROM shop_item
4880 )
4881
4882INSERT INTO invoice_item (num_item, invoice_id, price, quantity, type, shop_item_id)
4883SELECT
4884 isl.slot AS num_item,
4885 isl.invoice_id,
4886 ir.price,
4887 1 + (abs(hashtext(isl.invoice_id::text || 'qty' || isl.slot::text)) % 3) AS quantity,
4888 'shop_item' AS type,
4889 ir.id AS shop_item_id
4890FROM item_slots isl
4891 CROSS JOIN item_count ic
4892 JOIN item_ranked ir
4893 ON ir.rn = (abs(hashtext(isl.invoice_id::text || 'item' || isl.slot::text)) % ic.total) + 1;
4894
4895
4896ALTER TABLE invoice_item ENABLE TRIGGER trg_generate_num_item;
4897
4898-- ============================================================
4899-- invoice.total — recompute from invoice_item lines and apply
4900-- the redeemed coupon's discount, then bump coupon usage_count
4901-- ============================================================
4902
4903UPDATE coupon c
4904SET usage_count = LEAST(c.usage_count + sub.uses, c.usage_limit)
4905FROM (
4906 SELECT coupon_id, count(*) AS uses
4907 FROM invoice
4908 WHERE coupon_id IS NOT NULL
4909 GROUP BY coupon_id
4910 ) sub
4911WHERE c.id = sub.coupon_id;
4912
4913WITH invoice_calc AS (
4914 SELECT
4915 i.id AS invoice_id,
4916 i.coupon_id,
4917 SUM(ii.price * ii.quantity) AS subtotal
4918 FROM invoice i
4919 JOIN invoice_item ii ON ii.invoice_id = i.id
4920 GROUP BY i.id, i.coupon_id
4921)
4922UPDATE invoice i
4923SET total = GREATEST(
4924 ROUND(
4925 CASE
4926 WHEN c.id IS NOT NULL AND ic.subtotal >= COALESCE(c.min_total, 0) THEN
4927 CASE c.type
4928 WHEN 'fixed' THEN ic.subtotal - c.value
4929 WHEN 'percentage' THEN ic.subtotal * (1 - c.value / 100)
4930 END
4931 ELSE ic.subtotal
4932 END,
4933 2),
4934 0.00)
4935FROM invoice_calc ic
4936 LEFT JOIN coupon c ON c.id = ic.coupon_id
4937WHERE i.id = ic.invoice_id;
4938
4939-- ============================================================
4940-- payment
4941-- ============================================================
4942
4943INSERT INTO payment (date_payment, amount, method, invoice_id)
4944SELECT
4945 i.date_invoice + (abs(hashtext(i.id::text || 'paydate')) % 6) AS date_payment,
4946 i.total AS amount,
4947 CASE
4948 WHEN (abs(hashtext(i.id::text || 'paymethod')) % 100) < 30 THEN 'cash'
4949 WHEN (abs(hashtext(i.id::text || 'paymethod')) % 100) < 55 THEN 'debit card'
4950 WHEN (abs(hashtext(i.id::text || 'paymethod')) % 100) < 80 THEN 'credit card'
4951 WHEN (abs(hashtext(i.id::text || 'paymethod')) % 100) < 95 THEN 'digital wallet'
4952 ELSE 'other'
4953 END AS method,
4954 i.id AS invoice_id
4955FROM invoice i;
4956
4957-- ========================
4958-- Drop temporary tables
4959-- ========================
4960DROP TABLE IF EXISTS temp_addresses;
4961DROP TABLE IF EXISTS temp_breeds_mammal;
4962DROP TABLE IF EXISTS temp_breeds_bird;
4963DROP TABLE IF EXISTS temp_breeds_fish;
4964DROP TABLE IF EXISTS temp_breeds_amphibian;
4965DROP TABLE IF EXISTS temp_breeds_reptile;
4966DROP TABLE IF EXISTS temp_pet_names;
4967DROP TABLE IF EXISTS temp_medical_history;
4968DROP TABLE IF EXISTS temp_certificate_names;
4969DROP TABLE IF EXISTS temp_spec_data;
4970DROP TABLE IF EXISTS temp_prescription_advice;
4971DROP TABLE IF EXISTS temp_surnames;
4972DROP TABLE IF EXISTS temp_male_names;
4973DROP TABLE IF EXISTS temp_female_names;
4974DROP TABLE IF EXISTS temp_med1;