目录
一、CQ简介
类查询(Class Queries
) ,简称CQ
是一种工具,包含在类中,用于动态 SQL
,用于查找满足指定条件的记录。使用类查询,您可以为应用程序创建预定义的查找。例如,您可以按名称
查找记录,或提供满足一组特定条件的记录列表。
通过创建类查询,可以避免按内部 ID
查找特定对象。相反,您可以创建一个基于所需的任何类属性
进行查找的查询。这些甚至可以在运行时从用户输入中指定。
如果定义自定义类查询,则查找逻辑可以使用 Caché ObjectScript
,并且可以任意复杂。
有两种类型的类查询:
- 基本CQ,使用类
%SQLQuery
和SQL 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
(将其定义为ODBC
或JDBC
存储过程),则可以从SQL
上下文中将其作为存储过程调用。
三、基本CQ
若要定义基本类查询,请按如下方式定义查询:
- (用于简单的类查询)类型应为
%SQLQuery
。 - 在参数列表中,指定查询应接受的任何参数。
- 在定义的正文中,编写
SQL SELECT
语句。
在此语句中,若要引用参数,请在参数名称前面加上冒号 (:
)。
此 SELECT 语句不应包含INTO
子句。 - 指定查询的
ROWSPEC
参数(在括号中,在查询类型之后)。此参数提供有关查询结果集每行中字段的名称、数据类型、标题和顺序的信息。 - (可选)指定查询的
CONTAINID
参数(在括号中,在查询类型之后)。此参数指定包含特定行的 ID 的字段的列号(如果有);默认值为 1。
ROWSPEC
和CONTAINID
参数统称为查询规范。 - 在查询定义中包括
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
语句中指定的第一个字段是 ID
。ROWSPEC
参数指定字段为 ID
(视为整数)、Name、DOB 和 SSN;同样,SELECT 语句按该顺序包含字段 ID、Name、DOB 和 SSN。
关于 CONTAINID
CONTAINID
应设置为返回 ID
的列的编号(默认为 1)或 0(如果没有列返回 ID)。如果您使用新建查询向导创建查询,则 Studio
会根据您在该向导中指定的顺序自动为 CONTAINID
分配适当的值。
注意:
Caché
不验证 CONTAINID
的值。如果为此参数指定无效值,则 Caché
不会引发错误。这意味着,如果查询处理逻辑依赖于此信息,则如果 CONTAINID
参数设置不正确,则可能会遇到不一致的情况。
Query 类的其他参数
除了 ROWSPEC
和 CONTAINID
之外,还可以指定查询的以下参数。以下是 %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
是查询可以使用的任何运行时参数。
在此方法实现中,使用以下常规逻辑:
- 执行任何一次性设置步骤。
对于使用 SQL 代码的查询,此方法通常包括声明和打开游标。 - 根据
querynameFetch
方法的需要设置qHandle
。 - 返回状态值。
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
不建议一般使用此关键字。
在此方法实现中,使用以下常规逻辑:
- 检查以确定它是否应返回更多结果。
- 如果适用,请检索一行数据并创建一个
%List
对象,并将其放在Row
变量中。 - 根据此方法的后续调用(如果有)或
querynameClose
() 方法需要设置qHandle
。 - 如果不存在更多行,请将
Row
设置为null
字符串,并将AtEnd
设置为 1。 - 返回状态值。
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()
方法中)必须与Close
和Fetch
位于同一MAC
例程中,并且必须位于它们中的任何一个之前。如本章前面所示,在 querynameFetch() 和 querynameClose() 方法定义中使用方法关键字PlaceAfter
来确保发生这种情况。