清除过滤器
文章
姚 鑫 · 八月 19, 2021
# 第121章 查询关键字 - Private
指定查询是否为私有查询。
# 用法
要指定此查询为私有查询,请使用以下语法:
```java
Query name(formal_spec) As classname [ Private ] { //implementation }
```
否则,请省略此关键字或将该词放在该关键字之前。
# 详解
私有类成员只能由同一类(或其子类)的其他成员使用。请注意,其他语言通常使用单词`Protected`来描述这种可见性,使用单词`Private`来表示从子类不可见。
# 默认
如果省略此关键字,则此查询不是私有的。
# 第122章 查询关键字 - SoapBindingStyle
指定此查询用作`Web方法`时使用的绑定样式或`SOAP`调用机制。仅适用于定义为`Web服务`或`Web客户端`的类。
# 用法
要覆盖查询使用的默认绑定样式(当它用作`Web方法`时),请使用以下语法:
```java
Query name(formal_spec) As classname [ WebMethod, SoapBindingStyle = soapbindingstyle ] { //implementation }
```
其中`soapbindingstyle`为下列值之一:
- `document` - 此`Web方法`使用文档式调用。
使用这种绑定样式,`SOAP`消息被格式化为文档,并且通常只有一个部分。
在`SOAP`消息中,``元素通常包含单个子元素。``元素的每个子元素对应一个消息部分。
- `rpc` - 此`Web方法`使用`RP`C(远程过程调用)风格的调用。
使用这种绑定样式,`SOAP`消息被格式化为具有多个部分的消息。
在`SOAP`消息中,``元素包含一个子元素,其名称取自相应的操作名称。此元素是生成的包装元素,它为方法的参数列表中的每个参数包含一个子元素。
重要提示:对于手动创建的`Web服务`,此关键字的默认值通常比较合适。当使用`SOAP`向导从`WSDL`生成`Web客户端`或`服务`时,InterSystems IRIS会将此关键字设置为适用于该`WSDL`;如果修改此值,`Web客户端`或服务可能不再工作。
# 详情
此关键字允许指定此查询在作为`Web方法`调用时使用的绑定样式。
对于给定查询,此关键字覆盖`SoapBindingStyle`类关键字。
# 默认
如果忽略此关键字,``元素的`style`属性将由`SoapBindingStyle`类关键字的值决定。
# WSDL的关系
(请注意,与方法关键字和查询关键字相比,同名的`class关键字`对`WSDL`的影响更大。)
# 对SOAP消息的影响
有关信息,请参阅`SoapBindingStyle`类关键字的条目。
# 第123章 查询关键字 - SoapBodyUse
指定该查询用作`web方法`时,输入和输出使用的编码。
仅应用于定义为`web服务`或`web客户端`的类。
# 用法
要覆盖查询的输入和输出使用的默认编码(当它被用作`web方法`时),请使用以下语法:
```java
Query name(formal_spec) As classname [ WebMethod, SoapBodyUse = encoded ] { //implementation }
```
其中,`soapbodyuse`是下列值之一:
- `literal` - 这个`web方法`使用文字数据。
也就是说,`SOAP`消息的``中的`XML`与`WSDL`中给出的模式完全匹配。
- `encoded` = 这个web方法使用soap编码的数据。
也就是说,`SOAP`消息的``中的`XML`根据所使用的`SOAP`版本使用适当的SOAP编码,满足以下规范的要求:
- `SOAP 1.1` (https://www.w3.org/TR/2000/NOTE-SOAP-20000508/)
- `SOAP 1.2` (https://www.w3.org/TR/soap12-part2/)
重要提示:对于手工创建的`web服务`,这个关键字的默认值通常是合适的。
当使用`SOAP`向导从`WSDL`生成`web客户端`或服务时, IRIS将此关键字设置为适合该`WSDL`的;
如果修改了该值,`web客户端`或服务可能不再工作。
# 详解
该关键字允许您指定该查询作为`web方法`调用时的输入和输出的编码。
对于给定的查询,此关键字覆盖`SoapBodyUse`类关键字。
# 默认
如果忽略此关键字,则使用`SoapBodyUse`类关键字的值。
# 与WSDL的关系以及对SOAP消息的影响
有关信息,请参阅`SoapBodyUse`类关键字的条目。
# 第124章 查询关键字 - SoapNameSpace
在`WSDL`中的绑定操作级别指定名称空间。
仅应用于定义为`web服务`或`web客户端`的类。
# 用法
要在绑定操作级别覆盖默认命名空间(当查询被用作`web方法`时),请使用以下语法:
```java
Query name(formal_spec) As classname [ SoapNameSpace = "soapnamespace", WebMethod ] { //implementation }
```
其中,`soapnamespace`是一个名称空间`URI`。
注意,如果`URI`包含冒号(`:`),则字符串必须用引号括起来。
也就是说,你可以使用以下方法:
```java
Query MyQuery() [ SoapNameSpace = "http://www.mynamespace.org", WebMethod ]
```
或以下:
```java
Query MyQuery() [ SoapNameSpace = othervalue, WebMethod ]
```
但以下情况并非如此:
```java
Query MyQuery() [ SoapNameSpace = http://www.mynamespace.org, WebMethod ]
```
重要提示:对于手工创建的`web服务`,这个关键字的默认值通常是合适的。
当使用`SOAP`向导从`WSDL`生成`web客户`端或服务时, IRIS将此关键字设置为适合该WSDL的;
如果修改了该值,`web客户端`或服务可能不再工作。
# 详解
该关键字允许指定查询作为`web方法`调用时使用的`XML`名称空间。
注意:此关键字仅在查询使用`rpc`样式绑定时有效。
也就是说,查询(或包含它的类)必须用等于`rpc`的`SoapBindingStyle`标记。
(如果为使用文档样式绑定的查询指定此关键字,则`WSDL`将不是自一致的。)
# 默认
如果忽略此关键字,则`web方法`位于由`web服务`或客户端类的`namespace`参数指定的命名空间中。
# 与WSDL的关系以及对SOAP消息的影响
有关信息,请参阅`SoapNameSpace`方法关键字的条目。
文章
Nicky Zhu · 一月 11, 2021
本文将描述通过ObjectScript包管理器(见https://openexchange.intersystems.com/package/ObjectScript-Package-Manager-2)运行单元测试的过程,包括测试覆盖率测量(见https://openexchange.intersystems.com/package/Test-Coverage-Tool)。
## ObjectScript中的单元测试
关于在ObjectScript中编写单元测试,已经有很好的文档,因此我就不再赘述了。您可以在这里找到单元测试教程:https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=TUNT_preface
最好的做法是将单元测试代码单独放在源代码树中,无论它只是“/tests”还是其他名字。在InterSystems中,我们最终使用/internal/testing/unit_tests/作为我们事实上的标准,这是有意义的,因为测试是内部/非发布的,而且除了单元测试还有其他类型的测试,但这对于简单的开源项目来说可能有点复杂。您可以在我们的一些GitHub仓库中看到这种结构。
从工作流的角度来看,这在VSCode中非常简单,您只需创建目录并将类放在里面。对于较老的以服务器为中心的源代码控制方法(Studio中使用的方法),您需要正确地地映射这个包,使用的方法会因源代码控制程序而异。
从单元测试类命名的角度来看,我个人的偏好(以及我的团队的最佳实践)是:
UnitTest.[.]
例如,如果在类MyApplication.SomeClass 中对方法Foo进行单元测试,单元测试类将被命名为UnitTest.MyApplication.SomeClass.Foo;如果测试是针对整个类的,那么名字就是UnitTest.MyApplication.SomeClass。
## ObjectScript 包管理器中的单元测试
让ObjectScript包管理器知道您的单元测试,很简单!只需按如下所示向module.xml中添加一行代码(来自https://github.com/timleavitt/ObjectScript-Math/blob/master/module.xml - 这是Open Exchange上的@Peter Steiwer的出色数学扩展包,我以它作为简单的正面例子):
```objectscript
...
```
这些代码的意思是:
- 单元测试位于模块根目录下的“tests”目录中。
- 单元测试在“UnitTest.Math”包中。这样很直观,因为被测试的类就在“Math”包中。
- 单元测试在包生命周期的“测试”阶段运行。(当然还有一个可以运行它们的“验证”阶段,这里不赘述。)
## 运行单元测试
对于上述定义的单元测试,包管理器提供了一些实用工具来运行它们。您仍然可以像平常使用%UnitTest.Manager那样设置^UnitTestRoot等,但下面的方法可能更简单,尤其在同一个环境下做几个项目的时候。
您可以克隆上述objectscript-math仓库,然后用 ```zpm "load /path/to/cloned/repo/" ```加载,或者在您自己的包上使用包名(和测试名)替换“objectscript-math”来尝试所有这些方法。
重新加载模块,然后运行所有单元测试:
```zpm "objectscript-math test" ```
只运行单元测试(不重新加载):
```zpm "objectscript-math test -only" ```
只运行单元测试(不重新加载)并提供详细输出:
```zpm "objectscript-math test -only -verbose" ```
运行一个特定的测试套件(指一个测试目录 - 在本例中,是UnitTest/Math/Utils中的所有测试)而不重新加载,并提供详细输出:
```zpm "objectscript-math test -only -verbose -DUnitTest.Suite=UnitTest.Math.Utils" ```
运行一个特定的测试用例(在本例中,是UnitTest.Math.Utils.TestValidateRange)而不重新加载,并提供详细输出:
```zpm "objectscript-math test -only -verbose -DUnitTest.Case=UnitTest.Math.Utils.TestValidateRange" ```
如果您只是想解决单个测试方法中的小问题:
```zpm "objectscript-math test -only -verbose -DUnitTest.Case=UnitTest.Math.Utils.TestValidateRange -DUnitTest.Method=TestpValueNull" ```
## 通过ObjectScript包管理器进行测试覆盖率测量
怎样评估单元测试的质量?测量测试覆盖率虽然并不全面,但至少有参考意义。早在2018年的全球峰会上,我就展示过。 见 - https://youtu.be/nUSeGHwN5pc。
首先需要安装“测试覆盖率”包:
```zpm "install testcoverage" ```
注意,并不需要ObjectScript包管理器才能安装/运行;可以在Open Exchange上了解更多信息:https://openexchange.intersystems.com/package/Test-Coverage-Tool
不过如果您已经在用ObjectScript包管理器,那么您可以更好地利用这个“测试覆盖率”工具。
运行测试前,需要指定测试所覆盖的类/routine宏。这一点很重要,因为在非常大的代码库中(例如,HealthShare),测试和收集项目中所有文件的测试覆盖率所需要的内存可能超出您的系统内存。(提一句,如果您感兴趣,可以使用逐行监视器的gmheap。)
文件列表在您的单元测试根目录下的coverage.list文件中;单元测试的不同子目录(套件)可以拥有它们自己的副本,以覆盖在测试套件运行时将跟踪的类/例程。
有关objectscript-math的简单示例,见:https://github.com/timleavitt/ObjectScript-Math/blob/master/tests/UnitTest/coverage.list;测试覆盖率工具用户指南有更详细的介绍。
要在启用测试覆盖率测量的情况下运行单元测试,只需再向命令添加一个参数,指定应使用TestCoverage.Manager而非%UnitTest.Manager 来运行测试:
```zpm "objectscript-math test -only -DUnitTest.ManagerClass=TestCoverage.Manager" ```
输出(即使是非详细模式)将包括一个URL,供您查看您的类/routine(宏)的哪些行被单元测试覆盖了,以及一些汇总统计信息。
## 接下来的步骤
这些能不能在CI中自动化?能不能报告单元测试的结果和覆盖率分数/差异?答案是:能!您可以使用Docker,Travis CI和codecov.io来试一下这个简单示例,见https://github.com/timleavitt/ObjectScript-Math;我打算以后写篇文章来详细讲讲,介绍几种不同的方法。
文章
姚 鑫 · 五月 13, 2021
# 第五章 管理全局变量(二)
# 在全局变量中查找值
“查找全局变量字符串”页使可以在下标或选定全局变量的值中查找给定的字符串。
要访问和使用此页,请执行以下操作:
1. 显示“全局变量”页。
2. 选择要使用的全局变量。为此,请参阅“全局页简介”一节中的步骤2和3。
3. 单击查找按钮。
4. 对于查找内容,输入要搜索的字符串。
5. (可选)清除大小写匹配。默认情况下,搜索区分大小写。
6. 单击Find First或Find All。
然后,页面显示选定全局变量中下标或值包含给定字符串的第一个节点或所有节点。该表左侧显示了节点下标,右侧显示了相应的值。
7. 如果使用的是Find First,请根据需要单击Find Next以查看下一个节点。
8. 完成后,单击关闭窗口。
## 执行批量更换
注意:在进行任何编辑之前,请确保知道IRIS使用哪个全局系统,以及应用程序使用哪个全局系统;参见“一般建议”此选项会永久更改数据。不建议在生产系统中使用。
出于开发目的,“查找全局字符串”页面还提供了对全局节点中的值进行整体更改的选项。要使用此选项:
1. 显示“全局”页面。
2. 选择要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3
3. 单击替换按钮。
4. 使用此页面查找上一节中描述的值。
5. 为“替换为”指定一个值。
6. 单击全部替换。
7. 单击确定确认此操作。然后,页面会显示变更的预览。
8. 如果结果可以接受,请单击保存。
9. 单击确定确认此操作。
# 导出全局变量
注意:因为导入全局是非常容易的(这是一个不可逆转的变化),所以最好只导出需要导入的全局。请注意,如果导出所有全局变量,导出将包括所有包含代码的全局变量。请确保知道IRIS使用哪些全局系统,以及应用程序使用哪些全局系统;
“导出全局”页面允许导出全局。
要访问和使用此页面:
1. 显示“全局”页面。
2. 指定要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3
3. 单击导出按钮。
4. 指定要将全局文件导出到的文件。为此,请在输入服务器上的导出路径和名称字段中输入文件名(包括其绝对或相对路径名),或者单击浏览并导航到该文件。
5. 使用字符集列表选择导出文件的字符集。
6. 在页面的中央框中:选择输出格式,选择记录格式
7. 选择或清除“在此检查”以在后台运行导出...
8. 单击导出。
9. 如果文件已经存在,请单击“确定”用新版本覆盖它。
导出会创建一个. gof文件。
# 导入全局变量
注意:在导入任何全局变量之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;参见“一般建议”没有撤消选项。将全局导入现有全局(从而合并数据)后,无法将全局恢复到其以前的状态。
“导入全局”页面允许导入全局。要访问和使用此页面:
1. 显示“全局”页面。
2. 单击导入按钮。
3. 指定导入文件。为此,请在输入导入文件的路径和名称字段中输入文件(包括其绝对或相对路径名),或者单击浏览并导航到该文件。
4. 使用字符集列表选择导入文件的字符集。
5. 选择下一步。
6. 使用表中的复选框选择要导入的全局。
7. 也可以选择在后台运行导入。如果选择此项,任务将在后台运行。
8. 单击导入。
# 删除全局变量
注意:在删除任何全局变量之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;参见“一般建议”没有撤消选项。无法恢复已删除的全局。
“删除全局”页面允许删除全局。要访问和使用此页面:
1. 显示“全局”页面。
2. 选择要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3
3. 单击删除按钮。
4. 单击确定确认此操作。
# 管理任务的应用程序接口
InterSystems IRIS还提供了以下应用编程接口来执行本章中描述的一些任务:
- 类`%SYSTEM.OBJ`提供了以下方法:
- `Export()`使能够将全局导出到一个XML文件。
- `Load()`和`LoadDir()`使能够导入包含在`XML`文件中的全局。
这两者都可以通过$SYSTEM变量获得,例如:`$SYSTEM.OBJ.Export`
- 类别`%Library.Global`提供了以下方法:
- `Export()`使能够将全局导出到`.gof`和其他文件格式(不包括XML)。
- `Import()`使能够将全局导入到`.gof`和其他文件格式(不包括XML)。
`%Library.Global` 还提供了`Get()`类查询,根据给定的搜索条件,可以使用该查询来查找全局。

文章
姚 鑫 · 五月 13, 2021
# 第五章 管理全局变量(二)
# 在全局变量中查找值
“查找全局变量字符串”页使可以在下标或选定全局变量的值中查找给定的字符串。
要访问和使用此页,请执行以下操作:
1. 显示“全局变量”页。
2. 选择要使用的全局变量。为此,请参阅“全局页简介”一节中的步骤2和3。
3. 单击查找按钮。
4. 对于查找内容,输入要搜索的字符串。
5. (可选)清除大小写匹配。默认情况下,搜索区分大小写。
6. 单击Find First或Find All。
然后,页面显示选定全局变量中下标或值包含给定字符串的第一个节点或所有节点。该表左侧显示了节点下标,右侧显示了相应的值。
7. 如果使用的是Find First,请根据需要单击Find Next以查看下一个节点。
8. 完成后,单击关闭窗口。
## 执行批量更换
注意:在进行任何编辑之前,请确保知道IRIS使用哪个全局系统,以及应用程序使用哪个全局系统;参见“一般建议”此选项会永久更改数据。不建议在生产系统中使用。
出于开发目的,“查找全局字符串”页面还提供了对全局节点中的值进行整体更改的选项。要使用此选项:
1. 显示“全局”页面。
2. 选择要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3
3. 单击替换按钮。
4. 使用此页面查找上一节中描述的值。
5. 为“替换为”指定一个值。
6. 单击全部替换。
7. 单击确定确认此操作。然后,页面会显示变更的预览。
8. 如果结果可以接受,请单击保存。
9. 单击确定确认此操作。
# 导出全局变量
注意:因为导入全局是非常容易的(这是一个不可逆转的变化),所以最好只导出需要导入的全局。请注意,如果导出所有全局变量,导出将包括所有包含代码的全局变量。请确保知道IRIS使用哪些全局系统,以及应用程序使用哪些全局系统;
“导出全局”页面允许导出全局。
要访问和使用此页面:
1. 显示“全局”页面。
2. 指定要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3
3. 单击导出按钮。
4. 指定要将全局文件导出到的文件。为此,请在输入服务器上的导出路径和名称字段中输入文件名(包括其绝对或相对路径名),或者单击浏览并导航到该文件。
5. 使用字符集列表选择导出文件的字符集。
6. 在页面的中央框中:选择输出格式,选择记录格式
7. 选择或清除“在此检查”以在后台运行导出...
8. 单击导出。
9. 如果文件已经存在,请单击“确定”用新版本覆盖它。
导出会创建一个. gof文件。
# 导入全局变量
注意:在导入任何全局变量之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;参见“一般建议”没有撤消选项。将全局导入现有全局(从而合并数据)后,无法将全局恢复到其以前的状态。
“导入全局”页面允许导入全局。要访问和使用此页面:
1. 显示“全局”页面。
2. 单击导入按钮。
3. 指定导入文件。为此,请在输入导入文件的路径和名称字段中输入文件(包括其绝对或相对路径名),或者单击浏览并导航到该文件。
4. 使用字符集列表选择导入文件的字符集。
5. 选择下一步。
6. 使用表中的复选框选择要导入的全局。
7. 也可以选择在后台运行导入。如果选择此项,任务将在后台运行。
8. 单击导入。
# 删除全局变量
注意:在删除任何全局变量之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;参见“一般建议”没有撤消选项。无法恢复已删除的全局。
“删除全局”页面允许删除全局。要访问和使用此页面:
1. 显示“全局”页面。
2. 选择要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3
3. 单击删除按钮。
4. 单击确定确认此操作。
# 管理任务的应用程序接口
InterSystems IRIS还提供了以下应用编程接口来执行本章中描述的一些任务:
- 类`%SYSTEM.OBJ`提供了以下方法:
- `Export()`使能够将全局导出到一个XML文件。
- `Load()`和`LoadDir()`使能够导入包含在`XML`文件中的全局。
这两者都可以通过$SYSTEM变量获得,例如:`$SYSTEM.OBJ.Export`
- 类别`%Library.Global`提供了以下方法:
- `Export()`使能够将全局导出到`.gof`和其他文件格式(不包括XML)。
- `Import()`使能够将全局导入到`.gof`和其他文件格式(不包括XML)。
`%Library.Global` 还提供了`Get()`类查询,根据给定的搜索条件,可以使用该查询来查找全局。

文章
姚 鑫 · 七月 12, 2022
# 第三章 嵌入式Python概述(三)
## 使用 SQL
`IRIS` 中的类被投影到 `SQL`,除了使用类方法或直接全局访问之外,还允许使用查询访问数据。 `iris` 模块为提供了两种从 `Python` 运行 `SQL` 语句的不同方式。
以下示例使用 `iris.sql.exec()` 运行 `SQL SELECT` 语句以查找类名称以“`%Net.LDAP`”开头的所有类定义,返回一个包含每个名称和超类的结果集每个班级。在这里,系统类 `%Dictionary.ClassDefinition` 将 `SQL` 投影为同名表。
```java
>>> rs = iris.sql.exec("SELECT Name, Super FROM %Dictionary.ClassDefinition WHERE Name %STARTSWITH '%Net.LDAP'")
```
以下示例使用 `iris.sql.prepare()` 准备 `SQL` 查询对象,然后执行查询,将`“%Net.LDAP”`作为参数传入:
```java
>>> stmt = iris.sql.prepare("SELECT Name, Super FROM %Dictionary.ClassDefinition WHERE Name %STARTSWITH ?")
>>> rs = stmt.execute("%Net.LDAP")
```
无论哪种情况,都可以按如下方式遍历结果集,并且输出相同:
```java
>>> for idx, row in enumerate(rs):
... print(f"[{idx}]: {row}")
...
[0]: ['%Net.LDAP.Client.EditEntry', '%RegisteredObject']
[1]: ['%Net.LDAP.Client.Entries', '%RegisteredObject,%Collection.AbstractIterator']
[2]: ['%Net.LDAP.Client.Entry', '%RegisteredObject,%Collection.AbstractIterator']
[3]: ['%Net.LDAP.Client.PropList', '%RegisteredObject']
[4]: ['%Net.LDAP.Client.Search.Scope', '%Integer']
[5]: ['%Net.LDAP.Client.Session', '%RegisteredObject']
[6]: ['%Net.LDAP.Client.StringList', '%RegisteredObject']
[7]: ['%Net.LDAP.Client.ValueList', '%RegisteredObject,%Collection.AbstractIterator']
```
## 使用Globals
在 `IRIS` 数据库中,所有数据都存储在全局变量中。全局数组是持久的(意味着它们存储在磁盘上)、多维的(意味着它们可以有任意数量的下标)和稀疏的(意味着下标不必是连续的)。当您在表中存储类的对象或行时,这些数据实际上存储在全局变量中,尽管您通常通过方法或 `SQL` 访问它们并且从不直接接触全局变量。
有时将持久数据存储在全局变量中会很有用,而无需设置类或 `SQL` 表。在 `IRIS` 中,全局变量看起来很像任何其他变量,但它在名称前用插入符号 (`^`) 表示。以下示例将工作日的名称存储在当前命名空间的全局 `^Workdays` 中。
```java
>>> myGref = iris.gref('^Workdays')
>>> myGref[None] = 5
>>> myGref[1] = 'Monday'
>>> myGref[2] = 'Tuesday'
>>> myGref[3] = 'Wednesday'
>>> myGref[4] = 'Thursday'
>>> myGref[5] = 'Friday'
>>> print(myGref[3])
Wednesday
```
第一行代码 `mmyGref = iris.gref('^Workdays') ` 获取一个全局引用(或 `gref`),指向一个名为 `^Workdays` 的全局引用,它可能已经存在也可能不存在。
第二行 `myGref[None] = 5` 将工作日数存储在 `^Workdays` 中,不带下标。
第三行 `myGref[1] = 'Monday'` 将字符串 `Monday` 存储在位置 `^Workdays(1)` 中。接下来的四行将剩余的工作日存储在位置 `^Workdays(2)` 到 `^Workdays(5)` 中。
最后一行 `print(myGref[3])` 显示了如何在给定 `gref` 的情况下访问存储在全局中的值。
# 一起使用 ObjectScript 和 Python
`IRIS` 让 `ObjectScript` 和 `Python` 程序员的混合团队轻松协作。例如,类中的一些方法可以用 `ObjectScript` 编写,而另一些可以用 `Python` 编写。程序员可以选择用他们最熟悉的语言编写,或者更适合手头任务的语言。
## 创建混合 InterSystems IRIS 类
下面的类有一个用 `Python` 编写的 `Print()` 方法和一个用 `ObjectScript` 编写的 `Write()` 方法,但它们在功能上是等效的,并且可以从 `Python` 或 `ObjectScript` 调用这两种方法。
```java
Class Sample.Company Extends (%Persistent, %Populate, %XML.Adaptor)
{
/// The company's name.
Property Name As %String(MAXLEN = 80, POPSPEC = "Company()") [ Required ];
/// The company's mission statement.
Property Mission As %String(MAXLEN = 200, POPSPEC = "Mission()");
/// The unique Tax ID number for the company.
Property TaxID As %String [ Required ];
/// The last reported revenue for the company.
Property Revenue As %Integer;
/// The Employee objects associated with this Company.
Relationship Employees As Employee [ Cardinality = many, Inverse = Company ];
Method Print() [ Language = python ]
{
print ('\nName: ' + self.Name + ' TaxID: ' + self.TaxID)
}
Method Write() [ Language = objectscript ]
{
write !, "Name: ", ..Name, " TaxID: ", ..TaxID
}
}
```
此 `Python` 代码示例展示了如何使用 `%Id=2` 打开 `Company` 对象并调用 `Print()` 和 `Write()` 方法。
```java
>>> company = iris.cls("Sample.Company")._OpenId(2)
>>> company.Print()
Name: IntraData Group Ltd. TaxID: G468
>>> company.Write()
Name: IntraData Group Ltd. TaxID: G468
```
此 `ObjectScript` 代码示例展示了如何打开相同的 `Company` 对象并调用这两种方法。
```java
SAMPLES>set company = ##class(Sample.Company).%OpenId(2)
SAMPLES>do company.Print()
Name: IntraData Group Ltd. TaxID: G468
SAMPLES>do company.Write()
Name: IntraData Group Ltd. TaxID: G468
```
## 在 Python 和 ObjectScript 之间传递数据
虽然 `Python` 和 `ObjectScript` 在许多方面都兼容,但它们有许多自己的数据类型和结构,有时在将数据从一种语言传递到另一种语言时需要进行一些数据转换。之前看到了一个示例,即从 `ObjectScript` 向 `Python` 传递命名参数的示例。
`%SYS.Python` 类的 `Builtins()` 方法为提供了一种方便的方式来访问 `Python` 的内置函数,它可以帮助创建 `Python` 方法所期望的类型的对象。
以下 `ObjectScript` 示例创建两个 `Python` 数组 `newport` 和 `cleveland`,每个数组都包含一个城市的纬度和经度:
```java
USER>set builtins = ##class(%SYS.Python).Builtins()
USER>set newport = builtins.list()
USER>do newport.append(41.49008)
USER>do newport.append(-71.312796)
USER>set cleveland = builtins.list()
USER>do cleveland.append(41.499498)
USER>do cleveland.append(-81.695391)
USER>zwrite newport
newport=11@%SYS.Python ; [41.49008, -71.312796] ;
USER>zwrite cleveland
cleveland=11@%SYS.Python ; [41.499498, -81.695391] ;
```
下面的代码使用在前面的示例中看到的 `geopy` 包来计算纽波特,罗德岛和克利夫兰,俄亥俄州之间的距离。它使用 `geopy.distance.distance()` 方法创建一条路线,将数组作为参数传递,然后打印路线的英里属性。
```java
USER>set distance = $system.Python.Import("geopy.distance")
USER>set route = distance.distance(newport, cleveland)
USER>write route.miles
538.3904453677205311
```
注意: `geopy.distance.distance()` 方法实际上期望参数是 `Python` 元组数据类型,但数组也可以。
## 运行 Python 命令
当开发或测试某些东西时,有时运行一行 `Python` 代码以查看它的作用或是否有效可能会很有用。在这种情况下,可以使用 `%SYS.Python.Run()` 方法,如下例所示:
```java
USER>set rslt = ##class(%SYS.Python).Run("print('hello world')")
hello world
```
针对于字典如何初始化呢,目前是有异常,望指教
文章
Frank Ma · 三月 2, 2022
好人不需要规则。
神秘博士
要成为日期和时间的主人并不是一件容易的事,在任何编程语言中,这总是一个问题,有时会让人感到困惑,我们将澄清并提出一些提示,使这项任务尽可能简单。
坐上TARDIS,我将把你变成一个时间领主。
让我们从基本知识开始
如果你通常使用其他语言,请记住,Intersystems Object Script(以下简称IOS,不要与苹果手机混淆)的日期有点特殊。当我们在终端运行$HOROLOG 命令时,为了得到当前的日期和时间,你会看到它被分为两部分:
WRITE $HOROLOG
> 66149,67164
第一个值是天数,确切地说,是自1840年12月31日以来的天数,也就是说,值1是1841年1月1日;第二个值是自今天00:00以来的秒钟。
在这个例子中,66149对应于09/02/2022(欧洲格式的日/月/年的2月9日),67164对应于18:39:24。我们将这种格式称为数据和时间的内部格式。
感到困惑吗?好吧,我们将开始揭示宇宙的伟大秘密(日期和时间)。
如何将内部格式转换为更清晰的格式?
为此,我们将用到命令 $ZDATETIME
基本的命令是
SET RightNow = $HOROLOG
WRITE RightNow
> 66149,67164
WRITE $ZDATETIME(RightNow)
> 02/09/2022 18:39:24
默认情况下,它使用美国格式月/日/年(mm/dd/yyyy)。如果你想使用其他格式的日期,我们将使用第二个参数,比如欧洲格式日/月/年(dd/mm/yyyy),在这种情况下,我们将给它一个值4(关于更多的格式,见文档$ZDATETIME.dformat)。
SET RightNow = $HOROLOG
WRITE RightNow
> 66149,67164
WRITE $ZDATETIME(RightNow,4)
> 09/02/2022 18:39:24
该选项使用我们在本地变量中定义的分隔符和年份格式。
如果我们还想放另一种时间格式,例如12小时格式(AM/PM)而不是24小时格式,我们使用第三个参数,其值为3,如果我们不想显示秒,我们将使用值4(见文件$ZDATETIME.tformat)。
SET RightNow = $HOROLOG
WRITE RightNow
> 66149,67164
WRITE $ZDATETIME(RightNow,4,3)
> 09/02/2022 06:39:24PM
WRITE $ZDATETIME(RightNow,4,4)
> 09/02/2022 06:39PM
现在是不是更清楚了?那么让我们更深入地了解一下。
ODBC 格式
这个格式与你的本地配置无关,它将始终显示为年/月/日格式 yyyy-mm-dd,其值为3。 如果我们想创建要导出文件的数据,如CSV、HL7文件等,建议使用它。
SET RightNow = $HOROLOG
WRITE RightNow
> 66149,67164
WRITE $ZDATETIME(RightNow,3)
> 2022-02-09 18:39:24
一周的日子,星期名称,一年中的某天
Value 值
描述
10
一周的日子将是一个介于0和6之间的值,0代表星期天,6代表星期六。
11
星期的缩写名称,它将根据你定义的本地配置返回,IRIS的默认安装是 enuw (English, United States, Unicode)
12
长格式的星期名称,与11相同。
14
一年中的某一天,自1月1日以来的天数。
如果我们只是想分别处理日期和时间,应该分别使用$ZDATE和$ZTIME命令。格式的参数与 $ZDATETIME.dformat 和 $ZDATETIME.tformat中定义的参数相同。
SET RightNow = $HOROLOG
WRITE RightNow
> 66149,67164
WRITE $ZDATE(RightNow,10)
> 3
WRITE $ZDATE(RightNow,11)
> Wed
WRITE $ZDATE(RightNow,12)
> Wednesday
那我如何将日期转换为内部格式?
好了,现在我们来看看相反的步骤,即有一个带有日期的文本,并将其转换成IOS格式。对于这个任务,我们将使用命令 $ZDATETIMEH。
这一次,我们必须指出日期和时间的格式(如果我们使用$ZDATETIMEH),或者分别指出日期($ZDATEH)和时间($ZTIMEH)。
格式是相同的,也就是说,如果我们有一个ODBC格式(yyyy-mm-dd)的日期字符串,那么我们将使用值3。
SET MyDatetime = "2022-02-09 18:39:24"
SET Interna1 = $ZDATETIMEH(MyDatetime, 3, 1) // ODBC Format
SET MyDatetime = "09/02/2022 18:39:24"
SET Interna2 = $ZDATETIMEH(MyDatetime, 4, 1) // European format
SET MyDatetime = "02/09/2022 06:39:24PM"
SET Interna3 = $ZDATETIMEH(MyDatetime, 1, 3) // American format with time in 12h AM/PM
WRITE Interna1,!,Interna2,!,Interna3
> 66149,67164
66149,67164
66149,67164
从逻辑上讲,如果我们说字符串使用的是一种特殊的格式,而我们给它提供了错误的参数,那么任何事情都可能发生,比如它理解为2月9日,而不是9月2日。
不要混合格式,这样以后会出现问题。
SET MyDatetime = "09/02/2022"
/// American format
SET InternalDate = $ZDATEH(MyDatetime, 1)
/// European format
SET OtherDate = $ZDATETIME(InternalDate, 4)
WRITE InternalDate,!,OtherDate
> 66354
02/09/2022
不用说,如果我们试图设定一个欧洲的日期并试图将其转化为美国的日期...... 在情人节会发生什么?
SET MyDatetime = "14/02/2022"
SET InternalDate = $ZDATEH(MyDatetime, 1) // American format. month 14 doesn't exists!!!
^
<ILLEGAL VALUE>
嗯,就像所有的情人节一样......破碎的心,嗯......在这种情况下,破碎的代码。
好吧,让我们用你已经学到的东西做一些事情。
READ !,"Please indicate your date of birth (dd/mm/yyyy): ",dateOfBirth
SET internalFormat = $ZDATEH(dateOfBirth, 4)
SET dayOfWeek= $ZDATE(internalFormat, 10)
SET nameOfDay = $ZDATE(internalFormat, 12)
WRITE !,"The day of the week of your birth is: ",nameOfDay
IF dayOfWeek = 5 WRITE "you always liked to party!!!" // was born on friday
以后我们将看到其他的做事方法,以及如何处理错误。
下一章:如何进行时间旅行
好奇
如果你想知道为什么01/01/1841的值被当作1的值,那是因为选择这个日期是因为它是在世的最年长的美国公民出生前的非闰年,当MUMPS编程语言被设计时,他是一个121岁的内战老兵,它从这个语言中扩展了对象脚本。
文章
Michael Lei · 四月 17, 2022
在最近一次探索马里兰小镇的 "度假 "期间,我偶然发现了一家非常令人愉快的书店,在那里我愉快地消磨了一下午。我和我的家人都是读者,喜欢各种类型的书--新的、二手的、印刷的、电子的。我们尽量在当地购物,以帮助零售店保持运营。
这次访问促使我思考图书行业所发生的事情与我们的医疗保健系统所发生的事情之间的一些相似之处。
医疗保健行业与图书行业的趋势
数字化
我们阅读内容的格式已经发生了根本性的变化。在2020年,电子书几乎占美国市场的四分之一。音频书占美国图书收入的10亿美元。许多印刷书籍是按需出版的,而不是保存在库存中。同样,医疗保健早已不再是一个“伸出舌头说啊 ”的行业,基因组测试、由人工智能算法读取的X射线、可植入设备和远程医疗访问已经改变了医疗的面貌。
虚拟服务
书店现在有多种形式,医疗机构也是如此。订阅图书服务,从当地独立的小公司、大的连锁店、电子零售的网上订单。而与你的本地门诊竞争的是你手机上的一个应用程序。同样,你的治疗师可能是一个机器人,你的基层医疗服务可能由你社区附近药店的驻店医师提供,你可能在一个办公园区做手术。在所有这些竞争中,我们如何确保在我们需要时仍有健康的、提供全面服务的医院?
更智能的算法
分析和预测模型现在几乎和个人推荐一样重要。过去,当我想要一本书的建议,或者一个医生,我就会问朋友。虽然我仍然这样做,但我也同样有可能去看Goodreads,或查看在线医生评论。当我进行搜索时,亚马逊、苹果或谷歌也同样可能提供他们的建议,不管我是否要求它们。他们知道我是谁,我的购买模式是什么,我检查过哪些疾病和症状,以及在当地急诊科订购书籍或看医生的等待时间是什么。
合并和收购
无论你是卖书还是卖医疗服务,改变或死亡都是关键词。我们附近的一家大的巴诺书店(Barnes & Nobles,美国最大的实体书店)最近搬到了一个不到以前一半的地方。大多数独立书店出售的礼品和书籍一样多,而且许多书店同时出售新货和二手货。像Alibris这样的网站将当地的小企业与世界各地的买家联系起来。同样,根据普华永道的数据,2021年医疗保健业的合并和收购增长了56%,预计这一趋势在2022年还会继续。IQVIA艾昆纬研究所的一份报告发现,在主要的应用程序商店中,有大约35万个数字健康应用程序。而美国最大的零售商都在医疗保健领域进行了大量投资。例如,亚马逊是颠覆性的电子书业务的主要参与者,现在也有实体书店,它正积极地进入医疗保健服务领域,包括线上和线下服务。
对未来医疗的影响
对未来医疗的影响是什么?有趣的是,这两种业务都唤起了人们对也许是神话般的过去的相当大的怀念。虽然现在的实体书店比以前少了,但书籍实际上比以前更容易获得。对很大一部分人来说,亲切的家庭医生上门服务从来就不是那么容易的,而且不同地区采用的差异巨大的护理标准也不一定能带来最好的结果。
新世界秩序的便利性和选择令人难以置信地吸引人,无论我是在手机上购买一本书,还是在街上走到我附近的CVS公司购买Covid 疫苗加强针。但是,一方面浏览完所有这些选择也会让人感到困惑,另一方面我不想失去浏览当地商店的货架或者我年迈的母亲随时获得住院床位的选择。
没有任何整齐划一的策略可以向前推进。因此,作为一个消费者,我将继续光顾本地小店来帮助他们经营下去。作为一名医疗IT专业人士,我将继续关注如何利用信息来指导未来,带着一点点害怕,但更多的还是兴奋来展望未来。
关于作者:
Kathleen Aller负责InterSystems公司的医疗市场战略。她在医疗和技术领域有多年的经验,在分析、患者管理、电子健康记录、医疗信息共享以及质量和绩效评估方面有专长。
博客原文:https://www.intersystems.com/pulse-blog/browsing-the-future-of-healthcare
文章
姚 鑫 · 六月 14, 2023
# 第五十七章 镜像中断程序 - 在手动故障转移之前确定备份是否处于活动状态
## 在手动故障转移之前确定备份是否处于活动状态
假设有两个名为 `IRIS A` 和`IRIS B` 的故障转移成员。如果 `^MIRROR` 例程确认备份 (`IRIS B`) 在与主 (`IRIS A`) 丢失联系时处于活动状态,因此具有最新的来自 `IRIS A` 的日志数据,可以使用单个过程手动进行故障转移。当连接因主要故障而丢失时,不会造成数据丢失的风险。但是,当发生多个故障时,活动备份可能没有来自主服务器的所有最新日志数据,因为主服务器在连接丢失后继续运行了一段时间。
使用以下过程确定备份是否处于活动状态:
1. 确认 `IRIS` 实例 `IRIS A` 上的 `ISCAgent` 实际上已关闭(并确保它们在整个手动故障转移过程中保持关闭状态)。
2. 在 `IRIS B` 上,在终端的 `%SYS` 命名空间中运行 `^MIRROR` 例程(请参阅使用 `^MIRROR` 例程)。
3. 在主菜单中选择镜像管理,显示如下子菜单:
```java
1) Add mirrored database(s)
2) Remove mirrored database(s)
3) Activate or Catchup mirrored database(s)
4) Change No Failover State
5) Try to make this the primary
6) Connect to Mirror
7) Stop mirroring on this member
8) Modify Database Size Field(s)
9) Force this node to become the primary
10) Promote Async DR member to Failover member
11) Demote Backup member to Async DR member
12) Mark an inactive database as caught up
13) Manage mirror dejournaling on async member (disabled)
14) Pause dejournaling for database(s)
```
4. 选择 `Force this node to become the primary` 选项。如果在联系丢失时备份处于活动状态,则会显示如下消息:
```java
This instance was an active backup member the last time it was
connected so if the primary has not done any work since that time,
this instance can take over without having to rebuild the mirror
when the primary reconnects. If the primary has done any work
beyond this point (file #98),
C:\InterSystems\MyIRIS\mgr\journal\MIRROR-GFS-20180815.009
then the consequence of forcing this instance to become the primary is
that some operations may be lost and the other mirror member may need
to be rebuilt from a backup of this node before it can join as
a backup node again.
Do you want to continue?
```
如果有权访问主要文件的日志文件,则可以在继续之前确认引用的文件是最新的。
如果在与主服务器失去联系时备份未处于活动状态,则会显示如下消息:
```java
Warning, this action can result in forcing this node to become
the primary when it does not have all of the journal data which
has been generated in the mirror. The consequence of this is that
some operations may be lost and the other mirror member may need
to be rebuilt from a backup of this node before it can join as
a backup node again.
Do you want to continue?
```
## 手动故障转移到活动备份
如果 `^MIRROR` 例程的 `Force this node to become the primary` 选项确认备份在失去与主节点的连接时处于活动状态,请完成手动故障转移过程,如下所示:
1. 在要继续吗?提示继续该过程。 `Force this node to become the primary` 选项等待 `60` 秒以使镜像成员成为主要节点。如果操作未在 `60` 秒内成功完成,`^MIRROR` 报告操作可能未成功并指示您检查消息日志以确定操作是失败还是仍在进行中。
2. 一旦 `^MIRROR` 例程确认备份已成为主要备份,请在可以这样做时重新启动 `IRIS A`。当 `IRIS` 实例重新启动时, `IRIS A` 作为备份加入镜像。
## 备份不活动时手动故障转移
即使 `^MIRROR` 例程未确认备份 ( `IRIS B`) 在与主 ( `IRIS A`) 失去连接时处于活动状态,仍然可以使用以下过程继续手动故障转移过程,但是如果这样做,会有数据丢失的风险。如本程序所述,可以在手动故障转移之前将最新的镜像日志文件从 `IRIS A`(如果有权访问)复制到 `IRIS` B,从而最大限度地降低这种风险。
1. 如果有权访问主服务器的镜像日志文件,请将最新的文件复制到 `IRIS B`,从 `IRIS B` 上的最新日志文件开始,然后包括来自 `IRIS A` 的任何后续文件。例如,如果 `MIRROR-MIRRORA-20180220.001`是 `IRIS B` 上的最新文件,复制 `MIRROR-MIRRORA-20180220.001` 和 `IRIS A` 上的任何更新文件。检查文件的权限和所有权,并在必要时更改它们以匹配现有日志文件。
2. 如果接受数据丢失的风险,请在提示时输入 `y` 以确认要继续;备份成为主要的。 `Force this node to become the primary` 选项等待 `60` 秒以使镜像成员成为主要节点。如果操作未在 `60` 秒内成功完成,`^MIRROR` 报告操作可能未成功并指示您检查消息日志以确定操作是失败还是仍在进行中。
3. 一旦 `^MIRROR` 例程确认备份已成为主要备份,请在可以这样做时重新启动 `IRIS A`。
- 如果 `IRIS A` 在 `IRIS` 实例重新启动时加入镜像作为备份,则不需要进一步的步骤。任何在故障成员上但不在当前主成员上的日志数据都已被丢弃。
- 如果在 `IRIS` 实例重新启动时 `IRIS A` 无法加入镜像,如重建镜像成员中描述的引用不一致数据的消息日志消息所示 `IRIS A` 上的最新数据库更改晚于最新的日志数据当 `IRIS B` 被迫成为主服务器时,它会出现在 `IRIS B` 上。要解决此问题,请按照该部分中的描述重建 `IRIS A`。
文章
Michael Lei · 六月 18, 2023
在数字化时代,数据的重要性无可置疑。数据作为新型生产要素,不仅在宏观政策层面得到党和政府的大力推动,也是医院高质量发展的关键和改变医疗行业的驱动力。随着医疗信息化的迅猛发展,我们正迈向一个数据随处可及、人人可用易用的医疗信息化时代。这一时代将数据与人的需求相结合,致力于让数据能“主动”找到需要他们的医护人员和患者,每一个行业从业者,都应致力于为医护人员和患者提供简单易用的软件解决方案,减少工作量,提高效率,推动医疗行业的进步。
数据与人的融合是实现医疗行业数字化转型的核心。当然,医疗数据的收集、存储和管理对于提供高质量的医疗服务至关重要。然而,仅仅有大量的数据并不足够,我们需要将数据与人的需求紧密结合起来。这意味着我们应该让更多的数据关联起来,并且能服务于更多的人群,让患者能够随时随地访问他们的电子病历,让医生和科研人员也能及时有效地获取病人在医院围墙内外进行治疗和健康管理的数据,并且以直观易懂的方式呈现给医护人员和患者,使他们能够快速、准确地获取所需的信息。数据的融合还包括将不同来源的数据整合起来,为医护人员提供全面、完整的视图,同时基于医疗诊断的规则,不管是通过CDSS的形式,还是通过ChatBot(聊天机器人),帮助他们做出更好的决策。
实现数据和人的融合要按照人的需求投放数据。数字化转型的重要目标是为医护人员和患者提供所需的数据,以支持决策和治疗过程。这意味着我们应该了解用户的需求,将数据按照他们的角色、职责和关注点进行分类和投放。医生可能需要即时的患者数据、病历历史和最新的医学研究,而患者可能需要查看自己的健康记录、预约医生和接收个性化的健康建议。通过根据人的需求进行数据投放,新型软件可以提供个性化的服务和支持,形成千人千面,为每个用户提供有价值的信息。
简单易用是实现数字化转型成功的另一个关键。医护人员和患者使用的软件解决方案应该简单易用,不需要复杂的培训和技术知识。界面应该简单、直观、友好,操作流程简化和优化,以确保用户能够快速上手并高效地使用软件。简单易用的软件不仅能够减少用户的学习曲线和工作负担,还能提高用户满意度和工作效率。(比如Apple的医疗软件Apple Health,通过FHIR 技术,通过一个app能够连接数千家医院的病历数据,让患者可以通过一个app实现多家医院的互联网服务和数据整合)
无论是数字化转型还是高质量发展,软件为人服务始终是医疗信息化的核心宗旨。我们应该将软件看作是为人服务的工具,旨在帮助医护人员提供更好的医疗服务,提升患者的体验和健康结果。软件应该以用户体验为中心,并不断优化和改进,不断进行供给侧改革,以满足不断变化和不同人群的需求,而不是增加负担。
最后,数据会在安全可靠的前提下进行传递和流通。在互联网发展的早期时代,由于无法可依,野蛮生长,数据的滥用、隐私保护等存在很大问题。但随着《数据安全法》等法律法规的发布,相信未来的医疗行业数据一定会在更加安全、可靠、合规的前提下进行有序流动。
在未来的医疗信息化发展中,数据与人的关系将变得更加密不可分。通过数据的融合、按需投放、简单易用、安全可靠和以人为本的新一代软件,我们可以实现数据随处可及、人人可用易用的医疗信息化目标。这将为医护人员和患者提供更好的工作环境和医疗体验,推动整个医疗行业向前迈进。InterSystems公司作为创新性的数据平台解决方案供应商,我们始终致力于助力合作伙伴开发创新的解决方案,与合作伙伴一起共同实现这一愿景,改善医疗服务的质量和效率,提高患者体验的获得感的同时帮助医院降本增效,实现高质量发展。
文章
Michael Lei · 六月 2, 2022
IRIS Interoperability互操作性/HealthConnect(前身是Ensemble)有许多内置的适配器。但是没有一个接收邮件的服务或适配器。我洗的了一个电子邮件服务,通过SMTP接收邮件,这些邮件可以被传递到电子邮件操作。
现在我想对一个使用电子邮件操作向外部邮件服务器发送邮件的Production进行负载测试。邮件服务器团队不希望我向他们发送成千上万的信息。
我创建了iris-mail应用程序来替代邮件服务器。我更新了电子邮件操作中的服务器和端口设置。外发的邮件被发送到替代的邮件服务器,我能够计算出iris-mail中收到的邮件数量,并将其与邮件操作发送的邮件数量进行比较。
应用程序的源代码:https://openexchange.intersystems.com/package/iris-mail
文章
Michael Lei · 三月 17, 2022
来自奥兰多的报道,Epic公司的医生Jackie Gerhart博士在HIMSS22上讨论了电子病历的庞大研究数据库及其发现如何形成个人服务。
新冠疫情的发生证明了随时获取数据的重要性。在未知的病毒海洋中,全人群的分析有助于为公共卫生官员、医疗服务供应商和患者提供洞察力。
现在,在Epic公司从事临床信息学工作的医生Jackie Gerhart说,这家全球领先的电子病历厂商正在希望帮助临床医生使用数据来形成个性化患者服务。
Gerhart在HIMSS22会议上与《医疗保健IT新闻》坐下来讨论了Epic广泛的病人数据库、Epic研究结果以及其即将推出的 "我的病人的最佳护理(Best Care for My Patient "工具如何向临床医生展示全国各地的最有效实践。
问: 作为一名医生,你是如何帮助Epic形成产品的?
答: 我们的作用是,第一,帮助软件的研究和开发;第二,新的面向未来的项目,使得相关的数据和分析可以在护理节点或未来的研究中使用。
然后,我们还与我们目前的医疗机构合作,确保他们得到真正好的客户经验。例如,你可能在不同的头条新闻中看到,临床医生不喜欢他们的电子病历,或者就像众所周知的,"哦,我的上帝,我还得在晚上做这个。"
我们的部分工作是试图从软件内部以不同的方式减少这种情况,并确保在内部的处理过程是尽可能有效的,这样我们就不会陷入比实际需要更多的过多记录笔记的工作中。所以这就是医生在Epic的主要作用。
问: 我们已经听到了很多关于Epic的Cosmos数据库的消息,它利用了来自客户的匿名化病人记录。公共卫生官员和临床医生如何利用它来获得全体人群的有效信息?
答:目前,Cosmos包含了1.4亿名患者和22亿次临床医生的记录。与其他许多医疗数据库不同,我们的目标是必须向它持续提供数据,以便能够使用它的数据--这意味着社区中的不同客户可以选择成为Cosmos的一部分,如果他们加入了,我们就从他们的病人那里获取匿名化的数据,即所谓有限数据集(Limited Data Set)。然后,这个有限的数据集与所有其他参与的组织一起被使用。
我们将这些数据用于研究,所以任何为Cosmos提供数据的组织都可以对数据进行查询。
作为一个数据库,Cosmos有三个不同之处。第一,你可以快速搜索它。第二点--当我们与CDC合作时,CDC告诉我们他们是主要看中我们的库对美国人口的代表程度与我们合作。我们的目标是努力获得尽可能清晰和接近于美国人口的代表性。我们一直在疫情期间与CDC做了大量的工作,特别是在CDC可能无法立即获得的较大的数据集上工作,或者他们可能没有一个有代表性的样本。
例如,在上个月,他们的一个MMWRs与新冠的突破性感染有关。他们有来自纽约公共卫生的数据,也有来自加利福尼亚公共卫生的数据。但同样,这是两个特定的人群,不一定代表整个美国。因此,我们一直在合作,通过Cosmos来查看。我们的研究团队和我们的数据科学家将与CDC一起设计一项研究,我们将运行数据,然后我们将有望一起工作,把它放到记录中。这就是第三个差异化因素:数据库的规模。
问: 对这些数据发现的审查过程是怎样的?
答:有三种情况。第一种是内部的,它是一个两组的过程。因此,在一个团队中,有一个临床医生,一个数据科学家,通常还有一些公共卫生或研究背景的人。然后在另一个团队中也有同样的人员构成。他们都独立设计出一种方法来回答一个广泛的问题。然后双方讨论,看对方的方法,一起工作,找出最合理的方法--然后他们再回去分开各自工作,用具体的方法重新编码一切。然后他们整合在一起,看看他们的结果是否有效。同样,这更像是一种准确性的检查。
在这两个团队的过程完成后,如果是在内部完成的,那么我们将其发布在我们的EpicResearch.org网站上。它不一定得出任何具体的因果关系。其目的是说,"我们调查了这个。这是初步的发现,现在,社区里的其他人去更多地研究这个问题,做一些更严格的统计分析。"
第二种是协作。这有点像我刚才和CDC谈的那样。我们还与凯撒家庭基金会和其他一些不同的组织如学术中心进行了合作。在这些情况下,它可以有两种方式:一种是跟刚才说的内部审查类似的;另一种是我们把它提交给一个杂志,然后杂志做他们自己的同行评审过程。
我们也有媒体的成员,他们想验证某个事情。这算是第三类,没有经过同行评议,实际上就是回答一个问题,把数据发给他们,然后让他们和他们的内部分析人员进行分析。
问:所以,这些都是对整个群体的洞察力。但你能和我谈谈 "对病人的最佳护理",以及它是如何利用Epic数据库的?
答:我们认为这个数据集有两个用途。一个是研究,也就是我刚才跟你谈的。然后是 "最佳护理",即专注于我作为临床医生在办公室里面对的每一个病人。
比方说,你是一个30岁的女性,因高血压来就诊,你也有过敏症,而且你正在服用胆固醇药物。在这种情况下,我想知道如何治疗你的血压。我们的想法是,根据整个社区其他地方的观察数据,尝试做个性化和个体化的医疗。
因此,数据库里有1.4亿病人。然后我把你的标准放进去--比方说把它降到10万个病人,然后我决定我还想包括你的高血脂症。所以现在我们大概有40,000名患者。在所有这4万名患者中,当他们的临床医生面临如何治疗高血压的确切问题时,这些临床医生使用了什么?他们的结果是什么?“病人最佳护理” 将显示这一部分。
问:作为一名临床医生,鉴于许多人已经面临着尽可能多看病人的巨大压力,你认为临床医生有时间这样做吗?
答:我将如何解决这个问题?如果我想用的话,这是我的工具带中又多了一个工具可以使用。我可能认识一个病人5年、10年、15年,并对我想做什么来治疗他们有了一个想法。但是,可能有一个不同的病人,我对他不太熟悉,或者有其他的疾病,而我没有阅读过相关的文献--比如,如果他们有这三种疾病,你该怎么治疗?
所以它只是给我提供了一点更多的信息。它的目的是非常简单和容易操作,这样你就可以在护理节点使用它。如果你想深入研究,那么你可以进一步探索数据。但对于普通的日常临床医生来说,我们的目标是尽量减少干预,只是向你提供数据,然后你选择如何使用它。
问:所以这个实施还没有上线,对吗?计划什么时候上线?
答:我们计划一到一年半左右的时间。
原文链接:https://www.healthcareitnews.com/news/best-care-my-patient-will-give-clinicians-data-driven-treatment-insights-says-epic
文章
姚 鑫 · 四月 16, 2021
# 第二章 定义和构建索引(四)
# 位片索引
当数字数据字段用于某些数值运算时,位片索引用于该字段。位片索引将每个数值数据值表示为二进制位串。位片索引不是使用布尔标志来索引数值数据值(如在位图索引中那样),而是以二进制值表示每个值,并为二进制值中的每个数字创建一个位图,以记录哪些行的该二进制数字具有1。这是一种高度专门化的索引类型,可以显著提高以下操作的性能:
- `SUM`、`COUNT`或`AVG` Aggregate计算。(位片索引不用于`COUNT(*)`计算。)。位片索引不用于其他聚合函数。
- 指定的字段 `TOP n ... ORDER BY field`
- 在范围条件运算中指定的字段,`WHERE field > n` 或 `WHERE field BETWEEN lownum AND highnum`、
SQL优化器确定是否应该使用定义的位片索引。通常,优化器仅在处理大量(数千)行时才使用位片索引。
可以为字符串数据字段创建位片索引,但位片索引将这些数据值表示为规范数字。换句话说,任何非数字字符串(如`“abc”`)都将被索引为0。这种类型的位片索引可用于快速计数具有字符串字段值的记录,而不计算那些为空的记录。
在下面的例子中,`Salary`是位片索引的候选项:
```sql
SELECT AVG(Salary) FROM SalesPerson
```
位片索引可用于使用`WHERE`子句的查询中的聚合计算。如果`WHERE`子句包含大量记录,则这是最有效的。在下面的示例中,SQL优化器可能会使用`Salary`上的位片索引(如果已定义);如果定义了位片索引,它还会使用`REGION`上的位图索引,使用定义的位图或为`REGION`生成位图临时文件:
```sql
SELECT AVG(Salary) FROM SalesPerson WHERE Region=2
```
但是,当索引无法满足`WHERE`条件时,不使用位片索引,而必须通过读取包含要聚合的字段的表来执行。以下示例将不使用`Salary`的位片索引:
```sql
SELECT AVG(Salary) FROM SalesPerson WHERE Name LIKE '%Mc%'
```
可以为任何包含数值的字段定义位片索引。InterSystems SQL使用`Scale`参数将小数转换为位字符串,如ObjectScript `$factor`函数中所述。可以为数据类型字符串的字段定义位片索引;在这种情况下,出于位片索引的目的,非数字字符串数据值被视为`0`。
可以为系统分配的行ID为正整数值的表中的字段定义位片索引,也可以为使用`%BID`属性定义以支持位图(和位片)索引的表中的字段定义位片索引。
位片索引只能为单个字段名定义,不能为多个字段的连接定义。
不能指定`WITH DATA`子句。
下面的例子比较了位片索引和位图索引。
如果你为1、5和22行创建一个位图索引,它会为这些值创建一个索引:
```java
^gloI("bitmap",1,1)= "100"
^gloI("bitmap",5,1)= "010"
^gloI("bitmap",22,1)="001"
```
如果为第1、2和3行的值1、5和22创建位切片索引,则会首先将这些值转换为位值:
```java
1 = 00001
5 = 00101
22 = 10110
```
然后,它为这些位创建索引:
```java
^gloI("bitslice",1,1)="110"
^gloI("bitslice",2,1)="001"
^gloI("bitslice",3,1)="011"
^gloI("bitslice",4,1)="000"
^gloI("bitslice",5,1)="001"
```
在本例中,位图索引中的值22需要设置1个全局节点;位片索引中的值22需要设置3个全局节点。
请注意,插入或更新需要在所有`n`个位片中设置一个位,而不是设置单个位串。这些附加的全局设置操作可能会影响涉及填充位片索引的插入和更新操作的性能。使用`INSERT`、`UPDATE`或`DELETE`操作填充和维护位片索引比填充位图索引或常规索引慢。维护多个位片索引和/或在频繁更新的字段上维护位片索引可能具有显著的性能成本。
在易失性表(执行许多插入、更新和删除操作)中,位片索引的存储效率可能会逐渐降低。`%SYS.Maint.Bitmap`实用程序方法同时压缩位图索引和位片索引,从而提高了还原效率。
# 重建索引
可以按如下方式构建/重新构建索引:
- 使用`BUILD INDEX` SQL命令构建指定索引,或构建为表、架构或当前命名空间定义的所有索引。
- 使用管理门户重建指定类(表)的所有索引。
- 使用`%BuildIndices()`(或`%BuildIndicesAsync()`)方法,如本节所述。
当前数据库访问确定应如何重建现有索引:
- 非活动系统(在索引构建或重建期间没有其他进程访问数据)
- `READONLY`活动系统(能够在索引构建或重建期间查询数据的其他进程)
- 读写活动系统(能够在索引构建或重建期间修改数据和查询数据的其他进程)
构建索引的首选方法是使用`%BuildIndices()`方法或`%BuildIndicesAsync()`方法。
- `%Library.Persistent.%BuildIndices()`:`%BuildIndices()`作为后台进程执行,但调用方必须等待`%BuildIndices()`完成才能接收回控制。
- `%Library.Persistent.%BuildIndicesAsync()`:`%BuildIndicesAsync()`将`%BuildIndices()`作为后台进程启动,调用方立即收到控制权。`%BuildIndicesAsync()`的第一个参数是`eueToken`输出参数。其余参数与`%BuildIndices()`相同。
`%BuildIndicesAsync()`返回`%Status`值:`Success`表示`%BuildIndices()`辅助作业已成功排队;失败表示该辅助作业未成功排队。
`%BuildIndicesAsync()`向`eueToken`输出参数返回一个值,该值指示`%BuildIndices()`完成状态。要获取完成状态,请通过引用将`eueToken`值传递`给%BuildIndicesAsyncResponse()`方法。还可以指定等待布尔值。如果`wait=1`,则`%BuildIndicesAsyncResponse()`将等待,直到由`eueToken`标识的`%BuildIndices()` JOB 完成。如果`wait=0`,`%BuildIndicesAsyncResponse()`将尽快返回状态值。如果返回时`%BuildIndicesAsyncResponse() ``eueToken`不为空,则`%BuildIndices()` job尚未完成。在这种情况下,可以使用`eueToken`再次调用`%BuildIndicesAsyncResponse()`。当`%BuildIndicesAsyncResponse()``eueToken`最终为`NULL`时,返回的`%BuildIndicesAsyncResponse()``%Status`值是`%BuildIndicesAsync()`调用的job的完成状态。
## 在非活动系统上构建索引
系统自动生成方法(由`%Persistent`类提供),这些方法构建或清除为类(表)定义的每个索引。可以通过以下两种方式之一使用这些方法:
- 通过管理门户进行交互。
- 以编程方式,作为方法调用。
构建索引执行以下操作:
1. 删除索引的当前内容。
2. 扫描(读取每一行)主表,并为表中的每一行添加索引项。如果可能,使用特殊的`$SortBegin`和`$SortEnd`函数来确保高效地构建大型索引。在构建标准索引时,除了在内存中缓存数据之外,使用`$SortBegin`/`$SortEnd`还可以使用`IRISTEMP`数据库中的空间。因此,在构建非常大的标准索引时,InterSystems IRIS可能需要`IRISTEMP`中大致等于最终索引大小的空间。
注:构建索引的方法仅为使用InterSystems IRIS默认存储结构的类(表)提供。映射到遗留存储结构的类不支持索引构建,因为它假定遗留应用程序管理索引的创建。
### 使用管理门户构建索引
可以通过执行以下操作来构建表的现有索引(重建索引):
1. 从管理门户中选择系统资源管理器,然后选择SQL。使用页面顶部的切换选项选择一个命名空间;这将显示可用命名空间的列表。选择命名空间后,选择屏幕左侧的`Schema`下拉列表。这将显示当前名称空间中的模式列表,其中带有布尔标志,指示是否有任何表或视图与每个模式相关联。
2. 从此列表中选择一个架构;该架构将显示在架构框中。它的正上方是一个下拉列表,允许选择属于该模式的表、系统表、视图、过程或所有这些。选择“表”或“全部”,然后打开“表”文件夹以列出此架构中的表。如果没有表,则打开文件夹将显示空白页。(如果未选择“表”或“全部”,则打开“表”文件夹将列出整个命名空间的表。)
3. 选择其中一个列出的表。这将显示表的目录详细信息。
- 要重建所有索引:单击操作下拉列表,然后选择重建表的索引。
- 要重建单个索引:单击索引按钮以显示现有索引。每个列出的索引都有重建索引的选项。
**注意:当其他用户正在访问表的数据时,不要重建索引。要在活动系统上重建索引,请参阅在活动系统上构建索引。**
### 以编程方式构建索引
为非活动表构建索引的首选方法是使用随表的`Persistent`类提供的`%BuildIndices()`(或`%BuildIndicesAsync()`)方法。
**若要以编程方式生成一个或多个索引,请使用`%Library.Persistent.%BuildIndices()`方法。**
生成所有索引:调用`%BuildIndices()`,不带参数生成为给定类(表)定义的所有索引(为其提供值):
```java
SET sc = ##class(MyApp.SalesPerson).%BuildIndices()
IF sc=1 {
WRITE !,"成功构建索引"
} ELSE {
WRITE !,"索引构建失败",!
DO $System.Status.DisplayError(sc) QUIT
}
```
生成指定索引:调用`%BuildIndices()`,并将`$LIST`索引名作为第一个参数,为给定类(表)生成指定的已定义索引(为其提供值):
```java
SET sc = ##class(MyApp.SalesPerson).%BuildIndices($ListBuild("NameIDX","SSNKey"))
IF sc=1 {
WRITE !,"成功构建索引"
} ELSE {
WRITE !,"索引构建失败",!
DO $System.Status.DisplayError(sc) QUIT
}
```
生成除以下项之外的所有索引:调用`%BuildIndices()`,并将索引名称的`$LIST`作为第七个参数来构建(为其提供值)给定类(表)的所有已定义索引(指定索引除外):
```java
SET sc = ##class(MyApp.SalesPerson).%BuildIndices("",,,,,,$ListBuild("NameIDX","SSNKey"))
IF sc=1 {
WRITE !,"成功构建索引"
} ELSE {
WRITE !,"索引构建失败",!
DO $System.Status.DisplayError(sc) QUIT
}
```
`%BuildIndices()`方法执行以下操作:
1. 对要重建的任何(非位图)索引调用`$SortBegin`函数(这将启动对这些索引的高性能排序操作)。
2. 循环遍历类(表)的主要数据,收集索引使用的值,并将这些值添加到索引(通过适当的排序转换)。
3. 调用`$SortEnd`函数来完成索引排序过程。
如果索引已经有值,则必须使用两个参数调用`%BuildIndices()`,其中第二个参数的值为1。
为此参数指定1将导致该方法在重新生成值之前清除这些值。
例如:
```java
SET sc = ##class(MyApp.SalesPerson).%BuildIndices(,1)
IF sc=1 {
WRITE !,"成功构建索引"
} ELSE {
WRITE !,"索引构建失败",!
DO $System.Status.DisplayError(sc) QUIT
}
```
清除并重建所有的索引。
你也可以清除并重建索引的子集,例如:
```java
SET sc = ##class(MyApp.SalesPerson).%BuildIndices($ListBuild("NameIDX","SSNKey"),1)
IF sc=1 {
WRITE !,"成功构建索引"
} ELSE {
WRITE !,"索引构建失败",!
DO $System.Status.DisplayError(sc) QUIT
}
```
注意:当表的数据被其他用户访问时,不要重建索引。
若要在活动系统上重建索引,请参见在活动系统上构建索引。
## 在活动系统上构建索引
在活动系统上构建(或重建)索引时,有两个问题:
- 除非正在构建的索引对`SELECT` `Query`隐藏,否则活动`Query`可能返回不正确的结果。这是在构建索引之前使用`SetMapSelecability()`方法处理的。
- 索引构建期间对数据的活动更新不会反映在索引条目中。这是通过在生成索引时使生成操作锁定单个行来处理的。
**注意:如果应用程序在单个事务内对数据执行大量更新,则可能会出现锁表争用问题。**
### 在Readonly主动系统上构建索引
如果表当前仅用于查询操作(`READONLY`),则可以在不中断查询操作的情况下构建新索引或重建现有索引。这是通过在重建索引时使索引对查询优化器不可用来实现的。
如果要为其构建一个或多个索引的所有类当前都是`READONLY`,请使用“在读写活动系统上构建索引”中描述的相同系列操作,但有以下区别:使用`%BuildIndices()`时,设置`pLockFlag=3`(共享区锁定)。
### 在读写活动系统上构建索引
如果持久化类(表)当前正在使用并且可用于读写访问(查询和数据修改),则可以在不中断这些操作的情况下构建新索引或重建现有索引。如果要为其重建一个或多个索引的类当前可读写访问,则构建索引的首选方法是使用与表的持久类一起提供的`%BuildIndices()`(或`%BuildIndicesAsync()`)方法。
**注意:以下信息适用于动态SQL查询,而不适用于嵌入式SQL。嵌入式SQL在编译时(而不是在运行时)检查`MapSelecability`设置。因此,关闭索引的`MapSelecability`对已经编译的嵌入式SQL查询没有任何影响。因此,嵌入式SQL查询仍可能尝试使用禁用的索引,并将给出不正确的结果。**
在并发读写访问期间,需要执行以下一系列操作来构建一个或多个索引:
1. 望构建的索引对查询不可用(读取访问权限)。这是使用`SetMapSelecability()`完成的。这使得查询优化器无法使用该索引。在重建现有索引和创建新索引时都应执行此操作。例如:
```java
SET status=$SYSTEM.SQL.Util.SetMapSelectability("Sample.MyStudents","StudentNameIDX",0)
```
- 第一个参数是`Schema.Table`名称,它是`SqlTableName`,而不是持久类名称。例如,默认模式是`SQLUser`,而不是`User`。该值区分大小写。
- 第二个参数是SQL索引映射名称。这通常是索引的名称,指的是磁盘上存储索引的名称。对于新索引,这是在创建索引时将使用的名称。该值不区分大小写。
- 第三个参数是`MapSelecability`标志,其中0将索引映射定义为不可选择(`OFF`),1将索引映射定义为可选择(`ON`)。指定0。
可以通过调用`GetMapSelecability()`方法来确定索引是否不可选。如果已将索引显式标记为不可选,则此方法返回0。在所有其他情况下,它返回1;它不执行表或索引是否存在的验证检查。请注意,`Schema.Table`名称是`SqlTableName`,并且区分大小写。
`SetMapSelecability()`和`GetMapSelecability()`仅适用于当前命名空间中的索引映射。如果该表映射到多个命名空间,并且需要在每个命名空间中构建索引,则应该在每个命名空间中调用`SetMapSelecability()`。
2. 在索引构建期间建立并发操作:
- 对于新索引:在类中创建索引定义(或在类的`%Storage.SQL`中创建新的SQL Index Map规范)。编译类。此时,索引存在于表定义中;这意味着对象保存、SQL `INSERT`操作和SQL `UPDATE`操作都记录在索引中。但是,由于在步骤1中调用了`SetMapSelecability()`,因此不会为任何数据检索选择此索引映射。`SetMapSelecability()`阻止查询使用区索引,但是数据映射将被投影到SQL以使用索引全局和数据全局。对于新索引,这是合适的,因为索引尚未填充。在对表运行查询之前,需要填充区索引。
- 对于现有索引:清除任何引用该表的缓存查询。索引构建执行的第一个操作是终止索引。因此,在重新生成索引时,不能依赖任何经过优化以使用该索引的代码。
3. 使用`pLockFlag=2`(行级锁定)的持久化类(表)的`%BuildIndices()`方法构建一个或多个索引。`PLockFlag=2`标志在重建过程中在单个行上建立独占写锁,以便并发数据修改操作与构建索引操作相协调。
默认情况下,`%BuildIndices()`构建为持久类定义的所有索引;可以使用`pIgnoreIndexList`从重建中排除索引。
默认情况下,`%BuildIndices()`为所有`ID`构建索引项。但是,可以使用`pStartID`和`pEndID`来定义`ID`范围。`%BuildIndices()`将仅为该范围内(含)的ID构建索引项。例如,如果使用带有`%NOINDEX`限制的`INSERT`将一系列新记录添加到表中,则可以稍后使用具有ID范围的`%BuildIndices()`为这些新记录构建索引项。还可以使用`pStartID`和`pEndID`在节中构建极大的索引。
`%BuildIndices()`返回`%Status`值。如果`%BuildIndices()`因检索数据时出现问题而失败,系统将生成一个`SQLCODE`错误和一条消息(`%msg`),其中包含遇到错误的`%ROWID`。
4. 构建完索引后,启用映射以供查询优化器选择。将第三个参数`MapSelecability`标志设置为1,如下例所示:
```java
SET status=$SYSTEM.SQL.Util.SetMapSelectability("Sample.MyStudents","StudentNameIDX",1)
```
5. 再次清除引用该表的所有缓存查询。这将消除在此程序中创建的缓存查询,这些查询无法使用索引,因此不如使用索引的相同查询最佳。
这就完成了这个过程。索引已完全填充,查询优化器能够考虑该索引。
注意:`%BuildIndices()`只能用于重建`ID`值为正整数的表的索引。如果父表具有正整数`ID`值,还可以使用`%BuildIndices()`重建子表中的索引。对于其他表,请使用`%ValidateIndices()`方法,如验证索引中所述。因为`%ValidateIndices()`是构建索引的最慢方法,所以只有在没有其他选项的情况下才应该使用它。