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