WXL (wul)
3 小时以前 fb34ba28cb908bd328cf6690ab0b19a56428f4e2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
-- =============================================================================
-- 病区出院随访统计 · 可传参视图
-- =============================================================================
-- 为什么是「参数表 + 视图」而不是 @变量:
--   MySQL 视图定义里禁止引用 @用户变量 / 系统变量 / 存储过程参数,
--   否则创建时报 ERROR 1351 (HY000): View's SELECT contains a variable or parameter。
--   官方原文:「The SELECT statement cannot refer to system variables or
--   user-defined variables.」
--   https://dev.mysql.com/doc/refman/8.0/en/create-view.html
--
-- 所以用单行参数表 report_param 承载参数:
--   1) REPLACE INTO report_param ... 改第 1 行(起止时间 / 口径 / 及时天数)
--   2) SELECT * FROM v_ward_followup_stats;
--
-- 并发提醒:参数表是共享状态,两个会话同时改会互相串。多用户并发请改用
--          同目录的 p_ward_followup_stats.sql(存储过程,真正按调用传参)。
-- =============================================================================
 
-- ---------------------------------------------------------------------------
-- 1. 参数表(永远只有 id = 1 这一行)
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS report_param (
  id          TINYINT    NOT NULL                COMMENT '固定为 1',
  start_time  DATETIME   NOT NULL                COMMENT '出院时间起(含),传 yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss 均可',
  end_time    DATETIME   NOT NULL                COMMENT '出院时间止(含当天 23:59:59),传 yyyy-MM-dd 即可',
  count_style VARCHAR(2) NOT NULL DEFAULT '1'    COMMENT '随访方式命中口径:1=应随访 3=仅成功 其它=全部',
  timely_days INT        NOT NULL DEFAULT 7      COMMENT '及时率的天数基数(补偿天数另加)',
  PRIMARY KEY (id)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '报表参数(单行,id 恒为 1)';
 
-- ---------------------------------------------------------------------------
-- 2. 视图
-- ---------------------------------------------------------------------------
CREATE OR REPLACE VIEW v_ward_followup_stats AS
SELECT
  t.leavehospitaldistrictname,                                -- 出院病区
  t.deptname,                                                 -- 出院科室
  t.dischargeCount,                                           -- 出院人次(首次随访记录数)
  t.nonFollowUp,                                              -- 无需随访人次
  t.followUpNeeded,                                           -- 应随访人次
  t.needFollowUp,                                             -- 首次应随访(= 应随访人次)
  t.pendingFollowUp,                                          -- 首次待随访
  t.followUpSuccess,                                          -- 首次随访成功
  t.followUpFail,                                             -- 首次随访失败(含人工超时)
  t.manualSuccess,                                            -- 首次人工随访成功
  t.voiceSuccess,                                             -- 首次语音随访成功
  t.smsSuccess,                                               -- 首次短信随访成功
  t.weChatSuccess,                                            -- 首次微信随访成功
  -- 随访成功率 = 成功 / 应随访(只算成功,不含失败)
  IF(t.needFollowUp = 0, '0.00%',
     CONCAT(ROUND(100 * t.followUpSuccess / t.needFollowUp, 2), '%'))   AS followUpSuccessRate,
  -- 渠道合计成功率 = 四种方式成功之和 / 四种方式命中之和
  IF(t.manual + t.voice + t.sms + t.weChat = 0, '0.00%',
     CONCAT(ROUND(100 * (t.manualSuccess + t.voiceSuccess + t.smsSuccess + t.weChatSuccess)
                  / (t.manual + t.voice + t.sms + t.weChat), 2), '%'))   AS channelSuccessRate,
  -- 及时率:及时 = finishtime 不为空 且 DATEDIFF(finishtime, endtime) <= 及时天数 + 补偿天数
  IF(IFNULL(rt.timely_base, 0) = 0, '0.00%',
     CONCAT(ROUND(100 * rt.timely_cnt / rt.timely_base, 2), '%'))       AS timelyRate
FROM (
  SELECT
    r.ward_code                                    AS ward_code,
    MAX(r.ward_name)                               AS leavehospitaldistrictname,
    MAX(r.dept_name)                               AS deptname,
    COUNT(*)                                       AS dischargeCount,
    SUM(r.f_no)                                    AS nonFollowUp,
    SUM(r.f_need)                                  AS followUpNeeded,
    SUM(r.f_need)                                  AS needFollowUp,
    SUM(IF(r.sendstate = 2, 1, 0))                 AS pendingFollowUp,
    SUM(r.f_succ)                                  AS followUpSuccess,
    SUM(r.f_fail)                                  AS followUpFail,
    SUM(IF(r.ch_hit = 1 AND r.cp = '1', 1, 0))     AS manual,
    SUM(IF(r.ch_hit = 1 AND r.cp = '3', 1, 0))     AS voice,
    SUM(IF(r.ch_hit = 1 AND r.cp = '4', 1, 0))     AS sms,
    SUM(IF(r.ch_hit = 1 AND r.cp = '5', 1, 0))     AS weChat,
    SUM(IF(r.sendstate = 6 AND r.cp = '1', 1, 0))  AS manualSuccess,
    SUM(IF(r.sendstate = 6 AND r.cp = '3', 1, 0))  AS voiceSuccess,
    SUM(IF(r.sendstate = 6 AND r.cp = '4', 1, 0))  AS smsSuccess,
    SUM(IF(r.sendstate = 6 AND r.cp = '5', 1, 0))  AS weChatSuccess
  FROM (
    SELECT
      ss.leavehospitaldistrictcode                              AS ward_code,
      ss.leavehospitaldistrictname                              AS ward_name,
      ss.leaveldeptname                                         AS dept_name,
      ss.sendstate                                              AS sendstate,
      ss.current_preachform                                     AS cp,
      IF(ss.sendstate = 4, 1, 0)                                AS f_no,
      IF(ss.sendstate IS NOT NULL AND ss.sendstate <> 4, 1, 0)  AS f_need,
      IF(ss.sendstate = 6, 1, 0)                                AS f_succ,
      IF(ss.sendstate IN (5, 7), 1, 0)                          AS f_fail,
      -- 随访方式命中条件:方式 1 排除「无需随访」;方式 3 只算已完成;其它方式不设限
      IF(p.count_style = '1',
         IF(ss.sendstate IS NOT NULL AND ss.sendstate <> 4, 1, 0),
         IF(p.count_style = '3',
            IF(ss.sendstate = 6, 1, 0),
            IF(ss.sendstate IS NOT NULL, 1, 0)))                AS ch_hit
    FROM service_subtask ss
    JOIN report_param p ON p.id = 1
    WHERE ss.del_flag = 0
      AND ss.service_type = '2'
      AND ss.visit_count = 1                                  -- 只要首次随访
      AND ss.leavehospitaldistrictcode IS NOT NULL
      -- >>> 可传参的时间范围:左闭右开,end_time 传日期即可含当天全天 <<<
      AND ss.endtime >= DATE(p.start_time)
      AND ss.endtime <  DATE_ADD(DATE(p.end_time), INTERVAL 1 DAY)
      AND ss.orgid = '20001001'
  ) r
  GROUP BY r.ward_code
) t
LEFT JOIN (
  -- 及时率:按病区预聚合一次再关联,避免一对多放大分母
  SELECT
    ss.leavehospitaldistrictcode AS ward_code,
    COUNT(*) AS timely_base,
    SUM(IF(ss.finishtime IS NOT NULL
           AND DATEDIFF(ss.finishtime, ss.endtime) <= p.timely_days + IFNULL(cp.compensate_days, 0),
           1, 0)) AS timely_cnt
  FROM service_subtask ss
  JOIN report_param p ON p.id = 1
  LEFT JOIN (
    -- 补偿时间:varchar 且可能是空串,先按子任务聚合成一行
    SELECT subid,
           SUM(CAST(COALESCE(NULLIF(TRIM(compensate_time), ''), '0') AS SIGNED)) AS compensate_days
    FROM service_subtask_preachform
    WHERE del_flag = 0
    GROUP BY subid
  ) cp ON cp.subid = ss.id
  WHERE ss.del_flag = 0
    AND ss.service_type = '2'
    AND ss.visit_count = 1
    AND ss.leavehospitaldistrictcode IS NOT NULL
    AND ss.visit_time IS NOT NULL
    AND ss.sendstate <> 4
    AND DATE(ss.visit_time) < DATE(NOW())     -- 接口未传 startTime/endTime 时的固定条件
    AND ss.endtime >= DATE(p.start_time)
    AND ss.endtime <  DATE_ADD(DATE(p.end_time), INTERVAL 1 DAY)
    AND ss.orgid = '20001001'
  GROUP BY ss.leavehospitaldistrictcode
) rt ON rt.ward_code = t.ward_code
ORDER BY t.dischargeCount DESC;
 
-- ---------------------------------------------------------------------------
-- 3. 用法
-- ---------------------------------------------------------------------------
-- 改参数(只改这一行,两个日期就是原来的 endtime 起止)
REPLACE INTO report_param (id, start_time, end_time, count_style, timely_days)
VALUES (1, '2026-10-04', '2026-11-03', '1', 7);
 
-- 查数
SELECT *
FROM v_ward_followup_stats
-- WHERE leavehospitaldistrictname LIKE '%骨科%'   -- 视图外照样能再加条件
;