清除过滤器
文章
姚 鑫 · 八月 19, 2021
# 第121章 查询关键字 - Private
指定查询是否为私有查询。
# 用法
要指定此查询为私有查询,请使用以下语法:
```java
Query name(formal_spec) As classname [ Private ] { //implementation }
```
否则,请省略此关键字或将该词放在该关键字之前。
# 详解
私有类成员只能由同一类(或其子类)的其他成员使用。请注意,其他语言通常使用单词`Protected`来描述这种可见性,使用单词`Private`来表示从子类不可见。
# 默认
如果省略此关键字,则此查询不是私有的。
# 第122章 查询关键字 - SoapBindingStyle
指定此查询用作`Web方法`时使用的绑定样式或`SOAP`调用机制。仅适用于定义为`Web服务`或`Web客户端`的类。
# 用法
要覆盖查询使用的默认绑定样式(当它用作`Web方法`时),请使用以下语法:
```java
Query name(formal_spec) As classname [ WebMethod, SoapBindingStyle = soapbindingstyle ] { //implementation }
```
其中`soapbindingstyle`为下列值之一:
- `document` - 此`Web方法`使用文档式调用。
使用这种绑定样式,`SOAP`消息被格式化为文档,并且通常只有一个部分。
在`SOAP`消息中,``元素通常包含单个子元素。``元素的每个子元素对应一个消息部分。
- `rpc` - 此`Web方法`使用`RP`C(远程过程调用)风格的调用。
使用这种绑定样式,`SOAP`消息被格式化为具有多个部分的消息。
在`SOAP`消息中,``元素包含一个子元素,其名称取自相应的操作名称。此元素是生成的包装元素,它为方法的参数列表中的每个参数包含一个子元素。
重要提示:对于手动创建的`Web服务`,此关键字的默认值通常比较合适。当使用`SOAP`向导从`WSDL`生成`Web客户端`或`服务`时,InterSystems IRIS会将此关键字设置为适用于该`WSDL`;如果修改此值,`Web客户端`或服务可能不再工作。
# 详情
此关键字允许指定此查询在作为`Web方法`调用时使用的绑定样式。
对于给定查询,此关键字覆盖`SoapBindingStyle`类关键字。
# 默认
如果忽略此关键字,``元素的`style`属性将由`SoapBindingStyle`类关键字的值决定。
# WSDL的关系
(请注意,与方法关键字和查询关键字相比,同名的`class关键字`对`WSDL`的影响更大。)
# 对SOAP消息的影响
有关信息,请参阅`SoapBindingStyle`类关键字的条目。
# 第123章 查询关键字 - SoapBodyUse
指定该查询用作`web方法`时,输入和输出使用的编码。
仅应用于定义为`web服务`或`web客户端`的类。
# 用法
要覆盖查询的输入和输出使用的默认编码(当它被用作`web方法`时),请使用以下语法:
```java
Query name(formal_spec) As classname [ WebMethod, SoapBodyUse = encoded ] { //implementation }
```
其中,`soapbodyuse`是下列值之一:
- `literal` - 这个`web方法`使用文字数据。
也就是说,`SOAP`消息的``中的`XML`与`WSDL`中给出的模式完全匹配。
- `encoded` = 这个web方法使用soap编码的数据。
也就是说,`SOAP`消息的``中的`XML`根据所使用的`SOAP`版本使用适当的SOAP编码,满足以下规范的要求:
- `SOAP 1.1` (https://www.w3.org/TR/2000/NOTE-SOAP-20000508/)
- `SOAP 1.2` (https://www.w3.org/TR/soap12-part2/)
重要提示:对于手工创建的`web服务`,这个关键字的默认值通常是合适的。
当使用`SOAP`向导从`WSDL`生成`web客户端`或服务时, IRIS将此关键字设置为适合该`WSDL`的;
如果修改了该值,`web客户端`或服务可能不再工作。
# 详解
该关键字允许您指定该查询作为`web方法`调用时的输入和输出的编码。
对于给定的查询,此关键字覆盖`SoapBodyUse`类关键字。
# 默认
如果忽略此关键字,则使用`SoapBodyUse`类关键字的值。
# 与WSDL的关系以及对SOAP消息的影响
有关信息,请参阅`SoapBodyUse`类关键字的条目。
# 第124章 查询关键字 - SoapNameSpace
在`WSDL`中的绑定操作级别指定名称空间。
仅应用于定义为`web服务`或`web客户端`的类。
# 用法
要在绑定操作级别覆盖默认命名空间(当查询被用作`web方法`时),请使用以下语法:
```java
Query name(formal_spec) As classname [ SoapNameSpace = "soapnamespace", WebMethod ] { //implementation }
```
其中,`soapnamespace`是一个名称空间`URI`。
注意,如果`URI`包含冒号(`:`),则字符串必须用引号括起来。
也就是说,你可以使用以下方法:
```java
Query MyQuery() [ SoapNameSpace = "http://www.mynamespace.org", WebMethod ]
```
或以下:
```java
Query MyQuery() [ SoapNameSpace = othervalue, WebMethod ]
```
但以下情况并非如此:
```java
Query MyQuery() [ SoapNameSpace = http://www.mynamespace.org, WebMethod ]
```
重要提示:对于手工创建的`web服务`,这个关键字的默认值通常是合适的。
当使用`SOAP`向导从`WSDL`生成`web客户`端或服务时, IRIS将此关键字设置为适合该WSDL的;
如果修改了该值,`web客户端`或服务可能不再工作。
# 详解
该关键字允许指定查询作为`web方法`调用时使用的`XML`名称空间。
注意:此关键字仅在查询使用`rpc`样式绑定时有效。
也就是说,查询(或包含它的类)必须用等于`rpc`的`SoapBindingStyle`标记。
(如果为使用文档样式绑定的查询指定此关键字,则`WSDL`将不是自一致的。)
# 默认
如果忽略此关键字,则`web方法`位于由`web服务`或客户端类的`namespace`参数指定的命名空间中。
# 与WSDL的关系以及对SOAP消息的影响
有关信息,请参阅`SoapNameSpace`方法关键字的条目。
文章
姚 鑫 · 六月 14, 2023
# 第五十七章 镜像中断程序 - 在手动故障转移之前确定备份是否处于活动状态
## 在手动故障转移之前确定备份是否处于活动状态
假设有两个名为 `IRIS A` 和`IRIS B` 的故障转移成员。如果 `^MIRROR` 例程确认备份 (`IRIS B`) 在与主 (`IRIS A`) 丢失联系时处于活动状态,因此具有最新的来自 `IRIS A` 的日志数据,可以使用单个过程手动进行故障转移。当连接因主要故障而丢失时,不会造成数据丢失的风险。但是,当发生多个故障时,活动备份可能没有来自主服务器的所有最新日志数据,因为主服务器在连接丢失后继续运行了一段时间。
使用以下过程确定备份是否处于活动状态:
1. 确认 `IRIS` 实例 `IRIS A` 上的 `ISCAgent` 实际上已关闭(并确保它们在整个手动故障转移过程中保持关闭状态)。
2. 在 `IRIS B` 上,在终端的 `%SYS` 命名空间中运行 `^MIRROR` 例程(请参阅使用 `^MIRROR` 例程)。
3. 在主菜单中选择镜像管理,显示如下子菜单:
```java
1) Add mirrored database(s)
2) Remove mirrored database(s)
3) Activate or Catchup mirrored database(s)
4) Change No Failover State
5) Try to make this the primary
6) Connect to Mirror
7) Stop mirroring on this member
8) Modify Database Size Field(s)
9) Force this node to become the primary
10) Promote Async DR member to Failover member
11) Demote Backup member to Async DR member
12) Mark an inactive database as caught up
13) Manage mirror dejournaling on async member (disabled)
14) Pause dejournaling for database(s)
```
4. 选择 `Force this node to become the primary` 选项。如果在联系丢失时备份处于活动状态,则会显示如下消息:
```java
This instance was an active backup member the last time it was
connected so if the primary has not done any work since that time,
this instance can take over without having to rebuild the mirror
when the primary reconnects. If the primary has done any work
beyond this point (file #98),
C:\InterSystems\MyIRIS\mgr\journal\MIRROR-GFS-20180815.009
then the consequence of forcing this instance to become the primary is
that some operations may be lost and the other mirror member may need
to be rebuilt from a backup of this node before it can join as
a backup node again.
Do you want to continue?
```
如果有权访问主要文件的日志文件,则可以在继续之前确认引用的文件是最新的。
如果在与主服务器失去联系时备份未处于活动状态,则会显示如下消息:
```java
Warning, this action can result in forcing this node to become
the primary when it does not have all of the journal data which
has been generated in the mirror. The consequence of this is that
some operations may be lost and the other mirror member may need
to be rebuilt from a backup of this node before it can join as
a backup node again.
Do you want to continue?
```
## 手动故障转移到活动备份
如果 `^MIRROR` 例程的 `Force this node to become the primary` 选项确认备份在失去与主节点的连接时处于活动状态,请完成手动故障转移过程,如下所示:
1. 在要继续吗?提示继续该过程。 `Force this node to become the primary` 选项等待 `60` 秒以使镜像成员成为主要节点。如果操作未在 `60` 秒内成功完成,`^MIRROR` 报告操作可能未成功并指示您检查消息日志以确定操作是失败还是仍在进行中。
2. 一旦 `^MIRROR` 例程确认备份已成为主要备份,请在可以这样做时重新启动 `IRIS A`。当 `IRIS` 实例重新启动时, `IRIS A` 作为备份加入镜像。
## 备份不活动时手动故障转移
即使 `^MIRROR` 例程未确认备份 ( `IRIS B`) 在与主 ( `IRIS A`) 失去连接时处于活动状态,仍然可以使用以下过程继续手动故障转移过程,但是如果这样做,会有数据丢失的风险。如本程序所述,可以在手动故障转移之前将最新的镜像日志文件从 `IRIS A`(如果有权访问)复制到 `IRIS` B,从而最大限度地降低这种风险。
1. 如果有权访问主服务器的镜像日志文件,请将最新的文件复制到 `IRIS B`,从 `IRIS B` 上的最新日志文件开始,然后包括来自 `IRIS A` 的任何后续文件。例如,如果 `MIRROR-MIRRORA-20180220.001`是 `IRIS B` 上的最新文件,复制 `MIRROR-MIRRORA-20180220.001` 和 `IRIS A` 上的任何更新文件。检查文件的权限和所有权,并在必要时更改它们以匹配现有日志文件。
2. 如果接受数据丢失的风险,请在提示时输入 `y` 以确认要继续;备份成为主要的。 `Force this node to become the primary` 选项等待 `60` 秒以使镜像成员成为主要节点。如果操作未在 `60` 秒内成功完成,`^MIRROR` 报告操作可能未成功并指示您检查消息日志以确定操作是失败还是仍在进行中。
3. 一旦 `^MIRROR` 例程确认备份已成为主要备份,请在可以这样做时重新启动 `IRIS A`。
- 如果 `IRIS A` 在 `IRIS` 实例重新启动时加入镜像作为备份,则不需要进一步的步骤。任何在故障成员上但不在当前主成员上的日志数据都已被丢弃。
- 如果在 `IRIS` 实例重新启动时 `IRIS A` 无法加入镜像,如重建镜像成员中描述的引用不一致数据的消息日志消息所示 `IRIS A` 上的最新数据库更改晚于最新的日志数据当 `IRIS B` 被迫成为主服务器时,它会出现在 `IRIS B` 上。要解决此问题,请按照该部分中的描述重建 `IRIS A`。
文章
Michael Lei · 六月 18, 2023
在数字化时代,数据的重要性无可置疑。数据作为新型生产要素,不仅在宏观政策层面得到党和政府的大力推动,也是医院高质量发展的关键和改变医疗行业的驱动力。随着医疗信息化的迅猛发展,我们正迈向一个数据随处可及、人人可用易用的医疗信息化时代。这一时代将数据与人的需求相结合,致力于让数据能“主动”找到需要他们的医护人员和患者,每一个行业从业者,都应致力于为医护人员和患者提供简单易用的软件解决方案,减少工作量,提高效率,推动医疗行业的进步。
数据与人的融合是实现医疗行业数字化转型的核心。当然,医疗数据的收集、存储和管理对于提供高质量的医疗服务至关重要。然而,仅仅有大量的数据并不足够,我们需要将数据与人的需求紧密结合起来。这意味着我们应该让更多的数据关联起来,并且能服务于更多的人群,让患者能够随时随地访问他们的电子病历,让医生和科研人员也能及时有效地获取病人在医院围墙内外进行治疗和健康管理的数据,并且以直观易懂的方式呈现给医护人员和患者,使他们能够快速、准确地获取所需的信息。数据的融合还包括将不同来源的数据整合起来,为医护人员提供全面、完整的视图,同时基于医疗诊断的规则,不管是通过CDSS的形式,还是通过ChatBot(聊天机器人),帮助他们做出更好的决策。
实现数据和人的融合要按照人的需求投放数据。数字化转型的重要目标是为医护人员和患者提供所需的数据,以支持决策和治疗过程。这意味着我们应该了解用户的需求,将数据按照他们的角色、职责和关注点进行分类和投放。医生可能需要即时的患者数据、病历历史和最新的医学研究,而患者可能需要查看自己的健康记录、预约医生和接收个性化的健康建议。通过根据人的需求进行数据投放,新型软件可以提供个性化的服务和支持,形成千人千面,为每个用户提供有价值的信息。
简单易用是实现数字化转型成功的另一个关键。医护人员和患者使用的软件解决方案应该简单易用,不需要复杂的培训和技术知识。界面应该简单、直观、友好,操作流程简化和优化,以确保用户能够快速上手并高效地使用软件。简单易用的软件不仅能够减少用户的学习曲线和工作负担,还能提高用户满意度和工作效率。(比如Apple的医疗软件Apple Health,通过FHIR 技术,通过一个app能够连接数千家医院的病历数据,让患者可以通过一个app实现多家医院的互联网服务和数据整合)
无论是数字化转型还是高质量发展,软件为人服务始终是医疗信息化的核心宗旨。我们应该将软件看作是为人服务的工具,旨在帮助医护人员提供更好的医疗服务,提升患者的体验和健康结果。软件应该以用户体验为中心,并不断优化和改进,不断进行供给侧改革,以满足不断变化和不同人群的需求,而不是增加负担。
最后,数据会在安全可靠的前提下进行传递和流通。在互联网发展的早期时代,由于无法可依,野蛮生长,数据的滥用、隐私保护等存在很大问题。但随着《数据安全法》等法律法规的发布,相信未来的医疗行业数据一定会在更加安全、可靠、合规的前提下进行有序流动。
在未来的医疗信息化发展中,数据与人的关系将变得更加密不可分。通过数据的融合、按需投放、简单易用、安全可靠和以人为本的新一代软件,我们可以实现数据随处可及、人人可用易用的医疗信息化目标。这将为医护人员和患者提供更好的工作环境和医疗体验,推动整个医疗行业向前迈进。InterSystems公司作为创新性的数据平台解决方案供应商,我们始终致力于助力合作伙伴开发创新的解决方案,与合作伙伴一起共同实现这一愿景,改善医疗服务的质量和效率,提高患者体验的获得感的同时帮助医院降本增效,实现高质量发展。
文章
Jingwei Wang · 九月 16, 2022
连接前准备:
Python 开发环境
DB-API驱动:irispython wheel 文件
Connection String
步骤:
安装irispython wheel 文件
pip install intersystems_irispython-3.2.0-py3-none-any.whl
Connection String:其中import iris 用来导入iris, connection = iris.connect是connection string。connection.close()用来断开连接。
import iris
def main():
connection_string = "localhost:1972/USER"
username = "SQLAdmin"
password = "deployment-password"
connection = iris.connect(connection_string, username, password)
# when finished, use the line below to close the connection
# connection.close()
if __name__ == "__main__":
main()
文章
姚 鑫 · 四月 16, 2021
# 第二章 定义和构建索引(四)
# 位片索引
当数字数据字段用于某些数值运算时,位片索引用于该字段。位片索引将每个数值数据值表示为二进制位串。位片索引不是使用布尔标志来索引数值数据值(如在位图索引中那样),而是以二进制值表示每个值,并为二进制值中的每个数字创建一个位图,以记录哪些行的该二进制数字具有1。这是一种高度专门化的索引类型,可以显著提高以下操作的性能:
- `SUM`、`COUNT`或`AVG` Aggregate计算。(位片索引不用于`COUNT(*)`计算。)。位片索引不用于其他聚合函数。
- 指定的字段 `TOP n ... ORDER BY field`
- 在范围条件运算中指定的字段,`WHERE field > n` 或 `WHERE field BETWEEN lownum AND highnum`、
SQL优化器确定是否应该使用定义的位片索引。通常,优化器仅在处理大量(数千)行时才使用位片索引。
可以为字符串数据字段创建位片索引,但位片索引将这些数据值表示为规范数字。换句话说,任何非数字字符串(如`“abc”`)都将被索引为0。这种类型的位片索引可用于快速计数具有字符串字段值的记录,而不计算那些为空的记录。
在下面的例子中,`Salary`是位片索引的候选项:
```sql
SELECT AVG(Salary) FROM SalesPerson
```
位片索引可用于使用`WHERE`子句的查询中的聚合计算。如果`WHERE`子句包含大量记录,则这是最有效的。在下面的示例中,SQL优化器可能会使用`Salary`上的位片索引(如果已定义);如果定义了位片索引,它还会使用`REGION`上的位图索引,使用定义的位图或为`REGION`生成位图临时文件:
```sql
SELECT AVG(Salary) FROM SalesPerson WHERE Region=2
```
但是,当索引无法满足`WHERE`条件时,不使用位片索引,而必须通过读取包含要聚合的字段的表来执行。以下示例将不使用`Salary`的位片索引:
```sql
SELECT AVG(Salary) FROM SalesPerson WHERE Name LIKE '%Mc%'
```
可以为任何包含数值的字段定义位片索引。InterSystems SQL使用`Scale`参数将小数转换为位字符串,如ObjectScript `$factor`函数中所述。可以为数据类型字符串的字段定义位片索引;在这种情况下,出于位片索引的目的,非数字字符串数据值被视为`0`。
可以为系统分配的行ID为正整数值的表中的字段定义位片索引,也可以为使用`%BID`属性定义以支持位图(和位片)索引的表中的字段定义位片索引。
位片索引只能为单个字段名定义,不能为多个字段的连接定义。
不能指定`WITH DATA`子句。
下面的例子比较了位片索引和位图索引。
如果你为1、5和22行创建一个位图索引,它会为这些值创建一个索引:
```java
^gloI("bitmap",1,1)= "100"
^gloI("bitmap",5,1)= "010"
^gloI("bitmap",22,1)="001"
```
如果为第1、2和3行的值1、5和22创建位切片索引,则会首先将这些值转换为位值:
```java
1 = 00001
5 = 00101
22 = 10110
```
然后,它为这些位创建索引:
```java
^gloI("bitslice",1,1)="110"
^gloI("bitslice",2,1)="001"
^gloI("bitslice",3,1)="011"
^gloI("bitslice",4,1)="000"
^gloI("bitslice",5,1)="001"
```
在本例中,位图索引中的值22需要设置1个全局节点;位片索引中的值22需要设置3个全局节点。
请注意,插入或更新需要在所有`n`个位片中设置一个位,而不是设置单个位串。这些附加的全局设置操作可能会影响涉及填充位片索引的插入和更新操作的性能。使用`INSERT`、`UPDATE`或`DELETE`操作填充和维护位片索引比填充位图索引或常规索引慢。维护多个位片索引和/或在频繁更新的字段上维护位片索引可能具有显著的性能成本。
在易失性表(执行许多插入、更新和删除操作)中,位片索引的存储效率可能会逐渐降低。`%SYS.Maint.Bitmap`实用程序方法同时压缩位图索引和位片索引,从而提高了还原效率。
# 重建索引
可以按如下方式构建/重新构建索引:
- 使用`BUILD INDEX` SQL命令构建指定索引,或构建为表、架构或当前命名空间定义的所有索引。
- 使用管理门户重建指定类(表)的所有索引。
- 使用`%BuildIndices()`(或`%BuildIndicesAsync()`)方法,如本节所述。
当前数据库访问确定应如何重建现有索引:
- 非活动系统(在索引构建或重建期间没有其他进程访问数据)
- `READONLY`活动系统(能够在索引构建或重建期间查询数据的其他进程)
- 读写活动系统(能够在索引构建或重建期间修改数据和查询数据的其他进程)
构建索引的首选方法是使用`%BuildIndices()`方法或`%BuildIndicesAsync()`方法。
- `%Library.Persistent.%BuildIndices()`:`%BuildIndices()`作为后台进程执行,但调用方必须等待`%BuildIndices()`完成才能接收回控制。
- `%Library.Persistent.%BuildIndicesAsync()`:`%BuildIndicesAsync()`将`%BuildIndices()`作为后台进程启动,调用方立即收到控制权。`%BuildIndicesAsync()`的第一个参数是`eueToken`输出参数。其余参数与`%BuildIndices()`相同。
`%BuildIndicesAsync()`返回`%Status`值:`Success`表示`%BuildIndices()`辅助作业已成功排队;失败表示该辅助作业未成功排队。
`%BuildIndicesAsync()`向`eueToken`输出参数返回一个值,该值指示`%BuildIndices()`完成状态。要获取完成状态,请通过引用将`eueToken`值传递`给%BuildIndicesAsyncResponse()`方法。还可以指定等待布尔值。如果`wait=1`,则`%BuildIndicesAsyncResponse()`将等待,直到由`eueToken`标识的`%BuildIndices()` JOB 完成。如果`wait=0`,`%BuildIndicesAsyncResponse()`将尽快返回状态值。如果返回时`%BuildIndicesAsyncResponse() ``eueToken`不为空,则`%BuildIndices()` job尚未完成。在这种情况下,可以使用`eueToken`再次调用`%BuildIndicesAsyncResponse()`。当`%BuildIndicesAsyncResponse()``eueToken`最终为`NULL`时,返回的`%BuildIndicesAsyncResponse()``%Status`值是`%BuildIndicesAsync()`调用的job的完成状态。
## 在非活动系统上构建索引
系统自动生成方法(由`%Persistent`类提供),这些方法构建或清除为类(表)定义的每个索引。可以通过以下两种方式之一使用这些方法:
- 通过管理门户进行交互。
- 以编程方式,作为方法调用。
构建索引执行以下操作:
1. 删除索引的当前内容。
2. 扫描(读取每一行)主表,并为表中的每一行添加索引项。如果可能,使用特殊的`$SortBegin`和`$SortEnd`函数来确保高效地构建大型索引。在构建标准索引时,除了在内存中缓存数据之外,使用`$SortBegin`/`$SortEnd`还可以使用`IRISTEMP`数据库中的空间。因此,在构建非常大的标准索引时,InterSystems IRIS可能需要`IRISTEMP`中大致等于最终索引大小的空间。
注:构建索引的方法仅为使用InterSystems IRIS默认存储结构的类(表)提供。映射到遗留存储结构的类不支持索引构建,因为它假定遗留应用程序管理索引的创建。
### 使用管理门户构建索引
可以通过执行以下操作来构建表的现有索引(重建索引):
1. 从管理门户中选择系统资源管理器,然后选择SQL。使用页面顶部的切换选项选择一个命名空间;这将显示可用命名空间的列表。选择命名空间后,选择屏幕左侧的`Schema`下拉列表。这将显示当前名称空间中的模式列表,其中带有布尔标志,指示是否有任何表或视图与每个模式相关联。
2. 从此列表中选择一个架构;该架构将显示在架构框中。它的正上方是一个下拉列表,允许选择属于该模式的表、系统表、视图、过程或所有这些。选择“表”或“全部”,然后打开“表”文件夹以列出此架构中的表。如果没有表,则打开文件夹将显示空白页。(如果未选择“表”或“全部”,则打开“表”文件夹将列出整个命名空间的表。)
3. 选择其中一个列出的表。这将显示表的目录详细信息。
- 要重建所有索引:单击操作下拉列表,然后选择重建表的索引。
- 要重建单个索引:单击索引按钮以显示现有索引。每个列出的索引都有重建索引的选项。
**注意:当其他用户正在访问表的数据时,不要重建索引。要在活动系统上重建索引,请参阅在活动系统上构建索引。**
### 以编程方式构建索引
为非活动表构建索引的首选方法是使用随表的`Persistent`类提供的`%BuildIndices()`(或`%BuildIndicesAsync()`)方法。
**若要以编程方式生成一个或多个索引,请使用`%Library.Persistent.%BuildIndices()`方法。**
生成所有索引:调用`%BuildIndices()`,不带参数生成为给定类(表)定义的所有索引(为其提供值):
```java
SET sc = ##class(MyApp.SalesPerson).%BuildIndices()
IF sc=1 {
WRITE !,"成功构建索引"
} ELSE {
WRITE !,"索引构建失败",!
DO $System.Status.DisplayError(sc) QUIT
}
```
生成指定索引:调用`%BuildIndices()`,并将`$LIST`索引名作为第一个参数,为给定类(表)生成指定的已定义索引(为其提供值):
```java
SET sc = ##class(MyApp.SalesPerson).%BuildIndices($ListBuild("NameIDX","SSNKey"))
IF sc=1 {
WRITE !,"成功构建索引"
} ELSE {
WRITE !,"索引构建失败",!
DO $System.Status.DisplayError(sc) QUIT
}
```
生成除以下项之外的所有索引:调用`%BuildIndices()`,并将索引名称的`$LIST`作为第七个参数来构建(为其提供值)给定类(表)的所有已定义索引(指定索引除外):
```java
SET sc = ##class(MyApp.SalesPerson).%BuildIndices("",,,,,,$ListBuild("NameIDX","SSNKey"))
IF sc=1 {
WRITE !,"成功构建索引"
} ELSE {
WRITE !,"索引构建失败",!
DO $System.Status.DisplayError(sc) QUIT
}
```
`%BuildIndices()`方法执行以下操作:
1. 对要重建的任何(非位图)索引调用`$SortBegin`函数(这将启动对这些索引的高性能排序操作)。
2. 循环遍历类(表)的主要数据,收集索引使用的值,并将这些值添加到索引(通过适当的排序转换)。
3. 调用`$SortEnd`函数来完成索引排序过程。
如果索引已经有值,则必须使用两个参数调用`%BuildIndices()`,其中第二个参数的值为1。
为此参数指定1将导致该方法在重新生成值之前清除这些值。
例如:
```java
SET sc = ##class(MyApp.SalesPerson).%BuildIndices(,1)
IF sc=1 {
WRITE !,"成功构建索引"
} ELSE {
WRITE !,"索引构建失败",!
DO $System.Status.DisplayError(sc) QUIT
}
```
清除并重建所有的索引。
你也可以清除并重建索引的子集,例如:
```java
SET sc = ##class(MyApp.SalesPerson).%BuildIndices($ListBuild("NameIDX","SSNKey"),1)
IF sc=1 {
WRITE !,"成功构建索引"
} ELSE {
WRITE !,"索引构建失败",!
DO $System.Status.DisplayError(sc) QUIT
}
```
注意:当表的数据被其他用户访问时,不要重建索引。
若要在活动系统上重建索引,请参见在活动系统上构建索引。
## 在活动系统上构建索引
在活动系统上构建(或重建)索引时,有两个问题:
- 除非正在构建的索引对`SELECT` `Query`隐藏,否则活动`Query`可能返回不正确的结果。这是在构建索引之前使用`SetMapSelecability()`方法处理的。
- 索引构建期间对数据的活动更新不会反映在索引条目中。这是通过在生成索引时使生成操作锁定单个行来处理的。
**注意:如果应用程序在单个事务内对数据执行大量更新,则可能会出现锁表争用问题。**
### 在Readonly主动系统上构建索引
如果表当前仅用于查询操作(`READONLY`),则可以在不中断查询操作的情况下构建新索引或重建现有索引。这是通过在重建索引时使索引对查询优化器不可用来实现的。
如果要为其构建一个或多个索引的所有类当前都是`READONLY`,请使用“在读写活动系统上构建索引”中描述的相同系列操作,但有以下区别:使用`%BuildIndices()`时,设置`pLockFlag=3`(共享区锁定)。
### 在读写活动系统上构建索引
如果持久化类(表)当前正在使用并且可用于读写访问(查询和数据修改),则可以在不中断这些操作的情况下构建新索引或重建现有索引。如果要为其重建一个或多个索引的类当前可读写访问,则构建索引的首选方法是使用与表的持久类一起提供的`%BuildIndices()`(或`%BuildIndicesAsync()`)方法。
**注意:以下信息适用于动态SQL查询,而不适用于嵌入式SQL。嵌入式SQL在编译时(而不是在运行时)检查`MapSelecability`设置。因此,关闭索引的`MapSelecability`对已经编译的嵌入式SQL查询没有任何影响。因此,嵌入式SQL查询仍可能尝试使用禁用的索引,并将给出不正确的结果。**
在并发读写访问期间,需要执行以下一系列操作来构建一个或多个索引:
1. 望构建的索引对查询不可用(读取访问权限)。这是使用`SetMapSelecability()`完成的。这使得查询优化器无法使用该索引。在重建现有索引和创建新索引时都应执行此操作。例如:
```java
SET status=$SYSTEM.SQL.Util.SetMapSelectability("Sample.MyStudents","StudentNameIDX",0)
```
- 第一个参数是`Schema.Table`名称,它是`SqlTableName`,而不是持久类名称。例如,默认模式是`SQLUser`,而不是`User`。该值区分大小写。
- 第二个参数是SQL索引映射名称。这通常是索引的名称,指的是磁盘上存储索引的名称。对于新索引,这是在创建索引时将使用的名称。该值不区分大小写。
- 第三个参数是`MapSelecability`标志,其中0将索引映射定义为不可选择(`OFF`),1将索引映射定义为可选择(`ON`)。指定0。
可以通过调用`GetMapSelecability()`方法来确定索引是否不可选。如果已将索引显式标记为不可选,则此方法返回0。在所有其他情况下,它返回1;它不执行表或索引是否存在的验证检查。请注意,`Schema.Table`名称是`SqlTableName`,并且区分大小写。
`SetMapSelecability()`和`GetMapSelecability()`仅适用于当前命名空间中的索引映射。如果该表映射到多个命名空间,并且需要在每个命名空间中构建索引,则应该在每个命名空间中调用`SetMapSelecability()`。
2. 在索引构建期间建立并发操作:
- 对于新索引:在类中创建索引定义(或在类的`%Storage.SQL`中创建新的SQL Index Map规范)。编译类。此时,索引存在于表定义中;这意味着对象保存、SQL `INSERT`操作和SQL `UPDATE`操作都记录在索引中。但是,由于在步骤1中调用了`SetMapSelecability()`,因此不会为任何数据检索选择此索引映射。`SetMapSelecability()`阻止查询使用区索引,但是数据映射将被投影到SQL以使用索引全局和数据全局。对于新索引,这是合适的,因为索引尚未填充。在对表运行查询之前,需要填充区索引。
- 对于现有索引:清除任何引用该表的缓存查询。索引构建执行的第一个操作是终止索引。因此,在重新生成索引时,不能依赖任何经过优化以使用该索引的代码。
3. 使用`pLockFlag=2`(行级锁定)的持久化类(表)的`%BuildIndices()`方法构建一个或多个索引。`PLockFlag=2`标志在重建过程中在单个行上建立独占写锁,以便并发数据修改操作与构建索引操作相协调。
默认情况下,`%BuildIndices()`构建为持久类定义的所有索引;可以使用`pIgnoreIndexList`从重建中排除索引。
默认情况下,`%BuildIndices()`为所有`ID`构建索引项。但是,可以使用`pStartID`和`pEndID`来定义`ID`范围。`%BuildIndices()`将仅为该范围内(含)的ID构建索引项。例如,如果使用带有`%NOINDEX`限制的`INSERT`将一系列新记录添加到表中,则可以稍后使用具有ID范围的`%BuildIndices()`为这些新记录构建索引项。还可以使用`pStartID`和`pEndID`在节中构建极大的索引。
`%BuildIndices()`返回`%Status`值。如果`%BuildIndices()`因检索数据时出现问题而失败,系统将生成一个`SQLCODE`错误和一条消息(`%msg`),其中包含遇到错误的`%ROWID`。
4. 构建完索引后,启用映射以供查询优化器选择。将第三个参数`MapSelecability`标志设置为1,如下例所示:
```java
SET status=$SYSTEM.SQL.Util.SetMapSelectability("Sample.MyStudents","StudentNameIDX",1)
```
5. 再次清除引用该表的所有缓存查询。这将消除在此程序中创建的缓存查询,这些查询无法使用索引,因此不如使用索引的相同查询最佳。
这就完成了这个过程。索引已完全填充,查询优化器能够考虑该索引。
注意:`%BuildIndices()`只能用于重建`ID`值为正整数的表的索引。如果父表具有正整数`ID`值,还可以使用`%BuildIndices()`重建子表中的索引。对于其他表,请使用`%ValidateIndices()`方法,如验证索引中所述。因为`%ValidateIndices()`是构建索引的最慢方法,所以只有在没有其他选项的情况下才应该使用它。
文章
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`单元测试生成的测试报告。用户可以通过单击页面上的超链接深入查看提供有关测试的更多详细信息的页面。

# 测试优先方法论
敏捷软件方法论,例如测试驱动开发(TDD)和极限编程,特别强调单元测试。事实上,这些方法使用单元测试来驱动开发过程。他们提倡“测试优先”的软件开发方法。在这种方法中,开发人员在编写代码模块的一行代码之前设计并编写代码模块的单元测试。然后,开发人员创建代码模块,目标是通过单元测试。
`Test First`方法的倡导者声称该方法具有以下好处:
- 它迫使开发人员在开发任何模块之前很久就决定代码模块的正确输入和输出。
- 它集中了开发人员在创建代码模块时的注意力。开发人员关注的是在创建模块时通过单元测试的具体目标。
- 它可以防止单元测试成为事后的想法。如果首先创建单元测试,则在项目结束之前不能忽略单元测试。
- 它确保了代码的高度测试覆盖率。
注意:测试优先开发的支持者通常主张在代码模块之前执行单元测试,而不仅仅是创建单元测试。当然,在这一点上测试应该会失败。他们甚至可能不会编译。
# Red – Green – Refactor
`XUnit`和`%UnitTest`测试报告GUI报告以绿色表示通过测试,以红色表示未通过测试。下面是使用测试优先开发方法的开发节奏:
1. 红色 - 编写一个不起作用的小测试,也许一开始不会编译。
2. 绿色 - 让测试快速运行,在测试过程中犯下所有必要的错误。
3. 重构 - 消除仅在使测试正常工作时产生的所有重复。
> Kent Beck,《测试驱动的设计》
文章
姚 鑫 · 六月 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远程备份数据库的方法?
文章
姚 鑫 · 六月 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
}
```
文章
姚 鑫 · 七月 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` 实现(可选)是定义查询的零行或多行代码。
文章
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强了很多 这个极实用,推荐大伙收藏。 实用,收藏
文章
姚 鑫 · 六月 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` 比较运算符将字符串与指定的字符类型模式匹配。
文章
姚 鑫 · 八月 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` 区域设置 — 国家语言支持区域设置。
- 此会话的首选语言 - 管理门户已本地化并可显示的语言的下拉列表。可以通过从下拉列表中选择新的语言来更改显示语言。最初,浏览会话的首选语言是为浏览器指定的语言,如果不支持浏览器语言,则为英语;在特定浏览器中选择首选语言后,即使更改了浏览器语言,该浏览器中的管理门户也会使用该语言。
文章
王喆 👀 · 九月 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)实现全文检索
文章
姚 鑫 · 八月 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`存储过程不可用。