liusheng
4 天以前 fb89df539c6293a41933e445df25fa9b8047ba18
补充数据库修复/排查脚本,新增 .gitignore

1. sql/ 下新增 41 个一次性脚本(fix_*/diag_*/perf_*/index_*/开关配置等),
均为线上已执行的问题修复与数据回填记录,按日期命名便于回溯。
其中 cleanup_unused_tables_20260923.sql 含 DROP TABLE,属历史清理脚本,仅存档。
2. 新增 .gitignore:忽略构建产物 target/、IDE 目录、临时目录
tmp/ .tmp/ _backups/、备份文件 *.bak-*、一次性探针目录 sql/_probe/、
日志与本地临时配置,避免再次误提交编译产物和排查残留。
已添加42个文件
6056 ■■■■■ 文件已修改
.gitignore 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/backfill_service_subtask_dept_from_inhosp_20261106.sql 290 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/cleanup_unused_tables_20260923.sql 336 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/create_pat_med_outhosp_error.sql 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/diag_admin_dept_ward_20261106.sql 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/diag_出院随访少生成_原因排查.sql 166 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/fix_admin_dept_ward_missing_20261106.sql 150 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/fix_nhfy_patitem_slow_20261106.sql 302 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/fix_nhfy_satisfaction_statistics_20261021.sql 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/fix_nhfy_sf_statistics_count_20261021.sql 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/fix_nhfy_sltd_satisfaction_endpoints_20261021.sql 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/fix_nhfy_ssd_delflag_20261021.sql 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/fix_nhfy_出院随访任务院区病区回填_20261029.sql 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/fix_nhfy_随访病区统一为出院病区_20261029.sql 186 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/fix_service_subtask_campusid_20260924.sql 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/fix_service_subtask_campusid_20260928.sql 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/index_service_subtask_detail_unique_20261021.sql 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/ivr_assort_columns_20260928.sql 148 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/ivr_script_missing_columns_20260928.sql 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/pat_med_outhosp_serialnum_index.sql 184 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/patinhosp_select_list_index_optimize.sql 202 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/patinhosp_select_list_slow_diagnose.sql 222 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/perf_sf_statistics_details_20261102.sql 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/rename_service_subtask_dims_20261106.sql 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/service_out_path_radix_shortcode_20261106.sql 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/service_subtask_index_opt.sql 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/sltdMydTotalByDimension_index_20260924.sql 139 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/sync_svy_lib_default_value_to_task_20261106.sql 282 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/tracedeallist_index_optimize.sql 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/v_sf_statistics_ward.sql 603 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/任务开关_sys_config.sql 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/修复_nhfy_满意度明细被逻辑删除_20261021.sql 128 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/出入院随访开关_sys_config.sql 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/在院信息刷新_sys_job.sql 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/抓现场_谁把明细置为删除_20261021.sql 173 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/排查_导出问题占比人数与任务数不一致_20261029.sql 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/排查_问题占比与随访表单人数不一致.sql 168 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/数据采集开关_sys_config.sql 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/模板开关_sys_config.sql 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/满意度开关_sys_config.sql 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/电话开关_sys_config.sql 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/门急诊与手术随访开关_sys_config.sql 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
# ==================== æž„建产物 ====================
target/
out/
*.class
# ==================== IDE / ç¼–辑器 ====================
.idea/
*.iml
*.ipr
*.iws
.vscode/
.settings/
.project
.classpath
# ==================== ä¸´æ—¶ç›®å½• ====================
tmp/
.tmp/
_backups/
recovered/
__pycache__/
*.pyc
# ==================== å¤‡ä»½ä¸Žä¸´æ—¶æ–‡ä»¶ ====================
*.bak
*.bak[0-9]
*.bak-*
*.bak[0-9]-*
*.orig
*.rej
*~
# ==================== ä¸€æ¬¡æ€§æŽ’查脚本 / æŽ¢é’ˆ / æ—¥å¿—(不入库) ====================
sql/_probe/
*.log
_dbcheck.txt
package-lock.json
# ==================== æœ¬åœ°ä¸´æ—¶é…ç½® ====================
ruoyi-admin/src/main/resources/tempfile_*.yml
ruoyi-admin/src/main/resources/logback-backup.xml
sql/backfill_service_subtask_dept_from_inhosp_20261106.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,290 @@
-- ============================================================================
-- æŠŠ pat_med_inhosp çš„「科室 / ç—…区」回填到 service_subtask(在院 4 åˆ— + ç¦»é™¢ 4 åˆ—)
-- ç”Ÿæˆæ—¥æœŸï¼š2026-11-06(v2:修正 join é”® + åˆå¹¶ç¦»é™¢ç»´åº¦ï¼‰
-- é€‚用:MySQL 8.0 / 5.7(未用 CTE、窗口函数)
--
-- âš ï¸ å‰ç½®æ¡ä»¶ï¼šå…ˆæ‰§è¡Œè¿‡ sql/rename_service_subtask_dims_20261106.sql(列已改名)。
--
-- âš ï¸âš ï¸ join é”®ï¼ˆv1 å†™é”™è¿‡ï¼Œè¿™é‡Œæ›´æ­£ï¼‰ï¼š
--     service_subtask.inhospid å­˜çš„æ˜¯ **pat_med_inhosp.inhospid**(业务主键),不是 id。
--     è¯æ®ï¼šVisitInhospMapper.xml#selectForScan æŸ¥çš„æ˜¯ b.inhospid;
--           VisitSubtaskFactory é‡Œæ˜¯ d.setInhospid(inhosp.getInhospid())。
--     é—¨è¯Šä¾§ä¾‹å¤–:PatMedOuthospServiceDelegatingImpl ç”¨çš„æ˜¯ pat_med_outhosp.getId(),
--     æ‰€ä»¥ pat_med_outhosp çš„ id ä¼šå’Œ pat_med_inhosp.inhospid æ’žå· â†’ å¿…须排除 service_type='3'。
--
-- å­—段映射(8 åˆ—,注意「在院病区」和「出院病区」不是一回事)
--   åœ¨é™¢ç»´åº¦ï¼ˆæœ¬æ¬¡æ–°å¢žåˆ—)
--     service_subtask.hospitaldistrictcode/name      â† pat_med_inhosp.hospitaldistrictcode/name      ï¼ˆåœ¨é™¢ç—…区)
--     service_subtask.deptcode/name                   â† pat_med_inhosp.deptcode/name                  ï¼ˆåœ¨é™¢ç§‘室)
--   ç¦»é™¢/出院维度(老列,原 deptcode/deptname æ”¹åè€Œæ¥ï¼‰
--     service_subtask.leaveldeptcode/name             â† pat_med_inhosp.leaveldeptcode/name            ï¼ˆå‡ºé™¢ç§‘室)
--     service_subtask.leavehospitaldistrictcode/name  â† pat_med_inhosp.leavehospitaldistrictcode/name ï¼ˆå‡ºé™¢ç—…区)
--
-- å®‰å…¨ç­–ç•¥
--   1) å…¨éƒ¨ç”¨ COALESCE「目标列为空才写」→ **不覆盖任何已有值**,可重复执行;
--   2) æ›´æ–°å‰å…ˆå¤‡ä»½å—影响行的这 8 åˆ—旧值(§2);
--   3) å¤§è¡¨åˆ†æ‰¹æ¨¡æ¿è§ Â§6。
-- ============================================================================
-- ----------------------------------------------------------------------------
-- 0) å‰ç½®è‡ªæŸ¥
-- ----------------------------------------------------------------------------
SHOW FULL COLUMNS FROM service_subtask LIKE '%dept%';
SHOW FULL COLUMNS FROM service_subtask LIKE 'hospitaldistrict%';
SHOW FULL COLUMNS FROM pat_med_inhosp LIKE '%dept%';
SHOW FULL COLUMNS FROM pat_med_inhosp LIKE 'hospitaldistrict%';
-- ----------------------------------------------------------------------------
-- 1) å½±å“é¢è¯„估(先看数,再动手)
-- ----------------------------------------------------------------------------
-- 1.1 èƒ½ join ä¸Šçš„子任务数 + 8 åˆ—里各有多少是空的
SELECT COUNT(*)                                                                   AS joined_rows,
       SUM(hospitaldistrictcode      IS NULL OR hospitaldistrictcode      = '')    AS hd_code_empty,
       SUM(hospitaldistrictname      IS NULL OR hospitaldistrictname      = '')    AS hd_name_empty,
       SUM(deptcode                  IS NULL OR deptcode                  = '')    AS dept_code_empty,
       SUM(deptname                  IS NULL OR deptname                  = '')    AS dept_name_empty,
       SUM(leaveldeptcode            IS NULL OR leaveldeptcode            = '')    AS lv_dept_code_empty,
       SUM(leaveldeptname            IS NULL OR leaveldeptname            = '')    AS lv_dept_name_empty,
       SUM(leavehospitaldistrictcode IS NULL OR leavehospitaldistrictcode = '')    AS lv_hd_code_empty,
       SUM(leavehospitaldistrictname IS NULL OR leavehospitaldistrictname = '')    AS lv_hd_name_empty
  FROM service_subtask s
  JOIN pat_med_inhosp b
    ON b.inhospid = s.inhospid
   AND b.patid   = s.patid
 WHERE s.inhospid IS NOT NULL
   AND s.service_type <> '3'
   AND (s.hosp_type IS NULL OR s.hosp_type <> '8');
-- 1.2 æŒ‰ service_type çœ‹åˆ†å¸ƒï¼ˆ2/13 å‡ºé™¢éšè®¿ã€17/18 å…¥é™¢å®£æ•™/入院随访、6/14/15/16 æ»¡æ„åº¦å­ä»»åŠ¡ï¼‰
SELECT s.service_type,
       COUNT(*)                                                                  AS cnt,
       SUM(s.deptcode            IS NULL OR s.deptcode            = '')          AS dept_empty,
       SUM(s.hospitaldistrictcode IS NULL OR s.hospitaldistrictcode = '')        AS hd_empty,
       SUM(s.leaveldeptcode      IS NULL OR s.leaveldeptcode      = '')          AS lv_dept_empty,
       SUM(s.leavehospitaldistrictcode IS NULL OR s.leavehospitaldistrictcode = '') AS lv_hd_empty
  FROM service_subtask s
  JOIN pat_med_inhosp b ON b.inhospid = s.inhospid AND b.patid = s.patid
 WHERE s.inhospid IS NOT NULL
   AND s.service_type <> '3'
 GROUP BY s.service_type
 ORDER BY cnt DESC;
-- 1.3 join ä¸ä¸Šçš„诊断:inhospid æœ‰å€¼ã€ä½† patid å¯¹ä¸ä¸Š
SELECT s.service_type,
       COUNT(*) AS unmatched
  FROM service_subtask s
  LEFT JOIN pat_med_inhosp b
         ON b.inhospid = s.inhospid
        AND b.patid   = s.patid
 WHERE s.inhospid IS NOT NULL
   AND s.service_type <> '3'
   AND b.inhospid IS NULL
 GROUP BY s.service_type
 ORDER BY unmatched DESC;
-- 1.4 æºè¡¨ inhospid æ˜¯å¦å”¯ä¸€ï¼ˆä¸å”¯ä¸€ä¼šè®© UPDATE å–到其中一行,结果不确定)
SELECT inhospid, COUNT(*) AS c, COUNT(DISTINCT patid) AS pats
  FROM pat_med_inhosp
 WHERE inhospid IS NOT NULL
 GROUP BY inhospid
HAVING c > 1
 ORDER BY c DESC
 LIMIT 20;
-- 1.5 ä¸€æ¡å­ä»»åŠ¡æ˜¯å¦ä¼šè¢«å¤šè¡Œæºæ•°æ®å‘½ä¸­ï¼ˆåº”ä¸º 0;有值就先把 Â§1.4 çš„重复处理干净)
SELECT COUNT(*) AS subtask_rows, COUNT(*) - COUNT(DISTINCT s.id) AS dup_hit
  FROM service_subtask s
  JOIN pat_med_inhosp b ON b.inhospid = s.inhospid AND b.patid = s.patid
 WHERE s.inhospid IS NOT NULL
   AND s.service_type <> '3';
-- ----------------------------------------------------------------------------
-- 2) å¤‡ä»½å—影响行的 8 åˆ—旧值(回滚见 Â§7)
-- ----------------------------------------------------------------------------
CREATE TABLE bak_service_subtask_dept_20261106 AS
SELECT s.id,
       s.service_type,
       s.hosp_type,
       s.inhospid,
       s.patid,
       s.leaveldeptcode,
       s.leaveldeptname,
       s.leavehospitaldistrictcode,
       s.leavehospitaldistrictname,
       s.hospitaldistrictcode,
       s.hospitaldistrictname,
       s.deptcode,
       s.deptname
  FROM service_subtask s
  JOIN pat_med_inhosp b
    ON b.inhospid = s.inhospid
   AND b.patid   = s.patid
 WHERE s.inhospid IS NOT NULL
   AND s.service_type <> '3'
   AND (s.hosp_type IS NULL OR s.hosp_type <> '8')
   AND (  NULLIF(TRIM(s.hospitaldistrictcode), '')       IS NULL
       OR NULLIF(TRIM(s.hospitaldistrictname), '')       IS NULL
       OR NULLIF(TRIM(s.deptcode), '')                   IS NULL
       OR NULLIF(TRIM(s.deptname), '')                   IS NULL
       OR NULLIF(TRIM(s.leaveldeptcode), '')             IS NULL
       OR NULLIF(TRIM(s.leaveldeptname), '')             IS NULL
       OR NULLIF(TRIM(s.leavehospitaldistrictcode), '')  IS NULL
       OR NULLIF(TRIM(s.leavehospitaldistrictname), '')  IS NULL );
SELECT COUNT(*) AS backup_rows FROM bak_service_subtask_dept_20261106;
-- ----------------------------------------------------------------------------
-- 3) ä¸»å›žå¡«ï¼šåœ¨é™¢ 4 åˆ— + ç¦»é™¢ 4 åˆ—(只补空,不覆盖;可重复执行)
-- ----------------------------------------------------------------------------
UPDATE service_subtask s
  JOIN pat_med_inhosp b
    ON b.inhospid = s.inhospid
   AND b.patid   = s.patid
   SET s.hospitaldistrictcode      = COALESCE(NULLIF(TRIM(s.hospitaldistrictcode), ''),      b.hospitaldistrictcode),
       s.hospitaldistrictname      = COALESCE(NULLIF(TRIM(s.hospitaldistrictname), ''),      b.hospitaldistrictname),
       s.deptcode                  = COALESCE(NULLIF(TRIM(s.deptcode), ''),                  b.deptcode),
       s.deptname                  = COALESCE(NULLIF(TRIM(s.deptname), ''),                  b.deptname),
       s.leaveldeptcode            = COALESCE(NULLIF(TRIM(s.leaveldeptcode), ''),            b.leaveldeptcode),
       s.leaveldeptname            = COALESCE(NULLIF(TRIM(s.leaveldeptname), ''),            b.leaveldeptname),
       s.leavehospitaldistrictcode = COALESCE(NULLIF(TRIM(s.leavehospitaldistrictcode), ''), b.leavehospitaldistrictcode),
       s.leavehospitaldistrictname = COALESCE(NULLIF(TRIM(s.leavehospitaldistrictname), ''), b.leavehospitaldistrictname)
 WHERE s.inhospid IS NOT NULL
   AND s.service_type <> '3'                        -- é—¨è¯Šéšè®¿çš„ inhospid æ˜¯ pat_med_outhosp.id
   AND (s.hosp_type IS NULL OR s.hosp_type <> '8')  -- æ‰«ç ç”Ÿæˆçš„虚拟患者
   AND (  NULLIF(TRIM(s.hospitaldistrictcode), '')      IS NULL
       OR NULLIF(TRIM(s.hospitaldistrictname), '')      IS NULL
       OR NULLIF(TRIM(s.deptcode), '')                  IS NULL
       OR NULLIF(TRIM(s.deptname), '')                  IS NULL
       OR NULLIF(TRIM(s.leaveldeptcode), '')            IS NULL
       OR NULLIF(TRIM(s.leaveldeptname), '')            IS NULL
       OR NULLIF(TRIM(s.leavehospitaldistrictcode), '') IS NULL
       OR NULLIF(TRIM(s.leavehospitaldistrictname), '') IS NULL );
-- ----------------------------------------------------------------------------
-- 4) å£å¾„说明 + å¯é€‰çš„"强制改写"
--
--    4.1 ä¸ºä»€ä¹ˆç¦»é™¢ 4 åˆ—也用「只补空」:
--        service_type = 17/18(入院宣教/入院随访)生成时走 type 4/5 åˆ†æ”¯ï¼Œ
--        `leaveldeptcode/name` é‡Œå­˜çš„其实是**入院科室**(d.setLeaveldeptcode(inhosp.getDeptcode()))。
--        è¿™äº›è¡Œéžç©º â†’ Â§3 ä¸ä¼šåŠ¨å®ƒä»¬ï¼›è‹¥ä¸ºç©ºåˆ™ä¼šè¢«è¡¥æˆ pat_med_inhosp.leaveldeptcode
--        ï¼ˆä½é™¢æ‚£è€…通常为空 â†’ ç­‰äºŽæ²¡å˜ï¼‰ã€‚
--        å‡ºé™¢éšè®¿ï¼ˆ2/13)与满意度子任务(6/14/15/16)的离院 4 åˆ—生成时本来就取自
--        pat_med_inhosp,只可能是历史脏数据为空,补空即修数据。
--
--    4.2 å¦‚果你要的是"17/18 ä¹Ÿç»Ÿä¸€æˆå‡ºé™¢ç§‘室口径",用下面这段(**会覆盖**,先跑 Â§2 å¤‡ä»½ï¼‰ï¼š
-- ----------------------------------------------------------------------------
-- UPDATE service_subtask s
--   JOIN pat_med_inhosp b ON b.inhospid = s.inhospid AND b.patid = s.patid
--    SET s.leaveldeptcode            = b.leaveldeptcode,
--        s.leaveldeptname            = b.leaveldeptname,
--        s.leavehospitaldistrictcode = b.leavehospitaldistrictcode,
--        s.leavehospitaldistrictname = b.leavehospitaldistrictname
--  WHERE s.inhospid IS NOT NULL
--    AND s.service_type IN ('17', '18')
--    AND (NULLIF(TRIM(b.leaveldeptcode), '') IS NOT NULL
--         OR NULLIF(TRIM(b.leavehospitaldistrictcode), '') IS NOT NULL);
-- ----------------------------------------------------------------------------
-- 5) ã€å¯é€‰ã€‘全都强制覆盖版("原来有值但是错的"时用)
--    æŠŠ Â§3 çš„ COALESCE(...) æ¢æˆç›´æŽ¥èµ‹å€¼ï¼Œå¹¶åŽ»æŽ‰ WHERE é‡Œé‚£ä¸²ã€Œç›®æ ‡åˆ—为空」条件:
--      SET s.hospitaldistrictcode = b.hospitaldistrictcode,
--          s.hospitaldistrictname = b.hospitaldistrictname,
--          s.deptcode = b.deptcode, s.deptname = b.deptname,
--          s.leaveldeptcode = b.leaveldeptcode, s.leaveldeptname = b.leaveldeptname,
--          s.leavehospitaldistrictcode = b.leavehospitaldistrictcode,
--          s.leavehospitaldistrictname = b.leavehospitaldistrictname
--    âš ï¸ pat_med_inhosp.dept* ä¼šè¢«ã€Œåœ¨é™¢ä¿¡æ¯åˆ·æ–°ã€å®šæ—¶ä»»åŠ¡æŒ‰ HIS å½“前科室改写,
--       è¦†ç›–后可能把"出院当时的科室"改成"现在 HIS é‡Œçš„科室"。覆盖前务必先备份。
-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
-- 6) å¤§è¡¨åˆ†æ‰¹æ¨¡æ¿ï¼ˆå¤šè¡¨ UPDATE ä¸æ”¯æŒ LIMIT,用 id åŒºé—´ï¼›æ¯æ®µå•独提交)
--    å…ˆæŸ¥èŒƒå›´ï¼šSELECT MIN(id), MAX(id) FROM service_subtask;
-- ----------------------------------------------------------------------------
-- UPDATE service_subtask s
--   JOIN pat_med_inhosp b ON b.inhospid = s.inhospid AND b.patid = s.patid
--    SET s.hospitaldistrictcode      = COALESCE(NULLIF(TRIM(s.hospitaldistrictcode), ''),      b.hospitaldistrictcode),
--        s.hospitaldistrictname      = COALESCE(NULLIF(TRIM(s.hospitaldistrictname), ''),      b.hospitaldistrictname),
--        s.deptcode                  = COALESCE(NULLIF(TRIM(s.deptcode), ''),                  b.deptcode),
--        s.deptname                  = COALESCE(NULLIF(TRIM(s.deptname), ''),                  b.deptname),
--        s.leaveldeptcode            = COALESCE(NULLIF(TRIM(s.leaveldeptcode), ''),            b.leaveldeptcode),
--        s.leaveldeptname            = COALESCE(NULLIF(TRIM(s.leaveldeptname), ''),            b.leaveldeptname),
--        s.leavehospitaldistrictcode = COALESCE(NULLIF(TRIM(s.leavehospitaldistrictcode), ''), b.leavehospitaldistrictcode),
--        s.leavehospitaldistrictname = COALESCE(NULLIF(TRIM(s.leavehospitaldistrictname), ''), b.leavehospitaldistrictname)
--  WHERE s.id > 1 AND s.id <= 50000
--    AND s.inhospid IS NOT NULL
--    AND s.service_type <> '3'
--    AND (s.hosp_type IS NULL OR s.hosp_type <> '8')
--    AND (  NULLIF(TRIM(s.hospitaldistrictcode), '')      IS NULL
--        OR NULLIF(TRIM(s.hospitaldistrictname), '')      IS NULL
--        OR NULLIF(TRIM(s.deptcode), '')                  IS NULL
--        OR NULLIF(TRIM(s.deptname), '')                  IS NULL
--        OR NULLIF(TRIM(s.leaveldeptcode), '')            IS NULL
--        OR NULLIF(TRIM(s.leaveldeptname), '')            IS NULL
--        OR NULLIF(TRIM(s.leavehospitaldistrictcode), '') IS NULL
--        OR NULLIF(TRIM(s.leavehospitaldistrictname), '') IS NULL );
-- ----------------------------------------------------------------------------
-- 7) å›žæ»šï¼ˆæŒ‰ Â§2 å¤‡ä»½è¡¨è¿˜åŽŸ 8 åˆ—)
-- ----------------------------------------------------------------------------
-- UPDATE service_subtask s
--   JOIN bak_service_subtask_dept_20261106 k ON k.id = s.id
--    SET s.hospitaldistrictcode      = k.hospitaldistrictcode,
--        s.hospitaldistrictname      = k.hospitaldistrictname,
--        s.deptcode                  = k.deptcode,
--        s.deptname                  = k.deptname,
--        s.leaveldeptcode            = k.leaveldeptcode,
--        s.leaveldeptname            = k.leaveldeptname,
--        s.leavehospitaldistrictcode = k.leavehospitaldistrictcode,
--        s.leavehospitaldistrictname = k.leavehospitaldistrictname;
-- ----------------------------------------------------------------------------
-- 8) å›žå¡«åŽæ ¸å¯¹
-- ----------------------------------------------------------------------------
-- 8.1 è¿˜å‰©å¤šå°‘空(应接近 0;剩下的基本是 Â§1.3/§1.4 å‘½ä¸­ä¸äº†çš„)
SELECT COUNT(*)                                                                    AS still_empty_rows,
       SUM(hospitaldistrictcode      IS NULL OR hospitaldistrictcode      = '')     AS hd_code_empty,
       SUM(hospitaldistrictname      IS NULL OR hospitaldistrictname      = '')     AS hd_name_empty,
       SUM(deptcode                  IS NULL OR deptcode                  = '')     AS dept_code_empty,
       SUM(deptname                  IS NULL OR deptname                  = '')     AS dept_name_empty,
       SUM(leaveldeptcode            IS NULL OR leaveldeptcode            = '')     AS lv_dept_code_empty,
       SUM(leaveldeptname            IS NULL OR leaveldeptname            = '')     AS lv_dept_name_empty,
       SUM(leavehospitaldistrictcode IS NULL OR leavehospitaldistrictcode = '')     AS lv_hd_code_empty,
       SUM(leavehospitaldistrictname IS NULL OR leavehospitaldistrictname = '')     AS lv_hd_name_empty
  FROM service_subtask s
  JOIN pat_med_inhosp b ON b.inhospid = s.inhospid AND b.patid = s.patid
 WHERE s.inhospid IS NOT NULL
   AND s.service_type <> '3'
   AND (s.hosp_type IS NULL OR s.hosp_type <> '8')
   AND (  NULLIF(TRIM(s.hospitaldistrictcode), '')      IS NULL
       OR NULLIF(TRIM(s.hospitaldistrictname), '')      IS NULL
       OR NULLIF(TRIM(s.deptcode), '')                  IS NULL
       OR NULLIF(TRIM(s.deptname), '')                  IS NULL
       OR NULLIF(TRIM(s.leaveldeptcode), '')            IS NULL
       OR NULLIF(TRIM(s.leaveldeptname), '')            IS NULL
       OR NULLIF(TRIM(s.leavehospitaldistrictcode), '') IS NULL
       OR NULLIF(TRIM(s.leavehospitaldistrictname), '') IS NULL );
-- 8.2 é€è¡Œå¯¹ç…§æŠ½æ£€ï¼ˆä¸¤è¾¹æ˜¯å¦ä¸€è‡´ï¼‰
SELECT s.id, s.service_type, s.inhospid,
       s.deptcode                  AS sub_dept,    b.deptcode                  AS inhosp_dept,
       s.hospitaldistrictcode      AS sub_hd,      b.hospitaldistrictcode      AS inhosp_hd,
       s.leaveldeptcode            AS sub_lv_dept, b.leaveldeptcode            AS inhosp_lv_dept,
       s.leavehospitaldistrictcode AS sub_lv_hd,   b.leavehospitaldistrictcode AS inhosp_lv_hd
  FROM service_subtask s
  JOIN pat_med_inhosp b ON b.inhospid = s.inhospid AND b.patid = s.patid
 WHERE s.inhospid IS NOT NULL
   AND s.service_type <> '3'
 ORDER BY s.id DESC
 LIMIT 50;
sql/cleanup_unused_tables_20260923.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,336 @@
-- ============================================================================
-- smartor_nhfy æ•°æ®åº“ æœªä½¿ç”¨è¡¨æ¸…理脚本
-- ç”Ÿæˆæ—¥æœŸ: 20260923
-- æ¥æº: ä¸Žä»£ç å…¨é‡æ¯”对(178 ä¸ª Mapper XML + å…¨éƒ¨ Java æºç ï¼‰
-- èŒƒå›´: 132 å¼ ä»£ç é›¶å¼•用的表(空表 110 å¼  + æœ‰æ•°æ®çš„备份表 22 å¼ ï¼‰
-- æ³¨æ„: ä»¥ä¸‹è¡¨å·²è¢«æŽ’除,请勿删除:
--   * qrtz_*  (Quartz æ¡†æž¶è¡¨ï¼Œè¿è¡Œæ—¶ä½¿ç”¨)
--   * magic_config (magic-api æ¡†æž¶è¡¨)
--   * pat_med_outhosp_20260406 (分表机制+存储过程+视图 v_pat_med_outhosp ä½¿ç”¨)
-- ============================================================================
-- ----------------------------------------------------------------------------
-- ã€é‡è¦ã€‘执行前务必先备份。本库疑似多系统共用,建议先确认无其他程序读写。
-- æœ‰æ•°æ®çš„ 22 å¼ è¡¨å»ºè®®å…ˆæ‰§è¡Œä¸‹æ–¹ mysqldump å¤‡ä»½ï¼š
--   mysqldump -h192.168.99.192 -P3306 -uroot -p smartor_nhfy \
--     icd10_copy1 \
--     pat_archive-old \
--     pat_archive_copy1 \
--     pat_archive_copy6 \
--     pat_med_inhosp-old \
--     pat_med_inhosp_0710 \
--     pat_med_inhosp_0814 \
--     pat_med_inhosp_copy1 \
--     service_subtask_0710 \
--     service_subtask_copy1 \
--     service_subtask_copy2 \
--     service_subtask_copy3 \
--     service_subtask_copy4 \
--     service_subtask_copy5 \
--     service_subtask_detail_copy1 \
--     service_subtask_detail_copy2 \
--     service_task_260806 \
--     service_taskdept_260806 \
--     service_taskdiag_copy4 \
--     svy_lib_template_targetoption_copy1 \
--     svy_task_template_targetoption_copy1 \
--     sys_user-old
--   > backup_unused_tables_20260923.sql
-- ----------------------------------------------------------------------------
USE `smartor_nhfy`;
SET FOREIGN_KEY_CHECKS = 0;
-- ============================================================================
-- ç¬¬ 0 éƒ¨åˆ†ï¼šé¢„检查(只读,不会改任何数据)
-- è¯´æ˜Ž: information_schema.tables.table_rows å¯¹ InnoDB æ˜¯ä¼°ç®—值,
--       ä¸‹é¢çš„ UNION ALL æ˜¯ç²¾ç¡® COUNT,执行后应看到所有 cnt å‡ä¸º 0。
-- è‹¥æŸè¡Œ cnt > 0,说明该表仍有数据,请先从「第 1 éƒ¨åˆ†ã€ä¸­ç§»é™¤è¯¥è¡¨çš„ DROP è¯­å¥ã€‚
-- ============================================================================
SELECT 'xxx' AS tbl, 0 AS cnt WHERE 1=0
UNION ALL SELECT '_base_dept', COUNT(*) FROM `_base_dept`
UNION ALL SELECT '_base_dict', COUNT(*) FROM `_base_dict`
UNION ALL SELECT '_base_dict_item', COUNT(*) FROM `_base_dict_item`
UNION ALL SELECT '_base_dict_medarea', COUNT(*) FROM `_base_dict_medarea`
UNION ALL SELECT '_base_permission', COUNT(*) FROM `_base_permission`
UNION ALL SELECT '_base_permissionresources', COUNT(*) FROM `_base_permissionresources`
UNION ALL SELECT '_base_role', COUNT(*) FROM `_base_role`
UNION ALL SELECT 'ai_conversation', COUNT(*) FROM `ai_conversation`
UNION ALL SELECT 'ai_message', COUNT(*) FROM `ai_message`
UNION ALL SELECT 'base_applydisease', COUNT(*) FROM `base_applydisease`
UNION ALL SELECT 'base_dict_disease', COUNT(*) FROM `base_dict_disease`
UNION ALL SELECT 'base_dict_drug', COUNT(*) FROM `base_dict_drug`
UNION ALL SELECT 'base_dict_inspect', COUNT(*) FROM `base_dict_inspect`
UNION ALL SELECT 'base_dict_physicalpackage', COUNT(*) FROM `base_dict_physicalpackage`
UNION ALL SELECT 'base_dict_surgery', COUNT(*) FROM `base_dict_surgery`
UNION ALL SELECT 'base_dict_test', COUNT(*) FROM `base_dict_test`
UNION ALL SELECT 'base_person', COUNT(*) FROM `base_person`
UNION ALL SELECT 'base_pushtemplate', COUNT(*) FROM `base_pushtemplate`
UNION ALL SELECT 'base_scoringway', COUNT(*) FROM `base_scoringway`
UNION ALL SELECT 'base_scoringwaydetail', COUNT(*) FROM `base_scoringwaydetail`
UNION ALL SELECT 'base_sequence', COUNT(*) FROM `base_sequence`
UNION ALL SELECT 'base_taskvariable', COUNT(*) FROM `base_taskvariable`
UNION ALL SELECT 'base_template', COUNT(*) FROM `base_template`
UNION ALL SELECT 'base_templatecategory', COUNT(*) FROM `base_templatecategory`
UNION ALL SELECT 'base_wxaccount', COUNT(*) FROM `base_wxaccount`
UNION ALL SELECT 'bpbj', COUNT(*) FROM `bpbj`
UNION ALL SELECT 'chat_friend', COUNT(*) FROM `chat_friend`
UNION ALL SELECT 'chat_friend_group', COUNT(*) FROM `chat_friend_group`
UNION ALL SELECT 'chat_group_chat', COUNT(*) FROM `chat_group_chat`
UNION ALL SELECT 'chat_group_members', COUNT(*) FROM `chat_group_members`
UNION ALL SELECT 'chat_message', COUNT(*) FROM `chat_message`
UNION ALL SELECT 'chat_message_statistics', COUNT(*) FROM `chat_message_statistics`
UNION ALL SELECT 'chat_user', COUNT(*) FROM `chat_user`
UNION ALL SELECT 'crm_complaint', COUNT(*) FROM `crm_complaint`
UNION ALL SELECT 'cti_callseats', COUNT(*) FROM `cti_callseats`
UNION ALL SELECT 'dev_bug', COUNT(*) FROM `dev_bug`
UNION ALL SELECT 'dev_checkrecords', COUNT(*) FROM `dev_checkrecords`
UNION ALL SELECT 'dev_equipmentinfo', COUNT(*) FROM `dev_equipmentinfo`
UNION ALL SELECT 'dev_repairpl', COUNT(*) FROM `dev_repairpl`
UNION ALL SELECT 'dev_repairtype', COUNT(*) FROM `dev_repairtype`
UNION ALL SELECT 'dev_rprecords', COUNT(*) FROM `dev_rprecords`
UNION ALL SELECT 'flw_channel_definition', COUNT(*) FROM `flw_channel_definition`
UNION ALL SELECT 'flw_ev_databasechangelog', COUNT(*) FROM `flw_ev_databasechangelog`
UNION ALL SELECT 'flw_ev_databasechangeloglock', COUNT(*) FROM `flw_ev_databasechangeloglock`
UNION ALL SELECT 'flw_event_definition', COUNT(*) FROM `flw_event_definition`
UNION ALL SELECT 'flw_event_deployment', COUNT(*) FROM `flw_event_deployment`
UNION ALL SELECT 'flw_event_resource', COUNT(*) FROM `flw_event_resource`
UNION ALL SELECT 'flw_ru_batch', COUNT(*) FROM `flw_ru_batch`
UNION ALL SELECT 'flw_ru_batch_part', COUNT(*) FROM `flw_ru_batch_part`
UNION ALL SELECT 'ivr_task_singlerecords', COUNT(*) FROM `ivr_task_singlerecords`
UNION ALL SELECT 'ivr_taskdetail1', COUNT(*) FROM `ivr_taskdetail1`
UNION ALL SELECT 'pat_med_apointment', COUNT(*) FROM `pat_med_apointment`
UNION ALL SELECT 'pat_tube_feek', COUNT(*) FROM `pat_tube_feek`
UNION ALL SELECT 'pat_tube_feek_medicine', COUNT(*) FROM `pat_tube_feek_medicine`
UNION ALL SELECT 'patientinfo', COUNT(*) FROM `patientinfo`
UNION ALL SELECT 'questionnaire_advise', COUNT(*) FROM `questionnaire_advise`
UNION ALL SELECT 'questionnaire_question', COUNT(*) FROM `questionnaire_question`
UNION ALL SELECT 'questionnaire_questionnaire', COUNT(*) FROM `questionnaire_questionnaire`
UNION ALL SELECT 'questionnaire_questionoption', COUNT(*) FROM `questionnaire_questionoption`
UNION ALL SELECT 'questionnaire_questiontype', COUNT(*) FROM `questionnaire_questiontype`
UNION ALL SELECT 'questionnaire_result', COUNT(*) FROM `questionnaire_result`
UNION ALL SELECT 'questionnaire_resultdetail', COUNT(*) FROM `questionnaire_resultdetail`
UNION ALL SELECT 'questionnairequestion', COUNT(*) FROM `questionnairequestion`
UNION ALL SELECT 'questionnairequestionoption', COUNT(*) FROM `questionnairequestionoption`
UNION ALL SELECT 'scheme_taskrecord_callrecord', COUNT(*) FROM `scheme_taskrecord_callrecord`
UNION ALL SELECT 'service_scheme', COUNT(*) FROM `service_scheme`
UNION ALL SELECT 'service_taskdiagnose', COUNT(*) FROM `service_taskdiagnose`
UNION ALL SELECT 'sms_account', COUNT(*) FROM `sms_account`
UNION ALL SELECT 'soline', COUNT(*) FROM `soline`
UNION ALL SELECT 'sopaymentterm', COUNT(*) FROM `sopaymentterm`
UNION ALL SELECT 'student', COUNT(*) FROM `student`
UNION ALL SELECT 'svr_plan', COUNT(*) FROM `svr_plan`
UNION ALL SELECT 'svr_prj', COUNT(*) FROM `svr_prj`
UNION ALL SELECT 'svr_prjtask', COUNT(*) FROM `svr_prjtask`
UNION ALL SELECT 'svr_prjtaskline', COUNT(*) FROM `svr_prjtaskline`
UNION ALL SELECT 'svr_task', COUNT(*) FROM `svr_task`
UNION ALL SELECT 'svr_taskrecord', COUNT(*) FROM `svr_taskrecord`
UNION ALL SELECT 'svy_lib_resultrule', COUNT(*) FROM `svy_lib_resultrule`
UNION ALL SELECT 'svy_task_template_rule', COUNT(*) FROM `svy_task_template_rule`
UNION ALL SELECT 'sys_deploy_form', COUNT(*) FROM `sys_deploy_form`
UNION ALL SELECT 'sys_expression', COUNT(*) FROM `sys_expression`
UNION ALL SELECT 'sys_form', COUNT(*) FROM `sys_form`
UNION ALL SELECT 'sys_listener', COUNT(*) FROM `sys_listener`
UNION ALL SELECT 'sys_task_form', COUNT(*) FROM `sys_task_form`
UNION ALL SELECT 'sys_user_dept_import', COUNT(*) FROM `sys_user_dept_import`
UNION ALL SELECT 'sys_variable', COUNT(*) FROM `sys_variable`
UNION ALL SELECT 't_delid', COUNT(*) FROM `t_delid`
UNION ALL SELECT 'table_svy', COUNT(*) FROM `table_svy`
UNION ALL SELECT 'tabledesc', COUNT(*) FROM `tabledesc`
UNION ALL SELECT 'tools', COUNT(*) FROM `tools`
UNION ALL SELECT 'treatment_plan', COUNT(*) FROM `treatment_plan`
UNION ALL SELECT 'treatment_plandetail', COUNT(*) FROM `treatment_plandetail`
UNION ALL SELECT 'treatment_plantemplate', COUNT(*) FROM `treatment_plantemplate`
UNION ALL SELECT 'treatment_plantemplatedetail', COUNT(*) FROM `treatment_plantemplatedetail`
UNION ALL SELECT 'wechat_accesstoken', COUNT(*) FROM `wechat_accesstoken`
UNION ALL SELECT 'wechat_function', COUNT(*) FROM `wechat_function`
UNION ALL SELECT 'wechat_function_jumpurl', COUNT(*) FROM `wechat_function_jumpurl`
UNION ALL SELECT 'wechat_functiontype', COUNT(*) FROM `wechat_functiontype`
UNION ALL SELECT 'wechat_jssdk_ticket', COUNT(*) FROM `wechat_jssdk_ticket`
UNION ALL SELECT 'wechat_menu', COUNT(*) FROM `wechat_menu`
UNION ALL SELECT 'wechat_pay_success', COUNT(*) FROM `wechat_pay_success`
UNION ALL SELECT 'wechat_qrcode', COUNT(*) FROM `wechat_qrcode`
UNION ALL SELECT 'wechat_respmessage', COUNT(*) FROM `wechat_respmessage`
UNION ALL SELECT 'wechat_respmessage_key', COUNT(*) FROM `wechat_respmessage_key`
UNION ALL SELECT 'wechat_sendrecord', COUNT(*) FROM `wechat_sendrecord`
UNION ALL SELECT 'wechat_subscribeqrcode', COUNT(*) FROM `wechat_subscribeqrcode`
UNION ALL SELECT 'wechat_subscribesend', COUNT(*) FROM `wechat_subscribesend`
UNION ALL SELECT 'wechat_templatemessage_templatelist', COUNT(*) FROM `wechat_templatemessage_templatelist`
UNION ALL SELECT 'wechat_userinfo', COUNT(*) FROM `wechat_userinfo`
UNION ALL SELECT 'wechat_userinfo_subscribeinfo', COUNT(*) FROM `wechat_userinfo_subscribeinfo`
ORDER BY cnt DESC;
-- ============================================================================
-- ç¬¬ 1 éƒ¨åˆ†ï¼šåˆ é™¤ç©ºè¡¨ï¼ˆ110 å¼ ï¼Œå·²æ ¸å¯¹å‡ä¸º 0 è¡Œï¼‰
-- ä½¿ç”¨ IF EXISTS,可重复执行,不会报错
-- ============================================================================
DROP TABLE IF EXISTS `_base_dept`;
DROP TABLE IF EXISTS `_base_dict`;
DROP TABLE IF EXISTS `_base_dict_item`;
DROP TABLE IF EXISTS `_base_dict_medarea`;
DROP TABLE IF EXISTS `_base_permission`;
DROP TABLE IF EXISTS `_base_permissionresources`;
DROP TABLE IF EXISTS `_base_role`;
DROP TABLE IF EXISTS `ai_conversation`;
DROP TABLE IF EXISTS `ai_message`;
DROP TABLE IF EXISTS `base_applydisease`;
DROP TABLE IF EXISTS `base_dict_disease`;
DROP TABLE IF EXISTS `base_dict_drug`;
DROP TABLE IF EXISTS `base_dict_inspect`;
DROP TABLE IF EXISTS `base_dict_physicalpackage`;
DROP TABLE IF EXISTS `base_dict_surgery`;
DROP TABLE IF EXISTS `base_dict_test`;
DROP TABLE IF EXISTS `base_person`;
DROP TABLE IF EXISTS `base_pushtemplate`;
DROP TABLE IF EXISTS `base_scoringway`;
DROP TABLE IF EXISTS `base_scoringwaydetail`;
DROP TABLE IF EXISTS `base_sequence`;
DROP TABLE IF EXISTS `base_taskvariable`;
DROP TABLE IF EXISTS `base_template`;
DROP TABLE IF EXISTS `base_templatecategory`;
DROP TABLE IF EXISTS `base_wxaccount`;
DROP TABLE IF EXISTS `bpbj`;
DROP TABLE IF EXISTS `chat_friend`;
DROP TABLE IF EXISTS `chat_friend_group`;
DROP TABLE IF EXISTS `chat_group_chat`;
DROP TABLE IF EXISTS `chat_group_members`;
DROP TABLE IF EXISTS `chat_message`;
DROP TABLE IF EXISTS `chat_message_statistics`;
DROP TABLE IF EXISTS `chat_user`;
DROP TABLE IF EXISTS `crm_complaint`;
DROP TABLE IF EXISTS `cti_callseats`;
DROP TABLE IF EXISTS `dev_bug`;
DROP TABLE IF EXISTS `dev_checkrecords`;
DROP TABLE IF EXISTS `dev_equipmentinfo`;
DROP TABLE IF EXISTS `dev_repairpl`;
DROP TABLE IF EXISTS `dev_repairtype`;
DROP TABLE IF EXISTS `dev_rprecords`;
DROP TABLE IF EXISTS `flw_channel_definition`;
DROP TABLE IF EXISTS `flw_ev_databasechangelog`;
DROP TABLE IF EXISTS `flw_ev_databasechangeloglock`;
DROP TABLE IF EXISTS `flw_event_definition`;
DROP TABLE IF EXISTS `flw_event_deployment`;
DROP TABLE IF EXISTS `flw_event_resource`;
DROP TABLE IF EXISTS `flw_ru_batch`;
DROP TABLE IF EXISTS `flw_ru_batch_part`;
DROP TABLE IF EXISTS `ivr_task_singlerecords`;
DROP TABLE IF EXISTS `ivr_taskdetail1`;
DROP TABLE IF EXISTS `pat_med_apointment`;
DROP TABLE IF EXISTS `pat_tube_feek`;
DROP TABLE IF EXISTS `pat_tube_feek_medicine`;
DROP TABLE IF EXISTS `patientinfo`;
DROP TABLE IF EXISTS `questionnaire_advise`;
DROP TABLE IF EXISTS `questionnaire_question`;
DROP TABLE IF EXISTS `questionnaire_questionnaire`;
DROP TABLE IF EXISTS `questionnaire_questionoption`;
DROP TABLE IF EXISTS `questionnaire_questiontype`;
DROP TABLE IF EXISTS `questionnaire_result`;
DROP TABLE IF EXISTS `questionnaire_resultdetail`;
DROP TABLE IF EXISTS `questionnairequestion`;
DROP TABLE IF EXISTS `questionnairequestionoption`;
DROP TABLE IF EXISTS `scheme_taskrecord_callrecord`;
DROP TABLE IF EXISTS `service_scheme`;
DROP TABLE IF EXISTS `service_taskdiagnose`;
DROP TABLE IF EXISTS `sms_account`;
DROP TABLE IF EXISTS `soline`;
DROP TABLE IF EXISTS `sopaymentterm`;
DROP TABLE IF EXISTS `student`;
DROP TABLE IF EXISTS `svr_plan`;
DROP TABLE IF EXISTS `svr_prj`;
DROP TABLE IF EXISTS `svr_prjtask`;
DROP TABLE IF EXISTS `svr_prjtaskline`;
DROP TABLE IF EXISTS `svr_task`;
DROP TABLE IF EXISTS `svr_taskrecord`;
DROP TABLE IF EXISTS `svy_lib_resultrule`;
DROP TABLE IF EXISTS `svy_task_template_rule`;
DROP TABLE IF EXISTS `sys_deploy_form`;
DROP TABLE IF EXISTS `sys_expression`;
DROP TABLE IF EXISTS `sys_form`;
DROP TABLE IF EXISTS `sys_listener`;
DROP TABLE IF EXISTS `sys_task_form`;
DROP TABLE IF EXISTS `sys_user_dept_import`;
DROP TABLE IF EXISTS `sys_variable`;
DROP TABLE IF EXISTS `t_delid`;
DROP TABLE IF EXISTS `table_svy`;
DROP TABLE IF EXISTS `tabledesc`;
DROP TABLE IF EXISTS `tools`;
DROP TABLE IF EXISTS `treatment_plan`;
DROP TABLE IF EXISTS `treatment_plandetail`;
DROP TABLE IF EXISTS `treatment_plantemplate`;
DROP TABLE IF EXISTS `treatment_plantemplatedetail`;
DROP TABLE IF EXISTS `wechat_accesstoken`;
DROP TABLE IF EXISTS `wechat_function`;
DROP TABLE IF EXISTS `wechat_function_jumpurl`;
DROP TABLE IF EXISTS `wechat_functiontype`;
DROP TABLE IF EXISTS `wechat_jssdk_ticket`;
DROP TABLE IF EXISTS `wechat_menu`;
DROP TABLE IF EXISTS `wechat_pay_success`;
DROP TABLE IF EXISTS `wechat_qrcode`;
DROP TABLE IF EXISTS `wechat_respmessage`;
DROP TABLE IF EXISTS `wechat_respmessage_key`;
DROP TABLE IF EXISTS `wechat_sendrecord`;
DROP TABLE IF EXISTS `wechat_subscribeqrcode`;
DROP TABLE IF EXISTS `wechat_subscribesend`;
DROP TABLE IF EXISTS `wechat_templatemessage_templatelist`;
DROP TABLE IF EXISTS `wechat_userinfo`;
DROP TABLE IF EXISTS `wechat_userinfo_subscribeinfo`;
-- ============================================================================
-- ç¬¬ 2 éƒ¨åˆ†ï¼šæœ‰æ•°æ®çš„备份表(22 å¼ ï¼‰
-- ä¸ºå®‰å…¨èµ·è§ï¼Œé»˜è®¤åªåšã€Œæ”¹åå½’档」(可随时改回),观察一段时间后再删除:
--   * å½’档后表名前缀为 zzz_del_,排在库列表末尾,不影响业务
--   * æ¢å¤æ–¹æ³•: RENAME TABLE `zzz_del_xxx_20260923` TO `xxx`;
--   * ç¡®è®¤æ— è¯¯åŽï¼Œæ‰§è¡Œæœ¬æ–‡ä»¶æœ«å°¾è¢«æ³¨é‡ŠæŽ‰çš„ DROP è¯­å¥å½»åº•删除
-- ============================================================================
RENAME TABLE `icd10_copy1` TO `zzz_del_icd10_copy1_20260923`;
RENAME TABLE `pat_archive-old` TO `zzz_del_pat_archive-old_20260923`;
RENAME TABLE `pat_archive_copy1` TO `zzz_del_pat_archive_copy1_20260923`;
RENAME TABLE `pat_archive_copy6` TO `zzz_del_pat_archive_copy6_20260923`;
RENAME TABLE `pat_med_inhosp-old` TO `zzz_del_pat_med_inhosp-old_20260923`;
RENAME TABLE `pat_med_inhosp_0710` TO `zzz_del_pat_med_inhosp_0710_20260923`;
RENAME TABLE `pat_med_inhosp_0814` TO `zzz_del_pat_med_inhosp_0814_20260923`;
RENAME TABLE `pat_med_inhosp_copy1` TO `zzz_del_pat_med_inhosp_copy1_20260923`;
RENAME TABLE `service_subtask_0710` TO `zzz_del_service_subtask_0710_20260923`;
RENAME TABLE `service_subtask_copy1` TO `zzz_del_service_subtask_copy1_20260923`;
RENAME TABLE `service_subtask_copy2` TO `zzz_del_service_subtask_copy2_20260923`;
RENAME TABLE `service_subtask_copy3` TO `zzz_del_service_subtask_copy3_20260923`;
RENAME TABLE `service_subtask_copy4` TO `zzz_del_service_subtask_copy4_20260923`;
RENAME TABLE `service_subtask_copy5` TO `zzz_del_service_subtask_copy5_20260923`;
RENAME TABLE `service_subtask_detail_copy1` TO `zzz_del_service_subtask_detail_copy1_20260923`;
RENAME TABLE `service_subtask_detail_copy2` TO `zzz_del_service_subtask_detail_copy2_20260923`;
RENAME TABLE `service_task_260806` TO `zzz_del_service_task_260806_20260923`;
RENAME TABLE `service_taskdept_260806` TO `zzz_del_service_taskdept_260806_20260923`;
RENAME TABLE `service_taskdiag_copy4` TO `zzz_del_service_taskdiag_copy4_20260923`;
RENAME TABLE `svy_lib_template_targetoption_copy1` TO `zzz_del_svy_lib_template_targetoption_copy1_20260923`;
RENAME TABLE `svy_task_template_targetoption_copy1` TO `zzz_del_svy_task_template_targetoption_copy1_20260923`;
RENAME TABLE `sys_user-old` TO `zzz_del_sys_user-old_20260923`;
-- ----------------------------------------------------------------------------
-- å½»åº•删除(观察期结束、确认归档无误后,取消下面注释执行)
-- ----------------------------------------------------------------------------
-- DROP TABLE IF EXISTS `zzz_del_icd10_copy1_20260923`;
-- DROP TABLE IF EXISTS `zzz_del_pat_archive-old_20260923`;
-- DROP TABLE IF EXISTS `zzz_del_pat_archive_copy1_20260923`;
-- DROP TABLE IF EXISTS `zzz_del_pat_archive_copy6_20260923`;
-- DROP TABLE IF EXISTS `zzz_del_pat_med_inhosp-old_20260923`;
-- DROP TABLE IF EXISTS `zzz_del_pat_med_inhosp_0710_20260923`;
-- DROP TABLE IF EXISTS `zzz_del_pat_med_inhosp_0814_20260923`;
-- DROP TABLE IF EXISTS `zzz_del_pat_med_inhosp_copy1_20260923`;
-- DROP TABLE IF EXISTS `zzz_del_service_subtask_0710_20260923`;
-- DROP TABLE IF EXISTS `zzz_del_service_subtask_copy1_20260923`;
-- DROP TABLE IF EXISTS `zzz_del_service_subtask_copy2_20260923`;
-- DROP TABLE IF EXISTS `zzz_del_service_subtask_copy3_20260923`;
-- DROP TABLE IF EXISTS `zzz_del_service_subtask_copy4_20260923`;
-- DROP TABLE IF EXISTS `zzz_del_service_subtask_copy5_20260923`;
-- DROP TABLE IF EXISTS `zzz_del_service_subtask_detail_copy1_20260923`;
-- DROP TABLE IF EXISTS `zzz_del_service_subtask_detail_copy2_20260923`;
-- DROP TABLE IF EXISTS `zzz_del_service_task_260806_20260923`;
-- DROP TABLE IF EXISTS `zzz_del_service_taskdept_260806_20260923`;
-- DROP TABLE IF EXISTS `zzz_del_service_taskdiag_copy4_20260923`;
-- DROP TABLE IF EXISTS `zzz_del_svy_lib_template_targetoption_copy1_20260923`;
-- DROP TABLE IF EXISTS `zzz_del_svy_task_template_targetoption_copy1_20260923`;
-- DROP TABLE IF EXISTS `zzz_del_sys_user-old_20260923`;
SET FOREIGN_KEY_CHECKS = 1;
-- =============================== è„šæœ¬ç»“æŸ ====================================
sql/create_pat_med_outhosp_error.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
-- ============================================================================
-- çœç«‹åŒå¾·é—¨æ€¥è¯Šé‡‡é›†å¤±è´¥è®°å½•表
--
-- èƒŒæ™¯ï¼šHIS é—¨æ€¥è¯Šæ•°æ®åªèƒ½æŒ‰å¤©æŸ¥ï¼Œé‡‡é›†ä»»åŠ¡æ¯åŠå°æ—¶æŠŠå½“å¤©å…¨é‡ï¼ˆä¸€ä¸‡å¤šæ¡ï¼‰æ‹‰ä¸€éã€‚
--       å•条记录如果转换失败/档案处理失败/入库失败,原来会整页失败并卡住采集进度。
--       çŽ°åœ¨æ”¹ä¸ºå†™å…¥æœ¬è¡¨äº¤ç»™äººå·¥å¤„ç†ï¼Œé‡‡é›†è¿›åº¦ä¸å—å½±å“ã€‚
--
-- åŒä¸€æ¡è®°å½•同一类错误只保留一行:唯一索引 uk_serialnum_type(serialnum, error_type),
-- é‡å¤å¤±è´¥ç´¯åŠ  fail_count(可以看出它连续失败了多少轮),异常信息和原始 JSON ä¼šè¢«æœ€æ–°ä¸€æ¬¡è¦†ç›–。
-- äººå·¥æŠŠ handle_status æ”¹æˆ 1(已处理)后如果该记录再次失败,会自动回到 0(待处理);
-- æ”¹æˆ 2(已忽略)的不会再被打扰。
--
-- äººå·¥å¤„理常用 SQL:
--   æŸ¥çœ‹å¾…处理:select * from pat_med_outhosp_error where handle_status = '0' order by fail_count desc, id;
--   çœ‹åŽŸå§‹æ•°æ®ï¼šselect serialnum, error_type, error_msg, raw_data from pat_med_outhosp_error where handle_status = '0';
--   æ ‡è®°å·²å¤„理:update pat_med_outhosp_error set handle_status = '1', handle_remark = '原因+处理人' where id = ?;
-- ============================================================================
CREATE TABLE IF NOT EXISTS pat_med_outhosp_error
(
    id            BIGINT       NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    serialnum     VARCHAR(64)           DEFAULT NULL COMMENT '诊疗记录号(healthcareRecordNo)',
    outhospno     VARCHAR(64)           DEFAULT NULL COMMENT '门诊号',
    patname       VARCHAR(100)          DEFAULT NULL COMMENT '患者姓名',
    patno         VARCHAR(64)           DEFAULT NULL COMMENT '病历号',
    campusid      VARCHAR(32)           DEFAULT NULL COMMENT '院区ID',
    orgid         VARCHAR(32)           DEFAULT NULL COMMENT '机构ID',
    admitdate     DATETIME              DEFAULT NULL COMMENT '就诊时间',
    gather_date   VARCHAR(20)           DEFAULT NULL COMMENT '采集日期 yyyy-MM-dd',
    error_type    VARCHAR(20)  NOT NULL COMMENT '错误类型:CONVERT-数据转换失败 ARCHIVE-患者档案处理失败 INSERT-入库失败',
    error_msg     VARCHAR(1000)         DEFAULT NULL COMMENT '异常信息',
    raw_data      TEXT COMMENT '接口返回的原始记录JSON,人工修复后可据此重推',
    fail_count    INT          NOT NULL DEFAULT 1 COMMENT '累计失败次数',
    handle_status CHAR(1)      NOT NULL DEFAULT '0' COMMENT '处理状态:0待处理 1已处理 2已忽略',
    handle_remark VARCHAR(500)          DEFAULT NULL COMMENT '处理备注',
    create_time   DATETIME              DEFAULT NULL COMMENT '创建时间',
    update_time   DATETIME              DEFAULT NULL COMMENT '更新时间',
    PRIMARY KEY (id),
    UNIQUE KEY uk_serialnum_type (serialnum, error_type),
    KEY idx_handle_status (handle_status),
    KEY idx_error_type (error_type),
    KEY idx_create_time (create_time)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4 COMMENT ='省立同德门急诊采集失败记录(人工处理)';
sql/diag_admin_dept_ward_20261106.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,105 @@
-- =====================================================================================
-- è¯Šæ–­ï¼šadmin ç™»å½•/创建任务时看不到全部科室病区 + belongDepts/belongWards æŸ¥è¯¢æ…¢
-- æ—¥æœŸï¼š2026-11-06   åº“:smartor_nhfy(192.168.99.192:3306)
--
-- ä»£ç é“¾è·¯ï¼ˆå·²ç¡®è®¤ï¼‰ï¼š
--   GET /getInfo            â†’ SysLoginController#getInfo(L243-254)
--                                 â†’ ISysDeptService#selectLeafSysDeptListBySysUserDept
--   GET /system/user/getInfo â†’ SysUserController#getInfo(L151-165),同样两行
--                                 â†’ SysDeptServiceImpl#selectLeafSysDeptListBySysUserDept(L335-341)
--                                     â†’ SysDeptMapper#selectSysDeptListBySysUserDept(SysDeptMapper.xml L286-298)
--                                         â†’ TreeUtils.getLeafDepts(只留叶子)
--   å‰ç«¯ã€Œåˆ›å»ºä»»åŠ¡ã€çš„ç§‘å®¤/病区下拉就是用这两个返回的 belongDepts / belongWards。
--
-- å…³é”®ç‚¹ï¼ˆåŒä¸€æ®µ SQL åŒæ—¶é€ æˆã€Œçœ‹ä¸åˆ°ã€å’Œã€Œæ…¢ã€ï¼‰ï¼š
--   1) åˆ—表来自 sys_dept INNER JOIN sys_user_dept ON dept_code â‡’ åªæœ‰ã€Œè‡³å°‘被某个用户关联过」的
--      ç§‘室/病区才会出现;admin åœ¨ /getInfo é‡ŒåŒæ ·å¸¦ userId,等于只看自己被分配的部门。
--   2) è¯¥ SQL è¿˜å¸¦ `and d.campusid = ç™»å½•用户院区`(L295),sys_dept.campusid ä¸ºç©ºæˆ–与登录院区
--      ä¸ä¸€è‡´çš„部门会被过滤掉。
--   3) ç»“果再过 TreeUtils.getLeafDepts,凡是「被别的部门当成 parent_id」的部门都不返回。
--   4) æ…¢ï¼šadmin åˆ†æ”¯ï¼ˆSysUserServiceImpl#selectLeafSysDeptForAdmin)不带 userId,
--      è¿™æ¡ join å°±å˜æˆã€Œç§‘室 Ã— å…³è”该科室的所有用户」的笛卡尔放大;两个查询串行、每行还要
--      BeanUtils.copyProperties è½¬ä¸€æ¬¡ã€è¿˜è¢«åŒ…在 50 ç§’超时里。
-- =====================================================================================
-- 1. admin åœ¨ sys_user_dept é‡Œåˆ°åº•有多少条关系(少 â†’ ä¸‹æ‹‰å°±å°‘)
SELECT u.user_id, u.user_name, u.orgid, u.campusid, u.searchscope,
       (SELECT COUNT(*) FROM sys_user_dept ud
         WHERE ud.user_id = u.user_id AND ud.del_flag = '0' AND ud.dept_type = '1') AS ks_cnt,
       (SELECT COUNT(*) FROM sys_user_dept ud
         WHERE ud.user_id = u.user_id AND ud.del_flag = '0' AND ud.dept_type = '2') AS bq_cnt
FROM sys_user u
WHERE u.user_name IN ('admin')          -- æ¢æˆå®žé™…登录账号
   OR u.user_id IN (SELECT ud.user_id FROM sys_user_dept ud GROUP BY ud.user_id ORDER BY COUNT(*) DESC LIMIT 5);
-- 2. è¯¥æœºæž„「应该有」多少科室/病区(sys_dept å…¨é‡ï¼‰
SELECT orgid, dept_type, COUNT(*) AS rows_, COUNT(DISTINCT dept_code) AS codes
FROM sys_dept
WHERE del_flag = '0' AND orgid = '20001001'      -- æ¢æˆå®žé™… orgid
GROUP BY orgid, dept_type;
-- 3. ã€Œèƒ½æŸ¥å‡ºæ¥ã€å¤šå°‘:join åŽ + å¸¦ campusid è¿‡æ»¤ï¼Œå¯¹æ¯”第 2 æ­¥ï¼Œå·®å¤šå°‘就是丢了多少
SELECT '整体(不限院区)' AS scope, d.dept_type,
       COUNT(*) AS join_rows, COUNT(DISTINCT d.dept_code) AS dept_codes,
       COUNT(DISTINCT d.dept_id) AS dept_ids
FROM sys_dept d
         INNER JOIN sys_user_dept ud ON d.dept_code = ud.dept_code
WHERE d.del_flag = '0' AND ud.del_flag = '0'
  AND d.orgid = '20001001' AND ud.orgid = '20001001'
GROUP BY d.dept_type
UNION ALL
SELECT CONCAT('限院区 ', u.campusid) AS scope, d.dept_type,
       COUNT(*), COUNT(DISTINCT d.dept_code), COUNT(DISTINCT d.dept_id)
FROM sys_dept d
         INNER JOIN sys_user_dept ud ON d.dept_code = ud.dept_code
         JOIN sys_user u ON u.user_id = ud.user_id
WHERE d.del_flag = '0' AND ud.del_flag = '0'
  AND d.orgid = '20001001' AND ud.orgid = '20001001'
  AND d.campusid = u.campusid
GROUP BY u.campusid, d.dept_type;
-- 4. campusid æ˜¯å¦å¯¹ä¸ä¸Šï¼ˆsys_dept ä¾§ä¸ºç©º / ä¸Žç™»å½•院区不同)
SELECT IFNULL(d.campusid, '(NULL)') AS dept_campusid, d.dept_type, COUNT(*) AS rows_
FROM sys_dept d
WHERE d.del_flag = '0' AND d.orgid = '20001001'
GROUP BY d.campusid, d.dept_type
ORDER BY rows_ DESC;
-- 5. å¶å­è¿‡æ»¤ä¼šä¸¢æŽ‰å¤šå°‘(同一 dept_type å†…,有子部门的部门不算叶子)
SELECT d.dept_type,
       COUNT(*) AS all_rows,
       SUM(CASE WHEN EXISTS (SELECT 1 FROM sys_dept c
                              WHERE c.parent_id = d.dept_id AND c.del_flag = '0') THEN 1 ELSE 0 END) AS not_leaf,
       SUM(CASE WHEN NOT EXISTS (SELECT 1 FROM sys_dept c
                              WHERE c.parent_id = d.dept_id AND c.del_flag = '0') THEN 1 ELSE 0 END) AS leaf
FROM sys_dept d
WHERE d.del_flag = '0' AND d.orgid = '20001001'
GROUP BY d.dept_type;
-- 6. æ…¢çš„量化:这条 join æ¯æ¬¡éƒ½è¿”回多少行(行数就是传输量 + BeanUtils è½¬æ¢æ¬¡æ•°ï¼‰
SELECT COUNT(*) AS join_rows_without_user
FROM sys_dept d
         INNER JOIN sys_user_dept ud ON d.dept_code = ud.dept_code
WHERE d.del_flag = '0' AND ud.del_flag = '0'
  AND d.orgid = '20001001' AND ud.orgid = '20001001'
  AND d.dept_type = '1';
-- æœŸæœ›ï¼šè¿œå¤§äºŽ sys_dept é‡Œç§‘室真实条数。这就是 admin åˆ†æ”¯æ…¢ï¼ˆselectLeafSysDeptForAdmin)的全部原因。
-- 7. ç›¸å…³ç´¢å¼•现状(下面建议的索引缺哪个补哪个)
SELECT table_name, index_name, seq_in_index, column_name
FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name IN ('sys_dept', 'sys_user_dept')
ORDER BY table_name, index_name, seq_in_index;
-- 8. å»ºè®®ç´¢å¼•(先看 7 å†æ‰§è¡Œï¼‰
-- ALTER TABLE sys_user_dept ADD INDEX idx_sud_code_org_type (dept_code, orgid, dept_type, del_flag, user_id);
-- ALTER TABLE sys_user_dept ADD INDEX idx_sud_user_type (user_id, dept_type, orgid, del_flag, dept_code);
-- ALTER TABLE sys_dept      ADD INDEX idx_sd_org_type_campus (orgid, dept_type, campusid, del_flag, dept_code);
-- ALTER TABLE sys_dept      ADD INDEX idx_sd_code_org (dept_code, orgid, del_flag);
-- 9. ä¿®å®ŒåŽè‡ªæ£€ï¼šadmin åº”该能看到该机构全部科室/病区(数量等于第 2 æ­¥çš„ codes)
SELECT d.dept_type, COUNT(DISTINCT d.dept_code) AS visible_codes
FROM sys_dept d
WHERE d.del_flag = '0' AND d.orgid = '20001001'
  AND (d.campusid IS NULL OR d.campusid = '30001002')   -- ä¸Žç™»å½•院区保持一致
GROUP BY d.dept_type;
sql/diag_³öÔºËæ·ÃÉÙÉú³É_Ô­ÒòÅŲé.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,166 @@
-- ============================================================================
-- å‡ºé™¢éšè®¿ã€Œå°‘生成」排查脚本(南华附一 / nhfy)—— **只读**,全部是 SELECT
-- ----------------------------------------------------------------------------
-- ç»“论先行(2026-10-29 åœ¨ nhfy çº¿ä¸Šåº“实测:病区=骨科中心一病区 40011020,出院日期 2026-07-01~09-30):
--   å‡ºé™¢ç™»è®° 475 æ¡  â†’  éšè®¿ä»»åŠ¡å…¶å®ž **475 æ¡ä¸€æ¡æ²¡å°‘**(service_subtask.service_type='2')
--   é¡µé¢åªæ˜¾ç¤º 443 æ¡ï¼ˆä½ æˆªå›¾æ—¶ 444),差额是「页面查不到」,不是「没生成」:
--     â‘  25 æ¡ service_subtask.campusid ä¸º NULL
--        â€”— ServiceSubtaskController#patItem å¼ºåˆ¶ setCampusid(登录用户院区),
--           SQL æ¡ä»¶ `and campusid = #{campusid}`,所以院区为空的任务对任何人都不显示
--     â‘¡  7 æ¡ service_subtask.leavehospitaldistrictcode ä¸æ˜¯æœ¬ç—…区
--        ï¼ˆ4 æ¡ä¸ºç©º / 2 æ¡éª¨ç§‘二病区 / 1 æ¡éª¨ç§‘三病区)→ æŒ‰ç—…区过滤时看不到
--     â‘¢  475 - 25 - 7 = 443
--
-- â‘ çš„æˆå› ï¼ˆå·²æ ¸åˆ°è¡Œï¼‰ï¼š
--   è¿™ 25 æ¡å‡ºé™¢è®°å½• 2026-07-14~07-29 å…¥åº“æ—¶ pat_med_inhosp.campusid ä¸ºç©ºï¼›
--   2026-07-29~07-31 å‡Œæ™¨æ‰«æç”Ÿæˆä»»åŠ¡æ—¶ï¼ŒVisitSubtaskFactory#boxedServiceSubtask é‡Œ
--   `d.setCampusid(inhosp.getCampusid())` ç”¨å‡ºé™¢è®°å½•的院区覆盖了任务自带的院区 â†’ è½åº“ NULL;
--   2026-10-24 å‡Œæ™¨é‡‡é›†æ‰æŠŠ pat_med_inhosp.campusid è¡¥æˆ 30001002,但 service_subtask æ— äººå›žå¡«ã€‚
--
-- ç”¨æ³•:本脚本把参数写成字面量(不用 SET @变量,避免 MySQL8 ç”¨æˆ·å˜é‡çš„ collation å†²çªï¼‰ã€‚
--       è¦æ¢èŒƒå›´ï¼ŒæŠŠä¸‹é¢ 4 ä¸ªå€¼å…¨æ–‡æ›¿æ¢å³å¯ï¼š
--          orgid  = '20001001'
--          campus = '30001002'
--          ward   = '40011020'      -- éª¨ç§‘中心一病区
--          å‡ºé™¢æ—¶é—´ = ['2026-07-01 00:00:00', '2026-10-01 00:00:00')
-- ============================================================================
-- ---------------------------------------------------------------------------
-- ç»“æžœ 0:⚠️ å…ˆç¡®è®¤ä½ é¡µé¢ç”¨çš„æ˜¯å“ªä¸ªå£å¾„
--   å‡ºé™¢ç™»è®°é¡µæ”¯æŒã€Œç§‘室名称 / ç—…区 / æŸ¥æˆ¿é™¢åŒºã€å¤šç§è¿‡æ»¤ï¼ŒåŒä¸€ä¸ªç—…区换个口径就是 469 / 475,
--   æ‹¿ä¸åŒå£å¾„的两个数相减,会凭空多出一笔"差额"。先看这里哪一行等于你页面上的总数。
-- ---------------------------------------------------------------------------
SELECT '0_口径对照_出院记录' AS åˆ†ç»„,
       (SELECT COUNT(*) FROM (SELECT b.inhospid FROM pat_med_inhosp b JOIN pat_archive a ON a.id = b.patid
          WHERE a.del_flag='0' AND b.del_flag='0' AND a.idcardno IS NOT NULL AND b.inhospstate='1'
            AND b.leavehospitaldistrictcode = '40011020'
            AND b.endtime >= '2026-07-01 00:00:00' AND b.endtime < '2026-10-01 00:00:00'
          GROUP BY b.inhospid) x) AS æŒ‰ç¦»é™¢ç—…区,
       (SELECT COUNT(*) FROM (SELECT b.inhospid FROM pat_med_inhosp b JOIN pat_archive a ON a.id = b.patid
          WHERE a.del_flag='0' AND b.del_flag='0' AND a.idcardno IS NOT NULL AND b.inhospstate='1'
            AND b.leaveldeptcode = '40011021'
            AND b.endtime >= '2026-07-01 00:00:00' AND b.endtime < '2026-10-01 00:00:00'
          GROUP BY b.inhospid) x) AS æŒ‰ç¦»é™¢ç§‘室,
       (SELECT COUNT(*) FROM (SELECT b.inhospid FROM pat_med_inhosp b JOIN pat_archive a ON a.id = b.patid
          WHERE a.del_flag='0' AND b.del_flag='0' AND a.idcardno IS NOT NULL AND b.inhospstate='1'
            AND b.hospitaldistrictcode = '40011020'
            AND b.endtime >= '2026-07-01 00:00:00' AND b.endtime < '2026-10-01 00:00:00'
          GROUP BY b.inhospid) x) AS æŒ‰åœ¨é™¢ç—…区,
       (SELECT COUNT(*) FROM (SELECT b.inhospid FROM pat_med_inhosp b JOIN pat_archive a ON a.id = b.patid
          WHERE a.del_flag='0' AND b.del_flag='0' AND a.idcardno IS NOT NULL AND b.inhospstate='1'
            AND b.deptcode = '40011021'
            AND b.endtime >= '2026-07-01 00:00:00' AND b.endtime < '2026-10-01 00:00:00'
          GROUP BY b.inhospid) x) AS æŒ‰åœ¨é™¢ç§‘室,
       (SELECT COUNT(*) FROM service_subtask s WHERE s.del_flag=0 AND s.service_type='2'
          AND s.leavehospitaldistrictcode = '40011020'
          AND s.endtime >= '2026-07-01 00:00:00' AND s.endtime < '2026-10-01 00:00:00') AS ä»»åŠ¡_按离院病区,
       (SELECT COUNT(*) FROM service_subtask s WHERE s.del_flag=0 AND s.service_type='2'
          AND s.leaveldeptcode = '40011021'
          AND s.endtime >= '2026-07-01 00:00:00' AND s.endtime < '2026-10-01 00:00:00') AS ä»»åŠ¡_按离院科室;
-- ---------------------------------------------------------------------------
-- ç»“æžœ 1:到底有没有"没生成"
--   æœŸæœ›ï¼šæ²¡ç”Ÿæˆ = 0。若 > 0,再看结果 4。
-- ---------------------------------------------------------------------------
SELECT '1_总数核对' AS åˆ†ç»„,
       COUNT(*) AS å‡ºé™¢è®°å½•æ•°,
       SUM(CASE WHEN s.inhospid IS NOT NULL THEN 1 ELSE 0 END) AS æœ‰å‡ºé™¢éšè®¿ä»»åŠ¡,
       SUM(CASE WHEN s.inhospid IS NULL THEN 1 ELSE 0 END) AS æ²¡ç”Ÿæˆä»»åŠ¡çš„è®°å½•
FROM (
  SELECT b.inhospid
  FROM pat_med_inhosp b
  JOIN pat_archive a ON a.id = b.patid
  WHERE a.del_flag = '0' AND b.del_flag = '0' AND a.idcardno IS NOT NULL
    AND b.inhospstate = '1' AND b.orgid = '20001001'
    AND b.leavehospitaldistrictcode = '40011020'
    AND b.endtime >= '2026-07-01 00:00:00' AND b.endtime < '2026-10-01 00:00:00'
  GROUP BY b.inhospid
) r
LEFT JOIN (
  SELECT DISTINCT inhospid FROM service_subtask
  WHERE del_flag = 0 AND service_type = '2' AND inhospid IS NOT NULL
) s ON s.inhospid = r.inhospid;
-- ---------------------------------------------------------------------------
-- ç»“æžœ 2:页面可见性 â€”— ç”Ÿæˆäº†ä½†é¡µé¢æŸ¥ä¸åˆ°çš„那部分
--   é¡µé¢å¯è§ = é™¢åŒº + ç—…区都匹配,这才是"患者服务总量"该有的数
-- ---------------------------------------------------------------------------
SELECT '2_页面可见性' AS åˆ†ç»„,
       COUNT(*) AS ä»»åŠ¡æ€»æ•°,
       SUM(CASE WHEN (s.campusid IS NULL OR s.campusid = '') THEN 1 ELSE 0 END) AS é™¢åŒºä¸ºç©º_不可见,
       SUM(CASE WHEN (s.leavehospitaldistrictcode IS NULL OR s.leavehospitaldistrictcode <> '40011020')
                THEN 1 ELSE 0 END) AS ç—…区不匹配_不可见,
       SUM(CASE WHEN s.campusid = '30001002' AND s.leavehospitaldistrictcode = '40011020'
                THEN 1 ELSE 0 END) AS é¡µé¢å¯è§,
       SUM(CASE WHEN s.campusid = '30001002' AND s.leavehospitaldistrictcode = '40011020'
                 AND s.sendstate = 4 THEN 1 ELSE 0 END) AS å¯è§ä¸­æ— éœ€éšè®¿,
       SUM(CASE WHEN s.campusid = '30001002' AND s.leavehospitaldistrictcode = '40011020'
                 AND s.sendstate = 6 THEN 1 ELSE 0 END) AS å¯è§ä¸­å·²å®Œæˆ
FROM (
  SELECT DISTINCT b.inhospid
  FROM pat_med_inhosp b
  JOIN pat_archive a ON a.id = b.patid
  WHERE a.del_flag = '0' AND b.del_flag = '0' AND a.idcardno IS NOT NULL
    AND b.inhospstate = '1' AND b.orgid = '20001001'
    AND b.leavehospitaldistrictcode = '40011020'
    AND b.endtime >= '2026-07-01 00:00:00' AND b.endtime < '2026-10-01 00:00:00'
) r
JOIN service_subtask s ON s.inhospid = r.inhospid AND s.del_flag = 0 AND s.service_type = '2';
-- ---------------------------------------------------------------------------
-- ç»“æžœ 3:不可见明细(院区为空 / ç—…区不匹配),可直接对照修补
-- ---------------------------------------------------------------------------
SELECT '3_不可见明细' AS åˆ†ç»„, s.id AS å­ä»»åŠ¡ID, s.inhospid, s.sendname AS å§“名,
       DATE_FORMAT(s.endtime, '%Y-%m-%d') AS å‡ºé™¢æ—¥æœŸ,
       s.task_name AS éšè®¿ä»»åŠ¡, s.sendstate,
       s.campusid AS ä»»åŠ¡é™¢åŒº, s.leavehospitaldistrictcode AS ä»»åŠ¡ç—…åŒº,
       b.campusid AS è®°å½•院区, b.leavehospitaldistrictcode AS è®°å½•病区,
       DATE_FORMAT(s.create_time, '%Y-%m-%d %H:%i') AS ç”Ÿæˆæ—¶é—´
FROM service_subtask s
JOIN pat_med_inhosp b ON b.inhospid = s.inhospid
WHERE s.del_flag = 0 AND s.service_type = '2'
  AND s.endtime >= '2026-07-01 00:00:00' AND s.endtime < '2026-10-01 00:00:00'
  AND (s.campusid IS NULL OR s.campusid = ''
       OR s.leavehospitaldistrictcode IS NULL OR s.leavehospitaldistrictcode <> '40011020')
  AND b.leavehospitaldistrictcode = '40011020'
ORDER BY s.create_time;
-- ---------------------------------------------------------------------------
-- ç»“æžœ 4:真的没生成的记录 + åŽŸå› ï¼ˆä»…å½“ç»“æžœ 1 çš„"没生成" > 0 æ—¶æ‰éœ€è¦çœ‹ï¼‰
--   fuflag≠1 â†’ æ‰«æå™¨ä¸å¤„理这类记录;
--   deptcheck_flag=2 + long_task_reason â†’ æ‰«æè¿‡ï¼Œä½†ç§‘室/病区/疾病都没匹配到长期任务
-- ---------------------------------------------------------------------------
SELECT '4_真的没生成' AS åˆ†ç»„, b.inhospid, b.patname AS å§“名,
       DATE_FORMAT(b.endtime, '%Y-%m-%d') AS å‡ºé™¢æ—¥æœŸ,
       b.leaveldeptname AS ç§‘室, b.leavediagname AS ä¸»è¯Šæ–­,
       b.fuflag AS éšè®¿æ ‡è¯†, b.deptcheck_flag AS ç§‘室标志, b.wardcheck_flag AS ç—…区标志,
       b.diagcheck_flag AS ç–¾ç—…标志, LEFT(b.long_task_reason, 80) AS æœªç”ŸæˆåŽŸå› 
FROM pat_med_inhosp b
JOIN pat_archive a ON a.id = b.patid
WHERE a.del_flag = '0' AND b.del_flag = '0' AND a.idcardno IS NOT NULL
  AND b.inhospstate = '1' AND b.orgid = '20001001'
  AND b.leavehospitaldistrictcode = '40011020'
  AND b.endtime >= '2026-07-01 00:00:00' AND b.endtime < '2026-10-01 00:00:00'
  AND NOT EXISTS (
        SELECT 1 FROM service_subtask s
        WHERE s.inhospid = b.inhospid AND s.del_flag = 0 AND s.service_type = '2')
ORDER BY b.endtime DESC;
-- ---------------------------------------------------------------------------
-- ç»“æžœ 5(参考):修复用 SQL â€”— **默认注释掉,确认后再单独执行**
--   åªè¡¥"空的"院区/病区,不动业务表、不动已有值
-- ---------------------------------------------------------------------------
-- UPDATE service_subtask s
--   JOIN pat_med_inhosp b ON b.inhospid = s.inhospid
--    SET s.campusid = b.campusid
--  WHERE s.del_flag = 0 AND s.service_type = '2'
--    AND (s.campusid IS NULL OR s.campusid = '')
--    AND b.campusid IS NOT NULL AND b.campusid <> '';
--
-- UPDATE service_subtask s
--   JOIN pat_med_inhosp b ON b.inhospid = s.inhospid
--    SET s.leavehospitaldistrictcode = b.leavehospitaldistrictcode,
--        s.leavehospitaldistrictname = b.leavehospitaldistrictname
--  WHERE s.del_flag = 0 AND s.service_type = '2'
--    AND (s.leavehospitaldistrictcode IS NULL OR s.leavehospitaldistrictcode = '')
--    AND b.leavehospitaldistrictcode IS NOT NULL AND b.leavehospitaldistrictcode <> '';
sql/fix_admin_dept_ward_missing_20261106.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,150 @@
-- =====================================================================================
-- ã€å·²æ”¹ä¸ºä»£ç ä¿®å¤ï¼Œæœ¬æ–‡ä»¶çš„ INSERT ä¸éœ€è¦æ‰§è¡Œã€‘
--   2026-11-06 å†³å®šï¼šç®¡ç†å‘˜å¤©ç„¶æ‹¥æœ‰å…¨éƒ¨ç§‘室权限,直接查 sys_dept,不再往 sys_user_dept è¡¥æ•°æ®ã€‚
--   ä»£ç æ”¹åŠ¨è§ï¼š
--     SysDeptMapper.xml#selectSysDeptListBySysUserDept(userId ä¸ºç©ºæ—¶åªæŸ¥ sys_dept)
--     SysDeptServiceImpl#selectLeafSysDeptListBySysUserDept(userId ä¸ºç©ºæ—¶ä¸åšå¶å­è¿‡æ»¤ï¼‰
--     SysUserServiceImpl#selectLeafSysDeptList / selectLeafSysDeptForAdmin / selectUserByUserName
--     SysLoginController#getInfo(管理员不传 userId、不按院区过滤)
--   æœ¬æ–‡ä»¶ä¿ç•™ä¸‹æ¥åªä½œä¸ºã€Œè¯Šæ–­å£å¾„」参考:第 0 æ­¥å’Œç¬¬ 3 æ­¥çš„æŸ¥è¯¢ä»å¯ç”¨äºŽæ ¸å¯¹
--   ã€ŒæŸä¸ªç”¨æˆ·åˆ°åº•能查到多少科室/病区」,第 1、2 æ­¥çš„写入可以忽略。
-- =====================================================================================
-- =====================================================================================
-- ä¿®å¤ï¼šadmin ç™»å½•后没有科室/病区(belongDepts / belongWards ä¸ºç©ºï¼‰ï¼Œåˆ›å»ºä»»åŠ¡é€‰ä¸åˆ°ç§‘å®¤ç—…åŒº
-- æ—¥æœŸï¼š2026-11-06   åº“:smartor_nhfy
--
-- ç»“论(代码 + æ•°æ®é“¾è·¯å·²æ ¸å¯¹ï¼ŒåŠ ç´¢å¼•ä¸ä¼šæ”¹å˜ç»“æžœï¼Œå› ä¸ºè¿™ä¸æ˜¯æ…¢ï¼Œæ˜¯æŸ¥ä¸åˆ°ï¼‰ï¼š
--   å‰ç«¯ä¸‹æ‹‰ç”¨çš„æ˜¯ /getInfo è¿”回的 belongDepts / belongWards:
--     SysLoginController#getInfo(L243-254)
--       â†’ SysDeptServiceImpl#selectLeafSysDeptListBySysUserDept(L335-341)
--         â†’ SysDeptMapper#selectSysDeptListBySysUserDept(SysDeptMapper.xml L286-298)
--           æ ¸å¿ƒæ˜¯ INNER JOIN sys_user_dept ud ON d.dept_code = ud.dept_code
--           å¹¶ä¸”这次带上了 userId(sysUserDept.setUserId(user.getUserId()))。
--   è€Œ HIS é‡‡é›†å†™å…¥ sys_user_dept æ—¶æ˜Žç¡®æŽ’除了 admin:
--     CollectHISMapper.xml yhyksxx(L494)/ yhybfxx(L513)/ yhyyqxx(L536)都有 `ud.user_id != 'admin'`
--   æ‰€ä»¥ admin åœ¨ sys_user_dept é‡Œæ²¡æœ‰ä»»ä½•行 â†’ ä¸Šé¢è¿™æ¡ join è¿”回 0 è¡Œ â†’ ä¸¤ä¸ªåˆ—表都是空。
--   ï¼ˆç™»å½• token èµ°çš„那条 selectUserByUserName2 é‡Œ admin åˆ†æ”¯ä¸å¸¦ userId,所以那条反而是有数据的,
--     è¿™ä¹Ÿæ˜¯ä¸ºä»€ä¹ˆâ€œç™»å½•成功但还是没有科室病区”——登录返回的和 /getInfo è¦†ç›–的不一致。)
--
--   é¡ºå¸¦ä¸¤ä¸ªå‘:
--     a) sys_dept.campusid ä¸ºç©ºçš„部门会被 `and d.campusid = #{campusid}` è¿‡æ»¤æŽ‰ï¼›
--     b) sys_config é‡Œæ²¡æœ‰ sys.authority.campus.all(已查证为空),所以 admin çš„ campusid ä¸ä¼šè¢«æ¸…空。
-- =====================================================================================
-- -------------------------------------------------------------------------------------
-- ç¬¬ 0 æ­¥ï¼šå…ˆç¡®è®¤ï¼ˆåªè¯»ï¼›è¿™ä¸‰æ¡è·‘完就知道是不是这个原因)
-- -------------------------------------------------------------------------------------
-- 0.1 admin åœ¨ sys_user_dept é‡Œåˆ°åº•有没有数据(预期 0 è¡Œï¼‰
SELECT u.user_id, u.user_name, u.orgid, u.campusid, u.searchscope,
       (SELECT COUNT(*) FROM sys_user_dept ud
         WHERE ud.user_id = u.user_id AND ud.del_flag = '0')                     AS æœ‰æ•ˆå…³ç³»æ•°,
       (SELECT COUNT(*) FROM sys_user_dept ud WHERE ud.user_id = u.user_id)      AS å«å·²åˆ å…³ç³»æ•°
FROM sys_user u
WHERE u.user_name = 'admin';
-- 0.2 å¤åˆ» /getInfo çš„ SQL(带 userId)—— é¢„期 0 è¡Œ
SELECT d.dept_type, COUNT(*) AS rows_, COUNT(DISTINCT d.dept_code) AS codes
FROM sys_dept d
         INNER JOIN sys_user_dept ud ON d.dept_code = ud.dept_code
WHERE d.del_flag = '0' AND ud.del_flag = '0'
  AND d.orgid = '20001001' AND ud.orgid = '20001001'      -- æ¢æˆ admin çš„ orgid
  AND ud.user_id = (SELECT user_id FROM sys_user WHERE user_name = 'admin')
  AND d.campusid = '30001002'                              -- æ¢æˆ admin çš„ campusid
GROUP BY d.dept_type;
-- 0.3 åŒä¸€å¥— SQL åŽ»æŽ‰ userId / campusid ä¹‹åŽèƒ½æŸ¥åˆ°å¤šå°‘(预期几百行 = åº”该看到的量)
SELECT d.dept_type, COUNT(*) AS rows_, COUNT(DISTINCT d.dept_code) AS codes
FROM sys_dept d
WHERE d.del_flag = '0' AND d.orgid = '20001001'
GROUP BY d.dept_type;
-- -------------------------------------------------------------------------------------
-- ç¬¬ 1 æ­¥ï¼ˆæŽ¨èï¼Œç«‹åˆ»ç”Ÿæ•ˆã€ä¸ç”¨å‘版):给 admin è¡¥å…¨ sys_user_dept
--   æŠŠè¯¥æœºæž„下所有科室(dept_type='1')和病区(dept_type='2')都挂到 admin åä¸‹ï¼Œ
--   ä¹‹åŽ /getInfo é‚£æ¡ join å°±æœ‰æ•°æ®äº†ï¼›é…åˆåˆšåŠ çš„ç´¢å¼•ï¼ŒæŸ¥è¯¢ä¹Ÿæ˜¯æ¯«ç§’çº§ã€‚
--   å¹‚等:重复执行不会插重复行(NOT EXISTS æŒ‰ user_id + dept_code + dept_type åˆ¤é‡ï¼‰ã€‚
-- -------------------------------------------------------------------------------------
-- 1.1 å…ˆçœ‹ä¼šæ’多少行(不插,先确认数量合理)
SELECT u.user_name, d.dept_type, COUNT(*) AS will_insert
FROM sys_user u
         JOIN sys_dept d ON d.del_flag = '0'
    AND d.orgid = u.orgid
    AND d.dept_type IN ('1', '2')
WHERE u.user_name = 'admin'
  AND NOT EXISTS (SELECT 1 FROM sys_user_dept x
                  WHERE x.user_id = u.user_id
                    AND x.dept_code = d.dept_code
                    AND x.dept_type = d.dept_type
                    AND IFNULL(x.orgid, '') = IFNULL(d.orgid, ''))
GROUP BY u.user_name, d.dept_type;
-- 1.2 æ­£å¼æ’入(要跨院区看全部就把 campusid ä¸€å¹¶æŒ‰ sys_dept å¸¦è¿‡åŽ»ï¼‰
INSERT INTO sys_user_dept
(user_id, dept_id, dept_code, dept_name, dept_type, orgid, campusid, del_flag, create_time, update_time)
SELECT u.user_id, d.dept_id, d.dept_code, d.dept_name, d.dept_type, d.orgid, d.campusid,
       '0', NOW(), NOW()
FROM sys_user u
         JOIN sys_dept d ON d.del_flag = '0'
    AND d.orgid = u.orgid
    AND d.dept_type IN ('1', '2')
WHERE u.user_name = 'admin'
  AND NOT EXISTS (SELECT 1 FROM sys_user_dept x
                  WHERE x.user_id = u.user_id
                    AND x.dept_code = d.dept_code
                    AND x.dept_type = d.dept_type
                    AND IFNULL(x.orgid, '') = IFNULL(d.orgid, ''));
-- 1.3 å›žæ»š 1.2(如果插多了):按插入时间精确删
-- DELETE FROM sys_user_dept
--  WHERE user_id = (SELECT user_id FROM sys_user WHERE user_name = 'admin')
--    AND create_time >= '2026-11-06 00:00:00'
--    AND update_time = create_time;
-- -------------------------------------------------------------------------------------
-- ç¬¬ 2 æ­¥ï¼ˆå¯é€‰ï¼Œè®©å®ƒçœ‹åˆ°å…¨éƒ¨é™¢åŒºï¼‰ï¼šæŠŠ admin åŠ è¿›ã€Œä¸é™é™¢åŒºã€ç™½åå•
--   SysLoginController#getInfo L227-233 ä¼šè¯»è¿™ä¸ªé…ç½®ï¼Œå‘½ä¸­å°±æŠŠ campusid ç½®ç©ºï¼Œ
--   äºŽæ˜¯ `and d.campusid = #{campusid}` è¿™æ¡è¿‡æ»¤ä¸å†æ‹¼ï¼Œè·¨é™¢åŒºçš„科室病区也能出来。
--   çŽ°åœ¨ sys_config é‡Œæ²¡æœ‰è¿™ä¸ª key(已查证),需要手工加一条。
-- -------------------------------------------------------------------------------------
-- INSERT INTO sys_config (config_name, config_key, config_value, config_type, create_by, create_time, remark)
-- VALUES ('不限院区的账号', 'sys.authority.campus.all', 'admin', 'N', 'admin', NOW(),
--         '多个账号用英文逗号分隔,命中后登录不按院区过滤');
-- æ”¹å®Œè¦æ¸…缓存:重启或调用 /system/config/refreshCache(若前端有该按钮)。
-- -------------------------------------------------------------------------------------
-- ç¬¬ 3 æ­¥ï¼šéªŒè¯ï¼ˆé‡å¯/刷新前端后跑;期望两条都有几百条)
-- -------------------------------------------------------------------------------------
-- 3.1 æ¨¡æ‹Ÿ /getInfo çš„两个查询
SELECT d.dept_type, COUNT(DISTINCT d.dept_code) AS visible_codes
FROM sys_dept d
         INNER JOIN sys_user_dept ud ON d.dept_code = ud.dept_code
WHERE d.del_flag = '0' AND ud.del_flag = '0'
  AND d.orgid = '20001001' AND ud.orgid = '20001001'
  AND ud.user_id = (SELECT user_id FROM sys_user WHERE user_name = 'admin')
  AND d.campusid = '30001002'
GROUP BY d.dept_type;
-- 3.2 ç›´æŽ¥çœ‹ç§‘室/病区名字前 10 æ¡ï¼Œç¡®è®¤æ˜¯çœŸå®žç§‘室而不是测试数据
SELECT d.dept_type, d.dept_code, d.dept_name
FROM sys_dept d
         INNER JOIN sys_user_dept ud ON d.dept_code = ud.dept_code
WHERE d.del_flag = '0' AND ud.del_flag = '0'
  AND d.orgid = '20001001' AND ud.orgid = '20001001'
  AND ud.user_id = (SELECT user_id FROM sys_user WHERE user_name = 'admin')
  AND d.campusid = '30001002'
  AND d.dept_type = '1'
LIMIT 10;
-- -------------------------------------------------------------------------------------
-- ç¬¬ 4 æ­¥ï¼ˆæ ¹æ²»ï¼Œå‘版时做):代码里 admin ä¸è¯¥èµ° sys_user_dept
--   4.1 SysDeptMapper.xml L286 æŒ‰ userId åˆ†æ”¯ï¼šæ—  userId æ—¶ç›´æŽ¥æŸ¥ sys_dept(见对话里的 diff)
--   4.2 /getInfo ä¸Ž /system/user/getInfo é‡Œåˆ¤ admin,不传 userId
--   4.3 campusid æ¡ä»¶æ”¹æˆ and (d.campusid is null or d.campusid = #{campusid})
--   4.4 åˆ—表为空时兜底:再按 orgid æŸ¥ä¸€æ¬¡å…¨é‡ï¼Œé¿å…ä»¥åŽå†å‡ºçŽ°ã€Œç™»å½•æˆåŠŸä½†ä¸‹æ‹‰ç©ºã€
-- -------------------------------------------------------------------------------------
sql/fix_nhfy_patitem_slow_20261106.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,302 @@
-- =====================================================================================
-- å—华线上(smartor_nhfy @ 192.168.99.192)随访列表 /smartor/serviceSubtask/patItem æ…¢æŸ¥è¯¢ä¿®å¤
-- ç¼–写日期:2026-11-06    å®žæµ‹çŽ¯å¢ƒï¼šMySQL 8.0.22 Win64,库 2277MB,service_subtask 61,645 è¡Œ / 71.8MB
--
-- ä¸€ã€å®žæµ‹ç»“论(下面第 0 æ­¥çš„ SQL å¯åŽŸæ ·é‡è·‘å¤çŽ°ï¼‰
--   patItem ä¸€æ¬¡è¯·æ±‚实际跑的是 3 ç±» SQL + æ¯è¡Œ 2 æ¬¡å•条查询:
--     â‘  select id ... order by endtime desc limit 10      â† æ…¢çš„就在这,2.4~3.4 s
--        æ‰§è¡Œè®¡åˆ’:index_merge(sort_union(idx_service_subtask_district_visit_time,
--                  idx_service_subtask_dept_visit_time)) + Using filesort
--        EXPLAIN ANALYZE:实际 2915 ms,只为返回 10 è¡Œ
--     â‘¡ select <100 åˆ—> where id in (10 ä¸ª id)            â† 27 ms,正常
--     â‘¢ countByCondition(Redis æœªå‘½ä¸­æ—¶ï¼‰                 â† 2.5~3.0 s
--     â‘£ å¾ªçŽ¯é‡Œæ¯è¡Œ 2 æ¬¡å•条查询(pat_med_inhosp / pat_archive æŒ‰ä¸»é”®ï¼‰â† å„约 25 ms,10 è¡Œâ‰ˆ500 ms
--   åˆè®¡çº¦ 6~7 s,与「查询很慢」一致。
--
--   æ›´åº•层的原因(按影响排序):
--   (1) ã€æœ€è‡´å‘½ã€‘MySQL å®žä¾‹ innodb_buffer_pool_size = 8 MB(默认值,512 é¡µï¼Œpages_free=0),
--       è€Œå…¨åº“ 2277 MB。等于每次查询都在冷盘上读:
--         Â· select count(*) from service_subtask(6 ä¸‡è¡Œï¼‰è¦ 9.3~12.5 s  â†’ æœ‰æ•ˆè¯»é€Ÿçº¦ 7 MB/s
--         Â· performance_schema é‡Œ 100 åˆ—的列表查询 avg 653 ms(29.9 ä¸‡æ¬¡ï¼Œç´¯è®¡ 19.5 ä¸‡ç§’)
--   (2) service_subtask ä¸Šæ²¡æœ‰ã€Œæœºæž„+院区+类型+排序」的联合索引,deptOrDistrict=2 æ—¶
--       (leavehospitaldistrictcode IN .. OR leaveldeptcode IN ..) åªèƒ½èµ° index_merge + filesort,
--       è¦æ‰« 1200~54000 è¡Œå¹¶ä¸”大量回表。
--   (3) åº“里有 25 å¼  copy/bak åžƒåœ¾è¡¨ï¼Œåˆè®¡çº¦ 1.6 GB(占全库 70%),挤占磁盘与缓存。
--   (4) pat_archive æ²¡æœ‰ patientno ç´¢å¼•:`where ?=? and del_flag=? and patientno=?` æ¯æ¬¡å…¨è¡¨æ‰«
--       5.6 ä¸‡è¡Œï¼Œ37.4 ä¸‡æ¬¡è°ƒç”¨ã€ç´¯è®¡æ‰«æ 184 äº¿è¡Œã€avg 457 ms。
--
--   å•独做的效果(都在副本表 service_subtask_copy6 ä¸Šå®žæµ‹è¿‡ï¼Œæ•°æ®é‡åŒé‡çº§ï¼‰ï¼š
--     Â· åªå»ºç´¢å¼•、SQL ä¸æ”¹    â†’ ä¼˜åŒ–器仍选 index_merge,2.8~3.5 s(没用)
--     Â· åªæ”¹ SQL(UNION)、不建索引 â†’ 1.5~1.7 s(有用但不够)
--     Â· ç´¢å¼• + æ”¹å†™ä¸€èµ·ä¸Š      â†’ 26~27 ms(约 110 å€ï¼‰
--     Â· æŠŠ OR åŽŸæ · FORCE INDEX åˆ°å•臂索引 â†’ 28~33 s(千万别这么干)
--
-- äºŒã€ä¿®å¤æ­¥éª¤ï¼ˆæŒ‰æ€§ä»·æ¯”,1 å’Œ 2 åšå®Œè¿™æ¡ SQL å°±ä»Ž 3.1 s æŽ‰åˆ° 27 ms)
--   ç¬¬ 1 æ­¥ï¼ˆä¸æ”¹ä»£ç ï¼Œæ”¶ç›Šæœ€å¤§ï¼‰ï¼šè°ƒå¤§ innodb_buffer_pool_size(8MB â†’ 4G)
--   ç¬¬ 2 æ­¥ï¼ˆä¸åŠ ç´¢å¼•ä¹Ÿèƒ½è·‘ï¼‰ï¼šåŠ ä¸¤ä¸ªã€Œåˆ†æ”¯ã€è¦†ç›–ç´¢å¼•
--   ç¬¬ 3 æ­¥ï¼ˆæ”¹ SQL / æ”¹ mapper):把 OR æ”¹å†™æˆä¸¤åˆ†æ”¯ UNION(已实测 3.1 s â†’ 26 ms,结果逐条一致)
--   ç¬¬ 4 æ­¥ï¼šç»Ÿè®¡ count æ”¹å†™ã€pat_archive.patientno ç´¢å¼•、清垃圾表
-- =====================================================================================
-- -------------------------------------------------------------------------------------
-- 0. çŽ°çŠ¶è¯Šæ–­ï¼ˆåªè¯»ï¼Œå¯éšæ—¶é‡è·‘ï¼›è¿™é‡Œçš„ EXPLAIN ANALYZE ä¼šçœŸè·‘一次,耗时 3 s å·¦å³ï¼‰
-- -------------------------------------------------------------------------------------
-- 0.1 ç¼“冲池 / ç£ç›˜ç›¸å…³å‚数(关键:buf_mb = 8)
SELECT @@version                                AS version,
       @@version_compile_os                    AS os,
       @@innodb_buffer_pool_size / 1024 / 1024 AS buf_mb,
       @@innodb_buffer_pool_instances          AS bp_instances,
       @@innodb_io_capacity                    AS io_capacity,
       @@innodb_log_file_size / 1024 / 1024    AS log_mb;
-- 0.2 ç¼“冲池是不是全满(pages_free = 0 è¡¨ç¤ºä¸€ç‚¹ä½™é‡éƒ½æ²¡æœ‰ï¼Œè¯»å…¨èµ°ç£ç›˜ï¼‰
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages%';
-- 0.3 å†·è¯»æœ‰å¤šæ…¢ï¼š6 ä¸‡è¡Œçš„ count è¦ 10 ç§’就说明一切
SELECT COUNT(*) FROM service_subtask;
-- 0.4 ä½ è¿™æ¡è¯·æ±‚的实际执行计划(把 orgid/campusid/科室病区换成真实值)
EXPLAIN ANALYZE
SELECT id
FROM service_subtask
WHERE del_flag = '0'
  AND orgid = '20001001' AND campusid = '30001002'
  AND service_type = '2' AND visit_count = 1
  AND (leavehospitaldistrictcode IN ('40003309') OR leaveldeptcode IN ('40003307'))
ORDER BY endtime DESC
LIMIT 10 OFFSET 0;
-- æœŸæœ›çœ‹åˆ°çš„问题:index_merge(...) + Using filesort,actual time â‰ˆ 2900 ms
-- 0.5 çº¿ä¸Šæœ€è¿‘到底哪些语句最耗时(performance_schema)
SELECT SUBSTRING(DIGEST_TEXT, 1, 120)  AS sql_,
       COUNT_STAR                      AS cnt,
       ROUND(AVG_TIMER_WAIT / 1e9, 1)  AS avg_ms,
       ROUND(MAX_TIMER_WAIT / 1e9, 0)  AS max_ms,
       SUM_ROWS_EXAMINED               AS rows_examined
FROM performance_schema.events_statements_summary_by_digest
WHERE SCHEMA_NAME = 'smartor_nhfy'
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 15;
-- -------------------------------------------------------------------------------------
-- 1. ç´¢å¼•:给 OR çš„两个分支各配一个「到 endtime æœ‰åºã€ä¸”能覆盖查询」的索引
--
--    ä¸ºä»€ä¹ˆä¸æ˜¯æ™®é€šè”合索引?
--      å®žæµ‹åŠ  (orgid, campusid, del_flag, service_type, visit_count, endtime) åŽï¼Œ
--      ä¼˜åŒ–器依旧选 index_merge + filesort,耗时 2.8~3.5 s,等于白加。
--    ä¸ºä»€ä¹ˆè¦å¸¦ leavehospitaldistrictcode / leaveldeptcode åœ¨ endtime å‰é¢ï¼Ÿ
--      è¿™æ ·åˆ†æ”¯æŸ¥è¯¢æ˜¯ã€Œç­‰å€¼ + ç­‰å€¼ + ... + æŒ‰ endtime æœ‰åºã€ï¼Œå¯ä»¥åå‘扫索引取前 10 è¡Œï¼Œ
--      select åˆ—表只有 id,InnoDB äºŒçº§ç´¢å¼•自带主键 â†’ Using index å®Œå…¨è¦†ç›–,零回表。
--      å®žæµ‹å•分支 27 ms。
-- -------------------------------------------------------------------------------------
ALTER TABLE service_subtask
    ADD INDEX idx_patitem_ward (orgid, campusid, del_flag, service_type, visit_count, leavehospitaldistrictcode, endtime),
    ALGORITHM = INPLACE, LOCK = NONE;
ALTER TABLE service_subtask
    ADD INDEX idx_patitem_dept (orgid, campusid, del_flag, service_type, visit_count, leaveldeptcode, endtime),
    ALGORITHM = INPLACE, LOCK = NONE;
ANALYZE TABLE service_subtask;
-- å®žæµ‹å»ºç´¢å¼•各约 3.8 s(1 ä¸‡è¡Œ/秒级别,因为盘慢),业务无感。
-- è¯´æ˜Žï¼šç´¢å¼•里 visit_count åœ¨ endtime ä¹‹å‰ï¼Œæ˜¯å› ä¸ºåˆ—表请求都会带「随访次数」;
--       å¦‚果某些场景不带 visit_count,这个索引只能用到 service_type å‰ç¼€ï¼ŒæŽ’序仍会 filesort,
--       é‚£ç§åœºæ™¯å†å•独加:
-- ALTER TABLE service_subtask
--     ADD INDEX idx_patitem_ward_novc (orgid, campusid, del_flag, service_type, leavehospitaldistrictcode, endtime),
--     ADD INDEX idx_patitem_dept_novc (orgid, campusid, del_flag, service_type, leaveldeptcode, endtime);
-- -------------------------------------------------------------------------------------
-- 2. åˆ—表 SQL æ”¹å†™ï¼šOR æ‹†æˆä¸¤ä¸ªåˆ†æ”¯ UNION(去重)
--
--    å®žæµ‹ï¼ˆservice_subtask_copy6,同样数据量):
--      åŽŸ OR å†™æ³•                    ï¼š3102 / 3266 / 2984 ms
--      æ”¹å†™åŽ offset 0  / 100 / 500  ï¼š26 / 27 / 27 ms
--      ä¸‰æ¡£ç»“果与原写法逐条一致(id é¡ºåºå®Œå…¨ç›¸åŒï¼‰
--    æ³¨æ„ï¼š
--      Â· åˆ†æ”¯å†…必须各自带 limit (offset + pageSize),外层再 limit pageSize offset offset;
--      Â· ç”¨ UNION(去重),不要用 UNION ALL + â€œæŽ’除重叠”那种写法,
--        åŽè€…会破坏覆盖索引,实测退化到 987 ms~1.1 s;
--      Â· ä¸¤ä¸ªåˆ†æ”¯é‡Œçš„公共 where å¿…须与原来的 serviceSubtaskWhere å®Œå…¨ä¸€è‡´ï¼ˆé€å­—相同),
--        å¦åˆ™ç¬¬ 2 é˜¶æ®µæŒ‰ id å›žè¡¨æ—¶ä¼šå‡ºçŽ°è¡Œæ•°å¯¹ä¸ä¸Šã€‚
-- -------------------------------------------------------------------------------------
-- 2.1 åªå–当页 id(替换 mapper é‡Œçš„ selectServiceSubtaskIdsPage,仅在 deptOrDistrict = '2' æ—¶èµ°è¿™æ¡ï¼‰
SELECT id
FROM (
         (SELECT id, endtime
          FROM service_subtask
          WHERE del_flag = '0'
            AND orgid = '20001001' AND campusid = '30001002'
            AND service_type = '2' AND visit_count = 1
            AND leavehospitaldistrictcode IN ('40003309')
          ORDER BY endtime DESC
          LIMIT 10 OFFSET 0)
         UNION
         (SELECT id, endtime
          FROM service_subtask
          WHERE del_flag = '0'
            AND orgid = '20001001' AND campusid = '30001002'
            AND service_type = '2' AND visit_count = 1
            AND leaveldeptcode IN ('40003307')
          ORDER BY endtime DESC
          LIMIT 10 OFFSET 0)
     ) t
ORDER BY endtime DESC
LIMIT 10 OFFSET 0;
-- æ·±ç¿»é¡µæ—¶ï¼šåˆ†æ”¯å†… LIMIT (pageNum + pageSize) OFFSET 0,外层 LIMIT pageSize OFFSET pageNum。
-- å®žæµ‹ offset 100 / 500 éƒ½æ˜¯ 27 ms ä¸”结果正确。
-- 2.2 å¯¹ç…§ï¼šåŒæ ·çš„æ¡ä»¶ã€åŽŸæ¥çš„ OR å†™æ³•(用来对比耗时 / æ ¡éªŒç»“果)
SELECT id
FROM service_subtask
WHERE del_flag = '0'
  AND orgid = '20001001' AND campusid = '30001002'
  AND service_type = '2' AND visit_count = 1
  AND (leavehospitaldistrictcode IN ('40003309') OR leaveldeptcode IN ('40003307'))
ORDER BY endtime DESC
LIMIT 10 OFFSET 0;
-- 2.3 ç»“果一致性校验(两边 id é›†åˆå¿…须完全相同)
SELECT (SELECT GROUP_CONCAT(id ORDER BY id) FROM (SELECT id FROM service_subtask
         WHERE del_flag='0' AND orgid='20001001' AND campusid='30001002'
           AND service_type='2' AND visit_count=1
           AND (leavehospitaldistrictcode IN ('40003309') OR leaveldeptcode IN ('40003307'))
         ORDER BY endtime DESC LIMIT 10) a) AS old_ids,
       (SELECT GROUP_CONCAT(id ORDER BY id) FROM (
         SELECT id FROM (
             (SELECT id, endtime FROM service_subtask
              WHERE del_flag='0' AND orgid='20001001' AND campusid='30001002'
                AND service_type='2' AND visit_count=1
                AND leavehospitaldistrictcode IN ('40003309')
              ORDER BY endtime DESC LIMIT 10)
             UNION
             (SELECT id, endtime FROM service_subtask
              WHERE del_flag='0' AND orgid='20001001' AND campusid='30001002'
                AND service_type='2' AND visit_count=1
                AND leaveldeptcode IN ('40003307')
              ORDER BY endtime DESC LIMIT 10)
         ) t ORDER BY endtime DESC LIMIT 10) b) AS new_ids;
-- 2.4 æŽ’序字段说明:sort=0/1 æ˜¯ endtime(默认),sort=2/3/7/8 æ˜¯ visit_time。
--     ä¸Šé¢ä¸¤ä¸ªç´¢å¼•是按 endtime æŽ’的;如果常用「随访时间」排序,需要再补一对把 endtime æ¢æˆ visit_time:
-- ALTER TABLE service_subtask
--     ADD INDEX idx_patitem_ward_vt (orgid, campusid, del_flag, service_type, visit_count, leavehospitaldistrictcode, visit_time),
--     ADD INDEX idx_patitem_dept_vt (orgid, campusid, del_flag, service_type, visit_count, leaveldeptcode, visit_time);
-- -------------------------------------------------------------------------------------
-- 3. é¡¶éƒ¨ç»Ÿè®¡ï¼ˆcountByCondition)也要跟着改:拆两个分支再汇总
--    çŽ°çŠ¶ï¼šRedis æœªå‘½ä¸­æ—¶ 2.5~3.0 s(每次全量聚合这套 where)
--    æ”¹æ³•:两个分支各自聚合,外层把结果相加。分支里列出的列都在索引里 â†’ ä¸å›žè¡¨ã€‚
-- -------------------------------------------------------------------------------------
SELECT SUM(wzx) AS wzx, SUM(ywc) AS ywc, SUM(total) AS total
FROM (
         (SELECT SUM(CASE WHEN sendstate = 4 THEN 1 ELSE 0 END) AS wzx,
                 SUM(CASE WHEN sendstate = 6 THEN 1 ELSE 0 END) AS ywc,
                 COUNT(1)                                       AS total
          FROM service_subtask
          WHERE del_flag = '0'
            AND orgid = '20001001' AND campusid = '30001002'
            AND service_type = '2' AND visit_count = 1
            AND leavehospitaldistrictcode IN ('40003309'))
         UNION ALL
         (SELECT SUM(CASE WHEN sendstate = 4 THEN 1 ELSE 0 END),
                 SUM(CASE WHEN sendstate = 6 THEN 1 ELSE 0 END),
                 COUNT(1)
          FROM service_subtask
          WHERE del_flag = '0'
            AND orgid = '20001001' AND campusid = '30001002'
            AND service_type = '2' AND visit_count = 1
            AND leaveldeptcode IN ('40003307'))
     ) t;
-- æ³¨æ„ï¼šä¸¤ä¸ªåˆ†æ”¯ä¼šé‡å¤ç»Ÿè®¡ã€Œæ—¢å±žäºŽè¯¥ç—…区又属于该科室」的行(同一行两个条件都命中)。
-- åŽŸæœ‰ countByCondition æ˜¯å¯¹ OR åŽçš„结果集统计,不重复。
-- çº¿ä¸Šå®žæµ‹è¿™ä¸¤ä¸ªç§‘室/病区是一一对应的(leaveldeptcode=40003307 â‡” leavehospitaldistrictcode=40003309),
-- æ‰€ä»¥è¿™é‡Œç”¨ UNION ALL ä¹Ÿä¸ä¼šé‡å¤ï¼›å¦‚果存在跨科共用病区的情况,改成
-- â€œåˆ†æ”¯2 åŠ ä¸Š AND (leavehospitaldistrictcode IS NULL OR leavehospitaldistrictcode NOT IN (...))”,
-- æˆ–者干脆保留原来的 OR å†™æ³•(统计有 Redis ç¼“å­˜ 60~120 åˆ†é’Ÿï¼Œæ”¹å®Œç¬¬ 1 æ­¥å‚数后也就几十 ms)。
-- -------------------------------------------------------------------------------------
-- 4. é¡ºå¸¦ä¿®ï¼ˆå’Œ patItem æ…¢ç›¸å…³ï¼Œä½†ä¸æ˜¯åŒä¸€æ¡ SQL)
-- -------------------------------------------------------------------------------------
-- 4.1 pat_archive æ²¡æœ‰ patientno ç´¢å¼•:`where ?=? and del_flag=? and patientno=?` æ¯è·‘一次全表扫 5.6 ä¸‡è¡Œ
--     ï¼ˆperformance_schema:37.4 ä¸‡æ¬¡ã€ç´¯è®¡æ‰«æ 184 äº¿è¡Œã€avg 457 ms)
ALTER TABLE pat_archive
    ADD INDEX idx_pat_archive_patientno (patientno, del_flag),
    ALGORITHM = INPLACE, LOCK = NONE;
--     å¦å¤–那条 SQL é‡Œæœ‰ä¸ª `? = ?`(常量对常量)的条件,看着像拼参数时少了个列名,建议代码侧一起看一下。
-- 4.2 patItem çš„回填是「每行 2 æ¬¡å•条查询」(pat_med_inhosp / pat_archive å„一次,按主键)。
--     10 è¡Œå°±æ˜¯ 20 æ¬¡éšæœº I/O(冷盘一次约 25 ms)。建议改成 IN æ‰¹é‡æŸ¥ä¸¤æ¬¡ã€‚
--     è¿™æ¡æ˜¯ä»£ç æ”¹åŠ¨ï¼Œä¸æ˜¯ SQL;当前每页 10 è¡Œå½±å“çº¦ 0.3~0.5 s。
-- 4.3 åº“里有 25 å¼  copy/bak åžƒåœ¾è¡¨ï¼Œåˆè®¡çº¦ 1.6 GB(占全库 70%),对 8 MB çš„缓冲池是纯负担:
--     pat_med_inhosp_copy1..copy5(约 1.24 GB)、service_subtask_copy1..copy6(约 0.30 GB)等。
--     ç¡®è®¤æ— ç”¨åŽå…ˆæ”¹åå†åˆ ï¼Œé¿å…è¯¯åˆ ï¼ˆç¤ºä¾‹ï¼Œé€å¼ ç¡®è®¤åŽå†æ‰§è¡Œï¼‰ï¼š
--     RENAME TABLE pat_med_inhosp_copy4 TO zzz_del_pmi_copy4_20261106;
--     DROP TABLE zzz_del_pmi_copy4_20261106;
-- -------------------------------------------------------------------------------------
-- 5. æœåŠ¡å™¨å‚æ•°ï¼ˆæ”¶ç›Šæœ€å¤§çš„ä¸€æ­¥ï¼Œéœ€è¦æ”¹ my.ini åŽé‡å¯ MySQL;也可先 SET GLOBAL åœ¨çº¿æ”¾å¤§ç¼“冲池)
--    çŽ°çŠ¶ï¼šinnodb_buffer_pool_size = 8 MB(默认值),全库 2277 MB â†’ æ¯æ¬¡æŸ¥è¯¢éƒ½åœ¨å†·ç›˜ä¸Šè·‘
--    ç›®æ ‡ï¼š
--      innodb_buffer_pool_size      = 4G      ï¼ˆæŒ‰ç‰©ç†å†…å­˜ 50%~70% ç»™ï¼›è‡³å°‘ 2G,能装下热表)
--      innodb_buffer_pool_instances = 4       ï¼ˆç¼“冲池 â‰¥ 1G æ—¶æ‰æœ‰æ„ä¹‰ï¼‰
--      innodb_io_capacity           = 2000
--      innodb_io_capacity_max       = 4000
--      innodb_flush_method          = unbuffered / O_DIRECT(Windows ç”¨ unbuffered)
--      table_open_cache             = 4000
--      innodb_log_file_size         = 512M
--    åœ¨çº¿éªŒè¯ï¼ˆä¸ç”¨é‡å¯ï¼ŒMySQL 5.7+ æ”¯æŒåŠ¨æ€è°ƒæ•´ï¼›ç¡®è®¤æœºå™¨å†…å­˜å¤Ÿå†æ‰§è¡Œï¼‰ï¼š
--      SET GLOBAL innodb_buffer_pool_size = 2 * 1024 * 1024 * 1024;
--      ç„¶åŽé‡è·‘ 0.3 çš„ count(*):应从 ~10 s æŽ‰åˆ°å‡ å~一两百 ms。
--    è¿™ä¸€æ­¥åšå®Œï¼Œç¬¬ 2、3 æ­¥çš„æ”¶ç›Šä¼šè¿›ä¸€æ­¥æ”¾å¤§ï¼ˆç´¢å¼•页全在内存,27 ms è¿˜èƒ½å†é™ï¼‰ã€‚
-- -------------------------------------------------------------------------------------
-- 6. éªŒè¯ï¼ˆæ”¹å®Œé€æ¡è·‘)
-- -------------------------------------------------------------------------------------
-- 6.1 å•分支是否走覆盖索引(期望 key = idx_patitem_ward/dept,Extra å« Using index,
--     æ²¡æœ‰ Using filesort,也没有 Using where ä¹‹å¤–的回表)
EXPLAIN
SELECT id
FROM service_subtask
WHERE del_flag = '0'
  AND orgid = '20001001' AND campusid = '30001002'
  AND service_type = '2' AND visit_count = 1
  AND leavehospitaldistrictcode IN ('40003309')
ORDER BY endtime DESC
LIMIT 10;
-- 6.2 æ”¹å†™åŽæ•´ä½“耗时(期望 30 ms é‡çº§ï¼Œè€Œä¸æ˜¯ 3000 ms)
EXPLAIN ANALYZE
SELECT id FROM (
    (SELECT id, endtime FROM service_subtask
     WHERE del_flag='0' AND orgid='20001001' AND campusid='30001002'
       AND service_type='2' AND visit_count=1 AND leavehospitaldistrictcode IN ('40003309')
     ORDER BY endtime DESC LIMIT 10)
    UNION
    (SELECT id, endtime FROM service_subtask
     WHERE del_flag='0' AND orgid='20001001' AND campusid='30001002'
       AND service_type='2' AND visit_count=1 AND leaveldeptcode IN ('40003307')
     ORDER BY endtime DESC LIMIT 10)
) t ORDER BY endtime DESC LIMIT 10;
-- 6.3 æ”¶å°¾
ANALYZE TABLE service_subtask;
ANALYZE TABLE pat_archive;
-- -------------------------------------------------------------------------------------
-- 7. å›žæ»š
-- -------------------------------------------------------------------------------------
-- ALTER TABLE service_subtask DROP INDEX idx_patitem_ward;
-- ALTER TABLE service_subtask DROP INDEX idx_patitem_dept;
-- ALTER TABLE pat_archive   DROP INDEX idx_pat_archive_patientno;
-- ï¼ˆSQL / ä»£ç æ”¹å†™å›žæ»šç›´æŽ¥è¿˜åŽŸ mapper å³å¯ï¼›å‚数回滚把 innodb_buffer_pool_size è°ƒå›žåŽŸå€¼ï¼‰
sql/fix_nhfy_satisfaction_statistics_20261021.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,141 @@
-- ============================================================================
-- å—华附一(nhfy)满意度统计 /smartor/satisfaction/statistics æŸ¥ä¸å‡ºæ•°æ® â€”— å®šä½ä¸Žä¿®å¤
--                                                        2026-10-21
--
-- ã€è¯·æ±‚】POST /prod-api/smartor/satisfaction/statistics
--   { "type":2, "startTime":"2026-07-23", "endTime":"2026-10-21",
--     "deptcodes":["40003511"], "hospitaldistrictcodes":["40003514"],
--     "scriptids":[], "templateid":null }
--   ç»“果:页面一行数据都没有。
--
-- ã€æ ¹å› ã€‘三个问题叠加,任何一个都会让接口返回空:
--
--   â‘  ç§‘室/病区被写成 AND(代码 Bug)
--      SatisfactionMapper.xml#selectSatisfactionSubtaskTypes
--      åŽŸæ¥æ˜¯ã€Œleavehospitaldistrictcode IN(...) AND deptcode IN(...)」两条独立条件。
--      ä½†æ»¡æ„åº¦å­ä»»åŠ¡ï¼ˆtype=2 + service_type 6/14/15/16)在南华**只写 deptcode**,
--      leavehospitaldistrictcode å…¨ä¸º NULL(实测 3803 æ¡å…¨éƒ¨ NULL)→ ä¸¤ä¸ªæ¡ä»¶æ°¸è¿œä¸å¯èƒ½åŒæ—¶æˆç«‹
--      â†’ types æŸ¥ç©º â†’ hasVoice / hasQuestionnaire éƒ½ false â†’ æ•´ä¸ªæŽ¥å£ç›´æŽ¥è¿”回空。
--      åŒä¸€ä¸ªé¡µé¢çš„另外 3 æ¡ SQL(sendCount / FillPersonCountByQuestion / FillStatByMatchedtext)
--      ç”¨çš„都是「科室 OR ç—…区」,只有这一条写成了 AND,属于重构时漏改。
--
--   â‘¡ æ—¶é—´è¿‡æ»¤åˆ— visit_time å…¨ä¸º NULL(数据问题)
--      ç»Ÿè®¡çš„ 4 æ¡ SQL éƒ½ç”¨ `visit_time >= startTime AND visit_time < endTime+1d`,
--      è€Œå—华的满意度子任务 visit_time **3803 æ¡å…¨éƒ¨ä¸º NULL**(今天的也在写 NULL)
--      â†’ å³ä½¿ â‘  ä¿®å¥½ï¼Œæ—¥æœŸæ¡ä»¶ä»ç„¶æŠŠæ•°æ®å…¨éƒ¨æŽ’除。
--
--   â‘¢ é—®å·é¢˜ç›®æ²¡æœ‰æŒ‚到「满意度(myd)」分类(配置问题)
--      ç»Ÿè®¡é¡µçš„题目清单来自 svy_lib_script_category ä¸­ type å« myd çš„分类 â†’
--      svy_lib_template_script.categoryid IN (这些分类)。
--      å—华 myd åˆ†ç±»åªæœ‰ 1 ä¸ªï¼ˆid=62,name=满意度),里面是 6 é“老题(svyid 51/97/102/103/154/156,
--      å±žäºŽå…¶å®ƒé—®å·æ¨¡æ¿ï¼‰ï¼Œè€ŒçœŸæ­£åœ¨å¡«çš„ 19 é“题(svyid=200 / æ¨¡æ¿ 514)**categoryid å…¨æ˜¯ NULL**
--      â†’ å³ä½¿ â‘  â‘¡ ä¿®å¥½ï¼Œé¡µé¢ä¹Ÿåªä¼šåˆ—出那 6 é“没有数据的老题。
--
-- ã€å·²åšçš„修复】
--   ä»£ç ï¼ˆéœ€å‘版):
--     SatisfactionMapper.xml
--       Â· selectSatisfactionSubtaskTypes:科室/病区改为 OR,与其余三条统计 SQL å£å¾„一致
--       Â· 4 æ¡ç»Ÿè®¡ SQL çš„æ—¶é—´æ¡ä»¶æ”¹ä¸º
--         IFNULL(visit_time, IFNULL(finishtime, create_time))(visit_time ä¸ºç©ºæ—¶æŒ‰å®Œæˆ/创建时间兜底)
--   æ•°æ®ï¼ˆå·²æ‰§è¡Œï¼Œæœ¬è„šæœ¬è®°å½•ï¼‰ï¼š
--     Â· æŠŠæ¨¡æ¿ 200 çš„ 19 é“题挂到满意度分类 id=62(19 è¡Œï¼‰
--       å¤‡ä»½è¡¨ï¼šbak_svy_lts_cat_20261021
--
-- ã€ä¿®å¤åŽå®žæµ‹ï¼ˆ2026-10-21,条件同截图)】
--   types = ['2'];发送人数 387;19 é“题逐题答题人数 380(答题率 98.2%)
--
-- ã€å›žæ»šã€‘
--   UPDATE svy_lib_template_script t
--     JOIN bak_svy_lts_cat_20261021 b ON b.id = t.id
--   SET t.categoryid = b.categoryid;
-- ============================================================================
-- ----------------------------------------------------------------------------
-- ã€0】故障复现(修复前这三条要能说明问题)
-- ----------------------------------------------------------------------------
-- â‘  ç±»åž‹æŽ¢æµ‹ï¼šä¿®å¤å‰è¿”回 0 è¡Œ
SELECT DISTINCT type
FROM service_subtask
WHERE del_flag = 0
  AND leavehospitaldistrictcode IN ('40003514')
  AND deptcode IN ('40003511')            -- ä¸Žä¸Šä¸€è¡Œæ˜¯ AND,故永远不成立
  AND service_type IN ('6', '14', '15', '16')
  AND visit_time >= '2026-07-23'
  AND visit_time < date_add('2026-10-21', interval 1 day);
-- â‘¡ æ»¡æ„åº¦å­ä»»åŠ¡çš„æ—¶é—´/科室字段现状
SELECT COUNT(*)                              AS rows_total,
       SUM(visit_time IS NULL)               AS visit_time_null,
       SUM(leavehospitaldistrictcode IS NULL) AS district_null,
       SUM(deptcode IS NOT NULL)             AS has_deptcode
FROM service_subtask
WHERE del_flag = 0 AND type = '2' AND service_type IN ('6', '14', '15', '16');
-- â‘¢ ä¿®å¤å‰ myd åˆ†ç±»é‡Œåªæœ‰ 6 é“老题(svyid ä¸å« 200)
SELECT COUNT(*) AS myd_questions, GROUP_CONCAT(DISTINCT svyid) AS svyids
FROM svy_lib_template_script
WHERE categoryid = 62 AND del_flag = 0;
-- ----------------------------------------------------------------------------
-- ã€1】已执行:把模板 200 çš„ 19 é“题挂到满意度分类 62
-- ----------------------------------------------------------------------------
-- DROP TABLE IF EXISTS bak_svy_lts_cat_20261021;
-- CREATE TABLE bak_svy_lts_cat_20261021 AS
-- SELECT id, svyid, categoryid, script_content
-- FROM svy_lib_template_script
-- WHERE svyid = 200 AND categoryid IS NULL AND del_flag = 0;      -- 19 è¡Œ
--
-- UPDATE svy_lib_template_script
-- SET categoryid = 62
-- WHERE svyid = 200 AND categoryid IS NULL AND del_flag = 0;      -- 19 è¡Œ
-- ----------------------------------------------------------------------------
-- ã€2】修复后校验:页面上每道题的 å‘送人数 / ç­”题人数
--      é¢„期:19 é“ svyid=200 çš„题都是 387 / 380;6 é“老题 0 / 0
-- ----------------------------------------------------------------------------
SELECT s.id AS script_id, s.svyid,
       LEFT(s.script_content, 18) AS question,
       (SELECT COUNT(DISTINCT ss.id)
          FROM service_subtask ss
         WHERE ss.del_flag = 0 AND ss.type = '2' AND ss.libtemplateid = s.svyid
           AND ss.service_type IN ('6', '14', '15', '16')
           AND IFNULL(ss.visit_time, IFNULL(ss.finishtime, ss.create_time)) >= '2026-07-23 00:00:00'
           AND IFNULL(ss.visit_time, IFNULL(ss.finishtime, ss.create_time)) < date_add('2026-10-21 00:00:00', interval 1 day)
           AND (ss.leavehospitaldistrictcode IN ('40003514') OR ss.deptcode IN ('40003511'))) AS send_person,
       (SELECT COUNT(DISTINCT ss.id)
          FROM service_subtask ss
          JOIN service_subtask_detail d ON d.sub_id = ss.id AND d.del_flag = 0
         WHERE ss.del_flag = 0 AND ss.type = '2' AND ss.libtemplateid = s.svyid
           AND ss.service_type IN ('6', '14', '15', '16')
           AND IFNULL(ss.visit_time, IFNULL(ss.finishtime, ss.create_time)) >= '2026-07-23 00:00:00'
           AND IFNULL(ss.visit_time, IFNULL(ss.finishtime, ss.create_time)) < date_add('2026-10-21 00:00:00', interval 1 day)
           AND (ss.leavehospitaldistrictcode IN ('40003514') OR ss.deptcode IN ('40003511'))
           AND d.questiontext = s.script_content) AS answer_person
FROM svy_lib_template_script s
WHERE s.categoryid = 62 AND s.del_flag = 0
ORDER BY s.svyid, s.id;
-- ============================================================================
-- ã€åŽç»­å»ºè®®ï¼ˆå¯é€‰ï¼‰ã€‘
-- 1) visit_time è¡¥æ•°æ®ï¼ˆè®©æ‰€æœ‰æŒ‰ visit_time è¿‡æ»¤çš„æŠ¥è¡¨éƒ½èƒ½æŸ¥åˆ°æ»¡æ„åº¦æ•°æ®ï¼Œ
--    ä¹Ÿèƒ½èµ°ç´¢å¼•;代码里的 IFNULL å…œåº•是保险,不等于数据本身正确):
--      UPDATE service_subtask
--      SET visit_time = IFNULL(finishtime, create_time)
--      WHERE del_flag = 0 AND type = '2' AND service_type IN ('6','14','15','16')
--        AND visit_time IS NULL;                       -- å®žæµ‹ 3803 è¡Œ
--    âš ï¸ ä¸Žã€Œéšè®¿å®Œæˆæ—¶é—´ã€å£å¾„相关的看板会受影响,建议先确认业务口径再执行。
--
-- 2) åˆ†ç±»é‡Œé‚£ 6 é“老题(svyid 51/97/102/103/154/156)现在会以 0/0 æ˜¾ç¤ºâ€”—
--    å®ƒä»¬å±žäºŽå…¶å®ƒé—®å·æ¨¡æ¿ã€ä¸å‚与满意度统计但被挂在了 myd åˆ†ç±»ä¸Šã€‚
--    æƒ³è®©é¡µé¢åªæ˜¾ç¤º 19 é“真题的话,把它们从分类里摘掉即可:
--      UPDATE svy_lib_template_script SET categoryid = NULL
--      WHERE categoryid = 62 AND svyid <> 200 AND del_flag = 0;   -- 6 è¡Œï¼ˆå…ˆå¤‡ä»½ï¼‰
--
-- 3) å‰ç«¯è¯·æ±‚里带了 scriptids: [] å’Œ templateid: null,
--    ä½†åŽç«¯ PatSatisfactionReqVO æ²¡æœ‰ scriptids å­—段(会被忽略)、statistics ä¹Ÿä¸ç”¨ templateid,
--    å¦‚果要按题目/模板筛选,需要前后端一起补。
-- ============================================================================
sql/fix_nhfy_sf_statistics_count_20261021.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,144 @@
-- ============================================================================
-- å—华附一(nhfy)满意度统计页 /smartor/serviceSubtask/getSfStatisticsCount æŸ¥ä¸åˆ°æ•°æ®
--                                                        2026-10-21
--
-- ã€è¯·æ±‚】POST /dev-api/smartor/serviceSubtask/getSfStatisticsCount
--   { "configKey":"joyCount", "statisticaltype":1, "type":"2",
--     "serviceType":[6], "deptcodes":["40003511",...], "hospitaldistrictcodes":["40003514"],
--     "startTime":"2026-07-23", "endTime":"2026-10-21", "scriptids":[] }
--   é¡µé¢è¡¨æ ¼ã€Œæš‚无数据」。
--
-- ã€æ ¹å›  1(主因):时间过滤列 visit_time å…¨ä¸º NULL】
--   getSfStatistics / getSfStatisticsGroupKey / getSfStatisticsCategory /
--   getSfStatisticsHyperlink / getSfStatisticsJoy(-details) çš„「随访时间」条件都是
--       AND visit_time >= DATE(#{startTime}) AND visit_time < DATE_ADD(...)
--   è€Œå—华满意度子任务(type=2 + service_type 6/14/15/16)的 visit_time
--   **3811 æ¡å…¨éƒ¨ä¸º NULL** â†’ åªè¦é¡µé¢é€‰äº†æ—¶é—´åŒºé—´ï¼Œå°±æ˜¯ 0 è¡Œã€‚
--   å®žæµ‹ï¼ˆç§‘室 40003511 / å‡ºé™¢ 2026-07-23~2026-10-21):
--       åŽŸ SQL             = 0 è¡Œ
--       ä»…把科室病区改 OR  = 0 è¡Œï¼ˆæ—¶é—´æ¡ä»¶ä»ç„¶åƒæŽ‰å…¨éƒ¨ï¼‰
--       OR + æ—¶é—´å…œåº•      = 387 è¡Œ
--
-- ã€æ ¹å›  2:科室 AND ç—…区,而满意度子任务 83% æ²¡æœ‰ç—…区】
--   åŒä¸€æ¡ SQL é‡Œã€Œç§‘室」和「病区」是两条独立的 AND æ¡ä»¶ï¼š
--       AND leavehospitaldistrictcode IN (...)  AND deptcode IN (...)
--   å—华满意度子任务的 leavehospitaldistrictcode æœ‰ 3236/3811 ä¸º NULL
--   â†’ ä¸¤ä¸ªæ¡ä»¶æ°¸è¿œä¸å¯èƒ½åŒæ—¶æˆç«‹ã€‚
--   ä¿®å¤é‡‡ç”¨**最小改动**:病区为空视为"不限"——
--       AND (leavehospitaldistrictcode IS NULL OR leavehospitaldistrictcode IN (...))
--   æœ‰ç—…区的数据行为完全不变(随访 service_type=2 çš„ 49982 è¡Œé‡Œåªæœ‰ 17 è¡Œç—…区为空)。
--
-- ã€æ ¹å›  3:「满意度项目总条数 / æœªé‡‡é›†æ•° / å®ŒæˆçŽ‡ã€ä¸‰åˆ—æ’ä¸º 0】
--   getSfStatisticsCategory çš„统计口径是
--       SUM(CASE WHEN a.categoryid IN (sys_config:joyCount é‡Œçš„分类) THEN 1 ELSE 0 END)
--   è€Œ sys_config.joyCount = "62,1000000012,5,1000000015,465",
--   å—华满意度作答明细的 categoryid **69599 è¡Œå…¨éƒ¨ä¸º NULL**
--   ï¼ˆæ¥æºï¼šsvy_task_template_script.categoryid ä¹Ÿæ˜¯ NULL,落库时原样带下来)
--   â†’ ä¸‰åˆ—永远 0。
--
-- ã€å·²åšçš„修复】
--   ä»£ç ï¼ˆruoyi-task/src/main/resources/mapper/task/ServiceSubtaskMapper.xml,需发版):
--     Â· getSfStatistics / getSfStatisticsGroupKey / getSfStatisticsCategory /
--       getSfStatisticsHyperlink / getSfStatisticsJoy / getSfStatisticsJoydetails
--       å…­å¤„:visit_time â†’ IFNULL(visit_time, finishtime, create_time) å…œåº•
--     Â· åŒå…­å¤„:病区条件改为「为空即放过」
--     Â· getSfStatisticsScriptDetails / getSfStatisticsScriptItemDetails(点「查看详情」弹窗)
--       ä¸¤å¤„:同样把 visit_time æ¢æˆ IFNULL å…œåº• â€”— ä¸æ”¹çš„话弹窗永远是「暂无数据」
--   æ•°æ®ï¼ˆå·²æ‰§è¡Œï¼Œæœ¬è„šæœ¬è®°å½•ï¼‰ï¼š
--     Â· svy_task_template_script(templateID=514) 19 è¡Œ categoryid = 62(以后新答卷自动带上)
--       å¤‡ä»½è¡¨ bak_svy_tts_cat_20261021
--     Â· service_subtask_detail 69599 è¡Œ categoryid = 62(历史答卷补上)
--       å¤‡ä»½è¡¨ bak_ssd_category_20261021
--
-- ã€ä¿®å¤åŽå®žæµ‹ï¼ˆæ¡ä»¶åŒæˆªå›¾ï¼šç§‘室 40003511 / ç—…区 40003514 / 2026-07-23~2026-10-21)】
--   è¡¨æ ¼è¡Œæ•° 1:科室 è‚¿ç˜¤ç§‘一病房,采集 386 äººæ¬¡ã€å·²å®Œæˆ 379
--   æ»¡æ„åº¦é¢˜ç›®æ€»æ¡æ•° 7201(=379×19)、已采集 6510、完成率 â‰ˆ90%
--   å¼¹çª—「满意度指标详情」19 é“题:如「是否有照顾您的护士」379/379、
--     ã€Œæ€»ä½“来说…打多少分」359/379、「请打出您最满意的护士」97/379、「最不满意」1/379
-- ============================================================================
-- ----------------------------------------------------------------------------
-- ã€0】证据 SQL
-- ----------------------------------------------------------------------------
-- â‘  æ—¶é—´æ¡ä»¶çš„影响(三条对比)
SELECT '原 SQL(AND + visit_time)' AS version, COUNT(*) AS c
FROM service_subtask
WHERE del_flag=0 AND orgid='20001001' AND campusid='30001002'
  AND leavehospitaldistrictcode IN ('40003514') AND deptcode IN ('40003511')
  AND service_type IN (6) AND type='2'
  AND visit_time >= DATE('2026-07-23') AND visit_time < DATE_ADD(DATE('2026-10-21'), INTERVAL 1 DAY)
UNION ALL
SELECT '病区为NULL放过 + visit_time å…œåº•', COUNT(*)
FROM service_subtask
WHERE del_flag=0 AND orgid='20001001' AND campusid='30001002'
  AND (leavehospitaldistrictcode IS NULL OR leavehospitaldistrictcode IN ('40003514'))
  AND deptcode IN ('40003511')
  AND service_type IN (6) AND type='2'
  AND IFNULL(visit_time, IFNULL(finishtime, create_time)) >= DATE('2026-07-23')
  AND IFNULL(visit_time, IFNULL(finishtime, create_time)) < DATE_ADD(DATE('2026-10-21'), INTERVAL 1 DAY);
-- â‘¡ æ»¡æ„åº¦æ˜Žç»†åˆ†ç±»å›žå¡«å‰åŽçš„口径
SELECT 'categoryid ä¸ºç©ºæ˜Žç»†' AS k, COUNT(*) AS c
FROM service_subtask_detail d JOIN service_subtask s ON s.id=d.sub_id
WHERE s.del_flag=0 AND s.type='2' AND s.service_type IN ('6','14','15','16') AND d.del_flag=0 AND d.categoryid IS NULL;
-- â‘¢ é¡µé¢è¡¨æ ¼ä¸Žæ»¡æ„åº¦ä¸‰åˆ—的最终口径
SELECT COUNT(DISTINCT t.id) AS subtasks,
       SUM(CASE WHEN a.categoryid IN (62,1000000012,5,1000000015,465) THEN 1 ELSE 0 END) AS joy_all_count,
       SUM(CASE WHEN a.categoryid IN (62,1000000012,5,1000000015,465)
                 AND IFNULL(a.matchedtext,a.asrtext) IS NOT NULL AND IFNULL(a.matchedtext,a.asrtext) <> '' THEN 1 ELSE 0 END) AS joy_count
FROM service_subtask_detail a JOIN service_subtask t ON a.sub_id=t.id
WHERE a.del_flag=0 AND t.del_flag=0 AND t.orgid='20001001' AND t.campusid='30001002'
  AND (t.leavehospitaldistrictcode IS NULL OR t.leavehospitaldistrictcode IN ('40003514'))
  AND t.deptcode IN ('40003511')
  AND t.service_type IN (6) AND t.type='2'
  AND IFNULL(t.visit_time, IFNULL(t.finishtime, t.create_time)) >= DATE('2026-07-23')
  AND IFNULL(t.visit_time, IFNULL(t.finishtime, t.create_time)) < DATE_ADD(DATE('2026-10-21'), INTERVAL 1 DAY);
-- ============================================================================
-- ã€æ•°æ®ä¿®å¤è®°å½•(已执行)】
--   1) æ¨¡æ¿é¢˜ç›®æŒ‚到满意度分类(让以后的新答卷自带 categoryid)
--      DROP TABLE IF EXISTS bak_svy_tts_cat_20261021;
--      CREATE TABLE bak_svy_tts_cat_20261021 AS
--      SELECT id, templateID, categoryid FROM svy_task_template_script
--      WHERE templateID=514 AND categoryid IS NULL AND del_flag=0;      -- 19 è¡Œ
--      UPDATE svy_task_template_script SET categoryid=62
--      WHERE templateID=514 AND categoryid IS NULL AND del_flag=0;
--
--   2) åŽ†å²ä½œç­”æ˜Žç»†è¡¥åˆ†ç±»ï¼ˆ69599 è¡Œï¼ŒæŒ‰ id åˆ†æ®µæ‰§è¡Œï¼Œå•批 ~1.3 ä¸‡è¡Œï¼‰
--      DROP TABLE IF EXISTS bak_ssd_category_20261021;
--      CREATE TABLE bak_ssd_category_20261021 AS
--      SELECT d.id, d.categoryid, d.sub_id
--      FROM service_subtask_detail d JOIN service_subtask s ON s.id=d.sub_id
--      WHERE d.categoryid IS NULL AND d.del_flag=0 AND s.del_flag=0
--        AND s.type='2' AND s.service_type IN ('6','14','15','16');
--      -- åˆ†æ®µï¼ˆç¤ºä¾‹ï¼šæŒ‰ id æ¯ 3 ä¸‡ä¸€æ®µï¼Œå…± 8 æ®µï¼‰
--      -- UPDATE service_subtask_detail d JOIN service_subtask s ON s.id=d.sub_id
--      -- SET d.categoryid=62
--      -- WHERE d.id BETWEEN 128219 AND 158218 AND d.categoryid IS NULL AND d.del_flag=0
--      --   AND s.del_flag=0 AND s.type='2' AND s.service_type IN ('6','14','15','16');
--
--   å›žæ»šï¼š
--      UPDATE service_subtask_detail d JOIN bak_ssd_category_20261021 b ON b.id=d.id
--      SET d.categoryid = b.categoryid;
--      UPDATE svy_task_template_script t JOIN bak_svy_tts_cat_20261021 b ON b.id=t.id
--      SET t.categoryid = b.categoryid;
-- ============================================================================
-- ============================================================================
-- ã€ä»æœªè§£å†³ / éœ€è¦ä¸šåŠ¡ç¡®è®¤çš„ä¸¤åˆ—ã€‘
-- 1) éšè®¿çŽ‡ï¼ˆåŠæ—¶çŽ‡ï¼‰ï¼šselectTimelyRateBatch é‡Œæœ‰
--       AND ss.visit_time IS NOT NULL
--    ä¸”用 visit_time / endtime è®¡ç®—及时率;南华满意度两者都为空
--    â†’ è¿™ä¸€åˆ—对满意度口径没有数据来源,会显示 0。
--    è¦å‡ºæ•°å¿…须先有「应随访时间」和「出院时间」,属于数据治理范畴。
--
-- 2) ã€Œæ»¡æ„åº¦æŒ‡æ•°ã€tab è°ƒç”¨çš„ getSfStatisticsJoy / getSfStatisticsJoydetails æ˜¯**语音口径**:
--       AND targetid IN (SELECT id FROM ivr_liba_target WHERE assortid IN (joyCount é…ç½®))
--    å—华满意度是问卷(svy_* æŒ‡æ ‡ï¼‰ï¼Œtargetid ä¸Ž ivr_liba_target ä¸æ˜¯ä¸€å¥—
--    â†’ å³ä½¿ä¿®å¥½æ—¶é—´/病区条件,这个 tab å¯¹å—华也只能是 0。
--    è¦ä¹ˆç»™å—华做一版按 svy æŒ‡æ ‡ç»Ÿè®¡çš„实现,要么该 tab å¯¹å—华隐藏。
-- ============================================================================
sql/fix_nhfy_sltd_satisfaction_endpoints_20261021.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,153 @@
-- ============================================================================
-- å—华附一(nhfy):满意度统计 4 ä¸ªæŽ¥å£"数据不对" â€”— å®šä½ä¸Žä¿®å¤
--   Â· POST /smartor/serviceSubtaskDetail/sltdMydTotalByScore
--   Â· POST /smartor/serviceSubtaskDetail/statQuestionOptionByKsOrBq  (页面调了 3 æ¬¡)
--                                                        2026-10-21
--
-- ã€æ ¹å› ï¼ˆ3 ä¸ªï¼Œå‡å·²åœ¨åº“上量化)】
--
--   â‘  å‡ºé™¢æ—¶é—´è¿‡æ»¤ç”¨çš„æ˜¯ endtime,而南华满意度子任务的 endtime å¤§é‡ä¸ºç©º
--      service_subtask.endtime:3803 æ¡æ»¡æ„åº¦å­ä»»åС䏭 3151 æ¡ä¸º NULL(83%)。
--      è€Œ selectMydSubtaskRefs / statQuestionOption / statQuestionOptionBySubtaskIds
--      å››ä¸ª statement çš„"出院时间区间"都直接 date_format(s.endtime,...) è¿‡æ»¤
--      â†’ é¡µé¢ä¸€é€‰å‡ºé™¢æ—¥æœŸï¼Œæ•°æ®å°±è¢«ç­›æŽ‰å¤§åŠï¼ˆå®žæµ‹ï¼šåŠ  2026-07-23~2026-10-21 åŽ refs = 0)。
--      ä¿®å¤ï¼šç»Ÿä¸€æ”¹ä¸º IFNULL(s.endtime, IFNULL(s.finishtime, s.create_time)) å…œåº•。
--      ä¿®å¤åŽåŒä¸€æ¡ä»¶ refs = 387(= æ»¡æ„åº¦ sendstate=6 çš„ 380 + æœªå®Œæˆ 7)。
--
--   â‘¡ statQuestionOptionByKsOrBq çš„「科室 OR ç—…区」少了括号(真 Bug,同德也错)
--      åŽŸ SQL(deptOrDistrict==2 åˆ†æ”¯ï¼‰ï¼š
--          AND leavehospitaldistrictcode IN (...) OR deptcode IN (...)
--      AND ä¼˜å…ˆçº§é«˜äºŽ OR,实际等价于
--          (所有 AND æ¡ä»¶ AND ç—…区 IN ...) OR ç§‘室 IN ...
--      â†’ "科室命中"那一支完全绕过了 service_type / æ—¥æœŸ / del_flag æ¡ä»¶ã€‚
--      å®žæµ‹ï¼ˆå—华、科室 40003511、2026-07-23~2026-10-21):
--          ä¿®æ­£å‰ 17604 è¡Œ   ä¿®æ­£åŽ 7220 è¡Œ   ï¼ˆå¤šç®—了 2.4 å€ï¼Œå…¨æ˜¯åŽ†å²/其它任务类型)
--      ä¿®å¤ï¼šæ”¹æˆ AND (病区 IN (...) OR ç§‘室 IN (...))。
--
--   â‘¢ ç»¼åˆå¾—分口径写死成省立同德(SLTD)五档文案
--      SltdSatisfactionController#calcMydScore é‡Œ resolve(SatisfactionSites.SLTD) æ˜¯ç¡¬ç¼–码。
--      åŒå¾·å£å¾„靠选项文案 "很满意/满意/一般/不满意/很不满意" åˆ¤æ¡£ï¼Œ
--      è€Œå—华问卷(模板 514 / libtemplateid 200)的选项是「是(有)/(总是)很安静/很熟练/主动讲过…」
--      â†’ ä¸€æ¡éƒ½åŒ¹é…ä¸ä¸Š â†’ å¾—分恒为 "0.00"。
--      ä¿®å¤ï¼šç«™ç‚¹æ”¹ç”± sys_config: satisfaction.site å†³å®šï¼ˆé»˜è®¤ sltd,保持同德不变);
--            å—华可配 common(= æŒ‰æ˜Žç»† score æ±‚平均)。
--
--      âš ï¸ ä½†è¦æ³¨æ„ï¼šå—华这个问卷**根本没配分值**
--         svy_task_template_script.score(模板514,19题)  = '00000000000'
--         svy_task_template_targetoption.score(58个选项) = 0
--         service_subtask_detail.score(63108 è¡Œï¼‰         = 0 / '00000000000'
--         æ‰€ä»¥å³ä¾¿åˆ‡åˆ° common å£å¾„,综合得分仍然是 0.00。
--         è¦å‡ºå¾—分,必须先给选项/题目配分值(业务配置),否则这个列对南华没有意义。
--
-- ã€å·²æ”¹çš„代码】ruoyi-satisfaction
--   Â· SatisfactionMapper.xml
--       - selectMydSubtaskRefs / statQuestionOption / statQuestionOptionBySubtaskIds:
--         å‡ºé™¢æ—¶é—´ endtime â†’ IFNULL(endtime, finishtime, create_time)
--       - selectMydSubtaskRefs:deptOrDistrict è¡¥ä¸Š 2(OR)分支(原来只在 1 æ—¶æ‹¼ç—…区,科室却无条件拼)
--       - statQuestionOptionByKsOrBq:deptOrDistrict==2 çš„ OR è¡¥æ‹¬å·
--   Â· SatisfactionSwitch:新增 site(),读 sys_config: satisfaction.site(10 ç§’进程内缓存)
--   Â· SltdSatisfactionController#calcMydScore:改为 resolve(satisfactionSwitch.site())
--
-- ã€ä¿®å¤åŽå®žæµ‹ï¼ˆæ¡ä»¶ï¼šç§‘室 40003511 / ç—…区 40003514 / 2026-07-23~2026-10-21)】
--   refs = 387;按科室分组选项分布正常,例如「肿瘤科一病房」每题 379 äºº
-- ============================================================================
-- ----------------------------------------------------------------------------
-- ã€0】证据 SQL(随时可重跑)
-- ----------------------------------------------------------------------------
-- â‘  å‡ºé™¢æ—¶é—´ä¸ºç©ºçš„æ¯”例
SELECT COUNT(*)                    AS rows_total,
       SUM(endtime IS NULL)        AS endtime_null,
       SUM(leavehospitaldistrictname IS NULL OR leavehospitaldistrictname = '') AS district_name_empty
FROM service_subtask
WHERE del_flag = 0 AND type = '2' AND service_type IN ('6', '14', '15', '16');
-- â‘¡ OR æ¼æ‹¬å·çš„影响面(修正前 vs ä¿®æ­£åŽï¼‰
SELECT '修正前(无括号)' AS version, COUNT(*) AS rows_cnt
FROM service_subtask_detail d
JOIN service_subtask s ON d.sub_id = s.id
WHERE s.del_flag = 0 AND d.del_flag = 0 AND s.service_type IN ('6', '14')
  AND d.create_time >= '2026-07-23 00:00:00'
  AND d.create_time < date_add('2026-10-21', interval 1 day)
  AND s.leavehospitaldistrictcode IN ('40003514') OR s.deptcode IN ('40003511');
SELECT '修正后(括号包住 OR)' AS version, COUNT(*) AS rows_cnt
FROM service_subtask_detail d
JOIN service_subtask s ON d.sub_id = s.id
WHERE s.del_flag = 0 AND d.del_flag = 0 AND s.service_type IN ('6', '14')
  AND d.create_time >= '2026-07-23 00:00:00'
  AND d.create_time < date_add('2026-10-21', interval 1 day)
  AND (s.leavehospitaldistrictcode IN ('40003514') OR s.deptcode IN ('40003511'));
-- â‘¢ åˆ†å€¼åˆ°åº•有没有配(三个层面都看一眼)
SELECT '题目分值' AS layer, score, COUNT(*) AS cnt
FROM svy_task_template_script WHERE templateID = 514 AND del_flag = 0 GROUP BY score
UNION ALL
SELECT '选项分值', o.score, COUNT(*)
FROM svy_task_template_targetoption o
JOIN svy_task_template_script s ON s.id = o.scriptid
WHERE s.templateID = 514 AND s.del_flag = 0 GROUP BY o.score
UNION ALL
SELECT '明细分值', d.score, COUNT(*)
FROM service_subtask_detail d
JOIN service_subtask ss ON ss.id = d.sub_id
WHERE ss.type = '2' AND ss.service_type IN ('6', '14') AND d.del_flag = 0
GROUP BY d.score;
-- ----------------------------------------------------------------------------
-- ã€1】可选配置:综合得分口径站点(不改也能跑,只是得分口径按同德)
--     åŒå¾·ï¼šä¿æŒé»˜è®¤ sltd(或显式配置 sltd)
--     å—华:配 common â€”— âš ï¸ å‰ææ˜¯é—®å·é€‰é¡¹/题目配了分值,否则得分仍是 0.00
-- ----------------------------------------------------------------------------
-- INSERT INTO sys_config (config_name, config_key, config_value, config_type, create_by, create_time, remark)
-- VALUES ('满意度综合得分口径', 'satisfaction.site', 'common', 'Y', 'admin', sysdate(),
--         'sltd=省立同德五档文案加权(默认);common/nanhua=按明细 score æ±‚平均');
-- æ ¡éªŒ
-- SELECT config_key, config_value FROM sys_config WHERE config_key = 'satisfaction.site';
-- ----------------------------------------------------------------------------
-- ã€2】可选数据/配置:让「综合得分」对南华有意义
--     å½“前模板 514 çš„ 19 é“题、58 ä¸ªé€‰é¡¹åˆ†å€¼å…¨æ˜¯ 0,任何口径都算不出分。
--     è‹¥åŒ»é™¢æŽ¥å—"五档之外按 æ»¡è¶³/部分满足/不满足 æ‰“分",请业务在
--     ã€Œé—®å·æ¨¡æ¿ â†’ é¢˜ç›®é€‰é¡¹ã€é‡Œç»™æ¯ä¸ªé€‰é¡¹é…ç½®åˆ†å€¼ï¼Œä¹‹åŽï¼š
--       Â· ç«™ç‚¹å£å¾„建议配 common(按明细 score æ±‚平均,百分制)
--       Â· åŽ†å²æ˜Žç»†çš„ score ä¸ä¼šè‡ªåŠ¨å›žå¡«ï¼Œéœ€å¦è¡Œè¯„ä¼°è¦ä¸è¦æŒ‰é€‰é¡¹è¡¥åŽ†å²åˆ†å€¼
-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
-- ã€3】验证:修正后的 refs ä¸ŽæŒ‰ç§‘室的选项分布
-- ----------------------------------------------------------------------------
SELECT COUNT(*) AS refs
FROM service_subtask
WHERE del_flag = 0
  AND orgid = '20001001' AND campusid = '30001002'
  AND service_type = '6'
  AND date_format(IFNULL(endtime, IFNULL(finishtime, create_time)), '%y%m%d') >= date_format('2026-07-23', '%y%m%d')
  AND date_format(IFNULL(endtime, IFNULL(finishtime, create_time)), '%y%m%d') <= date_format('2026-10-21', '%y%m%d')
  AND (leavehospitaldistrictcode IN ('40003514') OR deptcode IN ('40003511'));
SELECT s.deptname, d.questiontext, d.asrtext, COUNT(*) AS cnt
FROM service_subtask_detail d
JOIN service_subtask s ON d.sub_id = s.id
WHERE s.del_flag = 0 AND d.del_flag = 0 AND s.service_type IN ('6', '14')
  AND d.create_time >= '2026-07-23 00:00:00'
  AND d.create_time < date_add('2026-10-21', interval 1 day)
  AND (s.leavehospitaldistrictcode IN ('40003514') OR s.deptcode IN ('40003511'))
GROUP BY s.deptname, d.questiontext, d.asrtext
ORDER BY cnt DESC
LIMIT 5;
-- ============================================================================
-- ã€å¦éœ€æ³¨æ„ï¼ˆæœªæ”¹ï¼Œå±žäº§å“å£å¾„)】
-- 1) statQuestionOptionByKsOrBq çš„ Java ä¾§æŒ‰ deptname / leavehospitaldistrictname åˆ†ç»„,
--    åˆ†ç»„名为空直接跳过。南华满意度子的 leavehospitaldistrictname æœ‰ 3228/3803 ä¸ºç©ºï¼Œ
--    æ‰€ä»¥é¡µé¢ä¸Šã€ŒæŒ‰ç—…区」这个维度对南华基本是空的——不是取数错,是数据里没有病区。
-- 2) åŒä¸€é¡µé¢çš„ mydTrend / sltdMydTotalByDimension / mzMydScoreRank ä¹Ÿéƒ½æ˜¯åŒå¾·å£å¾„
--    ï¼ˆå‡ºé™¢æ—¶é—´ã€ç—…区维度),南华要用需按同样思路逐个核对。
-- ============================================================================
sql/fix_nhfy_ssd_delflag_20261021.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,110 @@
-- ============================================================================
-- å—华附一(nhfy):恢复被逻辑删除的满意度作答明细(2026-10-21 å·²æ‰§è¡Œï¼‰
--
-- ã€èƒŒæ™¯ã€‘导出「问题详情占比」比「患者随访信息表单」少 10 äººï¼ˆ370 vs 380),
--         åŽŸå› æ˜¯è¿™ 10 ä¸ªå­ä»»åŠ¡çš„ service_subtask_detail å…¨éƒ¨è¢«ç½® del_flag='1'
--         ï¼ˆåŒä¸€æ‚£è€…还有 2~6 ä»½é‡å¤å‰¯æœ¬ï¼Œç­”案完全一致)。
--         è¯¦è§ / æŽ’查_问题占比与随访表单人数不一致.sql、抓现场_谁把明细置为删除_20261021.sql
--
-- ã€æœ¬æ¬¡æ‰§è¡Œå†…容】
--   1) æ¢å¤ 5 ä¸ªå­ä»»åŠ¡ï¼ˆ45108 45109 45110 45111 45114):每 (sub_id, scriptid) å– MAX(id) é‚£ä¸€ä»½ç½® del_flag='0'
--      -> æ›´æ–° 95 è¡Œï¼ˆ19 é¢˜ Ã— 5 äººï¼‰
--   2) è¡¥é½æ­¤å‰äººå·¥æ¢å¤æ¼æŽ‰çš„题(45099 ç¼º 4 é¢˜ã€45102 ç¼º 9 é¢˜ï¼‰
--      -> æ›´æ–° 13 è¡Œ
--   3) æœªæ”¹ update_time(保持 create_time=update_time çš„取证指纹,也与此前人工恢复的行一致)
--   4) é‡å¤å‰¯æœ¬ï¼ˆåŒé¢˜åŒç­”案的多份)仍保留 del_flag='1',不影响统计:
--      å¯¼å‡º SQL æŒ‰ (task, question, asrtext, targetvalue, value_type) åˆ†ç»„ + COUNT(DISTINCT s.id) åŽ»é‡
--
-- ã€æ‰§è¡Œç»“果(2026-10-21 å¤æ ¸ï¼‰ã€‘
--   è¡¨å•行数 380 / æœ‰æœ‰æ•ˆæ˜Žç»†çš„æ‚£è€… 380;19 é“题逐题人数全部 380(此前 370/378/379)
--   æ¯ä¸ªå­ä»»åŠ¡æ´»è¡Œé¢˜ç›®æ•°ï¼š44957(19) 45094(19) 45095(19) 45099(19) 45102(19)
--                         45108(19) 45109(19) 45110(19) 45111(19) 45114(19)
--
-- ã€å¤‡ä»½è¡¨ã€‘用于回滚
--   bak_ssd_20261021_nhfy    = 412 è¡Œï¼ˆ45108/45109/45110/45111/45114 çš„全部明细)
--   bak_ssd_20261021_nhfy_2  = 114 è¡Œï¼ˆ45099/45102 çš„全部明细)
-- ============================================================================
-- ----------------------------------------------------------------------------
-- ã€å·²æ‰§è¡Œã€‘(1) å¤‡ä»½
-- ----------------------------------------------------------------------------
-- DROP TABLE IF EXISTS bak_ssd_20261021_nhfy;
-- CREATE TABLE bak_ssd_20261021_nhfy AS
-- SELECT * FROM service_subtask_detail WHERE sub_id IN (45108, 45109, 45110, 45111, 45114);
-- DROP TABLE IF EXISTS bak_ssd_20261021_nhfy_2;
-- CREATE TABLE bak_ssd_20261021_nhfy_2 AS
-- SELECT * FROM service_subtask_detail WHERE sub_id IN (45099, 45102);
-- ----------------------------------------------------------------------------
-- ã€å·²æ‰§è¡Œã€‘(2) æ¢å¤ 5 ä¸ªå­ä»»åŠ¡ï¼šæ¯ (sub_id, scriptid) ç•™æœ€æ–°é‚£ä»½  -> 95 è¡Œ
-- ----------------------------------------------------------------------------
-- UPDATE service_subtask_detail d
--     JOIN (SELECT sub_id, scriptid, MAX(id) AS keep_id
--           FROM service_subtask_detail
--           WHERE sub_id IN (45108, 45109, 45110, 45111, 45114)
--           GROUP BY sub_id, scriptid) x ON x.keep_id = d.id
-- SET d.del_flag = '0';
-- ----------------------------------------------------------------------------
-- ã€å·²æ‰§è¡Œã€‘(3) è¡¥é½ 45099 / 45102 ç¼ºçš„题  -> 13 è¡Œ
-- ----------------------------------------------------------------------------
-- UPDATE service_subtask_detail d
--     JOIN (SELECT x.sub_id, x.scriptid, MAX(x.id) AS keep_id
--           FROM service_subtask_detail x
--           WHERE x.sub_id IN (45099, 45102)
--             AND NOT EXISTS (SELECT 1 FROM service_subtask_detail d3
--                             WHERE d3.sub_id = x.sub_id AND d3.scriptid = x.scriptid AND d3.del_flag = 0)
--           GROUP BY x.sub_id, x.scriptid) k ON k.keep_id = d.id
-- SET d.del_flag = '0';
-- ----------------------------------------------------------------------------
-- ã€æ ¡éªŒã€‘随时可重跑(预期 380 / 380)
-- ----------------------------------------------------------------------------
SET @expect := 380;
SELECT COUNT(*) AS form_rows,
       COUNT(DISTINCT CASE WHEN d.live = 1 THEN d.sub_id END) AS with_live_detail
FROM service_subtask s
LEFT JOIN (SELECT sub_id, MAX(del_flag = 0) AS live FROM service_subtask_detail GROUP BY sub_id) d
       ON d.sub_id = s.id
WHERE s.del_flag = '0' AND s.orgid = '20001001' AND s.campusid = '30001002'
  AND s.service_type = 6 AND s.sendstate = 6 AND s.visit_count = 1
  AND (s.deptcode = '40003511' OR s.leavehospitaldistrictcode = '40003514');
SELECT MIN(c) AS min_cnt, MAX(c) AS max_cnt, SUM(c <> @expect) AS not_equal
FROM (  SELECT COUNT(DISTINCT s.id) AS c
  FROM service_subtask s
  JOIN service_subtask_detail d
    ON d.sub_id = s.id AND d.del_flag = 0
   AND d.id = (SELECT MAX(d2.id) FROM service_subtask_detail d2
               WHERE d2.sub_id = d.sub_id AND d2.questiontext <=> d.questiontext AND d2.del_flag = '0')
  WHERE s.del_flag = '0' AND s.orgid = '20001001' AND s.campusid = '30001002'
    AND s.service_type = 6 AND s.sendstate = 6 AND s.visit_count = 1
    AND (s.deptcode = '40003511' OR s.leavehospitaldistrictcode = '40003514')
  GROUP BY d.questiontext
) x;
-- ----------------------------------------------------------------------------
-- ã€å›žæ»šã€‘(需要撤销时)
-- ----------------------------------------------------------------------------
-- UPDATE service_subtask_detail d
--     JOIN bak_ssd_20261021_nhfy b ON b.id = d.id
-- SET d.del_flag = b.del_flag, d.update_time = b.update_time;
-- UPDATE service_subtask_detail d
--     JOIN bak_ssd_20261021_nhfy_2 b ON b.id = d.id
-- SET d.del_flag = b.del_flag, d.update_time = b.update_time;
-- ============================================================================
-- ã€æ³¨æ„ã€‘本次只是修数据。根因(谁把明细置 1)尚未定位:
--   å…¨åº“被删明细的 update_time å…¨éƒ¨ç­‰äºŽ create_time,说明删除**没有**èµ°
--   Controller->Service é‚£ä¸¤æ¡æŽ¥å£ï¼ˆå®ƒä»¬ä¼šå¼ºåˆ¶ NOW());用
--   sql/抓现场_谁把明细置为删除_20261021.sql é‡Œçš„ general_log æŠ“一次 SQL åŽŸæ–‡å³å¯å®šæ¡ˆã€‚
--   åœ¨æ­¤ä¹‹å‰ï¼Œåªè¦è¿˜æœ‰æ‚£è€…"重填/被补默认值",同样的问题会再次出现。
-- ============================================================================
sql/fix_nhfy_³öÔºËæ·ÃÈÎÎñÔºÇø²¡Çø»ØÌî_20261029.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,165 @@
-- ============================================================================
-- å—华附一(nhfy):出院随访任务「院区 / ç—…区」回填脚本
-- ----------------------------------------------------------------------------
-- æ ¹å› ï¼ˆ2026-10-29 æŽ’查确认):
--   å‡ºé™¢éšè®¿ä»»åŠ¡ç”Ÿæˆæ—¶ï¼ŒVisitSubtaskFactory#boxedServiceSubtask æ— æ¡ä»¶æ‰§è¡Œ
--   `d.setCampusid(inhosp.getCampusid())`,当 pat_med_inhosp.campusid ä¸ºç©ºæ—¶ï¼Œ
--   ä¼šæŠŠä»Žé•¿æœŸä»»åŠ¡æ¬æ¥çš„é™¢åŒºè¦†ç›–æˆ NULL â†’ service_subtask.campusid è½ç©ºã€‚
--   è€Œéšè®¿åˆ—表接口 ServiceSubtaskController#patItem å¼ºåˆ¶ setCampusid(登录用户院区),
--   SQL æ¡ä»¶ `and campusid = #{campusid}`,NULL ä¸åŒ¹é…ä»»ä½•值
--   â†’ è¿™äº›ä»»åŠ¡"库里存在、页面上永远查不到"(就是"出院 475 æ¡ã€éšè®¿åªæœ‰ 444 æ¡"的直接原因)。
--   ä»£ç å·²åœ¨ VisitSubtaskFactory ä¸­ä¿®å¤ï¼ˆå‡ºé™¢è®°å½•有院区才覆盖),本脚本只修历史数据。
--
-- æœ¬æ¬¡å®žæµ‹ï¼ˆ2026-10-29,smartor_nhfy):
--   Â· service_type='2' ä¸” campusid ä¸ºç©ºï¼š**314 æ¡**
--       â€”— å…¨éƒ¨æ˜¯ã€Œå†æ¬¡éšè®¿ / æ‰‹å·¥æ–°å¢žã€äº§ç”Ÿçš„行(is_visit_again=1、inhospid ä¸ºç©ºã€upid ä¸ºç©ºï¼‰ï¼š
--          313 æ¡ create_by=曹慧玲、taskid=108「消化一区随访」、2026-06 ç”Ÿæˆï¼›
--          1 æ¡ taskid=177「任务测试0624」。
--          **这批无法从出院记录回填**(inhospid æ˜¯ç©ºçš„,也没有父任务可继承),
--          åªèƒ½é€€åˆ°æ‚£è€…档案 pat_archive.campusid(314 æ¡å…¨éƒ½æœ‰å€¼ï¼‰ã€‚
--   Â· service_type='2' ä¸” leaveldeptcode ä¸ºç©ºï¼š**303 æ¡**
--       â€”— å…¶ä¸­ 294 æ¡å°±æ˜¯ä¸Šé¢é‚£æ‰¹ï¼ˆinhospid ç©ºï¼‰ï¼Œå¦ 9 æ¡æœ‰ inhospid、可从出院记录回填
--          ï¼ˆ9 æ¡éƒ½æ˜¯ã€Œå†æ¬¡éšè®¿ã€ï¼Œtaskid=11,create_by=超级管理员,2026-10-19~21 ç”Ÿæˆï¼‰ã€‚
--   Â· service_type='2' ä¸” leavehospitaldistrictcode ä¸ºç©ºï¼š**10 æ¡**(出院记录里都有值,可回填)
--   Â· ç—…区"不为空但与出院记录不一致"的:57 æ¡ï¼ˆå¤šä¸ºè½¬ç§‘/转病区),**不自动修**,见 STEP 0-D
--   Â· éª¨ç§‘中心一病区(40011020)2026-07 é‚£ 25 æ¡ï¼ˆæ‰«æé“¾è·¯ campusid è½ç©ºï¼‰å·²å…ˆæ‰‹å·¥å›žå¡«ï¼Œ
--     é¡µé¢å¯è§æ•° 443 â†’ 468,现在 468 = 468 å·²å®Œå…¨å¯¹é½ï¼ˆå¯ç›´æŽ¥ç”¨ STEP 4-B å¤æ ¸ï¼‰
--
-- âš ï¸ å£å¾„提醒:同一批出院记录,「按离院病区」是 475 æ¡ã€ã€ŒæŒ‰ç¦»é™¢ç§‘室」是 469 æ¡
--   ï¼ˆå·® 6:475 é‡Œæœ‰ 7 æ¡ç—…区在骨科中心一但科室属于别的科室;另有 1 æ¡ç§‘室在骨科中心一但病区是疼痛科病区)。
--   å¯¹åº”的任务数是 468(病区)/ 465(科室)。**先确认页面用的是哪个口径再对数**,
--   å¦åˆ™ä¼šå‡­ç©ºå¤šå‡ºä¸€ç¬”差额。
--
-- æ‰§è¡Œé¡ºåºï¼šSTEP 0 é¢„检 â†’ STEP 1 å¤‡ä»½ â†’ STEP 2 å›žå¡«é™¢åŒº â†’ STEP 3 å›žå¡«ç—…区
--           â†’ STEP 4 æ ¡éªŒ â†’ ï¼ˆå¦‚需撤销)STEP 5 å›žæ»š
-- å»ºè®®ï¼šä¸šåŠ¡ä½Žå³°æœŸæ‰§è¡Œï¼›æ¯æ¡ UPDATE éƒ½å¸¦ LIMIT åˆ†æ‰¹çš„话,重复执行到影响 0 è¡Œä¸ºæ­¢ï¼ˆå¹‚等)。
-- ============================================================================
-- ---------------------------------------------------------------------------
-- STEP 0:预检(只读;反复执行安全,先看清影响面再动)
-- ---------------------------------------------------------------------------
SELECT '0-A å¾…修数据量' AS step,
       (SELECT COUNT(*) FROM service_subtask
          WHERE del_flag = 0 AND service_type = '2' AND (campusid IS NULL OR campusid = '')) AS å¾…补院区,
       (SELECT COUNT(*) FROM service_subtask
          WHERE del_flag = 0 AND service_type = '2'
            AND (leavehospitaldistrictcode IS NULL OR leavehospitaldistrictcode = '')) AS å¾…补病区;
SELECT '0-B é™¢åŒºæ¥æºæ˜¯å¦å¤Ÿç”¨' AS step,
       COUNT(*) AS å¾…补条数,
       SUM(CASE WHEN i.campusid IS NOT NULL AND i.campusid <> '' THEN 1 ELSE 0 END) AS å‡ºé™¢è®°å½•有院区,
       SUM(CASE WHEN a.campusid IS NOT NULL AND a.campusid <> '' THEN 1 ELSE 0 END) AS æ‚£è€…档案有院区,
       SUM(CASE WHEN (i.campusid IS NULL OR i.campusid = '')
                 AND (a.campusid IS NULL OR a.campusid = '') THEN 1 ELSE 0 END) AS ä¸¤å¤„都没有_需人工
FROM service_subtask s
LEFT JOIN pat_med_inhosp i ON i.inhospid = s.inhospid
LEFT JOIN pat_archive a ON a.id = s.patid
WHERE s.del_flag = 0 AND s.service_type = '2' AND (s.campusid IS NULL OR s.campusid = '');
SELECT '0-C å¾…补院区按病区分布' AS step, leavehospitaldistrictcode, leavehospitaldistrictname, COUNT(*) AS æ¡æ•°
FROM service_subtask
WHERE del_flag = 0 AND service_type = '2' AND (campusid IS NULL OR campusid = '')
GROUP BY leavehospitaldistrictcode, leavehospitaldistrictname
ORDER BY æ¡æ•° DESC;
-- ç—…区"不为空但与出院记录不一致"的,**不在本脚本自动修**(可能是转科/转病区的正常业务),
-- åªåˆ—出来人工确认(⚠️ è¿™æ¡è¾ƒæ…¢ï¼Œå®žæµ‹çº¦ 70s,放到最后跑或直接跳过):
SELECT '0-D ç—…区不一致_人工确认' AS step, s.id AS å­ä»»åŠ¡ID, s.inhospid, s.sendname AS å§“名,
       DATE_FORMAT(s.endtime, '%Y-%m-%d') AS å‡ºé™¢æ—¥æœŸ,
       s.leavehospitaldistrictcode AS ä»»åŠ¡ç—…åŒº, i.leavehospitaldistrictcode AS è®°å½•病区,
       i.leavehospitaldistrictname AS è®°å½•病区名
FROM service_subtask s
JOIN pat_med_inhosp i ON i.inhospid = s.inhospid
WHERE s.del_flag = 0 AND s.service_type = '2'
  AND s.leavehospitaldistrictcode IS NOT NULL AND s.leavehospitaldistrictcode <> ''
  AND i.leavehospitaldistrictcode IS NOT NULL AND i.leavehospitaldistrictcode <> ''
  AND s.leavehospitaldistrictcode <> i.leavehospitaldistrictcode
LIMIT 200;
-- ---------------------------------------------------------------------------
-- STEP 1:备份(只备份"将被修改"的行;建表后请确认行数与 STEP 0-A ä¸€è‡´ï¼‰
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS bak_service_subtask_campusward_20261029 AS
SELECT s.id,
       s.campusid,
       s.leavehospitaldistrictcode,
       s.leavehospitaldistrictname,
       s.update_time AS old_update_time
FROM service_subtask s
WHERE s.del_flag = 0
  AND s.service_type = '2'
  AND (s.campusid IS NULL OR s.campusid = ''
       OR s.leavehospitaldistrictcode IS NULL OR s.leavehospitaldistrictcode = '');
SELECT '1 å¤‡ä»½è¡Œæ•°' AS step, COUNT(*) AS backup_rows
FROM bak_service_subtask_campusward_20261029;
-- ---------------------------------------------------------------------------
-- STEP 2:回填院区(先取出院记录,取不到退回患者档案;两个来源都没有的留着不动)
-- ---------------------------------------------------------------------------
UPDATE service_subtask s
  JOIN pat_med_inhosp i ON i.inhospid = s.inhospid
   SET s.campusid = i.campusid
 WHERE s.del_flag = 0 AND s.service_type = '2'
   AND (s.campusid IS NULL OR s.campusid = '')
   AND i.campusid IS NOT NULL AND i.campusid <> '';
UPDATE service_subtask s
  JOIN pat_archive a ON a.id = s.patid
   SET s.campusid = a.campusid
 WHERE s.del_flag = 0 AND s.service_type = '2'
   AND (s.campusid IS NULL OR s.campusid = '')
   AND a.campusid IS NOT NULL AND a.campusid <> '';
-- ---------------------------------------------------------------------------
-- STEP 3:回填病区 / ç§‘室(**只补空的**,不动已有值)
--   Â· inhospid ä¸ºç©ºçš„那批(314 æ¡ã€Œå†æ¬¡éšè®¿ã€ï¼‰åœ¨è¿™é‡Œè¡¥ä¸åˆ°ï¼Œåªèƒ½é  STEP 2 çš„æ¡£æ¡ˆå…œåº•补院区
-- ---------------------------------------------------------------------------
UPDATE service_subtask s
  JOIN pat_med_inhosp i ON i.inhospid = s.inhospid
   SET s.leavehospitaldistrictcode = i.leavehospitaldistrictcode,
       s.leavehospitaldistrictname = i.leavehospitaldistrictname
 WHERE s.del_flag = 0 AND s.service_type = '2'
   AND (s.leavehospitaldistrictcode IS NULL OR s.leavehospitaldistrictcode = '')
   AND i.leavehospitaldistrictcode IS NOT NULL AND i.leavehospitaldistrictcode <> '';
UPDATE service_subtask s
  JOIN pat_med_inhosp i ON i.inhospid = s.inhospid
   SET s.leaveldeptcode = i.leaveldeptcode,
       s.leaveldeptname = i.leaveldeptname
 WHERE s.del_flag = 0 AND s.service_type = '2'
   AND (s.leaveldeptcode IS NULL OR s.leaveldeptcode = '')
   AND i.leaveldeptcode IS NOT NULL AND i.leaveldeptcode <> '';
-- ---------------------------------------------------------------------------
-- STEP 4:校验(期望:剩余 0 / 0,且页面可见数与出院记录数对齐)
-- ---------------------------------------------------------------------------
SELECT '4-A å‰©ä½™æœªä¿®' AS step,
       (SELECT COUNT(*) FROM service_subtask
          WHERE del_flag = 0 AND service_type = '2' AND (campusid IS NULL OR campusid = '')) AS ä»ç©ºé™¢åŒº,
       (SELECT COUNT(*) FROM service_subtask
          WHERE del_flag = 0 AND service_type = '2'
            AND (leavehospitaldistrictcode IS NULL OR leavehospitaldistrictcode = '')) AS ä»ç©ºç—…区;
-- ç”¨éª¨ç§‘中心一病区举例核对:任务总数应 = é¡µé¢å¯è§æ•°ï¼ˆè¯´æ˜Žä¸å†æœ‰"查不到"的)
SELECT '4-B éª¨ç§‘中心一病区核对' AS step,
       COUNT(*) AS ä»»åŠ¡æ€»æ•°,
       SUM(CASE WHEN s.campusid = '30001002' AND s.leavehospitaldistrictcode = '40011020'
                THEN 1 ELSE 0 END) AS é¡µé¢å¯è§
FROM service_subtask s
WHERE s.del_flag = 0 AND s.service_type = '2'
  AND s.leavehospitaldistrictcode = '40011020'
  AND s.endtime >= '2026-07-01 00:00:00' AND s.endtime < '2026-10-01 00:00:00';
-- ---------------------------------------------------------------------------
-- STEP 5:回滚(仅撤销本次改动;只回滚值确实被改过的行)
-- ---------------------------------------------------------------------------
-- UPDATE service_subtask s
--   JOIN bak_service_subtask_campusward_20261029 b ON b.id = s.id
--    SET s.campusid = b.campusid,
--        s.leavehospitaldistrictcode = b.leavehospitaldistrictcode,
--        s.leavehospitaldistrictname = b.leavehospitaldistrictname;
--
-- å›žæ»šåŽå»ºè®®å†è·‘一遍 STEP 4-A ç¡®è®¤å›žåˆ°ä¿®å¤å‰çŠ¶æ€ï¼ˆé™¢åŒº 314 / ç—…区 10)。
-- ç¡®è®¤æ•°æ®ç¨³å®šã€æ— éœ€å›žæ»šåŽï¼Œå†è€ƒè™‘删除备份表:
-- DROP TABLE bak_service_subtask_campusward_20261029;
sql/fix_nhfy_Ëæ·Ã²¡ÇøÍ³Ò»Îª³öÔº²¡Çø_20261029.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,186 @@
-- ============================================================================
-- å—华附一(nhfy):把「出院随访任务的病区」统一为「出院记录的病区」
-- ----------------------------------------------------------------------------
-- å£å¾„(2026-10-29 ä¸šåŠ¡ç¡®è®¤ï¼‰ï¼šå‡ºé™¢è®°å½• pat_med_inhosp.leavehospitaldistrictcode/name æ˜¯å”¯ä¸€æƒå¨ï¼Œ
--   éšè®¿ä»»åŠ¡ service_subtask ä¸€å¾‹å‘它对齐 â€”— ä¸å†æŒ‰æ‚£è€…档案反推、也不用前端传入的值。
--
-- ä¸ºä»€ä¹ˆä¹‹å‰ä¼šä¸ä¸€è‡´ï¼š
--   â‘  æ‰«æç”Ÿæˆï¼ˆå‡ºé™¢éšè®¿ä¸»é“¾è·¯ï¼‰æœ¬æ¥å°±æ˜¯å–出院记录的病区,没问题;
--   â‘¡ ã€Œå†æ¬¡éšè®¿ / æ‰‹å·¥æ–°å¢žã€ï¼ˆ/serviceSubtask/addSubTask、/addSubTaskAgain)是直接按前端
--      ä¼ æ¥çš„字段落库的,前端不带就是空 â†’ è¿™äº›ä»»åŠ¡åœ¨æŒ‰ç—…åŒºè¿‡æ»¤çš„éšè®¿åˆ—è¡¨é‡ŒæŸ¥ä¸åˆ°ï¼›
--   â‘¢ å‡ºé™¢åŽ HIS åˆèƒ½æ›´æ­£ç—…区(转科/转病区),已生成的任务不会跟着变 â†’ å‡ºçް"两边不一样"。
--   ä»£ç ä¾§å·²åœ¨ ServiceSubtaskServiceImpl åŠ  alignDischargeWardFromInhosp() å…œä½ â‘¡ï¼Œ
--   æœ¬è„šæœ¬ä¿®åŽ†å²æ•°æ®ï¼ˆâ‘ ä»¥å¤–çš„ â‘¡â‘¢ ä¸¤ç±»ï¼‰ã€‚
--
-- æœ¬æ¬¡å®žæµ‹ï¼ˆ2026-10-29,smartor_nhfy,del_flag=0 + service_type='2'):
--   Â· éœ€è¦ç»Ÿä¸€çš„:**67 æ¡**
--       - åŽŸæ¥ç—…åŒºä¸ºç©ºçš„ 10 æ¡
--       - åŽŸæ¥ç—…åŒºä¸Žå‡ºé™¢è®°å½•ä¸ä¸€è‡´çš„ 57 æ¡
--   Â· æ— æ³•统一的:0 æ¡ï¼ˆè¿™ 67 æ¡éƒ½èƒ½å®šä½åˆ°å‡ºé™¢è®°å½•,且出院记录的病区都非空)
--   Â· å¦ä¸€æ‰¹ 314 æ¡ã€Œå†æ¬¡éšè®¿ã€è¡Œæ˜¯**院区**(campusid)为空、病区本身有值、inhospid ä¸ºç©ºï¼Œ
--     ä¸Žæœ¬è„šæœ¬æ— å…³ï¼Œç”± fix_nhfy_出院随访任务院区病区回填_20261029.sql å¤„理
--   Â· é¡ºå¸¦å‘现:科室(leaveldeptcode)与出院记录不一致或为空的还有 12 æ¡ï¼Œ
--     æœ¬è„šæœ¬é»˜è®¤**不动**,见 STEP 2B(确认后可一起统一)
--
-- æ‰§è¡Œè®°å½•:
--   **2026-10-29 å·²æ‰§è¡Œ** STEP 1 + STEP 2:备份 67 è¡Œï¼ˆbak_service_subtask_ward_20261029),
--   UPDATE å½±å“ 67 è¡Œï¼›STEP 3-A ã€Œå‰©ä½™ä¸ä¸€è‡´çš„病区」= 0;
--   éª¨ç§‘中心一 å£å¾„对照由 475 / 468 å˜ä¸º **475 / 475**(出院记录按病区 = éšè®¿ä»»åŠ¡æŒ‰ç—…åŒºï¼‰ã€‚
--   STEP 2B(科室)**未执行**,等你确认。
--
-- æ‰§è¡Œé¡ºåºï¼šSTEP 0 é¢„检 â†’ STEP 1 å¤‡ä»½ â†’ STEP 2 ç»Ÿä¸€ç—…区 â†’ STEP 3 æ ¡éªŒ â†’(如需)STEP 4 å›žæ»š
-- æ³¨æ„ï¼šé¢„检的两条 SQL éœ€è¦æŠŠéšè®¿ä»»åŠ¡å’Œå‡ºé™¢è®°å½•å…¨é‡æ¯”å¯¹ï¼Œå®žæµ‹å„çº¦ 1 åˆ†é’Ÿï¼Œå±žæ­£å¸¸ã€‚
-- ============================================================================
-- ---------------------------------------------------------------------------
-- STEP 0:预检(只读)
-- ---------------------------------------------------------------------------
SELECT '0-A å¾…统一病区的总量' AS step, COUNT(*) AS å¾…改条数,
       SUM(CASE WHEN s.leavehospitaldistrictcode IS NULL OR s.leavehospitaldistrictcode = ''
                THEN 1 ELSE 0 END) AS åŽŸç—…åŒºä¸ºç©º,
       SUM(CASE WHEN s.leavehospitaldistrictcode IS NOT NULL AND s.leavehospitaldistrictcode <> ''
                THEN 1 ELSE 0 END) AS åŽŸç—…åŒºä¸ä¸€è‡´
FROM service_subtask s
JOIN pat_med_inhosp i ON i.inhospid = s.inhospid
WHERE s.del_flag = 0 AND s.service_type = '2'
  AND (s.leavehospitaldistrictcode IS NULL OR s.leavehospitaldistrictcode = ''
       OR s.leavehospitaldistrictcode <> i.leavehospitaldistrictcode);
SELECT '0-B æ”¹åŠ¨æ˜ å°„ï¼ˆä»Ž â†’ åˆ°ï¼‰' AS step,
       IFNULL(s.leavehospitaldistrictcode, '(空)') AS åŽŸç—…åŒºç ,
       IFNULL(s.leavehospitaldistrictname, '(空)') AS åŽŸç—…åŒºå,
       i.leavehospitaldistrictcode AS ç»Ÿä¸€ä¸º,
       i.leavehospitaldistrictname AS ç»Ÿä¸€ä¸ºç—…区名,
       COUNT(*) AS æ¡æ•°
FROM service_subtask s
JOIN pat_med_inhosp i ON i.inhospid = s.inhospid
WHERE s.del_flag = 0 AND s.service_type = '2'
  AND (s.leavehospitaldistrictcode IS NULL OR s.leavehospitaldistrictcode = ''
       OR s.leavehospitaldistrictcode <> i.leavehospitaldistrictcode)
GROUP BY s.leavehospitaldistrictcode, s.leavehospitaldistrictname,
         i.leavehospitaldistrictcode, i.leavehospitaldistrictname
ORDER BY æ¡æ•° DESC;
-- å¾…统一的 67 æ¡é‡Œï¼Œå‡ºé™¢è®°å½•自己的病区也是空的(这类无法统一,期望 0)
SELECT '0-C å¾…统一行里出院记录病区为空' AS step, COUNT(*) AS æ— æ³•统一
FROM service_subtask s
JOIN pat_med_inhosp i ON i.inhospid = s.inhospid
WHERE s.del_flag = 0 AND s.service_type = '2'
  AND (s.leavehospitaldistrictcode IS NULL OR s.leavehospitaldistrictcode = ''
       OR s.leavehospitaldistrictcode <> IFNULL(i.leavehospitaldistrictcode, ''))
  AND (i.leavehospitaldistrictcode IS NULL OR i.leavehospitaldistrictcode = '');
-- ä¸åœ¨æœ¬è„šæœ¬èŒƒå›´ï¼šæ²¡æœ‰å‡ºé™¢è®°å½•ID(inhospid ä¸ºç©ºï¼‰ä¸”病区为空的行,
-- æ— æ³•按出院记录统一,见 fix_nhfy_出院随访任务院区病区回填_20261029.sql
SELECT '0-D ä¸åœ¨æœ¬æ¬¡èŒƒå›´' AS step, COUNT(*) AS æ— å‡ºé™¢è®°å½•ID且病区为空
FROM service_subtask s
WHERE s.del_flag = 0 AND s.service_type = '2'
  AND (s.inhospid IS NULL OR s.inhospid = 0)
  AND (s.leavehospitaldistrictcode IS NULL OR s.leavehospitaldistrictcode = '');
-- ---------------------------------------------------------------------------
-- STEP 1:备份(只备份将被修改的行)
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS bak_service_subtask_ward_20261029 AS
SELECT s.id,
       s.leavehospitaldistrictcode,
       s.leavehospitaldistrictname,
       s.leaveldeptcode,
       s.leaveldeptname,
       s.update_time AS old_update_time
FROM service_subtask s
JOIN pat_med_inhosp i ON i.inhospid = s.inhospid
WHERE s.del_flag = 0 AND s.service_type = '2'
  AND (s.leavehospitaldistrictcode IS NULL OR s.leavehospitaldistrictcode = ''
       OR s.leavehospitaldistrictcode <> i.leavehospitaldistrictcode);
SELECT '1 å¤‡ä»½è¡Œæ•°ï¼ˆæœŸæœ› 67)' AS step, COUNT(*) AS backup_rows
FROM bak_service_subtask_ward_20261029;
-- ---------------------------------------------------------------------------
-- STEP 2:统一病区(码 + åç§°ï¼Œä¸€å¾‹ä»¥å‡ºé™¢è®°å½•为准)
--   åªå½±å“ service_type='2'(出院随访);科室见 STEP 2B(默认注释)
-- ---------------------------------------------------------------------------
UPDATE service_subtask s
  JOIN pat_med_inhosp i ON i.inhospid = s.inhospid
   SET s.leavehospitaldistrictcode = i.leavehospitaldistrictcode,
       s.leavehospitaldistrictname = i.leavehospitaldistrictname
 WHERE s.del_flag = 0 AND s.service_type = '2'
   AND i.leavehospitaldistrictcode IS NOT NULL AND i.leavehospitaldistrictcode <> ''
   AND (s.leavehospitaldistrictcode IS NULL OR s.leavehospitaldistrictcode = ''
        OR s.leavehospitaldistrictcode <> i.leavehospitaldistrictcode);
-- ---------------------------------------------------------------------------
-- STEP 2B(可选,**默认注释**):科室一起按出院记录统一(另有 12 æ¡ï¼‰
--   å®žæµ‹ï¼ˆ2026-10-29):12 æ¡é‡Œ 10 æ¡å·²åœ¨ bak_service_subtask_ward_20261029 é‡Œï¼Œ
--   å¦ 2 æ¡ï¼ˆå­ä»»åŠ¡ 33755 é¥¶è²ã€34120 é›·æ˜¥æ¢…)需先用 2B-1 å•独备份。
--   ç¡®è®¤"科室也以出院记录为准"之后再打开执行。
-- ---------------------------------------------------------------------------
-- 2B-1 å¤‡ä»½ç§‘室待改行
-- CREATE TABLE IF NOT EXISTS bak_service_subtask_dept_20261029 AS
-- SELECT s.id, s.leaveldeptcode, s.leaveldeptname, s.update_time AS old_update_time
-- FROM service_subtask s
-- JOIN pat_med_inhosp i ON i.inhospid = s.inhospid
-- WHERE s.del_flag = 0 AND s.service_type = '2'
--   AND i.leaveldeptcode IS NOT NULL AND i.leaveldeptcode <> ''
--   AND (s.leaveldeptcode IS NULL OR s.leaveldeptcode = '' OR s.leaveldeptcode <> i.leaveldeptcode);
--
-- 2B-2 æ›´æ–°ç§‘室
-- UPDATE service_subtask s
--   JOIN pat_med_inhosp i ON i.inhospid = s.inhospid
--    SET s.leaveldeptcode = i.leaveldeptcode,
--        s.leaveldeptname = i.leaveldeptname
--  WHERE s.del_flag = 0 AND s.service_type = '2'
--    AND i.leaveldeptcode IS NOT NULL AND i.leaveldeptcode <> ''
--    AND (s.leaveldeptcode IS NULL OR s.leaveldeptcode = ''
--         OR s.leaveldeptcode <> i.leaveldeptcode);
--
-- 2B-3 å›žæ»šç§‘室(如需)
-- UPDATE service_subtask s
--   JOIN bak_service_subtask_dept_20261029 b ON b.id = s.id
--    SET s.leaveldeptcode = b.leaveldeptcode,
--        s.leaveldeptname = b.leaveldeptname;
-- ---------------------------------------------------------------------------
-- STEP 3:校验
-- ---------------------------------------------------------------------------
-- 3-A åº”为 0
SELECT '3-A å‰©ä½™ä¸ä¸€è‡´çš„病区' AS step, COUNT(*) AS ä»ä¸ä¸€è‡´
FROM service_subtask s
JOIN pat_med_inhosp i ON i.inhospid = s.inhospid
WHERE s.del_flag = 0 AND s.service_type = '2'
  AND i.leavehospitaldistrictcode IS NOT NULL AND i.leavehospitaldistrictcode <> ''
  AND (s.leavehospitaldistrictcode IS NULL OR s.leavehospitaldistrictcode = ''
       OR s.leavehospitaldistrictcode <> i.leavehospitaldistrictcode);
-- 3-B éª¨ç§‘中心一病区/科室 ä¸¤ä¸ªå£å¾„对数(统一后病区任务数应等于本病区出院记录数)
SELECT '3-B éª¨ç§‘中心一 å£å¾„对照' AS step,
       (SELECT COUNT(*) FROM (
          SELECT b.inhospid FROM pat_med_inhosp b JOIN pat_archive a ON a.id = b.patid
          WHERE a.del_flag='0' AND b.del_flag='0' AND a.idcardno IS NOT NULL AND b.inhospstate='1'
            AND b.leavehospitaldistrictcode = '40011020'
            AND b.endtime >= '2026-07-01' AND b.endtime < '2026-10-01'
          GROUP BY b.inhospid) x) AS å‡ºé™¢è®°å½•_按病区,
       (SELECT COUNT(*) FROM service_subtask s WHERE s.del_flag=0 AND s.service_type='2'
          AND s.leavehospitaldistrictcode = '40011020'
          AND s.endtime >= '2026-07-01' AND s.endtime < '2026-10-01') AS éšè®¿ä»»åŠ¡_按病区,
       (SELECT COUNT(*) FROM (
          SELECT b.inhospid FROM pat_med_inhosp b JOIN pat_archive a ON a.id = b.patid
          WHERE a.del_flag='0' AND b.del_flag='0' AND a.idcardno IS NOT NULL AND b.inhospstate='1'
            AND b.leaveldeptcode = '40011021'
            AND b.endtime >= '2026-07-01' AND b.endtime < '2026-10-01'
          GROUP BY b.inhospid) x) AS å‡ºé™¢è®°å½•_按科室,
       (SELECT COUNT(*) FROM service_subtask s WHERE s.del_flag=0 AND s.service_type='2'
          AND s.leaveldeptcode = '40011021'
          AND s.endtime >= '2026-07-01' AND s.endtime < '2026-10-01') AS éšè®¿ä»»åŠ¡_按科室;
-- ---------------------------------------------------------------------------
-- STEP 4:回滚(仅撤销本次改动)
-- ---------------------------------------------------------------------------
-- UPDATE service_subtask s
--   JOIN bak_service_subtask_ward_20261029 b ON b.id = s.id
--    SET s.leavehospitaldistrictcode = b.leavehospitaldistrictcode,
--        s.leavehospitaldistrictname = b.leavehospitaldistrictname;
--
-- å›žæ»šåŽè·‘ STEP 0-A,应回到 67 æ¡å¾…改。
-- ç¡®è®¤æ— éœ€å›žæ»šåŽå†åˆ å¤‡ä»½è¡¨ï¼šDROP TABLE bak_service_subtask_ward_20261029;
sql/fix_service_subtask_campusid_20260924.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,108 @@
-- =============================================================================
-- çœç«‹åŒå¾·ï¼šæ»¡æ„åº¦å­ä»»åŠ¡ campusid ä¸ºç©ºçš„æ•°æ®ä¿®å¤ï¼ˆ2026-09-24)
--
-- èƒŒæ™¯ï¼š/sltdMydTotalByScore(满意度统计)等接口带 @AddOrgId,会按登录用户的
--      campusid(admin = 30001002)过滤 service_subtask,campusid ä¸º NULL çš„子任务
--      ä¼šè¢«é™é»˜ä¸¢æŽ‰ï¼Œå¯¼è‡´é¡µé¢ã€Œå„服务项目满意度统计」和 service_subtask_detail æ˜Žç»†
--      å¯¹ä¸ä¸Šï¼ˆå¦‚「挂号/就诊/检验/检查等环节等候时间」页面 6 é¡¹ vs æ˜Žç»† 13 è¡Œï¼‰ã€‚
--
-- çŽ°çŠ¶ï¼šorgid=20001001 ä¸‹ service_type IN ('6','14') çš„子任务共 767 æ¡ï¼Œ
--      campusid='30001002' 642 æ¡ã€'30001003' 1 æ¡ã€NULL 124 æ¡ï¼ˆæœ¬æ¬¡è¦è¡¥çš„就是这 124 æ¡ï¼‰ã€‚
--      è¿™ 124 æ¡åˆ†å¸ƒåœ¨å’Œ 30001002 ç›¸åŒçš„病区、相同的时间区间(2025-12-23 ~ 2026-09-12),
--      å±žäºŽå†™åº“时漏写院区,不是别的院区的数据;service_task.campusid ä¸€å¾‹æ˜¯ '1',不能作为院区来源。
--
-- æ‰§è¡Œé¡ºåºï¼š1) çœ‹æ¸…单 â†’ 2) å¤‡ä»½ â†’ 3) æ›´æ–° â†’ 4) æ ¡éªŒ
-- æ³¨æ„ï¼šç”Ÿäº§åº“执行前先备份,且请确认这些子任务确实都属于 30001002 é™¢åŒºã€‚
-- =============================================================================
-- -----------------------------------------------------------------------------
-- 1) å¾…补清单(124 æ¡ï¼‰ï¼šç¡®è®¤ç—…区/科室是否都属于 30001002 é™¢åŒº
-- -----------------------------------------------------------------------------
SELECT s.id,
       s.taskid,
       s.service_type,
       s.sendstate,
       s.deptcode,
       s.deptname,
       s.leavehospitaldistrictcode,
       s.leavehospitaldistrictname,
       date_format(s.create_time, '%Y-%m-%d %H:%i:%s') AS create_time,
       date_format(s.finishtime, '%Y-%m-%d %H:%i:%s')  AS finishtime
FROM service_subtask s
WHERE s.del_flag = 0
  AND s.service_type IN ('6', '14')
  AND s.campusid IS NULL
ORDER BY s.leavehospitaldistrictcode, s.id;
-- ç—…区/科室维度汇总(判断院区归属用;病区/科室都为空的 40 æ¥æ¡éœ€è¦äººå·¥ç¡®è®¤ï¼‰
SELECT s.leavehospitaldistrictcode AS district,
       s.deptcode,
       s.service_type,
       COUNT(*) AS campus_null_cnt
FROM service_subtask s
WHERE s.del_flag = 0
  AND s.service_type IN ('6', '14')
  AND s.campusid IS NULL
GROUP BY s.leavehospitaldistrictcode, s.deptcode, s.service_type
ORDER BY campus_null_cnt DESC;
-- -----------------------------------------------------------------------------
-- 2) å¤‡ä»½ï¼ˆåªå¤‡ä»½è¦æ”¹çš„字段)
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS bak_service_subtask_campusid_20260924 AS
SELECT id, campusid, update_time
FROM service_subtask
WHERE del_flag = 0
  AND service_type IN ('6', '14')
  AND campusid IS NULL;
SELECT COUNT(*) AS backup_rows FROM bak_service_subtask_campusid_20260924;
-- -----------------------------------------------------------------------------
-- 3) æ›´æ–°ï¼ˆç¡®è®¤æ¸…单无误后执行;预期影响 124 è¡Œï¼‰
-- -----------------------------------------------------------------------------
UPDATE service_subtask
SET campusid    = '30001002',
    update_time = NOW()
WHERE del_flag = 0
  AND service_type IN ('6', '14')
  AND campusid IS NULL;
-- å¦‚果病区/科室为空的那些行无法确定院区,只想补有明确病区的,用下面这条:
-- UPDATE service_subtask
-- SET campusid = '30001002', update_time = NOW()
-- WHERE del_flag = 0 AND service_type IN ('6','14') AND campusid IS NULL
--   AND leavehospitaldistrictcode IS NOT NULL AND leavehospitaldistrictcode <> '';
-- -----------------------------------------------------------------------------
-- 4) æ ¡éªŒï¼šé™¢åŒºåˆ†å¸ƒ + å•车核对「挂号/就诊/检验/检查等环节等候时间」
-- -----------------------------------------------------------------------------
SELECT s.campusid, s.service_type, COUNT(*) AS c
FROM service_subtask s
WHERE s.del_flag = 0 AND s.service_type IN ('6', '14')
GROUP BY s.campusid, s.service_type
ORDER BY c DESC;
-- é¢„期(补齐后,admin ç™»å½•页面)= å¾ˆæ»¡æ„ 1、不满意 3、很不满意 4、很满意(随访任务) ä¸è®¡å…¥
-- é¡µé¢å£å¾„:sendstate=6 + service_type 6/14 + finishtime åœ¨æ‰€é€‰åŒºé—´ + åŒä¸€å­ä»»åŠ¡åŒä¸€é¢˜åªå–æœ€æ–°ä¸€æ¡ + æœªä½œç­”不计
SELECT d.asrtext AS optionresult, COUNT(*) AS page_cnt
FROM service_subtask_detail d
INNER JOIN service_subtask s ON s.id = d.sub_id
WHERE d.questiontext = '挂号/就诊/检验/检查等环节等候时间'
  AND d.del_flag = 0
  AND s.del_flag = 0
  AND s.sendstate = 6
  AND s.service_type IN ('6', '14')
  AND s.campusid = '30001002'
  AND date_format(s.finishtime, '%y%m%d') >= '260625'
  AND date_format(s.finishtime, '%y%m%d') <= '260924'
  AND d.asrtext IS NOT NULL AND TRIM(d.asrtext) <> ''
  AND d.id = (SELECT MAX(d2.id) FROM service_subtask_detail d2
              WHERE d2.sub_id = d.sub_id AND d2.questiontext <=> d.questiontext AND d2.del_flag = '0')
GROUP BY d.asrtext
ORDER BY page_cnt DESC;
-- æ”¹é€ å‰ï¼šå¾ˆä¸æ»¡æ„ 4、不满意 1、很满意 1(页面即此结果)
-- è¡¥é½åŽï¼šå¾ˆä¸æ»¡æ„ 4、不满意 4、很满意 1  â† 9 é¡¹ï¼›æ˜Žç»†è¡¨é‡Œå¦å¤– 4 è¡Œä»ä¸ä¼šè¿›é¡µé¢ï¼ŒåŽŸå› è§ä¸‹ â†“
--   taskid 144(一般)   finishtime 2026-06-23,早于页面所选区间 2026-06-25
--   taskid 152(很不满意) / 156(很满意)  service_type=2(出院随访任务),不是满意度
--   taskid 175(空)     æœªä½œç­”(asrtext ä¸ºç©ºï¼‰ï¼Œæ—¢ä¸ç®—选项也不进分母
sql/fix_service_subtask_campusid_20260928.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,80 @@
-- ============================================================================
-- çœç«‹åŒå¾·ï¼šæ»¡æ„åº¦å­ä»»åŠ¡ campusid ä¸ºç©ºçš„æ•°æ®ä¿®å¤ï¼ˆ2026-09-28,已执行)
--
-- ã€çŽ°è±¡ã€‘/sltdMydTotalByScore、/satisfactionGraph ç­‰æŽ¥å£æŸ¥ä¸åˆ°æ•°æ®ï¼Œä½†æ‰‹å·¥æŸ¥åº“有数据。
--
-- ã€æ ¹å› ã€‘
--   1) å†™åº“处 ServiceSubtaskAnswerServiceImpl æž„造满意度子任务(service_type 6/14)时
--      dept/ward/orgid éƒ½å†™äº†ï¼Œå”¯ç‹¬æ¼å†™ campusid â†’ è½åº“为 NULL;
--      ï¼ˆåŒé¡¹ç›®å…¶å®ƒå†™åº“处都有写:PatMedInhospServiceImpl:1146 å–住院记录院区、
--        PatMedOuthospServiceImpl:486 å–任务院区,本次已按同样思路给该处补上)
--   2) è¿™äº›ç»Ÿè®¡æŽ¥å£å¸¦ @AddOrgId(campusField="campusid"),SetOrgIdAspect ä¼šæŠŠç™»å½•用户的院区
--      æ³¨å…¥æŸ¥è¯¢æ¡ä»¶ï¼›ServiceSubtaskMapper.selectServiceSubtaskList é‡Œæ˜¯
--      `and campusid = #{campusid}`,NULL ä¸åŒ¹é…ä»»ä½•值 â†’ è¿™äº›å­ä»»åŠ¡è¢«é™é»˜ä¸¢æŽ‰ï¼Œ
--      ä¼ ç»™æ˜Žç»†ç»Ÿè®¡ï¼ˆstatQuestionOptionBySubtaskIds)的子任务 id é‡Œæ²¡æœ‰å®ƒä»¬ â†’ é¡µé¢ç©ºã€‚
--
-- ã€ä¿®å¤å‰å®žæµ‹ã€‘service_type IN ('6','14') ä¸” campusid IS NULL å…± 122 è¡Œï¼š
--   - 58 è¡Œæ‚£è€…档案 pat_archive.campusid æœ‰å€¼ï¼ˆ57 è¡Œ 30001002、1 è¡Œ 30001004)
--   - 64 è¡Œæ‚£è€…档案院区也为空(其中多数病区/科室为 "1"/"2" æˆ–空,属投诉建议流程写库时字段缺失)
--   - pat_med_inhosp.campusid åœ¨è¿™ 122 è¡Œä¸Šå…¨ä¸ºç©ºï¼Œä¸èƒ½ä½œä¸ºé™¢åŒºæ¥æº
--
-- ã€æ‰§è¡Œç»“果(2026-09-28)】
--   å¤‡ä»½è¡¨ bak_service_subtask_campusid_20260928 = 122 è¡Œ
--   A) æŒ‰æ‚£è€…档案院区补:58 è¡Œ
--   B) æ¡£æ¡ˆä¹Ÿä¸ºç©ºçš„æŒ‰åŽŸ 20260924 è„šæœ¬ç»“è®ºå…œåº• 30001002:64 è¡Œ
--   æ ¡éªŒï¼šservice_type IN ('6','14') å·²æ—  campusid ä¸º NULL çš„行
-- ============================================================================
-- ----------------------------------------------------------------------------
-- ã€1】备份(已执行,仅备份被改字段,用于回滚)
-- ----------------------------------------------------------------------------
-- CREATE TABLE IF NOT EXISTS bak_service_subtask_campusid_20260928 AS
-- SELECT id, campusid, update_time FROM service_subtask
-- WHERE del_flag = 0 AND service_type IN ('6','14') AND campusid IS NULL;
-- ----------------------------------------------------------------------------
-- ã€2】更新(已执行)
-- ----------------------------------------------------------------------------
-- A) æ‚£è€…档案里有院区的,按患者归属院区补(58 è¡Œï¼‰
-- UPDATE service_subtask s
--     JOIN pat_archive a ON a.id = s.patid
-- SET s.campusid = a.campusid, s.update_time = NOW()
-- WHERE s.del_flag = 0 AND s.service_type IN ('6','14')
--   AND s.campusid IS NULL AND a.campusid IS NOT NULL AND a.campusid <> '';
-- B) æ‚£è€…档案也没有院区的,兜底 30001002(64 è¡Œï¼›ä¸ŽåŽŸ fix_service_subtask_campusid_20260924.sql ç»“论一致)
-- UPDATE service_subtask SET campusid = '30001002', update_time = NOW()
-- WHERE del_flag = 0 AND service_type IN ('6','14') AND campusid IS NULL;
-- ----------------------------------------------------------------------------
-- ã€3】校验
-- ----------------------------------------------------------------------------
SELECT IFNULL(campusid,'(仍为空)') AS campusid, service_type, COUNT(*) AS cnt
FROM service_subtask
WHERE del_flag = 0 AND service_type IN ('6','14')
GROUP BY campusid, service_type
ORDER BY cnt DESC;
-- æ‰§è¡ŒåŽé¢„期:30001002/6=709、30001002/14=56、30001004/14=1、30001003/14=1,无 NULL
-- ----------------------------------------------------------------------------
-- ã€4】回滚(如需撤销,先回滚字段值,再删备份表;仅恢复被本次改动的 122 è¡Œï¼‰
-- ----------------------------------------------------------------------------
-- UPDATE service_subtask s
--     JOIN bak_service_subtask_campusid_20260928 b ON b.id = s.id
-- SET s.campusid = b.campusid, s.update_time = b.update_time;
-- DROP TABLE bak_service_subtask_campusid_20260928;
-- ============================================================================
-- ã€é‡è¦æé†’】
-- 1) åªè¡¥æ•°æ®ä¸å¤Ÿï¼šå†™åº“处已同步修(ServiceSubtaskAnswerServiceImpl è¡¥ setCampusid),
--    è¯¥æ”¹åŠ¨éœ€è¦é‡æ–°å‘å¸ƒåŽç«¯æ‰ç”Ÿæ•ˆï¼Œå¦åˆ™æ–°äº§ç”Ÿçš„æ»¡æ„åº¦å­ä»»åŠ¡è¿˜ä¼šæ˜¯ NULL。
-- 2) é‚£ 1 è¡ŒæŒ‰æ‚£è€…档案补成了 30001004(不是 30001002),如确认该病区实际属本部,
--    å¯å•独改回:SELECT * FROM bak_service_subtask_campusid_20260928 WHERE id = ?;
-- 3) ç—…区/科室为空、档案也无院区的行一律按 30001002 å…œåº•;若后续要做多院区口径,
--    å»ºè®®æŠŠ leavehospitaldistrictcode/deptcode ä¹Ÿä¸€èµ·æ²»ç†ã€‚
-- ============================================================================
sql/index_service_subtask_detail_unique_20261021.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,124 @@
-- ============================================================================
-- (sub_id, scriptid) åŽ»é‡ + å”¯ä¸€ç´¢å¼• â€”— æ ¹æ²»"同一患者同一题堆出多份重复明细"
--                                    å—华附一(nhfy)2026-10-21
--
-- ã€ä¸ºä»€ä¹ˆè¦åšã€‘
--   ä¿å­˜ç­”案/补默认值的 upsert èµ° isExistServiceSubtaskDetail,历史上只看 del_flag=0;
--   æ˜Žç»†ä¸€æ—¦è¢«é€»è¾‘删除就查不到 â†’ ä¸‹ä¸€æ¬¡ä¿å­˜å¿…ç„¶ INSERT,副本越积越多
--   ï¼ˆ2026-09-08 é‚£ 10 ä½æ‚£è€…各堆了 2~6 ä»½ï¼Œç­”案完全相同)。
--   ä»£ç ä¾§å·²æ”¹ä¸ºã€Œå¿½ç•¥åˆ é™¤æ ‡è®°å–最新一行 + æ›´æ–°åŽ reviveServiceSubtaskDetail å¤æ´»ã€ï¼Œ
--   æœ¬è„šæœ¬å†ä»Žæ•°æ®åº“å±‚åŠ ä¸€é“å”¯ä¸€çº¦æŸå…œåº•ã€‚
--
-- ã€çŽ°çŠ¶ï¼ˆ2026-10-21 å®žæµ‹ smartor_nhfy)】
--   æ˜Žç»†æ€»æ•° 346,628;sub_id / scriptid å‡æ—  NULL
--   é‡å¤ç»„(同 sub_id+scriptid å¤šäºŽ 1 è¡Œï¼‰ï¼š5,389 ç»„,涉及 12,680 è¡Œ
--   è‹¥"每组只留 1 è¡Œ",需要物理删除 7,291 è¡Œï¼ˆç»å¤§å¤šæ•°æ˜¯ del_flag=1 çš„历史副本)
--
-- ã€æ‰§è¡Œé¡ºåºã€‘建议低峰期,按 0 â†’ 4 é¡ºåºæ‰§è¡Œï¼›ç¬¬ 3 æ­¥æ˜¯ç‰©ç†åˆ é™¤ï¼ŒåŠ¡å¿…å…ˆè·‘ç¬¬ 2 æ­¥å¤‡ä»½
-- ã€å›žæ»šã€‘第 5 æ­¥ï¼›åˆ æŽ‰çš„行走 bak_ssd_dup_20261021 è¿˜åŽŸ
-- ============================================================================
-- ----------------------------------------------------------------------------
-- ã€0】评估(只读,随时可跑)
-- ----------------------------------------------------------------------------
SELECT COUNT(*)                                   AS dup_groups,
       SUM(rows_cnt)                              AS rows_in_dup_groups,
       SUM(rows_cnt - 1)                          AS rows_to_delete_if_keep_one
FROM (SELECT sub_id, scriptid, COUNT(*) AS rows_cnt
      FROM service_subtask_detail
      WHERE sub_id IS NOT NULL AND scriptid IS NOT NULL
      GROUP BY sub_id, scriptid
      HAVING COUNT(*) > 1) x;
-- é‡å¤æœ€å¤šçš„ 10 ç»„,看看是不是都长一个样
SELECT sub_id, scriptid, COUNT(*) AS rows_cnt,
       SUM(del_flag = 0) AS live_cnt,
       COUNT(DISTINCT COALESCE(asrtext, '')) AS distinct_answers
FROM service_subtask_detail
GROUP BY sub_id, scriptid
HAVING COUNT(*) > 1
ORDER BY rows_cnt DESC
LIMIT 10;
-- ----------------------------------------------------------------------------
-- ã€1】备份"将被删除"的行(约 7,291 è¡Œï¼‰
--      ä¿ç•™è§„则:同一 (sub_id, scriptid) ä¿ç•™ 1 è¡Œ â€”— ä¼˜å…ˆ del_flag=0,其次 id æœ€å¤§
-- ----------------------------------------------------------------------------
-- DROP TABLE IF EXISTS bak_ssd_dup_20261021;
-- CREATE TABLE bak_ssd_dup_20261021 AS
-- SELECT d.*
-- FROM service_subtask_detail d
-- JOIN (SELECT sub_id, scriptid,
--              SUBSTRING_INDEX(GROUP_CONCAT(id ORDER BY (del_flag = 0) DESC, id DESC), ',', 1) AS keep_id
--       FROM service_subtask_detail
--       WHERE sub_id IS NOT NULL AND scriptid IS NOT NULL
--       GROUP BY sub_id, scriptid
--       HAVING COUNT(*) > 1) k
--   ON k.sub_id = d.sub_id AND k.scriptid = d.scriptid
-- WHERE d.id <> k.keep_id;
-- SELECT COUNT(*) FROM bak_ssd_dup_20261021;   -- é¢„期 â‰ˆ 7291
-- ----------------------------------------------------------------------------
-- ã€2】备份自检(确认备份表里没有"每组都应该保留的那一行")
-- ----------------------------------------------------------------------------
-- SELECT COUNT(*) AS wrong_rows
-- FROM bak_ssd_dup_20261021 b
-- JOIN (SELECT sub_id, scriptid,
--              SUBSTRING_INDEX(GROUP_CONCAT(id ORDER BY (del_flag = 0) DESC, id DESC), ',', 1) AS keep_id
--       FROM service_subtask_detail
--       GROUP BY sub_id, scriptid) k
--   ON k.sub_id = b.sub_id AND k.scriptid = b.scriptid
-- WHERE b.id = k.keep_id;   -- é¢„期 0
-- ----------------------------------------------------------------------------
-- ã€3】物理删除重复行(每组保留 1 è¡Œï¼‰
-- ----------------------------------------------------------------------------
-- DELETE d
-- FROM service_subtask_detail d
-- JOIN (SELECT sub_id, scriptid,
--              SUBSTRING_INDEX(GROUP_CONCAT(id ORDER BY (del_flag = 0) DESC, id DESC), ',', 1) AS keep_id
--       FROM service_subtask_detail
--       WHERE sub_id IS NOT NULL AND scriptid IS NOT NULL
--       GROUP BY sub_id, scriptid
--       HAVING COUNT(*) > 1) k
--   ON k.sub_id = d.sub_id AND k.scriptid = d.scriptid
-- WHERE d.id <> k.keep_id;
-- ----------------------------------------------------------------------------
-- ã€4】加唯一索引
--      âš ï¸ å‰æï¼šç¬¬ 3 æ­¥å·²è·‘过(否则必然 ORA/1062 å¤±è´¥ï¼‰
--      âš ï¸ 346k è¡Œçš„ ADD UNIQUE ä¼šçŸ­æ—¶é”è¡¨ï¼Œå»ºè®®ä½Žå³°æ‰§è¡Œï¼›
--         å¦‚果生产版本 MySQL æ”¯æŒ online DDL,可用 ALGORITHM=INPLACE, LOCK=NONE
-- ----------------------------------------------------------------------------
-- ALTER TABLE service_subtask_detail
--     ADD UNIQUE KEY uk_ssd_sub_script (sub_id, scriptid),
--     ALGORITHM = INPLACE, LOCK = NONE;
-- æ ¡éªŒ
-- SHOW INDEX FROM service_subtask_detail WHERE Key_name = 'uk_ssd_sub_script';
-- SELECT COUNT(*) AS dup_groups_should_be_0 FROM (
--   SELECT sub_id, scriptid FROM service_subtask_detail
--   GROUP BY sub_id, scriptid HAVING COUNT(*) > 1) x;
-- ----------------------------------------------------------------------------
-- ã€5】回滚
-- ----------------------------------------------------------------------------
-- ALTER TABLE service_subtask_detail DROP INDEX uk_ssd_sub_script;
-- INSERT INTO service_subtask_detail SELECT * FROM bak_ssd_dup_20261021;
-- DROP TABLE bak_ssd_dup_20261021;
-- ============================================================================
-- ã€åŠ ç´¢å¼•ä¹‹åŽå¿…é¡»æ³¨æ„ã€‘
--   å”¯ä¸€ç´¢å¼•生效后,并发保存同一 (sub_id, scriptid) æ—¶"后到的那一条 INSERT"会抛
--   DuplicateKeyException。代码侧已经做了两件事降低风险:
--     â‘  isExistServiceSubtaskDetail æ”¹ä¸º"忽略 del_flag å–最新一行"→ æ­£å¸¸ä¿å­˜èµ° UPDATE,不会 INSERT;
--     â‘¡ /smartor/subtaskAnswer/saveQuestionAnswer åŠ äº† Redis å¹‚等占位(60 ç§’,同一 pid+tid+subid åªå¤„理一次)。
--   ä½†å¦‚果将来还有别的写入路径直接 INSERT æ˜Žç»†ï¼Œå»ºè®®åœ¨æ’入处 catch DuplicateKeyException åŽè½¬ UPDATE。
-- ============================================================================
sql/ivr_assort_columns_20260928.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,148 @@
-- ============================================================================
-- smartor_sltd æ•°æ®åº“:语音问题相关表的列名对齐 / æ–°å¢žåˆ—脚本
-- ç”Ÿæˆæ—¥æœŸ: 20260928
-- ç›®æ ‡åº“  : smartor_sltd(ruoyi-admin/src/main/resources/application-druid.yml,profile=druid)
-- ç›®çš„    : è®© ivr_liba_template_script / ivr_task_template_script çš„列名与
--           IvrLibaTemplateScriptMapper.xml / IvrTaskTemplateScriptMapper.xml ä¸€è‡´
--           ï¼ˆè¿™ä¸¤ä¸ª mapper é‡ŒæŸ¥çš„æ˜¯ script_assortname、script_assortid)
--
-- ã€æ‰§è¡Œå‰å®žæµ‹çŽ°çŠ¶ï¼ˆ20260928,smartor_sltd)】
--   ivr_liba_script_assort   : 35 è¡Œ
--       index_assort_name varchar(255) NOT NULL  æ³¨é‡Šâ€œæŒ‡æ ‡åˆ†ç±»åç§°â€
--       type              varchar(255) NULL      â† è¯¥åˆ—【已存在】,35 è¡Œå…¨ä¸º NULL,无注释
--   ivr_liba_template_script : 121 è¡Œï¼ˆdel_flag=0 çš„ 54 è¡Œï¼‰
--       categoryName varchar(50) NULL  æ³¨é‡Šâ€œç±»åˆ«åâ€ï¼Œ121 è¡Œå…¨ä¸º NULL
--       æ²¡æœ‰ assortid åˆ—,也没有 script_assortid åˆ—
--   ivr_task_template_script : 1221 è¡Œ
--       categoryName varchar(50) NULL  æ³¨é‡Šâ€œç±»åˆ«åâ€ï¼Œä»… 26 è¡Œéžç©º
--       æ²¡æœ‰ assortid åˆ—,也没有 script_assortid åˆ—
--
--   â‡’ ç¬¬ 3 é¡¹ï¼ˆassortid â†’ script_assortid)在这两个表里都不是“改名”,
--     ä¸¤ä¸ªè¡¨æ ¹æœ¬æ²¡æœ‰ assortid åˆ—,只能“新增列 + å›žå¡«â€ã€‚
--   â‡’ å›žå¡«æ¥æºç”¨çˆ¶æ¨¡æ¿çš„ assortid:
--        ivr_liba_template_script.templateid â†’ ivr_liba_template.id.assortid        ï¼ˆ121/121 è¡Œå¯å›žå¡«ï¼‰
--        ivr_task_template_script.templateID â†’ ivr_task_template.id.assortid        ï¼ˆ358/1221 è¡Œå¯å›žå¡«ï¼›
--          å¦ 825 è¡Œ templateID åœ¨ ivr_task_template ä¸­ä¸å­˜åœ¨ã€38 è¡Œ templateID ä¸º NULL,保持 NULL)
--   â‡’ ä¾èµ–检查:本库没有引用这两张表的存储过程 / è§†å›¾ / è§¦å‘器(已查
--      information_schema.ROUTINES / VIEWS / TRIGGERS,均为 0 æ¡ï¼‰ï¼Œæ”¹åˆ—名不影响 DB ä¾§å¯¹è±¡ã€‚
--   â‡’ ä»£ç ä¾§å½±å“ï¼šIvrLibaTemplateScriptMapper、IvrTaskTemplateScriptMapper æœ¬æ¥å°±åœ¨ç”¨
--      script_assortname / script_assortid,改完后这两个 mapper æ‰çœŸæ­£èƒ½æŸ¥é€šï¼›
--      ä¸¤ä¸ª mapper éƒ½æ²¡æœ‰å¼•用 categoryName,所以改名不会影响现有功能。
-- ============================================================================
-- ----------------------------------------------------------------------------
-- ã€0】执行前检查(确认与上面的实测一致,正常应看到 categoryName、且没有 assort åˆ—)
-- ----------------------------------------------------------------------------
-- SHOW COLUMNS FROM ivr_liba_template_script LIKE '%assort%';
-- SHOW COLUMNS FROM ivr_liba_template_script LIKE 'categoryName';
-- SHOW COLUMNS FROM ivr_task_template_script LIKE 'categoryName';
-- SHOW COLUMNS FROM ivr_liba_script_assort LIKE 'type';
-- SELECT COUNT(*) FROM ivr_liba_template_script;      -- 121
-- SELECT COUNT(*) FROM ivr_task_template_script;      -- 1221
-- ----------------------------------------------------------------------------
-- ã€1】categoryName â†’ script_assortname(两张表,保留原类型 varchar(50) NULL å’ŒåŽŸæ³¨é‡Šâ€œç±»åˆ«åâ€ï¼‰
--      121 / 1221 è¡Œçš„小表,秒级完成;CHANGE COLUMN åªæ”¹åå¯èµ° INPLACE,不阻塞读写。
-- ----------------------------------------------------------------------------
ALTER TABLE ivr_liba_template_script
    CHANGE COLUMN categoryName script_assortname varchar(50) NULL COMMENT '类别名',
    ALGORITHM = INPLACE,
    LOCK = NONE;
ALTER TABLE ivr_task_template_script
    CHANGE COLUMN categoryName script_assortname varchar(50) NULL COMMENT '类别名',
    ALGORITHM = INPLACE,
    LOCK = NONE;
-- ----------------------------------------------------------------------------
-- ã€2】ivr_liba_script_assort.type
--      è¯¥åˆ—本来就有(varchar(255),无注释,35 è¡Œå…¨ NULL),这里只按需求补注释并把长度改成 100。
--      æ³¨æ„ï¼švarchar(255) â†’ varchar(100) å±žäºŽç±»åž‹æ”¶çª„,需要重建表,不能用 INPLACE/LOCK=NONE;
--      æœ¬è¡¨åªæœ‰ 35 è¡Œï¼Œç›´æŽ¥æ‰§è¡Œå³å¯ï¼ˆè‹¥ä¸æƒ³åŠ¨é•¿åº¦ï¼Œåªæ‰§è¡Œå¸¦ COMMENT çš„ MODIFY ä¹Ÿä¸€æ ·éœ€è¦é‡å»ºï¼Œ
--      å› ä¸ºæ”¹æ³¨é‡Šæœ¬èº«å°±æ˜¯ COPY)。若只想加注释、不改长度,把 varchar(100) æ¢æˆ varchar(255)。
-- ----------------------------------------------------------------------------
ALTER TABLE ivr_liba_script_assort
    MODIFY COLUMN type varchar(100) NULL COMMENT '问题类型(例如,门诊满意度:mzmyd     ä½é™¢æ»¡æ„åº¦ï¼šzymyd  ...)';
-- ----------------------------------------------------------------------------
-- ã€3】新增 script_assortid åˆ— + ç”¨çˆ¶æ¨¡æ¿çš„ assortid å›žå¡«
--      å¤‡æ³¨é‡ŒåŽŸæœ¬å†™çš„â€œivr_liba_script_ssort”应为“ivr_liba_script_assort”,下面按正确表名写。
-- ----------------------------------------------------------------------------
ALTER TABLE ivr_liba_template_script
    ADD COLUMN script_assortid int(11) NULL COMMENT '语音问题类别ID(对应的是ivr_liba_script_assort)',
    ALGORITHM = INPLACE,
    LOCK = NONE;
ALTER TABLE ivr_task_template_script
    ADD COLUMN script_assortid int(11) NULL COMMENT '语音问题类别ID(对应的是ivr_liba_script_assort)',
    ALGORITHM = INPLACE,
    LOCK = NONE;
-- å›žå¡«ï¼š121/121 è¡Œ
UPDATE ivr_liba_template_script s
    JOIN ivr_liba_template t ON t.id = s.templateid
SET s.script_assortid = t.assortid;
-- å›žå¡«ï¼š358/1221 è¡Œï¼ˆå…¶ä½™ä¸ºå­¤å„¿æ•°æ®ï¼Œä¿æŒ NULL)
UPDATE ivr_task_template_script s
    JOIN ivr_task_template t ON t.id = s.templateID
SET s.script_assortid = t.assortid;
-- ----------------------------------------------------------------------------
-- ã€4】可选:把 script_assortname ä¹Ÿè¡¥ä¸Šï¼ˆå½“前两表的 categoryName åŸºæœ¬å…¨æ˜¯ NULL,
--      æ”¹ååŽ script_assortname ä»æ˜¯ NULL,而 satisfactionGraph çš„语音分支是按
--      script_assortname åˆ†ç»„的,不补的话语音分类仍然出不来)
--      æ•°æ®æºï¼šçˆ¶æ¨¡æ¿ assortid â†’ ivr_liba_script_assort.index_assort_name
--      ç¡®è®¤è¦è¡¥å†æ‰§è¡Œè¿™æ®µï¼›ä¸éœ€è¦å°±è·³è¿‡ã€‚
-- ----------------------------------------------------------------------------
-- UPDATE ivr_liba_template_script s
--     JOIN ivr_liba_template t ON t.id = s.templateid
--     JOIN ivr_liba_script_assort a ON a.id = t.assortid
-- SET s.script_assortname = a.index_assort_name
-- WHERE s.script_assortname IS NULL OR s.script_assortname = '';
--
-- UPDATE ivr_task_template_script s
--     JOIN ivr_task_template t ON t.id = s.templateID
--     JOIN ivr_liba_script_assort a ON a.id = t.assortid
-- SET s.script_assortname = a.index_assort_name
-- WHERE s.script_assortname IS NULL OR s.script_assortname = '';
-- ----------------------------------------------------------------------------
-- ã€5】可选:给 ivr_liba_script_assort.type å¡«å€¼ï¼ˆå½“前 35 è¡Œå…¨ NULL,
--      è€Œä»£ç é‡Œ mydIdList æ˜¯æŒ‰ type å« "myd" è¿‡æ»¤çš„,不填值语音分支就永远是 0 ä¸ªåˆ†ç±»ï¼‰
--      å¡«å€¼å£å¾„需要业务确认,下面只是按现有分类名给的示例,确认后再执行:
--        æ»¡æ„åº¦ç›¸å…³ï¼ˆå¦‚ id=12 æ»¡æ„åº¦è°ƒæŸ¥ã€id=9 æœåŠ¡è¯„ä»·ï¼‰â†’ mzmyd / zymyd
--        å…¶ä½™åˆ†ç±»                                            â†’ ç•™ç©ºæˆ–按业务自定义
--      ï¼ˆæœ¬åº“ ivr_liba_script_assort é‡Œçš„分类名:未分类/患者信息/体征/疾病/症状/生活方式/
--        ç”¨è¯ä¾ä»Žæ€§/不良反应/通知提醒/服务评价/检查类/检验类/满意度调查/膳食类/运动类/睡眠类/
--        æŠ½çƒŸå–é…’/身高/体重/视力/听力/意见建议/检验检查类/随访指标/身份确认/复诊通知/复查通知/
--        æ˜¯å¦æ–¹ä¾¿/出院宣教/修养地/语音题目/语音题目测试 â€¦ï¼‰
-- ----------------------------------------------------------------------------
-- UPDATE ivr_liba_script_assort SET type = 'zymyd' WHERE id = 12;   -- ä¾‹ï¼šæ»¡æ„åº¦è°ƒæŸ¥
-- UPDATE ivr_liba_script_assort SET type = 'mzmyd' WHERE id = 9;    -- ä¾‹ï¼šæœåŠ¡è¯„ä»·
-- ----------------------------------------------------------------------------
-- ã€6】验证
-- ----------------------------------------------------------------------------
-- SHOW COLUMNS FROM ivr_liba_template_script LIKE '%assort%';
-- SHOW COLUMNS FROM ivr_task_template_script LIKE '%assort%';
-- SHOW COLUMNS FROM ivr_liba_script_assort LIKE 'type';
-- SELECT COUNT(*) AS liba_script_rows, SUM(script_assortid IS NULL) AS assortid_null FROM ivr_liba_template_script;      -- æœŸæœ› 121 / 0
-- SELECT COUNT(*) AS task_script_rows, SUM(script_assortid IS NULL) AS assortid_null FROM ivr_task_template_script;      -- æœŸæœ› 1221 / 863
-- ----------------------------------------------------------------------------
-- ã€7】回滚
-- ----------------------------------------------------------------------------
-- ALTER TABLE ivr_liba_template_script CHANGE COLUMN script_assortname categoryName varchar(50) NULL COMMENT '类别名';
-- ALTER TABLE ivr_task_template_script CHANGE COLUMN script_assortname categoryName varchar(50) NULL COMMENT '类别名';
-- ALTER TABLE ivr_liba_template_script DROP COLUMN script_assortid;
-- ALTER TABLE ivr_task_template_script DROP COLUMN script_assortid;
-- ALTER TABLE ivr_liba_script_assort MODIFY COLUMN type varchar(255) NULL COMMENT '';
sql/ivr_script_missing_columns_20260928.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,88 @@
-- ============================================================================
-- smartor_sltd æ•°æ®åº“:ivr è¯­éŸ³é—®é¢˜ä¸¤å¼ è¡¨è¡¥é½ mapper éœ€è¦çš„ 6 ä¸ªåˆ—(第二批)
-- ç”Ÿæˆæ—¥æœŸ: 20260928
--
-- ã€ä¸ºä»€ä¹ˆéœ€è¦ã€‘
--   20260928 ç¬¬ä¸€æ‰¹å·²æŠŠ categoryName â†’ script_assortname、并新增 script_assortid,
--   ä½† IvrLibaTemplateScriptMapper / IvrTaskTemplateScriptMapper çš„ select / insert / update
--   è¯­å¥é‡Œè¿˜å¼•用了下面 6 ä¸ªåˆ—,而 ivr_liba_template_script、ivr_task_template_script ä¸¤ä¸ªè¡¨
--   åœ¨æœ¬åº“中都不存在这些列(svy_lib_template_script é‡Œæ˜¯æœ‰çš„,定义照抄即可):
--       dimension          varchar(50)  é—®é¢˜ç»´åº¦
--       is_myd_exception   varchar(50)  æ»¡æ„åº¦æ„è§å¼‚常(只针对文本)
--       duty_dept_name     text         è´£ä»»ç§‘室名称(如果多个科室用逗号分割)
--       duty_dept_code     text         è´£ä»»ç§‘室编码(如果多个科室用逗号分割)
--       report_dept_name   text         æŠ¥å¤‡ç§‘室名称(如果多个科室用逗号分割)
--       report_dept_code   text         æŠ¥å¤‡ç§‘室编码(如果多个科室用逗号分割)
--   â‡’ ä¸è¡¥çš„话,satisfactionGraph çš„语音分支(type=1 æˆ– type=null)执行
--      IvrLibaTemplateScriptMapper.queryLibTemplateIds æ—¶ä»ä¼šæŠ›
--      Unknown column 'is_myd_exception' in 'field list'。
--      ï¼ˆç¬¬ä¸€æ‰¹ä¹‹å‰æŠ¥çš„æ˜¯ script_assortname,那两个列已经补好。)
--
-- ã€æ ¡éªŒæ–¹å¼ã€‘
--   å·²ç”¨è„šæœ¬é€åˆ—æ¯”å¯¹ï¼šä¸¤å¼ è¡¨çš„ mapper è¯­å¥ï¼ˆselect ç‰‡æ®µ 55 åˆ— + insert/update 54 åˆ—)
--   ä¸Žæœ¬åº“实际列做差集,缺失的就是上面这 6 ä¸ªï¼Œåˆ«æ— å…¶å®ƒã€‚
-- ============================================================================
-- ----------------------------------------------------------------------------
-- ã€0】执行前检查(应确认这 6 åˆ—不存在)
-- ----------------------------------------------------------------------------
-- SELECT COLUMN_NAME FROM information_schema.COLUMNS
--  WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ivr_liba_template_script'
--    AND COLUMN_NAME IN ('dimension','is_myd_exception','duty_dept_name','duty_dept_code','report_dept_name','report_dept_code');
-- SELECT COLUMN_NAME FROM information_schema.COLUMNS
--  WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ivr_task_template_script'
--    AND COLUMN_NAME IN ('dimension','is_myd_exception','duty_dept_name','duty_dept_code','report_dept_name','report_dept_code');
-- ----------------------------------------------------------------------------
-- ã€1】ivr_liba_template_script è¡¥ 6 åˆ—(121 è¡Œï¼ŒINPLACE ä¸é˜»å¡žè¯»å†™ï¼‰
-- ----------------------------------------------------------------------------
ALTER TABLE ivr_liba_template_script
    ADD COLUMN dimension varchar(50) NULL COMMENT '问题维度',
    ADD COLUMN is_myd_exception varchar(50) NULL COMMENT '满意度意见异常(只针对文本)',
    ADD COLUMN duty_dept_name text NULL COMMENT '责任科室名称(如果多个科室用逗号分割)',
    ADD COLUMN duty_dept_code text NULL COMMENT '责任科室编码(如果多个科室用逗号分割)',
    ADD COLUMN report_dept_name text NULL COMMENT '报备科室名称(如果多个科室用逗号分割)',
    ADD COLUMN report_dept_code text NULL COMMENT '报备科室编码(如果多个科室用逗号分割)',
    ALGORITHM = INPLACE,
    LOCK = NONE;
-- ----------------------------------------------------------------------------
-- ã€2】ivr_task_template_script è¡¥ 6 åˆ—(1221 è¡Œï¼‰
-- ----------------------------------------------------------------------------
ALTER TABLE ivr_task_template_script
    ADD COLUMN dimension varchar(50) NULL COMMENT '问题维度',
    ADD COLUMN is_myd_exception varchar(50) NULL COMMENT '满意度意见异常(只针对文本)',
    ADD COLUMN duty_dept_name text NULL COMMENT '责任科室名称(如果多个科室用逗号分割)',
    ADD COLUMN duty_dept_code text NULL COMMENT '责任科室编码(如果多个科室用逗号分割)',
    ADD COLUMN report_dept_name text NULL COMMENT '报备科室名称(如果多个科室用逗号分割)',
    ADD COLUMN report_dept_code text NULL COMMENT '报备科室编码(如果多个科室用逗号分割)',
    ALGORITHM = INPLACE,
    LOCK = NONE;
-- ----------------------------------------------------------------------------
-- ã€3】验证:这两条 mapper çš„ select ç‰‡æ®µåº”能正常返回
--   ç­‰ä»· SQL(MyBatis é‡Œå°±æ˜¯è¿™æ®µï¼‰ï¼š
--     select id, scriptno, is_myd_exception, ..., script_assortname, ..., script_assortid, ...
--       from ivr_liba_template_script where del_flag=0
-- ----------------------------------------------------------------------------
-- SELECT COUNT(*) FROM (
--   SELECT id, scriptno, is_myd_exception, dimension, script_assortname, script_assortid,
--          duty_dept_name, duty_dept_code, report_dept_name, report_dept_code,
--          duty_dept_person_name, duty_dept_person_code, report_dept_person_code, report_dept_person_name,
--          circulation_dept_person_code, circulation_dept_person_name
--   FROM ivr_liba_template_script WHERE del_flag = 0) t;
-- ----------------------------------------------------------------------------
-- ã€4】回滚
-- ----------------------------------------------------------------------------
-- ALTER TABLE ivr_liba_template_script
--     DROP COLUMN dimension, DROP COLUMN is_myd_exception, DROP COLUMN duty_dept_name,
--     DROP COLUMN duty_dept_code, DROP COLUMN report_dept_name, DROP COLUMN report_dept_code;
-- ALTER TABLE ivr_task_template_script
--     DROP COLUMN dimension, DROP COLUMN is_myd_exception, DROP COLUMN duty_dept_name,
--     DROP COLUMN duty_dept_code, DROP COLUMN report_dept_name, DROP COLUMN report_dept_code;
sql/pat_med_outhosp_serialnum_index.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,184 @@
-- ============================================================================
-- pat_med_outhosp ç³»åˆ—表按 serialnum åŠ ç´¢å¼•ï¼ˆé…åˆé—¨æ€¥è¯ŠæŒ‰å¤©å…¨é‡é‡‡é›†ï¼‰
--
-- ä¸ºä»€ä¹ˆè¦åŠ ï¼š
--   1) é‡‡é›†ä»»åŠ¡æ¯ä¸ªåŠå°æ—¶æŠŠå½“å¤©ä¸Šä¸‡ä¸ªè¯Šç–—è®°å½•æ•´è½®æ‹‰ä¸€éï¼Œé "整页批量查重"过滤已入库的,
--      æŸ¥é‡ SQL æ˜¯ where serialnum in (500个)。没有索引就是全表扫描,季度分表几百万行时一次就很慢;
--   2) å”¯ä¸€ç´¢å¼•是"不重复"的最后一道防线:极端并发、断点回退、分页偏移都不会插出重复记录。
--
-- æ‰§è¡Œé¡ºåºï¼š
--   ç¬¬ 1 æ­¥  çœ‹æ¸…有哪些表、现在有没有索引
--   ç¬¬ 2 æ­¥  æŸ¥é‡å¤æ•°æ®ï¼ˆæœ‰é‡å¤çš„话第 3 æ­¥åŠ å”¯ä¸€ç´¢å¼•ä¼šå¤±è´¥ï¼Œå…ˆå¤„ç†é‡å¤ï¼‰
--   ç¬¬ 3 æ­¥  åŠ å”¯ä¸€ç´¢å¼• uk_serialnum(脚本会自动跳过已有唯一索引的表)
--   ç¬¬ 4 æ­¥  æ ¡éªŒç»“æžœ
--
-- æ³¨æ„ï¼šADD INDEX èµ° online DDL(ALGORITHM=INPLACE, LOCK=NONE),不阻塞读写,
--       ä½†å¤§è¡¨ä»ä¼šæ¶ˆè€— IO,建议放在业务低峰执行。数据量大时分表一张一张来也可以。
--       å¦‚果某张表报错,先单独执行第 2 æ­¥çœ‹å®ƒæœ‰æ²¡æœ‰é‡å¤æ•°æ®ã€‚
-- ============================================================================
-- ----------------------------------------------------------------------------
-- ç¬¬ 1 æ­¥ï¼šçœ‹æœ‰å“ªäº› pat_med_outhosp è¡¨ï¼Œä»¥åŠ serialnum ä¸Šçš„索引情况
--   has_unique_index = 1 è¡¨ç¤ºå·²ç»æœ‰å”¯ä¸€ç´¢å¼•(不用再加)
--   has_any_index    = 0 è¡¨ç¤ºå®Œå…¨æ²¡ç´¢å¼•(查询会很慢,必须加)
-- ----------------------------------------------------------------------------
SELECT t.TABLE_NAME,
       t.TABLE_ROWS,
       (SELECT COUNT(*)
          FROM information_schema.STATISTICS s
         WHERE s.TABLE_SCHEMA = t.TABLE_SCHEMA
           AND s.TABLE_NAME = t.TABLE_NAME
           AND s.COLUMN_NAME = 'serialnum'
           AND s.NON_UNIQUE = 0) AS has_unique_index,
       (SELECT COUNT(*)
          FROM information_schema.STATISTICS s
         WHERE s.TABLE_SCHEMA = t.TABLE_SCHEMA
           AND s.TABLE_NAME = t.TABLE_NAME
           AND s.COLUMN_NAME = 'serialnum') AS has_any_index
FROM information_schema.TABLES t
WHERE t.TABLE_SCHEMA = DATABASE()
  AND (t.TABLE_NAME = 'pat_med_outhosp' OR t.TABLE_NAME REGEXP '^pat_med_outhosp_[0-9]{4,8}$')
ORDER BY t.TABLE_NAME;
-- ----------------------------------------------------------------------------
-- ç¬¬ 2 æ­¥ï¼šæŸ¥é‡å¤çš„ serialnum(有重复就必须先清理,否则唯一索引建不上)
--   æ‰§è¡ŒåŽå…ˆçœ‹ dup_serialnum_total,等于 0 å°±å¯ä»¥ç›´æŽ¥è¿›ç¬¬ 3 æ­¥
-- ----------------------------------------------------------------------------
DROP PROCEDURE IF EXISTS check_outhosp_serialnum_dup;
DELIMITER $$
CREATE PROCEDURE check_outhosp_serialnum_dup()
BEGIN
    DECLARE done INT DEFAULT 0;
    DECLARE tbl VARCHAR(64);
    DECLARE cur CURSOR FOR
        SELECT TABLE_NAME
          FROM information_schema.TABLES
         WHERE TABLE_SCHEMA = DATABASE()
           AND (TABLE_NAME = 'pat_med_outhosp' OR TABLE_NAME REGEXP '^pat_med_outhosp_[0-9]{4,8}$');
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
    DROP TEMPORARY TABLE IF EXISTS tmp_dup_serialnum;
    CREATE TEMPORARY TABLE tmp_dup_serialnum
    (
        table_name VARCHAR(64),
        serialnum  VARCHAR(64),
        cnt        INT
    );
    OPEN cur;
    read_loop:
    LOOP
        FETCH cur INTO tbl;
        IF done THEN
            LEAVE read_loop;
        END IF;
        SET @s = CONCAT('INSERT INTO tmp_dup_serialnum SELECT ''', tbl,
                        ''', serialnum, COUNT(*) FROM `', tbl,
                        '` WHERE serialnum IS NOT NULL AND serialnum <> ''''',
                        ' GROUP BY serialnum HAVING COUNT(*) > 1');
        PREPARE st FROM @s;
        EXECUTE st;
        DEALLOCATE PREPARE st;
    END LOOP;
    CLOSE cur;
    SELECT * FROM tmp_dup_serialnum ORDER BY cnt DESC LIMIT 200;
    SELECT COUNT(*) AS dup_serialnum_total FROM tmp_dup_serialnum;
END$$
DELIMITER ;
CALL check_outhosp_serialnum_dup();
-- æŸ¥å®Œå¯ä»¥åˆ æŽ‰è¿™ä¸ªè¿‡ç¨‹ï¼šDROP PROCEDURE IF EXISTS check_outhosp_serialnum_dup;
-- å¦‚果有重复,按下面的思路清理(保留 id æœ€å°çš„那条,先备份再删):
--   CREATE TABLE bak_dup_<表名>_<日期> AS
--   SELECT * FROM `pat_med_outhosp_20261012`
--    WHERE serialnum IN ('重复的serialnum1','重复的serialnum2');
--   DELETE t1 FROM `pat_med_outhosp_20261012` t1
--     JOIN `pat_med_outhosp_20261012` t2
--       ON t1.serialnum = t2.serialnum AND t1.id > t2.id
--    WHERE t1.serialnum IN ('重复的serialnum1','重复的serialnum2');
-- ----------------------------------------------------------------------------
-- ç¬¬ 3 æ­¥ï¼šç»™æ‰€æœ‰ pat_med_outhosp è¡¨åŠ å”¯ä¸€ç´¢å¼• uk_serialnum
--   å·²æœ‰å”¯ä¸€ç´¢å¼•的表自动跳过;如果之前建过同名的普通索引 idx_serialnum,会先删掉再建唯一的
-- ----------------------------------------------------------------------------
DROP PROCEDURE IF EXISTS add_outhosp_serialnum_index;
DELIMITER $$
CREATE PROCEDURE add_outhosp_serialnum_index()
BEGIN
    DECLARE done INT DEFAULT 0;
    DECLARE tbl VARCHAR(64);
    DECLARE has_unique INT DEFAULT 0;
    DECLARE has_normal INT DEFAULT 0;
    DECLARE cur CURSOR FOR
        SELECT TABLE_NAME
          FROM information_schema.TABLES
         WHERE TABLE_SCHEMA = DATABASE()
           AND (TABLE_NAME = 'pat_med_outhosp' OR TABLE_NAME REGEXP '^pat_med_outhosp_[0-9]{4,8}$');
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
    OPEN cur;
    add_loop:
    LOOP
        FETCH cur INTO tbl;
        IF done THEN
            LEAVE add_loop;
        END IF;
        SELECT COUNT(*) INTO has_unique
          FROM information_schema.STATISTICS
         WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = tbl
           AND COLUMN_NAME = 'serialnum' AND NON_UNIQUE = 0;
        IF has_unique > 0 THEN
            SELECT CONCAT('[跳过] ', tbl, ' å·²æœ‰ serialnum å”¯ä¸€ç´¢å¼•') AS msg;
        ELSE
            -- å…ˆæ¸…掉可能存在的非唯一索引(保留唯一索引一个就够)
            SELECT COUNT(*) INTO has_normal
              FROM information_schema.STATISTICS
             WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = tbl
               AND COLUMN_NAME = 'serialnum' AND NON_UNIQUE = 1 AND INDEX_NAME = 'idx_serialnum';
            IF has_normal > 0 THEN
                SET @s = CONCAT('ALTER TABLE `', tbl, '` DROP INDEX idx_serialnum');
                PREPARE st FROM @s;
                EXECUTE st;
                DEALLOCATE PREPARE st;
            END IF;
            SET @s = CONCAT('ALTER TABLE `', tbl, '` ADD UNIQUE INDEX uk_serialnum (serialnum)');
            PREPARE st FROM @s;
            EXECUTE st;
            DEALLOCATE PREPARE st;
            SELECT CONCAT('[完成] ', tbl, ' å·²æ·»åŠ å”¯ä¸€ç´¢å¼• uk_serialnum') AS msg;
        END IF;
    END LOOP;
    CLOSE cur;
END$$
DELIMITER ;
CALL add_outhosp_serialnum_index();
-- å®ŒæˆåŽåˆ æŽ‰è¿‡ç¨‹ï¼šDROP PROCEDURE IF EXISTS add_outhosp_serialnum_index;
-- å¦‚果某张表报 "Duplicate entry ... for key 'uk_serialnum'",说明它还有重复数据,
-- å›žåˆ°ç¬¬ 2 æ­¥æ¸…理该表后,单独执行:
--   ALTER TABLE `pat_med_outhosp_20261012` ADD UNIQUE INDEX uk_serialnum (serialnum);
-- ----------------------------------------------------------------------------
-- ç¬¬ 4 æ­¥ï¼šæ ¡éªŒï¼ˆæ¯å¼ è¡¨éƒ½åº”该 has_unique_index = 1)
-- ----------------------------------------------------------------------------
SELECT t.TABLE_NAME,
       (SELECT COUNT(*)
          FROM information_schema.STATISTICS s
         WHERE s.TABLE_SCHEMA = t.TABLE_SCHEMA
           AND s.TABLE_NAME = t.TABLE_NAME
           AND s.COLUMN_NAME = 'serialnum'
           AND s.NON_UNIQUE = 0) AS has_unique_index
FROM information_schema.TABLES t
WHERE t.TABLE_SCHEMA = DATABASE()
  AND (t.TABLE_NAME = 'pat_med_outhosp' OR t.TABLE_NAME REGEXP '^pat_med_outhosp_[0-9]{4,8}$')
ORDER BY t.TABLE_NAME;
sql/patinhosp_select_list_index_optimize.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,202 @@
-- ============================================================================
-- POST /smartor/patinhosp/selectPatMedInhospList æ…¢æŸ¥è¯¢è¯Šæ–­ + ç´¢å¼•脚本
--
-- çŽ°åœºå…¥å‚ï¼ˆåœ¨é™¢è§†å›¾ï¼‰ï¼š
--   {"bedSort":1,"pageNum":1,"pageSize":20,
--    "deptcodeList":["40003378"],"hospitaldistrictcodeList":["40003380"],"cry":0}
--
-- è°ƒç”¨é“¾ï¼ˆå·²é€è¡Œæ ¸å¯¹ï¼‰ï¼š
--   InhospVisitDataController#selectPatMedInhosplis
--     -> InhospVisitFacade#selectPatMedInhospList
--     -> DefaultInhospVisitDataService#selectPatMedInhospList
--     -> PatMedInhospMapper.xml <select id="selectPatMedInhospList">
--
-- âš ï¸ æ³¨æ„ï¼šè¿ç§»åŽè¿™æ¡é“¾ã€ä¸å†ç»è¿‡ã€‘smartor çš„ PatMedInhospServiceImpl#selectPatMedInhospList,
--    å› æ­¤é‚£é‡Œé¢çš„ cry å½’一化(cry=0 -> inhospstate='0')在这条接口上【已经不生效了】。
--    è¿™ä¸€ç‚¹åŒæ—¶æ˜¯"慢"的根因之一,见下面【第 4 èŠ‚ã€‘ã€‚
--
-- æ‰§è¡Œé¡ºåºï¼šç¬¬ 1 æ­¥çœ‹çŽ°çŠ¶ â†’ ç¬¬ 2 æ­¥ï¼ˆäºŒé€‰ä¸€ï¼‰â†’ ç¬¬ 3 æ­¥ EXPLAIN éªŒè¯ã€‚
-- ADD INDEX èµ° online DDL(不阻塞读写),但大表仍会吃 IO,建议业务低峰执行。
-- ============================================================================
-- ----------------------------------------------------------------------------
-- ç¬¬ 1 èŠ‚ï¼šè¿™æ¡å…¥å‚å®žé™…æ‰§è¡Œçš„ä¸¤æ¡ SQL(由 MyBatis åŠ¨æ€æ ‡ç­¾å±•å¼€ + PageHelper æ”¹å†™ï¼‰
-- ----------------------------------------------------------------------------
--
-- â‘  æ•°æ®æŸ¥è¯¢ï¼š<select id="selectPatMedInhospList">(cry=0 èµ° endtime is null åˆ†æ”¯ï¼›
--    bedSort ä¼ å­—符串 "1" ä¸åŒ¹é… XML é‡Œçš„ "asc"/"desc",故落到 <otherwise> é»˜è®¤æŽ’序)
--
--    SELECT a.age, a.telcode, a.sex, b.inhospid, b.nurse_id, ...(共 60+ åˆ—)..., a.idcardno
--      FROM pat_med_inhosp b
--      JOIN pat_archive a ON a.id = b.patid
--     WHERE 1=1
--       AND a.del_flag = 0                 -- â† æ•°å­—常量比 char åˆ—,见第 4 èŠ‚ F1
--       AND b.del_flag = 0                 -- â† åŒä¸Šï¼Œä¸”它正是 idx_inhosp_optimize çš„首列
--       AND a.idcardno IS NOT NULL
--       AND b.endtime IS NULL              -- â† åœ¨é™¢åˆ¤å®šï¼ˆç­‰ä»·æ¡ä»¶ï¼Œæ— æ³•用现有索引)
--       AND b.hospitaldistrictcode IN ('40003380')
--       AND b.deptcode IN ('40003378')
--       AND b.orgid = '<登录用户 orgid>'     -- ç”± @AddOrgId æ³¨å…¥
--      [AND b.campusid = '<登录用户 campusid>']  -- é™¢åŒºæƒé™æœªæ”¾å¼€æ—¶ç”± @AddOrgId æ³¨å…¥
--     GROUP BY b.inhospid, a.age, a.age_unit, a.telcode, a.id, a.sex   -- â† è§ç¬¬ 4 èŠ‚ F3
--     ORDER BY b.starttime DESC, b.bed_no ASC
--     LIMIT 0, 20
--
-- â‘¡ æ€»æ•°æŸ¥è¯¢ï¼šPageHelper è‡ªåŠ¨ç”Ÿæˆã€‚å› ä¸ºæœ‰ GROUP BY,isSimpleCount() ä¸º false,
--    CountSqlParser ä¼šã€æŠŠæ•´æ¡æŸ¥è¯¢åŒ…一层】而不是改写成 count(0)(已核对 pagehelper 5.3.x
--    å­—节码常量 "select count(" + ") from ( " + ") tmp_count"):
--
--    SELECT count(0) FROM ( <① åŽ»æŽ‰ ORDER BY åŽçš„完整 60 åˆ—语句> ) tmp_count
--
--    => æ¯æ¬¡ç¿»é¡µ = åŒä¸€æ¡é‡é‡çº§æŸ¥è¯¢è·‘两遍。这是"慢"的第二个根因,见第 4 èŠ‚ F2。
--
-- â‘¢ å¦å¤–一条:fillTagname ä¸åœ¨è¿™æ¡é“¾ä¸Šï¼ˆé‚£æ˜¯ smartor ä¾§ ServiceImpl çš„逻辑),可排除。
-- ----------------------------------------------------------------------------
-- ç¬¬ 1 æ­¥ï¼šå…ˆçœ‹çŽ°çŠ¶ â€”— çŽ°æœ‰ç´¢å¼•ã€æ…¢æ—¥å¿—ã€ä»¥åŠè¿™æ¡ SQL çš„真实执行计划
-- ----------------------------------------------------------------------------
-- 1.1 pat_med_inhosp / pat_archive çŽ°æœ‰ç´¢å¼•
--     é‡ç‚¹çœ‹ idx_inhosp_optimize çš„真实列顺序(代码注释里写的是 (del_flag, orgid, inhospstate))
SELECT TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX, COLUMN_NAME, CARDINALITY, NON_UNIQUE
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
  AND TABLE_NAME IN ('pat_med_inhosp', 'pat_archive')
ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX;
-- 1.2 å…³æŽ‰çš„æ¡ä»¶åˆ°åº•是不是索引列、列类型对不对(del_flag / inhospstate æ˜¯ char è¿˜æ˜¯ int,
--     ç›´æŽ¥å†³å®šä¸‹é¢ F1 / F4 è¦ä¸è¦æ”¹ï¼‰
SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
  AND TABLE_NAME IN ('pat_med_inhosp', 'pat_archive')
  AND COLUMN_NAME IN ('inhospid', 'patid', 'del_flag', 'orgid', 'campusid',
                      'inhospstate', 'endtime', 'starttime', 'bed_no',
                      'deptcode', 'hospitaldistrictcode');
-- 1.3 è¡¨ä½“量 + åœ¨é™¢å æ¯”(判断"全表扫描"到底贵不贵)
SELECT TABLE_NAME, TABLE_ROWS, ROUND(DATA_LENGTH / 1024 / 1024, 1) AS data_mb,
       ROUND(INDEX_LENGTH / 1024 / 1024, 1) AS index_mb
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME IN ('pat_med_inhosp', 'pat_archive');
SELECT COUNT(*) AS total,
       SUM(endtime IS NULL) AS in_hosp_by_endtime,
       SUM(inhospstate = '0') AS in_hosp_by_state,
       -- ä¸¤ç§"在院"定义不一致的行数:>0 å°±è¯´æ˜Žã€ä¸èƒ½ã€‘随手把 inhospstate='0' å½“作
       -- endtime IS NULL çš„æ›¿ä»£æ¡ä»¶ï¼ŒF2 åªèƒ½èµ°"加索引"那条路
       SUM(CASE WHEN endtime IS NULL AND IFNULL(inhospstate, '') <> '0' THEN 1 ELSE 0 END) AS in_hosp_def_conflict
FROM pat_med_inhosp
WHERE del_flag = '0';
-- 1.4 ç›´æŽ¥ EXPLAIN è¿™æ¡è¯­å¥ï¼ˆæŠŠ orgid æ¢æˆçŽ°åœºå€¼ï¼‰
EXPLAIN
SELECT a.age, a.telcode, a.sex, b.inhospid, b.patid, b.deptcode, b.bed_no, b.starttime, b.endtime
FROM pat_med_inhosp b
         JOIN pat_archive a ON a.id = b.patid
WHERE a.del_flag = 0
  AND b.del_flag = 0
  AND a.idcardno IS NOT NULL
  AND b.endtime IS NULL
  AND b.hospitaldistrictcode IN ('40003380')
  AND b.deptcode IN ('40003378')
  AND b.orgid = '<换成现场 orgid>'
GROUP BY b.inhospid, a.age, a.age_unit, a.telcode, a.id, a.sex
ORDER BY b.starttime DESC, b.bed_no ASC
LIMIT 0, 20;
--
-- é¢„期(改之前):b è¡¨ type=ref/range æˆ– ALL,key åªæœ‰ idx_inhosp_optimize çš„
--   (del_flag, orgid) å‰ç¼€èƒ½ç”¨åˆ°ï¼ˆæˆ–完全用不上);
--   rows çº¦ç­‰äºŽ"该机构全部历史住院记录数",Extra é‡Œå‡ºçް Using where; Using temporary; Using filesort。
-- æ”¹ä¹‹åŽè¦ç‚¹ï¼šb è¡¨ rows åº”降到"该机构在院人数"量级(数千),且 key å‘½ä¸­ä¸‹é¢çš„æ–°ç´¢å¼• / çŽ°æœ‰ç´¢å¼•å…¨å‰ç¼€ã€‚
-- ----------------------------------------------------------------------------
-- ç¬¬ 2 èŠ‚ï¼šä¸¤ç§ä¿®æ³• â€”— ã€æœ¬æ¬¡é‡‡ç”¨ä¿®æ³• A】,修法 B ç•™ä½œå¤‡é€‰
-- ----------------------------------------------------------------------------
-- ä¿®æ³• A(本次采用,零语义风险):给"在院"补一个能定位的索引。
--   çŽ°æœ‰ idx_inhosp_optimize çš„第三个等值列是 inhospstate,但本接口不传这个条件,
--   åªèƒ½ç”¨ä¸Š (del_flag, orgid) å‰ç¼€ = è¯¥æœºæž„全部历史住院记录,等于没走索引。
--   å‰æï¼šF1 çš„ del_flag = '0' å·²æ”¹ï¼ˆå¦åˆ™é¦–列用不上,这条索引也白建)。
ALTER TABLE pat_med_inhosp
    ADD INDEX idx_pmi_del_org_end (del_flag, orgid, endtime);
--   å¯é€‰åŠ å¼ºï¼šæŠŠä¸¤ä¸ª IN æ¡ä»¶ä¹Ÿæ”¾è¿›ç´¢å¼•,命中行不用回表就能判掉(索引更宽,写放大更明显)
-- ALTER TABLE pat_med_inhosp
--     ADD INDEX idx_pmi_del_org_end_dept (del_flag, orgid, endtime, deptcode, hospitaldistrictcode);
--   å‡ºé™¢ / è½¬ç§‘ / é¢„入院 ä¸‰ä¸ªè§†å›¾ï¼ˆcry=1/2/3,order by b.endtime desc)用这条:
--   ç­‰å€¼åˆ— (del_flag, orgid, inhospstate) å…¨å‘½ä¸­ï¼Œä¸” endtime å¯ä»¥é¡ºå¸¦é¿å… filesort çš„æŽ’序键
ALTER TABLE pat_med_inhosp
    ADD INDEX idx_pmi_del_org_state_end (del_flag, orgid, inhospstate, endtime);
-- ä¿®æ³• B(本次不采用,留作备选):不加索引,cry=0 æ—¶è¡¥ inhospstate='0',让现有
--   idx_inhosp_optimize çš„ (del_flag, orgid, inhospstate) å‰ç¼€å…¨éƒ¨ç­‰å€¼å‘½ä¸­ â€”—
--   å…¶å®žè¿™æ‰æ˜¯ã€è¿ç§»å‰çš„行为】:smartor çš„ PatMedInhospServiceImpl#selectPatMedInhospList
--   åœ¨ cry=0 æ—¶ä¼š setInhospstate("0"),迁到 ruoyi-inhospvisit åŽè¿™æ¡é“¾ç›´æŽ¥è°ƒ Mapper,
--   è¿™æ®µå½’一化丢了。
--   âš ï¸ ä¹‹æ‰€ä»¥æ²¡é€‰å®ƒï¼šå®ƒä¼šè®©"endtime ä¸ºç©ºä½† inhospstate<>'0'"的行静默消失,
--   æ˜¯å¦å®‰å…¨å–决于数据,得先看第 1.3 èŠ‚çš„ in_hosp_def_conflict。
--   è‹¥å°†æ¥ç¡®è®¤ = 0,这一条比建索引更省(见 PatMedInhospMapper.xml çš„
--   <if test="cry != null and cry == 0 "> åˆ†æ”¯ï¼‰ã€‚
-- ----------------------------------------------------------------------------
-- ç¬¬ 3 èŠ‚ï¼šéªŒè¯ï¼ˆç¬¬ 2 æ­¥åšå®ŒåŽé‡è·‘第 1.4 èŠ‚ï¼‰
-- ----------------------------------------------------------------------------
-- â‘  b è¡¨ type = ref,key = idx_pmi_del_org_end(或 idx_inhosp_optimize),rows æ•°åƒçº§ï¼›
-- â‘¡ Using temporary; Using filesort ä»ä¼šæœ‰ï¼ˆGROUP BY + åºŠä½æŽ’序导致,见 F3),
--    ä½†å› ä¸ºå‚与排序的行数已经从"全机构历史"降到"本部门在院",可以接受;
-- â‘¢ æŽ¥å£è€—时:count + data ä¸¤æ¡éƒ½æŒ‰è¿™ä¸ªè®¡åˆ’走,整体应降到几十毫秒级。
-- ----------------------------------------------------------------------------
-- ã€å›žæ»šã€‘
-- ----------------------------------------------------------------------------
-- ALTER TABLE pat_med_inhosp DROP INDEX idx_pmi_del_org_end;
-- ALTER TABLE pat_med_inhosp DROP INDEX idx_pmi_del_org_state_end;
-- ============================================================================
-- ç¬¬ 4 èŠ‚ï¼šä»£ç ä¾§é¡ºå¸¦å‘çŽ°çš„é—®é¢˜ï¼ˆæŒ‰å½±å“æŽ’åºï¼‰
--   âœ… å·²æ”¹ï¼šF1 / F2 / F4      â¸ æœªæ”¹ï¼ˆç­‰ç¡®è®¤ï¼‰ï¼šF3 / F5 / F6
--
-- F1  âœ… å·²æ”¹  b.del_flag = 0 / a.del_flag = 0:数字常量比 char åˆ—,MySQL ä¼šæŠŠã€åˆ—】转
--     DOUBLE,该条件只能过滤、不能定位。idx_inhosp_optimize çš„首列就是 del_flag,
--     ç­‰äºŽé¦–列白给。已改成 = '0'。本仓库已有同类结论,见
--     sql/sltdMydTotalByDimension_index_20260924.sql ç¬¬ 117-134 è¡Œï¼š"代码侧必须同步修正
--     del_flag çš„写法,否则索引只能用上一半"。
--     ï¼ˆåˆ—是 int æ—¶å†™ '0' ä¹Ÿå®‰å…¨ï¼šå¸¸é‡ä¾§è½¬æ¢ï¼Œç´¢å¼•照样可用。)
--
-- F2  âœ… å·²æ”¹  PageHelper çš„ count:有 GROUP BY æ—¶ä¸èƒ½ç®€åŒ–,只能包一层 tmp_count,
--     ç­‰äºŽæ¯æ¬¡ç¿»é¡µæŠŠè¿™æ¡ 60 åˆ—查询再跑一遍。已在
--     ruoyi-inhospvisit/.../PatMedInhospMapper.xml è¡¥ selectPatMedInhospList_COUNT
--     ï¼ˆpagehelper 5.3.x çš„ PageInterceptor æ”¯æŒ countSuffix="_COUNT" çš„æ‰‹å†™ count,
--     å·²æ ¸å¯¹å­—节码常量:genCountMsId / getExistedMappedStatement / executeManualCount),
--     å¹¶æŠŠ FROM/WHERE æŠ½æˆ selectPatMedInhospListFromWhere ç‰‡æ®µç»™ä¸¤æ¡è¯­å¥å…±ç”¨ï¼Œ
--     ä¿è¯ count ä¸Žåˆ—表永远是同一套条件。
--
-- F3  â¸ æœªæ”¹  GROUP BY b.inhospid, a.age, a.age_unit, a.telcode, a.id, a.sex æ’等、不去重:
--     a.id æ˜¯ pat_archive ä¸»é”®ã€b.patid åªå…³è”到一行,每个 join ç»“果行自成分组。
--     å®ƒåªæ¢æ¥ Using temporary + Using filesort。删它之前要先确认 pat_archive.id å”¯ä¸€ï¼›
--     ç¡®è®¤åŽåˆ æŽ‰ï¼ŒF2 çš„ count è¿˜ä¼šè‡ªåŠ¨å˜æˆç®€å•çš„ count(0)。
--
-- F4  âœ… å·²æ”¹  cry=1/2/3 åˆ†æ”¯åŽŸæ¥æ˜¯ b.inhospstate=1 / =2 / =3(数字常量 vs char åˆ—),同 F1,
--     å‡ºé™¢ / è½¬ç§‘ / é¢„入院 ä¸‰ä¸ªè§†å›¾çš„索引也用不上,已改成 '1' / '2' / '3'。
--
-- F5  â¸ æœªæ”¹  bedSort åœ¨è¿™æ¡é“¾ä¸Šæ²¡æœ‰å½’一化。前端传 bedSort:1(数字),Jackson è½åˆ° String
--     æ˜¯ "1",而 XML åªè®¤ "asc"/"desc",所以【按床位排序实际没生效】,落回默认
--     order by starttime desc, bed_no asc。归一化函数 normalizeBedSort ç›®å‰åªåœ¨
--     smartor çš„ ServiceImpl é‡Œï¼Œè€Œè¿™æ¡é“¾å·²ç»ä¸èµ°å®ƒäº†ã€‚
--
-- F6  â¸ æœªæ”¹  date_format(b.endtime,'%y%m%d') >= date_format(?, '%y%m%d') è¿™ç±»å†™æ³•
--     ï¼ˆå‡ºé™¢/入院时间范围过滤,本入参不涉及)对列用了函数,索引必失效。等价改法:
--       >= date_format(?, '%y%m%d')  ->  b.endtime >= DATE(?)
--       <= date_format(?, '%y%m%d')  ->  b.endtime <  DATE(?) + INTERVAL 1 DAY
--
-- å›žå½’检查:python sql\_probe\verify_patinhosp_list_count.py(17 é¡¹ï¼Œæ”¹å®Œè¯·è·‘一次)
-- ============================================================================
sql/patinhosp_select_list_slow_diagnose.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,222 @@
-- ============================================================================
-- /smartor/patinhosp/selectPatMedInhospList æ…¢ â€”— ä¸€é¡µè¯Šæ–­ï¼ˆæŠŠè¾“出贴回来即可定位)
--
-- çŽ°åœºå…¥å‚ï¼š{"bedSort":1,"pageNum":1,"pageSize":20,
--           "deptcodeList":["40003378"],"hospitaldistrictcodeList":["40003380"],"cry":0}
--
-- ä»£ç ä¾§å·²ç»åšå®Œçš„三件事(不用再查):
--   â‘  del_flag / inhospstate å·²æ”¹æˆå­—符串常量 â†’ ç´¢å¼•首列能用上了
--   â‘¡ åˆ†é¡µ count å·²æ”¹æˆæ‰‹å†™ _COUNT(pagehelper å®žæµ‹è§£æžåˆ° 5.3.2,该机制生效),
--      ä¸å†æŠŠ 60 åˆ—查询整个包一层重跑
--   â‘¢ ã€åœ¨é™¢å£å¾„】cry=0 çŽ°åœ¨åŒæ—¶å¸¦ endtime is null å’Œ inhospstate = '0'
--      â€”— è¿™æ˜¯è¿å‡ºå‰ PatMedInhospServiceImpl çš„行为,也是让索引第三列
--      (del_flag, orgid, inhospstate) çœŸæ­£ç­‰å€¼å‘½ä¸­çš„前提
-- å¦‚果【还是慢】,剩下的时间必然在这两处之一:
--   (甲) åœ¨é™¢å£å¾„选错了列 â€”— endtime IS NULL å‘½ä¸­çš„行数远大于真实在院人数
--        ï¼ˆHIS å¯¼å…¥çš„历史数据 endtime å¸¸å¹´ä¸ºç©ºï¼Œè¿™ä¸€æ¡æœ€å®¹æ˜“被忽略)
--   (乙) ç´¢å¼•没建 / å»ºäº†ä½†ç”¨ä¸ä¸Šï¼ˆç±»åž‹æˆ–字符集不一致,join é”®è¢«éšå¼è½¬æ¢ï¼‰
--
-- è·‘法:整段贴进 mysql å®¢æˆ·ç«¯ã€‚每个 SELECT çš„结果里客户端会打印
--       "1 row in set (0.0xx sec)",那个秒数就是我要的。
-- éœ€è¦æ›¿æ¢çš„占位符:<ORGID>(= ç™»å½•用户的 orgid)、<DEPT>、<DISTRICT>。
-- ============================================================================
-- ----------------------------------------------------------------------------
-- 0) ç‰ˆæœ¬ï¼ˆEXPLAIN ANALYZE éœ€è¦ 8.0.17+;5.7 å°±èµ°ç¬¬ 4 èŠ‚çš„æ™®é€š EXPLAIN)
-- ----------------------------------------------------------------------------
SELECT VERSION() AS mysql_version, @@sql_mode AS sql_mode;
-- ----------------------------------------------------------------------------
-- 1) ç´¢å¼•现状 + å…³é”®åˆ—类型/字符集(决定"索引能不能被用上")
-- ----------------------------------------------------------------------------
SELECT TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX, COLUMN_NAME, CARDINALITY, NON_UNIQUE
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
  AND TABLE_NAME IN ('pat_med_inhosp', 'pat_archive')
ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX;
-- âš ï¸ é‡ç‚¹çœ‹ä¸¤è¡Œï¼š
--    b.patid  ä¸Ž a.id  çš„ COLUMN_TYPE / COLLATION_NAME æ˜¯å¦ã€å®Œå…¨ä¸€è‡´ã€‘
--    ä¸ä¸€è‡´ = join é”®è¢«éšå¼è½¬æ¢ = pat_archive çš„主键索引用不上(见第 5 èŠ‚ï¼‰
SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, COLLATION_NAME, IS_NULLABLE
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
  AND ( (TABLE_NAME = 'pat_med_inhosp'
         AND COLUMN_NAME IN ('inhospid','patid','del_flag','orgid','campusid',
                             'inhospstate','endtime','starttime','bed_no',
                             'deptcode','hospitaldistrictcode'))
     OR (TABLE_NAME = 'pat_archive'
         AND COLUMN_NAME IN ('id','del_flag','idcardno','age','age_unit','telcode','sex')) )
ORDER BY TABLE_NAME, COLUMN_NAME;
SELECT TABLE_NAME, TABLE_ROWS, ROUND(DATA_LENGTH/1024/1024) AS data_mb,
       ROUND(INDEX_LENGTH/1024/1024) AS index_mb
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME IN ('pat_med_inhosp', 'pat_archive');
-- ----------------------------------------------------------------------------
-- 2) â˜…最关键:在院到底有多少行(两种口径对比)
--    å¦‚æžœ in_by_endtime è¿œå¤§äºŽ in_by_state,说明 endtime IS NULL ä¸æ˜¯"在院",
--    é‚£å†æ€Žä¹ˆåŠ ç´¢å¼•ä¹Ÿæ²¡æ•‘ â€”— å¿…须换成 inhospstate å£å¾„(代码一行)。
-- ----------------------------------------------------------------------------
SELECT
    COUNT(*)                                                              AS total_rows,
    SUM(del_flag = '0')                                                   AS alive_rows,
    SUM(del_flag = '0' AND endtime IS NULL)                               AS in_by_endtime,
    SUM(del_flag = '0' AND inhospstate = '0')                             AS in_by_state,
    SUM(del_flag = '0' AND endtime IS NULL AND IFNULL(inhospstate,'') <> '0') AS conflict_endtime_null_but_not_state0,
    SUM(del_flag = '0' AND inhospstate = '0' AND endtime IS NOT NULL)     AS conflict_state0_but_has_endtime
FROM pat_med_inhosp;
-- æ¢æˆ"该机构"再看一遍(口径分布可能因机构而异)
SELECT
    COUNT(*)                                                    AS org_alive_rows,
    SUM(endtime IS NULL)                                        AS org_in_by_endtime,
    SUM(inhospstate = '0')                                      AS org_in_by_state,
    SUM(endtime IS NULL AND IFNULL(inhospstate,'') <> '0')      AS org_conflict
FROM pat_med_inhosp
WHERE del_flag = '0' AND orgid = '<ORGID>';
-- å†çœ‹"该机构 + è¯¥é™¢åŒº + è¯¥ç§‘室"(就是这次入参的过滤组合)
SELECT
    COUNT(*)                                                    AS scope_alive_rows,
    SUM(endtime IS NULL)                                        AS scope_in_by_endtime,
    SUM(inhospstate = '0')                                      AS scope_in_by_state
FROM pat_med_inhosp
WHERE del_flag = '0' AND orgid = '<ORGID>'
  AND hospitaldistrictcode = '<DISTRICT>' AND deptcode = '<DEPT>';
-- ----------------------------------------------------------------------------
-- 3) åˆ†æ­¥è®¡æ—¶ï¼ˆä¸€æ¡ä¸€æ¡è·‘,看秒数落在哪一步)
-- ----------------------------------------------------------------------------
-- 3a åªæ‰« pat_med_inhosp,不 join、不 group by:这是"取数"的下限【新口径】
SELECT COUNT(*) AS c
FROM pat_med_inhosp b
WHERE b.del_flag = '0' AND b.orgid = '<ORGID>'
  AND b.hospitaldistrictcode = '<DISTRICT>' AND b.deptcode = '<DEPT>'
  AND b.endtime IS NULL AND b.inhospstate = '0';
-- 3g â˜…对照:同一条,但去掉 inhospstate(= æ”¹é€ å‰çš„口径)
--    3a ä¸Ž 3g çš„ç§’æ•°å·® = "补上 inhospstate åˆ°åº•救回来多少"。
--    å¦‚æžœ 3g è¿˜æ˜¯å‡ ç§’而 3a æ˜¯æ¯«ç§’级,说明病因就是它,到此为止;
SELECT COUNT(*) AS c
FROM pat_med_inhosp b
WHERE b.del_flag = '0' AND b.orgid = '<ORGID>'
  AND b.hospitaldistrictcode = '<DISTRICT>' AND b.deptcode = '<DEPT>'
  AND b.endtime IS NULL;
-- 3b åŠ ä¸Š join pat_archive(判 a.del_flag / a.idcardno):join è´µä¸è´µ
SELECT COUNT(*) AS c
FROM pat_med_inhosp b
JOIN pat_archive a ON a.id = b.patid
WHERE b.del_flag = '0' AND b.orgid = '<ORGID>'
  AND b.hospitaldistrictcode = '<DISTRICT>' AND b.deptcode = '<DEPT>'
  AND b.endtime IS NULL AND b.inhospstate = '0' AND a.del_flag = '0' AND a.idcardno IS NOT NULL;
-- 3c æ”¹é€ ã€å‰ã€‘çš„ count(60 åˆ— + GROUP BY æ•´ä¸ªåŒ…一层)—— è¿™å°±æ˜¯è¢«ä¼˜åŒ–掉的那条
SELECT COUNT(0) AS c FROM (
  SELECT a.age, a.telcode, a.sex, b.inhospid, b.patid, b.deptcode, b.bed_no, b.starttime
  FROM pat_med_inhosp b
  JOIN pat_archive a ON a.id = b.patid
  WHERE b.del_flag = '0' AND b.orgid = '<ORGID>'
    AND b.hospitaldistrictcode = '<DISTRICT>' AND b.deptcode = '<DEPT>'
    AND b.endtime IS NULL AND b.inhospstate = '0' AND a.del_flag = '0' AND a.idcardno IS NOT NULL
  GROUP BY b.inhospid, a.age, a.age_unit, a.telcode, a.id, a.sex
) tmp_count;
-- 3d æ•°æ®æŸ¥è¯¢ã€åŽ»æŽ‰ GROUP BY å’Œ ORDER BY】:纯取 20 è¡Œè¦å¤šä¹…
SELECT a.age, a.telcode, a.sex, b.inhospid, b.patid, b.deptcode, b.bed_no, b.starttime
FROM pat_med_inhosp b
JOIN pat_archive a ON a.id = b.patid
WHERE b.del_flag = '0' AND b.orgid = '<ORGID>'
  AND b.hospitaldistrictcode = '<DISTRICT>' AND b.deptcode = '<DEPT>'
  AND b.endtime IS NULL AND b.inhospstate = '0' AND a.del_flag = '0' AND a.idcardno IS NOT NULL
LIMIT 20;
-- 3e æ•°æ®æŸ¥è¯¢ã€åŠ  GROUP BY,不加 ORDER BY】:group by çš„代价
SELECT a.age, a.telcode, a.sex, b.inhospid, b.patid, b.deptcode, b.bed_no, b.starttime
FROM pat_med_inhosp b
JOIN pat_archive a ON a.id = b.patid
WHERE b.del_flag = '0' AND b.orgid = '<ORGID>'
  AND b.hospitaldistrictcode = '<DISTRICT>' AND b.deptcode = '<DEPT>'
  AND b.endtime IS NULL AND b.inhospstate = '0' AND a.del_flag = '0' AND a.idcardno IS NOT NULL
GROUP BY b.inhospid, a.age, a.age_unit, a.telcode, a.id, a.sex
LIMIT 20;
-- 3f çŽ°çŠ¶å…¨è²Œï¼ˆ= æŽ¥å£çœŸæ­£æ‰§è¡Œçš„那条):再加 ORDER BY
SELECT a.age, a.telcode, a.sex, b.inhospid, b.patid, b.deptcode, b.bed_no, b.starttime
FROM pat_med_inhosp b
JOIN pat_archive a ON a.id = b.patid
WHERE b.del_flag = '0' AND b.orgid = '<ORGID>'
  AND b.hospitaldistrictcode = '<DISTRICT>' AND b.deptcode = '<DEPT>'
  AND b.endtime IS NULL AND b.inhospstate = '0' AND a.del_flag = '0' AND a.idcardno IS NOT NULL
GROUP BY b.inhospid, a.age, a.age_unit, a.telcode, a.id, a.sex
ORDER BY b.starttime DESC, b.bed_no ASC
LIMIT 20;
-- è¯»æ³•:
--   3g æ…¢ã€3a å¿«      -> ç—…因就是"在院口径",本次补 inhospstate='0' å·²è§£å†³ï¼Œæ”¶å·¥
--   3a æ…¢             -> å–数就走错路了(索引缺失 / ç±»åž‹å­—符集不一致),看第 1、2、5 èŠ‚
--   3a å¿«ã€3b æ…¢      -> join æœ‰é—®é¢˜ï¼ˆpatid/id ç±»åž‹æˆ–字符集不一致,看第 5 èŠ‚ï¼‰
--   3b å¿«ã€3e æ…¢      -> GROUP BY çš„代价;可以确认"GROUP BY æ˜¯ç©ºæ“ä½œ"后删掉
--   3e å¿«ã€3f æ…¢      -> ORDER BY çš„ filesort ä»£ä»·ï¼›å¯è€ƒè™‘让索引顺序覆盖排序
--   3c æ˜Žæ˜¾æ…¢äºŽ 3b    -> è¯´æ˜Žè¿™æ¬¡çš„æ‰‹å†™ count æ”¹é€ ç¡®å®žçœæŽ‰äº†å¤§å¤´
-- ----------------------------------------------------------------------------
-- 4) æ‰§è¡Œè®¡åˆ’(MySQL 5.7 ç”¨æ™®é€š EXPLAIN;8.0.17+ ç›´æŽ¥ç”¨ EXPLAIN ANALYZE çœ‹çœŸå®žè€—时)
-- ----------------------------------------------------------------------------
EXPLAIN
SELECT a.age, a.telcode, a.sex, b.inhospid, b.patid, b.deptcode, b.bed_no, b.starttime
FROM pat_med_inhosp b
JOIN pat_archive a ON a.id = b.patid
WHERE b.del_flag = '0' AND b.orgid = '<ORGID>'
  AND b.hospitaldistrictcode = '<DISTRICT>' AND b.deptcode = '<DEPT>'
  AND b.endtime IS NULL AND b.inhospstate = '0' AND a.del_flag = '0' AND a.idcardno IS NOT NULL
GROUP BY b.inhospid, a.age, a.age_unit, a.telcode, a.id, a.sex
ORDER BY b.starttime DESC, b.bed_no ASC
LIMIT 20;
-- 8.0.17+ æ‰æœ‰ï¼šç›´æŽ¥ç»™å‡ºæ¯ä¸ªç®—子的实际行数/耗时,比 EXPLAIN æœ‰ç”¨å¾—多
-- EXPLAIN ANALYZE
-- SELECT ... (同上面这条)
-- çœ‹ EXPLAIN æ—¶åªç›¯ 4 åˆ—:
--   table  : ç¬¬ä¸€è¡Œæ˜¯ä¸æ˜¯ b(如果第一行是 a,说明优化器反着驱动,最坏情况)
--   type   : ALL / index = å…¨æ‰«ï¼ˆé—®é¢˜ï¼‰ï¼›ref / range = èµ°ç´¢å¼•(正常)
--   key    : å‘½ä¸­çš„索引名;NULL = æ²¡ç”¨ç´¢å¼•
--   rows   : ä¼°ç®—扫描行数(几万以上就是全机构历史,说明口径/索引仍不对)
-- ----------------------------------------------------------------------------
-- 5) join é”®ä¸€è‡´æ€§ï¼ˆç¬¬ 1 èŠ‚çœ‹ä¸æ˜Žç™½å°±è·‘è¿™æ¡ï¼Œç›´æŽ¥ç»™å‡º"会不会隐式转换"的结论)
-- ----------------------------------------------------------------------------
SELECT
    b_col.COLUMN_TYPE   AS b_patid_type,
    b_col.COLLATION_NAME AS b_patid_collation,
    a_col.COLUMN_TYPE   AS a_id_type,
    a_col.COLLATION_NAME AS a_id_collation,
    CASE
        WHEN a_col.COLUMN_TYPE = b_col.COLUMN_TYPE
         AND IFNULL(a_col.COLLATION_NAME,'') = IFNULL(b_col.COLLATION_NAME,'')
        THEN '✅ ä¸€è‡´ï¼Œjoin å¯ä»¥ç”¨ a.id ä¸»é”®'
        ELSE '❌ ä¸ä¸€è‡´ -> join é”®è¢«éšå¼è½¬æ¢ï¼Œpat_archive å¯èƒ½è¢«åå¤å…¨æ‰«'
    END AS verdict
FROM (SELECT COLUMN_TYPE, COLLATION_NAME FROM information_schema.COLUMNS
      WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'pat_med_inhosp' AND COLUMN_NAME = 'patid') b_col
JOIN (SELECT COLUMN_TYPE, COLLATION_NAME FROM information_schema.COLUMNS
      WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'pat_archive' AND COLUMN_NAME = 'id') a_col;
-- ============================================================================
-- è¦è´´å›žæ¥çš„东西(如果补了 inhospstate ä¹‹åŽè¿˜æ˜¯æ…¢ï¼ŒæŒ‰è¿™ä¸ªé¡ºåºï¼Œæœ€çœäº‹çš„三项就够):
--   â‘  ç¬¬ 3 èŠ‚ 3a å’Œ 3g çš„秒数(这两个一对比就知道病因是不是"在院口径")
--   â‘¡ ç¬¬ 2 èŠ‚ç¬¬ä¸€ç»„çš„å››ä¸ªæ•°å­—ï¼ˆtotal_rows / in_by_endtime / in_by_state / conflict...)
--   â‘¢ ç¬¬ 4 èŠ‚ EXPLAIN çš„ table / type / key / rows å››åˆ—
--   â‘£ éœ€è¦æ—¶å†è¡¥ç¬¬ 1 èŠ‚ç´¢å¼•æ¸…å•ä¸Žç¬¬ 5 èŠ‚çš„ join é”®ä¸€è‡´æ€§ç»“论
-- ============================================================================
sql/perf_sf_statistics_details_20261102.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,105 @@
-- ============================================================================
-- /smartor/serviceSubtask/getSfStatisticsCountDetails æ…¢æŸ¥è¯¢ä¼˜åŒ–(南华附一 nhfy)
--                                                          2026-11-02
--
-- ã€çŽ°è±¡ã€‘æ»¡æ„åº¦ç»Ÿè®¡é¡µç‚¹â€œæŸ¥çœ‹è¯¦æƒ…â€è¦ç­‰åå‡ ç§’åˆ°äºŒåå‡ ç§’ã€‚
--   è¯·æ±‚:{ "configKey":"joyCount", "leavehospitaldistrictcodes":["40003514"],
--           "serviceType":[6], "pageNum":1, "pageSize":20 }
--
-- ã€è¿™æ¡æŽ¥å£å®žé™…发了 5 æ¡ SQL】
--   ServiceSubtaskServiceImpl#getSfStatisticsScriptDetails åœ¨ type ä¸ºç©ºæ—¶ä¼šè·‘两趟:
--     type=1(语音)  â†’ getSfStatisticsScriptDetails + getSfStatisticsScriptItemDetails
--     type=2(问卷)  â†’ getSfStatisticsScriptDetails + getSfStatisticsScriptItemDetails
--   controller ä¸Šè¿˜æœ‰ PageUtils.startPageByPost(...),PageHelper ä¼šå¯¹ç¬¬ä¸€æ¡å†è¡¥ä¸€æ¬¡ COUNT。
--   æ¯ä¸€æ¡éƒ½åœ¨ service_subtask_detail(38 ä¸‡è¡Œï¼‰ä¸Šåšã€Œå­ä»»åŠ¡ JOIN é¢˜ç›®æ˜Žç»† GROUP BY」。
--
-- ã€æ ¹å› ï¼šè°“词把列包在函数里,索引全废】
--   ä¸ºäº†ã€Œå‡ºé™¢ç—…区为空回退到在院病区」,条件写成了
--       AND IFNULL(ss.leavehospitaldistrictcode, ss.hospitaldistrictcode) IN ('40003514')
--   åˆ—被 IFNULL åŒ…住 â†’ ä¼˜åŒ–器用不了任何索引,只能 orgid='20001001' çš„ 27864 æ¡å­ä»»åŠ¡å…¨æ‰«ï¼Œ
--   æ¯æ¡å†åŽ» detail è¡¨æŒ‰ sub_id æž 11 è¡Œï¼ˆâ‰ˆ30 ä¸‡è¡Œéšæœºè¯»ï¼‰ã€‚
--   EXPLAIN å®žæµ‹ï¼šss èµ° idx_service_subtask_orgid_visit_time,rows=27864。
--
-- ã€æ”¹æ³• 1:谓词展开成等价的 OR,让索引可用】
--       AND (ss.leavehospitaldistrictcode IN ('40003514')
--            OR (ss.leavehospitaldistrictcode IS NULL AND ss.hospitaldistrictcode IN ('40003514')))
--   è¯­ä¹‰ä¸Ž IFNULL å®Œå…¨ç­‰ä»·ï¼ˆ4 ä¸ªç—…区逐一比对明细行数:8873/95/4307/8550 å…¨éƒ¨ä¸€è‡´ï¼‰ã€‚
--   åªåšè¿™ä¸€æ­¥ï¼ˆä¸åŠ ç´¢å¼•ï¼‰ï¼š5.1s â†’ 1.9s,执行计划变成 ref_or_null,rows 27864 â†’ 3644。
--
-- ã€æ”¹æ³• 2:补两个索引,把范围再收窄】
--   æ˜Žç»†é¡µå›ºå®šæŒ‰ (病区, service_type, type) è¿‡æ»¤ï¼ŒåŽŸæ¥æ²¡æœ‰ä»»ä½•ç´¢å¼•èƒ½è¦†ç›–è¿™ä¸ªç»„åˆ
--   ï¼ˆservice_subtask ä¸Šåªæœ‰å•列 index,或 visit_time æ‰“头的复合索引)。
--
--   ç¦»é™¢ç—…区(含空值回退)→ idx_ss_district_pair
--   åœ¨é™¢ç—…区             â†’ idx_ss_hosp_dist_svc_type
--
-- ã€å®žæµ‹ï¼ˆ192.168.99.192 / smartor_nhfy,同一条件下的单条 SQL)】
--   ç¦»é™¢ç—…区 40003514:5120ms â†’ 1593ms(冷) / 728ms(热)
--   åœ¨é™¢ç—…区 40003514:4750ms â†’ 1854ms
--   æ˜Žç»†é€‰é¡¹ç»Ÿè®¡      ï¼š6183ms â†’ 629~1104ms
--   ä¸€æ¬¡è°ƒç”¨ 5 æ¡ SQL åˆè®¡ï¼šâ‰ˆ23s â†’ â‰ˆ2s
-- ============================================================================
-- ----------------------------------------------------------------------------
-- ã€ä¸€ã€‘DDL(每个环境都要单独执行;MySQL 8 æ”¯æŒ Online DDL,不会锁表)
-- ----------------------------------------------------------------------------
-- ALTER TABLE service_subtask
--     ADD INDEX idx_ss_district_pair
--         (leavehospitaldistrictcode, hospitaldistrictcode, service_type, type, del_flag),
--     ADD INDEX idx_ss_hosp_dist_svc_type
--         (hospitaldistrictcode, service_type, type, del_flag);
-- å•独执行写法(已在 192.168.99.192 æ‰§è¡Œè¿‡ï¼Œä¸¤æ¡å„ 5~8 ç§’):
-- CREATE INDEX idx_ss_district_pair      ON service_subtask (leavehospitaldistrictcode, hospitaldistrictcode, service_type, type, del_flag);
-- CREATE INDEX idx_ss_hosp_dist_svc_type ON service_subtask (hospitaldistrictcode, service_type, type, del_flag);
-- å›žæ»šï¼š
-- DROP INDEX idx_ss_district_pair      ON service_subtask;
-- DROP INDEX idx_ss_hosp_dist_svc_type ON service_subtask;
-- ----------------------------------------------------------------------------
-- ã€äºŒã€‘验证执行计划(执行 DDL åŽåº”看到 key = idx_ss_district_pair / idx_ss_hosp_dist_svc_type)
-- ----------------------------------------------------------------------------
-- EXPLAIN
-- SELECT ssd.scriptid, COUNT(*) AS all_quantity
-- FROM service_subtask_detail ssd
-- INNER JOIN service_subtask ss ON ssd.sub_id = ss.id
-- WHERE ss.del_flag = 0 AND ssd.del_flag = 0
--   AND ss.orgid = '20001001' AND ss.campusid = '30001002'
--   AND (ss.leavehospitaldistrictcode IN ('40003514')
--        OR (ss.leavehospitaldistrictcode IS NULL AND ss.hospitaldistrictcode IN ('40003514')))
--   AND ss.service_type IN (6) AND ss.type = '2'
--   AND ssd.categoryid IN (62,1000000012,5,1000000015,465)
-- GROUP BY ssd.scriptid;
-- ----------------------------------------------------------------------------
-- ã€ä¸‰ã€‘等价性自检:换写法前后每个病区的明细行数必须一模一样
-- ----------------------------------------------------------------------------
-- SELECT w.code,
--        (SELECT COUNT(*) FROM service_subtask ss JOIN service_subtask_detail ssd ON ssd.sub_id = ss.id
--         WHERE ss.del_flag=0 AND ssd.del_flag=0
--           AND ss.orgid='20001001' AND ss.campusid='30001002'
--           AND ss.service_type IN (6) AND ss.type='2'
--           AND IFNULL(ss.leavehospitaldistrictcode, ss.hospitaldistrictcode) IN (w.code))     AS ifnull_rows,
--        (SELECT COUNT(*) FROM service_subtask ss JOIN service_subtask_detail ssd ON ssd.sub_id = ss.id
--         WHERE ss.del_flag=0 AND ssd.del_flag=0
--           AND ss.orgid='20001001' AND ss.campusid='30001002'
--           AND ss.service_type IN (6) AND ss.type='2'
--           AND (ss.leavehospitaldistrictcode IN (w.code)
--                OR (ss.leavehospitaldistrictcode IS NULL AND ss.hospitaldistrictcode IN (w.code)))) AS or_rows
-- FROM (SELECT '40003514' AS code UNION ALL SELECT '40003309'
--       UNION ALL SELECT '40011024' UNION ALL SELECT '40003317') w;
-- å®žæµ‹ï¼š8873/8873、95/95、4307/4307、8550/8550 â€”— å®Œå…¨ä¸€è‡´ã€‚
-- ----------------------------------------------------------------------------
-- ã€å››ã€‘还没解决的两点(需要业务/前端配合,本次未动)
-- ----------------------------------------------------------------------------
-- 1) type ä¸ºç©ºæ—¶å›ºå®šè·‘两趟(语音 + é—®å·ï¼‰ã€‚南华满意度只可能是问卷(type=2),
--    è¯­éŸ³é‚£ä¸€è¶Ÿæ’为 0 è¡Œã€‚现在有索引后它只要 ~120ms,可以接受;
--    è‹¥è¦å½»åº•省掉,让前端明确传 type,或后端按 serviceType æŽ¨æ–­ã€‚
-- 2) PageHelper çš„ COUNT ä¼šå¯¹åŒä¸€æ¡é‡æŸ¥è¯¢å†è·‘一遍。这个结果集只有 19 è¡Œï¼Œ
--    å…¶å®žä¸éœ€è¦åˆ†é¡µã€‚要省就得去掉 startPageByPost,接口返回结构会变,先不动。
sql/rename_service_subtask_dims_20261106.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,105 @@
-- ============================================================================
-- service_subtask å­—段改名(出院维度 â†’ leavel*,复用 sendlimit* ç©ºåˆ— â†’ åœ¨é™¢ç—…区 / åœ¨é™¢ç§‘室)
-- ç”Ÿæˆæ—¥æœŸï¼š2026-11-06
-- é€‚用:MySQL 8.0(本仓库 SQL ç”¨åˆ° JSON_TABLE;5.7 è§æ–‡æœ«ã€Œå…¼å®¹å†™æ³•」)
--
-- æ”¹åæ˜ å°„:
--   deptcode        â†’ leaveldeptcode      ï¼ˆåŽŸæœ‰è¯­ä¹‰ = å‡ºé™¢ç§‘室)
--   deptname        â†’ leaveldeptname
--   sendlimitabegin â†’ hospitaldistrictcode(原「上午开始发送时间限制」,4 åˆ—均未被业务读写)
--   sendlimitaend   â†’ hospitaldistrictname(复用为「在院病区」,同 pat_med_inhosp.hospitaldistrictcode)
--   sendlimitpbegin â†’ deptcode            ï¼ˆå¤ç”¨ä¸ºã€Œåœ¨é™¢ç§‘室」)
--   sendlimitpend   â†’ deptname
--   sendlimitnbegin / sendlimitnend       â†’ ä¿ç•™ä¸åЍ
--
-- âš ï¸ é¡ºåºä¸èƒ½é¢ å€’:先把 deptcode/deptname è…¾å‡ºæ¥ï¼Œå†æŠŠ sendlimit* æ”¹æˆè¿™ä¸¤ä¸ªåå­—。
-- âš ï¸ RENAME COLUMN åŽŸåœ°ä¿ç•™æ•°æ®ï¼Œæ—§æ•°æ®çš„å‡ºé™¢ç§‘å®¤ä¼šè‡ªåŠ¨è·Ÿåˆ° leaveldeptcode/leaveldeptname。
-- ============================================================================
-- ----------------------------------------------------------------------------
-- 0) å…ˆçœ‹çŽ°çŠ¶ï¼ˆæŠŠç»“æžœè´´å‡ºæ¥ï¼Œæ ¸å¯¹é•¿åº¦/类型、确认 sendlimit* æ˜¯å¦å­˜åœ¨ï¼‰
-- ----------------------------------------------------------------------------
SHOW FULL COLUMNS FROM service_subtask LIKE '%dept%';
SHOW FULL COLUMNS FROM service_subtask LIKE 'sendlimit%';
SHOW FULL COLUMNS FROM pat_med_inhosp LIKE '%leaveldept%';
-- ----------------------------------------------------------------------------
-- 0.5) å¤‡ä»½ï¼ˆå¯é€‰ä½†å¼ºçƒˆå»ºè®®ï¼‰
-- ----------------------------------------------------------------------------
-- CREATE TABLE bak_service_subtask_rename_20261106 LIKE service_subtask;
-- INSERT INTO bak_service_subtask_rename_20261106 SELECT * FROM service_subtask;
-- ----------------------------------------------------------------------------
-- 1) å‡ºé™¢ç»´åº¦æ”¹åï¼ˆè…¾å‡º deptcode / deptname ä¸¤ä¸ªåå­—)
-- ----------------------------------------------------------------------------
ALTER TABLE service_subtask
  RENAME COLUMN deptcode TO leaveldeptcode,
  RENAME COLUMN deptname TO leaveldeptname;
-- ----------------------------------------------------------------------------
-- 2) 4 ä¸ªç©ºé—²åˆ—改成新语义
-- ----------------------------------------------------------------------------
ALTER TABLE service_subtask
  RENAME COLUMN sendlimitabegin  TO hospitaldistrictcode,
  RENAME COLUMN sendlimitaend   TO hospitaldistrictname,
  RENAME COLUMN sendlimitpbegin TO deptcode,
  RENAME COLUMN sendlimitpend   TO deptname;
-- ----------------------------------------------------------------------------
-- 3) é•¿åº¦/类型校准(必须)
--    RENAME COLUMN ä¼šæ²¿ç”¨ sendlimit* åŽŸæ¥çš„ç±»åž‹ï¼ˆé€šå¸¸æ˜¯ VARCHAR(10) ç”šè‡³ TIME),
--    è£…在院科室/在院病区编码与名称会不够。下面按推荐长度放大,不会缩小已有列。
--    è‹¥ä½ åº“里 deptcode/deptname åŽŸæ¥æ˜¯æ›´é•¿çš„ VARCHAR(255),请把 64/128 è°ƒå¤§è€Œä¸æ˜¯è°ƒå°ã€‚
-- ----------------------------------------------------------------------------
ALTER TABLE service_subtask
  MODIFY COLUMN hospitaldistrictcode VARCHAR(64)  NULL COMMENT '在院病区编码(原 sendlimitabegin)',
  MODIFY COLUMN hospitaldistrictname VARCHAR(128) NULL COMMENT '在院病区名称(原 sendlimitaend)',
  MODIFY COLUMN deptcode             VARCHAR(64)  NULL COMMENT '在院科室编码(原 sendlimitpbegin)',
  MODIFY COLUMN deptname             VARCHAR(128) NULL COMMENT '在院科室名称(原 sendlimitpend)';
-- ----------------------------------------------------------------------------
-- 3.5) å¦‚果第 3 æ­¥ä¹‹å‰å·²ç»æ‰§è¡Œè¿‡ï¼ˆåˆ—注释写成了「院区」),只修注释、不动数据:
-- ----------------------------------------------------------------------------
-- ALTER TABLE service_subtask
--   MODIFY COLUMN hospitaldistrictcode VARCHAR(64)  NULL COMMENT '在院病区编码(原 sendlimitabegin)',
--   MODIFY COLUMN hospitaldistrictname VARCHAR(128) NULL COMMENT '在院病区名称(原 sendlimitaend)';
-- ----------------------------------------------------------------------------
-- 4) æ¸…掉 sendlimit* å¯èƒ½æ®‹ç•™çš„æ—¶é—´ä¸²ï¼ˆå¦‚ '08:00:00')
--    å…ˆ SELECT ç¡®è®¤è¿™äº›åˆ—本来就无有效数据,再执行 UPDATE。
-- ----------------------------------------------------------------------------
-- SELECT COUNT(*) AS total,
--        SUM(hospitaldistrictcode IS NOT NULL AND hospitaldistrictcode <> '') AS hd_code,
--        SUM(hospitaldistrictname IS NOT NULL AND hospitaldistrictname <> '') AS hd_name,
--        SUM(deptcode IS NOT NULL AND deptcode <> '') AS dept_code,
--        SUM(deptname IS NOT NULL AND deptname <> '') AS dept_name
--   FROM service_subtask;
-- UPDATE service_subtask
--    SET hospitaldistrictcode = NULL, hospitaldistrictname = NULL,
--        deptcode = NULL, deptname = NULL;
-- ----------------------------------------------------------------------------
-- 5) æ ¸å¯¹
-- ----------------------------------------------------------------------------
SHOW FULL COLUMNS FROM service_subtask LIKE '%dept%';
SHOW FULL COLUMNS FROM service_subtask LIKE 'hospitaldistrict%';
-- ============================================================================
-- å¤‡é€‰ A:sendlimit* åˆ—在库里不存在(第 2 æ­¥æŠ¥ 1054)时,改成新增
-- ============================================================================
-- ALTER TABLE service_subtask
--   ADD COLUMN hospitaldistrictcode VARCHAR(64)  NULL COMMENT '院区/病区编码',
--   ADD COLUMN hospitaldistrictname VARCHAR(128) NULL COMMENT '院区/病区名称',
--   ADD COLUMN deptcode             VARCHAR(64)  NULL COMMENT '科室编码(入院/在院维度)',
--   ADD COLUMN deptname             VARCHAR(128) NULL COMMENT '科室名称(入院/在院维度)';
-- ============================================================================
-- å¤‡é€‰ B:MySQL 5.7(不支持 RENAME COLUMN)→ ç”¨ CHANGE,定义要与原列一致
-- ============================================================================
-- ALTER TABLE service_subtask
--   CHANGE COLUMN deptcode        leaveldeptcode       <原 deptcode ç±»åž‹> NULL COMMENT '出院科室编码',
--   CHANGE COLUMN deptname        leaveldeptname       <原 deptname ç±»åž‹> NULL COMMENT '出院科室名称',
--   CHANGE COLUMN sendlimitabegin hospitaldistrictcode VARCHAR(64)  NULL COMMENT '院区/病区编码',
--   CHANGE COLUMN sendlimitaend   hospitaldistrictname VARCHAR(128) NULL COMMENT '院区/病区名称',
--   CHANGE COLUMN sendlimitpbegin deptcode             VARCHAR(64)  NULL COMMENT '科室编码',
--   CHANGE COLUMN sendlimitpend   deptname             VARCHAR(128) NULL COMMENT '科室名称';
sql/service_out_path_radix_shortcode_20261106.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
-- =============================================================================
-- å¤–链短码改造(2026-11-06)
--   åŽŸçŸ­ç ï¼šString.format("%03X", id)   â€”— 3~4 ä½åå…­è¿›åˆ¶ï¼Œç­‰äºŽæŠŠè‡ªå¢žä¸»é”®å†™è¿›é“¾æŽ¥ï¼Œå¯éåކ
--   æ–°çŸ­ç ï¼šShortCode.encode(id)        â€”— å›ºå®š 6 ä½ base62,带密钥 Feistel åŒå°„,不可枚举
--
-- âš ï¸ æ‰§è¡Œé¡ºåºï¼š**先执行本脚本第 2 æ­¥ï¼Œå†å‘布新版本的 jar**。
--    å¦åˆ™å†™ 6 ä½çŸ­ç æ—¶ï¼š
--      * MySQL ä¸¥æ ¼æ¨¡å¼ï¼ˆ5.7+ é»˜è®¤ï¼‰â†’ UPDATE æŠ¥ "Data too long for column 'radix'",短信发不出去;
--      * éžä¸¥æ ¼æ¨¡å¼ â†’ é™é»˜æˆªæ–­æˆ 4 ä½ï¼Œradix åˆ—变脏数据
--        ï¼ˆçº¿ä¸Šè¿˜èƒ½é  getInfoByParam åè§£ä¸»é”®å…œåº•,但不应依赖这个兜底)。
--
-- æœ¬è„šæœ¬å¯é‡å¤æ‰§è¡Œã€‚
-- =============================================================================
-- 1) å…ˆçœ‹å½“前列定义(记录原始值,便于回滚)
SHOW COLUMNS FROM service_out_path LIKE 'radix';
-- 2) åˆ—宽不足 6 ä½æ—¶æ‰§è¡Œï¼›å·² >= 16 ä½å¯æ•´å¥è·³è¿‡
ALTER TABLE service_out_path
    MODIFY COLUMN radix VARCHAR(16) NULL
    COMMENT '外链短码:新数据为 6 ä½ base62,历史数据为 3~5 ä½åå…­è¿›åˆ¶ï¼›getInfoByParam ä¸¤ç§éƒ½è®¤';
-- 3) æ‰§è¡ŒåŽæ ¸å¯¹
SHOW COLUMNS FROM service_out_path LIKE 'radix';
-- 4) è§‚察存量分布(上线后新产生的短码应全部是 6 ä½ï¼‰
-- SELECT CHAR_LENGTH(radix) AS len, COUNT(*) AS cnt
-- FROM service_out_path
-- WHERE radix IS NOT NULL AND radix <> ''
-- GROUP BY CHAR_LENGTH(radix)
-- ORDER BY len;
sql/service_subtask_index_opt.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,146 @@
-- =====================================================================
-- service_subtask ç´¢å¼•优化脚本
-- ç›®æ ‡æŽ¥å£ï¼šPOST /smartor/serviceSubtask/patItem(及共用同一套 SQL çš„ /patItemCount、/patItemExport)
--
-- æ¶‰åŠçš„两条 SQL(都在 ruoyi-task/src/main/resources/mapper/task/ServiceSubtaskMapper.xml):
--   1) id = selectServiceSubtaskList   â€”— åˆ—表,约 100 åˆ— + order by + limit offset
--   2) id = countByCondition           â€”— åŒä¸€å¥— where çš„ SUM èšåˆï¼ˆRedis æœªå‘½ä¸­æ—¶æ‰§è¡Œï¼‰
--
-- å…¼å®¹æ€§ï¼šMySQL 5.7 / 8.0 å‡å¯æ‰§è¡Œã€‚
--   * 5.7 ä¸æ”¯æŒé™åºç´¢å¼•,但 ASC ç´¢å¼•可被反向扫描,order by ... desc åŒæ ·èƒ½ç”¨ï¼Œæ— éœ€é¢å¤–处理;
--   * 5.7 æ²¡æœ‰ EXPLAIN ANALYZE,验证用文末的 EXPLAIN + Handler çŠ¶æ€å˜é‡ã€‚
-- =====================================================================
-- ---------------------------------------------------------------------
-- 0. æ‰§è¡Œå‰å…ˆçœ‹çŽ°çŠ¶ï¼ˆé¿å…é‡å¤å»ºç´¢å¼• / ç¡®è®¤æ•°æ®é‡ä¸Žåˆ—类型)
-- ---------------------------------------------------------------------
SHOW INDEX FROM service_subtask;
SELECT COUNT(*) AS total_rows FROM service_subtask;
SELECT COLUMN_NAME, COLUMN_TYPE
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
  AND TABLE_NAME = 'service_subtask'
  AND COLUMN_NAME IN ('orgid', 'campusid', 'del_flag', 'endtime',
                      'visit_time', 'sendstate', 'taskid', 'create_time',
                      'patid', 'inhospid', 'schedule_id', 'loop_count');
-- ---------------------------------------------------------------------
-- 1. å¿…加:列表主查询 / èšåˆçš„公共前缀 + æŽ’序
--
--    where å›ºå®šæ˜¯ del_flag = 0 AND orgid = ? AND campusid = ?(Controller å¼ºåˆ¶æ³¨å…¥ï¼‰
--    order by é»˜è®¤æ˜¯ endtime asc/desc
--    â†’ å‘½ä¸­åŽ order by ä¸å† filesort,且 limit å¯ä»¥æå‰æˆªæ–­ï¼›
--      åŒæ—¶è®© countByCondition ä»Žã€Œå…¨è¡¨æ‰«æã€é™ä¸ºã€Œä¸€ä¸ªæœºæž„+院区的范围扫描」。
-- ---------------------------------------------------------------------
ALTER TABLE service_subtask
    ADD INDEX idx_ss_org_cam_del_endtime (orgid, campusid, del_flag, endtime),
    ALGORITHM = INPLACE, LOCK = NONE;
-- ---------------------------------------------------------------------
-- 2. å¿…加:按随访时间排序/筛选(sort=2/3/7/8,以及 startSendDateTime/endSendDateTime åŒºé—´ï¼‰
--    æ³¨ï¼šè¿™ä¸¤ä¸ªåŒºé—´æ¡ä»¶å·²ç”± date_format(...) æ”¹å†™ä¸ºå¯èµ°ç´¢å¼•的范围比较。
-- ---------------------------------------------------------------------
ALTER TABLE service_subtask
    ADD INDEX idx_ss_org_cam_del_vtime (orgid, campusid, del_flag, visit_time),
    ALGORITHM = INPLACE, LOCK = NONE;
-- ---------------------------------------------------------------------
-- 3. å¿…加:状态页签(sendstateView â†’ sendstate IN (1,2,3,5,7) / (6) / (4))
--    ç”¨æˆ·æœ€å¸¸ç‚¹çš„就是「待随访 / å·²å®Œæˆ / æ— éœ€éšè®¿ã€ä¸‰ä¸ª tab。
-- ---------------------------------------------------------------------
ALTER TABLE service_subtask
    ADD INDEX idx_ss_org_cam_del_state_end (orgid, campusid, del_flag, sendstate, endtime),
    ALGORITHM = INPLACE, LOCK = NONE;
-- ---------------------------------------------------------------------
-- 4. å»ºè®®ï¼šä»»åŠ¡ç»´åº¦ä¸‹é’»ï¼ˆtaskid æ˜¯è¿™å¼ è¡¨æœ€é«˜é¢‘的等值条件)
-- ---------------------------------------------------------------------
ALTER TABLE service_subtask
    ADD INDEX idx_ss_org_cam_del_task (orgid, campusid, del_flag, taskid),
    ALGORITHM = INPLACE, LOCK = NONE;
-- ---------------------------------------------------------------------
-- 5. å»ºè®®ï¼šcreate_time åŒºé—´
--    ä¾› selectServiceSubtaskListByCreateTime / selectServiceSubtaskListAgain ä½¿ç”¨ã€‚
-- ---------------------------------------------------------------------
ALTER TABLE service_subtask
    ADD INDEX idx_ss_org_cam_del_ctime (orgid, campusid, del_flag, create_time),
    ALGORITHM = INPLACE, LOCK = NONE;
-- ---------------------------------------------------------------------
-- 6. å»ºè®®ï¼šæ‚£è€… / ä½é™¢è®°å½•反查(患者端、再次随访、转科链路)
-- ---------------------------------------------------------------------
ALTER TABLE service_subtask
    ADD INDEX idx_ss_patid (patid),
    ALGORITHM = INPLACE, LOCK = NONE;
ALTER TABLE service_subtask
    ADD INDEX idx_ss_inhospid (inhospid),
    ALGORITHM = INPLACE, LOCK = NONE;
-- ---------------------------------------------------------------------
-- 7. å»ºè®®ï¼šä»»åŠ¡ç»„æ»šåŠ¨è°ƒåº¦ï¼ˆcountActiveByScheduleIdAndLoop,跑得非常勤)
-- ---------------------------------------------------------------------
ALTER TABLE service_subtask
    ADD INDEX idx_ss_sched_loop (schedule_id, loop_count),
    ALGORITHM = INPLACE, LOCK = NONE;
-- ---------------------------------------------------------------------
-- 8. å¯é€‰ï¼šè®© countByCondition å˜æˆè¦†ç›–索引(索引内完成聚合,不回表)
--    åªæœ‰å½“ Redis å‘½ä¸­çŽ‡ä½Žã€ç»Ÿè®¡æŽ¥å£ä»ç„¶æ…¢æ—¶æ‰åŠ ã€‚
--    ä»£ä»·ï¼šservice_subtask å†™å…¥é¢‘繁,多一个宽索引会加大写入开销。
-- ---------------------------------------------------------------------
-- ALTER TABLE service_subtask
--     ADD INDEX idx_ss_org_cam_del_state_excep (orgid, campusid, del_flag, sendstate, excep),
--     ALGORITHM = INPLACE, LOCK = NONE;
-- ---------------------------------------------------------------------
-- 9. æ”¶å°¾ï¼šæ›´æ–°ç»Ÿè®¡ä¿¡æ¯ï¼Œè®©ä¼˜åŒ–器立刻用上新索引
-- ---------------------------------------------------------------------
ANALYZE TABLE service_subtask;
-- =====================================================================
-- éªŒè¯ï¼ˆMySQL 5.7 å¯ç”¨ï¼‰
-- =====================================================================
-- 9.1 åˆ—表主查询:期望 key = idx_ss_org_cam_del_endtime,Extra é‡Œã€æ²¡æœ‰ã€‘Using filesort
EXPLAIN
SELECT id, endtime
FROM service_subtask
WHERE del_flag = 0
  AND orgid = '这里换成真实 orgid'
  AND campusid = '这里换成真实 campusid'
ORDER BY endtime DESC
LIMIT 20 OFFSET 0;
-- 9.2 çŠ¶æ€é¡µç­¾ï¼šæœŸæœ› key = idx_ss_org_cam_del_state_end
EXPLAIN
SELECT id, endtime
FROM service_subtask
WHERE del_flag = 0
  AND orgid = '这里换成真实 orgid'
  AND campusid = '这里换成真实 campusid'
  AND sendstate IN (1, 2, 3, 5, 7)
ORDER BY endtime DESC
LIMIT 20 OFFSET 0;
-- 9.3 å®žé™…扫描量对比(执行前后各跑一次,看 Handler_read_next æ˜¯å¦å¤§å¹…下降)
FLUSH STATUS;
SELECT COUNT(1) FROM service_subtask
WHERE del_flag = 0 AND orgid = '这里换成真实 orgid' AND campusid = '这里换成真实 campusid';
SHOW SESSION STATUS LIKE 'Handler_read%';
-- =====================================================================
-- å›žæ»š
-- =====================================================================
-- ALTER TABLE service_subtask DROP INDEX idx_ss_org_cam_del_endtime;
-- ALTER TABLE service_subtask DROP INDEX idx_ss_org_cam_del_vtime;
-- ALTER TABLE service_subtask DROP INDEX idx_ss_org_cam_del_state_end;
-- ALTER TABLE service_subtask DROP INDEX idx_ss_org_cam_del_task;
-- ALTER TABLE service_subtask DROP INDEX idx_ss_org_cam_del_ctime;
-- ALTER TABLE service_subtask DROP INDEX idx_ss_patid;
-- ALTER TABLE service_subtask DROP INDEX idx_ss_inhospid;
-- ALTER TABLE service_subtask DROP INDEX idx_ss_sched_loop;
-- ALTER TABLE service_subtask DROP INDEX idx_ss_org_cam_del_state_excep;
sql/sltdMydTotalByDimension_index_20260924.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,139 @@
-- ============================================================================
-- smartor_sltd æ•°æ®åº“ æ»¡æ„åº¦ç»´åº¦ç»Ÿè®¡æŽ¥å£æ€§èƒ½ä¼˜åŒ– â€”— ç´¢å¼•脚本
-- ç”Ÿæˆæ—¥æœŸ: 20260924
-- ç›®æ ‡æŽ¥å£: POST /smartor/serviceSubtaskDetail/sltdMydTotalByDimension
--           (dimensiontType=1 â†’ ServiceSubtaskMapper.sltdMydTotalByDimension
--            dimensiontType=2 â†’ ServiceSubtaskMapper.sltdMydTotalByYQ,同样受益)
-- ç›®æ ‡ SQL: smartor/src/main/resources/mapper/smartor/ServiceSubtaskMapper.xml:3050
--
-- ã€é—®é¢˜å®šä½ã€‘(线上库实测:MySQL 5.7.34,service_subtask_detail 102,249 è¡Œ /
--  service_task 153 è¡Œï¼Œä¸¤å¼ è¡¨é™¤ä¸»é”®å¤–没有任何二级索引)
--
--  EXPLAIN ç»“果显示 4 ä¸ªè®¿é—®è·¯å¾„全部是 type=ALL å…¨è¡¨æ‰«æ + Block Nested Loop:
--    id=2 DERIVED  service_subtask_detail  ALL  rows=101778   -- period æ—¶é—´æ®µå­æŸ¥è¯¢
--    id=3 DERIVED  service_subtask_detail  ALL  rows=101778   -- dimension ç»´åº¦å­æŸ¥è¯¢
--    id=1 PRIMARY  t (service_task)        ALL  rows=153
--    id=1 PRIMARY  d (service_subtask_detail) ALL rows=101778 -- Using join buffer
--
--  å³ï¼šä¸»æŸ¥è¯¢é‡Œ d.taskid = t.taskid æ²¡æœ‰ç´¢å¼•,102,778 è¡Œçš„æ˜Žç»†è¡¨è¢«å½“成 BNL å†…表
--  åå¤å…¨è¡¨æ‰«æï¼Œè¿™æ˜¯æŽ¥å£æ…¢çš„æ ¹æœ¬åŽŸå› ï¼›ä¸¤ä¸ªæ´¾ç”Ÿè¡¨ä¹Ÿå„è‡ªå…¨è¡¨æ‰«æä¸€æ¬¡ã€‚
-- ============================================================================
-- ----------------------------------------------------------------------------
-- ã€æ‰§è¡Œå‰æ£€æŸ¥ã€‘确认当前确实没有这些索引(正常应只看到 PRIMARY)
-- ----------------------------------------------------------------------------
-- SHOW INDEX FROM service_subtask_detail;
-- SHOW INDEX FROM service_task;
-- SELECT COUNT(*) FROM service_subtask_detail;   -- çº¦ 10 ä¸‡è¡Œï¼ŒåŠ ç´¢å¼•çº¦éœ€ 10~60 ç§’
-- ----------------------------------------------------------------------------
-- ã€æ­¥éª¤ 1】service_subtask_detail å¢žåŠ  3 ä¸ªç´¢å¼•
--
--  1) idx_ssd_taskid_flag_dim_ct (taskid, del_flag, dimension, create_time)
--     ç”¨é€”:主查询的 JOIN æ¡ä»¶ d.taskid = t.taskid
--           + d.del_flag = '0' + d.dimension = dim.dimension(等值)
--           + d.create_time åŒºé—´ï¼ˆèŒƒå›´ï¼‰ã€‚
--     æ•ˆæžœï¼šç”±ã€Œå…¨è¡¨æ‰«æ 10 ä¸‡è¡Œã€å˜ä¸ºã€ŒæŒ‰ taskid ç²¾ç¡®å®šä½çº¦ 700 è¡Œã€ã€‚
--
--  2) idx_ssd_ct_flag (create_time, del_flag)
--     ç”¨é€”:period æ—¶é—´æ®µå­æŸ¥è¯¢
--           WHERE del_flag='0' AND create_time >= ? AND create_time < ?
--     æ•ˆæžœï¼šcreate_time èµ°èŒƒå›´æ‰«æï¼›del_flag è™½åœ¨ç´¢å¼•第二位但仍可做
--           Index Condition Pushdown è¿‡æ»¤ï¼Œä¸å¿…回表,省掉 10 ä¸‡è¡Œå›žè¡¨ã€‚
--     ï¼ˆæ³¨æ„ï¼šdel_flag å¿…须放第二位,把 create_time æ”¾ç¬¬ä¸€ä½æ‰èƒ½ç”¨ä¸ŠèŒƒå›´æ‰«æï¼‰
--
--  3) idx_ssd_flag_dim (del_flag, dimension)
--     ç”¨é€”:dim ç»´åº¦å­æŸ¥è¯¢
--           SELECT DISTINCT dimension FROM service_subtask_detail
--           WHERE del_flag='0' AND dimension IS NOT NULL
--     æ•ˆæžœï¼šç´¢å¼•覆盖该子查询,可走松散索引扫描(Using index for group-by),
--           dimension åªæœ‰ 16 ä¸ªä¸åŒå€¼ï¼ŒåŸºæœ¬çž¬é—´è¿”回。
--
--  MySQL 5.7 çš„ ALTER TABLE åŠ äºŒçº§ç´¢å¼•æ”¯æŒ ONLINE DDL(不锁表、不阻塞读写),
--  ä¸‹é¢æ˜¾å¼å£°æ˜Ž ALGORITHM=INPLACE, LOCK=NONE;三条索引放在同一条 ALTER é‡Œï¼Œ
--  åªéœ€æ‰«æä¸€æ¬¡è¡¨ï¼Œæ¯”分开执行快。若中途报错不支持,去掉这两个选项重试即可。
-- ----------------------------------------------------------------------------
ALTER TABLE service_subtask_detail
    ADD INDEX idx_ssd_taskid_flag_dim_ct (taskid, del_flag, dimension, create_time),
    ADD INDEX idx_ssd_ct_flag (create_time, del_flag),
    ADD INDEX idx_ssd_flag_dim (del_flag, dimension),
    ALGORITHM = INPLACE,
    LOCK = NONE;
-- ----------------------------------------------------------------------------
-- ã€æ­¥éª¤ 2】service_task å¢žåŠ  1 ä¸ªç´¢å¼•
--
--  4) idx_st_type_flag (service_type, del_flag)
--     ç”¨é€”:LEFT JOIN service_task t ON t.service_type IN (6,14) AND t.del_flag='0'
--     æ•ˆæžœï¼šservice_task åªæœ‰ 153 è¡Œï¼Œæ”¶ç›Šæœ‰é™ï¼ˆ153 è¡Œ â†’ 46 è¡Œï¼‰ï¼Œ
--           ä½†èƒ½è®©è¯¥è¡¨ä¹Ÿèµ°ç´¢å¼•、避免作为 BNL å¤–表全扫,属于顺带优化。
-- ----------------------------------------------------------------------------
ALTER TABLE service_task
    ADD INDEX idx_st_type_flag (service_type, del_flag),
    ALGORITHM = INPLACE,
    LOCK = NONE;
-- ----------------------------------------------------------------------------
-- ã€æ­¥éª¤ 3】更新统计信息(加完索引后执行一次,让优化器选对索引)
-- ----------------------------------------------------------------------------
ANALYZE TABLE service_subtask_detail;
ANALYZE TABLE service_task;
-- ----------------------------------------------------------------------------
-- ã€éªŒè¯ã€‘执行下面两条 EXPLAIN,关键看:
--   * ä¸å†å‡ºçް type=ALL å…¨è¡¨æ‰«æï¼ˆé™¤æ´¾ç”Ÿè¡¨å¤–)
--   * d è¡Œå‡ºçް type=ref / key=idx_ssd_taskid_flag_dim_ct
--   * service_task è¡Œ key=idx_st_type_flag
-- ----------------------------------------------------------------------------
-- EXPLAIN
-- SELECT p.period, dim.dimension, IFNULL(ROUND(SUM(d.score), 2), 0) AS totalScore
-- FROM (SELECT DISTINCT DATE_FORMAT(d.create_time, '%Y-%m') AS period
--       FROM service_subtask_detail d
--       WHERE d.del_flag = '0' AND d.create_time >= '2025-01-01' AND d.create_time < '2026-12-31') p
-- CROSS JOIN (SELECT DISTINCT dimension FROM service_subtask_detail
--             WHERE del_flag = '0' AND dimension IS NOT NULL) dim
-- LEFT JOIN service_task t ON t.service_type IN (6, 14) AND t.del_flag = '0'
-- LEFT JOIN service_subtask_detail d
--        ON d.taskid = t.taskid AND d.del_flag = '0' AND d.dimension = dim.dimension
--       AND DATE_FORMAT(d.create_time, '%Y-%m') = p.period
--       AND d.create_time >= '2025-01-01' AND d.create_time < '2026-12-31'
-- GROUP BY p.period, dim.dimension ORDER BY p.period, dim.dimension;
-- ----------------------------------------------------------------------------
-- ã€å›žæ»šã€‘如需撤销,执行以下语句
-- ----------------------------------------------------------------------------
-- ALTER TABLE service_subtask_detail DROP INDEX idx_ssd_taskid_flag_dim_ct;
-- ALTER TABLE service_subtask_detail DROP INDEX idx_ssd_ct_flag;
-- ALTER TABLE service_subtask_detail DROP INDEX idx_ssd_flag_dim;
-- ALTER TABLE service_task DROP INDEX idx_st_type_flag;
-- ============================================================================
-- ã€é‡è¦ã€‘代码侧必须同步修正 del_flag çš„写法,否则索引只能用上一半
--
-- ServiceSubtaskMapper.xml ä¸­å†™çš„æ˜¯ï¼š  d.del_flag = 0
-- ä½† del_flag çš„列类型是 char(10)(取值 '0' / '1')。
-- MySQL åœ¨ã€Œå­—符串列 = æ•°å­—常量」时会按 DOUBLE éšå¼è½¬æ¢æ¯”较,
-- å¯¼è‡´è¯¥æ¡ä»¶æ— æ³•作为索引的等值条件使用(只能过滤,不能定位)。
-- ä¸Šé¢ 3 ä¸ªç´¢å¼•里 del_flag éƒ½æ˜¯ç­‰å€¼åˆ—,改成下面这样才算真正生效:
--
--   d.del_flag  = 0   â†’   d.del_flag  = '0'
--   del_flag    = 0   â†’   del_flag    = '0'
--   t.del_flag  = 0   â†’   t.del_flag  = '0'
--
-- é€‚用范围(同一文件内同样问题的查询,建议一起改):
--   * sltdMydTotalByDimension   (3050 è¡Œé™„è¿‘)
--   * sltdMydTotalByYQ          (3097 è¡Œé™„è¿‘)
--   * mydTrend                  (3030 è¡Œé™„è¿‘)
-- è¯­ä¹‰å½±å“ï¼š'0' ä¸Ž 0 çš„匹配结果在本库中完全一致(del_flag å®žæµ‹åªæœ‰ '0'、'1'
-- ä¸¤ç§å–值,无 NULL、无空串),改成字符串比较不会改变业务结果。
--
-- å¦ï¼šæœ¬æŽ¥å£ä¸¤æ¡ SQL çš„ LEFT JOIN service_task t åŽé¢æ²¡æœ‰è·Ÿä»»ä½•关联条件
-- ï¼ˆON é‡Œåªæœ‰ t è‡ªå·±çš„过滤条件),等价于先对 service_task åšä¸€æ¬¡è¿‡æ»¤å†äº¤å‰
-- è¿žæŽ¥ï¼Œæœ¬èº«å†™æ³•不规范;本次只加索引、不动 SQL,避免影响返回结构。
-- ============================================================================
sql/sync_svy_lib_default_value_to_task_20261106.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,282 @@
-- ============================================================================
-- æŠŠã€Œé—®å·åº“模板」题目的选项默认值(svy_lib_template_targetoption.default_value)
-- åŒæ­¥åˆ°ã€Œé—®å·ä»»åŠ¡æ¨¡æ¿ã€é¢˜ç›®é€‰é¡¹ï¼ˆsvy_task_template_targetoption.default_value)
--
-- è¡¨åè¯´æ˜Žï¼ˆé—®é¢˜é€‰é¡¹çš„默认值其实不在 script è¡¨é‡Œï¼Œè€Œåœ¨ option è¡¨é‡Œï¼‰ï¼š
--   åº“   é¢˜ç›® svy_lib_template_script  â”€â”€ id â† scriptid â”€â”€ åº“   é€‰é¡¹ svy_lib_template_targetoption
--   ä»»åŠ¡ é¢˜ç›® svy_task_template_script â”€â”€ id â† scriptid â”€â”€ ä»»åŠ¡ é€‰é¡¹ svy_task_template_targetoption
--   default_value è¯­ä¹‰ï¼š1=否(默认值),2=是(默认选项),NULL=未设置
--
-- ä»»åŠ¡æ¨¡æ¿ â†’ åº“模板 çš„æ˜ å°„链(实测 smartor_nhfy):
--   svy_task_template_script.templateid
--     = svy_task_template.id
--     â†’ svy_task_template.templateid = svy_lib_template.svyid
--     â†’ svy_lib_template_script.svyid,题面 script_content ç›¸åŒå³åŒä¸€é“题
--     â†’ svy_lib_template_targetoption.scriptid = svy_lib_template_script.id
--        ä¸” optioncontent ç›¸åŒå³åŒä¸€ä¸ªé€‰é¡¹
--
-- å®žæµ‹è¦†ç›–(2026-11-06,smartor_nhfy):
--   ä»»åŠ¡æ¨¡æ¿ 710 ä¸ªï¼Œèƒ½æ˜ å°„到库模板的 663 ä¸ªï¼ˆå¦å¤– 47 ä¸ªæ¨¡æ¿ / 879 ä¸ªé€‰é¡¹æ²¡æœ‰åº“来源,
--   æœ¬è„šæœ¬ä¸ä¼šåŠ¨å®ƒä»¬ï¼Œéœ€è¦äººå·¥å¤„ç†ï¼‰ï¼›
--   ä»»åŠ¡é¢˜ç›® 6409 é“,按题面能对上库题目 6353 é“(99.1%);
--   ä»»åС选项 16405 ä¸ªï¼Œèƒ½å¯¹ä¸Šåº“选项 16369 ä¸ªï¼ˆ99.8%)。
--
-- ä¸‰ä¸ªæ–¹æ¡ˆï¼ŒæŒ‰éœ€è¦åªè·‘其中一个:
--   æ–¹æ¡ˆA(推荐):只把 lib é‡Œæ ‡äº†é»˜è®¤(=2)的选项推到 task,不动 task å·²æœ‰çš„其它值
--                  â†’ å®žæµ‹å½±å“ 34 è¡Œ
--   æ–¹æ¡ˆB(完全对齐):lib=2 â†’ 2,lib=1/NULL â†’ 1(不写 NULL)
--                  â†’ å®žæµ‹å½±å“ 86 è¡Œï¼ˆé¢å¤– 52 è¡Œæ˜¯æŠŠ task é‡Œæœ‰ã€lib é‡Œæ²¡æœ‰çš„默认去掉)
--   æ–¹æ¡ˆC(严格镜像):连 NULL ä¸€èµ·ç…§æŠ„
--                  â†’ å®žæµ‹å½±å“ 10867 è¡Œï¼Œå…¶ä¸­ 10782 è¡Œä¼šè¢«ç½®æˆ NULL,通常不建议
--
-- æ‰§è¡Œå‰è¯·å…ˆè·‘第 0 æ­¥å¤‡ä»½ã€ç¬¬ 1 æ­¥é¢„检。
-- ============================================================================
-- ---------------------------------------------------------------------------
-- 0. å¤‡ä»½ï¼ˆåªå¤‡ä»½ä¼šè¢«æ”¹åŠ¨çš„é‚£å‡ åˆ—ï¼Œæ•´è¡¨å¤ªå¤§æ²¡å¿…è¦ï¼‰
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS bak_svy_task_targetoption_dv_20261106 AS
SELECT id,
       taskid,
       templateID,
       scriptid,
       optioncontent,
       default_value
FROM svy_task_template_targetoption
WHERE del_flag = '0';
-- è¡Œæ•°åº”与下面一致(实测 17403 è¡Œï¼‰
SELECT COUNT(*) AS bak_rows FROM bak_svy_task_targetoption_dv_20261106;
-- å˜æ›´å‰åˆ†å¸ƒï¼ˆå®žæµ‹ï¼š1=16397,2=1005,4=1)
SELECT default_value, COUNT(*) AS cnt
FROM svy_task_template_targetoption
WHERE del_flag = '0'
GROUP BY default_value
ORDER BY cnt DESC;
-- ---------------------------------------------------------------------------
-- 1. é¢„检:看看 lib çš„默认值同步到 task ä¼šæ”¹å¤šå°‘行、改成什么
-- ---------------------------------------------------------------------------
-- 1.1 æ±‡æ€»ï¼ˆæŒ‰æ–¹æ¡ˆA/B/C åˆ†åˆ«é¢„估)
SELECT '方案A åªæŽ¨ lib=2' AS plan,
       SUM(ld.dv = 2 AND COALESCE(o.default_value, 0) <> 2) AS will_change
FROM svy_task_template_targetoption o
         JOIN svy_task_template_script s ON s.id = o.scriptid AND s.del_flag = '0'
         JOIN svy_task_template tt ON tt.id = s.templateid AND tt.del_flag = '0'
         JOIN svy_lib_template lt ON lt.svyid = tt.templateid AND lt.del_flag = '0'
         JOIN (
    SELECT l.svyid, l.script_content, lo.optioncontent, MAX(lo.default_value) AS dv
    FROM svy_lib_template_script l
             JOIN svy_lib_template_targetoption lo ON lo.scriptid = l.id AND lo.del_flag = '0'
    WHERE l.del_flag = '0'
    GROUP BY l.svyid, l.script_content, lo.optioncontent
) ld ON ld.svyid = lt.svyid AND ld.script_content = s.script_content AND ld.optioncontent = o.optioncontent
WHERE o.del_flag = '0'
UNION ALL
SELECT '方案B 1/2 å®Œå…¨å¯¹é½',
       SUM(COALESCE(o.default_value, 1) <> (CASE WHEN ld.dv = 2 THEN 2 ELSE 1 END))
FROM svy_task_template_targetoption o
         JOIN svy_task_template_script s ON s.id = o.scriptid AND s.del_flag = '0'
         JOIN svy_task_template tt ON tt.id = s.templateid AND tt.del_flag = '0'
         JOIN svy_lib_template lt ON lt.svyid = tt.templateid AND lt.del_flag = '0'
         JOIN (
    SELECT l.svyid, l.script_content, lo.optioncontent, MAX(lo.default_value) AS dv
    FROM svy_lib_template_script l
             JOIN svy_lib_template_targetoption lo ON lo.scriptid = l.id AND lo.del_flag = '0'
    WHERE l.del_flag = '0'
    GROUP BY l.svyid, l.script_content, lo.optioncontent
) ld ON ld.svyid = lt.svyid AND ld.script_content = s.script_content AND ld.optioncontent = o.optioncontent
WHERE o.del_flag = '0'
UNION ALL
SELECT '方案C ä¸¥æ ¼é•œåƒ(含NULL)',
       SUM(COALESCE(o.default_value, -1) <> COALESCE(ld.dv, -1))
FROM svy_task_template_targetoption o
         JOIN svy_task_template_script s ON s.id = o.scriptid AND s.del_flag = '0'
         JOIN svy_task_template tt ON tt.id = s.templateid AND tt.del_flag = '0'
         JOIN svy_lib_template lt ON lt.svyid = tt.templateid AND lt.del_flag = '0'
         JOIN (
    SELECT l.svyid, l.script_content, lo.optioncontent, MAX(lo.default_value) AS dv
    FROM svy_lib_template_script l
             JOIN svy_lib_template_targetoption lo ON lo.scriptid = l.id AND lo.del_flag = '0'
    WHERE l.del_flag = '0'
    GROUP BY l.svyid, l.script_content, lo.optioncontent
) ld ON ld.svyid = lt.svyid AND ld.script_content = s.script_content AND ld.optioncontent = o.optioncontent
WHERE o.del_flag = '0';
-- 1.2 é€è¡Œå·®å¼‚清单(改动前先看一眼,确认没有不该改的)
SELECT o.id AS task_option_id,
       o.templateid AS task_template,
       tt.svyname,
       LEFT(s.script_content, 30) AS question,
       o.optioncontent,
       o.default_value AS task_now,
       ld.dv AS lib_value
FROM svy_task_template_targetoption o
         JOIN svy_task_template_script s ON s.id = o.scriptid AND s.del_flag = '0'
         JOIN svy_task_template tt ON tt.id = s.templateid AND tt.del_flag = '0'
         JOIN svy_lib_template lt ON lt.svyid = tt.templateid AND lt.del_flag = '0'
         JOIN (
    SELECT l.svyid, l.script_content, lo.optioncontent, MAX(lo.default_value) AS dv
    FROM svy_lib_template_script l
             JOIN svy_lib_template_targetoption lo ON lo.scriptid = l.id AND lo.del_flag = '0'
    WHERE l.del_flag = '0'
    GROUP BY l.svyid, l.script_content, lo.optioncontent
) ld ON ld.svyid = lt.svyid AND ld.script_content = s.script_content AND ld.optioncontent = o.optioncontent
WHERE o.del_flag = '0'
  AND COALESCE(o.default_value, 1) <> (CASE WHEN ld.dv = 2 THEN 2 ELSE 1 END)
ORDER BY o.templateid, s.id, o.id;
-- 1.3 é¡ºå¸¦çœ‹çœ‹å“ªäº› task é€‰é¡¹åœ¨åº“里找不到(题面被改过/新加选项),这些不会被同步
SELECT COUNT(*) AS unmapped_option_rows
FROM svy_task_template_targetoption o
         JOIN svy_task_template_script s ON s.id = o.scriptid AND s.del_flag = '0'
         JOIN svy_task_template tt ON tt.id = s.templateid AND tt.del_flag = '0'
         JOIN svy_lib_template lt ON lt.svyid = tt.templateid AND lt.del_flag = '0'
         LEFT JOIN svy_lib_template_script l
                   ON l.svyid = lt.svyid AND l.script_content = s.script_content AND l.del_flag = '0'
         LEFT JOIN svy_lib_template_targetoption lo
                   ON lo.scriptid = l.id AND lo.del_flag = '0' AND lo.optioncontent = o.optioncontent
WHERE o.del_flag = '0'
  AND lo.id IS NULL;
-- ---------------------------------------------------------------------------
-- 2. æ–¹æ¡ˆA(推荐):把库里的默认选项(=2)同步到任务模板选项
--    å®žæµ‹å½±å“ 34 è¡Œï¼›ä¸åЍ task é‡Œå·²æœ‰çš„ 1 / NULL,避免误删任务侧单独配过的默认项
--    ï¼ˆMySQL å¤šè¡¨ UPDATE ä¸èƒ½å¸¦ ORDER BY / LIMIT,下面的条件已经能定位到行)
-- ---------------------------------------------------------------------------
UPDATE svy_task_template_targetoption o
    JOIN svy_task_template_script s ON s.id = o.scriptid AND s.del_flag = '0'
    JOIN svy_task_template tt ON tt.id = s.templateid AND tt.del_flag = '0'
    JOIN svy_lib_template lt ON lt.svyid = tt.templateid AND lt.del_flag = '0'
    JOIN (
        SELECT l.svyid, l.script_content, lo.optioncontent, MAX(lo.default_value) AS dv
        FROM svy_lib_template_script l
                 JOIN svy_lib_template_targetoption lo ON lo.scriptid = l.id AND lo.del_flag = '0'
        WHERE l.del_flag = '0'
        GROUP BY l.svyid, l.script_content, lo.optioncontent
    ) ld ON ld.svyid = lt.svyid AND ld.script_content = s.script_content AND ld.optioncontent = o.optioncontent
SET o.default_value = 2
WHERE o.del_flag = '0'
  AND ld.dv = 2
  AND COALESCE(o.default_value, 0) <> 2;
-- ---------------------------------------------------------------------------
-- 3. æ–¹æ¡ˆB:和库完全对齐(lib=2 â†’ 2,lib=1/NULL â†’ 1),实测影响 86 è¡Œ
--    æ³¨æ„ï¼šä¼šæŠŠ task é‡Œã€Œæœ‰é»˜è®¤ã€ä½†åº“里没标默认」的 52 è¡Œæ”¹æˆ 1
-- ---------------------------------------------------------------------------
-- UPDATE svy_task_template_targetoption o
--     JOIN svy_task_template_script s ON s.id = o.scriptid AND s.del_flag = '0'
--     JOIN svy_task_template tt ON tt.id = s.templateid AND tt.del_flag = '0'
--     JOIN svy_lib_template lt ON lt.svyid = tt.templateid AND lt.del_flag = '0'
--     JOIN (
--         SELECT l.svyid, l.script_content, lo.optioncontent, MAX(lo.default_value) AS dv
--         FROM svy_lib_template_script l
--                  JOIN svy_lib_template_targetoption lo ON lo.scriptid = l.id AND lo.del_flag = '0'
--         WHERE l.del_flag = '0'
--         GROUP BY l.svyid, l.script_content, lo.optioncontent
--     ) ld ON ld.svyid = lt.svyid AND ld.script_content = s.script_content AND ld.optioncontent = o.optioncontent
-- SET o.default_value = CASE WHEN ld.dv = 2 THEN 2 ELSE 1 END
-- WHERE o.del_flag = '0'
--   AND COALESCE(o.default_value, 1) <> (CASE WHEN ld.dv = 2 THEN 2 ELSE 1 END);
-- ---------------------------------------------------------------------------
-- 4. æ–¹æ¡ˆC:严格镜像库里的值(连 NULL ä¸€èµ·ç…§æŠ„),实测影响 10867 è¡Œï¼Œ
--    å…¶ä¸­ 10782 è¡Œä¼šè¢«ç½®æˆ NULL(库里这些选项本身没设默认值)。一般不建议。
-- ---------------------------------------------------------------------------
-- UPDATE svy_task_template_targetoption o
--     JOIN svy_task_template_script s ON s.id = o.scriptid AND s.del_flag = '0'
--     JOIN svy_task_template tt ON tt.id = s.templateid AND tt.del_flag = '0'
--     JOIN svy_lib_template lt ON lt.svyid = tt.templateid AND lt.del_flag = '0'
--     JOIN (
--         SELECT l.svyid, l.script_content, lo.optioncontent, MAX(lo.default_value) AS dv
--         FROM svy_lib_template_script l
--                  JOIN svy_lib_template_targetoption lo ON lo.scriptid = l.id AND lo.del_flag = '0'
--         WHERE l.del_flag = '0'
--         GROUP BY l.svyid, l.script_content, lo.optioncontent
--     ) ld ON ld.svyid = lt.svyid AND ld.script_content = s.script_content AND ld.optioncontent = o.optioncontent
-- SET o.default_value = ld.dv
-- WHERE o.del_flag = '0'
--   AND COALESCE(o.default_value, -1) <> COALESCE(ld.dv, -1);
-- ---------------------------------------------------------------------------
-- 5. æ”¶çª„范围示例(只想动某几个任务模板时,在 WHERE é‡ŒåŠ æ¡ä»¶å³å¯ï¼‰
--    ä»»åŠ¡æ¨¡æ¿ id æ¥è‡ª svy_task_template.id(= svy_task_template_script.templateid)
-- ---------------------------------------------------------------------------
-- 5.1 åªåŒæ­¥ä»»åŠ¡æ¨¡æ¿ 514(全院科满意度;实测已一致,改 0 è¡Œï¼‰
-- UPDATE svy_task_template_targetoption o
--     JOIN svy_task_template_script s ON s.id = o.scriptid AND s.del_flag = '0'
--     JOIN svy_task_template tt ON tt.id = s.templateid AND tt.del_flag = '0'
--     JOIN svy_lib_template lt ON lt.svyid = tt.templateid AND lt.del_flag = '0'
--     JOIN (
--         SELECT l.svyid, l.script_content, lo.optioncontent, MAX(lo.default_value) AS dv
--         FROM svy_lib_template_script l
--                  JOIN svy_lib_template_targetoption lo ON lo.scriptid = l.id AND lo.del_flag = '0'
--         WHERE l.del_flag = '0'
--         GROUP BY l.svyid, l.script_content, lo.optioncontent
--     ) ld ON ld.svyid = lt.svyid AND ld.script_content = s.script_content AND ld.optioncontent = o.optioncontent
-- SET o.default_value = 2
-- WHERE o.del_flag = '0'
--   AND ld.dv = 2
--   AND COALESCE(o.default_value, 0) <> 2
--   AND s.templateid IN (514);
-- 5.2 åªåŒæ­¥æŸä¸ªæœºæž„下的任务模板(orgid åœ¨ä¸¤ä¸ª option è¡¨ä¸Šéƒ½æœ‰ï¼‰
--   AND o.orgid = ' xxx '
-- ---------------------------------------------------------------------------
-- 6. æ ¡éªŒ
-- ---------------------------------------------------------------------------
-- 6.1 æ”¹åŠ¨åŽåˆ†å¸ƒï¼ˆæ–¹æ¡ˆA ä¹‹åŽåº”为:1=16363,2=1039,4=1)
SELECT default_value, COUNT(*) AS cnt
FROM svy_task_template_targetoption
WHERE del_flag = '0'
GROUP BY default_value
ORDER BY cnt DESC;
-- 6.2 ä»ä¸Žåº“不一致的行数(方案A ä¹‹åŽåº”为 10833,差的都是 lib ä¾§æœªè®¾ç½®/为1 çš„行)
SELECT COUNT(*) AS still_diff
FROM svy_task_template_targetoption o
         JOIN svy_task_template_script s ON s.id = o.scriptid AND s.del_flag = '0'
         JOIN svy_task_template tt ON tt.id = s.templateid AND tt.del_flag = '0'
         JOIN svy_lib_template lt ON lt.svyid = tt.templateid AND lt.del_flag = '0'
         JOIN (
    SELECT l.svyid, l.script_content, lo.optioncontent, MAX(lo.default_value) AS dv
    FROM svy_lib_template_script l
             JOIN svy_lib_template_targetoption lo ON lo.scriptid = l.id AND lo.del_flag = '0'
    WHERE l.del_flag = '0'
    GROUP BY l.svyid, l.script_content, lo.optioncontent
) ld ON ld.svyid = lt.svyid AND ld.script_content = s.script_content AND ld.optioncontent = o.optioncontent
WHERE o.del_flag = '0'
  AND COALESCE(o.default_value, 0) <> COALESCE(ld.dv, 0);
-- 6.3 æ¯é¢˜ã€Œé»˜è®¤é€‰é¡¹çš„个数」分布:正常应最多 1 ä¸ªã€‚
--     å®žæµ‹è·‘完方案A åŽï¼š0 ä¸ª=5624 é¢˜ï¼Œ1 ä¸ª=1017 é¢˜ï¼Œ2 ä¸ª=8 é¢˜ï¼Œ3 ä¸ª=2 é¢˜
--     ï¼ˆåº“里本身就有 1 é“题挂了 2 ä¸ªé»˜è®¤é¡¹ï¼›å¸¦ 2/3 ä¸ªé»˜è®¤é¡¹çš„题目属于同步前就存在的问题)
SELECT c AS default_options_per_question, COUNT(*) AS questions
FROM (SELECT s.id, SUM(o.default_value = 2) c
      FROM svy_task_template_script s
               JOIN svy_task_template_targetoption o ON o.scriptid = s.id AND o.del_flag = '0'
      WHERE s.del_flag = '0'
      GROUP BY s.id) x
GROUP BY c
ORDER BY c;
-- ---------------------------------------------------------------------------
-- 7. å›žæ»šï¼ˆåªæŠŠè¢«æ”¹è¿‡çš„那几列写回备份值)
-- ---------------------------------------------------------------------------
-- UPDATE svy_task_template_targetoption o
--     JOIN bak_svy_task_targetoption_dv_20261106 b ON b.id = o.id
-- SET o.default_value = b.default_value
-- WHERE o.del_flag = '0';
--
-- ç¡®è®¤æ²¡é—®é¢˜åŽå†åˆ å¤‡ä»½è¡¨ï¼š
-- DROP TABLE bak_svy_task_targetoption_dv_20261106;
sql/tracedeallist_index_optimize.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,68 @@
-- ============================================================================
-- /smartor/trace/tracedeallist(异常问题处理情况查询)性能优化配套索引
--
-- æ”¹é€ åŽè¿™æ¡æŽ¥å£å›ºå®šæ‰§è¡Œ 5 æ¡èšåˆ SQL,过滤条件都是:
--   service_subtask_detail_trace:  orgid + template_type + del_flag(+ åˆ†ç»„/排序 questiontext)
--   service_subtask_detail:        scriptid
-- çº¿ä¸Šå¦‚果这两张表没有对应索引,聚合仍然是全表扫描。
--
-- æ‰§è¡Œé¡ºåºï¼šå…ˆè·‘第 1 æ­¥çœ‹çŽ°çŠ¶å’Œå­—æ®µç±»åž‹ï¼Œå†æŒ‰ç¬¬ 2 æ­¥åŠ ç¼ºå¤±çš„ç´¢å¼•ã€‚
-- ADD INDEX èµ° online DDL(不阻塞读写),但大表仍会吃 IO,建议业务低峰执行。
-- ============================================================================
-- ----------------------------------------------------------------------------
-- ç¬¬ 1 æ­¥ï¼šçœ‹çŽ°æœ‰ç´¢å¼• + questiontext çš„字段类型(决定能不能把 questiontext æ”¾è¿›ç´¢å¼•)
-- ----------------------------------------------------------------------------
SELECT INDEX_NAME, SEQ_IN_INDEX, COLUMN_NAME, NON_UNIQUE
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
  AND TABLE_NAME IN ('service_subtask_detail_trace', 'service_subtask_detail')
ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX;
SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, CHARACTER_MAXIMUM_LENGTH
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
  AND TABLE_NAME IN ('service_subtask_detail_trace', 'service_subtask_detail')
  AND COLUMN_NAME IN ('orgid', 'campusid', 'template_type', 'del_flag', 'scriptid', 'questiontext', 'handle_time', 'todeptcode');
SELECT TABLE_NAME, TABLE_ROWS, ROUND(DATA_LENGTH / 1024 / 1024, 1) AS data_mb
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
  AND TABLE_NAME IN ('service_subtask_detail_trace', 'service_subtask_detail');
-- ----------------------------------------------------------------------------
-- ç¬¬ 2 æ­¥ï¼šåŠ ç´¢å¼•ï¼ˆå·²å­˜åœ¨çš„ä¼šæŠ¥ Duplicate key name,跳过即可)
-- ----------------------------------------------------------------------------
-- â‘  trace è¡¨çš„主过滤条件:orgid + template_type + del_flag
--    ï¼ˆæœ¬æŽ¥å£å…¥å‚只有 templateType,orgid ç”± @AddOrgId æ³¨å…¥ï¼‰
ALTER TABLE service_subtask_detail_trace
    ADD INDEX idx_trace_deal_query (orgid, template_type, del_flag);
-- â‘¡ æ˜Žç»†è¡¨çš„「有效填写数」按 scriptid ç»Ÿè®¡ï¼ˆæ”¹é€ å‰æ˜¯é€ä¸ª scriptid æŸ¥ï¼Œæ”¹é€ åŽæ˜¯ä¸€æ¡ group by)
ALTER TABLE service_subtask_detail
    ADD INDEX idx_detail_scriptid (scriptid);
-- â‘¢ å¯é€‰ï¼šå¦‚果第 1 æ­¥æŸ¥åˆ° questiontext æ˜¯ VARCHAR(255) åŠä»¥ä¸‹ï¼ˆutf8mb4 ä¸è¶…过 255),
--    å¯ä»¥å†åŠ è¦†ç›–åˆ†ç»„/排序的索引,效果更好:
-- ALTER TABLE service_subtask_detail_trace
--     ADD INDEX idx_trace_deal_group (orgid, template_type, del_flag, questiontext);
-- â‘£ å¯é€‰ï¼šæœ€æ–°å¤„理人那条 SQL ç”¨åˆ° (questiontext, handle_time)
-- ALTER TABLE service_subtask_detail_trace
--     ADD INDEX idx_trace_deal_handle (orgid, template_type, del_flag, questiontext, handle_time);
-- ----------------------------------------------------------------------------
-- ç¬¬ 3 æ­¥ï¼šéªŒè¯ç´¢å¼•是否生效(把下面 SQL æ‹¿åŽ» EXPLAIN,key åˆ—应该命中刚建的索引)
-- ----------------------------------------------------------------------------
-- EXPLAIN SELECT questiontext, min(scriptid), count(*)
--           FROM service_subtask_detail_trace
--          WHERE del_flag = 0 AND questiontext IS NOT NULL
--            AND orgid = '20001001' AND template_type = 2
--          GROUP BY questiontext ORDER BY questiontext LIMIT 10 OFFSET 0;
--
-- EXPLAIN SELECT scriptid, count(*) FROM service_subtask_detail
--          WHERE scriptid IN (1, 2, 3) GROUP BY scriptid;
sql/v_sf_statistics_ward.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,603 @@
-- =============================================================================
-- /smartor/serviceSubtask/getSfStatistics çš„ SQL ç‰ˆï¼ˆç—…区维度 Â· å…¨éƒ¨ç—…区 Â· å‡ºé™¢éšè®¿ serviceType=2)
-- =============================================================================
-- å¯¹åº”代码:
--   ServiceSubtaskController#getSfStatistics
--     â†’ ServiceSubtaskServiceImpl#getSfStatistics            ï¼ˆåˆ†ç»„ + é€æ¡ç´¯åŠ ï¼‰
--     â†’ ServiceSubtaskMapper.getSfStatistics / getSfStatisticsGroupKey
--     â†’ ServiceSubtaskMapper.selectTimelyRateBatch           ï¼ˆåŠæ—¶çއ rate)
-- æ•°æ®æºï¼šservice_subtask(本接口只查这一张表)
--
-- å…¥å‚:只有 hospitalStartTime(出院时间下限)。
--   åˆ†ç»„:statisticaltype=1(病区),且不传 leavehospitaldistrictcodes â†’ å…¨éƒ¨ç—…区。
--   æœåŠ¡ç±»åž‹ï¼šserviceType = 2(出院随访)。
--   è¯´æ˜Žï¼šæŽ¥å£ Mapper é‡Œå‡ºé™¢æ—¶é—´æ¡ä»¶æ˜¯ã€ŒhospitalStartTime ä¸Ž hospitalEndTime éƒ½ä¼ æ‰ç”Ÿæ•ˆã€ï¼Œ
--        åªä¼  hospitalStartTime æ—¶æŽ¥å£å…¶å®žã€å®Œå…¨ä¸è¿‡æ»¤å‡ºé™¢æ—¶é—´ã€‘。下面按你要的语义实现:
--        å‡ºé™¢æ—¶é—´ >= hospitalStartTime(上限可选)。
--
-- å·²åœ¨ MySQL 5.7.34(smartor_sltd)实跑验证:
--   hospitalStartTime = '2026-10-01' â†’ 45 ä¸ªç—…区 / 3088 æ¡å‡ºé™¢è®°å½•,
--   æŠ½æŸ¥ç—…区 40003052:287 æ¡ã€æ— éœ€éšè®¿ 4、应随访 283、首次随访成功 250、随访率 88.34%,与明细逐条核对一致。
-- =============================================================================
-- -----------------------------------------------------------------------------
-- PART 0. å‚数(只有第一个是必填)
-- -----------------------------------------------------------------------------
SET @hospitalStartTime = '2026-10-01';   -- å¿…填:出院时间下限(当天 0 ç‚¹èµ·ï¼‰
SET @hospitalEndTime   = NULL;           -- å¯é€‰ï¼šå‡ºé™¢æ—¶é—´ä¸Šé™ï¼ŒNULL = ä¸é™
SET @followUpCountStyle = '1';           -- ç³»ç»Ÿé…ç½® followUpCountStyle;本库未配置该参数,接口按 '1' å¤„理
SET @orgid    = NULL;                    -- å¯é€‰ï¼šæœºæž„ï¼›NULL = ä¸é™ï¼ˆæŽ¥å£é‡Œç”±ç™»å½•信息注入)
SET @campusid = NULL;                    -- å¯é€‰ï¼šé™¢åŒºï¼›NULL = ä¸é™
-- -----------------------------------------------------------------------------
-- PART 1. ä¸»æŸ¥è¯¢ï¼šä¸€è¡Œ = ä¸€ä¸ªå‡ºé™¢ç—…区,覆盖全部病区
--   Â· å†…层 r   ï¼šä¸€è¡Œ = ä¸€æ¡éšè®¿å­ä»»åŠ¡ï¼ŒæŠŠã€Œç»Ÿè®¡å£å¾„ã€ç¿»è¯‘æˆ 0/1 æ ‡è®°
--   Â· ä¸­é—´ t   ï¼šæŒ‰ç—…区把标记累加
--   Â· æœ€å¤–层   ï¼šæŠŠç´¯åŠ å€¼ç®—æˆæŽ¥å£è¿”å›žçš„æ¯”çŽ‡å­—ç¬¦ä¸²
--   æƒ³æ”¹ç»Ÿè®¡ç»´åº¦ï¼šæŠŠ GROUP BY r.ward_code æ¢æˆ r.dept_code(科室)或 r.dr_code(医生)即可。
--   æƒ³è¦æ€»è®¡è¡Œï¼šæŠŠã€ŒGROUP BY r.ward_code」改成「GROUP BY r.ward_code WITH ROLLUP」,
--               æœ€åŽä¸€è¡Œ ward_code ä¸º NULL çš„就是全部病区合计。
-- -----------------------------------------------------------------------------
SELECT
  t.leavehospitaldistrictcode,
  t.leavehospitaldistrictname,
  t.deptcode,
  t.deptname,
  t.drcode,
  t.drname,
  t.dischargeCount,            -- å‡ºé™¢äººæ¬¡
  t.nonFollowUp,               -- æ— éœ€éšè®¿äººæ¬¡
  t.filterCount,               -- è¿‡æ»¤æ‚£è€…数量
  t.followUpNeeded,            -- åº”随访人次
  t.needFollowUp,              -- é¦–次应随访
  t.nonFollowUpFirst,          -- é¦–次无需随访
  t.pendingFollowUp,           -- é¦–次待随访(本接口此路径恒为 0,见 PART 5)
  t.followUpSuccess,           -- é¦–次随访成功
  t.followUpFail,              -- é¦–次随访失败(含人工超时)
  t.manual,                    -- é¦–次人工随访
  t.voice,                     -- é¦–次语音随访
  t.sms,                       -- é¦–次短信随访
  t.weChat,                    -- é¦–次微信随访
  t.manualSuccess,             -- é¦–次人工随访成功
  t.voiceSuccess,              -- é¦–次语音随访成功
  t.smsSuccess,                -- é¦–次短信随访成功
  t.weChatSuccess,             -- é¦–次微信随访成功
  -- é¦–次随访率 =(首次随访成功 + é¦–次随访失败)/ é¦–次应随访
  IF(t.needFollowUp = 0, '0.00%',
     CONCAT(ROUND(100 * (t.followUpSuccess + t.followUpFail) / t.needFollowUp, 2), '%')) AS followUpRate,
  t.abnormal,                  -- ç»“果异常(首次)
  t.needFollowUpAgain,         -- äºŒæ¬¡åº”随访
  t.nonFollowUpAgain,          -- äºŒæ¬¡æ— éœ€éšè®¿
  t.pendingFollowUpAgain,      -- äºŒæ¬¡å¾…随访
  t.followUpSuccessAgain,      -- äºŒæ¬¡éšè®¿æˆåŠŸ
  t.followUpFailAgain,         -- äºŒæ¬¡éšè®¿å¤±è´¥
  t.manualAgain,
  t.voiceAgain,
  t.smsAgain,
  t.weChatAgain,
  t.manualAgainSuccess,
  t.voiceAgainSuccess,
  t.smsAgainSuccess,
  t.weChatAgainSuccess,
  -- äºŒæ¬¡éšè®¿çއ
  IF(t.needFollowUpAgain = 0, '0.00%',
     CONCAT(ROUND(100 * (t.followUpSuccessAgain + t.followUpFailAgain) / t.needFollowUpAgain, 2), '%')) AS followUpRateAgain,
  t.abnormalAgain,             -- ç»“果异常(再次)
  t.taskSituation1,            -- éšè®¿æƒ…况:正常语音
  t.taskSituation2,            -- éšè®¿æƒ…况:患者拒接/拒访
  t.taskSituation3,            -- éšè®¿æƒ…况:面访/接诊
  t.taskSituation4,            -- éšè®¿æƒ…况:微信随访
  t.taskSituation5,            -- éšè®¿æƒ…况:随访电话不正确
  t.taskSituation6            -- éšè®¿æƒ…况:其他(task_situation = 7)
FROM (
  SELECT
    r.ward_code                                                             AS leavehospitaldistrictcode,
    MAX(r.ward_name)                                                        AS leavehospitaldistrictname,
    MAX(r.dept_code)                                                        AS deptcode,
    MAX(r.dept_name)                                                        AS deptname,
    MAX(r.dr_code)                                                          AS drcode,
    MAX(r.dr_name)                                                          AS drname,
    COUNT(*)                                                                AS dischargeCount,
    SUM(r.f_no)                                                             AS nonFollowUp,
    SUM(r.f_filter)                                                         AS filterCount,
    SUM(r.f_need)                                                           AS followUpNeeded,
    SUM(IF(r.f_first = 1 AND r.f_need = 1, 1, 0))                           AS needFollowUp,
    -- followUpCountStyle='1' æ—¶æŽ¥å£æŠŠã€Œé¦–次无需随访」加了两次(代码 L2195-2197 ä¸Ž L2211-2213),这里 *2 å¤åˆ»
    SUM(IF(r.f_first = 1 AND r.f_no = 1, 1, 0)) * IF(@followUpCountStyle = '1', 2, 1) AS nonFollowUpFirst,
    0                                                                       AS pendingFollowUp,
    SUM(IF(r.f_first = 1 AND r.f_succ_first = 1, 1, 0))                     AS followUpSuccess,
    SUM(IF(r.f_first = 1 AND r.f_fail_first = 1, 1, 0))                     AS followUpFail,
    SUM(IF(r.f_first = 1 AND r.ch_hit = 1 AND r.cp = '1', 1, 0))            AS manual,
    SUM(IF(r.f_first = 1 AND r.ch_hit = 1 AND r.cp = '3', 1, 0))            AS voice,
    SUM(IF(r.f_first = 1 AND r.ch_hit = 1 AND r.cp = '4', 1, 0))            AS sms,
    SUM(IF(r.f_first = 1 AND r.ch_hit = 1 AND r.cp = '5', 1, 0))            AS weChat,
    SUM(IF(r.f_first = 1 AND r.sendstate = 6 AND r.cp = '1', 1, 0))         AS manualSuccess,
    SUM(IF(r.f_first = 1 AND r.sendstate = 6 AND r.cp = '3', 1, 0))         AS voiceSuccess,
    SUM(IF(r.f_first = 1 AND r.sendstate = 6 AND r.cp = '4', 1, 0))         AS smsSuccess,
    SUM(IF(r.f_first = 1 AND r.sendstate = 6 AND r.cp = '5', 1, 0))         AS weChatSuccess,
    SUM(IF(r.f_first = 1 AND r.f_abnormal = 1, 1, 0))                       AS abnormal,
    SUM(IF(r.f_again = 1 AND r.f_need = 1, 1, 0))                           AS needFollowUpAgain,
    SUM(IF(r.f_again = 1 AND r.f_no = 1, 1, 0))                             AS nonFollowUpAgain,
    SUM(IF(r.f_again = 1 AND r.sendstate = 2, 1, 0))                        AS pendingFollowUpAgain,
    SUM(IF(r.f_again = 1 AND r.f_succ_again = 1, 1, 0))                     AS followUpSuccessAgain,
    SUM(IF(r.f_again = 1 AND r.f_fail_again = 1, 1, 0))                     AS followUpFailAgain,
    SUM(IF(r.f_again = 1 AND r.ch_hit = 1 AND r.cp = '1', 1, 0))            AS manualAgain,
    SUM(IF(r.f_again = 1 AND r.ch_hit = 1 AND r.cp = '3', 1, 0))            AS voiceAgain,
    SUM(IF(r.f_again = 1 AND r.ch_hit = 1 AND r.cp = '4', 1, 0))            AS smsAgain,
    SUM(IF(r.f_again = 1 AND r.ch_hit = 1 AND r.cp = '5', 1, 0))            AS weChatAgain,
    SUM(IF(r.f_again = 1 AND r.sendstate = 6 AND r.cp = '1', 1, 0))         AS manualAgainSuccess,
    SUM(IF(r.f_again = 1 AND r.sendstate = 6 AND r.cp = '3', 1, 0))         AS voiceAgainSuccess,
    SUM(IF(r.f_again = 1 AND r.sendstate = 6 AND r.cp = '4', 1, 0))         AS smsAgainSuccess,
    SUM(IF(r.f_again = 1 AND r.sendstate = 6 AND r.cp = '5', 1, 0))         AS weChatAgainSuccess,
    SUM(IF(r.f_again = 1 AND r.f_abnormal = 1, 1, 0))                       AS abnormalAgain,
    SUM(IF(r.task_situation = 1, 1, 0))                                     AS taskSituation1,
    SUM(IF(r.task_situation = 2, 1, 0))                                     AS taskSituation2,
    SUM(IF(r.task_situation = 3, 1, 0))                                     AS taskSituation3,
    SUM(IF(r.task_situation = 4, 1, 0))                                     AS taskSituation4,
    SUM(IF(r.task_situation = 5, 1, 0))                                     AS taskSituation5,
    SUM(IF(r.task_situation = 7, 1, 0))                                     AS taskSituation6
  FROM (
    SELECT
      ss.leavehospitaldistrictcode                            AS ward_code,
      ss.leavehospitaldistrictname                            AS ward_name,
      ss.leaveldeptcode                                       AS dept_code,
      ss.leaveldeptname                                       AS dept_name,
      ss.drcode                                               AS dr_code,
      ss.drname                                               AS dr_name,
      ss.sendstate                                            AS sendstate,
      ss.current_preachform                                   AS cp,
      ss.task_situation                                       AS task_situation,
      IF(ss.sendstate = 4, 1, 0)                              AS f_no,
      IF(ss.sendstate = 4 AND ss.task_situation = 6, 1, 0)    AS f_filter,
      IF(ss.sendstate IS NOT NULL AND ss.sendstate <> 4, 1, 0) AS f_need,
      IF(ss.visit_count = 1, 1, 0)                            AS f_first,
      IF(ss.visit_count > 1, 1, 0)                            AS f_again,
      -- é¦–次随访成功:统计方式 1/3 åªç®—已完成(6);其它方式算上不执行/已发送(3,4,6)
      IF(@followUpCountStyle IN ('1', '3'),
         IF(ss.sendstate = 6, 1, 0),
         IF(ss.sendstate IN (3, 4, 6), 1, 0))                 AS f_succ_first,
      -- å†æ¬¡éšè®¿æˆåŠŸï¼šç»Ÿè®¡æ–¹å¼ 1/3 åªç®—已完成(6);其它方式算上待随访失败前的 1 ç­‰(1,3,4,6)
      IF(@followUpCountStyle IN ('1', '3'),
         IF(ss.sendstate = 6, 1, 0),
         IF(ss.sendstate IN (1, 3, 4, 6), 1, 0))              AS f_succ_again,
      IF(ss.sendstate IN (5, 7), 1, 0)                        AS f_fail_first,
      IF(ss.sendstate = 5, 1, 0)                              AS f_fail_again,
      IF(ss.excep = '1', 1, 0)                                AS f_abnormal,
      -- éšè®¿æ–¹å¼å‘½ä¸­æ¡ä»¶ï¼šæ–¹å¼ 1 æŽ’除「无需随访」;方式 3 åªç®—已完成;其它方式不设限
      IF(@followUpCountStyle = '1',
         IF(ss.sendstate IS NOT NULL AND ss.sendstate <> 4, 1, 0),
         IF(@followUpCountStyle = '3',
            IF(ss.sendstate = 6, 1, 0),
            IF(ss.sendstate IS NOT NULL, 1, 0)))              AS ch_hit
    FROM service_subtask ss
    WHERE ss.del_flag = 0
      AND ss.service_type = '2'                              -- serviceType = 2 å‡ºé™¢éšè®¿
      -- æŽ¥å£é‡Œ groupKeyList ä¼šå¸¦å…¨é‡ç—…区码,等价于「出院病区为 NULL çš„行被丢掉」;
      -- è‹¥æƒ³æŠŠç©ºç—…区也统计进来,注释掉下一行。
      AND ss.leavehospitaldistrictcode IS NOT NULL
      AND ss.endtime >= DATE(@hospitalStartTime)
      AND (@hospitalEndTime IS NULL OR ss.endtime < DATE_ADD(DATE(@hospitalEndTime), INTERVAL 1 DAY))
      AND (@orgid    IS NULL OR ss.orgid    = @orgid)
      AND (@campusid IS NULL OR ss.campusid = @campusid)
  ) r
  GROUP BY r.ward_code
) t
ORDER BY t.dischargeCount DESC;
-- -----------------------------------------------------------------------------
-- PART 1B. åªè¦ã€Œé¦–次随访」的数据(等价于接口入参 visitCount = 1 â†’ getStatisticFirst)
--   Â· åªç»Ÿè®¡ visit_count = 1 çš„记录:二次随访记录不进入任何一列
--   Â· åˆ—:出院人次 / åº”随访拆解 / éšè®¿æˆåŠŸæ‹†è§£ / å„成功率 / åŠæ—¶çއ / å‡ºé™¢æ—¶é—´èŒƒå›´
--   Â· å£å¾„与 getStatisticFirst å¯¹é½ï¼špendingFollowUp æ˜¯ sendstate = 2 çš„真实数量
--
--   â˜… å¯¹è´¦ï¼šæŽ¥å£è¿™å‡ åˆ—本身不等于「应随访」,差额是两个真实存在的口径缺项,不是数据错:
--      é¦–次应随访 = å¾…发送(1) + å¾…随访(2) + å·²å‘送未完成(3) + å…¶å®ƒçŠ¶æ€ + éšè®¿æˆåŠŸ(6) + éšè®¿å¤±è´¥(5,7)
--      é¦–次随访成功 = äººå·¥æˆåŠŸ + è¯­éŸ³æˆåŠŸ + çŸ­ä¿¡æˆåŠŸ + å¾®ä¿¡æˆåŠŸ + æ— æ–¹å¼æˆåŠŸ(sendstate=6 ä½† current_preachform ä¸ºç©º)
--     æŽ¥å£åªç»Ÿè®¡ 2 / 6 / 5+7,所以「待随访 + æˆåŠŸ + å¤±è´¥ã€å°‘于应随访;
--     æ¸ é“成功按 current_preachform åˆ†ï¼Œæ²¡æœ‰æ–¹å¼çš„æˆåŠŸè®°å½•ä¸è½ä»»ä½•æ¸ é“ã€‚
--     ä¸‹é¢æŠŠ notSent / sentNotDone / otherStatus / noChannelSuccess æ˜¾å¼åˆ—出来,并给出 diffNeed / diffSucc
--     ä¸¤ä¸ªè‡ªæ£€åˆ—(正常恒为 0)。
--
--   â˜… å‡ºé™¢æ—¶é—´ï¼šä¸»æŸ¥è¯¢ç»™ endTimeMin/endTimeMax,及时率口径给 timelyEndMin/timelyEndMax。
--     âš  åŠæ—¶çŽ‡å­æŸ¥è¯¢é‡Œçš„å‡ºé™¢æ—¶é—´æ¡ä»¶å½“å‰æ˜¯æ³¨é‡ŠæŽ‰çš„ï¼ˆæŒ‰ä½ çš„ç‰ˆæœ¬ï¼‰ï¼Œæ‰€ä»¥å®ƒç»Ÿè®¡çš„æ˜¯è¯¥ç—…åŒºã€Œå…¨éƒ¨åŽ†å²ã€ï¼Œ
--       åˆ†æ¯ timelyBase ä¼šè¿œå¤§äºŽåº”随访(实测某病区 643 æ¡åº”随访 vs 2342 æ¡åŠæ—¶çŽ‡åˆ†æ¯ï¼‰ï¼Œ
--       timelyRate ä¸Žå…¶å®ƒåˆ—不再同区间、不可直接比较。要恢复同区间就把那两行放开。
--
--   ä¸»æŸ¥è¯¢å·²å®žè·‘校验(2026-09-04 ~ 2026-11-03,orgid=20001001,45 ä¸ªç—…区):7378 æ¡ / æ— éœ€éšè®¿ 428 /
--   åº”随访 6950 = å¾…发送 116 + å¾…随访 337 + å·²å‘送未完成 941 + æˆåŠŸ 4188 + å¤±è´¥ 1368;
--   æˆåŠŸ 4188 = äººå·¥ 2974 + çŸ­ä¿¡ 1197 + æ— æ–¹å¼ 17(本库 current_preachform åªæœ‰ 1 å’Œ 4,语音/微信为 0)。
-- -----------------------------------------------------------------------------
SELECT
  t.leavehospitaldistrictname,   -- å‡ºé™¢ç—…区
  t.endTimeMin,                  -- è¯¥ç—…区命中的最早出院时间
  t.endTimeMax,                  -- è¯¥ç—…区命中的最晚出院时间
  t.deptname,                    -- å‡ºé™¢ç§‘室
  t.dischargeCount,              -- å‡ºé™¢äººæ¬¡ï¼ˆé¦–次随访记录数)
  t.nonFollowUp,                 -- æ— éœ€éšè®¿äººæ¬¡
  t.followUpNeeded,              -- åº”随访人次
  t.needFollowUp,                -- é¦–次应随访
  t.notSent,                     -- å¾…发送(sendstate = 1)
  t.pendingFollowUp,             -- é¦–次待随访(sendstate = 2)
  t.sentNotDone,                 -- å·²å‘送未完成(sendstate = 3)
  t.otherStatus,                 -- å…¶å®ƒçŠ¶æ€å…œåº•ï¼ˆæ­£å¸¸ä¸º 0)
  t.followUpSuccess,             -- é¦–次随访成功(sendstate = 6)
  t.followUpFail,                -- é¦–次随访失败(sendstate = 5,7,含人工超时)
  -- å„随访方式成功数(想同时看总数就放开下面 4 è¡Œï¼ŒæˆåŠŸçŽ‡åˆ†æ¯å°±æ˜¯è¿™ 4 ä¸ªï¼‰
  -- t.manual,  t.voice,  t.sms,  t.weChat,
  t.manualSuccess,               -- é¦–次人工随访成功
  t.voiceSuccess,                -- é¦–次语音随访成功
  t.smsSuccess,                  -- é¦–次短信随访成功
  t.weChatSuccess,               -- é¦–次微信随访成功
  t.noChannelSuccess,            -- æˆåŠŸä½†æ²¡æœ‰éšè®¿æ–¹å¼ï¼ˆcurrent_preachform ä¸ºç©ºæˆ–不属于 1/3/4/5)
  -- éšè®¿çއ =(成功 + å¤±è´¥ï¼‰/ åº”随访(接口 followUpRate çš„口径)
  IF(t.needFollowUp = 0, '0.00%',
     CONCAT(ROUND(100 * (t.followUpSuccess + t.followUpFail) / t.needFollowUp, 2), '%')) AS followUpRate,
  -- éšè®¿æˆåŠŸçŽ‡ = æˆåŠŸ / åº”随访(只算成功,不含失败)
  IF(t.needFollowUp = 0, '0.00%',
     CONCAT(ROUND(100 * t.followUpSuccess / t.needFollowUp, 2), '%')) AS followUpSuccessRate,
  -- å„方式成功率 = è¯¥æ–¹å¼æˆåŠŸ / è¯¥æ–¹å¼å‘½ä¸­ï¼ˆsendstate<>4 ä¸” current_preachform ä¸ºè¯¥æ–¹å¼ï¼‰
  IF(t.manual = 0, '0.00%', CONCAT(ROUND(100 * t.manualSuccess / t.manual, 2), '%')) AS manualSuccessRate,
  IF(t.voice  = 0, '0.00%', CONCAT(ROUND(100 * t.voiceSuccess  / t.voice,  2), '%')) AS voiceSuccessRate,
  IF(t.sms    = 0, '0.00%', CONCAT(ROUND(100 * t.smsSuccess    / t.sms,    2), '%')) AS smsSuccessRate,
  IF(t.weChat = 0, '0.00%', CONCAT(ROUND(100 * t.weChatSuccess / t.weChat, 2), '%')) AS weChatSuccessRate,
  -- æ¸ é“合计成功率 = å››ç§æ–¹å¼æˆåŠŸä¹‹å’Œ / å››ç§æ–¹å¼å‘½ä¸­ä¹‹å’Œ
  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,
  -- åŠæ—¶çŽ‡ï¼šå£å¾„ä¸ŽæŽ¥å£ selectTimelyRateBatch ç›¸åŒï¼ˆç‹¬ç«‹çš„随访时间窗口 + æŽ’除「无需随访」),
  -- åŠæ—¶ = finishtime ä¸ä¸ºç©º ä¸” DATEDIFF(finishtime, endtime) <= åŠæ—¶å¤©æ•° + è¡¥å¿å¤©æ•°ã€‚
  -- åŠæ—¶å¤©æ•°é˜ˆå€¼è§ä¸‹é¢ rt å­æŸ¥è¯¢é‡Œçš„ 7,按需修改(接口不传 rateDay æ—¶è¿”回 0)。
  IFNULL(rt.timely_base, 0) AS timelyBase,   -- åŠæ—¶çŽ‡åˆ†æ¯ï¼ˆå£å¾„ä¸Žã€Œåº”éšè®¿ã€ä¸åŒï¼‰
  IF(IFNULL(rt.timely_base, 0) = 0, '0.00%',
     CONCAT(ROUND(100 * rt.timely_cnt / rt.timely_base, 2), '%')) AS timelyRate,
  rt.timelyEndMin,               -- åŠæ—¶çŽ‡å£å¾„å‘½ä¸­çš„æœ€æ—©å‡ºé™¢æ—¶é—´
  rt.timelyEndMax,               -- åŠæ—¶çŽ‡å£å¾„å‘½ä¸­çš„æœ€æ™šå‡ºé™¢æ—¶é—´
  -- è‡ªæ£€ï¼šä¸¤ä¸ªå·®é¢ï¼Œæ­£å¸¸æ’为 0
  t.needFollowUp - (t.notSent + t.pendingFollowUp + t.sentNotDone + t.otherStatus
                    + t.followUpSuccess + t.followUpFail) AS diffNeed,
  t.followUpSuccess - (t.manualSuccess + t.voiceSuccess + t.smsSuccess + t.weChatSuccess
                       + t.noChannelSuccess) AS diffSucc
  -- å¦å¤–几列需要时放开:
  -- , t.abnormal
  -- , t.taskSituation1, t.taskSituation2, t.taskSituation3, t.taskSituation4, t.taskSituation5, t.taskSituation6
FROM (
  SELECT
    r.ward_code                                    AS leavehospitaldistrictcode,
    MAX(r.ward_name)                               AS leavehospitaldistrictname,
    MAX(r.dept_name)                               AS deptname,
    MIN(r.end_time)                                AS endTimeMin,
    MAX(r.end_time)                                AS endTimeMax,
    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 = 1, 1, 0))                 AS notSent,
    SUM(IF(r.sendstate = 2, 1, 0))                 AS pendingFollowUp,
    SUM(IF(r.sendstate = 3, 1, 0))                 AS sentNotDone,
    SUM(IF(r.sendstate IS NOT NULL AND r.sendstate <> 4
           AND r.sendstate NOT IN (1, 2, 3, 5, 6, 7), 1, 0)) AS otherStatus,
    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,
    SUM(IF(r.sendstate = 6 AND (r.cp IS NULL OR r.cp NOT IN ('1','3','4','5')), 1, 0)) AS noChannelSuccess
  FROM (
    SELECT
      ss.leavehospitaldistrictcode                            AS ward_code,
      ss.leavehospitaldistrictname                            AS ward_name,
      ss.leaveldeptname                                       AS dept_name,
      ss.endtime                                              AS end_time,
      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(@followUpCountStyle = '1',
         IF(ss.sendstate IS NOT NULL AND ss.sendstate <> 4, 1, 0),
         IF(@followUpCountStyle = '3',
            IF(ss.sendstate = 6, 1, 0),
            IF(ss.sendstate IS NOT NULL, 1, 0)))              AS ch_hit
    FROM service_subtask ss
    WHERE ss.del_flag = 0
      AND ss.service_type = '2'
      AND ss.visit_count = 1                                 -- åªè¦é¦–次随访
      AND ss.leavehospitaldistrictcode IS NOT NULL
      AND ss.endtime >= DATE('2026-09-04 00:00:00')
      AND ss.endtime < DATE_ADD(DATE('2026-11-03 23:59:59'), INTERVAL 1 DAY)
      AND ss.orgid = '20001001'
  ) r
  GROUP BY r.ward_code
) t
LEFT JOIN (
  -- åŠæ—¶çŽ‡ï¼ˆselectTimelyRateBatch çš„等价写法,按病区预聚合一次再关联,避免一对多放大分母)
  SELECT
    ss.leavehospitaldistrictcode AS ward_code,
    COUNT(*) AS timely_base,
    SUM(IF(ss.finishtime IS NOT NULL
           AND DATEDIFF(ss.finishtime, ss.endtime) <= 7 + IFNULL(cp.compensate_days, 0), 1, 0)) AS timely_cnt,
    MIN(ss.endtime) AS timelyEndMin,     -- è¿™ä¸€å£å¾„实际覆盖到的出院时间范围
    MAX(ss.endtime) AS timelyEndMax
  FROM service_subtask ss
  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 æ—¶çš„固定条件
    -- åŠæ—¶çŽ‡è¦ä¸è¦è·Ÿä¸»æŸ¥è¯¢åŒåŒºé—´ï¼Ÿæ”¾å¼€ä¸‹é¢ä¸¤è¡Œå³å¯ï¼ˆæ”¾å¼€åŽ timelyBase æ‰ä¼šç­‰äºŽåº”随访)
    -- AND ss.endtime >= DATE('2026-09-04 00:00:00')
    -- AND ss.endtime < DATE_ADD(DATE('2026-11-03 23:59:59'), INTERVAL 1 DAY)
    AND ss.orgid = '20001001'
  GROUP BY ss.leavehospitaldistrictcode
) rt ON rt.ward_code = t.leavehospitaldistrictcode
ORDER BY t.dischargeCount DESC;
-- -----------------------------------------------------------------------------
-- PART 1C. æƒ³çœ‹ã€Œå‡ºé™¢æ—¶é—´ã€è¿™ä¸€åˆ—:按 ç—…区 + å‡ºé™¢æ—¥æœŸ(DATE(endtime)) åˆ†ç»„
--   ä¸ºä»€ä¹ˆä¸èƒ½åªåœ¨ PART 1B é‡Œ select ss.endtime å† GROUP BY ward_code:
--     Â· ONLY_FULL_GROUP_BY ä¸‹ä¼šæŠ¥ 1055;
--     Â· æŠŠå¸¦ç§’çš„ endtime åŠ è¿› GROUP BY = ä¸€æ¡è®°å½•一组,7378 æ¡è¢«æ‹†æˆ 7133 è¡Œï¼Œæ±‡æ€»æ²¡æœ‰æ„ä¹‰ï¼›
--     Â· æ›´å‘的是及时率子查询仍按病区聚合,整病区的 timelyBase ä¼šè¢«é‡å¤æŒ‚到每一行
--       ï¼ˆå®žæµ‹ timelyBase åˆè®¡ä¼šå˜æˆ 9,649,708)。这里 rt ä¹ŸæŒ‰ã€Œç—…区 + å‡ºé™¢æ—¥æœŸã€èšåˆï¼Œ
--       æ‰€ä»¥æ¯è¡Œçš„及时率和该行数据是同一个粒度。
--   å®žæµ‹ï¼š1637 è¡Œ / dischargeCount åˆè®¡ 7378 / timelyBase åˆè®¡ 6950 / diffNeed、diffSucc å…¨ 0。
-- -----------------------------------------------------------------------------
SELECT
  t.end_date                     AS å‡ºé™¢æ—¥æœŸ,      -- å‡ºé™¢æ—¶é—´ï¼ˆæŒ‰å¤©ï¼‰
  t.leavehospitaldistrictname    AS å‡ºé™¢ç—…区,
  t.deptname                     AS å‡ºé™¢ç§‘室,
  t.dischargeCount, t.nonFollowUp, t.followUpNeeded, t.needFollowUp,
  t.notSent, t.pendingFollowUp, t.sentNotDone, t.otherStatus,
  t.followUpSuccess, t.followUpFail,
  t.manualSuccess, t.voiceSuccess, t.smsSuccess, t.weChatSuccess, t.noChannelSuccess,
  IF(t.needFollowUp = 0, '0.00%',
     CONCAT(ROUND(100 * (t.followUpSuccess + t.followUpFail) / t.needFollowUp, 2), '%')) AS followUpRate,
  IF(t.needFollowUp = 0, '0.00%',
     CONCAT(ROUND(100 * t.followUpSuccess / t.needFollowUp, 2), '%')) AS followUpSuccessRate,
  IF(t.manual = 0, '0.00%', CONCAT(ROUND(100 * t.manualSuccess / t.manual, 2), '%')) AS manualSuccessRate,
  IF(t.voice  = 0, '0.00%', CONCAT(ROUND(100 * t.voiceSuccess  / t.voice,  2), '%')) AS voiceSuccessRate,
  IF(t.sms    = 0, '0.00%', CONCAT(ROUND(100 * t.smsSuccess    / t.sms,    2), '%')) AS smsSuccessRate,
  IF(t.weChat = 0, '0.00%', CONCAT(ROUND(100 * t.weChatSuccess / t.weChat, 2), '%')) AS weChatSuccessRate,
  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,
  IFNULL(rt.timely_base, 0) AS timelyBase,
  IF(IFNULL(rt.timely_base, 0) = 0, '0.00%',
     CONCAT(ROUND(100 * rt.timely_cnt / rt.timely_base, 2), '%')) AS timelyRate,
  t.needFollowUp - (t.notSent + t.pendingFollowUp + t.sentNotDone + t.otherStatus
                    + t.followUpSuccess + t.followUpFail) AS diffNeed,
  t.followUpSuccess - (t.manualSuccess + t.voiceSuccess + t.smsSuccess + t.weChatSuccess
                       + t.noChannelSuccess) AS diffSucc
FROM (
  SELECT
    r.ward_code                                    AS leavehospitaldistrictcode,
    r.end_date                                     AS end_date,
    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 = 1, 1, 0))                 AS notSent,
    SUM(IF(r.sendstate = 2, 1, 0))                 AS pendingFollowUp,
    SUM(IF(r.sendstate = 3, 1, 0))                 AS sentNotDone,
    SUM(IF(r.sendstate IS NOT NULL AND r.sendstate <> 4
           AND r.sendstate NOT IN (1, 2, 3, 5, 6, 7), 1, 0)) AS otherStatus,
    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,
    SUM(IF(r.sendstate = 6 AND (r.cp IS NULL OR r.cp NOT IN ('1','3','4','5')), 1, 0)) AS noChannelSuccess
  FROM (
    SELECT
      ss.leavehospitaldistrictcode              AS ward_code,
      DATE(ss.endtime)                          AS end_date,
      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,
      IF(@followUpCountStyle = '1',
         IF(ss.sendstate IS NOT NULL AND ss.sendstate <> 4, 1, 0),
         IF(@followUpCountStyle = '3',
            IF(ss.sendstate = 6, 1, 0),
            IF(ss.sendstate IS NOT NULL, 1, 0))) AS ch_hit
    FROM service_subtask ss
    WHERE ss.del_flag = 0
      AND ss.service_type = '2'
      AND ss.visit_count = 1
      AND ss.leavehospitaldistrictcode IS NOT NULL
      AND ss.endtime >= DATE('2026-09-04 00:00:00')
      AND ss.endtime < DATE_ADD(DATE('2026-11-03 23:59:59'), INTERVAL 1 DAY)
      AND ss.orgid = '20001001'
  ) r
  GROUP BY r.ward_code, r.end_date
) t
LEFT JOIN (
  -- åŠæ—¶çŽ‡ï¼šåŒæ ·æŒ‰ã€Œç—…åŒº + å‡ºé™¢æ—¥æœŸã€èšåˆï¼Œæ‰èƒ½å’Œå·¦è¾¹æ¯ä¸€è¡Œå¯¹ä¸Š
  SELECT
    ss.leavehospitaldistrictcode AS ward_code,
    DATE(ss.endtime)             AS end_date,
    COUNT(*)                     AS timely_base,
    SUM(IF(ss.finishtime IS NOT NULL
           AND DATEDIFF(ss.finishtime, ss.endtime) <= 7 + IFNULL(cp.compensate_days, 0), 1, 0)) AS timely_cnt
  FROM service_subtask ss
  LEFT JOIN (
    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())
    AND ss.orgid = '20001001'
  GROUP BY ss.leavehospitaldistrictcode, DATE(ss.endtime)
) rt ON rt.ward_code = t.leavehospitaldistrictcode AND rt.end_date = t.end_date
ORDER BY t.leavehospitaldistrictname, t.end_date;
-- -----------------------------------------------------------------------------
-- PART 1D. æ˜Žç»†ç‰ˆï¼šä¸€è¡Œ = ä¸€æ¡é¦–次随访记录,直接带出院时间(要核对到人/到天就跑这个)
-- -----------------------------------------------------------------------------
SELECT
  ss.endtime                                              AS å‡ºé™¢æ—¶é—´,
  ss.leavehospitaldistrictname                            AS å‡ºé™¢ç—…区,
  ss.leaveldeptname                                       AS å‡ºé™¢ç§‘室,
  ss.sendname                                             AS æ‚£è€…,
  ss.sendstate                                            AS çŠ¶æ€ç ,
  CASE ss.sendstate
    WHEN 1 THEN '待发送' WHEN 2 THEN '待随访' WHEN 3 THEN '已发送未完成'
    WHEN 4 THEN '无需随访' WHEN 5 THEN '随访失败' WHEN 6 THEN '随访成功'
    WHEN 7 THEN '失败/人工超时' ELSE '其它'
  END                                                     AS éšè®¿çŠ¶æ€,
  ss.current_preachform                                   AS éšè®¿æ–¹å¼,
  ss.visit_count                                          AS éšè®¿æ¬¡æ•°,
  ss.visit_time                                           AS éšè®¿æ—¶é—´,
  ss.finishtime                                           AS å®Œæˆæ—¶é—´,
  IF(ss.sendstate = 4 OR ss.visit_time IS NULL, '-',
     IF(ss.finishtime IS NOT NULL AND DATEDIFF(ss.finishtime, ss.endtime) <= 7, '及时', '不及时')) AS æ˜¯å¦åŠæ—¶
FROM service_subtask ss
WHERE ss.del_flag = 0
  AND ss.service_type = '2'
  AND ss.visit_count = 1
  AND ss.leavehospitaldistrictcode IS NOT NULL
  AND ss.endtime >= DATE('2026-09-04 00:00:00')
  AND ss.endtime < DATE_ADD(DATE('2026-11-03 23:59:59'), INTERVAL 1 DAY)
  AND ss.orgid = '20001001'
ORDER BY ss.leavehospitaldistrictname, ss.endtime;
-- -----------------------------------------------------------------------------
-- PART 2. åŠæ—¶çއ rate(接口只在请求里传了 rateDay æ—¶æ‰æœ‰å€¼ï¼Œä¸ä¼ è¿”回 0.0)
--   å£å¾„与接口不同:这一块用的是「随访时间(visit_time)」窗口,分母是 visit_time ä¸ä¸ºç©ºä¸”
--   ä¸æ˜¯ã€Œæ— éœ€éšè®¿ã€çš„记录数;出院时间只作为附加条件。
--   rate = åŠæ—¶æ•° / åˆ†æ¯ï¼ŒåŠæ—¶ = finishtime ä¸ä¸ºç©º ä¸” DATEDIFF(finishtime, endtime) <= rateDay + è¡¥å¿å¤©æ•°
-- -----------------------------------------------------------------------------
SET @rateDay = 7;   -- å‡ å¤©åŠæ—¶çŽ‡ï¼›ä¸æƒ³çœ‹å°±æ•´æ®µä¸è·‘
SELECT
  ss.leavehospitaldistrictcode                                                        AS leavehospitaldistrictcode,
  COUNT(*)                                                                            AS base,   -- åˆ†æ¯
  SUM(IF(ss.finishtime IS NOT NULL
         AND DATEDIFF(ss.finishtime, ss.endtime) <= @rateDay + IFNULL(cp.compensate_days, 0), 1, 0)) AS timely, -- åŠæ—¶æ•°
  ROUND(SUM(IF(ss.finishtime IS NOT NULL
               AND DATEDIFF(ss.finishtime, ss.endtime) <= @rateDay + IFNULL(cp.compensate_days, 0), 1, 0))
        / COUNT(*), 4)                                                                AS rate
FROM service_subtask ss
LEFT JOIN (   -- è¡¥å¿æ—¶é—´ï¼šå…ˆæŒ‰å­ä»»åŠ¡èšæˆä¸€è¡Œå†å…³è”ï¼Œé¿å…ä¸€å¯¹å¤šæŠŠåˆ†æ¯æ”¾å¤§
  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_time IS NOT NULL
  AND ss.sendstate <> 4
  AND DATE(ss.visit_time) < DATE(NOW())        -- æŽ¥å£æœªä¼  startTime/endTime æ—¶çš„固定条件
  AND ss.endtime >= DATE(@hospitalStartTime)   -- ç­‰ä»·äºŽæŽ¥å£çš„ date_format(endtime,'%y%m%d') >= date_format(...)
  AND (@orgid    IS NULL OR ss.orgid    = @orgid)
  AND (@campusid IS NULL OR ss.campusid = @campusid)
GROUP BY ss.leavehospitaldistrictcode
ORDER BY base DESC;
-- -----------------------------------------------------------------------------
-- PART 3. æƒ³å›ºå®šæˆã€Œè§†å›¾ã€çš„话用这个(MySQL çš„视图不支持传入参数,
--         æ‰€ä»¥è§†å›¾æŒ‰ã€Œä¸€æ¡å­ä»»åŠ¡ä¸€è¡Œã€è½æ˜Žç»† + å‡ºé™¢æ—¥æœŸï¼Œå‚数过滤放在查询外层)
--   æ³¨æ„ï¼šè§†å›¾é‡ŒæŠŠ followUpCountStyle å›ºå®šæˆ '1'(本库未配置该参数,接口实际就走 '1')。
--         è‹¥æŸæœºæž„配的是 '3' æˆ–其它值,改视图里的 f_succ_first / f_succ_again / ch_hit ä¸‰å¤„。
-- -----------------------------------------------------------------------------
CREATE OR REPLACE VIEW v_sf_statistic_flag AS
SELECT
  ss.id,
  DATE(ss.endtime)                                        AS stat_date,     -- å‡ºé™¢æ—¥æœŸï¼ˆhospitalStartTime è¿‡æ»¤ç”¨ï¼‰
  ss.leavehospitaldistrictcode                            AS ward_code,
  ss.leavehospitaldistrictname                            AS ward_name,
  ss.leaveldeptcode                                       AS dept_code,
  ss.leaveldeptname                                       AS dept_name,
  ss.drcode                                               AS dr_code,
  ss.drname                                               AS dr_name,
  ss.endtime                                              AS end_time,
  ss.visit_time,
  ss.finishtime,
  ss.sendstate,
  ss.visit_count,
  ss.current_preachform                                   AS cp,
  ss.task_situation,
  IF(ss.sendstate = 4, 1, 0)                              AS f_no,
  IF(ss.sendstate = 4 AND ss.task_situation = 6, 1, 0)    AS f_filter,
  IF(ss.sendstate IS NOT NULL AND ss.sendstate <> 4, 1, 0) AS f_need,
  IF(ss.visit_count = 1, 1, 0)                            AS f_first,
  IF(ss.visit_count > 1, 1, 0)                            AS f_again,
  IF(ss.sendstate = 6, 1, 0)                              AS f_succ_first,   -- style '1'/'3'
  IF(ss.sendstate = 6, 1, 0)                              AS f_succ_again,   -- style '1'/'3'
  IF(ss.sendstate IN (5, 7), 1, 0)                        AS f_fail_first,
  IF(ss.sendstate = 5, 1, 0)                              AS f_fail_again,
  IF(ss.excep = '1', 1, 0)                                AS f_abnormal,
  IF(ss.sendstate IS NOT NULL AND ss.sendstate <> 4, 1, 0) AS ch_hit         -- style '1'
FROM service_subtask ss
WHERE ss.del_flag = 0
  AND ss.service_type = '2';
-- å»ºå¥½è§†å›¾åŽï¼ŒæŠŠ PART 1 é‡Œå†…层 "FROM ( ... ) r" æ•´æ®µæ¢æˆä¸‹é¢è¿™ä¸€è¡Œå³å¯ï¼ˆå…¶ä½™ä¸åŠ¨ï¼‰ï¼š
--   FROM v_sf_statistic_flag r
--   WHERE r.stat_date >= DATE(@hospitalStartTime)
--     AND (@hospitalEndTime IS NULL OR r.end_time < DATE_ADD(DATE(@hospitalEndTime), INTERVAL 1 DAY))
--     AND (@orgid IS NULL OR 1 = 1)   -- è§†å›¾ä¸­æœªå¸¦ orgid/campusid,需要的话把它们也加进视图
-- åŒæ—¶æŠŠä¸­é—´å±‚ nonFollowUpFirst çš„ "* IF(@followUpCountStyle = '1', 2, 1)" æ”¹æˆ "* 2"(视图里已是 style '1')。
-- -----------------------------------------------------------------------------
-- PART 4. å£å¾„说明 / ä¸ŽæŽ¥å£çš„差异
-- -----------------------------------------------------------------------------
-- 1) æŽ¥å£è¿”回里恒为 0、本次未输出的字段(代码这条路径没赋值):
--    serviceCount、bzxCount、ysfCount、yfsCount、dfsCount、fssbCount、ywcCount、
--    dxfscs、dxsfwccs、dhfscs、dhsfwccs、yczs、hfycs、finishRate、joyCount、joyAllCount、joyTotal。
--    pendingFollowUp(首次待随访)同理:getStatistic çš„“不分次数”分支里没有累加,恒为 0。
-- 2) nonFollowUpFirst åœ¨ followUpCountStyle='1' æ—¶æŽ¥å£ä¼šé‡å¤ç´¯åŠ ä¸€æ¬¡ï¼ˆä¸¤ä¸ª if éƒ½å‘½ä¸­ sendstate=4),
--    SQL ç”¨ *2 å¤åˆ»ã€‚若按“一条记录算一次”应为 1 å€ï¼ŒæŠŠ IF(@followUpCountStyle='1',2,1) æ”¹æˆ 1。
-- 3) æ¯”率格式:接口用 DecimalFormat("##.##%")(四舍六入、末尾 0 çœç•¥ï¼Œå¦‚ 85.5%),
--    SQL ç”¨ ROUND(...,2)(四舍五入、固定两位,如 85.50%);分母为 0 æ—¶æŽ¥å£ç»™ "0.00%"。
-- 4) deptcode/deptname/drcode/drname:接口取「分组内第一条记录」的值(HashMap é¡ºåºï¼Œæ— ä¸šåŠ¡å«ä¹‰ï¼‰ï¼Œ
--    SQL ç”¨ MAX() ä»£æ›¿ï¼›æŒ‰ç—…区统计时这四列只作参考。
-- 5) éšè®¿æƒ…况 taskSituation1~6:task_situation = 1~5 åˆ†åˆ«å¯¹åº” 1~5,其他(=7) å¯¹åº” 6。
--    æœ¬åº“ service_subtask.task_situation å…¨ä¸º NULL,所以这几列目前必然都是 0。
-- 6) åªçœ‹é¦–次/再次:接口传 visitCount=1 åªçœ‹é¦–次、>1 åªçœ‹å†æ¬¡ï¼›æœ¬ SQL ä¸¤ç±»åŒæ—¶ç»™ï¼Œå‡ä¸º 0 è¯´æ˜Žè¯¥çª—口没有对应数据。
-- 7) æ¢ç»´åº¦ï¼šGROUP BY r.ward_code(病区,本 SQL)→ r.dept_code(科室)/ r.dr_code(医生)。
--    ç»Ÿè®¡æ–¹å¼ã€åŠæ—¶çŽ‡å¤©æ•°éƒ½é€šè¿‡ PART 0 çš„变量控制,不需要改结构。
-- =============================================================================
sql/ÈÎÎñ¿ª¹Ø_sys_config.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
-- ----------------------------
-- ä»»åŠ¡æ¨¡å—å¼€å…³ï¼ˆsys_config å‚数配置表)
-- ----------------------------
-- é”®åï¼štask.enabled
-- é”®å€¼ï¼štrue  = å¯ç”¨ï¼ˆé»˜è®¤ï¼Œç­‰ä»·äºŽä¸é…ç½®è¿™ä¸€è¡Œï¼‰
--       false = å…³é—­
--
-- å…³é—­åŽçš„行为:
--   1) æœ¬æ¨¡å— 59 ä¸ªä»»åŠ¡ç«¯ç‚¹ï¼ˆ/smartor/servicetask/** 11 ä¸ª +
--      /smartor/serviceSubtask/** 48 ä¸ªï¼‰ä¼šè¢« TaskAvailabilityInterceptor æ‹¦ä¸‹ï¼Œ
--      è¿”回明确业务错误「任务功能未启用(参数配置 task.enabled=false),该接口不可用」,
--      è€Œä¸æ˜¯ 404;
--   2) ä»»åŠ¡/子任务数据本身不受影响,其它模块仍可通过 ruoyi-task çš„ api å–æ•°ï¼›
--   3) æ”¹å®Œç«‹å³ç”Ÿæ•ˆï¼Œæœ€å¤š 10 ç§’(进程内开关缓存 TTL),不需要重启。
--
-- æ³¨æ„ï¼šæœ¬é¡¹ç›®ç”¨çš„ MySQL ç‰ˆæœ¬å¯èƒ½ä¸æ”¯æŒ ADD COLUMN IF NOT EXISTS,
--       å› æ­¤è¿™é‡Œç”¨ information_schema åˆ¤æ–­åŽå†åŠ¨æ€æ‰§è¡Œï¼Œè„šæœ¬å¯é‡å¤æ‰§è¡Œã€‚
-- ----------------------------
SET @task_config_exists = (
    SELECT COUNT(1) FROM information_schema.COLUMNS
    WHERE TABLE_SCHEMA = DATABASE()
      AND TABLE_NAME = 'sys_config'
      AND COLUMN_NAME = 'config_key'
);
SET @ddl = IF(@task_config_exists = 0,
    'SELECT ''sys_config è¡¨ä¸å­˜åœ¨ï¼Œè¯·å…ˆæ‰§è¡Œå»ºè¡¨è„šæœ¬'' AS warning',
    'INSERT INTO sys_config (config_name, config_key, config_value, config_type, create_by, create_time, remark)
     SELECT ''任务模块开关'', ''task.enabled'', ''true'', ''Y'', ''admin'', SYSDATE(),
            ''任务/子任务模块总开关(运行时读取,改完最多10秒生效):true启用,false关闭;默认true''
     FROM DUAL
     WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE config_key = ''task.enabled'')');
PREPARE task_stmt FROM @ddl;
EXECUTE task_stmt;
DEALLOCATE PREPARE task_stmt;
-- æŸ¥è¯¢å½“前值
-- SELECT config_id, config_name, config_key, config_value, remark FROM sys_config WHERE config_key = 'task.enabled';
sql/ÐÞ¸´_nhfy_ÂúÒâ¶ÈÃ÷ϸ±»Âß¼­É¾³ý_20261021.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,128 @@
-- ============================================================================
-- å—华附一(nhfy):满意度导出「问题详情占比」比「患者随访信息表单」少 10 äººï¼ˆ370 vs 380)
--                                                        2026-10-21 æŽ’查
--
-- ã€çŽ°è±¡ã€‘å¯¼å‡º patItemExport(sheet1=患者随访信息表单 380 è¡Œï¼Œsheet2=问题详情占比)
--         sheet2 é‡Œ 19 é“题的“数量合计”都只有 370,比 sheet1 å°‘ 10。
--
-- ã€æ ¹å› ï¼ˆå·²åœ¨åº“上核实)】
--   æœ¬æ‰¹ 380 ä¸ªå­ä»»åŠ¡é‡Œï¼Œæœ‰ 10 ä¸ªå­ä»»åŠ¡çš„**全部作答明细被逻辑删除**(service_subtask_detail.del_flag='1'),
--   è€Œ sheet2 çš„ SQL(SatisfactionMapper.xml#statMydQuestionOptionForExport)带 d.del_flag=0,
--   æ‰€ä»¥è¿™ 10 äººåœ¨å æ¯”里完全消失;sheet1 åªæŸ¥ service_subtask、不看明细,所以他们仍在表单里。
--
--   æ ¸å®žæ•°æ®ï¼ˆ2026-10-21 æŸ¥ 192.168.99.192/smartor_nhfy):
--     Â· å¤çŽ°å¯¼å‡ºæ¡ä»¶ = orgid 20001001 / campusid 30001002 / service_type 6 / sendstate 6 /
--       visit_count 1 / (deptcode='40003511' OR leavehospitaldistrictcode='40003514') â†’ æ°å¥½ 380 è¡Œ
--     Â· è¿™ 380 è¡Œé‡Œï¼Œæœ‰æœ‰æ•ˆæ˜Žç»†(del_flag=0)的只有 370 è¡Œ â†’ 19 é“题每题都少同样的 10 äºº
--     Â· 10 ä¸ªé—®é¢˜å­ä»»åŠ¡å…¨éƒ¨åœ¨ taskid=196「全院满意度」,完成时间都在 2026-09-08,deptcode=40003511
--     Â· æ¯ä¸ªå­ä»»åŠ¡è¢«å†™å…¥äº† 2~6 ä»½ä¸€æ¨¡ä¸€æ ·çš„答卷(19 é¢˜ Ã— N ä»½ï¼‰ï¼Œ**全部 del_flag=1**,
--       ä¸” update_time == create_time(写入后同一秒内就被删),例如 æ¨å†›(53783) 6 ä»½ç­”案逐题完全相同
--     Â· å…¨é™¢åŒç±»åž‹å¼‚常:service_type=6 é‡Œâ€œæ˜Žç»†å…¨è¢«åˆ å…‰â€çš„子任务共 22 ä¸ªï¼ˆ8月3个、9月19个),
--       å¦æœ‰ 60 ä¸ªæ˜¯â€œéƒ¨åˆ†è¢«åˆ â€ã€‚所以这不是一次性手滑,而是某条“重填/覆盖”链路反复发生。
--
-- ã€ä¸ºä»€ä¹ˆâ€œé‡å¡«â€ä¼šå˜æˆå¤šä»½å‰¯æœ¬ã€‘
--   ServiceSubtaskDetailMapper.xml#isExistServiceSubtaskDetail åªæŸ¥ del_flag=0,
--   æ˜Žç»†ä¸€æ—¦è¢«é€»è¾‘删除就查不到 â†’ ä¸‹ä¸€æ¬¡ä¿å­˜èµ° insert,于是每重填一次就多一份副本。
--
-- ã€è¯´æ˜Žã€‘当前仓库里唯一会把 service_subtask_detail.del_flag ç½® '1' çš„代码是
--   ServiceSubtaskServiceImpl#setFailSubTaskDefalutVal(约 3535~3543 è¡Œï¼šä¿å­˜é»˜è®¤å€¼å¤±è´¥æ—¶â€œåˆ é™¤è¯¯æ–°å¢ždetail里的数据”),
--   ä½†å®ƒåªå¤„理 sendstate=5、且只针对 sys_config é‡Œ hosp.default.dept(40003025,40003460) /
--   hosp.default.ward(40003026,40003461) é…ç½®çš„科室病区,不含 40003511、也不是 sendstate=6。
--   â†’ çº¿ä¸ŠåŒ…/其他入口还有一处“重填即先删旧明细”的逻辑,需要继续定位(见文末【待办】)。
--
-- ã€æœ¬è„šæœ¬ã€‘ç»™å‡ºä¸¤ç§æŠŠ 380 / 370 è¡¥æˆä¸€è‡´çš„办法,默认都注释掉,确认后再执行。
-- ============================================================================
-- ----------------------------------------------------------------------------
-- ã€0】影响清单(10 ä¸ªå­ä»»åŠ¡ï¼Œdeptcode=40003511,taskid=196,2026-09-08 å®Œæˆï¼‰
-- ----------------------------------------------------------------------------
SELECT s.id AS subid, s.patid, s.sendname, s.phone, s.finishtime,
       (SELECT COUNT(*) FROM service_subtask_detail d WHERE d.sub_id = s.id) AS detail_rows,
       (SELECT COUNT(*) FROM service_subtask_detail d WHERE d.sub_id = s.id AND d.del_flag = 0) AS live_rows
FROM service_subtask s
WHERE s.id IN (44957, 45094, 45095, 45099, 45102, 45108, 45109, 45110, 45111, 45114)
ORDER BY s.finishtime;
-- é¢„期:10 è¡Œï¼Œdetail_rows 38~114,live_rows å…¨ä¸º 0
-- ----------------------------------------------------------------------------
-- ã€1】备份(执行任何修改前先跑)
-- ----------------------------------------------------------------------------
-- CREATE TABLE bak_ssd_20261021_nhfy AS
-- SELECT * FROM service_subtask_detail
-- WHERE sub_id IN (44957, 45094, 45095, 45099, 45102, 45108, 45109, 45110, 45111, 45114);
-- ----------------------------------------------------------------------------
-- ã€2】方案A(推荐):每道题只恢复“最新一份”副本,其余副本继续保留 del_flag=1
--      å½±å“è¡Œæ•°ï¼š190(= 19 é¢˜ Ã— 10 äººï¼‰ï¼Œå·² dry-run æ ¸å®ž
--      âš ï¸ å‰æï¼šè¿™äº›ç­”案确实是患者的真实作答(已核对:每份副本答案完全一致,不是脏数据)
-- ----------------------------------------------------------------------------
-- UPDATE service_subtask_detail d
--     JOIN (SELECT sub_id, scriptid, MAX(id) AS keep_id
--           FROM service_subtask_detail
--           WHERE sub_id IN (44957, 45094, 45095, 45099, 45102, 45108, 45109, 45110, 45111, 45114)
--           GROUP BY sub_id, scriptid) x
--     ON x.keep_id = d.id
-- SET d.del_flag = '0', d.update_time = NOW();
-- ----------------------------------------------------------------------------
-- ã€2b】方案B(如果这几份问卷其实不该算“已完成”):把子任务改成“无需随访/不执行”,
--      è¿™æ ·å®ƒä»¬ä¸ä¼šå†è¿›â€œå·²å®Œæˆâ€å£å¾„,表单和占比都会是 370。
-- ----------------------------------------------------------------------------
-- UPDATE service_subtask SET sendstate = 4, update_time = NOW()
-- WHERE id IN (44957, 45094, 45095, 45099, 45102, 45108, 45109, 45110, 45111, 45114);
-- ----------------------------------------------------------------------------
-- ã€3】校验(执行方案A后应输出 380 / 380)
-- ----------------------------------------------------------------------------
SELECT COUNT(*) AS form_rows,
       COUNT(DISTINCT CASE WHEN d.live = 1 THEN d.sub_id END) AS with_live_detail
FROM service_subtask s
LEFT JOIN (SELECT sub_id, MAX(del_flag = 0) AS live FROM service_subtask_detail GROUP BY sub_id) d
       ON d.sub_id = s.id
WHERE s.del_flag = '0' AND s.orgid = '20001001' AND s.campusid = '30001002'
  AND s.service_type = 6 AND s.sendstate = 6 AND s.visit_count = 1
  AND (s.deptcode = '40003511' OR s.leavehospitaldistrictcode = '40003514');
-- é€é¢˜å†ç¡®è®¤ä¸€æ¬¡ï¼ˆ19 è¡Œï¼Œcounted_patients åº”全部 = 380)
SELECT d.questiontext, COUNT(DISTINCT s.id) AS counted_patients
FROM service_subtask s
JOIN service_subtask_detail d
  ON d.sub_id = s.id AND d.del_flag = 0
 AND d.id = (SELECT MAX(d2.id) FROM service_subtask_detail d2
             WHERE d2.sub_id = d.sub_id AND d2.questiontext <=> d.questiontext AND d2.del_flag = '0')
WHERE s.del_flag = '0' AND s.orgid = '20001001' AND s.campusid = '30001002'
  AND s.service_type = 6 AND s.sendstate = 6 AND s.visit_count = 1
  AND (s.deptcode = '40003511' OR s.leavehospitaldistrictcode = '40003514')
GROUP BY d.questiontext
ORDER BY counted_patients DESC, d.questiontext;
-- ----------------------------------------------------------------------------
-- ã€4】回滚(方案A执行后才需要)
-- ----------------------------------------------------------------------------
-- UPDATE service_subtask_detail d
--     JOIN bak_ssd_20261021_nhfy b ON b.id = d.id
-- SET d.del_flag = b.del_flag, d.update_time = b.update_time;
-- DROP TABLE bak_ssd_20261021_nhfy;
-- ============================================================================
-- ã€å¾…办:根因治理(代码侧)】
-- 1) å®šä½çº¿ä¸Šâ€œé‡å¡«/覆盖”把旧明细置 del_flag=1 çš„那段代码:
--    å½“前仓库只有 setFailSubTaskDefalutVal ä¼šè¿™ä¹ˆåšï¼ˆä¸”只覆盖 hosp.default.dept/ward çš„科室),
--    40003511 ä¸åœ¨é…ç½®é‡Œ â†’ è¯´æ˜Žçº¿ä¸ŠåŒ…版本不同,或还有另一处删除入口。
--    å»ºè®®ï¼šåœ¨ service_subtask_detail çš„ update/delete å…¥å£åŠ æ—¥å¿—ï¼ˆsub_id + æ“ä½œäºº + æ—¶é—´ï¼‰ï¼Œ
--          æˆ–对线上 jar ç›´æŽ¥åæŸ¥ updateServiceSubtaskDetail çš„调用点。
-- 2) æ”¹æˆâ€œå…ˆå†™æˆåŠŸã€å†åˆ æ—§â€ï¼Œä¸è¦â€œå…ˆåˆ åŽå†™â€ï¼š
--    çŽ°åœ¨ isExistServiceSubtaskDetail åªæŸ¥ del_flag=0,删除后必然再 insert,
--    æ‰€ä»¥ä¸€æ¬¡å¼‚常中断就会留下“全删无新”的空答卷(本问题)。
--    å»ºè®®ç»™ (sub_id, scriptid) åŠ å”¯ä¸€çº¦æŸæˆ–æ”¹æˆç‰©ç†è¦†ç›–æ›´æ–°ã€‚
-- 3) å¯¼å‡ºä¾§å…œåº•(口径可自解释):
--    â€œé—®é¢˜è¯¦æƒ…占比”Sheet å¢žåŠ ä¸€è¡Œã€Œæœªä½œç­”ã€ï¼›æˆ–åœ¨ã€Œæ‚£è€…éšè®¿ä¿¡æ¯è¡¨å•ã€åŠ ä¸€åˆ—ã€Œæ˜¯å¦å·²ä½œç­”ã€ï¼Œ
--    è¿™æ · 380 vs 370 å½“场就能对上,不用再翻库。
-- ============================================================================
sql/³öÈëÔºËæ·Ã¿ª¹Ø_sys_config.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,114 @@
-- ----------------------------
-- å‡ºå…¥é™¢éšè®¿æ¨¡å— + å®£æ•™æ¨¡å— å¼€å…³ï¼ˆsys_config å‚数配置表)
-- ----------------------------
-- æœ¬è„šæœ¬ä¸€æ¬¡å»ºä¸¤ä¸ªé”®ï¼Œéƒ½å¯é‡å¤æ‰§è¡Œï¼ˆå¹‚ç­‰ï¼‰ï¼š
--
--   é”®åï¼šinhospvisit.enabled
--   é”®å€¼ï¼štrue  = å¯ç”¨ï¼ˆé»˜è®¤ï¼Œç­‰ä»·äºŽä¸é…ç½®è¿™ä¸€è¡Œï¼‰
--         false = å…³é—­
--
--   é”®åï¼špublicity.enabled
--   é”®å€¼ï¼štrue  = å¯ç”¨ï¼ˆé»˜è®¤ï¼‰
--         false = å…³é—­ï¼ˆå…¥é™¢å®£æ•™ä¸Žè¯å“å®£æ•™éƒ½ä¸å†æ‰§è¡Œï¼‰
--
-- å…³é—­ inhospvisit åŽçš„行为:
--   1) ã€HTTP é¢ã€‘{@code /smartor/patinhosp} å‰ç¼€ä¸‹çš„端点**全部**被
--      InhospVisitAvailabilityInterceptor æ‹¦ä¸‹ï¼Œè¿”回明确业务错误
--      ã€Œå‡ºå…¥é™¢éšè®¿åŠŸèƒ½æœªå¯ç”¨ï¼ˆå‚æ•°é…ç½® inhospvisit.enabled=false),该接口不可用」而不是 404。
--      å—管的是**前缀通配** /smartor/patinhosp/**(不再逐个枚举路径),
--      å½“前实测覆盖 15 ä¸ªç«¯ç‚¹ï¼ˆ3 éšè®¿è§¦å‘ + 9 åŸºç¡€æŸ¥è¯¢/CRUD + 3 å¤åˆç«¯ç‚¹ï¼‰ã€‚
--      âœ… **以后再往本模块加接口,自动受管,不需要改这个配置、也不用改 WebConfig**
--         â€”— åªè¦æ–°æŽ§åˆ¶å™¨ä»æŒ‚在 /smartor/patinhosp å‰ç¼€ä¸‹ã€‚
--      âš ï¸ å£å¾„变更史:原设计只拦 2 ä¸ªéšè®¿è§¦å‘端点,基础查询刻意不拦
--      ï¼ˆç†ç”±ï¼š"关随访不该让出入院记录查不到")。用户实机发现
--      POST /smartor/patinhosp/selectPatMedInhospList åœ¨å¼€å…³å…³é—­åŽä»èƒ½æŸ¥åˆ°ï¼Œ
--      å†³å®šæ”¹ä¸º"关就整个模块的 HTTP é¢ä¸å¯ç”¨"。
--      è¦†ç›–性由 sql/_probe/verify_inhospvisit_switch.py ä»ŽæŽ§åˆ¶å™¨æºç **实测**核对
--      ï¼ˆæ–°æŽ§åˆ¶å™¨è‹¥æŒ‚错前缀会被它 FAIL å‡ºæ¥ï¼‰ã€‚
--   2) âš ï¸ **只拦 HTTP,不动跨模块数据契约**:InhospVisitDataService(HIS é‡‡é›†ã€æ¡£æ¡ˆæ±‡èšã€
--      ç»Ÿè®¡ç­‰æ¨¡å—内部/跨模块访问)**照常可用**。理由:那是数据同步链路,
--      å…³æŽ‰éšè®¿ä¸è¯¥è®© HIS å†™ä¸è¿›å‡ºå…¥é™¢è®°å½•(数据断流比"页面看不到"严重得多)。
--      æ‰€ä»¥"整个模块不可用"的准确含义是「该模块的 HTTP é¢ä¸å¯ç”¨ã€ã€‚
--   3) âš ï¸ **定时任务也受此开关控制**(2025 ä¿®å¤ï¼Œä¸ŽåŽŸå…ˆçš„"已拍板:不受控"相反 â€”— è§ä¸‹ï¼‰ã€‚
--      RyTask çš„ 5 ä¸ªå…¥å£å·²å…¨éƒ¨æŽ¥å¼€å…³ï¼šdealOutHospInfo / dealOutHospInfoBySchedule /
--      dealTaskGroupOutHosp / rollTaskGroupCycle èµ°é—¨é¢ï¼ŒdealSubtaskDeadline èµ°å…¥å£å®ˆå«ã€‚
--      å…³é—­åŽæ¨¡å—实现打 WARN「模块未启用…跳过」并返回 0,当天不再生成随访。
--
--      âš ï¸âš ï¸ è¿™æ¡**推翻了原设计**,必须知道风险变了:
--        åŽŸè®¾è®¡çš„ç†ç”±æ˜¯"关掉定时任务 = å½“天全院随访停摆,而开关是热读的,
--        ä¸€æ¬¡è¯¯æ“ä½œæ²¡æœ‰ä»»ä½•反馈渠道"。现在它确实会停掉定时任务,所以:
--          Â· è¯¯æ“ä½œä»£ä»· = å½“天全院随访不生成(与原设计担心的一致);
--          Â· ä½†åé¦ˆæ¸ é“**变好了** â€”— å…³é—­æ—¶æ—¥å¿—里会明确打出
--            ã€Œã€å‡ºå…¥é™¢éšè®¿ã€‘模块未启用(参数配置 inhospvisit.enabled=false),… è·³è¿‡ã€ï¼Œ
--            è€Œä¸æ˜¯åƒåŽŸæ¥é‚£æ ·"关了开关却毫无效果、也没任何提示";
--          Â· å¼€å…³åœ¨ã€Œç³»ç»Ÿç®¡ç† â†’ å‚数设置」里可见可改,比去「定时任务」里找 sys_job æ›´ç›´è§‚。
--        è¦æš‚停定时任务仍然可以去「系统管理 â†’ å®šæ—¶ä»»åŠ¡ã€æš‚åœ sys_job(两条路都有效):
--          ryTask.dealOutHospInfo / ryTask.dealOutHospInfoBySchedule / ryTask.dealTaskGroupOutHosp
--        **本开关覆盖的定时任务入口已全覆盖**(5 ä¸ªï¼‰ï¼š
--          dealOutHospInfo / dealOutHospInfoBySchedule / dealTaskGroupOutHosp /
--          rollTaskGroupCycle / dealSubtaskDeadline
--        å…¶ä¸­ rollTaskGroupCycle èµ°é—¨é¢ï¼ˆå®Œæˆäº†æ¨¡å—化方案里一直没做的"第 10 æ­¥"切换),
--        dealSubtaskDeadline å› æ¨¡å—侧无完整入口而采用"入口自带守卫"。
--        å¦ï¼šæ‰‹æœ¯éšè®¿ï¼ˆdealOperationInfo)与门诊随访(门诊生成)各自受
--        othervisit.enabled / outpatientvisit.enabled ç®¡è¾–,不受本开关管(这是刻意的)。
--   4) æ”¹å®Œç«‹å³ç”Ÿæ•ˆï¼Œæœ€å¤š 10 ç§’(进程内开关缓存 TTL),不需要重启。
--
-- å…³é—­ publicity åŽçš„行为:
--   1) ã€ç”Ÿæˆä¾§ã€‘入院宣教(sys_config: in.helibrary.switch = "2" æ—¶æ‰§è¡Œï¼‰ä¸Žè¯å“å®£æ•™
--      ï¼ˆä»… sltd ç«™ç‚¹ï¼‰éƒ½ä¸å†ç”Ÿæˆ â€”— å‡ºå…¥é™¢éšè®¿ä¸»æµç¨‹å›žè°ƒ PublicityApi,
--      ç”± PublicityFacade åˆ¤æœ¬å¼€å…³åŽè·³è¿‡ï¼›
--   2) ã€å‘送侧】已生成的宣教子任务(subtask.type = "3",含"通知")**也不再发出** â€”—
--      RyTask#sfHandlle ä¸Ž RedisMqReceiver#tsakHandle éƒ½åœ¨æ´¾å‘前判本开关,
--      å…³é—­æ—¶è·³è¿‡æ‰€æœ‰é€šé“(电话/短信/公众号/人工)并打 WARN:
--        ã€Œã€å®£æ•™ã€‘模块未启用(参数配置 publicity.enabled=false),跳过宣教发送。subtaskId=..., patid=...」
--      âš ï¸ 2025 ä¿®å¤å‰**只**管住了生成侧:关掉开关后已生成的宣教照样发出去。
--      âš ï¸ è·³è¿‡æ—¶åˆ»æ„**不推进 subtask çŠ¶æ€ã€ä¹Ÿä¸è®°å¤±è´¥** â€”— å…³é—­æ˜¯"暂时不发",
--         ä¸æ˜¯"这个患者不需要宣教";标成失败会让这条宣教在开关恢复后永远丢失。
--         å¼€å…³æ‰“开后,下一次 longTaskSend / compensateTask ä¼šç…§å¸¸æŠŠå®ƒæžå‡ºæ¥å‘出。
--   3) å‡ºå…¥é™¢éšè®¿ä¸»æµç¨‹ä¸å—影响(宣教失败绝不拖垮随访),其它类型的随访(type 1/2/4)也不受影响;
--   4) æ”¹å®Œç«‹å³ç”Ÿæ•ˆï¼Œæœ€å¤š 10 ç§’,不需要重启。
--
-- æ³¨æ„ï¼šæœ¬é¡¹ç›®ç”¨çš„ MySQL ç‰ˆæœ¬å¯èƒ½ä¸æ”¯æŒ ADD COLUMN IF NOT EXISTS,
--       å› æ­¤è¿™é‡Œç”¨ information_schema åˆ¤æ–­åŽå†åŠ¨æ€æ‰§è¡Œã€‚
-- ----------------------------
SET @sys_config_exists = (
    SELECT COUNT(1) FROM information_schema.COLUMNS
    WHERE TABLE_SCHEMA = DATABASE()
      AND TABLE_NAME = 'sys_config'
      AND COLUMN_NAME = 'config_key'
);
-- ---------------------------------------------------------------
-- 1) inhospvisit.enabled
-- ---------------------------------------------------------------
SET @ddl = IF(@sys_config_exists = 0,
    'SELECT ''sys_config è¡¨ä¸å­˜åœ¨ï¼Œè¯·å…ˆæ‰§è¡Œå»ºè¡¨è„šæœ¬'' AS warning',
    'INSERT INTO sys_config (config_name, config_key, config_value, config_type, create_by, create_time, remark)
     SELECT ''出入院随访模块开关'', ''inhospvisit.enabled'', ''true'', ''Y'', ''admin'', SYSDATE(),
            ''出入院随访总开关(运行时读取,改完最多10秒生效):true启用,false关闭;默认true。注意:2025修复后本开关也作用于定时任务(dealOutHospInfo等3个入口),关闭=当天不再生成随访''
     FROM DUAL
     WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE config_key = ''inhospvisit.enabled'')');
PREPARE inhospvisit_stmt FROM @ddl;
EXECUTE inhospvisit_stmt;
DEALLOCATE PREPARE inhospvisit_stmt;
-- ---------------------------------------------------------------
-- 2) publicity.enabled
-- ---------------------------------------------------------------
SET @ddl = IF(@sys_config_exists = 0,
    'SELECT ''sys_config è¡¨ä¸å­˜åœ¨ï¼Œè¯·å…ˆæ‰§è¡Œå»ºè¡¨è„šæœ¬'' AS warning',
    'INSERT INTO sys_config (config_name, config_key, config_value, config_type, create_by, create_time, remark)
     SELECT ''宣教模块开关'', ''publicity.enabled'', ''true'', ''Y'', ''admin'', SYSDATE(),
            ''宣教(入院宣教/药品宣教)总开关(运行时读取,改完最多10秒生效):true启用,false关闭;默认true。同时管生成侧与发送侧(type=3宣教/通知)''
     FROM DUAL
     WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE config_key = ''publicity.enabled'')');
PREPARE publicity_stmt FROM @ddl;
EXECUTE publicity_stmt;
DEALLOCATE PREPARE publicity_stmt;
-- æŸ¥è¯¢å½“前值
-- SELECT config_id, config_name, config_key, config_value, remark FROM sys_config
--  WHERE config_key IN ('inhospvisit.enabled', 'publicity.enabled');
sql/ÔÚÔºÐÅϢˢÐÂ_sys_job.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
-- ============================================================================
-- åœ¨é™¢æ‚£è€…"科室 / ç—…区 / åºŠä½"每日刷新 â€”— å®šæ—¶ä»»åŠ¡ç™»è®°ï¼ˆå¯é‡å¤æ‰§è¡Œï¼‰
--
-- èƒŒæ™¯ï¼šåœ¨é™¢æ‚£è€…会转科、转病区、换床,而 pat_med_inhosp çš„位置字段只在"入院采集"时
--       å†™è¿‡ä¸€æ¬¡ï¼Œä¹‹åŽä¸ä¼šè‡ªå·±æ›´æ–°ï¼Œè½¬ç§‘/换床患者会一直显示旧科室/旧病区/旧床位。
--       æœ¬ä»»åŠ¡æ¯å¤©æŒ‰æ‚£è€…æŸ¥ä¸€é HIS é‡Œè¯¥æ‚£è€…"当前的在院信息"
--       ï¼ˆdtcQueryHealthcareRecordList â€”— ä¸Žå…¥é™¢é‡‡é›†åŒä¸€ä¸ªæŽ¥å£ï¼ŒæŒ‰ healthcareRecordNo å•查、
--        statusList=["FH0109.24"] åªå–在院记录),与库内比对后只写变化的字段。
--       å·²ä¸åœ¨é™¢ï¼ˆæŽ¥å£è¿”回空)的患者只跳过计数:位置保持不动,inhospstate ä»ç”±å‡ºé™¢é‡‡é›†è´Ÿè´£ã€‚
--
-- å®žçŽ°ï¼šruoyi-datacollection çš„ InhospDeptRefreshServiceImpl
--       å…¥å£ï¼šruoyi-quartz çš„ com.ruoyi.quartz.task.RyTask#refreshInhospDeptInfo()
--       å£å¾„:仅 spring.profiles.active=nhfy(南华附一)执行;其他站点直接跳过
--
-- å‚数选择理由:
--   cron_expression = '0 30 3 * * ?'  æ¯å¤© 03:30
--       â€”— é¿å¼€ 01:00 çš„入院采集(dealHisData)与 02:00 çš„出院处理(dealOutHospInfo),
--          å¦åˆ™ä¼šä¸Žæœ¬ä»»åŠ¡æŠ¢"同一批患者的当前状态",读到半新半旧的数据。
--   concurrent      = '1'             ç¦æ­¢å¹¶å‘(一轮约 10 åˆ†é’Ÿï¼šå®žæµ‹å•次 137~460ms Ã— çº¦ 3000 äººï¼›
--                                     å åŠ æ‰§è¡Œæ²¡æœ‰æ„ä¹‰ï¼Œå®žçŽ°é‡Œå¦æœ‰ Redis è¿è¡Œé”å…œåº•手工触发)
--   misfire_policy  = '1'             ç«‹å³æ‰§è¡Œï¼šæœåŠ¡åœ¨ 03:30 åœæœºã€ä¹‹åŽæ‰æ¢å¤æ—¶è¡¥è·‘一次。
--                                     æœ¬ä»»åŠ¡å¹‚ç­‰ï¼ˆåªå†™å˜åŒ–çš„å­—æ®µï¼‰ï¼Œè¡¥è·‘æ— å®³ï¼›
--                                     è‹¥æƒ³"错过就算了",改成 '3' å³å¯ã€‚
--   status          = '0'             æ­£å¸¸ï¼ˆç™»è®°åŽå³å¯ç”¨ï¼‰ã€‚要停用改成 '1',
--                                     æˆ–直接在「系统管理 â†’ å®šæ—¶ä»»åŠ¡ã€é‡Œæš‚åœã€‚
--
-- æ³¨æ„ï¼šæœ¬ä»»åŠ¡å—æ•°æ®é‡‡é›†æ¨¡å—å¼€å…³ç®¡è¾–ï¼ˆsys_config: datacollection.enabled)——
--       æ¨¡å—关闭时门面抛 DataCollectionModuleDisabledException,RyTask åªæ‰“一条 WARN å¹¶è·³è¿‡æœ¬è½®ã€‚
--       è§ sql/数据采集开关_sys_config.sql。
-- ============================================================================
insert into sys_job (job_name, job_group, invoke_target, cron_expression,
                     misfire_policy, concurrent, status, create_by, create_time, remark)
select '在院患者科室病区床位刷新(南华附一,每天)',
       'DEFAULT',
       'ryTask.refreshInhospDeptInfo()',
       '0 30 3 * * ?',
       '1',
       '1',
       '0',
       'admin',
       sysdate(),
       '每天 03:30 æŒ‰æ‚£è€…刷新 pat_med_inhosp çš„科室/病区/床位(数据采集模块)'
from dual
where not exists (
    select 1 from sys_job where invoke_target = 'ryTask.refreshInhospDeptInfo()'
);
-- ---------------------------------------------------------------------------
-- ç™»è®°åŽè‡ªæ£€ï¼šåº”恰好一行,status='0'(正常)、concurrent='1'(禁止并发)
-- ---------------------------------------------------------------------------
-- select job_id, job_name, invoke_target, cron_expression, misfire_policy, concurrent, status
-- from sys_job where invoke_target = 'ryTask.refreshInhospDeptInfo()';
-- åªæƒ³å…ˆè§‚察、不启用时:登记成暂停状态,验证过再启用
-- update sys_job set status = '0' where invoke_target = 'ryTask.refreshInhospDeptInfo()';
-- å›žæ»šï¼ˆæ’¤é”€æœ¬ä»»åŠ¡ç™»è®°ï¼‰
-- delete from sys_job where invoke_target = 'ryTask.refreshInhospDeptInfo()';
sql/×¥ÏÖ³¡_Ë­°ÑÃ÷ϸÖÃΪɾ³ý_20261021.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,173 @@
-- ============================================================================
-- æŠ“现行:到底是谁把 service_subtask_detail.del_flag ä»Ž 0 æ”¹æˆ 1
--                                 å—华附一(nhfy)2026-10-21
--
-- ã€ç»“论先说】
--   å…¨ä»“库能写 service_subtask_detail.del_flag çš„只有 3 å¤„,其中只有 1 å¤„能“一次清空整份答卷”,
--   è€Œä¸”只有它不会改 update_time:
--
--   A. ServiceSubtaskServiceImpl#setFailSubTaskDefalutVal(3533~3544 è¡Œï¼‰
--        ç›´æŽ¥è°ƒ mapper å¾ªçŽ¯ç½® del_flag=1,**不改 update_time**;只有它能把整卷一次清空。
--        quartz å…¥å£ = ryTask.setFailSubTaskDefalutVal(),读 sys_config: hosp.default.fail.day
--        ï¼ˆnhfy è¯¥å‚æ•°=5,存在),但 sys_job é‡Œå½“前没有这条任务。
--   B. POST /smartor/serviceSubtaskDetail/edit、/batchEdit(Controller 118~142 è¡Œï¼‰
--        -> ServiceSubtaskDetailServiceImpl.updateServiceSubtaskDetail(180~183 è¡Œï¼‰
--        -> ä¼šæŠŠå®¢æˆ·ç«¯ä¼ çš„ delFlag åŽŸæ ·å†™è¿› SQL,且强制 updateTime = NOW()。
--   C. POST /smartor/serviceSubtaskDetail/editByCondition(Controller 144~148 è¡Œï¼Œ**没有 @Log**)
--        -> updateSSDByCondition,同样客户端可控 delFlag,同样强制 updateTime = NOW();
--        WHERE æ˜¯ patid + scriptid + sub_id(一次只能清一道题)。
--
-- ã€çŽ°åœºæŒ‡çº¹ï¼ˆ2026-09-08 é‚£ 10 ä¸ªäººï¼Œå…± 735 è¡Œè¢«åˆ æ˜Žç»†ï¼‰ã€‘
--   Â· æ¯è¡Œ update_time ä¸Ž create_time **逐秒相等**(45094 çš„ 6 ä»½å‰¯æœ¬è·¨äº† 15:41:18~15:41:25,
--     åˆ é™¤åŽä»ç„¶æ˜¯ç›¸ç­‰ï¼‰â†’ è¯´æ˜Žåˆ é™¤æ—¶æ²¡æœ‰é‡æ–°å†™ update_time。
--   Â· èµ° B / C ä¸€å®šä¼šæŠŠ update_time æ”¹æˆåˆ é™¤é‚£ä¸€åˆ» â†’ ä¸ŽçŽ°åœºä¸ç¬¦ï¼Œå¯åŸºæœ¬æŽ’é™¤ã€‚
--   Â· å”¯ä¸€ç¬¦åˆ"直接改成 1、不动 update_time"的就是 A é‚£ç§â€œè¯»å‡ºæ¥â†’setDelFlag→update”的写法。
--   Â· ä½† A çš„两个前提(sendstate=5、科室在 hosp.default.dept/ward)在 nhfy å½“前配置下都不满足,
--     ä¸” sys_job é‡Œæ²¡æœ‰è¿™ä¸ªä»»åŠ¡ã€2026-09-08 ä¹Ÿæ²¡æœ‰ä»»ä½•“手动执行任务”的操作日志
--     â†’ çº¿ä¸Šè·‘的包与当前仓库代码不一致,或这段“先插后删”的逻辑挂在别的入口上。
--
-- ã€æœ¬è„šæœ¬ã€‘ä¸¤æ¡æŠ“çŽ°è¡Œçš„è·¯å­ï¼šâ‘  MySQL general_log æŠ“ SQL åŽŸæ–‡ï¼ˆæœ€å‡†ï¼Œ10 åˆ†é’Ÿçª—口);
--          â‘¡ å®¡è®¡è§¦å‘器(可长期挂着,只记录 del_flag å˜åŒ–)。
--          éƒ½æ˜¯æŸ¥/记录,不改业务数据;general_log ç”¨å¼€å…³æŽ§åˆ¶ï¼Œè®°å¾—关。
-- ============================================================================
-- ============================================================================
-- æ–¹æ¡ˆâ‘ ï¼ˆæŽ¨èã€æœ€å¿«ï¼‰ï¼šgeneral_log æŠ“到 SQL åŽŸæ–‡ â€”— è¯­å¥å½¢çŠ¶ç›´æŽ¥æš´éœ²æ˜¯å“ªæ¡ä»£ç è·¯å¾„
--   Â· åªåœ¨å¤çŽ°çª—å£ï¼ˆä¾‹å¦‚æœ‰äººé‡å¡«é—®å· / å®šæ—¶ä»»åŠ¡è·‘æ‰¹çš„æ—¶æ®µï¼‰æ‰“å¼€ 5~10 åˆ†é’Ÿ
--   Â· éœ€è¦ root æˆ– SUPER æƒé™ï¼ˆnhfy åº”用账号就是 root)
--   Â· å½±å“å¾ˆå°ï¼Œä½†ç”Ÿäº§çŽ¯å¢ƒè¯·æŒ‘ä½Žå³°ã€æŠ“å®Œç«‹åˆ»å…³
-- ============================================================================
-- 1) æ‰“开(记录到 mysql.general_log è¡¨ï¼Œä¸å†™æ–‡ä»¶ï¼Œé¿å…ç£ç›˜æƒé™é—®é¢˜ï¼‰
--    âš ï¸ è¿™ä¸‰å¥è¯·**手工逐条执行**,不要整段跑:整段跑会"开了立刻关",什么都抓不到
-- SET GLOBAL log_output = 'TABLE';
-- SET GLOBAL general_log = 'ON';
-- TRUNCATE TABLE mysql.general_log;
-- 2) â€”— åœ¨è¿™é‡Œå¤çŽ°ï¼šè®©æŸä¸ªæ‚£è€…é‡æ–°å¡«ä¸€æ¬¡é—®å· / ç­‰ä¸€è½®å®šæ—¶ä»»åŠ¡ â€”—(开着等 5~10 åˆ†é’Ÿï¼‰
-- 3) æŠ“取所有“把明细置为删除”的 UPDATE åŽŸæ–‡
SELECT event_time, user_host, thread_id, argument
FROM mysql.general_log
WHERE command_type = 'Query'
  AND argument LIKE 'update service_subtask_detail%'
  AND argument LIKE '%del_flag%'
ORDER BY event_time DESC
LIMIT 200;
-- 4) é¡ºä¾¿çœ‹è¿™æ®µæ—¶é—´é‡Œå“ªäº›ä¼šè¯/接口在写明细(insert / update éƒ½åˆ—出来)
SELECT event_time, LEFT(argument, 160) AS sql_head, COUNT(*) AS times
FROM mysql.general_log
WHERE command_type = 'Query'
  AND argument LIKE '%service_subtask_detail%'
GROUP BY LEFT(argument, 160)
ORDER BY times DESC
LIMIT 30;
-- 5) å…³é—­ï¼ˆåŠ¡å¿…æ‰‹å·¥æ‰§è¡Œï¼‰
-- SET GLOBAL general_log = 'OFF';
-- ã€æ€Žä¹ˆè¯»ç»“果】看 WHERE çš„形状即可定位代码:
--   Â· where id = ?                                     -> /smartor/serviceSubtaskDetail/edit æˆ– /batchEdit
--   Â· where patid = ? and scriptid = ? and sub_id = ?  -> /smartor/serviceSubtaskDetail/editByCondition
--   Â· ä¸€æ¡ä¸€æ¡ by id çš„循环(同一秒内十几条,且 update_time æ²¡å˜ï¼‰ -> åŽå° job(setFailSubTaskDefalutVal é‚£ä¸€ç±»ï¼‰
--   Â· æ²¡æœ‰ update_time èµ‹å€¼                             -> ç›´æŽ¥è°ƒ mapper çš„写法(不是 Controller -> Service é“¾è·¯ï¼‰
-- ============================================================================
-- æ–¹æ¡ˆâ‘¡ï¼šå®¡è®¡è§¦å‘器(可长期挂着,只在 del_flag çœŸæ­£å˜åŒ–时记一行)
--   ä¸ä¾èµ– general_log å¼€å…³ï¼›è®°å½•毫秒时间 + æ˜¯å¦é¡ºå¸¦æ”¹äº† update_time,
--   å‰è€…用来和应用的 access log / ä¸šåŠ¡æ—¥å¿—å¯¹é½ï¼ŒåŽè€…ç”¨æ¥åˆ¤å®šèµ°çš„æ˜¯ä¸æ˜¯ Service é“¾è·¯ã€‚
-- ============================================================================
-- âš ï¸ ä»¥ä¸‹ DDL é»˜è®¤æ³¨é‡ŠæŽ‰ï¼›ç¡®è®¤è¦é•¿æœŸæŒ‚审计再取消注释后执行(Navicat / DBeaver å¯ç›´æŽ¥æ•´æ®µæ‰§è¡Œï¼‰
-- CREATE TABLE IF NOT EXISTS audit_detail_delflag (
--     audit_id    BIGINT AUTO_INCREMENT PRIMARY KEY,
--     detail_id   BIGINT      NULL COMMENT '被改的明细 id',
--     sub_id      BIGINT      NULL COMMENT '子任务 id',
--     scriptid    BIGINT      NULL COMMENT '题目 id',
--     old_flag    VARCHAR(10) NULL,
--     new_flag    VARCHAR(10) NULL,
--     created_at  DATETIME(3) NULL COMMENT '原行的 create_time',
--     updated_at  DATETIME(3) NULL COMMENT '原行的 update_time',
--     new_upd_at  DATETIME(3) NULL COMMENT '本次被改后的 update_time',
--     at          DATETIME(3) NOT NULL COMMENT '触发器触发时刻(毫秒)',
--     db_user     VARCHAR(64) NULL,
--     conn_id     BIGINT      NULL
-- ) ENGINE = InnoDB COMMENT = 'service_subtask_detail.del_flag å˜æ›´å®¡è®¡';
--
-- DROP TRIGGER IF EXISTS trg_ssd_delflag_au;
-- CREATE TRIGGER trg_ssd_delflag_au
-- AFTER UPDATE ON service_subtask_detail
-- FOR EACH ROW
-- BEGIN
--     IF COALESCE(OLD.del_flag, '') <> COALESCE(NEW.del_flag, '') THEN
--         INSERT INTO audit_detail_delflag
--             (detail_id, sub_id, scriptid, old_flag, new_flag, created_at, updated_at, new_upd_at, at, db_user, conn_id)
--         VALUES
--             (OLD.id, OLD.sub_id, OLD.scriptid, OLD.del_flag, NEW.del_flag,
--              OLD.create_time, OLD.update_time, NEW.update_time, NOW(3),
--              CURRENT_USER(), CONNECTION_ID());
--     END IF;
-- END;
-- äº‹åŽçœ‹ç»“论(建了 audit_detail_delflag ä¹‹åŽæ‰ç”¨ï¼‰
-- SELECT DATE(at) AS day, HOUR(at) AS hr, new_flag, COUNT(*) AS rows_cnt,
--        COUNT(DISTINCT sub_id) AS subs,
--        SUM(updated_at IS NULL OR updated_at = new_upd_at) AS upd_untouched,   -- >0 è¯´æ˜Žèµ°çš„不是 Service é“¾è·¯
--        SUM(updated_at IS NOT NULL AND updated_at <> new_upd_at) AS upd_changed
-- FROM audit_detail_delflag
-- GROUP BY day, hr, new_flag
-- ORDER BY day DESC, hr DESC;
-- æŸä¸ªå­ä»»åŠ¡çš„å®Œæ•´è½¨è¿¹ï¼ˆå’Œä¸šåŠ¡æ—¥å¿—çš„æ—¶é—´ç‚¹å¯¹é½ï¼›å»ºäº†å®¡è®¡è¡¨ä¹‹åŽæ‰ç”¨ï¼‰
-- SELECT * FROM audit_detail_delflag WHERE sub_id = 45094 ORDER BY at;
-- ============================================================================
-- äº‹åŽåˆ¤æ®ï¼ˆä¸æŠ“包也能用):拿历史数据判断某次删除走的是哪条链路
--   update_time ä¸Ž create_time ç›¸ç­‰ -> ä¸æ˜¯ Controller->Service é“¾è·¯ï¼ˆB/C ä¼šå¼ºåˆ¶åˆ·æ–° update_time)
--   update_time æ¯” create_time æ™š -> B æˆ– C
-- ============================================================================
SELECT sub_id,
       COUNT(*) AS rows_cnt,
       SUM(update_time = create_time) AS upd_untouched,   -- ç›´æŽ¥ mapper(job ä¸€ç±»ï¼‰
       SUM(update_time <> create_time) AS upd_changed,     -- Controller -> Service
       MIN(create_time) AS first_create, MAX(create_time) AS last_create
FROM service_subtask_detail
WHERE del_flag <> 0
GROUP BY sub_id
ORDER BY rows_cnt DESC
LIMIT 50;
-- ============================================================================
-- å½“前仍未修复的(全删无活行)—— æˆ‘复查时已有 5 ä¸ªè¢«æ‰‹å·¥æ¢å¤äº†ï¼Œå‰©ä¸‹çš„就是这些
-- ============================================================================
SELECT s.id AS subid, s.patid, s.sendname, s.finishtime,
       (SELECT COUNT(*) FROM service_subtask_detail d WHERE d.sub_id = s.id AND d.del_flag = 0) AS live_rows,
       (SELECT COUNT(*) FROM service_subtask_detail d WHERE d.sub_id = s.id) AS all_rows
FROM service_subtask s
WHERE s.service_type = 6 AND s.sendstate = 6
  AND EXISTS (SELECT 1 FROM service_subtask_detail d WHERE d.sub_id = s.id)
  AND NOT EXISTS (SELECT 1 FROM service_subtask_detail d WHERE d.sub_id = s.id AND d.del_flag = 0)
ORDER BY s.finishtime;
-- ============================================================================
-- ã€ä»£ç ä¾§ä¿®æ³•(定位到之后一起改)】
-- 1) æŠŠ delFlag ä»Žå®¢æˆ·ç«¯å¯æŽ§å­—段里摘掉:
--    ServiceSubtaskDetailMapper.xml#updateServiceSubtaskDetail / #updateSSDByCondition
--    åˆ æŽ‰ <if test="delFlag != null and delFlag != ''">del_flag = #{delFlag},</if> è¿™ä¸€è¡Œï¼›
--    é€»è¾‘删除只允许后台专用方法(明确的方法名 + æ“ä½œäºº + æ—¥å¿—)。
-- 2) åŽå°â€œè¡¥é»˜è®¤å€¼â€ä¸è¦â€œå…ˆæ’后删”:
--    setFailSubTaskDefalutVal é‡Œå…ˆæŠŠæ¯é¢˜çš„默认值算完、确认全都有默认值(flag),
--    å†å†³å®šæ˜¯â€œæ•´æ‰¹æ’入”还是“什么都不做”,避免删掉刚插进去的数据。
-- 3) æ˜Žç»†åŽ»é‡ï¼š
--    isExistServiceSubtaskDetail åªçœ‹ del_flag=0 â†’ æ¯æ¬¡é‡å»ºéƒ½ä¼šæ–°å¢žå‰¯æœ¬ã€‚
--    å»ºè®®ç»™ (sub_id, scriptid) åŠ å”¯ä¸€ç´¢å¼• + ç‰©ç†è¦†ç›–更新,副本就不会越积越多。
-- 4) æäº¤å¹‚等:
--    /smartor/subtaskAnswer/saveQuestionAnswer æ˜¯ @Async ä¸”无幂等(saveQuestionCache æ‰æœ‰ @RepeatSubmit),
--    æ‚£è€…端重复点击/重试会并发写多份,建议加 Redis å¹‚等键(subid+scriptid+答案摘要)。
-- ============================================================================
sql/ÅŲé_µ¼³öÎÊÌâÕ¼±ÈÈËÊýÓëÈÎÎñÊý²»Ò»ÖÂ_20261029.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,108 @@
-- ============================================================================
-- æŽ’查:随访导出的「问题详情占比」(Sheet2) äººæ•° æ¯”「患者随访信息表单」(Sheet1) å°‘
--       ä¾‹ï¼šSheet1 475 äººï¼ŒSheet2 çš„题目只有 283 / 172 äººä½œç­”
-- åªè¯»è„šæœ¬ã€‚æ”¹ä¸‹é¢ 3 ä¸ªå‚数即可复跑。
-- ----------------------------------------------------------------------------
-- ã€ç»“论(2026-10-29 å®žæµ‹ï¼Œnhfy,骨科中心一病区 40011020,出院 2026-07-01~09-30)】
--   475 æ¡ä»»åŠ¡ â‰  475 ä¸ª"同卷作答的人"。它的构成是:
--     Â· ä»»åŠ¡ 67「骨科中心一病区出院随访」→ æ¨¡æ¿ 115「骨科中心一病区随访问卷」
--         â†’ **10 é“满意度题**(医德评价/服务态度/医疗效果…),作答 283 äºº
--     Â· ä»»åŠ¡ 68「骨科中心一病区出院随访」→ æ¨¡æ¿ 116「股骨颈骨折术后随访问卷」
--         â†’ **8 é“骨科题**(伤口是否有红肿/是否注意饮水…),作答 129 äºº
--     Â· ä»»åŠ¡ 67 é‡Œè¿˜æœ‰ 44 æ¡ç”¨çš„æ˜¯ã€Œè‚¡éª¨é¢ˆéª¨æŠ˜æœ¯åŽéšè®¿é—®å·ã€â†’ 43 äºº
--         åŽŸå› ï¼ˆä¸šåŠ¡ç¡®è®¤ï¼‰ï¼šè¿™äº›æ‚£è€…æœ¬æ¬¡ä½é™¢æ˜¯**从其它科室/病区转过来**的,随访任务沿用的是
--         **转科前科室/病区**的那套问卷;任务 67 çš„配置本身没变 â€”—
--         ä½è¯ï¼šè¿™ 44 æ¡çš„生成时间散落在 2026-07-02 ~ 2026-10-01 å„天,不是某天统一切换;
--               ä¸”它们现在的记录科室/病区都已经是骨科中心一病房/一病区(转科后 HIS æ›´æ–°è¿‡ï¼‰ã€‚
--         â‡’ æ‰€ä»¥åŒä¸€ä»»åŠ¡åä¸‹ä¼šåŒæ—¶å‡ºçŽ°"满意度题"和"骨科题",且骨科题只有 43 äººä½œç­”。
--     Â· å¦æœ‰ 6 æ¡é›¶æ•£ä»»åŠ¡ï¼ˆ31/148/195/197/11),各 1~2 äºº
--   ä½œç­”覆盖:475 æ¡é‡Œ 458 æ¡æœ‰æ˜Žç»†ï¼ˆ17 æ¡æ²¡å¡«ï¼š11 æ¡"无需随访" + 6 æ¡å·²å®Œæˆä½†æ— æ˜Žç»†ï¼‰ã€‚
--
--   éª¨ç§‘题合计 172 äººï¼ˆ129 + 43);满意度题合计 283 äººã€‚
--   æ‰€ä»¥ Sheet2 æ¯é“题的人数只能是 283 æˆ– 172,永远凑不到 475 â€”—
--   è€Œä¸” Sheet2 çš„「占比」分母是**该题作答人数**,不是 475。
--
--   ä¸¤ä¸ªåŽŸå› ï¼ˆéƒ½ä¸æ˜¯"统计算错"):
--     â‘  åŒä¸€ç—…区配了两个同名长期任务(67 / 68),挂的是两套不同问卷 â€”— é…ç½®å±‚面;
--     â‘¡ è½¬ç§‘/转病区患者沿用**转科前科室/病区**的那套问卷 â€”— ä¸šåŠ¡ç¡®è®¤çš„å£å¾„ã€‚
--
-- ã€ç”¨æ³•】改参数后整段执行,按【1】→【5】顺序看。
-- ============================================================================
-- ----------------------------------------------------------------------------
-- ã€1】Sheet1 è¡Œæ•°ï¼šæœ¬æ¬¡å¯¼å‡ºèŒƒå›´ï¼ˆ= å¯¼å‡º Excel ç¬¬ 1 ä¸ª Sheet çš„行数)
-- ----------------------------------------------------------------------------
SELECT '1 è¡¨å•行数' AS step, COUNT(*) AS sheet1_rows
FROM (
  SELECT DISTINCT b.inhospid
  FROM pat_med_inhosp b
  JOIN pat_archive a ON a.id = b.patid
  WHERE a.del_flag = '0' AND b.del_flag = '0' AND a.idcardno IS NOT NULL
    AND b.inhospstate = '1'
    AND b.leavehospitaldistrictcode = '40011020'
    AND b.endtime >= '2026-07-01 00:00:00' AND b.endtime < '2026-10-01 00:00:00'
) r
JOIN service_subtask s ON s.inhospid = r.inhospid AND s.del_flag = 0 AND s.service_type = '2';
-- ----------------------------------------------------------------------------
-- ã€2】这批任务的分身:任务 + æ¨¡æ¿ + ä½œç­”人数
--     ä¸€çœ¼çœ‹å‡º"同一个任务名下混了几套问卷"
-- ----------------------------------------------------------------------------
SELECT '2 ä»»åŠ¡ä¸Žæ¨¡æ¿åˆ†å¸ƒ' AS step, s.taskid, MAX(s.task_name) AS ä»»åŠ¡å,
       s.templatename AS æ‚£è€…实际用的问卷, s.libtemplateid, COUNT(*) AS ä»»åŠ¡æ•°,
       SUM(CASE WHEN EXISTS (SELECT 1 FROM service_subtask_detail x
                              WHERE x.sub_id = s.id AND x.del_flag = 0) THEN 1 ELSE 0 END) AS æœ‰æ˜Žç»†
FROM (
  SELECT DISTINCT b.inhospid
  FROM pat_med_inhosp b JOIN pat_archive a ON a.id = b.patid
  WHERE a.del_flag = '0' AND b.del_flag = '0' AND a.idcardno IS NOT NULL
    AND b.inhospstate = '1'
    AND b.leavehospitaldistrictcode = '40011020'
    AND b.endtime >= '2026-07-01 00:00:00' AND b.endtime < '2026-10-01 00:00:00'
) r
JOIN service_subtask s ON s.inhospid = r.inhospid AND s.del_flag = 0 AND s.service_type = '2'
GROUP BY s.taskid, s.templatename, s.libtemplateid
ORDER BY ä»»åŠ¡æ•° DESC;
-- ----------------------------------------------------------------------------
-- ã€3】逐题作答人数(= Sheet2 é‡Œè¯¥é¢˜çš„「数量合计」,也是「占比」的分母)
--     ä¸Žå¯¼å‡º SQL å®Œå…¨åŒå£å¾„:同一人同一题只取 MAX(id) é‚£æ¡
-- ----------------------------------------------------------------------------
SELECT '3 é€é¢˜ä½œç­”人数' AS step, s.taskid, d.questiontext,
       COUNT(DISTINCT s.id) AS ä½œç­”人数,
       SUM(CASE WHEN TRIM(COALESCE(d.asrtext, '')) = '' THEN 1 ELSE 0 END) AS ç©ºç­”案行
FROM service_subtask s
JOIN service_subtask_detail d ON d.sub_id = s.id AND d.del_flag = 0
WHERE s.del_flag = 0 AND s.service_type = '2'
  AND s.taskid IN (67, 68)
  AND s.endtime >= '2026-07-01 00:00:00' AND s.endtime < '2026-10-01 00:00:00'
  AND d.id = (SELECT MAX(d2.id) FROM service_subtask_detail d2
              WHERE d2.sub_id = d.sub_id AND d2.questiontext <=> d.questiontext AND d2.del_flag = '0')
GROUP BY s.taskid, d.questiontext
ORDER BY s.taskid, ä½œç­”人数 DESC;
-- ----------------------------------------------------------------------------
-- ã€4】没作答的人是谁(Sheet1 æœ‰ã€Sheet2 é‡Œå®Œå…¨æ²¡æœ‰ï¼‰
-- ----------------------------------------------------------------------------
SELECT '4 æ²¡å¡«é—®å·çš„人' AS step, s.id AS å­ä»»åŠ¡ID, s.taskid, s.sendname AS å§“名,
       DATE_FORMAT(s.endtime, '%Y-%m-%d') AS å‡ºé™¢æ—¥æœŸ, s.sendstate,
       CASE WHEN s.sendstate = 4 THEN '无需随访(未发)' ELSE '已完成但没有明细' END AS åŽŸå› 
FROM (
  SELECT DISTINCT b.inhospid
  FROM pat_med_inhosp b JOIN pat_archive a ON a.id = b.patid
  WHERE a.del_flag = '0' AND b.del_flag = '0' AND a.idcardno IS NOT NULL
    AND b.inhospstate = '1'
    AND b.leavehospitaldistrictcode = '40011020'
    AND b.endtime >= '2026-07-01 00:00:00' AND b.endtime < '2026-10-01 00:00:00'
) r
JOIN service_subtask s ON s.inhospid = r.inhospid AND s.del_flag = 0 AND s.service_type = '2'
WHERE NOT EXISTS (SELECT 1 FROM service_subtask_detail d WHERE d.sub_id = s.id AND d.del_flag = 0)
ORDER BY s.sendstate, s.endtime DESC;
-- ----------------------------------------------------------------------------
-- ã€5】两个同名任务的配置对照(根因)
-- ----------------------------------------------------------------------------
SELECT '5 åŒåä»»åŠ¡çš„æ¨¡æ¿é…ç½®' AS step, t.taskid, t.task_name, t.templateid, t.templatename,
       t.libtemplateid, t.service_type, t.send_day, t.send_state
FROM service_task t
WHERE t.taskid IN (67, 68);
sql/ÅŲé_ÎÊÌâÕ¼±ÈÓëËæ·Ã±íµ¥ÈËÊý²»Ò»ÖÂ.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,168 @@
-- ============================================================================
-- æŽ’查:随访导出的「问题详情占比」人数 æ¯” ã€Œæ‚£è€…随访信息表单」行数少(例:370 vs 380)
--
-- ã€å£å¾„事实(代码坐实)】
--   * Sheet1「患者随访信息表单」= æœ¬æ¬¡å¯¼å‡ºçš„子任务行:ServiceSubtaskController#patItemExport
--     (ruoyi-task/.../controller/ServiceSubtaskController.java:237) -> patItem() -> selectServiceSubtaskList
--     => ä¸€è¡Œ = ä¸€ä¸ª service_subtask(与 sendstateView / visit_count / ç§‘室病区等筛选完全一致)
--   * Sheet2「问题详情占比」= SatisfactionFacade#getQuestionOptionRatioExport(subtaskIds)
--     -> DefaultSatisfactionApi -> SatisfactionSubtaskServiceImpl#statMydQuestionOptionForExport
--     -> SatisfactionMapper.xml#statMydQuestionOptionForExport(第 206 è¡Œèµ·ï¼‰
--     è¯¥ SQL æ˜¯ INNER JOIN service_subtask_detail,并且
--         AND s.del_flag='0' AND d.del_flag=0
--         AND d.id = (SELECT MAX(d2.id) ... d2.questiontext <=> d.questiontext ...)   -- åŒä¸€äººåŒä¸€é¢˜åªå–最新一条
--     å†ç”± ServiceSubtaskServiceImpl#buildMydOptionRows æŒ‰ã€Œä»»åŠ¡+题目」汇总:
--         è¯¥é¢˜â€œæ•°é‡åˆè®¡â€ = é€‰ä¸­å„选项的人数之和 = è¯¥é¢˜**有作答**的患者数(比例的分母也用这个)
--         å…¶ä¸­ asrtext ä¸ºç©ºçš„æ‚£è€…:
--             - è¯¥é¢˜åªé…äº† 1 ä¸ªé€‰é¡¹æ—¶ï¼Œç®—进这个选项;
--             - è¯¥é¢˜é…äº† â‰¥2 ä¸ªé€‰é¡¹æ—¶ï¼Œjava é‡Œç›´æŽ¥ continue ä¸¢æŽ‰ï¼ˆæ—¢ä¸ç®—数量,也不进分母)← è§ buildMydOptionRows ç¬¬ 181~185 è¡Œ
--     => åªè¦æŸæ‚£è€…在该题上“没有明细行”或“明细行答案为空且该题多选项”,他在 Sheet2 é‡Œå°±å½»åº•消失,
--        æ‰€ä»¥ Sheet2 çš„人数天然 â‰¤ Sheet1 çš„行数。差额就是这类患者。
--
-- ã€æœ¬è„šæœ¬ç”¨é€”ã€‘æŠŠå·®é¢æ‚£è€…ä¸€ä¸ªä¸ªåˆ—å‡ºæ¥ï¼Œåˆ¤å®šå±žäºŽå“ªç§ç¼ºå¤±ï¼Œåªè¯»ã€ä¸æ”¹ä»»ä½•æ•°æ®ã€‚
-- ã€ç”¨æ³•】
--   1) æŠŠä¸‹é¢ @变量 æ”¹æˆå‡ºé—®é¢˜é‚£æ¬¡å¯¼å‡ºçš„筛选条件(截图里 payload çš„那些值);
--   2) è‹¥æ‰‹ä¸Šå·²æœ‰ 380 ä¸ªå­ä»»åŠ¡ id,可直接跳到【0-B】用 id åˆ—表替代筛选;
--   3) ä»Žã€1】往下顺序执行,看注释里的“预期”。
-- ============================================================================
-- ----------------------------------------------------------------------------
-- ã€0-A】筛选条件(照抄导出请求 payload)
-- ----------------------------------------------------------------------------
SET @orgid           := '20001001';   -- ç™»å½•用户 orgid(后端强制注入,非页面参数)
SET @campusid        := '30001002';   -- ç™»å½•用户 campusid(同上)
SET @serviceType     := 6;            -- payload: serviceType
SET @sendstateView   := 2;            -- payload: sendstateView(1 å¾…随访[1,2,3,5,7] / 2 å·²å®Œæˆ[6] / 3 æ— éœ€éšè®¿[4])
SET @visitCount      := 1;            -- payload: visitcount(1=仅首次随访,>1=多次,NULL=不限)
SET @deptcodes       := '40003511';   -- payload: leavelptcodes[0],多个用逗号;无则置 NULL
SET @districtcodes   := '40003514';   -- payload: leavehospitaldistrictcodes[0],多个用逗号;无则置 NULL
SET @deptOrDistrict  := 2;            -- payload: deptOrDistrict(科室 OR ç—…区)
DROP TEMPORARY TABLE IF EXISTS tmp_export_subtask;
CREATE TEMPORARY TABLE tmp_export_subtask (id BIGINT NOT NULL PRIMARY KEY);
-- ã€0-B】二选一:A ç”¨ç­›é€‰æ¡ä»¶è¿˜åŽŸï¼ˆé»˜è®¤ï¼‰ï¼ŒB ç›´æŽ¥ç”¨å¯¼å‡ºé‡Œé‚£ 380 ä¸ªå­ä»»åŠ¡ id
INSERT INTO tmp_export_subtask (id)
SELECT s.id
FROM service_subtask s
WHERE s.del_flag = '0'
  AND s.orgid = @orgid
  AND s.campusid = @campusid
  AND s.service_type = @serviceType
  AND (   (@sendstateView = 2 AND s.sendstate = 6)
       OR (@sendstateView = 3 AND s.sendstate = 4)
       OR (@sendstateView = 1 AND s.sendstate IN (1, 2, 3, 5, 7)))
  AND (@visitCount IS NULL
       OR (@visitCount = 1 AND s.visit_count = 1)
       OR (@visitCount > 1 AND s.visit_count > 1))
  AND (@deptOrDistrict <> 2
       OR FIND_IN_SET(IFNULL(s.leavehospitaldistrictcode, ''), IFNULL(@districtcodes, '')) > 0
       OR FIND_IN_SET(IFNULL(s.deptcode, ''), IFNULL(@deptcodes, '')) > 0);
-- B) å·²çŸ¥ id åˆ—表时改跑这一句(把注释去掉、上面的 INSERT æ³¨é‡ŠæŽ‰ï¼‰ï¼Œé€—号分隔:
-- INSERT INTO tmp_export_subtask (id)
-- SELECT id FROM service_subtask WHERE id IN ( /* 380 ä¸ª id */ );
-- ----------------------------------------------------------------------------
-- ã€1】Sheet1 è¡Œæ•°ï¼šåº”该等于导出 Excel é‡Œã€Œæ‚£è€…随访信息表单」的行数(例 380)
-- ----------------------------------------------------------------------------
SELECT '1. è¡¨å•行数(Sheet1)' AS step, COUNT(*) AS cnt FROM tmp_export_subtask;
-- è®°ä¸‹æ€»æ•°ï¼ˆMySQL ä¸´æ—¶è¡¨åœ¨åŒä¸€è¯­å¥é‡Œä¸èƒ½è¢«æ‰“开两次,后面用变量代替子查询)
SET @total := (SELECT COUNT(*) FROM tmp_export_subtask);
-- ----------------------------------------------------------------------------
-- ã€2】Sheet2 å®žé™…能统计到的人数
--     2a = è‡³å°‘有 1 æ¡æœ‰æ•ˆæ˜Žç»†ï¼ˆdel_flag=0)的患者数
--     2b = æ˜Žç»†å­˜åœ¨ä½†å…¨éƒ¨è¢«é€»è¾‘删除的患者数(这些人在 Sheet2 é‡ŒåŒæ ·çœ‹ä¸åˆ°ï¼‰
--     é¢„期:2a <= 1 çš„ cnt,差额 = å®Œå…¨æ²¡å¡«æŠ¥/明细丢失的人
-- ----------------------------------------------------------------------------
SELECT '2a. æœ‰æœ‰æ•ˆæ˜Žç»†çš„æ‚£è€…æ•°' AS step, COUNT(DISTINCT d.sub_id) AS cnt
FROM tmp_export_subtask t
JOIN service_subtask_detail d ON d.sub_id = t.id AND d.del_flag = 0;
SELECT '2b. æ˜Žç»†å…¨è¢«é€»è¾‘删除的患者数' AS step, COUNT(*) AS cnt
FROM tmp_export_subtask t
WHERE EXISTS (SELECT 1 FROM service_subtask_detail d WHERE d.sub_id = t.id)
  AND NOT EXISTS (SELECT 1 FROM service_subtask_detail d WHERE d.sub_id = t.id AND d.del_flag = 0);
-- ----------------------------------------------------------------------------
-- ã€3】差额清单:在 Sheet1 é‡Œã€ä½†åœ¨ Sheet2 é‡Œå®Œå…¨çœ‹ä¸åˆ°çš„æ‚£è€…
--     reason è¯´æ˜Žï¼š
--       no_detail_row  â€”— service_subtask_detail é‡Œä¸€æ¡è¯¥å­ä»»åŠ¡çš„æ˜Žç»†éƒ½æ²¡æœ‰ï¼ˆæ²¡å¡«æŠ¥/中途退出/明细未落库)
--       all_deleted    â€”— æœ‰æ˜Žç»†ä½†å…¨è¢«é€»è¾‘删除
-- ----------------------------------------------------------------------------
SELECT '3. å·®é¢æ‚£è€…清单(原因1:无明细)' AS step;
SELECT t.id AS subid, s.taskid, s.patid, s.sendname, s.sendstate, s.visit_count,
       s.visit_time, s.finishtime, s.deptcode, s.leavehospitaldistrictcode,
       'no_detail_row' AS reason
FROM tmp_export_subtask t
JOIN service_subtask s ON s.id = t.id
WHERE NOT EXISTS (SELECT 1 FROM service_subtask_detail d WHERE d.sub_id = t.id)
ORDER BY t.id;
SELECT '3. å·®é¢æ‚£è€…清单(原因2:明细全被逻辑删除)' AS step;
SELECT t.id AS subid, s.taskid, s.patid, s.sendname, s.sendstate,
       (SELECT COUNT(*) FROM service_subtask_detail d WHERE d.sub_id = t.id) AS detail_rows,
       'all_deleted' AS reason
FROM tmp_export_subtask t
JOIN service_subtask s ON s.id = t.id
WHERE EXISTS (SELECT 1 FROM service_subtask_detail d WHERE d.sub_id = t.id)
  AND NOT EXISTS (SELECT 1 FROM service_subtask_detail d WHERE d.sub_id = t.id AND d.del_flag = 0)
ORDER BY t.id;
-- ----------------------------------------------------------------------------
-- ã€4】逐题人数:任何一个题的「有作答患者数」都 <= è¡¨å•行数,
--     å¯¼å‡ºé‡Œçœ‹åˆ°çš„ 370 å°±æ˜¯è¿™é‡ŒæŸä¸€è¡Œï¼ˆä¹Ÿæ˜¯è¯¥é¢˜æ¯”例的分母)
--       patients_with_row  : æœ‰è¯¥é¢˜æ˜Žç»†çš„人数
--       answered           : ç­”案非空的人数  â‰ˆ Sheet2 çš„“数量合计”与分母
--       empty_answer       : æœ‰æ˜Žç»†ä½†ç­”案为空的人数  â‰ˆ è¢« buildMydOptionRows ä¸¢æŽ‰çš„那部分(多选项题)
--       no_row             : è¯¥é¢˜è¿žæ˜Žç»†éƒ½æ²¡æœ‰çš„人数
--     ï¼ˆè¿™é‡Œæ²¡å¥— MAX(id) åŽ»é‡ï¼ŒåŽ»é‡ç‰ˆè§ã€4b】;两者差异=同一人同一题有多条明细)
-- ----------------------------------------------------------------------------
SELECT '4. é€é¢˜äººæ•°' AS step;
SELECT d.questiontext,
       COUNT(DISTINCT t.id) AS patients_with_row,
       COUNT(DISTINCT CASE WHEN TRIM(COALESCE(d.asrtext, '')) <> '' THEN t.id END) AS answered,
       COUNT(DISTINCT CASE WHEN TRIM(COALESCE(d.asrtext, '')) =  '' THEN t.id END) AS empty_answer,
       @total - COUNT(DISTINCT t.id) AS no_row
FROM tmp_export_subtask t
JOIN service_subtask_detail d ON d.sub_id = t.id AND d.del_flag = 0
GROUP BY d.questiontext
ORDER BY patients_with_row DESC, d.questiontext;
-- ã€4b】与 SQL å®Œå…¨åŒå£å¾„(同一人同一题只留 MAX(id) é‚£æ¡ï¼‰çš„逐题人数
SELECT '4b. é€é¢˜äººæ•°(去重口径,与导出一致)' AS step;
SELECT d.questiontext,
       COUNT(DISTINCT t.id) AS patients_counted_in_export,
       COUNT(DISTINCT CASE WHEN TRIM(COALESCE(d.asrtext, '')) = '' THEN t.id END) AS empty_answer_will_be_dropped
FROM tmp_export_subtask t
JOIN service_subtask_detail d
  ON d.sub_id = t.id
 AND d.del_flag = 0
 AND d.id = (SELECT MAX(d2.id) FROM service_subtask_detail d2
             WHERE d2.sub_id = d.sub_id
               AND d2.questiontext <=> d.questiontext
               AND d2.del_flag = '0')
GROUP BY d.questiontext
ORDER BY patients_counted_in_export DESC, d.questiontext;
-- ----------------------------------------------------------------------------
-- ã€5】同一患者同一题多条明细(重答/重复提交):导出只取最新一条
-- ----------------------------------------------------------------------------
SELECT '5. åŒä¸€äººåŒä¸€é¢˜å¤šæ¡æ˜Žç»†çš„æ‚£è€…æ•°' AS step, COUNT(*) AS cnt
FROM (SELECT d.sub_id, d.questiontext
      FROM tmp_export_subtask t
      JOIN service_subtask_detail d ON d.sub_id = t.id AND d.del_flag = 0
      GROUP BY d.sub_id, d.questiontext
      HAVING COUNT(*) > 1) x;
-- ----------------------------------------------------------------------------
-- ã€6】结论怎么读
--   * è‹¥ã€1】= 380 ä¸”【2a】= 370:差额 10 äºº = ã€3】里列出来的患者,
--     ä»–们要么没填报(no_detail_row),要么明细被删(all_deleted)
--     â€”— è¿™æ˜¯ã€Œå æ¯”只统计作答患者」的口径造成的,不是 SQL æ¼äº†ç­›é€‰æ¡ä»¶ã€‚
--   * è‹¥ã€1】= 380 ä½†ã€4】的 answered æœ‰å¤šè¡Œéƒ½å°äºŽ 380(比如 378/375/370…):
--     è¯´æ˜Žæ¯ä¸ªé¢˜ç›®çš„分母本来就不同(各有各的未作答),Sheet2 é‡Œæ²¡æœ‰â€œæœªä½œç­””这一行,
--     æ‰€ä»¥åˆè®¡æ°¸è¿œå‡‘不到 380。
-- ----------------------------------------------------------------------------
sql/Êý¾Ý²É¼¯¿ª¹Ø_sys_config.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
-- ----------------------------
-- æ•°æ®é‡‡é›†æ¨¡å—开关(sys_config å‚数配置表)
-- ----------------------------
-- é”®åï¼šdatacollection.enabled
-- é”®å€¼ï¼štrue  = å¯ç”¨ï¼ˆé»˜è®¤ï¼Œç­‰ä»·äºŽä¸é…ç½®è¿™ä¸€è¡Œï¼‰
--       false = åœé‡‡
--
-- å…³é—­åŽçš„行为:
--   1) å®šæ—¶ä»»åŠ¡å…¥å£ ryTask.dealHisData('...') ä»å¯è¢« quartz è°ƒåˆ°ï¼ˆå…¥å£ Bean ä¸ä¼šæ¶ˆå¤±ï¼‰ï¼Œ
--      ä½†é—¨é¢ DataCollectionFacade#dealHisData ä¼šæŠ›
--      DataCollectionModuleDisabledException;RyTask æ•获后只打一条 WARN
--      ã€Œæ•°æ®é‡‡é›†æ¨¡å—未启用……(本轮定时采集跳过)」,
--      å³ï¼š**每一轮都明确跳过,并在日志里留下痕迹**,不是静默什么都不做;
--   2) é—¨é¢ DataCollectionFacade#isEnabled() ä»å¯è°ƒç”¨ï¼Œä¾›è°ƒç”¨æ–¹é¢„判开关状态;
--   3) å…¶å®ƒå…¥å£ï¼ˆHTTP æ‰‹åŠ¨è§¦å‘é‡‡é›†çš„æŽ§åˆ¶å™¨ï¼‰ä¸å—æœ¬å¼€å…³å½±å“ â€”— å®ƒä»¬èµ°çš„æ˜¯
--      smartor / ruoyi-quartz é‡Œå„自的 Service,本开关只管"定时采集编排"这一条链路;
--   4) æ”¹å®Œç«‹å³ç”Ÿæ•ˆï¼Œæœ€å¤š 10 ç§’(进程内开关缓存 TTL),不需要重启。
--
-- âš ï¸ æ³¨æ„ï¼šåœé‡‡ä¸ä¼šåˆ é™¤ä»»ä½•数据,也不会回填"停采期间漏掉的时间段" â€”—
--          é‡æ–°æ‰“开后按当前时间窗口继续采(nhfy/sltd ç«™ç‚¹å¯ç”¨å¸¦æ—¥æœŸå…¥å‚的手工触发做回溯)。
-- âš ï¸ å‚数行缺失时按 true(fail-open):绝不因为库里少一行配置就静静停采。
--
-- æœ¬é¡¹ç›®ç”¨çš„ MySQL ç‰ˆæœ¬å¯èƒ½ä¸æ”¯æŒ ADD COLUMN IF NOT EXISTS,
-- å› æ­¤è¿™é‡Œç”¨ information_schema åˆ¤æ–­åŽå†åŠ¨æ€æ‰§è¡Œï¼Œè„šæœ¬å¯é‡å¤æ‰§è¡Œã€‚
-- ----------------------------
SET @datacollection_config_exists = (
    SELECT COUNT(1) FROM information_schema.COLUMNS
    WHERE TABLE_SCHEMA = DATABASE()
      AND TABLE_NAME = 'sys_config'
      AND COLUMN_NAME = 'config_key'
);
SET @ddl = IF(@datacollection_config_exists = 0,
    'SELECT ''sys_config è¡¨ä¸å­˜åœ¨ï¼Œè¯·å…ˆæ‰§è¡Œå»ºè¡¨è„šæœ¬'' AS warning',
    'INSERT INTO sys_config (config_name, config_key, config_value, config_type, create_by, create_time, remark)
     SELECT ''数据采集模块开关'', ''datacollection.enabled'', ''true'', ''Y'', ''admin'', SYSDATE(),
            ''数据采集模块(HIS é‡‡é›†å®šæ—¶ç¼–排)总开关(运行时读取,改完最多10秒生效):true启用,false停采;默认true''
     FROM DUAL
     WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE config_key = ''datacollection.enabled'')');
PREPARE datacollection_stmt FROM @ddl;
EXECUTE datacollection_stmt;
DEALLOCATE PREPARE datacollection_stmt;
-- æŸ¥è¯¢å½“前值
-- SELECT config_id, config_name, config_key, config_value, remark FROM sys_config WHERE config_key = 'datacollection.enabled';
sql/Ä£°å¿ª¹Ø_sys_config.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,65 @@
-- ----------------------------
-- æ¨¡æ¿æ¨¡å—开关(sys_config å‚数配置表)
-- ----------------------------
-- é”®åï¼štemplate.enabled
-- é”®å€¼ï¼štrue  = å¯ç”¨ï¼ˆé»˜è®¤ï¼Œç­‰ä»·äºŽä¸é…ç½®è¿™ä¸€è¡Œï¼‰
--       false = å…³é—­
--
-- è¯»å–方式:TemplateSwitch#isEnabled()
--   sys_config çƒ­è¯» + 10 ç§’进程内缓存,fail-open(读不到 / è¯»åº“异常都按 true å¤„理)。
--
-- å…³é—­åŽçš„行为:
--   1) æœ¬æ¨¡å— 66 ä¸ªæ¨¡æ¿ç®¡ç†ç«¯ç‚¹ä¼šè¢« TemplateAvailabilityInterceptor æ‹¦ä¸‹ï¼Œ
--      è¿”回明确业务错误「模板功能未启用(参数配置 template.enabled=false),该接口不可用」,
--      è€Œä¸æ˜¯ 404(前端能区分"模块被关闭"和"地址写错了");
--   2) è·¨æ¨¡å—调用也会被拒:其它模块经 TemplateFacade å–模板时,
--      6 ä¸ªæ•°æ®æ–¹æ³•统一抛 TemplateModuleDisabledException(提示语与上面一致)。
--      è°ƒç”¨æ–¹å„自决定降级方式,已在 4 ä¸ªè°ƒç”¨ç‚¹é€ä¸ªæ˜Žç¡®ï¼š
--        RyTask / RedisMqReceiver  -> å¦‚实记「发送失败」并升级下一通道(不掩盖)
--        ServiceTaskServiceImpl     -> è¿”回空列表,患者宣教页面照常可开可提交
--        ChatWebSocketHandler       -> æŠ›å¼‚常(语音问卷本就模板驱动,继续走只会 NPE)
--      âš ï¸ ä¹Ÿå°±æ˜¯è¯´ï¼š**关闭模板会连带停掉宣教短信的内容生成**,这是刻意的 â€”—
--      å–不到模板内容就是没内容可发,不该记成"发送成功"。
--   3) âš ï¸ å—管的是"管模板数据"的端点,共 66 ä¸ªå‰ç¼€ï¼ŒåŒ…含刻意留在 smartor çš„
--      3 ä¸ªæ¨¡æ¿ç®¡ç†æŽ§åˆ¶å™¨ï¼ˆSvyLibTemplateController / SvyTaskController /
--      SvyTaskTemplateController)。完整清单见 TemplateWebConfig#guardedPathPatterns()
--      æˆ– sql/_probe/verify_template_switch.py çš„输出。
--   4) âš ï¸ **不受管**:IvrTaskVisitResultController / IvrTasksmsController /
--      IvrTaskcallrecordController â€”— å®ƒä»¬æ˜¯"任务执行记录"(运行时实例),
--      å·²æŒ‰èŒè´£ç§»å‡ºæ¨¡æ¿æ¨¡å—;关掉"模板"不该让"任务执行记录查询"停摆。
--      æœåŠ¡å­ä»»åŠ¡ / æ¡£æ¡ˆ / æœåŠ¡ä»»åŠ¡ / é—®å·ä»»åŠ¡å®žä¾‹ / æ–¹æ¡ˆæµç¨‹ç­‰éžæ¨¡æ¿ç«¯ç‚¹åŒæ ·ä¸å—管。
--   5) æ”¹å®Œç«‹å³ç”Ÿæ•ˆï¼Œæœ€å¤š 10 ç§’,不需要重启。
--
-- æ³¨æ„ 1:本项目用的 MySQL ç‰ˆæœ¬å¯èƒ½ä¸æ”¯æŒ ADD COLUMN IF NOT EXISTS,
--         å› æ­¤è¿™é‡Œç”¨ information_schema åˆ¤æ–­åŽå†åŠ¨æ€æ‰§è¡Œï¼Œè„šæœ¬å¯é‡å¤æ‰§è¡Œã€‚
-- æ³¨æ„ 2:SysConfigServiceImpl#selectConfigByKey é‡Œ**写 Redis çš„那行是注释掉的**
--         ï¼ˆåªè¯» Redis ç¼“存、读不到就查库)。所以若历史上有人手动往 Redis å†™è¿‡è¿™ä¸ªé”®ï¼Œ
--         æ”¹åº“不一定立刻看得见。稳妥做法:走「系统管理 â†’ å‚数设置」页面改(会清缓存),
--         æˆ–执行 UPDATE åŽåˆ æŽ‰ Redis é‡Œçš„ sys_config:template.enabled。
-- ----------------------------
SET @sys_config_exists = (
    SELECT COUNT(1) FROM information_schema.COLUMNS
    WHERE TABLE_SCHEMA = DATABASE()
      AND TABLE_NAME = 'sys_config'
      AND COLUMN_NAME = 'config_key'
);
SET @ddl = IF(@sys_config_exists = 0,
    'SELECT ''sys_config è¡¨ä¸å­˜åœ¨ï¼Œè¯·å…ˆæ‰§è¡Œå»ºè¡¨è„šæœ¬'' AS warning',
    'INSERT INTO sys_config (config_name, config_key, config_value, config_type, create_by, create_time, remark)
     SELECT ''模板模块开关'', ''template.enabled'', ''true'', ''Y'', ''admin'', SYSDATE(),
            ''模板模块总开关(运行时读取,改完最多10秒生效):true启用,false关闭;默认true。关闭后66个模板管理端点与TemplateFacade跨模块调用一并被拒''
     FROM DUAL
     WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE config_key = ''template.enabled'')');
PREPARE template_stmt FROM @ddl;
EXECUTE template_stmt;
DEALLOCATE PREPARE template_stmt;
-- æ—¥å¸¸å¼€å…³æ“ä½œï¼ˆäºŒé€‰ä¸€ï¼Œæ”¹å®Œæœ€å¤š 10 ç§’生效):
--   å…³é—­ï¼šUPDATE sys_config SET config_value = 'false', update_time = SYSDATE() WHERE config_key = 'template.enabled';
--   å¼€å¯ï¼šUPDATE sys_config SET config_value = 'true',  update_time = SYSDATE() WHERE config_key = 'template.enabled';
-- æŸ¥è¯¢å½“前值
-- SELECT config_id, config_name, config_key, config_value, remark FROM sys_config WHERE config_key = 'template.enabled';
sql/ÂúÒâ¶È¿ª¹Ø_sys_config.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
-- ----------------------------
-- æ»¡æ„åº¦æ¨¡å—开关(sys_config å‚数配置表)
-- ----------------------------
-- é”®åï¼šsatisfaction.enabled
-- é”®å€¼ï¼štrue  = å¯ç”¨ï¼ˆé»˜è®¤ï¼Œç­‰ä»·äºŽä¸é…ç½®è¿™ä¸€è¡Œï¼‰
--       false = å…³é—­
--
-- å…³é—­åŽçš„行为:
--   1) æœ¬æ¨¡å— 17 ä¸ªæ»¡æ„åº¦ç«¯ç‚¹ä¼šè¢« SatisfactionAvailabilityInterceptor æ‹¦ä¸‹ï¼Œ
--      è¿”回明确业务错误「满意度功能未启用(参数配置 satisfaction.enabled=false),该接口不可用」,
--      è€Œä¸æ˜¯ 404;
--   2) éšè®¿å¯¼å‡º / éšè®¿ç»Ÿè®¡ / ç™»å½•链路通过 SatisfactionFacade å–数时降级为空值/全零,不受影响;
--   3) æ”¹å®Œç«‹å³ç”Ÿæ•ˆï¼Œæœ€å¤š 10 ç§’(进程内开关缓存 TTL),不需要重启。
--
-- æ³¨æ„ï¼šæœ¬é¡¹ç›®ç”¨çš„ MySQL ç‰ˆæœ¬å¯èƒ½ä¸æ”¯æŒ ADD COLUMN IF NOT EXISTS,
--       å› æ­¤è¿™é‡Œç”¨ information_schema åˆ¤æ–­åŽå†åŠ¨æ€æ‰§è¡Œï¼Œè„šæœ¬å¯é‡å¤æ‰§è¡Œã€‚
-- ----------------------------
SET @satisfaction_config_exists = (
    SELECT COUNT(1) FROM information_schema.COLUMNS
    WHERE TABLE_SCHEMA = DATABASE()
      AND TABLE_NAME = 'sys_config'
      AND COLUMN_NAME = 'config_key'
);
SET @ddl = IF(@satisfaction_config_exists = 0,
    'SELECT ''sys_config è¡¨ä¸å­˜åœ¨ï¼Œè¯·å…ˆæ‰§è¡Œå»ºè¡¨è„šæœ¬'' AS warning',
    'INSERT INTO sys_config (config_name, config_key, config_value, config_type, create_by, create_time, remark)
     SELECT ''满意度模块开关'', ''satisfaction.enabled'', ''true'', ''Y'', ''admin'', SYSDATE(),
            ''满意度模块总开关(运行时读取,改完最多10秒生效):true启用,false关闭;默认true''
     FROM DUAL
     WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE config_key = ''satisfaction.enabled'')');
PREPARE satisfaction_stmt FROM @ddl;
EXECUTE satisfaction_stmt;
DEALLOCATE PREPARE satisfaction_stmt;
-- æŸ¥è¯¢å½“前值
-- SELECT config_id, config_name, config_key, config_value, remark FROM sys_config WHERE config_key = 'satisfaction.enabled';
sql/µç»°¿ª¹Ø_sys_config.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
-- ----------------------------
-- ç”µè¯æ¨¡å—开关(sys_config å‚数配置表)
-- ----------------------------
-- é”®åï¼šphone.enabled
-- é”®å€¼ï¼štrue  = å¯ç”¨ï¼ˆé»˜è®¤ï¼Œç­‰ä»·äºŽä¸é…ç½®è¿™ä¸€è¡Œï¼‰
--       false = å…³é—­
--
-- å…³é—­åŽçš„行为:
--   1) æœ¬æ¨¡å— 8 ä¸ªç”µè¯ç«¯ç‚¹ä¼šè¢« PhoneAvailabilityInterceptor æ‹¦ä¸‹ï¼Œ
--      è¿”回明确业务错误「电话功能未启用(参数配置 phone.enabled=false),该接口不可用」,
--      è€Œä¸æ˜¯ 404:
--        /smartor/serviceSubtask/phoneCallBack            ä¸Šæµ·ï¼šè¯­éŸ³è¯†åˆ«ç»“果回调
--        /smartor/serviceSubtask/phoneCallBackYQ          é›¨ç»®ï¼šASR/静默/播放事件回调
--        /smartor/serviceSubtask/taskPull                 é›¨ç»®ï¼šä»»åŠ¡æ‹‰å–
--        /smartor/serviceSubtask/saveQuestionAnswerPhone   ç”µè¯ï¼šæ‚£è€…问题结果记录
--        /smartor/serviceSubtask/recordAcceptshanghai      ä¸Šæµ·ï¼šé€šè¯è®°å½•回调
--        /smartor/serviceSubtask/recordAccept              é›¨ç»®ï¼šé€šè¯è®°å½•回调
--        /smartor/shphone/getFirstQues                    ä¸‰æ±‡ï¼šå–首题
--        /smartor/shphone/processResust                   ä¸‰æ±‡ï¼šASR ç»“果处理
--   2) è¿™ 6 æ¡æŒ‚在 /smartor/serviceSubtask/** ä¸‹çš„电话端点,已从「任务模块开关」
--      ï¼ˆtask.enabled)的拦截范围里排除(见 TaskWebConfig#excludedPhonePaths),
--      å³å…³ä»»åŠ¡ä¸ä¼šè¿žå¸¦å…³æŽ‰ç”µè¯ï¼›åä¹‹äº¦ç„¶ï¼›
--   3) æ¨¡å—门面 PhoneFacade(其它模块调电话能力的入口)仍可注入,
--      ä½†å„方法降级为空值/空列表并打 WARN,不抛异常;
--   4) ç”µè¯ä¼šè¯çŠ¶æ€ï¼ˆRedis é‡Œçš„ uuid / å­ä»»åŠ¡-手机号 é”®ï¼‰ä¸å—开关影响,
--      é‡æ–°æ‰“开后仍在有效期内(最长 120 åˆ†é’Ÿï¼‰ï¼›
--   5) æ”¹å®Œç«‹å³ç”Ÿæ•ˆï¼Œæœ€å¤š 10 ç§’(进程内开关缓存 TTL),不需要重启。
--
-- âš ï¸ æ³¨æ„ï¼šå…³é—­ç”µè¯åŠŸèƒ½åªå½±å“"回调端点与门面调用",不会删除任何数据,
--          ä¹Ÿä¸ä¼šåœæ­¢å¤–部平台已经发起的外呼(它们的回调会被明确拒绝)。
--
-- æœ¬é¡¹ç›®ç”¨çš„ MySQL ç‰ˆæœ¬å¯èƒ½ä¸æ”¯æŒ ADD COLUMN IF NOT EXISTS,
-- å› æ­¤è¿™é‡Œç”¨ information_schema åˆ¤æ–­åŽå†åŠ¨æ€æ‰§è¡Œï¼Œè„šæœ¬å¯é‡å¤æ‰§è¡Œã€‚
-- ----------------------------
SET @phone_config_exists = (
    SELECT COUNT(1) FROM information_schema.COLUMNS
    WHERE TABLE_SCHEMA = DATABASE()
      AND TABLE_NAME = 'sys_config'
      AND COLUMN_NAME = 'config_key'
);
SET @ddl = IF(@phone_config_exists = 0,
    'SELECT ''sys_config è¡¨ä¸å­˜åœ¨ï¼Œè¯·å…ˆæ‰§è¡Œå»ºè¡¨è„šæœ¬'' AS warning',
    'INSERT INTO sys_config (config_name, config_key, config_value, config_type, create_by, create_time, remark)
     SELECT ''电话模块开关'', ''phone.enabled'', ''true'', ''Y'', ''admin'', SYSDATE(),
            ''电话模块(上海/雨绮/三汇外呼回调)总开关(运行时读取,改完最多10秒生效):true启用,false关闭;默认true''
     FROM DUAL
     WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE config_key = ''phone.enabled'')');
PREPARE phone_stmt FROM @ddl;
EXECUTE phone_stmt;
DEALLOCATE PREPARE phone_stmt;
-- æŸ¥è¯¢å½“前值
-- SELECT config_id, config_name, config_key, config_value, remark FROM sys_config WHERE config_key = 'phone.enabled';
sql/Ãż±ÕïÓëÊÖÊõËæ·Ã¿ª¹Ø_sys_config.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,91 @@
-- ----------------------------
-- é—¨æ€¥è¯Šéšè®¿æ¨¡å— + æ‰‹æœ¯éšè®¿æ¨¡å— å¼€å…³ï¼ˆsys_config å‚数配置表)
-- ----------------------------
-- æœ¬è„šæœ¬ä¸€æ¬¡å»ºä¸¤ä¸ªé”®ï¼Œéƒ½å¯é‡å¤æ‰§è¡Œï¼ˆå¹‚ç­‰ï¼‰ï¼š
--
--   é”®åï¼šoutpatientvisit.enabled     é—¨æ€¥è¯Šéšè®¿æ€»å¼€å…³
--   é”®åï¼šothervisit.enabled          æ‰‹æœ¯éšè®¿æ€»å¼€å…³
--   é”®å€¼ï¼štrue  = å¯ç”¨ï¼ˆé»˜è®¤ï¼Œç­‰ä»·äºŽä¸é…ç½®è¿™ä¸€è¡Œï¼‰
--         false = å…³é—­
--
-- è¯»å–方式:OuthospVisitSwitch#isEnabled() / OtherVisitSwitch#isEnabled()
--   sys_config çƒ­è¯» + 10 ç§’进程内缓存,fail-open(读不到 / è¯»åº“异常都按 true å¤„理)。
--
-- âš ï¸ è¿™ä¸¤ä¸ªæ¨¡å—与出入院/宣教**有一个关键结构差异**:
--   å®ƒä»¬çš„æŽ§åˆ¶å™¨é‡Œ**全是记录 CRUD**(list / export / getInfo / add / edit / remove),
--   **没有任何"生成随访"的 HTTP è§¦å‘端点** â€”— è§¦å‘器全在定时任务与 service è°ƒç”¨é‡Œã€‚
--   æ‰€ä»¥å®ƒä»¬**没有** XxxAvailabilityInterceptor / XxxWebConfig,
--   å¼€å…³çš„作用点在**门面的编排方法**上,不在 HTTP å±‚。
--   è¿™æ˜¯åˆ»æ„çš„:与出入院模块"判断标准是这个端点会不会生成随访,而不是它在不在本模块里"
--   å®Œå…¨ä¸€è‡´ â€”— æ—¢ç„¶ä¸€ä¸ªä¼šç”Ÿæˆéšè®¿çš„ HTTP ç«¯ç‚¹éƒ½æ²¡æœ‰ï¼Œå°±ä¸è¯¥ä¸ºäº†è®©å½¢çŠ¶æ•´é½è€Œç¡¬é€ æ‹¦æˆªå™¨ã€‚
--
-- å…³é—­ outpatientvisit åŽçš„行为:
--   1) OuthospVisitFacade#dealOutpatientInfo()(门诊随访主扫描:扫未处理门诊记录 â†’
--      åŒ¹é…é•¿æœŸä»»åŠ¡ â†’ å†™å­ä»»åŠ¡ï¼‰è¿”å›ž 0 å¹¶æ‰“ WARN,**不再生成新的门诊随访**;
--   2) âš ï¸ å®šæ—¶ä»»åŠ¡å…¥å£**改走该门面**才会受管 â€”— 2025 ä¿®å¤å‰ï¼ŒRyTask#dealOutHospInfo
--      è°ƒçš„æ˜¯ IPatMedOuthospService(经 OuthospDataFacade,无守卫),
--      å¼€å…³æ˜¯**失效**的。修复后与手术随访 RyTask#dealOperationInfo èµ° otherVisitFacade åŒå½¢ã€‚
--   3) é—¨è¯Šè®°å½•查询/CRUD(/smartor/patouthosp/**、/smartor/provisional/**)**不受影响**;
--   4) é—¨è¯Šåˆ†è¡¨ç»´æŠ¤ä¸Žå­˜å‚¨è¿‡ç¨‹é‡å»ºï¼ˆOuthospVisitFacade#createOuthospQuarterTable)
--      **不受本开关约束** â€”— å®ƒå»ºçš„æ˜¯ç‰©ç†åˆ†è¡¨ï¼Œå±žæ•°æ®åŸºç¡€è®¾æ–½ï¼›
--      å®ƒè‡ªå·±çš„开关是 sys_config: outhosp.split.mode(没配就整块不执行)。
--      è‹¥æŠŠå®ƒä¹Ÿå¥—上随访开关,关闭随访会让新周期分表不建 â†’ é—¨è¯Šè®°å½•写不进表。
--   5) æ”¹å®Œç«‹å³ç”Ÿæ•ˆï¼Œæœ€å¤š 10 ç§’,不需要重启。
--
-- å…³é—­ othervisit åŽçš„行为:
--   1) OtherVisitFacade#dealOperationInfo()(手术随访主扫描)返回 0 å¹¶æ‰“ WARN;
--   2) æ‰‹æœ¯è®°å½• / æ‰‹æœ¯æ˜Žç»† / æ‰‹æœ¯åŸºç¡€å­—典的查询与 CRUD
--      ï¼ˆ/smartor/operation/**、/smartor/item/**、/smartor/baseopera/**)**不受影响**;
--   3) æ”¹å®Œç«‹å³ç”Ÿæ•ˆï¼Œæœ€å¤š 10 ç§’,不需要重启。
--
-- âš ï¸ ä¸¤è€…都**不作用于**已生成的子任务:关闭开关是"当天不再生成新的随访",
--    å·²å­˜åœ¨çš„子任务的到期/回收由 ruoyi-inhospvisit çš„定时任务处理。
--
-- æ³¨æ„ï¼šæœ¬é¡¹ç›®ç”¨çš„ MySQL ç‰ˆæœ¬å¯èƒ½ä¸æ”¯æŒ ADD COLUMN IF NOT EXISTS,
--       å› æ­¤è¿™é‡Œç”¨ information_schema åˆ¤æ–­åŽå†åŠ¨æ€æ‰§è¡Œã€‚
-- ----------------------------
SET @sys_config_exists = (
    SELECT COUNT(1) FROM information_schema.COLUMNS
    WHERE TABLE_SCHEMA = DATABASE()
      AND TABLE_NAME = 'sys_config'
      AND COLUMN_NAME = 'config_key'
);
-- ---------------------------------------------------------------
-- 1) outpatientvisit.enabled
-- ---------------------------------------------------------------
SET @ddl = IF(@sys_config_exists = 0,
    'SELECT ''sys_config è¡¨ä¸å­˜åœ¨ï¼Œè¯·å…ˆæ‰§è¡Œå»ºè¡¨è„šæœ¬'' AS warning',
    'INSERT INTO sys_config (config_name, config_key, config_value, config_type, create_by, create_time, remark)
     SELECT ''门急诊随访模块开关'', ''outpatientvisit.enabled'', ''true'', ''Y'', ''admin'', SYSDATE(),
            ''门急诊随访总开关(运行时读取,改完最多10秒生效):true启用,false关闭;默认true。只作用于门诊随访编排,门诊记录CRUD与分表维护不受影响''
     FROM DUAL
     WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE config_key = ''outpatientvisit.enabled'')');
PREPARE outpatientvisit_stmt FROM @ddl;
EXECUTE outpatientvisit_stmt;
DEALLOCATE PREPARE outpatientvisit_stmt;
-- ---------------------------------------------------------------
-- 2) othervisit.enabled
-- ---------------------------------------------------------------
SET @ddl = IF(@sys_config_exists = 0,
    'SELECT ''sys_config è¡¨ä¸å­˜åœ¨ï¼Œè¯·å…ˆæ‰§è¡Œå»ºè¡¨è„šæœ¬'' AS warning',
    'INSERT INTO sys_config (config_name, config_key, config_value, config_type, create_by, create_time, remark)
     SELECT ''手术随访模块开关'', ''othervisit.enabled'', ''true'', ''Y'', ''admin'', SYSDATE(),
            ''手术随访总开关(运行时读取,改完最多10秒生效):true启用,false关闭;默认true。只作用于手术随访编排,手术记录/明细/基础字典CRUD不受影响''
     FROM DUAL
     WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE config_key = ''othervisit.enabled'')');
PREPARE othervisit_stmt FROM @ddl;
EXECUTE othervisit_stmt;
DEALLOCATE PREPARE othervisit_stmt;
-- æ—¥å¸¸å¼€å…³æ“ä½œï¼ˆäºŒé€‰ä¸€ï¼Œæ”¹å®Œæœ€å¤š 10 ç§’生效):
--   å…³é—­é—¨è¯Šéšè®¿ï¼šUPDATE sys_config SET config_value = 'false', update_time = SYSDATE() WHERE config_key = 'outpatientvisit.enabled';
--   å…³é—­æ‰‹æœ¯éšè®¿ï¼šUPDATE sys_config SET config_value = 'false', update_time = SYSDATE() WHERE config_key = 'othervisit.enabled';
-- æŸ¥è¯¢å½“前值
-- SELECT config_id, config_name, config_key, config_value, remark FROM sys_config
--  WHERE config_key IN ('outpatientvisit.enabled', 'othervisit.enabled');