清除过滤器
文章
Jingwei Wang · 九月 20, 2022
在虚拟化环境中使用镜像,构成镜像的InterSystems IRIS实例被安装在虚拟主机上,创造了一个混合的高可用性解决方案,将镜像的优点与虚拟化的优点结合起来。镜像通过自动故障切换对计划内或计划外的故障提供即时响应,而虚拟化HA软件在计划外的机器或操作系统故障后自动重新启动承载镜像成员的虚拟机。这允许失败的成员迅速重新加入镜像,充当备份(或在必要时作为主机)。
当镜像被配置在虚拟化环境中时,请参考以下建议:
故障转移成员的虚拟主机和备机不可以配置在同一台物理机上。
为了避免单点存储故障,故障转移成员上的InterSystems IRIS实例所使用的存储应永久隔离在不同磁盘组或存储阵列的独立数据存储中。
在虚拟化平台层面上进行的一些操作,如备份或迁移,可能会导致故障转移成员长时间没有反应,从而导致不需要的故障转移或不理想的警报频率。为了解决这个问题,你可以增加QoS超时设置。
在进行导致故障转移成员连接中断的计划性维护操作时,你可以暂时停止备份上的镜像,以避免不必要的故障转移和警报。
在镜像成员上必须非常谨慎地使用快照管理,因为将一个成员恢复到早期的快照,既会删除该成员的最新状态(例如,自拍摄快照以来,该成员可能已经从主机变为备机),也会删除其他成员仍然拥有的日志数据。 使用虚拟机备份恢复镜像时请特别注意:
被恢复到早期快照的故障转移成员只能从power-off 状态下恢复;从power-on 状态下恢复会造成两个故障转移成员同时作为主机的可能性。
如果被恢复到早期快照的故障转移成员在没有获得自快照以来创建的所有日志数据的情况下成为主要成员--例如,因为它被迫成为主要成员--所有其他镜像成员必须被重建(如需重建镜像,请联系WRC)。
文章
Louis Lu · 六月 10, 2024
列式存储是 InterSystems IRIS 提供的一项较新的技术。与传统的基于行的存储不同,它通过将数据存储在列而不是行中来优化查询处理,从而实现更快的访问和检索相关信息。
下面是使用SQL创建此类表的例子
CREATE TABLE table (column1 type1, column2 type2, column3 type3) WITH STORAGETYPE = COLUMNAR -- ex 1
CREATE TABLE table (column1 type1, column2 type2, column3 type3 WITH STORAGETYPE = COLUMNAR) -- ex 2
我们知道InterSystems IRIS 是支持多模型的DMBS, 它可以无缝的通过关系型或对象的方式访问通一数据,
我们使用下面的方式在使用Object Script类定义的时候定义列存储:
1. 如果你想对类中的所有属性都定义为列存储,则直接通过在类中添加parameter 的方式实现:
Parameter STORAGEDEFAULT = "columnar"
如前面的例子,我们使用object script定义就会是这样:
Class Post.Address Extends %Persistent [Final]
{
Parameter STORAGEDEFAULT = "columnar";
Parameter USEEXTENTSET=1;
Property City As %String(MAXLEN = 12);
Property ZIP As %String(MAXLEN = 9);
Property Country As %String(MAXLEN = 12);
}
其中 Parameter STORAGEEDFAULT = "columnar" 告诉系统,所有数据都以列的方式保存(意味着每一列都有其自己的global节点)
Parameter USEEXTENTSET = 1 告诉系统,为了生成的效率,hashed 的global 使用短名称命名。
你可以将任意表定义为使用列存储。但是将列存作为默认存储模式的表,必须添加Final 或 NoExtent 的类关键字,也就是不能再被继承。
2. 也可以仅对表中的某些字段定义为使用列式存储,要实现此种设计进需要在属性上添加 STORAGEDEFAULT = "columnar"
Class Post.Address Extends %Persistent
{
Parameter STORAGEDEFAULT = "row";
Parameter USEEXTENTSET=1;
Property City As %String(MAXLEN = 12);
Property ZIP As %String(MAXLEN = 9);
Property Country As %String(MAXLEN = 12, STORAGEDEFAULT = "columnar");
}
这个例子中的City 和 ZIP 仍使用行式存储,也就是数据会存储在global ^Post.AddressD 中,而Country会以列式的方式存储在不同的global节点中。
在这个例子中,使用列式存储会是一个更好的方法,因为如果我们有一个包含不同国家不同城市的数据库,国家的数目是有限的,而城市的数目则不是。
3. 还可以对现有行存储的类,针对某些字段添加列式索引
Class Sample.BankTransaction Extends %Persistent [ DdlAllowed ]
{
// Line below is optional
Parameter STORAGEDEFAULT = "row";
Property Amount As %Numeric(SCALE = 2);
Index AmountIndex On Amount [ type = columnar ];
.
.
.
}
在这个例子中,金额这样的数字类型字段,使用行式存储,但对其建立列式索引,会极大提高针对该字段的统计类sql查询效率,比如SUM、Avg等。
文章
姚 鑫 · 二月 18
# 第二十八章 T 开头的术语
# 以 T 开头的术语
### 表 (table)
**InterSystems SQL**
表是一种由表示特定实体的行和表示每个实体特定数据点的列组成的数据结构。
### 目标角色 (target role)
**系统**
在受保护的 `IRIS` 应用中,由应用程序授予给已经是其他角色(称为匹配角色)成员的用户的角色。如果用户拥有匹配角色,则在使用应用程序时,用户还可以被授予一个或多个额外的目标角色。
### 目标用户 (target user)
**系统**
试图认证到 `LDAP` 服务器的用户。`IRIS` 通过在特定 `LDAP` 配置的“编辑 `LDAP` 配置”页面上的 `LDAP` 唯一搜索属性字段中使用提供的值,尝试在 `LDAP` 数据库中查找该用户。可以从 `LDAP` 配置页面(系统管理 > 安全 > 系统安全 > `LDAP` 配置)访问“编辑 `LDAP` 配置”页面。(请注意,如果启用了 `Kerberos`,页面名称和菜单选项中会包含 `Kerberos`。)
### TCP/IP
**通用**
传输控制协议/互联网协议(`Transmission Control Protocol/Internet Protocol`),是可以管理关系客户端和关系服务器之间连接的通信协议之一。也称为 `TCP`。
### 临时全局 (temporary global)
**系统**
存储在临时数据库 `IRISTEMP` 中的全局。请参见临时全局和 `IRISTEMP` 数据库。
### 终端 (Terminal)
**系统**
正式而言,该术语指的是 `Windows` 终端应用程序。非正式而言,该术语也可以指 `ObjectScript shell`,即 `IRIS` 的交互式命令行接口。终端应用程序包括 `ObjectScript shell`,但也提供菜单和其他选项。请参见“使用终端”和“使用 `ObjectScript Shell`”。
### 事务 (transaction)
**通用**
一个逻辑工作单元。应用程序开发人员可以使用 `SQL` 或 `ObjectScript` 命令定义事务。`IRIS` 将事务中对全局的更新记录在日志文件中。如果事务不完整,可以回滚。
### 瞬态属性 (transient property)
**对象(Objects)**
瞬态属性存储在内存中,但不存储在磁盘上。
### 翻译方法 (translation methods)
**对象(Objects)**
翻译方法用于在 `ODBC`、显示、逻辑和存储格式之间转换值。
### 触发器 (trigger)
**InterSystems SQL**
由开发人员定义的一系列操作,在 `SQL` 应用程序或使用 `SQL` 存储的对象应用程序的各个点执行。触发器是由对表执行的 `INSERT`、`UPDATE` 或 `DELETE` 操作启动的数据库操作。触发器有助于维护完整性约束和其他数据依赖关系。
文章
Louis Lu · 六月 12, 2023
文章相关视频参见Synthea生成FHIR测试数据,以及FHIR服务器加载FHIR资源文件
1. 什么是Synthea
Synthea是一个开源软件包,可以模拟生成患者就诊数据。他的github地址在这里。
生成的就诊模版从最初的模拟美国前十种常见病、前十种慢性病到现今超过90种不同的模型。详细模型参见这里。
基于当前版本,Synthea的特性包括:
从出生到死亡的全生命周期
可配置的人口统计学信息(默认为美国马萨诸塞州人口普查数据)
模块化规则系统
插入通用模块
用于附加功能的自定义 Java 规则模块
主要医疗事件就诊、急诊室就诊和症状驱动的就诊
症状、 过敏、药品、 疫苗接种、观察/生命体征、实验室、处置、 护理计划
支持格式
HL7 FHIR(R4、STU3 v3.0.1 和 DSTU2 v1.0.2)
ndjson 格式的批量 FHIR(设置 exporter.fhir.bulk_data = true 以激活)
C-CDA (设置 exporter.ccda.export = true 以激活)
CSV (设置 exporter.csv.export = true 以激活)
CPCDS (设置 exporter.cpcds.export = true 以激活)
使用Graphviz可视化呈现规则和疾病模块
支持的参数可见下图
比如 -p 5 生成5条测试数据
-g M 生成男性测试数据
-a 60-65 生成年龄在60-65周岁患者测试数据
2. 使用Synthea 生成测试数据
为了方便使用,也将该软件做成了docker,所以你可以简单的执行下面命令行
docker run --rm -v $PWD/output:/output --name synthea-docker intersystemsdc/irisdemo-base-synthea:version-1.3.4 -p 5
该命令会在当前路径的output文件夹下生成5条患者符合FHIR标准的就诊数据,数据相关摘要信息如下面终端输出:
3. 加载生成的 FHIR 数据至 InterSystems IRIS for Health
生成完FHIR数据后,需要加载到FHIR服务器(FHIR资源仓库)中。
我们在输出目录下可以看到生成7条json数据,其中5条患者就诊相关,1条就诊医院信息,一条参与者(就诊医生)信息。
在InterSystems IRIS for health中可以方便的使用DataLoader类中的方法,批量加载FHIR资源数据,进入FHIR 资源仓库命名空间后执行:
zw ##class(HS.FHIRServer.Tools.DataLoader).SubmitResourceFiles("/external/fhir/","FHIRServer","/fhir/r4")
该方法中的第一个参数是fhir资源文件路径; 第二个参数服务类型,这里一般是FHIRServer; 第三个参数FHIRServer的service名称。
执行后显示如下:
之后我们可以进入管理门户,或者使用SQL客户端查询相关存储表,表明数据被正确导入
文章
姚 鑫 · 十二月 23, 2021
# 第三章 SQL聚合函数 COUNT(一)
返回表或指定列中的行数的聚合函数。
# 大纲
```sql
COUNT(*)
COUNT([ALL | DISTINCT [BY(col-list)]] expression [%FOREACH(col-list)] [%AFTERHAVING])
```
# 参数
- `*` - 指定应计算所有行以返回指定表中的总行数。
`COUNT(*)`不接受其他参数,不能与`ALL`或`DISTINCT`关键字一起使用。
`COUNT(*)`不接受表达式参数,也不使用任何特定列的信息。
`COUNT(*)`返回指定表或视图中的行数,但不消除重复项。
它分别计数每一行,包括包含`NULL`值的行。
- `ALL` - 可选-指定`COUNT`返回表达式中所有值的计数。
如果没有指定关键字,这是默认值。
- `DISTINCT` - 可选-一个`DISTINCT`子句,指定`COUNT`返回表达式的不同(唯一)值的计数。
不能与流字段一起使用。
`DISTINCT`可以指定`BY(colo -list)`子句,其中`colo -list`可以是单个列名,也可以是用逗号分隔的列名列表。
- `expression` - 任何有效的表达式。
通常是包含要计算的数据值的列的名称。
- `%FOREACH(col-list)` - 可选-列名或以逗号分隔的列名列表。
- `%AFTERHAVING` - 可选-应用在`HAVING`子句中的条件。
`COUNT`返回`BIGINT`数据类型。
# 描述
`COUNT`聚合函数有两种形式:
- `COUNT(expression)`以整数形式返回表达式中值的数目的计数。
通常,表达式是查询返回的多行中字段的名称(或包含一个或多个字段名称的表达式)。
`COUNT(表达式)`不计算`NULL`值。
它可以选择计数或不计数重复的字段值。
`COUNT`总是返回数据类型`BIGINT,` `xDBC`长度为`8`,精度为`19`,刻度为`0`。
- `COUNT(*)`以整数形式返回表中行数的计数。
`COUNT(*)`计数所有行,无论是否存在重复的字段值或`NULL`值。
`COUNT`可以在引用表或视图的`SELECT`查询或子查询中使用。
`COUNT`可以在`SELECT`列表或`HAVING`子句中与普通字段值一起出现。
`COUNT`不能用于`WHERE`子句。
`COUNT`不能在`JOIN`的`ON`子句中使用,除非`SELECT`是子查询。
与所有聚合函数一样,`COUNT(expression)`可以接受一个可选的`DISTINCT`子句。
`DISTINCT`子句只计算那些具有不同(唯一)值的列。
什么是一个不同的值取决于字段的排序;
当字段具有默认的排序规则`%SQLUPPER`时,字母大小写不同的值将不作为不同的值计算。
要将每个字母大小写变量作为一个不同的值进行计数,请使用`count (distinct (%EXACT(field)))`。
`COUNT DISTINCT`不将`NULL`视为一个不同的值。
`COUNT(DISTINCT BY(col2) col1)`计数不同的`col2`值的`col1`值;
但是,不同的`col2`值可以包含一个`NULL`作为不同的值。
`ALL`关键字统计所有非`null`值,包括所有重复值。
如果没有指定关键字,`ALL`是默认行为。
# 没有行返回
如果没有选择行,`COUNT`返回`0`或`NULL`,这取决于查询:
- 如果除了提供给聚合函数的字段之外,选择列表不包含对`FROM`子句表中的字段的任何引用,那么`COUNT`返回`0`。
只有`COUNT`聚合函数返回`0`;
其他聚合函数返回`NULL`。
该查询返回`%ROWCOUNT`为`1`。
如下示例所示:
```java
ClassMethod Count()
{
s myquery = 3
s myquery(1) = "SELECT COUNT(*) AS Recs,COUNT(Name) AS People,"
s myquery(2) = "AVG(Age) AS AvgAge,MAX(Age) AS MaxAge,CURRENT_TIMESTAMP AS Now"
s myquery(3) = " FROM Sample.Employee WHERE Name %STARTSWITH 'ZZZ'"
s tStatement = ##class(%SQL.Statement).%New()
s qStatus = tStatement.%Prepare(.myquery)
if qStatus'=1 {
w "%Prepare failed:"
d $System.Status.DisplayError(qStatus)
q
}
s rset = tStatement.%Execute()
d rset.%Display()
w !,"Rowcount:",rset.%ROWCOUNT
}
```
```java
DHC-APP> d ##class(PHA.TEST.SQLCommand).Count()
Recs People AvgAge MaxAge Now
0 0 2021-12-20 20:58:17
1 Rows(s) Affected
Rowcount:1
```
- 如果`select-list`包含对`FROM`子句表中某个字段的任何直接引用,或者如果指定了`TOP 0`,那么`COUNT`返回`NULL`。
该查询返回`%ROWCOUNT`为`0`。
以下示例不返回`COUNT`值,因为`%ROWCOUNT`值为`0`:
```java
ClassMethod Count1()
{
s myquery = 2
s myquery(1) = "SELECT COUNT(*) AS Recs,COUNT(Name) AS People,$LENGTH(Name) AS NameLen"
s myquery(2) = " FROM Sample.Employee WHERE Name %STARTSWITH 'ZZZ'"
s tStatement = ##class(%SQL.Statement).%New()
s qStatus = tStatement.%Prepare(.myquery)
if qStatus'=1 {
w "%Prepare failed:"
d $System.Status.DisplayError(qStatus)
q
}
s rset = tStatement.%Execute()
d rset.%Display()
w !,"Rowcount:",rset.%ROWCOUNT
}
```
```java
DHC-APP> d ##class(PHA.TEST.SQLCommand).Count1()
Recs People NameLen
0 Rows(s) Affected
Rowcount:0
```
- 如果没有指定表,`COUNT(*)`返回1。
该查询返回`%ROWCOUNT`为1。
如下示例所示:
```java
ClassMethod Count2()
{
s myquery = "SELECT COUNT(*) AS Recs"
s tStatement = ##class(%SQL.Statement).%New()
s qStatus = tStatement.%Prepare(myquery)
if qStatus'=1 {
w "%Prepare failed:"
d $System.Status.DisplayError(qStatus)
q
}
s rset = tStatement.%Execute()
d rset.%Display()
w !,"Rowcount:",rset.%ROWCOUNT
}
```
```java
DHC-APP> d ##class(PHA.TEST.SQLCommand).Count2()
Recs
1
1 Rows(s) Affected
Rowcount:1
```
# 流字段
可以使用`COUNT`(表达式)来计数流字段值,但有一些限制。
`COUNT(streamfield)`计算所有非`null`值。
它不会检查重复的值。
当`expression`是一个流字段时,不能指定`COUNT`函数的`DISTINCT`关键字。
试图在流字段中使用`DISTINCT`关键字会导致`SQLCODE -37`错误。
不能在`%FOREACH`冒号列表中指定流字段。
尝试这样做会导致`SQLCODE -37`错误。
下面的例子显示了`COUNT`函数的有效使用,其中`Title`是字符串字段,`Notes`和`Picture`是流字段:
```sql
SELECT DISTINCT Title,COUNT(Notes),COUNT(Picture %FOREACH(Title))
FROM Sample.Employee
```
当`Title`为字符串字段,`Notes`和`Picture`为流字段时,以下示例无效:
```sql
-- Invalid: DISTINCT keyword with stream field
SELECT Title,COUNT(DISTINCT Notes) FROM Sample.Employee
```
```sql
-- Invalid: %FOREACH col-list contains stream field
SELECT Title,COUNT(Notes %FOREACH(Picture))
FROM Sample.Employee
```
文章
Michael Lei · 三月 12, 2022
在Linux上设置TZ环境变量
v2015.1的更新检查表建议在Linux平台上设置TZ环境变量,并指出了tzset的手册。建议这样做是为了提高Caché的时间相关功能的性能。你可以在下文找到更多关于这个的信息:
Linux TZ环境变量未被设置以及对Caché的影响
我的CentOS 7测试系统上的manpage(RHEL 6也是这样说的)有这样的说法。
"tzset()函数从TZ环境变量初始化tzname变量。 这个函数会被其他依赖时区的时间转换函数自动调用"。
那么,你如何设置TZ?它是如何影响Linux服务器上的时间的?让我们来看看我们能学到什么:
系统时区 --
对于我的测试,我在一个虚拟的CentOS系统上使用Ensemble 2016.1。首先让我们检查一下系统的时区。system-config-date工具可以告诉你。
那么"系统时钟使用UTC "是怎么回事?这指的是服务器的硬件时钟。在专用服务器上,UTC是很常见的。当Linux与Windows进行双启动配置时,情况并非如此(Windows的系统时钟使用本地时间)。
由于双启动配置在Cache'和Ensemble安装中并不常见,我就不多说了。这里的关键思想是,系统时区要设置为服务器的时区,并且硬件时钟时间要设置正确。
用户看见的时间和日期–
让我们看一看。这里是我的终端会话的一个截图:
看起来不错。我的shell进程(运行date命令)和我的Caché进程显示相同的时间,除了我需要输入WRITE命令的几秒钟。
设置 TZ 变量 --
现在让我们在环境中设置TZ。做到这一点的命令是tzselect。下面是一个设置TZ的命令输出的脚本:
[ehemdal@localhost ~]$ tzselect
Please identify a location so that time zone rules can be set correctly.
Please select a continent or ocean.
1) Africa
2) Americas
3) Antarctica
4) Arctic Ocean
5) Asia
6) Atlantic Ocean
7) Australia
8) Europe
9) Indian Ocean
10) Pacific Ocean
11) none - I want to specify the time zone using the Posix TZ format.
#? 2
Please select a country.
1) Anguilla 19) Dominican Republic 37) Peru
2) Antigua & Barbuda 20) Ecuador 38) Puerto Rico
3) Argentina 21) El Salvador 39) St Barthelemy
4) Aruba 22) French Guiana 40) St Kitts & Nevis
5) Bahamas 23) Greenland 41) St Lucia
6) Barbados 24) Grenada 42) St Maarten (Dutch)
7) Belize 25) Guadeloupe 43) St Martin (French)
8) Bolivia 26) Guatemala 44) St Pierre & Miquelon
9) Brazil 27) Guyana 45) St Vincent
10) Canada 28) Haiti 46) Suriname
11) Caribbean NL 29) Honduras 47) Trinidad & Tobago
12) Cayman Islands 30) Jamaica 48) Turks & Caicos Is
13) Chile 31) Martinique 49) United States
14) Colombia 32) Mexico 50) Uruguay
15) Costa Rica 33) Montserrat 51) Venezuela
16) Cuba 34) Nicaragua 52) Virgin Islands (UK)
17) Curacao 35) Panama 53) Virgin Islands (US)
18) Dominica 36) Paraguay
#? 49
Please select one of the following time zone regions.
1) Eastern (most areas) 16) Central - ND (Morton rural)
2) Eastern - MI (most areas) 17) Central - ND (Mercer)
3) Eastern - KY (Louisville area) 18) Mountain (most areas)
4) Eastern - KY (Wayne) 19) Mountain - ID (south); OR (east)
5) Eastern - IN (most areas) 20) MST - Arizona (except Navajo)
6) Eastern - IN (Da, Du, K, Mn) 21) Pacific
7) Eastern - IN (Pulaski) 22) Alaska (most areas)
8) Eastern - IN (Crawford) 23) Alaska - Juneau area
9) Eastern - IN (Pike) 24) Alaska - Sitka area
10) Eastern - IN (Switzerland) 25) Alaska - Annette Island
11) Central (most areas) 26) Alaska - Yakutat
12) Central - IN (Perry) 27) Alaska (west)
13) Central - IN (Starke) 28) Aleutian Islands
14) Central - MI (Wisconsin border) 29) Hawaii
15) Central - ND (Oliver)
#? 1
The following information has been given:
United States
Eastern (most areas)
Therefore TZ='America/New_York' will be used.
Local time is now: Tue May 31 11:21:04 EDT 2016.
Universal Time is now: Tue May 31 15:21:04 UTC 2016.
Is the above information OK?
1) Yes
2) No
#? 1
You can make this change permanent for yourself by appending the line
TZ='America/New_York'; export TZ
to the file '.profile' in your home directory; then log out and log in again.
Here is that TZ value again, this time on standard output so that you
can use the /usr/bin/tzselect command in shell scripts:
America/New_York
[ehemdal@localhost ~]$
文件~/.profile(如果它存在的话)会在你登录时运行,并为你设置TZ变量。如果你使用的是/bin/sh或/bin/bash以外的shell,你可能会使用一个不同的初始化文件。我设置了这个并重新登录。如果你更新一个文件,如/etc/profile,你可以为所有用户应用这个。
这里你可以看到,TZ是为我的用户(ehemdal)设置的,但不是为root用户设置的。
用户看到的TZ,时间和日期–
如果一个用户从不同的时区连接到你的服务器,会发生什么?TZ变量有助于保持用户本地时间的正确性,同时将时区和夏令时的管理权掌握在操作系统手中。这也会影响到Cache'使用的时间。举个例子,我决定把我的时区改为檀香山的时区。
下面是两张截图,显示了结果。
我的用户进程已将时区设置为太平洋/檀香山。Root的进程没有设置TZ(所以使用的是系统时区America/New_York)。在操作系统层面(使用日期命令),显示的是两个用户的当地时间。日期命令反映了用户的本地时间(用户ehemdal为HST,root为EDT)。由于$HOROLOG的值来自于用户进程可用的操作系统时间,所以$H值对两个用户来说是不同的。
我选择檀香山时间作为一个有趣的例子,因为夏威夷不遵守夏令时。 通过为所有用户正确设置TZ,本地时间可以为遵守夏令时的用户 "提前和推迟",而对于不遵守夏令时的用户则保持稳定。
文章
Hao Ma · 十月 28, 2024
上一篇文章使用人工配置的方法简单的配置了webgateway container. 接下来来介绍如何在docker-compose里做自动化部署。
先总结我们要做的事情:
1. 配置到IRIS的连接。定义连接的iris的IP地址或者DNS, 以及连接的用户名密码 以及其他的对默认值的修改。
2. 配置apache2的配置文件,保证到IRIS的HTTP请求能发送给CSP Webgateway。
3. 很多时候,用户会希望使用HTTPS访问IRIS,因此需要在apache2上支持TLS。
这些是最基本的功能。除此之外, 用户还可能会要求建立WebGateway到IRIS的TLS连接,或者在Apache2部署自己的网页等等。后面的文章会一一介绍。
### 配置CSP.ini
上一篇文章中,我通过Webgateway管理页面定义了Webgateway到IRIS的连接,其实是定义了webgateway的配置文件`CSP.ini`。 无论WebServer是什么类型,IIS,Apache, Nginx, CSP.ini的都是一样的。在Linux中, CSP.ini位于`/opt/webgateway/bin`目录。
InterSystems提供了一个工具叫 CSP merge。 简单的说,就是可以定义一个`被合并的文件`, webgateway运行时会不停的扫描这个文件,发现有内容的修改,就把修改后的配置项合并到工作中的CSP.ini中去。 具体的内容您可以参见[在线文档-CSP.ini merge feature](https://docs.intersystems.com/iris20241/csp/docbook/DocBook.UI.Page.cls?KEY=GCGI_config_overviews#GCGI_config_routing_app)。
CSP merge的merge文件不需要定义所有的配置项,理论上,只定义需要修改的部分就可以。但注意在一个配置部分(section)内部, 配置项之间是有关系的,单独定义其中的一项配置是不会生效的。
比如, 这是原始的CSP.ini中的LOCAL服务器的配置部分:
因此,merge文件里正确的LOCAL部分是:
```zsh
[LOCAL]
Ip_Address=127.0.0.1
TCP_Port=1972
Minimum_Server_Connections=3
Maximum_Session_Connections=6
Connection_Security_Level=0 ; 0:password, 1:kerberos...
Username=CSPSystem
Password=]]]U1lT
SSLCC_Protocol_Min=16
SSLCC_Protocol_Max=32
SSLCC_Key_Type=2
SSLCC_Cipher_Suites=ALL:!aNULL:!eNULL:!EXP:!SSLv2
SSLCC_Cipher_Suites_1_3=TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
```
如果您只希望修改Ip_Address, 因此在被合并的文件中定义
```ini
[LOCAL]
Ip_Address=192.168.1.2
TCP_Port=1972
```
而这样的定义会 造成加载失败。也就是说,CSP merge需要合并完整的Section。上面的例子中定义了这个服务器的地址,但没有定义连接它的账户和密码,这是不能接受的。
我习惯用比较完整的配置文件做合并,下面是一个例子。文件名可以任意,这里我命名为`CSP-merge.ini`:
```ini
[APP_PATH_INDEX]
/=Enabled
/csp=Enabled
[APP_PATH:/]
Default_Server=LOCAL
Alternative_Server_0=1~~~~~~LOCAL
[APP_PATH:/csp]
Default_Server=LOCAL
Alternative_Server_0=1~~~~~~LOCAL
[SYSTEM]
IRISCONNECT_LIBRARY_PATH=/opt/webgateway/bin
System_Manager=*.*.*.*
SM_Timeout=28800
Server_Response_Timeout=60
No_Activity_Timeout=86400
Queued_Request_Timeout=60
Default_Server=LOCAL
RELOAD=1 ; reload csp gateway setting in one minute
[SYSTEM_INDEX]
LOCAL=Enabled
[LOCAL]
Ip_Address=iris-a
TCP_Port=1972
Minimum_Server_Connections=3
Maximum_Session_Connections=6
Connection_Security_Level=0 ; 0:password, 1:kerberos...
Username=CSPSystem
Password=]]]U1lT
SSLCC_Protocol_Min=16
SSLCC_Protocol_Max=32
SSLCC_Key_Type=2
SSLCC_Cipher_Suites=ALL:!aNULL:!eNULL:!EXP:!SSLv2
SSLCC_Cipher_Suites_1_3=TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
```
> Tip1: [SYSTEM]中配置RELOAD=1, 可以让webgateway setting每分钟检查一次csp-merge.ini, 如果有修改,会实时的执行合并。
> Tip2: 想了解更多的CSP.ini的配置项的内容,请访问[在线文档- Define a Server Access Profile for Your InterSystems IRIS Instance](https://docs.intersystems.com/iris20241/csp/docbook/DocBook.UI.Page.cls?KEY=GCGI_config_serv)。
> Tip3: 在生产系统中,定义SYSTEM部分的: `System_Manager=*.*.*.*`是不安全的。
### 自动部署CSP合并文件
简单的说明: CSP-merge.ini文件位于host中的`./webgateway`文件夹,通过`volumes`映射到container的`/external`文件夹。 通过定义环境变量`ISC_CSP_INI_FILE`,docker启动时会执行CSP文件的合并。
```yaml
webgateway-apache:
image: containers.intersystems.com/intersystems/webgateway-arm64:2024.1
container_name: wg-apache
hostname: wg-apache
ports:
- "8080:80"
volumes:
- ./webgateway:/external
environment:
- TZ=CST-8
- ISC_CSP_INI_FILE=/external/CSP-merge.ini
```
后面介绍如何配置apache2访问IRIS服务地址。
### 配置Apache2
配置apache2可以参考apache2的主页面,配置文件的结构如下:
apache2的主配置文件`/etc/apache2/apache2.conf`包含(includes)了其他自子目录下的被enabled的配置文件,结构如下图:
```zsh
root@wg-a:/etc/apache2# cat apache2.conf
...
# It is split into several files forming the configuration hierarchy outlined
# below, all located in the /etc/apache2/ directory:
#
# /etc/apache2/
# |-- apache2.conf
# | `-- ports.conf
# |-- mods-enabled
# | |-- *.load
# | `-- *.conf
# |-- conf-enabled
# | `-- *.conf
# `-- sites-enabled
# `-- *.conf
```
#### CSP.conf是怎么工作的
InterSystems Webgateway是apache2的一个工作的module. 它的配置文件是`/etc/apache2/mods-available/CSP.conf`。在webgateway container启动时,apache2会执行这个module的生效(enable), 这时会在`/etc/apache2/mods-enabled`目录中创建一个链接到原始文件。
默认的`CSP.conf`的内容如下:
```zsh
root@wg-a:/etc/apache2# cat /etc/apache2/mods-available/CSP.conf
CSPModulePath "${ISC_PACKAGE_INSTALLDIR}/bin/"
CSPConfigPath "${ISC_PACKAGE_INSTALLDIR}/bin/"
SetHandler csp-handler-sa
SetHandler csp-handler-sa
AllowOverride None
Options None
Require all granted
Require all denied
root@wg-a:/etc/apache2#
```
#### 修改CSP.conf
以上的CSP.conf中只有使用CSP module的内容,而并没有访问IRIS的URL的部分。 , 需要一个个的添加要发送到webgateway模块的URL。必须的有这几个:`/csp`, `/api`,`/isc`, 其他还包括您自己定义的URL, 也需要创建对应的``。
以下是要添加的内容:
```zsh
# 添加要发送给IRIS的URL
CSP On
CSP On
CSP On
CSP On
```
> Tip1: 如果您确认此container只用于连接IRIS, 您可以定义``将所有的请求发送给webgateway模块。但注意访问http://localhost:8080时会返回错误404,apache2原本会把请求发到`/var/www/html/index.html`,修改后会把这个请求发给IRIS。
#### 自动部署CSP.conf的修改
配置container是新的CSP.conf通过`SC_CSP_CONF_FILE`加入,这个不是合并, 是整个覆盖替换原始的配置文件。以下是compose文件的示意,同样,`CSP-apache.conf`文件位于host的`./webgateway`文件夹,通过`volumes`映射到container的`/external`文件夹。
```yaml
webgateway-apache:
image: containers.intersystems.com/intersystems/webgateway-arm64:2024.1
container_name: wg-apache
hostname: wg-apache
ports:
- "8080:80"
volumes:
- ./webgateway:/external
environment:
- TZ=CST-8
- ISC_CSP_INI_FILE=/external/CSP-merge.ini
- ISC_CSP_CONF_FILE=/external/CSP-apache.conf
```
> 要修改CSP.conf, 如果要在container外面修改,需要修改ISC_CONF-FILE的内容, 然后重启container. /dur里面的CSP.conf不能改, 改了保存后会自动改回来。
#### 配置Apache2的TLS
#### 方法1. 用命令开启
配置HTTPS访问apache2最简单的方式打开apache2内置的`ssl`模块,并使`default-ssl.conf`生效。如下面的脚本文件中的操作:
```zsh
root@wg-a:/etc/apache2# a2enmod ssl
root@wg-a:/etc/apache2# ls -l mods-enabled/ssl*
lrwxrwxrwx 1 root root 26 Oct 9 09:00 mods-enabled/ssl.conf -> ../mods-available/ssl.conf
lrwxrwxrwx 1 root root 26 Oct 9 09:00 mods-enabled/ssl.load -> ../mods-available/ssl.load
root@wg-a:/etc/apache2# a2ensite default-ssl
Enabling site default-ssl.
To activate the new configuration, you need to run:
service apache2 reload
root@wg-a:/etc/apache2# service apache2 reload
* Reloading Apache httpd web server apache2 *
root@wg-a:/etc/apache2#
```
#### 方法2. 设置自己的配置
因为已经Copy了一个CSP.conf进去,其实可以在这个文件里配置SSL的部分, 好处是不用单独写一个文件, 坏处是从维护的角度,把CSP module的配置和SSL的配置写在一个文件不是很合理。
```zsh
# 加入下部分到CSP.conf, 其中使用了自己的TLS配置,而不是openssl的证书和key.
LoadModule ssl_module /usr/lib/apache2/modules/mod_ssl.so
SSLEngine on
SSLCertificateFile "/external/cert/sslwebcert.crt"
SSLCertificateKeyFile "/external/cert/sslwebkey.key"
```
或者使用ssl mod的 conf
```zsh
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
SSLEngine on
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
SSLOptions +StdEnvVars
SSLOptions +StdEnvVars
```
文章
Kelly Huang · 七月 19, 2023
在之前的文章中,我们已经了解了如何配置和自定义我们的 EMPI,我们已经了解了如何通过 HL7 消息传递将新患者纳入我们的系统中,但当然,并不是所有的东西都是 HL7 v.2!我们如何配置 EMPI 实例以使用 FHIR 消息传递?
什么是FHIR?
对于那些不太熟悉 FHIR 这个术语的人,只需指出它们是Fast Healthcare Interoperability Resource的首字母缩写即可。 FHIR是HL7制定的医疗保健互操作性标准,基于JSON格式和REST通信,建立了一系列不同类型信息(患者数据、医院中心、诊断、医疗预约......)的“资源”。您可以在他们的官方页面上查看所有这些资源
InterSystems 和 FHIR
从 InterSystems 中,我们了解到 FHIR 为医疗保健互操作性领域提供的实用程序,因此我们拥有广泛的产品和功能,使我们能够充分利用并发挥其全部潜力:
InterSystems FHIR 服务器允许存储和管理 FHIR 资源。
InterSystems FHIR SQL Builder使用户可以通过 SQL 查询使用 FHIR 存储库中存储的所有信息。
InterSystems FHIR 适配器允许在我们的产品中发送和接收 FHIR 消息。
好吧,在我们的文章中,我们将利用适配器提供的功能来接收 FHIR 消息。
EMPI配置
FHIR 适配器安装
我们首先回顾一下独立 EMPI 安装中有哪些服务:
正如您所看到的,在独立模式下安装 EMPI 后,创建了一个注册表服务,其中包含配置 EMPI 所需的所有选项,以及我们称为 HSPIDATA 的命名空间,其中我们有一个产品来管理互操作性功能,需要。
对于我们的案例,我们创建了一个名为 WEBINAR 的新命名空间,我们将在其中部署正常的互操作性生产。我们将在此命名空间中安装FHIR 互操作性适配器,此安装将发布一个 Web 应用程序,我们将向该应用程序发送带有 FHIR 消息的 REST 调用。为此,我们将从终端执行以下命令:
zn "WEBINAR" set status = ##class (HS.FHIRServer.Installer).InteropAdapterConfig( "/csp/healthshare/webinar/fhir/r4" )
安装后,我们将检查从管理门户创建的新应用程序:
让我们在 WEBINAR 命名空间中检查我们的生产:
默认创建了两个新的业务组件:
InteropService :在我们的生产中,我们直接使用其类 HS.FHIRServer.Interop.Service 的名称,它将是负责接收发送到我们端点的 FHIR 消息的业务服务。 /csp/healthshare/网络研讨会/fhir/r4
InteropOperation :我们已将此名称更改为 HS.FHIRServer.Interop.Operation。对于我们的示例,我们忽略了此业务操作,因为我们将通过 TCP 将 FHIR 消息的信息发送到我们的 EMPI 生产
创建业务组件来管理 FHIR 消息传递
很好,我们已经启用了业务服务来接收 FHIR 消息,现在我们感兴趣的是从消息中提取信息并将其发送到我们的 EMPI 生产。让我们看一下 HS.FHIRServer.Interop.Service 将接收的消息类型:
Include HS.FHIRServer /// FHIRServer REST Business Service Class HS.FHIRServer.Interop.Service Extends (Ens.BusinessService, HS.HC.Util.Trace.Helper) { Parameter SETTINGS = "TargetConfigName:Basic:selector?context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId},Timeout:Basic" ; /// Configuration item to which to send inbound messages. Property TargetConfigName As Ens.DataType.ConfigName [ InitialExpression = "HS.FHIRServer.Interop.Operation" ] ; /// Timeout for dispatch (so we don't hold up the HTTP service too long or hang up a production shutdown). Property Timeout As %Integer [ InitialExpression = 25 ] ; /// Process an incoming message into the production; dispatch it to the configured target. /// The Interoperability contract requires that errors be returned as %Status here. Method OnProcessInput(pRequest As HS.FHIRServer.Interop.Request, Output pResponse As HS.FHIRServer.Interop.Response) As %Status
该消息的类型为 HS.FHIRServer.Interop.Request,如果我们查阅文档,我们将看到它有一个名为QuickStreamId的关联属性,我们将使用该属性来提取关联的 FHIR 消息。为此,我们将业务服务中收到的消息重定向到业务流程Webinar.BP.FHIRToHubRequest
该业务流程仅具有将消息重定向到负责通过 TCP 将消息发送到 EMPI 生产的业务操作FromFHIRToMPI的功能。
Method OnRequest(pRequest As HS.FHIRServer.Interop.Request, Output pResponse As HS.FHIRServer.Interop.Response) As %Status { Set tSC = ..SendRequestSync ( "FromFHIRToMPI" , pRequest, .pResponse) Quit $$$OK }
让我们看一下Webinar.BO.ToMPI类,并解释它如何从 FHIR 获取消息并将其转换为我们将发送到 EMPI 生产的字符串:
Include HS.FHIRServer Class Webinar.BO.ToMPI Extends Ens.BusinessOperation { Parameter ADAPTER = "EnsLib.TCP.CountedOutboundAdapter" ; Property Adapter As EnsLib.TCP.CountedOutboundAdapter ; Parameter INVOCATION = "Queue" ; Parameter SETTINGS = "FHIRMetadataSet::selector?context={HS.FHIRServer.Util.ContextSearch/FHIRMetadataSets}" ; /// FHIR Metadata Set. These are defined in HS_FHIRServer.FHIRMetadataSet. Property FHIRMetadataSet As %String (MAXLEN = 256 ) ; Method SendFHIRRequest(pRequest As HS.FHIRServer.Interop.Request, Output pResponse As HS.FHIRServer.Interop.Response) As %Status { // Get version of FHIR message configured Set tFHIRMetadataSetKey = $ZStrip ( $Piece ( ..FHIRMetadataSet , "/" , 1 ), "<>W" ) // We can have our FHIR message saved as a QuickStream or a Dynamic Object inside the json property of the request If pRequest.QuickStreamId'= "" { // Recover QuickStream of the FHIR message Set tQuickStream = ##class (HS.SDA3.QuickStream). %OpenId (pRequest.QuickStreamId) // Checking if message is in JSON format or XML If pRequest.Request.RequestFormatCode'= "" { Set tFHIRFormat = pRequest.Request.RequestFormatCode } Else { $$$ThrowOnError ( ##class (HS.HC.Util).GetFormatFromData(tQuickStream, .tFHIRFormat)) Do tQuickStream.Rewind() If tFHIRFormat= "json" { Set tFHIRFormat = $$$FHIRContentCodeJSON } ElseIf tFHIRFormat= "xml" { Set tFHIRFormat = $$$FHIRContentCodeXML } } // Transform QuickStream to DynamicObject Set tDynObj = ..GetDynObj (tQuickStream, tFHIRMetadataSetKey, tFHIRFormat) } ElseIf (( $IsObject (pRequest.Request.Json))&&(pRequest.Request.Json. %GetIterator (). %GetNext ())) { // Could have Json %DynamicObject if this host is called InProc. Set tDynObj = ..GetDynObj (pRequest.Request.Json, tFHIRMetadataSetKey) } Else { $$$ThrowStatus ( $$$ERROR ( $$$GeneralError , "FHIR interop request message missing FHIR content" )) } // Transform Dynamic Object to string set tRequest = tDynObj. %ToJSON () // Send message to EMPI production Set tSC= ..Adapter .SendMessageString(tRequest,.tResponse) $$$ThrowOnError (pRequest.NewResponse(.pResponse)) //Set pResponse.Response = ##class(HS.FHIRServer.API.Data.Response).%New() Set pResponse.Response.ResponseFormatCode = pRequest.Request.ResponseFormatCode set pResponse.Response.Status = 200 set pResponse.ContentType = "text/plain" Quit tSC } ClassMethod GetDynObj(stream As %Stream.Object , fhirVersion As %String , fhirFormat As %String ) As %DynamicObject { set schema = ##class (HS.FHIRServer.Schema).LoadSchema(fhirVersion) if fhirFormat = $$$FHIRContentCodeJSON { set dynObj = {}. %FromJSON (stream) } elseif fhirFormat = $$$FHIRContentCodeXML { set dynObj = ##class (HS.FHIRServer.Util.XMLToJSON).XMLToJSON(stream, schema) } Quit dynObj } XData MessageMap { <MapItems> <MapItem MessageType= "HS.FHIRServer.Interop.Request" > <Method>SendFHIRRequest</Method> </MapItem> </MapItems> } }
让我们一步步分析我们的类:
第一点是了解我们将在业务运营选项中配置的 FHIR 消息的版本。
Set tFHIRMetadataSetKey = $ZStrip ( $Piece ( ..FHIRMetadataSet , "/" , 1 ), "<>W" )
我们恢复正在处理的 FHIR 消息的类型,在我们的例子中它将是 R4。
接下来我们提取FHIR消息的内容:
// We can have our FHIR message saved as a QuickStream or a Dynamic Object inside the json property of the request If pRequest.QuickStreamId'= "" { // Recover QuickStream of the FHIR message Set tQuickStream = ##class (HS.SDA3.QuickStream). %OpenId (pRequest.QuickStreamId) // Checking if message is in JSON format or XML If pRequest.Request.RequestFormatCode'= "" { Set tFHIRFormat = pRequest.Request.RequestFormatCode } Else { $$$ThrowOnError ( ##class (HS.HC.Util).GetFormatFromData(tQuickStream, .tFHIRFormat)) Do tQuickStream.Rewind() If tFHIRFormat= "json" { Set tFHIRFormat = $$$FHIRContentCodeJSON } ElseIf tFHIRFormat= "xml" { Set tFHIRFormat = $$$FHIRContentCodeXML } } // Transform QuickStream to DynamicObject Set tDynObj = ..GetDynObj (tQuickStream, tFHIRMetadataSetKey, tFHIRFormat) } ElseIf (( $IsObject (pRequest.Request.Json))&&(pRequest.Request.Json. %GetIterator (). %GetNext ())) { // Could have Json %DynamicObject if this host is called InProc. Set tDynObj = ..GetDynObj (pRequest.Request.Json, tFHIRMetadataSetKey) } Else { $$$ThrowStatus ( $$$ERROR ( $$$GeneralError , "FHIR interop request message missing FHIR content" )) }
我们从 FHIR 消息中获取 %DynamicObject:
ClassMethod GetDynObj(stream As %Stream.Object , fhirVersion As %String , fhirFormat As %String ) As %DynamicObject { set schema = ##class (HS.FHIRServer.Schema).LoadSchema(fhirVersion) if fhirFormat = $$$FHIRContentCodeJSON { set dynObj = {}. %FromJSON (stream) } elseif fhirFormat = $$$FHIRContentCodeXML { set dynObj = ##class (HS.FHIRServer.Util.XMLToJSON).XMLToJSON(stream, schema) } Quit dynObj }
最后,我们将其转换为字符串,并通过 TCP 将其发送到我们的 EMPI 生产,向向我们发送原始消息的系统返回 200:
// Transform Dynamic Object to string set tRequest = tDynObj. %ToJSON () // Send message to EMPI production Set tSC= ..Adapter .SendMessageString(tRequest,.tResponse) $$$ThrowOnError (pRequest.NewResponse(.pResponse)) //Set pResponse.Response = ##class(HS.FHIRServer.API.Data.Response).%New() Set pResponse.Response.ResponseFormatCode = pRequest.Request.ResponseFormatCode set pResponse.Response.Status = 200 set pResponse.ContentType = "text/plain" Quit tSC
好吧,我们已经将消息发送到我们的 EMPI 生产中。让我们看一下示例消息的跟踪:
在下一篇文章中,我们将检索提交的字符串,将其转换回 %DynamicObject 并向 EMPI 提供数据以使用新数据执行注册/更新操作。
希望这些内容对你有用!
致敬原创作者 @Luis Angel
文章
姚 鑫 · 七月 6, 2021
# 第二十九章 从XML架构生成类
Studio提供了一个向导,该向导读取XML模式(从文件或URL),并生成一组支持XML的类,这些类对应于模式中定义的类型。
所有的类都扩展`%XML.Adaptor`。
指定一个包来包含类,以及控制类定义细节的各种选项。
向导还可以作为类方法使用,也可以使用该类方法。
在内部,SOAP向导在读取WSDL文档并生成web客户端或web服务时使用此方法;
注意:使用的任何XML文档的XML声明都应该指明该文档的字符编码,并且文档应该按照声明的方式进行编码。如果未声明字符编码,InterSystems IRIS将使用本书前面的“输入和输出的字符编码”中描述的默认值。如果这些默认值不正确,请修改XML声明,使其指定实际使用的字符集。
# 使用向导
要使用XML架构向导,请执行以下操作:
1. 选择 Tools > Add-Ins > XML Schema Wizard.

2. 在第一个屏幕上,指定要使用的XML模式。
做以下其中一项:
- 对于模式文件Schema File,选择Browse 以选择XML模式文件。
- 对于URL,指定模式的URL。

3. 选择Next。
下一个屏幕显示模式,以便可以验证选择了正确的模式。

4. 可选择以下选项:
- 保留空类Keep Empty Classes,它指定是否保留没有属性的未使用的类。
如果选择此选项,则不会在向导结束时删除此类;
否则,将删除它们。
- “不创建数组属性”Create No Array Properties控制向导是否生成数组属性。
如果选择此选项,向导不会生成数组属性,而是生成另一个表单。
- 为可为空的元素生成XMLNIL属性参数,它控制向导是否为生成的类中适用的属性指定XMLNIL属性参数。
该选项适用于每个对应于用`nillable="true"`指定的XML元素的属性。
如果选择此选项,向导将向属性定义添加`XMLNIL=1`。
否则不添加该参数。
该参数的详细信息请参见将对象投影到XML中的“处理空字符串和空值”。
- 为可为空的元素生成`XMLNILNOOBJECT`属性参数,它控制向导是否为生成的类中适用的属性指定`XMLNILNOOBJECT`属性参数。
该选项适用于每个对应于用`nillable="true"`指定的XML元素的属性。
如果选择此选项,向导将向属性定义添加`XMLNILNOOBJECT=1`。
否则不添加该参数。
该参数的详细信息请参见将对象投影到XML中的“处理空字符串和空值”。
5. 选择Next。
下一个屏幕显示关于要生成的类的选项的一些基本信息。
6. 在这个屏幕上,指定以下选项:
- 如果希望向导编译生成的类,可以选择“编译生成的类”。
- 可选择“添加NAMESPACE类参数”来指定`NAMESPACE`参数。
在本例中,`NAMESPACE`被设置为模式中`targetNamespace`的值。
如果不设置此选项,则不指定`NAMESPACE`。
建议在所有情况下都选择这个选项,因为每个支持XML的类都应该分配给一个XML名称空间。
(但是,为了向后兼容,可以将此选项清除。)
- 如果希望生成的类是持久类,请选择Create persistent classes。然后类扩展`%Persistent`。
可以稍后在向导中针对各个类更改这一点。
- 如果生成持久类,可以选择如何处理由另一个` b`的``组成的`` a。当向导生成一个包含属性`a`的持久类时,该属性有三种可能的形式。
可以将其定义为对象列表、一对多关系(默认)或父子关系。
下表总结了这些选择:
在持久性类中为集合属性使用关系 |向多对关系添加索引 |使用父子关系|生成的属性A的形式
---|---|---|---
selected (default)| not selected| not selected| 无索引的一对多关系
selected (default) |selected| not selected| 在多侧与索引的一对多关系
selected (default) |如果选择Use parent-child relationship,则忽略此选项| selected| 父子关系
not selected| not selected| not selected |List of objects
此外,如果未选择使用父子关系,则可以选择将`%OnDelete`方法添加到类以级联删除。如果选择此选项,当向导生成类定义时,它会在这些类中包含`%OnDelete()`回调方法的实现。生成的`%OnDelete()`方法删除类引用的所有持久对象。如确实选择了使用父子关系,请不要选择此选项;父子关系已经提供了类似的逻辑。
注意:如果修改生成的类,请确保根据需要修改`%OnDelete()`回调方法。
如果生成持久类,向导可以向每个对象类型类添加临时属性,以便可以为对象投影InterSystems IRIS内部标识符。选项如下:
- None-如果选择此选项,向导不会添加此处描述的任何属性。
- Use Id -如果选择此选项,向导将向每个对象类型类添加以下属性:
```
Property %identity As %XML.Id (XMLNAME="_identity", XMLPROJECTION="ATTRIBUTE") [Transient];
```
- Use Oid -如果选择此选项,向导将向每个对象类型类添加以下属性:
```
Property %identity As %XML.Oid (XMLNAME="_identity", XMLPROJECTION="ATTRIBUTE") [Transient];
```
- Use GUID-如果选择此选项,向导将向每个对象类型类添加以下属性:
```
Property %identity As %XML.GUID (XMLNAME="_identity", XMLPROJECTION="ATTRIBUTE") [Transient];
```
底部的表格列出了模式中的XML名称空间。在这里,指定包含该行中显示的XML名称空间的类的包。要执行此操作,请在程序包名字段中为该行指定程序包名。
7. 选择下一步。
8. 在下一个屏幕上,指定以下选项:
- Java Enabled - 如果选择此选项,则每个类都包括一个Java映射。
- Data Population数据填充-如果选择此选项,则除`%XML.Adaptor`外,每个类还继承会`%Populate`。
- SQL Column Order-如果选择此选项,每个属性将为`SqlColumnNumber`关键字指定一个值,以便属性在SQL中的顺序与它们在架构中的顺序相同。
- No Sequence Check-如果选中此选项,向导将生成的类中的`XMLSEQUENCE`参数设置为0。在某些情况下,如果XML文件的元素顺序与XML架构不同,则此选项非常有用。
默认情况下,`XMLSEQUENCE`参数在生成的类中设置为1。这可确保属性以与架构中相同的顺序包含在类定义中。
- XMLIGNORENULL-如果选择此选项,向导会将`XMLIGNORENULL=1`添加到类定义中。否则,它不会添加此参数。
- 将流用于二进制Use Streams for Binary - 如果选择此选项,向导将为`xsd:base64Binary`类型的任何元素生成`%Stream.GlobalBinary`类型的属性。如果清除此选项,则该属性的类型为`%xsd.base64Binary`。
请注意,向导将忽略`xsd:base64Binary`类型的任何属性。
- 在复选框下方,该表列出了向导将生成的类。对于每个类,确保适当地设置了`Extensions/Type`。在此,可以选择以下选项之一:
- 持久类`Persistent` -如果选择此选项,则类是持久性类。
- `Serial`-如果选择此选项,则类为序列类。
- `Registered Object`-如果选择此选项,则类为注册对象类。
所有生成的类还扩展`%XML.Adaptor`。
- 在表的右列中,为每个应编制索引的属性选择索引。
9. 选择Finish(完成)。
然后,向导将生成这些类,并在需要时编译它们。
对于这些类的属性,如果架构中相应元素的名称以下划线(_)开头,则属性名称以百分号(%)开头。
# 以编程方式生成类
XML架构向导也可用作`%XML.Utils.SchemaReader`类的`process()`方法。要使用此方法,请执行以下操作:
1. 创建`%XML.Utils.SchemaReader`的实例。
2. 可以选择设置实例的属性以控制其行为。
3. 可以选择创建InterSystems IRIS多维数组,以包含有关其他设置的信息。
4. 调用实例的`process()`方法:
```
method Process(LocationURL As %String,
Package As %String = "Test",
ByRef Features As %String) as %Status
```
- LocationURL必须是架构的URL或架构文件的名称(包括其完整路径)。
- Package是用于放置生成的类的包的名称。如果不指定程序包,InterSystems IRIS将使用服务名称作为程序包名称。
- Feature是在上一步中选择创建的多维数组。
# 每种XSD类型的默认IRIS数据类型
对于它生成的每个属性,XML架构向导会根据架构中指定的XSD类型自动使用适当的InterSystems IRIS数据类型类。下表列出了XSD类型和相应的InterSystems IRIS数据类型:
用于XML类型的InterSystems IRIS数据类型
源文档中的XSD类型 |生成的IRIS类中的数据类型
---|---
anyURI | %xsd.anyURI
base64Binary | `%xsd.base64Binary`或`%Stream.GlobalBinary`,具体取决于选择的选项。确定每个字符串是否可能超出字符串长度限制,如果可能,则将生成的属性从`%xsd.base64Binary`修改为适当的流类。)
boolean | `%Boolean`
byte | `%xsd.byte`
date | `%Date`
dateTime | `%TimeStamp`
decimal |`%Numeric`
double | `%xsd.double`
float | `%xsd.float`
hexBinary | `%xsd.hexBinary`
int |`%xsd.int`
integer |`%Integer`
long | `%Integer`
negativeInteger | `%xsd.negativeIntege`
nonNegativeInteger | `%xsd.nonNegativeInteger`
nonPositiveInteger |`%xsd.nonPositiveInteger`
positiveInteger | `%xsd.positiveInteger`
short | `%xsd.short`
string | `%String` (注意:责任确定每个字符串是否可能超出字符串长度限制,如果可能,则将生成的类型修改为适当的流类。)
time | `%Time`
unsignedByte | `%xsd.unsignedByte`
unsignedInt |`%xsd.unsignedInt`
unsignedLong | `%xsd.unsignedLong`
unsignedShort | `%xsd.unsignedShort`
no type given | `%String`
# 生成的属性的属性关键字
对于它生成的每个属性,XML架构向导还使用架构中的信息自动设置以下关键字:
- Description
- Required
- ReadOnly (如果相应的元素或属性是用固定属性定义的)
- InitialExpression (该值取自架构中的固定属性)
- Keywords related to relationships
# 生成的属性的参数
对于它生成的每个属性,XML架构向导会根据需要自动设置`XMLNAME`、`XMLPROJECTION`和所有其他与XML相关的参数。它还根据需要设置其他参数,如`MAXVAL`、`MINVAL`和`VALUELIST`。
# 调整为超长字符串生成的类
在极少数情况下,可能需要编辑生成的类来容纳超长的字符串或二进制值,超出字符串长度限制。
对于任何字符串类型,XML架构都不包含任何指示字符串长度的信息。XML架构向导将所有字符串值映射到InterSystems IRIS `%String`类,并将所有`base64Binary`值映射到`%xsd.base64Binary`类。这些选择可能不合适,具体取决于类要承载的数据。
在使用生成的类之前,应该执行以下操作:
- 检查生成的类,找到定义为`%string`或`%xsd.base64Binary`的属性。考虑将在其中使用这些类的上下文,特别是这些属性。
- 如果认为`%string`属性可能需要包含超出字符串长度限制的字符串,请将该属性重新定义为适当的字符流。同样,如果认为`%xsd.base64Binary`属性可能需要包含超过相同限制的字符串,请将该属性重新定义为适当的二进制流。
- 另请注意,对于类型为`%string`、`%xsd.string`和`%BINARY`的属性,默认情况下,`MAXLEN`属性参数为`50`个字符。可能需要指定更高的限制才能进行正确的验证。
(对于`%xsd.base64Binary`类型的属性,`MAXLEN`为`“”`,这意味着不会通过验证检查长度。但是,字符串长度限制确实适用。)
文章
姚 鑫 · 四月 13, 2021
# 第二章 定义和构建索引(一)
# 概述
索引是由持久类维护的结构,InterSystems IRIS®数据平台可以使用它来优化查询和其他操作。
可以在表中的字段值或类中的相应属性上定义索引。(还可以在多个字段/属性的组合值上定义索引。)。无论是使用SQL字段和表语法还是类属性语法定义相同的索引,都会创建相同的索引。当定义了某些类型的字段(属性)时,InterSystems IRIS会自动定义索引。可以在存储数据或可以可靠派生数据的任何字段上定义附加索引。InterSystems IRIS提供了几种类型的索引。可以为同一字段(属性)定义多个索引,为不同的目的提供不同类型的索引。
无论是使用SQL字段和表语法,还是使用类属性语法,只要对数据库执行数据插入、更新或删除操作,InterSystems IRIS就会填充和维护索引(默认情况下)。可以覆盖此默认值(通过使用`%NOINDEX`关键字)来快速更改数据,然后作为单独的操作生成或重新生成相应的索引。可以在用数据填充表之前定义索引。还可以为已经填充了数据的表定义索引,然后作为单独的操作填充(构建)索引。
InterSystems IRIS在准备和执行SQL查询时使用可用的索引。**默认情况下,它选择使用哪些索引来优化查询性能。** 可以根据需要覆盖此默认值,以防止对特定查询或所有查询使用一个或多个索引。
## 索引属性
每个索引都有一个唯一的名称。此名称用于数据库管理目的(报告、索引构建、删除索引等)。**与其他SQL实体一样,索引同时具有SQL索引名和相应的索引属性名;这些名称在允许的字符、区分大小写和最大长度方面有所不同。如果使用`SQL CREATE INDEX`命令定义,系统将生成相应的索引属性名称。如果使用持久类定义进行定义,则`SqlName`关键字允许用户指定不同的SQL索引名(SQL映射名称)。** Management Portal SQL界面的Catalog Details显示每个索引的SQL索引名称(SQL映射名称)和相应的索引属性名称(索引名称)。
索引类型由两个索引类关键字`Type`和`Extent`定义。IRIS提供的索引类型包括:
- 标准索引(`Type = index`)——一个持久数组,它将索引值与包含该值的行的 RowID相关联。
**任何没有明确定义为位图索引、位片索引或区段索引的索引都是标准索引。**
- 位图索引(`Type = Bitmap`)——一种特殊的索引,使用一系列位字符串来表示与给定索引值对应的RowID值集;
InterSystems IRIS包括许多位图索引的性能优化。
- 位片索引(`Type = Bitslice`)——一种特殊的索引,能够非常快速地计算某些表达式,例如总和数和范围条件。
某些SQL查询自动使用位片索引。
- 区段索引(`Extent Indices`)——一个区段中所有对象的索引。
有关更多信息,请参阅类定义参考中的区段索引关键字页。
- 范围索引-范围中所有对象的索引。
**表(类)的最大索引数为400。**
## 存储类型和索引
这里描述的索引功能适用于存储在持久化类中的数据。
InterSystems SQL支持使用InterSystems IRIS默认存储结构存储的数据的索引功能:`%Storage.Persistent`(`%Storage.Persistent`映射类)。
InterSystems SQL还支持使用`%Storage.SQL`(`%Storage.SQL`映射的类)存储的数据的索引功能。可以使用函数索引类型为`%Storage.SQL`映射的类定义索引。索引的定义方式与使用默认存储的类中的索引相同,但有以下特殊注意事项:
- 如果`IdKey`函数索引不是系统自动分配的,则该类必须定义`IdKey`函数索引。
- 此功能索引必须定义为索引。
请注意,不应直接调用`%Storage.Persistent`和`%Storage.SQL`类方法。相反,应该使用`%Persistent`类方法和本章中描述的操作调用索引功能。
## 索引全局名称
使用以下两种策略之一生成用于存储索引数据的下标全局:
- `%CLASSPARAMETER USEEXTENTSET=0`使用全局命名策略创建由用户指定的名称、附加的字母代码和索引名称组成的“传统”全局名称。用户可以理解这些全局名称,但它们可能很长,并且效率低于散列的全局名称。
- 如果`USEEXTENTSET=0`且未指定`DEFAULTGLOBAL`,则以下示例将描述生成的全局名称:`Sample.MyTest`持久类将定义名为`^Sample.MyTestD`的master map全局名`^Sample.MyTestD`位图范围索引全局名`^Sample.MyTestI(“$MyTest”)`(或`^Sample.MyTestI(“DDLBEIndex”)`),并且对于定义的索引`NameIDX`,它将定义名为`^Sample.MyTestI(“DDLBEIndex”)`的全局名。请注意,这些全局变量指定的是持久性类名(区分大小写),而不是SQL表名。
- 如果`USEEXTENTSET=0`并指定了`DEFAULTGLOBAL`,则指定的全局名称将替换永久类名。这允许指定一个比持久类名称更短或更清晰的名称。例如,如果`DEFAULTGLOBAL=“MyGlobal”`,则全局变量的名称如下:`^MyGlobalD`和`^MyGlobalI(“NameIDX”)`。
- `%CLASSPARAMETER USEEXTENTSET=1`使用创建哈希全局名称的全局命名策略。这包括对包名进行散列,对类名进行散列,然后追加一个点和一个标识索引的连续整数后缀。这些全局名称对用户来说不太容易理解,但往往更短、效率更高。
整数后缀仅作为索引名的关键字;与索引名和索引类型相关联的字段对整数编号没有影响。例如,`^EW3K.CgZk.1` 是 `Master Map`,`^EW3K.CgZk.2`是位图范围(`Bitmap Extent`),`^EW3K.CgZk.3`是`LastName`字段的已定义标准索引`NameIDX`,`^EW3K.CgZk.4`是已定义索引`WorkIdIDX`。如果删除`NameIDX`,则全局`^EW3K.CgZk.3`也会被删除,从而在整数序列中产生间隙。如果为`LastName`字段定义`LNameIDX`,则会创建全局`^EW3K.CgZk.5`;但是,如果稍后为`FullName`字段创建位图索引`NameIDX`,则全局索引将再次为`^EW3K.CgZk.3`。
- 如果`USEEXTENTSET=1`并且未指定`DEFAULTGLOBAL`,则包名和类名将被散列,如上所述。将追加连续的整数后缀。
- 如果`USEEXTENTSET=1`并指定了`DEFAULTGLOBAL`,则使用`DEFAULTGLOBAL`名称,而不是散列的包名和类名。将追加连续的整数后缀。例如,如果`DEFAULTGLOBAL="MyGlobal"`,则全局变量的名称如下:`^MyGlobal.1`和`^MyGlobal.3`
如果使用`CREATE TABLE`命令定义表,则`USEEXTENTSET`默认为1。因此,默认情况下,`CREATE TABLE`创建散列全局名称。可以使用`%CLASSPARAMETER`关键字以及`USEEXTENTSET`和`DEFAULTGLOBAL`参数更改此默认行为。可以使用`$SYSTEM.SQL.Util.SetOption()`方法在系统范围内更改此默认设置。
```java
SET status=$SYSTEM.SQL.Util.SetOption("DDLUseExtentSet",0,.oldval).
```
如果定义投影到表的持久类,则`USEEXTENTSET`默认为0。因此,默认情况下,使用传统的全局名称。
`DEFAULTGLOBAL`(如果已定义)将作为默认值。如果定义了`ExtentLocation`、`DataLocation`或`IndexLocation`存储关键字,则使用这些值,而不是上述默认值。
可以向`ZWRITE`提供全局名称以显示索引数据。
## Master Map
系统自动为每个表定义一个主图(`Data/Master`)。M`aster Map`不是索引,它是使用其`Map`下标字段直接访问数据本身的`Map`。默认情况下,`Master Map`下标字段是系统定义的`RowID`字段。默认情况下,使用`RowID`字段进行的这种直接数据访问由SQL映射名称(SQL索引名称)`IDKEY`表示。
默认情况下,用户定义的主键不是`IDKEY`。**这是因为使用`RowID`整数查找 `Master Map`总是比使用主键值查找效率更高。** 但是,如果指定主键为`IDKEY`,则主键索引被定义为表的主映射,SQL映射名称为主键SQL索引名。
对于单字段`key/IDKEY,`,主键索引是主映射,但主映射数据访问列仍然是`RowID`。这是因为在记录的唯一主键字段值和其`RowID`值之间存在一对一的匹配,而`RowID`被认为是更高效的查找。对于多字段主键/`IDKEY`,会为`Master Map`指定主键索引名称,并且`Master Map Data Access`列是主键字段。
可以通过Management Portal SQL Catalog Details(管理门户SQL目录详细信息)选项卡查看主图定义。除其他项目外,它还显示存储Master Map数据的全局名称。对于SQL和默认存储,此主映射全局默认为`^Package.classnameD`,并记录命名空间以防止歧义。对于自定义存储,未定义主地图数据存储全局名称;可以使用`DATALOCATIONGLOBAL`类参数指定数据存储全局名称。
对于SQL和默认存储,主映射数据存储在下标全局名为`^package.classnameD` 或 `^hashpackage.hashclass.1`。请注意,全局名指定持久类名,而不是相应的SQL表名,并且全局名区分大小写。可以向`ZWRITE`提供全局名称以显示`Master Map`数据。
**使用`Master Map`访问数据效率很低,尤其是对于大型表。因此,建议用户定义可用于访问`WHERE`条件、联接操作和其他操作中指定的数据字段的索引。**
# 自动定义的索引
定义表时,系统会自动定义某些索引。在为表格定义并在添加或修改表数据时,自动生成以下索引。如果定义:
- 不是`IDKEY`的主键,则系统会生成唯一类型的相应索引。主键索引的名称可以是用户指定的,也可以是从表名派生的。例如,如果定义一个未命名的主键,则相应的索引将命名为`tablenamePKEY#`,其中`#`是每个`UNIQUE`和`PRIMARY KEY`约束的顺序整数。
- 唯一的字段,Intersystems Iris为每个唯一字段生成索引,其中名称T`ableNameUnique#`,其中`#`是每个唯一和主键约束的顺序整数。
- 唯一约束,系统为每个具有指定名称的唯一约束生成索引,为共同定义唯一值的字段编制索引。
- shard key,系统在shard key字段上生成一个索引,命名为`ShardKey`。
可以通过Management Portal SQL Catalog Details选项卡查看这些索引。`CREATE INDEX`命令可用于添加唯一字段约束;D`ROP INDEX`命令可用于删除唯一字段约束。
默认情况下,系统在`RowID`字段上生成`IDKEY`索引。定义身份字段不会生成索引。但是,如果定义标识字段并将该字段作为主键,则InterSystems IRIS将在标识字段上定义`IDKEY`索引并将其作为主键索引。下面的示例显示了这一点:
```sql
CREATE TABLE Sample.MyStudents (
FirstName VARCHAR(12),
LastName VARCHAR(12),
StudentID IDENTITY,
CONSTRAINT StudentPK PRIMARY KEY (StudentID) )
```
同样,如果定义标识字段并为该字段提供唯一约束,则InterSystems IRIS将在标识字段上显式定义`IdKey/Unique`索引。下面的示例显示了这一点:
```sql
CREATE TABLE Sample.MyStudents (
FirstName VARCHAR(12),
LastName VARCHAR(12),
StudentID IDENTITY,
CONSTRAINT StudentU UNIQUE (StudentID) )
```
这些标识索引操作仅在没有明确定义的`idkey`索引时出现,并且表不包含任何数据。
## 位图范围索引
**位图范围索引是表的行的位图索引,而不是针对表的任何指定字段。在位图范围索引中,每个位表示顺序`ROWID`整数值,并且每个位的值指定相应的行是否存在。**
**SQL使用此索引来提高`Count(*)`的性能,返回表中的记录数(行)。**
**一个表最多可以有一个位图区段索引。创建多个位图范围索引导致`SQLCode -400`错误。** 其中 `ERROR #5445: Multiple Extent indices defined:DDLBEIndex.`
所有使用`CREATE TABLE`定义的表都会自动定义位图区段索引。
将此自动生成的索引分配索引名称(索引属性名称)`DDLBEIndex`和SQL MapName (SQL索引名称)`%%DDLBEIndex`。
定义为类的表可以有一个位图区索引,索引名和`$ClassName`的SQL MapName(其中`ClassName`是表的持久化类的名称)。
可以使用带有`BITMAPEXTENT`关键字的`CREATE INDEX`命令将位图区段索引添加到表中,或者重命名自动生成的位图区段索引。
可以通过管理门户SQL Catalog详细选项卡查看表的位图范围索引。虽然表只有一个位图范围索引,但是从另一个表中继承的表在其自身位图范围索引和它从其扩展的表中的位图范围索引中列出。例如,`Sample.employee`表扩展了`Sample.person`表;在目录详细信息映射 `Sample.Employee`列出`$Employee` 和 `$Person Bitmap`范围索引。
在经历许多删除操作的表格中,位图范围索引的存储可以逐渐变得效率较低。可以通过选择表的“目录详细信息”选项卡,“映射”选项和选择重建索引来重建从管理门户中重建位图范围索引。

**%SYS.Maint.Bitmap实用程序方法压缩位图范围索引,以及位图指数和bitslice索**引。
在以下任何情况下,调用`%BuildIndices()`方法都会构建现有的位图范围索引:未指定`%BuildIndices() pIndexList`参数(构建所有定义的索引);`pIndexList`按名称指定位图范围索引;或`pIndexList`指定任何定义的位图索引。
# 定义索引
## 使用类定义定义索引
在Studio中,可以使用新建索引向导或通过编辑类定义的文本将索引定义添加到`%Persistent`类定义。索引在一个或多个索引属性表达式上定义,后跟一个或多个可选索引关键字(可选)。它采用以下形式:
```sql
INDEX index_name ON index_property_expression_list [index_keyword_list];
```
- `index_name`是有效的标识符。
- `index_property_expression_list`是一个或多个以逗号分隔的属性表达式的列表,它们作为索引的基础。
- `index_keyword_list`是一个可选的索引关键字列表,用逗号分隔,用方括号括起来。
用于指定位图或位片索引的索引类型。
也用于指定唯一的、`IdKey`或`PrimaryKey`索引。
(根据定义,`IdKey`或`PrimaryKey`索引也是唯一索引。)
索引关键字的完整列表出现在类定义引用中。
`index_property_expression_list`参数由一个或多个索引属性表达式组成。
索引属性表达式包括:
- 要建立索引的属性的名称。
- 可选(元素)或(键)表达式,提供对集合子值进行索引的方法。
如果`index`属性不是一个集合,用户可以使用`BuildValueArray()`方法生成一个包含键和元素的数组。
- 可选的排序规则表达式。
它包含一个排序规则名称,后面可选地跟着一个或多个以逗号分隔的排序规则参数列表。
不能为惟一索引、`IdKey`索引或`PrimaryKey`索引指定索引排序规则。
唯一索引或`PrimaryKey`索引从正在建立索引的属性(字段)中获取其排序规则。
`IdKey`索引总是精确(`EXACT`)的排序。
例如,下面的类定义定义了两个属性和一个基于它们的索引:
```java
Class MyApp.Student Extends %Persistent [DdlAllowed]
{
Property Name As %String;
Property GPA As %Decimal;
Index NameIDX On Name;
Index GPAIDX On GPA;
}
```
更复杂的索引定义是:
```sql
Index Index1 On (Property1 As SQLUPPER(77), Property2 AS EXACT);
```
### 可以建立索引的属性
唯一可以被索引的属性是:
- 那些存储在数据库中的
- 那些可以从存储的属性可靠地派生出来的
必须使用`SQLComputed`关键字定义可以可靠地派生(并且未存储)的属性; `SQLComputeCode`指定的代码必须是导出属性值的唯一方法,并且无法直接设置属性。
如果可以直接设置一个派生属性的值,比如是一个简单的情况下(non-collection)属性定义为瞬态和不也定义为计算,然后直接设置属性的值将覆盖`SQLComputeCode`中定义的计算和存储的值不能可靠地来自属性;
这种类型的派生属性称为不确定性。(计算的关键字实际上意味着没有分配实例内存。)
一般规则是,只有定义为`calculate`和`SQLComputed`的派生属性才能被索引。
但是,派生集合有一个例外:派生的(`SQLComputed`)集合是暂时的(没有存储)集合,也没有定义为计算的集合(意味着没有实例内存)可以被索引。
**注意:`IdKey`索引所使用的任何属性的值内都不能有连续的一对竖条(`||`),除非该属性是对持久类实例的有效引用。**
这个限制是InterSystems SQL内部机制所要求的。
在`IdKey`属性中使用`||`会导致不可预知的行为。
### 多个属性的索引
可以在两个或多个属性(字段)的组合上定义索引。在类定义中,使用索引定义的`ON`子句指定属性列表,例如:
```java
Class MyApp.Employee Extends %Persistent [DdlAllowed]
{
Property Name As %String;
Property Salary As %Integer;
Property State As %String(MAXLEN=2);
Index MainIDX On(State,Salary);
}
```
如果需要执行使用字段值组合的查询,例如:
```sql
SELECT Name,State,Salary
FROM Employee
ORDER BY State,Salary
```
### 索引排序
唯一索引、`PrimaryKey`索引或`IdKey`索引不能指定排序规则类型。
对于其他类型的索引,索引定义中指定的每个属性都可以有一个排序规则类型。
应用索引时,索引排序类型应与属性(字段)排序类型匹配。
1. 如果索引定义包含为属性显式指定的排序规则,则索引使用该排序规则。
2. 如果索引定义不包括为属性显式指定的排序规则,则索引使用属性定义中显式指定的排序规则。
3. 如果属性定义不包括显式指定的排序规则,则索引使用属性数据类型的默认排序规则。
例如,`Name`属性被定义为字符串,**因此在默认情况下具有`SQLUPPER`排序规则。**
如果在`Name`上定义一个索引,默认情况下,它接受属性的排序规则,索引也将使用`SQLUPPER`定义。
属性排序和索引排序匹配。
但是,如果比较应用不同的排序规则,例如,`WHERE %EXACT(Name)=%EXACT(:invar)`,则此用法中的属性排序规则类型不再与索引排序规则类型匹配。属性比较排序规则类型与索引排序规则类型之间的不匹配可能会导致不使用索引。因此,在这种情况下,可能希望为具有精确(`EXACT`)排序规则的`Name`属性定义索引。如果`JOIN`语句的`ON`子句指定了排序规则类型,例如,`FROM Table1 LEFT JOIN Table2 ON %EXACT(Table1.Name) = %EXACT(Table2.Name)`,此处指定的属性排序类型与索引排序类型不匹配可能导致InterSystems IRIS不使用该索引。
以下规则控制索引和属性之间的排序规则匹配:
- **匹配的排序规则类型总是最大限度地使用索引。**
- **排序规则类型不匹配,其中属性指定为精确的排序规则(如上所示),并且索引有一些其他的排序规则,允许使用索引,但是它的使用不如匹配排序类型有效。**
- **排序规则类型不匹配,其中属性排序规则不准确,属性排序规则不匹配索引排序规则,这将导致不使用索引。**
要在索引定义中显式地为属性指定排序规则,语法如下:
```sql
Index IndexName On PropertyName As CollationName;
```
- `IndexName`是索引的名称
- `PropertyName`是被索引的属性
- `CollationName`是用于索引的排序规则的类型
例如:
```sql
Index NameIDX On Name As Exact;
```
不同的属性可以有不同的排序规则类型。
例如,在下面的例子中,`F1`属性使用`SQLUPPER`排序,而F2使用`EXACT`排序:
```sql
Index Index1 On (F1 As SQLUPPER, F2 As EXACT);
```
**注意:指定为`Unique`、`PrimaryKey`或`IdKey`的索引不能指定索引排序规则。
索引从属性`collations`中获取其`collation`。**
文章
姚 鑫 · 七月 17, 2021
# 第六章 使用%File对象
如果想要操作文件本身,需要使用`%Library.File`的`%New()`方法实例化`%File`对象。该类还提供了允许使用该文件的实例方法。
注意:本节提供了几个使用`%File`对象的示例,以供说明。
对于简单的文件读写,使用`%Stream.FileCharacter`和%`Stream.FileBinary`。因为它们提供了额外的功能,例如,以正确的模式自动打开文件。
## 创建%File对象的实例
要使用文件,需要使用`%New()`方法实例化表示该文件的%File对象。该文件可能已经存在,也可能不存在于磁盘上。
以下示例在默认目录中为文件`export.xml`实例化一个%File对象。
```java
set fileObj = ##class(%File).%New("export.xml")
```
## 打开和关闭文件
实例化`%File`对象后,需要使用`open()`方法打开文件,以读取或写入该文件:
```java
USER>set status = fileObj.Open()
USER>write status
1
```
使用`Close()`方法关闭文件:
```java
USER>do fileObj.Close()
```
## 检查%File对象的属性
一旦实例化了文件,就可以直接检查文件的属性。
```java
USER>write fileObj.Name
export.xml
USER>write fileObj.Size
2512
USER>write $zdate(fileObj.DateCreated)
11/18/2020
USER>write $zdate(fileObj.DateModified)
11/18/2020
USER>write fileObj.LastModified
2020-11-18 14:24:38
USER>write fileObj.IsOpen
0
```
请注意,`LastModified`是人类可读的时间戳,而不是`$H`格式的日期。
属性“大小Size”、“创建日期DateCreated”、“修改日期DateModified”和“最后修改日期LastModified”是在访问时计算的。为不存在的文件访问这些属性会返回-2,表示找不到该文件。
注意:Windows是目前唯一跟踪实际创建日期的平台。其他平台存储最后一次文件状态更改的日期。
```java
USER>write ##class(%File).Exists("foo.xml")
0
USER>set fooObj = ##class(%File).%New("foo.xml")
USER>write fooObj.Size
-2
```
如果文件已打开,可以通过访问`CanonicalName`属性来查看其规范名称,这是根目录的完整路径。
```java
USER>write fileObj.CanonicalName
USER>set status = fileObj.Open()
USER>write fileObj.IsOpen
1
USER>write fileObj.CanonicalName
c:\intersystems\IRIS\mgr\user\export.xml
```
## 从文件中读取
要读取文件,可以打开文件,然后使用`Read()`方法。
以下示例读取`messages.log`的前`200`个字符。
```java
USER>set messages = ##class(%File).%New(##class(%File).ManagerDirectory() _ "messages.log")
USER>set status = messages.Open("RU")
USER>write status
1
USER>set text = messages.Read(200, .sc)
USER>write text
*** Recovery started at Mon Dec 09 16:42:01 2019
Current default directory: c:\intersystems\IRIS\mgr
Log file directory: .\
WIJ file spec: c:\intersystems\IRIS\mgr\IR
USER>write sc
1
USER>do messages.Close()
```
要从文件中读取整行,请使用`ReadLine()`方法,该方法继承自`%Library.File`的父类`%Library.AbstractStream`。
下面的示例读取`E:\temp\new.txt`的第一行。
```java
/// desc: 读取数据
/// w ##class(Demo.FileDemo).ReadFileData("E:\temp\new.txt")
ClassMethod ReadFileData(str)
{
s fileObj = ##class(%File).%New(str)
s status = fileObj.Open("RU")
w status,!
s text = fileObj.ReadLine(,.sc)
w text,!
w sc,!
d fileObj.Close()
q ""
}
```
## 写入文件
要写入文件,可以打开文件,然后使用`Write()`或`WriteLine()`方法。
以下示例将一行文本写入新文件。
```java
/// desc: 写入数据
/// w ##class(Demo.FileDemo).WriteFileData("E:\temp\new.txt")
ClassMethod WriteFileData(str)
{
s fileObj = ##class(%File).%New(str)
s status = fileObj.Open("RUWSN")
w status,!
s status = fileObj.WriteLine("Writing to a new file.")
w status,!
w fileObj.Size,!
d fileObj.Rewind()
s text = fileObj.ReadLine(,.sc)
w text,!
q ""
}
```
## 倒回文件
从文件读取或写入文件后,希望使用`Rewind()`方法倒回文件,以便可以从文件开头执行操作。
从上一个示例停止的地方开始,`fileObj`现在位于其末尾。倒回文件并再次使用`WriteLine()`会覆盖该文件。
```java
USER>set status = fileObj.Rewind()
USER>write status
1
USER>set status = fileObj.WriteLine("Rewriting the file from the beginning.")
USER>write status
1
USER>write fileObj.Size
40
```
关闭文件并重新打开它也会倒回文件。
```java
USER>do fileObj.Close()
USER>set status = fileObj.Open("RU")
USER>write status
1
USER>set text = fileObj.ReadLine(,.sc)
USER>write sc
1
USER>write text
Rewriting the file from the beginning.
```
## 清除文件
要清除文件,可以打开文件,然后使用`Clear()`方法。这将从文件系统中删除该文件。
以下示例清除默认目录中的`junk.xml`。
```java
USER>write ##class(%File).Exists("junk.xml")
1
USER>set fileObj = ##class(%File).%New("junk.xml")
USER>set status = fileObj.Open()
USER>write status
1
USER>set status = fileObj.Clear()
USER>write status
1
USER>write ##class(%File).Exists("junk.xml")
0
```
文章
Nicky Zhu · 一月 11, 2021
当我向技术人员介绍InterSystems IRIS时,我一般会先讲其核心是一个多模型DBMS。
我认为这是其主要优势(在DBMS方面)。数据仅存储一次。您只需访问您想用的API。
- 您想要数据的概要?用SQL!
- 您想用一份记录做更多事情?用对象!
- 想要访问或设置一个值,并且您知道键?用Globals!
乍一看挺好的,简明扼要,又传达了信息,但当人们真正开始使用InterSystems IRIS时,问题就来了。类、表和Globals是如何关联的?它们之间有什么关系?数据是如何存储的?
本文我将尝试回答这些问题,并解释这些到底是怎么回事。
## 第一部分 模型偏见
处理数据的人往往对他们使用的模型有偏见。
开发者们把数据视为对象。对他们而言,数据库和表都是通过CRUD(增查改删,最好是基于ORM)交互的盒子,但底层的概念模型都是对象(当然这对于我们大多数使用面向对象编程语言的开发者来说没错)。
而DBA大部分时间都在搞关系型DBMS,他们把数据视为表。对象只是行的封装器。
对于InterSystems IRIS,持久类也是一个表,将数据存储在Global中,因此需要进行一些澄清。
## 第二部分 举例
假设您创建了类Point:
```objectscript
Class try.Point Extends %Persistent [DDLAllowed]
{
Property X;
Property Y;
}
```
您也可以用DDL/SQL创建相同的类:
```
CREATE Table try.Point (
X VARCHAR(50),
Y VARCHAR(50))
```
编译后,新类将自动生成一个存储结构,将原生存储在Global中的数据映射到列(对于面向对象开发者而言,是属性):
```
Storage Default
{
%%CLASSNAME
X
Y
^try.PointD
PointDefaultData
^try.PointD
^try.PointI
^try.PointS
%Library.CacheStorage
}
```
这是怎么回事?
自下向上(加粗文字很重要):
- Type: 生成的存储类型,本例中是持久对象的默认存储
- StreamLocation - 存储流的Global
- IndexLocation - 索引Global
- IdLocation - 存储ID自增计数器的Global
- **DefaultData** - 存储将Global值映射到列/属性的XML元素
- **DataLocation** - 存储数据的Global
现在我们的DefaultData是PointDefaultData,让我们分析下它的结构。本质上Global节点有这样的结构:
- 1 - %%CLASSNAME
- 2 - X
- 3 - Y
所以我们可能期望我们的Global是这样的:
```
^try.PointD(id) = %%CLASSNAME, X, Y
```
但如果我们输出 Global 它会是空的,因为我们没有添加任何数据:
```
zw ^try.PointD
```
让我们添加一个对象:
```
set p = ##class(try.Point).%New()
set p.X = 1
set p.Y = 2
write p.%Save()
```
现在我们的Global变成了这样
```
zw ^try.PointD
^try.PointD=1
^try.PointD(1)=$lb("",1,2)
```
可以看到,我们期望的结构%%CLASSNAME, X, Y是用 $lb("",1,2) 设置的,它对应的是对象的X和Y属性(%%CLASSNAME 是系统属性,忽略)。
我们也可以用SQL添加一行:
```
INSERT INTO try.Point (X, Y) VALUES (3,4)
```
现在我们的Global变成了这样:
```
zw ^try.PointD
^try.PointD=2
^try.PointD(1)=$lb("",1,2)
^try.PointD(2)=$lb("",3,4)
```
所以我们通过对象或SQL添加的数据根据存储定义被存储在Global中(备注:可以通过在PointDefaultData 中替换X和Y来手动修改存储定义,看看新数据会怎样!)。
现在,如果我们想执行SQL查询会怎样?
```
SELECT * FROM try.Point
```
这段sql查询被转换为ObjectScript代码, 遍历^try.PointD,并根据存储定义(其中的 PointDefaultData 部分)填充列。
下面是修改。让我们从表中删除所有数据:
```
DELETE FROM try.Point
```
看看我们的Global变成什么样了:
```
zw ^try.PointD
^try.PointD=2
```
可以看到,只剩下ID计数器,所以新对象/行的ID=3。我们的类和表也继续存在。
但如果我们运行会怎样:
```
DROP TABLE try.Point
```
它会销毁表、类并删除Global。
```
zw ^try.PointD
```
看完这个例子,希望您现在对Global、类和表如何相互集成和互补有了更好的理解。根据实际需要选用正确的API会让开发更快、更敏捷、bug更少。
问题
water huang · 四月 22, 2021
m 里面如何获取cpu的序列号? 可以调用操作系统的命令来获取CPU序列号。例如在Cache' for Windows上,可以执行:SAMPLES>s args=3SAMPLES>s args(1)="CPU"SAMPLES>s args(2)="get"SAMPLES>s args(3)="ProcessorID"SAMPLES>d $ZF(-100,"","wmic",.args)ProcessorId0FABFBFF000506EX0FABFBFF000006EX0FABFBFF000006EX0FABFBFF000006EX 乔工,请问 $zf函数的使用,在哪里可以查询到它的所有使用说明 InterSystems Cache'InterSystems IRIS 刚才试了一下,这个不行呢 感谢你的回答 但是我用的是ensemble2016 是Windows吗?在Windows命令行,执行wmic CPU get ProcessorID,能得到CPU序列号吗? Ensemble 2016有点久,没有$ZF(-100)。用$ZF(-1):
https://cedocs.intersystems.com/ens20161/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_fzf-1#RCOS_B80417 是Windows,在Windows命令行,执行wmic CPU get ProcessorID,能得到CPU序列号 刚才试了 还是不行 用$ZF(-1), 可以将OS命令输出保存到文件里。例如:w $ZF(-1,"wmic CPU get ProcessorID > c:\temp\cpuinfo.txt") 系统是windows 10 试了,不行,返回的值是1 返回值是1,说明报错了。确认一下是否OS命令写正确了。另外,输出不是看返回值,是看输出的文件 我直接复制的你写的这个命令。
文章
Michael Lei · 五月 24, 2021
作为一个软件架构师,如果要设计一个企业级的架构来满足当前的业务需求时,你需要达到5级的水平,这是一个巨大的挑战。有了InterSystems IRIS。这是有可能的。通过1个产品,你可以得到SQL + NoSQL + ESB + BI + Open Analytics + Real Time Virtual cubes + NLP + AutoML + ML(使用Python)和高级云支持 + Sharding支持。
以Oracle为例,你需要Oracle数据库+ Oracle NoSQL+ Oracle BI+ Oracle SOA套件+一些附加组件,如R、Partitioning和RAC以及一些Oracle云产品的NLP和AI功能。对于开源架构、IBM或者其他友商来说,也是类似的,你需要的组件只可能更多。
医疗机构在数字化转型在技术上最大的挑战之一,就是技术栈过于丰富、快速变化以及对人的技术要求很高(这也是为什么全栈工程师最值钱的原因);一个统一、高效、稳定、现代化、专为医疗定制的一体化数据平台能帮助客户很好地解决这个问题。IRIS for Health 是您的不二选择。
文章
Michael Lei · 八月 18, 2022
Hi 社区的朋友们,大家好!
有时我们需要以编程方式自动将CSV数据从文件或者UR网址L导入到InterSystems IRIS。我们希望创建具有适当数据类型的类并导入数据。
我在Open Exchange上发布了一个模块csvgen,它正是这样做的。
如果你只需要将CSV文件导入IRIS,你可以这么做:
USER>do ##class(community.csvgen).Generate("/usr/data/titanic.csv",,"Data.Titanic")
Class name: Data.Titanic
Header: PassengerId INTEGER,Survived INTEGER,Pclass INTEGER,Name VARCHAR(250),Sex VARCHAR(250),Age INTEGER,SibSp INTEGER,Parch INTEGER,Ticket VARCHAR(250),Fare MONEY,Cabin VARCHAR(250),Embarked VARCHAR(250)
Records imported: 891
USER>
或者你的CSV文件在互联网上, 例如GitHub上面的新冠疫情数据 你可以这样获得数据:
USER>d ##class(community.csvgen).GenerateFromURL("https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_daily_reports/05-29-2020.csv",",","Data.Covid19")
Class name: Data.Covid19
Header: FIPS INTEGER,Admin2 VARCHAR(250),Province_State VARCHAR(250),Country_Region VARCHAR(250),Last_Update DATE,Lat MONEY,Long_ DOUBLE,Confirmed INTEGER,Deaths INTEGER,Recovered INTEGER,Active INTEGER,Combined_Key VARCHAR(250),Incidence_Rate DOUBLE,Case-Fatality_Ratio DOUBLE
Records imported: 3522
USER>
Installation安装
从ZPM安装:
USER>zpm
zpm:USER>install csvgen
csvgen 模块是打包了 CSV2CLASS method .
ObjectiveScript 的profile还不是很理想,欢迎各种建议意见。谢谢!