清除过滤器
文章
Louis Lu · 五月 30, 2021
本文主要总结了在InterSystems IRIS 中如何保存、查询List类型数据
假设我们设计的对象中包含姓名,同时每个姓名下可以包含多个电话。我们可以使用下面方法进行处理。
1. 传统方式
我们可以把每一个姓名和电话放在不同列中。
Class Test.Person Extends %Persistent
{
Property Name As %String;
Property Phone As %String;
}
我们使用SQL语句插入数据:
insert into Test.Person values ('a','111-111-1111');
insert into Test.Person values ('b','222-111-1111');
insert into Test.Person values ('a','111-222-1111');
insert into Test.Person values ('c','333-111-1111');
insert into Test.Person values ('b','222-222-1111');
数据在表中是这样的:
Name
Phone
a
111-111-1111
b
222-111-1111
a
111-222-1111
c
333-111-1111
b
222-222-1111
这种情况下,我们可以使用下面的sql语句将结果返回:
SELECT
distinct %exact(Name) Name,
LIST(phone %foreach(Name)) Phonestr
FROM test.person
Name
Phonestr
a
111-111-1111,111-222-1111
b
222-111-1111,222-222-1111
c
333-111-1111
我们可以为电话号码创建索引,以提高搜索速度,如下:
Index IdxP On Phone;
使用这种方式保存list数据比较简单,当是当list数据非常多时,这种方法会使表格臃肿。
2. 保存在一个字符串字段中,使用分隔符区分
这里我们将所有电话号码保存在一个字符串字段中,每个号码之间用逗号区分
Class Test.Person2 Extends %Persistent
{
Property Name As %String;
Property PhoneStr As %String;
}
填充数据后,类似于这样
Name
PhoneStr
a
111-111-1111,111-222-1111
b
222-111-1111,222-222-1111
c
333-111-1111
d
333-111-1111,222-222-1111
这种情况下我们可以用下面方法实现对每个电话的索引
Index idxP On PhoneStr(ELEMENTS);
ClassMethod PhoneStrBuildValueArray(value, ByRef array) As %Status
{
if value="" {
s array(0)=value
}else{
s list=$lfs(value,","),ptr=0
while $listnext(list,ptr,item){
s array(ptr)=item
}
}
q $$$OK
}
这里用到了一个函数
ClassMethod propertynameBuildValueArray(value, ByRef valueArray) As %Status
其中:
value – 需要拆分的内容;
valueArray – 返回array类型的值,其中包含拆分的内容,格式为 array(key1)=value1, array(key2)=value2...
这时候我们的数据是这样:
USER>zw ^Test.Person2D
^Test.Person2D=4
^Test.Person2D(1)=$lb("","a","111-111-1111,111-222-1111")
^Test.Person2D(2)=$lb("","b","222-111-1111,222-222-1111")
^Test.Person2D(3)=$lb("","c","333-111-1111")
^Test.Person2D(4)=$lb("","d","333-111-1111,222-222-1111")
索引是这样:
USER>zw ^Test.Person2I
^Test.Person2I("idxP"," 111-111-1111",1)=""
^Test.Person2I("idxP"," 111-222-1111",1)=""
^Test.Person2I("idxP"," 222-111-1111",2)=""
^Test.Person2I("idxP"," 222-222-1111",2)=""
^Test.Person2I("idxP"," 222-222-1111",4)=""
^Test.Person2I("idxP"," 333-111-1111",3)=""
^Test.Person2I("idxP"," 333-111-1111",4)=""
这种情况下我们可以通过下面的SQL 语句查找 包含电话号码 333-111-1111 的姓名
select Name from test.person2 where phonestr ['333-111-1111'
select Name from test.person2 where phonestr like '%333-111-1111%'
但是当你检查查询计划的时候,却发现它并没有使用任何索引
我们只能通过类似于下面SQL语句的写法才能使用该索引
select Name from test.person2 where for some %element(Phonestr) (%value = '333-111-1111')
类似的还有下面的写法
(%Value %STARTSWITH 'а')
(%Value [ 'a' and %Value [ 'b')
(%Value in ('c','d'))
(%Value is null)
3. 使用 %List 类型
Class Test.Person3 Extends %Persistent
{
Property Name As %String;
Property PhoneList As %List;
Index idxP On PhoneList(ELEMENTS);
ClassMethod PhoneListBuildValueArray(value, ByRef array) As %Status
{
if value="" {
s array(0)=value
}else{
s ptr=0
while $listnext(value,ptr,item){
s array(ptr)=item
}
}
q $$$OK
}
}
插入数据
insert into Test.Person3 (Name,PhoneList) select 'a', $LISTBUILD('111-111-1111','111-222-1111')
insert into Test.Person3 (Name,PhoneList) select 'b', $LISTBUILD('222-111-1111','222-222-1111')
insert into Test.Person3 (Name,PhoneList) select 'c', $LISTBUILD('333-111-1111')
insert into Test.Person3 (Name,PhoneList) select 'd', $LISTBUILD('333-111-1111','222-222-1111')
数据和索引保存为
USER>zw ^Test.Person3D
^Test.Person3D=4
^Test.Person3D(1)=$lb("","a",$lb("111-111-1111","111-222-1111"))
^Test.Person3D(2)=$lb("","b",$lb("222-111-1111","222-222-1111"))
^Test.Person3D(3)=$lb("","c",$lb("333-111-1111"))
^Test.Person3D(4)=$lb("","d",$lb("333-111-1111","222-222-1111"))
USER>zw ^Test.Person3I
^Test.Person3I("idxP","111-111-1111",1)=""
^Test.Person3I("idxP","111-222-1111",1)=""
^Test.Person3I("idxP","222-111-1111",2)=""
^Test.Person3I("idxP","222-222-1111",2)=""
^Test.Person3I("idxP","222-222-1111",4)=""
^Test.Person3I("idxP","333-111-1111",3)=""
^Test.Person3I("idxP","333-111-1111",4)=""
同样可以使用下面的SQL语句查找包含电话333-111-1111的姓名
select Name from test.person2 where for some %element(phonelist) (%value = '333-111-1111')
4 使用 List Of、Array Of 保存
不需要定义propertynameBuildValueArray函数
Class Test.Person4 Extends %Persistent
{
Property Name As %String;
Property PhoneList As list Of %String;
Index idxP On PhoneList(ELEMENTS);
}
使用同样的方式插入数据
insert into Test.Person4 (Name,PhoneList) select 'a', $LISTBUILD('111-111-1111','111-222-1111')
insert into Test.Person4 (Name,PhoneList) select 'b', $LISTBUILD('222-111-1111','222-222-1111')
insert into Test.Person4 (Name,PhoneList) select 'c', $LISTBUILD('333-111-1111')
insert into Test.Person4 (Name,PhoneList) select 'd', $LISTBUILD('333-111-1111','222-222-1111')
数据和索引保存为
USER>zw ^Test.Person4D
^Test.Person4D=4
^Test.Person4D(1)=$lb("","a",$lb("111-111-1111","111-222-1111"))
^Test.Person4D(2)=$lb("","b",$lb("222-111-1111","222-222-1111"))
^Test.Person4D(3)=$lb("","c",$lb("333-111-1111"))
^Test.Person4D(4)=$lb("","d",$lb("333-111-1111","222-222-1111"))
USER>zw ^Test.Person4I
^Test.Person4I("idxP"," 111-111-1111",1)=""
^Test.Person4I("idxP"," 111-222-1111",1)=""
^Test.Person4I("idxP"," 222-111-1111",2)=""
^Test.Person4I("idxP"," 222-222-1111",2)=""
^Test.Person4I("idxP"," 222-222-1111",4)=""
^Test.Person4I("idxP"," 333-111-1111",3)=""
^Test.Person4I("idxP"," 333-111-1111",4)=""
使用同样的SQL查询可以得到结果
select Name from test.person4 where for some %element(Phonelist) (%value = '333-111-1111')
引申话题:针对日期字段的索引
日期格式通常是yyyy-mm-dd,我们经常要求按照某年或者某月查询数据,我们可以使用propertynameBuildValueArray函数设定保存的索引方式实现这个目的
Class Test.Person5 Extends %Persistent
{
Property Name As %String;
Property DOB As %Date;
Index idxD On (DOB(KEYS), DOB(ELEMENTS));
ClassMethod DOBBuildValueArray(value, ByRef array) As %Status
{
if value="" {
s array(0)=value
}else{
s d=$zd(value,3)
s array("yy")=+$p(d,"-",1)
s array("mm")=+$p(d,"-",2)
s array("dd")=+$p(d,"-",3)
}
q $$$OK
}
}
插入数据
insert into Test.Person5 (Name,DOB)
select 'a', {d '2000-01-01'} union all
select 'b', {d '2000-01-02'} union all
select 'c', {d '2000-02-01'} union all
select 'd', {d '2001-01-02'} union all
select 'e', {d '2001-01-01'} union all
select 'f', {d '2001-02-01'}
查看数据以及索引保存的内容
USER>zw ^Test.Person5D
^Test.Person5D=6
^Test.Person5D(1)=$lb("","a",58074)
^Test.Person5D(2)=$lb("","b",58075)
^Test.Person5D(3)=$lb("","c",58105)
^Test.Person5D(4)=$lb("","d",58441)
^Test.Person5D(5)=$lb("","e",58440)
^Test.Person5D(6)=$lb("","f",58471)
USER>zw ^Test.Person5I
^Test.Person5I("idxD","dd",1,1)=""
^Test.Person5I("idxD","dd",1,3)=""
^Test.Person5I("idxD","dd",1,5)=""
^Test.Person5I("idxD","dd",1,6)=""
^Test.Person5I("idxD","dd",2,2)=""
^Test.Person5I("idxD","dd",2,4)=""
^Test.Person5I("idxD","mm",1,1)=""
^Test.Person5I("idxD","mm",1,2)=""
^Test.Person5I("idxD","mm",1,4)=""
^Test.Person5I("idxD","mm",1,5)=""
^Test.Person5I("idxD","mm",2,3)=""
^Test.Person5I("idxD","mm",2,6)=""
^Test.Person5I("idxD","yy",2000,1)=""
^Test.Person5I("idxD","yy",2000,2)=""
^Test.Person5I("idxD","yy",2000,3)=""
^Test.Person5I("idxD","yy",2001,4)=""
^Test.Person5I("idxD","yy",2001,5)=""
^Test.Person5I("idxD","yy",2001,6)=""
执行下面 SQL 可以显示所有2月出生的信息
select * from Test.Person5 where for some %element(DOB) (%key='mm' and %value = 2)
这篇文章源自 这里,作者 Vitaliy Serdtsev
文章
姚 鑫 · 五月 15, 2021
# 第一章 单元测试概述
本教程的第一部分概述了单元测试。完成本教程的这一部分后,将能够:
- 定义单元测试并区分单元测试和集成测试
- 列出单元测试的几个好处
- 描述InterSystems IRIS `%UnitTest`包和`xUnit`测试框架之间的相似性。
- 列出软件开发中测试优先方法经常声称的几个好处。
# 什么是单元测试?
单元测试是对单个代码模块的正确性的测试,例如,方法或类的测试。通常,开发人员在开发代码时为其代码创建单元测试。典型的单元测试是一种执行方法的方法,该方法测试并验证该方法是否为给定的一组输入生成了正确的输出。
单元测试不同于集成测试。集成测试验证了一组代码模块交互的正确性。单元测试仅单独验证代码模块的正确性。一组代码模块的集成测试可能会失败,即使每个模块都通过了单元测试。
# 为什么要进行单元测试?
单元测试提供了许多好处,包括:
- 提供代码模块是否正确的验证。这是单元测试的主要原因。
- 提供自动回归测试。更改代码模块后,应重新运行单元测试,以确保代码模块仍然正确。也就是说,应该使用单元测试来确保更改没有破坏代码模块。理想情况下,所有代码模块的单元测试都应该在更改任何一个模块之后运行。
- 提供文档。通常,代码模块的单元测试与代码模块一起交付。检查单元测试提供了大量有关代码模块如何工作的信息。
# XUnit测试框架
单元测试框架是为开发和执行单元测试提供支持的类包。它们可以很容易地扩展以支持更具体或专门化类型的单元测试。
XUnit系列测试框架基于原始的`Sunit`框架(用于单元测试`SmallTalk`代码),包括以下框架:
- `JUnit-Java`代码的单元测试框架。
- `NUnit-C#`、`VB.NET`和其他`.NET`语言代码的单元测试框架。
- `CppUnit-C++`代码的单元测试框架。
- `PyUnit-Python`代码的单元测试框架。
# %UnitTest和xUnit框架的结构
`%UnitTest`包和`xUnit`框架共享相同的基本结构。熟悉任何`Unit`框架的开发人员都可以毫不费力地学习使用`%UnitTest`包。`%UnitTest`和`xUnit`框架都围绕以下基本测试结构组织:
- 测试装置-为一个测试或一组测试做准备和清理工作的代码。准备测试可能包括创建数据库连接,或使用测试数据初始化数据库。清理可能包括关闭数据库连接或恢复数据库状态。
- 测试用例-测试的最小单元。验证特定的一组输入是否会产生给定模块的特定输出。
- 测试套件-设计为一起执行的测试和测试套件的集合。
- Test Runner-用于执行测试并显示其结果的实用程序。
# 测试自动化
`%UnitTest`包和`xUnit`框架都支持测试自动化。当单元测试完成执行时,它会报告测试是通过还是失败。不需要解释测试结果。这是非常重要的。可以为每个代码更改执行大量单元测试。如果必须不断地阅读和解释结果,这个过程很快就会变得非常乏味和容易出错。
许多`xUnit`框架提供了汇总测试结果的图形用户界面(GUI)。`%UnitTest`会生成一个显示测试结果的网页。它以绿色显示有关通过的测试的信息,以红色显示有关失败的测试的信息。开发人员可以一目了然地判断是否有任何测试失败。
这是由`%UnitTest`单元测试生成的测试报告。用户可以通过单击页面上的超链接深入查看提供有关测试的更多详细信息的页面。
![image](6F95C9D03A024D2CB690E5213CF37A4B)
# 测试优先方法论
敏捷软件方法论,例如测试驱动开发(TDD)和极限编程,特别强调单元测试。事实上,这些方法使用单元测试来驱动开发过程。他们提倡“测试优先”的软件开发方法。在这种方法中,开发人员在编写代码模块的一行代码之前设计并编写代码模块的单元测试。然后,开发人员创建代码模块,目标是通过单元测试。
`Test First`方法的倡导者声称该方法具有以下好处:
- 它迫使开发人员在开发任何模块之前很久就决定代码模块的正确输入和输出。
- 它集中了开发人员在创建代码模块时的注意力。开发人员关注的是在创建模块时通过单元测试的具体目标。
- 它可以防止单元测试成为事后的想法。如果首先创建单元测试,则在项目结束之前不能忽略单元测试。
- 它确保了代码的高度测试覆盖率。
注意:测试优先开发的支持者通常主张在代码模块之前执行单元测试,而不仅仅是创建单元测试。当然,在这一点上测试应该会失败。他们甚至可能不会编译。
# Red – Green – Refactor
`XUnit`和`%UnitTest`测试报告GUI报告以绿色表示通过测试,以红色表示未通过测试。下面是使用测试优先开发方法的开发节奏:
1. 红色 - 编写一个不起作用的小测试,也许一开始不会编译。
2. 绿色 - 让测试快速运行,在测试过程中犯下所有必要的错误。
3. 重构 - 消除仅在使测试正常工作时产生的所有重复。
> Kent Beck,《测试驱动的设计》
文章
姚 鑫 · 八月 9, 2021
# 第八十一章 方法关键字 - SoapRequestMessage
当多个`web方法`具有相同的`SoapAction`时使用此方法。
在默认场景中,该关键字指定请求消息的`SOAP`正文中的顶级元素的名称。
仅适用于定义为`web服务`或`web客户端`的类。
# 用法
要在请求消息的`SOAP`体中指定顶级元素的名称,请使用以下语法:
```java
Method name(formal_spec) As returnclass [ WebMethod, SoapAction = "MyAct", SoapRequestMessage="MyReqMessage" ]
{ //implementation }
```
其中`soaprequestmessage`是有效的XML标识符。
# 详解
注意:此关键字仅对包装的文档/文字`document/literal`消息有效。
对于包装的文档/文字消息,该关键字指定请求消息的`SOAP`主体中的顶部元素的名称。(默认情况下,包装文档/文字消息。
如果对同一`web服务`中的多个`web方法`使用相同的`SoapAction`值,请指定此关键字。否则,一般不需要这个关键字。
# 与WSDL的关系
`SoapRequestMessage`关键字影响`web服务`的`WSDL`的``部分。例如,考虑以下web方法:
```java
Method Add(a as %Numeric,b as %Numeric) As %Numeric [ SoapAction = MyAct,SoapRequestMessage=MyReqMessage, WebMethod ]
{
Quit a + b
}
```
对于这个web服务,WSDL包含以下内容:
```xml
```
这些元素在``部分中相应地定义。
默认情况下,如果方法没有指定`SoapRequestMessage`关键字,``部分将改为如下所示:
```xml
```
如果使用`SOAP`向导从`WSDL` IRIS `web服务`或客户端, IRIS将此关键字设置为适合该WSDL的。
# 对Message的影响
对于前面显示的`web方法`,`web服务`需要以下形式的请求消息:
```xml
12
```
相反,如果该方法没有指定`SoapRequestMessage`关键字,则该消息将如下所示:
```xml
12
```
# 第八十二章 方法关键字 - SoapTypeNameSpace
为此`web方法`使用的类型指定`XML`命名空间。仅适用于定义为`web服务`或`web客户端`的类。
# 用法
若要重写类型的默认`XML`命名空间(当该方法用作web方法时),请使用以下语法:
```java
Method name(formal_spec) As returnclass [ SoapTypeNameSpace = "soapnamespace", SoapBindingStyle = document, WebMethod ]
{ //implementation }
```
其中`soapnamespace`是命名空间`URI`。请注意,如果`URI`包含冒号(`:`),则该字符串必须加引号。也就是说,可以使用以下内容:
```java
Method MyMethod() [ SoapTypeNameSpace = "http://www.mynamespace.org", SoapBindingStyle = document, WebMethod ]
```
或以下内容:
```java
Method MyMethod() [ SoapTypeNameSpace = othervalue, SoapBindingStyle = document, WebMethod ]
```
但不包括以下内容:
```java
Method MyMethod() [ SoapTypeNameSpace = http://www.mynamespace.org, SoapBindingStyle = document, WebMethod ]
```
重要提示:对于手动创建的`web服务`,该关键字的默认值通常是合适的。当使用SOAP向导从`WSDL`生成`web客户端`或服务时,InterSystems IRIS会将该关键字设置为适合该`WSDL`;如果修改该值,`web客户端`或服务可能不再工作。
# 详解
此关键字指定此`web方法`使用的类型的XML命名空间。
注意:只有当方法使用文档样式绑定时,此关键字才有作用。也就是说,方法(或包含它的类)必须用等于`document`的`SoapBindingStyle`标记。(对于使用`rpc-style`绑定的方法,指定这个关键字是没有意义的。)
# 默认
如果省略此关键字,则此方法的类型位于由`web服务`或`客户端`类的`TYPENAMESPACE`参数指定的命名空间中。如果未指定`TYPENAMESPACE`,则类型将位于由`web服务`或客户端的`are`参数指定的命名空间中。
# 与WSDL的关系
`SoapTypeNameSpace`关键字影响`WSDL`的以下部分:
``元素中的命名空间声明。指定的命名空间(例如,`http://www.customtypes.org`)将添加到这里。例如:
```xml
...
xmlns:ns2="http://www.customtypes.org"
xmlns:s0="http://www.wbns.org"
xmlns:s1="http://webservicetypesns.org"
...
targetNamespace="http://www.wbns.org"
```
在本例中,`http://www.customtypes.org`命名空间被分配给前缀`ns2`。
请注意,`WSDL`还像往常一样声明了以下名称空间:
- `Web服务`的命名空间(`http://www.wsns.org`),在本例中,它被分配给前缀`s0`,也用作`Web服务`的目标命名空间。
- 网络服务的类型命名空间`http://www.webservicetypesns.org`),在本例中它被分配给`前缀s1`。
如果在`web服务`类中没有指定类型命名空间,则该命名空间不包含在`WSDL`中。
- ``元素,它包含一个``元素,该元素的`targetNamespace`属性等于为`SoapTypeNameSpace`指定的命名空间:
```xml
...
...
```
相反,如果没有指定`SoapTypeNameSpace`,那么`WSDL`的这一部分将如下所示。请注意,``元素的`targetNamespace`是`web服务`类型的命名空间:
```xml
...
...
```
(此外,如果在`web服务类`中没有指定类型命名空间,则`targetNamespace`将改为`web服务`的命名空间。)
# 对消息的影响
`SOAP`消息可能如下所示(为了可读性,添加了换行符和空格):
```xml
3
```
请注意,``元素位于`“http://www.customtypes.org”`命名空间中。
相反,如果没有指定`SoapTypeNameSpace`关键字,则消息可以如下所示:
```xml
3
```
# 第八十三章 方法关键字 - SqlName
覆盖投影`SQL`存储过程的默认名称。
仅当此方法被投影为`SQL`存储过程时应用。
# 用法
要覆盖方法投射为`SQL`存储过程时使用的默认名称,请使用以下语法:
```java
ClassMethod name(formal_spec) As returnclass [ SqlProc, SqlName = sqlname ]
{ //implementation }
```
其中`sqlname`是`SQL`标识符。
# 详解
如果将此方法投影为`SQL`存储过程,则使用此名称作为存储过程的名称。
# 默认
如果忽略这个关键字, IRIS确定`SQL`名称如下:
```java
CLASSNAME_METHODNAME
```
默认使用大写字母。
但是,在调用存储过程时可以使用任何情况,因为SQL是不区分大小写的。
因此,在下面的示例中,默认的`SQL name`值是`TEST1_PROC1`。
这个默认值是在`SELECT`语句中指定的:
```java
Class User.Test1 Extends %Persistent
{
ClassMethod Proc1(BO,SUM) As %INTEGER [ SqlProc ]
{
///definition not shown
}
Query Q1(KD As %String,P1 As %String,P2 As %String) As %SqlQuery
{
SELECT SUM(SQLUser.TEST1_PROC1(1,2)) AS Sumd
FROM SQLUser.Test1
}
}
```
# 第八十四章 方法关键字 - SqlProc
指定是否可以作为`SQL`存储过程调用该方法。
只有类方法(而不是实例方法)可以作为SQL存储过程调用。
# 用法
要指定该方法可以作为`SQL`存储过程调用,请使用以下语法:
```java
ClassMethod name(formal_spec) As returnclass [ SqlProc ]
{ //implementation }
```
否则,忽略该关键字或将`Not`放在该关键字之前。
# 详解
该关键字指定可以作为`SQL`存储过程调用该方法。
只有类方法(而不是实例方法)可以作为`SQL`存储过程调用。
存储过程由子类继承。
# 默认
如果忽略此关键字,则该方法作为`SQL`存储过程不可用。
文章
姚 鑫 · 八月 15, 2022
# 第二章 使用管理门户(二)
# 管理门户概述
本节介绍管理门户页面的一些常见布局元素。
注意:在管理门户中的任何位置,将光标移到菜单项上都会显示该项目的描述。
## 管理门户主页
管理门户主页的标题是 `Welcome, `。在标题旁边,功能区包含以下选项:
- 两个视图按钮,可让指定如何在菜单列中显示链接。
- 搜索栏,位于功能区的右侧。当指定一个词并按 Enter 键时,将显示包含该词的所有页面的列表;然后,可以单击要显示的目标页面,而无需浏览子菜单。
以下部分描述了主页的区域:
### 管理门户菜单栏
位于主页左边缘的菜单栏是导航门户的主要方法。
### 管理门户欢迎窗格
欢迎窗格位于主页的中心,包括经常访问的页面的快捷方式。它包含以下字段:
- 收藏夹`Favorites` — 列出选择为收藏夹的管理门户页面(请参阅操作窗格);可以单击每个页面标题直接转到该页面。
- 最近`Recent` — 列出自上次启动 IRIS 以来最近显示的页面。
- `Did you know?` — 显示提示。
- 链接 `Links` - 指向可能想要访问的页面的链接。
### 管理门户消息窗格
位于主页右侧边缘的消息窗格显示一般系统信息并提供指向系统仪表板的链接。
如果实例是镜像成员,则消息窗格还显示它所属的镜像、其状态和成员类型以及指向镜像监视器的链接。
## 管理门户标题
页眉位于管理门户中每个页面的顶部,可用于快速导航门户。
标题包含以下链接:
- 主页`Home` — 显示管理门户主页。
- 关于`About` — 显示系统概览信息。
- 帮助`Help` — 显示正在查看的页面/主题的在线文档(帮助)。
- 联系方式`Contact` — 显示全球响应中心 (WRC) 的联系方式页面。
- 注销`Logout` — 注销您并带您进入管理门户的登录页面。
- 菜单`Menu` — 根据用户担任的角色显示常见任务列表。
标头还包含有用的信息,例如:
- 服务器`Server` — 运行 IRIS 的服务器的名称。
- 命名空间`Namespace` — 当前使用的命名空间的名称。要在不同的命名空间中工作,请单击切换并选择所需的命名空间。
- 用户`User` — 登录到管理门户的用户的名称。要更改用户的密码,请单击名称。
- 许可证`Licensed to` — 出现在许可证密钥信息中的客户名称。
- 实例`Instance` — 服务器上运行的 `IRIS` 实例的名称。
此外,可以显示系统模式标签(例如,测试系统);
管理门户标题的左侧显示正在使用的产品的名称。
## 管理门户功能区
功能区位于标题正下方,并显示特定于每个页面的不同内容。例如,数据库页面(`System Explorer` > `Databases`)的功能区如下图所示:
功能区的典型内容是:
- 正在显示的管理门户页面的标题。
- 当前页面的面包屑,直接列在页面标题上方。路径中列出的每个页面都是一个活动链接,可以使用它返回到先前显示的子菜单/列表。当在页面上进行未保存的更改时,会在面包屑中附加一个星号,例如系统 > 配置 >内存和启动 —(配置设置)*。在离开未保存的更改之前,系统始终会提示进行确认。
注意:页签不会列出路径中的每个页面,并且页签中的页面并不总是与导航菜单中的页面匹配。始终可以通过单击主页返回到管理门户主页并使用搜索工具导航到特定页面,本节稍后将对此进行介绍。
- 允许在页面上执行操作的几个按钮。例如,`Databases` 页面包含按钮 `Integrity Check` 和 `Integrity Log`。
- 刷新按钮,包含有关页面上次更新时间的信息。
## 系统概述信息
单击管理门户标题上的关于时,将显示一个表格,其中包含以下信息:
- 版本 — 此 `IRIS` 实例的特定构建信息,包括平台、构建号和构建日期。
- 配置 - 此实例正在使用的配置 (`.cpf`) 文件的名称和位置。
- 数据库缓存 (`MB`) — 为数据库分配的空间例程缓存 (`MB`) — 为例程分配的空间。
- 日志文件 - 当前日志文件的名称和位置。
- `SuperServer` 端口 — 运行 `IRIS` 服务器的端口号。
- `Web`服务器端口 — 运行私有 `IRIS Web` 服务器的端口号。
- 许可证服务器地址/端口 — `IRIS` 许可证服务器的 `IP` 地址和运行它的端口号。
- 许可给 — 出现在许可密钥信息中的客户名称。
- 集群支持 - 指示此实例是否是集群的一部分。
- 镜像 — 指示此实例是否是镜像的成员。
- `Time System Started` — 上次启动 `InterSystems IRIS` 实例的日期和时间。
- 加密密钥标识符 — 如果激活加密,则为加密密钥的 `GUID`(全局唯一 `ID`)。
- `NLS` 区域设置 — 国家语言支持区域设置。
- 此会话的首选语言 - 管理门户已本地化并可显示的语言的下拉列表。可以通过从下拉列表中选择新的语言来更改显示语言。最初,浏览会话的首选语言是为浏览器指定的语言,如果不支持浏览器语言,则为英语;在特定浏览器中选择首选语言后,即使更改了浏览器语言,该浏览器中的管理门户也会使用该语言。
文章
姚 鑫 · 七月 21, 2021
# 第五章 参数定义
描述参数定义的结构。
# 介绍
参数定义定义了一个给定类的所有对象都可用的常数值。创建类定义时(或在编译前的任何时候),可以设置其类参数的值。默认情况下,每个参数的值都是空字符串,但是可以在参数定义中指定一个非空值。在编译时,为类的所有实例建立参数值。除了极少数例外,该值不能在运行时更改。
# 详解
参数定义具有以下结构:
```java
/// description
Parameter name As parameter_type [ keyword_list ] = value ;
```
- `description`描述(可选)旨在显示在“类参考”中。默认情况下,描述为空白。
- `name`(必需)是参数的名称。这必须是有效的类成员名称,并且不能与任何其他类成员名称冲突。
- `parameter_type`(可选)指定参数的用户界面类型,由Studio用于在检查器内为参数提供输入验证。
**这不是类名;参见下一节。在大多数情况下,编译器会忽略这个关键字。**
如果省略参数类型,也要省略单词`As`
- `value`(可选)指定参数的值。如果省略值,也要省略等号=
- `keyword_list`(可选)是以逗号分隔的关键字列表,用于进一步定义参数。
如果省略此列表,也要省略方括号。
# 参数的允许类型
参数类型`parameter_type` 选项可以是下列值之一:
- `BOOLEAN` — true(1)或false(0)值。
- `CLASSNAME` — 有效的类名。
- `COSCODE` — ObjectScript代码。
- `COSEXPRESSION` — 有效的ObjectScript表达式。
**如果参数是`COSEXPRESSION`类型,则在运行时计算该表达式。**
与形参`Type`关键字的大多数其他值不同,这个值影响编译器。
- `COSIDENTIFIER` — 有效的ObjectScript标识符。
- `INTEGER` — 整数值。
- `SQL` — SQL语句
- `SQLIDENTIFIER` — 有效的SQL标识符。
- `STRING` —字符串值。
- `TEXT` — 多行文本值。
- `CONFIGVALUE` -可以在类定义之外修改的参数。
与形参`Type`关键字的大多数其他值不同,这个值影响编译器。
如果参数的类型是`CONFIGVALUE`,那么可以通过`$SYSTEM.OBJ.UpdateConfigParam()`修改参数。
例如,下面的代码更改了参数`MYPARM`(在类`MyApp`中)的值。
`MyClass`的新值为`42`:
```java
set sc=$system.OBJ.UpdateConfigParam("MyApp.MyClass","MYPARM",42)
```
**注意,`$SYSTEM.OBJ.UpdateConfigParam()`影响任何新进程所使用的生成的类描述符,但不影响类定义。
如果重新编译类,InterSystems IRIS将重新生成类描述符,该描述符现在将使用包含在类定义中的这个参数的值(从而覆盖通过`$SYSTEM.OBJ.UpdateConfigParam()`所做的更改)。**
也可以省略`parameter_type`,在这种情况下`Inspector`将允许参数的任何值。
```
/// web服务的名称。
Parameter SERVICENAME = "SOAPDemo" ;
```
# 第六章 映射定义
描述投影定义的结构。
# 介绍
投影定义指示类编译器在编译或删除类定义时执行指定的操作。
投影定义投影类的名称(来自`%Projection.AbstractProjection`)实现方法称为类的编译完成后,当一个类定义中删除(因为它被删除或者因为类即将重新编译)。
# 详情
投影定义有以下结构:
```java
/// description
Projection name As projection_class (parameter_list) ;
```
- `description`(可选)用于在类引用中显示(但请注意投影目前没有显示在类引用中)。
说明默认为空。
- `Name`(必需)是投影的名称。
这必须是一个有效的类成员名,并且不能与任何其他类成员名冲突。
- `projection_class` (required)是投影类的名称,它是`%Projection.AbstractProjection`的子类。
- `parameter_list`(可选)是一个以逗号分隔的参数及其值列表。
如果指定,这些应该是`projection_class`使用的参数。
如果省略了这个列表,也省略了括号。
- `Keyword_list`(可选)是一个逗号分隔的关键字列表,这些关键字进一步定义了投影。
如果省略了这个列表,也可以省略方括号。
# 第七章 属性定义
描述属性定义的结构。注意,关系是一种属性。
# 介绍
属性包含与类实例相关的信息。可以向对象类添加属性定义。它们在其他类中没有意义。
# 详情
属性定义有以下结构:
```java
/// description
Property name As classname (parameter_list) [ keyword_list ] ;
```
或者(对于列表属性):
```java
/// description
Property name As List Of classname (parameter_list) [ keyword_list ] ;
```
或者(对于数组属性):
```java
/// description
Property name As Array Of classname (parameter_list) [ keyword_list ] ;
```
或者(对于关系属性):
```java
/// description
Relationship name As classname [ keyword_list ] ;
```
- `description`(可选)用于在类引用中显示。说明默认为空。
- `name`(必需)是属性的名称。
这必须是一个有效的类成员名,并且不能与任何其他类成员名冲突。
- `classname`(可选)是该属性所基于的类的名称。
- `parameter_list`(可选)是参数及其值的逗号分隔列表。如果指定,这些应该是由类名使用的参数,或者是对所有属性都可用的参数。
如果省略此列表,也要省略括号。
- `keyword_list`(对于关系属性是必需的,但在其他方面是可选的)是一个逗号分隔的关键字列表,用于进一步定义属性。
如果省略此列表,也要省略方括号。
注意:分片类不支持属性关系。
```java
Property SSN As %String(PATTERN = "3N1""-""2N1""-""4N") [ Required ];
```
# 第八章 查询定义
描述查询定义的结构。
# 介绍
类查询是作为类结构一部分的命名查询,可以通过动态SQL进行访问。
**可以在任何类中定义类查询;不需要将它们包含在持久类中。**
# 详解
查询定义具有以下结构:
```java
/// description
Query name(formal_spec) As classname [ keyword_list ]
{ implementation }
```
- `description`描述(可选)旨在显示在“类别参考”中。默认情况下,描述为空白。
- `name`(必需)是查询的名称。这必须是有效的类成员名称,并且不能与任何其他类成员名称冲突。
- `formal_spec`(可选)指定传递给查询的参数列表。
具体来说,这是通过关联查询类的`Execute()`方法传递给查询的参数列表。
- `classname`(必需)指定此查询使用的查询类。
**对于基于SQL的查询,该值通常为`%SQLQuery`,对于自定义查询,该值通常为%Query。**
**注意:分片类不支持自定义类查询。**
- `keyword_list`(可选)是逗号分隔的关键字列表,用于进一步定义查询。
如果省略此列表,也要省略方括号。
- `implementation` 实现(可选)是定义查询的零行或多行代码。
文章
姚 鑫 · 六月 14, 2022
# 第九章 其他参考资料(二)
# 特殊变量 (SQL)
系统提供的变量。
```sql
$HOROLOG
$JOB
$NAMESPACE
$TLEVEL
$USERNAME
$ZHOROLOG
$ZJOB
$ZPI
$ZTIMESTAMP
$ZTIMEZONE
$ZVERSION
```
SQL直接支持许多对象脚本特殊变量。这些变量包含系统提供的值。只要可以在SQL中指定文字值,就可以使用它们。
SQL特殊变量名不区分大小写。大多数可以使用缩写来指定。
Variable| Name| Abbreviation| Data Type Returned Use
---|---|---|---
$HOROLOG| $H| %String/VARCHAR |当前进程的本地日期和时间
$JOB| $J| %String/VARCHAR |当前进程的 job ID
$NAMESPACE| none| %String/VARCHAR |当前命名空间名称
$TLEVEL| $TL| %Integer/INTEGER|| 当前事务嵌套级别
$USERNAME| none| %String/VARCHAR|当前进程的用户名
$ZHOROLOG| $ZH |%Numeric/NUMERIC(21,6)|自InterSystems IRIS启动后经过的秒数
$ZJOB| $ZJ| %Integer/INTEGER|当前进程的job状态
$ZPI| none| %Numeric/NUMERIC(21,18) |数值常量PI
$ZTIMESTAMP |$ZTS| %String/VARCHAR |协调世界时间格式的当前日期和时间
$ZTIMEZONE| $ZTZ| %Integer/INTEGER| 当地时区与GMT的偏移量
$ZVERSION| $ZV| %String/VARCHAR| IRIS的当前版本
# 示例
```sql
SELECT TOP 5 Name,$H
FROM Sample.Person
```
以下示例仅在时区位于大陆内时才返回结果集:
```sql
SELECT TOP 5 Name,Home_State
FROM Sample.Person
WHERE $ZTIMEZONE BETWEEN -480 AND 480
```
# 字符串操作(SQL)
字符串操作函数和运算符。
SQL 支持多种类型的字符串操作:
- 字符串可以通过长度、字符位置或子字符串值进行操作。
- 字符串可以通过指定的分隔符或分隔符字符串来操作。
- 字符串可以通过模式匹配和单词感知搜索来测试。
- 特殊编码的字符串(称为列表)包含嵌入的子字符串标识符,而不使用分隔符。各种 `$LIST` 函数对这些与标准字符串不兼容的编码字符串进行操作。唯一的例外是 `$LISTGET` 函数和 `$LIST` 的单参数和双参数形式,它们将编码字符串作为输入,但将单个元素值作为标准字符串输出。
SQL 支持字符串函数、字符串条件表达式和字符串运算符。
ObjectScript 字符串操作区分大小写。字符串中的字母可以转换为大写、小写或混合大小写。字符串排序规则可以区分大小写,也可以不区分大小写;默认情况下,SQL 字符串排序规则是不区分大小写的 `SQLUPPER`。 SQL 提供了许多字母大小写和排序规则函数和运算符。
当为数字参数指定字符串时,大多数 SQL 函数执行以下字符串到数字的转换: 非数字字符串转换为数字 0;将数字字符串转换为规范数字;并且混合数字字符串在第一个非数字字符处被截断,然后转换为规范数字。
# 字符串连接
以下函数将子字符串连接成字符串:
- `CONCAT`:连接两个子字符串,返回一个字符串。
- `STRING`:连接两个或多个子字符串,返回单个字符串。
- `XMLAGG`:连接列的所有值,返回单个字符串。
- `LIST`:连接列的所有值,包括逗号分隔符,返回单个字符串。
- 连接运算符 (`||`) 也可用于连接两个字符串。
# 字符串长度
以下函数可用于确定字符串的长度:
- `CHARACTER_LENGTH` 和 `CHAR_LENGTH`:返回字符串中的字符数,包括尾随空格。 `NULL` 返回 `NULL`。
- `LENGTH`:返回字符串中的字符数,不包括尾随空格。 `NULL` 返回 NULL。
- `$LENGTH`:返回字符串中的字符数,包括尾随空格。 `NULL` 返回为 0。
# Truncation and Trim
以下函数可用于截断或修剪字符串。截断限制字符串的长度,删除超出指定长度的所有字符。`Trim`从字符串中删除前导和/或尾随空格。
- `Truncation`: `CONVERT`, `%SQLSTRING`, and `%SQLUPPER`.
- `Trimming`: `TRIM`, `LTRIM`, and `RTRIM`.
# 子串搜索
以下函数在字符串中搜索子字符串并返回字符串位置:
- `POSITION`:按子字符串值搜索,找到第一个匹配项,返回子字符串开始的位置。
- `CHARINDEX`:按子字符串值搜索,找到第一个匹配项,返回子字符串开始的位置。可以指定起点。
- `$FIND`:按子串值搜索,找到第一个匹配项,返回子串结束的位置。可以指定起点。
- `INSTR`:按子字符串值搜索,找到第一个匹配项,返回子字符串开始的位置。可以指定起点和子串出现。
以下函数在字符串中按位置或分隔符搜索子字符串并返回子字符串:
- `$EXTRACT`:按字符串位置搜索,返回由开始位置或开始和结束位置指定的子字符串。从字符串的开头搜索。
- `SUBSTRING`:按字符串位置搜索,返回由开始位置或开始和长度指定的子字符串。从字符串的开头搜索。
- `SUBSTR`:按字符串位置搜索,返回由起始位置或起始和长度指定的子字符串。从字符串的开头或结尾搜索。
- `$PIECE`:按分隔符搜索,返回第一个分隔的子字符串。可以指定起点或默认为字符串的开头。
- `$LENGTH`:按分隔符搜索,返回分隔子串的数量。从字符串的开头搜索。
- `$LIST`:在特殊编码的列表字符串上按子字符串计数搜索。它通过子串计数定位子串并返回子串值。从字符串的开头搜索。
- 包含运算符 (`[`) 也可用于确定子字符串是否出现在字符串中。
- `%STARTSWITH` 比较运算符将指定的字符与字符串的开头进行匹配。
# 子串搜索和替换
以下函数在字符串中搜索子字符串并将其替换为另一个子字符串。
- `REPLACE`:按字符串值搜索,用新的子字符串替换子字符串。从字符串的开头搜索。
- `STUFF`:按字符串位置和长度搜索,用新的子字符串替换子字符串。从字符串的开头搜索。
# 字符类型和 Word-Aware 比较
`%PATTERN` 比较运算符将字符串与指定的字符类型模式匹配。
文章
姚 鑫 · 六月 24, 2021
# 第十七章 加密XML文档
本章介绍如何加密XML文档。
提示:发现在此命名空间中启用`SOAP`日志记录非常有用,这样就可以收到有关任何错误的更多信息。
# 关于加密的XML文档
加密的XML文档包括以下元素:
- ``元素,其中包含由随机生成的对称密钥加密的加密数据。(使用对称密钥加密比使用公钥加密更有效。)
- 至少有一个``元素。每个``元素携带用于加密数据的对称密钥的加密副本;它还包含一个带有公钥的`X.509`证书。拥有匹配私钥的接收方可以解密对称密钥,然后解密``元素。
- (可选)其他明文元素。
```xml
MIICnDCCAYQCAWUwDQYJKo... content omitted
J2DjVgcB8vQx3UCy5uejMB ... content omitted
LmoBK7+nDelTOsC3 ... content omitted
```
要创建加密文档,请使用类`%XML.Security.EncryptedData`和`%XML.Security.EncryptedKey`。这些启用XML的类投影到适当名称空间中的有效``和``元素。
# 创建加密的XML文档
创建加密的XML文档的最简单方法如下:
1. 定义并使用可以直接投影到所需XML文档的通用容器类。
2. 创建包含要加密的XML的流。
3. 加密该流,并将其与相应的加密密钥一起写入容器类的相应属性。
4. 为容器类生成XML输出。
## 加密的前提条件
在加密文档之前,必须创建包含要将加密文档发送到的实体的证书的 IRIS凭据集。在这种情况下,不需要(也不应该拥有)关联的私钥。
## 容器类的要求
一个通用容器类必须包括以下内容:
- 类型为`%XML.Security`的属性。
被投影为``元素的`EncryptedData`。
这个属性将携带加密的数据。
- 至少一个类型为`%XML.Security`的属性。被投影为``元素的`EncryptedKey`。
这些属性将携带相应的密钥信息。
示例如下:
```java
Class XMLEncryption.Container Extends (%RegisteredObject, %XML.Adaptor)
{
Property Data As %XML.Security.EncryptedData(XMLNAME = "EncryptedData");
Property Key As %XML.Security.EncryptedKey(XMLNAME = "EncryptedKey");
Parameter NAMESPACE = "http://www.w3.org/2001/04/xmlenc#";
}
```
## 生成加密的XML文档
要生成并编写加密文档,请执行以下操作:
1. 创建包含XML文档的流。
为此,通常使用`%XML.Writer`将启用XML的对象的输出写入流。
2. 创建`%SYS.X509Credentials`的至少一个实例,将访问要向其提供加密文档的实体的InterSystems IRIS凭据集。为此,请调用此类的`GetByAlias()`类方法。例如:
```java
set credset=##class(%SYS.X509Credentials).GetByAlias("recipient")
```
若要运行此方法,必须以该凭据集的`OwnerList`中包含的用户身份登录,否则`OwnerList`必须为空。
3. 至少创建`%XML.Security.EncryptedKey`实例。若要创建此类的实例,请使用此类的`CreateX509()`类方法。例如:
```java
set enckey=##class(%XML.Security.EncryptedKey).Createx509(credset,encryptionOptions,referenceOption)
```
- `credset`是`%SYS`的实例。
`x509credentials`在刚刚创建的新窗口中打开。
- `encryptionOptions`是`$$$SOAPWSIncludeNone`(还有其他选项,但它们不适用于此场景)。
此宏在`%soap.inc`包含文件中定义。
- `referenceOption`指定了对加密元素的引用的性质。
这里使用的宏在`%soap.inc`包含文件中定义。
4. 在创建`%Library.ListOfObjects`实例,并使用其`Insert()`方法在刚创建插入`%XML.Security.EncryptedKey`实例。
5. 使用`%New()`方法创建`%XML.Security.EncryptedData`实例。例如:
```java
set encdata=##class(%XML.Security.EncryptedData).%New()
```
6. 使用`%XML.Security.EncryptedData的EncryptStream()`实例方法加密在步骤2中创建的流。例如:
```java
set status=encdata.EncryptStream(stream,encryptedKeys)
```
- stream 流是在步骤1中创建的流。
- encryptedKeys是在步骤4中创建的密钥列表。
7. 创建并更新容器类的实例。
- 将键列表写入此类的相应属性。
- 将 `%XML.Security.EncryptedData`的实例写入此类的相应属性。
8. 使用`%XML.Writer`为容器类生成输出。
例如,前面显示的`CONTAINER`类还包括以下方法:
```java
/// w ##class(XMLEncryption.Container).Demo("E:\temp\SecurityXml.txt")
ClassMethod Demo(filename = "", obj = "")
{
#Include %soap
if (obj = "") {
s obj = ##class(MyApp.Person).%OpenId(1)
}
//从此启用XML的对象创建流
set writer = ##class(%XML.Writer).%New()
set stream = ##class(%GlobalCharacterStream).%New()
set status = writer.OutputToStream(stream)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit }
set status = writer.RootObject(obj)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit }
do stream.Rewind()
set container = ..%New() ; 这就是我们要写出的对象
set cred = ##class(%SYS.X509Credentials).GetByAlias("servercred")
set parts =$$$SOAPWSIncludeNone
set ref = $$$KeyInfoX509Certificate
set key = ##class(%XML.Security.EncryptedKey).CreateX509(cred, parts, ref)
set container.Key = key ; 这个细节取决于类
//需要创建一个键列表(本例中仅为一个)
set keys = ##class(%Collection.ListOfObj).%New()
do keys.Insert(key)
set encdata = ##class(%XML.Security.EncryptedData).%New()
set status = encdata.EncryptStream(stream, keys)
set container.Data = encdata ; 这个细节取决于类
// 为容器写输出
set writer = ##class(%XML.Writer).%New()
set writer.Indent = 1
if (filename'="") {
set status = writer.OutputToFile(filename)
if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit}
}
set status = writer.RootObject(container)
if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit}
}
```
此方法可以接受任何启用XML的类的`OREF`;如果没有提供,则使用默认值。
# 解密加密的XML文件
## 解密的前提条件
在解密加密的`XML`文档之前,必须同时提供以下两项:
- IRIS要使用的受信任证书。
- IRIS凭据集,其私钥与加密中使用的公钥匹配。
## 解密文档
要解密加密的XML文档,请执行以下操作:
1. 创建`%XML.Reader`实例打开并使用它打开文档。
2. 获取`Document`属性,`%XML.Reader`实例。
其中包含作为DOM的XML文档。
3. 使用阅读器的`correlation()`方法将``元素或元素与类`%XML.Security.EncryptedKey`关联起来。
例如:
```java
do reader.Correlate("EncryptedKey","%XML.Security.EncryptedKey")
```
4. 遍历文档以读取``元素或多个元素。
为此,可以使用阅读器的`Next()`方法,该方法通过引用返回一个导入的对象(如果有的话)。
例如:
```java
if 'reader.Next(.ikey,.status) {
write !,"Unable to import key",!
do $system.OBJ.DisplayError(status)
quit
}
```
导入的对象`是%XML.Security.EncryptedKey`的实例。
5. 创建`%Library.ListOfObjects`的实例。
并使用它的`Insert()`方法插入`%XML.Security.EncryptedKey`的实例。
刚从文档中获得的。
6. 调用类`%XML.Security.EncryptedData`的`ValidateDocument()`方法
```java
set status=##class(%XML.Security.EncryptedData).ValidateDocument(.doc,keys)
```
第一个参数(通过引用返回)是在第2步中检索到的DOM的修改版本。
第二个参数是上一步中的键列表。
7. 可以选择使用`%XML.Writer`为修改后的DOM生成输出。
例如,前面显示的`CONTAINER`类包含以下类方法:
```java
ClassMethod DecryptDoc(filename As %String)
{
#Include %soap
set reader = ##class(%XML.Reader).%New()
set status = reader.OpenFile(filename)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit }
set doc = reader.Document
//获取元素
do reader.Correlate("EncryptedKey","%XML.Security.EncryptedKey")
if 'reader.Next(.ikey,.status) {
write !,"无法导入密钥",!
do $system.OBJ.DisplayError(status)
quit
}
set keys = ##class(%Collection.ListOfObj).%New()
do keys.Insert(ikey)
// 以下步骤返回解密的文档
set status = ##class(%XML.Security.EncryptedData).ValidateDocument(.doc,keys)
set writer = ##class(%XML.Writer).%New()
set writer.Indent = 1
do writer.Document(doc)
quit $$$OK
}
```
文章
姚 鑫 · 六月 15, 2021
# 第七章 控制命名空间分配的外观
# 控制命名空间分配的外观
除了控制命名空间分配外,还可以控制命名空间分配在XML输出中的显示方式。具体地说,可以控制以下内容:
## 显式名称空间分配与隐式名称空间分配
将元素和属性分配给命名空间时,XML中有两种等效的表示形式,由编写器实例的`SuppressXmlns`属性控制。
为一个名为`Person`的对象生成XML输出,该对象被分配给名称空间`“http://www.person.org”`(通过前面讨论的`namespace`类参数)。
使用缺省输出(`SuppressXmlns`等于0)的示例如下:
```java
Uberoth,Amanda Q.
1952-01-13
```
另一种可能的形式完全相同,如下所示。
这是使用`SuppressXmlns`等于1生成的,它确保显式分配给名称空间的每个元素都显示为该名称空间的前缀。
```java
Uberoth,Amanda Q.
1952-01-13
```
请注意,此属性仅影响命名空间分配的显示方式;它不控制如何分配任何命名空间。如果不使用命名空间,则此参数无效。
## 为命名空间指定自定义前缀
当为对象生成XML输出时,系统会根据需要生成命名空间前缀。第一个名称空间前缀是`s01`,下一个是`s02`,依此类推。可以指定不同的前缀。为此,请在启用XML的对象本身的类定义中设置`XMLPREFIX`参数。此参数有两个效果:
- 它确保在XML输出中声明指定的前缀。也就是说,即使没有必要这样做,它也会被声明。
- 它使用该前缀,而不是在其他情况下会看到的自动生成的前缀。
# 控制空字符串(`""`)的导出方式
为对象启用XML时,需要指定将空值和空字符串投影到XML的方式
其中一个选项是在支持xml的类中将`XMLIGNORENULL`设置为`“RUNTIME”`(不区分大小写)。
在这种情况下,当使用%XML.Write的`RuntimeIgnoreNull`属性的值来确定如何处理任何等于`""`的属性,如下所示:
- **如果编写器的`RuntimeIgnoreNull`属性为0(默认值),则`XMLNIL`参数控制如何导出该属性。`XMLNIL`是一个类参数和一个属性参数;属性参数优先。**
- 如果`XMLNIL`为0(默认值),则不投影特性。也就是说,它不包含在XML文档中。
- 如果`XMLNIL`为1,并且该属性用作元素,则该属性将按如下方式导出:
```
```
- 如果`XMLNIL`为1并且特性用作属性,则不会输出特性。
- 如果编写器的`RuntimeIgnoreNull`属性为1,则该属性将导出为空元素或空属性(其导出方式与值`$char(0)`相同,后者始终导出为空元素或空导出)。
除非`XMLIGNORENULL`在启用xml的类中是`“RUNTIME”`,否则编写器的`RuntimeIgnoreNull`属性是无效的。
## 示例:`RuntimeIgnoreNull`为0(默认值)
```java
Class EmptyStrings.Export Extends (%Persistent, %XML.Adaptor)
{
Parameter XMLNAME="Test";
Parameter XMLIGNORENULL = "RUNTIME";
///把这个作为一个元素
///XMLNIL is 0, the default
Property Property1 As %String;
///把这个作为一个元素
///XMLNIL is 0, the default
Property Property2 As %String(XMLPROJECTION = "ATTRIBUTE");
///将其作为XMLNIL=1的元素
Property Property3 As %String(XMLNIL = 1);
///将其作为XMLNIL=1的属性进行项目
Property Property4 As %String(XMLNIL=1,XMLPROJECTION="ATTRIBUTE");
}
```
如果创建了这个类的一个新实例(并且没有设置任何属性的值),然后使用 `%XML.Writer`输出,如下所示:
```java
```
## 例如:`RuntimeIgnoreNull`为1
```java
```
**在本例中,因为`RuntimeIgnoreNull`为1,所以没有使用`XMLNIL`参数。
相反,`""`被导出为空属性或空元素。**
# 导出类型信息
默认情况下,XML编写器不写入类型信息。有两个选项可用于在输出中包括类型信息:
- 编写器的`OutputTypeAttribute`属性。如果此属性为1,则编写器包括其写入的对象内所有元素的XML类型信息(但不包括对象本身)。例如:
```java
Petersburg,Greta U.
1949-05-15
```
请注意,相应的命名空间将添加到XML文档的根。
- `Object()`和`RootObject()`方法的`className`参数。此参数用于指定对象的预期`ObjectScript`类型(类名)。
如果参数与实际类型相同,则编写器不包括对象的类型信息。
如果参数与实际类型不同,编写器将包括对象的实际XML类型(默认为类名)。例如,假设`Test2.PersonWithAddress`实例编写输出,并将`className`参数指定为`MyPackage.MyClass`。因为`MyPackage.MyClass`与实际的类名不同,所以编写器生成以下输出:
```java
Avery,Robert H.
Ukiah
82281
```
# 生成SOAP编码的XML
对于`%XML.Writer`,`Format`属性控制输出的整体格式。这是以下选项之一:
- `“literal”`,即默认值,在本书的大多数例子中都使用了它。
- `“encoded”`,按照SOAP 1.1标准中的描述进行编码。
- `“encoded12”`,按照SOAP 1.2标准中的描述进行编码。
## 创建内联引用
在编码格式中,任何对象值属性都被作为引用包含,被引用的对象被导出为单独的元素。
要将这些属性内联导出,而不是作为单独的元素,请将`ReferencesInline`属性设置为1。
如果格式是`“literal”`,`ReferencesInline`属性没有效果。
# 导出后控制unswizling
当导出一个支持xml的持久对象时,系统会像往常一样自动将所有需要的信息混合到内存中;该信息包括对象值属性。导出对象后,InterSystems IRIS将消除任何对象列表,但(默认情况下)不会消除单个对象引用。
对于大对象,这可能导致``错误。
在这种情况下,要使任何单个对象引用不被混合,请在支持xml的类中设置`XMLUNSWIZZLE`参数,如下所示:
```java
Parameter XMLUNSWIZZLE = 1;
```
该参数默认值为0。
# 控制元素的关闭
只包含属性的元素可以用以下任一方式表示:
```java
```
`Object()`方法始终使用第一个语法导出元素。如果需要使用此处显示的第二种语法关闭元素,请手动编写对象,如本章前面的“手动构造元素”中所述。
咨询一下,我也是初学,有没有类似于oracle的exp远程备份数据库的方法?
文章
王喆 👀 · 九月 13, 2022
背景
作为集成平台厂商,在医院同其它系统联调的时候所做的事情中,多的不是开发代码而是查消息。我先演示一下目前我正在使用的IRIS查消息的方式:
例子1:
需要看【个人信息注册服务】
我只需要在框中输入【个人信息注册】回车
点击【查看消息】,显示的是消息里面的内容,如图所示:
点击【查看流程】,显示的是IRIS消息可视化的页面,如图所示:
例子2:
需要查询患者ID为【2874621】这患者在集成平台调用了哪些服务
我只需要选择下拉选择【患者ID】,然后输入【2874621】,回车
这个是我们以用户需求的角度直观的查询到指定的消息,IRIS也有这个功能—消息查看器,它是如何使用的呢?首先,我们得知道这条消息使用了哪些Production组件,其次我们需要了解这个消息使用的实体类的结构。比如同样查这个人的消息,我需要输入request.Body.PatientID=“2874621”,选择消息类。如果我需要查多个服务的我还需要多选消息类 …… 本文不是来介绍如何使用消息查看器的,各位大概知道就好。
程序分析与设计思路
原始消息查看器的使用
我们先使用IRIS自带的【消息查看器】查患者ID为【2874621】个人信息注册服务,如图所示:
选时间,输入图中所示的条件去检索。步骤上好像也挺简单的,但是这里有两个前提条件,一是我得知道每个服务对应的消息类是哪个。二是我知道这个消息类里面患者ID对应的字段是哪个,而且这个字段不能在循环里面(好像说了3个条件 ~ 哈哈),如何处理摆在了我们的眼前。
SQL分析
使用IRIS自带的【显示查询】功能(这个如何开启使用可以看我的另外一篇文章 https://cn.community.intersystems.com/node/525741 ),如图所示:
找个位置格式化一下:
可以看到从这段SQL可以看出3点:
(1)IRIS消息记录的表是【Ens.MessageHeader】
(2)IRIS保存消息是给实体本身建立了一个表 比如上述的例子:
【BKIP_PatientInfo_MSG.PatientInfoRegister】
(3)head.MessageBodyId = BKIP_PatientInfo_MSG.PatientInfoRegister.%ID
通过这3条,我们可以得到下面这张图的信息:
倘若我们把SQL按照如上图所示的方式处理,然后反应在页面上供用户选择;患者ID也由用户选择或者输入;这样确实直观的解决了。但是,效率,上述消息查看器在用户选择时间的时候首先查到两个ID让查询就在这两个主键ID之间查找,增加了效率,我们当然可以使用。那么它是必须的么,去掉的话我们速度一定变慢?这是其一。第二点,如果患者ID是在循环中,甚至是在循环的循环中…… 消息查看器好像就没办法了(当然如果有大佬可以解决请接受我的膝盖)。
需要解决的问题
对照表,服务和消息类的对照表
如何把循环中的字段作为关键字进行查询
第一个问题就是解决方案,新建一张表维护服务和消息类的关系,第二个问题:我们要去看一下Ens.MessageHeader这张表:
好像看不出什么。。。
大家可以看看这个图
在图中红框框住的部分是一样的,同时我们进入消息可视化,如图:
我们可以不可以说这个会话ID就是代表了这个服务的这条消息呢?我们要查指定服务的指定人的消息可以理解为查这个会话ID也就是SessionID。我们把思路换一下,如果我把循环中的患者ID遍历出来存入一张表中,这一行的数据我有sessionID、患者ID、服务名。当我想查患者ID为【2874621】的个人信息注册服务得到sessionID之后,直接跳到可视化追踪,如果把患者ID换成医嘱ID也是一样的处理,其它关键字段也是一样,问题是不是迎刃而解?
程序设计(思路)
新建一个索引表 字段为 SessionID、服务名、属性名、属性值、创建时间,如图所示:
然后在每次服务被调用的时候取出我们需要的属性名、属性值和SessionID存入这张表中,如图所示:
我们在页面上进行查询的时候只需要编写如图所示的SQL,省略号代表and后面的条件。
后续的REST接口和前端的页面在此就【略】,大家可以参考我的另外一篇文章提供的思路编写提供出来Restful接口(https://cn.community.intersystems.com/node/525561 )。
总结
总的来说,我们思路是我们把原本数据量变小,把原本的多重循环的问题维护在一张单表的多行数据中去。这样把原本的多表联合查询改成了单表查询。这速度不快的飞起?目前这边只做思路分享,相信程序设计上大家肯定有自己的一套方式,我这边暂时不做过多展示。都看到这里了,给我点一个赞吧!!! 可以考虑使用ElasticSearch做全文搜索 是的,用ES是最好的。但是ES中有一个很重要的概念叫倒排索引,就是拆分词语存入倒排索引的库,方便在检索的时候分析,和我这边的把一个消息关键字段拆分成索引去检索是否有一点异曲同工捏。当然还是那句话ES是最好的,只不过我这边不需要那么高性能,而且也没试过IRIS结合ES去使用,这里只做分享哈。 还可以考虑用iknow(现在叫InterSystems NLP)实现全文检索
文章
姚 鑫 · 六月 17, 2023
# 第六十章 镜像中断程序 - 使用主 `ISCAgent` 的日志数据进行 `DR` 提升和手动故障转移
## 使用主 `ISCAgent` 的日志数据进行 `DR` 提升和手动故障转移
如果 `IRIS A` 的主机系统正在运行,但 `IRIS` 实例没有且无法重新启动,您可以使用以下过程在通过升级后使用来自 `IRIS A` 的最新日志数据更新升级的 `IRIS C IRIS A` 的 `ISCAgent`。
1. 推广 `IRIS C`,选择 `IRIS A` 作为故障转移伙伴。 `IRIS C` 被提升为故障转移成员,从 `IRIS A` 的代理获取最新的日志数据,并成为主要成员。
2. 重新启动 `IRIS A` 上的 `IRIS` 实例,它作为备份重新加入镜像。
3. 在 `IRIS A` 重新加入镜像并变为活动状态后,可以使用使用升级的 DR 异步临时替换故障转移成员中描述的过程,将所有成员返回到它们以前的角色,首先是正常关闭 `IRIS C` ,然后在 `IRIS B` 的配置参数文件的 `[MirrorMember]` 部分中设置 `ValidatedMember=0`(请参阅配置参数文件参考中的 `[MirrorMember]`),将 `IRIS B` 重新启动为 `DR` 异步,将 `IRIS B` 提升为备份,并以 `DR` 异步方式重新启动 `IRIS C`。
注意:如果 `IRIS A` 的主机系统已关闭,但 `IRIS B` 的主机系统已启动,尽管其 `IRIS` 实例未运行,请按照手动故障转移到活动备份中所述在 `IRIS B` 上运行 `^MIRROR` 例程以确定 是否`IRIS B` 在发生故障时是一个活动备份。如果是这样,使用前面的过程,但在升级期间选择 `IRIS B` 作为故障转移伙伴,允许 `IRIS C` 从 `IRIS B` 的 `ISCAgent` 获取最新的日志数据。
## 使用来自日志文件的日志数据进行 DR 提升和手动故障转移
如果 `IRIS A` 和 `IRIS B` 的主机系统都已关闭,但可以访问 `IRIS A` 的日志文件,或者 `IRIS B` 的日志文件和消息日志可用,您可以使用最新的日志数据更新 `IRIS C`从升级前的初级开始,使用以下过程。
1. 使用 `IRIS A` 或 `IRIS B` 的最新日志文件更新 `IRIS C`,如下所示:
- 如果 `IRIS A` 的日志文件可用,则将最新的镜像日志文件从 `IRIS A` 复制到 `IRIS C`,从 `IRIS C` 上的最新日志文件开始,并包括来自 `IRIS A` 的任何后续文件。例如,如果 `MIRROR -MIRRORA-20180220.001` 是 `IRIS C` 上的最新文件,复制 `MIRROR-MIRRORA-20180220.001` 和 `IRIS A` 上的任何更新文件。
- 如果 `IRIS A` 的日志文件不可用但 `IRIS B` 的日志文件和消息日志可用:
1. 确认`IRIS B`很可能已被捕获,如下所示:
a. 确认当`A`及其代理不可用时,`B`同时断开与 A的连接。可以通过在`Messages.log`文件中搜索类似于以下内容的消息来检查 `IRIS B`断开连接的时间:
```java
MirrorClient: Primary AckDaemon failed to answer status request
```
b. 通过在其 `messages.log` 文件中搜索类似于以下内容的消息,确认 IRIS B 在断开连接时是活动备份:
```java
Failed to contact agent on former primary, can't take over
```
注意:`messages.log` 文件中的如下消息表明 `IRIS B` 在断开连接时未处于活动状态:
```java
nonactive Backup is down
```
当无法确认它是否已被追上时强制提升的 `DR` 异步成为主数据库可能会导致它成为主数据库而没有镜像生成的所有日志数据。因此,一些全局更新操作可能会丢失,而其他镜像成员可能需要从备份中重建。
2. 如果可以确认 `IRIS B` 处于活动状态,请将最新的镜像日志文件从 `IRIS B` 复制到 `IRIS C`,从 `IRIS C` 上的最新日志文件开始,然后包括来自 `IRIS B` 的所有后续文件。例如,如果 `MIRROR-MIRRORA-20180220.001` 是 `InterSystems IRIS C` 上的最新文件,请从 `IRIS C` 复制 `MIRROR-MIRRORA-20180220.001` 和任何更新的文件。检查文件的权限和所有权,并在必要时更改它们以匹配现有日志文件。
2. 在不选择故障转移合作伙伴的情况下将 `IRIS C` 提升为故障转移成员。 `IRIS C` 成为主要的。
3. 当 `IRIS A` 和 `IRIS B` 的问题得到修复时,尽早并在重新启动 `IRIS` 之前,在每个成员上的 `IRIS` 实例的配置参数文件的 `[MirrorMember]` 部分中设置 `ValidatedMember = 0`(参见 `[ MirrorMember]` 在配置参数文件参考)。说明指出,此更改是必需的。完成此操作后,在每个成员上重新启动 `IRIS`,从 `IRIS A`(最近成为主成员的成员)开始。
1. 如果成员在 `IRIS` 重新启动时作为备份或 `DR` 异步加入镜像,则不需要进一步的步骤。任何在故障成员上但不在当前主成员上的日志数据都已被丢弃。
2. 如果在 `IRIS` 实例重新启动时成员无法加入镜像,如重建镜像成员中描述的引用不一致数据的消息日志消息所示,则成员上的最新数据库更改晚于存在于上的最新日志数据 `IRIS C` 成为主要时。要解决此问题,请按照该部分中的描述重建成员。
4. 在大多数情况下,`DR` 异步系统不是主要故障转移成员的合适永久主机。在 `IRIS A` 和 `IRIS B` 重新加入镜像后,使用使用升级的 `DR` 异步临时替换故障转移成员中描述的过程将所有成员返回到它们以前的角色。如果 `IRIS A` 或 `IRIS B` 作为备份重新启动,则在备份处于活动状态时从正常关闭 `IRIS C` 开始,以故障转移到备份;如果 `IRIS A` 或 `IRIS B` 都重新启动为 `DR` 异步,将其中一个提升为备份,然后在 `IRIS C` 上执行正常关闭。将另一个以前的故障转移成员提升为备份,然后将 `IRIS C` 作为 `DR` 异步重启。
文章
Guangliang Zhang · 十月 21, 2022
cache数据库自身带有系统监控Portal界面,但需要运维人员定期主动查看才能获取监控信息。当系统故障发生时,容易出现由于没有及时获取故障信息而不能及时处理,从而导致造成的影响扩大。本文研究通过解析cache数据库控制台日志(cconsole.log)进行监控信息获取并主动推送微信或短信实现cache数据库主动实时监控。
cache数据库在运行时会将所有控制台消息包括一般消息、系统错误、某些操作系统错误和网络错误都会发送到控制台日志文件,通过操作员控制台工具从其他系统远程启动的作业的成功或失败等信息也会写入控制台日志,因此通过对控制台日志的解析即可获取所需要监控信息。具体步骤方法如下:
解析控制台日志
控制台日志默认存储在install-dir\mgr路径下。
根据cache版本不同,使用的读取方法也不同。对于cache2016版本以上,系统提供了EnsLib.SQL.Snapshot类,可以直接获取日志的行和列信息,非常方便。对于cache2010及以下版本则无此方法,需要使用%File文件读取方法。
随着系统运行时间增加,控制台日志也会不断增大,造成每次检索时间加大,且不容易找出最近的监控信息,作出有效监控。较好的方式是每次解析时均从上次的节点继续进行,以便获取新的有效的监控信息。可以使用指定global来记录每次执行的最后一行ID,下次执行时从ID+1行开始解析来达到目的。对于控制台日志,EnsLib.SQL.Snapshot方法里提供了方法以获取当前行,对于cache2010及以下版本,则需要记录最后一次处理的内容,再次处理的时先找到本条内容的位置,从此位置继续处理。代码示例如下。
cache2016及以上版本代码示例:
ClassMethod GetAlertInfo() As %String{
//cconsole.log存储路径 set FilePath="D:\InterSystems\HealthShare\mgr\cconsole.log"
//使用EnsLib.SQL.Snapshot读取日志 set snap=##class(EnsLib.SQL.Snapshot).%New() set snap.MaxRowsToGet=-1 do snap.ImportFile(FilePath,," ") do Consolelogshow do snap.Clean() quit $$$OK
//处理日志内容Consolelogshow set colsCount=snap.ColCount
//获取上一次处理的最后一行行号 set snap.%CurrentRow=$g(^AlertCurrentRow("Consolelog","CurrentRow")) k ^AlertCurrentRow("Consolelog","PID") while snap.Next() { set alertLevel=snap.GetData(3) set pid=snap.GetData(2) set pid=$p(pid,"(",2) set pid=$p(pid,")",1) continue:pid=""
//0级别信息不处理 continue:((alertLevel=0))
//进程号已存在不处理,即只获取每次每个进程的第一条告警/错误信息 continue:($d(^AlertCurrentRow("Consolelog","PID",pid)))
//记录已处理的进程号 set ^AlertCurrentRow("Consolelog","PID",pid)=1
//定义并组装监控消息内容 set alertInfo="" i ((alertLevel=1)||(alertLevel=2)||(alertLevel=3)){ f i=4:1:colsCount{ set alertInfo=alertInfo_" "_snap.GetData(i) } set alertInfo=snap.GetData(1)_":"_alertLevel_":"_alertInfo continue:((alertLevel=1)&&(alertInfo'["Warning"))
//发送监控信息至信息推送平台,如微信或短信等 set rt=..sendAlertInfo(alertInfo) }
//记录当前处理的行 set ^AlertCurrentRow("Consolelog","CurrentRow")=snap.%CurrentRow } quit $$$OK}
cache2010及以下版本代码示例:
ClassMethod GetAlertInfo() As %String{ //使用%File读取日志 set file=##class(%File).%New("/cachesys/mgr/cconsole.log") set Status= file.Open("R") k ^AlertCurrentRow("Consolelog","PID")
//获取上次处理的信息内容 set LastPosition=$g(^AlertCurrentRow("Consolelog","LastPosition")) s:LastPosition="" LastPosition=0 set LastPosition=LastPosition+1 set int=0 while 'file.AtEnd{ i int=0{
//跳转到上次处理的最后位置 do file.MoveTo(LastPosition) } set file.LineTerminator=$c(10) set text= file.ReadLine(,.sc) set int=int+1
//解析日志 set pid=$p(text," ",2) set pid=$p(pid,"(",2) set pid=$p(pid,")",1) set alertLevel=$p(text," ",3) s:text'="" textLast=text
//定义并组装监控消息内容 i ((alertLevel=1)&&(alertLevel["Warning"))||(alertLevel=2)||(alertLevel=3){ set length=$length(text," ") set alert="",alertInfo="" f i=4:1:length{ set alert=$p(text," ",i) i i=4{ set alertInfo=alert }else{ set alertInfo=alertInfo_" "_alert } } set alertInfo=$p(text," ",1)_": "_alertLevel_": "_pid_":"_alertInfo set alertInfo=$zcvt(alertInfo,"O","UTF8") i '$d(^AlertCurrentRow("Consolelog","PID",pid)){
//记录已处理的进程号 set ^AlertCurrentRow("Consolelog","PID",pid)=1 //发送监控信息至信息推送平台,如微信或短信等 set rt=..sendAlertInfo(alertInfo) } } }
//记录最后处理的行内容 set ^AlertCurrentRow("Consolelog","LastPosition")=file.FindAt(1,textLast) do file.Close() quit $$$OK}
控制台日志在记录信息时,经常会记录一系列问题。记录模式为时间+进程ID+信息级别+详细信息。如图1所示。信息级别从0级到3级,问题严重程度依次增加。为0的一般为正常运行信息,也是占日志主要部分的内容。问题级别为1的为警告信息。问题级别为2的为错误信息。问题级别为3的为严重错误信息。因此信息级别为1以上均是监控需要关注的信息。需要将此部分信息及时发出。
图1 cconsole.log普通记录示例
有时候控制台日志也会有其它格式的信息出现,如图2所示。此类信息说明有比较严重的异常行为出现,需要重点关注,因为一般将其全部发出。
图2 cconsole.log特殊记录示例
监控信息推送
控制台日志会对一个进程的多个操作进行记录,如果将所有信息发出,则会造成信息量过大而找不到重点。一般将每个进程的第一个告警或错误信息发出即可。处理方式见上述示例代码。
将获取到所需要发送的监控信息调取微信(如图3)或短信(如图4)或其它信息推送终端的接口即可进行信息推送。
图3 微信告警示例 图4 短信告警示例
任务部署
将程序部署成定时任务,按照所需自定义监控频率即可实现cache数据库的主动实时监控。如图5所示。
图5 监控任务示例 确实升级后的Cache强了很多 这个极实用,推荐大伙收藏。 实用,收藏
文章
Michael Lei · 二月 14
FHIR 用例集: 打破数字医疗壁垒,实现高质量发展
--促进互联互通,改进工作流程,提高数据洞察
简介
HL7® FHIR®(快速医疗互操作性资源)是以电子方式访问、交换和管理医疗信息的国际标准。与以往的标准不同,FHIR 可让帮助行业从业者轻松构建创新应用程序,有效地收集、汇总和分析来自不同来源的各种医疗保健和管理数据。医疗机构、社保/保险公司、政府机构、生命科学公司、医疗设备制造商和医疗科技等多种主体利用 FHIR 来简化信息流、提高数据洞察力、改善临床效果和业务成果。
FHIR 基于 JSON、HTTP 和 REST 等流行的网络技术。有了 FHIR,没有医疗信息化背景的软件开发人员也能使用熟悉的开发工具和开源技术,快速、轻松地满足政府机构、临床医生、研究人员、医疗行业从业者以及各类市场主体的数据需求。
FHIR 是一种灵活、适应性强的医疗数据模型,可轻松定制,以实现各种用例的互操作性。FHIR 由称为 "资源 "的离散、可计算的数据对象组成,以实现最佳效率。通过 FHIR 资源,应用程序可以访问单个医疗记录元素,而无需检索摘要文档中包含的所有数据。
本文回顾了 FHIR 的实际应用,并提供了 InterSystems 客户如何使用 FHIR 连接不同系统、加速数字化转型和提高数据洞察力的真实案例。
FHIR 商机无限
FHIR 正在改变医疗健康数据的访问和交换。无论您是为政府、医疗机构、公共卫生机构、保险公司还是厂商工作,FHIR 都能帮助您高效地获取、检索和共享来自电子病历系统、智能医疗设备、可穿戴设备、临床试验和公共卫生监测系统等不同来源的医疗数据。
当前应用和未来的 FHIR 用例FHIR支持实现大量的不同业务场景。您可以在各种部署场景中将 FHIR 用于各种目的。下面的列表总结了 FHIR 在不同行业领域的一些当前应用和潜在的未来用例。
医疗机构
应用场景: 患者数据访问 API机会:可以基于FHIR资源和技术框架实现卫健委互联互通三年攻坚计划以及国家数据局"数据要素x医疗行业"三年行动计划中提到的相关电子健康档案共享、检验检查互认、医疗行业数据要素流通、交易等战略目标,通过基于标准的 (FHIR) API 让患者以程序化的方式访问其健康数据(病史、化验结果、治疗计划等),以及未来可能的全国统一医疗健康档案超级APP(患者端)。
应用场景: 临床决策支持机会: 使用 FHIR 改善临床决策系统的洞察力。将实时电子病历数据安全传输到第三方系统进行分析并返回建议,帮助临床医生做出明智决策。与以往的标准和方法不同,使用 FHIR,您可以将临床决策支持功能直接嵌入电子病历,以简化流程。
应用场景: 医疗机构与支付方(医保/保险公司)的合规数据交换 机会: 利用 FHIR 自动化医疗机构与支付方之间的数据交换。消除资源密集、耗时的人工流程(降低飞行检查和审计成本)。允许医疗机构直接将电子病历数据转发给支付方,无需人工干预。
使用案例: 临床试验和研究机会: 使用 FHIR 无缝共享临床试验招募和分析所需的患者数据,加快临床研究进程。
设备制造商、医疗科技公司和应用开发商
用例:远程医疗和远程监控机会: 使用 FHIR 可将患者数据从家用医疗设备安全地传输给医疗服务提供者,以便他们有效地远程监控和管理患者。
用例: 移动医疗应用程序机遇: 患者可以在手机端访问在不同医院治疗的电子病历,并且确保患者数据的隐私和安全。
用例: 慢性病管理应用程序机会: 使用 FHIR 在医疗服务提供者之间无缝共享患者数据,以实现一致的监控和协调的护理计划。
用例: 药物管理应用程序机会: 为临床医生和护理人员创建多功能药物管理应用程序。使用 FHIR 在区域全民健康信息平台之间高效共享处方信息、用药计划和药房记录。
生命科学公司、政府机构和付款人
用例:健康信息交换机会: 使用 FHIR,政府、公共卫生、保险公司等可高效开展电子健康档案/电子病历共享调阅数据,以进行质量评估、护理差距识别、理赔裁定以及开展潜在的数据交易等。
用例: 护理计划机会: 利用 FHIR,让跨机构护理团队--医生、家庭医疗工作者、社区护理人员、家庭成员等--能够无缝交换信息。让不同的医疗保健系统进行有效沟通。确保所有护理团队成员都能获得最新的患者信息。
用例: 公共卫生报告机会: 使用 FHIR 有效地汇总和共享患者数据,以进行监控和人口健康管理,从而简化公共卫生报告。利用电子病历批量检索功能。(注:该功能自 2022 年起已成为所有美国电子病历系统的强制性要求,在WHO、OECD、欧盟、亚洲、港澳台等地区也正在逐步推广普及)
以上只是部分FHIR的用例,有了FHIR,从业者可以打开无限想象空间,创建丰富多样、互联互通的数字医疗创新应用。
文章
姚 鑫 · 二月 24, 2021
# 第四十六章 Caché 变量大全 ^$GLOBAL 变量
提供有关全局变量和进程私有全局变量的信息。
# 大纲
```java
^$|nspace|GLOBAL(global_name)
^$|nspace|G(global_name)
^$||GLOBAL(global_name)
^$||G(global_name)
```
# 参数
- `|nspace|` 或 `[nspace]` - 可选-扩展SSVN引用,可以是显式名称空间名称,也可以是隐含名称空间。必须计算为带引号的字符串,该字符串括在方括号(`[“nspace”]`)或竖线(`|“nspace”|`)中。命名空间名称不区分大小写;它们以大写字母存储和显示。
- global_name 计算结果为包含无下标全局名称的字符串的表达式。全局名称区分大小写。使用`^$||global()`语法时,与进程专用全局名称相对应的无下标全局名称:`^a`表示`^||a`。
# 描述
可以将`^$GLOBAL`用作`$DATA`、`$ORDER`和`$QUERY`函数的参数,以返回有关当前名称空间(默认名称空间)或指定名称空间中是否存在全局变量的信息。还可以使用`^$global`返回有关存在进程私有全局变量的信息。
## 进程私有全局变量
可以使用`^$global`获取有关所有命名空间中是否存在进程私有全局变量的信息。可以将进程专用全局的查找指定为`^$||global`或`^$|“^”|global`。
例如,要获取有关进程私有全局`^||a`及其后代的信息,可以指定`$DATA(^$||global(“^a”))`。进程私有全局变量不是特定于名称空间的,因此在定义进程私有全局变量时,无论当前名称空间如何,此查找都会返回有关`^||a`的信息。
请注意,`^$GLOBAL`不支持在`GLOBAL_NAME`本身中指定进程专用全局语法。使用进程专用全局语法指定`GLOBAL_NAME`会导致``错误。
# 参数
## nspace
此可选参数允许`^$GLOBAL`查找在另一个命名空间中定义的`GLOBAL_NAME`。这称为扩展SSVN参考。可以显式地将命名空间名称指定为带引号的字符串文字、变量,也可以通过指定隐含的命名空间来指定。命名空间名称不区分大小写。可以使用方括号语法`[“user”]`或环境语法`|“user”|`。Nspace分隔符前后不允许有空格
可以使用以下方法测试是否定义了命名空间:
```java
DHC-APP>WRITE ##class(%SYS.Namespace).Exists("USER")
1
DHC-APP>WRITE ##class(%SYS.Namespace).Exists("LOSER")
0
```
以使用`$NAMESPACE`特殊变量来确定当前名称空间。更改当前名称空间的首选方式是新建`$NAMESPACE`,然后设置`$NAMESPACE=“nspace ename”`。
## global_name
计算结果为包含无下标全局名称的字符串的表达式。全局变量区分大小写。
- `^$global(“^a”)`:`global_name“^a”`在当前名称空间中查找此全局名称及其后代。它不查找进程私有全局`“^||a”`。
- `^$|"USER"|GLOBAL("^a")`:global_name `"^a"`在`“user”`名称空间中查找此全局名称及其后代。它不查找进程-私有全局`"^||a"`。
-` ^$||GLOBAL("^a")`:global_name `"^a"`在所有名称空间中查找进程私有全局`"^||a"`及其后代。它不查找全`"^a"`。
# 示例
以下示例显示如何将`^$GLOBAL`用作`$DATA`、`$ORDER`和`$QUERY`函数的参数。
## 作为`$DATA`的参数
`^$GLOBAL`作为`$DATA`的参数返回一个整数值,表示指定的全局名称是否作为`^$GLOBAL`节点存在。下表显示了`$DATA`可以返回的整数值。
Value | Meaning
---|---
0| 全局名称不存在
1| 全局名称是包含数据但没有子代的现有节点。
10| 全局名称是没有数据但具有子代的现有节点。
11| 全局名称是包含数据的现有节点,并且具有子代。
下面的示例测试当前命名空间中是否存在指定的全局变量:
```java
/// d ##class(PHA.TEST.SpecialVariables).GLOBAL()
ClassMethod GLOBAL()
{
KILL ^GBL
WRITE $DATA(^$GLOBAL("^GBL")),!
SET ^GBL="test"
WRITE $DATA(^$GLOBAL("^GBL")),!
SET ^GBL(1,1,1)="subscripts test"
WRITE $DATA(^$GLOBAL("^GBL"))
}
```
```java
DHC-APP>d ##class(PHA.TEST.SpecialVariables).GLOBAL()
0
1
11
```
下面的示例测试user命名空间中是否存在指定的全局变量:
```java
/// d ##class(PHA.TEST.SpecialVariables).GLOBAL1()
ClassMethod GLOBAL1()
{
SET $NAMESPACE="USER"
SET ^GBL(1)="test"
SET $NAMESPACE="%SYS"
WRITE $DATA(^$|"USER"|GLOBAL("^GBL"))
}
```
```java
DHC-APP>d ##class(PHA.TEST.SpecialVariables).GLOBAL1()
10
```
下面的示例测试任何命名空间中是否存在指定的进程私有全局变量:
```java
/// d ##class(PHA.TEST.SpecialVariables).GLOBAL2()
ClassMethod GLOBAL2()
{
SET $NAMESPACE="USER"
SET ^||PPG(1)="test"
SET $NAMESPACE="%SYS"
WRITE $DATA(^$||GLOBAL("^PPG"))
}
```
```java
DHC-APP>d ##class(PHA.TEST.SpecialVariables).GLOBAL2()
10
```
## 作为`$ORDER`的参数
`$ORDER(^$|nspace|GLOBAL( global_name),direction)`
`^$GLOBAL`作为`$ORDER`的参数,将排序序列中的下一个或上一个全局名称返回到指定的全局名称。如果`^$GLOBAL`中不存在这样的全局名称节点,`$ORDER`将返回空字符串。
注意:`$ORDER(^$GLOBAL(NAME))`不会从IRISSYS数据库返回`%global names`。
Direction参数指定是返回下一个全局名称还是返回上一个全局名称。如果不提供方向参数,InterSystems IRIS会将排序顺序中的下一个全局名称返回给您指定的全局名称。
以下子例程搜索当前名称空间,并将全局名称存储在名为global的本地数组中。
```java
/// d ##class(PHA.TEST.SpecialVariables).GLOBAL3()
ClassMethod GLOBAL3()
{
GLOB
SET NAME=""
WRITE !,"以下全局变量在 ",$NAMESPACE
FOR I=1:1 {
SET NAME=$ORDER(^$GLOBAL(NAME))
WRITE !,NAME
QUIT:NAME=""
SET GLOBAL(I)=NAME
}
WRITE !,"全部完成"
QUIT
}
```
```java
DHC-APP>d ##class(PHA.TEST.SpecialVariables).GLOBAL3()
以下全局变量在 DHC-APP
^%ISCWorkQueue
^%cspSession
^%qCacheMsg
^%qCacheMsgNames
^%qCacheObjectErrors
^%qCacheObjectKey
^%qCacheObjectQualifier
^%qCacheSQL
^%qHTMLElementD
^%qJavaMetaDictionary
^%qMgtPortal.Index
^%qPublicSuffix
^%qStream
^%qcspRule
^A
^AA
Visible+4^%SYS.GD
DHC-APP>
```
## 作为`$QUERY`的参数
`^$GLOBAL`作为`$QUERY`的参数,按排序顺序将下一个全局名称返回到指定的全局名称。如果`^$GLOBAL`中不存在这样的全局名称作为节点,则`$QUERY`将返回空字符串。
注意:`$QUERY(^$GLOBAL(NAME))`不会从IRISSYS数据库返回`%GLOBAL NAMES`。
在以下示例中,用`user`命名空间中存在三个全局变量(`^GBL1`、`^GBL2`和`^GBL3`)。
```java
/// d ##class(PHA.TEST.SpecialVariables).GLOBAL4()
ClassMethod GLOBAL4()
{
NEW $NAMESPACE
SET $NAMESPACE="USER"
SET (^GBL1,^GBL2,^GBL3)="TEST"
NEW $NAMESPACE
SET $NAMESPACE="%SYS"
WRITE $QUERY(^$|"USER"|GLOBAL("^GBL1")),!
WRITE $QUERY(^$|"USER"|GLOBAL("^GBL2"))
NEW $NAMESPACE
SET $NAMESPACE="USER"
KILL ^GBL1,^GBL2,^GBL3
}
```
```java
DHC-APP>d ##class(PHA.TEST.SpecialVariables).GLOBAL4()
^$|"USER"|GLOBAL("^GBL2")
^$|"USER"|GLOBAL("^GBL3")
```
## 作为`MERGE`的参数
`^$GLOBAL`作为`MERGE`命令的源参数,将全局目录复制到目标变量。`Merge`将每个全局名称添加为具有空值的目标下标。下面的示例显示了这一点:
```java
MERGE gbls=^$GLOBAL("")
ZWRITE gbls
```
```java
...
gbls("^zlgsql")=""
gbls("^zlgtem")=""
gbls("^zlgtem1")=""
gbls("^zlgtem4")=""
gbls("^zlgtemp")=""
gbls("^zlgtemp1")=""
gbls("^zlgtemp3")=""
gbls("^zlgtemp5")=""
gbls("^zlgtmp")=""
gbls("^zlj")=""
gbls("^zll")=""
gbls("^zltmp")=""
gbls("^zmc")=""
gbls("^znum")=""
gbls("^zpeterc")=""
gbls("^zsb")=""
gbls("^zseq")=""
gbls("^zstock")=""
gbls("^ztTmp")=""
gbls("^ztrap1")=""
gbls("^zwb1")=""
gbls("^zwhtmp")=""
gbls("^zx")=""
gbls("^zx1")=""
gbls("^zx2")=""
gbls("^zxdd")=""
gbls("^zyb")=""
gbls("^zyb1")=""
gbls("^zyb2")=""
gbls("^zyl")=""
gbls("^zzTT")=""
gbls("^zzdt")=""
gbls("^zzp")=""
gbls("^zzy")=""
gbls("^zzz")=""
```
文章
Michael Lei · 四月 24, 2022
# 基于Docker的Apache Web Gateway
Hi 社区
在本文中,我们将基于Docker程序化地配置一个Apache Web Gateway,使用。:
* HTTPS protocol.
* TLS\SSL to secure the communication between the Web Gateway and the IRIS instance.
![image](/sites/default/files/inline/images/net-schema-01.png)
我们将使用两个镜像:一个用于Web网关,第二个用于IRIS实例。
所有必需的文件都在这 [GitHub repository](https://github.com/lscalese/docker-webgateway-sample).
我们从git clone开始:
```bash
git clone https://github.com/lscalese/docker-webgateway-sample.git
cd docker-webgateway-sample
```
## 准备系统
为了避免权限方面的问题,你的系统需要一个用户和一个组:
* www-data
* irisowner
需要与容器共享证书文件。 如果你的系统中不存在这些文件,只需执行:
```bash
sudo useradd --uid 51773 --user-group irisowner
sudo groupmod --gid 51773 irisowner
sudo useradd –user-group www-data
```
## 生成证书
在这个示例中,我们使用以下三个证书:
1. HTTPS web server usage.
2. TLS\SSL encryption on Web Gateway client.
3. TLS\SSL encryption on IRIS Instance.
有一个随时可用的脚本来生成它们。.
然而,你应该自定义证书的主题;只需编辑这个文件 [gen-certificates.sh](https://github.com/lscalese/docker-webgateway-sample/blob/master/gen-certificates.sh) .
这是 OpenSSL `subj` argument的结构:
1. **C**: Country code
2. **ST**: State
3. **L**: Location
4. **O**: Organization
5. **OU**: Organization Unit
6. **CN**: Common name (basically the domain name or the hostname)
可以随意改动这些值.
```bash
# sudo is needed due chown, chgrp, chmod ...
sudo ./gen-certificates.sh
```
如果一切都OK,应该能看到两个带证书的新目录 `./certificates/` and `~/webgateway-apache-certificates/` with certificates:
| File | Container | Description |
|--- |--- |--- |
| ./certificates/CA_Server.cer | webgateway,iris | Authority server certificate|
| ./certificates/iris_server.cer | iris | Certificate for IRIS instance (used for mirror and wegateway communication encryption) |
| ./certificates/iris_server.key | iris | Related private key |
| ~/webgateway-apache-certificates/apache_webgateway.cer | webgateway | Certificate for apache webserver |
| ~/webgateway-apache-certificates/apache_webgateway.key | webgateway | Related private key |
| ./certificates/webgateway_client.cer | webgateway | Certificate to encrypt communication between webgateway and IRIS |
| ./certificates/webgateway_client.key | webgateway | Related private key |
请记住,如果有自签名的证书,浏览器会显示安全警报。 显然,如果你有一个由认证机构交付的证书,你可以用它来代替自签的证书(尤其是Apache服务器证书)
## Web Gateway 配置文件
让我们来看看配置文件.
### CSP.INI
你能看到在 `webgateway-config-files` 目录下 CSP.INI 文件.
将被推到镜像里, 但内容可以在runtime被修改.
可以把这个文件作为模版.
在这个示例中,以下参数将在容器启动时被覆盖:
* Ip_Address
* TCP_Port
* System_Manager
更多细节请参考 [startUpScript.sh](https://github.com/lscalese/docker-webgateway-sample/blob/master/startUpScript.sh) . 大致上,替换是通过`sed`命令行进行的.
同时, 这个文件包含 SSL\TLS 配置来确保与 IRIS 实例的通信:
```
SSLCC_Certificate_File=/opt/webgateway/bin/webgateway_client.cer
SSLCC_Certificate_Key_File=/opt/webgateway/bin/webgateway_client.key
SSLCC_CA_Certificate_File=/opt/webgateway/bin/CA_Server.cer
```
这些语句都比较重要. 我们必需确保证书文件可用.
我们稍后将在`docker-compose`文件中用一个卷来做这件事.
### 000-default.conf
这是一个Apache 配置文件. 允许使用HTTPS协议并将HTTP请求重定向到HTTPS.
证书和私钥文件在这个文件里设置:
```
SSLCertificateFile /etc/apache2/certificate/apache_webgateway.cer
SSLCertificateKeyFile /etc/apache2/certificate/apache_webgateway.key
```
## IRIS 实例
对我们 IRIS实例, 我们仅仅配置最低要求来允许SSL\TLS 和Web Gateway 之间的通信; 这涉及到:
1. `%SuperServer` SSL Config.
2. Enable SSLSuperServer security setting.
3. Restrict the list of IPs that can use the Web Gateway service.
为简化配置, config-api 用一个简单的JSON 配置文件.
```json
{
"Security.SSLConfigs": {
"%SuperServer": {
"CAFile": "/usr/irissys/mgr/CA_Server.cer",
"CertificateFile": "/usr/irissys/mgr/iris_server.cer",
"Name": "%SuperServer",
"PrivateKeyFile": "/usr/irissys/mgr/iris_server.key",
"Type": "1",
"VerifyPeer": 3
}
},
"Security.System": {
"SSLSuperServer":1
},
"Security.Services": {
"%Service_WebGateway": {
"ClientSystems": "172.16.238.50;127.0.0.1;172.16.238.20"
}
}
}
```
不需要做任何动作. 在容器启动时这个配置会自动加载.
## tls-ssl-webgateway 镜像
### dockerfile
```
ARG IMAGEWEBGTW=containers.intersystems.com/intersystems/webgateway:2021.1.0.215.0
FROM ${IMAGEWEBGTW}
ADD webgateway-config-files /webgateway-config-files
ADD buildWebGateway.sh /
ADD startUpScript.sh /
RUN chmod +x buildWebGateway.sh startUpScript.sh && /buildWebGateway.sh
ENTRYPOINT ["/startUpScript.sh"]
```
默认的 entry point是 `/startWebGateway`, 但是在启动webserver前需要执行一些操作. 记住我们的 CSP.ini 文件只是个 `模版`, 并且我们需要在启动时改变一些参数 (IP, port, system manager) . `startUpScript.sh` 将执行这些变化并启动初始 entry point 脚本 `/startWebGateway`.
## 启动容器
### docker-compose 文件
启动容器之前, 必须修改好`docker-compose.yml` 文件:
* `**SYSTEM_MANAGER**` 必须配好授权的IP来访问 **Web Gateway Management** https://localhost/csp/bin/Systems/Module.cxw
基本就是你自己的IP地址 (可以是一个用逗号分开的列表).
* `**IRIS_WEBAPPS**` 必须配好 CSP 应用列表. 这个表用空格隔开, 例如: `IRIS_WEBAPPS=/csp/sys /swagger-ui`. 默认, 只有 `/csp/sys` 被暴露.
* 80和 443 端口映射好. 如果你的系统中已经使用了这些端口,请将调整为其他端口.
```
version: '3.6'
services:
webgateway:
image: tls-ssl-webgateway
container_name: tls-ssl-webgateway
networks:
app_net:
ipv4_address: 172.16.238.50
ports:
# change the local port already used on your system.
- "80:80"
- "443:443"
environment:
- IRIS_HOST=172.16.238.20
- IRIS_PORT=1972
# Replace by the list of ip address allowed to open the CSP system manager
# https://localhost/csp/bin/Systems/Module.cxw
# see .env file to set environement variable.
- "SYSTEM_MANAGER=${LOCAL_IP}"
# the list of web apps
# /csp allow to the webgateway to redirect all request starting by /csp to the iris instance
# You can specify a list separate by a space : "IRIS_WEBAPPS=/csp /api /isc /swagger-ui"
- "IRIS_WEBAPPS=/csp/sys"
volumes:
# Mount certificates files.
- ./volume-apache/webgateway_client.cer:/opt/webgateway/bin/webgateway_client.cer
- ./volume-apache/webgateway_client.key:/opt/webgateway/bin/webgateway_client.key
- ./volume-apache/CA_Server.cer:/opt/webgateway/bin/CA_Server.cer
- ./volume-apache/apache_webgateway.cer:/etc/apache2/certificate/apache_webgateway.cer
- ./volume-apache/apache_webgateway.key:/etc/apache2/certificate/apache_webgateway.key
hostname: webgateway
command: ["--ssl"]
iris:
image: intersystemsdc/iris-community:latest
container_name: tls-ssl-iris
networks:
app_net:
ipv4_address: 172.16.238.20
volumes:
- ./iris-config-files:/opt/config-files
# Mount certificates files.
- ./volume-iris/CA_Server.cer:/usr/irissys/mgr/CA_Server.cer
- ./volume-iris/iris_server.cer:/usr/irissys/mgr/iris_server.cer
- ./volume-iris/iris_server.key:/usr/irissys/mgr/iris_server.key
hostname: iris
# Load the IRIS configuration file ./iris-config-files/iris-config.json
command: ["-a","sh /opt/config-files/configureIris.sh"]
networks:
app_net:
ipam:
driver: default
config:
- subnet: "172.16.238.0/24"
```
Build and start:
```bash
docker-compose up -d --build
```
`tls-ssl-iris 和 tls-ssl-webgateway 容器应该启动好了.`
## 测试 Web Access
### Apache 默认页
打开网页 [http://localhost](http://localhost).
你将自动被重定向到[https://localhost](https://localhost).
浏览器显示安全警告. 如果是自签署的证书,这是正常的,接受并继续.
![image](/sites/default/files/inline/images/apache-web-gateway-with-docker-02.png)
### Web Gateway 管理页面
打开 [https://localhost/csp/bin/Systems/Module.cxw](https://localhost/csp/bin/Systems/Module.cxw) 并测试服务器连接.
![image](/sites/default/files/inline/images/apache-web-gateway-with-docker-03.png)
### 管理门户
打开 [https://localhost/csp/sys/utilhome.csp](https://localhost/csp/sys/utilhome.csp)
![image](/sites/default/files/inline/images/apache-web-gateway-with-docker-04.png)
赞! Web Gateway 例子跑起来了!
## IRIS Mirror 与vWeb Gateway
在上一篇文章中,我们建立了一个镜像环境,但网络网关是一个缺失的部分。 现在,我们可以改进这一点。 一个包括Web Gateway和一些更多改进的资源库就可以用了 [iris-miroring-with-webgateway](https://github.com/lscalese/iris-mirroring-with-webgateway) :
1. 证书不再是即时生成的,而是在一个单独的过程中生成的.
2. IP地址被docker-compose和JSON配置文件中的环境变量所取代, 变量被定义在'.env'文件中.
3. 这个repository 可以作为一个模板来使用.
查看 repository文件 [README.md](https://github.com/lscalese/iris-mirroring-with-webgateway) 来运行以下环境:
![image](https://github.com/lscalese/iris-mirroring-with-webgateway/blob/master/img/network-schema-01.png?raw=true)
文章
Jingwei Wang · 七月 21, 2022
一个主题区是一个子立方体,可以选择覆盖项目的名称。你定义一个主题区是为了使用户能够关注较小的数据集,出于安全原因或其他原因。本章讨论了以下主题。
简介
在本教程中,我们创建了两个主题区域,按邮政编码划分患者:
Patient Set A: 居住在邮政编码为32006, 32007, or 36711区域的患者
Patient Set B: 居住在邮政编码为34577 or 38928区域的患者
创建主题领域
要创建主题区域,请做以下工作。
在模型中,点击 "新建"。
选中 "主题区域"。
对于主题区名称,键入Patient Set A
对于主题区的类名,输入 Tutorial.SubjectA
对于基础立方体,点击浏览并选择 Tutorial。
单击 OK。
在一个单独的浏览器标签或窗口中,访问分析器,然后做以下工作。
展开HomeD。
把ZIP Code放到过滤器框中。这就在数据透视表的正上方增加了一个过滤框。
在该过滤框中,点击搜索按钮,然后选择 32006, 32007, 和 36711。
然后点击'为透视表显示当前查询'按钮(笔记本带一个笔的图标)
系统会显示一个对话框,显示分析器所使用的MDX查询。
SELECT FROM [PPatients]
%FILTER %OR({[HOMED].[H1].[ZIP CODE].&[32006],[HOMED].[H1].[ZIP CODE].&[32007],[HOMED].[H1].[ZIP CODE].&[36711]})
将%FILTER后面的文本复制到系统剪贴板上。
点击确定。
在模型中,点击标有Patient Set A的一行。
在详细信息栏中,将复制的文本粘贴到 过滤器 中。
%OR({[HOMED].[H1].[ZIP Code].&[32006],[HOMED].[H1].[ZIP Code].&[32007],[HOMED].[H1].[ZIP Code].&[36711]})
点击保存,然后点击确定。
编译该主题区。
对于第二个主题区,重复前面的步骤,并作如下改动。
对于课题区名称,键入Patient Set B
对于主题区的类名,键入Tutorial.SubjectB
对另外两个邮政编码重复前面的步骤。因此,对于Filter,使用以下内容。
%OR({[HOMED].[H1].[ZIP Code].&[34577],[HOMED].[H1].[ZIP Code].&[38928]})
检查主题领域
现在我们检查一下我们所创建的主题领域。
在分析器中,点击左上角立方体按钮,选择Patient Set A。
单击 "确定"。然后分析器显示所选主题区的内容。 注意,总的记录数没有你的Tutorial基本立方体那么高。
在模型内容区,展开HomeD维度,ZIP Code级别,以及City级别。都没有之前的Tutorial基本立方体数据那么多。
对患者组B重复前面的步骤。
当您展开HomeD维度、ZIP Code级别,以及City级别。也没之前的Tutorial基本立方体数据那么多。
常见的过滤器表达式
在这一节中,我们在分析器中试验常见的过滤器,看看它们对生成的查询的影响。
在分析器中,打开Tutorial立方体。
分析器把立方体和主题区都称为主题区。它们之间的正式区别只有在你创建它们时才有意义。
点击新建。
分析器显示计数(记录的计数)。
在添加过滤器之前,让我们看看当前的查询是如何定义的,以便我们有一个比较的基础。
展开ColorD和Favorite Color。
把Orange拖到过滤器。
分析器现在只使用最喜欢的颜色是Orange的患者。
点击‘为透视表显示当前查询’按钮(笔记本加一个笔图标)。然后系统显示以下查询。
SELECT FROM [TUTORIAL] %FILTER [ColorD].[H1].[Favourite Color].&[Orange]
%FILTER关键字限制了查询。%FILTER后面的片段是一个过滤表达式。
点击确定。
给过滤器添加另一种颜色。点击过滤器中橙色旁边的X。这样就可以删除该过滤器。
把 "Favourite Color "拖到过滤器中。这就在数据透视表的正上方增加了一个过滤器框。
在该过滤框中,点击搜索按钮(放大镜图标),然后选择橙色和紫色。
系统现在只使用最喜欢的颜色是橙色或最喜欢的颜色是紫色的患者(注意,计数比单独的橙色要高)。
再次显示查询文本。现在你应该看到以下内容。
SELECT FROM [TUTORIAL] %FILTER %OR({[COLORD].[H1].[FAVOURITE COLOR].&[Orange],[COLORD].[H1].[FAVOURITE COLOR].&[Purple]})
在这种情况下,过滤器的表达式如下。
%FILTER %OR({[COLORD].[H1].[FAVOURITE COLOR].&[Orange],[COLORD].[H1].[FAVOURITE COLOR].&[Purple]})
%OR函数是InterSystems公司的一项优化;该函数的参数是一个集合。 这个集合被大括号{}所包围,由一个逗号分隔的元素列表组成。在这种情况下,该集合包含两个成员表达式。一个集合表达式指的是由该集合的元素所表示的所有记录。在本例中,该集合指的是所有最喜欢的颜色是橙色的患者和所有最喜欢的颜色是紫色的患者。
点击确定。
使用过滤器下拉列表,清除紫色旁边的复选框。现在分析器只使用最喜欢的颜色是橙色的患者。
展开AllerD和Allergies。将模具拖到过滤器,在最喜欢的颜色的下面。这个透视表只显示最喜欢的颜色是橙色和对霉菌过敏的患者。
再次显示查询文本。现在你应该看到以下内容。
SELECT FROM [TUTORIAL] %FILTER NONEMPTYCROSSJOIN([AllerD].[H1].[Allergies].&[mold],[COLORD].[H1].[FAVOURITE COLOR].&[Orange])
MDX函数NONEMPTYCROSSJOIN结合了两个成员,并返回结果元组。该元组只访问属于两个给定成员的记录。
现在你已经看到了三种最常见的过滤表达式。 当你使用一个成员表达式作为过滤器时,系统只访问属于这个成员的记录。你可以写一个成员表达式,如下所示。
[dimension name].[hierarchy name].[level name].&[member key]
或者。
[dimension name].[hierarchy name].[level name].[member name]
dimension name是一个维度的名称。 hierarchy name是一个层次结构的名称。您可以省略层次结构的名称。如果你这样做,查询会使用在这个维度中定义的具有给定名称的第一层。 level name是该层次结构中的一个层次的名称。你可以省略层次名称。如果你这样做,查询会使用在这个维度中定义的具有给定名称的第一个成员。 member key是给定层次中成员的键。这通常与成员名称相同。 member name是给定级别中成员的名称。
关于更多过滤规则,请看用DeepSee使用MDX和DeepSee MDX参考。