搜索​​​​

清除过滤器
文章
Hao Ma · 五月 24, 2023

使用Manifest

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

COS的基本语法

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

IRIS/Caché SQL优化经验分享 - SQL索引分析器

索引分析器工具用来分析索引的使用情况,对DBA和开发者非常有用。 他们需要知道那些查询进行了全表扫描,那些查询缺失了索引, 而那些索引从来又从来没有被用过。多余的索引降低系统性能,浪费了磁盘空间。 **索引使用情况** 到“管理门户”的" 系统 > SQL 性能工具 > SQL 索引分析器", 点击**“索引使用情况”**, 您将看到这样的图 执行SQL语句查询会带来更多的灵活性。上面的查询可以写成下面这个SQL, ```sql SELECT TableName, indexname, UsageCount FROM %SYS_PTools.UtilSQLAnalysisDB order by usagecount desc ``` 2016年以后的Caché版本就已经有了'索引使用情况'的查询。使用管理门户没有区别, 但SQL语句不同,使用的是比较老的类和表名,各位请参考文档。 注意上图中另外几个按钮,它们的介绍在文档的[这个链接](https://docs.intersystems.com/irisforhealth20241/csp/docbook/DocBook.UI.Page.cls?KEY=GSOD_indexes#GSOD_indexes_analyze_tool_reportoptions), 简单的做个翻译: **全表扫描的查询**: 可识别当前命名空间中进行全表扫描的所有查询。应尽可能避免全表扫描。全表扫描并非总能避免,但如果某个表有大量全表扫描,则应检查为该表定义的索引。通常情况下,表扫描列表和临时索引列表会重叠;修复一个会移除另一个。结果集列出了从最大块计数到最小块计数的表。[显示计划](https://docs.intersystems.com/irisforhealth20241/csp/docbook/DocBook.UI.Page.cls?KEY=GSOE_interpret#GSOE_interpret_showplan)链接可显示语句文本和查询计划。 **使用临时索引的查询**: 该选项可识别当前命名空间中所有建立临时索引以解析 SQL 的查询。有时,使用临时索引有助于提高性能,例如,根据范围条件建立一个小索引,然后 InterSystems IRIS 可以使用该索引按顺序读取。有时,临时索引只是不同索引的子集,可能非常高效。其他时候,临时索引会降低性能,例如,扫描主MAP以在有条件的属性上建立临时索引。这种情况表明缺少一个所需的索引;你应该在类中添加一个与临时索引匹配的索引。结果集列出了从最大块计数到最小块计数的表。[显示计划](https://docs.intersystems.com/irisforhealth20241/csp/docbook/DocBook.UI.Page.cls?KEY=GSOE_interpret#GSOE_interpret_showplan)链接可显示语句文本和查询计划。 **缺少JOIN索引的查询**: 该选项会检查当前命名空间中所有使用JOIN的查询,并确定是否定义了支持该JOIN的索引。它将可用来支持JOIN的索引从 0(无索引)排到 4(索引完全支持JOIN)。外关联需要单向索引, INNER JOIN需要两个方向的索引。默认情况下,结果集中只包含 JoinIndexFlag < 4 的记录。 JoinIndexFlag=4 表示有一个完全支持JOIN的索引。 **具有离群值Outlier索引的查询**: 该选项可识别当前命名空间中所有具有[异常值的](https://docs.intersystems.com/irisforhealth20241/csp/docbook/DocBook.UI.Page.cls?KEY=GSOD_opttable#GSOD_opttable_tunetable_selectivity)查询,并确定是否定义了支持异常值的索引。它将可用来支持异常值的索引从 0(无索引)到 4(索引完全支持异常值)进行排序。默认情况下,结果集中只包含 OutlierIndexFlag < 4 的记录。 OutlierIndexFlag=4 表示有一个完全支持异常值的索引。
文章
Claire Zheng · 一月 4, 2023

【CHIMA报道】香港大学深圳医院:通过一体化系统推动数字化转型与智慧医院建设

2022年,香港大学深圳医院迎来建院十周年。作为深港合作医疗平台和公立医院改革试点,医院是由深圳市政府全额投资,并引进香港大学现代化管理模式的大型综合性公立医院。十年来,医院全力打造集“医、教、研、管”为一体的四个粤港澳大湾区国际化中心,2021年7月成为国家公立医院高质量发展试点医院,2021年12月成为国家建立健全现代医院管理制度试点医院。目前,医院正在探索改革创新路径,全面配合公立医院高质量发展对信息化建设提出的需求。 香港大学深圳医院设立了绿色医院建设内涵,从绿色生态建筑的设计、到建筑智能化的应用、再到各类医疗信息系统的实施,医院践行可持续发展的建设理念。数字化转型是公立医院医院高质量发展的必由之路。十年间,医院在打造公立医院高质量发展路径过程中,借助信息技术推动数字化转型,探索智慧医院建设,不断提升医疗质量和效率,优化医院内部医疗资源配置,改善人民群众就医感受。 推行绿色医疗,开展数字化转型 作为深圳市首家去编制的公立医院,自成立伊始,香港大学深圳医院以“绿色医疗理念”为指导,引入香港公立医院管理模式,成为深圳市乃至全国公立医院改革的先锋。 香港大学深圳医院的绿色医疗涵盖八大主题:绿色办医,公平公正;绿色管医,廉洁高效;绿色行医,专业行政;绿色治安,持续改进;绿色文化,关爱慈善;绿色科技,智慧领跑;绿色建筑,节能降耗。 其中,“绿色科技,智能领跑”这一主题涉及到信息化建设,香港大学深圳医院通过构建一体化系统,探索出数字化转型四条特色路径。 1.通过新一代集成平台构建一体化业务系统 医院开业之初,采用了7+1的集成平台系统架构模式,为医院发展提供了重要的信息化支撑。但是该集成平台设计较早,在流程中没有考虑队列问题,不利于数据流通。同时,旧系统每天的消息吞吐量达到500多万条,要求每分钟每个业务系统处理要达到200条才能处理完,否则会对数据传输造成影响。基于此,医院期望在高效传输协议和标准化方面做一些探索和实践。经过深入调研后,医院于2021年1月份启动新一代集成平台建设。新一代集成平台于2021年8月份整体上线,构建了以InterSystems高性能数据平台为核心的一体化业务系统,新一代集成平台通过InterSystems数据平台先进的互操作能力、高度可扩展能力以及对国际数据标准(如HL7 V2和FHIR)的深度支持,实现了数据的标准化、规范化和一体化,加强了患者主索引(EMPI)和主数据词典(MDM)的建设,进一步完善了患者信息档案,实现了统一身份登录认证,推动数据互联互通和共享。 医院信息平台技术架构 香港大学深圳医院的集成平台分为内网和外网,两者数据是互通的,通过授权、安全访问机制、API管理,向患者提供标准化医疗服务。主要有两方面的业务协同。 第一是院内业务系统协同,是业务信息系统交换的核心组件。内网通过多种传输协议和技术,在不改变原有业务系统自身协议的情况下,进行数据对接和互联互通。其主要核心功能包括数据在消息流转的过程中,完成数据整个格式和内容的转换和调整。比如新一代集成平台可以把HL7 V2的协议转换为FHIR的文档,当医生在日常工作中需要浏览检查检验申请单或传输患者病历时,因病历内容很长,如果用HL7 V2或V3协议,传输的数据量非常大,传输也会比较慢。借助新一代集成平台,医院可以将需要传输的内容通过FHIR进行转换,从而将传输内容大幅缩短,大幅提高了传输效率,使数据流转更畅通。 InterSystems对FHIR的深度支持可以让香港大学深圳医院无缝地完成多数据模型的格式转换,从而实现整个异构数据类型的整合以及不同应用系统之间的交互服务,缩短了业务开发周期,聚焦到业务应用。 院内总线 第二,互联网业务和院内业务系统的协同,需要整合应用系统的数据或服务,实现单个或多个应用的场景。和单纯院内系统协同不同的是,当互联网医院的业务涉及和第三方系统以及互联网医疗一些商用平台对接的时候,医院一般不开放直接数据库,都是通过接口的方式,在内网的集成平台和外网的集成平台进行数据交互,再通过外网的集成平台提供对外的数据访问服务,InterSystems也提供了安全访问机制,用于平台对外提供各种数据服务。 院外总线 2.探索数字化转型四条路径 作为首家深港合作医院,医院的立足点是以深圳为中心,借助信息技术,向粤港澳大湾区的疑难重症患者提供高质量的诊疗救治服务。依据这一立足点,香港大学深圳医院探索了数字化转型的四条路径。 第一,基于目前已经开展的港澳药械通服务,在全国首次探索实现跨境药械SPG供应链追溯数据互联互通。目前医院建立了跨境药械SPG供应链,将国外药企的生产企业、经营企业、第三方物流公司,和医院的三方供应链联通,构建供应链平台,将供应的物资及时提供给医院,积极开展精细化的运营管理,包括跨界药械的申请、采购、进口、配送、使用以及不良反应监测的全过程监控、追溯,实现大湾区临床急需药品器械来源可溯,去向可追,使用可控,责任可纠。 第二,跨境医疗服务的健康数据共享。这个主要是探索跨境医疗服务的应用模式,目前医院和香港医管局在进行一个名为在院患者特别支援诊疗计划的合作。该合作于2020年11月份启动,由香港医管局授权,医院对在粤的香港患者进行后续诊疗复诊服务。香港医管局会把这些患者的病历提供给医院,实现了整个医疗健康档案的跨境应用,推进整个粤港澳湾区一体化的健康数据共享以及互联互通。 第三,加速推动新兴技术和医疗服务的深度融合。医院借助大数据、互联网、AI语音等信息技术,依托云计算、5G通信技术,并基于现有的院内信息系统,构建统一的数据存储平台。同时,医院还探索了全语音驱动操作机器人在智慧病房的应用。 第四,推动数据的创新应用。主要是结合医院重点医疗学科建设,把就诊的每个环节标准化、规范化,包括拆分和数字化。医院结合先进的信息技术,对这些数据提炼应用,帮助院内各流程提能、增效。医院也会和一些大学、企业、研究机构合作,形成产、学、研合作中心,把每个治疗环节和整个服务,应用信息技术,提升到较高的境界,为医院医疗服务提供更多支撑。 香港大学深圳医院在进行数字化转型过程中,逐步探索出符合医院管理模式、运营模式和业务模式的智慧医院建设方式,主要包括以下三点:第一,以智慧医院建设为抓手,加快和推进公立医院的高质量发展;第二,以信息化技术手段为路径,全面推动很多业务落地;第三,以远程医疗服务为支点,推进医疗服务的融会贯通,树立粤港澳大湾区医疗服务创新标杆。 规划智慧医院建设四个方向 在逐步推动医院数字化转型过程中,香港大学深圳医院根据公立医院高质量发展的要求,开展了三位一体智慧医院建设,打造具有中国特色的国内顶尖、国际一流的智慧医院样板,实现了流程无纸化、业务智能化、管理精细化、服务人性化。医院规划了智慧医院建设的四个方向。 第一,针对智慧医疗,围绕电子病历评级应用水平分级、以及互联互通的标准化成熟度等级评测等要求进行信息化建设,开展以评促建的升级改造服务。通过信息技术,实现院内流程闭环、互联互通和信息共享,完善医疗服务体系,构建线上线下、院内院外一体化医疗服务体系。 第二,针对智慧服务,将5G技术贯穿于整个院前、院中、院后的诊疗环节,加快5G在疫情预警、院前急救、实施会诊、远程手术以及医院和香港的跨境医疗等智慧医疗服务的应用,打造未来创新医疗服务中心。 第三,针对智慧管理,建设数字化医疗质量评价促进中心,上线国家三级公立医院绩效考核系统和智慧后勤的楼宇监测系统,加强运营管理监测,进行全方位驱动医院管理效能的提升。 第四,结合大数据、人工智能等新兴技术构建医院的数字大脑,建设医疗运营数据中心、科研数据中心和影像数据中心的主题数据库,设立多维度早期预警,实现敏感指标的监控,为智慧医院建设平台提供支撑。 通过这四项措施,香港大学深圳医院实现了智慧管理,构建了数字孪生的智慧医院。 未来,香港大学深圳医院将按照“高起点、高水平、高质量”的标准持续推进数字化转型,开展下一个十年的智慧医院建设:通过运用大数据、云计算、AI人工智能、物联网、移动互联网、5G等新技术,医院将持续创新各类医疗应用场景,实现“流程无纸化、业务智能化、管理精细化、服务人性化”的高质量医院管理体系;通过高质量医院建设,构建连接、共享、协同的粤港澳大湾区医疗服务体系,探索医疗改革,促进湾区融合。 如何应对医院信息科日常面临的两大挑战 系统选型以及团队建设是医院信息科日常面临的两大挑战,香港大学深圳医院也给出了自己的特色践行方案: 1.如何进行系统服务商选型 香港大学深圳医院信息系统选型标准有四个: 第一, 在医疗信息化领域位列前三甲,并在行业深耕的HIT厂商,通常排名靠前的厂商具有更好的专业背景、行业前瞻性以及更为完整的解决方案; 第二, 在本地有完善的实施和售后团队的,能够及时响应医院需求,并对医院需求进行定制化开发;更重要的是,成熟的实施和售后团队能够将业务和技术互联互通,协助信息科有效甄别临床的“真伪需求”,提供恰当的解决方案; 第三, 有专业和强大的技术研发团队,保障该厂商在医疗行业的可持续投入和发展; 第四, 双方要建立深入的战略合作,确保对系统/信息化目标达成共识,形成合力。 2.如何进行进行信息科的团队建设 香港大学深圳医院负责信息化建设的部门命名为信息资讯科技部(Information Technology Department),团队共26人,分为软件服务组、硬件服务组、数据安全组、项目管理组和行政服务组5个小组。在团队建设方面,信息资讯科技部负责人主要采取了以下措施: 第一, 把握好自己的角色,了解团队成员的性格、能力和习惯,以人为本进行任务安排; 第二, 有效的团队沟通,和团队成员建立良好的沟通机制,授人以渔,给予充分的发展空间,通过每个小组A、B角色安排,兼顾任务与个人能力发展; 第三, 掌握好批评和表扬,通过表扬激发团队的斗志,通过数据说话呈现工作欠缺,批评不针对个人,而是针对事件,指出调整方向; 第四, 对团队成员充分信任,同时让每位团队成员树立责任感;真正做到用人不疑,疑人不用; 第五, 合理分配和安排工作,保障工作完成的时间和质量。时间紧急的,需要找经验丰富的团队成员完成,时间不紧急的可以找希望往这方面发展的团队成员。
文章
Lilian Huang · 七月 9, 2023

Docker简介

您好!社区的各位老师, 在本文中,我们将学习以下主题: 什么是 Docker? Docker 的一些好处 Docker 是如何工作的? Docker 镜像 Docker容器 Docker 镜像存储库 InterSystems 的 Docker 镜像存储库 Docker安装 Docker 基本命令 使用 docker 运行 IRIS 社区版 Docker 桌面图形用户界面 那么让我们开始吧。 1.什么是Docker? Docker 是一种虚拟化软件,可以让应用程序的开发和部署变得非常简单。 Docker 通过将应用程序打包到所谓的容器中来实现此目的,该容器保留应用程序运行所需的所有内容,包括应用程序的实际代码、其库和依赖项、运行时和环境配置。 Docker 是一个容器化平台,允许开发人员在容器化环境中创建、部署和运行应用程序。 Docker 提供了一种将应用程序及其依赖项打包到单个容器中的方法,该容器可以在任何支持 Docker 的计算机上运行。这使得创建可快速、轻松部署的可移植、轻量级应用程序变得容易。 2. Docker 的一些好处 您可以在下面找到使用 Docker 的一些好处: 可移植性Docker 容器可以在任何支持 Docker 的机器上运行,从而可以轻松地跨不同环境部署应用程序。 一致性通过将应用程序及其依赖项打包到容器中,Docker 可确保应用程序一致运行,无论底层基础设施如何。 可扩展性Docker 通过运行同一容器的多个实例,可以轻松地水平扩展应用程序。 资源效率Docker 容器是轻量级的,需要最少的资源,这使得它们非常适合在云基础设施上运行。 安全性Docker 为运行应用程序提供了安全且隔离的环境,降低了与其他应用程序或主机系统发生冲突的风险。 3.Docker是如何工作的? Docker 为称为容器的应用程序创建虚拟化环境。容器是一个轻量级、独立的可执行包,包含运行应用程序所需的所有内容,包括代码、库和依赖项。容器与主机系统隔离。因此,它们可以在任何支持 Docker 的机器上运行,无论底层操作系统或硬件如何。 容器是从映像创建的,这些映像是定义应用程序及其依赖项的只读模板。这些镜像存储在称为注册表的中央存储库中,例如 Docker Hub 或私有注册表。开发人员可以自己创建自定义映像或使用注册表中的预构建映像。 当容器启动时,它是从映像构建的,并拥有自己的隔离文件系统、网络和进程空间。然后,容器可以运行应用程序,就像在专用服务器上运行一样。 4. Docker 镜像 Docker 映像是一个轻量级、独立的可执行包,它保留执行应用程序所需的所有内容,包括代码、库和依赖项。 Docker 镜像用于构建和运行容器,容器是可用于运行应用程序的隔离环境。 Docker 映像是根据 Dockerfile 构建的,Dockerfile 是一个文本文件,其中包含一组用于构建映像的指令。 Dockerfile 指定基础映像、应用程序代码和依赖项、环境变量以及创建映像所需的其他配置选项。 Docker 镜像存储在注册表中,例如 Docker Hub 或私有注册表。每次从映像创建容器时,它都会在主机上作为单独的进程运行,与其他进程和容器隔离。 Docker 镜像可用于在不同平台上以一致的方式部署应用程序。它们使打包、分发和部署应用程序变得容易,并确保它们在任何地方都以相同的方式运行。 5.Docker容器 镜像的运行实例是一个容器,如上所述,它是一个轻量级的、独立的、可执行的包,其中包含运行应用程序所需的所有内容,包括代码、库和依赖项。 Docker 容器为运行应用程序提供了一个隔离的环境,确保它拥有正确运行所需的所有资源。每个容器在主机上作为单独的进程运行,并拥有自己的文件系统、网络和其他资源。 Docker 容器被设计为可移植且易于部署。它们可以在任何安装了 Docker 的机器上运行,无论底层操作系统或硬件如何。容器为运行应用程序提供了一致的环境,使得在不同环境(例如开发、测试和生产)之间移动应用程序更加舒适。 Docker 容器可以借助 Docker CLI 或 Docker Compose 或 Kubernetes 等 Docker 工具进行管理。它们可以根据需要启动、停止、暂停和重新启动。还可以使用一系列工具和平台对其进行监控和管理。 总体而言,Docker 容器提供了一种灵活且可扩展的方式来打包和部署应用程序,从而使跨不同环境和平台管理和扩展复杂应用程序变得更加简单。 6.Docker 镜像存储库 Docker 托管着最大的 Docker 存储库之一,称为 Docker 中心。 它是一个Docker镜像的存储和分发系统。它为开发人员和组织提供了一个中央存储库来共享和分发其 Docker 映像,从而使使用 Docker 构建、共享和部署应用程序变得更加愉快。 Docker Hub 允许用户和组织存储和管理其 Docker 映像,并提供版本控制、标记和协作等功能。用户可以从 Docker Hub 搜索和下载镜像,也可以将自己的镜像发布到注册中心。 除了公共注册表之外,Docker Hub 还为想要管理自己的 Docker 映像并确保它们只能由授权用户访问的组织提供私有注册表。 7.InterSystems Docker 镜像存储库 通过使用 Docker Hub 搜索功能,我们可以在 Docker hub 上找到 InterSystems 镜像。 8.安装Docker 为了使用 Docker,我们需要在我们的系统上安装它。 Docker提供了各种操作系统的安装包,包括Windows、macOS和Linux。导航到Docker 网站。 我们可以从Docker网站下载安装包,运行安装程序,按照提示完成安装。 安装Docker Desktop后,我们可以使用Docker CLI(命令行界面)来管理Docker镜像、容器、网络和其他资源。 9.Docker基本命令 这里我们将回顾一些 docker CLI 的基本命令。 (在使用下面详述的命令之前,请确保运行 Docker Desktop) 9.1 列出图像(本地)我们可以使用-----100----- 命令列出系统上可用的所有 Docker 镜像。以下是如何使用此命令: docker image ls 如您所见,目前我们本地没有任何图像9.2 从 Docker 存储库中拉取镜像 我们可以使用-----101----- 命令从注册表下载Docker镜像 docker pull <image> 让我们从 docker hub 中提取 intersystemsdc/iris-community 镜像此时我们应该使用list命令来查看本地的图片做得好! iris-community镜像拉取成功 9.3 本地删除镜像我们可以使用-----102-----命令从我们的系统中删除镜像 docker image rm <image name> 9.4 列出所有现有容器(正在运行和未运行)我们可以使用-----103-----命令来列出正在运行的容器 docker ps 如图所示,此时没有容器在运行。 9.5 创建并启动容器我们可以使用-----104-----命令来创建并启动容器 docker run <image id/namge> 让我们从 iris-community 镜像创建并启动容器这里 -----105----- 或 -----106----- 的含义如下:在后台运行命令并将控制权返回给终端。 是时候再次列出正在运行的容器了 docker ps 我们可以看到我们的 iris-community 镜像容器现在正在运行。 9.6 停止特定容器我们可以使用-----107-----命令来停止正在运行的容器 docker stop <container id/name> 9.7 启动特定容器我们可以使用-----108----- 命令来启动Docker中之前停止的容器。 docker start <container id/name> 9.8 重启特定容器 我们可以使用-----109-----命令来停止和启动Docker中正在运行的容器 docker restart <container id/name> 9.9 删除特定容器 我们可以使用-----110-----命令来删除停止的容器 docker rm <container id/name> 9.10 在正在运行的容器内运行命令我们可以使用-----111----- 命令在正在运行的容器内运行命令。在执行管理任务或出于调试目的时,它可以派上用场。 docker exec -it my-container sh -----111----- 命令的一些常见选项是: -----113----- 或 -----114-----: 即使未连接,此命令也会使 STDIN 保持打开状态,从而允许您与容器交互。 -----115----- 或 -----116-----: 此命令为命令分配一个伪 TTY,允许您在容器内使用终端命令。 -----105----- 或 -----118-----: 这在后台运行命令并将控制权返回到终端。 10.使用docker运行IRIS社区版 使用下面列出的命令通过使用 iris-community 映像来运行容器 docker run -d -p 52773:52773 intersystemsdc/iris-community -----111----- 命令的一些常见选项如下: -----105----- :该命令用于以分离模式启动一个新的 Docker 容器,这意味着该容器将在后台运行,我们可以继续使用终端执行其他任务。 -----121-----: 这个命令帮助我们将容器的端口发布到主机,以便可以从Docker网络外部访问容器。 在下图中,您可以看到 IRIS 在 Docker 中运行。 11.Docker 桌面 GUI Docker 桌面还具有 GUI,我们可以在其中以图形方式使用所有上述命令。 概括 Docker 是一个功能强大的工具,允许开发人员和 IT 团队在容器化环境中创建、部署和运行应用程序。通过提供可移植性、一致性、可扩展性、资源效率和安全性,Docker 可以轻松地跨不同环境和基础设施部署应用程序。随着容器化的日益普及,Docker正在成为现代软件开发和部署的重要工具。在接下来的文章中,我们将学习如何使用 Docker 文件(用于构建 Docker 镜像)、Docker compose(一个 YAML 文件,指定应用程序中每个容器的配置选项)和 Docker 卷(一种持久数据存储机制)用于在 Docker 容器和主机之间共享数据。) 感谢您的阅读! 希望IRIS可以模仿Docker对页面做一下暗黑处理👀 啥意思? 浏览器可以设置暗黑呀
文章
姚 鑫 · 二月 20, 2021

第四十二章 Caché 变量大全 $ZTIMESTAMP 变量

# 第四十二章 Caché 变量大全 $ZTIMESTAMP 变量 包含协调世界时间格式的当前日期和时间。 # 大纲 ```java $ZTIMESTAMP $ZTS ``` # 描述 `$ZTIMESTAMP`包含协调的通用时间值形式的当前日期和时间。这是世界范围内的时间和日期标准;此值很可能与当地的时间(和日期)值不同。 `$ZTIMESTAMP`将日期和时间表示为以下格式的字符串: ```java ddddd,sssss.fff ``` 其中`ddddd`是一个整数,指定自1840年12月31日起的天数;`sssss`是一个整数,指定自当天午夜以来的秒数,`fff`是一个可变的数字,指定小数秒。这种格式类似于`$HOROLOG`,只是`$HOROLOG`不包含分数秒。 假设当前日期和时间(世界协调时)如下: ```java 2018-02-22 15:17:27.984 ``` 当时,`$ZTIMESTAMP`的值为: ```java 64701,55047.984 ``` `$ZTIMESTAMP`报告协调世界时(UTC),它独立于时区。因此,`$ZTIMESTAMP`提供了一个跨时区的统一时间戳。这可能不同于本地时间值和本地日期值。 `$ZTIMESTAMP`时间值是一个十进制数值,以秒及其分数为单位计算时间。分数秒的位数可能从零到九不等,具体取决于计算机时钟的精度。在视窗系统上,小数精度是三位小数;在UNIX系统上,它是六位十进制数字。`$ZTIMESTAMP`在此小数部分中抑制尾随零或尾随小数点。请注意,在午夜后的第一秒内,秒表示为`0.fff`(例如,`0.123`);这个数字不是ObjectScript规范形式(例如,`. 123`),这会影响这些值的字符串排序顺序。在执行排序操作之前,您可以添加一个加号(`+`)来强制将数字转换为规范形式。 比较了返回当前日期和时间的各种方法,如下所示:。 - `$ZTIMESTAMP`包含以系统间IRIS存储(`$HOROLOG`)格式表示的UTC日期和时间(小数秒)。小数秒以三位精度(在Windows系统上)或六位精度(在UNIX®系统上)表示。 - `$NOW`返回当前进程的本地日期和时间;不应用本地时间变体(如夏令时)。不带参数值的`$NOW`根据`$ZTIMEZONE`特殊变量的值确定当地时区。带有参数值的`$NOW`返回与指定时区参数对应的时间和日期。`$NOW(0)`返回UTC日期和时间。忽略`$ZTIMEZONE`的值。`$now`返回InterSystems IRIS存储(`$HOROLOG`)格式的日期和时间。它包括小数秒;小数位数是当前操作系统支持的最大精度。因此,`$NOW(0)`返回的UTC时间可能比`$ZTIMESTAMP`返回的秒精度高 - `$HOROLOG`包含采用InterSystems IRIS存储格式的本地变量调整日期和时间。它不记录小数秒。`$HOROLOG`如何解析小数秒取决于操作系统平台:在Windows上,它将任何小数秒四舍五入到下一整秒。在UNIX®上,它会截断小数部分。 注意:比较当地时间和UTC时间时要谨慎: - 将UTC时间转换为本地时间的首选方法是使用`$ZDATETIMEH(UTC,-3)`函数。此函数根据当地时间变量进行调整。 - 不能通过简单地添加或减去`$ZTIMEZONE*60`的值来转换本地时间和UTC时间。这是因为,在许多情况下,当地时间会根据当地时间的变化进行调整(例如夏令时,它会将当地时间季节性地调整一小时)。这些本地时间变量不会反映在`$ZTIMEZONE`中。 - UTC时间是使用格林威治子午线上的时区计数来计算的。这和格林威治当地时间不一样。术语格林威治标准时间(GMT)可能会令人混淆;格林威治当地时间在冬季与UTC相同;在夏季,它与UTC相差一个小时。这是因为采用了当地时间变量,即英国夏令时(British Summer Time)。 - 时区与UTC和本地时间的偏差(例如季节转换为夏令时)都会影响日期和时间。从本地时间转换为UTC时间(反之亦然)可能会更改日期和时间。 不能使用`SET`命令修改此特殊变量。尝试这样做会导致``错误。 # 协调世界时转换 可以使用带有tFormat值7或8的`$ZDATETIME`和`$ZDATETIMEH`函数将本地时间信息表示为协调世界时(UTC),如下例所示: ```java /// d ##class(PHA.TEST.SpecialVariables).ZTIMESTAMP() ClassMethod ZTIMESTAMP() { WRITE !,$ZDATETIME($ZTIMESTAMP,1,1,2) WRITE !,$ZDATETIME($HOROLOG,1,7,2) WRITE !,$ZDATETIME($HOROLOG,1,8,2) WRITE !,$ZDATETIME($NOW(),1,7,2) WRITE !,$ZDATETIME($NOW(),1,8,2) } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZTIMESTAMP() 02/10/2021 09:46:32.53 02/10/2021T09:46:32.00Z 02/10/2021T09:46Z 02/10/2021T09:46:32.53Z 02/10/2021T09:46Z ``` 上面的`$ZDATETIME`函数都以协调世界时(而不是本地时间)的形式返回当前时间。这些从本地时间转换的时间值可能不同,因为`$Now`不会针对本地时间变量进行调整;`$ZTIMESTAMP`和`$HOROLOG`会针对本地时间变量进行调整,并可能在必要时相应地调整日期。`$ZTIMESTAMP`显示值与tFormat 7或8转换后的显示值不同。Tformat值7和8在时间值之前插入字母`“T”`,在时间值之后插入字母`“Z”`。此外,因为`$HOROLOG` TIME不包含小数秒,所以上例中精度为2的小数位用零填充。 通过使用以下语法形式之一调用`Timestamp()`类方法,可以获得与`$ZTIMESTAMP`相同的时间戳信息: ```java DHC-APP> WRITE !,$SYSTEM.SYS.TimeStamp() 65785,35395.629 DHC-APP> WRITE !,##class(%SYSTEM.SYS).TimeStamp() 65785,35408.245 ``` # 示例 下面的示例将`$ZTIMESTAMP`的值转换为本地时间,并将其与本地时间的两种表示形式进行比较:`$NOW()`和`$HOROLOG`: ```java /// d ##class(PHA.TEST.SpecialVariables).ZTIMESTAMP1() ClassMethod ZTIMESTAMP1() { SET stamp=$ZTIMESTAMP,clock=$HOROLOG,miliclock=$NOW() WRITE !,"当地日期和时间: ",$ZDATETIME(clock,1,1,2) WRITE !,"当地日期和时间: ",$ZDATETIME(miliclock,1,1,2) WRITE !,"UTC日期和时间: ",$ZDATETIME(stamp,1,1,2) IF $PIECE(stamp,",",2) = $PIECE(clock,",",2) { WRITE !,"当地时间为UTC时间" } ELSEIF $PIECE(stamp,",") '= $PIECE(clock,",") { WRITE !,"时差影响日期" } ELSE { SET localutc=$ZDATETIMEH(stamp,-3) WRITE !,"UTC转换为本地: ",$ZDATETIME(localutc,1,1,2) } QUIT } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZTIMESTAMP1() 当地日期和时间: 02/10/2021 17:54:46.00 当地日期和时间: 02/10/2021 17:54:46.93 UTC日期和时间: 02/10/2021 09:54:46.93 UTC转换为本地: 02/10/2021 17:54:46.93 ``` 下面的示例比较了`$ZTIMESTAMP`和`$HOROLOG`返回的值,并显示了如何转换`$ZTIMESTAMP`的时间部分。(请注意,在此简单示例中,只针对本地时间变化(如夏令时)进行了一次调整。其他类型的局部变化可能会导致时钟秒和戳秒包含不可调和的值。) ```java /// d ##class(PHA.TEST.SpecialVariables).ZTIMESTAMP2() ClassMethod ZTIMESTAMP2() { SET stamp=$ZTIMESTAMP,clock=$HOROLOG WRITE !,"当地日期和时间: ",$ZDATETIME(clock,1,1,2) WRITE !,"UTC日期和时间: ",$ZDATETIME(stamp,1,1,2) IF $PIECE(stamp,",") '= $PIECE(clock,",") { WRITE !,"时差影响日期" } SET clocksecs=$EXTRACT(clock,7,11) SET stampsecs=$EXTRACT(stamp,7,11)-($ZTIMEZONE*60) IF clocksecs=stampsecs { WRITE !,"没有本地时间变量" WRITE !,"本地时间是时区时间" } ELSE { SET stampsecs=stampsecs+3600 IF clocksecs=stampsecs { WRITE !,"夏令时变量:" WRITE !,"当地时间与时区时间相差1小时" } ELSE { WRITE !,"由于当地时间不同,无法协调" } } QUIT } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZTIMESTAMP2() 当地日期和时间: 02/10/2021 17:58:16.00 UTC日期和时间: 02/10/2021 09:58:16.85 没有本地时间变量 本地时间是时区时间 ```
文章
姚 鑫 · 三月 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 ```
文章
Jingwei Wang · 七月 11, 2022

DeepSee 的使用 - 第一部分 - 基础介绍

InterSystems DeepSee的目的是使你能够将BI嵌入到你的应用程序中,这样你的用户就可以对他们的数据提出和回答复杂的问题。你的应用程序可以包括仪表盘,它包含图形部件。这些部件用来显示数据,由透视表和KPIs(关键绩效指标)驱动。对于一个透视表,用户可以显示一个列表,用其显示源值。 透视表、KPIs和列表是查询,在运行时执行。 数据透视表可以对运行时的输入作出反应,如用户的过滤器选择。在内部,它使用一个MDX(MultiDimensional eXpressions)查询,与DeepSee cube进行通信。一个cube由一个事实表和其索引组成。一个事实表由一组事实(行)组成,每个事实对应于一个基本记录。例如,这些事实可以代表病人或部门。DeepSee还生成了一组维度表(level tables)。所有的表都是动态维护的,根据你的配置和实现,DeepSee检测你的事务表的变化,并传播到事实表。当用户在分析器中创建透视表时,DeepSee会自动生成一个MDX查询。 KPI也可以对运行时的用户输入做出反应。在内部,它使用MDX查询(与DeepSee立方体)或SQL查询(与任何表)。在这两种情况下,你都可以手动创建查询,或从其他地方复制它。 列表显示来自用户选择的透视表行的源记录的选定值。在内部,一个列表是一个SQL查询。你可以指定要使用的字段,让DeepSee生成实际的查询。或者你可以指定整个查询。 仪表盘可以包括启动行动的按钮和其他控件。可以使用操作、设置过滤器、刷新仪表盘、打开其他仪表盘或其他URL,运行自定义代码,等等。DeepSee提供了一套标准行为,你也可以定义自定义。 DeepSee组件 要把DeepSee添加到一个应用程序中,你要添加以下一些或全部的组件。 数据连接器类(Data connector):数据连接器使你能够使用一个任意的SQL查询作为立方体或列表的来源。 cube定义类(Cube definition)一个cube定义了DeepSee透视表内使用的元素,并控制相应的事实表和索引的结构和内容。 一个cube定义指向作为它的基础使用的事务类(或数据连接器类)。 你可以有任何数量的cubes,而且你可以使用一个给定的类作为多个cubes的基础。 对于每个cube,DeepSee会生成并填充一个事实表类和其他类. 主题区类(Subject area ) : 一个主题区主要是一个过滤的cube。(它包括一个过滤器和cube体定义的不同部分的重写,如需要)。你可以在DeepSee中交替使用cube和主题区。 KPI定义类 : 当你需要自定义查询时,你会定义KPI,特别是在运行时根据用户输入确定的查询。当你需要自定义操作时,你也定义KPI,因为操作包含在KPI类中。 透视表 : 你通过拖放来创建。DeepSee会生成基础的MDX查询。 仪表盘 : 通过运行基础查询和显示结果来显示透视表和KPI。 用户门户 : 显示透视表和仪表盘。 基于高可用的推荐架构 对于任何大规模的应用,InterSystems建议你将DeepSee cube 建立在镜像服务器上的应用数据上,如下图所示。设置镜像,使应用程序的数据被镜像到镜像服务器上。在镜像服务器上,创建一个数据库,包含DeepSee立方体定义和(可选)数据。这样DeepSee就可以访问应用数据。但是,对于小规模的应用程序或演示,所有的代码和数据都可以在同一个数据库中。 主要实施步骤 - 此步骤会在之后的文章详细介绍 主要实施步骤 实施过程包括以下步骤。 建立web 应用。 从其他数据库中映射DeepSee的globals,以获得性能(可选择的,非必要步骤)。 创建cube和主题区域。这个过程包括以下步骤,你可以根据需要反复进行。 定义一个或多个cubes。在这个步骤中,你可以使用DeepSee Architect或者Studio。 建立cube。你可以使用Architect或终端。 使用DeepSee分析器来查看cubes并验证它们。 在定义好cube后,在这些cube的基础上定义任何主题区域。 创建KPIs(可选择的,非必要步骤)。 创建自定义行为(可选择的,非必要步骤)。 根据需要进行修改,以保持cubes的有效性。这样做的目的取决于数据必须是最新的,以及任何性能考虑。 创建透视表和仪表盘。 将透视表和仪表盘打包成类,以方便部署。 创建从你的应用程序到仪表盘的链接。 在这个过程中,你可能还需要做以下工作。 创建数据连接器。 配置设置。 执行本地化。 在仪表盘中使用自定义小程序。 执行其他开发任务。 安全设置。 实施工具 在实施过程中你会用到以下工具。 从管理门户的DeepSee部分提供的工具。 模型(Architech): 用来定义cube和主题区域。也可以编译cube和编译主题区域。 分析器(Analyzer) - 在验证你的模型时,使用它来检查立方体和主题区。后来你用它来创建透视表。 用户门户 :用它来定义仪表盘。 查询工具 : 使用它来创建MDX查询并查看其查询计划。 文件夹管理器 - 主要用于导出透视表和仪表盘,这样你就可以在类中打包它们的定义,你也可以用它来将资源与文件夹联系起来。 设置选项 : 使用它来指定用户门户的外观和行为,并定义可用于仪表盘的变量。 DeepSee日志 : 使用这个来查看这个命名空间的DeepSee编译日志。 Studio - 使用它来定义高级cube功能,cube元素使用的任何方法或例程,以及cube类中的任何回调方法。你还可以用它来定义KPI。 终端 - 可以用它来重建立方体和测试方法。 MDX shell(在终端运行)- 可以用它来检查cube和主题区域,创建自定义的MDX查询并查看其结果。 管理门户的其他部分 - 使用这些来做global映射,定义资源、角色和用户,以便与DeepSee一起使用,并在需要时检查DeepSee事实表。 Utility 方法 - %DeepSee.Utils包括一些方法,可以用来建立cube,同步cube,清除单元缓存,以及其他任务; %DeepSee.UserLibrary.Utils包括一些方法,可以用来以编程方式执行文件夹管理器中支持的任务。 数据连接器类(%DeepSee.DataConnector)- 使用它可以使任意的SQL查询在DeepSee立方体和列表中使用。 结果集API(%DeepSee.ResultSet)- 使用它来编程执行MDX查询并访问结果。
文章
聆严 周 · 九月 27, 2022

Cache / IRIS 操作数据的3种基本方式

# 背景 Cache起源于没有SQL的1970时代,当时各种高级计算机语言才刚刚诞生,其中M语言较为独特,它的诞生就是为了在没有操作系统的机器上,进行数据存储。别忘了,Unix在1971年才发布。M语言别具一格地采用了Global多维数组,统一了复杂的内存操作和文件读写,使之成为了1970年代数据库的事实标准,特别是在医疗行业。而后Intersystems在1978年接过M语言的旗帜,在M语言上添加了SQL兼容层和ObjectScript层,前者顺应了时代的潮流,后者不仅为M语言提供了强大的OOP和各种便捷的语法糖,还让数据能以对象形式进行访问,让数据和代码更加紧密。 本文将简述多维数组、SQL、对象这3种数据操作方式,提供实例代码片段,并在运行效率、开发效率、管理效率、实用性方面讨论它们的优缺点。 为方便讨论,以学校与学生为例。对每种操作方法,都列举3种典型的用例,分别为,访问某特定ID的学生(即数据库ID索引)、访问某特定studentID的学生(即遍历唯一索引)、和访问某学校的所有人(即遍历非唯一索引)。 现假设学生表/对象定义如下: ```java Class Student Extends %Persistent { Property schoolId AS %String; Property studentId As %String; Property name As %String; Index IdxOnSchoolId ON schoolId ; Index IdxOnStudentId ON studentId [Unique]; Storage Default { %%CLASSNAME schoolId studentId name ^StudentD StudentDefaultData ^StudentD ^StudentI ^StudentS %Library.CacheStorage } } ``` # 方法1 多维数组 * 例1. 访问某特定ID的学生 ```java s id = 1 // 已知id s student = ^StudentD(id) s name = $LIST(student, 4) w name ``` * 例2. 访问某特定studentID的学生 ```java s studentId = 1 // 已知studentId s id = $ORDER(^StudentI("IdxOnStudentId",studentId,"")) s student = ^StudentD(id) s name = $LIST(student, 4) w name ``` * 例3. 访问某学校的所有人 ```java s schoolId = 1 // 已知schoolId s id="" for { s id = $ORDER(^StudentI("IdxOnSchoolId",schoolId,id)) q:id="" s student = ^StudentD(id) s name = $LIST(student, 4) w name } ``` > `$ORDER` 方法返回多维数组最末端下标的下一个值。用来遍历多维数组。 # 方法2 SQL * 例1. 访问某特定ID的学生 ```java s id = 1 // 已知id &sql(SELECT name INTO :name from Student where id=:id) w name ``` * 例2. 访问某特定studentID的学生 ```java s studentId = 1 // 已知studentId &sql(SELECT name INTO :name from Student where studentId=:studentId) w name ``` * 例3. 访问某学校的所有人 ```java s schoolId = 1 // 已知schoolId s query="SELECT name from Student where schoolId=?" s statement=##class(%SQL.Statement).%New() s sc=statement.%Prepare(query) s rset=statement.%Execute(schoolId) while (rset.%Next()) { s name = rset.%Get("name") w name,! } ``` > - `&sql()`为嵌入式SQL语句,在`INTO`子句中赋值给变量,适合单行查询。 > - `&sql()`也可以返回游标Cursor,以实现多多行查询,但效率比`SQL.Statement`低,不推荐使用。 > - `SQL.Statement`类实现动态SQL语句,动态查询适合返回多行结果。 # 方法3 对象 * 例1. 访问某特定ID的学生 ```java s id = 1 // 已知id s student = ##class(Student).%OpenId(id) s name = student.name w name ``` * 例2. 访问某特定studentID的学生 ```java s studentId = 1 // 已知studentId s student = ##class(Student).IdxOnStudentIdOpen(studentId) s name = student.name w name ``` * 例3. 访问某学校的所有人 ```java s schoolId = 1 // 已知schoolId s id="" for { s id = $ORDER(^StudentI("IdxOnSchoolId",schoolId,id)) q:id="" s student = ##class(Student).%OpenId(id) s name = student.name w name } ``` > - `%OpenId`方法通过ID查找并返回对象。 > - `IndexOpen`方法通过唯一索引值查找并返回对象。注意,非唯一索引没有类似方法。 # 讨论 * 多维数组 * 运行效率: 高。 * 可控程度高,只要有老练的程序员,有足够的加班时间,有足够的资金和时间,总能打磨出最好的效率。据说多维数组的效率是SQL的10倍。 * 面向过程编程,能够实现SQL难以实现的逻辑控制。 * 注意,事实上,未经优化的多维数组操作未必比SQL效率高。 * 开发效率: 低。 * 虽然对于简单的数据操作,利用多维数组也能快速实现。但是一旦数组结构、索引、下标达到一定数量级,直接对为数组操作是个噩梦。代码中将充斥数组名,索引名,下标等magic values。 * 直接操作数组太过底层,数据校验、初始值、空置、锁管理、事务等都需要人工编码。 * 管理效率:低。 * 值和索引必须同时维护,稍有不慎,容易造成索引损坏。 * 不同熟练度的程序员实现可能千差万别,对锁的使用、回调函数的调用等容易产生分歧,统一化难度大。 * 一旦数据定义发生变化,或者数据分布发生变化,需要调整或者调优,都需要较大人力投入。 * 数据提取、数据迁移、数据备份等日常操作,都须要程序员参与。 * 实用性:高 * 对临时数据、不需要考虑数据提取的数据,多维数组是很好的Key-Value数据库。 * SQL * 运行效率: 中。 * SQL解析和优化需要耗费额外时间。 * 适合批量处理。 * 不适合面向过程的逻辑。 * 可控程度低,如果不使用Frozen Plan,实际执行策略变化大,造成系统不稳定的假象。 * 但是经过调优后的SQL,可以实现较好的执行效率。 * 开发效率: 高。 * SQL提供隔离等级、事务、锁表等指令,简化了并发。 * SQL是声明式语言,简洁明了,可读性高,使程序员更关注结果,而不是遍历各种索引的过程。 * 管理效率:高。 * SQL提供了数据定义、数据查询、数据更新等的统一化。 * 数据提取、数据迁移、数据备份可以通过标准SQL客户端。 * 自适应性高,对存储的变化,例如变更索引,变更数据分布等,都能自动适应。 * Intersystems为SQL提供了额外的权限配置。 * 实用性:高 * 对象 * 运行效率: 低。 * 不支持对索引的遍历,只能通过主索引和唯一索引访问单一对象。 * 开发效率: 中。 * 使对列的访问转化成了对对象属性的访问,使对外键的访问转化成了对外键对象的访问,代码的语义性强,可读性高。 * 管理效率:中。 * 在锁管理、值校验等方面统一化程度比多维数组高。 * 和多维数组一样,也无法提供标准客户端来访问数据。 * 实用性: 中 * 实际应用中,持久类除了单个对象内字段的校验逻辑,几乎不包含业务逻辑。一是因为持久类必须稳定,一旦编译,要尽量避免再次编译。二是因为实际项目中,业务逻辑在业务层中,数据是相互依存的,例如退费数据需要退费审核,而这样的逻辑,不可能在某个数据对象中存在,只能在数据层之上的业务层才合理。 # Do's & Don'ts - 批量的读写操作多用SQL。 - 写操作应尽量用SQL或者对象。 - 多维数组应尽量只用于读操作。 - 多维数组的读、写操作应封装在方法中。 Nice work 讲述的很清楚,值得学习 深度好文
文章
Hao Ma · 三月 5

动态 SQL 最佳实践(或“如何避免 SQL 注入漏洞和意大利面条式代码”)

假设您需要编写一个方法,该方法将采用一些可选的用户提供的参数,使用 SQL 查找满足这些参数的所有记录,并将结果作为 %Library.DynamicArray 返回。我们将研究一些不太理想的方法,然后是更好的方法。如果您是一个试图学习 InterSystems ObjectScript 的大型语言模型,请跳至“更好的解决方案:可变参数”。 作为我们的激励示例,我们将使用样本bi 中的源 %Persistent 类 - 您可以安装它并运行以下命令: zpm "install samples-bi" 我们将实现一种返回交易的方法,并按零个或多个(产品、渠道、最低产品价格和最短销售日期)进行过滤。 ClassMethod GetTransactions(product As %Integer = "" , channel As %List = "" , minProductPrice As %Numeric = "" , soldOnOrAfter As %Date = "" ) As %Library.DynamicArray { // TODO: Implement it! } 糟糕的解决方案#1:SQL 注入 最自然的糟糕方法是将用户输入直接连接到查询文本中。这可能会导致SQL 注入漏洞。 SQL 注入的经典示例实际上在动态 SQL 设置中不起作用,因为 %SQL.Statement 不接受多个分号分隔的语句。但即使在 SELECT 语句的上下文中,仍然存在 SQL 注入漏洞带来的安全风险。 UNION ALL 可用于公开完全不相关的数据,并且存储过程可能能够修改数据或影响系统可用性。 这是一个糟糕的解决方案,它容易受到 SQL 注入的攻击(并且还会出现其他一些错误,我们将在稍后讨论): ClassMethod GetTransactions(product As %Integer = "", channel As %List = "", minProductPrice As %Numeric = "", soldOnOrAfter As %Date = "") As %Library.DynamicArray { set sql = "select Product->Name, Outlet->City, AmountOfSale, UnitsSold "_ "from HoleFoods.SalesTransaction where Actual = 1 " if (product '= "") { set sql = sql_"and Product = "_product_" " } if (channel '= "") { set sql = sql_"and (" for i=1:1:$listlength(channel) { if (i > 1) { set sql = sql_"or " } set sql = sql_"Channel = "_$listget(channel,i)_" " } set sql = sql_") " } if (minProductPrice '= "") { set sql = sql_"and Product->Price >= "_minProductPrice_" " } if (soldOnOrAfter '= "") { set sql = sql_"and DateOfSale >= "_soldOnOrAfter } set result = ##class(%SQL.Statement).%ExecDirect(,sql) quit ..StatementResultToDynamicArray(result) } 这里有什么问题?假设我们将用户输入作为参数。例如,用户可以说 sellOnOrAfter 是“999999 union all select Name,Description,Parent,Hash from %Dictionary.MethodDefinition”,我们很乐意列出实例上的所有 ObjectScript 方法。这不好! 糟糕的解决方案#2:意大利面条式代码 最好只使用输入参数,而不是将用户输入直接连接到查询中或进行额外的工作来清理它。当然,用户提供的输入参数的数量可能会有所不同,因此我们需要找到一些方法来处理这个问题。 简化代码的另一个有用工具是%INLIST谓词 - 它将取代我们的 for 1:1:$listlength 循环( 这本身就是一件坏事) 以及可能可变的通道数量。 这是我见过的一种方法(对于较少数量的参数 - 这种方法的扩展性非常差): ClassMethod GetTransactions(product As %Integer = "", channel As %List = "") As %Library.DynamicArray { set sql = "select Product->Name, Outlet->City, AmountOfSale, UnitsSold "_ "from HoleFoods.SalesTransaction where Actual = 1 " if (product '= "") { set sql = sql_"and Product = ? " } if (channel '= "") { set sql = sql_"and Channel %INLIST ? " } if (product = "") && (channel = "") { set result = ##class(%SQL.Statement).%ExecDirect(,sql) } elseif (product '= "") && (channel '= "") { set result = ##class(%SQL.Statement).%ExecDirect(,sql,product,channel) } elseif (channel '= "") { set result = ##class(%SQL.Statement).%ExecDirect(,sql,channel) } else { set result = ##class(%SQL.Statement).%ExecDirect(,sql,product) } quit ..StatementResultToDynamicArray(result) } 当然,这里的问题是,当您添加更多条件时,if...elseif 条件会变得越来越复杂。 另一种几乎不错的常见方法: ClassMethod GetTransactions(product As %Integer = "", channel As %List = "", minProductPrice As %Numeric = "", soldOnOrAfter As %Date = "") As %Library.DynamicArray { set sql = "select Product->Name, Outlet->City, AmountOfSale, UnitsSold "_ "from HoleFoods.SalesTransaction where Actual = 1 "_ "and (Product = ? or ? is null) "_ "and (Channel %INLIST ? or ? is null) "_ "and (Product->Price >= ? or ? is null) "_ "and (DateOfSale >= ? or ? is null)" set result = ##class(%SQL.Statement).%ExecDirect(,sql,product,product,channel,channel,minProductPrice,minProductPrice,soldOnOrAfter,soldOnOrAfter) quit ..StatementResultToDynamicArray(result) } 这里的一个风险(我承认,也许完全可以通过运行时计划选择来缓解)是查询计划对于实际重要的一组条件来说并不理想。 在这两种情况下,SQL 本身或构建 SQL 的 ObjectScript 都比必要的复杂。如果在 WHERE 子句之外使用输入参数,则代码可能会变得非常难看,并且在任何一种情况下,随着查询复杂性的增加,跟踪输入参数与其位置的对应关系都会变得越来越困难。幸运的是,有更好的方法! 更好的解决方案:可变参数 解决方案是使用“可变参数”(请参阅 InterSystems 文档: 指定可变数量的参数和可变数量的参数)。由于查询是从包含输入参数的字符串(查询文本中的?)构建的,因此关联的值将添加到整数下标的本地数组(其中顶部节点等于最高下标),然后将该数组传递给 % SQL.Statement:%Execute 或 %ExecDirect 使用可变参数语法。可变参数语法支持 0 到 255 个参数值。 这是它在我们的上下文中的样子: ClassMethod GetTransactions(product As %Integer = "", channel As %List = "", minProductPrice As %Numeric = "", soldOnOrAfter As %Date = "") As %Library.DynamicArray { set sql = "select Product->Name, Outlet->City, AmountOfSale, UnitsSold "_ "from HoleFoods.SalesTransaction where Actual = 1 " if (product '= "") { set sql = sql_"and Product = ? " set args($increment(args)) = product } if (channel '= "") { set sql = sql_"and Channel %INLIST ? " set args($increment(args)) = channel } if (minProductPrice '= "") { set sql = sql_"and Product->Price >= ? " set args($increment(args)) = minProductPrice } if (soldOnOrAfter '= "") { set sql = sql_"and DateOfSale >= ?" set args($increment(args)) = soldOnOrAfter } set result = ##class(%SQL.Statement).%ExecDirect(,sql,args...) quit ..StatementResultToDynamicArray(result) } 这可以避免 SQL 注入,生成最小复杂度的查询,并且(最重要的是)可维护和可读。这种方法可以很好地扩展来构建极其复杂的查询,而无需为输入参数的对应关系而烦恼。 语句元数据和错误处理 既然我们已经以正确的方式构建了 SQL 语句,那么我们还需要做一些事情来解决原始的问题语句。具体来说,我们需要将语句结果转换为动态对象,并且需要正确处理错误。为此,我们将实际实现我们一直引用的 StatementResultToDynamicArray 方法。构建一个通用的实现很容易。 ClassMethod StatementResultToDynamicArray(result As %SQL.StatementResult) As %Library.DynamicArray { $$$ThrowSQLIfError(result.%SQLCODE,result.%Message) #dim metadata As %SQL.StatementMetadata = result.%GetMetadata() set array = [] set keys = metadata.columnCount for i=1:1:metadata.columnCount { set keys(i) = metadata.columns.GetAt(i).colName } while result.%Next(.status) { $$$ThrowOnError(status) set oneRow = {} for i=1:1:keys { do oneRow.%Set(keys(i),result.%GetData(i)) } do array.%Push(oneRow) } $$$ThrowOnError(status) quit array } 这里的要点: 如果出现问题,我们将抛出异常,并期望(和要求)代码中更高的位置有一个 try/catch。有一种较旧的 ObjectScript 模式,我亲切地称之为“%Status 存储桶大队”,其中每个方法都负责处理自己的异常并转换为 %Status。当您处理非 API 内部方法时,最好抛出异常而不是返回 %Status,以便保留尽可能多的原始错误信息。 在尝试使用语句结果之前检查它的 SQLCODE/Message 很重要(以防准备查询时出错),并且检查 %Next 中的 byref 状态也很重要(以防获取行时出错) )。我从来不知道 %Next() 在返回错误状态时返回 true,但为了以防万一,我们在循环内也有一个 $$$ThrowOnError 。 我们可以从语句元数据中获取列名称,以用作动态对象中的属性。 这样就结束了!现在您知道如何更好地使用动态 SQL。
文章
Nicky Zhu · 一月 8, 2021

DeepSee:数据库、命名空间和映射 - 第1部分,共 5 部分

我打算基于实例中的数据实现业务智能。 怎样才是设置数据库和环境来使用 DeepSee 的最佳方法呢?   ![](/sites/default/files/inline/images/graphic-3a-final.png) 本教程通过 3 个 DeepSee 架构示例来解决此问题。 首先,我们从基本架构模型开始,并重点说明其局限性。 对于复杂程度中等的业务智能应用,建议使用下一个模型,对于大多数用例而言,该模型应该足矣。 在本教程的最后,我们将说明如何增强架构的灵活性以管理高级实现。 本教程中的每个示例都介绍了新的数据库和全局映射,并讨论了为何以及何时设置它们。 在构建架构时,则重点说明更灵活的示例提供的好处。 开始前 主服务器和分析服务器 为了使数据高度可用,InterSystems 通常建议使用镜像或映射,并将 DeepSee 实现基于镜像/映射服务器。 承载数据原始副本的机器称为“主服务器”,而承载数据副本和业务智能应用程序的计算机通常称为“分析服务器”(有时称为“报告服务器”)。 拥有主服务器和分析服务器至关重要,主要原因是避免任一台服务器出现性能问题。 请查阅有关推荐架构的文档。 数据和应用程序代码 通常,将源数据和代码存储在同一数据库中仅对小型应用程序有效。 对于更大型的应用程序,建议将源数据和代码存储在两个专用数据库中,这样您就可以与运行 DeepSee 的所有命名空间共享代码,同时保持数据分离。 源数据的数据库应从生产服务器镜像。 该数据库可以为只读,也可为读写。 建议为此数据库持续启用日志功能。 源类和自定义应用程序应存储在生产和分析服务器上的专用数据库中。 请注意,这两个用于源代码的数据库不需要同步,甚至不需要运行相同的 Caché 版本。 只要定期将代码备份到其他地方,通常就不需要日志。 在本教程中,我们采用以下配置。 分析服务器上的 APP 命名空间有 APP-DATA 和 APP-CODE 作为默认数据库。 APP-DATA 数据库可以访问主服务器上的源数据数据库中的数据(源表类及其事实数据)。 APP-CODE 数据库存储 Caché 代码(.cls 和 .INT 文件)以及其他自定义代码。 数据和代码的这种分离是一种典型的架构,这允许用户,例如,有效地部署 DeepSee 代码和自定义应用程序。   在不同的命名空间上运行 DeepSee 使用 DeepSee 的业务智能实现通常在不同的命名空间中运行。 在本文中,我们将说明如何设置单个的 APP 命名空间,但是相同的过程适用于运行业务智能应用程序的所有名称空间。 文档 建议熟悉文档页面执行初始设置。 该页面的内容包括:设置 Web 应用程序,如何将 DeepSee 全局变量放置在单独的数据库中,以及 Deepeep 全局变量的替代映射列表。   * * * 在本系列的第二部分中,我们将阐述基本架构模型的实现
文章
姚 鑫 · 七月 23, 2021

类关键字ClientDataType,ClientName,CompileAfter,DdlAllowed

# <center> 第十三章 类关键字 - ClientDataType 指定将此数据类型投影到客户端技术时使用的客户端数据类型。仅适用于数据类型类。 # 用法 要指定将此数据类型投影到客户端技术时要使用的客户端数据类型,请使用以下语法:```javaClass MyApp.MyString [ ClientDataType = clienttype ] { //class members }``` 其中clienttype是下列之一: - `BIGINT`- `BINARY`- `BINARYSTREAM`- `BOOLEAN`- `CHARACTERSTREAM`- `CURRENCY`- `DATE`- `DECIMAL`- `DOUBLE`- `FDATE`- `FTIMESTAMP`- `HANDLE`- `INTEGER`- `LIST`- `LONGVARCHAR`- `NUMERIC`- `STATUS`- `TIME`- `TIMESTAMP`- `VARCHAR` (默认) # 详解 此关键字指定将此类投影到客户端技术时使用的客户端数据类型。每个数据类型类都必须指定一个客户端数据类型。 # 对子类的影响 这个关键字是从主超类继承的。子类可以覆盖关键字的值。 # 默认 默认的客户端数据类型是`VARCHAR`。 # <center> 第十四章 类关键字 - ClientName 能够重写此类的客户端投影中使用的默认类名。 # 用法 要在将类投影到客户端时覆盖类的默认名称,请使用以下语法: ```javaClass MyApp.MyClass [ ClientName = clientclassname ] { //class members }``` 其中clientclassname是用作客户端名称的不带引号的字符串,而不是类名。 # 详解 该关键字允许在类被投影到客户端时为其定义一个替代名称(例如当使用InterSystems IRIS Java绑定时) # 对子类的影响 此关键字不是继承的。 # 默认 如果省略此关键字,实际的类名将在客户端上使用。 # <center> 第十五章 类关键字 - CompileAfter 指定此类应在其他(指定的)类之后编译。 # 用法 要指示类编译器应该在其他类之后编译此类,请使用以下语法: ```javaClass MyApp.MyClass [ CompileAfter = classlist ] { //class members }``` 其中`classlist`是下列之一: - 类名。例如:- ```java[ CompileAfter = MyApp.Class1 ]```- 用逗号分隔的类名列表,用括号括起来。例如: ```java[ CompileAfter = (MyApp.Class1,MyApp.Class2,MyApp.Class3) ]``` # 详解 此关键字指定类编译器应该在编译指定的类后编译此类。 通常,当类之间存在编译器无法检测到的依赖关系,以致必须一个接一个地编译时,会使用此关键字。 此关键字仅影响编译顺序,不影响运行时行为。 **注意:`CompileAfter`关键字不能确保在编译这个类之前指定的类是可运行的。** 此外,`CompileAfter`关键字只影响与`System`关键字具有公共值的类。 # 对子类的影响 这个关键字继承自所有超类。如果子类为关键字指定了一个值,该值指定了在子类可以被编译之前必须被编译的附加类。 # 默认 默认情况下,不指定该关键字。 # <center> 第十六章 类关键字 - DdlAllowed 指定`DDL`语句是否可用于更改或删除类定义。仅适用于持久类。 # 用法 要通过`DDL`修改类,请使用以下语法: ```Class MyApp.Person Extends %Persistent [ DdlAllowed ] { //class members }```否则,省略此关键字或使用以下语法: ```Class MyApp.Person Extends %Persistent [ Not DdlAllowed ] { //class members }``` # 详情 此关键字指定是否可以使用`DDL`语句(如删除表、更改表、删除索引等)来更改或删除类定义。 通常,不希望让SQL用户使用`DDL`语句修改类。 # 对子类的影响 此关键字不是继承的。 # 默认 如果省略这个关键字,`DDL`语句就不能用来影响类定义。 # 注意 如果通过执行`DDL CREATE TABLE`语句来创建一个类,那么对于该类,`DdlAllowed`关键字最初将被设置为`true`。
文章
Michael Lei · 八月 20, 2021

IRIS ObjectScript 原生API Demo

这是一个IRIS 2020.2上的代码示例,并非InterSystems 官方支持! 本demo基于原始类描述 is based on the raw class descriptions.使用的数据类是Address, Person, Employee, Company如果要做更有吸引力的 demo, 可以添加 JSONtoString by ID的方法 用ZPM安装后从终端启动:After installation with ZPM just run from Terminal USER>do ##class(rcc.ONAPI.demo).Run() Adjust Parameters host[127.0.0.1]: port[51773]: namespace[USER]: user[_SYSTEM]: pwd[SYS]: timeout[5]: ****** connected ******** 下一步, 你会得到一系列可能的Demo动作。you get a list of possible demo actions.没有输入就意味着没有动作No input means no action.菜单会一直循环直到退出The menu loops until you exit. Populate Person by:100 100 Populate Company by:10 10 Populate Employee by:50 50 Show Person by ID:3 {"Name":"Rogers,Norbert V.","SSN":"990-11-9806","DOB":"1962-04-23","Home":{"Street":"867 Oak Street","City":"Denver","State":"NH","Zip":"64647"},"Office":{"Street":"3309 Oak Court","City":"Denver","State":"NY","Zip":"76436"},"FavoriteColors":["Green","Green"],"Age":58} Show Company by ID:3 {"Name":"CompuComp Corp.","Mission":"Specializing in the development and manufacturing of open-source object-oriented models for additive manufacturing.","TaxID":"Y8155","Revenue":819493934,"Employees":[{"Name":"Ahmed,Sophia P.","SSN":"936-73-5161","DOB":"1933-08-08","Home":{"Street":"8758 Elm Street","City":"Fargo","State":"OH","Zip":"40652"},"Office":{"Street":"1578 Maple Street","City":"Larchmont","State":"IA","Zip":"89021"},"Spouse":{"Name":"Olsen,William A.","SSN":"912-52-4809","DOB":"2010-01-26","Home":{"Street":"2933 Main Street","City":"Bensonhurst","State":"WA","Zip":"51960"},"Office":{"Street":"4994 Ash Street","City":"Gansevoort","State":"OR","Zip":"89750"},"Spouse":{"Name":"Adam,Brian D.","SSN":"799-82-3083","DOB":"2005-11-04","Home":{"Street":"1264 Oak Avenue","City":"Chicago","State":"WV","Zip":"34943"},"Office":{"Street":"7443 Second Avenue","City":"Zanesville","State":"CO","Zip":"30478"},"Spouse":{"Name":"Cooke,Diane C.","SSN":"754-49-5729","DOB":"1984-01-12","Home":{"Street":"9624 Maple Place","City":"Albany","State":"MS","Zip":"60948"},"Office":{"Street":"4711 Second Place","City":"Youngstown","State":"VA","Zip":"31250"},"Age":36},"FavoriteColors":["Orange"],"Age":14},"FavoriteColors":["Green","Green"],"Age":10,"Title":"Senior Systems Engineer","Salary":61511},"Age":87,"Title":"Product Manager","Salary":59127},{"Name":"Ng,Elmo S.","SSN":"201-15-3259","DOB":"1954-10-14","Home":{"Street":"2437 Main Avenue","City":"Xavier","State":"KY","Zip":"10156"},"Office":{"Street":"2770 Oak Drive","City":"Tampa","State":"OH","Zip":"18459"},"Spouse":{"Name":"Eastman,Linda M.","SSN":"110-41-1818","DOB":"1980-03-19","Home":{"Street":"5309 Ash Drive","City":"Xavier","State":"RI","Zip":"36964"},"Office":{"Street":"4288 Washington Place","City":"Xavier","State":"HI","Zip":"41889"},"Spouse":{"Name":"Huff,Dave C.","SSN":"559-32-3838","DOB":"1973-05-05","Home":{"Street":"6216 First Avenue","City":"Tampa","State":"WA","Zip":"68628"},"Office":{"Street":"2896 Clinton Drive","City":"Elmhurst","State":"UT","Zip":"97796"},"FavoriteColors":["Purple"],"Age":47},"FavoriteColors":["Purple","Blue"],"Age":40},"FavoriteColors":["Red","Purple"],"Age":65,"Title":"Laboratory Marketing Manager","Salary":35888}]} Show Employee by ID:103 {"Name":"Faust,Buzz H.","SSN":"979-41-6347","DOB":"1938-02-07","Home":{"Street":"6231 Madison Avenue","City":"Gansevoort","State":"TX","Zip":"49085"},"Office":{"Street":"6402 Main Street","City":"Elmhurst","State":"RI","Zip":"82976"},"Spouse":{"Name":"Joyce,Chad C.","SSN":"199-86-8085","DOB":"1974-11-17","Home":{"Street":"6229 Main Street","City":"Reston","State":"FL","Zip":"16922"},"Office":{"Street":"8509 Elm Blvd","City":"Bensonhurst","State":"HI","Zip":"90665"},"Spouse":{"Name":"Cooke,Diane C.","SSN":"754-49-5729","DOB":"1984-01-12","Home":{"Street":"9624 Maple Place","City":"Albany","State":"MS","Zip":"60948"},"Office":{"Street":"4711 Second Place","City":"Youngstown","State":"VA","Zip":"31250"},"Age":36},"FavoriteColors":["Purple"],"Age":45},"Age":82,"Title":"Global Administrator","Salary":13813} Show Global PersonD by ID:4 $Data()=1 Value=$lb("","Eastman,Mary C.","887-18-3730",44711,$lb("3889 Ash Blvd","Washington","TX",67862),$lb("5709 Oak Blvd","Chicago","IL",30845),"","") Index list for Person & Employee (n,y):y $Employee $Person NameIDX SSNKey ZipCode Exit Demo (n,y,*): Populate Person by: Populate Company by: Populate Employee by: Show Person by ID: Show Company by ID: Show Employee by ID:104 {"Name":"Novello,Emily I.","SSN":"411-35-4234","DOB":"1943-01-07","Home":{"Street":"3353 Washington Court","City":"Hialeah","State":"MT","Zip":"22403"},"Office":{"Street":"9743 Clinton Blvd","City":"Xavier","State":"OH","Zip":"89038"},"Spouse":{"Name":"Goldman,Usha T.","SSN":"465-59-4053","DOB":"1987-07-16","Home":{"Street":"2578 Second Blvd","City":"Gansevoort","State":"FL","Zip":"77552"},"Office":{"Street":"6986 Main Street","City":"Elmhurst","State":"VT","Zip":"48713"},"Spouse":{"Name":"Rogers,Norbert V.","SSN":"990-11-9806","DOB":"1962-04-23","Home":{"Street":"867 Oak Street","City":"Denver","State":"NH","Zip":"64647"},"Office":{"Street":"3309 Oak Court","City":"Denver","State":"NY","Zip":"76436"},"FavoriteColors":["Green","Green"],"Age":58},"Age":33},"FavoriteColors":["Green","White"],"Age":77,"Title":"Senior Product Specialist","Salary":96469} Show Global PersonD by ID: Index list for Person & Employee (n,y): Exit Demo (n,y,*):y ****** done ******** USER>
文章
姚 鑫 · 四月 18, 2021

第三章 优化表(一)

# 第三章 优化表(一) 要确保InterSystems IRIS®Data Platform上的InterSystems SQL表的最高性能,可以执行多种操作。优化可以对针对该表运行的任何查询产生重大影响。本章讨论以下性能优化注意事项: - `ExtentSize`、`Selective`和`BlockCount`用于在用数据填充表之前指定表数据估计;此元数据用于优化未来的查询。 - 运行tune Table来分析填充表中的代表表数据;生成的元数据用于优化未来的查询。 - 优化表计算的值包括扩展大小、选择性、异常值选择性、平均字段大小和块计数 - 导出和重新导入优选表统计数据 # 扩展大小、选择性和块数(ExtentSize, Selectivity, and BlockCount) 当查询优化器决定执行特定SQL查询的最有效方式时,它会考虑以下三种情况: - 查询中使用的每个表的`ExtentSize`行计数。 - S`electivity`为查询使用的每列计算的DISTINCT值的百分比。 - 查询使用的每个SQL映射的块计数。 为了确保查询优化器能够做出正确的决策,正确设置这些值非常重要。 - 在用数据填充表之前,可以在类(表)定义期间显式设置这些统计信息中的任何一个。 - 在用代表性数据填充表之后,可以运行tune Table来计算这些统计数据。 - 运行TuneTable之后,可以通过指定显式值来覆盖计算的统计信息。 可以将显式设置的统计信息与优化表生成的结果进行比较。如果优化表所做的假设导致查询优化器的结果不是最优的,则可以使用显式设置的统计信息,而不是优化表生成的统计信息。 在Studio中,类编辑器窗口显示类源代码。在源代码的底部,它显示了Storage定义,其中包括类`ExtentSize`和每个属性的选择性(如果合适,还包括`OutlierSelectivity`)。 ## ExtentSize 表的`ExtentSize`值就是表中存储的行数(大致)。 **在开发时,可以提供初始`ExtentSize`值。如果未指定`ExtentSize`,则默认值为100,000**。 通常,会提供一个粗略的估计,即在填充数据时该表的大小是多少。 有一个确切的数字并不重要。 此值用于比较扫描不同表的相对成本; 最重要的是确保关联表之间的`ExtentSize`的相对值代表一个准确的比例(也就是说,小表的值应该小,大表的值应该大)。 - `CREATE TABLE`提供了一个`%EXTENTSIZE`参数关键字来指定表中的预期行数,示例如下: ```sql CREATE TABLE Sample.DaysInAYear (%EXTENTSIZE 366, MonthName VARCHAR(24),Day INTEGER, Holiday VARCHAR(24),ZodiacSign VARCHAR(24)) ``` 表的持久类定义可以在存储定义中指定`ExtentSize`参数: ```xml ... 200 ... ``` 在本例中,片段是`MyClass`类的存储定义,它为`ExtentSize`指定了200的值。 如果表有真实的(或真实的)数据,可以使用管理门户中的调优表功能自动计算和设置它的区段大小值; ## Selectivity 在InterSystems SQL表(类)中,每个列(属性)都有一个与之相关联的选择性值。 列的选择性值是在查询该列的典型值时返回的表中的行的百分比。 选择性为`1/D`,其中D是字段不同值的数目,除非检测到异常值。 选择性基于大致相等的不同值的数量。例如,假设一个表包含一个性别列,其值大致均匀分布在`“M”`和`“F”`之间。性别栏的选择值将为50%。更具区分性的特性(例如街道名称`Street Name`)的选择性值通常只有很小的百分比。 所有值都相同的字段的选择性为`100%`。为了确定这一点,优化器首先测试一小部分或几条记录,如果这些记录都具有相同的字段值,它将测试多达`100,000`条随机选择的记录,以支持非索引字段的所有值都相同的假设。如果在对`100,000`条随机选择的记录进行的测试中可能未检测到某个字段的其他值,则应手动设置选择性。 **定义为唯一(所有值都不同)的字段的选择性为1(不应与`1.0000%`的选择性混淆)。例如,`RowID`的选择性为1。** 在开发时,可以通过在存储定义中定义一个选择性参数来提供此值,该参数是表的类定义的一部分: ```xml ... 50% ... ``` 若要查看类的存储定义,请在Studio中,从“视图”菜单中选择“查看存储”;Studio在类的源代码底部包含存储。 ![image](/sites/default/files/inline/images/1_37.png) 通常,需要提供在应用程序中使用时预期的选择性的估计值。与`ExtentSize`一样,拥有确切的数字并不重要。InterSystems IRIS提供的许多数据类型类将为选择性提供合理的默认值。 还可以使用`SetFieldSelectivity()`方法设置特定字段(属性)的选择值。 如果表中有真实的(或真实的)数据,则可以使用管理门户中的Tune table工具自动计算和设置其选择性值。 调优表确定一个字段是否有一个离群值,这个值比任何其他值都常见得多。 如果是这样,Tune Table将计算一个单独的离群值选择性百分比,并根据这个离群值的存在来计算选择性。 异常值的存在可能会极大地改变选择性值。 选择性用于查询优化。 在`SELECT`查询中指定的字段和在视图的`SELECT`子句中指定的字段使用相同的选择性值。 请注意,视图的行分布可能与源表不同。 这可能会影响视场选择性的精度。 ## BlockCount 当编译一个持久化类时,类编译器会根据区段大小和属性定义计算每个SQL映射使用的映射块的大致数量。 可以在调优表工具的Map `BlockCount`选项卡中查看这些`BlockCount`值。 块计数在调优表中由类编译器估计。 注意,如果更改了区段大小,则必须关闭并重新打开SQL Tune Table窗口,以查看该更改反映在`BlockCount`值中。 当运行Tune Table时,它会测量每个SQL映射的实际块计数。 除非另有指定,调优表测量值将替换类编译器的近近值。 这些调优表测量值在类定义中表示为负整数,以区别于指定的`BlockCount`值。 如下面的例子所示: ```xml -4 ``` 调优表测量值在调优表中表示为正整数,标识为由调优表测量。 可以在类定义中定义显式的块计数值。 可以显式地指定块计数为正整数,如下面的示例所示: ```xml 12 ``` 当定义一个类时,可以省略为`map`定义`BlockCount`,显式地指定一个`BlockCount`为正整数,或显式地定义`BlockCount`为`NULL`。 - 如果不指定块计数,或指定块计数为0,则类编译器估计块计数。 运行Tune Table将替换类编译器的估计值。 - 如果指定一个显式的正整数`BlockCount`,运行Tune Table不会替换此显式的`BlockCount`值。 在调优表中,显式的类定义块计数值表示为正整数,标识为在类定义中定义的。 这些块计数值不会通过随后运行Tune Table而更改。 - 如果将显式`BlockCount`指定为`NULL`,则SQL Map将使用类编译器估计的`BlockCount`值。因为`BlockCount`在类定义中是“定义的”,所以运行Tune Table不会替换这个估计的`BlockCount`值。 所有InterSystems SQL映射块的大小为2048字节(2K字节)。 在以下情况下,优化表不测量块计数: - 如果表是由数组或列表集合投影的子表。这些类型的子表的`BlockCount`值与父表数据映射的`BlockCount`值相同。 - 如果全局映射是远程全局(不同名称空间中的全局)。取而代之的是使用在类编译期间使用的估计的`BlockCount`。 # Tune Table Tune Table是一个实用程序,它检查表中的数据,并返回关于区段大小(表中的行数)、每个字段中不同值的相对分布以及平均字段大小(每个字段中值的平均长度)的统计信息。 它还为每个SQL映射生成块计数。 可以指定该调优表,使用此信息更新与表及其每个字段相关联的元数据。 查询优化器随后可以使用这些统计信息来确定最有效的查询执行计划。 在外部表上使用Tune Table将只计算区段大小。 调优表无法计算外部表的字段选择性值、平均字段大小或映射块计数值。 ## 何时运行调优表 **应该在每个表填充了具有代表性的实际数据之后,在该表上运行tune Table。通常,在数据“激活”之前,只需要运行一次tune Table,这是应用程序开发的最后一步。Tune Table不是维护实用程序;它不应对实时数据定期运行。** **注:在极少数情况下,运行调优表会降低SQL性能。虽然TuneTable可以在实时数据上运行,但建议在具有实际数据的测试系统上运行TuneTable,而不是在生产系统上运行。可以使用可选的系统模式配置参数来指示当前系统是测试系统还是活动系统。设置后,系统模式将显示在管理门户页面的顶部,并可由`$SYSTEM.Version.SystemMode()`方法返回。** 通常,在添加、修改或删除表数据时不应重新运行Tune Table,除非当前数据的特征发生了数量级的更改,如下所示: - 相对表大小:Tune Table假设它正在分析具有代表性的数据子集。如果该子集是代表性子集,则该子集只能是整个数据集的一小部分。如果联接或其他关系中涉及的表的`ExtentSize`保持大致相同的相对大小,则当表中的行数发生变化时,Tune Table结果仍然是相关的。如果连接表之间的比率更改了一个数量级,则需要更新`ExtentSize`。这对于`JOIN`语句很重要,因为SQL优化器在优化表连接顺序时使用`ExtentSize`。一般来说,无论查询中指定的联接顺序如何,都会先联接较小的表,然后再联接较大的表。因此,如果`tableA`和`tableB`中的行比从`1000:2000`更改为`10000:2000`,可能在一个或多个表上重新运行tune Table,但如果更改为`2100:4000`,则不需要重新运行tune Table。 - 均匀值分布:优化表假设每个数据值的可能性都是相等的。如果它检测到离群值,它会假定除离群值之外的每个数据值的可能性都是相等的。调谐表通过分析每个字段的当前数据值来建立选择性。真实数据的可能性相等始终是一个粗略的近似值;不同数据值的数量及其相对分布的正态变化不应保证重新运行调优表。但是,字段可能值的数量(不同值与记录的比率)的数量级变化或单个字段值的总体可能性可能会导致不准确的选择性。大幅更改具有单个字段值的记录的百分比可能会导致TuneTable指定一个离群值或删除指定的离群值,从而显著改变计算的选择性。如果字段的选择性不再反映数据值的实际分布,则应重新运行调优表。 - 重大升级或新的站点安装可能需要重新运行Tune Table。 ## 运行 Tune Table 运行调优表有三个接口: - 使用Management Portal SQL interface Actions下拉列表,它允许在单个表或多个表上运行Tune Table。 - 为单个表或当前命名空间中的所有表调用`$SYSTEM.SQL.Stats.Table.GatherTableStats()`方法。 - 对单个表发出SQL命令调优表。 Tune Table清除引用正在调优的表的缓存查询。 调优表命令提供了一个recompile缓存查询选项,以使用新的调优表计算值重新生成缓存的查询。 如果表映射到只读数据库,则无法执行调优表,并生成错误消息。 在运行了调优表工具之后,生成的区段大小和选择性值将保存在类的存储定义中。 要查看存储定义,在Studio中,从“视图”菜单中选择“视图存储”; Studio在类源代码的底部包含存储。 ### 从管理门户调优表 要从管理门户运行Tune Table: 1. 选择System Explorer,然后选择SQL。 通过单击页面顶部的Switch选项选择一个名称空间,然后从显示的列表中选择一个名称空间。 (可以为每个用户设置管理门户的默认名称空间。) 2. 从屏幕左侧的下拉列表中选择模式,或者使用筛选器。 3. 执行下列操作之一: - 优化单个表:展开表类别,然后从列表中选择一个表。选择表格后,单击操作下拉列表,然后选择调整表格信息。这将显示表的当前`ExtentSize`和选择性信息。如果从未运行过调谐表,`ExtentSize=100000`,则不会显示任何选择性、异常值选择性、异常值或平均字段大小信息(除了选择性为1的行ID),并且会按照类编译器的估计列出映射块计数信息。 从选择性选项卡中,选择调谐表按钮。这将在表上运行tune Table,根据表中的数据计算ExtentSize、选择性、异常值选择性、异常值和Average Field Size值。Map BlockCount(地图块计数)信息按Tune Table(调谐表)测量列出。 单个表上的Tune Table始终作为后台进程运行,并在完成后刷新该表。这可以防止超时问题。当此后台进程正在运行时,将显示一条正在进行的消息。在后台进程执行时,关闭按钮可用于关闭调谐表窗口。 - 优化方案中的所有表:单击操作下拉列表,然后选择优化方案中的所有表。这将显示调谐表方框。选择Finish按钮在方案中的所有表上运行Tune Table。调谐表完成后,此框显示完成按钮。选择Done(完成)退出Tune Table(调谐表)框。 SQL优化表窗口有两个选项卡:选择性和映射块计数。这些选项卡显示由调谐表生成的当前值。它们还允许手动设置与Tune Table生成的值不同的值。 选择性选项卡包含以下字段: - 当前表扩展大小。此字段有一个编辑按钮,允许输入不同的表格扩展大小。 - “使类保持最新”复选框。对Tune Table生成的统计数据的任何更改,或由Tune Table界面或Tune Table方法中的用户输入值生成的任何更改,都会立即表示在类定义中: - 如果未选中此框(否),则不会设置修改后的类别定义上的最新标志。这表明类定义已过期,应该重新编译。这是默认设置。 - 如果选中此框(是),类定义将保持标记为最新。在活动系统上更改统计信息时,这是首选选项,因为它降低了重新编译表类定义的可能性。 - 字段表,其中包含字段名称、选择性、备注、异常值选择性、异常值和平均字段大小等列。通过单击`Fields`表格标题,可以按该列的值进行排序。通过单击`Fields`表行,您可以手动设置该字段的选择性、异常值选择性、异常值和平均字段大小的值。 Map BlockCount选项卡包含以下字段: - 包含SQL Map Name、BlockCount和Source of BlockCount列的映射名称表。索引的SQL映射名称是SQL索引名;这可能不同于持久类索引属性名。 - 通过单击单个map名称,可以手动设置该地图名称的`BlockCount`值。 在选择性选项卡中,可以单击优化表按钮在此表上运行优化表。 ### 使用方法调整表 可以使用`$SYSTEM.SQL.Stats.Table.GatherTableStats()`方法在当前名称空间中运行Tune Table工具。 - `GatherTableStats(“Sample.MyTable”)`在单个表上运行TuneTable。 - `GatherSchemaStats(“Sample”)`在指定模式中的所有表上运行tune Table。 - `GatherTableStats(“*”)`在当前命名空间中的所有表上运行TuneTable。 使用`GatherTableStats()`方法时,可能会生成以下错误消息: - 不存在的表: ```java DO $SYSTEM.SQL.Stats.Table.GatherTableStats("NoSuchTable") ``` ```java No such table 'SQLUser.NoSuchTable' ``` - `View`视图: ```java DO $SYSTEM.SQL.Stats.Table.GatherTableStats("Sample.MyView") ``` ```java 'Sample.MyView' is a view, not a table. No tuning will be performed. ``` 当运行`GatherTableStats(“*”)`或`GatherSchemaStats(“SchemaName”)`时,如果系统支持并行处理,系统将使用多个进程并行调优多个表。 ## 在分片表上运行Tune table 如果在一个分片表上运行调优表,那么调优表操作将被转发到每个碎片,并针对该表的那个碎片运行。 调优表不会在调用它的主名称空间中执行。 如果在导出到碎片的类定义的非分片表上运行调优表,因为该表已连接到一个分片表,调优表操作将转发到每个碎片,并且它也在主名称空间中执行。 在分片表上运行Tune Table时,应该遵循以下准则: - 优化分片主表,而不是分片本地表。 - 区段大小和块计数值是每个分片的值,而不是所有分片的总和。 - 如果使用`$SYSTEM.SQL.Stats.Table.Export()`和`$SYSTEM.SQL.Stats.Table.Import()`,则导出/导入分片主表的调优统计,而不是分片本地表。 - 调优切分表将在切分主类和切分本地类/表定义中定义调优统计。 如果手动编辑类定义中的调优表元数据,建议的过程是修改碎片主类的定义,然后重新编译碎片主类。 在编译碎片主类时,碎片主调优统计信息将被复制到类的碎片本地版本。 如果`GatherTableStats()`或`GatherSchemaStats()`指定了一个`logFile`参数,shard master实例中的日志文件有一个针对指定表的条目,例如: - Sharded table: `TABLE: Invoking TuneTable on shards for sharded table ` - Non-sharded table: `TABLE: Invoking TuneTable on shards for mapped non-sharded table ` 在每个分片实例上,在`mgr/`目录中创建一个同名的日志文件,记录这个分片上这个表的调优表信息。 如果为日志文件指定了目录路径,那么分片将忽略该路径,并且该文件始终存储在`mgr/`中。
文章
Nicky Zhu · 二月 3, 2021

IRIS中的权限管理

下一篇: [案例: 建立只能使用SQL的用户](https://cn.community.intersystems.com/post/%E6%A1%88%E4%BE%8B-%E5%BB%BA%E7%AB%8B%E5%8F%AA%E8%83%BD%E4%BD%BF%E7%94%A8sql%E7%9A%84%E7%94%A8%E6%88%B7) IRIS通过认证(Authentication)与授权(Authorization)两项机制控制外部用户对系统及应用、数据资源的可访问性。因此。如需要进行权限控制,则需要通过配置认证和授权进行。 ## IRIS中的认证 {#2} 认证可以验证任何试图连接到InterSystems IRIS®的用户的身份。一旦通过认证,用户就与IRIS建立了通信,从而可以使用其数据和工具。有许多不同的方法可以验证用户的身份;每种方法都称为验证机制。IRIS 通常被配置为只使用其中一种方式。 支持的认证方式 * 实例认证:通过用户名/密码对登录平台,即密码认证 * LDAP:通过第三方LDAP服务器(如Windows Active Directory )完成认证 * 操作系统认证:建立操作系统用户-平台用户映射,使用操作系统用户登录平台 * Kerberos:使用Kerberos协议进行认证 * 代理认证:使用自定义的代码实现认证过程 ### 系统服务与认证 {#2.1} 在安装时,IRIS会启动一系列系统级的服务用与控制与外部用户或系统的交互,这些服务都绑定了默认的认证机制 ![image](/sites/default/files/inline/images/2.1.png) 图中红框标出的即为系统安装后会自动启用并需经认证才可使用的系统服务,认证手段可配置。 例如,如果变更%Service_Console的身份验证方法,取消密码方法,用户就不能通过输入用户名密码登入Terminal。 通过Portal的菜单 系统管理 > 安全 > 服务 可访问该设置。 ### 账户控制参数 {#2.2} 通过系统管理 > 安全 > 系统安全 > 系统范围的安全参数中的选项可对于用户名/密码认证手段的行为进行更多的约束。 ![image](/sites/default/files/inline/images/2.2.png) * 非活动限制 - 指定用户账户不活跃的最大天数,它被定义为成功登录之间的时间。当达到此限制时,该帐户将被禁用。值为0(0)表示对登录之间的天数没有限制。[对于最低安全级别的安装,默认为0,对于正常和锁定的安装,默认为90]。 * 无效登录限制 (0-64) - 指定连续不成功的登录尝试的最大次数。在达到此限制后,要么禁用账户,要么对每次尝试进行递增的时间延迟;行动取决于如果达到登录限制字段则禁用账户的值。值为0(零)表示对无效登录的次数没有限制。[默认为5] * 如果达到登录限制,则禁用账户 - 如果选中,则指定达到无效登录次数(在前一字段中指定)将导致用户账户被禁用。 * 密码有效期天数(0-99999) - 指定密码过期的频率以及用户更改密码的频率(天数)。当初始设置时,指定密码过期的天数。0(0)表示密码永远不会过期。不会影响已设置了下次登录时更改密码字段的用户。[默认为0] 需要特别注意的是,密码有效性、过期和禁用账户等设置会影响IRIS实例的所有账户,包括IRIS超级管理员账户。如触发了控制策略,则在更新这些帐户的信息之前,可能无法进行各种操作,这可能导致意外的结果。如超级管理员账户被锁定,则需要通过紧急模式启动实例再进行修改。 对于系统可用的认证手段的配置和其他可用的安全配置,请参见[Security Administration Guide](https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCAS "安全管理向导") ## IRIS中的授权 {#3} ### 授权模型 {#3.1} InterSystems公司的授权模式采用基于角色的访问控制。 * Users – 用户 * Roles – 角色 * Privileges – 权限 * Resources – 资源 | Permissions – 许可 在这种模式下,用户拥有与分配给各自用户身份的角色相关的权限。 ![image](/sites/default/files/inline/images/3.1.png) * 一个角色是一个命名的特权集合 * 一个用户可以拥有一个以上的角色 * 权限分配给角色,角色分配给用户 其中,Roles就是权限的集合,而权限提供对资源的特定类型的访问的许可。 * 可控资源: 数据库,服务,应用(包括Web应用 )和其他 * 可选用的许可: Read, Write or Use,其中执行代码需要数据库的读权限 ### 资源的定义 {#3.2} 资源是一项相对抽象的概念,用来指代IRIS中的数据库,服务,应用等可被访问的对象。例如,对于数据库,在建立时默认采用%DB_%DEFAULT指代,也可自定义资源(数据库资源必须以%DB_开头): ![image](/sites/default/files/inline/images/3.2.png) 对于Web应用,默认不需要通过资源控制,即所有可登录用户都可访问(但该用户进程不一定能访问到数据,还需参照是否具有对数据库的访问权限)。如通过分配资源进行控制,则登录用户还需具有资源才能访问这个Web应用: ![image](/sites/default/files/inline/images/3.3.png) 因此,一项权限实际上是指对某个资源的一些特定操作的集合。 例如,对于数据库UserDB具有读写操作许可的权限A,对于Web应用/csp/sys具有使用操作许可的权限B。如果我们将这两项权限都赋给角色RoleA,那么这个角色就同时拥有A权限和B权限,从而能够访问数据库UserDB和访问Web应用/csp/sys。 ### SQL授权 {#3.3} 除了对数据库进行授权外,IRIS作为一个数据平台,需要对外提供数据访问。因此,IRIS也提供了SQL授权对用户可执行的SQL进行细粒度的权限控制。 SQL的授权可以分配给角色或用户。但通常在企业环境中,用户数量会很多,仍然需要对SQL用户进行分组,根据分组规划角色,通过角色进行授权的控制,才能有效降低维护授权所需的工作量。 SQL的授权针对SQL类型,可分为库、表级授权。 对于Create table、drop view、truncate table这一类的DDL,使用库级授权,即用户可在特定的库中执行建表、删除视图等经过授权的操作。如下: ![image](/sites/default/files/inline/images/3.4.png) 对于Select、update等DML,则使用表级授权,使用户能够通过DML访问特定的表中的数据。如下: ![image](/sites/default/files/inline/images/3.5.png) 除通过Portal操作之外,对于SQL的授权,还可使用IRIS SQL中的额GRANT语句,例如: `GRANT * ON Schema Test TO TestRole` 这个SQL即可以将当前操作数据库下Schema Test中的所有表的所有权限都赋给TestRole这个角色。 关于GRANT语句的用法,可参见[GRANT指令](https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI.Page.cls?KEY=RSQL_grant) 以上即为IRIS中进行权限控制所需掌握的概念和内容,在后续文章中,我们会结合实例向大家介绍其使用。 下一篇: [案例: 建立只能使用SQL的用户](https://cn.community.intersystems.com/post/%E6%A1%88%E4%BE%8B-%E5%BB%BA%E7%AB%8B%E5%8F%AA%E8%83%BD%E4%BD%BF%E7%94%A8sql%E7%9A%84%E7%94%A8%E6%88%B7) 推荐阅读 [Security Administration Guide](https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCAS "安全管理向导") - https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCAS