背景与痛点
InterSystems IRIS 是一个强大的数据平台,其底层支持名为 Global 的数据结构。然而,并非所有数据都会映射到标准的 SQL 表中:
- 数据可能直接存储在 Global 中。
- 数据可能定义在类的参数里。
对于这些数据,常规的 SELECT ... FROM table 方式无法直接查询。通常,我们只能通过调用存储过程(CALL) 来获取它们。但这带来了几个问题:
- 迁移兼容性:代码迁移到低版本 Caché 数据库时,数据类型定义可能出错,且可能不再支持
SELECT调用存储过程的方式。 - 对第三方开发者不友好:对于熟悉 SQL 但不熟悉 ObjectScript 的开发者来说,使用
CALL语法不够直观。
本文将介绍一种称为 “进程表” 的方案,它巧妙结合了 Global 和 SQL 表的特性,让您能够用标准的 SELECT 语句查询这类“非表”数据。
什么是“进程表”?
进程表是一种虚拟的 SQL 表。它的核心思路是:
- 创建一个标准的持久化类(对应 SQL 表结构)。
- 手动修改其存储定义,将数据存储位置指向进程私有 Global(格式为
^||GlobalName)。这种 Global 仅存在于当前进程中,生命周期随进程结束而结束,非常适合用作临时数据容器。 - 编写一个类方法(标记为 SQL 存储过程),在该方法中将您想查询的数据(无论是来自 Global、类参数还是其他逻辑)写入这个进程私有 Global。
- 通过一个特殊的
WHERE子句调用该方法,从而“填充”这个虚拟表,并用SELECT查询出结果。
调用形式最终将统一为:
sql
SELECT * FROM YourTable WHERE YourTable_Method() = 1
完整示例
以下是一个名为 User.People 的“进程表”实现示例,它生成 100 条模拟的人员数据。
objectscript实体类
Class User.People Extends %Persistent{
/// 姓名
Property Name As %String;
/// 出生日期
Property DateOfBirth As %Date;
/// 性别
Property Gender As %String;
}
Query代码
/// 核心方法:填充进程表数据
/// 将此方法定义为SQL存储过程,它将在SELECT的WHERE子句中被调用
ClassMethod GLB(Args...) As %Status [ Language = objectscript, SqlProc ]
{
// 设置错误处理
s $zt="Error"
// 清空进程Global,确保每次查询都是新数据
k ^||User.PeopleD, ^||User.PeopleI
// 循环生成100条示例数据
for i=1:1:100 {
// 随机生成性别代码 (1 或 2)
s genderCode = $r(2) + 1
// 使用工具类生成随机的姓名和日期
s name = ##class(%PopulateUtils).Name(genderCode)
s dob = ##class(%PopulateUtils).Date(+$h-2000, +$h-10)
// 将性别代码转换为文本
s genderText = $case(genderCode, 1:"Male", 2:"Female", :"Unknown")
// 将数据以 $listbuild 格式存入进程Global
// 注意:第一个元素通常留空或用于其他用途
s ^||User.PeopleD($i(^||User.PeopleD)) = $lb("", name, dob, genderText)
}
// 返回成功状态
q $$$OK
Error
// 错误处理块
s $zt=""
// 将错误信息写入Global,以便在查询结果中看到
s ^||User.PeopleD($i(^||User.PeopleD)) = $lb("", "ERROR: "_$ze)
q $$$OK
}
/// 存储定义 - 这是关键,将数据位置指向进程私有Global
Storage Default
{
<Data name="PeopleDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>Name</Value>
</Value>
<Value name="3">
<Value>DateOfBirth</Value>
</Value>
<Value name="4">
<Value>Gender</Value>
</Value>
</Data>
<!-- 注意前缀 ^|| ,这表示进程私有Global -->
<DataLocation>^||User.PeopleD</DataLocation>
<DefaultData>PeopleDefaultData</DefaultData>
<IdLocation>^||User.PeopleD</IdLocation>
<IndexLocation>^||User.PeopleI</IndexLocation>
<StreamLocation>^||User.PeopleS</StreamLocation>
<Type>%Storage.Persistent</Type>
}
如何使用?
- 编译上述
User.People类。 - 在 SQL Shell 或任何 SQL 查询工具中,执行以下查询:
sql
SELECT * FROM User.People WHERE People_GLB() = 1
执行过程解析:
WHERE People_GLB() = 1会触发GLB类方法的执行。GLB方法会向^||User.PeopleD中写入100条数据。- SQL 引擎会从
^||User.PeopleD中读取数据,并按照User.People表的定义进行格式化。 - 最终返回一个包含100行记录的标准结果集。
关键要点与注意事项
- 固定调用模式:
SELECT ... WHERE TableName_Method() = 1是一个固定模式。=右边的值会作为参数传递给方法,但方法本身必须返回状态,=1是约定俗成的写法,表示“执行该方法以准备数据”。 - 异常捕获至关重要:
GLB方法中的$zt错误处理是必须的。它可以防止存储过程执行出错导致整个 SQL 查询崩溃,而是将错误信息作为一行数据返回,保证查询能完成。 - 内存考虑:此方案会在 IRIS 进程内存中构建结果集。如果用于导出海量数据(例如百万、千万行),请确保为 IRIS 分配了足够的内存,否则可能导致
错误。 - 方案适用性:进程表是一种将任意数据“伪装”成 SQL 表的通用适配器方案。它特别适用于:
- 将复杂的 Global 结构暴露给 BI 报表工具。
- 让只会 SQL 的第三方人员能查询系统内部数据。
- 临时性的、一次性的数据抽取和转换。
- 是否采用,需根据具体的性能要求、数据量和使用场景来权衡。
评论