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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user