搜索​​​​

清除过滤器
文章
Michael Lei · 五月 1, 2022

转发博客文章: 释放医疗互操作性的全部益处: 下一个前沿

临床研究必须与健康数据相连 就在不久以前,临床科研人员还需要依靠三联的纸质NCR表格,手工收集从堆积如山的手写电子病历中提炼出来的病人数据。从又大又重的《医师案头参考》(PDR)撕下几页,通过传真机发送给FDA,用于药物安全报告。业内专业人士接受了大量的培训,以确保数据经过源文件验证、双键处理,并在经过看似无休止的查询以纠正错误之后,保证其符合目的。 值得庆幸的是,随着电子健康档案的广泛采用,健康数据的数字化,这一过程得到了极大的改善。但是,鉴于临床研究进展缓慢,特别是精美的Excel表格仍由人工数据摘要完成,该领域早该有更多的技术变革,特别是围绕释放医疗互操作性的全部好处。如果我们能做到这一点,生命科学公司将有机会利用宝贵的健康数据来确保病人的安全,优化新药的疗效,并使临床开发过程更加高效,减少错误。 我们可能会比你想象的更快地将临床研究与健康数据联系起来。为什么?火神计划(Project Vulcan)正在进行中。这个项目组是最近在HL7的FHIR加速器项目中创建的,它已经召集了来自医疗、技术和生命科学领域的30多个利益相关的组织个和机构,利用FHIR这个医疗专用互操作性标准来进行临床和转化研究。自从我们在去年年初加入后,我们高兴的看到火神计划成员制定的早期想法越来越接近于功能现实。 但是,医疗互操作性究竟如何能使同时用药跟踪(在同一时间段内给予两种或多种药物)或不良事件报告(捕捉可能与药物有关的疾病)以及其他临床研究领域受益?这些答案让我们看到了未来。 互操作性彻底改变了不良事件报告 每个新药的批准都是从安全开始的。如果研究人员不能完成临床试验的第一和第二阶段,他们甚至没有机会在更大的人群中研究其药物的疗效。但要证明一种治疗方法是安全的,这就提出了严峻的挑战。 几十年来,业内人士已经知道,一些不良事件在临床试验中被忽略了。比方说,一个研究对象在其心脏病专家的照料下测试一种新的心脏候选药物时,出现了皮疹并去看皮肤科医生。一个月后,当该受试者来到他们的下一次研究访问时,他们可能忘记告知研究团队他们最近的诊断--由正在测试的研究药物引起的新皮疹。 现在,太多时候,故事就在这里结束。这条信息--以及与之相关的、可能说明药物安全性的所有信息--已经消失。其他真实世界的数据也是如此,比如病人服用的额外药物,但由未参与研究的护理团队成员开出。或者仅仅是网络外的访问和程序。 然而,如果火神计划成功,研究人员将有更好的机会通过参与者的电子病历健康档案和临床研究数据记录之间的联系来捕捉这些关键信息。 基于FHIR的互操作性还可以简化不良事件报告,使患者的完整快照可以向关键利益相关者提供重要数据。任何人--患者、研究人员或监管机构--都不会再担心出现问题,而这些问题本可以通过与真实世界的数据建立可靠的联系而轻松避免。 互操作性支持表型数据共享Phenopackets(开放的可计算的生物信息Phenopackets.org)和临床实验方案日程 除了电子病历数据可以为临床试验提供信息外,火神项目的目标之一是,希望借助于FHIR,临床试验中收集的数据可以被更广泛地访问, 而且可以用于采取行动。全球基因组学与健康联盟(GA4GH)的表型数据交换标准有望使业界分享脱敏后(去掉身份识别)的病例级病人信息,以用于登记册、知识库出版物和期刊。 任何从事临床试验的人都知道,尽管对实现研究目标至关重要,但临床试验的分步指南,很容易出现疏忽和偏离计划的情况。主要的问题是什么?就是人为失误。但通过 "火神计划",基于FHIR的分步指南活动安排表很快就能实现调度自动化,减少对手工数据输入的需求,并提高研究程序的一致性。 我们已准备好将FHIR引入临床研究 FHIR已经在推进互操作性和消除障碍。在InterSystems,我们每天都能看到FHIR对互操作性的影响,无论是通过创建用于临床决策支持的移动端APP,还是通过医院不同部门和医疗机构之间的数据无缝流动。这就是我们将FHIR纳入我们的技术栈的原因。 当火神项目实现其目标时,InterSystems将准备好立即为临床研究带来快速、无缝的数据交换。我们没有任何理由推迟更安全、更有效的药物的到来。 作者是:InterSystems公司生命科学顾问Matthew Stannard 原文--https://www.intersystems.com/pulse-blog/unlocking-benefits-of-healthcare-interoperability
文章
姚 鑫 · 八月 7, 2021

方法关键字Requires,ReturnResultsets,ServerOnly,SoapAction

# 第七十三章 方法关键字 - Requires 指定用户或进程调用此方法必须拥有的权限列表。 # 用法 要指定此方法应限于具有指定权限的用户或进程,请使用以下语法: ```java Method name(formal_spec) As returnclass [ Requires = privilegelist ] { //implementation } ``` 其中,`privilegelist` 要么是单个特权,要么是用引号括起来的以逗号分隔的特权列表。 每个权限都采用`resource:permission`的形式,其中`permission`是`Use`、`Read`或`Write`(或单字母缩写`U`、`R`或`W`)。 若要为一个资源`resource`指定多个权限,请使用单字母缩写。 # 详情 用户或进程必须拥有权限列表中的所有权限才能调用该方法。 调用没有指定权限的方法会导致``错误。 如果方法从超类继承了`Requires`关键字,则可以通过设置关键字的新值将其添加到所需特权的列表中。 不能以这种方式删除所需的特权。 # 默认 如果忽略此关键字,则调用此方法不需要特殊权限。 # 示例 下面的方法需要对`Sales`数据库的读权限和对`Marketing`数据库的写权限。 (注意,如果一个数据库有写权限,它会自动有读权限。) ```java ClassMethod UpdateTotalSales() [ Requires = "%DB_SALES: Read, %DB_MARKETING: Write" ] { set newSales = ^["SALES"]Orders set totalSales = ^["MARKETING"]Orders set totalSales = totalSales + newSales set ^["MARKETING"]Orders = totalSales } ``` 若要为一个资源指定多个权限,请使用单字母缩写。 以下两种方法在功能上是等价的: ```java ClassMethod TestMethod() [ Requires = "MyResource: RW" ] { write "You have permission to run this method" } ClassMethod TestMethodTwo() [ Requires = "MyResource: Read, MyResource: Write" ] { write "You have permission to run this method" } ``` # 第七十四章 方法关键字 - ReturnResultsets 指定此方法是否返回结果集(以便`ODBC`和`JDBC`客户机能够检索它们)。 # 用法 要指定该方法返回至少一个结果集,请使用以下语法: ```java ClassMethod name(formal_spec) As returnclass [ ReturnResultsets, SqlName = CustomSets, SqlProc ] { //implementation } ``` 否则,忽略此关键字或将单词`Not`紧接在关键字之前。 # 详解 此关键字指定该方法至少返回一个结果集。如果方法可能返回一个或多个结果集,则将此关键字设置为`true`。如果没有,`xDBC`客户端将无法检索结果集。 # 默认 如果省略此关键字,`xDBC`客户端将无法检索结果集。 # 第七十五章 方法关键字 - ServerOnly 指定此方法是否将被投影到Java客户端。 # 用法 将方法投影到`Java`客户端,请使用以下语法: ```java Method name(formal_spec) As returnclass [ ServerOnly=n ] { //implementation } ``` 其中`n`为下列其中之一: - `0`表示该方法可以映射。 - `1`表示该方法不会被映射。 # 详解 该关键字指定方法不会被投影到`Java`客户机。 # 提示 要查看类的哪些方法是`server-only`的,请在终端中使用以下实用程序: ```java do dumpMethods^%occLGUtil("Sample.Person") ``` 参数是完全限定类名。 该实用程序生成一个报告,该报告指出关于每个方法的基本信息:该方法是否为存根,该方法是否仅为服务器,以及(如果该方法是从某个属性派生的)派生该方法的属性。 # 默认 如果忽略这个关键字,这个方法如果是存根方法就不会被投影(但是如果不是存根方法就会被投影)。 ```java DHC-APP>do dumpMethods^%occLGUtil("Sample.Person") Method=%%OIDGet UseStub=0 serveronly=1 Method=%%OIDIsValid UseStub=1 serveronly=0 PropName=%%OID MethodName=IsValid Method=%%OIDSet UseStub=1 serveronly=1 PropName=%%OID MethodName=Set Method=%1Check UseStub=0 serveronly=1 Method=%AcquireLock UseStub=0 serveronly=1 Method=%AddJrnObjToSyncSet UseStub=0 serveronly=1 Method=%AddToSaveSet UseStub=0 serveronly=1 ``` # 第七十六章 方法关键字 - SoapAction 指定当通过HTTP将此方法作为`web方法`调用时,要在`HTTP`头中使用的`SOAP`操作。仅适用于定义为`web服务`或`web客户端`的类。 # 用法 要指定将此方法用作`web方法`时在HTTP头中使用的`SOAP`操作,请使用以下语法: ```java Method name(formal_spec) As returnclass [ WebMethod, SoapAction = soapaction ] { //implementation } ``` 其中`soapaction`是下列之一: - `“[default]”—SOAP`操作的默认值,即`NAMESPACE/Package.Class.Method` - `"customValue"` -使用`customValue`作为`SOAP`操作。 该值应该是标识`SOAP`请求意图的`URI`。 如果指定了一个自定义的值,它必须在`web服务`的每个`web方法`中是唯一的,或者你必须为每个`web方法`指定`SoapRequestMessage`关键字(并且为该关键字使用唯一的值)。 - "" -使用空值作为`SOAP`操作。这种情况很少见。 # 详情 `web方法`的`SOAP`动作通常用于路由请求`SOAP消息`。 例如,防火墙可以使用它来适当地过滤`SOAP请求消息`。 InterSystems IRIS `web服务`使用`SOAP操作`(与消息本身结合)来确定如何处理请求消息。 该关键字允许指定在作为`web方法`调用此方法时使用的HTTP `SOAP`动作。 对于`SOAP 1.1`, `SOAP`动作包含在`SOAPAction HTTP`报头中。 对于`SOAP 1.2`,它包含在`Content-Type` HTTP报头中。 # 默认 如果忽略`SoapAction`关键字,`SOAP`动作的形式如下: ```java NAMESPACE/Package.Class.Method ``` 其中`NAMESPACE`是`web服务`的`NAMESPACE`参数的值,`Package.Class`是`web服务`类的名称,`Method`是`web方法`的名称。 # WSDL的关系 `SoapAction`关键字影响`web服务`的`WSDL`中`的`部分。 例如,以下web方法: ```java Method Add(a as %Numeric,b as %Numeric) As %Numeric [ SoapAction = MySoapAction,WebMethod ] { Quit a + b } ``` 对于这个`web服务`,`WSDL`的``部分如下所示: ```xml ``` 默认情况下,如果方法没有指定`SoapAction`关键字,``元素可能会像下面这样: ```xml ``` 如果使用`SOAP`向导从`WSDL`生成 `web服务`服务或客户端,将此关键字设置为适合于该`WSDL`的关键字。 # 对消息的影响 对于前面显示的`web方法`,`web服务`期望收到以下形式的请求消息(对于SOAP 1.1): ```xml POST /csp/gsop/ROBJDemo.BasicWS.cls HTTP/1.1 User-Agent: Mozilla/4.0 (compatible; InterSystems IRIS;) Host: localhost:8080 Connection: Close Accept-Encoding: gzip SOAPAction: MySoapAction Content-Length: 379 Content-Type: text/xml; charset=UTF-8 ... ``` 默认情况下,如果方法没有指定`SoapAction`关键字,`SoapAction`行可能会像下面这样: ```xml SOAPAction: http://www.mynamespace.org/ROBJDemo.BasicWS.Add ``` 注意,对于`SOAP 1.2`,细节略有不同。 在这种情况下,`web服务`期望收到如下形式的请求消息: ```xml POST /csp/gsop/ROBJDemo.BasicWS.cls HTTP/1.1 User-Agent: Mozilla/4.0 (compatible; InterSystems IRIS;) Host: localhost:8080 Connection: Close Accept-Encoding: gzip Content-Length: 377 Content-Type: application/soap+xml; charset=UTF-8; action="MySoapAction" ... ```
文章
姚 鑫 · 三月 9, 2021

第六章 SQL定义和使用视图

# 第六章 SQL定义和使用视图 视图是一种虚拟表,由执行时通过`SELECT`语句或几个`SELECT`语句的`UNION`从一个或多个物理表中检索到的数据组成。 `SELECT`可以通过指定表或其他视图的任意组合来访问数据。因此,存储了视图的视图提供了物理表的所有灵活性和安全性特权。 InterSystemsIRIS®数据平台上的InterSystems SQL支持在视图上定义和执行查询的功能。 注意:不能对以只读方式安装的数据库中存储的数据创建视图。 无法在通过ODBC或JDBC网关连接链接的`Informix`表中存储的数据上创建视图。这是因为InterSystems IRIS查询转换对这种类型的查询使用FROM子句中的子查询。 `Informix`不支持`FROM`子句子查询。 # 创建一个视图 可以通过几种方式定义视图: - 使用SQL `CREATE VIEW`命令(在DDL脚本中或通过JDBC或ODBC)。 - 使用管理门户的“创建视图”界面。 视图名称:不合格的视图名称是一个简单的标识符:`MyView`。合格的视图名称由两个简单的标识符组成,即模式名称和视图名称,以句点分隔:`MySchema.MyView`。视图名称和表名称遵循相同的命名约定,并对不合格的名称执行相同的架构名称解析。同一模式中的视图和表不能具有相同的名称。 可以使用`$SYSTEM.SQL.ViewExists()`方法确定视图名称是否已存在。此方法还返回投影视图的类名称。可以使用`$SYSTEM.SQL.TableExists()`方法确定表名是否已存在。 视图可用于创建表的受限子集。以下嵌入式SQL示例创建一个视图,该视图限制了可以通过该视图访问的原始表的行(通过`WHERE`子句)和列(假设`Sample.Person`包含两个以上的列): ```java /// d ##class(PHA.TEST.SQL).View() ClassMethod View() { &sql(CREATE VIEW Sample.VSrStaff AS SELECT Name AS Vname,Age AS Vage FROM Sample.Person WHERE Age>75) IF SQLCODE=0 { WRITE "创建一个视图",! } ELSEIF SQLCODE=-201 { WRITE "视图已经存在",! } ELSE { WRITE "SQL报错: ",SQLCODE," ",%msg,! } } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).View() 创建一个视图 ``` 以下嵌入式SQL示例基于`SalesPeople`表创建一个视图,并创建一个新的计算值列`TotalPay`: ```java /// d ##class(PHA.TEST.SQL).View1() ClassMethod View1() { &sql(CREATE VIEW Sample.VSalesPay AS SELECT Name,(Salary + Commission) AS TotalPay FROM Sample.SalesPeople) IF SQLCODE=0 { WRITE "创建一个视图",! } ELSEIF SQLCODE=-201 { WRITE "视图已经存在",! } ELSE { WRITE "SQL报错: ",SQLCODE," ",%msg,! } } ``` ## 管理门户创建视图界面 可以从管理门户创建视图。转到InterSystems IRIS管理门户。在系统资源管理器中,选择SQL。使用页面顶部的Switch选项选择一个名称空间;这将显示可用名称空间的列表。选择名称空间后,单击“操作”下拉列表,然后选择“创建视图”。 这将显示“创建视图”窗口,其中包含以下字段: - 模式:可以决定将视图包含在现有模式中,也可以创建一个新模式。如果选择选择现有模式,则会提供一个现有模式的下拉列表。如果选择创建新架构,请输入架构名称。在这两种情况下,如果省略模式,则InterSystems IRIS都会使用系统范围内的默认模式名称。 - 视图名称:有效的视图名称。不能对同一模式中的表和视图使用相同的名称。 - 使用`Check Option`:选项为`READONLY`,`LOCAL`,`CASCADED`。 - 将视图的所有特权授予`_PUBLIC`:如果选中,则此选项为该视图授予所有用户执行特权。默认设置是不授予所有用户访问该视图的权限。 - 查看文字:可以通过以下三种方式中的任意一种来指定查看文字: - 在“查看文本”区域中键入SELECT语句。 - 使用查询生成器创建`SELECT`语句,然后按OK将此查询提供给“查看文本”区域。 - 如果在Management Portal SQL界面的左侧选择了一个缓存查询名称(例如`%sqlcq.USER.cls4`),然后调用`Create View`,则该缓存查询将提供给“视图文本”区域。请注意,在保存视图文本之前,必须在“视图文本”区域中用实际值替换主机变量引用。 ## 视图和相应的类 定义视图时,InterSystems IRIS会生成一个相应的类。按照名称转换规则,SQL视图名称用于生成相应的唯一类名称。 Management Portal SQL界面显示现有视图的“目录详细信息”,包括此类名称。 # 修改视图 在Management Portal SQL界面中,可以选择一个现有视图以显示该视图的“目录详细信息”。 “目录详细信息视图信息”选项显示“编辑视图”链接,该链接提供了用于编辑视图文本(视图的`SELECT`语句)的界面。它还提供了一个下拉列表,以将“带检查选项”选择为无,`READONLY`,`LOCAL`或`CASCADED`。 # 可更新的视图 可更新的视图是可以在其上执行`INSERT`,`UPDATE`和`DELETE`操作的视图。仅当满足以下条件时,才认为视图是可更新的: - 视图查询的`FROM`子句仅包含一个表引用。该表引用必须标识可更新的基表或可更新的视图。 - 视图查询的`SELECT`列表中的值表达式必须全部是列引用。 - 视图的查询中不得指定`GROUP BY`,`HAVING`或`SELECT DISTINCT`。 - 该视图不是投影为视图的类查询。 - 视图的类不包含类参数`READONLY = 1`(如果视图定义包含`WITH READ ONLY`子句,则为`true`)。 ## WITH CHECK选项 为了防止在视图上执行`INSERT`或`UPDATE`操作,而该操作会导致基础基表中的行不属于派生视图表的一部分,InterSystems SQL在视图定义中支持`WITH CHECK OPTION`子句。此子句只能与可更新视图一起使用。 `WITH CHECK OPTION`子句指定可更新视图上的任何`INSERT`或`UPDATE`操作必须对照视图定义的WHERE子句验证结果行,以确保插入或修改的行将成为派生视图表的一部分。 例如,以下DDL语句定义了一个可更新的`GoodStudent`视图,其中包含所有具有高`GPA`(平均绩点)的学生: ```java CREATE VIEW GoodStudent AS SELECT Name, GPA FROM Student WHERE GPA > 3.0 WITH CHECK OPTION ``` 由于视图包含`WITH CHECK OPTION`,因此任何尝试在`GPA`值小于或等于3.0的`GoodStudent`视图中插入或更新行都将失败(此类行将不表示“好学生”)。 有两种类型的`WITH CHECK`选项: - `WITH LOCAL CHECK`选项意味着只检查`INSERT`或`UPDATE`语句中指定的视图的`WHERE`子句。 - 与级联检查选项(和级联检查选项)意味着视图的`WHERE`子句中指定的`INSERT`或`UPDATE`语句以及所有视图检查基于这一观点,无论外表或与当地检查没有其他选项在这些视图定义条款。 如果指定了`just WITH CHECK`选项,默认值是级联的。 在更新或插入期间,在为基础表的字段计算了所有默认值和触发的计算字段之后,并在常规表验证(必需字段、数据类型验证、约束等)之前,检查`WITH CHECK`选项条件。 在`WITH CHECK`选项验证通过后,插入或更新操作继续进行,就像在基表本身上执行插入或更新一样。 检查所有约束,拉出触发器,等等。 如果在`INSERT`或`UPDATE`语句中指定了`%NOCHECK`选项,则不检查`WITH CHECK`选项的有效性。 有两个与`WITH CHECK`选项验证相关的`SQLCODE`值(插入/更新会导致派生视图表中不存在一行): - `SQLCODE -136`-`INSERT`中视图的`WITH CHECK OPTION`验证失败。 - `SQLCODE -137`-视图的`WITH CHECK OPTION`验证在`UPDATE`中失败。 # 只读视图 只读视图是不能在其上执行`INSERT`,`UPDATE`和`DELETE`操作的视图。任何不符合可更新视图标准的视图都是只读视图。 视图定义可以指定`WITH READ ONLY`子句,以强制其成为只读视图。 如果尝试针对只读视图编译/准备`INSERT`,`UPDATE`或`DELETE`语句,则会生成`SQLCODE -35`错误。 # 查看ID:%VID InterSystems IRIS为视图或`FROM`子句子查询返回的每一行分配一个整数视图`ID`(`%VID`)。与表行`ID`号一样,这些视图行`ID`号是系统分配的,唯一的,非空的,非零的和不可修改的。该`%VID`通常对用户不可见,并且仅在明确指定时返回。它以数据类型`INTEGER`返回。因为`%VID`值是顺序整数,所以如果视图返回有序数据,它们将更有意义。视图与TOP子句配对时,只能使用`ORDER BY`子句。以下嵌入式SQL示例创建一个名为`VSrStaff`的视图: ```java /// d ##class(PHA.TEST.SQL).View() ClassMethod View() { &sql(CREATE VIEW Sample.VSrStaff AS SELECT Name AS Vname,Age AS Vage FROM Sample.Person WHERE Age>75) IF SQLCODE=0 { WRITE "创建一个视图",! } ELSEIF SQLCODE=-201 { WRITE "视图已经存在",! } ELSE { WRITE "SQL报错: ",SQLCODE," ",%msg,! } } ``` 下面的示例返回`VSrStaff`视图定义的所有数据(使用`SELECT *`),并且还指定应返回每一行的视图`ID`。与表行`ID`不同,使用星号语法时不显示视图行`ID`。仅当在`SELECT`中明确指定时才显示: ```java SELECT *,%VID AS ViewID FROM Sample.VSrStaff ``` `%VID`可用于进一步限制`SELECT`从视图返回的行数,如以下示例所示: ```java SELECT *,%VID AS ViewID FROM Sample.VSrStaff WHERE %VID BETWEEN 5 AND 10 ``` **因此,可以使用`%VID`代替`TOP`(或除`TOP`之外)来限制查询返回的行数。通常,`TOP`子句用于返回数据记录的一小部分。 `%VID`用于返回大多数或所有数据记录,以小的子集返回记录。此功能可能很有用,尤其是对于移植Oracle查询(`%VID`轻松映射到Oracle ROWNUM)而言。但是,与`TOP`相比,用户应了解使用`%VID`时的一些性能限制:** - `%VID`不执行第一行时间优化。 `TOP`优化为尽快返回第一行数据。 `%VID`优化以尽快返回完整的数据集。 - 如果查询指定排序的结果,则`%VID`不会执行有限的排序(这是`TOP`进行的特殊优化)。该查询首先对完整的数据集进行排序,然后使用`%VID`限制返回数据集。 `TOP`是在排序之前应用的,因此`SELECT`只能执行有限的排序,仅涉及有限的行子集。 为了节省第一行优化和有限排序优化的时间,可以将`FROM`子句子查询与`TOP`和`%VID`结合使用。在`FROM`子查询中指定上限(在本例中为10)作为`TOP`的值,而不是使用`TOP ALL`。使用`%VID`在`WHERE`子句中指定下限(在这种情况下,`> 4`)。以下示例使用此策略返回与上一个视图查询相同的结果: ```sql SELECT *,%VID AS SubQueryID FROM (SELECT TOP 10 Name,Age FROM Sample.Person WHERE Age > 75 ORDER BY Name) WHERE %VID > 4 ``` 即使显式指定了`%PARALLEL`关键字,也无法对指定`%VID`的查询执行并行执行。 # List视图属性 `INFORMATION.SCHEMA.VIEWS`持久类显示有关当前名称空间中所有视图的信息。它提供了许多属性,包括视图定义,视图的所有者以及创建和最后修改视图时的时间戳。这些属性还包括视图是否可更新,如果可更新,是否使用检查选项定义。 在嵌入式SQL中指定时,`INFORMATION.SCHEMA.VIEWS`需要`#include%occInclude`宏预处理程序指令。 `Dynamic SQL`不需要此伪指令。 `VIEWDEFINITION`属性(`SqlFieldName = VIEW_DEFINITION`)以字符串形式返回当前名称空间中所有视图的视图字段名称和视图查询表达式。例如, ```sql SELECT View_Definition FROM INFORMATION_SCHEMA.VIEWS ``` 返回诸如`“(vName,vAge)SELECT Name,Age FROM Sample.Person WHERE Age> 21”`的字符串。当从Management Portal SQL执行查询界面发出时,此字符串的显示仅限于前100个字符,其中不包括空格和换行符,并且(如有必要)附加表示省略号的省略号(`...`)。否则,发出此查询将为每个视图返回最多`1048576`个字符的字符串,在视图字段列表和查询文本之间有一个换行符,并保留了视图查询表达式中指定的空格,并(如有必要)附加了省略号(`...`)表示内容被截断。 以下示例返回当前名称空间中所有视图的视图名称(Table_Name字段)和所有者名称: ```sql SELECT Table_Name,Owner FROM INFORMATION_SCHEMA.VIEWS ``` 以下示例返回当前名称空间中所有非系统视图的所有信息: ```sql SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE Owner != '_SYSTEM' ``` `INFORMATION.SCHEMA.VIEWCOLUMNUSAGE`持久性类显示当前名称空间中每个视图的源表字段的名称: ```sql SELECT * FROM INFORMATION_SCHEMA.VIEW_COLUMN_USAGE WHERE View_Name='VSrStaff' ``` 可以使用管理门户网站SQL界面中的“目录详细信息”选项卡为单个视图显示与`INFORMATION.SCHEMA.VIEWS`相同的信息。视图的“目录详细信息”包括每个视图字段的定义(数据类型,最大长度,最小值/最大值等),以及`INFORMATION.SCHEMA`视图类未提供的详细信息。 “目录详细信息”视图信息显示还提供了用于编辑视图定义的选项。 # 列出视图依赖 `INFORMATION.SCHEMA.VIEWTABLEUSAGE`持久类显示当前名称空间中的所有视图及其依赖的表。在下面的示例中显示: ```sql SELECT View_Schema,View_Name,Table_Schema,Table_Name FROM INFORMATION_SCHEMA.VIEW_TABLE_USAGE ``` 可以调用`%Library.SQLCatalog.SQLViewDependsOn`类查询以列出指定视图所依赖的表。可以为此类查询指定`schema.viewname`。如果仅指定视图名称,则它将使用系统范围的默认架构名称。调用者必须具有指定视图的特权才能执行此类查询。在下面的示例中显示: ```java /// d ##class(PHA.TEST.SQL).View3() ClassMethod View3() { SET statemt=##class(%SQL.Statement).%New() SET cqStatus=statemt.%PrepareClassQuery("%Library.SQLCatalog","SQLViewDependsOn") IF cqStatus'=1 { WRITE "%PrepareClassQuery failed:" DO $System.Status.DisplayError(cqStatus) QUIT } SET rset=statemt.%Execute("vschema.vname") DO rset.%Display() } ``` ```sql DHC-APP>d ##class(PHA.TEST.SQL).View3() Dumping result #1 SCHEMA TABLE_NAME 0 Rows(s) Affected ``` 此`SQLViewDependsOn`查询列出了视图所依赖的表,并列出了表架构和表名。如果调用者没有该视图所依赖的表的特权,则该表及其模式将列为``。这允许没有表特权的调用者确定视图所依赖的表数量,而不是表的名称。
文章
Claire Zheng · 三月 22, 2022

如何准确理解FHIR能力?医疗IT行业的数据交换和共享难题如何破?

最在第一期“极客聊吧”中,InterSystems销售工程师们聊了聊这些话题:为什么有些医院和某些商保之间可以直接结算,有些又不能?医院和保险之间的结算难在哪儿?在InterSystems 2021全球线上峰会中提到的医保结算案例对国内实践有哪些借鉴意义?FHIR又能起到什么关键作用?医疗数据实现互联互通的关键是什么?以下是文字版。 点击查看视频。 #从商保结算谈起# 菁伟 (@Jingwei.Wang ):大家好,我是InterSystems的销售工程师王菁伟,这是我的同事祝麟和刘皆良(Jeff)。今天我们一块来聊聊HIT行业里的数据交互那点事。去医院就诊的话,我们会发现有些医院可以和某些商业保险直接结算,有的医院或者某些保险产品又不能。为什么直结这个流程没有全面开通呢? 祝麟 ( @Lin.Zhu ):我们虽然平时看病都基本实现了医保直连, 但实际上背后医院的信息科和供应商付出了大量的努力,比如供应商就需要要适应各地医保结算流程和政策的差异,这个过程的数字化还是有很大难度的,也没法一蹴而就。 Jeff ( @Jieliang.Liu ):到商保这一侧,可以观察到很多商保产品的直结清单是逐年递增。那么保险公司的产品是一家一家和医院对接的,难度恐怕更大。 菁伟 (@Jingwei.Wang ):医院和保险之间的结算过程到底为什么落地困难呢? 祝麟 ( @Lin.Zhu ):原因很多,既有政策层面的,也有技术层面的。比如医院和院外机构间通信的安全性保障就是问题,如果没有安全的公共平台进行数据交换,哪家医院也不能独立承担数据流出医院的安全风险,这在政策的制定和技术的实现方面都有挑战。 Jeff ( @Jieliang.Liu ):医院和保险之间的接口也是问题,不同的医院会采用不同厂家提供的系统,不同保险公司采用的数据接口肯定也不一样,是个典型的多对多集成的问题,工作量会比较大,实施周期也很长。 #借鉴国际成功案例# 菁伟 (@Jingwei.Wang ):去年InterSystems的全球峰会上HealthShare的分论坛里是不是就谈到了医保结算的场景?这个案例对实现直接结算有没有借鉴意义呢? 祝麟 ( @Lin.Zhu ):是的。本身医保结算这个事是个全球性的挑战。除了我们国家以外,即使是商保比较发达的欧美国家,在打通流程方面也是处于进行中的状态。这次峰会上HealthShare发布解决方案,实现了CMS,也就是美国医保局所定义的医疗服务提供方和支付方的交互规范。这个方案覆盖的正是医疗机构、支付方以及患者三者之间结算和信息交互流程。 菁伟 (@Jingwei.Wang ):也就是说满足了政策要求? Jeff ( @Jieliang.Liu ):也包含技术要求。CMS定义的交互流程和数据模型是以FHIR为载体的。 祝麟 ( @Lin.Zhu ):是的。CMS定义的交互规约既包含流程规范,也包含接口和数据规范,目前是以FHIR R4规定的,所以必须以FHIR API的形式实现。 #如何准确理解FHIR能力# 菁伟 (@Jingwei.Wang ):那么,现在有没有实际的案例使用FHIR来完成业务功能呢? Jeff ( @Jieliang.Liu ):有的。我们在国际上已经看到不少基于FHIR的业务出现。比如我们和UC Davis Health以及Centene合作进行了医保预授权的自动化项目,就是基于FHIR的。在这个项目里,UC Davis Health的角色是医疗机构,Centene是保险的支付方,采用HealthShare作为平台来支撑不同角色间的实时交互。 祝麟 ( @Lin.Zhu ):UC Davis Health的医生在Epic的电子病历系统里下达医嘱,系统把患者和医嘱信息按自定义接口发到HealthShare;HealthShare将请求转为FHIR标准再通过FHIR接口与Centene的系统交互,检查这些医嘱能不能被Centene的保险产品覆盖,再把结果通过HealthShare转回到医生那儿。这样就可以在下达医嘱时实时地通过预授权的检验,杜绝医保拒付的情况。先不说杜绝拒付问题能节省的开支,光是将预授权自动化这一项,和人工处理相比,就能把每一单预授权的处理成本从3.68美金降到0.04美金,那每处理一百万次预授权就能省下几百万美金了。 菁伟 (@Jingwei.Wang ):那么,假设我们有一个集成平台,通过平台实现FHIR API接口,不同的系统通过FHIR API与集成平台对接,是不是就能解决数据交互问题呢? 祝麟 ( @Lin.Zhu ):这是个非常好的问题。FHIR在设计之初就声明采用了剃刀原理,它大概只能覆盖80%常见的交互需求。因此,对于差异化的需求或者是新技术引入的新的数据交互需求,FHIR要借助除了API之外的额外机制来解决这个问题。在医疗行业,我们现在经常遇到医疗文档共享或者跨机构流程这样的业务场景,除了API。Jeff,你认为平台这个层面还需要什么样的一些能力来支撑这些场景呢? Jeff ( @Jieliang.Liu ):Profile。针对患者健康档案共享和支付方之间的数据交换两个不同的场景,需要分别遵循USCore profile和PDex Profile的规约。也就是说在可预见的未来,对于一家医院或者一个区域级的数据交换中心来说,需要能够支持不同的Profile以应对不同的场景。 菁伟 (@Jingwei.Wang ):这些不同的Profile之间有什么差别吗? Jeff ( @Jieliang.Liu ):首先我们需要明白Profile立意于使用FHIR支持差异化的用例或场景。因此,一个合法的Profile就只覆盖这个场景所需要使用的模型而不会涉及到其他用例的内容。 US Core和PDex 这两个Profile首先在定位上不太一样。US Core面向患者就诊过程的信息交换,对就诊中的涉及例如治疗计划、体征和检验结果这样的临床信息进行了定义;而PDex是面向医保结算的用例,就会包含报销范围,支付方的组织机构信息这样一些和医保报销相关的信息,由CPCDS这个数据集定义。当然PDex也会引用US Core中定义的内容,比如患者的身份、病史、体征、检验结果,在PDex里这些临床信息是直接引用US Core里的定义的。 祝麟 ( @Lin.Zhu ):没错,正如同我们在医院信息互联互通标准化成熟度测评过程中可以体会到文档交换和服务调用是面向不同用例的交互手段,两者的信息构造有差异。那么使用FHIR进行交互时,面对不同的应用场景时完全可能需要套用不同的Profile,甚至是借助Profile套用特定的术语。因此,Profile之间会有很多差异。 如果FHIR在中国落地,可以想象到,由于医疗技术上我们包含中医,医疗福利上我们有医保和商保,还有很多其他差异,我们也需要有中国自己定义的多个Profile去投入使用。 因此,对FHIR的支持不能仅仅体现在支持HL7官网发布的协议结构上。支持多Profile,能够根据地区、应用场景的不同导入并套用Profile对于医疗行业的集成引擎、数据平台这样的产品是一项至关重要的FHIR能力。 Jeff ( @Jieliang.Liu ):除了遵循协议之外,还有数据架构。在HealthShare的解决方案里包含了一个整合好的数据存储。 祝麟 ( @Lin.Zhu ):是的,有一份在各业务系统之上,经过整合与泛化统一存储的数据能够极大简化应用标准协议的成本。大家可以想象一下,对于使用FHIR来说,在多Profile模式下运行的一个体系,如果没有一份整合的数据,那么每次要支持一个新的Profile,特别是和上一次应用的Profile所需的资源不一样,术语可能也不一样的时候,实施方需要再一次挨个把和各个业务系统的映射再做一遍,这个代价和周期可想而知。 菁伟 (@Jingwei.Wang ):梳理和整合数据存储的话,这样一套方案就不是单纯的集成平台方案了。 祝麟 ( @Lin.Zhu ):没错,这是因为采用集成平台,和采用集成加数据整合的医院信息平台,要解决的问题是不一样的。 当单纯应用集成平台整合流程时,要解决是打破烟囱的问题,但还谈不上整合数据与数据利用。对于这样的场景,即使点对点地两两集成,也能解决问题,坦率说除非每个应用系统都能支持某个标准协议通信,否则采不采用协议进行集成并不关键。但如果要上线统一患者档案这样的业务,就需要整合和泛化数据并进行存储,如何利用这份数据就是协议可以发挥作用的地方了。 Jeff ( @Jieliang.Liu ):日本群马大学医学部附属医院就用我们的产品作为信息平台使用。应用InterSystems IRIS作为信息交换引擎从HIS、检验这样的系统里通过实时和批量数据同步接口获取数据,转换为FHIR并存储在InterSystems IRIS的FHIR存储库里。这个FHIR存储库所承担的就是临床数据中心的角色。它们的科研系统REDCap则通过FHIR对这个存储库进行查询并获得患者档案。现在这个项目也还在扩展,通过和Apple的技术融合实现患者端对自己的临床档案的访问。 祝麟 ( @Lin.Zhu ):是的,FHIR API本身就能支持数据利用和数据共享。院里要做科研的话也可以通过FHIR API查询符合条件的患者集合,比如查2021年11月到12月间就诊的低密度脂蛋白低于100的II型糖尿病患者,这是个典型的数据利用问题。如果不借助一份整合好的数据,只凭借单纯的集成平台,那就需要把这样的请求解析并分解到不同的业务系统,再把结果整合起来,才能获得结果。实施难度大,业务压力大不说,效率还没有保障。直接从支持FHIR的存储库里获取数据要高效得多。 Jeff ( @Jieliang.Liu ):使用FHIR资源仓库还有一大好处是,FHIR资源仓库并不意味着中心化的存储,而是由一系列物理上分布的资源仓库组成。这些离散的仓库各自保存特定的数据,通过资源间的相互引用形成逻辑上的统一。由于这种引用本质上松耦合,因此能够形成一个逻辑上整合的数据中心。也因为物理离散而逻辑统一,FHIR存储库非常适合用在微服务架构中。当然无论如何,物理上分布的数据也仍然需要通过FHIR统一逻辑概念和语意,以便在异构的系统间和组织机构间进行共享。 因此在应用FHIR相关的技术方面,FHIR接口是个基本的合规要求,多Profile的支持和FHIR存储库则更加关键,这是两个会直接影响FHIR是否能落地的重要特性。 #小谈互联互通# 菁伟 (@Jingwei.Wang ):标准接口和整合数据存储,这个概念听着很耳熟。 Jeff ( @Jieliang.Liu ):当然。一是互联互通的推荐架构里,是包含临床数据中心的,也是对整合应用数据提出了要求。另外,InterSystems在介绍方案时,通常也是把集成平台、数据中心看作是一个解决方案中的两个组成部分来看的。 祝麟 ( @Lin.Zhu ):没错,随着互联互通这项工作的逐渐推广,相信各家医院和区域数据中心都会收到越来越多数据开放和应用的需求。基于FHIR的数据交换和数据利用会经历越来越多的讨论,我们也会见到大量的交互场景通过FHIR实现的案例,以及基于FHIR特性的智能应用的出现。 菁伟 (@Jingwei.Wang ):感谢两位工程师与大家分享FHIR的案例和FHIR协议落地时所需要的平台能力。FHIR除了资源和接口的定义之外,还有许多特性能够帮助医疗机构打通数据流程。作为一个面向互操作性的协议,相信未来我们还会见到更多通过FHIR支撑的业务场景。
文章
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 的用户,请让这些用户在下方发表评论。 这可以引起让所有参与者都能受益的讨论。
文章
Lilian Huang · 三月 14

教程:将 OpenAI 添加到互操作性生产中

人工智能(AI)最近受到广泛关注,因为它可以改变我们生活的许多领域。更好的计算机能力和更多数据帮助人工智能完成了许多惊人的事情,例如改进医学测试和制造自动驾驶汽车。人工智能还可以帮助企业做出更好的决策,提高工作效率,这也是人工智能越来越流行和广泛应用的原因。如何将 OpenAI API 调用集成到现有的 IRIS 互操作性应用程序中? 前提条件 在本教程中,我们假设您已经拥有一个现有的互操作性产品和一组 OpenAI 凭证来调用 OpenAI API。您可以从以下 GitHub 项目分支下载我们在本教程中使用的代码: https://github.com/banksiaglobal/bg-openai/tree/test-app-original要了解如何获取 OpenAI 凭证,请按照本教程https://allthings.how/how-to-get-your-open-ai-api-key/或打开 OpenAI API 密钥页面并创建一个https://platform .openai.com/api-keys 原始申请 我们的应用程序 AppExchange 可模拟 InterSystems OpenExchange 的发布功能:它可获取包含项目描述、项目徽标和 GitHub URL 的请求,并将其发布到 AppExchange 存储库中。 加入人工智能元素 现在,我们假设管理我们存储库的人注意到一些应用程序开发人员很懒,没有为他们正在发布的应用程序提供简短的摘要或徽标。这就是我们的人工智能朋友可以来救援的地方! 理想的工作流程如下所示: 应用程序接收存储库的 URL、摘要和徽标的 URL 作为输入。 如果摘要为空,则 URL 会被发送到基于 GPT 的模型,该模型解析存储库内容并生成项目的描述性摘要。这一过程可能涉及解析存储库中的README 文件、代码注释和资源库中的其他相关文档,以提取有关项目目的、功能和用途的关键信息。 然后,生成的项目摘要将用作另一个基于 GPT 的模型的输入,该模型的任务是为项目创建徽标。该模型使用描述来理解项目的主题,然后设计一个标志,来直观地代表项目的本质和特征。 应用程序输出的响应包括原始 URL、生成的项目摘要和新创建的徽标。此响应提供了项目的全面概述,以及可用于品牌和营销工作的视觉标识符。 为了实现这种集成,我们将使用业务流程设计器对应用程序的工作流程进行可视化设计。 第1步:安装 首先,我们将使用 ZPM 软件包管理器从 Open Exchange 下载 bg-openai 包: zpm "install bg-openai" 您可以在此处查看此软件包https://openexchange.intersystems.com/package/bg-openai-1并在此处查看其源代码https://github.com/banksiaglobal/bg-openai 这个包基于 @Francisco Lopez 的伟大工作,可在此处https://github.com/KurroLopez/iris-openai进行,有四个小更改:我们更改了类名,以使其更符合标准 IRIS 命名规则;我们添加了新的 SimplePrompt 请求,允许用户非常轻松地发送简单的 AI 文本提示;我们将 Api Key 更改为凭证而不是设置;我们将顶级包名称更改为“Banksia”,以符合公司标准。 第 2 步:设置 OpenAI 运行 为了进一步工作和配置产品,如果您在我们的原始应用程序中使用 Docker 映像,请转到位于以下链接的管理门户: http://localhost:42773/csp/sys/UtilHome.csp 导航到互操作性->[命名空间]->配置->生产并确保我们的原始生产正在运行。 添加一个基于类Banksia.OpenAi.Operation 的新操作,并将其命名为OpenAiOut。使其启用。此操作将与 OpenAI API 服务器进行通信。 操作类:Banksia.OpenAi.Operation 操作名称:OpenAiOut 现在,让我们进行在生产中使用新操作所需的最基本设置:添加 API 密钥和 SSL 配置。 导航至OpenAiOut->设置->基本设置->凭据,然后单击放大镜图标以配置凭证。 填写表单数据并在密码字段中添加 apiKey 。单击“保存”保存数据。您可以根据需要填写ID和用户名字段。 在“凭证”字段中,选择我们之前保存的凭证的ID 。 设置 SSL 配置:创建新的客户端 SSL 配置OpenAiSSL并在下拉列表中选择它。 步骤 3 - 使用业务流程设计器为业务流程添加摘要生成 导航到互操作性 > 业务流程设计器并打开AppExchange.Process商业 单击“打开”进行处理。 根据我们上面描述的算法构建该过程的流程图。下图显示了一个示例实现。 检查是否提供了存储库 URL,如果没有输入描述,我们需要查询 ChatGPT 以创建描述。 (request.Summary= "" ) & (request.GitHubUrl '= "" ) 然后,添加 <Сall> 块并创建一个目标OpenAiOut ,该目标将根据请求的类型调用 OpenAi api。 名称:生成摘要 自定义请求的类型和收到的响应,以及分配操作变量。 请求消息类:Banksia.OpenAi.Msg.SimplePrompt.Request set callrequest.Prompt =“访问下一步将为您提供的网站。在一个段落中描述该项目的主要思想、目标和主要功能。” 设置 callrequest.UserInput = request.GitHubUrl 设置callrequest.Model =“gpt-4” 响应消息类:Banksia.OpenAi.Msg.SimplePrompt.Response 设置 context.summary = callresponse.Content 添加一个 <sync> 步骤来等待响应,在 Calls 字段中添加上一个 <call> 的名称 调用:生成摘要 第 4 步 - 将徽标生成添加到业务流程中 获得存储库描述后,让我们继续下一个逻辑部分 - 徽标生成。让我们检查是否有生成图像的描述,并检查是否没有提供图像 URL。让我们设置以下条件: (request.LogoUrl= "" ) & (request.Summary'= "" ) 配置下一个 <call> 元素,同时将OpenAiOut操作作为目标。 名称:生成徽标 自定义请求的类型和收到的响应。 请求消息类:Banksia.OpenAi.Msg.Images.Request 设置 callrequest.ResponseFormat = "url" 设置 callrequest.Operation = "世代" set callrequest.Prompt = "为以下移动应用程序创建一个简单的应用程序图标:"_request.Summary 设置 callrequest.Size = "256x256" 响应消息类:Banksia.OpenAi.Msg.Images.Response 设置 request.LogoURL = callresponse.Data.GetAt(1).Url 完成我们业务流程的修改后,点击编译按钮。 您可以从以下 GitHub 项目分支下载完成的 OpenAI 集成示例: https://github.com/banksiaglobal/bg-openai/tree/test-app 第 5 步:在生产中测试我们的新业务流程 转到互操作性->配置->生产部分 首先,我们需要重新启动流程以应用所有最新更改,导航到AppProcess->Actions->Restart 。 要测试该流程,请转至AppProcess->Actions->Test。使用 OpenAI API 的 GitHub URL 创建测试消息并通过生产发送: 验证应用程序是否正确接收并处理来自 OpenAI API 的响应。转到Visual Trace查看完整的应用程序周期,并确保在每个流程元素中传输正确的数据。 这是人工智能对我们应用程序徽标的看法: 结论 通过执行以下步骤,您可以使用 InterSystems IRIS 中的业务流程将 OpenAI API 集成到互操作性生产中。 bg-openai 模块对于希望将人工智能融入应用程序的开发人员来说是一个很好的资源。通过简化集成过程,它为利用人工智能的力量增强应用程序开辟了新的可能性。 关于作者 Mariia Nesterenko是 Banksia Global 的认证 IRIS 开发人员,专门从事应用程序开发、数据结构、互操作性和地理空间数据。 关于班克西亚全球 Banksia Global是一家国际精品咨询公司,总部位于澳大利亚悉尼,专门为 InterSystems 技术提供专业服务。我们拥有一支由敬业且经验丰富的专业人士组成的团队,我们很自豪能够成为 InterSystems 的官方首要合作伙伴,并获得授权在全球范围内提供服务。我们对卓越和创新的热情促使我们提供高质量的解决方案,满足客户的独特需求。
文章
姚 鑫 · 三月 21, 2021

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

# 第十二章 使用嵌入式SQL(五) # 嵌入式SQL变量 以下局部变量在嵌入式SQL中具有特殊用途。这些局部变量名称区分大小写。在过程启动时,这些变量是不确定的。它们由嵌入式SQL操作设置。也可以使用SET命令直接设置它们,或使用`NEW`命令将其重置为未定义。像任何局部变量一样,值将在过程持续期间或直到设置为另一个值或使用`NEW`进行定义之前一直存在。例如,某些成功的嵌入式SQL操作未设置`%ROWID`。执行这些操作后,`%ROWID`是未定义的或保持设置为其先前值。 - `%msg` - `%ROWCOUNT` - `%ROWID` - `SQLCODE` 这些局部变量不是由Dynamic SQL设置的。 (请注意,SQL Shell和Management Portal SQL接口执行Dynamic SQL。)相反,Dynamic SQL设置相应的对象属性。 在嵌入式SQL中使用以下ObjectScript特殊变量。这些特殊的变量名称不区分大小写。在过程启动时,这些变量将初始化为一个值。它们由嵌入式SQL操作设置。不能使用SET或NEW命令直接设置它们。 - `$TLEVEL` - `$USERNAME` 作为已定义的InterSystems IRIS嵌入式SQL接口的一部分,InterSystems IRIS可以在嵌入式SQL处理期间设置任何这些变量。 如果嵌入式SQL在类方法中(`procedureBlock = ON`),则系统会自动将所有这些变量放在`PublicList`中,并自动将`SQLCODE`,`%ROWID`,`%ROWCOUNT`,`%msg`以及SQL语句。可以通过引用方法来传递这些变量;通过引用传递的变量将不会在类方法过程块中自动更新。 如果嵌入式SQL在例程中,则程序员有责任在调用嵌入式SQL之前新建`%msg`,`%ROWCOUNT`,`%ROWID`和`SQLCODE`变量。更新这些变量可防止干扰这些变量的先前设置。为避免``错误,不应在迭代周期内执行此`NEW`操作。 ## %msg 包含系统提供的错误消息字符串的变量。如果InterSystems SQL将`SQLCODE`设置为负整数(表示错误),则仅设置`%msg`。如果`SQLCODE`设置为`0`或`100`,则`%msg`变量与其先前值保持不变。 此行为不同于相应的Dynamic SQL `%Message`属性,当没有当前错误时,该属性将设置为空字符串。 在某些情况下,特定的`SQLCODE`错误代码可能与一个以上的`%msg`字符串相关联,描述了生成`SQLCODE`的不同条件。 `%msg`还可以接受用户定义的消息字符串。当触发器代码显式设置`%ok = 0`来中止触发器时,这最常用于从触发器发出用户定义的消息。 当执行SQL代码时,将使用有效的NLS语言生成错误消息字符串。可以在不同的NLS语言环境中编译SQL代码。该消息将根据运行时NLS环境生成。请参见`$ SYS.NLS.Locale.Language`。 ## %ROWCOUNT 一个整数计数器,指示受特定语句影响的行数。 - `INSERT`,`UPDATE`,`INSERT OR UPDATE`和`DELETE`将`%ROWCOUNT`设置为受影响的行数。带有显式值的INSERT命令只能影响一行,因此将`%ROWCOUNT`设置为`0`或`1`。`INSERT`查询结果,`UPDATE`或`DELETE`可以影响多行,因此可以将`%ROWCOUNT`设置为0或正数。整数。 - 无论删除多少行还是删除任何行,`TRUNCATE TABLE`始终将`%ROWCOUNT`设置为`–1`。因此,要确定实际删除的行数,请在`TRUNCATE TABLE`之前对表执行`COUNT(*)`,或者使用`DELETE`而不是`TRUNCATE TABLE`删除表中的所有行。 - 没有声明游标的`SELECT`只能作用于一行,因此执行简单的`SELECT`总是会将`%ROWCOUNT`设置为`1`(与检索到的选择标准匹配的单行)或`0`(没有与选择标准匹配的行)。 - `DECLARE`游标名`CURSOR FOR SELECT`不会初始化`%ROWCOUNT`; `SELECT`之后,`%ROWCOUNT`不变,而`OPEN`游标名之后,`%ROWCOUNT`不变。第一个成功的`FETCH`设置`%ROWCOUNT`。如果没有行符合查询选择条件,则`FETCH`设置`%ROWCOUNT = 0`;否则,设置`%ROWCOUNT = 0`。如果`FETCH`检索与查询选择条件匹配的行,则它将设置`%ROWCOUNT = 1`。随后的每个获取行的`FETCH`都将递增`%ROWCOUNT`。 `CLOSE`时或`FETCH`发出`SQLCODE 100`(无数据或无更多数据)时,`%ROWCOUNT`包含已检索的总行数。 此`SELECT`行为与相应的Dynamic SQL`%ROWCOUNT`属性不同,该属性在查询执行完成时设置为0,并且仅在程序迭代查询返回的结果集时才递增。 如果`SELECT`查询仅返回聚合函数,则每个`FETCH`都将设置`%ROWCOUNT = 1`。即使表中没有数据,第一个`FETCH`始终以`SQLCODE = 0`来完成;任何后续的`FETCH`均以`SQLCODE = 100`完成,并设置`%ROWCOUNT = 1`。 以下嵌入式SQL示例声明一个游标,并使用`FETCH`来获取表中的每一行。到达数据结尾(`SQLCODE = 100`)时,`%ROWCOUNT`包含已检索的行数: ```java /// d ##class(PHA.TEST.SQL).ROWCOUNT() ClassMethod ROWCOUNT() { SET name="LastName,FirstName",state="##" &sql(DECLARE EmpCursor CURSOR FOR SELECT Name, Home_State INTO :name,:state FROM Sample.Person WHERE Home_State %STARTSWITH 'M') WRITE !,"BEFORE: Name=",name," State=",state &sql(OPEN EmpCursor) QUIT:(SQLCODE'=0) FOR { &sql(FETCH EmpCursor) QUIT:SQLCODE WRITE !,"Row fetch count: ",%ROWCOUNT WRITE " Name=",name," State=",state } WRITE !,"最终提取SQLCODE: ",SQLCODE &sql(CLOSE EmpCursor) WRITE !,"AFTER: Name=",name," State=",state WRITE !,"提取的总行数: ",%ROWCOUNT } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).ROWCOUNT() BEFORE: Name=LastName,FirstName State=## Row fetch count: 1 Name=O'Rielly,Chris H. State=MS Row fetch count: 2 Name=Orwell,John V. State=MT Row fetch count: 3 Name=Zevon,Heloisa O. State=MI ... Row fetch count: 37 Name=Joyce,Elmo R. State=MO Row fetch count: 38 Name=Jafari,Christine Z. State=MI 最终提取SQLCODE: 100 AFTER: Name=Jafari,Christine Z. State=OH 提取的总行数: 38 ``` 以下嵌入式SQL示例执行`UPDATE`并设置受更改影响的行数: ```java /// d ##class(PHA.TEST.SQL).ROWCOUNT1() ClassMethod ROWCOUNT1() { &sql(UPDATE Sample.Employee SET Salary = (Salary * 1.1) WHERE Salary < 50000) IF SQLCODEd ##class(PHA.TEST.SQL).ROWCOUNT1() Employees: 48 ``` **请记住,所有嵌入式SQL语句(在给定进程内)都会修改`%ROWCOUNT`变量。如需要`%ROWCOUNT`提供的值,请确保在执行其他Embedded SQL语句之前获取其值。根据嵌入式SQL的调用方式,可能必须在输入嵌入式SQL之前新建`%ROWCOUNT`变量。** **另请注意,显式回滚事务不会影响`%ROWCOUNT`的值。例如,以下内容将报告已进行了更改,即使它们已经滚动了。** ```java /// d ##class(PHA.TEST.SQL).ROWCOUNT2() ClassMethod ROWCOUNT2() { TSTART // 开始事务 NEW SQLCODE,%ROWCOUNT,%ROWID &sql(UPDATE Sample.Employee SET Salary = (Salary * 1.1) WHERE Salary < 50000) IF SQLCODEd ##class(PHA.TEST.SQL).ROWCOUNT2() Employees: 37 ``` 隐式事务(例如,如果UPDATE未通过约束检查)由`%ROWCOUNT`反映。 ## %ROWID 初始化进程时,未定义`%ROWID`。当发出`NEW %ROWID`命令时,`%ROWID`将重置为未定义。 `%ROWID`由下面描述的嵌入式SQL操作设置。如果该操作不成功或成功完成,但未获取或修改任何行,则`%ROWID`值与其先前值保持不变:未定义,或由先前的嵌入式SQL操作设置为某个值。因此,在每个嵌入式SQL操作之前,请务必新建`%ROWID`。 `%ROWID`设置为受以下操作影响的最后一行的`RowID`: - `INSERT`,`UPDATE`,`INSERT OR UPDATE`或`DELETE`:单行操作后,`%ROWID`变量包含系统分配的`RowID`(对象ID)值,该值分配给插入,更新或删除的记录。经过多行操作之后,`%ROWID`变量包含系统分配的最后一条插入,更新或删除的记录的`RowID`(对象ID)的值。如果未插入,更新或删除任何记录,则`%ROWID`变量值将保持不变。 `TRUNCATE TABLE`没有设置`%ROWID`。 - 基于游标的`SELECT:DECLARE`游标名称`CURSOR`和`OPEN`游标名称语句未初始化`%ROWID`; `%ROWID`值与其先前值保持不变。第一个成功的`FETCH`设置`%ROWID`。随后的每个获取行的`FETCH`都会将`%ROWID`重置为当前`RowID`值。如果`FETCH`检索一行可更新游标,则会设置`%ROWID`。可更新游标是其中顶部`FROM`子句仅包含一个元素(单个表名或可更新视图名)的游标。如果游标不可更新,则`%ROWID`保持不变。如果没有行符合查询选择条件,则`FETCH`不会更改先前的`%ROWID`值(如果有)。 `CLOSE`时或`FETCH`发出`SQLCODE 100`(无数据或无更多数据)时,`%ROWID`包含检索到的最后一行的`RowID`。 具有`DISTINCT`关键字或`GROUP BY`子句的基于游标的`SELECT`不会设置`%ROWID`。 `%ROWID`值与其先前的值(如果有)保持不变。 如果基于游标的`SELECT`仅返回聚合函数值,则不会设置`%ROWID`。如果它同时返回字段值和聚合函数值,则将每个`FETCH`的`%ROWID`值设置为查询返回的最后一行的`RowID`。 - 没有声明游标的`SELECT`不会设置`%ROWID`。完成简单的`SELECT`语句后,`%ROWID`值将保持不变。 在Dynamic SQL中,相应的`%ROWID`属性返回插入,更新或删除的最后一条记录的`RowID`值。执行`SELECT`查询时,Dynamic SQL不会返回`%ROWID`属性值。 可以使用以下方法调用从ObjectScript中检索当前的`%ROWID`: ``` DHC-APP> WRITE $SYSTEM.SQL.GetROWID() 213 ``` 在执行`INSERT`,`UPDATE`,`DELETE`,`TRUNCATE TABLE`或基于游标的`SELECT`操作之后,`LAST_IDENTITY` SQL函数将为最近修改的记录返回`IDENTITY`字段的值。如果表没有`IDENTITY`字段,则此函数返回最近修改记录的`RowID`。 ## SQLCODE 运行嵌入式SQL查询后,必须在处理输出主机变量之前检查`SQLCODE`。 如果`SQLCODE = 0`,则查询成功完成并返回数据。输出主机变量包含字段值。 如果`SQLCODE = 100`,则查询成功完成,但是输出主机变量值可能不同。任何一个: - 查询返回一个或多个数据行(`SQLCODE = 0`),然后到达数据的末尾(`SQLCODE = 100`),在这种情况下,输出主机变量设置为返回的最后一行的字段值。 `%ROWCOUNT> 0`。 - 查询未返回任何数据,在这种情况下,输出主机变量未定义。 `%ROWCOUNT = 0`。 如果查询仅返回聚合函数,则即使表中没有数据,第一个`FETCH`也会始终以`SQLCODE = 0`和`%ROWCOUNT = 1`来完成。第二个`FETCH`以`SQLCODE = 100`和`%ROWCOUNT = 1`结束。如果表中没有数据或没有数据与查询条件匹配,查询将根据需要将输出主机变量设置为0或空字符串。 如果`SQLCODE`为负数,则查询失败,并显示错误条件。 根据嵌入式SQL的调用方式,可能必须在输入嵌入式SQL之前新建`SQLCODE`变量。在触发代码中,将`SQLCODE`设置为非零值会自动将`%ok = 0`设置为中止并回滚触发操作。 在动态SQL中,相应的`%SQLCODE`属性返回SQL错误代码值。 ## $TLEVEL 事务级计数器。 InterSystems SQL将`$TLEVEL`初始化为0。 如果没有当前事务,`$TLEVEL`为0。 - 初始`START TRANSACTION`将`$LEVEL`设置为1。其他`START TRANSACTION`语句对`$TLEVEL`无效。 - 每个`SAVEPOINT`语句将`$TLEVEL`加1。 - `ROLLBACK TO SAVEPOINT`点名语句减少`$TLEVEL`。递减量取决于指定的保存点。 - `COMMIT`将`$LEVEL`重置为0。 - `ROLLBACK`将`$LEVEL`重置为0。 还可以使用`%INTRANSACTION`语句来确定事务是否在进行中。 `$TLEVEL`也由ObjectScript事务命令设置。 ## $USERNAME SQL用户名与InterSystems IRIS用户名相同,存储在ObjectScript `$USERNAME`特殊变量中。用户名可以用作系统范围的默认架构,也可以用作架构搜索路径中的元素。
文章
王喆 👀 · 九月 21, 2023

IRIS自动安装集群--manifest(安装清单)

前言 生产环境下我们部署和使用IRiS引擎,往往采用其主备镜像模式,虽然此架构简单但是往往我们需要持续在电脑前点击或者操作1到2小时,如果中间有个环节出现了问题有时我们可能需要部署一天. 接下来我分享的是IRIS自带的一个功能帮助我们部署---manifest-安装清单。他的主要使用方式是提前通过配置约定好我们期望的安装设置,在安装的过程中由IRIS程序直接执行脚本,简化IRIS集群的部署,减少运维人员的操作步骤,让我们有更多的精力放在实际项目和业务上。 1 简介 %Installer 实用程序允许您定义描述和配置特定 InterSystems IRIS 配置的安装清单,而不是分步安装过程。为此,我们需要创建一个类,其中包含描述所需配置的 XData 块,使用包含通常在安装期间提供的信息(超级服务器端口、操作系统等)的变量。我们还可以在类中包含一个使用 XData 块生成代码以配置实例的方法。本文提供了安装清单的示例,您可以复制和粘贴这个示例尝试使用。 定义清单后,可以在安装期间、从终端会话或代码调用它。注意:清单必须在 %SYS 命名空间中运行。 2 Manifest的最终成品 此成品展示的是一个一键安装主、备、仲裁的机器命令,此方法的使用可以便捷快速的安装主备环境,其基本每一行都有注释其说明: Include %occInclude /// Classname: App.MirrorInstall <br/> /// Summary: 镜像安装清单 <br/> /// Version: 1.0 <br/> /// Date: 2023年09月13日 14:23:24 <br/> /// Author: 王喆 <br/> Class App.MirrorInstall { XData Install [ XMLNamespace = INSTALLER ] { <Manifest> <!-- 镜像配置 --> <Log Text="镜像名称 " Level="0"/> <Var Name="MirrorName" Value="MIRRORSET" /> <Log Text=" 虚拟IP " Level="0"/> <Var Name="VituralIP" Value="192.168.98.110/24" /> <Log Text="仲裁机IP " Level="0"/> <Var Name="ArbiterNode" Value = "192.168.98.103|2188" /> <Log Text="网卡名称 " Level="0"/> <Var Name="PrimaryNetworkAdapter" Value = "Ethernet0" /> <Log Text="主机IP " Level="0"/> <Var Name="MasterIP" Value = "192.168.98.101" /> <Log Text="主机端口 " Level="0"/> <Var Name="MasterPort" Value = "2188" /> <Log Text="主机名称 " Level="0"/> <Var Name="MasterName" Value = "IRIS01" /> <Log Text="备机网卡名称 " Level="0"/> <Var Name="BackupNetworkAdapter" Value = "Ethernet0" /> <Log Text="备机IP " Level="0"/> <Var Name="BackupIP" Value = "192.168.98.102" /> <Log Text="备机名称 " Level="0"/> <Var Name="BackupName" Value = "IRIS02" /> <Log Text="实例名称 " Level="0"/> <Var Name="InstanceName" Value = "IRISHEALTH" /> <Log Text="镜像模式: 主机1或者备机0 " Level="0"/> <!-- <Var Name="MirrorModel" Value="1" />--> <!-- 安装文件所在的目录 --> <!-- <Var Name="INSTALLERDIR" Value = "D:\deploy" /> --> <!-- 实例所在的安装目录 --> <!-- <Var Name="PRODDIR" Value = "C:\InterSystems\IRISHealth" /> --> <!-- 激活 --> <!-- 通用内存堆的大小。 --> <SystemSetting Name="Config.config.gmheap" Value="1048576"/> <!-- 通用内存堆的大小。 --> <SystemSetting Name="Config.config.locksiz" Value="134217728"/> <!-- 错误日志中的最大条目数。 --> <SystemSetting Name="Config.config.errlog" Value="10000"/> <!-- 用于缓存例程缓冲区的共享内存大小。 --> <SystemSetting Name="Config.config.routines" Value="256"/> <!-- 为 8KB 数据库缓存分配的内存 一般为内存的一半 --> <SystemSetting Name="Config.config.globals8kb" Value="1000"/> <!-- 写入图像日志文件目录。 --> <SystemSetting Name="Config.config.wijdir" Value="D:/cache/wij"/> <!-- 日志文件的主要位置。 --> <SystemSetting Name="Config.Journal.CurrentDirectory" Value="E:/cache/journal"/> <!-- 日志文件的备用位置。 --> <SystemSetting Name="Config.Journal.AlternateDirectory" Value="D:/cache/journal"/> <!-- Caché 清除已完成日志文件之前的天数 --> <SystemSetting Name="Config.Journal.DaysBeforePurge" Value="3"/> <!-- 在此连续备份数后 --> <SystemSetting Name="Config.Journal.BackupsBeforePurge" Value="3"/> <!-- 最大IRISTempSizeAtStart --> <SystemSetting Name="Config.Startup.MaxIRISTempSizeAtStart" Value="10"/> <IfDef Var="INSTALLERDIR"> <Log Text=" 激活 " Level="0"/> <Invoke Class="App.MirrorInstall" Method="ConfigureInstance" CheckStatus="0"> <Arg Value="${INSTALLERDIR},${PRODDIR}"/> </Invoke> </IfDef> <!-- 创建命名空间 --> <Log Text="BKLINIK 命名空间存在与否判断 " Level="0"/> <If Condition='(##class(Config.Namespaces).Exists("BKLINIK")=0)'> <Log Text="不存在开始创建 " Level="0"/> <Namespace Name="BKLINIK" Create="yes" Code="BKLINIKAPP" Ensemble="1" Data="BKLINIKMSG"> <Configuration> <!-- 消息的目录 --> <Database Name="BKLINIKMSG" Dir="D:/DB/BKLINIKMSG" Create="yes"/> <!-- 代码的目录 --> <Database Name="BKLINIKAPP" Dir="D:/DB/BKLINIKAPP" Create="yes"/> <!-- 配置映射 --> <ClassMapping Package="HS" From="HSLIB"/> <ClassMapping Package="HSMOD" From="HSLIB"/> <ClassMapping Package="SchemaMap" From="HSLIB"/> <RoutineMapping Routines="HS.*" From="HSLIB" /> <RoutineMapping Routines="HSMOD.*" Type="INC" From="HSLIB" /> <RoutineMapping Routines="HSMOD.*" From="HSLIB" /> <RoutineMapping Routines="SchemaMap*" Type="INC" From="HSLIB" /> <GlobalMapping Global="%SYS" From="IRISSYS" /> <GlobalMapping Global="%SYS("HealthShare")" From="HSSYS"/> <GlobalMapping Global="EnsHL7.Annotation("HealthShare_2.5")" From="HSLIB" /> <GlobalMapping Global="EnsHL7.Description("HealthShare_2.5")" From="HSLIB" /> <GlobalMapping Global="EnsHL7.Schema("HealthShare_2.5")" From="HSLIB" /> <GlobalMapping Global="IRIS.Msg("HS")" From="HSLIB" /> <GlobalMapping Global="IRIS.Msg("HSErr")" From="HSLIB" /> <GlobalMapping Global="IRIS.Msg("HSFHIRErr")" From="HSLIB" /> <GlobalMapping Global="IRIS.Msg("HSFHIRXErr")" From="HSLIB" /> <GlobalMapping Global="IRIS.MsgNames("HS")" From="HSLIB" /> <GlobalMapping Global="IRIS.MsgNames("HSErr")" From="HSLIB" /> <GlobalMapping Global="IRIS.MsgNames("HSFHIRErr")" From="HSLIB" /> <GlobalMapping Global="IRIS.MsgNames("HSFHIRXErr")" From="HSLIB" /> </Configuration> </Namespace> <Log Text="创建命名空间 BKLINIK 结束" Level="0"/> <Log Text="Production启动" Level="0"/> <Namespace Name="BKLINIK" Create="no"> <Log Text=" 设置web应用程序 " Level="0"/> <CSPApplication Url="/csp/bklinik" Directory="${CSPDIR}bklinik" AuthenticationMethods="64" IsNamespaceDefault="true" Grant="%ALL" /> <CSPApplication Url="/csp/bklinik/services" Description="" Directory="${CSPDIR}bklinik\services" Resource="" Grant="%ALL" Recurse="1" LoginClass="" CookiePath="/csp/bklinik/services" AuthenticationMethods="64"/> <Log Text=" 导入代码 " Level="0"/> <!-- 如果设置了 SourceDir 就加载其中的文件 --> <!-- <IfDef Var="SourceDir"> --> <!-- <Log Text="SourceDir已定义-从脱机安装 ${SourceDir}" Level="0"/>--> <Import File="${INSTALLERDIR}\distr\"/> <!-- 设置命名空间的Production自动启动 前提得有这个--> <!-- <Production Name="BKLINIKPKG.FoundationProduction" AutoStart="1" /> --> <!-- </IfDef> --> <Log Text=" 导入代码成功 " Level="0"/> </Namespace> </If> <Log Text=" 镜像 " Level="0"/> <IfDef Var="MirrorModel"> <Log Text="Master已定义-从脱机安装 ${主机}" Level="0"/> <If Condition='(${MirrorModel}=1)' > <!-- 创建主镜像 --> <Log Text=" 创建主镜像 " Level="0"/> <Invoke Class="App.MirrorInstall" Method="CreateMirror" CheckStatus="0"> <Arg Value="${MirrorName},${VituralIP},${ArbiterNode},${PrimaryNetworkAdapter},${MasterIP},${MasterName}"/> </Invoke> </If> <If Condition='(${MirrorModel}=0)' > <!-- 加入镜像 --> <Log Text=" 加入镜像 " Level="0"/> <Invoke Class="App.MirrorInstall" Method="JoinMirror" CheckStatus="0"> <Arg Value="${MirrorName},${MasterName},${MasterIP},${MasterPort},${BackupNetworkAdapter},${BackupIP},${InstanceName}"/> </Invoke> </If> </IfDef> </Manifest> } /// MethodName: setup <br> /// Summary: 入口点方法,您需要调用,在类编译时,它从清单生成CachéObjectScript代码之后,您可以从终端运行此安装程序: <br> /// Parameter: Set pVars("Namespace")="NewNamespace" /// Set pVars("SourceDir")="C:\deploy\distr\" /// set pVart("MirrorModel")=1 /// Do ##class(App.MirrorInstall).setup(.pVart) <br> /// Return: { Boolean } <br> /// Date: 2023年09月13日 15:39:05 <br> /// Author: 王喆 <br> ClassMethod setup(ByRef pVars, pLogLevel As %Integer = 0, pInstaller As %Installer.Installer, pLogger As %Installer.AbstractLogger) As %Status [ CodeMode = objectgenerator, Internal ] { Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "Install") } /// MethodName: ConfigureInstance <br> /// Summary: 激活的方法 <br> /// Parameter: { Object } <br> /// Return: { %Status } <br> /// Date: 2023年09月13日 14:24:17 <br> /// Author: 王喆 <br> ClassMethod ConfigureInstance(InstallerDir As %String, ProdDir As %String) As %Status { Set tSC = $ZF(-1,"copy "_ InstallerDir _ "\iris.key "_ProdDir_"\mgr\iris.key") Set tSC = ##class(%SYSTEM.License).Upgrade() Set tSC = $ZF(-1,"net start ISCAgent") //do ##class() Quit tSC } /// MethodName: CreateMirror <br> /// Summary: 创建镜像 <br> /// Parameter: { Object } <br> /// Return: { %Status } <br> /// Date: 2023年09月13日 14:24:52 <br> /// Author: 王喆 <br> ClassMethod CreateMirror(MirrorName As %String, VituralIP As %String, ArbiterNode As %String, PrimaryNetworkAdapter As %String, MasterIP As %String, MasterName As %String) As %Status { ZNspace "%SYS" #; Start %Service_Mirror 开启镜像服务 Set tSC = ##class(Security.Services).Get("%Service_Mirror",.properties) Set properties("Enabled")=1 Set tSC = ##class(Security.Services).Modify("%Service_Mirror",.properties) Set MirrorInfo("VirtualAddress") = VituralIP Set MirrorInfo("ArbiterNode") = ArbiterNode If "" '= MasterIP{ Set MirrorInfo("ECPAddress") = MasterIP } If "" '= PrimaryNetworkAdapter{ Set MirrorInfo("VirtualAddressInterface") = PrimaryNetworkAdapter } #; if "" '= MasterName #; Set MasterName = ##class(SYS.Mirror).DefaultSystemName() Try { Set tSC = ##class(SYS.Mirror).CreateNewMirrorSet(MirrorName,MasterName,.MirrorInfo) If $$$ISERR(tSC){ Set errobj = ##class(%Exception.General).%New() Set errobj.Name = "Error #",errobj.Code = $SYSTEM.Status.GetErrorCodes(tSC),errobj.Data = $SYSTEM.Status.GetOneStatusText(tSC,1) Throw errobj } } Catch ex { Write ex.DisplayString() } //Create Mirror Se1t //do ##class(SYS.Mirror). Quit tSC } /// MethodName: JoinMirror <br> /// Summary: 加入镜像 <br> /// Parameter: { Object } <br> /// Return: { %Status } <br> /// Date: 2023年09月13日 14:25:19 <br> /// Author: 王喆 <br> ClassMethod JoinMirror(MirrorName As %String, MasterName As %String, MasterIP As %String, MasterPort As %Integer, BackupNetworkAdapter As %String, BackupIP As %String, InstanceName As %String) As %Status { ZNspace "%SYS" //Start %Service_Mirror Set tSC = ##class(Security.Services).Get("%Service_Mirror",.properties) Set properties("Enabled")=1 Set tSC = ##class(Security.Services).Modify("%Service_Mirror",.properties) If "" '= BackupNetworkAdapter{ Set LocalInfo("VirtualAddressInterface") = BackupNetworkAdapter } If "" '= BackupIP{ Set LocalInfo("ECPAddress") = BackupIP } Try { Set SysName = ##class(SYS.Mirror).DefaultSystemName() Set tSC = ##class(SYS.Mirror).JoinMirrorAsFailoverMember(MirrorName,SysName,InstanceName,MasterIP,MasterPort,.LocalInfo) If $$$ISERR(tSC){ Set errobj = ##class(%Exception.General).%New() Set errobj.Name = "Error #",errobj.Code = $SYSTEM.Status.GetErrorCodes(tSC),errobj.Data = $SYSTEM.Status.GetOneStatusText(tSC,1) Throw errobj } } Catch ex { Write ex.DisplayString() } Quit tSC } } 3 安装清单执行 3.1 前置条件: 特别注意:windows server r2 2012 先安装2022版本的IRIS务必去微软官网下载Windows8.1-KB2999226-x64.msu并安装(必须) 预先安装 Java环境推荐Java8(必须) 服务器磁盘分区得有C盘 D盘 E盘可用(推荐) 部署包解压路径 D:\deploy(可选) 安装浏览器(可选) 3.2安装脚本 以管理员权限运行cmd,进入D://deploy 执行安装程序,其在安装时候就可以配置好镜像: 主机执行: ./IRISHealth-2022.1.2.574.0-win_x64.exe INSTALLERMANIFEST="D:\deploy\distr\MirrorInstall.xml" INSTALLERMANIFESTPARAMS="MirrorModel=1,PRODDIR=C:\InterSystems\IRISHealth,INSTALLERDIR=D:\deploy,GlobalBuffers=32768,RoutineCache=1000" INSTALLERMANIFESTLOGFILE="D:\deploy\mirrorLog.txt" INSTALLERMANIFESTLOGLEVEL="3" 备机执行: ./IRISHealth-2022.1.2.574.0-win_x64.exe INSTALLERMANIFEST="D:\deploy\distr\MirrorInstall.xml" INSTALLERMANIFESTPARAMS="MirrorModel=0,PRODDIR=C:\InterSystems\IRISHealth,INSTALLERDIR=D:\deploy,GlobalBuffers=32768,RoutineCache=1000" INSTALLERMANIFESTLOGFILE="D:\deploy\mirrorLog.txt" INSTALLERMANIFESTLOGLEVEL="3" 脚本解释: ./IRISHealth-2022.1.2.574.0-win_x64.exe // 安装程序 INSTALLERMANIFEST="D:\deploy\distr\MirrorInstall.xml" // 安装清单所在的目录 INSTALLERMANIFESTPARAMS=" // 安装的时候传入的参数 MirrorModel=1, // 1-主机 0-备机 PRODDIR=C:\InterSystems\IRISHealth, // 你想安装的目录 INSTALLERDIR=D:\deploy, // 安装文件所在的目录 GlobalBuffers=32768, // Global内存的大小 RoutineCache=1000" // Routine内存的大小 INSTALLERMANIFESTLOGFILE="D:\deploy\mirrorLog.txt" // 安装时候记录的日志位置 INSTALLERMANIFESTLOGLEVEL="3" // 日志级别 好了以上是我本次分享的关于IRIS关于Manifest的相关内容,本文只对manifest做了示例,具体的如何使用及其步骤在官网中描述的比较清楚,如果有需要大家可以去官网看看。 如果您觉得对你有帮助就给我点个赞吧,感激不尽!!!
文章
Michael Lei · 十二月 9, 2022

基于 IRIS SQL高级功能实现 CI/CD的技术原理和指导

在数量众多、形形色色的 SQL 数据库市场中,InterSystems IRIS 作为一个超越 SQL 的平台脱颖而出,它提供无缝的多模型体验,支持丰富的开发范式。 特别是,先进的对象-关系引擎已经帮助组织为其数据密集型工作负载的每个方面使用了最适合的开发方式,例如在通过对象获取数据并同时通过 SQL 查询数据。 持久类与 SQL 表相对应,其属性与表中的各列相对应,可以使用用户定义的函数或存储过程轻松访问业务逻辑。 在这篇文章中,我们将深入了解表面之下的一点底层技术,讨论它可能如何影响您的开发和部署方式。 这是我们计划发展和改进的产品领域,因此请不要犹豫,在下面的评论区分享您的观点和体验。 保存存储定义 {Saving the Storage Definition} 编写全新的业务逻辑很容易,而且假如您有定义明确的 API 和规范,那么调整或扩展通常也很容易。 但是,当它不仅仅是业务逻辑,还涉及持久化数据时,从初始版本更改的任何内容都将需要能够妥善处理通过早期版本获取的数据。 在 InterSystems IRIS 上,数据和代码在一个高性能引擎中共存,没有您在其他 3GL 或 4GL 编程框架中可能看到的六个抽象层。 这意味着,在使用默认存储时,只有一个非常薄且透明的映射将您的类属性映射到每行数据Global节点的 $list 位置。 如果想添加或移除属性,您不希望已移除的属性中的数据显示在新属性下。 类属性的这个映射是由存储定义进行处理的,这是一个您可能已经在类定义的底部注意到的神秘XML 块。 首次编译类时,将根据该类的属性和参数生成新的存储定义。 当您对类定义进行更改后,在重新编译时,这些更改将与现有存储定义进行协同和修正,以便与现有数据保持兼容。 因此,当您试着重构类时,存储定义会仔细考虑您之前做的事情,并确保新旧数据都可以访问。 我们称之为**Schema 演进**。 在其他大多数 SQL 数据库中,表的物理存储是不透明的,如果可见的话,并且只能通过类似 `ALTER TABLE` 语句进行修改。 这些都是标准 DDL(数据定义语言)命令,但表达能力比直接在 IRIS 上修改类定义和过程代码要差很多。 InterSystems 致力于让 IRIS 开发者能够干净利落地分离代码和数据,因为这对于确保应用程序的顺利打包和部署至关重要。 存储定义在这方面发挥着独特的作用,因为它可以捕获两者之间的映射方式。 这就是值得在传统开发和 CI/CD pipeline不同实践的背景下对其进行深入研究的原因。 导出为通用描述语言 Universal Definition Language 如今,源代码管理是以文件为基础的,所以我们先来看看 IRIS 的主要文件导出格式。 顾名思义,通用描述语言UDL意味着您在 InterSystems IRIS 上编写的所有代码的文件格式是通用的。 这是使用 VS Code ObjectScript 插件时的默认导出格式,并产生易于读取的文件,该文件与您在 IDE 中看到的内容几乎完全相同,并且您的应用程序中的每个类(表)都有一个单独的 .cls 文件。 您可以使用$SYSTEM.OBJ.Export 或者是用 VS Code 插件来创建 UDL 文件。 在Studio的时代,您可能还记得一种 XML 格式,它会捕获与 UDL 相同的信息,并允许将多个类分组到一个导出中。 虽然这种格式在某些场景中很方便,但在读取和跟踪不同版本之间的差异时却不太实用,因此我们暂时将其忽略。 由于 UDL 旨在捕获 IRIS 可以表达的关于类的所有内容,它会包含类定义的所有元素,包括完整的存储定义。 在导入已包含存储定义的类定义时,IRIS 会验证该存储定义是否涵盖该类的所有属性和索引,如果是,只需按原样接受并覆盖该类先前的存储定义即可。 这使得 UDL 成为一种管理类及其存储定义版本的实用格式,因为无论您将它部署到何处,它都会保留通过先前版本的类获取的数据的向后兼容性。 如果您是一位硬核开发者,您可能想知道这些存储定义是否会不断增长,是否需要无限期地携带这些“包袱”。 存储定义的目的是保持与预先存在的数据的兼容性,因此如果您知道没有这些数据,并且希望摆脱冗长的兼容和继承关系,则可以通过从类定义中移除存储定义并让类编译器重新生成来“重置”存储定义。 例如,您可以使用它来利用新的最佳实践,比如扩展集的使用,它们会实现经过哈希处理的Global名称,并将每个索引分离到自己的全局名称中,从而提高效率。 为了在应用程序内实现向后兼容性,我们不能在 %Persistent 超类中统一更改此类默认值(但我们在使用 `CREATE TABLE` DDL 命令从头开始创建表时会应用它们),因此定期检查类及其存储是很有价值的。 用户也可以直接编辑存储定义 XML文件,但应格外小心,因为这可能会导致现有数据无法访问。 存储定义会提供类之间的智能映射,并随着Schema的演进而自动调整。 还有什么? 静态还是动态? 您可能知道,InterSystems IRIS SQL 引擎充分利用表统计信息来确定用户执行的任何给定语句的最佳查询计划。 表统计信息包括表大小、值在列中的分布方式等指标。 此信息可以帮助 IRIS SQL 优化器确定哪个索引最有用,按何种顺序连接表等,因此直观地说,统计信息越新,优化查询计划的机会就越大。 遗憾的是,直到我们在 IRIS 2021.2 中引入快速块采样之前,收集准确的表统计信息一直是一项计算成本高昂的操作。 因此,当客户将同一应用程序部署到数据模式基本相同的许多环境中时,有必要考虑应用程序代码中的表统计信息部分并将其包含在表定义中。 这就是如今在 IRIS 上您会发现存储定义内嵌入了表统计信息的原因。 通过手动调用 `TUNE TABLE` 收集表统计信息或通过查询(见下文)隐式收集表统计信息时,新统计信息会写入存储定义,此表的现有查询计划将失效,因此它们可以在下次执行时利用新统计信息。 由于它们是存储定义的一部分,这些统计信息将是 UDL 类导出的一部分,因此会出现在源代码存储库中。 如果是经过仔细审查的已打包应用程序的统计信息,这是可取的,因为您会希望这些特定的统计信息能够推动生成所有应用程序部署的查询计划。 从 2021.2 开始,当查询一个根本没有任何统计资料且符合快速块采样条件的表时,IRIS会在查询计划开始时自动收集表的统计数据。在我们的测试中,使用最新的统计数据对比完全没有统计数据而言的好处,明显大于即时收集统计数据的成本。然而,对于一些客户来说,这会有一些副作用,即在开发人员的实例上自动收集的统计数据最终出现在源控制系统的存储定义中,并最终出现在打包的应用程序中。很明显,该开发者环境中的数据以及其上的统计数据可能无法代表真正的客户部署,并导致次优的查询计划。 这种情况可以轻松避免。 可以在调用$SYSTEM.OBJ.Export()时使用 `/exportselectivity=0` 限定符,在类定义导出中排除表统计信息。 可以使用$SYSTEM.OBJ.SetQualifiers("/exportselectivity=0")配置此标志的系统默认值。 然后,可以由最终部署中的自动收集来决定如何获取代表性的统计信息,将明确的统计信息收集作为部署过程的一部分,这会覆盖应用程序可能打包的任何内容,或者通过它们自己的导入/导出函数单独管理表统计信息:$SYSTEM.SQL.Stats.Table.Export()和 Import()。 从长远来看,我们打算将表统计信息移到数据中,而不是作为代码的一部分,并且更清晰地区分开发者明确定义的统计信息与从实际数据中收集的统计信息。 此外,我们正在计划实现更大程度的自动化,以根据表数据随时间的变化情况定期更新这些统计信息。 总结 在这篇文章中,我们概述了存储定义在 IRIS 对象-关系引擎中的作用,它如何支持Schema的演进,以及将其包含在源代码管理系统中的意义。 我们还介绍了目前将表统计信息存储在该存储定义中的原因,以及为了确保您的应用程序部署最终具有代表实际客户数据的统计信息而建议的开发做法。 如上所述,我们计划进一步增强这些功能,因此我们期待您对当前功能和计划功能提供反馈意见并适当优化我们的设计。
文章
Hao Ma · 二月 16, 2023

ChatGPT 为您创建消息转换?

A "big" or a "little" ask for ChatGPT? 几周前我尝试了 OpenAI GPT 的编码模型,看看它是否可以在医疗保健系统之间进行一些消息转换。它肯定可以,在相当大的程度上。已经将近 3 周了,对于 ChatGPT 来说是很长很长的时间,所以我想知道它现在成长得有多快,以及它是否可以为我们做一些集成工程师的工作,例如它是否可以创建一个 InterSystems COS DTL将 HL7 转换为 FHIR 信息? 在不到一两分钟的时间内,我立即得到了一些答案。 测试 首先我想测试一下我是在和它背后的正确“人”说话 问题一:如何将HL7 V2.4报文转为FHIR STU3? ChatGPT: 将 HL7 V2.4 消息转换为 FHIR STU3 涉及多个步骤,因为这两个标准具有不同的数据模型和结构。以下是该过程的高级概述: 识别 HL7 消息中需要映射到 FHIR 资源的相关数据元素。这将取决于具体用例和交换的数据。 为每个数据元素定义映射规则。这涉及识别对应于 HL7 数据元素的等效 FHIR 资源和元素,以及任何必要的转换或翻译。 编写一个程序或脚本,可以从 HL7 消息中提取数据,应用映射规则,并创建相应的 FHIR 资源。这可能涉及使用正则表达式、解析库和 FHIR 资源创建库的组合。 根据 STU3 规范验证 FHIR 资源,以确保它们符合标准并包含所有必需的元素。 以下是此过程如何将 HL7 V2.4 观察消息转换为 FHIR STU3 观察资源的示例: 识别 HL7 消息中的相关数据元素,例如观察代码、值和单位。 定义每个数据元素的映射规则,例如将 HL7 观察代码映射到 FHIR CodeableConcept,将值和单位映射到 FHIR 数量。 编写可以从 HL7 消息中提取数据、应用映射规则并创建具有适当元素的 FHIR 观察资源的程序或脚本。 根据 STU3 规范验证 FHIR 观察资源,以确保它包含所有必需的元素并且有效。 具体的实现细节将取决于所使用的编程语言和库,以及映射的特定 HL7 消息和 FHIR 资源。建议查阅 HL7 和 FHIR 规范,并在需要时寻求专家建议。 好吧,这不仅听起来是对的——实际上它的回答比大多数人会做的更全面。它现在引起了我的注意,所以让我们开始吧。 问题 2:您能否在 Python 中创建示例转换以将 HL7 转换为 FHIR ChatGPT : 当然!下面是一个示例 Python 代码片段,演示了如何将 HL7 消息转换为 FHIR STU3 观察资源: from hl7apy import core from fhir.resources.observation import Observation # Parse the HL7 message hl7_message = 'MSH|^~\&|SENDING_APP|SENDING_FACILITY|RECEIVING_APP|RECEIVING_FACILITY|20220527120512||ORU^R01|MSGID12345|P|2.4\r' \ 'OBX|1|NM|GLU^Glucose||100|mg/dL||N|||F|||20220527120512\r' hl7_parsed = core.parse_message(hl7_message) # Map HL7 elements to FHIR elements observation = Observation() observation.code = { "coding": [{ "system": "http://loinc.org", "code": hl7_parsed.obx[0].observation_id.value, "display": hl7_parsed.obx[0].observation_id.value }] } observation.valueQuantity = { "value": hl7_parsed.obx[0].value.value, "unit": hl7_parsed.obx[0].units.value, "system": "http://unitsofmeasure.org", "code": hl7_parsed.obx[0].units.value } # Serialize the FHIR resource as JSON observation_json = observation.json() # Print the serialized FHIR resource print(observation_json) 此代码使用hl7apy库解析HL7 消息并提取相关数据元素,并从fhir.resources中使用fhir.resources.observation类----- 创建 FHIR 观察资源的模块。 在此示例中,代码将 HL7 观察 ID 映射到 FHIR 代码元素,并将 HL7 值和单位映射到 FHIR valueQuantity 元素。然后使用json()方法将 FHIR 资源序列化为 JSON。 请注意,这是一个简单示例,您可能需要根据具体用例映射更多数据元素。此外,您还需要安装 hl7apy 和 fhir.resources库才能运行此代码。 这是个好答案 - 给出了一个不错的样板代码。现在让我们给它一个具体的挑战: 问题 3:您能否提供在 Intersystems COS DTL 中从 HL7 V2 到 FHIR Stu3 的示例转换 ChatGPT : 当然!以下是如何在 InterSystems COS DTL(数据转换语言)中将 HL7 V2 消息转换为 FHIR STU3 观察资源的示例: Class DTL.HL7toFHIR Extends %RegisteredObject { ClassMethod TransformHL7ToObservation(hl7Message As %String) As %String [ CodeMode = expression, DDLAllowed = 0 ] { set src = ##class(EnsLib.HL7.Message).%New() set src.DocType = "2.4" set src.Parse(hl7Message) set obs = ##class(FHIR.Resource.Observation).%New() set obs.subject = { "reference": "Patient/"_src.MSH.GetSendingApplication() } set obs.status = "final" set loincCode = src.GetSegment("OBX", 1).GetField(3).GetField(1) set obs.code = { "coding": [ { "system": "http://loinc.org", "code": loincCode, "display": loincCode } ] } set value = src.GetSegment("OBX", 1).GetField(5).GetField(1) set units = src.GetSegment("OBX", 1).GetField(6).GetField(1) set obs.valueQuantity = { "value": value, "unit": units, "system": "http://unitsofmeasure.org", "code": units } quit obs.%ToJSON() } } 此代码定义了一个名为 TransformHL7ToObservationI()的类方法,它将 HL7 V2 消息作为输入并返回 FHIR STU3 观察资源作为 JSON 字符串。 该方法首先创建一个新的 EnsLib.HL7.Message 对象并使用它来解析输入的 HL7 消息。然后创建一个新的 FHIR.Resource.Observation 对象并设置 subject和 status 元素。 接下来,代码从 HL7 消息中提取 LOINC 代码、值和单位,并将它们映射到 FHIR 资源中的相应元素。 最后,该方法将 FHIR 资源序列化为 JSON 并将其返回。 要使用此类方法,您可以从另一个 COS 方法或使用 InterSystems 的 REST API 从外部系统调用它。下面是如何从另一个 COS 方法调用该方法的示例: set hl7Message = "MSH|^~\&|SENDING_APP|SENDING_FACILITY|RECEIVING_APP|RECEIVING_FACILITY|20220527120512||ORU^R01|MSGID12345|P|2.4\rOBX|1|NM|GLU^Glucose||100|mg/dL||N|||F|||20220527120512\r" set fhirObservation = ##class (DTL.HL7toFHIR).TransformHL7ToObservation(hl7Message) write fhirObservation 请注意,您需要在 InterSystems 环境中定义 -----107----- 和 -----108----- 类,并具有运行 COS 代码的适当权限。 好吧,这可能是迄今为止我们所能预料到的最有效、最全面、最切题的答案,只需大约 25 秒。是不是? 既然我们在这里,我们需要公平对待我也喜欢的其他广泛使用的选项。于是我又怀着信心继续问了几个问题: 问题4:能否给出Orion Rhapsody中从HL7 V2到FHIR Stu3的转换示例 ChatGPT : 当然!以下是如何在 Orion Rhapsody 中将 HL7 V2 消息转换为 FHIR STU3 观察资源的示例: 首先,创建一个新的 Rhapsody 通道并添加一个 HL7 Connector 组件来接收输入的 HL7 V2 消息。 将新的数据转换组件添加到通道并将其配置为使用自定义脚本。 在数据转换组件中,添加以下脚本以将 HL7 V2 消息转换为 FHIR STU3 观察资源: function transform(msg) { var hl7 = Packages.ca.uhn.hl7v2.model.v24.message.ORU_R01(msg.getRawData()); var observation = new Packages.org.hl7.fhir.dstu3.model.Observation(); observation.setStatus(Packages.org.hl7.fhir.dstu3.model.Observation.ObservationStatus.FINAL); var codeableConcept = new Packages.org.hl7.fhir.dstu3.model.CodeableConcept(); var coding = new Packages.org.hl7.fhir.dstu3.model.Coding(); coding.setSystem("http://loinc.org"); coding.setCode(hl7.getRESPONSE().getORDER_OBSERVATION()[0].getOBR().getUniversalServiceIdentifier().getIdentifier().getValue()); coding.setDisplay(hl7.getRESPONSE().getORDER_OBSERVATION()[0].getOBR().getUniversalServiceIdentifier().getText().getValue()); codeableConcept.addCoding(coding); observation.setCode(codeableConcept); var quantity = new Packages.org.hl7.fhir.dstu3.model.Quantity(); quantity.setValue(hl7.getRESPONSE().getORDER_OBSERVATION()[0].getOBSERVATION()[0].getOBX().getObservationValue()[0].getValue().getValue()); quantity.setUnit(hl7.getRESPONSE().getORDER_OBSERVATION()[0].getOBSERVATION()[0].getOBX().getUnits().getText().getValue()); observation.setValue(quantity); var reference = new Packages.org.hl7.fhir.dstu3.model.Reference(); reference.setReference("Patient/" + hl7.getPATIENT_RESULT().getPATIENT().getPid3_PatientIdentifierList()[0].getID().getValue()); observation.setSubject(reference); return JSON.stringify(observation); } 保存脚本并通过向通道发送 HL7 V2 消息来测试转换。转换后的 FHIR STU3 观察资源应由数据转换组件以 JSON 格式输出。 此脚本使用 HAPI FHIR 和 HAPI HL7 v2 库,它们包含在 Orion Rhapsody 中。它首先使用 HAPI HL7 库解析输入的 HL7 V2 消息。 接下来,脚本创建一个新的 FHIR STU3 观测资源并设置它的 status, code, value, 和 subject 元素基于 HL7 消息中的相应值。 最后,脚本将 FHIR 资源序列化为 JSON 并将其返回。 请注意,这只是一个简单示例,可能不适用于所有 HL7 V2 消息类型或 FHIR 资源。您可能需要修改脚本以处理不同的消息类型或将其他字段映射到 FHIR 资源。 问题 5你能在 Mirth 集成引擎中创建一个示例转换,将 HL7 v2.4 转换为 FHIR R4 ChatGPT : 当然!以下是如何在 Mirth 中将 HL7 V2.4 消息转换为 FHIR R4 观察资源的示例: 首先,创建一个新的 Mirth 频道并添加一个 HL7v2 Listener 源连接器以接收输入的 HL7 V2 消息。 将新的 JavaScript Writer 目标连接器添加到通道并将其配置为使用自定义脚本。 在 JavaScript Writer 连接器中,添加以下脚本以将 HL7 V2 消息转换为 FHIR R4 观察资源: var hl7msg = msg['HL7v2 Message'].toString(); // get the HL7 V2 message var hl7data = Packages.ca.uhn.hl7v2.parser.Parser.parse(hl7msg); // parse the message with the HAPI HL7 parser // extract values from the HL7 message and create the FHIR resource var obs = new Packages.org.hl7.fhir.r4.model.Observation(); obs.setStatus(Packages.org.hl7.fhir.r4.model.Observation.ObservationStatus.FINAL); var code = new Packages.org.hl7.fhir.r4.model.CodeableConcept(); code.addCoding() .setSystem("http://loinc.org") .setCode(hl7data.get("OBR-4-1").getValue()) .setDisplay(hl7data.get("OBR-4-2").getValue()); obs.setCode(code); var value = new Packages.org.hl7.fhir.r4.model.Quantity(); value.setValue(hl7data.get("OBX-5-1").getValue()); value.setUnit(hl7data.get("OBX-6").getValue()); obs.setValue(value); var subject = new Packages.org.hl7.fhir.r4.model.Reference(); subject.setReference("Patient/" + hl7data.get("PID-3-1").getValue()); obs.setSubject(subject); // output the FHIR resource as JSON var json = org.hl7.fhir.r4.formats.JsonParser().setPrettyPrint(true).encodeResourceToString(obs); return json; 保存脚本并通过向通道发送 HL7 V2 消息来测试转换。转换后的 FHIR R4 观察资源应由 JavaScript Writer 连接器以 JSON 格式输出。 此脚本使用 HAPI FHIR 和 HAPI HL7 v2 库,它们包含在 Mirth 中。它首先从 Mirth 消息对象中提取 HL7 V2 消息,并使用 HAPI HL7 解析器对其进行解析。 接下来,该脚本创建一个新的 FHIR R4 观察资源并设置其 status, code, value, 和 subject 元素基于 HL7 消息中的相应值。 最后,脚本将 FHIR 资源序列化为 JSON 并将其返回。 请注意,这只是一个简单示例,可能不适用于所有 HL7 V2 消息类型或 FHIR 资源。您可能需要修改脚本以处理不同的消息类型或将其他字段映射到 FHIR 资源。 结论? 我不再有任何结论或下一步,因为即使是我的想象力也无法赶上它的飞跃。看起来我现在的选择似乎很有限:要么我可以花时间挑出它不完美的编码风格,要么我可以再过几周重新考虑我盘子里剩下的东西?我需要比以往任何时候都更仔细地思考这件事的潜力。 :)开个玩笑,虽然我很喜欢在这个论坛上发帖(感谢主持人),但我想到的另一个想法是,这“可能”实际上是一些竞争激烈的利基市场参与者迈向大众市场的重要推动力,对吧?由于各种原因,过去需要数年时间才能真正适应某些编码语言和脚本,但现在情况正在发生变化,使用 ChatGPT,它不仅提供精心编写的文档、说明和示例,而且还可以在未来几个月或几年内自动制造您选择的工程工具,对吗?它似乎能够提升“编程语言”的水平,最终使得编程语言中非功能性的特点,比如性能和服务质量等,将变得更重要。 太强了,就很离谱,这比官方文档还好使吗 还是有些错误的,详情可以到英文原文评论中看到
文章
姚 鑫 · 二月 19, 2021

第四十一章 Caché 变量大全 $ZSTORAGE 变量

# 第四十一章 Caché 变量大全 $ZSTORAGE 变量 包含进程的最大可用内存。 # 大纲 ``` $ZSTORAGE $ZS ``` # 描述 `$ZSTORAGE`包含`JOB`的进程私有内存的最大内存量(以`KB`为单位)。此内存可用于局部变量、堆栈和其他表。此内存限制不包括例程目标代码的空间。此内存根据需要分配给进程,例如在分配数组时。 一旦将此内存分配给进程,通常在该进程退出之前不会释放它。但是,当大量内存被使用(例如,大于32MB)然后被释放时,系统间IRIS会尝试在可能的情况下将释放的内存释放回操作系统。 还可以使用`$ZSTORAGE`设置最大内存大小。例如,以下语句将作业的最大进程专用内存设置为`524288 KB`: ```java SET $ZSTORAGE=524288 ``` 更改`$ZSTORAGE`会更改`$STORAGE`特殊变量的初始值,该变量包含进程的当前可用内存(以字节为单位)。 `$ZSTORAGE`的最大值为`2147483647`。`$ZSTORAGE`默认值为`262144`。`$ZSTORAGE`的最小值为`128`。`$ZSTORAGE`值大于最大值或小于最小值会自动默认为最大值或最小值。`$ZSTORAGE`设置为整数值;InterSystems IRIS截断任何小数部分(如果指定)。 可以通过更改最大每进程内存(KB)系统配置设置来更改`$ZSTORAGE`默认值。在管理门户中,依次选择System Administration、Configuration、Systtem Configuration、Memory和Startup。可以根据需要增加每个进程的最大内存(KB),最大为2147483647 KB。更改每个进程的最大内存(KB)会更改后续启动的进程的`$ZSTORAGE`值;对当前进程的`$ZSTORAGE`值没有影响。 ![image](77C3C8B1AB324C1993338A39D5579A17) ![image](85C6C2092243441193944B91DB6F6191) # 示例 以下示例将`$ZSTORAGE`设置为其最大值和最小值。尝试将`$ZSTORAGE`设置为小于最小值的值(16)时,会自动将`$ZSTORAGE`设置为其最小值(128): ```java /// d ##class(PHA.TEST.SpecialVariables).ZS() ClassMethod ZS() { SET $ZS=128 WRITE "minimum storage=",$ZS,! SET $ZS=16 WRITE "less than minimum storage=",$ZS,! SET $ZS=2147483647 WRITE "maximum storage=",$ZS,! } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZS() minimum storage=128 less than minimum storage=128 maximum storage=2147483647 ```
文章
Claire Zheng · 八月 17, 2021

FHIR标准和国际基于FHIR的互联互通实践(1):如何定义互联互通?

什么是互联互通?我们所说的互联互通其实就是国际上的互操作性,HIMSS对于互操作性定义的是:不同的信息系统、设备、应用系统之间、程序之间,在机构区域和国家边界之内,以及跨机构、区域和国家边界,以协调的方式来访问交换集成和协作使用数据的能力。 它对于使用效果提出了一些目标,比如希望能够提供及时和无缝的信息的可移植性,和优化个人和人群的健康。这是它定义的对于互操作性的4个不同的级别: 基础级别就比较简单,仅仅打通了系统之间进行数据通讯的通道; 结构级别,在基础级别之上定义了数据交换的格式和语法; 再上面有语义级别,语义级别是建立在行业通用的数据模型和数据编码之上的,使用标准化的行业语义来定义数据元素,使用标准的值集,因此语义级别的互操作性才是全行业可以理解的,并且有确定的行业意义的这种互操作,也就是说语义级别的互操作才是基于标准的互操作; 在语义级别之上,还有组织级别的互操作。通常这些都是由国家、国际的行业协会、行业标准开发组织开发的,加入了很多的其他方面的一些考虑,比如说政策的、社会的、法律的,分析了通用的业务流程和工作流,在这个基础之上设定了参与互操作的各方的角色、权限、服务、知情同意策略等等。 我国目前的互联互通标准化成熟度测试,其实就是组织级别的互操作。 注:本文根据InterSystems中国技术总监乔鹏演讲整理而成。
文章
Claire Zheng · 八月 17, 2021

FHIR标准和国际基于FHIR的互联互通实践(2):互操作标准构成

在医疗行业要实现互操作,应该要达到语义级别。只有达到语义级别才能保障医疗信息的准确和医疗行为的安全。而要达到语义级别,我们需要基于标准。 这里标准特别多,我们把它称之为五位一体。标准有5个方面。 首先是词汇/术语标准,是相互通讯的健康医疗信息系统需要依赖于结构化的词汇术语、代码值集、分类系统来进行相应的表述。词汇和术语的标准就是表达健康概念的标准。例如我们在使用的世界卫生组织的ICD-10,当然还有其他的,比如SNOMED-CT等等。 第二是内容标准,是描述信息交换过程中间的数据内容和结构的标准。它还包括了通用数据的定义,例如我们熟悉的HL7 CDA、 HL7 V2、 C-CDA,这些都是内容标准。 第三是传输标准,定义了计算机系统、文档架构、临床模板、用户界面,数据的连接之间的交换的消息的格式,传输的方式等等。通过传输方式,可以来确定是通过推、还是拉的方式来进行数据的共享交换。在这个层面DICOM 、IHE都是传输的一个标准。 第四是隐私与安全的标准,也就是说我们要确定谁、什么时候、出于什么目的、可以使用哪种个人健康信息的权利,以及我们如何来保护这些健康信息的机密性、可用性、完整性的一些标准。美国有HIPPA,欧洲的GDPR,这些都是关于隐私和安全的。 第五是标识符的标准,标识符的标准是用来唯一标识患者、机构、医护技、设备等等的标准,例如说我们互联互通里面用到的OID。 注:本文根据InterSystems中国技术总监乔鹏演讲整理而成。
问题
j ay · 三月 22, 2023

cache2016版本默认sample空间没有Backup.General

1、默认samples空间没有Backup.General无法进行外部备份 2、%SYS空间有Backup.General Backup.General怎么在其他空间里创建这个类 我没明白问题, 外部备份是在%sys命名空间执行的呀, 为什么要在在sample里执行备份呢? 1、我对这个cache不太理解,第一次做这个,我目前做的外部备份是: 前提:所有数据都是用sample命名空间进行添加的 a. 用sample命名空间,去连接cache,调用Backup.General ExternalFreeze进行冻结 b. 备份mgr目录下面sample数据库文件CACHE.DAT c. 调用ExternalThaw解冻 问题:1.如果我用%SYS命名空间去冻结解冻,备份mgr目录下面sample数据库文件CACHE.DAT,数据是恢复不了的 2.日志journal备份后在重新写入恢复,cache会启动不了 我不知道这样的备份恢复流程对不对,能说一下正确的外部备份流程吗 冻结其实是控制系统的写进程,让它暂时挂起来,在数据库日志上做个标记。这些都不是那一个数据库比如sample的事情,是整个系统的操作。 你的问题1: 如果我用%SYS命名空间去冻结解冻,备份mgr目录下面sample数据库文件CACHE.DAT,数据是恢复不了的。 怎么恢复不了?那一步出错了? 问题2: 你是先恢复数据库, 再恢复日志的是吗?出的什么错误? 这样, 你先看看这两个链接, 如果在有问题, 我找我们的专家和你私聊。第2个帖子有点长,里面连冻结解冻的脚本都包括了, 很详细 ,:) https://cn.community.intersystems.com/post/faq-%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E7%B3%BB%E5%88%97-%E7%B3%BB%E7%BB%9F%E7%AE%A1%E7%90%86%E7%AF%87-%E5%A6%82%E4%BD%95%E8%BF%9B%E8%A1%8C%E6%95%B0%E6%8D%AE%E5%BA%93%E5%A4%87%E4%BB%BD https://community.intersystems.com/post/intersystems-data-platforms-and-performance-%E2%80%93-vm-backups-and-cach%C3%A9-freezethaw-scripts
文章
Hao Ma · 四月 29, 2021

WebGateway系列(4): 配置HTTPS访问IRIS的Web服务

经常被问到有关IRIS如何支持SSL,HTTPS的问题,有必要写个东西介绍一下。 ##HTTPS的原理 简单的说,https实现两个目的:一是访问网站加密,2是确认被访问的网站是真的。 首先,被访问的网站要申请一个证书,这个证书必须是权威机构发放的,比如google, VeriSign等等,所有的浏览器里有预装了这些组织的公钥(Public Key),因此能确认你提供的证书真是这些组织给出的,而这个证书可以证明你的网站的身份。注意证书证明的是提供服务的组织和服务的真实性,和用什么设备没关系,也就是说,IRIS不管证书的事儿。 接下去,被访问的服务器可以生成公钥和私钥,和客户端交换key,生成整个世界只有两者知道的security code,用来两者之间数据的交换。详细的过程和消息交互可以在网上找到很多很好的文章和视频,比如这个: [How does HTTPS work? What's a CA? What's a self-signed Certificate?](https://www.youtube.com/watch?v=T4Df5_cojAs)。 如果是测试环境或者使用者可以控制的内部网络,self-signed证书非常常用。self-signed证书就是不去花钱找人认证,而是告诉客户端,我这个证书是自己认证的,你知道我这台机器试内网的一个机器,不用权威机构证明我服务器的身份,咱们交换一下钥匙把通信加密了吧。操作系统,各种Web服务器都提供这样的假证书,可以用于测试。浏览器访问这样的网站时会提醒用户这个网站不安全,客户需要确认继续访问。 ## IRIS的https访问 如果要访问的是IRIS上的http服务或者页面,需要做的是在连接IRIS的Web服务器配置SSL/TLS。有关IRIS和IRIS WebGateway的介绍,请查看[这个系列前面的文章](https://cn.community.intersystems.com/post/webgateway%E7%B3%BB%E5%88%971-web-gateway%E4%BB%8B%E7%BB%8D)。 *不需要在IRIS或者IRIS Gateway做任何配置。在IRIS文档里有各种有关SSL/TLS的内容,除非你要开发一个TCP层的使用SSL/TLS的应用或者IRIS作为客户端访问其他HTTPS的服务,你根本不用阅读。* 下面简单介绍配置Apache Web服务器简单实现IRIS管理页面的HTTPS访问的步骤。 **1.apache Web服务器安装SSL.** 如果你的Apache没有安装过SSL组件,运行下面命令安装 ``` yum -y install mod_ssl ``` 命令执行结束安装完成后,在/etc/httpd/modules目录会添加了mod_ssl.so,并且在/etc/httpd/conf.d 目录下会出现一个ssl.conf文件。 如果是Windows, 您需要下载使用Windows的Apache服务器,比如从这个页面:[Apach2.4.46](https://www.apachehaus.com/cgi-bin/download.plx)。 按照说明,您需要将软件解压缩到一个目录,比如c:/Apache24,然后执行 "httpd -k install"安装。 并且, 你要确保httpd.conf文件中下面两行没有被注释 LoadModule ssl_module modules/mod_ssl.so Include conf/extra/httpd-ssl.conf 访问https://WebServerIP,你会被浏览器提醒这不是个可信任的网站,是不是还要继续访问,确认后会看到Apache的测试页,访问是成功的。 **2.到IRIS的WebGateway的连接。** 我一般放在一个单独的配置文件里,在linux下是在./conf.d/isc.conf, 在Windows系统是在./extra/httpd-isc.conf。这个配置文件是要被include在httpd.conf里面。配置https并不需要修改这个配置文件。下面是在Windows下的httpd-isc.conf的配置示意。 LoadModule csp_module_sa C:/InterSystems/WebGateway/CSPa24.dll SetHandler csp-handler-sa SetHandler csp-handler-sa CSPFileTypes * Alias /csp/ c:/InterSystems/WebGateway/csp/ AllowOverride None Options MultiViews FollowSymLinks ExecCGI Require all granted Require all denied 这时您应该可以测试到IRIS管理页面的HTTPS访问了。 **3. 获得证书并添加到Web服务器。** 这步是可选的。面向公众服务的Web服务通常会购物证书, 而内部服务个个客户的网络中会有相关的CA的处理方式,相应的如何修改Apache服务器的配置请自行查看文档。 五一节快乐