搜索​​​​

清除过滤器
文章
Michael Lei · 一月 26, 2022

跟Robert Cemper老师学习 IRIS 和Caché

@Robert.Cemper1003 我们全球(几乎是)最资深最活跃粉丝最多的社区成员 (最畅销!). Robert 老师从1970年代就开始从事软件行业,从1978年就开始从事MUMPS, DSM, ... Caché, ...IRIS等等直到今天还在活越地写代码。所以跟着Robert老师超过100篇的社区文章中学习 InterSystems 技术是最好不过的选择了。我们按照主题精选了一些文章,后面会陆续翻译,大家希望优先翻译哪一篇,欢迎在评论区留言。 关于SQL DB Migration using SQLgateway IRIS 使用 SQLgateway 迁移到IRIS. Dataset Lightweight M:N 和 M:N Relationship 高级SQL 关系 Using ClassQueries() as Tables 把ClassQueries 当成表来使用 (就像使用objectscript的视图 ) Effective use of Collection Indexing and Querying Collections through SQL SQL 查询采集索引 Show Global by SQL SELECT 看 globals的SQL 语句 SQL for ^ERROR Global get ^ERROR global 的SQL 语句 SPOOL as SQL Table get the SPOOL report 的SQL 语句 Materialized Views 如何用IRIS实现 "物化视图" Semi-Persistent Classes and Tables 仅仅持久化你需要的类的内容 Static WHERE Conditions 使用sql 类方法来筛选where里面的数据 Repairing your Index 重构索引------------------------------------------------------------------ 关于Python 和原生 API AoC2021-rcc 使用嵌入式Python的25个样本代码 Using ZPM for Node.js 从Node.js 使用 zpm 模块 WebSocket Client with embedded Python 嵌入式Python的WebSocket 客户端 Trying Embedded Python IRIS内的python 例子 Native API for ObjectScript Demo 和 IRIS Native API for ObjectScript 简单的原生API demo IRIS-NativeAPI-Nodejs-compact 和 WebSocket Client JS with IRIS Native API as Docker Micro Server Node.js 和原生 NativeAPI的轻量级docker实例 Client for WebSockets based on Node.js 基于 Node.js的web sockets ------------------------------------------------------------------ 关于 Globals, ObjectScript, ZPM 和 COS Traditional Debugging in ObjectScript Debug 你的 ObjectScript 代码 The future position of ObjectScript ObjectScript开发的未来 Helper for ObjectScript Language Extensions 扩展ObjectScript的帮助app FOREACH for ObjectScript 从Macro到foreach 的 ObjectScript ObjectScript over ODBC ObjectScript使用 ODBC Global Scanning & Slicing 输出任何级别的globals细节 ZPMshow - a helper for tired fingers 如何使用ZPM Un-Typical persistence 关于持久化的高级技巧 persistence snapshot to JSON 从快照 到 JSON A function to check if string is JSON object 检查字符串是否为JSON对象的功能 fast JSON formatting for Caché / Ensemble 和 fast JSON formatting for IRIS 漂亮地格式化你的 JSON 字符串 Organize %ZLANG** 关于ZLANG的技巧 Wrap JSON to multi lines / Test ISJSON JSON 多行处理 Parameter passing to Language Extentions 为语言扩展给参数传值(SystemFunctions, SystemVariables, SystemCommands) Terminal Multi-Line Command Editor 和 Terminal Multi-Line Option 多行终端 Backport %JSON.* to Caché 把%JSON backport到 Caché Moving Code from IRIS to Caché 把代码从 IRIS 移动到 Caché Multidimensional Property Persistence - Part 1 (Classic) 持久化多维属性 SUDOKU demo ObjectScript SUDOKU Demo Synchronize Data with DSTIME DSTime 例子 Adopted Bitmaps example now on Open Exchange 和The adopted Bitmap 使用 BITMAPS的应用例子 Client for WebSockets based on CSP 基于CSP的web socket客户端 Execute Server Commands from Caché / Ensemble / IRIS 从caché/ensemble/IRIS执行服务器命令. Sharding evaluation #1 和 Manual Setup of Sharding IRIS分片技术 Global Time Management datetime属性里的UTC time properties Summary on Local Variable Scoping Variable scopings The 'unlimited' UNIQUE index UNIQUE 索引技术 @Indirection and eXECUTE - why ? 动态调用 Light weight EXCEL download 轻量数据集/excel Date before Dec.1840 ? Negative $H(orolog) ? 关于日期 dates------------------------------------------------------------------ 关于互操作性 Interoperability Generating OFX [V1] 为MS OFX format自定义 production ------------------------------------------------------------------ 关于IRIS 基础架构和docker Storage Considerations on large data sets 处理大型数据的存储考虑 datasets Docker Desktop Windows - disk space consumption 关于 docker 磁盘空间管理 SSH for IRIS container IRIS容器的 SSH 访问. How to execute IRIS restart from inside 正确滴重启 IRIS Splitting an IRIS db to multiple drives 把IRISDB分解到多个HD drives How to shrink IRISTEMP db in runtime? 节省 IRISTEMP 磁盘分配 IRIS easy ECP workbench 使用IRIS ECP Making development in Docker environment easier 让 docker 环境下的开发更容易 Using ECP across IRIS and Caché 跨 IRIS 和 Cache 使用ECP ECP between IRIS and Caché / Ensemble 在 IRIS, Cache 和 Ensemble同时存在的情况下使用ECP moving code between IRIS and CACHÉ 在 IRIS 和 Cache之间移动代码 Break in case of Emergency 紧急情况下的远程访问 IRIS-Docker-micro-Durability 再次运行 docker 实例时保留参数 Docker vs. Durability 较少可用资源下的Docker instances WebSocket Client IRIS internal %Net.WebSocket.客户端 WebSocket Echo Server in IRIS Web socker demo A more useFull Object Dump 一个更有用的Object dump Using Interjob communication (IJC) 使用 IJC Background Jobs over ECP 分布式任务--ECP 下的后台任务 Simple Remote Server Control 简单的远程服务器监控 SPOOL - the forgotten device SPOOL 管理被遗忘的设备------------------------------------------------------------------ 其他 Successful Troubleshooting 成功的Troubleshooting 感谢🙏 Robert Cemper先生, 我们感谢您! From The Roots to InterSystems Robert先生的经历简介
文章
Hao Ma · 三月 26, 2021

精华文章---基于Docker的一体化集成AI环境中部署机器学习/深度学习模型

**关键字**:IRIS,IntegratedML,Flask,FastAPI,TensorFlow Serving,HAProxy,Docker,Covid-19 ## 目的: 过去几个月里,我们提到了一些深度学习和机器学习的快速演示,包括一个简单的 Covid-19 X 射线图像分类器和一个用于可能的 ICU 入院的 Covid-19 实验室结果分类器。  我们还介绍了 ICU 分类器的 IntegratedML 演示实现。  虽然“数据科学”远足仍在继续,但从“数据工程”的角度来看,或许也是尝试一些 AI 服务部署的好时机 - 我们能否将目前所接触到的一切都封装成一套服务 API?  我们可以利用哪些常用的工具、组件和基础架构,以最简单的方式实现这样的服务堆栈?   ## 范围 ### **范围内:** 作为快速入门,我们可以使用 docker-compose 将以下 docker 化组件部署到 AWS Ubuntu 服务器中 * **HAProxy ** - 负载均衡器 * **Gunicorn** vs. **Univorn ** - Web 网关****服务器 * **Flask** vs. **FastAPI** - Web 应用 UI 的应用服务器、服务 API 定义和热图生成等 * **TensorFlow Model Serving** vs. **TensorFlow-GPU Model Serving** - 图像等分类的应用后端服务器 * IRIS **IntegratedML** - 带有 SQL 界面的统一 App+DB AutoML * **Jupyter Notebook** 中模拟客户端进行**基准测试**的 Python3 *  Docker 和 **docker-compose** * 配备 Tesla T4 GPU 的 **AWS Ubuntu** 16.04  注:配备 GPU 的 TensorFlow Serving 仅用于演示目的 - 您只需关闭 GPU 相关镜像(在 dockerfile 中)和配置(在 docker-compose.yml 中)。 ### **范围外**或者在下一个愿望清单上: * **Nginx** 或 **Apache** 等 Web 服务器在演示中暂时省略。 * **RabbitMQ** 和 Redis - 用于可靠消息传递的队列代理,可由 IRIS 或 Ensemble 替代。    * **IAM** ([Intersystems API Manger](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=AIAM)) 或 **Kong** 在愿望清单上 * **SAM **(Intersystems [System Alert & Monitoring](https://docs.intersystems.com/sam/csp/docbook/DocBook.UI.Page.cls?KEY=ASAM))  * **ICM** ([Intersystems Cloud Manager](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=PAGE_DEPLOYMENT_ICM)) 与 **Kubernetes** Operator - 诞生以来一直是我的最爱 * **FHIR**(基于 Intesystems IRIS 的 FHIR R4 服务器和 FHIR Sandbox,用于 FHIR 应用上的 SMART) * **CI/CD** devop 工具或 **Github Actions** “机器学习工程师”必然会动手遍历这些组件,在服务生命周期内提供一些生产环境。 随着时间的推移,我们可以扩大范围。   ## GitHub 仓库 完整源代码位于 [integratedML-demo-template 仓库](https://github.com/intersystems-community/integratedml-demo-template)也与新仓库一同重用。   ## 部署模式 以下为此“Docker 中的 AI 演示”测试框架的逻辑部署模式。 出于演示目的,我特意创建了 2 个独立的堆栈,用于深度学习分类以及 Web 渲染,然后使用 HAProxy 作为软负载均衡器,以无状态方式在这 2 个堆栈之间分配传入的 API 请求。 * Guniorn + Flask + TensorFlow Serving * Univcorn + FaskAPI + TensorFlow Serving GPU IRIS 与 IntegratedML 用于机器学习演示示例,即 ICU 预测的前一篇文章中。 在目前的演示中,我省略了一些生产服务需要或考虑的常用组件: * Web 服务器:Nginx 或 Apache 等 HAProxy 和 Gunicorn/Uvicorn 之间需要它们,以进行正确的 HTTP 会话处理,即避免 DoS 攻击等。 * 队列管理器和数据库:RabbitMQ 和/或 Redis 等,在 Flask/FastAPI 和后端服务之间,用于可靠的异步服务和数据/配置持久性等。   * API 网关:IAM 或 Kong 集群,在 HAProxy 负载均衡器和 Web 服务器之间进行 API 管理,不创建单点故障。 * 监视和警报:SAM 很不错。 * 为 CI/CD DevOps 进行配置:云中立的部署和管理以及带有其他常见 devops 工具的 CI/CD 将需要带 K8s 的 ICM。 其实,IRIS 本身当然可以作为企业级队列管理器以及用于可靠消息传递的高性能数据库。 在模式分析中,很明显 IRIS 可以代替 RabbitMQ/Redis/MongoDB 等队列代理和数据库,得到更好的整合,大幅减少延迟,并提高整体性能。  还有,IRIS Web Gateway(先前为 CSP Gateway)当然可以代替 Gunicorn 或 Unicorn 等,对吧?     ## 环境拓扑 在全 Docker 组件中实现上述逻辑模式有几种常见选项。 首先:   * docker-compose * docker swarm 等 * Kubernetes 等  * 带 K8s 操作的 ICM 这个演示从功能性 PoC 和一些基准测试的“docker-compose”开始。 当然,我们很想使用 K8s,也有可能随着时间的推移使用 ICM。  如 [docker-compose.yml](https://github.com/zhongli1990/covid-ai-demo-deployment/blob/master/docker-compose.yml) 文件中所述,它的环境拓扑在 AWS Ubuntu 服务器上的物理实现最终将是:   上图显示了如何将所有 Docker 实例的**服务端口**映射并直接暴露于 Ubuntu 服务器以进行演示。 在生产中应该全部经过安全加固。 纯粹出于演示目的,所有容器都连接到同一个 Docker 网络中;而在生产中,它将被分为外部可路由和内部不可路由。   ## Docker 化组件  下面显示了主机中的那些**存储卷**如何按照这个 [docker-compose.yml](https://github.com/zhongli1990/covid-ai-demo-deployment/blob/master/docker-compose.yml) 文件的指示挂载到各个容器实例:  ubuntu@ip-172-31-35-104:/zhong/flask-xray$ tree ./ -L 2 ./ ├── covid19 (Flask+Gunicorn container and Tensorflow Serving container will mount here) │ ├── app.py (Flask main app: Both web application and API service interfaces are defined and implemented here) │ ├── covid19_models (Tensorflow models are published and versioned here for image classification Tensorflow Serving container with CPU) │ ├── Dockerfile (Flask server with Gunicorn: CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:5000", "--workers", "4", "--threads", "2"]) │ ├── models (Models in .h5 format for Flask app and API demo of heatmap generation by grad-cam on X-Rays) │ ├── __pycache__ │ ├── README.md │ ├── requirements.txt (Python packages needed for the full Flask+Gunicorn apps) │ ├── scripts │ ├── static (Web static files) │ ├── templates (Web rendering templates) │ ├── tensorflow_serving (Config file for tensorflow serving service) │ └── test_images ├── covid-fastapi (FastAPI+Uvicorn container and Tensorflow Serving with GPU container will mount here) │ ├── covid19_models (Tensorflow serving GPU models are published and versioned here for image classification) │ ├── Dockerfile (Uvicorn+FastAPI server are started here: CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4" ]) │ ├── main.py (FastAPI app: both web application and API service interfaces are defined and implemented here) │ ├── models (Models in .h5 format for FastAPI app and API demo of heatmap generation by grad-cam on X-Rays) │ ├── __pycache__ │ ├── README.md │ ├── requirements.txt │ ├── scripts │ ├── static │ ├── templates │ ├── tensorflow_serving │ └── test_images ├── docker-compose.yml (Full stack Docker definition file. Version 2.3 is used to accommodate Docker GPU "nvidia runtime", otherwise can be version 3.x) ├── haproxy (HAProxy docker service is defined here. Note: sticky session can be defined for backend LB. ) │ ├── Dockerfile │ └── haproxy.cfg └── notebooks (Jupyter Notebook container service with Tensorflow 2.2 and Tensorboard etc) ├── Dockerfile ├── notebooks (Sample notebook files to emulate external API Client apps for functional tests and API benchmark tests in Python on the load balancer etc) └── requirements.txt 注:以上 [docker-compose.yml](https://github.com/zhongli1990/covid-ai-demo-deployment/blob/master/docker-compose.yml) 用于Covid-19 X 射线的深度学习演示。  它与另一个 [integratedML-demo-template](https://github.com/intersystems-community/integratedml-demo-template) 的 [docker-compose.yml](https://github.com/intersystems-community/integratedml-demo-template/blob/master/docker-compose.yml) 一起使用,形成环境拓扑中显示的完整服务堆栈。     ## 服务启动  简单的 **docker-compose up -d** 即可启动所有容器服务: ubuntu@ip-172-31-35-104:~$ docker psCONTAINER ID        IMAGE                                 COMMAND                  CREATED             STATUS                PORTS                                                                              NAMES31b682b6961d        iris-aa-server:2020.3AA               "/iris-main"             7 weeks ago         Up 2 days (healthy)   2188/tcp, 53773/tcp, 54773/tcp, 0.0.0.0:8091->51773/tcp, 0.0.0.0:8092->52773/tcp   iml-template-master_irisimlsvr_16a0f22ad3ffc        haproxy:0.0.1                         "/docker-entrypoint.…"   8 weeks ago         Up 2 days             0.0.0.0:8088->8088/tcp                                                             flask-xray_lb_171b5163d8960        ai-service-fastapi:0.2.0              "uvicorn main:app --…"   8 weeks ago         Up 2 days             0.0.0.0:8056->8000/tcp                                                             flask-xray_fastapi_1400e1d6c0f69        tensorflow/serving:latest-gpu         "/usr/bin/tf_serving…"   8 weeks ago         Up 2 days             0.0.0.0:8520->8500/tcp, 0.0.0.0:8521->8501/tcp                                     flask-xray_tf2svg2_1eaac88e9b1a7        ai-service-flask:0.1.0                "gunicorn app:app --…"   8 weeks ago         Up 2 days             0.0.0.0:8051->5000/tcp                                                             flask-xray_flask_1e07ccd30a32b        tensorflow/serving                    "/usr/bin/tf_serving…"   8 weeks ago         Up 2 days             0.0.0.0:8510->8500/tcp, 0.0.0.0:8511->8501/tcp                                     flask-xray_tf2svg1_1390dc13023f2        tf2-jupyter:0.1.0                     "/bin/sh -c '/bin/ba…"   8 weeks ago         Up 2 days             0.0.0.0:8506->6006/tcp, 0.0.0.0:8586->8888/tcp                                     flask-xray_tf2jpt_188e8709404ac        tf2-jupyter-jdbc:1.0.0-iml-template   "/bin/sh -c '/bin/ba…"   2 months ago        Up 2 days             0.0.0.0:6026->6006/tcp, 0.0.0.0:8896->8888/tcp                                     iml-template-master_tf2jupyter_1 以 **docker-compose up --scale fastapi=2 --scale flask=2 -d** 为例,将水平扩展到 2 个 Gunicorn+Flask 容器和 2 个 Univcorn+FastAPI 容器: ubuntu@ip-172-31-35-104:/zhong/flask-xray$ docker psCONTAINER ID        IMAGE                                 COMMAND                  CREATED             STATUS                PORTS                                                                              NAMESdbee3c20ea95        ai-service-fastapi:0.2.0              "uvicorn main:app --…"   4 minutes ago       Up 4 minutes          0.0.0.0:8057->8000/tcp                                                             flask-xray_fastapi_295bcd8535aa6        ai-service-flask:0.1.0                "gunicorn app:app --…"   4 minutes ago       Up 4 minutes          0.0.0.0:8052->5000/tcp                                                             flask-xray_flask_2 ... ... 在“integrtedML-demo-template”的工作目录下再运行一个“docker-compose up -d”,就出现了上面列表中的 irisimlsvr 和 tf2jupyter 容器。   ## 测试 ### **1. 带有简单 UI 的 AI 演示 Web 应用** 启动上述 docker 服务后,我们可以访问 AWS EC2 实例中托管的 [Covid-19 肺部 X 射线检测](https://community.intersystems.com/post/run-some-covid-19-lung-x-ray-classification-and-ct-detection-demos)演示 Web 应用,临时地址为 http://ec2-18-134-16-118.eu-west-2.compute.amazonaws.com:8056/ 以下是从我的手机截取的屏幕。  它有一个非常简单的演示 UI:基本上只需要点击“Choose File”,然后点击“Submit”按钮,上传 [X 射线图像](https://github.com/zhongli1990/Covid19-X-Rays/tree/master/all/test),然后应用就会显示分类报告。 如果图像被分类为 Covid-19 X 射线图像,则会[显示热图](https://community.intersystems.com/post/explainability-and-visibility-covid-19-x-ray-classifiers-deep-learning),通过 DL 模拟“检测到的”病变区域;如果未被分类为 Covid-19 X 射线图像,分类报告将仅显示上传的 X 射线图像。          该 Web 应用是一个 Python 服务器页面,其逻辑主要在 [FastAPI 的 main.py](https://github.com/zhongli1990/covid-ai-demo-deployment/blob/master/covid-fastapi/main.py) 文件以及 [Flask 的 app.py](https://github.com/zhongli1990/covid-ai-demo-deployment/blob/master/covid19/app.py) 文件中进行编码。 如果有更多的空闲时间,我可能会详细说明 Flask 和 FastAPI 之间的编码和惯例差异。  其实我希望可以为 AI 演示托管对比 Flask、FastAPI 与 IRIS。    ### **2. 测试演示 API**       FastAPI(在端口 8056 处公开)内置 Swagger API 文档,如下所示。 这非常好用。 我需要做的就是在其 URL 中使用“/docs”,例如:  ![](/sites/default/files/inline/images/images/image(875).png) 我内置了一些占位符(如 /hello 和 /items)和一些真正的演示 API 接口(如 /healthcheck、/predict 和 predict/heatmap)。   **来对这些 API 进行一个快速测试**,在我为这个 AI 演示服务准备的一个 [Jupyter Notebook 示例文件](https://github.com/zhongli1990/covid-ai-demo-deployment/tree/master/notebooks/notebooks)中运行一些 Python 行(作为 API 客户端应用模拟器)。   下面我以运行这个文件为例: 首先测试后端 TF-Serving(端口 8511)和 TF-Serving-GPU(端口 8521)是否正常运行:  !curl http://172.17.0.1:8511/v1/models/covid19 # tensorflow serving !curl http://172.17.0.1:8521/v1/models/covid19 # tensorflow-gpu serving { "model_version_status": [ { "version": "2", "state": "AVAILABLE", "status": { "error_code": "OK", "error_message": "" } } ] } { "model_version_status": [ { "version": "2", "state": "AVAILABLE", "status": { "error_code": "OK", "error_message": "" } } ] }   然后测试以下服务 API 是否正常运行: Gunicorn+Flask+TF-Serving Unicorn+FastAPI+TF-Serving-GPU 以上麻烦服务之前的负载均衡器 HAProxy r = requests.get('http://172.17.0.1:8051/covid19/api/v1/healthcheck') # tf srving docker with cpu print(r.status_code, r.text) r = requests.get('http://172.17.0.1:8056/covid19/api/v1/healthcheck') # tf-serving docker with gpu print(r.status_code, r.text) r = requests.get('http://172.17.0.1:8088/covid19/api/v1/healthcheck') # tf-serving docker with HAproxy print(r.status_code, r.text) 结果应为: 200 Covid19 detector API is live! 200 "Covid19 detector API is live!\n\n" 200 "Covid19 detector API is live!\n\n"   测试一些功能性 API 接口(例如 **/predict/heatmap**)来返回输入 X 射线图像的分类和热图结果。  根据 API 定义,在通过 HTTP POST 发送之前,入站图像为 based64 编码: %%time # importing the requests library import argparse import base64 import requests # defining the api-endpoint API_ENDPOINT = "http://172.17.0.1:8051/covid19/api/v1/predict/heatmap" image_path = './Covid_M/all/test/covid/nejmoa2001191_f3-PA.jpeg' #image_path = './Covid_M/all/test/normal/NORMAL2-IM-1400-0001.jpeg' #image_path = './Covid_M/all/test/pneumonia_bac/person1940_bacteria_4859.jpeg' b64_image = "" # Encoding the JPG,PNG,etc. image to base64 format with open(image_path, "rb") as imageFile: b64_image = base64.b64encode(imageFile.read()) # data to be sent to api data = {'b64': b64_image} # sending post request and saving response as response object r = requests.post(url=API_ENDPOINT, data=data) print(r.status_code, r.text) # extracting the response print("{}".format(r.text)) 所有此类[测试图像也已上传到 GitHub](https://github.com/zhongli1990/Covid19-X-Rays/tree/master/all/test)。  以上代码的结果将为: 200 {"Input_Image":"http://localhost:8051/static/source/0198f0ae-85a0-470b-bc31-dc1918c15b9620200906-170443.png","Output_Heatmap":"http://localhost:8051/static/result/Covid19_98_0198f0ae-85a0-470b-bc31-dc1918c15b9620200906-170443.png.png","X-Ray_Classfication_Raw_Result":[[0.805902302,0.15601939,0.038078323]],"X-Ray_Classification_Covid19_Probability":0.98,"X-Ray_Classification_Result":"Covid-19 POSITIVE","model_name":"Customised Incpetion V3"} {"Input_Image":"http://localhost:8051/static/source/0198f0ae-85a0-470b-bc31-dc1918c15b9620200906-170443.png","Output_Heatmap":"http://localhost:8051/static/result/Covid19_98_0198f0ae-85a0-470b-bc31-dc1918c15b9620200906-170443.png.png","X-Ray_Classfication_Raw_Result":[[0.805902302,0.15601939,0.038078323]],"X-Ray_Classification_Covid19_Probability":0.98,"X-Ray_Classification_Result":"Covid-19 POSITIVE","model_name":"Customised Incpetion V3"} CPU times: user 16 ms, sys: 0 ns, total: 16 ms Wall time: 946 ms   ### **3. 基准测试演示服务 API** 我们设置了一个 HAProxy 负载均衡器实例。 我们还启动了一个有 4 个工作进程的 Flask 服务,以及一个也有 4 个工作进程的 FastAPI 服务。 为什么不直接在 Notebook 文件中创建 8 个 Pyhon 进程,模拟 8 个并发 API 客户端向演示服务 API 发送请求,看看会发生什么  #from concurrent.futures import ThreadPoolExecutor as PoolExecutor from concurrent.futures import ProcessPoolExecutor as PoolExecutor import http.client import socket import time start = time.time() #laodbalancer: API_ENDPOINT_LB = "http://172.17.0.1:8088/covid19/api/v1/predict/heatmap" API_ENDPOINT_FLASK = "http://172.17.0.1:8052/covid19/api/v1/predict/heatmap" API_ENDPOINT_FastAPI = "http://172.17.0.1:8057/covid19/api/v1/predict/heatmap" def get_it(url): try: # loop over the images for imagePathTest in imagePathsTest: b64_image = "" with open(imagePathTest, "rb") as imageFile: b64_image = base64.b64encode(imageFile.read()) data = {'b64': b64_image} r = requests.post(url, data=data) #print(imagePathTest, r.status_code, r.text) return r except socket.timeout: # in a real world scenario you would probably do stuff if the # socket goes into timeout pass urls = [API_ENDPOINT_LB, API_ENDPOINT_LB, API_ENDPOINT_LB, API_ENDPOINT_LB, API_ENDPOINT_LB, API_ENDPOINT_LB, API_ENDPOINT_LB, API_ENDPOINT_LB] with PoolExecutor(max_workers=16) as executor: for _ in executor.map(get_it, urls): pass print("--- %s seconds ---" % (time.time() - start)) 因此,处理 8x27 = 216 张测试图像花了 74s。 这个负载均衡的演示堆栈每秒能够处理 3 张图像(通过将分类和热图结果返回客户端): --- 74.37691688537598 seconds --- 从 Putty 会话的 Top 命令中,我们可以看到在上述基准脚本开始运行后,8 个服务器进程(4 个 gunicorn + 4 个 unicorn/python)开始加速   ## 未来计划 这篇帖子只是将“All-in-Docker AI 演示”部署堆栈组合为测试框架的起点。 接下来,我希望根据 FHIR R4 等添加更多的 API 演示接口(例如 Covid-19 ICU 预测接口等),并添加一些支持 DICOM 输入格式。 这也可以成为一个测试台,用于探索与 IRIS 托管的 ML 功能更紧密的集成。 未来它也可以用作测试框架(也是一个非常简单的框架),随着我们在医疗影像、人群健康或个性化预测以及 NLP 等各个 AI 领域的发展,截取越来越多的 ML 或 DL 专业模型。 我还在[上一篇帖子的末尾(在“未来计划”部分)](https://community.intersystems.com/post/run-some-covid-19-icu-predictions-ml-vs-integratedml-part-ii)列出了一个愿望清单。   
文章
姚 鑫 · 五月 16, 2021

第二章 使用%UnitTest进行单元测试

# 第二章 使用%UnitTest进行单元测试 本教程的第二部分介绍了如何使用%UnitTest包对InterSystems IRIS代码进行单元测试。完成本教程的这一部分后,将能够: - 解释`%UnitTest`包中三个主要类的角色。 - 列出基于`%UnitTest`包的单元测试类和方法的要求。 - 创建并执行方法的单元测试。 - 浏览`%UnitTest.Manager`创建的测试报告。 - 执行单元测试时,使用`%UnitTest.TestCase`方法初始化和还原数据库数据。 # 什么是%UnitTest? `%UnitTest`包是一组为IRIS提供测试框架的类。在结构上,它类似于`xUnit`测试框架。`%UnitTest`为创建和执行以下各项的单元测试提供类和工具: - 类和方法 - ObjectScript例程(routines) - InterSystems SQL脚本 - Productions # 创建和执行单元测试套件 以下是创建和执行一套单元测试的基本步骤: 1. 创建一个(或多个)包含要测试的方法的类。 2. 创建扩展`%UnitTest.TestCase`的测试类(或多个测试类)。 3. 将方法添加到将测试方法输出的测试类。在每个方法中至少使用一个断言(`AssertX`宏)。每个测试方法名称都以`Test`开头。 4. 将测试类导出到文件。 5. 打开终端并切换到包含要测试的类的名称空间。为`^UnitTestRoot`分配一个字符串,该字符串包含包含导出的测试类文件的目录的父目录的路径。 6. 在终端中,运行`%UnitTest.Manager.RunTest`,向其传递包含测试类文件的(子)目录的名称。 7. 查看测试报告。终端中的输出包括网页的URL,该网页以易于阅读的表格形式显示结果。 # %UnitTest类 此表描述了用于为InterSystems IRIS类和方法创建和执行单元测试的主要`%UnitTest`类。 - `TestCase` 扩展此类以创建包含测试方法的类。如果一个或多个`AssertX`方法返回`False`,则测试失败;否则测试通过。将使用关联的宏调用`AssertX`方法。这些方法和宏是: - `AssertEqualsViaMacro`-如果表达式相等,则返回`TRUE`。使用`$$$AssertEquals`宏调用。 - `AssertNotEqualsViaMacro`-如果表达式不相等,则返回`TRUE`。使用`$$$AssertNotEquals`宏调用。 - `AssertStatusOKViaMacro`-如果返回的状态代码为1,则返回`TRUE`。使用`$$$AssertStatusOK`宏调用。 - `AssertStatusNotOKViaMacro`-如果返回的状态码为0,则返回`TRUE`。使用`$$$AssertStatusNotOK`宏调用。 - `AssertTrueViaMacro`-如果表达式为TRUE,则返回TRUE。使用`$$$AssertTrue`宏调用。 - `AssertNotTrueViaMacro`-如果表达式不为TRUE,则返回`TRUE`。使用`$$$AssertNotTrue`宏调用。 - `AssertFilesSameViaMacro`-如果两个文件相同,则返回`TRUE`。使用`$$$AssertFilesSame`宏调用。 - `LogMessage`-将日志消息写入`^UnitTestLog`全局。使用`$$$LogMessage`宏调用。 - 设置和拆除条件的方法包括: - `OnBeforeOneTest`-紧接在测试类中的每个测试方法之前执行。 - `OnBeforeAllTests`-在测试类中的任何测试方法之前执行一次。 - `OnAfterOneTest`-在测试类中的每个测试方法之后立即执行。 - `OnAfterAllTests`-在测试类中的所有测试方法执行完毕后执行一次。 - `Manager` 使用此类启动测试。其方法包括: - `RunTest` -在目录中执行一个测试或一组测试。 - `DebugRunTestCase`-执行一个测试或一组测试,而不加载或删除任何测试类。 - `Report` 定义报告执行一个测试或一组测试的结果的网页。 # 断言方法和宏 单元测试的主要测试操作来自`AssertX`方法及其关联宏。将直接调用宏来测试方法的输出。宏测试方法是否为给定的输入创建所需的输出。只要`AssertX`宏返回`FALSE`(或以错误结束),包含它的测试就会失败。 在创建代码时,请计划将创建的单元测试以测试代码。在这里的示例中,已经创建了一个名为`TestMe`的类,其中包含一个名为`Add`的方法。现在想测试一下新的`TestMe`类,看看它是否工作。 以下命令运行`AssertEquals`宏以测试`Add`方法的输入`(2,2)`是否等于`4`。 ```java Do $$$AssertEquals(##class(MyPackage.TestMe).Add(2,2),4, "Test Add(2,2)=4") ``` `AssertEquals`宏比较两个值并接受三个参数: 1. `##class(MyPackage.TestMe).Add(2,2)`-第一个值是以`2,2`作为输入进行测试的方法。 2. `4`-第二个值。 3. `"Test Add(2,2)=4"`-写在结果页上的文本说明。(此参数不影响测试。如果不包含测试描述,该类将使用求值的表达式创建一个测试描述。) 以下是用于测试对象是否正确保存的`AssertStatusOK`宏的示例。 ```java Do $$$AssertStatusOK(contact.%Save(),"Saving a Contact") ``` 此`AssertStatusOk`宏计算方法返回的状态。如果为1,则测试通过。 1. `Contact.%Save`-返回状态代码的表达式。 2. `"Saving a Contact"` -文本说明。这是测试报告的文档。这不会影响测试。 # 创建要在示例中使用的类 要完成以下动手示例,请使用`Atelier`创建以下类:`MyPackage.TestMe`和`MyPackage.Contact`。 - `MyPackage.TestMe` ```java Class MyPackage.TestMe Extends %RegisteredObject { ClassMethod Add(arg1 As %Integer, arg2 As %Integer) As %Integer { Return arg1 + arg2 } ClassMethod CreateContact(name As %String, type As %String) As MyPackage.Contact { Set contact = ##class(MyPackage.Contact).%New() Set contact.Name=name Set contact.ContactType=type Return contact } ClassMethod GetContactsByType(type As %String) As %ListOfObjects { Set list=##class(%Library.ResultSet).%New() } } ``` - `MyPackage.Contact` ```java Class MyPackage.Contact Extends (%Persistent, %Populate, %XML.Adaptor) { /// 描述联系的性质:: Personal or Business Property ContactType As %String(TRUNCATE = 1, VALUELIST = ",Business,Personal"); /// 表示联系人的姓名 Property Name As %String(POPSPEC = "Name()", TRUNCATE = 1) [ Required ]; Query ByContactType(type As %String) As %SQLQuery(CONTAINID = 1) { SELECT %ID FROM Contact WHERE (ContactType = :type) ORDER BY Name } Storage Default { %%CLASSNAME ContactType Name ^MyPackage.ContactD ContactDefaultData ^MyPackage.ContactD ^MyPackage.ContactI ^MyPackage.ContactS %Storage.Persistent } } ``` # 示例:创建并导出测试类 类`MyPackage.TestMe`包含一个名为`Add`的方法,该方法将两个整数相加。在此示例中,将创建并运行单元测试以检查`Add`方法是否正确地将两个整数相加。 创建将包含单元测试的测试类。以下是方法: 1. 使用Atelier在`MyPackage`包中创建名为`Tests`的新类。测试必须扩展`%UnitTest.TestCase`。 2. 添加以下名为`TestAdd`并编译测试的方法: ```java Class MyPackage.Tests Extends %UnitTest.TestCase { Method TestAdd() { do $$$AssertEquals(##class(MyPackage.TestMe).Add(2,2),4, "Test Add(2,2)=4") do $$$AssertNotEquals(##class(MyPackage.TestMe).Add(2,2),5,"Test Add(2,2)'=5") } } ``` 3. 将类测试导出到单元测试目录中的XML文件。如果尚未创建测试目录,请创建一个。此示例使用 `C:\unittests\mytests\。 a. 在Atelier中,单击文件>导出。 b. 在“Atelier ”下,单击“旧版XML文件”。单击下一步 c. 选择项目`Test.cls`和`c:\unittests\mytests\` 目录。 d. 单击Finish(完成)。 e. Atelier将测试类导出到`C:\unittests\mytests\cls\MyPackage`。 注意,目录名(在本例中为`mytest`)是一套测试的名称,也是`^UnitTestRoot`指定的目录的子级。运行`Manager.RunTest(“mytest”)`运行存储在`mytest`目录中的所有测试。 注意:还可以将测试类导出为`.cls`文件,而不是`XML`文件。也可以简单地从Atelier工作区复制它们,而不是导出它们。 # [源码](https://download.csdn.net/download/yaoxin521123/18703118)
文章
Qiao Peng · 一月 30, 2022

多语言字符集系列文章-- 第二篇 各种技术对字符集使用的声明

各种技术在交换数据的时候,就需要知道对方给的数据使用什么字符集和字符编码,否则很可能就解码错了。这里列举了医疗行业常见的数据交换技术方式和它们对字符集使用的声明方式。 2.1 文件 文件是字符型数据最常见的交换方式,文本编辑工具通常在保存时都会让用户选择保存成什么字符编码。对于不同的字符编码,文件是如何保存的呢? 通常会在文件头使用字节顺序标志(BOM,Byte Order Mark)来标记文件的编码。下表是常见的编码格式对应的BOM,注意ANSI并不需要BOM,我把它列在这里的目的是希望一目了然。 字节 编码格式 00 00 FE FF UTF-32, big-endian FF FE 00 00 UTF-32, little-endian FE FF UTF-16, big-endian FF FE UTF-16, little-endian EF BB BF UTF-8 空 ANSI 例如,汉字的“中”的各种编码如下: 用Windows的写字板软件将这个汉字分别保存成UTF-8、ANSI、Unicode编码,在UltraEdit打开其16进制模式查看,就可以看到如下的输出: 这里ANSI其实保存的是GBK码(使用GBK代码页936),Unicode其实保存的是UTF16编码。试图以ANSI保存文件时,对超出了GBK编码范围的汉字,Windows写字板软件会提示包含Unicode文字,以ANSI保存会丢失数据,如下图: 对于ANSI保存的包含汉字的文件,代码页信息并不在文件里。并不是所有的文本编辑器和文字处理代码都能正确解析这样的文件,因为它们并不知道代码页。这是可能造成文件中文乱码的一个原因。 另外,前面提到通过BOM可以确定文件编码方式,但并不是所有的文件都使用了BOM。因此特定的文本编辑器和文字处理代码对中文都可能产生显示乱码。 2.2 HTTP 对于HTTP消息内容,包括SOAP、RESTful,浏览器/客户端和服务器怎么知道字符编码呢? HTTP头的Content-Type可以通过参数charset指定文字编码。例如: Content-Type: text/html; charset = UTF-8 如果没有正确配置charset,就可能产生乱码。例如网页表单提交的中文数据,服务器没有正确解码从而产生乱码。 不同的Web服务器都可以设置默认的charset,例如Apache可以修改httpd.conf文件,通过配置AddDefaultCharset指定默认字符集编码。 2.3 XML XML文件当然可以使用前面提到的文件编码方式设置,以HTTP传递的XML数据也可以使用HTTP的文字编码设置。同时,XML规范自己也定义了文字编码声明的方式,从而保证通过任何方式传递(例如TCP)的XML都可以被正确解析。 XML定义的编码方式是设置encoding属性,例如: <?xml version="1.0" encoding="UTF-8"?> 注意,JSON并没有声明文字编码的设置。通常JSON数据都是通过HTTP传递的,因此使用HTTP的文字编码设置。 2.4 数据库连接 数据库连接也是造成文字乱码的一个重灾区。客户端从数据库服务器获取数据、向数据库服务器提交数据,怎么知道数据的文字编码呢? 和数据库相关的文字编码有2部分:数据库内码、数据库连接使用的字符编码。 数据库内码: 由于Unicode码历史并不悠久,数据库管理系统的历史远早于Unicode,直到最近这20年,数据库厂商才开始支持Unicode内码。当然,每个厂商的Unicode内码编码也不一样,例如InterSystems的Caché和IRIS是UTF-16格式,而Oracle是UTF-8和UTF-16。因为UTF-8处理效率低,大多数Unicode数据库都使用UTF-16。现在依然能看到不是Unicode内码的老版本数据库。 很多数据库相关的字符编码问题和数据库内码设置有关。例如国内不少的Oracle安装时没设置过字符编码,数据库内码NLS_CHARACTERSET默认为US7ASCII,客户端字符集NLS_LANG默认为AMERICAN_AMERICA.US7ASCII。这并不会造成中文无法保存,因为数据库只是将客户端的数据逐位保存下来,无论什么编码。但这样保存的中文数据只有Oracle自己的客户端能正常显示,其它的客户端可能就按ASCII处理从而造成乱码。因此正确设置数据库的内码很关键。 数据库连接使用的字符编码: ODBC: 直到ODBC 3.5标准(1997年)之前绝大多数的ODBC连接的函数调用和字符串编码都是ANSI(单字节或双字节),所以中文生僻字大多都会处理异常。 ODBC 3.5 规定ODBC驱动管理器要能够透明地处理Unicode和ANSI之间的转换,从而让ANSI和Unicode的数据库客户端都可以正确地向数据库获取和提交数据。 当然,并不是所有的Unicode字符都能转为ANSI码,例如中文“”字,所以ANSI的数据库客户端依然会遇到生僻字乱码。 JDBC: Java对字符串使用UTF-16编码,如果数据库内码也使用UTF-16,那么不会有乱码问题,例如InterSystems IRIS。但很多数据库并非使用UTF-16,这些数据库的JDBC驱动就需要支持UTF-16和数据库内码之间的转换。通常可以通过设置JDBC的连接字符串的特定属性来实现。 例如mysql的连接字符串中指定characterEncoding: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8 其它连接方式: 另外,很多数据库还提供XDBC之外的连接方式,这些连接方式有自己的字符编码逻辑。 2.5 HL7 V2 在医疗行业,HL7 V2是目前最为广泛使用的消息交换标准。它可以通过文件、TCP、HTTP、SOAP 等多种通道交换。因此,HL7 V2消息标准中,也有设置消息字符编码的字段:MSH-18。正确设置该字段有助于不同的系统正确地处理HL7 V2消息里的字符数据。 例如下面的HL7消息在MSH段设置其字符编码为UTF-8: MSH|^~\&||PHLS|||||ORU^R01|||2.5|||AL|||UTF-8| PID||S2345|S2345^^^PHLS^MR|C9876^^^COR^XX~S45008787^^^MA^DL|张^三||19301019|M|||1 Memorial Drive^^剑桥市^MA^02142||||||||063070516 PV1||O|||||ISCGP001^建国^李|||||||EO|||||HSVN00008|||||||||||||||||||||||||20200912090700|20200912090700 ORC||00265-001|0606:H00550R||||^^^202009120910||202009120910|||ISCGP001^建国^李|PHLS||||||||PHLS||||||||LAB OBR||00265-001|0606:H00550R|CBCD^血常规^L|||202009121049|||||||202009120937|Blood|ISCGP001^Moore^James||||||202009121227|||F OBX||NM|WBC^WHITE BLOOD CELL COUNT||6.24|10(9)/L|4.0-10.6||||F|||202009121049 OBX||NM|RBC^RED BLOOD CELL COUNT||4.99|10x12/L|4.5-5.9||||F|||202009121049 OBX||NM|HGB^HEMOGLOBIN||13.6|g/dL|12.0-16.0||||F|||202009121049 OBX||NM|HCT^HEMATOCRIT||41.6|Percent|36.0-46.0||||F|||202009121049 2.6 其它 在上面提到的和字符集相关的乱码之外,有时我们会混淆一些其它的、并非真正乱码的情况。下面这些中文显示的“乱码”,并非乱码: URL编码: 根据RFC 3986,如果URL的路径中有URL的保留字,就需要对URL路径中的保留字使用转义符% 进行转码,也叫做百分号编码。例如ASCII中下面的字符都需要转义: ! # $ & ' ( ) * + , / : ; = ? @ [ ] %21 %23 %24 %26 %27 %28 %29 %2A %2B %2C %2F %3A %3B %3D %3F %40 %5B %5D 对于非ASCII码字符,按其UTF-8编码字节顺序加%转义。例如“https://cn.community.intersystems.com/post/多语言字符集系列文章-第一篇-多语言字符集和相关标准简史”会被转义为: https://cn.community.intersystems.com/post/%E5%A4%9A%E8%AF%AD%E8%A8%80%E5%AD%97%E7%AC%A6%E9%9B%86%E7%B3%BB%E5%88%97%E6%96%87%E7%AB%A0-%E7%AC%AC%E4%B8%80%E7%AF%87-%E5%A4%9A%E8%AF%AD%E8%A8%80%E5%AD%97%E7%AC%A6%E9%9B%86%E5%92%8C%E7%9B%B8%E5%85%B3%E6%A0%87%E5%87%86%E7%AE%80%E5%8F%B2 其中%E5%A4%9A就是“多”字的UTF-8编码E5A49A的3个字节加上%。 在InterSystems技术平台上,可以用$ZCVT("%E5%A4%9A","I","URI")将URL编码翻译为中文字符,或使用$ZCVT("多","O","URI")将汉字进行URL编码。 HTML实体编码(转义): HTML实体编码(HTML Entity Encode)将字符编码为&开头;结尾的字符串,例如"<"编码为"<"。而中文字符就会被按其unicode编码进行转义,例如“广”字的unicode 10进制码为24191,所以它的HTML实体编码是"广"。在InterSystems技术平台上,可以用$ZCVT("广","I","HTML")将其翻译为中文字符。 Base64 编码: 当使用SOAP传递的数据是一个XML字符串或其它应被视为二进制类型的数据时,通常使用Base64对其进行编码,从而不会破坏XML结构解析。所以它也不是乱码。
文章
Jingwei Wang · 十二月 23, 2021

在大学使用IRIS(一项有趣的任务)

正如你在我的简介中看到的,我在一所大学教书,我想分享我对IRIS(或之前的Caché)教学的看法。 已经有一段时间了,但我还记得在今年早些时候看到YURI MARX GOMES关于 "用InterSystems对象和SQL开发 "一系列课程。他对第1天、第2天和第3天的课程内容进行了简要的描述,并附有讲师Joel Solon的评论。我心想,分享我自己的经验可能会有趣。 在我真正写下我教给学生的东西之前,先简单介绍一下我的经历。 在我毕业拿到硕士学位回到大学工作后,我们系决定更新我们的课程,在普通课程中增加几门新课程。其中一门是 "后关系型数据库"。它是为研究生第一年的学生讲授的。开始时,它包括72小时的讲座和72小时的实践。现在变为秋季学期72小时的讲座和36小时的实践。 由于我是一名新员工,而且是一名年轻有为的员工,我被赋予了讲授这门新学科的职责。我感到惊讶和惊恐是不言而喻的。首先,我根本没有任何教学方面的实践经验。其次,我只有夏天的三个月时间来学习一项对我来说完全陌生的技术并准备课程的讲授。幸运的是,我已经知道了应该教授哪个数据库。这个数据库就是InterSystems Caché。 总之,我或多或少地准备好了,然后我亲爱的学生们的问题开始了。例如,为什么他们必须学习这个数据库,他们在哪里以及如何使用这个数据库,等等。由于当时是2010年,我还很年轻,没有经验,而且这方面的书也不多,我决定直接去找源头,即InterSystems。不知怎的,我最终与Solon先生交谈,他给了我一些很好的提示,还把我介绍给了Evgeny Shvarov。从那时起,一切都变得更加容易和清晰了。 在接下来的几年里,我根据现代的趋势和要求,对我的课程内容做了相当多的改变。在开始的时候,为了展示如何从其他应用程序中利用这个数据库,我使用了Java绑定和.NET管理提供者。后来出现了eXTreme for Java,然后是eXTreme for .NET。ZEN在一两年后被教授和遗忘。现在是RESTful和SOAP服务以及CSP。很多东西都变了,但核心部分几乎是一样的。 既然我们在这里讨论的是讲授硕士水平的课程,我有很多的期望。首先,我希望我的学生知道面向对象的范式,并且能够绘制正确的UML类图。其次,我希望他们了解关系型数据库(包括索引、键等)和SQL。最后,他们应该至少有一个简单的网络开发的知识。 考虑到所有的先决条件,以下是我的教学大纲的大纲: 第1节 后关系型数据库概述:面向对象,对象关系型,以及不同种类的NoSQL数据库,并有模型和例子的描述。多模型数据库和例子。 第2节 架构、结构和管理IRIS的基础知识,作为后关系型DBMS的示例,IRIS管理的基础知识 : 数据库和命名空间一起工作。语法、命令、变量、表达式,以及ObjectScript的一些功能。还有用户代码、异常和事务。 第3节 分层模型 : 列表和多维数组(以及globals)。处理列表和数组的函数。这里是第一个任务--设计一个有4个下标层次的global,并使用至少4个函数与数组一起工作,对数据进行处理。 第4节 对象模型: 类,分层,继承,参数,不同类型的属性(以及如何使用它们),不同类型的方法(使用计算属性的例子,单元测试,使用Populate创建测试数据,用户数据类型),参数(以及如何使用它们)。数据是如何存储的,以及如何设置存储。这里的任务包括设计一个具有不同类型属性的类图(引用、内置对象、关系、集合和流);创建这些类和每个类的几个对象,并在IDE中把所有东西连接起来;创建一个计算属性、一个用户数据类型、单元测试以检查所有约束条件是否工作(如最小或最大长度/值、必需属性、唯一属性等)和生成测试数据。 第5节 关系模型:对象模型和关系模型之间的相关性。嵌入式(简单语句和游标)和动态SQL。类查询(基于SQL和COS)。隐式连接。IDKEY与其附带的方法。这里的任务是将不同类型的SQL查询通过join和参数添加到之前的任务中的类中,创建一个触发器,将唯一的属性改为IDKEY,看看它是如何改变相关的globals的。 第6节。从IRIS外部访问数据 :CSP和关于它的一切。RESTful服务和客户端(处理JSON的类)。SOAP服务和客户端(处理XML的类)。这里的任务是从之前的任务中选择一个类,并创建一个CSP页面,列出所选类的所有对象,并给出编辑、创建和删除对象的功能。然后使用RESTful和SOAP服务做同样的事情(创建方法来返回所有对象、一个特定的对象、更新和删除一个特定的对象)。 课程安排就这样了。然而,现在有了嵌入式Python,我可能会改用它,因为所有关于ObjectScript语法的问题和评论都很扰乱我的神经。 当然,COVID-19和学校的封锁给整个工作方式带来了一些变化。我认为,这些变化是最好的。而且,这真的取决于事情的组织方式。例如,其中一个好处是,现在我可以向学生展示知道我在整个学期所讲的所有内容的有用性。 一般来说,在学期末,他们有一份作业,来检查他们对课程主要部分的知识的掌握。以前,他们在课堂上写作业,我可以监督他们。之后,由于大流行病的发生非常意外,我不得不随机应变,为每个学生提供一套独特的任务。不幸的是,在那个时候,没有现成的软件,所以我决定写一个简单的网络门户来处理这个作业。由于现在已经过去了将近两年,我的简单门户变成了一个RESTful服务,从不同的集合中随机选择问题,将它们分配给学生,并接收答案。所有这些都非常现代化,而且易于使用(和编写) . 现在轮到了我现在的学生来写这个作业(使这个社区里有一些乐趣),你可以参加测试并发送你的答案, 当然,问题将与我的学生的问题不同(而且更容易)。我将使用InterSystems学习实验室的服务器,所以你只有几天的时间来做这件事并检查你的知识。 我在问题中使用的类图如下: 要获得这些问题,只需向以下地址发送一个带有你姓名的GET请求: http://52773-1-e5a0b608.labs.learning.intersystems.com/community/task/%3CYour_name> 你就会得到你的4套问题: 要发送答案,只需向以下地址发送一个POST请求: http://52773-1-e5a0b608.labs.learning.intersystems.com/community/answer/<Your_name> 在body中填写一个JSON: { "Answer1": "answer 1", "Answer2": "answer 2", "Answer3": "answer 3", "Answer4": "answer 4" } 我会在某个时候检查你的答案(因为有这个讨厌的东西叫做时差),你可以通过向以下地址发送GET请求来获得你的结果: http://52773-1-e5a0b608.labs.learning.intersystems.com/controlwork/marks/<Your_name> 如果分数是空的,说明我还没有时间去检查(或者我还没有起床)。分数如下。 0 分意味着答案是完全错误的。 1 分意味着答案或多或少是正确的。 2 分意味着答案是正确的。 第一个发送答案的人将得到我的一个虚拟拥抱(或者一个真正的拥抱,如果我们见面的话) 总之,如果你对我的课程有任何意见或问题,不要犹豫,请在评论区写出来。 查看原帖 由 @Irene.Mikhaylova 撰写 第1天、第2天和第3天的课程,这三篇文章都有中文的,请换成中文社区链接,谢谢!
文章
姚 鑫 · 七月 5, 2021

第二十八章 定制SAX解析器创建自定义内容处理程序

[toc] # 第二十八章 定制SAX解析器创建自定义内容处理程序 # 创建自定义内容处理程序 如果直接调用InterSystems IRIS SAX解析器,则可以根据自己的需要创建自定义内容处理程序。本节讨论以下主题: - Overview - 要在内容处理程序中自定义的方法的描述 - `%XML.SAX.Parser`类中解析方法的参数列表摘要 - 示例 ## 创建自定义内容处理程序概述 要定制InterSystems IRIS SAX解析器导入和处理XML的方式,请创建并使用定制的SAX内容处理程序。具体地说,创建`%XML.SAX.ContentHandler`的子类。然后,在新类中,重写任何默认方法以执行所需的操作。在解析XML文档时使用新的内容处理程序作为参数;为此,需要使用`%XML.SAX.Parser`类的解析方法。 此操作如下图所示: ![image](/sites/default/files/inline/images/tu_pian__6.png) 创建和使用自定义导入机制的过程如下: 1. 创建扩展`%XML.SAX.ContentHandler`的类。 2. 在该类中,包括希望覆盖的方法,并根据需要提供新定义。 3. 在使用`%XML.SAX.Parser`的分析方法之一(即`ParseFile()`、`ParseStream()`、`ParseString()`或`ParseURL()`)编写读取XML文档的类方法。 调用分析方法时,请将自定义内容处理程序指定为参数。 ## SAX内容处理程序的可定制方法 `%XML.SAX.ContentHandler`类在特定时间自动执行某些方法。通过覆盖它们,您可以自定义内容处理程序的行为。 ### 响应事件 `%XML.SAX.ContentHandle`类分析XML文件,并在它到达XML文件中的特定点时生成事件。根据事件的不同,会执行不同的方法。这些方法如下: - `OnPostParse()` — 在XML解析完成时触发。 - `characters()` — 由字符数据触发。 - `comment()` — 注释触发 - `endCData()` —由CDATA部分的末尾触发。 - `endDocument()` —由文档结尾触发。 - `endDTD()` — 由DTD结束触发。 - `endElement()` —由元素的末尾触发。 - `endEntity()` — 由一个实体的终结触发。 - `endPrefixMapping()` — 由名称空间前缀映射的结束触发。 - `ignorableWhitespace()` — 由元素内容中的可忽略空格触发。 - `processingInstruction()` — 由XML处理指令触发。 - `skippedEntity()` — 被跳过的实体触发。 - `startCData()` —由CDATA部分的开头触发。 - `startDocument()` — 由文档的开头触发。 - `startDTD()` — 由DTD的开头触发。 - `startElement()` — 由元素的开始触发。 - `startEntity()` — 由一个实体的开始触发。 - `startPrefixMapping()` — 由名称空间前缀映射的开始触发。 默认情况下,这些方法是空的,可以在自定义内容处理程序中覆盖它们。 ### 处理错误 `%XML.SAX.ContentHandler`类在遇到某些错误时也会执行方法: - `error()` — 由可恢复的解析器错误触发。 - `fatalError()` — 由致命的XML解析错误触发。 - `warning()` — 由解析器警告通知触发。 默认情况下,这些方法为空,可以在自定义内容处理程序中重写它们。 ### 计算事件掩码 当调用InterSystems IRIS SAX解析器(通过`%XML.SAX.Parser`类)时,可以指定一个掩码参数来指示哪些回调是感兴趣的。如果未指定掩码参数,解析器将调用内容处理程序的`Mask()`方法。此方法返回一个整数,该整数指定与内容处理程序的重写方法相对应的复合掩码。 例如,假设创建了一个自定义内容处理程序,其中包含`startElement()`和`endElement()`方法的新版本。在本例中,`Mask()`方法返回一个数值,该数值等于`$$$SAXSTARTELEMENT`和`$$$SAXENDELEMENT`,之和,这两个标志对应于这两个事件。如果没有为解析方法指定掩码参数,则解析器将调用内容处理程序的`Mask()`方法,因此只处理这两个事件。 ### 其他有用的方法 `%XML.SAX.ContentHandler`类提供在特殊情况下有用的其他方法: - `LocatePosition()`-通过引用返回两个参数,这两个参数指示解析的文档中的当前位置。第一个表示行号,第二个表示行偏移。 - `PushHandler()`-在堆栈上推送新的内容处理程序。SAX的所有后续回调都将转到这个新的内容处理程序,直到该处理程序完成处理。 如果在解析一种类型的文档时遇到想要以不同方式解析的一段XML,则可以使用此方法。在本例中,当检测到要以不同方式处理的段时,调用`PushHandler()`方法,该方法将创建一个新的内容处理程序实例。所有回调都会转到此内容处理程序,直到调用`PopHandler()`返回上一个内容处理程序。 - `PopHandler()`-返回堆栈上的上一个内容处理程序。 这些是`final`方法,不能重写。 ## SAX解析方法的参数列表 要指定文档源,请使用`%XML.SAX.Parser`类的`ParseFile()`、`ParseStream()`、`ParseString()`或`ParseURL()`方法。在任何情况下,源文档都必须是格式良好的XML文档;也就是说,它必须遵守XML语法的基本规则。完整的参数列表按顺序如下: 1. pFilename, pStream, pString, or pURL — 文档源. 2. pHandler — 内容处理程序,它是`%XML.SAX.ContentHandler`类的实例。 3. pResolver — 分析源时使用的实体解析器。 4. pFlags — 用于控制SAX解析器执行的验证和处理的标志。 5. pMask — 用于指定XML源中感兴趣的项的掩码。通常不需要指定此参数,因为对于`%XML.SAX.Parser`的解析方法,默认掩码为`0`。这意味着解析器调用内容处理程序的`Mask()`方法。该方法通过检测(在编译期间)在事件处理程序中自定义的所有事件回调来计算掩码。只处理那些事件回调。 6. pSchemaSpec — 验证文档源所依据的架构规范。此参数是一个字符串,其中包含以逗号分隔的命名空间/URL对列表: ``` "namespace URL,namespace URL" ``` 这里,`Namespace`是用于模式的XML名称空间,`URL`是提供模式文档位置的`URL`。名称空间和`URL`值之间有一个空格字符。 7. pHttpRequest (For the ParseURL() method only) — 这里,`Namespace`是用于模式的XML名称空间,URL是提供模式文档位置的URL。名称空间和URL值之间有一个空格字符。 8. pSSLConfiguration — 客户端`SSL/TLS`配置的配置名称。 注意:请注意,此参数列表与`%XML.TextReader`类的解析方法略有不同。有一点不同,`%XML.TextReader`不提供指定自定义内容处理程序的选项。 ## SAX处理程序示例 想要一个文件中出现的所有XML元素的列表。要做到这一点,只需记录每个开始元素。那么这个过程是这样的: 1. 创建一个名为`MyApp.Handler`的类,它扩展`%XML.SAX.ContentHandler`: ``` Class MyApp.Handler Extends %XML.SAX.ContentHandler { } ``` 2. 使用以下内容覆盖`startElement()`方法: ``` Class MyApp.MyHandler extends %XML.SAX.ContentHandler { // ... Method startElement(uri as %String, localname as %String, qname as %String, attrs as %List) { //we have found an element write !,"Element: ",localname } } ``` 3. 将一个类方法添加到读取和分析外部文件的Handler类: ``` Class MyApp.MyHandler extends %XML.SAX.ContentHandler { // ... ClassMethod ReadFile(file as %String) as %Status { //create an instance of this class set handler=..%New() //parse the given file using this instance set status=##class(%XML.SAX.Parser).ParseFile(file,handler) //quit with status quit status } } ``` 请注意,这是一个类方法,因为它在应用程序中被调用以执行其处理。此方法执行以下操作: 1. 它创建内容处理程序对象的实例: ``` set handler=..%New() ``` 2. 它在一个调用`%XML.SAX.Parser`的`ParseFile()`方法。这将验证并解析文档(由`fileName`指定),并调用内容处理程序对象的各种事件处理方法: ``` set status=##class(%XML.SAX.Parser).ParseFile(file,handler) ``` 每次在解析器解析文档时发生事件(如开始或结束元素)时,解析器都会调用内容处理程序对象中的适当方法。在本例中,唯一被覆盖的方法是`startElement()`,它随后写出元素名称。对于其他事件,例如到达`End`元素,不会发生任何事情(默认行为)。 3. 当`ParseFile()`方法到达文件末尾时,它返回。处理程序对象超出作用域,并自动从内存中删除。 4. 在应用程序中的相应点,调用`ReadFile()`方法,将文件传递给解析: ``` Do ##class(Samples.MyHandler).ReadFile(filename) ``` 其中,filename是正在读取的文件的路径。 例如,如果文件的内容如下: ``` Edwards,Angela U. 1980-04-19 K8134 Vail 94059 Uberoth,Wilma I. Wells,George H. ``` 则此示例的输出如下所示: ``` Element: Root Element: Person Element: Name Element: DOB Element: GroupID Element: HomeAddress Element: City Element: Zip Element: Doctors Element: Doctor Element: Name Element: Doctor Element: Name ``` # 使用HTTPS `%XML.SAX.Parser`支持`HTTPS`。也就是说,可以使用此类执行以下操作: - (对于`ParseURL()`)解析`HTTPS`位置提供的XML文档。 - (对于所有解析方法)解析`HTTPS`位置的实体。 在所有情况下,如果这些项目中的任何一个是`在HTTPS`位置上提供的,请执行以下操作: 1. 使用管理门户创建包含所需连接详细信息的`SSL/TLS`配置。这是一次性的步骤。 2. 调用`%XML.SAX.Parser`的适用解析方法时,请指定`pSSLConfiguration`参数。 默认情况下,InterSystems IRIS使用`Xerces`图元解析。`%XML.SAX.Parser`仅在以下情况下使用其自己的实体解析: - `PSSLConfiguration`参数非空。 - 已配置代理服务器。
文章
Hao Ma · 三月 26, 2021

使用 IntegratedML 和 Sapphire 进行机器学习

目前,机器学习的使用过程仍然有些困难,需要消耗大量的数据科学家服务。 AutoML 技术的诞生便是为了帮助组织降低这种复杂性和对专业 ML 人员的依赖。 AutoML 允许用户指向一个数据集,选择感兴趣的主题(特征)并设置影响主题的变量(标签)。 接着,用户告知模型名称,然后基于机器学习创建预测或数据分类模型。 这不需要了解认知算法,因为 AutoML 会在内部执行市面上的主要算法,并指出预测或分类数据的最佳算法。 用户现在可以从经过训练的模型中指向新数据,轻松进行预测和分类。 新的 InterSystems IRIS Advanced Analytics 除了与非常著名的 H2O 或 DataRobot(作为附加选项)搭配使用之外,还具有 AutoML 引擎 IntegratedML。 AutoML 运算通过 SQL 命令执行,详见:https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GIML 这就是 Sapphire 的用武之地。 它是 Open Exchange上的一个 Web 应用 (https://openexchange.intersystems.com/package/SAPPHIRE),允许连接到 IRIS Advanced Analytics,以可视方式创建和训练 IntegratedML 模型,抽象化在数据库命令行上交互的需求。 示例如下: 1) 按照 https://openexchange.intersystems.com/package/integratedml-demo-template 的说明运行 AutoML 环境。 2) 按照 https://openexchange.intersystems.com/package/SAPPHIRE 的说明运行 Sapphire 实例。 3) 访问 localhost: 8080(或在 docker 上配置的端口),输入问题数据,如下图所示: ![](/sites/default/files/inline/images/images/image(676).png) 4) 按 Save 按钮保存模型。 按 Test 按钮测试连接。 5) 在 AutoML 菜单顶部选择 Model Definition: ![](/sites/default/files/inline/images/images/image(677).png) 6) 在模型定义中,选择第 4 步中创建的问题,并按示例填写字段: ![](/sites/default/files/inline/images/images/image(678).png) 提示:要加载 IRIS 表,应填写 IRIS Schema 并点击 Get tables。 7) 点击 Save 按钮保存数据。 保存后,点击 Create Model 以在 IntegratedML 中创建 ML 模型。 8) 训练模型。 在 AutoML 菜单中选择 Train Model: ![](/sites/default/files/inline/images/images/image(679).png) 9) 选择模型,点击 Train Model,将模型训练到 IntegratedML。 示例: ![](/sites/default/files/inline/images/images/image(680).png) 10) 您的模型已经准备好预测新数据了!
文章
姚 鑫 · 二月 27, 2021

第四十九章 Caché 变量大全 ^$ROUTINE 变量

# 第四十九章 Caché 变量大全 ^$ROUTINE 变量 提供例程信息。 # 大纲 ```java ^$|nspace|ROUTINE(routine_name) ^$|nspace|R(routine_name) ``` ## 参数 - `|nspace|`或`[nspace]` 可选-扩展SSVN引用,可以是显式名称空间名称,也可以是隐含名称空间。必须计算为带引号的字符串,该字符串括在方括号(`[“nspace”]`)或竖线(`|“nspace”|`)中。命名空间名称不区分大小写;它们以大写字母存储和显示。 - routine_name 计算结果为包含例程名称的字符串的表达式。 # 描述 可以将`^$ROUTINE`结构化系统变量用作`$DATA`、`$ORDER`和`$QUERY`函数的参数,以从当前命名空间(默认)或指定命名空间返回例程信息。`^$ROUTINE`返回有关例程的OBJ代码版本的例程信息。 在InterSystems ObjectScript中,一个例程有三个代码版本:MAC(用户编写的代码,可能包括宏预处理器语句)、INT(编译的MAC代码,用于执行宏预处理)和OBJ(可执行目标代码)。可以使用`^$ROUTINE global`返回关于int代码版本的信息。可以使用`^$ROUTINE`返回有关OBJ代码版本的信息。 # 参数 ## nspace 此可选参数允许使用扩展SSVN引用在另一个命名空间中指定全局。可以显式地将命名空间名称指定为带引号的字符串文字或变量,也可以通过指定隐含的命名空间来指定。命名空间名称不区分大小写。可以使用方括号语法`[“user”]`或环境语法`|“user”|`。Nspace分隔符前后不允许有空格。 ```java WRITE ##class(%SYS.Namespace).Exists("USER"),! ; an existing namespace WRITE ##class(%SYS.Namespace).Exists("LOSER") ; a non-existent namespace ``` 可以使用`$NAMESPACE`特殊变量来确定当前名称空间。更改当前名称空间的首选方式是新建`$NAMESPACE`,然后设置`$NAMESPACE=“nspace ename”`。 ## routine_name 计算结果为包含现有例程名称的字符串的表达式。例程名称在前255个字符内必须是唯一的;应避免超过220个字符。 # 示例 以下示例使用`^$`例程作为`$DATA`、`$ORDER`和`$QUERY`函数的参数。 ## 作为$DATA的参数 `$DATA(^$|nspace|ROUTINE(routine_name))` `^$ROUTINE`作为`$DATA`的参数将返回一个整数值,该整数值指定例程名OBJ代码版本是否作为`^$ROUTINE`中的节点存在。下表显示了`$DATA`可以返回的整数值。 Value| Meaning ---|--- 0 |例程不存在 10 |例程存在 下面的Terminal示例测试myrou例程的OBJ代码版本是否存在。此示例假定在`USER`名称空间中有一个名为myrou的已编译MAC例程: ```java USER>WRITE ^ROUTINE("myrou",0,"GENERATED") // INT code version exists 1 USER>WRITE $DATA(^$ROUTINE("myrou")) // OBJ code version exists 1 USER>KILL ^rOBJ("myrou") // Kills the OBJ code version USER>DO ^myrou DO ^myrou ^ *myrou USER>WRITE ^ROUTINE("myrou",0,"GENERATED") // INT code version exists 1 USER>WRITE $DATA(^$ROUTINE("myrou")) // OBJ code version does not exist 0 USER> ``` ## 作为`$ORDER`的参数 `$ORDER(^$|nspace|ROUTINE( routine_name),direction)` `^$ROUTINE`作为`$ORDER`的参数,按整理顺序返回指定的例程名称的下一个或上一个例程名称。如果在`^$ROUTINE`中没有这样的例程名称作为节点存在,则`$ORDER`返回空字符串。 direction参数指定是否返回下一个或上一个例程名称:1 =下一个,-1 =上一个。如果不提供方向参数,则InterSystems IRIS将按整理顺序将下一个例程名称返回到指定的名称。 以下子例程搜索USER名称空间,并将例程名称存储在名为ROUTINE的本地数组中。 ```java /// d ##class(PHA.TEST.SpecialVariables).ROUTINE() ClassMethod ROUTINE() { SET rname="" FOR I=1:1 { SET rname=$ORDER(^$|"USER"|ROUTINE(rname)) QUIT:rname="" SET ROUTINE(I)=rname WRITE !,"Routine name: ",rname } WRITE !,"All routines stored" QUIT } ``` ```java Routine name: INFORMATION.SCHEMA.TABLECONSTRAINTS.1 Routine name: INFORMATION.SCHEMA.TABLES.0 Routine name: INFORMATION.SCHEMA.TABLES.1 Routine name: INFORMATION.SCHEMA.TRIGGERS.0 Routine name: INFORMATION.SCHEMA.TRIGGERS.1 Routine name: INFORMATION.SCHEMA.VIEWCOLUMNUSAGE.0 Routine name: INFORMATION.SCHEMA.VIEWCOLUMNUSAGE.1 Routine name: INFORMATION.SCHEMA.VIEWS.0 Routine name: INFORMATION.SCHEMA.VIEWS.1 Routine name: INFORMATION.SCHEMA.VIEWTABLEUSAGE.0 Routine name: INFORMATION.SCHEMA.VIEWTABLEUSAGE.1 All routines stored ``` ## 作为$QUERY的参数 `$QUERY(^$|nspace|ROUTINE(routine_name))` `^$ROUTINE`作为`$QUERY`的参数,按整理顺序将下一个例程名称返回到指定的例程名称。指定的例程名称不必存在。如果以后在排序序列中没有例程名称,则`$QUERY(^$ROUTINE)`返回一个空字符串。 在下面的示例中,两个`$QUERY`函数在`USER`名称空间中指定例程名称之后返回下一个例程。 ```java /// d ##class(PHA.TEST.SpecialVariables).ROUTINE1() ClassMethod ROUTINE1() { SET rname="" WRITE !,"1st routine: ",$QUERY(^$|"USER"|ROUTINE(rname)) SET rname="%m" WRITE !,"1st ",rname, " routine: ",$QUERY(^$|"USER"|ROUTINE(rname)) QUIT } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ROUTINE1() 1st routine: ^$|"USER"|ROUTINE("%APILIB") 1st %m routine: ^$|"USER"|ROUTINE("%mgw1") ``` 能够理解总结知识,但是结合到场景不知道如何使用。 知识讲的很仔细全面,GET了
文章
姚 鑫 · 七月 22, 2021

关键字触发器定义,扩展数据块,类关键字Abstract,ClassType

# 第九章 触发器定义 描述触发器定义的结构。 # 介绍 触发器是在SQL中发生特定事件时执行的代码段。InterSystems IRIS支持基于执行`INSERT`、`UPDATE`和`DELETE`命令的触发器。根据触发器定义,指定的代码将在相关命令执行之前或之后立即执行。每个事件可以有多个触发器,只要它们被分配了执行顺序。 可以向持久类添加触发器定义。它们在其他类中没有意义。 # 详情 触发器定义具有以下结构: ```java /// description Trigger name [ keyword_list ] { implementation } ``` - `description` 描述(可选)旨在显示在“类参考”中。默认情况下,描述为空白。 - `name`(必需)是触发器的名称。这必须是有效的类成员名称,并且不能与任何其他类成员名称冲突。 - `keyword_list`(必需)是以逗号分隔的关键字列表,用于进一步定义触发器。 - `implementation` 实现(必需)是零行或多行ObjectScript代码,用于定义触发触发器时要执行的代码。 # 示例 ```java /// 此触发器在每次插入后更新日志表 Trigger LogEvent [ Event = INSERT, Time = AFTER ] { // 获取插入行的行id NEW id SET id = {ID} // 将值插入日志表 &sql(INSERT INTO LogTable (TableName, IDValue) VALUES ('MyApp.Person', :id)) } ``` # 第十章 扩展数据块 描述XData块的结构。 # 介绍 `XData`块是包含在类定义中的命名数据单元,通常由类中的方法使用。最常见的情况是,它是一个XML文档,但是它可以由其他形式的数据组成,例如`JSON`或`YAML`。 # 详情 `XData`块具有以下结构: ```java /// description XData name [ keyword_list ] { data } ``` - `description` 描述(可选)旨在显示在“类别参考”中。默认情况下,描述为空白。 - `name`(必需)是`XData`块的名称。这必须是有效的类成员名称,并且不能与任何其他类成员名称冲突。 - `data` 数据(可选)包含扩展数据块的有效载荷。如果是XML,则它必须是格式良好的文档(只有一个根元素),开头没有XML声明。 - `keyword_list`(可选)是以逗号分隔的关键字列表,进一步定义了`XData`块。如果省略此列表,也要省略方括号。 # 示例 ```java Class Demo.CoffeeMakerRESTServer Extends %CSP.REST { Parameter HandleCorsRequest = 1 XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ] { } ``` # 第十一章 类关键字 - Abstract 指定这是否是抽象类。 # 用法 要将类标记为抽象类,请使用以下语法: ```java Class MyApp.MyClass [ Abstract ] { //class members } ``` 否则,省略此关键字或将单词Not放在关键字的前面。 # 详解 如果一个类是抽象的,就不能创建它的实例。 # 对子类的影响 此关键字不是继承的。 # 默认 如果省略这个关键字,这个类就不是抽象的。 # 第十二章 类关键字 - ClassType 指定此类的类型(或行为)。 # 用法 要指定类的类型(如果需要),请使用以下语法: ```java Class MyApp.MyClass [ ClassType = classtype ] { //class members } ``` 其中`classtype`是下列之一: - `datatype` — 该类是一个数据类型类,用于表示文字值。 - `persistent` 持久—该类表示要存储在数据库中的数据。 - `serial` —该类表示要存储在另一个持久对象中(处于序列化状态)的数据。 - `stream` —该类表示流数据。 - `view`视图—该类用于定义一个SQL视图。 - `index` —该类是一个索引类,一个定义索引接口的专用类。 - 空字符串,表示此类没有特定类型。抽象类通常不指定类类型。 如果未指定此关键字,则类类型从主超类继承(如果有)。 请注意,`ClassType`是为`%RegisteredObject`、`%SerialObject`、`%Persistent`和数据类型类等系统类指定的,因此如果对这些类进行子类化,通常不需要指定此关键字。 # 详解 此关键字指定如何使用此类。类别编译器使用类别类型关键字来决定如何编译类别。例如,如果`ClassType`是持久性的,则类编译器还会调用存储编译器来为类生成持久性代码。除非明确定义,否则`ClassType`的值要么是默认值,要么是从主超类继承而来的。 对于持久性类,只有在标准持久性行为被重写时,才需要显式的`ClassType`语句。如果一个类定义包含这样的语句,要么是因为开发人员指定了它,要么是因为这个类起源于用旧版本的InterSystems IRIS开发的代码。 # 对子类的影响 这个关键字是从主超类继承的。子类可以覆盖关键字的值。 # 默认 如果省略此关键字,类类型将从主超类继承(如果有)。 注意:分片类的类类型不能有持久以外的任何值。
文章
Weiwei Gu · 五月 4, 2023

在 Docker 中配置镜像

我们客户的一个共同需求是配置 HealthShare HealthConnect 和 IRIS的高可用性模式。 市场上的其他集成引擎通常被宣传为具有“高可用性”配置,但事实并非如此。通常,这些解决方案与外部数据库一起使用,因此,如果这些数据库未配置为高可用性,当发生数据库崩溃或与它的连接丢失时,整个集成工具将变得不可用。 对于 InterSystems 解决方案,这个问题不存在,因为数据库是工具本身的一部分和核心。 InterSystems 如何解决高可用性问题?深奥的配置会把我们拖入异化和疯狂的漩涡?不!在 InterSystems,我们倾听并处理了您的投诉(正如我们一直努力做的那样 ;)),并且我们已将镜像功能提供给所有用户和开发人员。 镜像 镜像如何工作?这个概念本身非常简单。如您所知,IRIS 和 HealthShare 都使用一个日志系统,该系统记录每个实例的数据库上的所有更新操作。这个日志系统是后来帮助我们在崩溃后恢复实例而不会丢失数据的系统。好吧,这些日志文件在镜像中配置的实例之间发送,允许并保持镜像中配置的实例永久更新。 架构 让我们简要解释一下在 Mirror 中配置的系统架构是什么样的: 在故障转移模式下配置的两个实例: 主动节点——接收所有常规的读/写操作。 被动节点:在读取模式下,它同步接收主动节点产生的任何变化。 0-14个异步实例:你可以使用多个异步实例,它们可以是两种类型: DR 异步(灾难恢复):处于读取模式的节点不是故障转移的一部分,尽管它们可以被手动提升成故障转移节点。如果是这样,它们可以在其他两个故障转移节点发生故障时自动提升为主节点。您的数据更新是异步的,所以不能保证其数据是最新的。 报告异步:异步更新节点,用于 BI 任务或数据挖掘。它们不能升级为故障转移,因为可以对数据执行写入。 ISCAgent:安装在每个实例所在的服务器上。它将负责监视所述服务器实例的状态。这是镜像服务器之间除了直接通信之外的另一种通信方式。 Arbiter:它是一个独立于构成镜像的服务器安装的 ISCAgent,并允许通过监视安装的 ISCAgent 和 IRIS/HealthShare 实例来提高安全性和内部故障转移的控制。它的安装不是必须的。 这是一个由只有两个节点的故障转移形成的镜像的操作: 先前的警告 与本文相关的项目没有允许配置镜像的活动许可证。如果你想尝试,直接给我发邮件或者在文末添加评论,我会联系你。 在 Docker 中部署 对于本文,我们将在 Docker 中建立一个小项目,允许我们设置 2 个故障转移实例和一个Arbiter 。默认情况下,可用于 Docker 的 IRIS 映像已经安装并配置了 ISCAgent,因此我们可以跳过该步骤。有必要配置与来自 Visual Studio Code 的文章关联的项目,因为这将使我们以后可以更轻松地使用服务器文件。 让我们看看我们的 docker-compose.yml 会有什么形式: version: '3.3' services: arbiter: container_name: arbiter hostname: arbiter image: containers.intersystems.com/intersystems/arbiter:2022.1.0.209.0 init: true command: - /usr/local/etc/irissys/startISCAgent.sh 2188 mirrorA: image: containers.intersystems.com/intersystems/iris:2022.1.0.209.0 container_name: mirrorA depends_on: - arbiter ports: - "52775:52773" volumes: - ./sharedA:/shared - ./install:/install - ./management:/management command: --check-caps false --key /install/iris.key -a /install/installer.sh environment: - ISC_DATA_DIRECTORY=/shared/durable hostname: mirrorA mirrorB: image: containers.intersystems.com/intersystems/iris:2022.1.0.209.0 container_name: mirrorB depends_on: - arbiter - mirrorA ports: - "52776:52773" volumes: - ./sharedB:/shared - ./install:/install - ./management:/management command: --check-caps false --key /install/iris.key -a /install/installer.sh environment: - ISC_DATA_DIRECTORY=/shared/durable hostname: mirrorB 可以看到我们定义了3个容器: Arbiter :它对应于将被部署以控制将形成 Mirror Failover 的 IRIS 实例的 ISCAgent(即使图像称为 Arbiter)。当启动容器时,它将执行一个 shell 文件,该文件将启动侦听容器端口 2188 的 ISCAgent。 mirrorA :将部署 IRIS v.2022.1.0.209 映像的容器,稍后我们将其配置为主故障转移节点。 mirrorB :将部署 IRIS v.2022.1.0.209 镜像的容器,稍后我们将其配置为辅助故障转移节点。 当我们执行docker-compose up -d命令时,定义的容器将部署在我们的 Docker 中,它在我们的 Docker 桌面中应该看起来像这样(如果我们从 Windows 执行此操作)。 镜像配置。 部署容器后,我们将继续访问我们将在镜像中配置的实例,第一个将在端口 52775 ( mirrorA ) 上侦听,第二个在 52776 ( mirrorB ) 上侦听。访问用户和密码将是superuser/ SYS 由于实例部署在 Docker 中,我们将有两个选项来配置我们服务器的 IP。第一种是在配置中直接使用我们容器的名称(这是最简单的方法)或检查 Docker 为每个容器分配的 IP(打开控制台并执行返回分配的 IP 的 ifconfig)。为了清楚起见,我们将在示例中使用我们为每个容器指定的名称作为 Docker 中每个容器的地址。 首先,我们将配置我们将用作故障转移活动节点的实例。在我们的例子中,它将是我们所说的mirrorA 。 第一步是启用镜像服务,因此我们将从管理门户访问镜像菜单: System Administration --> Configuration --> Mirror Settings --> Enable Mirror Service并标记Service Enabled检查: 启用服务后,我们可以开始配置我们的活动节点。启用该服务后,您将能够在镜像菜单中看到新选项已启用: 在这种情况下,由于我们还没有创建任何镜像配置,我们必须使用Create Mirror选项创建一个新配置。当我们访问这个选项时,管理门户将打开一个新窗口,我们可以从中配置我们的镜像: 让我们仔细看看每个选项: Mirror Name :我们将用来标识我们的镜像的名称。对于我们的示例,我们将其称为 MIRRORSET 需要 SSL/TLS :对于我们的示例,我们不会使用 SSL/TLS 配置连接,尽管在生产环境中,防止在实例之间没有任何类型的加密的情况下共享日志文件会比操作方便更重要。如果您有兴趣配置它,您可以在文档的以下URL中获得所有必要的信息。 使用 Arbiter :此选项不是强制性的,但强烈推荐,因为它为我们的镜像配置增加了一层安全性。对于我们的示例,我们将选中它并指示我们运行 Arbiter 的 IP。对于我们的示例,IP 将位于容器名称arbiter中。 用户虚拟 IP :在 Linux/Unix 环境中,此选项非常有趣,因为它允许我们为将由我们的镜像管理的活动节点配置虚拟 IP。此虚拟 IP 必须与故障转移节点属于同一子网。虚拟IP的操作非常简单,当主动节点出现故障时镜像会自动在待提升的被动节点所在的服务器上配置虚拟IP。这样,被动节点到主动节点的升级对用户来说将是完全透明的,因为他们将继续连接到同一个 IP,即使它将配置在不同的服务器上。如果您想了解有关虚拟 IP 的更多信息,可以查看文档的此URL 。 其余的配置可以保持原样。在屏幕右侧我们会看到镜像中这个节点的相关信息: Mirror Member Name :这个镜像成员的名称,默认情况下它将采用服务器的名称以及实例的名称。 超级服务器地址:这个节点的超级服务器 IP 地址,在我们的例子中是mirrorA 。 Agent Port :配置了该节点对应的ISCAgent的端口。默认为2188 。 配置必要的字段后,我们可以继续保存镜像。我们可以从镜像监视器(系统操作-->镜像监视器)检查配置情况。 完美,这里我们有了新配置的镜像。如您所见,只有我们刚刚创建的活动节点出现。很好,接下来让我们在故障转移中添加我们的被动节点。我们访问mirrorB管理门户并访问镜像设置菜单。正如我们已经为mirrorA实例所做的那样,我们必须启用镜像服务。我们重复该操作,一旦菜单选项更新,我们将选择Join as Failover 。 这里我们有镜像连接屏幕。让我们简要解释一下每个字段的含义: 镜像名称:我们在创建时为镜像指定的名称,在我们的示例中为MIRRORSET 。 Agent Address on Other System :部署主动节点ISCAgent的服务器IP,对我们来说就是mirrorA。 代理端口:我们创建镜像的服务器的ISCAgent监听端口。默认为2188 。 InterSystems IRIS 实例名称:主动节点上 IRIS 实例的名称。在这种情况下,它与被动节点IRIS的一致。 保存镜像数据后,我们将可以选择定义与我们正在配置的被动节点相关的信息。我们再来看看被动节点可以配置的字段: 镜像成员名称:被动节点将在镜像中使用的名称。默认情况下由服务器名称和实例组成。 超级服务器地址:被动节点中超级服务器的 IP 地址。在这种情况下mirrorB 。 代理端口:我们配置的被动节点服务器上安装的ISCAgent的监听端口。默认为2188 。 SSL/TLS 要求:在此示例中不可配置,我们不使用 SSL/TLS。 镜像私有地址:被动节点的 IP 地址。正如我们所见,在使用 Docker 时,我们可以使用容器名称mirrorB 。 代理地址:安装 ISCAgent 的服务器的 IP 地址。和以前一样, mirrorB 。 我们按照指示保存配置,然后返回到镜像监视器以验证我们是否已正确配置所有内容。我们可以将mirrorA中的主动节点和mirrorB中的被动节点的监视器可视化。让我们看看这两个实例之间的差异。 活动节点mirrorA上的镜像监视器: 被动节点mirrorB上的镜像监视器: 如您所见,显示的信息是相似的,基本上改变了故障转移成员的顺序。选项也不同,让我们看看其中的一些: 主动节点mirrorA : 设置无故障转移:防止在作为其中一部分的任何实例停止的情况下执行故障转移。 降级其他成员:从镜像配置中删除其他故障转移成员(在本例中为mirrorB )。 被动节点mirrorB : Stop Mirror On This Member :停止故障转移被动节点上的镜像同步。 降级为 DR 成员:将此节点从其实时同步故障转移的一部分降级为异步模式下的灾难恢复模式。 完美,我们已经配置了节点,现在让我们看看配置的最后一步。我们必须决定哪些表将成为镜像的一部分,并在两个节点上进行配置。如果您查看与本文相关的 Open Exchange 项目的 README.md,您将看到我们配置和部署了两个通常用于训练的应用程序。当我们启动 Docker 容器时,这些应用程序会自动部署,默认情况下会创建 NAMESPACES 和数据库。 第一个应用程序是COMPANY ,它允许我们保存公司记录,第二个应用程序是PHONEBOOK ,它允许我们添加与注册公司以及客户相关的个人联系人。 让我们添加一家公司: 现在让我们为之前的公司创建个人联系人: 公司数据将在COMPANY数据库中注册,联系人数据在PERSONAL中,这两个数据库都已映射,以便可以从命名空间 PHONEBOOK 访问它们。如果我们检查两个节点中的表,我们将看到在mirrorA中我们有公司和联系人的数据,但在mirrorB中仍然没有任何数据,这是合乎逻辑的。 mirrorA注册的公司: 好的,让我们继续在我们的镜像上配置数据库。为此,从我们的活动节点 ( mirrorA ),我们访问本地数据库管理屏幕(系统管理员-->配置-->系统配置-->本地数据库)并单击添加到镜像选项,我们必须选择从列表中我们要添加的所有数据库并从屏幕上读取消息: 一旦我们将数据库从主动节点添加到镜像中,我们必须对其进行备份或复制数据库文件 (IRIS.dat) 并将它们还原到被动节点上。如果您决定直接复制 IRIS.dat 文件,请记住您必须冻结要复制的数据库中的写入,您可以在文档的以下URL中查看必要的命令。在我们的例子中,没有必要暂停,因为除了我们之外没有人在写入数据。 在复制数据库文件之前,让我们从活动节点的监视器上检查镜像的状态: 让我们看看被动节点: 正如我们所看到的,从被动节点我们被告知,虽然我们在镜像中配置了 3 个数据库,但配置尚未完成。让我们继续将数据库从主动节点复制到被动节点,不要忘记我们必须卸载被动节点的数据库才能进行复制,为此我们将从管理门户访问系统配置—— >数据库并访问它们中的每一个,我们继续卸载它们。 完美的!卸载的数据库。让我们从 Visual Studio Code 访问与本文相关的项目代码,看看我们有 IRIS 安装所在的文件夹, sharedA用于mirrorA和sharedB用于mirrorB 。让我们访问 COMPANY、CUSTOMER 和 PERSONAL 数据库所在的文件夹 ( /sharedA/durable/mgr ),然后继续将镜像中每个数据库的 IRIS.dat 复制到 mirrorB 的相应目录 ( /sharedB/durable/mgr) ). 复制完成后,我们再次挂载mirrorB数据库并从mirrorB中的镜像监视器检查已配置数据库的状态: 答对了!我们的镜像已经识别出数据库,现在我们只需要激活和更新它们。为此,我们将点击Activate操作,然后点击Catchup ,这将在激活后出现。让我们看看他们是如何结束的: 完美,我们的数据库已经在镜像中正确配置,如果我们查询 COMPANY 数据库,我们应该看到我们之前从mirrorA注册的记录: 显然我们的COMPANY数据库中有我们之前在mirrorA中输入的记录,毕竟我们已经复制了整个数据库。让我们继续从mirrorA添加一个新公司,我们将其称为“另一家公司”,然后再次查询 COMPANY 数据库表: 在这里,我们完成了。我们只需要确保我们在镜像中配置的数据库对于被动节点mirrorB处于只读模式: 他们在那里!是只读模式R 。好吧,我们已经配置了镜像并同步了数据库。如果我们的产品正在运行,这不会成为问题,因为镜像会自动负责管理它们,并在主动节点出现故障时在被动节点中启动它们。 非常感谢大家走到这一步!它很长,但我希望你觉得它有用。
文章
Claire Zheng · 三月 14, 2023

医疗行业的生态创新:如何实现数据利用和应用创新

本文根据InterSystems中国技术总监乔鹏( @Peng.Qiao )的演讲“互联互通套件赋能数据利用与应用创新”整理而成。 IRIS医疗版互联互通套件的缘起与发展演进 来源HL7:正在到来的挑战 http://hl7.org/fhir/change.html 这是来自HL7官网上的一张图,描述了我们在医疗卫生行业面临的一些挑战,以及信息化建设在应对挑战中发挥的作用。当今,医疗卫生、生物学、信息技术有很强的融合趋势,加之社会变革带来的经济方面的需求,同时构成颠覆传统医疗卫生行业的因素。 这张图显示了从“被动医疗”转向“主动医疗”过程中信息的爆炸式增长,信息共享交换推动了我们对信息的利用,在这一进程中,医疗卫生信息化起着核心作用——而让信息更具价值,赋予信息标准化和互操作能力的过程,这也是InterSystems一直努力的方向,我们在国内支持大量医院实现了互联互通建设。在建设过程中,我们注意到项目的定量部分的建设成本占比是比较高的,很多的工作都花在了合规性和相关管理工具的开发上——应用标准的实施是有成本的,而对于标准的理解在各个项目上水平不尽相同,这就进一步影响了互联互通项目的建设成果。有鉴于此,2021年我们发布了InterSystems IRIS医疗版互联互通套件1.0版,初衷在于将我国的互联互通建设标准放到我们自己的技术平台里,为用户提供开箱即用的、标准合规性的基础和相应工具——这套工具包括了我们的医疗卫生信息模型、文档模型与管理、互联互通服务接口等,从而可以降低用户的实施成本和相应的实施风险。 在互联互通套件1.0版,我们提供了这样一些能力: · 在医疗卫生信息模型部分,我们提供了完整的卫生数据元与值集管理、数据集管理,并且提供了本地术语注册,以及针对于本地术语和标准术语之间的转换服务。 · 在文档部分,我们提供了电子病历文档模型和模型管理能力、电子病历文档的校验、数据源质量的分析,以及自动生成合规的互联互通文档。 · 在服务部分,我们提供了互联互通标准服务接口、互联互通标准消息模型、ESB服务总线,以及通用的服务发布/订阅功能。 我们知道,互联互通评测的目标之一就是“以评促用”,实现合规仅仅是第一步,我们希望可以为用户提供更多能力,让用户可以真正把符合互联互通标准的医院信息平台应用起来。于是我们继续发布了InterSystems IRIS医疗版互联互通套件2.0版。 传统数据中心建设面临的一个普遍问题是标准缺乏,导致数据中心的建设成本相对较高,而且复用性很差,很难形成生态。因为缺乏标准,所以数据治理、上面可以运行的数据应用,大多都是由数据厂商自己开发的,这就像早期不同的手机厂商提供的功能手机,就那么几个应用可以用,它不是智能手机,功能是非常有限的。 所以2.0版强调的是生态。在规划2.0版的时候,我们将重点放在了核心数据资产上——我们希望在2.0中建设、治理、保存核心数据资产,助力用户应用好数据资产,并且通过基于标准的开放能力,推动医疗卫生信息化的生态建设。 我们在2.0版打通了国内互联互通和FHIR两大生态,希望通过这种生态融合提升数据资产的价值。我们借助FHIR来治理数据资产,令互联互通消息和互联互通文档都能够转换成FHIR资源,直接保存在FHIR资源仓库里面。 我们使用FHIR资源模型来提供统一的行业语义,通过FHIR API,包括InterSystems提供的互联互通服务,向用户提供统一的、基于行业语义的互操作能力,这样一来,用户能够以生态的方式来扩展数据资产的利用,体现数据资产的核心价值。 我们也注意到,行业中的确已经有很多关于微服务架构的思考。在2.0版本上,我们还扩展了服务架构,新增了对新的应用开发架构——微服务架构——的支持,这对于微服务架构在行业中的落地肯定是有推动作用的。 此外,我们增加了全面的API网关能力,用户可以通过 API网关的形式,对访问互联互通的医院信息平台的这些客户端来进行流量控制、认证管理等,实现服务的全生命周期管理。 在2.0版中,我们还提供了一个完整的FHIR服务器,以打通FHIR生态与国内互联互通生态。在决策支持上,FHIR生态已经提供决策支持架构和利用机器学习的一些用例,我们也基于FHIR为决策者提供完整的、关联的、实时的决策要素,比如说通过底层互操作架构来提供整个流程闭环的基础,通过内置的机器学习、自然语言处理、商业智能等工具,为用户提供数据决策支持工具。 在平台监控方面,2.0版新增了互联互通监控指标,以及针对通用的互操作性的指标监控能力。 在实际工作中,我们注意到互联互通项目建成之后,有一些项目的建设成果并没有得到全面地利用。例如,根据最近一位北京专家的调研结果,很多用户在电子健康档案的相关项目建设上面花费了巨额经费,但是在数据调阅频次上面却“低得出人意料”——我想这是相当一部分项目的现状。 互联互通建设的成果是我们的医院信息平台,我们是以医院互联互通为标准来建设医院信息平台的,那么医院信息平台上的数据利用应该是我们的核心。 因此在3.0的版本里,我们希望以最佳实践的方式来展现如何充分利用我们的互联互通的生态,提升互联互通价值,通过向用户提供一些用例,抛砖引玉地引导用户来正确使用互联互通建设成果,真正发挥出数据的价值。我们规划了两类互联互通应用方向:一个是数据利用,另一个是应用创新。 数据利用可以有很多方向,例如数字孪生,我们可以通过更完整、更实时的数据来建立准确、实时的患者画像;例如数据编织(data fabric),我们可以把所有数据来源进行统一编织,建立一个跨数据来源的、统一的语义平台;例如在数据流通领域发挥核心价值。 在应用创新上,借助打通FHIR和互联互通发展生态,我们可以利用FHIR应用能力来建设互联互通的应用生态。例如在FHIR生态里有Smart on FHIR,这是一个全新的、即插即用的软件开发的架构;再如基于FHIR生态的CDS Hooks决策支持架构等。这些都是我们可以来参考、引进和学习的。 我借助两则近期的新闻来介绍一下如何利用互联互通建设生态来满足数据利用和应用创新。 “数据二十条”:打破数据垄断、实现数据要素价值 2022年12月19日,国务院发布了《中共中央 国务院关于构建数据基础制度更好发挥数据要素作用的意见》(以下简称“《意见》”)。 这份意见为数据流通奠定了一个政策基础。简单梳理一下这份重要的意见里面关键词可以发现其核心就是打破数据垄断、实现数据要素价值。 关键词梳理,依据《中共中央国务院关于构建数据基础制度更好发挥数据要素作用的意见》 在实现方法上,我们就要重视对数据质量标准化体系的建设,基于信息安全和标准化的数据采集,整合互联互通和互操作性,《意见》在实现方法上面提出了一些非常重要的观点,如原始数据不出域,数据可用不可见等等这样的一些关键点,都是我们未来在医疗卫生信息数据流通上的指导思想和指导原则。 那么互联互通套件如何来支持这种政策要求? 在互联互通套件2.0架构里面,我们治理后的医疗卫生信息保存在FHIR资源仓库里。FHIR这种对象模型非常适合表达医疗卫生的复杂数据模型,但是对类似数据分析上报这些传统应用来说,通常会使用基于SQL的数据分析和操作工具,如果使用FHIR资源,操作起来是很难的。此外,要让用户直接使用FHIR资源仓库,也不符合《意见》提到的“原始数据不出域”的要求。 所以互联互通套件提供了FHIR SQL构建器,这是一个图形化的工具,用户可以将FHIR资源按需求映射成不同的SQL表——例如数据上报需要用到数据的一部分内容,可能内部数据用户对于数据需求范围是比较大的,外部用户对于数据需求范围可能就会更窄一些——不管是用户需求是什么样的,都可以通过映射的方式提供相应的SQL表。这种方式既满足了数据用户,使他们能够通过熟悉的工具(SQL)获得数据和操作数据,同时也满足了“原始数据不出域”的要求。 另外需要强调的一点是,SQL映射不是创建一份数据拷贝。它并没有把FHIR资源仓库原始数据拷贝给用户,而是通过映射的方式,映射出来一份虚拟SQL表。用户可以以SQL的访问方式来操作它——通过这种模式提供的数据是实时的、完整的、按需提供的。 Epic的生态创新:以标准为基础,推动应用建设的专业化 互联互通赋能应用创新这部分,面临着很多基于标准的问题和限制。 以美国为例,美国国家卫生IT协调员办公室研究发现,只有29%的用户能够将外部来源的卫生信息和应用整合到自己的电子健康档案中,也就是说美国是没有实现“应用通”的,只有不到1/3的电子健康档案是可以使用别的厂商开发的应用的。 2022年12月还有一个非常重要的新闻,12月9日的这则新闻来自全球电子病历领头羊Epic,Epic发布了名为“Connection Hub”的平台,这个平台向所有开发者开放。 这意味着什么? 我们来看看Epic目前拥有的能力。作为一个电子病历厂商,Epic有自己的应用市场,类似于智能手机的应用市场。 截至2022年12月22日,这个应用市场里共有619个应用,这些应用都是不同厂商开发的,这些应用都可以作为一个即插即用的应用直接下载并安装在Epic的电子健康档案系统上,所以这是一个非常有意思的事情。 作为行业里最重要的电子病历厂商之一,Epic对这些应用的开发趋势,包括它持有的这种开放的、应用的生态理念,我相信会为行业应用创新带来很多价值。 回顾一下我们面临的现状,我们的数据中心是缺乏标准的,每个厂商的数据中心都是不同的数据模型,提供厂商自定义的数据接口,所以当基于这些数据来进行应用开发的时候,我们做的是低水平重复建设和无法复用的应用建设。 例如,几乎每个项目的数据中心产品里都会部署一个患者360视图或类似应用,但这个应用每个厂商、甚至每个项目都会重新开发一次,所以这是一个低水平重复建设的典型应用。而且即便觉得好,如果换一个数据中心产品,这个应用也无法直接移植过去。 在缺乏标准的情况下,这些数据中心及相关的应用建设变得非常昂贵。如果我们整个行业能够采用一个标准的语义来建设数据中心或者电子健康档案,那么通过提供基于行业统一语义的数据和基于标准的数据接口,就可以让应用开发厂商只需关注在业务,潜心开发高水平应用。而且基于生态标准的应用可以运行在任何基于标准的数据中心(或电子健康档案)上,这就为释放数据应用价值奠定了非常好的基础。 回到前面这则新闻,Epic的标准是什么?Epic采用了FHIR标准,它基于最新的R4开放了55个资源和450个FHIR API,这样一来,它就可以运行类似于“儿童生长发育曲线”这类的Smart on FHIR应用,加速应用创新。 Epic能做到的,我相信我们也可以借鉴,InterSystems IRIS医疗版互联互通套件已经提供了针对FHIR仓库的能力,并且打通了互联互通与FHIR生态。 InterSystems IRIS医疗版 互联互通套件能力 以互联互通为基础,让医院信息平台成为数字化转型的核心 我想借助这张IDC于2019年发布的“数字化转型平台”示意图作为结尾。这张图是一张概念图,描述了我们数字化转型平台的功能,以及通过无限循环的路径来摆脱传统的技术栈的思维模式。 它将数字化转型平台定义为一个加速企业数字化转型的技术架构,用来支撑我们快速创建面向市场的数字化的服务和体验的平台。同时通过它来积极推动机构内部IT环境的现代化,使它成为将数据运用于业务的智能核心——基于互联互通将数据、算法、代码和模型进行资产化管理、整理、复用和共享。 数字化转型已经颠覆了很多行业,我相信医疗卫生信息行业也不会例外。我们希望通过互联互通套件,以互联互通为基础,让医院信息平台成为医疗卫生行业数字化转型的智能核心。
文章
姚 鑫 · 三月 19, 2021

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

# 第十二章 使用嵌入式SQL(三) # 主机变量 **主机变量是将文字值传入或传出嵌入式SQL的局部变量。** 最常见的是,主机变量用于将本地变量的值作为输入值传递给Embedded SQL,或者将SQL查询结果值作为输出主机变量传递给Embedded SQL查询。 主机变量不能用于指定SQL标识符,例如架构名称,表名称,字段名称或游标名称。主机变量不能用于指定SQL关键字。 - 输出主机变量仅在嵌入式SQL中使用。它们在`INTO`子句中指定,`INTO`子句是仅嵌入式SQL支持的SQL查询子句。 - 输入主机变量可以在嵌入式SQL或动态SQL中使用。在动态SQL中,还可以使用`“?”`向SQL语句输入文字。输入参数。这 `”?”`语法不能在Embedded SQL中使用。 在嵌入式SQL中,可以在可以使用文字值的任何位置使用输入主机变量。使用SELECT或FETCH语句的`INTO`子句指定输出主机变量。 注意:当SQL `NULL`输出到ObjectScript时,它由一个ObjectScript空字符串(`“”`)表示,该字符串的长度为零。 **要将变量或属性引用用作宿主变量,请在其前面加上一个冒号(`:`)。** 嵌入式InterSystems SQL中的主机变量可以是以下之一: - 一个或多个ObjectScript局部变量,例如:`myvar`,指定为以逗号分隔的列表。局部变量可以完全形成并且可以包含下标。像所有局部变量一样,它区分大小写,并且可以包含Unicode字母字符。 - 单个ObjectScript局部变量数组,例如:`myvars()`。局部变量数组只能从单个表(而不是联接表或视图)中接收字段值。 - 对象引用,例如:`oref.Prop`,其中`Prop`是属性名称,带有或不带有前导`%`字符。这可以是简单属性或多维数组属性,例如:`oref.Prop(1)`。它可以是一个实例变量,例如:`i%Prop`或:`i %% Data`。属性名称可以定界。例如:`Person."Home City"`.即使停用了对分隔标识符的支持,也可以使用分隔属性名称。多维属性可以包括:`i%Prop()`和:`m%Prop()`主机变量引用。对象引用主机变量可以包含任意数量的点语法级别;例如,例如,:`Person.Address.City`。 当`oref.Prop`用作过程块方法内的宿主变量时,系统会自动将`oref`变量(而不是整个`oref.Prop`引用)添加到`PublicList`并对其进行更新。 主机变量中的双引号指定文字字符串,而不是带分隔符的标识符。例如,`:request.GetValueAt("PID:SetIDPID") o`r `:request.GetValueAt("PID:PatientName(1).FamilyName")`. 主机变量应在ObjectScript过程的PublicList变量列表中列出,并使用`NEW`命令重新初始化。您可以配置InterSystems IRIS以便在注释文本中列出Embedded SQL中使用的所有主机变量。使用InterSystems SQL的注释部分对此进行了描述。 主机变量值具有以下行为: - **输入主机变量永远不会被SQL语句代码修改。即使嵌入式SQL运行后,它们仍保留其原始值。但是,输入主机变量值在提供给SQL语句代码之前会被“轻度格式化”:有效数字值将去除前导和尾随零,单个前导加号和尾随小数点。时间戳记值将除去尾随空格,以小数秒为单位的尾随零和(如果没有小数秒的话)尾随的小数点。** - **当`SQLCODE = 0`时,即返回有效行时,将设置`INTO`子句中指定的输出主机变量。如果执行`SELECT`语句或`FETCH`语句导致`SQLCODE = 100`(没有数据与查询匹配),则`INTO`子句中指定的输出主机变量将设置为`null(“”)`。如果在执行`SELECT`语句或`FETCH`语句之前未定义`INTO`变量,导致`SQLCODE = 100`,则该变量将保持未定义状态。主机变量值仅应在`SQLCODE = 0`时使用。在`DECLARE` ... `SELEC`T ... `INTO`语句中,请勿在两个`FETCH`调用之间修改`INTO`子句中的输出主机变量,因为这可能会导致不可预测的查询结果。** 在处理输出主机变量之前,必须检查`SQLCODE`值。仅当`SQLCODE = 0`时才应使用输出主机变量值。 **当在`INTO`子句中使用逗号分隔的主机变量列表时,必须指定与选择项数量相同的主机变量数量(字段,集合函数,标量函数,算术表达式,文字)。宿主变量太多或太少都会在编译时导致`SQLCODE -76`基数错误。** 在嵌入式SQL中使用`SELECT *`时,这通常是一个问题。例如,`SELECT * FROM Sample.Person`仅对以逗号分隔的15个主机变量列表有效(非隐藏列的确切数目,具体取决于表定义,该数目可能包含也可能不包含系统生成的RowID) (`ID`)列)。 因为列数可以更改,所以用单个宿主变量的`INTO`子句列表指定`SELECT *`通常不是一个好主意。使用`SELECT *`时,通常最好使用主机变量下标数组,例如: ```java /// d ##class(PHA.TEST.SQL).EmbedSQL9() ClassMethod EmbedSQL9() { NEW SQLCODE &sql(SELECT %ID,* INTO :tflds() FROM Sample.Person ) IF SQLCODEd ##class(PHA.TEST.SQL).EmbedSQL9() field 1 = 1 field 2 = 30 field 3 = 54536 field 4 = ReOrangYellow field 6 = yaoxin field 8 = 111-11-1117 field 9 = 13 field 11 = St Louis field 12 = WI field 13 = 889 Clinton Drive field 14 = 78672 field 15 = Ukiah field 16 = AL field 17 = 9619 Ash Avenue field 18 = 56589 ``` **本示例使用`%ID`返回`RowID`作为字段号1,无论`RowID`是否隐藏。** 注意,在此示例中,字段编号下标可能不是连续序列;有些字段可能被隐藏并被跳过。包含`NULL`的字段以空字符串值列出。 ** 退出嵌入式SQL后立即检查`SQLCODE`值是一种良好的编程习惯。仅当`SQLCODE = 0`时才应使用输出主机变量值。** ## 主机变量示例 在下面的ObjectScript示例中,Embedded SQL语句使用输出主机变量将名称和归属状态地址从SQL查询返回到ObjectScript: ```java /// d ##class(PHA.TEST.SQL).EmbedSQL10() ClassMethod EmbedSQL10() { &sql(SELECT Name,Home_State INTO :CName,:CAddr FROM Sample.Person) IF SQLCODEd ##class(PHA.TEST.SQL).EmbedSQL10() Name is: yaoxin State is: WI ``` 嵌入式SQL使用`INTO`子句指定主机变量`:CName`和`:CAddr`,以在局部变量`CName`中返回所选客户的姓名,并在局部变量`CAddr`中返回主目录状态。 下面的示例使用带下标的局部变量执行相同的操作: ```java /// d ##class(PHA.TEST.SQL).EmbedSQL11() ClassMethod EmbedSQL11() { &sql(SELECT Name,Home_State INTO :CInfo(1),:CInfo(2) FROM Sample.Person) IF SQLCODEd ##class(PHA.TEST.SQL).EmbedSQL11() Name is: yaoxin State is: WI ``` 这些主机变量是带有用户提供的下标(`:CInfo(1)`)的简单局部变量。但是,如果省略下标(`:CInfo()`),则InterSystems IRIS使用`SqlColumnNumber`填充主机变量下标数组,如下所述。 在下面的ObjectScript示例中,嵌入式SQL语句同时使用输入主机变量(在`WHERE`子句中)和输出主机变量(在`INTO`子句中): ```java /// d ##class(PHA.TEST.SQL).EmbedSQL12() ClassMethod EmbedSQL12() { SET minval = 10000 SET maxval = 50000 &sql(SELECT Name,Salary INTO :outname, :outsalary FROM Sample.Employee WHERE Salary > :minval AND Salary < :maxval) IF SQLCODEd ##class(PHA.TEST.SQL).EmbedSQL12() Name is: Chadwick,Phyllis L. Salary is: 16377 ``` 以下示例在输入主机变量上执行`“light normalization”`。请注意,InterSystems IRIS将输入变量值视为字符串,并且不对其进行规范化,但是Embedded SQL将此数字规范化为`65`,以在`WHERE`子句中执行相等比较: ```java /// d ##class(PHA.TEST.SQL).EmbedSQL13() ClassMethod EmbedSQL13() { SET x="+065.000" &sql(SELECT Name,Age INTO :a,:b FROM Sample.Person WHERE Age=:x) IF SQLCODEd ##class(PHA.TEST.SQL).EmbedSQL13() Input value is: +065.000 Name value is: Houseman,Martin D. Age value is: 65 ``` 在下面的ObjectScript示例中,嵌入式SQL语句使用对象属性作为宿主变量: ```sql &sql(SELECT Name, Title INTO :obj.Name, :obj.Title FROM MyApp.Employee WHERE %ID = :id ) ``` 在这种情况下,`obj`必须是对具有可变(即可以修改)属性`Name`和`Title`的对象的有效引用。请注意,如果查询包含`INTO`语句并且没有返回任何数据(即`SQLCODE`为`100`),则执行查询可能会导致修改主机变量的值。 ## 用列号下标的主机变量 如果`FROM`子句包含一个表,则可以为从该表中选择的字段指定带下标的主机变量;否则,可以为该表指定一个下标主机变量。例如,本地数组`:myvar()`。 InterSystems IRIS使用每个字段的`SqlColumnNumber`作为数字下标填充本地数组。请注意,`SqlColumnNumber`是表定义中的列号,而不是选择列表序列。 (不能将带下标的宿主变量用于视图的字段。) 主机变量数组必须是省略了最低级别下标的局部数组。因此,`:myvar()`, `:myvar(5,)`, and `:myvar(5,2,)`都是有效的主机变量下标数组。 - 主机变量下标数组可以用于`INSERT`,`UPDATE`或`INSERT` OR `UPDATE`语句`VALUES`子句中的输入。当在`INSERT`或`UPDATE`语句中使用时,主机变量数组使您可以定义在运行时而不是在编译时更新哪些列。 - 主机变量下标数组可以用于`SELECT`或`DECLARE`语句`INTO`子句中的输出。在下面的示例中显示了`SELECT`中的下标数组用法。 在下面的示例中,`SELECT`使用指定字段的值填充`Cdata`数组。 `Cdata()`的元素对应于表列定义,而不是`SELECT`元素。因此,在`Sample.Person`中,`“名称”`字段是第6列,`“年龄”`字段是第2列,`“出生日期”`(`DOB`)字段是第3列: ```java /// d ##class(PHA.TEST.SQL).EmbedSQL14() ClassMethod EmbedSQL14() { &sql(SELECT Name, Age, DOB INTO :Cdata() FROM Sample.Person) IF SQLCODEd ##class(PHA.TEST.SQL).EmbedSQL14() Name is: yaoxin Age is: 30 DOB is: 04/25/90 ``` 以下示例使用带下标的数组主机变量返回行的所有字段值: ```java /// d ##class(PHA.TEST.SQL).EmbedSQL15() ClassMethod EmbedSQL15() { &sql(SELECT * INTO :Allfields() FROM Sample.Person) IF SQLCODE d ##class(PHA.TEST.SQL).EmbedSQL16() 1 field is 1 2 field is 30 3 field is 54536 4 field is ReOrangYellow 6 field is yaoxin 8 field is 111-11-1117 9 field is 13 11 field is St Louis 12 field is WI 13 field is 889 Clinton Drive 14 field is 78672 15 field is Ukiah 16 field is AL 17 field is 9619 Ash Avenue 18 field is 56589 date & time now is 2021-03-13 16:00:40 exact age is 30.88295687885010267 ``` 请注意,非数组主机变量必须在数量和顺序上与非列`SELECT`项匹配。 将主机变量用作下标数组受以下限制: - **只有在`FROM`子句的单个表中选择字段时,才可以使用带下标的列表。这是因为从多个表中选择字段时,`SqlColumnNumber`值可能会发生冲突。** - 下标列表只能在选择表字段时使用。它不能用于表达式或聚合字段。这是因为这些选择列表项没有`SqlColumnNumber`值。 ## `NULL`和未定义的主机变量 如果指定未定义的输入主机变量,则嵌入式SQL将其值视为`NULL`。 ```java /// d ##class(PHA.TEST.SQL).EmbedSQL17() ClassMethod EmbedSQL17() { NEW x &sql(SELECT Home_State,:x INTO :a,:b FROM Sample.Person) IF SQLCODEd ##class(PHA.TEST.SQL).EmbedSQL17() Home_State的长度为: 2 x的长度是: 0 ``` SQL `NULL`等效于ObjectScript“”字符串(长度为零的字符串)。 如果将`NULL`输出到主机变量,则Embedded SQL会将其值视为ObjectScript`“”`字符串(零长度字符串)。例如,`Sample.Person`中的某些记录具有`NULL Spouse`字段。执行此查询后: ```java /// d ##class(PHA.TEST.SQL).EmbedSQL18() ClassMethod EmbedSQL18() { &sql(SELECT Name,Spouse INTO :name, :spouse FROM Sample.Person WHERE Spouse IS NULL) IF SQLCODEd ##class(PHA.TEST.SQL).EmbedSQL18() Name: xiaoli of length 6 defined: 1 Spouse: of length 0 defined: 1 ``` 宿主变量`spouse`将设置为`“”`(长度为零的字符串)以指示`NULL`值。因此,不能使用ObjectScript `$DATA`函数来确定SQL字段是否为`NULL`。当传递带有`NULL`值的SQL字段的输出主机变量时,`$DATA`返回true(定义了变量)。 在极少数情况下,表字段包含SQL零长度字符串(`''`),例如,如果应用程序将字段显式设置为SQL `''`字符串,则主机变量将包含特殊标记值`$CHAR(0 )`(长度为1的字符串,仅包含一个ASCII 0字符),它是SQL零长度字符串的ObjectScript表示形式。强烈建议不要使用SQL零长度字符串。 下面的示例比较SQL `NULL`和SQL零长度字符串输出的主机变量: ```java /// d ##class(PHA.TEST.SQL).EmbedSQL19() ClassMethod EmbedSQL19() { &sql(SELECT '',Spouse INTO :zls, :spouse FROM Sample.Person WHERE Spouse IS NULL) IF SQLCODEd ##class(PHA.TEST.SQL).EmbedSQL19() In ObjectScript ZLS is of length 1 defined: 1 NULL is of length 0 defined: 1 ``` 请注意,此主机变量`NULL`行为仅在基于服务器的查询(嵌入式SQL和动态SQL)中为true。在ODBC和JDBC中,使用ODBC或JDBC接口显式指定NULL值。 ## 主机变量的有效性 - **嵌入式SQL永远不会修改输入主机变量。** - **仅当`SQLCODE = 0`时,输出主机变量才在Embedded SQL之后可靠地有效。** 例如,以下`OutVal`的用法不可靠: ```java /// d ##class(PHA.TEST.SQL).EmbedSQL20() ClassMethod EmbedSQL20() { InvalidExample SET InVal = "1234" SET OutVal = "None" &sql(SELECT Name INTO :OutVal FROM Sample.Person WHERE %ID=:InVal) IF OutVal="None" { WRITE !,"没有数据返回" WRITE !,"SQLCODE=",SQLCODE } ELSE { WRITE !,"Name is: ",OutVal } } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).EmbedSQL20() 没有数据返回 SQLCODE=100 ``` 调用嵌入式SQL之前设置的`OutVal`的值在从嵌入式SQL返回之后不应该被IF命令引用。 相反,应该使用`SQLCODE`变量编写如下示例: ```java /// d ##class(PHA.TEST.SQL).EmbedSQL21() ClassMethod EmbedSQL21() { ValidExample SET InVal = "1234" &sql(SELECT Name INTO :OutVal FROM Sample.Person WHERE %ID=:InVal) IF SQLCODE'=0 { SET OutVal="None" IF OutVal="None" { WRITE !,"没有数据返回" WRITE !,"SQLCODE=",SQLCODE } } ELSE { WRITE !,"Name is: ",OutVal } } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).EmbedSQL21() 没有数据返回 SQLCODE=100 ``` **嵌入式SQL将`SQLCODE`变量设置为0,以指示成功地检索输出行。 `SQLCODE`值为`100`表示没有找到与`SELECT`条件匹配的行。 `SQLCODE`负数表示SQL错误条件。** ## 主机变量和程序块 如果嵌入式SQL在过程块内,则所有输入和输出主机变量必须是公共的。可以通过在过程块开始处的`PUBLIC`部分中声明它们,或用一个初始`%`字符命名它们(自动使它们公开)来完成它们。但是请注意,用户定义的`%`主机变量是自动公开的,但不是自动更新的。用户有责任根据需要对这些变量执行`NEW`。如嵌入式SQL变量中所述,某些SQL`%`变量(例如`%ROWCOUNT`,`%ROWID`和`%msg`)既自动公开又自动更新。必须将`SQLCODE`声明为`public`。 在以下过程块示例中,主机变量`zip`,`city`和`state`以及`SQLCODE`变量被声明为`PUBLIC`。 SQL系统变量`%ROWCOUNT`,`%ROWID`和`%msg`已经公开,因为它们的名称以`%`字符开头。然后,过程代码对`SQLCODE`,其他SQL系统变量和状态局部变量执行`NEW`。 ```java /// d ##class(PHA.TEST.SQL).EmbedSQL22() ClassMethod EmbedSQL22() { UpdateTest(zip,city) [SQLCODE,zip,city,state] PUBLIC { NEW SQLCODE,%ROWCOUNT,%ROWID,%msg,state SET state="MA" &sql(UPDATE Sample.Person SET Home_City = :city, Home_State = :state WHERE Home_Zip = :zip) IF SQLCODE
文章
姚 鑫 · 四月 15, 2021

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

# 第二章 定义和构建索引(三) # 位图索引 位图索引是一种特殊类型的索引,它使用一系列位串来表示与给定索引数据值相对应的一组ID值。 位图索引具有以下重要功能: - 位图是高度压缩的:位图索引可以比标准索引小得多。这大大减少了磁盘和缓存的使用量。 - 位图操作针对事务处理进行了优化:与使用标准索引相比,可以在表中使用位图索引,而不会降低性能。 - 位图上的逻辑操作(`counting`、`AND`和`OR`)经过优化以获得高性能。 - SQL引擎包括许多可以利用位图索引的特殊优化。 位图索引的创建取决于表的唯一标识字段的性质: - 如果表的`ID`字段定义为具有正整数值的单个字段,则可以使用此`ID`字段为字段定义位图索引。此类型的表使用系统分配的唯一正整数`ID`,或使用`IdKey`定义自定义`ID`值,其中`IdKey`基于类型为`%Integer`且`MINVAL`>的单个属性,或类型`%Numeric`型且`Scale=0`且`MINVA>0`。 - 如果表的`ID`字段未定义为具有正整数值的单个字段(例如,子表),则可以定义采用正整数的`%BID`(位图`ID`)字段作为代理`ID`字段;这允许为该表中的字段创建位图索引。 受下列限制,位图索引的操作方式与标准索引相同。 索引值将被整理,可以在多个字段的组合上建立索引。 ## 位图索引操作 位图索引的工作方式如下。 假设`Person`表,其中包含一些列: ![image](/sites/default/files/inline/images/1_35.png) 此表中的每一行都有一个系统分配的`RowID`号(一组递增的整数值)。位图索引使用一组位字符串(包含1和0值的字符串)。在位串中,位的序号位置对应于索引表的`RowID`。对于给定值,假设`State`为`“NY”`,则有一个位串,每个位置对应一个包含`“NY”`的行,其他位置为0。 例如,`State`上的位图索引可能如下所示: ![image](/sites/default/files/inline/images/2_20.png) 而关于`Age` 年龄的索引可能如下所示: ![image](/sites/default/files/inline/images/3_16.png) 注:此处显示的年龄字段可以是普通数据字段,也可以是其值可以可靠派生(`Calculated` 和`SQLComputed`)的字段。 除了将位图索引用于标准操作外,SQL引擎还可以使用位图索引来使用多个索引的组合来高效地执行特殊的基于集合的操作。例如,要查找居住在纽约的24岁`Person`的所有实例,SQL引擎只需执行`Age`和`State`索引的逻辑与: ![image](/sites/default/files/inline/images/4_12.png) 生成的位图包含匹配搜索条件的所有行的集合。SQL引擎使用它从这些行返回数据。 SQL引擎可以将位图索引用于以下操作: - 对给定表上的多个条件进行`AND`运算。 - 对给定表上的多个条件进行`OR`运算。 - 给定表上的`RANGE`范围条件。 - 对给定表上的操作进行计数`COUNT`。 ## 使用类定义定义IdKey位图索引 如果表的`ID`是值限制为唯一正整数的字段,则可以使用新建索引向导或通过与创建标准索引相同的方式编辑类定义的文本,将位图索引定义添加到类定义中。唯一的区别是需要将索引类型指定为“位图”: ```java Class MyApp.SalesPerson Extends %Persistent [DdlAllowed] { Property Name As %String; Property Region As %Integer; Index RegionIDX On Region [Type = bitmap]; } ``` ## 使用类定义定义`%BID`位图索引 如果表的`ID`不限于正整数,则可以创建`%BID`属性以用于创建位图索引定义。可以将此选项用于具有任何数据类型的`ID`字段的表,以及由多个字段组成的`IDKEY`(包括子表)。可以为以下任一数据存储类型创建`%BID`位图:默认结构表或`%Storage.SQL`表。此功能称为“任意表的位图”或`BAT`。 要在这样的表上启用位图索引,必须执行以下操作: 1. 为类定义`%BID`属性/字段。这可以是类的现有属性,也可以是新属性。它可以有任何名称。如果这是新属性,则必须为表中的所有现有行填充此属性/字段。此`%BID`字段必须定义为将字段数据值限制为唯一正整数的数据类型。例如,将`MyBID`属性设置为`%Counter`; 2. 定义新的类参数以定义哪个属性是`%BID`字段。此参数被命名为`BIDField`。此参数设置为`%BID`属性的`SQLFieldName`。例如,参数`BIDField=“MyBID”`; 3. 定义`%BID`的索引。例如,`MyBID`上的`Index BIDIdx[Type=Key,Unique]`; 4. 定义`%BID`定位器索引。 这将`%BID`索引绑定到表的`ID`键字段。 下面的例子是一个表的一个复合`IDKey`组成两个字段: ```java Index IDIdx On (IDfield1, IDfield2) [ IdKey, Unique ]; Index BIDLocIdx On (IDfield1, IDfield2, MyBID) [ Data = IdKey, Unique ]; ``` 此表现在支持位图索引。可以使用标准语法根据需要定义位图索引。例如: `Index RegionIDX On Region [Type = bitmap]`; 此表现在还支持位片索引。可以使用标准语法定义位片索引。 **注意:要构建或重新生成`%BID`位图索引,必须使用`%BuildIndices()`。`%BID`位图索引不支持`%ConstructIndicesParallel()`方法。** ## 使用DDL定义位图索引 如果使用DDL语句定义表,还可以使用以下DDL命令为`ID`为正整数的表格创建和删除位图索引: ```sql CREATE BITMAP INDEX RegionIDX ON TABLE MyApp.SalesPerson (Region) ``` ## 生成位图范围索引 **编译包含位图索引的类时,如果类中存在任何位图索引,并且没有为该类定义位图范围索引,则类编译器会生成位图范围索引。如果位图范围索引存在(无论是定义的还是生成的),该类从主超类继承位图范围索引。为类构建索引时,如果要求构建位图范围索引,或者正在构建另一个位图索引并且位图范围索引结构为空,则会构建位图范围索引。** 除非存在位图索引,否则InterSystems IRIS不会生成位图范围索引。位图范围索引定义为:`type = bitmap`, `extent = true`。这意味着从主要超类继承的位图范围索引被认为是位图索引,并且如果在该子类中没有显式定义位图范围索引,则将触发在子类中生成位图范围索引。 InterSystems IRIS不会基于未来的可能性在超类中生成位图范围索引。这意味着,除非存在`type=bitmap`的索引,否则InterSystems IRIS永远不会在持久类中生成位图范围索引。假设将来的某个子类可能引入`type=bitmap`的索引是不够的。 **注意:在将位图索引添加到生产系统上的类的过程中需要特别小心(在生产系统中,用户正在使用特定的类,编译所述类,然后为其构建位图索引结构)。在这样的系统上,位图范围索引可以在编译完成和索引构建进行之间的过渡期间被填充。这可能导致索引构建过程未隐式构建位图范围索引,这导致部分完整的位图范围索引。** ## 选择索引类型 **下面是在位图和标准索引之间选择的一般准则。 一般来说,所有类型的键和引用都要使用标准索引:** - Primary key - Foreign key - Unique keys - Relationships - Simple object references 否则,位图索引通常更可取(假设表使用系统分配的数字ID号)。 其他因素: - **每个属性上的单独位图索引通常比多个属性上的位图索引具有更好的性能。这是因为SQL引擎可以使用`AND`和`OR`操作有效地组合单独的位图索引。** - **如果一个属性(或确实需要一起编制索引的一组属性)有超过`10,000-20,000`个不同的值(或值组合),请考虑标准索引。但是,如果这些值的分布非常不均匀,以至于很少的值只占行的很大一部分,那么位图索引可能会更好。一般来说,目标是减少索引所需的总体大小。** ## 位图索引的限制 所有位图索引都有以下限制: - 不能在唯一列上定义位图索引。 - 不能在位图索引中存储数据值。 - 除非字段的`SqlCategory`是 `INTEGER`, `DATE`, `POSIXTIME`, or `NUMERIC(scale=0)`,否则不能在字段上定义位图索引。 - **对于包含超过100万条记录的表,当惟一值的数量超过`10,000`时,位图索引的效率低于标准索引。 因此,对于大型表,建议避免为任何包含(或可能包含)超过`10,000`个惟一值的字段使用位图索引; 对于任意大小的表,避免对任何可能包含超过`20,000`个惟一值的字段使用位图索引。 这些是一般的近似值,不是确切的数字。** 必须创建一个`%BID`属性来支持一个表上的位图索引: - 使用非整数字段作为唯一的`ID`键。 - 使用一个多字段`ID`键。 - 是父子关系中的子表。 可以使用`$SYSTEM.SQL.Util.SetOption()`方法`SET status=$SYSTEM.SQL.Util.SetOption("BitmapFriendlyCheck",1,.oldval) `设置系统范围的配置参数,以便在编译时检查此限制,从而确定`%Storage.SQL`类中是否允许定义的位图索引。此检查仅适用于使用`%Storage.SQL`的类。默认值为0可以使用`$SYSTEM.SQL.Util.GetOption(“BitmapFriendlyCheck”)`来确定此选项的当前配置。 ### 应用程序逻辑限制 位图结构可以由位串数组表示,其中数组的每个元素表示具有固定位数的`"chunk"`。因为`UNDEFINED`等同于一个全为0位的块,所以该数组可以是稀疏的。表示全部0位的块的数组元素根本不需要存在。因此,应用程序逻辑应该避免依赖于0值位的`$BITCOUNT(str,0)`计数。 由于位串包含内部格式,因此应用程序逻辑不应依赖于位串的物理长度,也不应依赖于将具有相同位值的两个位串相等。在回滚操作之后,位串恢复到事务之前的位值。然而,由于内部格式化,回滚的位串可能不等于或不具有与事务之前的位串相同的物理长度。 ## 维护位图索引 **在易失性表(执行许多插入和删除操作)中,位图索引的存储效率可能会逐渐降低。要维护位图索引,可以运行`%SYS.Maint.Bitmap`实用程序方法来压缩位图索引,使其恢复到最佳效率。可以使用`OneClass()`方法压缩单个类的位图索引。或者,可以使用`Namespace()`方法来压缩整个命名空间的位图索引。这些维护方法可以在带电系统上运行。** 运行`%SYS.Maint.Bitmap`实用程序方法的结果将写入调用该方法的进程。这些结果还会写入`%SYS.Maint.BitmapResults`类。 ## 位图块的SQL操作 InterSystems SQL提供了以下扩展来直接操作位图索引: - `%CHUNK`函数 - `%Bitpos`函数 - `%BITMAP`聚合函数 - `%BITMAPCHUNK`聚合函数 - `%SETINCHUNK`谓词条件 所有这些扩展都遵循InterSystems SQL位图表示约定,将一组正整数表示为一系列位图块,每个块最多包含`64,000个`整数。 这些扩展允许在查询和嵌入式SQL中更轻松、更高效地操作某些条件和筛选器。在嵌入式SQL中,它们支持位图的简单输入和输出,特别是在单个块级别。它们支持处理完整的位图,这些位图由`%bitmap()`和`%SQL.Bitmap`类处理。它们还支持非`RowID`值的位图处理,例如外键值、子表的父引用、关联的任一列等。 例如,要输出指定块的位图,请执行以下操作: ```sql SELECT %BITMAPCHUNK(Home_Zip) FROM Sample.Person WHERE %CHUNK(Home_Zip)=2 ``` 要输出整个表的所有块,请执行以下操作: ```sql SELECT %CHUNK(Home_Zip),%BITMAPCHUNK(Home_Zip) FROM Sample.Person GROUP BY %CHUNK(Home_Zip) ORDER BY 1 ``` ### %CHUNK函数 `%%CHUNK(F)`返回位图索引字段f值的块分配。这被计算为`f\64000+1.%%CHUNK(F)`非位图索引字段的任何字段或值`f`的`%chunk(F)`始终返回1。 ### %BITPOS函数 `%Bitpos(F)`返回分配给其区块内的位图索引字段`f`值的位位置。这被计算为`f#64000+1`。对于不是位图索引字段的任何字段或值`f`,`%Bitpos(F)`返回的值比其整数值多`1`。字符串的整数值为`0`。 ### %BITMAP聚合函数 聚合函数`%bitmap(F)`将许多`f`值组合到一个`%SQL.Bitmap`对象中,在该对象中,对于结果集中的每个值`f`,与适当块中的`f`相对应的位被设置为`1`。上述所有参数中的f通常是正整数字段(或表达式),通常(但不一定)是`RowID`。 ### %BITMAPCHUNK聚合函数 聚合函数`%BITMAPCHUNK(F)`将字段f的许多值组合成`64,000`位的InterSystems SQL标准位图字符串,其中对于集合中的每个值`f`,位`f#64000+1=%Bitpos(F)`被设置为`1`。请注意,无论`%chunk(F)`的值是多少,都会在结果中设置该位。`%BITMAPCHUNK()`为空集生成`NULL`,并且与任何其他聚合一样,它忽略输入中的`NULL`值。 ### %SETINCHUNK谓词条件 当且仅当($BIT(BM,`%Bitpos(F)=1`时,条件(`f%SETINCHUNK BM`)为真。Bm可以是任何位图表达式字符串,例如输入主机变量:`bm`,或`%BITMAPCHUNK()`聚合函数的结果,等等。请注意,无论`%chunk(F)`的值是多少,都会检查`` 位。如果`` 不是位图或为`NULL`,则条件返回`FALSE`。(`F%SETINCHUNK NULL`)生成`FALSE`(非未知)。
文章
Qiao Peng · 六月 8, 2022

数字孪生医院:一个围绕着智慧医院决策的建模、管理和行动闭环的全新方式

01 智慧医院的本质 什么是智慧医院? 国家卫健委提出建设智慧医院的目的是要不断增强人民群众的获得感,要求医院的流程更便捷、服务更高效、管理更精细,主要聚焦于三大领域,面向医务人员的智慧医疗、面向患者的智慧服务以及面向管理的智慧管理。 如果从技术角度进行高度抽象,我们把智慧医院的本质浓缩为一点,就是为智慧的科学决策以及基于科学决策的业务执行闭环。在现实世界里,我们要从真实业务(调查研究)中获得决策依据,同时还要有决策的方法,把决策应用在现实世界上,来影响和改变现实世界。 02 科学决策的三个阶段 第一个时代是人工决策时代。我们医疗行业就是望闻问切,我们能拿到的数据非常有限,所以属于一个数据饥渴的时代。我们通过有限的数据来进行决策,非常容易落入我们经常批判的“三拍”型决策,“拍脑袋决策、拍胸脯保证、拍屁股走人”。显然这个决策效率不是很高。 人工决策时代:“数据稀缺”时代的决策 第二个时代是信息化时代。我们有了很多数据,能够把部分现实世界来进行数字化表达,我们也通过信息辅助人工决策进行智能决策,例如通过药品知识库推动合理用药,通过人工智辅助影像判断等等。 然而我们依旧面临着很多数据问题。首先是数据还不够完整,经常无法获取需要的数据,数据模型也不能完全表达医院的实际业务;其次,我们也还是会遇到很多数据来源问题(我们常常不知道该信任哪一份数据)、数据的关联性、实时性等问题。系统多了、数据多了,但同时孤岛也多了、噪音也多了,如何通过技术的手段让真实世界在数字化的世界里面完整、客观表达,这是实现数字化医院最基础的工作。 此外,我们仍然没有解决人工决策的局限性,例如决策所依赖的依据,我们的大脑能够处理的依据差不多十个左右,但是现在医学发展也有很多的数据,比如有临床数据、有功能遗传学、有蛋白质组学的等等,我们面对着上千个决策因素,但受制于人类自身的限制,我们也很难直观地理解和认识这些数据并发挥其价值。 人工决策的局限 数据来源:Evidence-Based Medicine and the Changing Nature of Healthcare: 2007 IOM Annual Meeting Summary 在数字化时代,决策模式从信息辅助决策升级为人工干预的智能决策。数字化时代的决策有四个要素。 第一是全面的数据表达,我们要把真实世界的所有数据能够挖掘和展现出来,并将其沉淀在数据平台。数据的全面性包含了数据广度、数据深度、数据时效性、数据的关联性、数据的动态变化等等。 第二是数字化的逻辑推理能力,基于知识图谱和基于机器学习来把逻辑推理过程实现数字化,比如深度学习、神经网络等等,都是在试图将我们的逻辑推理进行数字化表现。 第三是流程与场景数字化,我们现在已经有了临床辅助决策,为什么在现实业务中还会遇到例如应该报警的没报警,不该报警的反而误报警等现象,主要原因是现在决策流程都是在各个业务系统内通过人工环节里面来体现的,使得我们并不了解实际业务中的决策流程,所以如果想要真正了解我们的决策是不是正确的、及时的,以及闭环地应用在业务里面,我们需要对业务的流程和场景进行数字化。 第四,对决策本身的绩效评价数字化。我们怎么知道决策是好的还是坏的,是最优的还是次优的?我们应该如何优化我们的决策,让决策能够变得越来越好?我们需要对决策进行可度量的数字化评价,评价的模型、指标,决策的效果等,对应这些我们也需要进行数字化。有了这些评价的结果,我们才能依据这些结果进行推理逻辑的改进,让决策更好。 数字化时代决策的四要素: 全面的数据、逻辑推理数字化、流程与场景数字化、绩效评价数字化 03 数字孪生——智慧医院的未来 这时我们就不难看出为什么数字孪生是智慧医院的未来方向了。 根据Gartner 对数字孪生的定义:“现实世界实体或系统的数字表达。数字孪生体的实现是一套封装的软件对象或模型,它反映了一个独特的物理对象、过程、组织、人或其他抽象概念。” 数字孪生作为现实世界全面的数字化表达,不是为了表达而表达,而是通过全面表达来实现决策方法、决策流程和决策评价的全面数字化。数字孪生是一个能够全面反映真实医院,并且跟实际业务紧密集成、互相打通的数字化世界,在数字孪生世界里很多决策可以由它来产生,不需要由真实世界来产生,数字孪生世界里的决策可以真实地进行模拟、验证、演练,从而最大程度降低我们的试错成本,同时还可以反馈给真实世界,形成一个统一的、闭环的数字——真实一体化的世界。所以数字孪生并不仅仅是一个简单的真实世界的镜像,而是一个更复杂的、跟现实世界有互通性、互操作性的数字世界。 数字孪生:现实+决策+流程+评价的全面数字化 04 InterSystems IRIS医疗版实现数字孪生医院的最佳数智底座 数字孪生的世界里我们有什么样的平台和技术来支撑它?对于数字孪生的智慧医院来说,我们认为应该有一个功能强大的数智基座能满足前面提到数字孪生时代决策的四个关键要素。 第一是数据的全面性。面对复杂的真实世界,我们需要多种数据建模方式,类似思维导图,对某一事物进行N维建模,同时可以随着我们的认知加深而不断扩展的建模方式,而不是简单的关系型二维表来描述我们的实际业务。同时这些数据是实时的、有关联性的,只有这样我们做的决策才能够发现或者反应事物的本质,才有价值。 第二是流程和场景的数字化。现在的流程大部分都是分散在各个业务系统和人工工作里面,相互割裂、理解起来很困难,合理性、是否需要优化、如何优化更是无从谈起。在数字孪生时代,我们需要对于整个流程来建立可视化的、直观的并且能够优化的流程,通过流程建模来建模,同时建立这个流程模型,在数字化的世界里是闭环的,也就是说可以把决策的结果实时反馈给真正在运营的流程,并且反馈给我们真实世界。这个流程是可以优化的,可以不断进行业务流程的再造。 第三,要实现逻辑推理的数字化。我们要借助机器学习、深度学习等技术来提升我们决策的科学水平,对于很多行业里面的非结构化的数据,我们借助自然语言处理来挖掘真实的非结构化数据真实的含义,当然还有许多通过知识图谱认知来分析来提升整体决策的能力。 最后,从决策的评价和改进层面来说,基于行业的模型行业的标准对于决策进行相应评价,通过数据深度的分析对于评价指标进行相应的计算,并且把结果实时反馈给业务流程,通过推理和逻辑来优化决策,提升决策水平。 以上这四个能力就是未来数字孪生智慧医院数智基座应该具有的能力。 InterSystems IRIS医疗版数据平台正是这样一个平台,可以帮助用户从各种数据源收集和编辑相应的数据,并且能够对真实世界使用不同的模型,真实反映真实世界全貌的数据模型,里面包含了我们很多方面的数据,例如说临床的、组学的、物联网的、社交的、健康管理等等数据。同时InterSystems IRIS医疗版数据平台提供完整的互操作平台,可以帮助我们业务流程进行可视化,实现可视化流程的建模——这种方式可以支撑我们对于这个流程的实时优化,同时通过实时数据分析把实时洞察反馈给业务流程,支撑用户做更智能的决策。 通过开放型分析,基于全面的数据,我们可以应用机器学习、自然语言处理、知识图谱等等对数据进行分析、决策和相应决策的评价来驱动业务持续改进。 InterSystems IRIS医疗版数据平台:数字孪生医院最佳数智基座 数字化时代,基于真实业务数据的决策智能和基于合理决策的业务执行闭环是实现智慧医院的核心,而数字孪生开启了一个全新的围绕着智慧决策的建模、管理行动的全新方式,能够帮助医院完整认知和理解医院业务,提高决策质量和效率、优化资源利用,从而提高医疗质量、优化患者体验以及降低运营成本,更重要的是可以支持医院实现可持续的高质量发展和数字化创新能力。 InterSystems IRIS数据平台作为全美排名前20医院、复旦百强中40%的医院以及全国数百家医院的共同选择,毫无疑问是实现数字孪生医院数智底座的最佳选择。
文章
Jingwei Wang · 五月 4, 2022

IntegratedML 实践实验室

你是否尝试过[InterSystems IRIS IntegratedML学习平台](https://learning.intersystems.com/course/view.php?id=1535&ssoPass=1)?在这个平台中,你可以在再入院数据集上训练和测试一个模型,并能够预测一个病人何时会再入院,或计算其再入院的概率。 你不需要在你的系统上进行任何安装就可以尝试,你所要做的就是启动一个虚拟实验室环境(Zeppelin),然后玩一玩! 在这篇文章中,我们将利用这个实验室向你简要介绍IntegratedML,向你介绍要处理的问题,如何使用IntegratedML来创建一个再入院预测模型,以及如何分析其性能指标的一些见解。 ## 什么是IntegratedML? ![IntegratedML infographic](https://user-images.githubusercontent.com/8899513/85149599-7848f900-b21f-11ea-9b65-b5d703752de3.PNG) 来源: https://github.com/intersystems-community/integratedml-demo-template 在开始本教程之前,让我们简单谈谈IRIS IntegratedML。这个工具使你能够直接在SQL语句中执行机器学习(ML)任务,抽象出复杂过程的实现,例如,选择哪些列和ML算法是对目标列进行分类或回归的最佳选择。 IntegratedML的另一个伟大功能是易于部署。一旦你的模型被训练并表现良好,你只需要运行SQL语句,以便让你的模型投入生产。 IntegratedML让你选择使用哪个 [ML提供者](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_Configuration_Providers)。默认的提供者是 [AutoML](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_AUTOML), 这是InterSystems公司使用Python实现的ML库 [scikit-learn](https://scikit-learn.org/stable/). 但是你也可以选择其他三个提供者: PMML, [H2O](https://www.h2o.ai/) and [DataRobot](https://www.datarobot.com/). 在这篇文章中,我们将使用AutoML。 ## 问题和解决方案 现在,让我们来介绍一下这个问题,以及我们如何努力提出一种方法来尽量减少其影响。我们将尝试减少由于再入院而产生的问题。 [根据维基百科](https://en.wikipedia.org/wiki/Hospital_readmission), 医院再入院是指病人被批准离开医院(出院),但此人在一个意想不到的短时间间隔内再次回到医院(再入院)的事件。 ![Hospital readmission inforgraphic](https://news.yale.edu/sites/default/files/styles/horizontal_image/public/d6_files/Hospital_v03_YNews.jpg) 来源: [https://news.yale.edu/2015/01/15/when-used-effectively-discharge-summari...](https://news.yale.edu/2015/01/15/when-used-effectively-discharge-summaries-reduce-hospital-readmissions) 再入院会造成病人护理质量的损失--出院和再入院之间的时间可能很关键,医院的资源也会优化。 克服这个问题的一个方法是尝试使用历史数据库来创建一个数据集,其中过去的再入院事件可以通过机器学习算法进行分析,创建一个ML模型。如果数据集足够丰富和干净,再入院模式可以被正确检测出来,新的事件可以通过这个模型计算出其概率。 因此,通过使用ML模型来预测再入院,能够避免错误的出院,肯定会成为医院提高服务质量和利润的一个有价值的选择。 ## 使用IntegratedML创建和使用一个ML模型 ### 模型创建 使用IntegratedML创建一个ML模型,就像执行一个SQL语句一样容易。你只需要定义数据所在的历史数据集,并为你的模型起一个名字。 ``` CREATE MODEL Readmission PREDICTING (MxWillReAdmit) FROM EncountersHistory ``` After that statement, you have declared and created a model called Readmission intended to predict the values for a column called MxWillReAdmit, based on a dataset called EncountersHistory. You can find more information about this statement [here](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_CREATEMODEL). For example, in design phase, it’s handy forces the same results on training, so you can use the USING argument with some arbitrary constant number, like this: 在该声明之后,你已经声明并创建了一个名为Readmission的模型,基于名为EncountersHistory的数据集,预测名为MxWillReAdmit的列的值。 你可以找到关于这个声明的更多信息,[点击这里](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_CREATEMODEL)。例如,在设计阶段,很方便地迫使训练中出现相同的结果,所以你可以用USING参数加上一些任意的常数,像这样。 ``` CREATE MODEL Readmission PREDICTING (MxWillReAdmit) FROM EncountersHistory USING {"seed": 3} ``` ### 模型训练 现在,通过使用TRAIN MODEL语句,你的模型已经准备好被训练。 ``` TRAIN MODEL Readmission ``` 在这里,IntegratedML为你做了很多工作(使用AutoML ML供应商): - [Feature engineering](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_AutoML#GIML_AutoML_Feature): 使用哪些列 - [Data encoding](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_AutoML#GIML_AutoML_Keys): 所选栏目中的数据必须如何呈现给ML算法 - [ML algorithm selection](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_AutoML#GIML_AutoML_Algos): 哪种算法能带来最好的结果 - [Model selection](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_AutoML#GIML_AutoML_Selection): 根据目标列的数据类型,应该选择一个分类模型或回归模型。在这个例子中,一旦目标列有布尔值,就会选择一个分类模型。 这个声明可能需要一些时间,取决于数据集的大小和你的数据的复杂性。 ### 模型验证 到现在,我们应该急于知道我们的模型到底有多好。Integrated ML有一个计算性能指标的声明,基于另一个数据集,而不是用于训练的数据集 这个新的数据集被称为测试或验证数据集,这取决于你使用哪种策略进行验证。这个数据集通常是从用于创建训练数据集的同一个数据集中检索出来的。一个常见的方法是随机选择70%或80%的数据集用于训练,其余的用于测试/验证。 在实验室里,之前已经为这项任务准备了一个新的可使用的数据集:EncountersNew数据集。 现在,我们可以找出我们的模型有多好(或多坏)。 ``` VALIDATE MODEL Readmission FROM EncountersNew ``` 为了得到结果,你应该查询 INFORMATION_SCHEMA.ML_VALIDATION_METRICS 表。 ``` SELECT * FROM INFORMATION_SCHEMA.ML_VALIDATION_METRICS ``` 模型的性能是用 [IntegratedML的四个指标](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_VALIDATEMODEL#GIML_VALIDATEMODEL_Metrics): - Accuracy: 预测正确率(数值接近1表示正确率高)。 - Precision:对模型所做的所有阳性预测的正确率(接近1的值意味着很少有假阳性预测)。 - Recall:关于数据集中所有实际阳性值的正确阳性预测率(接近1的值意味着很少有假阴性预测)。 - F-Measure: 另一种衡量准确率的方法,当准确率表现不佳时使用,一般用于不平衡问题(数值接近1意味着高正确率)。 ### 关于性能指标的一些讨论 在这里,我们可以看到,该模型的准确率为82%。但是,这个指标不应该被单独分析,其他的指标如精确度和召回率也必须被评估。 让我们思考一下错误预测的影响--假阳性和假阴性。对于我们的模型来说,假阳性意味着预测的再入院并不是实际的再入院。而假阴性,意味着一个病人已经再入院,而模型预测这个病人不会再入院。 这两种情况都会导致错误的决定。一个假阳性的预测可能会导致一个超过必要的病人留在医院的决定;而一个假阴性的预测可能会导致一个提前出院的决定,然后再让这个病人重新入院。 请注意,在我们的模型中,假阴性会导致再入院病例,这正是我们要避免的。所以,我们必须选择能减少假阴性病例的模型,即使假阳性病例的数量增加。 因此,为了选择具有低假阴性率的模型,我们需要选择具有高召回率的模型。这是因为召回率越高,假阴性就越低。 由于我们的模型有84%的召回率,让我们暂时假定它是一个合理的值。 ### 预测 一旦你训练好了你的模型,而且它的性能可以接受,现在你就可以执行这个模型,以便预测结果。 你应该使用 [PREDICT](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_PREDICT) 函数,以便用户对模型进行预测。 ``` SELECT TOP 100 ID, PREDICT(Readmission) AS PredictedReadmission, MxWillReAdmit AS ActualReadmission FROM EncountersNew ``` 这个函数根据内部阈值,评估模型的目标列的行是真值还是假值的概率。 你可以看到我们的模型做了一些错误的预测--这很正常。 另一个可以用来进行预测的函数,是 [PROBABILITY](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_PROBABILITY) 函数. ``` SELECT TOP 10 ID, PROBABILITY(Readmission FOR '1') AS ReadmissionProbability, PREDICT(Readmission) AS PredictedReadmission, MxWillReAdmit AS ActualReadmission FROM EncountersNew ``` 与PREDICTION相同,PROBABILITY使用一行的列作为指定模型的输入,但在这里我们需要定义我们想要计算的概率是哪一类。 如果我们想定制用于预测的阈值,这个函数可能很有用。 ### 查询训练好的模型 在创建和训练你的模型后,你可以使用 INFORMATION_SCHEMA.ML_TRAINED_MODELS 表来查询它们。 ``` SELECT * FROM INFORMATION_SCHEMA.ML_TRAINED_MODELS ``` ## 结论 在这篇文章中,我们对历史上的病人再入院数据集进行了创建、训练、验证和预测。这些预测可以作为寻求更好决策的另一个工具,从而改善对病人的护理,降低医院服务的成本。 所有这些步骤都是通过InterSystems IntegratedML使用SQL语言完成的,它为广大的开发者带来了机器学习的力量。