#InterSystems IRIS for Health

0 关注者 · 862 帖子

InterSystems IRIS for Health™ 是全球第一个也是唯一一个专门为医疗应用程序的快速开发而设计的数据平台,用于管理全世界最重要的数据。它包括强大的开箱即用的功能:事务处理和分析、可扩展的医疗保健数据模型、基于 FHIR 的解决方案开发、对医疗保健互操作性标准的支持等等。所有这些将使开发者能够快速实现价值并构建具有突破性的应用程序。了解更多信息

公告 Nicky Zhu · 三月 30, 2021

2021年3月23日 – 提醒:镜像Dejournaling的潜在数据完整性问

InterSystems 已经纠正了一个缺陷,该缺陷在极少数情况下会导致非主镜像成员的数据不一致问题。此缺陷影响到InterSystems产品的所有发布版本。

如果发生该缺陷,则会在镜像系统的正常运行中悄然发生。该缺陷的结果是,镜像成员未能删除数据库日志的子集,然后导致镜像成员之间的数据不一致。这对故障转移和异步成员都有影响。虽然这个缺陷遇到的可能性非常小,但InterSystems建议所有使用镜像的客户都要获得修正,并使用DataCheck验证镜像数据库的一致性。有关可能触发该缺陷的具体情况以及将遇到该缺陷的风险降至最低的步骤的更多详细信息,请联系全球响应中心(WRC)。

该缺陷的更正被确定为 SML2898、HYY2434 和 HYY2435,并将包含在所有未来的产品版本中,包括 Caché 和 Ensemble 2018.1.5 以及 InterSystems IRIS 和 InterSystems IRIS for Health 2019.1.2 和 2020.1.1。这些更正也可通过InterSystems全球响应中心(WRC)的Ad hoc分发版本获得。

如果您对这个提醒有任何疑问,请联系 Worldwide Response Center

0
0 171
公告 Nicky Zhu · 三月 30, 2021

现已推出三套新的维护版本:

  • Caché  2018.1.5, Ensemble 2018.1.5, and HSAP 2018.1.5
  • InterSystems IRIS 2019.1.2, IRIS for Health 2019.1.2, and HealthShare Health Connect 2019.1.2
  • InterSystems IRIS 2020.1.1, IRIS for Health 2020.1.1, and HealthShare Health Connect 2020.1.1

安装包和容器版本可从以下网站下载  WRC Software Distribution site

这些都是维护版本,在广泛的领域内有许多更新。  有关这些版本中的修正信息,请参考该版本的文档,其中包括发行说明和升级检查表、发行变更列表,以及类参考资料和全套指南、参考资料、教程和文章。所有的文档都可以通过以下方式获得 docs.intersystems.com 。

在这些版本中也加入了新的平台支持。  特别是,Ubuntu 20.04 LTS的支持已经添加到所有版本中,IBM AIX 7.1和7.2对System p-64的支持已经添加到2019.1.2中(并且已经在2020.1中),而对Linux的ARM64支持已经添加到2020.1.1中。  详情请参见各版本的支持平台文档。

0
0 353
文章 姚 鑫 · 三月 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
文章 Qiao Peng · 三月 29, 2021 4m read

 

前面介绍了通过mirroring或shadow,使用journal日志过滤器的方式,在不改动数据模型的情况下实现InterSystems IRIS/Caché上的CDC能力。
但如果你可以修改InterSystems IRIS/Caché上的数据模型,也可以考虑使用DSTIME特性实现变更数据捕获。

DSTIME特性

DSTIME特性是InterSystems IRIS/Caché的嵌入式实时BI工具DeepSee用于跟踪数据变更的。
InterSystems IRIS和2011版之后的Caché,都支持DSTIME特性。它会自动记录数据库中SQL表记录或持久化对象的变更,并将变更记录写入持久化的多维数组^OBJ.DSTIME中。

DSTIME特性是针对于SQL表/持久化类的,因此需要为每个需要记录变化的表/持久化类开启。
当开启了DSTIME的表/持久化类,有记录插入、更新、删除时,InterSystems IRIS/Caché引擎会自动在^OBJ.DSTIME中记录这些操作。其格式为:
^OBJ.DSTIME(类名,DSTIME,对象ID) = 执行的操作代码

DSTIME:
当SQL表/持久化类的参数DSINTERVAL未被设置时,DSTIME=0;
当SQL表/持久化类的参数DSINTERVAL被设置时,DSTIME=1840/12/31午夜12点到记录发生时的秒数。

执行的操作代码

0
0 556
文章 Qiao Peng · 三月 29, 2021 8m read

 

一些熟悉SQL的用户希望用SQL表的方式获取InterSystems IRIS/Caché的变更数据。知道了Global和SQL表的对应关系,就可以知道是哪一张SQL表数据变化了,甚至通过SQL查询获取变更的数据。
下面介绍如何实现这种方式,和注意事项。

获取Global和SQL表的对应关系

通常InterSystems IRIS/Caché的持久化的对象模型(类)和SQL表之间有一一对应的关系;而持久化的对象模型和Global之间也有一一对应关系。建立Global和SQL表的对应关系,通常可以使用以下的SQL查询特定SQL schema下所有表对应的Global:

SELECT CC.SqlQualifiedNameQ SQLTable, CS.parent Class, CS.DataLocation 
FROM %Dictionary.CompiledStorage CS, %Dictionary.CompiledClass CC
WHERE CS.parent = CC.ID 
AND CC.SqlSchemaName= <schemaname>

其中<schemaname>是SQL的Schema名称; 返回字段SQLTable是SQL表名、Class是对象类名、DataLocation是保存数据的Global名称。

多种建模方式Global和SQL表的对应关系的影响

0
1 1008
文章 Qiao Peng · 三月 29, 2021 2m read

Caché Shadow

上次介绍了使用InterSystems IRIS/Caché的Reporting类型的异步镜像成员获取数据变更。但早期的Caché并不支持镜像,例如Caché 2008,它只支持Shadow。

在这些早期版本上是否也可以实利用Journal实现类似CDC的功能呢?答案是肯定的,就是利用Shadow服务器的Dejournaling filter routine (Dejournaling过滤器routine)。

Dejournaling filter routine

InterSystems Caché的 Dejournaling过滤器routine是一个用户自定义的routine,用于在Shadow服务器上过滤Journal记录。当配置了Dejournaling 过滤器后,Shadow服务器在redo journal前会自动调用Dejournaling过滤器routine。该routine传入参数以下:

  1. pid:记录进程ID;
  2. dir:源数据库目录;
  3. glo:Global节点(包括下标);
  4. type: 操作类型,为S (Set)、K (Kill)、s (BITSET)、k (ZKILL);
  5. addr:Journal记录在Journal文件中的偏移量;
  6. time:记录的时间戳。
0
0 585
文章 姚 鑫 · 三月 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
文章 Qiao Peng · 三月 28, 2021 9m read

        如果您正打算学习FHIR、或者正在基于FHIR开发,最佳的学习和开发环境需要一个完备的FHIR 服务器,帮助您理解FHIR标准和调试FHIR应用。使用InterSystems IRIS医疗行业版可以快速配置出FHIR服务器和FHIR资源仓库。这篇文章介绍如何在5分钟内在InterSystems IRIS医疗行业版上配置FHIR服务器和FHIR资源仓库;同时对初学者介绍FHIR测试数据生成与加载的方法,和基本的FHIR REST API操作。

软件准备

1. InterSystems IRIS医疗行业版

您可以使用已有的2020.1及以上的InterSystems IRIS医疗行业版。如果您还没有,那么就在社区里下载一个最新的、免费的社区版吧。

2. 测试用的FHIR数据

如果您正在做FHIR开发,应该有很多FHIR测试数据。如果您正在学习FHIR,那么Synthea是一个不错的FHIR测试数据生成器选项。

3. REST测试工具

您或许已经有趁手的REST测试工具了。如果还没有,POSTMAN是个不错的选择。我的例子里用到的就是POSTMAN。

FHIR服务器配置

1. 安装InterSystems IRIS医疗行业版或社区版

如果您还没有安装过InterSystems IRIS医疗行业版或社区版,社区里有很多很好的介绍文章。

2. 配置FHIR服务器

0
0 560
文章 姚 鑫 · 三月 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
公告 Claire Zheng · 一月 20, 2021

去年,AWS推出了基于ARM架构的AWS Graviton处理器第一代Amazon EC2 A1实例。在2019年AWS re:Invent 大会上,亚马逊宣布了第二代AWS Graviton2处理器和相关的Amazon EC2 M6g实例。相比前一代基于Intel Xeon的M5实例,Amazon EC2 M6g实例的性价比提升幅度高达40%。

1
0 428
文章 姚 鑫 · 三月 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
文章 姚 鑫 · 三月 8, 2021 9m read

第五章 SQL定义表(三)

使用DDL定义表

可以使用标准DDL命令在InterSystems SQL中定义表:

InterSystems SQL中可用的DDL命令

  • ALTER命令 ALTER TABLEALTER VIEW
  • CREATE 命令 CREATE TABLECREATE VIEWCREATE INDEXCREATE TRIGGER
  • DROP 命令 DROP TABLEDROP VIEWDROP INDEXDROP TRIGGER

可以通过多种方式执行DDL命令,包括:

  • 使用动态SQL。
  • 使用嵌入式SQL。
  • 使用DDL脚本文件。
  • 使用ODBC调用。
  • 使用JDBC调用。

在嵌入式SQL中使用DDL

在ObjectScript方法或例程中,可以使用嵌入式SQL来调用DDL命令。

例如,以下方法创建一个Sample.Employee表:

/// d ##class(PHA.TEST.SQL).CreateTable()
ClassMethod CreateTable() As %String
{
	&sql(CREATE TABLE Sample.Employee (
		EMPNUM              INT NOT NULL,
		NAMELAST            CHAR (30) NOT NULL,
		NAMEFIRST           CHAR (30) NOT NULL,
		STARTDATE           TIMESTAMP,
		SALARY              MONEY,
		ACCRUEDVACATION     INT,
		ACCRUEDSICKLEAVE    INT,
		CONSTRAINT EMPLOYEEPK PRIMARY KEY (EMPNUM)))

	IF SQLCODE=0 {WRITE "Table created"  RETURN "Success"}
	ELSEIF SQLCODE=-201 {WRITE "Table already exists"  RETURN SQLCODE}
	ELSE {WRITE "Serious SQL Error, returning SQLCODE"  RETURN SQLCODE_" "_%msg}
}

DHC-APP>d ##class(PHA.TEST.SQL).CreateTable()
Table already exists

调用此方法时,它将尝试创建Sample.Employee表(以及相应的Sample.Employee类)。如果成功,则将SQLCODE变量设置为0。如果失败,则SQLCODE包含指示错误原因的SQL错误代码。

这样的DDL命令失败的最常见原因是:

  • SQLCODE -99(违反权限):此错误表明没有执行所需DDL命令的权限。通常,这是因为应用程序尚未确定当前用户是谁。可以使用$SYSTEM.Security.Login()方法以编程方式执行此操作:
DHC-APP>w $SYSTEM.Security.Login("yx","123456")
0

SQLCODE -201(表或视图名称不是唯一的):此错误表明正在尝试使用已经存在的表的名称创建新表。

使用类方法执行DDL

在ObjectScript中,可以使用Dynamic SQL%SQL.Statement对象使用Dynamic SQL准备和执行DDL命令。

下面的示例定义了一个使用动态SQL创建表的类方法:

 ClassMethod DefTable(user As %String,pwd As %String) As %Status [Language=objectscript]
{
	DO ##class(%SYSTEM.Security).Login(user,pwd)
	SET myddl=2
	SET myddl(1)="CREATE TABLE Sample.MyTest "
	SET myddl(2)="(NAME VARCHAR(30) NOT NULL,SSN VARCHAR(15) NOT NULL)"
	SET tStatement=##class(%SQL.Statement).%New()
	SET tStatus=tStatement.%Prepare(.myddl)
	IF qStatus'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT}
	SET rset=tStatement.%Execute()
	IF rset.%SQLCODE=0 {WRITE "Created a table"}
	ELSEIF rset.%SQLCODE=-201 {WRITE "table already exists"}
	ELSE {WRITE "Unexpected error SQLCODE=",rset.%SQLCODE}
}

与嵌入式SQL示例一样,如果当前没有用户登录,则此方法将失败。

通过导入和执行DDL脚本定义表

可以使用IRIS()方法从终端会话中交互式地导入InterSystems SQL DDL脚本文件,也可以使用DDLImport(“ IRIS”)方法作为后台作业来导入InterSystems SQL DDL脚本文件。此方法可以导入和执行多个SQL命令,使可以使用txt脚本文件来定义表和视图,并用数据填充它们。

如果要将表从另一供应商的关系数据库迁移到InterSystems IRIS,则文本文件中可能包含一个或多个DDL脚本。 InterSystems IRIS提供了几种%SYSTEM.SQL方法来帮助将此类表加载到InterSystems IRIS中。可以使用通用的DDLImport()方法或特定供应商的%SYSTEM.SQL方法。供应商特定的SQL转换为InterSystems SQL并执行。错误和不支持的功能记录在日志文件中。

例如,从ObjectScript命令行加载一个Oracle DDL文件:

  1. 使用InterSystems IRIS启动器菜单中的“终端”命令启动终端会话。
  2. 切换到希望在其中加载表定义的名称空间:
 SET $namespace = "MYNAMESPACE"
  1. 调用所需的DDL导入方法:
 DO $SYSTEM.SQL.Oracle()

并按照终端上显示的说明进行操作。

image

定义分片表

创建分片表有三个要求。

  1. 许可证密钥必须支持分片。使用管理门户,系统管理,许可,许可证密钥显示当前许可证或激活新许可证。
  2. 必须在IRIS实例上启用分片。必须具有%Admin_Secure特权才能启用分片。使用“管理门户”,“系统管理”,“配置”,“系统配置”,“分片配置”来选择“启用分片”按钮。这使当前的InterSystems IRIS实例可以在分片群集中使用。选择“为任何角色启用此实例”或“仅对碎片主机角色启用此实例”。按确定。重新启动您的InterSystems IRIS实例。
  3. 必须在IRIS实例上部署分片群集。此分片群集包含一个分片主名称空间。如果未为分片配置当前名称空间,则尝试定义分片表失败,并显示错误#9319:当前名称空间%1没有配置分片。

然后,可以在Shard Master命名空间中定义一个分片表,该表已定义为分片集群的一部分。可以使用CREATE TABLE通过指定分片键来定义分片表。或者,可以创建一个持久化类,该持久化类投影到分片表。

通过查询现有表定义表

可以使用$SYSTEM.SQL.QueryToTable()方法基于一个或多个现有表来定义和填充新表。指定一个查询和一个新的表名称。现有表名和/或新表名可以是合格的或不合格的。该查询可以包含JOIN语法。该查询可以提供列名别名,这些别名将成为新表中的列名。

  1. QueryToTable()复制现有表的DDL定义,并为其指定指定的新表名。它复制查询中指定的字段的定义,包括数据类型,maxlengthminval / maxval。它不复制字段数据约束,例如默认值,必需值或唯一值。它不会将引用从字段复制到另一个表。

如果查询指定SELECT *SELECT%ID,则将原始表的RowID字段复制为数据类型为整数的非必需,非唯一数据字段。 QueryToTable()为新表生成唯一的RowID字段。如果复制的RowID名为ID,则生成的RowID名为ID1

QueryToTable()为此新表创建一个对应的持久化类。持久类定义为DdlAllowed。新表的所有者是当前用户。

不管源表中的这些设置如何,新表都将使用Default Storage = YES定义,并且Supports Bitmap Indices = YES

为新表创建的唯一索引是IDKEY索引。没有位图范围索引生成。复制字段的索引定义不会复制到新表中。

  1. QueryToTable()然后使用查询选择的字段中的数据填充新表。它将表格的“范围大小”设置为100,000。它估计IDKEY块计数。运行“音调表”以设置实际的“范围大小”和“块计数”,以及每个字段的“选择性”和“平均字段大小”值。

QueryToTable()既创建表定义,又用数据填充新表。如果只希望创建表定义,请在查询WHERE子句中指定一个不选择任何数据行的条件。例如,WHERE Age < 20 AND Age > 20.

下面的示例从Sample.Person复制“名称”“年龄”字段,并创建一个AVG(Age)字段。这些字段定义用于创建名为Sample.Youth的新表。然后,该方法where Age < 21. 的那些记录的Sample.Person数据填充Sample.YouthAvgInit字段包含创建表时所选记录的合计值。

  DO $SYSTEM.SQL.QueryToTable("SELECT Name,Age,AVG(Age) AS AvgInit FROM Sample.Person WHERE Age < 21","Sample.Youth",1,.errors)
DHC-APP>  DO $SYSTEM.SQL.QueryToTable("SELECT Name,Age,AVG(Age) AS AvgInit FROM Sample.Person WHERE Age < 21","Sample.Youth",1,.errors)
 
Preparing query...
Creating class...
Compiling class...
Copying data...

image

外部表

在InterSystems SQL中,还可以具有“外部表”,这些表在SQL词典中定义但存储在外部关系数据库中。外部表的行为就像它们是本机InterSystems IRIS表一样:可以对它们发出查询并执行INSERTUPDATEDELETE操作。 InterSystems SQL网关提供对外部数据库的访问,该网关使用ODBC或JDBC提供透明的连接。

List表

INFORMATION.SCHEMA.TABLES持久类显示有关当前名称空间中所有表(和视图)的信息。它提供了许多属性,包括模式和表名称,表的所有者以及是否可以插入新记录。 TABLETYPE属性指示它是基表还是视图。

以下示例返回当前名称空间中所有表和视图的表类型,架构名称,表名称和所有者:

SELECT Table_Type,Table_Schema,Table_Name,Owner FROM INFORMATION_SCHEMA.TABLES 

image

INFORMATION.SCHEMA.CONSTRAINTTABLEUSAGE持久类为为当前名称空间中的每个表定义的每个主键(显式或隐式),外键或唯一性约束显示一行。 INFORMATION.SCHEMA.KEYCOLUMNUSAGE为定义为当前名称空间中每个表的这些约束之一的一部分的每个字段显示一行。

列出列名和数字

可以通过以下四种方式列出指定表的所有列名(字段名):

  • GetColumns()方法。这列出了所有列名和列号,包括隐藏的列。 ID(RowID)字段可以隐藏也可以不隐藏。 x__classname列始终是隐藏的;除非使用Final class关键字定义了持久类,否则它将自动定义。
  • 管理门户网站SQL界面(系统资源管理器,SQL)架构内容的“目录详细信息”选项卡。它列出了所有列名和列号(包括隐藏的列)以及其他信息,包括数据类型和指示列是否被隐藏的标志。
  • SELECT TOP 0 * FROM表名。这将按列号顺序列出所有非隐藏的列名。请注意,由于隐藏的列可以按列号顺序出现在任何位置,因此您无法通过计算这些非隐藏的列名来确定列号。
  • INFORMATION.SCHEMA.COLUMNS持久类为当前名称空间中每个表或视图中的每个非隐藏列列出一行。 INFORMATION.SCHEMA.COLUMNS提供了大量属性,用于列出表和视图列的特征。请注意,ORDINALPOSITION与列号不同,因为不计算隐藏字段。 GetColumns()方法同时计算隐藏字段和非隐藏字段。

下面的示例使用INFORMATION.SCHEMA.COLUMNS列出一些列属性:

SELECT TABLE_NAME,COLUMN_NAME,ORDINAL_POSITION,DATA_TYPE,CHARACTER_MAXIMUM_LENGTH,
       COLUMN_DEFAULT,IS_NULLABLE,UNIQUE_COLUMN,PRIMARY_KEY 
       FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='Sample'

image

GetColumns()方法

要以列号顺序列出表中的列名,可以使用GetColumns()方法,如下所示:

/// d ##class(PHA.TEST.SQL).GetColumn()
ClassMethod GetColumn()
{
	SET stat=##class(%SYSTEM.SQL).GetColumns("Sample.Person",.byname,.bynum)
	IF stat=1 {
		SET i=1
		WHILE $DATA(bynum(i)) { 
			WRITE "name is ",bynum(i),"   col num is ",i,!
            SET i=i+1 
			}
	}
	ELSE { WRITE "GetColumns()无法找到指定的表" }
}

GetColumns()列出所有已定义的列,包括隐藏的列。如果表引用了嵌入式%SerialObject类,则GetColumns()首先列出持久性类中的所有列,包括引用%SerialObject的属性,然后列出所有%SerialObject属性。在下面的GetColumns()结果中显示了这一点:

DHC-APP>d ##class(PHA.TEST.SQL).GetColumn()
name is ID   col num is 1
name is Age   col num is 2
name is DOB   col num is 3
name is FavoriteColors   col num is 4
name is Home   col num is 5
name is Name   col num is 6
name is Office   col num is 7
name is SSN   col num is 8
name is Spouse   col num is 9
name is x__classname   col num is 10
name is Home_City   col num is 11
name is Home_State   col num is 12
name is Home_Street   col num is 13
name is Home_Zip   col num is 14
name is Office_City   col num is 15
name is Office_State   col num is 16
name is Office_Street   col num is 17
name is Office_Zip   col num is 18

还可以使用此方法确定指定列名的列号,如下所示:

/// d ##class(PHA.TEST.SQL).GetColumn1()
ClassMethod GetColumn1()
{
	SET stat=##class(%SYSTEM.SQL).GetColumns("Sample.Person",.byname)
	IF stat=1 {
		WRITE "Home_State is column number ",byname("Home_State"),!  
	} ELSE { 
		WRITE "GetColumns()无法找到指定的表" 
	}
}
DHC-APP>d ##class(PHA.TEST.SQL).GetColumn1()
Home_State is column number 12
0
0 187