news 2026/11/4 2:52:41

Caché 与 HIS 数据库选型:Global 结构、二次开发与运维避坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Caché 与 HIS 数据库选型:Global 结构、二次开发与运维避坑

简介:面向医疗信息化开发者与 HIS 系统实施人员的 Caché 数据库开发文档合集,聚焦 InterSystems Caché 在医疗业务场景中的安装配置、数据建模、接口开发与运维安全等主题,适合希望从入门过渡到实战的工程师查漏补缺。压缩包共 145 个文件,以 143 份 PDF 技术文档为主体,辅以 1 个 HTML 入口页和 1 张 JPG 示意图,整体约 77.18MB,目录按文档主题归集,便于按模块检索。已有 1036 人学习下载。内容覆盖基础教程、数据模型设计指南、MUMPS 编程语法与最佳实践、API 参考、高可用与灾难恢复策略、权限与数据安全管理,以及报表分析工具说明和示例项目,读者可据此理解 Caché 多维数据模型在实时医疗数据读写、集群切换和系统集成中的落地方式,并对照文档搭建开发环境、梳理接口调用与排错思路。

1. Caché 撑起的 HIS:一张门诊挂号单背后的数据库选型

早上七点四十,门诊挂号窗口刚开,一台自助机卡了两三秒,后面的队伍立刻往前挤。这类现场问题,最后往往落到同一个地方——HIS 的数据库。很多运行了十年以上的医院信息系统,底层用的不是常见的关系库,而是 Caché 这种后关系型数据库:数据以多维稀疏数组(Global)存储,同时又能用 SQL 和对象两种方式访问,读写路径短、单机吞吐高,这也是它当年能在门诊高峰撑住的原因。这份资料包就是围绕 Caché + HIS 的开发文档,覆盖部署、数据字典、ObjectScript 编码规范、接口对接和运维巡检,适合刚接手 HIS 二次开发的程序员、需要排查线上问题的运维,以及想搞懂 Global 结构再去改业务逻辑的人。它不教你数据库原理,它给你一套能对着改的现场资料。

2. 资料包全景:文档目录结构与它覆盖的 HIS 业务模块

拿到一份资料,先别急着翻代码。我一般先看目录,目录结构基本能反映这套系统的分层思路:哪些是部署层的东西,哪些是数据层的东西,哪些是给二次开发看的接口约定。这份 Caché HIS 文档资料的目录大致长这样:

HIS-Cache-Docs/ ├─ 01-部署与安装/ 单机部署、双机热备与 ECP、升级回滚 checklist ├─ 02-数据字典/ 核心 Global 清单、字段与索引对照、字典码表 ├─ 03-开发规范/ ObjectScript 编码规范、包结构、事务与锁约定 ├─ 04-接口文档/ 医保结算、LIS/PACS 对接、REST 与 WebService 入口 ├─ 05-运维手册/ 日常巡检、备份与 Journal 恢复、告警处理 └─ 06-示例代码/ HIS.Biz 业务类、HIS.Web 页面类、demo-data 导出

这个划分不是随便切的。01和05解决「跑起来」和「别跑挂」,02和03解决「看懂代码」和「按规矩改代码」,04解决「和外部系统说话」,06是给你练手的可导入示例。真正上手时,阅读顺序建议反过来:先看02的数据字典认表,再看03的规范认风格,然后06导入跑通,最后回头补01和05。

2.1 业务模块与数据归属:HIS 到底分了哪几块

HIS 不是一个单体功能,它是十几个业务域拼起来的。文档里按模块拆得很清楚,每个模块对应一组 Global 和一组类:

业务模块核心数据对象典型操作
门诊挂号患者、就诊、排班建就诊号、分诊、退号
门急诊收费收费单、明细、发票划价、结算、退费、日结
药房药库库存、出入库、处方发药、盘点、调拨
住院管理住院登记、床位、预交金入出院、转科、欠费控制
医嘱处理长期/临时医嘱、执行记录开立、审核、执行、停止
医保结算结算流水、上传报文预结算、正式结算、对账
接口层消息队列、日志LIS/PACS/EMR 数据交换

理解这张表的价值在于:改任何一个功能之前,你得先知道它动的是哪几个 Global。门诊挂号看似简单,实际上同时写就诊主表、排班余量、挂号流水三处数据,任何一处漏写,第二天对账就会不平。文档里每个模块都附了「涉及 Global 清单」,这是最该先背下来的部分,比背代码有用。

2.2 技术栈与依赖关系:ObjectScript、CSP 和 SQL 三条腿

Caché 的特点是同一份数据有三种访问姿势,HIS 这套代码三种都用上了,这也是新手最容易绕晕的地方。

第一种是 ObjectScript 直接操作 Global。业务里对性能敏感的部分,比如挂号取号、发药扣库存,很多是直接读写 Global 的,路径最短。第二种是对象访问,%Persistent类提供%New()、%Save()、%OpenId()这套 API,索引和事务由框架维护,适合业务逻辑复杂的场景。第三种是 SQL,报表、查询、对外接口基本都走 SQL,因为写起来直观,也方便 BI 工具直接连。

三条腿并存带来的直接后果是:数据一致性靠人守。用对象层保存,索引会自动更新;直接Set ^HIS.ADM(id)=...,索引不动。这个坑后面第 5 章会专门讲。

技术栈上还有两块要留意。CSP 是页面层,类继承%CSP.Page,OnPage()里输出 HTML,很多老 HIS 的门诊工作站界面就是这么写的,不是前后端分离。集成层如果用了 Caché 自带的消息组件,接口报文走的是命名空间内的消息类,排查问题时不能只看应用日志,还得去消息日志里翻。

提示:资料包里的示例代码默认命名空间叫HISAPP,如果你本地建成了别的名字,导入类之前先改包映射,否则类能编译但找不到数据。

3. 数据模型拆解:Global 多维数组怎么撑起 HIS 的表结构

这一章是整份资料里含金量最高的部分。你打开管理门户看到的是「表」,但在 Caché 里,表只是 Global 的一层皮。搞不清这层映射关系,调试时就会陷入「SQL 查得到、终端看不到」或者反过来的黑匣子状态。

3.1 Global 存储结构:一个就诊记录在磁盘上长什么样

Caché 的 Global 是带下标的持久变量,下标可以是字符串也可以是数字,天然支持多层嵌套,稀疏存储不占空间。HIS 里最典型的设计是以「就诊」为主线:

; 就诊主索引:就诊号 -> RowId,用于按号快速定位 Set ^HIS.ADM(0,"IDX",AdmNo) = RowId ; 就诊主记录:RowId -> 定长字段列表 ; $LB 打包:患者ID、就诊日期、科室、医生、状态 Set ^HIS.ADM(RowId) = $LB(PatientID, +$H, DeptID, DocID, "R") ; 就诊下的医嘱明细:RowId + 序号 -> 医嘱内容 Set ^HIS.ADM(RowId,"ORD",1) = $LB("XY01", 2, 12.50) Set ^HIS.ADM(RowId,"ORD",2) = $LB("XY07", 1, 36.00)

$LB是 list build,把多个值打包成一个字符串存进单个节点,读取时用$LI(^HIS.ADM(RowId), 3)取第 3 个元素。这是 Caché 里非常常见的手法,好处是一条记录一次磁盘 IO 就能取全,坏处是字段定长、加字段要改所有读写点。

参数说明:第一层下标0是保留的索引区,业务数据从1开始;"IDX"是纯字符串下标,和数字下标混用没问题;"ORD"是子节点,表示一对多关系。这种「主表节点 + 子表节点」的树形结构,就是 Caché 里表达关系模型的标准做法,SQL 引擎在背后也是这么翻译的。

3.2 从 Global 到 SQL 表:映射规则和索引怎么建

SQL 层看到的HIS_Biz.Admission表,其实是上面那些 Global 的一个投影。Caché 通过类定义的 Storage 段来描述映射,日常开发不用手写 Storage,定义属性和索引,编译时自动生成。关键是索引:

Class HIS.Biz.Admission Extends %Persistent { Property AdmNo As %String(MAXLEN = 20) [ Required ]; Property PatientID As %String(MAXLEN = 30) [ Required ]; Property AdmDate As %Date; Property DeptID As %String(MAXLEN = 10); Property Status As %String(MAXLEN = 2) [ InitialExpression = "R" ]; Index AdmNoIdx On AdmNo [ Unique ]; Index PatDateIdx On (PatientID, AdmDate); Index DeptDateIdx On (DeptID, AdmDate) [ Type = bitmap ]; }

Index定义会在 Global 里额外生成一组索引节点,比如^HIS.Biz.AdmissionI("PatDateIdx", PatientID, AdmDate, RowId)。所以一条记录写下去,实际磁盘上动了主数据和三份索引,共四个位置。Unique保证就诊号不重;bitmap位图索引适合DeptID这种低基数字段,按科室统计当天挂号量时走位图比走普通索引快一个量级。

参数要点:MAXLEN在 Caché 里按字符算,中文姓名给 30 够用;InitialExpression只在对象新建时生效,直接写 Global 不会触发;[ Required ]是对象层校验,SQL 层插入时如果字段非空约束没建,仍然可能写进空值。

3.3 一条挂号落库的完整链路:从终端到事务提交

光看结构没感觉,看一段真实业务代码。挂号的核心动作是:校验患者、生成就诊号、写主记录、写流水,全程一个事务。

ClassMethod Register(pPatientID As %String, pDeptID As %String, pDocID As %String, ByRef pAdmNo As %String) As %Status { Set sts = $$$OK, pAdmNo = "" TSTART Try { ; 1. 患者必须存在,否则直接抛异常回滚 If '##class(HIS.Biz.Patient).%ExistsId(pPatientID) { Set sts = $$$ERROR($$$GeneralError, "患者不存在:"_pPatientID) Throw ##class(%Exception.StatusException).CreateFromStatus(sts) } ; 2. 生成就诊号:日期 + 当日 6 位流水,$INCREMENT 是原子自增 Set pAdmNo = $ZDATE(+$H, 8)_$TRANSLATE($J($INCREMENT(^HIS.ADM.CNT(+$H)), 6), " ", "0") ; 3. 落主记录 Set adm = ##class(HIS.Biz.Admission).%New() Set adm.AdmNo = pAdmNo Set adm.PatientID = pPatientID Set adm.DeptID = pDeptID Set adm.DocID = pDocID Set adm.AdmDate = +$H Set adm.AdmTime = $PIECE($H, ",", 2) Set adm.Status = "R" Set sts = adm.%Save() If $$$ISERR(sts) Throw ##class(%Exception.StatusException).CreateFromStatus(sts) TCOMMIT } Catch ex { TROLLBACK Set sts = ex.AsStatus() } Quit sts }

逐段说。TSTART到TCOMMIT之间是一个事务,Catch里必须TROLLBACK,否则事务挂起会污染后续请求,这是 ObjectScript 里最常见的翻车点。$INCREMENT对 Global 做原子自增,并发挂号不会拿到重复流水号,比「先查再加一再写」安全得多。+$H是 Caché 的 horolog 日期(从某基准日起的天数),$PIECE($H, ",", 2)取当天秒数,两者拼接就是完整时间戳。%Save()内部同时维护主记录和全部索引。

调用和验证:

Set sts = ##class(HIS.Biz.Admission).Register("P000123", "D01", "DOC07", .admNo) Write "status=", $$$ISOK(sts), " admNo=", admNo, ! Write $LI(^HIS.ADM(1), 3), ! ; 取第 1 条记录的科室

如果status=0,用Do $SYSTEM.Status.DisplayError(sts)打出具体错误。第三个参数.admNo是引用传递,前面必须加点号,漏了点号拿不到返回值,这个细节坑过不少人。

4. 环境落地:在本机把 Caché + HIS 示例库跑起来

文档看得再熟,不跑一遍永远不知道自己卡在哪。这一章按我这边的习惯,把从零到能调业务代码的步骤走一遍。整套东西对机器要求不高,普通开发本 8G 内存够用,安装包和示例代码都在资料包的01和06目录里。

4.1 安装与命名空间划分:把数据和代码分开放

安装过程没什么可说的,一路下一步,两个地方要留意:一是安装类型选「开发」,会带上编译器和终端工具;二是字符集选 Unicode,选了别的字符集,后面中文姓名和地址会出现截断,改起来只能重装。

装完先做数据库和命名空间规划,别把业务表扔在USER里。我一般建两个数据库:HISDAT存数据(CACHE.DAT),HISCODE存例程和类定义,然后建一个命名空间HISAPP,把两者映射进去。这样做的好处是备份数据不影响代码,导出代码不影响数据。

# Windows 下启动实例并进入终端 ccontrol start CACHE csession CACHE -U HISAPP

进终端后用Write $NAMESPACE确认当前命名空间,用Write $SYSTEM.Version.GetEncoding()确认字符集。这两条我每次搭新环境都敲一遍,血泪经验:有一次连错环境调了半天,最后发现是在%SYS里改数据。

4.2 导入示例代码并跑通第一个查询

资料包里的demo-data.xml是类导出文件,用$SYSTEM.OBJ.Load导入:

; 导入整个目录的类定义,/compile 表示导入后编译 Set sts = $SYSTEM.OBJ.LoadDir("D:\his\src", "ck", .err, 1) Write "import status=", $$$ISOK(sts), !

第一个参数是目录,第二个"ck"表示编译并保留源码,.err收集错误,最后一个1表示递归子目录。导入报错先看.err数组,多半是包名对不上或者依赖类缺失。

然后跑一个只读查询验证数据通不通:

Set sql = "SELECT AdmNo, PatientID, DeptID FROM HIS_Biz.Admission WHERE AdmDate = ? AND Status = ?" Set stmt = ##class(%SQL.Statement).%New() Set stmt.%SelectMode = 2 ; 2 = 只读游标,别用默认值 Set sts = stmt.%Prepare(sql) If $$$ISERR(sts) { Write $SYSTEM.Status.DisplayError(sts), ! Quit } Set rs = stmt.%Execute(+$H, "R") While rs.%Next() { Write rs.AdmNo, " | ", rs.PatientID, " | ", rs.DeptID, ! } Write "rowcount=", rs.%ROWCOUNT, !

%SelectMode = 2表示逻辑模式,日期按 horolog 数字返回,避免格式转换踩坑;%Execute的参数按问号顺序传;%Next()返回 0 时循环结束,%ROWCOUNT是总行数。这套 API 和 JDBC 很像,从 Java 转过来的话半天就能适应。

页面层再验证一次。建个 CSP 类,能打开就说明 Web 网关通了:

Class HIS.Web.RegList Extends %CSP.Page { ClassMethod OnPage() As %Status { Write "<table border=1>" Set rs = ##class(%SQL.Statement).%ExecDirect(, "SELECT TOP 20 * FROM HIS_Biz.Admission ORDER BY AdmDate DESC") While rs.%Next() { Write "<tr><td>", rs.AdmNo, "</td><td>", rs.DeptID, "</td></tr>" } Write "</table>" Quit $$$OK }

访问地址是http://localhost:57772/csp/his/HIS.Web.RegList.cls,端口以安装时配置为准。打不开先查 IIS/Apache 网关配置,再查类有没有编译通过,最后看%SYS里的 Web 应用路径是不是指到了HISAPP命名空间。

注意:示例库里的数据是脱敏造出来的,AdmDate用的是当天 horolog 值,如果查询时传的是字符串日期,结果一定为空,别急着怀疑 SQL 写错了。

5. 避坑与排查:Caché 跑 HIS 时最容易翻车的五个地方

这一章按「现象 → 原因 → 解决」写,都是我在现场真碰过的。Caché 的问题有个共同特点:症状出现在上层,根因往往在数据层或者部署层,不熟的话很容易在应用代码里空转。

5.1 数据一致性类:SQL 查不到终端里明明有的数据

现象:终端里Write ^HIS.ADM(100)有完整记录,SELECT却查不出来,报表当天少统计了一批患者。

原因:有人为了图快,直接Set ^HIS.ADM(100)=...写 Global,绕过了%Save()。主数据是有了,但^HIS.Biz.AdmissionI("PatDateIdx", ...)这些索引节点没写,SQL 走索引查自然查不到。批量导数据时这个坑几乎必踩。

解决:立刻重建索引,Set sts = ##class(HIS.Biz.Admission).%BuildIndices(),大表加$SYSTEM.SQL.SetOption控制批大小,避免锁表。根治办法是约定:业务代码一律走对象层或 SQL,确实需要裸写 Global 的场景(比如日志、计数),写完必须同步索引,并在03-开发规范里登记。

5.2 排序类:$ORDER取「最新一条」取错了

现象:按流水号倒序取当日最大号,结果取到了 9 号,实际上已经发到 100 号。

原因:Global 下标是字符串排序,"100" < "9"。$ORDER(^HIS.ADM.CNT(""), -1)倒序遍历拿到的是字典序最大的那个,不是数值最大的。

解决:要么用$INCREMENT自己在计数节点上维护,直接取值不做遍历;要么把下标补齐成定长,$J(seq, 10)生成"0000000100";要么这种统计直接交给 SQL 层ORDER BY。我在03-开发规范里加了一条硬规矩:所有参与排序的数字下标,一律补零定长,谁破例谁请客。

5.3 并发类:高峰期偶发<LOCK>错误

现象:门诊高峰,挂号接口偶发 500,日志里是<LOCK>报错,重试一次又能过。

原因:两个可能。一是事务里夹了外部调用,医保预结算接口慢的时候,事务持锁几秒,后面排队的请求直接超时;二是多个方法的加锁顺序不一致,A 先锁就诊再锁收费,B 反过来,形成死锁。

解决:外部调用挪到TSTART之前做完,事务里只保留纯数据库操作;加锁统一顺序,按「患者 → 就诊 → 费用」的层级加,禁止反向;给锁设置等待上限LOCK +^HIS.ADM(id):5,超时就返回友好提示而不是挂死。改完之后,高峰期<LOCK>从每天十几次降到零。

5.4 环境类:脚本跑到了生产库上

现象:测试环境执行了一个批量修复脚本,生产环境的数据跟着变了。

原因:Global 映射配错,或者脚本执行前没切命名空间,默认落在了USER,而USER又被映射到了生产库的 Global。

解决:脚本第一行强制ZN "HISAPP"并断言If $NAMESPACE'="HISAPP" Quit;运维账号按环境拆分,生产库给脚本账号只读权限,需要写入的操作必须走审批工单。这条不是技术问题,是流程问题,但出一次事的代价比写十条规范都大。

5.5 字符集类:中文姓名变问号或被截断

现象:新增患者保存成功,查询出来姓名是问号,或者报<MAXLEN>超长。

原因:安装时字符集选了非 Unicode;或者客户端连接串没指定 UTF-8,驱动按本地编码转换,中文一个字符被当成多字节截断。

解决:先用Write $SYSTEM.Version.GetEncoding()确认服务端编码,再用Write $LENGTH("张三")验证是按字符还是按字节计数(正常应该返回 2)。客户端 JDBC 连接串补上字符集参数。如果服务端本身装错了,只能重装实例再导数据,所以安装那一步别图省事。

提示:这五类问题在资料包的05-运维手册/常见告警处理.md里都有对应的巡检命令,出问题先跑一遍巡检脚本,比人肉翻日志快得多。

6. 二次开发:从读文档到改代码的几条实操路径

资料看得差不多,接下去就是真改代码了。二次开发最容易犯的错是「一上来就动核心表结构」,HIS 的表结构牵着收费、医保、报表一大片,加字段之前先想清楚这个字段会不会进结算报文。我的做法是先做只读改造,再碰写入逻辑。

第一条路径是加查询,不动数据。这类需求最多,比如「按科室和日期统计挂号量」。做法是加一个只读类方法,走 SQL,必要的时候补位图索引:

Class HIS.Biz.Admission Extends %Persistent { Index DeptDateIdx On (DeptID, AdmDate) [ Type = bitmap ]; ClassMethod StatByDept(pDate As %Integer) As %Status { Set sql = "SELECT DeptID, COUNT(*) AS Cnt FROM HIS_Biz.Admission "_ "WHERE AdmDate = ? AND Status <> 'C' GROUP BY DeptID" Set rs = ##class(%SQL.Statement).%ExecDirect(, sql, pDate) While rs.%Next() { Write rs.DeptID, ":", rs.Cnt, ! } Quit $$$OK } }

位图索引在低基数字段上效果好,但要注意:频繁增删的表上大量位图索引会带来锁竞争,门诊挂号表写入量大,索引数量控制在三个以内,多了反而拖慢%Save()。

第二条路径是改业务流程。比如挂号要加「是否复诊」标记。步骤是:类定义加属性 → 编译 → 老数据兼容(属性给默认值InitialExpression)→ 改Register()方法签名 → 同步改调用方 → 跑回归。千万别直接改 Global 结构,加了字段不补历史数据,$LI取到空值会在下游炸出一堆<NULL VALUE>。

第三条路径是接口对接。资料包04-接口文档里给了报文格式和重试约定,改造时重点看两点:接口失败要不要回滚本地事务(一般不要,落本地消息表后异步重试),以及幂等键用什么(HIS 里通常用就诊号 + 业务类型)。

验证方法我固定走三步:先在终端里用%SQL.Statement跑一遍 SQL 看结果集;再写一个只读 CSP 页面肉眼核对;最后用示例库造 1000 条并发数据跑压测,看<LOCK>和响应时间。三步都过才算改完。

踩过的坑里最贵的一次,是给一张百万级的费用明细表加了个普通索引,白天业务高峰直接锁表十几分钟被投诉。从那以后我每次加索引,都强制先在测试库用同等数据量跑一遍%BuildIndices(),记录耗时,超过阈值就改到夜间窗口做,并且提前把回滚脚本准备好。这套习惯后来救过我好几次。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/4 2:51:45

GEO系统源码选购指南:避开技术陷阱,选对赚钱工具

1. GEO系统源码选购指南&#xff1a;如何避开技术陷阱&#xff0c;选对真正能赚钱的工具&#xff1f;在AI技术快速渗透各行各业的今天&#xff0c;GEO&#xff08;生成式引擎优化&#xff09;系统已经成为企业获取AI流量的关键基础设施。作为一位在AI营销领域深耕多年的从业者&…

作者头像 李华
网站建设 2026/11/4 2:51:30

MySQL高效插入操作全解析与优化实践

1. MySQL插入操作基础认知作为关系型数据库最基础的操作之一&#xff0c;插入&#xff08;INSERT&#xff09;语句承载着数据持久化的核心功能。我在处理电商系统订单数据时&#xff0c;曾遇到单日百万级插入请求的挑战&#xff0c;这让我深刻认识到看似简单的INSERT背后隐藏着…

作者头像 李华
网站建设 2026/11/4 2:49:55

SciTE4AutoHotkey 配置与调试实战:从记事本到高效 IDE

简介&#xff1a;SciTE4Autohotkey 是面向 AutoHotkey 脚本开发者的一款专用源代码编辑器&#xff0c;基于轻量级 Scintilla 引擎构建&#xff0c;适合希望摆脱笨重 IDE、追求快速启动与便携使用的初、中级自动化脚本编写者。它针对 AutoHotkey 语言做了深度适配&#xff0c;提…

作者头像 李华
网站建设 2026/11/4 2:49:41

RabbitMQ消息可靠投递机制与高可用架构设计

1. RabbitMQ投递回调机制解析RabbitMQ作为企业级消息队列的标杆产品&#xff0c;其投递回调机制是确保消息可靠传输的核心设计。在实际生产环境中&#xff0c;消息从生产者发出到最终被消费者处理&#xff0c;中间可能经历网络抖动、队列满溢、消费者崩溃等多种异常场景。理解并…

作者头像 李华
网站建设 2026/11/4 2:49:18

云上Hadoop集群搭建与Spark on YARN实战:从ECS到WordCount

简介&#xff1a;《华为云大数据实验报告hhu》是一份面向高校大数据、云计算课程学习者的实验记录型文档&#xff0c;围绕华为云环境下ECS、OBS资源开通与集群部署展开&#xff0c;适合需要完成课程实验、复盘上机流程或准备大数据平台搭建练习的学生参考。压缩包内为1个docx文…

作者头像 李华
网站建设 2026/11/4 2:48:31

SQL Server中STUFF函数的深度解析与应用实践

1. STUFF函数基础解析STUFF函数是SQL Server中一个强大的字符串处理工具&#xff0c;它允许我们在指定位置删除特定长度的字符&#xff0c;并插入新的字符串。这个函数在数据处理、报表生成和动态SQL构建等场景中非常实用。1.1 函数语法与参数详解STUFF函数的完整语法如下&…

作者头像 李华