0 关注者 · 478 帖子

SQL 是在关系数据库中存储、操作和检索数据的标准语言。

文章 姚 鑫 · 三月 30, 2021 9m read

第十四章 使用SQL Shell界面(一)

执行SQL的其他方式

可以使用$SYSTEM.SQL.Execute() 方法从Terminal命令行执行一行SQL代码,而无需调用SQL Shell。以下示例显示如何在终端提示下使用此方法:

DHC-APP>SET result=$SYSTEM.SQL.Execute("SELECT TOP 5 name,dob,ssn FROM Sample.Person")
 
DHC-APP>DO result.%Display()
Name    DOB     SSN
yaoxin  54536   111-11-1117
xiaoli          111-11-1111
姚鑫    63189   111-11-1112
姚鑫    63189   111-11-1113
姚鑫    50066   111-11-1114
 
5 Rows(s) Affected

如果SQL语句包含错误,则Execute()方法成功完成;否则,该方法无效。 %Display()方法返回错误信息,例如:

USER>DO result.%Display()

[SQLCODE: <-29>:<Field not found in the applicable tables>]
[%msg: < Field 'GAME' not found in the applicable tables^ SELECT TOP ? game ,>]
0 Rows Affected
USER>

Execute()方法还提供了可选的SelectModeDialectObjectSelectMode参数。

InterSystems IRIS支持许多其他编写和执行SQL代码的方法

这些包括:

  • 嵌入式SQL:嵌入ObjectScript代码中的SQL代码。
  • 动态SQL:使用%SQL。 从ObjectScript代码中执行SQL语句的语句类方法。
  • 管理门户SQL接口:使用Execute Query接口从InterSystems IRIS管理门户执行动态SQL。

调用SQL Shell

可以使用$SYSTEM.SQL.Shell()方法在终端提示符中调用SQL Shell,如下所示:

DO $SYSTEM.SQL.Shell()

或者,可以使用%SQL作为一个实例调用SQL Shell。 Shell类,如下所示:

  DO ##class(%SQL.Shell).%Go("IRIS")

  SET sqlsh=##class(%SQL.Shell).%New()
  DO sqlsh.%Go("IRIS")

无论如何调用,SQL Shell都会返回SQL Shell提示符,如下所示:

[SQL]termprompt>>

其中[SQL]是指在SQL Shell中,termprompt是配置的终端提示符,>>是指SQL命令行。 默认情况下,SQL Shell提示符如下所示:[SQL]nsp>>,其中“nsp”是当前命名空间的名称。

DHC-APP>DO $SYSTEM.SQL.Shell()
SQL Command Line Shell
----------------------------------------------------
 
The command prefix is currently set to: <<nothing>>.
Enter q to quit, ? for help.
DHC-APP>>  << entering multiline statement mode >>
        1>>
        1>>SELECT TOP 5 name,dob,ssn FROM Sample.Person
        2>>
        2>>q
DHC-APP>>q

在这个提示符下,可以使用以下任一Shell模式:

  • 单行模式:在提示符下键入一行SQL代码。 结束SQL语句,按“Enter”。 默认情况下,这将准备并执行SQL代码(这称为立即执行模式)。 对于查询,结果集显示在终端屏幕上。 对于其他SQL语句,将在终端屏幕上显示SQLCODE和行数值。
  • 多行模式:在提示符下按Enter。这使进入多行模式。可以键入多行SQL代码,每个新行提示均指示行号。 (空行不会增加行号。)要结束多行SQL语句,请键入GO并按Enter。默认情况下,这既准备并执行SQL代码。对于查询,结果集显示在终端屏幕上。对于其他SQL语句,SQLCODE和行计数值显示在终端屏幕上。

多行模式提供以下命令,可以在多行提示符下键入以下命令,然后按Enter:LLIST列出到目前为止输入的所有SQL代码。 CCLEAR删除到目前为止输入的所有SQL代码。 C nCLEAR n(其中n是行号整数)以删除特定的SQL代码行。 GGO准备和执行SQL代码,然后返回单行模式。 QQUIT删除到目前为止输入的所有SQL代码并返回单行模式。这些命令不区分大小写。发出命令不会增加下一个多行提示的行号。打在多行提示符处列出了这些多行命令。

为了准备一条SQL语句,SQL Shell首先验证该语句,包括确认指定的表存在于当前名称空间中,并且指定的字段存在于表中。如果不是,它将显示适当的SQLCODE。

如果该语句有效,并且具有适当的特权,则SQL Shell会回显SQL语句,并为其分配一个序号。无论您是否更改名称空间和/或退出并重新进入SQL Shell,这些数字在终端会话期间都将按顺序分配。这些分配的语句编号允许重新调用以前的SQL语句,如下所述。

也可以使用DO Shell^%apiSQL.在终端提示下调用SQL Shell。

要列出所有可用的SQL Shell命令,请输入。在SQL提示下。

要终止SQL Shell会话并返回到Terminal提示符,请在SQL提示符下输入QQUIT命令或E命令。 SQL Shell命令不区分大小写。

以下是使用默认参数设置的示例SQL Shell会话:

DHC-APP>DO $SYSTEM.SQL.Shell()
SQL Command Line Shell
----------------------------------------------------
 
The command prefix is currently set to: <<nothing>>.
Enter q to quit, ? for help.
DHC-APP>>SELECT TOP 5 Name,Home_State FROM Sample.Person ORDER BY Home_State
1.      SELECT TOP 5 Name,Home_State FROM Sample.Person ORDER BY Home_State
 
Name    Home_State
xiaoli
姚鑫
姚鑫
姚鑫
姚鑫
 
5 Rows(s) Affected
statement prepare time(s)/globals/lines/disk: 0.0523s/45502/270281/2ms
          execute time(s)/globals/lines/disk: 0.0004s/225/2915/0ms
---------------------------------------------------------------------------
DHC-APP>>q

以下是使用默认参数设置的多行SQL Shell会话:

DHC-APP>DO $SYSTEM.SQL.Shell()
SQL Command Line Shell
----------------------------------------------------
 
The command prefix is currently set to: <<nothing>>.
Enter q to quit, ? for help.
DHC-APP>>  << entering multiline statement mode >>
        1>>SELECT TOP 5
        2>>Name,Home_State
        3>>FROM Sample.Person
        4>>ORDER BY Home_State
        5>>GO
2.      SELECT TOP 5
        Name,Home_State
        FROM Sample.Person
        ORDER BY Home_State
 
Name    Home_State
xiaoli
姚鑫
姚鑫
姚鑫
姚鑫
 
5 Rows(s) Affected
statement prepare time(s)/globals/lines/disk: 0.0002s/18/1168/0ms
          execute time(s)/globals/lines/disk: 0.0003s/225/2886/0ms
---------------------------------------------------------------------------

GO命令

SQL Shell GO命令执行最新的SQL语句。在单行模式下,GO重新执行最近执行的SQL语句。在多行模式下,GO命令用于执行多行SQL语句并退出多行模式。单行模式下的后续GO将重新执行先前的多行SQL语句。

输入参数

SQL Shell支持使用“?”输入参数的使用SQL语句中的字符。每次执行SQL语句时,系统都会提示指定这些输入参数的值。必须以与“?”相同的顺序指定这些值字符出现在SQL语句中:第一个提示为第一个“?”提供一个值,第二个提示为第二个“?”提供一个值,依此类推。

输入参数的数量没有限制。可以使用输入参数将值提供给TOP子句,WHERE子句,并将表达式提供给SELECT列表。不能使用输入参数将列名提供给SELECT列表。

可以将主机变量指定为输入参数值。在输入参数提示下,指定一个以冒号(:)开头的值。该值可以是公共变量,ObjectScript特殊变量,数字文字或表达式。然后,SQL Shell会提示“这是文字(Y / N)吗?”。在此提示下指定N(否)(或仅按Enter)意味着将输入值解析为主机变量。例如,:myval将被解析为局部变量myval的值; :^ myval将被解析为全局变量^myval的值; :$HOROLOG将被解析为$HOROLOG特殊变量的值; :3将被解析为数字3; :10-3将被解析为数字7。在此提示符下指定Y(是)表示将输入值(包括冒号)作为文字提供给输入参数。

执行ObjectScript命令

在SQL Shell中,可能希望发出一个ObjectScript命令。例如,通过使用SET $NAMESPACE命令将InterSystems IRIS命名空间更改为包含要引用的SQL表或存储过程的命名空间。可以使用SQL Shell!命令或OBJ命令以发出由一个或多个ObjectScript命令组成的ObjectScript命令行。 (OBJ是OBJECTSCRIPT的缩写。)!,OBJ和OBJECTSCRIPT命令是同义词。以下示例显示了这些命令的用法:

%SYS>DO $SYSTEM.SQL.Shell()
SQL Command Line Shell
----------------------------------------------
The command prefix is currently set to: <<nothing>>.
Enter q to quit, ? for help.
[SQL]%SYS>>! SET oldns=$NAMESPACE SET $NAMESPACE="USER" WRITE "changed the namespace"
changed the namespace
[SQL]USER>>OBJ SET $NAMESPACE=oldns WRITE "reverted to old namespace"
reverted to old namespace
[SQL]%SYS>>

OBJ命令之后的其余命令行被视为ObjectScript代码。 !之间不需要空格。和ObjectScript命令行。可以在SQL Shell单行模式或SQL Shell多行模式下指定OBJ命令。以下示例在USER名称空间中定义的表上执行SELECT查询:

%SYS>DO $SYSTEM.SQL.Shell()
SQL Command Line Shell
----------------------------------------------
The command prefix is currently set to: <<nothing>>.
Enter q to quit, ? for help.
[SQL]%SYS>>  << entering multiline statement mode >>
        1>>OBJ SET $NAMESPACE="USER"

        1>>SELECT TOP 5 Name,Home_State
        2>>FROM Sample.Person
        3>>GO
   /* SQL query results */
[SQL]USER>>

请注意,OBJ语句不会增加SQL行数。

在SQL Shell多行模式下,在返回行时将执行OBJ命令,但是直到指定GO才发出SQL语句。因此,以下示例在功能上与先前的示例相同:

%SYS>DO $SYSTEM.SQL.Shell()
SQL Command Line Shell
----------------------------------------------
The command prefix is currently set to: <<nothing>>.
Enter q to quit, ? for help.
[SQL]%SYS>>  << entering multiline statement mode >>
        1>>SELECT TOP 5 Name,Home_State
        2>>FROM Sample.Person
        3>>OBJ SET $NAMESPACE="USER" WRITE "changed namespace"
changed namespace
        3>>GO
   /* SQL query results */
[SQL]USER>>

以下示例使用OBJ命令定义主机变量:

USER>DO $SYSTEM.SQL.Shell()
SQL Command Line Shell
----------------------------------------------
The command prefix is currently set to: <<nothing>>.
Enter q to quit, ? for help.
[SQL]USER>>  << entering multiline statement mode >>
        1>>SELECT TOP :n Name,Home_State
        2>>FROM Sample.Person
        3>>OBJ SET n=5

        3>>GO

浏览命名空间

SQL Shell支持BROWSE命令,该命令显示在当前名称空间中定义或可从其访问的架构,表和视图。该显示包括多个级别的提示。要返回上一个提示级别,请在提示时按Return键。名称区分大小写。

  1. 在SQL Shell提示符下键入BROWSE,以列出当前名称空间中的架构。
  2. 在“架构:”提示下,按名称或编号选择一个架构。这将列出架构中的表和视图。
  3. 在“表/视图:”提示下,按名称或编号选择一个表(T)或视图(V)。这将显示表信息,然后显示选项列表。
  4. 在“选项:”提示下,按编号选择一个选项。可以使用此选项列出为表定义的字段或映射。

指定选项1(按名称表示的字段)或选项2(按数字表示的字段)以显示“Field:”提示。指定选项3(地图)以显示“Map:”提示。

  1. “Field:”提示下,按数字或名称选择一个字段,或指定*以列出所有字段。这列出了详细的字段信息。

“Map:”提示下,按数字或名称选择地图,或指定*列出所有Map。这列出了详细的Map信息。

CALL 命令

可以使用SQL Shell发出SQL CALL语句来调用SQL存储过程,如以下示例所示:

DHC-APP>>CALL Sample.PersonSets('G','NY')
3.      CALL Sample.PersonSets('G','NY')
 
 
 
Dumping result #1
Name    DOB     Spouse
Gallant,Thelma Q.       45767   94
Gibbs,Mark S.   37331   13
Goldman,Will H. 59069   10
Gomez,Mo Q.     55626   55
Gore,Alfred M.  42991   13
Gore,Fred X.    32391   6
Grabscheid,Jocelyn B.   59676   79
 
7 Rows(s) Affected
 
Dumping result #2
Name    Age     Home_City       Home_State
Chadbourne,Danielle G.  34      Pueblo  NY
Eastman,Clint G.        4       Miami   NY
Pape,Linda M.   71      Vail    NY
Peterson,Janice N.      49      Islip   NY
Schaefer,Jocelyn V.     93      Zanesville      NY
 
5 Rows(s) Affected
statement prepare time(s)/globals/lines/disk: 0.0043s/2153/15795/0ms
          execute time(s)/globals/lines/disk: 0.0015s/315/7829/0ms
---------------------------------------------------------------------------

如果指定的存储过程在当前名称空间中不存在,则SQL Shell会发出SQLCODE -428错误。

如果指定的输入参数多于存储过程中定义的参数,则SQL Shell会发出SQLCODE -370错误。可以使用文字(“字符串”),主机变量(:var)和输入参数()的任意组合为存储过程指定参数值。

可以在CALL语句中使用主机变量,如以下示例所示:

[SQL]USER>>OBJ SET a="G",b="NY"
[SQL]USER>>CALL Sample.PersonSets(:a,:b)
  • 可以在CALL语句中使用输入参数(“?”字符),如以下示例所示:
[SQL]USER>>CALL Sample.PersonSets(?,?)

当执行CALL语句时,SQL Shell会提示为每个输入参数提供一个值。

执行SQL脚本文件

SQL Shell RUN命令执行一个SQL脚本文件。脚本文件的类型由DIALECT设置确定。 DIALECT的默认值为IRIS(InterSystems SQL)。

0
0 882
文章 姚 鑫 · 三月 29, 2021 16m read

第十三章 使用动态SQL(七)

SQL元数据

动态SQL提供以下类型的元数据:

  • 在“准备”之后,描述查询类型的元数据。
  • 在“准备”之后,描述查询中选择项的元数据(“列”和“扩展列信息”)。
  • 在准备之后,描述查询参数的元数据:参数,:var参数和常量。 (语句参数,形式参数和对象)
  • 执行之后,描述查询结果集的元数据。在执行Prepare操作(%Prepare()%PrepareClassQuery()%ExecDirect())之后,可以使用%SQL.StatementMetadata属性值。
  • 可以直接为最新的%Prepare()返回%SQL.Statement元数据属性。
  • 可以返回包含%SQL.StatementMetadata属性的oref的%SQL.Statement%Metadata属性。这使可以返回多个准备操作的元数据。

SELECTCALL语句返回所有这些元数据。 INSERTUPDATEDELETE返回语句类型元数据和形式参数。

语句类型元数据

使用%SQL.Statement类进行Prepare之后,可以使用%SQL.StatementMetadata statementType属性来确定准备哪种类型的SQL语句,如以下示例所示。本示例使用%SQL.Statement%Metadata属性来保存和比较两个Prepare操作的元数据:

/// d ##class(PHA.TEST.SQL).MetaData()
ClassMethod MetaData()
{
	SET tStatement = ##class(%SQL.Statement).%New()
	SET myquery1 = "SELECT TOP ? Name,Age,AVG(Age),CURRENT_DATE FROM Sample.Person"
	SET myquery2 = "CALL Sample.SP_Sample_By_Name(?)"
	SET qStatus = tStatement.%Prepare(myquery1)
	IF qStatus'=1 {
		WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT
	}
	SET meta1 = tStatement.%Metadata
	SET qStatus = tStatement.%Prepare(myquery2)
	IF qStatus'=1 {
		WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT
	}
	SET meta2 = tStatement.%Metadata
	WRITE "语句类型query 1: ",meta1.statementType,!
	WRITE "语句类型query 2: ",meta2.statementType,!
	WRITE "End of metadata"
}

DHC-APP>d ##class(PHA.TEST.SQL).MetaData()
语句类型query 1: 1
语句类型query 2: 45
End of metadata

statementType属性的“类引用”条目列出了语句类型整数代码。最常见的代码是1(SELECT查询)和45(CALL到存储的查询)。

可以使用%GetImplementationDetails()实例方法返回相同的信息,如成功准备的结果中所述。

执行查询后,可以从结果集中返回语句类型名称(例如SELECT)。

选择项目Select-item元数据

使用%SQL.Statement类准备SELECTCALL语句之后,可以通过显示所有元数据或指定各个元数据项来返回有关查询中指定的每个选择项列的元数据。此列元数据包括ODBC数据类型信息,以及客户端类型和InterSystems Objects属性的起源以及类类型信息。

以下示例返回最近准备的查询中指定的列数:

/// d ##class(PHA.TEST.SQL).MetaData1()
ClassMethod MetaData1()
{
	SET myquery = "SELECT %ID AS id,Name,DOB,Age,AVG(Age),CURRENT_DATE,Home_State FROM Sample.Person"
	SET tStatement = ##class(%SQL.Statement).%New()
	SET qStatus = tStatement.%Prepare(myquery)
	IF qStatus'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT}
	WRITE "Number of columns=",tStatement.%Metadata.columnCount,!
	WRITE "End of metadata"
}
DHC-APP>d ##class(PHA.TEST.SQL).MetaData1()
Number of columns=7
End of metadata

以下示例返回列名称(或列别名),ODBC数据类型,最大数据长度(精度),以及每个SELECT项目字段的比例:

/// d ##class(PHA.TEST.SQL).MetaData2()
ClassMethod MetaData2()
{
	SET $NAMESPACE="SAMPLES"
	SET myquery=2
	SET myquery(1)="SELECT Name AS VendorName,LastPayDate,MinPayment,NetDays,"
	SET myquery(2)="AVG(MinPayment),$HOROLOG,%TABLENAME FROM Sample.Vendor"
	SET rset = ##class(%SQL.Statement).%New()
	SET qStatus = rset.%Prepare(.myquery)
	IF qStatus'=1 {
		WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT
	}
	SET x=rset.%Metadata.columns.Count()
	SET x=1
	WHILE rset.%Metadata.columns.GetAt(x) {
		SET column=rset.%Metadata.columns.GetAt(x)
		WRITE !,x," ",column.colName," 是数据类型 ",column.ODBCType
		WRITE " 大小为 ",column.precision," 规模 = ",column.scale
		SET x=x+1 
	}
	WRITE !,"End of metadata"
}

DHC-APP>d ##class(PHA.TEST.SQL).MetaData2()
 
1 VendorName 是数据类型 12 大小为 50 规模 = 0
2 LastPayDate 是数据类型 9 大小为 10 规模 = 0
3 MinPayment 是数据类型 8 大小为 6 规模 = 0
4 NetDays 是数据类型 4 大小为 3 规模 = 0
5 Aggregate_5 是数据类型 8 大小为 20 规模 = 0
6 Expression_6 是数据类型 12 大小为 255 规模 = 0
7 Literal_7 是数据类型 12 大小为 13 规模 = 0
End of metadata

下面的示例使用%SQL.StatementMetadata%Display()实例方法显示所有列元数据:

/// d ##class(PHA.TEST.SQL).MetaData3()
ClassMethod MetaData3()
{
	SET tStatement = ##class(%SQL.Statement).%New()
	SET qStatus = tStatement.%Prepare("SELECT %ID AS id,Name,DOB,Age,AVG(Age),CURRENT_DATE,Home_State FROM Sample.Person")
	IF qStatus'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT}
	DO tStatement.%Metadata.%Display()
	WRITE !,"End of metadata"
}
DHC-APP>d ##class(PHA.TEST.SQL).MetaData3()
 
 
Columns (SQLRESULTCOL, property 'columns'):
 
Column Name  Type Prec Scale Null Label        Table        Schema       CType
-----------  ---- ---- ----- ---- ------------ ------------ ------------ -----
id              4   10     0    0 id           Person       Sample          5
Name           12   50     0    0 Name         Person       Sample         10
DOB             9   10     0    1 DOB          Person       Sample          2
Age             4   10     0    1 Age          Person       Sample          5
Aggregate_5     2   20     8    1 Aggregate_5                              14
Expression_6    9   11     0    2 Expression_6                              2
Home_State     12    2     0    1 Home_State   Person       Sample         10
 
 
Extended Column Info (SQLRESULTCOL)
     Flags: 1:AutoIncrement,2:CaseSensitive,3:Currency,4:ReadOnly,5:RowVersion,
            6:Unique,7:Aliased,8:Expression,9:Hidden,10:Identity,11:KeyColumn,
            12:RowId
 
Column Name  Linked Prop           Type Class            Flags
------------ --------------------- --------------------- -----------------------
id                                 Sample.Person         Y,N,N,Y,N,Y,Y,N,N,Y,Y,Y
Name         Sample.Person.Name    %Library.String       N,N,N,N,N,N,N,N,N,N,N,N
DOB          Sample.Person.DOB     %Library.Date         N,N,N,N,N,N,N,N,N,N,N,N
Age          Sample.Person.Age     %Library.Integer      N,N,N,N,N,N,N,N,N,N,N,N
Aggregate_5                        %Library.Numeric      N,N,N,Y,N,N,Y,N,N,N,N,N
Expression_6                       %Library.Date         N,N,N,Y,N,N,Y,Y,N,N,N,N
Home_State   Sample.Address.State
                                   %Library.String       N,N,N,N,N,N,N,N,N,N,N,N
 
 
Statement Parameters (property 'parameters'):
 
Nbr. Type precision scale nullable colName      columntype
---- ---- --------- ----- -------- ------------ ----------
 
 
Formal Parameters (property 'formalParameters'):
 
Nbr. Type precision scale nullable colName      columntype
---- ---- --------- ----- -------- ------------ ----------
 
 
Objects:
 
Col Column Name Extent            ExportCall
--- ----------- ----------------- -----------------------------
  1 id          Sample.Person     ##class(Sample.Person).%SQLQuickLoad

这将返回所选字段的两个表列表。第一列元数据表列出了列定义信息:

显示标题%SQL.StatementColumn属性描述
Column NamecolName列的SQL名称。如果为该列提供了别名,则会在此处列出该列的别名,而不是字段名称。名称和别名将被截断为12个字符。对于表达式,聚合,文字,主机变量或子查询,列出了分配的“ Expression_n”“ Aggregate_n”“ Literal_n”“ HostVar_n”“ Subquery_n”标签(nSELECT项序列号)。如果为表达式,聚合,文字,主机变量或子查询分配了别名,则在此处列出该别名。
TypeODBCTypeODBC数据类型的整数代码。请注意,这些ODBC数据类型代码与CType数据类型代码不同。
Precprecision精度或最大长度(以字符为单位)。日期,时间,PosixTime和TimeStamp数据类型中描述了TIME数据类型的精度和小数位元数据。
Scalescale小数位数的最大数目。对于整数或非数值返回0。日期,时间,PosixTime和TimeStamp数据类型中描述了TIME数据类型的精度和小数位元数据。
NullisNullable一个整数值,指示是否将列定义为Non-NULL(0),或者是否允许NULL(1)。 RowID返回0。如果SELECT项是可能导致NULL的聚合或子查询,或者如果它指定NULL文字,则该项设置为1。如果SELECT项是表达式或主机变量,则设置此项到2(无法确定)。
Labellabel列名或列别名(与列名相同)。
TabletableNameSQL表名称。即使为表指定了别名,也始终在此处列出实际的表名。如果SELECT项是表达式或聚合,则不会列出任何表名。如果SELECT项是子查询,则列出子查询表名称。
SchemaschemaName表的架构名称。如果未指定架构名称,则返回系统范围的默认架构。如果SELECT项是表达式或聚合,则不会列出任何模式名称。如果SELECT项是子查询,则不会列出任何架构名称。
CTypeclientType客户端数据类型的整数代码。

第二列元数据表列出了扩展列信息。扩展列信息表列出了具有十二个布尔标志(SQLRESULTCOL)的每一列,这些标志被指定为Y(是)或N(否):

显示标题%SQL.StatementColumn属性描述
1: AutoIncrementisAutoIncrementTRowID和IDENTITY字段返回Y。
2: CaseSensitiveisCaseSensitive具有%EXACT归类的字符串数据类型字段返回Y。引用%SerialObject嵌入式对象的属性返回Y。
3: CurrencyisCurrency使用%Library.Currency数据类型定义的字段,例如MONEY数据类型。
4: ReadOnlyisReadOnly表达式,聚合,文字,HostVar或子查询返回Y。RowID,IDENTITY和RowVersion字段返回Y。
5: RowVersionisRowVersionRowVersion字段返回Y。
6: UniqueisUnique定义为具有唯一值约束的字段。 RowID和IDENTITY字段返回Y。
7: AliasedisAliased系统为非字段选择项提供别名。因此,无论用户是否通过指定列别名替换了系统别名,表达式,聚合,文字,HostVar或子查询都将返回Y。此标志不受用户指定的列别名的影响。
8: ExpressionisExpression表达式返回Y。
9: HiddenisHidden如果使用%PUBLICROWIDSqlRowIdPrivate = 0(默认值)定义表,则RowID字段返回N。否则,RowID字段返回Y。引用%SerialObject嵌入式对象的属性返回Y。
10: IdentityisIdentity定义为IDENTITY字段的字段返回Y。如果未隐藏RowID,则RowID字段返回Y。
11: KeyColumnisKeyColumn定义为主键字段或外键约束目标的字段。 RowID字段返回Y。
12: RowIDisRowIdROWID和Identity字段返回Y.

扩展列信息元数据表列出了每个选定字段的列名称(SQL名称或列别名),链接属性(链接的持久性类属性)和类型类(数据类型类)。请注意,链接属性列出了持久性类名(不是SQL表名)和属性名(不是列别名)。

  • 对于普通表字段(SELECT Name FROM Sample.Person): Linked Prop=Sample.Person.Name, Type Class=%Library.String.
  • 对于表格的RowID (SELECT %ID FROM Sample.Person): Linked Prop= [none], Type Class=Sample.Person.
  • 对于表达式,聚合,文字,HostVar或子查询 (SELECT COUNT(Name) FROM Sample.Person): Linked Prop= [none], Type Class=%Library.BigInt.
  • 供参考%Serial Object嵌入式对象属性 (SELECT Home_State FROM Sample.Person). Linked Prop=Sample.Address.State, Type Class=%Library.String.
  • 对于引用%SerialObject嵌入式对象的字段(SELECT Home FROM Sample.Person). Linked Prop=Sample.Person.Home, Type Class=Sample.Address.

在此示例中,Sample.Person中的Home_State字段引用%SerialObjectSample.AddressState属性。

下面的示例返回带有一个形式参数(也就是语句参数)的被调用存储过程的元数据:

/// d ##class(PHA.TEST.SQL).MetaData4()
ClassMethod MetaData4()
{
	SET $NAMESPACE="SAMPLES"
	SET mysql = "CALL Sample.SP_Sample_By_Name(?)"
	SET tStatement = ##class(%SQL.Statement).%New()
	SET qStatus = tStatement.%Prepare(.mysql)
	IF qStatus'=1 {
		WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT
	}
	DO tStatement.%Metadata.%Display()
	WRITE !,"End of metadata"
}

它不仅返回列(字段)信息,还返回语句参数,形式参数和对象的值。

以下示例返回具有三个形式参数的的元数据。这三个参数之一用问号()指定,使其成为语句参数:

/// d ##class(PHA.TEST.SQL).MetaData5()
ClassMethod MetaData5()
{
	SET $NAMESPACE="SAMPLES"
	SET mycall = "CALL personsets(?,'MA')"
	SET tStatement = ##class(%SQL.Statement).%New(0,"sample")
	SET qStatus = tStatement.%Prepare(mycall)
	IF qStatus'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT}
	DO tStatement.%Metadata.%Display()
	WRITE !,"End of metadata"
}

DHC-APP>d ##class(PHA.TEST.SQL).MetaData5()
 
 
Columns (SQLRESULTCOL, property 'columns'):
 
Column Name  Type Prec Scale Null Label        Table        Schema       CType
-----------  ---- ---- ----- ---- ------------ ------------ ------------ -----
 
 
Extended Column Info (SQLRESULTCOL)
     Flags: 1:AutoIncrement,2:CaseSensitive,3:Currency,4:ReadOnly,5:RowVersion,
            6:Unique,7:Aliased,8:Expression,9:Hidden,10:Identity,11:KeyColumn,
            12:RowId
 
Column Name  Linked Prop           Type Class            Flags
------------ --------------------- --------------------- -----------------------
 
 
Statement Parameters (property 'parameters'):
 
Nbr. Type precision scale nullable colName      columntype
---- ---- --------- ----- -------- ------------ ----------
   1   12     50        0     2    name            1
 
 
Formal Parameters (property 'formalParameters'):
 
Nbr. Type precision scale nullable colName      columntype
---- ---- --------- ----- -------- ------------ ----------
   1    4      4        0     2    _isc_sp_ret_val 5
   2   12     50        0     2    name            1
   3   12     50        0     2    state           1
 
 
Objects:
 
Col Column Name Extent            ExportCall
--- ----------- ----------------- -----------------------------
 
End of metadata

请注意,此元数据不返回任何列信息,但是“语句参数”,“形式参数”列表包含列名称和数据类型。

Query参数元数据

使用%SQL.Statement类进行Prepare之后,您可以返回有关查询参数的元数据:输入参数(指定为问号()),输入主机变量(指定为:varname)和常量(文字值)。可以返回以下元数据:

  • 参数:parameterCount属性
  • ODBC数据类型为参数:%SQL.StatementMetadata%Display()实例方法“语句参数”列表。
  • ?,v(:var)和c(常量)参数的列表:%GetImplementationDetails()实例方法,如成功准备的结果中所述。
  • ?,v(:var)和c(常量)参数的ODBC数据类型:formalParameters属性。 %SQL.StatementMetadata%Display()实例方法“形式参数”列表。
  • 查询文本,其中显示以下参数:%GetImplementationDetails()实例方法,如成功准备结果中所述。

语句元数据%Display()方法列出了“语句参数”和“形式参数”。对于每个参数,它列出了顺序参数号,ODBC数据类型,精度,小数位数,该参数是否可为空(2表示始终提供一个值)及其对应的属性名称(colName)和列类型。

请注意,某些ODBC数据类型以负整数形式返回。

下面的示例按顺序返回每个查询参数(:var和常量)的ODBC数据类型。请注意,TOP参数以数据类型12(VARCHAR)而不是数据类型4(INTEGER)返回,因为可以指定TOP ALL

/// d ##class(PHA.TEST.SQL).MetaData6()
ClassMethod MetaData6()
{
	SET myquery = 4
	SET myquery(1) = "SELECT TOP ? Name,DOB,Age+10 "
	SET myquery(2) = "FROM Sample.Person"
	SET myquery(3) = "WHERE %ID BETWEEN :startid :endid AND DOB=?"
	SET myquery(4) = "ORDER BY $PIECE(Name,',',?)"
	SET tStatement = ##class(%SQL.Statement).%New()
	SET qStatus = tStatement.%Prepare(.myquery)
	IF qStatus'=1 {
		WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT
	}
	SET prepmeta = tStatement.%Metadata
	WRITE "Number of ? parameters=",prepmeta.parameterCount,!
	SET formalobj = prepmeta.formalParameters
	SET i=1
	WHILE formalobj.GetAt(i) {
		SET prop=formalobj.GetAt(i)
		WRITE prop.colName," type= ",prop.ODBCType,!
		SET i=i+1
	}
	WRITE "End of metadata"
}

执行Execute之后,无法从查询结果集元数据中获取参数元数据。在结果集中,所有参数均已解析。因此parameterCount = 0formalParameters不包含任何数据。

Query结果集元数据

使用%SQL.Statement类执行Execute之后,可以通过调用返回结果集元数据:

  • %SQL.StatementResult类的属性。
  • %SQL.StatementResult%GetMetadata()方法,访问%SQL.StatementMetadata类属性。

%SQL.StatementResult属性

执行查询操作后,%SQL.StatementResult返回:

  • %StatementType属性返回与最近执行的SQL语句相对应的整数代码。以下是这些整数代码的部分列表:1 = SELECT; 2 = INSERT; 3 = UPDATE; 4 = DELETE or TRUNCATE TABLE; 9 = CREATE TABLE; 15 = CREATE INDEX; 45 = CALL.
  • %StatementTypeName计算的属性基于%StatementType返回最近执行的SQL语句的命令名称。此名称以大写字母返回。请注意,TRUNCATE TABLE操作将作为DELETE返回。即使执行了更新操作,INSERT OR UPDATE也将作为INSERT返回。
  • %ResultColumnCount属性返回结果集行中的列数。

下面的示例显示这些属性:

/// d ##class(PHA.TEST.SQL).MetaData7()
ClassMethod MetaData7()
{
	SET myquery = "SELECT TOP ? Name,DOB,Age FROM Sample.Person WHERE Age > ?"
	SET tStatement = ##class(%SQL.Statement).%New()
	SET qStatus = tStatement.%Prepare(myquery)
	IF qStatus'=1 {
		WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT
	}
	SET rset = tStatement.%Execute(10,55)
	IF rset.%SQLCODE=0 {
		WRITE "Statement type=",rset.%StatementType,!
		WRITE "Statement name=",rset.%StatementTypeName,!
		WRITE "Column count=",rset.%ResultColumnCount,!
		WRITE "End of metadata" 
	} ELSE { 
		WRITE !,"SQLCODE=",rset.%SQLCODE," ",rset.%Message 
	}
}

%SQL.StatementResult %GetMetadata()

执行之后,可以使用%SQL.StatementResult %GetMetadata()方法访问%SQL.StatementMetadata类属性。这些是在Prepare之后由%SQL.Statement%Metadata属性访问的相同属性。

以下示例显示了属性:

/// d ##class(PHA.TEST.SQL).MetaData8()
ClassMethod MetaData8()
{
	SET myquery=2
	SET myquery(1)="SELECT Name AS VendorName,LastPayDate,MinPayment,NetDays,"
	SET myquery(2)="AVG(MinPayment),$HOROLOG,%TABLENAME FROM Sample.Vendor"
	SET tStatement = ##class(%SQL.Statement).%New()
	SET qStatus = tStatement.%Prepare(.myquery)
	IF qStatus'=1 {
		WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT
	}
	SET rset = tStatement.%Execute()
	IF rset.%SQLCODE=0 {
	SET rsmeta=rset.%GetMetadata()
	SET x=rsmeta.columns.Count()
	SET x=1
	WHILE rsmeta.columns.GetAt(x) {
		SET column=rsmeta.columns.GetAt(x)
		WRITE !,x," ",column.colName," is data type ",column.ODBCType
		WRITE " with a size of ",column.precision," and scale = ",column.scale
		SET x=x+1 }
	} ELSE { 
		WRITE !,"SQLCODE=",rset.%SQLCODE," ",rset.%Message 
	}
	WRITE !,"End of metadata"
}

请注意,结果集元数据不提供参数元数据。这是因为Execute操作会解析所有参数。因此,在结果集中,parameterCount = 0,而formalParameters不包含任何数据。

审核动态SQL

InterSystems IRIS支持动态SQL语句的可选审核。启用%System /%SQL / DynamicStatement系统审核事件时,将执行动态SQL审核。默认情况下,未启用此系统审核事件。

如果启用%System /%SQL / DynamicStatement,则系统将自动审核在系统范围内执行的每个%SQL.Statement动态语句。审核将信息记录在审核数据库中。

要查看审核数据库,请依次转到管理门户,系统管理,选择安全性,审核,然后查看审核数据库。可以将“事件名称”过滤器设置为DynamicStatement,以将View Audit Database限制为Dynamic SQL语句。审核数据库列出了时间(本地时间戳),用户,PID(进程ID)和事件的描述。说明指定动态SQL语句的类型。例如,SQL SELECT语句(%SQL.Statement)或SQL CREATE VIEW语句(%SQL.Statement)。

通过选择事件的详细信息链接,可以列出其他信息,包括事件数据。事件数据包括执行的SQL语句和该语句的任何参数的值。例如:

SELECT TOP ? Name , Age FROM Sample . MyTest WHERE Name %STARTSWITH ?
/*#OPTIONS {"DynamicSQLTypeList":",1"} */ 
Parameter values:
%CallArgs(1)=5 
%CallArgs(2)="Fred"

事件数据的总长度(包括语句和参数)为3,632,952个字符。如果该语句和参数长于3632952,则事件数据将被截断。

InterSystems IRIS还支持ODBC和JDBC语句的审核(事件名称= XDBCStatement),以及嵌入式SQL语句的审核(事件名称= EmbeddedStatement)。

0
0 224
文章 姚 鑫 · 三月 28, 2021 7m read

第十三章 使用动态SQL(六)

%ObjectSelectMode = 1 Swizzling字段名称属性

下面的示例使用%ObjectSelectMode = 1进行准备,当使用字段名称属性返回值时,其类型类别为可Swizzle类型的字段(持久性类,序列类或流类)将自动发生Swizzle。转换字段值的结果是相应的对象参考(oref)。使用%Get()%GetData()方法访问字段时,InterSystems IRIS不会执行此筛选操作。在此示例中,rset.Home处于Swizzle状态,而引用同一字段的rset.%GetData(2)处于not swizzled状态:

/// d ##class(PHA.TEST.SQL).PropSQL2()
ClassMethod PropSQL2()
{
	SET myquery = "SELECT TOP 5 Name,Home FROM Sample.Person"
	SET tStatement = ##class(%SQL.Statement).%New(0)
	SET tStatement.%ObjectSelectMode=1
	WRITE !,"set ObjectSelectMode=",tStatement.%ObjectSelectMode,!
	SET qStatus = tStatement.%Prepare(myquery)
	IF qStatus'=1 {
		WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT
	}
	SET rset = tStatement.%Execute()
	WHILE rset.%Next() {
		WRITE "Row count ",rset.%ROWCOUNT,!
		WRITE rset.Name
		WRITE " ",rset.Home,!
		WRITE rset.%GetData(1)
		WRITE " ",$LISTTOSTRING(rset.%GetData(2)),!!
	}
	WRITE !,"End of data"
	WRITE !,"Total row count=",rset.%ROWCOUNT
}

DHC-APP> d ##class(PHA.TEST.SQL).PropSQL2()
 
set ObjectSelectMode=1
Row count 1
yaoxin 5@Sample.Address
yaoxin 889 Clinton Drive,St Louis,WI,78672
 
Row count 2
xiaoli 5@Sample.Address
xiaoli
 
Row count 3
姚鑫 5@Sample.Address
姚鑫
 
Row count 4
姚鑫 5@Sample.Address
姚鑫
 

下面的示例使用%ObjectSelectMode = 1从唯一记录ID(%ID)导出所选记录的Home_State值。请注意,在原始查询中未选择Home_State字段:

/// d ##class(PHA.TEST.SQL).PropSQL2()
ClassMethod PropSQL2()
{
	SET myquery = "SELECT TOP 5 %ID AS MyID,Name,Age FROM Sample.Person"
	SET tStatement = ##class(%SQL.Statement).%New()
	SET tStatement.%ObjectSelectMode=1
	SET qStatus = tStatement.%Prepare(myquery)
	IF qStatus'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT}
	SET rset = tStatement.%Execute()
	WHILE rset.%Next() {
		WRITE rset.Name
		WRITE " Home State:",rset.MyID.Home.State,!
	}
	WRITE !,"End of data"
	WRITE !,"Total row count=",rset.%ROWCOUNT
}

DHC-APP>d ##class(PHA.TEST.SQL).PropSQL2()
yaoxin Home State:WI
xiaoli Home State:
姚鑫 Home State:
姚鑫 Home State:
姚鑫 Home State:
 
End of data
Total row count=5

如果已配置,则如果配置了swizzled属性,但系统无法生成引用,则系统会生成<SWIZZLE FAIL>错误。如果引用的属性已从磁盘中意外删除或被另一个进程锁定,则会发生这种情况。要确定SWIZZLE失败的原因,请在<SWIZZLE FAIL>错误之后立即在%objlasterror中查找并解码此%Status值。

默认情况下,未配置<SWIZZLE FAIL>。可以通过设置SET ^%SYS("ThrowSwizzleError")=1或使用InterSystems IRIS管理门户来全局设置此行为。在“系统管理”中,选择“配置”,然后选择“ SQL和对象设置”,然后选择“对象”。在此屏幕上,可以设置<SWIZZLE FAIL>选项。

%Get("fieldname")方法

可以使用%Get(“ fieldname”)实例方法按字段名称或字段名称别名返回数据值。 Dynamic SQL根据需要解析字母大小写。如果指定的字段名称或字段名称别名不存在,系统将生成<PROPERTY DOES NOT EXIST>错误。

下面的示例从查询结果集中返回Home_State字段和Last_Name别名的值。

/// d ##class(PHA.TEST.SQL).PropSQL4()
ClassMethod PropSQL4()
{
	SET myquery = "SELECT TOP 5 Home_State,Name AS Last_Name FROM Sample.Person"
	SET tStatement = ##class(%SQL.Statement).%New(2)
	SET qStatus = tStatement.%Prepare(myquery)
	IF qStatus'=1 {
		WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT
	}
	SET rset = tStatement.%Execute()
	WHILE rset.%Next() {
		WRITE rset.%Get("Home_State")," : ",rset.%Get("Last_Name"),!
	}
	WRITE !,"End of data"
	WRITE !,"Total row count=",rset.%ROWCOUNT
}
DHC-APP>d ##class(PHA.TEST.SQL).PropSQL4()
WI : yaoxin
 : xiaoli
 : 姚鑫
 : 姚鑫
 : 姚鑫
 
End of data
Total row count=5

必须使用%Get("fieldname")实例方法从使用%PrepareClassQuery()准备的现有查询中按字段属性名称检索单个数据项。如果字段属性名称不存在,则系统会生成<PROPERTY DOES NOT EXIST>错误。

下面的示例从内置查询中按字段属性名称返回Nsp(命名空间)字段值。因为此查询是现有的存储查询,所以此字段检索需要使用%Get("fieldname") 方法。请注意,由于“Nsp”是属性名称,因此区分大小写:

/// d ##class(PHA.TEST.SQL).PropSQL5()
ClassMethod PropSQL5()
{
	SET tStatement = ##class(%SQL.Statement).%New(2)
	SET qStatus = tStatement.%PrepareClassQuery("%SYS.Namespace","List")
	IF qStatus'=1 {
		WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT
	}
	SET rset = tStatement.%Execute()
	WHILE rset.%Next() {
		WRITE "Namespace: ",rset.%Get("Nsp"),!
	}
	WRITE !,"End of data"
	WRITE !,"Total row count=",rset.%ROWCOUNT
}
DHC-APP>d ##class(PHA.TEST.SQL).PropSQL5()
Namespace: %SYS
Namespace: DHC-APP
Namespace: DHC-CHSSWEB
Namespace: DHC-CSM
Namespace: DHC-DATA
Namespace: DHC-DWR
Namespace: DHC-EKG
Namespace: DHC-HEIS
Namespace: DHC-HR
Namespace: DHC-LISDATA
Namespace: DHC-LISSRC
Namespace: DHC-MEDSRC
Namespace: DHC-MRQ
Namespace: DOCBOOK
Namespace: FDBMS
Namespace: PACS
Namespace: PIS
Namespace: RIS
Namespace: SAMPLES
Namespace: USER
 
End of data
Total row count=20

重复名称:如果名称解析为相同的属性名称,则它们是重复的。重复名称可以是对同一字段的多个引用,对表中不同字段的引用或对不同表中字段的引用。如果SELECT语句包含相同字段名称或字段名称别名的多个实例,则%Get(“fieldname”)始终返回查询中指定的重复名称的最后一个实例。这与rset.PropName相反,后者返回查询中指定的重复名称的第一个实例。在下面的示例中显示:

/// d ##class(PHA.TEST.SQL).PropSQL6()
ClassMethod PropSQL6()
{
	SET myquery = "SELECT c.Name,p.Name FROM Sample.Person AS p,Sample.Company AS c"
	SET tStatement = ##class(%SQL.Statement).%New()
	SET qStatus = tStatement.%Prepare(myquery)
	IF qStatus'=1 {
		WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT
	}
	SET rset = tStatement.%Execute()
	WHILE rset.%Next() {
		WRITE "Prop=",rset.Name," Get=",rset.%Get("Name"),! 
	}
	WRITE !,rset.%ROWCOUNT," End of data"
}

%GetData(n)方法

%GetData(n)实例方法返回由结果集的整数计数列号索引的当前行的数据您可以将%GetData(n)与使用%Prepare()准备的指定查询或使用%PrepareClassQuery()准备的存储查询一起使用。

使用%PrepareClassQuery()准备。 整数n对应于查询中指定的选择项列表的序列。除非在选择项列表中明确指定,否则不会为RowID字段提供整数n值。如果n大于查询中的选择项数,或者为0,或者为负数,则Dynamic SQL不返回任何值,也不发出错误。

%GetData(n)是返回特定重复字段名称或重复别名的唯一方法; rset.Name返回第一个重复项,%Get(“Name”)返回最后一个重复项。

/// d ##class(PHA.TEST.SQL).PropSQL7()
ClassMethod PropSQL7()
{
	SET myquery="SELECT TOP 5 Name,SSN,Age FROM Sample.Person"
	SET tStatement = ##class(%SQL.Statement).%New()
	SET qStatus = tStatement.%Prepare(myquery)
	IF qStatus'=1 {
		WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT
	}
	SET rset = tStatement.%Execute()
	WHILE rset.%Next() {
		WRITE "Years:",rset.%GetData(3)," Name:",rset.%GetData(1),!
	}
	WRITE "End of data"
	WRITE !,"Total row count=",rset.%ROWCOUNT
}
DHC-APP>d ##class(PHA.TEST.SQL).PropSQL7()
Years:30 Name:yaoxin
Years: Name:xiaoli
Years:7 Name:姚鑫
Years:7 Name:姚鑫
Years:43 Name:姚鑫
End of data
Total row count=5

返回多个结果集

CALL语句可以将多个动态结果集作为一个集合返回,称为结果集序列(RSS)。

下面的示例使用%NextResult()方法分别返回多个结果集:

/// d ##class(PHA.TEST.SQL).PropSQL8()
ClassMethod PropSQL8()
{
	SET mycall = "CALL Sample.CustomSets()"
	SET rset = ##class(%SQL.Statement).%ExecDirect(,mycall)
	IF rset.%SQLCODE'=0 {
		WRITE !,"ExecDirect SQLCODE=",rset.%SQLCODE,!,rset.%Message  QUIT
	}
	SET rset1=rset.%NextResult()
	DO rset1.%Display()
	WRITE !,"End of 1st Result Set data",!!
	SET rset2=rset.%NextResult()
	DO rset2.%Display()
	WRITE !,"End of 2nd Result Set data"
}
DHC-APP> d ##class(PHA.TEST.SQL).PropSQL8()
ID      Name    Street  City    State   Spouse
3       Davis,Robert I. 4177 Franklin Court     Fargo   WY      86
2       Hanson,Roberta O.       9840 Ash Drive  Boston  KS      155
4       Huff,Olga M.    1902 Franklin Avenue    Vail    DE      150
1       Woo,Jocelyn A.  9932 Clinton Avenue     Queensbury      NM      14
5       Zubik,George T. 8102 First Drive        Denver  VA      110
 
5 Rows(s) Affected
End of 1st Result Set data
 
ID      Name    Street  City    State   Spouse
5       Campos,Alvin N. 1847 Franklin Drive     Ukiah   WY      206
1       Fripp,Kristen A.        1487 Ash Place  Islip   NC      133
3       Jafari,Christen K.      7384 Washington Court   Newton  CO      168
4       Kratzmann,Mark V.       9573 Second Blvd        Chicago OR      43
2       O'Donnell,George H.     3413 Main Drive Newton  RI      143
7       Ravazzolo,Danielle Y.   2898 Clinton Blvd       Tampa   HI      133
10      Rodriguez,Sophia U.     4766 Clinton Avenue     Ukiah   AR      202
6       Sverdlov,Phyllis J.     5010 Oak Place  Fargo   VT      214
8       Uhles,Andrew O. 4931 Madison Street     Bensonhurst     IA      129
9       Xerxes,Mo C.    49 Main Drive   Vail    CA      151
 
10 Rows(s) Affected
End of 2nd Result Set data
0
0 191
文章 姚 鑫 · 三月 26, 2021 6m read

第十三章 使用动态SQL(四)

返回完整结果集

使用%Execute()%ExecDirect()执行语句将返回一个实现%SQL.StatementResult接口的对象。该对象可以是单一值,结果集或从CALL语句返回的上下文对象。

%Display()方法

可以通过调用%SQL.StatementResult类的%Display()实例方法来显示整个结果集(结果对象的内容),如以下示例所示:

  DO rset.%Display()

请注意,%Display()方法不会返回%Status值。

显示查询结果集时,%Display()通过显示行数来结束:“受影响的5行”。 (这是%Display()遍历结果集之后的%ROWCOUNT值。)请注意,%Display()不会在此行计数语句之后发出行返回。

%Display()有两个可选参数:

  • 分隔符:在数据列和数据标题之间插入的字符串。它出现在结果集列之间,紧靠标题或数据值之前。默认为无定界符。如果省略,请在“列对齐”标志之前指定一个占位符逗号。
  • 列对齐:整数标志,指定如何计算数据列和数据标题之间的空格。可用的选项有:
    • 0:结果集标题/数据列将根据标准定界符(选项卡)对齐。这是默认值。
    • 1:结果集标题/数据列将根据列标题和标准定界符(标签)的长度对齐。
    • 2:结果集标题/数据列将根据列数据属性的精度/长度和标准定界符(选项卡)进行对齐。

%DisplayFormatted()方法

可以通过调用%SQL.StatementResult类的%DisplayFormatted()实例方法,而不是调用%Display(),将结果集内容重新格式化并重定向到生成的文件。

可以通过指定字符串选项%DisplayFormatted(“HTML”)或相应的整数代码%DisplayFormatted(1)来指定结果集格式。可以使用以下格式:XML(整数代码0),HTML(整数代码1),PDF(整数代码2),TXT(整数代码99)或CSV(整数代码100)。 (请注意,CSV格式未实现为真正的逗号分隔值输出;相反,它使用制表符来分隔列。)TXT格式(整数代码99)以行数结尾(例如,“受影响的5行”) ”);其他格式不包括行数。 InterSystems IRIS生成指定类型的文件,并附加适当的文件扩展名。

可以指定或省略结果集文件名:

  • 如果指定一个目标文件(例如,%DisplayFormatted(99,"myresults")),则在当前命名空间的子目录的mgr目录中生成具有该名称和相应后缀(文件扩展名)的文件。 例如,C:\InterSystems\IRIS\mgr\user\myresults.txt. 如果具有该后缀的指定文件已经存在,则InterSystems IRIS将用新数据覆盖它。
  • 如果没有指定目标文件(例如,%DisplayFormatted(99),则在Temp子目录的mgr目录中生成一个具有随机生成的名称和适当后缀(文件扩展名)的文件。 例如,C:\InterSystems\IRIS\mgr\Temp\w4FR2gM7tX2Fjs.txt. 每次运行一个查询时,都会生成一个新的目标文件。

这些例子显示了Windows文件名; InterSystems IRIS支持其他操作系统上的等效位置。

如果无法打开指定的文件,则此操作将在30秒后超时并显示一条错误消息;否则,该操作将超时。当用户没有对指定目录(文件夹)的WRITE权限时,通常会发生这种情况。

如果无法以指定的格式呈现数据,则将创建目标文件,但不会将结果集数据写入其中。而是将适当的消息写入目标文件。例如,流字段OID包含与XML和HTML特殊格式字符冲突的字符。可以通过在流字段上使用XMLELEMENT函数来解决此XML和HTML流字段问题。例如SELECT Name,XMLELEMENT(“ Para”,Notes)

可以选择提供%DisplayFormatted()在执行指定格式转换时将使用的转换表的名称。

如果一个结果集序列中有多个结果集,则每个结果集的内容都将写入其自己的文件中。

可选的第三个%DisplayFormatted()参数指定消息存储在单独的结果集中。成功完成后,将返回类似以下的消息:

Message
21 row(s) affected.

下面的Windows示例在C:\InterSystems\IRIS\mgr\user\中创建了两个PDF(整数代码2)结果集文件。 它为消息创建一个mess结果集,然后使用%Display()将消息显示到终端:

/// d ##class(PHA.TEST.SQL).CreatePDF()
ClassMethod CreatePDF()
{

	SET myquery=2
	SET myquery(1)="SELECT Name,Age FROM Sample.Person"
	SET myquery(2)="WHERE Age > ? AND Age < ? ORDER BY Age"
	SET rset = ##class(%SQL.Statement).%ExecDirect(,.myquery,12,20)
	IF rset.%SQLCODE'=0 {
		WRITE !,"1st ExecDirect SQLCODE=",rset.%SQLCODE,!,rset.%Message  QUIT
	}
	DO rset.%DisplayFormatted(2,"Teenagers",.mess)
	DO mess.%Display()
	WRITE !,"End of teen data",!!
	SET rset2 = ##class(%SQL.Statement).%ExecDirect(,.myquery,19,30)
	IF rset2.%SQLCODE'=0 {
		WRITE !,"2nd ExecDirect SQLCODE=",rset2.%SQLCODE,!,rset2.%Message  QUIT
	}
	DO rset2.%DisplayFormatted(2,"Twenties",.mess)
	DO mess.%Display()
	WRITE !,"End of twenties data"
}
DHC-APP>d ##class(PHA.TEST.SQL).CreatePDF()
Message
9 row(s) affected.
 
End of teen data
 
Message
20 row(s) affected.
 
End of twenties data
/// d ##class(PHA.TEST.SQL).CreatePDF1()
ClassMethod CreatePDF1()
{
	  ZNSPACE "SAMPLES"
  SET myquery=2
  SET myquery(1)="SELECT Name,Age FROM Sample.Person"
  SET myquery(2)="WHERE Age > ? AND Age < ? ORDER BY Age"
  SET rset = ##class(%SQL.Statement).%ExecDirect(,.myquery,12,20)
  DO rset.%DisplayFormatted(2,"Teenagers")
  WRITE !,"End of teen data",!!
  SET rset2 = ##class(%SQL.Statement).%ExecDirect(,.myquery,19,30)
  DO rset2.%DisplayFormatted(2,"Twenties")
  WRITE !,"End of twenties data"
}
DHC-APP>d ##class(PHA.TEST.SQL).CreatePDF1()
 
End of teen data
 
 
End of twenties data

对结果集进行分页

可以使用一个视图ID (%VID)来分页结果集。下面的例子从结果集中返回页面,每个页面包含5行:

/// d ##class(PHA.TEST.SQL).Paginating()
ClassMethod Paginating()
{
	SET q1="SELECT %VID AS RSRow,* FROM "
	SET q2="(SELECT Name,Home_State FROM Sample.Person WHERE Home_State %STARTSWITH 'M') "
	SET q3="WHERE %VID BETWEEN ? AND ?"
	SET myquery = q1_q2_q3
	SET tStatement = ##class(%SQL.Statement).%New()
	SET qStatus=tStatement.%Prepare(myquery)
	IF qStatus'=1 {
		WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT
	}
	FOR i=1:5:25 {
		WRITE !!,"Next Page",!
		SET rset=tStatement.%Execute(i,i+4)
		DO rset.%Display()
	}
}
DHC-APP>d ##class(PHA.TEST.SQL).Paginating()
 
 
Next Page
RSRow   Name    Home_State
1       O'Rielly,Chris H.       MS
2       Orwell,John V.  MT
3       Zevon,Heloisa O.        MI
4       Kratzmann,Emily Z.      MO
5       King,Dmitry G.  MO
 
5 Rows(s) Affected
 
Next Page
RSRow   Name    Home_State
6       Hanson,George C.        MD
7       Martinez,Emilio G.      MO
8       Cheng,Charlotte Y.      MI
9       Emerson,Edgar T.        MO
10      Nelson,Neil E.  MT
 
5 Rows(s) Affected
 
Next Page
RSRow   Name    Home_State
11      Larson,Nataliya Z.      MD
12      Lennon,Chelsea T.       MD
13      Ingleman,Kristen U.     MT
14      Zucherro,Olga H.        MN
15      Ng,Lola H.      MD
 
5 Rows(s) Affected
 
Next Page
RSRow   Name    Home_State
16      Frost,Xavier D. MO
17      Adams,Diane F.  MD
18      Isaacs,Chad N.  MN
19      Van De Griek,Phil S.    MS
20      Schaefer,Usha G.        MO
 
5 Rows(s) Affected
 
Next Page
RSRow   Name    Home_State
21      Wells,Sophia U. MS
22      Vivaldi,Michelle N.     MD
23      Anderson,Valery N.      MD
24      Frost,Heloisa K.        MI
25      Gallant,Thelma Q.       MA
 
5 Rows(s) Affected
0
0 278
文章 姚 鑫 · 三月 25, 2021 11m read

第十三章 使用动态SQL(三)

执行SQL语句

有两种使用%SQL.Statement类执行SQL语句的方法:

  • %Execute(),它执行以前使用%Prepare()%PrepareClassQuery()准备的SQL语句。
  • %ExecDirect(),它同时准备和执行一条SQL语句。

也可以通过使用$SYSTEM.SQL.Execute()方法执行SQL语句而无需创建对象实例。此方法既准备又执行SQL语句。它创建一个缓存的查询。下面的终端示例显示Execute()方法:

USER>SET topnum=5
USER>SET rset=$SYSTEM.SQL.Execute("SELECT TOP :topnum Name,Age FROM Sample.Person")
USER>DO rset.%Display()

%Execute()

准备查询后,可以通过调用%SQL.Statement类的%Execute()实例方法来执行查询。对于非SELECT语句,%Execute()调用所需的操作(例如执行INSERT)。对于SELECT查询,%Execute()生成一个结果集,用于后续遍历和数据检索。例如:

  SET rset = tStatement.%Execute()

%Execute()方法为所有SQL语句设置%SQL.StatementResult类属性%SQLCODE%Message%Execute()设置其他%SQL.StatementResult属性,如下所示:

  • INSERTUPDATEINSERTUPDATEDELETETRUNCATE TABLE语句将%ROWCOUNT设置为受操作影响的行数。 TRUNCATE TABLE无法确定删除的实际行数,因此将%ROWCOUNT设置为-1。

INSERTUPDATEINSERT OR UPDATEDELETE%ROWID设置为最后一条插入,更新或删除的记录的RowID值。如果该操作未插入,更新或删除任何记录,则%ROWID是未定义的,或保持设置为其先前值。 TRUNCATE TABLE没有设置%ROWID

  • SELECT语句在创建结果集时会将%ROWCOUNT属性设置为0。当程序遍历结果集的内容(例如,使用%Next()方法)时,%ROWCOUNT会增加。 %Next()返回1表示它位于一行上,返回0表示它位于最后一行之后(在结果集的末尾)。如果光标位于最后一行之后,则%ROWCOUNT的值指示结果集中包含的行数。

如果SELECT查询仅返回聚合函数,则每个%Next()都将设置%ROWCOUNT = 1。即使表中没有数据,第一个%Next()始终设置%SQLCODE = 0。任何后续的%Next()都会设置%SQLCODE = 100并设置%ROWCOUNT = 1

SELECT还设置%CurrentResult%ResultColumnCountSELECT未设置%ROWID

可以使用ZWRITE返回所有%SQL.StatementResult类属性的值。

具有输入参数的%Execute()

%Execute()方法可以采用一个或多个与准备的SQL语句中的输入参数(以“?”表示)相对应的参数。 %Execute()参数对应于“?”的顺序字符出现在SQL语句中:第一个参数用于第一个“?”,第二个参数用于第二个“?”,依此类推。多个%Execute()参数以逗号分隔。可以通过指定占位符逗号来省略参数值。 %Execute()参数的数量必须与“?”相对应输入参数。如果%Execute()参数少于或大于相应的“?”输入参数,执行失败,并且%SQLCODE属性设置为SQLCODE -400错误。

可以使用输入参数为SELECT列表和其他查询子句(包括TOP子句和WHERE子句)提供文字值或表达式。不能使用输入参数为SELECT列表或其他查询子句提供列名或列名别名。

当指定为显式%Execute()参数时,最大输入参数数为255。使用可变长度数组%Execute(vals ...)指定时,最大输入参数数为380。

在执行Prepare之后,可以使用Prepare参数元数据来返回的计数和所需的数据类型。输入参数。可以使用%GetImplementationDetails()方法返回的列表。在准备好的查询中输入参数,并在查询文本中使用输入参数显示在上下文中。

以下ObjectScript示例使用两个输入参数执行查询。它在%Execute()方法中指定输入参数值(21和26)。

/// d ##class(PHA.TEST.SQL).PrepareClassQuery7()
ClassMethod PrepareClassQuery7()
{
	SET tStatement = ##class(%SQL.Statement).%New(1)
	SET tStatement.%SchemaPath = "MyTests,Sample,Cinema"
	SET myquery=2
	SET myquery(1)="SELECT Name,DOB,Age FROM Person"
	SET myquery(2)="WHERE Age > ? AND Age < ? ORDER BY Age"
	SET qStatus = tStatement.%Prepare(.myquery)
	IF qStatus'=1 {
		WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT
	}
	SET rset = tStatement.%Execute(21,26)
	WRITE !,"Execute OK: SQLCODE=",rset.%SQLCODE,!!
	DO rset.%Display()
	WRITE !,"End of data: SQLCODE=",rset.%SQLCODE
}
DHC-APP>d ##class(PHA.TEST.SQL).PrepareClassQuery7()
 
Execute OK: SQLCODE=0
 
Name    DOB     Age
Van De Griek,Dick U.    1998-12-21      22
Peterson,Kirsten R.     1997-12-13      23
Van De Griek,Phil S.    1996-09-26      24
Wijnschenk,Lydia G.     1997-01-17      24
Xiang,Kirsten U.        1996-08-06      24
Schaefer,Usha G.        1995-09-16      25
Peterson,Sophia A.      1995-12-05      25
Petersburg,Bill O.      1995-10-23      25
 
8 Rows(s) Affected
End of data: SQLCODE=100

下面的ObjectScript示例执行相同的查询。 %Execute()方法形式参数列表使用可变长度数组(dynd ...)指定不确定数量的输入参数值;在这种情况下,为dynd数组的下标。 dynd变量设置为2以指示两个下标值。

/// d ##class(PHA.TEST.SQL).PrepareClassQuery8()
ClassMethod PrepareClassQuery8()
{
	SET tStatement = ##class(%SQL.Statement).%New(1)
	SET tStatement.%SchemaPath = "MyTests,Sample,Cinema"
	SET myquery=2
	SET myquery(1)="SELECT Name,DOB,Age FROM Person"
	SET myquery(2)="WHERE Age > ? AND Age < ? ORDER BY Age"
	SET dynd=2,dynd(1)=21,dynd(2)=26
	SET qStatus = tStatement.%Prepare(.myquery)
	IF qStatus'=1 {
		WRITE "%Prepare failed:" 
		DO $System.Status.DisplayError(qStatus) 
		QUIT
	}
	SET rset = tStatement.%Execute(dynd...)
	WRITE !,"Execute OK: SQLCODE=",rset.%SQLCODE,!!
	DO rset.%Display()
	WRITE !,"End of data: SQLCODE=",rset.%SQLCODE
}
DHC-APP>d ##class(PHA.TEST.SQL).PrepareClassQuery8()
 
Execute OK: SQLCODE=0
 
Name    DOB     Age
Van De Griek,Dick U.    1998-12-21      22
Peterson,Kirsten R.     1997-12-13      23
Van De Griek,Phil S.    1996-09-26      24
Wijnschenk,Lydia G.     1997-01-17      24
Xiang,Kirsten U.        1996-08-06      24
Schaefer,Usha G.        1995-09-16      25
Peterson,Sophia A.      1995-12-05      25
Petersburg,Bill O.      1995-10-23      25
 
8 Rows(s) Affected
End of data: SQLCODE=100

可以对准备好的结果集执行多个%Execute()操作。这使可以多次运行查询,并提供不同的输入参数值。不必在%Execute()操作之间关闭结果集,如以下示例所示:

/// d ##class(PHA.TEST.SQL).PrepareClassQuery9()
ClassMethod PrepareClassQuery9()
{
	SET myquery="SELECT Name,SSN,Age FROM Sample.Person WHERE Name %STARTSWITH ?"
	SET tStatement = ##class(%SQL.Statement).%New()
	SET qStatus = tStatement.%Prepare(myquery)
	IF qStatus'=1 {
		WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT
	}
	SET rset = tStatement.%Execute("A")
	DO rset.%Display()
	WRITE !,"End of A data",!!
	SET rset = tStatement.%Execute("B")
	DO rset.%Display()
	WRITE !,"End of B data"
}

DHC-APP>d ##class(PHA.TEST.SQL).PrepareClassQuery9()
Name    SSN     Age
Alton,Martin S. 624-25-8488     47
Ahmed,Elmo X.   950-40-6135     77
Anderson,Mario L.       604-10-9256     77
Adams,Diane F.  640-77-5933     9
Anderson,Valery N.      882-50-4971     27
Alton,Phil T.   785-37-8519     68
Adams,Susan E.  947-66-8684     52
 
7 Rows(s) Affected
End of A data
 
Name    SSN     Age
Bukowski,Mario V.       683-32-4214     85
Bachman,Susan O.        102-59-3932     88
Bush,Jules K.   547-97-7915     13
Basile,Filomena X.      888-66-1725     86
Browne,Robert X.        308-58-1444     82
Burroughs,Barbara H.    627-56-2213     86
Beatty,Molly Z. 794-64-5615     54
 
7 Rows(s) Affected
End of B data

使用TRY / CATCH处理%Execute错误

可以在TRY块结构内执行Dynamic SQL,将运行时错误传递给关联的CATCH块异常处理程序。对于%Execute()错误,可以使用%Exception.SQL类创建一个异常实例,然后将其扔到CATCH异常处理程序中。

下面的示例在发生%Execute()错误时创建一个SQL异常实例。在这种情况下,错误是数量之间的基数不匹配。输入参数(1)和%Execute()参数的数量(3)。它将%SQLCODE%Message属性值(作为CodeData)抛出到CATCH异常处理程序中。异常处理程序使用%IsA()实例方法测试异常类型,然后显示%Execute()错误:

/// d ##class(PHA.TEST.SQL).SQLTRY()
ClassMethod SQLTRY()
{
	TRY {
		SET myquery = "SELECT TOP ? Name,DOB FROM Sample.Person"
		SET tStatement = ##class(%SQL.Statement).%New()
		SET qStatus = tStatement.%Prepare(myquery)
		IF qStatus'=1 {
			WRITE "%Prepare failed:" 
			DO $System.Status.DisplayError(qStatus) QUIT
		}
		SET rset = tStatement.%Execute(7,9,4)
		IF rset.%SQLCODE=0 { 
			WRITE !,"Executed query",! 
		} ELSE { 
			SET badSQL=##class(%Exception.SQL).%New(,rset.%SQLCODE,,rset.%Message)
			THROW badSQL 
		}
		DO rset.%Display()
		WRITE !,"End of data"
		RETURN
	}
	CATCH exp { 
		WRITE "In the CATCH block",!
		IF 1=exp.%IsA("%Exception.SQL") {
		WRITE "SQLCODE: ",exp.Code,!
		WRITE "Message: ",exp.Data,! }
		ELSE { WRITE "Not an SQL exception",! }
		RETURN
	}
}
DHC-APP>d ##class(PHA.TEST.SQL).SQLTRY()
In the CATCH block
SQLCODE: -400
Message: Dynamic SQL Execute, more parameter values passed than are specified in the dynamic statement

%ExecDirect()

%SQL.Statement类提供%ExecDirect()类方法,该方法可以在单个操作中准备和执行查询。它可以准备指定的查询(如%Prepare())或现有的类查询(如%PrepareClassQuery())。

%ExecDirect()准备并执行指定的查询:

/// d ##class(PHA.TEST.SQL).ExecDirect()
ClassMethod ExecDirect()
{
	SET myquery=2
	SET myquery(1)="SELECT Name,Age FROM Sample.Person"
	SET myquery(2)="WHERE Age > 21 AND Age < 30 ORDER BY Age"
	SET rset = ##class(%SQL.Statement).%ExecDirect(,.myquery)
	IF rset.%SQLCODE=0 { 
		WRITE !,"ExecDirect OK",!! 
	} ELSE { 
		WRITE !,"ExecDirect SQLCODE=",rset.%SQLCODE,!,rset.%Message  QUIT
	}
	DO rset.%Display()
	WRITE !,"End of data: SQLCODE=",rset.%SQLCODE
}

DHC-APP> d ##class(PHA.TEST.SQL).ExecDirect()
 
ExecDirect OK
 
Name    Age
Van De Griek,Dick U.    22
Peterson,Kirsten R.     23
Van De Griek,Phil S.    24
Wijnschenk,Lydia G.     24
Xiang,Kirsten U.        24
Schaefer,Usha G.        25
Peterson,Sophia A.      25
Petersburg,Bill O.      25
Ng,Josephine Z. 26
Munt,Valery W.  26
Ingleman,Martin T.      26
Eno,Diane U.    26
Pascal,Kim P.   27
Ipsen,Jane A.   27
Anderson,Valery N.      27
Gomez,Mo Q.     27
Xerxes,Angelo P.        28
Young,Barbara N.        29
 
18 Rows(s) Affected
End of data: SQLCODE=100

%ExecDirect()准备并执行现有的类查询:

/// d ##class(PHA.TEST.SQL).ExecDirect1()
ClassMethod ExecDirect1()
{
	SET mycallq = "?=CALL Sample.PersonSets('A','NH')" 
	SET rset = ##class(%SQL.Statement).%ExecDirect(,mycallq)
	IF rset.%SQLCODE=0 { 
		WRITE !,"ExecDirect OK",!! 
	} ELSE { 
		WRITE !,"ExecDirect SQLCODE=",rset.%SQLCODE,!,rset.%Message  QUIT
	}
	DO rset.%Display()
	WRITE !,"End of data: SQLCODE=",rset.%SQLCODE
}

可以将输入参数值指定为%ExecDirect()类方法的第三个参数和后续参数,如以下示例所示:

/// d ##class(PHA.TEST.SQL).ExecDirect2()
ClassMethod ExecDirect2()
{
	SET myquery=2
	SET myquery(1)="SELECT Name,Age FROM Sample.Person"
	SET myquery(2)="WHERE Age > ? AND Age < ? ORDER BY Age"
	SET rset = ##class(%SQL.Statement).%ExecDirect(,.myquery,12,20)
	IF rset.%SQLCODE'=0 {
		WRITE !,"1st ExecDirect SQLCODE=",rset.%SQLCODE,!,rset.%Message  QUIT
	}
	DO rset.%Display()
	WRITE !,"End of teen data",!!
	SET rset2 = ##class(%SQL.Statement).%ExecDirect(,.myquery,19,30)
	IF rset2.%SQLCODE'=0 {
		WRITE !,"2nd ExecDirect SQLCODE=",rset2.%SQLCODE,!,rset2.%Message  QUIT
	}
	DO rset2.%Display()
	WRITE !,"End of twenties data"
}
DHC-APP> d ##class(PHA.TEST.SQL).ExecDirect2()
Name    Age
Bush,Jules K.   13
...
Eastman,Howard K.       18
 
9 Rows(s) Affected
End of teen data
 
Name    Age
Ingrahm,Susan N.        20
...
Young,Barbara N.        29
 
20 Rows(s) Affected
End of twenties data

%ExecDirect()输入参数对应于“?”的顺序字符出现在SQL语句中:第三个参数用于第一个“?”,第四个参数用于第二个“?”,依此类推。可以通过指定占位符逗号来省略参数值。如果%ExecDirect()输入参数少于相应的“?”输入参数,则使用默认值(如果存在)。

在下面的示例中,第一个%ExecDirect()指定所有三个“?”输入参数,第二个%ExecDirect()仅指定第二个输入参数,并省略第一个和第三个。它使用第三个输入参数的默认Sample.PersonSets()('MA')

/// d ##class(PHA.TEST.SQL).ExecDirect3()
ClassMethod ExecDirect3()
{
	SET mycall = "?=CALL Sample.PersonSets(?,?)"
	SET rset = ##class(%SQL.Statement).%ExecDirect(,mycall,"","A","NH")
	IF rset.%SQLCODE'=0 {WRITE !,"1st ExecDirect SQLCODE=",rset.%SQLCODE,!,rset.%Message  QUIT}
	DO rset.%Display()
	WRITE !,"End of A people data",!!
	SET rset2 = ##class(%SQL.Statement).%ExecDirect(,mycall,,"B")
	IF rset2.%SQLCODE'=0 {WRITE !,"2nd ExecDirect SQLCODE=",rset2.%SQLCODE,!,rset2.%Message  QUIT}
	DO rset2.%Display()
	WRITE !,"End of B people data"
}
DHC-APP>d ##class(PHA.TEST.SQL).ExecDirect3()
 
 
Output Values:
 
 0. 1
 
Dumping result #1
Name    DOB     Spouse
...
 
1 Rows(s) Affected
End of B people data

%ExecDirect()可以调用%SQL.Statement%Display()实例方法或%GetImplementationDetails()实例方法以返回当前准备好的语句的详细信息。因为%ExecDirect()可以准备并执行指定的查询或现有的类查询,所以可以使用%GetImplementationDetails()pStatementType参数来确定准备哪种查询:

/// d ##class(PHA.TEST.SQL).ExecDirect4()
ClassMethod ExecDirect4()
{
	SET mycall = "?=CALL Sample.PersonSets('A',?)"
	SET rset = ##class(%SQL.Statement).%ExecDirect(tStatement,mycall,,"NH")
	IF rset.%SQLCODE'=0 {
		WRITE !,"ExecDirect SQLCODE=",rset.%SQLCODE,!,rset.%Message  QUIT
	}
	SET bool = tStatement.%GetImplementationDetails(.pclassname,.ptext,.pargs,.pStatementType)
	IF bool=1 {
		IF pStatementType=1 {WRITE "Type= specified query",!
	} ELSEIF pStatementType=45 {
		WRITE "Type= existing class query",!
	}
	WRITE "Implementation class= ",pclassname,!
	WRITE "Statement text= ",ptext,!
	WRITE "Arguments= ",$LISTTOSTRING(pargs),!!  }
	ELSE {WRITE "%GetImplementationDetails() failed"}
	DO rset.%Display()
	WRITE !,"End of data"
}
0
0 188
文章 姚 鑫 · 三月 23, 2021 12m read

第十三章 使用动态SQL(一)

动态SQL简介

动态SQL是指在运行时准备并执行的SQL语句。在动态SQL中,准备和执行SQL命令是单独的操作。通过动态SQL,可以以类似于ODBC或JDBC应用程序的方式在InterSystems IRIS中进行编程(除了要在与数据库引擎相同的进程上下文中执行SQL语句)。动态SQL是从ObjectScript程序调用的。

动态SQL查询是在程序执行时准备的,而不是在编译时准备的。这意味着编译器无法在编译时检查错误,并且不能在Dynamic SQL中使用预处理器宏。这也意味着执行程序可以响应用户或其他输入而创建专门的Dynamic SQL查询。

动态SQL可用于执行SQL查询。它也可以用于发出其他SQL语句。本章中的示例执行SELECT查询。

动态SQL用于执行InterSystems IRIS SQL Shell,InterSystems IRIS管理门户网站“执行查询”界面,SQL代码导入方法以及“数据导入和导出实用程序”。

在Dynamic SQL(和使用它的应用程序)中,行的最大大小为3,641,144个字符。

动态SQL与嵌入式SQL

动态SQL与嵌入式SQL在以下方面有所不同:

  • 动态SQL查询的初始执行效率比嵌入式SQL稍低,因为它不会生成查询的内联代码。但是,动态SQL和嵌入式SQL的重新执行比第一次执行查询要快得多,因为它们都支持缓存的查询。
  • 动态SQL可以通过两种方式接受输入到查询的文字值:使用“?”指定的输入参数。字符和输入主机变量(例如:var)。嵌入式SQL使用输入和输出主机变量(例如:var)。
  • 使用结果集对象(即Data属性)的API检索动态SQL输出值。嵌入式SQL将主机变量(例如:var)与SELECT语句的INTO子句一起使用以输出值。
  • 动态SQL设置%SQLCODE%Message%ROWCOUNT%ROWID对象属性。嵌入式SQL设置相应的SQLCODE%msg%ROWCOUNT%ROWID局部变量。动态SQL不会为SELECT查询设置%ROWID;嵌入式SQL为基于游标的SELECT查询设置%ROWID
  • 动态SQL提供了一种简单的方法来查找查询元数据(例如列的数量和名称)。
  • 动态SQL执行SQL特权检查;必须具有适当的权限才能访问或修改表,字段等。Embedded SQL不执行SQL特权检查。
  • 动态SQL无法访问私有类方法。要访问现有的类方法,必须将该方法公开。这是一般的SQL限制。但是,嵌入式SQL克服了此限制,因为嵌入式SQL操作本身是同一类的方法。

动态SQL和嵌入式SQL使用相同的数据表示形式(默认情况下为逻辑模式,但是可以更改)和NULL处理。

%SQL.Statement

动态SQL的首选接口是%SQL.Statement类。要准备和执行动态SQL语句,请使用%SQL.Statement的实例。执行动态SQL语句的结果是一个SQL语句结果对象,该对象是%SQL.StatementResult类的实例。 SQL语句结果对象可以是单一值,结果集或上下文对象。在所有情况下,结果对象都支持标准接口。每个结果对象都会初始化%SQLCODE%Message和其他结果对象属性;这些属性设置的值取决于发出的SQL语句。对于成功执行的SELECT语句,对象是结果集(特别是%SQL.StatementResult的实例),并且支持预期的结果集功能。

以下ObjectScript代码准备并执行动态SQL查询:

/// d ##class(PHA.TEST.SQL).DynamicSQL()
ClassMethod DynamicSQL()
{
	/* 简单的%SQL.Statement示例 */
	SET myquery = "SELECT TOP 5 Name,DOB FROM Sample.Person"
	SET tStatement = ##class(%SQL.Statement).%New()
	SET qStatus = tStatement.%Prepare(myquery)
	IF qStatus'=1 {
		WRITE "%Prepare 失败" 
		DO $System.Status.DisplayError(qStatus) 
		QUIT
	}
	SET rset = tStatement.%Execute()
	DO rset.%Display()
	WRITE !,"End of data"
}
DHC-APP>d ##class(PHA.TEST.SQL).DynamicSQL()
Name    DOB
yaoxin  54536
xiaoli
姚鑫    63189
姚鑫    63189
姚鑫    50066
 
5 Rows(s) Affected
End of data

本章中的示例使用与%SQL.Statement%SQL.StatementResult类关联的方法。

创建一个对象实例

可以使用%New()类方法创建%SQL.Statement类的实例:

SET tStatement = ##class(%SQL.Statement).%New()

此时,结果集对象已准备好准备SQL语句。创建%SQL.Statement类的实例后,可以使用该实例发出多个动态SQL查询和/或INSERTUPDATEDELETE操作。

%New()按以下顺序接受三个可选的逗号分隔参数:

  1. %SelectMode,它指定用于数据输入和数据显示的模式。
  2. %SchemaPath,它指定用于为无限定的表名提供架构名称的搜索路径。
  3. %Dialect,它指定Transact-SQL(TSQL)Sybase或MSSQL方言。默认值为IRIS(InterSystems SQL)。

还有一个%ObjectSelectMode属性,不能将其设置为%New()参数。 %ObjectSelectMode指定字段到其相关对象属性的数据类型绑定。

在下面的ObjectScript示例中,%SelectMode为2(显示模式),%SchemaPath“Sample”指定为默认架构:

  SET tStatement = ##class(%SQL.Statement).%New(2,"Sample")

在下面的ObjectScript示例中,未指定%SelectMode(请注意占位符逗号),并且%SchemaPath指定包含三个架构名称的架构搜索路径:

  SET tStatement = ##class(%SQL.Statement).%New(,"MyTests,Sample,Cinema")

%SelectMode属性

%SelectMode属性指定以下模式之一:0 =Logical逻辑(默认)1 = ODBC2 =Display.显示。这些模式指定如何输入和显示数据值。模式最常用于日期和时间值以及显示%List数据(包含编码列表的字符串)。数据以逻辑模式存储。

SELECT查询使用%SelectMode值确定用于显示数据的格式。

INSERTUPDATE操作使用%SelectMode值来确定允许的数据输入格式。

%SelectMode用于数据显示。 SQL语句在内部以逻辑模式运行。例如,无论%SelectMode设置如何,ORDER BY子句均根据记录的逻辑值对记录进行排序。 SQL函数使用逻辑值,而不管%SelectMode设置如何。映射为SQLPROC的方法也可以在逻辑模式下运行。在SQL语句中称为函数的SQL例程需要以逻辑格式返回函数值。

  • 对于SELECT查询,%SelectMode指定用于显示数据的格式。将%SelectMode设置为ODBC或Display也会影响用于指定比较谓词值的数据格式。某些谓词值必须以%SelectMode格式指定,而其他谓词值必须以逻辑格式指定,而与%SelectMode无关。
    • %SelectMode = 1(ODBC)中的时间数据类型数据可以显示小数秒,这与实际的ODBC时间不同。 InterSystems IRIS Time数据类型支持小数秒。相应的ODBC TIME数据类型(TIME_STRUCT标准标头定义)不支持小数秒。 ODBC TIME数据类型将提供的时间值截断为整秒。 ADO DotNet和JDBC没有此限制。
    • %SelectMode = 0(逻辑)中的%List数据类型数据不会显示内部存储值,因为%List数据是使用非打印字符编码的。而是,Dynamic SQL将%List数据值显示为$LISTBUILD语句,例如:$lb("White","Green")%SelectMode = 1(ODBC)中的%List数据类型数据显示用逗号分隔的列表元素;此元素分隔符指定为CollectionOdbcDelimiter参数。 %SelectMode = 2中的%List数据类型数据(显示)显示由$ CHAR(10,13)分隔的列表元素(换行,回车);此元素分隔符指定为CollectionDisplayDelimiter参数。
  • 对于INSERTUPDATE操作,%SelectMode指定将转换为逻辑存储格式的输入数据的格式。为了进行此数据转换,必须使用RUNTIME(默认)的选择模式编译SQL代码,以便在执行INSERTUPDATE时使用DisplayODBC %SelectMode。有关日期和时间的允许输入值,请参考日期和时间数据类型。

可以将%SelectMode指定为%New()类方法的第一个参数,或直接对其进行设置,如以下两个示例所示:

  SET tStatement = ##class(%SQL.Statement).%New(2)
  SET tStatement = ##class(%SQL.Statement).%New()
  SET tStatement.%SelectMode=2

下面的示例返回%SelectMode的当前值:


/// d ##class(PHA.TEST.SQL).DynamicSQL1()
ClassMethod DynamicSQL1()
{
	SET tStatement = ##class(%SQL.Statement).%New()
	WRITE !,"默认选择模式=",tStatement.%SelectMode
	SET tStatement.%SelectMode=2
	WRITE !,"设置选择模式=",tStatement.%SelectMode
}
DHC-APP>d ##class(PHA.TEST.SQL).DynamicSQL1()
 
默认选择模式=0
设置选择模式=2

可以使用$SYSTEM.SQL.Util.GetOption("SelectMode") 方法为当前进程确定SelectMode默认设置。当n可以为0 =逻辑1 = ODBC2 = Display时,可以使用$SYSTEM.SQL.Util.SetOption("SelectMode",n) 方法来更改当前进程的SelectMode默认设置。设置%SelectMode会覆盖当前对象实例的默认设置。它不会更改SelectMode进程的默认值。

%SchemaPath属性

%SchemaPath属性指定用于为非限定的表名,视图名或存储过程名提供架构名的搜索路径。模式搜索路径用于数据管理操作,例如SELECTCALLINSERTTRUNCATE TABLE;数据定义操作(例如DROP TABLE)将忽略它。

搜索路径被指定为带引号的字符串,其中包含模式名称或逗号分隔的一系列模式名称。 InterSystems IRIS以从左到右的顺序搜索列出的模式。 InterSystems IRIS会搜索每个指定的架构,直到找到第一个匹配的表,视图或存储过程名称。因为模式是按指定顺序搜索的,所以不会检测到歧义的表名。仅搜索当前名称空间中的架构名称。

模式搜索路径可以包含文字模式名称以及CURRENT_PATHCURRENT_SCHEMADEFAULT_SCHEMA关键字。

  • CURRENT_PATH指定当前模式搜索路径,如先前的%SchemaPath属性中所定义。这通常用于将架构添加到现有架构搜索路径的开头或结尾。
  • 如果%SQL.Statement调用是从类方法中进行的,则CURRENT_SCHEMA指定当前模式容器的类名称。如果在类方法中定义了#SQLCompile Path宏指令,则CURRENT_SCHEMA是映射到当前类包的架构。否则,CURRENT_SCHEMADEFAULT_SCHEMA相同。
  • DEFAULT_SCHEMA指定系统范围的默认架构。使用此关键字,可以在搜索其他列出的架构之前,在架构搜索路径中将系统范围的默认架构作为一个项目进行搜索。如果已经搜索了路径中指定的所有模式而没有匹配项,则在搜索模式搜索路径后始终会搜索系统范围内的默认模式。

%SchemaPath是InterSystems IRIS在架构中搜索匹配表名的第一位。如果未指定%SchemaPath,或者未列出包含匹配表名的架构,则InterSystems IRIS将使用系统范围的默认架构。

可以通过指定%SchemaPath属性或指定%New()类方法的第二个参数来指定模式搜索路径,如以下两个示例所示:

  SET path="MyTests,Sample,Cinema"
  SET tStatement = ##class(%SQL.Statement).%New(,path)
  SET tStatement = ##class(%SQL.Statement).%New()
  SET tStatement.%SchemaPath="MyTests,Sample,Cinema"

可以在使用它的%Prepare()方法之前的任何位置设置%SchemaPath

下面的示例返回%SchemaPath的当前值:

/// d ##class(PHA.TEST.SQL).DynamicSQL2()
ClassMethod DynamicSQL2()
{
	SET tStatement = ##class(%SQL.Statement).%New()
	WRITE !,"默认 path=",tStatement.%SchemaPath
	SET tStatement.%SchemaPath="MyTests,Sample,Cinema"
	WRITE !,"设置 path=",tStatement.%SchemaPath
}

DHC-APP>d ##class(PHA.TEST.SQL).DynamicSQL2()
 
默认 path=
设置 path=MyTests,Sample,Cinema

可以使用%ClassPath()方法将%SchemaPath设置为为指定的类名定义的搜索路径:

/// d ##class(PHA.TEST.SQL).DynamicSQL3()
ClassMethod DynamicSQL3()
{
	SET tStatement = ##class(%SQL.Statement).%New()
	SET tStatement.%SchemaPath=tStatement.%ClassPath("Sample.Person")
	WRITE tStatement.%SchemaPath
}
DHC-APP>d ##class(PHA.TEST.SQL).DynamicSQL3()
Sample

%Dialect属性

%Dialect属性指定SQL语句方言。可以指定Sybase,MSSQL或IRIS(InterSystems SQL)。 Sybase或MSSQL设置导致使用指定的Transact-SQL方言处理SQL语句。

Sybase和MSSQL方言在这些方言中支持SQL语句的有限子集。它们支持SELECTINSERTUPDATEDELETEEXECUTE语句。他们支持CREATE TABLE语句用于永久表,但不支持临时表。支持创建视图。支持CREATE TRIGGERDROP TRIGGER。但是,如果CREATE TRIGGER语句部分成功,但是在类编译时失败,则此实现不支持事务回滚。支持CREATE PROCEDURECREATE FUNCTION

Sybase和MSSQL方言支持IF控制流语句。 IRIS(InterSystems SQL)方言不支持此命令。

默认值为InterSystems SQL,由空字符串(“”)表示,或指定为“ IRIS”

可以将%Dialect指定为%New()类方法的第三个参数,或者将其直接设置为属性,或者使用方法进行设置,如以下三个示例所示:

%New()类方法中设置%Dialect

/// d ##class(PHA.TEST.SQL).DynamicSQL4()
ClassMethod DynamicSQL4()
{
	SET tStatement = ##class(%SQL.Statement).%New(,,"Sybase")
	WRITE "语言模式设置为=",tStatement.%Dialect
}
DHC-APP>d ##class(PHA.TEST.SQL).DynamicSQL4()
语言模式设置为=Sybase

直接设置%Dialect属性:

/// d ##class(PHA.TEST.SQL).DynamicSQL5()
ClassMethod DynamicSQL5()
{
	SET tStatement = ##class(%SQL.Statement).%New()
	SET defaultdialect=tStatement.%Dialect
	WRITE "默认语言模式=",defaultdialect,!
	SET tStatement.%Dialect="Sybase"
	WRITE "语言模式设置为=",tStatement.%Dialect,!
	SET tStatement.%Dialect="IRIS"
	WRITE "语言模式重置为默认=",tStatement.%Dialect,!
}
DHC-APP>d ##class(PHA.TEST.SQL).DynamicSQL5()
默认语言模式=
语言模式设置为=Sybase
语言模式重置为默认=iris

使用%DialectSet()实例方法设置%Dialect属性,该方法将返回错误状态:

/// d ##class(PHA.TEST.SQL).DynamicSQL6()
ClassMethod DynamicSQL6()
{
	SET tStatement = ##class(%SQL.Statement).%New()
	SET tStatus = tStatement.%DialectSet("Sybase")
	IF tStatus'=1 {
		WRITE "%DialectSet 失败:" 
		DO $System.Status.DisplayError(tStatus) QUIT
	}
	WRITE "语言模式设置为=",tStatement.%Dialect
}
DHC-APP>d ##class(PHA.TEST.SQL).DynamicSQL6()
语言模式设置为=Sybase

%DialectSet()方法返回%Status值:成功返回状态1。失败返回以0开头的对象表达式,后跟编码错误信息。因此,无法执行tStatus = 0测试是否失败;您可以执行$$ISOK(tStatus)= 0宏测试以检查失败

%ObjectSelectMode属性

%ObjectSelectMode属性是一个布尔值。如果%ObjectSelectMode = 0(默认),则SELECT列表中的所有列都将绑定到结果集中具有文字类型的属性。如果%ObjectSelectMode = 1,则SELECT列表中的列将绑定到具有关联属性定义中定义的类型的属性。

%ObjectSelectMode允许指定如何在从SELECT语句生成的结果集类中定义类型类为swizzleable类的列。如果%ObjectSelectMode = 0,则将在结果集中将与swizzleable列相对应的属性定义为与SQL表的RowID类型相对应的简单文字类型。如果%ObjectSelectMode = 1,则将使用列的声明类型定义属性。这意味着访问结果集属性将触发 swizzling。

无法将%ObjectSelectMode设置为%New()的参数。

下面的示例返回%ObjectSelectMode默认值,设置%ObjectSelectMode,然后返回新的%ObjectSelectMode值:

/// d ##class(PHA.TEST.SQL).DynamicSQL7()
ClassMethod DynamicSQL7()
{
	SET myquery = "SELECT TOP 5 %ID AS MyID,Name,Age FROM Sample.Person"
	SET tStatement = ##class(%SQL.Statement).%New()
	WRITE !,"默认 ObjectSelectMode=",tStatement.%ObjectSelectMode
	SET tStatement.%ObjectSelectMode=1
	WRITE !,"语言 ObjectSelectMode=",tStatement.%ObjectSelectMode
}
DHC-APP>d ##class(PHA.TEST.SQL).DynamicSQL7()
 
默认 ObjectSelectMode=0
语言 ObjectSelectMode=1

当使用字段名称属性从结果集中返回值时,主要使用%ObjectSelectMode = 1。本章“从结果集中返回特定值”部分的字段名属性中的示例对此进行了进一步说明。

SELECT列表中的字段链接到集合属性时,可以使用%ObjectSelectMode = 1%ObjectSelectMode将使集合swizzle。如果%SelectMode = 1或2,则系统在转换前将收集序列值转换为逻辑模式形式。生成的oref支持完整的收集接口。

0
0 596
文章 姚 鑫 · 三月 22, 2021 1m read

第十二章 使用嵌入式SQL(六)

持久类方法中的嵌入式SQL

下面的示例显示了一个持久类,其中包含一个类方法和一个实例方法,两者都包含嵌入式SQL:

Class Sample.MyClass Extends %Persistent [DdlAllowed]
 { 
 ClassMethod NameInitial(Myval As %String) As %String [SqlProc]
  {
     &sql(SELECT Name INTO :n FROM Sample.Stuff WHERE Name %STARTSWITH :Myval)
        IF SQLCODE<0 {WRITE "SQLCODE error ",SQLCODE  RETURN %msg}
        ELSEIF SQLCODE=100 {WRITE "Query returns no results"  RETURN}
   WRITE "Hello "  RETURN n
  }
 Method CountRows() As %Integer
  {
   &sql(SELECT COUNT(*) INTO :count FROM Sample.Stuff)
        IF SQLCODE<0 {WRITE "SQLCODE error ",SQLCODE  RETURN %msg}
        ELSEIF SQLCODE=100 {WRITE "Query returns no results"  RETURN}
   WRITE "Number of rows is "  RETURN count
  }
 }

类方法的调用如下:

 WRITE ##class(Sample.MyClass).NameInitial("G")

实例方法的调用如下:

  SET x=##class(Sample.MyClass).%New()
  WRITE x.CountRows()

要成功编译这些方法,不必存在诸如表和字段之类的SQL实体。由于检查SQL实体是否存在是在运行时执行的,因此嵌入式SQL方法应包含SQLCODE测试逻辑。

以测试嵌入式SQL中指定的SQL实体是否存在,而无需执行代码。验证嵌入式SQL代码中对此进行了描述。

验证嵌入式SQL代码

可以通过两种方式验证嵌入式SQL代码而无需执行代码:

  • 使用/compileembedded = 1限定符编译包含嵌入式SQL代码的例程。
  • 使用$SYSTEM.OBJ.GenerateEmbedded()方法编译多个嵌入式SQL例程。
  • 使用管理门户网站SQL界面的“显示计划”选项测试嵌入式SQL代码。

使用/compileembedded限定符进行编译

可以通过使用$SYSTEM.OBJ类的编译类方法并在qspec参数中指定/ compileembedded = 1限定符来验证嵌入式SQL代码。

  • $SYSTEM.OBJ.Compile()编译指定的类以及该类中的所有例程。
  • $SYSTEM.OBJ.CompileList()编译指定类的列表以及这些类中的所有例程。
  • $SYSTEM.OBJ.CompilePackage()编译指定包(架构)中的所有类/例程。
  • $SYSTEM.OBJ.CompileAll()编译当前名称空间中的所有类/例程。
  • $SYSTEM.OBJ.CompileAllNamespaces()编译所有命名空间中的所有类/例程。

要显示qspec限定词(包括/compileembedded)的列表,请调用:

  DO $SYSTEM.OBJ.ShowQualifiers()

使用Show Plan进行测试

可以使用Management Portal SQL界面来验证嵌入式SQL代码,而无需执行该代码。此操作既可以验证SQL语法,也可以检查指定的SQL实体是否存在。

从Management Portal System Explorer选项中,选择SQL选项以显示Execute Query code区域。

  1. 输入嵌入式SQL查询。例如,SELECT Name INTO:n from Sample.MyTest或DECLARE MyCursor CURSOR FOR SELECT Name,Age INTO:n,:a FROM Sample.MyTest,age> 21,仅供只读。
  2. 按下显示计划按钮以检查代码。如果代码有效,则“显示计划”将显示一个查询计划。如果代码无效,则“显示计划”将显示SQLCODE错误值和消息。

image

请注意,如果缺少INTO子句,Show Plan验证将不会发出错误,因为可以在FETCH语句中指定INTO子句。如果INTO子句包含错误或位于错误的位置,则Show Plan将发出适当的错误。

不能使用“执行”按钮执行嵌入式SQL代码。

审核嵌入式SQL

InterSystems IRIS支持对嵌入式SQL语句的可选审核。满足以下两个要求时,将执行嵌入式SQL审核:

  1. %System /%SQL / EmbeddedStatement系统审核事件在系统范围内启用。默认情况下,未启用此系统审核事件。要启用,请依次转到管理门户,系统管理,安全性,审核,然后配置系统事件。
  2. 包含嵌入式SQL语句的例程必须包含#SQLCompile Audit宏预处理程序指令。如果此伪指令设置为ON,则在执行时将审核编译例程中跟在其后的任何嵌入式SQL语句。

审核将信息记录在审核数据库中。要查看审核数据库,请依次转到管理门户,系统管理,选择安全性,审核,然后查看审核数据库。可以将“事件名称”过滤器设置为Embedded Statement,以将“查看审核数据库”限制为“嵌入式SQL”语句。审核数据库列出了时间(本地时间戳记),用户,PID(进程ID)和描述,它们指定了嵌入式SQL语句的类型。例如,SQL SELECT语句。

image

通过选择事件的详细信息链接,可以列出其他信息,包括事件数据。事件数据包括执行的SQL语句和该语句的任何输入参数的值。例如:

SELECT TOP :n Name,ColorPreference INTO :name,:color FROM Sample.Stuff WHERE Name %STARTSWITH :letter 
Parameter values: 
n=5 
letter="F"

InterSystems IRIS还支持对动态SQL语句(事件名称= DynamicStatement)以及ODBC和JDBC语句(事件名称= XDBCStatement)进行审核。

0
0 192
文章 姚 鑫 · 三月 21, 2021 9m read

第十二章 使用嵌入式SQL(五)

嵌入式SQL变量

以下局部变量在嵌入式SQL中具有特殊用途。这些局部变量名称区分大小写。在过程启动时,这些变量是不确定的。它们由嵌入式SQL操作设置。也可以使用SET命令直接设置它们,或使用NEW命令将其重置为未定义。像任何局部变量一样,值将在过程持续期间或直到设置为另一个值或使用NEW进行定义之前一直存在。例如,某些成功的嵌入式SQL操作未设置%ROWID。执行这些操作后,%ROWID是未定义的或保持设置为其先前值。

  • %msg
  • %ROWCOUNT
  • %ROWID
  • SQLCODE

这些局部变量不是由Dynamic SQL设置的。 (请注意,SQL Shell和Management Portal SQL接口执行Dynamic SQL。)相反,Dynamic SQL设置相应的对象属性。

在嵌入式SQL中使用以下ObjectScript特殊变量。这些特殊的变量名称不区分大小写。在过程启动时,这些变量将初始化为一个值。它们由嵌入式SQL操作设置。不能使用SET或NEW命令直接设置它们。

  • $TLEVEL
  • $USERNAME

作为已定义的InterSystems IRIS嵌入式SQL接口的一部分,InterSystems IRIS可以在嵌入式SQL处理期间设置任何这些变量。

如果嵌入式SQL在类方法中(procedureBlock = ON),则系统会自动将所有这些变量放在PublicList中,并自动将SQLCODE%ROWID%ROWCOUNT%msg以及SQL语句。可以通过引用方法来传递这些变量;通过引用传递的变量将不会在类方法过程块中自动更新。

如果嵌入式SQL在例程中,则程序员有责任在调用嵌入式SQL之前新建%msg%ROWCOUNT%ROWIDSQLCODE变量。更新这些变量可防止干扰这些变量的先前设置。为避免<FRAMESTACK>错误,不应在迭代周期内执行此NEW操作。

%msg

包含系统提供的错误消息字符串的变量。如果InterSystems SQL将SQLCODE设置为负整数(表示错误),则仅设置%msg。如果SQLCODE设置为0100,则%msg变量与其先前值保持不变。

此行为不同于相应的Dynamic SQL %Message属性,当没有当前错误时,该属性将设置为空字符串。

在某些情况下,特定的SQLCODE错误代码可能与一个以上的%msg字符串相关联,描述了生成SQLCODE的不同条件。 %msg还可以接受用户定义的消息字符串。当触发器代码显式设置%ok = 0来中止触发器时,这最常用于从触发器发出用户定义的消息。

当执行SQL代码时,将使用有效的NLS语言生成错误消息字符串。可以在不同的NLS语言环境中编译SQL代码。该消息将根据运行时NLS环境生成。请参见$ SYS.NLS.Locale.Language

%ROWCOUNT

一个整数计数器,指示受特定语句影响的行数。

  • INSERTUPDATEINSERT OR UPDATEDELETE%ROWCOUNT设置为受影响的行数。带有显式值的INSERT命令只能影响一行,因此将%ROWCOUNT设置为01INSERT查询结果,UPDATEDELETE可以影响多行,因此可以将%ROWCOUNT设置为0或正数。整数。
  • 无论删除多少行还是删除任何行,TRUNCATE TABLE始终将%ROWCOUNT设置为–1。因此,要确定实际删除的行数,请在TRUNCATE TABLE之前对表执行COUNT(*),或者使用DELETE而不是TRUNCATE TABLE删除表中的所有行。
  • 没有声明游标的SELECT只能作用于一行,因此执行简单的SELECT总是会将%ROWCOUNT设置为1(与检索到的选择标准匹配的单行)或0(没有与选择标准匹配的行)。
  • DECLARE游标名CURSOR FOR SELECT不会初始化%ROWCOUNTSELECT之后,%ROWCOUNT不变,而OPEN游标名之后,%ROWCOUNT不变。第一个成功的FETCH设置%ROWCOUNT。如果没有行符合查询选择条件,则FETCH设置%ROWCOUNT = 0;否则,设置%ROWCOUNT = 0。如果FETCH检索与查询选择条件匹配的行,则它将设置%ROWCOUNT = 1。随后的每个获取行的FETCH都将递增%ROWCOUNTCLOSE时或FETCH发出SQLCODE 100(无数据或无更多数据)时,%ROWCOUNT包含已检索的总行数。

SELECT行为与相应的Dynamic SQL%ROWCOUNT属性不同,该属性在查询执行完成时设置为0,并且仅在程序迭代查询返回的结果集时才递增。

如果SELECT查询仅返回聚合函数,则每个FETCH都将设置%ROWCOUNT = 1。即使表中没有数据,第一个FETCH始终以SQLCODE = 0来完成;任何后续的FETCH均以SQLCODE = 100完成,并设置%ROWCOUNT = 1

以下嵌入式SQL示例声明一个游标,并使用FETCH来获取表中的每一行。到达数据结尾(SQLCODE = 100)时,%ROWCOUNT包含已检索的行数:

/// d ##class(PHA.TEST.SQL).ROWCOUNT()
ClassMethod ROWCOUNT()
{
	SET name="LastName,FirstName",state="##"
	&sql(DECLARE EmpCursor CURSOR FOR 
		SELECT Name, Home_State
		INTO :name,:state FROM Sample.Person
		WHERE Home_State %STARTSWITH 'M')
	WRITE !,"BEFORE: Name=",name," State=",state
	&sql(OPEN EmpCursor)
	QUIT:(SQLCODE'=0)
	FOR { 
		&sql(FETCH EmpCursor)
		QUIT:SQLCODE  
		WRITE !,"Row fetch count: ",%ROWCOUNT
		WRITE " Name=",name," State=",state
	}
	WRITE !,"最终提取SQLCODE: ",SQLCODE
	&sql(CLOSE EmpCursor)
	WRITE !,"AFTER: Name=",name," State=",state
	WRITE !,"提取的总行数: ",%ROWCOUNT
}
DHC-APP>d ##class(PHA.TEST.SQL).ROWCOUNT()
 
BEFORE: Name=LastName,FirstName State=##
Row fetch count: 1 Name=O'Rielly,Chris H. State=MS
Row fetch count: 2 Name=Orwell,John V. State=MT
Row fetch count: 3 Name=Zevon,Heloisa O. State=MI
...
Row fetch count: 37 Name=Joyce,Elmo R. State=MO
Row fetch count: 38 Name=Jafari,Christine Z. State=MI
最终提取SQLCODE: 100
AFTER: Name=Jafari,Christine Z. State=OH
提取的总行数: 38

以下嵌入式SQL示例执行UPDATE并设置受更改影响的行数:

/// d ##class(PHA.TEST.SQL).ROWCOUNT1()
ClassMethod ROWCOUNT1()
{
	&sql(UPDATE Sample.Employee 
		SET Salary = (Salary * 1.1)
		WHERE Salary < 50000)
	IF SQLCODE<0 {
		WRITE "SQLCODE error ",SQLCODE," ",%msg  QUIT
		}
	WRITE "Employees: ", %ROWCOUNT,!
}
DHC-APP>d ##class(PHA.TEST.SQL).ROWCOUNT1()
Employees: 48

请记住,所有嵌入式SQL语句(在给定进程内)都会修改%ROWCOUNT变量。如需要%ROWCOUNT提供的值,请确保在执行其他Embedded SQL语句之前获取其值。根据嵌入式SQL的调用方式,可能必须在输入嵌入式SQL之前新建%ROWCOUNT变量。

另请注意,显式回滚事务不会影响%ROWCOUNT的值。例如,以下内容将报告已进行了更改,即使它们已经滚动了。

/// d ##class(PHA.TEST.SQL).ROWCOUNT2()
ClassMethod ROWCOUNT2()
{
	TSTART // 开始事务
	NEW SQLCODE,%ROWCOUNT,%ROWID
	&sql(UPDATE Sample.Employee 
		SET Salary = (Salary * 1.1)
		WHERE Salary < 50000)
	IF SQLCODE<0 {
		WRITE "SQLCODE error ",SQLCODE," ",%msg  QUIT
	}
	TROLLBACK // 强制回滚;不会修改%rowcount
	Write "Employees: ", %ROWCOUNT,!
}

DHC-APP>d ##class(PHA.TEST.SQL).ROWCOUNT2()
Employees: 37

隐式事务(例如,如果UPDATE未通过约束检查)由%ROWCOUNT反映。

%ROWID

初始化进程时,未定义%ROWID。当发出NEW %ROWID命令时,%ROWID将重置为未定义。 %ROWID由下面描述的嵌入式SQL操作设置。如果该操作不成功或成功完成,但未获取或修改任何行,则%ROWID值与其先前值保持不变:未定义,或由先前的嵌入式SQL操作设置为某个值。因此,在每个嵌入式SQL操作之前,请务必新建%ROWID

%ROWID设置为受以下操作影响的最后一行的RowID

  • INSERTUPDATEINSERT OR UPDATEDELETE:单行操作后,%ROWID变量包含系统分配的RowID(对象ID)值,该值分配给插入,更新或删除的记录。经过多行操作之后,%ROWID变量包含系统分配的最后一条插入,更新或删除的记录的RowID(对象ID)的值。如果未插入,更新或删除任何记录,则%ROWID变量值将保持不变。 TRUNCATE TABLE没有设置%ROWID
  • 基于游标的SELECT:DECLARE游标名称CURSOROPEN游标名称语句未初始化%ROWID%ROWID值与其先前值保持不变。第一个成功的FETCH设置%ROWID。随后的每个获取行的FETCH都会将%ROWID重置为当前RowID值。如果FETCH检索一行可更新游标,则会设置%ROWID。可更新游标是其中顶部FROM子句仅包含一个元素(单个表名或可更新视图名)的游标。如果游标不可更新,则%ROWID保持不变。如果没有行符合查询选择条件,则FETCH不会更改先前的%ROWID值(如果有)。 CLOSE时或FETCH发出SQLCODE 100(无数据或无更多数据)时,%ROWID包含检索到的最后一行的RowID

具有DISTINCT关键字或GROUP BY子句的基于游标的SELECT不会设置%ROWID%ROWID值与其先前的值(如果有)保持不变。

如果基于游标的SELECT仅返回聚合函数值,则不会设置%ROWID。如果它同时返回字段值和聚合函数值,则将每个FETCH%ROWID值设置为查询返回的最后一行的RowID

  • 没有声明游标的SELECT不会设置%ROWID。完成简单的SELECT语句后,%ROWID值将保持不变。

在Dynamic SQL中,相应的%ROWID属性返回插入,更新或删除的最后一条记录的RowID值。执行SELECT查询时,Dynamic SQL不会返回%ROWID属性值。

可以使用以下方法调用从ObjectScript中检索当前的%ROWID

DHC-APP>  WRITE $SYSTEM.SQL.GetROWID()
213

在执行INSERTUPDATEDELETETRUNCATE TABLE或基于游标的SELECT操作之后,LAST_IDENTITY SQL函数将为最近修改的记录返回IDENTITY字段的值。如果表没有IDENTITY字段,则此函数返回最近修改记录的RowID

SQLCODE

运行嵌入式SQL查询后,必须在处理输出主机变量之前检查SQLCODE

如果SQLCODE = 0,则查询成功完成并返回数据。输出主机变量包含字段值。 如果SQLCODE = 100,则查询成功完成,但是输出主机变量值可能不同。任何一个:

  • 查询返回一个或多个数据行(SQLCODE = 0),然后到达数据的末尾(SQLCODE = 100),在这种情况下,输出主机变量设置为返回的最后一行的字段值。 %ROWCOUNT> 0
  • 查询未返回任何数据,在这种情况下,输出主机变量未定义。 %ROWCOUNT = 0

如果查询仅返回聚合函数,则即使表中没有数据,第一个FETCH也会始终以SQLCODE = 0%ROWCOUNT = 1来完成。第二个FETCHSQLCODE = 100%ROWCOUNT = 1结束。如果表中没有数据或没有数据与查询条件匹配,查询将根据需要将输出主机变量设置为0或空字符串。

如果SQLCODE为负数,则查询失败,并显示错误条件。

根据嵌入式SQL的调用方式,可能必须在输入嵌入式SQL之前新建SQLCODE变量。在触发代码中,将SQLCODE设置为非零值会自动将%ok = 0设置为中止并回滚触发操作。

在动态SQL中,相应的%SQLCODE属性返回SQL错误代码值。

$TLEVEL

事务级计数器。 InterSystems SQL将$TLEVEL初始化为0。 如果没有当前事务,$TLEVEL为0。

  • 初始START TRANSACTION$LEVEL设置为1。其他START TRANSACTION语句对$TLEVEL无效。
  • 每个SAVEPOINT语句将$TLEVEL加1。
  • ROLLBACK TO SAVEPOINT点名语句减少$TLEVEL。递减量取决于指定的保存点。
  • COMMIT$LEVEL重置为0。
  • ROLLBACK$LEVEL重置为0。

还可以使用%INTRANSACTION语句来确定事务是否在进行中。

$TLEVEL也由ObjectScript事务命令设置。

$USERNAME

SQL用户名与InterSystems IRIS用户名相同,存储在ObjectScript $USERNAME特殊变量中。用户名可以用作系统范围的默认架构,也可以用作架构搜索路径中的元素。

0
0 177
文章 姚 鑫 · 三月 20, 2021 5m read

第十二章 使用嵌入式SQL(四)

SQL游标

游标是指向数据的指针,该数据允许嵌入式SQL程序对所指向的记录执行操作。通过使用游标,Embedded SQL可以遍历结果集。嵌入式SQL可以使用游标执行查询,该查询从多个记录返回数据。嵌入式SQL还可以使用游标更新或删除多个记录。

必须首先对SQL游标进行DECLARE,并为其命名。在DECLARE语句中,提供了SELECT语句,该语句标识游标将指向的记录。然后,将此游标名称提供给OPEN游标语句。然后,反复发出FETCH游标语句以遍历SELECT结果集。然后,发出CLOSE游标语句。

  • 基于游标的查询使用DECLARE游标名称CURSOR FOR SELECT来选择记录,并(可选)将select列值返回到输出主机变量中。 FETCH语句遍历结果集,使用这些变量返回选定的列值。
  • 基于游标的DELETEUPDATE使用DECLARE游标名CURSOR FOR SELECT选择操作的记录。没有指定输出主机变量。 FETCH语句遍历结果集。 DELETEUPDATE语句包含WHERE CURRENT OF子句,以标识当前光标位置,以便对所选记录执行操作。

请注意,游标不能跨越方法。因此,必须在同一类方法中声明,打开,获取和关闭游标。在生成类和方法的所有代码(例如从.CSP文件生成的类)中考虑这一点很重要。

下面的示例使用游标执行查询并将结果显示给主体设备:

/// d ##class(PHA.TEST.SQL).CURSOR()
ClassMethod CURSOR()
{
	&sql(DECLARE C5 CURSOR FOR
		SELECT %ID,Name
		INTO :id, :name
		FROM Sample.Person
		WHERE Name %STARTSWITH 'A'
		ORDER BY Name
	)

	&sql(OPEN C5)
	QUIT:(SQLCODE'=0)
	&sql(FETCH C5)

	While (SQLCODE = 0) {
		Write id, ":  ", name,!        
		&sql(FETCH C5)
	}

	&sql(CLOSE C5)
}
DHC-APP>d ##class(PHA.TEST.SQL).CURSOR()
95:  Adams,Diane F.
183:  Adams,Susan E.
71:  Ahmed,Elmo X.
28:  Alton,Martin S.
175:  Alton,Phil T.
86:  Anderson,Mario L.
131:  Anderson,Valery N.

此示例执行以下操作:

  1. 声明一个游标C1,该游标返回一组按Name排序的Person行。
  2. 打开游标。
  3. 游标上调用FETCH直到到达数据末尾。每次调用FETCH之后,如果有更多数据要提取,则SQLCODE变量将设置为0。每次调用FETCH后,返回的值都将复制到DECLARE语句的INTO子句指定的主机变量中。
  4. 关闭光标。

DECLARE游标声明

DECLARE语句同时指定了游标名称和定义游标的SQL SELECT语句。 DECLARE语句必须在例程中出现在使用游标的任何语句之前。

游标名称区分大小写。

游标名称在类或例程中必须唯一。因此,递归调用的例程不能包含游标声明。在这种情况下,最好使用动态SQL。

下面的示例声明一个名为MyCursor的游标:

 &sql(DECLARE MyCursor CURSOR FOR
    SELECT Name, DOB
    FROM Sample.Person
    WHERE Home_State = :state
    ORDER BY Name
    )

DECLARE语句可以包括一个可选的INTO子句,该子句指定在遍历游标时将接收数据的本地主机变量的名称。例如,我们可以在前面的示例中添加一个INTO子句:

 &sql(DECLARE MyCursor CURSOR FOR
    SELECT Name, DOB
    INTO :name, :dob
    FROM Sample.Person
    WHERE Home_State = :state
    ORDER BY Name
    )

INTO子句可以包含逗号分隔的主机变量列表,单个主机变量数组或两者的组合。如果指定为以逗号分隔的列表,则INTO子句宿主变量的数量必须与游标的SELECT列表中的列数完全匹配,否则在编译该语句时会收到“基数不匹配”错误。

如果DECLARE语句不包含INTO子句,则INTO子句必须出现在FETCH语句中。通过在DECLARE语句而不是FETCH语句中指定INTO子句,可能会导致性能的小幅提高。

因为DECLARE是声明,而不是执行的语句,所以它不会设置或终止SQLCODE变量。

如果已经声明了指定的游标,则编译将失败,并显示SQLCODE -52错误,游标名称已声明。

执行DECLARE语句不会编译SELECT语句。 SELECT语句在第一次执行OPEN语句时被编译。嵌入式SQL不在常规编译时进行编译,而是在SQL执行时(运行时)进行编译。

OPEN游标声明

OPEN语句为后续执行准备了一个游标:

 &sql(OPEN MyCursor)

执行OPEN语句将编译在DECLARE语句中找到的Embedded SQL代码,创建优化的查询计划,并生成缓存的查询。执行OPEN(在SQL运行时)时,会发出涉及缺少资源(例如未定义的表或字段)的错误。

成功调用OPEN后,SQLCODE变量将设置为0。

必须先调用OPEN才能从游标中获取数据。

FETCH游标声明

FETCH语句获取游标下一行的数据(由游标查询定义):

 &sql(FETCH MyCursor)

必须先对游标进行DECLARE并打开,然后才能在其上调用FETCH

FETCH语句可以包含INTO子句,该子句指定在游标游标时将接收数据的本地主机变量的名称。例如,我们可以在前面的示例中添加一个INTO子句:

 &sql(FETCH MyCursor INTO :a, :b)

INTO子句可以包含逗号分隔的主机变量列表,单个主机变量数组或两者的组合。如果指定为以逗号分隔的列表,则INTO子句宿主变量的数量必须与游标的SELECT列表中的列数完全匹配,否则在编译该语句时,将收到SQLCODE -76“基数不匹配”错误。

通常,INTO子句是在DECLARE语句中指定的,而不是在FETCH语句中指定的。如果DECLARE语句中的SELECT查询和FETCH语句都包含INTO子句,则仅设置由DECLARE语句指定的主机变量。如果仅FETCH语句包含INTO子句,则将设置由FETCH语句指定的主机变量。

如果FETCH检索数据,则将SQLCODE变量设置为0;否则,将SQLCODE变量设置为0。如果没有数据(或没有更多数据)到FETCH,则将SQLCODE设置为100(没有更多数据)。主机变量值仅应在SQLCODE = 0时使用。

根据查询,第一次调用FETCH可能会执行其他任务(例如对临时数据结构中的值进行排序)。

CLOSE游标声明

CLOSE语句终止游标的执行:

 &sql(CLOSE MyCursor)

CLOSE语句清除查询执行所使用的任何临时存储。无法调用CLOSE的程序将遇到资源泄漏(例如,不需要的IRIS TEMP临时数据库增加)。

成功调用CLOSE后,SQLCODE变量将设置为0。因此,在关闭游标之前,应检查最终的FETCH是否将SQLCODE设置为0100

0
0 158
文章 姚 鑫 · 三月 19, 2021 11m read

第十二章 使用嵌入式SQL(三)

主机变量

主机变量是将文字值传入或传出嵌入式SQL的局部变量。 最常见的是,主机变量用于将本地变量的值作为输入值传递给Embedded SQL,或者将SQL查询结果值作为输出主机变量传递给Embedded SQL查询。

主机变量不能用于指定SQL标识符,例如架构名称,表名称,字段名称或游标名称。主机变量不能用于指定SQL关键字。

  • 输出主机变量仅在嵌入式SQL中使用。它们在INTO子句中指定,INTO子句是仅嵌入式SQL支持的SQL查询子句。
  • 输入主机变量可以在嵌入式SQL或动态SQL中使用。在动态SQL中,还可以使用“?”向SQL语句输入文字。输入参数。这 ”?”语法不能在Embedded SQL中使用。

在嵌入式SQL中,可以在可以使用文字值的任何位置使用输入主机变量。使用SELECT或FETCH语句的INTO子句指定输出主机变量。

注意:当SQL NULL输出到ObjectScript时,它由一个ObjectScript空字符串(“”)表示,该字符串的长度为零。

要将变量或属性引用用作宿主变量,请在其前面加上一个冒号(:)。 嵌入式InterSystems SQL中的主机变量可以是以下之一:

  • 一个或多个ObjectScript局部变量,例如:myvar,指定为以逗号分隔的列表。局部变量可以完全形成并且可以包含下标。像所有局部变量一样,它区分大小写,并且可以包含Unicode字母字符。
  • 单个ObjectScript局部变量数组,例如:myvars()。局部变量数组只能从单个表(而不是联接表或视图)中接收字段值。
  • 对象引用,例如:oref.Prop,其中Prop是属性名称,带有或不带有前导字符。这可以是简单属性或多维数组属性,例如:oref.Prop(1)。它可以是一个实例变量,例如:i%Prop或:i %% Data。属性名称可以定界。例如:Person."Home City".即使停用了对分隔标识符的支持,也可以使用分隔属性名称。多维属性可以包括:i%Prop()和:m%Prop()主机变量引用。对象引用主机变量可以包含任意数量的点语法级别;例如,例如,:Person.Address.City

oref.Prop用作过程块方法内的宿主变量时,系统会自动将oref变量(而不是整个oref.Prop引用)添加到PublicList并对其进行更新。

主机变量中的双引号指定文字字符串,而不是带分隔符的标识符。例如,:request.GetValueAt("PID:SetIDPID") or :request.GetValueAt("PID:PatientName(1).FamilyName").

主机变量应在ObjectScript过程的PublicList变量列表中列出,并使用NEW命令重新初始化。您可以配置InterSystems IRIS以便在注释文本中列出Embedded SQL中使用的所有主机变量。使用InterSystems SQL的注释部分对此进行了描述。

主机变量值具有以下行为:

  • 输入主机变量永远不会被SQL语句代码修改。即使嵌入式SQL运行后,它们仍保留其原始值。但是,输入主机变量值在提供给SQL语句代码之前会被“轻度格式化”:有效数字值将去除前导和尾随零,单个前导加号和尾随小数点。时间戳记值将除去尾随空格,以小数秒为单位的尾随零和(如果没有小数秒的话)尾随的小数点。
  • SQLCODE = 0时,即返回有效行时,将设置INTO子句中指定的输出主机变量。如果执行SELECT语句或FETCH语句导致SQLCODE = 100(没有数据与查询匹配),则INTO子句中指定的输出主机变量将设置为null(“”)。如果在执行SELECT语句或FETCH语句之前未定义INTO变量,导致SQLCODE = 100,则该变量将保持未定义状态。主机变量值仅应在SQLCODE = 0时使用。在DECLARE ... SELECT ... INTO语句中,请勿在两个FETCH调用之间修改INTO子句中的输出主机变量,因为这可能会导致不可预测的查询结果。

在处理输出主机变量之前,必须检查SQLCODE值。仅当SQLCODE = 0时才应使用输出主机变量值。

当在INTO子句中使用逗号分隔的主机变量列表时,必须指定与选择项数量相同的主机变量数量(字段,集合函数,标量函数,算术表达式,文字)。宿主变量太多或太少都会在编译时导致SQLCODE -76基数错误。

在嵌入式SQL中使用SELECT *时,这通常是一个问题。例如,SELECT * FROM Sample.Person仅对以逗号分隔的15个主机变量列表有效(非隐藏列的确切数目,具体取决于表定义,该数目可能包含也可能不包含系统生成的RowID) (ID)列)。

因为列数可以更改,所以用单个宿主变量的INTO子句列表指定SELECT *通常不是一个好主意。使用SELECT *时,通常最好使用主机变量下标数组,例如:

/// d ##class(PHA.TEST.SQL).EmbedSQL9()
ClassMethod EmbedSQL9()
{
	NEW SQLCODE
	&sql(SELECT %ID,* INTO :tflds() FROM Sample.Person )
	IF SQLCODE<0 {
		WRITE "SQLCODE错误 ",SQLCODE," ",%msg  QUIT
	} ELSEIF SQLCODE=100 {
		WRITE "查询没有结果"  QUIT
	}
	FOR i=0:1:25 { 
		IF $DATA(tflds(i)) {
			WRITE "field ",i," = ",tflds(i),! 
		}
	}
}
DHC-APP>d ##class(PHA.TEST.SQL).EmbedSQL9()
field 1 = 1
field 2 = 30
field 3 = 54536
field 4 = ReOrangYellow
field 6 = yaoxin
field 8 = 111-11-1117
field 9 = 13
field 11 = St Louis
field 12 = WI
field 13 = 889 Clinton Drive
field 14 = 78672
field 15 = Ukiah
field 16 = AL
field 17 = 9619 Ash Avenue
field 18 = 56589

本示例使用%ID返回RowID作为字段号1,无论RowID是否隐藏。 注意,在此示例中,字段编号下标可能不是连续序列;有些字段可能被隐藏并被跳过。包含NULL的字段以空字符串值列出。 ** 退出嵌入式SQL后立即检查SQLCODE值是一种良好的编程习惯。仅当SQLCODE = 0时才应使用输出主机变量值。**

主机变量示例

在下面的ObjectScript示例中,Embedded SQL语句使用输出主机变量将名称和归属状态地址从SQL查询返回到ObjectScript:

/// d ##class(PHA.TEST.SQL).EmbedSQL10()
ClassMethod EmbedSQL10()
{
	&sql(SELECT Name,Home_State
		INTO :CName,:CAddr
		FROM Sample.Person)
	IF SQLCODE<0 {
		WRITE "SQLCODE错误 ",SQLCODE," ",%msg  QUIT
	} ELSEIF SQLCODE=100 {
		WRITE "查询没有结果"  QUIT
	}
	
	WRITE !,"Name is: ",CName
	WRITE !,"State is: ",CAddr
}
DHC-APP>d ##class(PHA.TEST.SQL).EmbedSQL10()
 
Name is: yaoxin
State is: WI

嵌入式SQL使用INTO子句指定主机变量:CName:CAddr,以在局部变量CName中返回所选客户的姓名,并在局部变量CAddr中返回主目录状态。

下面的示例使用带下标的局部变量执行相同的操作:

/// d ##class(PHA.TEST.SQL).EmbedSQL11()
ClassMethod EmbedSQL11()
{
	&sql(SELECT Name,Home_State
	INTO :CInfo(1),:CInfo(2)
	FROM Sample.Person)
	IF SQLCODE<0 {
		WRITE "SQLCODE错误 ",SQLCODE," ",%msg  QUIT
	} ELSEIF SQLCODE=100 {
		WRITE "查询没有结果"  QUIT
	}	
	WRITE !,"Name is: ",CInfo(1)
	WRITE !,"State is: ",CInfo(2)
}
DHC-APP>d ##class(PHA.TEST.SQL).EmbedSQL11()
 
Name is: yaoxin
State is: WI

这些主机变量是带有用户提供的下标(:CInfo(1))的简单局部变量。但是,如果省略下标(:CInfo()),则InterSystems IRIS使用SqlColumnNumber填充主机变量下标数组,如下所述。

在下面的ObjectScript示例中,嵌入式SQL语句同时使用输入主机变量(在WHERE子句中)和输出主机变量(在INTO子句中):

/// d ##class(PHA.TEST.SQL).EmbedSQL12()
ClassMethod EmbedSQL12()
{
	SET minval = 10000
	SET maxval = 50000
	&sql(SELECT Name,Salary INTO :outname, :outsalary
	FROM Sample.Employee
	WHERE Salary > :minval AND Salary < :maxval)
	IF SQLCODE<0 {
		WRITE "SQLCODE错误 ",SQLCODE," ",%msg  QUIT
	} ELSEIF SQLCODE=100 {
		WRITE "查询没有结果"  QUIT
	}
	WRITE !,"Name is: ",outname
	WRITE !,"Salary is: ",outsalary
}
DHC-APP>d ##class(PHA.TEST.SQL).EmbedSQL12()
 
Name is: Chadwick,Phyllis L.
Salary is: 16377

以下示例在输入主机变量上执行“light normalization”。请注意,InterSystems IRIS将输入变量值视为字符串,并且不对其进行规范化,但是Embedded SQL将此数字规范化为65,以在WHERE子句中执行相等比较:

/// d ##class(PHA.TEST.SQL).EmbedSQL13()
ClassMethod EmbedSQL13()
{
	SET x="+065.000"
		&sql(SELECT Name,Age
		INTO :a,:b
		FROM Sample.Person
		WHERE Age=:x)
	IF SQLCODE<0 {
		WRITE "SQLCODE错误 ",SQLCODE," ",%msg  QUIT
	} ELSEIF SQLCODE=100 {
		WRITE "查询没有结果"  QUIT
	}
	WRITE !,"Input value is: ",x
	WRITE !,"Name value is: ",a
	WRITE !,"Age value is: ",b
}

DHC-APP>d ##class(PHA.TEST.SQL).EmbedSQL13()
 
Input value is: +065.000
Name value is: Houseman,Martin D.
Age value is: 65

在下面的ObjectScript示例中,嵌入式SQL语句使用对象属性作为宿主变量:

   &sql(SELECT Name, Title INTO :obj.Name, :obj.Title
        FROM MyApp.Employee
        WHERE %ID = :id )

在这种情况下,obj必须是对具有可变(即可以修改)属性NameTitle的对象的有效引用。请注意,如果查询包含INTO语句并且没有返回任何数据(即SQLCODE100),则执行查询可能会导致修改主机变量的值。

用列号下标的主机变量

如果FROM子句包含一个表,则可以为从该表中选择的字段指定带下标的主机变量;否则,可以为该表指定一个下标主机变量。例如,本地数组:myvar()。 InterSystems IRIS使用每个字段的SqlColumnNumber作为数字下标填充本地数组。请注意,SqlColumnNumber是表定义中的列号,而不是选择列表序列。 (不能将带下标的宿主变量用于视图的字段。)

主机变量数组必须是省略了最低级别下标的局部数组。因此,:myvar(), :myvar(5,), and :myvar(5,2,)都是有效的主机变量下标数组。

  • 主机变量下标数组可以用于INSERTUPDATEINSERT OR UPDATE语句VALUES子句中的输入。当在INSERTUPDATE语句中使用时,主机变量数组使您可以定义在运行时而不是在编译时更新哪些列。
  • 主机变量下标数组可以用于SELECTDECLARE语句INTO子句中的输出。在下面的示例中显示了SELECT中的下标数组用法。

在下面的示例中,SELECT使用指定字段的值填充Cdata数组。 Cdata()的元素对应于表列定义,而不是SELECT元素。因此,在Sample.Person中,“名称”字段是第6列,“年龄”字段是第2列,“出生日期”DOB)字段是第3列:

/// d ##class(PHA.TEST.SQL).EmbedSQL14()
ClassMethod EmbedSQL14()
{
	&sql(SELECT Name, Age, DOB
		INTO :Cdata()
		FROM Sample.Person)
	IF SQLCODE<0 {
		WRITE "SQLCODE错误 ",SQLCODE," ",%msg  QUIT
	} ELSEIF SQLCODE=100 {
		WRITE "查询没有结果"  QUIT
	}
	WRITE !,"Name is: ",Cdata(6)
	WRITE !,"Age is: ",Cdata(2)
	WRITE !,"DOB is: ",$ZDATE(Cdata(3),1)
}
DHC-APP>d ##class(PHA.TEST.SQL).EmbedSQL14()
 
Name is: yaoxin
Age is: 30
DOB is: 04/25/90

以下示例使用带下标的数组主机变量返回行的所有字段值:

/// d ##class(PHA.TEST.SQL).EmbedSQL15()
ClassMethod EmbedSQL15()
{
   	&sql(SELECT * INTO :Allfields()
		FROM Sample.Person)
	IF SQLCODE<0 {
		WRITE "SQLCODE错误 ",SQLCODE," ",%msg  QUIT
	} ELSEIF SQLCODE=100 {
		WRITE "查询没有结果"  QUIT
	}
	SET x=1
	WHILE x '="" {
		WRITE !,x," field is ",Allfields(x)
		SET x=$ORDER(Allfields(x))
	}
}
DHC-APP>d ##class(PHA.TEST.SQL).EmbedSQL15()
 
1 field is 1
2 field is 30
3 field is 54536
4 field is ReOrangYellow
6 field is yaoxin
8 field is 111-11-1117
9 field is 13
11 field is St Louis
12 field is WI
13 field is 889 Clinton Drive
14 field is 78672
15 field is Ukiah
16 field is AL
17 field is 9619 Ash Avenue
18 field is 56589

请注意,此WHILE循环使用$ORDER而不是简单的x = x + 1进行递增。这是因为在许多表(例如Sample.Person)中,可能存在隐藏的列。这些导致列号序列不连续。

如果SELECT列表包含不是该表中的字段的项,例如表达式或箭头语法字段,则INTO子句还必须包含逗号分隔的非数组主机变量。下面的示例组合了一个带下标的数组主机变量,以返回与定义的表列对应的值,而主机变量组合为返回与定义的表列不对应的值:

/// d ##class(PHA.TEST.SQL).EmbedSQL16()
ClassMethod EmbedSQL16()
{
   	&sql(SELECT Name, Home_City, {fn NOW}, Age, ($HOROLOG-DOB)/365.25, Home_State
		INTO :Allfields(), :timestmp('now'), :exactage
		FROM Sample.Person)
	IF SQLCODE<0 {
		WRITE "SQLCODE错误 ",SQLCODE," ",%msg  QUIT
	} ELSEIF SQLCODE=100 {
		WRITE "查询没有结果"  QUIT
	}
	SET x = $ORDER(Allfields(""))
	WHILE x '="" {
		WRITE !,x," field is ",Allfields(x)
		SET x=$ORDER(Allfields(x)) 
	}
	WRITE !,"date & time now is ",timestmp("now")
	WRITE !,"exact age is ",exactage
}
DHC-APP> d ##class(PHA.TEST.SQL).EmbedSQL16()
 
1 field is 1
2 field is 30
3 field is 54536
4 field is ReOrangYellow
6 field is yaoxin
8 field is 111-11-1117
9 field is 13
11 field is St Louis
12 field is WI
13 field is 889 Clinton Drive
14 field is 78672
15 field is Ukiah
16 field is AL
17 field is 9619 Ash Avenue
18 field is 56589
date & time now is 2021-03-13 16:00:40
exact age is 30.88295687885010267

请注意,非数组主机变量必须在数量和顺序上与非列SELECT项匹配。

将主机变量用作下标数组受以下限制:

  • 只有在FROM子句的单个表中选择字段时,才可以使用带下标的列表。这是因为从多个表中选择字段时,SqlColumnNumber值可能会发生冲突。
  • 下标列表只能在选择表字段时使用。它不能用于表达式或聚合字段。这是因为这些选择列表项没有SqlColumnNumber值。

NULL和未定义的主机变量

如果指定未定义的输入主机变量,则嵌入式SQL将其值视为NULL

/// d ##class(PHA.TEST.SQL).EmbedSQL17()
ClassMethod EmbedSQL17()
{
	NEW x
	&sql(SELECT Home_State,:x
		INTO :a,:b
		FROM Sample.Person)
	IF SQLCODE<0 {
		WRITE "SQLCODE错误 ",SQLCODE," ",%msg  QUIT
	} ELSEIF SQLCODE=100 {
		WRITE "查询没有结果"  QUIT
	}
	WRITE !,"Home_State的长度为: ",$LENGTH(a)
	WRITE !,"x的长度是: ",$LENGTH(b)
}
DHC-APP>d ##class(PHA.TEST.SQL).EmbedSQL17()
 
Home_State的长度为: 2
x的长度是: 0

SQL NULL等效于ObjectScript“”字符串(长度为零的字符串)。

如果将NULL输出到主机变量,则Embedded SQL会将其值视为ObjectScript“”字符串(零长度字符串)。例如,Sample.Person中的某些记录具有NULL Spouse字段。执行此查询后:

/// d ##class(PHA.TEST.SQL).EmbedSQL18()
ClassMethod EmbedSQL18()
{
	&sql(SELECT Name,Spouse
		INTO :name, :spouse
		FROM Sample.Person
		WHERE Spouse IS NULL)
	IF SQLCODE<0 {
		WRITE "SQLCODE错误 ",SQLCODE," ",%msg  QUIT
	} ELSEIF SQLCODE=100 {
		WRITE "查询没有结果"  QUIT
	}
	WRITE !,"Name: ",name," of length ",$LENGTH(name)," defined: ",$DATA(name)
	WRITE !,"Spouse: ",spouse," of length ",$LENGTH(spouse)," defined: ",$DATA(spouse)
}
DHC-APP>d ##class(PHA.TEST.SQL).EmbedSQL18()
 
Name: xiaoli of length 6 defined: 1
Spouse:  of length 0 defined: 1

宿主变量spouse将设置为“”(长度为零的字符串)以指示NULL值。因此,不能使用ObjectScript $DATA函数来确定SQL字段是否为NULL。当传递带有NULL值的SQL字段的输出主机变量时,$DATA返回true(定义了变量)。

在极少数情况下,表字段包含SQL零长度字符串(''),例如,如果应用程序将字段显式设置为SQL ''字符串,则主机变量将包含特殊标记值$CHAR(0 )(长度为1的字符串,仅包含一个ASCII 0字符),它是SQL零长度字符串的ObjectScript表示形式。强烈建议不要使用SQL零长度字符串。

下面的示例比较SQL NULL和SQL零长度字符串输出的主机变量:

/// d ##class(PHA.TEST.SQL).EmbedSQL19()
ClassMethod EmbedSQL19()
{
	&sql(SELECT '',Spouse
		INTO :zls, :spouse
		FROM Sample.Person
		WHERE Spouse IS NULL)
	IF SQLCODE<0 {
		WRITE "SQLCODE错误 ",SQLCODE," ",%msg  QUIT
	} ELSEIF SQLCODE=100 {
		WRITE "查询没有结果"  QUIT
	}
	WRITE "In ObjectScript"
	WRITE !,"ZLS is of length ",$LENGTH(zls)," defined: ",$DATA(zls)
	/* Length=1, Defined=1 */
	WRITE !,"NULL is of length ",$LENGTH(spouse)," defined: ",$DATA(spouse)
	/* Length=0, Defined=1 */
}
DHC-APP>d ##class(PHA.TEST.SQL).EmbedSQL19()
In ObjectScript
ZLS is of length 1 defined: 1
NULL is of length 0 defined: 1

请注意,此主机变量NULL行为仅在基于服务器的查询(嵌入式SQL和动态SQL)中为true。在ODBC和JDBC中,使用ODBC或JDBC接口显式指定NULL值。

主机变量的有效性

  • 嵌入式SQL永远不会修改输入主机变量。
  • 仅当SQLCODE = 0时,输出主机变量才在Embedded SQL之后可靠地有效。

例如,以下OutVal的用法不可靠:

/// d ##class(PHA.TEST.SQL).EmbedSQL20()
ClassMethod EmbedSQL20()
{
InvalidExample
	SET InVal = "1234"
	SET OutVal = "None"
	&sql(SELECT Name
		INTO :OutVal
		FROM Sample.Person
		WHERE %ID=:InVal)
	IF OutVal="None" {          
		WRITE !,"没有数据返回"
		WRITE !,"SQLCODE=",SQLCODE 
	} ELSE {
		WRITE !,"Name is: ",OutVal 
	}
}

DHC-APP>d ##class(PHA.TEST.SQL).EmbedSQL20()
 
没有数据返回
SQLCODE=100

调用嵌入式SQL之前设置的OutVal的值在从嵌入式SQL返回之后不应该被IF命令引用。

相反,应该使用SQLCODE变量编写如下示例:

/// d ##class(PHA.TEST.SQL).EmbedSQL21()
ClassMethod EmbedSQL21()
{
ValidExample
	SET InVal = "1234"
	&sql(SELECT Name
		INTO :OutVal
		FROM Sample.Person
		WHERE %ID=:InVal)
	IF SQLCODE'=0 { 
		SET OutVal="None" 
		IF OutVal="None" {
		WRITE !,"没有数据返回"
		WRITE !,"SQLCODE=",SQLCODE } 
	} ELSE {
		WRITE !,"Name is: ",OutVal 
	}
}
DHC-APP>d ##class(PHA.TEST.SQL).EmbedSQL21()
 
没有数据返回
SQLCODE=100

嵌入式SQL将SQLCODE变量设置为0,以指示成功地检索输出行。 SQLCODE值为100表示没有找到与SELECT条件匹配的行。 SQLCODE负数表示SQL错误条件。

主机变量和程序块

如果嵌入式SQL在过程块内,则所有输入和输出主机变量必须是公共的。可以通过在过程块开始处的PUBLIC部分中声明它们,或用一个初始字符命名它们(自动使它们公开)来完成它们。但是请注意,用户定义的主机变量是自动公开的,但不是自动更新的。用户有责任根据需要对这些变量执行NEW。如嵌入式SQL变量中所述,某些SQL变量(例如%ROWCOUNT%ROWID%msg)既自动公开又自动更新。必须将SQLCODE声明为public

在以下过程块示例中,主机变量zipcitystate以及SQLCODE变量被声明为PUBLIC。 SQL系统变量%ROWCOUNT%ROWID%msg已经公开,因为它们的名称以字符开头。然后,过程代码对SQLCODE,其他SQL系统变量和状态局部变量执行NEW

/// d ##class(PHA.TEST.SQL).EmbedSQL22()
ClassMethod EmbedSQL22()
{
UpdateTest(zip,city)
	[SQLCODE,zip,city,state] PUBLIC {
	NEW SQLCODE,%ROWCOUNT,%ROWID,%msg,state
	SET state="MA"
	&sql(UPDATE Sample.Person
		SET Home_City = :city, Home_State = :state
		WHERE Home_Zip = :zip)
	IF SQLCODE<0 {
		WRITE "SQLCODE error ",SQLCODE," ",%msg  QUIT
	}
		QUIT %ROWCOUNT
	}
}
0
0 148
文章 姚 鑫 · 三月 18, 2021 6m read

第十二章 使用嵌入式SQL(二)

嵌入式SQL代码

简单的SQL语句

可以将简单的SQL语句(单个嵌入式SQL语句)用于各种操作,包括:

  • INSERTUPDATEINSERT OR UPDATEDELETE语句。
  • `DDL语句。
  • GRANTREVOKE语句。
  • 只返回一行的SELECT语句(或者如果只对返回的第一行感兴趣)。

简单的SQL语句也被称为非基于游标的SQL语句。本章稍后将介绍基于游标的嵌入式SQL。

例如,以下语句查找ID为43的(唯一的)患者的姓名:

 &sql(SELECT Name INTO :name
    FROM Patient
    WHERE %ID = 43)

如果对可以返回多行的查询使用简单语句,则只返回第一行:

 &sql(SELECT Name INTO :name
    FROM Patient
    WHERE Age = 43)

根据查询的不同,不能保证哪一行将首先被返回。此外,如果一个查询包含一个INTO语句,并且该表不包含任何数据(SQLCODE=100),执行该查询将导致未定义(空)的输出主机变量。因此,在访问输出主机变量之前,所有简单嵌入式SQL语句都应该测试SQLCODE=0

架构名称解析

表名,视图名或存储过程名是合格的(指定架构名称)或不合格的(不指定架构名称)。如果名称未指定架构名称,则InterSystems IRIS会按以下方式解析架构名称:

  • 数据定义:InterSystems IRIS使用系统范围内的默认架构来解析不合格的名称。如果默认模式不存在,则InterSystems IRIS将创建模式和相应的类包。所有数据定义语句都使用系统范围内的默认架构。数据定义语句忽略#Import#SQLCompile Path宏预处理程序指令。
  • 数据管理:InterSystems IRIS对包含嵌入式SQL语句的类或例程使用#SQLCompile路径和/或#Import宏预处理程序指令指定的架构搜索路径。 #Import#SQLCompile Path指令是具有不同功能的可能模式名称的相互独立列表。二者之一或两者均可用于为不合格的表,视图或存储过程名称提供架构名称。如果未指定架构搜索路径,则InterSystems IRIS将使用系统范围的默认架构名称。

文字值

嵌入式SQL查询可能包含文字值(字符串,数字或日期)。字符串应用单引号(')引起来。 (在InterSystems SQL中,双引号指定分隔的标识符):

/// d ##class(PHA.TEST.SQL).EmbedSQL4()
ClassMethod EmbedSQL4()
{
	&sql(SELECT 'Employee (' || Name || ')' INTO :name 
		FROM Sample.Employee)
	IF SQLCODE<0 {
		WRITE "SQLCODE错误 ",SQLCODE," ",%msg  QUIT
	} ELSEIF SQLCODE=100 {
		WRITE "查询没有结果"  QUIT
	}
	WRITE name
}

DHC-APP>d ##class(PHA.TEST.SQL).EmbedSQL4()
Employee (Adams,Susan E.)

数值可以直接使用。在InterSystems IRIS将这些文字值与字段值进行比较之前,先对文字数字和时间戳值进行“lightly normalized”,如以下示例所示,其中+0050.000被格式化为50

/// d ##class(PHA.TEST.SQL).EmbedSQL5()
ClassMethod EmbedSQL5()
{
	&sql(SELECT Name,Age INTO :name,:age 
	   FROM Sample.Person
	   WHERE Age = +0050.000)
	IF SQLCODE<0 {
		WRITE "SQLCODE错误 ",SQLCODE," ",%msg  QUIT
	} ELSEIF SQLCODE=100 {
		WRITE "查询没有结果"  QUIT
	}
	WRITE name," age=",age
}
DHC-APP>d ##class(PHA.TEST.SQL).EmbedSQL5()
Russell,Quentin V. age=50

可以指定算术、函数和特殊变量表达式:

/// d ##class(PHA.TEST.SQL).EmbedSQL6()
ClassMethod EmbedSQL6()
{
	&sql(DECLARE C3 CURSOR FOR 
			SELECT Name, Age - 65, $HOROLOG INTO :name,:retire,:today 
			FROM Sample.Person
			WHERE Age > 60
			ORDER BY Age, Name)
	&sql(OPEN C3)
	QUIT:(SQLCODE'=0)
	&sql(FETCH C3)
	WHILE (SQLCODE = 0) {
		WRITE $ZDATE(today)," ",name," has ",retire," eligibility years",!
		&sql(FETCH C3) 
	}
	&sql(CLOSE C3)
}

DHC-APP>d ##class(PHA.TEST.SQL).EmbedSQL6()
03/13/2021 Moon,Rhonda T. has -4 eligibility years
03/13/2021 Olsen,Ashley G. has -4 eligibility years
03/13/2021 Quixote,Terry J. has -4 eligibility years
03/13/2021 Yoders,Liza U. has -4 eligibility years
03/13/2021 Gore,Alfred M. has -3 eligibility years
03/13/2021 Houseman,Alice R. has -2 eligibility years
03/13/2021 Nichols,Heloisa M. has -2 eligibility years
03/13/2021 Houseman,Martin D. has 0 eligibility years
03/13/2021 LaRocca,David X. has 0 eligibility years
03/13/2021 Ng,Liza Z. has 0 eligibility years
03/13/2021 Smith,Elvis Y. has 0 eligibility years

在嵌入式SQL中,字符串文字中不允许使用以##开头的一些字符序列,而必须使##lit指定。这些字符序列是##;, ##beginlit, ##expression(, ##function(, ##quote(, ##stripq(, and ##unique(。例如,以下示例失败:

ClassMethod EmbedSQL7()
{
  WRITE "Embedded SQL test",!
  &sql(SELECT 'the sequence ##unique( is restricted' INTO :x)
  WRITE x
}

以下解决方法成功:

/// d ##class(PHA.TEST.SQL).EmbedSQL7()
ClassMethod EmbedSQL7()
{
	WRITE "Embedded SQL test",!
	&sql(SELECT 'the sequence ##lit(##unique() is restricted' INTO :x)
	WRITE x
}
DHC-APP>d ##class(PHA.TEST.SQL).EmbedSQL7()
Embedded SQL test
the sequence ##unique( is restricted

数据格式

在嵌入式SQL中,数据值处于“逻辑模式”。也就是说,值采用SQL查询处理器使用的本机格式。对于未定义LogicalToODBCLogicalToDisplay转换的字符串,整数和其他数据类型,这无效。数据格式会影响%List数据以及%Date%Time数据类型。

%List数据类型在逻辑模式下显示为以非打印列表编码字符开头的元素值。 WRITE命令将这些值显示为连接的元素。例如,Sample.PersonFavoriteColors字段以%List数据类型存储数据,例如:$LISTBUILD('Red','Black')。在嵌入式SQL中,这在逻辑模式下显示为RedBlack,长度为12个字符。在“显示”模式下,它显示为“Red,Black”;在ODBC模式下,它显示为Red,Black。在下面的示例中显示:


/// d ##class(PHA.TEST.SQL).EmbedSQL8()
ClassMethod EmbedSQL8()
{
	&sql(DECLARE C4 CURSOR FOR
			SELECT TOP 10 FavoriteColors INTO :colors
			FROM Sample.Person WHERE FavoriteColors IS NOT NULL)
	&sql(OPEN C4)
	QUIT:(SQLCODE'=0)
	&sql(FETCH C4)
	WHILE (SQLCODE = 0) {
		WRITE $LENGTH(colors),": ",colors,!
		&sql(FETCH C4) 
	}
	&sql(CLOSE C4)
}
DHC-APP>d ##class(PHA.TEST.SQL).EmbedSQL8()
21: ReOrangYellow
28: ReOrangYellowGreen
35: ReOrangYellowGreenGreen
36: ReOrangYellowGreeYellow
7: White
7: Black
14: GreenWhite
8:Purple
8:Yellow
10: RedRed
 

InterSystems IRIS提供的%Date%Time数据类型使用InterSystems IRIS内部日期表示形式($HOROLOG格式)作为其逻辑格式。 %Date数据类型在逻辑模式下返回INTEGER数据类型值;在“显示”模式下为VARCHAR数据类型值,在“ODBC”模式下为DATE数据类型值。 %TimeStamp数据类型的逻辑,显示和ODBC格式使用ODBC日期-时间格式(YYYY-MM-DD HH:MM:SS)。

例如,考虑以下类定义:

Class MyApp.Patient Extends %Persistent
{
/// Patient name
Property Name As %String(MAXLEN = 50);

/// Date of birth
Property DOB As %Date;

/// Date and time of last visit
Property LastVisit As %TimeStamp;
}

针对该表的简单嵌入式SQL查询将以逻辑模式返回值。例如,考虑以下查询:

 &sql(SELECT Name, DOB, LastVisit
        INTO :name, :dob, :visit
         FROM Patient
        WHERE %ID = :id)

该查询将三个属性的逻辑值返回到主机变量名称,dobvisit中:

主机变量
name"Weiss,Blanche"
dob44051
visit"2001-03-15 11:11:00"

请注意,dob$HOROLOG格式。可以使用$ZDATETIME函数将其转换为显示格式:

 SET dob = 44051
 WRITE $ZDT(dob,3),!

WHERE子句中的true相同的考虑因素。例如,要查找具有给定生日的患者,必须在WHERE子句中使用逻辑值:

 &sql(SELECT Name INTO :name
        FROM Patient
        WHERE DOB = 43023)

或者,使用主机变量:

 SET dob = $ZDH("01/02/1999",1)

 &sql(SELECT Name INTO :name
        FROM Patient
        WHERE DOB = :dob)

在这种情况下,我们使用$ZDATEH函数将显示格式日期转换为其等效的$HOROLOG逻辑值。

权限检查

嵌入式SQL不执行SQL特权检查。可以访问所有表,视图和列,并执行任何操作,而不管特权分配如何。假定使用嵌入式SQL的应用程序将在使用嵌入式SQL语句之前检查特权。

可以在嵌入式SQL中使用InterSystems SQL %CHECKPRIV语句来确定当前权限。

0
0 150
文章 姚 鑫 · 三月 17, 2021 9m read

第十二章 使用嵌入式SQL(一)

可以将SQL语句嵌入InterSystemsIRIS®数据平台使用的ObjectScript代码中。这些嵌入式SQL语句在运行时转换为优化的可执行代码。

嵌入式SQL有两种:

  • 一个简单的嵌入式SQL查询只能返回单行中的值。简单嵌入式SQL还可以用于单行插入,更新和删除以及其他SQL操作。
  • 基于游标的嵌入式SQL查询可以遍历查询结果集,并从多行中返回值。基于游标的嵌入式SQL也可以用于多行更新和删除SQL操作。

注意:嵌入式SQL不能输入到Terminal命令行,也不能在XECUTE语句中指定。要从命令行执行SQL,请使用$SYSTEM.SQL.Execute()方法或SQL Shell接口。

编译嵌入式SQL

当包含嵌入式SQL的例程被编译时,嵌入式SQL不会被编译。 相反,嵌入式SQL的编译发生在SQL代码的第一次执行(运行时)。 第一次执行定义了一个可执行的缓存查询。 这与动态SQL的编译类似,在动态SQL中,直到执行SQL Prepare操作才编译SQL代码。

直到第一次执行例程,嵌入式SQL代码才会根据SQL表和其他实体进行验证。 因此,可以编译包含嵌入式SQL的持久化类的例程或方法,这些SQL引用在例程编译时不存在的表或其他SQL实体。 由于这个原因,大多数SQL错误是在运行时执行时返回的,而不是编译时返回的。

在例程编译时,对嵌入式SQL执行SQL语法检查。 ObjectScript编译器失败,并为嵌入式SQL中的无效SQL语法生成编译错误。

可以使用Management Portal SQL接口测试嵌入式SQL中指定的SQL实体是否存在,而不需要执行SQL代码。 这在验证嵌入式SQL代码中进行了描述,该代码既验证SQL语法,又检查是否存在SQL实体。 可以选择在运行时执行之前验证嵌入式SQL代码,方法是使用/compileembedded=1限定符编译包含嵌入式SQL代码的例程,如验证嵌入式SQL代码中所述。

成功执行的嵌入式SQL语句将生成一个缓存的查询。该嵌入式SQL的后续执行将使用缓存的查询,而不是重新编译嵌入式SQL源。这提供了对嵌入式SQL的缓存查询的性能优势。

当首次使用OPEN命令打开游标时,会执行基于游标的Embedded SQL语句的运行时执行。在执行的这一点上,将生成优化的缓存查询计划,如管理门户中的“ SQL语句”列表中所示。列出的“ SQL语句”位置是包含嵌入式SQL代码的例程的名称。请注意,执行嵌入式SQL不会在“缓存的查询”列表中生成一个条目。这些清单(带有类名称,例如%sqlcq.USER.cls1)是由Dynamic SQL查询创建的。

注意:较早版本的IRIS中使用的#SQLCompile Mode预处理程序语句已被弃用。它已被解析,但不再对大多数嵌入式SQL命令执行任何操作。无论#SQLCompile Mode设置如何,大多数嵌入式SQL命令都会在运行时进行编译。但是,设置#SQLCompile Mode = deferred对于少量的嵌入式SQL命令仍然有意义,因为它会强制在运行时编译所有类型的嵌入式SQL命令。

嵌入式SQL和宏预处理器

可以在方法内和触发器内(前提是它们已定义为使用ObjectScript)或在ObjectScript MAC例程内使用嵌入式SQL。 MAC例程由InterSystems IRIS宏预处理器处理,并转换为INT(中间)代码,随后将其编译为可执行的OBJ代码。这些操作是在包含嵌入式SQL的例程的编译时执行的,而不是在嵌入式SQL代码本身上执行的,嵌入式SQL代码本身直到运行时才进行编译。

**如果嵌入式SQL语句本身包含InterSystems IRIS宏预处理器语句(#命令,##函数或$$macro引用),则在编译例程时将编译这些语句,并在运行时将其提供给SQL代码。**这可能会影响包含ObjectScript代码主体的CREATE PROCEDURECREATE FUNCTIONCREATE METHODCREATE QUERYCREATE TRIGGER语句。

在嵌入式SQL中包含文件

嵌入式SQL语句要求它们引用的任何宏包含文件都必须在运行时加载到系统上。

因为嵌入式SQL的编译将推迟到首次引用之前进行,所以嵌入式SQL类的编译上下文将是运行时环境,而不是包含类或例程的编译时环境。如果运行时当前名称空间与包含例程的编译时名称空间不同,则编译时名称空间中的包含文件可能在运行时名称空间中不可见。在这种情况下,将发生以下情况:

  1. 如果在运行时名称空间中看不到包含文件,则嵌入式SQL编译将删除所有包含文件。由于SQL编译很少需要包含文件,因此如果没有这些文件,运行时嵌入式SQL编译通常会成功。
  2. 如果删除包含文件后编译失败,则InterSystems IRIS错误将报告例程编译时名称空间,嵌入式SQL运行时名称空间以及从运行时名称空间看不到的包含文件列表。

#SQLCompile宏指令

宏预处理器提供了三个与嵌入式SQL一起使用的预处理器指令:

  • #SQLCompile Select指定从Select语句返回时数据显示的格式,或者指定插入或更新语句时数据输入所需的格式,或者指定Select输入主机变量。 它支持以下6个选项:Logical(默认值)DisplayODBCRuntimeText(与Display相同)FDBMS(见下文)。 如果#SQLCompile Select=Runtime,可以使用$SYSTEM.SQL.Util.SetOption("SelectMode",n)方法来更改数据的显示方式。 n取值为0=Logical1=ODBC2=Display

无论指定了#SQLCompile Select选项,INSERTUPDATE都会自动将指定的数据值转换为相应的逻辑格式进行存储。

不管指定了#SQLCompile Select选项,Select都会自动将输入的主机变量值转换为谓词匹配的相应逻辑格式。

使用#SQLCompile Select进行查询显示如下示例所示。 这些示例显示DOB(出生日期)值,然后将SelectMode更改为ODBC格式,然后再次显示DOB。 在第一个例子中,改变SelectMode对显示没有影响; 在第二个示例中,因为#SQLCompile Select=Runtime,更改SelectMode将更改显示:

/// d ##class(PHA.TEST.SQL).EmbedSQL()
ClassMethod EmbedSQL()
{
	#SQLCompile Select=Display
	&sql(SELECT DOB INTO :a FROM Sample.Person)
	IF SQLCODE<0 {
		WRITE "SQLCODE error ",SQLCODE," ",%msg  
		QUIT
	} ELSEIF SQLCODE=100 {
		WRITE "Query returns no results"  
		QUIT
	}
	WRITE "1st date of birth is ",a,!
	DO $SYSTEM.SQL.Util.SetOption("SelectMode",1)
	WRITE "changed select mode to: ",$SYSTEM.SQL.Util.GetOption("SelectMode"),!
	&sql(SELECT DOB INTO :b FROM Sample.Person)
	WRITE "2nd date of birth is ",b
}
DHC-APP>d ##class(PHA.TEST.SQL).EmbedSQL()
1st date of birth is 04/25/1990
2nd date of birth is 04/25/1990
/// d ##class(PHA.TEST.SQL).EmbedSQL1()
ClassMethod EmbedSQL1()
{
   #SQLCompile Select=Runtime
   &sql(SELECT DOB INTO :a FROM Sample.Person)
      IF SQLCODE<0 {WRITE "SQLCODE error ",SQLCODE," ",%msg  QUIT}
      ELSEIF SQLCODE=100 {WRITE "Query returns no results"  QUIT}
   WRITE "1st date of birth is ",a,!
   //DO $SYSTEM.SQL.Util.SetOption("SelectMode",1)
   //WRITE "changed select mode to: ",$SYSTEM.SQL.Util.GetOption("SelectMode"),!
   &sql(SELECT DOB INTO :b FROM Sample.Person)
   WRITE "2nd date of birth is ",b
}
DHC-APP>d ##class(PHA.TEST.SQL).EmbedSQL1()
1st date of birth is 1990-04-25
2nd date of birth is 1990-04-25
  • 提供#SQLCompile Select=FDBMS是为了使嵌入式SQL能够以与FDBMS相同的方式格式化数据。 如果一个查询在WHERE子句中有一个常量值,FDBMS模式假定它是一个显示值,并使用DisplayToLogical转换对它进行转换。 如果一个查询在WHERE子句中有一个变量,FDBMS模式使用FDBMSToLogical conversion对它进行转换。 应该设计FDBMS转换方法来处理三种FDBMS变量格式:InternalInternal_$c(1)_External$c(1)_External。 如果查询选择一个变量,它将调用LogicalToFDBMS转换方法。 这个方法返回Internal_$c(1)_External

  • #SQLCompile Path(或#Import)指定模式搜索路径,用于解析SELECTCALLINSERTUPDATEDELETETRUNCATE表等数据管理命令中未限定的表、视图和存储过程名称。 如果没有指定模式搜索路径,或者在指定的模式中找不到表,InterSystems IRIS将使用默认模式。 数据定义语句如ALTER TABLEDROP VIEWCREATE INDEXCREATE TRIGGER会忽略#SQLCompile Path#Import。 数据定义语句使用默认模式来解析非限定名称。

  • #SQLCompile Audit计是一个布尔开关,指定嵌入式SQL语句的执行是否应该记录在系统事件审计日志中。

嵌入式SQL语法

&sql指令

嵌入式SQL语句由&sql()指令与其余代码分开,如以下示例所示:

/// d ##class(PHA.TEST.SQL).EmbedSQL2()
ClassMethod EmbedSQL2()
{
	NEW SQLCODE,a
	WRITE "调用嵌入式SQL",!
	&sql(SELECT Name INTO :a FROM Sample.Person)
	IF SQLCODE<0 {
		WRITE "SQLCODE错误 ",SQLCODE," ",%msg  QUIT
	} ELSEIF SQLCODE=100 {
		WRITE "查询没有结果"  QUIT
	}
	WRITE "名字是 ",a
}
DHC-APP>d ##class(PHA.TEST.SQL).EmbedSQL2()
调用嵌入式SQL
名字是 Adams,Diane F.

使用指定一个或多个主机变量的INTO子句返回结果。在这种情况下,主机变量名为:a

&sql指令不区分大小写;可以使用&sql&SQL&Sql等。 &sql指令必须后跟一个开放的括号,并且中间没有空格,换行符或注释。&sql指令可以与标签在同一行上使用,如以下示例所示:

/// d ##class(PHA.TEST.SQL).EmbedSQL3()
ClassMethod EmbedSQL3()
{
Mylabel  &sql(
			SELECT Name INTO :a 
			FROM Sample.Person
			)
}

&sql指令的主体应包含一个有效的Embedded SQL语句,并用括号括起来。可以按照自己喜欢的任何方式设置SQL语句的格式:SQL会忽略空格和换行符。 Studio可以识别&sql指令,并使用可识别SQL的着色器对SQL代码语句进行语法着色。

当宏预处理器遇到&sql指令时,它将随附的SQL语句交给SQL查询处理器。查询处理器返回执行查询所需的代码(ObjectScript INT格式)。然后,宏预处理器用此代码(或对包含该代码的标签的调用)替换&sql指令。在Studio中,可以根据需要查看生成的代码,方法是查看为类或例程生成的INT代码(使用“查看”菜单中的“查看其他代码”选项)。

如果&sql指令包含无效的Embedded SQL语句,则宏预处理器会生成编译错误。无效的SQL语句可能具有语法错误,或者引用了在编译时不存在的表或列。

&sql指令可以在括号内的任何位置包含SQL样式的注释,可以不包含SQL代码,或仅包含注释文本。如果&sql指令不包含SQL代码或仅包含注释文本,则将该指令解析为无操作,并且未定义SQLCODE变量

  NEW SQLCODE
  WRITE !,"Entering Embedded SQL"
  &sql()
  WRITE !,"Leaving Embedded SQL"
  NEW SQLCODE
  WRITE !,"Entering Embedded SQL"
  &sql(/* SELECT Name INTO :a FROM Sample.Person */)
  WRITE !,"Leaving Embedded SQL"

&sql替代语法

由于复杂的嵌入式SQL程序可能包含多个&sql指令(包括嵌套的&sql指令),因此提供了以下替代语法格式:

  • ## sql(...):此指令在功能上等同于&sql。它提供了另一种语法来使代码清晰。但是,它不能包含标记语法。
  • &sql <marker>(...)<reversemarker>:此伪指令允许指定多个&sql伪指令,并使用用户选择的标记字符或字符串标识每个伪伪指令。下一节将介绍此标记语法。

&sql标记语法

可以使用用户定义的标记语法来标识特定的&sql指令。该语法由在“&sql”和右括号之间指定的字符或字符串组成。在嵌入式SQL的结尾处,在右括号后必须立即显示此标记的相反内容。语法如下:

  &sql<marker>( SQL statement )<reverse-marker>

请注意,在&sql,标记和右括号之间不允许有空格(空格,制表符或行返回),并且在右括号和反向标记之间不允许有空格。

标记可以是单个字符或一系列字符。标记不能包含以下标点符号:

( + - / \ | * )

标记不能包含空格字符(空格,制表符或换行符)。它可能包含所有其他可打印字符和字符组合,包括Unicode字符。标记和反向标记区分大小写。

相应的反向标记必须包含与反向标记相同的字符。例如:&sqlABC(...)CBA 如果标记包含[或{字符,则反向标记必须包含相应的]或}字符。以下是有效的&sql标记和反向标记对的示例:

  &sql@@( ... )@@
  &sql[( ... )]
  &sqltest( ... )tset
  &sql[Aa{( ... )}aA]

选择标记字符或字符串时,请注意以下重要的SQL限制:SQL代码不能在代码中的任何位置(包括文字字符串和注释)包含字符序列“)<reversemarker>”。例如,如果标记“ABC,则字符串“)CBA”不能出现在嵌入式SQL代码中的任何位置。如果发生这种情况,有效标记和有效SQL代码的组合将使编译失败。因此,在选择标记字符或字符串时要格外小心,以防止发生这种冲突,这一点很重要。

嵌入式SQL和行偏移量

嵌入式SQL的存在会影响ObjectScript行偏移量,如下所示:

  • 嵌入式SQL在例程中的该点处将INT代码行的总数加(至少)2。因此,嵌入式SQL的单行计为3行,嵌入式SQL的两行计为4行,依此类推。调用其他代码的嵌入式SQL可以向INT代码添加更多行。

一个虚拟的嵌入式SQL语句,仅包含一个注释,算作2条INT代码行,如以下示例所示:&sql(/ *供将来使用* /)

  • 嵌入式SQL中的所有行都计为行偏移,包括注释和空白行。

可以使用^ROUTINE全局显示INT代码行。

0
0 272
文章 姚 鑫 · 三月 16, 2021 5m read

第十一章 SQL隐式联接(箭头语法)

InterSystems SQL提供了一个特殊的–>运算符,作为从相关表中获取值的快捷方式,而在某些常见情况下无需指定显式的JOIN即可。可以使用此箭头语法代替显式联接语法,也可以将其与显式联接语法结合使用。箭头语法执行左外部联接。

箭头语法可用于类的属性或父表的关系属性的引用。其他类型的关系和外键不支持箭头语法。不能在ON子句中使用箭头语法(–>)。

属性引用

可以使用- >操作符作为从“引用表”获取值的简写。 例如,假设定义了两个类:Company:

Class Sample.Company Extends %Persistent [DdlAllowed]
{
/// The Company name
Property Name As %String;
}

Employee:

Class Sample.Employee Extends %Persistent [DdlAllowed]
{
/// The Employee name
Property Name As %String;

/// The Company this Employee works for
Property Company As Company;
}

Employee类包含一个属性,该属性是对Company对象的引用。 在基于对象的应用程序中,可以使用点语法遵循此引用。 例如,要查找Employee工作的Company名称:

 Set name = employee.Company.Name

可以使用使用外部连接来连接EmployeeCompany表的SQL语句来执行相同的任务:

SELECT Sample.Employee.Name, Sample.Company.Name AS CompName
FROM Sample.Employee LEFT OUTER JOIN Sample.Company
ON Sample.Employee.Company = Sample.Company.ID

image

使用- >操作符,可以更简洁地执行相同的外连接操作:

SELECT Name, Company->Name AS CompName
FROM Sample.Employee

image

只要在表中有引用列,就可以使用–>运算符;也就是说,其列的值是被引用表的ID(本质上是外键的特殊情况)。在这种情况下,Sample.EmployeeCompany字段包含Sample.Company表中记录的ID。可以在可以在查询中使用列表达式的任何地方使用–>运算符。例如,在WHERE子句中:

SELECT Name,Company AS CompID,Company->Name AS CompName
FROM Sample.Employee
WHERE Company->Name %STARTSWITH 'G'

image

使用–>运算符,可以更简洁地执行相同的OUTER JOIN操作:

这等效于:

SELECT E.Name,E.Company AS CompID,C.Name AS CompName
FROM Sample.Employee AS E, Sample.Company AS C
WHERE E.Company = C.ID AND C.Name %STARTSWITH 'G'

请注意,在这种情况下,此等效查询使用INNER JOIN

以下示例使用箭头语法访问Sample.Person中的“Spouse”字段。如示例所示,Sample.Employee中的Spouse字段包含Sample.Person中记录的ID。本示例返回Employee与其SpouseHome_State相同的Home_StateOffice_State的那些记录:

SELECT Name,Spouse,Home_State,Office_State,Spouse->Home_State AS SpouseState
FROM Sample.Employee
WHERE Home_State=Spouse->Home_State OR Office_State=Spouse->Home_State

image

可以在GROUP BY子句中使用–>运算符:

SELECT Name,Company->Name AS CompName
FROM Sample.Employee
GROUP BY Company->Name

image

可以在ORDER BY子句中使用–>运算符:

SELECT Name,Company->Name AS CompName
FROM Sample.Employee
ORDER BY Company->Name

image

或在ORDER BY子句中为–>运算符列引用列别名:

SELECT Name,Company->Name AS CompName
FROM Sample.Employee
ORDER BY CompName

支持复合箭头语法,如以下示例所示。在此示例中,Cinema.Review表包含“Film”字段,其中包含Cinema.Film表的行IDCinema.Film表包含Category字段,其中包含Cinema.Category表的行ID。因此,Film-> Category-> CategoryName访问以下三个表,以返回具有ReviewScore的每部电影的CategoryName

SELECT ReviewScore,Film,Film->Title,Film->Category,Film->Category->CategoryName
FROM Cinema.Review
ORDER BY ReviewScore

子表引用

可以使用–>运算符来引用子表。例如,如果LineItemsOrders表的子表,则可以指定:

SELECT LineItems->amount
FROM Orders

请注意,在Orders中没有称为LineItems的属性。 LineItems是包含数量字段的子表的名称。该查询在结果集中为每个Order行生成多个行。它等效于:

SELECT L.amount
FROM Orders O LEFT JOIN LineItems L ON O.id=L.custorder

其中ustustLineItems表的父引用字段。

箭头语法权限

使用箭头语法时,必须对两个表中的引用数据都具有SELECT权限。必须在被引用的列上具有表级SELECT权限或列级SELECT权限。使用列级权限,需要对被引用表以及被引用列的ID具有SELECT权限。

以下示例演示了所需的列级权限:

SELECT Name,Company->Name AS CompanyName
FROM Sample.Employee
GROUP BY Company->Name
ORDER BY Company->Name

在上面的示例中,必须对Sample.Employee.NameSample.Company.NameSample.Company.ID具有列级SELECT权限:

// d ##class(PHA.TEST.SQL).arrow()
ClassMethod arrow()
{
	SET tStatement = ##class(%SQL.Statement).%New()
	SET privchk1="%CHECKPRIV SELECT (Name,ID) ON Sample.Company"
	SET privchk2="%CHECKPRIV SELECT (Name) ON Sample.Employee"
CompanyPrivTest
	SET qStatus = tStatement.%Prepare(privchk1)
	IF qStatus'=1 {
		WRITE "%Prepare 失败:" 
		DO $System.Status.DisplayError(qStatus) 
		QUIT
	}
	SET rset = tStatement.%Execute()
	IF rset.%SQLCODE=0 {
		WRITE !,"拥有Company权限",! 
	} ELSE {  
		WRITE !,"无权限: SQLCODE=",rset.%SQLCODE,! 
	}
EmployeePrivTest
	SET qStatus = tStatement.%Prepare(privchk2)
	IF qStatus'=1 {
		WRITE "%Prepare 失败:" 
		DO $System.Status.DisplayError(qStatus) 
		QUIT
	}
	SET rset = tStatement.%Execute()
	IF rset.%SQLCODE=0 {
		WRITE !,"拥有Employee权限",! 
	} ELSE {
		WRITE !,"无权限: SQLCODE=",rset.%SQLCODE 
	}
}
DHC-APP>d ##class(PHA.TEST.SQL).arrow()
 
拥有Company权限
 
拥有Employee权限
 
0
0 233
文章 姚 鑫 · 三月 15, 2021 6m read

第十章 SQL排序(二)

查询排序

InterSystems SQL提供了排序规则功能,可用于更改字段的排序规则或显示。

第十章 SQL排序(二)

查询排序

InterSystems SQL提供了排序规则功能,可用于更改字段的排序规则或显示。

查询明细排序

将排序功能应用于查询选择项会更改该项目的显示。

  • 字母大小写:默认情况下,查询显示带有大写和小写字母的字符串。例外情况是对排序规则类型SQLUPPER的字段进行DISTINCTGROUP BY操作。这些操作以所有大写字母显示该字段。可以使用%EXACT排序功能来反转此字母大小写转换,并以大写和小写字母显示该字段。不应在选择项列表中使用%SQLUPPER排序规则函数以所有大写字母显示字段。这是因为%SQLUPPER在字符串的长度上添加了一个空格字符。请改用UPPER函数:
SELECT TOP 5 Name,$LENGTH(Name) AS NLen,
             %SQLUPPER(Name) AS UpCollN,$LENGTH(%SQLUPPER(Name)) AS UpCollLen,
             UPPER(Name) AS UpN,$LENGTH(UPPER(Name)) AS UpLen
FROM Sample.Person

image

  • 字符串截断:可以使用%TRUNCATE排序函数来限制显示的字符串数据的长度。 %TRUNCATE%SQLUPPER更可取,后者会在字符串的长度上添加一个空格字符。
SELECT TOP 5 Name,$LENGTH(Name) AS NLen,
             %TRUNCATE(Name,8) AS TruncN,$LENGTH(%TRUNCATE(Name,8)) AS TruncLen
FROM Sample.Person

image

请注意,不能嵌套排序规则函数或大小写转换函数。

  • WHERE子句比较:大多数WHERE子句谓词条件比较使用字段/属性的排序规则类型。因为字符串字段默认为SQLUPPER,所以这些比较通常不区分大小写。可以使用%EXACT排序规则功能使它们区分大小写:

下面的示例返回Home_City字符串匹配项,无论字母大小写如何:

SELECT Home_City FROM Sample.Person WHERE Home_City = 'albany'

image

以下示例返回区分大小写的Home_City字符串匹配:

SELECT Home_City FROM Sample.Person WHERE %EXACT(Home_City) = 'albany'

image

SQL Follows运算符(])使用字段/属性归类类型。

但是,无论字段/属性的排序规则类型如何,SQL Contains运算符([)都使用EXACT排序规则:

SELECT Home_City FROM Sample.Person WHERE Home_City [ 'c' 
ORDER BY Home_City

image

%MATCHES%PATTERN谓词条件使用EXACT排序规则,而不管字段/属性的排序规则类型如何。 %PATTERN谓词提供区分大小写的通配符和不区分大小写的通配符('A')。

ORDER BY子句:ORDER BY子句使用名称空间默认排序规则对字符串值进行排序。因此,ORDER BY不会基于字母大小写进行排序。可以使用%EXACT排序规则根据字母大小写对字符串进行排序。

DISTINCTGROUP BY排序规则

默认情况下,这些操作使用当前的名称空间排序。默认的名称空间排序规则是SQLUPPER

  • DISTINCTDISTINCT关键字使用名称空间默认排序规则来消除重复值。因此,DISTINCT Name返回所有大写字母的值。可以使用EXACT排序规则返回大小写混合的值。 DISTINCT消除仅字母大小写不同的重复项。要保留大小写不同的重复项,但要消除确切的重复项,请使用EXACT排序规则。 以下示例消除了精确的重复项(但不消除字母大写的变体),并以混合的大写和小写形式返回所有值:
SELECT DISTINCT %EXACT(Name) FROM Sample.Person

image

UNION涉及隐式DISTINCT操作。

  • GROUP BYGROUP BY子句使用名称空间默认排序规则来消除重复的值。因此,GROUP BY Name返回所有大写字母的值。可以使用EXACT排序规则返回大小写混合的值。GROUP BY消除仅字母大小写不同的重复项。若要保留大小写不同的重复项,但要消除完全相同的重复项,必须在GROUP BY子句(而不是select-item)上指定%EXACT归类函数。

下面的示例返回大小写混合的值; GROUP BY消除重复项,包括字母大小写不同的重复项:

SELECT %EXACT(Name) FROM Sample.Person GROUP BY Name

image

下面的示例返回大小写混合的值; GROUP BY消除了精确的重复项(但不消除字母大写的变体):

SELECT Name FROM Sample.Person GROUP BY %EXACT(Name)

image

旧版排序类型

InterSystems SQL支持多种旧式排序规则类型。它们已被弃用,不建议与新代码一起使用,因为它们的目的是为遗留系统提供持续的支持。他们是:

  • %ALPHAUP — 除去问号(“?”)和逗号(“,”)之外的所有标点符号,并将所有小写字母转换为大写字母。主要用于映射旧全局变量。由SQLUPPER代替。
  • %STRING —将逻辑值转换为大写,去除所有标点符号和空格(逗号除外),并在字符串的开头添加一个前导空格。它将所有仅包含空格(空格,制表符等)的值作为SQL空字符串进行整理。由SQLUPPER代替。
  • %UPPER —将所有小写字母转换为大写字母。主要用于映射旧全局变量。由SQLUPPER代替。
  • SPACESPACE排序将单个前导空格附加到一个值,强制将其作为字符串求值。要建立SPACE排序规则,CREATE TABLE提供一个SPACE排序规则关键字,而ObjectScript在%SYSTEM.Util类的Collation()方法中提供一个SPACE选项。没有相应的SQL排序规则功能。

注意:如果使用EXACTUPPERALPHAUP排序定义了字符串数据类型字段,并且查询在此字段上应用了%STARTSWITH条件,则可能导致不一致的行为。如果指定给%STARTSWITH的子字符串是规范数字(尤其是负数和/或小数),则%STARTSWITH可能会根据字段是否被索引而给出不同的结果。如果未对列进行索引,则%STARTSWITH应该会按预期执行。如果该列已建立索引,则可能会发生意外的结果。

SQL和NLS排序

上面描述的SQL排序规则不应与InterSystems IRIS NLS排序规则功能混淆,后者提供符合特定本国语言排序规则要求的下标级别编码。这是提供分页的两个独立系统,它们在产品的不同级别上工作。

InterSystems IRIS NLS排序可以具有当前过程的过程级别排序,并且可以具有特定全局变量的不同排序。

为了确保使用InterSystems SQL时的正常运行,要求进程级NLS排序规则与所涉及的所有全局变量的NLS排序规则完全匹配,包括表所使用的全局变量以及用于临时文件(例如进程专用全局变量和IRIS TEMP)的全局变量。否则,查询处理器设计的不同处理计划可能会得出不同的结果。在发生排序的情况下,例如ORDER BY子句或范围条件,查询处理器将选择最有效的排序策略。它可以使用索引,可以在进程专用的全局文件中使用临时文件,可以在本地数组中排序,也可以使用“]]”(之后排序)比较。所有这些都是下标类型的比较,遵循有效的InterSystems IRIS NLS归类,这就是为什么所有这些类型的全局变量都必须使用完全相同的NLS归类的原因。

系统使用数据库默认排序规则创建全局变量。可以使用%Library.GlobalEdit类的Create()方法来创建具有不同排序规则的全局变量。唯一的要求是指定的归类可以是内置的(例如InterSystems IRIS标准),也可以是当前语言环境中可用的国家归类之一。

0
0 440
文章 姚 鑫 · 三月 14, 2021 7m read

第十章 SQL排序

排序规则指定值的排序和比较方式,并且是InterSystems SQL和InterSystemsIRIS®数据平台对象的一部分。有两种基本排序规则:数字和字符串。

  • 数值排序规则按以下顺序基于完整数字对数字进行排序:null,然后是负数,从最大到最小,零,然后是正数,从最小到最大。这将创建如下序列:–210,–185,–54,–34,-.02、0、1、2、10、17、100、120
  • 字符串归类通过对每个顺序字符进行归类来对字符串进行排序。这将创建以下顺序:null,A,AA,AA,AAA,AAB,AB,B。对于数字,这将创建以下顺序:–.02,–185,–210,–34,–54 ,0、1、10、100、120、17、2

默认的字符串排序规则是SQLUPPER;为每个名称空间设置此默认值。 SQLUPPER排序规则将所有字母都转换为大写(出于排序的目的),并在字符串的开头附加一个空格字符。此转换仅用于整理目的;在InterSystems中,无论所应用的排序规则如何,SQL字符串通常以大写和小写字母显示,并且字符串的长度不包括附加的空格字符。

时间戳记是一个字符串,因此遵循当前的字符串排序规则。但是,由于时间戳是ODBC格式,因此如果指定了前导零,则字符串排序规则与时间顺序相同。

  • 字符串表达式(例如使用标量字符串函数LEFTSUBSTR的表达式)使其结果归类为EXACT
  • 两个文字的任何比较都使用EXACT归类。

可以使用“ObjectScript排序后”运算符来确定两个值的相对排序顺序。

可以按以下方式指定排序规则:

  • 命名空间默认值
  • 表字段/属性定义
  • 索引定义查询
  • SELECT
  • 查询DISTINCTGROUP BY子句

排序类型

排序规则可以在字段/属性的定义或索引的定义中指定为关键字。

可以通过对查询子句中的字段名应用排序规则函数来指定排序规则。 在指定排序函数时必须使用%前缀。

排序规则采用升序的ASCII/Unicode序列,具有以下转换:

  • EXACT - 强制字符串数据区分大小写。 如果字符串数据包含规范数字格式的值(例如123-.57),则不建议使用。
  • SQLSTRING - 去除末尾的空格(空格、制表符等),并在字符串的开头添加一个前导空格。 它将任何只包含空格(空格、制表符等)的值作为SQL空字符串进行排序。 SQLSTRING支持可选的maxlen整数值。
  • SQLUPPER - 将所有字母字符转换为大写,去除末尾的空格(空格、制表符等),然后在字符串的开头添加一个前导空格字符。 附加这个空格字符的原因是为了强制将数值作为字符串进行整理(因为空格字符不是有效的数字字符)。 这种转换还导致SQL将SQL空字符串(")值和任何只包含空格(空格、制表符等)的值作为单个空格字符进行整理。 SQLUPPER支持可选的maxlen整数值。 注意,SQLUPPER转换与SQL函数UPPER的结果不同。
  • TRUNCATE —增强字符串数据的区分大小写,并且(与EXACT不同)允许指定截断该值的长度。当索引比下标支持的数据长的精确数据时,此功能很有用。它采用%TRUNCATE(string,n)形式的正整数参数将字符串截断为前n个字符,从而改善了对长字符串的索引和排序。如果未为TRUNCATE指定长度,则其行为与EXACT相同;同时支持此行为。如果仅在定义了长度的情况下使用TRUNCATE而在没有定义长度的情况下使用EXACT,则定义和代码可能更易于维护。
  • PLUS —使值成为数字。非数字字符串值将返回0。
  • MINUS — 使数值成为数字并更改其符号。非数字字符串值将返回0。

注意:还有多种传统排序规则类型,不建议使用。

在SQL查询中,可以指定不带括号%SQLUPPER Name或带括号%SQLUPPER(Name)的排序规则函数。如果排序规则函数指定了截断,则必须使用括号%SQLUPPER(Name,10)

三种排序规则类型:SQLSTRINGSQLUPPERTRUNCATE支持可选的maxlen整数值。如果指定,maxlen会将字符串的分析截断为前n个字符。在对长字符串进行索引和排序时,可以使用它来提高性能。可以在查询中使用maxlen进行排序,分组或返回截断的字符串值。

还可以使用 %SYSTEM.Util.Collation()方法执行排序规则类型转换。

命名空间范围的默认排序规则

每个名称空间都有一个当前的字符串排序规则设置。此字符串排序规则是为%Library.String中的数据类型定义的。默认值为SQLUPPER。此默认值可以更改。

可以基于每个命名空间定义排序规则默认值。默认情况下,名称空间没有分配的排序规则,这意味着它们使用SQLUPPER排序规则。可以为命名空间分配其他默认排序规则。此名称空间默认排序规则适用于所有进程,并且在InterSystems上保持不变,IRIS会重新启动,直到明确重置为止。

/// d ##class(PHA.TEST.SQL).Collation()
ClassMethod Collation()
{
	SET stat=$$GetEnvironment^%apiOBJ("collation","%Library.String",.collval)
	WRITE "初始排序 ",$NAMESPACE,!
	ZWRITE collval
SetNamespaceCollation
	DO SetEnvironment^%apiOBJ("collation","%Library.String","SQLstring")
	SET stat=$$GetEnvironment^%apiOBJ("collation","%Library.String",.collnew)
	WRITE "user-assigned排序为 ",$NAMESPACE,!
	ZWRITE collnew
ResetCollationDefault
	DO SetEnvironment^%apiOBJ("collation","%Library.String",.collval)
	SET stat=$$GetEnvironment^%apiOBJ("collation","%Library.String",.collreset)
	WRITE "恢复排序规则的默认值 ",$NAMESPACE,!
	ZWRITE collreset
}

DHC-APP>d ##class(PHA.TEST.SQL).Collation()
初始排序 DHC-APP
user-assigned排序为 DHC-APP
collnew="SQLstring"
恢复排序规则的默认值 DHC-APP

注意,如果从未设置名称空间排序的默认值,那么$$GetEnvironment将返回一个未定义的排序变量,例如本例中的.collval。 这个未定义的排序规则默认为SQLUPPER

注意:如果数据包含德语文本,大写排序规则可能不是理想的默认设置。 这是因为德语eszett字符($CHAR(223))只有小写形式。 相当于大写的是两个字母“SS”。 转换为大写的SQL排序规则不会转换eszett,eszett保持为单个小写字母不变。

表字段/属性定义排序

在SQL中,排序规则可以分配为字段/属性定义的一部分。字段使用的数据类型确定其默认排序规则。字符串数据类型的默认排序规则为SQLUPPER。非字符串数据类型不支持排序规则分配。

可以在CREATE TABLEALTER TABLE中为字段指定排序规则:

CREATE TABLE Sample.MyNames (
    LastName CHAR(30),
    FirstName CHAR(30) COLLATE SQLstring)

注意:使用CREATE TABLE``和ALTER TABLE为字段指定排序规则时,前缀是可选的:COLLATE SQLstringCOLLATE %SQLstring

在使用持久类定义定义表时,可以为属性指定排序规则:

Class Sample.MyNames Extends %Persistent [DdlAllowed]
{
Property LastName As %String;
Property FirstName As %String(COLLATION = "SQLstring");
}

注意:在为类定义和类方法指定排序规则时,请勿将前缀用于排序规则类型名称。

在这些示例中,LastName字段采用默认排序规则(SQLUPPER,不区分大小写),FirstName字段使用区分大小写的SQLSTRING排序规则进行定义。

如果更改类属性的排序规则,并且已经存储了该类的数据,则该属性上的所有索引都将变为无效。必须基于此属性重建所有索引。

索引定义排序

CREATE INDEX命令无法指定索引排序规则类型。索引使用与要索引的字段相同的排序规则。

定义为类定义一部分的索引可以指定排序规则类型。默认情况下,给定一个或多个给定属性的索引使用属性数据的排序规则类型。例如,假设已定义类型为%String的属性Name

Class MyApp.Person Extends %Persistent [DdlAllowed]
{
Property Name As %String;
Index NameIDX On Name;
}

名称的排序规则为SQLUPPER%String的默认值)。假设“Person”表包含以下数据:

IDName
1Jones
2JOHNSON
3Smith
4jones
5SMITH

然后,Name上的索引将包含以下条目:

NameID(s)
JOHNSON2
JONES1, 4
SMITH3, 5

SQL引擎可以将此索引直接用于ORDER BY或使用“Name”字段进行比较操作。

可以通过在索引定义中添加一个As子句来覆盖用于索引的默认排序规则:

Class MyApp.Person Extends %Persistent [DdlAllowed]
{
Property Name As %String;
Index NameIDX On Name As SQLstring;
}

在这种情况下,NameIDX索引现在将以SQLSTRING(区分大小写)的形式存储值。使用上面示例中的数据:

NameID(s)
JOHNSON2
Jones1
jones4
SMITH5
Smith3

在这种情况下,对于需要区分大小写排序规则的任何查询,SQL Engine都可以利用此索引。

通常,不必更改索引的排序规则。如果要使用其他排序规则,最好在属性级别定义它,然后让属性上的所有索引都采用正确的排序规则。

如果使用索引属性执行属性比较,则在比较中指定的属性应与相应索引具有相同的排序规则类型。例如,SELECTWHERE子句或JOINON子句中的Name属性应与为Name属性定义的索引具有相同的排序规则。如果属性归类和索引归类之间不匹配,则索引可能无效或根本不使用。

如果将索引定义为使用多个属性,则可以分别指定每个索引的排序规则:

Index MyIDX On (Name As SQLstring, Code As Exact);
0
0 496
文章 姚 鑫 · 三月 13, 2021 9m read

第九章 SQL查询数据库(二)

调用用户定义函数的查询

InterSystems SQL允许您在SQL查询中调用类方法。这为扩展SQL语法提供了强大的机制。

若要创建用户定义的函数,请在持久性InterSystems IRIS类中定义一个类方法。该方法必须具有文字(非对象)返回值。这必须是一个类方法,因为在SQL查询中将没有对象实例可以在其上调用实例方法。还必须将其定义为SQL存储过程。

例如,我们可以在MyApp.Person类中定义一个Cube()方法:

Class MyApp.Person Extends %Persistent [DdlAllowed]
{
/// Find the Cube of a number
ClassMethod Cube(val As %Integer) As %Integer [SqlProc]
  {
    RETURN val * val * val
  }
}

可以使用CREATE FUNCTIONCREATE METHODCREATE PROCEDURE语句创建SQL函数。

要调用SQL函数,请指定SQL过程的名称。可以在可能指定标量表达式的任何地方以SQL代码调用SQL函数。函数名称可以使用其架构名称进行限定,也可以不限定。不合格的函数名称采用用户提供的模式搜索路径或系统范围内的默认模式名称。函数名称可以是定界标识符。

SQL函数必须具有用括号括起来的参数列表。参数列表可以为空,但括号是强制性的。所有指定的参数均充当输入参数。不支持输出参数。

SQL函数必须返回一个值。

例如,以下SQL查询将用户定义的SQL函数作为方法调用,就像它是内置SQL函数一样:

SELECT %ID, Age, Sample.Person_Cube(Age) FROM Sample.Person

image

对于Age的每个值,此查询将调用Cube()方法并将其返回值放入结果中。

SQL函数可能是嵌套的。

如果找不到指定的功能,则InterSystems IRIS会发出SQLCODE -359错误。如果指定的函数名称不明确,则InterSystems IRIS会发出SQLCODE -358错误。

查询串行对象属性

使用默认存储(%Storage.Persistent)从类中映射为SQL的子表的串行对象属性也将在该类映射表中的单个列中映射。该列的值是串行对象属性的序列化值。该单列属性被映射为SQL %List字段。

例如,Sample.Person中的Home列定义为Property Home As Sample.Address;。它将映射到类Sample.Address扩展(%SerialObject),其中包含属性StreetCityStatePostalCode。 以下示例从各个串行对象列返回值:

SELECT TOP 4 Name,Home_Street,Home_City,Home_State,Home_PostalCode
FROM Sample.Person

以下示例将所有串行对象列的值(按顺序)作为单个%List格式字符串返回,并将每一列的值作为%List的元素:

SELECT TOP 4 Name,$LISTTOSTRING(Home,'^'),$length(Name)
FROM Sample.Person

image

默认情况下,此“主页”列是隐藏的,并且不映射为Sample.Person的列。

查询集合

可以从SQL WHERE子句引用集合,如下所示:

 WHERE FOR SOME %ELEMENT(collectionRef) [AS label] (predicate)

FOR SOME%ELEMENT子句可用于指定STORAGEDEFAULT =“ list”的列表集合和数组。谓词可以包含对伪列%KEY%VALUE或两者的引用。一些示例应有助于阐明如何使用FOR SOME%ELEMENT子句。以下返回其最喜欢的颜色包括“红色”的每个人的名字和最喜欢的颜色的列表。

SELECT Name,FavoriteColors FROM Sample.Person
    WHERE FOR SOME %ELEMENT(FavoriteColors) (%Value = 'Red')

image

任何SQL谓词都可能出现在%Value(或%Key)之后,因此例如以下也是合法语法:

SELECT Name,FavoriteColors FROM Sample.Person
    WHERE FOR SOME %ELEMENT(Sample.Person.FavoriteColors)
        (%Value IN ('Red', 'Blue', 'Green'))

image

列表集合被认为是具有连续数字键1、2等的数组集合的特例。数组集合可以具有任意的非空键:

 FOR SOME (children) (%Key = 'betty' AND %Value > 5)

除了内置列表和数组集合类型之外,还可以通过为任何属性提供BuildValueArray()类方法来创建通用集合。 BuildValueArray()类方法将属性的值转换为本地数组,其中数组的每个下标是一个%KEY,该值是对应的%VALUE

除了可以在%KEY%VALUE上进行简单选择之外,还可以在逻辑上连接两个集合,如以下示例所示:

   FOR SOME %ELEMENT(flavors) AS f
      (f.%VALUE IN ('Chocolate', 'Vanilla') AND
       FOR SOME %ELEMENT(toppings) AS t
           (t.%VALUE = 'Butterscotch' AND
            f.%KEY = t.%KEY))

此示例有两个集合:FavorsTOPING,这两个集合通过键在位置上相关。该查询限定了将巧克力香草指定为口味元素的行,并且还将奶油糖果列为相应的配料,其中通过%key建立对应关系。

可以使用$SYSTEM.SQL配置方法GetCollectionProjection()SetCollectionProjection()来确定如果将集合映射为子表,则是否将集合映射为列。在编译或重新编译该类时,对该系统范围的设置所做的更改将对每个类生效。

使用说明和限制

  • FOR SOME%ELEMENT只能出现在WHERE子句中。
  • %KEY/%VALUE只能出现在FOR谓词中。
  • 任何特定的%KEY或%VALUE只能被引用一次。
  • %KEY%VALUE可能不会出现在外部联接中。
  • %KEY%VALUE可能不会出现在值表达式中(仅在谓词中)。

调用文本搜索的查询

InterSystems IRIS支持所谓的“自由文本搜索”,包括支持:

  • 通配符
  • 填充物
  • 多词搜索(也称为n-gram)
  • 自动分类
  • 词典管理

此功能使SQL能够支持全文索引,还使SQL能够索引和引用集合的单个元素,而无需将集合属性映射为子表。虽然支持集合索引和全文索引的底层机制密切相关,但文本检索具有许多特殊属性,因此为文本检索提供了特殊的类和SQL功能。

伪字段

InterSystems SQL查询支持以下伪字段值:

  • %ID —返回RowID字段值,而不管RowID字段的实际名称是什么。
  • %TABLENAME —返回在FROM子句中指定的现有表的限定名称。定义表时,使用限定的字母大小写返回合格的表名,而不是FROM子句中指定的字母大小写。如果FROM子句指定了不合格的表名,则%TABLENAME将返回合格的表名(schema.table),以及从用户提供的模式搜索路径或系统范围内的默认模式名称提供的模式名称。例如,如果FROM子句指定mytable,则%TABLENAME变量可能返回SQLUser.MyTable
  • %CLASSNAME —返回与FROM子句中指定的现有表相对应的合格类名称(package.class)。例如,如果FROM子句指定了SQLUser.mytable,则%CLASSNAME变量可能返回User.MyTable

注意:请勿将%CLASSNAME伪字段值与%ClassName()实例方法混淆。它们返回不同的值。

伪字段变量只能为包含数据的表返回。

如果在FROM子句中指定了多个表,则必须使用表别名,如以下嵌入式SQL示例所示:

/// d ##class(PHA.TEST.SQL).Query3()
ClassMethod Query3(val As %Integer) As %Integer [ SqlProc ]
{
	&sql(SELECT P.Name,P.%ID,P.%TABLENAME,E.%TABLENAME 
		INTO :name,:rid,:ptname,:etname
		FROM Sample.Person AS P,Sample.Employee AS E)
	IF SQLCODE<0 {
		WRITE "SQLCODE error ",SQLCODE," ",%msg  QUIT
	} ELSEIF SQLCODE=100 {
		WRITE "Query returns no results"  QUIT
	}
	WRITE ptname,"Person table Name is: ",name,!
	WRITE ptname,"Person table RowId is: ",rid,!
	WRITE "P alias TableName is: ",ptname,!
	WRITE "E alias TableName is: ",etname,!
}
DHC-APP>d ##class(PHA.TEST.SQL).Query3()
Sample.PersonPerson table Name is: Adams,Diane F.
Sample.PersonPerson table RowId is: 95
P alias TableName is: Sample.Person
E alias TableName is: Sample.Employee

%TABLE NAME%CLASS NAME列分配了默认的列名称Literal N,其中nSELECT语句中伪字段变量的select-item位置。

查询元数据

可以使用Dynamic SQL返回有关查询的元数据,例如查询中指定的列数,查询中指定的列的名称(或别名)以及查询中指定的列的数据类型。

下面的ObjectScript Dynamic SQL示例为Sample.Person中的所有列返回列名和该列的ODBC数据类型的整数代码:

/// d ##class(PHA.TEST.SQL).Query4()
ClassMethod Query4()
{
	SET myquery="SELECT * FROM Sample.Person"
	SET rset = ##class(%SQL.Statement).%New()
	SET qStatus = rset.%Prepare(myquery)
	IF qStatus'=1 {
		WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT
	}
	SET x=rset.%Metadata.columns.Count()
	WHILE x>0 {
		SET column=rset.%Metadata.columns.GetAt(x)
		WRITE !,x," ",column.colName," ",column.ODBCType
		SET x=x-1 
	}
	WRITE !,"end of columns"
}
DHC-APP>d ##class(PHA.TEST.SQL).Query4()
 
15 Office_Zip 12
14 Office_Street 12
13 Office_State 12
12 Office_City 12
11 Home_Zip 12
10 Home_Street 12
9 Home_State 12
8 Home_City 12
7 Spouse 4
6 SSN 12
5 Name 12
4 FavoriteColors 12
3 DOB 9
2 Age 4
1 ID 4
end of columns

在此示例中,列以反向列顺序列出。请注意,包含列表结构化数据的FavoriteColors列返回的数据类型为12(VARCHAR),因为ODBC将InterSystems IRIS列表数据类型值表示为以逗号分隔的值的字符串。

快速查询

InterSystems IRIS支持快速选择,这是一种内部优化,用于通过ODBC和JDBC快速执行查询。此优化将InterSystems全局变量映射到Java对象。它将全局节点(数据记录)的内容作为Java对象传递。收到这些Java对象后,它将从它们中提取所需的列值并生成结果集。 InterSystems IRIS会尽可能自动应用此优化。这种优化是自动的,用户看不见。当准备好查询时,InterSystems IRIS会将查询标记为使用快速选择机制执行还是使用标准查询机制执行。

如果查询仅引用字段,常量或引用字段和/或常量的表达式,则快速选择将应用于%PARALLEL查询和针对分片表的查询。

服务器和客户端都必须支持快速选择。要在客户端中启用或禁用“快速选择”,请在类实例的定义中使用“属性”,如下所示:

Properties p = new Properties();
p.setProperty("FeatureOption","3");   / 1 is fast Select, 2 is fast Insert, 3 is both

由于性能上的差异,对于用户来说重要的是要知道什么情况限制了快速选择的应用。

表限制:无法使用快速选择来查询以下类型的表:

  • 链接表
  • 一个表,其主/数据映射具有多个节点
  • 具有映射到同一数据位置的多个字段的表(仅可使用%Storage.SQL来实现)

字段限制:如果选择项列表中包含以下列,则无法使用“快速选择”执行查询。这些类型的列可以在表中定义,但是查询无法选择它们:

  • 流字段(数据类型%Stream.GlobalCharacter%Stream.GlobalBinary
  • 查询时计算的字段(计算的COMPUTECODE或瞬态)
  • 是列表集合的字段(具有LogicalToOdbc转换)
  • 一个执行LogicalToOdbc转换并且不是数据类型%Date%Time或%PosixTime`的字段
  • 覆盖了LogicalToOdbc转换代码的字段
  • 执行LogicalToStorage转换的字段
  • 地图数据输入使用检索码的字段
  • 一个其地图数据条目具有定界符的字段(不是%List存储)
  • 映射到一块嵌套存储的字段

索引限制:如果选择项目列表仅由%ID字段和/或均映射到同一索引的字段组成,则不使用快速选择。

如果使用“快速选择”执行查询,则在启用了%System /%SQL / XDBCStatement的情况下,在审计数据库的SELECT审计事件中会标记此事实。

查询和企业缓存协议Enterprise Cache Protocol (ECP)

使用企业缓存协议(ECP)的InterSystems IRIS实现(例如分布式缓存群集)可以同步查询结果。 ECP是一种分布式数据缓存体系结构,用于管理服务器系统的异构网络之间的数据分布和锁定。

如果ECP同步处于活动状态,则每次执行SELECT语句时,InterSystems IRIS都会将所有未决的ECP请求强制发送到数据服务器。完成后,这可以确保客户端缓存是同步的。此同步发生在查询的“打开”逻辑中。如果这是游标查询,则在OPEN游标执行中。

要激活ECP同步,请使用%SYSTEM.SQL类的SetECPSync()方法。若要确定当前设置,请调用$SYSTEM.SQL.CurrentSettings()

0
0 508
文章 姚 鑫 · 三月 12, 2021 5m read

第九章 SQL查询数据库

查询类型

查询是执行数据检索并生成结果集的语句。查询可以包含以下任意项:

  • 一个简单的SELECT语句,用于访问指定表或视图中的数据。
  • 具有JOIN语法的SELECT语句,用于访问多个表或视图中的数据。
  • 合并多个SELECT语句的结果的UNION语句。
  • 使用SELECT语句为封闭的SELECT查询提供单个数据项的子查询。
  • 在嵌入式SQL中,这是一个使用SQL游标通过FETCH语句访问多行数据的SELECT语句。

使用SELECT语句

SELECT语句从一个或多个表或视图中选择一行或多行数据。下面的示例显示了一个简单的SELECT

SELECT Name,DOB FROM Sample.Person WHERE Name %STARTSWITH 'A' ORDER BY DOB

image

在此的示例NameDOBSample.Person表中的列(数据字段)。

SELECT语句中必须指定子句的顺序是:SELECT DISTINCT TOP ...选择项INTO ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY。这是命令语法顺序。所有这些子句都是可选的,但SELECT选择项除外。

SELECT子句的执行顺序

可以通过注意SELECT语句的语义处理顺序(与SELECT语法顺序不同)来理解SELECT语句的操作。 SELECT的子句按以下顺序处理:

  1. FROM子句-指定一个表,一个视图,多个表或使用JOIN语法的视图或一个子查询。
  2. WHERE子句-限制使用各种条件选择的数据。
  3. GROUP BY子句—将所选数据组织为具有匹配值的子集;每个值仅返回一条记录。
  4. HAVING子句—限制使用各种条件从组中选择什么数据。
  5. select-item —从指定的表或视图中选择一个数据字段。选择项也可以是可以引用也可以不引用特定数据字段的表达式。
  6. DISTINCT子句—应用于SELECT结果集,它将返回的行限制为包含不同(非重复)值的行。
  7. ORDER BY子句—应用于SELECT结果集,它按指定字段对按排序顺序返回的行进行排序。

这种语义顺序表明,所有子句都可以识别表别名(在FROM子句中定义),而列别名(在SELECT选择项中定义)只能由ORDER BY子句识别。

要在其他SELECT子句中使用列别名,可以使用子查询,如以下示例所示:

SELECT Interns FROM 
      (SELECT Name AS Interns FROM Sample.Employee WHERE Age<21) 
WHERE Interns %STARTSWITH 'A'

选择字段

当发出SELECT时,InterSystems SQL会尝试将每个指定的select-item字段名称与对应于指定表的类中定义的属性进行匹配。每个类属性都有一个属性名称和一个SqlFieldName。如果使用SQL定义表,则在CREATE TABLE命令中指定的字段名称为SqlFieldName,并且InterSystems IRIS从SqlFieldName生成属性名称。

字段名称,类属性名称和SqlFieldName名称具有不同的命名约定:

  • SELECT语句中的字段名称不区分大小写。 SqlFieldName名称和属性名称区分大小写。
  • 遵循标识符命名约定,SELECT语句中的字段名称和SqlFieldName名称可以包含某些非字母数字字符。属性名称只能包含字母数字字符。生成属性名称时,InterSystems IRIS会去除非字母数字字符。 InterSystems IRIS可能必须附加一个字符以创建唯一的属性名称。

字段的这三个名称之间的转换确定了查询行为的几个方面。可以使用字母大小写的任意组合来指定选择项目的字段名称,并且InterSystems SQL将标识相应的相应属性。结果集显示中的数据列标题名称是SqlFieldName,而不是select-item中指定的字段名称。这就是为什么数据列标题的字母大小写可能与select-item字段名称不同的原因。

可以为选择项字段指定列别名。列别名可以采用字母大小写的任何混合形式,并且可以遵循标识符命名约定包含非字母数字字符。可以使用字母大小写的任意组合来引用列别名(例如,在ORDER BY子句中),并且InterSystems SQL解析为select-item字段中指定的字母大小写。 InterSystems IRIS始终尝试匹配列别名列表,然后再尝试匹配对应于已定义字段的属性列表。如果定义了列别名,则结果集显示中的数据列标题名称是指定字母大小写的列别名,而不是SqlFieldName

SELECT查询成功完成后,InterSystems SQL会为该查询生成结果集类。结果集类包含一个与每个选定字段相对应的属性。如果SELECT查询包含重复的字段名称,则系统将通过附加字符为查询中字段的每个实例生成唯一的属性名称。因此,查询中不能包含36个以上相同字段的实例。

查询的生成结果集类还包含列别名的属性。为避免字母大小写解析的性能损失,在引用列别名时应使用与在SELECT语句中指定列别名时使用的字母大小写相同的字母大小写。

除了用户指定的列别名外,InterSystems SQL还会自动为每个字段名称生成最多三个别名,这些别名与该字段名称的常见字母大小写变体相对应。这些生成的别名对用户不可见。提供它们是出于性能方面的考虑,因为通过别名访问属性比通过字母大小写转换解决字母大小写的速度更快。例如,如果SELECT指定FAMILYNAME,并且对应的属性是familyname,则InterSystems SQL使用生成的别名(FAMILYNAME AS familyname)来解析字母大小写。但是,如果SELECT指定fAmILyNaMe且对应的属性为Familyname,则InterSystems SQL必须使用较慢的字母大小写转换过程来解析字母大小写。

选择项还可以是表达式,聚合函数,子查询,用户定义的函数(如星号)或其他某个值。

JOIN操作

JOIN提供了一种将一个表中的数据链接到另一个表中的数据的方法,并且经常用于定义报告和查询。在SQL中,JOIN是一种操作,它将来自两个表的数据合并以产生第三个表,但要遵守限制性条件。结果表的每一行都必须满足限制条件。

InterSystems SQL支持五种类型的联接(有些具有多种语法形式):CROSS JOININNER JOINLEFT OUTER JOINRIGHT OUTER JOIN和FULL OUTER JOIN。外部联接通过各种条件表达式谓词和逻辑运算符支持ON子句。对NATURAL外部联接和带有USING子句的外部联接有部分支持。

如果查询包含联接,则该查询中的所有字段引用都必须具有附加的表别名。由于InterSystems IRIS在数据列标题名称中不包含表别名,因此可能希望为select-item字段提供列别名,以澄清哪个表是数据源。

以下示例使用联接操作将Sample.Person中的“fake”(随机分配)邮政编码与Sample.USZipCode中的真实邮政编码和城市名称进行匹配。之所以提供WHERE子句,是因为USZipCode不包括所有可能的5位邮政编码:

SELECT P.Home_City,P.Home_Zip AS FakeZip,Z.ZipCode,Z.City AS ZipCity,Z.State
FROM Sample.Person AS P LEFT OUTER JOIN Sample.USZipCode AS Z 
ON P.Home_Zip=Z.ZipCode
WHERE Z.ZipCode IS NOT NULL
ORDER BY P.Home_City

选择大量字段的查询

一个查询不能选择超过1,000个选择项字段。

选择超过150个选择项字段的查询可能有以下性能考虑。InterSystems IRIS自动生成结果集列别名。这些生成的别名是为没有用户定义别名的字段名提供的,以便快速解决字母大小写的变化。使用别名的字母大小写解析明显快于逐个字母大小写。但是,生成的结果集列别名的数量限制为500个。因为通常InterSystems IRIS会为每个字段生成其中的三个别名(针对三种最常见的字母大小写变化),所以系统会为查询中大约前150个指定字段生成别名。因此,引用少于150个字段的查询通常比引用更多字段的查询具有更好的结果集性能。通过在非常大的查询中为每个字段select-item指定一个精确的列别名(例如,SELECT FamilyName AS FamilyName),然后确保在按列别名引用结果集项时使用相同的字母大小写,可以避免此性能问题。

定义和执行命名查询

可以按如下方式定义和执行命名查询:

  • 使用CREATE QUERY定义查询。该查询被定义为一个存储过程,可以使用CALL执行。
  • 定义类查询(在类定义中定义的查询)。类查询被投影为存储过程。可以用CALL执行。也可以使用%SQL准备一个类查询。语句%PrepareClassQuery()方法,然后使用%Execute()方法执行。

创建查询和调用

可以使用CREATE QUERY定义查询,然后使用CALL按名称执行查询。在以下示例中,第一个是定义查询AgeQuery的SQL程序,第二个是执行查询的Dynamic SQL:

/// d ##class(PHA.TEST.SQL).Query()
ClassMethod Query()
{
	&sql(
	CREATE QUERY Sample.AgeQuery(IN topnum INT DEFAULT 10,IN minage INT 20)
		PROCEDURE
		BEGIN
		SELECT TOP :topnum Name,Age FROM Sample.Person
		WHERE Age > :minage 
		ORDER BY Age ;
		END
   )
	SET mycall = "CALL Sample.AgeQuery(11,65)"
	SET tStatement = ##class(%SQL.Statement).%New()
	SET qStatus = tStatement.%Prepare(mycall)
	IF qStatus'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT}
	SET rset = tStatement.%Execute()
	DO rset.%Display()
	&sql(DROP QUERY Sample.AgeQuery)
}
DHC-APP>d ##class(PHA.TEST.SQL).Query()
 
 
Dumping result #1
Name    Age
Ingrahm,Yan S.  66
Hertz,Uma C.    66
Zweifelhofer,Zelda J.   67
Zampitello,Josephine Q. 67
Xiang,Molly F.  67
Davis,Jane E.   67
Vanzetti,Alexandra O.   67
Solomon,Emily D.        68
Isaacs,Elvis V. 68
Alton,Phil T.   68
Yeats,Debby G.  69
 
11 Rows(s) Affected

类查询

可以在类中定义查询。该类可以是%Persistent类,但不是必须的。该类查询可以引用在同一类或在同一命名空间中的另一类中定义的数据。编译包含查询的类时,在类查询中引用的表,字段和其他数据实体必须存在。

编译包含查询的类时,不会编译该类查询。而是在第一次执行SQL代码(运行时)时进行类查询的编译。当使用%PrepareClassQuery()方法在Dynamic SQL中准备查询时,会发生这种情况。第一次执行定义了一个可执行的缓存查询。

以下类定义示例定义了一个类查询:

/// DO ##class(%ResultSet).RunQuery("PHA.TEST.SQL","MyQ","MO")
Query MyQ(Myval As %String) As %SQLQuery(CONTAINID = 1, ROWSPEC = "Name,Home_State") [ SqlProc ]
{
     SELECT Name,Home_State FROM Sample.Person 
     WHERE Home_State = :Myval  ORDER BY Name
}
DHC-APP>DO ##class(%ResultSet).RunQuery("PHA.TEST.SQL","MyQ","MO")
 
Name:Home_State:
Burroughs,Barbara H.:MO:
Emerson,Edgar T.:MO:
Frost,Xavier D.:MO:
Joyce,Elmo R.:MO:
King,Dmitry G.:MO:
Klingman,Rhonda G.:MO:
Kratzmann,Emily Z.:MO:
Martinez,Emilio G.:MO:
Schaefer,Usha G.:MO:

下面的示例执行上一示例中的Sample.QClass中定义的MyQ查询:

/// d ##class(PHA.TEST.SQL).Query1()
ClassMethod Query1()
{
	SET Myval="NY"
	SET stmt=##class(%SQL.Statement).%New()
	SET status = stmt.%PrepareClassQuery("PHA.TEST.SQL","MyQ")
	IF status'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(status) QUIT}
	SET rset = stmt.%Execute(Myval)
	DO rset.%Display()
	WRITE !,"End of data"
}
DHC-APP>d ##class(PHA.TEST.SQL).Query1()
 
 
Dumping result #1
Name    Home_State
Chadbourne,Danielle G.  NY
Eastman,Clint G.        NY
Pape,Linda M.   NY
Peterson,Janice N.      NY
Schaefer,Jocelyn V.     NY
 
5 Rows(s) Affected
End of data

以下动态SQL示例使用%SQL.Statement执行在Sample.Person类中定义的ByName查询,并传递一个字符串以将返回的名称限制为以该字符串值开头的名称:

/// d ##class(PHA.TEST.SQL).Query2()
ClassMethod Query2()
{
	SET statemt=##class(%SQL.Statement).%New()
	SET cqStatus=statemt.%PrepareClassQuery("Sample.Person","ByName")
	IF cqStatus'=1 {WRITE "%PrepareClassQuery failed:" DO $System.Status.DisplayError(cqStatus) QUIT}
	SET rs=statemt.%Execute("L")
	DO rs.%Display()
}
DHC-APP>d ##class(PHA.TEST.SQL).Query2()
 
 
Dumping result #1
ID      Name    DOB     SSN
146     LaRocca,David X.        42013   603-23-8852
54      Larson,Nataliya Z.      52896   277-65-7763
65      Lee,Zoe Z.      62253   548-60-1784
105     Leiberman,Nataliya F.   46706   624-13-9765
56      Lennon,Chelsea T.       54537   190-51-5484
106     Lennon,Imelda Z.        57911   594-60-9044
137     Lennon,Maureen M.       38392   746-77-6520
178     Lepon,Janice T. 45675   188-86-7267
29      Lepon,Jeff Z.   37144   212-43-4979
112     Lepon,Kevin N.  31575   929-85-8355
154     Lopez,Ralph W.  45541   391-39-9235
77      Love,Janice E.  33050   515-29-7228
 
12 Rows(s) Affected
0
0 326
文章 姚 鑫 · 三月 11, 2021 10m read

第八章 SQL修改数据库

可以对现有的表使用SQL语句,也可以对相应的持久化类使用ObjectScript操作来修改InterSystems IRIS®数据平台数据库的内容。 不能修改定义为只读的持久类(表)。

使用SQL命令为维护数据的完整性提供了自动支持。 SQL命令是一个原子操作(全部或没有)。 如果表上定义了索引,SQL将自动更新它们以反映更改。 如果定义了任何数据或引用完整性约束,SQL将自动执行它们。 如果有任何已定义的触发器,执行这些操作将拉动相应的触发器。

插入数据

可以使用SQL语句或设置和保存持久化类属性将数据插入表中。

使用SQL插入数据

INSERT语句将一条新记录插入SQL表中。 可以插入一条记录或多条记录。

下面的示例插入一条记录。 它是插入单个记录的几种可用语法形式之一:

 INSERT INTO MyApp.Person
    (Name,HairColor) 
    VALUES ('Fred Rogers','Black')

以下示例通过查询现有表中的数据插入多条记录:

 INSERT INTO MyApp.Person
    (Name,HairColor) 
     SELECT Name,Haircolor FROM Sample.Person WHERE Haircolor IS NOT NULL

还可以发出INSERTUPDATE语句。 如果SQL表中不存在新记录,则该语句将该记录插入该SQL表中。 如果记录存在,则该语句使用提供的字段值更新记录数据。

使用对象属性插入数据

可以使用ObjectScript插入一条或多条数据记录。 创建一个现有持久化类的实例,设置一个或多个属性值,然后使用%Save()插入数据记录:

下面的例子插入一条记录:

  SET oref=##class(MyApp.Person).%New()
  SET oref.Name="Fred Rogers"
  SET oref.HairColor="Black"
  DO oref.%Save()

下面的例子插入多条记录:

  SET nom=$LISTBUILD("Fred Rogers","Fred Astare","Fred Flintstone")
  SET hair=$LISTBUILD("Black","Light Brown","Dark Brown")
  FOR i=1:1:$LISTLENGTH(nom) {
      SET oref=##class(MyApp.Person).%New()
      SET oref.Name=$LIST(nom,i)
      SET oref.HairColor=$LIST(hair,i)
      SET status = oref.%Save() }

UPDATE语句

UPDATE语句修改SQL表中的一条或多条现有记录中的值:

UPDATE语句修改SQL表中的一条或多条现有记录中的值:

在插入或更新时计算字段值

在定义计算字段时,可以指定ObjectScript代码来计算该字段的数据值。 可以在插入、更新行、插入和更新行或查询行时计算此数据值。 下表显示了每种计算操作类型所需的关键字以及字段/属性定义示例:

  • 只在插入时计算
    • SQL DDL COMPUTECODE关键字Birthday VARCHAR(50) COMPUTECODE {SET {Birthday}=$PIECE($ZDATE({DOB},9),",")_" changed: "_$ZTIMESTAMP}
    • 持久化类定义SqlComputeCodeSqlComputeCode关键字属性生日为%String(MAXLEN = 50) [SqlComputeCode = {SET {Birthday}=$PIECE($ZDATE({DOB},9),",") _" changed: "_$ZTIMESTAMP}, SqlComputed];
  • 只在更新时计算
    • SQL DDL DEFAULT, COMPUTECODE,和COMPUTEONCHANGE关键字Birthday VARCHAR(50) DEFAULT ' ' COMPUTECODE {SET {Birthday}=$PIECE($ZDATE({DOB},9),",")_" changed: "_$ZTIMESTAMP} COMPUTEONCHANGE (DOB)
  • 在插入和更新上都进行计算
    • SQL DDL COMPUTECODECOMPUTEONCHANGE关键字Birthday VARCHAR(50) COMPUTECODE {SET {Birthday}=$PIECE($ZDATE({DOB},9),",")_" changed: "_$ZTIMESTAMP} COMPUTEONCHANGE (DOB)
    • 持久化类定义SqlComputeCode, SqlComputed, and SqlComputeOnChange属性关键字属性Birthday As %String(MAXLEN = 50) [SqlComputeCode = {SET {Birthday}=$PIECE($ZDATE({DOB},9),",") _" changed: "_$ZTIMESTAMP}, SqlComputed, SqlComputeOnChange = DOB];
  • 计算对查询
    • SQL DDL COMPUTECODE和计算或瞬态关键字Birthday VARCHAR(50) COMPUTECODE {SET {Birthday}=$PIECE($ZDATE({DOB},9),",")_" changed: "_$ZTIMESTAMP}计算
    • 持久类定义SqlComputeCode, SqlComputed, and calculate或瞬态属性关键字属性Birthday为%String(MAXLEN = 50) [SqlComputeCode = {SET {Birthday}=$PIECE($ZDATE({DOB},9),",") _" changed: "_$ZTIMESTAMP}, SqlComputed, calculate];

DDL DEFAULT关键字在插入时优先于计算数据值。 DEFAULT必须接受一个数据值,例如空字符串; 不能为空。 在持久类定义中,InitialExpression属性关键字在插入时不会覆盖SqlComputed数据值。

DDL COMPUTEONCHANGE关键字可以使用单个字段名,也可以使用逗号分隔的字段名列表。 这些字段名指定了哪些字段更新时会触发对该字段的计算; 列出的字段名称必须存在于表中,但它们不必出现在计算代码中。 必须指定实际的字段名; 不能指定星号语法。

在修改记录时,可以使用ON UPDATE关键字短语将字段设置为文字或系统变量(如当前时间戳),而不是使用COMPUTECODECOMPUTEONCHANGEON UPDATE短语同时修饰INSERTUPDATE; 若要只在更新时修改,请使用默认短语和更新短语。

每次查询访问该字段时,DDL计算或TRANSIENT关键字都会计算一个数据值。 该字段不需要在选择列表中指定。 例如,SELECT Name FROM MyTable WHERE LENGTH(Birthday)=36在计算条件表达式之前计算生日字段。 管理门户Open Table选项执行一个查询,因此计算计算的和临时的数据值。

计算字段限制:

  • 不更新的更新:为记录中的字段提供与它们之前的值相同的值的更新实际上并不更新记录。 如果没有对记录执行真正的更新,则不会调用COMPUTEONCHANGE。 即使没有对一条记录执行真正的更新,也会在更新操作上调用ON UPDATE。 如果希望在更新时总是重新计算已计算字段,而不管记录是否实际更新,请使用更新触发器。
  • 用户为计算字段指定的显式值:
    • INSERT:在INSERT时,您总是可以向COMPUTECODEDEFAULTOn UPDATE字段提供显式的值。 InterSystems SQL总是采用显式的值,而不是生成的值。
    • 更新COMPUTEONCHANGE:更新操作可以为COMPUTEONCHANGE字段提供显式的值。 InterSystems SQL总是采用显式的值,而不是计算的值。
    • 更新时更新:更新操作不能为ON UPDATE字段提供显式值。 InterSystems SQL忽略用户提供的值,并接受ON UPDATE生成的值。 但是,InterSystems SQL确实会对显式值执行字段验证,例如,如果提供的值大于最大数据大小,就会生成SQLCODE -104错误。
    • 计算或暂态:插入或更新操作不能为计算或暂态字段提供显式值,因为计算或暂态字段不存储数据。 但是,InterSystems SQL确实会对显式值执行字段验证,例如,如果提供的值大于最大数据大小,就会生成SQLCODE -104错误。

删除语句

DELETE语句从SQL表中删除一条或多条现有记录:

 DELETE FROM MyApp.Person
     WHERE HairColor = 'Aqua'

可以执行TRUNCATE TABLE命令删除表中的所有记录。 还可以使用delete删除表中的所有记录。 DELETE(默认情况下)提取删除触发器; TRUNCATE TABLE不拉出删除触发器。 使用DELETE删除所有记录不会重置表计数器; TRUNCATE TABLE重置这些计数器。

事务处理

事务是一系列插入、更新、删除、插入或更新以及截断表数据修改语句,它们组成单个工作单元。

SET TRANSACTION命令用于设置当前进程的事务参数。 还可以使用START TRANSACTION命令设置相同的参数。 这些事务参数在多个事务中继续有效,直到显式更改为止。

START TRANSACTION命令显式地启动事务。 这个命令通常是可选的; 如果事务%COMMITMODE是隐式或显式的,事务从第一个数据库修改操作自动开始。 如果事务%COMMITMODENONE,则必须显式指定START transaction来启动事务处理。

如果事务成功,提交其更改可以是隐式(自动)或显式的; %COMMITMODE值决定是否需要显式地使用COMMIT语句来永久地将数据修改添加到数据库并释放资源。

如果事务失败,可以使用ROLLBACK语句撤消其数据修改,这样这些数据就不会进入数据库。

注意:通过管理门户执行SQL查询接口运行SQL时,不支持SQL事务语句。 这个接口旨在作为开发SQL代码的测试环境,而不是用于修改实际数据。

事务和保存点

在InterSystems SQL中,可以执行两种事务处理:完整事务处理和使用保存点的事务处理。通过完整的事务处理,事务将从START TRANSACTION语句(显式或隐式)开始,一直持续到COMMIT语句(显式或隐式)结束事务并提交所有工作,或者ROLLBACK语句反转事务期间完成的所有工作。

通过保存点,InterSystems SQL支持事务中的级别。可以使用START TRANSACTION语句(显式或隐式)开始事务。然后,在事务期间,可以使用SAVEPOINT在程序中指定一个或多个命名保存点。可以在一个事务中最多指定255个命名保存点。添加一个保存点会增加$TLEVEL事务级别计数器。

  • COMMIT提交事务期间执行的所有工作。保存点将被忽略。
  • ROLLBACK将回滚事务期间执行的所有工作。保存点将被忽略。
  • ROLLBACK TO SAVEPOINT点名将回滚自点名指定的SAVEPOINT以来执行的所有工作,并以适当数量的保存点级别将内部事务级别计数器递减。例如,如果建立了两个保存点svpt1svpt2,然后回滚到svpt1,则ROLLBACK TO SAVEPOINTsvpt1会反转自svpt1以来所做的工作,在这种情况下,将事务级别计数器减2。

非事务操作

当事务生效时,以下操作不包括在事务中,因此无法回滚:

  • IDKey计数器增量不是事务操作。IDKey$INCREMENT(或$SEQUENCE)自动生成,它维护独立于SQL事务的计数。例如,如果插入IDKey为17、18和19的记录,然后回滚此插入,则下一条要插入的记录的IDKey将为20。
  • 缓存查询的创建、修改和清除不是事务操作。因此,如果在事务期间清除高速缓存的查询,然后回滚该事务,则在回滚操作之后,高速缓存的查询将保持清除状态(不会恢复)。
  • 事务内发生的DDL操作或调谐表操作可以创建和运行临时例程。此临时例程被视为与缓存查询相同。也就是说,临时例程的创建、编译和删除不被视为事务的一部分。临时例程的执行被认为是事务的一部分。

事务锁

事务使用锁来保护唯一的数据值。例如,如果进程删除了唯一的数据值,则该值在事务持续时间内被锁定。因此,在第一个事务完成之前,另一个进程无法使用相同的唯一数据值插入记录。这可以防止回滚导致具有唯一性约束的字段出现重复值。这些锁由INSERTUPDATEINSERTUPDATEDELETE语句自动应用,除非该语句包含%NOLOCK限制参数。

事务大小限制

除了日记文件的空间可用性外,可以在事务中指定的操作数量没有限制。锁表的大小通常不会施加限制,因为InterSystems IRIS提供自动锁升级。

每个表有1000个锁的默认锁阈值。对于当前事务,一个表可以有1000个唯一的数据值锁。第100个锁定操作在事务持续时间内将该表的锁定升级为表锁。

此锁定阈值可使用以下任一选项进行配置:

  • 调用$SYSTEM.SQL.SetLockThreshold()方法。此方法更改当前系统范围的值和配置文件设置。要确定当前的锁升级阈值,请使用$SYSTEM.SQL.GetLockThreshold()方法。
  • 转到管理门户。从系统管理中,依次选择配置、SQL和对象设置、SQL。在此屏幕上,可以查看和编辑锁定阈值的当前设置。

可以终止的子节点(子表)的数量没有限制。所有子节点终止都被记录下来,因此可以回滚。

读取未提交的数据

可以通过为发出查询的进程设置SET TRANSACTIONSTART TRANSACTION来指定读取隔离级别。

  • 提交未提交的隔离级别:对于其他用户进行查询(只读)访问,可以看到未提交的对数据的插入,更新和删除。如果未指定任何事务,则为默认设置。
  • 已验证隔离级别:可供其他用户以查询(只读)访问的方式看到未提交的对数据的插入,更新和删除。提供对查询条件所使用并由查询显示的数据的重新检查。
  • 读取已提交的隔离级别:未提交的插入和更新对数据所做的更改未显示在查询结果集中。查询结果集仅包含已提交的插入和更新。但是,未提交的删除对数据所做的更改将显示在查询结果集中。

不管当前的隔离级别如何,以下SELECT命令子句始终返回未提交的数据:聚合函数,DISTINCT子句,GROUP BY子句或带有%NOLOCK关键字的SELECT

ObjectScript事务命令

ObjectScript和SQL事务命令是完全兼容和可互换的,但以下情况除外:

如果没有当前事务,则ObjectScript TSTARTSQL START TRANSACTION都将启动一个事务。但是,START TRANSACTION不支持嵌套事务。因此,如果需要(或可能需要)嵌套事务,则最好使用TSTART启动事务。如果需要与SQL标准兼容,请使用START TRANSACTION

ObjectScript事务处理为嵌套事务提供了有限的支持。 SQL事务处理为事务中的保存点提供支持。

0
0 390
文章 姚 鑫 · 三月 10, 2021 5m read

第七章 SQL表之间的关系

要在表之间强制执行引用完整性,可以定义外键。修改包含外键约束的表时,将检查外键约束。

定义外键

有几种方法可以在InterSystems SQL中定义外键:

  • 可以定义两个类之间的关系。定义关系会自动将外键约束投影到SQL。
  • 可以在类定义中添加显式外键定义(对于关系未涵盖的情况)。
  • 可以使用CREATE TABLEALTER TABLE命令添加外键。可以使用ALTER TABLE命令删除外键。

用作外键引用的RowID字段必须是公共的。引用隐藏的RowID?有关如何使用公用(或专用)RowID字段定义表的信息。

一个表(类)的外键最大数目为400。

外键引用完整性检查

外键约束可以指定更新或删除时的引用操作。 在CREATE TABLE reference action子句中描述了使用DDL定义这个引用操作。 在类定义引用的OnDeleteOnUpdate外键关键字中定义了一个持久化类来定义这个引用操作,该类投射到一个表。 在创建分片表时,这些引用操作必须设置为无操作。

默认情况下,InterSystemsIRIS®数据平台对INSERTUPDATEDELETE操作执行外键引用完整性检查。如果该操作将违反参照完整性,则不会执行;该操作将发出SQLCODE -121,-122,-123或-124错误。参照完整性检查失败会生成如下错误:

错误#5540:SQLCODE:-124消息:表'HealthLanguage.FKey2'中至少存在1行,该行引用键NewIndex1-外键约束'NewForeignKey1'(字段'Pointer1')的NO ACTION引用操作失败[Execute + 5 ^ IRISSql16:USER]

可以使用$SYSTEM.SQL.SetFilerRefIntegrity()方法在系统范围内禁止此检查。若要确定当前设置,请调用$SYSTEM.SQL.CurrentSettings()

默认情况下,当删除带有外键的行时,InterSystems IRIS将在相应的被引用表的行上获取长期(直到事务结束)共享锁。这样可以防止在引用行上的DELETE事务完成之前对引用行进行更新或删除。这样可以防止删除引用行,然后回退删除引用行的情况。如果发生这种情况,外键将引用不存在的行。如果使用NoCheck定义外键,或者使用%NOCHECK%NOLOCK指定引用行的DELETE,则不会获取此锁定。

使用持久性类定义定义表时,可以使用NoCheck关键字定义外键,以禁止将来对该外键进行检查。 CREATE TABLE不提供此关键字选项。

可以使用%NOCHECK关键字选项禁止检查特定操作。

默认情况下,InterSystems IRIS还对以下操作执行外键引用完整性检查。如果指定的操作违反了引用完整性,则不执行该命令:

  • ALTER TABLE DROP COLUMN
  • ALTER TABLE DROP CONSTRAINT删除约束 问题-317 SQLCODE。 可以使用SET选项COMPILEMODE=NOCHECK来抑制外键完整性检查。
  • 删除表。问题-320 SQLCODE。可以使用SET选项COMPILEMODE = NOCHECK来抑制外键插入检查。
  • 触发器事件,包括事件之前。 例如,如果删除操作因违反外键引用完整性而不能执行,则不会执行BEFORE DELETE触发器。

在父/子关系中,没有定义子元素的顺序。 应用程序代码不能依赖于任何特定的顺序。

父表和子表

定义父表和子表

在定义投射到表的持久类时,可以使用relationship属性指定两个表之间的父/子关系。

下面的例子定义了父表:

Class Sample.Invoice Extends %Persistent 
{
Property Buyer As %String(MAXLEN=50) [Required];
Property InvoiceDate As %TimeStamp;
Relationship Pchildren AS Sample.LineItem [ Cardinality = children, Inverse = Cparent ];
}

下面的例子定义了一个子表:

Class Sample.LineItem Extends %Persistent 
{
Property ProductSKU As %String;
Property UnitPrice As %Numeric;
Relationship Cparent AS Sample.Invoice [ Cardinality = parent, Inverse = Pchildren ];
}

注意这两句话:

  • Relationship Pchildren AS Sample.LineItem [ Cardinality = children, Inverse = Cparent ];
  • Relationship Cparent AS Sample.Invoice [ Cardinality = parent, Inverse = Pchildren ];

在Management Portal SQL interface Catalog Details选项卡中,表信息提供了子表和/或父表的名称。 如果是子表,则提供对父表的引用,如:parent->Sample.Invoice

子表本身可以是子表的父表。 (子表的子表被称为“孙”表。) 在本例中,表Info提供了父表和子表的名称。

向父表和子表插入数据

在将相应的记录插入子表之前,必须将每个记录插入父表。 例如:

INSERT INTO Sample.Invoice (Buyer,InvoiceDate) VALUES ('yaoxin',CURRENT_TIMESTAMP)
INSERT INTO Sample.LineItem (Cparent,ProductSKU,UnitPrice) VALUES (1,'45-A7',99.95)
INSERT INTO Sample.LineItem (Cparent,ProductSKU,UnitPrice) VALUES (1,'22-A1',0.75)

image

尝试插入没有对应父记录ID的子记录时,会使用%msg子表'Sample生成SQLCODE -104错误。 LineItem'引用父表中不存在的行。

在子表上的插入操作期间,在父表的相应行上获得共享锁。 在插入子表行时,该行被锁定。 然后,锁被释放(直到事务结束时才被持有)。 这确保了在插入操作期间引用的父行不会被更改。

标识父表和子表

在嵌入式SQL中,可以使用主机变量数组来标识父表和子表。 在子表中,主机变量数组的下标0被设置为父引用(Cparent),格式为parentref,下标1被设置为子记录ID,格式为parentref|| childf。 在父表中,没有定义下标0。 如下面的例子所示:

/// d ##class(PHA.TEST.SQL).FatherChildTable()
ClassMethod FatherChildTable()
{
	KILL tflds,SQLCODE,C1
	&sql(DECLARE C1 CURSOR FOR
		SELECT *,%TABLENAME INTO :tflds(),:tname
		FROM Sample.Invoice)
	&sql(OPEN C1)
	IF SQLCODE<0 {
		WRITE "严重的SQL错误:",SQLCODE," ",%msg  QUIT
	}
	&sql(FETCH C1)
	IF SQLCODE=100 {
		WRITE "The ",tname," 表中不包含数据",!  QUIT
	}
	WHILE $DATA(tflds(0)) {
		WRITE tname," 是一个子表",!,"parent ref: ",tflds(0)," %ID: ",tflds(1),!
		&sql(FETCH C1)
		IF SQLCODE=100 {QUIT}
	}
	IF $DATA(tflds(0))=0 {
		WRITE tname," 是父表",!
	}
	&sql(CLOSE C1)
	IF SQLCODE<0 {
		WRITE "错误关闭游标:",SQLCODE," ",%msg  QUIT
	}
}
DHC-APP> d ##class(PHA.TEST.SQL).FatherChildTable()
Sample.Invoice 是父表
/// d ##class(PHA.TEST.SQL).FatherChildTable1()
ClassMethod FatherChildTable1()
{
	KILL tflds,SQLCODE,C2
	&sql(DECLARE C2 CURSOR FOR
		SELECT *,%TABLENAME INTO :tflds(),:tname
		FROM Sample.LineItem)
	&sql(OPEN C2)
	IF SQLCODE<0 {
		WRITE "严重的SQL错误::",SQLCODE," ",%msg  QUIT
	}
	&sql(FETCH C2)
	IF SQLCODE=100 {
		WRITE "The ",tname," 表中不包含数据",! QUIT
	}
	WHILE $DATA(tflds(0)) {
		WRITE tname," 是一个子表",!,"parent ref: ",tflds(0)," %ID: ",tflds(1),!
		&sql(FETCH C2)
		IF SQLCODE=100 {QUIT}
	}
	IF $DATA(tflds(0))=0 {
		WRITE tname," 是父表",!
	}
	&sql(CLOSE C2)
	IF SQLCODE<0 {
		WRITE "错误关闭游标::",SQLCODE," ",%msg  QUIT
	}
}

对于子表,tflds(0)tflds(1)返回如下值:

DHC-APP>d ##class(PHA.TEST.SQL).FatherChildTable1()
Sample.LineItem 是一个子表
parent ref: 1 %ID: 1||1
Sample.LineItem 是一个子表
parent ref: 1 %ID: 1||2

对于“孙”表(即子表的子表),tflds(0)tflds(1)返回如下值:

parent ref: 1||1 %ID: 1||1||1
parent ref: 1||1 %ID: 1||1||7
parent ref: 1||1 %ID: 1||1||8
parent ref: 1||2 %ID: 1||2||2
parent ref: 1||2 %ID: 1||2||3
parent ref: 1||2 %ID: 1||2||4
parent ref: 1||2 %ID: 1||2||5
parent ref: 1||2 %ID: 1||2||6
0
0 189
文章 姚 鑫 · 三月 9, 2021 11m read

第六章 SQL定义和使用视图

视图是一种虚拟表,由执行时通过SELECT语句或几个SELECT语句的UNION从一个或多个物理表中检索到的数据组成。 SELECT可以通过指定表或其他视图的任意组合来访问数据。因此,存储了视图的视图提供了物理表的所有灵活性和安全性特权。

InterSystemsIRIS®数据平台上的InterSystems SQL支持在视图上定义和执行查询的功能。

注意:不能对以只读方式安装的数据库中存储的数据创建视图。 无法在通过ODBC或JDBC网关连接链接的Informix表中存储的数据上创建视图。这是因为InterSystems IRIS查询转换对这种类型的查询使用FROM子句中的子查询。 Informix不支持FROM子句子查询。

创建一个视图

可以通过几种方式定义视图:

  • 使用SQL CREATE VIEW命令(在DDL脚本中或通过JDBC或ODBC)。
  • 使用管理门户的“创建视图”界面。

视图名称:不合格的视图名称是一个简单的标识符:MyView。合格的视图名称由两个简单的标识符组成,即模式名称和视图名称,以句点分隔:MySchema.MyView。视图名称和表名称遵循相同的命名约定,并对不合格的名称执行相同的架构名称解析。同一模式中的视图和表不能具有相同的名称。

可以使用$SYSTEM.SQL.ViewExists()方法确定视图名称是否已存在。此方法还返回投影视图的类名称。可以使用$SYSTEM.SQL.TableExists()方法确定表名是否已存在。

视图可用于创建表的受限子集。以下嵌入式SQL示例创建一个视图,该视图限制了可以通过该视图访问的原始表的行(通过WHERE子句)和列(假设Sample.Person包含两个以上的列):

/// d ##class(PHA.TEST.SQL).View()
ClassMethod View()
{
	&sql(CREATE VIEW Sample.VSrStaff 
		AS SELECT Name AS Vname,Age AS Vage
		FROM Sample.Person WHERE Age>75)
	IF SQLCODE=0 {
		WRITE "创建一个视图",!
	} ELSEIF SQLCODE=-201 {
		WRITE "视图已经存在",!
	} ELSE {
		WRITE "SQL报错: ",SQLCODE," ",%msg,! 
	}
}
DHC-APP>d ##class(PHA.TEST.SQL).View()
创建一个视图

以下嵌入式SQL示例基于SalesPeople表创建一个视图,并创建一个新的计算值列TotalPay

/// d ##class(PHA.TEST.SQL).View1()
ClassMethod View1()
{
	&sql(CREATE VIEW Sample.VSalesPay AS
		SELECT Name,(Salary + Commission) AS TotalPay
		FROM Sample.SalesPeople)
	IF SQLCODE=0 {
		WRITE "创建一个视图",!
	} ELSEIF SQLCODE=-201 {
		WRITE "视图已经存在",!
	} ELSE {
		WRITE "SQL报错: ",SQLCODE," ",%msg,! 
	}
}

管理门户创建视图界面

可以从管理门户创建视图。转到InterSystems IRIS管理门户。在系统资源管理器中,选择SQL。使用页面顶部的Switch选项选择一个名称空间;这将显示可用名称空间的列表。选择名称空间后,单击“操作”下拉列表,然后选择“创建视图”。

这将显示“创建视图”窗口,其中包含以下字段:

  • 模式:可以决定将视图包含在现有模式中,也可以创建一个新模式。如果选择选择现有模式,则会提供一个现有模式的下拉列表。如果选择创建新架构,请输入架构名称。在这两种情况下,如果省略模式,则InterSystems IRIS都会使用系统范围内的默认模式名称。
  • 视图名称:有效的视图名称。不能对同一模式中的表和视图使用相同的名称。
  • 使用Check Option:选项为READONLYLOCALCASCADED
  • 将视图的所有特权授予_PUBLIC:如果选中,则此选项为该视图授予所有用户执行特权。默认设置是不授予所有用户访问该视图的权限。
  • 查看文字:可以通过以下三种方式中的任意一种来指定查看文字:
    • 在“查看文本”区域中键入SELECT语句。
    • 使用查询生成器创建SELECT语句,然后按OK将此查询提供给“查看文本”区域。
    • 如果在Management Portal SQL界面的左侧选择了一个缓存查询名称(例如%sqlcq.USER.cls4),然后调用Create View,则该缓存查询将提供给“视图文本”区域。请注意,在保存视图文本之前,必须在“视图文本”区域中用实际值替换主机变量引用。

视图和相应的类

定义视图时,InterSystems IRIS会生成一个相应的类。按照名称转换规则,SQL视图名称用于生成相应的唯一类名称。 Management Portal SQL界面显示现有视图的“目录详细信息”,包括此类名称。

修改视图

在Management Portal SQL界面中,可以选择一个现有视图以显示该视图的“目录详细信息”。 “目录详细信息视图信息”选项显示“编辑视图”链接,该链接提供了用于编辑视图文本(视图的SELECT语句)的界面。它还提供了一个下拉列表,以将“带检查选项”选择为无,READONLYLOCALCASCADED

可更新的视图

可更新的视图是可以在其上执行INSERTUPDATEDELETE操作的视图。仅当满足以下条件时,才认为视图是可更新的:

  • 视图查询的FROM子句仅包含一个表引用。该表引用必须标识可更新的基表或可更新的视图。
  • 视图查询的SELECT列表中的值表达式必须全部是列引用。
  • 视图的查询中不得指定GROUP BYHAVINGSELECT DISTINCT
  • 该视图不是投影为视图的类查询。
  • 视图的类不包含类参数READONLY = 1(如果视图定义包含WITH READ ONLY子句,则为true)。

WITH CHECK选项

为了防止在视图上执行INSERTUPDATE操作,而该操作会导致基础基表中的行不属于派生视图表的一部分,InterSystems SQL在视图定义中支持WITH CHECK OPTION子句。此子句只能与可更新视图一起使用。

WITH CHECK OPTION子句指定可更新视图上的任何INSERTUPDATE操作必须对照视图定义的WHERE子句验证结果行,以确保插入或修改的行将成为派生视图表的一部分。

例如,以下DDL语句定义了一个可更新的GoodStudent视图,其中包含所有具有高GPA(平均绩点)的学生:

CREATE VIEW GoodStudent AS
    SELECT Name, GPA
      FROM Student
        WHERE GPA > 3.0
    WITH CHECK OPTION

由于视图包含WITH CHECK OPTION,因此任何尝试在GPA值小于或等于3.0的GoodStudent视图中插入或更新行都将失败(此类行将不表示“好学生”)。

有两种类型的WITH CHECK选项:

  • WITH LOCAL CHECK选项意味着只检查INSERTUPDATE语句中指定的视图的WHERE子句。
  • 与级联检查选项(和级联检查选项)意味着视图的WHERE子句中指定的INSERTUPDATE语句以及所有视图检查基于这一观点,无论外表或与当地检查没有其他选项在这些视图定义条款。

如果指定了just WITH CHECK选项,默认值是级联的。

在更新或插入期间,在为基础表的字段计算了所有默认值和触发的计算字段之后,并在常规表验证(必需字段、数据类型验证、约束等)之前,检查WITH CHECK选项条件。

WITH CHECK选项验证通过后,插入或更新操作继续进行,就像在基表本身上执行插入或更新一样。 检查所有约束,拉出触发器,等等。

如果在INSERTUPDATE语句中指定了%NOCHECK选项,则不检查WITH CHECK选项的有效性。

有两个与WITH CHECK选项验证相关的SQLCODE值(插入/更新会导致派生视图表中不存在一行):

  • SQLCODE -136-INSERT中视图的WITH CHECK OPTION验证失败。
  • SQLCODE -137-视图的WITH CHECK OPTION验证在UPDATE中失败。

只读视图

只读视图是不能在其上执行INSERTUPDATEDELETE操作的视图。任何不符合可更新视图标准的视图都是只读视图。

视图定义可以指定WITH READ ONLY子句,以强制其成为只读视图。

如果尝试针对只读视图编译/准备INSERTUPDATEDELETE语句,则会生成SQLCODE -35错误。

查看ID:%VID

InterSystems IRIS为视图或FROM子句子查询返回的每一行分配一个整数视图ID%VID)。与表行ID号一样,这些视图行ID号是系统分配的,唯一的,非空的,非零的和不可修改的。该%VID通常对用户不可见,并且仅在明确指定时返回。它以数据类型INTEGER返回。因为%VID值是顺序整数,所以如果视图返回有序数据,它们将更有意义。视图与TOP子句配对时,只能使用ORDER BY子句。以下嵌入式SQL示例创建一个名为VSrStaff的视图:

/// d ##class(PHA.TEST.SQL).View()
ClassMethod View()
{
	&sql(CREATE VIEW Sample.VSrStaff 
		AS SELECT Name AS Vname,Age AS Vage
		FROM Sample.Person WHERE Age>75)
	IF SQLCODE=0 {
		WRITE "创建一个视图",!
	} ELSEIF SQLCODE=-201 {
		WRITE "视图已经存在",!
	} ELSE {
		WRITE "SQL报错: ",SQLCODE," ",%msg,! 
	}
}

下面的示例返回VSrStaff视图定义的所有数据(使用SELECT *),并且还指定应返回每一行的视图ID。与表行ID不同,使用星号语法时不显示视图行ID。仅当在SELECT中明确指定时才显示:

SELECT *,%VID AS ViewID FROM Sample.VSrStaff

%VID可用于进一步限制SELECT从视图返回的行数,如以下示例所示:

SELECT *,%VID AS ViewID FROM Sample.VSrStaff WHERE %VID BETWEEN 5 AND 10

因此,可以使用%VID代替TOP(或除TOP之外)来限制查询返回的行数。通常,TOP子句用于返回数据记录的一小部分。 %VID用于返回大多数或所有数据记录,以小的子集返回记录。此功能可能很有用,尤其是对于移植Oracle查询(%VID轻松映射到Oracle ROWNUM)而言。但是,与TOP相比,用户应了解使用%VID时的一些性能限制:

  • %VID不执行第一行时间优化。 TOP优化为尽快返回第一行数据。 %VID优化以尽快返回完整的数据集。
  • 如果查询指定排序的结果,则%VID不会执行有限的排序(这是TOP进行的特殊优化)。该查询首先对完整的数据集进行排序,然后使用%VID限制返回数据集。 TOP是在排序之前应用的,因此SELECT只能执行有限的排序,仅涉及有限的行子集。

为了节省第一行优化和有限排序优化的时间,可以将FROM子句子查询与TOP%VID结合使用。在FROM子查询中指定上限(在本例中为10)作为TOP的值,而不是使用TOP ALL。使用%VIDWHERE子句中指定下限(在这种情况下,> 4)。以下示例使用此策略返回与上一个视图查询相同的结果:

SELECT *,%VID AS SubQueryID
   FROM (SELECT TOP 10 Name,Age 
         FROM Sample.Person
         WHERE Age > 75
         ORDER BY Name)
   WHERE %VID > 4

即使显式指定了%PARALLEL关键字,也无法对指定%VID的查询执行并行执行。

List视图属性

INFORMATION.SCHEMA.VIEWS持久类显示有关当前名称空间中所有视图的信息。它提供了许多属性,包括视图定义,视图的所有者以及创建和最后修改视图时的时间戳。这些属性还包括视图是否可更新,如果可更新,是否使用检查选项定义。

在嵌入式SQL中指定时,INFORMATION.SCHEMA.VIEWS需要#include%occInclude宏预处理程序指令。 Dynamic SQL不需要此伪指令。

VIEWDEFINITION属性(SqlFieldName = VIEW_DEFINITION)以字符串形式返回当前名称空间中所有视图的视图字段名称和视图查询表达式。例如,

SELECT View_Definition FROM INFORMATION_SCHEMA.VIEWS

返回诸如“(vName,vAge)SELECT Name,Age FROM Sample.Person WHERE Age> 21”的字符串。当从Management Portal SQL执行查询界面发出时,此字符串的显示仅限于前100个字符,其中不包括空格和换行符,并且(如有必要)附加表示省略号的省略号(...)。否则,发出此查询将为每个视图返回最多1048576个字符的字符串,在视图字段列表和查询文本之间有一个换行符,并保留了视图查询表达式中指定的空格,并(如有必要)附加了省略号(...)表示内容被截断。

以下示例返回当前名称空间中所有视图的视图名称(Table_Name字段)和所有者名称:

SELECT Table_Name,Owner FROM INFORMATION_SCHEMA.VIEWS

以下示例返回当前名称空间中所有非系统视图的所有信息:

SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE Owner != '_SYSTEM'

INFORMATION.SCHEMA.VIEWCOLUMNUSAGE持久性类显示当前名称空间中每个视图的源表字段的名称:

SELECT * FROM INFORMATION_SCHEMA.VIEW_COLUMN_USAGE WHERE View_Name='VSrStaff'

可以使用管理门户网站SQL界面中的“目录详细信息”选项卡为单个视图显示与INFORMATION.SCHEMA.VIEWS相同的信息。视图的“目录详细信息”包括每个视图字段的定义(数据类型,最大长度,最小值/最大值等),以及INFORMATION.SCHEMA视图类未提供的详细信息。 “目录详细信息”视图信息显示还提供了用于编辑视图定义的选项。

列出视图依赖

INFORMATION.SCHEMA.VIEWTABLEUSAGE持久类显示当前名称空间中的所有视图及其依赖的表。在下面的示例中显示:

SELECT View_Schema,View_Name,Table_Schema,Table_Name FROM INFORMATION_SCHEMA.VIEW_TABLE_USAGE

可以调用%Library.SQLCatalog.SQLViewDependsOn类查询以列出指定视图所依赖的表。可以为此类查询指定schema.viewname。如果仅指定视图名称,则它将使用系统范围的默认架构名称。调用者必须具有指定视图的特权才能执行此类查询。在下面的示例中显示:

/// d ##class(PHA.TEST.SQL).View3()
ClassMethod View3()
{
	SET statemt=##class(%SQL.Statement).%New()
	SET cqStatus=statemt.%PrepareClassQuery("%Library.SQLCatalog","SQLViewDependsOn")
	IF cqStatus'=1 {
		WRITE "%PrepareClassQuery failed:" DO $System.Status.DisplayError(cqStatus) QUIT
	}
	SET rset=statemt.%Execute("vschema.vname")
	DO rset.%Display()
}

DHC-APP>d ##class(PHA.TEST.SQL).View3()
 
 
Dumping result #1
SCHEMA  TABLE_NAME
 
0 Rows(s) Affected

SQLViewDependsOn查询列出了视图所依赖的表,并列出了表架构和表名。如果调用者没有该视图所依赖的表的特权,则该表及其模式将列为<NOT PRIVILEGED>。这允许没有表特权的调用者确定视图所依赖的表数量,而不是表的名称。

0
0 276
文章 姚 鑫 · 三月 7, 2021 17m read

第五章 SQL定义表(二)

主键

InterSystems IRIS提供了两种方法来唯一标识表中的行:RowID和主键。

可选的主键是一个有意义的值,应用程序可以使用该值唯一地标识表中的行(例如,联接中的行)。主键可以是用户指定的数据字段,也可以是多个数据字段的组合。主键值必须是唯一的,但不必是整数值。 RowID是一个内部用于标识表中行的整数值。通常,主键是由应用程序生成的值,而RowID是由InterSystems IRIS生成的唯一整数值。

系统会自动创建一个主map,以使用RowID字段访问数据行。如果定义主键字段,系统将自动创建并维护主键索引。

显然,具有两个不同的字段和索引来标识行的双重性不一定是一件好事。可以通过以下两种方式之一解析为单个行标识符和索引:

  • 使用应用程序生成的主键值作为IDKEY。 可以通过使用关键字PrimaryKeyIdKey在类定义中标识主键索引来实现这一点(如果为此目的设置了PKey is IdKey标志,也可以在DDL中实现这一点)。 这使得主键索引成为表的主映射。 因此,主键将被用作行的主要内部地址。 如果主键包含多个字段,或者主键值不是整数,那么这种方法的效率会较低。
  • 不要使用应用程序生成的主键值,而应在应用程序中使用系统生成的RowID整数作为应用程序使用的主键(例如,在joins中)。这样做的好处是,整数RowID有助于进行更有效的处理,包括使用位图索引。

根据应用程序的性质,可能希望解析为单个行标识符和索引,或者为应用程序生成的主键和系统生成的RowID具有单独的索引。

RowVersion,AutoIncrement和串行计数器字段

InterSystems SQL支持三种专用数据类型,用于自动增加计数器值。这三种数据类型都是扩展%Library.BigInt数据类型类的子类。

  • %Library.RowVersion:计算在命名空间范围内所有RowVersion表的插入和更新。只有在包含ROWVERSION字段的表中进行插入和更新时,此计数器才会递增。 ROWVERSION值是唯一的且不可修改。此名称空间范围的计数器永远不会重置。
  • %Library.Counter(也称为SERIAL计数器字段):对表中的插入进行计数。默认情况下,此字段接收一个自动递增的整数。但是,用户可以为此字段指定一个非零的整数值。用户可以指定重复值。如果用户提供的值大于系统提供的最高值,则将自动递增计数器设置为从用户指定的值开始递增。
  • %Library.AutoIncrement:计数插入到表中的次数。默认情况下,此字段接收一个自动递增的整数。但是,用户可以为此字段指定一个非零的整数值。用户可以指定重复值。指定用户值对自动增量计数器无效。

这三个字段以及IDENTITY字段均返回AUTO_INCREMENT = YES,如以下示例所示:

SELECT COLUMN_NAME,AUTO_INCREMENT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'MyTable'

RowVersion Field

RowVersion字段是一个可选的用户定义字段,它提供行级版本控制,使可以确定对每个命名空间范围内的行中的数据进行更改的顺序。 InterSystems IRIS维护一个整个命名空间范围的计数器,并在每次修改行数据(插入,更新或%Save)时向该字段分配一个唯一的增量正整数。因为此计数器是整个名称空间范围的,所以对具有ROWVERSION字段的一个表进行的操作将设置ROWVERSION计数器的增量点,该值将用于同一名称空间中具有ROWVERSION字段的所有其他表。

通过指定数据类型为ROWVERSION(%Library.RowVersion)的字段来创建RowVersion字段。每个表只能指定一个ROWVERSION数据类型字段。尝试创建具有多个ROWVERSION字段的表会导致5320编译错误。

该字段可以具有任何名称,并且可以出现在任何列位置。 ROWVERSION(%Library.RowVersion)数据类型映射到BIGINT(%Library.BigInt)

此字段从自动递增计数器接收一个从1开始的正整数。只要通过插入,更新或%Save操作修改了任何启用ROWVERSION的表中的数据,此计数器就会递增。递增的值记录在已插入或更新的行的ROWVERSION字段中。

名称空间可以包含具有RowVersion字段的表和不具有该字段的表。仅对具有RowVersion字段的表的数据更改会增加整个命名空间范围的计数器。

当用数据填充表时,InterSystems IRIS会为每个插入的行将此字段分配连续的整数。如果使用ALTER TABLEROWVERSION字段添加到已经包含数据的表中,则该字段将被创建为NULL以用于预先存在的字段。对该表的任何后续插入或更新都会为该行的RowVersion字段分配一个顺序整数。该字段是只读的;尝试修改RowVersion值会生成SQLCODE -138错误:无法为只读字段插入/更新值。因此,RowVersion字段被定义为唯一且不可修改,但不是必需字段或非null

RowVersion值始终递增。它们不被重用。因此,插入和更新按时间顺序分配唯一的RowVersion值。删除操作从该序列中删除数字。因此,RowVersion值可能在数字上不连续。

此计数器永远不会重置。删除所有表数据不会重置RowVersion计数器。即使删除名称空间中包含ROWVERSION字段的所有表,也不会重置此计数器。

RowVersion字段不应包含在唯一键或主键中。 RowVersion字段不能是IDKey索引的一部分。

分片表不能包含RowVersion字段。

RowVersion字段未隐藏(通过SELECT *显示)。

在同一名称空间中的三个表的以下示例中显示了这一点。

  1. 创建表1表3,每个都有一个ROWVERSION字段,并创建表2没有一个ROWVERSION字段。
  2. Table1中插入十行。这些行的ROWVERSION值是接下来的十个计数器增量。由于以前未使用过计数器,因此它们是1到10。
  3. Table2中插入十行。由于Table2没有ROWVERSION字段,因此计数器不会增加。
  4. 更新表1的行。该行的ROWVERSION值将更改为下一个计数器增量(在这种情况下为11)。
  5. Table3中插入十行。这些行的ROWVERSION值是接下来的十个计数器增量(12到21)。
  6. 更新表1的行。该行的ROWVERSION值更改为下一个计数器增量(在这种情况下为22)。
  7. 删除表1的行。 ROWVERSION计数器不变。
  8. 更新Table3的一行。该行的ROWVERSION值将更改为下一个计数器增量(在这种情况下为23)。

Serial Counter Field

可以使用SERIAL数据类型(在持久性类表定义中为%Library.Counter)来指定一个或多个可选的整数计数器字段,以记录在表中插入记录的顺序。每个串行计数器字段都维护自己的独立计数器。

每当将一行插入表中时,串行计数器字段都会从其自动增量计数器接收一个正整数,该行没有提供任何值(NULL)或值为0。但是,用户可以指定非零整数值插入期间针对此字段的值,将覆盖表计数器的默认值。

  • 如果INSERT没有为计数器字段指定非零整数值,则计数器字段将自动接收正整数计数器值。计数从1开始。每个连续值都是从为此字段分配的最高计数器值开始的1增量。
  • 如果INSERTcounter字段指定了一个非零的整数值,则该字段将接收该值。它可以是正整数或负整数,可以低于或高于当前计数器值,并且可以是已经分配给该字段的整数。如果该值大于任何分配的计数器值,它将自动增量计数器的增量起始点设置为该值。

尝试更新计数器字段值会导致SQLCODE -105错误。

TRUNCATE TABLE命令将该计数器重置为1。即使使用DELETE命令删除表中的所有行,也不会通过DELETE命令将其重置。

分片表不能包含串行计数器字段。

AutoIncrement Field

可以使用%Library.AutoIncrement数据类型(或BIGINT AUTO_INCREMENT)来指定一个整数计数器字段,以记录在表中插入记录的顺序。每个表只能指定一个%AutoIncrement数据类型字段。每当将一行插入表中时,此字段都会从自动增量计数器接收一个正整数,该行没有提供任何值(NULL)或值为0。但是,用户可以为此指定非零整数值插入过程中的字段,将覆盖表计数器的默认值。

  • 如果INSERT没有为计数器字段指定非零整数值,则计数器字段将自动接收正整数计数器值。计数从1开始。每个连续值都是从为此字段分配的最高计数器值开始的1增量。
  • 如果INSERTcounter字段指定了一个非零的整数值,则该字段将接收该值。它可以是正整数或负整数,可以低于或高于当前计数器值,并且可以是已经分配给该字段的整数。用户分配的值对自动增量计数器无效。

尝试更新计数器字段值会导致SQLCODE -105错误。

TRUNCATE TABLE命令将该计数器重置为1。即使使用DELETE命令删除表中的所有行,也不会通过DELETE命令将其重置。

分片表可以包含一个AutoIncrement字段。

通过创建持久性类来定义表

在InterSystems IRIS中定义表的主要方法是使用Studio创建持久性类定义。当这些类在InterSystems IRIS数据库中保存并编译时,它们会自动投影到与类定义相对应的关系表中:每个类代表一个表;每个类代表一个表。每个属性代表一列,依此类推。可为一个类(表)定义的属性(列)的最大数量为1000

例如,以下定义了持久类MyApp.Person

Class MyApp.Person Extends %Persistent 
{
Property Name As %String(MAXLEN=50) [Required];
Property SSN As %String(MAXLEN=15) [InitialExpression = "Unknown"];
Property DateOfBirth As %Date;
Property Sex As %String(MAXLEN=1);
}

编译后,这将在MyApp模式中创建MyApp.Person持久类和相应的SQL表Person

在此示例中,指定了程序包名称MyApp。定义持久类时,未指定的程序包名称默认为User。这对应于默认的SQL模式名称SQLUser。例如,将名为“Students”的表定义为持久类将创建类User.Students,以及相应的SQL schema.table名称SQLUser.Students

在此示例中,持久类名称Person是默认的SQL表名称。可以使用SqlTableName类关键字来提供其他SQL表名称。

可以使用DDL CREATE TABLE语句(指定SQL schema.table名称)定义相同的MyApp.Person表。成功执行此SQL语句会生成一个相应的持久性类,其包名称为MyApp,类名称为Person:

CREATE TABLE MyApp.Person (
    Name VARCHAR(50) NOT NULL,
    SSN VARCHAR(15) DEFAULT 'Unknown',
    DateOfBirth DATE,
    Sex VARCHAR(1)
)

CREATE TABLE在相应的类定义中未指定显式的StorageStrategy。相反,它将采用已定义的默认存储策略。

默认情况下,CREATE TABLE在相应的类定义中指定Final class关键字,指示它不能具有子类。

请注意,诸如上图所示的持久性类定义在编译时会创建相应的表,但是无法使用SQL DDL命令(或通过使用Management Portal Drop操作)来修改或删除此表定义,这会向显示消息“未为类'schema.name'启用DDL ...”)。必须在表类定义中指定[DdlAllowed]才能进行以下操作:

Class MyApp.Person Extends %Persistent [DdlAllowed]

可以在类定义中指定%Populate以启用使用测试数据自动填充表。

Class MyApp.Person Extends (%Persistent,%Populate) [DdlAllowed]

这为该类提供了Populate()方法。运行此方法将在表中填充十行测试数据。

定义数据值参数

每个属性(字段)定义都必须指定一个数据类型类,该类指定该属性所基于的类。指定的数据类型将字段的允许数据值限制为该数据类型。定义投影到表的持久类时,必须使用%Library包中的类指定此数据类型。可以将此类指定为%Library.Datatype%Datatype

许多数据类型类提供的参数使可以进一步定义允许的数据值。这些参数特定于单个数据类型。以下是一些较常见的数据定义参数:

  • 数据值物理限制
  • 允许的数据值:枚举或模式匹配
  • 通过定义唯一索引来唯一数据值
  • 通过定义SqlComputeCode计算数据值

数据值限制

对于数字数据类型,可以指定MAXVALMINVAL参数以限制允许值的范围。根据定义,数字数据类型具有最大支持值(正数和负数)。可以使用MAXVALMINVAL进一步限制允许的范围。

对于字符串数据类型,可以指定MAXLENMINLEN参数以限制允许的长度(以字符为单位)。根据定义,字符串数据类型具有最大支持的长度。可以使用MAXLENMINLEN进一步限制允许的范围。默认情况下,超过MAXLEN的数据值会生成字段验证错误:INSERTSQLCODE -104UPDATESQLCODE -105。可以指定TRUNCATE = 1以允许超过MAXLEN的字符串数据值。指定的字符串将被截断为MAXLEN长度。

允许的数据值

可以通过两种方式限制实际数据值:

  • 允许值的列表(带有VALUELISTDISPLAYLIST的枚举值)。
  • 允许值的匹配模式(PATTERN)。

枚举值

通过将表定义为持久类,可以定义仅包含某些指定值的属性(字段)。这是通过指定VALUELIST参数来完成的。 VALUELIST(指定逻辑存储值的列表)通常与DISPLAYLIST(指定相应的显示值的列表)一起使用。这两个列表都以列表定界符开头。几种数据类型可以指定VALUELISTDISPLAYLIST。下面的示例定义两个带有枚举值的属性:

Class Sample.Students Extends %Persistent 
{
Property Name As %String(MAXLEN=50) [Required];
Property DateOfBirth As %Date;
Property ChoiceStr As %String(VALUELIST=",0,1,2",DISPLAYLIST=",NO,YES,MAYBE");
Property ChoiceODBCStr As %EnumString(VALUELIST=",0,1,2",DISPLAYLIST=",NO,YES,MAYBE");
}

如果指定了VALUELIST,则INSERTUPDATE只能指定VALUELIST中列出的值之一,或者不提供值(NULL)。 VALUELIST有效值区分大小写。指定与VALUELIST值不匹配的数据值会导致字段值验证失败:INSERTSQLCODE -104UPDATESQLCODE -105

在ODBC模式下显示时,%String%EnumString数据类型的行为不同。使用上面的示例,当以逻辑模式显示时,ChoiceStrChoiceODBCStr都显示其VALUELIST值。在“显示”模式下显示时,ChoiceStrChoiceODBCStr均显示其DISPLAYLIST值。当以ODBC模式显示时,ChoiceStr显示VALUELIST值;否则显示VALUELIST值。 ChoiceODBCStr显示DISPLAYLIST值。

值的模式匹配

几种数据类型可以指定PATTERN参数。 PATTERN将允许的数据值限制为与指定的ObjectScript模式匹配的数据值,指定为带引号的字符串,省略前导问号。以下示例使用模式定义属性:

Class Sample.Students Extends %Persistent 
{
Property Name As %String(MAXLEN=50) [Required];
Property DateOfBirth As %Date;
Property Telephone As %String(PATTERN = "3N1""-""3N1""-""4N");
}

由于将模式指定为带引号的字符串,因此模式中指定的文字必须将其双引号引起来。请注意,模式匹配是在MAXLENTRUNCATE之前应用的。因此,如果为可能超过MAXLEN并被截断的字符串指定了一个模式,则可能希望以“ .E”(任何类型的尾随字符数不限)结束该模式。

PATTERN不匹配的数据值会生成字段验证错误:INSERTSQLCODE -104UPDATESQLCODE -105

唯一值

CREATE TABLE允许将字段定义为UNIQUE。这意味着每个字段值都是唯一(非重复)值。

将表定义为持久类不支持相应的uniqueness属性关键字。相反,必须同时定义属性和该属性的唯一索引。下面的示例为每个记录提供唯一的Num值:

  Class Sample.CaveDwellers Extends %Persistent [ DdlAllowed ]
  { 
  Property Num As %Integer;
  Property Troglodyte As %String(MAXLEN=50);
  Index UniqueNumIdx On Num [ Type=index,Unique ];
  }

索引名称遵循属性的命名约定。可选的Type关键字指定索引类型。 Unique关键字将属性(字段)定义为唯一。

使用INSERTUPDATE语句时,必须具有唯一的值字段。

计算值

下面的类定义示例定义一个表,该表包含一个字段(生日),该字段在最初设置DateOfBirth字段值时使用SqlComputed来计算其值,而在更新DateOfBirth字段值时使用SqlComputeOnChange来重新计算其值。 Birthday字段值包括当前时间戳,以记录该字段值的计算/重新计算时间:

Class Sample.MyStudents Extends %Persistent [DdlAllowed]
{
  Property Name As %String(MAXLEN=50) [Required];
  Property DateOfBirth As %Date;
  Property Birthday As %String 
          [ SqlComputeCode = {SET {Birthday}=$PIECE($ZDATE({DateOfBirth},9),",")_
                              " changed: "_$ZTIMESTAMP},
                              SqlComputed, SqlComputeOnChange = DateOfBirth ];
}
```java

请注意,对`DateOfBirth`的`UPDATE`指定现有的`DateOfBirth`值不会重新计算`Birthday`字段值。

## 嵌入式对象(%SerialObject)

可以通过引用定义属性的嵌入式串行对象类来简化持久表的结构。例如,希望`MyData.Person`包含地址信息,包括街道,城市,州和邮政编码。可以定义一个定义这些属性的串行对象(`%SerialObject`)类,而不是在`MyData.Person`中指定这些属性,然后在`MyData.Person`中指定一个引用该嵌入式对象的`Home`属性。在以下类定义中显示了这一点:

```java
Class MyData.Person Extends (%Persistent) [ DdlAllowed ]
{  Property Name As %String(MAXLEN=50);
   Property Home As MyData.Address;
   Property Age As %Integer;
} 
Class MyData.Address Extends (%SerialObject)
{  Property Street As %String;
   Property City As %String;
   Property State As %String;
   Property PostalCode As %String;
 }

不能直接访问串行对象属性中的数据,必须通过引用它的持久类/表访问它们:

  • 要从持久性表中引用单个串行对象属性,请使用下划线。例如,SELECT名称Home_State FROM MyData.Person返回状态串行对象属性值作为字符串。串行对象属性值以查询中指定的顺序返回。
  • 要引用持久性表中的所有串行对象属性,请指定引用字段。例如,SELECT Home FROM MyData.Person%List结构形式返回所有MyData.Address属性的值。串行对象属性值以串行对象中指定的顺序返回:Home_Street,Home_City,Home_State,Home_PostalCode。在Management Portal SQL界面“目录详细信息”中,此引用字段称为“容器”字段。这是一个Hidden字段,因此SELECT *语法不返回。
  • 持久类的SELECT *单独返回所有串行对象属性,包括嵌套的串行对象。例如,SELECT * FROM MyData.Person返回Age,Name,Home_City,Home_PostalCode,Home_State和Home_Street值(按此顺序);它不返回Home%List结构值。串行对象属性值以排序顺序返回。 SELECT *首先按排序顺序(通常按字母顺序)列出持久性类中的所有字段,然后按排序顺序列出嵌套的串行对象属性。

请注意,嵌入式串行对象不必与引用它的持久性表位于同一程序包中。

定义嵌入式对象可以简化持久性表定义:

  • 持久表可以包含多个属性,这些属性引用同一嵌入式对象中的不同记录。例如,MyData.Person表可以包含HomeOffice属性,这两个属性均引用MyData.Address串行对象类。
  • 多个持久表可以引用同一嵌入式对象的实例。例如,MyData.Person表的Home属性和MyData.Employee WorkPlace属性都可以引用MyData.Address串行对象类。
  • 一个嵌入式对象可以引用另一个嵌入式对象。例如,MyData.Address嵌入式对象包含引用MyData.Telephone嵌入式对象的Phone属性,其中包含CountryCodeAreaCodePhoneNum属性。在持久类中,使用多个下划线来引用嵌套的串行对象属性,例如Home_Phone_AreaCode

编译串行对象类会在存储定义中生成数据规范。编译器通过在串行对象类名称后附加单词“State”来为该规范分配数据名称。因此,为MyData.Address分配了<Data name =“ AddressState”>。如果此名称(在此示例中为AddressState)已经用作属性名称,则编译器将附加一个整数以创建唯一的数据名称:<Data name =“ AddressState1”>

类方法

可以将类方法指定为表定义的一部分,如以下示例所示:

Class MyApp.Person Extends %Persistent 
{
Property Name As %String(MAXLEN=50) [Required];
Property SSN As %String(MAXLEN=15) [InitialExpression = "Unknown"];
Property DateOfBirth As %Date;
Property Sex As %String(MAXLEN=1);
ClassMethod Numbers() As %Integer [ SqlName = Numbers, SqlProc ]
  {
   QUIT 123
  }
}

在SELECT查询中,可以按以下方式调用此方法:

SELECT Name,SSN,Sample.Numbers() FROM Sample.Person

image

通过创建持久性类来定义分片表

必须先建立分片环境,然后才能定义作为分片表投影的持久性类。

要将持久性类定义为分片,请指定类关键字Sharded = 1。 (类关键字Sharded = 2保留供生成的类内部使用。)

注意:请勿尝试设置或更改现有类定义的与分片相关的类属性。仅应为不包含数据的新表指定这些属性。这包括设置Sharded类关键字和与分片相关的索引关键字。尝试编辑现有类的任何与分片相关的属性都可能导致数据无法访问。

下例显示了Sharded = 1持久类的类定义:

Class Sample.MyShardT Extends %Persistent [ ClassType = persistent, DdlAllowed, Final, Sharded = 1]
{
...
}

如果将一个类定义为分片,则它必须是持久性的ClassType。如果未将分片类定义为ClassType持久类,则在类编译期间将返回错误,例如:ERROR#5599:分片类'Sample.Address'必须为ClassType'persistent',而不是ClassType'serial'。分片类使用的存储类必须为%Storage.Persistent或其子类%Storage.Shard。如果分片类的存储类不是%Storage.Persistent,则在类编译期间将返回以下错误:错误#5598:分片类'Sample.Vendor'必须使用存储类型%Storage.Persistent,而不是存储类型' %Storage.SQL”

定义分片类时,应定义参数DEFAULTCONCURRENCY = 0

然后,可以定义ShardKey索引。

创建分片表时,将自动生成抽象的分片键索引。分片键索引的目的是用作确定行所在的分片的键。

分片类方法

分片类(Sharded = 1)支持%Library.Persistent方法%Open(),%OpenId(),%Save(),%Delete()和%DeleteId()具有以下限制:并发concurrency参数被忽略;删除将始终使用并发concurrency= 0,而不管用户提供的并发值如何。完全支持回调方法%OnDelete(),%OnAfterDelete(),%OnOpen(),%OnBeforeSave()和%OnAfterSave()。这些回调方法在分片主机上执行,而不是在分片服务器上执行。分片本地类(Sharded = 2)不支持这些方法。

分片类(Sharded = 1)不支持%Library.Persistent方法%LockExtent()%UnlockExtent()。定义并发参数的对象方法中的所有并发参数都要求值concurrency = 0;否则,值为0。可以通过设置DEFAULTCONCURRENCY = 0来建立默认值

分片类限制

  • 分片类不支持的类参数:CONNECTIONDEFAULTGLOBALDSINTERVALDSTIMEIDENTIFIEDBYOBJJOURNAL
  • 分片类不支持的类关键字:languageViewQuery
  • 分片类不支持的超级类:%Library.IndexBuilder%DocDB.Document
  • 分片类不支持的属性数据类型:%Library.Text
  • 分片类不支持关系属性。
  • 分片类不支持投影。
  • 分片类不支持功能索引(无法定义索引TypeClass)。
  • 分片类不支持使用除“对象”以外的语言的任何方法。
  • 分片类不支持任何非%SQLQuery类型的类查询。

尝试使用任何这些功能来编译分片类都会导致编译时错误。

0
0 152
文章 Nicky Zhu · 二月 4, 2021 7m read

上一篇: IRIS中的权限管理

在上一篇文章中,我们介绍了IRIS中的权限控制体系。在本文中我们将以一个常见的实施需求为例介绍如何使用IRIS的权限配置出一个只能使用SQL的用户。

需求的分解

和所有用户需求一样,当用户提出一个需求时,除其语义显式的含义之外,还需分析其是否具有没有明确说明的含义。 对于一个只能使用SQL的用户这样一个需求,即应当结合平台的特征分解成为功能需求: 具有一个合法,可通过用户名和密码使用IRIS的用户 该用户的数据库权限

  • 确认项:可以使用SQL访问所有数据库还是某几个特定的数据库? 该用户的SQL权限
  • 确认项:对于特定的数据库,是否可以执行所有的DDL?
  • 确认项:对于特定的数据库,是否对每一张表都可以执行Select、Update等所有的DML 该用户的程序权限
  • 确认项:用户是否可以通过Portal登录并管理IRIS?

如上所示,一个只能使用SQL的用户,这样一个看似简单的需求,如果需要与用户获得一致的理解并付诸实施,则需要将其分解,对于没有在用户需求中明确约定的部分,应作为待确认项与用户确认。 另外,需要注意的是,IRIS作为一个数据平台,除了提供底层的数据库之外,也提供了Portal等Web应用程序便于使用Sharding、HA、Interoperability等平台功能,通过平台对外提供的Webservice、REST等web接口也受Web应用程序控制。因此,当需要设计权限体系时,不但需要考虑用户使用的数据库相关的权限,也需要考虑是否需要控制Web应用程序的权限。

我们假设需要提供一个严格意义上的只能对某个库的某个Schema下的所有表具有只读权限的用户,即: 具有一个合法,可通过用户名和密码使用IRIS的用户 该用户的数据库权限

  • 该用户只能使用DemoSpace命名空间下的数据库 该用户的SQL权限
  • 该用户不能执行任何DDL
  • 该用户只能对DemoSchema下的所有表执行Select语句 该用户的程序权限
  • 该用户不能登录Portal,不能执行Portal中提供的任何管理功能

对数据库的配置

在IRIS中创建数据库时,默认的行为是引用%DB_%DEFAULT这个资源,并引用%DB_%DEFAULT角色(注意,平台中有一个叫做%DB_%DEFAULT的资源,同时还有一个叫做%DB_%DEFAULT的角色。%DB_%DEFAULT角色通过%DB_%DEFAULT资源获得默认的数据库访问权限)。如果直接使用%DB_%DEFAULT角色或%DB_%DEFAULT资源,都有可能影响到之前以默认配置创建的数据库,因此,在需要细粒度控制访问权限时,往往需要自定义资源和角色实现。

创建资源%DB_DemoDB_Res {#3.1}

资源可在创建数据库的同时创建,也可以在使用默认资源创建数据库之后,再给数据库指派其他资源。本例中,我们在创建命名空间DEMOSPACE的同时创建数据库DEMODB并创建资源。 image

创建角色DemoDB_Read_Role {#3.2}

image 在创建角色之后,即可为其分配资源。根据资源类型的不同,对资源的操作可以有读、写和使用三种权限。对于数据库引用的资源,是读权限和写权限。在本例中,我们需要创建的是只读用户,因此,资源权限分配读权限即可,不用赋写权限。 image

创建用户DemoUser并分配角色

通过Portal创建用户之后,即可给用户分配角色。在本例中,需要给这个用户分配之前创建的DemoDB_Read_Role角色。 image 在经过上述配置之后,用户DemoUser即已拥有对命名空间DEMOSPACE中数据库的访问权限。 image 此时,用户对数据库拥有读权限,但并没有对表执行查询或建表的权限,如果尝试create table,则会得到如下的权限错误信息: image 为继续实验,我们通过Portal执行这SQL语句先创建DemoSchema.Persons这张表 image

为角色DemoDB_Read_Role分配SQL权限

由于用户具有的权限不足,无法执行SQL操作,因此我们需要对该用户的角色赋予对应的权限(或直接给用户赋权,但平台用户较多时,考虑到用户管理的成本,并不推荐这样做)。可以采用如下手段进行SQL的授权

通过Portal授权

在Portal的用户管理和角色管理功能中,均可指定要授予的SQL权限。 SQL特权栏用于对DDL进行数据库级的授权,例如对DEMOSPACE命名空间下的数据库分配建表、修改表、建视图等DDL操作。在本例中,用户不具有这些权限,因此不对该用户对应的角色授予这些权限。 image 在SQL表,SQL视图和SQL过程栏中,则是分别对表、视图和存储过程授予查询、执行等权限。在本例中,用户需要对DemoSchema这个Schema下的表拥有select查询权限,即可通过对SQL表授权进行 image 授权后该角色的SQL表权限如下 image 此时通过SQL工具已可执行查询 image 使用Portal授权时是针对单个的表、视图或存储过程进行。在上例中,我们单独对表DemoSchema.Persons进行了授权,如果我们再建立一张DemoSchema.Employee表,当前的角色和用户并不能自动获得读取其数据的权限。

通过SQL授权

超级管理员或拥有SQL授权权限的用户可以通过SQL的GRANT语句对数据库对象(包括库,函数,表,视图和存储过程等)进行授权,SQL GRANT语句的语法和使用详见GRANT,此处不再赘述。 在上面的例子中我们建立了表DemoSchema.Persons,建表的同时建立的Schema DemoSchema。假如现在我们希望对授权进行简化,使角色DemoDB_Read_Role可以直接获得Schema下所有表的读权限,则可以用如下的SQL:

GRANT SELECT ON SCHEMA DemoSchema to DemoDB_Read_Role

执行成功后再查看DemoDB_Read_Role的SQL权限,会发现: image 即这个角色已经拥有了Schema级的授权,因此,对整个Schema下的所有表都拥有权限。之后在Schema中如果建立了新的表,则这个角色会自动拥有这些表的读权限。

限制用户登录和使用Portal

上例配置的用户可以用于登录IRIS的Portal,但由于没有任何系统功能的权限,不能执行操作。 image IRIS的Web应用程序在创建时默认并不需要额外的资源去访问,这意味着所有合法用户都能登入这个Web应用,但由于支撑应用的后台程序和数据是受到资源的保护的,能登入的用户不一定具有运行程序、访问数据的权限,正如我们建立的用户DemoUser可以登录Portal,但没有功能菜单可用。 如果需要进一步限制用户的行为,禁止其登录,则还需要对应用权限进行控制。 如我们在上一篇文章:IRIS中的权限管理 中所述,Portal是平台提供的Web应用程序,是通过Web应用的权限控制可访问性,因此,需要修改应用的资源要求。 通过菜单: 系统管理 > 安全 > 应用程序 > Web应用程序 可以访问当前系统提供的Web应用列表,其中的/csp/sys即为系统管理门户Portal image 其中必要的资源一栏即为该应用的资源需求,默认为空,即访问该应用不需要特定资源,只要是合法用户即可。我们可以为该应用指定所需资源,例如%Development,即只有具有%Development资源的角色及其对应的用户才能够访问该程序。保存设置后,再尝试以用户DemoUser登录Portal,结果是 image 除非我们为DemoUser引用的角色DemoDB_Read_Role分配资源%Development,该用户都不能登录。 当然,超级管理员由于拥有所有权限,不受这个设置的影响。

总结

通过上述实验,我们创建了一个用户,只能使用SQL连入并查询指定Schema下的表。希望通过这个实验,大家能够掌握IRIS权限管理的基本元素:

  • 用户,角色,权限,资源和许可构成的权限控制体系
  • 数据库、SQL和应用程序都是权限管理的对象

大家在实际项目中可以根据最终用户的实际需要,灵活应用这些概念,构建满足需求的权限配置。

上一篇: IRIS中的权限管理

3
1 448
文章 Qiao Peng · 三月 5, 2021 3m read

Global 扫描和切片

这是一个在 IRIS 2020.1 和 Caché 2018.1.3 上工作的代码示例
不会与新版本保持同步
也不会获得 InterSystems 提供的支持服务!   

大多数情况下,默认存储使用的 global 只有 1 个表示 IDKEY 的下标级别。 对于索引 global,可能有 2 个或更多下标级别。

数组、父子关系或扩展了基础数据类的持久化类 是具有更多下标级别的示例。 但所有这些 global 都非常统一。

1
0 162
文章 Nicky Zhu · 一月 18, 2021 4m read

在最近的项目里,多方同时连接同一个数据库并执行增删改查等各项数据操作。研发人员不时发现一些数据在不合规的情况下被新增甚至删除。因此,在实际工作中会有监控数据操作以便识别和处理异常操作的需求。本文将以监控和识别删除操作为例,介绍如何通过IRIS的审计功能实现对数据操作的监控和查询。

注意事项

在应用审计功能之前,必须注意的是:

  1. 开启审计功能会事无巨细地记录每一条对应的操作(如被执行的SQL),因此对于存储空间的需求将急剧增加。举例而言,仅开启对XDBCStatement的监控后,对于一张只由5个简单(整型,VARCHAR型)字段构成的表中插入100万条记录,在Audit数据库中将占用300~400MB的空间。在因业务所需确实需要开启审计功能时,必须预先分配更多磁盘空间给IRIS Audit数据库,并在审计功能开启期间定时巡检磁盘空间,避免因日志占满磁盘导致其他数据无法写入引发系统挂起的故障。
  2. 在研发环境中多人、多单位需要连接数据库时,应为不同的开发者和数据来源分配独立的数据库账户和权限,避免多人共用超级账户,导致数据异常时难以追踪异常操作究竟从何而来。也就意味着为不同角色的开发、测试、用户等参与者开启独立的用户,分配各自所需的的数据库权限以及管理数据库账户这样一系列项目正常运行所依赖的实践并不能被审计功能所替代。在项目进展过程中,您更希望见到的,一定不是出现问题后再来跟踪问题和耽误工期,而是通过良好的协作规程和研发习惯减少非技术问题出现的概率。

IRIS中的审计功能

IRIS提供了丰富的数据库审计功能,用于记录从系统权限变更到数据删除的各类操作,用户可通过我们的官方文档查看系统支持的各类审计事件。 对于SQL操作而言,可用到的审计事件包括三大类:

  • %System/%SQL/DynamicStatement 可捕获通过动态SQL执行的SQL操作
  • %System/%SQL/EmbeddedStatement 可捕获通过嵌入式SQL执行的SQL操作
  • %System/%SQL/XDBCStatement 可捕获通过第三方(JDBC/ODBC)SQL连接执行的SQL操作

开启数据库审计

  1. 由于默认安装时IRISAUDIT数据库通常位于IRIS安装目录,空间有限,因此在需要开启审计时,应先将IRISAUDIT数据库从系统安装目录迁移到空间更充裕的数据库目录

  2. 在System Administration -> Security -> Auditing -> Configure System Events下,开启对%System/%SQL/XDBCStatement的监控,如下图 image

  3. 预先配置好删除AuditLog的任务,以便在AuditLog过大时清除数据 系统虽然自带定时删除Audit数据库的任务,但其默认的触发条件为在Journal发生切换时才执行,因此不适用于需要扩展监控范围导致审计库将剧增的情况,应配置额外的任务便于随时执行。 image 并将其设置为按需启动 image

  4. 待可疑操作发生时,使用SQL工具或portal在%SYS命名空间下运行如下SQL:

SELECTID, AuditIndex, Authentication, CSPSessionID, ClientExecutableName, ClientIPAddress, Description, Event, EventData, EventSource, EventType, GroupName, JobId, JobNumber, Namespace, OSUsername, Pid, Roles, RoutineSpec, Status, SystemID, UTCTimeStamp, UserInfo, UsernameFROM %SYS.Auditwhere Description = 'SQL DELETE Statement'order by UTCTimeStamp desc

即可找到最近的删除操作。通过Truncate table或delete语句执行的操作均可由该日志捕获到。如需监控insert或update等操作,在上述Description字段的选择上加入对insert或update语句的筛选即可。 通过这些日志,可以查看到操作发生的时间,来源IP和数据库用户等信息,如下所示: image

  1. 必须再次提醒各位,一旦开启对语句的监控,Audit数据库会快速增长,需要每日甚至每日多次巡检,确保磁盘空间不会被占满导致系统崩溃。 一旦发现Audit所在磁盘占有量过大(例如大于80%)或Audit库本身的占用过大(例如大于20G),即应运行步骤3中 配置的任务,然后对Audit库进行压缩和截断操作释放空间。
0
0 410
文章 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 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
文章 Hao Ma · 一月 10, 2021 15m read

自 Caché 2017 以后,SQL 引擎包含了一些新的统计信息。 这些统计信息记录了执行查询的次数以及运行查询所花费的时间。

对于想要对包含许多 SQL 语句的应用程序的性能进行监控和尝试优化的人来说,这是一座宝库,但访问数据并不像一些人希望的那么容易。

本文和相关的示例代码说明了如何使用这些信息,以及如何例行提取每日统计信息的摘要,并保存应用程序的 SQL 性能的历史记录。

记录了什么?

每次执行 SQL 语句时,都记录花费的时间。 这是非常轻量的操作,无法关闭。 为了最大程度地降低开销,统计信息保留在内存中并定期写入磁盘。 数据包括一天中执行查询的次数以及所花费的平均时间和总时间。

数据不会立即写入磁盘,并且在写入之后,统计信息将由“更新 SQL 查询统计信息”任务更新,该任务通常计划为每小时运行一次。 该任务可以手动触发,但是如果你希望在测试查询时实时查看统计信息,则整个过程需要一点耐心。

警告:在 InterSystems IRIS 2019 及更早版本中,不会针对已使用 %Studio.Project:Deploy 机制部署类或例程中的嵌入式 SQL 收集这些统计信息。 示例代码不会有任何中断,但这可能会使你产生误导(我被误导过),让你以为一切正常,因为没有查询显示为高开销。

如何查看信息?

你可以在管理门户中查看查询列表。 转到 SQL 页面,点击“SQL 语句”选项卡。 对于你正在运行并查看的新查询,这种方式很好;但是如果有数千条查询正在运行,则可能变得难以管理。

另一种方法是使用 SQL 搜索查询。 信息存储在 INFORMATION_SCHEMA 模式的表中。 该模式含有大量表,我在本文的最后附上了一些 SQL 查询示例。

何时删除统计信息?

每次重新编辑查询时会删除其数据。 因此对于动态查询,这可能意味着清除缓存的查询时。 对于嵌入式 SQL,则意味着重新编译在其中嵌入 SQL 的类或例程时。

在活跃的站点上,可以合理预期统计信息将保存超过一天,但是存放统计信息的表不能用作运行报告或长期分析的长期参考源。

如何汇总信息?

我建议每天晚上将数据提取到永久表中,这些表在生成性能报告时更易于使用。 如果在白天编译类,可能会丢失一些信息,但这不太可能对慢速查询的分析产生任何实际影响。

下面的代码示例说明了如何将每个查询的统计信息提取到每日汇总中。 它包括三个简短的类:

  • 一个应在每晚运行的任务。
  • DRL.MonitorSQL 是主类,用于从 INFORMATION_SCHEMA 表提取数据并存储。
  • 第三个类 DRL.MonitorSQLText 是一个优化类,它存储一次(可能很长的)查询文本,并且只将查询的哈希存储在每天的统计信息中。
  • 示例说明

    该任务提取前一天的信息,因此应安排在午夜后不久执行。

    你可以导出更多历史数据,只要其存在。 要提取过去 120 天的数据

    Do ##class(DRL.MonitorSQL).Capture($h-120,$h-1)

    该示例代码直接读取全局 ^rIndex,因为最早版本的统计信息未将日期公开给 SQL。

    我所包括的变体将循环实例中的所有命名空间,但这并不总是合适的。

    如何查询已提取的数据

    提取数据后,您可以通过运行以下语句查找最繁重的查询

    SELECT top 20S.RunDate,S.RoutineName,S.TotalHits,S.SumpTIme,S.Hash,t.QueryTextfrom DRL.MonitorSQL Sleft join DRL.MonitorSQLText T on S.Hash=T.Hashwhere RunDate='08/25/2019'order by SumpTime desc

     

    此外,如果选择了开销大的查询的哈希,可以通过以下语句查看该查询的历史记录

    SELECT S.RunDate,S.RoutineName,S.TotalHits,S.SumpTIme,S.Hash,t.QueryTextfrom DRL.MonitorSQL Sleft join DRL.MonitorSQLText T on S.Hash=T.Hashwhere S.Hash='CgOlfRw7pGL4tYbiijYznQ84kmQ='order by RunDate

     

    今年早些时候,我获取了一个活跃站点的数据,然后查看了开销最大的查询。 有一个查询的平均时间不到 6 秒,但每天被调用 14000 次,加起来每天消耗的时间将近 24 小时。 实际上,一个核心完全被这个查询占用。 更糟糕的是,第二个查询要花一个小时,它是第一个查询的变体。

    <td>
      例程名称
    </td>
    
    <td>
      总命中次数
    </td>
    
    <td>
      总时间
    </td>
    
    <td>
      哈希
    </td>
    
    <td>
      查询文本(有节略)
    </td>
    
    <td>
       
    </td>
    
    <td>
      14,576
    </td>
    
    <td>
      85,094
    </td>
    
    <td>
      5xDSguu4PvK04se2pPiOexeh6aE=
    </td>
    
    <td>
      DECLARE C CURSOR FOR SELECT * INTO :%col(1) , :%col(2) , :%col(3) , :%col(4)  …
    </td>
    
    <td>
       
    </td>
    
    <td>
      15,552
    </td>
    
    <td>
      3,326
    </td>
    
    <td>
      rCQX+CKPwFR9zOplmtMhxVnQxyw=
    </td>
    
    <td>
      DECLARE C CURSOR FOR SELECT * INTO :%col(1) , :%col(2) , :%col(3) , :%col(4) , …
    </td>
    
    <td>
       
    </td>
    
    <td>
      16,892
    </td>
    
    <td>
      597
    </td>
    
    <td>
      yW3catzQzC0KE9euvIJ+o4mDwKc=
    </td>
    
    <td>
      DECLARE C CURSOR FOR SELECT * INTO :%col(1) , :%col(2) , :%col(3) , :%col(4) , :%col(5) , :%col(6) , :%col(7) ,
    </td>
    
    <td>
       
    </td>
    
    <td>
      16,664
    </td>
    
    <td>
      436
    </td>
    
    <td>
      giShyiqNR3K6pZEt7RWAcen55rs=
    </td>
    
    <td>
      DECLARE C CURSOR FOR SELECT * , TKGROUP INTO :%col(1) , :%col(2) , :%col(3) , ..
    </td>
    
    <td>
       
    </td>
    
    <td>
      74,550
    </td>
    
    <td>
      342
    </td>
    
    <td>
      4ZClMPqMfyje4m9Wed0NJzxz9qw=
    </td>
    
    <td>
      DECLARE C CURSOR FOR SELECT …
    </td>
    
    运行日期
    03/16/2019
    03/16/2019
    03/16/2019
    03/16/2019
    03/16/2019

    1:客户站点的实际结果

    INFORMATION_SCHEMA 模式中的表

    除了统计信息外,此模式中的表还会跟踪查询、列、索引等的使用位置。 通常,SQL 语句是起始表,它的连接方式类似于“Statements.Hash=OtherTable.Statement”。

    直接访问这些表以查找一天中开销最大的查询,这一操作的等效查询是...

    SELECT DS.Day,Loc.Location,DS.StatCount,DS.StatTotal,S.Statement,S.HashFROM INFORMATION_SCHEMA.STATEMENT_DAILY_STATS DSleft join INFORMATION_SCHEMA.STATEMENTS  Son S.Hash=DS.Statementleft join INFORMATION_SCHEMA.STATEMENT_LOCATIONS  Locon S.Hash=Loc.Statementwhere Day='08/26/2019'order by DS.stattotal desc

     

    无论你是否考虑建立一个更系统的过程,我都建议每个使用 SQL 处理大型应用程序的人今天都运行这个查询。

    如果某个特定查询显示为高开销,则可以通过运行以下语句获取历史记录

    SELECT DS.Day,Loc.Location,DS.StatCount,DS.StatTotal,S.Statement,S.HashFROM INFORMATION_SCHEMA.STATEMENT_DAILY_STATS DSleft join INFORMATION_SCHEMA.STATEMENTS  Son S.Hash=DS.Statementleft join INFORMATION_SCHEMA.STATEMENT_LOCATIONS  Locon S.Hash=Loc.Statementwhere S.Hash='jDqCKaksff/4up7Ob0UXlkT2xKY='order by DS.Day

     

     
    每日提取统计信息的代码示例
    标准免责声明 - 此示例仅用于说明。 不对其提供支持,也不保证其有效。
    <p>
    </p>
    
    <p>
      <span style="font-size:11pt"><span style="line-height:107%"><span style=""><span style="color:navy">Class DRL.MonitorSQLTask Extends %SYS.Task.Definition</span><br /><span style="color:black">{</span><br /><span style="color:navy">Parameter </span><span style="color:black">TaskName = </span><span style="color:purple">"SQL Statistics Summary"</span><span style="color:black">;</span><br /><span style="color:navy">Method </span><span style="color:black">OnTask() </span><span style="color:navy">As %Status</span><br /><span style="color:black">{</span><br />              <span style="color:blue">set </span><span style="color:olive">tSC</span><span style="color:black">=</span><span style="color:blue">$$$OK</span><br />              <span style="color:blue">TRY </span><span style="color:purple">{</span><br />                             <span style="color:blue">do </span><span style="color:navy">##class</span><span style="color:black">(</span><span style="color:teal">DRL.MonitorSQL</span><span style="color:black">).</span><span style="color:blue">Run</span><span style="color:black">()</span><br />              <span style="color:purple">}</span><br />              <span style="color:blue">CATCH </span><span style="color:olive">exp </span><span style="color:purple">{</span><br />                            <span style="color:blue">set </span><span style="color:olive">tSC</span><span style="color:black">=</span><span style="color:blue">$SYSTEM</span><span style="color:teal">.Status</span><span style="color:black">.</span><span style="color:blue">Error</span><span style="color:black">(</span><span style="color:green">"Error in SQL Monitor Summary Task"</span><span style="color:black">)</span><br />              <span style="color:purple">}</span><br />              <span style="color:blue">quit </span><span style="color:olive">tSC</span><br /><span style="color:black"> }</span><br /><span style="color:black">}</span></span></span></span>
    </p>
    
    <p>
       
    </p>
    
    <p>
      <span style="font-size:11pt"><span style="line-height:107%"><span style=""><span style="color:navy">Class DRL.MonitorSQLText Extends %Persistent</span><br /><span style="color:black">{</span><br /><span style="color:navy">/// Hash of query text</span><br /><span style="color:navy">Property </span><span style="color:black">Hash </span><span style="color:navy">As %String</span><span style="color:black">;</span></span></span></span><br /> 
    </p>
    
    <p>
      <span style="font-size:11pt"><span style="line-height:107%"><span style=""><span style="color:navy">/// query text for hash</span><br /><span style="color:navy">Property </span><span style="color:black">QueryText </span><span style="color:navy">As %String</span><span style="color:black">(</span><span style="color:navy">MAXLEN </span><span style="color:black">= </span><span style="color:navy">9999</span><span style="color:black">);</span><br /><span style="color:navy">Index </span><span style="color:black">IndHash On Hash [ </span><span style="color:navy">IdKey</span><span style="color:black">, </span><span style="color:navy">Unique </span><span style="color:black">];</span><br /><span style="color:black">}</span></span></span></span>
    </p>
    
    <p>
      <span style="font-size:11pt"><span style="line-height:107%"><span style=""><span style="color:navy">/// Summary of very low cost SQL query statistics collected in Cache 2017.1 and later. <br /></span><br /><span style="color:navy">/// Refer to documentation on "SQL Statement Details" for information on the source data. <br /></span><br /><span style="color:navy">/// Data is stored by date and time to support queries over time. <br /></span><br /><span style="color:navy">/// Typically run to summarise the SQL query data from the previous day.</span><br /><span style="color:navy">Class DRL.MonitorSQL Extends %Persistent</span><br /><span style="color:black">{</span><br /><span style="color:navy">/// RunDate and RunTime uniquely identify a run</span><br /><span style="color:navy">Property </span><span style="color:black">RunDate </span><span style="color:navy">As %Date</span><span style="color:black">;</span><br /><span style="color:navy">/// Time the capture was started</span><br /><span style="color:navy">/// RunDate and RunTime uniquely identify a run</span><br /><span style="color:navy">Property </span><span style="color:black">RunTime </span><span style="color:navy">As %Time</span><span style="color:black">;</span><br /><br /><span style="color:navy">/// Count of total hits for the time period for </span><br /><span style="color:navy">Property </span><span style="color:black">TotalHits </span><span style="color:navy">As %Integer</span><span style="color:black">;</span><br /><br /><span style="color:navy">/// Sum of pTime</span><br /><span style="color:navy">Property </span><span style="color:black">SumPTime </span><span style="color:navy">As %Numeric</span><span style="color:black">(</span><span style="color:navy">SCALE </span><span style="color:black">= </span><span style="color:navy">4</span><span style="color:black">);</span><br /><br /><span style="color:navy">/// Routine where SQL is found</span><br /><span style="color:navy">Property </span><span style="color:black">RoutineName </span><span style="color:navy">As %String</span><span style="color:black">(</span><span style="color:navy">MAXLEN </span><span style="color:black">= </span><span style="color:navy">1024</span><span style="color:black">);</span><br /><br /><span style="color:navy">/// Hash of query text</span><br /><span style="color:navy">Property </span><span style="color:black">Hash </span><span style="color:navy">As %String</span><span style="color:black">;</span><br /><br /><span style="color:navy">Property </span><span style="color:black">Variance </span><span style="color:navy">As %Numeric</span><span style="color:black">(</span><span style="color:navy">SCALE </span><span style="color:black">= </span><span style="color:navy">4</span><span style="color:black">);</span><br /><br /><span style="color:navy">/// Namespace where queries are run</span><br /><span style="color:navy">Property </span><span style="color:black">Namespace </span><span style="color:navy">As %String</span><span style="color:black">;</span><br /><br /><span style="color:navy">/// Default run will process the previous days data for a single day.</span><br /><span style="color:navy">/// Other date range combinations can be achieved using the Capture method.</span><br /><span style="color:navy">ClassMethod </span><span style="color:black">Run()</span><br /><span style="color:black">{</span><br />              <span style="color:green">//Each run is identified by the start date / time to keep related items together</span><br />             <br />              <span style="color:blue">set </span><span style="color:olive">h</span><span style="color:black">=</span><span style="color:blue">$h</span><span style="color:black">-1</span><br />              <span style="color:blue">do </span><span style="color:black">..</span><span style="color:blue">Capture</span><span style="color:black">(+</span><span style="color:olive">h</span><span style="color:black">,+</span><span style="color:olive">h</span><span style="color:black">)</span><br /><span style="color:black">}</span><br /><br /><span style="color:navy">/// Captures historic statistics for a range of dates</span><br /><span style="color:navy">ClassMethod </span><span style="color:black">Capture(</span><span style="color:fuchsia">dfrom</span><span style="color:black">, </span><span style="color:fuchsia">dto</span><span style="color:black">)</span><br /><span style="color:black">{</span><br />              <span style="color:blue">set </span><span style="color:olive">oldstatsvalue</span><span style="color:black">=</span><span style="color:blue">$system</span><span style="color:teal">.SQL</span><span style="color:black">.</span><span style="color:blue">SetSQLStatsJob</span><span style="color:black">(-1)</span><br />             <br />                <span style="color:blue">set </span><span style="color:olive">currNS</span><span style="color:black">=</span><span style="color:blue">$znspace</span><br />                <span style="color:blue">set </span><span style="color:olive">tSC</span><span style="color:black">=</span><span style="color:navy">##class</span><span style="color:black">(</span><span style="color:teal">%SYS.Namespace</span><span style="color:black">).</span><span style="color:blue">ListAll</span><span style="color:black">(.</span><span style="color:olive">nsArray</span><span style="color:black">)</span><br />                <span style="color:blue">set </span><span style="color:olive">ns</span><span style="color:black">=</span><span style="color:green">""</span><br />      <span style="color:blue">set </span><span style="color:olive">time</span><span style="color:black">=</span><span style="color:blue">$piece</span><span style="color:black">(</span><span style="color:blue">$h</span><span style="color:black">,</span><span style="color:green">","</span><span style="color:black">,2)</span><br />      <span style="color:blue">kill </span><span style="color:black">^||TMP.MonitorSQL</span><br />                <span style="color:blue">do </span><span style="color:purple">{</span><br />                               <span style="color:blue">set </span><span style="color:olive">ns</span><span style="color:black">=</span><span style="color:blue">$o</span><span style="color:black">(</span><span style="color:olive">nsArray</span><span style="color:black">(</span><span style="color:olive">ns</span><span style="color:black">))</span><br />                               <span style="color:blue">quit</span><span style="color:black">:</span><span style="color:olive">ns</span><span style="color:black">=</span><span style="color:green">""</span><br />                               <span style="color:blue">use </span><span style="color:black">0 </span><span style="color:blue">write </span><span style="color:black">!,</span><span style="color:green">"processing namespace "</span><span style="color:black">,</span><span style="color:olive">ns</span><br />                               <span style="color:blue">zn </span><span style="color:olive">ns</span><br />                                           <span style="color:blue">for </span><span style="color:maroon">dateh</span><span style="color:black">=</span><span style="color:fuchsia">dfrom</span><span style="color:black">:1:</span><span style="color:fuchsia">dto </span><span style="color:purple">{</span><br />                                                          <span style="color:blue">set </span><span style="color:maroon">hash</span><span style="color:black">=</span><span style="color:green">""</span><br />                                                          <span style="color:blue">set </span><span style="color:olive">purgedun</span><span style="color:black">=0</span><br />                                                          <span style="color:blue">do </span><span style="color:purple">{</span><br />                                                                        <span style="color:blue">set </span><span style="color:maroon">hash</span><span style="color:black">=</span><span style="color:blue">$order</span><span style="color:black">(^rINDEXSQL(</span><span style="color:green">"sqlidx"</span><span style="color:black">,1,</span><span style="color:maroon">hash</span><span style="color:black">))</span><br />                                                                        <span style="color:blue">continue</span><span style="color:black">:</span><span style="color:maroon">hash</span><span style="color:black">=</span><span style="color:green">""</span><br />                                                                        <span style="color:blue">set </span><span style="color:olive">stats</span><span style="color:black">=</span><span style="color:blue">$get</span><span style="color:black">(^rINDEXSQL(</span><span style="color:green">"sqlidx"</span><span style="color:black">,1,</span><span style="color:maroon">hash</span><span style="color:black">,</span><span style="color:green">"stat"</span><span style="color:black">,</span><span style="color:maroon">dateh</span><span style="color:black">))</span><br />                                                                        <span style="color:blue">continue</span><span style="color:black">:</span><span style="color:olive">stats</span><span style="color:black">=</span><span style="color:green">""</span><br />                                                                        <span style="color:blue">set </span><span style="color:black">^||TMP.MonitorSQL(</span><span style="color:maroon">dateh</span><span style="color:black">,</span><span style="color:olive">ns</span><span style="color:black">,</span><span style="color:maroon">hash</span><span style="color:black">)=</span><span style="color:olive">stats</span><br />                                                                       <br />                                                          <span style="color:purple">&SQL(</span><span style="color:blue">SELECT  </span><span style="color:green">Location </span><span style="color:navy">into </span><span style="color:maroon">:tLocation </span><span style="color:navy">FROM </span><span style="color:green">INFORMATION_SCHEMA</span><span style="color:black">.</span><span style="color:green">STATEMENT_LOCATIONS </span><span style="color:navy">WHERE </span><span style="color:green">Statement</span><span style="color:black">=</span><span style="color:maroon">:hash</span><span style="color:purple">)</span><br />                                                                        <span style="color:blue">if </span><span style="color:maroon">SQLCODE</span><span style="color:black">'=0 </span><span style="color:blue">set </span><span style="color:olive">Location</span><span style="color:black">=</span><span style="color:green">""</span><br />                                                                        <span style="color:blue">set </span><span style="color:black">^||TMP.MonitorSQL(</span><span style="color:maroon">dateh</span><span style="color:black">,</span><span style="color:olive">ns</span><span style="color:black">,</span><span style="color:maroon">hash</span><span style="color:black">,</span><span style="color:green">"Location"</span><span style="color:black">)=</span><span style="color:maroon">tLocation</span><br />                                                                       <br />                                                          <span style="color:purple">&SQL(</span><span style="color:blue">SELECT  </span><span style="color:green">Statement </span><span style="color:navy">INTO </span><span style="color:maroon">:Statement </span><span style="color:navy">FROM </span><span style="color:green">INFORMATION_SCHEMA</span><span style="color:black">.</span><span style="color:green">STATEMENTS </span><span style="color:navy">WHERE </span><span style="color:green">Hash</span><span style="color:black">=</span><span style="color:maroon">:hash</span><span style="color:purple">)</span><br />                                                                        <span style="color:blue">if </span><span style="color:maroon">SQLCODE</span><span style="color:black">'=0 </span><span style="color:blue">set </span><span style="color:maroon">Statement</span><span style="color:black">=</span><span style="color:green">""</span><br />                                                                        <span style="color:blue">set </span><span style="color:black">^||TMP.MonitorSQL(</span><span style="color:maroon">dateh</span><span style="color:black">,</span><span style="color:olive">ns</span><span style="color:black">,</span><span style="color:maroon">hash</span><span style="color:black">,</span><span style="color:green">"QueryText"</span><span style="color:black">)=</span><span style="color:maroon">Statement</span><br />                                                          <span style="color:purple">} </span><span style="color:blue">while </span><span style="color:maroon">hash</span><span style="color:black">'=</span><span style="color:green">""</span><br />                                                         <br />                                           <span style="color:purple">}</span><br />                <span style="color:purple">} </span><span style="color:blue">while </span><span style="color:olive">ns</span><span style="color:black">'=</span><span style="color:green">""</span><br />                <span style="color:blue">zn </span><span style="color:olive">currNS</span><br />                <span style="color:blue">set </span><span style="color:maroon">dateh</span><span style="color:black">=</span><span style="color:green">""</span><br />                <span style="color:blue">do </span><span style="color:purple">{</span><br />                               <span style="color:blue">set </span><span style="color:maroon">dateh</span><span style="color:black">=</span><span style="color:blue">$o</span><span style="color:black">(^||TMP.MonitorSQL(</span><span style="color:maroon">dateh</span><span style="color:black">))</span><br />                               <span style="color:blue">quit</span><span style="color:black">:</span><span style="color:maroon">dateh</span><span style="color:black">=</span><span style="color:green">""</span><br />                               <span style="color:blue">set </span><span style="color:olive">ns</span><span style="color:black">=</span><span style="color:green">""</span><br />                               <span style="color:blue">do </span><span style="color:purple">{</span><br />                                             <span style="color:blue">set </span><span style="color:olive">ns</span><span style="color:black">=</span><span style="color:blue">$o</span><span style="color:black">(^||TMP.MonitorSQL(</span><span style="color:maroon">dateh</span><span style="color:black">,</span><span style="color:olive">ns</span><span style="color:black">))</span><br />                                             <span style="color:blue">quit</span><span style="color:black">:</span><span style="color:olive">ns</span><span style="color:black">=</span><span style="color:green">""</span><br />                                             <span style="color:blue">set </span><span style="color:maroon">hash</span><span style="color:black">=</span><span style="color:green">""</span><br />                                             <span style="color:blue">do </span><span style="color:purple">{</span><br />                                                         <span style="color:blue">set </span><span style="color:maroon">hash</span><span style="color:black">=</span><span style="color:blue">$o</span><span style="color:black">(^||TMP.MonitorSQL(</span><span style="color:maroon">dateh</span><span style="color:black">,</span><span style="color:olive">ns</span><span style="color:black">,</span><span style="color:maroon">hash</span><span style="color:black">))</span><br />                                                          <span style="color:blue">quit</span><span style="color:black">:</span><span style="color:maroon">hash</span><span style="color:black">=</span><span style="color:green">""</span><br />                                                         <span style="color:blue">set </span><span style="color:olive">stats</span><span style="color:black">=</span><span style="color:blue">$g</span><span style="color:black">(^||TMP.MonitorSQL(</span><span style="color:maroon">dateh</span><span style="color:black">,</span><span style="color:olive">ns</span><span style="color:black">,</span><span style="color:maroon">hash</span><span style="color:black">))</span><br />                                                          <span style="color:blue">continue</span><span style="color:black">:</span><span style="color:olive">stats</span><span style="color:black">=</span><span style="color:green">""</span><br />                                                          <span style="color:green">// The first time through the loop delete all statistics for the day so it is re-runnable</span><br />                                                          <span style="color:green">// But if we run for a day after the raw data has been purged, it will wreck eveything</span><br />                                                          <span style="color:green">// so do it here, where we already know there are results to insert in their place.</span><br />                                                          <span style="color:blue">if </span><span style="color:olive">purgedun</span><span style="color:black">=0 </span><span style="color:purple">{</span><br />                                                                        <span style="color:purple">&SQL(</span><span style="color:blue">DELETE </span><span style="color:navy">FROM </span><span style="color:green">websys</span><span style="color:black">.</span><span style="color:green">MonitorSQL </span><span style="color:navy">WHERE </span><span style="color:green">RunDate</span><span style="color:black">=</span><span style="color:maroon">:dateh </span><span style="color:purple">)</span><br />                                                                        <span style="color:blue">set </span><span style="color:olive">purgedun</span><span style="color:black">=1</span><br />                                                          <span style="color:purple">}</span><br />                                                         <br />                                                          <span style="color:blue">set </span><span style="color:olive">tObj</span><span style="color:black">=</span><span style="color:navy">##class</span><span style="color:black">(</span><span style="color:teal">DRL.MonitorSQL</span><span style="color:black">).</span><span style="color:blue">%New</span><span style="color:black">()</span><br /><br />                                                          <span style="color:blue">set </span><span style="color:olive">tObj</span><span style="color:black">.</span><span style="color:blue">Namespace</span><span style="color:black">=</span><span style="color:olive">ns</span><br />                                                          <span style="color:blue">set </span><span style="color:olive">tObj</span><span style="color:black">.</span><span style="color:blue">RunDate</span><span style="color:black">=</span><span style="color:maroon">dateh</span><br />                                                          <span style="color:blue">set </span><span style="color:olive">tObj</span><span style="color:black">.</span><span style="color:blue">RunTime</span><span style="color:black">=</span><span style="color:olive">time</span><br />                                                          <span style="color:blue">set </span><span style="color:olive">tObj</span><span style="color:black">.</span><span style="color:blue">Hash</span><span style="color:black">=</span><span style="color:maroon">hash</span><br />                                                          <span style="color:blue">set </span><span style="color:olive">tObj</span><span style="color:black">.</span><span style="color:blue">TotalHits</span><span style="color:black">=</span><span style="color:blue">$listget</span><span style="color:black">(</span><span style="color:olive">stats</span><span style="color:black">,1)</span><br />                                                          <span style="color:blue">set </span><span style="color:olive">tObj</span><span style="color:black">.</span><span style="color:blue">SumPTime</span><span style="color:black">=</span><span style="color:blue">$listget</span><span style="color:black">(</span><span style="color:olive">stats</span><span style="color:black">,2)</span><br />                                                          <span style="color:blue">set </span><span style="color:olive">tObj</span><span style="color:black">.</span><span style="color:blue">Variance</span><span style="color:black">=</span><span style="color:blue">$listget</span><span style="color:black">(</span><span style="color:olive">stats</span><span style="color:black">,3)</span><br />                                                          <span style="color:blue">set </span><span style="color:olive">tObj</span><span style="color:black">.</span><span style="color:blue">Variance</span><span style="color:black">=</span><span style="color:blue">$listget</span><span style="color:black">(</span><span style="color:olive">stats</span><span style="color:black">,3)</span><br />                                                         <br />                                                         <span style="color:blue">set </span><span style="color:olive">queryText</span><span style="color:black">=^||TMP.MonitorSQL(</span><span style="color:maroon">dateh</span><span style="color:black">,</span><span style="color:olive">ns</span><span style="color:black">,</span><span style="color:maroon">hash</span><span style="color:black">,</span><span style="color:green">"QueryText"</span><span style="color:black">)</span><br />                                                         <span style="color:blue">set </span><span style="color:olive">tObj</span><span style="color:black">.</span><span style="color:blue">RoutineName</span><span style="color:black">=^||TMP.MonitorSQL(</span><span style="color:maroon">dateh</span><span style="color:black">,</span><span style="color:olive">ns</span><span style="color:black">,</span><span style="color:maroon">hash</span><span style="color:black">,</span><span style="color:green">"Location"</span><span style="color:black">)</span><br />                                                         <br />                                                    <span style="color:purple">&SQL(</span><span style="color:blue">Select </span><span style="color:green">ID </span><span style="color:navy">into </span><span style="color:maroon">:TextID </span><span style="color:navy">from </span><span style="color:green">DRL</span><span style="color:black">.</span><span style="color:green">MonitorSQLText </span><span style="color:navy">where </span><span style="color:green">Hash</span><span style="color:black">=</span><span style="color:maroon">:hash</span><span style="color:purple">)</span><br />                                                          <span style="color:blue">if </span><span style="color:maroon">SQLCODE</span><span style="color:black">'=0 </span><span style="color:purple">{</span><br />                                                                        <span style="color:blue">set </span><span style="color:olive">textref</span><span style="color:black">=</span><span style="color:navy">##class</span><span style="color:black">(</span><span style="color:teal">DRL.MonitorSQLText</span><span style="color:black">).</span><span style="color:blue">%New</span><span style="color:black">()</span><br />                                                                        <span style="color:blue">set </span><span style="color:olive">textref</span><span style="color:black">.</span><span style="color:blue">Hash</span><span style="color:black">=</span><span style="color:olive">tObj</span><span style="color:black">.</span><span style="color:blue">Hash</span><br />                                                                        <span style="color:blue">set </span><span style="color:olive">textref</span><span style="color:black">.</span><span style="color:blue">QueryText</span><span style="color:black">=</span><span style="color:olive">queryText</span><br />                                                                        <span style="color:blue">set </span><span style="color:olive">sc</span><span style="color:black">=</span><span style="color:olive">textref</span><span style="color:black">.</span><span style="color:blue">%Save</span><span style="color:black">()</span><br />                                                          <span style="color:purple">}                                                        </span><br />                                                          <span style="color:blue">set </span><span style="color:olive">tSc</span><span style="color:black">=</span><span style="color:olive">tObj</span><span style="color:black">.</span><span style="color:blue">%Save</span><span style="color:black">()</span><br />                                                         <br />                                                          <span style="color:green">//avoid dupicating the query text in each record because it can be very long. Use a lookup</span><br />                                                          <span style="color:green">//table keyed on the hash. If it doesn't exist add it.</span><br />                                                          <span style="color:blue">if $$$ISERR</span><span style="color:black">(</span><span style="color:olive">tSc</span><span style="color:black">) </span><span style="color:blue">do $system</span><span style="color:teal">.OBJ</span><span style="color:black">.</span><span style="color:blue">DisplayError</span><span style="color:black">(</span><span style="color:olive">tSc</span><span style="color:black">)</span><br />                                                         <br /><br />                                                          <span style="color:blue">if $$$ISERR</span><span style="color:black">(</span><span style="color:olive">tSc</span><span style="color:black">) </span><span style="color:blue">do $system</span><span style="color:teal">.OBJ</span><span style="color:black">.</span><span style="color:blue">DisplayError</span><span style="color:black">(</span><span style="color:olive">tSc</span><span style="color:black">)</span><br />                                             <span style="color:purple">} </span><span style="color:blue">while </span><span style="color:maroon">hash</span><span style="color:black">'=</span><span style="color:green">""</span><br />                               <span style="color:purple">} </span><span style="color:blue">while </span><span style="color:olive">ns</span><span style="color:black">'=</span><span style="color:green">""</span><br />                                                         <br />                <span style="color:purple">} </span><span style="color:blue">while </span><span style="color:maroon">dateh</span><span style="color:black">'=</span><span style="color:green">""</span><br />                <br />                <br />             <br />              <span style="color:blue">do $system</span><span style="color:teal">.SQL</span><span style="color:black">.</span><span style="color:blue">SetSQLStatsJob</span><span style="color:black">(0)</span><br /><span style="color:black">}</span><br /><br /><span style="color:navy">Query </span><span style="color:black">Export(</span><span style="color:fuchsia">RunDateH1 </span><span style="color:navy">As %Date</span><span style="color:black">, </span><span style="color:fuchsia">RunDateH2 </span><span style="color:navy">As %Date</span><span style="color:black">) </span><span style="color:navy">As %SQLQuery</span><br /><span style="color:black">{</span><br /><span style="color:blue">SELECT </span><span style="color:green">S</span><span style="color:black">.</span><span style="color:green">Hash</span><span style="color:black">,</span><span style="color:green">RoutineName</span><span style="color:black">,</span><span style="color:green">RunDate</span><span style="color:black">,</span><span style="color:green">RunTime</span><span style="color:black">,</span><span style="color:green">SumPTime</span><span style="color:black">,</span><span style="color:green">TotalHits</span><span style="color:black">,</span><span style="color:green">Variance</span><span style="color:black">,</span><span style="color:green">RoutineName</span><span style="color:black">,</span><span style="color:green">T</span><span style="color:black">.</span><span style="color:green">QueryText</span><br />              <span style="color:navy">FROM DRL.</span><span style="color:green">MonitorSQL S </span><span style="color:navy">LEFT JOIN DRL.</span><span style="color:green">MonitorSQLText T </span><span style="color:navy">on </span><span style="color:green">S</span><span style="color:black">.</span><span style="color:green">Hash</span><span style="color:black">=</span><span style="color:green">T</span><span style="color:black">.</span><span style="color:green">Hash</span><br />              <span style="color:navy">WHERE </span><span style="color:green">RunDate</span><span style="color:black">>=</span><span style="color:maroon">:RunDateH1 </span><span style="color:black">AND </span><span style="color:green">RunDate</span><span style="color:black"><=</span><span style="color:maroon">:RunDateH2</span><br /><span style="color:black">}</span><br /><span style="color:black">}</span></span></span></span>
    </p>   
    
    <p>
       
    </p>
    
    0
    0 615
    文章 Stefan Wittmann · 一月 4, 2021 4m read

    最近我一直在发布对 JSON 功能的一些更新,我很高兴许多人提供了反馈。 今天,我想将重点放在另一个方面:使用 SQL 查询生成 JSON。

    显然,SQL 是从关系模型中检索记录的重要概念。 假设你要查询数据并将其以简单 JSON 结构的形式公开给 REST 服务, 通常,你必须查询数据,然后对结果集进行遍历,最后为每条记录构造一个 JSON 结构。 你需要编写自定义代码。

    我们添加了标准 SQL 函数 JSON_OBJECTJSON_ARRAY,这样可以更容易地直接从 SQL 查询生成 JSON,而无需编写代码。 这两个函数是 Caché 2016.2 开始新增的。

    以下表 Baking.Pastry 为例:

    <td>
      类型
    </td>
    
    <td>
      说明
    </td>
    
    <td>
      Choux Pastry
    </td>
    
    <td>
      A light pastry that is often filled with cream
    </td>
    
    <td>
      Puff Pastry
    </td>
    
    <td>
      Many layers that cause the pastry to expand or puff when baked
    </td>
    
    <td>
      Filo Pastry
    </td>
    
    <td>
      Multiple layers of a paper-thin dough wrapped around a filling
    </td>
    
    1
    2
    3

    JSON_OBJECT

    JSON_OBJECT 是一个接受多个键值对并生成 JSON 对象的函数。

    SELECT JSON_OBJECT('pastry_type' : Type, 'pastry_description' : Description) AS pastryJSON FROM Baking.Pastry
    
    Row    pastryJSON
    
    ----   ---------------
    
    1      {"pastry_type" : "Choux Pastry", "pastry_description" : "A light pastry that is often filled with cream"}
    
    2      {"pastry_type" : "Puff Pastry", "pastry_description" : "Many layers that cause the pastry to expand or puff when baked"}
    
    3      {"pastry_type" : "Filo Pastry", "pastry_description" : " Multiple layers of a paper-thin dough wrapped around a filling"}
    

    在此例中,JSON_OBJECT 函数为每条记录生成一个 JSON 对象。 每个对象都包含两个属性 pastry_typepastry_description,因为我们为函数提供了两个参数。 每个参数由两部分组成,用冒号分隔:

    1. 应注入到JSON对象中的键的名称
    2. 与该键关联的字段值

    此示例设置了静态键,因为我只提供了字符串文字,例如“pastry_type”。 对于字段值(我指的是列),例如 Type,无论该列的内容是什么,都将设置为关联键的值。 这是构造 JSON 对象的常见用例,但是通过为键传递列,还可以动态创建键。

    JSON_ARRAY

    JSON_ARRAY 的工作方式类似。 它构造一个 JSON 数组,每传递一个参数都会将相应的值推送到数组中。

    SELECT JSON_ARRAY(Type, Description) AS pastryJSON FROM Baking.Pastry
    
    
    Row  pastryJSON
    
    ---- ---------------
    
    1    ["Choux Pastry" , "A light pastry that is often filled with cream"]
    
    2    ["Puff Pastry" , "Many layers that cause the pastry to expand or puff when baked"]
    
    3    ["Filo Pastry" , "Multiple layers of a paper-thin dough wrapped around a filling"]
    

    JSON_ARRAY 是一个非常简单的函数。 此示例创建一个数组,其中每行包含两个元素。 第一项由列 Type 的值填充,而第二项由列 Description 的值填充。

    高级方案

    也许你需要创建一个更复杂的 JSON 结构。 值参数可以是嵌套的 JSON_ARRAYJSON_OBECT 函数调用,从而可以构造嵌套的 JSON 结构。 我们回到第一个示例,将 JSON 对象包装在标头结构中:

    SELECT JSON_OBJECT('food_type' : 'pastry', 'details' : JSON_OBJECT('type' : Type, 'description' : Description)) AS pastryJSON FROM Baking.Pastry
    
    
    Row  pastryJSON
    
    ---- ---------------
    
    1    {"food_type" : "pastry", "details" : {"type" : "Choux Pastry", "description" : "A light pastry that is often filled with cream"}}
    
    2    {"food_type" : "pastry", "details" : {"type" : "Puff Pastry", "description" : " Many layers that cause the pastry to expand or puff when baked"}}
    
    3    {"food_type" : "pastry", "details" : {"type" : "Filo Pastry", "description" : " Multiple layers of a paper-thin dough wrapped around a filling"}}
    

    我们计划在将来的版本中实现更多的 JSON SQL 函数,但这两个函数是坚实的开始。 主要用例是从关系型数据构造简单的 JSON 元素,而无需编写代码。 这样即使无法更改后端代码,也可以从系统直接发布 JSON。

    要创建更复杂的结构,使用新的复合接口可以更高效地构建,该接口允许将Persistent/Registered对象转换为动态对象/数组。 然后可以根据需要轻松修改结构,最后通过 $toJSON() 调用将其导出为 JSON 字符串。 我会在将来的帖子中更详细地介绍这个主题。

    0
    3 950