0 关注者 · 478 帖子

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

文章 姚 鑫 · 十月 2, 2021 6m read

第三十三章 SQL命令 DROP INDEX

删除索引。

大纲

DROP INDEX index-name [ON [TABLE] table-name]

DROP INDEX table-name.index-name

参数

  • index-name - 要删除的索引的名称。Index-name是名称的SQL版本,可以包括下划线和其他标点符号。它作为SQL映射名称列在表的Management Portal SQL Catalog Details中。
  • ON table-nameON TABLE table-name - 可选-与索引关联的表的名称。可以使用任一语法指定表名:第一个语法使用ON子句;TABLE关键字是可选的。第二个语法使用限定名称语法schema-name.table-name.index-name。表名可以是限定的(schema.table),也可以是非限定的(Table)。未限定的表名采用缺省模式名。如果完全省略表名, IRIS将删除找到的第一个与index-name匹配的索引,如下所述。

描述

DROP INDEX语句从表定义中删除索引。可以使用DROP INDEX删除标准索引、位图索引或位片索引。通过删除相应的唯一索引,可以使用DROP INDEX删除唯一约束或主键约束。不能使用DROP INDEX删除位图范围索引或主地图(数据/主)IDKEY索引。

可能出于以下任一原因希望删除索引:

  • 打算对表执行大量的INSERTUPDATEDELETE操作。可以对操作使用%noindex选项,而不是接受让这些操作中的每个操作都写入索引的性能开销。或者,在某些情况下,可能更可取的做法是删除索引,对数据库执行批量更改,然后重新创建索引并填充它。
  • 不用于查询操作的字段或字段组合存在索引。在这种情况下,维护索引的性能开销可能不值得。
  • 现在包含大量重复数据的字段或字段组合存在索引。在这种情况下,查询性能的最小收益可能不值得。

如果表中有数据,则不能删除IDKEY索引。尝试这样做会生成SQLCODE-325错误。

权限与锁

DROP INDEX命令是特权操作。用户必须具有%ALTER_TABLE管理权限才能执行DROP INDEX。否则将导致%msg用户‘name’没有%ALTER_TABLE权限的SQLCODE-99错误。如果拥有适当的授予权限,则可以使用GRANT命令将%ALTER_TABLE权限分配给用户或角色。管理权限是特定于命名空间的。

用户必须对指定表拥有%ALTER特权。如果用户是表的所有者(创建者),则会自动授予该用户对该表的%ALTER权限。否则,必须授予用户对该表的%ALTER特权。否则将导致SQLCODE-99错误,因为%msg用户‘name’没有更改‘Schema.TableName’的表定义所需的%ALTER特权。可以通过调用%CHECKPRIV命令来确定当前用户是否具有%ALTER特权。可以使用GRANT命令将%ALTER权限分配给指定表。

  • 不能对从持久类投影的表使用DROP INDEX,除非表类定义包括[DdlAllowed]。否则,操作将失败,并出现SQLCODE-300错误,同时未为类‘Schema.tablename’启用%msg DDL
  • DROP INDEX不能用于从部署的持久类投射的表。此操作失败,并出现SQLCODE-400错误,并显示%msg Unable to Execute DDL以修改已部署的类:‘classname’

DROP INDEX语句获取对table-name的表级锁。这可以防止其他进程修改表的数据。此锁在DROP INDEX操作结束时自动释放。

索引名称

当指定索引名来创建索引时,系统会通过剔除所有标点符号来生成相应的类索引名;它会将在类中指定的索引名保留为索引的SqlName值(SQL映射名称)。指定要删除索引的索引名时,需要指定包含标点符号的名称,标点符号将作为SQL映射名称在表的管理门户SQL目录详细资料中列出。例如,为唯一约束(MyTable_UNIQUE2)指定生成的SQL映射名称,而不是为索引名称(MYTABLEUNIQUE2)指定。此索引名称不区分大小写。

表名

可以使用DROP INDEX语法FORM指定与索引关联的表:

  • INDEX-NAME ON TABLE语法:指定表名称是可选的。如果省略, IRIS将在命名空间中的所有类中搜索相应的索引。

  • Table-name.index-name语法:需要指定表名。

在这两种语法中,表名可以是非限定的(Table),也可以是限定的(schema.table)。如果省略架构名称,则使用默认架构名称。

如果DROP INDEX没有指定表名, IRIS会在所有索引中搜索与索引名匹配的索引名SqlName,或者与索引名匹配的索引名SqlName,以查找没有为索引指定SQlname的索引。如果IRIS在任何类中都找不到匹配的索引,就会产生一个SQLCODE -333错误,表明不存在这样的索引。如果 IRIS找到多个匹配的索引,DROP INDEX无法确定要删除哪个索引;它会发出SQLCODE -334错误:“索引名称不明确。在多个表中找到索引。IRIS中的索引名称在每个命名空间中不是唯一的。

不存在的索引

默认情况下,如果尝试删除不存在的索引,DROP INDEX会默认发出SQLCODE-333错误。要确定当前设置,请调用$SYSTEM.SQL.CurrentSettings(),它显示一个允许DDL丢弃不存在的索引设置。默认值为0(“否”)。这是推荐的设置。如果设置为1(“是”),则不存在索引的DROP INDEX不执行任何操作,也不发出错误消息。

在管理门户、系统管理、配置、SQL和对象设置中,通过选中忽略冗余DDL语句复选框,可以在系统范围内设置此选项(以及其他类似的创建、更改和删除选项)。

表名

如果指定可选的table-name,则它必须与现有表相对应。

  • 如果指定的表名不存在, IRIS将发出SQLCODE-30错误,并将%msg设置为表‘SQLUser.tname’不存在。
  • 如果指定的表名称存在,但没有名为index-name的索引, IRIS将发出SQLCODE-333错误,并将%msg设置为尝试删除表SQLUSER.TNAME上的索引‘MyIndex’-未找到索引。
  • 如果指定的表名是视图, IRIS将发出SQLCODE-333错误,并将%msg设置为尝试删除视图SQLUSER.VNAME上的索引‘EmpSalaryIndex’失败。索引仅支持表,不支持视图。

示例

第一个示例创建名为Employee的表,本节的所有示例都使用该表。

下面的嵌入式SQL示例创建一个名为“EmpSalaryIndex”的索引,然后删除它。 注意,这里DROP INDEX没有指定与索引关联的表; 它假设“EmpSalaryIndex”是这个名称空间中的唯一索引名称。

ClassMethod DropIndex()
{
	&sql(
		CREATE TABLE 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)
		)
	)
	w !,"SQLCODE = ",SQLCODE," 创建表"
	&sql(
		CREATE INDEX EmpSalaryIndex
		ON TABLE Employee
		(
			Namelast,Salary
		)
	)
	w !,"SQLCODE=",SQLCODE," 创建索引"

	n SQLCODE ,%msg
	&sql(
		DROP INDEX EmpSalaryIndex
	)
	w !,"SQLCODE=",SQLCODE," 删除索引"
	if (SQLCODE '=0 ) { 
		w !,"message",%msg
	}
}

下面的嵌入式SQL示例使用ON table子句指定与要删除的索引相关联的表:

/// d ##class(PHA.TEST.SQLCommand).DropIndex1()
ClassMethod DropIndex1()
{
	&sql(CREATE INDEX EmpVacaIndex
		ON TABLE Employee
		(
			NameLast,AccruedVacation
		)
	)
	w !,"SQLCODE=",SQLCODE," 创建索引"

	&sql(DROP INDEX EmpVacaIndex ON TABLE Employee)
	w !,"SQLCODE=",SQLCODE," 删除索引"
}

下面的嵌入式SQL示例使用限定名语法指定了与要删除的索引相关联的表:

ClassMethod DropIndex2()
{
	&sql(CREATE INDEX EmpSickIndex
		ON TABLE Employee
		(
			NameLast,AccruedSickLeave
		)
	)
		w !,"SQLCODE=",SQLCODE," 创建索引"
	
	&sql(DROP INDEX Employee.EmpSickIndex)
	w !,"SQLCODE=",SQLCODE," 删除索引"
}

下面的命令删除一个不存在的索引。 它生成SQLCODE -333错误:

DROP INDEX PeopleIndex ON TABLE Employee
0
0 145
文章 姚 鑫 · 十月 1, 2021 2m read

第三十二章 SQL命令 DROP FUNCTION

删除函数

大纲

DROP FUNCTION name [ FROM className ]

参数

  • name - 要删除的函数的名称。名称是一个标识符。不要指定函数的参数括号。名称可以是限定的(schema.name),也可以是非限定的(Name)。除非指定了FROM className子句,否则非限定函数名称将采用系统范围的默认架构名称。
  • FROM className - 可选-如果指定,FROM className子句将从给定类中删除函数。请注意,必须指定函数的类名(FuncBonusCalc),而不是SQL名称(BonusCalc)。如果未指定FROM子句, IRIS将在架构的所有类中搜索该函数,并将其删除。但是,如果找不到此名称的函数,或者找到多个此名称的函数,则会返回错误代码。如果删除函数会导致空类,则DROP函数也会删除该类。

描述

DROP Function命令用于删除函数。当删除某个函数时, IRIS会将其从已授予该函数的所有用户和角色中撤消,并将其从数据库中删除。

要删除函数,必须拥有由GRANT命令指定的%DROP_Function管理权限。否则,系统会生成SQLCODE-99错误(权限冲突)。

如果包含函数定义的类定义是已部署的类,则不能删除该函数。此操作失败,并出现SQLCODE-400错误,并显示%msg Unable to Execute DDL以修改已部署的类:‘classname’

支持名称和From className的以下组合。请注意,FROM子句指定类包名称和函数名称,而不是SQL名称。在这些示例中,系统范围内的默认架构名称是SQLUser,它对应于User类程序包:

  • DROP FUNCTION BonusCalc FROM funcBonusCalc: 删除函数 SQLUser.BonusCalc().
  • DROP FUNCTION BonusCalc FROM User.funcBonusCalc: 删除函数 SQLUser.BonusCalc().
  • DROP FUNCTION Test.BonusCalc FROM funcBonusCalc: 删除函数 SQLUser.BonusCalc().
  • DROP FUNCTION BonusCalc FROM Employees.funcBonusCalc:删除函数Employees.BonusCalc().
  • DROP FUNCTION Test.BonusCalc FROM Employees.funcBonusCalc: 删除函数 Employees.BonusCalc().

如果指定的函数不存在,DROP Function将生成SQLCODE-362错误。如果指定的类不存在,DROP函数将生成SQLCODE-360错误。如果指定的函数可以引用两个或多个函数,DROP Function将生成SQLCODE-361错误;必须指定一个className来解决此歧义。

示例

下面的嵌入式SQL示例尝试从类User.Employee中删除myfunc

ClassMethod DropFunction()
{
	&sql(
		DROP FUNCTION myfunc FROM sqluser.Employee
	)
	if SQLCODE=0 {
		w !,"函数已删除" 
	} elseif SQLCODE = -360 {
		w !,"不存在的类: ",%msg 
	} elseif SQLCODE=-362 {
		w !,"不存在的函数: ",%msg 
	} else {
		w !,"意外错误代码: ",SQLCODE
	}
}
0
0 132
文章 姚 鑫 · 九月 30, 2021 3m read

第三十一章 SQL命令 DROP DATABASE

删除数据库(命名空间)。

大纲

DROP DATABASE dbname [RETAIN_FILES]

参数

  • dbname - 要删除的数据库(命名空间)的名称。
  • RETAIN_FILES - 可选-如果指定,则不会删除物理数据库文件(IRIS.DAT文件)。默认情况下,删除.dat文件以及命名空间和其他数据库实体。

描述

DROP DATABASE命令删除命名空间及其关联的数据库。

指定的dbname是包含相应数据库文件的命名空间和目录的名称。指定dbname作为标识符。命名空间名称不区分大小写。如果指定的DBNAME命名空间不存在, IRIS将发出SQLCODE-340错误。

DROP DATABASE命令是一个特权操作。 在使用DROP DATABASE之前,必须以%Admin_Manage资源的用户身份登录。 用户还必须拥有用于例程和全局数据库定义的资源的READ权限。 如果不这样做,将导致SQLCODE -99错误(权限冲突)。

使用$SYSTEM.Security.Login()方法为用户分配适当的权限:

   DO $SYSTEM.Security.Login("_SYSTEM","SYS")
   &sql(      )

必须具有%Service_Login:Use权限才能调用$SYSTEM.Security.Login方法。

不管权限如何,DROP DATABASE都不能用于删除系统命名空间。尝试这样做会导致SQLCODE-342错误。

DROP DATABASE不能用于删除当前正在使用或连接到的命名空间。尝试这样做会导致SQLCODE-344错误。

还可以使用管理门户删除命名空间。依次选择System Administration、Configuration、System Configuration、Namespaces以列出现有的Namespace。单击要删除的命名空间的删除按钮。

RETAIN_FILES

如果指定此选项,则保留物理文件结构;删除数据库及其关联的命名空间。执行此操作后,后续尝试使用DBNAME将导致以下结果:

  • DROP DATABASE不带RETAIN_FILES无法删除此物理文件结构。相反,它会导致SQLCODE-340错误(未找到数据库)。
  • DROP DATABASE WITH RETAIN_FILES还会导致SQLCODE-340错误(找不到数据库)。
  • CREATE DATABASE无法创建同名的新数据库。相反,它会导致SQLCODE-341错误(无法为数据库创建数据库文件)。
  • 尝试使用此命名空间会导致<NAMESPACE>错误。

服务器初始化和断开代码

服务器初始化代码和服务器断开代码可以通过$SYSTEM.SQL.Util.SetOption("ServerInitCode",value)$SYSTEM.SQL.Util.SetOption("ServerDisconnectCode",value)方法分配给命名空间。 可以使用相应的$SYSTEM.SQL.Util.GetOption()方法选项来确定当前值。

使用DROP DATABASE或其他接口删除命名空间,将删除这些Server Init CodeServer Disconnect Code值。 因此,删除并重新创建名称空间需要重新指定这些值。

示例

CREATE DATABASE DocTestDB ON DIRECTORY 'c:\InterSystems\IRIS142\mgr\DocTestDB'
DROP DATABASE DocTestDB RETAIN_FILES
0
0 123
文章 姚 鑫 · 九月 29, 2021 1m read

第三十章 SQL命令 DROP AGGREGATE

删除用户定义的聚合函数。

大纲

DROP AGGREGATE name

参数

  • name - 要删除的用户定义聚合函数的名称。名称可以是限定的(schema.aggname),也可以是非限定的(aggname)。非限定名称采用默认架构名称。

描述

DROP Aggregate命令删除用户定义的聚合函数(UDAF)。使用CREATE Aggregate命令创建用户定义的聚合函数。

如果尝试删除不存在的UDAF,SQL会发出SQLCODE-428错误,并显示如下消息:User Defined Aggregate Function Sample.SecondHighest does not exist.

删除UDAF会自动清除引用该UDAF的所有缓存查询。

0
0 111
文章 姚 鑫 · 九月 29, 2021 9m read

第二十九章 SQL命令 DISTINCT

指定仅返回不同值的SELECT子句。

大纲

SELECT [DISTINCT [BY (item {,item2})] ]  |  [ALL]
  select-item {,select-item2}

参数

  • DISTINCT - 可选-返回组合选择项值唯一的行。
  • DISTINCT BY (item {,item2}) - 可选-返回按(项)值唯一的行的选择项值。
  • ALL - 可选-返回结果集中的所有行。默认设置。

描述

可选DISTINCT子句出现在SELECT关键字之后、可选TOP子句和第一个SELECT-ITEM之前。

DISTINCT子句应用于SELECT语句的结果集。它将每个不同(唯一)值返回的行数限制为一个任意行。如果未指定DISTINCT子句,则默认情况下显示满足选择条件的所有行。ALL子句与不指定DEFAULT子句相同;如果指定ALLSELECT将返回表中满足选择条件的所有行。

DISTINCT从句有两种形式:

  • SELECT DISTINCT:为选择项值的每个唯一组合返回一行。可以指定一个或多个选择项。例如,以下查询返回一行,其中包含Home_StateAge值的每个唯一组合的Home_StateAge值:
SELECT DISTINCT Home_State,Age FROM Sample.Person
  • SELECT DISTINCT BY(Item):为项目值的每个唯一组合返回一行。可以指定单个项目或逗号分隔的项目列表。指定的项目或项目列表必须用括号括起来。可以在by关键字和圆括号之间指定或省略空格。选择项列表可以(但不一定)包括指定的项。例如,以下查询返回一行,其中包含Home_StateAge值的每个唯一组合的NameAge值:
SELECT DISTINCT BY (Home_State,Age) Name,Age FROM Sample.Person

项目字段必须按列名指定。有效值包括以下值:列名(DISTINCT BY(City));%ID(返回所有行);指定列名的标量函数(DISTINCT BY(ROUND(Age,-1);指定列名的排序函数(DISTINCT BY(%Exact(City)。不能按列别名指定字段;尝试这样做会生成SQLCODE-29错误。不能按列号指定字段;这将被解释为文字,并返回一行。将文字指定为DISTINCT子句中的项值将返回1行;返回哪行是不确定的。因此,指定7‘Chicago’‘’0NULL都返回1行。但是,如果将文字指定为逗号分隔列表中的项值,则该文字将被忽略,并且DISTINCT将为指定字段名的每个唯一组合选择一行。

DISTINCT子句在TOP子句之前应用。如果两者都指定,则SELECT只返回具有唯一值的行,唯一值是在TOP子句中指定的唯一值行数。

如果DISTINCT子句中指定的列包含NULL(不包含值)行,则DISTINCT将返回一行作为DISTINCT(唯一)值的NULL,如以下示例所示:

SELECT DISTINCT FavoriteColors FROM Sample.Person
SELECT DISTINCT BY (FavoriteColors) Name,FavoriteColors FROM Sample.Person
ORDER BY FavoriteColors

DISTINCT子句在嵌入式SQL简单查询中没有意义,因为在这种类型的嵌入式SQL中,SELECT始终只返回一行数据。但是,嵌入式SQL基于游标的查询可以返回多行数据;在基于游标的查询中,DISTINCT子句只返回唯一值行。

DISTINCT和ORDER BY

DISTINCT子句在ORDER BY子句之前应用。因此,DISTINCTORDER BY的组合将首先选择满足DISTINCT子句的任意行,然后根据ORDER BY子句对这些行进行排序。

DISTINCT和GROUP BY

DISTINCTGROUP BY这两个记录按指定字段(或多个字段)分组,并为该字段的每个唯一值返回一条记录。它们之间的一个重要区别是DISTINCT在分组之前计算聚合函数。GROUP BY计算分组后的聚合函数。以下示例显示了这种差异:

SELECT DISTINCT BY (ROUND(Age,-1)) Age,AVG(Age) AS AvgAge FROM Sample.Person
 /* Avg(Age)返回表中所有年龄的平均值 */
SELECT Age,AVG(Age) AS AvgAge FROM Sample.Person GROUP BY ROUND(Age,-1)
 /* Avg(Age)返回每个年龄组的平均年龄 */

DISTINCT子句可以用一个或多个聚合函数字段指定,尽管这很少有意义,因为聚合函数返回单个值。因此,下面的示例返回单行:

SELECT DISTINCT BY (AVG(Age)) Name,Age,AVG(Age) FROM Sample.Person

注意:如果将聚合函数作为唯一项或选择项的DISTINCT子句与GROUP BY子句一起使用,则DISTINCT子句将被忽略。可以使用子查询实现DISTINCT、聚合函数和GROUP BY的预期组合。

字母大小写与DISTINCT优化

根据为字段定义的排序规则类型,将字符串值不同地分组在一起。默认情况下,字符串数据类型字段使用SQLUPPER排序规则定义,该排序规则不区分大小写。

如果字段/特性排序规则类型为SQLUPPER,则分组的字段值将全部以大写字母返回。要按原始字母大小写对值进行分组,或以原始字母大小写显示分组字段的返回值,请使用%Exact排序规则函数。以下示例显示了这一点,这些示例假设Home_City字段是使用排序规则类型SQLUPPER定义的,并且包含值‘New York’‘New York’

SELECT DISTINCT BY (Home_City) Name,Home_City FROM Sample.Person
/* 将Home_City值按其大写字母值组合在一起将以大写字母返回每个分组城市的名称。因此,返回‘new york’.                          */
SELECT DISTINCT BY (Home_City) Name,%EXACT(Home_City) FROM Sample.Person
/* 将Home_City值按其大写字母值组合在一起将返回每个分组的城市的名称(原始字母大小写)。因此,可以返回‘New York’或‘new York’,但不能同时返回两者。 */
SELECT DISTINCT BY (%EXACT(Home_City)) Name,Home_City FROM Sample.Person
/* 将Home_City值按其原始字母大小写组合在一起将返回每个分组的城市的名称(原始字母大小写)。
因此,‘New York’和‘new York’都会返回。
未使用优化.                       */

可以使用管理门户优化包含DISTINCT子句的查询的查询性能。依次选择系统管理、配置、SQL和对象设置、SQL。查看和编辑GROUP BYDISTINCT查询必须生成原始值选项。(此优化也适用于GROUP BY子句。)。默认值为“否”。

此默认设置按字母值的大写排序规则对字母值进行分组。此优化利用选定字段的索引。因此,只有在一个或多个选定字段存在索引时才有意义。它对存储在索引中的字段值进行排序;字母字符串以全部大写字母返回。您可以设置此系统范围的选项,然后使用%exact排序规则函数为特定查询覆盖它以保留字母大小写。

也可以使用$SYSTEM.SQL.Util.SetOption()方法快速区分选项在系统范围内设置此选项。要确定当前设置,请调用$SYSTEM.SQL.CurrentSettings(),它显示打开的不同优化设置;默认值为1

DISTINCT的其他用法

  • 流字段:DISTINCT对流字段的OID进行操作,而不是对其实际数据进行操作。因为所有流字段OID都是唯一值,所以DISTINCT对实际流字段重复数据值没有影响。DISTINCT BY(StreamField)将流字段为空的记录数减少到一个空记录。
  • 星号语法:DISTINCT*语法是合法的,但没有意义,因为根据定义,所有行都包含一些不同的唯一标识符。不同于(*)的语法不合法。
  • 子查询:在子查询中使用DISTINCT子句是合法的,但没有意义,因为子查询返回单个值。
  • 未选择行数据:DISTINCT子句可以与不访问任何表数据的SELECT一起使用。如果SELECT包含FROM子句,则在一行中指定DISTINCT结果将包含这些非表值;如果未指定DISTINCT(或TOP),则SELECT将产生与FROM子句表中的行数相同的行数。如果SELECT不包含FROM子句,则DISTINCT是合法的,但没有意义。
  • 聚合函数:可以在聚合函数中使用DISTINCT子句,以仅选择要包含在聚合中的不同(唯一)字段值。与SELECT DISTINCT子句不同,聚合函数中的DISTINCT不包括NULL作为DISTINCT(唯一)值。请注意,MAXMIN聚合函数分析DISTINCT子句语法没有错误,但此语法不执行任何操作。

DISTINCT和%ROWID

指定DISTINCT关键字会导致基于游标的嵌入式SQL查询不设置%ROWID变量。即使DISTINCT不限制返回的行数,也不设置%ROWID。下面的示例显示了这一点:

ClassMethod Distinct()
{
	s %ROWID = 999
	&sql(
		DECLARE EmpCursor3 CURSOR FOR 
			SELECT DISTINCT Name, Home_State
			INTO :name,:state FROM Sample.Person
			WHERE Home_State %STARTSWITH 'M'
	)
	&sql(
		OPEN EmpCursor3
	)
	q:(SQLCODE '= 0)
	for { 
		&sql(
			FETCH EmpCursor3
		)
		q:SQLCODE  
		w !,"RowID: ",%ROWID," row count: ",%ROWCOUNT
		w " Name=",name," State=",state
	}
	&sql(
		CLOSE EmpCursor3
	)
}

查询行为的这种更改仅适用于基于游标的嵌入式SQL SELECT查询。动态SQL SELECT查询和非游标嵌入式SQL SELECT查询从未设置%ROWID

DISTINCT和事务处理

指定DISTINCT关键字会导致查询检索所有当前数据,包括当前事务尚未提交的数据。忽略事务的READ COMMITTED隔离模式参数(如果设置);在READ UNCOMMITTED模式下检索所有数据。

示例

以下查询为每个不同的Home_State值返回一行:

SELECT DISTINCT Home_State FROM Sample.Person
ORDER BY Home_State

以下查询为每个不同的Home_State值返回一行,但返回该行的其他字段。无法预测检索到的是哪一行:

SELECT DISTINCT BY (Home_State) %ID,Name,Home_State,Office_State FROM Sample.Person
ORDER BY Home_State

以下查询为Home_StateOffice_State值的每个不同组合返回一行。根据数据的不同,它要么返回更多行,要么返回与上一个示例相同的行数:

SELECT DISTINCT BY (Home_State,Office_State) %ID,Name,Home_State,Office_State FROM Sample.Person
ORDER BY Home_State,Office_State

以下查询使用DISTINCT BY为每个不同的名称长度返回一行:

SELECT DISTINCT BY ($LENGTH(Name)) Name,$LENGTH(Name) AS lname
FROM Sample.Person
ORDER BY lname

下面的查询使用DISTINCT BYFavoriteColors%List值的每个不同的第一个元素返回一行。它列出FavoriteColors为空的不同行:

SELECT DISTINCT BY ($LIST(FavoriteColors,1)) Name,FavoriteColors,$LIST(FavoriteColors,1) AS FirstColor
FROM Sample.Person

以下查询按排序规则升序返回从Sample.Person检索到的前20个不同的Home_State值。“top”行反映Sample.Person中所有行的ORDER BY子句排序。

SELECT DISTINCT TOP 20 Home_State FROM Sample.Person ORDER BY Home_State

以下查询在主查询和WHERE子句子查询中都使用DISTINCT。它返回Sample.Person中的前20个不同的Home_State值,这些值也在Sample.Employee中。如果未提供子查询DISTINCT,它将检索Sample.Person中与Sample.Employee中随机选择的Home_State值匹配的DISTINCT Home_State值:

SELECT DISTINCT TOP 20 Home_State FROM Sample.Person 
WHERE Home_State IN(SELECT DISTINCT TOP 20 Home_State FROM Sample.Employee)
ORDER BY Home_State

以下查询返回前20个不同的FavoriteColore值。这反映了Sample.Person中所有行的ORDER BY子句排序。众所周知,FavoriteColors字段具有NULL,因此FavoriteColorsNULL的不同行出现在排序规则序列的顶部。

SELECT DISTINCT BY (FavoriteColors) TOP 20 FavoriteColors,Name FROM Sample.Person 
      ORDER BY FavoriteColors

还要注意,在前面的示例中,因为FavoriteColors是一个列表字段,所以归类序列包括元素长度字节。因此,以三个字母元素(红色)开头的不同列表值列在以四个字母元素(蓝色)开头的列表值之前。

0
0 525
文章 姚 鑫 · 九月 28, 2021 4m read

第二十八章 SQL命令 DELETE(二)

示例

以下两个示例都删除了TempEmployees表中的所有行。请注意,FROM关键字是可选的:

DELETE FROM TempEmployees
DELETE TempEmployees

以下示例从Employees表中删除员工编号234

DELETE
     FROM Employees
     WHERE EmpId = 234

下面的示例从ActiveEmployees表中删除CurStatus列设置为“RETIRED”的所有行:

DELETE FROM ActiveEmployees
     WHERE CurStatus = 'Retired'

下面的示例使用子查询删除行:

DELETE FROM (SELECT Name,Age FROM Sample.Person WHERE Age > 65)

嵌入式SQL和动态SQL示例

在下面的一组程序示例中,第一个程序创建了一个名为SQLUser.WordPair的表,该表有三列。下一个程序插入六条记录。后续程序使用基于指针的嵌入式SQL删除所有英语记录,并使用动态SQL删除所有法语记录。最后一个程序显示剩余的记录,然后删除该表。

ClassMethod Delete1()
{
	&sql(
		CREATE TABLE SQLUser.WordPairs 
		(
			Lang        CHAR(2) NOT NULL,
			Firstword   CHAR(30),
			Lastword    CHAR(30) 
		)
	)
	if SQLCODE = 0 {
		w !,"表已创建" 
	} elseif SQLCODE = -201 {
		w !,"表已存在"  
		q
	} else {
		w !,"CREATE TABLE失败. SQLCODE=",SQLCODE 
	}
}
ClassMethod Delete2()
{
	#SQLCompile Path = Cinema,Sample
	&sql(
		INSERT INTO WordPairs 
		(
			Lang, Firstword, Lastword
		) 
		VALUES 
		(
			'En', 'hello', 'goodbye'
		)
	)
	if SQLCODE = 0 { 
		w !,"插入的第一条记录" 
	} else { 
		w !,"Insert 失败, SQLCODE=",SQLCODE
		q
	}
	&sql(INSERT INTO WordPairs (Lang,Firstword,Lastword) VALUES 
	('Fr','bonjour','au revoir'))
	if SQLCODE = 0 { 
		w !,"插入的第二条记录" 
	} else { 
		w !,"Insert 失败, SQLCODE=",SQLCODE
		q
	}
	&sql(INSERT INTO WordPairs (Lang,Firstword,Lastword) VALUES 
	('It','pronto','ciao'))
	if SQLCODE = 0 { 
		w !,"插入的第三条记录" 
	} else { 
		w !,"Insert 失败, SQLCODE=",SQLCODE
		q
	}
	&sql(INSERT INTO WordPairs (Lang,Firstword,Lastword) VALUES 
	('Fr','oui','non'))
	if SQLCODE = 0 { 
		w !,"插入的第四条记录" 
	} else { 
		w !,"Insert 失败, SQLCODE=",SQLCODE
		q
	}
	&sql(INSERT INTO WordPairs (Lang,Firstword,Lastword) VALUES 
	('En','howdy','see ya'))
	if SQLCODE = 0 { 
		w !,"插入的第五条记录" 
	} else { 
		w !,"Insert 失败, SQLCODE=",SQLCODE
		q
	}
	&sql(INSERT INTO WordPairs (Lang,Firstword,Lastword) VALUES 
	('Es','hola','adios'))
	IF SQLCODE = 0 { 
		w !,"插入的第六条记录",!!
		s myquery = "SELECT %ID,* FROM SQLUser.WordPairs"
		s tStatement = ##class(%SQL.Statement).%New()
		s qStatus = tStatement.%Prepare(myquery)
		if qStatus'=1 {
			w "%Prepare failed:" 
			d $System.Status.DisplayError(qStatus) 
			q
		}
		s rset = tStatement.%Execute()
		d rset.%Display()
		w !,"End of data" 
	} else { 
		w !,"Insert 失败, SQLCODE=",SQLCODE
		q
	}
}
ClassMethod Delete3()
{

	#SQLCompile Path=Sample
	n %ROWCOUNT,%ROWID
	&sql(
		DECLARE WPCursor CURSOR FOR 
		SELECT Lang FROM WordPairs
		WHERE Lang='En'
	)
	&sql(
		OPEN WPCursor
	)
	q:(SQLCODE'=0)
	for { 
		&sql(
			FETCH WPCursor
		)
		q:SQLCODE 
		&sql(
			DELETE FROM WordPairs
			WHERE CURRENT OF WPCursor
		)
		if SQLCODE=0 {
			w !,"Delete 成功"
			w !,"Row count=",%ROWCOUNT," RowID=",%ROWID 
		} else {
			w !,"Delete 失败, SQLCODE=",SQLCODE 
		}
	}
	&sql(
		CLOSE WPCursor
	)
}
ClassMethod Delete4()
{
	s sqltext = "DELETE FROM WordPairs WHERE Lang=?"
	s tStatement = ##class(%SQL.Statement).%New(0,"Sample")
	s qStatus = tStatement.%Prepare(sqltext)
	if qStatus'=1 {
		w "%Prepare failed:" 
		d $System.Status.DisplayError(qStatus) 
		q
	}
	s rtn = tStatement.%Execute("Fr")
	if rtn.%SQLCODE=0 {
		w !,"Delete succeeded"
		w !,"Row count=",rtn.%ROWCOUNT," RowID of last record=",rtn.%ROWID 
	} else {
		w !,"Delete failed, SQLCODE=",rtn.%SQLCODE }
}
ClassMethod Delete5()
{

	s myquery = "SELECT %ID,* FROM SQLUser.WordPairs"
	s tStatement = ##class(%SQL.Statement).%New()
	s qStatus = tStatement.%Prepare(myquery)
	if qStatus'=1 {
		w "%Prepare 失败:" 
		d $System.Status.DisplayError(qStatus) 
		q
	}
	s rset = tStatement.%Execute()
	d rset.%Display()
	w !,"End of data"
	&sql(
		DROP TABLE SQLUser.WordPairs
	)
	if SQLCODE=0 {
		w !!,"Table 删除"
		q 
	} else {
		w !,"Table 删除是啊白, SQLCODE=",SQLCODE 
	}
}
0
0 91
文章 姚 鑫 · 九月 27, 2021 13m read

第二十七章 SQL命令 DELETE(一)

从表中删除行。

大纲

DELETE [%keyword] [FROM] table-ref [[AS] t-alias]
     [FROM [optimize-option] select-table [[AS] t-alias]
              {,select-table2 [[AS] t-alias]} ]
     [WHERE condition-expression]

DELETE [%keyword] [FROM] table-ref [[AS] t-alias]
     [WHERE CURRENT OF cursor]

参数

  • %keyword - 可选-以下一个或多个关键字选项,以空格分隔:%NOCHECK%NOFPLAN%NOINDEX%NOJOURN%NOLOCK%NOTRIGGER%PROFILE%PROFILE_ALL
  • FROM table-ref - 要从中删除行的表。这不是FROM子句;它是一个FROM关键字,后跟一个表引用。(FROM关键字是可选的;table-ref是必需的。)表名(或视图名)可以是限定的(schema.table),也可以是不限定的(Table)。使用架构搜索路径(如果提供)或默认架构名称将非限定名称与其架构匹配。可以指定可通过其删除表行的视图,而不是表引用,也可以指定括在圆括号中的子查询。与SELECT语句FROM子句不同,不能在此处指定Optimize-Option关键字。不能在此参数中指定表值函数或联接语法。
  • FROM clause - 可选-FROM子句,在table-ref之后指定。此FROM可用于指定一个或多个选择表,用于选择要删除的行。可以将多个表指定为逗号分隔的列表或与ANSI联接关键字关联。可以指定表或视图的任意组合。如果在此处的两个选择表之间指定逗号, IRIS将对这两个表执行交叉联接,并从联接操作的结果表中检索数据。如果在此处的两个选择表之间指定ANSI联接关键字,则 IRIS将执行指定的联接操作。可以选择指定一个或多个OPTIMIZE-OPTION关键字来优化查询执行。可用选项为:%ALLINDEX%FIRSTTABLE TABLE TABNAME%FULL%INORDER%IGNOREINDICES%NOFLATTEN%NOMERGE%NOSVSO%NOTOPOPT%NOUNIONOROPT%PARALLEL和%STARTTABLE
  • AS t-alias - 可选-表或视图名称的别名。别名必须是有效的标识符。AS关键字是可选的。
  • WHERE condition-expression - 可选-指定一个或多个布尔谓词,用于限制要删除的行。可以指定WHERE子句或WHERE CURRENT OF子句,但不能同时指定两者。如果未提供WHERE子句(或WHERE CURRENT OF子句),则DELETE将从表中删除所有行。
  • WHERE CURRENT OF cursor - 可选:仅嵌入式SQL-指定删除操作删除游标当前位置的记录。可以指定WHERE CURRENT OF子句或WHERE子句,但不能同时指定两者。如果未提供WHERE CURRENT OF子句(或WHERE子句),则DELETE将从表中删除所有行。

描述

DELETE命令从满足指定条件的表中删除行。可以直接从表中删除行、通过视图删除或删除使用子查询选择的行。通过视图删除受要求和限制的约束,如创建视图中所述。

删除操作将%ROWCOUNT局部变量设置为已删除行数,并将%ROWID局部变量设置为已删除最后一行的RowID值。如果没有删除任何行,则%ROWCOUNT=0%ROWID未定义或保持设置为其先前的值。

必须指定table-reftable-ref前的from关键字是可选的。要从表中删除所有行,只需指定:

DELETE FROM tablename

DELETE tablename

这将从表中删除所有行数据,但不会重置RowIDIdentity、流字段OID值和序列(%Library.Counter)字段计数器。TRUNCATE TABLE命令既删除表中的所有行数据,又重置这些计数器。默认情况下,DELETE FROM TABLENAME将拉取DELETE触发器;可以指定DELETE %NOTRIGGER FROM TABLENAME不拉取DELETE触发器。TRUNCATE TABLE不拉取删除触发器。

更常见的情况是,删除指定基于条件表达式的特定行(或多行)的删除。默认情况下,删除操作遍历表的所有行,并删除满足条件表达式的所有行。如果没有满足条件表达式的行,则DELETE成功完成,并设置SQLCODE=100(没有更多数据)。

可以指定WHERE子句或WHERE CURRENT OF子句(但不能同时指定两者)。如果使用WHERE CURRENT OF子句,删除操作将删除游标当前位置的记录。

默认情况下,DELETE是一个全有或全无事件:要么完全删除所有指定的行,要么不执行任何删除。 IRIS设置状态变量SQLCODE,指示删除是成功还是失败。

要从表中删除行,请执行以下操作:

  • 该表必须存在于当前(或指定的)命名空间中。如果找不到指定的表, IRIS将发出SQLCODE-30错误。
  • 用户必须具有对指定表的删除权限。如果用户是表的所有者(创建者),则会自动授予该用户对该表的删除权限。否则,必须授予用户对该表的删除权限。否则将导致SQLCODE-99错误,因为%msg用户‘name’没有该操作的特权。可以通过调用%CHECKPRIV命令来确定当前用户是否具有删除权限。可以使用GRANT命令将删除权限分配给指定表。
  • 表不能被另一个进程以独占模式锁定。尝试从锁定表中删除行将导致SQLCODE-110错误,错误代码为%msg,如下所示:无法获取用于删除行ID为‘10’的行的表‘Sample.Person’的锁。请注意,只有当DELETE语句找到第一条要删除的记录,然后无法在超时期限内锁定它时,才会出现SQLCODE-110错误。
  • 如果DELETE命令的WHERE子句指定了一个不存在的字段,则会发出SQLCODE-29。要如果该字段存在,但没有一个字段值满足DELETE命令的WHERE子句,则不会影响任何行,并发出SQLCODE 100(数据结束)。
  • 不能将该表定义为READONLY。尝试编译引用只读表的删除会导致SQLCODE-115错误。请注意,此错误现在在编译时发出,而不是仅在执行时发出。
  • 如果通过视图删除,则不能将该视图定义为只读。尝试这样做会导致SQLCODE-35错误。如果视图基于分割表,则不能通过使用CHECK OPTION定义的视图进行删除。尝试这样做会导致SQLCODE-35,其中不允许基于带有CHECK选项条件的切片表的视图(sample.myview)使用%msg INSERT/UPDATE/DELETE。同样,如果试图通过子查询进行删除,则子查询必须是可更新的;例如,以下子查询会导致SQLCODE-35错误: DELETE FROM (SELECT COUNT(*) FROM Sample.Person) AS x
  • 要删除的行必须存在。通常,尝试删除不存在的行会导致SQLCODE 100(没有更多数据),因为找不到指定的行。但是,在极少数情况下,DELETE WITH%NOLOCK会找到要删除的行,但随后该行会被另一个进程立即删除;这种情况会导致SQLCODE-106错误。此错误的%msg列出了表名和RowID
  • 指定要删除的所有行都必须可供删除。默认情况下,如果无法删除一行或多行,则删除操作将失败,并且不会删除任何行。如果要删除的行已被另一个并发进程锁定,则DELETE会发出SQLCODE-110错误。如果删除指定行之一会违反外键引用完整性(并且未指定%NOCHECK),则删除操作将发出SQLCODE-124错误。此默认行为是可修改的,如下所述。
  • 某些%SYS命名空间系统提供的工具受到保护,不会被删除。例如,DELETE FROM Security.Users 不能用于DELETE _SYSTEM_PUBLICUNKNOWnUser。尝试这样做会导致SQLCODE-134错误。

From语法

一个DELETE命令可以包含两个指定表的FROM关键字。From的这两种用法从根本上说是不同的:

  • FROM BEFORE TABLE-REF指定要从中删除行的表(或视图)。它是FROM关键字,而不是FROM子句。只能指定一个表。不能指定联接语法或优化选项关键字。FROM关键字本身是可选的;table-ref是必需的。
  • FROM AFTER TABLE-REF是一个可选的FROM子句,可用于确定应该删除哪些行。它可以指定一个或多个表。它支持SELECT语句可用的所有FROM子句语法,包括联接语法和优化选项关键字。此FROM子句通常(但不总是)与WHERE子句一起使用。

因此,以下任何一种都是有效的语法形式:

DELETE FROM table WHERE ...
DELETE table WHERE ...
DELETE FROM table FROM table2 WHERE ...
DELETE table FROM table2 WHERE ...

此语法以与Transact-SQL兼容的方式支持复杂的选择条件。

下面的示例显示如何使用这两个FROM关键字。它从Employees表中删除那些记录,在Replrees表中也可以找到相同的EmpId

DELETE FROM Employees AS Emp
       FROM Retirees AS Rt
       WHERE Emp.EmpId = Rt.EmpId

如果两个FROM关键字引用了同一个表,则这些引用可以是对同一个表的引用,也可以是对该表的两个实例的联接。这取决于如何使用表别名:

  • 如果两个表引用都没有别名,则两者都引用同一个表:
  DELETE FROM table1 FROM table1,table2   /* join of 2 tables */
  • 如果两个表引用具有相同的别名,则两者引用同一个表:
  DELETE FROM table1 AS x FROM table1 AS x,table2   /* join of 2 tables */
  • 如果两个表引用都有别名,并且别名不同,则 IRIS将执行表的两个实例的联接:
  DELETE FROM table1 AS x FROM table1 AS y,table2   /* join of 3 tables */
  • 如果第一个表引用具有别名,而第二个表引用没有别名,则 IRIS将执行表的两个实例的联接:
  DELETE FROM table1 AS x FROM table1,table2   /* join of 3 tables */
  • 如果第一个表引用没有别名,而第二个表引用具有别名的表只有一个引用,则这两个表都引用同一个表,并且此表具有指定的别名:
  DELETE FROM table1 FROM table1 AS x,table2   /* join of 2 tables */
  • 如果第一个表引用没有别名,而第二个表引用有多个对表的引用,则 IRIS会将每个别名实例视为单独的表,并对这些表执行联接:
  DELETE FROM table1 FROM table1,table1 AS x,table2   /* join of 3 tables */
  DELETE FROM table1 FROM table1 AS x,table1 AS y,table2   /* join of 4 tables */

%Keyword 选项

指定%Keyword参数将按如下方式限制处理:

  • %NOCHECK-禁止对引用要删除的行的外键进行参照完整性检查。用户必须具有当前命名空间的相应%NOCHECK管理权限才能应用此限制。否则将导致SQLCODE-99错误,因为%msg用户‘name’没有%NOCHECK权限。
  • %NOFPLAN-忽略此操作的冻结计划(如果有);该操作将生成新的查询计划。冻结的计划将保留,但不会使用。
  • %NOINDEX -禁止删除要删除行的所有索引中的索引项。使用时应格外小心,因为它会在表索引中留下孤立值。用户必须具有当前命名空间的相应%noindex管理权限才能应用此限制。否则将导致SQLCODE-99错误,因为%msg用户‘name’没有%noindex权限。
  • %NOJOURN-在删除操作期间禁止日志记录。任何行中所做的任何更改都不会被记录下来,包括拉出的任何触发器。如果在使用%NOJOURN的语句之后执行ROLLBACK,则不会回滚该语句所做的更改。
  • %NOLOCK-禁止对要删除的行进行行锁定。这应该仅在单个用户/进程更新数据库时使用。用户必须具有当前命名空间的相应%NOLOCK管理权限才能应用此限制。否则将导致SQLCODE-99错误,因为%msg用户‘name’没有%NOLOCK权限。
  • %NOTRIGGER-禁止拉取基表触发器,否则将在删除处理期间拉取这些触发器。用户必须具有当前命名空间的相应%NOTRIGGER管理权限才能应用此限制。否则将导致SQLCODE-99错误,因为%msg用户‘name’没有%NOTRIGGER权限。
  • %PROFILE%PROFILE_ALL-如果指定了其中一个关键字指令,则生成SQLStats收集代码。这与启用PTools时生成的代码相同。不同之处在于,SQLStats收集代码只为该特定语句生成。正在编译的例程/类中的所有其他SQL语句将生成代码,就像PTools已关闭一样。这使用户能够分析/检查应用程序中的特定问题SQL语句,而无需收集未被调查的SQL语句的无关统计信息。

%PROFILE收集主查询模块的SQLStat%PROFILE_ALL收集主查询模块及其所有子查询模块的SQLStat

如果在删除父记录时指定%KEYWORD参数,则删除相应的子记录时也会应用相同的%KEYWORD参数。

参照完整性

如果不指定%NOCHECK, IRIS将使用系统范围的配置设置来确定是否执行外键引用完整性检查;默认情况下执行外键引用完整性检查。可以在系统范围内设置此默认值,如外键引用完整性检查中所述。要确定当前系统范围的设置,请调用$SYSTEM.SQL.CurrentSettings()

在删除操作期间,对于每个外键引用,都会在被引用表中的相应行上获取一个共享锁。此行将被锁定,直到事务结束。这可确保引用的行在可能回滚删除之前不会更改。

如果将一系列外键引用定义为级联,则删除操作可能会导致循环引用。 IRIS防止DELETE与级联引用操作一起执行循环引用循环递归。 IRIS在返回到原始表时结束级联序列。

如果使用%NOLOCK对使用CASCADESET NULLSET DEFAULT定义的外键字段执行DELETE操作,则也会使用%NOLOCK执行相应的更改外键表的引用操作。

原子性

默认情况下,DELETEUPDATEINSERTTRUNCATE TABLE是原子操作。删除要么成功完成,要么回滚整个操作。如果无法删除任何指定的行,则不会删除任何指定的行,并且数据库将恢复到发出DELETE之前的状态。

可以通过调用SET TRANSACTION %COMMITMODE来修改SQL中当前进程的此默认值。可以通过调用SetOption()方法在ObjectScript中修改当前进程的此默认值,如下所示 SET status=$SYSTEM.SQL.Util.SetOption("AutoCommit",intval,.oldval)。以下整型整数选项可用:

  • 1或隐式(自动提交打开)-如上所述的默认行为。每次删除都构成一个单独的事务。
  • 2EXPLICIT(AUTOCOMMIT OFF)-如果没有正在进行的事务,则DELETE会自动启动一个事务,但必须显式提交或回滚才能结束该事务。在显式模式下,每个事务的数据库操作数由用户定义。
  • 0None(无自动事务)-调用DELETE时不会启动任何事务。失败的删除操作可能会使数据库处于不一致的状态,其中一些指定的行已删除,另一些未删除。要在此模式下提供事务支持,必须使用START TRANSACTION来启动事务,并使用COMMITROLLBACK来结束事务。

切片表始终处于非自动事务模式,这意味着对切片表的所有插入、更新和删除都在事务范围之外执行。

可以使用 GetOption("AutoCommit")方法确定当前进程的原子性设置,如下面的ObjectScript示例所示:

ClassMethod Delete()
{
	s stat = $SYSTEM.SQL.Util.SetOption("AutoCommit", $RANDOM(3), .oldval)
	if stat '= 1 {
		w "SetOption 失败:" 
		d $System.Status.DisplayError(stat) QUIT
	}
	s x = $SYSTEM.SQL.Util.GetOption("AutoCommit")
	if x = 1 {
		w "默认原子性行为",!
		w "自动提交或回滚" 
	} elseif x = 0 {
		w "未启动任何事务,没有原子性:",!
		w "删除失败可能会使数据库不一致",!
		w "不支持回档" 
	} else { 
		w "需要显式提交或回滚" 
	}
}

事务锁定

如果未指定%NOLOCK,系统将自动对INSERTUPDATEDELETE操作执行标准记录锁定。在当前事务期间锁定每个受影响的记录(行)。

默认锁定阈值是每个表1000个锁。这意味着如果在事务期间从表中删除1000条以上的记录,就会达到锁定阈值, IRIS会自动将锁定级别从记录锁升级为表锁。这允许在事务期间进行大规模删除,而不会使锁表溢出。

IRIS应用以下两种锁升级策略之一:

  • “E”-类型的锁升级:如果满足以下条件, IRIS将使用这种类型的锁升级:(1)类使用%Storage.Persistent(可以从管理门户SQL架构显示中的目录详细信息确定)。(2)该类不指定IDKey索引,或者指定单属性IDKey索引。
  • 传统的SQL锁升级:类不使用“E”类型锁升级的最可能原因是多属性IDKey索引的存在。在这种情况下,每个%Save都会递增锁定计数器。这意味着如果在一个事务内对单个对象执行1001次保存, IRIS将尝试升级锁。

对于这两种锁升级策略,都可以使用$SYSTEM.SQL.Util.GetOption(“LockThreshold”)方法确定当前系统范围的锁阈值。默认值为1000。此系统范围的锁定阈值是可配置的:

  • 使用$SYSTEM.SQL.Util.SetOption(“LockThreshold”)方法。
  • 使用管理门户:依次选择系统管理、配置、SQL和对象设置、SQL。查看和编辑锁定升级阈值的当前设置。默认值为1000个锁。如果更改此设置,则更改后启动的任何新进程都将具有新设置。

需要在“%Admin Manage Resource”中具有“USE”权限才能修改锁定阈值。 IRIS会立即将对锁阈值的任何更改应用到所有当前进程。

自动锁升级的潜在后果是,当升级到表锁的尝试与持有该表中的记录锁的另一个进程冲突时,可能会发生死锁情况。有几种可能的策略可以避免这种情况:(1)提高锁升级阈值,使锁升级不太可能在事务内发生。(2)大幅降低锁升级阈值,使锁升级几乎立即发生,从而降低其他进程锁定同一表中记录的机会。(3)在事务期间应用表锁,不要执行记录锁。这可以在事务开始时完成,方法是指定LOCK TABLE,然后解锁TABLE(不使用IMMEDIATE关键字,这样表锁将一直持续到事务结束),然后使用%NOLOCK选项执行删除。

自动锁升级旨在防止锁表溢出。但是,如果执行的删除次数太多,以至于出<LOCKTABLEFULL> 错误,则DELETE会发出SQLCODE-110错误。

0
0 236
文章 姚 鑫 · 九月 26, 2021 4m read

第二十六章 SQL命令 DECLARE

声明游标

大纲

DECLARE cursor-name CURSOR FOR query

参数

  • cursor-name - 游标的名称,必须以字母开头,并且仅包含字母和数字。(游标名称不遵循SQL标识符约定)。游标名称区分大小写。它们受其他命名限制的约束,如下所述。
  • query - 定义游标结果集的标准SELECT语句。此选择可以包括%NOFPLAN关键字,以指定应忽略此查询的冻结计划(如果有)。此SELECT可以包括ORDER BY子句,可以带有或不带有TOP子句。此SELECT可以在FROM子句中指定表值函数。

描述

DECLARE语句声明在基于游标的嵌入式SQL中使用的游标。声明游标后,可以发出OPEN语句来打开游标,然后发出一系列FETCH语句来检索各个记录。游标定义SELECT查询,该查询用于选择要由这些FETCH语句检索的记录。可以发出一条CLOSE语句来关闭(但不是删除)游标。

作为SQL语句,仅嵌入式SQL支持DECLARE。对于动态SQL,可以使用简单的SELECT语句(不带INTO子句),也可以使用动态SQL和嵌入式SQL的组合。使用ODBC API通过ODBC支持等效操作。

DECLARE声明只进(不可滚动)游标。提取操作从查询结果集中的第一条记录开始,并按顺序遍历结果集记录。一次提取只能提取一次记录。下一次提取将获取结果集中的下一条连续记录。

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

游标名称

游标名称区分大小写。

游标名称在例程和相应类中必须是唯一的。游标名称可以是任意长度,但在前29个字符内必须是唯一的。游标名称区分大小写。如果已声明指定的游标,编译将失败,并显示SQLCODE-52错误,游标名称已声明。

游标名称不是特定于命名空间的。可以在一个命名空间中声明游标,并在另一个命名空间中打开、获取或关闭此游标。在执行OPEN命令时编译嵌入式SQL。SQL表和局部变量是特定于名称空间的,因此必须在查询中指定的表所在的同一名称空间中调用OPEN操作(或者能够访问名称空间中的表)。

游标名称的第一个字符必须是字母。游标名称的第二个和后续字符必须是字母或数字。与SQL标识符不同,游标名称中不允许使用标点符号。

可以使用分隔符字符(双引号)将SQL保留字指定为游标名称。分隔游标名称不是SQL分隔标识符;分隔游标名称仍然区分大小写,不能包含标点符号。在大多数情况下,SQL保留字不应用作游标名称。

通过游标更新

可以使用带有WHERE CURRENT OF子句的UPDATEDELETE语句,通过声明的游标执行记录更新和删除。 SQL中,如果对受影响的表和列具有适当的权限,则游标始终可以用于更新或删除操作。

DECLARE语句可以在查询后指定FOR UPDATEFOR READ ONLY关键字子句。这些子句是可选的,不执行任何操作。它们是作为在代码中记录发出查询的进程是否具有所需的更新和删除对象权限的一种方式提供的。

示例

下面的嵌入式SQL示例使用DECLARE为指定两个输出主机变量的查询定义游标。然后,光标被打开、重复读取和关闭:

ClassMethod Declare()
{
	s name = "John Doe", state = "##"
	&sql(
		DECLARE EmpCursor CURSOR FOR 
			SELECT Name, Home_State
			INTO :name,:state FROM Sample.Person
			WHERE Home_State %STARTSWITH 'A'
		FOR READ ONLY
	)
	w !,"BEFORE: Name = ",name," State = ",state 
	&sql(
		OPEN EmpCursor
	)
	if SQLCODE < 0 {
		w "SQL打开游标错误:",SQLCODE," ",%msg  
		q
	}
	n %ROWCOUNT,%ROWID
	for { 
		&sql(
			FETCH EmpCursor
		)
		q:SQLCODE  
		w !,"DURING: Name = ",name," State = ",state 
	}
	w !,"获取状态SQLCODE = ",SQLCODE
	w !,"读取的行数 = ",%ROWCOUNT
	&sql(
		CLOSE EmpCursor
	)
	if SQLCODE < 0 {
		w "SQL关闭游标错误:",SQLCODE," ",%msg  
		q
	} 
	w !,"AFTER: Name = ",name," State = ",state
}

下面的嵌入式SQL示例使用DECLARE为查询定义游标,该查询在INTO子句中指定OUTPUT主机变量,在WHERE子句中指定INPUT主机变量。然后,光标被打开、重复读取和关闭:

ClassMethod Declare1()
{
	n SQLCODE,%ROWCOUNT,%ROWID
	s EmpZipLow = "10000"
	s EmpZipHigh = "19999"
	&sql(
		DECLARE EmpCursor1 CURSOR FOR
			SELECT Name,Home_Zip
			INTO :name,:zip
			FROM Sample.Employee 
			WHERE Home_Zip BETWEEN :EmpZipLow AND :EmpZipHigh)
	&sql(
		OPEN EmpCursor1
	)
	if SQLCODE < 0 {
		w "SQL打开游标错误:",SQLCODE," ",%msg  
		q
	}
	for { 
		&sql(
			FETCH EmpCursor1
		)
		q:SQLCODE  
		w !,name," ",zip 
	}
	&sql(
		CLOSE EmpCursor1
	)
	if SQLCODE < 0 {
		w "SQL关闭游标错误:",SQLCODE," ",%msg  
		q
	}
}

下面的嵌入式SQL示例使用表值函数作为查询的FROM子句:

ClassMethod Declare2()
{
	s $NAMESPACE="Samples"
	&sql(DECLARE EmpCursor2 CURSOR FOR 
			SELECT Name INTO :name FROM Sample.SP_Sample_By_Name('A')
			FOR READ ONLY
	)
	&sql(
		OPEN EmpCursor2
	)
	if SQLCODE < 0 {
		w "SQL打开游标错误:",SQLCODE," ",%msg  
		q
	}
	n %ROWCOUNT,%ROWID
	for { 
		&sql(
			FETCH EmpCursor2
		)
		q:SQLCODE  
		w "Name=",name,! 
	}
	w !,"获取状态SQLCODE = ",SQLCODE
	w !,"读取的行数 = ",%ROWCOUNT
	&sql(
		CLOSE EmpCursor2
	)
	if SQLCODE < 0 {
		w "SQL关闭游标错误:",SQLCODE," ",%msg  
		q
	}
}
0
0 185
文章 姚 鑫 · 九月 24, 2021 4m read

第二十五章 SQL命令 CREATE VIEW(二)

通过视图更新

视图可用于更新视图所基于的表。可以通过视图插入新行,更新通过视图看到的行中的数据,以及删除通过视图看到的行。如果CREATE VIEW语句指定了此功能,则可以为视图发出INSERTUPDATEDELETE语句。要允许通过视图进行更新,请在定义视图时指定WITH CHECK选项(默认值)。

注意:如果视图基于分片表,则不能通过WITH CHECK OPTION视图进行INSERTUPDATEDELETE操作。 尝试这样做会导致一个SQLCODE -35,其中%msg INSERT/UPDATE/DELETE not allowed for view (sample.myview) based on sharded table with check option conditions

若要防止通过视图进行更新,请指定WITH READ ONLY。尝试通过使用READ ONLY创建的视图执行插入、更新或删除操作会生成SQLCODE-35错误。

要通过视图进行更新,必须具有要更新表或视图的适当权限,如GRANT命令所指定。

通过视图更新受以下限制:

  • 该视图不能是投影为视图的类查询。
  • 视图的类不能包含类参数READONLY=1
  • 视图的SELECT语句不能包含DISTINCTTOPGROUP BYHAVING子句,也不能是UNION的一部分。
  • 视图的SELECT语句不能包含子查询。
  • 视图的SELECT语句只能列出作为列引用的值表达式。
  • 视图的SELECT语句只能有一个表引用;它不能在SELECT-LISTWHERE子句中包含FROM子句、联接语法或箭头语法。表引用必须指定可更新的表或可更新的视图。

WITH CHECK OPTION子句导致INSERTUPDATE操作根据视图定义的WHERE子句验证结果行。这可确保插入或修改的行是派生视图表格的一部分。有两个可用的检查选项:

  • WITH LOCAL CHECK OPTION-仅检查INSERTUPDATE语句中指定的视图的WHERE子句。
  • WITH CASCADED CHECK OPTION-检查INSERTUPDATE语句中指定的视图的WHERE子句和所有基础视图。这将覆盖这些基础视图中的任何WITH LOCAL CHECK OPTION子句。对于所有可更新的视图,建议使用WITH CASCADED CHECK选项。

如果指定WITH CHECK OPTION,则CHECK选项默认为CASCADED。关键字CASCADECASCADED的同义词。

如果插入操作因检查选项验证失败(如上所述), IRIS将发出SQLCODE-136错误。

如果更新操作因检查选项验证(如上所述)而失败,则 IRIS会发出SQLCODE-137错误。

示例

下面的示例从PhoneBook表中创建了名为“CityPhoneBook”的视图:

CREATE VIEW CityPhoneBook AS
     SELECT Name FROM PhoneBook WHERE City='Boston'

下面的示例从Guides表中创建了一个名为“GuideHistory”的视图。 它列出了所有的Title以及这个人是否已经退休:

CREATE VIEW GuideHistory AS
     SELECT Guides, Title, Retired, Date_Retired 
     FROM Guides

下面的嵌入式SQL示例创建表MyTest,然后为该表创建一个视图MyTestView,该视图从MyTest中选择一个字段:


ClassMethod CreateView1()
{
	d $SYSTEM.Security.Login("_SYSTEM","SYS")
	&sql(DROP TABLE Sample.MyTest)
	&sql(DROP VIEW Sample.MyTestView)
CreateTable
	&sql(CREATE TABLE Sample.MyTest 
		(
			TestNum     INT NOT NULL,
			FirstWord   CHAR (30) NOT NULL,
			LastWord    CHAR (30) NOT NULL,
			CONSTRAINT MyTestPK PRIMARY KEY (TestNum)
		)
	)
	if SQLCODE = 0 { 
		w !,"创建表" 
	} else { 
		w "创建表错误 SQLCODE=",SQLCODE 
	}
CreateView
	&sql(
		CREATE VIEW Sample.MyTestView AS
			SELECT FirstWord FROM Sample.MyTest
			WITH CASCADED CHECK OPTION
	)
	if SQLCODE = 0 { 
		w !,"创建视图" 
	} else { 
		w "创建视图错误 SQLCODE=",SQLCODE 
	}
}

下面的嵌入式SQL示例创建视图MyTestView,该视图从MyTest中选择两个字段。此视图的SELECT查询包含一个TOP子句和一个ORDER BY子句:

ClassMethod CreateView2()
{
	d $SYSTEM.Security.Login("_SYSTEM","SYS")
	&sql(DROP TABLE Sample.MyTest)
	&sql(DROP VIEW Sample.MyTestView)
CreateTable
	&sql(
		CREATE TABLE Sample.MyTest 
		(
			TestNum     INT NOT NULL,
			FirstWord   CHAR (30) NOT NULL,
			LastWord    CHAR (30) NOT NULL,
			CONSTRAINT MyTestPK PRIMARY KEY (TestNum)
		)
	)
	if SQLCODE = 0 { 
		w !,"创建表" 
	} else { 
		w "创建表错误 SQLCODE=",SQLCODE 
	}
CreateView
	&sql(
		CREATE VIEW Sample.MyTestView AS
		SELECT TOP ALL FirstWord,LastWord FROM Sample.MyTest
		ORDER BY LastWord
	)
	if SQLCODE = 0 { 
		w !,"创建视图" 
	} else { 
		w "创建视图错误 SQLCODE=",SQLCODE 
	}
}

下面的示例从三个表(ProjStaffWorks)创建了一个名为“StaffWorksDesign”的视图。 列NameCostProject提供数据。

CREATE VIEW StaffWorksDesign (Name,Cost,Project)
     AS SELECT EmpName,Hours*2*Grade,PName
     FROM Proj,Staff,Works 
     WHERE Staff.EmpNum=Works.EmpNum 
          AND Works.PNum=Proj.PNum AND PType='Design'

下面的例子通过使用UNIONb.table2a.table1中选择,创建了一个名为“v_3”的视图:

CREATE VIEW v_3(fvarchar)
     AS SELECT DISTINCT * 
     FROM
       (SELECT fVARCHAR2 FROM b.table2 
        UNION ALL
        SELECT fVARCHAR1 FROM a.table1) 
0
0 108
文章 姚 鑫 · 九月 23, 2021 9m read

第二十四章 SQL命令 CREATE VIEW(一)

创建视图

大纲

CREATE [OR REPLACE] VIEW view-name [(column-commalist)]
       AS select-statement 
       [ WITH READ ONLY  |  WITH [level] CHECK OPTION ]

参数

  • view-name - 正在创建的视图的名称。有效的标识符,受与表名相同的附加命名限制。视图名称可以是限定的(schema.viewname),也可以是不限定的(Viewname)。未限定的视图名称采用默认架构名称。请注意,同一架构中的表和视图不能使用相同的名称。
  • column-commalist - 可选-组成视图的列名、一个或多个有效标识符。如果指定,此列表括在圆括号中,列表中的项目用逗号分隔。
  • AS select-statement - 定义视图的SELECT语句。
  • WITH READ ONLY - 可选-指定不能通过此视图对视图所基于的表执行插入、更新或删除操作。默认情况下,允许通过视图执行这些操作,但要遵守下面描述的约束条件。
  • WITH level CHECK OPTION - 可选-指定如何通过此视图对视图所基于的表执行插入、更新或删除操作。级别可以是关键字LOCALCASCADED。如果未指定级别,则WITH CHECK选项默认为级联。

描述

CREATE VIEW命令定义视图的内容。定义视图的SELECT语句可以引用多个表,也可以引用其他视图。

权限

CREATE VIEW命令是特权操作。用户必须具有%CREATE_VIEW管理权限才能执行CREATE VIEW。否则将导致%msg用户‘name’没有%CREATE_VIEW权限的SQLCODE-99错误。如果拥有适当的授予权限,则可以使用GRANT命令分配%CREATE_VIEW权限。

要从正在创建的视图的SELECT子句中引用的对象中进行选择,需要具有适当的权限:

  • 使用动态SQL或xDBC创建视图时,必须对从视图引用的基础表(或视图)中选择的所有列具有SELECT权限。如果对指定表(或视图)没有SELECT权限,则不会执行CREATE VIEW命令。

但是,在编译投影已定义视图的类时,不会对从视图引用的基础表(或视图)中选择的列强制执行这些SELECT特权。例如,如果使用特权例程(具有这些SELECT权限)创建视图,则可以在以后编译视图类,因为是视图的所有者,而不管是否对视图引用的表具有SELECT权限。

  • 若要获得视图的SELECT特权WITH GRANT OPTION,则必须对该视图引用的每个表(或视图)都具有WITH GRANT OPTION
  • 若要接收视图的INSERTUPDATEDELETEREFERENCES权限,必须对该视图引用的每个表(或视图)具有相同的权限。要接收其中任何权限的WITH GRANT OPTION,必须在基础表上拥有PRIVICATION WITH GRANT OPTION
  • 如果该视图指定为只读,则不会授予该视图INSERTUPDATEDELETE权限,无论您对基础表拥有哪些权限。如果稍后将视图重新定义为读/写,则在重新编译投影视图的类时会添加这些权限。

可以通过调用%CHECKPRIV命令来确定当前用户是否拥有这些表级权限。可以通过调用$SYSTEM.SQL.Security.CheckPrivileve()方法来确定指定用户是否拥有这些表级权限。

编译视图时,视图的创建者(所有者)被授予%ALTER PRIVATION WITH GRANT选项。

在嵌入式SQL中,可以使用$SYSTEM.Security.Login()方法以具有适当权限的用户身份登录:

   DO $SYSTEM.Security.Login("_SYSTEM","SYS")
   &sql(      )

必须具有%Service_Login:Use权限才能调用$SYSTEM.Security.Login方法。

%CREATE_VIEW权限是通过GRANT命令分配的,这需要将此权限分配给用户或角色。 默认情况下,强制执行CREATE VIEW安全特权。 这个权限要求是可在系统范围内配置的,使用$SYSTEM.SQL.Util.SetOption()方法SET status=$SYSTEM.SQL.Util.SetOption("SQLSecurity",0,.oldval); 要确定当前设置,调用$SYSTEM.SQL.CurrentSettings()方法,该方法显示启用SQL安全的设置。

默认值是1(启用)。 当SQL Security被启用时,用户只能对已被授予权限的表或视图执行操作。 这是该选项的推荐设置。

如果此方法设置为0,则更改此设置后启动的任何新进程将禁用SQL Security。 这意味着禁止基于特权的表/视图安全性。 可以在不指定用户的情况下创建表。 在本例中,动态SQL将“_SYSTEM”指定为user,嵌入式SQL“”(空字符串)指定为user。 任何用户都可以对表或视图执行操作,即使该用户没有这样做的特权。

视图命名约定

视图名称与表名具有相同的命名约定,并且共享相同的名称集。因此,不能对同一架构中的表和视图使用相同的名称。尝试这样做会导致SQLCODE-201错误。若要确定当前命名空间中是否已存在表,请使用$SYSTEM.SQL.Schema.TableExists(“schema.tname”)方法。投射同名的表定义和视图定义的类也会生成SQLCODE-201错误。

视图名称遵循标识符约定,并受以下限制的约束。默认情况下,视图名称是简单标识符。视图名称不应超过128个字符。视图名称不区分大小写。

IRIS使用视图名称生成相应的类名。类名仅包含字母数字字符(字母和数字),并且在前96个字符内必须是唯一的。要生成这个类名, IRIS首先从视图名称中去掉标点符号,然后生成一个在前96个字符内唯一的标识符,在需要创建唯一的类名时用一个整数(从0开始)代替最后一个字符。 IRIS从有效的视图名称生成唯一的类名,但此名称生成对视图的命名施加了以下限制:

  • 视图名称必须至少包含一个字母。视图名称的第一个字符或首个标点符号字符后的第一个字符必须是字母。
  • IRIS支持视图名称使用16位(宽)字符。如果字符通过$ZNAME测试,则该字符是有效字母。
  • 如果视图名称的第一个字符是标点符号,则第二个字符不能是数字。这会导致SQLCODE-400错误,%msg值为Error#5053:类名‘schema.name’无效(没有标点符号)。例如,指定视图名称%7A会生成%msg错误#5053:类名‘User.7A’无效。
  • 由于生成的类名不包括标点符号,因此不建议(尽管可能)创建仅在标点符号方面与现有视图或表名不同的视图名称。在这种情况下, IRIS用一个整数(从0开始)代替名称的最后一个字符,以创建唯一的类名。
  • 视图名称可能比96个字符长得多,但前96个字母数字字符不同的视图名称更易于使用。

视图名称可以是限定的,也可以是非限定的。

限定的视图名称(schema.viewname)可以指定现有架构或新架构。如果指定了新架构,系统将创建该架构。

未限定的视图名称(视图名称)采用默认架构名称。

Existing View

要确定指定的视图是否已经存在于当前命名空间中,请使用$SYSTEM.SQL.Schema.ViewExists("schema.vname")方法。

当创建与现有视图同名的视图时,会发生什么取决于可选的OR REPLACE关键字和配置设置。

With OR REPLACE

如果指定CREATE OR REPLACE VIEW,则现有视图将被SELECT子句中指定的视图定义和任何指定的WITH READ ONLYWITH CHECK OPTION替换。 这与执行相应的ALTER VIEW语句相同。 已授予原始视图的任何特权仍然保留。

这个关键字短语没有提供ALTER VIEW不可用的功能。 它是为兼容Oracle SQL代码而提供的。

Without OR REPLACE

默认情况下,如果指定CREATE VIEW, IRIS将拒绝使用现有视图的名称创建视图的尝试,并发出SQLCODE -201错误。 要确定当前设置,调用$SYSTEM.SQL.CurrentSettings(),它为现有的表或视图设置显示一个Allow DDL CREATE TABLECREATE VIEW。 默认值是0 (No),这是推荐的设置。 如果此选项设置为1 (Yes), IRIS将删除与视图关联的类定义,然后重新创建它。 这与先执行DROP VIEW,然后执行CREATE VIEW非常相似。 注意,此设置同时影响CREATE VIEWCREATE TABLE

在管理门户、系统管理、配置、SQL和对象设置、SQL中,可以通过选择“忽略冗余DDL语句”复选框,在系统范围内设置此选项(以及其他类似的创建、更改和删除选项)。

列名

视图可以有选择地包括用括号括起来的列名的列分隔符列表。 这些列名(如果指定的话)是在使用该视图时用于访问和显示列的数据的名称。

如果省略了列逗号,下面的应用程序:

  • 选择源表的列名用于在使用视图时访问和显示数据。
  • 如果任何选择源表列名具有列别名,则列别名是使用视图时用于访问和显示数据的名称。
  • 如果选择源表列名具有表别名,则在使用视图时用于访问和显示数据的名称中不会使用表别名。

如果省略列名列表,则还必须省略圆括号。

如果指定列COMMANCEL,则以下情况适用:

  • 列名列表必须指定外围括号,即使指定单个字段也是如此。多个列名之间必须用逗号分隔。在列注释器中允许有空格和注释。
  • 列名的数量必须与SELECT语句中指定的列数相对应。视图列数和查询列数之间的不匹配导致编译时出现SQLCODE-142错误。
  • 列名的名称必须是有效的标识符。它们可以是与选择列名不同的名称、与选择列名相同的名称或两者的组合。视图列名的指定顺序与选择列名的顺序相对应。由于可以为视图列分配不相关的选择列的名称,因此在分配视图列名称时必须格外小心。
  • 列名必须是唯一的。指定重复的列名会导致SQLCODE-97错误。列名通过去除标点符号转换为相应的类属性名;允许使用仅在标点符号方面不同的列名,但不鼓励这样做。

下面的示例显示了一个包含视图列和查询列匹配列表的创建视图:

CREATE VIEW MyView (ViewCol1, ViewCol2, ViewCol3) AS
     SELECT TableCol1, TableCol2, TableCol3 
     FROM MyTable

或者,也可以在查询中使用AS关键字将视图列指定为查询列/视图列对,如下例所示:

CREATE VIEW MyView AS 
  SELECT TableCol1 AS ViewCol1,
     TableCol2 AS ViewCol2,
     TableCol3 AS ViewCol3
     FROM MyTable

选择列和查看列

可以将多个选择列中的数据连接到单个视图列中。例如:

CREATE VIEW MyView (fullname) AS SELECT firstname||' '||lastname FROM MyTable

多个视图列可以引用同一选择列。例如:

CREATE VIEW MyView (lname,surname) AS SELECT lastname,lastname FROM MyTable

SELECT子句注意事项

视图不必是一个特定表的行和列的简单子集。 可以使用任意复杂度的SELECT子句创建视图,指定表或视图的任意组合。 然而,对于视图定义的SELECT子句有一些限制:

  • 只有当ORDER BY子句与TOP子句成对出现时,才能包括此子句。如果希望包括视图中的所有行,可以使用TOP ALL子句。可以包含不带ORDER BY子句的TOP子句。但是,如果包含没有TOP子句的ORDER BY子句,则会生成SQLCODE-143错误。如果从视图类投影SQL视图,而视图类的查询包含ORDER BY子句,则在视图投影中将忽略ORDER BY子句。
  • 不能包含主机变量。如果尝试在SELECT子句中引用主机变量,系统将生成SQLCODE-148错误。
  • 不能包含INTO关键字。可以创建使用INTO子句指定SELECT的视图,但该视图的执行失败,并出现SQLCODE-25错误。

CREATE VIEW可以包含UNION语句,以从两个表的联合中选择列。可以指定UNION,如下面的嵌入式SQL示例所示:

ClassMethod CreateView()
{
	d $SYSTEM.Security.Login("_SYSTEM","SYS")
	&sql(
		CREATE VIEW MyView (vname,vstate) AS
			SELECT t1.name,t1.home_state
			FROM Sample.Person AS t1
			UNION
			SELECT t2.name,t2.office_state
			FROM Sample.Employee AS t2
	)
	if SQLCODE = 0 { 
		w !,"创建视图" 
	} else { 
		w "创建视图错误 SQLCODE=",SQLCODE 
	}
}

请注意,非限定视图名称(如上例中)默认为默认模式名称(例如,初始模式默认SQLUser.MyView),即使视图引用的表在示例模式中也是如此。因此,通常最好总是限定视图名称,以确保它与其关联表一起存储。

View ID: %vid

通过视图访问数据时, IRIS会为该视图返回的每一行分配一个连续的整数视图ID(%VID)。与表行ID编号一样,这些视图行ID编号是系统分配的、唯一的、非零的、非空的和不可修改的。此%VID通常是不可见的。与表行ID不同,它在使用星号语法时不会显示;只有在SELECT中显式指定时才会显示。%vid可用于进一步限制SELECT访问视图返回的行数

0
0 173
文章 姚 鑫 · 九月 22, 2021 3m read

第二十三章 SQL命令 CREATE USER

创建用户帐户。

大纲

CREATE USER user-name IDENTIFY BY password

CREATE USER user-name IDENTIFIED BY password

参数

  • user-name - 要创建的用户的名称。名称是最多128个字符的标识符。它可以包含Unicode字母。用户名不区分大小写。
  • password - 此用户的密码。密码必须至少为3个字符,并且不能超过32个字符。密码区分大小写。密码可以包含Unicode字符。

描述

CREATE USER命令使用指定的密码创建用户帐户。

用户名可以是最多160个字符的任何有效标识符。用户名必须遵循标识符命名约定。用户名可以包含Unicode字符。用户名不区分大小写。

作为分隔标识符指定的用户名可以是SQL保留字,可以包含逗号()、句号(.)、插入符号(^)和两个字符的箭头序列(->)。 它可以以除星号(*)以外的任何有效字符开头。

IDENTIFY BYIDENTIFIED BY关键字是同义词。

密码可以是数字文字、标识符或带引号的字符串。数字文字或标识符不必用引号括起来。带引号的字符串通常用于在密码中包含空格;带引号的密码可以包含除引号字符本身之外的任何字符组合。数字文字只能由09字符组成。标识符必须以字母(大写或小写)或%(百分号)开头;后面可以是字母、数字或以下任何符号的任意组合:_(下划线)。

密码区分大小写。密码长度必须至少为三个字符,且少于33个字符。指定过长或过短的密码都会生成SQLCODE-400错误,%msg值为“Error#845:Password is not Match Length or Pattern Requirements(错误#845:密码与长度或模式要求不匹配)”。

不能使用主机变量指定用户名或密码值。

创建用户不会创建任何角色,也不会向用户授予任何角色。相反,用户将被授予对其正在登录的数据库的权限,如果用户在命名空间中至少拥有一个SQL权限,则将被授予对%SQL/Service服务的使用权限。要为用户分配权限或角色,请使用GRANT命令。要创建角色,请使用CREATE ROLE命令。

如果调用CREATE USER来创建已经存在的用户,SQL会发出SQLCODE-118错误,并显示%msg值“名为‘name’的用户已存在”。可以通过调用$SYSTEM.SQL.Security.UserExists()方法来确定用户是否已经存在:

  WRITE $SYSTEM.SQL.Security.UserExists("Admin"),!
  WRITE $SYSTEM.SQL.Security.UserExists("BertieWooster")

如果指定的用户存在,则此方法返回1,如果该用户不存在,则返回0。用户名不区分大小写。

权限

CREATE USER命令是特权操作。在嵌入式SQL中使用CREATE USER之前,必须以具有适当权限的用户身份登录。否则将导致SQLCODE-99错误(特权冲突)。

使用$SYSTEM.Security.Login()方法分配具有适当权限的用户:

   DO $SYSTEM.Security.Login("_SYSTEM","SYS")
   &sql( /* SQL code here */  )

必须具有%Service_Login:Use权限才能调用$SYSTEM.Security.Login方法。

示例

下面的嵌入式SQL示例创建名为“BillTest”、密码为“Carl4SHK”的新用户。(提供了$Random切换,以便您可以重复执行此示例程序。)

ClassMethod CreateUser()
{
	d $SYSTEM.Security.Login("_SYSTEM","SYS")
	s x = $SYSTEM.SQL.UserExists("BillTest")
	if x = 0 {
		&sql(
			CREATE USER BillTest IDENTIFY BY Carl4SHK
		)
		if SQLCODE '= 0 {
			w "创建用户错误: ",SQLCODE,!
	        q
		}
	}
	w "用户BillTest存在",!

	s toggle = $RANDOM(2)
	if toggle = 0 { 
		&sql(DROP USER BillTest)
		if SQLCODE '= 0 {
			w "创建用户错误: ",SQLCODE,!
	        q
		}
	} else {
		w !,"没有创建。",!
	}
	w "用户BillTest存在? ", $SYSTEM.SQL.UserExists("BillTest"),!
	q
}
0
0 143
文章 姚 鑫 · 九月 21, 2021 9m read

第二十二章 SQL命令 CREATE TRIGGER(二)

SQL触发器代码

如果LANGUAGE SQL(默认),被触发的语句是一个SQL过程块,包括一个SQL过程语句后跟一个分号,或者关键字BEGIN后跟一个或多个SQL过程语句,每个SQL过程语句后跟一个分号,最后以END关键字结束。

被触发的操作是原子的,它要么完全应用,要么根本不应用,并且不能包含COMMITROLLBACK语句。 关键字BEGIN ATOMIC与关键字BEGIN是同义词。

如果语言是SQL, CREATE TRIGGER语句可以选择包含引用子句、WHEN子句和/或UPDATE OF子句。 UPDATE OF子句指定,只有在对为该触发器指定的一个或多个列执行UPDATE时,才应该执行该触发器。 带有LANGUAGE OBJECTSCRIPTCREATE TRIGGER语句不能包含这些子句。

SQL触发器代码作为嵌入式SQL执行。 这意味着IRIS将SQL触发器代码转换为ObjectScript; 因此,如果查看与SQL触发器代码对应的类定义,将在触发器定义中看到Language=objectscript

在执行SQL触发器代码时,系统会自动重置(NEWs)触发器代码中使用的所有变量。 在执行每条SQL语句之后 IRIS会检查SQLCODE。 如果发生错误,IRIS将%ok变量设置为0,终止并回滚触发器代码操作和相关的INSERTUPDATEDELETE

ObjectScript触发代码

如果LANGUAGE OBJECTSCRIPT,则CREATE TRIGGER语句不能包含引用子句、WHEN子句或UPDATE OF子句。 使用LANGUAGE OBJECTSCRIPT指定这些仅sql子句将分别导致编译时SQLCODE错误-49-57-50

如果LANGUAGE OBJECTSCRIPT,则触发语句是一个由一个或多个OBJECTSCRIPT语句组成的块,用花括号括起来。

因为触发器的代码不是作为过程生成的,所以触发器中的所有局部变量都是公共变量。 这意味着触发器中的所有变量都应该用NEW语句显式声明; 这可以防止它们在调用触发器的代码中与变量发生冲突。

如果触发器代码包含宏预处理器语句(#命令、##函数或$$$宏引用),这些语句将在CREATE trigger DDL代码本身之前编译。

ObjectScript触发器代码可以包含嵌入式SQL。

通过将%ok变量设置为0,可以在触发器代码中发出错误。 这将创建一个运行时错误,该错误将中止并回滚触发器的执行。 它生成适当的SQLCODE错误(例如,SQLCODE -131 " After insert trigger failed "),并返回用户指定的%msg变量的值作为字符串,以描述触发代码错误的原因。 请注意,将%ok设置为非数字值将设置%ok=0

即使是多事件触发器,系统也只生成一次触发器代码。

字段引用和伪字段引用

在ObjectScript中编写的触发器代码可以包含字段引用,指定为{fieldname},其中fieldname指定当前表中已有的字段。 花括号内不允许有空格。

你可以在字段名后面加上*N (new)*O (old),或*C (compare)来指定如何处理插入、更新或删除的字段数据值,如下所示:

  • {fieldname*N}
    • 对于UPDATE,在进行指定更改后返回新的字段值。
    • 对于INSERT,返回插入的值。
    • 对于DELETE,返回删除前的字段值。
  • {fieldname*O}
    • 对于UPDATE,返回进行指定更改之前的旧字段值。
    • 对于INSERT,返回NULL。
    • 对于DELETE,返回删除前的字段值。
  • {fieldname*C}
    • 对于UPDATE,如果新值与旧值不同,则返回1(TRUE),否则返回0(FALSE)。
    • 对于INSERT,如果插入的值非NULL,则返回1(TRUE),否则返回0(FALSE)。
    • 对于DELETE,如果要删除的值非NULL,则返回1(TRUE),否则返回0(FALSE)。

对于UPDATEINSERTDELETE{fieldname}返回与{fieldname*N}相同的值。

例如,以下触发器返回插入到Sample.Employee中的新行的Name字段值。(可以从SQL Shell执行插入以查看此结果):

CREATE TRIGGER InsertNameTrig AFTER INSERT ON Sample.Employee
   LANGUAGE OBJECTSCRIPT
   {WRITE "The employee ",{Name*N}," was ",{%%OPERATION},"ed on ",{%%TABLENAME},!}

在设置字段值的语句中不允许回车。

可以使用GetAllColumns()方法列出为表定义的字段名称。

用ObjectScript编写的触发器代码还可以包含伪字段引用变量{%%CLASSNAME}{%%CLASSNAMEQ}{%%OPERATION}{%%TABLENAME}{%%ID}。伪字段在类编译时被转换为特定值。所有这些伪字段关键字都不区分大小写。

  • {%%CLASSNAME}{%%CLASSNAMEQ}都转换为投影SQL表定义的类的名称。{%%CLASSNAME}返回不带引号的字符串,{%%CLASSNAMEQ}返回带引号的字符串。
  • 根据调用触发器的操作,{%%operation}转换为字符串文字,可以是INSERTUPDATEDELETE
  • {%%TABLENAME}转换为表的完全限定名称。
  • {%%ID}转换为RowID名称。当不知道RowID字段的名称时,此引用非常有用。

引用流属性

在触发器定义(如{StreamField}{StreamField*O}{StreamField*N})中引用流字段/属性时,{StreamField}引用的值是流的OID(对象ID)值。

对于BEFORE INSERTBEFORE UPDATE触发器,如果INSERT/UPDATE/ObjectSave指定了新值,则{StreamField*N}值将是临时流对象的OID或新的文字流值。对于BEFORE UPDATE触发器,如果没有为流字段/属性指定新值,则{StreamField*O}{StreamField*N}都将是当前字段/属性流对象的OID。

引用SQLComputed属性

当触发器定义中引用瞬态SqlComputed字段/属性(“calculate”或显式地“transient”)时,触发器不会识别Get()/Set()方法覆盖。 使用SQLCOMPUTED/SQLCOMPUTONCHANGE,而不是覆盖属性的Get()Set()方法。

使用Get()/Set()方法覆盖可能会导致以下错误结果:{property*O}值是用SQL确定的,没有使用覆盖的Get()/Set()方法。 因为属性没有存储在磁盘上,{property*O}使用SqlComputeCode“重新创建”旧值。 然而,{property*N}使用覆盖的Get()/Set()方法来访问属性的值。 因此,即使属性实际上没有改变,也有可能{property*O}{property*N}是不同的(因此{property*C}=1)

标签

触发器代码可能包含行标签(标签)。 若要在触发器代码中指定标签,请在标签行前面加上冒号,以指示该行应从第一列开始。 IRIS去掉冒号并将其余行作为标签处理。 但是,因为触发器代码是在任何过程块的作用域之外生成的,所以在整个类定义中每个标签必须是唯一的。 编译到类例程中的任何其他代码都不能定义相同的标签,包括在其他触发器、非过程块方法、SqlComputeCode和其他代码中。

注意:对标签使用冒号前缀要优先于对主机变量引用使用冒号前缀。 为了避免这种冲突,建议嵌入式SQL触发器代码行永远不要以主机变量引用开始。 如果必须以主机变量引用开始触发器代码行,可以通过加倍冒号前缀将其指定为主机变量(而不是标签)。

方法调用

可以从触发器代码中调用类方法,因为类方法不依赖于开放对象。 必须使用##class(classname).Method()语法来调用方法。 不能使用..Method()语法,因为该语法需要当前打开的对象。

可以将当前行字段的值作为类方法的参数传递,但类方法本身不能使用字段语法。

列出现有触发器

可以使用INFORMATION.SCHEMA.TRIGGERS类列出当前定义的触发器。 这个类列出每个触发器的名称、关联的模式和表名称以及触发器创建时间戳。 对于每个触发器,它列出EVENT_MANIPULATION属性(INSERT, UPDATE, DELETE, INSERT/UPDATE, INSERT/UPDATE/DELETE)和ACTION_TIMING属性(BEFORE, AFTER)。 它还列出了ACTION_STATEMENT,这是生成的SQL触发器代码。

引发运行时错误

触发器及其调用事件作为单个行上的原子操作执行。 那就是:

  • 回滚触发器失败之前,不执行关联的INSERTUPDATEDELETE操作,并释放该行上的所有锁。
  • 回滚失败的AFTER触发器,回滚关联的INSERTUPDATEDELETE操作,并释放该行上的所有锁。
  • 回滚失败的INSERTUPDATEDELETE操作,回滚关联的BEFORE触发器,释放该行上的所有锁。
  • 回滚失败的INSERTUPDATEDELETE操作,不执行关联的AFTER触发器,释放该行上的所有锁。

请注意,仅为当前行操作维护完整性。应用程序必须使用事务处理语句处理涉及多行操作的数据完整性问题。

因为触发器是原子操作,所以不能在触发器代码中编写事务语句(如COMMITROLLBACKS)。

如果INSERTUPDATEDELETE操作导致执行多个触发器,则一个触发器失败会导致所有其余触发器保持未执行状态。

  • SQLCODE-415:如果触发器代码中存在错误(例如,对不存在的表或未定义的变量的引用),则触发器代码的执行在运行时会失败, IRIS会发出SQLCODE-415错误“FATAL ERROR OVERT INGRED INTERT SQL FILER”
  • SQLCODE-130-135:当触发器操作失败时, IRIS在运行时发出SQLCODE错误代码-130-135之一,指示失败的触发器类型。可以通过在触发器代码中将%ok变量设置为0来强制触发器失败。这将发出相应的SQLCODE错误(例如,SQLCODE-131“AFTER INSERT TRIGGER FAILED”),并以字符串形式返回用户指定的%msg变量值,以描述触发器代码错误的原因。

示例

下面的示例演示使用ObjectScript DELETE触发器创建触发器。它假设有一个包含记录的数据表(TestDummy)。它使用嵌入式SQL创建一个日志表(TestDummyLog)和一个删除触发器,该触发器在对数据表执行删除操作时写入日志表。触发器插入数据表的名称、已删除行的RowId、当前日期和执行的操作类型(%oper特殊变量),在本例中为“DELETE”

ClassMethod CreateTrigger()
{
	&sql(
		CREATE TABLE TestDummyLog 
		(
			TableName VARCHAR(40),
			IDVal INTEGER,
			LogDate DATE,
			Operation VARCHAR(40)
		)
	)
	w !,"SQL日志表编码为: ",SQLCODE

	&sql(
		CREATE TRIGGER TrigTestDummy AFTER DELETE ON TestDummy
			LANGUAGE OBJECTSCRIPT 
			{
				NEW id
				SET id = {ID}
				&sql(
					INSERT INTO TestDummyLog 
					( 
						TableName, IDVal, LogDate, Operation 
					) 
					VALUES 
					(
						'TestDummy', :id, +$HOROLOG, :%oper)
					)
			}
	)
	w !,"SQL触发器代码为: ",SQLCODE
}

以下示例演示了使用SQL INSERT触发器的CREATE TRIGGER。第一个嵌入式SQL程序创建表、该表的插入触发器和日志表以供触发器使用。第二个嵌入式SQL程序针对该表发出INSERT命令,该命令调用触发器,该触发器在日志表中记录一个条目。显示日志条目后,程序将删除这两个表,以便可以重复运行此程序:

ClassMethod CreateTrigger1()
{
	d $SYSTEM.Security.Login("_SYSTEM","SYS")
	&sql(
		CREATE TABLE TestDummy 
		(
			testnum     INT NOT NULL,
			firstword   CHAR (30) NOT NULL,
			lastword    CHAR (30) NOT NULL,
			CONSTRAINT TestDummyPK PRIMARY KEY (testnum)
		)
	)
	w !,"SQL表编码为: ",SQLCODE
	&sql(
		CREATE TABLE TestDummyLog 
		(
			entry CHAR (60) NOT NULL
		)
	)
	w !,"SQL日志表编码为: ",SQLCODE
	&sql(
		CREATE TRIGGER TrigTestDummy AFTER INSERT ON TestDummy
		LANGUAGE SQL
		BEGIN
			INSERT INTO TestDummyLog 
			(
				entry
			) 
			VALUES 
			(
				CURRENT_TIMESTAMP||' INSERT to TestDummy'
			);
		END 
	)
	w !,"SQL触发器代码为: ",SQLCODE
}
ClassMethod CreateTrigger2()
{
	n SQLCODE, %ROWCOUNT, %ROWID
	&sql(
		INSERT INTO sqluser.TestDummy 
		(
			testnum, firstword, lastword
		) 
		VALUES 
		(
			46639, 'hello', 'goodbye'
		)
	)
	if SQLCODE = 0 {
		w !,"Insert succeeded"
		w !,"Row count=",%ROWCOUNT
		w !,"Row ID=",%ROWID 
	} else {
		w !,"Insert failed, SQLCODE=",SQLCODE 
	}
	&sql(
		SELECT entry INTO :logitem FROM sqluser.TestDummyLog
	)
	w !,"Log entry: ",logitem
	&sql(DROP TABLE TestDummy)
	&sql(DROP TABLE TestDummyLog)
	w !,"finished!"
}

下面的示例包括一个WHEN子句,该子句指定只有在满足括号中的谓词条件时才应执行操作:

CREATE TRIGGER Trigger_2 AFTER INSERT ON Table_1
  WHEN (f1 %STARTSWITH 'A')
  BEGIN
    INSERT INTO Log_Table VALUES (new_row.Category);
  END

以下示例定义在Sample.Employee中插入、更新或删除行后返回旧名称字段值和新名称字段值的触发器。(可以在SQL Shell中执行触发事件操作来查看此结果):

CREATE TRIGGER EmployNameTrig AFTER INSERT,UPDATE,DELETE ON Sample.Employee
   LANGUAGE OBJECTSCRIPT
   {WRITE "Employee old name:",{Name*O}," new name:",{Name*N}," ",{%%OPERATION}," on ",{%%TABLENAME},!}
0
0 136
文章 姚 鑫 · 九月 20, 2021 11m read

第二十一章 SQL命令 CREATE TRIGGER(一)

创建触发器

大纲

CREATE TRIGGER trigname {BEFORE | AFTER} event [,event]
          [ORDER integer]
          ON table
          [REFERENCING {OLD | NEW} [ROW] [AS] alias]
         action

参数

  • trigname - 要创建的触发器的名称,它是一个标识符。触发器名称可以是限定的,也可以是非限定的;如果限定,则其架构名称必须与表的架构名称匹配。
  • BEFORE eventAFTER event - 事件执行触发器的时间(之前或之后)。 触发器事件或以逗号分隔的触发器事件列表。可用的事件列表选项包括INSERTDELETEUPDATE。 可以指定事件的单个更新。UPDATE OF子句后跟列名或逗号分隔的列名列表。仅当languageSQL时才能指定UPDATE OF子句。不能在逗号分隔的事件列表中指定UPDATE OF子句。
  • ORDER integer - 可选-当具有相同时间和事件的表有多个触发器时,触发器的执行顺序。如果省略顺序,则为触发器分配的顺序为0。
  • ON table - 为其创建触发器的表。表名可以是限定的,也可以是非限定的;如果限定,则触发器必须驻留在与表相同的架构中。
  • REFERENCING OLD ROW AS aliasREFERENCING NEW ROW AS alias - 可选-仅当LanguageSQL时才能使用REFERENCING子句。REFERENCING子句允许指定可用于引用列的别名。引用旧行允许在UPDATEDELETE触发器期间引用列的旧值。引用新行允许在INSERTUPDATE触发器期间引用列的新值。作为关键字的行是可选的。对于更新,可以在同一引用子句中指定oldnew,如下所示:REFERENCING OLD oldalias NEW newalias
  • action - 触发器的程序代码。Action参数可以包含各种可选关键字子句,包括(按顺序):For Each子句;带有控制触发操作执行的谓词条件的WHEN子句;以及指定Language SQLLanguage OBJECTSCRIPTLANGUAGE子句。如果省略LANGUAGE子句,则默认为SQL。在这些子句之后,指定一行或多行SQL触发器代码或ObjectScript触发器代码,指定在执行触发器时要执行的操作。

描述

CREATE TRIGGER命令定义触发器,即修改特定表中的数据时要执行的代码块。当特定的触发事件发生时(例如将新行插入到指定表中),就会执行(“触发”或“拉出”)触发器。触发器执行用户指定的触发器代码。可以指定触发器应该在执行触发事件之前或之后执行此代码。触发器特定于指定表。

  • 触发器由指定的事件触发:INSERTDELETEUPDATE操作。可以指定逗号分隔的事件列表,以便在指定表上发生任何指定事件时执行触发器。
  • 一个触发器由一个事件触发(可能)多次或只触发一次。每修改一行,就触发一次行级触发器。语句级触发器对一个事件触发一次。此触发器类型是使用FOR EACH子句指定的。行级触发器是默认的触发器类型。
  • 通常,触发触发器代码会对另一个表或文件执行操作,例如执行日志记录操作或显示消息。触发触发器不能修改触发记录中的数据。例如,如果更新记录7会触发触发器,则该触发器的代码块不能更新或删除记录7。触发器可以修改调用该触发器的同一个表,但触发事件和触发器代码操作必须不同,以防止递归触发器无限循环。

如果要修改现有触发器,则必须先调用DROP TRIGGER删除旧版本的触发器,然后再调用CREATE TRIGGER来替换它。DROP TABLE删除与该表关联的所有触发器。

权限和锁

CREATE TRIGGER命令是特权操作。用户必须具有%CREATE_TRIGGER管理权限才能执行CREATE TRIGGER。否则将导致%msg User 'name' does not have %CREATE_TRIGGER privilegesSQLCODE-99错误。

用户必须对指定表拥有%ALTER特权。如果用户是表的所有者(创建者),则会自动授予该用户对该表的%ALTER权限。否则,必须授予用户对该表的%ALTER特权。否则将导致SQLCODE-99错误,并显示%msg User 'name' does not have required %ALTER privilege needed to create a trigger on table: 'Schema.TableName'

如果拥有适当的授予权限,则可以使用GRANT命令分配%CREATE_TRIGGER%ALTER权限。

在嵌入式SQL中,可以使用$SYSTEM.Security.Login()方法以具有适当权限的用户身份登录:

   DO $SYSTEM.Security.Login("_SYSTEM","SYS")
   &sql(      )

必须具有%Service_Login:Use权限才能调用$SYSTEM.Security.Login方法。

  • CREATE TRIGGER不能用于从持久类投影的表,除非表类定义包括[DdlAllowed]。否则,操作将失败,并显示SQLCODE-300错误%msg DDL not enabled for class 'Schema.tablename'.
  • CREATE TRIGGER不能用于从部署的持久类投射的表。此操作失败,并显示%msgSQLCODE-400错误 Unable to execute DDL that modifies a deployed class: 'classname'.

CREATE TRIGGER语句获取TABLE的表级锁。这可以防止其他进程修改表的数据。此锁在创建触发器操作结束时自动释放。

若要创建触发器,表不能在独占模式或共享模式下被另一个进程锁定。尝试在锁定表上执行CREATE TRIGGER操作将导致SQLCODE-110错误,并显示如下%msg: Unable to acquire exclusive table lock for table 'Sample.MyTest'

定义触发器的其他方式

可以将SQL触发器定义为类对象,如触发器定义中所述。以下是对象触发器的示例:

Trigger SQLJournal [ CodeMode = objectgenerator, Event = INSERT/UPDATE, ForEach = ROW/OBJECT, Time = AFTER ]
{  /* ObjectScript trigger code
      that updates a journal file
      after a row is inserted or updated. */
}

参数

trigname

触发器名称遵循与表名称相同的标识符要求,但不具有相同的惟一性要求。 触发器名称对于模式中的所有表应该是唯一的。 因此,在一个模式中引用不同表的触发器不应该具有相同的名称。 违反这种唯一性要求可能会导致DROP TRIGGER错误。

触发器及其关联表必须驻留在相同的模式中。 在相同的模式中,不能对触发器和表使用相同的名称。 违反触发器命名约定将导致在CREATE trigger执行时出现SQLCODE -400错误。

触发器名称可以是限定的,也可以是限定的。 限定触发器名称的形式如下:

schema.trigger

如果触发器名称未限定,则触发器架构名称默认为与指定表架构相同的架构。如果表名不合格,则表架构名默认为与指定触发器架构相同的架构。如果两者都未限定,则使用默认架构名称;不使用架构搜索路径。如果两者都是限定的,则触发器架构名称必须与表架构名称相同。模式名称不匹配会导致SQLCODE-366错误;只有当触发器名称和表名都是限定的,并且它们指定了不同的模式名称时才会出现这种情况。

触发器名称遵循标识符约定,受以下限制。默认情况下,触发器名称是简单标识符。触发器名称不应超过128个字符。触发器名称不区分大小写。

IRIS使用TRIGNAME IRIS类中生成相应的触发器名称。相应的类触发器名称仅包含字母数字字符(字母和数字),最大长度为96个字符。要生成此标识符名, IRIS首先从触发器名称中删除标点符号,然后生成96个(或更少)字符的唯一标识符,在需要创建唯一名称时用数字代替第96个字符。此名称生成对触发器的命名施加了以下限制:

  • 触发器名称必须至少包含一个字母。触发器名称的第一个字符或首个标点符号字符后的第一个字符必须是字母。
  • IRIS支持使用16位(宽)字符作为触发器名称。如果字符通过$ZNAME测试,则该字符是有效字母。
  • 由于为IRIS类生成的名称不包括标点符号,因此不建议(尽管可能)创建仅在标点符号方面不同的触发器名称。
  • 触发器名称可能比96个字符长得多,但前96个字母数字字符不同的触发器名称更易于使用。

使用现有触发器的名称发出CREATE TRIGGER会发出SQLCODE-365“触发器名称不唯一”错误。要更改现有触发器,必须发出DROP TRIGGER,然后使用新的触发器定义执行CREATE TRIGGER。如果模式中引用不同表的两个触发器具有相同的名称,则DROP TRIGGER可能会发出SQLCODE-365“Trigger Name Not Unique”错误,并显示消息“Trigger‘MyTrigName’Found in 2 CLASS”

event

触发触发器的时间由BEFOREAFTER关键字指定;这些关键字指定触发器操作应在 IRIS执行触发事件之前或之后发生。在执行指定事件之前但在验证事件之后执行BEFORE触发器。例如, IRIS仅在DELETE语句对指定行有效并且进程具有执行删除所需的权限(包括任何外键引用完整性检查)的情况下才执行BEFOREDELETE触发器。如果进程无法执行指定的事件, IRIS将为该事件发出错误代码;它不会执行BEFORE触发器。

关键字BEFOREAFTER后跟触发事件的名称,或以逗号分隔的触发事件列表。在指定表中插入行时,将执行指定为INSERT的触发器。从指定表中删除行时,将执行指定为DELETE的触发器。在指定表中更新行时,将执行指定为UPDATE的触发器。可以按任意顺序指定单个触发器事件或以逗号分隔的INSERTUPDATEDELETE触发器事件列表。

指定为UPDATE OF的触发器仅在指定表的一行中更新了一个或多个指定列时才执行。列名指定为逗号分隔的列表。列名可以按任何顺序指定。触发器的更新有以下限制:

  • UPDATE OF仅在触发器代码语言为SQL(默认)时有效;如果触发器代码语言为OBJECTSCRIPT,则会发出SQLCODE-50错误。
  • UPDATE OF不能与其他触发事件组合;如果在逗号分隔的触发事件列表中指定UPDATE OF,则会发出SQLCODE-1错误。
  • UPDATE OF不能指定不存在的字段; 发出SQLCODE -400错误。 UPDATE OF不能指定重复的字段名; 发出SQLCODE -58错误。

以下是事件类型的示例:

CREATE TRIGGER TrigBI BEFORE INSERT ON Sample.Person
       INSERT INTO TLog (Text) VALUES ('before insert')
CREATE TRIGGER TrigAU AFTER UPDATE ON Sample.Person
       INSERT INTO TLog (Text) VALUES ('after update')
CREATE TRIGGER TrigBUOF BEFORE UPDATE OF Home_Street,Home_City,Home_State ON Sample.Person
       INSERT INTO TLog (Text) VALUES ('before address update')
CREATE TRIGGER TrigAD AFTER UPDATE,DELETE ON Sample.Person
       INSERT INTO TLog (Text) VALUES ('after update or delete')

ORDER

ORDER子句确定同一表具有相同时间和事件的多个触发器时触发器的执行顺序。例如,两个AFTER DELETE触发器。首先执行具有最低阶整数的触发器,然后执行下一个更高的整数,依此类推。如果未指定ORDER子句,则使用分配的order 0(零)创建触发器。因此,不带ORDER子句的触发器总是在带ORDER子句的触发器之前执行。

可以将相同的订单值分配给多个触发器。还可以创建多个顺序为0(隐式或显式)的触发器。具有相同时间、事件和顺序的多个触发器以随机顺序一起执行。

触发器按以下顺序执行:time > order > event。因此,如果有BEFORE INSERT触发器和BEFORE INSERTUPDATE触发器,则将首先执行顺序值最低的触发器。如果具有相同顺序值的BEFORE INSERT触发器和BEFORE INSERTUPDATE触发器,则INSERT将在INSERTUPDATE。这是因为-时间和顺序相同-单事件触发器总是在多事件触发器之前执行。如果两个(或多个)触发器具有相同的时间、顺序和事件值,则执行顺序是随机的。

下面的示例展示了ORDER号的工作方式。 所有这些CREATE TRIGGER语句都创建由同一个事件执行的触发器:

CREATE TRIGGER TrigA BEFORE DELETE ON doctable
       INSERT INTO TLog (Text) VALUES ('doc deleted')
  /* Assigned ORDER=0 */
CREATE TRIGGER TrigB BEFORE DELETE ORDER 4 ON doctable
       INSERT INTO TReport (Text) VALUES ('doc deleted')
  /* Specified as ORDER=4 */
CREATE TRIGGER TrigC BEFORE DELETE ORDER 2 ON doctable
       INSERT INTO Ttemps (Text) VALUES ('doc deleted')
  /* Specified as ORDER=2 */
CREATE TRIGGER TrigD BEFORE DELETE ON doctable
       INSERT INTO Tflags (Text) VALUES ('doc deleted')
  /* Also assigned ORDER=0 */

这些触发器将按照以下顺序执行:(TrigA, TrigD)TrigC, TrigB。 注意,TrigATrigD有相同的序号,因此以随机顺序执行。

REFERENCING

REFERENCING子句可以为行的旧值和/或新值指定别名。旧值是UPDATEDELETE触发器触发操作之前的行值。新值是UPDATEINSERT触发器的触发操作之后的行值。对于UPDATE触发器,可以为BEFOREAFTER行值指定别名,如下所示:

REFERENCING OLD ROW AS oldalias NEW ROW AS newalias

关键字ROWAS是可选的。因此,同样的条款也可以指定为:

REFERENCING OLD oldalias NEW newalias

INSERT之前引用旧值或在DELETE之后引用新值是没有意义的。尝试这样做会导致编译时出现SQLCODE-48错误。

只有当操作程序代码为SQL时,才能使用REFERENCING子句。 使用LANGUAGE OBJECTSCRIPT子句指定references子句将导致SQLCODE -49错误。

下面是一个使用REFERENCINGINSERT的例子:

CREATE TRIGGER TrigA AFTER INSERT ON doctable
      REFERENCING NEW ROW AS new_row
BEGIN
      INSERT INTO Log_Table VALUES ('INSERT into doctable');
      INSERT INTO New_Log_Table VALUES ('INSERT into doctable',new_row.ID);
END

action

触发动作由以下元素组成:

  • 每个子句都是可选的。 可用的值为FOR EACH ROWFOR EACH ROW_AND_OBJECTFOR EACH STATEMENT。 默认值是FOR EACH ROW:
    • FOR EACH ROW—该触发器由触发语句影响的每一行触发。 注意,TSQL不支持行级触发器。
    • FOR EACH ROW_AND_OBJECT—该触发器由触发语句影响的每一行或通过对象访问进行的更改触发。 注意,TSQL不支持行级触发器。这个选项定义了一个统一触发器,之所以这么叫,是因为它是由通过SQL或对象访问发生的数据更改触发的。 (相比之下,与其他触发器相比,如果您希望在通过对象访问发生更改时使用相同的逻辑,则需要实现回调,如%OnDelete()。)
    • FOR EACH STATEMENT—该触发器对整个语句触发一次。 ObjectScript和TSQL触发器都支持语句级触发器。

可以使用INFORMATION.SCHEMA.TRIGGERSACTIONORIENTATION属性列出每个触发器的FOR EACH值。

一个可选的WHEN子句,由WHEN关键字和括在括号中的谓词条件(简单或复杂)组成。 如果谓词条件的计算结果为TRUE,则执行触发器。 当语言为SQL时,才能使用WHEN子句。 WHEN子句可以引用oldaliasnewalias值。

一个可选的LANGUAGE子句,可以是LANGUAGE SQLLANGUAGE OBJECTSCRIPT。 默认为LANGUAGE SQL

在触发器执行时执行的用户编写的代码。

0
0 208
文章 姚 鑫 · 九月 19, 2021 6m read

第二十章 SQL命令 CREATE TABLE AS SELECT

将现有表中的列定义和列数据复制到新表中。

大纲

CREATE TABLE table-name AS query [shard-key] [WITH table-option]

参数

  • table-name 要创建的表的名称,指定为有效标识符。表名可以是限定的(schema.table),也可以是非限定的(Table)。未限定的表名采用缺省模式名。
  • query 为新表提供列定义和列数据的SELECT查询。此查询可以指定表、视图或多个联接的表。
  • shard-key - 可选-切片键定义,由切片键本身或后跟附加切片键定义语法组成。
  • WITH table-option - 可选-一个或多个表选项的逗号分隔列表,如%CLASSPARAMETER关键字后跟名称和关联的文字,或STORAGETYPE=ROWSTORAGETYPE=COLUMN

描述

CREATE TABLE AS SELECT命令通过复制SELECT查询中指定的现有表(或多个表)中的列定义和列数据来创建新表。SELECT查询可以指定表或视图的任意组合。

注:CREATE TABLE AS SELECT COPPLICES FROM现有表定义。使用CREATE TABLE命令指定新的表定义。

还可以使用QueryToTable()方法调用复制表操作:

DO $SYSTEM.SQL.Schema.QueryToTable(query,table-name,0)

复制数据定义

  • CREATE TABLE AS SELECT从查询表复制列定义。要重命名复制的列,请在查询中指定列别名。

如果查询指定联接的表,CREATE TABLE AS SELECT可以从多个表复制列定义。

  • CREATE TABLE AS SELECT始终将RowID定义为隐藏。
    • 如果源表具有隐藏的RowID,则CREATE TABLE AS SELECT不会复制源表RowID,但会为创建的表创建新的RowID列。复制的行将被分配新的连续RowID值。
    • 如果源表具有公共(非隐藏)RowID,或者如果查询显式选择了隐藏的RowID,则CREATE TABLE AS SELECT将为该表创建一个新的RowID列。源表RowID作为普通的BigInt字段复制到新表中,该字段不是隐藏的、不是唯一的,也不是必需的。如果源表RowID命名为“ID”,则新表的RowID命名为“ID1”
  • 如果源表有标识字段,CREATE TABLE AS SELECT会将其及其当前数据复制为非零正整数的普通BIGINT字段,该字段既不唯一也不是必需的。
  • CREATE TABLE AS SELECT定义IDKEY索引。它不复制与复制的列定义相关联的索引。
  • CREATE TABLE AS SELECT不复制任何列约束:它不复制与复制的列定义关联的NULL/NOT NULLUNIQUEPRIMARY KEYFOREIGN KEY约束。
  • CREATE TABLE AS SELECT不复制与复制的列定义关联的默认限制或值。
  • CREATE TABLE AS SELECT不复制与复制的列定义关联的COMPUTECODE数据约束。
  • CREATE TABLE AS SELECT不复制与复制表或列定义关联的%DESCRIPTION字符串。

权限

CREATE TABLE AS SELECT命令是一个特权操作。用户必须具有%CREATE_TABLE管理权限才能执行CREATE TABLE AS SELECT。否则将导致%msg用户‘name’没有%CREATE_TABLE权限的SQLCODE-99错误。如果拥有适当的授予权限,则可以使用GRANT命令将%CREATE_TABLE权限分配给用户或角色。管理权限是特定于命名空间的。

用户必须对query中指定的表具有SELECT权限。

表名

表名可以是限定的,也可以是非限定的。

  • 非限定表名具有以下语法:tablename;它省略架构(和句点(.)。字符)。未限定的表名采用缺省模式名。系统范围内的初始默认架构名称是SQLUser,它对应于默认的类包名称User。架构搜索路径值将被忽略。

可以配置默认架构名称。

要确定当前系统范围内的默认架构名称,请使用$SYSTEM.SQL.Schema.Default()方法。

  • 限定表名具有以下语法:schema.tablename。它可以指定现有的架构名称,也可以指定新的架构名称。指定现有架构名称会将该表放入该架构中。指定新的模式名称将创建该模式(以及关联的类包),并将表放入该模式中。

表名和模式名遵循SQL标识符命名约定,受使用非字母数字字符、唯一性和最大长度的附加约束。以%字符开头的名称保留供系统使用。默认情况下,模式名和表名是简单标识符,不区分大小写。

IRIS使用表名生成相应的类名。 IRIS使用架构名称来生成相应的类包名称。类名仅包含字母数字字符(字母和数字),并且在前96个字符内必须是唯一的。要生成类名 IRIS首先从表名中剔除符号(非字母数字)字符,然后生成唯一的类名,从而施加唯一性和最大长度限制。要生成包名,它然后对架构名中的符号(非字母数字)字符进行剥离或执行特殊处理。然后, IRIS生成唯一的包名,施加唯一性和最大长度限制。

可以对架构和表使用相同的名称。同一架构中的表和视图不能使用相同的名称。

架构名称不区分大小写;相应的类包名称区分大小写。如果指定的架构名称仅与现有类包名的大小写不同,并且包定义为空(不包含类定义)。 IRIS通过更改类包名称的大小写来协调这两个名称。

IRIS支持16位(宽)字符作为表名和列名。对于大多数区域设置,可以使用重音字母作为表名,并且重音符号包含在生成的类名中。以下示例对SQL表名执行验证测试:

ClassMethod CreateTableAsSelect(tname)
{
	s x = $SYSTEM.SQL.IsValidRegularIdentifier(tname)
	if x = 0 {
		IF $l(tname)>200  {
			w "表名太长" 
			QUIT
		} elseif $SYSTEM.SQL.IsReservedWord(tname) {
			w "表名是保留字" 
			q
		} else {
			w "表名包含无效字符",!
			s nls = ##class(%SYS.NLS.Locale).%New()
			if nls.Language [ "Japanese" {
				w "日语区域设置不能使用重音字母"
				q 
			}
			q 
		}
	} else { 
		w tname," 是有效的表名"
	}
}

注意:日语区域设置不支持标识符中的重音字母字符。日语标识符可以包含(除日语字符外)拉丁字母字符A-Za-z(65-9097-122)、下划线字符(95)和希腊大写字母字符(913-929931-937)。Nls.language测试使用[(CONTAINS运算符)而不是=,因为不同的操作系统平台有不同的日语区域设置。

表存在

要确定当前命名空间中是否已存在表,请使用$SYSTEM.SQL.Schema.TableExists(“schema.tname”).

默认情况下,当创建与现有表同名的表时, IRIS拒绝REATE TABLE尝试并发出SQLCODE-201错误。要确定当前系统范围的配置设置,请调用$SYSTEM.SQL.CurrentSettings(),它将显示Allow DDL CREATE TABLECREATE VIEW for Existing tableview设置。默认值为0;这是此选项的推荐设置。如果此选项设置为1 IRIS将删除与该表关联的类定义,然后重新创建它。这与执行DROP TABLE、删除现有表,然后执行CREATE TABLE大致相同。在这种情况下,强烈建议$SYSTEM.SQL.CurrentSettings(),Does DDL DROP TABLE delete the table's data?值设置为1(默认值)。

在管理门户、系统管理、配置、SQL和对象设置中,通过选中忽略冗余DDL语句复选框,可以在系统范围内设置此选项(以及其他类似的创建、更改和删除选项)。

WITH table-option

可以在SELECT查询之后指定可选的WITH子句。WITH子句可以包含逗号分隔的%CLASSPARAMETER子句列表和/或STORAGETYPE子句。

使用%CLASSPARAMETER关键字可以将类参数定义为CREATE TABLE AS SELECT命令的一部分。类参数始终定义为常量值。关键字%CLASSPARAMETER后跟类参数名称、可选等号和要分配给该类参数的文字值(字符串或数字)。

可以指定多个%CLASSPARAMETER关键字子句,每个子句定义一个类参数。多个%CLASSPARAMETER子句用逗号分隔。

例如,默认情况下,CREATE TABLE AS SELECT将使用生成的全局名称(如^EPgS.D8T6.1)为创建的表创建IDKEY索引;其他索引使用具有唯一整数后缀的相同全局名称。以下示例显示如何为IDKEY索引和未来的其他索引指定显式全局名称:

CREATE TABLE Sample.YoungPeople AS SELECT Name,Age FROM Sample.People WHERE Age<21 WITH %CLASSPARAMETER DEFAULTGLOBAL = '^GL.UNDERTWENTYONE'
0
0 1155
文章 姚 鑫 · 九月 18, 2021 4m read

第十九章 SQL命令 CREATE TABLE(六)

WITH子句,%CLASSPARAMETER关键字,STORAGETYPE关键字

可选的WITH子句可以在表格元素逗号结尾的圆括号之后和Shard Key定义(如果存在的话)之后指定。 WITH子句可以包含一个用逗号分隔的列表:

  • 一个或多个%CLASSPARAMETER 子句。
  • STORAGETYPE子句

%CLASSPARAMETER子句

WITH关键字之后,可以指定多个%CLASSPARAMETER关键字子句,每个子句定义一个类参数。 多个%CLASSPARAMETER子句子句之间用逗号分隔。 为了向后兼容,支持将%CLASSPARAMETER关键字子句指定为table-element-commalist中的元素。 在两个位置中指定相同的%CLASSPARAMETER关键字子句将产生SQLCODE -327错误。

%CLASSPARAMETER关键字后面跟着类参数名称、一个可选的等号和要分配给该类参数的文字值(字符串或数字)。 类参数总是定义为常数值。

因为用户可以用任何名称或值定义额外的类参数,所以只执行语法验证; 既不验证类参数是否存在,也不验证类参数的有效值。 下面的示例定义了两个类参数; 第一个%CLASSPARAMETER子句使用了等号,第二个省略了等号:

CREATE TABLE OurEmployees (
    EMPNUM     INT NOT NULL,
    NAMELAST   CHAR(30) NOT NULL,
    NAMEFIRST  CHAR(30) NOT NULL,
    CONSTRAINT EMPLOYEEPK PRIMARY KEY (EMPNUM)
    )
WITH %CLASSPARAMETER DEFAULTGLOBAL = '^GL.EMPLOYEE',
     %CLASSPARAMETER MANAGEDEXTENT 0

DEFAULTGLOBAL:默认情况下,CREATE TABLE用生成的全局名称为创建的表创建IDKEY索引,例如^EPgS.D8T6.1; 其他索引使用生成的具有唯一整数后缀的相同全局名称。这个例子指定%CLASSPARAMETER DEFAULTGLOBAL = '^GL.EMPLOYEE' 作为索引的显式全局名称。可以使用DEFAULTGLOBAL指定扩展的全局引用,或者完整引用(%CLASSPARAMETER DEFAULTGLOBAL = '^|"USER"|GL.EMPLOYEE')或者只是命名空间部分 (%CLASSPARAMETER DEFAULTGLOBAL = '^|"USER"|')

当前使用的类参数有ALLOWIDENTITYINSERT, DATALOCATIONGLOBAL, DEFAULTGLOBAL, DSINTERVAL, DSTIME, EXTENTQUERYSPEC, EXTENTSIZE, GUIDENABLED, MANAGEDEXTENT, READONLY, ROWLEVELSECURITY, SQLPREVENTFULLSCAN, USEEXTENTSET, VERSIONCLIENTNAME, VERSIONPROPERTY

可以使用USEEXTENTSETDEFAULTGLOBAL类参数定义表数据存储和索引数据存储的全局命名策略。

IDENTIFIEDBY类参数已弃用。 必须将IDENTIFIEDBY关系转换为 IRIS中支持的正确的父/子关系。

定义分片表的CREATE TABLE不能定义DEFAULTGLOBALDSINTERVALDSTIMEVERSIONPROPERTY类参数。

STORAGETYPE子句

WITH关键字之后,可以指定一个STORAGETYPE子句,STORAGETYPE=ROWSTORAGETYPE=COLUMN。该表选项用于设置STORAGEDEFAULT参数。 如果指定ROW,则PARAMETER STORAGEDEFAULT; 将出现在类定义中。将出现在类定义中。 如果指定COLUMN,则PARAMETER STORAGEDEFAULT = "column"; 将出现在类定义中。

如果多次指定STORAGETYPE,则生成SQLCODE -327错误。

示例:动态SQL和嵌入式SQL

下面的示例演示了使用动态SQL和嵌入式SQL创建表。 注意,在动态SQL中,可以在同一个程序中创建一个表并将数据插入到表中; 在嵌入式SQL中,必须使用单独的程序来创建表并将数据插入到表中。

最后一个程序示例删除表,以便可以重复运行这些示例。

下面的动态SQL示例创建表SQLUser.MyStudents。 注意,因为COMPUTECODE是ObjectScript代码,而不是SQL代码,ObjectScript $PIECE函数使用双引号分隔符; 因为这行代码本身是一个带引号的字符串,$PIECE分隔符必须通过加倍的方式转义为字面量,如下所示:

ClassMethod CreateTable7()
{
	s stuDDL=5
	s stuDDL(1)="CREATE TABLE SQLUser.MyStudents ("
	s stuDDL(2)="StudentName VARCHAR(32),StudentDOB DATE,"
	s stuDDL(3)="StudentAge INTEGER COMPUTECODE {SET {StudentAge}="
	s stuDDL(4)="$PIECE(($PIECE($H,"","",1)-{StudentDOB})/365,""."",1)} CALCULATED,"
	s stuDDL(5)="Q1Grade CHAR,Q2Grade CHAR,Q3Grade CHAR,FinalGrade VARCHAR(2))"
	s tStatement = ##class(%SQL.Statement).%New(0,"Sample")
	s qStatus = tStatement.%Prepare(.stuDDL)
	if qStatus'=1 {
		w "%Prepare failed:" 
		d $System.Status.DisplayError(qStatus) 
		q 
	}
	s rtn = tStatement.%Execute()
	if rtn.%SQLCODE = 0 {
		w !,"表创建成功"
	} elseif rtn.%SQLCODE=-201 {
		w "表已存在,SQLCODE=",rtn.%SQLCODE,!
	} else {
		w !,"表创建失败,SQLCODE=",rtn.%SQLCODE,!
		w rtn.%Message,! 
	}
}

以下嵌入式 SQL 示例创建表 SQLUser.MyStudents

ClassMethod CreateTable8()
{
	&sql(CREATE TABLE SQLUser.MyStudents 
		(
			StudentName VARCHAR(32),StudentDOB DATE,
			StudentAge INTEGER COMPUTECODE {
				SET {StudentAge}=
				$PIECE(($PIECE($H,",",1)-{StudentDOB})/365,".",1)
			} CALCULATED,
			Q1Grade CHAR,Q2Grade CHAR,Q3Grade CHAR,FinalGrade VARCHAR(2)
		)
	)
	if SQLCODE=0 {
		WRITE !,"Created table" 
	} ELSEIF SQLCODE=-201 {
		WRITE !,"SQLCODE=",SQLCODE," ",%msg 
	} ELSE {
		WRITE !,"CREATE TABLE failed, SQLCODE=",SQLCODE 
	} 
}

以下示例删除由前面的示例创建的表:

ClassMethod CreateTable9()
{
	&sql(
		DROP TABLE SQLUser.MyStudents
	)
	if SQLCODE=0 {
		w !,"表已删除" 
	} else {
		w !,"SQLCODE=",SQLCODE," ",%msg 
	}
}
0
0 101
文章 姚 鑫 · 九月 17, 2021 9m read

第十八章 SQL命令 CREATE TABLE(五)

定义外键

外键是引用另一个表的字段;存储在外键字段中的值是唯一标识另一个表中的记录的值。此引用的最简单形式如下例所示,其中外键显式引用Customers表中的主键字段CustID

CREATE TABLE Orders (
   OrderID INT UNIQUE NOT NULL,
   OrderItem VARCHAR,
   OrderQuantity INT,
   CustomerNum INT,
   CONSTRAINT OrdersPK PRIMARY KEY (OrderID),
   CONSTRAINT CustomersFK FOREIGN KEY (CustomerNum) REFERENCES Customers (CustID)
   )

最常见的情况是,外键引用另一个表的主键字段。但是,外键可以引用RowID(ID)或标识列。在任何情况下,外键引用都必须存在于被引用的表中,并且必须定义为唯一的;被引用的字段不能包含重复值或NULL

在外键定义中,可以指定:

  • 字段名:FOREIGN KEY (CustomerNum) REFERENCES Customers (CustID)。 外键字段(CustomerNum)和引用字段(CustID)可以有不同的名称(或相同的名称),但必须具有相同的数据类型和字段约束。
  • 以逗号分隔的字段名列表: FOREIGN KEY (CustomerNum,SalespersonNum) REFERENCES Customers (CustID,SalespID)。 外键字段和引用字段的数量和列出的顺序必须相对应。
  • 省略的字段名:FOREIGN KEY (CustomerNum)引用Customers
  • 显式的RowID字段:FOREIGN KEY (CustomerNum) REFERENCES Customers (%ID)。 省略字段名的同义词。 如果表的类定义包含SqlRowIdName,可以将此值指定为显式的RowID

如果定义了一个外键并省略了引用的字段名,外键的默认值如下:

  • 为指定的表定义的主键字段。
  • 如果指定的表没有定义主键,则外键默认为为指定的表定义的IDENTITY列。
  • 如果指定的表既没有定义的主键,也没有定义的标识列,则外键默认为RowID。仅当指定的表将RowID定义为PUBLIC时才会发生这种情况;指定的表定义可以通过指定%PUBLICROWID关键字或通过SqlRowIdPrivate=0(默认值)的相应类定义显式执行此操作。如果指定的表未将RowID定义为PUBLIC, IRIS会发出SQLCODE-315错误。在RowID上定义外键时必须省略引用的字段名;尝试将ID显式指定为引用的字段名会导致SQLCODE-316错误。

如果这些默认值都不适用,IRIS将发出SQLCODE-315错误。

在类定义中,可以指定包含基于父表IDKEY属性的字段的外键,如下例所示:

  ForeignKey Claim(CheckWriterPost.Hmo,Id,Claim) References SQLUser.Claim.Claim(DBMSKeyIndex);

因为在子类的外键中定义的父字段必须是父类的IDKEY索引的一部分,所以此类型的外键唯一支持的引用操作是无操作。

  • 如果外键引用了不存在的表, IRIS会发出SQLCODE-310错误,并在%msg中提供其他信息。
  • 如果外键引用了不存在的字段, IRIS将发出SQLCODE-316错误,并在%msg中提供其他信息。
  • 如果外键引用了非唯一字段, IRIS会发出SQLCODE-314错误,并在%msg中提供其他信息。

如果外键字段引用单个字段,则这两个字段必须具有相同的数据类型和字段数据约束。

在父/子关系中,没有定义的子项顺序。应用程序代码不得依赖于任何特定顺序。

可以定义引用以只读方式装载的数据库中的类的外键约束。要定义外键,用户必须对被引用的表或被引用的表的列具有REFERENCES特权。如果通过动态SQL或xDBC执行CREATE TABLE,则需要REFERENCES权限。

指称动作子句

如果一个表包含外键,对一个表的更改会对另一个表产生影响。为了保持数据的一致性,在定义外键时,还需要定义外键数据所来自的记录的更改对外键值的影响。

外键定义可能包含两个引用动作子句:

ON DELETE ref-action

ON UPDATE ref-action

ON DELETE子句为引用的表定义了删除规则。当试图从引用表中删除一行时,ON DELETE子句定义应该对引用表中的行采取什么操作。

ON UPDATE子句定义被引用表的更新规则。当尝试更改(更新)引用表中行的主键值时,ON UPDATE子句定义应该对引用表中的行执行什么操作。

SQL支持以下外键引用操作:

  • NO ACTION
  • SET DEFAULT
  • SET NULL
  • CASCADE

NO ACTION-删除行或更新被引用表中的键值时,将检查所有引用表,以查看是否有任何行引用要删除或更新的行。如果是,则删除或更新失败。(如果外键引用自身,则此约束不适用。)。默认情况下不执行任何操作。无操作是切片表支持的唯一引用操作。任何其他引用操作都会导致SQLCODE-400错误,并显示如下消息:Error#5600:Feature not supported for sharded class Sample.MyShardT: Foreign Key ON UPDATE action of 'setnull'

SET NULL-删除行或更新被引用表中的键值时,将检查所有引用表,以查看是否有任何行引用要删除或更新的行。如果是,则该操作会导致引用要删除或更新的行的外键字段设置为NULL。外键字段必须允许空值。

SET DEFAULT-删除行或更新被引用表中的键值时,将检查所有引用表,以查看是否有任何行引用要删除或更新的行。如果是,则该操作会导致引用要删除或更新的行的外键字段设置为该字段的默认值。如果外键字段没有默认值,它将被设置为NULL。需要注意的是,在包含缺省值条目的被引用表中必须存在一行。

CASCADE -删除被引用表中的行时,将检查所有引用表,以查看是否有任何行引用要删除的行。如果是这样,则删除操作会导致其外键字段引用要删除的行的行也被删除。

在被引用表中更新行的键值时,将检查所有引用表,以查看是否有任何行引用要更新的行。如果是,则更新会导致引用要更新的行的外键字段将更新级联到所有引用行。

表定义不应该有两个不同名称的外键,这两个外键引用相同的标识符-公共字段并执行相互矛盾的引用操作。根据ANSI标准,如果定义了对同一字段执行相互矛盾的引用操作的两个外键(例如,ON DELETE CASCADEON DELETE SET NULL), SQL不会发出错误。相反,当DELETEUPDATE操作遇到这些相互矛盾的外键定义时, SQL会发出错误。

下面是一个嵌入式SQL示例,它发出一条使用两个引用动作子句的CREATE TABLE语句。请注意,本例假设已存在名为PhysNum(主键字段为PhysNum)的关联表。

ClassMethod CreateTable6()
{
	d $SYSTEM.Security.Login("_SYSTEM","SYS")
	&sql(
		CREATE TABLE Patient 
		(
			PatNum VARCHAR(16),
			Name VARCHAR(30),
			DOB DATE,
			Primary_Physician VARCHAR(16) DEFAULT 'A10001982321',
				CONSTRAINT Patient_PK PRIMARY KEY (PatNum),
				CONSTRAINT Patient_Physician_FK FOREIGN KEY
			Primary_Physician REFERENCES Physician (PhysNum)
				ON UPDATE CASCADE
				ON DELETE SET NULL
		)
	)
	WRITE !,"SQL code: ",SQLCODE
}

NOCHECK关键字

如果指定NOCHECK关键字, IRIS不检查外键引用完整性。这意味着INSERTUPDATE操作可能会为外键字段指定一个与被引用表中的行不对应的值。NOCHECK关键字还阻止执行外键的引用操作子句。SQL查询处理器可以使用外键来优化表之间的联接。但是,如果将外键定义为NOCHECK,则SQL查询处理器不会将其视为已定义的外键。NOCHECK外键仍然作为外键报告给xDBC目录查询。

分片表和外键

外键支持分片和未分片表的任意组合,包括:键表分片、fkey表未分片;key表未分片、fkey表分片;同时支持key表和fkey表分片。被引用表中的键可以是碎片键,也可以是另一个键。外键可以是单个字段或多个字段。

NO ACTION是切片表支持的唯一引用操作。

隐式外键

最好显式定义所有外键。如果定义了显式外键, IRIS会报告此约束,而不定义隐式外键约束。

但是,可以将隐式外键投影到ODBC/JDBC和管理门户。所有字段引用都作为外键投影到ODBC/JDBC,如下所示:

这些隐式外键被报告为无操作的UPDATEDELETE引用操作。此隐式引用外键不是真正的外键,因为没有强制执行引用操作。为引用报告的此外键的名称为“IMPLICIT_FKEY_REFERENCE__”_fieldname。将此引用报告为外键是为了与第三方工具实现互操作性。

定义分片键

提供将表定义为分片的选项是为了提高针对该表的查询性能,特别是对于包含大量记录的表。分片表只能在分片环境中使用;非分片表可以在分片或非分片环境中使用。并不是所有的表都适合进行分片。分片环境中的最佳性能通常是通过组合使用分片表(通常非常大的表)和非分片表来实现的。

如果当前命名空间配置为分片(分片主数据服务器上的主命名空间),则可以为表指定分片键。如果没有为切分配置当前命名空间,则指定切片键的CREATE TABLE失败,并返回SQLCODE-400致命错误,并显示%msg错误#9319:Current namespace %1 has no shards configured

切片键定义应该紧跟在table-element-commist的右括号之后,但在WITH子句之前(如果指定)。为了向后兼容,支持将分片键定义指定为table-element-commist中的元素。在两个位置指定分片键定义会生成SQLCODE-327错误。

有三个选项可用于指定碎片键定义:

  • SHARD:如果仅指定关键字Shard, IRIS使用表的RowID字段作为碎片键。对于几乎所有的切片表,这都是最有效的方法。如果表有定义的标识字段,但没有显式的分片键,它将使用该标识字段作为分片键。
  • SHARD KEY(Fieldname):可以使用此语法指定RowID以外的分片键。可以指定一个字段名称或逗号分隔的字段名称列表作为分片键。分片键字段的数据类型必须是数字或字符串数据类型。
  • SHARD KEY (fieldname) COSHARD WITH (tablename):可选的COSHARD WITH子句允许指定一个表,以便使用定义的切片表进行编码。此选项用于为查询中通常联接的大表启用共分联接。带有关键字的COSHARD子句和包含coshard表名的圆括号都是可选的。

定义的切片表必须具有显式指定的切片键(字段)。此分片键字段必须采用整数值;它应该与系统分配的协分片表的RowID值相匹配。例如,SHARD KEY (deptnum) COSHARD WITH departmentCOSHARD WITH子句中指定的表必须是具有系统分配的切片键的切片表。

COSHARD WITH子句在分割表的ShardKey索引中定义CoshardWith索引关键字。此CoshardWith索引关键字等于投影表的类。

可以通过查看Cosharding Comment选项来确定查询中指定的哪些已分片表是共分的。

必须使用CREATE TABLE或持久类定义将表定义为分割表。不能使用ALTER TABLE向现有表添加分片键。

如果表有定义的IDKEY,则必须将字段定义为分片键字段。既不能指定fieldname不是该字段的分片键(Fieldname),也不能指定没有定义键字段的分片。尝试这样做会导致SQLCODE-400错误,并显示如下%msg:ERROR #5597: Sharded table's shard key (%1) must be the same as the idkey (%2) when the idkey is defined.

如果表中定义了标识字段,则可以将该字段定义为分片关键字字段,也可以在标识字段以外的一个或多个字段上定义分片关键字。

除非切片键是唯一键的子集,否则切片表上的唯一字段约束可能会对插入/更新性能产生重大负面影响。

涉及到需要原子性的复杂事务的表永远不应该被分片。

分片表在分片主数据服务器上的主命名空间中定义。该主命名空间还可以包括非分片表。分片对于SQL查询是透明的;不需要特殊的查询语法。查询不需要知道表是分片的还是非分片的。同一查询可以访问分割表和非分割表。查询可以包括分割表和非分割表之间的联接。

分片表定义限制

  • 分片表不能包含ROWVERSION数据类型或SERIAL (%Library.Counter)数据类型字段。
  • 分片表不能指定VERSIONPROPERTY类参数。
0
0 141
文章 姚 鑫 · 九月 15, 2021 10m read

第十七章 SQL命令 CREATE TABLE(四)

唯一字段约束

唯一字段约束对多个字段的组合值施加唯一值约束。它具有以下语法:

CONSTRAINT uname UNIQUE (f1,f2)

此约束指定字段f1f2的值组合必须始终是唯一的,即使这两个字段本身的值可能不是唯一的。可以为此约束指定一个、两个或多个字段。

此约束中指定的所有字段都必须在字段定义中定义。如果在此约束中指定的字段没有出现在字段定义中,则会生成SQLCODE-86错误。指定的字段应定义为非空。任何指定的字段都不应定义为唯一的,因为这会使指定此约束变得毫无意义。

字段可以按任何顺序指定。字段顺序指定相应索引定义的字段顺序。允许重复的字段名称。虽然可以在唯一字段约束中指定单个字段名称,但这与为该字段指定唯一数据约束在功能上是相同的。单字段约束确实提供了约束名称以供将来使用。

可以在表定义中指定多个唯一字段约束语句。约束语句可以在字段定义中的任何位置指定;按照惯例,它们通常放在已定义字段列表的末尾。

约束名称

Constraint关键字和唯一字段约束名称是可选的。以下各项在功能上等效:

CONSTRAINT myuniquefields UNIQUE (name,dateofbirth)
UNIQUE (name,dateofbirth)

约束名唯一地标识约束,并且还用于派生相应的索引名。 建议指定约束名称; 当使用ALTER TABLE命令从表定义中删除约束时,需要此约束名。 约束名称可以是任何有效的标识符; 如果指定为分隔符,则约束名可以包含".", "^", ",", "->"字符。

ALTER TABLE无法删除约束UNIQUE中列出的列。尝试这样做会生成SQLCODE-322错误。

RowID记录标识符

在 SQL 中,每条记录都由一个唯一的整数值标识,称为 RowID。在 SQL 中,不需要指定 RowID 字段。创建表并指定所需的数据字段时,会自动创建 RowID 字段。此 RowID 在内部使用,但未映射到类属性。默认情况下,它的存在仅在类投影到 SQL 表时可见。在这个投影的 SQL 表中,会出现一个额外的 RowID 字段。默认情况下,此字段命名为“ID”并分配给第 1 列。

%PUBLICROWID

默认情况下,RowID是隐藏和私有的。指定%PUBLICROWID关键字使RowID不会隐藏和公开。如果指定%PUBLICROWID关键字,则使用“not SqlRowIdPrivate”定义与表对应的类。此可选关键字可以在逗号分隔的表元素列表中的任何位置指定。ALTER TABLE不能用于指定%PUBLICROWID

如果RowID是公共的:

  • RowID值通过SELECT *显示。
  • RowID可以用作外键引用。
  • 如果没有定义主键,RowID将被视为具有约束名称RowIDField_As_PKey的隐式主键约束。
  • 如果没有指定要复制的字段名,则不能使用该表将数据复制到重复表中。

位图扩展索引

使用CREATE TABLE创建表时,缺省情况下, IRIS会自动定义相应类的位图范围索引。位图区索引的SQL MapName%%DDLBEIndex

Index DDLBEIndex [ Extent, SqlName = "%%DDLBEIndex", Type = bitmap ];

在以下任何情况下都不会创建该位图范围索引:

  • 该表被定义为临时表。
  • 该表定义了显式IDKEY索引。
  • 该表包含定义的标识字段,该字段没有MINVAL=1
  • $SYSTEM.SQL.Util.SetOption()方法DDLDefineBitmapExtent选项设置为0以覆盖系统范围的默认设置。要确定当前设置,请调用$SYSTEM.SQL.CurrentSettings()方法,该方法显示a Do classes created by a DDL CREATE TABLE statement define a bitmap extent index

如果在创建位图索引后,对自动定义位图范围索引的表调用CREATE BITMAPEXTENT INDEX,则先前定义的位图范围索引将重命名为CREATE BITMAPEXTENT INDEX语句指定的名称。

有关自动删除现有位图范围索引的DDL操作,请参阅ALTER TABLE

IDENTITY标识字段

SQL自动为每个表创建一个RowID字段,其中包含一个系统生成的整数,作为唯一的记录id。 可选的IDENTITY关键字允许定义一个具有与RowID记id字段相同属性的命名字段。 IDENTITY字段作为一个单字段IDKEY索引,其值是系统生成的唯一整数。

定义标识字段可防止将主键定义为IDKEY

与任何系统生成的ID字段一样,IDENTITY字段具有以下特征:

  • 每个表只能将一个字段定义为IDENTITY字段。 试图为表定义多个IDENTITY字段会产生SQLCODE -308错误。
  • IDENTITY字段的数据类型必须是整数数据类型。 如果不指定数据类型,则将其数据类型自动定义为BIGINT。 可以指定任何整数数据类型,如integerSMALLINT; 建议使用BIGINT匹配RowID的数据类型。 接受任何指定的字段约束,如NOT NULLUNIQUE,但忽略。
  • 数据值由系统生成。 它们由唯一的非零正整数组成。
  • 默认情况下,IDENTITY字段数据值不能由用户指定。 默认情况下,INSERT语句没有也不能指定IDENTITY字段值。 尝试这样做会产生SQLCODE -111错误。 要确定是否可以指定IDENTITY字段值,调用$SYSTEM.SQL.Util.GetOption("IdentityInsert")方法; 默认值是0。要更改当前进程的此设置,请调用$SYSTEM.SQL.Util.SetOption()方法,如下所示:设置status=$SYSTEM.SQL.Util.SetOption(“IdentityInsert”,1,.oldval)。也可以在表定义中指定%CLASSPARAMETER ALLOWIDENTITYINSERT=1。指定ALLOWIDENTITYINSERT=1将覆盖使用SetOption(“IdentityInsert”)应用的任何设置。
  • 不能在UPDATE语句中修改标识字段数据值。尝试这样做会生成SQLCODE-107错误。
  • 系统会自动将标识字段上的主键投影到ODBCJDBC。如果CREATE TABLEALTER TABLE语句在标识字段或包括标识字段的一组列上定义了主键约束或唯一约束,则会忽略约束定义,并且不会创建相应的主键或唯一索引定义。
  • SELECT*语句确实返回表的标识字段。

INSERTUPDATEDELETE操作之后,可以使用LAST_IDENTITY函数返回最近修改的记录的标识字段的值。如果未定义标识字段,LAST_IDENTITY将返回最近修改的记录的RowID值。

以下嵌入式SQL程序创建一个具有标识字段的表,然后在表中插入一条记录,从而生成标识字段值:

/// d ##class(PHA.TEST.SQLCommand).CreateTable5()
ClassMethod CreateTable5()
{
	d $SYSTEM.Security.Login("_SYSTEM","SYS")
	&sql(
		CREATE TABLE Employee 
		(
			EmpNum INT NOT NULL,
			MyID   IDENTITY NOT NULL,
			Name   CHAR(30) NOT NULL,
			CONSTRAINT EMPLOYEEPK PRIMARY KEY (EmpNum)
		)
	)
	if SQLCODE '= 0 {
		w !,"创建表错误是: ",SQLCODE 
	} else {
		w !,"表已创建" 
	}
	&sql(
		INSERT INTO Employee 
		(
			EmpNum,Name
		) 
		SELECT ID,Name FROM SQLUser.Person WHERE Age >= '25'
	)
	if SQLCODE '= 0 {
		w !,"插入错误 error is: ",SQLCODE 
	} else {
		w !,"插入到表中的记录" 
	}
}

在本例中,主键(EmpNum)取自另一个表的ID字段。因此,EmpNum值是唯一的整数,但是(因为WHERE子句)它们的序列中可能包含空格。标识字段myid为每条记录分配一个用户可见的唯一顺序整数。

ROWERSION、SERIAL和AUTO_INCREMENT字段

SQL提供三种类型的系统生成的整数计数器字段。这三种数据类型都是扩展%Library.BigInt数据类型类的子类。

计数器类型计数器范围自动增加当用户提供的值为用户提供的值重复的值类型字段计数器复位分片表支持
AUTO_INCREMENTper-tableINSERTNULL or 0允许,不影响系统计数器允许每个表一个截断表Yes
SERIALper-serial计数器字段INSERTNULL or 0允许,可增加系统计数器Allowedmultiple per table截断表No
ROWVERSIONnamespace-wideINSERT and UPDATENot AllowedNot Allowedone per tablenot resetNo

下面的CREATE TABLE示例定义了这些字段:

CREATE TABLE MyStudents (
   Name VARCHAR(16) NOT NULL,
   DOB DATE,
   AutoInc BIGINT AUTO_INCREMENT,
   Counter SERIAL,
   RowVer ROWVERSION
   )

指定ROWVERSIONSERIAL关键字,而不是显式的数据类型。 因此以下是有效的字段定义语法:MySerial SERIALMyRowVer ROWVERSION

AUTO_INCREMENT关键字在显式数据类型之后指定。也可以使用%Library.AutoIncrement数据类型定义AUTO_INCREMENT字段。因此,以下是有效的字段定义语法:MyAutoInc %AutoIncrement, MyAutoInc %AutoIncrement AUTO_INCREMENT, or MyAutoInc INTEGER AUTO_INCREMENT

定义主键

定义主键是可选的。定义表格时,IRIS会自动创建一个生成的字段,即RowID Field(默认名称“ID”),它的作用是唯一的行标识符。在将每条记录添加到表中时, IRIS会为该记录的RowID字段分配一个唯一的不可修改的正整数。可以有选择地定义一个主键,该主键还用作唯一的行标识符。主键允许用户定义对应用程序有意义的行标识符。例如,主键可以是员工ID字段、社会保险号、患者记录ID字段或库存库存编号。

可以使用PRIMARY KEY子句将一个字段(或一组字段)显式定义为主记录标识符。定义主键有三种语法形式:

CREATE TABLE MyTable (Field1 INT PRIMARY KEY, Field2 INT)

CREATE TABLE MyTable (Field1 INT, Field2 INT, PRIMARY KEY (Field1))

CREATE TABLE MyTable (Field1 INT, Field2 INT, CONSTRAINT MyTablePK PRIMARY KEY (Field1))

第一种语法将一个字段定义为主键;通过将其指定为主键,根据定义,该字段是唯一的,并且不为空。第二和第三种语法可用于单个字段主键,但允许包含多个字段的主键。例如,主键(Field1、Field2)。如果指定单个字段,则根据定义,此字段是唯一的,并且不为空。如果指定逗号分隔的字段列表,则每个字段都被定义为非NULL,但只要字段值的组合是唯一值,就可以包含重复值。第三种语法允许显式地命名主键;前两种语法形式生成一个主键名称,如下所示:表名“PKEY”约束COUNT INTEGER

主键只接受唯一值,不接受NULL。(主键索引属性不会根据需要自动定义;但是,它实际上是必需的,因为不能为主键字段归档或保存空值。)。主键的排序规则类型在字段本身的定义中指定。

作为IDKEY的主键

默认情况下,主键不是唯一的IDKEY索引。在许多情况下,这样做更可取,因为它使能够更新主键值、设置主键的排序规则类型等。在某些情况下,最好将主键定义为IDKEY索引。请注意,这会对主键的未来使用施加IDKEY限制。

如果向现有字段添加主键约束,则该字段还可能自动定义为IDKEY索引。这取决于数据是否存在,以及通过以下方式之一建立的配置设置:

  • SQL SET OPTION PKEY_IS_IDKEY语句。
  • 系统范围的$SYSTEM.SQL.Util.SetOption()方法配置选项DDLPKeyNotIDKey。要确定当前设置,请调用$SYSTEM.SQL.CurrentSettings(),它显示的是通过DDL而不是ID键创建的主键;默认值为1。
  • 进入管理门户,选择系统管理,配置,SQL和对象设置,SQL。 查看通过DDL创建的表的将主键定义为ID键的当前设置。
    • 如果没有选中复选框(默认情况下),则Primary Key不会成为类定义中的IDKEY索引。 使用非IDKEY的主键访问记录的效率显著降低; 但是,这种类型的主键值是可以修改的。
    • 如果选中了复选框,当通过DDL指定Primary Key约束时,它将自动成为类定义中的IDKEY索引。 选择了这个选项后,数据访问更加有效,但是主键值一旦设置,就永远不能修改。

但是,如果在表中定义了IDENTITY字段,则不能将主键定义为IDKEY,即使使用了这些配置设置之一来建立将主键定义为IDKEY

IRIS支持作为IDKEY索引的一部分的属性(字段)成为SqlComputed。 例如,父引用字段。 属性必须是一个触发的计算字段。 定义为SqlComputedIDKEY属性仅在首次保存新ObjectINSERT操作时计算。 不支持UPDATE计算,因为作为IDKEY索引一部分的字段不能被更新。

没有主键

在大多数情况下,应该显式定义主键。但是,如果未指定主键, IRIS将根据以下规则尝试使用另一个字段作为ODBC/JDBC投影的主键:

  1. 如果单个字段上有IDKEY索引,则将IDKEY字段报告为SQLPrimaryKey字段。
  2. 否则,如果使用SqlRowIdPrivate=0(默认值)定义类,则将RowID字段报告为SQLPrimaryKey字段。
  3. 否则,如果有IDKEY索引,则将IDKEY字段报告为SQLPrimaryKey字段。
  4. 否则,不报告SQLPrimaryKey

多个主键

只能定义一个主键。默认情况下,当主键已经存在时, IRIS拒绝定义主键的尝试,或者拒绝定义同一主键两次,并发出SQLCODE-307错误。即使主键的第二个定义与第一个定义相同,也会发出SQLCODE-307错误。要确定当前配置,请调用$SYSTEM.SQL.CurrentSettings(),该函数显示当键存在时允许通过DDL创建主键设置。默认值为0(否),这是建议的配置设置。如果此选项设置为1(是), IRIS将删除现有的主键约束,并将最后指定的主键建立为表的主键。

在管理门户、系统管理、配置、SQL和对象设置中,通过选中忽略冗余DDL语句复选框,可以在系统范围内设置此选项(以及其他类似的创建、更改和删除选项)。

例如,下面的CREATE TABLE语句:

CREATE TABLE MyTable (f1 VARCHAR(16), 
CONSTRAINT MyTablePK PRIMARY KEY (f1))

创建主键(如果不存在)。随后的ALTER TABLE语句:

ALTER TABLE MyTable ADD CONSTRAINT MyTablePK PRIMARY KEY (f1)

生成SQLCODE-307错误。

0
0 277
文章 姚 鑫 · 九月 15, 2021 10m read

第十六章 SQL命令 CREATE TABLE(三)

字段数据约束

数据约束控制字段允许使用的值、字段的默认值以及数据值使用的排序规则类型。所有这些数据约束都是可选的。可以按任何顺序指定多个数据约束,并以空格分隔。

NULL和NOT NULL

NOT NULL数据约束关键字指定该字段不接受空值;换句话说,每条记录都必须为该字段指定一个值。NULL和空字符串('') IRIS中是不同的值。可以在接受字符串的字段中输入空字符串,即使该字段定义了NOT NULL限制也是如此。不能在数值字段中输入空字符串。

NULL数据约束关键字显式指定此字段可以接受空值;这是字段的默认定义。

UNIQUE

唯一数据约束指定此字段仅接受唯一值。因此,没有两条记录可以包含该字段的相同值。SQL空字符串('')被视为数据值,因此在应用了UNIQUE数据约束的情况下,任何两条记录都不能包含此字段的空字符串值。NULL不被视为数据值,因此唯一数据约束不适用于多个NULL。要限制字段使用NULL,请使用NOT NULL关键字约束。

  • 唯一数据约束要求指定字段的所有值都是唯一值。
  • UNIQUE fields constraint(使用CONSTRAINT关键字)要求一组指定字段的所有值在串联在一起时产生唯一值。不需要将各个字段限制为唯一值。

定义为分片表的表对UNIQUE数据约束的使用有额外的限制。 不包含shard键的字段或字段组上的唯一约束为插入和更新增加了显著的性能成本。 因此,当插入和更新性能是一个重要的考虑因素时,建议避免这种类型的唯一约束。

DEFAULT

默认数据约束指定 IRIS在INSERT操作期间自动为此字段提供的默认数据值(如果INSERT未为此字段提供数据值)。如果插入操作为字段数据值提供NULL,则采用NULL而不是默认数据值。因此,为同一字段同时指定DEFAULTNOT NULL数据约束是很常见的。

默认值可以作为文字值或关键字选项提供。作为文字默认值提供的字符串必须用单引号引起来。数字默认值不需要单引号。例如:

CREATE TABLE membertest
(MemberId INT NOT NULL,
Membership_status CHAR(13) DEFAULT 'M',
Membership_term INT DEFAULT 2)

创建表时不会验证默认值。定义后,默认值可以忽略数据类型、数据长度和数据约束限制。但是,当使用INSERT向表提供数据时,缺省值是受约束的;它不受数据类型和数据长度限制,而是受数据约束限制。例如,定义了Ordernum int Unique Default‘No Number’的字段可以采用默认值一次,忽略int数据类型限制,但不能第二次采用缺省值,因为这将违反唯一字段数据约束。

如果未指定默认值,则隐含的默认值为NULL。如果字段具有非空数据约束,则必须显式或默认地为该字段指定值。不要将SQL零长度字符串(空字符串)用作非空默认值。

DEFAULT Keywords

默认数据约束可以接受关键字选项来定义其值。支持以下选项:NULLUSERCURRENT_USERSESSION_USERSYSTEM_USERCURRENT_DATECURRENT_TIMECURRENT_TIMESTAMPSYSDATEOBJECTSCRIPT

USERCURRENT_USERSESSION_USER默认关键字将字段值设置为ObjectScript $USERNAME特殊变量。

CURRENT_DATECURRENT_TIMECURRENT_TIMESTAMPGETDATEGETUTCDATESYSDATE SQL函数也可以用作默认值。它们在各自的参考页中进行了描述。当用作默认值时,可以指定CURRENT_TIMETIMESTAMP函数,有没有精度值。如果未指定精度,则将使用SQL配置设置“GETDATE()CURRENT_TIMECURRENT_TIMESTAMP的默认时间精度”的精度,默认为0DEFAULT函数在准备/编译CREATE TABLE语句时(而不是在执行语句时)使用有效的时间精度设置。

可以将CURRENT_TIMESTAMP指定为数据类型为%Library.PosiTime%Library.TimeStamp;的字段的默认值。当前日期和时间以字段数据类型指定的格式存储。可以将CURRENT_TIMESTAMPGETDATEGETUTCDATESYSDATE指定为%Library.TimeStamp字段(数据类型TIMESTAMPDATETIME)的默认值。 IRIS将日期值转换为适合该数据类型的格式。

CREATE TABLE mytest
(
    TestId INT NOT NULL,
    CREATE_DATE DATE DEFAULT CURRENT_TIMESTAMP(2),
    WORK_START DATE DEFAULT SYSDATE
)

可以使用TO_DATE函数作为数据类型DATE的默认数据约束。可以使用TO_TIMESTAMP函数作为数据类型TIMESTAMP的默认数据约束。

OBJECTSCRIPT文字关键字短语使您能够通过提供包含ObjectScript代码的带引号的字符串来生成默认值,如下例所示:

CREATE TABLE mytest
(
    TestId INT NOT NULL,
    CREATE_DATE DATE DEFAULT OBJECTSCRIPT '+$HOROLOG' NOT NULL,
    LOGNUM NUMBER(12,0) DEFAULT OBJECTSCRIPT '$INCREMENT(^LogNumber)'
)

ON UPDATE

ON UPDATE子句使字段的计算值为%%UPDATE。这是定义字段的快捷语法,每当表中的行被更新时,该字段总是被计算。此功能最常见的用途是在表中定义一列,该列包含上次更新该行的时间戳值。

可用的更新规格选项有:

CURRENT_DATE | CURRENT_TIME[(precision)] | CURRENT_TIMESTAMP[(precision)] | GETDATE([prec]) | GETUTCDATE([prec]) | SYSDATE | 
USER | CURRENT_USER | SESSION_USER | SYSTEM_USER |
 NULL | <literal> | -<number> 

以下示例在插入行以及每次更新该行时,将行字段设置为当前时间戳值:

CREATE TABLE mytest
(
    Name VARCHAR(48),
    RowTS TIMESTAMP DEFAULT Current_Timestamp(6) ON UPDATE Current_Timestamp(6) 
)

在本例中,如果没有为RowTS字段指定显式值,则DEFAULT关键字将RowTS设置为插入时的当前时间戳。如果UPDATERowTS字段指定了显式值,则ON UPDATE关键字将验证但忽略指定值,并使用当前时间戳更新RowTS。如果指定的值未通过验证,则会生成SQLCODE-105错误。

下面的示例将HasBeenUpdateed字段设置为布尔值:

CREATE TABLE mytest
(Name VARCHAR(48),
 HasBeenUpdated TINYINT DEFAULT 0 ON UPDATE 1 )

下面的示例将whLastUpdateed字段设置为当前用户名:

CREATE TABLE mytest
(Name VARCHAR(48),
 WhoLastUpdated VARCHAR(48) DEFAULT CURRENT_USER ON UPDATE CURRENT_USER )

如果该字段还具有COMPUTECODE数据约束,则不能指定ON UPDATE子句。尝试这样做会在编译/准备时导致SQLCODE-1错误。

Collation Parameters

可选的排序规则参数指定对字段的值进行排序时要使用的字符串排序规则类型。 SQL支持十种类型的排序规则。如果未指定排序规则,则默认为%SQLUPPER排序规则,不区分大小写。

为便于编程,建议在COLLATION参数之前指定可选关键字COLLATE,但此关键字不是必需的。各种排序参数关键字的百分号(%)前缀是可选的。

%Exact排序规则遵循ANSI(或Unicode)字符排序规则序列。这提供区分大小写的字符串排序,并识别前导和尾随空格以及制表符。

%SQLUPPER归类将所有字母转换为大写以进行归类。

%SPACE%SQLUPPER排序规则会在数据后追加一个空格。这将强制对空值和数字值进行字符串排序。

%SQLSTRING%SQLUPPER%TRUNCATE排序规则提供了一个可选的maxlen参数,该参数必须用圆括号括起来。Maxlen是一个截断整数,它指定执行排序时要考虑的最大字符数。当创建包含大数据值的字段的索引时,此参数非常有用。

%PLUS%MINUS排序规则将NULL处理为0(0)值。

注意:shard键字段只能接受%EXACT%SQLSTRING%SQLUPPER排序,没有截断。

ObjectScript为数据排序规则转换提供了%SYSTEM.Util类的Colation()方法。

注意:要将命名空间默认排序规则从%SQLUPPER(不区分大小写)更改为另一种排序规则类型,如%SQLSTRING(区分大小写),请使用以下命令:

  WRITE $$SetEnvironment^%apiOBJ("collation","%Library.String","SQLSTRING")

发出此命令后,必须清除索引,重新编译所有类,然后重建索引。当其他用户正在访问表的数据时,不要重建索引。这样做可能会导致不准确的查询结果。

%DESCRIPTION

可以为字段提供描述文本。此选项遵循与为表格提供描述文本相同的约定。上面使用其他表元素对其进行了描述。

计算字段

可以定义一个或多个计算其值的字段,而不是用户提供的字段。计算字段值的事件取决于以下关键字选项:

  • COMPUTECODE:值在插入时计算并存储,值在更新时不变。
  • COMPUTECODE WITH COMPUTEONCHANGE:VALUEINSERT时计算并存储,在UPDATE时重新计算并存储。
  • COMPUTECODE WITH DEFAULT和COMPUTEONCHANGE:默认值在插入时存储,值在更新时计算和存储。
  • COMPUTECODE WITH COMPUTTECODE WITH COMPUTED或TRANSPENT:值不存储,而是在每次查询字段时生成。

COMPUTECODE

COMPUTECODE数据约束指定ObjectScript代码来计算此字段的默认数据值。ObjectScript代码在大括号内指定。在ObjectScript代码中,可以使用大括号分隔符指定SQL字段名称。ObjectScript代码可以由多行代码组成。它可以包含嵌入式SQL。允许在ObjectScript代码大括号分隔符之前或之后使用空格和回车。

COMPUTECODE指定SqlComputeCode字段名称及其值的计算。在COMPUTECODESqlComputeCode类属性中指定计算字段名称时,必须指定SQL字段名称,而不是相应的生成的表属性名称。

计算机代码提供的默认数据值必须处于逻辑(内部存储)模式。计算机代码中的嵌入式SQL被自动编译并以逻辑模式运行。

以下示例定义了Birthday COMPUTECODE字段。它使用ObjectScript代码从道布字段值计算其默认值:

CREATE TABLE MyStudents 
(
   Name VARCHAR(16) NOT NULL,
   DOB DATE,
   Birthday VARCHAR(12) COMPUTECODE {SET {Birthday}=$PIECE($ZDATE({DOB},9),",")},
   Grade INT
)

COMPUTECODE可以包含伪字段引用变量{%%CLASSNAME}{%%CLASSNAMEQ}{%%OPERATION}{%%TABLENAME}{%%ID}。这些伪字段在类编译时被转换为特定值。所有这些伪字段关键字都不区分大小写。

COMPUTECODE值是默认值;只有在未向该字段提供值的情况下才会返回该值。COMPUTECODE值不受数据类型限制。COMPUTECODE值受到唯一数据约束和其他数据约束限制的限制。如果同时指定DEFAULTCOMPUTECODE,则始终采用默认值。

COMPUTECODE可以选择接受COMPUTEONCHANGECOMPUTEONCHANGETEMPUTEONCHANGE关键字。支持以下关键字组合行为:

如果ObjectScript COMPUTECODE代码中存在错误,则在第一次执行代码之前,SQL不会检测到此错误。因此,如果在INSERT时首先计算值,则INSERT操作失败,出现SQLCODE-415错误;如果在更新时首先计算值,则UPDATE操作失败,出现SQLCODE-415错误;如果在查询时首先计算值,则SELECT操作失败并出现SQLCODE-350错误。

可以索引COMPUTECODE存储值。应用程序开发人员负责确保根据计算字段存储值的数据类型验证和标准化计算字段存储值(规范化形式的数字),特别是在为计算字段定义(或打算定义)索引的情况下。

COMPUTEONCHANGE

COMPUTECODE本身会导致在INSERT过程中计算字段值并将其存储在数据库中;该值在后续操作中保持不变。默认情况下,后续的更新或触发器代码操作不会更改计算值。指定COMPUTEONCHANGE关键字会导致后续的UPDATE或触发器代码操作重新计算并替换此存储值。

如果使用COMPUTEONCHANGE子句指定一个字段或以逗号分隔的字段列表,则对其中一个字段的值所做的任何更改都会导致 SQL重新计算COMPUTECODE字段值。

如果COMPUTEONCHANGE中指定的字段不是表规范的一部分,则会生成SQLCODE-31

在下面的示例中,生日是根据DOB(出生日期)值进行插入计算的。 更新DOB时重新计算生日:

CREATE TABLE SQLUser.MyStudents (
   Name VARCHAR(16) NOT NULL,
   DOB DATE,
   Birthday VARCHAR(40) COMPUTECODE {
        SET {Birthday}=$PIECE($ZDATE({DOB},9),",")
        _" changed: "_$ZTIMESTAMP }
        COMPUTEONCHANGE (DOB)
     )

COMPUTEONCHANGE用与字段定义相对应的类属性的%%UPDATE值定义SqlComputeOnChange关键字。 该属性值最初是作为INSERT操作的一部分计算的,并在UPDATE操作期间重新计算。

CALCULATED和TRANSIENT

指定COMPUTECODETEMPUTE关键字指定COMPUTECODE字段值不保存在数据库中;它作为访问它的每个查询操作的一部分进行计算。这会减小数据存储的大小,但可能会降低查询性能。因为这些关键字导致 IRIS不存储COMPUTECODE字段值,所以这些关键字和COMPUTEONCHANGE关键字是互斥的。以下是计算字段的示例:

CREATE TABLE MyStudents (
   Name VARCHAR(16) NOT NULL,
   DOB DATE,
   Days2Birthday INT COMPUTECODE{SET {Days2Birthday}=$ZD({DOB},14)-$ZD($H,14)} CALCULATED
   )

Computed为与字段定义对应的类属性定义计算的布尔关键字。瞬态定义与字段定义对应的类属性的瞬态布尔关键字。

计算和瞬态提供了几乎相同的行为,但有以下差异。 TRANSIENT意味着IRIS不存储该属性。 计算意味着IRIS不为属性分配任何实例内存。 因此,当指定calculate时,将隐式设置TRANSIENT

瞬态属性不能被索引。 除非属性也是SQLComputed,否则无法为计算属性建立索引。

0
0 309
文章 姚 鑫 · 九月 13, 2021 9m read

第十五章 SQL命令 CREATE TABLE(二)

全局临时表

指定GLOBAL TEMPORARY关键字将表定义为全局临时表。表定义是全局的(对所有进程都可用);表数据是临时的(在进程期间持续存在)。相应的类定义包含一个附加的类参数SQLTABLETYPE=“GLOBAL TEMPORARY”。与标准的 IRIS表一样,ClassType=Persistent,并且类包含Final关键字,表示它不能有子类。

无论哪个进程创建临时表,临时表的所有者都会自动设置为_PUBLIC。这意味着所有用户都可以访问缓存的临时表定义。例如,如果存储过程创建了一个临时表,则允许调用该存储过程的任何用户都可以访问该表定义。这仅适用于临时表定义;临时表数据特定于调用,因此只能由当前用户进程访问。

全局临时表的表定义与基表相同。全局临时表必须具有唯一的名称;尝试为其提供与现有基表相同的名称会导致SQLCODE-201错误。该表将一直存在,直到显式删除(使用DROP TABLE)。可以使用ALTER TABLE更改表定义。

下面的嵌入式SQL示例创建一个全局临时表:

ClassMethod CreateTable3()
{
	d $SYSTEM.Security.Login("_SYSTEM","SYS")
	n SQLCODE,%msg
	&sql(
		CREATE GLOBAL TEMPORARY TABLE TempEmp 
		(
			EMPNUM     INT NOT NULL,
			NAMELAST   CHAR(30) NOT NULL,
			NAMEFIRST  CHAR(30) NOT NULL,
			CONSTRAINT EMPLOYEEPK PRIMARY KEY (EMPNUM)
		)
	)
	if SQLCODE=0 { 
		w !,"表创建"
	} else { 
		w !,"SQLCODE=",SQLCODE,": ",%msg 
	}
}

/// 
Class User.TempEmp Extends %Persistent [ ClassType = persistent, DdlAllowed, Final, Owner = {_PUBLIC}, ProcedureBlock, SqlRowIdPrivate, SqlTableName = TempEmp ]
{

Property EMPNUM As %Library.Integer(MAXVAL = 2147483647, MINVAL = -2147483648) [ Required, SqlColumnNumber = 2 ];

Property NAMELAST As %Library.String(MAXLEN = 30) [ Required, SqlColumnNumber = 3 ];

Property NAMEFIRST As %Library.String(MAXLEN = 30) [ Required, SqlColumnNumber = 4 ];

Parameter SQLTABLETYPE = "GLOBAL TEMPORARY";

/// DDL Primary Key Specification
Index EMPLOYEEPK On EMPNUM [ PrimaryKey, Type = index, Unique ];
}

%DESCRIPTION, %FILE, %EXTENTSIZE / %NUMROWS, %ROUTINE

这些可选关键字短语可以在逗号分隔的表元素列表中的任何位置指定。

SQL提供了一个%DESCRIPTION关键字,可以使用该关键字为记录表或字段提供描述。%DESCRIPTION后面跟着用单引号括起来的文本字符串。这个文本可以是任意长度的,可以包含任何字符,包括空格。 (描述中的单引号字符由两个单引号表示。 例如:“Joe' s Table”。) 一个表可以有%DESCRIPTION。 表的每个字段都可以有自己的%DESCRIPTION,在数据类型之后指定。 如果为一个表指定多个表宽%DESCRIPTION, IRIS将发出SQLCODE -82错误。 如果您为一个字段指定了多个%DESCRIPTION,系统只保留最后一个指定的%DESCRIPTION。 不能使用ALTER TABLE更改现有的描述。

在相应的持久化类定义中,在对应的类(表)或属性(字段)语法之前的一行中出现了以三个斜杠开头的描述。 例如:/// Joe's Table。 在对应持久化类的类引用中,表描述出现在类名和SQL表名之后; 字段说明出现在相应的属性语法之后。

可以使用INFORMATION.SCHEMA.TABLESINFORMATION.SCHEMA.COLUMNSDESCRIPTION属性显示%DESCRIPTION文本。 例如:

SELECT COLUMN_NAME,DESCRIPTION FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='MyTable'

image

SQL提供了一个%FILE关键字,该关键字用于提供记录表的文件名。 %FILE后面跟着用单引号括起来的文本字符串。 一个表定义只能有一个%FILE关键字; 指定多个会产生SQLCODE -83错误。

SQL提供了可选的%EXTENTSIZE%NUMROWS关键字,它们用于存储一个整数,记录该表中预期的行数。 这两个关键词是同义词; %EXTENTSIZE是首选术语。 当创建一个表来保存已知的数据行数时,特别是当初始的行数不太可能随后更改时(比如包含州和省的表),设置%EXTENTSIZE可以节省空间并提高性能。 如果未指定,则标准表的初始分配值为100,000,临时表的初始分配值为50。 一个表定义只能有一个%EXTENTSIZE%NUMROWS关键字; 指定多个会导致SQLCODE -84错误。 一旦用数据填充了表,就可以通过运行Tune table将这个%EXTENTSIZE值更改为实际的行数。

SQL提供了一个%ROUTINE关键字,它允许为为这个基表生成的例程指定例程名称前缀。 %ROUTINE后面跟着用单引号括起来的文本字符串。 例如,%ROUTINE 'myname'在名为myname1myname2等的例程中生成代码。 不能从%ROUTINE调用用户定义的(“外部”)函数。 一个表定义只能有一个%ROUTINE关键字; 指定多个会导致SQLCODE -85错误。 在Studio中,例程名称前缀显示为SqlRoutinePrefix值。

仅支持兼容性选项

SQL仅接受以下CREATE TABLE选项用于解析,以帮助将现有SQL代码转换为 SQL。 这些选项不提供任何实际的功能。

{ON | IN} dbspace-name

LOCK MODE [ROW | PAGE]

[CLUSTERED | NONCLUSTERED]

WITH FILLFACTOR = literal

MATCH [FULL | PARTIAL]

CHARACTER SET identifier

COLLATE identifier  /* But note use of COLLATE keyword, described below */

NOT FOR REPLICATION

字段定义

在表名之后,一组圆括号包含表中所有字段(列)的定义。字段定义用逗号分隔。按照惯例,每个字段定义通常在单独的行上显示,并使用缩进;建议这样做,但不是必需的。定义最后一个字段后,请记住为字段定义提供右括号。

字段定义的各个部分由空格分隔。首先列出字段名称,然后列出其数据特征。字段的数据特征按以下顺序显示:数据类型、(可选)数据大小,然后是(可选)数据约束。然后,可以附加一个可选的字段%DESCRIPTION来记录该字段。

字段定义可以引用定义多个字段(属性)的现有嵌入式串行对象,而不是定义字段。字段名后面是串行对象的包名和类名。例如,Office Sample.Address。不要指定数据类型或数据约束;可以指定%DESCRIPTION。不能使用CREATE TABLE创建嵌入式串行对象。

注:我们建议避免创建列超过400列的表。重新设计数据库,以便:这些列变成行;列在几个相关的表中划分;或者数据以字符流或位流的形式存储在较少的列中。

字段名称

字段名遵循标识符约定,具有与表名相同的命名限制。应避免以%字符开头的字段名(允许以%z%Z开头的字段名)。字段名称不应超过128个字符。默认情况下,字段名是简单标识符。它们不区分大小写。尝试创建与同一表中的另一个字段仅在字母大小写上不同的字段名会生成SQLCODE-306错误。

IRIS使用该字段名生成相应的类属性名。特性名称仅包含字母数字字符(字母和数字),最大长度为96个字符。要生成此属性名, IRIS首先从字段名中删除标点符号,然后生成96个(或更少)字符的唯一标识符。当创建唯一的属性名需要时, IRIS会用整数(从0开始)替换字段名的最后一个字符。

下面的示例显示 IRIS如何处理仅标点符号不同的字段名称。这些字段对应的类属性分别命名为PatNumPatNu0PatNu1

CREATE TABLE MyPatients (
     _PatNum VARCHAR(16),
     %Pat@Num INTEGER,
     Pat_Num VARCHAR(30),
     CONSTRAINT Patient_PK PRIMARY KEY (_PatNum))

image

/// 
Class User.MyPatients Extends %Persistent [ ClassType = persistent, DdlAllowed, Final, Owner = {yx}, ProcedureBlock, SqlRowIdPrivate, SqlTableName = MyPatients ]
{

Property PatNum As %Library.String(MAXLEN = 16) [ SqlColumnNumber = 2, SqlFieldName = _PatNum ];

Property PatNu0 As %Library.Integer(MAXVAL = 2147483647, MINVAL = -2147483648) [ SqlColumnNumber = 3, SqlFieldName = %Pat@Num ];

Property PatNu1 As %Library.String(MAXLEN = 30) [ SqlColumnNumber = 4, SqlFieldName = Pat_Num ];

/// Bitmap Extent Index auto-generated by DDL CREATE TABLE statement.  Do not edit the SqlName of this index.
Index DDLBEIndex [ Extent, SqlName = "%%DDLBEIndex", Type = bitmap ];

/// DDL Primary Key Specification
Index PatientPK On PatNum [ PrimaryKey, SqlName = Patient_PK, Type = index, Unique ];

}

CREATE TABLE中指定的字段名称在class属性中显示为SqlFieldName值。

在动态选择操作期间, IRIS可以生成属性名称别名,以便于常见的字母大小写变体。例如,在给定字段名Home_Street的情况下, IRIS可能会为特性名称指定别名HOME_STREATHOME_STREATHomeStreet。如果别名与另一字段名的名称冲突,或与分配给另一字段名的别名冲突, IRIS不会分配别名。

数据类型

每个字段定义都必须指定一个数据类型,该数据类型映射到字段定义所基于的数据类型类。指定的数据类型将字段允许的数据值限制为适合该数据类型的值。 SQL支持大多数标准SQL数据类型。本参考的数据类型部分提供了支持的数据类型的完整列表。

CREATE TABLE允许使用 SQL数据类型(例如,VARCHAR(24)CHARACTER VARING(24))或通过直接指定它映射到的数据类型类(例如,%Library.String(MAXLEN=24)%String(MAXLEN=24))来指定数据类型。(对于所有数据类型类,语法形式%Library.Datatype%Datatype 是同义词。)

通常, SQL(如CREATE TABLE命令)指定数据类型。可以直接指定数据类型类来定义其他数据定义参数,例如允许的数据值的枚举列表、允许的数据值的模式匹配、最大和最小数值以及超过最大长度(MAXLEN)的数据值的自动截断。

注:数据类型类参数默认值可能不同于 SQL数据类型默认值。例如,VARCHAR()CHARACTER VARING()缺省为MAXLEN=1;相应的数据类型类%Library.String默认为MAXLEN=50

IRIS通过提供SQL.SystemDataTypes映射表和SQL.UserDataTypes映射表,将这些标准SQL数据类型映射到 IRIS数据类型。用户可以添加SQL.UserDataTypes以包括其他用户定义的数据类型。

要查看和修改当前数据类型映射,请转到管理门户,选择系统管理、配置、SQL和对象设置、系统DDL映射。要创建其他数据类型映射,请转到管理门户,选择系统管理、配置、SQL和对象设置、用户DDL映射。

如果在SQL中指定的数据类型不存在相应的 IRIS数据类型,则SQL数据类型名称将用作相应类属性的数据类型。必须在DDL运行时(SQLExecute)之前创建此用户定义的IRIS数据类型。

还可以覆盖单个参数值的数据类型映射。例如,假设不希望VARCHAR(100)映射到提供的标准映射%string(MAXLEN=100)。可以通过将DDL数据类型‘VARCHAR(100)’添加到表中,然后指定其相应 IRIS类型来覆盖它。例如:

VARCHAR(100) maps to MyString100(MAXLEN=100)

注:定义分割表的CREATE TABLE不能包含ROWVERSION数据类型或SERIAL(%Library.Counter)数据类型的字段。

数据大小

在数据类型之后,可以在括号中表示允许的数据大小。允许使用数据类型名称和数据大小括号之间的空格,但不是必需的。

对于字符串,数据大小表示最大字符数。例如:

ProductName VARCHAR (64)

对于允许使用小数的数字,这表示为一对整数(p,s)。第一个整数(P)是数据类型精度,但它与数值精度(数字中的位数)不同。这是因为底层IRIS数据类型类没有精度,而是使用此数字来计算MAXVALMINVAL;第二个整数是小数位数,它指定最大小数位数。例如:

UnitPrice NUMERIC(6,2)  /* maximum value 9999.99 */

要确定字段的最大允许值和最小允许值,请使用以下ObjectScript函数:

  WRITE $$maxval^%apiSQL(6,2),!
  WRITE $$minval^%apiSQL(6,2)

请注意,因为p不是数字计数,所以它可以小于刻度的值:

ClassMethod CreateTable4()
{
	for i = 0 : 1 : 6 {
		w "Max for (",i,",2)=",$$maxval^%apiSQL(i,2),!
	}
}

DHC-APP>d ##class(PHA.TEST.SQLCommand).CreateTable4()
Max for (0,2)=.99
Max for (1,2)=.99
Max for (2,2)=.99
Max for (3,2)=9.99
Max for (4,2)=99.99
Max for (5,2)=999.99
Max for (6,2)=9999.99
0
0 276
文章 姚 鑫 · 九月 12, 2021 9m read

第十四章 SQL命令 CREATE TABLE(一)

创建表

大纲

CREATE [GLOBAL TEMPORARY] TABLE 
table (table-element-commalist) [shard-key] [WITH table-option-commalist]

table-element ::= 
     [%DESCRIPTION string] 
     [%FILE string] 
     [{%EXTENTSIZE | %NUMROWS} integer] 
     [%PUBLICROWID] 
     [%ROUTINE string] 
   
    { fieldname datatype [AUTO_INCREMENT] | IDENTITY | SERIAL | ROWVERSION 
            [ %DESCRIPTION string ]
           {
             [ [COLLATE] sqlcollation ]
             [ UNIQUE ]
             [ NULL | NOT NULL ]
             [ PRIMARY KEY ]
             [ REFERENCES table  (reffield-commalist) ]
             [ DEFAULT [(]default-spec[)] ]
             [ ON UPDATE update-spec ]
             [ COMPUTECODE { ObjectScript-code } 
                   [ COMPUTEONCHANGE (field-commalist) |
                     CALCULATED | TRANSIENT ] ]
             } , }

     [{ [CONSTRAINT uname] 
          UNIQUE (field-commalist) }]

    [ [CONSTRAINT pkname] 
          PRIMARY KEY (field-commalist) ] 

     [{ [CONSTRAINT fkname] 
          FOREIGN KEY (field-commalist) REFERENCES table 
              [(reffield-commalist)]  
             [ON DELETE ref-action] [ON UPDATE ref-action]  [NOCHECK] }]


[ SHARD [ KEY (field-commalist)  [ COSHARD [ WITH ] [(]table[)] ] ] ]

[WITH table-option ::=
         { %CLASSPARAMETER paramname [=] value } , }
         [ STORAGETYPE [=] {ROW | COLUMN} ]
     ]

sqlcollation ::=
     { %EXACT | %MINUS | %MVR | %PLUS | %SPACE |   
        %SQLSTRING [(maxlen)] | %SQLUPPER [(maxlen)] |
        %TRUNCATE[(maxlen)]  }

此摘要不包括仅为兼容性而分析的关键字,但不执行任何操作。下面单独一节列出了这些受支持的no-op关键字。

参数

  • GLOBAL TEMPORARY - 可选-此关键字子句将表创建为临时表。
  • table - 要创建的表的名称,指定为有效标识符。表名可以是限定的(schema.table),也可以是非限定的(Table)。未限定的表名采用默认模式名。
  • table-element - 一个或多个字段定义或关键字短语的逗号分隔列表。此逗号分隔的列表用圆括号括起来。每个字段定义(至少)由一个字段名(指定为有效标识符)和一个数据类型组成。关键字短语可以只由关键字(%PUBLICROWID)、关键字和文字组成。
  • WITH table-option - 可选-一个或多个表选项(如一个或多个%CLASSPARAMETER子句或STORAGETYPE子句)的逗号分隔列表。
  • COLLATE sqlcollation - 可选-指定以下SQL排序规则类型之一:%Exact%Minus%Plus%SPACE%SQLSTRING%SQLUPPER%TRUNCATE%MVR。默认值为名称空间默认排序规则(除非更改,否则为%SQLUPPER)。%SQLSTRING%SQLUPPER%TRUNCATE可以使用可选的最大长度截断参数(括在圆括号中的整数)指定。这些排序参数关键字的百分号(%)前缀是可选的。COLLATE关键字是可选的。
  • uname,pkname,fkname - 可选-约束的名称,指定为有效标识符。如果指定为分隔标识符,则约束名称可以包".", "^", ",", "->" 字符。此可选约束名称在ALTER TABLE中用于标识已定义的约束。
  • field-commalist - 字段名或逗号分隔的任意顺序的字段名列表。用于定义唯一、主键或外键约束。为约束指定的所有字段名也必须在字段定义中定义。必须用括号括起来。
  • reffield-commalist - 可选-在FOREIGN KEY约束中指定的引用表中定义的字段名或现有字段名列表(以逗号分隔)。如果指定,必须用圆括号括起来。如果省略,则采用默认值,如定义外键中所述。
  • ref-action - 可选-外键定义可以指定两个ref-action子句:ON DELETE REF-ACTIONON UPDATE REF-ACTION。支持的引用操作选项有no actionset defaultset nullCASCADE

描述

CREATE TABLE命令创建指定结构的表定义。 IRIS自动创建与此表定义对应的持久化类,其属性与字段定义对应。CREATE TABLE将相应的类定义为DdlAllowed。它不在相应的类定义中指定显式StorageStrategy;它使用默认存储%Storage.Persistent。默认情况下,CREATE TABLE在相应的类定义中指定最终的CLASS关键字,指示它不能有子类。(默认值为1;可以使用$SYSTEM.SQL.Util.SetOption()方法设置status=$SYSTEM.SQL.Util.SetOption("DDLFinal",0,.oldval)在系统范围内更改此默认值;要确定当前设置,请调用$SYSTEM.SQL.CurrentSettings()方法)

注:CREATE TABLE通过指定字段定义和其他元素创建表。使用CREATE TABLE AS SELECT命令通过从现有表复制字段定义和数据来定义表。

语法概述

CREATE TABLE命令具有以下总体语法:

  • 表名,限定名(schema.tablename)或非限定名(Tablename)。
  • 一对圆括号,用逗号分隔的表格元素列表括起来。这些表元素包括字段定义、约束、关键字子句以及主键和外键定义。元素可以按任何顺序指定。元素必须用逗号分隔。
  • 可选的分片键定义,可以在右括号后指定。
  • 可选的WITH子句,可以在右括号之后和分片键定义(如果存在)之后指定。WITH子句可以包含逗号分隔的%CLASSPARAMETER子句列表 和/或 STORAGETYPE子句。

较早的CREATE TABLE代码可能会将SHARD键定义和%CLASSPARAMETER子句作为逗号分隔的元素包含在表元素的圆括号内。首选语法是在结束表元素括号之后指定这些子句。指定这些子句的重复项会生成SQLCODE-327错误。

SQL安全和权限

CREATE TABLE命令是特权操作。用户必须具有%CREATE_TABLE管理权限才能执行CREATE TABLE。否则将导致 SQLCODE –99 %msg User 'name' does not have %CREATE_TABLE privileges。如果拥有适当的授予权限,则可以使用GRANT命令将%CREATE_TABLE权限分配给用户或角色。管理权限是特定于命名空间的。

默认情况下,将强制执行CREATE TABLE安全权限。此权限要求可使用$SYSTEM.SQL.Util.SetOption()方法在系统范围内配置 status=$SYSTEM.SQL.Util.SetOption("SQLSecurity",0,.oldval)。要确定当前设置,请调用$SYSTEM.SQL.CurrentSettings()方法,该方法显示an SQL security enabled setting.

默认值为1(已启用)。启用SQL安全性后,用户只能对其已被授予权限的表或视图执行操作。这是此选项的推荐设置。

如果此方法设置为0,则对更改此设置后启动的任何新进程禁用SQL安全性。这意味着基于特权的表/视图安全性被抑制。可以在不指定用户的情况下创建表。在本例中,动态SQL将“_SYSTEM”指定为用户,嵌入式SQL将""(空字符串)指定为用户。任何用户都可以对表或视图执行操作,即使该用户没有权限执行操作。

嵌入式SQL不使用SQL权限。在嵌入式SQL中,可以使用$SYSTEM.Security.Login()方法以具有适当权限的用户身份登录。必须具有%Service_Login:Use权限才能调用$SYSTEM.Security.Login()方法。

下面的嵌入式SQL示例创建Employee表:

ClassMethod CreateTable()
{
	d $SYSTEM.Security.Login("_SYSTEM","SYS")
	n SQLCODE,%msg
	&sql(
		CREATE TABLE 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 { 
		w !,"表创建"
	} else { 
		w !,"SQLCODE=",SQLCODE,": ",%msg 
	}
}

image

这个名为Employee的表有许多已定义的字段。EMPNUM字段(包含员工的公司ID号)是一个不能为空的整数值;此外,它被声明为表的主键。员工的姓和名都有一个字段,这两个字段都是最大长度为30的字符串,不能为空。此外,还有员工的开始日期、累计假期时间和累计病假时间字段(使用TIMESTAMPINT数据类型)。

使用下面的程序删除上一个示例中创建的表:

ClassMethod CreateTable1()
{
	d $SYSTEM.Security.Login("_SYSTEM","SYS")
	n SQLCODE,%msg
	&sql(
		DROP TABLE Employee
	)
	if SQLCODE=0 { 
		w !,"表被删除"
	} else { 
		w !,"SQLCODE=",SQLCODE,": ",%msg 
	}
}

表名

表名可以是限定的,也可以是非限定的。

非限定表名具有以下语法:tablename;它省略架构(和句点(.)。字符)。未限定的表名采用默认模式名。系统范围内的初始默认架构名称是SQLUser,它对应于默认的类包名称User。架构搜索路径值将被忽略。

可以配置系统范围的默认架构名称。

要确定当前系统范围内的默认架构名称,请使用$SYSTEM.SQL.Schema.Default()方法。

限定表名具有以下语法:schema.tablename。它可以指定现有的架构名称,也可以指定新的架构名称。指定现有架构名称会将该表放入该架构中。指定新的模式名称将创建该模式(以及关联的类包),并将表放入该模式中。

表名和模式名遵循SQL标识符命名约定,受使用非字母数字字符、唯一性和最大长度的附加约束。以%字符开头的名称保留供系统使用。默认情况下,模式名和表名是简单标识符,不区分大小写。

IRIS使用表名生成相应的类名。 IRIS使用架构名称来生成相应的类包名称。类名仅包含字母数字字符(字母和数字),并且在前96个字符内必须是唯一的。要生成类名, IRIS首先从表名中剔除符号(非字母数字)字符,然后生成唯一的类名,从而施加唯一性和最大长度限制。要生成包名,它然后对架构名中的符号(非字母数字)字符进行剥离或执行特殊处理。然后, IRIS生成唯一的包名,施加唯一性和最大长度限制。

可以对架构和表使用相同的名称。同一架构中的表和视图不能使用相同的名称。

架构名称不区分大小写;相应的类包名称区分大小写。如果指定的架构名称仅与现有类包名的大小写不同,并且包定义为空(不包含类定义)。 IRIS通过更改类包名称的大小写来协调这两个名称。

IRIS支持表名和字段名的16位(宽)字符。对于大多数区域设置,可以使用重音字母作为表名,并且重音符号包含在生成的类名中。以下示例对SQL表名执行验证测试:

ClassMethod CreateTable2()
{
	s tname = "MyTestTableName"
	s x = $SYSTEM.SQL.IsValidRegularIdentifier(tname)
	if x = 0 {
		if $length(tname) > 200 {
			w "表名太长" 
			q
		} elseif $SYSTEM.SQL.IsReservedWord(tname) {
			w "表名是保留字" 
			q
		} else {
			w "表名包含无效字符",!
			s nls = ##class(%SYS.NLS.Locale).%New()
			if nls.Language [ "Japanese" {
				w "日语区域设置不能使用重音字母"
				q 
			}
			q 
		}
	} else { 
		w tname," 是有效的表名称"
	}
}

注意:日语区域设置不支持标识符中的重音字母字符。日语标识符可以包含(除日语字符外)拉丁字母字符A-Za-z(65-9097-122)、下划线字符(95)和希腊大写字母字符(913-929931-937)。Nls.language测试使用[(CONTAINS运算符)而不是=,因为不同的操作系统平台有不同的日语区域设置。

表存在

要确定当前命名空间中是否已存在表,请使用$SYSTEM.SQL.Schema.TableExists("schema.tname")

默认情况下,当创建与现有表同名的表时, IRIS拒绝CREATE TABLE尝试并发出SQLCODE-201错误。要确定当前系统范围的配置设置,请调用$SYSTEM.SQL.CurrentSettings(),它将显示Allow DDL CREATE TABLE or CREATE VIEW for existing table or view setting。默认值为0;这是此选项的推荐设置。如果此选项设置为1, IRIS将删除与该表关联的类定义,然后重新创建它。这与执行DROP TABLE、删除现有表,然后执行CREATE TABLE大致相同。在这种情况下,强烈建议$SYSTEM.SQL.CurrentSettings()DDL DROP TABLE是否删除表的数据?值设置为1(默认值)。

在管理门户、系统管理、配置、SQL和对象设置中,通过选中忽略冗余DDL语句复选框,可以在系统范围内设置此选项(以及其他类似的创建、更改和删除选项)。

0
0 93
文章 姚 鑫 · 九月 11, 2021 3m read

第十三章 SQL命令 CREATE ROLE

创建角色

大纲

CREATE ROLE role-name

参数

  • role-name - 要创建的角色的名称,它是一个标识符。角色名称不区分大小写。

描述

CREATE ROLE命令可创建角色。角色是可以分配给多个用户的一组命名权限。一个角色可以分配给多个用户,一个用户可以分配多个角色。角色在系统范围内可用,它不限于特定的命名空间。

角色名可以是最多64个字符的任何有效标识符。角色名称必须遵循标识符命名约定。角色名称可以包含Unicode字符。角色名称不区分大小写。如果选中了支持分隔标识符配置选项(默认设置),则角色名可以是用引号括起来的分隔标识符。如果是分隔标识符,则角色名可以是SQL保留字。它可以包含句点(.)、插入符号(^)和两个字符的箭头序列(->)。它不能包含逗号()或冒号()字符。它可以以除星号(*)以外的任何有效字符开头。

最初创建角色时,角色只是一个名称;它没有权限。要向角色添加权限,请使用GRANT命令。还可以使用GRANT命令为角色分配一个或多个角色。这允许创建角色层次结构。

如果调用CREATE ROLE来创建已经存在的角色,SQL会发出SQLCODE-118错误。可以通过调用$SYSTEM.SQL.Security.RoleExists()方法确定角色是否已存在:

  WRITE $SYSTEM.SQL.Security.RoleExists("%All"),!
  WRITE $SYSTEM.SQL.Security.RoleExists("Madmen")

如果指定的角色存在,则此方法返回1,如果该角色不存在,则返回0。角色名称不区分大小写。

要删除角色,请使用DROP ROLE命令。

权限

CREATE ROLE命令是特权操作。在嵌入式SQL中使用CREATE ROLE之前,需要以具有%Admin_Secure:USE权限的用户身份登录。否则将导致SQLCODE-99错误(特权冲突)。使用$SYSTEM.Security.Login()方法分配具有适当权限的用户:

   DO $SYSTEM.Security.Login(username,password)
   &sql(      )

必须具有%Service_Login:Use权限才能调用$SYSTEM.Security.Login()方法。

示例

以下示例尝试创建名为BkUser的角色。第一个示例中的用户“fred”没有创建角色权限。第二个示例中的用户“_system”确实拥有CREATE ROLE权限。

ClassMethod CreateRole()
{
	d $SYSTEM.Security.Login("yao","YaoPassword")
	&sql(
		CREATE ROLE BkUser
	)
	if SQLCODE = -99 {
		w !,"没有创建角色的权限" 
	} elseif SQLCODE = -118 {
		w !,"角色已经存在" 
	} else {
		w !,"创建了一个角色。错误代码是: ",SQLCODE 
	}
}

ClassMethod CreateRole1()
{
	d $SYSTEM.Security.Login("_SYSTEM","SYS")
Main
	&sql(
		CREATE ROLE BkUser
	)
	if SQLCODE = -99 {
		w !,"没有创建角色的权限" 
	} elseif SQLCODE = -118 {
		w !,"角色已经存在" 
	} else {
		w !,"创建了一个角色。错误代码是: ",SQLCODE 
	}
Cleanup
	s toggle = $RANDOM(2)
	if toggle = 0 { 
		&sql(
			DROP ROLE BkUser
		)
		w !,"删除角色错误代码: ",SQLCODE
	} else { 
		w !,"未删除"
		q 
	}
}
0
0 120
文章 姚 鑫 · 九月 10, 2021 10m read

第十二章 SQL命令 CREATE QUERY

创建Query

大纲

CREATE QUERY queryname(parameter_list) [characteristics] 
   [ LANGUAGE SQL ]
   BEGIN
code_body ;
   END

CREATE QUERY queryname(parameter_list) [characteristics] 
    LANGUAGE OBJECTSCRIPT
   { code_body }

参数

  • queryname - 要在存储过程类中创建的查询的名称。queryname必须是有效的标识符。过程名可以是限定的(schema.procname),也可以是非限定的(procname)。非限定过程名接受默认模式名。即使没有指定参数,queryname也必须后跟括号。
  • parameter_list - 可选-传递给查询的参数列表。参数列表用圆括号括起来,列表中的参数用逗号分隔。即使没有指定参数,括号也是必须的。
  • characteristics - 可选-指定查询特征的一个或多个关键字。允许的关键字有结果、容器IDFORFINALPROCEDURESELECTMODE。多个特征由空白(空格或换行符)分隔。特性可以以任何顺序指定。
  • LANGUAGE OBJECTSCRIPTLANGUAGE SQL - 可选—指定用于code_body的编程语言的关键字子句。指定语言对象脚本或语言SQL。如果省略了LANGUAGE子句,则默认为SQL
  • code_body - 查询的程序代码。SQL程序代码以BEGIN关键字开头,以END关键字结尾。查询的code_body只包含一个完整的SQL语句(一个SELECT语句)。该SELECT语句以分号(;)结束。ObjectScript程序代码用花括号括起来。ObjectScript代码行必须缩进。

描述

CREATE QUERY语句在类中创建一个查询。 默认情况下,名为MySelect的查询将被存储为User.queryMySelectSQLUser.queryMySelect

CREATE QUERY创建的查询可能作为存储过程公开,也可能不作为存储过程公开。 要创建公开为存储过程的查询,必须指定procedure关键字作为其特征之一。 还可以使用CREATE PROCEDURE语句创建作为存储过程公开的查询。

为了创建查询,必须拥有%CREATE_QUERY管理权限,如GRANT命令所指定的。如果试图为已定义所有者的现有类创建查询,则必须以该类的所有者身份登录。否则,操作将失败,并出现SQLCODE -99错误。

如果类定义是已部署的类,则不能在类中创建查询。此操作失败,出现SQLCODE -400错误,出现%msgUnable to execute DDL that modifies a deployed class: 'classname'

参数

queryname

要创建为存储过程的查询的名称。此名称可以是非限定名称(StoreName)并采用默认架构名称,也可以通过指定架构名称(Patient.StoreName)进行限定。可以使用$SYSTEM.SQL.Schema.Default()方法来确定当前系统范围内的默认架构名称。系统范围内的初始默认模式名是SQLUser,它对应于类包名User

注意,FOR特征(将在下面描述)覆盖queryname中指定的类名。 如果已经存在具有此名称的方法,则操作将失败,并出现SQLCODE -361错误。

生成的类的名称是对应于架构名称的包名,后跟一个点,后跟“query”,后跟指定的queryname。例如,如果非限定查询名RandomLetter采用初始默认模式SQLUser,则得到的类名将是:User.queryRandomLetter

SQL不允许指定只以字母大小写不同的查询名。 指定一个与现有查询名称仅在字母大小写上不同的查询名称将导致SQLCODE -400错误。

如果指定的queryname已经存在于当前命名空间中,系统将生成SQLCODE -361错误。

parameter-list

用于将值传递给查询的参数的参数声明列表。 形参列表用圆括号括起来,列表中的形参声明用逗号分隔。 括号是必须的,即使没有指定参数。

列表中的每个参数声明由(按顺序)组成:

  • 一个可选关键字,指定参数模式是IN(输入值)、OUT(输出值)还是INOUT(修改值)。 如果省略,默认参数模式为IN
  • 参数名称。 参数名称区分大小写。
  • 参数的数据类型。
  • 可选:默认值。可以指定DEFAULT关键字后跟一个默认值;DEFAULT关键字是可选的。如果没有指定默认值,则假定默认值为NULL

下面的示例创建了一个公开为存储过程的查询,该存储过程具有两个输入参数,这两个参数都具有默认值。 topnum输入参数指定可选的DEFAULT关键字; minage输入参数忽略了这个关键字:

CREATE QUERY AgeQuery(IN topnum INT DEFAULT 10,IN minage INT 20)
   PROCEDURE
   BEGIN
   SELECT TOP :topnum Name,Age FROM Sample.Person
   WHERE Age > :minage ;
   END

以下是该查询的所有有效CALL语句:Call AgeQuery(6,65);Call AgeQuery(6);Call AgeQuery(,65);Call AgeQuery()

CALL AgeQuery(6,65); CALL AgeQuery(6); CALL AgeQuery(,65); CALL AgeQuery()

image

characteristics

可用的特征关键字如下:

  • CONTAINID integer - 指定返回ID的字段(如果有)。将CONTAINID设置为返回ID的列的编号,如果没有列返回ID,则设置为0。 IRIS不验证命名字段是否确实包含ID,因此此处的用户错误会导致数据不一致。
  • FOR className - 指定要在其中创建方法的类的名称。如果该类不存在,则会创建它。还可以通过限定方法名称来指定类名。在FOR子句中指定的类名将覆盖通过限定方法名指定的类名。
  • FINAL - 指定子类不能重写该方法。默认情况下,方法不是最终的。Final关键字由子类继承。
  • PROCEDURE - 指定查询为SQL存储过程。存储过程由子类继承。(此关键字可以缩写为proc。)
  • RESULTS (result_set) - 按查询返回数据字段的顺序指定数据字段。如果指定RESULTS子句,则必须将查询返回的所有字段作为逗号分隔的列表列出,并将其括在圆括号中。指定比查询返回的字段少或多的字段会导致SQLCODE-76基数不匹配错误。为每个字段指定列名(将用作列标题)和数据类型。如果使用SQL语言,则可以省略RESULTS子句。如果省略RESULTS子句,则会在类编译期间自动生成ROWSPEC
  • SELECTMODE mode - 指定用于编译查询的模式。可能的值有LogicalODBCRuntimeDisplay。默认值为运行时。

如果指定的方法关键字(如PRIVATERETURNS)对查询无效,系统将生成SQLCODE-47错误。指定重复特征会导致SQLCODE-44错误。

SELECTMODE子句指定返回数据的模式。如果模式值是逻辑值,则返回逻辑值(内部存储)。例如,日期以$HOROLOG格式返回。如果模式值为ODBC,则应用逻辑到ODBC的转换,并返回ODBC格式值。如果模式值为DISPLAY,则应用逻辑到显示的转换,并返回显示格式值。如果模式值为RUNTIME,则可以通过设置%SQL.Statement%SelectMode属性在执行时设置模式(设置为LOGICALODBCDISPLAY),运行时模式的值为Logical。为SELECTMODE指定的值将添加到ObjectScript类方法代码的开头:#SQLCompile select=mode

RESULTS子句指定查询的结果。RESULTS子句中的SQL数据类型参数被转换为查询的ROWSPEC中相应的 IRIS数据类型参数。例如,RESULTS子句RESULTS(Code VARCHAR(15))生成ROWSPEC范ROWSPEC=“Code:%Library.String(MAXLEN=15)”

LANGUAGE

指定CODE_BODY使用的语言的关键字子句。允许的子句是Language OBJECTSCRIPTLanguage SQL。如果省略LANGUAGE子句,则默认为SQL

如果语言是SQL,则会生成%Library.SQLQuery类型的类查询。如果语言是OBJECTSCRIPT,则会生成%Library.Query类型的类查询。

code_body

要创建的查询的程序代码。可以在SQL或ObjectScript中指定此代码。使用的语言必须与LANGUAGE子句匹配。但是,在ObjectScript中指定的代码可以包含嵌入式SQL

如果指定的代码是SQL,则它必须由单个SELECT语句组成。SQL中查询的程序代码以BEGIN关键字开头,后跟程序代码(SELECT语句)。在程序代码的末尾,指定分号(),然后指定END关键字。

如果指定的代码是OBJECTSCRIPT,则它必须包含对 IRIS提供的%Library.Query类的Execute()Fetch()类方法的调用,并且可以包含Close()FetchRows()GetInfo()方法调用。ObjectScript代码用大括号括起来。如果EXECUTE()FETCH()丢失,则编译时会生成SQLCODE-46错误。

如果ObjectScript代码块将数据提取到局部变量(例如,Row)中,则必须以行set Row=""结束代码块,以指示数据结束条件。

如果查询公开为存储过程(通过在Characteristic中指定PROCEDURE关键字),则它使用过程上下文处理程序在过程及其调用方之间来回传递过程上下文。

调用存储过程时,%Library.SQLProcContext类的对象在%sqlcontext变量中实例化。这用于在过程及其调用者(例如,ODBC服务器)之间来回传递过程上下文。

%sqlcontext由几个属性组成,包括错误对象、SQLCODE错误状态、SQL行数和错误消息。下面的示例显示了用于设置其中几个值的值:

  SET %sqlcontext.%SQLCODE=SQLCODE
  SET %sqlcontext.%ROWCOUNT=%ROWCOUNT
  SET %sqlcontext.%Message=%msg

SQLCODE%ROWCOUNT的值由SQL语句的执行自动设置。每次执行前都会重置%sqlcontext对象。

或者,可以通过实例化%SYSTEM.Error对象并将其设置为%sqlcontext.Error来建立错误上下文。

IRIS使用提供的代码生成查询的实际代码。

示例

下面的嵌入式SQL示例创建名为DocTestPersonState的查询。它不声明任何参数,设置SELECTMODE特征,并采用默认语言(SQL):

ClassMethod CreateQuery()
{
	&sql(
		CREATE QUERY DocTestPersonState() SELECTMODE RUNTIME
			BEGIN
				SELECT Name,Home_State FROM Sample.Person ;
			END
	)
	if SQLCODE=0 { 
		w !,"创建查询" 
	} elseif SQLCODE=-361 { 
		w !,"查询存在: ",%msg 
	} else { 
		w !,"创建 QUERY 错误 ",SQLCODE 
	}
}

可以转到管理门户,选择Classes选项,然后选择Samples命名空间。将在那里找到由上面的示例创建的查询:User.queryDocTestPersonState.cls。在重新运行上面的程序示例之前,您可以从该显示中删除此查询。当然,可以使用DROP QUERY删除创建的查询。

Class User.queryDocTestPersonState Extends %Library.RegisteredObject [ ClassType = "", DdlAllowed, Owner = {yx}, Not ProcedureBlock ]
{

Query DocTestPersonState() As %Library.SQLQuery(SELECTMODE = "RUNTIME")
{
	SELECT Name,Home_State FROM Sample.Person
}

}

下面的嵌入式SQL示例创建一个名为DocTestSQLCODEList的基于方法的查询,该查询获取SQLCODE及其说明的列表。它设置结果结果集特征,将语言设置为ObjectScript,并调用Execute()Fetch()Close()方法:

ClassMethod CreateQuery1()
{
	&sql(
		CREATE QUERY DocTestSQLCODEList()
			RESULTS 
			(
				SQLCODE SMALLINT, Description VARCHAR(100)
			)
			PROCEDURE
			LANGUAGE OBJECTSCRIPT
			Execute(INOUT QHandle BINARY(255))
			{
				s QHandle=1, %i(QHandle)=""
				q ##lit($$$OK)
			}
			Fetch(INOUT QHandle BINARY(255), INOUT Row %List, INOUT AtEnd INT)
			{
				s AtEnd = 0, Row = ""
				s %i(QHandle) = $o(^%qCacheSQL("SQLCODE", %i(QHandle)))
				if %i(QHandle) = "" {
					s AtEnd = 1 q ##lit($$$OK) 
				}
				s Row = $lb(%i(QHandle), ^%qCacheSQL("SQLCODE", %i(QHandle), 1, 1))
				q ##lit($$$OK)
			}
			Close(INOUT QHandle BINARY(255))
			{
				k %i(QHandle)
				q ##lit($$$OK)
			}
	)
	if SQLCODE=0 { 
		w !,"创建查询" 
	} elseif SQLCODE=-361 { 
		w !,"查询存在: ",%msg 
	} else { 
		w !,"创建 QUERY 错误 ",SQLCODE _ " "_%msg 
	}
}

可以转到管理门户,选择Classes选项,然后选择Samples命名空间。将在那里找到由上面的示例创建的查询:User.queryDocTestSQLCODEList.cls。在重新运行上面的程序示例之前,可以从该显示中删除此查询。当然,可以使用DROP QUERY删除创建的查询。

下面的动态SQL示例创建名为DocTest的查询,然后使用%SQL.Statement类的%PrepareClassQuery()方法执行此查询:

ClassMethod CreateQuery2()
{
	s SQLCODE = 0
	/* 创建 Query */
	s myquery=4
	s myquery(1) = "CREATE QUERY DocTest() SELECTMODE RUNTIME "
	s myquery(2) = "BEGIN "
	s myquery(3) = "SELECT TOP 5 Name,Home_State FROM Sample.Person ; "
	s myquery(4) = "END"
	s tStatement = ##class(%SQL.Statement).%New()
	s qStatus = tStatement.%Prepare(.myquery)
	if qStatus '= 1 {
		w "%Prepare 失败:" DO $System.Status.DisplayError(qStatus) 
		q
	}
	s rset = tStatement.%Execute()
	if SQLCODE = 0 { 
		w !,"创建查询" 
	} elseif SQLCODE=-361 { 
		w !,"查询存在: ",%msg 
	} else { 
		w !,"创建 QUERY 错误 ",SQLCODE _ " "_%msg 
	}
	/* 调用 Query */
	w !,"调用 Query",!
	s cqStatus = tStatement.%PrepareClassQuery("User.queryDocTest","DocTest")
	if cqStatus'=1 {
		w "%PrepareClassQuery 失败:" 
		d $System.Status.DisplayError(cqStatus)
	}
	s rset = tStatement.%Execute()
	w "Query 数据",!,!
	while rset.%Next() {
		d rset.%Print() 
	} 
	w !,"结束结束"
	/* 删除 Query */
	&sql(DROP QUERY DocTest)
	if SQLCODE = 0 { 
		w !,"删除 Query" 
	}
}
0
0 153
文章 姚 鑫 · 九月 9, 2021 7m read

第十一章 SQL命令 CREATE PROCEDURE(二)

characteristics

用于创建方法的特征与用于创建查询的特征不同。

如果指定的特征无效,系统将生成SQLCODE -47错误。 指定重复的特征将导致SQLCODE -44错误。

可用的方法特征关键字如下:

方法关键字含义
FOR className指定要在其中创建方法的类的名称。如果这个类不存在,它将被创建。还可以通过限定方法名来指定类名。FOR子句中指定的类名通过限定方法名重写指定的类名。如果使用FOR my.class语法指定类名, IRIS将用Sqlname=procname定义类方法。因此,该方法应该作为my.procname()调用(而不是my.class_procname())。
FINAL指定子类不能重写该方法。默认情况下,方法不是finalFINAL关键字由子类继承。
PRIVATE指定该方法只能由它自己的类或子类的其他方法调用。默认情况下,方法是公共的,可以不受限制地调用。这个限制由子类继承。
RESULT SETSDYNAMIC RESULT SETS [n]指定创建的方法将包含ReturnResultsets关键字。这一特征短语的所有形式都是同义词。
RETURNS datatype指定调用该方法返回的值的数据类型。如果省略RETURNS,则该方法不能返回值。这个规范由子类继承,并且可以由子类修改。该数据类型可以指定类型参数,如MINVALMAXVALSCALE。例如RETURNS DECIMAL(19,4)。注意,当返回一个值时, IRIS会忽略数据类型的长度;例如,RETURNS VARCHAR(32)可以接收由调用方法返回的任意长度的字符串。
SELECTMODE mode仅当LANGUAGE为SQL(默认)时使用。当指定时, IRIS将#SQLCOMPILE SELECT=mode语句添加到相应的类方法中,从而生成使用指定的SELECTMODE在方法中定义的SQL语句。可能的模式值是LOGICALODBCRUNTIMEDISPLAY。默认为LOGICAL

可用的查询特征关键字如下:

Query查询关键字含义
CONTAINID integer指定返回ID的字段(如果有的话)。将CONTAINID设置为返回ID的列的编号,如果没有列返回ID,则设置为0。 IRIS不验证命名字段是否实际包含ID,因此此处的用户错误会导致数据不一致。
FOR className指定要在其中创建方法的类的名称。如果这个类不存在,它将被创建。还可以通过限定方法名来指定类名。FOR子句中指定的类名通过限定方法名重写指定的类名。
FINAL指定子类不能重写该方法。默认情况下,方法不是finalFINAL关键字由子类继承。
RESULTS (result_set)按照查询返回的顺序指定数据字段。如果指定RESULTS子句,则必须将查询返回的所有字段用括号括起来的逗号分隔列表列出。在SQLCODE -76基数不匹配错误中,指定比查询结果返回的字段少或多。为每个字段指定一个列名(将用作列标题)和一个数据类型。如果使用SQL语言,则可以省略RESULTS子句。如果忽略RESULTS子句,则会在类编译期间自动生成ROWSPEC
SELECTMODE mode指定用于编译查询的模式。可能的值是LOGICALODBCRUNTIMEDISPLAY。默认是RUNTIME

SELECTMODE子句用于SELECT查询操作以及INSERTUPDATE操作。 它指定编译时选择模式。 为SELECTMODE指定的值添加在ObjectScript类方法代码的开头,如:#SQLCompile Select=mode

  • SELECT查询中,SELECTMODE指定返回数据的模式。 如果模式值为LOGICAL,则返回逻辑(内部存储)值。 例如,日期以$HOROLOG格式返回。 如果模式值为ODBC,则应用逻辑到ODBC的转换,并返回ODBC格式值。 如果模式值为DISPLAY,则应用逻辑到显示的转换,并返回显示格式值。 如果mode值为RUNTIME,则可以在执行时设置显示模式(LOGICALODBCdisplay)。
  • INSERTUPDATE操作中,SELECTMODE RUNTIME选项支持将输入数据值从显示格式(displayODBC)自动转换为逻辑存储格式。 只有当SQL代码执行时的选择模式设置为LOGICAL(这是所有 SQL执行接口的默认设置)时,才会应用这个已编译的从显示到逻辑的数据转换代码。

RESULTS子句指定查询的结果。 RESULTS子句中的SQL数据类型参数被转换为查询的ROWSPEC中相应的 IRIS数据类型参数。 例如,RESULTS子句RESULTS (Code VARCHAR(15))生成ROWSPEC规范:ROWSPEC = " Code:%Library.String(MAXLEN=15) "。

LANGUAGE

指定过程代码语言的关键字子句。可用的选项是:

  • 语言OBJECTSCRIPT(用于OBJECTSCRIPT)或语言SQL。过程代码在code_body中指定。
  • Language JavaLanguage PythonLanguage DotNet用于使用这些语言之一调用外部存储过程的SQL过程。外部存储过程的语法如下:
LANGUAGE langname EXTERNAL NAME external-routine-name

其中,langname是JAVA、PYTHON或DOTNET,而external-routine-name是一个引号括起来的字符串,包含指定语言中的外部例程的名称。 SQL过程调用现有的例程; 不能在CREATE PROCEDURE语句中用这些语言编写代码。 这些语言中的存储过程库存储在IRIS外部,因此不必在IRIS内打包、导入或编译。 下面是一个CREATE过程调用现有JAVA外部存储过程的示例:

CREATE PROCEDURE updatePrice (item_name VARCHAR, new_price INTEGER)
LANGUAGE JAVA  
EXTERNAL NAME 'Orders.updatePrice'

如果省略LANGUAGE子句,则默认为SQL

code_body

要创建的方法或查询的程序代码。可以在SQL或ObjectScript中指定此代码。使用的语言必须与language子句匹配。但是,ObjectScript中指定的代码可以包含嵌入式SQL。IRIS使用提供的代码来生成方法或查询的实际代码。

  • SQL程序代码以BEGIN关键字开头,后面跟着SQL代码本身。 在每个完整的SQL语句的末尾,指定一个分号(;)。 一个查询只包含一条SQL语句——一条SELECT语句。 还可以创建插入、更新或删除数据的过程。 SQL程序代码以END关键字结束。

输入参数在SQL语句中作为主机变量指定,形式为:name。 (注意,在SQL代码中不应该使用问号(?)来指定输入参数。 过程将成功构建,但在调用过程时,不能传递这些参数或接受默认值。)

  • ObjectScript程序代码用花括号括起来:{code}。 代码行必须缩进。 如果指定了,标签或#Include预处理器命令必须以冒号作为前缀,并出现在第一列,如下所示:
CREATE PROCEDURE SP123()
  LANGUAGE OBJECTSCRIPT 
{
:Top
:#Include %occConstant
  WRITE "Hello World"
  IF 0=$RANDOM(2) { GOTO Top }
  ELSE {QUIT $$$OK }
}

系统自动包含%occInclude。 如果程序代码包含 IRIS Macro Preprocessor语句(# commands## functions,或$$$ Macro references),则这些语句的处理和扩展是过程方法定义的一部分,并在方法编译时进行处理和扩展。

IRIS在生成过程时提供额外的代码行,该过程将SQL嵌入到ObjectScript“包装器”中,提供过程上下文处理程序,并处理返回值。 下面是iris生成的包装器代码的示例:

   NEW SQLCODE,%ROWID,%ROWCOUNT,title
   &sql(
        -- code_body
       )
   QUIT $GET(title)

如果指定的代码是OBJECTSCRIPT,则必须显式定义“包装器”(该NEWs变量并使用QUIT val在完成时返回一个值。

示例

下面的示例分为使用SQL code_body的示例和使用ObjectScript code_body的示例。

使用SQL代码的示例

下面的示例创建了一个名为PersonStateSP的简单查询,该查询作为存储过程公开。 它没有声明任何参数,并接受特征和语言的默认值:

ClassMethod CreateProcedure()
{
	&sql(
		CREATE PROCEDURE PersonStateSP() 
		BEGIN
			SELECT Name,Home_State FROM Sample.Person ;
		END
	)
	if SQLCODE=0 { 
		w !,"创建存储过程" 
	} elseif SQLCODE=-361 { 
		w !,"存储过程已经存在" 
	} else { 
		w !,"SQL 错误 ",SQLCODE 
	}
}

可以转到Management Portal,选择Classes选项,然后选择SAMPLES名称空间。 在这里,将找到上述示例创建的存储过程:User.procPersonStateSP.cls

Class User.procPersonStateSP Extends %Library.RegisteredObject [ ClassType = "", DdlAllowed, Owner = {yx}, Not ProcedureBlock ]
{

Query PersonStateSP() As %Library.SQLQuery [ SqlName = PersonStateSP, SqlProc ]
{
	SELECT Name,Home_State FROM Sample.Person
}

}

在重新运行上面的程序示例之前,可以从这个显示中删除这个过程。 当然,可以使用DROP PROCEDURE来删除一个过程:

ClassMethod CreateProcedure1()
{
	&sql(
		DROP PROCEDURE PersonStateSP
	)
	if SQLCODE=0 { 
		w !,"删除储存过程" 
	} elseif SQLCODE=-362 { 
		w !,"存储过程不存在" 
	} else { 
		w !,"SQL错误r: ",SQLCODE 
	}
}

下面的示例创建一个更新数据的过程。它使用CREATE PROCEDURESample.Employee类中生成方法UpdateSalary

CREATE PROCEDURE UpdateSalary ( IN SSN VARCHAR(11), IN Salary INTEGER )
   FOR Sample.Employee
   BEGIN
     UPDATE Sample.Employee SET Salary = :Salary WHERE SSN = :SSN;
   END

使用ObjectScript代码的示例

下面的示例创建生成随机大写字母的RandomLetterSP()存储过程方法。然后,可以在SELECT语句中将此方法作为函数调用。提供了一个删除RandomLetterSP()方法的删除过程。

CREATE PROCEDURE RandomLetterSP()
RETURNS INTEGER
LANGUAGE OBJECTSCRIPT
{
:Top
 SET x=$RANDOM(90)
 IF x<65 {GOTO Top}
 ELSE {QUIT $CHAR(x)}
}
SELECT Name FROM Sample.Person
WHERE Name %STARTSWITH RandomLetterSP()
DROP PROCEDURE RandomLetterSP

下面的CREATE PROCEDURE示例使用ObjectScript调用Execute()Fetch()。和Close()方法。此类过程还可以包含FetchRows()GetInfo()方法调用:

CREATE PROCEDURE GetTitle()
    FOR Sample.Employee
    RESULTS (ID %Integer)
    CONTAINID 1
    LANGUAGE OBJECTSCRIPT
    Execute(INOUT qHandle %Binary)
    {  QUIT 1 }
    Fetch(INOUT qHandle %Binary, INOUT Row %List, INOUT AtEnd %Integer)
    {  QUIT 1 }
    Close(INOUT qHandle %Binary)
    {  QUIT 1 }

下面的CREATE PROCEDURE示例使用ObjectScript调用%SQL.Statement结果集类:

CREATE PROCEDURE Sample_Employee.GetTitle(
    INOUT Title VARCHAR(50) )
    RETURNS VARCHAR(30)
    FOR Sample.Employee
    LANGUAGE OBJECTSCRIPT
  {
  SET myquery="SELECT TOP 10 Name,Title FROM Sample.Employee"
  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()
  DO rset.%Display()
  WRITE !,"End of data"
  }

如果ObjectScript代码块将数据提取到局部变量(例如,Row)中,则必须以行set Row=""结束代码块,以指示数据结束条件。

下面的示例将CREATE PROCEDURE与调用嵌入式SQL的ObjectScript代码一起使用。它在Sample.Employee类中生成方法GetTitle,并将Title值作为参数传出:

CREATE PROCEDURE Sample_Employee.GetTitle(
   IN SSN VARCHAR(11), 
   INOUT Title VARCHAR(50) )
    RETURNS VARCHAR(30)
    FOR Sample.Employee
    LANGUAGE OBJECTSCRIPT
    {
        NEW SQLCODE,%ROWCOUNT
        &sql(SELECT Title INTO :Title FROM Sample.Employee 
             WHERE SSN = :SSN)
        IF $GET(%sqlcontext)'= "" {
           SET %sqlcontext.%SQLCODE=SQLCODE
           SET %sqlcontext.%ROWCOUNT=%ROWCOUNT }
           QUIT
     }

它使用%sqlcontext对象,并使用相应的SQL变量设置它的%SQLCODE%ROWCOUNT属性。 注意,在过程的LANGUAGE ObjectScript关键字后面的花括号中包含ObjectScript代码。 在ObjectScript代码中有嵌入式SQL代码,用&sql标记,用括号括起来。

0
0 203
文章 姚 鑫 · 九月 8, 2021 7m read

第十章 SQL命令 CREATE PROCEDURE(一)

创建作为SQL存储过程公开的方法或查询。

大纲

CREATE PROCEDURE procname(parameter_list)
   [ characteristics ]
   [ LANGUAGE SQL ]
   BEGIN
code_body ;
   END

CREATE PROCEDURE procname(parameter_list)
    [ characteristics ]
    LANGUAGE OBJECTSCRIPT
   { code_body }

CREATE PROCEDURE procname(parameter_list)
    [ characteristics ]
    LANGUAGE { JAVA | PYTHON | DOTNET } EXTERNAL NAME external-stored-procedure

CREATE PROC procname(parameter_list)
   [ characteristics ]
   [ LANGUAGE SQL ]
   BEGIN
code_body ;
   END

CREATE PROC procname(parameter_list)
    [ characteristics ]
    LANGUAGE OBJECTSCRIPT
   { code_body }

CREATE PROC procname(parameter_list)
    [ characteristics ]
    LANGUAGE { JAVA | PYTHON | DOTNET } EXTERNAL NAME external-stored-procedure

参数

  • procname - 要在存储过程类中创建的过程的名称。 procname必须是一个有效的标识符。 过程名可以是限定的(schema.procname),也可以是非限定的(procname)。 非限定过程名接受默认模式名。 procname后面必须跟圆括号,即使没有指定参数。
  • parameter_list - 可选——传递给过程的一个包含零个或多个参数的列表。 参数列表用圆括号括起来,列表中的参数用逗号分隔。 即使没有指定参数,括号也是必须的。 每个参数由(按顺序)组成:一个可选的 INOUTINOUT关键字; 变量名; 数据类型; 和一个可选的DEFAULT子句。
  • characteristics - 可选-一个或多个关键字,指定过程的特征。 在创建方法时,允许的关键字是FINALFORPRIVATERETURNSSELECTMODE。 在创建查询时,允许的关键字是CONTAINIDFINALFORRESULTSSELECTMODE。可以指定特征关键字短语RESULT SETSDYNAMIC RESULT SETSDYNAMIC RESULT SETS n,其中n是整数。 这些短语是同义词; DYNAMIC关键字和n整数为no-ops,提供兼容性。 多个特征由空格(一个空格或换行符)分隔。 特性可以以任何顺序指定。
  • LANGUAGE OBJECTSCRIPTLANGUAGE SQL - 可选-一个关键字子句,指定用于code_body的编程语言。 指定语言OBJECTSCRIPT(用于OBJECTSCRIPT)或语言SQL。 如果省略LANGUAGE子句,则默认为SQL
  • LANGUAGE JAVALANGUAGE PYTHONLANGUAGE DOTNET - 可选-关键字子句,指定用于调用指定语言中的现有外部存储过程的编程语言。 被调用的例程必须是一个静态方法。
  • code_body - 该程序的程序代码。SQL程序代码以BEGIN关键字开头,以END关键字结尾。 code_body中的每个完整SQL语句都以分号(;)结束。ObjectScript程序代码用花括号括起来。 ObjectScript代码行必须缩进。

描述

CREATE PROCEDURE语句创建一个方法或查询,该方法或查询将自动作为SQL存储过程公开。 存储过程可以由当前名称空间中的所有进程调用。 存储过程由子类继承。

  • 如果使用SQL语言,则code_body必须包含SELECT语句,以便生成公开为存储过程的查询。 如果代码不包含SELECT语句,则CREATE PROCEDURE创建一个方法。
  • 如果LANGUAGE OBJECTSCRIPT,则code_body必须调用Execute()Fetch()方法,以生成公开为存储过程的查询。 它也可以调用Close()FetchRows()GetInfo()方法。 如果代码没有调用Execute()Fetch(),则CREATE PROCEDURE创建一个方法。

默认情况下,CREATE PROCEDURE创建公开为存储过程的方法。

要创建未公开为存储过程的方法,请使用 CREATE METHODCREATE FUNCTION语句。 若要创建未公开为存储过程的查询,请使用CREATE QUERY语句。 通过指定procedure特征关键字,这些语句还可以用于创建作为存储过程公开的方法或查询。

为了创建一个过程,必须具有GRANT命令指定的%CREATE_PROCEDURE管理权限。 如果为具有已定义所有者的现有类创建过程,则必须作为该类的所有者登录。 否则,操作将失败,并出现SQLCODE -99错误。

如果类定义是已部署的类,则不能在类中创建过程。 此操作失败,并出现一个带有%msgSQLCODE -400错误Unable to execute DDL that modifies a deployed class: 'classname'.

使用CALL语句执行存储过程。

参数

procname

要创建为存储过程的方法或查询的名称。即使没有指定参数,procname后面也必须有括号。过程名可以采用以下任何形式:

  • Unqualified不限定的:接受默认模式名。例如,MedianAgeProc()
  • Qualified:提供模式名称。例如,Patient.MedianAgeProc()
  • Multilevel多级:限定为一个或多个模式层,以并行相应的类包成员。 在这种情况下,procname可能只包含一个句点字符; 对应类方法名中的其他句点将被下划线字符替换。 在最低级别的类包成员之前指定句点。 例如,%SYSTEM.SQL_GetROWID()%SYS_PTools.StatsSQL_Export()

非限定的procname接受默认模式名。 可以使用$SYSTEM.SQL.Schema.Default()方法确定当前系统范围的默认模式名。 系统范围的初始默认模式名是SQLUser,它对应于类包名User

注意,FOR特征(将在下面描述)覆盖了在procname中指定的类名。 如果已经存在具有此名称的过程,则操作将失败,并出现SQLCODE -361错误。

SQL使用SQL procname生成相应的类名。 该名称由与模式名对应的包名、点、" proc "和指定的过程名组成。 例如,如果非限定过程名RandomLetter()接受默认模式SQLUser,则产生的类名将是:User.procRandomLetter()

SQL不允许指定只以字母大小写不同的procname。 指定一个只在字母大小写上与现有过程名不同的procname将导致SQLCODE -400错误。

如果指定的procname已经存在于当前命名空间中,系统将生成SQLCODE -361错误。 要确定指定的procname是否已经存在于当前命名空间中,请使用$SYSTEM.SQL.Schema.ProcedureExists()方法。

注意: SQL过程名称和 TSQL过程名称共享同一组名称。 因此,不能在同一命名空间中创建与TSQL过程同名的SQL过程。 尝试这样做会导致SQLCODE -400错误。

parameter_list

用于将值传递给方法或查询的参数列表。 形参列表用圆括号括起来,列表中的形参声明用逗号分隔。 括号是必须的,即使没有指定参数。

列表中的每个参数声明由(按顺序)组成:

  • 一个可选关键字,指定参数模式是IN(输入值)、OUT(输出值)还是INOUT(修改值)。如果省略,默认参数模式为IN
  • 参数名称。参数名称区分大小写。
  • 参数的数据类型。
  • 可选:默认值。 可以指定DEFAULT关键字后跟一个默认值; DEFAULT关键字是可选的。 如果没有指定默认值,则假定默认值为NULL

下面的示例创建了一个具有两个输入参数的存储过程,这两个参数都具有默认值。 一个输入参数指定可选的DEFAULT关键字,另一个输入参数忽略该关键字:

CREATE PROCEDURE AgeQuerySP(IN topnum INT DEFAULT 10,IN minage INT 20)
   BEGIN
   SELECT TOP :topnum Name,Age FROM Sample.Person
   WHERE Age > :minage ;
   END

下面的示例在功能上与上面的示例相同。可选的DEFAULT关键字省略:

CREATE PROCEDURE AgeQuerySP(IN topnum INT 10,IN minage INT 20)
   BEGIN
   SELECT TOP :topnum Name,Age FROM Sample.Person
   WHERE Age > :minage ;
   END

下面是这个过程中所有有效的CALL语句:

CALL AgeQuerySP(6,65); CALL AgeQuerySP(6); CALL AgeQuerySP(,65); CALL AgeQuerySP().

image

下面的示例创建了一个公开为具有三个参数的存储过程的方法:

CREATE PROCEDURE UpdatePaySP
  (IN Salary INTEGER DEFAULT 0,
   IN Name VARCHAR(50), 
   INOUT PayBracket VARCHAR(50) DEFAULT 'NULL')
BEGIN
   UPDATE Sample.Employee SET Salary = :Salary
   WHERE Name=:Name ;
END

存储过程不执行参数的自动格式转换。 例如,ODBC格式或Display格式的输入参数仍然保持该格式。 调用过程的代码和过程代码本身负责以适合应用程序的格式处理IN/OUT值,并执行任何必要的转换。

因为方法或查询是作为存储过程公开的,所以它使用过程上下文处理程序在过程及其调用方之间来回传递过程上下文。调用存储过程时,%Library.SQLProcContext类的对象在%sqlcontext变量中实例化。这用于在过程及其调用者(例如ODBC服务器)之间来回传递过程上下文。

%sqlcontext由几个属性组成,包括Error对象、SQLCODE错误状态、SQL行数和错误消息。 下面的示例显示了用于设置其中几个参数的值:

  SET %sqlcontext.%SQLCODE=SQLCODE
  SET %sqlcontext.%ROWCOUNT=%ROWCOUNT
  SET %sqlcontext.%Message=%msg

SQLCODE%ROWCOUNT的值是在执行SQL语句时自动设置的。 %sqlcontext对象在每次执行之前都被重置。

或者,也可以通过实例化%SYSTEM来建立错误上下文。 对象,并将其设置为%sqlcontext.Error

0
0 141
文章 Botai Zhang · 一月 26, 2021 3m read

利用Intersystems IRIS医疗版数据平台内置多模型整合

医院信息查询业务解决方案

概述:

随着医院信息化建设的逐步完善医院子系统越来越多,系统间接口越来越多,同时接口费用不断增加,管理工作变得越来越复杂。其中,查询类业务接口根据业务类型分化,数量也是逐步递增,带来接口量大、开发工作繁重、代码冗余、维护困难等等问题。针对这一困境,我们利用Intersystems IRIS数据平台内置多模型整合医院信息查询业务解决方案。该应用程序可通过内置模型应用完成查询业务,大大缩小开发、维护、实施等项目关键运转周期。

应用链接:HealthInfoQueryLayer           

关键应用:IRIS for Health、REST APIObjectScriptGlobals SQL、DATA LOOKUP TABLES

应用程序采用模型及应用介绍:

1.采用模型

1.1. Globals (key-value)

Globals是可以在IRIS数据库中存储和管理的稀疏多维数组。您可以使用ObjectScript和本机API处理Globals

工具:

https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GGBL_MANAGING

应用:

2
0 484
文章 姚 鑫 · 九月 7, 2021 4m read

第九章 SQL命令 CREATE METHOD(二)

characteristics

可用的关键字如下:

  • FOR className - 指定要在其中创建方法的类的名称。 如果这个类不存在,它将被创建。 还可以通过限定方法名来指定类名。 FOR子句中指定的类名通过限定方法名重写指定的类名。
  • FINAL - 指定子类不能重写该方法。 默认情况下,方法不是finalFINAL关键字由子类继承。
  • PRIVATE - 指定该方法只能由它自己的类或子类的其他方法调用。 默认情况下,方法是公共的,可以不受限制地调用。 这个限制由子类继承。
  • PROCEDURE - 指定该方法是一个SQL存储过程。 存储过程由子类继承。 (这个关键字可以缩写为PROC。)
  • RESULT SETS ,DYNAMIC RESULT SETS [n] - 指定创建的方法将包含ReturnResultsets关键字。 这一特征短语的所有形式都是同义词。
  • RETURNS datatype - 指定调用该方法返回的值的数据类型。 如果省略RETURNS,则该方法不能返回值。 这个规范由子类继承,并且可以由子类修改。 该数据类型可以指定类型参数,如MINVALMAXVALSCALE。 例如RETURNS DECIMAL(19,4)。 注意,当返回一个值时, IRIS会忽略数据类型的长度; 例如,RETURNS VARCHAR(32)可以接收由调用方法返回的任意长度的字符串。
  • SELECTMODE mode - 仅当LANGUAGESQL(默认)时使用。 当指定时, IRIS将#SQLCOMPILE SELECT=mode语句添加到相应的类方法中,从而生成使用指定的SELECTMODE在方法中定义的SQL语句。 可能的模式值是LOGICALODBCRUNTIMEDISPLAY。 默认为LOGICAL

如果指定对方法无效的查询关键字(如CONTAINSIDRESULTS),系统将生成SQLCODE -47错误。 如果指定了重复的查询关键字(例如FINAL FINAL),系统将生成SQLCODE -44错误。

SELECTMODE子句用于SELECT查询操作以及INSERTUPDATE操作。 它指定编译时选择模式。 为SELECTMODE指定的值添加在ObjectScript类方法代码的开头,如:#SQLCompile Select=mode

  • SELECT查询中,SELECTMODE指定返回数据的模式。 如果模式值为LOGICAL,则返回逻辑(内部存储)值。 例如,日期以$HOROLOG格式返回。 如果模式值为ODBC,则应用逻辑到ODBC的转换,并返回ODBC格式值。 如果模式值为DISPLAY,则应用逻辑到显示的转换,并返回显示格式值。 如果mode值为RUNTIME,则可以在执行时设置显示模式(LOGICALODBCdisplay)。
  • INSERTUPDATE操作中,SELECTMODE RUNTIME选项支持将输入数据值从显示格式(displayODBC)自动转换为逻辑存储格式。 只有当SQL代码执行时的选择模式设置为LOGICAL(这是所有 SQL执行接口的默认设置)时,才会应用这个已编译的从显示到逻辑的数据转换代码。

执行SQL代码时,%SQL.Statement%SelectMode属性指定执行时选择模式。

LANGUAGE

指定CODE_BODY使用的语言的关键字子句。允许的子句是Language OBJECTSCRIPT(对于ObjectScript)或Language SQL。如果省略LANGUAGE子句,则默认为SQL

code_body

要创建的方法的程序代码。可以在SQL或ObjectScript中指定此代码。使用的语言必须与LANGUAGE子句匹配。但是,在ObjectScript中指定的代码可以包含嵌入式SQL

IRIS使用提供的代码来生成该方法的实际代码。

如果指定的代码是SQL, IRIS会在生成将SQL嵌入到ObjectScript“包装器wrapper”中的方法时提供额外的代码行,提供过程上下文处理程序(如有必要),并处理返回值。以下是此IRIS生成的包装代码的示例:

   NEW SQLCODE,%ROWID,%ROWCOUNT,title
   &sql( SELECT col FROM tbl )
   QUIT $GET(title)

如果指定的代码是OBJECTSCRIPT,则必须用大括号将ObjectScript代码括起来。除标签和宏预处理器指令外,所有代码行都必须从第1列缩进。标签或宏指令必须在第1列中以冒号()开头。

对于ObjectScript代码,必须显式定义“包装器”(该NEWs变量并使用QUIT退出,并(可选地)在完成时返回一个值)。

通过指定PROCEDURE关键字,可以将该方法公开为存储过程。调用存储过程时,%Library.SQLProcContext类的对象在%sqlcontext变量中实例化。此过程上下文处理程序用于在过程及其调用方(例如,ODBC服务器)之间来回传递过程上下文。

%sqlcontext由几个属性组成,包括错误对象、SQLCODE错误状态、SQL行数和错误消息。下面的示例显示了用于设置其中几个值的值:

  SET %sqlcontext.%SQLCODE=SQLCODE
  SET %sqlcontext.%ROWCOUNT=%ROWCOUNT
  SET %sqlcontext.%Message=%msg

SQLCODE%ROWCOUNT的值由SQL语句的执行自动设置。每次执行前都会重置%sqlcontext对象。

或者,可以通过实例化%SYSTEM.Error对象并将其设置为%sqlcontext.Error来建立错误上下文。

示例

下面的示例使用带有SQL代码的Create方法在Sample.Employee类中生成UpdateSalary方法:

CREATE METHOD UpdateSalary ( IN SSN VARCHAR(11), IN Salary INTEGER )
   FOR Sample.Employee
   BEGIN
     UPDATE Sample.Employee SET Salary = :Salary WHERE SSN = :SSN;
   END

注意给表添加关键字[ DdlAllowed ]

下面的示例创建存储为生成随机大写字母的过程的RandomLetter()方法。然后,可以在SELECT语句中将此方法作为函数调用。提供了一个Drop方法来删除RandomLetter()方法。

CREATE METHOD RandomLetter()
RETURNS INTEGER
PROCEDURE
LANGUAGE OBJECTSCRIPT
{
:Top
 SET x=$RANDOM(91)
 IF x<65 {GOTO Top}
 ELSE {QUIT $CHAR(x)}
}

Class User.methRandomLetter Extends %Library.RegisteredObject [ ClassType = "", DdlAllowed, Owner = {yx}, Not ProcedureBlock ]
{

ClassMethod RandomLetter() As %Library.Integer(MAXVAL=2147483647,MINVAL=-2147483648) [ SqlName = RandomLetter, SqlProc ]
{
Top
		 SET x=$RANDOM(91)
		 IF x<65 {GOTO Top}
		 ELSE {QUIT $CHAR(x)}
}

}

SELECT Name FROM Sample.Person
WHERE Name %STARTSWITH RandomLetter()

image

DROP METHOD RandomLetter

下面的嵌入式SQL示例使用带有ObjectScript代码的Create方法在SQLUser.MyStudents类中生成方法TraineeTitle,并返回一个Title值:

ClassMethod CreateMethod()
{
	&sql(
		CREATE METHOD TraineeTitle
		(
			IN SSN VARCHAR(11), 
			INOUT Title VARCHAR(50) 
		)
		RETURNS VARCHAR(30)
		FOR SQLUser.MyStudents
		LANGUAGE OBJECTSCRIPT
		{
			n SQLCODE,%ROWCOUNT
			&sql(
				SELECT Title INTO :Title FROM Sample.Employee 
				WHERE SSN = :SSN
			)
			if $g(%sqlcontext)'= "" {
				s %sqlcontext.%SQLCODE=SQLCODE
				s %sqlcontext.%ROWCOUNT=%ROWCOUNT 
			}
			q
		}
	)
	if SQLCODE=0 { 
		w !,"创建方法" QUIT
	} elseif SQLCODE=-361 { 
		w !,"方法已存在SQLCODE: ",SQLCODE
		&sql(
			DROP METHOD TraineeTitle FROM SQLUser.MyStudents
		)
		if SQLCODE=0 { 
			w !,"删除方法" QUIT
		}
	} else { 
		w !,"SQL error: ",SQLCODE 
	}
}

它使用%sqlcontext对象,并使用相应的SQL变量设置它的%SQLCODE%ROWCOUNT属性。 请注意,在方法的LANGUAGE ObjectScript关键字后面,用花括号括住ObjectScript代码。 在ObjectScript代码中有嵌入式SQL代码,用&sql标记,用括号括起来。

0
0 99
文章 姚 鑫 · 九月 6, 2021 5m read

第八章 SQL命令 CREATE METHOD(一)

在类中创建方法。

大纲

CREATE [STATIC] METHOD name (parameter_list) 
   [ characteristics ]
   [ LANGUAGE SQL ]
   BEGIN
code_body ;
   END

CREATE [STATIC] METHOD name (parameter_list) 
    [ characteristics ]
    LANGUAGE OBJECTSCRIPT
   { code_body }

参数

  • name - 要在存储过程类中创建的方法的名称。 名称必须是有效的标识符。 过程名可以是限定的(schema.procname),也可以是非限定的(procname)。 非限定过程名接受默认模式名。 名称后面必须跟括号,即使没有指定参数。
  • parameter_list - 可选——传递给方法的参数列表。 参数列表用圆括号括起来,列表中的参数用逗号分隔。 即使没有指定参数,括号也是必须的。
  • characteristics - 可选—指定方法特征的一个或多个关键字。 允许的关键字是RETURNS, FOR, FINAL, PRIVATE, PROCEDURE, SELECTMODE。可以指定特征关键字短语RESULT SETSDYNAMIC RESULT SETSDYNAMIC RESULT SETS n,其中n是整数。 这些短语是同义词; DYNAMIC关键字和n整数为no-ops,提供兼容性。多个特征由空格(一个空格或换行符)分隔。 特性可以以任何顺序指定。
  • LANGUAGE OBJECTSCRIPTLANGUAGE SQL - 可选—用于代码体的编程语言。指定语言对象脚本(对于对象脚本)或语言SQL。如果省略了LANGUAGE子句,则默认为SQL
  • code_body - 方法的程序代码。SQL程序代码以BEGIN关键字开头,以END关键字结尾。code_body中的每个完整的SQL语句都以分号().ObjectScript程序代码用花括号括起来。 ObjectScript代码行必须缩进。

描述

CREATE METHOD语句创建一个类方法。 这个类方法可能是存储过程,也可能不是。 要在公开为SQL存储过程的类中创建方法,必须指定procedure关键字。 默认情况下,CREATE METHOD不会创建一个同时也是存储程序的方法; CREATE PROCEDURE语句总是创建一个同时也是存储过程的方法。

提供可选的STATIC关键字是为了说明所创建的方法是一个静态(类)方法,而不是一个实例方法。 该关键字没有提供实际的功能。

为了创建方法,必须具有GRANT命令指定的%CREATE_METHOD管理权限。 如果试图为具有已定义所有者的现有类创建方法,则必须作为该类的所有者登录。 否则,操作将失败,并出现SQLCODE -99错误。

如果类定义是已部署的类,则不能在类中创建方法。 此操作失败,并出现一个带有%msgSQLCODE -400错误Unable to execute DDL that modifies a deployed class: 'classname'.

下面两个示例都展示了相同类方法的创建。 第一个示例使用CREATE METHOD,第二个示例在类User中定义类方法。 字母:

CREATE METHOD RandCaseLetter(IN caps CHAR) 
  RETURNS INTEGER 
  PROCEDURE 
LANGUAGE OBJECTSCRIPT
{
:Top
	if caps = "U" {
		s x = $random(91) 
		if x > 64 {
			q $char(x)
		} else {
			g Top
		}
	} elseif caps="L" { 
		s x = $random(123)  
		if x > 97 {
			q $char(x)
		} else {
			g Top
		}
	} else {
		q "大小写必须 'U' 或 'L'"
	}
}
}

自动创建的后台类

Class User.methRandCaseLetter Extends %Library.RegisteredObject [ ClassType = "", DdlAllowed, Owner = {yx}, Not ProcedureBlock ]
{

ClassMethod RandCaseLetter(caps As %Library.String(MAXLEN=1)) As %Library.Integer(MAXVAL=2147483647,MINVAL=-2147483648) [ SqlName = RandCaseLetter, SqlProc ]
{
Top
		 IF caps="U" {SET x=$RANDOM(91) IF x>64 {QUIT $CHAR(x)}
		   ELSE {GOTO Top}}
		 ELSEIF caps="L" {SET x=$RANDOM(123) IF x>97 {QUIT $CHAR(x)}
		   ELSE {GOTO Top}}
		 ELSE {QUIT "case must be 'U' or 'L'"}
}

}

Class User.Letters Extends %Persistent [ DdlAllowed ] 
{
ClassMethod RandCaseLetter(caps) As %String [ SqlName = RandomLetter, SqlProc ]
{ 
Top
	if caps = "U" {
		s x = $random(91) 
		if x > 64 {
			q $char(x)
		} else {
			g Top
		}
	} elseif caps="L" { 
		s x = $random(123)  
		if x > 97 {
			q $char(x)
		} else {
			g Top
		}
	} else {
		q "大小写必须 'U' 或 'L'"
	}
}
}

参数

name

要创建的方法的名称。 此名称可以是非限定的(StoreName)并接受系统范围的默认模式名称,也可以通过指定模式名称(Patient.StoreName)进行限定。 可以使用$SYSTEM.SQL.Schema.Default()方法确定当前系统范围的默认模式名。 系统范围的初始默认模式名是SQLUser,它对应于类包名User

注意,FOR特征(将在下面描述)覆盖了name中指定的类名。 如果已经存在具有此名称的方法,则操作将失败,并出现SQLCODE -361错误。

生成的类的名称是与模式名对应的包名,后面跟着一个点,然后是“meth”,最后是指定的名称。 例如,如果非限定方法名RandomLetter接受初始默认模式SQLUser,则产生的类名将是:User.methRandomLetter

parameter-list

用于将值传递给方法的参数列表。 形参列表用圆括号括起来,列表中的形参声明用逗号分隔。 即使没有指定参数,括号也是必须的。 列表中的每个参数声明由(按顺序)组成:

  • 一个可选关键字,指定参数模式是IN(输入值)、OUT(输出值)还是INOUT(修改值)。 如果省略,默认参数模式为IN
  • 参数名称。 参数名称区分大小写。
  • 参数的数据类型。
  • 可选:默认值。 可以指定DEFAULT关键字后跟一个默认值; DEFAULT关键字是可选的。 如果没有指定默认值,则假定默认值为NULL

方法的输出值自动从Logical格式转换为Display/ODBC格式。

默认情况下,方法的输入值不会从Display/ODBC格式转换为Logical格式。 但是,可以使用$SYSTEM.SQL.Util.SetOption("SQLFunctionArgConversion")方法在系统范围内配置输入显示到逻辑的转换。 可以使用$SYSTEM.SQL.Util.GetOption("SQLFunctionArgConversion")来确定该选项的当前配置。

下面的示例指定两个输入参数,它们都有默认值。 为第一个参数指定可选的DEFAULT关键字,为第二个参数省略:

CREATE METHOD RandomLetter(IN firstlet CHAR DEFAULT 'A',IN lastlet CHAR 'Z')
BEGIN
-- SQL program code
END
0
0 102
文章 姚 鑫 · 九月 5, 2021 6m read

第七章 SQL命令 CREATE INDEX(二)

UNIQUE关键字

使用UNIQUE关键字,可以指定索引中的每条记录都有一个唯一的值。 更具体地说,这确保了索引(以及包含索引的表)中的两条记录不能具有相同的排序值。 默认情况下,大多数索引使用大写字符串排序(使搜索不区分大小写)。 在本例中,值“Smith”“SMITH”被认为是相等的,而不是唯一的。 CREATE INDEX不能指定非默认索引字符串排序规则。 通过在类定义中定义索引,可以为各个索引指定不同的字符串排序规则。

可以更改名称空间的默认排序规则,使字段/属性在默认情况下区分大小写。 更改此选项需要重新编译命名空间中的所有类并重新构建所有索引。 转到Management Portal,选择Classes选项,为存储的查询选择名称空间,并使用Compile选项重新编译相应的类。 然后重建所有指数。 它们将区分大小写。

注意:当表的数据被其他用户访问时,不要重建索引。 这样做可能会导致不准确的查询结果。

BITMAP 关键字

使用BITMAP关键字,你可以指定这个索引将是位图索引。 位图索引由一个或多个位字符串组成,其中位位置表示行id,每个位值表示该行字段(或合并字段名字段的值)的特定值的存在(1)或不存在(0)。 SQL在插入、更新或删除数据时维护这些位置位(作为压缩位串); 在使用位图索引和使用常规索引之间,INSERTUPDATEDELETE操作的性能没有显著差异。 位图索引对于许多类型的查询操作都是非常高效的。 它们具有以下特点:

  • 只能在表(类)中定义位图索引,这些表(类)使用系统分配的 RowID 和正整数值,或者当 IDKEY 基于类型为 %IntegerMINVAL 的单个属性时使用主键 IDKEY 来定义自定义 ID> 0,或键入 %Numeric,其中 SCALE = 0MINVAL > 0

可以使用$SYSTEM.SQL.Util.SetOption()方法 SET status=$SYSTEM.SQL.Util.SetOption("BitmapFriendlyCheck",1,.oldval) 设置一个系统范围的配置参数,在编译时检查该限制,确定是否允许在%Storage.SQL中定义位图索引。 此检查仅适用于使用%Storage.SQL的类。 默认值是0。 可以使用$SYSTEM.SQL.Util.GetOption("BitmapFriendlyCheck")来确定该选项的当前配置。

只能为使用默认(%Storage.Persistent)结构的表定义位图索引。 具有复合键的表,例如子表,不能使用位图索引。 如果使用DDL(而不是使用类定义)来创建表,那么它就满足了这个要求,并且可以使用位图索引。

  • 位图索引应该只在可能的不同字段值的数量有限且相对较小的情况下使用。 例如,对于性别、国籍或时区字段,位图索引是一个很好的选择。 位图不应该在具有UNIQUE约束的字段上使用。 如果一个字段可以有超过10,000个不同的值,或者多个索引字段可以有超过10,000个不同的值,那么就不应该使用位图。
  • 位图索引在WHERE子句中与逻辑ANDOR操作结合使用时非常有效。 如果两个或多个字段通常被组合查询,那么为这些字段定义位图索引可能是有利的。

BITMAPEXTENT关键字

位图区段索引是表本身的位图索引。 SQL使用这个索引来提高COUNT(*)的性能,它返回表中记录(行)的数量。 一个表最多可以有一个位图扩展索引。 创建多个位图区段索引将导致一个带有%msgSQLCODE -400错误ERROR #5445: Multiple Extent indices defined: DDLBEIndex

所有使用CREATE TABLE定义的表都会自动定义位图范围索引。 这个自动生成的索引被分配为索引名称DDLBEIndexSQL MapName %%DDLBEIndex。 定义为类的表可以有位图范围索引,索引名称和SQL MapName$ClassName

可以使用CREATE BITMAPEXTENT INDEX向表中添加位图区段索引,或者重命名自动生成的位图区段索引。 指定的index-name应该是表的table-name对应的类名。 这将成为索引的SQL MapName。 不能指定字段名或WITH DATA子句。

以下示例使用索引名DDLBEIndexSQL MapName Patient创建位图区索引。如果Sample.Patient已具有%%DDLBEIndex位图区索引,则此示例将该索引重命名为SQL MapName Patient

  &sql(CREATE BITMAPEXTENT INDEX Patient ON TABLE Sample.Patient)
  WRITE !,"SQL code: ",SQLCODE

BITSLICE 关键字

使用BITSLICE关键字,可以指定此索引将是位片索引。位片索引专门用于计算中使用的数字数据。位片索引将每个数值数据值表示为二进制位串。位片索引不是使用布尔标志来索引数值数据值(就像在位图索引中那样),而是为每个数值创建一个位串,为每个记录创建一个单独的位串。这是一种高度专门化的索引类型,应该仅用于快速聚合计算。例如,以下内容将是位切片索引的候选对象:

SELECT SUM(Salary) FROM Sample.Employee

可以为字符串数据字段创建位片索引,但位片索引将这些数据值表示为规范数字。换句话说,任何非数字字符串(如“abc”)都将被索引为0。这种类型的位片索引可用于快速计数具有字符串字段值的记录,而不计算那些为空的记录。

不应在WHERE子句中使用位片索引,因为SQL查询优化器不使用位片索引。

使用INSERTUPDATEDELETE操作填充和维护位片索引比使用位图索引或常规索引慢得多。在频繁更新的字段上使用多个位片索引和/或使用位片索引可能具有显著的性能代价。

位片索引只能用于系统分配的行ID为正整数值的记录。位片索引只能用于单个字段名。不能指定WITH DATA子句。

重建索引

使用CREATE INDEX语句创建索引会自动构建索引。但是,在某些情况下,可能希望显式重新生成索引。

注意:如果其他用户正在访问表的数据,则在重建索引时必须采取其他步骤。如果不这样做,可能会导致查询结果不准确。有关更多详细信息,请参阅在活动系统上构建索引。

可以按如下方式构建/重新构建索引:

  • 使用构建索引SQL命令。
  • 使用管理门户重建指定类(表)的所有索引。
  • 使用%BuildIndices()方法。

要重建非活动表的所有索引,请执行以下操作:

  SET status = ##class(myschema.mytable).%BuildIndices()

默认情况下,此命令在重建索引之前清除索引。可以覆盖此清除默认值,并使用%PurgeIndices()方法显式清除指定的索引。如果对一定范围的ID值调用%BuildIndices(),则默认情况下 IRIS不会清除索引。

还可以清除/重建指定的索引:

  SET status = ##class(myschema.mytable).%BuildIndices($ListBuild("NameIDX","SpouseIDX"))

如上所述,如果索引损坏,可能需要清除/重建索引,或者更改索引的区分大小写。要重新压缩位图索引,请使用%SYS.Maint.Bitmap方法,而不是清除/重建。

示例

下面的嵌入式SQL示例创建了一个名为Fred的表,然后在Fred表的LastwordFirstword字段上创建了一个名为“FredIndex”的索引(通过从提供的名称“Fred_Index”中去掉标点)。

ClassMethod CreateIndex()
{
	&sql(
		CREATE TABLE Fred 
		(
			TESTNUM     INT NOT NULL,
			FIRSTWORD   CHAR (30) NOT NULL,
			LASTWORD    CHAR (30) NOT NULL,
			CONSTRAINT FredPK PRIMARY KEY (TESTNUM)
		)
	)
	if SQLCODE = 0 { 
		w !,"表创建" 
	} elseif SQLCODE = -201 { 
		w !,"表已经存在" 
	} else { 
		w !,"SQL表创建错误代码: ",SQLCODE
		q 
	}
	&sql(
		CREATE INDEX Fred_Index
		ON TABLE Fred
		(
			LASTWORD,FIRSTWORD
		)
	)
	if SQLCODE = -324 {
		w !,"索引已经存在" 
		q 
	} elseif SQLCODE = 0 { 
		w !,"创建索引" 
	} else { 
		w !,"SQL索引创建错误代码是: ",SQLCODE 
		q 
	}
}

下面的示例在Staff表的City字段上创建了一个名为“CityIndex”的索引:

CREATE INDEX CityIndex ON Staff (City)

下面的示例在Staff表的EmpName字段上创建了一个名为“EmpIndex”的索引。 UNIQUE约束用来避免在字段中有相同值的行:

CREATE UNIQUE INDEX EmpIndex ON TABLE Staff (EmpName)

下面的示例在Purchases表的SKU字段上创建一个名为“SKUIndex”的位图索引。 BITMAP关键字表示这是位图索引:

CREATE BITMAP INDEX SKUIndex ON TABLE Purchases (SKU)
0
0 152
文章 姚 鑫 · 九月 4, 2021 9m read

第六章 SQL命令 CREATE INDEX(一)

为表创建索引。

大纲

CREATE [UNIQUE | BITMAP | BITMAPEXTENT | BITSLICE ] INDEX index-name
        ON [TABLE] table-name
       (field-name, ...)
       [AS index-class-name [ (parameter-name = parameter_value, ... ) ] ]
       [WITH DATA  (datafield-name, ...)]

参数

  • UNIQUE - 可选的——一个约束,确保表中不会有两行索引中所有字段的值相同。不能为位图或位片索引指定此关键字。UNIQUE关键字后面可以跟(或被)CLUSTEREDNONCLUSTERED关键字替换。 这些关键字是no-ops; 它们是为了与其他供应商兼容而提供的。
  • BITMAP - 可选—表示创建位图索引。 位图索引允许对具有少量不同值的字段进行快速查询。
  • BITMAPEXTENT - 可选-表示应该创建位映射范围索引。 一个表最多只能创建一个位映射范围索引。 BITMAPEXTENT中没有指定字段名。
  • BITSLICE - 可选—创建位片索引。 位片索引可以非常快速地计算某些表达式,比如求和和范围条件。 这是一种专门化的索引类型,只能用于解决非常特定的问题。
  • index-name - 定义的索引。名称是一个标识符。
  • table-name - 为其定义索引的现有表的名称。不能为视图创建索引。表名可以是限定的(schema.table),也可以是非限定的(Table)。未限定的表名采用缺省模式名。
  • field-name - 用作索引基础的一个或多个字段名。字段名必须用括号括起来。多个字段名称用逗号分隔。每个字段名后面可以跟一个ASCDESC关键字。这些关键词是禁止操作;提供它们是为了与其他供应商兼容。
  • AS index-class-name - 可选—定义索引的类,可选地后跟圆括号,圆括号中包含一对或多对逗号分隔的参数名称和关联值。
  • WITH DATA (datafield-name) - 可选—要定义为索引数据属性的一个或多个字段名。字段名必须用括号括起来。多个字段名称用逗号分隔。指定位图或位片索引时,不能指定WITH DATA子句。

描述

CREATE INDEX在命名表的指定字段(或多个字段)上创建排序索引。IRIS使用索引来提高查询操作的性能。 IRIS在INSERTUPDATEDELETE操作期间自动维护索引,这种索引维护可能会对这些数据修改操作的性能产生负面影响。

可以使用CREATE INDEX命令或通过将索引定义添加到类定义来创建索引,可以使用DROP INDEX命令删除索引。

CREATE INDEX可用于创建以下三种类型的索引中的任何一种:

  • 常规索引(Type=index):指定CREATE INDEX(用于非唯一值)或CREATE UNIQUE INDEX(用于唯一值)。
  • 位图索引(Type=bitmap):指定CREATE bitmap index
  • bitslice索引(Type=bitslice):指定CREATE bitslice index

还可以使用%Dictionary.IndexDefinition类定义索引。

可以使用CREATE INDEX向分片表添加索引。

权限与锁

CREATE INDEX命令属于特权操作。 用户必须具有%ALTER_TABLE管理权限才能执行CREATE INDEX。 如果不这样做,将导致%msgSQLCODE -99错误,User 'name' does not have %ALTER_TABLE privileges。 如果拥有适当的权限,可以使用GRANT命令为用户或角色分配%ALTER_TABLE权限。 管理权限是特定于名称空间的。

用户必须对指定的表具有%ALTER权限。 如果用户是表的Owner(创建者),则自动授予该用户对该表的%ALTER权限。 否则,用户必须被授予%ALTER权限。 如果不这样做,将导致一个SQLCODE -99错误,其中%msgUser 'name' does not have required %ALTER privilege needed to change the table definition for 'Schema.TableName'。 可以通过调用%CHECKPRIV命令来确定当前用户是否具有%ALTER权限。 可以使用GRANT命令为指定的表分配%ALTER权限。

  • 不能在从持久类投射的表上使用CREATE INDEX,除非表类定义包含[DdlAllowed]。 否则,操作将失败,出现SQLCODE -300错误,并且%msg DDL not enabled for class 'Schema.tablename'
  • 不能在部署的持久类映射的表上使用CREATE INDEX。 此操作失败,出现SQLCODE -400错误,并显示%msg Unable to execute DDL that modifies a deployed class: 'classname'

CREATE INDEX语句获取表名上的表级锁。 这可以防止其他进程修改表的数据。 这个锁在CREATE INDEX操作结束时自动释放。 CREATE INDEX在相应的类定义上维护一个锁,直到创建索引操作(包括索引数据的填充)完成。

在创建索引时,表不能被其他进程以EXCLUSIVE模式或SHARE模式锁定。 在被锁定的表上尝试CREATE INDEX操作会导致SQLCODE -110错误,并带有%msg,如下所示: Unable to acquire exclusive table lock for table 'Sample.MyTest'

仅支持兼容性选项

SQL仅接受以下``CREATE INDEX选项用于解析目的,以帮助将现有SQL代码转换为 SQL。 这些选项不提供任何实际的功能。

CLUSTERED | NONCLUSTERED owner.catalog. ASC | DESC

下面的例子展示了这些no-op关键字的位置:

CREATE UNIQUE CLUSTERED INDEX index-name
  ON TABLE owner.catalog.schema.table
   (field1 ASC, field2 DESC)

索引名称

索引的名称在给定的表中必须是唯一的。 索引名称遵循标识符约定,受以下限制。 默认情况下,索引名是简单的标识符; 索引名称可以是分隔的标识符。 索引名不能超过128个字符。 索引名不区分大小写。

IRIS使用提供的名称(它引用为“SqlName”)在类和全局中生成相应的索引属性名称。 此索引属性名称仅包含字母和数字字符(字母和数字),长度最多为96个字符。 为了生成索引属性名, IRIS首先从提供的SqlName中去掉标点字符,然后生成一个只有96个字符(或更少)的唯一标识符来创建唯一的索引属性名。

  • 索引名称可以与字段、表或视图名称相同,但不建议重复。
  • 索引属性名(在标点剥离后)必须是唯一的。 如果指定了重复的SQL索引名,系统将生成SQLCODE -324错误。 如果指定的SQL索引名称仅在标点字符方面与现有SQL索引名称不同, IRIS将最后一个字符替换为大写字母(以“a”开头),以创建唯一的索引属性名称。 因此,可以(尽管不建议)创建仅在标点字符上不同的SQL索引名。
  • 索引属性名必须以字母开头。 因此,索引名的第一个字符或去掉初始标点字符后的第一个字符必须是字母。 有效的字母是通过$ZNAME测试的字符。 如果SQL索引名称的第一个字符是一个标点符号字符(%_),第二个字符是一个数字, IRIS会附加一个小写的“n”作为剥离后的索引属性名称的第一个字符。
  • 索引名可能比31个字符长得多,但是前31个字母数字字符不同的索引名更容易处理。

管理门户SQL接口Catalog Details显示每个索引的SQL索引名称(SQL Map name)和相应的索引属性名称(index name)。

当试图创建与现有索引同名的索引时,将会发生什么呢?

现有索引

默认情况下, IRIS拒绝创建与该表的现有索引同名的索引,并发出SQLCODE -324错误。 要确定当前设置,调用$SYSTEM.SQL.CurrentSettings(),它为现有索引设置显示一个Allow DDL CREATE INDEX for existing index。 默认值是0,这是该选项的推荐设置。 如果该选项设置为1, IRIS将从类定义中删除现有索引,然后通过执行CREATE index重新创建它。 它从CREATE index中指定的表中删除指定的索引。 此选项允许删除/重新创建UNIQUE约束索引(不能使用DROP index命令完成)。 要删除/重新创建主键索引,请参考ALTER TABLE命令。

在管理门户、系统管理、配置、SQL和对象设置、SQL中,可以通过选择“忽略冗余DDL语句”复选框,在系统范围内设置此选项(以及其他类似的创建、更改和删除选项)。

但是,即使将此选项设置为允许重新创建现有索引,如果表包含数据,则不能重新创建Primary Key IDKEY索引。 尝试这样做会产生SQLCODE -324错误。

表明

必须指定现有表的名称。

  • 如果table-name是一个不存在的表,则CREATE INDEX失败,出现SQLCODE -30错误,并将%msg设置为Table 'SQLUSER.MYTABLE' does not exist
  • 如果table-name是视图,则CREATE INDEX失败,出现SQLCODE -30错误,并将%msg设置为Attempt to CREATE INDEX 'My_Index' on view SQLUSER.MYVIEW failed. Indices only supported for tables, not views

创建索引修改表的定义; 如果没有更改表定义的权限,则CREATE INDEX将失败,出现SQLCODE -300错误,并将%msg设置为DDL not enabled for class 'schema.tablename'

字段名称

必须指定至少一个要索引的字段名。 指定一个字段名或用逗号分隔的字段名列表,这些字段名用括号括起来。 在索引定义中允许并保留重复的字段名。 指定多个字段可能会提高GROUP BY操作的性能,例如,先按州分组,然后再按每个州中的城市分组。 通常,应该避免在一个或多个具有大量重复数据的字段上建立索引。 例如,在人员数据库中,在Name字段上建立索引是合适的,因为大多数名称都是惟一的。 在State字段上建立索引(在大多数情况下)是不合适的,因为存在大量重复的数据值。 指定的字段必须在表中定义,或者在表的持久类的超类中定义。 (当然,所有的类都必须经过编译。) 指定不存在的字段将产生SQLCODE -31错误。

除了普通的数据字段,还可以使用CREATE INDEX来创建索引:

  • SERIAL字段(%Counter字段)上。
  • IDENTITY字段上。
  • 在集合的ELEMENTSKEYS值上。

不能在流值字段上创建索引。

如果一个IDKEY字段(属性)是SQL Computed,则不能创建具有多个IDKEY字段的索引。 这个限制不适用于单个字段IDKEY索引。 因为索引中的多个IDKEY字段是用“||”(双竖条)字符分隔的,所以不能在IDKEY字段数据中包含这个字符串。

嵌入对象中的字段(%SerialObject)

要在嵌入式对象中创建字段索引,需要在引用该嵌入式对象的表(%Persistent类)中创建索引。 在CREATE INDEX中,字段名指定了表(%Persistent object)中引用字段的名称(通过下划线连接到嵌入对象(%SerialObject)中的字段名),如下面的示例所示:

CREATE INDEX StateIdx ON TABLE Sample.Person (Home_State)

这里HomeSample.Person中的一个字段,它引用嵌入式对象Sample.Address,其中包含State字段。

只有那些与持久类引用属性相关联的嵌入对象记录才会被索引。不能直接索引%SerialObject属性。

索引类名

此可选语法允许用户使用SQL指定函数索引的类和参数。

SQL示例如下:

CREATE INDEX HistIdx ON TABLE Sample.Person (MedicalHistory) AS %iFind.Index.Basic (LANGUAGE='en', LOWER=1)

WITH DATA子句

指定此子句可能允许仅通过读取索引来解析查询,这将极大地减少磁盘I/O量,从而提高性能。

如果field-name使用字符串排序,你应该在field-nameWITH DATA datfield -name中指定相同的字段; 这允许检索未排序的值,而不必转到主映射。 如果field-name中的值不使用字符串排序规则,那么在WITH DATA datfield -name中指定该字段没有任何好处。

可以在WITH DATA datfield -name中指定未被索引的字段。 这允许从索引中满足更多的查询,而不需要访问主映射。 权衡是你想维护多少个索引; 而向索引中添加数据会使索引变得更大,这会减慢不需要数据的操作。

可以在WITH DATA DATA -name中指定在表的持久化类的超类中定义的字段。

0
0 103
文章 姚 鑫 · 九月 3, 2021 3m read

第五章 SQL命令 BUILD INDEX

用数据填充一个或多个索引。

大纲

BUILD INDEX [%NOLOCK] [%NOJOURN] FOR TABLE table-name [INDEX index-name [,index-name]]

BUILD INDEX [%NOLOCK] [%NOJOURN] FOR SCHEMA schema-name

BUILD INDEX [%NOLOCK] [%NOJOURN] FOR ALL

参数

  • FOR TABLE table-name - 已存在表的名称。 表名可以是限定的(schema.table),也可以是非限定的(table)。 非限定表名接受默认模式名。
  • INDEX index-name - 可选—索引名称或以逗号分隔的索引名称列表。 如果指定,则只构建这些索引。 如果未指定,将构建为表定义的所有索引。
  • FOR SCHEMA schema-name - 现有模式的名称。 此命令为指定模式中的所有表构建所有索引。

描述

BUILD INDEX提供了三种语法形式来构建/重新构建所有已定义的索引:

  • Table: BUILD INDEX FOR TABLE table-name. 可选的INDEX子句允许仅构建/重新构建指定的索引。
  • 模式中的所有表:BUILD INDEX FOR schema schema-name
  • 当前命名空间中的所有表:BUILD INDEX FOR All

可能出于以下任何原因希望构建索引:

  • 已经使用CREATE INDEX向已经包含数据的表添加了一个或多个索引。
  • 已经使用%NOINDEX选项对表执行了INSERTUPDATEDELETE操作,而不是接受将每个操作写入索引的性能开销。

在这两种情况下,都可以使用BUILD INDEX用数据填充这些索引。

BUILD INDEX将修改的表的数量作为受影响的行数返回。

权限

BUILD INDEX命令是一个特权操作。 用户必须具有%BUILD_INDEX管理权限才能执行BUILD INDEX。 如果不这样做,会出现一个带有%msgSQLCODE -99错误,User 'name' does not have %BUILD_INDEX privileges。 如果拥有适当的授予权限,可以使用GRANT命令将%BUILD_INDEX权限分配给用户或角色。 管理权限是特定于名称空间的。

用户必须对指定的表具有SELECT权限。 如果用户是该表的Owner(创建者),则自动授予该用户对该表的SELECT权限。 否则,用户必须被授予该表的SELECT权限。

  • 在指定的表上执行BUILD INDEX FOR TABLE而没有SELECT权限将导致SQLCODE -30错误,并且%msg Table 'name' not found
  • 发出BUILD INDEX FOR SCHEMA只会为用户具有SELECT权限的表构建索引。 如果用户对模式中的任何表都没有SELECT权限,则该命令完成时不会出现错误,不会影响0行。

可以通过调用%CHECKPRIV命令来确定当前用户是否具有SELECT权限。 可以使用GRANT命令为指定的表分配SELECT权限。

锁和日志

默认情况下,BUILD INDEX语句会在构建索引之前获取每个表的范围锁。这可以防止其他进程修改表的数据。该锁在BUILD INDEX操作结束时自动释放。可以指定%NOLOCK来防止表锁定。

默认情况下,BUILD INDEX语句使用当前进程的日志记录设置。可以指定% NOJOURN来阻止日志记录。

错误代码

  • 如果指定的表名不存在, IRIS会发出一个SQLCODE -30错误并将%msg设置为 Table 'sample.tname' does not exist。如果指定的是视图而不是表,或者指定的表没有SELECT权限,则会返回此错误消息。
  • 如果指定的索引名不存在,IRIS会发出SQLCODE -400错误并将%msg设置为 ERROR #5066: Index name 'sample.tname::badindex' is invalid
  • 如果指定的索引名不存在,IRIS会发出SQLCODE -400错误并将%msg设置为Schema 'sample' not found
0
0 106