清除过滤器
文章
姚 鑫 · 五月 23, 2021
# 第四章 收发电子邮件
本主题描述如何使用InterSystems IRIS发送和接收`MIME`电子邮件消息。
注意:本主题中的示例是经过组织的,因此管理电子邮件的方法可以用于不同的电子邮件服务器,这在测试和演示期间非常有用。这不一定是最适合生产需要的代码组织。
# 支持电子邮件协议
电子邮件使用标准协议通过Internet发送消息。
InterSystems IRIS支持以下三种协议:
- InterSystems IRIS提供`MIME`电子邮件的对象表示形式。它支持文本和非文本附件、单部分或多部分邮件正文,以及`ASCII`和非`ASCII`字符集的标题。
- 可以通过`SMTP`服务器发送电子邮件。`SMTP`(简单邮件传输协议)是发送电子邮件的Internet标准。
- 还可以通过`POP3`从电子邮件服务器检索电子邮件,`POP3`是从远程服务器检索电子邮件的最常用标准。
注意:InterSystems IRIS不提供邮件服务器。相反,它提供了连接到邮件服务器并与之交互的功能。
# InterSystems IRIS如何表示`MIME`电子邮件
首先,了解InterSystems IRIS如何表示`MIME`电子邮件非常有用。
通常,多部分`MIME`邮件由以下部分组成:
- 一组邮件标头,每个标头都包含邮件发送到的地址等信息。这还包括整个消息的`Mime-Type`标头和`Content-Type`标头。
对于多部分消息,`Content-Type`头必须是多部分/混合或多部分的其他子类型;`MIME`标准有许多变体。
- 多个消息部分,每个消息部分由以下部分组成:
- 一组内容标头,包括`Content-Type`标头和特定于此部件的其他标头。
- 一种正文,它可以是文本或二进制,并且可以使用与其它部分的正文不同的字符集。
InterSystems IRIS使用两个类来表示电子邮件:`%Net.MailMessage`和`%Net.MailMessagePart`,即`%Net.MailMessage`的超类。下图显示了这些类之间的关系:

- 要表示普通的、由一部分组成的消息,请使用`%Net.MailMessage`
- 要表示多部分消息,请使用`%Net.MailMessage`作为父消息,并使用`%Net.MailMessagePart`的多个实例作为其部分。
# 创建由单个部分组成的电子邮件
要创建由单个部分组成的电子邮件,请使用`%Net.MailMessage`类。要创建邮件,请执行以下操作:
1. 创建`%Net.MailMessage`的实例。
提示:可以将字符集指定为`%New()`;的参数,如果这样做,则会设置消息的`CharSet`属性。
2. 设置实例的`To`、`From`和`Subject`属性。
- `To`收件人-此邮件将发送到的电子邮件地址列表。此属性是标准的InterSystems IRIS列表类;要使用它,需要使用标准列表方法:`Insert()`、`GetAt()`、`RemoveAt()`、`Count()`和`Clear()`。
- `From`发件人-此邮件的发件人电子邮件地址。
- `Subject`主题-邮件的主题(如果您使用的SMTP服务器需要该主题)。
3. 可以选择设置日期、抄送、密件抄送和其他属性。
4. 如果邮件不是纯文本,请设置以下属性以指示您要创建的邮件的类型:
- 如果这是一封HTML邮件,请将`IsHTML`属性设置为1。
- 如果这是二进制消息,请将`IsBinary`属性设置为1。
5. 若要指定消息及其标头的字符集,请根据需要设置`CharSet`属性。
重要提示:在添加消息内容之前指定字符集非常重要。
6. 添加消息内容:
- 对于纯文本或`HTML`,请使用`TextData`属性,该属性是`%FileCharacterStream`的实例。不需要指定此流的`TranslateTable`属性;当指定邮件的字符集时,该属性会自动发生。
- 对于二进制数据,请使用`BinaryData`属性,该属性是`%FileBinaryStream`的实例。
提示:指定流的`Filename`属性时,请确保使用用户有权写入的目录。
要使用这些属性,请使用标准流方法:`Write()`、`WriteLine()`、`Read()`、`ReadLine()`、`Rewind()`、`MoveToEnd()`和`Clear()`。还可以使用流的`Size`属性,该属性提供消息内容的大小。
注意:应该了解正在使用的`SMTP`服务器的要求。例如,某些`SMTP`服务器要求包含主题标头。同样,某些`SMTP`服务器不允许任意`FROM`标头。
类似地,一些`SMTP`服务器识别优先级报头,而其他服务器则识别`X-Priority`。
示例1:`CreateTextMessage()`
以下方法创建一条简单消息并为其指定地址:
```java
ClassMethod CreateTextMessage() As %Net.MailMessage
{
Set msg = ##class(%Net.MailMessage).%New()
set msg.From = "test@test.com"
Do msg.To.Insert("xxx@xxx.com")
Do msg.Cc.Insert("yyy@yyy.com")
Do msg.Bcc.Insert("zzz@zzz.com")
Set msg.Subject="subject line here"
Set msg.IsBinary=0
Set msg.IsHTML=0
Do msg.TextData.Write("This is the message.")
Quit msg
}
```
示例2:`SimpleMessage()`
在实际发送邮件时指定地。上例的以下变体生成一条没有地址的文本消息:
```java
ClassMethod SimpleMessage() As %Net.MailMessage
{
Set msg = ##class(%Net.MailMessage).%New()
Set msg.Subject="Simple message "_$h
Set msg.IsBinary=0
Set msg.IsHTML=0
Do msg.TextData.Write("This is the message.")
Quit msg
}
```
`Samples`命名空间中还有其他示例。要查找它们,请在该命名空间中搜索`%Net.MailMessage`。
# 创建多部分电子邮件
要创建由多部分组成的电子邮件,请执行以下操作:
1. 创建`%Net.MailMessage`的实例,并将其`To`、`From`和`Subject`属性设置为。可以选择设置其他属性以指定其他邮件标头。
2. 将`IsMultiPart`属性设置为1。
3. 将`MultiPartType`属性设置为以下值之一: `"related"`, `"alternative"`, 或 `"mixed"`。这会影响整个消息的`Content-Type`标头。
4. 对于邮件应包含的每个部分,创建`%Net.MailMessagePart`的实例并指定其属性,如从步骤4开始的“创建由单个部分组成的电子邮件”中所述。
5. 对于父电子邮件,设置`Parts`属性,该属性是一个数组。将每个子消息部分插入到此数组中。
发送邮件时,`%Net.SMTP`类会根据需要自动设置邮件的`Content-Type`标头(给定`MultiPartType`属性值)。
# 指定电子邮件标题
如前所述,消息本身和消息的每个部分都有一组标头。
`%Net.MailMessage`和`%Net.MailMessagePart`类提供的属性使可以轻松访问最常用的标头,但可以添加所需的任何标头。本节提供有关所有标头以及如何创建自定义标头的信息。
给定消息部分的标头使用由该部分的`CharSet`属性指定的字符集。
注意:应该了解正在使用的`SMTP`服务器的要求。例如,某些`SMTP`服务器要求包含主题标头。同样,某些`SMTP`服务器不允许任意`FROM`标头。
类似地,一些`SMTP`服务器识别优先级报头,而其他服务器则识别`X-Priority`。
# 指定基本电子邮件标题
设置以下属性(仅在`%Net.MailMessage`中)以设置邮件本身最常用的标头:
- `To`-(必填)此邮件将发送到的电子邮件地址列表。此属性是标准的InterSystems IRIS列表;要使用它,请使用标准列表方法:`Insert()`、`GetAt()`、`RemoveAt()`、`Count()`和`Clear()`。
- From-(必填)发送此邮件的电子邮件地址。
- Date-此消息的日期。
- Subject-(必选)包含此邮件主题的字符串。
- Sender-邮件的实际发件人。
- Cc-此邮件将发送到的抄送地址列表。
- Bcc-此邮件将被发送到的密件副本地址列表。
## 内容类型标题
发送邮件时,邮件和每个邮件部分的`Content-Type`标头会自动设置如下:
- 如果消息是纯文本(`IsHTML`等于0,`IsBinary`等于0),则`Content-Type`标头被设置为 `"text/plain`。
- 如果消息是`HTML`(`IsHTML`等于1,`IsBinary`等于0),则`Content-Type`标头设置为`“text/html”`。
- 如果消息是二进制的(`IsBinary`等于1),则`Content-Type`报头设置为如果消息是二进制的(`IsBinary`等于1),则`Content-Type`报头设置为`"application/octet-stream"`.
- 如果邮件是多部分邮件,则会为`MultiPartType`属性的值适当设置`Content-Type`标头。
`%Net.MailMessage`和`%Net.MailMessagePart`都提供了`contentType`属性,使可以访问`Content-Type`标头。
## 内容传输编码标头
`%Net.MailMessage`和`%Net.MailMessagePart`都提供了`ContentTransferEncoding`属性,该属性提供了一种指定消息或消息部分的`Content-Transfer-Encoding`头的简单方法。
此属性可以是以下属性之一:`"base64" "quoted-printable" "7bit" "8bit"`
默认值如下:
对于二进制消息或消息部分:`"base64"`
**重要提示:请注意,如果内容为`“Base64”`编码,则不能包含任何Unicode字符。如果要发送的内容包括Unicode字符,请确保使用`$ZCONVERT`将内容转换为`UTF-8`,然后对其进行`base-64`编码。例如:**
```java
set BinaryText=$ZCONVERT(UnicodeText,"O","UTF8")
set Base64Encoded=$system.Encryption.Base64Encode(BinaryText)
```
收件人必须使用相反的过程来解码文本:
```java
set BinaryText=$system.Encryption.Base64Decode(Base64Encoded)
set UnicodeText=$ZCONVERT(BinaryText,"I","UTF8")
```
对于文本消息或消息部分:`"quoted-printable"`
## 自定义标题
使用`%Net.MailMessage`和`%Net.MailMessagePart`,可以通过访问`Headers`属性设置或获取自定义标题,该属性是一个具有以下结构的数组:
数组键 |数组值
---|---
标头的名称,如`“Priority”` | 标头的值
此属性用于包含其他标头,如`X-Priority`和其他标头。例如:
```java
do msg.Headers.SetAt(1,"X-Priority")
do msg.Headers.SetAt("High","X-MSMail-Priority")
do msg.Headers.SetAt("High","Importance")
```
不同的电子邮件服务器和客户端可以识别不同的标头,因此设置多个相似的标头以确保服务器或客户端接收到的邮件具有它可以识别的标头是很有用的。
文章
姚 鑫 · 五月 30, 2021
# 第十一章 发送和接收IBM WebSphere MQ消息
InterSystems IRIS为IBM `WebSphere MQ`提供了一个接口,可以使用该接口在InterSystems IRIS和IBM `WebSphere MQ`的消息队列之间交换消息。要使用此接口,必须能够访问IBM `WebSphere MQ`服务器,并且IBM `WebSphere MQ`客户端必须与InterSystems IRIS在同一台计算机上运行。
该接口由`%Net.MQSend`和`%Net.MQRecv`类组成,这两个类都是`%Net.abstractMQ`的子类。这些类使用由InterSystems IRIS在所有合适的平台上自动安装的动态链接库。(这是Windows上的`MQInterface.dll`;其他平台的文件扩展名不同。)。反过来,InterSystems IRIS动态链接库需要IBM `WebSphere MQ`动态链接库。
该界面仅支持发送和接收文本数据,不支持二进制数据。
# 使用IBM WebSphere MQ的RIS接口
通常,要使用IBM `WebSphere MQ`的InterSystems IRIS接口,请执行以下操作:
1. 确保有权访问`IBM WebSphereMQv7.x`或更高版本。具体而言:
- IBM `WebSphere MQ`客户端必须与InterSystems IRIS安装在同一台计算机上。请注意,安装程序会根据需要更新`PATH`环境变量并添加其他系统变量。
- 确保在安装客户端后重新启动计算机,以便InterSystems IRIS能够识别该客户端。
- 客户端必须能够访问IBM `WebSphere MQ`服务器。
- 将用来访问服务器的用户名必须具有使用队列管理器和计划使用的队列的权限。
2. 创建`%Net.MQSend`或`%Net.MQRecv`的新实例,具体取决于要发送还是接收消息。
3. 连接到IBM `WebSphere MQ`服务器。执行此操作时,您需要提供以下信息:
- 队列管理器的名称。
- 要使用的队列的名称。
- 与该队列通信的通道。可以指定IBM `WebSphere MQ`服务器的通道名称、传输机制以及IP地址和端口。
如果正在使用IBM `WebSphere MQ`的身份验证功能,还可以提供名称和密码。
4. 调用`%Net.MQSend`或`%Net.MQRecv`的相应方法来发送或接收消息。
注意:要在64位Linux平台上使用IBM `Websphere MQ`,必须设置`LD_LIBRARY_PATH`以包括`MQ`库的位置。因为必须为任何使用`MQ`接口的InterSystems IRIS进程设置路径,所以如果正在运行后台进程,则必须在启动InterSystems IRIS之前设置该路径,并在运行IRIS终端之前在任何UNIX®终端中设置该路径。
## 获取错误代码
`%Net.MQSend`和`%Net.MQRecv`的方法如果成功则返回1,如果不成功则返回0。在出现错误的情况下,调用`%GetLastError()`方法,该方法返回IBM `WebSphere MQ`给出的最后一个原因代码。
# 创建连接对象
在可以通过IBM `WebSphere MQ`发送或接收消息之前,必须创建一个`Connection`对象,该对象可以建立到队列管理器的连接、打开通道和打开队列以供使用。有两种方法可以做到这一点:
- 可以使用`%Init`方法,该方法接受指定所有所需信息的参数。
- 可以在首次设置指定所有所需信息的属性后使用`%Connect`方法。
## 使用%Init()方法
要使用`%Init()`方法创建连接对象,请执行以下操作:
1. 创建`%Net.MQSend`(如果要发送消息)或`%Net.MQRecv`(如果要接收消息)的实例。本主题将此实例称为连接对象。
注意:如果收到 ``错误,则表示缺少动态链接库,并且`messages.log`文件(在系统管理器的目录中)有更多详细信息。
2. 如果需要身份验证,请设置`Connection`对象的以下属性:
- 用户名-指定有权使用此频道的用户名。
- 密码-指定给定用户的密码。
3. 调用`Connection`对象的`%Init()`方法。此方法按顺序接受以下参数。
a. 指定队列名称的字符串;这应该是指定队列管理器的有效队列。
b. 指定队列管理器的字符串;它应该是IBM `WebSphere MQ`服务器上的有效队列管理器。
如果省略此参数,系统将使用IBM `WebSphere MQ`中配置的默认队列管理器。或者,如果IBM `WebSphere MQ`已配置为队列管理器由队列名称确定,则系统将使用适合给定队列名称的队列管理器。
c. 指定频道规范的字符串,格式如下:
```java
"channel_name/transport/host_name(port)"
```
这里,`channel_name`是要使用的通道的名称,`Transport`是通道使用的传输,`host_name`是运行IBM `WebSphere MQ`服务器的服务器名称(或IP地址),`port`是该通道应该使用的端口。
传输可以是以下之一:`TCP`、`LU62`、`NETBIOS`、`SPX`
例如:
```java
"CHAN_1/TCP/rodan(1401)"
```
```java
"CHAN_1/TCP/127.0.0.1(1401)"
```
如果省略此参数,系统将使用IBM `WebSphere MQ`中配置的默认通道规范。或者,如果系统已配置为通道由队列名称确定,则系统使用适合给定队列名称的通道。
d. 一个可选字符串,它指定要向其中写入错误消息的日志文件。默认情况下,不进行日志记录。
4. 检查`%Init()`方法返回的值。如果该方法返回1,则表明连接已成功建立,可以使用`Connection`对象发送或接收消息(具体取决于使用的类)。
## 使用%Connect()方法
在某些情况下,可能更喜欢单独指定连接的所有详细信息。为此,请使用`%Connect()`方法,如下所示:
1. 创建`%Net.MQSend`(如果要发送消息)或`%Net.MQRecv`(如果要接收消息)的实例。如前所述,本主题将此实例称为连接对象。
注意:如果收到`` 错误,则表示缺少动态链接库,并且`messages.log`文件(在系统管理器的目录中)有更多详细信息。
2. 设置`Connection`对象的以下属性:
- `QName`-(必选)指定队列名称;这应该是指定队列管理器的有效队列。
- `QMgr`-指定要使用的队列管理器;它应该是IBM `WebSphere MQ`服务器上的有效队列管理器。
如果省略此参数,系统将使用IBM `WebSphere MQ`中配置的默认队列管理器。或者,如果IBM `WebSphere MQ`已配置为队列管理器由队列名称确定,则系统将使用适合给定队列名称的队列管理器。
3. 或者,通过设置`Connection`对象的以下属性来指定要使用的频道:
- `Connection` - 指定IBM `WebSphere MQ`服务器的主机和端口。例如:`"127.0.0.1:1401"`。
- `Channel` - 指定要使用的频道的名称。这必须是IBM WebSphere MQ服务器上的有效通道。
- `Transport` - 指定通道使用的传输。此属性可以是以下之一: `"TCP"`, `"LU62"`, `"NETBIOS"`, `"SPX"`
如果省略这些参数,系统将使用IBM `WebSphere MQ`中配置的默认通道规范。或者,如果系统已配置为通道由队列名称确定,则系统使用适合给定队列名称的通道。
4. 如果频道需要身份验证,请设置`Connection`对象的以下属性:
- 用户名-指定有权使用此频道的用户名。
- 密码-指定给定用户的密码。
5. 调用`Connection`对象的`%ErrLog()`方法。此方法接受一个参数,即要用于此连接对象的日志文件的名称。
6. 检查`%ErrLog()`方法返回的值。
7. 调用`Connection`对象的`%Connect()`方法。
8. 检查`%Connect()`方法返回的值。如果该方法返回1,则表明连接已成功建立,可以使用`Connection`对象发送或接收消息(具体取决于您使用的类)。
# 指定字符集(CCSID)
要设置用于消息转换的字符集,请调用`Connection`对象的`%SetCharSet()`方法。指定在IBM `WebSphere MQ`中使用的整数编码字符集`ID(CCSID)`。
- 如果正在发送消息,这应该是这些消息的字符集。如果不指定字符集,则MQ系统假定消息使用为`MQ`客户端指定的默认字符集。
- 如果要检索邮件,则这是要将这些邮件翻译为的字符集。
要获取当前正在使用的`CCSID`,请调用`%charset()`方法。此方法通过引用返回`CCSID`,并返回1或0以指示是否成功.
# 指定其他消息选项
要指定消息描述符选项,可以选择设置连接对象的以下属性:
- `ApplIdentityData`指定应用程序标识消息描述符选项。
- `PutApplType`指定`PUT Application Type`消息描述符选项。
## 发送消息
要发送邮件,请执行以下操作:
1. 按照“创建连接对象”中的说明创建连接对象。在这种情况下,请创建`%Net.MQSend`的实例。`Connection`对象有一个消息队列,可以向该队列发送消息。
2. 根据需要调用以下方法:
- `%put()`-给定一个字符串,此方法将该字符串写入消息队列。
- `%PutStream()`-给定初始化的文件字符流,此方法将该字符串写入消息队列。请注意,必须设置流的`Filename`属性才能对其进行初始化。不支持二进制流。
- `%SetMsgId()`-给定一个字符串,此方法使用该字符串作为发送的下一条消息的消息ID。
3. 检查调用的方法返回的值。
4. 检索完消息后,调用`Connection`对象的`%Close()`方法以释放动态链接库的句柄。
示例1:`SendString()`
下面的类方法使用队列管理器`QM_antigua`和名为 `S_antigua`的队列通道向队列`mqtest`发送一条简单的字符串消息。通道使用TCP传输,IBM `WebSphere MQ`服务器运行在名为`Antigua`的机器上,并侦听端口1401。
```java
///Method returns reason code from IBM WebSphere MQ
ClassMethod SendString() As %Integer
{
Set send=##class(%Net.MQSend).%New()
Set queue="mqtest"
Set qm="QM_antigua"
Set chan="S_antigua/TCP/antigua(1414)"
Set logfile="c:\mq-send-log.txt"
Set check=send.%Init(queue,qm,chan,logfile)
If 'check Quit send.%GetLastError()
//send a unique message
Set check=send.%Put("This is a test message "_$h)
If 'check Quit send.%GetLastError()
Quit check
}
```
示例2:`SendCharacterStream()`
下面的类方法发送文件字符流的内容。它使用的队列与上一个示例中使用的队列相同:
```java
///Method returns reason code from IBM WebSphere MQ
ClassMethod SendCharacterStream() As %Integer
{
Set send=##class(%Net.MQSend).%New()
Set queue="mqtest"
Set qm="QM_antigua"
Set chan="S_antigua/TCP/antigua(1414)"
Set logfile="c:\mq-send-log.txt"
Set check=send.%Init(queue,qm,chan,logfile)
If 'check Quit send.%GetLastError()
//initialize the stream and tell it what file to use
Set longmsg=##class(%FileCharacterStream).%New()
Set longmsg.Filename="c:\input-sample.txt"
Set check=send.%PutStream(longmsg)
If 'check Quit send.%GetLastError()
Quit check
}
```
示例3:从终端发送消息
以下示例显示了向IBM `WebSphere MQ`队列发送消息的终端会话。这只能在配置了IBM `WebSphere MQ`客户端的计算机上运行。
```java
Set MySendQ = ##class(%Net.MQSend).%New()
Do MySendQ.%Init("Q_1", "QM_1","QC_1/TCP/127.0.0.1(1401)","C:\mq.log")
Do MySendQ.%Put("Hello from tester")
Set MyRecvQ =##class(%Net.MQRecv).%New()
Do MyRecvQ.%Init("Q_1", "QM_1","QC_1","C:\mq.log")
Do MyRecvQ.%Get(.msg, 10000)
Write msg,!
```
文章
Michael Lei · 六月 21, 2022
如果您使用InterSystems技术开发了自己的网络应用,现在想在客户端进行验证码验证,以确定用户真实性使其更加安全。有一些现代框架可以解决验证码的问题,然而它们中的大多数需要互联网接入来生成代码,有时实施起来很复杂。考虑到图像识别已经非常成熟,您可以参考本文为基本例子。这就是为什么现在倾向于看到更多的模式识别验证码而不是单纯的阅读验证码。(例如,点击所有有店面的图片)。如果你需要更复杂的东西,请继续开发,改进这个代码并分享它。 继续阅读以了解如何使用这个基本的例子:
Demo.Captcha class
使用这个类,你可以在一个物理目录上创建验证码图像文件,以便在你的应用程序上显示。请注意,创建图像的目录必须是可用的,以便你的Web应用程序访问这些图像。要创建验证码图像,请调用以下方法,将完整的文件名作为一个参数:
创建 image 文件
Set tCount = $Increment(^CacheTemp("CAPTCHA",0)) Set tPath = "C:\InterSystems\Ensemble201710\CSP\user\images\captcha\"If '##class(%File).DirectoryExists(tPath) { Set tSC = ##class(%File).CreateDirectoryChain(tPath) } Set tFileName = %session.SessionId_tCount_".bmp" Set tFullName = tPath_tFileNameSet tCaptcha = ##class(Demo.Captcha).CreateImage(tFullName) Write tCaptcha,!
在 System/系统 > Security Management/安全管理 > Web Applications/Web 应用 > Edit Web Application/编辑Web应用菜单下添加“\images\captcha\” , 请注意 CSP 文件物理路径在上面代码里是一样的。
运行上面的代码来创建验证码图像后,请看一下该路径。你会看到所有生成的验证码图像如下(注意,你需要一个%session对象):
Demo.Captcha类中的CreateImage()方法也将返回生成的验证码,它将允许你在你的Web应用程序上对用户输入的验证码进行验证。
例子
为了使大家的工作简单点,我准备了一个简单的CSP文件,可以渲染一个验证码图像并进行验证。你可以导入所附的XML文件,并根据需要验证和改变路径以匹配你的CSP Web应用。
安装在USER命名空间上,打开Studio并导入XML文件;
在浏览器上打开 captcha.csp 文件;
点击 “change image/换一张” 按钮 来创建和显示新的captcha图像;
在空白输入框中输入图像代码;
点击验证按钮并检查信息;
导入类
打开 Studio;
选择 USER 命名空间;
到Tools/工具->Import Local导入本地 菜单下并选择你下载好的 captcha.xml ;
根据下图导入需要的类;
根据你的CSP Web应用改变验证码图像路径;
在浏览器通过点击浏览网页按钮 打开captcha.csp 文件;
如果你需要改变图像,点击改变图像按钮;
查看验证码图像目录;
在空白输入框中输入验证码,并点击验证;
查看结果;
随意重复这些步骤;
希望这些能帮助到您,欢迎随时联系我们.
Fábio Gonçalves
Sales Engineer - Intersystems Brazil
文章
Jingwei Wang · 九月 20, 2022
1. VMWare快照引起的网络中断导致非计划内主备机切换问题
1.1 问题描述
在创建虚拟机快照时,虚拟机需要短时间的冻结,这个短时间冻结通常指虚拟机静默,在静默过程中网络连接处于中断状态。由于数据库的不断增长,使用VMWare快照的方式对虚拟机进行备份的时长也会不断增加,当对虚拟机进行快照的时长长于InterSystems镜像服务质量 (QoS) 超时时间,即当主机网络中断超过QoS超时时间,仲裁机与备机观察并相互确认与主机的连接丢失后,将发起切换过程,使原备机成为主机并将虚拟IP从主机MAC地址解绑后重绑定到备机的MAC地址上。特别需要注意的是,在这种由于网络中断引起的切换中,仲裁与备机都无法得知主机的实际工作状态。因此,在主机网络连接恢复后,为了为了避免因切换期间主机还在处理数据导致主备机间数据不一致,备机主动发起请求让主机下线,关闭主机,此时主机状态显示为关闭(Down)。
1.2 解决方案
对于由于虚拟机快照引起的网络中断造成的非计划内主备切换问题,请参考以下两种解决方案:
使用数据库冻结/解冻脚本,具体脚本和操作方式请参考社区文章虚拟机备份和 Caché 冻结/解冻脚本,如有其他疑问请联系InterSystems WRC
在镜像集群中增加一个异步镜像成员,使用异步镜像成员创建虚拟机快照,由于此异步镜像成员也会同步获取主机数据,但是不会自动切换,所以在此异步镜像成员中创建虚拟机快照,既可以备份虚拟机,也不会引起非计划内主备机切换问题。
2. VMWare资源不足导致的主备机非计划内切换问题:
2.1 问题描述
当虚拟机的物理资源不足,可能会导致网络中断或者InterSystems IRIS 实例日志守护进程不响应超过 30 秒(或300 秒),导致主备非计划内切换。
2.2 解决方案
请扩展虚拟机的物理资源,且增加对虚拟机的状态进行监控,以防再次出现资源不足情况。
监控网络状态Log
由于网络中断造成的镜像主备切换是非计划内主备切换的重要原因之一,所以为了方便排查问题,可以在仲裁机上加一个ping的脚本,用来ping主机和备机,并将ping的结果放入log中,当发生非计划内主备切换时这个log有利于排查问题。也可以使用此ping脚本来监控网络状态,使用户能够第一时间获得网络状态信息。
文章
姚 鑫 · 五月 14, 2021
# 第六章 临时全局变量和IRISTEMP数据库
对于某些操作,可能需要全局变量的功能,而不需要无限期保存数据。例如,可能希望使用全局对某些不需要存储到磁盘的数据进行排序。对于这些操作,`InterSystems IRIS`提供了临时全局机制。该机制的工作方式如下:
- 对于应用程序名称空间,可以定义一个全局映射,以便将具有特定命名约定的全局变量映射到`IRISTEMP`数据库,该数据库是一个特殊的数据库,如下所述。
例如,可以定义一个全局映射,以便将名称为`^AcmeTemp*`的所有全局变量映射到`IRISTEMP`数据库。
- 当代码需要临时存储数据并再次读取它时,代码将向使用该命名约定的全局变量写入数据,并从全局变量读取数据。
例如,要保存值,代码可能会执行以下操作:
```java
set ^AcmeTempOrderApp("sortedarray")=some value
```
然后,稍后代码可能会执行以下操作:
```java
set somevariable = ^AcmeTempOrderApp("sortedarray")
```
**通过使用临时全局变量,可以利用`IRISTEMP`数据库没有日志记录这一事实。因为数据库没有日记记录,所以使用该数据库的操作不会产生日记文件。日志文件可能会变得很大,并可能导致空间问题**。但是,请注意以下几点:
不能回滚修改`IRISTEMP`数据库中的全局变量的任何事务;此行为特定于`IRISTEMP`。如果需要通过事务管理临时工作,请不要使用`IRISTEMP`中的全局变量来实现此目的。
请注意,仅对不需要保存的工作使用`IRISTEMP`。
# 定义临时全局变量的映射
要定义临时全局变量的映射,请执行以下操作:
1. 选择一个命名约定,并确保所有开发人员都知道这一点。请注意以下几点:
- **考虑是要有多个临时全局变量还是要少一些具有多个节点的临时全局变量。与读取或写入相同数量的独立全局变量相比,`InterSystems IRIS`更容易高效地读取或写入同一全局变量中的不同节点。这种效率差异对于少数全局变量来说可以忽略不计,但当有数百个独立的全局变量时,效率差异就非常明显。**
- 如果计划在多个名称空间中使用相同的全局映射,那么设计一个系统,使一个名称空间中的工作不会干扰另一个名称空间中的工作。例如,可以使用命名空间名称作为全局变量中的下标。
- 类似地,即使在一个命名空间内,也要设计一个系统,使代码的每个部分在同一全局中使用不同的全局或不同的下标,以避免干扰。
- 请勿使用系统保留的全局名称。
2. 在管理门户中,导航到命名空间页面(System Administration > Configuration > System Configuration > Namespaces)。
3. 在应用程序命名空间所在的行中,单击Global Mappings。
4. 在全局映射页面中,单击新建全局映射。
5. 对于全局数据库位置,选择`IRISTEMP`。
6. 对于全局名称,输入以星号(`*`)结尾的名称。不要包括名称的第一个插入符号。
例如: `AcmeTemp*`
此映射会导致名称以`AcmeTemp*`开头的所有全局变量映射到`IRISTEMP`数据库。
7. 单击OK。
注意:>> 显示在新映射行的第一列中,表示已打开映射进行编辑。
8. 要保存映射以便InterSystems IRIS使用它们,请单击保存更改。




# IRISTEMP的系统使用
请注意,InterSystems使用临时全局变量作为临时空间,例如,在执行某些查询(用于排序、分组、计算聚合等)期间用作临时索引。
IRISTEMP中包含的一些系统全局变量包括:
- `^IRIS.Temp*`
- `^mtemp*`
永远不要更改这些全局变量中的任何一个。
文章
Hao Ma · 一月 10, 2021
本文讨论 Windows 写入缓存设置,该设置会使系统在断电或操作系统崩溃的情况下容易发生数据丢失或损坏。 该设置在某些 Windows 配置中默认开启。
为磁盘启用 Windows 写入缓存意味着 Caché(或任何程序)写入该磁盘的某些内容不一定会立即提交到持久性存储(即使 Caché 在其写入阶段的特定关键点刷新从操作系统缓存到磁盘的写入也是如此)。 如果计算机断电,为该设备缓存的任何内容都会丢失,除非该设备的缓存是非易失性的或者由电池供电。 Caché 依靠操作系统来保证数据的持久性。 在这种情况下,保证是无效的。 对于 Caché 来说,这可能会导致数据库损坏或者数据库或日志文件中的数据缺失。
InterSystems 的文档显示,使“写入映像日志”提供的保证失效的一种情况是回写缓存内容丢失(请参见 [http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GCDI_wij#GCDI/wij_limits](http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GCDI_wij#GCDI_wij_limits))。 InterSystems 全球响应中心的数据完整性团队研究了许多 Windows 平台上的数据丢失或损坏案例,这些案例中有证据表明,回写缓存内容丢失是由该设置的值导致的。
值得一提的是,磁盘的缓存可能会有效防止发生此类问题。 如果相关磁盘的缓存是非易失性的或由电池供电,则即使开启该设置,写入磁盘也应该是安全的。 如果相关存储比直接连接的磁盘更复杂,您需要了解在该存储基础架构的何处对写入进行缓存,以及这些缓存是否是易失性的或者是否由电池供电,以评估风险。
您可以转到“设备管理器”,展开“磁盘驱动器”部分,然后查看给定磁盘的属性来查看设置。 我们感兴趣的设置在“策略”选项卡上。

界面上的用词并不总是与您在这里看到的相同,可能因设备类型的不同而有所不同。 不过,这是常见的用词表述,并且 Windows 明确指出,开启该设置后,如果机器断电或崩溃,系统可能会发生数据丢失。
接下来是同一机器上另一个磁盘的示例,其中的影响没有那么明显。在这里选择“更好的性能”将与在另一个示例中选择“启用写入缓存”带来相同问题。

在这两个示例中,您看到的选定设置都是该设备的默认设置,我没有更改过。 您可以看到,在第一个示例中,默认设置使设备处于风险之中,而第二个示例则没有。 据我所知,并没有通用于所有设备类型或 Windows 版本的默认设置。 换句话说,需要在每台设备上检查此设置,以了解设备是否存在此风险。
作为系统管理员,处理这种情况有三种基本方法。 禁用该设置是最简单的方法,可确保不会面临此风险。 但是,禁用该设置可能会对性能产生不可接受的影响。 如果是这种情况,您可能更愿意开启该设置,并将计算机连接到不间断电源。 这样做可以防止断电导致的数据丢失或损坏,因为 UPS 应该可以在断电时提供足够的时间让您从容地关机。 最后一个选择是简单地接受服务器断电或崩溃时数据丢失的风险。 InterSystems 建议不要采用此方式。 消费级 UPS 已相当便宜,而且检测完整性问题并从中恢复可能非常耗时又会产生问题。
InterSystems 建议您在未确保计算机连接到不间断电源的情况下不要开启此设置。 如果存储是外部设备,则该设备也需要连接到 UPS。
文章
Michael Lei · 八月 12, 2021
我最近看到一个客户问题,是使用 Caché 数据库上运行的病毒扫描程序导致应用程序间歇性变慢和用户响应时间不佳。
出乎意料的是,这是一个常见问题,所以本帖就是提个醒,要将主要 Caché 组件排除在病毒扫描之外。
通常,病毒扫描必须排除 CACHE.DAT 数据库文件和 Caché 二进制文件。 如果防病毒软件扫描 CACHE.DAT 和 InterSystems 文件,那么系统性能_将_受到较大影响。
具体来说,防病毒软件必须排除的 Caché 文件包括:
* Caché 数据库 (CACHE.DAT)。
* Ensemble/bin 或 cache/bin 目录中的 Caché 可执行文件。
* 写入映像日志 (WIJ)。
* 日志目录中的日志文件。
更多详细信息,请参见在线文档。
[更多 Caché 文档](http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GSA_thirdparty)
文章
Johnny Wang · 一月 30, 2022
我的几个孩子都很喜欢手工艺品,您可能会觉得作为一个父亲我会用夸张的表情表扬他们很有创意然后还拍照片发到Pinterest(世界上最大的图片社交分享网站)上去,但是我从来都没这么做过。我肯定爱我的孩子,我也确实认为他们有创意,但老实讲如果你有一个正上小学的孩子,你肯定很清楚他们实际的水平。
首先,要去制作一个手工艺品,你得先在油管上面看五分钟的教学视频,在此之前你可能已经浏览过大量的同类视频,你也知道那些标题党起的“五分钟教你学会”的题目就是在骗你点进去,但你可能刷着刷着手机就过去了五个钟。这类视频无非是一个人拿着一个彩色的塑料瓶,或者一个有光泽的鞋盒,把所有东西切开、用胶带和胶水粘在一起,然后你就获得了一个精美无比但是根本没用的艺术品,但至少拍照片发个朋友圈看起来还蛮不错。
第二步,你小孩看完视频了,也想自己动手做。老实讲你没法拒绝这种能够锻炼他们创意的好机会,这绝对是寓教于乐的好机会,你也把那些瓶子、纸板箱和其他的垃圾重新利用起来了。这些视频往往十分重要,这说不定会影响他们以后成为成功的设计师、建筑师或者行业精英。
再然后,关键的东西来了:胶水。
我家的手工艺品购买预算(您别说真有这么个预算)几乎都花在了买胶水或者胶带上,在过去几年中,我都在用一种叫做“和纸”的带光泽的胶带、用来打胶的胶枪和会发光的百特棒,它贴在厨房或者餐桌等等地方就会很亮。还好我现在的工作让我承担得起这些花费,但同样让人沮丧的是大部分钱都被花在胶带胶水这种辅助工具上,而不是花在提升手工艺品的质量上。
系统之间的数据流成本很高
当我看到企业架构师描述他们的环境时,我经常有一种感觉,同时这种感觉也经常让我觉得挫败。那些架构师将经过验证的开源技术(比如 Postgres 和 MySQL 或他们的云版),与一种或多种花哨的专用技术(如 Clickhouse、TimescaleDB 或 CouchBase)结合到一个数据架构中。从表面上看,这完全有道理。 我们非常感谢那些经过验证的开源技术,作为一名技术专家,我经常对这些用于特殊用途的解决方案所产生的创新印象深刻。 然而,这种方法产生的复合数据架构通常不像预期的那么简单或预算友好,这与我的孩子经常要求增加手工艺品预算的原因相同:胶水不够用了。
一个好的数据管道或 ETL 软件并不便宜,构建这些数据流的数据工程师或顾问也不便宜。 更糟糕的是,就像我孩子的胶带会磨损一样,系统之间的数据流需要昂贵的维护,因为它们需要保持运行的时间也很长,这绝不是一个5分钟的手工艺品视频讲得清楚的事情。
采用统一技术的高效数据架构
像 InterSystems IRIS 这样的统一数据平台提供了事务处理和分析工作负载的数据架构,并通过许多不同的数据模型提供高效的并发访问,所有这些都采用统一技术。 通过在 InterSystems IRIS 上构建你的应用程序、数据仓库和其他解决方案,你可以节省大量粘合和手动工作,而不会牺牲处理您的用例所要求的特殊用途数据,因为没有复制、同步或推送你的数据。 更重要的是,使用这种类型的数据架构,您最终将获得在医疗保健、金融服务和许多其他行业中已经得到证明的弹性,而不是五种不同工具的最小公分母。
回到手工艺品的话题上,我希望未来每隔一周左右我问我的孩子们的问题能够变成这样:“我不是不要你的创意; 我只是要求你少用胶水。”
关于作者:Benjamin DeBoe
Benjamin 是 InterSystems 数据平台组的产品经理,负责可扩展性和分析领域。 作为 InterSystems 收购 iKnow 的一份子,他于 2010 年加入 InterSystems,并曾使用各种数据库技术,主要在数据仓库、自然语言处理和任何分析领域。
点击查看原文链接 乐高就是不用胶水的创造,中国传统的木结构(榫卯)也是不需要胶水的,最好/稳固的创造/搭建都是不用胶水的。。。 这个类比非常到位了
文章
姚 鑫 · 三月 26, 2021
# 第十三章 使用动态SQL(四)
# 返回完整结果集
使用`%Execute()`或`%ExecDirect()`执行语句将返回一个实现`%SQL.StatementResult`接口的对象。该对象可以是单一值,结果集或从`CALL`语句返回的上下文对象。
## %Display()方法
可以通过调用`%SQL.StatementResult`类的`%Display()`实例方法来显示整个结果集(结果对象的内容),如以下示例所示:
```
DO rset.%Display()
```
请注意,`%Display()`方法不会返回%Status值。
显示查询结果集时,`%Display()`通过显示行数来结束:“受影响的5行”。 (这是`%Display()`遍历结果集之后的`%ROWCOUNT`值。)请注意,`%Display()`不会在此行计数语句之后发出行返回。
`%Display()`有两个可选参数:
- 分隔符:在数据列和数据标题之间插入的字符串。它出现在结果集列之间,紧靠标题或数据值之前。默认为无定界符。如果省略,请在“列对齐”标志之前指定一个占位符逗号。
- 列对齐:整数标志,指定如何计算数据列和数据标题之间的空格。可用的选项有:
- 0:结果集标题/数据列将根据标准定界符(选项卡)对齐。这是默认值。
- 1:结果集标题/数据列将根据列标题和标准定界符(标签)的长度对齐。
- 2:结果集标题/数据列将根据列数据属性的精度/长度和标准定界符(选项卡)进行对齐。
## `%DisplayFormatted()`方法
可以通过调用`%SQL.StatementResult`类的`%DisplayFormatted()`实例方法,而不是调用`%Display()`,将结果集内容重新格式化并重定向到生成的文件。
可以通过指定字符串选项`%DisplayFormatted(“HTML”)`或相应的整数代码`%DisplayFormatted(1)`来指定结果集格式。可以使用以下格式:XML(整数代码0),HTML(整数代码1),PDF(整数代码2),TXT(整数代码99)或CSV(整数代码100)。 (请注意,CSV格式未实现为真正的逗号分隔值输出;相反,它使用制表符来分隔列。)TXT格式(整数代码99)以行数结尾(例如,“受影响的5行”) ”);其他格式不包括行数。 InterSystems IRIS生成指定类型的文件,并附加适当的文件扩展名。
可以指定或省略结果集文件名:
- 如果指定一个目标文件(例如,`%DisplayFormatted(99,"myresults")`),则在当前命名空间的子目录的mgr目录中生成具有该名称和相应后缀(文件扩展名)的文件。
例如,`C:\InterSystems\IRIS\mgr\user\myresults.txt`.
如果具有该后缀的指定文件已经存在,则InterSystems IRIS将用新数据覆盖它。
- 如果没有指定目标文件(例如,`%DisplayFormatted(99)`,则在Temp子目录的mgr目录中生成一个具有随机生成的名称和适当后缀(文件扩展名)的文件。
例如,`C:\InterSystems\IRIS\mgr\Temp\w4FR2gM7tX2Fjs.txt.`
每次运行一个查询时,都会生成一个新的目标文件。
这些例子显示了Windows文件名;
InterSystems IRIS支持其他操作系统上的等效位置。
如果无法打开指定的文件,则此操作将在30秒后超时并显示一条错误消息;否则,该操作将超时。当用户没有对指定目录(文件夹)的`WRITE`权限时,通常会发生这种情况。
**如果无法以指定的格式呈现数据,则将创建目标文件,但不会将结果集数据写入其中。而是将适当的消息写入目标文件。例如,流字段`OID`包含与XML和HTML特殊格式字符冲突的字符。可以通过在流字段上使用`XMLELEMENT`函数来解决此XML和HTML流字段问题。例如`SELECT Name,XMLELEMENT(“ Para”,Notes)`。**
可以选择提供`%DisplayFormatted()`在执行指定格式转换时将使用的转换表的名称。
如果一个结果集序列中有多个结果集,则每个结果集的内容都将写入其自己的文件中。
可选的第三个`%DisplayFormatted()`参数指定消息存储在单独的结果集中。成功完成后,将返回类似以下的消息:
```
Message
21 row(s) affected.
```
下面的Windows示例在`C:\InterSystems\IRIS\mgr\user\`中创建了两个PDF(整数代码2)结果集文件。
它为消息创建一个mess结果集,然后使用`%Display()`将消息显示到终端:
```java
/// d ##class(PHA.TEST.SQL).CreatePDF()
ClassMethod CreatePDF()
{
SET myquery=2
SET myquery(1)="SELECT Name,Age FROM Sample.Person"
SET myquery(2)="WHERE Age > ? AND Age < ? ORDER BY Age"
SET rset = ##class(%SQL.Statement).%ExecDirect(,.myquery,12,20)
IF rset.%SQLCODE'=0 {
WRITE !,"1st ExecDirect SQLCODE=",rset.%SQLCODE,!,rset.%Message QUIT
}
DO rset.%DisplayFormatted(2,"Teenagers",.mess)
DO mess.%Display()
WRITE !,"End of teen data",!!
SET rset2 = ##class(%SQL.Statement).%ExecDirect(,.myquery,19,30)
IF rset2.%SQLCODE'=0 {
WRITE !,"2nd ExecDirect SQLCODE=",rset2.%SQLCODE,!,rset2.%Message QUIT
}
DO rset2.%DisplayFormatted(2,"Twenties",.mess)
DO mess.%Display()
WRITE !,"End of twenties data"
}
```
```java
DHC-APP>d ##class(PHA.TEST.SQL).CreatePDF()
Message
9 row(s) affected.
End of teen data
Message
20 row(s) affected.
End of twenties data
```
```java
/// d ##class(PHA.TEST.SQL).CreatePDF1()
ClassMethod CreatePDF1()
{
ZNSPACE "SAMPLES"
SET myquery=2
SET myquery(1)="SELECT Name,Age FROM Sample.Person"
SET myquery(2)="WHERE Age > ? AND Age < ? ORDER BY Age"
SET rset = ##class(%SQL.Statement).%ExecDirect(,.myquery,12,20)
DO rset.%DisplayFormatted(2,"Teenagers")
WRITE !,"End of teen data",!!
SET rset2 = ##class(%SQL.Statement).%ExecDirect(,.myquery,19,30)
DO rset2.%DisplayFormatted(2,"Twenties")
WRITE !,"End of twenties data"
}
```
```java
DHC-APP>d ##class(PHA.TEST.SQL).CreatePDF1()
End of teen data
End of twenties data
```
## 对结果集进行分页
可以使用一个视图ID (`%VID`)来分页结果集。下面的例子从结果集中返回页面,每个页面包含5行:
```java
/// d ##class(PHA.TEST.SQL).Paginating()
ClassMethod Paginating()
{
SET q1="SELECT %VID AS RSRow,* FROM "
SET q2="(SELECT Name,Home_State FROM Sample.Person WHERE Home_State %STARTSWITH 'M') "
SET q3="WHERE %VID BETWEEN ? AND ?"
SET myquery = q1_q2_q3
SET tStatement = ##class(%SQL.Statement).%New()
SET qStatus=tStatement.%Prepare(myquery)
IF qStatus'=1 {
WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT
}
FOR i=1:5:25 {
WRITE !!,"Next Page",!
SET rset=tStatement.%Execute(i,i+4)
DO rset.%Display()
}
}
```
```java
DHC-APP>d ##class(PHA.TEST.SQL).Paginating()
Next Page
RSRow Name Home_State
1 O'Rielly,Chris H. MS
2 Orwell,John V. MT
3 Zevon,Heloisa O. MI
4 Kratzmann,Emily Z. MO
5 King,Dmitry G. MO
5 Rows(s) Affected
Next Page
RSRow Name Home_State
6 Hanson,George C. MD
7 Martinez,Emilio G. MO
8 Cheng,Charlotte Y. MI
9 Emerson,Edgar T. MO
10 Nelson,Neil E. MT
5 Rows(s) Affected
Next Page
RSRow Name Home_State
11 Larson,Nataliya Z. MD
12 Lennon,Chelsea T. MD
13 Ingleman,Kristen U. MT
14 Zucherro,Olga H. MN
15 Ng,Lola H. MD
5 Rows(s) Affected
Next Page
RSRow Name Home_State
16 Frost,Xavier D. MO
17 Adams,Diane F. MD
18 Isaacs,Chad N. MN
19 Van De Griek,Phil S. MS
20 Schaefer,Usha G. MO
5 Rows(s) Affected
Next Page
RSRow Name Home_State
21 Wells,Sophia U. MS
22 Vivaldi,Michelle N. MD
23 Anderson,Valery N. MD
24 Frost,Heloisa K. MI
25 Gallant,Thelma Q. MA
5 Rows(s) Affected
```
文章
Frank Ma · 五月 24, 2022
同事们,大家好
在这篇文章中,我将告诉你我们如何将报告生成时间从28分钟减少到1分钟。让我告诉你我们是如何实现这一目标的
我希望,如果有必要,你将能够为自己重现同样的结果。这篇文章里有一些有用的链接,所以要读到最后。
让我们开始吧。
报告
我们使用Adaptive Analytics和InterSystems Reports Server为一家公司做报告。以前,这个报告是以DeepSee的屏幕截图形式生成的。总的来说,这并不坏,但它花费了大量的时间,而且看起来不是很可读。该报告本身由12页组成,为PDF格式。
一般来说,数据不是太大,不会使报告的生成花费很多时间
源数据
然而,在撰写本文时,有一个表包含11,330,263行。不是那么关键,但它造成了延迟。即使是计算行数的查询也需要近30秒
最初,系统的交互方案是这样的:
Atscale创建了自己的数据缓存,这导致了性能的提高。
Logi使用自己的数据缓存,这稍微加快了报告的开发速度。
但总的来说,这仍然导致了报告在28分钟内形成的事实。
鉴于报告只有12页,这个速度还是很慢的。
我们甚至故意添加了新的标签,并将报告中的小部件复制到那里,以便在开发或调试时不需要生成整个报告。也许这就是在Logi上开发时的一种日常技巧,或者说是一种正常的开发方法。总的来说,我们在工作中使用了它
在报告生成时,有数百个请求从Logi到Atscale,还有一些请求从Astcale到IRIS。一些单独的查询长达4分钟。有几次,请求一般都是在超时的情况下发出的。
然后我们意识到,这种情况不能再继续下去了。于是我们把自适应分析的主要功能连接起来,方案变成了这样:
这是一个UDAF功能,换句话说,这是个用户定义的聚合函数。实际上这是些汇总表,根据要求,定期地将需要测量的聚合值存储在其中。而最有趣的是,这些汇总表是由Atscale自动创建和更新的。
为了实现聚合,我们编译了isc-aa-udaf包,该包目前在一个私人仓库中,因为根据Atscale的使用条款,它不能被免费分发。
在打开汇总表后,创建服务表花了几分钟,汇总表被计算,总的来说,数据库被加载。但随后真正的解脱开始了。系统开始像它应该的那样工作。以前需要4分钟才能形成的请求,开始在5秒内形成。缓存变得更快。
结果,原先花了28分钟才形成的报告现在开始在1分钟内形成。
值得注意的是,这样的增长更多的是稳定的、生产系统的特征,在这些系统中,立方体是相对稳定的,聚集物被越来越多的收集。当立方体结构发生变化时,在对变化后的立方体的第一次请求中,聚合被重构并重新创建。
我们做了什么
我们所做的基本如下:
安装IRIS Adaptive Analytics UDAF
在IRIS中创建名称空间,用于存储聚合数据
连接IRIS 到 Atscale
定制功能安装模式=在数据仓库中启用定制管理功能
Adaptive Analytics已经用数据创建了一个立方体
在Logi中创建报告
测试和测量
这里的细节:
InterSystems IRIS已经为UDAF的工作安装了必要的类。我们已经将它们打包在一个名为isc-aa-udaf的ZPM包中,该包存储在zpm注册表pm.intersystems.com中。这个注册表对InterSystems的官方客户是可用的。
iris 命令:
zpm “install isc-aa-udaf”
一个旨在优化资源使用的可选项:我们为IRIS添加了一个专门的命名空间,它将存储预先计算的聚合值。这个命名空间的名称将在下一步中用到。
将IRIS连接到Atscale作为立方体(Cube)的数据源。转到设置(Setting),然后是数据仓库(Data Warehouses ),最后是创建数据仓库(CREATE DATA WAREHOUSE)。 在字段号1中指定了解析后的数据所存储的命名空间
在字段号2中,我们指定了在步骤2中创建的用于存储聚合的命名空间。这个值可以与1的值相同,在这种情况下,集合体将被存储在数据的旁边。在这种情况下,数据源不能是只读的。
我们将定制功能安装模式(CUSTOM FUNCTION INSTALLATION MODE)设置为用户管理(Customer Managed),因为我们之前在第一步安装了UDAF。如果你指定无(None)模式,那么即使安装了UDAF,这些功能也不会被使用,也不会有性能上的提高。 如果一切操作正确,那么UDAF检查显示绿色。
使用创建的数据仓库创建了一个项目和一个立方体。这是一个漫长而令人兴奋的过程。我不会在这里详细谈论这个问题,已经有好几篇关于它的文章,包括我的文章《如何轻松开始在Adaptive Analytics + InterSystems Reports中工作》。
Atscale上发布的项目为Logi报告创建了数据源连接。在之前的文章中,我也介绍了如何创建报告。链接为: 《如何轻松开始在Adaptive Analytics + InterSystems Reports中工作》。
测试和测量。这里很有趣。我最初设计的报告没有启用UDAF。正因为如此,一些请求被执行了4分钟或更长时间。由于该报告由12页组成,完成报告的时间平均为28分钟。
启用UDAF后,Atscale系统在自动模式下用一段时间加载数据源。她会自己计算将在报告中使用的实际查询,并为它准备预先计算的数值。此外,Intersystems报告是基于计算出来的参数,Intersystems报告本身对这些参数进行了部分缓存,AtScale系统给出了额外的优化,它缓存了执行相同查询的结果并即时返回,而不是重新发送到数据源。
在所述的捆绑工作包中,还有一个有趣的点:报告生成的频率越高,制作报告的时间就越短。
根据所有操作和几次测量的结果,生成12页报告的时间开始是60秒,也就是1分钟。
差别是28倍。
同时,类似的报告,在结构上完全相同,但从其他数据库中获取数据,其构建速度也有类似的提高。
基于我们所看到的,我们做出了一个明确的结论,推荐在所有未来的项目中使用这个捆绑包。它可以提高开发速度,提升调试速度,并减少向这些报告的商业消费者交付报告的时间。
我希望将来我们能够从IRIS - AtScale - Logi捆绑系统中提取更多的性能,并能够与你分享我们发现的解决方案。
如果你也有这类提升工具链性能的经验和我们分享,我将非常感激。
文章
Michael Lei · 八月 8, 2022
医疗行业的互操作性在改善病人护理、降低医疗服务提供者的成本以及为提供者提供更准确的情况方面发挥着重要作用。然而,由于有这么多不同的系统,数据的格式也有很多不同的方式。有许多标准被创造出来以试图解决这个问题,包括HL7v2、HL7v3和CDA,但每一种都有其缺点。
FHIR,即快速医疗互操作性资源,是一种新的医疗数据格式,旨在解决这些问题。它是由国际卫生级七组织(HL7)开发的,该组织还开发了HL7v2、HL7v3和CDA。
今天我们将探讨如何在VS代码中借助IntelliSense和自动完成功能,通过使用FHIR Schema 创建和验证FHIR资源。
第 1 步 :从FHIR 官方网站 https://www.hl7.org/fhir/下载 JSON schema file 文件用来做资源校验
第 2 步: 创建文件夹(在这个例子中,我使用病人文件夹和病人资源),并将提取的fhir.schema.json文件复制到同一文件夹,然后从VS Code中打开文件夹
第 3 步: 通过修改setting.json文件,设置VS代码以识别FHIR模式。按CTRL+SHIFT+P并输入工作区设置JSON文件
第 4 步: 在同一文件夹中创建一个新文件patient.fhir.json。按Ctrl+Space,你将通过IntelliSense获得FHIR资源的所有属性
添加资源类型 "病人",与病人资源有关的所有属性将出现在IntelliSense中。
VS Code 讲自动校验资源的结构和语法。
在IntelliSense和自动完成的帮助下,我们已经创建并验证了我们的病人资源。
第 5 步: 使用Postman的Rest API在InterSystems FHIR服务器上发布创建资源。
通过使用获取方法检索已创建的病人资源。
恭喜你!,我们已经创建、验证了我们的病人资源,并成功地使用postman发布和检索到InterSystems FHIR服务器。通过这种方式,我们可以轻松地创建和验证任何FHIR资源。
文章
Frank Ma · 七月 28, 2022
应用集成平台市场上产品众多,商家专家观点纷纭,莫衷一是。Gartner公司从用户角度出发,搭建了Peer Insight “大众点评”平台,让用户能够为自己使用的产品发声,对各个产品打分。以下是来自用户的声音,供参考。
第一款产品是微软的BizTalk,综合得分3.9。
第二款产品是InterSystems的Ensemble,综合得分4.6。
第三款产品是IBM的WebSphere Enterprise Service Bus,综合得分3.8。
BizTalk by MicroSoft
Ensemble by InterSystems
WebSphereEnterprise Services Busby IBM
总体评价:57%的用户愿意推荐该产品
总体评价:88%的用户愿意推荐该产品
总体评价:55%的用户愿意推荐该产品
分项评分
分项评分
分项评分
综合能力得分
综合能力得分
综合能力得分
评估与签约 3.3 定价灵活性
4.0 理解需求的能力
评估与签约 4.2 定价灵活性
4.6 理解需求的能力
评估与签约 3.5 定价灵活性
4.0 理解需求的能力
集成与部署 3.4 部署便利性 3.3 终端用户培训的质量 3.9 使用标准API和工具进行集成的便利性 3.1 第三方资源的可用性
集成与部署 4.3 部署便利性 4.3 终端用户培训的质量 4.3 使用标准API和工具进行集成的便利性 4.3 第三方资源的可用性
集成与部署 4.6 部署便利性 4.2 终端用户培训的质量 4.0 使用标准API和工具进行集成的便利性 4.1 第三方资源的可用性
服务与支持 4.0 供应商回应的及时性
3.7技术支持的质量
3.4 同行用户群的质量
服务与支持 4.6 供应商回应的及时性
4.6技术支持的质量
4.2 同行用户群的质量
服务与支持 4.0 供应商回应的及时性
4.3技术支持的质量
4.1 同行用户群的质量
更多信息请参考 Gartner英文原文。
公告
Claire Zheng · 七月 4, 2024
👉即日起积极参与社区互动,就有机会获得赠书《AI医疗革命》(中文版)!
Hi 开发者们,
为鼓励大家积极参与社区建设,即日起我们将持续、按月举办💡“中文社区最佳贡献奖”💡活动。
📅 活动简介
我们会在每个月的新增成员和现有成员中分别抽取一名当月积分最高的社区成员,奉上“中文社区最佳贡献奖”,奖品为《AI医疗革命》(中文繁体译版)一本。本书中文简体译版为《超越想象的GPT医疗》,原著The AI REVOLUTION IN MEDICINE(GPT-4 AND BEYOND) 出版于2023年,作者Peter Lee,Carey Goldberg,Isaac Kohane分享了以GPT-4 为代表的大语言模型在医学领域的诸多应用可能性,更重要的是向大家展现了一种可能出现的人- 机相结合的“关系”范式。本书(中文简体译版)也在CHIMA 2024期间作为优秀图书得到了推荐,值得一读。
🎉 哪些成员可以参与?
InterSystems开发者社区中文社区现有成员,以及当月新加入中文社区的成员(非InterSystems员工),均在此次活动范围。
✅如何参与?
我们欢迎您在社区进行以下互动:
发布文章/经验帖;
在Open Exchange发布应用;
在现有的帖子下,积极发布有效评论/回复;
将英文社区的文章翻译到中文社区;
邀请新成员加入开发者社区(如果您邀请了新成员,请通过站内信联系 @Xuying.Zheng 并告知)。
🎁 获奖须知
每位社区成员仅有一次获奖机会,我们会在抽奖时排除已获得过“中文社区最佳贡献奖”的成员。
我们会于次月公布上月的获奖者,通过开发者社区站内信与获奖者取得联系并进行奖品邮寄。敬请您留意。
🍀 如果您对此次活动有任何疑问,欢迎跟帖提问!🍀
文章
姚 鑫 · 四月 29, 2021
# 第九章 冻结计划
大多数SQL语句都有一个关联的查询计划。查询计划是在准备SQL语句时创建的。默认情况下,添加索引和重新编译类等操作会清除此查询计划。下次调用查询时,将重新准备查询并创建新的查询计划。冻结计划使可以跨编译保留(冻结)现有查询计划。查询执行使用冻结的计划,而不是执行新的优化并生成新的查询计划。
对系统软件的更改也可能导致不同的查询计划。通常,这些升级会带来更好的查询性能,但软件升级可能会降低特定查询的性能。冻结计划使可以保留(冻结)查询计划,以便查询性能不会因系统软件升级而改变(降级或提高)。
# 如何使用冷冻计划
使用冻结计划有两种策略-乐观策略和悲观策略:
- 乐观:如果假设更改系统软件或类定义会提高性能,请使用此策略。运行查询并冻结计划。导出(备份)冻结的计划。解冻该计划。更改软件。重新运行查询。这会产生一个新的计划。比较这两个查询的性能。如果新计划没有提高性能,可以从备份文件中导入先前冻结的计划。
- 悲观:如果假设系统软件或类定义的更改可能不会提高特定查询的性能,请使用此策略。运行查询并冻结计划。更改软件。使用`%NOFPLAN`关键字重新运行查询(这会导致冻结的计划被忽略)。比较这两个查询的性能。如果忽略冻结的计划没有提高性能,请保持冻结该计划并从查询中删除`%NOFPLAN`。
# 软件版本升级自动冻结计划
将InterSystems IRIS®Data Platform升级到新的主要版本时,现有的查询计划将自动冻结。这可确保重大软件升级永远不会降低现有查询的性能。升级软件版本后,对性能关键型查询执行以下步骤:
1. 执行计划状态为冻结/升级的查询,并监控性能。这是在软件升级之前创建的优化查询计划。
2. 将`%NOFPLAN`关键字添加到查询中,然后执行并监视性能。这将使用软件升级提供的SQL优化器优化查询计划。它不会解冻现有的查询计划。
3. 比较性能指标。
- 如果`%NOFPLAN`性能更好,则软件升级改进了查询计划。解冻查询计划。删除`%NOFPLAN`关键字。
- 如果`%NOFPLAN`性能较差,则软件升级会使查询计划降级。保持查询计划冻结状态,将查询计划从冻结/升级升级为冻结/显式。删除`%NOFPLAN`关键字。
4. 测试性能关键型查询后,可以解冻所有剩余的冻结/升级计划。
当在比最初创建计划时使用的InterSystems软件版本更新的InterSystems软件版本下准备/编译查询时,会发生这种自动冻结。例如,考虑一条在系统软件版本xxxx.1下准备/编译的SQL语句。随后升级到版本xxxx.2,再次准备/编译SQL语句。系统将检测到这是SQL语句在新版本上的第一次准备/编译,并自动将计划状态标记为冻结/升级,并将现有计划用于新的准备/编译。这确保使用的查询计划不会比以前版本的查询计划差。
只有主要版本的InterSystems系统软件升级才会自动冻结现有查询计划。维护发布版本升级不会冻结现有查询计划。例如,主要版本升级(如从2018.1升级到2019.1)将执行此操作。维护版本升级(如2018.1.0到2018.1.1)不执行此操作。
在管理门户SQL界面中,SQL语句计划状态列将这些自动冻结的计划指示为冻结/升级,计划版本指示原始计划的系统间软件版本。
可以使用`INFORMATION.SCHEMA.STATEMENTS` `Frozen=2`属性列出当前命名空间中的所有冻结/升级计划。
可以使用以下`$SYSTEM.SQL.Statement`方法冻结单个查询计划或多个查询计划:`FreezeStatement()`用于单个计划;`FreezeRelation()`用于关系的所有计划;`FreezeSchema()`用于架构的所有计划;`FreezeAll()`用于当前命名空间中的所有计划。有相应的解冻方法。
- 冻结方法可以提升(“冻结”)标记为冻结/升级到冻结/显式的查询计划。通常,可以使用此方法有选择地将适当的冻结/升级计划升级为冻结/显式,然后解冻所有剩余的冻结/升级计划。
- 解冻方法可以解冻指定范围内的冻结/升级查询计划:命名空间、架构、关系(表)或单个查询。
# 冻结计划界面
冻结计划界面有两种,用途不同:
- Management Portal SQL语句界面,用于冻结(或解冻)单个查询的计划。
- `$SYSTEM.SQL.Statement`冻结和解冻方法,用于冻结或解冻命名空间、架构、表或单个查询的所有计划。
在Management Portal SQL界面中,选择`Execute Query`选项卡。编写查询,然后单击显示计划按钮以显示当前查询执行计划。如果计划被冻结,则查询计划部分的第一行是“冻结计划”。
在管理门户SQL界面中,选择SQL语句选项卡。这将显示SQL语句列表。此列表的计划状态列指定解冻、解冻/并行、冻结/显式或冻结/升级。(如果语句没有关联的查询计划,则计划状态列为空。)
可以使用`INFORMATION.SCHEMA.STATEMENTS` Frozen属性值列出当前命名空间中所有SQL语句的计划状态:`UNFRECTED(0)`、`Frozen/EXPLICIT(1)`、`Frozen/Upgrade(2)`或`UNFORMATED/PARALLEL(3)`。
要冻结或解冻计划,请在SQL语句文本列中选择SQL语句。这将显示“SQL语句详细信息”框。在此框的底部显示对帐单文本和查询计划。如果计划未冻结,则这些横断面的背景颜色为绿色,如果计划已冻结,则背景颜色为蓝色。在其正上方的对帐单操作下,可以根据需要选择冻结计划或解冻计划按钮。然后选择关闭。
- 冻结计划按钮:单击此按钮将冻结此语句的查询优化计划。冻结计划并编译该SQL语句时,SQL编译将使用冻结的计划信息并跳过查询优化阶段。
- 解冻计划按钮:点击该按钮将删除该语句冻结的计划,该语句的新编译将进入查询优化阶段,以确定要使用的最佳计划。
还可以使用`$SYSTEM.SQL.Statement`冻结和解冻方法冻结或解冻一个或多个计划。通过指定适当的方法,可以指定冻结或解冻操作的范围:单个计划的`FreezeStatement()`;关系的所有计划的`FreezeRelation()`;架构的所有计划的`FreezeSchema()`;当前命名空间中的所有计划的`FreezeAll()`。有相应的解冻方法。
## 权限
用户只能查看他们具有`EXECUTE`权限的那些SQL语句。这既适用于Management Portal SQL语句列表,也适用于`INFORMATION.SCHEMA.STATEMENTS`类查询。
管理门户SQL语句访问要求对`%Development`资源具有`“USE”`权限。任何可以在管理门户中看到SQL语句的用户都可以冻结或解冻该语句。
对于SQL语句的目录访问,如果您具有执行该语句的权限或对`%Development`资源具有`“Use”`权限,则可以看到这些语句。
对于`$SYSTEM.SQL.Statement`冻结或解冻方法调用,必须对`%Developer`资源拥有`“U”`权限。
## 冻结计划不同
如果计划被冻结,可以确定解冻该计划是否会导致不同的计划,而无需实际解冻该计划。此信息可以帮助您确定哪些SQL语句值得使用`%NOFPLAN`进行测试,以确定解冻计划是否会带来更好的性能。
可以使用`INFORMATION.SCHEMA.STATEMENTS` `FrozenDifferent`属性列出当前命名空间中此类型的所有冻结计划。
冻结的计划可能会因以下任一操作而与当前计划不同:
- 重新编译该表或该表引用的表
- 使用`SetMapSelecability()`激活或停用索引
- 在表上运行`TuneTable`
- 升级InterSystems软件版本
重新编译会自动清除现有的缓存查询。对于其他操作,必须手动清除现有缓存查询才能使新查询计划生效。
这些操作可能会也可能不会产生不同的查询计划。有两种方法可以确定它们是否这样做:
- 手工检查个别冻结计划
- 每天自动扫描所有冻结计划
如果计划尚未由这两个操作中的任何一个检查,或者计划未冻结,则列出新计划的SQL语句列为空。解冻选中的冻结计划会将新建计划列重置为空。
## 手动冻结计划检查
在冻结计划的SQL语句详细资料页的顶部有一个检查冻结按钮。按此按钮将显示解冻不同计划复选框。如果选中此框,则解冻计划将导致不同的查询计划。
对冻结计划执行此检查冻结测试后:
- 如果选中解冻计划不同框,则列出新计划的SQL语句列包含“1”。这表明解冻计划将导致不同的计划。
- 如果未选中解冻计划不同框,则列出新计划的SQL语句列将包含“0”。这表明解冻计划不会产生不同的计划。
- 已冻结的缓存查询的New Plan为“0”;清除缓存查询,然后解冻该计划会导致SQL语句消失。
- 已冻结的`Natura`l查询在New Plan列中为空。
执行此测试后,检查冻结按钮消失。如果要重新测试冻结的计划,请选择刷新页面按钮。这将重新显示检查冻结按钮。
## 日冻结计划自动检查
InterSystems SQL每晚`2:00`自动扫描SQL语句清单中的所有冻结语句。这次扫描最多持续一个小时。如果扫描未在一小时内完成,系统会记下它停止的位置,并从该点继续进行下一次每日扫描。可以使用管理门户监视此每日扫描或强制其立即扫描:选择系统操作、任务管理器、任务计划,然后选择扫描冻结计划任务。
此扫描检查所有冻结的计划:
- 如果冻结的计划具有与当前版本相同的InterSystems软件版本,InterSystems IRIS®Data Platform将计算两个计划的引用表和时间戳的散列,以创建可能已更改的内部计划列表。对于这个子集,它然后执行两个计划的逐个字符串比较,以确定哪些计划实际上不同。如果两个计划之间有任何不同(无论有多小),它都会在列`出New Plan`列的SQL语句中用`“1”`标记SQL语句。这表明解冻计划将导致不同的查询计划。
- 如果冻结的计划具有与当前版本相同的InterSystems IRIS版本,并且两个计划的逐字符串比较完全匹配,则它会将列出新计划的SQL语句列中的SQL语句标记为`“0”`。这表明解冻计划不会导致不同的查询计划。
- 如果冻结的计划具有与当前版本(冻结/更新)不同的InterSystems软件版本,InterSystems IRIS将确定对SQL优化器逻辑的更改是否会导致不同的查询计划。如果是,它将用`“1”`标记“SQL Statements Listing New Plan”列中的SQL语句。否则,它会用`“0”`标记SQL语句`New Plan`列。
可以通过调用`INFORMATION.SCHEMA.STATEMENTS`来检查此扫描的结果。以下示例返回所有冻结计划的SQL语句,指示冻结的计划是否与未冻结的计划不同。请注意,解冻语句可以是`Frozen=0`或`Frozen=3`:
```sql
SELECT Frozen,FrozenDifferent,Timestamp,Statement FROM INFORMATION_SCHEMA.STATEMENTS
WHERE Frozen=1 OR Frozen=2
```
## 冻结计划出错
如果语句的计划被冻结,并且计划使用的定义发生了某些更改,从而导致计划无效,则会发生错误。例如,如果从语句`PLAN`使用的类中删除了索引:
- 该声明的计划仍处于冻结状态。
- 在“SQL语句详细信息”页上,“编译设置”区域显示“计划错误”字段。例如,如果查询计划使用索引名`indxdob`,然后您修改了类定义以删除索引`indxdob`,则会显示如下消息: `Map 'indxdob' not defined in table 'Sample.Mytable', but it was specified in the frozen plan for the query`.
- 在SQL语句详细资料页上,查询计划区域显示由于冻结计划中的错误而无法确定计划。
如果在冻结计划处于错误状态时重新执行查询,则InterSystems IRIS不使用冻结计划。相反,系统会创建一个新的查询计划,该计划将在给定当前定义的情况下工作,并执行查询。此查询计划被分配了与前一个查询计划相同的缓存查询类名。
在计划解冻或修改定义以使计划返回有效状态之前,出错的计划将一直处于错误状态。
如果修改定义以使计划返回有效状态,请转到SQL语句详细资料页,然后按清除错误按钮以确定是否已更正错误。如果更正,计划错误字段将消失;否则将重新显示计划错误消息。如果已更正定义,则不必显式清除计划错误,SQL即可开始使用冻结计划。如果已更正定义,则清除错误按钮会使SQL语句详细资料页的冻结查询计划区域再次显示执行计划。
计划错误可能是 `“soft error.”`。当计划使用索引,但查询优化器当前无法选择该索引时,可能会出现这种情况,因为`SetMapSelecability()`已将其可选择性设置为`0`。这样做可能是为了[重建]索引。当InterSystems IRIS遇到具有冻结计划的语句的软错误时,查询处理器会尝试自动清除错误并使用冻结计划。如果该计划仍然出错,则该计划将再次标记为出错,并且查询执行将尽可能使用最佳计划。
# %NOFPLAN关键字
可以使用`%NOFPLAN`关键字覆盖冻结的计划。包含`%NOFPLAN`关键字的SQL语句将生成新的查询计划。冻结的计划将保留,但不会使用。这允许测试生成的计划行为,而不会丢失冻结的计划。
```sql
DECLARE CURSOR FOR SELECT %NOFPLAN ...
SELECT %NOFPLAN ....
INSERT [OR UPDATE] %NOFPLAN ...
DELETE %NOFPLAN ...
UPDATE %NOFPLAN
```
在`SELECT`语句中,`%NOFPLAN`关键字只能在查询中的第一个`SELECT`之后立即使用:它只能与`UNION`查询的第一个分支一起使用,不能在子查询中使用。`%NOFPLAN`关键字必须紧跟在`SELECT`关键字之后,位于`DISTINCT`或`TOP`等其他关键字之前。
# 导出和导入冻结计划
可以将SQL语句作为`XML`格式的文本文件导出或导入。这使可以将冻结的计划从一个位置移动到另一个位置。SQL语句导出和导入包括关联查询计划的编码版本和指示该计划是否冻结的标志。
文章
姚 鑫 · 三月 23, 2021
# 第十三章 使用动态SQL(一)
# 动态SQL简介
动态SQL是指在运行时准备并执行的SQL语句。在动态SQL中,准备和执行SQL命令是单独的操作。通过动态SQL,可以以类似于ODBC或JDBC应用程序的方式在InterSystems IRIS中进行编程(除了要在与数据库引擎相同的进程上下文中执行SQL语句)。动态SQL是从ObjectScript程序调用的。
动态SQL查询是在程序执行时准备的,而不是在编译时准备的。这意味着编译器无法在编译时检查错误,并且不能在Dynamic SQL中使用预处理器宏。这也意味着执行程序可以响应用户或其他输入而创建专门的Dynamic SQL查询。
动态SQL可用于执行SQL查询。它也可以用于发出其他SQL语句。本章中的示例执行SELECT查询。
动态SQL用于执行InterSystems IRIS SQL Shell,InterSystems IRIS管理门户网站“执行查询”界面,SQL代码导入方法以及“数据导入和导出实用程序”。
在Dynamic SQL(和使用它的应用程序)中,行的最大大小为`3,641,144`个字符。
## 动态SQL与嵌入式SQL
动态SQL与嵌入式SQL在以下方面有所不同:
- **动态SQL查询的初始执行效率比嵌入式SQL稍低,因为它不会生成查询的内联代码。但是,动态SQL和嵌入式SQL的重新执行比第一次执行查询要快得多,因为它们都支持缓存的查询。**
- 动态SQL可以通过两种方式接受输入到查询的文字值:使用`“?”`指定的输入参数。字符和输入主机变量(例如`:var`)。嵌入式SQL使用输入和输出主机变量(例如`:var`)。
- 使用结果集对象(即`Data`属性)的API检索动态SQL输出值。嵌入式SQL将主机变量(例如`:var`)与`SELECT`语句的`INTO`子句一起使用以输出值。
- **动态SQL设置`%SQLCODE`,`%Message`,`%ROWCOUNT`和`%ROWID`对象属性。嵌入式SQL设置相应的`SQLCODE`,`%msg`,`%ROWCOUNT`和`%ROWID`局部变量。动态SQL不会为`SELECT`查询设置`%ROWID`;嵌入式SQL为基于游标的`SELECT`查询设置`%ROWID`。**
- 动态SQL提供了一种简单的方法来查找查询元数据(例如列的数量和名称)。
- 动态SQL执行SQL特权检查;必须具有适当的权限才能访问或修改表,字段等。Embedded SQL不执行SQL特权检查。
- 动态SQL无法访问私有类方法。要访问现有的类方法,必须将该方法公开。这是一般的SQL限制。但是,嵌入式SQL克服了此限制,因为嵌入式SQL操作本身是同一类的方法。
动态SQL和嵌入式SQL使用相同的数据表示形式(默认情况下为逻辑模式,但是可以更改)和NULL处理。
# `%SQL.Statement`类
动态SQL的首选接口是`%SQL.Statement`类。要准备和执行动态SQL语句,请使用`%SQL.Statement`的实例。执行动态SQL语句的结果是一个SQL语句结果对象,该对象是`%SQL.StatementResult`类的实例。 SQL语句结果对象可以是单一值,结果集或上下文对象。在所有情况下,结果对象都支持标准接口。每个结果对象都会初始化`%SQLCODE`,`%Message`和其他结果对象属性;这些属性设置的值取决于发出的SQL语句。对于成功执行的`SELECT`语句,对象是结果集(特别是`%SQL.StatementResult`的实例),并且支持预期的结果集功能。
以下ObjectScript代码准备并执行动态SQL查询:
```java
/// d ##class(PHA.TEST.SQL).DynamicSQL()
ClassMethod DynamicSQL()
{
/* 简单的%SQL.Statement示例 */
SET myquery = "SELECT TOP 5 Name,DOB FROM Sample.Person"
SET tStatement = ##class(%SQL.Statement).%New()
SET qStatus = tStatement.%Prepare(myquery)
IF qStatus'=1 {
WRITE "%Prepare 失败"
DO $System.Status.DisplayError(qStatus)
QUIT
}
SET rset = tStatement.%Execute()
DO rset.%Display()
WRITE !,"End of data"
}
```
```java
DHC-APP>d ##class(PHA.TEST.SQL).DynamicSQL()
Name DOB
yaoxin 54536
xiaoli
姚鑫 63189
姚鑫 63189
姚鑫 50066
5 Rows(s) Affected
End of data
```
本章中的示例使用与`%SQL.Statement`和`%SQL.StatementResult`类关联的方法。
# 创建一个对象实例
可以使用`%New()`类方法创建`%SQL.Statement`类的实例:
` SET tStatement = ##class(%SQL.Statement).%New()`
此时,结果集对象已准备好准备SQL语句。创建`%SQL.Statement`类的实例后,可以使用该实例发出多个动态SQL查询和/或`INSERT`,`UPDATE`或`DELETE`操作。
`%New()`按以下顺序接受三个可选的逗号分隔参数:
1. `%SelectMode`,它指定用于数据输入和数据显示的模式。
2. `%SchemaPath`,它指定用于为无限定的表名提供架构名称的搜索路径。
3. `%Dialect`,它指定Transact-SQL(TSQL)Sybase或MSSQL方言。默认值为IRIS(InterSystems SQL)。
还有一个`%ObjectSelectMode`属性,不能将其设置为`%New()`参数。 `%ObjectSelectMode`指定字段到其相关对象属性的数据类型绑定。
在下面的ObjectScript示例中,`%SelectMode`为2(显示模式),`%SchemaPath`将`“Sample”`指定为默认架构:
```java
SET tStatement = ##class(%SQL.Statement).%New(2,"Sample")
```
在下面的ObjectScript示例中,未指定`%SelectMode`(请注意占位符逗号),并且`%SchemaPath`指定包含三个架构名称的架构搜索路径:
```java
SET tStatement = ##class(%SQL.Statement).%New(,"MyTests,Sample,Cinema")
```
## %SelectMode属性
`%SelectMode`属性指定以下模式之一:`0 =Logical逻辑(默认)`,`1 = ODBC`,`2 =Display.显示`。这些模式指定如何输入和显示数据值。模式最常用于日期和时间值以及显示`%List`数据(包含编码列表的字符串)。数据以逻辑模式存储。
`SELECT`查询使用`%SelectMode`值确定用于显示数据的格式。
`INSERT`或`UPDATE`操作使用`%SelectMode`值来确定允许的数据输入格式。
`%SelectMode`用于数据显示。 SQL语句在内部以逻辑模式运行。例如,无论`%SelectMode`设置如何,`ORDER BY`子句均根据记录的逻辑值对记录进行排序。 SQL函数使用逻辑值,而不管`%SelectMode`设置如何。映射为SQLPROC的方法也可以在逻辑模式下运行。在SQL语句中称为函数的SQL例程需要以逻辑格式返回函数值。
- 对于`SELECT`查询,`%SelectMode`指定用于显示数据的格式。将`%SelectMode`设置为ODBC或Display也会影响用于指定比较谓词值的数据格式。某些谓词值必须以`%SelectMode`格式指定,而其他谓词值必须以逻辑格式指定,而与`%SelectMode`无关。
- `%SelectMode = 1(ODBC)`中的时间数据类型数据可以显示小数秒,这与实际的ODBC时间不同。 InterSystems IRIS Time数据类型支持小数秒。相应的ODBC TIME数据类型(TIME_STRUCT标准标头定义)不支持小数秒。 ODBC TIME数据类型将提供的时间值截断为整秒。 ADO DotNet和JDBC没有此限制。
- `%SelectMode = 0(逻辑)`中的`%List`数据类型数据不会显示内部存储值,因为`%List`数据是使用非打印字符编码的。而是,Dynamic SQL将`%List`数据值显示为`$LISTBUILD`语句,例如:`$lb("White","Green")`。 `%SelectMode = 1(ODBC)`中的`%List`数据类型数据显示用逗号分隔的列表元素;此元素分隔符指定为`CollectionOdbcDelimiter`参数。 `%SelectMode = 2`中的`%List`数据类型数据(显示)显示由`$ CHAR(10,13)`分隔的列表元素(换行,回车);此元素分隔符指定为CollectionDisplayDelimiter参数。
- 对于`INSERT`或`UPDATE`操作,`%SelectMode`指定将转换为逻辑存储格式的输入数据的格式。为了进行此数据转换,必须使用RUNTIME(默认)的选择模式编译SQL代码,以便在执行`INSERT`或`UPDATE`时使用`Display`或`ODBC %SelectMode`。有关日期和时间的允许输入值,请参考日期和时间数据类型。
可以将`%SelectMode`指定为`%New()`类方法的第一个参数,或直接对其进行设置,如以下两个示例所示:
```java
SET tStatement = ##class(%SQL.Statement).%New(2)
```
```java
SET tStatement = ##class(%SQL.Statement).%New()
SET tStatement.%SelectMode=2
```
下面的示例返回`%SelectMode`的当前值:
```java
/// d ##class(PHA.TEST.SQL).DynamicSQL1()
ClassMethod DynamicSQL1()
{
SET tStatement = ##class(%SQL.Statement).%New()
WRITE !,"默认选择模式=",tStatement.%SelectMode
SET tStatement.%SelectMode=2
WRITE !,"设置选择模式=",tStatement.%SelectMode
}
```
```java
DHC-APP>d ##class(PHA.TEST.SQL).DynamicSQL1()
默认选择模式=0
设置选择模式=2
```
可以使用`$SYSTEM.SQL.Util.GetOption("SelectMode")` 方法为当前进程确定`SelectMode`默认设置。当`n`可以为`0 =逻辑`,`1 = ODBC`或`2 = Display`时,可以使用`$SYSTEM.SQL.Util.SetOption("SelectMode",n)` 方法来更改当前进程的`SelectMode`默认设置。设置`%SelectMode`会覆盖当前对象实例的默认设置。它不会更改`SelectMode`进程的默认值。
## %SchemaPath属性
`%SchemaPath`属性指定用于为非限定的表名,视图名或存储过程名提供架构名的搜索路径。模式搜索路径用于数据管理操作,例如`SELECT`,`CALL`,`INSERT`和`TRUNCATE TABLE`;数据定义操作(例如`DROP TABLE`)将忽略它。
搜索路径被指定为带引号的字符串,其中包含模式名称或逗号分隔的一系列模式名称。 InterSystems IRIS以从左到右的顺序搜索列出的模式。 InterSystems IRIS会搜索每个指定的架构,直到找到第一个匹配的表,视图或存储过程名称。因为模式是按指定顺序搜索的,所以不会检测到歧义的表名。仅搜索当前名称空间中的架构名称。
模式搜索路径可以包含文字模式名称以及`CURRENT_PATH`,`CURRENT_SCHEMA`和`DEFAULT_SCHEMA`关键字。
- `CURRENT_PATH`指定当前模式搜索路径,如先前的`%SchemaPath`属性中所定义。这通常用于将架构添加到现有架构搜索路径的开头或结尾。
- 如果`%SQL.Statement`调用是从类方法中进行的,则`CURRENT_SCHEMA`指定当前模式容器的类名称。如果在类方法中定义了`#SQLCompile Path`宏指令,则`CURRENT_SCHEMA`是映射到当前类包的架构。否则,`CURRENT_SCHEMA`与`DEFAULT_SCHEMA`相同。
- `DEFAULT_SCHEMA`指定系统范围的默认架构。使用此关键字,可以在搜索其他列出的架构之前,在架构搜索路径中将系统范围的默认架构作为一个项目进行搜索。如果已经搜索了路径中指定的所有模式而没有匹配项,则在搜索模式搜索路径后始终会搜索系统范围内的默认模式。
`%SchemaPath`是InterSystems IRIS在架构中搜索匹配表名的第一位。如果未指定`%SchemaPath`,或者未列出包含匹配表名的架构,则InterSystems IRIS将使用系统范围的默认架构。
可以通过指定`%SchemaPath`属性或指定`%New()`类方法的第二个参数来指定模式搜索路径,如以下两个示例所示:
```java
SET path="MyTests,Sample,Cinema"
SET tStatement = ##class(%SQL.Statement).%New(,path)
```
```java
SET tStatement = ##class(%SQL.Statement).%New()
SET tStatement.%SchemaPath="MyTests,Sample,Cinema"
```
可以在使用它的`%Prepare()`方法之前的任何位置设置`%SchemaPath`。
下面的示例返回`%SchemaPath`的当前值:
```java
/// d ##class(PHA.TEST.SQL).DynamicSQL2()
ClassMethod DynamicSQL2()
{
SET tStatement = ##class(%SQL.Statement).%New()
WRITE !,"默认 path=",tStatement.%SchemaPath
SET tStatement.%SchemaPath="MyTests,Sample,Cinema"
WRITE !,"设置 path=",tStatement.%SchemaPath
}
```
```java
DHC-APP>d ##class(PHA.TEST.SQL).DynamicSQL2()
默认 path=
设置 path=MyTests,Sample,Cinema
```
可以使用`%ClassPath()`方法将`%SchemaPath`设置为为指定的类名定义的搜索路径:
```java
/// d ##class(PHA.TEST.SQL).DynamicSQL3()
ClassMethod DynamicSQL3()
{
SET tStatement = ##class(%SQL.Statement).%New()
SET tStatement.%SchemaPath=tStatement.%ClassPath("Sample.Person")
WRITE tStatement.%SchemaPath
}
```
```java
DHC-APP>d ##class(PHA.TEST.SQL).DynamicSQL3()
Sample
```
## %Dialect属性
`%Dialect`属性指定SQL语句方言。可以指定Sybase,MSSQL或IRIS(InterSystems SQL)。 Sybase或MSSQL设置导致使用指定的Transact-SQL方言处理SQL语句。
Sybase和MSSQL方言在这些方言中支持SQL语句的有限子集。它们支持`SELECT`,`INSERT`,`UPDATE`,`DELETE`和`EXECUTE`语句。他们支持`CREATE TABLE`语句用于永久表,但不支持临时表。支持创建视图。支持`CREATE TRIGGER`和`DROP TRIGGER`。但是,如果`CREATE TRIGGER`语句部分成功,但是在类编译时失败,则此实现不支持事务回滚。支持`CREATE PROCEDURE`和`CREATE FUNCTION`。
Sybase和MSSQL方言支持IF控制流语句。 IRIS(InterSystems SQL)方言不支持此命令。
默认值为InterSystems SQL,由空字符串(`“”`)表示,或指定为“ IRIS”
可以将`%Dialect`指定为`%New()`类方法的第三个参数,或者将其直接设置为属性,或者使用方法进行设置,如以下三个示例所示:
在`%New()`类方法中设置`%Dialect`:
```java
/// d ##class(PHA.TEST.SQL).DynamicSQL4()
ClassMethod DynamicSQL4()
{
SET tStatement = ##class(%SQL.Statement).%New(,,"Sybase")
WRITE "语言模式设置为=",tStatement.%Dialect
}
```
```java
DHC-APP>d ##class(PHA.TEST.SQL).DynamicSQL4()
语言模式设置为=Sybase
```
直接设置`%Dialect`属性:
```java
/// d ##class(PHA.TEST.SQL).DynamicSQL5()
ClassMethod DynamicSQL5()
{
SET tStatement = ##class(%SQL.Statement).%New()
SET defaultdialect=tStatement.%Dialect
WRITE "默认语言模式=",defaultdialect,!
SET tStatement.%Dialect="Sybase"
WRITE "语言模式设置为=",tStatement.%Dialect,!
SET tStatement.%Dialect="IRIS"
WRITE "语言模式重置为默认=",tStatement.%Dialect,!
}
```
```java
DHC-APP>d ##class(PHA.TEST.SQL).DynamicSQL5()
默认语言模式=
语言模式设置为=Sybase
语言模式重置为默认=iris
```
使用`%DialectSet()`实例方法设置`%Dialect`属性,该方法将返回错误状态:
```java
/// d ##class(PHA.TEST.SQL).DynamicSQL6()
ClassMethod DynamicSQL6()
{
SET tStatement = ##class(%SQL.Statement).%New()
SET tStatus = tStatement.%DialectSet("Sybase")
IF tStatus'=1 {
WRITE "%DialectSet 失败:"
DO $System.Status.DisplayError(tStatus) QUIT
}
WRITE "语言模式设置为=",tStatement.%Dialect
}
```
```java
DHC-APP>d ##class(PHA.TEST.SQL).DynamicSQL6()
语言模式设置为=Sybase
```
`%DialectSet()`方法返回`%Status`值:成功返回状态1。失败返回以0开头的对象表达式,后跟编码错误信息。因此,无法执行`tStatus = 0`测试是否失败;您可以执行`$$ISOK(tStatus)= 0`宏测试以检查失败
## %ObjectSelectMode属性
`%ObjectSelectMode`属性是一个布尔值。如果`%ObjectSelectMode = 0(默认)`,则`SELECT`列表中的所有列都将绑定到结果集中具有文字类型的属性。如果`%ObjectSelectMode = 1`,则`SELECT`列表中的列将绑定到具有关联属性定义中定义的类型的属性。
`%ObjectSelectMode`允许指定如何在从`SELECT`语句生成的结果集类中定义类型类为swizzleable类的列。如果`%ObjectSelectMode = 0`,则将在结果集中将与swizzleable列相对应的属性定义为与SQL表的RowID类型相对应的简单文字类型。如果`%ObjectSelectMode = 1`,则将使用列的声明类型定义属性。这意味着访问结果集属性将触发 swizzling。
无法将`%ObjectSelectMode`设置为`%New()`的参数。
下面的示例返回`%ObjectSelectMode`默认值,设置`%ObjectSelectMode`,然后返回新的`%ObjectSelectMode`值:
```java
/// d ##class(PHA.TEST.SQL).DynamicSQL7()
ClassMethod DynamicSQL7()
{
SET myquery = "SELECT TOP 5 %ID AS MyID,Name,Age FROM Sample.Person"
SET tStatement = ##class(%SQL.Statement).%New()
WRITE !,"默认 ObjectSelectMode=",tStatement.%ObjectSelectMode
SET tStatement.%ObjectSelectMode=1
WRITE !,"语言 ObjectSelectMode=",tStatement.%ObjectSelectMode
}
```
```java
DHC-APP>d ##class(PHA.TEST.SQL).DynamicSQL7()
默认 ObjectSelectMode=0
语言 ObjectSelectMode=1
```
当使用字段名称属性从结果集中返回值时,主要使用`%ObjectSelectMode = 1`。本章“从结果集中返回特定值”部分的字段名属性中的示例对此进行了进一步说明。
当`SELECT`列表中的字段链接到集合属性时,可以使用`%ObjectSelectMode = 1`。 `%ObjectSelectMode`将使集合swizzle。如果`%SelectMode = 1或2`,则系统在转换前将收集序列值转换为逻辑模式形式。生成的oref支持完整的收集接口。