搜索​​​​

清除过滤器
文章
姚 鑫 · 七月 27, 2023

第四章 HL7 架构和可用工具 - 查看数据结构

# 第四章 HL7 架构和可用工具 - 查看数据结构 ## 查看数据结构 当单击“数据结构”列中的名称时,`InterSystems` 会显示该数据结构中的所有字段。这是 `HL7` 数据结构页面。显示的以下列是最有用的: - 组件列列出了可用于访问段内字段的数字。 - 属性名称列列出了可用于访问段内字段的名称。 - 单击“数据结构”列中的条目可深入了解详细信息。 - 单击“代码表”列(如果有)中的条目可查看可在此字段中输入的有效代码。 当单击上面段结构页面中名为 `2.3:XCN` 的数据结构项时,将出现以下示例页面。该页面指出类别 `2.3` 数据结构 `XCN` 描述“扩展复合 `ID` 号和名称”并由 `14` 个字段组成。其中,有些是简单值,有些是数据结构,有些是代码。 有了这些信息,就可以为消息结构 `2.3:ADT_A01` 中的复杂 `PR1grp().PR1:Surgeon` 字段创建虚拟属性路径,如下所示: ``` PR1grp().PR1:Surgeon.familyname PR1grp().PR1:Surgeon.degree ``` ## 查看代码表 当单击“代码表”列中的名称时,它会列出并解释该字段的有效代码。这是 `HL7` 代码表页面。当单击上一节中显示的数据结构页面中名为 `2.3:200` 的代码表项时,将出现以下示例页面。 上面的示例示出类别`2.3`代码表`200`描述可以具有值`L`、`O`、`M`、`A`、`C`或`D`的“名称类型”。 这意味着,如果有一条 `DocType` 为 `2.3:ADT_A01` 的 `HL7` 消息,则它具有一个可选虚拟属性,路径为 `PR1grp().PR1:Anesthesiograph.nametype`,可以包含以下值之一:`L`、`O`、`M`、 `A`、`C` 或 `D`。 ## 使用自定义架构编辑器 自定义架构编辑器允许创建新的自定义 `HL7` 架构或编辑现有的自定义 `HL7` 架构。通常,自定义模式具有基本模式,它是标准模式或其他自定义模式。当 `InterSystems` 产品使用自定义架构来解析消息时,如果自定义架构中未定义消息类型、段或其他元素,它将使用基本架构中的定义。因此,只需在自定义架构中定义基本架构中不存在的元素,或者需要与基本架构中的定义不同的元素。无法编辑标准架构。 定义自定义架构的最常见原因是能够解析带有尾部 `Z` 段的 `HL7` 消息。 `InterSystems` 产品可以处理带有架构中未定义的尾部 `Z` 段的消息,但要执行以下任一操作,需要定义自定义架构: - 访问路由规则、数据转换或 `ObjectScript` 代码中尾部 `Z` 段中的字段路径。 - 验证尾部 `Z` 段。 如果`production`当前正在使用标准模式,并且需要访问数据转换或路由规则中的尾部 `Z` 段字段路径,则应执行以下操作: 1. 使用管理门户中的自定义架构编辑器创建新的 `HL7` 架构。输入自定义架构的名称并指定基本架构。请参阅创建新的自定义架构。 2. 定义可以出现在消息中的 `Z` 段。如果 `Z` 段与基础架构中的现有段具有相似的字段,可以从基础复制定义,然后根据需要进行修改。否则,可以创建一个新段。可以添加字段、删除字段或更改字段的顺序。请参阅定义新段。 3. 对于包含尾随 `Z` 段的每个消息类型,在从基础架构复制的自定义架构中创建消息类型和结构类型。将 `Z` 段添加到结构类型的末尾。请参阅定义新消息类型和结构类型 4. 修改`production` 中的业务服务以使用新的自定义架构而不是基本架构。 5. **通过向`production`的业务服务提供带有尾部 `Z` 段的新消息来测试`production`。如果在消息查看器中查看消息,则 `Z` 段(如果它们在架构中定义)将显示为蓝色。无法识别的段显示为黑色。**
文章
Qiao Peng · 一月 24, 2021

解决SQL适配器连接到字符集为US7ASCII的Oracle数据库的中文乱码问题

在使用xDBC连接到字符集为US7ASCII的Oracle数据库时,大家可能遇到过中文的乱码问题,尤其是使用Oracle自己的xDBC驱动的时候。 字符集为US7ASCII的Oracle数据库虽然可以保存中文数据,但给客户端带来了很多麻烦,需要对获取和提交的数据进行转码。 在Ensemble/Health Connect/InterSystems IRIS 中使用SQL适配器连接到这样的Oracle数据库时,可以使用$ZCVT函数进行转码。 1. $ZCVT函数 $ZCVT函数是广泛使用的字符串转换函数,可以做大小写转换、编码转换、URL 和 URI 转换等。我们用其编码转换能力来解决字符集转码问题。 2. 获取的SQL结果集数据有中文时 这时,Oracle的驱动返回的中文数据通常是GB码,而不是Unicode或UTF码。可以通过$ZCVT函数对GB码的数据进行转码,转换为Unicode: Set tCorrectData = $ZCVT(tOriginalData,"I","GB18030")其中$ZCVT函数的第一个参数tOriginalData是获取到到结果集字段值;第二个参数“I”说明tOriginalData是输入字符串;第三个参数“GB18030”是说明输入字符串的字符集编码是GB18030。 上面的代码会将tOriginalValue按GB18030编码转换为Unicode编码,并将转换结果赋给变量tCorrectData。 3. 发送中文数据或中文查询条件时 首先,需要将本地Unicode的中文数据转码为GB码。还是使用$ZCVT函数: Set tSendingData = $ZCVT(tOriginalData,"O","GB18030") 其中$ZCVT函数的第一个参数tOriginalData是输出的含中文数据;第二个参数“O”说明要转换输出数据;第三个参数“GB18030”是说明输出时要转换为GB18030。 上面的代码会将tOriginalValue按Unicode编码转换为GB18030编码,并将转换结果赋给变量tSendingData。 做完这一步,Oracle驱动很可能依然不认识其中的GB码中文数据。这时,需要将GB码的中文数据转换成GB明码字符串,例如你要将查询条件 中文 传给Oracle,你需要传递 D6D0CEC4过去(中的GB码为D6D0,文的GB码为CEC4),然后再使用Oracle的函数UTL_RAW.CAST_TO_VARCHAR2,将GB明码字符串转为内部使用的中文数据。所以修改后代码如下: Set tSendingData = $ZCVT(tOriginalData,"O","GB18030") Set tTmpDataHex = "" //转换为GB明码 For i=1:1:$length(tSendingData) { Set tTmpDataHex = tTmpDataHex_$zhex($ASCII(tSendingData,i)) } //使用Oracle的UTL_RAW.CAST_TO_VARCHAR2的函数 Set tSQL = "update dept_dict set dept_name = UTL_RAW.CAST_TO_VARCHAR2('"_ tTmpDataHex_"') where dept_code = '1'" 这里用到了$ZHEX和$ASCII函数,将数据转换为其编码,并转为16进制值,从而得到GB明码。关于更多的Ensemble/Health Connect/InterSystems IRIS函数,可以参考文档。 自此,应该可以解决从Ensemble和Health Connect连接到字符集为US7ASCII的Oracle数据库所遇到的各种中文乱码问题了。 如果您遇到更多的问题,欢迎在社区提问。 点赞
文章
姚 鑫 · 七月 1, 2021

第二十四章 执行XSLT转换

# 第二十四章 执行XSLT转换 # 执行XSLT转换 要执行`XSLT`转换,请执行以下操作: - 如果使用的是`Xalan`处理器(对于`XSLT 1.0`),请使用`%XML.XSLT.Transformer`的以下类方法之一: - `TransformFile()`——转换给定XSLT样式表的文件。 - `TransformFileWithCompiledXSL()`——转换一个文件,给定一个已编译的XSLT样式表。 - `TransformStream()`——转换给定XSLT样式表的流。 - `TransformStreamWithCompiledXSL()`——转换一个流,给定一个已编译的XSLT样式表。 - `TransformStringWithCompiledXSL()`——转换给定已编译XSLT样式表的字符串。 - 如果使用`Saxon`处理器(用于XSLT 2.0),请使用`%XML.XSLT2.Transformer`的以下类方法之一: - `TransformFile()`——转换给定XSLT样式表的文件。 - `TransformFileWithCompiledXSL()`——转换一个文件,给定一个已编译的XSLT样式表。 - `TransformStream()`——转换给定XSLT样式表的流。 - `TransformStreamWithCompiledXSL()`——转换一个流,给定一个已编译的XSLT样式表。 这些方法具有相似的签名。这些方法的参数列表按顺序如下: - pSource—要转换的源XML。请参见此列表后面的表。 - pXSL -样式表或编译样式表。请参阅此列表后面的表格。 - pOutput -作为输出参数返回的结果XML。请参阅此列表后面的表格。 - pErrorHandler -一个可选的自定义错误处理程序。请参阅本章后面的“自定义错误处理”。如果不指定自定义错误处理程序,该方法将使用%XML.XSLT.ErrorHandler的新实例(对于两个类)。 - pParms -一个可选的InterSystems IRIS多维数组,包含要传递给样式表的参数。 - pCallbackHandler -定义`XSLT`扩展函数的可选回调处理程序。 - pResolver -一个可选的实体解析器。 8. (仅适用于`%XML.XSLT2.Transformer`)网关-`%Net.Remote.Gateway`的可选实例。如果要重用XSLT网关连接以获得更好的性能,请指定此参数; 作为参考,下表显示了这些方法的前三个参数,并进行了对比: XSLT变换方法的比较 Method | pSource (Input XML) |pXSL (Stylesheet) |pOutput(Output XML) ---|---|---|--- TransformFile() |String that gives a file name| String that gives a file name |String that gives a file name TransformFileWithCompiledXSL()| String that gives a file name |Compiled style sheet| String that gives a file name TransformStream()| Stream| Stream |Stream, returned by reference TransformStreamWithCompiledXSL()| Stream |Compiled style sheet |Stream, returned by reference TransformStringWithCompiledXSL()| String |Compiled style sheet| String that gives a file name # 示例 本节使用以下代码(但不同的输入文件)展示了几种转换: ```java Set in="c:\0test\xslt-example-input.xml" Set xsl="c:\0test\xslt-example-stylesheet.xsl" Set out="c:\0test\xslt-example-output.xml" Set tSC=##class(%XML.XSLT.Transformer).TransformFile(in,xsl,.out) Write tSC ``` ## 示例1:简单替换 在本例中,我们从以下输入XML开始: ```xml Content ``` 我们使用以下样式表: ```xml Content Replaced ``` 在这种情况下,输出文件将如下所示: ```xml Content Replaced ``` ## 示例2:内容提取 在本例中,我们从以下输入XML开始: ```xml Some text Some more text ``` 我们使用以下样式表: ```xml : ``` 在这种情况下,输出文件将如下所示: ```java 13: Some text 14: Some more text ``` ## 其他示例 InterSystems IRIS提供了以下附加示例: - 对于`XSLT 1.0`,请参阅`%XML.XSLT.Transformer`中的`Example()`、`Example2()`和其它方法。 - 对于`XSLT 2.0`,请参见`Samples`命名空间中的类`XSLT2.Examples`。
文章
王喆 👀 · 九月 24, 2022

使用Global进行数据可视化---商业智能(BI)

在医院但凡接触“数据”和“指标”的人,对以下场景应该是深有感触。同样的指标、同样的时间,有可能是同一个部门出的,最后“数据不一致”。除了“匪夷所思”,更有“深恶痛绝”。那么,如何解决这个问题?我的答案是商业智能(BI)。随着技术和市场的发展,有很多公司开始研发直接面向业务用户的敏捷BI工具,FineBI就是这样的一款BI工具。这个也是我接触的第一款国产BI。 项目的前提条件和设置 操作系统:Windows 64 IRIS产品:HealthConnect 2020 BI工具:FineBI JDK: JDK1.8 FineBI、JDK、HealthConnect、FineBI的相关安装步骤网上有很多,在此就暂时略过。要在FineBI上创建仪表板,我们需要一点数据,在此我使用的是开发过程中的一些测试数据,相关测试数据和互联互通的69个交互服务息息相关,有需要可以自己模拟的造。 将FineBI和IRIS连接 我们首先找到HealthConnect的安装文件,我们需要找到intersystems-jdbc-3.2.0.jar这样一个文件。大概在【安装目录】\InterSystems\HealthConnect\dev\java\lib\JDK18,如图所示: 将文件复制到FineBI的安装目录\webapps\webroot\WEB-INF\lib,如图所示: 这个时候我们启动FineBI 看到此说明FineBI启动成功,如果启动失败重启一下电脑或者检查以下JDK的环境变量是否配置 在浏览器上打开控制面板 点击【管理系统】--【数据连接】-【新建数据连接】-【数据连接管理】--【其他】--【其他JDBC】 填入内容 连接成功 创建仪表板 通过编写SQL的方式在FineBI中建立业务表: 按照此方式在【数据准备】-【数据列表】中建立多个业务表,如图所示: 新建仪表板 通过添加组件,选择表,选择样式 最终编辑成如图所示仪表板 总结 从上面演示的例子,我们可以看出,极短的时间我们就把FineBI连接到了IRIS的HealthConnect上,然后创建一些数据上的可视化的功能和东西。作为开发人员来说这很便捷,我们可以很快的完成一些数据展示的工作。同时如果把这些东西开放给一些精通业务但是对技术很生疏的人,就可以十分快速的把数据按照业务上需要的方式展示出来,一个部门乃至多个部分对出具的数据也有了更清晰的认识,在可视化的帮助下,那些乱起八糟的数据也开始变的更接近真实。 本文只展示了FineBI的使用,相信一些可以使用JDBC或者ODBC作为连接的BI工具都可以,比如最经典的PowerBI。
文章
姚 鑫 · 三月 28, 2021

第十三章 使用动态SQL(六)

# 第十三章 使用动态SQL(六) ### 用`%ObjectSelectMode = 1` Swizzling字段名称属性 下面的示例使用`%ObjectSelectMode = 1`进行准备,当使用字段名称属性返回值时,其类型类别为可Swizzle类型的字段(持久性类,序列类或流类)将自动发生Swizzle。转换字段值的结果是相应的对象参考(oref)。使用`%Get()`或`%GetData()`方法访问字段时,InterSystems IRIS不会执行此筛选操作。在此示例中,`rset.Home`处于Swizzle状态,而引用同一字段的`rset.%GetData(2)`处于not swizzled状态: ```java /// d ##class(PHA.TEST.SQL).PropSQL2() ClassMethod PropSQL2() { SET myquery = "SELECT TOP 5 Name,Home FROM Sample.Person" SET tStatement = ##class(%SQL.Statement).%New(0) SET tStatement.%ObjectSelectMode=1 WRITE !,"set ObjectSelectMode=",tStatement.%ObjectSelectMode,! SET qStatus = tStatement.%Prepare(myquery) IF qStatus'=1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT } SET rset = tStatement.%Execute() WHILE rset.%Next() { WRITE "Row count ",rset.%ROWCOUNT,! WRITE rset.Name WRITE " ",rset.Home,! WRITE rset.%GetData(1) WRITE " ",$LISTTOSTRING(rset.%GetData(2)),!! } WRITE !,"End of data" WRITE !,"Total row count=",rset.%ROWCOUNT } ``` ```java DHC-APP> d ##class(PHA.TEST.SQL).PropSQL2() set ObjectSelectMode=1 Row count 1 yaoxin 5@Sample.Address yaoxin 889 Clinton Drive,St Louis,WI,78672 Row count 2 xiaoli 5@Sample.Address xiaoli Row count 3 姚鑫 5@Sample.Address 姚鑫 Row count 4 姚鑫 5@Sample.Address 姚鑫 ``` 下面的示例使用`%ObjectSelectMode = 1`从唯一记录ID(`%ID`)导出所选记录的`Home_State`值。请注意,在原始查询中未选择`Home_State`字段: ```java /// d ##class(PHA.TEST.SQL).PropSQL2() ClassMethod PropSQL2() { SET myquery = "SELECT TOP 5 %ID AS MyID,Name,Age FROM Sample.Person" SET tStatement = ##class(%SQL.Statement).%New() SET tStatement.%ObjectSelectMode=1 SET qStatus = tStatement.%Prepare(myquery) IF qStatus'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT} SET rset = tStatement.%Execute() WHILE rset.%Next() { WRITE rset.Name WRITE " Home State:",rset.MyID.Home.State,! } WRITE !,"End of data" WRITE !,"Total row count=",rset.%ROWCOUNT } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).PropSQL2() yaoxin Home State:WI xiaoli Home State: 姚鑫 Home State: 姚鑫 Home State: 姚鑫 Home State: End of data Total row count=5 ``` 如果已配置,则如果配置了swizzled属性,但系统无法生成引用,则系统会生成``错误。如果引用的属性已从磁盘中意外删除或被另一个进程锁定,则会发生这种情况。要确定SWIZZLE失败的原因,请在``错误之后立即在`%objlasterror`中查找并解码此`%Status`值。 默认情况下,未配置``。可以通过设置`SET ^%SYS("ThrowSwizzleError")=1`或使用InterSystems IRIS管理门户来全局设置此行为。在“系统管理”中,选择“配置”,然后选择“ SQL和对象设置”,然后选择“对象”。在此屏幕上,可以设置``选项。 ## %Get("fieldname")方法 可以使用`%Get(“ fieldname”)`实例方法按字段名称或字段名称别名返回数据值。 Dynamic SQL根据需要解析字母大小写。如果指定的字段名称或字段名称别名不存在,系统将生成``错误。 下面的示例从查询结果集中返回`Home_State`字段和`Last_Name`别名的值。 ```java /// d ##class(PHA.TEST.SQL).PropSQL4() ClassMethod PropSQL4() { SET myquery = "SELECT TOP 5 Home_State,Name AS Last_Name FROM Sample.Person" SET tStatement = ##class(%SQL.Statement).%New(2) SET qStatus = tStatement.%Prepare(myquery) IF qStatus'=1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT } SET rset = tStatement.%Execute() WHILE rset.%Next() { WRITE rset.%Get("Home_State")," : ",rset.%Get("Last_Name"),! } WRITE !,"End of data" WRITE !,"Total row count=",rset.%ROWCOUNT } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).PropSQL4() WI : yaoxin : xiaoli : 姚鑫 : 姚鑫 : 姚鑫 End of data Total row count=5 ``` 必须使用`%Get("fieldname")`实例方法从使用`%PrepareClassQuery()`准备的现有查询中按字段属性名称检索单个数据项。如果字段属性名称不存在,则系统会生成``错误。 下面的示例从内置查询中按字段属性名称返回Nsp(命名空间)字段值。因为此查询是现有的存储查询,所以此字段检索需要使用`%Get("fieldname") `方法。请注意,由于`“Nsp”`是属性名称,因此区分大小写: ```java /// d ##class(PHA.TEST.SQL).PropSQL5() ClassMethod PropSQL5() { SET tStatement = ##class(%SQL.Statement).%New(2) SET qStatus = tStatement.%PrepareClassQuery("%SYS.Namespace","List") IF qStatus'=1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT } SET rset = tStatement.%Execute() WHILE rset.%Next() { WRITE "Namespace: ",rset.%Get("Nsp"),! } WRITE !,"End of data" WRITE !,"Total row count=",rset.%ROWCOUNT } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).PropSQL5() Namespace: %SYS Namespace: DHC-APP Namespace: DHC-CHSSWEB Namespace: DHC-CSM Namespace: DHC-DATA Namespace: DHC-DWR Namespace: DHC-EKG Namespace: DHC-HEIS Namespace: DHC-HR Namespace: DHC-LISDATA Namespace: DHC-LISSRC Namespace: DHC-MEDSRC Namespace: DHC-MRQ Namespace: DOCBOOK Namespace: FDBMS Namespace: PACS Namespace: PIS Namespace: RIS Namespace: SAMPLES Namespace: USER End of data Total row count=20 ``` 重复名称:如果名称解析为相同的属性名称,则它们是重复的。重复名称可以是对同一字段的多个引用,对表中不同字段的引用或对不同表中字段的引用。如果`SELECT`语句包含相同字段名称或字段名称别名的多个实例,则`%Get(“fieldname”)`始终返回查询中指定的重复名称的最后一个实例。这与`rset.PropName`相反,后者返回查询中指定的重复名称的第一个实例。在下面的示例中显示: ```java /// d ##class(PHA.TEST.SQL).PropSQL6() ClassMethod PropSQL6() { SET myquery = "SELECT c.Name,p.Name FROM Sample.Person AS p,Sample.Company AS c" SET tStatement = ##class(%SQL.Statement).%New() SET qStatus = tStatement.%Prepare(myquery) IF qStatus'=1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT } SET rset = tStatement.%Execute() WHILE rset.%Next() { WRITE "Prop=",rset.Name," Get=",rset.%Get("Name"),! } WRITE !,rset.%ROWCOUNT," End of data" } ``` ## %GetData(n)方法 `%GetData(n)`实例方法返回由结果集的整数计数列号索引的当前行的数据您可以将`%GetData(n)`与使用`%Prepare()`准备的指定查询或使用`%PrepareClassQuery()`准备的存储查询一起使用。 使用`%PrepareClassQuery()`准备。 整数n对应于查询中指定的选择项列表的序列。除非在选择项列表中明确指定,否则不会为`RowID`字段提供整数n值。如果n大于查询中的选择项数,或者为0,或者为负数,则Dynamic SQL不返回任何值,也不发出错误。 `%GetData(n)`是返回特定重复字段名称或重复别名的唯一方法; `rset.Name`返回第一个重复项,`%Get(“Name”)`返回最后一个重复项。 ```java /// d ##class(PHA.TEST.SQL).PropSQL7() ClassMethod PropSQL7() { SET myquery="SELECT TOP 5 Name,SSN,Age FROM Sample.Person" SET tStatement = ##class(%SQL.Statement).%New() SET qStatus = tStatement.%Prepare(myquery) IF qStatus'=1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT } SET rset = tStatement.%Execute() WHILE rset.%Next() { WRITE "Years:",rset.%GetData(3)," Name:",rset.%GetData(1),! } WRITE "End of data" WRITE !,"Total row count=",rset.%ROWCOUNT } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).PropSQL7() Years:30 Name:yaoxin Years: Name:xiaoli Years:7 Name:姚鑫 Years:7 Name:姚鑫 Years:43 Name:姚鑫 End of data Total row count=5 ``` # 返回多个结果集 `CALL`语句可以将多个动态结果集作为一个集合返回,称为结果集序列(RSS)。 下面的示例使用`%NextResult()`方法分别返回多个结果集: ```java /// d ##class(PHA.TEST.SQL).PropSQL8() ClassMethod PropSQL8() { SET mycall = "CALL Sample.CustomSets()" SET rset = ##class(%SQL.Statement).%ExecDirect(,mycall) IF rset.%SQLCODE'=0 { WRITE !,"ExecDirect SQLCODE=",rset.%SQLCODE,!,rset.%Message QUIT } SET rset1=rset.%NextResult() DO rset1.%Display() WRITE !,"End of 1st Result Set data",!! SET rset2=rset.%NextResult() DO rset2.%Display() WRITE !,"End of 2nd Result Set data" } ``` ```java DHC-APP> d ##class(PHA.TEST.SQL).PropSQL8() ID Name Street City State Spouse 3 Davis,Robert I. 4177 Franklin Court Fargo WY 86 2 Hanson,Roberta O. 9840 Ash Drive Boston KS 155 4 Huff,Olga M. 1902 Franklin Avenue Vail DE 150 1 Woo,Jocelyn A. 9932 Clinton Avenue Queensbury NM 14 5 Zubik,George T. 8102 First Drive Denver VA 110 5 Rows(s) Affected End of 1st Result Set data ID Name Street City State Spouse 5 Campos,Alvin N. 1847 Franklin Drive Ukiah WY 206 1 Fripp,Kristen A. 1487 Ash Place Islip NC 133 3 Jafari,Christen K. 7384 Washington Court Newton CO 168 4 Kratzmann,Mark V. 9573 Second Blvd Chicago OR 43 2 O'Donnell,George H. 3413 Main Drive Newton RI 143 7 Ravazzolo,Danielle Y. 2898 Clinton Blvd Tampa HI 133 10 Rodriguez,Sophia U. 4766 Clinton Avenue Ukiah AR 202 6 Sverdlov,Phyllis J. 5010 Oak Place Fargo VT 214 8 Uhles,Andrew O. 4931 Madison Street Bensonhurst IA 129 9 Xerxes,Mo C. 49 Main Drive Vail CA 151 10 Rows(s) Affected End of 2nd Result Set data ```
文章
姚 鑫 · 四月 14, 2021

第二章 定义和构建索引(二)

# 第二章 定义和构建索引(一) # 定义索引 ### 使用带有索引的Unique、PrimaryKey和IdKey关键字 与典型的SQL一样,InterSystems IRIS支持惟一键和主键的概念。 InterSystems IRIS还能够定义`IdKey`,它是类实例(表中的行)的唯一记录ID。 这些特性是通过`Unique`、`PrimaryKey`和`IdKey`关键字实现的: - `Unique` -在索引的属性列表中列出的属性上定义一个唯一的约束。 也就是说,只有这个属性(字段)的唯一数据值可以被索引。 唯一性是根据属性的排序来确定的。 例如,**如果属性排序是精确的,则字母大小写不同的值是唯一的; 如果属性排序是`SQLUPPER`,则字母大小写不同的值不是唯一的。** 但是,请注意,对于未定义的属性,不会检查索引的惟一性。 根据SQL标准,未定义的属性总是被视为唯一的。 - `PrimaryKey` -在索引的属性列表中列出的属性上定义一个主键约束。 - `IdKey` -定义一个唯一的约束,并指定哪些属性用于定义实例(行)的唯一标识。 `IdKey`总是具有精确的排序规则,即使是数据类型为`string`时也是如此。 这些关键字的语法出现在下面的例子中: ```java Class MyApp.SampleTable Extends %Persistent [DdlAllowed] { Property Prop1 As %String; Property Prop2 As %String; Property Prop3 As %String; Index Prop1IDX on Prop1 [ Unique ]; Index Prop2IDX on Prop2 [ PrimaryKey ]; Index Prop3IDX on Prop3 [ IdKey ]; } ``` **注意:`IdKey`、`PrimaryKey`和`Unique`关键字只在标准索引下有效。 不能将它们与位图或位片索引一起使用。** 同时指定`IdKey`和`PrimaryKey`关键字也是有效的语法,例如: ``` Index IDPKIDX on Prop4 [ IdKey, PrimaryKey ]; ``` 这个语法指定`IDPKIDX`索引既是类(表)的`IdKey`,也是它的主键。 这些关键字的所有其他组合都是多余的。 对于使用这些关键字之一定义的任何索引,都有一个方法允许打开类的实例,其中与索引关联的属性有特定的值; ### 定义SQL搜索索引 可以在表类定义中定义SQL搜索索引,如下所示: ```java Class Sample.TextBooks Extends %Persistent [DdlAllowed] { Property BookName As %String; Property SampleText As %String(MAXLEN=5000); Index NameIDX On BookName [ IdKey ]; Index SQLSrchIDXB On (SampleText) As %iFind.Index.Basic; Index SQLSrchIDXS On (SampleText) As %iFind.Index.Semantic; Index SQLSrchIDXA On (SampleText) As %iFind.Index.Analytic; } ``` ### 用索引存储数据 **可以使用index Data关键字指定一个或多个数据值的副本存储在一个索引中:** ```java Class Sample.Person Extends %Persistent [DdlAllowed] { Property Name As %String; Property SSN As %String(MAXLEN=20); Index NameIDX On Name [Data = Name]; } ``` 在本例中,索引`NameIDX`的下标是各种`Name`值的排序(大写)值。名称的实际值的副本存储在索引中。当通过SQL更改`Sample.Person`表或通过对象更改对应的`Sample.Person`类或其实例时,将维护这些副本。 在经常执行选择性(从许多行中选择一些行)或有序搜索(从许多列中返回一些列)的情况下,在索引中维护数据副本会很有帮助。 例如,考虑以下针对`Sample.Person`表的查询: SQL引擎可以通过读取`NameIDX`而从不读取表的主数据来决定完全满足此请求。 ![image](/sites/default/files/inline/images/1_34.png) **注意:不能使用位图索引存储数据值。** ### 索引null 如果一个索引字段的数据为`NULL`(没有数据存在),相应的索引使用索引`NULL`标记来表示这个值。 **默认情况下,索引空标记值为`-1E14`。** 使用索引空标记可以使空值排序在所有非空值之前。 `%Library.BigInt`数据类型存储小于`-1E14`的小负数。默认情况下,`%BigInt`索引空标记值为`-1E14`,因此与现有`BigInt`索引兼容。如果索引的`%BigInt`数据值可能包括这些极小的负数,则可以使用`INDEXNULLMARKER`属性参数更改特定字段的索引`NULL`标记值,作为特性定义的一部分,如下例所示: ```java Property ExtremeNums As %Library.BigInt(INDEXNULLMARKER = "-1E19"); ``` 还可以在数据类型类定义中更改索引`NULL`标记的默认值。 **此参数属性在IRIS里有,Cache里没有。** ### 索引集合 为属性编制索引时,放在索引中的值是整个已整理属性值。对于集合,可以通过将(`Elements`)或(`Key`)附加到属性名称来定义与集合的元素和键值相对应的索引属性。(元素)和(键)允许指定从单个属性值生成多个值,并对每个子值进行索引。当属性是集合时,`Elements`令牌通过值引用集合的元素,`Key`令牌通过位置引用它们。当元素和键都出现在单个索引定义中时,索引键值包括键和关联的元素值。 例如,假设有一个基于`Sample.Person`类的`FavoriteColors`属性的索引。对此属性集合中的项进行索引的最简单形式是以下任一种: ```java INDEX fcIDX1 ON (FavoriteColors(ELEMENTS)); ``` 或 ```java INDEX fcIDX2 ON (FavoriteColors(KEYS)); ``` 其中,`FavoriteColor(Elements)`是指`FavoriteColors`属性的元素,因为它是一个集合。一般形式是`PropertyName`(元素)或`PropertyName`(键),其中该集合的内容是定义为某个数据类型的列表或数组的属性中包含的一组元素)。 若要索引文本属性,可以创建一个由`PropertyNameBuildValueArray()`方法生成的索引值数组(在下一节中介绍)。与集合本身一样,(Elements)和(Key)语法对索引值数组有效。 如果属性集合被投影为数组,则索引必须遵守以下限制才能被投影到集合表。索引必须包括(键)。索引不能引用集合本身和对象ID值以外的任何属性。如果投影索引还定义了要存储在索引中的数据,则存储的数据属性也必须限制为集合和ID。否则,不会投影索引。此限制适用于投影为数组的集合属性上的索引;不适用于投影为列表的集合上的索引。 与集合的元素或键值对应的索引还可以具有所有标准索引功能,例如将数据与索引一起存储、特定于索引的排序规则等。 InterSystems SQL可以通过指定`FOR SOME%ELEMENT`谓词来使用集合索引。 ### 使用(Elements)和(Key)索引数据类型属性 为了索引数据类型属性,还可以使用`BuildValueArray()`方法创建索引值数组。此方法将属性值解析为键和元素的数组;它通过生成从与其关联的属性的值派生的元素值集合来实现这一点。使用`BuildValueArray()`创建索引值数组时,其结构适合索引。 `BuildValueArray()`方法的名称为`PropertyNameBuildValueArray()`,其签名为: ```java ClassMethod propertynameBuildValueArray(value, ByRef valueArray As %Library.String) As %Status ``` - `BuildValueArray()`方法的名称以组合方法的典型方式派生于属性名。 - 第一个参数是属性值。 - 第二个参数是通过引用传递的数组。 这是一个包含键-元素对的数组,键下标的数组等于元素。 - 该方法返回一`%Status` 值。 这个例子: ```java /// DescriptiveWords是一个以逗号分隔的单词字符串 Property DescriptiveWords As %String; /// 基于描述词的索引 Index dwIDX On DescriptiveWords(ELEMENTS); /// 方法的作用是:演示如何在属性的子值上建立索引。 /// /// (如果DescriptiveWords被定义为一个集合,则不需要此方法。) ClassMethod DescriptiveWordsBuildValueArray( Words As %Library.String = "", ByRef wordArray As %Library.String) As %Status { If Words '= "" { For tPointer = 1:1:$Length(Words,",") { Set tWord = $Piece(Words,",",tPointer) If tWord '= "" { Set wordArray(tPointer) = tWord } } } Else { Set wordArray("TODO") = "Enter keywords for this person" } Quit $$$OK } ``` 在本例中,`dwIDX`索引基于`DescriptiveWords`属性。 `DescriptiveWordsBuildValueArray()`方法接受由`Words`参数指定的值,基于该值创建一个索引值数组,并将其存储在`wordArray`中。 InterSystems IRIS在内部使用`BuildValueArray()`实现; 不调用此方法。 注意:没有必要将任何元素/键值建立在属性值的基础上。 唯一的建议是,每次向该方法传递给定值时,都创建相同的元素和键数组。 为各种实例的描述性词所属性设置值和检查这些值的属性涉及活动(如以下): ```java SAMPLES>SET empsalesoref = ##class(MyApp.Salesperson).%OpenId(3) SAMPLES>SET empsalesoref.DescriptiveWords = "Creative" SAMPLES>WRITE empsalesoref.%Save() 1 SAMPLES>SET empsalesoref = ##class(MyApp.Salesperson).%OpenId(4) SAMPLES>SET empsalesoref.DescriptiveWords = "Logical,Tall" SAMPLES>WRITE empsalesoref.%Save() 1 ``` 这 `sample index`内容,例如: DescriptiveWords(ELEMENTS) | ID | Data ---|---|--- " CREATIVE" |3 |"" " ENTER KEYWORDS FOR THIS PERSON"| 1 |"" " ENTER KEYWORDS FOR THIS PERSON"| 2| "" " LOGICAL" |4| "" " TALL"| 4| "" 注意:此表显示抽象中的索引内容。磁盘上的实际存储形式可能会有所变化。 ### 将数组(元素)上的索引投影到子表 要在嵌入式对象中索引属性,需要在引用该嵌入式对象的持久化类中创建索引。 属性名必须指定表(`%Persistent`类)中的引用字段的名称和嵌入对象(`%SerialObject`)中的属性的名称,如下面的示例所示: ```java Class Sample.Person Extends (%Persistent) [ DdlAllowed ] { Property Name As %String(MAXLEN=50); Property Home As Sample.Address; Index StateInx On Home.State; } ``` 此处`Home`是`Sample.Person`中引用嵌入对象`Sample.Address`的属性,该对象包含`State`属性,如下例所示: ```java Class Sample.Address Extends (%SerialObject) { Property Street As %String; Property City As %String; Property State As %String; Property PostalCode As %String; } ``` **只有与持久类属性引用相关联的嵌入对象的实例中的数据值被索引。不能直接索引`%SerialObject`属性**。`%Library.SerialObject`(以及`%SerialObject`的所有未显式定义`SqlCategory`的子类)的`SqlCategory`为字符串。 还可以使用`SQL CREATE INDEX`语句在嵌入式对象属性上定义索引,如下例所示: ```sql CREATE INDEX StateIdx ON TABLE Sample.Person (Home_State) ``` ### 类中定义的索引注释 当在类定义中使用索引时,需要记住以下几点: - **索引定义仅从主(第一个)超类继承。** - **如果使用Studio添加(或删除)数据库中存储数据的类的索引定义,则必须使用“构建索引”中描述的过程之一来手动填充索引。** ## 使用DDL定义索引 如果你使用DDL语句来定义表,也可以使用以下DDL命令来创建和删除索引: - `CREATE INDEX` - `DROP INDEX` `DDL index`命令执行以下操作: 1. 它们更新在其上添加或删除索引的相应类和表定义。 重新编译修改后的类定义。 2. 它们根据需要在数据库中添加或删除索引数据:`CREATE index`命令使用当前存储在数据库中的数据填充索引。 类似地,`DROP INDEX`命令从数据库中删除索引数据(即实际索引)。
文章
Louis Lu · 四月 15, 2021

精华文章--访问IRIS数据平台的四种方式

IRIS 中支持的四种方式: SQL、Objects、REST 和 GraphQL 卡济米尔·马列维奇,《运动员》(1932) > > “你当然无法理解! 习惯了坐马车旅行的人怎么可能理解乘坐火车或者飞机旅行的人的感受和印象?”> > > > 卡济米尔·马列维奇 (1916)> ## 引言 我们已经讨论过为什么在主题领域建模使用对象类型优于使用 SQL。 当时得出的结论和总结的事实如今依然适用。 那么,我们为什么要退后到对象和类型之前的时代,讨论将对象的操作拖回到使用global的技术? 我们又为什么要鼓励面条式代码?难道是为了用它难以跟踪的错误考验开发者的技能熟练度? 目前有几种观点支持通过基于 SQL/REST/GraphQL 的 API 传输数据,而不是将其表示为类型/对象: 这些技术经过深入研究,相当易于部署。 知名度非常高,已在便捷的开源软件中广泛实现。 您通常别无选择,只能使用这些技术,尤其是在网络和数据库中。 最重要的是,API 仍然使用对象,因为它们提供了在代码中实现 API 的最适途径。 在讨论实现 API 之前,我们先来看一下底层的抽象层。 下图显示了数据在永久存储位置与处理并向应用程序用户呈现的位置之间的移动方式。 如今,数据存储在旋转硬盘驱动器 (HDD) 上,或者使用更现代的技术存储在 SSD 的闪存芯片中。 再使用由 HDD/SSD 上独立存储块组成的流完成数据的读取和写入。 分块并不是随机的, 而是由数据存储介质的物理学、力学和电子学特性决定。 在 HDD 中,这是旋转磁盘上的磁道/扇区。 在 SSD 中,这是可写硅芯片中的内存段。 它们本质上都是信息块,只有找到这些信息块并将其组合才能检索出需要的数据。 数据必须装配成将我们的数据模型/类型与查询时间对应值相匹配的结构。 数据的装配和检索过程由与操作系统中的文件子系统捆绑的 DBMS 负责。 我们可以直接寻址文件系统甚至 HDD/SSD,绕过 DBMS。 但这样一来就失去了两个极其重要的数据桥梁:存储块和文件流之间的桥梁,以及文件和数据库模型中的有序结构之间的桥梁。 换句话说,我们要负责开发所有处理块、文件和模型的代码,包括所有的优化、精细调试和长期可靠性测试。DBMS 使用可理解的模型和表示形式,给我们提供了以高级语言处理数据的绝好机会。 这也是这些系统的一大优势。 DBMS 和数据平台(如 InterSystems IRIS)提供了更多功能:能够以多种方式同时访问有序数据。 而在您的项目中,使用哪一种则取决于您自己。 利用 IRIS 提供的各种工具, 我们可以让代码更美观、更简洁。 我们将直接通过 ObjectScript 面向对象的语言来利用和开发 API。 也就是说,例如,我们会直接从 ObjectScript 软件内部调用 SQL 代码。 对于其他 API,我们将使用现成的库和内置的 ObjectScript 工具。 我们将以 SQLZoo Internet 项目为例,该项目提供了 SQL 的学习资源。 我们将在其他 API 示例中使用相同的数据。 如果您想了解 API 设计的各种方法并利用现成的解决方案,这里有一个有趣又实用的公共 API 集合,汇集到 GitHub 上的一个项目中。 SQL 最自然的方法莫过于从 SQL 开始。 这里没人对它不熟悉吧? SQL 的教程和书籍浩如烟海。 我们选择的是 SQLZoo。 这是一门很好的 SQL 入门课程,其中有示例,有操作步骤详解,也有语言引用。 我们将一些任务从 SQLZoo 带到 IRIS 平台,并使用各种方法解决。 您可以用多快的速度在您的电脑上访问 InterSystems IRIS? 最快的选择之一是在 Docker 中从现成的 InterSystems IRIS Community Edition 镜像部署容器。 InterSystems IRIS Community Edition 是 InterSystems IRIS Data Platform 的免费开发者版本。 其他方式:在学习门户中访问 InterSystems IRIS Community Edition 将数据从 SQLZoo 转移到我们自己的 IRIS 实例存储中。 需要执行以下操作: 打开管理门户(以我的为例,http://localhost:52773/csp/sys/UtilHome.csp)。 切换到 USER 命名空间 - 在 Namespace %SYS 中,点击“Switch”链接,选择 USER 转到 System > SQL - 依次打开 System Explorer、SQL,然后点击“Go”按钮。 右侧将打开“Execute query”标签页,带有“Execute”按钮,这就是我们需要的。 要详细了解如何通过管理门户网站使用 SQL,请参阅此文档。 在 Data 部分的描述中查看用于部署数据库和 SQLZoo 测试数据集的现成脚本。 这里有 world 表的几个直接链接: 用于创建 world 数据库的脚本 进入该表的数据 创建数据库的脚本可以在 IRIS 管理门户的 Query Executor 表单中执行。 CREATE TABLE world( name VARCHAR(50) NOT NULL ,continent VARCHAR(60) ,area DECIMAL(10) ,population DECIMAL(11) ,gdp DECIMAL(14) ,capital VARCHAR(60) ,tld VARCHAR(5) ,flag VARCHAR(255) ,PRIMARY KEY (name) ) 要加载 Query Executor 表单的测试集,首先转到 Wizards > Data Import 菜单。 请注意,在创建容器时,必须预先添加带有测试数据文件的目录,或者通过浏览器从计算机中加载。 该选项在数据导入向导的控制面板中可用。 在 Query Executor 表单中运行以下脚本,检查带有数据的表是否存在: SELECT * FROM world 接下来可以从 SQLZoo 网站访问示例和任务。 以下所有示例均要求您在第一次赋值中实现 SQL 查询: SELECT population FROM world WHERE name = 'France' 这样就可以将任务从 SQLZoo 转移到 IRIS 平台,保持与 API 的无缝协作。 请注意:我发现,SQLZoo 网站界面的数据与导出的数据不同。 至少在第一个示例中,法国和德国的人口值是不同的。 不必过度纠结。 使用[欧洲统计局数据](https://ec.europa.eu/eurostat/tgm/table.do?tab=table&language=en&pcode=tps00001&tableSelection=1&footnotes=yes&labeling=labels&plugin=1)作为参考。 另一种在 IRIS 中获得数据库 SQL 访问的便捷途径是 Visual Studio 代码编辑器与 SQLTools 插件和 SQLTools Driver for InterSystems IRIS。 这种解决方案很受开发者的欢迎,不妨一试。 为了顺利进行下一步并获得对我们数据库的对象访问权,让我们绕一个小弯,从“纯粹”的 SQL 查询转到[在 ObjectScript 中嵌入应用程序代码](https://docs.intersystems.com/iris20203/csp/docbook/Doc.View.cls?KEY=GSQL_esql)的 SQL 查询,ObjectScript 是 IRIS 内置的一种面向对象的语言。 如何在 VSCode 中设置 IRIS 访问并使用 ObjectScript 进行开发。 Class User.worldquery { ClassMethod WhereName(name As %String) { &sql( SELECT population INTO :population FROM world WHERE name = :name ) IF SQLCODE<0 {WRITE "SQLCODE error ",SQLCODE," ",%msg QUIT} ELSEIF SQLCODE=100 {WRITE "Query returns no results" QUIT} WRITE name, " ", population } } 在终端中查看结果: do ##class(User.worldquery).WhereName("France") 您应该会收到国家/地区名称和居民人数的响应。 对象 类型 接下来是 REST/GraphQL 的部分。 我们正在为 Web 协议实现一个 API。 大多数情况下,服务器端的后台源代码是以良好支持类型的语言开发的,甚至是完全面向对象的范式编写的。 包括:Java/Kotlin 中的 Spring、Python 中的 Django、Ruby on Rails、C# 中的 ASP.NET或者 TypeScript 中的 Angular。 当然,还有 IRIS 平台原生的 ObjectScript 中的对象。 为什么这很重要? 因为在发送时,代码中的类型和对象将简化为数据结构。 您需要考虑如何在程序中简化模型,这类似于考虑关系模型中的损失。 您还需要确保在 API 的另一侧,模型得到了充分的还原,可以不失真地投入使用。 这就带来了额外的负担:您作为程序员需要承担额外的责任。 在代码之外,除去转换器、编译器和其他自动工具的帮助,您还需要不断确保模型得到正确转移。 如果从另一个角度看待上述问题,我们还没有发现任何技术和工具可以用来轻松地将类型/对象从一种语言的程序转移到另一种语言的程序。 还剩下什么? 有 SQL/REST/GraphQL 的简化实现,以及大量用人类友好语言描述 API 的文档。 面向开发者的非正式(从计算机的角度)文档具体描述了应使用所有可用方法将哪些内容转换为正式代码,以便计算机处理。 程序员一直在开发不同的方法来解决上述问题。 其中一个成功的方法是 IRIS 平台对象 DBMS 中的跨语言范式。 下表应该可以帮助您理解 IRIS 中 OPP 与 SQL 模型之间的关系: 面向对象编程 (OOP) 结构化查询语言 (SQL) 包 Schema 类 表 属性 列 方法 存储过程 两个类之间的关系 外键约束,内置联接 对象(在内存中或磁盘上) 行(在磁盘上) 您可以从 [IRIS 文档]中详细了解如何显示对象和关系模型。 执行我们的 SQL 查询根据示例创建 world 表时,IRIS 将在名为 User.world 的类中自动生成对应对象的描述。 Class User.world Extends %Persistent [ ClassType = persistent, DdlAllowed, Final, Owner = {_SYSTEM}, ProcedureBlock, SqlRowIdPrivate, SqlTableName = world ] { Property name As %Library.String(MAXLEN = 50) [ Required, SqlColumnNumber = 2 ]; Property continent As %Library.String(MAXLEN = 60) [ SqlColumnNumber = 3 ]; Property area As %Library.Numeric(MAXVAL = 9999999999, MINVAL = -9999999999, SCALE = 0) [ SqlColumnNumber = 4 ]; Property population As %Library.Numeric(MAXVAL = 99999999999, MINVAL = -99999999999, SCALE = 0) [ SqlColumnNumber = 5 ]; Property gdp As %Library.Numeric(MAXVAL = 99999999999999, MINVAL = -99999999999999, SCALE = 0) [ SqlColumnNumber = 6 ]; Property capital As %Library.String(MAXLEN = 60) [ SqlColumnNumber = 7 ]; Property tld As %Library.String(MAXLEN = 5) [ SqlColumnNumber = 8 ]; Property flag As %Library.String(MAXLEN = 255) [ SqlColumnNumber = 9 ]; Parameter USEEXTENTSET = 1; /// Bitmap Extent Index auto-generated by DDL CREATE TABLE statement. 不要编辑此索引的 SqlName 。 Index DDLBEIndex [ Extent, SqlName = "%%DDLBEIndex", Type = bitmap ]; /// DDL Primary Key Specification Index WORLDPKey2 On name [ PrimaryKey, Type = index, Unique ]; } 您可以用这个模板来开发面向对象风格的应用程序。 您需要做的就是向 ObjectScript 中的类添加方法,ObjectScript 天生与数据库捆绑。 事实上,这个类的方法按 SQL 术语来说就是“存储过程”。 让我们用 SQL 实现之前完成的同一个示例。 将 WhereName 方法添加到 User.world 类中,充当输入的国家/地区名称的“Country information”对象设计器: ClassMethod WhereName(name As %String) As User.world { Set id = 1 While ( ..%ExistsId(id) ) { Set countryInfo = ..%OpenId(id) if ( countryInfo.name = name ) { Return countryInfo } Set id = id + 1 } Return countryInfo = "" } 在终端中查看: set countryInfo = ##class(User.world).WhereName("France") write countryInfo.name write countryInfo.population 从此示例可以看出,与 SQL 查询不同,为了通过国家/地区名称查找所需对象,我们需要手动对数据库记录进行逐一排序。 在最坏情况下,如果我们的对象位于列表末尾(或根本不在列表中),我们就不得不从头到尾将所有记录都整理一遍。 关于如何在 IRIS 中通过索引对象字段和自动生成类方法来加快搜索过程有一个单独的讨论。 有关更多详情,请参阅[文档]和[开发者社区门户]中的帖子。 例如,对于我们的类,了解 IRIS 从 WORLDPKey2 国家/地区名称生成的索引名称后,您可以使用单个快速查询从数据库中直接加载一个对象: set countryInfo = ##class(User.world).WORLDPKey2Open("France") 同时检查: write countryInfo.name write countryInfo.population 此文档中包含一些指导原则,可以帮助您决定使用对象还是 SQL 访问存储的内容。 当然,您也要始终牢记,您只能将其中之一完全用于您的任务。 此外,由于 IRIS 中提供了现成的二进制捆绑包,支持常见的 OOP 语言,例如 Java、Python、C、C#(.Net)、JavaScript,甚至是正在迅速普及的 Julia(参见 GitHub 和 OpenExchange),您总能找到最方便的语言开发工具。 接下来,让我们深入讨论 Web API 中的数据。 REST 或 RESTful Web API 跳出服务器和常见终端,转向一些更主流的接口:浏览器和类似应用程序。 这些应用程序依靠 HTTP 系列的超文本协议管理系统之间的交互。 IRIS 自带许多适合这个目的的工具,包括一个实实在在的数据库服务器和 Apache HTTP 服务器。 表述性状态转移 (REST) 是一种架构样式,用于设计分布式应用程序,尤其是 Web 应用程序。 REST 虽然流行,但它只是一套架构原则,而 SOAP 则是由 World Wide Web Consortium (W3C) 维护的标准协议,因此基于 SOAP 的技术具有标准支持。 REST 中的全局 ID 是 URL,当与数据库或后端应用程序交换时,由它定义每个连续的信息单元。 请参阅在 IRIS 中开发 REST 服务的文档。 在我们的示例中,基础标识符类似于 IRIS 服务器地址的基础 (http://localhost:52773),以及指向我们数据的 /world/ 子目录路径。 特别是我们的 /world/France 国家/地区目录。 在 Docker 容器中类似于: http://localhost:52773/world/France 如果要开发完整应用程序,务必查看 IRIS 文档推荐。 其中之一基于遵循 OpenAPI 2.0 规范的 REST API 描述。 让我们用简单的方法,手动实现 API。 在示例中,我们将创建最简单的 REST 解决方案,这个解决方案在 IRIS 中只需要两个步骤: 在 URL 内创建一个类路径监视器,它将继承 %CSP.REST 系统类 配置 IRIS web application时,添加对我们的监视器类的调用 第 1 步:创建类监视器 您应该清楚如何实现一个类。 按照文档中的说明手动创建REST。 Class User.worldrest Extends %CSP.REST { Parameter UseSession As Integer = 1; Parameter CHARSET = "utf-8"; XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ] { <Routes> <Route Url="/:name" Method="GET" Call="countryInfo" /> </Routes> } } 确保包括处理程序方法。 它执行的功能应该与先前示例中终端的调用完全相同: ClassMethod countryInfo(name As %String) As %Status { set countryInfo = ##class(User.world).WhereName(name) write "Country: ", countryInfo.name write "<br>" write "Population: ", countryInfo.population return $$$OK } 如您所见,以冒号开头的参数 :name 表示从传入的 REST 查询将监视器中被调用的处理程序方法的名称传递给参数。 第 2 步:配置 IRIS web application 在 System Administration > Security > Applications > Web Applications 下, 添加一个新的 web application,其 URL 入口地址为 /world,并添加以下处理程序:our worldrest monitor class。 配置完成后,转到 http://localhost:52773/world/France 时,web application应立即响应。 请记住,数据库区分大小写,因此在向方法参数传输请求数据时必须使用正确的字母大小写。 小诀窍: 根据需要使用调试工具。 您可以在这篇由两部分组成的文章中找到很好的描述(评论也值得一看)。 > 如果出现错误“401 Unauthorized”,并且您确定监视器类在服务器上,链接中也没有错误,请尝试在 Web 应用程序设置的 Application Roles 标签页添加 %All 角色。 这不是一种完全安全的方法,您需要了解允许访问所有角色可能带来的影响,但是这对于本地安装是可以接受的。 GraphQL 这是一个全新的领域,因为您在当前的 IRIS 文档中找不到关于使用 GraphQL 的 API 的任何内容。 然而,我们不能就这样停止使用这个出色的工具。 距 GraphQL 公开推出不过五年。 GraphQL 由 Linux Foundation 开发,是 API 的查询语言。 可以肯定地说,这是 REST 架构和各种 Web API 改进所带来的最好的技术。 这是是一篇简短介绍,供初学者参考。 并且,在 InterSystems 爱好者和工程师的努力下,IRIS 从 2018 年开始提供 GraphQL 支持。 > 这是一篇相关文章《为 InterSystems 平台实现 GraphQL》。 这里是 GraphQL 理解、解释和实现。 GraphQL 应用程序由两个模块组成:IRIS 侧的应用程序后端和在浏览器中运行的前端部分。 换句话说,您需要根据 GraphQL 和 GraphiQL Web 应用程序的说明进行配置。 例如,这是我的应用程序配置在 Docker 容器内的 IRIS 中的样子。 这些是 GraphQL Web 应用程序(充当 REST 监视器和数据库架构处理程序)的设置: 第二个 GraphQL 应用程序是浏览器的用户界面,使用 HTML 和 JavaScript 编写: 可前往 http://localhost:52773/graphiql/index.html 运行。 如果没有额外限制性设置,应用程序将立即拾取在安装区域中能找到的所有数据库schema。 这意味着我们的示例将立即开始工作。 另外,前端还提供了来自可用对象的一系列良好清晰的提示。 这是我们数据库的示例 GraphQL 查询: { User_world ( name: France ) { name population } } 这是匹配的响应: { "data": { "User_world": [ { "name": "France", "population": 65906000 } ] } } 在浏览器中是这样: 总结 技术名称 技术历史 查询示例 SQL 50 年 Edgar F. Codd SELECT population FROM world WHERE name = 'France' OOP 40 年 Alan Kay 和 Dan Ingalls set countryInfo = ##class(User.world).WhereName("France") REST 20 年 Roy Thomas Fielding http://localhost:52773/world/France GraphQL 5 年 Lee Byron { User_world ( name: France ) { name p opulation } } SQL、REST,可能还有 GraphQL 等技术均已获得大量投入。 它们的背后也都有着丰富的历史。 在 IRIS 平台内,这些技术都可以良好配合,创建数据处理程序。 虽然本文未提及,但 IRIS 也支持其他已充分实现的基于 XML (SOAP) 和 JSON 的 API。 除非您会特别注意,比如整理对象,否则,请记住通过 API 交换的数据仍然代表不完整的、精简的对象传输版本。 作为开发者,您(而不是代码)有责任确保正确地传输对象的数据类型信息。 尊敬的读者,请您提供反馈 本文的目的不仅仅是与现代 API 进行比较,更不是要回顾 IRIS 的基本功能。 它是为了帮助您了解,访问数据库时在 API 之间切换是多么容易。向 IRIS 迈出第一步,马上就能从任务中快速获得结果。 因此,我很想知道您的想法: 这种方式能否帮助您上手使用软件? 哪些流程步骤使您难以掌握用于在 IRIS 中使用 API 的工具? 您能说出一个您先前无法预见的障碍吗? 如果您认识仍在学习如何使用 IRIS 的用户,请让这些用户在下方发表评论。 这可以引起让所有参与者都能受益的讨论。
文章
姚 鑫 · 十二月 1, 2021

第二章 SQL谓词的概述(二)

# 第二章 SQL谓词的概述(二) # 谓词和`%SelectMode` 所有谓词都使用逻辑(内部存储)数据值进行比较。 但是,有些谓词可以对谓词值执行格式模式转换,将谓词值从`ODBC`或`Display`格式转换为`Logical`格式。 其他谓词不能执行格式模式转换,因此必须始终以`Logical`格式指定谓词值。 执行格式模式转换的谓词确定是否需要从匹配字段的数据类型(如`DATE``%List`)进行转换,并确定从`%SelectMode`设置进行转换的类型。 如果`%SelectMode`设置为逻辑格式以外的值(例如`%SelectMode=ODBC`或`%SelectMode=Display`),则必须以正确的ODBC或`Display`格式指定谓词值。 - 相等谓词执行格式模式转换。 IRIS将谓词值转换为逻辑格式,然后与字段值进行匹配。 如果`%SelectMode`设置为逻辑格式以外的模式,则必须以`%SelectMode`格式(ODBC或Display)指定显示值与逻辑存储值不同的数据类型的谓词值。 例如,日期、时间和`%list`格式的字符串。 因为IRIS会自动执行这种格式转换,所以在`Logical`格式中指定这种类型的谓词值通常会导致`SQLCODE`错误。 例如,`SQLCODE -146`“无法将日期输入转换为有效的逻辑日期值”(IRIS假设提供的逻辑值是ODBC或`Display`值,并试图将其转换为逻辑值——但没有成功)。 受影响的谓词包括`=`、``、`BETWEEN`和`IN`。 - 模式谓词不能执行格式模式转换,因为IRIS不能有意义地转换谓词值。 因此,谓词值必须以`Logical`格式指定,而不管`%SelectMode`设置如何。 以ODBC或`Display`格式指定谓词值通常会导致没有数据匹配或意外的数据匹配。 受影响的谓词包括`%INLIST`、`LIKE`、`%MATCHES`、`%PATTERN`、`%STARTSWITH`、`[`(`Contains`操作符)和](`Follows`操作符)。 可以使用`%INTERNAL`、`%EXTERNAL`或`%ODBCOUT`格式转换函数来转换谓词操作的字段。 这允许以另一种格式指定谓词值。 例如,`WHERE %ODBCOut(DOB) %STARTSWITH '1955-'`。 但是,在匹配字段上指定格式转换函数将阻止对该字段使用索引。 这可能会对性能产生显著的负面影响。 在以下动态SQL示例中,`BETWEEN`谓词(相等谓词)必须以`%SelectMode=1 (ODBC)`格式指定日期: ```java ClassMethod Predicates3() { s q1 = "SELECT Name,DOB FROM Sample.Person " s q2 = "WHERE DOB BETWEEN '1950-01-01' AND '1960-01-01'" s myquery = q1 _ q2 s tStatement = ##class(%SQL.Statement).%New() s tStatement.%SelectMode = 1 s qStatus = tStatement.%Prepare(myquery) if qStatus '= 1 { w "%Prepare failed:" d $System.Status.DisplayError(qStatus) q } s rset = tStatement.%Execute() d rset.%Display() w !,"End of data" } ``` ```java DHC-APP>d ##class(PHA.TEST.SQLCommand).Predicates3() Name DOB Houseman,Martin D. 1955-09-25 Ingrahm,Yan S. 1954-06-15 Smith,Elvis Y. 1955-06-29 Gore,Alfred M. 1958-09-15 Yoders,Liza U. 1959-06-05 Ng,Liza Z. 1955-10-05 Yeats,Debby G. 1951-12-06 ``` 在以下动态SQL示例中,`%STARTSWITH`谓词(模式谓词)不能执行格式模式转换。 第一个示例尝试以`%SelectMode=ODBC`格式为20世纪50年代的日期指定`%STARTSWITH`。 但是,由于该表不包含以`$HOROLOG 195`开始的出生日期(日期在`1894`年),所以没有选择行: ```java ClassMethod Predicates4() { s q1 = "SELECT Name,DOB FROM Sample.Person " s q2 = "WHERE DOB %STARTSWITH '195'" s myquery = q1 _ q2 s tStatement = ##class(%SQL.Statement).%New() s tStatement.%SelectMode = 1 s qStatus = tStatement.%Prepare(myquery) if qStatus '= 1 { w "%Prepare failed:" d $System.Status.DisplayError(qStatus) q } s rset = tStatement.%Execute() q rset.%Display() w !,"End of data" } ``` 下面的示例在匹配的DOB字段上使用`%ODBCOut`格式转换函数,以便`%STARTSWITH`可用ODBC格式选择`20`世纪`50`年代的年份。 但是,请注意,这种用法会阻止在`DOB`字段上使用索引。 ```java ClassMethod Predicates5() { s q1 = "SELECT Name,DOB FROM Sample.Person " s q2 = "WHERE %ODBCOut(DOB) %STARTSWITH '195'" s myquery = q1_q2 s tStatement = ##class(%SQL.Statement).%New() s tStatement.%SelectMode=1 s qStatus = tStatement.%Prepare(myquery) if qStatus '= 1 { w "%Prepare failed:" d $System.Status.DisplayError(qStatus) q } s rset = tStatement.%Execute() d rset.%Display() w !,"End of data" } ``` ```java DHC-APP>d ##class(PHA.TEST.SQLCommand).Predicates5() Name DOB Houseman,Martin D. 1955-09-25 Ingrahm,Yan S. 1954-06-15 Smith,Elvis Y. 1955-06-29 Gore,Alfred M. 1958-09-15 Yoders,Liza U. 1959-06-05 Ng,Liza Z. 1955-10-05 Yeats,Debby G. 1951-12-06 Zweifelhofer,Zelda J. 1954-02-19 ``` 在下面的示例中,`%STARTSWITH`谓词为逻辑(内部)格式的日期指定了`%STARTSWITH`。 选择`DOB`逻辑值以`41`开始的行(日期从1953年4月4日(`$HOROLOG 41000`)到1955年12月28日(`$HOROLOG 41999`))。 使用`DOB`字段索引: ```java lassMethod Predicates6() { s q1 = "SELECT Name,DOB FROM Sample.Person " s q2 = "WHERE DOB %STARTSWITH '41'" s myquery = q1 _ q2 s tStatement = ##class(%SQL.Statement).%New() s tStatement.%SelectMode = 1 s qStatus = tStatement.%Prepare(myquery) if qStatus '= 1 { w "%Prepare failed:" d $System.Status.DisplayError(qStatus) q } s rset = tStatement.%Execute() d rset.%Display() w !,"End of data" } ``` ```java DHC-APP>d ##class(PHA.TEST.SQLCommand).Predicates6() Name DOB Houseman,Martin D. 1955-09-25 Ingrahm,Yan S. 1954-06-15 Smith,Elvis Y. 1955-06-29 Ng,Liza Z. 1955-10-05 Zweifelhofer,Zelda J. 1954-02-19 Zampitello,Josephine Q. 1953-08-14 Hertz,Uma C. 1954-07-25 Davis,Jane E. 1953-07-28 Vanzetti,Alexandra O. 1953-12-29 9 Rows(s) Affected ``` # 谓词和PosixTime、时间戳和日期 相等谓词比较自动在这些不同的`date`和`datetime`表示之间执行转换。 这个转换与`%SelectMode`无关。 因此,下面是所有有意义的比较谓词: ```sql WHERE MyPosixField = MyTimestampField WHERE MyPosixField < CURRENT_TIMESTAMP WHERE MyPosixField BETWEEN DATEADD('month',-1,CURRENT_TIMESTAMP) AND $HOROLOG WHERE MyPosixField BETWEEN DATEADD('day',-1,CURRENT_DATE) AND LAST_DAY(CURRENT_DATE) ``` 模式谓词比较,如`%STARTSWITH`,不执行不同日期和日期时间表示之间的转换。 对实际存储数据值的操作。 # 取消文字替换 通过将谓词参数括在双圆括号中,可以在编译前分析期间禁止文字替换。例如,`LIKE((‘ABC%’))`。这可以通过提高总体选择性和/或下标绑定选择性来提高查询性能。但是,当使用不同的值多次调用同一查询时,应该避免这种情况,因为这将导致为每个查询调用创建一个单独的缓存查询。 # 示例 下面的示例在查询的WHERE子句中使用了各种条件: ```sql SELECT PurchaseOrder FROM MyTable WHERE OrderTotal >= 1000 AND ItemName %STARTSWITH :partname AND AnnualOrders BETWEEN 50000 AND 100000 AND City LIKE 'Ch%' AND CustomerNumber IN ( SELECT CustNum FROM TheTop100 WHERE TheTop100.City='Boston' ) AND :minorder > SOME ( SELECT OrderTotal FROM Orders WHERE Orders.Customer = :cust ) ```
问题
Yufeng Li · 十二月 23, 2021

在IRIS 上怎么实现读写分离

在IRIS 上怎么实现读写分离? IRIS的镜像(Mirroring)支持多个报告类型(Reporting Asyncs)的异步成员,这些异步成员可以用于查询、报表运行、BI等多种场景。 我们现在是用前端调用rest api,这种情况下要怎么区分调用读服务器还是写服务器? 可以封装一个REST服务用于前端api调用,这个REST服务根据不同api操作和路径,在后台调用不同服务器上的REST API或SQL操作。或者也可以考虑使用InterSystems API管理器做这个事。 相关文章请参考:https://cn.community.intersystems.com/smartsearch?search=API 如果是指数据库层的读写分离,可以使用Sharding技术,利用Sharding技术中的计算结点和数据结点,搭建负载均衡+读写分离的数据库集群,参考: https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI.Page.cls?KEY=GSCALE_sharding#GSCALE_sharding_reference_plan_qs 如果服务器/实例资源有限,又想实现读负载与写负载的分离,那么基于@Qiao Peng指出的镜像异步成员,在API层(即应用程序层)通过业务流程来控制查询/写入操作的分发则是成本较低的方案。 请参考这个文档链接[Deploy Compute Nodes for Workload Separation and Increased Query Throughput](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GSCALE_sharding#GSCALE_sharding_options_qs)
文章
Michael Lei · 三月 12, 2022

Linux TZ环境变量未被设置以及对Caché的影响

在最近的大规模基准测试活动中,我们看到过多的%sys CPU时间,对应用程序的扩展产生了负面影响。 问题 我们发现,由于TZ环境变量没有被设置,很多时间都花在了localtime()系统调用上。 我们创建了一个简单的测试程序来证实这一观察结果,设置了TZ与未设置TZ的时间差和所需的CPU资源都是惊人的。 我们发现,当TZ没有设置时,从localtime()继承使用stat()系统调用到/etc/local_time是成本很高。 建议 InterSystems强烈建议在任何Linux安装中,无论是x86还是Linux on Power,都要确认TZ环境变量的设置是否适当,以获得最佳性能。 更多详情请见。"man tzset"。 目前的Caché 2016.1现场测试包含了对日期和时间功能的优化,初步测试表明有很大的改进。 下面是测试新的内部函数调用的样本输出,在这两种情况下,在Power上的Linux都没有设置TZ。 在Caché 2016.1 FT之前: real 0m22.60s user 0m1.64s sys 0m20.89s 带有Caché 2016.1 FT: real 0m0.40s user 0m0.37s sys 0m0.00s 请在评论区说明你的应用程序在TZ环境变量方面的任何经验,以及Caché 2016.1现场测试在设置或不设置TZ时的影响。谢谢!
公告
Michael Lei · 一月 7

新增和即将推出的嵌入式 Git 功能

我们已经有一段时间没有在开发者社区上发表关于嵌入式 Git 的文章了,我想借此机会更新一下今年我们完成的大量工作以及未来的工作计划。 背景信息 如果您要在 IRIS 上构建解决方案,并想要使用 Git,那就太棒了! 只需将 VSCode 与本地 Git 仓库结合使用,并将更改推送到服务器上即可,就是这么简单。 但在以下使用场景中该怎么办: 您与其他开发者在共享的远程开发环境中合作,并想要避免因同时编辑同一文件而影响彼此的工作 您使用位于管理门户中的编辑器来实现互操作性或业务智能,并希望对您的工作直接进行源代码控制,即使在本地容器中也希望如此 您仍在使用 Studio 处理一些任务并且/或偶尔会再次从 VSCode 跳转回 Studio;或者您的团队还没有完全接受 VSCode,一些团队成员仍想使用 Studio,而其他人在使用 VSCode 您同时在同一命名空间中处理多个独立的项目,例如使用 InterSystems Package Manager 定义的多个软件包,并想仅通过一个 isfs 编辑视图使用所有软件包(而不是在多个单独的项目中使用),同时在合适的本地 git 仓库中自动跟踪更改 在以上任何情况下,您都非常需要嵌入式源代码控制。 您可能听说过“服务器端源代码控制”或“源代码控制挂钩”这类叫法,它们的含义相同,都表示可在所有编辑器(包括 IDE 和图形编辑器)中对 IRIS 实例实现一致的源代码控制行为。 所有控制操作都是针对与您的 IRIS 实例位于同一位置的源代码控制仓库执行的,该仓库可能位于远程服务器上、您自己的机器上,甚至可能位于容器中。 如果您希望开始使用 Git 进行嵌入式源代码控制,可以先从嵌入式 Git (https://github.com/intersystems/git-source-control) 着手。 嵌入式 Git 不是 InterSystems 支持的产品,但得到了 InterSystems 内我的团队(应用程序服务团队)和广大用户社区的大力支持。 我们始终欢迎大家提出 PR 和 GitHub 议题,我们会监控开发者社区活动(特别是使用相对较新的“嵌入式 Git”标签)。 2024 年嵌入式 Git 工作回顾 2024 年初,我曾向已能轻松使用 Git 并愿意在必要时进行深入研究、更偏向技术型的用户推荐嵌入式 Git。 现在,我极力推荐所有人使用嵌入式 Git。 为了展示我们对此工具倾注的心血以及我们在 2024 年取得的飞跃,下方是过去几年的提交量图: 如果您想了解我们的最新动态,可以查看版本或我们的变更日志,但下面是对要点的总结: 7 月,我们添加了“基本模式”(具体说明请参阅此处),其中的“同步”操作可以简化拉取/提交/变基/推送工作流,我们推荐大家在一般情况下使用该模式,尤其是那些不太熟悉 Git 的用户。 在多个版本中,我们将 git 拉取和签出操作(实际上是任何修改 Git 仓库状态的操作)无缝同步回 IRIS,从而无需强制重新加载整个代码库便可确保所有内容均为最新状态。 在我们的 9 月版本中,通过从管理门户下载 VSCode 工作空间文件,您可以非常轻松地建立 isfs 连接。 在我们的 11 月版本中,可以通过扩展设置页面进行更多配置,无需在终端运行命令,并针对互操作性设置中的两种最常见(几乎一定会出现)的用例增加了智能合并冲突解决功能。 除了以上工作之外,我们还解决了数十个小错误和实用性问题,简化了管理门户的导航,并增加了很多小功能,以简化互操作性用例以及跨多个开发者特定命名空间在共享远程实例上的协作(我们认为这是一种常用方式,并推荐大家使用)。 即将推出 下一个版本 (2.8.0) 预计在两周内发布,其中将包含我们历经数月开发出的“生产分解”功能。 互操作性生产通常以单个文件的形式进行源代码控制,由于一次只能由一人编辑生产,这会导致并发问题。 共享开发环境中还存在一些合并问题,这些问题几乎一定会出现,虽然我们可以通过智能化方式解决它们,但在某些分支工作流中,这些问题很可能会继续出现。 利用生产分解功能,每个业务主机(服务、进程或操作)在源代码控制中都会以单个文件的形式表示。 如果您有兴趣尝试此功能,请联系我们! 进入 2025 年,我们将继续保持嵌入式 Git 的发展势头,重点关注其他身份验证方法并支持其他常见部署模式。 您可以访问 2025 H1 里程碑了解我们的大致计划(其中包括几次版本发布;我们通常会每月发布次要版本,并根据需要发布补丁版本修复重要错误)。 如果您有兴趣深入了解开发细节/获取最新开发信息,欢迎加入我们每周五召开的利益相关者会议。 此会议也成为了嵌入式 Git 的一种“办公时间”。 请随时留言告诉我您的电子邮件地址,我会邀请您加入会议。
文章
Hao Ma · 一月 10, 2021

使用规范优先的方式开发REST API

在本文中,我想谈一谈规范优先的 REST API 开发方式。 传统的代码优先 REST API 开发是这样的: * 编写代码 * 使其支持 REST * 形成文档(成为 REST API) 规范优先遵循同样的步骤,不过是反过来的。 我们先制定规范(同时兼做文档),然后根据它生成一个样板 REST 应用,最后编写一些业务逻辑。 这是有好处的,因为: * 对于想要使用你的 REST API 的外部或前端开发者,你总是有相关且有用的文档 * 使用 OAS (Swagger) 创建的规范可以导入各种工具,从而进行编辑、客户端生成、API 管理、单元测试和自动化,或者许多其他任务的简化 * 改进了 API 架构。 在代码优先的方式中,API 是逐个方法开发的,因此开发者很容易失去对整体 API 架构的跟踪,但在规范优先的方式中,开发者被强制从 API 使用者的角度与 API 进行交互,这通常有助于设计出更简洁的 API 架构 * 更快的开发速度 - 由于所有样板代码都是自动生成的,你无需编写代码,只需开发业务逻辑。 * 更快的反馈循环 - 使用者可以立即查看 API,并且只需修改规范即可轻松提供建议 让我们以规范优先的方式开发 API 吧! ### 计划 1. 使用 swagger 制定规范 * Docker * 本地 * 在线 2. 将规范加载到 IRIS 中 * API 管理 REST API * ^REST * 类 3. 我们的规范会怎样? 4. 实现 5. 进一步开发 6. 注意事项 * 特殊参数 * CORS 7. 将规范加载到 IAM 中   ### 制定规范   勿庸置疑,第一步是编写规范。 InterSystems IRIS 支持 Open API 规范 (OAS): > **OpenAPI 规范** (以前称为 Swagger 规范)是 REST API 的 API 描述格式。 OpenAPI 文件允许描述整个 API,包括: > > * 可用端点 (`/users`) 和每个端点上的操作(`GET /users`、`POST /users`) > * 每次操作的操作参数输入和输出 > * 身份验证方法 > * 联系信息、许可证、使用条款和其他信息。 > > API 规范可以使用 YAML 或 JSON 编写。 格式易于学习,并且对人和机器都可读。 完整的 OpenAPI 规范可在 GitHub 上找到:[OpenAPI 3.0 规范](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md)   - 来自 Swagger 文档。 我们将使用 Swagger 编写 API。 使用 Swagger 有几种方法: * [在线](https://editor.swagger.io/) * Docker:`docker run -d -p 8080:8080 swaggerapi/swagger-editor` * [本地安装](https://swagger.io/docs/open-source-tools/swagger-editor/) 安装/运行 Swagger 后,你应该在 Web 浏览器中看到以下窗口: 在左侧编辑 API 规范,在右侧可以立即看到渲染的 API 文档/测试工具。 我们将第一个 API 规范加载到其中(使用 [YAML](https://en.wikipedia.org/wiki/YAML))。 这是一个简单的 API,包含一个 GET 请求 - 返回指定范围内的随机数。   Math API 规范 swagger: "2.0" info: description: "Math" version: "1.0.0" title: "Math REST API" host: "localhost:52773" basePath: "/math" schemes: - http paths: /random/{min}/{max}: get: x-ISC_CORS: true summary: "Get random integer" description: "Get random integer between min and max" operationId: "getRandom" produces: - "application/json" parameters: - name: "min" in: "path" description: "Minimal Integer" required: true type: "integer" format: "int32" - name: "max" in: "path" description: "Maximal Integer" required: true type: "integer" format: "int32" responses: 200: description: "OK" 以下是其包含的内容。 有关 API 和使用的 OAS 版本的基本信息。 swagger: "2.0" info: description: "Math" version: "1.0.0" title: "Math REST API" 服务器主机、协议(http、https)和 Web 应用程序名称: host: "localhost:52773" basePath: "/math" schemes: - http 接下来指定路径(完整的 URL 是 `http://localhost:52773/math/random/:min/:max`)和 HTTP 请求方法(get、post、put、delete): paths: /random/{min}/{max}: get: 之后,指定有关请求的信息: x-ISC_CORS: true summary: "Get random integer" description: "Get random integer between min and max" operationId: "getRandom" produces: - "application/json" parameters: - name: "min" in: "path" description: "Minimal Integer" required: true type: "integer" format: "int32" - name: "max" in: "path" description: "Maximal Integer" required: true type: "integer" format: "int32" responses: 200: description: "OK" 在此部分中,我们定义请求: * 为 CORS 启用此路径(稍后将详细介绍) * 提供 _summary_ 和 _description_ * _operationId_ 允许规范内引用,它也是我们的实现类中生成的方法名 * _produces_ - 响应格式(例如文本、xml、json) * _parameters_ 指定输入参数(在 URL 或正文中),在我们的示例中,我们指定 2 个参数 - 随机数生成器的范围 * _responses_ 列出服务器的可能响应 如你所见,这种格式并不是特别有挑战性,虽然还有很多可用功能。这里是[规范](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md)。 最后,我们将定义导出为 JSON。 转到“文件 → 转换”并另存为 JSON。 规范应如下所示:   Math API 规范 { "swagger": "2.0", "info": { "description": "Math", "version": "1.0.0", "title": "Math REST API" }, "host": "localhost:52773", "basePath": "/math", "schemes": [ "http" ], "paths": { "/random/{min}/{max}": { "get": { "x-ISC_CORS": true, "summary": "Get random integer", "description": "Get random integer between min and max", "operationId": "getRandom", "produces": [ "application/json" ], "parameters": [ { "name": "min", "in": "path", "description": "Minimal Integer", "required": true, "type": "integer", "format": "int32" }, { "name": "max", "in": "path", "description": "Maximal Integer", "required": true, "type": "integer", "format": "int32" } ], "responses": { "200": { "description": "OK" } } } } } }   ### 将规范加载到 IRIS 中 现在我们有了规范,我们可以在 InterSystems IRIS 中为此 REST API 生成样板代码。 要进入此阶段,我们需要三个东西: * REST 应用程序名称:我们生成的代码的包(假定为 `math) JSON 格式的 OAS 规范:我们刚刚在上一步中创建 Web 应用程序名称:用于访问我们的 REST API 的基本路径(我们的示例中为 /math`) 有三种方法使用我们的规范来生成代码,它们本质上是相同的,只是提供了多种访问相同功能的方式 1. 调用 `^%REST` 例程(在交互式终端会话中 `Do ^%REST`), [参见文档](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GREST_routine)。 2. 调用 `%REST` 类(`Set sc = ##class(%REST.API).CreateApplication(applicationName, spec)`,非交互式),[参见文档](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GREST_objectscriptapi)。 3. 使用 API 管理 REST API,[参见文档](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GREST_apimgmnt)。 我认为文档足以描述所需的步骤,因此选择一个即可。 我再补充两点说明: * 在第 1 种和第 2 种方法中,可以向动态对象传递文件名或 URL * 在第 2 种和第 3 种方法中,**必须** 进行一个额外的调用才能创建 Web 应用程序:`set sc = ##class(%SYS.REST).DeployApplication(restApp, webApp, authenticationType)`,所以在我们的示例中为 `set sc = ##class(%SYS.REST).DeployApplication("math", "/math")`,从 `%sySecurity` 包含文件获取 `authenticationType` 参数的值,相关条目为 `$$$Authe*`,因此对于未经身份验证的访问,传递 `$$$AutheUnauthenticated`。 如果省略,该参数默认为密码身份验证。   ### 我们的规范会怎样? 如果你已成功创建应用,新的 `math` 包应该包含三个类: * _Spec_ - 按原样存储规范。 * _Disp_ - 在调用 REST 服务时直接调用。 它封装 REST 处理并调用实现方法。 * _Impl_ - 保存 REST 服务的实际内部实现。 你只应该编辑此类。 [文档](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GREST_intro#GREST_intro_classes)包含有关这些类的更多信息。 ### 实现 最初,我们的实现类 `math.impl` 只包含一个方法,对应于我们的 `/random/{min}/{max}` 操作: /// Get random integer between min and max /// The method arguments hold values for: /// min, Minimal Integer /// max, Maximal Integer ClassMethod getRandom(min As %Integer, max As %Integer) As %DynamicObject { //(Place business logic here) //Do ..%SetStatusCode() //Do ..%SetHeader(,) //Quit (Place response here) ; response may be a string, stream or dynamic object } 让我们从简单的实现开始: ClassMethod getRandom(min As %Integer, max As %Integer) As %DynamicObject { quit {"value":($random(max-min)+min)} } 最后,我们可以通过在浏览器中打开此页面来调用我们的 REST API:`http://localhost:52773/math/random/1/100` 输出应该是: { "value": 45 } 此外,在 Swagger 编辑器中按 `Try it out`(试用)按钮并填写请求参数也会发送同样的请求: 恭喜! 我们使用规范优先的方式创建的第一个 REST API 现在已经生效!   ### 进一步开发 当然,我们的 API 不是静态的,我们需要添加新路径等等。 在规范优先的开发中,首先要修改规范,然后更新 REST 应用程序(调用与创建应用程序相同),最后编写代码。 请注意,规范更新是安全的:你的代码不会受到影响,即使从规范中删除路径,在实现类中也不会删除方法。   ### 注意事项 更多说明! #### 特殊参数 InterSystems 向 swagger 规范添加了特殊参数,如下所示: 名称 数据类型 默认值 位置 描述 x-ISC_DispatchParent 类名 %CSP.REST 信息 调度类的超类。 x-ISC_CORS 布尔 false 操作 一个标志,指示对此端点/方法组合的 CORS 请求应该获得支持。 x-ISC_RequiredResource 数组   操作 以逗号分隔的已定义资源及其访问模式(资源:模式)的列表,这些资源和模式是访问 REST 服务的此端点所必需的。 示例:["%Development:USE"] x-ISC_ServiceMethod 字符串   操作 后端调用的用于维护此操作的类方法的名称;默认值为 operationId,通常就很合适。   #### CORS 有三种方法启用 CORS 支持。 1. 在逐条路由的基础上,将 `x-ISC_CORS` 指定为 true。 我们的 Math REST API 中就是这样做的。 2. 在每个 API 的基础上,添加 Parameter HandleCorsRequest = 1; 然后重新编译该类。 规范更新后它也会保留。 3. (推荐)在每个 API 的基础上,实现自定义调度器超类(应该扩展 `%CSP.REST`)并编写 CORS 处理逻辑。 要使用此超类,请将 `x-ISC_DispatchParent` 添加到规范中。 ### 将规范加载到 IAM 中   最后,我们将规范添加到 IAM中,以便将其发布给其他开发者。 如果您尚未开始使用 IAM,请参见[此文章](https://community.intersystems.com/post/introducing-intersystems-api-manager)。 它还涵盖了通过 IAM 提供 REST API,所以我在这里不做介绍。 您可能需要修改规范的 `host` 和 `basepath` 参数,使它们指向 IAM,而不是 InterSystems IRIS 实例。 打开 IAM 管理员门户,转到相关工作区的 `Specs`(规范)选项卡。 点击 `Add Spec`(添加规范)按钮并输入新 API 的名称(我们的示例中为 `math`)。 在 IAM 中创建新规范后,点击 `Edit`(编辑)并粘贴规范代码(JSON 或 YAML - IAM 都支持): 不要忘记点击 `Update File`(更新文件)。 现在我们的 API 已发布给开发者。 打开开发者门户,然后点击右上角的 `Documentation`(文档)。 除了三个默认 API,还应该看到我们的新 `Math REST API`: 打开它: 现在,开发者可以看到我们的新 API 的文档,并在同一个地方试用它! ###   ### 结论   InterSystems IRIS 简化了 REST API 的开发过程,规范优先的方式使 REST API 生命周期管理更快更简单。 通过这种方式,你可以使用各种工具来完成各种相关任务,例如客户端生成、单元测试、API 管理等等。   ### 链接 * [OpenAPI 3.0 规范](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md) * [创建 REST 服务](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GREST) * [从 IAM 开始](https://community.intersystems.com/post/introducing-intersystems-api-manager) * [IAM 文档](https://docs.intersystems.com/irislatest/csp/docbook/apimgr/index.html)
文章
Louis Lu · 六月 11, 2024

使用 IRIS 以及 langchain 构建 问答聊天机器人

这篇文章介绍了使用由支持 langchain 框架的IRIS来实现问答聊天机器人,其重点介绍了检索增强生成(RAG)。 文章探讨了IRIS中的向量搜索如何在langchain-iris中完成数据的存储、检索和语义搜索,从而实现对用户查询的精确、快速的响应。通过无缝集成以及索引和检索/生成等流程,由IRIS驱动的RAG应用程序使InterSystems开发者能够利用GenAI系统的能力。 为了帮助读者巩固这些概念,文章提供了Jupyter notebook和一个完整的问答聊天机器人应用程序,以供参考。 什么是RAG以及它在问答聊天机器人中的角色 RAG,即检索增强生成,是一种通过整合超出初始训练集的补充数据来丰富语言模型(LLM)知识库的技术。尽管LLM在跨不同主题进行推理方面具有能力,但它们仅限于在特定截止日期之前训练的公共数据。为了使AI应用程序能够有效处理私有或更近期的数据,RAG通过按需补充特定信息来增强模型的知识。这是一种替代微调LLM的方法,微调可能会很昂贵。 在问答聊天机器人领域,RAG在处理非结构化数据查询中发挥着关键作用,包括两个主要组成部分:索引和检索/生成。 索引从数据源摄取数据开始,然后将其分割成更小、更易于管理的块以进行高效处理。这些分割的块随后被存储和索引,通常使用嵌入模型和向量数据库,确保在运行时能够快速准确地检索。 在检索和生成过程中,系统在接收到用户查询后,使用与索引阶段相同的嵌入模型生成嵌入向量,然后使用检索器组件从索引中检索相关数据块。这些检索到的段落随后传递给LLM以生成答案。 因此,RAG赋予了问答聊天机器人访问和利用结构化和非结构化数据源的能力,从而通过使用嵌入模型和向量数据库作为LLM微调的替代方案,增强了它们提供精确和最新用户查询响应的能力。 IRIS 向量搜索 InterSystems IRIS的向量搜索是一个新功能,它在数据库内启用了语义搜索和生成式AI能力。它允许用户根据数据的含义而不是原始内容来查询数据,利用了检索增强生成(RAG)架构。这项技术将非结构化数据(如文本)转换为结构化的向量,便于高效处理和响应生成。 该平台支持在关系模式中以压缩和高性能的向量类型(VECTOR)存储向量,允许与现有数据结构无缝集成。向量通过Embeddings表示语言的语义含义,相似的含义在高维几何空间中通过接近度反映出来。 通过使用点积(dot product)操作比较输入向量和存储的向量,用户可以确定两者的语义相似性,这非常适合信息检索等任务。IRIS还通过专用的向量数据类型提供高效的向量存储和操作,增强了对大型数据集操作的性能。 要利用这一能力,文本必须通过一系列步骤转换为嵌入,涉及文本预处理和模型实例化。InterSystems IRIS支持Python代码的无缝集成用于嵌入生成,以及ObjectScript用于数据库交互,使基于向量的应用实现顺畅。 你可以在这里查看更多有关向量搜索的文档和实例。 langchain-iris 简短的说,langchain-iris 是在 langchain 框架中使用 InterSystems IRIS 向量搜索的一种方式。 InterSystems IRIS 向量搜索与 langchain 的向量存储需求非常契合。IRIS 存储和检索embedding的数据,对于相似性搜索至关重要。凭借IRIS的 VECTOR 类型,IRIS 支持存储embeddings,使其可对非结构化数据进行语义搜索,并促进文档无缝处理到向量存储中。 通过利用点积比较等操作,IRIS 促进了语义相似性的比较算法,这对于 langchain 的相似性搜索需求也非常理想。 因此,langchain-iris 允许使用由 InterSystems IRIS 数据平台支持的 langchain 框架开发 RAG 应用程序。 有关 langchain-iris 的更多信息,请查看这里。 将 IRIS 作为 langchain 的向量存储目标 第一步,需要将langchain-iris导入 pip install langchain-iris 之后,可以使用 IRISVector 中的方法 from_documents() : db = IRISVector.from_documents( embedding=embeddings, documents=docs, collection_name=COLLECTION_NAME, connection_string=CONNECTION_STRING, ) 其中: embedding:设置langchain.embeddings的embeddings模型实例,比如OpenAI或hugging faces. documents:是一系列字符串的数组,这些字符串将被应用于embedding模型,并且生成的向量将存储在IRIS中。通常,由于embedding模型的大小限制以及为了更好的管理,文档应该被分割;langchain框架提供了几种分割器。 collection_name:用于文档以及他的embedding 向量存储的表的名称 connection_string:用于连接IRIS的参数,通常使用下面的格式 iris://<username>:<password>@<hostname>:<iris_port>/<namespace> 在这里查看完整的使用 langchian-iris 的 hello world 代码。 进一步查看整个过程 首先,我们查看由langchain提供的原始版本的文档机器人示例。 这个原始示例中,使用了Chroma作为向量数据库: from langchain_chroma import Chroma … vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings()) 所以,我们这里修改使用IRIS 作为向量数据库: vectorstore = IRISVector.from_documents( embedding=OpenAIEmbeddings(), documents=splits, collection_name="chatbot_docs", connection_string=iris://_SYSTEM:SYS@localhost:1972/USER', ) 你可以在这个jupter notebook里查看完整示例代码。 当运行完示例,我们可以在IRIS中查看由 langchain-iris 创建的表,表名由SQLUser包名以及collection_name中设置: 可以看到其中有四个字段组成: id:文档id dcoument:使用的文档或者将文档分割为文字块后传入的文字块 metadata:JSON对象包含了文档的相关信息 embedding::以embedding vector的方式保存的文档信息,这是IRIS的向量搜索功能中的VECTOR类型数据。 上面实现的是创建索引的过程。也就是langchain对每一个文档进行分割后,应用embedding 模型,并将其向量存储在IRIS中。 下一步,为了实现RAG应用,我们需要根据给定的查询字符串,在 IRIS 中查询最相关的文档,这是通过实现langchain框架中的检索器(retrievers)来实现的。 你可以使用下面的代码创建一个真对IRIS存储文档的检索器 (retriever): retriever = vectorstore.as_retriever() 用这个检索器,您可以针对自然语言查询最相似的文档。langchain框架将使用索引步骤中使用的相同embedding模型从查询中提取向量。这样,就可以检索到与查询具有相似语义内容的文档片段。 为了举例说明,让我们使用langchain的例子,它索引了一个包含有关LLM(大型语言模型)代理信息的网页。这个页面解释了几个概念,比如任务分解。让我们看看,如果给定一个查询,比如“What are the approaches to Task Decomposition(任务分解的方法有哪些)?”,检索器会返回什么: 现在让我们执行一个语义上相同但句法上不同的查询,即使用具有相似含义的不同词语提问,来看看向量搜索引擎返回什么结果: 这里,我们可以看到即使传递不同的查询字符串,结果也几乎是相同的。这意味着嵌入向量在某种程度上抽象了文档和查询字符串中的语义。 为了进一步证明这种语义查询能力,现在让我们继续询问有关任务分解的问题,但这次询问它的潜在缺点: 可以看到,这次最相关的结果与之前的查询不同。此外,最初的结果中并没有直接出现“downside”这个词,但包含了一些相关词汇,如“challenges”(挑战)、“limitations”(限制)和“restricted”(受限)。 这加强了嵌入向量在向量数据库中进行语义搜索的能力。 检索步骤之后,最相关的文档被添加为上下文信息,与用户查询一起发送到LLM(大型语言模型)进行处理: from langchain import hub prompt = hub.pull("rlm/rag-prompt") user_query = "What are the approaches to Task Decomposition?" retrieved_docs = [doc.page_content for doc in retriever.invoke(user_query)] example_messages = prompt.invoke( {"context": "filler context", "question": user_query} ).to_messages() print(example_messages[0].content) 这段代码将会生成提示词如下,可以看到,查询的返回结果作为上下文一起递交给LLM进行处理: """ You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise. Question: What are the approaches to Task Decomposition? Context: Tree of Thoughts (Yao et al. 2023) extends CoT by exploring multiple ... (truncated in the sake of brevity) Answer: """ 于是,RAG应用可以在LLM提示词长度限制的情况下增强查询的准确度。 最后几句 总结来说,将IRIS向量搜索与langchain框架的集成为InterSystems开发者社区中的问答聊天机器人和其他依赖于语义搜索和生成式AI的应用程序的开发开辟了新的视野。 通过langchain-iris将IRIS作为向量存储的无缝集成简化了实现过程,为开发者提供了一个强大而高效的解决方案,用于管理和查询大量结构化和非结构化信息的数据集。 通过索引、检索和生成过程,由IRIS向量搜索驱动的RAG应用程序可以有效地利用公共和私有数据源,增强基于LLM的AI系统的能力,为用户提供更全面和最新的响应。 最后,如果您想更深入地了解并查看一个完整的应用程序实现这些概念,以及与其他功能如互操作性和业务主机一起,与OpenAI和Telegram等外部API通信,请查看我们的应用程序iris-medicopilot。 这种集成展示了如何利用先进的技术来构建智能系统,这些系统不仅能够理解和回应用户的查询,还能够与外部服务和API进行交互,提供更加丰富和动态的用户体验。随着技术的不断发展,我们可以期待看到更多创新的应用程序,它们将利用这些工具和框架来解决现实世界的问题。
文章
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 中构建的函数。编译器不会理解从方法到函数的区别。 感谢您的阅读,请随时分享建议、评论、疑问或您正在开发的任何内容!
文章
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()