目录
前言
Caché
的一个关键特性是它结合了对象技术和 SQL
。您可以为任何给定方案使用最方便的访问模式。
Caché
提供了有时称为对象数据库
的东西:一个与面向对象编程语言相结合的数据库。因此,您可以编写灵活的代码来执行以下所有操作:
- 通过 SQL 执行数据大容量插入。
- 打开对象,对其进行修改并保存,从而在不使用 SQL 的情况下更改一个或多个表中的数据。
- 创建并保存新对象,在不使用 SQL 的情况下向一个或多个表添加行。
- 使用 SQL 从与给定条件匹配的记录中检索值,而不是循环访问大量对象。
- 删除对象,从一个或多个表中删除记录,而不使用 SQL。
也就是说,您可以在任何给定时间选择适合您需求的访问模式。
在内部,所有访问都是通过直接全局访问完成的,您也可以在适当的时候以这种方式访问数据。(如果您有类定义,则不建议使用直接全局访问来更改数据)
Caché SQL
Caché
提供了SQL
的实现,称为Caché SQL
。
Caché SQL
支持完整的入门级 SQL-92
标准,但有一些例外和几个特殊扩展。Caché SQL
还支持索引、触发器、BLOB 和存储过程(这些是典型的 RDBMS
功能,但不是 SQL-92
标准的一部分)。
在哪里可以使用 Caché SQL
您可以在Routines
和方法中使用 Caché SQL
。若要在这些上下文中使用 SQL
,可以使用以下的两种方式:
- 嵌入式 SQL,如以下示例所示:
&sql(SELECT COUNT(*) INTO :myvar FROM Sample.Person)
Write myvar
动态 SQL(%SQL.Statement 和%SQL.StatementResult 类),如下面的示例所示:
SET myquery = "SELECT TOP 5 Name,DOB FROM Sample.Person"
SET tStatement = ##class(%SQL.Statement).%New()
SET tStatus = tStatement.%Prepare(myquery)
SET rset = tStatement.%Execute()
//now use proprties of rset object
SQL 的对象扩展
为了便于在对象应用程序中使用 SQL,Caché 在 SQL 中包含了许多对象扩展。
箭头运算符
这些扩展中最有趣的扩展之一是能够使用引用(“–>
”)运算符跟踪对象引用
。例如,假设您有一个供应商引用其他两个类的类:联系和地区.您可以使用引用运算符引用相关类的属性:
SELECT ID,Name,ContactInfo->Name
FROM Vendor
WHERE Vendor->Region->Name = 'Antarctica'
当然,也可以使用 SQL JOIN 语法表示相同的查询。引用运算符语法的优点是它简洁明了,一目了然地易于理解。
持久类的特殊选项
在 Caché
中,所有持久性类都扩展了 %Library.Persistent
(也称为 %Persistent
)。此类为Caché
中的 object-SQL
对应关系提供了大部分框架。在持久性类中,您有如下选项:
- 用于打开、保存和删除对象的方法。
打开持久性对象时,可以指定并发锁定的程度,因为持久性对象可能由多个用户或多个进程使用。
当您打开对象实例并引用对象值属性时,系统也会自动打开该对象。此过程称为旋转(也称为延迟加载
)。然后,您也可以使用该对象。例如:
Set person=##class(Sample.Person).%OpenId(10)
Set person.Name="Andrew Park"
Set person.Address.City="Birmingham"
Do person.%Save()
同样,当您保存对象时,系统也会自动保存其所有对象值属性。这被称为深度保存
。有一个选项可以改为执行浅保存
。
- 默认查询(扩展数据块查询),它是包含此类对象数据的 SQL 结果集。
在此类(或其他类)中,可以定义其他查询; - 能够定义作为外键投影到 SQL 的类之间的关系。
关系是一种特殊类型的对象值属性,它定义两个或多个对象实例如何相互关联。每个关系都是双面的:对于每个关系定义,都有一个相应的逆向关系来定义另一侧。Caché 自动强制实施数据的参照完整性,一端的任何操作在另一侧立即可见。关系会自动管理其内存中和磁盘上的行为。它们还提供了优于对象集合的缩放和并发性 - 能够定义外键。实际上,添加外键以向现有应用程序添加参照完整性约束。对于新应用程序,定义关系会更简单。
- 能够在这些类中定义索引。
索引提供了一种机制,用于优化跨持久性类实例的搜索;它们定义了与类关联的常用请求数据的特定排序子集。它们在减少性能关键型搜索的开销方面非常有帮助。
可以对属于其类的一个或多个属性对索引进行排序。这使您可以对结果返回的顺序进行大量特定的控制。
此外,索引可以存储基于排序属性的查询经常请求的其他数据。通过将其他数据作为索引的一部分包括在内,可以大大提高使用该索引的查询的性能。当查询使用索引生成其结果集时,它可以在不访问主数据存储设施的情况下执行此操作。 - 能够在这些类中定义触发器,以控制插入、修改或删除行时发生的情况。
- 能够将方法和类查询投影为 SQL 存储过程。
- 能够将投影微调为 SQL(例如,指定 SQL 查询中看到的表名和列名)。
- 能够微调存储对象数据的全局变量的结构。
持久类的 SQL 投影
对于任何持久性类,该类的每个实例都可用作表中的一行,您可以通过 SQL 对其进行查询和操作。
由于继承不是关系模型的一部分,因此类编译器将持久性类的“扁平化”表示形式投影为关系表。下表列出了如何将某些各种对象元素投影到 SQL:
Object Concept | SQL Concept |
---|---|
Package | Schema |
Class | Table |
Data type property (数据类型属性) | Field |
Embedded object | Set of fields |
List property | List field |
Array property | Child table |
Stream property | BLOB |
Index | Index |
Class method marked as stored procedure | Stored procedure(存储过程) |
投影表包含该类的所有相应字段,包括继承的字段。
对象 IDs
每个对象在其所属的每个范围内都有一个唯一的 ID。在大多数情况下,您可以使用此 ID 来处理对象。此 ID 是 %Persistent
类的以下常用方法的参数:
- %DeleteId()
- %ExistsId()
- %OpenId()
如何确定 ID
Caché
会在您首次保存对象时分配ID
值。任务是永久性的;您无法更改对象的 ID。删除或更改其他对象时,不会为对象分配新 ID。
任何 ID 在其范围内都是唯一的。
对象的 ID 按如下方式确定:
- 对于大多数类,默认情况下,ID 是在保存该类的对象时按顺序分配的整数。
- 对于在父子关系中用作子类的类,ID 的形成如下所示:
parentID||childID
其中 parentID
是父对象的 ID,childID
是子对象在父子关系中未使用时将接收的 ID
- 如果类的索引类型为 IdKey,并且索引位于特定属性上,则该属性值将用作 ID。
SKU-447
此外,属性值不能更改。
- 如果类具有 IdKey 类型的索引,并且该索引位于多个属性上,则这些属性值将串联起来形成 ID。例如:
CATEGORY12||SUBCATEGORYA
访问 ID
若要访问对象的 ID 值,请使用对象从 %Persistent
继承的 %Id()
实例方法。
在 SQL 中,对象的 ID 值可用作名为 %Id
的伪字段。
存储
每个持久性类定义都包含描述如何将类属性映射到实际存储它们的全局变量的信息。类编译器为类生成此信息,并在您修改和重新编译时对其进行更新。
查看存储定义
查看此信息可能很有用,在极少数情况下,您可能希望更改某些详细信息(非常仔细)。对于持久性类,Studio 会在类定义中显示如下所示的内容:
<Storage name="Default">
<Data name="PersonDefaultData"><Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>Name</Value>
</Value>
<Value name="3">
<Value>SSN</Value>
</Value>
<Value name="4">
<Value>DOB</Value>
</Value>
...
</Storage>
持久类使用的全局变量
存储定义包括几个元素,这些元素指定存储数据的全局变量:
<DataLocation>^Sample.PersonD</DataLocation>
<IdLocation>^Sample.PersonD</IdLocation>
<IndexLocation>^Sample.PersonI</IndexLocation>
...
<StreamLocation>^Sample.PersonS</StreamLocation>
默认情况下,使用默认存储:
- 类数据存储在类的数据全局中。其名称以完整的类名(包括包名)开头。名称后面附加“D”。例如:Sample.PersonD
- 索引数据存储在类的索引全局中。它的名称以类名开头,以“I”结尾。例如:Sample.PersonI
- 任何已保存的流属性都存储在类的流全局中。它的名称以类名开头,以“S”结尾。例如:Sample.PersonS
注意
:
如果完整的类名很长,系统会自动使用类名的哈希形式。因此,当您查看存储定义时,有时可能会看到全局名称,例如 ^package1.pC347.VeryLongCla4F4AD
。如果出于任何原因计划直接使用某个类的数据全局,请确保检查存储定义,以便知道全局变量的实际名称。
存储对象的默认结构
对于典型类,大多数数据都包含在数据全局中,其中包括如下节点:
节点 | 节点内容 |
---|---|
^full_class_name(“id”) 其中 full_class_name 是包含包的完整类名,如有必要,将长度保持在 31 个字符以求散列。此外,id 是“对象 ID”中所述的对象 ID。 | $ListBuild返回的格式列表。在此列表中,存储的属性按存储定义中<值>元素的 name 属性给出的顺序列出。根据定义,不存储瞬态属性。流属性存储在类的流全局中 |
注意点
请注意以下几点:
- 切勿为已存储数据的类重新定义或删除存储。如果这样做,则必须手动重新创建存储,因为在下次编译类时创建的新默认存储可能与类所需的存储不匹配。
- 在开发过程中,您可能希望重置类的存储定义。如果同时删除数据,然后重新加载或重新生成数据,则可以执行此操作。
- 默认情况下,当您在开发过程中添加和删除属性时,系统会通过称为架构演变的过程自动更新存储定义。
例外情况是,如果对<Type>
元素使用非默认存储类。默认值为%Library.CacheStorage
;如果不使用此存储类,Caché
不会更新存储定义。另一个常用选项是%Library.CacheSQLStorage
,它主要用于支持在Caché
提供类之前编写的应用程序。
用于创建持久类和表的选项
若要创建持久性类及其相应的SQL
表,可以执行以下任一操作:
- 使用
Studio
定义基于%Persistent
的类。编译类时,系统将创建表。 - 在管理门户中,可以使用数据迁移向导,该向导读取外部表,提示你输入一些详细信息,生成基于
%Persistent
的类,然后将记录加载到相应的SQL
表中。
您可以稍后再次运行该向导以加载更多记录,而无需重新定义该类。 - 在管理门户中,可以使用链接表向导,该向导读取外部表,提示你输入一些详细信息,并生成链接到外部表的类。该类在运行时从外部表中检索数据。
这是一个特例,本书不作进一步讨论。 - 在管理门户中,可以使用 FileMan 向导,该向导读取 FileMan 文件并创建类。
在 Caché SQL 中,使用 CREATE TABLE 或其他 DDL 语句。这还会创建一个类。 - 在终端(或代码)中,使用
%SQL.Util.Procedures
中的CSVTOCLASS()
方法。
访问数据
若要访问、修改和删除与持久性类关联的数据,您的代码可以执行以下任何或所有操作:
- 打开持久性类的实例,修改它们,然后保存它们。
- 删除持久性类的实例。
- 使用嵌入式
SQL
。 - 使用动态
SQL
(SQL 语句和结果集接口)。 - 使用低级命令和函数进行直接全局访问。请注意,除了检索存储的值之外,不建议使用此技术,因为它会绕过由对象和 SQL 接口定义的逻辑。
Caché SQL
适用于以下情况:
- 您最初不知道要打开的实例的 ID,而是会根据输入条件选择一个或多个实例。
- 您希望执行批量加载或进行批量更改。
- 您希望查看数据,但不希望打开对象实例。
(但请注意,使用对象访问时,可以控制并发锁定的程度。如果您知道不打算更改数据,则可以使用最小并发锁定) - 您精通 SQL。
对象访问适用于以下情况:
- 您正在创建一个新对象。
- 您知道要打开的实例的 ID。
- 您会发现设置属性值比使用 SQL 更直观。
查看存储的数据
本节演示,对于任何持久性对象,相同的值通过对象访问
、SQL
访问和直接全局访问
可见。
在工作室中,如果我们查看样本.人类,我们看到以下属性定义:
/// Person's name.
Property Name As %String(POPSPEC = "Name()") [ Required ];
...
/// Person's age.<br>
/// This is a calculated field whose value is derived from <property>DOB</property>.
Property Age As %Integer [ details removed for this example ];
/// Person's Date of Birth.
Property DOB As %Date(POPSPEC = "Date()");
在终端中,我们可以打开一个存储对象并写入其属性值:
SAMPLES>set person=##class(Sample.Person).%OpenId(1)
SAMPLES>w person.Name
Newton,Dave R.
SAMPLES>w person.Age
14
SAMPLES>w person.DOB
58153
请注意,在这里我们看到DOB
属性的文字存储值。我们可以调用一个方法来返回此属性的显示值:
SAMPLES>write person.DOBLogicalToDisplay(person.DOB)
03/20/2000
在管理门户中,我们可以浏览此类的存储数据,如下所示:
请注意,在本例中,我们将看到DOB
属性的显示值。(在门户中,还有另一个用于执行查询的选项,使用该选项可以控制对结果是使用逻辑模式还是显示模式。
在门户中,我们还可以浏览包含此类数据的全局变量:
或者,在终端中,我们可以写入包含此实例的全局节点的值:
zw ^Sample.PersonD("1")
^Sample.PersonD(1)=$lb("","Newton,Dave R.","384-10-6538",58153,$lb("6977 First Street","Pueblo","AK",63163),
$lb("9984 Second Blvd","Washington","MN",42829),"",$lb("Red"))
存储 Caché SQL 的生成代码
对于 Caché SQL
(用作嵌入式 SQL 时除外),系统会生成可重用的代码来访问数据。
首次执行 SQL
语句时,Caché 会优化查询并生成和存储检索数据的代码。它将代码与优化的查询文本一起存储在查询缓存中。请注意,此缓存是代码缓存
,而不是数据缓存。
稍后,当您执行 SQL
语句时,Caché
会对其进行优化,然后将该查询的文本与查询缓存中的项进行比较。如果 Caché 找到与给定查询匹配的存储查询(除了空格等细微差异),它将使用为该查询存储的代码。
您可以查看查询缓存并删除其中的任何项目。