清除过滤器
文章
Lilian Huang · 七月 20, 2022
本文将讨论 FHIR 中的问卷和问卷反馈(Questionnaire and Questionnaire Response), 从创建表单到上传到服务器以及如何填写它们。
tl;dr :
通过使用该工具链接“ this online tool” ,您可以轻松的开始构建您自己的表单,或者使用现有模版。
通过使用InterSystems 本地FHIR 服务器链接“ this InterSystems local FHIR server” ,您可以轻松的存储您的FHIR资源和问卷。
通过使用此应用程序“this app” ,您可以像医生一样操作,对您的 FHIR 服务器上的每位患者进行问卷调查和回复。
需要注意的是,该应用程序不使用 Content-Type 'application/json+fhir' 进行通信,而只是使用 Content-Type 'application/json' ,所以它不会像我们的本地 InterSystems FHIR 服务器那样工作。
这就是为什么我创建了这个 GitHub 存储库“this GitHub repo”,其中包含应用程序的修改版本,使用 Content-Type 'application/json+fhir',拥有本地 FHIR 服务器和指向问卷生成器工具的链接以及一些解释。
克隆 repo 后,通过执行 docker-compose up -d, npm ci, npm run build 然后出现 npm run start ,您将可以访问该应用程序,通过选择您想要的 FHIR 服务器和您想要工作的患者使用,您将能够填写调查问卷并将其保存到您的服务器中,只需单击 2 次。
End of tl;dr
接下来是 GitHub 的自述文件。
1. 使用本地 fhir 服务器的 FHIR 表单应用程序包括如下
1. App for FHIR forms using a local fhir server(使用本地 fhir 服务器的 FHIR 表单应用程序)
2. Requirements(要求)
2.1. Add Node.js and npm to your path(将 Node.js 和 npm 添加到您的路径)
2.2. Install Dependencies(安装依赖项)
3. Local FHIR server(本地FHIR服务器)
4. Using the app(使用应用程序)
4.1. Build the application(构建应用程序)
4.2. Run the Application(运行应用程序)
5. FHIR form / questionnaire (FHIR表格/问卷)
5.1. Creating your own FHIR form(创建您自己的 FHIR 表单)
5.2. Importing your FHIR form(导入您的 FHIR 表格)
这是一个基于此“this repo”的应用程序,可用于显示, “FHIR” “SDC” “Questionnaire”并收集数据作为 FHIR 问卷反馈的资源。
通过使用docker-compose up -d ,您将可以访问本地 FHIR 服务器“local FHIR server” 然后可以使用该服务器来测试应用程序。
2. 要求
该应用程序依赖于“LHC-Forms “ 渲染,用于显示表单的小部件,它支持部分 FHIR 问卷(版本 STU3 和 R4) 和结构化数据捕获实现指导“Structured Data Capture ImplementationGuide“。此小部件将与依赖项一起安装。
为可以尝试一些示例表单,这个库里在这个下面e2e-test/data/附带了一些表单,
会在构建时自动加载到本地 FHIR 服务器。
2.1. 将 Node.js 和 npm 添加到您的路径
文件 bashrc.lforms-fhir-app 指定了我们正在为开发使用的 Node.js 的版本,下载Node.js的该版本,并将其 bin 目录添加到你的路径。
2.2. 安装依赖项
通过运行此命令,您将能够安装应用程序运行所需的一切。
npm ci
3. 本地FHIR 服务器
如果您没有 FHIR 服务器来试用此应用程序,您可以在 fhir-form 文件夹中启动并使用由 InterSystems 技术支持的本地 FHIR 服务器:
docker-compose up -d
等待一段时间后,您的本地 FHIR 服务器已启动,您可以使用 http://localhost:32783/fhir/r4请注意,此链接已在应用程序中注册。
4. 使用应用程序
要使用该应用程序,您必须构建“build” 并启动“start” 它。您现在可以使用应用程序的菜单访问您选择的任何 FHIR 服务器,但如果您愿意,您可以使用此本地 FHIR 服务器“ local FHIR server”。
4.1. 构建应用程序
npm run build
这将在“dist”目录中创建用于生产的文件,但也会从node_modules 复制一些需要的文件中。
4.2.运行应用程序
npm run start
将启动一个在 8000 端口运行的 http 服务器。
现在在localhost:8000/lforms-fhir-app/浏览到应用程序。
在这里您可以选择要连接的服务器。
如果要使用本地 FHIR 服务器,请启动本地 FHIR 服务器“start the local FHIR server”, 然后在应用程序上选择第一个选项 http://localhost:32783/fhir/r4
5. FHIR表格/问卷
5.1. 创建您自己的 FHIR 表单
通过使用这个在线工具“this online tool”, 您可以轻松地从头开始构建自己的表单或使用现有的表单。
我们建议您导入 e2e-tests/data/R4 文件夹中现有的一个,然后从这里开始了解该工具的工作原理。
5.2. 导入您的FHIR表格
使用该应用程序,您可以轻松导入本地表单并使用上传按钮upload 立即使用它们。
如果您使用的是工具”formbuilder tool “, 如果您有支持Content-Type 'application/json' 的 FHIR 服务器,则可以使用导出按钮将您正在创建的表单直接导出到 fhir 服务器。
如果您的服务器不支持 Content-Type 'application/json' 而只支持 Content-Type 'application/json+fhir' 例如,作为我们的本地 FHIR 服务器“local FHIR server “,您必须将表单导出到文件,然后在应用程序上 ,将文件上传到服务器,因为应用程序以 Content-Type 'application/json+fhir' 进行通信。
原文请点击该链接:https://community.intersystems.com/post/questionnaire-forms-fhir-creation-usage#3-local-fhir-server
文章
Hao Ma · 一月 4, 2023
以下是我们应客户的要求拟定的Caché系统健康检查的建议。InterSystems的工程师们认为其中的项目足以了解客户当前的系统健康状况。
这些项目中有些,比如Buttons, pButtons报告是必须的,其他内容,尤其是问卷部分,越多回答对系统健康的了解也越清楚。InterSystems公司的技术支持中心WRC(World Response Center),在合适的条件下可以协助用户解读健康检查的结果。
在后面的内容中, 我会详细介绍这些检查的项目,比如报告的执行步骤,已经如何简单的发现问题。
检查的内容也适用于IRIS,仅仅是执行的步骤上有细微的区别,后面文章会详细说。
## 健康检查项目
本健康检查只用于Caché系统本身的内容, 不包括Caché上使用的各种应用。
建议用户收集下列两部分数据和资料:
### 系统运行数据
- [ ] 所有Caché实例服务器的网络架构图,包含所有的数据服务器,应用服务器,镜像服务器,灾备服务器。还应该包含网段的划分, 相关的Web服务器,负载均衡设备的部署等情况。以及一切客户认为和Caché工作相关的网络配置的情况。
- [ ] Caché数据库使用的存储设备的信息, 不限于类型,大小,品牌等等任何可以帮助了解存储设备的信息。
- [ ] 所有数据库上一次的完整性检查报告。
- [ ] 所有Caché实例的
- [ ] 系统监控检查报告(Buttons)
- [ ] 24小时系统性能报告(pButtons):
所有关联的系统,比如一个Caché数据服务器以及和它连接的应用服务器(ECP服务器),应该在尽量相同的时间执行24小时pButton测量
- [ ] 一年内或自上次启动后(以其中更长时间为准)的Console日志
- [ ] 导出的日常任务(Task)
- [ ] 导出的后台任务历史列表
- [ ] 系统时钟同步的配置
- [ ] 所有CSP Gateway的配置文件,以及CSP Gateway工作的Apache Web Server, Nginx Web Server,Windows IIS的配置文件。
- [ ] 如果用户使用了外部备份,请提供外部备份的操作步骤及使用的脚本程序。
### 维护工作的问卷
以下问题的回答能帮助InterSystems的工程师更好的了解客户的Caché工作情况,以及更方便的分析上面采集的数据。
- [ ] 请列出近一年内Caché的软硬件变动
- [ ] 是否有测试环境(TestBed), 测试服务器的梳理,配置
- [ ] 请提供Caché的日常维护的情况说明,尽可能提供以下日常维护的方案,执行频率,执行时长等等。包括但不限于:
- [ ] 备份恢复
- 方案,Caché在线备份还是外部备份。如果是Caché在线备份,各种备份类型的安排情况(全备份,增量备份,累计备份)
- 执行频率,执行的时间点
- 各种数据量情况下的执行时长,不如全备份的时长,增量备份的数据量是多少,执行时长是多少等等
- [ ] 数据库完整性检查
- 完整性检查的方案,频率
- 数据库的大小及对应的完整性检查的执行时长
- [ ] 告警通知
- 告警通知发送的方式。(告警通知默认是Console log里严重级别为2,3的条目)
- 告警通知的处理流程
- 告警通知的产生:是否有客户定制的通知消息
- Console Log中出现的严重级别为1的消息(Warning消息)是否被通知,或者是否有任何处理方式
- [ ] 性能测量
- 提供业务活动量在一段时间内的变动模式, 比如一周,一天中业务量的忙时,闲时,以及是否月初活着月底有大的报表生成等等
- 详细列出各种周期性执行的和Caché性能相关的操作的时间点和时长,处了上面提到的备份恢复,数据库完整性检测等,还可以是任意的Caché操作,以及Caché所在的虚拟机,服务器的操作,还可以包括可能影响Caché性能表现的连接的第3方的业务系统监控系统,审计系统的与之有关的操作
- 是否有常规的性能测试方案,包括Caché上的指标测量(pButtons), 以及操作系统的性能指标测量
- 无论以何种形式,是否能提供Caché系统的性能基准。这个性能基准应该以客户的业务活动量做为采样周期,比如以周为单位
- 上述指标是否能提供图表的展示
- [ ] 尽可能的提供近一年中在Caché日常维护中遇到的各种故障及异常的列表。对列表中的每一项,尽量提供详细的描述和信息,包括并不限于:
- 是否报告InterSystems, 如果报告了, WRC号码是多少
- 发生的频率如何?
- 如果已经有解决,解决的方案是什么?
- 如果没有经过人工处理,那么故障恢复的时长平均是多少?
- 维护工程师对故障产生的原因以及造成后果的分析讨论的结果,如果有。
- [ ] 其他内容(可选)
- Caché维护团队的工作分配, 以及相关的外部团队的职责,比如应用实施方,用户的其他IT团队,硬件维护,硬件监控团队等等。
- 对Caché维护最期待的改进,工具的提供等
- 其他任何有关Caché维护工作而上面各项中未涵盖的内容。
文章
Hao Ma · 一月 10, 2021
虽然 Caché 和 InterSystems IRIS 数据库的[完整性](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCDI_integrity)完全不会受到系统故障的影响,但物理存储设备故障确实会损坏其存储的数据。 因此,许多站点选择运行定期数据库完整性检查,尤其要与备份配合,以验证在发生灾难时是否可以依赖给定的备份。 系统管理员在应对涉及存储损坏的灾难时,也可能强烈需要完整性检查。 完整性检查必须读取所检查的 global 的每个块(如果尚未在缓冲区中),并且按照 global 结构指示的顺序读取。 这会花费大量时间,**但完整性检查能够以存储子系统可以承受的最快速度进行读取**。 在某些情况下,需要以这种方式运行以尽快获得结果。 在其他情况下,完整性检查需要更加保守,以避免消耗过多的存储子系统带宽。
## 行动计划
以下概述适合大多数情况。 本文其余部分中的详细讨论提供了采取其中任一行动或得出其他行动方案所需的信息。
1. 如果使用 Linux 并且完整性检查很慢,请参阅下面有关启用异步 I/O 的信息。
2. 如果完整性检查必须尽快完成,则在隔离的环境中运行;或者如果迫切需要结果,则使用多进程完整性检查来并行检查多个 global 或数据库。 进程数乘以每个进程将执行的并发异步读取数(默认为 8,如果使用 Linux 并且禁用异步 I/O 则为 1)是实时并发读取数的限制。 假定平均数是限制数量的一半,然后与存储子系统的能力进行比较。 例如,存储由 20 个驱动器条带化,每个进程的默认并发读取数为 8,则可能需要 5 个或更多进程才能利用存储子系统的全部能力 (5*8/2=20)。
3. 在平衡完整性检查速度与对生产的影响时,首先调整多进程完整性检查的进程数,然后如果需要的话,查看可调参数 SetAsyncReadBuffers。 对于长期解决方案(以及为消除误报),请参见下面的隔离完整性检查。
4. 如果已经被限制为一个进程(例如有一个极大的 global 或存在其他外部约束),并且完整性检查的速度需要上下调整,则查看下面的可调参数 SetAsyncReadBuffers。
## 多进程完整性检查
让完整性检查更快完成(以更高的速度使用系统资源)的一般解决方案是将工作分给多个并行进程。 一些完整性检查用户界面和 API 会这样做,而其他一些则使用单个进程。 对进程的分配按 global 进行,因此对单个 global 的检查始终由一个进程执行(Caché 2018.1 之前的版本按数据库而不是按 global 分配工作)。
多进程完整性检查的主要 API 是 **CheckLIst^Integrity**(有关详细信息,请参阅[文档](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GCDI_integrity#GCDI_integrity_verify_utility))。 它将结果收集在一个临时的 global 中,通过 Display^Integrity 来显示。 以下是使用 5 个进程检查 3 个数据库的示例。 这里如省略数据库列表参数,将检查所有数据库。
set dblist=$listbuild(“/data/db1/”,”/data/db2/”,”/data/db3/”)
set sc=$$CheckList^Integrity(,dblist,,,5)
do Display^Integrity()
kill ^IRIS.TempIntegrityOutput(+$job)
/* Note: evaluating ‘sc’ above isn’t needed just to display the results, but...
$system.Status.IsOK(sc) - ran successfully and found no errors
$system.Status.GetErrorCodes(sc)=$$$ERRORCODE($$$IntegrityCheckErrors) // 267
- ran successfully, but found errors.
Else - a problem may have prevented some portion from running, ‘sc’ may have
multiple error codes, one of which may be $$$IntegrityCheckErrors. */
像这样使用 CheckLIst^Integrity 是实现我们感兴趣的控制水平的最直接方法。 管理门户接口和完整性检查任务(内置但未安排)使用多个进程,但可能无法为我们的用途提供足够的控制。*
其他完整性检查接口,尤其是终端用户接口 ^INTEGRIT 或 ^Integrity 以及 Silent^Integrity,在单个进程中执行完整性检查。 因此,这些接口不能以最快的速度完成检查,并且它们使用的资源也较少。 但一个优点是,它们的结果是可见的,可以记录到文件或输出到终端,因为每个 global 都会被检查,而且顺序明确。
## 异步 I/O
完整性检查进程会排查 global 的每个指针块,一次检查一个,根据它指向的数据块的内容来进行验证。 数据块以异步 I/O 的方式读取,以确保每时每刻都有一定数量的读取请求供存储子系统处理,并且每次读取完成后都进行验证。
在 Linux 上,异步 I/O 只有与直接 I/O 结合时才有效,而 InterSystems IRIS 2020.3 之前的版本默认不启用直接 I/O。 这解释了大量 Linux 上完整性检查时间过长的情况。 幸运的是,可以在 Cache 2018.1、IRIS 2019.1 及以后的版本上启用直接 I/O,方法是在 .cpf 文件的 [config] 部分中设置 **wduseasyncio=1**,然后重新启动。 通常建议设置此参数,以在繁忙系统上实现 I/O 可伸缩性,并且自 Caché 2015.2 起,在非 Linux 平台上默认设置此参数。 在启用之前,确保已经为数据库缓存(global 缓冲区)配置了足够的内存,因为启用直接 I/O 后,数据库将不再被 Linux(冗余)缓存。 未启用时,完整性检查执行的读取会同步完成,不能有效利用存储。
在所有平台上,完整性检查进程一次执行的读取数默认设置为 8。 如果必须更改单个完整性检查进程从磁盘读取的速率,可以调整此参数 – 向上调会使单个进程更快完成,向下调则使用更少的存储带宽。 请记住:
* 此参数应用于每个完整性检查进程。 当使用多个进程时,进程数会使实时读取数增加。更改并行完整性检查进程数会产生较大影响,因此这通常是先做的事情。 每个进程还受到计算时间的限制(除其他限制外),因此增加此参数的值所获得的收益也有限。
* 这只在存储子系统处理并发读取的能力范围内有效。 如果数据库存储在单个本地驱动器上,再高的数值也没有用处,而在几十个驱动器上条带化的存储阵列可以并发处理几十个读取。
要从 %SYS 命名空间调整此参数,则 **do SetAsyncReadBuffers^Integrity(**value**)**。 要查看当前值,则 **write $$GetAsyncReadBuffers^Integrity()**。 更改在检查下一个 global 时生效。 目前,该设置在系统重启后不会保持,虽然可以将其添加到 SYSTEM^%ZSTART 中。
有一个相似的参数用于在磁盘上的块是连续(或接近连续)分布时控制每次读取的最大大小。 此参数很少需要用到,尽管具有高存储延迟的系统或具有较大块大小的数据库可能会从微调中受益。 该值的单位为 64KB,因此值 1 表示 64KB,4 表示 256KB 等等。0(默认值)表示让系统选择,当前选择 1 (64KB)。 此参数的 ^Integrity 函数(类似于上面提及的函数)为 **SetAsyncReadBufferSize** 和 **GetAsyncReadBufferSize**。
## 隔离完整性检查
许多站点直接在生产系统上运行定期完整性检查。 这当然是最简单的配置,但并不理想。 除了完整性检查对存储带宽的影响,并发数据库更新活动有时还可能导致误报错误(尽管检查算法内置了缓解措施)。 因此,在生产系统上运行的完整性检查所报告的错误,需要由管理员进行评估和/或重新检查。
很多时候,存在更好的选择。 可以将存储快照或备份映像挂载到另一台主机上,在那里由隔离的 Caché 或 IRIS 实例运行完整性检查。 这样不仅可以防止任何误报,而且如果存储也与生产隔离,运行完整性检查可以充分利用存储带宽并更快完成。 这种方法非常适合使用完整性检查来验证备份的模型;经过验证的备份可以有效验证截至生成备份前的生产情况。 还可以通过云和虚拟化平台更容易地从快照建立可用的隔离环境。
* * *
* 管理门户接口、完整性检查任务和 SYS.Database 的 IntegrityCheck 方法会选择相当多的进程(等于 CPU 内核数),在很多情况下缺少所需的控制。 管理门户和任务还会对任何报告错误的 global 执行完整的重新检查,以识别可能因并发更新而出现的误报。 除了完整性检查算法内置的误报缓解措施,也可能进行这种重新检查;在某些情况下,由于会花费额外的时间(重新检查在单个进程中运行,并检查整个 global),可能并不需要重新检查。 此行为将来可能会更改。
文章
姚 鑫 · 六月 29, 2022
# 第十三章 信号(三)- 示例演示
## 运行示例
`Main`、`Producer` 和 `Consumer` 这三个类中的每一个都有自己的 `Run` 方法,最好在各自的终端窗口中运行它们。每次运行时,它都会显示它为日志生成的消息。一旦用户通过提供它正在等待的输入来响应 `Main` 类,`Main` 的 `Run` 方法将终止删除信号量。然后,用户可以通过键入命令查看所有进程的合并日志文件的显示
```java
Do ##class(Semaphore.Util).ShowLog()
```
注意:以下所有示例都假定所有类都已在`“USER”`命名空间中编译。
### 示例 1 - 创建和删除信号量
最简单的例子演示了信号量的创建和销毁。它使用 `Semaphore.Main` 类。请执行下列操作:
1. 打开一个终端窗口。
2. 输入命令——
```java
Do ##class(Semaphore.Main).Run()
```
3. 该方法创建信号量。如果成功,将看到消息“输入任何字符以终止运行方法”。按下 `Enter` 键。该方法显示信号量的初始化值,将其删除,然后退出。
4. 通过发出命令显示日志文件
```java
Do ##class(Semaphore.Util).ShowLog()
```
按照上述步骤在终端窗口中显示的消息示例如下
```java
消息示例如下
DHC-APP>Do ##class(Semaphore.Main).Run()
(1) Main Started
(2) New semaphore
(3) Created: "Counter"; Value = 0; Id = 0x0x10000
(4) Semaphore create result: 1
(5) Enter any character to terminate Run method
(6) Final value = 0
(7) Semaphore delete status: 1
(8) Main Finished
(9) Closing Semaphore: Id = 0x10000
日志输出如下所示:
DHC-APP> Do ##class(Semaphore.Util).ShowLog()
Message Log: Entries = 9
# $JOB Sender Message
1) 24888 Main: Main Started
2) 24888 Counter: New semaphore
3) 24888 Counter: Created: "Counter"; Value = 0; Id = 0x0x10000
4) 24888 Main: Semaphore create result: 1
5) 24888 Main: Enter any character to terminate Run method
6) 24888 Main: Final value = 0
7) 24888 Main: Semaphore delete status: 1
8) 24888 Main: Main Finished
9) 24888 Counter: Closing Semaphore: Id = 0x10000
```
### 示例 2——创建信号量并连续递增它
这个例子展示了生产者在工作,以及从两个进程中捕获日志消息。
1. 打开两个单独的终端窗口。称它们为`“A”`和`“B”`。
2. 在窗口 `A` 中,键入以下命令,但不要在末尾键入 ENTER 键 -
```java
Do ##class(Semaphore.Main).Run()
```
3. 在窗口 `B` 中,键入以下命令,但同样,不要在命令末尾键入 `ENTER` 键 -
```java
Do ##class(Semaphore.Producer).Run()
```
4. 现在,在窗口 `A` 中,按 `ENTER` 键。然后在窗口 `B` 中,按 `ENTER` 键。这将启动两个类并行执行。他们各自的消息显示在他们自己的窗口中。
5. `Producer` 进程完成后,关闭 `B` 窗口。
6. 在 `A` 窗口中,按 `ENTER` 键以完成 `Main` 类。然后,使用以下命令显示日志 -
```java
Do ##class(Semaphore.Util).ShowLog()
```
对于此示例,以下是输出
`A` 窗口
```java
DHC-APP>Do ##class(Semaphore.Main).Run()
(1) Main Started
(2) New semaphore
(3) Created: "Counter"; Value = 0; Id = 0x0x20001
(4) Semaphore create result: 1
(5) Enter any character to terminate Run method
(17) Final value = 30
(18) Semaphore delete status: 1
(19) Main Finished
(20) Closing Semaphore: Id = 0x20001
```
`B` 窗口
```java
DHC-APP>Do ##class(Semaphore.Producer).Run()
(6) Producer.1 Started
(7) New semaphore
(8) Open Id = 0x20001
(9) Increment 0x20001 = 0 by 5 wait 10 sec
(10) Increment 0x20001 = 5 by 5 wait 4 sec
(11) Increment 0x20001 = 10 by 3 wait 1 sec
(12) Increment 0x20001 = 13 by 5 wait 9 sec
(13) Increment 0x20001 = 18 by 5 wait 8 sec
(14) Increment 0x20001 = 23 by 4 wait 2 sec
(15) Increment 0x20001 = 27 by 1 wait 8 sec
(16) Increment 0x20001 = 28 by 2 wait 5 sec
(21) Producer.1 Finished
(22) Closing Semaphore: Id = 0x20001
```
日志显示
```java
DHC-APP>Do ##class(Semaphore.Util).ShowLog()
Message Log: Entries = 22
# $JOB Sender Message
1) 21080 Main: Main Started
2) 21080 Counter: New semaphore
3) 21080 Counter: Created: "Counter"; Value = 0; Id = 0x0x20001
4) 21080 Main: Semaphore create result: 1
5) 21080 Main: Enter any character to terminate Run method
6) 27724 Producer.1: Producer.1 Started
7) 27724 Counter: New semaphore
8) 27724 Producer.1: Open Id = 0x20001
9) 27724 Producer.1: Increment 0x20001 = 0 by 5 wait 10 sec
10) 27724 Producer.1: Increment 0x20001 = 5 by 5 wait 4 sec
11) 27724 Producer.1: Increment 0x20001 = 10 by 3 wait 1 sec
12) 27724 Producer.1: Increment 0x20001 = 13 by 5 wait 9 sec
13) 27724 Producer.1: Increment 0x20001 = 18 by 5 wait 8 sec
14) 27724 Producer.1: Increment 0x20001 = 23 by 4 wait 2 sec
15) 27724 Producer.1: Increment 0x20001 = 27 by 1 wait 8 sec
16) 27724 Producer.1: Increment 0x20001 = 28 by 2 wait 5 sec
17) 21080 Main: Final value = 30
18) 21080 Main: Semaphore delete status: 1
19) 21080 Main: Main Finished
20) 21080 Counter: Closing Semaphore: Id = 0x20001
21) 27724 Producer.1: Producer.1 Finished
22) 27724 Counter: Closing Semaphore: Id = 0x20001
```
### 示例 3 - 同时运行所有三个进程
此示例显示尝试以连贯的方式增加和减少相同的信号量。它使用所有三个主要类。
1. 打开三个单独的终端窗口。称它们为`“A”`、`“B”`和`“C”`。
2. 在窗口 `A` 中,键入以下命令,但最后不要按 `ENTER` 键
```java
Do ##class(Semaphore.Main).Run()
```
3. 在窗口 `B` 中,键入以下命令,但同样,不要在命令末尾按 `ENTER` 键 -
```java
Do ##class(Semaphore.Producer).Run()
```
4. 在窗口 `C` 中,键入以下命令,但同样,不要在命令末尾按 `ENTER` 键 -
```java
Do ##class(Semaphore.Consumer).Run()
```
5. 从窗口 `A` 开始,访问每个窗口并键入 `ENTER` 键。这将启动 `Main` 类,然后是其他两个类。如前所述,每个进程都会在自己的窗口中显示其日志消息。
6. 当`Producer` 和`Consumer` 进程都完成后,关闭`B` 窗口和`C` 窗口。
7. 在 `A` 窗口中,按 `ENTER` 键以完成 `Main` 类。然后,使用以下命令显示日志
```java
Do ##class(Semaphore.Util).ShowLog()
```
运行此示例会产生类似于以下内容的输出:
窗口 `A`
```java
DHC-APP>Do ##class(Semaphore.Main).Run()
(1) Main Started
(2) New semaphore
(3) Created: "Counter"; Value = 0; Id = 0x0x40003
(4) Semaphore create result: 1
(5) Enter any character to terminate Run method
(64) Final value = 0
(65) Semaphore delete status: 1
(66) Main Finished
(67) Closing Semaphore: Id = 0x40003
```
窗口`B`
```java
DHC-APP>Do ##class(Semaphore.Producer).Run()
(6) Producer.1 Started
(7) New semaphore
(8) Open Id = 0x40003
(9) Increment 0x40003 = 0 by 5 wait 8 sec
(20) Increment 0x40003 = 0 by 4 wait 4 sec
(25) Increment 0x40003 = 0 by 3 wait 1 sec
(29) Increment 0x40003 = 0 by 2 wait 10 sec
(36) Increment 0x40003 = 0 by 4 wait 3 sec
(40) Increment 0x40003 = 0 by 5 wait 5 sec
(52) Increment 0x40003 = 0 by 5 wait 6 sec
(58) Increment 0x40003 = 0 by 2 wait 2 sec
(62) Producer.1 Finished
(63) Closing Semaphore: Id = 0x40003
```
窗口`C`
```java
DHC-APP>Do ##class(Semaphore.Consumer).Run()
(10) Consumer.1 Started
(11) New semaphore
(12) Consumer: Open Id = 0x40003
(13) Decrement 0x40003 = 5 by 1 wait 10 sec
(14) WaitCompleted: 0x40003; Amt = 1
(15) Granted
(16) Decrement 0x40003 = 4 by 5 wait 2 sec
(17) WaitCompleted: 0x40003; Amt = 4
(18) Granted
(19) Decrement 0x40003 = 0 by 5 wait 8 sec
(21) WaitCompleted: 0x40003; Amt = 4
(22) Granted
(23) Decrement 0x40003 = 0 by 5 wait 6 sec
(25) WaitCompleted: 0x40003; Amt = 3
(26) Granted
(27) Decrement 0x40003 = 0 by 3 wait 1 sec
(28) Timeout
(30) Decrement 0x40003 = 0 by 4 wait 4 sec
(31) WaitCompleted: 0x40003; Amt = 2
(32) Granted
(33) Decrement 0x40003 = 0 by 2 wait 7 sec
(34) Timeout
(35) Decrement 0x40003 = 0 by 4 wait 9 sec
(37) WaitCompleted: 0x40003; Amt = 4
(38) Granted
(39) Decrement 0x40003 = 0 by 2 wait 5 sec
(41) WaitCompleted: 0x40003; Amt = 2
(42) Granted
(43) Decrement 0x40003 = 3 by 1 wait 3 sec
(44) WaitCompleted: 0x40003; Amt = 1
(45) Granted
(46) Decrement 0x40003 = 2 by 2 wait 10 sec
(47) WaitCompleted: 0x40003; Amt = 2
(48) Granted
(49) Decrement 0x40003 = 0 by 2 wait 4 sec
(50) Timeout
(51) Decrement 0x40003 = 0 by 3 wait 4 sec
(53) WaitCompleted: 0x40003; Amt = 5
(54) Granted
(55) Decrement 0x40003 = 0 by 1 wait 1 sec
(56) Timeout
(57) Decrement 0x40003 = 0 by 3 wait 7 sec
(59) WaitCompleted: 0x40003; Amt = 2
(60) Granted
(61) Consumer.1 Finished
```
日志显示
```java
DHC-APP>Do ##class(Semaphore.Util).ShowLog()
Message Log: Entries = 67
# $JOB Sender Message
1) 6412 Main: Main Started
2) 6412 Counter: New semaphore
3) 6412 Counter: Created: "Counter"; Value = 0; Id = 0x0x40003
4) 6412 Main: Semaphore create result: 1
5) 6412 Main: Enter any character to terminate Run method
6) 22236 Producer.1: Producer.1 Started
7) 22236 Counter: New semaphore
8) 22236 Producer.1: Open Id = 0x40003
9) 22236 Producer.1: Increment 0x40003 = 0 by 5 wait 8 sec
10) 20224 Consumer.1: Consumer.1 Started
11) 20224 Counter: New semaphore
12) 20224 Consumer.1: Consumer: Open Id = 0x40003
13) 20224 Consumer.1: Decrement 0x40003 = 5 by 1 wait 10 sec
14) 20224 Counter: WaitCompleted: 0x40003; Amt = 1
15) 20224 Consumer.1: Granted
16) 20224 Consumer.1: Decrement 0x40003 = 4 by 5 wait 2 sec
17) 20224 Counter: WaitCompleted: 0x40003; Amt = 4
18) 20224 Consumer.1: Granted
19) 20224 Consumer.1: Decrement 0x40003 = 0 by 5 wait 8 sec
20) 22236 Producer.1: Increment 0x40003 = 0 by 4 wait 4 sec
21) 20224 Counter: WaitCompleted: 0x40003; Amt = 4
22) 20224 Consumer.1: Granted
23) 20224 Consumer.1: Decrement 0x40003 = 0 by 5 wait 6 sec
24) 22236 Producer.1: Increment 0x40003 = 0 by 3 wait 1 sec
25) 20224 Counter: WaitCompleted: 0x40003; Amt = 3
26) 20224 Consumer.1: Granted
27) 20224 Consumer.1: Decrement 0x40003 = 0 by 3 wait 1 sec
28) 20224 Consumer.1: Timeout
29) 22236 Producer.1: Increment 0x40003 = 0 by 2 wait 10 sec
30) 20224 Consumer.1: Decrement 0x40003 = 0 by 4 wait 4 sec
31) 20224 Counter: WaitCompleted: 0x40003; Amt = 2
32) 20224 Consumer.1: Granted
33) 20224 Consumer.1: Decrement 0x40003 = 0 by 2 wait 7 sec
34) 20224 Consumer.1: Timeout
35) 20224 Consumer.1: Decrement 0x40003 = 0 by 4 wait 9 sec
36) 22236 Producer.1: Increment 0x40003 = 0 by 4 wait 3 sec
37) 20224 Counter: WaitCompleted: 0x40003; Amt = 4
38) 20224 Consumer.1: Granted
39) 20224 Consumer.1: Decrement 0x40003 = 0 by 2 wait 5 sec
40) 22236 Producer.1: Increment 0x40003 = 0 by 5 wait 5 sec
41) 20224 Counter: WaitCompleted: 0x40003; Amt = 2
42) 20224 Consumer.1: Granted
43) 20224 Consumer.1: Decrement 0x40003 = 3 by 1 wait 3 sec
44) 20224 Counter: WaitCompleted: 0x40003; Amt = 1
45) 20224 Consumer.1: Granted
46) 20224 Consumer.1: Decrement 0x40003 = 2 by 2 wait 10 sec
47) 20224 Counter: WaitCompleted: 0x40003; Amt = 2
48) 20224 Consumer.1: Granted
49) 20224 Consumer.1: Decrement 0x40003 = 0 by 2 wait 4 sec
50) 20224 Consumer.1: Timeout
51) 20224 Consumer.1: Decrement 0x40003 = 0 by 3 wait 4 sec
52) 22236 Producer.1: Increment 0x40003 = 0 by 5 wait 6 sec
53) 20224 Counter: WaitCompleted: 0x40003; Amt = 5
54) 20224 Consumer.1: Granted
55) 20224 Consumer.1: Decrement 0x40003 = 0 by 1 wait 1 sec
56) 20224 Consumer.1: Timeout
57) 20224 Consumer.1: Decrement 0x40003 = 0 by 3 wait 7 sec
58) 22236 Producer.1: Increment 0x40003 = 0 by 2 wait 2 sec
59) 20224 Counter: WaitCompleted: 0x40003; Amt = 2
60) 20224 Consumer.1: Granted
61) 20224 Consumer.1: Consumer.1 Finished
62) 22236 Producer.1: Producer.1 Finished
63) 22236 Counter: Closing Semaphore: Id = 0x40003
64) 6412 Main: Final value = 0
65) 6412 Main: Semaphore delete status: 1
66) 6412 Main: Main Finished
67) 6412 Counter: Closing Semaphore: Id = 0x40003
```
## 其他变量
此示例的其他变量是可能的。虽然一次只能运行一个 `Semaphore.Main`,但在其他窗口中执行的生产者或消费者的数量没有限制。鼓励用户在各种场景中尝试不同数量的消费者和生产者,例如
- 运行三个消费者和一个生产者,这样信号量就会有更大的“竞争”。运气好的话,日志会显示两个或多个消费者发出了减少信号量的请求,并且都成功了,因为信号量值大到足以满足两个请求的部分或全部。
- 还可以使用这些类来演示删除信号量时其他进程中发生的情况。为此,在 `Producers` 或 `Consumers` 运行时,切换到 `Main` 类正在运行的窗口,然后按 `ENTER`。在完成处理过程中,`Main` 类将删除信号量,`Producer` 或 `Consumer` 的 `OREF` 将不再有效。下次尝试使用将产生错误。
- 通过将信号量的名称更改为看起来像全局名称的名称,可以将信号量映射到例如 `ECP` 系统上的不同实例。
文章
姚 鑫 · 六月 16, 2021
# 第九章 将XML导入到对象中
本章介绍如何使用%XML.Reader将XML文档导入到 IRIS对象中。
**注意:使用的任何XML文档的XML声明都应该指明该文档的字符编码,并且文档应该按照声明的方式进行编码。如果未声明字符编码, IRIS将使用前面的“输入和输出的字符编码”中描述的默认值。如果这些默认值不正确,请修改XML声明,使其指定实际使用的字符集。**
还可以使用`%XML.Reader`读取任意XML文档并返回DOM(文档对象模型)。
# 创建XML读取器概述
IRIS提供了一些工具,用于读取XML文档并创建与该文档的元素相对应的启用XML的 IRIS对象的一个或多个实例。基本要求如下:
- 该对象的类定义必须扩展`%XML.Adaptor`。除了少数例外,该对象引用的类还必须扩展`%XML.Adaptor`。
提示:如果相应的XML模式可用,可以使用它来生成类(以及任何支持的类)。
- 要导入XML文档,创建`%XML.Reader`的实例,然后调用该实例的方法。这些方法指定XML源文档,将XML元素与启用XML的类相关联,并将源中的元素读取到对象中。
`%XML.Reader`使用类中的`%XML.Adaptor`提供的方法执行以下操作:
- 它使用InterSystems IRIS SAX接口解析和验证传入的XML文档。验证可以包括DTD或XML架构验证。
- 它确定是否有任何启用了XML的对象与XML文档中包含的元素相关,并在读取文档时创建这些对象的内存中实例。
**请注意,`%XML.Reader`创建的对象实例不存储在数据库中;它们是内存中的对象。如果要将对象存储在数据库中,则必须调用%Save()方法(对于持久对象),或者将相关属性值复制到持久对象并保存它。应用程序还必须决定何时插入新数据和何时更新现有数据;`%XML.Reader`无法进行此区分。**
下面的终端会话显示了一个简单的示例。在这里,我们将XML文件读入一个新对象,检查该对象,然后保存该对象:
```java
/// w ##class(PHA.TEST.Xml).ReadXml()
ClassMethod ReadXml()
{
Set reader = ##class(%XML.Reader).%New()
Set file="E:\temp\samplePerson.xml"
Set status = reader.OpenFile(file)
if $$$ISERR(status) { do $System.Status.DisplayError(status) quit }
Write status,!
Do reader.Correlate("Person","Sample.Person")
Do reader.Next(.object,.status)
if $$$ISERR(status) { do $System.Status.DisplayError(status) quit }
Write object.Name,!
Do object.%Save()
q ""
}
```
此示例使用以下示例XML文件:
```java
Worthington,Jeff R.
1976-11-03
Elm City
27820
Best,Nora A.
Weaver,Dennis T.
```
# 创建导入方法
## 总体方法结构
方法应按以下顺序执行以下部分或全部操作:
1. 创建`%XML.Reader`的实例。
2. 也可以指定此实例的`Format`属性,以指定要导入的文件的格式。
默认情况下,InterSystems IRIS假定XML文件为文字格式。如果文件是SOAP编码格式,则必须指明这一点,以便可以正确读取该文件。
3. 可以选择设置此实例的其他属性。
4. 请使用`%XML.Reader`的以下方法之一
- `OpenFile()` -打开文件。
- `OpenStream()`-打开一个流。
- `OpenString()` -打开一个字符串。
- `OpenURL()` -打开一个URL。
在每种情况下,可以选择性地为该方法指定第二个参数,以覆盖`Format`属性的值。
5. 将这个文件中的一个或多个XML元素名与具有相应结构的支持InterSystems IRIS XML的类关联起来。
有两种方法可以做到这一点:
- 使用`Correlate()`方法,它有以下签名:
```
method Correlate(element As %String,
class As %String,
namespace As %String)
```
其中`element`是XML元素名,class是InterSystems IRIS类名(带包),`namespace`是可选的名称空间`URI`。
如果使用`namespace`参数,则匹配仅限于指定命名空间中的指定元素名。
如果将命名空间参数指定为"",则与`Next()`方法中给出的默认命名空间相匹配。
如果不使用`namespace`参数,则只使用元素名进行匹配。
提示:可以反复调用`Correlate()`方法来关联多个元素。
- 使用`CorrelateRoot()`方法,它有以下签名:
```
method CorrelateRoot(class As %String)
```
其中class是InterSystems IRIS类名(带包)。此方法指定XML文档的根元素与指定的类相关。
6. 按如下方式实例化类实例:
如果使用`Correlate()`,则遍历文件中的相关元素,一次循环一个元素。在循环中,使用Next()方法,该方法具有以下签名:
```
method Next(ByRef oref As %ObjectHandle,
ByRef sc As %Status,
namespace As %String = "") as %Integer
```
其中`OREF`是该方法创建的对象,`sc`是状态,`Namespace`是文件的默认名称空间。
- 如果使用`CorrelateRoot()`,请调用`next()`方法一次,这会导致实例化相关类。
`Next()`方法在到达文件末尾时返回0。如果在此之后再次调用`next()`,则将从文件顶部开始再次循环遍历文件中的对象。(指定的关联仍然有效。)
## 错误检查
**上一节提到的大多数方法都返回状态。应该在每个步骤之后检查状态,并在适当的情况下退出。**
## 基本导入示例
名为`test.xml`的以下XML文件:
```xml
姚
鑫
```
我们首先定义一个启用XML的类`MyApp.Person`,它是`Person`的对象表示:
```java
Class MyApp.Person Extends (%Persistent, %XML.Adaptor)
{
Parameter XMLNAME = "Person";
Property Name As %String;
Storage Default
{
%%CLASSNAME
Name
^MyApp.PersonD
PersonDefaultData
^MyApp.PersonD
^MyApp.PersonI
^MyApp.PersonS
%Library.CacheStorage
}
}
```
要将此文件导入到`MyAppPerson`类的实例中,我们可以编写以下方法:
```java
/// w ##class(PHA.TEST.Xml).ImportXml()
ClassMethod ImportXml()
{
// 创建%XML.Reader的实例
Set reader = ##class(%XML.Reader).%New()
// 开始处理文件
Set status = reader.OpenFile("E:\temp\testPerson.xml")
If $$$ISERR(status) {do $System.Status.DisplayError(status)}
// 将类名与XML元素名相关联
Do reader.Correlate("Person","MyApp.Person")
// 从XML文件读取对象
While (reader.Next(.object,.status)) {
Write object.Name,!
}
// 如果在处理过程中发现错误,则将其显示
If $$$ISERR(status) {do $System.Status.DisplayError(status)}
q ""
}
```
```java
DHC-APP>w ##class(PHA.TEST.Xml).ImportXml()
姚
鑫
```
此方法执行几个任务:
- 它使用InterSystems IRIS `SAX`接口解析输入文件。这包括根据文档的DTD或架构(如果指定)验证文档。
- `Correlate()`方法将类`MyApp`关联起来。
`MyPerson`与XML元素``;
``中的每个子元素都成为`MyPerson`的一个属性。
- 它从输入文件中读取每个``元素,直到没有剩余元素。
- 最后,如果循环因错误而终止,则该错误将显示在当前输出设备上。
如上所述,此示例不将对象存储到数据库。因为`MyPerson`是持久对象,所以可以通过在`While`循环中添加以下行来完成此操作:
```java
/// w ##class(PHA.TEST.Xml).ImportXml()
ClassMethod ImportXml()
{
// 创建%XML.Reader的实例
Set reader = ##class(%XML.Reader).%New()
// 开始处理文件
Set status = reader.OpenFile("E:\temp\testPerson.xml")
If $$$ISERR(status) {do $System.Status.DisplayError(status)}
// 将类名与XML元素名相关联
Do reader.Correlate("Person","MyApp.Person")
// 从XML文件读取对象
While (reader.Next(.object,.status)) {
Write object.Name,!
Set savestatus = object.%Save()
If $$$ISERR(savestatus) {do $System.Status.DisplayError(savestatus)}
}
// 如果在处理过程中发现错误,则将其显示
If $$$ISERR(status) {do $System.Status.DisplayError(status)}
q ""
}
```

## 通过HTTPS URL访问文档
对于`OpenURL()`方法,如果文档位于需要`SSL/TLS`的`URL`,请执行以下操作:
1. 使用管理门户创建包含所需连接详细信息的`SSL/TLS`配置。这是一次性的步骤。
2. 使用`%XML.Reader`时,请设置读取器实例的`SSLConfiguration`属性。对于该值,请指定在上一步中创建的SSL/TLS配置的名称。
或者,当使用`%XML.Reader`,还可以执行以下操作:
1. 创建`%Net.HttpRequest`实例。
2. 将该实例的`SSLConfiguration`属性设置为等于管理门户中创建的`SSL/TLS`配置的配置名称。
3. 使用`%Net.HttpRequest`的实例作为`OpenURL()`的第三个参数。
例如:

```java
Class YX.Config Extends (%Persistent, %XML.Adaptor)
{
Parameter XMLNAME = "update";
Property version As %String;
Property name As %String;
Property url As %String;
}
```
```java
/// 请求http的xml,映射到本地类
/// w ##class(PHA.TEST.Xml).ReadXmlHttp("http://192.168.10.3/dthealth/web/csp/version.xml")
ClassMethod ReadXmlHttp(url)
{
set reader = ##class(%XML.Reader).%New()
set request = ##class(%Net.HttpRequest).%New()
set request.SSLConfiguration="yx"
set status = reader.OpenURL(url,,request)
If $$$ISERR(status) {do $System.Status.DisplayError(status)}
// 将类名与XML元素名相关联
Do reader.Correlate("update","YX.Config")
While (reader.Next(.object,.status)) {
Write object.version,!
Write object.name,!
Write object.url,!
}
q ""
}
```
```java
DHC-APP>w ##class(PHA.TEST.Xml).ReadXmlHttp("http://192.168.10.3/dthealth/web/csp/version.xml")
27
Herb
http://192.168.31.124/dthealth/web/csp/Herb.apk
```
### 在服务器需要身份验证时访问文档
**如果服务器需要身份验证,请创建`%Net.HttpRequest`的实例,并设置该实例的用户名和密码属性。还可以如上所述使用SSL(因此还要设置`SSLConfiguration`属性)。然后使用`%Net.HttpRequest`的实例作为`OpenURL()`的第三个参数,如上例所示。**
文章
姚 鑫 · 二月 21, 2021
# 第四十三章 Caché 变量大全 $ZTIMEZONE 变量
包含格林威治子午线的时区偏移量。
# 大纲
```
$ZTIMEZONE
$ZTZ
```
# 描述
`$ZTIMEZONE`可以通过两种方式使用:
- 返回计算机的本地时区偏移量。
- 为当前进程设置本地时区偏移量。
`$ZTIMEZONE`包含从格林威治子午线偏移的时区(以分钟为单位)。 (格林威治子午线包括整个英国和爱尔兰。)此偏移量表示为-1440到1440范围内的有符号整数。格林威治以西的时区指定为正数;格林威治东部的时区指定为负数。 (时区必须以分钟为单位,因为并非所有时区都以小时为单位。)默认情况下,`$ZTIMEZONE`初始化为计算机操作系统设置的时区。
注意:`$ZTIMEZONE`将本地时间调整为固定的偏移量。它不适应夏令时或其他当地时间的变化。 InterSystems IRIS从基础操作系统获取本地时间,该操作系统将本地时间变体应用于为该计算机配置的位置。因此,使用`$ZTIMEZONE`调整的本地时间将从配置的语言环境中获取其本地时间变化,而不是在`$ZTIMEZONE`中指定的时区。
使用格林威治子午线(`$ZTIMEZONE = 0`)的时区计数来计算UTC时间。它与当地格林威治时间不同。格林威治标准时间(GMT)一词可能令人困惑;格林威治的当地时间与冬季的UTC相同。在夏季,它与UTC的差异为一小时。这是因为应用了称为英国夏令时的本地时间变体。
对于使用`$ZTIMEZONE`的函数和程序,经过的本地时间始终是连续的,但是时间值可能需要季节性调整以与本地时钟时间相对应。
## 设定时区
可以使用`$ZTIMEZONE`设置当前InterSystems IRIS进程使用的时区。设置`$ZTIMEZONE`不会更改默认的InterSystems IRIS时区或计算机的时区设置。
注意:更改`$ZTIMEZONE`特殊变量是为某些特殊情况设计的功能。更改`$ZTIMEZONE`并不是更改InterSystems IRIS用于本地日期/时间操作的时区的一致方法。除非已准备好处理所有导致的不一致的程序,否则不应更改`$ZTIMEZONE`特殊变量。
在某些平台上,更改时区可能比更改`$ZTIMEZONE`特殊变量更好。如果平台具有特定于进程的时区设置(例如POSIX系统上的TZ环境变量),则进行外部系统调用来更改特定于进程的时区可能比更改`$ZTIMEZONE`更好。在操作系统级别更改特定于流程的时区将更改UTC的本地时间偏移,并应用确定何时应用本地时变的相应算法。如果默认系统时区在北半球,而所需的过程时区在南半球,则这尤其重要。更改`$ZTIMEZONE`会将本地时间更改为与UTC偏移的新时区,但是确定何时应用本地时变的算法保持不变。
使用`SET`命令将`$ZTIMEZONE`设置为指定的带符号整数分钟数。数字的前导零和小数部分将被忽略。如果在设置`$ZTIMEZONE`时指定非数字值或无值,则InterSystems IRIS会将`$ZTIMEZONE`设置为0(格林威治子午线)。
例如,北美东部标准时间(EST)在格林威治以西五个小时。因此,要将当前的InterSystems IRIS流程设置为EST,则需要指定300分钟。要指定格林威治以东一小时的时区,请指定–60分钟。要指定格林威治本身,可以指定0分钟。
设置`$ZTIMEZONE`:
- 影响无参数的`$NOW()`当地时间值。它更改了`$NOW()`的时间部分,并且此时间更改也可以更改当前进程的`$NOW()`的日期部分。 `$NOW()`精确地反映了`$ZTIMEZONE`设置,其值未针对本地时变进行调整。
- 影响`$HOROLOG`当地时间值。 `$HOROLOG`从`$ZTIMEZONE`获取其时区值,然后季节性调整本地时间,例如夏令时。因此,`$HOROLOG`始终符合本地时钟时间,但全年的`$HOROLOG`经过时间不是连续的。
- 影响`%SYSTEM.Util`类方法`IsDST()`,`UTCtoLocalWithZTIMEZONE()`和`LocalWithZTIMEZONEtoUTC()`。
- 不会影响`$ZTIMESTAMP`或`$ZHOROLOG`值。
- 不会影响`$ZDATE`,`$ZDATEH`,`$ZDATETIME`,`$ZDATETIMEH`,`$ZTIME`和`$ZTIMEH`函数执行的日期和时间格式转换。
- 不会影响`$NOW(n`)函数。
- 不会影响`%SYSTEM.Process`类的`FixedDate()`类方法,该方法将`$HOROLOG`中的日期设置为固定值。
更改`$ZTIMEZONE`后发生以下异常:
- `$ZDATETIME($HOROLOG,1,7)`通常返回UTC时间,但是如果`$ZTIMEZONE`已更改,它将不返回UTC时间。
- 如果`$ZTIMEZONE`已更改,`$ZDATETIME($HOROLOG,1,5)`将不会返回正确的时区偏移量。
- 如果`$ZTIMEZONE`已更改,则本地时间和UTC时间之间的`$ZDATETIME($HOROLOG,-3)`和`$ZDATETIMEH($ZTIMESTAMP,-3)`转换将不正确。
## 其他时区方法
可以通过调用`TimeZone()`类方法来获取相同的时区信息,如下所示:
```java
DHC-APP>WRITE $SYSTEM.SYS.TimeZone()
-480
```
可以使用tformat值为5或6的`$ZDATETIME`和`$ZDATETIMEH`函数,将本地时间变化作为日期和时间字符串的一部分返回,如以下示例所示
```java
DHC-APP>WRITE !,$ZDATETIME($HOROLOG,1,5)
02/10/2021T18:24:21+08:00
```
该字符串的最后一部分(+08:00)表示系统的本地时间变化设置,以格林威治子午线为单位,以小时和分钟为单位进行偏移。注意,这种变化不一定是时区偏移量。在上述情况下,时区位于格林威治(-5:00)西部5小时,但是本地时区(夏令时)将时区时间偏移一小时到-04:00。设置`$ZTIMEZONE`将更改`$ZDATETIME($HOROLOG,1,5)`返回的当前处理日期和时间,但不会更改系统本地时间变化设置。
## `$ZDATETIMEH`使用时区设置
可以将`$ZDATETIMEH`与`dformat = -3`一起使用,以将协调世界时(UTC)日期和时间值转换为本地时间。该函数将UTC值(`$ZTIMESTAMP`)作为输入。它使用本地时区设置来返回相应的日期和时间,并在适用的情况下应用本地时变(例如夏时制)。
```java
/// d ##class(PHA.TEST.SpecialVariables).ZTIMEZONE()
ClassMethod ZTIMEZONE()
{
SET clock=$HOROLOG
SET stamp=$ZDATETIMEH($ZTIMESTAMP,-3)
WRITE !,"本地/本地日期和时间: ",$ZDATETIME(clock,1,1,2)
WRITE !,"UTC/本地日期和时间: ",$ZDATETIME(stamp,1,1,2)
}
```
```java
DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZTIMEZONE()
本地/本地日期和时间: 02/10/2021 18:31:27.00
UTC/本地日期和时间: 02/10/2021 18:31:27.94
```
## 使用`$ZTIMEZONE`的本地/UTC转换方法
`%SYSTEM.Util`类的两个类方法在本地日期和时间与UTC日期和时间之间进行转换:`UTCtoLocalWithZTIMEZONE()`和`LocalWithZTIMEZONEtoUTC()`。这些方法受`$ZTIMEZONE`更改的影响。
```java
/// d ##class(PHA.TEST.SpecialVariables).ZTIMEZONE1()
ClassMethod ZTIMEZONE1()
{
WRITE $SYSTEM.Util.UTCtoLocalWithZTIMEZONE($ZTIMESTAMP),!
WRITE $HOROLOG,!
WRITE $SYSTEM.Util.LocalWithZTIMEZONEtoUTC($H),!
WRITE $ZTIMESTAMP
}
```
```java
DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZTIMEZONE1()
65785,66819.613
65785,66819
65785,38019
65785,38019.614
```
# 示例
以下示例返回当前时区:
```java
/// d ##class(PHA.TEST.SpecialVariables).ZTIMEZONE2()
ClassMethod ZTIMEZONE2()
{
SET zone=$ZTIMEZONE
IF zone=0 {
WRITE !,"时区是格林威治标准时间"
} ELSEIF zone>0 {
WRITE !,"时区是 ",zone/60," 格林威治以西"
} ELSE {
WRITE !,"时区是 ",(-zone)/60," 格林威治以东"
}
}
```
```java
DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZTIMEZONE2()
时区是 8 格林威治以东
```
以下示例显示了设置时区可以更改日期和时间:
```java
/// d ##class(PHA.TEST.SpecialVariables).ZTIMEZONE3()
ClassMethod ZTIMEZONE3()
{
SET zonesave=$ZTIMEZONE
WRITE !,"当前时区的日期: ",$ZDATE($HOROLOG)
IF $ZTIMEZONE=0 {
SET $ZTIMEZONE=720
} ELSEIF $ZTIMEZONE>0 {
SET $ZTIMEZONE=($ZTIMEZONE-720)
} ELSE {
SET $ZTIMEZONE=($ZTIMEZONE+720)
}
WRITE !,"Date halfway around the world: ",$ZDATE($HOROLOG)
WRITE !,"格林威治天文台的日期: ",$ZDATE($ZTIMESTAMP)
SET $ZTIMEZONE=zonesave
}
```
```java
DHC-APP> d ##class(PHA.TEST.SpecialVariables).ZTIMEZONE3()
当前时区的日期: 02/10/2021
Date halfway around the world: 02/10/2021
格林威治天文台的日期: 02/10/2021
```
以下示例确定本地时间是否与时区时间相同:
```java
/// d ##class(PHA.TEST.SpecialVariables).ZTIMEZONE4()
ClassMethod ZTIMEZONE4()
{
SET localnow=$HOROLOG, stamp=$ZTIMESTAMP
WRITE !,"当地日期和时间: ",$ZDATETIME(localnow,1,1)
SET clocksecs=$PIECE(localnow,",",2)
SET stampsecs=$EXTRACT(stamp,7,11)-($ZTIMEZONE*60)
IF clocksecs=stampsecs {
WRITE !,"没有本地时间变量:"
WRITE !,"本地时间是时区时间"
} ELSE {
IF clocksecs=stampsecs+3600 {
WRITE !,"夏令时变体:"
WRITE !,"从时区时间偏移1小时的本地时间"
} ELSE {
WRITE !,"当地时间和时区时间为"
WRITE !,(clocksecs-stampsecs)/60," 分钟不同"
}
}
QUIT
}
```
```java
DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZTIMEZONE4()
当地日期和时间: 02/10/2021 18:40:21
没有本地时间变量:
本地时间是时区时间
```
问题
kun an · 五月 27, 2021
按照此网址的指示 https://gettingstarted.intersystems.com/language-quickstarts/java-quickstart/ 下载quickstarts-java示例代码 运行xepplaystocktsTask1模块程序提示InterSystems XEP is not supported by the specified server 请问有人知道具体原因吗我的server有什么不对吗。
使用客户端的server manager添加server没有server类型的选项,是因为我使用的cache版本目前不支持吗 不知您测试使用的具体是IRIS哪个版本,以及运行在哪个操作系统上。
还有提到的客户端使用的是哪个IRIS版本。低版本的客户端没法连上高版本的IRIS服务端,Caché客户端更加连不上IRIS的服务器。 您好 感谢解答。我使用的是IRISHealth_Community-2020.1.0.217.1-win_x64 运行在windows10上。我在官网教程上下载的示例代码,其余访问方式都运行正常就xep方式报错, 提示指定的server不支持, 不知道都有什么类型的server,哪些server可以支持 请参考下面资料检查是否符合运行需求以及相关配置是否正确:https://docs.intersystems.com/irisforhealth20201/csp/docbook/Doc.View.cls?KEY=BJAVXEP_intro#BJAVXEP_intro_config
其中的检查包括JDK版本、安装时的选项、服务%Service_CallIn是否打开以及环境变量CLASSPATH 的设置
文章
Michael Lei · 十二月 30, 2021
对于那些在某种程度上需要测试ECP的水平可扩展性(计算能力和/或用户和进程的并发性),但又懒得建立环境、配置服务器节点等的人来说,我刚刚在Open Exchange上发布了OPNEx-ECP部署的应用/示例。
这只是一个小项目,可以在GitHub上找到,供所有人使用。基本上,它可以让你在自己的笔记本上建立3个InterSystems IRIS实例作为应用服务器,1个实例作为数据服务器,通过ECP连接。
它还将启动一个LoadBalancer(使用WebGateway),作为一个独立于其他实例的节点,它将是进入你的系统的入口(通过HTTP),它将负责在应用服务器之间分配请求。
只需很少的工作,你就可以添加你的应用代码,并利用该项目在分布式ECP中测试你的应用。应用服务器预装了其他有趣的开源模块(ZPM, WebTerminal, RestForms2 y Restforms2-ui),还有一个小包OPNEx-Model,其中有一些类和一个REST服务作为例子来实现。
详细信息请访问Open Exchange或GitHub。
好了,希望这对你有帮助!
祝大家编程愉快!!
文章
姚 鑫 · 三月 31, 2021
# 第十四章 使用SQL Shell界面(二)
# 存储和调用SQL语句
## 通过数据回调
SQL Shell自动将在终端会话期间发出的每个成功的SQL语句存储在本地缓存中,并为其分配一个顺序号。这些数字用于在当前Terminal过程中重新调用以前的SQL语句。 SQL Shell仅将数字分配给成功的SQL语句。如果在准备SQL语句期间发生错误,则不会分配任何编号。这些数字分配不是特定于名称空间的。以下是可用的数字调用命令:
- `#`:可以使用#列出所有先前缓存的SQL语句及其分配的编号。
- `#n`:可以通过在SQL Shell提示符下指定#n来调用并执行先前的SQL语句,其中`n`是SQL Shell分配给该语句的整数。
- `#0`:可以通过在SQL Shell提示符下指定`#0`来调用并执行最近准备的SQL语句。 `#0`调用最近准备的SQL语句,而不必调用最近执行的SQL语句。因此,调用和执行SQL语句对#0调用哪个SQL语句没有影响。
通过数字调用SQL语句不会为该语句分配新的数字。 SQL Shell在终端会话的持续时间内顺序分配数字;退出并重新进入SQL Shell或更改名称空间不会影响数字分配或先前分配的数字的有效性。
要删除所有号码分配,请使用`#CLEAR`并在显示的提示符下确认此操作。这将删除所有先前的号码分配,并从1重新开始号码分配。
## 通过名字回调
可以选择为SQL语句分配名称,然后按名称重新调用该语句。这些名称用于重新调用从任何当前用户的Terminal进程发出的SQL语句。通过名称保存和调用SQL语句有两种方法:
- 使用`SAVEGLOBAL`保存到全局;使用`OPEN`从全局调用。
- 使用`SAVE`保存到文件;使用`LOAD`从文件中调用。
### 保存到全局变量
要将全局名称分配给最新的SQL语句,请使用sql shell命令`saveglobal`名称,该名称可以缩写为`SG`名称。然后,可以使用SQL Shell命令打开名称来调用全局的SQL语句。如果`Executemode`是立即的,则SQL shell都会调用并执行该语句。如果延迟了`executemode`,则将准备该语句,但在指定`GO`命令之前,不会执行该语句。
每次使用打开名称以全局名称调用SQL语句时,SQL shell会为语句分配新号码。旧的和新数字都对调用数字仍然有效。
名称可以包含除空白字符之外的任何可打印字符。名称中的字母区分大小写。名称可以是任何长度。名称特定于当前命名空间。可以多次使用不同名称保存相同的SQL语句;所有已保存的名称都保持有效。如果使用已分配的名称保存SQL语句,则SQL Shell会提示是否希望覆盖现有名称,将其重新分配给新的SQL语句。
为当前命名空间分配全局名称。可以使用SQL Shell `L`(或列表)命令列出当前命名空间的所有分配的全局名称。分配后,所有当前用户的终端进程都可以使用名称。在创建它结束的终端进程后,分配的名称仍然存在。如果没有名称分配,则列表返回“保存”消息的“无语句”。
要删除全局名称分配,请使用清除名称。要删除当前命名空间的所有全局名称分配,请在显示的PROMP下使用清除并确认此操作
### 保存到文件
要将文件名分配给最新的SQL语句,请使用SQL Shell命令保存名称。然后,可以使用SQL Shell命令加载名称来调用SQL语句。如果`Executemode`是立即的,则SQL shell都会调用并执行该语句。每次使用`Load Name`按文件名调用SQL语句时,SQL Shell会将新号码分配给语句。旧的和新数字都对召回数字仍然有效。
名称可以包含除空白字符之外的任何可打印字符。名称中的字母区分大小写。名称可以是任何长度。名称特定于当前命名空间。可以多次使用不同名称保存相同的SQL语句;所有已保存的名称都保持有效。如果尝试使用已分配的名称保存SQL语句,则SQL Shell会提示是否希望覆盖现有名称,将其重新分配给新的SQL语句。
为当前命名空间分配名称。分配后,所有当前用户的终端进程都可以使用名称。在创建它结束的终端进程后,分配的名称仍然存在。
# 清除缓存查询Query
SQL shell提供了清除(缩写`p`)命令,以清除当前命名空间中的所有缓存查询。此命令清除名称空间中的所有缓存查询,而不仅仅是使用SQL Shell生成的查询。
`$SYSTEM.SQL.Purge()`方法和管理门户操作下拉列表选项为提供了更具体的选项,仅清除所选择的缓存查询或清除命名空间中的所有缓存查询。
# 配置SQL shell
- 可以使用Management Portal配置SQL Shell默认值。
- 可以使用SQL Shell参数配置单个SQL shell。更改SQL Shell参数覆盖SQL shell的当前调用的系统范围默认值;它不会更改系统范围的SQL shell默认值。
以下是可用的SQL Shell配置选项,相应的shell参数和默认设置:
管理门户shell配置| Shell 参数| 默认
---|---|---
Select Mode| selectmode| Logical
SQL Dialect (TSQL) |dialect (TSQL) |IRIS
Schema Search Path| path| none
Result Column Alignment| colalign |Delimiter
Command Prefix (TSQL)| commandprefix (TSQL)| none
Result Output Display Mode |displaymode |Current Device
Display Path| displaypath|none
Display File| displayfile| none
Display File Translate Table| displaytranslatetable|none
Echo Mode |echo| On
Execute Mode| executemode|| Immediate
Messages Mode| messages| On
IF condition to allow execution || 1
| |log| Off
标记为(TSQL)的参数主要用于从SQL Shell执行`Sybase`或`MSSQL` `Transact-SQL`代码。
## 配置SQL Shell系统范围默认值
转到管理门户,选择系统管理,配置,SQL和对象设置,SQL。选择SQL Shell选项卡。查看并设置SQL Shell系统范围的当前默认设置。
如果更改一个或多个配置设置,则在管理门户路径之后立即由屏幕的左上角的星号(`*`)表示。例如,系统>配置> SQL *。按SAVE按钮接受更改。激活更改,星号消失。
## 为SQL shell配置参数
SQL Shell配置参数特定于当前终端进程上的当前SQL Shell调用。设置跨名称空间应用。但是,如果退出SQL Shell,则所有SQL Shell参数都会重置为系统宽的默认值。 Intersystems Iris提供系统默认值;您可以使用Set Save建立当前进程的不同默认值,如下所述。
SQL shell set命令(没有参数)显示当前shell配置参数,如以下示例所示。在此示例中,该组显示系统默认值,这些值是调用SQL Shell时建立的值:
```java
[SQL]USER>>SET
commandprefix = ""
dialect = IRIS
displayfile =
displaymode = currentdevice
displaypath =
displaytranslatetable =
echo = on
executemode = immediate
log = off
messages = on
path = SQLUser
selectmode = logical
[SQL]USER>>
```
要显示单个配置参数的当前设置,请指定`set param`。例如,`SET SelectMode`返回当前选择介绍设置。
可以使用`SQL Shell Set`命令设置shell配置参数。 SQL Shell调用的持续时间持续一个设定值;每次调用SQL shell时,参数都会重置为默认值。设置可以使用以下任一语法表单:
```java
SET param value
SET param = value
```
参数和值都不区分大小写。允许空间,但不需要,之前和之后。
SQL Shell `Set Save`命令将当前shell配置参数设置保存为用户默认值。这些默认值应用于当前进程的所有后续SQL Shell调用。它们也被应用于SQL Shell默认值,以在该用户调用的终端过程中的任何后续调用的SQL Shell。它们仍然有效,直到特别重置。使用`Set`保存不会影响当前正在运行的SQL Shell调用。
SQL Shell `Set Clear`命令清除(重置为系统默认值)当前进程的当前shell配置参数设置。 Intersystems IRIS将此重置应用于当前进程的后续SQL Shell调用,或者当前用户调用的任何新终端进程。设置清除不会影响当前运行的SQL Shell调用。
既不设定保存也没有设置清除更改系统范围的SQL Shell Shell默认设置,使用管理门户进行配置和显示。
## Setting COLALIGN
可以使用`Set Colalign`来指定用于显示查询`ResultSet`数据和列标题的空格格式。可用选项包括:
- 分隔符:`ResultSet`标题/数据列将基于标准分隔符(标签)对齐。这是默认值。
- 标题:`ResultSet`标题/数据列将基于列标题的长度和标准分隔符(标签)对齐。
- 数据:`ResultSet`标题/数据列将基于列数据属性的精度/长度和标准分隔符(标签)对齐。
## 设置displaymode和displaytranslatetable
可以使用`Set DisplayMode`指定用于显示查询数据的格式,如以下示例所示:
```java
DHC-APP>DO $SYSTEM.SQL.Shell()
SQL Command Line Shell
----------------------------------------------------
The command prefix is currently set to: .
Enter q to quit, ? for help.
DHC-APP>>SET DISPLAYMODE XML
displaymode = xml
DHC-APP>>
```
`DisplayMode`默认值是`CurrentDevice`,其在TXT格式中显示终端上的查询数据。可以指定`set displaymode = cur`恢复`CurrentDevice`默认值。
其他可用选项有`TXT`、`HTML`、`PDF`、`XML`和`CSV`。
格式的选择决定了文件类型。
InterSystems IRIS创建这种类型的文件,将查询数据写入该文件,并在可能的情况下启动适当的程序来显示该查询数据文件。
对于除TXT之外的所有选项,将创建第二个文件来记录结果集消息。
默认情况下,SQL Shell在InterSystems IRIS mgr\Temp\目录中创建这些文件,并分配一个随机生成的带有适当文件类型后缀的文件名。
生成的消息文件名与数据文件名相同,除了附加的字符串`“Messages”`。
对于`HTML`、`PDF`和`XML`选项,消息文件具有与查询数据文件相同的文件类型后缀。
对于`CSV`选项,消息文件具有`TXT`文件类型后缀。
以下是`DisplayMode = TXT`时创建的文件的示例:
```
C:\InterSystems\IRIS\mgr\Temp\sGm7qLdVZn5VbA.txt
C:\InterSystems\IRIS\mgr\Temp\sGm7qLdVZn5VbAMessages.txt
```
每次运行查询时,SQL shell都会创建一个具有随机生成的文件名的新文件。
如果显示屏是`txt`或`csv`,则可以选择在执行格式转换时指定要应用的翻译表的名称可以指定`SET DISPLAYTRANSLATE`或`SET DISPLAYTRANSLATERATE`。转换表名称值区分大小写。
如果`DisplayMode`被设置为除`CurrentDevice`以外的值,则任何查询结果集包含控制字符的数据会导致生成的警告消息。通常,控制字符仅在逻辑模式下出现在查询结果集数据中。例如,列表结构中的数据包含在逻辑模式下显示的控制字符。因此,建议将`DisplayMode`设置为`CurrentDevice`以外的值时,还将`SelectMode`设置为显示或ODBC。
### 设置displayfile和displaypath
如果`DisplayMode`设置为`“CurrentDevice以外的值”`,则可以使用`DisplayFile`和`DisplayPath`参数指定目标文件位置:
- `DISPLAYFILE:`设置为一个没有后缀的简单文件名;
例如:`SET DISPLAYFILE = myfile`。
也可以将该参数设置为部分限定路径,系统间的IRIS将该路径追加到`DISPLAYPATH`值或默认目录中,根据需要创建子目录;
例如:`SET DISPLAYFILE = mydir\myfile`。
如果设置了`DISPLAYPATH`,系统将在指定的目录中创建一个以该文件名命名的文件;
如果没有设置`DISPLAYPATH`,系统将在InterSystems IRIS mgr\Temp\目录下创建一个以该文件名命名的文件。
- `DISPLAYPATH:`根据操作系统平台的不同,设置为以斜杠(`“/”`)或反斜杠(`“\”`)结尾的现有的全限定目录路径结构。
如果设置了`DISPLAYFILE`,系统将在此目录下创建一个名为`DISPLAYFILE`的文件;
如果没有设置`DISPLAYFILE`,系统将在该目录下创建一个随机生成的文件名文件。
如果目录`“DISPLAYPATH”`不存在,InterSystems IRIS将忽略`“DISPLAYPATH”`和`“DISPLAYFILE”`的设置,使用默认目录和随机生成的默认文件名。
必要时,系统自动在`DISPLAYPATH`值的末尾添加斜杠(或反斜杠)和/或从`DISPLAYFILE`值的开始删除斜杠(或反斜杠),以创建有效的完全限定目录路径。
设置`DISPLAYMODE`、`DISPLAYFILE`和`DISPLAYPATH`:
```java
DHC-APP>>SET DISPLAYMODE XML
displaymode = xml
DHC-APP>>SET DISPLAYFILE = myfile
displayfile = myfile
DHC-APP>>SET DISPLAYPATH = C:\temp\mydir\
displaypath = C:\temp\mydir\
DHC-APP>>
```
执行查询时,SQL shell将生成以下文件。第一个包含查询数据。第二个包含Query执行产生的任何消息:
```java
C:\temp\mydir\myfile.xml
C:\temp\mydir\myfileMessages.xml
```
如果既不指定`DISPLAYFILE`也不指定`DISPLAYPATH`,系统将在Mgr\Temp\目录下为InterSystems IRIS安装(例如,`C:\InterSystems\IRIS\Mgr\Temp\`)创建一个随机生成的文件名。
如果显示屏未设置为`CurrentDevice`,则每次使用`displayfile`集运行查询时,命名文件中的任何现有数据都会被新查询数据替换为新的查询数据。每次使用`displayfile`未设置查询时,SQL shell都会使用随机生成的文件名和新的相应邮件文件创建一个新文件。
如果`displaymode`设置为`currentDevice`,则`DisplayFile`和`DisplayPath`参数无效。
## 设置executemode
SQL Shell支持立即和延迟的SQL语句执行。立即执行准备并在按Enter键时执行指定的SQL语句。延迟执行准备在输入Enter时,但在指定转到SQL提示符之前,不会执行它。
可用选项已立即设置`ExecuteMode`(默认值),设置`ExecuteMode`延迟和设置`ExecuteMode`以显示当前模式设置。以下示例设置执行模式:
```java
DHC-APP>>SET EXECUTEMODE DEFERRED
Executemode = deferred
```
延迟执行允许准备多个SQL查询,然后按名称或编号调用它们以进行执行。要执行准备好的SQL语句,请调用所需的语句(来自适当的命名空间),然后指定`Go`。
以下示例显示了在延迟模式下准备三个查询。前两个保存并分配了调用名称;第三个未分配一个名称,但可以通过数字来调用:
```java
DHC-APP>>SELECT TOP 5 Name,Home_State FROM Sample.Person
1. SELECT TOP 5 Name,Home_State FROM Sample.Person
---------------------------------------------------------------------------
DHC-APP>>SAVE 5sample
...statement saved as: 5sample
DHC-APP>>SELECT TOP 5 Name,Home_State FROM Sample.Person ORDER BY Home_State
2. SELECT TOP 5 Name,Home_State FROM Sample.Person ORDER BY Home_State
---------------------------------------------------------------------------
DHC-APP>>SAVE 5ordered
...statement saved as: 5ordered
DHC-APP>>SELECT Name,Home_State FROM Sample.Person ORDER BY Home_State
3. SELECT Name,Home_State FROM Sample.Person ORDER BY Home_State
---------------------------------------------------------------------------
```
以下示例显示了延迟模式执行前一个示例中定义的两个查询的执行。请注意,此示例通过名称调用一个查询(在调用SQL Shell提供新号码时,并按编号调用一个查询:
```java
DHC-APP>>go
C:\InterSystems\Cache\mgr\Temp\ffQlXfFdbGnOxA.xml
Messages.xml
statement prepare time(s)/globals/lines/disk: 0.0526s/45464/263430/5ms
execute time(s)/globals/lines/disk: 0.2948s/153553/1042318/75ms
---------------------------------------------------------------------------
```
## Setting ECHO
可以使用`Set Echo`来指定是否将查询结果恢复到SQL Shell。如果指定`SET Echo = OFF`,则准备查询,定义缓存查询,并执行查询。终端没有查询结果。这在以下示例中显示:
```java
DHC-APP>>set echo=off
echo = off
DHC-APP>>SELECT Name,Age FROM Sample.Person
4. SELECT Name,Age FROM Sample.Person
--------------------------------------------------------------------------
```
如果指定`SET Echo = ON`(默认值),则将查询结果显示给终端。这在以下示例中显示:
```java
DHC-APP>>set echo=on
echo = on
DHC-APP>>SELECT Name,Age FROM Sample.Person
5. SELECT Name,Age FROM Sample.Person
---------------------------------------------------------------------------
DHC-APP>>go
C:\InterSystems\Cache\mgr\Temp\LVZpPfjfxXXJBg.xml
Messages.xml
statement prepare time(s)/globals/lines/disk: 0.0001s/5/187/0ms
execute time(s)/globals/lines/disk: 0.1613s/152365/1040157/0ms
---------------------------------------------------------------------------
```
`SET Echo`如果`displaymode = currentDevice`(默认值)仅有意义。
`SET ECHO`和`SET MESSAGES`指定终端显示的内容;
它们不会影响查询的准备或执行。
如果`SET MESSAGES=OFF和SET ECHO=OFF`,则查询准备好了,一个缓存的查询被创建,查询执行创建一个查询结果集,但是没有返回给终端。
## Setting MESSAGES
可以使用`SET MESSAGES`来指定是否显示查询错误消息(如果不成功)或查询执行信息(如果成功):
- 如果查询执行不成功:如果指定`SET MESSAGES=OFF`,则终端不会显示任何信息。
如果指定`SET MESSAGES=ON`(默认值),则返回查询错误提示,如下所示`:error #5540: SQLCODE: -30 message: Table 'SAMPLE`。
值得注意的没有找到。
- 如果查询执行成功:如果指定`SET MESSAGES=OFF`,则只显示查询结果和受影响的`n`行。
如果指定`SET MESSAGES=ON`(默认值),则查询结果和受影响的`n`行(`s`行)后面紧跟着语句准备度量、语句执行度量和生成的缓存查询的名称。
准备和执行指标以运行时间(以毫秒为单位)、全局引用总数、执行的命令总数和磁盘读取延迟(以毫秒为单位)来衡量。
设置`DISPLAYMODE`不会改变`SET MESSAGES=ON`时显示的信息。
一些`DISPLAYMODE`选项同时创建一个查询结果集文件和一个消息文件。
该消息文件包含结果集消息,而不是`set messages =ON`时显示到终端的查询准备和执行消息。
设置消息并设置`echo`指定终端上显示的内容;它们不会影响查询的准备或执行。如果`SET MESSAGENT = OFF`和`SET ECHO = OFF`,则准备成功的查询,创建缓存的查询,查询执行创建查询结果集,但没有返回到终端。
## Setting LOG
可以使用`Set`日志指定是否将SQL Shell活动记录到文件。可用选项包括:
- `SET LOG OFF`: 默认值。 Intersystems IRIS不会为当前SQL Shell记录活动。
- `SET LOG ON`: Intersystems Iris将SQL Shell活动记录到默认日志文件。
- `SET LOG pathname`:Intersystems Iris将SQL Shell活动记录到`Pathname`指定的文件。
`SET LOG ON`在IRIS\mgr\namespace中创建一个日志文件,其中namespace是进程当前命名空间的名称。
这个默认日志文件名为xsqlnnnn。
其中nnnn是当前进程的进程ID (pid)号。
日志文件可以挂起并恢复。
创建日志文件后,`SET log OFF`会挂起对该日志文件的写入。
设置`LOG ON`恢复写入默认日志文件。
日志重新启动:日志恢复时,将日期时间写入日志文件。
设置`LOG ON`总是激活默认日志文件。
因此,如果暂停写入指定的路径名日志文件,则在恢复时必须指定`SET log pathname`。
激活日志文件创建终端上显示的SQL Shell活动的副本;它不会重定向SQL Shell终端输出。 SQL Shell Log为失败的SQL执行和SQL代码记录SQL错误,并为成功的SQL执行而导致的行计数。 SQL Shell日志不会记录结果集数据。
如果日志已处于活动状态,则指定“设置”登录无效。如果日志已处于活动状态,则指定设置日志路径名暂停当前日志并激活路径名指定的日志。、
## Setting PATH
可以使用SET路径架构来设置Schema Search Path,SQL用于提供不合格表名的正确架构名称。架构可以是单个架构名称,或者逗号分隔的架构名称列表,如下例所示:
```java
DHC-APP>>SET PATH cinema,sample,user
path = cinema,sample,user
```
没有任何参数的设置路径删除了当前架构搜索路径,恢复系统范围的默认模式名称。
如果未指定`SET`路径架构,或者在指定的模式中找不到表,则SQL Shell使用系统范围的默认模式名称。
## Setting SELECTMODE
可以使用`SetSeliteMode`指定用于显示查询数据的模式。
```java
DHC-APP>>SET SELECTMODE DISPLAY
selectmode = display
```
可用选项显示,逻辑和ODBC。逻辑是默认值。要确定当前模式,请指定`SETELESMODE`,无需值:
```java
DHC-APP>>SET SELECTMODE logical
selectmode = logical
```
`%List`数据使用非打印字符进行编码。因此,当`SelectMode =逻辑`时,SQL shell将`%List`数据值显示为`$listbuild`语句,例如以下`$lb("White","Green")`。时间数据类型数据支持分数秒。因此,当`SelectMode = ODBC`时,SQL Shell显示分数秒,这与ODBC标准不对应。实际的ODBC时间数据类型截断分数秒。
还可以使用`SET SELECTMODE`指定输入数据是否将从显示格式转换为逻辑存储格式。
要进行此数据转换,必须使用`select`运行时模式编译SQL代码。
在执行时,`SET SELECTMODE`必须设置为`LOGICAL`(默认值)。
文章
Michael Lei · 三月 12, 2022
在Linux上设置TZ环境变量
v2015.1的更新检查表建议在Linux平台上设置TZ环境变量,并指出了tzset的手册。建议这样做是为了提高Caché的时间相关功能的性能。你可以在下文找到更多关于这个的信息:
Linux TZ环境变量未被设置以及对Caché的影响
我的CentOS 7测试系统上的manpage(RHEL 6也是这样说的)有这样的说法。
"tzset()函数从TZ环境变量初始化tzname变量。 这个函数会被其他依赖时区的时间转换函数自动调用"。
那么,你如何设置TZ?它是如何影响Linux服务器上的时间的?让我们来看看我们能学到什么:
系统时区 --
对于我的测试,我在一个虚拟的CentOS系统上使用Ensemble 2016.1。首先让我们检查一下系统的时区。system-config-date工具可以告诉你。
那么"系统时钟使用UTC "是怎么回事?这指的是服务器的硬件时钟。在专用服务器上,UTC是很常见的。当Linux与Windows进行双启动配置时,情况并非如此(Windows的系统时钟使用本地时间)。
由于双启动配置在Cache'和Ensemble安装中并不常见,我就不多说了。这里的关键思想是,系统时区要设置为服务器的时区,并且硬件时钟时间要设置正确。
用户看见的时间和日期–
让我们看一看。这里是我的终端会话的一个截图:
看起来不错。我的shell进程(运行date命令)和我的Caché进程显示相同的时间,除了我需要输入WRITE命令的几秒钟。
设置 TZ 变量 --
现在让我们在环境中设置TZ。做到这一点的命令是tzselect。下面是一个设置TZ的命令输出的脚本:
[ehemdal@localhost ~]$ tzselect
Please identify a location so that time zone rules can be set correctly.
Please select a continent or ocean.
1) Africa
2) Americas
3) Antarctica
4) Arctic Ocean
5) Asia
6) Atlantic Ocean
7) Australia
8) Europe
9) Indian Ocean
10) Pacific Ocean
11) none - I want to specify the time zone using the Posix TZ format.
#? 2
Please select a country.
1) Anguilla 19) Dominican Republic 37) Peru
2) Antigua & Barbuda 20) Ecuador 38) Puerto Rico
3) Argentina 21) El Salvador 39) St Barthelemy
4) Aruba 22) French Guiana 40) St Kitts & Nevis
5) Bahamas 23) Greenland 41) St Lucia
6) Barbados 24) Grenada 42) St Maarten (Dutch)
7) Belize 25) Guadeloupe 43) St Martin (French)
8) Bolivia 26) Guatemala 44) St Pierre & Miquelon
9) Brazil 27) Guyana 45) St Vincent
10) Canada 28) Haiti 46) Suriname
11) Caribbean NL 29) Honduras 47) Trinidad & Tobago
12) Cayman Islands 30) Jamaica 48) Turks & Caicos Is
13) Chile 31) Martinique 49) United States
14) Colombia 32) Mexico 50) Uruguay
15) Costa Rica 33) Montserrat 51) Venezuela
16) Cuba 34) Nicaragua 52) Virgin Islands (UK)
17) Curacao 35) Panama 53) Virgin Islands (US)
18) Dominica 36) Paraguay
#? 49
Please select one of the following time zone regions.
1) Eastern (most areas) 16) Central - ND (Morton rural)
2) Eastern - MI (most areas) 17) Central - ND (Mercer)
3) Eastern - KY (Louisville area) 18) Mountain (most areas)
4) Eastern - KY (Wayne) 19) Mountain - ID (south); OR (east)
5) Eastern - IN (most areas) 20) MST - Arizona (except Navajo)
6) Eastern - IN (Da, Du, K, Mn) 21) Pacific
7) Eastern - IN (Pulaski) 22) Alaska (most areas)
8) Eastern - IN (Crawford) 23) Alaska - Juneau area
9) Eastern - IN (Pike) 24) Alaska - Sitka area
10) Eastern - IN (Switzerland) 25) Alaska - Annette Island
11) Central (most areas) 26) Alaska - Yakutat
12) Central - IN (Perry) 27) Alaska (west)
13) Central - IN (Starke) 28) Aleutian Islands
14) Central - MI (Wisconsin border) 29) Hawaii
15) Central - ND (Oliver)
#? 1
The following information has been given:
United States
Eastern (most areas)
Therefore TZ='America/New_York' will be used.
Local time is now: Tue May 31 11:21:04 EDT 2016.
Universal Time is now: Tue May 31 15:21:04 UTC 2016.
Is the above information OK?
1) Yes
2) No
#? 1
You can make this change permanent for yourself by appending the line
TZ='America/New_York'; export TZ
to the file '.profile' in your home directory; then log out and log in again.
Here is that TZ value again, this time on standard output so that you
can use the /usr/bin/tzselect command in shell scripts:
America/New_York
[ehemdal@localhost ~]$
文件~/.profile(如果它存在的话)会在你登录时运行,并为你设置TZ变量。如果你使用的是/bin/sh或/bin/bash以外的shell,你可能会使用一个不同的初始化文件。我设置了这个并重新登录。如果你更新一个文件,如/etc/profile,你可以为所有用户应用这个。
这里你可以看到,TZ是为我的用户(ehemdal)设置的,但不是为root用户设置的。
用户看到的TZ,时间和日期–
如果一个用户从不同的时区连接到你的服务器,会发生什么?TZ变量有助于保持用户本地时间的正确性,同时将时区和夏令时的管理权掌握在操作系统手中。这也会影响到Cache'使用的时间。举个例子,我决定把我的时区改为檀香山的时区。
下面是两张截图,显示了结果。
我的用户进程已将时区设置为太平洋/檀香山。Root的进程没有设置TZ(所以使用的是系统时区America/New_York)。在操作系统层面(使用日期命令),显示的是两个用户的当地时间。日期命令反映了用户的本地时间(用户ehemdal为HST,root为EDT)。由于$HOROLOG的值来自于用户进程可用的操作系统时间,所以$H值对两个用户来说是不同的。
我选择檀香山时间作为一个有趣的例子,因为夏威夷不遵守夏令时。 通过为所有用户正确设置TZ,本地时间可以为遵守夏令时的用户 "提前和推迟",而对于不遵守夏令时的用户则保持稳定。
文章
姚 鑫 · 十二月 23, 2021
# 第三章 SQL聚合函数 COUNT(一)
返回表或指定列中的行数的聚合函数。
# 大纲
```sql
COUNT(*)
COUNT([ALL | DISTINCT [BY(col-list)]] expression [%FOREACH(col-list)] [%AFTERHAVING])
```
# 参数
- `*` - 指定应计算所有行以返回指定表中的总行数。
`COUNT(*)`不接受其他参数,不能与`ALL`或`DISTINCT`关键字一起使用。
`COUNT(*)`不接受表达式参数,也不使用任何特定列的信息。
`COUNT(*)`返回指定表或视图中的行数,但不消除重复项。
它分别计数每一行,包括包含`NULL`值的行。
- `ALL` - 可选-指定`COUNT`返回表达式中所有值的计数。
如果没有指定关键字,这是默认值。
- `DISTINCT` - 可选-一个`DISTINCT`子句,指定`COUNT`返回表达式的不同(唯一)值的计数。
不能与流字段一起使用。
`DISTINCT`可以指定`BY(colo -list)`子句,其中`colo -list`可以是单个列名,也可以是用逗号分隔的列名列表。
- `expression` - 任何有效的表达式。
通常是包含要计算的数据值的列的名称。
- `%FOREACH(col-list)` - 可选-列名或以逗号分隔的列名列表。
- `%AFTERHAVING` - 可选-应用在`HAVING`子句中的条件。
`COUNT`返回`BIGINT`数据类型。
# 描述
`COUNT`聚合函数有两种形式:
- `COUNT(expression)`以整数形式返回表达式中值的数目的计数。
通常,表达式是查询返回的多行中字段的名称(或包含一个或多个字段名称的表达式)。
`COUNT(表达式)`不计算`NULL`值。
它可以选择计数或不计数重复的字段值。
`COUNT`总是返回数据类型`BIGINT,` `xDBC`长度为`8`,精度为`19`,刻度为`0`。
- `COUNT(*)`以整数形式返回表中行数的计数。
`COUNT(*)`计数所有行,无论是否存在重复的字段值或`NULL`值。
`COUNT`可以在引用表或视图的`SELECT`查询或子查询中使用。
`COUNT`可以在`SELECT`列表或`HAVING`子句中与普通字段值一起出现。
`COUNT`不能用于`WHERE`子句。
`COUNT`不能在`JOIN`的`ON`子句中使用,除非`SELECT`是子查询。
与所有聚合函数一样,`COUNT(expression)`可以接受一个可选的`DISTINCT`子句。
`DISTINCT`子句只计算那些具有不同(唯一)值的列。
什么是一个不同的值取决于字段的排序;
当字段具有默认的排序规则`%SQLUPPER`时,字母大小写不同的值将不作为不同的值计算。
要将每个字母大小写变量作为一个不同的值进行计数,请使用`count (distinct (%EXACT(field)))`。
`COUNT DISTINCT`不将`NULL`视为一个不同的值。
`COUNT(DISTINCT BY(col2) col1)`计数不同的`col2`值的`col1`值;
但是,不同的`col2`值可以包含一个`NULL`作为不同的值。
`ALL`关键字统计所有非`null`值,包括所有重复值。
如果没有指定关键字,`ALL`是默认行为。
# 没有行返回
如果没有选择行,`COUNT`返回`0`或`NULL`,这取决于查询:
- 如果除了提供给聚合函数的字段之外,选择列表不包含对`FROM`子句表中的字段的任何引用,那么`COUNT`返回`0`。
只有`COUNT`聚合函数返回`0`;
其他聚合函数返回`NULL`。
该查询返回`%ROWCOUNT`为`1`。
如下示例所示:
```java
ClassMethod Count()
{
s myquery = 3
s myquery(1) = "SELECT COUNT(*) AS Recs,COUNT(Name) AS People,"
s myquery(2) = "AVG(Age) AS AvgAge,MAX(Age) AS MaxAge,CURRENT_TIMESTAMP AS Now"
s myquery(3) = " FROM Sample.Employee WHERE Name %STARTSWITH 'ZZZ'"
s tStatement = ##class(%SQL.Statement).%New()
s qStatus = tStatement.%Prepare(.myquery)
if qStatus'=1 {
w "%Prepare failed:"
d $System.Status.DisplayError(qStatus)
q
}
s rset = tStatement.%Execute()
d rset.%Display()
w !,"Rowcount:",rset.%ROWCOUNT
}
```
```java
DHC-APP> d ##class(PHA.TEST.SQLCommand).Count()
Recs People AvgAge MaxAge Now
0 0 2021-12-20 20:58:17
1 Rows(s) Affected
Rowcount:1
```
- 如果`select-list`包含对`FROM`子句表中某个字段的任何直接引用,或者如果指定了`TOP 0`,那么`COUNT`返回`NULL`。
该查询返回`%ROWCOUNT`为`0`。
以下示例不返回`COUNT`值,因为`%ROWCOUNT`值为`0`:
```java
ClassMethod Count1()
{
s myquery = 2
s myquery(1) = "SELECT COUNT(*) AS Recs,COUNT(Name) AS People,$LENGTH(Name) AS NameLen"
s myquery(2) = " FROM Sample.Employee WHERE Name %STARTSWITH 'ZZZ'"
s tStatement = ##class(%SQL.Statement).%New()
s qStatus = tStatement.%Prepare(.myquery)
if qStatus'=1 {
w "%Prepare failed:"
d $System.Status.DisplayError(qStatus)
q
}
s rset = tStatement.%Execute()
d rset.%Display()
w !,"Rowcount:",rset.%ROWCOUNT
}
```
```java
DHC-APP> d ##class(PHA.TEST.SQLCommand).Count1()
Recs People NameLen
0 Rows(s) Affected
Rowcount:0
```
- 如果没有指定表,`COUNT(*)`返回1。
该查询返回`%ROWCOUNT`为1。
如下示例所示:
```java
ClassMethod Count2()
{
s myquery = "SELECT COUNT(*) AS Recs"
s tStatement = ##class(%SQL.Statement).%New()
s qStatus = tStatement.%Prepare(myquery)
if qStatus'=1 {
w "%Prepare failed:"
d $System.Status.DisplayError(qStatus)
q
}
s rset = tStatement.%Execute()
d rset.%Display()
w !,"Rowcount:",rset.%ROWCOUNT
}
```
```java
DHC-APP> d ##class(PHA.TEST.SQLCommand).Count2()
Recs
1
1 Rows(s) Affected
Rowcount:1
```
# 流字段
可以使用`COUNT`(表达式)来计数流字段值,但有一些限制。
`COUNT(streamfield)`计算所有非`null`值。
它不会检查重复的值。
当`expression`是一个流字段时,不能指定`COUNT`函数的`DISTINCT`关键字。
试图在流字段中使用`DISTINCT`关键字会导致`SQLCODE -37`错误。
不能在`%FOREACH`冒号列表中指定流字段。
尝试这样做会导致`SQLCODE -37`错误。
下面的例子显示了`COUNT`函数的有效使用,其中`Title`是字符串字段,`Notes`和`Picture`是流字段:
```sql
SELECT DISTINCT Title,COUNT(Notes),COUNT(Picture %FOREACH(Title))
FROM Sample.Employee
```
当`Title`为字符串字段,`Notes`和`Picture`为流字段时,以下示例无效:
```sql
-- Invalid: DISTINCT keyword with stream field
SELECT Title,COUNT(DISTINCT Notes) FROM Sample.Employee
```
```sql
-- Invalid: %FOREACH col-list contains stream field
SELECT Title,COUNT(Notes %FOREACH(Picture))
FROM Sample.Employee
```
文章
Qiao Peng · 十月 6, 2022
最近一些用户问到监控集成平台业务行为查询的问题,例如如何查询服务的平均耗时、发生错误的服务数量...
业务行为监控对于集成平台来说非常重要,可以帮助我们:
监控系统健康情况 — 查看系统性能表现。例如发现队列积压和长耗时的消息处理,都可能是性能问题的表现。
排查异常 — 通过查看业务行为数据,帮助我们判断特定的业务组件配置是否是造成性能瓶颈的主要原因。
做业务规划 — 通过业务行为数据,了解各个业务量变化情况,并辅助我们做业务规划。
做硬件规划 — 通过长期跟踪消息吞吐量的变化了解性能的变化和业务量的增长,进而辅助我们做硬件计划,避免出现在性能问题。
仅提供这些查询是很容易的,但要更好地监控集成平台的业务行为,需要更深入的了解InterSystems集成架构。
InterSystems集成架构
无论Ensemble、Health Connect还是InterSystems IRIS,它们都具有下面的集成架构:
通过业务服务向外发布服务、通过业务操作连接第三方接口、通过业务流程协同业务操作,它们统称为集成业务组件。可以简单理解为业务行为是由这些业务组件完成的:
业务服务 = 服务,它启动一个业务流程;
业务操作 = 接口,它通过接口操作第三方业务系统;
业务流程 = 流程,它控制一笔业务需要按什么流程调用哪些接口。
要监控业务行为,服务、接口和流程都需要监控。
另一个需要了解的概念是关于耗时的:
完整的业务流程由会话(Session)表达,会话完成的耗时(会话耗时)和服务耗时、接口耗时不是同一件事。例如下图的一笔业务,HL7FileService这个服务在第4步就结束了,但整笔业务还在还在继续,直到第9步才结束。由服务调用者来看,服务耗时是第4步到第1步的时间差;而整个业务流程(会话)耗时是第9步到第1步的时间差。所以要注意区分服务耗时和会话耗时的概念,而通常这2个耗时都需要关注。
监控业务行为的手段
InterSystems提供了多种方式和数据,帮助监控业务行为,包括消息、业务活动量采集、自动化的业务行为报警等多种手段。
1. 消息
InterSystems 集成产品的所有消息都是自动持久化的,也就是都保存在内建的数据库里,并且通过SQL就可以查询分析的。消息分为消息头和消息体:消息头记录消息传递的流程、时间、状态、目标等信息;消息体记录具体传递的消息内容。
消息头表(Ens.MessageHeader)有以下重要字段:
字段
说明
Id
消息头主键。会话的第一个消息的Id和其SessionId相同。
SessionId
消息所属的会话。可以用这个字段将所有相同会话的消息关联起来。
IsError
是否处理错误。
TimeCreated
消息创建时间。
TimeProcessed
消息处理时间。
CorrespondingMessageId
与此消息是请求/响应消息对里的另一个消息Id。可以通过它获取相应的响应消息。
MessageBodyId
对应的消息体对象Id。可以用它找到消息体数据
可以看出,通过分析消息头表,就可以得出几乎所有业务行为的分析结果,借助消息体表,更可以分析所有的业务行为。
但实际分析业务行为时,我们并不总是用消息头表。原因是:
消息头表可能非常大:例如,我们国内最大的医院客户的日消息吞吐量已经超过1亿条,按消息3个月的保存需求计算,就是90亿条以上的记录数。在不使用消息仓库的情况下,对这么大量的消息头进行频繁、复杂的SQL分析显然不会得到多好的性能。
定期的消息清除:考虑到硬件资源的限制,通常都会设置定期清除消息,例如自动删除3个月前的消息。只保留有限时间的消息,无法支持更长时间范围的业务行为分析。
因此,InterSystems提供了业务活动量采集,来弥补消息分析的不足。
2. 业务活动量采集
业务活动量采集是通过一个系统提供的业务操作自动采集、统计业务活动,并保存在特定的SQL表中供查询分析。只要将Ens.Activity.Operation.Local加入集成产品(Production)中,它会自动采集所有业务组件的活动量数据,并保存到活动数据表中。
活动数据表是三张表(Ens_Activity_Data.Days\ Ens_Activity_Data.Hours\ Ens_Activity_Data.Seconds): 这3张表记录按天\小时\秒来统计的业务组件(BS\BP\BO)的调用历史。因为仅是统计数据,数据量远小于消息头表、且不受消息头表定期清除的影响,适合更长时间范围的业务活动分析。而这3张表提供了时间尺度的统计数据,方便由粗到细追踪到问题根源。
这些表有以下重要字段:
字段
说明
timeslot
统计时间。
HostName
业务组件名称。
HostType
业务组件类型:1为BS、2为BP、3为BO。
totalduration
在统计时间段内的总处理时间。
totalcount
在统计时间段内的总消息数量。
如何开启业务活动量采集?只要把Ens.Activity.Operation.Local加入Production的BO中:
* 注意,如果业务服务使用 SendRequest 开启业务流程,需要做代码开启,参考:https://docs.intersystems.com/irisforhealth20221/csp/docbook/DocBook.UI.Page.cls?KEY=EMONITOR_activity#EMONITOR_activity_overview
通过分析活动数据表,我们也可以得到业务行为监控的数据。但分析消息表、活动数据表,都是被动的、事后分析的。要想得到即时的性能提醒,InterSystems还提供了一系列的业务行为报警设置,让其自动报警。
3. 业务行为报警
业务组件上有很多业务行为报警设置,例如业务操作和业务流程组件有队列长度计数告警和队列等待处理时间告警设置,在队列长度超过设置值、队列中消息等待时间超过设置值的情况下,自动发出警告。很多用户设置将这些警告发送到管理员的钉钉、微信或短信平台上,以及时提醒。
通过上面提到的这些业务行为监控手段,可以加强对集成产品的监控、了解业务情况。下面我们具体看看常见的监控需求如何实现。
常见业务行为查询需求
1. 服务、接口和流程总览。了解集成产品有哪些组件,以及这些组件的当前运行状态(启用/禁用)。
对集成产品中业务组件及状态查询,InterSystems提供了一个查询:Ens.Config.Production类的EnumerateConfigItems查询。它的头两个入参是Production名称和组件类型。组件类型为:1 - 业务服务;2 - 业务流程;3 - 业务操作。
例如下面代码列出所有的业务服务组件、备注、是否启用、请求消息类型和响应消息类型:
ClassMethod GetServiceList()
{
Set tStatement = ##class(%SQL.Statement).%New(),tRet=0
Set tStatus = tStatement.%PrepareClassQuery("Ens.Config.Production","EnumerateConfigItems")
Set tResult = tStatement.%Execute("HCCPKG.FoundationProduction",1)
While (tResult.%Next() '= 0)
{
W tResult.%Get("ConfigName"),"--",tResult.%Get("CommentOrClassName"),"--",tResult.%Get("Enabled"),"--",tResult.%Get("RequestClasses"),"--",tResult.%Get("ResponseClasses"),!
}
Do tResult.%Close(),tStatement.%Close()
Quit tRet
}
可以使用表Ens_Config.Item,它提供类似的信息,但没有区分业务组件类型。
2. 服务和接口的调用次数和耗时
通过上面提到的业务活动量采集表Ens_Activity_Data.*,可以容易地获得这类调用信息。
例如下面的SQL是查询业务服务当日被调用的次数和平均耗时,并按平均耗时降序排序:
SELECT hostname AS "服务名称",
SUM(totalcount) AS "调用次数",
ROUND(SUM(totalduration)/SUM(totalcount),4 )*1000 AS "平均服务耗时(ms)"
FROM Ens_Activity_Data.Days
WHERE timeslot = (SELECT Date(NOW()))
AND HostType=1
AND HostName not LIKE 'ENS.%'
GROUP BY hostname
ORDER BY "平均服务耗时(ms)" DESC
3. 服务调用失败(错误)的次数
可以使用SQL通过消息头表的消息处理错误列(IsError),并关联到会话查询其业务服务:
SELECT B.sourceconfigname AS "服务名称",
Count(*) AS "错误次数"
FROM (SELECT distinct sessionid as sessionid FROM Ens.MessageHeader
WHERE IsError =1
AND TimeCreated BETWEEN '2022-08-22 00:00:00' AND '2022-08-23 00:00:00') A,
Ens.MessageHeader B
WHERE A.sessionid=B.id
AND NOT B.SourceConfigName %STARTSWITH 'ENS.'
GROUP BY B.sourceconfigname
4. 查询具有特定消息体信息的消息历史
例如查询HL7 V2消息处理历史,并返回服务名称、患者编号、开始时间、结束时间、服务耗时、服务状态、请求消息内容,响应消息内容。这类查询涉及到消息体内容(患者编号),以及请求消息和响应消息的关联关系。
对于结构化消息体,可以直接关联到消息体表;但对于半结构化消息体,例如HL7 V2消息或自定义XML字符串的消息,可以通过SearchTable技术,建立对特定消息体内容的索引表,加速查询速度。在上面的查询例子中,要找到HL7 V2消息里的患者编号,可以通过HL7的SearchTable表:EnsLib_HL7.SearchTable 进行查询,而患者编号是其字段PropId = 4的记录中PropValue字段的值。对于请求和响应消息的关联,通过消息头表的CorrespondingMessageId字段即可获得。
因此上面的查询例子,可以用下面的SQL获得:
SELECT MI."服务名称",
MI."患者编号",
MI."服务开始时间",
MI."服务结束时间",
MI."服务耗时(ms)",
MI."服务状态",
MB.RawContent As "请求消息",
ME.RawContent As "响应消息"
FROM
(SELECT MHB.Id,MHB.sourceconfigname AS "服务名称",
ST.PropValue AS "患者编号",
MHB.TimeCreated AS "服务开始时间",
MHE.TimeProcessed AS "服务结束时间",
DATEDIFF(ms, MHB.TimeCreated, MHE.TimeProcessed) AS "服务耗时(ms)",
MHB.Status AS "服务状态",
MHB.MessageBodyId As MHBBId,
MHE.MessageBodyId As MHEBId
FROM Ens.MessageHeader MHB
LEFT JOIN EnsLib_HL7.SearchTable ST ON MHB.MessageBodyId = ST.DocId AND ST.PropId = 4
LEFT JOIN Ens.MessageHeader MHE ON MHE.CorrespondingMessageId = MHB.Id
WHERE MHB.ID = MHB.SessionId
AND MHB.MessageBodyClassName='EnsLib.HL7.Message'
AND TimeCreated BETWEEN '2022-08-22 00:00:00' AND '2022-08-23 00:00:00') MI
LEFT JOIN EnsLib_HL7.Message MB ON MI.MHBBId=MB.ID
LEFT JOIN EnsLib_HL7.Message ME ON MI.MHEBId=ME.ID
可能大家好奇,我是怎么知道的患者编号在EnsLib_HL7.SearchTable的PropId = 4的记录中?
在管理门户(SMP)中,大家经常通过消息查看器页面查询和检索消息,查询和检索条件选项非常丰富,包括消息头、消息体、虚拟文档字段和Search Table字段都可以作为查询条件。
在消息查看器页面里设置好查询条件,进行搜索后再查看它生成的SQL语句(通过按钮“显示查询”),我们就知道怎么写SQL了:
没看到“显示查询”按钮?在对应的命名空间下执行下面的命令即可:
Set ^Ens.Debug("UtilEnsMessages","sql")=1
5. 查询业务组件间的调用关系
例如想获得所有的服务名称、对应生产方和消费方。
这类的需求很典型,但在InterSystems集成产品里,并不是那么直观可以获得的。为什么?因为消费方并非是固定的。
在基于消息路由规则的场景下,消费方是消息最终发送的目标接口,理论上可以通过查询路由规则获得。
在业务流程模型里,调用方(消费方)可以是动态的。
当在发布/订阅模式下,订阅方并不是“编码”实现的,而且可以灵活的参与或取消订阅。
因此要想知道明确的生产方和消费方需要知道很多信息,而这也是业务流程灵活性的体现。我们可以考虑通过对消息流程历史的分析来简单获得:
SELECT distinct B.sourceconfigname AS '生产方',A.TargetConfigName AS '消费方'
FROM
(SELECT sessionid,TargetConfigName
FROM Ens.MessageHeader
WHERE TargetBusinessType='3') A,
(SELECT sessionid,sourceconfigname
FROM Ens.MessageHeader
WHERE id=sessionid) B
WHERE A.sessionid=B.sessionid
因为消息可能被删除,因此需要注意使用它的局限性。(这里的TargetBusinessType='3'中的数字3表示BusinessOperation)
6. 查询会话耗时
前面介绍了服务耗时和会话耗时的差异,会话耗时和服务耗时有时并不一致。要分析每笔会话的完整耗时?可以参考这个会话耗时的SQL查询方法:
SELECT SourceConfigName AS "服务名称",
max(ti) AS "会话最大耗时",
min(ti) AS "会话最小耗时",
avg(ti) AS "会话平均耗时"
FROM
(SELECT a.SourceConfigName,
a.SessionId,
tp,TimeCreated,
{fn TIMESTAMPDIFF(SQL_TSI_SECOND,TimeCreated,tp)} as ti
FROM (SELECT SourceConfigName ,SessionId, TimeCreated
FROM Ens.MessageHeader
WHERE ID= SessionId) a,
(SELECT SessionId, max(TimeProcessed) tp
FROM Ens.MessageHeader
GROUP BY SessionId) b
WHERE a.sessionid=b.sessionid)
GROUP BY SourceConfigName
如果大家有其它对业务行为分析的需求,欢迎留言。
文章
姚 鑫 · 八月 8, 2021
# 第七十七章 方法关键字 - SoapBindingStyle
指定此方法用作`web方法`时使用的绑定样式或`SOAP`调用机制。仅适用于定义为`web服务`或`web客户端`的类。
# 用法
若要重写方法使用的默认绑定样式(当它用作`web方法`时),请使用以下语法:
```java
Method name(formal_spec) As returnclass [ WebMethod, SoapBindingStyle = soapbindingstyle ]
{ //implementation }
```
其中`soapbindingstyle`是下列之一:
- `document`文档(默认)—此`web方法`使用文档样式的调用。
使用这种绑定风格,`SOAP消息`被格式化为文档,并且通常只有一个部分。
在`SOAP消息`中,``元素通常包含一个子元素。``元素的每个子元素对应于一个消息部分。
- `rpc` —这个`web方法`使用`rpc`(远程过程调用)风格的调用。
使用这种绑定风格,`SOAP消息`被格式化为具有多个部分的消息。
在`SOAP消息`中,``元素包含一个子元素,其名称取自相应的操作名称。这个元素是一个生成的包装元素,它为方法的参数列表中的每个参数包含一个子元素。
重要提示:对于手动创建的`web服务`,该关键字的默认值通常是合适的。当使用`SOAP`向导从`WSDL`生成`web客户端`或`服务`时,InterSystems IRIS会将此关键字设置为适合该`WSDL`;如果修改该值,`web客户端`或服务可能不再工作。
# 详解
此关键字允许指定`web方法`使用的绑定样式。它影响`SOAP`主体的格式(但不影响任何`SOAP`头)。
# 默认
如果省略此关键字,则``元素的样式属性将改为由`SoapBindingStyle`类关键字的值确定。
# 与WSDL的关系
SoapBindingStyle方法关键字指定了WSDL的部分中< soap:operation >元素的样式属性的值。例如,如果SoapBindingStyle方法关键字是document,则WSDL可能如下所示:
```xml
...
...
...
```
相比之下,如果`SoapBindingStyle`是`rpc`,则`WSDL`可以改为如下所示:
```xml
...
...
...
```
绑定样式还会影响`web方法`的请求和响应``元素,如下所示:
= 如果绑定样式是文档,默认情况下,每条消息只有一个部分。例如:
```xml
```
如果`ARGUMENTSTYLE`参数是`message`,那么一条消息可以有多个部分。例如:
```xml
```
- 如果绑定样式是`rpc`,消息可以有多个部分。例如:
```xml
```
# 与 %XML.DataSet 一起使用
如果将此关键字与使用 `%XML.DataSet` 类型的对象作为输入或输出的方法一起使用,则存在一些限制。
# 第七十八章 方法关键字 - SoapBodyUse
当此方法用作 `Web方法`时,指定此方法的输入和输出使用的编码。仅适用于定义为 `Web服务`或 `Web客户端`的类。
# 用法
若要重写方法的输入和输出使用的默认编码(当它用作`web方法`时),请使用以下语法:
```java
Method name(formal_spec) As returnclass [ WebMethod, SoapBodyUse = soapbodyuse ]
{ //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客户端`或服务时,InterSystems IRIS会将此关键字设置为适合该WSDL;如果修改该值,web客户端或服务可能不再工作。
# 详情
此关键字指定`web方法`的输入和输出的编码。
# 默认
如果省略此关键字,将使用`SoapBodyUse`类关键字的值。
# 与 %XML.DataSet 一起使用
如果将此关键字与使用 `%XML.DataSet` 类型的对象作为输入或输出的方法一起使用,则存在一些限制。
# 第七十九章 方法关键字 - SoapMessageName
指定此`web方法`的响应消息的``元素的`name`属性。
仅适用于定义为`web服务`或`web客户端`的类。
# 用法
要覆盖响应消息的``元素的默认名称,请使用以下语法:
```java
Method name(formal_spec) As returnclass [ WebMethod, SoapMessageName = MyResponse ]
{ //implementation }
```
其中`soapmessagename`是在`XML`中有效的任何标识符。
# 详解
注意:此关键字仅对使用`SoapBindingStyle`等于`document`(这是默认设置)的`web方法`有效。
此关键字指定响应消息正文的子元素的名称。
# 默认
如果省略此关键字,消息名称就是结尾附加了响应的`web方法`的名称。
`web方法`的名称取自`web服务`中的`web方法`定义;这只能通过重命名该方法来更改。
# 与WSDL的关系
`SoapMessageName`关键字影响`web服务`的`WSDL`的``和``部分。例如,以下web方法:
```java
Method Add(a as %Numeric,b as %Numeric) As %Numeric [ SoapMessageName=MyResponseMessage,WebMethod ]
{
Quit a + b
}
```
对于这个`web服务`,WSDL的``和``部分如下所示:
```xml
```
默认情况下,如果方法没有指定`SoapMessageName`关键字,`AddSoapOut`消息将包含一个名为`addressponse`的元素,而不是`MyResponseMessa`
注意,`SoapMessageName`不影响响应消息的子元素(例如`AddResult`)。
如果使用`SOAP Wizard`从`WSDL`生成`web`服务或客户端,InterSystems IRIS将此关键字设置为适合该`WSDL`的关键字。
# 对SOAP消息的影响
`web服务`可能会发送如下响应消息:
```xml
42
```
默认情况下,如果该方法没有指定`SoapMessageName`关键字,则``元素将改为``。
# 第八十章 方法关键字 - SoapNameSpace
指定`web方法`使用的`XML`命名空间。
仅适用于定义为`web服务`或`web客户端`的类。
# 用法
要覆盖方法使用的默认`XML`命名空间(当该方法作为一个`web方法`使用时),请使用以下语法:
```java
Method name(formal_spec) As returnclass [ SoapNameSpace = "soapnamespace", WebMethod ]
{ //implementation }
```
其中`soapnamespace`是一个命名空间`URI`。
注意,如果`URI`包含冒号(`:`),则字符串必须用引号括起来。
也就是说,你可以使用以下方法:
```java
Method MyMethod() [ SoapNameSpace = "http://www.mynamespace.org", WebMethod ]
```
或以下:
```java
Method MyMethod() [ SoapNameSpace = othervalue, WebMethod ]
```
但不包括以下内容:
```java
Method MyMethod() [ SoapNameSpace = http://www.mynamespace.org, WebMethod ]
```
重要提示:对于手动创建的`web服务`,该关键字的默认值通常是合适的。
使用`SOAP`向导从`WSDL`生成`web`客户端或服务时,InterSystems IRIS将此关键字设置为适合该`WSDL`的关键字;
如果修改该值,`web客户端`或服务可能不再工作。
# 详解
这个关键字指定了这个`web方法`使用的`XML`名称空间。
注意:这个关键字只有在方法使用`rpc`样式绑定时才有效果。
也就是说,方法(或包含它的类)必须用`SoapBindingStyle`等于`rpc`来标记。
(如果为使用文档样式绑定的方法指定此关键字,则WSDL将不会是自一致的。)
# 默认
如果忽略此关键字,则该方法位于`web服务`或客户端类的`namespace`参数指定的名称空间中。
# WSDL的关系
对于InterSystems IRIS web服务服务,`SoapNameSpace`关键字影响``元素中的名称空间声明。
这里添加了指定的名称空间(例如`http://www.customtypes.org`)。
例如:
```xml
...
xmlns:ns2="http://www.customtypes.org"
xmlns:s0="http://www.wsns.org"
...
targetNamespace="http://www.wsns.org"
```
在本例中,`http://www.customtypes.org`名称空间被分配给前缀`ns2`。
请注意,`WSDL`通常还声明了`web服务`的名称空间(`http://www.wsns.org`)。
在本例中,该名称空间被分配给前缀`s0`,并且也用作目标名称空间。
# 对SOAP消息的影响
一个可能的`SOAP消息`可能如下所示(为了可读性添加了换行符和空格):
```xml
42
```
注意,``元素位于`http://www.webservicetypesns.org`名称空间中。
相反,如果我们没有指定`SoapNameSpace`关键字,则消息将如下所示:
```xml
42
```
在本例中,``元素位于web服务的名称空间`http://www.wsns.org`中。
文章
姚 鑫 · 七月 1, 2021
# 第二十四章 执行XSLT转换
# 执行XSLT转换
要执行`XSLT`转换,请执行以下操作:
- 如果使用的是`Xalan`处理器(对于`XSLT 1.0`),请使用`%XML.XSLT.Transformer`的以下类方法之一:
- `TransformFile()`——转换给定XSLT样式表的文件。
- `TransformFileWithCompiledXSL()`——转换一个文件,给定一个已编译的XSLT样式表。
- `TransformStream()`——转换给定XSLT样式表的流。
- `TransformStreamWithCompiledXSL()`——转换一个流,给定一个已编译的XSLT样式表。
- `TransformStringWithCompiledXSL()`——转换给定已编译XSLT样式表的字符串。
- 如果使用`Saxon`处理器(用于XSLT 2.0),请使用`%XML.XSLT2.Transformer`的以下类方法之一:
- `TransformFile()`——转换给定XSLT样式表的文件。
- `TransformFileWithCompiledXSL()`——转换一个文件,给定一个已编译的XSLT样式表。
- `TransformStream()`——转换给定XSLT样式表的流。
- `TransformStreamWithCompiledXSL()`——转换一个流,给定一个已编译的XSLT样式表。
这些方法具有相似的签名。这些方法的参数列表按顺序如下:
- pSource—要转换的源XML。请参见此列表后面的表。
- pXSL -样式表或编译样式表。请参阅此列表后面的表格。
- pOutput -作为输出参数返回的结果XML。请参阅此列表后面的表格。
- pErrorHandler -一个可选的自定义错误处理程序。请参阅本章后面的“自定义错误处理”。如果不指定自定义错误处理程序,该方法将使用%XML.XSLT.ErrorHandler的新实例(对于两个类)。
- pParms -一个可选的InterSystems IRIS多维数组,包含要传递给样式表的参数。
- pCallbackHandler -定义`XSLT`扩展函数的可选回调处理程序。
- pResolver -一个可选的实体解析器。
8. (仅适用于`%XML.XSLT2.Transformer`)网关-`%Net.Remote.Gateway`的可选实例。如果要重用XSLT网关连接以获得更好的性能,请指定此参数;
作为参考,下表显示了这些方法的前三个参数,并进行了对比:
XSLT变换方法的比较
Method | pSource (Input XML) |pXSL (Stylesheet) |pOutput(Output XML)
---|---|---|---
TransformFile() |String that gives a file name| String that gives a file name |String that gives a file name
TransformFileWithCompiledXSL()| String that gives a file name |Compiled style sheet| String that gives a file name
TransformStream()| Stream| Stream |Stream, returned by reference
TransformStreamWithCompiledXSL()| Stream |Compiled style sheet |Stream, returned by reference
TransformStringWithCompiledXSL()| String |Compiled style sheet| String that gives a file name
# 示例
本节使用以下代码(但不同的输入文件)展示了几种转换:
```java
Set in="c:\0test\xslt-example-input.xml"
Set xsl="c:\0test\xslt-example-stylesheet.xsl"
Set out="c:\0test\xslt-example-output.xml"
Set tSC=##class(%XML.XSLT.Transformer).TransformFile(in,xsl,.out)
Write tSC
```
## 示例1:简单替换
在本例中,我们从以下输入XML开始:
```xml
Content
```
我们使用以下样式表:
```xml
Content Replaced
```
在这种情况下,输出文件将如下所示:
```xml
Content Replaced
```
## 示例2:内容提取
在本例中,我们从以下输入XML开始:
```xml
Some text
Some more text
```
我们使用以下样式表:
```xml
:
```
在这种情况下,输出文件将如下所示:
```java
13: Some text
14: Some more text
```
## 其他示例
InterSystems IRIS提供了以下附加示例:
- 对于`XSLT 1.0`,请参阅`%XML.XSLT.Transformer`中的`Example()`、`Example2()`和其它方法。
- 对于`XSLT 2.0`,请参见`Samples`命名空间中的类`XSLT2.Examples`。
文章
Qiao Peng · 一月 24, 2021
在使用xDBC连接到字符集为US7ASCII的Oracle数据库时,大家可能遇到过中文的乱码问题,尤其是使用Oracle自己的xDBC驱动的时候。
字符集为US7ASCII的Oracle数据库虽然可以保存中文数据,但给客户端带来了很多麻烦,需要对获取和提交的数据进行转码。
在Ensemble/Health Connect/InterSystems IRIS 中使用SQL适配器连接到这样的Oracle数据库时,可以使用$ZCVT函数进行转码。
1. $ZCVT函数
$ZCVT函数是广泛使用的字符串转换函数,可以做大小写转换、编码转换、URL 和 URI 转换等。我们用其编码转换能力来解决字符集转码问题。
2. 获取的SQL结果集数据有中文时
这时,Oracle的驱动返回的中文数据通常是GB码,而不是Unicode或UTF码。可以通过$ZCVT函数对GB码的数据进行转码,转换为Unicode:
Set tCorrectData = $ZCVT(tOriginalData,"I","GB18030")其中$ZCVT函数的第一个参数tOriginalData是获取到到结果集字段值;第二个参数“I”说明tOriginalData是输入字符串;第三个参数“GB18030”是说明输入字符串的字符集编码是GB18030。
上面的代码会将tOriginalValue按GB18030编码转换为Unicode编码,并将转换结果赋给变量tCorrectData。
3. 发送中文数据或中文查询条件时
首先,需要将本地Unicode的中文数据转码为GB码。还是使用$ZCVT函数:
Set tSendingData = $ZCVT(tOriginalData,"O","GB18030")
其中$ZCVT函数的第一个参数tOriginalData是输出的含中文数据;第二个参数“O”说明要转换输出数据;第三个参数“GB18030”是说明输出时要转换为GB18030。
上面的代码会将tOriginalValue按Unicode编码转换为GB18030编码,并将转换结果赋给变量tSendingData。
做完这一步,Oracle驱动很可能依然不认识其中的GB码中文数据。这时,需要将GB码的中文数据转换成GB明码字符串,例如你要将查询条件 中文 传给Oracle,你需要传递 D6D0CEC4过去(中的GB码为D6D0,文的GB码为CEC4),然后再使用Oracle的函数UTL_RAW.CAST_TO_VARCHAR2,将GB明码字符串转为内部使用的中文数据。所以修改后代码如下:
Set tSendingData = $ZCVT(tOriginalData,"O","GB18030") Set tTmpDataHex = "" //转换为GB明码 For i=1:1:$length(tSendingData) { Set tTmpDataHex = tTmpDataHex_$zhex($ASCII(tSendingData,i)) } //使用Oracle的UTL_RAW.CAST_TO_VARCHAR2的函数 Set tSQL = "update dept_dict set dept_name = UTL_RAW.CAST_TO_VARCHAR2('"_ tTmpDataHex_"') where dept_code = '1'"
这里用到了$ZHEX和$ASCII函数,将数据转换为其编码,并转为16进制值,从而得到GB明码。关于更多的Ensemble/Health Connect/InterSystems IRIS函数,可以参考文档。
自此,应该可以解决从Ensemble和Health Connect连接到字符集为US7ASCII的Oracle数据库所遇到的各种中文乱码问题了。
如果您遇到更多的问题,欢迎在社区提问。
点赞