第十七章 Caché 定义和使用类查询

第十七章 Caché 定义和使用类查询

本章讨论类查询,它们用作命名查询,它们是类结构的一部分,可以通过动态SQL访问。

类查询简介

类查询是一种工具(包含在类中,旨在与动态SQL配合使用),用于查找满足指定条件的记录。使用类查询,可以为应用程序创建预定义的查找。例如,可以按名称查找记录,或提供满足特定条件的记录列表,例如从巴黎到马德里的所有航班。

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

如果定义自定义类查询,则查询逻辑可以使用CachéObjectScript。

有两种类查询:

  • 基本类查询,使用类%SQLQuery和SQL SELECT语句。
  • 定制类查询,使用类%Query和定制逻辑来执行,获取和关闭查询。

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

使用类查询

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

  1. 使用%New()创建%SQL.Statement的实例。
  2. 调用该实例的%PrepareClassQuery()方法。作为参数,按顺序使用以下命令:
  • 定义要使用的查询的类的全限定名称。
  • 该类中查询的名称。
  • 此方法返回%Status值,应该检查该值。
  1. 调用%SQL.Statement实例的%Execute()方法。这将返回%SQL.StatementResult的实例。
  2. 使用%SQL.StatementResult的方法从结果集中检索数据。

请注意,可以类似的方式使用较旧的动态SQL API(%ResultSet)。

下面显示了一个可以在SAMPLES命名空间中使用的简单示例。本示例使用Sample.Person的ByName查询:

/// d ##class(PHA.OP.MOB.Test).TestQuery()
ClassMethod TestQuery()
{
    #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")
    }
}

DHC-APP>d ##class(PHA.OP.MOB.Test).TestQuery()
 
Adams,Diane F.
Adams,Susan E.
Ahmed,Elmo X.
Alton,Martin S.
Alton,Phil T.
Anderson,Mario L.
Anderson,Valery N.
Bachman,Susan O.
Basile,Filomena X.
Beatty,Molly Z.
Browne,Robert X.
Bukowski,Mario V.
Burroughs,Barbara H.
Zweifelhofer,Roberta A.
Zweifelhofer,Zelda J.
姚鑫
姚鑫
姚鑫
姚鑫
姚鑫

如果使用的是CachéJava或ActiveX绑定,则可以使用该绑定中的结果集类。如果查询标记有SqlProc(将其定义为ODBC或JDBC存储过程),则可以从SQL上下文中将其作为存储过程调用。

定义基本类查询

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

  • (对于简单的类查询)类型应为%SQLQuery。
  • 在参数列表中,指定查询应接受的所有参数。
  • 在定义主体中,编写一个SQL SELECT语句。
  • 在此语句中,要引用参数,请在参数名称前加上冒号(:)。
  • 指定查询的ROWSPEC参数(在查询类型之后的括号中)。此参数提供有关查询结果集每一行中字段的名称,数据类型,标题和顺序的信息。第二小节提供了详细信息。
  • (可选)指定查询的CONTAINID参数(在查询类型之后的括号中)。此参数指定包含特定行ID的字段的列号(如果有)。默认值为1。第三小节提供详细信息。

ROWSPEC和CONTAINID参数一起被称为查询规范。

  • 在查询定义中包含SqlProc关键字。

如果计划使用%ResultSet来调用查询,并且不需要将查询作为存储过程来调用,则可以省略此步骤。如果计划使用%SQL.Statement来调用查询,则必须指定SqlProc关键字。

  • 如果希望存储过程的名称不是默认名称,则可以选择在查询定义中指定SqlName关键字。

这些是编译器关键字,因此在查询类型(%SQLQuery)之后的任何参数之后都将它们放在方括号中。

CachéStudio提供了一个向导(新查询向导),可以使用该向导来定义这样的基本类查询。以下小节显示了一个示例。

示例

下面显示了一个简单的示例:

/// w ##class(%ResultSet).%New("ListEmployees")
Query ListEmployees(City As %String = "") As %SQLQuery(CONTAINID = 1, ROWSPEC = "ID:%Integer,Name:%String,Title:%String") [ SqlName = MyProcedureName, SqlProc ]
{
    SELECT ID,Name,Title FROM Sample.Employee
    WHERE (Home_City %STARTSWITH :City)
    ORDER BY Name
}

/// d ##class(PHA.OP.MOB.Test).TestRunQuery()
ClassMethod TestRunQuery()
{
    
    Set tRS = ##class(%ResultSet).%New("PHA.OP.MOB.Test:ListEmployees")
    Set tSC = tRS.Execute()
    For {
            Quit:'tRS.Next()
            Set Name = tRS.Get("Name")
            Set Title = tRS.Get("Title")
            Set ID = tRS.Get("ID")
            w Name_" "_Title_" "_ID,!
        }
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestRunQuery()
Adams,Susan E. Laboratory Hygienist 183
Alton,Phil T. Executive Developer 175
Anderson,Valery N. Accounts Rep. 131
Beatty,Molly Z. Assistant Director 176
Browne,Robert X. Senior Marketing Manager 150
Burroughs,Barbara H. Laboratory Technician 167
Cerri,Stavros Q. Associate Director 182
Chadwick,Phyllis L. Assistant Systems Engineer 116

关于ROWSPEC

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

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

ROWSPEC将字段的顺序指定为以逗号分隔的列表。每个字段的信息包括一个用冒号分隔的名称列表,其数据类型(如果它与相应属性的数据类型不同)和一个可选的标题。

要编辑ROWSPEC,选项为:

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

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

例如,在SAMPLES数据库中,Sample.Person示例类的ByName查询如下:

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语句中指定的第一个字段是ID。ROWSPEC参数指定字段为ID(视为整数),Name,DOB和SSN。同样,SELECT语句按顺序包含字段ID,Name,DOB和SSN。

关于CONTAINID

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

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

查询类的其他参数

除了ROWSPEC和CONTAINID,还可以指定查询的以下参数。这些是%SQLQuery的类参数:

  • SELECTMODE
  • COMPILEMODE

定义自定义类查询

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

  • 指定%Query作为查询类型。
  • 将查询定义的主体保留为空。例如:
Query All() As %Query(CONTAINID = 1, ROWSPEC = "Title:%String,Author:%String")
{
}

在同一类中定义以下类方法:

  • querynameExecute —此方法必须执行一次性设置。
  • querynameFetch —此方法必须返回结果集的一行;随后的每个调用都返回下一行。
  • querynameClose —此方法必须执行任何清理操作。

其中queryname是查询的名称。

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

这些方法定义查询。以下小节提供了有关它们的详细信息。

出于基本的演示目的,前三个小节显示了一个简单的示例,也可以将其实现为基本的类查询。可以在SAMPLES名称空间中使用此示例。这些方法实现以下查询的代码:

Query AllPersons() As %Query(ROWSPEC = "ID:%String,Name:%String,DOB:%String,SSN:%String")
{
}

定义querynameExecute()方法

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

ClassMethod queryNameExecute(ByRef qHandle As %Binary, 
                             additional_arguments) As %Status
  • qHandle用于与实现此查询的其他方法进行通信。

此方法应根据查询名获取方法的需要设置qHandle。

尽管qHandle的格式为%Binary,但是它可以保存任何值,包括OREF或多维数组。

  • Additional_arguments是查询可以使用的任何运行时参数。

在此方法的实现中,使用以下一般逻辑:

  1. 执行任何一次性设置步骤。对于使用SQL代码的查询,此方法通常包括声明和打开游标。
  2. 通过querynameFetch方法根据需要设置qHandle。
  3. 返回状态值。

下面显示了一个简单的示例,前面介绍了用于AllPersons查询的AllPersonsExecute()方法:

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()方法返回的值。
如前所述,该类查询也可以实现为基本类查询而不是自定义类查询。但是,某些自定义类查询确实使用动态SQL作为起点。

定义querynameFetch()方法

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

ClassMethod queryNameFetch(ByRef qHandle As %Binary, 
                           ByRef Row As %List,
                           ByRef AtEnd As %Integer = 0) As %Status [ PlaceAfter = querynameExec
  • qHandle用于与实现此查询的其他方法进行通信。

当Caché开始执行此方法时,qHandle的值由querynameExecute方法或该方法的上一次调用(如果有)建立。此方法应根据后续逻辑的需要设置qHandle。

  • Row必须是表示要返回的数据行的值的%List,或者如果没有返回数据则为空字符串。

  • 到达最后一行数据时,AtEnd必须为1。

  • PlaceAfter方法关键字控制此方法在生成的代码中的位置。对于querynameExecute,请替换特定的querynameExecute()方法的名称。如果查询使用SQL游标,请确保包括此内容。(控制此顺序的功能是一项高级功能,应谨慎使用。InterSystems建议不要普遍使用此关键字。)

在此方法的实现中,使用以下一般逻辑:

  • 检查以确定它是否应该返回更多结果。
  • 如果合适,检索一行数据并创建一个%List对象,并将其放在Row变量中。
  • 根据需要通过此方法的后续调用(如果有的话)或querynameClose()方法设置qHandle。
  • 如果不存在行,则将Row设置为空字符串,并将AtEnd设置为1。
  • 返回状态值。

对于AllPersons示例,AllPersonsFetch()方法可能如下:

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
}

注意,此方法使用qHandle参数,该参数提供%SQL.StatementResult对象。然后,该方法使用该类的方法来检索数据。该方法将构建一个$List并将其放置在Row变量中,该变量作为单行数据返回。还要注意,当无法检索到更多数据时,该方法包含用于设置AtEnd变量的逻辑。

如前所述,该类查询也可以实现为基本类查询而不是自定义类查询。本示例的目的是演示设置Row和AtEnd变量。

querynameClose()方法

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

ClassMethod queryNameClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = querynameFetch ]
  • qHandle用于与实现此查询的其他方法进行通信。

当Caché开始执行此方法时,qHandle具有由最后一次调用querynameFetch方法建立的值。

  • PlaceAfter方法关键字控制此方法在生成的例程代码中的位置。对于querynameFetch,请替换特定的querynameFetch()方法的名称。如果您的查询使用SQL游标,请确保包括此内容。

在此方法的实现中,根据需要从内存中删除变量,关闭所有SQL游标或执行任何其他清理。该方法必须返回状态值。

  • 对于AllPersons示例,AllPersonsClose()方法可能如下:
  • 例如,ByNameClose()方法的签名可能是:
ClassMethod AllPersonsClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = AllPersonsFetch ]
{
        Set qHandle=""
        Quit $$$OK
}

定制查询的生成方法

Caché自动生成querynameGetInfo()和querynameFetchRows()。应用程序不会直接调用任何这些方法-%Library.ResultSet对象使用它们来处理查询请求。

完整示例

/// d ##class(%ResultSet).RunQuery("PHA.OP.MOB.Test","AllPersons")
Query AllPersons() As %Query(ROWSPEC = "ID:%String,Name:%String,DOB:%String,SSN:%String")
{
}

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
}

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
}

ClassMethod AllPersonsClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = AllPersonsFetch ]
{
        Set qHandle=""
        Quit $$$OK
}
DHC-APP>d ##class(%ResultSet).RunQuery("PHA.OP.MOB.Test","AllPersons")
 
ID:Name:DOB:SSN:
95:Adams,Diane F.:62279:640-77-5933:
183:Adams,Susan E.:46708:947-66-8684:
71:Ahmed,Elmo X.:37396:950-40-6135:
28:Alton,Martin S.:48380:624-25-8488:
175:Alton,Phil T.:40963:785-37-8519:
86:Anderson,Mario L.:37365:604-10-9256:
6:姚鑫::111-11-1112:
9:姚鑫::111-11-1113:
10:姚鑫::111-11-1114:
13:姚鑫::111-11-1115:
14:姚鑫::111-11-1116:

定义自定义查询的参数

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

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

  • 在querynameExecute方法的实现中,根据需要使用适当的参数。

其他自定义查询示例

上一节提供了一个自定义类查询的简单示例,可以轻松地将其实现为基本类查询。本节显示了Caché类库中的一个更典型的示例。另请参阅下一节以获取其他想法。

重要说明:提供此示例是为了演示可以使用的方法,而不是说明类库如何实现特定功能。因此,本部分不指示当前哪个类包含此代码,也不会更新本部分以反映该类将来的更改。

在此示例中,查询将构建并使用进程专用全局变量。该查询的定义如下:

Query ByServer() As %Query(ROWSPEC = "Name,Port,PingPort,Renderer,State,StateEx") [ SqlProc ]
{
}

querynameExecute()方法如下:

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
}

请注意,此方法将数据保存到进程专用的全局变量中,而不是保存到qHandle变量中。另请注意,此方法使用较旧的动态SQL类(%ResultSet)。

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
}

这个例子运行不了 ..GetState方法不存在 $$$Text报错

何时使用自定义查询

以下列表提出了一些适合自定义查询的方案:

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

SQL游标和类查询

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

  • 由类型为%SQLQuery的查询生成的游标会自动具有名称,例如Q14。

    必须确保为游标指定不同的名称。

  • 错误消息指的是内部游标名称,该名称通常带有一个额外的数字。因此,游标Q140的错误消息可能指向Q14。

  • 在尝试使用游标之前,类编译器必须找到游标声明。这意味着在定义使用游标的自定义查询时必须格外小心。

DECLARE语句(通常在querynameExecute()方法中)必须与Close和Fetch处于同一MAC例程中,并且必须位于二者之一之前。如本章前面所示,在querynameFetch()和querynameClose()方法定义中都使用关键字PlaceAfter的方法,以确保发生这种情况。

你可能感兴趣的:(第十七章 Caché 定义和使用类查询)