搜索​​​​

清除过滤器
文章
姚 鑫 · 二月 27, 2021

第四十九章 Caché 变量大全 ^$ROUTINE 变量

# 第四十九章 Caché 变量大全 ^$ROUTINE 变量 提供例程信息。 # 大纲 ```java ^$|nspace|ROUTINE(routine_name) ^$|nspace|R(routine_name) ``` ## 参数 - `|nspace|`或`[nspace]` 可选-扩展SSVN引用,可以是显式名称空间名称,也可以是隐含名称空间。必须计算为带引号的字符串,该字符串括在方括号(`[“nspace”]`)或竖线(`|“nspace”|`)中。命名空间名称不区分大小写;它们以大写字母存储和显示。 - routine_name 计算结果为包含例程名称的字符串的表达式。 # 描述 可以将`^$ROUTINE`结构化系统变量用作`$DATA`、`$ORDER`和`$QUERY`函数的参数,以从当前命名空间(默认)或指定命名空间返回例程信息。`^$ROUTINE`返回有关例程的OBJ代码版本的例程信息。 在InterSystems ObjectScript中,一个例程有三个代码版本:MAC(用户编写的代码,可能包括宏预处理器语句)、INT(编译的MAC代码,用于执行宏预处理)和OBJ(可执行目标代码)。可以使用`^$ROUTINE global`返回关于int代码版本的信息。可以使用`^$ROUTINE`返回有关OBJ代码版本的信息。 # 参数 ## nspace 此可选参数允许使用扩展SSVN引用在另一个命名空间中指定全局。可以显式地将命名空间名称指定为带引号的字符串文字或变量,也可以通过指定隐含的命名空间来指定。命名空间名称不区分大小写。可以使用方括号语法`[“user”]`或环境语法`|“user”|`。Nspace分隔符前后不允许有空格。 ```java WRITE ##class(%SYS.Namespace).Exists("USER"),! ; an existing namespace WRITE ##class(%SYS.Namespace).Exists("LOSER") ; a non-existent namespace ``` 可以使用`$NAMESPACE`特殊变量来确定当前名称空间。更改当前名称空间的首选方式是新建`$NAMESPACE`,然后设置`$NAMESPACE=“nspace ename”`。 ## routine_name 计算结果为包含现有例程名称的字符串的表达式。例程名称在前255个字符内必须是唯一的;应避免超过220个字符。 # 示例 以下示例使用`^$`例程作为`$DATA`、`$ORDER`和`$QUERY`函数的参数。 ## 作为$DATA的参数 `$DATA(^$|nspace|ROUTINE(routine_name))` `^$ROUTINE`作为`$DATA`的参数将返回一个整数值,该整数值指定例程名OBJ代码版本是否作为`^$ROUTINE`中的节点存在。下表显示了`$DATA`可以返回的整数值。 Value| Meaning ---|--- 0 |例程不存在 10 |例程存在 下面的Terminal示例测试myrou例程的OBJ代码版本是否存在。此示例假定在`USER`名称空间中有一个名为myrou的已编译MAC例程: ```java USER>WRITE ^ROUTINE("myrou",0,"GENERATED") // INT code version exists 1 USER>WRITE $DATA(^$ROUTINE("myrou")) // OBJ code version exists 1 USER>KILL ^rOBJ("myrou") // Kills the OBJ code version USER>DO ^myrou DO ^myrou ^ *myrou USER>WRITE ^ROUTINE("myrou",0,"GENERATED") // INT code version exists 1 USER>WRITE $DATA(^$ROUTINE("myrou")) // OBJ code version does not exist 0 USER> ``` ## 作为`$ORDER`的参数 `$ORDER(^$|nspace|ROUTINE( routine_name),direction)` `^$ROUTINE`作为`$ORDER`的参数,按整理顺序返回指定的例程名称的下一个或上一个例程名称。如果在`^$ROUTINE`中没有这样的例程名称作为节点存在,则`$ORDER`返回空字符串。 direction参数指定是否返回下一个或上一个例程名称:1 =下一个,-1 =上一个。如果不提供方向参数,则InterSystems IRIS将按整理顺序将下一个例程名称返回到指定的名称。 以下子例程搜索USER名称空间,并将例程名称存储在名为ROUTINE的本地数组中。 ```java /// d ##class(PHA.TEST.SpecialVariables).ROUTINE() ClassMethod ROUTINE() { SET rname="" FOR I=1:1 { SET rname=$ORDER(^$|"USER"|ROUTINE(rname)) QUIT:rname="" SET ROUTINE(I)=rname WRITE !,"Routine name: ",rname } WRITE !,"All routines stored" QUIT } ``` ```java Routine name: INFORMATION.SCHEMA.TABLECONSTRAINTS.1 Routine name: INFORMATION.SCHEMA.TABLES.0 Routine name: INFORMATION.SCHEMA.TABLES.1 Routine name: INFORMATION.SCHEMA.TRIGGERS.0 Routine name: INFORMATION.SCHEMA.TRIGGERS.1 Routine name: INFORMATION.SCHEMA.VIEWCOLUMNUSAGE.0 Routine name: INFORMATION.SCHEMA.VIEWCOLUMNUSAGE.1 Routine name: INFORMATION.SCHEMA.VIEWS.0 Routine name: INFORMATION.SCHEMA.VIEWS.1 Routine name: INFORMATION.SCHEMA.VIEWTABLEUSAGE.0 Routine name: INFORMATION.SCHEMA.VIEWTABLEUSAGE.1 All routines stored ``` ## 作为$QUERY的参数 `$QUERY(^$|nspace|ROUTINE(routine_name))` `^$ROUTINE`作为`$QUERY`的参数,按整理顺序将下一个例程名称返回到指定的例程名称。指定的例程名称不必存在。如果以后在排序序列中没有例程名称,则`$QUERY(^$ROUTINE)`返回一个空字符串。 在下面的示例中,两个`$QUERY`函数在`USER`名称空间中指定例程名称之后返回下一个例程。 ```java /// d ##class(PHA.TEST.SpecialVariables).ROUTINE1() ClassMethod ROUTINE1() { SET rname="" WRITE !,"1st routine: ",$QUERY(^$|"USER"|ROUTINE(rname)) SET rname="%m" WRITE !,"1st ",rname, " routine: ",$QUERY(^$|"USER"|ROUTINE(rname)) QUIT } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ROUTINE1() 1st routine: ^$|"USER"|ROUTINE("%APILIB") 1st %m routine: ^$|"USER"|ROUTINE("%mgw1") ``` 能够理解总结知识,但是结合到场景不知道如何使用。 知识讲的很仔细全面,GET了
文章
姚 鑫 · 七月 22, 2021

关键字触发器定义,扩展数据块,类关键字Abstract,ClassType

# 第九章 触发器定义 描述触发器定义的结构。 # 介绍 触发器是在SQL中发生特定事件时执行的代码段。InterSystems IRIS支持基于执行`INSERT`、`UPDATE`和`DELETE`命令的触发器。根据触发器定义,指定的代码将在相关命令执行之前或之后立即执行。每个事件可以有多个触发器,只要它们被分配了执行顺序。 可以向持久类添加触发器定义。它们在其他类中没有意义。 # 详情 触发器定义具有以下结构: ```java /// description Trigger name [ keyword_list ] { implementation } ``` - `description` 描述(可选)旨在显示在“类参考”中。默认情况下,描述为空白。 - `name`(必需)是触发器的名称。这必须是有效的类成员名称,并且不能与任何其他类成员名称冲突。 - `keyword_list`(必需)是以逗号分隔的关键字列表,用于进一步定义触发器。 - `implementation` 实现(必需)是零行或多行ObjectScript代码,用于定义触发触发器时要执行的代码。 # 示例 ```java /// 此触发器在每次插入后更新日志表 Trigger LogEvent [ Event = INSERT, Time = AFTER ] { // 获取插入行的行id NEW id SET id = {ID} // 将值插入日志表 &sql(INSERT INTO LogTable (TableName, IDValue) VALUES ('MyApp.Person', :id)) } ``` # 第十章 扩展数据块 描述XData块的结构。 # 介绍 `XData`块是包含在类定义中的命名数据单元,通常由类中的方法使用。最常见的情况是,它是一个XML文档,但是它可以由其他形式的数据组成,例如`JSON`或`YAML`。 # 详情 `XData`块具有以下结构: ```java /// description XData name [ keyword_list ] { data } ``` - `description` 描述(可选)旨在显示在“类别参考”中。默认情况下,描述为空白。 - `name`(必需)是`XData`块的名称。这必须是有效的类成员名称,并且不能与任何其他类成员名称冲突。 - `data` 数据(可选)包含扩展数据块的有效载荷。如果是XML,则它必须是格式良好的文档(只有一个根元素),开头没有XML声明。 - `keyword_list`(可选)是以逗号分隔的关键字列表,进一步定义了`XData`块。如果省略此列表,也要省略方括号。 # 示例 ```java Class Demo.CoffeeMakerRESTServer Extends %CSP.REST { Parameter HandleCorsRequest = 1 XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ] { } ``` # 第十一章 类关键字 - Abstract 指定这是否是抽象类。 # 用法 要将类标记为抽象类,请使用以下语法: ```java Class MyApp.MyClass [ Abstract ] { //class members } ``` 否则,省略此关键字或将单词Not放在关键字的前面。 # 详解 如果一个类是抽象的,就不能创建它的实例。 # 对子类的影响 此关键字不是继承的。 # 默认 如果省略这个关键字,这个类就不是抽象的。 # 第十二章 类关键字 - ClassType 指定此类的类型(或行为)。 # 用法 要指定类的类型(如果需要),请使用以下语法: ```java Class MyApp.MyClass [ ClassType = classtype ] { //class members } ``` 其中`classtype`是下列之一: - `datatype` — 该类是一个数据类型类,用于表示文字值。 - `persistent` 持久—该类表示要存储在数据库中的数据。 - `serial` —该类表示要存储在另一个持久对象中(处于序列化状态)的数据。 - `stream` —该类表示流数据。 - `view`视图—该类用于定义一个SQL视图。 - `index` —该类是一个索引类,一个定义索引接口的专用类。 - 空字符串,表示此类没有特定类型。抽象类通常不指定类类型。 如果未指定此关键字,则类类型从主超类继承(如果有)。 请注意,`ClassType`是为`%RegisteredObject`、`%SerialObject`、`%Persistent`和数据类型类等系统类指定的,因此如果对这些类进行子类化,通常不需要指定此关键字。 # 详解 此关键字指定如何使用此类。类别编译器使用类别类型关键字来决定如何编译类别。例如,如果`ClassType`是持久性的,则类编译器还会调用存储编译器来为类生成持久性代码。除非明确定义,否则`ClassType`的值要么是默认值,要么是从主超类继承而来的。 对于持久性类,只有在标准持久性行为被重写时,才需要显式的`ClassType`语句。如果一个类定义包含这样的语句,要么是因为开发人员指定了它,要么是因为这个类起源于用旧版本的InterSystems IRIS开发的代码。 # 对子类的影响 这个关键字是从主超类继承的。子类可以覆盖关键字的值。 # 默认 如果省略此关键字,类类型将从主超类继承(如果有)。 注意:分片类的类类型不能有持久以外的任何值。
文章
Weiwei Gu · 五月 4, 2023

在 Docker 中配置镜像

我们客户的一个共同需求是配置 HealthShare HealthConnect 和 IRIS的高可用性模式。 市场上的其他集成引擎通常被宣传为具有“高可用性”配置,但事实并非如此。通常,这些解决方案与外部数据库一起使用,因此,如果这些数据库未配置为高可用性,当发生数据库崩溃或与它的连接丢失时,整个集成工具将变得不可用。 对于 InterSystems 解决方案,这个问题不存在,因为数据库是工具本身的一部分和核心。 InterSystems 如何解决高可用性问题?深奥的配置会把我们拖入异化和疯狂的漩涡?不!在 InterSystems,我们倾听并处理了您的投诉(正如我们一直努力做的那样 ;)),并且我们已将镜像功能提供给所有用户和开发人员。 镜像 镜像如何工作?这个概念本身非常简单。如您所知,IRIS 和 HealthShare 都使用一个日志系统,该系统记录每个实例的数据库上的所有更新操作。这个日志系统是后来帮助我们在崩溃后恢复实例而不会丢失数据的系统。好吧,这些日志文件在镜像中配置的实例之间发送,允许并保持镜像中配置的实例永久更新。 架构 让我们简要解释一下在 Mirror 中配置的系统架构是什么样的: 在故障转移模式下配置的两个实例: 主动节点——接收所有常规的读/写操作。 被动节点:在读取模式下,它同步接收主动节点产生的任何变化。 0-14个异步实例:你可以使用多个异步实例,它们可以是两种类型: DR 异步(灾难恢复):处于读取模式的节点不是故障转移的一部分,尽管它们可以被手动提升成故障转移节点。如果是这样,它们可以在其他两个故障转移节点发生故障时自动提升为主节点。您的数据更新是异步的,所以不能保证其数据是最新的。 报告异步:异步更新节点,用于 BI 任务或数据挖掘。它们不能升级为故障转移,因为可以对数据执行写入。 ISCAgent:安装在每个实例所在的服务器上。它将负责监视所述服务器实例的状态。这是镜像服务器之间除了直接通信之外的另一种通信方式。 Arbiter:它是一个独立于构成镜像的服务器安装的 ISCAgent,并允许通过监视安装的 ISCAgent 和 IRIS/HealthShare 实例来提高安全性和内部故障转移的控制。它的安装不是必须的。 这是一个由只有两个节点的故障转移形成的镜像的操作: 先前的警告 与本文相关的项目没有允许配置镜像的活动许可证。如果你想尝试,直接给我发邮件或者在文末添加评论,我会联系你。 在 Docker 中部署 对于本文,我们将在 Docker 中建立一个小项目,允许我们设置 2 个故障转移实例和一个Arbiter 。默认情况下,可用于 Docker 的 IRIS 映像已经安装并配置了 ISCAgent,因此我们可以跳过该步骤。有必要配置与来自 Visual Studio Code 的文章关联的项目,因为这将使我们以后可以更轻松地使用服务器文件。 让我们看看我们的 docker-compose.yml 会有什么形式: version: '3.3' services: arbiter: container_name: arbiter hostname: arbiter image: containers.intersystems.com/intersystems/arbiter:2022.1.0.209.0 init: true command: - /usr/local/etc/irissys/startISCAgent.sh 2188 mirrorA: image: containers.intersystems.com/intersystems/iris:2022.1.0.209.0 container_name: mirrorA depends_on: - arbiter ports: - "52775:52773" volumes: - ./sharedA:/shared - ./install:/install - ./management:/management command: --check-caps false --key /install/iris.key -a /install/installer.sh environment: - ISC_DATA_DIRECTORY=/shared/durable hostname: mirrorA mirrorB: image: containers.intersystems.com/intersystems/iris:2022.1.0.209.0 container_name: mirrorB depends_on: - arbiter - mirrorA ports: - "52776:52773" volumes: - ./sharedB:/shared - ./install:/install - ./management:/management command: --check-caps false --key /install/iris.key -a /install/installer.sh environment: - ISC_DATA_DIRECTORY=/shared/durable hostname: mirrorB 可以看到我们定义了3个容器: Arbiter :它对应于将被部署以控制将形成 Mirror Failover 的 IRIS 实例的 ISCAgent(即使图像称为 Arbiter)。当启动容器时,它将执行一个 shell 文件,该文件将启动侦听容器端口 2188 的 ISCAgent。 mirrorA :将部署 IRIS v.2022.1.0.209 映像的容器,稍后我们将其配置为主故障转移节点。 mirrorB :将部署 IRIS v.2022.1.0.209 镜像的容器,稍后我们将其配置为辅助故障转移节点。 当我们执行docker-compose up -d命令时,定义的容器将部署在我们的 Docker 中,它在我们的 Docker 桌面中应该看起来像这样(如果我们从 Windows 执行此操作)。 镜像配置。 部署容器后,我们将继续访问我们将在镜像中配置的实例,第一个将在端口 52775 ( mirrorA ) 上侦听,第二个在 52776 ( mirrorB ) 上侦听。访问用户和密码将是superuser/ SYS 由于实例部署在 Docker 中,我们将有两个选项来配置我们服务器的 IP。第一种是在配置中直接使用我们容器的名称(这是最简单的方法)或检查 Docker 为每个容器分配的 IP(打开控制台并执行返回分配的 IP 的 ifconfig)。为了清楚起见,我们将在示例中使用我们为每个容器指定的名称作为 Docker 中每个容器的地址。 首先,我们将配置我们将用作故障转移活动节点的实例。在我们的例子中,它将是我们所说的mirrorA 。 第一步是启用镜像服务,因此我们将从管理门户访问镜像菜单: System Administration --> Configuration --> Mirror Settings --> Enable Mirror Service并标记Service Enabled检查: 启用服务后,我们可以开始配置我们的活动节点。启用该服务后,您将能够在镜像菜单中看到新选项已启用: 在这种情况下,由于我们还没有创建任何镜像配置,我们必须使用Create Mirror选项创建一个新配置。当我们访问这个选项时,管理门户将打开一个新窗口,我们可以从中配置我们的镜像: 让我们仔细看看每个选项: Mirror Name :我们将用来标识我们的镜像的名称。对于我们的示例,我们将其称为 MIRRORSET 需要 SSL/TLS :对于我们的示例,我们不会使用 SSL/TLS 配置连接,尽管在生产环境中,防止在实例之间没有任何类型的加密的情况下共享日志文件会比操作方便更重要。如果您有兴趣配置它,您可以在文档的以下URL中获得所有必要的信息。 使用 Arbiter :此选项不是强制性的,但强烈推荐,因为它为我们的镜像配置增加了一层安全性。对于我们的示例,我们将选中它并指示我们运行 Arbiter 的 IP。对于我们的示例,IP 将位于容器名称arbiter中。 用户虚拟 IP :在 Linux/Unix 环境中,此选项非常有趣,因为它允许我们为将由我们的镜像管理的活动节点配置虚拟 IP。此虚拟 IP 必须与故障转移节点属于同一子网。虚拟IP的操作非常简单,当主动节点出现故障时镜像会自动在待提升的被动节点所在的服务器上配置虚拟IP。这样,被动节点到主动节点的升级对用户来说将是完全透明的,因为他们将继续连接到同一个 IP,即使它将配置在不同的服务器上。如果您想了解有关虚拟 IP 的更多信息,可以查看文档的此URL 。 其余的配置可以保持原样。在屏幕右侧我们会看到镜像中这个节点的相关信息: Mirror Member Name :这个镜像成员的名称,默认情况下它将采用服务器的名称以及实例的名称。 超级服务器地址:这个节点的超级服务器 IP 地址,在我们的例子中是mirrorA 。 Agent Port :配置了该节点对应的ISCAgent的端口。默认为2188 。 配置必要的字段后,我们可以继续保存镜像。我们可以从镜像监视器(系统操作-->镜像监视器)检查配置情况。 完美,这里我们有了新配置的镜像。如您所见,只有我们刚刚创建的活动节点出现。很好,接下来让我们在故障转移中添加我们的被动节点。我们访问mirrorB管理门户并访问镜像设置菜单。正如我们已经为mirrorA实例所做的那样,我们必须启用镜像服务。我们重复该操作,一旦菜单选项更新,我们将选择Join as Failover 。 这里我们有镜像连接屏幕。让我们简要解释一下每个字段的含义: 镜像名称:我们在创建时为镜像指定的名称,在我们的示例中为MIRRORSET 。 Agent Address on Other System :部署主动节点ISCAgent的服务器IP,对我们来说就是mirrorA。 代理端口:我们创建镜像的服务器的ISCAgent监听端口。默认为2188 。 InterSystems IRIS 实例名称:主动节点上 IRIS 实例的名称。在这种情况下,它与被动节点IRIS的一致。 保存镜像数据后,我们将可以选择定义与我们正在配置的被动节点相关的信息。我们再来看看被动节点可以配置的字段: 镜像成员名称:被动节点将在镜像中使用的名称。默认情况下由服务器名称和实例组成。 超级服务器地址:被动节点中超级服务器的 IP 地址。在这种情况下mirrorB 。 代理端口:我们配置的被动节点服务器上安装的ISCAgent的监听端口。默认为2188 。 SSL/TLS 要求:在此示例中不可配置,我们不使用 SSL/TLS。 镜像私有地址:被动节点的 IP 地址。正如我们所见,在使用 Docker 时,我们可以使用容器名称mirrorB 。 代理地址:安装 ISCAgent 的服务器的 IP 地址。和以前一样, mirrorB 。 我们按照指示保存配置,然后返回到镜像监视器以验证我们是否已正确配置所有内容。我们可以将mirrorA中的主动节点和mirrorB中的被动节点的监视器可视化。让我们看看这两个实例之间的差异。 活动节点mirrorA上的镜像监视器: 被动节点mirrorB上的镜像监视器: 如您所见,显示的信息是相似的,基本上改变了故障转移成员的顺序。选项也不同,让我们看看其中的一些: 主动节点mirrorA : 设置无故障转移:防止在作为其中一部分的任何实例停止的情况下执行故障转移。 降级其他成员:从镜像配置中删除其他故障转移成员(在本例中为mirrorB )。 被动节点mirrorB : Stop Mirror On This Member :停止故障转移被动节点上的镜像同步。 降级为 DR 成员:将此节点从其实时同步故障转移的一部分降级为异步模式下的灾难恢复模式。 完美,我们已经配置了节点,现在让我们看看配置的最后一步。我们必须决定哪些表将成为镜像的一部分,并在两个节点上进行配置。如果您查看与本文相关的 Open Exchange 项目的 README.md,您将看到我们配置和部署了两个通常用于训练的应用程序。当我们启动 Docker 容器时,这些应用程序会自动部署,默认情况下会创建 NAMESPACES 和数据库。 第一个应用程序是COMPANY ,它允许我们保存公司记录,第二个应用程序是PHONEBOOK ,它允许我们添加与注册公司以及客户相关的个人联系人。 让我们添加一家公司: 现在让我们为之前的公司创建个人联系人: 公司数据将在COMPANY数据库中注册,联系人数据在PERSONAL中,这两个数据库都已映射,以便可以从命名空间 PHONEBOOK 访问它们。如果我们检查两个节点中的表,我们将看到在mirrorA中我们有公司和联系人的数据,但在mirrorB中仍然没有任何数据,这是合乎逻辑的。 mirrorA注册的公司: 好的,让我们继续在我们的镜像上配置数据库。为此,从我们的活动节点 ( mirrorA ),我们访问本地数据库管理屏幕(系统管理员-->配置-->系统配置-->本地数据库)并单击添加到镜像选项,我们必须选择从列表中我们要添加的所有数据库并从屏幕上读取消息: 一旦我们将数据库从主动节点添加到镜像中,我们必须对其进行备份或复制数据库文件 (IRIS.dat) 并将它们还原到被动节点上。如果您决定直接复制 IRIS.dat 文件,请记住您必须冻结要复制的数据库中的写入,您可以在文档的以下URL中查看必要的命令。在我们的例子中,没有必要暂停,因为除了我们之外没有人在写入数据。 在复制数据库文件之前,让我们从活动节点的监视器上检查镜像的状态: 让我们看看被动节点: 正如我们所看到的,从被动节点我们被告知,虽然我们在镜像中配置了 3 个数据库,但配置尚未完成。让我们继续将数据库从主动节点复制到被动节点,不要忘记我们必须卸载被动节点的数据库才能进行复制,为此我们将从管理门户访问系统配置—— >数据库并访问它们中的每一个,我们继续卸载它们。 完美的!卸载的数据库。让我们从 Visual Studio Code 访问与本文相关的项目代码,看看我们有 IRIS 安装所在的文件夹, sharedA用于mirrorA和sharedB用于mirrorB 。让我们访问 COMPANY、CUSTOMER 和 PERSONAL 数据库所在的文件夹 ( /sharedA/durable/mgr ),然后继续将镜像中每个数据库的 IRIS.dat 复制到 mirrorB 的相应目录 ( /sharedB/durable/mgr) ). 复制完成后,我们再次挂载mirrorB数据库并从mirrorB中的镜像监视器检查已配置数据库的状态: 答对了!我们的镜像已经识别出数据库,现在我们只需要激活和更新它们。为此,我们将点击Activate操作,然后点击Catchup ,这将在激活后出现。让我们看看他们是如何结束的: 完美,我们的数据库已经在镜像中正确配置,如果我们查询 COMPANY 数据库,我们应该看到我们之前从mirrorA注册的记录: 显然我们的COMPANY数据库中有我们之前在mirrorA中输入的记录,毕竟我们已经复制了整个数据库。让我们继续从mirrorA添加一个新公司,我们将其称为“另一家公司”,然后再次查询 COMPANY 数据库表: 在这里,我们完成了。我们只需要确保我们在镜像中配置的数据库对于被动节点mirrorB处于只读模式: 他们在那里!是只读模式R 。好吧,我们已经配置了镜像并同步了数据库。如果我们的产品正在运行,这不会成为问题,因为镜像会自动负责管理它们,并在主动节点出现故障时在被动节点中启动它们。 非常感谢大家走到这一步!它很长,但我希望你觉得它有用。
文章
Claire Zheng · 三月 14, 2023

医疗行业的生态创新:如何实现数据利用和应用创新

本文根据InterSystems中国技术总监乔鹏( @Peng.Qiao )的演讲“互联互通套件赋能数据利用与应用创新”整理而成。 IRIS医疗版互联互通套件的缘起与发展演进 来源HL7:正在到来的挑战 http://hl7.org/fhir/change.html 这是来自HL7官网上的一张图,描述了我们在医疗卫生行业面临的一些挑战,以及信息化建设在应对挑战中发挥的作用。当今,医疗卫生、生物学、信息技术有很强的融合趋势,加之社会变革带来的经济方面的需求,同时构成颠覆传统医疗卫生行业的因素。 这张图显示了从“被动医疗”转向“主动医疗”过程中信息的爆炸式增长,信息共享交换推动了我们对信息的利用,在这一进程中,医疗卫生信息化起着核心作用——而让信息更具价值,赋予信息标准化和互操作能力的过程,这也是InterSystems一直努力的方向,我们在国内支持大量医院实现了互联互通建设。在建设过程中,我们注意到项目的定量部分的建设成本占比是比较高的,很多的工作都花在了合规性和相关管理工具的开发上——应用标准的实施是有成本的,而对于标准的理解在各个项目上水平不尽相同,这就进一步影响了互联互通项目的建设成果。有鉴于此,2021年我们发布了InterSystems IRIS医疗版互联互通套件1.0版,初衷在于将我国的互联互通建设标准放到我们自己的技术平台里,为用户提供开箱即用的、标准合规性的基础和相应工具——这套工具包括了我们的医疗卫生信息模型、文档模型与管理、互联互通服务接口等,从而可以降低用户的实施成本和相应的实施风险。 在互联互通套件1.0版,我们提供了这样一些能力: · 在医疗卫生信息模型部分,我们提供了完整的卫生数据元与值集管理、数据集管理,并且提供了本地术语注册,以及针对于本地术语和标准术语之间的转换服务。 · 在文档部分,我们提供了电子病历文档模型和模型管理能力、电子病历文档的校验、数据源质量的分析,以及自动生成合规的互联互通文档。 · 在服务部分,我们提供了互联互通标准服务接口、互联互通标准消息模型、ESB服务总线,以及通用的服务发布/订阅功能。 我们知道,互联互通评测的目标之一就是“以评促用”,实现合规仅仅是第一步,我们希望可以为用户提供更多能力,让用户可以真正把符合互联互通标准的医院信息平台应用起来。于是我们继续发布了InterSystems IRIS医疗版互联互通套件2.0版。 传统数据中心建设面临的一个普遍问题是标准缺乏,导致数据中心的建设成本相对较高,而且复用性很差,很难形成生态。因为缺乏标准,所以数据治理、上面可以运行的数据应用,大多都是由数据厂商自己开发的,这就像早期不同的手机厂商提供的功能手机,就那么几个应用可以用,它不是智能手机,功能是非常有限的。 所以2.0版强调的是生态。在规划2.0版的时候,我们将重点放在了核心数据资产上——我们希望在2.0中建设、治理、保存核心数据资产,助力用户应用好数据资产,并且通过基于标准的开放能力,推动医疗卫生信息化的生态建设。 我们在2.0版打通了国内互联互通和FHIR两大生态,希望通过这种生态融合提升数据资产的价值。我们借助FHIR来治理数据资产,令互联互通消息和互联互通文档都能够转换成FHIR资源,直接保存在FHIR资源仓库里面。 我们使用FHIR资源模型来提供统一的行业语义,通过FHIR API,包括InterSystems提供的互联互通服务,向用户提供统一的、基于行业语义的互操作能力,这样一来,用户能够以生态的方式来扩展数据资产的利用,体现数据资产的核心价值。 我们也注意到,行业中的确已经有很多关于微服务架构的思考。在2.0版本上,我们还扩展了服务架构,新增了对新的应用开发架构——微服务架构——的支持,这对于微服务架构在行业中的落地肯定是有推动作用的。 此外,我们增加了全面的API网关能力,用户可以通过 API网关的形式,对访问互联互通的医院信息平台的这些客户端来进行流量控制、认证管理等,实现服务的全生命周期管理。 在2.0版中,我们还提供了一个完整的FHIR服务器,以打通FHIR生态与国内互联互通生态。在决策支持上,FHIR生态已经提供决策支持架构和利用机器学习的一些用例,我们也基于FHIR为决策者提供完整的、关联的、实时的决策要素,比如说通过底层互操作架构来提供整个流程闭环的基础,通过内置的机器学习、自然语言处理、商业智能等工具,为用户提供数据决策支持工具。 在平台监控方面,2.0版新增了互联互通监控指标,以及针对通用的互操作性的指标监控能力。 在实际工作中,我们注意到互联互通项目建成之后,有一些项目的建设成果并没有得到全面地利用。例如,根据最近一位北京专家的调研结果,很多用户在电子健康档案的相关项目建设上面花费了巨额经费,但是在数据调阅频次上面却“低得出人意料”——我想这是相当一部分项目的现状。 互联互通建设的成果是我们的医院信息平台,我们是以医院互联互通为标准来建设医院信息平台的,那么医院信息平台上的数据利用应该是我们的核心。 因此在3.0的版本里,我们希望以最佳实践的方式来展现如何充分利用我们的互联互通的生态,提升互联互通价值,通过向用户提供一些用例,抛砖引玉地引导用户来正确使用互联互通建设成果,真正发挥出数据的价值。我们规划了两类互联互通应用方向:一个是数据利用,另一个是应用创新。 数据利用可以有很多方向,例如数字孪生,我们可以通过更完整、更实时的数据来建立准确、实时的患者画像;例如数据编织(data fabric),我们可以把所有数据来源进行统一编织,建立一个跨数据来源的、统一的语义平台;例如在数据流通领域发挥核心价值。 在应用创新上,借助打通FHIR和互联互通发展生态,我们可以利用FHIR应用能力来建设互联互通的应用生态。例如在FHIR生态里有Smart on FHIR,这是一个全新的、即插即用的软件开发的架构;再如基于FHIR生态的CDS Hooks决策支持架构等。这些都是我们可以来参考、引进和学习的。 我借助两则近期的新闻来介绍一下如何利用互联互通建设生态来满足数据利用和应用创新。 “数据二十条”:打破数据垄断、实现数据要素价值 2022年12月19日,国务院发布了《中共中央 国务院关于构建数据基础制度更好发挥数据要素作用的意见》(以下简称“《意见》”)。 这份意见为数据流通奠定了一个政策基础。简单梳理一下这份重要的意见里面关键词可以发现其核心就是打破数据垄断、实现数据要素价值。 关键词梳理,依据《中共中央国务院关于构建数据基础制度更好发挥数据要素作用的意见》 在实现方法上,我们就要重视对数据质量标准化体系的建设,基于信息安全和标准化的数据采集,整合互联互通和互操作性,《意见》在实现方法上面提出了一些非常重要的观点,如原始数据不出域,数据可用不可见等等这样的一些关键点,都是我们未来在医疗卫生信息数据流通上的指导思想和指导原则。 那么互联互通套件如何来支持这种政策要求? 在互联互通套件2.0架构里面,我们治理后的医疗卫生信息保存在FHIR资源仓库里。FHIR这种对象模型非常适合表达医疗卫生的复杂数据模型,但是对类似数据分析上报这些传统应用来说,通常会使用基于SQL的数据分析和操作工具,如果使用FHIR资源,操作起来是很难的。此外,要让用户直接使用FHIR资源仓库,也不符合《意见》提到的“原始数据不出域”的要求。 所以互联互通套件提供了FHIR SQL构建器,这是一个图形化的工具,用户可以将FHIR资源按需求映射成不同的SQL表——例如数据上报需要用到数据的一部分内容,可能内部数据用户对于数据需求范围是比较大的,外部用户对于数据需求范围可能就会更窄一些——不管是用户需求是什么样的,都可以通过映射的方式提供相应的SQL表。这种方式既满足了数据用户,使他们能够通过熟悉的工具(SQL)获得数据和操作数据,同时也满足了“原始数据不出域”的要求。 另外需要强调的一点是,SQL映射不是创建一份数据拷贝。它并没有把FHIR资源仓库原始数据拷贝给用户,而是通过映射的方式,映射出来一份虚拟SQL表。用户可以以SQL的访问方式来操作它——通过这种模式提供的数据是实时的、完整的、按需提供的。 Epic的生态创新:以标准为基础,推动应用建设的专业化 互联互通赋能应用创新这部分,面临着很多基于标准的问题和限制。 以美国为例,美国国家卫生IT协调员办公室研究发现,只有29%的用户能够将外部来源的卫生信息和应用整合到自己的电子健康档案中,也就是说美国是没有实现“应用通”的,只有不到1/3的电子健康档案是可以使用别的厂商开发的应用的。 2022年12月还有一个非常重要的新闻,12月9日的这则新闻来自全球电子病历领头羊Epic,Epic发布了名为“Connection Hub”的平台,这个平台向所有开发者开放。 这意味着什么? 我们来看看Epic目前拥有的能力。作为一个电子病历厂商,Epic有自己的应用市场,类似于智能手机的应用市场。 截至2022年12月22日,这个应用市场里共有619个应用,这些应用都是不同厂商开发的,这些应用都可以作为一个即插即用的应用直接下载并安装在Epic的电子健康档案系统上,所以这是一个非常有意思的事情。 作为行业里最重要的电子病历厂商之一,Epic对这些应用的开发趋势,包括它持有的这种开放的、应用的生态理念,我相信会为行业应用创新带来很多价值。 回顾一下我们面临的现状,我们的数据中心是缺乏标准的,每个厂商的数据中心都是不同的数据模型,提供厂商自定义的数据接口,所以当基于这些数据来进行应用开发的时候,我们做的是低水平重复建设和无法复用的应用建设。 例如,几乎每个项目的数据中心产品里都会部署一个患者360视图或类似应用,但这个应用每个厂商、甚至每个项目都会重新开发一次,所以这是一个低水平重复建设的典型应用。而且即便觉得好,如果换一个数据中心产品,这个应用也无法直接移植过去。 在缺乏标准的情况下,这些数据中心及相关的应用建设变得非常昂贵。如果我们整个行业能够采用一个标准的语义来建设数据中心或者电子健康档案,那么通过提供基于行业统一语义的数据和基于标准的数据接口,就可以让应用开发厂商只需关注在业务,潜心开发高水平应用。而且基于生态标准的应用可以运行在任何基于标准的数据中心(或电子健康档案)上,这就为释放数据应用价值奠定了非常好的基础。 回到前面这则新闻,Epic的标准是什么?Epic采用了FHIR标准,它基于最新的R4开放了55个资源和450个FHIR API,这样一来,它就可以运行类似于“儿童生长发育曲线”这类的Smart on FHIR应用,加速应用创新。 Epic能做到的,我相信我们也可以借鉴,InterSystems IRIS医疗版互联互通套件已经提供了针对FHIR仓库的能力,并且打通了互联互通与FHIR生态。 InterSystems IRIS医疗版 互联互通套件能力 以互联互通为基础,让医院信息平台成为数字化转型的核心 我想借助这张IDC于2019年发布的“数字化转型平台”示意图作为结尾。这张图是一张概念图,描述了我们数字化转型平台的功能,以及通过无限循环的路径来摆脱传统的技术栈的思维模式。 它将数字化转型平台定义为一个加速企业数字化转型的技术架构,用来支撑我们快速创建面向市场的数字化的服务和体验的平台。同时通过它来积极推动机构内部IT环境的现代化,使它成为将数据运用于业务的智能核心——基于互联互通将数据、算法、代码和模型进行资产化管理、整理、复用和共享。 数字化转型已经颠覆了很多行业,我相信医疗卫生信息行业也不会例外。我们希望通过互联互通套件,以互联互通为基础,让医院信息平台成为医疗卫生行业数字化转型的智能核心。
文章
姚 鑫 · 三月 19, 2021

第十二章 使用嵌入式SQL(三)

# 第十二章 使用嵌入式SQL(三) # 主机变量 **主机变量是将文字值传入或传出嵌入式SQL的局部变量。** 最常见的是,主机变量用于将本地变量的值作为输入值传递给Embedded SQL,或者将SQL查询结果值作为输出主机变量传递给Embedded SQL查询。 主机变量不能用于指定SQL标识符,例如架构名称,表名称,字段名称或游标名称。主机变量不能用于指定SQL关键字。 - 输出主机变量仅在嵌入式SQL中使用。它们在`INTO`子句中指定,`INTO`子句是仅嵌入式SQL支持的SQL查询子句。 - 输入主机变量可以在嵌入式SQL或动态SQL中使用。在动态SQL中,还可以使用`“?”`向SQL语句输入文字。输入参数。这 `”?”`语法不能在Embedded SQL中使用。 在嵌入式SQL中,可以在可以使用文字值的任何位置使用输入主机变量。使用SELECT或FETCH语句的`INTO`子句指定输出主机变量。 注意:当SQL `NULL`输出到ObjectScript时,它由一个ObjectScript空字符串(`“”`)表示,该字符串的长度为零。 **要将变量或属性引用用作宿主变量,请在其前面加上一个冒号(`:`)。** 嵌入式InterSystems SQL中的主机变量可以是以下之一: - 一个或多个ObjectScript局部变量,例如:`myvar`,指定为以逗号分隔的列表。局部变量可以完全形成并且可以包含下标。像所有局部变量一样,它区分大小写,并且可以包含Unicode字母字符。 - 单个ObjectScript局部变量数组,例如:`myvars()`。局部变量数组只能从单个表(而不是联接表或视图)中接收字段值。 - 对象引用,例如:`oref.Prop`,其中`Prop`是属性名称,带有或不带有前导`%`字符。这可以是简单属性或多维数组属性,例如:`oref.Prop(1)`。它可以是一个实例变量,例如:`i%Prop`或:`i %% Data`。属性名称可以定界。例如:`Person."Home City"`.即使停用了对分隔标识符的支持,也可以使用分隔属性名称。多维属性可以包括:`i%Prop()`和:`m%Prop()`主机变量引用。对象引用主机变量可以包含任意数量的点语法级别;例如,例如,:`Person.Address.City`。 当`oref.Prop`用作过程块方法内的宿主变量时,系统会自动将`oref`变量(而不是整个`oref.Prop`引用)添加到`PublicList`并对其进行更新。 主机变量中的双引号指定文字字符串,而不是带分隔符的标识符。例如,`:request.GetValueAt("PID:SetIDPID") o`r `:request.GetValueAt("PID:PatientName(1).FamilyName")`. 主机变量应在ObjectScript过程的PublicList变量列表中列出,并使用`NEW`命令重新初始化。您可以配置InterSystems IRIS以便在注释文本中列出Embedded SQL中使用的所有主机变量。使用InterSystems SQL的注释部分对此进行了描述。 主机变量值具有以下行为: - **输入主机变量永远不会被SQL语句代码修改。即使嵌入式SQL运行后,它们仍保留其原始值。但是,输入主机变量值在提供给SQL语句代码之前会被“轻度格式化”:有效数字值将去除前导和尾随零,单个前导加号和尾随小数点。时间戳记值将除去尾随空格,以小数秒为单位的尾随零和(如果没有小数秒的话)尾随的小数点。** - **当`SQLCODE = 0`时,即返回有效行时,将设置`INTO`子句中指定的输出主机变量。如果执行`SELECT`语句或`FETCH`语句导致`SQLCODE = 100`(没有数据与查询匹配),则`INTO`子句中指定的输出主机变量将设置为`null(“”)`。如果在执行`SELECT`语句或`FETCH`语句之前未定义`INTO`变量,导致`SQLCODE = 100`,则该变量将保持未定义状态。主机变量值仅应在`SQLCODE = 0`时使用。在`DECLARE` ... `SELEC`T ... `INTO`语句中,请勿在两个`FETCH`调用之间修改`INTO`子句中的输出主机变量,因为这可能会导致不可预测的查询结果。** 在处理输出主机变量之前,必须检查`SQLCODE`值。仅当`SQLCODE = 0`时才应使用输出主机变量值。 **当在`INTO`子句中使用逗号分隔的主机变量列表时,必须指定与选择项数量相同的主机变量数量(字段,集合函数,标量函数,算术表达式,文字)。宿主变量太多或太少都会在编译时导致`SQLCODE -76`基数错误。** 在嵌入式SQL中使用`SELECT *`时,这通常是一个问题。例如,`SELECT * FROM Sample.Person`仅对以逗号分隔的15个主机变量列表有效(非隐藏列的确切数目,具体取决于表定义,该数目可能包含也可能不包含系统生成的RowID) (`ID`)列)。 因为列数可以更改,所以用单个宿主变量的`INTO`子句列表指定`SELECT *`通常不是一个好主意。使用`SELECT *`时,通常最好使用主机变量下标数组,例如: ```java /// d ##class(PHA.TEST.SQL).EmbedSQL9() ClassMethod EmbedSQL9() { NEW SQLCODE &sql(SELECT %ID,* INTO :tflds() FROM Sample.Person ) IF SQLCODEd ##class(PHA.TEST.SQL).EmbedSQL9() field 1 = 1 field 2 = 30 field 3 = 54536 field 4 = ReOrangYellow field 6 = yaoxin field 8 = 111-11-1117 field 9 = 13 field 11 = St Louis field 12 = WI field 13 = 889 Clinton Drive field 14 = 78672 field 15 = Ukiah field 16 = AL field 17 = 9619 Ash Avenue field 18 = 56589 ``` **本示例使用`%ID`返回`RowID`作为字段号1,无论`RowID`是否隐藏。** 注意,在此示例中,字段编号下标可能不是连续序列;有些字段可能被隐藏并被跳过。包含`NULL`的字段以空字符串值列出。 ** 退出嵌入式SQL后立即检查`SQLCODE`值是一种良好的编程习惯。仅当`SQLCODE = 0`时才应使用输出主机变量值。** ## 主机变量示例 在下面的ObjectScript示例中,Embedded SQL语句使用输出主机变量将名称和归属状态地址从SQL查询返回到ObjectScript: ```java /// d ##class(PHA.TEST.SQL).EmbedSQL10() ClassMethod EmbedSQL10() { &sql(SELECT Name,Home_State INTO :CName,:CAddr FROM Sample.Person) IF SQLCODEd ##class(PHA.TEST.SQL).EmbedSQL10() Name is: yaoxin State is: WI ``` 嵌入式SQL使用`INTO`子句指定主机变量`:CName`和`:CAddr`,以在局部变量`CName`中返回所选客户的姓名,并在局部变量`CAddr`中返回主目录状态。 下面的示例使用带下标的局部变量执行相同的操作: ```java /// d ##class(PHA.TEST.SQL).EmbedSQL11() ClassMethod EmbedSQL11() { &sql(SELECT Name,Home_State INTO :CInfo(1),:CInfo(2) FROM Sample.Person) IF SQLCODEd ##class(PHA.TEST.SQL).EmbedSQL11() Name is: yaoxin State is: WI ``` 这些主机变量是带有用户提供的下标(`:CInfo(1)`)的简单局部变量。但是,如果省略下标(`:CInfo()`),则InterSystems IRIS使用`SqlColumnNumber`填充主机变量下标数组,如下所述。 在下面的ObjectScript示例中,嵌入式SQL语句同时使用输入主机变量(在`WHERE`子句中)和输出主机变量(在`INTO`子句中): ```java /// d ##class(PHA.TEST.SQL).EmbedSQL12() ClassMethod EmbedSQL12() { SET minval = 10000 SET maxval = 50000 &sql(SELECT Name,Salary INTO :outname, :outsalary FROM Sample.Employee WHERE Salary > :minval AND Salary < :maxval) IF SQLCODEd ##class(PHA.TEST.SQL).EmbedSQL12() Name is: Chadwick,Phyllis L. Salary is: 16377 ``` 以下示例在输入主机变量上执行`“light normalization”`。请注意,InterSystems IRIS将输入变量值视为字符串,并且不对其进行规范化,但是Embedded SQL将此数字规范化为`65`,以在`WHERE`子句中执行相等比较: ```java /// d ##class(PHA.TEST.SQL).EmbedSQL13() ClassMethod EmbedSQL13() { SET x="+065.000" &sql(SELECT Name,Age INTO :a,:b FROM Sample.Person WHERE Age=:x) IF SQLCODEd ##class(PHA.TEST.SQL).EmbedSQL13() Input value is: +065.000 Name value is: Houseman,Martin D. Age value is: 65 ``` 在下面的ObjectScript示例中,嵌入式SQL语句使用对象属性作为宿主变量: ```sql &sql(SELECT Name, Title INTO :obj.Name, :obj.Title FROM MyApp.Employee WHERE %ID = :id ) ``` 在这种情况下,`obj`必须是对具有可变(即可以修改)属性`Name`和`Title`的对象的有效引用。请注意,如果查询包含`INTO`语句并且没有返回任何数据(即`SQLCODE`为`100`),则执行查询可能会导致修改主机变量的值。 ## 用列号下标的主机变量 如果`FROM`子句包含一个表,则可以为从该表中选择的字段指定带下标的主机变量;否则,可以为该表指定一个下标主机变量。例如,本地数组`:myvar()`。 InterSystems IRIS使用每个字段的`SqlColumnNumber`作为数字下标填充本地数组。请注意,`SqlColumnNumber`是表定义中的列号,而不是选择列表序列。 (不能将带下标的宿主变量用于视图的字段。) 主机变量数组必须是省略了最低级别下标的局部数组。因此,`:myvar()`, `:myvar(5,)`, and `:myvar(5,2,)`都是有效的主机变量下标数组。 - 主机变量下标数组可以用于`INSERT`,`UPDATE`或`INSERT` OR `UPDATE`语句`VALUES`子句中的输入。当在`INSERT`或`UPDATE`语句中使用时,主机变量数组使您可以定义在运行时而不是在编译时更新哪些列。 - 主机变量下标数组可以用于`SELECT`或`DECLARE`语句`INTO`子句中的输出。在下面的示例中显示了`SELECT`中的下标数组用法。 在下面的示例中,`SELECT`使用指定字段的值填充`Cdata`数组。 `Cdata()`的元素对应于表列定义,而不是`SELECT`元素。因此,在`Sample.Person`中,`“名称”`字段是第6列,`“年龄”`字段是第2列,`“出生日期”`(`DOB`)字段是第3列: ```java /// d ##class(PHA.TEST.SQL).EmbedSQL14() ClassMethod EmbedSQL14() { &sql(SELECT Name, Age, DOB INTO :Cdata() FROM Sample.Person) IF SQLCODEd ##class(PHA.TEST.SQL).EmbedSQL14() Name is: yaoxin Age is: 30 DOB is: 04/25/90 ``` 以下示例使用带下标的数组主机变量返回行的所有字段值: ```java /// d ##class(PHA.TEST.SQL).EmbedSQL15() ClassMethod EmbedSQL15() { &sql(SELECT * INTO :Allfields() FROM Sample.Person) IF SQLCODE d ##class(PHA.TEST.SQL).EmbedSQL16() 1 field is 1 2 field is 30 3 field is 54536 4 field is ReOrangYellow 6 field is yaoxin 8 field is 111-11-1117 9 field is 13 11 field is St Louis 12 field is WI 13 field is 889 Clinton Drive 14 field is 78672 15 field is Ukiah 16 field is AL 17 field is 9619 Ash Avenue 18 field is 56589 date & time now is 2021-03-13 16:00:40 exact age is 30.88295687885010267 ``` 请注意,非数组主机变量必须在数量和顺序上与非列`SELECT`项匹配。 将主机变量用作下标数组受以下限制: - **只有在`FROM`子句的单个表中选择字段时,才可以使用带下标的列表。这是因为从多个表中选择字段时,`SqlColumnNumber`值可能会发生冲突。** - 下标列表只能在选择表字段时使用。它不能用于表达式或聚合字段。这是因为这些选择列表项没有`SqlColumnNumber`值。 ## `NULL`和未定义的主机变量 如果指定未定义的输入主机变量,则嵌入式SQL将其值视为`NULL`。 ```java /// d ##class(PHA.TEST.SQL).EmbedSQL17() ClassMethod EmbedSQL17() { NEW x &sql(SELECT Home_State,:x INTO :a,:b FROM Sample.Person) IF SQLCODEd ##class(PHA.TEST.SQL).EmbedSQL17() Home_State的长度为: 2 x的长度是: 0 ``` SQL `NULL`等效于ObjectScript“”字符串(长度为零的字符串)。 如果将`NULL`输出到主机变量,则Embedded SQL会将其值视为ObjectScript`“”`字符串(零长度字符串)。例如,`Sample.Person`中的某些记录具有`NULL Spouse`字段。执行此查询后: ```java /// d ##class(PHA.TEST.SQL).EmbedSQL18() ClassMethod EmbedSQL18() { &sql(SELECT Name,Spouse INTO :name, :spouse FROM Sample.Person WHERE Spouse IS NULL) IF SQLCODEd ##class(PHA.TEST.SQL).EmbedSQL18() Name: xiaoli of length 6 defined: 1 Spouse: of length 0 defined: 1 ``` 宿主变量`spouse`将设置为`“”`(长度为零的字符串)以指示`NULL`值。因此,不能使用ObjectScript `$DATA`函数来确定SQL字段是否为`NULL`。当传递带有`NULL`值的SQL字段的输出主机变量时,`$DATA`返回true(定义了变量)。 在极少数情况下,表字段包含SQL零长度字符串(`''`),例如,如果应用程序将字段显式设置为SQL `''`字符串,则主机变量将包含特殊标记值`$CHAR(0 )`(长度为1的字符串,仅包含一个ASCII 0字符),它是SQL零长度字符串的ObjectScript表示形式。强烈建议不要使用SQL零长度字符串。 下面的示例比较SQL `NULL`和SQL零长度字符串输出的主机变量: ```java /// d ##class(PHA.TEST.SQL).EmbedSQL19() ClassMethod EmbedSQL19() { &sql(SELECT '',Spouse INTO :zls, :spouse FROM Sample.Person WHERE Spouse IS NULL) IF SQLCODEd ##class(PHA.TEST.SQL).EmbedSQL19() In ObjectScript ZLS is of length 1 defined: 1 NULL is of length 0 defined: 1 ``` 请注意,此主机变量`NULL`行为仅在基于服务器的查询(嵌入式SQL和动态SQL)中为true。在ODBC和JDBC中,使用ODBC或JDBC接口显式指定NULL值。 ## 主机变量的有效性 - **嵌入式SQL永远不会修改输入主机变量。** - **仅当`SQLCODE = 0`时,输出主机变量才在Embedded SQL之后可靠地有效。** 例如,以下`OutVal`的用法不可靠: ```java /// d ##class(PHA.TEST.SQL).EmbedSQL20() ClassMethod EmbedSQL20() { InvalidExample SET InVal = "1234" SET OutVal = "None" &sql(SELECT Name INTO :OutVal FROM Sample.Person WHERE %ID=:InVal) IF OutVal="None" { WRITE !,"没有数据返回" WRITE !,"SQLCODE=",SQLCODE } ELSE { WRITE !,"Name is: ",OutVal } } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).EmbedSQL20() 没有数据返回 SQLCODE=100 ``` 调用嵌入式SQL之前设置的`OutVal`的值在从嵌入式SQL返回之后不应该被IF命令引用。 相反,应该使用`SQLCODE`变量编写如下示例: ```java /// d ##class(PHA.TEST.SQL).EmbedSQL21() ClassMethod EmbedSQL21() { ValidExample SET InVal = "1234" &sql(SELECT Name INTO :OutVal FROM Sample.Person WHERE %ID=:InVal) IF SQLCODE'=0 { SET OutVal="None" IF OutVal="None" { WRITE !,"没有数据返回" WRITE !,"SQLCODE=",SQLCODE } } ELSE { WRITE !,"Name is: ",OutVal } } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).EmbedSQL21() 没有数据返回 SQLCODE=100 ``` **嵌入式SQL将`SQLCODE`变量设置为0,以指示成功地检索输出行。 `SQLCODE`值为`100`表示没有找到与`SELECT`条件匹配的行。 `SQLCODE`负数表示SQL错误条件。** ## 主机变量和程序块 如果嵌入式SQL在过程块内,则所有输入和输出主机变量必须是公共的。可以通过在过程块开始处的`PUBLIC`部分中声明它们,或用一个初始`%`字符命名它们(自动使它们公开)来完成它们。但是请注意,用户定义的`%`主机变量是自动公开的,但不是自动更新的。用户有责任根据需要对这些变量执行`NEW`。如嵌入式SQL变量中所述,某些SQL`%`变量(例如`%ROWCOUNT`,`%ROWID`和`%msg`)既自动公开又自动更新。必须将`SQLCODE`声明为`public`。 在以下过程块示例中,主机变量`zip`,`city`和`state`以及`SQLCODE`变量被声明为`PUBLIC`。 SQL系统变量`%ROWCOUNT`,`%ROWID`和`%msg`已经公开,因为它们的名称以`%`字符开头。然后,过程代码对`SQLCODE`,其他SQL系统变量和状态局部变量执行`NEW`。 ```java /// d ##class(PHA.TEST.SQL).EmbedSQL22() ClassMethod EmbedSQL22() { UpdateTest(zip,city) [SQLCODE,zip,city,state] PUBLIC { NEW SQLCODE,%ROWCOUNT,%ROWID,%msg,state SET state="MA" &sql(UPDATE Sample.Person SET Home_City = :city, Home_State = :state WHERE Home_Zip = :zip) IF SQLCODE
文章
姚 鑫 · 四月 15, 2021

第二章 定义和构建索引(三)

# 第二章 定义和构建索引(三) # 位图索引 位图索引是一种特殊类型的索引,它使用一系列位串来表示与给定索引数据值相对应的一组ID值。 位图索引具有以下重要功能: - 位图是高度压缩的:位图索引可以比标准索引小得多。这大大减少了磁盘和缓存的使用量。 - 位图操作针对事务处理进行了优化:与使用标准索引相比,可以在表中使用位图索引,而不会降低性能。 - 位图上的逻辑操作(`counting`、`AND`和`OR`)经过优化以获得高性能。 - SQL引擎包括许多可以利用位图索引的特殊优化。 位图索引的创建取决于表的唯一标识字段的性质: - 如果表的`ID`字段定义为具有正整数值的单个字段,则可以使用此`ID`字段为字段定义位图索引。此类型的表使用系统分配的唯一正整数`ID`,或使用`IdKey`定义自定义`ID`值,其中`IdKey`基于类型为`%Integer`且`MINVAL`>的单个属性,或类型`%Numeric`型且`Scale=0`且`MINVA>0`。 - 如果表的`ID`字段未定义为具有正整数值的单个字段(例如,子表),则可以定义采用正整数的`%BID`(位图`ID`)字段作为代理`ID`字段;这允许为该表中的字段创建位图索引。 受下列限制,位图索引的操作方式与标准索引相同。 索引值将被整理,可以在多个字段的组合上建立索引。 ## 位图索引操作 位图索引的工作方式如下。 假设`Person`表,其中包含一些列: ![image](/sites/default/files/inline/images/1_35.png) 此表中的每一行都有一个系统分配的`RowID`号(一组递增的整数值)。位图索引使用一组位字符串(包含1和0值的字符串)。在位串中,位的序号位置对应于索引表的`RowID`。对于给定值,假设`State`为`“NY”`,则有一个位串,每个位置对应一个包含`“NY”`的行,其他位置为0。 例如,`State`上的位图索引可能如下所示: ![image](/sites/default/files/inline/images/2_20.png) 而关于`Age` 年龄的索引可能如下所示: ![image](/sites/default/files/inline/images/3_16.png) 注:此处显示的年龄字段可以是普通数据字段,也可以是其值可以可靠派生(`Calculated` 和`SQLComputed`)的字段。 除了将位图索引用于标准操作外,SQL引擎还可以使用位图索引来使用多个索引的组合来高效地执行特殊的基于集合的操作。例如,要查找居住在纽约的24岁`Person`的所有实例,SQL引擎只需执行`Age`和`State`索引的逻辑与: ![image](/sites/default/files/inline/images/4_12.png) 生成的位图包含匹配搜索条件的所有行的集合。SQL引擎使用它从这些行返回数据。 SQL引擎可以将位图索引用于以下操作: - 对给定表上的多个条件进行`AND`运算。 - 对给定表上的多个条件进行`OR`运算。 - 给定表上的`RANGE`范围条件。 - 对给定表上的操作进行计数`COUNT`。 ## 使用类定义定义IdKey位图索引 如果表的`ID`是值限制为唯一正整数的字段,则可以使用新建索引向导或通过与创建标准索引相同的方式编辑类定义的文本,将位图索引定义添加到类定义中。唯一的区别是需要将索引类型指定为“位图”: ```java Class MyApp.SalesPerson Extends %Persistent [DdlAllowed] { Property Name As %String; Property Region As %Integer; Index RegionIDX On Region [Type = bitmap]; } ``` ## 使用类定义定义`%BID`位图索引 如果表的`ID`不限于正整数,则可以创建`%BID`属性以用于创建位图索引定义。可以将此选项用于具有任何数据类型的`ID`字段的表,以及由多个字段组成的`IDKEY`(包括子表)。可以为以下任一数据存储类型创建`%BID`位图:默认结构表或`%Storage.SQL`表。此功能称为“任意表的位图”或`BAT`。 要在这样的表上启用位图索引,必须执行以下操作: 1. 为类定义`%BID`属性/字段。这可以是类的现有属性,也可以是新属性。它可以有任何名称。如果这是新属性,则必须为表中的所有现有行填充此属性/字段。此`%BID`字段必须定义为将字段数据值限制为唯一正整数的数据类型。例如,将`MyBID`属性设置为`%Counter`; 2. 定义新的类参数以定义哪个属性是`%BID`字段。此参数被命名为`BIDField`。此参数设置为`%BID`属性的`SQLFieldName`。例如,参数`BIDField=“MyBID”`; 3. 定义`%BID`的索引。例如,`MyBID`上的`Index BIDIdx[Type=Key,Unique]`; 4. 定义`%BID`定位器索引。 这将`%BID`索引绑定到表的`ID`键字段。 下面的例子是一个表的一个复合`IDKey`组成两个字段: ```java Index IDIdx On (IDfield1, IDfield2) [ IdKey, Unique ]; Index BIDLocIdx On (IDfield1, IDfield2, MyBID) [ Data = IdKey, Unique ]; ``` 此表现在支持位图索引。可以使用标准语法根据需要定义位图索引。例如: `Index RegionIDX On Region [Type = bitmap]`; 此表现在还支持位片索引。可以使用标准语法定义位片索引。 **注意:要构建或重新生成`%BID`位图索引,必须使用`%BuildIndices()`。`%BID`位图索引不支持`%ConstructIndicesParallel()`方法。** ## 使用DDL定义位图索引 如果使用DDL语句定义表,还可以使用以下DDL命令为`ID`为正整数的表格创建和删除位图索引: ```sql CREATE BITMAP INDEX RegionIDX ON TABLE MyApp.SalesPerson (Region) ``` ## 生成位图范围索引 **编译包含位图索引的类时,如果类中存在任何位图索引,并且没有为该类定义位图范围索引,则类编译器会生成位图范围索引。如果位图范围索引存在(无论是定义的还是生成的),该类从主超类继承位图范围索引。为类构建索引时,如果要求构建位图范围索引,或者正在构建另一个位图索引并且位图范围索引结构为空,则会构建位图范围索引。** 除非存在位图索引,否则InterSystems IRIS不会生成位图范围索引。位图范围索引定义为:`type = bitmap`, `extent = true`。这意味着从主要超类继承的位图范围索引被认为是位图索引,并且如果在该子类中没有显式定义位图范围索引,则将触发在子类中生成位图范围索引。 InterSystems IRIS不会基于未来的可能性在超类中生成位图范围索引。这意味着,除非存在`type=bitmap`的索引,否则InterSystems IRIS永远不会在持久类中生成位图范围索引。假设将来的某个子类可能引入`type=bitmap`的索引是不够的。 **注意:在将位图索引添加到生产系统上的类的过程中需要特别小心(在生产系统中,用户正在使用特定的类,编译所述类,然后为其构建位图索引结构)。在这样的系统上,位图范围索引可以在编译完成和索引构建进行之间的过渡期间被填充。这可能导致索引构建过程未隐式构建位图范围索引,这导致部分完整的位图范围索引。** ## 选择索引类型 **下面是在位图和标准索引之间选择的一般准则。 一般来说,所有类型的键和引用都要使用标准索引:** - Primary key - Foreign key - Unique keys - Relationships - Simple object references 否则,位图索引通常更可取(假设表使用系统分配的数字ID号)。 其他因素: - **每个属性上的单独位图索引通常比多个属性上的位图索引具有更好的性能。这是因为SQL引擎可以使用`AND`和`OR`操作有效地组合单独的位图索引。** - **如果一个属性(或确实需要一起编制索引的一组属性)有超过`10,000-20,000`个不同的值(或值组合),请考虑标准索引。但是,如果这些值的分布非常不均匀,以至于很少的值只占行的很大一部分,那么位图索引可能会更好。一般来说,目标是减少索引所需的总体大小。** ## 位图索引的限制 所有位图索引都有以下限制: - 不能在唯一列上定义位图索引。 - 不能在位图索引中存储数据值。 - 除非字段的`SqlCategory`是 `INTEGER`, `DATE`, `POSIXTIME`, or `NUMERIC(scale=0)`,否则不能在字段上定义位图索引。 - **对于包含超过100万条记录的表,当惟一值的数量超过`10,000`时,位图索引的效率低于标准索引。 因此,对于大型表,建议避免为任何包含(或可能包含)超过`10,000`个惟一值的字段使用位图索引; 对于任意大小的表,避免对任何可能包含超过`20,000`个惟一值的字段使用位图索引。 这些是一般的近似值,不是确切的数字。** 必须创建一个`%BID`属性来支持一个表上的位图索引: - 使用非整数字段作为唯一的`ID`键。 - 使用一个多字段`ID`键。 - 是父子关系中的子表。 可以使用`$SYSTEM.SQL.Util.SetOption()`方法`SET status=$SYSTEM.SQL.Util.SetOption("BitmapFriendlyCheck",1,.oldval) `设置系统范围的配置参数,以便在编译时检查此限制,从而确定`%Storage.SQL`类中是否允许定义的位图索引。此检查仅适用于使用`%Storage.SQL`的类。默认值为0可以使用`$SYSTEM.SQL.Util.GetOption(“BitmapFriendlyCheck”)`来确定此选项的当前配置。 ### 应用程序逻辑限制 位图结构可以由位串数组表示,其中数组的每个元素表示具有固定位数的`"chunk"`。因为`UNDEFINED`等同于一个全为0位的块,所以该数组可以是稀疏的。表示全部0位的块的数组元素根本不需要存在。因此,应用程序逻辑应该避免依赖于0值位的`$BITCOUNT(str,0)`计数。 由于位串包含内部格式,因此应用程序逻辑不应依赖于位串的物理长度,也不应依赖于将具有相同位值的两个位串相等。在回滚操作之后,位串恢复到事务之前的位值。然而,由于内部格式化,回滚的位串可能不等于或不具有与事务之前的位串相同的物理长度。 ## 维护位图索引 **在易失性表(执行许多插入和删除操作)中,位图索引的存储效率可能会逐渐降低。要维护位图索引,可以运行`%SYS.Maint.Bitmap`实用程序方法来压缩位图索引,使其恢复到最佳效率。可以使用`OneClass()`方法压缩单个类的位图索引。或者,可以使用`Namespace()`方法来压缩整个命名空间的位图索引。这些维护方法可以在带电系统上运行。** 运行`%SYS.Maint.Bitmap`实用程序方法的结果将写入调用该方法的进程。这些结果还会写入`%SYS.Maint.BitmapResults`类。 ## 位图块的SQL操作 InterSystems SQL提供了以下扩展来直接操作位图索引: - `%CHUNK`函数 - `%Bitpos`函数 - `%BITMAP`聚合函数 - `%BITMAPCHUNK`聚合函数 - `%SETINCHUNK`谓词条件 所有这些扩展都遵循InterSystems SQL位图表示约定,将一组正整数表示为一系列位图块,每个块最多包含`64,000个`整数。 这些扩展允许在查询和嵌入式SQL中更轻松、更高效地操作某些条件和筛选器。在嵌入式SQL中,它们支持位图的简单输入和输出,特别是在单个块级别。它们支持处理完整的位图,这些位图由`%bitmap()`和`%SQL.Bitmap`类处理。它们还支持非`RowID`值的位图处理,例如外键值、子表的父引用、关联的任一列等。 例如,要输出指定块的位图,请执行以下操作: ```sql SELECT %BITMAPCHUNK(Home_Zip) FROM Sample.Person WHERE %CHUNK(Home_Zip)=2 ``` 要输出整个表的所有块,请执行以下操作: ```sql SELECT %CHUNK(Home_Zip),%BITMAPCHUNK(Home_Zip) FROM Sample.Person GROUP BY %CHUNK(Home_Zip) ORDER BY 1 ``` ### %CHUNK函数 `%%CHUNK(F)`返回位图索引字段f值的块分配。这被计算为`f\64000+1.%%CHUNK(F)`非位图索引字段的任何字段或值`f`的`%chunk(F)`始终返回1。 ### %BITPOS函数 `%Bitpos(F)`返回分配给其区块内的位图索引字段`f`值的位位置。这被计算为`f#64000+1`。对于不是位图索引字段的任何字段或值`f`,`%Bitpos(F)`返回的值比其整数值多`1`。字符串的整数值为`0`。 ### %BITMAP聚合函数 聚合函数`%bitmap(F)`将许多`f`值组合到一个`%SQL.Bitmap`对象中,在该对象中,对于结果集中的每个值`f`,与适当块中的`f`相对应的位被设置为`1`。上述所有参数中的f通常是正整数字段(或表达式),通常(但不一定)是`RowID`。 ### %BITMAPCHUNK聚合函数 聚合函数`%BITMAPCHUNK(F)`将字段f的许多值组合成`64,000`位的InterSystems SQL标准位图字符串,其中对于集合中的每个值`f`,位`f#64000+1=%Bitpos(F)`被设置为`1`。请注意,无论`%chunk(F)`的值是多少,都会在结果中设置该位。`%BITMAPCHUNK()`为空集生成`NULL`,并且与任何其他聚合一样,它忽略输入中的`NULL`值。 ### %SETINCHUNK谓词条件 当且仅当($BIT(BM,`%Bitpos(F)=1`时,条件(`f%SETINCHUNK BM`)为真。Bm可以是任何位图表达式字符串,例如输入主机变量:`bm`,或`%BITMAPCHUNK()`聚合函数的结果,等等。请注意,无论`%chunk(F)`的值是多少,都会检查`` 位。如果`` 不是位图或为`NULL`,则条件返回`FALSE`。(`F%SETINCHUNK NULL`)生成`FALSE`(非未知)。
文章
Qiao Peng · 六月 8, 2022

数字孪生医院:一个围绕着智慧医院决策的建模、管理和行动闭环的全新方式

01 智慧医院的本质 什么是智慧医院? 国家卫健委提出建设智慧医院的目的是要不断增强人民群众的获得感,要求医院的流程更便捷、服务更高效、管理更精细,主要聚焦于三大领域,面向医务人员的智慧医疗、面向患者的智慧服务以及面向管理的智慧管理。 如果从技术角度进行高度抽象,我们把智慧医院的本质浓缩为一点,就是为智慧的科学决策以及基于科学决策的业务执行闭环。在现实世界里,我们要从真实业务(调查研究)中获得决策依据,同时还要有决策的方法,把决策应用在现实世界上,来影响和改变现实世界。 02 科学决策的三个阶段 第一个时代是人工决策时代。我们医疗行业就是望闻问切,我们能拿到的数据非常有限,所以属于一个数据饥渴的时代。我们通过有限的数据来进行决策,非常容易落入我们经常批判的“三拍”型决策,“拍脑袋决策、拍胸脯保证、拍屁股走人”。显然这个决策效率不是很高。 人工决策时代:“数据稀缺”时代的决策 第二个时代是信息化时代。我们有了很多数据,能够把部分现实世界来进行数字化表达,我们也通过信息辅助人工决策进行智能决策,例如通过药品知识库推动合理用药,通过人工智辅助影像判断等等。 然而我们依旧面临着很多数据问题。首先是数据还不够完整,经常无法获取需要的数据,数据模型也不能完全表达医院的实际业务;其次,我们也还是会遇到很多数据来源问题(我们常常不知道该信任哪一份数据)、数据的关联性、实时性等问题。系统多了、数据多了,但同时孤岛也多了、噪音也多了,如何通过技术的手段让真实世界在数字化的世界里面完整、客观表达,这是实现数字化医院最基础的工作。 此外,我们仍然没有解决人工决策的局限性,例如决策所依赖的依据,我们的大脑能够处理的依据差不多十个左右,但是现在医学发展也有很多的数据,比如有临床数据、有功能遗传学、有蛋白质组学的等等,我们面对着上千个决策因素,但受制于人类自身的限制,我们也很难直观地理解和认识这些数据并发挥其价值。 人工决策的局限 数据来源:Evidence-Based Medicine and the Changing Nature of Healthcare: 2007 IOM Annual Meeting Summary 在数字化时代,决策模式从信息辅助决策升级为人工干预的智能决策。数字化时代的决策有四个要素。 第一是全面的数据表达,我们要把真实世界的所有数据能够挖掘和展现出来,并将其沉淀在数据平台。数据的全面性包含了数据广度、数据深度、数据时效性、数据的关联性、数据的动态变化等等。 第二是数字化的逻辑推理能力,基于知识图谱和基于机器学习来把逻辑推理过程实现数字化,比如深度学习、神经网络等等,都是在试图将我们的逻辑推理进行数字化表现。 第三是流程与场景数字化,我们现在已经有了临床辅助决策,为什么在现实业务中还会遇到例如应该报警的没报警,不该报警的反而误报警等现象,主要原因是现在决策流程都是在各个业务系统内通过人工环节里面来体现的,使得我们并不了解实际业务中的决策流程,所以如果想要真正了解我们的决策是不是正确的、及时的,以及闭环地应用在业务里面,我们需要对业务的流程和场景进行数字化。 第四,对决策本身的绩效评价数字化。我们怎么知道决策是好的还是坏的,是最优的还是次优的?我们应该如何优化我们的决策,让决策能够变得越来越好?我们需要对决策进行可度量的数字化评价,评价的模型、指标,决策的效果等,对应这些我们也需要进行数字化。有了这些评价的结果,我们才能依据这些结果进行推理逻辑的改进,让决策更好。 数字化时代决策的四要素: 全面的数据、逻辑推理数字化、流程与场景数字化、绩效评价数字化 03 数字孪生——智慧医院的未来 这时我们就不难看出为什么数字孪生是智慧医院的未来方向了。 根据Gartner 对数字孪生的定义:“现实世界实体或系统的数字表达。数字孪生体的实现是一套封装的软件对象或模型,它反映了一个独特的物理对象、过程、组织、人或其他抽象概念。” 数字孪生作为现实世界全面的数字化表达,不是为了表达而表达,而是通过全面表达来实现决策方法、决策流程和决策评价的全面数字化。数字孪生是一个能够全面反映真实医院,并且跟实际业务紧密集成、互相打通的数字化世界,在数字孪生世界里很多决策可以由它来产生,不需要由真实世界来产生,数字孪生世界里的决策可以真实地进行模拟、验证、演练,从而最大程度降低我们的试错成本,同时还可以反馈给真实世界,形成一个统一的、闭环的数字——真实一体化的世界。所以数字孪生并不仅仅是一个简单的真实世界的镜像,而是一个更复杂的、跟现实世界有互通性、互操作性的数字世界。 数字孪生:现实+决策+流程+评价的全面数字化 04 InterSystems IRIS医疗版实现数字孪生医院的最佳数智底座 数字孪生的世界里我们有什么样的平台和技术来支撑它?对于数字孪生的智慧医院来说,我们认为应该有一个功能强大的数智基座能满足前面提到数字孪生时代决策的四个关键要素。 第一是数据的全面性。面对复杂的真实世界,我们需要多种数据建模方式,类似思维导图,对某一事物进行N维建模,同时可以随着我们的认知加深而不断扩展的建模方式,而不是简单的关系型二维表来描述我们的实际业务。同时这些数据是实时的、有关联性的,只有这样我们做的决策才能够发现或者反应事物的本质,才有价值。 第二是流程和场景的数字化。现在的流程大部分都是分散在各个业务系统和人工工作里面,相互割裂、理解起来很困难,合理性、是否需要优化、如何优化更是无从谈起。在数字孪生时代,我们需要对于整个流程来建立可视化的、直观的并且能够优化的流程,通过流程建模来建模,同时建立这个流程模型,在数字化的世界里是闭环的,也就是说可以把决策的结果实时反馈给真正在运营的流程,并且反馈给我们真实世界。这个流程是可以优化的,可以不断进行业务流程的再造。 第三,要实现逻辑推理的数字化。我们要借助机器学习、深度学习等技术来提升我们决策的科学水平,对于很多行业里面的非结构化的数据,我们借助自然语言处理来挖掘真实的非结构化数据真实的含义,当然还有许多通过知识图谱认知来分析来提升整体决策的能力。 最后,从决策的评价和改进层面来说,基于行业的模型行业的标准对于决策进行相应评价,通过数据深度的分析对于评价指标进行相应的计算,并且把结果实时反馈给业务流程,通过推理和逻辑来优化决策,提升决策水平。 以上这四个能力就是未来数字孪生智慧医院数智基座应该具有的能力。 InterSystems IRIS医疗版数据平台正是这样一个平台,可以帮助用户从各种数据源收集和编辑相应的数据,并且能够对真实世界使用不同的模型,真实反映真实世界全貌的数据模型,里面包含了我们很多方面的数据,例如说临床的、组学的、物联网的、社交的、健康管理等等数据。同时InterSystems IRIS医疗版数据平台提供完整的互操作平台,可以帮助我们业务流程进行可视化,实现可视化流程的建模——这种方式可以支撑我们对于这个流程的实时优化,同时通过实时数据分析把实时洞察反馈给业务流程,支撑用户做更智能的决策。 通过开放型分析,基于全面的数据,我们可以应用机器学习、自然语言处理、知识图谱等等对数据进行分析、决策和相应决策的评价来驱动业务持续改进。 InterSystems IRIS医疗版数据平台:数字孪生医院最佳数智基座 数字化时代,基于真实业务数据的决策智能和基于合理决策的业务执行闭环是实现智慧医院的核心,而数字孪生开启了一个全新的围绕着智慧决策的建模、管理行动的全新方式,能够帮助医院完整认知和理解医院业务,提高决策质量和效率、优化资源利用,从而提高医疗质量、优化患者体验以及降低运营成本,更重要的是可以支持医院实现可持续的高质量发展和数字化创新能力。 InterSystems IRIS数据平台作为全美排名前20医院、复旦百强中40%的医院以及全国数百家医院的共同选择,毫无疑问是实现数字孪生医院数智底座的最佳选择。
文章
Jingwei Wang · 五月 4, 2022

IntegratedML 实践实验室

你是否尝试过[InterSystems IRIS IntegratedML学习平台](https://learning.intersystems.com/course/view.php?id=1535&ssoPass=1)?在这个平台中,你可以在再入院数据集上训练和测试一个模型,并能够预测一个病人何时会再入院,或计算其再入院的概率。 你不需要在你的系统上进行任何安装就可以尝试,你所要做的就是启动一个虚拟实验室环境(Zeppelin),然后玩一玩! 在这篇文章中,我们将利用这个实验室向你简要介绍IntegratedML,向你介绍要处理的问题,如何使用IntegratedML来创建一个再入院预测模型,以及如何分析其性能指标的一些见解。 ## 什么是IntegratedML? ![IntegratedML infographic](https://user-images.githubusercontent.com/8899513/85149599-7848f900-b21f-11ea-9b65-b5d703752de3.PNG) 来源: https://github.com/intersystems-community/integratedml-demo-template 在开始本教程之前,让我们简单谈谈IRIS IntegratedML。这个工具使你能够直接在SQL语句中执行机器学习(ML)任务,抽象出复杂过程的实现,例如,选择哪些列和ML算法是对目标列进行分类或回归的最佳选择。 IntegratedML的另一个伟大功能是易于部署。一旦你的模型被训练并表现良好,你只需要运行SQL语句,以便让你的模型投入生产。 IntegratedML让你选择使用哪个 [ML提供者](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_Configuration_Providers)。默认的提供者是 [AutoML](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_AUTOML), 这是InterSystems公司使用Python实现的ML库 [scikit-learn](https://scikit-learn.org/stable/). 但是你也可以选择其他三个提供者: PMML, [H2O](https://www.h2o.ai/) and [DataRobot](https://www.datarobot.com/). 在这篇文章中,我们将使用AutoML。 ## 问题和解决方案 现在,让我们来介绍一下这个问题,以及我们如何努力提出一种方法来尽量减少其影响。我们将尝试减少由于再入院而产生的问题。 [根据维基百科](https://en.wikipedia.org/wiki/Hospital_readmission), 医院再入院是指病人被批准离开医院(出院),但此人在一个意想不到的短时间间隔内再次回到医院(再入院)的事件。 ![Hospital readmission inforgraphic](https://news.yale.edu/sites/default/files/styles/horizontal_image/public/d6_files/Hospital_v03_YNews.jpg) 来源: [https://news.yale.edu/2015/01/15/when-used-effectively-discharge-summari...](https://news.yale.edu/2015/01/15/when-used-effectively-discharge-summaries-reduce-hospital-readmissions) 再入院会造成病人护理质量的损失--出院和再入院之间的时间可能很关键,医院的资源也会优化。 克服这个问题的一个方法是尝试使用历史数据库来创建一个数据集,其中过去的再入院事件可以通过机器学习算法进行分析,创建一个ML模型。如果数据集足够丰富和干净,再入院模式可以被正确检测出来,新的事件可以通过这个模型计算出其概率。 因此,通过使用ML模型来预测再入院,能够避免错误的出院,肯定会成为医院提高服务质量和利润的一个有价值的选择。 ## 使用IntegratedML创建和使用一个ML模型 ### 模型创建 使用IntegratedML创建一个ML模型,就像执行一个SQL语句一样容易。你只需要定义数据所在的历史数据集,并为你的模型起一个名字。 ``` CREATE MODEL Readmission PREDICTING (MxWillReAdmit) FROM EncountersHistory ``` After that statement, you have declared and created a model called Readmission intended to predict the values for a column called MxWillReAdmit, based on a dataset called EncountersHistory. You can find more information about this statement [here](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_CREATEMODEL). For example, in design phase, it’s handy forces the same results on training, so you can use the USING argument with some arbitrary constant number, like this: 在该声明之后,你已经声明并创建了一个名为Readmission的模型,基于名为EncountersHistory的数据集,预测名为MxWillReAdmit的列的值。 你可以找到关于这个声明的更多信息,[点击这里](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_CREATEMODEL)。例如,在设计阶段,很方便地迫使训练中出现相同的结果,所以你可以用USING参数加上一些任意的常数,像这样。 ``` CREATE MODEL Readmission PREDICTING (MxWillReAdmit) FROM EncountersHistory USING {"seed": 3} ``` ### 模型训练 现在,通过使用TRAIN MODEL语句,你的模型已经准备好被训练。 ``` TRAIN MODEL Readmission ``` 在这里,IntegratedML为你做了很多工作(使用AutoML ML供应商): - [Feature engineering](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_AutoML#GIML_AutoML_Feature): 使用哪些列 - [Data encoding](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_AutoML#GIML_AutoML_Keys): 所选栏目中的数据必须如何呈现给ML算法 - [ML algorithm selection](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_AutoML#GIML_AutoML_Algos): 哪种算法能带来最好的结果 - [Model selection](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_AutoML#GIML_AutoML_Selection): 根据目标列的数据类型,应该选择一个分类模型或回归模型。在这个例子中,一旦目标列有布尔值,就会选择一个分类模型。 这个声明可能需要一些时间,取决于数据集的大小和你的数据的复杂性。 ### 模型验证 到现在,我们应该急于知道我们的模型到底有多好。Integrated ML有一个计算性能指标的声明,基于另一个数据集,而不是用于训练的数据集 这个新的数据集被称为测试或验证数据集,这取决于你使用哪种策略进行验证。这个数据集通常是从用于创建训练数据集的同一个数据集中检索出来的。一个常见的方法是随机选择70%或80%的数据集用于训练,其余的用于测试/验证。 在实验室里,之前已经为这项任务准备了一个新的可使用的数据集:EncountersNew数据集。 现在,我们可以找出我们的模型有多好(或多坏)。 ``` VALIDATE MODEL Readmission FROM EncountersNew ``` 为了得到结果,你应该查询 INFORMATION_SCHEMA.ML_VALIDATION_METRICS 表。 ``` SELECT * FROM INFORMATION_SCHEMA.ML_VALIDATION_METRICS ``` 模型的性能是用 [IntegratedML的四个指标](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_VALIDATEMODEL#GIML_VALIDATEMODEL_Metrics): - Accuracy: 预测正确率(数值接近1表示正确率高)。 - Precision:对模型所做的所有阳性预测的正确率(接近1的值意味着很少有假阳性预测)。 - Recall:关于数据集中所有实际阳性值的正确阳性预测率(接近1的值意味着很少有假阴性预测)。 - F-Measure: 另一种衡量准确率的方法,当准确率表现不佳时使用,一般用于不平衡问题(数值接近1意味着高正确率)。 ### 关于性能指标的一些讨论 在这里,我们可以看到,该模型的准确率为82%。但是,这个指标不应该被单独分析,其他的指标如精确度和召回率也必须被评估。 让我们思考一下错误预测的影响--假阳性和假阴性。对于我们的模型来说,假阳性意味着预测的再入院并不是实际的再入院。而假阴性,意味着一个病人已经再入院,而模型预测这个病人不会再入院。 这两种情况都会导致错误的决定。一个假阳性的预测可能会导致一个超过必要的病人留在医院的决定;而一个假阴性的预测可能会导致一个提前出院的决定,然后再让这个病人重新入院。 请注意,在我们的模型中,假阴性会导致再入院病例,这正是我们要避免的。所以,我们必须选择能减少假阴性病例的模型,即使假阳性病例的数量增加。 因此,为了选择具有低假阴性率的模型,我们需要选择具有高召回率的模型。这是因为召回率越高,假阴性就越低。 由于我们的模型有84%的召回率,让我们暂时假定它是一个合理的值。 ### 预测 一旦你训练好了你的模型,而且它的性能可以接受,现在你就可以执行这个模型,以便预测结果。 你应该使用 [PREDICT](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_PREDICT) 函数,以便用户对模型进行预测。 ``` SELECT TOP 100 ID, PREDICT(Readmission) AS PredictedReadmission, MxWillReAdmit AS ActualReadmission FROM EncountersNew ``` 这个函数根据内部阈值,评估模型的目标列的行是真值还是假值的概率。 你可以看到我们的模型做了一些错误的预测--这很正常。 另一个可以用来进行预测的函数,是 [PROBABILITY](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_PROBABILITY) 函数. ``` SELECT TOP 10 ID, PROBABILITY(Readmission FOR '1') AS ReadmissionProbability, PREDICT(Readmission) AS PredictedReadmission, MxWillReAdmit AS ActualReadmission FROM EncountersNew ``` 与PREDICTION相同,PROBABILITY使用一行的列作为指定模型的输入,但在这里我们需要定义我们想要计算的概率是哪一类。 如果我们想定制用于预测的阈值,这个函数可能很有用。 ### 查询训练好的模型 在创建和训练你的模型后,你可以使用 INFORMATION_SCHEMA.ML_TRAINED_MODELS 表来查询它们。 ``` SELECT * FROM INFORMATION_SCHEMA.ML_TRAINED_MODELS ``` ## 结论 在这篇文章中,我们对历史上的病人再入院数据集进行了创建、训练、验证和预测。这些预测可以作为寻求更好决策的另一个工具,从而改善对病人的护理,降低医院服务的成本。 所有这些步骤都是通过InterSystems IntegratedML使用SQL语言完成的,它为广大的开发者带来了机器学习的力量。
文章
Hao Ma · 三月 25, 2021

为什么 COVID-19 对机器学习也有危险? (Part II)

继[上一部分](https://community.intersystems.com/post/why-covid-19-also-dangerous-machine-learning-part-i),现在要利用 IntegratedML VALIDATION MODEL 语句提供信息以监视您的 ML 模型。 您可以在[此处](https://www.youtube.com/watch?v=q9ORM32zPjs)观看实际运作。 此处所示代码衍生自 [InterSystems IntegragedML 模板](https://openexchange.intersystems.com/package/integratedml-demo-template)或 [IRIS 文档](https://irisdocs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GCM_healthmon)提供的示例,我主要是把代码混合了起来。 这是一个简单的示例,目的是为进一步讨论和未来工作提供一个起点。 注:此处提供的代码仅作说明之用。 如果您想尝试,我开发了一个 Open Exchange 技术示例应用 ([iris-integratedml-monitor-example](https://openexchange.intersystems.com/package/iris-integratedml-monitor-example)),并将其提交到 InterSystems IRIS AI Contest。 读完这篇文章后您可以去看看,如果喜欢,就请[投我一票吧](https://openexchange.intersystems.com/contest/current)! :) # 目录 ### 第一部分: * [IRIS IntegratedML 和 ML 系统](https://community.intersystems.com/post/why-covid-19-also-dangerous-machine-learning-part-i#iris_integratedml_and_ml_systems) * [新旧常态之间](https://community.intersystems.com/post/why-covid-19-also-dangerous-machine-learning-part-i#between_the_old_and_new_normal) ### 第二部分: * [监视 ML 性能](#monitoring_ml_performance) * [简单用例](#a_simple_use_case) * [未来工作](#future_works) # 监视 ML 性能 要监视 ML 模型,至少需要两个功能: 1) 性能指标提供程序 2) 监视和通知服务 幸运的是,IRIS 为我们提供了这两个必要的功能。 ## 获取 ML 模型性能指标 如[上一部分](https://community.intersystems.com/post/why-covid-19-also-dangerous-machine-learning-part-i)所示,IntegratedML 提供了 VALIDATE MODEL 语句来计算以下性能参数: * 准确率:模型的好坏(值接近 1 表示正确答案率高) * 精度:模型处理误报的能力如何(值接近 1 表示**无**误报率高) * 召回率:模型处理漏报的能力如何(值接近 1 表示**无**漏报率高) * F 度量:另一种测量准确率的方法,用于准确率表现不佳的情况(值接近 1 表示正确答案率高) 注:这些定义并不是正式的,而且非常浅显! 我推荐您花些时间[了解它们](https://medium.com/analytics-vidhya/accuracy-vs-f1-score-6258237beca2)。 最妙的是,每次调用 VALIDATE MODEL 时,IntegrateML 都会存储它的性能指标,这样的功能可以很好地用于监视。 ## 监视引擎 InterSystems IRIS 提供 System Monitor 框架用于处理监视任务。 它还允许您定义自定义规则,以根据这些指标上应用的谓词触发通知。 默认提供磁盘、内存、进程、网络等一系列指标。 此外,System Monitor 还可以让您扩展监视器,覆盖无限的可能性。 这样的自定义监视器在系统监视器术语中称为应用监视器。 您可以在[此处](https://irisdocs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GCM_healthmon)了解有关 System Monitor 的更多信息。 ## 整合 现在,有了一种获取各模型验证性能指标值的方法,还有一个可以根据应用于自定义指标源的自定义规则触发警报的工具.……那么,是时候把它们结合起来了。 首先,我们需要通过扩展 %Monitor.Abstract 类创建自定义应用监视器类,并实现 *Initialize* 和 *GetSample* 方法。 ``` Class MyMetric.IntegratedMLModelsValidation Extends %Monitor.Adaptor { /// Initialize the list of models validation metrics. Method Initialize() As %Status { Return $$$OK } /// Get routine metric sample. /// A return code of $$$OK indicates there is a new sample instance. /// Any other return code indicates there is no sample instance. Method GetSample() As %Status { Return $$$OK } } ``` 系统监视器会定期发出调用以监视类,获取一组称为样本的指标。 这样的样本可以仅用于监视,也可用于检查是否必须提高警报规则。 您可以通过在监视器类中定义标准的非内部属性来定义此类样本的结构。 需要注意的是,必须在参数 INDEX 中指定其中一个属性作为每个样本的主键,否则将抛出键重复错误。 ``` Class MyMetric.IntegratedMLModelsValidation1 Extends %Monitor.Adaptor { Parameter INDEX = "ModelTrainedName"; /// Name of the model definition Property ModelName As %Monitor.String; /// Name of the trained model being validated Property ModelTrainedName As %Monitor.String; /// Validation error (if encountered) Property StatusCode As %Monitor.String; /// Precision Property ModelMetricPrecision As %Monitor.Numeric; /// Recall Property ModelMetricRecall As %Monitor.Numeric; /// F-Measure Property ModelMetricFMeasure As %Monitor.Numeric; /// Accuracy Property ModelMetricAccuracy As %Monitor.Numeric; ... } ``` *Initialize* 方法在每次监视器调用时被调用一次,*GetSample* 方法则被调用到返回 0 为止。 因此,我们可以在 IntegrateML 验证历史上设置 SQL,向监视器提供指标信息,实现 *Initialize* 和*GetSample* 方法: ``` /// Initialize the list of models validation metrics. Method Initialize() As %Status { // Get the latest validation for each model validated by VALIDATION MODEL statement Set sql = "SELECT MODEL_NAME, TRAINED_MODEL_NAME, STATUS_CODE, %DLIST(pair) AS METRICS_LIST FROM ("_ "SELECT m.*, $LISTBUILD(m.METRIC_NAME, m.METRIC_VALUE) pair, r.STATUS_CODE "_ "FROM INFORMATION_SCHEMA.ML_VALIDATION_RUNS r "_ "JOIN INFORMATION_SCHEMA.ML_VALIDATION_METRICS m "_ "ON m.MODEL_NAME = r.MODEL_NAME "_ "AND m.TRAINED_MODEL_NAME = r.TRAINED_MODEL_NAME "_ "AND m.VALIDATION_RUN_NAME = r.VALIDATION_RUN_NAME "_ "GROUP BY m.MODEL_NAME, m.METRIC_NAME "_ "HAVING r.COMPLETED_TIMESTAMP = MAX(r.COMPLETED_TIMESTAMP)"_ ") "_ "GROUP BY MODEL_NAME" Set stmt = ##class(%SQL.Statement).%New() $$$THROWONERROR(status, stmt.%Prepare(sql)) Set ..Rspec = stmt.%Execute() Return $$$OK } /// Get routine metric sample. /// A return code of $$$OK indicates there is a new sample instance. /// Any other return code indicates there is no sample instance. Method GetSample() As %Status { Set stat = ..Rspec.%Next(.sc) $$$THROWONERROR(sc, sc) // Quit if we have done all the datasets If 'stat { Quit 0 } // populate this instance Set ..ModelName = ..Rspec.%Get("MODEL_NAME") Set ..ModelTrainedName = ..Rspec.%Get("TRAINED_MODEL_NAME")_" ["_$zdt($zts,3)_"]" Set ..StatusCode = ..Rspec.%Get("STATUS_CODE") Set metricsList = ..Rspec.%Get("METRICS_LIST") Set len = $LL(metricsList) For iMetric = 1:1:len { Set metric = $LG(metricsList, iMetric) Set metricName = $LG(metric, 1) Set metricValue = $LG(metric, 2) Set:(metricName = "PRECISION") ..ModelMetricPrecision = metricValue Set:(metricName = "RECALL") ..ModelMetricRecall = metricValue Set:(metricName = "F-MEASURE") ..ModelMetricFMeasure = metricValue Set:(metricName = "ACCURACY") ..ModelMetricAccuracy = metricValue } // quit with return value indicating the sample data is ready Return $$$OK } ``` 编译监视器类后,您需要重新启动系统监视器,使系统意识到一个新的监视器已经创建并可以使用。 您可以使用 ^%SYSMONMGR 例程或 %SYS.Monitor 类来完成这一步。 # 简单用例 这样就有了所需的工具来收集、监视和发布 ML 性能指标的警报。 接下来要做的是定义自定义警报规则,模拟已部署的 ML 模型开始对性能造成负面影响的场景。 首先,我们必须配置电子邮件警报及其触发规则。 这可以使用 ^%SYSMONMGR 例程完成。 不过,为了方便,我创建了一个设置方法,它可以设置所有电子邮件配置和警报规则。 您需要将 <> 之间的值替换为您的电子邮件服务器和帐户参数。 ``` ClassMethod NotificationSetup() { // Set E-mail parameters Set sender = "" Set password = "" Set server = "" Set port = "" Set sslConfig = "default" Set useTLS = 1 Set recipients = $LB("") Do ##class(%Monitor.Manager).AppEmailSender(sender) Do ##class(%Monitor.Manager).AppSmtpServer(server, port, sslConfig, useTLS) Do ##class(%Monitor.Manager).AppSmtpUserName(sender) Do ##class(%Monitor.Manager).AppSmtpPassword(password) Do ##class(%Monitor.Manager).AppRecipients(recipients) // E-mail as default notification method Do ##class(%Monitor.Manager).AppNotify(1) // Enable e-mail notifications Do ##class(%Monitor.Manager).AppEnableEmail(1) Set name = "perf-model-appointments-prediction" Set appname = $namespace Set action = 1 Set nmethod = "" Set nclass = "" Set mclass = "MyMetric.IntegratedMLModelsValidation" Set prop = "ModelMetricAccuracy" Set expr = "%1 < .8" Set once = 0 Set evalmethod = "" // Create an alert Set st = ##class(%Monitor.Alert).Create(name, appname, action, nmethod, nclass, mclass, prop, expr, once, evalmethod) $$$THROWONERROR(st, st) // Restart monitor Do ##class(MyMetric.Install).RestartMonitor() } ``` 在以前的方法中,警报在监视器获得的准确率值小于 90% 时发出。 现在,设置警报规则后,让我们用前 500 条记录创建、训练和验证履约/失约预测模型,并通过前 600 条记录进行验证。 注:*种子*参数只是为了保证可重复性(即没有随机值),通常在生产中必须避免。 ``` -- Creates the model CREATE MODEL AppointmentsPredection PREDICTING (Show) FROM MedicalAppointments USING {\"seed\": 3} -- Train it using first 500 records from dataset TRAIN MODEL AppointmentsPredection FROM MedicalAppointments WHERE ID
文章
Hao Ma · 九月 17, 2022

IRIS的镜像配置(1)

因为篇幅太长, 我把它分为3篇贴在社区 # 配置前的准备 配置Mirror前要准备三件事儿: 1. 规划网络连接。 2. 在所有的服务器中启动ISCAgent服务。 3. 准备服务器的SSL/TLS证书。可选, 但非常推荐。 我假设您在动手前一定已经对Mirror的原理和架构已经不陌生了,对镜像成员,DR(灾备)成员, Arbiter, ISCAgent等术语已经自动切换的概念有大概的认识。如果不是这样,请先阅读在线文档,或者这篇文章。 ## 规划网络连接 Mirror应该配置两个网段:一个用于IRIS和外部的通信;另一个用于两个Mirror成员间的内部通信,也就是数据的同步。 尽管不是必须的,但Mirror作为一个高可用方案,为了保证服务器之间的内部通信不受和外部连接的干扰,把内部通信放在单独的网段是通常的做法,尤其是在生产环境。 下图来自IRIS的在线文档:其中绿色所示的是IRIS提供服务的网段,IRIS到所有外部系统的连接,ECP应用服务器,和Arbiter在工作在这个网段。紫色的“Data Center Private LAN for Mirror Communication"用于内部通信,准确的说, 用于journal的同步。为了方便, 我会在后面的步骤中简单的把这两个网段简单的称为**外网网段**和**内网网段**。 ![Mirror的网络链接](https://docs.intersystems.com/iris20212/csp/docbook/images/gha_mirror_netconfig_simple.png) 也是来自在线文档,上图的IP地址配置像这个样子。(请忽略C栏和D栏,它们是DR服务器的地址) ![mirror网络配置](https://docs.intersystems.com/iris20221/csp/docbook/images/gha_mirror_netconfig_ecp_table.png) 在安装配置Mirror之前, 您需要检查的是: ( Agent address用的是公网地址,但书上有句话:When attempting to contact this member’s agent, other members try this address first. Critical agent functions (such as those involved in failover decisions) will retry on the mirror private and superserver addresses (if different) when this address is not accessible. Because the agent can send journal data to other members, journal data may travel over this network. ) - ServerA, ServerB, Arbiter三台机器的在绿色网段可以相互访问。ServerA, ServerB的1972端口可以访问,Arbiter的2188端口可以访问。 - ServerA, ServerB在紫色网段可以互相访问2188端口。 - Virtual IP绑定在Server A, 并且IRIS的服务和连接通过Virtual IP提供。 下面是我用的两个服务器的网络配置,因为不方便使用(懒的修改)上图的地址,我自己做的地址配置如下 | Virtual IP Address | | | | ------------------------------ | -------------- | -------------- | | Arbiter Address | 172.16.58.100 | | | Member-Specific Mirror Address | serverA | serverB | | SuperServer Address | 172.16.58.101 | 172.16.58.102 | | Mirror Private Address | 172.16.159.101 | 172.16.159.102 | | Agent Address | 172.16.58.101 | 172.16.58.102 | 其中172.16.58.0网段为外网网段; 172.16.159.0网段为内网网段。 在操作系统上查看IP, 是这个样子: **servera** ```sh #servera上的端口配置 [root@servera mgr]# ip -4 -br addr lo UNKNOWN 127.0.0.1/8 ens33 UP 172.16.58.101/24 ens36 UP 172.16.159.101/24 [root@servera mgr]# firewall-cmd --list-ports 1972/tcp 52773/tcp 2188/tcp [root@servera ~]# #serverb上的端口配置 [root@serverb isc]# ip -4 -br addr lo UNKNOWN 127.0.0.1/8 ens33 UP 172.16.58.102/24 ens36 UP 172.16.159.102/24 [root@serverb isc]# # firewall-cmd --list-ports 1972/tcp 52773/tcp 2188/tcp ``` ## 在所有的镜像成员启动ISCAgent服务 无论是同步成员,异步成员,还是Arbiter,它们之间的通信都依赖ISCAgent服务。在操作系统上,它是一个独立于IRIS的服务,IRIS的默认安装也没有把它设置为自动启动,所以您需要在安装IRIS的机器,也就是同步,异步成员上手工启动这个服务。至于Arbiter,您可以理解Arbiter就是一个装了ISCAgent服务的机器,可以是任何一台客户的机器,装上ISCAgent, 能帮助IRIS主备成员自动切换判断,它就是Arbiter了。简单说, 您需要 1. 在Mirror的所有成员上启动ISCAgent 2. 在一台机器上安装ISCAgent并启动,它从此就是这个Mirror的Arbiter了。 Arbiter需要和IRIS服务器用相同的操作系统吗?没必要。很多客户的IRIS装在Linux上,而Arbiter是一个Windows机器, 跑着和IRIS无关的业务, 都不用是Server版的Windows。 默认配置下, ISCAgent通过TCP的2188端口和远端连接,启动ISC Agent后请检查防火墙,保证2188端口访问是可以访问的。 下面是具体的配置细节。 ### 在Mirror的所有成员上启动ISCAgent IRIS服务器不需要单独安装ISCAgent。 你需要做的是启动服务,并服务器重启后ISCAgent能自动启动。 - Windows: 进入管理工具—服务,选择ISCAgent,将启动类型改为自动。点启动ISCAgent,并确认服务已启动。 - Linux: 使用systemctl启动ISCAgent, 并加入系统自启动列表。 ```sh [root@servera isc]# systemctl start ISCAgent [root@servera isc]# systemctl status ISCAgent ● ISCAgent.service - InterSystems Agent Loaded: loaded (/etc/systemd/system/ISCAgent.service; disabled; vendor preset: disabled) Active: active (running) since Fri 2022-04-15 10:26:20 CST; 11s ago Process: 3651 ExecStart=/usr/local/etc/irissys/ISCAgent (code=exited, status=0/SUCCESS) Main PID: 3653 (ISCAgent) CGroup: /system.slice/ISCAgent.service ├─3653 /usr/local/etc/irissys/ISCAgent └─3654 /usr/local/etc/irissys/ISCAgent Apr 15 10:26:20 servera systemd[1]: Starting InterSystems Agent... Apr 15 10:26:20 servera systemd[1]: Started InterSystems Agent. Apr 15 10:26:20 servera ISCAgent[3653]: Starting Apr 15 10:26:20 servera ISCAgent[3654]: Starting ApplicationServer on *:2188 [root@servera isc]#systemctl enable ISCAgent Created symlink from /etc/systemd/system/multi-user.target.wants/ISCAgent.service to /etc/systemd/system/ISCAgent.service. [root@servera isc]# ``` ### Arbiter(仲裁服务) 你需要到WRC的下载网址下载ISCAgent的软件。下面是在Linux下安装ISCAgent的过程。 ```sh [root@serverc isc]# cd ISCAgent-2022.1.0.164.0-lnxrh7x64 [root@serverc ISCAgent-2022.1.0.164.0-lnxrh7x64]# ls agentinstall cplatname dist package tools [root@serverc ISCAgent-2022.1.0.164.0-lnxrh7x64]# ./agentinstall Your system type is 'Red Hat Enterprise Linux (x64)'. Please review the installation options: ------------------------------------------------------------------ ISCAgent version to install: 2022.1.0.164.0 ------------------------------------------------------------------ Do you want to proceed with the installation ? yes Starting installation... Installation completed successfully [root@serverc ISCAgent-2022.1.0.164.0-lnxrh7x64] ``` 安装后同上一小节使用systemctl命令启动。 ## 准备服务器的SSL/TLS证书 您的每个服务器需要准备两个证书:一个本机的证书, 一个是CA的证书。(如果是公开证书也要吗,openssl应该是没有,除非自己去下载)。 如果您使用的是IRIS自带的PKI签发的self-signed证书,那么每台服务器不仅仅要自己的证书,非CA所在的服务器要:“获取证书颁发机构证书”(‘Get Certificate Authority Certificate’)。这个选项“证书颁发机构客户端的”从证书颁发机构获取证书页面。 举例: servera, serverb为镜像成员。 ```sh [root@servera mgr]# ls *.cer *.key iris.key iscCA.cer iscCASigned.cer iscCASigned.key ``` 忽略iris.key。 其他的iscCA.cer是CA的证书, iscCASigned.cer是servera的证书, iscCASigned.key是servera的私钥。 同样, 在serverb: ```sh [root@serverb ~]# cd /isc/iris/mgr [root@serverb mgr]# ls *.cer *.key iris.key iscCA.cer iscCASignedserverb.cer iscCASignedserverb.key [root@serverb mgr]# ``` ## 增加gmheap的大小 Mirror成员间的数据同步默认使用“Parallel dejournaling", 也就是多个进程并行处理同步数据,而这要求增大gmheap资源。 gmheap又被称为shared memory heap(SMH)。它的默认配置是38MB, 但在实际的生产系统中,gmheap通常配置比这要大。 每一个"parallel dejournalling job"需要增加200M的gmheap。比如说, 为了支持4个并行的同步任务, gmheap至少需要800M。如果系统资源足够,最多可以有16个并行同步任务。 **折中的考虑,当您配置镜像时,请将您系统的gmheap值增加为1GB。** 后面我们进入正式的Mirror的配置工作。Mirror的配置和把数据库添加入镜像。
文章
Hao Ma · 三月 25, 2021

为什么 COVID-19 对机器学习也有危险?(Part I)

几个月前,我在 MIT Technology Review 读到一篇很有意思的[文章](https://www.technologyreview.com/2020/05/11/1001563/covid-pandemic-broken-ai-machine-learning-amazon-retail-fraud-humans-in-the-loop/),作者解释了新冠疫情如何给全球 IT 团队带来关乎机器学习 (ML) 系统的难题。 这篇文章引起我对 ML 模型部署后如何处理性能问题的思考。 我在一个 Open Exchange 技术示例应用 ([iris-integratedml-monitor-example](https://openexchange.intersystems.com/package/iris-integratedml-monitor-example)) 中模拟了一个简单的性能问题场景,并提交到 InterSystems IRIS AI Contest。 读完这篇文章后您可以去看看,如果喜欢,就请[投我一票吧](https://openexchange.intersystems.com/contest/current)! :) # 目录 ### 第一部分: * [IRIS IntegratedML 和 ML 系统](#iris_integratedml_and_ml_systems) * [新旧常态之间](#between_the_old_and_new_normal) ### 第二部分: * [监视 ML 性能](#monitoring_ml_performance) * [简单用例](#a_simple_use_case) * [未来工作](#future_works) # IRIS IntegratedML 和 ML 系统 讨论 COVID-19 以及它对全球 ML 系统的影响之前,我们先来简单谈谈 InterSystems IRIS IntegratedML。 通过将特征选择之类的任务及其与标准 SQL 数据操作语言的集成自动化,IntegratedML 可以协助开发和部署 ML 解决方案。 例如,对医疗预约的数据进行适当的操作和分析后,可以使用以下 SQL 语句设置 ML 模型,预测患者的履约/失约情况: ```sql CREATE MODEL AppointmentsPredection PREDICTING (Show) FROM MedicalAppointments TRAIN MODEL AppointmentsPredection FROM MedicalAppointments VALIDATE MODEL AppointmentsPredection FROM MedicalAppointments ``` AutoML 提供程序将选择性能最好的特征集和 ML 算法。 这里,AutoML 提供程序使用 scikit-learn 库选择了逻辑回归模型,获得 90% 的准确率。 ``` | | MODEL_NAME | TRAINED_MODEL_NAME | PROVIDER | TRAINED_TIMESTAMP | MODEL_TYPE | MODEL_INFO | |---|------------------------|-------------------------|----------|-------------------------|----------------|---------------------------------------------------| | 0 | AppointmentsPredection | AppointmentsPredection2 | AutoML | 2020-07-12 04:46:00.615 | classification | ModelType:Logistic Regression, Package:sklearn... | ``` ``` | METRIC_NAME | Accuracy | F-Measure | Precision | Recall | |--------------------------|----------|-----------|-----------|--------| | AppointmentsPredection21 | 0.9 | 0.94 | 0.98 | 0.91 | ``` 集成到 SQL 后,您可以通过估计履约和失约的患者,将 ML 模型无缝集成到现的预约系统中以提高其性能: ```sql SELECT PREDICT(AppointmentsPredection) As Predicted FROM MedicalAppointments WHERE ID = ? ``` 您可以在[此处](https://docs.intersystems.com/iris20202/csp/docbook/DocBook.UI.Page.cls?KEY=GIML)详细了解 IntegrateML。 有关这个简单的预测模型的更多详细信息,可以参考[此处](https://github.com/jrpereirajr/iris-integratedml-monitor-example/blob/master/jupyter-samples/IntegeratedML-Monitor-Example.ipynb)。 然而,由于 AI/ML 模型在设计上是为了直接或间接地适应社会行为,因此当相关行为快速变化时,这些模型可能会受到很大影响。 最近,由于新冠疫情,我们(很遗憾地)得以实验这种场景。 # 新旧常态之间 如[MIT Technology Review 的文章](https://www.technologyreview.com/2020/05/11/1001563/covid-pandemic-broken-ai-machine-learning-amazon-retail-fraud-humans-in-the-loop/)所解释,新冠疫情一直在显著且迅速地改变着社会行为。 我在 Google Trends 中查询了一些文章中引用的词语,如 N95 口罩、卫生纸和消毒洗手液,确认在全球大流行中这些词语的热度有所提高: 文章中提到: > “但是它们(指由 COVID-19 引起的变化)也影响了人工智能,给库存管理、欺诈检测、营销等幕后运行的算法造成干扰。 根据正常人类行为进行训练的机器学习模型现在发现,所谓的‘正常’已经发生变化,有些模型因而不再能发挥应有的作用。” 即,在“旧常态”和“新常态”之间,我们正在经历一种“新异常”。 文章中还有这样一段话: > “机器学习模型虽然是为了应对变化而设计的, 但大多数也很脆弱。当输入数据与训练的数据相差太大时,它们的表现就会很糟糕。 (...) AI 是一种活着的引擎。” 本文继续列出一些 AI/ML 模型的示例,这些示例有的是性能突然开始受到负面影响,有的需要立即进行更改。 一些示例: * 零售公司的非常规产品在批量订购后缺货; * 由于媒体文章内容过于悲观,投资推荐服务根据情绪分析提出的建议失准; * 自动短语生成器由于新的语境而开始生成不合适的内容; * Amazon 更改了卖家推荐系统,选择自己送货的卖家,避免对其仓库物流的过度需求。 因此,我们要监控我们的 AI/ML 模型,确保模型能可靠地持续帮助客户。 到这里,希望您已经明白,对 ML 模型的创建、训练和部署并不是全部,跟踪过程也是必不可少的。 在下一篇文章中,我将展示如何使用 IRIS %Monitor.Abstract 框架来监视 ML 系统的性能,以及如何根据监视器的指标设置警报触发器。 *同时,我很想知道您是否遇到过疫情导致的问题,以及您又是如何应对的。请在评论区留言吧!* 敬请关注!保重身体 😊!
文章
姚 鑫 · 五月 17, 2021

第三章 执行测试

# 第三章 执行测试 # 示例:执行测试 现在使用`%UnitTest.Manager.RunTest`执行单元测试。以下是方法: 1. 在包含单元测试的名称空间中打开终端;在本例中为用户。如果终端未在正确的命名空间中打开,请使用ZN更改命名空间。 2. 将`^UnitTestRoot`全局值设置为包含导出的测试类的目录的父级。 ```java DHC-APP>Set ^UnitTestRoot="d:\Temp" ``` 3. 使用方法`%UnitTest.Manager.RunTest`执行测试。 ```java DHC-APP>do ##class(%UnitTest.Manager).RunTest("test") ``` 4. IRIS从`XML`文件加载测试类,编译类,执行测试,从服务器删除测试代码,并向终端发送报告。 ```java HC-APP>do ##class(%UnitTest.Manager).RunTest("test") =============================================================================== Directory: D:\Temp\test\ =============================================================================== test begins ... Load of directory started on 05/14/2021 14:07:17 '*.xml;*.XML;*.cls;*.mac;*.int;*.inc;*.CLS;*.MAC;*.INT;*.INC' Loading file D:\Temp\test\Tests.xml as xml Imported class: MyPackage.Tests Compilation started on 05/14/2021 14:07:17 with qualifiers '', using up to 4 worker jobs Compiling class MyPackage.Tests Compiling routine MyPackage.Tests.1 Compilation finished successfully in 0.019s. Load finished successfully. MyPackage.Tests begins ... TestAdd() begins ... AssertEquals:Test Add(2,2)=4 (passed) AssertNotEquals:Test Add(2,2)'=5 (passed) LogMessage:Duration of execution: .000061 sec. TestAdd passed MyPackage.Tests passed test passed Use the following URL to view the result: http://172.18.18.159:52773/csp/sys/%25UnitTest.Portal.Indices.cls?Index=3&$NAMESPACE=DHC-APP All PASSED ``` ![image](/sites/default/files/inline/images/1_46.png) ![image](/sites/default/files/inline/images/2_27.png) 最后一行显示了测试报告的URL。 **注意:以这种方式运行测试会在它们执行后从InterSystems IRIS中删除它们。如果在执行测试后返回到Atelier查看测试,将看到一个指示,表明Atelier中可见的文件与服务器不同步。可以保存或重新编译该类,以将代码添加回服务器。 如果使用的是`.cls`文件而不是XML文件,则必须向`RunTest`提供`/loadudl`限定符。** ```java USER>do ##class(%UnitTest.Manager).RunTest("mytests","/loadudl") ``` # 示例:UnitTest Portal 运行单元测试将生成测试报告。InterSystems IRIS提供了一个用于查看报告的`UnitTest`门户。报告按命名空间组织。 可以使用系统资源管理器System Explorer > Tools > UnitTest Portal导航到UnitTest门户。如有必要,请切换到用户命名空间。 ![image](/sites/default/files/inline/images/3_22.png) # 示例:在单元测试门户中查看报告 门户将测试结果组织成一系列报告。每个测试报告将测试结果组织到一系列超链接页面中。按照链接查找越来越具体的信息。 第一页提供了所有测试套件的摘要。在这种情况下,所有测试套件都通过了。 ![image](/sites/default/files/inline/images/4_16.png) 单击要查看的报告的`ID`列中的`ID`号。 第二个页面显示每个测试套件的结果。在本例中,`mytest`是测试套件,并且通过了测试。 ![image](/sites/default/files/inline/images/5_7.png) 单击 `mytests`. 第三个页面显示每个测试用例的结果。在本例中,通过了单个测试用例`MyPackage.Tests`。 ![image](/sites/default/files/inline/images/6_6.png) 单击 `MyPackage.Tests` 第四页显示了通过测试方法得出的结果。这里通过了单个测试方法`TestAdd`。 ![image](/sites/default/files/inline/images/7_2.png) 单击 TestAdd. 最后一页显示测试方法中使用的每个`AssertX`宏的结果。在本例中,`AssertEquals`和`AssertNotEquals`都通过了。 ![image](/sites/default/files/inline/images/8_3.png) # 设置和拆卸 `%UnitTest.TestCase`类提供的方法可用于在一个测试或一组测试执行之前设置测试环境,然后在测试完成后拆除该环境。以下是对这些方法的说明: 方法 | 描述 ---|--- `OnBeforeAllTests` |在测试类中的任何测试方法执行之前执行一次。可以设置测试环境。 `OnAfterAllTests` |在测试类中的所有测试方法执行后执行一次。可以破坏测试环境。 `OnBeforeOneTest` |在测试类中的每个测试方法执行之前立即执行。 `OnAfterOneTest` |在文本类中的每个测试方法执行后立即执行。 # 示例:向测试类添加Setup和Tear Down方法 在本例中,将添加一个名为`TestEditContact`的测试方法。此方法验证`MyPackage.Contact`类的`ContactType`属性是否限制为`“Personal”`或`“Business”`。添加了一个`OnBeforeAllTests`方法,该方法在测试执行之前准备数据库。还可以添加一个`OnAfterAllTests`方法,该方法在测试执行后还原数据库状态。 1. 在`Studio`中打开`MyPackage.Tests`(可能需要从`^UnitTestRoot`目录导入它)。 2. 添加`OnBeforeAllTests`和`OnAfterAllTests`方法。 ```java Method OnBeforeAllTests() As %Status { Do ##class(MyPackage.Contact).Populate(1) Return $$$OK } ``` ```java Method OnAfterAllTests() As %Status { Do ##class(MyPackage.Contact).%KillExtent() Return $$$OK } ``` `OnBeforeAllTests`方法使用单个`Contact`实例填充数据库。`OnAfterAllTests`方法从数据库中删除所有`Contact`实例。 3. 现在将`TestEditContact`测试方法添加到`MyPackage.Tests`: ```java Method TestEditContact() { set contact=##class(MyPackage.Contact).%OpenId(1) set contact.Name="Rockwell,Norman" set contact.ContactType="Friend" Do $$$AssertStatusNotOK(contact.%Save(),"ContactType = Friend") Set contact.ContactType="Personal" Do $$$AssertStatusOK(contact.%Save(),"ContactType = Personal") } ``` 该方法在两种情况下测试执行`%Save on Contact`返回的状态值:为`ContactType`分配无效值之后和为`ContactType`分配有效值之后。 4. 将测试导出到`c:\unittest\mytest`,覆盖现有的`Tests.xml`。 # [源码](https://download.csdn.net/download/yaoxin521123/18703118)
文章
姚 鑫 · 五月 29, 2021

第十章 使用FTP

# 第十章 使用FTP IRIS提供了一个类`%Net.FtpSession`,可以使用它从InterSystems IRIS内建立与FTP服务器的会话。 # 建立FTP会话 要建立FTP会话,请执行以下操作: 1. 创建`%Net.FtpSession`的实例。 2. 可以选择设置此实例的属性,以控制会话的常规行为: - `Timeout` 超时指定等待FTP服务器回复的时间(以秒为单位)。 - `SSLConfiguration`指定用于连接的激活的`SSL/TLS`配置(如果有)。如果`FTP`服务器使用HTTPS,请使用此选项。 - `TranslateTable`指定在读取文件内容或写入文件内容时要使用的转换表。 - `UsePASV`启用PASV模式。 - 当`FTP`服务器使用`https`时,`SSLCheckServerIdentity`适用。默认情况下,当`%Net.FtpSession`的实例连接到`SSL/TLS`服务器时,它会检查证书服务器名称是否与用于连接到服务器的DNS名称匹配。如果这些名称不匹配,则不允许连接。 若要禁用此检查,请将`SSLCheckServerIdentity`属性设置为0。 3. 调用`Connect()`方法以连接到特定的FTP服务器。 4. 调用`ascii()`或`binary()`方法将传输模式分别设置为ASCII模式或二进制模式。要查看当前传输模式,请检查实例的Type属性的值。 注意:`%Net.FtpSession`的每个方法都返回一个状态,应该检查该状态。这些方法还设置提供有关会话状态的有用信息的属性的值: - 如果当前已连接,则`CONNECTED`为TRUE,否则为FALSE。 - `ReturnCode`包含上次与FTP服务器通信时的返回代码。 - `ReturnMessage`包含上次与FTP服务器通信时的返回消息。 `Status()`方法返回(通过引用)FTP服务器的状态。 ## 命令的转换表 `%Net.FtpSession`在FTP服务器上查看文件名和路径名时,使用`RFC 2640`中介绍的技术自动处理字符集转换。当`%Net.FtpSession`的实例连接到FTP服务器时,它会使用Feat消息来确定服务器是否使用`UTF-8`字符。如果是,它将命令通道通信切换到`UTF-8`,以便所有文件名和路径名都可以正确地与`UTF-8`相互转换。 如果服务器不支持`FEAT`命令或未报告支持`UTF-8`,`%Net.FtpSession`实例将使用`RAW`模式并读取或写入RAW字节。 在极少数情况下,如果需要指定要使用的转换表,请设置`%Net.FtpSession`实例的`CommandTranslateTable`属性。一般情况下,应该没有必要使用此属性。 # FTP文件和系统方法 一旦建立了FTP会话,就可以调用会话实例的方法来执行FTP任务。`%Net.FtpSession`提供以下读写文件的方法: ### Delete() 删除文件。 ### Retrieve() 将文件从FTP服务器复制到InterSystems IRIS流中,并通过引用返回该流。要使用此流,请使用标准流方法:`Write()`、`WriteLine()`、`Read()`、`ReadLine()`、`Rewind()`、`MoveToEnd()`和`Clear()`。还可以使用流的`Size`属性。 ### RetryRetrieve() 允许继续检索文件,因为给定的流是由上一次使用`Retrieve()`创建的。 ### Store() 将 IRIS流的内容写入FTP服务器上的文件。 ### Append() 将流的内容追加到指定文件的末尾。 ### Rename() 重命名文件。 此外,`%Net.FtpSession`提供了导航和修改FTP服务器上的文件系统的方法:`GetDirectory()`、`SetDirectory()`、`SetToParentDirectory()`和`MakeDirectory()`。 要检查文件系统的内容,请使用`list()`或`NameList()`方法。 - `List()`创建一个流,其中包含其名称与给定模式匹配的所有文件的列表,并通过引用返回该流。 - `NameList()`创建文件名数组并通过引用返回该数组。 还可以使用`ChangeUser()`方法更改为其他用户;这比注销并再次登录要快。使用`Logout()`方法注销。 `System()`方法返回(通过引用)有关托管FTP服务器的计算机类型的信息。 `Size()`和`MDTM()`方法分别返回文件的大小和修改时间。 使用通用`sendCommand()`方法向FTP服务器发送命令并读取响应。此方法可用于发送`%Net.FtpSession`中未明确支持的命令。 # 使用链接的流上载大文件 如果要上传大文件,请考虑使用流接口的`LinkToFile()`方法。也就是说,不是创建流并将文件读入其中,而是创建流并将其链接到文件。在调用`%Net.FtpSession`的`Store()`方法时使用此链接流。 ```java Method SendLargeFile(ftp As %Net.FtpSession, dir As %String, filename As %String) { Set filestream=##class(%FileBinaryStream).%New() Set sc=filestream.LinkToFile(dir_filename) If $$$ISERR(sc) {do $System.Status.DisplayError(sc) quit } //上传的文件将与原始文件同名 Set newname=filename Set sc=ftp.Store(newname,filestream) If $$$ISERR(sc) {do $System.Status.DisplayError(sc) quit } } ``` # 自定义FTP服务器发出的回调 可以自定义`FTP`服务器生成的回调。例如,通过这样做,可以向用户提供服务器仍在处理大型传输的指示,或允许用户中止传输。 要自定义FTP回调,请执行以下操作: 1. 创建`%Net.FtpCallback`的子类。 2. 在这个子类中,实现`RetrieveCallback()`方法,该方法在从FTP服务器接收数据时定期调用。 3. 还要实现`StoreCallback()`方法,在将数据写入FTP服务器时会定期调用该方法。 4. 创建`FTP`会话时(如“建立FTP会话”中所述),将回调属性设置为等于的子类`%Net.FtpCallback`。
文章
Weiwei Gu · 十一月 29, 2021

翻译文章: 不是所有的多模型数据库都是相同的

不是所有的多模型数据库都是相同的 作者:David Menninger 今天,许多现代应用程序需要的数据库管理能力往往不能通过一种方式就能实现。例如,当我建立一个支持旅游推荐和预订业务的应用程序时,我可能需要使用一些不同类型的数据库,包括用于用户会话的键值存储,用于产品目录的文档数据库,用于推荐的图形数据库,以及用于财务数据的关系数据库。由于各种原因,选择一个(关系型)数据库就可以了这种一刀切的数据库时代,已经离我们很远了。虽然关系型数据库仍然是许多应用的正确选择,但对于某些类型的应用,非关系型数据库提供了关系型数据库根本无法提供的优势。 关系型数据库用表和行表示数据,并使用结构化查询语言(SQL)来访问和操作数据。对于需要可靠性和ACID(原子性、一致性、隔离性和持久性)保证的事务性应用,以及需要SQL查询和报告的效率和简单性的应用,它们是一个很好的选择。 但是,关系型数据库是有代价的;它们需要数据库管理员,需要遵守预先定义的关系型结构,而且随着数据规模和工作负载的增加,它们的扩展也不经济。尽管如此,关系型数据库仍然是许多关键任务应用的正确选择,并继续为其提供动力。 相比之下,非关系型数据库,包括文档、对象、图形和键值数据库等,比关系型数据库具有某些优势--尤其是在灵活性和扩展性方面。非关系型数据库不需要DBA创建预先定义的模式,应用程序开发人员能够更容易地存储和管理数据,而不必担心映射固定的数据结构。 幸运的是,所有这些不同种类的数据库技术都是很成熟稳健的,也为应用开发者提供了丰富的功能,他们可以善加利用。 今天市场上有数以百计的数据库;非关系型数据库约占所有部署到生产中的数据库的一半。 但是,将多种数据库技术纳入一个应用程序并不总是那么简单。 多模型的一种方法--称为混合持久化--采用不同的数据库来支持每种类型的数据结构。 这是一种最佳的方法。 但是,在应用程序的整个生命周期中实施、同步和维护不同的数据库系统是复杂的,而且容易出错。 另一种方法是使用业界所称的多模型数据库;一种在同一 "产品 "中支持各种数据表示的数据库。但不是所有的多模型数据库都是一样的。有些数据库坚持混合范式,为不同的数据表示法采用多个独立的数据库引擎,造成数据的重复,并需要在不同的数据存储之间进行映射和整合。 还有一些支持引擎内的不同模型,但不支持相同的数据。 在InterSystems,我们已经开发了一种纯粹的多模型数据库管理方法。我们的产品,InterSystems IRIS数据平台存储了数据的单一表示。它利用了一个单一的数据库引擎,支持关系型和非关系型的数据访问和操作,没有重复。对于非关系型访问,它不需要预先定义的模式。它提供事务性的ACID保证,可以纵向和横向扩展,并支持本地、公共和私有云环境,以及混合(公共/公共、公共/私有、云/本地)部署环境。同样的数据可以使用SQL访问和操作,也可以作为文档、对象或键值数据。应用程序开发人员不需要使用多个数据库,也不需要在多个数据存储中整合和同步数据。 我们的多模型方法整合了关系型和非关系型技术的优点,而摒弃了其缺点,也没有与多角化持久性相关的复杂性或低效率。 所有这些都整合在我们从头开始建立的这个单一的数据库管理系统中了。 (David Menninger是Ventana Research的高级副总裁和研究总监,也是长期的行业老兵,他对多种数据表示的需求以及多模型数据库的各种方法的优势和劣势进行了深刻的分析。你可以在这里阅读他的报告。) https://www.intersystems.com/data-excellence-blog/not-all-multi-model-dbms-are-created-equal/
文章
Qiao Peng · 十月 20, 2022

在集成产品中压缩解压文件

InterSystems IRIS医疗版里有一个文件压缩解压的适配器HS.Util.Zip.Adapter和对应的文件压缩解压业务操作HS.Util.Zip.Operations。集成产品可以使用它们进行文件的压缩和解压操作。这2个类的联机文档说明较少,这里介绍它们的使用方法。 1. 基础配置 InterSystems IRIS使用操作系统的压缩和解压缩能力,因此需要注册操作系统执行压缩解压的命令。 在管理门户的Health标签页下,选中配置注册(Configuration Registry): 在其中增加2个注册项目: \ZipUtility\UnZipCommand 和\ZipUtility\ZipCommand,分别代表解压和压缩命令。适配器HS.Util.Zip.Adapter会检查这2个注册项并得到相应的命令。各个操作系统的命令并不一样,示例如下: \ZipUtility\UnZipCommand 解压缩命令 Windows "c:\program files\7-zip\7z" x %1 -o. -r 非Windows unzip %1 -d . \ZipUtility\ZipCommand 压缩命令 Windows "c:\program files\7-zip\7z" a %1 . -r 非Windows zip -rm %1 . 注意,其中要有%1,代表解压后的或压缩后的目标文件路径。 2. 适配器配置 可以直接使用业务操作HS.Util.Zip.Operations,无需自己开发业务操作类。这个系统提供的业务操作使用的就是适配器HS.Util.Zip.Adapter。 将业务操作HS.Util.Zip.Operations加入Production,配置其工作目录(WorkingDirectory),就是上面提到压缩/解压命令中%1代表的目录。注意,这个目录并非HS.Util.Zip.Operations输出的目标路径,它只是压缩/解压过程中用到的临时目录。这个业务操作执行压缩/解压后,会自动删除临时目录中的文件,而将目标文件流数据保存到响应消息里。 3. 调用压缩/解压业务操作 这个业务操作的请求消息是HS.Message.ZipRequest,响应消息是HS.Message.ZipResponse。 3.1 请求消息准备 HS.Message.ZipRequest里有如下属性: Operation:执行的操作,压缩或解压缩。可用值为"FromZip" - 解压缩,和"ToZip" 压缩。 File:解压文件的Stream数据,类型为%Stream.GlobalBinary。在执行文件解压时才需要设置该属性 - 需要将解压文件的Stream赋值给File属性。 Items: 文件项目列表,列表元素类型为HS.Types.ZipItem。在执行文件压缩时才需要设置该属性 - 需要将多个压缩文件的数据赋值给Items列表元素的以下属性: Filename:未来解压缩出来的目标文件名 Path:未来解压缩出来的目标文件子目录 File:需要压缩的文件流数据 3.2 处理响应消息 HS.Message.ZipResponse有如下属性: File:在执行文件压缩操作时,该属性保存压缩后的流数据。在执行解压操作时,该属性为空。 Items:文件项目列表,列表元素类型为HS.Types.ZipItem。在执行文件解压操作时,该列表属性的元素保护以下属性: Filename:解压缩出来的目标文件名 Path:解压缩出来的目标文件子目录 File:解压缩出来的流数据 4. 示例代码 以下是直接调用HS.Util.Zip.Operations的业务服务代码示例。 4.1 压缩示例 Method OnProcessInput(pInput As %RegisteredObject, Output pOutput As %RegisteredObject) As %Status { //实例化请求消息 Set tReq = ##class(HS.Message.ZipRequest).%New() //设置源文件目录 Set tSrcFolder = "/Users/test/irishealth/mgr/Temp/" //设置请求消息为压缩操作 Set tReq.Operation = "ToZip" Set tFile = ##class(%Stream.FileBinary).%New() Set sc=tFile.LinkToFile(tTgtFolder_"output.zip") Set tReq.File = tFile //压缩的第一个文件 Set tItem1 = ##class(HS.Types.ZipItem).%New() //设置未来解压缩的文件名 Set tItem1.Filename = "test.png" //设置未来解压缩的子文件夹 Set tItem1.Path = "pic/" //打开目标文件,获取流数据 Set tFileSrc = ##class(%Stream.FileBinary).%New() Do tFileSrc.LinkToFile(tSrcFolder_tItem1.Filename) Do tItem1.File.CopyFrom(tFileSrc) kill tFileSrc //将压缩文件信息插入列表 Do tReq.Items.Insert(tItem1) //压缩的第二个文件 Set tItem2 = ##class(HS.Types.ZipItem).%New() //设置未来解压缩的文件名 Set tItem2.Filename = "HS.SDA3.xsd" //设置未来解压缩的子文件夹 Set tItem2.Path = "code/" //打开目标文件,获取流数据 Set tFileSrc = ##class(%Stream.FileBinary).%New() Do tFileSrc.LinkToFile(tSrcFolder_tItem2.Filename) Do tItem2.File.CopyFrom(tFileSrc) kill tFileSrc //将压缩文件信息插入列表 Do tReq.Items.Insert(tItem2) //调用业务操作 Do ..SendRequestSync("HS.Util.Zip.Operations",tReq,.tRes) //设置目标路径 Set tFolder = "/Users/test/irishealth/mgr/Temp/Target/" //保存压缩文件到目标路径 Set tTgtFile = ##class(%Stream.FileBinary).%New() Set tFullPath = tFolder_"output.zip" Set tTgtFile.Filename = tFullPath Set tSC = tTgtFile.CopyFrom(tRes.File) Set tSC= tTgtFile.%Save() Quit $$$OK } 4.2 解压缩示例 Method OnProcessInput(pInput As %RegisteredObject, Output pOutput As %RegisteredObject) As %Status { //实例化请求消息 Set tReq = ##class(HS.Message.ZipRequest).%New() //设置请求消息为解压缩操作 Set tReq.Operation = "FromZip" //打开压缩文件,获取流数据 Set tFileSrc = ##class(%Stream.FileBinary).%New() Set sc=tFileSrc.LinkToFile("/Users/test/Downloads/TestCase.zip") //Set tReq.File = tFileSrc Do tReq.File.CopyFrom(tFileSrc) kill tFileSrc //调用业务操作 Do ..SendRequestSync("HS.Util.Zip.Operations",tReq,.tRes) //设置目标文件路径 Set tTgtFolder = "/Users/test/irishealth/mgr/Temp/" //获取解压后的数据,并保存到目标文件路径下 For i=1:1:tRes.Items.Count() { Set tFileItem = tRes.Items.GetAt(i) Set tSubFolder = tFileItem.Path Set tFileName = tFileItem.Filename Set tTgtFile = ##class(%Stream.FileBinary).%New() Set tFullPath = tTgtFolder_tSubFolder_tFileName Set tTgtFile.Filename = tFullPath Set tSC = tTgtFile.CopyFrom(tFileItem.File) Set tSC= tTgtFile.%Save() Kill tTgtFile } Quit $$$OK }