Caché Objects | 第七章 | 类查询(CQ)

本文详细介绍了Caché中的类查询(CQ)及其两种类型:基本CQ和自定义CQ。重点讲解了如何使用和定义自定义CQ,包括querynameExecute,querynameFetch,和querynameClose方法的实现,以及何时选择使用自定义CQ的场景。还涵盖了SQL游标在CQ中的注意事项。
摘要由CSDN通过智能技术生成

一、CQ简介

类查询(Class Queries) ,简称CQ是一种工具,包含在类中,用于动态 SQL,用于查找满足指定条件的记录。使用类查询,您可以为应用程序创建预定义的查找。例如,您可以按名称查找记录,或提供满足一组特定条件的记录列表。

通过创建类查询,可以避免按内部 ID 查找特定对象。相反,您可以创建一个基于所需的任何类属性进行查找的查询。这些甚至可以在运行时从用户输入中指定。

如果定义自定义类查询,则查找逻辑可以使用 Caché ObjectScript,并且可以任意复杂。

有两种类型的类查询:

  • 基本CQ,使用类 %SQLQuerySQL SELECT 语句。
  • 自定义CQ,它使用类 %Query 和自定义逻辑来执行、提取和关闭查询。

请注意:您可以在任何类中定义类查询;不需要将它们包含在持久类中。

二、使用CQ

在了解如何定义类查询之前,了解如何使用它们很有用。在服务器端代码中,可以使用类查询,如下所示:

  • 使用 %New() 创建 %SQL.Statement 的实例。
  • 调用该实例的 %PrepareClassQuery() 方法。作为参数,按顺序使用以下内容:
    • 定义要使用的查询的类的完全限定名称。
    • 该类中的查询的名称。
      此方法返回一个 %Status 值,您应该检查该值。
  • 调用 %SQL.Statement%Execute() 方法。这将返回 %SQL.StatementResult.的实例。
  • 使用 %SQL.StatementResult的方法, 从结果集中检索数据。
#include %occInclude
 
 set statement=##class(%SQL.Statement).%New()
 set status=statement.%PrepareClassQuery("Sample.Person","ByName")
 if $$$ISERR(status) { do $system.OBJ.DisplayError(status) }
 set resultset=statement.%Execute()
 while resultset.%Next() {
    write !, resultset.%Get("Name")
 }
  • 如果查询标记为 SqlProc(将其定义为 ODBCJDBC 存储过程),则可以从 SQL 上下文中将其作为存储过程调用。

三、基本CQ

若要定义基本类查询,请按如下方式定义查询:

  • (用于简单的类查询)类型应为 %SQLQuery
  • 在参数列表中,指定查询应接受的任何参数。
  • 在定义的正文中,编写 SQL SELECT 语句。
    在此语句中,若要引用参数,请在参数名称前面加上冒号 ()。
    此 SELECT 语句不应包含 INTO 子句。
  • 指定查询的 ROWSPEC 参数(在括号中,在查询类型之后)。此参数提供有关查询结果集每行中字段的名称、数据类型、标题和顺序的信息。
  • (可选)指定查询的 CONTAINID 参数(在括号中,在查询类型之后)。此参数指定包含特定行的 ID 的字段的列号(如果有);默认值为 1。
    ROWSPECCONTAINID 参数统称为查询规范
  • 在查询定义中包括 SqlProc 关键字。
    如果计划使用 %ResultSet 调用查询,并且不需要将查询作为存储过程调用,则可以省略此步骤。如果您计划使用 %SQL.Statement 调用查询,必须指定 SqlProc 关键字。
  • (可选)如果希望存储过程的名称不是默认名称,则可以在查询定义中指定 SqlName 关键字。
    这些是编译器关键字,因此请将它们包含在任何参数后面的方括号中,在查询类型 (%SQLQuery) 之后。
/// d ##Class(%ResultSet).RunQuery("DHCAnt.Auth.Test","ListEmployees","D")
Query ListEmployees(City As %String = "D") As %SQLQuery(CONTAINID = 1, ROWSPEC = "ID:%Integer,TATitleDR:%String,TAControlType:%String") [ SqlName = MyProcedureName, SqlProc ]
{
SELECT ID,TATitleDR,TAControlType FROM CF_DOC_ANT.TitleAuth
 WHERE (TAControlType %STARTSWITH :City)
 ORDER BY ID
}

在这里插入图片描述

关于ROWSPEC

查询的 ROWSPEC 参数提供有关每行中字段的名称、数据类型、标题和顺序的信息。它是变量名称和数据类型的带引号和逗号分隔的列表,其形式为:

ROWSPEC = "Var1:%Type1,Var2:%Type2[:OptionalDescription],Var3"

ROWSPEC逗号分隔列表的形式指定字段的顺序。每个字段的信息由以冒号分隔的名称列表、数据类型(如果与相应属性的数据类型不同)和可选标题组成。要编辑 ROWSPEC,选项包括:

  • 直接编辑代码。
  • 对于已存在的查询,请在 Studio Inspector 窗口中显示该查询,展开其参数列表,然后使用可用的对话框。

ROWSPEC 参数中的元素数必须与查询中的字段数匹配。否则,Caché 将返回“Cardinality Mismatch”错误。

Query ByName(name As %String = "") 
    As %SQLQuery(CONTAINID = 1, ROWSPEC = "ID:%Integer,Name,DOB,SSN", SELECTMODE = "RUNTIME") 
   [ SqlName = SP_Sample_By_Name, SqlProc ]
{
        SELECT ID, Name, DOB, SSN
            FROM Sample.Person
            WHERE (Name %STARTSWITH :name)
            ORDER BY Name
}

此处,CONTAINID 参数指定行ID是第一个字段(默认值); 请注意,SELECT 语句中指定的第一个字段是 IDROWSPEC 参数指定字段为 ID(视为整数)、Name、DOB 和 SSN;同样,SELECT 语句按该顺序包含字段 ID、Name、DOB 和 SSN。

关于 CONTAINID

CONTAINID 应设置为返回 ID 的列的编号(默认为 1)或 0(如果没有列返回 ID)。如果您使用新建查询向导创建查询,则 Studio 会根据您在该向导中指定的顺序自动为 CONTAINID 分配适当的值。

注意
Caché 不验证 CONTAINID 的值。如果为此参数指定无效值,则 Caché 不会引发错误。这意味着,如果查询处理逻辑依赖于此信息,则如果 CONTAINID 参数设置不正确,则可能会遇到不一致的情况。

Query 类的其他参数

除了 ROWSPECCONTAINID 之外,还可以指定查询的以下参数。以下是 %SQLQuery 的类参数:

  • SELECTMODE:选择模式
  • COMPILEMODE:编译模式
    有关详细信息,请参阅 %Library.SQLQuery%Library.Query(其超类)的类引用。

四、自定义CQ

尽管简单的 %SQLQuery 查询会为您执行所有结果集管理,但对于某些应用程序来说,这还不够。对于这种情况,Caché 允许您编写自定义查询,这些查询在方法中定义(默认情况下是用 Caché ObjectScript 编写的)。若要定义自定义查询,请使用本章前面给出的说明,并进行以下更改:

  • 指定 %Query 作为查询类型。
  • 将查询定义的正文留空。例如:
Query AllPersons() As %Query(CONTAINID = 1,ROWSPEC = "ID:%String,Name:%String,DOB:%String,SSN:%String")
{
}
  • 在同一类中定义以下类方法:
    • querynameExecute — 此方法必须执行任何一次性设置。
    • querynameFetch — 此方法必须返回结果集的一行;每个后续调用都返回下一行。
    • querynameClose — 此方法必须执行任何清理操作。
      其中 queryname 是查询的名称。

这些方法中的每一个都接受一个参数 (qHandle),该参数通过引用传递。可以使用此参数在这些方法之间传递信息

定义 querynameExecute()方法

querynameExecute() 方法必须提供所需的所有设置逻辑。方法的名称必须是 querynameExecute,其中 queryname 是查询的名称。此方法必须具有以下签名:

ClassMethod queryNameExecute(ByRef qHandle As %Binary, 
                             additional_arguments) As %Status
  • qHandle 用于与实现此查询的其他方法进行通信。
    此方法应根据 querynameFetch 方法的需要设置 qHandle
    尽管 qHandle 在形式上属于 %Binary 类型,但它可以保存任何值,包括 OREF 或多维数组。
  • additional_arguments 是查询可以使用的任何运行时参数

在此方法实现中,使用以下常规逻辑:

  1. 执行任何一次性设置步骤。
    对于使用 SQL 代码的查询,此方法通常包括声明和打开游标。
  2. 根据 querynameFetch 方法的需要设置 qHandle
  3. 返回状态值。
ClassMethod AllPersonsExecute(ByRef qHandle As %Binary) As %Status
{
    set statement=##class(%SQL.Statement).%New()
    set status=statement.%PrepareClassQuery("Sample.Person","ByName")
    if $$$ISERR(status) { quit status }
    set resultset=statement.%Execute()
    set qHandle=resultset
    Quit $$$OK
}

在此方案中,该方法将 qHandle 设置为等于 OREF,是 %SQL.StatementResult, 的实例,这是 %Execute() 方法返回的值。

定义 querynameFetch()方法

querynameFetch() 方法必须以 $List 格式返回单行数据。方法的名称必须是 querynameFetch,其中 queryname 是查询的名称。此方法必须具有以下签名:

ClassMethod queryNameFetch(ByRef qHandle As %Binary, 
                           ByRef Row As %List,
                           ByRef AtEnd As %Integer = 0) As %Status [ PlaceAfter = querynameExecute ]
  • qHandle 用于与实现此查询的其他方法进行通信。
    Caché 开始执行此方法时,qHandle 具有由 querynameExecute 方法或此方法的上一次调用(如果有)建立的值。此方法应根据后续逻辑的需要设置 qHandle
    尽管 qHandle 在形式上属于 %Binary 类型,但它可以保存任何值,包括 OREF 或多维数组。
  • Row 必须是表示要返回的数据行的值的 %List,如果未返回任何数据,则必须为 null 字符串。
  • 到达最后一行数据时,AtEnd 必须为 1
  • PlaceAfter 方法关键字控制此方法在生成的例程代码中的位置。对于 querynameExecute,替换特定 querynameExecute() 方法的名称。如果您的查询使用 SQL 游标,请确保包含此游标。(控制此顺序的能力是一项高级功能,应谨慎使用。InterSystems不建议一般使用此关键字。

在此方法实现中,使用以下常规逻辑:

  1. 检查以确定它是否应返回更多结果。
  2. 如果适用,请检索一行数据并创建一个 %List 对象,并将其放在 Row 变量中。
  3. 根据此方法的后续调用(如果有)或 querynameClose() 方法需要设置 qHandle
  4. 如果不存在更多行,请将 Row 设置为 null 字符串,并将 AtEnd 设置为 1。
  5. 返回状态值。
ClassMethod AllPersonsFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status 
[ PlaceAfter = AllPersonsExecute ]
{
    set rs=$get(qHandle)
    if rs="" quit $$$OK

    if rs.%Next() {
        set Row=$lb(rs.%GetData(1),rs.%GetData(2),rs.%GetData(3),rs.%GetData(4))
        set AtEnd=0
    } else {
        set Row=""
        set AtEnd=1
    }
    Quit $$$OK
}

querynameClose() 方法

querynameClose() 方法必须在数据检索完成后执行任何需要的清理。方法的名称必须是 querynameClose,其中 queryname 是查询的名称。此方法必须具有以下签名:

ClassMethod queryNameClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = querynameFetch ]
  • qHandle 用于与实现此查询的其他方法进行通信。
    Caché 开始执行此方法时,qHandle 具有由上次调用 querynameFetch 方法建立的值。
  • PlaceAfter 方法关键字控制此方法在生成的例程代码中的位置。对于 querynameFetch,替换特定 querynameFetch() 方法的名称。如果您的查询使用 SQL 游标,请确保包含此游标。(控制此顺序的能力是一项高级功能,应谨慎使用。InterSystems不建议一般使用此关键字。

在此方法实现中

  • 从内存中删除变量、关闭任何 SQL 游标或根据需要执行任何其他清理。
  • 该方法必须返回状态值。
ClassMethod AllPersonsClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = AllPersonsFetch ]
{
        Set qHandle=""
        Quit $$$OK
}

为自定义查询生成的方法

系统自动生成 querynameGetInfo()querynameFetchRows()。应用程序不直接调用这些方法中的任何一个 — %Library.ResultSet 对象使用它们来处理查询请求。

五、自定义CQ的参数

如果自定义查询应接受参数,请执行以下操作:

  • 将它们包含在查询类成员的参数列表中。以下示例使用名为 MyParm 的参数:
Query All(MyParm As %String) As %Query(CONTAINID = 1, ROWSPEC = "Title:%String,Author:%String")
{
}
  • querynameExecute 方法的参数列表中包含相同的参数,其顺序与查询类成员中的顺序相同。
  • querynameExecute 方法的实现中,根据需要使用参数。

六、其他自定义CQ示例

上一节提供了自定义类查询的简单示例,该示例可以很容易地作为基本类查询实现。本节显示了 Caché 类库中一个更典型的示例。

提示:此示例旨在演示可以使用的方法,而不是记录类库如何实现特定功能。

Query ByServer() As %Query(ROWSPEC = "Name,Port,PingPort,Renderer,State,StateEx") [ SqlProc ]
{
}
  • querynameExecute() 方法,请注意,此方法将数据保存到 process-private 全局变量中,而不是保存到 qHandle 变量中。另请注意,此方法使用较旧的动态 SQL 类 (%ResultSet)。
ClassMethod ByServerExecute(ByRef qHandle As %Binary) As %Status [ Internal ]
{
    Set tSC = $$$OK
    Try {
        Set tRS = ##class(%ResultSet).%New("%ZEN.Report.RenderServer:ByName")
        Kill ^||%ISC.ZRS
        Set tSC = tRS.Execute()
        For {
            Quit:'tRS.Next()
            Set tType = tRS.Get("ServerType")
            If (tType'=0) && (tType'="") Continue // Not a Render Server
            Set name = tRS.Get("Name")
            Set ^||%ISC.ZRS(name) = $LB(name,tRS.Get("Port"),tRS.Get("PingPort"),tRS.Get("Renderer"))
        }
    }
    Catch (ex) {
        Set tSC = ex.AsStatus()
    }
    Set qHandle = $LB("")
    Quit tSC
}
  • querynameFetch() 方法如下:
ClassMethod ByServerFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) 
As %Status [ Internal, PlaceAfter = ByServerExecute ]
{
    Set index = $List(qHandle,1)
    Set index = $O(^||%ISC.ZRS(index))
    If index="" {
        Set Row = ""
        Set AtEnd = 1
    }
    Else {
        Set Row = ^||%ISC.ZRS(index)
        Set stInt = ..GetState($List(Row,2),$List(Row,3),$List(Row,4))
        Set stExt = $Case(stInt,0:$$$Text("Inactive"),1:$$$Text("Active"),
                   2:$$$Text("Unresponsive"),3:$$$Text("Troubled"),4:$$$Text("Error"),
                   5:$$$Text("Mismatch"),:"")
        Set $List(Row,5) = stInt, $List(Row,6) = stExt
    }
    Set qHandle = $LB(index)
    Quit $$$OK
}

最后,querynameClose() 方法如下:

ClassMethod ByServerClose(ByRef qHandle As %Binary) As %Status [ Internal, PlaceAfter = ByServerExecute ]
{
    Set qHandle = ""
    Kill ^||%ISC.ZRS
    Quit $$$OK
}

七、何时使用自定义CQ

以下列表建议了自定义查询适用的一些方案:

  • 如果需要使用非常复杂的逻辑来确定是否在返回的数据中包含特定行。querynameFetch() 方法可以包含任意复杂的逻辑。
  • 如果您的 API 以对当前用例不方便的格式返回数据。在这种情况下,您将定义 querynameFetch() 方法,以便根据 Row 变量的需要将该格式的数据转换为$List
  • 如果数据存储在没有类接口的全局数据库中。
  • 如果访问数据需要角色升级。在这种情况下,您可以在 querynameExecute() 方法中执行角色升级。
  • 如果访问数据需要调用文件系统(例如,在构建文件列表时)。在这种情况下,您可以在 querynameExecute() 方法中执行标注,然后将结果存储在 qHandle全局中。
  • 如果需要在检索数据之前执行安全检查、检查连接或执行其他一些特殊设置工作。您将在 querynameExecute() 方法中执行此类工作。

八、SQL 游标和CQ

如果类查询使用 SQL 游标,请注意以下几点:

  • %SQLQuery 类型的查询生成的游标自动名称,例如具有 Q14 等名称。
    您必须确保为游标指定不同的名称。
  • 错误消息是指内部游标名称,该名称通常有一个额外的数字。因此,游标 Q140 的错误消息可能是指 Q14。
  • 类编译器必须先找到游标声明,然后才能尝试使用游标。这意味着在定义使用游标的自定义查询时必须格外小心。
  • DECLARE 语句(通常在 querynameExecute() 方法中)必须与 CloseFetch 位于同一 MAC 例程中,并且必须位于它们中的任何一个之前。如本章前面所示,在 querynameFetch() 和 querynameClose() 方法定义中使用方法关键字 PlaceAfter 来确保发生这种情况。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值