0 关注者 · 478 帖子

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

文章 姚 鑫 · 十一月 1, 2021 8m read

第六十三章 SQL命令 ORDER BY(一)

指定结果集中行排序的SELECT子句。

大纲

ORDER BY ordering-item [ASC | DESC]{,ordering-item [ASC | DESC] ...}

参数

  • ordering-item - 决定排序顺序的文字。 列名、列别名或列号。 ORDER BY子句可以包含单个排序项或以逗号分隔的排序项列表,以指定排序层次结构。
  • ASC DESC - 可选-按升序(ASC)或降序(DESC)排序。 默认为升序。

描述

ORDER BY子句根据指定列的数据值或以逗号分隔的列序列对查询结果集中的记录进行排序。 该语句对单个结果集进行操作,这些结果集要么来自SELECT语句,要么来自多个SELECT语句的UNION

ORDER BY按逻辑(内部存储)数据值对记录进行排序,而不考虑当前的选择模式设置。

ORDER BY子句是SELECT语句中的最后一个子句。 它出现在FROMWHEREGROUP BYHAVING子句之后。 以错误的顺序指定SELECT子句将产生SQLCODE -25错误。

如果SELECT语句没有指定ORDER BY子句,则返回的记录顺序是不可预测的。

如果SELECT语句指定了ORDER BYTOP子句,则作为“TOP”行的返回的记录将与ORDER BY子句中指定的顺序一致。 为例。 SELECT TOP 5 Name,Age FROM MyTable ORDER BY Age DESC返回MyTable中年龄最大的5行数据,按从老到小的顺序排列。

SELECT列表中执行窗口函数(包括窗口函数自己的ORDER BY子句)之后应用ORDER BY子句。 因此,窗口函数返回的值不受SELECT查询的ORDER by子句的影响。

限制

如果SELECT查询指定了ORDER BY子句,则生成的数据是不可更新的。 因此,如果指定后续的DECLARE CURSOR FOR UPDATE语句,则忽略FOR UPDATE子句,并将游标声明为只读。

如果ORDER BY应用于UNION,则排序项必须是一个数字或简单列名。 它不可能是一个表达式。 如果使用列名,它将引用在UNION的第一个SELECT列表中命名的结果列。

在子查询中使用ORDER BY子句时,必须与TOP子句配对。 这可能是TOP ALL子句。 例如,下面的FROM子句子查询无效:(SELECT DISTINCT age FROM table1 ORDER BY age); 但是,下面的FROM子句子查询是有效的:(SELECT DISTINCT TOP ALL age FROM table1 ORDER BY age)。

指定列排序

可以指定要排序的单个列,也可以指定多个列作为逗号分隔的列表。 排序由第一个列出的列完成,然后在该列中由第二个列出的列完成,以此类推。

列可以通过列名、列别名或列号指定。

ORDER BY不限于字段值。

  • 无论字段是否在SELECT列表中指定,都可以通过列名将字段指定为排序项。
  • 可以将表达式指定为排序项,例如ORDER BY LENGTH(Name)
  • 可以将窗口函数指定为排序项,例如ORDER BY ROW_NUMBER() OVER (PARTITION BY State)
  • 不能在ORDER BY子句中直接指定聚合函数; 尝试这样做会产生SQLCODE -73错误。

可以在ORDER BY子句中根据列别名或列号指定任何选择项,包括聚合函数、窗口函数或表达式。 如果在SELECT列表中没有指定列别名,则在指定聚合函数、窗口函数或表达式时,使用选择项列号(例如3),而不是默认的列名(例如Aggregate_3)。

ORDER BY子句可以指定列名、列别名和选择项列号的任意组合。如果ordering-item的第一个字符是数字,则 IRIS假定指定的是列号。否则,假定使用列名或列别名。请注意,列名和列别名不区分大小写。

除了少数例外,订货项必须指定为字面量。 如果一个排序项不能被解析为有效的标识符(列名或列名),或者不能被解析为无符号整数(列号),那么该排序项将被忽略,ORDER BY执行将继续执行逗号分隔列表中的下一个排序项。 一些被忽略的订单项值的例子是动态SQL ? 输入参数或嵌入式SQL:var主机变量、子查询、解析为数字、带符号的数字或括号中的数字的表达式。

列名

可以将列名指定为文字。 在某些情况下,对列名进行操作的表达式可以用作排序项。 不能使用将列名作为字符串提供的变量或其他表达式。

下面的ORDER BY子句按列名排序:

SELECT Name,Home_State,DOB
FROM Sample.Person
ORDER BY Home_State,Name

无论排序列是否在选择项列表中,都可以按列名排序。 (由于明显的原因,不能按列别名或列号排序,除非排序列位于选择项列表中。) 下面的示例以与前一个示例相同的顺序返回相同的记录:

SELECT Name,DOB
FROM Sample.Person
ORDER BY Home_State,Name

如果order -item不是指定表中现有的列名(或列别名),则会发出SQLCODE -29错误。

即使RowID是私有的并且没有在选择项列表中列出,也可以按RowID值排序。 应该指定%ID伪列名作为ordering-item,而不是实际的RowID字段名。 如果查询包含TOP子句,则按RowID排序更改TOP子句选择的行。 例如,如果一个表有100行(RowIDs是连续的),SELECT TOP 5% ID FROM table ORDER BY %ID返回RowIDs 1, 2, 3, 4, 5; SELECT TOP 5 %ID FROM Table ORDER BY %ID DESC返回RowIDs 100, 99, 98, 97, 96

ORDER BY子句可以指定表名或表别名作为ordering-item的一部分:

SELECT P.Name AS People,E.Name As Employees
FROM Sample.Person AS P,Sample.Employee AS E 
ORDER BY P.Name

ORDER BY子句可以使用箭头语法(- >)操作符在非基表的表中指定一个字段:

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

列别名

列别名必须指定为文字。 不能在表达式中指定列别名,也不能使用变量提供它。

下面的ORDER BY子句按列别名排序:

SELECT Name,Home_State AS HS,DOB
FROM Sample.Person
ORDER BY HS,Name

列别名可以与列名相同(尽管不建议这样做)。 如果提供了列别名,ORDER BY首先引用列别名,然后引用任何没有别名的列名。 如果列别名和非别名列名之间存在歧义,ORDER BY子句将生成SQLCODE -24错误。 但是,如果列别名与别名列名相同,这种明显的歧义不会生成错误,但可能会产生意想不到的结果。 下面的例子显示了这一点:

SELECT Name AS Moniker,Home_City AS Name
FROM Sample.Person
ORDER BY Name

可以使用列别名按选择项列表中的表达式进行排序,如下面的示例所示:

SELECT Name,Age,$PIECE(AVG(Age)-Age,'.',1) AS AgeDev
FROM Sample.Employee ORDER BY AgeDev,Name

不能指定一个非字段的列名默认值,比如Expression_3; 相反,指定选择项列号(在本例中为3),或者最好为该选择项指定列别名。

Column Number

列号必须指定为无符号数字字面值。 不能将列号指定为变量或表达式的结果。 不能将列号括在括号中。 整数截断规则用于将非整数值解析为整数; 例如,1.99解析为1

下面的ORDER BY子句按列号(检索列的数字序列,如SELECT选择项列表中指定的)排序:

SELECT Name,Home_State,DOB
FROM Sample.Person
ORDER BY 2,1

列号指的是SELECT子句列表中的位置。 它们不指向表本身中列的位置。 但是,可以按列号对SELECT *结果进行排序; 如果RowID是公共的,它就被计算为第1列,如果RowID是隐藏的,它就不被计算为第1列。

ORDER BY中指定与SELECT列表列不对应的列号将导致SQLCODE -5错误。 ORDER BY 0导致SQLCODE -5错误。

可以使用列号按选择项列表中的表达式进行排序,如下面的示例所示:

SELECT Name,Age,$PIECE(AVG(Age)-Age,'.',1)
FROM Sample.Employee ORDER BY 3,Name

指定排序

排序是按照排序顺序进行的。 默认情况下,字符串值的排序是根据创建时为order -item字段指定的排序规则进行的。 IRIS对每个名称空间都有一个默认的字符串排序规则; 字符串数据类型字段的初始排序规则默认值是SQLUPPER,不区分大小写。 因此,ORDER BY排序通常不区分大小写。

数字数据类型字段的排序是基于数字排序规则完成的。 对于表达式,默认排序规则是EXACT

通过对排序项字段名应用排序规则函数,可以覆盖字段的默认排序规则。 例如,ORDER BY %EXACT(Name)。 不能对列别名应用排序规则函数; 尝试这样做会产生SQLCODE -29错误。

默认升序排序顺序认为NULL是最小值,后面跟着空字符串(")。 ORDER BY不区分空字符串和仅由空格组成的字符串。

如果为列指定的排序规则是字母数字的,则前导数字将按字符排序顺序而不是整数顺序排序。 可以使用%PLUS排序函数按整数顺序排序。 但是,%PLUS排序函数将所有非数字字符视为0

因此,要正确地以数字序列对混合数字字符串进行排序,需要多个排序项。 例如,在Sample中。 街道地址由一个整数门牌号和街道名之间用一个空格隔开。 街道名由两个部分组成,中间用一个空格隔开。 比较下面两个例子。 第一个示例按字符排序顺序对街道地址进行排序:

SELECT Name,Home_Street FROM Sample.Person
ORDER BY Home_Street

第二个示例按整数顺序对房屋编号进行排序,按字符排序顺序对街道名称进行排序:

SELECT Name,Home_Street FROM Sample.Person
ORDER BY $PIECE(%PLUS(Home_Street),' ',1),$PIECE(Home_Street,' ',2),$PIECE(Home_Street,' ',3)

请注意,此示例仅适用于列名,而不适用于列别名或列编号。

ASC和DESC

可以按升序或降序排序顺序为每一列指定排序,由列标识符后面的可选ASC(升序)或DESC(降序)关键字指定。 如果未指定ASCDESC,则ORDER BY按升序对该列进行排序。 你不能指定ASCDESC关键字使用动态SQL ? 输入参数或嵌入式SQL:var主机变量。

NULL总是ASC序列中的最低值和DESC序列中的最高值。

多个逗号分隔的ORDER BY值指定排序操作的层次结构,如下面的示例所示:

SELECT A,B,C,M,E,X,J
FROM LetterTable
ORDER BY 3,7 DESC,1 ASC

本例将SELECT子句列表中第三个列表项(C)的数据值按升序排序; 在这个序列中,它按降序对第7个列出的项(J)值进行排序; 在其中,它按升序对第一个列出的项(A)值进行排序。

ORDER BY值列表中的重复列不起作用。 这是因为第二种排序在第一种排序的顺序之内。 例如,ORDER BY Name ASCName DESC按升序对Name列进行排序。

NLS排序

如果指定了非默认的NLS排序规则,则必须确保所有排序规则都对齐并使用完全相同的国家排序规则序列。 这不仅包括表使用的全局变量,还包括临时文件(如IRISTEMPprocess-private globals)中用于索引的全局变量。

0
0 142
文章 姚 鑫 · 十月 31, 2021 2m read

第六十二章 SQL命令 OPEN

打开游标。

大纲

OPEN cursor-name

参数

  • cursor-name - 游标的名称,已经声明过了。 游标名称是在DECLARE语句中指定的。 游标名称区分大小写。

描述

OPEN语句根据游标的DECLARE语句中指定的参数打开游标。 一旦打开,就可以获取游标。 打开的游标必须关闭。

  • 试图打开未声明的游标会出现SQLCODE -52错误。
  • 试图打开已经打开的游标会导致SQLCODE -101错误。
  • 试图获取或关闭未打开的游标将导致SQLCODE -102错误。

成功的OPEN设置SQLCODE = 0,即使结果集是空的。

作为SQL语句,这只在嵌入式SQL中支持。 通过ODBC使用ODBC API支持等价的操作。

示例

下面的嵌入式SQL示例显示了一个正在打开和关闭的游标(名为EmpCursor):

ClassMethod Open()
{
	s name = "LastName,FirstName",state = "##"
	&sql(
		DECLARE EmpCursorO CURSOR FOR 
			SELECT Name, Home_State
			INTO :name, :state FROM Sample.Person
			WHERE Home_State %STARTSWITH 'A'
	)
	w !,"BEFORE: Name=",name," State=",state 
	&sql(OPEN EmpCursorO)
	if SQLCODE '= 0 { 
		w "打开错误: ",SQLCODE
	    q 
	}
	n %ROWCOUNT,%ROWID
	for { 
		&sql(FETCH EmpCursorO)
		q:SQLCODE  
		w !,"DURING: Name=",name," State=",state 
	}
	w !,"FETCH status SQLCODE=",SQLCODE
	w !,"获取的行数 = ",%ROWCOUNT
	&sql(CLOSE EmpCursorO)
	w !,"AFTER: Name=",name," State=",state
}
DHC-APP>d ##class(PHA.TEST.SQLCommand).Open()
 
BEFORE: Name=LastName,FirstName State=##
DURING: Name=Quixote,Terry J. State=AR
DURING: Name=Xerxes,Angelo P. State=AL
DURING: Name=Gore,Alfred M. State=AZ
DURING: Name=Ahmed,Elmo X. State=AZ
DURING: Name=Press,Juanita Q. State=AZ
DURING: Name=DeLillo,Olga A. State=AR
DURING: Name=Klein,Richard K. State=AL
DURING: Name=Lopez,Ralph W. State=AZ
DURING: Name=Yu,Edward Y. State=AR
DURING: Name=Underman,Maria P. State=AL
DURING: Name=Vanzetti,Alexandra O. State=AL
DURING: Name=Koivu,Joshua J. State=AR
FETCH status SQLCODE=100
获取的行数 = 12
AFTER: Name=Koivu,Joshua J. State=OH
0
0 148
文章 姚 鑫 · 十月 30, 2021 5m read

第六十一章 SQL命令 LOCK

锁表

大纲

LOCK [TABLE] tablename IN EXCLUSIVE MODE [WAIT seconds]

LOCK [TABLE] tablename IN SHARE MODE [WAIT seconds]

参数

  • tablename - 要锁定的表的名称。 Tablename必须是已经存在的表。 表名可以是限定的(schema.table),也可以是非限定的(table)。 非限定表名接受默认模式名。 模式搜索路径被忽略。
  • IN EXCLUSIVE MODE / IN SHARE MODE - IN EXCLUSIVE MODE关键字短语创建一个常规的IRIS锁。 IN SHARE MODE关键字短语创建一个共享的IRIS锁。
  • WAIT seconds - 可选-一个整数,指定在超时前尝试获取锁的秒数。 如果省略,则应用系统默认超时时间。

描述

LOCKLOCK TABLE是同义词。

LOCK命令显式锁定SQL表。 此表必须是已存在的表,对其具有必要的特权。 如果tablename是一个不存在的表,LOCK会失败并出现编译错误。 如果tablename是临时表,则命令执行成功,但不执行任何操作。 如果tablename是视图,则命令失败,并出现SQLCODE -400错误。

UNLOCK命令用来反转LOCK操作。 显式LOCK将保持有效,直到针对同一模式发出显式UNLOCK,或者直到进程终止。

可以使用LOCK多次锁定一个表; 必须显式解锁表,解锁次数为表被显式锁定的次数。 每个UNLOCK必须指定与相应LOCK相同的模式。

权限

LOCK命令是一个特权操作。 在使用LOCK IN SHARE MODE之前,进程必须对指定的表拥有SELECT特权。 在使用LOCK IN EXCLUSIVE MODE之前,进程必须对指定的表拥有INSERTUPDATEDELETE特权。 对于IN EXCLUSIVE MODE, INSERTUPDATE特权必须在表的至少一个字段上。 未能持有足够的特权将导致SQLCODE -99错误(特权违反)。 可以通过调用%CHECKPRIV命令来确定当前用户是否具有必要的特权。 可以通过调用$SYSTEM.SQL.Security.CheckPrivilege()方法来确定指定的用户是否具有必要的特权。

这些特权是获取锁所必需的; 它们没有定义锁的性质。 IN EXCLUSIVE MODE锁阻止其他进程执行INSERTUPDATEDELETE操作,而不管锁持有者是否拥有相应的特权。

锁模式

LOCK支持SHAREEXCLUSIVE两种模式。 这些锁模式是相互独立的。 可以对同一个表应用SHARE锁和EXCLUSIVE锁。 EXCLUSIVE模式下的锁只能通过EXCLUSIVE模式下的UNLOCK解锁。 “SHARE”模式下的锁只能通过“UNLOCK”解锁。

  • LOCK mytable IN SHARE MODE可以防止其他进程对mytable发出EXCLUSIVE锁,或者调用DDL操作,比如DROP TABLE
  • LOCK mytable IN EXCLUSIVE MODE可以防止其他进程对mytable发出EXCLUSIVE锁或SHARE锁,也可以防止其他进程对mytable执行插入、更新或删除操作,或者调用DDL操作(如DROP TABLE)。

LOCK允许对表的读访问。 这两种LOCK模式都不能阻止其他进程在READ UNCOMMITTED模式(默认的SELECT模式)下对表执行SELECT操作。

锁冲突

  • 如果一个表在EXCLUSIVE模式下已经被其他用户锁定,那么在任何模式下都不能锁定它。
  • 如果一个表在SHARE模式下已经被其他用户锁定,也可以在SHARE模式下锁定该表,但不能在EXCLUSIVE模式下锁定该表。

这些锁冲突产生SQLCODE -110错误,并生成%msg,如下所示:

锁超时

LOCK尝试获取指定的SQL表锁,直到超时。 当超时发生时,LOCK生成SQLCODE -110错误。

  • 如果指定了WAIT秒数,SQL表锁定超时将在该秒数过后发生。
  • 否则,当当前进程的SQL超时结束时,SQL表锁定超时发生。 可以使用$SYSTEM.SQL.Util.SetOption()方法的ProcessLockTimeout选项为当前进程设置锁定超时。 还可以使用带有LOCK_TIMEOUT选项的SQL命令set OPTION为当前进程设置锁定超时。 (SET OPTION不能从SQL Shell中使用。) 当前进程的SQL锁定超时默认为系统范围的SQL锁定超时。
  • 否则,SQL表锁定超时发生时,系统范围的SQL超时。系统范围的缺省值是10秒。设置全系统锁定超时时间有两种方式:
    • 使用$SYSTEM.SQL.Util.SetOption()方法的LockTimeout选项。 这将立即更改新进程的系统范围锁定超时默认值,并将当前进程的ProcessLockTimeout重置为这个新的系统范围值。 设置系统范围的锁超时对当前运行的其他进程的ProcessLockTimeout设置没有影响。
    • 使用管理门户,选择系统管理、配置、SQL和对象设置、SQL。 查看和编辑当前的锁定超时(秒)设置。 这将更改在保存配置更改后启动的新进程的系统范围锁定超时默认值。 它对当前运行的进程没有影响。

要返回当前系统范围的锁超时值,调用$SYSTEM.SQL.Util.GetOption("LockTimeout")方法。

要返回当前进程的锁超时值,请调用$SYSTEM.SQL.Util.GetOption("ProcessLockTimeout")方法。

事务处理

LOCK操作不是事务的一部分。 回滚发出LOCK的事务不会释放锁。 UNLOCK可以定义为在当前事务结束时发生,或者立即发生。

其他锁定操作

许多DDL操作,包括ALTER TABLEDELETE TABLE,都需要独占表锁。

INSERTUPDATEDELETE命令也执行锁定。 默认情况下,它们在当前事务期间锁定在记录级别; 如果其中一个命令锁定了足够多的记录(默认设置为1000),那么锁将自动提升为表锁。 LOCK命令允许显式地设置表级锁,使能够更好地控制数据资源的锁。 INSERTUPDATEDELETE可以通过指定%NOLOCK关键字来覆盖LOCK

带有LOCK_TIMEOUT选项的SQL SET OPTION设置当前进程的INSERTUPDATEDELETESELECT操作的超时时间。

SQL支持$SYSTEM.SQL.Util.SetOption()方法的CachedQueryLockTimeout选项。

示例

下面的嵌入式SQL示例创建一个表,然后锁定它:

ClassMethod Lock()
{
	n SQLCODE,%msg
	&sql(
		CREATE TABLE mytest (
			ID NUMBER(12,0) NOT NULL,
			CREATE_DATE DATE DEFAULT CURRENT_TIMESTAMP(2),
			WORK_START DATE DEFAULT SYSDATE
		) 
	)
	if SQLCODE = 0 { 
		w !,"表创建" 
	} elseif SQLCODE = -201 { 
		w !,"表已经存在" 
	} else { 
		w !,"SQL表创建错误代码: ",SQLCODE
		q 
	}
}
ClassMethod Lock1()
{
	n SQLCODE,%msg
	s x = $zh
	&sql(
		LOCK mytest IN EXCLUSIVE MODE WAIT 4
	) 
	if SQLCODE = 0 { 
		w !,"表锁" 
	} elseif SQLCODE = -110 { 
		w "等待 ",$ZHOROLOG - x," 秒"
		w !,"表被另一个进程锁定",!,%msg 
	} else { 
		w !,"错误: ",SQLCODE,!,%msg 
	}
}

从管理门户运行的SQL程序生成一个进程,该进程在程序执行时立即终止。 因此,锁几乎立即被释放。 因此,要观察锁冲突,首先在运行相同名称空间中的SQL Shell的终端中发出lock mytest IN EXCLUSIVE MODE命令。 然后运行上面的嵌入式SQL锁定程序。 在排他模式下从终端SQL Shell发出一个UNLOCK mytest。 然后重新运行上面的嵌入式SQL锁定程序。

0
0 171
文章 姚 鑫 · 十月 29, 2021 5m read

第六十章 SQL命令 JOIN(二)

单向外部联接

IRIS支持单向外部联接:左外部联接和右外部联接。

使用标准的“inner”联接时,当一个表的行链接到第二个表的行时,第一个表中找不到第二个表中对应行的行将从输出表中排除。

使用单向外联接时,即使第二个表中没有匹配项,第一个表中的所有行也会包括在输出表中。使用单向外连接,第一个表会从第二个表中取出相关信息,但不会因为第二个表中缺少匹配项而牺牲自己的行。

例如,如果查询首先列出Table1并创建一个左外部联接,那么它应该能够看到Table1中的所有行,即使它们在Table2中没有对应的记录。

在指定单向外联接时,在FROM子句中命名表的顺序非常重要。对于左外部联接,指定的第一个表是联接的源表。对于右外部联接,指定的第二个表是联接的源表。因此,%INORDER%STARTTABLE优化关键字不能与RIGHT OUTER JOIN一起使用。以下语法相互矛盾,导致SQLCODE-34错误:FROM%INORDER TABLE1 RIGHT OUTER JOIN TABLE2 ON....

外部联接语法

IRIS支持两种表示外连接的格式:

  1. ANSI标准语法:LEFT OUTER JOINRIGHT OUTER JOIN。SQL标准语法将外联接放在SELECT语句的FROM子句中,而不是WHERE子句中,如下例所示:
FROM tbl1 LEFT OUTER JOIN tbl2 ON (tbl1.key = tbl2.key) 
  1. ODBC规范外部联接扩展语法,使用转义语法{OJ Join-Expression},其中Join-Expression是任何ANSI标准联接语法。

Null填充

单向外联接执行空值填充。这意味着,如果源表的某一行的合并列具有空值,则会为非源表中的相应字段返回空值。

左外部联接条件由以下语法表示:

A LEFT OUTER JOIN B ON A.x=B.y

这指定返回A中的每一行。对于返回的每个A行,如果有一个B行使得A.x=B.Y,则还将返回所有相应的B值。

如果没有A.x=B.yB行,则填充空值会导致该A行的所有B值返回为空值。

例如,考虑包含患者信息的Patient表,其中包括一个字段Patient。 指定患者主治医生的DocIDID代码。 数据库中的一些患者没有主诊医生,因此对这些患者记录“患者”。 DocID字段为NULL。 现在,我们在Patient表和Doctor表之间执行连接,以生成一个包含患者姓名和相应医生姓名的表。

SELECT Patient.PName,Doctor.DName
   FROM Patient INNER JOIN Doctor
   ON Patient.DocID=Doctor.DocID

INNER JOIN不执行空填充。 因此,如果没有相应的医生姓名,则不会返回患者姓名。

单向外联接确实执行空值填充。因此,没有相应医生名称的患者名称将为Doctor.DName返回NULL

SELECT Patient.PName,Doctor.DName
   FROM Patient LEFT OUTER JOIN Doctor
   ON Patient.DocID=Doctor.DocID

单向外联接条件(包括必要的空值填充)在其他条件之前应用。因此,WHERE子句中不能由填充空值的值满足的条件(例如,B中字段的范围或相等条件)有效地将AB的单向外联接转换为常规联接(内联接)。

例如,如果将子句“WHERE Doctor.Age < 45”添加到上面的两个“Patient”表查询中,则它们是等效的。但是,如果添加子句“WHERE Doctor.Age < 45 OR Doctor.Age is null”,它将保留这两个查询之间的差异。

混合外部和内部连接

IRIS支持任意顺序的混合内部连接和外部连接的所有语法。

多重连接和隐式连接的性能

默认情况下,查询优化器将多个连接操作按其对最优序列的最佳估计排序。 这不一定是在查询中指定的连接顺序。 可以在FROM子句中指定%INORDER%FIRSTTABLE%STARTTABLE查询优化选项,以显式指定表连接的顺序。

查询优化器可以执行子查询扁平化,将某些子查询转换为显式连接。 当子查询数量较少时,这将极大地提高连接性能。 当子查询的数量超过一个或两个时,子查询扁平化在某些情况下可能会略微降低性能。 可以在FROM子句中指定%NOFLATTEN查询优化选项,以显式指定不应该执行子查询扁平化。

只有当子查询扁平化后,查询中的连接总数不超过15个连接时,查询优化器才会执行子查询扁平化。 指定超过15个联接,如果其中一些联接是隐式联接或联接子查询,则会导致查询性能的显著下降。

示例

下面的示例显示了在表1和表2上执行JOIN操作的结果。

Table1

Column1Column2
aaabbb
cccccc
xxxyyy
hhhzzz

Table2

Column1Column3
ggghhh
xxxzzz

CROSS JOIN 示例

SELECT * FROM Table1 CROSS JOIN Table2
Column1Column2Column1Column3
aaabbbggghhh
aaabbbxxxzzz
ccccccggghhh
ccccccxxxzzz
xxxyyyggghhh
xxxyyyxxxzzz
hhhzzzggghhh
hhhzzzxxxzzz

NATURAL JOIN 示例

SELECT * FROM Table1 NATURAL JOIN Table2
Column1Column2Column1Column3
xxxyyyxxxzzz

请注意,NATURAL JOIN的 IRIS实现不会合并具有相同名称的列。

使用ON子句的INNER JOIN示例

SELECT * FROM Table1 INNER JOIN Table2
     ON Table1.Column1=Table2.Column3
Column1Column2Column1Column3
hhhzzzggghhh

使用USING子句的INNER JOIN示例

SELECT * FROM Table1 INNER JOIN Table2
  USING (Column1)
Column1Column2Column1Column3
xxxyyyxxxzzz

注意,USING子句的IRIS实现不会合并具有相同名称的列。

LEFT OUTER JOIN 示例

SELECT * FROM Table1 LEFT OUTER JOIN Table2
  ON Table1.Column1=Table2.Column3
Column1Column2Column1Column3
aaabbbnullnull
ccccccnullnull
xxxyyynullnull
hhhzzzggghhh

RIGHT OUTER JOIN 示例

SELECT * FROM Table1 RIGHT OUTER JOIN Table2
     ON Table1.Column1=Table2.Column3
Column1Column2Column1Column3
hhhzzzggghhh
nullnullxxxzzz

FULL OUTER JOIN

SELECT * FROM Table1 FULL OUTER JOIN Table2
  ON Table1.Column1=Table2.Column3
Column1Column2Column1Column3
aaabbbnullnull
ccccccnullnull
xxxyyynullnull
hhhzzzggghhh
nullnullxxxzzz
0
0 86
文章 姚 鑫 · 十月 28, 2021 7m read

第五十九章 SQL命令 JOIN(一)

基于两个表中的数据创建表的SELECT子句。

大纲

table1 [[AS] t-alias1] CROSS JOIN table2 [[AS] t-alias2] |
table1 [[AS] t-alias1] , table2 [[AS] t-alias2]

 table1 [[AS] t-alias1]
NATURAL [INNER] JOIN |
NATURAL LEFT [OUTER] JOIN |
NATURAL RIGHT [OUTER] JOIN |
table2 [[AS] t-alias2] 

table1 [[AS] t-alias1]
[INNER] JOIN |
LEFT [OUTER] JOIN |
RIGHT [OUTER] JOIN |
FULL [OUTER] JOIN
table2 [[AS] t-alias2] 
ON condition-expression

table1 [[AS] t-alias1]
[INNER] JOIN |
LEFT [OUTER] JOIN |
RIGHT [OUTER] JOIN |
table2 [[AS] t-alias2] 
USING (identifier-commalist)

(上述联接语法用于SELECT语句FROM子句。可以在其他SELECT语句子句中使用其他联接语法。)

描述

联接是将两个表组合在一起以生成联接表的操作,可以选择遵守一个或多个限制条件。新表的每一行都必须满足限制条件。联接提供了将一个表中的数据与另一个表中的数据链接起来的方法,并且经常用于定义报表和查询。

有几种表示联接的语法形式。首选形式是在SELECT语句中指定显式联接表达式作为FROM子句的一部分。FROM子句联接表达式可以包含多个联接。

注意: SQL还支持在SELECT语句SELECT-ITEM列表、WHERE子句、ORDER BY子句和其他地方使用箭头语法(–>)的隐式联接。指定隐式联接以执行表与另一个表中的字段的左外联接;指定显式联接以联接两个表。这种隐式联接语法可以很好地替代显式联接语法,或者与显式联接语法一起出现在同一查询中。但是,在组合箭头语法和显式连接语法方面有一些重要的限制。这些限制如下所述。

IRIS使用复杂的优化算法来最大化连接操作的性能。它不一定按照表的指定顺序联接表。相反,SQL优化器根据每个表的Tune Table数据(以及其他因素)确定表连接顺序。因此,在复杂SQL查询中使用表之前,必须先针对表运行调优表,这一点很重要。

在大多数情况下,SQL优化器策略提供最佳结果。但是, IRIS还提供联接优化关键字,如%FIRSTTABLE%INORDER%FULL,可以在FROM关键字之后立即使用这些关键字来覆盖特定查询的默认优化策略。

JOIN 定义

IRIS支持多种不同的连接语法形式。但是,这许多公式涉及以下五种类型的联接。

ANSI连接语法句法上的等价于
CROSS JOIN与符号表示相同:FROM子句中的Table1Table2(用逗号分隔的表列表)。
INNER JOINJOIN相同。符号表示:“=”(在WHERE子句中)。
LEFT OUTER JOIN与左连接相同。箭头语法(->)还执行左外部联接。
RIGHT OUTER JOIN与右连接相同。
FULL OUTER JOINFULL JOIN相同。

除非另有说明,否则所有连接语法都在FROM子句中指定。

  • 交叉连接是将第一个表的每一行与第二个表的每一行交叉的连接。 这将产生一个笛卡尔积,即一个具有大量数据重复的、逻辑上全面的大型表。 通常这种连接是通过在FROM子句中提供一个逗号分隔的表列表来执行的,然后使用WHERE子句来指定限制性条件。 %INORDER%STARTTABLE优化关键字不能用于交叉连接。 尝试这样做会导致SQLCODE -34错误。
  • INNER JOIN是将第一个表的行与第二个表的行连接起来的连接,不包括在第一个表中没有在第二个表中找到相应行的任何行。
  • OUTER JOIN和右OUTER JOIN在大多数方面功能相同(语法相反),因此经常统称为单向外部连接。 单向外部连接是将第一个(源)表的行与第二个表的行链接在一起的连接,包括第一个表的所有行,即使第二个表中没有匹配。 这将导致第一个(源)表的某些字段可能与NULL数据配对。
  • 在指定单向外部连接时,在FROM子句中命名表的顺序非常重要。 对于LEFT OUTER JOIN,指定的第一个表是该连接的源表。 对于RIGHT OUTER JOIN,指定的第二个表是连接的源表。
  • FULL OUTER JOIN是将在两个表上执行左OUTER JOIN和右OUTER JOIN的结果组合在一起的连接。 它包括在第一个表或第二个表中找到的所有行,并在两边的缺失匹配中填充null

CROSS JOIN 注意事项

显式使用JOIN关键字比使用逗号语法指定交叉连接具有更高的优先级。 IRIS将t1,t2 JOIN t3解释为t1,(t2 JOIN t3)

不能执行涉及本地表和通过ODBC或JDBC网关连接链接的外部表的交叉连接。 例如,FROM Sample.Person,Mylink.Person。 尝试这样做的结果是SQLCODE -161:“对SQL连接的引用必须构成整个子查询”。 要执行此交叉连接,必须将链接表指定为子查询。 例如,FROM Sample。 Person,(SELECT * FROM Mylink.Person)

自然连接

NATURAL JOIN是以NATURAL关键字为前缀的INNER JOINLEFT OUTER JOINRIGHT OUTER JOIN。 在连接前加上单词NATURAL,说明正在连接具有相同名称的两个表的所有列。 由于NATURAL连接对具有相同名称的所有列自动执行相等条件,因此不可能指定on子句或USING子句。 尝试这样做会导致SQLCODE -25错误。

对于NATURAL连接的两个操作数,只支持简单的基表引用(不支持视图或子查询)。

只能将NATURAL连接指定为连接表达式中的第一个连接。

NATURAL连接不会合并名称相同的列。

FULL JOIN不能以NATURAL关键字作为前缀。 尝试这样做会导致SQLCODE -94错误。

ON 子句

内连接、左外连接、右外连接或全外连接都可以有ON子句。 ON子句包含一个或多个条件表达式,用于限制连接操作返回的值。 带有ON子句的连接可以在连接表达式中的任何位置指定。 带有ON子句的连接可以为连接的任一操作数指定表、视图或子查询。

ON子句由一个或多个条件表达式谓词组成。 其中包括SQL支持的大多数谓词。 但是,不能使用FOR SOME %ELEMENT集合谓词来限制连接操作。

可以使用ANDORNOT逻辑操作符关联多个条件表达式。 AND优先于OR。 括号可以用来嵌套和分组条件表达式。 除非用括号分组,否则使用相同逻辑运算符的谓词严格按照从左到右的顺序执行。

ON子句有以下限制:

  • 带有ON子句的连接只能使用ANSI连接关键字语法。
  • 带有ON子句的连接不能使用NATURAL关键字前缀。 这将导致SQLCODE -25错误。
  • 带有ON子句的连接不能接受USING子句。 这将导致SQLCODE -25错误。
  • ON子句不能包含箭头语法(- >)。 这将导致SQLCODE -67错误。
  • ON子句只能引用ANSI关键字JOIN操作中显式指定的表。 在FROM子句中指定的其他表不能在ON子句中引用。 这将导致SQLCODE -23错误。
  • ON子句只能引用位于JOIN操作数中的列。 多个连接中的语法优先级可能会导致ON子句失败。 例如,查询SELECT * FROM t1,t2 JOIN t3 ON t1.p1=t3.p3失败,因为t1t3不是join的操作数; t1连接t2 JOIN t3的结果集。 SELECT * FROM t1 CROSS JOIN t2 JOIN t3 ON t1.p1=t3。 p3或者SELECT * FROM t2,t1 JOIN t3 ON t1.p1=t3.p3
SELECT *
FROM Table1
   LEFT JOIN Table2 ON Table1.k = Table2.k
   LEFT JOIN Table3 ON COALESCE(Table1.k,Table2.k) = Table3.k

使用FULL OUTER JOINRIGHT OUTER JOIN的类似示例也有这个限制。

ON子句索引

为了获得最佳性能,ON子句中引用的字段应该(在大多数情况下)具有关联索引。 ON子句可以使用只满足某些连接条件的现有索引。 在多个字段上指定条件的ON子句可以使用只包含这些字段子集的索引作为下标,以部分满足连接; IRIS将直接从表中测试其余字段的连接条件。

ON子句中引用的字段的排序规则类型应该与它在相应索引中的排序规则类型匹配。 排序规则类型不匹配可能导致索引不被使用。 但是,如果连接条件位于%EXACT字段值上,但只有排序字段值上的索引可用, IRIS可以使用该索引来限制要检查的行以获取准确值。

在一些非常特殊的情况下,可能希望通过在ON子句条件前面加上%NOINDEX关键字来防止索引的使用。

USING 子句

INNER JOINLEFT OUTER JOINRIGHT OUTER JOIN可以有USING子句。 对于使用USING子句的连接的操作数,只支持简单的基表引用(不支持视图或子查询)。 带有USING子句的连接只能指定为连接表达式中的第一个连接。 使用USING子句的连接不能使用NATURAL关键字前缀或ON子句。

USING子句列出一个或多个列名,列名由逗号分隔,用括号括起来。 括号是必需的。 只允许显式的列名; %ID不允许。 重复的列名被忽略。 USING子句不会合并名称相同的列。

USING子句是表示ON子句中表达的相等条件的一种简单方式。因此:t1 INNER JOIN t2 USING (a,b)等价于t1.a=t2.at1.b=t2.b上的T1内连接T2

0
0 101
文章 姚 鑫 · 十月 27, 2021 2m read

第五十八章 SQL命令 %INTRANSACTION

显示事务状态。

大纲

%INTRANSACTION
%INTRANS

参数

描述

%INTRANSACTION语句设置SQLCODE以指示事务状态:

  • 如果当前在事务中,则SQLCODE=0
  • 如果不在事务中,则SQLCODE=100

事务正在进行时,%INTRANSACTION返回SQLCODE=0。此事务可以是由START TRANSACTIONSAVEPOINT发起的SQL事务。它也可以是由TSTART发起的ObjectScript事务。

事务嵌套对%INTRANSACTION没有影响。SET TRANSACTION%INTRANSACTION没有影响。

还可以使用$TLEVEL确定事务状态。%INTRANSACTION仅指示事务是否正在进行。$TLEVEL指示事务是否正在进行以及当前的事务级别数。

示例

以下嵌入式SQL示例显示%INTRANSACTION如何设置SQLCODE

ClassMethod %INTRANSACTION()
{
	n SQLCODE
	&sql(%INTRANSACTION)
	w "Before %INTRANS SQLCODE=",SQLCODE," TL=",$TLEVEL,!
	&sql(SET TRANSACTION %COMMITMODE EXPLICIT)
	n SQLCODE
	&sql(%INTRANSACTION)
	w "SetTran %INTRANS SQLCODE=",SQLCODE," TL=",$TLEVEL,!
	&sql(START TRANSACTION)
	n SQLCODE
	&sql(%INTRANSACTION)
	w "StartTran %INTRANS SQLCODE=",SQLCODE," TL=",$TLEVEL,!
	&sql(SAVEPOINT a)
	n SQLCODE
	&sql(%INTRANSACTION)
	w "Savepoint %INTRANS SQLCODE=",SQLCODE," TL=",$TLEVEL,!
	&sql(ROLLBACK TO SAVEPOINT a)
	n SQLCODE
	&sql(%INTRANSACTION)
	w "Rollback to Savepoint %INTRANS SQLCODE=",SQLCODE," TL=",$TLEVEL,!
	&sql(COMMIT)
	n SQLCODE
	&sql(%INTRANSACTION)
	w "After Commit %INTRANS SQLCODE=",SQLCODE," TL=",$TLEVEL
}
0
0 150
文章 姚 鑫 · 十月 26, 2021 8m read

第五十七章 SQL命令 INTO

一个SELECT子句,指定在宿主变量中存储选定的值。

大纲

INTO :hostvar1 [,:hostvar2]...

参数

  • :hostvar1 - 在宿主语言中声明的输出宿主变量。 当在INTO子句中指定时,变量名前面加冒号(:)。 主机变量可以是局部变量(非下标或下标)或对象属性。 可以将多个变量指定为逗号分隔的列表、单个下标数组变量或逗号分隔的列表和单个下标数组变量的组合。

描述

INTO子句和主机变量仅在嵌入式SQL中使用。它们不在动态SQL中使用。在动态SQL中,%SQL.Statement类为输出变量提供了类似的功能。在通过ODBCJDBC或动态SQL处理的SELECT查询中指定INTO子句会导致SQLCODE-422错误。

INTO子句可以在SELECTDECLAREFETCH语句中使用。INTO子句对于所有三个语句都是相同的;本页上的示例都引用SELECT语句。

INTO子句使用在SELECT-ITEM列表中检索(或计算)的值来设置相应的输出主机变量,从而使这些返回的数据值可用于ObjectScript。在SELECT中,可选INTO子句出现在SELECT-ITEM列表之后、FROM子句之前。

注意:编译嵌入式SQL时,输出主机变量将初始化为空字符串。这可以防止在执行时出现<UNDECLARED>错误。因此,只有当SQLCODE=0时,主机变量才包含有意义的值。在使用输出主机变量值之前,请始终检查SQLCODE。当SQLCODE=100SQLCODE为负数时,不要使用这些变量值。

主机变量

主机变量只能包含单个值。因此,嵌入式SQL中的SELECT只检索一行数据。这默认为表格的第一行。当然,可以通过使用WHERE条件限制符合条件的行来从表的其他行检索数据。

在嵌入式SQL中,可以通过声明游标,然后为每一连续行发出FETCH命令,从多行返回数据。INTO子句主机变量可以在DECLARE查询中指定,也可以在FETCH中指定。

  • 主机变量列表,由逗号分隔的主机变量列表组成,每个选择项对应一个主机变量列表。
  • 主机变量数组,由单个下标主机变量组成。

注意:如果主机语言声明变量的数据类型,则在调用SELECT语句之前,所有主机变量都必须用主机语言声明。检索到的字段值的数据类型必须与主机变量声明匹配。(ObjectScript不声明变量的数据类型。)

使用主机变量列表

INTO子句中指定主机变量列表时,以下规则适用:

  • INTO子句中的主机变量数必须与SELECT-ITEM列表中指定的字段数匹配。如果所选字段和主机变量的数量不同,SQL将返回“基数不匹配”错误。
  • 选定字段和主机变量按相对位置匹配。因此,这两个列表中对应的项必须以相同的顺序出现。
  • 列出的主机变量可以是无下标变量或下标变量的任意组合。
  • 列出的主机变量可以返回聚合值(如计数、总和或平均值)或函数值。
  • 列出的主机变量可以返回%CLASSNAME%TABLENAME值。
  • 列出的主机变量可以从涉及多个表的SELECT返回字段值,也可以从没有FROM子句的SELECT返回值。

下面的示例从包含四个主机变量的列表中选择四个字段。本例中的主机变量带有下标:

ClassMethod Into()
{
	&sql(
		SELECT %ID,Home_City,Name,SSN 
		INTO :mydata(1),:mydata(2),:mydata(3),:mydata(4)
		FROM Sample.Person
		WHERE Home_State='MA' )
	if SQLCODE = 0 {
		for i = 1 : 1 : 15 { 
			if $d(mydata(i)) {
				w "field ",i," = ",mydata(i),! 
			}
		} 
	} else {
		w "SQLCODE=",SQLCODE,! 
	}
}

使用主机变量数组

主机变量数组使用单个下标变量来包含所有选定的字段值。此数组是根据表中字段定义的顺序填充的,而不是根据选择项列表中字段的顺序填充的。

INTO子句中使用主机变量数组时,适用以下规则:

  • 选择项列表中指定的字段被选入单个主机变量的下标。因此,不必将选择项列表中的项数与主机变量COUNT匹配。
  • 主机变量下标由表定义中相应的字段位置填充。例如,表定义中定义的第6个字段对应于mydata(6)。与指定选择项不对应的所有下标仍未定义。选择项中项的顺序对如何填充下标没有影响。
  • 主机变量数组只能从单个表返回字段值。
  • 主机变量数组只能返回字段值。它不能返回聚合值(如COUNTSUMAverage)、函数值或%CLASSNAME%TABLENAME值。(可以通过指定将主机变量列表项与主机变量数组相结合的主机变量参数来返回这些参数。)

以下示例将四个字段选择到主机变量数组中:

ClassMethod Into1()
{
	&sql(
		SELECT %ID,Home_City,Name,SSN
		INTO :mydata()   
		FROM Sample.Person
		WHERE Home_State='MA' 
	)
	if SQLCODE = 0 {
		for i = 1 : 1 : 15 { 
			if $d(mydata(i)) {
				w "field ",i," = ",mydata(i),! 
			}
		} 
	} else {
		w "SQLCODE=",SQLCODE,! 
	}
}

返回字段值的主机变量

下面的嵌入式SQL示例从表的第一条记录中选择三个字段(嵌入式SQL始终检索单个记录),并使用INTO设置三个相应的无下标主机变量。然后,ObjectScript写入命令使用这些变量。在从嵌入式SQL返回时立即测试SQLCODE变量被认为是很好的编程实践。如果SQLCODE不等于0,则将输出主机变量的值初始化为空字符串。

ClassMethod Into2()
{
	&sql(
		SELECT Home_State, Name, Age 
		INTO :state, :name, :age   
		FROM Sample.Person
	)
	if SQLCODE=0 {
		w !,"  Name=",name
		w !,"  Age=",age
		w !,"  Home State=",state 
	} else {
		w !,"SQL error ",SQLCODE  
	}
}

下面的嵌入式SQL示例返回由两个表联接产生的行中的字段值。从多个表返回字段时,必须使用主机变量列表:

ClassMethod Into3()
{
	&sql(
		SELECT P.Name,E.Title,E.Name,P.%TABLENAME,E.%TABLENAME 
		INTO :name(1),:title,:name(2),:ptname,:etname
		FROM Sample.Person AS P LEFT JOIN
			Sample.Employee AS E ON E.Name %STARTSWITH 'B'
		WHERE P.Name %STARTSWITH 'A')
	if SQLCODE = 0 {
		w ptname," = ",name(1),!
		w etname," = ",title,!
		w etname," = ",name(2) 
	} else {
		w !,"SQL error ",SQLCODE  
	}
}

返回文字值和聚合值的主机变量

由于输出主机变量仅在SQLCODE=0时有效,因此避免使用发出SQLCODE=100(查询不返回表数据)的查询结果非常重要。SQLCODE=100将所有输出主机变量默认为空字符串,包括返回的文字和计数聚合。

下面的嵌入式SQL示例将一个主机变量(TODAY)传递给SELECT语句,其中的计算结果是INTO子句变量VALUE(:TOWORY)。该主机变量被传递给包含该主机的程序。此查询没有引用表字段,因此没有指定FROM子句。没有FROM子句的嵌入式SQL查询不能发出SQLCODE=100。带有FROM子句的嵌入式SQL查询可以发出SQLCODE=100,这会将所有输出变量定义为缺省的空字符串的值,包括那些不是表字段值的变量,例如:Tomorrow

ClassMethod Into4()
{
	s today = $h
	&sql(
		SELECT :today+1
		INTO :tomorrow 
	)
	if SQLCODE=0 {
		w !,"Tomorrow is: ",$ZDATE(tomorrow) 
	} else {
		w !,"SQL error ",SQLCODE  
	}
}

下面的嵌入式SQL示例返回聚合值。它使用COUNT聚合函数对表中的记录进行计数,并使用AVG对工资字段值进行平均。INTO子句将这些值作为两个下标主机变量返回给ObjectScript

因为两个SELECT-Items都是聚合的,所以即使指定的表不包含数据,该程序也总是发出SQLCODE=0。在本例中,count(*)=0AVG(Salary)是默认的空字符串。

ClassMethod Into5()
{
	&sql(
		SELECT COUNT(*),AVG(Salary)
		INTO :agg(1),:agg(2)
		FROM Sample.Employee)
	if SQLCODE = 0 {
		w !,"Total Employee records= ",agg(1)
		w !,"Average Employee salary= ",agg(2) 
	} elseif SQLCODE=100 {
		w !,"Total Employee records= ",agg(1) 
	} else {
		w !,"SQL error ",SQLCODE  
	}
}

下面的嵌入式SQL示例与上一个示例相同,只是它还返回一个字段值。因为SELECT-ITEMS包括一个字段值,所以当指定的表不包含数据时,该程序可以发出SQLCODE=100。在此示例中,如果SQLCODE=100,则COUNT(*)是默认的空字符串,而不是0

ClassMethod Into6()
{
	&sql(
		SELECT COUNT(*),AVG(Salary),Salary
		INTO :agg(1),:agg(2),:pay
		FROM Sample.Employee
	)
	if SQLCODE = 0 {
		w !,"Total Employee records= ",agg(1)
		w !,"Average Employee salary= ",agg(2)
		w !,"Sample Employee salary=",pay 
	} else {
		w !,"SQL error ",SQLCODE  
	}
}

主机变量数组

以下两个嵌入式SQL示例使用主机变量数组从一行返回非隐藏数据字段值。在这些示例中,%ID是在SELECT-Item列表中指定的,因为在默认情况下,SELECT*不返回RowId(尽管它为Sample.Person返回);RowId始终是字段1。请注意,Sample.Person字段49可以为空,字段5不是数据字段(它引用Sample.Address),字段10是隐藏的。

第一个示例返回指定数量的字段(FirstFld);此计数中包括隐藏字段和非数据字段,但不显示。当从包含多个字段的表返回行时,使用firstfld将是合适的。请注意,此示例可以返回作为父引用的字段0Sample.Person不是子表,因此tflds(0)未定义:

ClassMethod Into7()
{
	&sql(
		SELECT *,%ID INTO :tflds()   
		FROM Sample.Person 
	)
	if SQLCODE = 0 {
		s firstflds = 14
		for i = 0 : 1 : firstflds { 
			if $d(tflds(i)) {
				w "field ",i," = ",tflds(i),! 
			}
		} 
	} else {
		WRITE "SQLCODE error=",SQLCODE,! 
	}
}

第二个示例返回Sample.Person中的所有非隐藏数据字段。请注意,此示例不会尝试返回父引用Field 0,因为在Sample.Person中,tflds(0)是未定义的,因此会生成<UNDEFINED>错误:

ClassMethod Into8()
{
	&sql(
		SELECT *,%ID INTO :tflds()   
		FROM Sample.Person 
	)
	if SQLCODE=0 {
		s x = 1
		while x '="" {
			w "field ",x," = ",tflds(x),!
			s x= $ORDER(tflds(x)) 
		}
	} else { 
		w "SQLCODE error=",SQLCODE,! 
	}
}

下面的嵌入式SQL示例将逗号分隔的主机变量列表(用于非字段值)和主机变量数组(用于字段值)组合在一起:

ClassMethod Into9()
{
	&sql(
		SELECT %TABLENAME, Name, Age, AVG(Age)
		INTO :tname, :tflds(), :ageavg
		FROM Sample.Person
		WHERE Age > 50 
	)
	if SQLCODE = 0 {
		w "Table name is = ",tname,!
		for i = 0 : 1 : 25 { 
			if $d(tflds(i)) {
				w "field ",i," = ",tflds(i),! 
			}
		} 
		w "Average age is = ",ageavg,! 
	} else {
		w "SQLCODE=",SQLCODE,! 
	}
}

0
0 123
文章 姚 鑫 · 十月 25, 2021 7m read

第五十六章 SQL命令 INSERT OR UPDATE

在表中添加新行或更新表中的现有行。

大纲

INSERT OR UPDATE [%keyword] [INTO] table
          SET column = scalar-expression {,column2 = scalar-expression2} ...  |
          [ (column{,column2} ...) ] VALUES (scalar-expression {,scalar-expression2} ...)  |
          VALUES :array()  |
          [  (column{,column2} ...) ] query  |
          DEFAULT VALUES

参数

  • %keyword - 可选-以下一个或多个关键字选项,以空格分隔:%NOCHECK%NOFPLAN%NOINDEX%NOJOURN%NOLOCK%NOTRIGGER%PROFILE%PROFILE_ALL
  • table - 要对其执行插入操作的表或视图的名称。此参数可以是子查询。INTO关键字是可选的。
  • column - 可选-与提供的值列表顺序对应的列名或以逗号分隔的列名列表。如果省略,值列表将按列号顺序应用于所有列。
  • scalar-expression - 为相应列字段提供数据值的标量表达式或以逗号分隔的标量表达式列表。
  • :array() - 仅嵌入式SQL-指定为主机变量的值的动态本地数组。必须未指定数组的最低下标级别。因此:myupdates(), :myupdates(5,):myupdates(1,1,)都是有效的规范。
  • query - 一种选择查询,其结果集为一行或多行的相应列字段提供数据值。

描述

INSERTUPDATE语句是INSERT语句的扩展(它与INSERT语句非常相似):

  • 如果指定的记录不存在,则INSERTUPDATE执行INSERT
  • 如果指定的记录已存在,则INSERTUPDATE执行更新。它使用指定的字段值更新记录。即使指定的数据与现有数据相同,也会进行更新。

INSERTUPDATE通过将唯一关键字字段值与现有数据值匹配来确定记录是否存在。如果发生违反唯一键约束的情况,则INSERTUPDATE将执行UPDATE操作。请注意,唯一键字段值可能不是在INSERTUPDATE中显式指定的值;它可能是列默认值或计算值的结果。当对切片表运行INSERTUPDATE时,如果切片键与UNIQUE KEY约束相同(或是其子集),则INSERTUPDATE将执行UPDATE操作。如果INSERTUPDATE因为找到任何其他唯一键值(不是切片键)而尝试执行更新,则该命令会失败,并由于UNIQUE约束失败而出现SQLCODE-119错误。

注意:由于%NOCHECK关键字禁用唯一值检查,因此INSERTUPDATE %NOCHECK总是导致INSERT操作。因此,请不要指定%NOCHECK

单个记录的INSERTUPDATE始终将%ROWCOUNT变量设置为1,并将已插入或更新的行的%ROWID变量设置为1。

INSERTUPDATE语句与SELECT语句组合可以插入和/或更新多个表行。

INSERTUPDATE使用相同的语法,并且通常具有与INSERT语句相同的功能和限制。这里描述了插入或更新的特殊注意事项。除非此处另有说明,否则请参阅插入以了解详细信息。

权限

INSERTUPDATE同时需要插入和更新权限。必须将这些权限作为表级权限或列级权限拥有。对于表级权限:

  • 无论实际执行的是什么操作,用户都必须拥有对指定表的INSERTUPDATE权限。
  • 如果使用SELECT查询插入或更新另一个表中的数据,则用户必须对该表具有SELECT权限。

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

IDKEY字段

可以插入IDKEY字段值,但不能更新IDKEY字段值。如果表具有IDKEY索引和另一个唯一键约束,则INSERTUPDATE将匹配这些字段以确定是执行INSERT还是UPDATE。如果另一个键约束失败,则强制INSERTUPDATE执行更新而不是INSERT。但是,如果指定的IDKEY字段值与现有IDKEY字段值不匹配,则此更新将失败并生成SQLCODE-107错误,因为更新正在尝试修改IDKEY字段。

例如,表MyTest定义了四个字段:A、B、C、D,具有IDKEY(A,B)Unique(C,D)约束。该表包含以下记录:

Row 1: A=1, B=1, C=2, D=2

Row 2: A=1, B=2, C=3, D=4

调用INSERTUPDATE ABC(A,B,C,D)(2,2,3,4),因为UNIQUE(C,D)约束失败,所以该语句不能执行INSERT。相反,它会尝试更新第2行。第2行的IDKEY为(1,2),因此INSERTUPDATE语句将尝试将字段A的值从1更改为2。但无法更改IDKEY值,因此更新失败,并显示SQLCODE-107错误。

计数器字段

当执行INSERTUPDATE时, IRIS最初假定操作将是INSERT。因此,它将用于向串行(%Library.Counter)字段提供整数的内部计数器加1。INSERT使用这些递增的计数器值将整数值分配给这些字段。但是,如果 IRIS确定该操作需要更新,则INSERTUPDATE已经递增了内部计数器,但它不会将这些递增的整数值分配给计数器字段。如果下一个操作是INSERT,则会导致这些字段的整数序列出现间隙。下面的示例显示了这一点:

  1. 内部计数器值为4INSERTUPDATE递增内部计数器,然后插入行5:内部计数器=5,串行字段值=5
  2. INSERTUPDATE递增内部计数器,然后确定它必须对现有行执行更新:INTERNAL COUNTER=6,不更改字段计数器。
  3. INSERTUPDATE递增内部计数器,然后插入一行:内部计数器=7序列字段值=7

Identity和RowID字段

INSERTUPDATERowId值分配的影响取决于是否存在标识字段:

  • 如果没有为表定义标识字段,则INSERT操作会导致 IRIS自动将下一个连续整数值分配给ID(RowID)字段。更新操作对后续插入没有影响。因此,INSERTUPDATE执行与INSERT相同的INSERT操作。
  • 如果为表定义了标识字段,则INSERTUPDATE会导致 IRIS在确定操作是INSERT还是UPDATE之前,将用于向标识字段提供整数的内部计数器加1。插入操作将该递增的计数器值分配给标识字段。但是,如果 IRIS确定INSERTUPDATE操作需要更新,则它已经递增了内部计数器,但不会分配这些递增的整数值。如果下一个INSERTUPDATE操作是INSERT,则会导致标识字段的整数序列出现间隙。RowID字段值取自Identity字段值,导致ID(RowID)整数值的分配存在差距。

示例

以下五个示例:创建一个新表(SQLUser.CaveDwell);使用INSERTUPDATE用数据填充该表;使用INSERTUPDATE添加新行并更新现有行;使用SELECT*显示数据;以及删除该表。

以下示例使用CREATE TABLE创建具有唯一字段(NUM)的表:

ClassMethod InsertOrUpdate()
{
	&sql(
		CREATE TABLE SQLUser.CaveDwellers (
			Num          INT UNIQUE,
			CaveCluster  CHAR(80) NOT NULL,
			Troglodyte   CHAR(50) NOT NULL,
			CONSTRAINT CaveDwellerPK PRIMARY KEY (Num)
		)
	)
	if SQLCODE = 0 { 
		w !,"表创建" 
	} elseif SQLCODE = -201 { 
		w !,"表已经存在" 
	} else { 
		w !,"SQL表创建错误代码: ",SQLCODE
		q 
	}
}

下面的示例使用类定义定义同一个表,为num定义唯一键:

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

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

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

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

/// DDL Unique Key Specification
Index CAVEDWELLERSUNIQUE1 On Num [ SqlName = CAVEDWELLERS_UNIQUE1, Unique ];

/// DDL Primary Key Specification
Index CaveDwellerPK On Num [ PrimaryKey, Type = index, Unique ];

/// 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 ];

}

SELECT * FROM SQLUser.CaveDwellers ORDER BY Num

以任何顺序运行以下两个示例一次或多次。他们将插入记录15。如果记录4已经存在,插入或更新将更新它。使用SELECT *示例显示表格数据:

ClassMethod InsertOrUpdate1()
{

	&sql(
		INSERT OR UPDATE INTO SQLUser.CaveDwellers 
		(
			Num, CaveCluster, Troglodyte
		) 
		VALUES 
		(
			3, 'Bedrock', 'Flintstone,Fred'
		)
	)
	if SQLCODE = 0 { 
		SET rcount=%ROWCOUNT 
	}
	&sql(
		INSERT OR UPDATE INTO SQLUser.CaveDwellers 
		(
			Num, CaveCluster, Troglodyte
		) 
		VALUES 
		(
			4, 'Bedrock1', 'Flintstone,Wilma'
		)
	)
	if SQLCODE = 0 { 
		s rcount = rcount + %ROWCOUNT 
		w !,rcount," records inserted/updated" 
	} else { 
		w !,"Insert/Update failed, SQLCODE=",SQLCODE 
	}
}
ClassMethod InsertOrUpdate2()
{

	n SQLCODE,%ROWCOUNT,%ROWID
	&sql(
		INSERT OR UPDATE SQLUser.CaveDwellers
		(
			Num,CaveCluster,Troglodyte
		)
		SELECT %ID,Home_City,Name
		FROM Sample.Person
		WHERE %ID BETWEEN 2 AND 5)
	if SQLCODE=0 {
		w !,"Insert/Update succeeded"
		w !,%ROWCOUNT," records inserted/updated"
		w !,"Row ID=",%ROWID 
	} else {
		w !,"Insert/Update failed, SQLCODE=",SQLCODE 
	}
}

以下示例删除该表:

ClassMethod InsertOrUpdate3()
{
	&sql(DROP TABLE SQLUser.CaveDwellers)
	if SQLCODE = 0 {
		w !,"表已删除" 
	} elseif SQLCODE = -30 {
		w !,"表不存在"
	} else {
		w !,"删除表失败. SQLCODE=",SQLCODE 
	}
}
0
0 229
文章 姚 鑫 · 十月 24, 2021 4m read

第五十五章 SQL命令 INSERT(四)

嵌入式SQL和动态SQL示例

下面的嵌入式SQL示例创建一个新表SQLUser.MyKids。下面的示例使用INSERT用数据填充此表。在插入示例之后,提供了一个删除SQLUser.MyKids的示例。

ClassMethod Insert2()
{
	&sql(
		CREATE TABLE SQLUser.MyKids 
		(
			KidName VARCHAR(16) UNIQUE NOT NULL,
			KidDOB INTEGER NOT NULL,
			KidPetName VARCHAR(16) DEFAULT 'no pet'
		) 
	)
	if SQLCODE=0 {
		w !,"创建的表" 
	} elseif SQLCODE=-201 {
		w !,"表已存在"  
		q
	} else {
		w !,"CREATE TABLE失败。SQLCODE=",SQLCODE 
	}
}

下面的嵌入式SQL示例插入具有两个字段值的行(第三个字段KidPetName采用默认值)。请注意,表架构名称由#SQLCompile Path宏指令作为架构搜索路径提供:

ClassMethod Insert3()
{
	#SQLCompile Path = Sample
	n SQLCODE,%ROWCOUNT,%ROWID
	&sql(
		INSERT INTO MyKids 
		(
			KidName, KidDOB
		) 
		VALUES 
		(
			'Molly', 60000
		)
	)
	if SQLCODE = 0 {
		w !,"插入成功"
		w !,"Row count=",%ROWCOUNT
		w !,"Row ID=",%ROWID
		q 
	} elseif SQLCODE = -119 {
		w !,"未写入重复记录",!
		w %msg,!
		q 
	} else {
		w !,"插入失败,SQLCODE=",SQLCODE 
	}
}

下面的嵌入式SQL示例使用表的列顺序插入具有三个字段值的行:

ClassMethod Insert4()
{

	n SQLCODE,%ROWCOUNT,%ROWID
	&sql(
		INSERT INTO SQLUser.MyKids VALUES ('Josie','40100','Fido') 
	)
	if SQLCODE = 0 {
		w !,"插入成功"
		w !,"Row count=",%ROWCOUNT
		w !,"Row ID=",%ROWID
		q 
	} elseif SQLCODE = -119 {
		w !,"未写入重复记录",!
		w %msg,!
		q 
	} else {
		w !,"插入失败,SQLCODE=",SQLCODE 
	}
}

下面的嵌入式SQL示例使用主机变量插入具有两个字段值的行。这里使用的插入语法指定了COLUMN=VALUE对:

ClassMethod Insert5()
{
	#SQLCompile Path=Sample
	n SQLCODE,%ROWCOUNT,%ROWID
	s x = "Sam"
	s y = "57555"
	&sql(
		INSERT INTO MyKids 
		SET 
			KidName = :x, KidDOB = :y 
	)
	if SQLCODE = 0 {
		w !,"插入成功"
		w !,"Row count=",%ROWCOUNT
		w !,"Row ID=",%ROWID
		q 
	} elseif SQLCODE = -119 {
		w !,"未写入重复记录",!
		w %msg,!
		q 
	} else {
		w !,"插入失败,SQLCODE=",SQLCODE 
	}
}

下面的嵌入式SQL示例使用主机变量数组插入具有三个字段值的行。数组元素按列顺序编号。请注意,用户提供的数组值以myarray(2)开头;第一个数组元素对应于RowID列,该列是自动提供的,不能由用户定义:

ClassMethod Insert6()
{
	#SQLCompile Path=Sample
	n SQLCODE, %ROWCOUNT, %ROWID
	s myarray(2) = "Deborah"
	s myarray(3) = 60200
	s myarray(4) = "Bowie"
	&sql(
		INSERT INTO MyKids VALUES :myarray()
	)
	if SQLCODE = 0 {
		w !,"插入成功"
		w !,"Row count=",%ROWCOUNT
		w !,"Row ID=",%ROWID
		q 
	} elseif SQLCODE = -119 {
		w !,"未写入重复记录",!
		w %msg,!
		q 
	} else {
		w !,"插入失败,SQLCODE=",SQLCODE 
	}
}

下面的动态SQL示例使用%SQL.Statement类插入具有三个字段值的行。请注意,表架构名称在%New()方法中作为架构搜索路径提供:

ClassMethod Insert7()
{
	s x = "Noah"
	s y = "61000"
	s z = "Luna"
	s sqltext = "INSERT INTO MyKids (KidName,KidDOB,KidPetName) VALUES (?,?,?)"
	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(x, y, z)
	if rtn.%SQLCODE = 0 {
		w !,"插入成功"
		w !,"Row count=",rtn.%ROWCOUNT
		w !,"Row ID=",rtn.%ROWID 
	} elseif rtn.%SQLCODE = -119 {
		w !,"未写入重复记录",!,rtn.%Message
		q 
	} else {
		w !,"插入失败,SQLCODE=",rtn.%SQLCODE 
	}
}

下面的嵌入式SQL示例显示插入的记录,然后删除SQLUser.MyKids表:

ClassMethod Insert8()
{
	s myquery = "SELECT * FROM SQLUser.MyKids"
	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"
	&sql(DROP TABLE SQLUser.MyKids)
	if SQLCODE = 0 {
		w !,"Deleted table"
		q 
	} else {
		w !,"Table delete failed, SQLCODE=",SQLCODE 
	}
}

DHC-APP>d ##class(PHA.TEST.SQLCommand).Insert8()
KidName KidDOB  KidPetName
Molly   60000   no pet
Josie   40100   Fido
Sam     57555   no pet
Deborah 60200   Bowie
Noah    61000   Luna
 
5 Rows(s) Affected
End of data
Deleted table

下面的嵌入式SQL示例演示了主机变量数组的使用。请注意,对于主机变量数组,可以使用带有未指定最后一个下标的动态本地数组来传递要在运行时插入的值数组。例如:

ClassMethod Insert9()
{
  n SQLCODE,%ROWCOUNT,%ROWID
  &sql(INSERT INTO Sample.Employee VALUES :emp('profile',))
  w !,"SQL Error code: ",SQLCODE," Row Count: ",%ROWCOUNT
}

使插入的“Employee”行中的每个字段设置为:

emp("profile",col)

其中,“col”Sample.Employee表中字段的列号。

下面的示例显示如何将SELECT查询的结果用作INSERT语句的数据输入,从而为多行提供数据:

INSERT INTO StudentRoster (NAME,GPA,ID_NUM)
     SELECT FullName,GradeAvg,ID
     FROM temp WHERE SchoolYear = '2004'
0
0 128
文章 姚 鑫 · 十月 23, 2021 12m read

第五十四章 SQL命令 INSERT(三)

SQLCODE错误

默认情况下,INSERT是要么全有要么全无的事件:要么完全插入行,要么根本不插入行。 IRIS返回一个状态变量SQLCODE,指示插入是成功还是失败。要将行插入到表中,插入操作必须满足所有表、字段名和字段值要求,如下所示。

表:

  • 该表必须已经存在。尝试插入到不存在的表会导致SQLCODE-30错误。
  • 不能将该表定义为READONLY。尝试编译引用ReadOnly表的插入会导致SQLCODE-115错误。请注意,此错误是在编译时发出的,而不是在执行时发出的。
  • 如果通过视图更新表,则不能将该视图定义为只读。尝试这样做会导致SQLCODE-35错误。如果视图基于分割表,则不能通过使用CHECK OPTION定义的视图进行插入。尝试这样做会导致SQLCODE-35,其中不允许基于带有CHECK选项条件的切片表的视图(sample.myview)使用%msg INSERT/UPDATE/DELETE
  • 必须具有适当的权限才能插入表

字段名称:

  • 该字段必须存在。尝试插入不存在的字段会导致SQLCODE-29错误。
  • 插入必须指定所有必填字段。尝试插入行而不为必填字段指定值会导致SQLCODE-108错误。
  • 插入不能包含重复的字段名称。尝试插入包含两个同名字段的行会导致SQLCODE-377错误。
  • 插入不能包含定义为READONLY的字段。尝试编译引用READONLY字段的插入会导致SQLCODE-138错误。请注意,此错误现在在编译时发出,而不是仅在执行时发出。使用链接表向导链接表时,可以选择将字段定义为只读。源系统上的字段可能不是只读的,但如果IRIS将链接表的字段定义为只读,则尝试引用此字段的INSERT将导致SQLCODE-138错误。

字段值:

  • 每个字段值都必须通过数据类型验证。尝试插入不适合该字段数据类型的字段值会导致SQLCODE-104错误。请注意,这仅适用于插入的数据值;如果采用字段的默认值,则不必通过数据类型验证或数据大小验证。

    • 数据类型不匹配:决定是否合适的是字段的数据类型,而不是插入数据的类型。例如,除非字符串通过当前模式的日期验证,否则尝试将字符串数据类型值插入日期字段会失败;但是,尝试将日期数据类型值插入字符串字段会成功,将日期作为文字字符串插入。可以使用CONVERT函数将数据转换为目标数据类型。
    • 数据大小不匹配:数据值必须在字段的MAXLENMAXVALMINVAL范围内。例如,试图将长度超过24个字符的字符串插入到定义为VARCHAR(24)的字段中,或试图将大于127个字符的数字插入到定义为TINYINT的字段中,将导致SQLCODE-104错误。
    • 数字类型不匹配:如果通过ODBCJDBC提供了无效的双精度数,则会出现SQLCODE-104错误。
  • 每个字段值必须将显示传递到逻辑模式转换。尝试以无法转换为逻辑存储值的格式插入字段值会导致SQLCODE-146错误(对于日期)或SQLCODE-147错误(对于时间)。

  • 每个字段值都必须通过数据约束验证:

    • 必须为定义为NOT NULL的字段提供数据值。如果没有默认值,则不指定数据值将导致SQLCODE-108错误,表明没有指定必填字段。
    • 字段值必须符合唯一性约束。尝试在具有唯一性约束的字段(或字段组)中插入重复字段值会导致SQLCODE-119错误。如果字段具有唯一数据约束,或者如果已将唯一字段约束应用于一组字段,则返回此错误。如果为唯一字段或主键字段指定了重复的值,或者未指定值并且第二次使用该字段的默认值将提供重复的值,则可能会发生此错误。SQLCODE-119%msg字符串包括违反唯一性约束的字段和值。例如:<Table 'Sample.MyTable', Constraint 'MYTABLE_UNIQUE3', Field(s) FullName="Molly Bloom"; failed unique check> or <Table 'Sample.MyTable', Constraint 'MYTABLE_PKEY2', Field(s) FullName="Molly Bloom"; failed unique check>
    • 使用VALUELIST参数定义为永久类属性的字段只能接受VALUELIST中列出的值之一作为有效值,或者不提供任何值(NULL)VALUELIST有效值区分大小写。指定与VALUELIST值不匹配的数据值会导致SQLCODE-104字段值未通过验证错误。
  • 数字以规范形式插入,但可以使用前导和尾随零以及多个前导符号指定。但是,在SQL中,两个连续的减号被解析为单行注释指示符。因此,尝试使用两个连续的前导减号指定一个数字会导致SQLCODE-12错误。

  • 默认情况下,INSERT不能为系统生成值的字段指定值,例如RowID、IDKeyIdentity字段。默认情况下,尝试为这些字段中的任何一个插入非空字段值都会导致SQLCODE-111错误。尝试为其中一个字段插入NULL会导致IRIS使用系统生成的值覆盖NULL;插入成功完成,并且不会发出错误代码。

如果定义了数据类型为ROWVERSION的字段,则在插入行时会自动为其分配系统生成的计数器值。尝试将值插入ROWVERSION字段会导致SQLCODE-138错误。

可以使IDENTITY字段接受用户指定的值。 通过设置SetOption(“IdentityInsert”)方法,您可以覆盖IDENTITY字段的默认约束,并允许将唯一整数值插入IDENTITY字段。 (可以通过调用GetOption(“IdentityInsert”)方法返回该约束的当前设置。) 插入IDENTITY字段值将更改IDENTITY计数器,以便后续系统生成的值从这个用户指定的值递增。 试图为IDENTITY字段插入NULL将产生SQLCODE -108错误。

IDKey数据有以下限制: 因为索引中的多个IDKey字段是用“||”(双竖条)字符分隔的,所以不能在IDKey字段数据中包含这个字符串。

插入不能包含值违反外键引用完整性的字段,除非指定了%NOCHECK关键字,或者外键是用NOCHECK关键字定义的。 否则,尝试违反外键引用完整性的插入将导致SQLCODE -121错误,并带有%msg,如下所示:<Table 'Sample.MyTable', Foreign Key Constraint 'MYTABLEFKey2', Field(s) FULLNAME failed referential integrity check>

  • 字段值不能是子查询。 试图将子查询指定为字段值将导致SQLCODE -144错误。

插入操作

Privileges

要将一行或多行数据插入到表中,您必须拥有该表的表级特权或列级特权。

表级权限

  • 用户必须对指定的表具有INSERT权限。
  • 如果使用SELECT查询从另一个表插入数据,用户必须对该表具有SELECT权限。

如果用户是该表的Owner(创建者),则自动授予该用户对该表的所有特权。 否则,必须向用户授予该表的权限。 如果不这样做,将导致一个带有%msgSQLCODE -99错误。 可以通过调用%CHECKPRIV命令来确定当前用户是否具有适当的特权。 可以使用GRANT命令分配用户表权限。

要插入到分片表,您必须对目标表具有insert权限。 如果没有这些权限会出现SQLCODE -253错误:Sharded INSERT/UPDATE/DELETE run-time error

表级特权相当于(但不完全相同)在表的所有列上拥有列级特权。

列级权限

如果没有表级的INSERT权限,则必须对表中的至少一列具有列级的INSERT权限。 要将指定的值插入到列中,必须对该列具有列级insert权限。 只有具有INSERT权限的列才能接收INSERT命令中指定的值。

如果对指定的列没有列级INSERT权限, SQL将插入列的默认值(如果定义了)或NULL(如果没有定义默认值)。 如果对没有默认值且定义为not NULL的列没有INSERT权限, IRIS会在Prepare时间发出SQLCODE -99 (privilege Violation)错误。

如果INSERT命令指定结果集SELECTWHERE子句中的字段,则如果这些字段不是数据插入字段,则必须具有这些字段的SELECT权限,如果这些字段包含在结果集中,则必须具有这些字段的SELECTINSERT权限。

当属性被定义为ReadOnly时,相应的表字段也被定义为ReadOnly。 只读字段只能使用InitialExpressionSqlComputed赋值。 尝试为具有列级ReadOnly (SELECT或REFERENCES)权限的字段插入值将导致SQLCODE -138错误:无法为只读字段插入/更新值。

可以使用%CHECKPRIV来确定是否具有适当的列级特权。

快速插入

当使用JDBC在表中插入行时 IRIS默认情况下会自动执行高效的Fast Insert操作。 Fast Insert将插入的数据的规范化和格式化从服务器转移到客户机。 然后,服务器可以直接将表的整行数据设置为全局数据,而无需对服务器进行操作。 这将这些任务从服务器转移到客户机上,可以显著提高INSERT性能。 由于客户端承担了格式化数据的任务,因此在客户端环境中可能会出现不可预见的使用量增加。 如果有问题,可以使用FeatureOption属性禁用快速插入。

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

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

如果Fast Insert是活动的,则使用缓存查询执行的Insert将使用Fast Insert执行。 生成缓存查询的初始INSERT不是使用Fast INSERT执行的。 这使能够比较初始插入与使用缓存查询执行的后续Fast Inserts的性能。 如果不支持快速插入(出于以下原因),则执行普通插入。

快速插入必须在表上执行。 不能在可更新视图上执行。 当表具有以下任何特征时,不执行快速插入:

  • 该表使用嵌入式(嵌套)存储结构(%SerialObject)。
  • 该表是一个链接的表。
  • 该表是子表。
  • 该表有一个显式定义的多字段IDKEY索引。
  • 该表有一个SERIAL (%Counter)AUTO_INCREMENT%RowVersion字段。
  • 该表有一个属性(字段),带有定义的VALUELIST参数。
  • 表有一个已定义的插入触发器。
  • 该表执行字段值的LogicalToStorage转换。
  • 这个表是一个Shard Master表。

如果Insert语句具有以下特征之一,则不能执行快速插入:

  • 它指定一个流字段((数据类型%stream.GlobalCharacter%Stream.GlobalBinary)、集合字段(列表或数组)或只读字段。 这些类型的字段可以存在于表中,但不能在INSERT中指定。
  • 它指定一个用双括号括起来的字面值,禁止字面值替换。 例如,((A))
  • 它指定一个省略日期值的{ts}时间戳值。
  • 它包括一个DEFAULT VALUES子句。

对于SQL xDBC语句审计事件,使用Fast INSERT接口的INSERT语句具有SQL fastINSERT语句的描述。 如果使用Fast Insert接口,则Audit事件不包括任何参数数据,但包括消息参数值对于fastInsert语句不可用。

参照完整性

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

此设置不适用于用NOCHECK关键字定义的外键。

在INSERT操作期间,对于每个外键引用,都会在引用表中相应的行上获得一个共享锁。 在执行引用完整性检查和插入该行时,此行被锁定。 然后释放锁(直到事务结束才持有锁)。 这确保了引用的行不会在引用完整性检查和插入操作完成之间发生更改。

但是,如果指定了%NOLOCK关键字,则不会对指定的表或引用表中相应的外键行执行锁操作。

子表插入

在对子表执行INSERT操作期间,父表中相应行的共享锁将被获取。 在插入子表行时,此行被锁定。 然后释放锁(直到事务结束才持有锁)。 这确保在插入操作期间不会更改引用的父行。

原子性

默认情况下,INSERTUPDATEDELETETRUNCATE TABLE是原子操作。 INSERT要么成功完成,要么回滚整个操作。 如果不能插入任何指定的行,则不插入任何指定的行,并且数据库恢复到发出INSERT之前的状态。

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

  • 1IMPLICIT (autocommit on)——默认行为,如上所述。 每个INSERT构成一个单独的事务。
  • 2EXPLICIT (autocommit off)——如果没有事务在进行,INSERT会自动启动一个事务,但必须显式地COMMITROLLBACK来结束事务。 在EXPLICIT模式下,每个事务的数据库操作数是用户定义的。
  • 0NONE(没有自动事务)——调用INSERT时不会启动任何事务。 INSERT操作失败可能会使数据库处于不一致的状态,一些指定的行被插入,而一些未插入。 要在此模式中提供事务支持,必须使用START transaction来启动事务,并使用COMMITROLLBACK来结束事务。

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

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

ClassMethod Insert1()
{
	s stat = $SYSTEM.SQL.SetOption("AutoCommit",$RANDOM(3),.oldval)
	if stat '= 1 {
		w "SetOption 失败:" 
		d $System.Status.DisplayError(stat) 
		q
	}
	s x = $SYSTEM.SQL.GetOption("AutoCommit")
	if x = 1 {
		w "默认的原子性行为r",!
		w "自动提交或回滚" 
	} elseif x = 0 {
		w "没有启动事务,就没有原子性:",!
		w "DELETE失败会导致数据库不一致",!
		w "不支持回滚" 
	} else { 
		w "需要显式提交或回滚" 
	}
}

事务锁

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

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

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

  • “E”类型的锁升级: IRIS使用这种类型的锁升级,如果以下条件为真: 持久性(可以从Management Portal SQL模式显示的Catalog Details中确定这一点)。 (2)类要么不指定IDKey索引,要么指定单一属性的IDKey索引。 “E”类型的锁升级在ObjectScript Reference中的lock命令中进行了描述。
  • 传统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,然后指定UNLOCK TABLE(不带IMMEDIATE关键字,以便表锁一直持续到事务结束),然后使用%NOLOCK选项执行插入操作。

自动锁升级旨在防止锁表溢出。 但是,如果执行大量插入操作导致出现<LOCKTABLEFULL>错误,INSERT将发出SQLCODE -110错误。

行级安全性

IRIS行级安全性允许INSERT添加行,即使定义了行安全性,也不允许随后访问该行。为确保INSERT不会阻止随后对该行进行SELECT访问,建议通过具有WITH CHECK选项的视图执行INSERT

Microsoft Access

要使用INSERT通过Microsoft Access将数据添加到 IRIS表格中,请将表格RowID字段标记为专用,或者在一个或多个附加字段上定义唯一索引。

0
0 220
文章 姚 鑫 · 十月 22, 2021 6m read

第五十三章 SQL命令 INSERT(二)

流数据

可以将以下类型的数据值插入到流字段中:

  • 对于任何表:字符串文字或包含字符串文字的主机变量,例如:
    set literal="Technique 1"

    //do the insert; use a string
    &sql(INSERT INTO MyStreamTable (MyStreamField) VALUES (:literal))
  • 对于非切片表:对流对象的对象引用(OREF)。 IRIS打开此对象并将其内容复制到新的流字段中。例如:
    set oref=##class(%Stream.GlobalCharacter).%New()
    do oref.Write("Technique non-shard 1")

    //do the insert; use an actual OREF
    &sql(INSERT INTO MyStreamTable (MyStreamField) VALUES (:oref))

或流的OREF的字符串版本,例如:

    set oref=##class(%Stream.GlobalCharacter).%New()
    do oref.Write("Technique non-shard 2")

    //next line converts OREF to a string OREF
    set string=oref_""

    //do the insert
    &sql(INSERT INTO MyStreamTable (MyStreamField) VALUES (:string))
  • 对于切片表:使用存储在^IRIS.Stream.Shard全局中的临时流对象的对象ID(OID):
    SET clob=##class(%Stream.GlobalCharacter).%New("Shard")
    DO clob.Write("Technique Sharded Table 1")
    SET sc=clob.%Save() // Handle $$$ISERR(sc)
      set ClobOid=clob.%Oid()
    //do the insert
    &sql(INSERT INTO MyStreamTable (MyStreamField) VALUES (:ClobOid))

尝试插入定义不正确的流值会导致SQLCODE-412错误:常规流错误。

List 结构化数据

IRIS支持列表结构数据类型%List(数据类型类%Library.List)。这是一种压缩的二进制格式,不会映射到 SQL的相应本机数据类型。它对应于默认MAXLEN32749的数据类型VARBINARY。因此,动态SQL不能使用INSERTUPDATE来设置%LIST类型的属性值。

插入计数器值

表可以有选择地将一个字段定义为Identity。默认情况下,每当向表中插入行时,此字段都会从自动递增的表计数器接收整数。默认情况下,插入不能为此字段指定值。但是,此默认值是可配置的。更新操作不能修改身份字段值。此计数器由截断表操作重置。

表可以选择将一个字段定义为数据类型ROWVERSION。如果定义了该字段,插入操作会自动将命名空间范围的RowVersion计数器中的整数插入到该字段中。更新操作使用当前命名空间范围的RowVersion计数器值自动更新此整数。不能为ROWVERSION字段插入用户指定的值、计算的值或默认值。此计数器无法重置。

表可以有选择地将一个或多个字段定义为数据类型SERIAL(%Library.Counter)。默认情况下,每当向表中插入行时,此字段都会从自动递增的表计数器接收整数。但是,用户可以在插入期间为该字段指定整数值,覆盖表计数默认值。更新操作不能修改序列(%COUNTER)字段值。此计数器由截断表操作重置。

插入序列值SERIAL Values

插入操作可以为具有串行数据类型的字段指定下列值之一,结果如下:

  • 无值、0(零)或非数字值: IRIS忽略指定值,改为将此字段的当前串行计数器值递增1,并将生成的整数插入到该字段中。
  • 正整数值: IRIS将用户指定的值插入该字段,并将该字段的串行计数器值更改为该整数值。

因此,串行字段包含一系列递增的整数值。这些值不一定是连续的或唯一的。例如,以下是序列字段的一系列有效值:1、2、3、17、18、25、25、26、27。顺序整数是IRIS生成的或用户提供的;非顺序整数是用户提供的。如果希望序列字段值是唯一的,则必须对该字段应用唯一约束。

插入计算值

使用COMPUTECODE定义的字段可以在INSERT操作中插入值,除非对该字段进行了计算。如果为计算字段提供值,或者如果此字段具有默认值,则INSERT将存储此显式值。否则,将计算该字段值,如下所示:

  • COMPUTECODE:值在插入时计算并存储,值在更新时不变。
  • COMPUTECODE WITH COMPUTEONCHANGEVALUEINSERT时计算并存储,在UPDATE时重新计算并存储。
  • COMPUTECODE WITH DEFAULTCOMPUTEONCHANGE:默认值在插入时存储,值在更新时计算和存储。
  • COMPUTECODE WITH COMPUTECODE WITH COMPUTED或瞬态:不能为此字段插入值,因为没有存储值。查询时会计算该值。但是,作为插入操作的一部分, IRIS确实会对此字段执行验证:
    • 如果尝试在计算字段中插入值, IRIS将对提供的值执行验证,如果值无效则发出错误。如果值有效, IRIS将继续行插入:它不会在此字段中插入值,不会发出SQLCODE错误,并且会递增ROWCOUNT
    • 如果此类型的字段是外键约束的一部分,则会在插入期间计算此字段的值,以便执行引用完整性检查;不会存储此计算值。

如果计算代码包含编程错误(例如,除以零),则插入操作将失败,并显示SQLCODE-415错误。

默认值子句

可以将行插入到其所有字段值都设置为默认值的表中。定义了默认值的字段将设置为该值。未定义默认值的字段设置为NULL。这可以使用以下命令来完成:

INSERT INTO Mytable DEFAULT VALUES

使用NOT NULL约束定义的字段和未定义的默认值使用SQLCODE-108使此操作失败。

可以使用此语句插入使用UNIQUE约束定义的字段。如果字段定义了唯一约束且没有默认值,则重复调用会插入多行,并将此唯一字段设置为NULL。如果使用唯一约束和默认值定义字段,则此语句只能使用一次。第二次调用失败,返回SQLCODE-119

默认值为计数器字段插入具有系统生成的整数值的行。这些字段包括RowID、可选的标识字段、序列号(%Counter)字段和ROWVERSION字段。

插入查询结果:使用SELECT插入

通过将单个INSERTSELECT语句结合使用,可以使用单个INSERT将多行插入到表中。可以使用任何有效的SELECT查询。SELECT从一个或多个表中提取列数据,而INSERT在其表中创建包含该列数据的相应新行。对应的字段可以具有不同的列名和列长度,只要插入的数据适合插入表字段即可。相应的字段必须通过数据类型和长度验证;否则将生成SQLCODE-104错误。

可以通过在SELECT语句中指定TOP子句来限制插入的行数。还可以在SELECT语句中使用ORDER BY子句来确定TOP子句将选择哪些行。

可以使用GROUP BY子句仅插入一个(或多个)字段的唯一值。由于默认情况下,GROUP BY会将值转换为大写,以便进行分组,因此可能需要使用%Exact排序规则来保留插入值的字母大小写。下面的示例显示了这一点:

INSERT INTO Sample.UniquePeople (Name,Age) SELECT Name,Age FROM Sample.Person WHERE Name IS NOT NULL GROUP BY %EXACT Name

INSERT WITH SELECT操作将%ROWCOUNT变量设置为插入的行数(0或正整数)。

以下示例使用具有两个嵌入式SQL语句的例程。Create table创建一个新表SQLUser.MyStudents,然后INSERT用从Sample.Person提取的数据填充该表。(或者,可以使用$SYSTEM.SQL.Schema.QueryToTable()方法从现有表定义创建新表,并在单个操作中插入现有表中的数据。)

ClassMethod Insert()
{
	w !,"正在创建表"
	&sql(
		CREATE TABLE SQLUser.MyStudents 
		(
			StudentName VARCHAR(32),
			StudentDOB DATE,
			StudentAge INTEGER COMPUTECODE 
			{
				SET {StudentAge} = $PIECE(($PIECE($H, ",", 1) - {StudentDOB}) / 365, ".", 1)
			}
			CALCULATED 
		)
	)
	if SQLCODE=0 {
		w !,"已创建表,SQLCODE=",SQLCODE 
	} elseif SQLCODE=-201 {
		w !,"表已存在,SQLCODE=",SQLCODE 
	}
	w !,"使用数据填充表"
	n SQLCODE,%ROWCOUNT,%ROWID
	&sql(
		INSERT INTO SQLUser.MyStudents 
		(
			StudentName,StudentDOB
		)
		SELECT Name,DOB
		FROM Sample.Person WHERE Age <= '21'
	)
	if SQLCODE=0 {
		w !,"插入的记录数=",%ROWCOUNT
		w !,"插入的最后一条记录的行ID=",%ROWID 
	} else {
		w !,"插入失败,SQLCODE=",SQLCODE 
	}
}

要显示数据,请转到管理门户,选择所需命名空间的全局选项。滚动到“SQLUser.MyStudentsD”并单击Data选项。

以下程序显示MyStudents表数据,然后删除该表:

SELECT * FROM SQLUser.MyStudents ORDER BY StudentAge
  &sql(DROP TABLE SQLUser.MyStudents)
  IF SQLCODE=0 {WRITE !,"Table deleted" }
  ELSE {WRITE !,"SQLCODE=",SQLCODE," ",%msg }

默认情况下,插入查询结果操作是原子操作。指定的所有行都插入到表中,或者没有插入任何行。例如,如果插入指定行中的一行会违反外键引用完整性,则插入将失败,并且不会插入任何行。此默认值是可修改的,如下所述。

将数据复制到复制表中

只要列顺序匹配且数据类型兼容,就可以使用INSERT WITH SELECT*将数据从表复制到复制表。列名不必匹配。

INSERT INTO Sample.DupTable SELECT * FROM Sample.SrcTable
  • 与数据值兼容的数据类型:例如,可以将整数字段中的整数数据插入到VARCHAR字段中。如果任何数据值与目标数据类型不兼容,插入将失败,并显示SQLCODE-104
  • 与数据值兼容的数据类型长度:定义的列数据长度不必彼此匹配,只需与实际数据匹配即可。例如,SrcTable可以具有列FullName VARCHAR(60),而DupTable可以具有对应的PersonName VARCHAR(40)。只要现有的FullName值都不超过40个字符,插入就会成功。如果任何FullName值超过40个字符,插入将失败,并显示SQLCODE-104
  • 兼容的列顺序:这两个表必须具有相同的列顺序。否则将生成SQLCODE-64错误。DDL CREATE TABLE操作按定义的顺序列出列。定义表的持久化类按字母顺序列出列。
  • 兼容列计数:目标表可以具有复制列之外的其他列。例如,SrcTable可以具有列FullName VARCHAR(60)Age INTEGERDupTable可以具有PersonName VARCHAR(60)Year INTEGERShoeSize INTEGER。但是,请注意,定义表的持久化类按字母顺序列出列。
  • 私有行ID:定义表时,RowID字段被定义为公共或私有(隐藏)。默认情况下,DDL CREATE TABLE操作将RowID定义为私有。默认情况下,定义表的持久化类将RowID定义为公共的;要使其成为私有的,必须在定义持久化类时指定SqlRowIdPrivate类关键字。

复制表的最简单方法是使用私有的RowID定义源表和目标表。但是,外键只能引用具有公共RowID的表。表复制操作的行为如下:

  • 如果源私有,目标私有:可以使用INSERTSELECTSELECT*将数据复制到复制表。
  • 如果源为PUBLIC,目标为PUBLIC:不能使用INSERT SELECT将数据复制到重复表。将生成SQLCODE-111错误。
  • 如果源为私有,目标为公共:不能使用INSERT SELECT将数据复制到重复表。将生成SQLCODE-111错误。
  • 如果Source是公共的而Destination是私有的:不能使用带有SELECT *INSERT SELECT将数据复制到重复表中。 会生成一个SQLCODE -64错误,因为RowID出现在一个选择列表中使该选择列表不兼容。 可以使用包含所有字段名(不包括RowID)的列表的INSERT SELECT将数据复制到重复表中。 但是,如果Source有一个外键public RowID,则不会为目标表保留外键关系。 目的地将拥有新的系统生成的RowIDs

如果Source具有外键public RowID,并且希望Destination具有相同的外键关系,则必须使用ALLOWIDENTITYINSERT=1参数定义Destination。将目标定义为持久类时,请指定参数ALLOWIDENTITYINSERT=1;。使用CREATE TABLE定义目标时,请指定%CLASSPARAMETER ALLOWIDENTITYINSERT=1。如果将表定义为ALLOWIDENTITYINSERT=1,则不能使用SetOption(“IdentityInsert”)方法更改此设置。

  • 定义这些表的持久化类是否为Final对将数据复制到复制表中没有任何影响。

此操作可用于将现有数据复制到重新定义的表中,该表将接受在原始表中无效的未来列数据值。

0
0 120
文章 姚 鑫 · 十月 21, 2021 10m read

第五十二章 SQL命令 INSERT(一)

向表中添加新行(或多行)。

大纲

INSERT [%keyword] [INTO] table
          SET column1 = scalar-expression1 
                 {,column2 = scalar-expression2} ...  |
          [ (column1{,column2} ...) ] 
                 VALUES (scalar-expression1 {,scalar-expression2} ...)  |
          VALUES :array()  |
          [ (column1{,column2} ...) ] query  |
          DEFAULT VALUES

参数

  • %keyword - 可选参数:%NOCHECK%NOFPLAN%NOINDEX%NOJOURN%NOLOCK%NOTRIGGER%PROFILE%PROFILE_ALL
  • table - 要对其执行插入操作的表或视图的名称。此参数可以是子查询。INTO关键字是可选的。表名(或视图名)可以是限定的(schema.table),也可以是不限定的(Table)。使用架构搜索路径(如果提供)或默认架构名称将非限定名称与其架构匹配。
  • column - 可选 - 与提供的值列表顺序对应的列名或以逗号分隔的列名列表。如果省略,值列表将按列号顺序应用于所有列。
  • scalar-expression - 为相应列字段提供数据值的标量表达式或以逗号分隔的标量表达式列表。
  • :array() - 仅嵌入式SQL-指定为主机变量的值的动态本地数组。必须未指定数组的最低下标级别。因此:myupdate():myupdate(5,):myupdate(1,1,)都是有效的规范。
  • query - 一种选择查询,其结果集为一个或多个新行的相应列字段提供数据值。

描述

INSERT语句有两种使用方式:

  • 单行插入会向表中添加一个新行。它为所有指定的列(字段)插入数据值,并将未指定的列值默认为NULL或定义的默认值。它将%ROWCOUNT变量设置为受影响的行数(始终为10)。
  • 带有SELECTINSERT会向表中添加多个新行。它为查询结果集中每一行的所有指定列(字段)插入数据值,并将未指定的列值默认为NULL或定义的默认值。INSERT语句与SELECT查询的结合使用通常用于用从其他表中提取的现有数据填充表,如下面的“插入查询结果”部分所述。

INSERT OR UPDATE

INSERTUPDATE语句是INSERT语句的变体,它同时执行INSERTUPDATE操作。首先,它尝试执行插入操作。如果INSERT请求由于唯一键冲突而失败(对于某个唯一键的字段,存在与为INSERT指定的行具有相同值的行),则它会自动转换为该行的UPDATE请求,并且INSERTUPDATE使用指定的字段值更新现有行。

INSERTUPDATE不支持快速插入。

%Keyword字选项

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

  • %NOCHECK-不执行唯一值检查和外键引用完整性检查。也不执行针对数据类型、最大长度、数据约束和其他验证条件的列数据验证。通过视图执行插入时,不执行视图的WITH CHECK选项验证。

注意:由于使用%NOCHECK可能导致无效数据,因此只有在从可靠的数据源执行批量插入或更新时才应使用此%Keyword字参数。

用户必须具有当前命名空间的相应%NOCHECK管理权限才能应用此限制。否则将导致SQLCODE-99错误,因为%msg用户‘name’没有%NOCHECK权限

如果希望在指定%NOCHECK时防止插入非唯一数据值,请在插入之前执行EXISTS检查。

如果只希望禁用外键引用完整性检查,请使用$SYSTEM.SQL.SetFilerRefIntegrity()方法,而不是指定%NOCHECK。或者,可以使用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参数。多个参数由空格分隔。

表参数

可以指定要直接插入到表中的表参数、通过视图插入的表参数或通过子查询插入的表参数。如创建视图中所述,通过视图插入受要求和限制的约束。下面是使用子查询代替TABLE参数的INSERT示例:

INSERT INTO (SELECT field1 AS ff1 FROM MyTable) (ff1) VALUES ('test')

子查询目标必须是可更新的,遵循用于确定视图的查询是否可更新的相同标准。尝试使用不可更新的视图或子查询进行插入会生成SQLCODE-35错误。

不能在表参数中指定表值函数或联接语法。

赋值

本节介绍如何在INSERT操作期间将数据值分配给列(字段):

  • 值赋值语法描述将数据值指定为列(字段)的文字的各种语法选项。
  • 显示到逻辑数据的转换
  • %SerialObject属性
  • 非显示字符
  • 特殊变量
  • 流数据
  • 列出结构化数据
  • IdentityROWVERSION和串行计数器
  • 计算字段值
  • 默认值子句 如果省略COLUMN LIST参数,则INSERT将假定按列号顺序插入所有列。如果指定列列表,则各个值必须在位置上与列列表中的列名相对应。

值赋值语法

插入记录时,可以通过多种方式为指定列赋值。默认情况下,所有未指定的列必须接受NULL或具有定义的默认值。

  • 显式列名使用SET关键字,将一个或多个COLUMN=标量-表达式对指定为逗号分隔的列表。例如:
SET StatusDate='05/12/06',Status='Purged'
  • 使用VALUES关键字显式列名,指定与相应的标量表达式列表相等的列列表。 例如:
(StatusDate,Status) VALUES ('05/12/06','Purged')

当将标量表达式值赋给列列表时,每个指定的列必须有一个标量表达式。

  • 没有列名。在使用不带列列表的VALUES关键字时,请按顺序指定与表的字段相对应的标量表达式列表。例如:
VALUES ('Fred Wang',65342,'22 Main St. Anytown MA','123-45-6789')

必须按列号顺序指定值。必须为采用用户提供的值的每个基表列指定值;使用列顺序的插入不能采用定义的字段默认值。如果指定的值少于表列的数量,则会发出SQLCODE-62错误。如果指定的值多于表列的数量,则会发出SQLCODE-116错误。

RowID列不能由用户指定,因此不包括在此语法中。

默认情况下,不能使用此语法填充具有定义的标识字段或RowVersion字段的表。如果定义了其中一个字段,则如果没有为这些字段指定值,此INSERT语法将发出SQLCODE-62错误;如果确实为这些字段指定值,此INSERT语法将发出SQLCODE-138无法插入/更新只读字段的值错误。(身份字段可以配置为允许用户提供的值;请参阅身份字段。)

可以使用此语法填充具有定义的序列(%COUNTER)字段或%AutoIncrement字段的表,但必须为这些计数器字段指定用户提供的值。

  • 没有列名。当使用不带列列表的VALUES关键字时,请指定一个标量表达式的动态本地数组,该数组隐式对应于按列顺序的行的列。例如:
VALUES :myarray()

此值赋值只能使用主机变量从嵌入式SQL执行。与所有其他值赋值不同,这种用法允许将指定要插入哪些列的时间推迟到运行时(通过在运行时填充数组)。所有其他类型的插入都需要指定准备插入时要插入的列。此语法不能与链接表一起使用;尝试这样做会导致SQLCODE-155错误。

必须按列号顺序指定值。必须为采用用户提供的值的每个基表列指定值;使用列顺序的插入不能采用定义的字段默认值。提供的数组值必须以array(2)开头。第1列是RowID字段;不能为RowID字段指定值。

如果指定列名和相应的数据值,则可以省略定义了默认值或接受NULL的列。INSERT可以为大多数字段数据类型插入默认值,包括流字段。

如果未指定列名,则数据值必须在位置上与定义的列列表相对应。必须为每个用户可指定的基表列指定值;不能使用定义的默认值。(当然,可以指定空字符串作为列值。)

显示到逻辑数据的转换

数据以逻辑模式格式存储。例如,日期存储为天数的整数,时间存储为午夜起的秒数,%list存储为编码字符串。大多数其他数据(如字符串和数字)不需要转换;无论当前模式如何,它们都以相同的格式输入和存储。

对于转换的数据,可以以逻辑模式(默认)输入数据,或者通过指定选择模式以更易于阅读的格式(显示模式或ODBC模式)输入数据。例如,通过指定选择模式,可以输入显示格式(例如2018年2/22/2018)、odbc格式(例如2018-02-22)或逻辑格式(例如64701)的日期。对于某些数据类型,还可以在ODBC或显示选择模式下以逻辑格式指定数据。可以在SQL执行环境中显式设置SELECT模式,如下所示:

  • ObjectScript程序中或从Terminal 接口:调用SetOption()方法,如下所示:设置SET status=$SYSTEM.SQL.Util.SetOption("SelectMode",n,.oldval),其中整数n0=逻辑(默认值)1=odbc2=显示
  • 在动态SQL中,指定%SelectMode=n属性,其中整数n0=逻辑(默认值)1=ODBC2=显示
  • 从SQL Shell中,指定SET SELECTMODE关键字,其中关键字=逻辑、ODBCDISPLAY
  • 从管理门户中选择系统资源管理器、SQL,然后使用显示模式下拉列表指定逻辑模式、ODBC模式或显示模式。

非逻辑模式格式的输入数据必须转换为逻辑模式格式进行存储。编译后的SQL支持将输入值从显示或ODBC格式自动转换为逻辑格式。无法转换的输入值会导致SQLCODE错误,例如SQLCODE-146SQLCODE-147。输入数据的自动转换需要两个因素:编译时,SQL必须指定运行时模式;执行时,SQL必须在逻辑模式环境中执行。

  • 在嵌入式SQL中,如果指定#SQLCompile Select=Runtime, IRIS将使用将输入值从显示格式转换为逻辑模式存储格式的代码编译SQL语句。 IRIS对单个值和值数组执行此模式转换。
  • SQL CREATE FunctionCREATE METHODCREATE PROCEDURE语句中,如果指定SELECTMODE运行时, IRIS将使用将输入值从显示格式转换为逻辑模式存储格式的代码编译SQL语句。

如果SQL执行环境处于逻辑模式,则数据以逻辑格式存储。这是所有 SQL执行环境的默认模式。

%SerialObject属性

将数据插入%SerialObject时,必须插入引用嵌入的%SerialObject的表(持久化类);不能直接插入%SerialObject。从引用表中,可以执行以下任一操作:

  • 使用引用字段将多个%SerialObject属性的值作为%List结构插入。例如,如果持久类具有引用包含特性StreetCityCountry(按顺序)的序列对象的特性PAddress,则插入set PAddress=$LISTBUILD(‘123 Main St.’,‘Newtown’,‘USA’)(PAddress)($LISTBUILD(‘123 Main St.’,‘Newtown’,‘USA’))(PAddress)(:Vallist)%List必须包含串行对象属性(或占位符逗号)的值,其顺序与这些属性在串行对象中指定的顺序相同。

此类型的插入可能不会执行%SerialObject属性值的验证。因此,强烈建议在使用%List结构插入%SerialObject属性值之后,使用$SYSTEM.SQL.Schema.ValidateTable()方法执行表数据验证。

  • 使用下划线语法可以按任意顺序为各个%SerialObject属性插入值。例如,如果持久类具有引用包含特性StreetCityCountry的序列对象的特性PAddress,则插入set PAddress_City=‘Newtown’,PAddress_Street=‘123 Main St.’,PAddress_Country=‘USA’。未指定的串行对象属性默认为NULL

此类型的插入执行%SerialObject属性值的验证。

非显示字符

可以使用CHAR函数和串联运算符插入非显示字符。例如,下面的示例插入一个由字母“A”、换行符和字母“B”组成的字符串:

INSERT INTO MyTable (Text) VALUES ('A'||CHAR(10)||'B')

请注意,要连接函数的结果,必须使用||连接运算符,而不是_连接运算符。

查询可以使用LENGTH$LENGTH函数确定是否存在非显示字符。

特殊变量

可以在列中插入以下特殊变量的值:

%TABLENAME%CLASSNAME伪字段变量关键字。%TABLENAME返回当前表名。%CLASSNAME返回与当前表对应的类名。

以下一个或多个ObjectScript特殊变量(或其缩写):$HOROLOG$JOB$NAMESPACE$TLEVEL$USERNAME$ZHOROLOG$ZJOB$ZNSPACE$ZPI$ZTIMESTAMP$ZTIMEZONE$ZVERSION

0
0 263
文章 姚 鑫 · 十月 20, 2021 6m read

第五十一章 SQL命令 HAVING(二)

In和%INLIST谓词

IN谓词用于将值与一系列非结构化的项进行匹配。

%INLIST谓词是 IRIS扩展,用于将值与列表结构的元素进行匹配。

使用任一谓词,都可以执行相等比较和子查询比较。

在中有两种格式。第一个用作使用与OR运算符链接在一起的多个相等比较的速记。例如:

SELECT Name, Home_State FROM Sample.Person
GROUP BY Home_State
HAVING Home_State IN ('ME','NH','VT','MA','RI','CT')

如果Home_State等于括号列表中的任意值,则计算为TRUE。列表元素可以是常量或表达式。排序规则适用于IN比较,因为它适用于相等性测试。默认情况下,IN比较使用字段定义的排序规则类型;默认情况下,字符串字段定义为SQLUPPER,不区分大小写。

当日期或时间用于IN谓词相等比较时,会自动执行适当的数据类型转换。如果HAVING子句字段是TIMESTAMP类型,则DATETIME类型的值将转换为TIMESTAMP。如果HAVING子句字段为DATE类型,则TIMESTAMPSTRING类型的值将转换为DATE。如果HAVING子句字段为TIME类型,则TIMESTAMPSTRING类型的值将转换为TIME

下面的示例都执行相同的相等比较并返回相同的数据。 groupby字段指定对于每个成功的相等比较只返回一条记录。 DOB字段的数据类型为Date:

SELECT Name,DOB FROM Sample.Person 
GROUP BY DOB
HAVING DOB IN ({d '2014-01-02'},{d '1990-04-25'})
SELECT Name,DOB FROM Sample.Person
GROUP BY DOB
HAVING DOB IN ({ts '2014-01-02 00:00:00'},{ts '1990-04-25 00:00:00'})

%INLIST谓词可用于对列表结构的元素执行相等比较。 %INLIST使用EXACT排序。 因此,默认情况下,%INLIST字符串比较是区分大小写的。

下面的例子使用%INLIST来匹配一个字符串值到FavoriteColors列表字段的元素:

SELECT Name,FavoriteColors FROM Sample.Person 
HAVING 'Red' %INLIST FavoriteColors

它返回FavoriteColors中包含元素“Red”的所有记录。

下面的嵌入式SQL示例将Home_State列值与northne(新英格兰北部各州)列表中的元素匹配:

ClassMethod Having()
{
	s northne = $lb("VT","NH","ME")
	&sql(
		DECLARE StateCursor CURSOR FOR 
		SELECT Name,Home_State
		INTO :name,:state FROM Sample.Person
		HAVING Home_State %INLIST :northne
	)
	&sql(OPEN StateCursor)
	q:(SQLCODE'=0)
	n %ROWCOUNT,%ROWID
	for { 
		&sql(FETCH StateCursor)
		q:SQLCODE  
		w !,"#",%ROWCOUNT," Name=",name," State=",state,!
	}
	w !,"Final Fetch SQLCODE: ",SQLCODE
	&sql(CLOSE StateCursor)
}

DHC-APP>d ##class(PHA.TEST.SQLCommand).Having()
 
#1 Name=Lepon,Jeff Z. State=NH
 
#2 Name=Ingleman,Terry A. State=NH
 
#3 Name=Jung,Keith W. State=NH
 
#4 Name=Xiang,Kirsten U. State=ME
 
#5 Name=Jackson,Ralph V. State=VT
 
#6 Name=Tesla,Geoffrey O. State=NH

还可以在子查询中使用IN%INLIST来测试列值(或任何其他表达式)是否等于任何子查询行值。 例如:

SELECT Name,Home_State FROM Sample.Person
HAVING Name IN 
 (SELECT Name FROM Sample.Employee
 HAVING Salary < 50000)

注意,子查询在SELECT列表中必须只有一个项。

%STARTSWITH谓词

IRIS %STARTSWITH比较操作符允许对字符串或数字的初始字符执行部分匹配。 下面的示例使用%STARTSWITH。 它根据年龄进行选择,然后为每个以“S”开头的Name返回一条记录:

SELECT Name,Age FROM Sample.Person
WHERE Age > 30
HAVING Name %STARTSWITH 'S'
ORDER BY Name

与其他字符串字段比较一样,%STARTSWITH比较不区分大小写。

Contains Operator ([)

Contains操作符是左括号符号:[。 它允许将子字符串(字符串或数字)匹配到字段值的任何部分。 比较总是区分大小写的。 下面的例子在HAVING子句中使用Contains操作符选择那些Home_State值包含“K”的记录,然后对这些状态执行%AFTERHAVING计数:

SELECT Home_State,COUNT(Home_State) AS States,
   COUNT(Home_State %AFTERHAVING) AS KStates
 FROM Sample.Person
 HAVING Home_State [ 'K'

FOR SOME谓词

HAVING子句的FOR SOME谓词决定是否根据一个或多个字段值的条件测试返回结果集。 该谓词的语法如下:

FOR SOME (table[AS t-alias]) (fieldcondition)

FOR SOME指定字段condition的值必须为true; 至少有一个字段值必须匹配指定的条件。 Table可以是单个表,也可以是逗号分隔的表列表,也可以是表别名。 Fieldcondition为指定表中的一个或多个字段指定一个或多个条件。 table参数和字段condition参数都必须用括号分隔。

下面的例子展示了FOR SOME谓词的用法:

SELECT Name,Age
FROM Sample.Person
HAVING FOR SOME (Sample.Person)(Age>20)
ORDER BY Age

在上面的示例中,如果至少有一个字段包含大于20Age值,则返回所有记录。 否则,不返回任何记录。

NULL 谓词

这将检测未定义的值。 你可以检测所有空值,或所有非空值:

SELECT Name, FavoriteColors FROM Sample.Person
HAVING FavoriteColors IS NULL 
SELECT Name, FavoriteColors FROM Sample.Person
HAVING FavoriteColors IS NOT NULL 
ORDER BY FavoriteColors

使用GROUP BY子句,可以为指定字段的每个非空值返回一条记录:

SELECT Name, FavoriteColors FROM Sample.Person
GROUP BY FavoriteColors
HAVING FavoriteColors IS NOT NULL 
ORDER BY FavoriteColors

EXISTS 谓词

它使用子查询来测试子查询是否计算为空集。

SELECT t1.disease FROM illness_tab t1 WHERE EXISTS 
 (SELECT t2.disease FROM disease_registry t2 
 WHERE t1.disease = t2.disease 
 HAVING COUNT(t2.disease) > 100) 

LIKE、%MATCHES和%PATTERN谓词

这三个谓词允许执行模式匹配。

  • LIKE允许使用文字和通配符进行模式匹配。 当希望返回包含已知字面值子字符串的数据值,或在已知序列中包含多个已知子字符串时,请使用LIKELIKE使用目标的排序规则进行字母大小写比较。
  • %MATCHES允许使用文字、通配符、列表和范围进行模式匹配。 当希望返回包含已知字面值子字符串的数据值,或包含一个或多个位于可能字符列表或范围内的字面值字符,或在已知序列中包含多个这样的子字符串时,请使用%MATCHES%MATCHES使用EXACT排序法进行字母大小写比较。
  • 允许指定字符类型的模式。 例如,'1U4L1",".A'(1个大写字母,4个小写字母,一个逗号,后面跟着任意数量的字母字符)。 如果希望返回包含已知字符类型序列的数据值,请使用%PATTERN。 当数据值不重要,但这些值的字符类型格式很重要时,%PATTERN特别有用。 PATTERN还可以指定已知的文字字符。 它使用EXACT排序法进行文字比较,这总是区分大小写的。

要与字符串的第一个字符进行比较,请使用%STARTSWITH谓词。

示例

下面的示例为每个至少有一个21岁以下的人的州返回一行。 对于每一行,它返回该州所有人的平均、最小和最大年龄。

 SELECT Home_State, MIN(Age) AS Youngest,
  AVG(Age) AS AvgAge, MAX(Age) AS Oldest
 FROM Sample.Person
 GROUP BY Home_State
 HAVING Age < 21
 ORDER BY Youngest

下面的示例为每个至少有一个21岁以下的人的州返回一行。 对于每一行,它返回该州所有人的平均、最小和最大年龄。 使用%AFTERHAVING关键字,它还返回该州21岁以下的人的平均年龄(AvgYouth),以及该州21岁以下最年长的人的年龄(OldestYouth)。

SELECT Home_State,AVG(Age) AS AvgAge,
   AVG(Age %AFTERHAVING) AS AvgYouth,
   MIN(Age) AS Youngest, MAX(Age) AS Oldest,
   MAX(Age %AFTERHAVING) AS OldestYouth
 FROM Sample.Person
 GROUP BY Home_State
 HAVING Age < 21
 ORDER BY AvgAge
0
0 112
文章 姚 鑫 · 十月 19, 2021 5m read

第五十章 SQL命令 HAVING(一)

对一组数据值指定一个或多个限制性条件的SELECT子句。

大纲

SELECT field
FROM table
GROUP BY field
HAVING condition-expression

SELECT aggregatefunc(field %AFTERHAVING)
FROM table
[GROUP BY field]
HAVING condition-expression

参数

  • condition-expression - 由一个或多个布尔谓词组成的表达式,用于控制要检索哪些数据值。

描述

可选的HAVING子句出现在FROM子句、可选的WHEREGROUP BY子句之后,可选的ORDER BY子句之前。

SELECT语句的HAVING子句限定或取消查询选择中的特定行。符合条件的行是条件表达式为真的行。条件表达式是一系列逻辑测试(谓词),它们可以通过ANDOR逻辑运算符链接起来。

HAVING子句类似于WHERE子句,它可以在组上操作,而不是在整个数据集上操作。因此,在大多数情况下,HAVING子句要么与使用%AFTERHAVING关键字的聚合函数一起使用,要么与GROUP BY子句结合使用,或者两者兼而有之。

HAVING子句条件表达式还可以指定聚合函数。WHERE子句条件表达式不能指定聚合函数。下面的示例显示了这一点:

SELECT Name,Age,AVG(Age) AS AvgAge
FROM Sample.Person
HAVING Age > AVG(Age)
ORDER BY Age

image

HAVING子句通常用于将子群体的聚合与整个群体的聚合进行比较。

指定字段

HAVING子句条件表达式或%AFTERHAVING关键字表达式中指定的字段必须指定为字段名或聚合函数。不能按列号指定字段或聚合函数。不能按列别名指定字段或聚合函数;尝试这样做会生成SQLCODE-29错误。但是,可以使用子查询定义列别名,然后在HAVING子句中使用该别名。例如:

SELECT Y AS TeenYear,AVG(Y %AFTERHAVING) AS AvgTeenAge FROM 
      (SELECT Age AS Y FROM Sample.Person WHERE Age<20) 
HAVING Y > 12 ORDER BY Y

image

选择项列表中的聚合函数

HAVING子句选择要返回的行。默认情况下,此行选择不确定选择项列表中的聚合函数的值。这是因为HAVING子句在SELECT-ITEM列表中的聚合函数之后进行解析。

在下面的示例中,只返回Age > 65的行。但AVG(年龄)是基于所有行计算的,而不仅仅是HAVING子句选择的行:

SELECT Name,Age,AVG(Age) AS AvgAge FROM Sample.Person
HAVING Age > 65
 ORDER BY Age

image

将它与WHERE子句进行比较,WHERE子句选择返回哪些行,以及在select-item列表的聚合函数中包含哪些行值:

SELECT Name,Age,AVG(Age) AS AvgAge FROM Sample.Person
WHERE Age > 65
ORDER BY Age

HAVING子句可以用于只返回聚合值的查询:

  • 聚合阈值:HAVING子句使用聚合阈值来确定是返回1行(包含查询聚合值)还是0行。 因此,可以使用HAVING子句只在达到聚合阈值时返回聚合计算。 下面的示例仅在表中至少有100行时返回表中所有行的Age值的平均值。 如果小于100行,所有行的Age值的平均值可能被认为没有意义,因此不应该返回:
SELECT AVG(Age) FROM Sample.Person HAVING COUNT(*)>99

image

  • 多行:带有聚合函数且没有GROUP BY子句的HAVING子句返回满足HAVING子句条件的行数。 聚合函数值是根据表中的所有行计算的:
SELECT AVG(Age) FROM Sample.Person HAVING %ID<10

image 这与带有聚合函数的WHERE子句相反,后者返回一行。 聚合函数值是根据满足WHERE子句条件的行计算的:

SELECT AVG(Age) FROM Sample.Person HAVING %ID<10

image

%AFTERHAVING

%AFTERHAVING关键字可以与选择项列表中的聚合函数一起使用,以指定在应用HAVING子句条件之后执行聚合操作。

SELECT Name,Age,AVG(Age) AS AvgAge,
 AVG(Age %AFTERHAVING) AS AvgMiddleAge
 FROM Sample.Person
 HAVING Age > 40 AND Age < 65
 ORDER BY Age

image

只有满足以下两个条件时,%AFTERHAVING关键字才会给出有意义的结果:

  • 选择项列表必须至少包含一个非聚合字段引用的项。 这个字段引用可以是FROM子句中指定的任何表中的任何字段、使用隐式连接(箭头语法)引用的字段、%ID别名或星号(*)。
  • HAVING子句条件必须应用至少一个非聚合条件。 因此,有HAVING Age>50, HAVING Age>AVG(Age), or HAVING Age>50 AND MAX(Age)>75是有效的条件,但有HAVING Age>50 OR MAX(Age)>75不是有效条件。

下面的示例使用带有GROUP BY子句的HAVING子句返回状态平均年龄,以及大于表中所有行平均年龄的人的状态平均年龄。 它还使用子查询返回表中所有行的平均年龄:

SELECT Home_State,(SELECT AVG(Age) FROM Sample.Person) AS AvgAgeAllRecs,
       AVG(Age) AS AvgAgeByState,AVG(Age %AFTERHAVING) AS AvgOlderByState 
FROM Sample.Person
GROUP BY Home_State
HAVING Age > AVG(Age)
ORDER BY Home_State

image

逻辑谓词

SQL谓词可分为以下几类:

  • Equality Comparison谓词
  • BETWEEN谓语
  • In%INLIST谓词
  • %STARTSWITH谓词
  • 包含运算符([)
  • FOR SOME谓词
  • NULL 谓词
  • EXISTS 谓词
  • LIKE, %MATCHES, and %PATTERN 谓词
  • %INSET and %FIND 谓词

注意:不能在HAVING子句中使用FOR SOME %ELEMENT集合谓词。此谓词只能在WHERE子句中使用。

谓词区分大小写

谓词使用为字段定义的排序规则类型。默认情况下,字符串数据类型字段使用SQLUPPER排序规则定义,该排序规则不区分大小写。

%INLISTCONTAINS运算符([)、%Matches%%PATTERN谓词不使用字段的默认排序规则。它们总是使用精确排序,这是区分大小写的。

两个文字字符串的谓词比较始终区分大小写。

谓词条件和%NOINDEX

可以使用%NOINDEX关键字作为谓词条件的前缀,以防止查询优化器在该条件上使用索引。 这在指定绝大多数行都满足的范围条件时非常有用。 例如,HAVING %NOINDEX Age >= 1

相等比较谓词

以下是可用的比较谓词:

谓词操作
=相等
<>不相等
!=不相等
>大于
<小于
>=大于等于
<=小雨等于

以下示例使用比较谓词。它为小于21岁的每个年龄返回一条记录:

SELECT Name, Age FROM Sample.Person
GROUP BY Age
HAVING Age < 21
ORDER BY Age

image

请注意,SQL根据排序规则(值的排序顺序)定义比较操作。如果两个值以完全相同的方式排序,则它们相等。如果一个值在第二个值之后排序,则该值大于另一个值。字符串数据类型字段排序规则基于字段的默认排序规则。默认情况下,它不区分大小写。因此,两个字符串字段值的比较或字符串字段值与字符串文字的比较(默认情况下)不区分大小写。例如,如果Home_State字段值是由两个字母组成的大写字符串:

ExpressionValue
'MA' = Home_StateTRUE for values MA.
'ma' = Home_StateTRUE for values MA.
'VA' < Home_StateTRUE for values VT, WA, WI, WV, WY.
'ar' >= Home_StateTRUE for values AK, AL, AR.

但是请注意,两个文字字符串的比较区分大小写:其中'ma'='MA'始终为false

BETWEEN谓语

这等效于大于或等于且小于或等于的配对。下面的示例使用BETWEEN谓词。它为1835岁(包括1835岁)的每个年龄返回一条记录:

SELECT Name, Age FROM Sample.Person
GROUP BY Age
HAVING Age BETWEEN 18 AND 35
ORDER BY Age

image

0
0 124
文章 姚 鑫 · 十月 18, 2021 8m read

第四十九章 SQL命令 GROUP BY

SELECT子句,它根据一个或多个列对查询的结果行进行分组。

大纲

SELECT ...
GROUP BY field {,field2}

参数

  • field - 从其中检索数据的一个或多个字段。 单个字段名或以逗号分隔的字段名列表。

描述

GROUP BYSELECT命令的一个子句。 可选的GROUP BY子句出现在FROM子句和可选的WHERE子句之后,可选的HAVINGORDER BY子句之前。

GROUP BY子句接受查询的结果行,并根据一个或多个数据库列将它们分成单独的组。 当将SELECTGROUP BY结合使用时,将为GROUP BY字段的每个不同值检索一行。 GROUP BYNULL(没有指定值)字段作为一个独立的值组。

GROUP BY子句在概念上类似于 IRIS聚合函数扩展关键字%FOREACH,但是GROUP BY操作整个查询,而%FOREACH允许在子填充上选择聚合,而不限制整个查询填充。

GROUP BY可以在INSERT命令的SELECT子句中使用。 不能在UPDATEDELETE命令中使用GROUP BY

指定字段

GROUP BY子句最简单的形式指定单个字段,如GROUP BY City。 这将为每个惟一的City值选择任意一行。 还可以指定以逗号分隔的字段列表,将其组合值视为单个分组术语。 它为每个CityAge值的唯一组合选择任意一行。 因此,GROUP BY City,Age返回与GROUP BY Age,City相同的结果。

字段必须通过列名指定。 有效的字段值包括以下内容:列名(GROUP BY City); %ID(返回所有行); 指定列名的标量函数(GROUP BY ROUND(Age,-1)); 指定列名的排序规则函数(GROUP BY %EXACT(City))。

不能通过列别名指定字段; 尝试这样做会产生SQLCODE -29错误。 不能通过列号指定字段; 这被解释为一个文字并返回一行。 不能指定聚合字段; 尝试这样做将生成SQLCODE -19错误。 不能指定子查询; 这被解释为一个文字并返回一行。

GROUP BY StreamField操作流字段的OID,而不是它的实际数据。 因为所有流字段oid都是唯一的值,GROUP BY对实际的流字段重复数据值没有影响。 GROUP BY StreamField将流字段为NULL的记录数量减少为一条记录。

GROUP BY子句可以使用箭头语法(- >)操作符在非基表的表中指定字段。 例如:GROUP BY Company->Name

GROUP BY子句中指定一个字面值作为字段值返回1行; 返回哪一行是不确定的。 因此,指定7'Chicago'''0NULL都返回1行。 但是,如果在逗号分隔的列表中指定一个字面值作为字段值,则该字面值将被忽略,并且GROUP BY将为指定字段名的每个惟一组合选择任意一行。

具有GROUP BY和DISTINCT BY的聚合函数

在计算聚合函数之前应用GROUP BY子句。 在下面的示例中,COUNT聚合函数计算每个GROUP BY组中的行数:

SELECT Home_State,COUNT(Home_State)
FROM Sample.Person
GROUP BY Home_State

在计算聚合函数之后应用DISTINCT BY子句。 在下面的例子中,COUNT聚合函数计算整个表中的行数:

SELECT DISTINCT BY(Home_State) Home_State,COUNT(Home_State)
FROM Sample.Person

为了计算整个表的聚合函数,而不是GROUP BY组,可以指定一个选择项子查询:

SELECT Home_State,(SELECT COUNT(Home_State) FROM Sample.Person)
FROM Sample.Person
GROUP BY Home_State

当选择列表由聚合字段组成时,不应将GROUP BY子句与DISTINCT子句一起使用。 例如,下面的查询旨在返回共享相同Home_State的不同数量的人:

/* 此查询不应用DISTINCT关键字 */
/* 这里提供了一个警示的例子  */
SELECT DISTINCT COUNT(*) AS mynum
FROM Sample.Person 
GROUP BY Home_State
ORDER BY mynum

这个查询没有返回预期的结果,因为它没有应用DISTINCT关键字。 要同时应用DISTINCT聚合和GROUP BY子句,请使用子查询,如下例所示:

SELECT DISTINCT *
FROM (SELECT COUNT(*) AS mynum
      FROM Sample.Person 
      GROUP BY Home_State) AS Sub
ORDER BY Sub.mynum

此示例成功返回共享相同Home_State的不同人数。 例如,如果任何Home_State被8个人共享,查询返回8。

如果查询仅由聚合函数组成且不返回表中的任何数据,则返回%ROWCOUNT=1,并为聚合函数返回一个空字符串(或0)值。 例如:

SELECT AVG(Age) FROM Sample.Person WHERE Name %STARTSWITH 'ZZZZ'

但是,如果这种类型的查询包含GROUP BY子句,它将返回%ROWCOUNT=0,并且聚合函数值仍未定义。

飘絮,字母大小写和优化

本节描述GROUP BY如何处理只有字母大小写不同的数据值。

  • 组合字母变体在一起(返回大写字母):

默认情况下,GROUP By根据创建字段时为其指定的排序规则将字符串值分组。 IRIS有一个默认的字符串排序规则,可以为每个名称空间设置; 所有名称空间的初始字符串排序规则默认值是SQLUPPER。 因此,除非另有说明,通常GROUP BY排序规则不区分大小写。

GROUP BY根据字段的大写字母排序规则,使用SQLUPPER排序规则对字段的值进行分组。 只有字母大小写不同的字段值被分组在一起。 分组字段值全部以大写字母返回。 这样做的性能优势在于允许GROUP BY为字段使用索引,而不是访问实际的字段值。 因此,只有在一个或多个选定字段的索引存在时才有意义。 它的结果是group by字段值全部以大写字母返回,即使实际数据值中没有一个都是大写字母。

  • 组合字母大小写变体在一起(返回实际的字母大小写):

GROUP BY可以将字母大小写不同的值分组在一起,并使用实际的字段字母大小写值返回分组的字段值(随机选择)。 这样做的好处是返回的值是实际值,显示数据中至少一个值的字母大小写。 它的性能缺点是不能使用字段的索引。 可以通过对select-item字段应用%EXACT排序函数来为单个查询指定这个值。

  • 不要将不同的字母组合在一起(返回实际的字母):

通过对GROUP BY字段应用%EXACT排序功能,GROUP BY可以对值进行区分大小写的分组。 这样做的好处是将每个字母变体作为一个单独的组返回。 它的性能缺点是不能使用字段的索引。

可以使用管理门户在系统范围内为包含GROUP BY子句的所有查询配置此行为。依次选择系统管理、配置、SQL和对象设置、SQL。查看和编辑GROUP BYDISTINCT查询必须生成原始值复选框。默认情况下,此复选框未选中。此默认设置按字母值的大写排序规则对字母值进行分组。(此优化也适用于DISTINCT子句。)。

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

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

以下示例显示了这些行为。这些示例假定Sample.Person包含具有Home_City字段的记录,该字段具有SQLUPPER排序规则,值为‘New York’‘New York’

SELECT Home_City FROM Sample.Person GROUP BY Home_City
/* 将Home_City值按其大写字母值组合在一起将以大写字母返回每个分组城市的名称。因此,返回‘NEW YORK’。   
SELECT %EXACT(Home_City) FROM Sample.Person GROUP BY Home_City
/*将Home_City值按其大写字母值组合在一起将返回以原始字母大小写表示的分组城市的名称。因此,可以返回‘New York’或‘new York’,但不能同时返回两者。*/

SELECT Home_City FROM Sample.Person GROUP BY %EXACT(Home_City)
/*将Home_City值按其原始字母大小写组合在一起将返回每个分组的城市的名称(原始字母大小写)。因此,‘New York’和‘New York’都作为单独的组返回。*/

%ROWID

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

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

DHC-APP>d ##class(PHA.TEST.SQLCommand).GroupBy()
 
RowID: 999 row count: 1 Name=O'Rielly,Chris H. State=MS
RowID: 999 row count: 2 Name=Orwell,John V. State=MT
RowID: 999 row count: 3 Name=Zevon,Heloisa O. State=MI
RowID: 999 row count: 4 Name=Kratzmann,Emily Z. State=MO
RowID: 999 row count: 5 Name=Hanson,George C. State=MD
RowID: 999 row count: 6 Name=Zucherro,Olga H. State=MN
RowID: 999 row count: 7 Name=Gallant,Thelma Q. State=MA
RowID: 999 row count: 8 Name=Xiang,Kirsten U. State=ME

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

事务提交的更改

包含GROUP BY子句的查询不支持READ COMMITTED隔离级别。在定义为READ COMMITTED的事务中,不带GROUP BY子句的SELECT语句仅返回已提交的数据修改;换句话说,它返回当前事务之前的数据状态。带有GROUP BY子句的SELECT语句返回所做的所有数据修改,无论它们是否已提交。

示例

下面的示例按名称的首字母对名称进行分组。它返回首字母、共享该首字母的姓名计数以及一个Name值的示例。名称使用其SQLUPPER排序规则进行分组,而不考虑实际值的字母大小写。请注意,名称SELECT-ITEM包含大写首字母;%Exact排序规则用于显示实际的Name值:

SELECT Name AS Initial,COUNT(Name) AS SameInitial,%EXACT(Name) AS Example
FROM Sample.Person GROUP BY %SQLUPPER(Name,2)
0
0 120
文章 姚 鑫 · 十月 17, 2021 7m read

第四十八章 SQL命令 GRANT(二)

GRANT COLUMN-权限

列权限授予用户或角色对指定表或视图上的指定列列表的指定权限。这允许访问某些表列,而不允许访问同一表的其他列。这提供了比GRANT OBJECT-PRIVICATION选项更具体的访问控制,后者定义了整个表或视图的权限。向被授权者授予权限时,应为表授予表级权限或列级权限,但不能同时授予两者。SELECTINSERTUPDATEREFERENCES权限可用于授予对单个列中数据的访问权限。

对具有GRANT OPTION的表具有SELECTINSERTUPDATEREFERENCES对象权限的用户可以向其他用户授予该表的列的相同类型的列权限。

可以指定单个列,也可以指定逗号分隔的列列表。列列表必须用括号括起来。列名可以按任意顺序指定,允许重复。将COLUMN特权授予已具有该特权的列不起作用。

以下示例授予两列的UPDATE权限:

GRANT UPDATE(Name,FavoriteColors) ON Sample.Person TO Deborah

可以授予表或视图的列特权。可以向任何类型的被授权者授予列权限,包括用户列表、角色列表、*_PUBLIC。但是,不能将星号(*)通配符用于权限、字段名或表名。

如果用户将新记录插入到表中,则只会将数据插入到已授予列权限的那些字段中。所有其他数据列都设置为定义的列默认值,如果没有定义的默认值,则设置为NULL。不能向RowIDIDENTITY列授予列级INSERTUPDATE权限。插入时, SQL会自动提供RowID和标识列值(如果需要)。

列级权限可以通过SQL GRANTREVOKE命令或通过 IRIS System Security授予或撤消。转到管理门户,依次选择System Administration、Security、Users(或System Administration、Security、Roles),选择所需用户或角色的名称,然后选择SQL Tables或SQL Views选项卡。从下拉列表中选择所需的命名空间。然后选择Add Columns按钮。在显示的窗口中,选择一个方案,选择一个表,选择一个或多个列,然后分配权限。

授予多个权限

可以使用单个GRANT语句指定以下权限组合:

  • 一个或多个角色。
  • 一个或多个表级权限和一个或多个列级权限。要指定多个表级和列级权限,该权限必须紧跟在列列表之前才能授予列级权限。否则,它将授予表级特权。
  • 一个或多个管理员权限。不能在同一GRANT语句中包含管理员权限和角色名称或对象权限。尝试这样做会导致SQLCODE-1错误。

以下示例授予Deborah表级SELECTUPDATE权限以及列级INSERT权限:

GRANT SELECT,UPDATE,INSERT(Name,FavoriteColors) ON Sample.Person TO Deborah

以下示例授予Deborah列级SELECTINSERTUPDATE权限:

GRANT SELECT(Name,FavoriteColors),INSERT(Name,FavoriteColors),UPDATE(FavoriteColors) ON Sample.Person TO Deborah

WITH GRANT OPTION子句

对象的所有者自动拥有该对象的所有权限。GRANT语句的TO子句指定要向其授予访问权限的用户或角色。在使用TO选项指定被授权者之后,可以选择指定WITH GRANT OPTION关键字子句,以允许被授权者也能够将相同的权限授予其他用户。可以将WITH GRANT OPTION关键字子句与对象权限或列权限一起使用。带CASCADEREVOKE命令可用于撤消这一系列级联授予的权限。

例如,可以使用以下命令向用户授予对Employees表的Chris%ALTERSELECTINSERT权限:

GRANT %ALTER, SELECT, INSERT
     ON EMPLOYEES
     TO Chris

为了使Chris也能够将这些权限授予其他用户,GRANT命令包含WITH GRANT OPTION子句:

GRANT %ALTER, SELECT, INSERT
     ON EMPLOYEES
     TO Chris WITH GRANT OPTION

可以使用%SQLCatalogPriv.SQLUsers()方法调用来查找GRANT语句的结果。

使用GRANT OPTION向模式授予权限允许被授权者能够将相同的模式权限授予其他用户。但是,它不允许被授权者授予该架构中指定对象的特权,除非用户已被显式授予该特定对象的特权(GRANT OPTION)。下面的示例显示了这一点:

  • 用户A和用户B在没有权限的情况下启动。
  • 使用GRANT OPTION授予用户对模式SAMPLESELECT权限。
  • 用户A可以向用户B授予对模式SAMPLESELECT权限。
  • 用户A无法将表Sample.Person的SELECT权限授予用户B。

WITH ADMIN OPTION子句

WITH ADMIN OPTION子句授予被授权者将其收到的相同权限授予其他人的权利。要授予系统权限,必须已被授予具有ADMIN选项的系统权限。

可以授予一个角色,如果这个角色已经通过ADMIN OPTION授予给,或者如果拥有%Admin_Secure:"U"资源。

授予WITH ADMIN OPTION将取代先前没有此选项的相同权限的授予。 因此,如果在没有WITH ADMIN OPTION的情况下授予一个用户特权,然后再将相同的特权授予WITH ADMIN OPTION用户,那么该用户就拥有WITH ADMIN OPTION权限。 但是,没有WITH ADMIN OPTION的授予不会取代之前使用该选项授予的相同权限。 要从特权中删除WITH ADMIN OPTION权限,必须撤销该特权,然后在不使用此条款的情况下重新授予该特权。

导出权限

可以使用$SYSTEM.SQL.Schema.ExportDDL()方法导出特权。 在此方法中指定表时, IRIS将导出为该表授予的所有表级特权和所有列级特权。

IRIS安全

在嵌入式SQL中使用GRANT之前,需要以具有适当特权的用户身份登录。 如果不这样做,将导致SQLCODE -99错误(特权冲突)。 使用$SYSTEM.Security.Login()方法为用户分配适当的权限:

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

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

执行权限

SQL特权只能通过ODBCJDBC和动态SQL (%SQL. statement)强制执行。

在系统范围内执行特权取决于$SYSTEM.SQL.Util.SetOption("SQLSecurity")方法调用的设置。 要确定当前设置,调用$SYSTEM.SQL.CurrentSettings(),它会显示一个SQL Security ON: setting

默认值是1 (Yes):用户只能对已被授予权限的表和视图执行操作。 这是该选项的推荐设置。 如果此选项设置为0 (No),则更改此设置后启动的任何新进程将禁用SQL Security。 这意味着禁止基于特权的表/视图安全性。 您可以在不指定用户的情况下创建表。 在本例中,管理门户将“_SYSTEM”分配为user,嵌入式SQL将“”(空字符串)分配为user。 任何用户都可以对表或视图执行操作,即使该用户没有这样做的特权。

示例

下面的示例创建用户、创建角色,然后将角色分配给用户。 如果用户或角色已经存在,则发出SQLCODE -118错误。 如果已经完成了特权或角色的分配,则不会发出错误(SQLCODE = 0)。

ClassMethod Grant1()
{
	d $SYSTEM.Security.Login("_SYSTEM","SYS")
CreateUser
	s x = $SYSTEM.SQL.UserExists("MarthaTest")
	if x = 0 {
		&sql(
			CREATE USER MarthaTest IDENTIFY BY birdpw
		)
		if SQLCODE '= 0 {
			w "创建用户 error: ",SQLCODE,!
	        q
	    }
	} else {
		w "用户MarthaTest已存在,但未更改其角色",!
		q 
	}
CreateRoleAndGrant
	&sql(
		CREATE ROLE workerbee
	)
	w !,"创建角色错误代码: ",SQLCODE
	&sql(
		GRANT %CREATE_TABLE TO workerbee
	)
	w !,"授予权限错误码: ",SQLCODE
	&sql(
		GRANT workerbee TO MarthaTest
	)
	w !,"授予角色错误码: ",SQLCODE
}

下面的示例显示了多个特权的分配。 它创建一个用户和两个角色。 一个GRANT语句将这些角色和一组admin权限分配给用户。 如果用户或角色已经存在,则发出SQLCODE -118错误。 如果已经完成了特权或角色的分配,则不会发出错误(SQLCODE = 0)。

ClassMethod Grant2()
{
   	d $SYSTEM.Security.Login("_SYSTEM","SYS")
CreateUser
	s x = $SYSTEM.SQL.UserExists("NoahTest")
	if x = 0 {
		&sql(CREATE USER NoahTest IDENTIFY BY birdpw)
		if SQLCODE '= 0 {
			w "创建用户 error: ",SQLCODE,!
	        q
	    }
	} else {
		w "用户NoahTest已存在,但未更改其角色",!
		q 
	}
Create2RolesAndGrant
   &sql(CREATE ROLE workerbee)
   w !,"CREATE ROLE 1 error code: ",SQLCODE
   &sql(CREATE ROLE drone)
   w !,"CREATE ROLE 2 error code: ",SQLCODE
   &sql(GRANT workerbee,drone,%CREATE_TABLE,%DROP_TABLE TO NoahTest)
   w !,"GRANT角色和权限错误代码: ",SQLCODE
}

下面的示例将当前命名空间中所有表上的所有7个基本权限授予所有当前定义的没有%all角色的用户:

   DO $SYSTEM.Security.Login("_SYSTEM","SYS")
    &sql(GRANT * ON * TO *)
0
0 135
文章 姚 鑫 · 十月 16, 2021 8m read

第四十七章 SQL命令 GRANT(一)

向用户或角色授予特权。

大纲

GRANT admin-privilege TO grantee [WITH ADMIN OPTION]

GRANT role TO grantee [WITH ADMIN OPTION] 

GRANT object-privilege ON object-list TO grantee [WITH GRANT OPTION]

GRANT SELECT ON CUBE[S] object-list TO grantee [WITH GRANT OPTION]

GRANT column-privilege (column-list) ON table TO grantee  [WITH GRANT OPTION]  

参数

  • grantee - 一个或多个用户或角色的逗号分隔列表。 有效值是用户列表、角色列表、“*”或_PUBLIC。 星号(*)指定当前定义的所有没有% all角色的用户。 _PUBLIC关键字指定所有当前定义的和尚未定义的用户。
  • admin-privilege - 被授予的管理级别特权或以逗号分隔的管理级别特权列表。 该列表可由下列一项或多项按任何顺序组成: %CREATE_METHOD%DROP_METHOD%CREATE_FUNCTION%DROP_FUNCTION%CREATE_PROCEDURE%DROP_PROCEDURE%CREATE_QUERY%DROP_QUERY%CREATE_TABLE %ALTER_TABLE, ``%DROP_TABLE%CREATE_VIEW%ALTER_VIEW%DROP_VIEW%CREATE_TRIGGER%DROP_TRIGGER%DB_OBJECT_DEFINITION,授予上述所有16个权限。 %NOCHECK%NOINDEX%NOLOCK%NOTRIGGER用于INSERT, UPDATEDELETE操作。 %BUILD_INDEXBUILD INDEX`命令授予特权。
  • role - 被授予权限的角色或以逗号分隔的角色列表。
  • object-privilege - 被授予的基本级别特权或以逗号分隔的基本级别特权列表。 该列表可以包含以下一个或多个:%ALTERDELETESELECTINSERTUPDATEEXECUTEREFERENCES。 可以使用“all [privileges]”或“*”作为参数值授予所有表和视图特权。 注意,只能授予多维数据集SELECT权限。
  • object-list - 为其授予对象特权的一个或多个表、视图、存储过程或多维数据集的逗号分隔列表。 可以使用SCHEMA关键字指定将对象特权授予指定模式中的所有对象。 可以使用" * "指定将对象特权授予当前命名空间中的所有表或所有非隐藏的存储过程。 注意,多维数据集对象列表需要CUBE(或cubes)关键字,并且只能被授予SELECT权限。
  • column-privilege - 向一个或多个列出的列授予基本权限。 可用选项有SELECTINSERTUPDATEREFERENCES
  • column-list - 由一个或多个列名组成的列表,用逗号分隔,用括号括起来。
  • table - 包含列列表列的表或视图的名称。

描述

GRANT命令将对指定的表、视图、列或其他实体执行指定任务的权限授予一个或多个指定的用户或角色。 可以进行以下基本操作:

  • 给用户授予特权。
  • 为角色授予特权。
  • 赋予用户角色。
  • 将一个角色赋予另一个角色,创建角色的层次结构。

如果为某个用户赋予特权,则该用户可以立即行使该特权。 如果为角色授予权限,则已被授予该角色的用户可以立即使用该权限。 如果取消特权,用户将立即失去该特权。 一个用户实际上只被授予一次特权。 多个用户可以多次授予一个用户相同的权限,但单个REVOKE会删除该权限。

特权是基于每个名称空间授予的。

SQL特权只能通过ODBCJDBC和动态SQL (%SQL. statement)强制执行。

因为GRANT准备和执行非常快,而且通常只运行一次,所以 IRIS不会在ODBCJDBC或动态SQL中为GRANT创建缓存查询。 执行GRANT命令时,会执行*的扩展。

GRANT admin-privilege

SQL管理(admin)权限适用于用户或角色。 任何没有绑定到任何特定对象的特权(因此是该用户或角色的一般权限)都被认为是管理特权。 这些特权是根据当前名称空间的每个名称空间授予的。

%DB_OBJECT_DEFINITION权限授予所有16个数据定义权限。 它没有授予%BUILD_INDEX%NOCHECK%NOINDEX%NOLOCK%NOTRIGGER权限,这些权限必须显式授予。

%BUILD_INDEX特权授予使用BUILD INDEX命令。 %NOCHECK%NOINDEX%NOLOCK%NOTRIGGER权限授予在INSERTUPDATEINSERT OR UPDATEDELETE语句的约束子句中使用这些选项。 它们对使用%NOINDEX关键字作为谓词条件的前言没有影响。 因为TRUNCATE TABLE执行删除具有%NOTRIGGER行为的表中的所有行,所以必须拥有%NOTRIGGER权限才能运行TRUNCATE TABLE。 在准备INSERTUPDATEINSERT or UPDATEDELETE语句时,必须具有适当的%NOCHECK%NOINDEX%NOLOCK%NOTRIGGER权限才能使用该限制。

如果指定的管理特权不是有效的特权名称(例如,由于拼写错误), IRIS将成功完成,并发出SQLCODE 100(到达数据末尾); IRIS不检查指定的用户(或角色)是否存在。 如果指定的管理权限有效,但指定的用户(或角色)不存在, IRIS将发出SQLCODE -118错误。

GRANT role

这种形式的GRANT将用户分配给指定的角色。 还可以将一个角色分配给另一个角色。 如果接收分配的指定角色不存在 IRIS将发出SQLCODE 100(到达数据末尾)。 如果分配给角色的指定用户(或角色)不存在 IRIS将发出SQLCODE -118错误。 如果不是超级用户,并且正在尝试授予一个不拥有且没有ADMIN OPTION的角色, IRIS将发出SQLCODE -112错误。

使用CREATE ROLE语句创建角色。 如果角色名是分隔的标识符,则在分配时必须将其括在引号中。

角色可以通过SQL GRANTREVOKE命令授予或撤销,也可以通 IRIS System Security:

  • 进入管理界面,选择“系统管理”、“安全”、“用户”,显示当前用户。 选择所需用户的名称以显示该用户的编辑选项,然后选择Roles选项卡以将用户分配(或取消分配)到一个或多个角色。
  • 进入“管理门户”界面,选择“系统管理”、“安全”、“角色”,显示当前角色。 选择所需角色的名称以显示该角色的编辑选项,然后选择Assigned to选项卡将该角色分配(或取消分配)给一个或多个角色。 注意,ObjectScript $ROLES特殊变量不显示授予角色的角色。

GRANT object-privilege

对象特权赋予用户或角色对特定对象的某些权限。 将对象列表上的对象特权授予被授予者。 对象列表可以在当前名称空间中指定一个或多个表、视图、存储过程或多维数据集。 通过使用逗号分隔的列表,单个GRANT语句可以将多个对象上的多个对象特权授予多个用户和/或角色。

以下是可用的对象特权值:

  • %ALTERDELETE权限授予对表或视图定义的访问权。
  • SELECTINSERTUPDATEDELETEREFERENCES权限授予对表数据的访问权限。
  • EXECUTE特权授予对存储过程的访问权。 执行存储过程或在查询中调用用户定义的SQL函数需要此特权。 例如:SELECT Field1,MyFunc() FROM SQLUserMyTable需要SQLUser上的SELECT权限。 SQLUser上的MyTableEXECUTE权限。 MyFunc过程。
  • ALL PRIVILEGES授予所有表和视图权限; 它不授予EXECUTE权限。

可以使用星号(*)通配符作为对象列表值,将对象特权授予当前命名空间中的所有对象。 例如,GRANT SELECT ON * TO Deborah授予该用户对所有表和视图的SELECT权限。 GRANT EXECUTE ON * TO Deborah授予该用户对所有非隐藏存储过程的EXECUTE权限。

可以使用SCHEMA SCHEMA -name作为对象列表值,将对象特权授予当前命名空间中命名模式中的所有表、视图和存储过程。 例如,GRANT SELECT ON SCHEMA Sample TO Deborah为该用户授予Sample模式中所有对象的SELECT权限。 这包括将来将在该模式中定义的所有对象。 可以将多个模式指定为逗号分隔的列表; 例如,GRANT SELECT ON SCHEMA Sample,Cinema TO Deborah授予SampleCinema模式中所有对象的SELECT权限。

多维数据集是不受模式名称限制的SQL标识符。 要指定多维数据集对象列表,必须指定CUBE(或cubes)关键字。 只能向多维数据集授予SELECT权限。

下面的示例演示了如何为特定表的特定用户授予SELECTUPDATE权限:

ClassMethod Grant()
{
	d $SYSTEM.Security.Login("_SYSTEM","SYS")
CreateUser
	s x = $SYSTEM.SQL.UserExists("DeborahTest")
	if x = 0 {
		&sql(
			CREATE USER DeborahTest IDENTIFY BY birdpw
		)
		if SQLCODE '= 0 {
			WRITE "创建用户错误: ",SQLCODE,!
	    	q
	    }
	} else {
		w "用户DeborahTest已经存在,没有更改权限",!
		q 
	}
GrantPrivsToUser
	&sql(
		GRANT SELECT,UPDATE ON SQLUSER.T1 TO DeborahTest
	)
	w !,"GRANT 错误代码: ",SQLCODE
DropUser
	&sql(
		DROP USER DeborahTest
	)
	if SQLCODE '= 0 {
		w "DROP USER error: ",SQLCODE,!
	}
}

权限只能显式授予已经存在的表、视图或存储过程。 如果指定的对象不存在,IRIS将发出SQLCODE -30错误。 但是,可以向一个模式授予特权,该模式将特权授予该模式中所有现有的对象,以及在授予特权时该模式中不存在的所有未来对象。

如果表的所有者是_PUBLIC,则用户访问表不需要被授予对象权限。

如果指定的用户不存在, IRIS将发出SQLCODE -118错误。 如果已经授予了指定的对象特权, IRIS将发出SQLCODE 100(到达数据末尾)。

对象权限可以通过以下方式授予或撤销:

  • GRANTREVOKE命令。
  • $SYSTEM.SQL.Security.GrantPrivileve()$SYSTEM.SQL.Security.RevokePrivileve()方法。这些方法返回%Status值并设置SQLCODE变量。与任何方法或函数一样,始终首先测试返回值:
    • 如果%status=1SQLCODE=0:特权被授予或撤销。
    • 如果%status=1SQLCODE=100:未授予或撤消任何特权,因为它已被授予或撤消。
    • 如果%status不是1,则不设置SQLCODE并且可能未定义:由于方法错误,未授予或撤消任何特权。%Status包含指示失败类型的SQLCODE:ObjPriv:SQLCODE-60表示无效特权;ObjList:指定对象类型的ObjList对象不存在:SQLCODE-30-187-428-473;类型:SQLCODE-400应为表、视图、多维数据集、架构或存储过程的对象类型;用户:SQLCODE-118未知或非唯一的用户或角色。
  • 通过IRIS系统安全。转到管理门户,依次选择System Administration、Security、Users(或System Administration、Security、Roles),选择所需用户或角色的名称,然后选择SQL Tables或SQL Views选项卡。从下拉列表中选择所需的命名空间。然后选择Add Tables或Add Views按钮。在显示的窗口中,选择方案,选择一个或多个表,然后分配权限。

可以通过调用%CHECKPRIV命令来确定当前用户是否具有指定的对象权限。可以通过调用$SYSTEM.SQL.Security.CheckPrivileve()方法来确定指定用户是否具有指定的表级对象权限,如下例所示:

   WRITE "SELECT privilege? ",$SYSTEM.SQL.Security.CheckPrivilege("DeborahTest","1,SQLUSER.TestT1","s"),!
   WRITE "UPDATE privilege? ",$SYSTEM.SQL.Security.CheckPrivilege("DeborahTest","1,SQLUSER.TestT1","u"),!
   WRITE "DELETE privilege? ",$SYSTEM.SQL.Security.CheckPrivilege("DeborahTest","1,SQLUSER.TestT1","d"),!

对象所有者权限

表、视图或过程的所有者始终隐式拥有SQL对象的所有SQL特权。该对象的所有者在该对象映射到的所有名称空间中拥有该对象的特权。

0
0 195
文章 姚 鑫 · 十月 15, 2021 8m read

[toc]

第四十六章 SQL命令 FROM(二)

%PARALLEL

这个可选关键字在查询的FROM子句中指定。 它建议 IRIS使用多个处理器(如果适用)并行处理查询。 这可以显著提高使用一个或多个COUNTSUMAVGMAXMIN聚合函数和/或GROUP BY子句的某些查询的性能,以及许多其他类型的查询。 这些通常是处理大量数据并返回小结果集的查询。 例如,SELECT AVG(SaleAmt) FROM %PARALLEL User.AllSales GROUP BY Region使用并行处理。

既指定单个字段又指定聚合函数且不包含GROUP BY子句的查询不能执行并行处理。 例如,SELECT Name,AVG(Age) FROM %PARALLEL SamplePerson不执行并行处理,而是从SELECT Name,AVG(Age) FROM %PARALLEL Sample.Person GROUP BY Home_State执行并行处理。

%PARALLEL用于SELECT查询及其子查询。 INSERT命令子查询不能使用%PARALLEL

指定%PARALLEL可能会降低某些查询的性能。 在具有多个并发用户的系统上使用%PARALLEL运行查询可能会导致整体性能下降。

注意:指定%PARALLEL的查询必须在读/写而不是只读的数据库中运行。 否则,可能发生<PROTECT>错误。

不管在FROM子句中是否存在%PARALLEL关键字,有些查询可能使用线性处理,而不是并行处理:有些查询不支持并行处理; 一些优化后的查询可能无法从并行处理中获益。 可以使用Show Plan确定 IRIS是否以及如何对查询进行了并行处理分区。 要确定当前系统上的处理器数量,使用 %SYSTEM.Util.NumberOfCPUs()方法。

%STARTTABLE

这个可选关键字指定查询优化器应该开始对FROM子句中列出的第一个表执行联接。 其余表的连接顺序留给查询优化器。 将此关键字与%INORDER进行比较,后者指定了完整的连接顺序。

%STARTTABLE不能与交叉连接或右外连接一起使用。 不能使用%STARTTABLE(或%FIRSTTABLE)从左OUTER join(或右OUTER join)的左边开始连接顺序。 如果指定的开始表与外部连接的要求不一致,则会生成一个SQLCODE -34错误:“优化器未能找到可用的连接顺序。” 为了避免这种情况,当与外部连接一起使用时,建议%STARTTABLE只与ansi风格的左外部连接或完整外部连接一起使用。

下表显示了在使用%INORDER%STARTTABLE优化组合超查询父视图和内联视图时的合并行为:

""没有连接优化器的超查询具有%STARTTABLE的超级查询%INORDER的超级查询
不带连接优化器的视图如果可能,合并视图如果视图是超查询start: don't merge。否则,如果可能,合并视图。合并如果可能的话;视图的底层表是无序的。
使用%STARTTABLE查看不合并如果视图是超级查询start: merge,如果可能的话。视图的开始表变成了超级查询的开始表。否则,不合并。不合并
使用%INORDER查看不合并不合并如果视图不是由%INORDER控制的,则不要合并。否则,如果可能,合并视图;视图的顺序被替换为超级查询连接顺序。

%FIRSTTABLE提示在功能上与%STARTTABLE相同,但是提供了以任意顺序指定连接表序列的灵活性。

FROM子句中的表值函数

表值函数是一个类查询,它被投影为一个存储过程,并返回单个结果集。 表值函数是任何具有SqlProc TRUE的类查询。 用作表值函数的类查询必须在LOGICALRUNTIME模式下编译。 当作为表值函数使用并在RUNTIME模式下编译时,表值函数查询将在LOGICAL模式下调用。

表值函数遵循与类查询的存储过程名称相同的命名约定。 参数括号是必须的; 括号可以是空的,可以包含一个字面值或一个主机变量,也可以包含一个用逗号分隔的字面值和主机变量列表。 如果不指定参数(空括号或空字符串),表值函数将返回所有数据行。

要使用表值函数发出查询,用户必须对定义表值函数的存储过程拥有EXECUTE权限。 用户还必须对表值函数查询访问的表或视图具有SELECT权限。

在下面的示例中,类查询Sample.Person.ByName被投影为一个存储过程,因此可以用作表值函数:

SELECT Name,DOB FROM Sample.SP_Sample_By_Name('A')

下面的动态SQL示例指定相同的表值函数。它使用%Execute()方法将参数值提供给入参:

ClassMethod From()
{
	s myquery="SELECT Name,DOB FROM Sample.SP_Sample_By_Name(?)"
	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("A")
	d rset.%Display()
	w !,"End of A data",!!
	s rset = tStatement.%Execute("B")
	d rset.%Display()
	w !,"End of B data"
}

表值函数只能在SELECT语句或DECLARE语句的FROM子句中使用。表值函数名可以用模式名限定,也可以用非限定名(没有模式名)限定;非限定名使用默认模式。在SELECT语句FROM子句中,只要可以使用表名,就可以使用表值函数。它可以在视图或子查询中使用,并且可以使用逗号分隔的列表或显式联接语法与其他表引用项联接。

表值函数不能直接用于INSERTUPDATEDELETE语句。但是,可以为这些命令指定子查询,以指定表值函数。

SQL没有为表值函数定义EXTENTSIZE,也没有为表值函数列定义SELECTIVITY

FROM子句中的子查询

可以在FROM子句中指定子查询。 这称为流子查询。 子查询被视为与表相同的处理方式,包括它在JOIN语法中的使用以及使用as关键字可选地分配别名。 FROM子句可以以任何组合包含多个表、视图和子查询,但要受JOIN语法的限制,如JOIN中所述。

SELECT name,region
FROM (SELECT t1.name,t1.state,t2.region
      FROM Employees AS t1 LEFT OUTER JOIN Regions AS t2
      ON t1.state=t2.state)
GROUP BY region

子查询可以指定TOP子句。 当与TOP子句配对时,子查询可以包含ORDER BY子句。

子查询可以使用SELECT *语法,但有以下限制:因为FROM子句的结果是值表达式,所以包含SELECT *的子查询只能生成一列。

子查询中的连接不能是NATURAL连接或接受USING子句。

从子查询和%VID

当调用FROM子查询时,它为返回的每个子查询行返回一个%VID%VID是一个整数计数器字段; 它的值是系统分配的、唯一的、非空的、非零的、不可修改的。 %VID仅在显式指定时返回。 它以数据类型INTEGER返回。 因为%VID值是顺序整数,所以如果子查询返回的是顺序数据,则它们更有意义; 子查询只能在与TOP子句配对时使用ORDER BY子句。

因为%VID是一个顺序整数,所以可以用它来确定带有ORDER BY子句的子查询中项目的排名。 在下面的示例中,10条最新的记录按名称顺序列出,但是使用%VID值可以很容易地看到它们的时间戳排名:

SELECT Name,%VID,TimeStamp FROM
   (SELECT TOP 10 * FROM MyTable ORDER BY TimeStamp DESC)
ORDER BY Name 

%VID的一个常见用途是“window”结果集,将执行划分为符合显示窗口中可用行数的顺序子集。 例如,显示20条记录,然后等待用户按Enter键,然后显示下20条记录。

ClassMethod From1()
{
	s myq=4
	s myq(1)="SELECT %VID,* "
	s myq(2)="FROM (SELECT TOP 60 Name,Age FROM Sample.Person "
	s myq(3)="WHERE Age > 55 ORDER BY Name) "
	s myq(4)="WHERE %VID BETWEEN ? AND ?"
	s tStatement = ##class(%SQL.Statement).%New()
	s qStatus = tStatement.%Prepare(.myq)
	if qStatus'=1 {w "%Prepare failed:" d $System.Status.DisplayError(qStatus) q}
	for i=1:10:60 {
		s rset = tStatement.%Execute(i, i+9)
		while rset.%Next() {
			d rset.%Print() 
		} 
		w !!
	}
	w "End of data"
}

DHC-APP>d ##class(PHA.TEST.SQLCommand).From1()
1 "Ahmed,Elmo X." 78
2 "Alton,Phil T." 68
3 "Anderson,Mario L." 78
4 "Bachman,Susan O." 88
5 "Basile,Filomena X." 87
6 "Browne,Robert X." 83
7 "Bukowski,Mario V." 86
8 "Burroughs,Barbara H." 86
9 "Cerri,Stavros Q." 96
10 "Chadbourne,Barb B." 94

可选FROM子句

如果SELECT项列表(直接或间接)没有引用表数据,则FROM子句是可选的。 这种SELECT可以用于从函数、运算符表达式、常量或宿主变量返回数据。 对于不引用表数据的查询:

  • 如果省略FROM子句,则不管TOP关键字值如何,最多返回一行数据; TOP 0不返回任何数据。 DISTINCT子句被忽略。 不需要特权。
  • 如果指定了FROM子句,则必须指定当前命名空间中的现有表。 必须对该表具有SELECT权限,即使该表没有被引用。 除非指定了TOPDISTINCT子句,或者用WHEREHAVING子句限制它,否则返回的相同数据行数等于指定表中的行数。 指定DISTINCT子句将输出限制为单行数据。 TOP关键字将输出限制为TOP值指定的行数; TOP 0不返回任何数据。

无论是否有FROM子句,都可以指定后续子句(如GROUP BYHAVINGORDER BY)。 WHEREHAVING子句可用于确定是否返回结果,或返回多少相同的结果行。 即使没有指定FROM子句,这些子句也可以引用表。 可以指定GROUP BYORDER BY子句,但这些子句没有意义。

下面是不引用表数据的SELECT语句示例。 两个示例都返回一行信息。

下面的例子省略了FROM子句。 DISTINCT关键字不是必需的,但是可以指定。 不允许使用SELECT子句。

SELECT 3+4 AS Arith,
      {fn NOW} AS NowDateTime,
      {fn DAYNAME({fn NOW})} AS NowDayName,
      UPPER('MixEd cASe EXPreSSioN') AS UpCase,
      {fn PI} AS PiConstant   

下面的示例包含一个FROM子句。 DISTINCT关键字用于返回单行数据。 FROM子句表引用必须是一个有效的表。 这里允许使用ORDER BY子句,但没有意义。 注意,ORDER BY子句必须指定一个有效的选择项别名:

SELECT DISTINCT 3+4 AS Arith,
    {fn NOW} AS NowDateTime,
    {fn DAYNAME({fn NOW})} AS NowDayName,
    UPPER('MixEd cASe EXPreSSioN')  AS UpCase,
    {fn PI} AS PiConstant
FROM Sample.Person
ORDER BY NowDateTime  

下面的例子都使用了WHERE子句来决定是否返回结果。 第一个包含FROM子句,并使用DISTINCT关键字返回单行数据。 第二个省略了FROM子句,因此最多返回一行数据。 在这两种情况下,WHERE子句表引用必须是具有SELECT权限的有效表:

SELECT DISTINCT
   {fn NOW} AS DataOKDate
FROM Sample.Person
WHERE FOR SOME (Sample.Person)(Name %STARTSWITH 'A')  
SELECT {fn NOW} AS DataOKDate
WHERE FOR SOME (Sample.Person)(Name %STARTSWITH 'A')
0
0 135
文章 姚 鑫 · 十月 14, 2021 9m read

第四十五章 SQL命令 FROM(一)

一个SELECT子句,指定要查询的一个或多个表。

大纲

SELECT ... FROM [optimize-option] table-ref [[AS] t-alias][,table-ref [[AS] t-alias]][,...]

参数

  • optimize-option - 可选-指定查询优化选项(优化器提示)的单个关键字或由空格分隔的一系列关键字。支持以下关键字:%ALLINDEX%FIRSTTABLE TABLE TABNAME%FULL%IGNOREINDEX NAME%INORDER%NOFLATTEN%NOMERGE%NOREDUCE%NOSVSO%NOTOPOPT%NOUNIONOROPT%PARALLEL%STARTTABLE
  • table-ref - 从其中检索数据的一个或多个表、视图、表值函数或子查询,以逗号分隔的列表或使用JOIN语法指定。 在使用带有JOIN语法的视图时存在一些限制。 可以指定一个用括号括起来的子查询。
  • AS t-alias - 可选—表名的别名。 必须是有效的标识符。

描述

FROM子句指定在SELECT语句中查询数据的一个或多个表(或视图或子查询)。 如果没有查询表数据,则FROM子句是可选的,如下所述。

多个表被指定为逗号分隔的列表,或者由其他JOIN语法分隔的列表。 可以为每个表名提供一个别名。

SELECT语句中为多个表指定字段名时使用表名别名。 如果FROM子句中指定了两个(或更多)表,可以通过指定tablename来指明需要哪个表的字段。 SELECT SELECT -item子句中每个字段的字段名。 由于表名通常是长名称,因此短表名别名在此上下文中很有用(t-alias.fieldname)。

下面的示例展示了表名别名的使用:

SELECT e.Name,c.Name
FROM Sample.Company AS c,Sample.Employee AS e

AS关键字可以省略。 它是为了兼容性和清晰度而提供的。

向表引用提供模式名称

table-ref名称可以是限定的(schema.tablename)或非限定的(tablename)。 非限定表名(或视图名)的模式名使用模式搜索路径或系统范围的默认模式名提供:

  1. 如果提供了模式搜索路径, IRIS将在指定的模式中搜索匹配的表名。
  2. 如果没有提供模式搜索路径,或者模式搜索路径没有生成匹配, IRIS将使用系统范围的默认模式名。

表连接

当在FROM子句中指定多个表名时, SQL将对这些表执行连接操作。 执行的连接类型由每对表名之间的连接关键字短语或符号指定。 当两个表名用逗号分隔时,将执行交叉连接。

执行连接的顺序是由SQL查询优化器自动确定的,而不是基于查询中列出的表的顺序。 如果需要,可以通过指定查询优化选项来控制执行连接的顺序。

以下三个SELECT语句显示了两个单独表的行数,以及指定两个表的SELECT的行数。后者产生一个更大的表,即笛卡尔乘积,其中第一个表中的每一行都与第二个表中的每一行相匹配,这一操作称为交叉联接(Cross Join)。

SELECT COUNT(*)
FROM Sample.Company
SELECT COUNT(*)
FROM Sample.Vendor
SELECT COUNT(*)
FROM Sample.Company,Sample.Vendor

Sample.Company、Sample.Vendor中选择计数(*)

SELECT COUNT(*)
FROM Sample.Company CROSS JOIN Sample.Vendor

在大多数情况下,交叉连接的大量数据复制是不可取的,而某些其他类型的连接更可取。

如果在SELECT语句中指定WHERE子句,则执行交叉联接,然后WHERE子句谓词确定结果集。这等效于使用ON子句执行内联接。因此,以下两个示例返回相同的结果:

SELECT p.Name,p.Home_State,em.Name,em.Office_State
FROM Sample.Person AS p, Sample.Employee AS em
WHERE p.Name %STARTSWITH 'E' AND em.Name %STARTSWITH 'E'
SELECT p.Name,p.Home_State,em.Name,em.Office_State
FROM Sample.Person AS p INNER JOIN Sample.Employee AS em
ON p.Name %STARTSWITH 'E' AND em.Name %STARTSWITH 'E'

查询优化选项

默认情况下, SQL查询优化器使用复杂而灵活的算法来优化涉及联接操作和/或多个索引的复杂查询的性能。在大多数情况下,这些默认值可提供最佳性能。但是,在极少数情况下,可能希望向查询优化器提供“提示”,指定查询优化的一个或多个方面。因此, SQL在FROM子句中提供了OPTIMIZE-OPTION关键字。可以按任意顺序指定多个优化关键字,并以空格分隔。

可以在简单的SELECT语句、CREATE VIEW DEFINITION SELECT语句或FROM子句的子查询SELECT语句中使用OPTIMIZE-OPTION FROM子句关键字。

%ALLINDEX

此可选关键字指定提供任何好处的所有索引都用于查询联接顺序中的第一个表。只有在定义了多个索引时才应使用此关键字。优化器的默认设置是只使用优化器认为最有益的那些索引。默认情况下,这包括所有有效的相等索引和其他类型的选定索引。%ALLINDEX使用所有类型的所有可能有益的索引。测试所有索引的开销较大,但在某些情况下,它可能会提供比默认优化更好的性能。当使用多个范围条件索引和低效相等条件索引时,此选项特别有用。在这些情况下,查询优化器可能无法获得准确的索引选择性。%ALLINDEX可以与%IGNOREINDEX一起使用,以包括/排除特定索引。通常,%ALLINDEX不应与TOP子句查询一起使用。

可以将%STARTTABLE%ALLINDEX配合使用,以指定%ALLINDEX应用于哪个表。

可以使用%NOINDEX条件级别提示为特定条件的%ALLINDEX指定异常。 %NOINDEX提示放置在不应该使用索引的每个查询选择条件的前面。 例如,WHERE %NOINDEX hiredate < ?。 这在绝大多数数据没有被排除的情况下最常用。 对于小于(<)或大于(>)条件,使用%NOINDEX条件级别提示通常是有益的。 对于相等条件,使用%NOINDEX条件级提示没有任何好处。 对于连接条件,ON子句连接支持%NOINDEX

%FIRSTTABLE

%FIRSTTABLE tablename

此可选关键字指定查询优化器应开始使用指定的表名执行联接。tablename为稍后在联接序列中指定的表命名。其余表的联接顺序留给查询优化器。此提示在功能上与%STARTTABLE相同,但为提供了以任意顺序指定联接表序列的灵活性。

tablename必须是简单标识符,可以是表别名,也可以是非限定表名。不能使用限定表名(schema.table)。如果查询指定了表别名,则必须将该表别名用作表名。例如:

FROM %FIRSTTABLE P Sample.Employee AS E JOIN Sample.Person AS P ON E.Name = P.Name

%FIRSTTABLE%STARTTABLE都允许指定用于联接操作的初始表。%INORDER允许指定用于联接操作的所有表的顺序。这三个关键词是相互排斥的;只指定一个和一个。如果不使用这些关键字,查询优化器将按照其认为最佳的顺序对表执行联接,而不管这些表的列出顺序如何。

不能使用%FIRSTTABLE%STARTTABLE从左外部联接的右侧(或右外部联接的左侧)开始联接顺序。尝试这样做会导致SQLCODE-34错误:“优化器无法找到可用的联接顺序”。

%FULL

此可选关键字指定编译器优化器检查所有可选联接序列以最大化访问性能。例如,在创建存储过程时,增加的编译时间可能值得提供更优化的访问。默认优化是,当FROM子句中有许多表时,不检查不太可能的连接序列。%FULL将覆盖此默认行为。

FROM子句包含使用箭头语法访问的表时,可以同时指定%INORDER%FULL关键字,这些表的顺序不受约束。

%IGNOREINDEX

此可选关键字指定查询优化器忽略指定的索引或索引列表。(为了向后兼容,支持不推荐使用的同义词%IGNOREINDICES。)

在此关键字后面指定一个或多个索引名。多个索引名必须用逗号分隔。可以使用以下格式之一指定索引名:

%IGNOREINDEX [[schemaname.]tablename.]indexname [,...] %IGNOREINDEX [[schemaname.]tablename.]* [,...]

方案名和表名是可选的。如果省略,则使用当前默认架构和指定为from table-ref的表名。星号(*)通配符指定指定表的所有索引名。可以按任意顺序指定索引名称。 SQL不会验证指定的索引名(或它们的模式名和表名);不存在或重复的索引名将被忽略。

通过使用此优化约束,可以使查询优化器不使用对特定查询不是最佳的索引。通过指定除一个索引名之外的所有索引名,实际上可以强制查询优化器使用剩余的索引。

还可以通过在条件前面加上%noindex关键字来忽略特定条件表达式的特定索引。

%INORDER

此可选关键字指定查询优化器按照表在FROM子句中列出的顺序执行联接。这最大限度地减少了编译时间。子查询的扁平化和索引使用不受影响。

%INORDER不能与交叉联接或右外部联接一起使用。如果指定的表顺序与外部联接的要求不一致,则会生成SQLCODE-34错误:“Optimizer找不到可用的联接顺序。”为避免这种情况,建议在与外部联接一起使用%INORDER时,仅与ANSI样式的左外部联接或完全外部联接一起使用。

视图和表子查询按照它们在FROM子句中指定的顺序进行处理。

  • 流式视图:%INORDER不影响视图中表的处理顺序。
  • 合并视图:%INORDER导致在视图的引用点处按照视图的FROM子句顺序处理视图表。

将此关键字与%FIRSTTABLE%STARTTABLE进行比较,这两个关键字都只指定初始连接表,而不指定完整的连接顺序。

不能同时使用%INORDER%PARALLEL优化;如果同时指定了这两个优化,则忽略%PARALLEL

%NOFLATTEN

此可选关键字在量化的子查询(返回布尔值的子查询)的FROM子句中指定。它指定编译器优化器应抑制子查询展平。此优化选项禁用“扁平化”(默认),它通过将子查询有效地集成子查询到查询中来优化包含量化的子查询的查询:将子查询的表添加到查询的FROM子句中,并将子查询中的条件转换为查询的WHERE子句中的联接或限制。

以下是使用%NOFLATTEN的量化子查询的示例:

SELECT Name,Home_Zip FROM Sample.Person WHERE Home_Zip IN 
      (SELECT Office_Zip FROM %NOFLATTEN Sample.Employee)
SELECT Name,(SELECT Name FROM Sample.Company WHERE EXISTS
             (SELECT * FROM %NOFLATTEN Sample.Company WHERE Revenue > 500000000))
 FROM Sample.Person

%INORDER%STARTTABLE优化隐式指定%NOFLATTEN

%NOMERGE

此可选关键字在子查询的FROM子句中指定。它指定编译器优化器应该禁止子查询到视图的转换。此优化选项通过将子查询作为内联视图添加到查询的FROM子句来禁用对包含子查询的查询的优化;子查询与查询字段的比较将作为联接移动到查询的WHERE子句。

%NOREDUCE

此可选关键字在流式子查询的FROM子句中指定-返回行的结果集的子查询,即封闭查询的FROM子句中的子查询。它指定编译器优化器应该禁止将子查询(或视图)合并到包含查询中。

在下面的示例中,查询优化器通常会通过对子查询执行Sample.Person的笛卡尔乘积联接来“减少”该查询。%NOREDUCE优化选项可防止出现这种情况。 IRIS改为在GNAME上构建临时索引,并在此临时索引上执行联接:

SELECT * FROM Sample.Person AS p, 
   (SELECT Name||'goo' AS gname FROM %NOREDUCE Sample.Employee) AS e
    WHERE p.name||'goo' = e.gname

%NOSVSO

此可选关键字在量化的子查询(返回布尔值的子查询)的FROM子句中指定。它指定编译器优化器应禁止集值子查询优化(SVSO)。

在大多数情况下,集值子查询优化可以提高[NOT] EXISTS[NOT] In子查询的性能,特别是对于只有一个可分离关联条件的子查询。 它通过用满足条件的数据值填充临时索引来实现这一点。 IRIS不是重复执行子查询,而是在临时索引中查找这些值。 例如,SVSO优化了 NOT EXISTS (SELECT P.num FROM Products P WHERE S.num=P.num AND P.color='Pink'),创建临时索引。

SVSO优化了ALLANY关键字与相对操作符(>>=<,或<=)和子查询的子查询,如… WHERE S.num > ALL (SELECT P.num…) 它通过将子查询表达式sqbExpr(在本例中为P.num)替换为MIN(sqbExpr)MAX(sqbExpr)来实现这一点。 当sqbExpr上有索引时,它支持快速计算。

%INORDER%STARTTABLE优化不禁止集值子查询优化。

%NOTOPOPT

当使用带有ORDER BY子句的TOP子句时指定此可选关键字。 默认情况下,TOPORDER By优化到第一行的最快时间。 相反,指定%NOTOPOPT(没有TOP优化)将优化查询,以最快地检索完整的结果集。

%NOUNIONOROPT

此可选关键字在查询或子查询的FROM子句中指定。 它禁用为多个OR条件和针对UNION查询表达式的子查询提供的自动优化。 这些自动优化将多个OR条件转换为UNION子查询,或将UNION子查询转换为OR条件。 这些UNION/OR转换允许EXISTS和其他低级谓词迁移到顶级条件,以便IRIS查询优化器索引使用它们。 这些默认转换在大多数情况下都是可取的。

然而,在某些情况下,这些UNION/OR转换会带来很大的开销负担。 %NOUNIONOROPT对与此FROM子句关联的WHERE子句中的所有条件禁用这些自动UNION/OR转换。 因此,在一个复杂的查询中,可以对一个子查询禁用这些自动UNION/OR优化,同时在其他子查询中允许它们。

UNION %PARALLEL关键字禁用自动UNION-to- or优化。

%INORDER%STARTTABLE优化抑制了OR-to-UNION优化。 %INORDER%STARTTABLE优化不抑制UNION-to-OR优化。

0
0 133
文章 姚 鑫 · 十月 13, 2021 2m read

第四十四章 SQL命令 FREEZE PLANS

冻结查询计划。

大纲

FREEZE PLANS BY ID statement-hash

FREEZE PLANS BY TABLE table-name

FREEZE PLANS BY SCHEMA schema-name

FREEZE PLANS

参数

  • statement-hash - 查询计划的SQL语句定义的内部哈希表示,用引号括起来。 偶尔,看起来相同的SQL语句可能有不同的语句散列项。 需要SQL语句的不同代码生成的设置/选项的任何差异都会导致不同的语句散列。 这种情况可能发生在支持不同内部优化的不同客户机版本或不同平台上。
  • table-name - 现有表或视图的名称。 表名可以是限定的(schema.table),也可以是非限定的(table)。 非限定表名接受默认模式名。
  • schema-name - 现有模式的名称。 该命令冻结指定模式中所有表的所有查询计划。

描述

FREEZE PLANS命令用来冻结查询计划。 需要解冻已冻结的查询计划,使用unfreeze plans命令。

“冻结计划”可以冻结“计划状态”为“未冻结”的查询计划。 它不能冻结计划状态为“Unfrozen/Parallel”的查询计划。

FREEZE PLANS为冻结查询计划提供了四种语法形式:

  • 指定的查询计划:FREEZE PLANS BY ID语句哈希。 语句哈希值必须用双引号分隔。
  • 表的所有查询计划:FREEZE plans BY table table表名。 可以指定表名或视图名。 如果一个查询计划引用多个表和/或视图,指定其中任何一个表或视图都会冻结查询计划。
  • 模式中所有表的所有查询计划:FREEZE plans BY schema schema-name
  • 当前命名空间中所有表的所有查询计划:FREEZE plans

如果一个或多个查询计划被冻结,该命令将发出SQLCODE 0; 如果没有冻结查询计划,则发出SQLCODE 100。 受影响的行数(%ROWCOUNT)表示被冻结的查询计划数量。

其他接口

可以使用下面的$SYSTEM.SQL。 语句方法用于冻结单个查询计划或多个查询计划:用于单个计划的freezstatement (); FreezeRelation()用于一个关系的所有计划(查询计划中引用的表或视图); FreezeSchema()用于一个模式的所有计划; FreezeAll()用于当前名称空间中的所有计划。 有相应的Unfreeze方法。

0
0 108
文章 姚 鑫 · 十月 12, 2021 3m read

第四十三章 SQL命令 FETCH

重新定位游标,并从中检索数据。

大纲

FETCH cursor-name [INTO host-variable-list ]

参数

  • cursor-name - 当前打开的游标的名称。 游标名称是在DECLARE命令中指定的。 游标名称区分大小写。
  • INTO host-variable-list - 可选—将取操作列中的数据放入局部变量中。 host-variable-list指定一个主机变量或一个逗号分隔的主机变量列表,它们是包含与游标关联的数据的目标。 INTO句是可选的。 如果没有指定,FETCH语句只定位游标。

描述

在嵌入式SQL应用程序中,FETCH语句从游标检索数据。 所需的操作顺序是:DECLAREOPENFETCHCLOSE。 在未打开的游标上尝试FETCH会导致SQLCODE -102错误。

作为SQL语句,这只在嵌入式SQL中得到支持。 通过ODBC使用ODBC API支持等价的操作。

INTO子句可以指定为DECLARE语句的子句,也可以指定为FETCH语句的子句,或者两者都指定。 INTO子句允许将fetch列中的数据放到本地主机变量中。 列表中的每个主机变量,从左到右,都与游标结果集中的相应列相关联。 每个变量的数据类型必须匹配或支持对应结果集列的数据类型的隐式转换。 变量的数量必须与游标选择列表中的列数匹配。

当游标前进到数据的末尾时,FETCH操作就完成了。 这将设置SQLCODE=100(没有更多数据)。 它还将%ROWCOUNT变量设置为获取的行数。

注意:只有当SQLCODE=0时,INTO子句宿主变量返回的值才是可靠的。 如果SQLCODE=100(没有更多数据),则不应该使用主机变量值。

游标名称不是特定于名称空间的。 更改当前名称空间对声明游标的使用没有影响。 唯一需要考虑的名称空间是FETCH必须出现在包含要查询的表的名称空间中。

%ROWID

FETCH检索可更新游标的行时,它将%ROWID设置为所获取行的ROWID值。 可更新游标是指顶部FROM子句只包含一个元素(表名或可更新视图名)的游标。

为检索到的每一行设置%ROWID受以下条件的限制:

  • DECLARE cursorname CURSOROPEN cursorname语句不初始化%ROWID; %ROWID值与之前的值不变。 第一个成功的FETCH设置%ROWID。 每个后续的FETCH检索行都会将%ROWID重置为当前的ROWIDFETCH如果检索可更新游标的行,则设置%ROWID。 如果游标不可更新,%ROWID将保持不变。 如果没有匹配查询选择条件的行,FETCH不会更改之前的%ROWID值。 在CLOSEFETCH发出SQLCODE 100 (No Data, or No More Data)时,%ROWID包含检索到的最后一行的ROWID
  • 带有DISTINCT关键字或GROUP BY子句的基于游标的SELECT不会设置%ROWID%ROWID值与之前的值(如果有的话)保持不变。
  • 基于游标的SELECT只执行聚合操作,不设置%ROWID%ROWID值与之前的值(如果有的话)保持不变。

没有声明游标的嵌入式SQL SELECT不会设置%ROWID。 在完成一个简单的SELECT语句后,%ROWID值是不变的。

FETCH for UPDATE or DELETE

可以使用FETCH来检索要进行更新或删除的行。 UPDATEDELETE必须指定WHERE CURRENT OF子句。 DECLARE应该指定FOR UPDATE子句。 下面的示例显示了一个基于游标的删除操作,它删除所有选中的行:

ClassMethod FETCH()
{
	s $NAMESPACE="Samples"
	&sql(
		DECLARE MyCursor CURSOR FOR SELECT %ID,Status
		FROM Sample.Quality WHERE Status='Bad' FOR UPDATE
	)
	&sql(
		OPEN MyCursor
	)
	if SQLCODE<0 {
		w "SQL Open游标错误:",SQLCODE," ",%msg  
		q
	}
	n %ROWCOUNT,%ROWID
	for {
		&sql(
			FETCH MyCursor
		)  
		q:SQLCODE'=0
		&sql(
			DELETE FROM Sample.Quality WHERE CURRENT OF MyCursor
		) 
	}
	w !,"更新的行数=",%ROWCOUNT
	&sql(
		CLOSE MyCursor
	)
	if SQLCODE<0 {
		w "SQL关闭游标错误:",SQLCODE," ",%msg  
		q
	}
}

示例

下面的嵌入式SQL示例显示了一个无参数的FOR循环调用FETCH,从名为EmpCursor的游标检索数据。 INTO子句在DECLARE语句中指定:

ClassMethod FETCH1()
{
	&sql(
		DECLARE EmpCursor CURSOR FOR 
			SELECT Name, Home_State
			INTO :name,:state FROM Sample.Employee
			WHERE Home_State %STARTSWITH 'M'
	)
	&sql(
		OPEN EmpCursor
	)
	if SQLCODE<0 {
		w "SQL Open游标错误:",SQLCODE," ",%msg  
		q
	}
	n %ROWCOUNT,%ROWID
	for { 
		&sql(
			FETCH EmpCursor
		)
		q:SQLCODE'=0  
		w "count: ",%ROWCOUNT," RowID: ",%ROWID,!
		w "  Name=",name," State=",state,! 
	}
	w !,"Final Fetch SQLCODE: ",SQLCODE
	&sql(
		CLOSE EmpCursor
	)
	if SQLCODE<0 {
		w "SQL关闭游标错误:",SQLCODE," ",%msg  
		q
	}
}

下面的嵌入式SQL示例显示了一个无参数的FOR循环调用FETCH,从名为EmpCursor的游标检索数据。 INTO子句被指定为FETCH语句的一部分:

ClassMethod FETCH2()
{
	&sql(
		DECLARE C1 CURSOR FOR 
			SELECT Name,Home_State INTO :name,:state FROM Sample.Person
			WHERE Home_State %STARTSWITH 'M'
	)
	&sql(OPEN C1)
	if SQLCODE<0 {
		w "SQL Open游标错误:",SQLCODE," ",%msg  
		q
	}
	&sql(FETCH C1)
	while (SQLCODE = 0) {
		w "count: ",%ROWCOUNT," RowID: ",%ROWID,!
		w "  Name=",name," State=",state,!
		&sql(FETCH C1) 
	}
	w !,"Final Fetch SQLCODE: ",SQLCODE
	&sql(CLOSE C1)
	if SQLCODE<0 {
		w "SQL关闭游标错误:",SQLCODE," ",%msg  
		q
	}
}

下面的嵌入式SQL示例显示FETCH检索聚合函数值。 %ROWID没有设置:

ClassMethod FETCH3()
{
	&sql(
		DECLARE PersonCursor CURSOR FOR 
		SELECT COUNT(*),AVG(Age) FROM Sample.Person 
	)
	&sql(OPEN PersonCursor)
	if SQLCODE<0 {
		w "SQL Open游标错误:",SQLCODE," ",%msg  
		q
	}
	n %ROWCOUNT
	for { 
		&sql(
			FETCH PersonCursor INTO :cnt,:avg
		)
		q:SQLCODE'=0  
		w %ROWCOUNT," Num People=",cnt," Average Age=",avg,! 
	}
	w !,"Final Fetch SQLCODE: ",SQLCODE
	&sql(CLOSE PersonCursor)
	if SQLCODE<0 {
		w "SQL关闭游标错误:",SQLCODE," ",%msg  
		q
	}
}

下面的嵌入式SQL示例显示FETCH检索DISTINCT值。 %ROWID没有设置:

ClassMethod FETCH4()
{
	&sql(
		DECLARE EmpCursor CURSOR FOR 
		SELECT DISTINCT Home_State FROM Sample.Employee
		WHERE Home_State %STARTSWITH 'M'
		ORDER BY Home_State 
	)
	&sql(OPEN EmpCursor)
	if SQLCODE<0 {
		w "SQL Open游标错误:",SQLCODE," ",%msg  
		q
	}
	n %ROWCOUNT
	for { 
		&sql(
			FETCH EmpCursor INTO :state
		)
		q:SQLCODE'=0  
		w %ROWCOUNT," State=",state,! 
	}
	w !,"Final Fetch SQLCODE: ",SQLCODE
	&sql(CLOSE EmpCursor)
	if SQLCODE<0 {
		w "SQL关闭游标错误:",SQLCODE," ",%msg  
		q
	}
}

下面的嵌入式SQL示例显示了游标跨名称空间持久存在。 该游标在%SYS中声明,在USER中打开和获取,在SAMPLES中关闭。 注意,OPEN必须在包含要查询的表的名称空间中执行,FETCH必须能够访问输出主机变量,这些变量是特定于名称空间的:

ClassMethod FETCH5()
{
	&sql(USE DATABASE %SYS)
	w $ZNSPACE,!
	&sql(DECLARE NSCursor CURSOR FOR SELECT Name INTO :name FROM Sample.Employee)
	&sql(USE DATABASE "USER")
	w $ZNSPACE,!
	&sql(OPEN NSCursor)
	if SQLCODE<0 {
		w "SQL Open游标错误:",SQLCODE," ",%msg  
		q
	}
	n SQLCODE,%ROWCOUNT,%ROWID
	for { 
		&sql(FETCH NSCursor)
		q:SQLCODE  
		w "Name=",name,! 
	}
	&sql(USE DATABASE SAMPLES)
	w $ZNSPACE,!
	&sql(CLOSE NSCursor)
	if SQLCODE<0 {
		w "SQL关闭游标错误:",SQLCODE," ",%msg  
		q
	}
}
0
0 160
文章 姚 鑫 · 十月 11, 2021 5m read

第四十二章 SQL命令 EXPLAIN

返回指定查询的查询计划。

大纲

EXPLAIN [ALT] [STAT]  [INTO :host-variable] query

参数

  • ALT - 可选-返回备用查询计划。默认情况下,返回单个查询计划。
  • STAT - 可选-(仅限动态SQL):返回查询计划运行时性能统计信息。默认情况下,返回不带运行时统计信息的查询计划。对于嵌入式SQL,此语法被忽略。
  • INTO :host-variable - 可选-(仅限嵌入式SQL):放置查询计划的输出主机变量。对于动态SQL,此语法被忽略。
  • query - SELECT query

描述

EXPLAIN命令以xml标记文本字符串的形式返回指定查询的查询计划。 该查询计划作为一个结果集返回,该结果集由一个名为plan的字段组成。

查询必须是SELECT查询。 EXPLAIN不能用于创建查询计划的其他SQL操作,例如带SELECT子句的INSERT。 指定不以SELECT关键字开始的查询将导致SQLCODE -51。 可以使用Show Plan显示非select查询的查询计划。

ALTSTAT关键字可以以任何顺序指定。 INTO关键字必须在这些关键字之后指定。

可选的ALT关键字生成备用查询计划。 所有备用查询计划都以相同的xml标记文本字符串返回。 规范化查询文本(标记为<sql>)在每个查询计划之前列出。

可选的STAT关键字生成查询计划中每个模块的运行时性能统计。 这些统计信息包含在包含查询计划的xml标记文本字符串中。 每个模块的统计数据如下:

  • <ModuleName>:模块名。
  • <TimeSpent>:模块的总执行时间,以秒为单位。
  • <GlobalRefs>:全局引用计数。
  • <LinesOfCode>:执行的代码行数。
  • <DiskWait>:磁盘等待时间,单位为秒。
  • <RowCount>:结果集的行数。
  • <ModuleCount>:该模块被执行的次数。
  • <Counter>:这个程序被执行的次数。

这些统计信息在查询计划的文本中以xml标记的文本字符串的形式返回。 查询计划中所有模块的性能统计信息会在关联查询计划之前返回。 嵌入式SQL不能生成或返回运行时性能统计数据; 忽略STAT关键字,不发出错误。

EXPLAIN命令通过调用$SYSTEM,SQL.Explain()方法返回Show Plan结果,然后将结果集格式化为包含xml标记文本字符串的单个字段。

EXPLAIN ALT命令通过调用带有all=1限定符的$SYSTEM,SQL.Explain()方法返回备用的显示计划结果,然后将结果集格式化为包含xml标记文本字符串的单个字段。

结果集XML结构

下面是用于EXPLAIN ALT STAT查询的xml标记文本字符串的结构。 为了便于解释,这里提供了换行、缩进和注释注释:

<plans> /* tag included even if there is only one plan */
   <plan> /* the first query plan */
      <sql> /* the normalzed SELECT statement text */  </sql>
      <cost value="1147000"/>
         /* if STAT, include the following <stats> tags */
      <stats> <ModuleName>MAIN</ModuleName> /* XML-tagged list of stats (above) for MAIN module */ </stats>
      <stats> <ModuleName>FIRST</ModuleName> /* XML-tagged list of stats (above) for FIRST module */ </stats>
      <stats>  /* additional modules */ </stats>
   /* text of query plan */
   </plan>
   <plan> /* if ALT, same info for first alternate plan */
      ...
   </plan>
</plans>

Explain() 方法

可以使用$SYSTEM.SQL.Explain()方法从ObjectScript返回相同的查询计划信息,示例如下:

/// d ##class(PHA.TEST.SQLCommand).Explain()
ClassMethod Explain()
{
	s myquery=2
	s myquery(1) = "SELECT Name,Age FROM Sample.Person WHERE Name %STARTSWITH 'Q' "
	s myquery(2) = "ORDER BY Age"
	s status = $SYSTEM.SQL.Explain(.myquery,{"all":0},,.plan)
	if status '= 1 {
		w "Explain() failed:" 
		d $System.Status.DisplayError(status) 
		q
	}
	zw plan
}

示例

下面的动态SQL示例以XML字符串的形式返回查询计划。 它首先返回SQL查询文本,然后返回查询计划:

ClassMethod Explain1()
{
	s myquery = "EXPLAIN SELECT Name,DOB FROM Sample.Person WHERE Name [ 'Q'"
	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()
	while rset.%Next() {
		d rset.%Print("")
	}
}

下面的动态SQL示例与前一个相同,只是它使用%Display()来显示结果。 注意,%Display()在XML字符串的开头添加了列名“Plan”,并在XML字符串的末尾添加了“1 Rows(s) Affected”:

ClassMethod Explain2()
{
	s myquery = "EXPLAIN SELECT Name,DOB FROM Sample.Person WHERE Name [ 'Q'"
	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()
}

下面的动态SQL示例以XML字符串的形式返回查询计划和性能统计数据。 它首先返回SQL查询文本,然后返回性能统计数据(按模块),然后返回查询计划:

ClassMethod Explain3()
{
	s myquery = "EXPLAIN STAT SELECT Name,DOB FROM Sample.Person WHERE Name [ 'Q'"
	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()
	while rset.%Next() {
		w rset.Plan
	}
}

下面的动态SQL示例以XML字符串的形式返回备用查询计划。 它在每个查询计划之前返回SQL查询文本:

ClassMethod Explain4()
{
	s myquery = "EXPLAIN STAT SELECT Name,DOB FROM Sample.Person WHERE Name [ 'Q'"
	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()
	while rset.%Next() {
		w rset.Plan
	}
}

下面的动态SQL示例返回一个更复杂的查询计划。 请注意,在查询计划之前和查询计划中,性能统计是如何显示的:

ClassMethod Explain5()
{
	s q1 = "EXPLAIN STAT SELECT p.Name AS Person, e.Name AS Employee "
	s q2 = "FROM Sample.Person AS p,Sample.Employee AS e "
	s q3 = "WHERE p.Name %STARTSWITH 'Q' GROUP BY e.Name ORDER BY p.Name"
	s myquery = q1_q2_q3
	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()
	while rset.%Next() {
		w rset.Plan
	}
}

下面的嵌入式SQL示例以XML字符串的形式返回查询计划。 它首先返回SQL查询文本,然后返回查询计划:

   #SQLCompile Select=Runtime
   &sql(EXPLAIN INTO :qplan SELECT Name,DOB FROM Sample.Person WHERE Name [ 'Q')
   WRITE qplan

下面的嵌入式SQL示例返回查询计划。 STAT关键字被忽略:

   #SQLCompile Select=Runtime
   &sql(EXPLAIN STAT INTO :qplan SELECT Name,DOB FROM Sample.Person WHERE Name [ 'Q')
   WRITE qplan

注意:Caché没有此关键字。

0
0 112
文章 姚 鑫 · 十月 10, 2021 3m read

第四十一章 SQL命令 DROP VIEW

删除视图

大纲

DROP VIEW view-name [CASCADE | RESTRICT]

参数

  • view-name 要删除的视图的名称。 视图名可以是限定的(schema.viewname),也可以是非限定的(viewname)。 非限定视图名接受默认模式名。
  • CASCADE RESTRICT - 可选-指定CASCADE关键字以删除引用view-name的任何其他视图。如果有另一个视图引用view-name,则指定RESTRITE将发出SQLCODE-321错误。默认值为限制。

描述

DROP VIEW命令删除视图,但不删除基础表或数据。

也可以使用DropView()方法调用来调用拖放视图操作:

$SYSTEM.SQL.Schema.DropView(viewname,SQLCODE,%msg)

权限

DROP VIEW命令是特权操作。在使用DROP VIEW之前,进程必须拥有指定视图的%DROP_VIEW管理权限或DELETE OBJECT权限。否则将导致SQLCODE-99错误(特权冲突)。可以通过调用%CHECKPRIV命令来确定当前用户是否具有删除权限。可以通过调用$SYSTEM.SQL.Security.CheckPrivileve()方法来确定指定用户是否具有删除权限。如果拥有适当的授予权限,则可以使用GRANT命令分配%DROP_VIEW权限。

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

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

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

可以删除基于从部署的持久类投影的表的视图。

Nonexistent View

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

默认情况下,如果尝试删除不存在的视图,DROP VIEW会发出SQLCODE-30错误。要确定当前设置,请调用$SYSTEM.SQL.CurrentSettings(),它显示允许DDL删除不存在的表或视图设置。默认值为0(“否”)。这是此选项的推荐设置。如果设置为1(“Yes”),则为不存在的视图和表发出DROP VIEWDROP TABLE不会执行任何操作,也不会发出错误消息。

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

其他视图引用的视图

如果试图删除其他视图在其查询中引用的视图,DROP VIEW默认情况下会发出SQLCODE-321错误。这就是限制关键字行为。

通过指定CASCADE关键字,可以成功删除其他视图在其查询中引用的视图。删除视图还会删除这些其他视图。如果 IRIS无法执行所有级联视图删除操作(例如,由于SQLCODE-300错误),则不会删除任何视图。

关联查询

删除视图会自动清除所有相关的缓存查询,并清除%SYS.PTools.StatsSQL生成的查询信息。删除视图会自动清除任何相关查询的所有SQL运行时统计信息(SQL Stat)信息。

示例

下面的嵌入式SQL示例创建名为“CityAddressBook”的视图,然后删除该视图。由于它是使用RESTRITE关键字(默认值)指定的,因此如果该视图被其他视图引用,则会发出SQLCODE-321错误:

ClassMethod DropView()
{
	&sql(
		CREATE VIEW CityAddressBook AS
			SELECT Name,Home_Street FROM Sample.Person 
			WHERE Home_City='Boston'
	)
	if SQLCODE=0 { 
		w !,"视图创建" 
	} else { 
		w !,"创建视图错误: ",SQLCODE
		q 
	} 
	/* Use the view */
	n SQLCODE,%msg
	&sql(
		DROP VIEW CityAddressBook RESTRICT
	) 
	if SQLCODE=0 { 
		w !,"删除视图" 
	} elseif SQLCODE=-30 { 
		w !,"视图不存在",!,%msg 
	} elseif SQLCODE=-321 { 
		w !,"其他视图引用的视图",!,%msg 
	} else { 
		w !,"其他删除视图错误: ",SQLCODE,!,%msg 
	}
}

0
0 164
文章 姚 鑫 · 十月 9, 2021 1m read

第四十章 SQL命令 DROP USER

删除用户帐户。

大纲

DROP USER user-name

参数

  • user-name 要删除的用户名。

描述

DROP USER命令可删除用户帐户。此用户帐户是使用CREATE USER创建的,并指定了用户名。如果指定的用户名与现有用户帐户不对应, IRIS将发出SQLCODE-118错误。可以通过调用$SYSTEM.SQL.Security.UserExists()方法来确定用户是否存在。

用户名不区分大小写。

还可以使用管理门户删除用户。选择System Administration(系统管理)、Security(安全性)、Users(用户)以列出现有用户。在此用户帐户表上,可以单击要删除的用户帐户的删除。

权限

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

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

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

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

示例

可以通过下面的语句来删除PSMITH:

DROP USER psmith
0
0 89
文章 姚 鑫 · 十月 8, 2021 4m read

第三十九章 SQL命令 DROP TRIGGER

删除触发器

大纲

DROP TRIGGER name [ FROM table ]

参数

  • name - 要删除的触发器的名称。触发器名称可以是限定的,也可以是非限定的;如果限定,则其架构名称必须与表的架构名称匹配。
  • FROM table - 可选-要从中删除触发器的表。如果指定了FROM子句,则只在表中搜索命名触发器。如果未指定FROM子句,则在NAME中指定的整个架构中搜索命名触发器。

描述

DROP TRIGGER命令删除触发器。如果要修改现有触发器,则必须先调用DROP TRIGGER删除旧版本的触发器,然后再调用CREATE TRIGGER

注:DROP TABLE删除与该表关联的所有触发器。

DROP TRIGGER命令是特权操作。用户必须具有%DROP_TRIGGER管理权限才能执行DROP TRIGGER。否则将导致SQLCODE-99错误,因为%msg用户‘name’没有%DROP_TRIGGER权限。

用户必须对指定表拥有%ALTER特权。如果用户是表的所有者(创建者),则会自动授予该用户对该表的%ALTER权限。否则,必须授予用户对该表的%ALTER特权。否则将导致SQLCODE-99错误,因为%msg用户‘name’没有更改‘Schema.TableName’的表定义所需的%ALTER特权。

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

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

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

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

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

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

FROM 子句

触发器及其表必须驻留在同一架构中。如果触发器名称未限定,则触发器架构名称默认为与表架构相同的架构,如FROM子句中所指定。如果触发器名称是非限定的,并且没有FROM子句,或者表名也是非限定的,则触发器模式缺省为缺省模式名称;不使用模式搜索路径。如果两个名称都是限定的,则触发器架构名称必须与表架构名称相同。模式名称不匹配会导致SQLCODE-366错误;只有当触发器名称和表名都是限定的,并且它们指定了不同的模式名称时才会出现这种情况。

在SQL中,对于特定表,触发器名称在其架构内必须是唯一的。因此,在一个模式中可以有多个同名触发器。可选的FROM子句用于确定要删除的触发器:

  • 如果未指定FROM子句,并且 IRIS在架构中找到与指定名称匹配的唯一触发器,则 IRIS将删除该触发器。
  • 如果指定了FROM子句,并且 IRIS在架构中找到了与指定名称和FROM表名都匹配的唯一触发器,则 IRIS将删除该触发器。
  • 如果未指定FROM子句,并且 IRIS找到多个与指定名称匹配的触发器,则 IRIS将发出SQLCODE-365错误。
  • 如果 IRIS找不到与指定名称匹配的触发器(对于FROM子句中指定的表),或者如果没有FROM子句,则对于架构中的任何表, IRIS都会发出SQLCODE-363错误。

示例

以下示例删除与系统范围默认架构中的任何表关联的名为TRIGGER_1的触发器。(初始默认架构为SQLUser):

DROP TRIGGER Trigger_1

以下示例删除与A架构中的任意表关联的名为TRIGGER_2的触发器。

DROP TRIGGER A.Trigger_2

以下示例删除与系统范围默认架构中的Patient表关联的名为TRIGGER_3的触发器。如果找到名为TRIGGER_3的触发器,但它与患者没有关联,则IRIS会发出SQLCODE-363错误。

DROP TRIGGER Trigger_3 FROM Patient

以下示例都删除了与Test模式中的Patient表关联的名为TRIGGER_4的触发器。

DROP TRIGGER Test.Trigger_4 FROM Patient
DROP TRIGGER Trigger_4 FROM Test.Patient
DROP TRIGGER Test.Trigger_4 FROM Test.Patient
0
0 126
文章 姚 鑫 · 十月 7, 2021 6m read

第三十八章 SQL命令 DROP TABLE

删除表及其数据(可选)。

大纲

DROP TABLE table 
     [RESTRICT | CASCADE] [%DELDATA | %NODELDATA]

参数

  • table - 要删除的表的名称。 表名可以是限定的(schema.table),也可以是非限定的(table)。 非限定表名接受默认模式名。 没有使用架构搜索路径值。
  • RESTRICTCASCADE - 可选-限制仅允许删除没有依赖视图或完整性约束的表。如果未指定关键字,则默认设置为RESTRITIONCASCADE允许删除具有依赖视图或完整性约束的表;作为表删除的一部分,任何引用视图或完整性约束也将被删除。外键约束不支持CASCADE关键字选项。
  • %DELDATA%NODELDATA - 可选-这些关键字指定在删除表时是否删除与表关联的数据。默认情况下,删除表格数据。

描述

DROP TABLE命令删除一个表及其对应的持久化类定义。如果该表是其架构中的最后一项,则删除该表也会删除该架构及其相应的持久化类包。

默认情况下,DROP TABLE同时删除表定义和表数据(如果存在)。%NODELDATA关键字允许指定删除表定义,但不能指定删除表的数据。

DROP TABLE删除与该表关联的所有索引和触发器。

要删除表格,必须满足以下条件:

  • 该表必须存在于当前命名空间中。尝试删除不存在的表会生成SQLCODE-30错误。
  • 表定义必须是可修改的。如果投影表的类没有定义[DdlAllowed],则尝试删除该表会生成SQLCODE-300错误。
  • 该表不能被另一个并发进程锁定。如果表被锁定,DROP TABLE将无限期等待锁被释放。如果可能出现锁争用,那么在发出DROP TABLE之前以独占模式锁定表是很重要的。
  • 该表必须没有关联的视图,或者DROP TABLE必须指定CASCADE关键字。尝试在不级联的情况下删除具有关联视图的表会生成SQLCODE-321错误。
  • 必须具有必要的权限才能删除该表。尝试在没有必要权限的情况下删除表会生成SQLCODE-99错误。
  • 即使相应的类被定义为已部署的类,也可以删除表。
  • 如果投影表的持久类具有派生类(子类),则不能删除该表。尝试删除会使子类成为孤立的超类时,会生成SQLCODE-300错误,并显示以下消息:Class 'MySuperClass' has derived classes and therefore cannot be dropped via DDL.

可以使用$SYSTEM.SQL.Schema.DropTable()方法删除当前名称空间中的表。可以指定SQL表名。与DROP TABLE不同,此方法可以删除未使用[DdlAllowed]定义的表。第二个参数指定是否也应该删除表数据;默认情况下,不删除数据。

  DO $SYSTEM.SQL.Schema.DropTable("Sample.MyTable",1,.SQLCODE,.%msg)
  IF SQLCODE '= 0 {WRITE "SQLCODE ",SQLCODE," error: ",%msg}

可以使用$SYSTEM.OBJ.Delete()方法删除当前名称空间中的一个或多个表。必须指定投影表的永久类名(而不是SQL表名)。可以使用通配符指定多个类名。第二个参数指定是否也应该删除表数据;默认情况下,不删除数据。

权限

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

即使DROP TABLE操作同时删除了表和表数据,用户也不必具有指定表的DELETE OBJECT权限。

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

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

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

DROP TABLE不能用于通过定义持久类创建的表,除非表类定义包括[DdlAllowed]。否则,操作将失败,并出现SQLCODE-300错误,同时未为类‘Schema.tablename’启用%msg DDL

Existing Object Privileges

删除表不会删除该表的对象权限。例如,授予用户在该表上插入、更新或删除数据的权限。

  • 如果删除一个表,然后创建另一个同名的表,则用户和角色对新表的权限将与对旧表的权限相同。
  • 一旦表被删除,就不可能撤销该表的对象权限。

由于这些原因,通常建议在删除表之前使用REVOKE命令撤消表中的对象权限。

包含数据的表

默认情况下,DROP TABLE删除表定义和表数据。此表数据删除是原子操作;如果DROP TABLE遇到无法删除的数据(例如,具有引用约束的行),则任何已执行的数据删除都会自动回滚,结果是不会删除表数据。

可以使用$SYSTEM.SQL.Util.SetOption()方法DDLDropTabDelData选项设置表数据删除的系统范围默认值。要确定当前设置,请调用$SYSTEM.SQL.CurrentSettings(),显示Does DDL DROP TABLE delete the table's data? setting.

默认值为1(“是”)。这是此选项的推荐设置。如果希望DROP TABLE在删除表定义时不删除表数据,请将此选项设置为0(“否”)。

可以在每个表的基础上覆盖数据删除。删除表时,可以使用%NODELDATA选项指定DROP TABLE,以防止自动删除表数据。如果系统范围的默认值设置为不删除表数据,则可以通过使用%DELDATA选项指定DROP TABLE来逐个表删除数据。

在大多数情况下,DROP TABLE使用高效的终止范围操作自动删除表的数据。以下情况阻止使用KILL EXTEND:表有引用它的外键;投影表的类是持久类的子类;类不使用默认存储;有ForEach = "row/object"触发器;有引用非默认流字段全局位置的流字段。如果其中任何一个适用,DROP TABLE将使用效率较低的DELETE RECORD操作删除表的数据。

可以使用TRUNCATE TABLE命令删除表的数据,而不删除表定义。

锁应用

DROP TABLE语句获取表的独占表级锁。这可以防止其他进程在表删除过程中修改表定义或表数据。这个表级锁足以删除表定义和表数据;DROP TABLE不会获得表数据的每一行的锁。此锁在DROP TABLE操作结束时自动释放。

外键约束

默认情况下,如果在引用尝试删除的表的另一个表上定义了任何外键约束,则不能删除该表。在删除它们引用的表之前,必须删除所有引用的外键约束。在尝试DROP TABLE操作之前未删除这些外键约束会导致SQLCODE-320错误。

此默认行为与限制关键字选项一致。外键约束不支持CASCADE关键字选项。

要更改此默认外键约束行为,请参考SET OPTION命令的COMPILEMODE=NOCHECK选项。

相关查询

删除表会自动清除所有相关的高速缓存查询,并清除%SYS.PTools.StatsSQL生成的查询信息。删除表会自动清除任何相关查询的所有SQL运行时统计信息(SQL Stats)信息。

不存在的表

要确定当前命名空间中是否存在指定表,请使用$SYSTEM.SQL.Schema.TableExists()方法。

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

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

示例

下面的嵌入式SQL示例创建名为SQLUser.MyEmployees的表,然后将其删除。此示例指定在删除该表时不删除与该表关联的任何数据:

ClassMethod DropTable()
{
	&sql(CREATE TABLE SQLUser.MyEmployees 
		(
			NAMELAST     CHAR (30) NOT NULL,
			NAMEFIRST    CHAR (30) NOT NULL,
			STARTDATE    TIMESTAMP,
			SALARY       MONEY
		)
	)
	w !,"创建表"
	/*
	&sql(SQL code populating SQLUser.MyEmployees table)
	&sql(SQL code using SQLUser.MyEmployees table)
	*/
	n SQLCODE,%msg
	&sql(DROP TABLE SQLUser.MyEmployees %NODELDATA)
	if SQLCODE = 0 {
		w !,"表已删除"
	} else {
		w !,"SQLCODE=",SQLCODE,": ",%msg 
	}
}
0
0 264
文章 姚 鑫 · 十月 6, 2021 2m read

第三十七章 SQL命令 DROP ROLE

删除角色

大纲

DROP ROLE role-name

参数

  • role-name - 要删除的角色的名称。名称是一个标识符。角色名称不区分大小写。

描述

DROP ROLE语句删除角色。删除角色时, IRIS会将其从已授予该角色的所有用户和角色中撤消,并将其从数据库中删除。

可以通过调用$SYSTEM.SQL.Security.RoleExists()方法来确定角色是否存在。如果尝试删除不存在(或已被删除)的角色,则DROP ROLE将发出SQLCODE-118错误。

权限

DROP ROLE命令是特权操作。在嵌入式SQL中使用DROP ROLE之前,至少需要满足以下要求之一:

  • 必须具有%Admin_Secure:USE权限。
  • 该角色的所有者。
  • 已被授予具有管理员选项的角色。

否则将导致SQLCODE-99错误(特权冲突)。

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

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

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

示例

下面的嵌入式SQL示例创建名为BkUser的角色,稍后将其删除:

ClassMethod DropRole()
{
	DO $SYSTEM.Security.Login("MyName","SecretPassword")
	&sql(
		CREATE ROLE BkName
	)
	if SQLCODE=-99 {
		w !,"没有创建角色的权限" 
	} else { 
		w !,"创建角色"
	}
	/* Use role */
	&sql(
		DROP ROLE BkName
	)
	if SQLCODE=-99 {
		w !,"没有删除角色的权限" 
	} else { 
		w !,"删除橘色" 
	}
}
0
0 102
文章 姚 鑫 · 十月 5, 2021 2m read

第三十六章 SQL命令 DROP QUERY

删除查询

大纲

DROP QUERY name [ FROM className ]

参数

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

描述

DROP QUERY命令删除查询。删除查询时, IRIS将从所有已授予该查询的用户和角色中吊销该查询,并将其从数据库中删除。

要删除查询,必须具有GRANT命令指定的%DROP_QUERY管理权限。如果要尝试删除具有已定义所有者的类的查询,则必须以类的所有者身份登录。否则,系统会生成SQLCODE-99错误(权限冲突)。

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

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

  • DROP QUERY BonusCalc FROM queryBonusCalc: drops the query SQLUser.BonusCalc().
  • DROP QUERY BonusCalc FROM User.queryBonusCalc: drops the query SQLUser.BonusCalc().
  • DROP QUERY Test.BonusCalc FROM queryBonusCalc: drops the query SQLUser.BonusCalc().
  • DROP QUERY BonusCalc FROM Employees.queryBonusCalc: drops the query Employees.BonusCalc().
  • DROP QUERY Test.BonusCalc FROM Employees.queryBonusCalc: drops the query Employees.BonusCalc().

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

也可以通过从类定义中移除查询(投影为存储过程),然后重新编译该类,或删除整个类来删除查询。

示例

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

ClassMethod DropQuery()
{
	&sql(DROP QUERY myq FROM User.Employee)
	if SQLCODE = 0 {
		w !,"查询已删除" 
	} elseif SQLCODE = -360 {
		w !,"不存在的类: ",%msg 
	} elseif SQLCODE=-362 {
		w !,"不存在的函数: ",%msg 
	} else {
		w !,"意外错误代码: ",SQLCODE
	}
}
0
0 98
文章 姚 鑫 · 十月 4, 2021 3m read

第三十五章 SQL命令 DROP PROCEDURE

删除存储过程

大纲

DROP PROCEDURE procname [ FROM className ]
DROP PROC procname [ FROM className ]

参数

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

描述

DROP PROCEDURE命令删除当前命名空间中的过程。删除过程时, IRIS会将其从已授予该过程的所有用户和角色中撤消,并将其从数据库中删除。

要删除过程,必须具有GRANT命令指定的%DROP_PROCEDURE管理权限。如果要尝试删除具有已定义所有者的类的过程,则必须以类的所有者身份登录。否则,系统会生成SQLCODE-99错误(权限冲突)。

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

进程名不区分大小写。必须指定不带参数括号的procname;指定参数括号会导致SQLCODE-25错误。

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

  • DROP PROCEDURE BonusCalc FROM procBonusCalc: drops the procedure SQLUser.BonusCalc().

  • DROP PROCEDURE BonusCalc FROM User.procBonusCalc: drops the procedure SQLUser.BonusCalc().

  • DROP PROCEDURE Test.BonusCalc FROM procBonusCalc: drops the procedure SQLUser.BonusCalc().

  • DROP PROCEDURE BonusCalc FROM Employees.procBonusCalc: drops the procedure Employees.BonusCalc().

  • DROP PROCEDURE Test.BonusCalc FROM Employees.procBonusCalc: drops the procedure Employees.BonusCalc().

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

要确定当前命名空间中是否存在指定的进程名,请使用$SYSTEM.SQL.Schema.ProcedureExists()方法。此方法可以识别使用PROCEDURE关键字定义的过程和方法。可以使用DROP PROCEDURE删除使用PROCEDURE关键字定义的方法。

如果对属于ObjectScript类查询过程的过程执行DROP过程, IRIS还将删除与该过程相关的方法,如myprocExecute()myprocGetInfo()myprocFetch()myprocFetchRows()myprocClose()

还可以通过从类定义中移除存储过程,然后重新编译类,或删除整个类来删除过程。

示例

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

ClassMethod DropProcedure()
{
	&sql(DROP PROCEDURE myprocSP FROM User.Employee)
	if SQLCODE = 0 {
		w !,"过程已删除" 
	} elseif SQLCODE = -360 {
		w !,"不存在的类: ",%msg 
	} elseif SQLCODE=-362 {
		w !,"不存在的函数: ",%msg 
	} else {
		w !,"意外错误代码: ",SQLCODE
	}
}
0
0 126
文章 姚 鑫 · 十月 3, 2021 3m read

第三十四章 SQL命令 DROP METHOD

删除方法

大纲

DROP METHOD name [ FROM className ]

参数

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

描述

DROP METHOD命令可删除方法。删除方法时, IRIS会将其从已授予该方法的所有用户和角色中撤消,并将其从数据库中删除。

要删除方法,必须具有GRANT命令指定的%DROP_METHOD管理权限。如果要尝试删除具有已定义所有者的类的方法,则必须以类所有者的身份登录。否则,系统会生成SQLCODE-99错误(权限冲突)。

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

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

  • DROP METHOD BonusCalc FROM methBonusCalc: 删除该方法 SQLUser.BonusCalc().
  • DROP METHOD BonusCalc FROM User.methBonusCalc: 删除该方法 SQLUser.BonusCalc().
  • DROP METHOD Test.BonusCalc FROM methBonusCalc: 删除该方法 SQLUser.BonusCalc().
  • DROP METHOD BonusCalc FROM Employees.methBonusCalc: 删除该方法 Employees.BonusCalc().
  • DROP METHOD Test.BonusCalc FROM Employees.methBonusCalc: 删除该方法d Employees.BonusCalc().

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

如果使用PROCEDURE CHARECTURE关键字定义了方法,则可以通过调用$SYSTEM.SQL.Schema.ProcedureExists()方法来确定该方法是否存在于当前命名空间中。使用PROCEDURE关键字定义的方法可以通过DROP方法或DROP PROCEDURE删除。

还可以删除方法,方法是从类定义中移除方法,然后重新编译类,或者删除整个类。

示例

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

ClassMethod DropMethod()
{
	&sql(
		DROP METHOD mymeth FROM User.Employee
	)
	if SQLCODE = 0 {
		w !,"方法已删除" 
	} elseif SQLCODE = -360 {
		w !,"不存在的类: ",%msg 
	} elseif SQLCODE=-362 {
		w !,"不存在的函数: ",%msg 
	} else {
		w !,"意外错误代码: ",SQLCODE
	}
}
0
0 94