Compare commits

...

14 Commits
main ... master

6
.idea/.gitignore vendored

@ -0,0 +1,6 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

@ -1,2 +0,0 @@
# test

File diff suppressed because it is too large Load Diff

@ -0,0 +1,50 @@
ed416a25-220e-45ef-9b6f-b6b728ecd8c2,NK,内科,住院部3楼,011-48624075
366bf6cf-12c9-4fa7-a8f3-b0c99a2005a8,WK,外科,住院部2楼,018-69512948
fa29374c-6f8c-41c9-8793-2d999ce8e777,EK,儿科,门诊部1楼,019-19102686
920a35c7-5a87-4b97-af7b-75f3a78e7aaa,FCK,妇产科,门诊部3楼,010-12051811
e64aa0d1-447c-4270-b0c3-9277bfbde479,YK,眼科,门诊部4楼,018-19592407
33fd3c0d-9b5b-481c-97a7-25d5284f5c17,EBH,耳鼻喉科,门诊部4楼,019-37012145
7cac7292-641b-40e8-a2e5-a277d620f282,KQK,口腔科,门诊部5楼,015-22338224
34151e3d-6e46-4c97-966d-fb42cd5dde47,PFK,皮肤科,门诊部2楼,020-67025909
3ce502f1-a56c-4761-886d-bab8704858f5,ZYK,中医科,中医楼1楼,012-68121268
1a4cf65c-57d9-4412-ad05-d4af2dc12f79,JZK,急诊科,急诊中心,013-29971053
61a1e53b-d98a-4c40-9551-598527830c6c,MZK,麻醉科,手术楼2楼,010-49597832
7a174bd3-d91f-4c8b-8020-d4e1c2a56a47,FSK,放射科,影像中心,018-87776279
4d654fd0-c1a5-4618-9dad-cd636aad66cc,JYK,检验科,检验中心,016-10658124
af4d73aa-5127-453b-ad76-e2a5efbd5fa5,YJK,药剂科,药房1楼,021-66005273
ebe4bee0-9c68-420e-92c2-e0c834f83d1e,KFK,康复科,康复中心,020-27794338
e7ef86c8-150f-4dba-925c-c83a084dc148,ZK016,专科16,门诊部4楼,012-99789244
7d26cfc6-59df-4bab-99c1-3ba02895af9d,ZK017,专科17,门诊部4楼,018-33572695
14e4406a-89d7-46d8-b9b2-82479c1a8f6f,ZK018,专科18,门诊部3楼,016-79183950
d7cc7f3d-346d-48f4-b885-1e3cf049a8b1,ZK019,专科19,门诊部5楼,021-24781588
43a63dfb-573e-4836-8254-ccdaee19c360,ZK020,专科20,门诊部5楼,020-58109256
766bec2d-c9d9-41a8-8bc9-e5ba107bd448,ZK021,专科21,门诊部3楼,011-39139589
52e82246-3df0-447e-9837-2ba69134daf1,ZK022,专科22,门诊部5楼,016-36981347
0a94f510-209c-40d6-bb4b-c859dac6f3c7,ZK023,专科23,门诊部2楼,018-54478461
877cd65b-72c1-475d-b507-9318d910464d,ZK024,专科24,门诊部4楼,021-51627590
ccbd2c9e-812f-4ced-8171-31a3ae2355d8,ZK025,专科25,门诊部4楼,014-41436890
39165549-ef5f-49b7-9c4e-67d222164580,ZK026,专科26,门诊部5楼,012-18676827
e06d4194-1503-412c-94f8-afa990790305,ZK027,专科27,门诊部3楼,014-60325356
ba554797-337a-4cc3-b198-92e4da350aa0,ZK028,专科28,门诊部4楼,011-45626822
089ca5b3-b7c1-4d34-9c81-936d704242a2,ZK029,专科29,门诊部5楼,018-11184665
4e8f64de-ef37-43b4-b743-0ead745d6a55,ZK030,专科30,门诊部4楼,014-32489665
181bd784-458a-478e-8e29-2b29d3be22a8,ZK031,专科31,门诊部2楼,015-46234637
10ed97cb-1613-49fb-93ee-e1b463bd105f,ZK032,专科32,门诊部1楼,012-74769956
f4d7f17c-ff09-4fd6-8107-cb9bc4ea6f75,ZK033,专科33,门诊部1楼,017-62968428
8a4873ba-c39b-46a9-8835-14a2880f2f44,ZK034,专科34,门诊部1楼,011-49619724
61735c8b-563f-4b66-837a-4ce0ba90e626,ZK035,专科35,门诊部5楼,015-29467540
c064a222-2bac-491b-9c5d-e8655fd284a2,ZK036,专科36,门诊部2楼,011-55315724
b58cbb78-31b9-457d-88f2-30d53a92b98d,ZK037,专科37,门诊部1楼,012-62683435
cc2cb171-0dec-4ffb-9135-d3d8b8a068a1,ZK038,专科38,门诊部2楼,016-44295640
63ab4171-8566-423e-b052-308f913679b7,ZK039,专科39,门诊部4楼,013-87719168
5329064e-e423-4b9f-80d0-36774e9a3ecf,ZK040,专科40,门诊部4楼,010-10291914
eb3c869a-be26-4ed4-8838-9fc99cf4654a,ZK041,专科41,门诊部1楼,011-45328457
84ac9274-3aca-4cfc-aba1-b2db63bc544e,ZK042,专科42,门诊部5楼,012-71627636
40d7305f-56a1-4671-b202-9591f346d4cf,ZK043,专科43,门诊部4楼,011-28998350
f4ee41cb-046d-4d55-ba1d-ed8513fd3e26,ZK044,专科44,门诊部5楼,021-81507348
0f09082e-5f31-44bd-9ee0-0871cb8d8dda,ZK045,专科45,门诊部5楼,015-42045642
776e1d6f-ac60-4dd6-a1ba-1e9169b02a02,ZK046,专科46,门诊部3楼,012-25959215
f18e91df-7f5e-4a82-90d9-0806af5a7fbc,ZK047,专科47,门诊部2楼,017-65768741
202315f2-87cd-4d99-bbfa-3cbd098a8db6,ZK048,专科48,门诊部5楼,021-55593132
090d9f38-1358-4163-94f0-52f99bb3add5,ZK049,专科49,门诊部2楼,019-62774694
7b3dd3fe-5f22-4c93-b95a-6fb66a1c9fc9,ZK050,专科50,门诊部3楼,011-14977745
1 ed416a25-220e-45ef-9b6f-b6b728ecd8c2 NK 内科 住院部3楼 011-48624075
2 366bf6cf-12c9-4fa7-a8f3-b0c99a2005a8 WK 外科 住院部2楼 018-69512948
3 fa29374c-6f8c-41c9-8793-2d999ce8e777 EK 儿科 门诊部1楼 019-19102686
4 920a35c7-5a87-4b97-af7b-75f3a78e7aaa FCK 妇产科 门诊部3楼 010-12051811
5 e64aa0d1-447c-4270-b0c3-9277bfbde479 YK 眼科 门诊部4楼 018-19592407
6 33fd3c0d-9b5b-481c-97a7-25d5284f5c17 EBH 耳鼻喉科 门诊部4楼 019-37012145
7 7cac7292-641b-40e8-a2e5-a277d620f282 KQK 口腔科 门诊部5楼 015-22338224
8 34151e3d-6e46-4c97-966d-fb42cd5dde47 PFK 皮肤科 门诊部2楼 020-67025909
9 3ce502f1-a56c-4761-886d-bab8704858f5 ZYK 中医科 中医楼1楼 012-68121268
10 1a4cf65c-57d9-4412-ad05-d4af2dc12f79 JZK 急诊科 急诊中心 013-29971053
11 61a1e53b-d98a-4c40-9551-598527830c6c MZK 麻醉科 手术楼2楼 010-49597832
12 7a174bd3-d91f-4c8b-8020-d4e1c2a56a47 FSK 放射科 影像中心 018-87776279
13 4d654fd0-c1a5-4618-9dad-cd636aad66cc JYK 检验科 检验中心 016-10658124
14 af4d73aa-5127-453b-ad76-e2a5efbd5fa5 YJK 药剂科 药房1楼 021-66005273
15 ebe4bee0-9c68-420e-92c2-e0c834f83d1e KFK 康复科 康复中心 020-27794338
16 e7ef86c8-150f-4dba-925c-c83a084dc148 ZK016 专科16 门诊部4楼 012-99789244
17 7d26cfc6-59df-4bab-99c1-3ba02895af9d ZK017 专科17 门诊部4楼 018-33572695
18 14e4406a-89d7-46d8-b9b2-82479c1a8f6f ZK018 专科18 门诊部3楼 016-79183950
19 d7cc7f3d-346d-48f4-b885-1e3cf049a8b1 ZK019 专科19 门诊部5楼 021-24781588
20 43a63dfb-573e-4836-8254-ccdaee19c360 ZK020 专科20 门诊部5楼 020-58109256
21 766bec2d-c9d9-41a8-8bc9-e5ba107bd448 ZK021 专科21 门诊部3楼 011-39139589
22 52e82246-3df0-447e-9837-2ba69134daf1 ZK022 专科22 门诊部5楼 016-36981347
23 0a94f510-209c-40d6-bb4b-c859dac6f3c7 ZK023 专科23 门诊部2楼 018-54478461
24 877cd65b-72c1-475d-b507-9318d910464d ZK024 专科24 门诊部4楼 021-51627590
25 ccbd2c9e-812f-4ced-8171-31a3ae2355d8 ZK025 专科25 门诊部4楼 014-41436890
26 39165549-ef5f-49b7-9c4e-67d222164580 ZK026 专科26 门诊部5楼 012-18676827
27 e06d4194-1503-412c-94f8-afa990790305 ZK027 专科27 门诊部3楼 014-60325356
28 ba554797-337a-4cc3-b198-92e4da350aa0 ZK028 专科28 门诊部4楼 011-45626822
29 089ca5b3-b7c1-4d34-9c81-936d704242a2 ZK029 专科29 门诊部5楼 018-11184665
30 4e8f64de-ef37-43b4-b743-0ead745d6a55 ZK030 专科30 门诊部4楼 014-32489665
31 181bd784-458a-478e-8e29-2b29d3be22a8 ZK031 专科31 门诊部2楼 015-46234637
32 10ed97cb-1613-49fb-93ee-e1b463bd105f ZK032 专科32 门诊部1楼 012-74769956
33 f4d7f17c-ff09-4fd6-8107-cb9bc4ea6f75 ZK033 专科33 门诊部1楼 017-62968428
34 8a4873ba-c39b-46a9-8835-14a2880f2f44 ZK034 专科34 门诊部1楼 011-49619724
35 61735c8b-563f-4b66-837a-4ce0ba90e626 ZK035 专科35 门诊部5楼 015-29467540
36 c064a222-2bac-491b-9c5d-e8655fd284a2 ZK036 专科36 门诊部2楼 011-55315724
37 b58cbb78-31b9-457d-88f2-30d53a92b98d ZK037 专科37 门诊部1楼 012-62683435
38 cc2cb171-0dec-4ffb-9135-d3d8b8a068a1 ZK038 专科38 门诊部2楼 016-44295640
39 63ab4171-8566-423e-b052-308f913679b7 ZK039 专科39 门诊部4楼 013-87719168
40 5329064e-e423-4b9f-80d0-36774e9a3ecf ZK040 专科40 门诊部4楼 010-10291914
41 eb3c869a-be26-4ed4-8838-9fc99cf4654a ZK041 专科41 门诊部1楼 011-45328457
42 84ac9274-3aca-4cfc-aba1-b2db63bc544e ZK042 专科42 门诊部5楼 012-71627636
43 40d7305f-56a1-4671-b202-9591f346d4cf ZK043 专科43 门诊部4楼 011-28998350
44 f4ee41cb-046d-4d55-ba1d-ed8513fd3e26 ZK044 专科44 门诊部5楼 021-81507348
45 0f09082e-5f31-44bd-9ee0-0871cb8d8dda ZK045 专科45 门诊部5楼 015-42045642
46 776e1d6f-ac60-4dd6-a1ba-1e9169b02a02 ZK046 专科46 门诊部3楼 012-25959215
47 f18e91df-7f5e-4a82-90d9-0806af5a7fbc ZK047 专科47 门诊部2楼 017-65768741
48 202315f2-87cd-4d99-bbfa-3cbd098a8db6 ZK048 专科48 门诊部5楼 021-55593132
49 090d9f38-1358-4163-94f0-52f99bb3add5 ZK049 专科49 门诊部2楼 019-62774694
50 7b3dd3fe-5f22-4c93-b95a-6fb66a1c9fc9 ZK050 专科50 门诊部3楼 011-14977745

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,514 @@
import csv
import uuid
import random
from datetime import datetime, timedelta
import pandas as pd
from faker import Faker
import time
# 初始化Faker
fake = Faker('zh_CN')
# 配置参数
NUM_DEPARTMENTS = 50 # 科室数量
NUM_DOCTORS = 2000 # 医生数量
NUM_PATIENTS = 10000 # 患者数量
NUM_DRUGS = 1000 # 药品数量
NUM_BATCHES = 5000 # 库存批次数量
NUM_SCHEDULES = 30000 # 排班数量
NUM_APPOINTMENTS = 25000 # 预约数量
NUM_MEDICAL_RECORDS = 20000 # 病历数量
NUM_PRESCRIPTIONS = 18000 # 处方数量
NUM_PRESCRIPTION_ITEMS = 45000 # 处方明细数量
# 生成时间范围
START_DATE = datetime(2023, 1, 1)
END_DATE = datetime(2024, 12, 31)
def random_date(start, end):
"""生成随机日期"""
delta = end - start
random_days = random.randint(0, delta.days)
return start + timedelta(days=random_days)
def generate_departments():
"""生成科室数据"""
print("生成科室数据...")
departments = []
department_ids = []
# 常见的医院科室
common_depts = [
("内科", "NK", "住院部3楼"),
("外科", "WK", "住院部2楼"),
("儿科", "EK", "门诊部1楼"),
("妇产科", "FCK", "门诊部3楼"),
("眼科", "YK", "门诊部4楼"),
("耳鼻喉科", "EBH", "门诊部4楼"),
("口腔科", "KQK", "门诊部5楼"),
("皮肤科", "PFK", "门诊部2楼"),
("中医科", "ZYK", "中医楼1楼"),
("急诊科", "JZK", "急诊中心"),
("麻醉科", "MZK", "手术楼2楼"),
("放射科", "FSK", "影像中心"),
("检验科", "JYK", "检验中心"),
("药剂科", "YJK", "药房1楼"),
("康复科", "KFK", "康复中心"),
]
for i in range(NUM_DEPARTMENTS):
if i < len(common_depts):
name, code, location = common_depts[i]
else:
name = f"专科{i+1}"
code = f"ZK{i+1:03d}"
location = f"门诊部{random.randint(1, 5)}"
dept_id = str(uuid.uuid4())
department = {
'department_id': dept_id,
'code': code,
'name': name,
'location': location,
'phone': f'0{random.randint(10, 21)}-{random.randint(1000, 9999)}{random.randint(1000, 9999)}'
}
departments.append(department)
department_ids.append(dept_id)
save_to_csv('department.csv', departments)
return department_ids
def generate_doctors(department_ids):
"""生成医生数据"""
print("生成医生数据...")
doctors = []
doctor_ids = []
# 医生职称
titles = ["主任医师", "副主任医师", "主治医师", "住院医师", "医师"]
for i in range(NUM_DOCTORS):
doctor_id = str(uuid.uuid4())
doctor = {
'doctor_id': doctor_id,
'department_id': random.choice(department_ids),
'name': fake.name(),
'title': random.choice(titles),
'license_no': f'Y{random.randint(100000, 999999)}',
'contact': fake.phone_number(),
'work_status': random.choice(['active', 'active', 'active', 'on_leave', 'resigned']) # 80%活跃
}
doctors.append(doctor)
doctor_ids.append(doctor_id)
save_to_csv('doctor.csv', doctors)
return doctor_ids
def generate_patients():
"""生成患者数据"""
print("生成患者数据...")
patients = []
patient_ids = []
for i in range(NUM_PATIENTS):
patient_id = str(uuid.uuid4())
gender = random.choice(['M', 'F'])
birth_date = fake.date_of_birth(minimum_age=1, maximum_age=90)
patient = {
'patient_id': patient_id,
'name': fake.name(),
'id_number': fake.ssn(),
'mobile': fake.phone_number(),
'email': fake.email() if random.random() > 0.3 else None,
'gender': gender,
'birth_date': birth_date.strftime('%Y-%m-%d'),
'address': fake.address(),
'status': random.choice(['active', 'active', 'suspended']), # 66%活跃
'created_at': random_date(START_DATE, END_DATE).strftime('%Y-%m-%d %H:%M:%S'),
'updated_at': random_date(START_DATE, END_DATE).strftime('%Y-%m-%d %H:%M:%S')
}
patients.append(patient)
patient_ids.append(patient_id)
save_to_csv('patient.csv', patients)
return patient_ids
def generate_drugs():
"""生成药品数据"""
print("生成药品数据...")
drugs = []
drug_ids = []
# 常见药品
common_drugs = [
("阿司匹林", "拜阿司匹灵", "100mg*30片", ""),
("二甲双胍", "格华止", "0.5g*20片", ""),
("氨氯地平", "络活喜", "5mg*7片", ""),
("阿托伐他汀", "立普妥", "20mg*7片", ""),
("胰岛素", "诺和灵", "300单位", ""),
("布洛芬", "芬必得", "0.3g*20粒", ""),
("头孢克肟", "世福素", "100mg*6片", ""),
("氯雷他定", "开瑞坦", "10mg*6片", ""),
("奥美拉唑", "洛赛克", "20mg*7粒", ""),
("缬沙坦", "代文", "80mg*7粒", ""),
]
# 剂型
dosage_forms = ["片剂", "胶囊", "注射液", "颗粒剂", "软膏", "喷雾剂", "滴眼液"]
# 单位
units = ["", "", "", "", "", "", "g", "mg", "ml"]
for i in range(NUM_DRUGS):
drug_id = str(uuid.uuid4())
if i < len(common_drugs):
generic_name, trade_name, specification, unit = common_drugs[i]
else:
generic_name = f"药品{i+1}"
trade_name = f"商品名{i+1}" if random.random() > 0.3 else None
specification = f"{random.randint(10, 500)}mg*{random.randint(10, 100)}{random.choice(['', '', ''])}"
unit = random.choice(units)
drug = {
'drug_id': drug_id,
'generic_name': generic_name,
'trade_name': trade_name,
'specification': specification,
'unit': unit,
'dosage_form': random.choice(dosage_forms),
'manufacturer': fake.company() + "制药有限公司",
'approval_no': f"国药准字H{random.randint(10000000, 99999999)}",
'is_prescription': random.choice([True, False]),
'shelf_life_days': random.choice([365, 730, 1095, 1460]),
'storage_conditions': random.choice(["常温保存", "阴凉处保存", "2-8℃冷藏", "避光保存"]),
'created_at': random_date(START_DATE, END_DATE).strftime('%Y-%m-%d %H:%M:%S')
}
drugs.append(drug)
drug_ids.append(drug_id)
save_to_csv('drug.csv', drugs)
return drug_ids
def generate_inventory_batches(drug_ids, department_ids):
"""生成库存批次数据"""
print("生成库存批次数据...")
batches = []
for i in range(NUM_BATCHES):
batch_id = str(uuid.uuid4())
production_date = random_date(datetime(2022, 1, 1), datetime(2024, 6, 30))
expiry_date = production_date + timedelta(days=random.choice([365, 730, 1095]))
purchase_quantity = random.randint(100, 10000)
current_stock = max(0, purchase_quantity - random.randint(0, purchase_quantity * 3 // 4))
batch = {
'batch_id': batch_id,
'drug_id': random.choice(drug_ids),
'batch_no': f"B{random.randint(20220000, 20249999)}",
'production_date': production_date.strftime('%Y-%m-%d'),
'expiry_date': expiry_date.strftime('%Y-%m-%d'),
'purchase_quantity': purchase_quantity,
'purchase_price': round(random.uniform(5.0, 500.0), 2),
'purchase_date': random_date(production_date, expiry_date - timedelta(days=30)).strftime('%Y-%m-%d %H:%M:%S'),
'current_stock': current_stock,
'department_id': random.choice(department_ids),
'min_stock_threshold': random.choice([20, 50, 100, 200]),
'last_stock_in': random_date(START_DATE, END_DATE).strftime('%Y-%m-%d %H:%M:%S') if random.random() > 0.2 else None,
'last_stock_out': random_date(START_DATE, END_DATE).strftime('%Y-%m-%d %H:%M:%S') if current_stock < purchase_quantity else None
}
batches.append(batch)
save_to_csv('inventory_batch.csv', batches)
def generate_schedules(doctor_ids):
"""生成排班数据"""
print("生成排班数据...")
schedules = []
schedule_ids = []
# 生成未来30天的排班
base_date = datetime.now()
for i in range(NUM_SCHEDULES):
schedule_id = str(uuid.uuid4())
work_date = base_date + timedelta(days=random.randint(1, 30))
max_appointments = random.choice([10, 15, 20, 25, 30])
remaining_appointments = random.randint(0, max_appointments)
schedule = {
'schedule_id': schedule_id,
'doctor_id': random.choice(doctor_ids),
'work_date': work_date.strftime('%Y-%m-%d'),
'slot_no': random.randint(1, 3), # 1:上午, 2:下午, 3:晚上
'max_appointments': max_appointments,
'remaining_appointments': remaining_appointments
}
schedules.append(schedule)
schedule_ids.append(schedule_id)
save_to_csv('schedule.csv', schedules)
return schedule_ids
def generate_appointments(patient_ids, schedule_ids):
"""生成预约数据"""
print("生成预约数据...")
appointments = []
# 确保每个排班的预约数不超过剩余预约数
schedule_usage = {sid: 0 for sid in schedule_ids}
for i in range(NUM_APPOINTMENTS):
appointment_id = str(uuid.uuid4())
schedule_id = random.choice(schedule_ids)
# 获取排班信息这里简化处理实际应该从schedules数据中获取
max_apt = 20 # 假设最大20
# 确保不超过最大预约数
if schedule_usage[schedule_id] >= max_apt:
continue
schedule_usage[schedule_id] += 1
# 预约时间设置为排班日期的随机时间
schedule_date = datetime.now() + timedelta(days=random.randint(1, 30))
appointment_time = schedule_date.replace(
hour=random.choice([8, 9, 10, 14, 15, 16, 18, 19]),
minute=random.randint(0, 59)
)
status = random.choice(['scheduled', 'scheduled', 'checked_in', 'cancelled', 'completed'])
appointment = {
'appointment_id': appointment_id,
'patient_id': random.choice(patient_ids),
'schedule_id': schedule_id,
'appointment_time': appointment_time.strftime('%Y-%m-%d %H:%M:%S'),
'status': status,
'booking_channel': random.choice(['online', 'offline', 'phone']),
'created_at': (appointment_time - timedelta(days=random.randint(1, 7))).strftime('%Y-%m-%d %H:%M:%S'),
'cancelled_at': appointment_time.strftime('%Y-%m-%d %H:%M:%S') if status == 'cancelled' and random.random() > 0.5 else None,
'cancel_reason': random.choice(["患者取消", "医生停诊", "时间冲突", None]) if status == 'cancelled' else None
}
appointments.append(appointment)
save_to_csv('appointment.csv', appointments)
def generate_medical_records(patient_ids, doctor_ids):
"""生成电子病历数据"""
print("生成电子病历数据...")
medical_records = []
record_ids = []
# 常见疾病
common_diseases = [
"上呼吸道感染", "高血压", "糖尿病", "胃炎", "湿疹",
"过敏性鼻炎", "颈椎病", "腰椎间盘突出", "哮喘", "冠心病"
]
for i in range(NUM_MEDICAL_RECORDS):
record_id = str(uuid.uuid4())
visit_time = random_date(START_DATE, END_DATE)
# 随机选择1-3个诊断
num_diagnoses = random.randint(1, 3)
diagnoses = random.sample(common_diseases, num_diagnoses)
medical_record = {
'record_id': record_id,
'patient_id': random.choice(patient_ids),
'doctor_id': random.choice(doctor_ids),
'appointment_id': str(uuid.uuid4()) if random.random() > 0.3 else None,
'visit_time': visit_time.strftime('%Y-%m-%d %H:%M:%S'),
'chief_complaint': random.choice(["头痛", "发热", "咳嗽", "腹痛", "乏力", "皮疹", "心悸"]),
'present_illness': fake.text(max_nb_chars=200),
'past_history': fake.text(max_nb_chars=150),
'allergies': random.choice(["青霉素过敏", "海鲜过敏", "无已知过敏", "花粉过敏"]),
'diagnosis_codes': ",".join(diagnoses),
'treatment_plan': fake.text(max_nb_chars=300),
'notes': fake.text(max_nb_chars=200) if random.random() > 0.5 else None,
'attachments': None,
'created_at': visit_time.strftime('%Y-%m-%d %H:%M:%S'),
'updated_at': (visit_time + timedelta(days=random.randint(0, 7))).strftime('%Y-%m-%d %H:%M:%S')
}
medical_records.append(medical_record)
record_ids.append(record_id)
save_to_csv('medical_record.csv', medical_records)
return record_ids
def generate_prescriptions(record_ids, doctor_ids):
"""生成处方数据"""
print("生成处方数据...")
prescriptions = []
prescription_ids = []
# 确保每个病历只有一个处方
used_records = set()
for i in range(NUM_PRESCRIPTIONS):
if len(record_ids) == 0:
break
# 随机选择一个未使用的病历
available_records = [r for r in record_ids if r not in used_records]
if not available_records:
break
record_id = random.choice(available_records)
used_records.add(record_id)
prescription_id = str(uuid.uuid4())
issued_at = random_date(START_DATE, END_DATE)
prescription = {
'prescription_id': prescription_id,
'record_id': record_id,
'doctor_id': random.choice(doctor_ids),
'issued_at': issued_at.strftime('%Y-%m-%d %H:%M:%S'),
'status': random.choice(['pending', 'approved', 'dispensed', 'rejected']),
'pharmacist_id': str(uuid.uuid4()) if random.random() > 0.5 else None,
'reviewed_at': (issued_at + timedelta(hours=random.randint(1, 24))).strftime('%Y-%m-%d %H:%M:%S') if random.random() > 0.3 else None
}
prescriptions.append(prescription)
prescription_ids.append(prescription_id)
save_to_csv('prescription.csv', prescriptions)
return prescription_ids
def generate_prescription_items(prescription_ids, drug_ids):
"""生成处方明细数据"""
print("生成处方明细数据...")
prescription_items = []
# 处方药品组合
drug_combinations = [
(["阿司匹林", "二甲双胍"], 1),
(["头孢克肟", "布洛芬"], 2),
(["胰岛素", "氯雷他定"], 1),
(["奥美拉唑", "缬沙坦"], 2),
]
item_count = 0
prescription_drug_map = {} # 记录每个处方已使用的药品
for prescription_id in prescription_ids:
if item_count >= NUM_PRESCRIPTION_ITEMS:
break
# 每个处方1-5种药品
num_items = random.randint(1, 5)
for item_num in range(num_items):
if item_count >= NUM_PRESCRIPTION_ITEMS:
break
item_id = str(uuid.uuid4())
# 确保同一种药品在同一处方中只出现一次
available_drugs = [d for d in drug_ids if d not in prescription_drug_map.get(prescription_id, set())]
if not available_drugs:
break
drug_id = random.choice(available_drugs)
# 添加到处方药品映射
if prescription_id not in prescription_drug_map:
prescription_drug_map[prescription_id] = set()
prescription_drug_map[prescription_id].add(drug_id)
quantity = random.randint(1, 30)
price = round(random.uniform(5.0, 200.0), 2)
prescription_item = {
'item_id': item_id,
'prescription_id': prescription_id,
'drug_id': drug_id,
'dosage': random.choice(["1片", "2片", "1粒", "2粒", "1支"]),
'frequency': random.choice(["每日1次", "每日2次", "每日3次", "每6小时1次"]),
'duration_days': random.randint(3, 14),
'quantity': quantity,
'instructions': random.choice(["饭后服用", "饭前服用", "遵医嘱", None]),
'price': price,
'amount': round(price * quantity, 2)
}
prescription_items.append(prescription_item)
item_count += 1
save_to_csv('prescription_item.csv', prescription_items)
def save_to_csv(filename, data):
"""保存数据到CSV文件"""
if not data:
print(f"警告: {filename} 没有数据")
return
with open(filename, 'w', newline='', encoding='utf-8-sig') as csvfile:
fieldnames = data[0].keys()
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(data)
print(f"已生成 {filename},包含 {len(data)} 条记录")
def main():
"""主函数"""
print("开始生成医疗系统测试数据...")
start_time = time.time()
try:
# 1. 生成科室
department_ids = generate_departments()
# 2. 生成医生
doctor_ids = generate_doctors(department_ids)
# 3. 生成患者
patient_ids = generate_patients()
# 4. 生成药品
drug_ids = generate_drugs()
# 5. 生成库存批次
generate_inventory_batches(drug_ids, department_ids)
# 6. 生成排班
schedule_ids = generate_schedules(doctor_ids)
# 7. 生成预约
generate_appointments(patient_ids, schedule_ids)
# 8. 生成电子病历
record_ids = generate_medical_records(patient_ids, doctor_ids)
# 9. 生成处方
prescription_ids = generate_prescriptions(record_ids, doctor_ids)
# 10. 生成处方明细
generate_prescription_items(prescription_ids, drug_ids)
end_time = time.time()
print(f"\n数据生成完成!总耗时: {end_time - start_time:.2f}")
print("\n生成的CSV文件:")
print("1. department.csv - 科室表")
print("2. doctor.csv - 医生表")
print("3. patient.csv - 患者表")
print("4. drug.csv - 药品表")
print("5. inventory_batch.csv - 库存批次表")
print("6. schedule.csv - 排班表")
print("7. appointment.csv - 预约表")
print("8. medical_record.csv - 电子病历表")
print("9. prescription.csv - 处方表")
print("10. prescription_item.csv - 处方明细表")
print("\n可以使用以下命令导入PostgreSQL:")
print(r'psql -h localhost -U username -d database -c "\copy table_name FROM \'file.csv\' DELIMITER \',\' CSV HEADER;"')
except Exception as e:
print(f"生成数据时出错: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()

@ -0,0 +1,92 @@
-- ============================================
-- 功能04优化医生当日待诊列表
-- ============================================
WITH schedule_ids AS (
SELECT schedule_id
FROM schedule
WHERE doctor_id = '550e8400-e29b-41d4-a716-446655440004'
AND work_date = '2024-12-15'
),
appointment_data AS (
SELECT a.appointment_id, a.patient_id, a.appointment_time
FROM appointment a
WHERE a.schedule_id IN (SELECT schedule_id FROM schedule_ids)
AND a.status = 'scheduled'
)
SELECT
ad.appointment_id,
p.name AS patient_name,
ad.appointment_time
FROM appointment_data ad
JOIN patient p ON ad.patient_id = p.patient_id
ORDER BY ad.appointment_time;
-- ============================================
-- 功能13优化扣减药品库存
-- ============================================
-- 优化后(使用索引覆盖)
CREATE INDEX idx_inventory_batch_drug_stock_expiry_covering
ON inventory_batch(drug_id, current_stock, expiry_date)
INCLUDE (batch_id);
-- 然后使用索引优化查询
EXPLAIN (ANALYZE, VERBOSE)
SELECT batch_id
FROM inventory_batch
WHERE drug_id = '550e8400-e29b-41d4-a716-446655440018'
AND current_stock >= 10
ORDER BY expiry_date
LIMIT 1;
-- ============================================
-- 功能18优化统计医生工作量
-- ============================================
-- 优化后(使用日期范围,可索引优化)
SELECT d.name,
COUNT(mr.record_id) as record_count
FROM doctor d
LEFT JOIN medical_record mr ON d.doctor_id = mr.doctor_id
AND mr.visit_time >= '2024-12-01'::date
AND mr.visit_time < '2025-01-01'::date
WHERE d.work_status = 'active'
GROUP BY d.doctor_id, d.name;
-- ============================================
-- 功能16优化查询库存预警药品
-- ============================================
-- 优化后使用子查询减少JOIN
WITH drug_stock AS (
SELECT
drug_id,
SUM(current_stock) as total_stock,
MIN(min_stock_threshold) as min_threshold
FROM inventory_batch
GROUP BY drug_id
HAVING SUM(current_stock) < MIN(min_stock_threshold)
)
SELECT
d.generic_name,
ds.total_stock,
ds.min_threshold
FROM drug_stock ds
JOIN drug d ON ds.drug_id = d.drug_id;
-- ============================================
-- 功能16优化查询库存预警药品
-- ============================================
-- 优化后减少JOIN层级使用子查询
SELECT
dep.name,
COALESCE(apt_stats.appointment_count, 0) as appointment_count
FROM department dep
LEFT JOIN (
SELECT
d.department_id,
COUNT(a.appointment_id) as appointment_count
FROM doctor d
JOIN schedule s ON d.doctor_id = s.doctor_id
LEFT JOIN appointment a ON s.schedule_id = a.schedule_id
WHERE s.work_date = '2024-12-15'
GROUP BY d.department_id
) apt_stats ON dep.department_id = apt_stats.department_id
ORDER BY dep.name;

@ -0,0 +1,234 @@
### 索引优化策略
```sql
-- 1. 排班表索引优化
CREATE INDEX IF NOT EXISTS idx_schedule_doctor_date_remain
ON schedule(doctor_id, work_date, remaining_appointments);
-- 2. 预约表索引优化
CREATE INDEX IF NOT EXISTS idx_appointment_schedule_status_time
ON appointment(schedule_id, status, appointment_time);
CREATE INDEX IF NOT EXISTS idx_appointment_status
ON appointment(status);
-- 3. 库存批次表索引优化
CREATE INDEX IF NOT EXISTS idx_inventory_batch_drug_stock_expiry
ON inventory_batch(drug_id, current_stock, expiry_date);
-- 4. 药品表索引优化(已有主键索引)
CREATE INDEX IF NOT EXISTS idx_drug_generic_name
ON drug(generic_name);
-- 5. 医生表索引优化
CREATE INDEX IF NOT EXISTS idx_doctor_status
ON doctor(work_status);
-- 6. 电子病历表索引优化
CREATE INDEX IF NOT EXISTS idx_medical_record_doctor_visit
ON medical_record(doctor_id, visit_time);
-- 7. 处方明细表索引优化
CREATE INDEX IF NOT EXISTS idx_prescription_item_prescription
ON prescription_item(prescription_id);
-- 8. 患者表索引优化
CREATE INDEX IF NOT EXISTS idx_patient_name
ON patient(name);
```
### 查询重写优化
#### 功能04优化医生当日待诊列表
```sql
-- 优化前存在Seq Scan
EXPLAIN (ANALYZE, VERBOSE)
SELECT a.appointment_id, p.name AS patient_name, a.appointment_time
FROM appointment a
JOIN patient p ON a.patient_id = p.patient_id
JOIN schedule s ON a.schedule_id = s.schedule_id
WHERE s.doctor_id = '550e8400-e29b-41d4-a716-446655440004'
AND s.work_date = '2024-12-15'
AND a.status = 'scheduled'
ORDER BY a.appointment_time;
-- 优化后使用CTE和优化JOIN顺序
WITH schedule_ids AS (
SELECT schedule_id
FROM schedule
WHERE doctor_id = '550e8400-e29b-41d4-a716-446655440004'
AND work_date = '2024-12-15'
),
appointment_data AS (
SELECT a.appointment_id, a.patient_id, a.appointment_time
FROM appointment a
WHERE a.schedule_id IN (SELECT schedule_id FROM schedule_ids)
AND a.status = 'scheduled'
)
SELECT
ad.appointment_id,
p.name AS patient_name,
ad.appointment_time
FROM appointment_data ad
JOIN patient p ON ad.patient_id = p.patient_id
ORDER BY ad.appointment_time;
```
| | | | | | | | | |
|---|---|---|---|---|---|---|---|---|
|SELECT (select)||||||||Planning Time = 0.323; Triggers = []; Execution Time = 0.031;|
|SORT (sort)||1|0|17.18|0.012|17.17|0.012|Parallel Aware = false; Async Capable = false; Plan Width = 32; Actual Loops = 1; Sort Key = ["a.appointment_time"]; Sort Method = quicksort; Sort Space Used = 25; Sort Space Type = Memory;|
|NESTED_LOOPS (Nested Loop)||1|0|17.16|0.008|0.98|0.008|Parent Relationship = Outer; Parallel Aware = false; Async Capable = false; Join Type = Inner; Plan Width = 32; Actual Loops = 1; Inner Unique = true;|
|NESTED_LOOPS (Nested Loop)||1|0|16.75|0.008|0.7|0.008|Parent Relationship = Outer; Parallel Aware = false; Async Capable = false; Join Type = Inner; Plan Width = 40; Actual Loops = 1; Inner Unique = false;|
|INDEX_SCAN (index scan)|table: schedule; index: idx_schedule_doctor_date_remain;|1|0|8.31|0.008|0.29|0.008|Parent Relationship = Outer; Parallel Aware = false; Async Capable = false; Scan Direction = Forward; Alias = schedule; Plan Width = 16; Actual Loops = 1; Index Cond = ((doctor_id = '550e8400-e29b-41d4-a716-446655440004'::uuid) AND (work_date = '2024-12-15'::date)); Rows Removed by Index Recheck = 0;|
|INDEX_SCAN (index scan)|table: appointment; index: idx_appointment_schedule_status_time;|1|0|8.43|0.0|0.41|0.0|Parent Relationship = Inner; Parallel Aware = false; Async Capable = false; Scan Direction = Forward; Alias = a; Plan Width = 56; Actual Loops = 0; Index Cond = ((schedule_id = schedule.schedule_id) AND ((status)::text = 'scheduled'::text)); Rows Removed by Index Recheck = 0;|
|INDEX_SCAN (index scan)|table: patient; index: patient_pkey;|1|0|0.41|0.0|0.29|0.0|Parent Relationship = Inner; Parallel Aware = false; Async Capable = false; Scan Direction = Forward; Alias = p; Plan Width = 24; Actual Loops = 0; Index Cond = (patient_id = a.patient_id); Rows Removed by Index Recheck = 0;|
#### 功能13优化扣减药品库存
```sql
-- 优化前(全表扫描+排序)
EXPLAIN (ANALYZE, VERBOSE)
SELECT batch_id FROM inventory_batch
WHERE drug_id = '550e8400-e29b-41d4-a716-446655440018'
AND current_stock >= 10
ORDER BY expiry_date ASC LIMIT 1;
-- 优化后(使用索引覆盖)
CREATE INDEX idx_inventory_batch_drug_stock_expiry_covering
ON inventory_batch(drug_id, current_stock, expiry_date)
INCLUDE (batch_id);
-- 然后使用索引优化查询
EXPLAIN (ANALYZE, VERBOSE)
SELECT batch_id
FROM inventory_batch
WHERE drug_id = '550e8400-e29b-41d4-a716-446655440018'
AND current_stock >= 10
ORDER BY expiry_date ASC
LIMIT 1;
```
Limit (cost=4.41..4.41 rows=1 width=20) (actual time=0.071..0.072 rows=0 loops=1)
" Output: batch_id, expiry_date"
-> Sort (cost=4.41..4.42 rows=5 width=20) (actual time=0.071..0.071 rows=0 loops=1)
" Output: batch_id, expiry_date"
Sort Key: inventory_batch.expiry_date
Sort Method: quicksort Memory: 25kB
-> Index Only Scan using idx_inventory_batch_drug_stock_expiry_covering on hms.inventory_batch (cost=0.28..4.38 rows=5 width=20) (actual time=0.067..0.067 rows=0 loops=1)
" Output: batch_id, expiry_date"
Index Cond: ((inventory_batch.drug_id = '550e8400-e29b-41d4-a716-446655440018'::uuid) AND (inventory_batch.current_stock >= 10))
Heap Fetches: 0
Planning Time: 1.619 ms
Execution Time: 0.095 ms
#### 功能18优化统计医生工作量
```sql
-- 优化前使用EXTRACT函数无法使用索引
SELECT d.name, COUNT(mr.record_id) as record_count
FROM doctor d
LEFT JOIN medical_record mr ON d.doctor_id = mr.doctor_id
AND EXTRACT(YEAR FROM mr.visit_time) = 2024
AND EXTRACT(MONTH FROM mr.visit_time) = 12
WHERE d.work_status = 'active'
GROUP BY d.doctor_id, d.name;
-- 优化后(使用日期范围,可索引优化)
SELECT
d.name,
COUNT(mr.record_id) as record_count
FROM doctor d
LEFT JOIN medical_record mr ON d.doctor_id = mr.doctor_id
AND mr.visit_time >= '2024-12-01'::date
AND mr.visit_time < '2025-01-01'::date
WHERE d.work_status = 'active'
GROUP BY d.doctor_id, d.name;
```
| | | | | | | | | |
|---|---|---|---|---|---|---|---|---|
|SELECT (select)||||||||Planning Time = 0.244; Triggers = []; Execution Time = 1.622;|
|AGGREGATE (aggregate)||1194|1194|2966.52|1.514|2954.58|1.446|Strategy = Hashed; Partial Mode = Simple; Parallel Aware = false; Async Capable = false; Plan Width = 32; Actual Loops = 1; Group Key = ["d.doctor_id"]; Planned Partitions = 0; HashAgg Batches = 1; Peak Memory Usage = 193; Disk Usage = 0;|
|HASH_JOIN (hash join)||1194|1295|2948.61|1.284|672.45|0.725|Parent Relationship = Outer; Parallel Aware = false; Async Capable = false; Join Type = Right; Plan Width = 40; Actual Loops = 1; Inner Unique = true; Hash Cond = (mr.doctor_id = d.doctor_id);|
|BITMAP_INDEX_SCAN (Bitmap Heap Scan)|table: medical_record;|899|865|2878.32|0.834|604.52|0.414|Parent Relationship = Outer; Parallel Aware = false; Async Capable = false; Alias = mr; Plan Width = 32; Actual Loops = 1; Recheck Cond = ((visit_time >= '2024-12-01'::date) AND (visit_time < '2025-01-01'::date)); Rows Removed by Index Recheck = 0; Exact Heap Blocks = 781; Lossy Heap Blocks = 0;|
|BITMAP_INDEX_SCAN (bitmap index scan)|index: idx_medical_record_doctor_visit;|899|865|604.3|0.354|0.0|0.354|Parent Relationship = Outer; Parallel Aware = false; Async Capable = false; Plan Width = 0; Actual Loops = 1; Index Cond = ((visit_time >= '2024-12-01'::date) AND (visit_time < '2025-01-01'::date));|
|TRANSFORM (Hash)||1194|1194|53.0|0.303|53.0|0.303|Parent Relationship = Inner; Parallel Aware = false; Async Capable = false; Plan Width = 24; Actual Loops = 1; Hash Buckets = 2048; Original Hash Buckets = 2048; Hash Batches = 1; Original Hash Batches = 1; Peak Memory Usage = 82;|
|SEQ_SCAN (Seq Scan)|table: doctor;|1194|1194|53.0|0.201|0.0|0.013|Parent Relationship = Outer; Parallel Aware = false; Async Capable = false; Alias = d; Plan Width = 24; Actual Loops = 1; Filter = ((work_status)::text = 'active'::text); Rows Removed by Filter = 806;|
#### 功能16优化查询库存预警药品
```sql
-- 优化前Hash Join + 全表扫描)
SELECT d.generic_name, SUM(ib.current_stock) AS total_stock
FROM drug d
JOIN inventory_batch ib ON d.drug_id = ib.drug_id
GROUP BY d.drug_id, d.generic_name, ib.min_stock_threshold
HAVING SUM(ib.current_stock) < ib.min_stock_threshold;
-- 优化后使用子查询减少JOIN
WITH drug_stock AS (
SELECT
drug_id,
SUM(current_stock) as total_stock,
MIN(min_stock_threshold) as min_threshold
FROM inventory_batch
GROUP BY drug_id
HAVING SUM(current_stock) < MIN(min_stock_threshold)
)
SELECT
d.generic_name,
ds.total_stock,
ds.min_threshold
FROM drug_stock ds
JOIN drug d ON ds.drug_id = d.drug_id;
```
| | | | | | | | | |
|---|---|---|---|---|---|---|---|---|
|SELECT (select)||||||||Planning Time = 1.723; Triggers = []; Execution Time = 1.241;|
|HASH_JOIN (hash join)||331|0|231.01|1.191|194.37|1.19|Parallel Aware = false; Async Capable = false; Join Type = Inner; Plan Width = 21; Actual Loops = 1; Inner Unique = true; Hash Cond = (d.drug_id = ds.drug_id);|
|SEQ_SCAN (Seq Scan)|table: drug;|1000|1|34.0|0.01|0.0|0.009|Parent Relationship = Outer; Parallel Aware = false; Async Capable = false; Alias = d; Plan Width = 25; Actual Loops = 1;|
|TRANSFORM (Hash)||331|0|190.24|1.176|190.24|1.175|Parent Relationship = Inner; Parallel Aware = false; Async Capable = false; Plan Width = 28; Actual Loops = 1; Hash Buckets = 1024; Original Hash Buckets = 1024; Hash Batches = 1; Original Hash Batches = 1; Peak Memory Usage = 8;|
|ACCESS (Subquery Scan)||331|0|190.24|1.175|174.5|1.175|Parent Relationship = Outer; Parallel Aware = false; Async Capable = false; Alias = ds; Plan Width = 28; Actual Loops = 1;|
|AGGREGATE (aggregate)||331|0|186.93|1.175|174.5|1.175|Strategy = Hashed; Partial Mode = Simple; Parent Relationship = Subquery; Parallel Aware = false; Async Capable = false; Plan Width = 28; Actual Loops = 1; Group Key = ["inventory_batch.drug_id"]; Filter = (sum(inventory_batch.current_stock) < min(inventory_batch.min_stock_threshold)); Planned Partitions = 0; HashAgg Batches = 1; Peak Memory Usage = 193; Disk Usage = 0; Rows Removed by Filter = 994;|
|SEQ_SCAN (Seq Scan)|table: inventory_batch;|5000|5000|137.0|0.22|0.0|0.005|Parent Relationship = Outer; Parallel Aware = false; Async Capable = false; Alias = inventory_batch; Plan Width = 24; Actual Loops = 1;|
#### 功能17优化统计科室预约量
```sql
-- 优化前(复杂嵌套循环)
SELECT dep.name, COUNT(a.appointment_id) as appointment_count
FROM department dep
JOIN doctor d ON dep.department_id = d.department_id
JOIN schedule s ON d.doctor_id = s.doctor_id
LEFT JOIN appointment a ON s.schedule_id = a.schedule_id
WHERE s.work_date = '2024-12-15'
GROUP BY dep.department_id, dep.name;
-- 优化后减少JOIN层级使用子查询
SELECT
dep.name,
COALESCE(apt_stats.appointment_count, 0) as appointment_count
FROM department dep
LEFT JOIN (
SELECT
d.department_id,
COUNT(a.appointment_id) as appointment_count
FROM doctor d
JOIN schedule s ON d.doctor_id = s.doctor_id
LEFT JOIN appointment a ON s.schedule_id = a.schedule_id
WHERE s.work_date = '2024-12-15'
GROUP BY d.department_id
) apt_stats ON dep.department_id = apt_stats.department_id
ORDER BY dep.name;
```
| | | | | | |
|---|---|---|---|---|---|
|SELECT (select)||||||
|SORT (sort)||100|702.93|702.68|Parallel Aware = false; Async Capable = false; Plan Width = 226; Sort Key = ["dep.name"];|
|HASH_JOIN (hash join)||100|699.36|688.08|Parent Relationship = Outer; Parallel Aware = false; Async Capable = false; Join Type = Left; Plan Width = 226; Inner Unique = true; Hash Cond = (dep.department_id = apt_stats.department_id);|
|SEQ_SCAN (Seq Scan)|table: department;|100|11.0|0.0|Parent Relationship = Outer; Parallel Aware = false; Async Capable = false; Alias = dep; Plan Width = 234;|
|TRANSFORM (Hash)||1|688.07|688.07|Parent Relationship = Inner; Parallel Aware = false; Async Capable = false; Plan Width = 24;|
|ACCESS (Subquery Scan)||1|688.07|688.04|Parent Relationship = Outer; Parallel Aware = false; Async Capable = false; Alias = apt_stats; Plan Width = 24;|
|AGGREGATE (aggregate)||1|688.06|688.04|Strategy = Sorted; Partial Mode = Simple; Parent Relationship = Subquery; Parallel Aware = false; Async Capable = false; Plan Width = 24; Group Key = ["d.department_id"];|
|SORT (sort)||1|688.05|688.04|Parent Relationship = Outer; Parallel Aware = false; Async Capable = false; Plan Width = 32; Sort Key = ["d.department_id"];|
|NESTED_LOOPS (Nested Loop)||1|688.03|0.69|Parent Relationship = Outer; Parallel Aware = false; Async Capable = false; Join Type = Left; Plan Width = 32; Inner Unique = false;|
|NESTED_LOOPS (Nested Loop)||1|679.59|0.28|Parent Relationship = Outer; Parallel Aware = false; Async Capable = false; Join Type = Inner; Plan Width = 32; Inner Unique = true;|
|SEQ_SCAN (Seq Scan)|table: schedule;|1|671.29|0.0|Parent Relationship = Outer; Parallel Aware = false; Async Capable = false; Alias = s; Plan Width = 32; Filter = (work_date = '2024-12-15'::date);|
|INDEX_SCAN (index scan)|table: doctor; index: doctor_pkey;|1|8.29|0.28|Parent Relationship = Inner; Parallel Aware = false; Async Capable = false; Scan Direction = Forward; Alias = d; Plan Width = 32; Index Cond = (doctor_id = s.doctor_id);|
|INDEX_SCAN (index scan)|table: appointment; index: idx_appointment_schedule_status_time;|1|8.43|0.41|Parent Relationship = Inner; Parallel Aware = false; Async Capable = false; Scan Direction = Forward; Alias = a; Plan Width = 32; Index Cond = (schedule_id = s.schedule_id);|

@ -0,0 +1,252 @@
-- ============================================
-- 原始SQL测试文件
-- 医院管理系统查询性能测试
-- 测试环境PostgreSQL 17DataGrip 2025.2.5
-- 数据规模:万级到十万级
-- ============================================
-- ============================================
-- 功能01查询医生排班号源
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
SELECT schedule_id, slot_no, remaining_appointments
FROM schedule
WHERE doctor_id = '550e8400-e29b-41d4-a716-446655440000'
AND work_date = '2024-12-15'
AND remaining_appointments > 0;
-- ============================================
-- 功能02扣减排班号源
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
UPDATE schedule SET remaining_appointments = remaining_appointments - 1
WHERE schedule_id = '550e8400-e29b-41d4-a716-446655440001';
-- 回滚以保持数据一致性
ROLLBACK;
-- ============================================
-- 功能03创建预约记录
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
INSERT INTO appointment (appointment_id, patient_id, schedule_id, appointment_time, status)
VALUES (gen_random_uuid(), '89c5bb83-c039-4619-b458-7afe7a31fc64',
'8510e8ab-37eb-4b12-b69d-f24c076845ca',
'2024-12-15 09:30:00', 'scheduled');
-- 回滚以保持数据一致性
ROLLBACK;
-- ============================================
-- 功能04查询医生当日待诊列表
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
SELECT a.appointment_id, p.name AS patient_name, a.appointment_time
FROM appointment a
JOIN patient p ON a.patient_id = p.patient_id
JOIN schedule s ON a.schedule_id = s.schedule_id
WHERE s.doctor_id = '550e8400-e29b-41d4-a716-446655440004'
AND s.work_date = '2024-12-15'
AND a.status = 'scheduled'
ORDER BY a.appointment_time;
-- ============================================
-- 功能05更新预约状态为"已签到"
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
UPDATE appointment SET status = 'checked_in'
WHERE appointment_id = '550e8400-e29b-41d4-a716-446655440005';
-- 回滚以保持数据一致性
ROLLBACK;
-- ============================================
-- 功能06创建初始病历记录
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
INSERT INTO medical_record (record_id, patient_id, doctor_id, appointment_id, visit_time)
VALUES (gen_random_uuid(), '5e8d512a-22c9-4272-9cc7-b5b602b447c6',
'51e660c9-8270-439b-b33a-12230b5f27b2',
'3221a9b3-05e2-4375-92ce-f395ca05115f',
CURRENT_TIMESTAMP);
-- 回滚以保持数据一致性
ROLLBACK;
-- ============================================
-- 功能07更新病历诊断信息
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
UPDATE medical_record
SET chief_complaint = '头痛、发热3天',
diagnosis_codes = '上呼吸道感染,高血压',
updated_at = CURRENT_TIMESTAMP
WHERE record_id = '550e8400-e29b-41d4-a716-446655440009';
-- 回滚以保持数据一致性
ROLLBACK;
-- ============================================
-- 功能08创建处方主记录
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
INSERT INTO prescription (prescription_id, record_id, doctor_id, status)
VALUES ('5fcd0fcb-e522-43fb-b82e-8270c70aeb03', '05e5fe94-6b58-4e08-ae06-fc6990793c29',
'0193cd15-f4c4-4331-beaa-01fb67a3d00c', 'pending');
-- 回滚以保持数据一致性
ROLLBACK;
-- ============================================
-- 功能09添加处方明细项
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
INSERT INTO prescription_item (item_id, prescription_id, drug_id, dosage, frequency, duration_days, quantity)
VALUES (gen_random_uuid(), '550e8400-e29b-51d4-a716-446655440012',
'550e8400-e29b-41d4-a716-446655440013',
'1片', '每日3次', 7, 21);
-- 回滚以保持数据一致性
ROLLBACK;
-- ============================================
-- 功能10查询处方明细清单
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
SELECT d.generic_name, i.dosage, i.frequency, i.quantity
FROM prescription_item i
JOIN drug d ON i.drug_id = d.drug_id
WHERE i.prescription_id = '550e8400-e29b-41d4-a716-446655440014';
-- ============================================
-- 功能11更新处方审核状态
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
UPDATE prescription SET status = 'approved',
pharmacist_id = '550e8400-e29b-41d4-a716-446655440015',
reviewed_at = CURRENT_TIMESTAMP
WHERE prescription_id = '550e8400-e29b-41d4-a716-446655440016';
-- 回滚以保持数据一致性
ROLLBACK;
-- ============================================
-- 功能12查询药品库存可用数量
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
SELECT SUM(current_stock) AS total_stock
FROM inventory_batch
WHERE drug_id = '550e8400-e29b-41d4-a716-446655440017';
-- ============================================
-- 功能13扣减药品库存
-- ============================================
-- 查询符合条件的批次(用于查看执行计划)
EXPLAIN (ANALYZE, VERBOSE)
SELECT batch_id FROM inventory_batch
WHERE drug_id = '550e8400-e29b-41d4-a716-446655440018'
AND current_stock >= 10
ORDER BY expiry_date
LIMIT 1;
-- ============================================
-- 功能14更新处方状态为"已发药"
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
UPDATE prescription SET status = 'dispensed'
WHERE prescription_id = '550e8400-e29b-41d4-a716-446655440020';
-- 回滚以保持数据一致性
ROLLBACK;
-- ============================================
-- 功能15取消预约并回退号源
-- ============================================
-- 更新预约状态
EXPLAIN (ANALYZE, VERBOSE)
UPDATE appointment SET status = 'cancelled', cancelled_at = CURRENT_TIMESTAMP
WHERE appointment_id = '550e8400-e29b-41d4-a716-446655440021';
-- 回滚以保持数据一致性
ROLLBACK;
-- 回退号源(子查询版本)
EXPLAIN (ANALYZE, VERBOSE)
UPDATE schedule SET remaining_appointments = remaining_appointments + 1
WHERE schedule_id = (SELECT schedule_id FROM appointment
WHERE appointment_id = '550e8400-e29b-41d4-a716-446655440022');
-- 回滚以保持数据一致性
ROLLBACK;
-- ============================================
-- 功能16查询库存预警药品
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
SELECT d.generic_name, SUM(ib.current_stock) AS total_stock
FROM drug d
JOIN inventory_batch ib ON d.drug_id = ib.drug_id
GROUP BY d.drug_id, d.generic_name, ib.min_stock_threshold
HAVING SUM(ib.current_stock) < ib.min_stock_threshold;
-- ============================================
-- 功能17统计科室预约量
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
SELECT dep.name, COUNT(a.appointment_id) as appointment_count
FROM department dep
JOIN doctor d ON dep.department_id = d.department_id
JOIN schedule s ON d.doctor_id = s.doctor_id
LEFT JOIN appointment a ON s.schedule_id = a.schedule_id
WHERE s.work_date = '2024-12-15'
GROUP BY dep.department_id, dep.name;
-- ============================================
-- 功能18统计医生工作量
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
SELECT d.name, COUNT(mr.record_id) as record_count
FROM doctor d
LEFT JOIN medical_record mr ON d.doctor_id = mr.doctor_id
AND EXTRACT(YEAR FROM mr.visit_time) = 2024
AND EXTRACT(MONTH FROM mr.visit_time) = 12
WHERE d.work_status = 'active'
GROUP BY d.doctor_id, d.name;
-- ============================================
-- 数据分布统计(基本版)
-- ============================================
-- 1. 各表数据量统计
SELECT 'department' as table_name, COUNT(*) as row_count FROM department
UNION ALL
SELECT 'doctor', COUNT(*) FROM doctor
UNION ALL
SELECT 'patient', COUNT(*) FROM patient
UNION ALL
SELECT 'drug', COUNT(*) FROM drug
UNION ALL
SELECT 'inventory_batch', COUNT(*) FROM inventory_batch
UNION ALL
SELECT 'schedule', COUNT(*) FROM schedule
UNION ALL
SELECT 'appointment', COUNT(*) FROM appointment
UNION ALL
SELECT 'medical_record', COUNT(*) FROM medical_record
UNION ALL
SELECT 'prescription', COUNT(*) FROM prescription
UNION ALL
SELECT 'prescription_item', COUNT(*) FROM prescription_item;
-- 2. 现有索引检查
SELECT
t.relname as table_name,
i.relname as index_name,
a.attname as column_name
FROM pg_class t
JOIN pg_index ix ON t.oid = ix.indrelid
JOIN pg_class i ON i.oid = ix.indexrelid
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey)
WHERE t.relkind = 'r'
AND t.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')
ORDER BY t.relname, i.relname;

@ -0,0 +1,469 @@
```sql
-- ============================================
-- 功能01查询医生排班号源
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
SELECT schedule_id, slot_no, remaining_appointments
FROM schedule
WHERE doctor_id = '550e8400-e29b-41d4-a716-446655440000'
AND work_date = '2024-12-15'
AND remaining_appointments > 0;
```
Index Scan using schedule_doctor_id_work_date_slot_no_key on hms.schedule (cost=0.29..8.31 rows=1 width=22) (actual time=0.042..0.042 rows=0 loops=1)
" Output: schedule_id, slot_no, remaining_appointments"
Index Cond: ((schedule.doctor_id = '550e8400-e29b-41d4-a716-446655440000'::uuid) AND (schedule.work_date = '2024-12-15'::date))
Filter: (schedule.remaining_appointments > 0)
Planning Time: 0.201 ms
Execution Time: 0.063 ms
```sql
-- ============================================
-- 功能02扣减排班号源
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
UPDATE schedule SET remaining_appointments = remaining_appointments - 1
WHERE schedule_id = '550e8400-e29b-41d4-a716-446655440001';
```
Update on hms.schedule (cost=0.29..8.31 rows=0 width=0) (actual time=0.026..0.026 rows=0 loops=1)
-> Index Scan using schedule_pkey on hms.schedule (cost=0.29..8.31 rows=1 width=10) (actual time=0.018..0.018 rows=0 loops=1)
" Output: (remaining_appointments - 1), ctid"
Index Cond: (schedule.schedule_id = '550e8400-e29b-41d4-a716-446655440001'::uuid)
Planning Time: 0.095 ms
Execution Time: 0.245 ms
```sql
-- ============================================
-- 功能03创建预约记录
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
INSERT INTO appointment (appointment_id, patient_id, schedule_id, appointment_time, status)
VALUES (gen_random_uuid(), '89c5bb83-c039-4619-b458-7afe7a31fc64',
'8510e8ab-37eb-4b12-b69d-f24c076845ca',
'2024-12-15 09:30:00', 'scheduled');
```
Insert on hms.appointment (cost=0.00..0.02 rows=0 width=0) (actual time=0.078..0.078 rows=0 loops=1)
-> Result (cost=0.00..0.02 rows=1 width=704) (actual time=0.018..0.018 rows=1 loops=1)
" Output: gen_random_uuid(), '89c5bb83-c039-4619-b458-7afe7a31fc64'::uuid, '8510e8ab-37eb-4b12-b69d-f24c076845ca'::uuid, '2024-12-15 09:30:00'::timestamp without time zone, 'scheduled'::character varying(20), 'online'::character varying(20), CURRENT_TIMESTAMP, NULL::timestamp without time zone, NULL::character varying(255)"
Planning Time: 0.022 ms
Trigger RI_ConstraintTrigger_c_27573 for constraint fk_apt_patient: time=0.082 calls=1
Trigger RI_ConstraintTrigger_c_27578 for constraint fk_apt_schedule: time=0.044 calls=1
Execution Time: 0.215 ms
```sql
-- ============================================
-- 功能04查询医生当日待诊列表
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
SELECT a.appointment_id, p.name AS patient_name, a.appointment_time
FROM appointment a
JOIN patient p ON a.patient_id = p.patient_id
JOIN schedule s ON a.schedule_id = s.schedule_id
WHERE s.doctor_id = '550e8400-e29b-41d4-a716-446655440004'
AND s.work_date = '2024-12-15'
AND a.status = 'scheduled'
ORDER BY a.appointment_time;
```
Sort (cost=731.51..731.52 rows=1 width=32) (actual time=0.087..0.090 rows=0 loops=1)
" Output: a.appointment_id, p.name, a.appointment_time"
Sort Key: a.appointment_time
Sort Method: quicksort Memory: 25kB
-> Nested Loop (cost=8.60..731.50 rows=1 width=32) (actual time=0.053..0.055 rows=0 loops=1)
" Output: a.appointment_id, p.name, a.appointment_time"
Inner Unique: true
-> Hash Join (cost=8.32..731.09 rows=1 width=40) (actual time=0.052..0.054 rows=0 loops=1)
" Output: a.appointment_id, a.appointment_time, a.patient_id"
Inner Unique: true
Hash Cond: (a.schedule_id = s.schedule_id)
-> Seq Scan on hms.appointment a (cost=0.00..696.50 rows=10008 width=56) (actual time=0.028..0.028 rows=1 loops=1)
" Output: a.appointment_id, a.patient_id, a.schedule_id, a.appointment_time, a.status, a.booking_channel, a.created_at, a.cancelled_at, a.cancel_reason"
Filter: ((a.status)::text = 'scheduled'::text)
Rows Removed by Filter: 4
-> Hash (cost=8.31..8.31 rows=1 width=16) (actual time=0.014..0.015 rows=0 loops=1)
Output: s.schedule_id
Buckets: 1024 Batches: 1 Memory Usage: 8kB
-> Index Scan using schedule_doctor_id_work_date_slot_no_key on hms.schedule s (cost=0.29..8.31 rows=1 width=16) (actual time=0.014..0.014 rows=0 loops=1)
Output: s.schedule_id
Index Cond: ((s.doctor_id = '550e8400-e29b-41d4-a716-446655440004'::uuid) AND (s.work_date = '2024-12-15'::date))
-> Index Scan using patient_pkey on hms.patient p (cost=0.29..0.41 rows=1 width=24) (never executed)
" Output: p.patient_id, p.name, p.id_number, p.mobile, p.email, p.gender, p.birth_date, p.address, p.status, p.created_at, p.updated_at"
Index Cond: (p.patient_id = a.patient_id)
Planning Time: 1.273 ms
Execution Time: 0.119 ms
```sql
-- ============================================
-- 功能05更新预约状态为"已签到"
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
UPDATE appointment SET status = 'checked_in'
WHERE appointment_id = '550e8400-e29b-41d4-a716-446655440005';
```
Update on hms.appointment (cost=0.29..8.30 rows=0 width=0) (actual time=0.016..0.016 rows=0 loops=1)
-> Index Scan using appointment_pkey on hms.appointment (cost=0.29..8.30 rows=1 width=64) (actual time=0.015..0.015 rows=0 loops=1)
" Output: 'checked_in'::character varying(20), ctid"
Index Cond: (appointment.appointment_id = '550e8400-e29b-41d4-a716-446655440005'::uuid)
Planning Time: 0.079 ms
Execution Time: 0.061 ms
```sql
-- ============================================
-- 功能06创建初始病历记录
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
INSERT INTO medical_record (record_id, patient_id, doctor_id, appointment_id, visit_time)
VALUES (gen_random_uuid(), '5e8d512a-22c9-4272-9cc7-b5b602b447c6',
'51e660c9-8270-439b-b33a-12230b5f27b2',
'3221a9b3-05e2-4375-92ce-f395ca05115f',
CURRENT_TIMESTAMP);
```
Insert on hms.medical_record (cost=0.00..0.03 rows=0 width=0) (actual time=0.068..0.069 rows=0 loops=1)
-> Result (cost=0.00..0.03 rows=1 width=1312) (actual time=0.024..0.024 rows=1 loops=1)
" Output: gen_random_uuid(), '5e8d512a-22c9-4272-9cc7-b5b602b447c6'::uuid, '51e660c9-8270-439b-b33a-12230b5f27b2'::uuid, '3221a9b3-05e2-4375-92ce-f395ca05115f'::uuid, CURRENT_TIMESTAMP, NULL::text, NULL::text, NULL::text, NULL::character varying(255), NULL::character varying(255), NULL::text, NULL::text, NULL::text, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP"
Planning Time: 0.023 ms
Trigger RI_ConstraintTrigger_c_27594 for constraint fk_record_patient: time=0.112 calls=1
Trigger RI_ConstraintTrigger_c_27599 for constraint fk_record_doctor: time=0.056 calls=1
Trigger RI_ConstraintTrigger_c_27604 for constraint fk_record_appointment: time=0.558 calls=1
Execution Time: 0.813 ms
```sql
-- ============================================
-- 功能07更新病历诊断信息
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
UPDATE medical_record
SET chief_complaint = '头痛、发热3天',
diagnosis_codes = '上呼吸道感染,高血压',
updated_at = CURRENT_TIMESTAMP
WHERE record_id = '550e8400-e29b-41d4-a716-446655440009';
```
Update on hms.medical_record (cost=0.29..8.31 rows=0 width=0) (actual time=0.022..0.022 rows=0 loops=1)
-> Index Scan using medical_record_pkey on hms.medical_record (cost=0.29..8.31 rows=1 width=562) (actual time=0.020..0.020 rows=0 loops=1)
" Output: '头痛、发热3天'::text, '上呼吸道感染,高血压'::character varying(255), CURRENT_TIMESTAMP, ctid"
Index Cond: (medical_record.record_id = '550e8400-e29b-41d4-a716-446655440009'::uuid)
Planning Time: 0.064 ms
Execution Time: 0.056 ms
```sql
-- ============================================
-- 功能08创建处方主记录
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
INSERT INTO prescription (prescription_id, record_id, doctor_id, status)
VALUES ('5fcd0fcb-e522-43fb-b82e-8270c70aeb03', '05e5fe94-6b58-4e08-ae06-fc6990793c29',
'0193cd15-f4c4-4331-beaa-01fb67a3d00c', 'pending');
```
Insert on hms.prescription (cost=0.00..0.01 rows=0 width=0) (actual time=1.102..1.102 rows=0 loops=1)
-> Result (cost=0.00..0.01 rows=1 width=138) (actual time=0.002..0.003 rows=1 loops=1)
" Output: '5fcd0fcb-e522-43fb-b82e-8270c70aeb03'::uuid, '05e5fe94-6b58-4e08-ae06-fc6990793c29'::uuid, '0193cd15-f4c4-4331-beaa-01fb67a3d00c'::uuid, CURRENT_TIMESTAMP, 'pending'::character varying(20), NULL::uuid, NULL::timestamp without time zone"
Planning Time: 0.020 ms
Trigger RI_ConstraintTrigger_c_27620 for constraint fk_prescription_record: time=0.083 calls=1
Trigger RI_ConstraintTrigger_c_27625 for constraint fk_prescription_doctor: time=0.094 calls=1
Execution Time: 1.288 ms
```sql
-- ============================================
-- 功能09添加处方明细项
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
INSERT INTO prescription_item (item_id, prescription_id, drug_id, dosage, frequency, duration_days, quantity)
VALUES (gen_random_uuid(), '550e8400-e29b-51d4-a716-446655440012',
'550e8400-e29b-41d4-a716-446655440013',
'1片', '每日3次', 7, 21);
```
Insert on hms.prescription_item (cost=0.00..0.01 rows=0 width=0) (actual time=0.087..0.088 rows=0 loops=1)
-> Result (cost=0.00..0.01 rows=1 width=356) (actual time=0.019..0.019 rows=1 loops=1)
" Output: gen_random_uuid(), '550e8400-e29b-51d4-a716-446655440012'::uuid, '550e8400-e29b-41d4-a716-446655440013'::uuid, '1片'::character varying(50), '每日3次'::character varying(50), 7, 21, NULL::text, NULL::numeric(10,2), NULL::numeric(10,2)"
Planning Time: 0.025 ms
Execution Time: 0.102 ms
```sql
-- ============================================
-- 功能10查询处方明细清单
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
SELECT d.generic_name, i.dosage, i.frequency, i.quantity
FROM prescription_item i
JOIN drug d ON i.drug_id = d.drug_id
WHERE i.prescription_id = '550e8400-e29b-41d4-a716-446655440014';
```
Nested Loop (cost=4.71..40.80 rows=3 width=30) (actual time=0.358..0.358 rows=0 loops=1)
" Output: d.generic_name, i.dosage, i.frequency, i.quantity"
Inner Unique: true
-> Bitmap Heap Scan on hms.prescription_item i (cost=4.44..15.91 rows=3 width=37) (actual time=0.357..0.357 rows=0 loops=1)
" Output: i.item_id, i.prescription_id, i.drug_id, i.dosage, i.frequency, i.duration_days, i.quantity, i.instructions, i.price, i.amount"
Recheck Cond: (i.prescription_id = '550e8400-e29b-41d4-a716-446655440014'::uuid)
-> Bitmap Index Scan on prescription_item_prescription_id_drug_id_key (cost=0.00..4.44 rows=3 width=0) (actual time=0.339..0.339 rows=0 loops=1)
Index Cond: (i.prescription_id = '550e8400-e29b-41d4-a716-446655440014'::uuid)
-> Index Scan using drug_pkey on hms.drug d (cost=0.28..8.29 rows=1 width=25) (never executed)
" Output: d.drug_id, d.generic_name, d.trade_name, d.specification, d.unit, d.dosage_form, d.manufacturer, d.approval_no, d.is_prescription, d.shelf_life_days, d.storage_conditions, d.created_at"
Index Cond: (d.drug_id = i.drug_id)
Planning Time: 0.342 ms
Execution Time: 0.376 ms
```sql
-- ============================================
-- 功能11更新处方审核状态
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
UPDATE prescription SET status = 'approved',
pharmacist_id = '550e8400-e29b-41d4-a716-446655440015',
reviewed_at = CURRENT_TIMESTAMP
WHERE prescription_id = '550e8400-e29b-41d4-a716-446655440016';
```
Update on hms.prescription (cost=0.29..8.31 rows=0 width=0) (actual time=0.015..0.015 rows=0 loops=1)
-> Index Scan using prescription_pkey on hms.prescription (cost=0.29..8.31 rows=1 width=88) (actual time=0.014..0.014 rows=0 loops=1)
" Output: 'approved'::character varying(20), '550e8400-e29b-41d4-a716-446655440015'::uuid, CURRENT_TIMESTAMP, ctid"
Index Cond: (prescription.prescription_id = '550e8400-e29b-41d4-a716-446655440016'::uuid)
Planning Time: 0.075 ms
Execution Time: 0.042 ms
```sql
-- ============================================
-- 功能12查询药品库存可用数量
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
SELECT SUM(current_stock) AS total_stock
FROM inventory_batch
WHERE drug_id = '550e8400-e29b-41d4-a716-446655440017';
```
Aggregate (cost=149.51..149.52 rows=1 width=8) (actual time=0.500..0.500 rows=1 loops=1)
Output: sum(current_stock)
-> Seq Scan on hms.inventory_batch (cost=0.00..149.50 rows=5 width=4) (actual time=0.494..0.494 rows=0 loops=1)
" Output: batch_id, drug_id, batch_no, production_date, expiry_date, purchase_quantity, purchase_price, purchase_date, current_stock, department_id, min_stock_threshold, last_stock_in, last_stock_out"
Filter: (inventory_batch.drug_id = '550e8400-e29b-41d4-a716-446655440017'::uuid)
Rows Removed by Filter: 5000
Planning Time: 0.131 ms
Execution Time: 0.515 ms
```sql
-- ============================================
-- 功能13扣减药品库存
-- ============================================
-- 查询符合条件的批次(用于查看执行计划)
EXPLAIN (ANALYZE, VERBOSE)
SELECT batch_id FROM inventory_batch
WHERE drug_id = '550e8400-e29b-41d4-a716-446655440018'
AND current_stock >= 10
ORDER BY expiry_date ASC LIMIT 1;
```
Limit (cost=162.03..162.03 rows=1 width=20) (actual time=0.537..0.538 rows=0 loops=1)
" Output: batch_id, expiry_date"
-> Sort (cost=162.03..162.04 rows=5 width=20) (actual time=0.536..0.536 rows=0 loops=1)
" Output: batch_id, expiry_date"
Sort Key: inventory_batch.expiry_date
Sort Method: quicksort Memory: 25kB
-> Seq Scan on hms.inventory_batch (cost=0.00..162.00 rows=5 width=20) (actual time=0.525..0.525 rows=0 loops=1)
" Output: batch_id, expiry_date"
Filter: ((inventory_batch.current_stock >= 10) AND (inventory_batch.drug_id = '550e8400-e29b-41d4-a716-446655440018'::uuid))
Rows Removed by Filter: 5000
Planning Time: 0.127 ms
Execution Time: 0.550 ms
```sql
-- ============================================
-- 功能14更新处方状态为"已发药"
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
UPDATE prescription SET status = 'dispensed'
WHERE prescription_id = '550e8400-e29b-41d4-a716-446655440020';
```
Update on hms.prescription (cost=0.29..8.30 rows=0 width=0) (actual time=0.015..0.015 rows=0 loops=1)
-> Index Scan using prescription_pkey on hms.prescription (cost=0.29..8.30 rows=1 width=64) (actual time=0.014..0.014 rows=0 loops=1)
" Output: 'dispensed'::character varying(20), ctid"
Index Cond: (prescription.prescription_id = '550e8400-e29b-41d4-a716-446655440020'::uuid)
Planning Time: 0.080 ms
Execution Time: 0.031 ms
```sql
-- ============================================
-- 功能15取消预约并回退号源
-- ============================================
-- 更新预约状态
EXPLAIN (ANALYZE, VERBOSE)
UPDATE appointment SET status = 'cancelled', cancelled_at = CURRENT_TIMESTAMP
WHERE appointment_id = '550e8400-e29b-41d4-a716-446655440021';
```
Update on hms.appointment (cost=0.29..8.31 rows=0 width=0) (actual time=0.014..0.014 rows=0 loops=1)
-> Index Scan using appointment_pkey on hms.appointment (cost=0.29..8.31 rows=1 width=72) (actual time=0.013..0.013 rows=0 loops=1)
" Output: 'cancelled'::character varying(20), CURRENT_TIMESTAMP, ctid"
Index Cond: (appointment.appointment_id = '550e8400-e29b-41d4-a716-446655440021'::uuid)
Planning Time: 0.062 ms
Execution Time: 0.029 ms
```sql
-- 回退号源(子查询版本)
EXPLAIN (ANALYZE, VERBOSE)
UPDATE schedule SET remaining_appointments = remaining_appointments + 1
WHERE schedule_id = (SELECT schedule_id FROM appointment
WHERE appointment_id = '550e8400-e29b-41d4-a716-446655440022');
```
Update on hms.schedule (cost=8.59..16.61 rows=0 width=0) (actual time=0.019..0.019 rows=0 loops=1)
InitPlan 1
-> Index Scan using appointment_pkey on hms.appointment (cost=0.29..8.30 rows=1 width=16) (actual time=0.007..0.007 rows=0 loops=1)
Output: appointment.schedule_id
Index Cond: (appointment.appointment_id = '550e8400-e29b-41d4-a716-446655440022'::uuid)
-> Index Scan using schedule_pkey on hms.schedule (cost=0.29..8.31 rows=1 width=10) (actual time=0.018..0.018 rows=0 loops=1)
" Output: (schedule.remaining_appointments + 1), schedule.ctid"
Index Cond: (schedule.schedule_id = (InitPlan 1).col1)
Planning Time: 0.101 ms
Execution Time: 0.038 ms
```sql
-- ============================================
-- 功能16查询库存预警药品
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
SELECT d.generic_name, SUM(ib.current_stock) AS total_stock
FROM drug d
JOIN inventory_batch ib ON d.drug_id = ib.drug_id
GROUP BY d.drug_id, d.generic_name, ib.min_stock_threshold
HAVING SUM(ib.current_stock) < ib.min_stock_threshold;
```
HashAggregate (cost=234.18..284.18 rows=1333 width=37) (actual time=1.724..1.869 rows=14 loops=1)
" Output: d.generic_name, sum(ib.current_stock), d.drug_id, ib.min_stock_threshold"
" Group Key: d.drug_id, ib.min_stock_threshold"
Filter: (sum(ib.current_stock) < ib.min_stock_threshold)
Batches: 1 Memory Usage: 721kB
Rows Removed by Filter: 2896
-> Hash Join (cost=46.50..196.68 rows=5000 width=33) (actual time=0.197..1.071 rows=5000 loops=1)
" Output: d.drug_id, ib.min_stock_threshold, d.generic_name, ib.current_stock"
Inner Unique: true
Hash Cond: (ib.drug_id = d.drug_id)
-> Seq Scan on hms.inventory_batch ib (cost=0.00..137.00 rows=5000 width=24) (actual time=0.006..0.167 rows=5000 loops=1)
" Output: ib.batch_id, ib.drug_id, ib.batch_no, ib.production_date, ib.expiry_date, ib.purchase_quantity, ib.purchase_price, ib.purchase_date, ib.current_stock, ib.department_id, ib.min_stock_threshold, ib.last_stock_in, ib.last_stock_out"
-> Hash (cost=34.00..34.00 rows=1000 width=25) (actual time=0.185..0.185 rows=1000 loops=1)
" Output: d.generic_name, d.drug_id"
Buckets: 1024 Batches: 1 Memory Usage: 65kB
-> Seq Scan on hms.drug d (cost=0.00..34.00 rows=1000 width=25) (actual time=0.006..0.113 rows=1000 loops=1)
" Output: d.generic_name, d.drug_id"
Planning Time: 0.155 ms
Execution Time: 1.989 ms
```sql
-- ============================================
-- 功能17统计科室预约量
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
SELECT dep.name, COUNT(a.appointment_id) as appointment_count
FROM department dep
JOIN doctor d ON dep.department_id = d.department_id
JOIN schedule s ON d.doctor_id = s.doctor_id
LEFT JOIN appointment a ON s.schedule_id = a.schedule_id
WHERE s.work_date = '2024-12-15'
GROUP BY dep.department_id, dep.name;
```
GroupAggregate (cost=1379.43..1379.45 rows=1 width=242) (actual time=2.454..2.459 rows=0 loops=1)
" Output: dep.name, count(a.appointment_id), dep.department_id"
Group Key: dep.department_id
-> Sort (cost=1379.43..1379.44 rows=1 width=250) (actual time=2.453..2.457 rows=0 loops=1)
" Output: dep.department_id, dep.name, a.appointment_id"
Sort Key: dep.department_id
Sort Method: quicksort Memory: 25kB
-> Nested Loop (cost=671.72..1379.42 rows=1 width=250) (actual time=2.436..2.440 rows=0 loops=1)
" Output: dep.department_id, dep.name, a.appointment_id"
Inner Unique: true
-> Nested Loop (cost=671.58..1379.24 rows=1 width=32) (actual time=2.436..2.439 rows=0 loops=1)
" Output: d.department_id, a.appointment_id"
Inner Unique: true
-> Hash Right Join (cost=671.30..1370.94 rows=1 width=32) (actual time=2.435..2.438 rows=0 loops=1)
" Output: s.doctor_id, a.appointment_id"
Inner Unique: true
Hash Cond: (a.schedule_id = s.schedule_id)
-> Seq Scan on hms.appointment a (cost=0.00..634.00 rows=25000 width=32) (never executed)
" Output: a.appointment_id, a.patient_id, a.schedule_id, a.appointment_time, a.status, a.booking_channel, a.created_at, a.cancelled_at, a.cancel_reason"
-> Hash (cost=671.29..671.29 rows=1 width=32) (actual time=2.428..2.430 rows=0 loops=1)
" Output: s.doctor_id, s.schedule_id"
Buckets: 1024 Batches: 1 Memory Usage: 8kB
-> Seq Scan on hms.schedule s (cost=0.00..671.29 rows=1 width=32) (actual time=2.427..2.427 rows=0 loops=1)
" Output: s.doctor_id, s.schedule_id"
Filter: (s.work_date = '2024-12-15'::date)
Rows Removed by Filter: 27703
-> Index Scan using doctor_pkey on hms.doctor d (cost=0.28..8.29 rows=1 width=32) (never executed)
" Output: d.doctor_id, d.department_id, d.name, d.title, d.license_no, d.contact, d.work_status"
Index Cond: (d.doctor_id = s.doctor_id)
-> Index Scan using department_pkey on hms.department dep (cost=0.14..0.18 rows=1 width=234) (never executed)
" Output: dep.department_id, dep.code, dep.name, dep.location, dep.phone"
Index Cond: (dep.department_id = d.department_id)
Planning Time: 0.311 ms
Execution Time: 2.495 ms
```sql
-- ============================================
-- 功能18统计医生工作量
-- ============================================
EXPLAIN (ANALYZE, VERBOSE)
SELECT d.name, COUNT(mr.record_id) as record_count
FROM doctor d
LEFT JOIN medical_record mr ON d.doctor_id = mr.doctor_id
AND EXTRACT(YEAR FROM mr.visit_time) = 2024
AND EXTRACT(MONTH FROM mr.visit_time) = 12
WHERE d.work_status = 'active'
GROUP BY d.doctor_id, d.name;
```
GroupAggregate (cost=5247.03..5270.93 rows=1194 width=32) (actual time=13.444..14.002 rows=1194 loops=1)
" Output: d.name, count(mr.record_id), d.doctor_id"
Group Key: d.doctor_id
-> Merge Left Join (cost=5247.03..5253.02 rows=1194 width=40) (actual time=13.439..13.643 rows=1295 loops=1)
" Output: d.doctor_id, d.name, mr.record_id"
Merge Cond: (d.doctor_id = mr.doctor_id)
-> Sort (cost=114.02..117.01 rows=1194 width=24) (actual time=0.345..0.384 rows=1194 loops=1)
" Output: d.name, d.doctor_id"
Sort Key: d.doctor_id
Sort Method: quicksort Memory: 98kB
-> Seq Scan on hms.doctor d (cost=0.00..53.00 rows=1194 width=24) (actual time=0.006..0.200 rows=1194 loops=1)
" Output: d.name, d.doctor_id"
Filter: ((d.work_status)::text = 'active'::text)
Rows Removed by Filter: 806
-> Sort (cost=5133.01..5133.02 rows=1 width=32) (actual time=13.090..13.111 rows=865 loops=1)
" Output: mr.record_id, mr.doctor_id"
Sort Key: mr.doctor_id
Sort Method: quicksort Memory: 65kB
-> Seq Scan on hms.medical_record mr (cost=0.00..5133.00 rows=1 width=32) (actual time=0.016..12.946 rows=865 loops=1)
" Output: mr.record_id, mr.doctor_id"
Filter: ((EXTRACT(year FROM mr.visit_time) = '2024'::numeric) AND (EXTRACT(month FROM mr.visit_time) = '12'::numeric))
Rows Removed by Filter: 19136
Planning Time: 0.171 ms
Execution Time: 14.051 ms
```sql
-- 1. 各表数据量统计
SELECT 'department' as table_name, COUNT(*) as row_count FROM department
UNION ALL
SELECT 'doctor', COUNT(*) FROM doctor
UNION ALL
SELECT 'patient', COUNT(*) FROM patient
UNION ALL
SELECT 'drug', COUNT(*) FROM drug
UNION ALL
SELECT 'inventory_batch', COUNT(*) FROM inventory_batch
UNION ALL
SELECT 'schedule', COUNT(*) FROM schedule
UNION ALL
SELECT 'appointment', COUNT(*) FROM appointment
UNION ALL
SELECT 'medical_record', COUNT(*) FROM medical_record
UNION ALL
SELECT 'prescription', COUNT(*) FROM prescription
UNION ALL
SELECT 'prescription_item', COUNT(*) FROM prescription_item;
```
prescription_item,45002
medical_record,20001
appointment,25001
schedule,27703
prescription,18001
patient,10000
inventory_batch,5000
doctor,2000
drug,1000
department,50
```sql
-- 2. 现有索引检查(使用系统表)
SELECT
t.relname as table_name,
i.relname as index_name,
a.attname as column_name
FROM pg_class t
JOIN pg_index ix ON t.oid = ix.indrelid
JOIN pg_class i ON i.oid = ix.indexrelid
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey)
WHERE t.relkind = 'r'
AND t.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')
ORDER BY t.relname, i.relname;
```
# 完整运行结果
| | | | | | | | | |
|---|---|---|---|---|---|---|---|---|
|SELECT (select)||||||||Planning Time = 0.375; Triggers = []; Execution Time = 0.1;|
|SORT (sort)||3|0|49.36|0.073|49.35|0.072|Parallel Aware = false; Async Capable = false; Plan Width = 192; Actual Loops = 1; Sort Key = ["t.relname","i.relname"]; Sort Method = quicksort; Sort Space Used = 25; Sort Space Type = Memory;|
|SEQ_SCAN (Seq Scan)|table: pg_namespace;|1|0|1.05|0.004|0.0|0.004|Parent Relationship = InitPlan; Subplan Name = InitPlan 1; Parallel Aware = false; Async Capable = false; Alias = pg_namespace; Plan Width = 4; Actual Loops = 1; Filter = (nspname = 'public'::name); Rows Removed by Filter = 7;|
|NESTED_LOOPS (Nested Loop)||3|0|48.28|0.067|21.54|0.067|Parent Relationship = Outer; Parallel Aware = false; Async Capable = false; Join Type = Inner; Plan Width = 192; Actual Loops = 1; Inner Unique = true;|
|NESTED_LOOPS (Nested Loop)||3|0|46.09|0.067|21.27|0.066|Parent Relationship = Outer; Parallel Aware = false; Async Capable = false; Join Type = Inner; Plan Width = 132; Actual Loops = 1; Inner Unique = false; Join Filter = (t.oid = a.attrelid); Rows Removed by Join Filter = 0;|
|HASH_JOIN (hash join)||7|0|28.06|0.067|20.99|0.066|Parent Relationship = Outer; Parallel Aware = false; Async Capable = false; Join Type = Inner; Plan Width = 103; Actual Loops = 1; Inner Unique = true; Hash Cond = (ix.indrelid = t.oid);|
|SEQ_SCAN (Seq Scan)|table: pg_index;|164|1|6.64|0.007|0.0|0.007|Parent Relationship = Outer; Parallel Aware = false; Async Capable = false; Alias = ix; Plan Width = 35; Actual Loops = 1;|
|TRANSFORM (Hash)||19|0|20.75|0.054|20.75|0.054|Parent Relationship = Inner; Parallel Aware = false; Async Capable = false; Plan Width = 68; Actual Loops = 1; Hash Buckets = 1024; Original Hash Buckets = 1024; Hash Batches = 1; Original Hash Batches = 1; Peak Memory Usage = 8;|
|SEQ_SCAN (Seq Scan)|table: pg_class;|19|0|20.75|0.054|0.0|0.054|Parent Relationship = Outer; Parallel Aware = false; Async Capable = false; Alias = t; Plan Width = 68; Actual Loops = 1; Filter = ((relkind = 'r'::"char") AND (relnamespace = (InitPlan 1).col1)); Rows Removed by Filter = 454;|
|INDEX_SCAN (index scan)|table: pg_attribute; index: pg_attribute_relid_attnam_index;|1|0|2.56|0.0|0.28|0.0|Parent Relationship = Inner; Parallel Aware = false; Async Capable = false; Scan Direction = Forward; Alias = a; Plan Width = 70; Actual Loops = 0; Index Cond = (attrelid = ix.indrelid); Rows Removed by Index Recheck = 0; Filter = (attnum = ANY ((ix.indkey)::smallint[])); Rows Removed by Filter = 0;|
|INDEX_SCAN (index scan)|table: pg_class; index: pg_class_oid_index;|1|0|0.73|0.0|0.27|0.0|Parent Relationship = Inner; Parallel Aware = false; Async Capable = false; Scan Direction = Forward; Alias = i; Plan Width = 68; Actual Loops = 0; Index Cond = (oid = ix.indexrelid); Rows Removed by Index Recheck = 0;|

@ -0,0 +1,224 @@
### 功能01查询医生排班号源
**输入**医生ID预约日期
**输出**排班ID、时段、剩余号源
**SQL**
```sql
SELECT schedule_id, slot_no, remaining_appointments
FROM schedule
WHERE doctor_id = ? AND work_date = ? AND remaining_appointments > 0;
```
---
### 功能02扣减排班号源
**输入**排班ID
**输出**:更新号源数量
**SQL**
```sql
UPDATE schedule SET remaining_appointments = remaining_appointments - 1
WHERE schedule_id = ?;
```
---
### 功能03创建预约记录
**输入**患者ID排班ID预约时间
**输出**:新增预约记录
**SQL**
```sql
INSERT INTO appointment (appointment_id, patient_id, schedule_id, appointment_time, status)
VALUES (gen_random_uuid(), ?, ?, ?, 'scheduled');
```
---
### 功能04查询医生当日待诊列表
**输入**医生ID工作日期
**输出**:患者姓名、预约时间
**SQL**
```sql
SELECT a.appointment_id, p.name AS patient_name, a.appointment_time
FROM appointment a
JOIN patient p ON a.patient_id = p.patient_id
JOIN schedule s ON a.schedule_id = s.schedule_id
WHERE s.doctor_id = ? AND s.work_date = ? AND a.status = 'scheduled'
ORDER BY a.appointment_time;
```
---
### 功能05更新预约状态为"已签到"
**输入**预约ID
**输出**:修改状态字段
**SQL**
```sql
UPDATE appointment SET status = 'checked_in' WHERE appointment_id = ?;
```
---
### 功能06创建初始病历记录
**输入**患者ID医生ID预约ID
**输出**:新增病历记录
**SQL**
```sql
INSERT INTO medical_record (record_id, patient_id, doctor_id, appointment_id, visit_time)
VALUES (gen_random_uuid(), ?, ?, ?, CURRENT_TIMESTAMP);
```
---
### 功能07更新病历诊断信息
**输入**病历ID主诉诊断编码
**输出**:更新病历字段
**SQL**
```sql
UPDATE medical_record
SET chief_complaint = ?, diagnosis_codes = ?, updated_at = CURRENT_TIMESTAMP
WHERE record_id = ?;
```
---
### 功能08创建处方主记录
**输入**病历ID开方医生ID
**输出**:新增处方记录
**SQL**
```sql
INSERT INTO prescription (prescription_id, record_id, doctor_id, status)
VALUES (gen_random_uuid(), ?, ?, 'pending');
```
---
### 功能09添加处方明细项
**输入**处方ID药品ID用量数量
**输出**:新增处方明细
**SQL**
```sql
INSERT INTO prescription_item (item_id, prescription_id, drug_id, dosage, frequency, duration_days, quantity)
VALUES (gen_random_uuid(), ?, ?, ?, ?, ?, ?);
```
---
### 功能10查询处方明细清单
**输入**处方ID
**输出**:药品列表及用法
**SQL**
```sql
SELECT d.generic_name, i.dosage, i.frequency, i.quantity
FROM prescription_item i
JOIN drug d ON i.drug_id = d.drug_id
WHERE i.prescription_id = ?;
```
---
### 功能11更新处方审核状态
**输入**处方ID药师ID状态值
**输出**:修改处方状态
**SQL**
```sql
UPDATE prescription SET status = ?, pharmacist_id = ?, reviewed_at = CURRENT_TIMESTAMP
WHERE prescription_id = ?;
```
---
### 功能12查询药品库存可用数量
**输入**药品ID
**输出**:总库存数量
**SQL**
```sql
SELECT SUM(current_stock) AS total_stock
FROM inventory_batch
WHERE drug_id = ?;
```
---
### 功能13扣减药品库存
**输入**药品ID扣减数量
**输出**:更新库存数量
**SQL**
```sql
UPDATE inventory_batch
SET current_stock = current_stock - ?
WHERE batch_id = (
SELECT batch_id FROM inventory_batch
WHERE drug_id = ? AND current_stock >= ?
ORDER BY expiry_date ASC LIMIT 1
);
```
---
### 功能14更新处方状态为"已发药"
**输入**处方ID
**输出**:修改状态字段
**SQL**
```sql
UPDATE prescription SET status = 'dispensed' WHERE prescription_id = ?;
```
---
### 功能15取消预约并回退号源
**输入**预约ID
**输出**:更新预约状态,回退号源
**SQL**
```sql
-- 更新预约状态
UPDATE appointment SET status = 'cancelled', cancelled_at = CURRENT_TIMESTAMP
WHERE appointment_id = ?;
-- 回退号源(后端判断后执行)
UPDATE schedule SET remaining_appointments = remaining_appointments + 1
WHERE schedule_id = (SELECT schedule_id FROM appointment WHERE appointment_id = ?);
```
---
### 功能16查询库存预警药品
**输入**预警线倍数如100表示低于预警线
**输出**:药品名称及库存
**SQL**
```sql
SELECT d.generic_name, SUM(ib.current_stock) AS total_stock
FROM drug d
JOIN inventory_batch ib ON d.drug_id = ib.drug_id
GROUP BY d.drug_id, d.generic_name, ib.min_stock_threshold
HAVING SUM(ib.current_stock) < ib.min_stock_threshold;
```
---
### 功能17统计科室预约量
**输入**:统计日期
**输出**:各科室预约数量
**SQL**
```sql
SELECT dep.name, COUNT(a.appointment_id)
FROM department dep
JOIN doctor d ON dep.department_id = d.department_id
JOIN schedule s ON d.doctor_id = s.doctor_id
LEFT JOIN appointment a ON s.schedule_id = a.schedule_id
WHERE s.work_date = ?
GROUP BY dep.department_id, dep.name;
```
---
### 功能18统计医生工作量
**输入**:统计月份(格式'YYYY-MM'
**输出**:医生接诊数量
**SQL**
```sql
SELECT d.name, COUNT(mr.record_id)
FROM doctor d
LEFT JOIN medical_record mr ON d.doctor_id = mr.doctor_id
AND TO_CHAR(mr.visit_time, 'YYYY-MM') = ?
WHERE d.work_status = 'active'
GROUP BY d.doctor_id, d.name;
```

@ -0,0 +1,283 @@
### 创建科室表
```sql
-- 科室表:存储医院科室基础信息
CREATE TABLE department (
department_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(20) NOT NULL UNIQUE,
name VARCHAR(100) NOT NULL,
location VARCHAR(200),
phone VARCHAR(20),
CONSTRAINT dept_name_check CHECK (LENGTH(name) > 0)
);
-- 添加表和字段注释
COMMENT ON TABLE department IS '科室信息表';
COMMENT ON COLUMN department.department_id IS '科室唯一标识';
COMMENT ON COLUMN department.code IS '科室代码,唯一标识';
COMMENT ON COLUMN department.name IS '科室名称';
COMMENT ON COLUMN department.location IS '科室位置';
COMMENT ON COLUMN department.phone IS '科室电话';
COMMENT ON COLUMN department.dept_name_check IS '确保科室名称不为空';
```
### 创建医生表
```sql
-- 医生表:存储医生基本信息
CREATE TABLE doctor (
doctor_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
department_id UUID NOT NULL,
name VARCHAR(100) NOT NULL,
title VARCHAR(50),
license_no VARCHAR(64) UNIQUE NOT NULL,
contact VARCHAR(100),
work_status VARCHAR(20) DEFAULT 'active',
CONSTRAINT fk_doctor_dept FOREIGN KEY (department_id)
REFERENCES department(department_id) ON DELETE RESTRICT,
CONSTRAINT fk_doctor_status CHECK (work_status IN ('active', 'on_leave', 'resigned'))
);
-- 添加注释
COMMENT ON TABLE doctor IS '医生信息表';
COMMENT ON COLUMN doctor.doctor_id IS '医生唯一标识';
COMMENT ON COLUMN doctor.department_id IS '所属科室ID外键';
COMMENT ON COLUMN doctor.name IS '姓名';
COMMENT ON COLUMN doctor.title IS '职称';
COMMENT ON COLUMN doctor.license_no IS '执业证号,唯一';
COMMENT ON COLUMN doctor.contact IS '联系电话';
COMMENT ON COLUMN doctor.work_status IS '在岗状态active在职, on_leave休假, resigned离职';
```
### 创建患者表
```sql
-- 患者表:存储患者基本信息
CREATE TABLE patient (
patient_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL,
id_number VARCHAR(18) UNIQUE NOT NULL,
mobile VARCHAR(20),
email VARCHAR(200),
gender CHAR(1),
birth_date DATE,
address VARCHAR(255),
status VARCHAR(20) DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_patient_gender CHECK (gender IN ('M', 'F')),
CONSTRAINT fk_patient_status CHECK (status IN ('active', 'suspended'))
);
-- 添加注释
COMMENT ON TABLE patient IS '患者信息表';
COMMENT ON COLUMN patient.patient_id IS '患者唯一标识';
COMMENT ON COLUMN patient.name IS '患者姓名(实际项目中应加密存储)';
COMMENT ON COLUMN patient.id_number IS '身份证号(唯一,应加密)';
COMMENT ON COLUMN patient.mobile IS '手机号(应脱敏存储)';
COMMENT ON COLUMN patient.email IS '邮箱';
COMMENT ON COLUMN patient.gender IS '性别M男 F女';
COMMENT ON COLUMN patient.birth_date IS '出生日期';
COMMENT ON COLUMN patient.address IS '联系地址';
COMMENT ON COLUMN patient.status IS '状态active正常, suspended暂停';
COMMENT ON COLUMN patient.created_at IS '注册时间';
COMMENT ON COLUMN patient.updated_at IS '最后更新时间';
```
### 创建药品表
```sql
-- 药品表:存储药品基础信息
CREATE TABLE drug (
drug_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
generic_name VARCHAR(200) NOT NULL,
trade_name VARCHAR(200),
specification VARCHAR(100),
unit VARCHAR(20) NOT NULL,
dosage_form VARCHAR(50),
manufacturer VARCHAR(200),
approval_no VARCHAR(64) UNIQUE,
is_prescription BOOLEAN DEFAULT true,
shelf_life_days INT,
storage_conditions VARCHAR(200),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_drug_name_check CHECK (LENGTH(generic_name) > 0)
);
-- 添加注释
COMMENT ON TABLE drug IS '药品目录表';
COMMENT ON COLUMN drug.drug_id IS '药品唯一标识';
COMMENT ON COLUMN drug.generic_name IS '通用名';
COMMENT ON COLUMN drug.trade_name IS '商品名';
COMMENT ON COLUMN drug.specification IS '规格';
COMMENT ON COLUMN drug.unit IS '单位:片、瓶、支、盒等';
COMMENT ON COLUMN drug.dosage_form IS '剂型';
COMMENT ON COLUMN drug.manufacturer IS '生产厂家';
COMMENT ON COLUMN drug.approval_no IS '批准文号';
COMMENT ON COLUMN drug.is_prescription IS '是否处方药';
COMMENT ON COLUMN drug.shelf_life_days IS '保质期(天)';
COMMENT ON COLUMN drug.storage_conditions IS '存储条件';
```
### 创建库存批次表
```sql
-- 库存批次表:记录药品批次库存信息
CREATE TABLE inventory_batch (
batch_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
drug_id UUID NOT NULL,
batch_no VARCHAR(64) NOT NULL,
production_date DATE,
expiry_date DATE NOT NULL,
purchase_quantity INT NOT NULL DEFAULT 0,
purchase_price DECIMAL(10,2),
purchase_date TIMESTAMP,
current_stock INT NOT NULL DEFAULT 0,
department_id UUID NOT NULL,
min_stock_threshold INT DEFAULT 50,
last_stock_in TIMESTAMP,
last_stock_out TIMESTAMP,
CONSTRAINT fk_batch_drug FOREIGN KEY (drug_id) REFERENCES drug(drug_id),
CONSTRAINT fk_batch_dept FOREIGN KEY (department_id) REFERENCES department(department_id),
CONSTRAINT fk_batch_stock_check CHECK (current_stock >= 0),
CONSTRAINT fk_batch_date_check CHECK (expiry_date > production_date),
CONSTRAINT fk_batch_quantity_check CHECK (purchase_quantity > 0)
);
-- 添加注释
COMMENT ON TABLE inventory_batch IS '药品库存批次表';
COMMENT ON COLUMN inventory_batch.batch_id IS '批次唯一标识';
COMMENT ON COLUMN inventory_batch.drug_id IS '药品ID外键';
COMMENT ON COLUMN inventory_batch.batch_no IS '生产批号';
COMMENT ON COLUMN inventory_batch.current_stock IS '实时库存,发药时扣减';
```
### 创建排班表
```sql
-- 排班表:医生每日出诊排班
CREATE TABLE schedule (
schedule_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
doctor_id UUID NOT NULL,
work_date DATE NOT NULL,
slot_no SMALLINT NOT NULL,
max_appointments INT NOT NULL DEFAULT 20,
remaining_appointments INT NOT NULL DEFAULT 20,
CONSTRAINT fk_schedule_doctor FOREIGN KEY (doctor_id) REFERENCES doctor(doctor_id),
CONSTRAINT fk_schedule_slot CHECK (slot_no BETWEEN 1 AND 3),
CONSTRAINT fk_schedule_remain_check CHECK (remaining_appointments <= max_appointments),
CONSTRAINT fk_schedule_negative_check CHECK (remaining_appointments >= 0),
UNIQUE (doctor_id, work_date, slot_no)
);
-- 添加注释
COMMENT ON TABLE schedule IS '医生排班表';
COMMENT ON COLUMN schedule.schedule_id IS '排班唯一标识';
COMMENT ON COLUMN schedule.doctor_id IS '医生ID外键';
COMMENT ON COLUMN schedule.remaining_appointments IS '实时剩余号源预约成功减1';
```
### 创建预约表
```sql
-- 预约表:患者预约挂号记录
CREATE TABLE appointment (
appointment_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
patient_id UUID NOT NULL,
schedule_id UUID NOT NULL,
appointment_time TIMESTAMP NOT NULL,
status VARCHAR(20) DEFAULT 'scheduled',
booking_channel VARCHAR(20) DEFAULT 'online',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
cancelled_at TIMESTAMP,
cancel_reason VARCHAR(255),
CONSTRAINT fk_apt_patient FOREIGN KEY (patient_id) REFERENCES patient(patient_id),
CONSTRAINT fk_apt_schedule FOREIGN KEY (schedule_id) REFERENCES schedule(schedule_id),
CONSTRAINT fk_apt_status CHECK (status IN ('scheduled', 'checked_in', 'cancelled', 'completed')),
CONSTRAINT fk_apt_channel CHECK (booking_channel IN ('online', 'offline', 'phone'))
);
-- 添加注释
COMMENT ON TABLE appointment IS '预约挂号记录表';
COMMENT ON COLUMN appointment.appointment_id IS '预约唯一标识';
COMMENT ON COLUMN appointment.status IS '预约状态';
```
### 创建电子病历表
```sql
-- 电子病历表:记录患者就诊的详细医疗信息
CREATE TABLE medical_record (
record_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
patient_id UUID NOT NULL,
doctor_id UUID NOT NULL,
appointment_id UUID,
visit_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
chief_complaint TEXT,
present_illness TEXT,
past_history TEXT,
allergies VARCHAR(255),
diagnosis_codes VARCHAR(255),
treatment_plan TEXT,
notes TEXT,
attachments TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_record_patient FOREIGN KEY (patient_id) REFERENCES patient(patient_id),
CONSTRAINT fk_record_doctor FOREIGN KEY (doctor_id) REFERENCES doctor(doctor_id),
CONSTRAINT fk_record_appointment FOREIGN KEY (appointment_id) REFERENCES appointment(appointment_id)
);
-- 添加注释
COMMENT ON TABLE medical_record IS '电子病历表';
COMMENT ON COLUMN medical_record.record_id IS '病历唯一标识';
```
### 创建处方主表
```sql
-- 处方主表:一次就诊对应一张处方
CREATE TABLE prescription (
prescription_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
record_id UUID NOT NULL UNIQUE,
doctor_id UUID NOT NULL,
issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(20) DEFAULT 'pending',
pharmacist_id UUID,
reviewed_at TIMESTAMP,
CONSTRAINT fk_prescription_record FOREIGN KEY (record_id) REFERENCES medical_record(record_id),
CONSTRAINT fk_prescription_doctor FOREIGN KEY (doctor_id) REFERENCES doctor(doctor_id),
CONSTRAINT fk_prescription_status CHECK (status IN ('pending', 'approved', 'rejected', 'dispensed'))
);
-- 添加注释
COMMENT ON TABLE prescription IS '处方主表';
COMMENT ON COLUMN prescription.record_id IS '与病历表为一对一关系';
```
### 创建处方明细表
```sql
-- 处方明细表:记录处方中的药品清单
CREATE TABLE prescription_item (
item_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
prescription_id UUID NOT NULL,
drug_id UUID NOT NULL,
dosage VARCHAR(50) NOT NULL,
frequency VARCHAR(50) NOT NULL,
duration_days INT NOT NULL,
quantity INT NOT NULL,
instructions TEXT,
price DECIMAL(10,2),
amount DECIMAL(10,2),
CONSTRAINT fk_item_prescription FOREIGN KEY (prescription_id) REFERENCES prescription(prescription_id) ON DELETE CASCADE,
CONSTRAINT fk_item_drug FOREIGN KEY (drug_id) REFERENCES drug(drug_id),
CONSTRAINT fk_item_quantity_check CHECK (quantity > 0),
CONSTRAINT fk_item_duration_check CHECK (duration_days > 0),
UNIQUE (prescription_id, drug_id)
);
-- 添加注释
COMMENT ON TABLE prescription_item IS '处方明细表';
COMMENT ON COLUMN prescription_item.amount IS '金额 = price * quantity';
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Loading…
Cancel
Save