搜索​​​​

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

第十四章 XML获取当前节点信息

# 第十四章 XML获取当前节点信息 # DOM节点类型 `%XML.Document`和`%XML.Node`类识别以下`DOM`节点类型: - Element (`$$$xmlELEMENTNODE`) 请注意,这些宏在%xml.DOM.inc包含文件中定义。 - Text (`$$$xmlTEXTNODE`) - Whitespace (`$$$xmlWHITESPACENODE`). 其他类型的`DOM`节点被简单地忽略。 请以下XML文档: ```xml Jack O'Neill Samantha Carter Daniel Jackson ``` 当作为DOM查看时,该文档由以下节点组成: 文档节点示例 NodeID| NodeType |LocalName| Notes ---|---|---|--- 0,29| `$$$xmlELEMENTNODE`| team | 1,29| `$$$xmlWHITESPACENODE`| | 该节点是``节点的子节点 1,23| `$$$xmlELEMENTNODE`| member| 该节点是``节点的子节点 2,45| `$$$xmlTEXTNODE`| Jack O'Neill| 该节点是第一个``节点的子节点 1,37| `$$$xmlWHITESPACENODE`| | 该节点是``节点的子节点 1,41| `$$$xmlELEMENTNODE`| member |该节点是``节点的子节点 3,45| `$$$xmlTEXTNODE` |Samantha Carter| 该节点是第二个``节点的子节点 1,45| `$$$xmlWHITESPACENODE`| |该节点是``节点的子节点 1,49| `$$$xmlELEMENTNODE`| member |该节点是``节点的子节点 4,45| `$$$xmlTEXTNODE`| Daniel Jackson| 该节点是第三个``节点的子节点 1,53| `$$$xmlWHITESPACENODE`| |该节点是``节点的子节点 # 获取当前节点信息 `%XML.Node`的以下字符串属性。提供关于当前节点的信息。 在所有情况下,如果没有当前节点,将抛出一个错误。 ### LocalName 当前元素节点的本地名称。如果访问其他类型节点的此属性,则会引发错误。 ### Namespace 当前元素节点的命名空间URI。如果尝试访问其他类型节点的此属性,则会引发错误。 ### NamespaceIndex 当前元素节点的命名空间的索引。 当InterSystems IRIS读取XML文档并创建DOM时,它会标识文档中使用的所有名称空间,并为每个名称空间分配一个索引号。 如果尝试访问其他类型节点的此属性,则会引发错误。 ### Nil 如果`xsi:nil`或`xsi:null`为true,则等于true;如果此元素节点为1,则等于1。否则,此属性等于`False`。 ### NodeData 字符节点的值。 ### NodeId 当前节点ID。 可以设置此属性以导航到另一个节点。 ### NodeType 当前节点的类型,如前一节所述。 ### QName 元素节点的Q名称。仅当前缀对文档有效时才用于输出为XML。 以下方法提供有关当前节点的其他信息: ### GetText() ```java method GetText(ByRef text) as %Boolean ``` 获取元素节点的文本内容。如果返回文本,则此方法返回TRUE;在本例中,实际文本被追加到第一个参数后,该参数通过引用返回。 ### HasChildNodes() ```java method HasChildNodes(skipWhitespace As %Boolean = 0) as %Boolean ``` 如果当前节点有子节点,则返回True;否则返回False。 ### GetNumberAttributes() ```java method GetNumberAttributes() as %Integer ``` 方法`GetNumberAttributes()`为`%Integer` ## 示例 下面的示例方法编写一个报告,提供有关当前节点的信息: ```java ClassMethod ShowNode(node As %XML.Node) { w !,"LocalName=" _ node.LocalName if node.NodeType=$$$xmlELEMENTNODE { w !,"Namespace=" _ node.Namespace } if node.NodeType = $$$xmlELEMENTNODE { w !,"NamespaceIndex=" _ node.NamespaceIndex } w !,"Nil=" _ node.Nil w !,"NodeData=" _ node.NodeData w !,"NodeId=" _ node.NodeId w !,"NodeType=" _ node.NodeType w !,"QName=" _ node.QName w !,"HasChildNodes returns " _ node.HasChildNodes() w !,"GetNumberAttributes returns " _ node.GetNumberAttributes() s status = node.GetText(.text) if status { w !, "该节点的文本为 "_text } else { w !, "GetText不返回文本" } } ``` 示例输出可能如下所示: ```java LocalName=update Namespace= NamespaceIndex= Nil=0 NodeData=update NodeId=0,29 NodeType=0 QName=update HasChildNodes returns 1 GetNumberAttributes returns 0 GetText不返回文本 文档中的命名空间数: 1 Namespace 1 is http://www.w3.org/2001/XMLSchema-instance DHC-APP> ```
文章
Michael Lei · 五月 24, 2021

将 pButtons 数据提取到 csv 文件以便绘制图表

本帖的目的是回答一个问题。 在本系列的第二篇帖子中,我包括了从 pButtons 提取的性能数据的图表。 有人在线下问我,有没有比剪切/粘贴更快的方法从 pButtons .html文件中提取 `mgstat` 等指标,以便在 Excel 中绘图。 参见:- [第 2 部分 - 研究收集的指标](https://cn.community.intersystems.com/post/intersystems-数据平台和性能-–-第-2篇) pButtons 将其收集的数据编译成一个 html 文件,以便发送给 WRC 和查看整理的数据。 不过,尤其是对于长时间(如 24 小时)进行收集的 pButtons 来说,一些基于时间的数据(如 mgstat、vmstat 等)以图形方式查看更容易查找趋势或模式。 我知道把 pButtons 数据压缩到一个 html 文件再花时间去解压听起来很疯狂,但请记住,pButtons 是 WRC 用来获取许多系统指标的视图以帮助解决性能问题的工具。 系统级指标和 Caché 指标可以单独运行,但对我来说,在这个系列中使用 pButtons 捕获和分析性能指标是很方便的,因为我知道所有 Caché 安装都会有一个副本,或者可以下载副本,而且所有基本指标都可以放在一个文件中供不同的操作系统使用。 能够每天通过一个简单的例程来捕获这些指标也是很方便的,前提是没有以任何其他方式收集数据。 > _**2017 年 2 月。 我用 Python 重写了本文中的脚本,并添加了包括交互式 html 在内的图表。**_我认为 Python 实用工具有用得多。 请参见 [Yape - 另一个 pButtons 提取程序(以及自动创建图表)](https://community.intersystems.com/post/yape-yet-another-pbuttons-extractor-and-automatically-create-charts) ## 将 pButtons 性能指标提取到 csv 文件 由于我使用 Apple 笔记本电脑和 Unix 操作系统,所以很自然地写了一个快速 shell 脚本来提取数据到 csv 文件。 以下脚本从 pButtons .html 文件中提取 mgstat、vmstat 或 Windows 性能监视器数据。 下面的示例使用了大多数 *nix 系统都已安装的 Perl,但也可以使用其他脚本语言或在 Windows 上使用 powershell。 我将展示如何进行提取,有了这些信息,您就可以使用您喜欢的工具来执行同样操作。 关键是 html 文件中有标记来分隔指标。 例如,mgstat 用括号括起: 和 在 mgstat 部分中还有一些其他描述符信息,后面是 mgstat 输出的标题行。 vmstat 和 win_perfmon 的标记类似。 这个简单的脚本只是查找开始标记,然后输出从标题行到结束标记之前的行的所有内容。 #!/usr/bin/perl # extract_pButtons.pl - Simple extractor for pButtons # usage: ./extract_pButtons.pl # pButtons has the following markers in the html source # Metrics Parameters to pass # -------- ------------------- # mgstat mgstat Date # windows performance monitor win_perfmon Time # vmstat vmstat fre # usage example - Search for mgstat and redirect to .csv file # ./extract_pButtons.pl DB1_20160211_0001_24Hour_5Sec.html mgstat Date > myMgstatOutput.csv # usage example - Process a set of html files # for i in $(ls *.html); do ./extract_pButtons.pl ${i} vmstat fre > ${i}.vmstat.csv ; done # usage example - Pipeline to add commas # ./extract_pButtons.pl P570A_CACHE_20150418_0030_day.html vmstat fre | ./make_csv.pl >P570A_CACHE_20150418_0030_day.html.vmstat.csv $filename=$ARGV[0]; $string=$ARGV[1]; $firstLine=$ARGV[2]; $searchBeg="beg_".$string; $search2=$firstLine; $foundEnd="end_".$string; $foundString=0; $printIt=0; $break=0; open HTMLFILEIN, "
文章
Claire Zheng · 八月 17, 2021

FHIR标准和国际基于FHIR的互联互通实践(4):HL7的互操作标准

在国际上有很多互操作标准的开发组织,在我们医院信息化、医疗信息化领域有40多个标准开发组织,最广为人知的就是HL7国际、IHE,当然SNOMED也是,它开发的是行业术语跟语义的标准。 这些标准开发组织和和政府对于互操作标准有很多的测试和一致性认证,例如说HL7有FHIR连接马拉松(FHIR Connectathons),今年HL7中国也在正在开始做 FHIR连接马拉松测试(FHIR Connectathons)。IHE也有自己的连接马拉松(IHE Connectathons)测试。美国政府的 ONC IT认证计划,都是针对于互操作性的一些测试和认证。 我们来看看FHIR。在 HL7的提供的互操作标准里面,有很多的标准。我们现在提到的 FHIR是它最新的。HL7的标准的历史比较长,有40多年历史的V2,采纳度非常高,红色这张柱子是我们当前所处的 时间段,虽然它的采纳度在下降,但是仍比其他标准的采纳度都高。 V2有问题——V2的问题就是它的结构过于灵活,标准化程度不高,这也是我们使用它的一些问题。在使用V2的时候,我们通常面临的问题就是要使用V2标准的双方要坐下来谈怎么使用V2消息,才能够真正实现互联互通。在上世纪90年代的时候,HL7发布了V3版本的互操作标准,这也是个消息标准。 大家可以看看到上图里V3的采纳度历史最高峰已经过去了,最高峰也大概只到HL7 V2的一半。为什么会有这种低的采纳度?是因为V3虽然方法论很好——大家知道V3可能都知道参考信息模型,但是V3标准非常复杂,即便是非常有经验的集成工程师要使用V3来做一个集成,恐怕也要数周的时间,这也是V3在全球采纳度整体来说不高的一个原因。 此外有CDA,CDA是一个文档的标准,现在我们恰恰处在 CDA实施的这么一个高峰,采纳度的高峰。最近10年美国CDA的采纳度有一个非常快速的提升,原因是美国的市场上关于互联互通需求的提升,造成了这种以文档方式来进行共享交换的CDA标准采纳度的上升。 FHIR是最新的标准,大家可以看到FHIR标准现在其实还处于标准采纳度爬坡的阶段。它目前采纳度并不是很高,差不多刚刚跟V3降下来的采纳度到相同的水平。但是根据HL7组织的预测,FHIR将会成为未来广受欢迎的、或者说采纳度最高的它自己的标准。 注:本文根据InterSystems中国技术总监乔鹏演讲整理而成。
文章
姚 鑫 · 一月 14, 2023

第四十五章 使用 ^SystemPerformance 监视性能 - Abort ^SystemPerformance

# 第四十五章 使用 ^SystemPerformance 监视性能 - Abort ^SystemPerformance # `Abort ^SystemPerformance` 如果要停止正在运行的配置文件,可以中止数据收集,并可选择使用 `$$Stop^SystemPerformance(runid)` 命令删除配置文件的所有 `.log` 文件。例如,要中止由 `runid20111220_1327_12hours` 标识的报告的数据收集并删除到目前为止写入的所有 `.log` 文件,请在终端的 `%SYS` 命名空间中输入以下命令: ``` do Stop^SystemPerformance("20111220_1327_12hours") ``` 要在不删除日志文件的情况下停止作业并从这些日志文件生成 `HTML` 性能报告,请输入: ``` do Stop^SystemPerformance("20111220_1327_12hours",0) ``` 有关此命令的更多信息,请参阅以编程方式运行 `^SystemPerformance` 小节中的 `$$Stop^SystemPerformance("runid")`。 注意:必须有权停止`jobs`和删除文件。 # 以编程方式运行 `^SystemPerformance`。 可以使用启动、收集、预览和停止功能的入口点以编程方式运行 `^SystemPerformance` 实用程序,如下表所述: 注意:可以同时运行多个配置文件。 - `$$run^SystemPerformance("profile")` - 启动指定的配置文件。如果成功,返回runid;如果不成功,则返回 `0`。 - `$$literun^SystemPerformance("profile")` - 与 `$$run^SystemPerformance("profile")` 相同,只是它不包括操作系统数据。 注意:此命令适用于运行多个 `IRIS` 实例的服务器,其中操作系统数据将被复制。 - `$$Collect^SystemPerformance("runid")` - 为指定的 `runid` 生成可读的 HTML 性能报告文件。如果成功,返回 `1` 和报告文件名;如果不成功,返回 `0` 后跟一个克拉和失败的原因。 - `$$Preview^SystemPerformance("runid")` - 为指定的 `runid` 生成可读的 HTML 临时(不完整)性能报告文件。如果成功,则返回 `1`,后跟 `carat` 和文件位置。如果不成功,则返回 `0`,后跟 `carat` 和失败的原因。 - `$$Stop^SystemPerformance("runid",[0])` - 停止(中止)`^SystemPerformance` 收集指定 `runid` 的数据,并默认删除实用程序生成的关联 `.log` 文件。要在不删除 `.log` 文件的情况下停止数据收集并从这些日志文件生成 HTML 性能报告,请在 `runid` 后面包含 `0` 参数。如果不成功,该函数返回 `0`,后跟一个 `carat `和失败的原因;如果成功,它返回:`1:2:3:4_1:2:3:4`。 “成功”状态由下划线分隔的两部分组成:特定于操作系统和特定于 IRIS;在每个部分中,以冒号分隔的值指定:1.成功停止的作业数 2.停止失败的作业数 3.成功删除的文件数 4.未删除的文件数 - `$$waittime^SystemPerformance("runid")` - 报告指定 `runid` 的最终 `HTML` 文件完成之前的时间。如果 `runid` 完成,则返回 `ready now`,否则返回 `XX` 小时 `YY` 分钟 `ZZ` 秒形式的字符串。 在以下示例中,由 `^SystemPerformance` 实用程序创建的 `runid` 以编程方式获取,然后进行测试以确定是否已生成完整报告或临时报告。尚未创建完整报告,因为配置文件尚未完成(返回“`0^not ready`”),但已创建临时报告(“返回 `1`”)。根据这些信息,知道已经生成了一个 `HTML` 文件。 ```java %SYS>set runid=$$run^SystemPerformance("30mins") %SYS>set status=$$Collect^SystemPerformance(runid) SystemPerformance run 20181004_123815_30mins is not yet ready for collection. %SYS>write status 0^not ready %SYS>set status=$$Preview^SystemPerformance(runid) %SYS>write status 1^c:\intersystems\iris\mgr\USER_IRIS_20181004_123815_30mins_P1.html %SYS> ```
文章
姚 鑫 · 一月 7, 2023

第三十七章 使用 ^PROFILE 监控例程性能 - ^PROFILE 示例

# 第三十七章 使用 ^PROFILE 监控例程性能 - Using ^PROFILE - 当显示子例程标签列表(以及每个标签的指标)时,可以指定以下任何一项 Option |Description ---|--- `#` |要更详细地分析的子例程标签(在代码中)的行号。按 `Enter` 后,将显示指定标签的代码。 `B` |显示列表的上一页。 `L`|切换到子程序的行级显示。 `N`| 显示列表的下一页。 `Q`| 退出列表,返回上一级。 `R`| 使用最新指标刷新列表。注:如果列表中显示`*UNKNOWN*`,请输入`R`。 当显示代码行时,系统会提示指定下一步要执行的操作。的选择包括: Option |Description ---|--- `#`|要更详细地分析的代码中的行号。按Enter键后,将显示指定标签的代码。 `B`| 显示列表的上一页。 `C`| 在源代码和中间(`INT/MVI`)代码之间切换代码显示。 `M`| 更改页边距和长度。 `N`| 显示列表的下一页。 `O`| 根据不同的指标对页面进行重新排序。 `Q`| 退出列表,返回到上一级别。 `R`| 使用最新指标刷新列表。 `S`| 切换到例程的子例程级别显示。 # ^PROFILE 示例 以下是在终端中以交互方式运行^配置文件实用程序(从`%sys`命名空间)的示例: 1. 输入以下命令: ``` do ^PROFILE ``` 2. 此时将显示以下消息。 ``` WARNING: This routine will start a system-wide collection of data on routine activity and then display the results. There may be some overhead associated with the initial collection, and it could significantly affect a busy system. The second phase of collecting line level detail activity has high overhead and should ONLY BE RUN ON A TEST SYSTEM! Are you ready to start the collection? Yes => ``` 3. 按`Enter`键开始收集指标。将显示与以下内容类似的指标: ``` Waiting for initial data collection ... RtnLine Time CPU RtnLoad GloRef GloSet 1. 41.48% 12.19% 0.00% 28.97% 10.65% 0.00% %Library.ResultSet.1.INT (IRISLIB) 2. 35.09% 56.16% 65.22% 9.35% 36.77% 42.55% SYS.Database.1.INT (IRISSYS) 3. 10.75% 6.62% 0.00% 43.30% 22.68% 46.81% Config.Databases.1.INT (IRISSYS) 4. 7.13% 3.22% 0.00% 6.23% 0.00% 0.00% %Library.Persistent.1.INT (IRISLIB) 5. 1.26% 0.71% 0.00% 4.36% 4.12% 4.26% PROFILE.INT (IRISSYS) 6. 1.20% 0.00% 0.00% 0.00% 5.15% 6.38% %SYS.WorkQueueMgr.INT (IRISSYS) 7. 0.76% 15.08% 34.78% 0.00% 0.00% 0.00% %SYS.API.INT (IRISSYS) 8. 0.64% 1.05% 0.00% 0.00% 17.18% 0.00% %Library.JournalState.1.INT (IRISLIB) 9. 0.61% 0.31% 0.00% 3.74% 0.00% 0.00% %Library.IResultSet.1.INT (IRISLIB) 10. 0.28% 0.93% 0.00% 0.00% 1.72% 0.00% %Library.Device.1.INT (IRISLIB) 11. 0.24% 0.71% 0.00% 0.62% 0.00% 0.00% Config.CPF.1.INT (IRISSYS) Select routine(s) or '?' for more options N => ``` 4. 输入与要更详细分析的例程相关联的数字。例如,输入`2-3`、`5`、`7`、`10`,然后输入N或B以显示其他页面,以可以选择其他程序。 5. 选择要分析的所有例程后,输入q以显示类似以下内容的消息: ``` There are 2 routines selected for detailed profiling. You may now end the routine level collection and start a detailed profiler collection. WARNING !! This will have each process on the system gather subroutine level and line level activity on these routines. Note that this part of the collection may have a significant effect on performance and should only be run in a test or development instance. Are you ready to start the detailed collection? Yes => ``` 6. 按`Enter`键后,将显示类似以下内容的页面: ``` Stopping the routine level Profile collection ... Loading ^%Library.Persistent.1 in ^^c:\intersystems\iris\mgr\irislib\ Detail level Profile collection started. RtnLine Routine Name (Database) 1. 96.72% %Library.Persistent.1.INT (IRISLIB) 2. 3.28% Config.CPF.1.INT (IRISSYS) Select routine to see details or '?' for more options R => ``` 7. 选择要分析其代码的例程后,该例程将显示一页有关该代码的信息。
文章
姚 鑫 · 四月 19, 2021

第三章 优化表(二)

# 第三章 优化表(二) # 调整表计算值 调优表操作根据表中的代表性数据计算和设置表统计信息: - `ExtentSize`,它可能是表中的实际行数(行数),也可能不是。 - 表中每个属性(字段)的选择性。 可以选择性地阻止单个属性的选择性计算。 - 属性的离群选择性,其中一个值比其他值出现得更普遍。 有效的查询可以利用离群值优化。 - 标识某些属性特征的每个属性的注释。 - 每个属性的平均字段大小。 - 表的SQL `Map Name`、`BlockCount`和`Source of BlockCount`。 ## 区段大小和行计数 从管理门户运行Tune Table工具时,`ExtentSize`是表中当前行的实际计数。默认情况下,`GatherTableStats()`方法还将实际行数用作`ExtentSize`。当表包含大量行时,最好对较少的行执行分析。可以使用SQL tune table命令并指定`%SAMPLE_PERCENT`来仅对总行的一定百分比执行分析。在针对包含大量行的表运行时,可以使用此选项来提高性能。此`%SAMPLE_PERCENT`值应该足够大,以便对代表性数据进行采样。如果`ExtentSize`。 如果`TuneTable`返回异常值选择性,则正常选择性仍然是整个行集内每个非异常值数据值的百分比。例如,如果在`1000`个随机选择的值中检测到`11`个不同的值,其中一个是异常值,则选择性为`1/11(9.09%)`:平均每个条目出现的几率为十一分之一。如果异常值选择性是`80%`,常规选择性是`1%`,那么除了异常值之外,还可以找到大约`20((1-0.80)/0.01)`个额外的非异常值。 如果优化表初始采样仅返回单个值,但附加采样返回多个不同的值,则这些采样结果会修改正常选择性。例如,990个值的初始随机采样仅检测一个值,但后续采样检测其他不同值的10个单个实例。在这种情况下,初始离群值会影响选择性值,该值现在被设置为`1/1000(0.1%)`,因为10个非离群值中的每一个在1000个记录中只出现一次。 异常值选择性的最常见示例是允许`NULL`的属性。如果某个特性具有`NULL`的记录数大大超过该特性具有任何特定数据值的记录数,则`NULL`为异常值。以下是`FavoriteColors`字段的选择性和异常值选择性: ```java SELECTIVITY of FIELD FavoriteColors CURRENT = 1.8966% CALCULATED = 1.4405% CURRENT OUTLIER = 45.0000%, VALUE = CALCULATED OUTLIER = 39.5000%, VALUE = ``` 如果一个字段只包含一个不同的值(所有行都具有相同的值),则该字段的选择性为`100%`。选择性为`100%`的值不被视为异常值。调谐表通过采样数据来建立选择性和异常值选择值。为了确定这一点,优选表首先测试少量或几条记录,如果这些记录都具有相同的字段值,它将测试多达`100,000`条随机选择的记录,以支持非索引字段的所有值都相同的假设。只有在字段已编制索引,字段是索引的第一个字段,并且字段和索引具有相同的排序规则类型的情况下,优化表才能完全确定该字段的所有值是否相同。 - 如果已知未编制索引的字段具有在测试`100,000`条随机选择的记录中可能检测不到的其他值,则应手动设置选择性和离群值选择性。 - 如果已知非索引字段没有其他值,则可以手动指定`100%`的选择性,删除任何异常值选择性,并设置`CALCSELECTIVITY=0`以防止优选表尝试计算选择性或将此值指定为异常值。 要修改这些选择性、异常值选择性和异常值计算值,请从调谐表显示中选择单个字段。这会在显示屏右侧的详细信息区域中显示该字段的这些值。可以将选择性、异常值选择性和/或异常值修改为更适合预期完整数据集的值。 - 可以将选择性指定为带有百分号(`%`)的行的百分比,也可以指定为整数行(没有百分号)。如果指定为整数行数,InterSystems IRIS将使用区大小来计算选择性百分比。 - 可以为以前没有异常值的字段指定异常值选择性和异常值。将异常值选择性指定为带百分号(`%`)的百分比。如果仅指定异常值选择性,则Tune Table假定异常值为``。如果仅指定异常值,则除非还指定异常值选择性,否则调谐表不会保存此值。 ## CALCSELECTIVITY参数与不计算选择性 在某些情况下,可能不希望优化表工具计算属性的选择性。要防止计算选择性,请将属性的`CALCSELECTIVITY`参数的值指定为`0`(默认值为`1`)。在Studio中,可以在“新建属性向导”的“属性参数”页上设置`CALCSELECTIVITY`,也可以在检查器中的属性参数列表中设置`CALCSELECTIVITY`(可能需要收缩并重新展开属性参数列表才能显示它)。 应该指定`CALCSELECTIVITY=0`的一种情况是,如果该字段未编制索引,则已知该字段在所有行中只包含一个值(`选择性=100%`)。 ## 离群值的优化 默认情况下,查询优化器假定查询不会选择离群值。 例如,查询通常选择特定的字段值并从数据库返回少量记录,而不是返回大量记录,其中该字段值是离群值。 查询优化器总是使用选择性来构造查询计划,除非执行一些要求考虑离群选择性的操作。 根据选择离群值,可以执行以下几个操作来调整查询优化: - 如果异常值是``,则在查询`WHERE`子句中为该字段指定一个`is null`或`is NOT null`条件。 这将导致查询优化器在构造查询时使用离群值选择性。 - 如果离群值是一个数据值,查询优化器会假定选择的字段值不是离群值。 例如,总部位于马萨诸塞州的公司的员工记录可能有`Office_State`字段离群值`MA` (`Massachusetts`)。 优化器假设查询不会选择`' MA '`,因为这将返回数据库中的大多数记录。 但是,如果正在编写一个查询来选择离群值,可以通过将离群值封装在双括号中来通知优化器。 在该字段上查询时,指定一个`WHERE`子句,如下所示`:WHERE Office_State=(('MA'))`。 这种技术抑制了文字替换,并迫使查询优化器在构建查询计划时使用离群值选择性。 对于动态SQL查询,以及在使用ODBC/JDBC提供的InterSystems IRIS之外编写的查询,这种语法是必需的。 对于类查询、嵌入式SQL查询或通过视图访问的查询,则不需要这样做。 - 根据参数值SQL设置配置系统范围的优化查询。 该选项为离群值设置了运行时计划选择(RTPC)优化和作为离群值(BQO)优化的偏差查询的适当组合。 注意,更改此配置设置将清除所有名称空间中的所有缓存查询。 使用管理门户,选择System Administration、Configuration、SQL和Object Settings、SQL来查看和更改此选项。 可用的选择有: - 假设查询参数值不是字段离群值(`BQO=OFF`, `RTPC=OFF`,初始默认值) - 假设查询参数值经常匹配字段离群值(`BQO=ON`, `RTPC=OFF`) - 在运行时优化实际查询参数值(`BQO=OFF`, `RTPC=ON`) 要确定当前设置,调用`$SYSTEM.SQL.CurrentSettings()`。 - 覆盖查询的系统范围配置设置。 通过指定`%NORUNTIME restrict`关键字,可以覆盖单个查询的`RTPC`。 如果查询`SELECT Name,HaveContactInfo FROM t1 WHERE HaveContactInfo=?` 将导致`RTPC`处理,查询`SELECT %NORUNTIME Name,HaveContactInfo FROM t1 WHERE HaveContactInfo=?` 将覆盖RTPC,从而产生一个标准的查询计划。 通过指定注释选项`/*#OPTIONS {"BiasAsOutlier":1} */`,可以覆盖偏见查询作为单个查询的离群值。 ## “备注”列 管理门户优化表信息选项为每个字段显示一个备注列。此字段中的值是系统定义的,不可修改。它们包括以下内容: - `RowID`字段:一个表有一个`RowID`,由系统定义。它的名称通常是ID,但可以有不同的系统分配的名称。由于其所有值(根据定义)都是唯一的,因此其选择性始终为1。如果类定义包括`SqlRowIdPrivate`,则`Notes`列值为`RowID`字段、`Hidden`字段。 - 隐藏字段:隐藏字段定义为私有,`SELECT*`不显示。默认情况下,`CREATE TABLE`将`RowID`字段定义为隐藏;可以指定`%PUBLICROWID`关键字以使`RowID`不隐藏和公开。默认情况下,由持久化类定义定义的表将`RowID`定义为非隐藏;可以指定`SqlRowIdPrivate`将`RowID`定义为隐藏和私有。容器字段定义为隐藏。 - 流字段:表示使用流数据类型定义的字段,可以是字符流(`CLOB`),也可以是二进制流(`BLOB`)。流文件没有平均字段大小。 - 父引用字段:引用父表的字段。 注释列中未标识标识字段、`ROWVERSION`字段、序列字段或`UNIQUEIDENTIFIER(GUID)`字段。 ## 平均字段大小 运行调谐表根据当前表格数据集计算所有非流字段的平均字段大小(以字符为单位)。这与`AVG($length(Field))`相同(除非另有说明),四舍五入到小数点后两位。可以更改各个字段的平均字段大小,以反映字段数据的预期平均大小。 - NULL:因为`$LENGTH`函数将`NULL`字段视为长度为0,所以将长度为0的`NULL`字段取平均值。这可能会导致平均字段大小小于一个字符。 - 空列:如果列不包含数据(所有行都没有字段值),则平均字段大小值为1,而不是0。对于不包含数据的列,`AVG($length(Field))`为0。 - `ExtentSize=0`:将`ExtentSize`设置为0时,所有字段的平均字段大小将重置为0。 - 逻辑字段值:平均字段大小始终根据字段的逻辑(内部)值计算。 - 列表字段:InterSystems IRIS列表字段根据其逻辑(内部)编码值计算。此编码长度大于列表中元素的总长度。 - 容器字段:集合的容器字段大于其集合对象的总长度。例如,在`Sample.Person`中,`Home`容器字段的`Average Field` Size大于`Home_Street`、`Home_City`、`Home_State`和`Home_Zip`平均字段大小的总和。 - 流字段:流字段没有平均字段大小。 如果特性/字段的特性参数`CALCSELECTIVITY`设置为0,则调谐表不会计算该特性/字段的平均字段大小。 可以通过从调谐表显示中选择单个字段来修改平均字段大小计算值。这将在显示屏右侧的详细信息区域中显示该字段的值。可以将“平均字段大小”修改为更适合预期的完整数据集的值。由于设置此值时优化表不执行验证,因此应确保该字段不是流字段,并且指定的值不大于最大字段大小(`MaxLen`)。 平均字段大小还显示在管理门户目录详细信息选项卡字段选项表中。必须已为字段选项表运行了调整表,才能显示平均字段大小值。 ## map BlockCount选项卡 调优表Map `BlockCount`选项卡显示SQL映射名称、`BlockCount`(作为正整数)和`BlockCount`的来源。 块计数的来源可以在类定义中定义、由类编译器估计或由TuneTable度量。 将类编译器估计的调优表更改运行到TuneTable测量; 它不影响在类定义中定义的值。 通过从调优表显示中选择单个SQL映射名称,可以修改`BlockCount`计算值。 这将在显示器右侧的详细信息区域中显示该地图名称的块计数。 可以将块计数修改为一个更适合预期的完整数据集的值。 因为在设置该值时,Tune Table不执行验证,所以应该确保块计数是一个有效值。 修改`BlockCount`会将`BlockCount`的来源更改为类定义中定义的。 # 导出和重新导入调优表统计信息 可以从一个表或一组表导出调优表统计信息,然后将这些调优表统计信息导入一个表或一组表。 以下是可能希望执行此导出/导入的三种情况。 (为简单起见,这些描述了从单个表导出/导入统计数据; 在实际使用中,通常会从多个相互关联的表中导出/导入统计数据): - 为生产系统建模:生产表完全填充了实际数据,并使用`Tune table`进行优化。 在测试环境中,创建的表具有相同的表定义,但数据少得多。 通过从生产表导出调优表统计信息并将它们导入测试表,可以在测试表上对生产表优化建模。 - 要复制生产系统:生产表完全填充了实际数据,并使用tune Table进行了优化。将创建具有相同表定义的第二个生产表。(例如,生产环境及其备份环境,或者多个相同的表定义,每个表包含不同医院的患者记录。)。通过从第一个表导出调优表统计信息并将其导入第二个表,您可以为第二个表提供与第一个表相同的优化,而无需第二次运行调优表或等待第二个表填充有代表性的数据。 - 要恢复到以前的统计信息集:可以通过运行tune Table或显式设置统计信息来创建表的优化统计信息。通过导出这些统计信息,可以在尝试其他统计信息设置时保留它们。一旦确定了最佳统计信息集,就可以将它们重新导入到表中。 可以使用`$SYSTEM.SQL.Stats.Table.Export()`方法将调优表统计信息导出到`XML`文件。此方法可以导出名称空间中一个、多个或所有表的优化表统计信息,如以下示例所示: ```java DO $SYSTEM.SQL.Stats.Table.Export("C:\AllStats.xml") /* 导出当前命名空间中所有架构/表的TuneTable统计信息 */ ``` ```java DO $SYSTEM.SQL.Stats.Table.Export("C:\SampleStats.xml","Sample") /* 导出Sample模式中所有表的可调统计信息 */ ``` ```java DO $SYSTEM.SQL.Stats.Table.Export("C:\SamplePStats.xml","Sample","P*") /* 导出Sample模式中所有以字母“P”开头的表的可调统计信息 */ ``` ```java DO $SYSTEM.SQL.Stats.Table.Export("C:\SamplePersonStats.xml","Sample","Person") /* 导出Sample的可调统计信息Person */ ``` 可以使用`$SYSTEM.SQL.Stats.Table.Import()`方法重新导入使用`$SYSTEM.SQL.Stats.Table.Import()`方法导出的调优表统计信息。 `$SYSTEM.SQL.Stats.Table.Import()`有一个`KeepClassUpToDate boolean`选项。 如果为真(并且`update`为真),`$SYSTEM.SQL.Stats.Table.Import()`将用新的`EXTENTSIZE`和选择性值更新类定义,但类定义将保持最新。 但是,在许多情况下,最好在调优了类表之后重新编译类,这样类定义中的查询就可以重新编译,SQL查询优化器就可以使用更新后的数据统计信息。 默认值为`FALSE(0)`。请注意,如果该类已部署,则类定义不会更新。 `$SYSTEM.SQL.Stats.Table.Import()`有一个`ClearCurrentStats boolean`选项。 如果为`TRUE`, `$SYSTEM.SQL.Stats.Table.Import()`将在导入统计信息之前从现有表中清除所有先前的区段大小、选择性、块计数和其他调优表统计信息。 如果您想要完全清除导入文件中没有指定的那些表状态,而不是让它们在表的`persistent`类中定义,则可以使用此方法。 默认值是`FALSE(0)`。 如果`$SYSTEM.SQL.Stats.Table.Import()`没有找到相应的表,它将跳过该表并继续导入文件中指定的下一个表。 如果找到了一个表,但是没有找到一些字段,那么这些字段将被跳过。 无法继承类存储定义中映射的`BlockCount`。`BlockCount`只能出现在映射起源的类的存储定义中。如果映射源自超类,则`$SYSTEM.SQL.Stats.Table.Import()`仅设置投影表的`BlockCount`元数据,而不设置类存储`BlockCount`元数据。
文章
Michael Lei · 四月 19, 2022

用Caché ObjectScript 生成EXCEL

有很多方法可以使用Intersystems生成excel文件,其中一些是ZEN报告、IRIS报告(Logi报告或正式称为JReports),或者我们可以使用第三方Java库,可能性几乎是无限的。 但是,如果你想只用Caché ObjectScript创建一个简单的电子表格呢?(没有第三方应用程序) 在我的案例中,我需要生成包含大量原始数据的报告(财务人员喜欢这些数据),但是我的ZEN/IRIS失败了,给了我一个我想称之为 "零字节的文件",基本上说java的内存用完了,并导致报告服务器上的重载。 这可以用Office Open XML(OOXML)来完成。Office Open XML格式是由一个ZIP包内的一些XML文件组成的。因此,基本上我们需要生成这些XML文件,并将其压缩重命名为.xslx。就这么简单。 这些文件遵循一套简单的惯例,称为开放包装惯例。你需要声明各部分的内容类型,以及告诉消费应用程序应该从哪里开始。 为了创建一个简单的电子表格,我们至少需要5个文件。 workbook.xml worksheet.xml [Content_Types].xml styles.xml _rels .rels workbook.xml.rels workbook.xml工作簿是各种工作表的容器。工作簿是你可以引用样式部分、共享字符串表以及适用于整个电子表格文件的任何其他信息的地方。、 ClassMethod GenerateWorkbookXML(){ set status =$$$OK set xmlfile = tempDirectoryPath_"workbook.xml" try{ set stream = ##class(%Stream.FileCharacter).%New() set sc=stream.LinkToFile(xmlfile) do stream.WriteLine("<?xml version='1.0' encoding='UTF-8' standalone='yes'?>") do stream.WriteLine("<workbook xmlns='http://schemas.openxmlformats.org/spreadsheetml/2006/main' xmlns:r='http://schemas.openxmlformats.org/officeDocument/2006/relationships'>") do stream.WriteLine("<sheets> <sheet name='"_workSheetName_"' sheetId='1' r:id='rId1'/>") do stream.WriteLine("</sheets> </workbook>") do stream.%Save() }catch{ set status=$$$NO } kill stream return status } _rels/workbook.xml.rels 我们只需要创建一个id为rId1的关系,这样它就会与workbook.xml部分的引用相匹配 ClassMethod CreateRelsXML(){ set status =$$$OK set isunix=$zcvt($p($zv," ",3,$l($p($zv," (")," ")),"U")["UNIX" if isunix { set ext="/" }else{ set ext="\" } set xmlfile = fileDirectory_"_rels"_ext_"workbook.xml.rels" set stream = ##class(%Stream.FileCharacter).%New() set sc=stream.LinkToFile(xmlfile) do stream.WriteLine("<?xml version='1.0' encoding='UTF-8' standalone='yes'?>") do stream.WriteLine("<Relationships xmlns='http://schemas.openxmlformats.org/package/2006/relationships'>") do stream.WriteLine("<Relationship Id='rId1' Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet' Target='worksheet.xml'/>") do stream.WriteLine("<Relationship Id='rId2' Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles' Target='styles.xml' />") do stream.WriteLine("</Relationships>") try{ do stream.%Save() }catch{ set status=$$$NO } kill stream set xmlfile = fileDirectory_"_rels"_ext_".rels" set stream = ##class(%Stream.FileCharacter).%New() set sc=stream.LinkToFile(xmlfile) do stream.WriteLine("<?xml version='1.0' encoding='UTF-8' standalone='yes'?>") do stream.WriteLine("<Relationships xmlns='http://schemas.openxmlformats.org/package/2006/relationships'>") do stream.WriteLine("<Relationship Id='rId1' Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument' Target='workbook.xml'/>") do stream.WriteLine("</Relationships>") try{ do stream.%Save() }catch{ set status=$$$NO } kill stream return status } [Content_Types].xml静态文件(目前,它应该是一个动态文件,取决于工作表的数量)将工作簿的工作表和样式链接在一起。每个Office Open XML文件必须声明ZIP包中使用的内容类型。这是用[Content_Types].xml文件完成的。 ClassMethod GenerateConntentTypesXML(){ set status =$$$OK set xmlfile = tempDirectoryPath_"[Content_Types].xml" set stream = ##class(%Stream.FileCharacter).%New() set sc=stream.LinkToFile(xmlfile) try{ do stream.WriteLine("<?xml version='1.0' encoding='UTF-8' standalone='yes'?>") do stream.WriteLine("<Types xmlns='http://schemas.openxmlformats.org/package/2006/content-types'>") do stream.WriteLine("<Default Extension='rels' ContentType='application/vnd.openxmlformats-package.relationships+xml'/>") do stream.WriteLine("<Override PartName='/workbook.xml' ContentType='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml'/>") do stream.WriteLine("<Override PartName='/worksheet.xml' ContentType='application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml'/>") do stream.WriteLine("<Override PartName='/styles.xml' ContentType='application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml' />") do stream.WriteLine("</Types>") do stream.%Save() }catch{ set status=$$$NO } kill stream return status } styles.xml所有的格式化都在这里,目前我已经添加了一些静态样式,(计划将其转换为更多的动态工作簿特定的样式)。 Excel Styles ID Style Excel Format 1 default Text 2 #;[Red]-# Number 3 #.##;[Red]-#.## Number 4 yyyy/mm/dd Date 5 hh:mm Date 6 Header and Center Aligned Text 7 Header 2 Left Aligned Text 8 Good(Green Highlight) General 9 Bad(Red Highlight) General 10 Neutral(Orange Highlight) General 11 yyyy/mm/dd hh:mm Date ClassMethod CreateStylesXML(){ set status =$$$OK set xmlfile = tempDirectoryPath_"styles.xml" try{ set stream = ##class(%Stream.FileCharacter).%New() set sc=stream.LinkToFile(xmlfile) do stream.WriteLine("<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>") do stream.WriteLine("<styleSheet xmlns=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"" xmlns:mc=""http://schemas.openxmlformats.org/markup-compatibility/2006"" mc:Ignorable=""x14ac x16r2 xr"" xmlns:x14ac=""http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac"" xmlns:x16r2=""http://schemas.microsoft.com/office/spreadsheetml/2015/02/main"" xmlns:xr=""http://schemas.microsoft.com/office/spreadsheetml/2014/revision"">") do stream.WriteLine("<numFmts count=""4"">") do stream.WriteLine("<numFmt numFmtId=""166"" formatCode=""#,##0;[Red]\-#,##0""/>") do stream.WriteLine("<numFmt numFmtId=""168"" formatCode=""#,##0.00;[Red]\-#,##0.00""/>") do stream.WriteLine("<numFmt numFmtId=""169"" formatCode=""dd\/mm\/yyyy;@""/>") do stream.WriteLine("<numFmt numFmtId=""170"" formatCode=""dd/mm/yyyy\ hh:mm""/></numFmts>") do stream.WriteLine("<fonts count=""5"" x14ac:knownFonts=""1"">") do stream.WriteLine("<font><sz val=""10""/><color theme=""1""/><name val=""Calibri""/><family val=""2""/><scheme val=""minor""/></font>") do stream.WriteLine("<font><sz val=""10""/><color rgb=""FF006100""/><name val=""Calibri""/><family val=""2""/><scheme val=""minor""/></font>") do stream.WriteLine("<font><sz val=""10""/><color rgb=""FF9C0006""/><name val=""Calibri""/><family val=""2""/><scheme val=""minor""/></font>") do stream.WriteLine("<font><sz val=""10""/><color rgb=""FF9C5700""/><name val=""Calibri""/><family val=""2""/><scheme val=""minor""/></font>") do stream.WriteLine("<font><b/><sz val=""10""/><color theme=""1""/><name val=""Calibri""/><family val=""2""/><scheme val=""minor""/></font></fonts>") do stream.WriteLine("<fills count=""5"">") do stream.WriteLine("<fill><patternFill patternType=""none""/></fill>") do stream.WriteLine("<fill><patternFill patternType=""gray125""/></fill>") do stream.WriteLine("<fill><patternFill patternType=""solid""><fgColor rgb=""FFC6EFCE""/></patternFill></fill>") do stream.WriteLine("<fill><patternFill patternType=""solid""><fgColor rgb=""FFFFC7CE""/></patternFill></fill>") do stream.WriteLine("<fill><patternFill patternType=""solid""><fgColor rgb=""FFFFEB9C""/></patternFill></fill></fills>") do stream.WriteLine("<borders count=""1""><border><left/><right/><top/><bottom/><diagonal/></border></borders>") do stream.WriteLine("<cellStyleXfs count=""4"">") do stream.WriteLine("<xf numFmtId=""0"" fontId=""0"" fillId=""0"" borderId=""0""/>") do stream.WriteLine("<xf numFmtId=""0"" fontId=""1"" fillId=""2"" borderId=""0"" applyNumberFormat=""0"" applyBorder=""0"" applyAlignment=""0"" applyProtection=""0""/>") do stream.WriteLine("<xf numFmtId=""0"" fontId=""2"" fillId=""3"" borderId=""0"" applyNumberFormat=""0"" applyBorder=""0"" applyAlignment=""0"" applyProtection=""0""/>") do stream.WriteLine("<xf numFmtId=""0"" fontId=""3"" fillId=""4"" borderId=""0"" applyNumberFormat=""0"" applyBorder=""0"" applyAlignment=""0"" applyProtection=""0""/></cellStyleXfs>") do stream.WriteLine("<cellXfs count=""12""><xf numFmtId=""0"" fontId=""0"" fillId=""0"" borderId=""0"" xfId=""0""/>") do stream.WriteLine("<xf numFmtId=""49"" fontId=""0"" fillId=""0"" borderId=""0"" xfId=""0"" quotePrefix=""1"" applyNumberFormat=""1""/>") do stream.WriteLine("<xf numFmtId=""166"" fontId=""0"" fillId=""0"" borderId=""0"" xfId=""0"" applyNumberFormat=""1""/>") do stream.WriteLine("<xf numFmtId=""168"" fontId=""0"" fillId=""0"" borderId=""0"" xfId=""0"" applyNumberFormat=""1""/>") do stream.WriteLine("<xf numFmtId=""169"" fontId=""0"" fillId=""0"" borderId=""0"" xfId=""0"" applyNumberFormat=""1""/>") do stream.WriteLine("<xf numFmtId=""20"" fontId=""0"" fillId=""0"" borderId=""0"" xfId=""0"" applyNumberFormat=""1""/>") do stream.WriteLine("<xf numFmtId=""49"" fontId=""4"" fillId=""0"" borderId=""0"" xfId=""0"" applyNumberFormat=""1"" applyFont=""1""/>") do stream.WriteLine("<xf numFmtId=""49"" fontId=""4"" fillId=""0"" borderId=""0"" xfId=""0"" applyNumberFormat=""1"" applyFont=""1"" applyAlignment=""1""><alignment horizontal=""center""/>") do stream.WriteLine("</xf>") do stream.WriteLine("<xf numFmtId=""49"" fontId=""1"" fillId=""2"" borderId=""0"" xfId=""1"" applyNumberFormat=""1""/>") do stream.WriteLine("<xf numFmtId=""0"" fontId=""2"" fillId=""3"" borderId=""0"" xfId=""2""/>") do stream.WriteLine("<xf numFmtId=""0"" fontId=""3"" fillId=""4"" borderId=""0"" xfId=""3""/>") do stream.WriteLine("<xf numFmtId=""170"" fontId=""0"" fillId=""0"" borderId=""0"" xfId=""0"" applyNumberFormat=""1""/></cellXfs>") do stream.WriteLine("<cellStyles count=""4""><cellStyle name=""Bad"" xfId=""2"" builtinId=""27""/>") do stream.WriteLine("<cellStyle name=""Good"" xfId=""1"" builtinId=""26""/><cellStyle name=""Neutral"" xfId=""3"" builtinId=""28""/>") do stream.WriteLine("<cellStyle name=""Normal"" xfId=""0"" builtinId=""0""/></cellStyles><dxfs count=""0""/>") do stream.WriteLine("<tableStyles count=""0"" defaultTableStyle=""TableStyleMedium2"" defaultPivotStyle=""PivotStyleLight16""/> ") do stream.WriteLine("<extLst><ext uri=""{EB79DEF2-80B8-43e5-95BD-54CBDDF9020C}"" xmlns:x14=""http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"">") do stream.WriteLine("<x14:slicerStyles defaultSlicerStyle=""SlicerStyleLight1""/></ext><ext uri=""{9260A510-F301-46a8-8635-F512D64BE5F5}"" xmlns:x15=""http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"">") do stream.WriteLine("<x15:timelineStyles defaultTimelineStyle=""TimeSlicerStyleLight1""/></ext></extLst>") do stream.WriteLine("</styleSheet>") do stream.%Save() }catch{ set status=$$$NO } kill stream return status } worksheet.xml这是我们的数据所在的地方。工作表的第一行将有列的标题。 接下来的行将只有第一列的数据。我们将在这里定义每一列的列宽,如果不是默认的,列将被设置为自动适应。 worksheet xml 示例 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <worksheet xmlns="https://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="https://schemas.openxmlformats.org/officeDocument/2006/relationships"> <sheetData> <row> <c t="inlineStr"> <is> <t>Name</t> </is> </c> <c t="inlineStr"> <is> <t>Amount</t> </is> </c> </row> <row> <c t="inlineStr"> <is> <t>Jhon Smith</t> </is> </c> <c> <v>1000.74</v> </c> </row> <row> <c t="inlineStr"> <is> <t>Tracy A</t> </is> </c> <c> <v>6001.74</v> </c> </row> </sheetData> </worksheet> Excel 示例 工作表中的公式可以用函数<f>标签来完成 <c > <f>B2*0.08</f > </c > <c > <f>B2+C2</f > </c> and finally we zip them, rename it to.xlsx (using unix zip) set cmd ="cd "_fileDirectory_" && find . -type f | xargs zip .."_ext_xlsxFile 生成excel文件. 以下代码生成excel 文件. set file = "/temp/test.xlsx" set excelObj = ##class(XLSX.writer).%New(file) do excelObj.SetWorksheetName("test1") set status = excelObj.BeginWorksheet() set row = 0 set row = row+1 ;----------- excelObj.Cells(rowNumber,columnNumber,style,content) set status = excelObj.Cells(row,1,1,"Header1") set row = row+1 set status = excelObj.Cells(row,1,2,"Content 1") set status = excelObj.EndWorksheet() W !,excelObj.fileName 写Excel类请看这里 xlsx.writer.xml.zip
文章
姚 鑫 · 三月 17, 2021

第十二章 使用嵌入式SQL(一)

# 第十二章 使用嵌入式SQL(一) 可以将SQL语句嵌入InterSystemsIRIS®数据平台使用的ObjectScript代码中。这些嵌入式SQL语句在运行时转换为优化的可执行代码。 嵌入式SQL有两种: - 一个简单的嵌入式SQL查询只能返回单行中的值。简单嵌入式SQL还可以用于单行插入,更新和删除以及其他SQL操作。 - 基于游标的嵌入式SQL查询可以遍历查询结果集,并从多行中返回值。基于游标的嵌入式SQL也可以用于多行更新和删除SQL操作。 **注意:嵌入式SQL不能输入到`Terminal`命令行,也不能在`XECUTE`语句中指定。要从命令行执行SQL,请使用`$SYSTEM.SQL.Execute()`方法或SQL Shell接口。** # 编译嵌入式SQL 当包含嵌入式SQL的例程被编译时,嵌入式SQL不会被编译。 相反,嵌入式SQL的编译发生在SQL代码的第一次执行(运行时)。 第一次执行定义了一个可执行的缓存查询。 这与动态SQL的编译类似,在动态SQL中,直到执行SQL Prepare操作才编译SQL代码。 直到第一次执行例程,嵌入式SQL代码才会根据SQL表和其他实体进行验证。 因此,可以编译包含嵌入式SQL的持久化类的例程或方法,这些SQL引用在例程编译时不存在的表或其他SQL实体。 由于这个原因,大多数SQL错误是在运行时执行时返回的,而不是编译时返回的。 **在例程编译时,对嵌入式SQL执行SQL语法检查。 ObjectScript编译器失败,并为嵌入式SQL中的无效SQL语法生成编译错误。** 可以使用Management Portal SQL接口测试嵌入式SQL中指定的SQL实体是否存在,而不需要执行SQL代码。 这在验证嵌入式SQL代码中进行了描述,该代码既验证SQL语法,又检查是否存在SQL实体。 可以选择在运行时执行之前验证嵌入式SQL代码,方法是使用`/compileembedded=1`限定符编译包含嵌入式SQL代码的例程,如验证嵌入式SQL代码中所述。 **成功执行的嵌入式SQL语句将生成一个缓存的查询。该嵌入式SQL的后续执行将使用缓存的查询,而不是重新编译嵌入式SQL源。这提供了对嵌入式SQL的缓存查询的性能优势。** 当首次使用`OPEN`命令打开游标时,会执行基于游标的Embedded SQL语句的运行时执行。在执行的这一点上,将生成优化的缓存查询计划,如管理门户中的“ SQL语句”列表中所示。列出的“ SQL语句”位置是包含嵌入式SQL代码的例程的名称。请注意,执行嵌入式SQL不会在“缓存的查询”列表中生成一个条目。这些清单(带有类名称,例如`%sqlcq.USER.cls1`)是由Dynamic SQL查询创建的。 **注意:较早版本的IRIS中使用的`#SQLCompile Mode`预处理程序语句已被弃用。它已被解析,但不再对大多数嵌入式SQL命令执行任何操作。无论`#SQLCompile Mode`设置如何,大多数嵌入式SQL命令都会在运行时进行编译。但是,设置`#SQLCompile Mode = deferred`对于少量的嵌入式SQL命令仍然有意义,因为它会强制在运行时编译所有类型的嵌入式SQL命令。** ## 嵌入式SQL和宏预处理器 可以在方法内和触发器内(前提是它们已定义为使用ObjectScript)或在ObjectScript MAC例程内使用嵌入式SQL。 MAC例程由InterSystems IRIS宏预处理器处理,并转换为INT(中间)代码,随后将其编译为可执行的OBJ代码。这些操作是在包含嵌入式SQL的例程的编译时执行的,而不是在嵌入式SQL代码本身上执行的,嵌入式SQL代码本身直到运行时才进行编译。 **如果嵌入式SQL语句本身包含InterSystems IRIS宏预处理器语句(#命令,`##函`数或`$$macro`引用),则在编译例程时将编译这些语句,并在运行时将其提供给SQL代码。**这可能会影响包含ObjectScript代码主体的`CREATE PROCEDURE`,`CREATE FUNCTION`,`CREATE METHOD`,`CREATE QUERY`或`CREATE TRIGGER`语句。 ### 在嵌入式SQL中包含文件 嵌入式SQL语句要求它们引用的任何宏包含文件都必须在运行时加载到系统上。 因为嵌入式SQL的编译将推迟到首次引用之前进行,所以嵌入式SQL类的编译上下文将是运行时环境,而不是包含类或例程的编译时环境。如果运行时当前名称空间与包含例程的编译时名称空间不同,则编译时名称空间中的包含文件可能在运行时名称空间中不可见。在这种情况下,将发生以下情况: 1. 如果在运行时名称空间中看不到包含文件,则嵌入式SQL编译将删除所有包含文件。由于SQL编译很少需要包含文件,因此如果没有这些文件,运行时嵌入式SQL编译通常会成功。 2. 如果删除包含文件后编译失败,则InterSystems IRIS错误将报告例程编译时名称空间,嵌入式SQL运行时名称空间以及从运行时名称空间看不到的包含文件列表。 ### #SQLCompile宏指令 宏预处理器提供了三个与嵌入式SQL一起使用的预处理器指令: - `#SQLCompile Select`指定从`Select`语句返回时数据显示的格式,或者指定插入或更新语句时数据输入所需的格式,或者指定Select输入主机变量。 它支持以下6个选项:`Logical(默认值)`、`Display`、`ODBC`、`Runtime`、`Text(与Display相同)`和`FDBMS`(见下文)。 如果`#SQLCompile Select=Runtime`,可以使用`$SYSTEM.SQL.Util.SetOption("SelectMode",n)`方法来更改数据的显示方式。 `n`取值为`0=Logical`、`1=ODBC`、`2=Display`。 无论指定了`#SQLCompile Select`选项,`INSERT`或`UPDATE`都会自动将指定的数据值转换为相应的逻辑格式进行存储。 不管指定了`#SQLCompile Select`选项,`Select`都会自动将输入的主机变量值转换为谓词匹配的相应逻辑格式。 使用`#SQLCompile Select`进行查询显示如下示例所示。 这些示例显示`DOB`(出生日期)值,然后将`SelectMode`更改为`ODBC`格式,然后再次显示`DOB`。 在第一个例子中,改变`SelectMode`对显示没有影响; 在第二个示例中,因为`#SQLCompile Select=Runtime`,更改`SelectMode`将更改显示: ```java /// d ##class(PHA.TEST.SQL).EmbedSQL() ClassMethod EmbedSQL() { #SQLCompile Select=Display &sql(SELECT DOB INTO :a FROM Sample.Person) IF SQLCODEd ##class(PHA.TEST.SQL).EmbedSQL() 1st date of birth is 04/25/1990 2nd date of birth is 04/25/1990 ``` ```java /// d ##class(PHA.TEST.SQL).EmbedSQL1() ClassMethod EmbedSQL1() { #SQLCompile Select=Runtime &sql(SELECT DOB INTO :a FROM Sample.Person) IF SQLCODEd ##class(PHA.TEST.SQL).EmbedSQL1() 1st date of birth is 1990-04-25 2nd date of birth is 1990-04-25 ``` - 提供`#SQLCompile Select=FDBMS`是为了使嵌入式SQL能够以与`FDBMS`相同的方式格式化数据。 如果一个查询在`WHERE`子句中有一个常量值,`FDBMS`模式假定它是一个显示值,并使用`DisplayToLogical`转换对它进行转换。 如果一个查询在`WHERE`子句中有一个变量,`FDBMS`模式使用`FDBMSToLogical conversion`对它进行转换。 应该设计`FDBMS`转换方法来处理三种`FDBMS`变量格式:`Internal`、`Internal_$c(1)_External`和`$c(1)_External`。 如果查询选择一个变量,它将调用`LogicalToFDBMS`转换方法。 这个方法返回`Internal_$c(1)_External`。 - `#SQLCompile Path(或#Import)`指定模式搜索路径,用于解析`SELECT`、`CALL`、`INSERT`、`UPDATE`、`DELETE`和`TRUNCATE`表等数据管理命令中未限定的表、视图和存储过程名称。 如果没有指定模式搜索路径,或者在指定的模式中找不到表,InterSystems IRIS将使用默认模式。 数据定义语句如`ALTER TABLE`、`DROP VIEW`、`CREATE INDEX`或`CREATE TRIGGER`会忽略`#SQLCompile Path`和`#Import`。 数据定义语句使用默认模式来解析非限定名称。 - `#SQLCompile Audit`计是一个布尔开关,指定嵌入式SQL语句的执行是否应该记录在系统事件审计日志中。 # 嵌入式SQL语法 ## `&sql`指令 嵌入式SQL语句由`&sql()`指令与其余代码分开,如以下示例所示: ```java /// d ##class(PHA.TEST.SQL).EmbedSQL2() ClassMethod EmbedSQL2() { NEW SQLCODE,a WRITE "调用嵌入式SQL",! &sql(SELECT Name INTO :a FROM Sample.Person) IF SQLCODEd ##class(PHA.TEST.SQL).EmbedSQL2() 调用嵌入式SQL 名字是 Adams,Diane F. ``` 使用指定一个或多个主机变量的`INTO`子句返回结果。在这种情况下,主机变量名为:`a`。 **`&sql`指令不区分大小写;可以使用`&sql`,`&SQL`,`&Sql`等。 `&sql`指令必须后跟一个开放的括号,并且中间没有空格,换行符或注释。** `&sql`指令可以与标签在同一行上使用,如以下示例所示: ```java /// d ##class(PHA.TEST.SQL).EmbedSQL3() ClassMethod EmbedSQL3() { Mylabel &sql( SELECT Name INTO :a FROM Sample.Person ) } ``` **`&sql`指令的主体应包含一个有效的Embedded SQL语句,并用括号括起来。可以按照自己喜欢的任何方式设置SQL语句的格式:SQL会忽略空格和换行符。 Studio可以识别`&sql`指令,并使用可识别SQL的着色器对SQL代码语句进行语法着色。** **当宏预处理器遇到`&sql`指令时,它将随附的SQL语句交给SQL查询处理器。查询处理器返回执行查询所需的代码(ObjectScript INT格式)。然后,宏预处理器用此代码(或对包含该代码的标签的调用)替换`&sql`指令。在Studio中,可以根据需要查看生成的代码,方法是查看为类或例程生成的INT代码(使用“查看”菜单中的“查看其他代码”选项)。** 如果`&sql`指令包含无效的Embedded SQL语句,则宏预处理器会生成编译错误。无效的SQL语句可能具有语法错误,或者引用了在编译时不存在的表或列。 `&sql`指令可以在括号内的任何位置包含SQL样式的注释,可以不包含SQL代码,或仅包含注释文本。如果`&sql`指令不包含SQL代码或仅包含注释文本,则将该指令解析为无操作,并且未定义`SQLCODE变量`。 ```java NEW SQLCODE WRITE !,"Entering Embedded SQL" &sql() WRITE !,"Leaving Embedded SQL" ``` ```java NEW SQLCODE WRITE !,"Entering Embedded SQL" &sql(/* SELECT Name INTO :a FROM Sample.Person */) WRITE !,"Leaving Embedded SQL" ``` ## &sql替代语法 由于复杂的嵌入式SQL程序可能包含多个`&sql`指令(包括嵌套的`&sql`指令),因此提供了以下替代语法格式: - `## sql(...)`:此指令在功能上等同于`&sql`。它提供了另一种语法来使代码清晰。但是,它不能包含标记语法。 - `&sql (...)`:此伪指令允许指定多个`&sql`伪指令,并使用用户选择的标记字符或字符串标识每个伪伪指令。下一节将介绍此标记语法。 ## &sql标记语法 可以使用用户定义的标记语法来标识特定的`&sql`指令。该语法由在`“&sql”`和右括号之间指定的字符或字符串组成。在嵌入式SQL的结尾处,在右括号后必须立即显示此标记的相反内容。语法如下: ```java &sql( SQL statement ) ``` 请注意,在`&sql`,标记和右括号之间不允许有空格(空格,制表符或行返回),并且在右括号和反向标记之间不允许有空格。 **标记可以是单个字符或一系列字符。标记不能包含以下标点符号:** ```java ( + - / \ | * ) ``` 标记不能包含空格字符(空格,制表符或换行符)。它可能包含所有其他可打印字符和字符组合,包括Unicode字符。标记和反向标记区分大小写。 **相应的反向标记必须包含与反向标记相同的字符。例如:`&sqlABC(...)CBA`。** 如果标记包含[或{字符,则反向标记必须包含相应的]或}字符。以下是有效的`&sql`标记和反向标记对的示例: ```java &sql@@( ... )@@ &sql[( ... )] &sqltest( ... )tset &sql[Aa{( ... )}aA] ``` 选择标记字符或字符串时,请注意以下重要的SQL限制:SQL代码不能在代码中的任何位置(包括文字字符串和注释)包含字符序列`“)”`。例如,如果标记“ABC,则字符串`“)CBA”`不能出现在嵌入式SQL代码中的任何位置。如果发生这种情况,有效标记和有效SQL代码的组合将使编译失败。因此,在选择标记字符或字符串时要格外小心,以防止发生这种冲突,这一点很重要。 ## 嵌入式SQL和行偏移量 嵌入式SQL的存在会影响ObjectScript行偏移量,如下所示: - 嵌入式SQL在例程中的该点处将INT代码行的总数加(至少)2。因此,嵌入式SQL的单行计为3行,嵌入式SQL的两行计为4行,依此类推。调用其他代码的嵌入式SQL可以向INT代码添加更多行。 一个虚拟的嵌入式SQL语句,仅包含一个注释,算作2条INT代码行,如以下示例所示:`&sql(/ *供将来使用* /)`。 - 嵌入式SQL中的所有行都计为行偏移,包括注释和空白行。 可以使用`^ROUTINE`全局显示INT代码行。
文章
Jingwei Wang · 一月 19, 2023

介绍使用嵌入式 Python 进行 Web 抓取

什么是网页抓取: 简单来说,网络抓取、网络收获或网络数据提取是从网站收集大数据(非结构化)的自动化过程。用户可以根据需要提取特定站点上的所有数据或特定数据。收集的数据可以以结构化格式存储以供进一步分析。 网页抓取涉及的步骤: 找到您要抓取的网页的 URL 通过检查选择特定元素 编写代码获取被选元素的内容 以需要的格式存储数据 就这么简单! 用于网络抓取的流行库/工具是: Selenium – 用于测试 Web 应用程序的框架 BeautifulSoup – 用于从 HTML、XML 和其他标记语言中获取数据的 Python 库 Pandas – 用于数据操作和分析的 Python 库 什么是Beautiful Soup? Beautiful Soup 是一个纯 Python 库,用于从网站中提取结构化数据。它允许您解析来自 HTML 和 XML 文件的数据。它充当辅助模块,并以与使用其他可用开发人员工具以网页交互的方式与 HTML 交互。 它通常可以为程序员节省数小时或数天的工作时间,因为它可以与您最喜欢的解析器(如 lxml 和 html5lib)一起使用,以提供 Python organic方式来导航、搜索、并修改解析树。 beautiful soup 的另一个强大而有用的功能是它可以智能地将正在获取的文档转换为 Unicode 并将传出文档转换为 UTF-8。作为一名开发人员,您不必关心这一点,除非文档内在未指定编码或 Beautiful Soup 无法检测到编码。 与其他一般解析或抓取技术相比,它也被认为更快。 在今天的文章中,我们将使用带有对象脚本的嵌入式 Python 来抓取 ae.indeed.com 上的 Python 职位空缺和公司 第 1 步 -找到您要抓取的网页的 URL。 url = https://ae.indeed.com/jobs?q=python&l=Dubai&start=0 我们要从中抓取数据的网页看起来像这样 为了简单和学习目的,我们将提取“职位”和“公司”,输出将类似于下面的屏幕截图。 我们将使用两个 python 库。 requests :Requests 是 Python 编程语言的 HTTP 库。该项目的目标是使 HTTP 请求更简单、更人性化。 bs4 for BeautifulSoup :BeautifulSoup 是一个用于解析 HTML 和 XML 文档的 Python 包。它为已解析的页面创建一个解析树,可用于从 HTML 中提取数据,这对于网络抓取很有用。 让我们安装这个 python 包 (windows) irispip install --target C:\InterSystems\IRISHealth\mgr\python bs4 irispip install --target C:\InterSystems\IRISHealth\mgr\python requests 让我们将 python 库导入到 ObjectScript Class PythonTesting.WebScraper Extends %Persistent { // pUrl = https://ae.indeed.com/jobs?q=python&l=Dubai&start= // pPage = 0 ClassMethod ScrapeWebPage(pUrl, pPage) { // imports the requests python library set requests = ##class ( %SYS.Python ).Import( "requests" ) // import the bs4 python library set soup = ##class ( %SYS.Python ).Import( "bs4" ) // import builtins package which contains all of the built-in identifiers set builtins = ##class ( %SYS.Python ).Import( "builtins" ) } 让我们使用请求收集 html 数据;注意:我们从谷歌搜索“my user agent”中获取的用户代理url为“https://ae.indeed.com/jobs?q=python&l=Dubai&start=”,pPage为页码 我们将使用 requests 对 URL 执行 http get 请求并将响应存储在“req” set headers = { "User-Agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36" } set url = "https://ae.indeed.com/jobs?q=python&l=Dubai&start=" _pPage set req = requests.get(url, "headers=" _headers) req 对象将具有从网页返回的 html。 让我们通过 BeautifulSoup html 解析器运行它,以便我们可以提取作业数据。 set soupData = soup.BeautifulSoup(req.content, "html.parser" ) set title = soupData.title.text W !,title 标题看起来如下 第二步:通过检查选择需要的元素。 在这种情况下,我们对通常位于 <div> 标记中的作业列表感兴趣,在您的浏览器中,您可以检查该元素以找到 div 类。在我们的例子中,所需的信息存储在 <div class="cardOutline tapItem ... </div> 第三步:编写获取选中元素内容的代码 我们将使用 BeautifulSoup 上的 find_all 功能来查找所有包含类名“cardOutline”的 <div> 标签 //parameters to python would be sent as a python dictionary set divClass = { "class" : "cardOutline" } set divsArr = soupData. "find_all" ( "div" ,divClass...) 这将返回一个列表,我们可以遍历该列表并提取职位和公司 第 4 步:以所需格式存储/显示数据。 在下面的示例中,我们将数据写入终端。 set len = builtins.len(divsArr) W !, "Job Title" , $C ( 9 )_ " --- " _ $C ( 9 ), "Company" for i = 1 : 1 :len { Set item = divsArr. "__getitem__" (i - 1 ) set title = $ZSTRIP (item.find( "a" ).text, "<>W" ) set companyClass = { "class_" : "companyName" } set company = $ZSTRIP (item.find( "span" , companyClass...).text, "<>W" ) W !,title, $C ( 9 ), " --- " , $C ( 9 ),company } 请注意,我们正在使用 builtins.len() 来获取 divsArr 列表的长度 标识符名称:命名标识符的规则在 ObjectScript 和 Python 之间是不同的。例如,Python 方法名中允许使用下划线 (_),实际上广泛用于所谓的“dunder”方法和属性(“dunder”是“双下划线”的缩写),例如 __getitem__ 或 __class__ .要从 ObjectScript 使用此类标识符,请将它们括在双引号中:关于标识符名称的系统间文档 类方法示例 类方法 ScrapeWebPage(pUrl, pPage) // pUrl = https://ae.indeed.com/jobs?q=python&l=Dubai&start= // pPage = 0 ClassMethod ScrapeWebPage(pUrl, pPage) { set requests = ##class ( %SYS.Python ).Import( "requests" ) set soup = ##class ( %SYS.Python ).Import( "bs4" ) set builtins = ##class ( %SYS.Python ).Builtins() set headers = { "User-Agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36" } set url = pUrl_pPage set req = requests.get(url, "headers=" _headers) set soupData = soup.BeautifulSoup(req.content, "html.parser" ) set title = soupData.title.text W !,title set divClass = { "class_" : "cardOutline" } set divsArr = soupData. "find_all" ( "div" ,divClass...) set len = builtins.len(divsArr) W !, "Job Title" , $C ( 9 )_ " --- " _ $C ( 9 ), "Company" for i = 1 : 1 :len { Set item = divsArr. "__getitem__" (i - 1 ) set title = $ZSTRIP (item.find( "a" ).text, "<>W" ) set companyClass = { "class_" : "companyName" } set company = $ZSTRIP (item.find( "span" , companyClass...).text, "<>W" ) W !,title, $C ( 9 ), " --- " , $C ( 9 ),company } } 下一步.. 使用对象脚本和嵌入式 python 以及几行代码;我们可以很容易地从我们最喜欢的工作网站上抓取数据,收集工作名称、公司、薪水、工作描述和电子邮件/链接。例如,如果您有多个页面,您可以使用页面轻松遍历它们可以将此数据添加到 pandas 数据框并删除重复项,可以根据您感兴趣的特定关键字应用过滤器。通过 numpy 运行此数据,并获得一些线图或者对数据执行 One-Hot 编码,并创建/训练您的 ML 模型,如果有您感兴趣的特定职位空缺,请向自己发送通知。 😉快乐编码! 别忘了按赞按钮😃
文章
Lilian Huang · 四月 16

Iris-AgenticAI: 基于OpenAI智能体SDK驱动的企业自动化多智能体工作流平台

Hi 大家好在本文中,我讲介绍我的应用 iris-AgenticAI . 代理式人工智能的兴起标志着人工智能与世界互动方式的变革性飞跃--从静态响应转变为动态、目标驱动的问题解决方式。参看 OpenAI’s Agentic SDK , OpenAI Agents SDK使您能够在一个轻量级、易用且抽象程度极低的软件包中构建代理人工智能应用程序。它是我们之前的代理实验 Swarm 的生产就绪升级版。 该应用展示了下一代自主人工智能系统,这些系统能够进行推理、协作,并以类似人类的适应能力执行复杂任务。 应用功能 Agent Loop 🔄 一个内置循环,可自主管理工具的执行,将结果发回 LLM,并迭代直至任务完成。 Python-First 🐍 利用本地 Python 语法(装饰器、生成器等)来协调和连锁代理,而无需外部 DSL。 Handoffs 🤝 通过在专业代理之间委派任务,无缝协调多代理工作流程。 Function Tools ⚒️ 用 @tool 修饰任何 Python 函数,可立即将其集成到代理的工具包中。 Vector Search (RAG) 🧠 原生集成向量存储(IRIS),用于 RAG 检索。 Tracing 🔍 内置跟踪功能,可实时可视化、调试和监控代理工作流(想想 LangSmith 的替代方案)。 MCP Servers 🌐 通过 stdio 和 HTTP 支持模型上下文协议(MCP),实现跨进程代理通信。 Chainlit UI 🖥️ 集成 Chainlit 框架,可使用最少的代码构建交互式聊天界面。 Stateful Memory 🧠 跨会话保存聊天历史、上下文和代理状态,以实现连续性和长期任务。 代理 代理是应用程序的核心构件。代理是一个大型语言模型(LLM),配置有指令和工具。基本配置您需要配置的代理最常见的属性,包括: Instructions (说明):也称为开发人员信息或系统提示。model:要使用的 LLM,以及可选的 model_settings,用于配置温度、top_p 等模型调整参数。tools工具: 代理用来完成任务的工具。 from agents import Agent, ModelSettings, function_tool @function_tool def get_weather(city: str) -> str: return f"The weather in {city} is sunny" agent = Agent( name="Haiku agent", instructions="Always respond in haiku form", model="o3-mini", tools=[get_weather], ) 运行代理 您可以通过 Runner 类运行代理。您有 3 个选项: 1.Runner.run():异步运行并返回 RunResult。2.Runner.run_sync(),这是一种同步方法,只是在引擎盖下运行 .run()。3.Runner.run_streamed():异步运行并返回 RunResultStreaming。它以流式模式调用 LLM,并在接收到事件时将其流式传输给您。 from agents import Agent, Runner async def main(): agent = Agent(name="Assistant", instructions="You are a helpful assistant") result = await Runner.run(agent, "Write a haiku about recursion in programming.") print(result.final_output) # Code within the code, # Functions calling themselves, # Infinite loop's dance. 代理架构 该应用程序由 7 个专业代理组成: 1. 分诊代理 🤖 角色: 主要路由器,接收用户输入,并通过切换分配任务 示例: 路由 “显示生产错误” → IRIS 生产代理 2.矢量搜索代理 🤖 作用: 提供 IRIS 2025.1 版本说明详情(RAG 功能) 示例: 路由 “向我提供发行说明摘要”→矢量搜索代理 3. IRIS 仪表板代理 🤖功能: 提供实时管理门户指标:明文副本。 ApplicationErrors, CSPSessions, CacheEfficiency, DatabaseSpace, DiskReads, DiskWrites, ECPAppServer, ECPDataServer, GloRefs, JournalStatus, LicenseCurrent, LockTable, Processes, SystemUpTime, WriteDaemon, [...] 4. IRIS Running Process Agent 🤖 功能: 监控活动进程的详细信息: Process ID | Namespace | Routine | State | PidExternal 5. IRIS Production Agent 🤖 角色: 提供生产详情以及启动和停止生产的功能。 6. WebSearch Agent 🤖 功能: 通过 API 集成执行上下文网络搜索 7.Order Agent 🤖 功能: 使用订单 ID 检索订单状态 交接 交接允许代理将任务委托给另一个代理。这在不同代理擅长不同领域的情况下尤其有用。例如,客户支持应用程序可能会有专门处理订单状态、退款、常见问题等任务的代理。 分流代理是我们的主代理,它会根据用户输入将任务分配给另一个代理 #TRIAGE AGENT, Main agent receives user input and delegates to other agent by using handoffs triage_agent = Agent( name="Triage agent", instructions=( "Handoff to appropriate agent based on user query." "if they ask about Release Notes, handoff to the vector_search_agent." "If they ask about production, handoff to the production agent." "If they ask about dashboard, handoff to the dashboard agent." "If they ask about process, handoff to the processes agent." "use the WebSearchAgent tool to find information related to the user's query and do not use this agent is query is about Release Notes." "If they ask about order, handoff to the order_agent." ), handoffs=[vector_search_agent,production_agent,dashboard_agent,processes_agent,order_agent,web_search_agent] ) 跟踪 Agents SDK 包括内置跟踪功能,可收集代理运行期间事件的全面记录: LLM 生成、工具调用、切换、防护栏,甚至发生的自定义事件。使用跟踪仪表板,您可以在开发和生产过程中调试、可视化和监控工作流。https://platform.openai.com/logs 应用界面 应用工作流程矢量搜索代理 矢量搜索代理自动获取 New in InterSystems IRIS 2025.1 如果数据还不存在,只需将文本信息输入 IRIS 矢量存储区一次。 使用下面的查询来获取数据 SELECT id, embedding, document, metadata FROM SQLUser.AgenticAIRAG 分流代理接收用户输入,将问题转给矢量搜索代理。 IRIS 仪表盘代理 分流代理接收用户输入,将问题路由到 IRIS 仪表板代理。 IRIS 流程代理分流代理接收用户输入,将问题路由到 IRIS 流程代理。IRIS 生产代理 使用生产代理启动和停止生产。使用生产代理获取生产详情。 本地代理 分流代理接收用户输入,将问题转给本地订单代理。WebSearch 代理 在这里,分流代理接收到两个问题,并将两个问题都路由到 WebSearcg 代理。 MCP Server 应用 MCP Server在这里运行 https://localhost:8000/sse 下面是启动 MCP 服务器的代码: import os import shutil import subprocess import time from typing import Any from dotenv import load_dotenv load_dotenv() #Get OPENAI Key, if not fond in .env then get the GEIMINI API KEY #IF Both defined then take OPENAI Key openai_api_key = os.getenv("OPENAI_API_KEY") if not openai_api_key: raise ValueError("OPENAI_API_KEY is not set. Please ensure to defined in .env file.") if __name__ == "__main__": # Let's make sure the user has uv installed if not shutil.which("uv"): raise RuntimeError( "uv is not installed. Please install it: https://docs.astral.sh/uv/getting-started/installation/" ) # We'll run the SSE server in a subprocess. Usually this would be a remote server, but for this # demo, we'll run it locally at http://localhost:8000/sse process: subprocess.Popen[Any] | None = None try: this_dir = os.path.dirname(os.path.abspath(__file__)) server_file = os.path.join(this_dir, "MCPserver.py") print("Starting SSE server at http://localhost:8000/sse ...") # Run `uv run server.py` to start the SSE server process = subprocess.Popen(["uv", "run", server_file]) # Give it 3 seconds to start time.sleep(3) print("SSE server started. Running example...\n\n") except Exception as e: print(f"Error starting SSE server: {e}") exit(1) MCP 服务器配备了以下工具: 提供 IRIS 2025.1 发行说明详情(矢量搜索) IRIS 信息工具 检查天气工具 查找暗语工具(本地功能) 加法工具(本地功能) MCP 应用在这里运行 http://localhost:8001 MCP 服务器矢量搜索(RAG)功能 MCP 服务器配备 InterSystems IRIS 向量搜索摄取功能和检索增强生成 (RAG) 功能。 MCP Server other functionality The MCP Server dynamically delegates tasks to the appropriate tool based on user input. 更多详情,请访问 iris-AgenticAI open exchange 界面。 谢谢!
文章
姚 鑫 · 三月 7, 2021

第五章 SQL定义表(二)

# 第五章 SQL定义表(二) # 主键 InterSystems IRIS提供了两种方法来唯一标识表中的行:`RowID`和主键。 可选的主键是一个有意义的值,应用程序可以使用该值唯一地标识表中的行(例如,联接中的行)。主键可以是用户指定的数据字段,也可以是多个数据字段的组合。主键值必须是唯一的,但不必是整数值。 `RowID`是一个内部用于标识表中行的整数值。通常,主键是由应用程序生成的值,而`RowID`是由InterSystems IRIS生成的唯一整数值。 系统会自动创建一个主map,以使用`RowID`字段访问数据行。如果定义主键字段,系统将自动创建并维护主键索引。 显然,具有两个不同的字段和索引来标识行的双重性不一定是一件好事。可以通过以下两种方式之一解析为单个行标识符和索引: - 使用应用程序生成的主键值作为`IDKEY`。 可以通过使用关键字`PrimaryKey`和`IdKey`在类定义中标识主键索引来实现这一点(如果为此目的设置了`PKey is IdKey`标志,也可以在DDL中实现这一点)。 这使得主键索引成为表的主映射。 因此,主键将被用作行的主要内部地址。 如果主键包含多个字段,或者主键值不是整数,那么这种方法的效率会较低。 - 不要使用应用程序生成的主键值,而应在应用程序中使用系统生成的`RowID`整数作为应用程序使用的主键(例如,在`joins`中)。这样做的好处是,整数`RowID`有助于进行更有效的处理,包括使用位图索引。 根据应用程序的性质,可能希望解析为单个行标识符和索引,或者为应用程序生成的主键和系统生成的`RowID`具有单独的索引。 # RowVersion,AutoIncrement和串行计数器字段 InterSystems SQL支持三种专用数据类型,用于自动增加计数器值。这三种数据类型都是扩展`%Library.BigInt`数据类型类的子类。 - `%Library.RowVersion`:计算在命名空间范围内所有`RowVersion`表的插入和更新。只有在包含`ROWVERSION`字段的表中进行插入和更新时,此计数器才会递增。 `ROWVERSION`值是唯一的且不可修改。此名称空间范围的计数器永远不会重置。 - `%Library.Counter`(也称为SERIAL计数器字段):对表中的插入进行计数。默认情况下,此字段接收一个自动递增的整数。但是,用户可以为此字段指定一个非零的整数值。用户可以指定重复值。如果用户提供的值大于系统提供的最高值,则将自动递增计数器设置为从用户指定的值开始递增。 - `%Library.AutoIncrement`:计数插入到表中的次数。默认情况下,此字段接收一个自动递增的整数。但是,用户可以为此字段指定一个非零的整数值。用户可以指定重复值。指定用户值对自动增量计数器无效。 这三个字段以及`IDENTITY`字段均返回`AUTO_INCREMENT = YES`,如以下示例所示: ```java SELECT COLUMN_NAME,AUTO_INCREMENT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'MyTable' ``` ## RowVersion Field `RowVersion`字段是一个可选的用户定义字段,它提供行级版本控制,使可以确定对每个命名空间范围内的行中的数据进行更改的顺序。 InterSystems IRIS维护一个整个命名空间范围的计数器,并在每次修改行数据(插入,更新或`%Save`)时向该字段分配一个唯一的增量正整数。因为此计数器是整个名称空间范围的,所以对具有`ROWVERSION`字段的一个表进行的操作将设置`ROWVERSION`计数器的增量点,该值将用于同一名称空间中具有`ROWVERSION`字段的所有其他表。 通过指定数据类型为`ROWVERSION(%Library.RowVersion)`的字段来创建`RowVersion`字段。每个表只能指定一个`ROWVERSION`数据类型字段。尝试创建具有多个`ROWVERSION`字段的表会导致5320编译错误。 该字段可以具有任何名称,并且可以出现在任何列位置。 `ROWVERSION(%Library.RowVersion)`数据类型映射到`BIGINT(%Library.BigInt)`。 此字段从自动递增计数器接收一个从1开始的正整数。只要通过插入,更新或`%Save`操作修改了任何启用`ROWVERSION`的表中的数据,此计数器就会递增。递增的值记录在已插入或更新的行的`ROWVERSION`字段中。 名称空间可以包含具有`RowVersion`字段的表和不具有该字段的表。仅对具有`RowVersion`字段的表的数据更改会增加整个命名空间范围的计数器。 当用数据填充表时,InterSystems IRIS会为每个插入的行将此字段分配连续的整数。如果使用`ALTER TABLE`将`ROWVERSION`字段添加到已经包含数据的表中,则该字段将被创建为`NULL`以用于预先存在的字段。对该表的任何后续插入或更新都会为该行的`RowVersion`字段分配一个顺序整数。该字段是只读的;尝试修改`RowVersion`值会生成`SQLCODE -138`错误:无法为只读字段插入/更新值。因此,`RowVersion`字段被定义为唯一且不可修改,但不是必需字段或非`null`。 `RowVersion`值始终递增。它们不被重用。因此,插入和更新按时间顺序分配唯一的`RowVersion`值。删除操作从该序列中删除数字。因此,`RowVersion`值可能在数字上不连续。 此计数器永远不会重置。删除所有表数据不会重置`RowVersion`计数器。即使删除名称空间中包含`ROWVERSION`字段的所有表,也不会重置此计数器。 `RowVersion`字段不应包含在唯一键或主键中。 `RowVersion`字段不能是`IDKey`索引的一部分。 分片表不能包含`RowVersion`字段。 `RowVersion`字段未隐藏(通过`SELECT *`显示)。 在同一名称空间中的三个表的以下示例中显示了这一点。 1. 创建`表1`和`表3`,每个都有一个`ROWVERSION`字段,并创建表2没有一个`ROWVERSION`字段。 2. 在`Table1`中插入十行。这些行的`ROWVERSION`值是接下来的十个计数器增量。由于以前未使用过计数器,因此它们是1到10。 3. 在`Table2`中插入十行。由于`Table2`没有`ROWVERSION`字段,因此计数器不会增加。 4. 更新`表1`的行。该行的`ROWVERSION`值将更改为下一个计数器增量(在这种情况下为11)。 5. 在`Table3`中插入十行。这些行的`ROWVERSION`值是接下来的十个计数器增量(12到21)。 6. 更新`表1`的行。该行的`ROWVERSION`值更改为下一个计数器增量(在这种情况下为22)。 7. 删除`表1`的行。 `ROWVERSION`计数器不变。 8. 更新`Table3`的一行。该行的`ROWVERSION`值将更改为下一个计数器增量(在这种情况下为23)。 ## Serial Counter Field 可以使用`SERIAL`数据类型(在持久性类表定义中为`%Library.Counter`)来指定一个或多个可选的整数计数器字段,以记录在表中插入记录的顺序。每个串行计数器字段都维护自己的独立计数器。 每当将一行插入表中时,串行计数器字段都会从其自动增量计数器接收一个正整数,该行没有提供任何值(`NULL`)或值为0。但是,用户可以指定非零整数值插入期间针对此字段的值,将覆盖表计数器的默认值。 - 如果`INSERT`没有为计数器字段指定非零整数值,则计数器字段将自动接收正整数计数器值。计数从1开始。每个连续值都是从为此字段分配的最高计数器值开始的1增量。 - 如果`INSERT`为`counter`字段指定了一个非零的整数值,则该字段将接收该值。它可以是正整数或负整数,可以低于或高于当前计数器值,并且可以是已经分配给该字段的整数。如果该值大于任何分配的计数器值,它将自动增量计数器的增量起始点设置为该值。 尝试更新计数器字段值会导致`SQLCODE -105`错误。 `TRUNCATE TABLE`命令将该计数器重置为1。即使使用`DELETE`命令删除表中的所有行,也不会通过DELETE命令将其重置。 分片表不能包含串行计数器字段。 ## AutoIncrement Field 可以使用`%Library.AutoIncrement`数据类型(或`BIGINT AUTO_INCREMENT`)来指定一个整数计数器字段,以记录在表中插入记录的顺序。每个表只能指定一个`%AutoIncrement`数据类型字段。每当将一行插入表中时,此字段都会从自动增量计数器接收一个正整数,该行没有提供任何值(`NULL`)或值为0。但是,用户可以为此指定非零整数值插入过程中的字段,将覆盖表计数器的默认值。 - 如果`INSERT`没有为计数器字段指定非零整数值,则计数器字段将自动接收正整数计数器值。计数从1开始。每个连续值都是从为此字段分配的最高计数器值开始的1增量。 - 如果`INSERT`为`counter`字段指定了一个非零的整数值,则该字段将接收该值。它可以是正整数或负整数,可以低于或高于当前计数器值,并且可以是已经分配给该字段的整数。用户分配的值对自动增量计数器无效。 尝试更新计数器字段值会导致`SQLCODE -105`错误。 `TRUNCATE TABLE`命令将该计数器重置为1。即使使用`DELETE`命令删除表中的所有行,也不会通过`DELETE`命令将其重置。 分片表可以包含一个`AutoIncrement`字段。 # 通过创建持久性类来定义表 在InterSystems IRIS中定义表的主要方法是使用Studio创建持久性类定义。当这些类在InterSystems IRIS数据库中保存并编译时,它们会自动投影到与类定义相对应的关系表中:每个类代表一个表;每个类代表一个表。每个属性代表一列,依此类推。可为一个类(表)定义的属性(列)的**最大数量为1000**。 例如,以下定义了持久类`MyApp.Person`: ```java Class MyApp.Person Extends %Persistent { Property Name As %String(MAXLEN=50) [Required]; Property SSN As %String(MAXLEN=15) [InitialExpression = "Unknown"]; Property DateOfBirth As %Date; Property Sex As %String(MAXLEN=1); } ``` 编译后,这将在MyApp模式中创建`MyApp.Person`持久类和相应的SQL表`Person`。 在此示例中,指定了程序包名称`MyApp`。定义持久类时,未指定的程序包名称默认为`User`。这对应于默认的SQL模式名称`SQLUser`。例如,将名为`“Students”`的表定义为持久类将创建类`User.Students`,以及相应的`SQL schema.table`名称`SQLUser.Students`。 在此示例中,持久类名称`Person`是默认的SQL表名称。可以使用`SqlTableName`类关键字来提供其他SQL表名称。 可以使用`DDL CREATE TABLE`语句(指定`SQL schema.table`名称)定义相同的`MyApp.Person`表。成功执行此SQL语句会生成一个相应的持久性类,其包名称为MyApp,类名称为Person: ```java CREATE TABLE MyApp.Person ( Name VARCHAR(50) NOT NULL, SSN VARCHAR(15) DEFAULT 'Unknown', DateOfBirth DATE, Sex VARCHAR(1) ) ``` `CREATE TABLE`在相应的类定义中未指定显式的`StorageStrategy`。相反,它将采用已定义的默认存储策略。 默认情况下,`CREATE TABLE`在相应的类定义中指定`Final class`关键字,指示它不能具有子类。 请注意,诸如上图所示的持久性类定义在编译时会创建相应的表,但是无法使用SQL DDL命令(或通过使用Management Portal Drop操作)来修改或删除此表定义,这会向显示消息“未为类'schema.name'启用DDL ...”)。必须在表类定义中指定[DdlAllowed]才能进行以下操作: ```java Class MyApp.Person Extends %Persistent [DdlAllowed] ``` 可以在类定义中指定`%Populate`以启用使用测试数据自动填充表。 ```java Class MyApp.Person Extends (%Persistent,%Populate) [DdlAllowed] ``` 这为该类提供了`Populate()`方法。运行此方法将在表中填充十行测试数据。 ## 定义数据值参数 每个属性(字段)定义都必须指定一个数据类型类,该类指定该属性所基于的类。指定的数据类型将字段的允许数据值限制为该数据类型。定义投影到表的持久类时,必须使用`%Library`包中的类指定此数据类型。可以将此类指定为`%Library.Datatype`或`%Datatype`。 许多数据类型类提供的参数使可以进一步定义允许的数据值。这些参数特定于单个数据类型。以下是一些较常见的数据定义参数: - 数据值物理限制 - 允许的数据值:枚举或模式匹配 - 通过定义唯一索引来唯一数据值 - 通过定义`SqlComputeCode`计算数据值 ### 数据值限制 **对于数字数据类型,可以指定`MAXVAL`和`MINVAL`参数以限制允许值的范围。根据定义,数字数据类型具有最大支持值(正数和负数)。可以使用`MAXVAL`和`MINVAL`进一步限制允许的范围。** 对于字符串数据类型,可以指定`MAXLEN`和`MINLEN`参数以限制允许的长度(以字符为单位)。根据定义,字符串数据类型具有最大支持的长度。可以使用`MAXLEN`和`MINLEN`进一步限制允许的范围。默认情况下,超过`MAXLEN`的数据值会生成字段验证错误:`INSERT`的`SQLCODE -104`或`UPDATE`的`SQLCODE -105`。可以指定`TRUNCATE = 1`以允许超过`MAXLEN`的字符串数据值。指定的字符串将被截断为`MAXLEN`长度。 ### 允许的数据值 可以通过两种方式限制实际数据值: - **允许值的列表(带有`VALUELIST`和`DISPLAYLIST`的枚举值)。** - **允许值的匹配模式(`PATTERN`)。** #### 枚举值 通过将表定义为持久类,可以定义仅包含某些指定值的属性(字段)。这是通过指定`VALUELIST`参数来完成的。 `VALUELIST`(指定逻辑存储值的列表)通常与`DISPLAYLIST`(指定相应的显示值的列表)一起使用。这两个列表都以列表定界符开头。几种数据类型可以指定`VALUELIST`和`DISPLAYLIST`。下面的示例定义两个带有枚举值的属性: ```java Class Sample.Students Extends %Persistent { Property Name As %String(MAXLEN=50) [Required]; Property DateOfBirth As %Date; Property ChoiceStr As %String(VALUELIST=",0,1,2",DISPLAYLIST=",NO,YES,MAYBE"); Property ChoiceODBCStr As %EnumString(VALUELIST=",0,1,2",DISPLAYLIST=",NO,YES,MAYBE"); } ``` 如果指定了`VALUELIST`,则`INSERT`或`UPDATE`只能指定`VALUELIST`中列出的值之一,或者不提供值(`NULL`)。 `VALUELIST`有效值区分大小写。指定与`VALUELIST`值不匹配的数据值会导致字段值验证失败:`INSERT`的`SQLCODE -104`或`UPDATE`的`SQLCODE -105`。 在ODBC模式下显示时,`%String`和`%EnumString`数据类型的行为不同。使用上面的示例,当以逻辑模式显示时,`ChoiceStr`和`ChoiceODBCStr`都显示其`VALUELIST`值。在“显示”模式下显示时,`ChoiceStr`和`ChoiceODBCStr`均显示其DISPLAYLIST值。当以ODBC模式显示时,`ChoiceStr`显示`VALUELIST`值;否则显示`VALUELIST`值。 `ChoiceODBCStr`显示`DISPLAYLIST`值。 #### 值的模式匹配 几种数据类型可以指定`PATTERN`参数。 `PATTERN`将允许的数据值限制为与指定的ObjectScript模式匹配的数据值,指定为带引号的字符串,省略前导问号。以下示例使用模式定义属性: ```java Class Sample.Students Extends %Persistent { Property Name As %String(MAXLEN=50) [Required]; Property DateOfBirth As %Date; Property Telephone As %String(PATTERN = "3N1""-""3N1""-""4N"); } ``` 由于将模式指定为带引号的字符串,因此模式中指定的文字必须将其双引号引起来。请注意,模式匹配是在`MAXLEN`和`TRUNCATE`之前应用的。因此,如果为可能超过`MAXLEN`并被截断的字符串指定了一个模式,则可能希望以“ `.E`”(任何类型的尾随字符数不限)结束该模式。 与`PATTERN`不匹配的数据值会生成字段验证错误:`INSERT`的`SQLCODE -104`或`UPDATE`的`SQLCODE -105`。 ### 唯一值 `CREATE TABLE`允许将字段定义为`UNIQUE`。这意味着每个字段值都是唯一(非重复)值。 将表定义为持久类不支持相应的`uniqueness`属性关键字。相反,必须同时定义属性和该属性的唯一索引。下面的示例为每个记录提供唯一的Num值: ```java Class Sample.CaveDwellers Extends %Persistent [ DdlAllowed ] { Property Num As %Integer; Property Troglodyte As %String(MAXLEN=50); Index UniqueNumIdx On Num [ Type=index,Unique ]; } ``` 索引名称遵循属性的命名约定。可选的`Type`关键字指定索引类型。 `Unique`关键字将属性(字段)定义为唯一。 使用`INSERT`或`UPDATE`语句时,必须具有唯一的值字段。 ### 计算值 下面的类定义示例定义一个表,该表包含一个字段(生日),该字段在最初设置`DateOfBirth`字段值时使用`SqlComputed`来计算其值,而在更新`DateOfBirth`字段值时使用`SqlComputeOnChange`来重新计算其值。 `Birthday`字段值包括当前时间戳,以记录该字段值的计算/重新计算时间: ```java Class Sample.MyStudents Extends %Persistent [DdlAllowed] { Property Name As %String(MAXLEN=50) [Required]; Property DateOfBirth As %Date; Property Birthday As %String [ SqlComputeCode = {SET {Birthday}=$PIECE($ZDATE({DateOfBirth},9),",")_ " changed: "_$ZTIMESTAMP}, SqlComputed, SqlComputeOnChange = DateOfBirth ]; } ```java 请注意,对`DateOfBirth`的`UPDATE`指定现有的`DateOfBirth`值不会重新计算`Birthday`字段值。 ## 嵌入式对象(%SerialObject) 可以通过引用定义属性的嵌入式串行对象类来简化持久表的结构。例如,希望`MyData.Person`包含地址信息,包括街道,城市,州和邮政编码。可以定义一个定义这些属性的串行对象(`%SerialObject`)类,而不是在`MyData.Person`中指定这些属性,然后在`MyData.Person`中指定一个引用该嵌入式对象的`Home`属性。在以下类定义中显示了这一点: ```java Class MyData.Person Extends (%Persistent) [ DdlAllowed ] { Property Name As %String(MAXLEN=50); Property Home As MyData.Address; Property Age As %Integer; } ``` ```java Class MyData.Address Extends (%SerialObject) { Property Street As %String; Property City As %String; Property State As %String; Property PostalCode As %String; } ``` 不能直接访问串行对象属性中的数据,必须通过引用它的持久类/表访问它们: - 要从持久性表中引用单个串行对象属性,请使用下划线。例如,`SELECT名称Home_State FROM MyData.Person`返回状态串行对象属性值作为字符串。串行对象属性值以查询中指定的顺序返回。 - 要引用持久性表中的所有串行对象属性,请指定引用字段。例如,`SELECT Home FROM MyData.Person`以`%List`结构形式返回所有`MyData.Address`属性的值。串行对象属性值以串行对象中指定的顺序返回:`Home_Street,Home_City,Home_State,Home_PostalCode。在Management Portal SQL`界面“目录详细信息”中,此引用字段称为“容器”字段。这是一个Hidden字段,因此`SELECT *`语法不返回。 - 持久类的`SELECT *`单独返回所有串行对象属性,包括嵌套的串行对象。例如,`SELECT * FROM MyData.Person`返回`Age,Name,Home_City,Home_PostalCode,Home_State和Home_Street`值(按此顺序);它不返回`Home%List`结构值。串行对象属性值以排序顺序返回。 `SELECT *`首先按排序顺序(通常按字母顺序)列出持久性类中的所有字段,然后按排序顺序列出嵌套的串行对象属性。 请注意,嵌入式串行对象不必与引用它的持久性表位于同一程序包中。 定义嵌入式对象可以简化持久性表定义: - 持久表可以包含多个属性,这些属性引用同一嵌入式对象中的不同记录。例如,`MyData.Person`表可以包含`Home`和`Office`属性,这两个属性均引用`MyData.Address`串行对象类。 - 多个持久表可以引用同一嵌入式对象的实例。例如,`MyData.Person`表的`Home`属性和`MyData.Employee WorkPlace`属性都可以引用`MyData.Address`串行对象类。 - 一个嵌入式对象可以引用另一个嵌入式对象。例如,`MyData.Address`嵌入式对象包含引用`MyData.Telephone`嵌入式对象的`Phone`属性,其中包含`CountryCode`,`AreaCode`和`PhoneNum`属性。在持久类中,使用多个下划线来引用嵌套的串行对象属性,例如`Home_Phone_AreaCode`。 编译串行对象类会在存储定义中生成数据规范。编译器通过在串行对象类名称后附加单词`“State”`来为该规范分配数据名称。因此,为`MyData.Address`分配了``。如果此名称(在此示例中为`AddressState`)已经用作属性名称,则编译器将附加一个整数以创建唯一的数据名称:``。 ## 类方法 可以将类方法指定为表定义的一部分,如以下示例所示: ```java Class MyApp.Person Extends %Persistent { Property Name As %String(MAXLEN=50) [Required]; Property SSN As %String(MAXLEN=15) [InitialExpression = "Unknown"]; Property DateOfBirth As %Date; Property Sex As %String(MAXLEN=1); ClassMethod Numbers() As %Integer [ SqlName = Numbers, SqlProc ] { QUIT 123 } } ``` 在SELECT查询中,可以按以下方式调用此方法: ```java SELECT Name,SSN,Sample.Numbers() FROM Sample.Person ``` ![image](/sites/default/files/inline/images/tu_pian__4.png) ## 通过创建持久性类来定义分片表 必须先建立分片环境,然后才能定义作为分片表投影的持久性类。 要将持久性类定义为分片,请指定类关键字`Sharded = 1`。 (类关键字`Sharded = 2`保留供生成的类内部使用。) **注意:请勿尝试设置或更改现有类定义的与分片相关的类属性。仅应为不包含数据的新表指定这些属性。这包括设置`Sharded`类关键字和与分片相关的索引关键字。尝试编辑现有类的任何与分片相关的属性都可能导致数据无法访问。** 下例显示了`Sharded = 1`持久类的类定义: ```java Class Sample.MyShardT Extends %Persistent [ ClassType = persistent, DdlAllowed, Final, Sharded = 1] { ... } ``` 如果将一个类定义为分片,则它必须是持久性的`ClassType`。如果未将分片类定义为`ClassType`持久类,则在类编译期间将返回错误,例如:`ERROR#5599:分片类'Sample.Address'必须为ClassType'persistent',而不是ClassType'serial'`。分片类使用的存储类必须为`%Storage.Persistent`或其子类`%Storage.Shard`。如果分片类的存储类不是`%Storage.Persistent`,则在类编译期间将返回以下错误:`错误#5598:分片类'Sample.Vendor'必须使用存储类型%Storage.Persistent,而不是存储类型' %Storage.SQL”`。 定义分片类时,应定义参数`DEFAULTCONCURRENCY = 0`。 然后,可以定义`ShardKey`索引。 创建分片表时,将自动生成抽象的分片键索引。分片键索引的目的是用作确定行所在的分片的键。 ### 分片类方法 分片类(`Sharded = 1`)支持`%Library.Persistent`方法`%Open(),%OpenId(),%Save(),%Delete()和%DeleteId()`具有以下限制:并发`concurrency参数`被忽略;删除将始终使用`并发concurrency= 0`,而不管用户提供的并发值如何。完全支持回调方法`%OnDelete(),%OnAfterDelete(),%OnOpen(),%OnBeforeSave()和%OnAfterSave()`。这些回调方法在分片主机上执行,而不是在分片服务器上执行。分片本地类(`Sharded = 2`)不支持这些方法。 分片类(`Sharded = 1`)不支持`%Library.Persistent`方法`%LockExtent()`和`%UnlockExtent()`。定义并发参数的对象方法中的所有并发参数都要求值`concurrency = 0`;否则,值为0。可以通过设置`DEFAULTCONCURRENCY = 0`来建立默认值 ### 分片类限制 - 分片类不支持的类参数:`CONNECTION`,`DEFAULTGLOBAL`,`DSINTERVAL`,`DSTIME`,`IDENTIFIEDBY`,`OBJJOURNAL`。 - 分片类不支持的类关键字:`language`,`ViewQuery`。 - 分片类不支持的超级类:`%Library.IndexBuilder`,`%DocDB.Document`。 - 分片类不支持的属性数据类型:`%Library.Text`。 - 分片类不支持关系属性。 - 分片类不支持投影。 - 分片类不支持功能索引(无法定义索引`TypeClass`)。 - 分片类不支持使用除`“对象”`以外的语言的任何方法。 - 分片类不支持任何非`%SQLQuery`类型的类查询。 尝试使用任何这些功能来编译分片类都会导致编译时错误。
文章
Claire Zheng · 八月 17, 2021

FHIR标准和国际基于FHIR的互联互通实践(3):常见的互操作范式

实现互通的方式方法有很多种,我们通常会见到4种:消息交换、文档交换、服务和 API。 消息交换是最常见的一种互操作的方式,在医疗用得非常多。消息交换是基于消息引擎的,通常它应用在低业务集成度和跨数据管理域的业务环境里面。通常消息是基于临床事件,描述临床事件发生的上下文,并且能够在临床事件发生的时候,通过消息引擎把它路由给消息的接收方。 消息交换的本质是一个中心化的互操作方式,中心化的方式也能够保证消息的先进先出。我们常见的消息标准有HL7 V2、 HL7 V3的消息。 文档交换是另外一种常见的互操作方式。有别于基于临床事件的消息,文档是一种阶段性的、小结性的、完整的医疗信息的汇总。它的应用场景也是低业务集成度、跨数据管理域的环境,不过通常情况下,文档交换通常都是用在机构之间的,而不是在一家医疗机构内部的不同业务系统之间。可以通过消息引擎,当然也可以使用其他的方式来进行相应的交换。文档的标准,我们最常见的有HL7 CDA。 服务的交换是另外一种。服务是什么?服务是封装好的,并且暴露出来的一组内聚的应用系统的功能。基于服务交互的互操作,需要双方规范互操作的业务流程和角色。服务交互通常是基于面向服务的这种架构、通过服务总线来进行交互的,也是应用在低业务集成度和跨数据管理的业务环境。服务是基于规范的业务流程、角色的,但是在医疗行业并不是所有的医疗流程都已经或者是能够规范的,所以服务交互目前来看还是有一定的适用范围。最常见的国际上的服务标准是IHE。 API是现在最流行的。API在当今的网络经济里已经是无处不在了。它是概述了一组明确定义的规范,允许应用程序能够建立在另一个应用程序已有的数据和功能之上,而且不需要去了解其他的应用系统的系统设计。有别于消息和文档,API可以仅传输必要的信息,效率比较高,不需要传完整的上下文,所以它不需要中心化的消息,本质上是一个去中心化的架构,比较适合于业务集成度更紧密的互操作的场景。 目前面临的是传统的医疗边界被突破了——例如互联网医院这样的使用场景——我们需要扩大互操作性、互操作对象和数据,而这也驱动了医疗API的出现,这也是我这次介绍的一个重点,HL7 FHIR标准。 注:本文根据InterSystems中国技术总监乔鹏演讲整理而成。
文章
Hao Ma · 五月 24, 2023

使用Manifest

Manifest也许应该被翻译成“清单”, 字典上是这么解释的: 提供船舶及其货物和其他物品、乘客和船员的全面细节的文件,供海关官员使用,比如:飞机上的乘客或货物清单; 一辆货运列车的车厢清单。 在计算机语言中, Manifest可以是各种格式,用的最多的是xml和json,在IRIS中,manifest是xml格式的, 放在objectscript类的XDATA块里。 ## 编写mainfest IRIS用manifest来做配置。内部工具*%install*, 会读取manifest, 生成真正的objectscript代码来配置IRIS。我们来看个基本的例子。 ### 基本用法 下面的User.Manifest.cls` ,它配置了IRIS的global buff, bbsize等等, 然后还创建了一个命名空间。 ```java Include %occInclude Class User.Manifest { ClassMethod setup(ByRef pVars, pLogLevel As %Integer = 3, pInstaller As %Installer.Installer, pLogger As %Installer.AbstractLogger) As %Status [ CodeMode = objectgenerator, Internal ] { Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "MyInstall") } XData MyInstall [ XMLNamespace = INSTALLER ]{ } } ``` 稍微解释一下代码: - `Include %occInclude`是必须的 - `setup()`用来读取manifest的内容,完成配置工作。用户基本不用修改这个method。 - mainifest本身的逻辑层次很清楚,要配置什么内容查查文档都可以。上面的manifest只是个示意,真正用起来可以需要非常多的配置项,比如namespace, database的配置,有很多的标签可选。 ### 传参数给manifest 调用manifest的method, 也就是例子里的setup(), 注意第一个参数是`ByRef pVars`。这是objectscript里常用的By referrence的传参方式。请看下面的例子: ```java Include %occInclude Class User.Manifest { ClassMethod main(){ Set pVars("Namespace")="MYNAMESPACE" Set pVars("AnotherKey")= "AnotherValue" $$$ThrowOnError(..CreateNamespace(.pVars)) } ClassMethod CreateNamespace(ByRef pVars, pLogLevel As %Integer = 3, pInstaller As %Installer.Installer, pLogger As %Installer.AbstractLogger) As %Status [ CodeMode = objectgenerator, Internal ]{ Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "CreateNamespace") } XData CreateNamespace [ XMLNamespace = INSTALLER ]{ } } ``` 上面code在main()里定义了一个pVars, 放了两个key-value, 用来调用CreateName()。 定义的namespace在manifest用到了, Anotherkey被用在了log里, 只是一个示意。 注意这个定义: `Dir="${MGRDIR}/${Namespace}"`。 其中的`MGRDIR`不需要自己定义。Manifest有一堆自己定义的Variable, 用的最多的是 CFGDIR, CSPDIR, INSTALLDIR, MGRDIR, PORT等等。具体列表见文档。 ### 更多的用法 下面这个例子包括了import文件和copy文件, *SourceDir*和*Namespace*是传入的参数。导入文件要在一个namespace的定义里面, 拷贝文件和命名空间无关。 ```xml ``` **CSPApplication** 在manifest里定义cspapplication不难,麻烦的是不同的版本使用的标签上会有修改。下面给了一个例子。 ```xml ``` > **调用代码的例子** ```xml ``` **创建User, Role** ```xml ``` Nampespace Mapping ```xml ``` 其他还有很多的用法。想了解更多,可以看看[文档说明里的Tag列表](https://docs.intersystems.com/iris20223/csp/docbook/DocBook.UI.Page.cls?KEY=GCI_manifest#GCI_manifest_tags), 和[template](https://docs.intersystems.com/iris20223/csp/docbook/DocBook.UI.Page.cls?KEY=GCI_manifest#GCI_manifest_example_template)。 ## 怎么执行cls文件 [Using the Manifest](https://docs.intersystems.com/iris20223/csp/docbook/DocBook.UI.Page.cls?KEY=GCI_manifest#GCI_manifest_invoke) **在Terminal里执行** ```zsh %SYS>do ##class(MyPackage.MyInstaller).setup() ``` 或者, 带上参数 ``` %SYS>set vars("SourceDir")="c:\myinstaller" %SYS>set vars("Updated")="Yes" %SYS>do ##class(MyPackage.MyInstaller).setup(.vars,3) ``` **During IRIS安装**? Export the manifest class as DefaultInstallerClass.xml to the same directory where the InterSystems IRIS install (either .msi, setup_irisdb.exe, or irisinstall) is run. It is imported into %SYS and compiled, and the setup() method is executed. 那么是irisinstall里面的什么语句在执行manifest呢? ```bash # script, 用ISC_INSTALLER_MANIFEST, installer-manifest-example.xml [root@silent jhvinstall]# cat cache_install.sh #!/bin/bash echo -n "Installing Cache..." ISC_INSTALLER_MANIFEST=$3 ISC_PACKAGE_INSTANCENAME=$1 ISC_PACKAGE_INSTALLDIR=$2 ISC_PACKAGE_UNICODE="Y" ISC_PACKAGE_INITIAL_SECURITY="Minimal" ISC_PACKAGE_MGRUSER="cacheusr" ISC_PACKAGE_MGRGROUP="cacheusr" ISC_PACKAGE_USER_PASSWORD="sys" ./cinstall_silent [root@silent jhvinstall]# ./cache_install.sh CACHE6 "/cache/tmpcache6" "/tmp/installer-manifest-example.xml" Installing Cache... ``` **写一个脚本执行** 这里给一个脚本的例子,简短,但内容很丰富。 ```bash #!/bin/bash # Usage install.sh [instanceName] [password] instanceName=$1 password=$2 DIR=$(pwd) ClassImportDir=$DIR/install NameSpace="ENSDEMO" CspPath="/csp/ensdemo" SrcDir=$DIR/src/CLS DirFront=$DIR/src/CSP/csp/demo irissession $instanceName -U USER
文章
王喆 👀 · 十月 18, 2022

COS的基本语法

ObjectScript是一种面向对象的编程语言,它是InterSystems公司的Caché和Ensemble数据库的核心语言之一。ObjectScript语言的语法类似于MUMPS语言,它支持面向对象编程、过程式编程、函数式编程等多种编程范式。ObjectScript语言主要用于开发Caché和Ensemble数据库应用程序,它可以访问数据库中的数据、调用数据库中的存储过程、触发器和事件,还可以与其他编程语言进行交互。 Cache使用的语言是ObjectScript简称COS,下面展示的是其基本语法,也是我个人的COS字典: 1 系统指令 SET 缩写 s ,赋值命令,样例 - s hello ="Hello World"; WRITE 缩写 w ,向当前设备输出,样例 - w hello (特殊用法:w ! 换行、w # 清屏 ) DO 缩写 d ,执行函数,样例 – d ##class(%SYSTEM.License).ShowSummary(); Kill 缩写 k ,从堆栈中清楚变量 x,慎用(不加参数调用时候将清楚内存中的所有变量!)样例 - k x Quit 缩写 q , 返回 样例 - q $$$OK 注意:系统指令不区分大小写,变量和对象大小写敏感 2 数据类型 %Integer 整数型 0,1,2,3,4,5,6...... %Boolean 布尔类型 0-F 1-T %String 字符串类型 "你好,世界" %Date 时间类型 更多的数据类型: https://docs.intersystems.com/healthconnect20201/csp/docbook/DocBook.UI.Page.cls?KEY=GOBJ_proplit#GOBJ_C27523 3 运算符 数学运算符 加减乘除+,-,*,/ W 2+3+2*2 14 运算时严格从左到右执行,数学运算没有优先级 W 2+3+2*2 9 连字符_ W "shanghai"_"disney" 逻辑运算符 与或非&, ||, ‘ W 1&0 4 流程控制 For 循环 FOR variable=start:increment:end { . . . } #; 第1种 For i = 1:1:9 { Write i, ! } #; 第2种 Set key = "" For { Set key = $ORDER(array(key)) Quit:key="" // process array(key) } #; 第3种 For value = "Red","Green","Blue" { Write value, ! } If else判断 IF expression1 { . . . } ELSEIF expression2 { . . . } ELSE { . . . } If a>0{ w a }ElseIf a<0&&a=-1{ w a }ElseIf a<-1{ w a }else{ w a } While循环 WHILE expression,... { ;. . . } #; 第1种 Do { } While (1 /* condition */) #; 第2种 While (1 /* condition */) { } 5 系统变量 当前时间 $Horolog — W $H 时间戳 $ZTIMESTAMP — w ZTS 系统版本 $ZVERSION — W $ZV 注意:系统变量不区分大小写 更多系统变量 https://docs.intersystems.com/healthconnect20201/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_VARIABLES 6 函数 字符串类型与日期类型相互转换: $ZDH/$ZD — w $ZDH("2017-03-13",3) — w $zd(0) 字符串截取函数: $piece — W $P("This is training", " ",3) 字符串比较替换函数: $Case — W $case(10,1:"100",2:"200",:"1000") 按值查找,并返回一个整数,该整数指定子字符串中的结束位置 $FIND w $f("acvs","c",1) 输出为字符串的位置,输出为0标识不存在 更多函数: https://docs.intersystems.com/healthconnect20201/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_FUNCTIONS 7 Global Global算是IRIS的一个特殊概念,其实可以把它理解为可以持久化的多维数组,下面展示的是把Global当成多维数组的处理方式 下标:可以是数字也可以是字符串 操作:赋值 - set(s) 、删除 - kill(k) 持久化:多维数组命名时候以 ^ 开头,会按下标存储,如果用的是HealthConnect或者IRISHealth在【系统资源管理器】- 【Global】中可以看到。 样例: s a = 1,a(1) = "a",a(1,1) = "b",a(1,1,"wow") = "foo",a(1,2) = "c",a(2) = 0 zw a (这里,的意思代表省略了前面的 set 系统指令)如图: 这是我早些时候的笔记,可能至今还有一些错误,希望有看出问题的大佬指正。 非常棒的分享!
文章
姚 鑫 · 五月 6, 2021

第三章 使用多维存储(全局变量)(二)

# 第三章 使用多维存储(全局变量)(二) # 遍历全局变量中的数据 有许多方法可以遍历(迭代)存储在全局变量中的数据。 ## $ORDER(下一个/上一个)函数 ObjectScript `$Order`函数允许顺序访问全局中的每个节点。 `$ORDER`函数返回给定级别(下标编号)的下一个下标的值。例如,假设定义了以下全局设置: ```java Set ^Data(1) = "" Set ^Data(1,1) = "" Set ^Data(1,2) = "" Set ^Data(2) = "" Set ^Data(2,1) = "" Set ^Data(2,2) = "" Set ^Data(5,1,2) = "" ``` 要查找第一个第一级下标,我们可以使用: ```java SET key = $ORDER(^Data("")) ``` 这将返回空字符串(`“”`)之后的第一个第一级下标。(空字符串用于表示第一个条目之前的下标值;作为返回值,它用于指示没有后续的下标值。)。在本例中,`key`现在将包含值`1`。 我们可以通过在`$ORDER`表达式中使用`1`或键来查找下一个第一级下标: ```java SET key = $ORDER(^Data(key)) ``` 如果`key`的初始值为`1`,则此语句将其设置为`2`(因为`^Data(2)`是下一个第一级下标)。再次执行此语句会将`key`设置为`5`,因为这是下一个第一级下标。请注意,即使没有直接存储在`^Data(5)`中的数据,也会返回`5`。再次执行此语句将把`key`设置为空字符串(`“”`),表示没有更多的一级下标。 通过将附加下标与`$ORDER`函数一起使用,可以迭代不同的下标级别。`$order`返回其参数列表中最后一个下标的下一个值。使用上述数据,该语句如下: ```java SET key = $ORDER(^Data(1,"")) ``` 将关键字设置为`1`,因为`^Data(1,1)`是下一个二级下标。再次执行此语句会将`KEY`设置为`2`,因为这是下一个二级下标。再次执行此语句将把`key`设置为`“”`,表示在节点`^Data(1)`下没有更多的二级下标。 ## 使用$ORDER循环 下面的ObjectScript代码定义了一个简单的全局变量,然后循环遍历其所有第一级子脚本: ```java /// w ##class(PHA.TEST.Global).ReadGlobalSimpleFor() ClassMethod ReadGlobalSimpleFor() { // 清除^Data,以防它有数据 Kill ^Data // 使用示例数据填写^Data For i = 1:1:100 { // 将每个节点设置为随机人名 Set ^Data(i) = ##class(%PopulateUtils).Name() } // 在每个节点上循环 查找第一个节点 Set key = $Order(^Data("")) While (key '= "") { Write "#", key, " ", ^Data(key),! // F查找下一个节点 Set key = $Order(^Data(key)) } q "" } ``` ```java DHC-APP>w ##class(PHA.TEST.Global).ReadGlobalSimpleFor() #1 Edwards,Barbara T. #2 Ragon,Kevin K. #3 Avery,Josephine U. #4 Townsend,Buzz R. #5 Joyce,Quentin V. #6 Xenia,Ted F. #7 Chadwick,Wilma N. #8 Duquesnoy,Orson A. #9 Uberoth,Orson X. #10 Jones,Joe O. #11 Hills,Barb R. #12 Yakulis,Pat J. #13 Tesla,Al P. #14 Goncharuk,Sam J. #15 Presley,Amanda D. #16 Olsen,Kristen I. #17 Roentgen,John T. #18 Minichillo,Elmo N. #19 Koivu,Patrick R. #20 Harrison,Lawrence I. #21 Page,Agnes P. #22 Wijnschenk,Hannah L. #23 Chesire,Bart S. #24 Klingman,Liza K. #25 Smyth,Imelda J. #26 Alton,Filomena L. #27 Minichillo,Charles U. #28 Nichols,Jeff W. #29 O'Rielly,Thelma X. #30 Schaefer,Kristen G. #31 Black,Filomena R. #32 Vivaldi,Xavier B. #33 Allen,Phyllis U. #34 Mastrolito,Zelda Z. #35 Quilty,Jane V. #36 Zevon,Maureen H. #37 O'Rielly,Maureen C. #38 Olsen,Robert W. #39 Page,Milhouse D. #40 Nelson,Dick R. #41 Ironhorse,Danielle I. #42 Tweed,Rhonda T. #43 Quincy,Terry L. #44 Tsatsulin,Jocelyn C. #45 Yeats,Michelle E. #46 Jackson,Paul V. #47 Humby,Dave I. #48 Kelvin,Natasha R. #49 Kelvin,Kyra R. #50 Yoders,Agnes R. #51 Tesla,Amanda F. #52 Harrison,Christen T. #53 Allen,Nataliya J. #54 Xenia,Diane W. #55 Xenia,Phyllis E. #56 Isaksen,Pam D. #57 Waterman,Charles M. #58 Peters,Sophia N. #59 Peterson,Bart B. #60 Eastman,Edward S. #61 Young,Belinda F. #62 White,Fred G. #63 Ubertini,Lola U. #64 Uhles,Xavier T. #65 Quine,Phyllis T. #66 Hernandez,Umberto B. #67 Allen,Zelda S. #68 Harrison,David Z. #69 Harrison,Danielle T. #70 Ott,Dick D. #71 Lennon,Joe Y. #72 Quigley,Alfred M. #73 Klausner,Mario J. #74 Tsatsulin,Emily S. #75 Anderson,Edward R. #76 Lennon,Fred H. #77 DeSantis,Molly J. #78 Browne,Dave H. #79 Cunningham,Buzz L. #80 Ingersol,Edgar G. #81 Paraskiv,Linda O. #82 Beatty,Kim H. #83 Quilty,Wilma P. #84 Dunlap,Jules I. #85 Waterman,Buzz D. #86 Edison,Kim C. #87 Eagleman,Michael N. #88 Huff,Hannah K. #89 Vanzetti,Maria E. #90 Zampitello,Angela Q. #91 Anderson,Angela Z. #92 Isaacs,Charlotte Q. #93 O'Donnell,Paul A. #94 Underman,Zeke R. #95 Schultz,James I. #96 Chadbourne,Janice N. #97 Lennon,William T. #98 Vonnegut,Pam V. #99 Miller,Patricia T. #100 Hills,Charles C. ``` ## 其他$ORDER参数 ObjectScript `$ORDER`函数接受可选的第二个和第三个参数。 第二个参数是一个方向标志,指示希望在哪个方向上遍历全局变量。 默认值`1`指定正向遍历,而`-1`指定反向遍历。 第三个参数(如果存在)包含一个局部变量名。 如果`$ORDER`找到的节点包含数据,则将找到的数据写入这个本地变量。 当您在一个全局循环中,并且您对节点值和下标值感兴趣时,这样操作更有效。 ## $QUERY函数 如果需要访问全局变量中的每个节点和子节点,在子节点上上下移动,请使用ObjectScript `$Query`函数。(或者,可以使用嵌套的`$ORDER`循环)。 **`$Query`函数接受全局变量引用,并返回一个字符串,其中包含全局变量中下一个节点的全局引用(如果没有后续节点,则返回`""`)。若要使用`$QUERY`返回的值,必须使用ObjectScript间接运算符`(@)`。** 例如,假设定义了以下全局设置: ```java Set ^Data(1) = "" Set ^Data(1,1) = "" Set ^Data(1,2) = "" Set ^Data(2) = "" Set ^Data(2,1) = "" Set ^Data(2,2) = "" Set ^Data(5,1,2) = "" ``` 以下是对`$QUERY`的调用: ```java SET node = $QUERY(^Data("")) ``` 将节点设置为字符串`“^Data(1)”`,即全局中第一个节点的地址。然后,要获取全局中的下一个节点,请再次调用`$QUERY`并在节点上使用间接运算符: 此时,节点包含字符串`“^Data(1,1)”`。 以下示例定义一组全局变量节点,然后使用`$QUERY`遍历它们,同时写入每个节点的地址: ```java /// w ##class(PHA.TEST.Global).ReadGlobalSimpleQuery() ClassMethod ReadGlobalSimpleQuery() { Kill ^Data // 确保^Data为空 // 将一些数据放入^Data // Set ^Data(1) = "" Set ^Data(1,1) = "" Set ^Data(1,2) = "" Set ^Data(2) = "" Set ^Data(2,1) = "" Set ^Data(2,2) = "" Set ^Data(5,1,2) = "" // 现在浏览^Data 查找第一个节点 Set node = $Query(^Data("")) While (node '= "") { Write node,! // 获取下一个节点 Set node = $Query(@node) } q "" } ``` ```java DHC-APP>w ##class(PHA.TEST.Global).ReadGlobalSimpleQuery() ^Data(1) ^Data(1,1) ^Data(1,2) ^Data(2) ^Data(2,1) ^Data(2,2) ^Data(5,1,2) ```