清除过滤器
文章
姚 鑫 · 十一月 6, 2021
# 第六十八章 SQL命令 SAVEPOINT
在事务中标记一个点。
# 大纲
```java
SAVEPOINT pointname
```
## 参数
- `pointname` - 保存点的名称,指定为标识符。
# 描述
`SAVEPOINT`语句标记事务中的一个点。建立保存点使能够执行事务回滚到保存点,撤消在此期间完成的所有工作并释放在此期间获得的所有锁。在长期运行的事务或具有内部控制结构的事务中,通常希望能够回滚事务的一部分,而不撤消在事务期间提交的所有工作。
保存点的建立会递增`$TLEVEL`事务级别计数器。回滚到保存点会将`$TLEVEL`事务级别计数器递减到紧接在保存点之前的值。可以在一个事务内建立最多`255`个保存点。超过这个保存点数量会导致`SQLCODE-400`致命错误,这是在SQL执行期间捕获的`` 异常。终端提示符将当前事务级别显示为提示符的`TLn:`前缀,其中`n`是介于`1`和`255`之间的整数,表示当前$TLEVEL计数。
每个保存点都与一个保存点名称相关联,这是一个唯一的标识符。保存点名称不区分大小写。保存点名称可以是分隔的标识符。
- 如果指定的保存点没有点名,或者指定的点名不是有效的标识符或SQL保留字,则会发出运行时`SQLCODE-301`错误。
- 如果指定点名称以`“SYS”`开头的保存点,则会发出运行时`SQLCODE-302`错误。这些保存点名称是保留的。
保存点名称不区分大小写;因此`resetpt`,` ResetPt`和`“RESETPT”`是相同的点名。此重复项是在回滚到保存点期间检测到的,而不是在保存点期间检测到的。当指定具有重复点名的`SAVEPOINT`语句时, IRIS会递增事务级别计数器,就像点名是唯一的一样。但是,最近的点名称会覆盖保存点名称表中所有先前重复的值。因此,当指定回滚到保存点点名时, IRIS会回滚到具有该点名称的最近建立的保存点,并相应地递减事务级别计数器。但是,如果再次指定回滚到同名的保存点点名,则会生成`SQLCODE-375`错误,并显示`%msg:Cannot Rollback to Unestabled SavePoint‘name’`,整个事务将回滚,`$TLEVEL`计数恢复为`0`。
## 使用保存点
嵌入式SQL、动态SQL、ODBC和JDBC支持`SAVEPOINT`语句。在`JDBC`中,`connection.setSavepoint(Pointname)`设置一个保存点,`connection.roll back(Pointname)`回滚到指定的保存点。
如果已建立保存点,请执行以下操作:
- 回滚到保存点点名将回滚自指定保存点以来所做的工作,删除该保存点和所有中间保存点,并将`$TLEVEL`事务级别计数器递减删除的保存点数量。如果`pointname`不存在或已经回滚,此命令将回滚整个事务,将`$TLEVEL`重置为`0`,并释放所有锁。
- 回滚回滚当前事务期间完成的所有工作,回滚自`START TRANSACTION`以来完成的工作。它将`$TLEVEL`事务级别计数器重置为零,并释放所有锁。请注意,常规回滚会忽略保存点。
- `COMMIT`提交在当前事务期间完成的所有工作。它将`$TLEVEL`事务级别计数器重置为零,并释放所有锁。请注意,提交操作会忽略保存点。
在事务内发出第二个`START TRANSACTION`对保存点或`$TLEVEL`事务级别计数器没有影响。
如果事务操作未能成功完成,则会发出`SQLCODE-400`错误。
# 示例
以下嵌入式`SQL`示例创建具有两个保存点的事务:
```java
ClassMethod Savepoint()
{
n SQLCODE,%ROWCOUNT,%ROWID
&sql(
START TRANSACTION
)
&sql(
DELETE FROM Sample.Person WHERE Name = NULL
)
if SQLCODE = 100 {
w !,"没有要删除的空名称记录"
} elseif SQLCODE '= 0 {
&sql(ROLLBACK)
} else {
w !,%ROWCOUNT," 已删除Null Name记录"
}
&sql(
SAVEPOINT svpt_age1
)
&sql(
DELETE FROM Sample.Person WHERE Age = NULL
)
if SQLCODE = 100 {
w !,"没有要删除的空年龄记录"
} elseif SQLCODE '= 0 {
&sql(ROLLBACK TO SAVEPOINT svpt_age1)
} else {
w !,%ROWCOUNT," 删除空年龄记录"
}
&sql(
SAVEPOINT svpt_age2
)
&sql(
DELETE FROM Sample.Person WHERE Age > 65
)
if SQLCODE = 0 {
&sql(COMMIT)
} elseif SQLCODE = 100 {
&sql(COMMIT)
} else {
&sql(
ROLLBACK TO SAVEPOINT svpt_age2
)
w !,"退休年龄删除失败"
}
&sql(COMMIT)
&sql(COMMIT)
}
```
# ObjectScript和SQL事务
**使用`TSTART`和`TCOMMIT`的`ObjectScript`事务处理与使用SQL语句`START transaction`、`SAVEPOINT`和`COMMIT`的SQL事务处理不同,也不兼容。
ObjectScript和InterSystems SQL都提供了对嵌套事务的有限支持。
ObjectScript事务处理不与`SQL`锁控制变量交互;
特别需要关注的是`SQL`锁升级变量。
应用程序不应该尝试混合这两种事务处理类型。**
如果事务涉及`SQL`更新语句,则事务应该由`SQL START transaction`语句启动,并使用`SQL COMMIT`语句提交。
使用`TSTART/TCOMMIT`嵌套的方法可以包含在事务中,只要它们不初始化事务。
方法和存储过程通常不应该使用SQL事务控制语句,除非按照设计,它们是事务的主控制器。
文章
Michael Lei · 十月 10, 2022
Hi 大家好,
我最近开始学习InterSystems IRIS 的互操作性,我发现官方文档对理解它的工作原理很有帮助,尽管我自己在实现它时仍有一些困难。在我的同事的帮助下,我成功地创建了一个系统的Demo,并从实践中学习。因此,我决定写一下文章,分享我得到的帮助,来帮助更多的其他人。
介绍
首先,让我们掌握一些基本概念:
互操作性 - 这个词的含义并不像它的发音那样复杂--它基本上是把各种信息从一个系统带到另一个系统的“魔术”。
业务主机 - 如果把互操作性比作是魔术,那么业务主机Business Host就是魔术师的魔法帽--业务主机里有能够识别和接收信息的业务服务Business Service/BS,并将其作为消息发送给业务流程BP或业务操作BO。业务操作执行所需的操作(顾名思义)并传递信息。业务流程控制着消息的流动:它们定义了消息的去向(基于你所选择的任何东西)以及它是如何被传递的。
适配器 - 适配器是一些我们可以用来识别和操作我们可能要处理的各种信息的类。在实践中,我们把它们作为参数和(可选)属性来访问其方法和属性
准备搭建Production
从简单的开始比较容易--让我们先想想服务和操作--比如说你有一个接收一种信息的服务,它很容易被我们唯一的操作所识别。
当生产的目的和它的部分非常清楚时,开发就比较容易。如果你愿意,画一张图或写下你希望它完成的步骤可能会有帮助。
例如:
首先要问自己, "需要做什么?" - 在我的演示中,我需要操作一个SQL表--我要把一本书的标题和作者等信息,并将其插入到一个表中。
"那么,我需要Production做什么呢?- 它必须接收一个包含书名和作者的信息,并执行一个SQL INSERT。
"好的,如何让这件事发生?- 业务服务(BS)将接收标题和作者,将其传递给业务操作(BO)。BO执行SQL代码。
"现在我有了信息将遵循的路径,我需要理解信息。它是什么?" - 我有很多方法可以发送数据。我可以在一个文件中发送,或一个REST应用程序,甚至一个电子邮件。让我们选择文件来开始简单。我的BS将接收文件,读取它并将其信息发送给BO。BO执行查询。
开始干活
你可以从你对最有信心的代码部分开始。
业务服务 Business Service/BS
我是从业务服务BS开始的. 现在我很清楚,我需要实现Request(请求)类,以便它存储数据和将被发送到的操作。
业务操作Business Operation/BO
请求Request
Request类很简单。我只用它来存储一些数据。%XML.Adaptor是为了在管理门户上显示Request以进行错误管理。
业务操作Business Operation
BO有一个消息地图,为每一种到达的消息提供足够的方向。例如,如果在BS中,在SendRequestSync方法和Request参数中,我使用了不同的类型,如 "Demo.Books.BO.SearchTable.Request",我可以用这种消息类型创建另一个MapItem,引用一个Search方法。
方法Method
在这里,你实现了操作应该做的任何事情.
管理门户设置
最后,为了让事情顺利进行,请遵循以下步骤:
管理门户 > 互操作性 > 列表 > 生产 > 新建(Management Portal > Interoperability > List > Productions > New)
门户网站将用Production生产信息创建一个类。
然后,你用你创建的类添加一个服务,并设置文件路径(你将把输入文件放在哪里)和工作路径。
另外,用你创建的类添加一个操作,必要时指定其设置。
几点观察
业务操作可以接收一个同步请求,这意味着服务只有在BO检索到它的信息后才会响应,因为服务的响应取决于BO的响应,例如,如果它必须在表操作中执行一个搜索。因为Production生产只执行INSERT操作,BS只需要发送信息,BO就会INSERT;不需要响应,所以我们可以有一个异步请求。
讨论例如文件和SQL适配器等适配器的规格超出了本文的范围,本文的目的是对编码和步骤进行概述,以便更好地理解实际工作。
欢迎与我联系--我很乐意提供任何可能的帮助!
文章
Tete Zhang · 九月 14, 2022
从消息查看器看到清除周期以外的消息没有被正常清除
这种情况先抽查这些消息所处的会话中是否有未完成操作周期的消息(状态为除“Completed”“Error”“Discarded”之外的状态)。如有,且定期清除任务配置了“KeepIntegrity”,且该环境并不需要保留这些消息,可通过关闭清除任务中的“KeepIntegrity”配置清除这些会话和包含的消息。如果有这类消息,但是定期清除任务未配置“KeepIntegrity”,可能是定期清除任务的逻辑或消息数据问题导致清楚任务查找的时候没有覆盖这些消息,请联系WRC帮助排查具体原因。
有关定期清除任务的更多信息请参见文档
Purging Production Data | Managing Productions | InterSystems IRIS for Health 2022.1
从消息查看器看不到清除周期之外的消息,但是^%GSIZE显示有global占据了很大的磁盘空间
这种情况需要具体排查每个较大的global。可能有以下原因:
系统定义的global占用很大空间。您可以联系WRC帮助排查具体原因。
自定义的global占用很大空间。这可能是消息中嵌套的持久化数据或流数据,或者是和消息没有直接关系的独立的表里面的数据。请对创建这种global的代码进行复盘,找到创建逻辑并增加相应的数据管理逻辑。
孤立消息 (Orphan Messages)
孤立消息是指不存在配套Message Header的所有消息对象。
定期清除任务会根据Message Header里的时间信息和状态信息去判断一条消息是否符合清除条件。Message Header是在消息从一个组件发向另一个组件的时候被创建的。所以,当我们创建将被永久存储的对象之前,我们都要思考:这个对象会被保存吗?被保存后会被发送到另一个组件吗?如果不会被发送,该对象将不存在配套的Message Header,也就不会被定期清除。这种情况我们需要开发相应的自定义逻辑去定期管理该表中的数据,或确保该对象被发送到某个组件以创建Message Header。消息对象中嵌套对象或流的情况要尤其注意,对每一个嵌套的对象或流都要定义相对应的%OnDelete()删除逻辑。
在测试阶段我们可以做如下的测试:
测试前跑^%GSIZE报告并检查磁盘存储
跑一套测试消息
用清除任务删除系统上所有的消息(DaysToKeep=0)
跑^%GSIZE报告并检查磁盘存储
如果对比前后的^%GSIZE报告和磁盘空间之后,发现清除任务完成后没有遗留多余的数据,那么这就证明我们的逻辑中对消息及相关嵌套数据进行了很好的管理。反之如果发现了遗留数据,我们可以在研发测试阶段就对问题进行排查,尽量避免开放生产环境以后出现磁盘满或数据库过大的问题。
如果发现了环境中有孤立消息的问题,请联系WRC进行排查和消息清除管理。
HL7v2:孤立字段(Orphan Segment)
HL7v2在数据库中的存储逻辑如下。
EnsLib.HL7.Message对象存在以下两个global里:
^EnsLib.H.MessageD
^EnsHL7.Segment
示例:
HL7v2消息 (^EnsLib.H.MessageD global):
1: ^EnsLib.H.MessageD = 1257406
2: ^EnsLib.H.MessageD(1257406) = $lb("","","2.3:ORU_R01",0,"2019-06-03 15:28:38.819","2.3.1","C:\Support\inarchive\testoru.txt_2019-06-03_11.28.38.814","","")
3: ^EnsLib.H.MessageD(1257406,"segs") = 5
4: ^EnsLib.H.MessageD(1257406,"segs",1) = "11612,25"
5: ^EnsLib.H.MessageD(1257406,"segs",2) = "11612,26"
6: ^EnsLib.H.MessageD(1257406,"segs",3) = "11612,27"
7: ^EnsLib.H.MessageD(1257406,"segs",4) = "11612,28"
8: ^EnsLib.H.MessageD(1257406,"segs",5) = "11612,29"
其中, 125706是该HL7v2消息的Object ID。Global值"11612,25","11612,26"指向相应的HL7v2字段。
HL7v2字段 (^EnsHL7.Segment global):
1: ^EnsHL7.Segment(11612) = 30
2: ^EnsHL7.Segment(11612,25) = "|^~\&MSH|^~\&||GA0000||VAERS PROCESSOR|20010331605||ORU^R01|20010422GA03|T|2.3.1|||AL|"
3: ^EnsHL7.Segment(11612,25,0,1257406) = ""
其中,11612是创建该HL7v2消息的进程 ID (PID)。^EnsHL7.Segment(11612,25) 存储了该字段的具体数据。^EnsHL7.Segment(11612,25,0,1257406) 中的第四个值(1257406)是这个字段所属消息的Object ID。
从以上示例可以看出,HL7v2字段数据存储于^EnsHL7.Segment global。所以在^%GSIZE中看到^EnsHL7.Segment global比 ^EnsLib.H.MessageD global大是正常现象。使用平台自带的逻辑在最新版本上目前也没有已知问题会导致孤立字段。如果您持续观察^%GSIZE报告,发现^EnsHL7.Segment global的大小出现异常增长,可以联系WRC排查是否有孤立字段的情况。
文章
姚 鑫 · 六月 4, 2023
# 第二十四章 开发Productions - ObjectScript Productions - 定义业务服务
本页介绍如何定义业务服务类。
提示: `IRIS® `提供使用特定入站适配器的专用业务服务类,其中之一可能适合需要。如果是这样,则不需要编程。有关部分列表,请参阅 `Introducing Interoperability Productions` 中的连接选项。
# 介绍
业务服务负责接受来自外部应用程序的请求到 `IRIS`。下图显示了它是如何工作的:
请注意,此图仅显示数据的输入流,而不是可选响应。
业务服务负责以下活动:
- 等待特定的外部事件(例如来自应用程序的通知、收到 `TCP` 消息等)。
- 读取、解析和验证伴随此类事件的数据,
- 如果需要,返回对外部应用程序的确认,表明已收到事件。
- 创建请求消息的实例并将其转发到适当的业务流程或业务操作以进行处理。
业务服务的目的通常是接收数据输入。在大多数情况下,业务服务有一个与之关联的入站适配器。但是,在某些情况下不需要适配器,因为应用程序能够将请求消息发送到服务中,或者因为业务服务已被编写为处理特定类型的外部调用,例如来自复合应用程序的调用。这种类型的业务服务称为无适配器业务服务。
当业务服务具有入站适配器时,它处于数据拉取(而不是推送)模式。在这种模式下,业务服务会定期轮询适配器,看它是否有数据。同时,如果适配器随时遇到输入数据,它会调用业务服务来处理输入。
当业务服务没有适配器时,它不会拉取数据。相反,客户端应用程序调用业务服务并告诉它处理输入(这是一种数据推送模式)。
# 关键原则
首先,务必阅读 `Programming in InterSystems IRIS`。
在业务服务中,可以访问关联适配器的属性和方法,这些适配器作为业务服务的 Adapter 属性提供。这意味着可以更改适配器的默认行为;这样做可能合适也可能不合适。记住封装原则很有用。封装的思想是适配器类应该负责技术特定的逻辑,而业务服务类应该负责生产特定的逻辑。
如果发现有必要在业务服务类中大量或频繁地改变适配器类的行为,那么创建适配器类的自定义子类可能更合适。请参阅不太常见的任务。
这个原则也适用于商业运作。
# 定义业务服务类
要创建一个业务服务类,定义一个类如下:
- 类必须在(或子类)中扩展 `Ens.BusinessService`。
- 在类中,`ADAPTER` 参数必须等于此业务服务要使用的适配器类的名称。
提示:如果只是希望业务服务定期唤醒和运行而不关心 `IRIS` 外部的事件,请使用适配器类 `Ens.InboundAdapter`。
- 类必须实现 `OnProcessInput()` 方法,如实现 `OnProcessInput()` 方法中所述。
- 类可以添加或删除设置。请参阅添加和删除设置。
- 类可以实现任何或所有启动和拆卸方法。请参阅覆盖启动和停止行为。
- 类可以包含完成自身内部工作的方法。
有关业务服务类的示例,请参阅适配器指南。
# 实施 `OnProcessInput()` 方法
在业务服务类中, `OnProcessInput()` 方法可以具有以下通用签名:
```java
Method OnProcessInput(pInput As %RegisteredObject, pOutput As %RegisteredObject) As %Status
```
这里的`pInput`是适配器要发送给这个业务服务的输入对象,`pOutput`是输出对象。
首先查看选择的适配器类。 建议编辑 `OnProcessInput()` 方法签名以使用适配器所需的特定输入参数。
`OnProcessInput()` 方法应该执行以下部分或全部操作:
1. 可选地设置业务服务类的属性(在任何适当的时间)。最受关注的业务服务属性是 `%WaitForNextCallInterval`。它的值控制 `IRIS` 调用适配器的 `OnTask()` 方法的频率。
有关其他属性,请参阅 `Ens.BusinessService`的类参考。
2. 如有必要,验证输入对象。
3. 检查输入对象并决定如何使用它。
4. 创建请求消息类的实例,这将是业务服务发送的消息。
5. 对于请求消息,使用输入对象中的值适当地设置其属性。
6. 确定要将请求消息发送到哪里。当发送消息时,将需要在生产中使用业务主机的配置名称。
7. 将请求消息发送到生产(业务流程或业务操作)中的目的地。请参阅下一节。
8. 确保设置输出参数 (`pOutput`)。通常,将其设置为等于您收到的响应消息。此步骤是必需的。
9. 返回适当的状态。此步骤是必需的。
文章
Claire Zheng · 十月 18, 2022
各位开发者社区的同学们,大家好!
您想更好地获得帮助、讨论有趣的功能、发布公告或分享您的知识吗?在这篇文章中,我们将告诉你如何做到这一切。
我们将通过以下几部分来分享“如何做”:
一般发帖步骤
问题
文章或公告
讨论
一般发帖步骤
首先,你需要点击开发者社区网站顶部菜单中的“发布新帖”按钮:
之后,您将看到编辑器中显示创建一个问题、一则公告、一篇文章或一个讨论。不同类型的帖子有自己的一组必填字段和可选字段。
首先,让我们讨论所有类型的帖子的公共字段,然后继续讨论细节。
基本上,每篇文章都有一个标题*、正文*、组*、标签和一些额外的选项,你可以在其中添加调查或附加PDF文件。所有用星号(*)标记的文本字段都是必填项。因此,首先,你需要选择帖子的类型,可以像上面提到的问题,公告,文章或讨论。
接下来,请用最精确和简洁的方式表述你的问题的主要思想,并将其作为标题。
之后,在文章主体中,你可以写任何你想与他人分享的东西。在写文章的时候有两个选择。你可以使用编辑器“所见即所得”(WYSIWYG)模式或者Markdown。当然,这两种方法得到的结果是一样的。
vs.
在你写完文本后,你必须选择组,通常是InterSystems提供的技术、产品或服务。
在组字段之后,有一个标签字段,您可以在其中添加与文章内容相关的标签。有相当多的标签,所以请认真选择,因为其他成员会通过这些标签寻找或排序所需要的信息。
在标签下面,有一个链接可以查看更多选项。在那里,您可以附加一个pdf文档(例如,pdf格式的事件时间表)并提供您想要显示的名称。
你可以通过“更多选项”做的另一件事是添加投票。在字段中填写一个问题、可能的答案、选择持续时间等。
完成后,你可以预览你的帖子,看看它对其他人来说是什么样子,你可以保存它以便以后继续编辑,或立即发布它。
此外,您可以预约发布您的文章。只需点击向下箭头,选择安排帖子,并设置日期和时间。
一切都设置好后,只需点击安排帖子,就完成了。
基本上,这是创建帖子的常见功能。
问题
从它的名字来看,很明显,如果你需要别人的帮助,你应该选择这种类型的帖子。在这里,在开发者社区中,有很多专家,有些人可能已经遇到了相同的情况。所以不要犹豫,提出问题或回答问题吧:)
要寻求帮助,请阐明你的问题的主要思想,并将其作为一个标题写下来。接下来,请选择您正在使用的产品版本,因为不同版本具有不同的功能和类,一些建议可能对某些版本有用,而对其他版本无用。
更准确地说,您可以提供当前在$ZV文本框中使用的完整构建。要获得完整版本,可以打开Terminal并执行以下命令:
write $ZV
在你正在使用的IDE中也可以执行相同的操作,或者你可以在管理门户(Management Portal)中看到:
其余字段与前面描述的相同。
文章或公告
要分享你的知识或发布公告,你应该分别选择一种类型的帖子——文章或公告。这些类型的帖子除了公共字段之外还有一些额外的字段。这些是上一篇文章、下一篇文章和打开Exchange应用程序链接。
因此,基本上,如果当前的文章/声明(或讨论的分支)链接到另一篇文章,您可以在“上一个公告”字段中添加链接,这样其他社区成员将在文章末尾看到以下相关文章块。
你不需要再打开上一篇文章去添加到下一篇文章的链接,它将自动链接。
添加完这些链接后,用户也可以通过使用链接文章右上角的导航按钮轻松地从一个帖子导航到另一个帖子。
如果你的帖子在Open Exchange上有一个项目链接到它,你可以在相应的字段中添加这个项目的链接。
讨论
要开始关于某个功能的对话,或者分享您使用该技术的经验并寻求反馈,您可以开始一个讨论。这种类型的文章有所有公共字段,也有到上一篇和下一篇的链接。
就这些!
这就是您在社区上开始发布一个新帖子时所需要知道的。
期待着看到您的精彩发帖:)
文章
Kelly Huang · 七月 12, 2023
FHIR 通过提供标准化数据模型来构建医疗保健应用程序并促进不同医疗保健系统之间的数据交换,彻底改变了医疗保健行业。由于 FHIR 标准基于现代 API 驱动的方法,因此移动和 Web 开发人员更容易使用它。然而,与 FHIR API 交互仍然具有挑战性,尤其是在使用自然语言查询数据时。
隆重推出FHIR - AI 和 OpenAPI 链应用程序,该解决方案允许用户使用自然语言查询与 FHIR API 进行交互。该应用程序使用OpenAI 、 LangChain和Streamlit构建,简化了查询 FHIR API 的过程并使其更加用户友好。
FHIR OpenAPI 规范是什么?
OpenAPI 规范(以前称为 Swagger,目前是OpenAPI Initiative的一部分)已成为软件开发领域的重要工具,使开发人员能够更有效地设计、记录 API 并与 API 交互。 OpenAPI 规范定义了一种标准的机器可读格式来描述 RESTful API,提供了一种清晰一致的方式来理解其功能并有效地使用它们。
在医疗保健领域,FHIR 成为数据交换和互操作性的领先标准。为了增强FHIR的互操作能力, HL7正式记录了FHIR OpenAPI规范,使开发人员能够将FHIR资源和操作无缝集成到他们的软件解决方案中。
FHIR OpenAPI 规范的优点:
标准化 API 描述:OpenAPI 规范提供 FHIR 资源、操作和交互的全面且标准化的描述。开发人员可以轻松了解基于 FHIR 的 API 的结构和功能,从而更轻松地构建集成并与医疗保健系统交互。
促进互操作性:促进开发人员之间的协作,推动 FHIR 标准和最佳实践的采用。该规范提供了一种通用语言和框架,用于讨论基于 FHIR 的集成和实现,促进开发人员之间的协作。
增强的文档和测试:交互式文档和测试套件,以便更好地理解和验证。开发人员可以创建详细的API文档,使其他开发人员更容易理解和使用基于FHIR的API。基于规范的测试套件可以对API集成进行全面的测试和验证,确保医疗数据交换的可靠性和准确性。
改进的开发人员体验:自动生成客户端库和 SDK 以实现无缝集成。这简化了集成过程,并减少了将 FHIR 功能合并到应用程序中所需的时间和精力
FHIR、OpenAI 和 OpenAPI Chain 如何协同工作?
FHIR - AI 和 OpenAPI Chain应用程序利用 LangChain 来加载和解析 OpenAPI 规范( OpenAPI Chain )。之后,根据这些规范,通过 OpenAI 给出的提示链旨在理解自然语言查询并将其转换为适当的 FHIR API 请求。用户可以用简单的语言提出问题,应用程序将与所选的 FHIR API 交互以检索相关信息。
例如,用户可能会问:“患者 John Doe (ID 111) 的最新血压读数是多少?”然后,应用程序会将此查询转换为 FHIR API 请求,获取所需的数据,并以易于理解的格式将其呈现给用户。
FHIR - AI 和 OpenAPI 链的优势
用户友好的交互:通过允许用户使用自然语言查询与 FHIR API 交互,该应用程序使非技术用户可以更轻松地访问和分析医疗保健数据。
提高效率:该应用程序简化了查询 FHIR API 的过程,减少了获取相关信息所需的时间和精力。此外,它还有可能减少从应用程序中查找任何特定信息的点击次数(花费的时间)。
可定制:FHIR 标准简化了从任何 FHIR 服务器检索一致数据的过程,从而可以轻松定制。它可以轻松配置为与任何 FHIR API 无缝集成,为不同的医疗保健数据需求提供灵活且适应性强的解决方案。
FHIR 入门 - AI 和 OpenAPI 链
要开始使用 FHIR - AI 和 OpenAPI Chain 应用程序,请按照以下步骤操作:
从OpenAI Platform获取 OpenAI API 密钥。
获取 FHIR 服务器 API 端点。您可以使用自己的示例 FHIR 服务器(需要未经身份验证的访问),也可以按照InterSystems IRIS FHIR 学习平台中给出的说明创建临时示例服务器。
在线试用该应用程序或使用提供的说明在本地进行设置。
通过集成人工智能和自然语言处理功能,FHIR - AI 和 OpenAPI Chain 应用程序提供了一种与 FHIR API 交互的更直观的方式,使所有技术背景的用户都更容易访问和分析医疗数据。
如果您发现我们的应用程序很有前途,请在大奖赛中投票!
如果您能想到使用此实现的任何潜在应用程序,请随时在讨论线程中分享它们。
@Ikram Shah 致敬原创作者~
文章
jieliang liu · 五月 22
基于 XSLT 的互联互通临床文档到 FHIR 资源转换
国家卫健委互联互通成熟度评测中的临床共享文档,作为医疗信息交换的重要载体,采用了XML标准的文档格式。随着医疗信息化的发展,FHIR(Fast Healthcare Interoperability Resources)作为新一代医疗信息交换标准,因其简洁性、灵活性和RESTful架构,逐渐成为医疗数据交换的理想选择。将共享文档文档转换为FHIR资源,能够有效促进不同医疗系统间的数据互通,提升医疗信息的利用价值。
XSLT(可扩展样式表语言转换)是一种用于将XML文档转换为其他XML文档或文本格式的语言。在医疗数据转换场景中,XSLT凭借其强大的XML处理能力,成为共享文档到FHIR转换的理想工具。
我们知道共享文档文档是一种结构化的XML文档,通常包含以下主要部分:
- 文档头(Document Header):包含文档元数据,如文档类型、创建时间、作者等
- 临床数据部分(Clinical Sections):按章节组织的临床信息,如问题列表、用药记录、检查报告等
- 数据条目(Entries):具体的临床数据项,如诊断、药物、检验结果等
FHIR则采用了资源导向的设计理念,每个临床概念都被建模为独立的资源,通过RESTful API进行访问。FHIR资源具有以下特点:
- 模块化设计:每个资源专注于特定的临床领域,如Patient(患者)、Condition(疾病)、MedicationRequest(用药申请)等
- 灵活的数据结构:支持复杂的数据类型和嵌套结构
- 丰富的语义表达:通过代码系统和扩展机制提供标准化的术语支持
互联互通共享文档到FHIR的转换并非简单的格式转换,而是需要在保持临床语义的前提下,将共享文档的文档结构映射到FHIR的资源模型。这需要深入理解两种标准的数据模型和语义。
在Intersystems IRIS中,我们内嵌了我司创建的SDA医疗数据模型,此模型也是以xml为结构,并且在IRIS中已经开箱即用地实现了SDA模型到FHIR资源的转化,所以在IRIS互联互通套件中把共享文档向FHIR资源转化的思路就变成了由互联互通共享文档->SDA文档->FHIR资源的转化流程。(关于我司SDA数据模型的介绍参见https://docs.intersystems.com/irisforhealth20251/csp/docbook/Doc.View.cls?KEY=HXSDA_ch_sda#HXSDA_sda_structure)
下面是一个使用XSLT将互联互通文档中的信息转换为SDA再使用IRIS开箱即用功能转为FHIR资源的示例:
在互联互通套件中,我们已经实现了由互联互通文档向SDA转化的xslt,这些xslt在https://gitee.com/jspark/CCHDist 代码仓库的https://gitee.com/jspark/CCHDist/tree/master/image-iris/src/hccns/Setting/HCC 部分可以找到,文件名为HCC2SDA.xsl,其内引用的其他xlst模版也在相同文件夹下。
互联互通文档转化为SDA文档的示例代码:
Method HCCToSDA(pHCC As %Stream.Object, ByRef pSDA As %Stream.Object) As %Status
{
Set tSC = $$$OK
Try
{
If $ISOBJECT(pSDA) &&(pSDA.%IsA("%Stream.Object"))
{
}
Else
{
Set tSC = $$$ERROR(-10000,"Input parameter pSDA is not a stream")
Quit
}
// transfter to SDA first
Set tSlash = $Case($system.Version.GetOS(),"Windows":"\",:"/")
Set tXSL="file:///"_$SYSTEM.Util.InstallDirectory()_"csp"_tSlash_"xslt"_tSlash_"HCC"_tSlash_"HCC2SDA.xsl"
Set tSC = ..Transformer.Transform(pHCC,tXSL,.tSDA)
Quit:($$$ISERR(tSC))
D pSDA.CopyFrom(tSDA)
// store SDA for debug purpose
if (..Debug=1)
{
Set ..FileGUID = $Case($system.Version.GetOS(),"Windows":"\",:"/")_$SYSTEM.Util.CreateGUID()
Set tSDAFile=##class(%Stream.FileCharacter).%New()
Set tSC=tSDAFile.LinkToFile(..DebugPath_..FileGUID_".XML")
Set tSDAFile.TranslateTable="UTF8"
Do tSDAFile.Write(pSDA.Read())
Do tSDAFile.%Save(),tSDAFile.%Close()
Kill tSDAFile
}
}
Catch ex
{
Set tSC=ex.AsStatus()
}
Quit tSC
}
由SDA转化为FHIR资源的示例代码:
Method SDAToFHIR(pSDA As %Stream.Object, ByRef pFHIR As %Stream.Object) As %Status
{
If ($ISOBJECT(pSDA) && pSDA.%IsA("%Stream.Object"))
{}
Else
{
Quit $$$ERROR(-10000,"Input parameter pSDA is not a stream")
}
Set tSC = $$$OK
Try
{
// transfer SDA to FHIR
Set tFHIR = ##class(HS.FHIR.DTL.Util.API.Transform.SDA3ToFHIR).TransformStream(pSDA,"HS.SDA3.Container","R4")
If ('$ISOBJECT(tFHIR))
{
K tFHIR
Set tSC = $$$ERROR(-10000,"SDA transfer to FHIR error!")
Quit
}
Else
{
Set:($Get(pFHIR)="") pFHIR=##class(%Stream.TmpCharacter).%New()
Do pFHIR.Write(tFHIR.bundle.%ToJSON())
Do tFHIR.%Close()
// store SDA for debug purpose
if ..Debug=1
{
Set tFHIRFile=##class(%Stream.FileCharacter).%New()
Set tSC=tFHIRFile.LinkToFile(..DebugPath_..FileGUID_".JSON")
Set tFHIRFile.TranslateTable="UTF8"
Do pFHIR.Rewind()
Do tFHIRFile.Write(pFHIR.Read())
D tFHIRFile.%Save(),tFHIRFile.%Close()
K tFHIRFile
}
}
}
Catch ex
{
Set tSC=ex.AsStatus()
}
Quit tSC
}
综合起来互联互通文档转化为FHIR资源的示例,这里pFHIR的输出就是得到的fhir资源:
Method HCCToFHIR(pHCC, ByRef pFHIR) As %Status
{
If ($ISOBJECT(pHCC) && pHCC.%IsA("%Stream.Object"))
{}
Else
{
Quit $$$ERROR(-10000,"Input parameter pHCC is not a stream")
}
// transfter to SDA first
Set tSDA = ##class(%Stream.GlobalCharacter).%New()
Set tSC = ..HCCToSDA(pHCC,.tSDA)
Quit:($$$ISERR(tSC)) tSC
// store SDA for debug purpose
Set tSC = ..SDAToFHIR(tSDA,.pFHIR)
Quit tSC
}
在我们的IRIS互联互通套件里实现了由共享文档生成再到FHIR资源的转化的全部流程,https://gitee.com/jspark/CCHDist 可以在这个代码仓库中找到IRIS互联互通套件的代码实现以及相关介绍文档。
文章
Michael Lei · 三月 17, 2022
来自奥兰多的报道,Epic公司的医生Jackie Gerhart博士在HIMSS22上讨论了电子病历的庞大研究数据库及其发现如何形成个人服务。
新冠疫情的发生证明了随时获取数据的重要性。在未知的病毒海洋中,全人群的分析有助于为公共卫生官员、医疗服务供应商和患者提供洞察力。
现在,在Epic公司从事临床信息学工作的医生Jackie Gerhart说,这家全球领先的电子病历厂商正在希望帮助临床医生使用数据来形成个性化患者服务。
Gerhart在HIMSS22会议上与《医疗保健IT新闻》坐下来讨论了Epic广泛的病人数据库、Epic研究结果以及其即将推出的 "我的病人的最佳护理(Best Care for My Patient "工具如何向临床医生展示全国各地的最有效实践。
问: 作为一名医生,你是如何帮助Epic形成产品的?
答: 我们的作用是,第一,帮助软件的研究和开发;第二,新的面向未来的项目,使得相关的数据和分析可以在护理节点或未来的研究中使用。
然后,我们还与我们目前的医疗机构合作,确保他们得到真正好的客户经验。例如,你可能在不同的头条新闻中看到,临床医生不喜欢他们的电子病历,或者就像众所周知的,"哦,我的上帝,我还得在晚上做这个。"
我们的部分工作是试图从软件内部以不同的方式减少这种情况,并确保在内部的处理过程是尽可能有效的,这样我们就不会陷入比实际需要更多的过多记录笔记的工作中。所以这就是医生在Epic的主要作用。
问: 我们已经听到了很多关于Epic的Cosmos数据库的消息,它利用了来自客户的匿名化病人记录。公共卫生官员和临床医生如何利用它来获得全体人群的有效信息?
答:目前,Cosmos包含了1.4亿名患者和22亿次临床医生的记录。与其他许多医疗数据库不同,我们的目标是必须向它持续提供数据,以便能够使用它的数据--这意味着社区中的不同客户可以选择成为Cosmos的一部分,如果他们加入了,我们就从他们的病人那里获取匿名化的数据,即所谓有限数据集(Limited Data Set)。然后,这个有限的数据集与所有其他参与的组织一起被使用。
我们将这些数据用于研究,所以任何为Cosmos提供数据的组织都可以对数据进行查询。
作为一个数据库,Cosmos有三个不同之处。第一,你可以快速搜索它。第二点--当我们与CDC合作时,CDC告诉我们他们是主要看中我们的库对美国人口的代表程度与我们合作。我们的目标是努力获得尽可能清晰和接近于美国人口的代表性。我们一直在疫情期间与CDC做了大量的工作,特别是在CDC可能无法立即获得的较大的数据集上工作,或者他们可能没有一个有代表性的样本。
例如,在上个月,他们的一个MMWRs与新冠的突破性感染有关。他们有来自纽约公共卫生的数据,也有来自加利福尼亚公共卫生的数据。但同样,这是两个特定的人群,不一定代表整个美国。因此,我们一直在合作,通过Cosmos来查看。我们的研究团队和我们的数据科学家将与CDC一起设计一项研究,我们将运行数据,然后我们将有望一起工作,把它放到记录中。这就是第三个差异化因素:数据库的规模。
问: 对这些数据发现的审查过程是怎样的?
答:有三种情况。第一种是内部的,它是一个两组的过程。因此,在一个团队中,有一个临床医生,一个数据科学家,通常还有一些公共卫生或研究背景的人。然后在另一个团队中也有同样的人员构成。他们都独立设计出一种方法来回答一个广泛的问题。然后双方讨论,看对方的方法,一起工作,找出最合理的方法--然后他们再回去分开各自工作,用具体的方法重新编码一切。然后他们整合在一起,看看他们的结果是否有效。同样,这更像是一种准确性的检查。
在这两个团队的过程完成后,如果是在内部完成的,那么我们将其发布在我们的EpicResearch.org网站上。它不一定得出任何具体的因果关系。其目的是说,"我们调查了这个。这是初步的发现,现在,社区里的其他人去更多地研究这个问题,做一些更严格的统计分析。"
第二种是协作。这有点像我刚才和CDC谈的那样。我们还与凯撒家庭基金会和其他一些不同的组织如学术中心进行了合作。在这些情况下,它可以有两种方式:一种是跟刚才说的内部审查类似的;另一种是我们把它提交给一个杂志,然后杂志做他们自己的同行评审过程。
我们也有媒体的成员,他们想验证某个事情。这算是第三类,没有经过同行评议,实际上就是回答一个问题,把数据发给他们,然后让他们和他们的内部分析人员进行分析。
问:所以,这些都是对整个群体的洞察力。但你能和我谈谈 "对病人的最佳护理",以及它是如何利用Epic数据库的?
答:我们认为这个数据集有两个用途。一个是研究,也就是我刚才跟你谈的。然后是 "最佳护理",即专注于我作为临床医生在办公室里面对的每一个病人。
比方说,你是一个30岁的女性,因高血压来就诊,你也有过敏症,而且你正在服用胆固醇药物。在这种情况下,我想知道如何治疗你的血压。我们的想法是,根据整个社区其他地方的观察数据,尝试做个性化和个体化的医疗。
因此,数据库里有1.4亿病人。然后我把你的标准放进去--比方说把它降到10万个病人,然后我决定我还想包括你的高血脂症。所以现在我们大概有40,000名患者。在所有这4万名患者中,当他们的临床医生面临如何治疗高血压的确切问题时,这些临床医生使用了什么?他们的结果是什么?“病人最佳护理” 将显示这一部分。
问:作为一名临床医生,鉴于许多人已经面临着尽可能多看病人的巨大压力,你认为临床医生有时间这样做吗?
答:我将如何解决这个问题?如果我想用的话,这是我的工具带中又多了一个工具可以使用。我可能认识一个病人5年、10年、15年,并对我想做什么来治疗他们有了一个想法。但是,可能有一个不同的病人,我对他不太熟悉,或者有其他的疾病,而我没有阅读过相关的文献--比如,如果他们有这三种疾病,你该怎么治疗?
所以它只是给我提供了一点更多的信息。它的目的是非常简单和容易操作,这样你就可以在护理节点使用它。如果你想深入研究,那么你可以进一步探索数据。但对于普通的日常临床医生来说,我们的目标是尽量减少干预,只是向你提供数据,然后你选择如何使用它。
问:所以这个实施还没有上线,对吗?计划什么时候上线?
答:我们计划一到一年半左右的时间。
原文链接:https://www.healthcareitnews.com/news/best-care-my-patient-will-give-clinicians-data-driven-treatment-insights-says-epic
文章
Jingwei Wang · 七月 21, 2022
增加level
到目前为止,我们所创建的每个维度都包含一个具有一个level的层级结构。在这一节中,我们将在HomeD维度的层级结构中添加一个level。
在Tutorial模型中,为HomeD维度添加一个level,如下所示。
在类浏览器中,展开HomeCity。
拖动PostalCode并把它放到HomeD维度的H1层级结构上。这一步是在City 层之后添加新的PostalCode层。
点击PostalCode,在详细信息栏中,将名称改为ZIP Code。
编译这个立方体。
Build这个立方体。
进入分析器。
展开左边的HomeD维度。将ZIP Code级别显示为行。 注意,有些成员有相同的名字。有时,有多个成员具有相同的名字是正确的。然而,在这种情况下,这是一个错误,因为邮政编码是唯一的。 一个级别只有两种方式可以拥有多个同名的成员。
级别名称是基于一个级别属性,而这个属性是不唯一的。(对于一个例子,请看我们在前一章定义的医生级别)。
该级别有一个父级别。当DeepSee创建一个级别的成员时,它不仅考虑源属性或表达式;它还考虑父成员。
在现实中,邮政编码和城市之间存在着多对多的关系,所以两者都不是对方的父级。
当我们添加邮政编码级别时,我们把它放在城市级别之后,这意味着城市是邮政编码的父级。这影响了系统为ZIP Code生成成员的方式。例如,系统认为城市Juniper的ZIP Code 32006与城市Spruce的ZIP Code 32006是不一样的。
回到模型,纠正HomeD维度。
点击ZIP Code层。
点击向上箭头按钮。
编译这个立方体。模型会保存这个立方体。
Build这个立方体。
进入分析器。
展开左边的HomeD维度。将邮政编码级别显示为行。现在显示已经正确了。
双击第32006邮政编码,现在系统会显示这个邮政编码内的所有城市。
可以选择做以下工作,看看这个变化对事实表和水平表有什么影响。
进入管理门户,进入SAMPLES命名空间,如前所述。
点击系统资源管理器 > SQL。
在左边的区域,导航到表Tutorial_Cube.Fact。
注意这个表现在除了DxNameViaHomeCity之外,还有一个字段DxPostalCodeViaHomeCity。也就是说,事实表为每个级别都存储了一个值,甚至级别之间也有关联。
在左边的区域,导航到并打开表StarNameViaHomeCity。 注意,现在该表为每个城市存储了该城市所属的邮政编码。
关闭这个表,并导航到Tutorial_Cube.StarPostalCodeViaHomeCity这个表。
这个级别表和其他级别表一样:每个级别成员有一行。
时间level
在教程的这一部分,我们向立方体添加时间级别。
Patients类包括病人的出生日期的几种形式(这样你可以用DeepSee尝试不同的格式)。
Property BirthDate As %Date;
Property BirthDateTimeStamp As %TimeStamp;
Property BirthDateMV As %MV.Date;
DeepSee内置支持所有这三种格式,以及$HOROLOG格式和其他格式。
该类还包括病人的出生时间,作为BirthDateTimeStamp属性的一部分或作为以下属性。
Property BirthTime As %Time;
最灵活的属性是BirthDateTimeStamp,因为它同时包含了出生日期和出生时间,所以我们将用它作为时间level的基础。
进入模型,显示Tutorial cube。
点击添加元素。
在输入新元素名称时,输入 BirthD。
点击时间维度,点击确定。系统创建了一个维度,level和层级结构。
对该维度做如下修改。
点击属性旁边的搜索按钮(放大镜图标),点击BirthDateTimeStamp,然后点击确定。
将level重命名为 Year。
从函数提取值下拉列表中,选择Year。这个选项意味着这个level只基于病人的出生年份。
添加另一个level,在这个维度中点击层级结构H1。
点击添加元素。
在 "输入新元素名称 "中,输入 Month Year。
点击级别。
点击确定。系统在层级结构H1中,现有的年份level之后创建一个新的level,。
对于 Month Year级别,做如下修改。
从函数提取值下拉列表中,选择MonthYear。
这个选项意味着这个level是基于出生年份和月份的组合。
为BirthD维度添加另一个level和级别,点击BirthD维度名称。
点击添加元素。
在输入新元素名称中,键入H2。
点击层次。
点击确定。系统会创建一个新的level和级别。
对于新的level,做以下修改。
把这个level重命名为Time。
从函数提取值下拉列表中,选择HourNumber。这个选项意味着这个level是基于病人出生时的时间。
编译这个立方体。
Build立方体。
访问分析器。
当你在左边区域展开BirthD - Year,'现在' 是一个特殊的成员,指的是当前的一年。
使用集合属性
你可以基于集合属性来创建级别。具体来说,系统可以直接使用$LIST、%List返回的类型的列表,或者以字符分隔的列表。如果一个集合属性以某种其他方式存储数据,则有必要提取必要的数据并创建一个支持的列表类型。
DeepSee.Study.Patient类有几个集合属性,包括Allergies和DiagnosesAsLB。DiagnosesAsLB属性定义如下。
Property DiagnosesAsLB As %List;
Property Allergies As list Of BI.Study.PatientAllergy;
这一部分告诉你如何创建使用这些属性的level和度量。
访问模型,显示Tutorial cube。
添加一个使用DiagnosesAsLB属性的维度、层次结构和level。
点击添加元素。
在输入新元素名称中,输入DiagD。
点击数据维度。
点击确定。系统创建了一个维度,层级结构和级别。
把这个level重命名为 "Diagnoses"。
当level被选中时,点击属性的搜索按钮,选择DiagnosesAsLB属性,并点击确定。
对于源值是一个列表的类型,在资源值列表类型下拉菜单中选择 $LIST结构。这种类型指的是具有$LIST函数返回的格式或具有%List类型的数据。
保存立方体类。
在模型中,像以前一样添加一个维度、level和级别,并做如下修改。
维度名称应该是AllerD。
level名称应该是Allergies。不要为属性指定一个值。因为没有我们可以直接使用的属性。我们有必要通过表达式来提取Allergy列表。
为表达式指定以下值。
##class(Tutorial.Cube).GetAllergies(%source.%ID)
当系统Build立方体时,对于事实表中的每一行,系统都会评估这个表达式一次。
变量%source指的是当前记录。这个表达式得到病人的ID,调用utility方法(我们还没有写),并返回病人的过敏列表。
在资源值列表类型下拉菜单中选择 $LIST结构。
然后保存。
下一步将是编写这个实用方法。
打开Studio,访问SAMPLES命名空间。打开你的cube类,Tutorial.Cube。
添加一个名为GetAllergies()的方法,如下所示。
ClassMethod GetAllergies(ID As %Numeric) As %List
{
Set allergies=##class(BI.Study.Patient).%OpenId(ID,0).Allergies
If (allergies.Count()=0) {Quit $LISTFROMSTRING("")}
Set list=""
For i=1:1:allergies.Count() {
Set $LI(list,i)=allergies.GetAt(i).Allergen.Description
}
Quit list
}
给定一个病人的ID,这个方法返回该病人的过敏列表,格式是我们创建的级别所期望的。 %OpenId()的第二个参数指定了要使用的并发锁的级别。因为我们只需要从对象中读取数据,所以我们把这个值指定为0,这样就不会建立并发锁定,从而运行得更快。
在Studio中保存并编译你的cube类。
添加一个包含病人拥有的过敏症数量的度量。为此,我们使用Allergies属性,如下所示。
返回到模型。
点击添加元素。
在 "输入新元素名称 "中,输入 "Avg Allergy Count"。
点击 度量。
点击确定。
在汇总中,单击AVG。
对于表达式,输入以下内容。
##class(Tutorial.Cube).GetAllergyCount(%source.%ID)
我们将在以后写这个方法。
点击保存。因为你已经在Studio中编辑了这个类,模型显示了一个对话框,询问你是否要覆盖存储的定义。点击 OK。模型只覆盖你可以在模型中编辑的类定义的部分,也就是说,它不覆盖你已经添加到类中的任何方法。
在 Studio 中,给你的立方体类添加以下方法。
ClassMethod GetAllergyCount(ID As %Numeric) As %Numeric
{
Set allergies=##class(BI.Study.Patient).%OpenId(ID,0).Allergies
Quit allergies.Count()
}
保存并编译Studio中的立方体类。
重新Build DeepSee立方体。要做到这一点,你可以返回到模型,用你之前的方法重建。或者你可以打开一个终端窗口,在SAMPLES命名空间输入以下命令。
do ##class(%DeepSee.Utils).%BuildCube("tutorial")
注意,该方法使用了立方体的逻辑名称(而不是类的名称)。也注意到立方体的名字不区分大小写。
访问分析器。
以行的形式显示诊断。
将新的过敏显示为行,并显示Count和Avg Allergy Count度量。
null成员(称为None)代表了Allergies属性为null的病人。因为假设这些病人没有过敏症是不正确的,所以这个成员的名字有误导性。一个更好的名字是No Data Available。 请注意,对于属于None成员的病人,平均过敏数测量值为0。对这些病人来说,平均过敏数的测量值应该是空的。 同样注意到,对于没有已知过敏症的病人,平均过敏计数是1。这是因为Allergies属性确实包括特殊的nil known allergies。这些病人的平均过敏数应该是0。在本节后面,我们将纠正None成员的名称,并调整我们对平均过敏数测量的逻辑。
返回到模型。
点击 Allergies(过敏)层。
对于空替换字符串,指定No Data Available。
保存立方体类。
在Studio中,编辑GetAllergyCount()方法,如下。
ClassMethod GetAllergyCount(ID As %Numeric)
{
Set allergies=##class(BI.Study.Patient).%OpenId(ID,0).Allergies
//check to see if patient has any recorded allergy data
//if not, count is null
If allergies.Count()=0 {
Set allcount=""
}
//check to see if patient has "Nil known allergies"
//in this case, the patient has one "allergen" whose code is 000
Elseif ((allergies.Count()=1) && (allergies.GetAt(1).Allergen.Code="000")) {
Set allcount=0
}
Else {
Set allcount=allergies.Count()
}
Quit allcount
}
保存立方体类。编译cube类。
在模型中Build立方体。
访问分析器。将过敏症显示为行,并显示计数和平均过敏症计数的度量。
可以选择做以下工作,看看基于列表的级别是如何在事实和级别表中表示的。
进入管理门户,进入SAMPLES命名空间,点击系统资源管理器>SQL。
在左侧区域,导航并打开表Tutorial_Cube.Fact,滚动到字段DxDiagnosesAsLB。 这个字段包含病人的诊断。注意它在某些情况下包含多个值。该表也显示过敏水平,这个字段的名字不太明显,因为它是生成的,因为这个级别本身是基于表达式的。
现在导航到并打开Tutorial_Cube.StarDiagnosesAsLB这个表。这个level表和其他level表一样:每个level成员有一行。
定义替换值
在本教程的这一部分,我们使用将level的原始值转换为其他值的选项。这里我们将使用病人的年龄属性。我们将定义level,将病人放入大于一年的桶中。
Age Group级别将有以下成员。
0到29岁的成员由30岁以下的病人组成。
30至59岁的成员包括30至59岁的病人。
60岁以上成员由60岁以上的病人组成。
同样,Age Bucket级别将有0到9岁的成员,10到19岁的成员,以此类推。
访问模型。
按以下方法给AgeD维度增加一个level。
点击Age层。这样可以确保新的level,将被添加到Age 层之前。
点击添加元素。
在输入新元素名称时,键入Age Group。
点击级别。
点击确定。
重新定义新的年龄组级别,使其有一个范围表达,如下所示。
点击Age Group级别。
对于事实表中的字段名,指定DxAgeGroup这将使我们更容易看到级别定义如何影响生成的表。
对于属性,输入Age。
单击 "范围表达式 "旁边的搜索按钮。系统会显示一个对话框,你在这里指定一组替换。 对于数字数据,对于每一个替换,你要指定一个原始值的范围,以及一个新的值来代替。
在To中键入29。
点击To右边的按钮,使其变为中括号。
在Replacement Value中键入0到29。结果如下。
点击添加替换值。
在新行中,点击From和To旁边的切换按钮。
在From中输入30,在To中输入59。
在替换值中键入30至59。
单击 "添加替换",添加最后一行,使结果如下。
单击 "确定"。
保存立方体。
对于Age Bucket级别,我们可以使用同样的技术。然而,我们将使用一个替代方法:一个源表达式,将以Age为单位的年龄转换为一个字符串,对应于适当的十年。
在Studio中,打开BI.Tutorial.Cube类。
看看GetAgeBucket()方法的定义,其内容如下。
ClassMethod GetAgeBucket(age As %Numeric) As %String
{
If (age="") {Set return=""}
ElseIf (age<10) {Set return="0 to 9"}
ElseIf (age<20) {Set return="10 to 19"}
ElseIf (age<30) {Set return="20 to 29"}
ElseIf (age<40) {Set return="30 to 39"}
ElseIf (age<50) {Set return="40 to 49"}
ElseIf (age<60) {Set return="50 to 59"}
ElseIf (age<70) {Set return="60 to 69"}
ElseIf (age<80) {Set return="70 to 79"}
ElseIf (age>=80) {Set return="80+"}
Else {Set return=""}
Quit return
}
注意这个方法的输入只是一个数字,而不是一个病人的标识符。
在模型中,按以下方法给AgeD增加一个level。
点击Age层。这样可以确保新的level,即颗粒度较小的level,将在Agelevel之前被添加。
点击 Add Element。
对于输入新元素名称,输入 Age Bucket。
点击级别。
点击确定。
新的级别被添加到Age之前,但在Age Group之后。
对于事实表中的字段名,指定DxAgeBucket这将使我们更容易看到级别定义如何影响生成的表。
对于表达式,输入以下内容。
##class(BI.Tutorial.Cube).GetAgeBucket(%source.Age)
注意。 在实践中,你更有可能在一个中心位置包括Utility方法,如使用它们的立方体类。这个练习的一个要点是证明你可以调用这个命名空间中可以访问的任何类方法。同样地,你也可以调用任何常规或系统函数。
保存立方体。因为你已经在Studio中编辑了这个类,模型显示了一个对话框,询问你是否想覆盖存储的定义。点击确定。模型只覆盖了你可以在模型中编辑的类定义的部分,也就是说,它不覆盖你添加到类中的任何方法。
编译这个立方体。
重新Build立方体。
进入分析器。
显示Age Group 为行。将Age Bucket级别显示为行。你现在应该看到像下面这样的东西。
检查其中一个新的级别表,了解系统做了什么。
访问管理门户,进入SAMPLES命名空间,如前所述。点击系统资源管理器>SQL。
在左边的区域,导航到并打开表Tutorial_Cube.Fact。
这个表现在有三个字段来存储AgeD层级结构的值。
导航到并打开Tutorial_Cube.DxAgeGroup表。
打开Tutorial_Cube.DxAgeBucket表。因为这个level不在层级结构的顶端,所以它包含了一个引用,对于每个元素,它的父级成员在年龄组level;见DxAgeGroup列。
访问其他类
DeepSee 模型提供了对基类中大多数属性的简单访问,但我们也可以使用其他属性,包括你只能通过 SQL 访问的类的属性。在本教程的这一部分,我们使用DeepSee.Study.PatientDetails类中的数据作为我们立方体中的level。
DeepSee.Study.Patient和DeepSee.Study.PatientDetails类没有通过类属性连接,也没有任何正式的连接。相反,这两个表都有一个PatientID属性,它通过惯例将它们连接起来。也就是说,要找到某个病人的信息,你必须在这两个表中找到具有相同PatientID的记录。
在这个练习中,我们检查DeepSee.Study.PatientDetails中的数据,尝试各种SQL查询,并将查询包在一个方法中,用于定义一个级别。如果你对SQL比较熟练,你可能想跳过前面的一些步骤。
进入管理门户,进入SAMPLES命名空间,如前所述。点击系统资源管理器 > SQL。执行下面的查询。
SELECT PatientID FROM DeepSee_Study.Patient
记下其中的一个PatientID值,执行下面的查询。
SELECT * FROM DeepSee_Study.PatientDetails WHERE PatientID='SUBJ_100301'
SELECT FavoriteColor FROM DeepSee_Study.PatientDetails WHERE PatientID='SUBJ_100301'
现在我们需要写一个类方法,运行一个类似的查询,并返回查询得到的值。这个方法将包含一个用&sql()包裹的查询。
在Studio中,在你的立方体类Tutorial.Cube中添加以下方法。
ClassMethod GetFavoriteColor(patientID As %String) As %String
{
&sql(SELECT FavoriteColor INTO :ReturnValue FROM BI_Study.PatientDetails WHERE PatientID=:patientID)
If (SQLCODE'=0) {
Set ReturnValue=""
}
Quit ReturnValue
}
注意: 在DeepSee.Study.PatientDetails中的PatientID字段上有一个索引。这使得查询的运行速度比其他方式更快。
保存并编译该类。
在终端中,按如下方式测试该方法。
SAMPLES>write ##class(Tutorial.Cube).GetFavoriteColor("SUBJ_100301")
访问模型。
创建一个新的维度、level和级别,如下所示。
点击添加元素。
在输入新元素名称时,输入ColorD。
点击数据维度。
点击确定。系统会创建一个维度、level和级别。
将level重命名为喜爱的颜色。
对于事实表中的字段名,指定DxFavColor这将使我们更容易看到level定义如何影响生成的表。
对于级别,在表达式中键入以下内容。
##class(Tutorial.Cube).GetFavoriteColor(%source.PatientID)
保存立方体。因为你已经在Studio中编辑了这个类,Architect显示一个对话框,询问你是否要覆盖存储的定义。点击 OK。模型只覆盖你可以在模型中编辑的类定义的部分,也就是说,它不覆盖你已经添加到类中的任何方法。
编译这个立方体。
重新build立方体。
系统对基表的每条记录执行一次你的方法和它的嵌入式 SQL。
打开分析器,将新的级别显示为行。
文章
姚 鑫 · 六月 29, 2021
# 第二十二章 计算XPath表达式
`XPath`(XML路径语言)是一种基于XML的表达式语言,用于从XML文档获取数据。使用类中的`%XML.XPATH.Document`,可以轻松地计算`XPath`表达式(给定提供的任意XML文档)。
注意:使用的任何XML文档的XML声明都应该指明该文档的字符编码,并且文档应该按照声明的方式进行编码。如果未声明字符编码,InterSystems IRIS将使用本书前面的“输入和输出的字符编码”中描述的默认值。如果这些默认值不正确,请修改XML声明,使其指定实际使用的字符集。
## IRIS中XPath表达式求值概述
要使用InterSystems IRIS XML支持使用任意XML文档计算`XPath`表达式,请执行以下操作:
1. 创建`%XML.XPATH.Document`的实例。为此,请使用以下类方法之一:`CreateFromFile()`、`CreateFromStream()`或`CreateFromString()``。使用这些方法中的任何一种,都可以将输入XML文档指定为第一个参数,并接收%XML.XPATH.Document`的一个实例作为输出参数。
这一步使用内置的XSLT处理器解析XML文档。
2. 使用`%XML.XPATH.Document`实例的`EvaluateExpression()`方法。对于此方法,需要指定节点上下文和要计算的表达式。
节点上下文指定要在其中计算表达式的上下文。这使用`XPath`语法来表示到所需节点的路径。例如:
```java
"/staff/doc"
```
要计算的表达式还使用`XPath`语法。例如:
```java
"name[@last='Marston']"
```
可以将结果作为输出参数(作为第三个参数)接收。
注意:如果要迭代一大组文档并计算每个文档的`XPath`表达式,建议在处理完文档后,在打开下一个文档之前将该文档的`OREF`设置为`NULL`。这绕过了第三方软件的一个限制。在循环中处理大量文档时,此限制会导致CPU使用率略有增加。
# 创建XPath文档时的参数列表
若要在创建`%XML.XPATH.Document`的实例,请使用该类的`CreateFromFile()`、`CreateFromStream()`或`CreateFromString()`类方法。对于这些类方法,完整的参数列表按顺序如下:
1. PSource、pStream或pString-源文档。
- 对于`CreateFromFile()`,此参数是文件名。
- 对于`CreateFromStream()`,此参数是二进制流。
- 对于`CreateFromString()`,此参数是一个字符串。
2. PDocument-作为输出参数返回的结果。这是`%XML.XPATH.Document`的实例。
3. PResolver-解析源时使用的可选实体解析器。
4. PErrorHandler-一个可选的自定义错误处理程序。
5. PFlags-控制SAX解析器执行的验证和处理的可选标志。
6. PSchemaSpec-可选的架构规范,用于验证文档源。此参数是一个字符串,其中包含以逗号分隔的命名空间/URL对列表:
```java
"namespace URL,namespace URL"
```
这里,`Namespace`是用于模式的XML名称空间,URL是提供模式文档位置的URL。名称空间和URL值之间有一个空格字符。
7. PPrefix Mappings-可选的前缀映射字符串。
`CreateFromFile()`、`CreateFromStream()`和`CreateFromString()`方法返回应检查的状态。例如:
```java
Set tSC=##class(%XML.XPATH.Document).CreateFromFile("c:\sample.xml",.tDocument)
If $$$ISERR(tSC) Do $System.OBJ.DisplayError(tSC)
```
# 为默认命名空间添加前缀映射
当XML文档使用默认名称空间时,这会给`XPath`带来问题。请考虑以下示例:
```xml
Mr. Marston
Mr. Bertoni
Mr. Leslie
Ms. Farmer
```
在本例中, `` 元素属于名称空间,但没有名称空间前缀。`XPath`不提供访问 `` 元素的简单方法。
- 可以设置`%XML.XPATH.Document`实例的`Prefix Mappings`属性。该属性旨在为源文档中的每个默认名称空间提供唯一的前缀,以便`XPath`表达式可以使用这些前缀,而不是使用完整的名称空间URI。
`PrefixMappings` 属性是一个由逗号分隔的列表组成的字符串;每个列表项都是一个前缀,后跟一个空格,后跟一个命名空间URI。
- 调用`CreateFromFile()`、`CreateFromStream()`或`CreateFromString()`时,可以指定`PrefixMappings`参数。此字符串的格式必须与前面描述的相同。
然后以与使用任何名称空间前缀相同的方式使用这些前缀。
例如,假设将前面的XML读入`%XML.XPATH.Document`的实例时,按如下方式指定了前缀映射:
```java
"s http://www.staff.org"
```
在本例中,可以使用`"/s:staff/s:doc"`访问`` 元素。
请注意,可以使用实例方法`GetPrefix()`来获取先前为文档中的给定路径指定的前缀。
# 计算XPath表达式
要计算`XPath`表达式,请使用`%XML.XPATH.Document`实例的`EvaluateExpression()`方法。对于此方法,请按顺序指定以下参数:
1. PContext-节点上下文,指定在其中计算表达式的上下文。指定一个字符串,该字符串包含指向所需节点的路径的`XPath`语法。例如:
```java
"/staff/doc"
```
2. PExpression-选择特定结果的谓词。指定包含所需XPath语法的字符串。例如:
```java
"name[@last='Marston']"
```
注意:对于其他技术,通常的做法是将谓词连接到节点路径的末尾。类中的`%XML.XPATH.Document`不支持此语法,因为基础XSLT处理器需要节点上下文和谓词作为单独的参数。
3. PResults-作为输出参数返回的结果。
`EvaluateExpression()`方法返回应该检查的状态。例如:
```java
Set tSC=tDoc.EvaluateExpression("/staff/doc","name[@last='Smith']",.tRes)
If $$$ISERR(tSC) {Do $System.OBJ.DisplayError(tSC)}
```
# 使用XPath结果
XPath表达式可以返回XML文档的一个子树、多个子树或标量结果。在`%XML.XPATH.Document`的`EvaluateExpression()`方法旨在处理所有这些情况。具体地说,它返回一个结果列表。该列表中的每个项目都有一个Type属性,该属性具有下列值之一:
- `$$$XPATHDOM`-指示该项包含XML文档的子树。此项目是`%XML.XPATH.DOMResult`实例,提供导航和检查子树的方法。
- `$$$XPATHVALUE`-指示该项是单个标量结果。此项目是`%XML.XPATH.ValueResult`实例。
这些宏在`%occXSLT.inc`包含文件中定义。
## 检查XML子树
本节介绍如何导航由`%XML.XPATH.DOMResult`表示的XML子树,以及如何获取有关您在该子树中当前位置的信息。
### 导航子树
要导航`%XML.XPATH.DOMResult`的实例,可以使用该实例的以下方法:`Read()`、`MoveToAttributeIndex()`、`MoveToAttributeName()`、`MoveToElement()`和`Rewind()`。
要移动到文档中的下一个节点,请使用`read()`方法。`Read()`方法返回TRUE值,直到没有更多节点可读为止(即,直到到达文档末尾)。
导航到某个元素时,如果该元素具有属性,则可以使用以下方法导航到这些属性:
- 使用`MoveToAttributeIndex()`方法按索引(属性在元素中的序号位置)移动到特定属性。此方法只有一个参数:属性的索引号。请注意,可以使用`AttributeCount`属性来了解给定元素有多少个属性。
- 使用`MoveToAttributeName()`方法按名称移动到特定属性。此方法有两个参数:属性名称和命名空间URI(可选)。
完成当前元素的属性后,可以通过调用其中一个导航方法(如`read()`)移动到文档中的下一个元素。或者,可以调用`MoveToElement()`方法返回到包含当前属性的元素。
这里描述的所有方法都在文档中前进,但`Rewind()`方法除外,它导航到文档的开头并重置所有属性。
### 节点的属性
除`Type`属性外,`%XML.XPATH.DOMResult`的以下属性还提供有关当前位置的信息。
#### AttributeCount
如果当前节点是元素,则此属性指示元素的属性数。
#### EOF
如果读取器已到达源文档的末尾,则为true;否则为false。
#### HasAttributes
如果当前节点是一个元素,则如果该元素具有属性,则此属性为true(如果没有属性,则为false)。如果当前节点是属性,则此属性为true。
对于任何其他类型的节点,此属性为False。
#### HasValue
如果当前节点是具有值的节点类型(即使该值为空),则为True。否则,此属性为false。
#### LocalName
对于属性或元素类型的节点,这是当前元素或属性的名称,不带命名空间前缀。对于所有其他类型的节点,此属性为`NULL`。
#### Name
当前节点的完全限定名称,视节点类型而定。
#### NodeType
当前节点的类型,如下之一:`attribute`, `chars`, `cdata`, `comment`, `document`, `documentfragment`, `documenttype`, `element`, `entity`, `entityreference`, `notation`,或处理指令。
#### Path
对于元素类型的节点,这是到元素的路径。
对于所有其他类型的节点,此属性为空。
#### ReadState
表示总体读状态,有以下几种:
- `“initial”`表示`Read()`方法还没有被调用。
- `“cursoractive”`意味着`Read()`方法至少被调用过一次。
- `“eof”`表示已经到达文件的末尾。
#### Uri
当前节点的URI。
返回的值取决于节点的类型。
#### Value
值(如果有的话),适合于节点类型。
如果该值小于`32kb`,则为字符串。
否则,它是一个字符流。
## 检查标量结果
本节介绍在类中使用由`%XML.XPATH.ValueResult`表示的`XPath`结果。除`Type`属性外,该类还提供`Value`属性。
请注意,如果该值的长度大于32KB,则会自动将其放入流对象中。除非确定将收到的结果类型,否则应该检查`Value`是否为流对象。为此,可以使用`$IsObject`函数。(也就是说,如果此值是对象,则它是流对象,因为它是唯一可以是对象的类型。)
```java
// 如果结果长度大于32KB,则值可以是流
Set tValue=tResult.Value
If $IsObject(tValue){
Write ! Do tValue.OutputToDevice()
} else {
Write tValue
}
```
## 一般方法
除非可以确定在计算`XPath`表达式时会收到什么样的结果,否则应该编写代码来处理最常见的情况。代码的可能组织如下:
1. 查找返回结果列表中的元素数量。遍历此列表。
2. 对于每个列表项,检查`Type`属性。
- 如果`Type`为`$$$XPATHDOM`,, 在类中使用`%XML.XPATH.DOMResult`的方法导航并检查此XML子树。
- 如果`Type`为`$$$XPATHVALUE`,请检查`Value`属性是否为流对象。如果是流对象,则使用常用的流接口访问数据。否则,`Value`属性为字符串。
# 示例
本节中的示例针对以下`XML`文档计算`XPath`表达式:
```xml
Yao Xin
Mr. Bertoni
Mr. Leslie
Ms. Farmer
Ms. Midy
Mr. Dick
Mr. Boag
Mr. Curcuru
Mr. Kesselman
Mr. Auriemma
```
## 计算具有子树结果的XPath表达式
```java
/// 计算返回DOM Result的XPath表达式
ClassMethod Example1()
{
Set tSC=$$$OK
do {
Set tSC=##class(%XML.XPATH.Document).CreateFromFile(filename,.tDoc)
If $$$ISERR(tSC) {Do $System.OBJ.DisplayError(tSC) Quit}
Set context="/staff/doc"
Set expr="name[@last='Marston']"
Set tSC=tDoc.EvaluateExpression(context,expr,.tRes)
If $$$ISERR(tSC) Quit
Do ##class(%XML.XPATH.Document).ExampleDisplayResults(tRes)
} while (0)
If $$$ISERR(tSC) {Do $System.OBJ.DisplayError(tSC)}
Quit
}
```
本例选择了``元素的`last`属性等于`Yao`的任何节点。
该表达式在``元素的``节点中计算。
请注意,此示例使用`%XML.XPATH.Document`的`ExampleDisplayResults()`类方法。
执行`example1()`方法时,将先前的XML文件作为输入提供,您会看到以下输出:
```java
DHC-APP>d ##class(PHA.TEST.Xml).Example1("E:\temp\xmlXpath.txt")
XPATH DOM
element: name
attribute: first Value: Xin
attribute: last Value: Yao
chars : #text Value: Yao Xin
```
## 计算具有标量结果的`XPath`表达式
下面的类方法读取XML文件并计算返回标量结果的XPath表达式:
```java
/// 计算返回值结果的XPath表达式
/// d ##class(PHA.TEST.Xml).Example2("E:\temp\xmlXpath.txt")
ClassMethod Example2(filename)
{
Set tSC=$$$OK
do {
Set tSC=##class(%XML.XPATH.Document).CreateFromFile(filename,.tDoc)
If $$$ISERR(tSC) {Do $System.OBJ.DisplayError(tSC) Quit}
Set tSC=tDoc.EvaluateExpression("/staff","count(doc)",.tRes)
If $$$ISERR(tSC) Quit
Do ##class(%XML.XPATH.Document).ExampleDisplayResults(tRes)
} while (0)
If $$$ISERR(tSC) {Do $System.OBJ.DisplayError(tSC)}
Quit
}
```
这个例子统计``子节点。
该表达式在``元素中求值。
当执行`Example2()`方法,提供前面的XML文件作为输入时,会看到以下输出:
```java
DHC-APP> d ##class(PHA.TEST.Xml).Example2("E:\temp\xmlXpath.txt")
XPATH VALUE
2
```
文章
Lele Yang · 六月 8, 2023
++ 更新:2018 年 8 月 1 日
使用内置于 Caché 数据库镜像的 InterSystems 虚拟 IP (VIP) 地址有一定的局限性。特别是,它只能在镜像成员驻留在同一网络子网时使用。当使用多个数据中心时,由于增加了网络复杂性( 此处有更详细的讨论),网络子网通常不会“延伸”到物理数据中心之外。出于类似的原因,当数据库托管在云端时,虚拟 IP 通常无法使用。
负载均衡器(物理或虚拟)等网络流量管理设备可用于实现相同级别的透明度,为客户端应用程序或设备提供单一地址。网络流量管理器自动将客户端重定向到当前镜像主服务器的真实 IP 地址。自动化旨在满足灾难后 HA 故障转移和 DR 升级的需求。
网络流量管理器的集成
当今市场上有许多支持网络流量重定向的选项。这些中的每一个都支持类似甚至多种方法来根据应用程序要求控制网络流量。为了简化这些方法,我们考虑了三个类别:数据库服务器调用 API、网络设备轮询或两者的组合。
下一节将概述这些方法中的每一个,并就如何将这些方法与 InterSystems 产品集成提供指导。在所有情况下,仲裁器都用于在镜像成员无法直接通信时提供安全的故障转移决策。可以在此处找到有关仲裁器的详细信息。
出于本文的目的,示例图将描述 3 个镜像成员:主机、备份和 DR 异步。但是,我们知道您的配置可能比这更多或更少。
选项 1:网络设备轮询(推荐)
在这种方法中,网络负载均衡设备使用其内置的轮询机制与两个镜像成员通信以确定主镜像成员。
使用 2017.1 中可用的 CSP 网关的mirror_status.cxw页面的轮询方法可以用作 ELB 健康监视器中对添加到 ELB 服务器池的每个镜像成员的轮询方法。只有主镜像会响应“SUCCESS”,从而将网络流量仅定向到活动的主镜像成员。
此方法不需要向 ^ZMIRROR 添加任何逻辑。请注意,大多数负载均衡网络设备对运行状态检查的频率都有限制。通常,最高频率不少于 5 秒,这通常可以接受以支持大多数正常运行时间服务级别协议。
对以下资源的 HTTP 请求将测试本地缓存配置的镜像成员状态。
/csp/bin/mirror_status.cxw
对于所有其他情况,这些镜像状态请求的路径应该使用与请求真实 CSP 页面所用的相同的层次机制解析到适当的缓存服务器和名称空间。
示例:测试 /csp/user/ 路径中应用程序配置服务的镜像状态:
/csp/user/mirror_status.cxw
注意:调用镜像状态检查不会消耗 CSP 许可证。
根据目标实例是否是活动主机,网关将返回以下 CSP 响应之一:
** 成功(是主镜像成员)
===============================
HTTP/1.1 200 OK
Content-Type: text/plain
Connection: close
Content-Length: 7
SUCCESS
** 失败(不是主镜像成员)
===============================
HTTP/1.1 503 Service Unavailable
Content-Type: text/plain
Connection: close
Content-Length: 6
FAILED
** 失败(Caché服务器不支持Mirror_Status.cxw请求)
===============================
HTTP/1.1 500 Internal Server Error
Content-Type: text/plain
Connection: close
Content-Length: 6
FAILED
考虑下图作为轮询的示例。
同步故障转移镜像成员之间自动发生故障转移:
下图演示了将 DR 异步镜像成员提升到负载均衡池中,这通常假设同一个负载均衡网络设备正在为所有镜像成员提供服务(地理分割方案将在本文后面介绍)。根据标准 DR 程序,灾难恢复成员的提升涉及人为决策,然后是数据库级别的简单管理操作。但是,一旦采取该操作,就不需要对网络设备执行任何管理操作:它会自动发现新的主要设备。
选项 2:数据库服务器调用 API
在这种方法中,使用了网络流量管理设备,它有一个用故障转移镜像成员和潜在的 DR 异步镜像成员定义的服务器池。
当镜像成员成为主镜像成员时,向网络设备发出 API 调用以调整优先级或权重,以立即指示网络设备将网络流量定向到新的主镜像成员。
相同的模型适用于在主镜像成员和备份镜像成员都不可用的情况下提升 DR 异步镜像成员。
此 API 在 ^ZMIRROR 代码中定义为过程调用的一部分: $$CheckBecomePrimaryOK^ZMIRROR()
在此过程调用中,插入可用于相应网络设备的任何 API 逻辑和方法,例如 REST API、命令行界面等。与虚拟 IP 一样,这是网络配置的突然更改,不涉及任何应用程序逻辑以通知连接到故障主镜像成员的现有客户端正在发生故障转移。根据故障的性质,这些连接可能由于应用程序超时或错误、新主实例强制旧主实例关闭或客户端使用的TCP 保持活动计时器过期造成的故障本身而关闭。
因此,用户可能必须重新连接并登录。您的应用程序的行为将决定此行为。
选项 3:地理分散部署
在具有多个数据中心和可能地理分散的部署(例如具有多个可用性区域和地理区域的云部署)的配置中,需要使用基于 DNS 的负载均衡和本地负载均衡在一个简单且易于支持的模型中考虑地理重定向实践。
通过这种组合模型,引入了与 DNS 服务配合使用的附加网络设备,如 Amazon Route 53、F5 Global Traffic Manager、Citrix NetScaler Global Server Load Balancing 或 Cisco Global Site Selector,在每个数据中心、可用性区域或云地理区域与网络负载均衡器相结合。
在此模型中,前面提到的轮询(推荐)或 API 方法在本地用于操作任何镜像成员(故障转移或 DR 异步)的位置。这用于向地理/全球网络设备报告它是否可以将流量定向到任一数据中心。同样在此配置中,本地网络流量管理设备将其自己的 VIP 提供给地理/全球网络设备。
在正常稳定状态下,活动主镜像成员向本地网络设备报告它是主镜像成员并提供“启动”状态。此“启动”状态被转发到地理/全球设备以调整和维护 DNS 记录,以将所有请求转发到此活动的主镜像成员。
在同一数据中心内的故障转移场景中(备份同步镜像成员成为主镜像成员),API 或轮询方法与本地负载均衡器一起使用,现在重定向到同一数据中心内的新主镜像成员。由于新的主镜像成员处于活动状态,因此本地负载均衡器仍以“启动”状态响应,因此未对地理/全局设备进行任何更改。
出于本示例的目的,API 方法在下图中用于本地集成到网络设备。
在使用 API 或轮询方法到不同数据中心(备用数据中心中的同步镜像或 DR 异步镜像成员)的故障转移场景中,新提升的主镜像成员开始向本地网络设备报告为主要成员。
在故障转移期间,曾经包含主镜像成员的数据中心现在不再从本地负载均衡器向地理/全球报告“Up”。地理/全球设备不会将流量定向到该本地设备。备用数据中心的本地设备将向地理/全球设备报告“Up”,并将调用 DNS 记录更新以现在定向到备用数据中心的本地负载均衡器提供的虚拟 IP。
选项 4:多层和地理分散的部署
为了使解决方案更进一步,引入了一个单独的 Web 服务器层,既可以作为私有 WAN 的内部,也可以通过 Internet 访问。此选项可能是大型企业应用程序的典型部署模型。
以下示例显示了使用多个网络设备安全隔离和支持 Web 和数据库层的示例配置。在此模型中,使用了两个地理位置分散的位置,其中一个位置被视为“主要”位置,另一个位置纯粹是数据库层的“灾难恢复”位置。数据库层灾难恢复位置将在主要位置因任何原因停止服务的情况下使用。此外,此示例中的 Web 层将显示为双活,这意味着用户将根据各种规则(例如最低延迟、最低连接数、IP 地址范围或您认为合适的其他路由规则)定向到任一位置。
如上例所示,如果在同一位置发生故障转移,则会发生自动故障转移,并且本地网络设备现在指向新的主机。用户仍然连接到任一位置的 Web 服务器, Web 服务器及其关联的 CSP 网关继续指向位置 A。
在下一个示例中,考虑在位置 A 发生的整个故障转移或中断,其中主要和备份故障转移镜像成员都无法使用。然后,DR 异步镜像成员将被手动提升为主要和备份故障转移镜像成员。在升级后,新指定的主镜像成员将允许位置 B 的负载均衡设备使用前面讨论的 API 方法(轮询方法也是一个选项)报告“Up”。由于本地负载均衡器现在报告“启动”,基于 DNS 的设备将识别这一点并将流量从位置 A 重定向到现在的位置 B 以用于数据库服务器服务。
结论
在没有虚拟 IP 的情况下设计镜像故障转移有许多可能的排列。这些选项可应用于最简单的高可用性场景或具有多层的多地理区域部署,包括故障转移和 DR 异步镜像成员,以获得高可用性和容灾解决方案,旨在为您的应用程序维持最高水平的运营弹性.
希望本文提供了一些关于成功部署具有故障转移的数据库镜像的可能的不同组合和用例的见解,这些组合和用例适合您的应用程序和可用性要求。
文章
Michael Lei · 六月 23, 2021
部分 Ansible 帮助我解决了快速部署 Caché 和应用程序组件以进行数据平台基准测试的问题。 您可以使用相同的工具和方法来建立您的测试实验室、培训系统、开发或其他环境。 如果在客户站点部署应用程序,可以将大量部署自动化,并确保系统、Caché 和应用程序的配置符合您的应用程序最佳做法标准。
## 概述
作为一名技术架构师,我们的团队职责之一是在不同供应商的硬件和操作系统上对 InterSystems 数据平台进行基准测试。 通常,基础架构是预发布版,在必须归还或移交给其他人之前,我们的时间有限,因此,快速准确地设置基准测试,让我们有尽可能多的时间来做真正的基准测试工作,这一点至关重要。
多年来,我们通过 shell 脚本小程序来自动执行许多基准测试安装任务,并从速查表和检查清单中剪切和粘贴,但此类操作非常密集,而且容易出错,特别是有许多服务器并且在不同的操作系统之间切换时 - 在 SLES 11、Red Hat 6、Red Hat 7、AIX 等操作系统上安装或使用服务的差异可能很微小,让人厌烦。
在研究了几个可用于自动化配置和管理系统的软件选项之后,我选择了 Ansible 来执行预置数据平台、应用程序和基准测试组件的任务。 需要注意的是,我并没有规定 Ansible 是部署和配置的 **THE** 解决方案。 在选择 Ansible 之前,我研究了其他工具(如 Puppet 和 Chef)的功能和操作。 如果您的组织已经在使用其他工具,您可以使用它们 — 我在 Ansible 中使用的方法和命令等等应该可以转换到其他软件中,我希望这些帖子可以帮助您,不管您使用的是什么工具。
这是本系列的第一个帖子,将介绍在部署 InterSystems 数据平台应用程序时如何使用 Ansible。 本帖介绍如何通过安装 Caché 打下基础,下一帖将扩展解决方案以包括应用程序安装,包括使用 %installer 类。 本帖涵盖:
* Ansible 的概述和安装
* Ansible 便于管理和扩展的布局。
* 同时在一个或多个服务器上安装 Caché。
## 什么是 Ansible?
通过 Ansible 可以在配置一个或多个服务器的同时将复杂的任务自动化,并可以非常简单地添加新服务器。 任务会设计成幂等(您可以在同一台服务器上多次运行相同的脚本,得到的服务器配置将是相同的)。
我选择 Ansible 执行预置任务的一个关键原因是它对系统的要求最低(Python 2.7,Linux 服务器上自带 ),而且它是一个自包含解决方案 — Ansible 代码只安装在控制服务器上,并使用推送架构,通过 OpenSSH 在目标服务器上运行命令和脚本。 所预置的服务器上不需要任何代理。 作为对比,Chef 和 Puppet 采用拉取架构,软件在客户端服务器(Web、数据库等)上加载,并且客户端不断轮询主服务器以查找更新。 Ansible 的推送架构也适合按照您的计划需求逐步实施服务器。
Ansible 是开源的,由社区维护。 Ansible, Inc 从 2015 年开始为 Red Hat 所拥有。 Ansible, Inc 有一个高级的生命周期产品 (Ansible Tower),并提供收费的支持和培训,不过本帖中的所有内容均使用开源命令行版本。 还有一个活跃的社区 (Ansible Galaxy),您可以从中下载许多预制的解决方案来完成大量任务,如安装 Web 服务器、ftp、kerbros,不胜枚举。 例如,对于完整的基准测试部署项目,我包括了一个下载的 Apache 模块,并自定义成在 RHEL、SLES 或 Solaris(以及其他平台)上安装和配置 Apache 2.x。
Ansible 的下载和安装说明可以在 Ansible 网站和 Github 上找到。 如果您有问题或希望做出贡献,可以访问活跃社区。
https://www.ansible.com/get-startedhttp://docs.ansible.com
## 安装 Ansible
本帖中的示例已经在运行 Red Hat 7.0 和 7.2 的虚拟机上进行了测试 - 我也在我的安装了 Centos 7 的笔记本电脑上使用 virtual box 和 vagrant 对 Ansible 控制器服务器进行了初始测试。 Caché 不需要安装在控制器上,所以您的操作系统选择要多于 Caché 支持的平台列表。 为了简单起见,我使用了当前适用于 Red Hat 的 rpm 版本的 Ansible (Ansible 1.9.4),更高的版本可以从 GitHub 获取。
在示例中,我安装的是 cache-2015.2.2.805.0-lnxrhx64,但相同的常规过程也适用于 HealthShare 或 Ensemble 发行版。 您将在后面看到,我们使用特定文件名、目录路径等变量来参数化安装选项。
在第一个帖子中,我将任务削减为基本的 Caché 安装,因此大多数任务是独立于平台的。 当 Ansible playbook 启动时,首要任务之一是获取目标机器的清单 — 操作系统、接口卡、内存详细信息、CPU 数量、磁盘布局等,当运行命令以从目标上运行的实际命令(例如,Red Hat 上的 service start httpd 与 SLES 上的 /etc/init.d/apache2 restart)中提取 Ansible 脚本命令时,将使用目标操作系统的这些信息。
我假定您已读过说明,并且已按照您的平台说明在控制机上安装了 Ansible。
Ansible 必须使用 Linux 系统作为控制器,但目标系统可以是 Linux 或 Windows。 有关 Windows 目标的更多信息,请参见 Ansible 文档。
### 控制器系统安装示例:在 RHEL/CentOS 7 64 位上安装 Ansible
在 Red Hat 或 CentOS 上,必须先安装 epel-release (Extra Packages for Enterprise Linux) RPM,其中包含 Ansible。 epel 项目面向主要 Linux 发行版设计,提供了许多有用的开源软件包(网络、系统管理、监视等)。
[root@localhost tmp]# wget http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-5.noarch.rpm
:
:
[root@localhost tmp]# rpm -ivh epel-release-7-5.noarch.rpm
:
:
[root@localhost tmp]# yum --enablerepo=epel info ansible
Loaded plugins: langpacks, product-id, search-disabled-repos, subscription-manager
Installed Packages
Name : ansible
Arch : noarch
Version : 1.9.4
Release : 1.el7
Size : 7.0 M
Repo : installed
From repo : epel
Summary : SSH-based configuration management, deployment, and task execution system
URL : http://ansible.com
License : GPLv3+
Description :
: Ansible is a radically simple model-driven configuration management,
: multi-node deployment, and remote task execution system. Ansible works
: over SSH and does not require any software or daemons to be installed
: on remote nodes. Extension modules can be written in any language and
: are transferred to managed machines automatically.
[root@localhost tmp]#
[root@localhost tmp]# sudo yum install ansible
:
:
[root@localhost tmp]# ansible --version
ansible 1.9.4
configured module search path = None
**很好... 准备开始!**
## Ansible 方面
关于不同的 Ansible 组件(清单、Playbook、模块、角色等)的用法,应查看 Ansible 文档。
为了简化管理以及避免使用大而复杂的脚本文件,使用了预定义的目录结构和搜索路径。 在本帖中,我将使用一个采用 Ansible 建议的文件结构,当您考虑构建更大的安装示例时,可以将本结构用作模型。
所使用的 Ansible 模块带有注释和自我说明,可以在 Github 上获取。 下载文件并通读以了解工作流程。
我们的示例的基本目录包含以下文件;
* ansible.cfg:对 Ansible 默认值的更改。
* 清单:定义和描述工作环境。 例如服务器名称/IP。
* <各种>.yml 文件:这些文件描述了将为特定服务器角色运行的任务集。
### 术语
为了使后面的讨论更清晰,下面是一些 Ansible 术语的快速解释。
模块是用于创建对系统执行的自动化操作的构建块。 每个模块都为特定任务构建,可以使用参数更改该任务。 例如复制文件、创建用户、运行命令、启动服务等。 目前,默认的 Ansible 安装已包含 400 多个模块,另外还有更多来自社区的模块,您也可以创建您自己的模块。
模块组合在一起生成 play 和 playbook,作为执行自动化工作流程的一种方式。 一个 Play 可以有多个任务,一个 Playbook 可以有多个 Play。
角色允许您组合 Playbook。 角色可以被视为根据目标服务器使用情况分组在一起的服务器组件配置。 在本帖的示例中,角色构建配置层以构建服务器。
在我的基准测试设置中,我有以下角色来构建服务器:
* hs\_server\_common:配置操作系统,安装 Apache,安装 Caché。
* webserver:复制 Web 文件(csp、html、js 等),针对应用程序配置 Apache。
* generator:复制文件,创建和配置 webstress 生成器数据库、命名空间、全局映射等。
* dbserver:复制文件,配置数据库服务器系统设置、应用程序数据库、命名空间、全局映射等。
可以组合这些角色来构建不同的服务器类型:
* hs\_server\_common + webserver + generator = webstress 生成器服务器。
* hs\_server\_common + webserver = 应用程序 Web 服务器。
* hs\_server\_common + dbsevrer = 数据库服务器。
角色的组成以及每个角色中包含的配置将非常特定于要部署的应用程序。 本帖中的示例将使用最小的任务集,并假定操作系统已预先配置,但是,使用 Ansible 和 Galaxy 上的模块可以实现更复杂和全功能的系统配置。
## 关于安装 Caché 的说明 {.MsoNormal}
我写了几个例子来介绍一些有趣和有用的特性,下面是一些精华部分。 注意:这些示例可以用作 InterSystems 数据平台(Caché、HealthShare 和 Ensemble)的安装指南。 我写的示例是安装 HealthShare,但 HealthShare 和 Caché 示例中的操作是相同的。
**./testserver/roles/hs\_server\_common/tasks/main.yml**
这是配置操作系统、安装 Apache、安装 Caché 等常见任务的主线脚本。 本帖中对其进行了删减,使其只包含用于 Red Hat 的文件,以及只安装和配置 Caché。 您可以看到,Ansible 在启动后已经在 _ansible_*_ 变量(包括 _ansible\_os\_family_)中保存了操作系统信息,我们可以在脚本中使用这些变量做出决策。
**./testserver/roles/hs\_server\_common/tasks/configure-healthshare2015.yml**
这是用于安装 Caché 的主脚本。 浏览该脚本,会看到针对目标执行的任务的逻辑工作流程,包括:
* 创建操作系统用户和组。
* 从控制器上的清单文件夹复制安装文件。
* 解压缩安装文件。
* 使用静默安装方式安装 Caché(请参见下面的注释)。
* 复制 Caché 密钥文件
* 设置默认 Caché 实例
* 重启 Apache
* 重启 Caché
Caché 的静默安装有几个选项,包括:
* 使用 parameters.is 文件。 模板 .isc 文件由以前的安装创建,可以按原样使用,也可以修改。
* 使用 cinstall_silent 以及环境中设置的键值对。
* 使用 %installer 类。
在此示例中,我选择了使用 install\_silent,但是我还包括了一个注释掉的使用参数文件的备用方法,以说明如何在 Ansible 中使用模板文件(请参见 /roles/hs\_server\_common/templates/parameters\_hs20152_rh64.isc)。
在以后的帖子中,我将说明如何使用 %installer 类安装 Caché 以及设置数据库和命名空间。 有关安装选项的详细信息,请参见 Caché 在线文档,社区中也有一个非常好的帖子介绍了 %installer 类的使用。
当您想要安装并配置 Caché,以将 CSPGateway 与除了旧版本 Caché 内部的 Apache 版本以外的 Web 服务器一起使用时,参数文件很有用。 自 Caché 2016.1 起,%installer 提供此功能。
**./testserver/roles/hs\_server\_common/tasks/setup_RedHat.yml**
包含此示例是为了说明如何使用系统特定变量 (ansible_*) 和设置操作系统变量。
**./testserver/roles/hs\_server\_common/vars/***
变量文件包含键:值对形式的变量,如您所见,这是一种在不同环境和情况下重复使用相同脚本的方法。
## 运行 Caché 安装
对于此示例,我假定系统可用,并按如下方式进行设置。
1. 控制器已安装 Ansible,并且以下目录填充了来自 Github 的文件和结构。
* **./testserver/***:包含清单、.yml 文件等等的目录树。 包括...
* **./testserver/Distribution_Files/Cache**:(包含 Caché 分发包和 cache.key 的清单)。
2. 目标机器已安装 Red Hat 和 Apache。
您需要编辑以下文件来为您的测试环境自定义安装。
1. **inventory_test**
您需要编辑测试服务器名称或 IP 地址。
2. .**/testserver/roles/hs\_server\_common/vars/healthshare2015.yml**
您必须编辑路径以适合您的测试环境。 查看目标服务器的以下路径:
* **common\_install\_base_path**:清单文件将复制到该位置后解压,并运行 Caché 安装。
* **ISC\_PACKAGE\_INSTALLDIR**:Caché 安装目录
如果目标服务器上不存在这些目录路径,将会创建。
注:自动化部署的特性之一是并行构建多个服务器。 如果清单文件中有多个服务器,在各个目标服务器上将并发运行每个步骤,直至该步骤完成,然后再在组中的每个服务器上开始下一个步骤。 如果任何服务器上有步骤失败,脚本将停止。 您将看到一条错误消息,帮助您更正问题。 更正错误后,只需从头重新运行 — 这是脚本的一个关键特性 — 脚本设计成幂等。 幂等的意思是,在某个步骤运行某个模块(例如复制文件)时,如果文件已经存在,则该步骤不会重新运行,脚本只是继续下一个步骤。 copy 之类的模块有参数可以设置为强制复制,但这不是默认设置。 仔细检查脚本会发现,在某些情况下使用了“creates”参数,例如:
- name: unattended install of hs using cinstall_silent
shell: >
ISC_PACKAGE_INSTANCENAME="{{ ISC_PACKAGE_INSTANCENAME }}"
ISC_PACKAGE_INSTALLDIR="{{ ISC_PACKAGE_INSTALLDIR }}"
ISC_PACKAGE_UNICODE="{{ ISC_PACKAGE_UNICODE }}"
ISC_PACKAGE_INITIAL_SECURITY="{{ ISC_PACKAGE_INITIAL_SECURITY }}"
ISC_PACKAGE_MGRUSER="{{ ISC_PACKAGE_MGRUSER }}"
ISC_PACKAGE_MGRGROUP="{{ ISC_PACKAGE_MGRGROUP }}"
ISC_PACKAGE_USER_PASSWORD="{{ ISC_PACKAGE_USER_PASSWORD }}"
ISC_PACKAGE_CACHEUSER="{{ ISC_PACKAGE_CACHEUSER }}"
ISC_PACKAGE_CACHEGROUP="{{ ISC_PACKAGE_CACHEGROUP }}" ./cinstall_silent
chdir="{{ common_install_base_path }}/{{ hs_install_unpack_path }}"
args:
creates: "{{ ISC_PACKAGE_INSTALLDIR }}/cinstall.log"
上面一节使用 creates 参数告诉 Ansible 模块(在本例中是 shell 模块),此操作创建 cinstall.log 文件。 如果模块发现该文件(Caché 已经安装),则此步骤将不会运行。
好了,全部设置完毕后,我们可以运行安装了。
$ ansible-playbook dbserver.yml
PLAY [dbservers] **************************************************************
GATHERING FACTS ***************************************************************
ok: [db1]
TASK: [hs_server_common | include_vars healthshare2015.yml] *******************
ok: [db1]
TASK: [hs_server_common | include_vars os-RedHat.yml] *************************
ok: [db1]
etc
etc
etc
TASK: [hs_server_common | Create default cache group] *************************
changed: [db1]
TASK: [hs_server_common | Create default cache manager group] *****************
changed: [db1]
TASK: [hs_server_common | Create default cache user] **************************
changed: [db1]
TASK: [hs_server_common | Create default cache system users] ******************
changed: [db1]
TASK: [hs_server_common | Create full hs install temp directory] **************
changed: [db1]
TASK: [hs_server_common | Check tar file (gunzipped already) does not exist] ***
ok: [db1]
TASK: [hs_server_common | Copy healthshare install file] **********************
changed: [db1]
TASK: [hs_server_common | un zip hs folder] ***********************************
changed: [db1]
TASK: [hs_server_common | un tar hs install] **********************************
changed: [db1]
TASK: [hs_server_common | Create hs install directory] ************************
changed: [db1]
TASK: [hs_server_common | touch ztrak.conf.] **********************************
changed: [db1]
TASK: [hs_server_common | Process parameters file] ****************************
changed: [db1]
TASK: [hs_server_common | unattended install of hs using cinstall_silent] *****
changed: [db1]
TASK: [hs_server_common | copy hs key] ****************************************
changed: [db1]
TASK: [hs_server_common | Set default hs instance] ****************************
changed: [db1]
TASK: [hs_server_common | restart apache to initialize CSP.ini file] **********
changed: [db1]
NOTIFIED: [hs_server_common | restart healthshare] ****************************
changed: [db1]
PLAY RECAP ********************************************************************
db1 : ok=32 changed=21 unreachable=0 failed=0
如果我们查看目标服务器 — 数据库服务器 Caché 现在已经启动并运行。
$ ccontrol list
Configuration 'H2015' (default)
directory: /test/hs2015
versionid: 2015.2.1.705.0
conf file: cache.cpf (SuperServer port = 1972, WebServer = 57772)
status: running, since Wed Feb 17 15:59:11 2016
state: ok
## 总结
在后续的帖子中,我将构建包含其他任务的脚本,如编辑配置文件和使用 %installer 类配置应用程序。
如果您对此感兴趣并开始创建您自己的部署,请随时与我联系,提出问题或建议。 我经常在全球峰会上发表关于虚拟化和性能的演讲 - 因此,如果您参加今年的全球峰会,请介绍一下您自己,我非常乐意和您聊一聊 Ansible 的使用经验或任何其他系统架构话题。
文章
姚 鑫 · 四月 28, 2021
# 第八章 解释SQL查询计划(一)
# SQL语句
这个SQL语句列表为每个表提供了SQL查询和其他操作的记录,包括插入、更新和删除。
这些SQL语句链接到一个查询计划,该链接提供冻结该查询计划的选项。
系统为每个SQL DML操作创建一条SQL语句。
这提供了一个按表、视图或过程名称列出的SQL操作列表。
如果更改表定义,可以使用此SQL Statements列表来确定每个SQL操作的查询计划是否会受到此DDL更改的影响,以及/或是否需要修改某个SQL操作。
然后,可以:
- 确定每个SQL操作使用哪个查询计划。
可以决定使用反映对表定义所做更改的修改后的查询计划。
或者可以冻结当前查询计划,保留在更改表定义之前生成的查询计划。
- 根据对表定义所做的更改,确定是否对对该表执行SQL操作的例程进行代码更改。
注意:SQL语句是一个SQL例程列表,它们可能会受到表定义更改的影响。
它不应该用作表定义或表数据更改的历史记录。
# 创建SQL语句操作
下面的SQL操作会创建相应的SQL语句:
数据管理(DML)操作包括对表的查询、插入、更新和删除操作。
每个数据管理(DML)操作(动态SQL和嵌入式SQL)在执行时都会创建一个SQL语句。
- 动态SQL `SELECT`命令在准备查询时创建SQL语句。
此外,在管理门户缓存查询列表中创建了一个条目。
- 嵌入式SQL基于指针的`SELECT`命令在`OPEN`命令调用声明的查询时创建SQL语句。管理门户缓存查询列表中不会创建单独的条目。
如果查询引用多个表,则在名称空间的SQL语句中创建一条SQL语句,该语句列出表/视图/过程名列中的所有被引用表,并且对于每个单独的被引用表,该表的SQL语句列表都包含该查询的条目。
SQL语句是在第一次准备查询时创建的。如果多个客户端发出相同的查询,则只记录第一次准备。例如,如果JDBC发出一个查询,然后ODBC发出一个相同的查询,那么SQL语句索引将只有关于第一个JDBC客户端的信息,而不是关于ODBC客户端的信息。
大多数SQL语句都有关联的查询计划。
创建该查询计划时,将解冻该查询计划;
可以随后将该查询计划指定为冻结计划。
带有查询计划的SQL语句包括涉及`SELECT`操作的DML命令。
下面的“计划状态”部分列出了没有查询计划的SQL语句。
注意:SQL语句只列出SQL操作的最新版本。
除非冻结SQL语句,否则InterSystems IRIS®数据平台将用下一个版本替换它。
因此,在例程中重写和调用SQL代码将导致旧的SQL代码从SQL语句中消失。
## 其他SQL语句操作
下面的SQL命令执行更复杂的SQL语句操作:
- `CREATE TRIGGER`: 在定义触发器的表中,无论是在定义触发器还是在提取触发器时,都不会创建SQL语句。
但是,如果触发器对另一个表执行DML操作,那么定义触发器将在被触发器代码修改过的表中创建一个SQL语句。
`Location`指定在其中定义触发器的表。
在定义触发器时定义SQL语句;
删除触发器将删除SQL语句。
触发触发器不会创建SQL语句。
- `CREATE VIEW` 不创建SQL语句,因为没有编译任何内容。
它也不会更改源表的SQL语句的Plan Timestamp。
然而,为视图编译DML命令会为该视图创建一个SQL语句。
# List SQL语句
本节介绍使用Management Portal界面列出SQL语句的详细信息。
也可以使用`^rINDEXSQL`全局返回SQL语句的索引列表。
注意,这个SQL语句List可能包含过时的(不再有效的)List
从Management Portal SQL界面可以列出如下SQL语句:
- SQL语句选项卡:此选项卡列出名称空间中的所有SQL语句,先按模式排序,然后按每个模式中的表名/视图名排序。此列表仅包括当前用户拥有权限的那些表/视图。如果SQL语句引用多个表,则表/视图/过程名列将按字母顺序列出所有被引用的表。
- 通过单击列标题,可以按表/视图/过程名、计划状态、位置、SQL语句文本或列表中的任何其他列对SQL语句列表进行排序。这些可排序列使能够快速查找,例如,所有冻结计划(计划状态)、所有缓存查询(位置)或最慢的查询(平均时间)。
- 可以使用此选项卡提供的`Filter`选项将列出的SQL语句缩小到指定的子集。
指定的筛选器字符串筛选SQL语句列表中的所有数据,最有用的是模式或模式。
表名、例程位置或SQL语句文本中找到的子字符串。
过滤字符串不区分大小写,但必须紧跟语句文本标点空格`(name , age, not name,age)`。
如果查询引用了多个表,如果它选择了表/视图/过程名称列中的任何引用表,则`Filter`包括SQL语句。
过滤选项是用户自定义的。
- 最大行选项默认为`1,000`。
最大值为`10,000`。
最小值为`10`。
要列出超过`10,000`条SQL语句,请使用`INFORMATION_SCHEMA.STATEMENTS`。
页面大小和最大行选项是用户自定义的。
- Catalog Details选项卡:选择一个表并显示其Catalog详细信息。
此选项卡提供了一个表的SQL语句按钮,用于显示与该表关联的SQL语句。
注意,如果一个SQL语句引用了多个表,那么它将在表的SQL语句列表中列出每个被引用的表,但只有当前选择的表在表名列中列出。
通过单击列标题,可以根据列表的任何列对表的SQL语句列表进行排序。
可以使用`SQLTableStatements()`目录查询或`INFORMATION_SCHEMA`。
语句,列出根据各种条件选择的SQL语句,如下面的查询SQL语句中所述。
## 列表列
SQL语句选项卡列出名称空间中的所有SQL语句。目录详细信息选项卡表的SQL语句按钮列出了所选表的SQL语句。这两个列表都包含以下列标题:
- `#`:列表行的顺序编号。这些数字与特定的SQL语句没有关联。
- 表/视图/过程名:限定的SQL表(或视图或过程)名:`schema.name`。如果SQL语句查询引用了多个表或视图,则所有这些表或视图都会在此处列出。
- 计划状态:请参阅下面的计划状态。
- 新计划:见“冻结计划”一章中不同的新计划。
- 自然查询:请参阅下面的语句详细信息部分。
- 计数:请参阅下面的性能统计数据。
- 平均计数:请参阅下面的性能统计数据。
- 总时间:请参阅下面的性能统计数据。
- 平均时间:请参阅下面的性能统计数据。
- 标准开发人员:请参阅下面的性能统计数据。
- Location(S):编译查询的位置,例程名称(对于嵌入式SQL)或缓存查询名称(对于动态SQL)。如果包名为`%sqlcq`,则SQL语句为缓存查询。
- SQL语句文本:规范化格式的SQL语句文本(截断为`128`个字符),可能与以下SQL语句文本中指定的命令文本不同。
## 计划状态
计划状态列出以下内容之一:
- 解冻Unfrozen:未冻结,可冻结。
- 解冻/平行Unfrozen/Parallel::未冻结,不能冻结。
- 冻结/显式Frozen/Explicit:由用户动作冻结,可以解冻。
- Frozen/Upgrade:被InterSystems IRIS版本升级冻结,可以解冻。
- blank:没有关联的查询计划:
- `INSERT... VALUES()` 命令创建的SQL语句没有关联的查询计划,因此无法解冻或冻结(计划状态列为空)。尽管此SQL命令不会生成查询计划,但它在SQL语句中的列表仍然很有用,因为它允许快速定位针对该表的所有SQL操作。例如,如果向表中添加一列,则可能需要找出该表的所有SQL插入的位置,以便可以更新这些命令以包括此新列。
- 基于游标的`UPDATE`或`DELETE`命令没有关联的查询计划,因此不能解冻或冻结(“计划状态”列为空)。对已声明的游标执行`OPEN`命令会生成一条带有关联查询计划的SQL语句。使用该游标的嵌入式SQL语句(`FETCH cursor, UPDATE...WHERE CURRENT OF cursor, DELETE...WHERE CURRENT OF cursor, and CLOSE cursor`)不生成单独的SQL语句。即使基于游标的`UPDATE`或`DELETE`不会产生查询计划,但SQL语句中列出的查询计划仍然很有用,因为它允许快速定位针对该表的所有SQL操作。
## SQL语句文本
SQL语句文本通常不同于SQL命令,因为SQL语句生成规范化了字母和空格。
其他差异如下:
如果从Management Portal接口或SQL Shell接口发出查询,所得到的SQL语句与在`SELECT`语句前面加上`DECLARE QRS CURSOR FOR`(其中“QRS”可以是各种生成的游标名称)的查询不同。
这允许语句文本与Dynamic SQL缓存的查询相匹配。
如果SQL命令指定了一个非限定的表或视图名,那么生成的SQL语句将使用模式搜索路径(如果提供了DML)或默认模式名来提供模式。
SQL语句文本在`1024`个字符之后被截断。
要查看完整的SQL语句文本,请显示SQL语句详细信息。
一个SQL命令可能会产生多个SQL语句。
例如,如果一个查询引用一个视图,SQL Statements将显示两个语句文本,一个列在视图名称下,另一个列在基础表名称下。
冻结任意一条语句都会导致两个语句的Plan State为Frozen。
当通过xDBC准备SQL语句时,如果需要这些选项来生成语句索引散列,则SQL语句生成会向语句文本添加SQL Comment Options (`# Options`)。
如下面的例子所示:
```sql
DECLARE C CURSOR FOR SELECT * INTO :%col(1) , :%col(2) , :%col(3) , :%col(4) , :%col(5) FROM SAMPLE . COMPANY /*#OPTIONS {"xDBCIsoLevel":0} */
```
# 陈旧的SQL语句
删除与SQL语句关联的例程或类时,不会自动删除SQL语句列表。这种类型的SQL语句列表称为陈旧。由于访问此历史信息以及与SQL语句相关联的性能统计信息通常很有用,因此这些过时的条目将保留在管理门户SQL语句列表中。
可以使用`Clean Stale`(清除陈旧)按钮删除这些陈旧条目。清除陈旧删除关联例程或类(表)不再存在或不再包含SQL语句查询的所有非冻结SQL语句。清除陈旧不会删除冻结的SQL语句。
可以使用`$SYSTEM.SQL.Statement.Clean()`方法执行相同的清除陈旧操作。
如果删除与SQL语句关联的表(持久化类),则会修改表/视图/过程名称列,如下例所示:`SAMPLE.MYTESTTABLE - Deleted??;` ;已删除表的名称将转换为全部大写字母,并标记为“`DELETED??`”。或者,如果SQL语句引用了多个表:`SAMPLE.MYTESTTABLE - Deleted?? Sample.Person`.
- 对于动态SQL查询,删除表时`Location`列为空,因为与该表关联的所有缓存查询都已自动清除。`CLEAN STALE`删除SQL语句。
- 对于嵌入式SQL查询,`Location`列包含用于执行查询的例程的名称。当更改例程使其不再执行原始查询时,位置列为空。`CLEAN STALE`删除SQL语句。删除查询使用的表时,该表被标记`“Deleted??”`;Clean Stale不会删除SQL语句。
注:系统任务在所有名称空间中每小时自动运行一次,以清除任何可能过时或具有过时例程引用的SQL语句的索引。执行此操作是为了维护系统性能。此内部清理不会反映在管理门户SQL语句列表中。可以使用管理门户监视此每小时一次的清理或强制其立即执行。要查看此任务上次完成和下次调度的时间,请依次选择系统操作、任务管理器、任务调度,然后查看清理SQL语句索引任务。可以单击任务名称查看任务详细信息。在Task Details(任务详细信息)显示中,可以使用Run(运行)按钮强制立即执行任务。请注意,这些操作不会更改SQL语句清单;必须使用Clean Stale来更新SQL语句清单。
# 数据管理(DML)SQL语句
创建SQL语句的数据管理语言(DML)命令包括:`INSERT`、`UPDATE`、`INSERT`或`UPDATE`、`DELETE`、`TRUNCATE TABLE`、`SELECT`和`OPEN CURSOR`(用于声明的基于游标的`SELECT`)。可以使用动态SQL或嵌入式SQL来调用DML命令。可以为表或视图调用DML命令,InterSystems IRIS将创建相应的SQL语句。
注意:系统在准备动态SQL或打开嵌入式SQL游标时(而不是在执行DML命令时)创建SQL语句。SQL语句时间戳记录此SQL代码调用的时间,而不是查询执行的时间(或是否)。因此,SQL语句可能表示从未实际执行的表数据更改。
准备动态SQL DML命令将创建相应的SQL语句。与此SQL语句关联的位置是缓存查询。动态SQL是在从管理门户SQL界面、SQL Shell界面执行SQL或从`.txt`文件导入时准备的。清除未冻结的缓存查询会将相应的SQL语句标记为清除陈旧删除。清除冻结的缓存查询会删除相应SQL语句的位置值。解冻SQL语句会将其标记为Clean Stale删除。
执行非游标嵌入式SQL数据管理语言(DML)命令将创建相应的SQL语句。每个嵌入式SQL DML命令都会创建相应的SQL语句。如果一个例程包含多个嵌入式SQL命令,则每个嵌入式SQL命令都会创建一个单独的SQL语句。(某些嵌入式SQL命令会创建多条SQL语句。)。SQL语句清单的Location列指定包含嵌入式SQL的例程。通过这种方式,SQL语句维护每个嵌入式SQL DML命令的记录。
打开基于游标的嵌入式SQL数据管理语言(DML)例程将创建带有查询计划的SQL语句。
关联的嵌入式SQL语句(`FETCH`游标、`CLOSE`游标)不会生成单独的SQL语句。
在FETCH游标之后,一个关联的`UPDATE table WHERE CURRENT OF cursor 或DELETE FROM table WHERE CURRENT OF cursor`会生成一个单独的SQL语句,但不会生成单独的`Query Plan`。
插入文字值的`INSERT`命令将创建一个“计划状态”列为空的SQL语句。
由于该命令不会创建查询计划,因此无法冻结SQL语句。
## select命令
调用查询将创建相应的SQL语句。
它可以是一个简单的`SELECT`操作,也可以是一个基于指针的`SELECT/FETCH`操作。
可以对表或视图发出查询。
- 包含`JOIN`的查询为每个表创建相同的SQL语句。
Location是清单中存储的每个表的相同查询。
如SQL语句详细信息例程和关系部分所述,该语句使用以下关系列出所有表。
- 包含选择项子查询的查询为每个表创建相同的SQL语句。
`Location`是清单中存储的每个表的相同查询。
如SQL语句详细信息例程和关系部分所述,该语句使用以下关系列出所有表。
- 引用外部(链接)表的查询不能被冻结。
- 一个包含`FROM`子句`%PARALLEL`关键字的查询可以创建多个SQL语句。
你可以通过调用来显示这些生成的SQL语句:

这将显示包含原始查询的语句哈希的`Statement`列和包含生成的查询版本的语句哈希的`ParentHash`列。
`%PARALLEL`查询的SQL语句的计划状态为“未冻结/并行”,不能被冻结。
- 不包含FROM子句(因此不引用任何表)的查询仍然创建SQL语句。
例如:`SELECT $LENGTH('this string')`创建一个SQL语句,表列值`%TSQL_sys.snf`。
文章
姚 鑫 · 五月 13, 2021
# 第五章 管理全局变量(二)
# 在全局变量中查找值
“查找全局变量字符串”页使可以在下标或选定全局变量的值中查找给定的字符串。
要访问和使用此页,请执行以下操作:
1. 显示“全局变量”页。
2. 选择要使用的全局变量。为此,请参阅“全局页简介”一节中的步骤2和3。
3. 单击查找按钮。
4. 对于查找内容,输入要搜索的字符串。
5. (可选)清除大小写匹配。默认情况下,搜索区分大小写。
6. 单击Find First或Find All。
然后,页面显示选定全局变量中下标或值包含给定字符串的第一个节点或所有节点。该表左侧显示了节点下标,右侧显示了相应的值。
7. 如果使用的是Find First,请根据需要单击Find Next以查看下一个节点。
8. 完成后,单击关闭窗口。
## 执行批量更换
注意:在进行任何编辑之前,请确保知道IRIS使用哪个全局系统,以及应用程序使用哪个全局系统;参见“一般建议”此选项会永久更改数据。不建议在生产系统中使用。
出于开发目的,“查找全局字符串”页面还提供了对全局节点中的值进行整体更改的选项。要使用此选项:
1. 显示“全局”页面。
2. 选择要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3
3. 单击替换按钮。
4. 使用此页面查找上一节中描述的值。
5. 为“替换为”指定一个值。
6. 单击全部替换。
7. 单击确定确认此操作。然后,页面会显示变更的预览。
8. 如果结果可以接受,请单击保存。
9. 单击确定确认此操作。
# 导出全局变量
注意:因为导入全局是非常容易的(这是一个不可逆转的变化),所以最好只导出需要导入的全局。请注意,如果导出所有全局变量,导出将包括所有包含代码的全局变量。请确保知道IRIS使用哪些全局系统,以及应用程序使用哪些全局系统;
“导出全局”页面允许导出全局。
要访问和使用此页面:
1. 显示“全局”页面。
2. 指定要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3
3. 单击导出按钮。
4. 指定要将全局文件导出到的文件。为此,请在输入服务器上的导出路径和名称字段中输入文件名(包括其绝对或相对路径名),或者单击浏览并导航到该文件。
5. 使用字符集列表选择导出文件的字符集。
6. 在页面的中央框中:选择输出格式,选择记录格式
7. 选择或清除“在此检查”以在后台运行导出...
8. 单击导出。
9. 如果文件已经存在,请单击“确定”用新版本覆盖它。
导出会创建一个. gof文件。
# 导入全局变量
注意:在导入任何全局变量之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;参见“一般建议”没有撤消选项。将全局导入现有全局(从而合并数据)后,无法将全局恢复到其以前的状态。
“导入全局”页面允许导入全局。要访问和使用此页面:
1. 显示“全局”页面。
2. 单击导入按钮。
3. 指定导入文件。为此,请在输入导入文件的路径和名称字段中输入文件(包括其绝对或相对路径名),或者单击浏览并导航到该文件。
4. 使用字符集列表选择导入文件的字符集。
5. 选择下一步。
6. 使用表中的复选框选择要导入的全局。
7. 也可以选择在后台运行导入。如果选择此项,任务将在后台运行。
8. 单击导入。
# 删除全局变量
注意:在删除任何全局变量之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;参见“一般建议”没有撤消选项。无法恢复已删除的全局。
“删除全局”页面允许删除全局。要访问和使用此页面:
1. 显示“全局”页面。
2. 选择要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3
3. 单击删除按钮。
4. 单击确定确认此操作。
# 管理任务的应用程序接口
InterSystems IRIS还提供了以下应用编程接口来执行本章中描述的一些任务:
- 类`%SYSTEM.OBJ`提供了以下方法:
- `Export()`使能够将全局导出到一个XML文件。
- `Load()`和`LoadDir()`使能够导入包含在`XML`文件中的全局。
这两者都可以通过$SYSTEM变量获得,例如:`$SYSTEM.OBJ.Export`
- 类别`%Library.Global`提供了以下方法:
- `Export()`使能够将全局导出到`.gof`和其他文件格式(不包括XML)。
- `Import()`使能够将全局导入到`.gof`和其他文件格式(不包括XML)。
`%Library.Global` 还提供了`Get()`类查询,根据给定的搜索条件,可以使用该查询来查找全局。

文章
姚 鑫 · 五月 13, 2021
# 第五章 管理全局变量(二)
# 在全局变量中查找值
“查找全局变量字符串”页使可以在下标或选定全局变量的值中查找给定的字符串。
要访问和使用此页,请执行以下操作:
1. 显示“全局变量”页。
2. 选择要使用的全局变量。为此,请参阅“全局页简介”一节中的步骤2和3。
3. 单击查找按钮。
4. 对于查找内容,输入要搜索的字符串。
5. (可选)清除大小写匹配。默认情况下,搜索区分大小写。
6. 单击Find First或Find All。
然后,页面显示选定全局变量中下标或值包含给定字符串的第一个节点或所有节点。该表左侧显示了节点下标,右侧显示了相应的值。
7. 如果使用的是Find First,请根据需要单击Find Next以查看下一个节点。
8. 完成后,单击关闭窗口。
## 执行批量更换
注意:在进行任何编辑之前,请确保知道IRIS使用哪个全局系统,以及应用程序使用哪个全局系统;参见“一般建议”此选项会永久更改数据。不建议在生产系统中使用。
出于开发目的,“查找全局字符串”页面还提供了对全局节点中的值进行整体更改的选项。要使用此选项:
1. 显示“全局”页面。
2. 选择要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3
3. 单击替换按钮。
4. 使用此页面查找上一节中描述的值。
5. 为“替换为”指定一个值。
6. 单击全部替换。
7. 单击确定确认此操作。然后,页面会显示变更的预览。
8. 如果结果可以接受,请单击保存。
9. 单击确定确认此操作。
# 导出全局变量
注意:因为导入全局是非常容易的(这是一个不可逆转的变化),所以最好只导出需要导入的全局。请注意,如果导出所有全局变量,导出将包括所有包含代码的全局变量。请确保知道IRIS使用哪些全局系统,以及应用程序使用哪些全局系统;
“导出全局”页面允许导出全局。
要访问和使用此页面:
1. 显示“全局”页面。
2. 指定要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3
3. 单击导出按钮。
4. 指定要将全局文件导出到的文件。为此,请在输入服务器上的导出路径和名称字段中输入文件名(包括其绝对或相对路径名),或者单击浏览并导航到该文件。
5. 使用字符集列表选择导出文件的字符集。
6. 在页面的中央框中:选择输出格式,选择记录格式
7. 选择或清除“在此检查”以在后台运行导出...
8. 单击导出。
9. 如果文件已经存在,请单击“确定”用新版本覆盖它。
导出会创建一个. gof文件。
# 导入全局变量
注意:在导入任何全局变量之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;参见“一般建议”没有撤消选项。将全局导入现有全局(从而合并数据)后,无法将全局恢复到其以前的状态。
“导入全局”页面允许导入全局。要访问和使用此页面:
1. 显示“全局”页面。
2. 单击导入按钮。
3. 指定导入文件。为此,请在输入导入文件的路径和名称字段中输入文件(包括其绝对或相对路径名),或者单击浏览并导航到该文件。
4. 使用字符集列表选择导入文件的字符集。
5. 选择下一步。
6. 使用表中的复选框选择要导入的全局。
7. 也可以选择在后台运行导入。如果选择此项,任务将在后台运行。
8. 单击导入。
# 删除全局变量
注意:在删除任何全局变量之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;参见“一般建议”没有撤消选项。无法恢复已删除的全局。
“删除全局”页面允许删除全局。要访问和使用此页面:
1. 显示“全局”页面。
2. 选择要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3
3. 单击删除按钮。
4. 单击确定确认此操作。
# 管理任务的应用程序接口
InterSystems IRIS还提供了以下应用编程接口来执行本章中描述的一些任务:
- 类`%SYSTEM.OBJ`提供了以下方法:
- `Export()`使能够将全局导出到一个XML文件。
- `Load()`和`LoadDir()`使能够导入包含在`XML`文件中的全局。
这两者都可以通过$SYSTEM变量获得,例如:`$SYSTEM.OBJ.Export`
- 类别`%Library.Global`提供了以下方法:
- `Export()`使能够将全局导出到`.gof`和其他文件格式(不包括XML)。
- `Import()`使能够将全局导入到`.gof`和其他文件格式(不包括XML)。
`%Library.Global` 还提供了`Get()`类查询,根据给定的搜索条件,可以使用该查询来查找全局。
