搜索​​​​

清除过滤器
文章
姚 鑫 · 六月 29, 2021

第二十二章 计算XPath表达式

# 第二十二章 计算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

没有虚拟 IP 地址的数据库镜像

++ 更新: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 异步镜像成员,以获得高可用性和容灾解决方案,旨在为您的应用程序维持最高水平的运营弹性. 希望本文提供了一些关于成功部署具有故障转移的数据库镜像的可能的不同组合和用例的见解,这些组合和用例适合您的应用程序和可用性要求。
文章
Nicky Zhu · 一月 11, 2021

ObjectScript包管理器中的单元测试和测试覆盖率

本文将描述通过ObjectScript包管理器(见https://openexchange.intersystems.com/package/ObjectScript-Package-Manager-2)运行单元测试的过程,包括测试覆盖率测量(见https://openexchange.intersystems.com/package/Test-Coverage-Tool)。 ## ObjectScript中的单元测试 关于在ObjectScript中编写单元测试,已经有很好的文档,因此我就不再赘述了。您可以在这里找到单元测试教程:https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=TUNT_preface 最好的做法是将单元测试代码单独放在源代码树中,无论它只是“/tests”还是其他名字。在InterSystems中,我们最终使用/internal/testing/unit_tests/作为我们事实上的标准,这是有意义的,因为测试是内部/非发布的,而且除了单元测试还有其他类型的测试,但这对于简单的开源项目来说可能有点复杂。您可以在我们的一些GitHub仓库中看到这种结构。 从工作流的角度来看,这在VSCode中非常简单,您只需创建目录并将类放在里面。对于较老的以服务器为中心的源代码控制方法(Studio中使用的方法),您需要正确地地映射这个包,使用的方法会因源代码控制程序而异。 从单元测试类命名的角度来看,我个人的偏好(以及我的团队的最佳实践)是: UnitTest.[.] 例如,如果在类MyApplication.SomeClass 中对方法Foo进行单元测试,单元测试类将被命名为UnitTest.MyApplication.SomeClass.Foo;如果测试是针对整个类的,那么名字就是UnitTest.MyApplication.SomeClass。 ## ObjectScript 包管理器中的单元测试 让ObjectScript包管理器知道您的单元测试,很简单!只需按如下所示向module.xml中添加一行代码(来自https://github.com/timleavitt/ObjectScript-Math/blob/master/module.xml - 这是Open Exchange上的@Peter Steiwer的出色数学扩展包,我以它作为简单的正面例子): ```objectscript   ...   ``` 这些代码的意思是: - 单元测试位于模块根目录下的“tests”目录中。 - 单元测试在“UnitTest.Math”包中。这样很直观,因为被测试的类就在“Math”包中。 - 单元测试在包生命周期的“测试”阶段运行。(当然还有一个可以运行它们的“验证”阶段,这里不赘述。) ## 运行单元测试 对于上述定义的单元测试,包管理器提供了一些实用工具来运行它们。您仍然可以像平常使用%UnitTest.Manager那样设置^UnitTestRoot等,但下面的方法可能更简单,尤其在同一个环境下做几个项目的时候。 您可以克隆上述objectscript-math仓库,然后用 ```zpm "load /path/to/cloned/repo/" ```加载,或者在您自己的包上使用包名(和测试名)替换“objectscript-math”来尝试所有这些方法。 重新加载模块,然后运行所有单元测试: ```zpm "objectscript-math test" ``` 只运行单元测试(不重新加载): ```zpm "objectscript-math test -only" ``` 只运行单元测试(不重新加载)并提供详细输出: ```zpm "objectscript-math test -only -verbose" ``` 运行一个特定的测试套件(指一个测试目录 - 在本例中,是UnitTest/Math/Utils中的所有测试)而不重新加载,并提供详细输出: ```zpm "objectscript-math test -only -verbose -DUnitTest.Suite=UnitTest.Math.Utils" ``` 运行一个特定的测试用例(在本例中,是UnitTest.Math.Utils.TestValidateRange)而不重新加载,并提供详细输出: ```zpm "objectscript-math test -only -verbose -DUnitTest.Case=UnitTest.Math.Utils.TestValidateRange" ``` 如果您只是想解决单个测试方法中的小问题: ```zpm "objectscript-math test -only -verbose -DUnitTest.Case=UnitTest.Math.Utils.TestValidateRange -DUnitTest.Method=TestpValueNull" ``` ## 通过ObjectScript包管理器进行测试覆盖率测量 怎样评估单元测试的质量?测量测试覆盖率虽然并不全面,但至少有参考意义。早在2018年的全球峰会上,我就展示过。 见 - https://youtu.be/nUSeGHwN5pc。 首先需要安装“测试覆盖率”包: ```zpm "install testcoverage" ``` 注意,并不需要ObjectScript包管理器才能安装/运行;可以在Open Exchange上了解更多信息:https://openexchange.intersystems.com/package/Test-Coverage-Tool 不过如果您已经在用ObjectScript包管理器,那么您可以更好地利用这个“测试覆盖率”工具。 运行测试前,需要指定测试所覆盖的类/routine宏。这一点很重要,因为在非常大的代码库中(例如,HealthShare),测试和收集项目中所有文件的测试覆盖率所需要的内存可能超出您的系统内存。(提一句,如果您感兴趣,可以使用逐行监视器的gmheap。) 文件列表在您的单元测试根目录下的coverage.list文件中;单元测试的不同子目录(套件)可以拥有它们自己的副本,以覆盖在测试套件运行时将跟踪的类/例程。 有关objectscript-math的简单示例,见:https://github.com/timleavitt/ObjectScript-Math/blob/master/tests/UnitTest/coverage.list;测试覆盖率工具用户指南有更详细的介绍。 要在启用测试覆盖率测量的情况下运行单元测试,只需再向命令添加一个参数,指定应使用TestCoverage.Manager而非%UnitTest.Manager 来运行测试: ```zpm "objectscript-math test -only -DUnitTest.ManagerClass=TestCoverage.Manager" ``` 输出(即使是非详细模式)将包括一个URL,供您查看您的类/routine(宏)的哪些行被单元测试覆盖了,以及一些汇总统计信息。 ## 接下来的步骤 这些能不能在CI中自动化?能不能报告单元测试的结果和覆盖率分数/差异?答案是:能!您可以使用Docker,Travis CI和codecov.io来试一下这个简单示例,见https://github.com/timleavitt/ObjectScript-Math;我打算以后写篇文章来详细讲讲,介绍几种不同的方法。
文章
姚 鑫 · 五月 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()`类查询,根据给定的搜索条件,可以使用该查询来查找全局。 ![image](/sites/default/files/inline/images/1_44.png)
文章
姚 鑫 · 五月 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()`类查询,根据给定的搜索条件,可以使用该查询来查找全局。 ![image](/sites/default/files/inline/images/1_44.png)
文章
Michael Lei · 四月 17, 2022

翻译博客文章--浏览医疗保健的未来

在最近一次探索马里兰小镇的 "度假 "期间,我偶然发现了一家非常令人愉快的书店,在那里我愉快地消磨了一下午。我和我的家人都是读者,喜欢各种类型的书--新的、二手的、印刷的、电子的。我们尽量在当地购物,以帮助零售店保持运营。 这次访问促使我思考图书行业所发生的事情与我们的医疗保健系统所发生的事情之间的一些相似之处。 医疗保健行业与图书行业的趋势 数字化 我们阅读内容的格式已经发生了根本性的变化。在2020年,电子书几乎占美国市场的四分之一。音频书占美国图书收入的10亿美元。许多印刷书籍是按需出版的,而不是保存在库存中。同样,医疗保健早已不再是一个“伸出舌头说啊 ”的行业,基因组测试、由人工智能算法读取的X射线、可植入设备和远程医疗访问已经改变了医疗的面貌。 虚拟服务 书店现在有多种形式,医疗机构也是如此。订阅图书服务,从当地独立的小公司、大的连锁店、电子零售的网上订单。而与你的本地门诊竞争的是你手机上的一个应用程序。同样,你的治疗师可能是一个机器人,你的基层医疗服务可能由你社区附近药店的驻店医师提供,你可能在一个办公园区做手术。在所有这些竞争中,我们如何确保在我们需要时仍有健康的、提供全面服务的医院? 更智能的算法 分析和预测模型现在几乎和个人推荐一样重要。过去,当我想要一本书的建议,或者一个医生,我就会问朋友。虽然我仍然这样做,但我也同样有可能去看Goodreads,或查看在线医生评论。当我进行搜索时,亚马逊、苹果或谷歌也同样可能提供他们的建议,不管我是否要求它们。他们知道我是谁,我的购买模式是什么,我检查过哪些疾病和症状,以及在当地急诊科订购书籍或看医生的等待时间是什么。 合并和收购 无论你是卖书还是卖医疗服务,改变或死亡都是关键词。我们附近的一家大的巴诺书店(Barnes & Nobles,美国最大的实体书店)最近搬到了一个不到以前一半的地方。大多数独立书店出售的礼品和书籍一样多,而且许多书店同时出售新货和二手货。像Alibris这样的网站将当地的小企业与世界各地的买家联系起来。同样,根据普华永道的数据,2021年医疗保健业的合并和收购增长了56%,预计这一趋势在2022年还会继续。IQVIA艾昆纬研究所的一份报告发现,在主要的应用程序商店中,有大约35万个数字健康应用程序。而美国最大的零售商都在医疗保健领域进行了大量投资。例如,亚马逊是颠覆性的电子书业务的主要参与者,现在也有实体书店,它正积极地进入医疗保健服务领域,包括线上和线下服务。 对未来医疗的影响 对未来医疗的影响是什么?有趣的是,这两种业务都唤起了人们对也许是神话般的过去的相当大的怀念。虽然现在的实体书店比以前少了,但书籍实际上比以前更容易获得。对很大一部分人来说,亲切的家庭医生上门服务从来就不是那么容易的,而且不同地区采用的差异巨大的护理标准也不一定能带来最好的结果。 新世界秩序的便利性和选择令人难以置信地吸引人,无论我是在手机上购买一本书,还是在街上走到我附近的CVS公司购买Covid 疫苗加强针。但是,一方面浏览完所有这些选择也会让人感到困惑,另一方面我不想失去浏览当地商店的货架或者我年迈的母亲随时获得住院床位的选择。 没有任何整齐划一的策略可以向前推进。因此,作为一个消费者,我将继续光顾本地小店来帮助他们经营下去。作为一名医疗IT专业人士,我将继续关注如何利用信息来指导未来,带着一点点害怕,但更多的还是兴奋来展望未来。 关于作者: Kathleen Aller负责InterSystems公司的医疗市场战略。她在医疗和技术领域有多年的经验,在分析、患者管理、电子健康记录、医疗信息共享以及质量和绩效评估方面有专长。 博客原文:https://www.intersystems.com/pulse-blog/browsing-the-future-of-healthcare
文章
姚 鑫 · 七月 27, 2021

类关键字PropertyClass,ServerOnly,Sharded,SoapBindingStyle

# 第二十九章 类关键字 - PropertyClass 向该类添加属性参数。 # 用法 要向该类添加属性参数,请使用以下语法: ```java Class PropClass.MyClass Extends %RegisteredObject [ PropertyClass = PropClass.MyPropertyClass ] { //class members } ``` 其中·propertyclasslist·是下列之一: ```java [ PropertyClass = PropClass.MyPropertyClass ] ``` - 用逗号分隔的类名列表,用括号括起来。 # 详情 如果需要添加自定义属性参数,请执行以下操作: 1. 定义并编译一个定义一个或多个类参数的类。例如: ```java Class PropClass.MyPropertyClass { Parameter MYPARM As %String = "XYZ"; } ``` 这些类参数在下一步中成为属性参数。 2. 在定义属性的类中,指定`PropertyClass`关键字。 # 对子类的影响 子类继承这个关键字添加的自定义行为。如果子类为关键字指定了一个值,则该值会指定一个或多个为该类的属性指定参数的附加类。 # 第三十章 类关键字 - ServerOnly 指定此类是否被投影到Java客户端。 # 用法 要覆盖将类投影到`Java`客户端的默认方式,请使用以下语法: ```java Class Sample.NewClass1 [ ServerOnly = serveronlyvalue ] { //class members } ``` 其中`serveronlyvalue`是以下值之一: - 0表示可以投影此类。 - 1表示这个类不会被投影。 # 详解 如果该关键字为`1`,则该类不会被投影到`Java`客户端。如果该关键字为`0`,则将投影该类。 # 对子类的影响 此关键字不是继承的。 # 默认 如果省略这个关键字,这个类如果不是存根就会被投影(但是如果是存根就不会被投影)。 # 第三十一章 类关键字 - Sharded 指定此类是否被分片。仅适用于包含分片集群的环境中的持久类。 # 用法 要将类定义为分片类,请使用以下语法: ```java Class MyApp.MyClass Extends %Persistent [ Sharded = 1 ] { //class members } ``` 否则,省略此关键字。 # 详解 分片是一种水平扩展数据存储的机制。如果一个类被分片,该类的实例将分布在分片集群中任何已定义的数据节点上。 如果有一个分片环境,并且将一个类定义为未分片,那么该类的实例只存储在第一个数据节点上,尽管所有节点都可以看到该数据。 # 对子类的影响 这个关键字是继承的。 # 默认 如果省略这个关键字,类就不会被分割。 # 第三十二章 类关键字 - SoapBindingStyle 指定此类中定义的任何web方法使用的绑定样式或SOAP调用机制。仅适用于定义为web服务或web客户端的类。 # 用法 要指定此类中定义`web method`使用的绑定样式,请使用以下语法: ```java Class MyApp.MyClass [ SoapBindingStyle = soapbindingstyle ] { //class members } ``` 其中`soapbindingstyle`是下列之一: - `document` 文档(默认)—默认情况下,此类中的`web method`使用文档样式的绑定。 使用这种绑定风格,`SOAP`消息被格式化为文档,并且通常只有一个部分。 在`SOAP`消息中,``元素通常包含一个子元素。``元素的每个子元素对应于一个消息部分。 - `rpc` —默认情况下,此类中的`web method`使用`rpc`(远程过程调用)样式的绑定。 使用这种绑定风格,`SOAP`消息被格式化为具有多个部分的消息。 在`SOAP`消息中,``元素包含一个子元素,其名称取自相应的操作名称。这个元素是一个生成的包装元素,它为方法的参数列表中的每个参数包含一个子元素。 如果`SoapBindingStyle`是文档,如果`ARGUMENTSTYLE`是消息,那么消息样式与`RPC`非常相似; 重要提示:对于手动创建的`web service`,该关键字的默认值通常是合适的。当使用`SOAP`向导从`WSDL`生成`web客户端或服务`时,InterSystems IRIS会将此关键字设置为适合该`WSDL`;如果修改该值,`web客户端或服务`可能不再工作。 # 详解 此关键字允许指定此类中定义的任何`web method`使用的默认绑定样式。它影响`SOAP`主体的格式(但不影响任何SOAP头)。 通过使用`SoapBindingStyle`方法关键字或`SoapBindingStyle`查询关键字,可以重写单个方法的绑定样式。 # 对子类的影响 此关键字不是继承的。 # 默认 默认值为文档。 # 与WSDL的关系 `SoapBindingStyle`类关键字指定了`WSDL`的``部分中``元素的样式属性的值。例如,如果`SoapBindingStyle`是文档,则`WSDL`可能如下所示: ```xml ... ... ``` 如这里所示,在`WSDL`的``部分中,`SoapBindingStyle`类关键字还指定了``元素的样式属性的默认值;该属性由`SoapBindingStyle`方法关键字进一步控制。 相比之下,如果`SoapBindingStyle`是`rpc`,则`WSDL`可以改为如下所示: ```xml ... ... ``` 绑定样式也会影响``元素,如下所示: - 如果绑定样式是文档,默认情况下,消息只有一个部分。例如: ```xml ``` 如果`ARGUMENTSTYLE`参数是`message`,那么一条消息可以有多个部分。例如: ```xml ``` - 如果绑定样式是`rpc`,消息可以有多个部分。例如: ```xml ``` # 对SOAP消息的影响 对`SOAP`消息的主要影响是控制`SOAP`主体是否可以包含多个子元素。 对于使用`RPC`样式绑定和编码样式消息的`web method`,下面显示了请求消息正文的示例: ```xml 10 5 17 2 ``` 相比之下,下面显示了使用文字绑定和编码样式消息的`web method`的请求消息正文的示例: ```xml 10 5 17 2 ``` 在这种情况下,`SOAP`主体只有一个子元素。 # 与 `%XML.DataSet` 一起使用 对于 `%XML.DataSet`, 类型的对象,并非所有 `SoapBindingStyle` 和 `SoapBodyUse` 关键字的排列都是允许的,,如下表总结: type | supported?| supported? ---|---|--- 空 |SoapBodyUse=literal(默认) | SoapBodyUse=encoded SoapBindingStyle=document(default) |supported| not supported SoapBindingStyle=rpc| supported |supported
文章
姚 鑫 · 八月 19, 2021

查询关键字Private,SoapBindingStyle,SoapBodyUse,SoapNameSpace

# 第121章 查询关键字 - Private 指定查询是否为私有查询。 # 用法 要指定此查询为私有查询,请使用以下语法: ```java Query name(formal_spec) As classname [ Private ] { //implementation } ``` 否则,请省略此关键字或将该词放在该关键字之前。 # 详解 私有类成员只能由同一类(或其子类)的其他成员使用。请注意,其他语言通常使用单词`Protected`来描述这种可见性,使用单词`Private`来表示从子类不可见。 # 默认 如果省略此关键字,则此查询不是私有的。 # 第122章 查询关键字 - SoapBindingStyle 指定此查询用作`Web方法`时使用的绑定样式或`SOAP`调用机制。仅适用于定义为`Web服务`或`Web客户端`的类。 # 用法 要覆盖查询使用的默认绑定样式(当它用作`Web方法`时),请使用以下语法: ```java Query name(formal_spec) As classname [ WebMethod, SoapBindingStyle = soapbindingstyle ] { //implementation } ``` 其中`soapbindingstyle`为下列值之一: - `document` - 此`Web方法`使用文档式调用。 使用这种绑定样式,`SOAP`消息被格式化为文档,并且通常只有一个部分。 在`SOAP`消息中,``元素通常包含单个子元素。``元素的每个子元素对应一个消息部分。 - `rpc` - 此`Web方法`使用`RP`C(远程过程调用)风格的调用。 使用这种绑定样式,`SOAP`消息被格式化为具有多个部分的消息。 在`SOAP`消息中,``元素包含一个子元素,其名称取自相应的操作名称。此元素是生成的包装元素,它为方法的参数列表中的每个参数包含一个子元素。 重要提示:对于手动创建的`Web服务`,此关键字的默认值通常比较合适。当使用`SOAP`向导从`WSDL`生成`Web客户端`或`服务`时,InterSystems IRIS会将此关键字设置为适用于该`WSDL`;如果修改此值,`Web客户端`或服务可能不再工作。 # 详情 此关键字允许指定此查询在作为`Web方法`调用时使用的绑定样式。 对于给定查询,此关键字覆盖`SoapBindingStyle`类关键字。 # 默认 如果忽略此关键字,``元素的`style`属性将由`SoapBindingStyle`类关键字的值决定。 # WSDL的关系 (请注意,与方法关键字和查询关键字相比,同名的`class关键字`对`WSDL`的影响更大。) # 对SOAP消息的影响 有关信息,请参阅`SoapBindingStyle`类关键字的条目。 # 第123章 查询关键字 - SoapBodyUse 指定该查询用作`web方法`时,输入和输出使用的编码。 仅应用于定义为`web服务`或`web客户端`的类。 # 用法 要覆盖查询的输入和输出使用的默认编码(当它被用作`web方法`时),请使用以下语法: ```java Query name(formal_spec) As classname [ WebMethod, SoapBodyUse = encoded ] { //implementation } ``` 其中,`soapbodyuse`是下列值之一: - `literal` - 这个`web方法`使用文字数据。 也就是说,`SOAP`消息的``中的`XML`与`WSDL`中给出的模式完全匹配。 - `encoded` = 这个web方法使用soap编码的数据。 也就是说,`SOAP`消息的``中的`XML`根据所使用的`SOAP`版本使用适当的SOAP编码,满足以下规范的要求: - `SOAP 1.1` (https://www.w3.org/TR/2000/NOTE-SOAP-20000508/) - `SOAP 1.2` (https://www.w3.org/TR/soap12-part2/) 重要提示:对于手工创建的`web服务`,这个关键字的默认值通常是合适的。 当使用`SOAP`向导从`WSDL`生成`web客户端`或服务时, IRIS将此关键字设置为适合该`WSDL`的; 如果修改了该值,`web客户端`或服务可能不再工作。 # 详解 该关键字允许您指定该查询作为`web方法`调用时的输入和输出的编码。 对于给定的查询,此关键字覆盖`SoapBodyUse`类关键字。 # 默认 如果忽略此关键字,则使用`SoapBodyUse`类关键字的值。 # 与WSDL的关系以及对SOAP消息的影响 有关信息,请参阅`SoapBodyUse`类关键字的条目。 # 第124章 查询关键字 - SoapNameSpace 在`WSDL`中的绑定操作级别指定名称空间。 仅应用于定义为`web服务`或`web客户端`的类。 # 用法 要在绑定操作级别覆盖默认命名空间(当查询被用作`web方法`时),请使用以下语法: ```java Query name(formal_spec) As classname [ SoapNameSpace = "soapnamespace", WebMethod ] { //implementation } ``` 其中,`soapnamespace`是一个名称空间`URI`。 注意,如果`URI`包含冒号(`:`),则字符串必须用引号括起来。 也就是说,你可以使用以下方法: ```java Query MyQuery() [ SoapNameSpace = "http://www.mynamespace.org", WebMethod ] ``` 或以下: ```java Query MyQuery() [ SoapNameSpace = othervalue, WebMethod ] ``` 但以下情况并非如此: ```java Query MyQuery() [ SoapNameSpace = http://www.mynamespace.org, WebMethod ] ``` 重要提示:对于手工创建的`web服务`,这个关键字的默认值通常是合适的。 当使用`SOAP`向导从`WSDL`生成`web客户`端或服务时, IRIS将此关键字设置为适合该WSDL的; 如果修改了该值,`web客户端`或服务可能不再工作。 # 详解 该关键字允许指定查询作为`web方法`调用时使用的`XML`名称空间。 注意:此关键字仅在查询使用`rpc`样式绑定时有效。 也就是说,查询(或包含它的类)必须用等于`rpc`的`SoapBindingStyle`标记。 (如果为使用文档样式绑定的查询指定此关键字,则`WSDL`将不是自一致的。) # 默认 如果忽略此关键字,则`web方法`位于由`web服务`或客户端类的`namespace`参数指定的命名空间中。 # 与WSDL的关系以及对SOAP消息的影响 有关信息,请参阅`SoapNameSpace`方法关键字的条目。
文章
Frank Ma · 三月 2, 2022

如何成为时间领主 - 诞生

好人不需要规则。 神秘博士 要成为日期和时间的主人并不是一件容易的事,在任何编程语言中,这总是一个问题,有时会让人感到困惑,我们将澄清并提出一些提示,使这项任务尽可能简单。 坐上TARDIS,我将把你变成一个时间领主。 让我们从基本知识开始 如果你通常使用其他语言,请记住,Intersystems Object Script(以下简称IOS,不要与苹果手机混淆)的日期有点特殊。当我们在终端运行$HOROLOG 命令时,为了得到当前的日期和时间,你会看到它被分为两部分: WRITE $HOROLOG > 66149,67164 第一个值是天数,确切地说,是自1840年12月31日以来的天数,也就是说,值1是1841年1月1日;第二个值是自今天00:00以来的秒钟。 在这个例子中,66149对应于09/02/2022(欧洲格式的日/月/年的2月9日),67164对应于18:39:24。我们将这种格式称为数据和时间的内部格式。 感到困惑吗?好吧,我们将开始揭示宇宙的伟大秘密(日期和时间)。 如何将内部格式转换为更清晰的格式? 为此,我们将用到命令 $ZDATETIME 基本的命令是 SET RightNow = $HOROLOG WRITE RightNow > 66149,67164 WRITE $ZDATETIME(RightNow) > 02/09/2022 18:39:24 默认情况下,它使用美国格式月/日/年(mm/dd/yyyy)。如果你想使用其他格式的日期,我们将使用第二个参数,比如欧洲格式日/月/年(dd/mm/yyyy),在这种情况下,我们将给它一个值4(关于更多的格式,见文档$ZDATETIME.dformat)。 SET RightNow = $HOROLOG WRITE RightNow > 66149,67164 WRITE $ZDATETIME(RightNow,4) > 09/02/2022 18:39:24 该选项使用我们在本地变量中定义的分隔符和年份格式。 如果我们还想放另一种时间格式,例如12小时格式(AM/PM)而不是24小时格式,我们使用第三个参数,其值为3,如果我们不想显示秒,我们将使用值4(见文件$ZDATETIME.tformat)。 SET RightNow = $HOROLOG WRITE RightNow > 66149,67164 WRITE $ZDATETIME(RightNow,4,3) > 09/02/2022 06:39:24PM WRITE $ZDATETIME(RightNow,4,4) > 09/02/2022 06:39PM 现在是不是更清楚了?那么让我们更深入地了解一下。 ODBC 格式 这个格式与你的本地配置无关,它将始终显示为年/月/日格式 yyyy-mm-dd,其值为3。 如果我们想创建要导出文件的数据,如CSV、HL7文件等,建议使用它。 SET RightNow = $HOROLOG WRITE RightNow > 66149,67164 WRITE $ZDATETIME(RightNow,3) > 2022-02-09 18:39:24 一周的日子,星期名称,一年中的某天 Value 值 描述 10 一周的日子将是一个介于0和6之间的值,0代表星期天,6代表星期六。 11 星期的缩写名称,它将根据你定义的本地配置返回,IRIS的默认安装是 enuw (English, United States, Unicode) 12 长格式的星期名称,与11相同。 14 一年中的某一天,自1月1日以来的天数。 如果我们只是想分别处理日期和时间,应该分别使用$ZDATE和$ZTIME命令。格式的参数与 $ZDATETIME.dformat 和 $ZDATETIME.tformat中定义的参数相同。 SET RightNow = $HOROLOG WRITE RightNow > 66149,67164 WRITE $ZDATE(RightNow,10) > 3 WRITE $ZDATE(RightNow,11) > Wed WRITE $ZDATE(RightNow,12) > Wednesday 那我如何将日期转换为内部格式? 好了,现在我们来看看相反的步骤,即有一个带有日期的文本,并将其转换成IOS格式。对于这个任务,我们将使用命令 $ZDATETIMEH。 这一次,我们必须指出日期和时间的格式(如果我们使用$ZDATETIMEH),或者分别指出日期($ZDATEH)和时间($ZTIMEH)。 格式是相同的,也就是说,如果我们有一个ODBC格式(yyyy-mm-dd)的日期字符串,那么我们将使用值3。 SET MyDatetime = "2022-02-09 18:39:24" SET Interna1 = $ZDATETIMEH(MyDatetime, 3, 1) // ODBC Format SET MyDatetime = "09/02/2022 18:39:24" SET Interna2 = $ZDATETIMEH(MyDatetime, 4, 1) // European format SET MyDatetime = "02/09/2022 06:39:24PM" SET Interna3 = $ZDATETIMEH(MyDatetime, 1, 3) // American format with time in 12h AM/PM WRITE Interna1,!,Interna2,!,Interna3 > 66149,67164 66149,67164 66149,67164 从逻辑上讲,如果我们说字符串使用的是一种特殊的格式,而我们给它提供了错误的参数,那么任何事情都可能发生,比如它理解为2月9日,而不是9月2日。 不要混合格式,这样以后会出现问题。 SET MyDatetime = "09/02/2022" /// American format SET InternalDate = $ZDATEH(MyDatetime, 1) /// European format SET OtherDate = $ZDATETIME(InternalDate, 4) WRITE InternalDate,!,OtherDate > 66354 02/09/2022 不用说,如果我们试图设定一个欧洲的日期并试图将其转化为美国的日期...... 在情人节会发生什么? SET MyDatetime = "14/02/2022" SET InternalDate = $ZDATEH(MyDatetime, 1) // American format. month 14 doesn't exists!!! ^ <ILLEGAL VALUE> 嗯,就像所有的情人节一样......破碎的心,嗯......在这种情况下,破碎的代码。 好吧,让我们用你已经学到的东西做一些事情。 READ !,"Please indicate your date of birth (dd/mm/yyyy): ",dateOfBirth SET internalFormat = $ZDATEH(dateOfBirth, 4) SET dayOfWeek= $ZDATE(internalFormat, 10) SET nameOfDay = $ZDATE(internalFormat, 12) WRITE !,"The day of the week of your birth is: ",nameOfDay IF dayOfWeek = 5 WRITE "you always liked to party!!!" // was born on friday 以后我们将看到其他的做事方法,以及如何处理错误。 下一章:如何进行时间旅行 好奇 如果你想知道为什么01/01/1841的值被当作1的值,那是因为选择这个日期是因为它是在世的最年长的美国公民出生前的非闰年,当MUMPS编程语言被设计时,他是一个121岁的内战老兵,它从这个语言中扩展了对象脚本。
文章
姚 鑫 · 六月 14, 2023

第五十七章 镜像中断程序 - 在手动故障转移之前确定备份是否处于活动状态

# 第五十七章 镜像中断程序 - 在手动故障转移之前确定备份是否处于活动状态 ## 在手动故障转移之前确定备份是否处于活动状态 假设有两个名为 `IRIS A` 和`IRIS B` 的故障转移成员。如果 `^MIRROR` 例程确认备份 (`IRIS B`) 在与主 (`IRIS A`) 丢失联系时处于活动状态,因此具有最新的来自 `IRIS A` 的日志数据,可以使用单个过程手动进行故障转移。当连接因主要故障而丢失时,不会造成数据丢失的风险。但是,当发生多个故障时,活动备份可能没有来自主服务器的所有最新日志数据,因为主服务器在连接丢失后继续运行了一段时间。 使用以下过程确定备份是否处于活动状态: 1. 确认 `IRIS` 实例 `IRIS A` 上的 `ISCAgent` 实际上已关闭(并确保它们在整个手动故障转移过程中保持关闭状态)。 2. 在 `IRIS B` 上,在终端的 `%SYS` 命名空间中运行 `^MIRROR` 例程(请参阅使用 `^MIRROR` 例程)。 3. 在主菜单中选择镜像管理,显示如下子菜单: ```java 1) Add mirrored database(s) 2) Remove mirrored database(s) 3) Activate or Catchup mirrored database(s) 4) Change No Failover State 5) Try to make this the primary 6) Connect to Mirror 7) Stop mirroring on this member 8) Modify Database Size Field(s) 9) Force this node to become the primary 10) Promote Async DR member to Failover member 11) Demote Backup member to Async DR member 12) Mark an inactive database as caught up 13) Manage mirror dejournaling on async member (disabled) 14) Pause dejournaling for database(s) ``` 4. 选择 `Force this node to become the primary` 选项。如果在联系丢失时备份处于活动状态,则会显示如下消息: ```java This instance was an active backup member the last time it was connected so if the primary has not done any work since that time, this instance can take over without having to rebuild the mirror when the primary reconnects. If the primary has done any work beyond this point (file #98), C:\InterSystems\MyIRIS\mgr\journal\MIRROR-GFS-20180815.009 then the consequence of forcing this instance to become the primary is that some operations may be lost and the other mirror member may need to be rebuilt from a backup of this node before it can join as a backup node again. Do you want to continue? ``` 如果有权访问主要文件的日志文件,则可以在继续之前确认引用的文件是最新的。 如果在与主服务器失去联系时备份未处于活动状态,则会显示如下消息: ```java Warning, this action can result in forcing this node to become the primary when it does not have all of the journal data which has been generated in the mirror. The consequence of this is that some operations may be lost and the other mirror member may need to be rebuilt from a backup of this node before it can join as a backup node again. Do you want to continue? ``` ## 手动故障转移到活动备份 如果 `^MIRROR` 例程的 `Force this node to become the primary` 选项确认备份在失去与主节点的连接时处于活动状态,请完成手动故障转移过程,如下所示: 1. 在要继续吗?提示继续该过程。 `Force this node to become the primary` 选项等待 `60` 秒以使镜像成员成为主要节点。如果操作未在 `60` 秒内成功完成,`^MIRROR` 报告操作可能未成功并指示您检查消息日志以确定操作是失败还是仍在进行中。 2. 一旦 `^MIRROR` 例程确认备份已成为主要备份,请在可以这样做时重新启动 `IRIS A`。当 `IRIS` 实例重新启动时, `IRIS A` 作为备份加入镜像。 ## 备份不活动时手动故障转移 即使 `^MIRROR` 例程未确认备份 ( `IRIS B`) 在与主 ( `IRIS A`) 失去连接时处于活动状态,仍然可以使用以下过程继续手动故障转移过程,但是如果这样做,会有数据丢失的风险。如本程序所述,可以在手动故障转移之前将最新的镜像日志文件从 `IRIS A`(如果有权访问)复制到 `IRIS` B,从而最大限度地降低这种风险。 1. 如果有权访问主服务器的镜像日志文件,请将最新的文件复制到 `IRIS B`,从 `IRIS B` 上的最新日志文件开始,然后包括来自 `IRIS A` 的任何后续文件。例如,如果 `MIRROR-MIRRORA-20180220.001`是 `IRIS B` 上的最新文件,复制 `MIRROR-MIRRORA-20180220.001` 和 `IRIS A` 上的任何更新文件。检查文件的权限和所有权,并在必要时更改它们以匹配现有日志文件。 2. 如果接受数据丢失的风险,请在提示时输入 `y` 以确认要继续;备份成为主要的。 `Force this node to become the primary` 选项等待 `60` 秒以使镜像成员成为主要节点。如果操作未在 `60` 秒内成功完成,`^MIRROR` 报告操作可能未成功并指示您检查消息日志以确定操作是失败还是仍在进行中。 3. 一旦 `^MIRROR` 例程确认备份已成为主要备份,请在可以这样做时重新启动 `IRIS A`。 - 如果 `IRIS A` 在 `IRIS` 实例重新启动时加入镜像作为备份,则不需要进一步的步骤。任何在故障成员上但不在当前主成员上的日志数据都已被丢弃。 - 如果在 `IRIS` 实例重新启动时 `IRIS A` 无法加入镜像,如重建镜像成员中描述的引用不一致数据的消息日志消息所示 `IRIS A` 上的最新数据库更改晚于最新的日志数据当 `IRIS B` 被迫成为主服务器时,它会出现在 `IRIS B` 上。要解决此问题,请按照该部分中的描述重建 `IRIS A`。
文章
Michael Lei · 三月 2, 2023

基于IRIS的Python实践与示例

在这里,您将找到一个在 IRIS 环境中使用 Python 的简单程序,以及另一个在 Python 环境中使用 ObjectScript 的简单程序。另外,我想分享一些我在学习实践时遇到的麻烦。 IRIS 环境中的 Python 比方说,您在 IRIS 环境中想要解决一个您认为使用 Python 更容易或更有效的问题。 您可以简单地更改环境:像创建任何其他方法一样创建您的方法,并在其名称和规范的末尾添加 [Language = python]: 您可以在该方法中使用任何类型的参数,并且要访问它们,您可以执行与在 COS 中完全相同的操作: 假设您有这个 %String 参数 Arg 和一个来自自定义类的参数 OtherArg。这个其他类可能具有标题和作者等属性。您希望像这样访问: 此方法提供如下输出: 而且,对于访问类方法,它几乎是一样的。假设我们在 Demo.Books.PD.Books 中有一个名为“CreateString”的方法,它将标题和作者连接成类似于“Title: <Title>; Author: <Author>”的内容。 将其添加到我们的 python 方法的末尾: 将提供以下输出: (要访问该方法,您可以使用 OtherArg.CreateString(),但我选择将 OtherArg 中的相同值传递给 CreateString 方法,以便输出看起来相似并且代码看起来更简单) Python 环境中的 ObjectScript 此外,在 Python 环境中,您也可能希望能用上ObjectScript 的代码或资源。 首先,您需要勾选此列表中的一些项目,以便能够以多种方式从 Python 环境访问您的 COS 文件(我不一定会在这里使用所有这些): 你有先决条件吗? 在这里查看 你可以使用 python 外部服务器吗? 在这里查看 你有你需要的驱动吗?在此处下载它们或在此处了解更多信息 如果您觉得有帮助,可以随时返回这些链接或检查我在首次使用 COS 创建 python 文件时遇到的错误。 那么让我们开始编程吧! 首先,我们必须将一些东西从 COS 调整到 Python。幸运的是,InterSystems 已经做到了,我们只需输入“import iris”即可访问所有内容! 在下一步中,我们创建到所需命名空间的连接,使用包含主机、端口和命名空间的路径 (host:port/namespace),并提供用户和密码: 请注意我们最后是如何创建一个 IRIS 对象的,因此我们可以使用此连接来访问该命名空间中我们想要的所有内容。 最后,就可以编写你想要写的内容: 您可以通过提供类名、方法名和参数来使用 irispy.classMethodValue() 访问方法,使用 .set() (用于属性)和许多其他可能性操作对象,同时按照您喜欢的方式处理 python 上的所有内容。 更多iris提供的功能和使用方法,查看Native SDK for Python简介 在这个例子中,我在第 16 行实例化了一个 Persistent 类,在接下来的几行中将它的属性 Title 和 Author 设置为指环王和 Tolkien。 在第 20 行,我从另一个类中调用了一个方法,该方法将对象保存到一个表中,如果有效则返回一个状态。最后,我在第 23 行打印状态。 混合环境 在 ObjectScript 环境中,您可能希望使用 Python 的已知库或您自己的带有函数和例程的自定义文件。 您可以将“导入”命令与 Numpy、SciPy 和任何您想要的东西一起使用(前提是您已正确安装它们: 在此处检查如何执行此操作) 而且,如果您想访问您的本地文件,有几种方法可以做到这一点,并且很容易找到相关教程,因为 Python 非常流行。 对我来说,最容易使用的是以下内容: 在这里,我从位于 C:/python 的文件 testesql.py 中导入了所有内容,并打印了 select() 函数的结果 彩蛋——我遇到的麻烦 SHELLS :使用 Shell 时,请记住 Windows PowerShell 是作为基于 UNIX 的系统工作的(因为它基于 .NET),而命令提示符将与官方文档中的 Windows 示例一起使用。对于一些更有经验的程序员来说,这听起来很基础,但如果您没有给予足够的重视,您可能会在这上面浪费很多时间,所以我发现写一些关于它的内容很重要。 用户和权限:在 Python 环境中使用 ObjectScript 进行编码的当前用户需要拥有命名空间资源的权限。请记住,如果您不选择任何用户,则当前为 UnknownUser,如果您不选择任何名称空间,则当前为 USER。因此,在最简单的访问中,您可能需要遵循:管理门户 > 系统管理 > 安全 > 用户 > 未知用户 > 角色并选择 %DB_USER 并保存。 我不知道发生了什么:要检查有关您遇到的错误的更多信息,您可能需要关注管理门户 > 系统资源管理器 > SQL 并键入“SELECT * FROM %SYS.Audit ORDER BY UTCTimeStamp Desc”以获取最近的审计。在那里你会找到错误的原因,比如 IRIS_ACCESSDENIED() 以及更多你甚至可能在 IRIS 环境之外得到的错误。 PYTHON COMPILING ERROR:您将希望避免方法名称,例如 try() 或已在 Python 中构建的函数。编译器不会理解从方法到函数的区别。 感谢您的阅读,请随时分享建议、评论、疑问或您正在开发的任何内容!
文章
Michael Lei · 六月 18, 2023

医疗行业的未来--数据与人的融合

在数字化时代,数据的重要性无可置疑。数据作为新型生产要素,不仅在宏观政策层面得到党和政府的大力推动,也是医院高质量发展的关键和改变医疗行业的驱动力。随着医疗信息化的迅猛发展,我们正迈向一个数据随处可及、人人可用易用的医疗信息化时代。这一时代将数据与人的需求相结合,致力于让数据能“主动”找到需要他们的医护人员和患者,每一个行业从业者,都应致力于为医护人员和患者提供简单易用的软件解决方案,减少工作量,提高效率,推动医疗行业的进步。 数据与人的融合是实现医疗行业数字化转型的核心。当然,医疗数据的收集、存储和管理对于提供高质量的医疗服务至关重要。然而,仅仅有大量的数据并不足够,我们需要将数据与人的需求紧密结合起来。这意味着我们应该让更多的数据关联起来,并且能服务于更多的人群,让患者能够随时随地访问他们的电子病历,让医生和科研人员也能及时有效地获取病人在医院围墙内外进行治疗和健康管理的数据,并且以直观易懂的方式呈现给医护人员和患者,使他们能够快速、准确地获取所需的信息。数据的融合还包括将不同来源的数据整合起来,为医护人员提供全面、完整的视图,同时基于医疗诊断的规则,不管是通过CDSS的形式,还是通过ChatBot(聊天机器人),帮助他们做出更好的决策。 实现数据和人的融合要按照人的需求投放数据。数字化转型的重要目标是为医护人员和患者提供所需的数据,以支持决策和治疗过程。这意味着我们应该了解用户的需求,将数据按照他们的角色、职责和关注点进行分类和投放。医生可能需要即时的患者数据、病历历史和最新的医学研究,而患者可能需要查看自己的健康记录、预约医生和接收个性化的健康建议。通过根据人的需求进行数据投放,新型软件可以提供个性化的服务和支持,形成千人千面,为每个用户提供有价值的信息。 简单易用是实现数字化转型成功的另一个关键。医护人员和患者使用的软件解决方案应该简单易用,不需要复杂的培训和技术知识。界面应该简单、直观、友好,操作流程简化和优化,以确保用户能够快速上手并高效地使用软件。简单易用的软件不仅能够减少用户的学习曲线和工作负担,还能提高用户满意度和工作效率。(比如Apple的医疗软件Apple Health,通过FHIR 技术,通过一个app能够连接数千家医院的病历数据,让患者可以通过一个app实现多家医院的互联网服务和数据整合) 无论是数字化转型还是高质量发展,软件为人服务始终是医疗信息化的核心宗旨。我们应该将软件看作是为人服务的工具,旨在帮助医护人员提供更好的医疗服务,提升患者的体验和健康结果。软件应该以用户体验为中心,并不断优化和改进,不断进行供给侧改革,以满足不断变化和不同人群的需求,而不是增加负担。 最后,数据会在安全可靠的前提下进行传递和流通。在互联网发展的早期时代,由于无法可依,野蛮生长,数据的滥用、隐私保护等存在很大问题。但随着《数据安全法》等法律法规的发布,相信未来的医疗行业数据一定会在更加安全、可靠、合规的前提下进行有序流动。 在未来的医疗信息化发展中,数据与人的关系将变得更加密不可分。通过数据的融合、按需投放、简单易用、安全可靠和以人为本的新一代软件,我们可以实现数据随处可及、人人可用易用的医疗信息化目标。这将为医护人员和患者提供更好的工作环境和医疗体验,推动整个医疗行业向前迈进。InterSystems公司作为创新性的数据平台解决方案供应商,我们始终致力于助力合作伙伴开发创新的解决方案,与合作伙伴一起共同实现这一愿景,改善医疗服务的质量和效率,提高患者体验的获得感的同时帮助医院降本增效,实现高质量发展。
文章
TZ Zhuang · 二月 3, 2023

PerfTools IO 测试套件

# 目的 这两个工具(RanRead 和 RanWrite)用于在数据库(或一对数据库)内生成随机读写事件,以测试每秒输入/输出的操作数 (IOPS)。它们可以一起使用或分开单独使用,以测试 IO 硬件容量、验证目标 IOPS 并确保系统拥有可接受的磁盘响应时间。从 IO 测试中收集的结果将因配置而异,具体取决于 IO 子系统。在运行这些测试之前,请确保相应的操作系统监控和存储级别监控已配置,这些捕获的 IO 性能指标可以为以后的分析提供帮助。我们推荐使用 IRIS 中捆绑的系统性能工具,例如^SystemPerformance。 请注意,这里使用的工具是对先前版本的更新。之前的版本可在[这里](https://community.intersystems.com/post/random-read-io-storage-performance-tool)找到。 # 安装 从 GitHub 下载 **PerfTools.RanRead.xml** 和 **PerfTools.RanWrite.xml** 工具 点击[这里](https://github.com/intersystems-community/perftools-io-test-suite)。 将工具导入 USER 命名空间。 USER> do $system.OBJ.Load("/tmp/PerfTools.RanRead.xml","ckf") USER> do $system.OBJ.Load("/tmp/PerfTools.RanWrite.xml","ckf") 运行帮助方法以查看所有入口点。所有命令都在 USER 中运行。 USER> do ##class(PerfTools.RanRead).Help() - do ##class(PerfTools.RanRead).Setup(Directory,DatabaseName,SizeGB,LogLevel) 创建具有相同名称的数据库和命名空间。日志级别必须在 0 到 3 的范围内,其中 0 是“无”,3 是“详细”。 - do ##class(PerfTools.RanRead).Run(Directory,Processes,Count,Mode) 运行随机读取 IO 测试。模式参数,1(默认)代表时间,以秒为单位 ,2是循环次数,用前面的 Count 参数控制。 - do ##class(PerfTools.RanRead).Stop() 终止所有后台作业。 - do ##class(PerfTools.RanRead).Reset() 删除先前运行的统计信息。在测试之间运行这个很重要,否则之前运行的统计数据将平均到当前运行的统计数据中。 - do ##class(PerfTools.RanRead).Purge(Directory) 删除同名的命名空间和数据库。 - do ##class(PerfTools.RanRead).Export(Directory) 将所有随机读取测试历史的摘要导出到逗号分隔的文本文件。 USER> do ##class(PerfTools.RanWrite).Help() - do ##class(PerfTools.RanWrite).Setup(Directory,DatabaseName) 创建具有相同名称的数据库和命名空间。 - do ##class(PerfTools.RanWrite).Run(Directory,NumProcs,RunTime,HangTime,HangVariationPct,Global name length,Global node depth,Global subnode length) 运行随机写入 IO 测试。除目录外的所有参数都有默认值。 - do ##class(PerfTools.RanWrite).Stop() 终止所有后台作业。 - do ##class(PerfTools.RanWrite).Reset() 删除先前运行的统计信息。 - do ##class(PerfTools.RanWrite).Purge(Directory) 删除同名的命名空间和数据库。 - do ##class(PerfTools.RanWrite).Export(Directory) 将所有随机写入测试历史的摘要导出到逗号分隔的文本文件。 # 配置 创建一个名为 RAN 的空(预扩展)数据库,其大小至少是要测试的物理主机内存的两倍。同时确保这个空数据库至少是存储控制器缓存大小的四倍。数据库需要大于物理内存以确保读取的数据不会缓存在文件系统缓存中。您可以手动创建或使用以下方法自动创建命名空间和数据库。 USER> do ##class(PerfTools.RanRead).Setup("/ISC/tests/TMP","RAN",200,1) Created directory /ISC/tests/TMP/ Creating 200GB database in /ISC/tests/TMP/ Database created in /ISC/tests/TMP/ 注意:RanRead 和 RanWrite 可以使用相同的数据库。如果需要一次测试多个磁盘或用于特定目的,也可以使用分开的数据库。 RanRead 代码允许指定数据库的大小,但 RanWrite 代码不允许,因此最好使用 RanRead Setup 命令来创建所需的任何预先确定大小的数据库,即使要创建 RanWrite 做测试的数据库也可以。 # 方法论 从少量进程和 30-60 秒运行时间开始测试。然后增加进程数,例如从 10 个作业开始,然后增加 10、20、40 等。继续运行单个测试,直到响应时间始终超过 10 毫秒或计算出的 IOPS 不再以线性方式增加。 该工具使用 ObjectScript VIEW 命令读取内存中的数据库块,因此如果您没有获得预期的结果,则可能所有数据库块都已在内存中。 作为指南,全闪存阵列通常可以接受以下 8KB 和 64KB 数据库随机读取(非缓存)的响应时间: * 平均 do ##class(PerfTools.RanRead).Export("/ISC/tests/TMP/ ") Exporting summary of all random read statistics to /usr/iris/db/zranread/PerfToolsRanRead_20221023-1408.txt Done. # 分析 建议使用内置的 [SystemPerformance 工具](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCM_systemperf)来获取被分析的系统的真实情况。 SystemPerformance 的命令需要在 %SYS 命名空间中运行。要切换到那个命名空间,请使用 ZN 命令: USER> ZN "%SYS" 要查找系统瓶颈的详细信息,或者如果需要系统如何以目标 IOPS 运行的更多详细信息,则应创建具有高频率数据采集的 SystemPerformance 配置文件: %SYS> set rc=$$addprofile^SystemPerformance("5minhighdef","A 5-minute run sampling every second",1,300) 然后运行该配置文件(从 %SYS)并立即切换回 USER 并使用“job”而不是“do”来启动 RanRead 和/或 RanWrite: %SYS> set runid=$$run^SystemPerformance("5minhighdef") %SYS> ZN “USER” USER> job ##class(PerfTools.RanRead).Run("/ISC/tests/TMP",5,60) USER> job ##class(PerfTools.RanWrite).Run("/ISC/tests/TMP",1,60,.001) 然后可以等待 SystemPerformance 作业结束,并使用 [yaspe](https://github.com/murrayo/yaspe) 等工具分析生成的 html 文件。 # 清理 运行完测试后,需要删除历史记录: %SYS> do ##class(PerfTools.RanRead).Reset()
文章
姚 鑫 · 七月 12, 2022

第三章 嵌入式Python概述(三)

# 第三章 嵌入式Python概述(三) ## 使用 SQL `IRIS` 中的类被投影到 `SQL`,除了使用类方法或直接全局访问之外,还允许使用查询访问数据。 `iris` 模块为提供了两种从 `Python` 运行 `SQL` 语句的不同方式。 以下示例使用 `iris.sql.exec()` 运行 `SQL SELECT` 语句以查找类名称以“`%Net.LDAP`”开头的所有类定义,返回一个包含每个名称和超类的结果集每个班级。在这里,系统类 `%Dictionary.ClassDefinition` 将 `SQL` 投影为同名表。 ```java >>> rs = iris.sql.exec("SELECT Name, Super FROM %Dictionary.ClassDefinition WHERE Name %STARTSWITH '%Net.LDAP'") ``` 以下示例使用 `iris.sql.prepare()` 准备 `SQL` 查询对象,然后执行查询,将`“%Net.LDAP”`作为参数传入: ```java >>> stmt = iris.sql.prepare("SELECT Name, Super FROM %Dictionary.ClassDefinition WHERE Name %STARTSWITH ?") >>> rs = stmt.execute("%Net.LDAP") ``` 无论哪种情况,都可以按如下方式遍历结果集,并且输出相同: ```java >>> for idx, row in enumerate(rs): ... print(f"[{idx}]: {row}") ... [0]: ['%Net.LDAP.Client.EditEntry', '%RegisteredObject'] [1]: ['%Net.LDAP.Client.Entries', '%RegisteredObject,%Collection.AbstractIterator'] [2]: ['%Net.LDAP.Client.Entry', '%RegisteredObject,%Collection.AbstractIterator'] [3]: ['%Net.LDAP.Client.PropList', '%RegisteredObject'] [4]: ['%Net.LDAP.Client.Search.Scope', '%Integer'] [5]: ['%Net.LDAP.Client.Session', '%RegisteredObject'] [6]: ['%Net.LDAP.Client.StringList', '%RegisteredObject'] [7]: ['%Net.LDAP.Client.ValueList', '%RegisteredObject,%Collection.AbstractIterator'] ``` ## 使用Globals 在 `IRIS` 数据库中,所有数据都存储在全局变量中。全局数组是持久的(意味着它们存储在磁盘上)、多维的(意味着它们可以有任意数量的下标)和稀疏的(意味着下标不必是连续的)。当您在表中存储类的对象或行时,这些数据实际上存储在全局变量中,尽管您通常通过方法或 `SQL` 访问它们并且从不直接接触全局变量。 有时将持久数据存储在全局变量中会很有用,而无需设置类或 `SQL` 表。在 `IRIS` 中,全局变量看起来很像任何其他变量,但它在名称前用插入符号 (`^`) 表示。以下示例将工作日的名称存储在当前命名空间的全局 `^Workdays` 中。 ```java >>> myGref = iris.gref('^Workdays') >>> myGref[None] = 5 >>> myGref[1] = 'Monday' >>> myGref[2] = 'Tuesday' >>> myGref[3] = 'Wednesday' >>> myGref[4] = 'Thursday' >>> myGref[5] = 'Friday' >>> print(myGref[3]) Wednesday ``` 第一行代码 `mmyGref = iris.gref('^Workdays') ` 获取一个全局引用(或 `gref`),指向一个名为 `^Workdays` 的全局引用,它可能已经存在也可能不存在。 第二行 `myGref[None] = 5` 将工作日数存储在 `^Workdays` 中,不带下标。 第三行 `myGref[1] = 'Monday'` 将字符串 `Monday` 存储在位置 `^Workdays(1)` 中。接下来的四行将剩余的工作日存储在位置 `^Workdays(2)` 到 `^Workdays(5)` 中。 最后一行 `print(myGref[3])` 显示了如何在给定 `gref` 的情况下访问存储在全局中的值。 # 一起使用 ObjectScript 和 Python `IRIS` 让 `ObjectScript` 和 `Python` 程序员的混合团队轻松协作。例如,类中的一些方法可以用 `ObjectScript` 编写,而另一些可以用 `Python` 编写。程序员可以选择用他们最熟悉的语言编写,或者更适合手头任务的语言。 ## 创建混合 InterSystems IRIS 类 下面的类有一个用 `Python` 编写的 `Print()` 方法和一个用 `ObjectScript` 编写的 `Write()` 方法,但它们在功能上是等效的,并且可以从 `Python` 或 `ObjectScript` 调用这两种方法。 ```java Class Sample.Company Extends (%Persistent, %Populate, %XML.Adaptor) { /// The company's name. Property Name As %String(MAXLEN = 80, POPSPEC = "Company()") [ Required ]; /// The company's mission statement. Property Mission As %String(MAXLEN = 200, POPSPEC = "Mission()"); /// The unique Tax ID number for the company. Property TaxID As %String [ Required ]; /// The last reported revenue for the company. Property Revenue As %Integer; /// The Employee objects associated with this Company. Relationship Employees As Employee [ Cardinality = many, Inverse = Company ]; Method Print() [ Language = python ] { print ('\nName: ' + self.Name + ' TaxID: ' + self.TaxID) } Method Write() [ Language = objectscript ] { write !, "Name: ", ..Name, " TaxID: ", ..TaxID } } ``` 此 `Python` 代码示例展示了如何使用 `%Id=2` 打开 `Company` 对象并调用 `Print()` 和 `Write()` 方法。 ```java >>> company = iris.cls("Sample.Company")._OpenId(2) >>> company.Print() Name: IntraData Group Ltd. TaxID: G468 >>> company.Write() Name: IntraData Group Ltd. TaxID: G468 ``` 此 `ObjectScript` 代码示例展示了如何打开相同的 `Company` 对象并调用这两种方法。 ```java SAMPLES>set company = ##class(Sample.Company).%OpenId(2) SAMPLES>do company.Print() Name: IntraData Group Ltd. TaxID: G468 SAMPLES>do company.Write() Name: IntraData Group Ltd. TaxID: G468 ``` ## 在 Python 和 ObjectScript 之间传递数据 虽然 `Python` 和 `ObjectScript` 在许多方面都兼容,但它们有许多自己的数据类型和结构,有时在将数据从一种语言传递到另一种语言时需要进行一些数据转换。之前看到了一个示例,即从 `ObjectScript` 向 `Python` 传递命名参数的示例。 `%SYS.Python` 类的 `Builtins()` 方法为提供了一种方便的方式来访问 `Python` 的内置函数,它可以帮助创建 `Python` 方法所期望的类型的对象。 以下 `ObjectScript` 示例创建两个 `Python` 数组 `newport` 和 `cleveland`,每个数组都包含一个城市的纬度和经度: ```java USER>set builtins = ##class(%SYS.Python).Builtins() USER>set newport = builtins.list() USER>do newport.append(41.49008) USER>do newport.append(-71.312796) USER>set cleveland = builtins.list() USER>do cleveland.append(41.499498) USER>do cleveland.append(-81.695391) USER>zwrite newport newport=11@%SYS.Python ; [41.49008, -71.312796] ; USER>zwrite cleveland cleveland=11@%SYS.Python ; [41.499498, -81.695391] ; ``` 下面的代码使用在前面的示例中看到的 `geopy` 包来计算纽波特,罗德岛和克利夫兰,俄亥俄州之间的距离。它使用 `geopy.distance.distance()` 方法创建一条路线,将数组作为参数传递,然后打印路线的英里属性。 ```java USER>set distance = $system.Python.Import("geopy.distance") USER>set route = distance.distance(newport, cleveland) USER>write route.miles 538.3904453677205311 ``` 注意: `geopy.distance.distance()` 方法实际上期望参数是 `Python` 元组数据类型,但数组也可以。 ## 运行 Python 命令 当开发或测试某些东西时,有时运行一行 `Python` 代码以查看它的作用或是否有效可能会很有用。在这种情况下,可以使用 `%SYS.Python.Run()` 方法,如下例所示: ```java USER>set rslt = ##class(%SYS.Python).Run("print('hello world')") hello world ``` 针对于字典如何初始化呢,目前是有异常,望指教
文章
姚 鑫 · 四月 28, 2021

第八章 解释SQL查询计划(一)

# 第八章 解释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语句: ![image](E3927D1DDE6B49FFA01E0F96682173B7) 这将显示包含原始查询的语句哈希的`Statement`列和包含生成的查询版本的语句哈希的`ParentHash`列。 `%PARALLEL`查询的SQL语句的计划状态为“未冻结/并行”,不能被冻结。 - 不包含FROM子句(因此不引用任何表)的查询仍然创建SQL语句。 例如:`SELECT $LENGTH('this string')`创建一个SQL语句,表列值`%TSQL_sys.snf`。