#对象数据模型

0 关注者 · 19 帖子

对象数据模型是指数据或者由将数据和处理数据的过程的模块组合的代码。

了解更多信息

文章 Hao Ma · 三月 6, 2024 2m read

[翻译文章:大型数据集的存储注意事项]这篇文章其实很有用, 但恐怕注意的人不多,特意写文章介绍一下。

USEEXTENTSET翻译过来就是"使用EXTENT集合“,不够直白,它的工作简单说就是:通过哈希类名,使用更短的Global名字

ObjectScript的持久类编译后会默认使用"^类名D" 和"^类名I"的global来存储数据和索引, 当在类定义里加入`Parameter USEEXTENTSET = 1;` 这句话后, 会使用一个自动生成的短类名。 比如*Class User.EMR.MSG.Items.FirstPageCostsItem1*, 默认的存储为类似“^User.EMR.MEC31.FirstPageCoC0D4D”, 使用USEEXTENTSET“后会定义为“^U3Dx.EOXs.1”。

短Global名字的好处 

global名字缩短了,数据和索引的占用并不会改变。改变的是Journal的尺寸。 同样的表和数据插入, 之前的Journal是这样的:

 

使用短global名字后的journal记录是这样的:

 

我的测试中, 使用USEEXTENTSET减少了30%的Journal尺寸。

0
0 114
文章 Michael Lei · 八月 31, 2023 1m read

InterSystems 常见问题解答

通过在持久类(=table)定义中提供的%BuildIndices() 方法的参数中指定要重建索引的 ID 的开始值和结束值,您可以仅重建该范围内的索引。

例如,要仅针对 ID=10 到 20 重建 Sample.Person 类中的 NameIDX 索引和 ZipCode 索引,请执行以下代码(ID 范围在第 5 个和第 6 个参数中指定)。

set status = ##class (Sample.Person). %BuildIndices ( $LB ( "NameIDX" , "ZipCode" ), 1 ,, 1 , 10 , 20 )

$LB() 是$ListBuild() 函数。 %BuildIndices() 方法使用它来指定索引名称。

有关如何重建索引的更多信息,请参阅文档

2018.1版本请参考此文档

0
0 176
文章 Weiwei Gu · 八月 4, 2023 3m read

InterSystems IRIS 目前将类限制为 999 个属性。

但是,如果您需要为每个对象存储更多数据该怎么办?

本文将回答这个问题(附加了社区 Python 网关的客串以及如何将广泛的数据集传输到 Python 中)。

答案其实很简单 - InterSystems IRIS 目前将类限制为 999 个属性,但不限制 999 个基元(primitives)。 InterSystems IRIS 中的属性可以是具有 999 个属性的对象等等 - 该限制很容易被忽略。

0
0 176
文章 Michael Lei · 四月 3, 2023 2m read

如果您使用过 Caché Objects,你已经知道所有关于关系(一对多,父子孙继承等)......
但是您不会在文档中找到关于多对多关系的地方。

但我经常遇到 Caché 对象的新用户提出的问题:
“是否可以实现多对多关系?”答案是——当然!

如何实现取决于相关的任务:有一个比较重的和一个轻量级的解决方案。
两者的共同点是它们不能开箱即用需要添加一些代码来管理它。

让我们举一个基于 SAMPLES 命名空间的例子:

我们有 N:1 关系的情况

但是,您如何处理超过 1 个公司的员工?

重的解决方案:
您添加一个额外的持久类,与双方都有一个:多的关系。

优点:您可以添加管理信息作为有效性、各种时间戳……
以及与此“链接”相关的其他内容。
但它是一个额外的持久类,具有所有优点和缺点。所以我觉得这个很重。
该类可能如下所示:

0
0 172
文章 Qiao Peng · 二月 22, 2023 12m read

面向对象编程的优势

在应用程序开发时,我们使用的大多数开发语言都是面向对象编程 object-oriented programming (OOP)语言,例如大家熟悉的Java、.NET。而TIOBE的2023年2月的最新开发语言流行排行榜上,前5大语言都是面向对象编程语言,连排名第六的Visual Basic都有了越来越多的OO特性:

为什么使用面向对象编程这么流行?因为它有诸多优势:

  • 封装:将数据和操作数据的代码封装在一个单元中,在确定范围的数据上进行编程。方便代码的开发、管理与分享。
  • 抽象:将业务数据概括为不同的对象类型,从而进行业务分组开发、简化程序。
  • 继承:一个类可以从另一个类继承它属性和行为,从而实现更大范围的代码复用。
  • 多态:多个对象可以创建自一个类,且可以有不同的行为。一段灵活的代码能实现多种形态的业务,它进一步降低了代码开发量和调用难度。

而这4个优势正是面向对象编程的核心特征。

关系型数据库的对象/关系错配

虽然面向对象编程是绝对的主流,但数据通常被保存到关系型数据库中。关系型数据库的行和列二维关系与复杂的对象并不匹配:

0
0 597
文章 Lilian Huang · 九月 1, 2022 6m read

在我们开始谈论数据库和现有的不同数据模型之前,我们最好先谈谈什么是数据库以及如何使用它。

一个数据库是以电子方式存储和访问的有组织的数据集合。它用于存储和检索通常与主题或活动相关的结构化、半结构化或原始数据。
每个数据库的核心至少存在一个用于描述其数据的模型。并且根据它所基于的模型,一个数据库可能具有略微不同的特征并存储不同数据类型。

要写入、检索、修改、排序、转换或打印数据库中的信息,需要使用称为数据库管理系统 (DBMS) 的软件。

数据库及其各自的数据库管理系统的大小、容量和性能增加了几个数量级。各个领域的技术进步使之成为可能,例如处理器、计算机内存、计算机存储和计算机网络。一般来说,数据库技术的发展根据数据模型或结构分为四代:导航型、关系型、对象型和后关系型。

与以特定数据模型为特征的前三代不同,第四代包括许多基于不同模型的不同数据库。它们包括列、图、文档、组件、多维、键值、内存等。所有这些数据库都由一个单一的名称 NoSQL 联合起来(没有 SQL,或者现在更准确地说不仅仅是 SQL)。

而且,现在出现了一个新的类,叫做NewSQL这些是现代关系数据库,旨在为在线事务处理工作负载(读写)提供与 NoSQL 系统相同的可扩展性能,同时使用 SQL 和维护 ACID

0
0 387
文章 Michael Lei · 八月 9, 2022 1m read

在Caché基础课程中,同学们同时学习了Caché开发和ObjectScript语法。为了帮助大家完成练习,我们提供了ObjectScript快速参考(俗称 "小抄")。

它不是所有ObjectScript的参考资料! 它是学生在课程中使用的ObjectScript命令和函数的列表,以及对象、集合等的常用语法。它还包含一些有用的宏。

我们正在为开发者社区提供一个pdf版本。

0
0 476
问题 Michael Lei · 四月 29, 2022

我想知道是否有更好的方法来使用动态SQL对数据集进行分页,而不是我下面使用的方法。问题是,当潜在的数据池变大时,这段代码就会变慢,以至于无法使用。在分析下面的每一行代码时,似乎速度变慢与最初的rset.%Next()迭代有关。 有没有什么不需要子查询/%VID的可用方法,比如简单的LIMIT/OFFSET?

我的代码类似于:

s sql=##class(%SQL.Statement).%New()

s query="SELECT *,%VID FROM (SELECT prop FROM table WHERE prop=x) WHERE %VID BETWEEN 1 AND 100"             

s sc=sql.%Prepare(query)

s rset=sql.%Execute()

while rset.%Next() {.....

1
1 502
文章 Michael Lei · 六月 26, 2022 5m read

前一篇文章中,我已经演示了一种简单的方法来记录数据的变化。在这个时候,我改变了负责记录审计数据的 "审计抽象类 "和记录审计日志的数据结构。

我已经将数据结构改为父子结构,其中将有两个表来记录 "交易 "和在该交易中改变的 "字段的值"。

看一下新的数据模型:

看看从 "审计类 "改变的代码吧:

0
0 225
文章 Weiwei Gu · 二月 24, 2022 7m read

@Ming Zhou 在 https://community.intersystems.com/post/how-get-all-properties-defined-c 上问了一个很好的问题......而这个答案正好总结了为什么ObjectScript 是我最喜欢的语言。

当我第一次向别人介绍ObjectScript或IRIS时,我总是解释说,你可以写一个类,编译它,得到一个表,并从对象或关系的角度来处理你的数据--这是一种最自然的方式。

但无论哪种方式,它都只是一个薄薄的包装,包裹着被称为Globals的超级快速的内部数据结构,当你真的需要更快的速度时,你可以使用这些结构。

当我和比较爱较真的技术人员沟通时,我会告诉他们:ObjectScript允许各种炫酷的元编程,因为你可以用完全相同的方式与你刚刚写的类进行交互--从对象或关系的角度。或者在你需要额外的速度时直接使用超快的底层数据结构。

你可以在对这个问题的回答中看到答案: "我怎样才能得到一个类中的所有属性,包括继承的属性?"

这里有三种不同的方式来获得相同的答案。

1
0 309
文章 Michael Lei · 十一月 2, 2021 3m read

作为一个12岁的书呆子,我最喜欢的书是斯蒂芬-皮尔写的《英雄失败之书》,这是一本关于人类不足之处的纪事。对我来说,这本书的亮点是佩德罗-卡罗莱纳的故事,一个努力开发葡语-英语短语手册的人。这是一项崇高的事业,但由于他缺乏说英语的能力,也没有一本葡英字典,因此受到阻碍。

然而,他确实拥有一本法英词典,一本葡法词典和一种真正能干的态度。他的劳动成果对葡萄牙度假者毫无益处,却让12岁的我笑得歇斯底里。我经常愉快地回忆起这个故事,但直到我开始工作时,我才开始把自己和卡洛琳娜先生进行比较。

我的工作是一名信息建模师。信息建模师的工作可以轻描淡写地概括为把现实变成可以存储在数据库中的东西。这是通过设计信息模型来完成的。在传统的软件设计中,有三种类型的信息模型。概念性、逻辑性和物理性。你从概念模型开始,它是对我们想要数字化的东西以及它们之间的关系的一个高级描述。然后你转到逻辑模型,描述这些对象如何在数据库中理想地创建。最后,你在物理模型中实现这一点,物理模型是由用户维护数据的数据库的实际实现。
 
"这是信息建模的传统智慧。但这种方法是否能实现目的有待商榷"。
 
这是信息建模的传统智慧。但这种方法是否能实现目的有待商榷。佩德罗的短语书的故事之所以有趣,是因为其方法明显不合适,而且客观上结果很差。这在语言学上相当于用叉子喝汤。但是,当主题比较模糊时,就很难推断方法是如何以及何时有缺陷的。

0
0 373
文章 Louis Lu · 四月 15, 2021 15m read

IRIS 中支持的四种方式:

SQL、Objects、REST 和 GraphQL  

卡济米尔·马列维奇,《运动员》(1932) 

>

> “你当然无法理解! 习惯了坐马车旅行的人怎么可能理解乘坐火车或者飞机旅行的人的感受和印象?”
>

> >

> 卡济米尔·马列维奇 (1916)
>

## 引言

我们已经讨论过为什么在主题领域建模使用对象类型优于使用 SQL。 当时得出的结论和总结的事实如今依然适用。 那么,我们为什么要退后到对象和类型之前的时代,讨论将对象的操作拖回到使用global的技术? 我们又为什么要鼓励面条式代码?难道是为了用它难以跟踪的错误考验开发者的技能熟练度? 

目前有几种观点支持通过基于 SQL/REST/GraphQL 的 API 传输数据,而不是将其表示为类型/对象:

  • 这些技术经过深入研究,相当易于部署。
  • 知名度非常高,已在便捷的开源软件中广泛实现。
  • 您通常别无选择,只能使用这些技术,尤其是在网络和数据库中。
  • 最重要的是,API 仍然使用对象,因为它们提供了在代码中实现 API 的最适途径。

在讨论实现 API 之前,我们先来看一下底层的抽象层。 下图显示了数据在永久存储位置与处理并向应用程序用户呈现的位置之间的移动方式。

0
0 680
文章 Qiao Peng · 一月 14, 2021 7m read

您好! 本文介绍另一种为基于 InterSystems Caché 的解决方案创建安装程序的简单方法。 主题将涵盖只需一项操作即可安装或从 Caché 中完全删除的应用程序。 如果您仍在编写需要执行多个步骤才能安装应用程序的安装说明,是时候将这个过程自动化了。

问题的提出

假设我们为 Caché 开发了一个小型实用程序,之后我们想要将其分发。 当然,最好不要让不必要的配置和安装细节打扰到安装它的用户。 此外,这些说明必须非常全面,而且要面向可能对 Caché 一无所知的用户。如果是 Web 实用程序,安装程序不仅会要求用户将其类导入 Caché,而且至少还要配置 Web 应用程序才能对其进行访问,这是相当大的工作量:

当然,所有这些操作都可以通过编程方式执行。 您只需要了解如何实现。 但即使是这样,我们也需要让用户执行操作,例如在终端中执行一个命令。

通过单次导入操作进行安装

Caché 允许我们在类导入期间执行安装。 这意味着用户只需要使用任一方便的方法导入包含类包的 XML 文件:

  • 将 XML 文件拖放到 Studio 区域。
  • 通过管理门户:系统资源管理器 -> 类 -> 导入。
  • 通过终端:do $system.OBJ.Load("C:\FileToImport.xml","ck")
  • 我们为安装应用程序而预先准备的代码将在类导入和编译后立即执行。 如果用户要卸载我们的应用程序(删除软件包),我们还可以清理应用程序在安装过程中创建的所有内容。

    创建投影

    为了扩展 Caché 编译器的行为,或者,在我们的示例中,为了在类的编译或反编译期间执行代码,我们需要在软件包中创建一个投影类。 它是一个扩展了 %Projection.AbstractProjection 的类,并重载了它的两个方法:CreateProjection(在编译过程中执行)和 RemoveProjection(在重新编译或删除类时触发)。

    通常,将这个类命名为 Installer 是个好方法。 我们来看一个名为“MyPackage”的软件包的简单安装程序示例:

    Class MyPackage.Installer Extends %Projection.AbstractProjection [ CompileAfter = (Class1, Class2) ]
    {
    Projection Reference As Installer;
    /// This method is invoked when a class is compiled.
    ClassMethod CreateProjection(cls As %String, ByRef params) As %Status
    {
        write !, "Installing..."
    }
    /// This method is invoked when a class is 'uncompiled'.
    ClassMethod RemoveProjection(cls As %String, ByRef params, recompile As %Boolean) As %Status
    {
        write !, "Uninstalling..."
    }
    }

    这里的行为可以描述为:

    • 第一次导入和编译软件包时,只触发 CreateProjection 方法。
    • 以后再编译 MyApp.Installer 时,或者导入“新”的安装程序类覆盖“旧”类时,将触发旧类的 RemoveProjection 方法,且 %recompile 参数等于 1,之后调用新类的 CreateProjection 方法。
    • 删除软件包(同时删除 MyApp.Installer)时,将只调用 RemoveProjection 方法,参数 recompile = 0

    还需要注意以下几点:

    • 类关键字 CompileAfter 应该包括应用程序的类名列表,在执行投影类的方法之前,需要对它们进行编译。 始终建议在此列表中填入应用程序中的所有类,因为如果安装过程中出错,我们不需要执行投影类的代码;
    • 两个方法都接受 cls 参数 - 它是顶级类名,在我们的示例中为 MyApp.Installer。 这个理念来自于创建投影类的本义 - 通过从派生自 %Projection.AbstractProjection 的类再派生,可以单独为我们的应用程序的任何类制作“安装程序”。 只有在这种情况下才会体现出意义,但对于我们的任务来说是多余的;
    • CreateProjectionRemoveProjection 方法都接受第二个参数 params - 它是一个关联数组,以“参数名称”-“值”对的形式处理有关当前编译设置和当前类的参数值的信息。 通过执行 zwrite params 可以非常容易地探索该参数的内容;
    • RemoveProjection 方法接受 recompile 参数,只有删除类时,该参数才等于 0,重新编译时不等于 0。

    %Projection.AbstractProjection 还有其他方法,我们可以重新定义这些方法,但我们的任务并不需要这样做。

    一个示例

    让我们更深入地了解为我们的实用程序创建 Web 应用程序的任务,并创建一个简单示例。 假设我们的实用程序是一个 REST 应用程序,在浏览器中打开时,它只发送一个响应“I am installed!”。 要创建这样的应用程序,我们需要创建一个描述它的类:

    Class MyPackage.REST Extends %CSP.REST
    {
    XData UrlMap
    {
    <Routes>
        <Route Url="/" Method="GET" Call="Index"/>
    Routes>
    }
    ClassMethod Index() As %Status
    {
        write "I am installed!"
        return $$$OK
    }
    }

    创建并编译该类后,我们需要将其注册为 Web 应用程序入口点。 我在本文的顶部图示了如何进行配置。 在执行所有这些步骤后,最好通过访问 http://localhost:57772/myWebApp/ 来检查我们的应用程序是否正常工作(注意以下几点:1. 末尾的斜线是必需的;2. 端口 57772 在您的系统中可能有所不同。 它将匹配您的管理门户端口)。

    当然,所有这些步骤都可以通过 Web 应用程序创建方法 CreateProjection 以及删除方法 RemoveProjection 中的一些代码来自动执行。 在这种情况下,我们的投影类如下所示:

    Class MyPackage.Installer Extends %Projection.AbstractProjection [ CompileAfter = MyPackage.REST ]
    {
    Projection Reference As Installer;
    Parameter WebAppName As %String = "/myWebApp";
    Parameter DispatchClass As %String = "MyPackage.REST";
    ClassMethod CreateProjection(cls As %String, ByRef params) As %Status
    {
        set currentNamespace = $Namespace
        write !, "Changing namespace to %SYS..."
        znspace "%SYS" // we need to change the namespace to %SYS, as Security.Applications class exists only there
        write !, "Configuring WEB application..."
        set cspProperties("AutheEnabled") = $$$AutheUnauthenticated // public application
        set cspProperties("NameSpace") = currentNamespace // web-application for the namespace we import classes to
        set cspProperties("Description") = "A test WEB application." // web-application description
        set cspProperties("IsNameSpaceDefault") = $$$NO // this application is not the default application for the namespace
        set cspProperties("DispatchClass") = ..#DispatchClass // the class we created before that handles the requests
        return ##class(Security.Applications).Create(..#WebAppName, .cspProperties)
    }
    ClassMethod RemoveProjection(cls As %String, ByRef params, recompile As %Boolean) As %Status
    {
        write !, "Changing namespace to %SYS..."
        znspace "%SYS"
        write !, "Deleting WEB application..."
        return ##class(Security.Applications).Delete(..#WebAppName)
    }
    }

    在此示例中,每次编译 MyPackage.Installer 类都将创建一个 Web 应用程序,每次“反编译”都将其删除。 最好在创建或删除应用程序之前检查该应用程序是否存在(例如,使用 ##class(Security.Applications).Exists(“Name”) ),但是为了使本示例简单起见,这个作业就留给阅读本文的读者来完成了。

    在创建 MyPackage.RESTMyPackage.Installer 类之后,我们可以将这些类导出为一个 XML 文件,并将该文件分享给所有有需要的人。 导入此 XML 的用户将自动设置 Web 应用程序,然后可以开始在浏览器中使用。

    结果

    InterSystems 社区上介绍的使用 %Installer 类部署应用程序的方法不同,这种方法有以下优点:

    1. 使用“纯”Caché ObjectScript。 至于 %Installer,需要用特定标签来填充 xData 块,大量文档对此进行了介绍。
    2. 安装我们的应用程序的方法是在类编译后立即执行的,我们不需要手动执行;
    3. 如果类(包)被删除,将自动执行删除我们的应用程序的方法,这不能通过使用 %Installer 来实现。

    我的项目中已经使用这种应用程序安装方法 - Caché WEB TerminalCaché Class ExplorerCaché Visual Editor。 您可以在此处找到 Installer 类的示例。

    顺便提一下,开发者社区还有一个帖子介绍了投影的功能,作者是 John Murray

    另外值得一提的是 Package Manager 项目,该项目旨在让 InterSystems 数据平台的第三方应用程序只需通过一个命令或一次点击即可安装,就像类似 npm 的包管理器一样。

    0
    0 150
    文章 Qiao Peng · 一月 14, 2021 8m read

    三部曲中的第四部,有人是《银河系漫游指南》的粉丝吗?

    如果你希望为旧的 MUMPS 应用程序注入新的生命,请按照以下步骤将 global 映射到类,并将所有这些漂亮的数据公开给 Objects 和 SQL。

    如果上述内容听起来很陌生,请阅读以下几篇文章从头开始: [将 global 映射到类的技巧(第 1/3 部分)](https://community.intersystems.com/post/art-mapping-globals-classes-1-3) [将 global 映射到类的技巧(第 2/3 部分)](https://community.intersystems.com/post/art-mapping-globals-classes%C2%A0-2-3) [将 global 映射到类的技巧(第 3/3 部分)](https://community.intersystems.com/post/art-mapping-globals-classes-3-3)

    这篇文章是献给你的,Joel! 我们将在上一个示例中定义的父子关系的基础上,创建一个孙子类来处理添加到 ^ParentChild Global 中的新季节信息。

    同样的免责声明:如果你在阅读完这些文章后仍然对 global 没有头绪,请联系 WRC,我们将尽力为你提供帮助:Support@InterSystems.com。

    将 Global 映射到类的步骤:

    1. 1. 确定 global 数据中的重复模式。
    2. 2. 确定唯一键的组成。
    3. 3. 确定属性及其类型。
    4. 4. 在类中定义属性(不要忘记变量下标中的属性)。
    5. 5. 定义 IdKey 索引。
    6. 6. 定义存储定义:
      1. 定义直到 IdKey(包括 IdKey)的下标。
      2. 定义“Data(数据)”部分。
      3. 忽略“Row ID(行 ID)”部分。 99% 的时候,默认值就是你需要的,所以让系统填写。
    7. 7. 编译并测试你的类/表。

    ^ParentChild(1)="Brendan^45956"

    ^ParentChild(1,"Hobbies",1)="Pit Crew"

    ^ParentChild(1,"Hobbies",1,"Seasons")="Fall*Winter"

    ^ParentChild(1,"Hobbies",2)="Kayaking"

    ^ParentChild(1,"Hobbies",2,"Seasons")="Spring\*Summer\*Fall"

    ^ParentChild(1,"Hobbies",3)="Skiing"

    ^ParentChild(1,"Hobbies",3,"Seasons")="Summer*Winter"

    ^ParentChild(2)="Sharon^46647"

    ^ParentChild(2,"Hobbies",1)="Yoga"

    ^ParentChild(2,"Hobbies",1,"Seasons")="Spring\*Summer\*Fall*Winter"

    ^ParentChild(2,"Hobbies",2)="Scrap booking"

    ^ParentChild(2,"Hobbies",2,"Seasons")="Spring\*Summer\*Fall*Winter"

    ^ParentChild(3)="Kaitlin^56009"

    ^ParentChild(3,"Hobbies",1)="Lighting Design"

    ^ParentChild(3,"Hobbies",1,"Seasons")="Spring\*Summer\*Fall*Winter"

    ^ParentChild(3,"Hobbies",2)="pets"

    ^ParentChild(3,"Hobbies",2,"Seasons")="Spring\*Summer\*Fall*Winter"

    ^ParentChild(4)="Melissa^56894"

    ^ParentChild(4,"Hobbies",1)="Marching Band"

    ^ParentChild(4,"Hobbies",1,"Seasons")="Fall"

    ^ParentChild(4,"Hobbies",2)="Pep Band"

    ^ParentChild(4,"Hobbies",2,"Seasons")="Winter"

    ^ParentChild(4,"Hobbies",3)="Concert Band"

    ^ParentChild(4,"Hobbies",3,"Seasons")="Spring\*Summer\*Fall*Winter"

    ^ParentChild(5)="Robin^57079"

    ^ParentChild(5,"Hobbies",1)="Baking"

    ^ParentChild(5,"Hobbies",1,"Seasons")="Spring\*Summer\*Fall*Winter"

    ^ParentChild(5,"Hobbies",2)="Reading"

    ^ParentChild(5,"Hobbies",2,"Seasons")="Spring\*Summer\*Fall*Winter"

    ^ParentChild(6)="Kieran^58210"

    ^ParentChild(6,"Hobbies",1)="SUBA"

    ^ParentChild(6,"Hobbies",1,"Seasons")="Summer"

    ^ParentChild(6,"Hobbies",2)="Marching Band"

    ^ParentChild(6,"Hobbies",2,"Seasons")="Fall"

    ^ParentChild(6,"Hobbies",3)="Rock Climbing"

    ^ParentChild(6,"Hobbies",3,"Seasons")="Spring\*Summer\*Fall"

    ^ParentChild(6,"Hobbies",4)="Ice Climbing"

    ^ParentChild(6,"Hobbies",4,"Seasons")="Winter"

    步骤 1:

    为我们的新类找到重复数据并不是很难,就是 Seasons 子节点。 棘手的部分在于,我不希望“Spring*Summer*Fall”都在一行,我想要的是三行:“Spring”、“Summer”、“Fall”。

    ^ParentChild(1)="Brendan^45956"

    ^ParentChild(1,"Hobbies",1)="Pit Crew"

    ^ParentChild(1,"Hobbies",1,"Seasons")="Fall*Winter"

    ^ParentChild(1,"Hobbies",2)="Kayaking"

    ^ParentChild(1,"Hobbies",2,"Seasons")="Spring*Summer*Fall"

    ^ParentChild(1,"Hobbies",3)="Skiing"

    ^ParentChild(1,"Hobbies",3,"Seasons")="Summer*Winter"

    每个爱好可以有 1 到 4 个季节。 我想我们可以在 Example3Child 类中再创建 4 个属性,但是如果有人发明了一个新季节,我们该怎么办? 更灵活的解决方案是创建一个孙子表,这样季节的数量可以保持动态。

    步骤 2:

    我们都知道要查看下标来获取 IdKey 的各个部分,因此我们知道下标 1 和下标 3 将是 IdKey 的一部分,我们还需要一个值来唯一标识不同的季节,但是没有下标了!

    我们放在映射中的信息用于生成查询代码。 也许如果我们考虑需要什么 COS 命令来获取此信息,这将帮助我们定义映射。 我们需要做的 3 大件事是:

                    SET sub1=$ORDER(^Parentchild(sub1))

                    SET sub2=$ORDER(^Parentchild(sub1,”Hobbies”,sub2))

                    SET season=$PIECE(^ParentChild(sub1,”Hobbies”,sub2”,”Seasons”),”*”,PC)

    我们可以在映射的“下标”部分中执行相同的操作。 Caché SQL 存储支持 4 种不同类型的下标:Piece、Global、Sub 和 Other。 默认是 Sub,也就是到目前为止我们一直在使用的,它让我们获得了所需的 $ORDER() 循环。

    在此示例中,我们将介绍 Piece 选项。 在此级别使用的属性将用作 Piece 计数器(上面示例中的 PC)。 它的默认行为是递增 1,直到到达字符串末尾。

    步骤 3:

    3 个属性:数据很简单:Season,然后是关系属性:HobbyRef,最后还需要一个子下标:PieceCount

    步骤 4:

    Property Season As %String; Property PieceCounter As %Integer; Relationship HobbyRef As Mapping.Example3Child [ Cardinality = parent, Inverse = Seasons ];

    步骤 5:

    查看下标映射时,可以看到 3 个变量级别,但是对于 IdKey 索引,我们只引用 2 个属性:HobbyRef 和 PieceCounter

    Index Master On (HobbyRef, PieceCounter) [ IdKey ];

    步骤 6:

    还是我们一直在讨论的三个部分。 我们仍然忽略行 ID。 这次,我们需要更详细地说明下标,并定义访问类型。 此类将使用“Sub”和“Piece”。 如果你想看“Global”和“Other”的示例,你需要获得我的示例压缩文件。

    步骤 6a:

    主下标页看起来与往常一样,只添加了两个级别。 记住,对于父引用的两个部分,我们需要重新引用我们引用的类:{Mapping.Example3Parent} 和 {Mapping.Example3Child}。 当点击窗口左侧的一个下标级别时,就会显示出差异。

    在下图中,你可以看到在每个下标级别可以执行的所有不同操作。 对于访问类型“Sub”,假设你要从“”开始,然后 $ORDER(),直到到达“”。 如果不是这种情况,你可以提供一个“开始值”或一个“停止值”。

    “Data Access(数据访问)”允许你更改从上一个级别看到的内容,例如,你可以更改要循环的 global。

    “Next Code(后续代码)”和“Invalid Conditions(无效条件)”结合在一起,是该窗口中最常使用的内容。 如果简单的 $ORDER() 不能让你从一个有效值转到下一个有效值,则可以编写你自己的代码让我们使用。

    “Next Code(后续代码)”将用于从一个有效值转到下一个有效值,就像 $ORDER() 一样。

    “Invalid Conditions(无效条件)”用于测试给定值。 如果你为“subscriptX”提供了一个值,不需要调用 Next 来找到它,你已经提供了它。 需要的是一些代码,用于确定该值是否有效。

    我的长期承诺的示例压缩文件中有许多使用“Next Code(后续代码)”和“Invalid Conditions(无效条件)”的类。

    “Access Variables(访问变量)”是该页的最后一项内容,很少使用。 基本上,你可以在这里设置变量,在一个下标级别为其赋一个值,然后在更高的下标级别中使用它。 生成的表代码将为你处理范围。

    对于下标级别 5,“Access Type(访问类型)”为“Piece”,“Delimiter(分隔符)”为“*”。 生成的代码将从 Piece 1 开始,然后递增 1,直到用完 $PIECE 值为止。 重申一次,我们可以提供“Start Values(开始值)”或“Stop Values(停止值)”来对此进行控制。

    步骤 6b:

    数据部分只是一个属性;对于“Piece”或“Delimiter(分隔符)”来说不需要。 如果此表有更多字段,我们很可能需要提供“Piece”和“Delimiter(分隔符)”,那样也没问题。

    步骤 6c: 仍然将此处留空。

    步骤 7:

    所有代码都编译得干净漂亮。

    Compilation started on 11/30/2016 08:17:42 with qualifiers 'uk/importselectivity=1 /checkuptodate=expandedonly'
    Compiling 2 classes, using 2 worker jobs
    Compiling class Mapping.Example3Child
    Compiling class Mapping.Example3GrandChild
    Compiling table Mapping.Example3GrandChild
    Compiling table Mapping.Example3Child
    Compiling routine Mapping.Example3Child.1
    Compiling routine Mapping.Example3GrandChild.1
    Compilation finished successfully in 1.021s.

    三个表的连接:

    SELECT P.ID, P.Name, P.DateOfBirth,

    C.ID, C.Hobby, G.ID, G.Season

    FROM Mapping.Example3Parent P

    JOIN Mapping.Example3Child C ON P.ID = C.ParentRef

    JOIN Mapping.Example3Grandchild G ON C.ID = G.HobbyRef

    WHERE P.Name = 'Kieran'

    得出:

    ID姓名出生日期ID爱好ID季节
    6Kieran05/16/20006||1SUBA6||1||1Summer
    6Kieran05/16/20006||2Marching Band6||2||1Fall
    6Kieran05/16/20006||3Rock Climbing6||3||1Spring
    6Kieran05/16/20006||3Rock Climbing6||3||2Summer
    6Kieran05/16/20006||3Rock Climbing6||3||3Fall
    6Kieran05/16/20006||4Ice Climbing6||4||1Winter

    记住,我说过子表 IdKey 总是由两部分组成:父引用和子下标。

    在第一行中,Example3Child ID 为 6||1:父引用 = 6,子下标 = 1。

    对于 Example3GrandChild,IdKey 由三部分组成:6||1||1,但仍然是父引用和子下标。 父引用只是更复杂一些:父引用 = 6||1,子下标 = 1。

    在 Example3Child 中,下标中的属性数与 IdKey 中的属性数相匹配。 在父子结构中嵌套更深入时,IdKey 会变成复合形式,并且下标的数量将增加。

    这是此示例中使用的 3 个类的导出:MappingExample4.zip。

    0
    0 225
    文章 Nicky Zhu · 一月 11, 2021 9m read

    简介

    许多应用程序都需要记录数据库中的数据变化,包括:哪些数据被更改、更改人和更改时间(审计日志记录) (维基百科audit logging)。 关于这个问题已经有了很多文章,而关于如何在Caché中实现也有很多不同的方法。

    本文将介绍一个机制,帮助您实现用一个框架来跟踪和记录数据更改。一旦您的持久类继承自“审计抽象类”(Sample.AuditBase),此机制将通过“objectgenarator”方法创建一个触发器。由于这个持久类继承了Sample.AuditBase,所以当您编译持久类时,将自动生成用于审计更改的触发器。


    Audit Class  

    这是将记录更改的类。

    Class Sample.Audit Extends %Persistent
    {
              Property Date As %Date;
              Property UserName As %String(MAXLEN = "");
              Property ClassName As %String(MAXLEN = "");
              Property Id As %Integer;
              Property Field As %String(MAXLEN = "");
              Property OldValue As %String(MAXLEN = "");
              Property NewValue As %String(MAXLEN = "");
    }

    Audit Abstract Class  

    这是您的持久类将继承的抽象类。这个类包含触发器方法(objectgenerator),除了在审核表(Sample.Audit)中写入更改之外,该触发器方法还知道如何识别哪些字段被更改、更改人、新旧值等。

    Class Sample.AuditBase [ Abstract ]
    {
    Trigger SaveAuditAfter [ CodeMode = objectgenerator, Event = INSERT/UPDATE, Foreach = row/object, Order = 99999, Time = AFTER ]
    {
              #dim %compiledclass As %Dictionary.CompiledClass
              #dim tProperty As %Dictionary.CompiledProperty
              #dim tAudit As Sample.Audit
              Do %code.WriteLine($Char(9)_"; get username and ip adress")
              Do %code.WriteLine($Char(9)_"Set tSC = $$$OK")
              Do %code.WriteLine($Char(9)_"Set tUsername = $USERNAME")
              Set tKey = ""
              Set tProperty = %compiledclass.Properties.GetNext(.tKey)
              Set tClassName = %compiledclass.Name
              Do %code.WriteLine($Char(9)_"Try {")
              Do %code.WriteLine($Char(9,9)_"; Check if the operation is an update - %oper = UPDATE")
              Do %code.WriteLine($Char(9,9)_"if %oper = ""UPDATE"" { ")
              While tKey '= "" {
                        set tColumnNbr = $Get($$$EXTPROPsqlcolumnnumber($$$pEXT,%classname,tProperty.Name))
                        Set tColumnName = $Get($$$EXTPROPsqlcolumnname($$$pEXT,%classname,tProperty.Name))
                        If tColumnNbr '= "" {
                                  Do %code.WriteLine($Char(9,9,9)_";")
                                  Do %code.WriteLine($Char(9,9,9)_";")
                                  Do %code.WriteLine($Char(9,9,9)_"; Audit Field: "_tProperty.SqlFieldName)
                                  Do %code.WriteLine($Char(9,9,9)_"if {" _ tProperty.SqlFieldName _ "*C} {")
                                  Do %code.WriteLine($Char(9,9,9,9)_"Set tAudit = ##class(Sample.Audit).%New()")
                                  Do %code.WriteLine($Char(9,9,9,9)_"Set tAudit.ClassName = """_tClassName_"""")
                                  Do %code.WriteLine($Char(9,9,9,9)_"Set tAudit.Id = {id}")
                                  Do %code.WriteLine($Char(9,9,9,9)_"Set tAudit.UserName = tUsername")
                                  Do %code.WriteLine($Char(9,9,9,9)_"Set tAudit.Field = """_tColumnName_"""")
                                  Do %code.WriteLine($Char(9,9,9,9)_"Set tAudit.Date = +$Horolog")
                                  Do %code.WriteLine($Char(9,9,9,9)_"Set tAudit.OldValue = {"_tProperty.SqlFieldName_"*O}")
                                  Do %code.WriteLine($Char(9,9,9,9)_"Set tAudit.NewValue = {"_tProperty.SqlFieldName_"*N}")
                                  Do %code.WriteLine($Char(9,9,9,9)_"Set tSC = tAudit.%Save()")
                                  do %code.WriteLine($Char(9,9,9,9)_"If $$$ISERR(tSC) $$$ThrowStatus(tSC)")
                                  Do %code.WriteLine($Char(9,9,9)_"}")
                        }
                        Set tProperty = %compiledclass.Properties.GetNext(.tKey)
              }
              Do %code.WriteLine($Char(9,9)_"}")
              Do %code.WriteLine($Char(9)_"} Catch (tException) {")
              Do %code.WriteLine($Char(9,9)_"Set %msg = tException.AsStatus()")
              Do %code.WriteLine($Char(9,9)_"Set %ok = 0")
              Do %code.WriteLine($Char(9)_"}")
              Set %ok = 1
    }
    }

    Data Class (Persistent Class)

    这是用户数据类,用户(应用程序)可以在其中进行更改、创建记录、删除记录,以及任何您允许他做的事情。总之,这通常是持久化类。

    要开始跟踪和记录更改,您需要从抽象类(Sample.AuditBase)继承这个持久类。

    Class Sample.Person Extends (%Persistent, %Populate, Sample.AuditBase)
    {
              Property Name As %String [ Required ];
              Property Age As %String [ Required ];
              Index NameIDX On Name [ Data = Name ];
    }

    测试

    由于您从审计抽象类(Sample.AuditBase)继承了数据类(Sample.Person),因此您能够插入数据、进行更改并查看审计类(Sample. Audit)上记录的更改。

    如果要测试一下,您需要在Sample.Person类或您选择的任何其他类创建一个Test()类方法。

    ClassMethod Test(pKillExtent = 0)
    {
              If pKillExtent '= 0 {
                        Do ##class(Sample.Person).%KillExtent()
                        Do ##class(Sample.Audit).%KillExtent()
              }
              &SQL(INSERT INTO Sample.Person (Name, Age) VALUES ('TESTE', '01'))
              Write "INSERT INTO Sample.Person (Name, Age) VALUES ('TESTE', '01')",!
              Write "SQLCODE: ",SQLCODE,!!!
              Set tRS = $SYSTEM.SQL.Execute("SELECT * FROM Sample.Person")
              Do tRS.%Display()
              &SQL(UPDATE Sample.Person SET Name = 'TESTE 2' WHERE Name = 'TESTE')
              Write !!!
              Write "UPDATE Sample.Person SET Name = 'TESTE 2' WHERE Name = 'TESTE'",!
              Write "SQLCODE:",SQLCODE,!!!
              Set tRS = $SYSTEM.SQL.Execute("SELECT * FROM Sample.Person")
              Do tRS.%Display()
              Quit
    }

    运行Test()方法:

    d ##class(Sample.Person).Test(1)

    参数1将消除Sample.Person和Sample.Audit类的内容。

     

    测试类方法负责:

    • 插入一个名为“TEST”的新person;
    • 显示插入结果;
    • 将person “TEST”更新为“TEST ABC”
    • 显示更新结果;

    现在您可以检查审核日志表。为此,依次打开系统管理门户 -> 系统探索 -> SQL。(记得切换到您的命名空间)

    运行以下 SQL 命令并检查结果:

    SELECT * FROM Sample.Audit 

    注意,OldValue是“TEST”,NewValue是“TEST ABC”。现在您可以通过将“TEST ABC”的名称更改为“您自己的名称”或更改年龄值(举个例子)来自行测试。见:

    UPDATE Sample.Person SET Name = 'Fabio Goncalves' WHERE Name = 'TEST ABC'


    生成的代码

    考虑到您已经实现了下面的审计机制,可以在您的计算机上启动Studio(或Atelier),打开持久类(Sample.Person)并检查编译Sample.Person类后生成的中间代码。为此,您只要按下Ctrl + Shift + V(查看其他源代码)- 检查 .INT。然后向下滚动到zSaveAuditAfterExecute标签,查看生成的代码:

     

    优点

    通过滚出旧数据实现审计日志记录,很简单。您不需要附加表。维护也很简单。如果您决定删除旧数据,那么这就是一个SQL的问题。

    如果您需要在更多表中实现审计日志记录,您只需从抽象类(Sample.AuditBase)继承即可。

    根据您的需要进行更改,例如:在流上记录更改。

    只记录已修改的字段。不保存已更改的整个记录。

    缺点

    有这样的问题:当数据更改时,那么整个记录会被复制,也就是没更改的数据也会被复制。

    如果表person有一个带有包含图片的二进制数据(流)字段-“photo”,那么每次用户更改图片时,流都会被记录(消耗磁盘空间)。  

    另一个缺点是,增加了每个支持审计日志记录的表的复杂性。请时刻牢记,检索记录不是一件简单的事情。切记必须有条件地使用SELECT子句:“...WHERE Status = active”或考虑添加“日期间隔”。

    考虑事务和回滚。


    对于某些应用程序来说,为了提高效率,审计是一个重要的需求。通常,要确定数据更改,应用程序开发人员必须在其应用程序中通过组合使用触发器、时间戳列和附加表来实现自定义跟踪方法。创建这些机制,通常需要做大量的工作来实现,因此导致方案改动,且常常大幅增加性能开销。这是一个简单的例子,可以帮助您开始创建自己的框架。  

    请阅读 下一篇文章! 

    0
    0 389
    文章 Nicky Zhu · 一月 11, 2021 3m read

    当我向技术人员介绍InterSystems IRIS时,我一般会先讲其核心是一个多模型DBMS。

    我认为这是其主要优势(在DBMS方面)。数据仅存储一次。您只需访问您想用的API。

    • 您想要数据的概要?用SQL!
    • 您想用一份记录做更多事情?用对象!
    • 想要访问或设置一个值,并且您知道键?用Globals!

    乍一看挺好的,简明扼要,又传达了信息,但当人们真正开始使用InterSystems IRIS时,问题就来了。类、表和Globals是如何关联的?它们之间有什么关系?数据是如何存储的?

    本文我将尝试回答这些问题,并解释这些到底是怎么回事。

    第一部分 模型偏见

    处理数据的人往往对他们使用的模型有偏见。

    开发者们把数据视为对象。对他们而言,数据库和表都是通过CRUD(增查改删,最好是基于ORM)交互的盒子,但底层的概念模型都是对象(当然这对于我们大多数使用面向对象编程语言的开发者来说没错)。

    而DBA大部分时间都在搞关系型DBMS,他们把数据视为表。对象只是行的封装器。

    对于InterSystems IRIS,持久类也是一个表,将数据存储在Global中,因此需要进行一些澄清。

    第二部分 举例

    假设您创建了类Point:

    Class try.Point Extends %Persistent [DDLAllowed]
    {
        Property X;
        Property Y;
    }
    

    您也可以用DDL/SQL创建相同的类:

    CREATE Table try.Point (
        X VARCHAR(50),
        Y VARCHAR(50))
    

    编译后,新类将自动生成一个存储结构,将原生存储在Global中的数据映射到列(对于面向对象开发者而言,是属性):

    Storage Default
    {
    <Data name="PointDefaultData">
        <Value name="1">
            <Value>%%CLASSNAME</Value>
        </Value>
        <Value name="2">
            <Value>X</Value>
        </Value>
        <Value name="3">
            <Value>Y</Value>
        </Value>
    </Data>
    <DataLocation>^try.PointD</DataLocation>
    <DefaultData>PointDefaultData</DefaultData>
    <IdLocation>^try.PointD</IdLocation>
    <IndexLocation>^try.PointI</IndexLocation>
    <StreamLocation>^try.PointS</StreamLocation>
    <Type>%Library.CacheStorage</Type>
    }
    

    这是怎么回事?

    自下向上(加粗文字很重要):

    • Type: 生成的存储类型,本例中是持久对象的默认存储
    • StreamLocation - 存储流的Global
    • IndexLocation - 索引Global
    • IdLocation - 存储ID自增计数器的Global
    • DefaultData - 存储将Global值映射到列/属性的XML元素
    • DataLocation - 存储数据的Global

    现在我们的DefaultData是PointDefaultData,让我们分析下它的结构。本质上Global节点有这样的结构:

    • 1 - %%CLASSNAME
    • 2 - X
    • 3 - Y

    所以我们可能期望我们的Global是这样的:

    ^try.PointD(id) = %%CLASSNAME, X, Y
    

    但如果我们输出 Global 它会是空的,因为我们没有添加任何数据:

    zw ^try.PointD
    

    让我们添加一个对象:

    set p = ##class(try.Point).%New()
    set p.X = 1
    set p.Y = 2
    write p.%Save()
    

    现在我们的Global变成了这样

    zw ^try.PointD
    ^try.PointD=1
    ^try.PointD(1)=$lb("",1,2)
    

    可以看到,我们期望的结构%%CLASSNAME, X, Y是用 $lb("",1,2) 设置的,它对应的是对象的X和Y属性(%%CLASSNAME 是系统属性,忽略)。

    我们也可以用SQL添加一行:

    INSERT INTO try.Point (X, Y) VALUES (3,4)
    

    现在我们的Global变成了这样:

    zw ^try.PointD
    ^try.PointD=2
    ^try.PointD(1)=$lb("",1,2)
    ^try.PointD(2)=$lb("",3,4)
    

    所以我们通过对象或SQL添加的数据根据存储定义被存储在Global中(备注:可以通过在PointDefaultData 中替换X和Y来手动修改存储定义,看看新数据会怎样!)。

    现在,如果我们想执行SQL查询会怎样?

    SELECT * FROM try.Point
    

    这段sql查询被转换为ObjectScript代码, 遍历^try.PointD,并根据存储定义(其中的 PointDefaultData 部分)填充列。

    下面是修改。让我们从表中删除所有数据:

    DELETE FROM try.Point
    

    看看我们的Global变成什么样了:

    zw ^try.PointD
    ^try.PointD=2
    

    可以看到,只剩下ID计数器,所以新对象/行的ID=3。我们的类和表也继续存在。

    但如果我们运行会怎样:

    DROP TABLE try.Point
    

    它会销毁表、类并删除Global。

    zw ^try.PointD
    

    看完这个例子,希望您现在对Global、类和表如何相互集成和互补有了更好的理解。根据实际需要选用正确的API会让开发更快、更敏捷、bug更少。

    0
    0 161
    文章 Jeff Liu · 一月 8, 2021 5m read

    你好!

    本文简单介绍一款工具,帮您理解InterSystems产品(从IRISCachéEnsemble以及HealthShare)中的类及其结构。

    简言之,它将类或整个包可视化,显示类之间的关系,并向开发人员和团队领导提供各种信息,而无需到 Studio 中检查代码。

    如果您正在学习InterSystems产品,经常查看项目,或只对InterSystems技术解决方案中的新内容感兴趣,欢迎阅读ObjectScript类浏览器概述!

    InterSystems 产品简介

    IRIS(之前称为Caché) 是一个多模型DBMS。您可以使用SQL查询来访问它,也可以通过各种编程语言可用的接口与存储的对象和过程进行交互。但最多的还是使用DBMS原生内置语言--ObjectScript (COS) 开发应用程序。

    Caché支持DBMS级别的类。有两种主要的类类型:Persistent(可以存储在数据库中)和 Registered(不存储在数据库中,扮演程序和处理程序的角色)。还有几种特殊的类类型:Serial(可集成到持久类中用于创建复杂数据类型(如:地址)的类),DataType(用于创建用户定义的数据类型)、IndexView Stream

    进入类浏览器

    0
    1 473