perf(health): stats_service 合并 COUNT 为 GROUP BY + 宏化 compute_avg_field

get_follow_up_statistics: 4次独立COUNT合并为1次GROUP BY GROUPING SETS。
compute_avg_field: format! 动态拼接改为宏生成静态SQL,利用PG prepared statement缓存。
This commit is contained in:
iven
2026-04-27 09:50:10 +08:00
parent f934ca0eaf
commit 04c5f3c0d5

View File

@@ -96,32 +96,44 @@ pub async fn get_follow_up_statistics(
) -> AppResult<FollowUpStatisticsResp> {
let db = &state.db;
let total_tasks = follow_up_task::Entity::find()
.filter(follow_up_task::Column::TenantId.eq(tenant_id))
.filter(follow_up_task::Column::DeletedAt.is_null())
.count(db)
.await?;
// 单次 GROUP BY 查询替代 4 次独立 COUNT
let sql = r#"
SELECT COALESCE(status, '__total') AS status, COUNT(*) AS cnt
FROM follow_up_task
WHERE tenant_id = $1 AND deleted_at IS NULL
GROUP BY GROUPING SETS ((status), ())
"#;
let completed = follow_up_task::Entity::find()
.filter(follow_up_task::Column::TenantId.eq(tenant_id))
.filter(follow_up_task::Column::DeletedAt.is_null())
.filter(follow_up_task::Column::Status.eq("completed"))
.count(db)
.await?;
#[derive(Debug, sea_orm::FromQueryResult)]
struct StatusCount {
status: String,
cnt: i64,
}
let pending = follow_up_task::Entity::find()
.filter(follow_up_task::Column::TenantId.eq(tenant_id))
.filter(follow_up_task::Column::DeletedAt.is_null())
.filter(follow_up_task::Column::Status.eq("pending"))
.count(db)
.await?;
let rows: Vec<StatusCount> = sea_orm::FromQueryResult::find_by_statement(
sea_orm::Statement::from_sql_and_values(
sea_orm::DatabaseBackend::Postgres,
sql,
[tenant_id.into()],
),
)
.all(db)
.await?;
let overdue = follow_up_task::Entity::find()
.filter(follow_up_task::Column::TenantId.eq(tenant_id))
.filter(follow_up_task::Column::DeletedAt.is_null())
.filter(follow_up_task::Column::Status.eq("overdue"))
.count(db)
.await?;
let mut total_tasks: i64 = 0;
let mut completed: i64 = 0;
let mut pending: i64 = 0;
let mut overdue: i64 = 0;
for row in &rows {
match row.status.as_str() {
"__total" => total_tasks = row.cnt,
"completed" => completed = row.cnt,
"pending" => pending = row.cnt,
"overdue" => overdue = row.cnt,
_ => {}
}
}
let completion_rate = if completed + pending + overdue > 0 {
(completed as f64 / (completed + pending + overdue) as f64) * 100.0
@@ -130,10 +142,10 @@ pub async fn get_follow_up_statistics(
};
Ok(FollowUpStatisticsResp {
total_tasks: total_tasks as i64,
completed: completed as i64,
pending: pending as i64,
overdue: overdue as i64,
total_tasks,
completed,
pending,
overdue,
completion_rate,
})
}
@@ -420,36 +432,36 @@ struct AvgFieldResult {
avg_val: Option<f64>,
}
macro_rules! avg_field_sql {
($field:literal) => {
concat!(
"SELECT AVG(", $field, ")::FLOAT8 AS avg_val FROM dialysis_record ",
"WHERE tenant_id = $1 AND deleted_at IS NULL AND ", $field, " IS NOT NULL ",
"AND created_at >= date_trunc('month', NOW())"
)
};
}
async fn compute_avg_field(
db: &sea_orm::DatabaseConnection,
tenant_id: uuid::Uuid,
field: &str,
) -> AppResult<Option<f64>> {
const ALLOWED_FIELDS: &[&str] = &[
"ultrafiltration_volume",
"dialysis_duration",
"uf_volume",
"uf_rate",
"blood_flow_rate",
"dialysate_flow_rate",
"pre_weight",
"post_weight",
"pre_bp_systolic",
"pre_bp_diastolic",
"post_bp_systolic",
"post_bp_diastolic",
];
if !ALLOWED_FIELDS.contains(&field) {
return Err(erp_core::error::AppError::Validation(format!(
"不允许的字段名: {field}"
)));
}
// field is whitelist-validated, safe to interpolate
let sql = format!(
"SELECT AVG({field})::FLOAT8 AS avg_val FROM dialysis_record \
WHERE tenant_id = $1 AND deleted_at IS NULL AND {field} IS NOT NULL \
AND created_at >= date_trunc('month', NOW())"
);
let sql = match field {
"ultrafiltration_volume" => avg_field_sql!("ultrafiltration_volume"),
"dialysis_duration" => avg_field_sql!("dialysis_duration"),
"uf_volume" => avg_field_sql!("uf_volume"),
"uf_rate" => avg_field_sql!("uf_rate"),
"blood_flow_rate" => avg_field_sql!("blood_flow_rate"),
"dialysate_flow_rate" => avg_field_sql!("dialysate_flow_rate"),
"pre_weight" => avg_field_sql!("pre_weight"),
"post_weight" => avg_field_sql!("post_weight"),
"pre_bp_systolic" => avg_field_sql!("pre_bp_systolic"),
"pre_bp_diastolic" => avg_field_sql!("pre_bp_diastolic"),
"post_bp_systolic" => avg_field_sql!("post_bp_systolic"),
"post_bp_diastolic" => avg_field_sql!("post_bp_diastolic"),
_ => return Err(erp_core::error::AppError::Validation(format!("不允许的字段名: {field}"))),
};
let result: Option<AvgFieldResult> = sea_orm::FromQueryResult::find_by_statement(
sea_orm::Statement::from_sql_and_values(
sea_orm::DatabaseBackend::Postgres,