0 关注者 · 478 帖子

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

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

第二章 SQL谓词的概述(二)

谓词和%SelectMode

所有谓词都使用逻辑(内部存储)数据值进行比较。 但是,有些谓词可以对谓词值执行格式模式转换,将谓词值从ODBCDisplay格式转换为Logical格式。 其他谓词不能执行格式模式转换,因此必须始终以Logical格式指定谓词值。

执行格式模式转换的谓词确定是否需要从匹配字段的数据类型(如DATE``%List)进行转换,并确定从%SelectMode设置进行转换的类型。 如果%SelectMode设置为逻辑格式以外的值(例如%SelectMode=ODBC%SelectMode=Display),则必须以正确的ODBC或Display格式指定谓词值。

  • 相等谓词执行格式模式转换。 IRIS将谓词值转换为逻辑格式,然后与字段值进行匹配。 如果%SelectMode设置为逻辑格式以外的模式,则必须以%SelectMode格式(ODBC或Display)指定显示值与逻辑存储值不同的数据类型的谓词值。 例如,日期、时间和%list格式的字符串。 因为IRIS会自动执行这种格式转换,所以在Logical格式中指定这种类型的谓词值通常会导致SQLCODE错误。 例如,SQLCODE -146“无法将日期输入转换为有效的逻辑日期值”(IRIS假设提供的逻辑值是ODBC或Display值,并试图将其转换为逻辑值——但没有成功)。 受影响的谓词包括=<>BETWEENIN
  • 模式谓词不能执行格式模式转换,因为IRIS不能有意义地转换谓词值。 因此,谓词值必须以Logical格式指定,而不管%SelectMode设置如何。 以ODBC或Display格式指定谓词值通常会导致没有数据匹配或意外的数据匹配。 受影响的谓词包括%INLISTLIKE%MATCHES%PATTERN%STARTSWITH[(Contains操作符)和](Follows操作符)。

可以使用%INTERNAL%EXTERNAL%ODBCOUT格式转换函数来转换谓词操作的字段。 这允许以另一种格式指定谓词值。 例如,WHERE %ODBCOut(DOB) %STARTSWITH '1955-'。 但是,在匹配字段上指定格式转换函数将阻止对该字段使用索引。 这可能会对性能产生显著的负面影响。

在以下动态SQL示例中,BETWEEN谓词(相等谓词)必须以%SelectMode=1 (ODBC)格式指定日期:

ClassMethod Predicates3()
{
	s q1 = "SELECT Name,DOB FROM Sample.Person "
	s q2 = "WHERE DOB BETWEEN '1950-01-01' AND '1960-01-01'"
	s myquery = q1 _ q2
	s tStatement = ##class(%SQL.Statement).%New()
	s tStatement.%SelectMode = 1
	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"
}

DHC-APP>d ##class(PHA.TEST.SQLCommand).Predicates3()
Name    DOB
Houseman,Martin D.      1955-09-25
Ingrahm,Yan S.  1954-06-15
Smith,Elvis Y.  1955-06-29
Gore,Alfred M.  1958-09-15
Yoders,Liza U.  1959-06-05
Ng,Liza Z.      1955-10-05
Yeats,Debby G.  1951-12-06

在以下动态SQL示例中,%STARTSWITH谓词(模式谓词)不能执行格式模式转换。 第一个示例尝试以%SelectMode=ODBC格式为20世纪50年代的日期指定%STARTSWITH。 但是,由于该表不包含以$HOROLOG 195开始的出生日期(日期在1894年),所以没有选择行:

ClassMethod Predicates4()
{
	s q1 = "SELECT Name,DOB FROM Sample.Person "
	s q2 = "WHERE DOB %STARTSWITH '195'"
	s myquery = q1 _ q2
	s tStatement = ##class(%SQL.Statement).%New()
	s tStatement.%SelectMode = 1
	s qStatus = tStatement.%Prepare(myquery)
	if qStatus '= 1 {
		w "%Prepare failed:" 
		d $System.Status.DisplayError(qStatus) 
		q
	}
	s rset = tStatement.%Execute()
	q rset.%Display()
	w !,"End of data"
}

下面的示例在匹配的DOB字段上使用%ODBCOut格式转换函数,以便%STARTSWITH可用ODBC格式选择20世纪50年代的年份。 但是,请注意,这种用法会阻止在DOB字段上使用索引。


ClassMethod Predicates5()
{
	s q1 = "SELECT Name,DOB FROM Sample.Person "
	s q2 = "WHERE %ODBCOut(DOB) %STARTSWITH '195'"
	s myquery = q1_q2
	s tStatement = ##class(%SQL.Statement).%New()
	s tStatement.%SelectMode=1
	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"
}

DHC-APP>d ##class(PHA.TEST.SQLCommand).Predicates5()
Name    DOB
Houseman,Martin D.      1955-09-25
Ingrahm,Yan S.  1954-06-15
Smith,Elvis Y.  1955-06-29
Gore,Alfred M.  1958-09-15
Yoders,Liza U.  1959-06-05
Ng,Liza Z.      1955-10-05
Yeats,Debby G.  1951-12-06
Zweifelhofer,Zelda J.   1954-02-19

在下面的示例中,%STARTSWITH谓词为逻辑(内部)格式的日期指定了%STARTSWITH。 选择DOB逻辑值以41开始的行(日期从1953年4月4日($HOROLOG 41000)到1955年12月28日($HOROLOG 41999))。 使用DOB字段索引:

lassMethod Predicates6()
{
	s q1 = "SELECT Name,DOB FROM Sample.Person "
	s q2 = "WHERE DOB %STARTSWITH '41'"
	s myquery = q1 _ q2
	s tStatement = ##class(%SQL.Statement).%New()
	s tStatement.%SelectMode = 1
	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"
}

DHC-APP>d ##class(PHA.TEST.SQLCommand).Predicates6()
Name    DOB
Houseman,Martin D.      1955-09-25
Ingrahm,Yan S.  1954-06-15
Smith,Elvis Y.  1955-06-29
Ng,Liza Z.      1955-10-05
Zweifelhofer,Zelda J.   1954-02-19
Zampitello,Josephine Q. 1953-08-14
Hertz,Uma C.    1954-07-25
Davis,Jane E.   1953-07-28
Vanzetti,Alexandra O.   1953-12-29
 
9 Rows(s) Affected

谓词和PosixTime、时间戳和日期

相等谓词比较自动在这些不同的datedatetime表示之间执行转换。 这个转换与%SelectMode无关。 因此,下面是所有有意义的比较谓词:

WHERE MyPosixField = MyTimestampField
WHERE MyPosixField < CURRENT_TIMESTAMP
WHERE MyPosixField BETWEEN DATEADD('month',-1,CURRENT_TIMESTAMP) AND $HOROLOG
WHERE MyPosixField BETWEEN DATEADD('day',-1,CURRENT_DATE) AND LAST_DAY(CURRENT_DATE)

模式谓词比较,如%STARTSWITH,不执行不同日期和日期时间表示之间的转换。 对实际存储数据值的操作。

取消文字替换

通过将谓词参数括在双圆括号中,可以在编译前分析期间禁止文字替换。例如,LIKE((‘ABC%’))。这可以通过提高总体选择性和/或下标绑定选择性来提高查询性能。但是,当使用不同的值多次调用同一查询时,应该避免这种情况,因为这将导致为每个查询调用创建一个单独的缓存查询。

示例

下面的示例在查询的WHERE子句中使用了各种条件:


SELECT PurchaseOrder FROM MyTable 
	WHERE OrderTotal >= 1000 
		AND ItemName %STARTSWITH :partname 
		AND AnnualOrders BETWEEN 50000 AND 100000 
		AND City LIKE 'Ch%' 
		AND CustomerNumber IN 
			(
				SELECT CustNum FROM TheTop100 
				WHERE TheTop100.City='Boston'
			) 
		AND :minorder > SOME 
   			(
   				SELECT OrderTotal FROM Orders 
				WHERE Orders.Customer = :cust
			)
0
0 85
文章 姚 鑫 · 十一月 30, 2021 1m read

第一章 SQL谓词的概述(一)

描述计算结果为真或假的逻辑条件。

使用谓词

谓词是一个条件表达式,其计算结果为布尔值(truefalse)。

谓词可以如下使用:

  • SELECT语句的WHERE子句或HAVING子句中确定哪些行与特定查询相关。 注意,不是所有谓词都可以在HAVING子句中使用。
  • JOIN操作的ON子句中确定哪些行与连接操作相关。
  • UPDATEDELETE语句的WHERE子句中,确定要修改哪些行。
  • WHERE CURRENT OF语句的AND子句中。
  • CREATE TRIGGER语句的WHEN子句中确定何时应用触发操作代码。

谓词列表

每个谓词包含一个或多个比较操作符,可以是符号,也可以是关键字子句。 SQL支持以下比较操作符:

  • = (equals) ,<> (does not equal),!= (does not equal),> (is greater than),>= (is greater than or equal to),< (is less than),<= (is less than or equal to) - 比较条件。 可用于数字比较或字符串排序顺序比较。 对于数值比较,空字符串值(")被计算为0。 在任何相等比较中,NULL总是返回空集; 请使用IS NULL谓词。
  • IS [NOT] NULL - 测试字段是否有未定义(NULL)值。
  • IS [NOT] JSON - 测试一个值是JSON格式的字符串还是JSON数组或JSON对象的oref
  • EXISTS (subquery) - 使用子查询测试指定表是否存在一行或多行。
  • BETWEEN x AND y - BETWEEN条件同时使用>=<=比较条件。 匹配必须在两个指定的范围限制值(包括)之间。
  • IN (item1,item2[...,itemn])IN (subquery) - 一个等式条件,它将字段值与逗号分隔列表中的任何项或子查询返回的任何项匹配。
  • %INLIST listfield - 将字段值与%List结构化列表中的任何元素匹配的相等条件。
  • [ - 包含运算符。 Match必须包含指定的字符串。 Contains操作符使用EXACT排序规则,因此区分大小写。 必须以逻辑格式指定值。
  • ] - 跟随运算符。在排序规则序列中,匹配项必须出现在指定项之后。必须以逻辑格式指定值。
  • %STARTSWITH string - 匹配必须以指定的字符串开始。
  • FOR SOME - 布尔比较条件。对于指定字段的至少一个数据值,For Some条件必须为True
  • FOR SOME %ELEMENT - 带有%VALUE%KEY谓词子句的列表元素比较条件。%value必须与列表中至少一个元素的值匹配。%key必须小于或等于列表中的元素数。%VALUE%KEY子句可以使用任何其他比较运算符。
  • LIKE - 使用文字和通配符的模式匹配条件。当希望返回包含已知子字符串的文字字符或包含已知序列中的多个已知子字符串的数据值时,请使用LIKELIKE使用其目标的排序规则进行字母大小写比较。(与CONTAINS运算符形成对比,后者使用精确排序规则。)
  • %MATCHES - 使用文字、通配符以及列表和范围的模式匹配条件。如果希望返回的数据值包含已知子字符串的文字字符,或包含一个或多个落在可能字符列表或范围内的文字字符,或按已知序列包含多个这样的子字符串,请使用%Matches%Matches使用精确排序规则进行字母大小写比较。
  • %PATTERN - 使用字符类型的模式匹配条件。例如,'1U4L1",".A'(1个大写字母,4个小写字母,一个文字逗号,后跟任意数量的字母字符)。如果希望返回包含已知字符类型序列的数据值,请使用%Pattern%Pattern可以指定已知的文字字符,但在数据值不重要但这些值的字符类型格式很重要时尤其有用。
  • ALLANYSOME - 一种量化的比较条件。
  • %INSET%FIND - 启用使用以编程方式指定的抽象临时文件或位图索引筛选RowId字段值的字段值比较条件。%Inset支持简单比较。%Find支持涉及位图索引的比较。

NULL

NULL表示没有任何值。根据定义,它不能通过所有布尔测试:没有值等于NULL,没有值不等于NULL,没有值大于或小于NULL。即使NULL=NULL也不能作为谓词。因为IN谓词是一系列相等性测试,所以在IN值列表中指定NULL没有意义。因此,指定任何谓词条件都会消除该字段的任何为空的实例。在结果集中包含来自谓词条件的NULL字段的唯一方法是使用IS NULL谓词。下面的示例显示了这一点:

SELECT FavoriteColors FROM Sample.Person
WHERE FavoriteColors = $LISTBUILD('Red') OR FavoriteColors IS NULL

image

排序

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

如果在查询中指定排序规则类型,则必须在比较的两边指定它。 指定排序规则类型会影响索引的使用;

某些谓词比较可能涉及嵌入在字符串中的子字符串:Contains操作符([)、%MATCHES谓词和%PATTERN谓词。 这些谓词总是使用EXACT排序法,因此总是区分大小写。 因为有些排序规则会在字符串中附加一个空格,所以如果这些谓词遵循字段的默认排序规则,它们就不能执行它们的功能。 但是,LIKE谓词可以使用通配符来匹配嵌入在字符串中的子字符串。 LIKE使用字段的默认排序规则,默认情况下不区分大小写。

复合谓词

谓词是条件表达式的最简单版本; 条件表达式可以由一个或多个谓词组成。 可以使用ANDOR逻辑操作符将多个谓词链接在一起。 通过将NOT一元操作符放在谓词之前,可以颠倒谓词的含义。 NOT一元操作符只影响紧随其后的谓词。 谓词严格按照从左到右的顺序计算。 可以使用括号对谓词进行分组。 可以在左括号前放置NOT一元操作符,以反转一组谓词的含义。 括号前后、括号与逻辑运算符之间不需要空格。

IN%INLIST谓词在功能上相当于多个OR相等谓词。 下面的例子是等价的:

ClassMethod Predicates()
{
	s q1 = "SELECT Name,Home_State FROM Sample.Person "
	s q2 = "WHERE Home_State='MA' OR Home_State='VT' OR Home_State='NH'"
	s myquery = q1_q2
	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()
}
DHC-APP>d ##class(PHA.TEST.SQLCommand).Predicates()
Name    Home_State
Lepon,Jeff Z.   NH
Ingleman,Terry A.       NH
Jung,Keith W.   NH
Gallant,Thelma Q.       MA
Jackson,Ralph V.        VT
Tesla,Geoffrey O.       NH
Tweed,Al O.     NH
Fives,Kristen F.        NH
Olsen,Ashley G. NH
 
9 Rows(s) Affected
ClassMethod Predicates1()
{
	s q1 = "SELECT Name,Home_State FROM Sample.Person "
	s q2 = "WHERE Home_State IN('MA','VT','NH')"
	s myquery = q1 _ q2
	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()
}
ClassMethod Predicates2()
{
	s list = $LISTBUILD("MA","VT","NH")
	s q1 = "SELECT Name,Home_State FROM Sample.Person "
	s q2 = "WHERE Home_State %INLIST(?)"
	s myquery = q1 _ q2
	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(list)
	d rset.%Display()
}

FOR SOME %ELEMENT谓词可以包含逻辑操作符,也可以使用逻辑操作符链接到其他谓词。 下面的例子显示了这一点:

SELECT Name,FavoriteColors FROM Sample.Person
WHERE FOR SOME %ELEMENT(FavoriteColors)(%VALUE='Red' OR %Value='White' 
      OR %Value %STARTSWITH 'B') 
      AND (Name BETWEEN 'A' AND 'F' OR Name %STARTSWITH 'S')
ORDER BY Name 

image

注意括号(Name BETWEEN 'A' AND 'F' OR Name %STARTSWITH 'S'); 如果没有这些分组括号,FOR SOME %ELEMENT条件将不适用于Name %STARTSWITH 'S'

使用OR的集合谓词

FOR SOME %ELEMENT 是一个集合谓词。 该谓词与OR逻辑操作符的使用受到限制,如下所示。 不能使用OR逻辑操作符将引用表字段的集合谓词与引用另一个表中的字段的谓词关联起来。 例如,

WHERE FOR SOME %ELEMENT(t1.FavoriteColors) (%VALUE='purple') 
OR t2.Age < 65

因为这个限制取决于优化器如何使用索引,所以SQL只能在向表添加索引时强制执行这个限制。 强烈建议在所有查询中避免这种类型的逻辑。

0
0 110
文章 姚 鑫 · 十一月 28, 2021 2m read

第九十章 SQL命令 WHERE CURRENT OF

使用游标指定当前行的UPDATE/DELETE子句。

大纲

WHERE CURRENT OF cursor

参数

  • cursor - 指定在光标的当前位置执行操作,光标是指向表的光标。

描述

WHERE CURRENT OF子句可用于基于游标的嵌入式SQL UPDATEDELETE语句,以指定位于要更新或删除记录上的游标。 例如:

   &sql(DELETE FROM Sample.Employees WHERE CURRENT OF EmployeeCursor)

删除最后一个FETCH命令从“EmployeeCursor”游标获得的行。

嵌入式SQL UPDATEDELETE可以使用WHERE子句(不带游标)或WHERE CURRENT OF(带声明游标),但不能同时使用两者。 如果指定的UPDATEDELETE既不带WHERE也不带WHERE CURRENT OF,则会更新或删除表中的所有记录。

更新的限制

当使用WHERE CURRENT OF子句时,不能使用当前字段值更新字段以生成更新的值。 例如,SET Salary=Salary+100SET Name=UPPER(Name)。 尝试这样做会导致SQLCODE -69错误:SET <field> = <value expression> not allowed with WHERE CURRENT OF <cursor>

示例

下面的嵌入式SQL示例显示了使用WHERE CURRENT OFUPDATE操作:

ClassMethod WhereCurrentOf()
{
	n %ROWCOUNT,%ROWID
	&sql(
		DECLARE WPCursor CURSOR FOR 
			SELECT Lang FROM SQLUser.WordPairs
			WHERE Lang='Sp'
	)
	&sql(OPEN WPCursor)
	q:(SQLCODE '= 0)
	for { 
		&sql(FETCH WPCursor)
		q:SQLCODE 
		&sql(
			UPDATE SQLUser.WordPairs SET Lang='Es'
			WHERE CURRENT OF WPCursor
		)
		if SQLCODE = 0 {
			w !,"Update succeeded"
			w !,"Row count=",%ROWCOUNT," RowID=",%ROWID 
		} else {
			w !,"Update failed, SQLCODE=",SQLCODE 
		}
	}
	&sql(CLOSE WPCursor)
}

下面的嵌入式SQL示例显示了使用WHERE CURRENT OFDELETE操作:

ClassMethod WhereCurrentOf1()
{
	n %ROWCOUNT,%ROWID
	&sql(
		DECLARE WPCursor CURSOR FOR 
		SELECT Lang FROM SQLUser.WordPairs
		WHERE Lang='En'
	)
	&sql(OPEN WPCursor)
	q:(SQLCODE'=0)
	for { 
		&sql(FETCH WPCursor)
		q:SQLCODE 
		&sql(
			DELETE FROM SQLUser.WordPairs
			WHERE CURRENT OF WPCursor
		)
		if SQLCODE=0 {
			w !,"Delete succeeded"
			w !,"Row count=",%ROWCOUNT," RowID=",%ROWID 
		} else {
			w !,"Delete failed, SQLCODE=",SQLCODE 
		}
	}
	&sql(CLOSE WPCursor)
}
0
0 93
文章 姚 鑫 · 十一月 27, 2021 4m read

第八十九章 SQL命令 WHERE(二)

相等比较谓词

下面是可用的相等比较谓词:

PredicateOperation
=Equals
<>Does not equal
!=Does not equal
>Is greater than
<Is less than
>=Is greater than or equal to
<=Is less than or equal to

例如:

SELECT Name, Age FROM Sample.Person
WHERE Age < 21

SQL根据排序规则(值的排序顺序)定义了比较操作。 如果两个值以完全相同的方式排序,则它们相等。 如果一个值排在第二个值之后,则该值大于另一个值。 字符串字段排序规则接受字段的默认排序规则。 IRIS默认排序规则不区分大小写。 因此,两个字符串字段值的比较或字符串字段值与字符串文字的比较(默认情况下)是不区分大小写的。 例如,如果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.

然而,请注意,两个字面值字符串的比较是区分大小写的:WHERE 'ma'=' ma'总是FALSE

BETWEEN谓词

BETWEEN比较操作符允许选择语法BETWEEN lowvalhighval指定范围内的数据值。 这个范围包括lowvalhighval本身的值。 这相当于一个成对的大于或等于运算符和一个小于或等于运算符。 这个比较如下面的例子所示:

SELECT Name,Age FROM Sample.Person
WHERE Age BETWEEN 18 AND 21

这将返回Sample中的所有记录。 人表,年龄值介于1821之间,包括这些值。 注意,必须按升序指定BETWEEN值; 像BETWEEN 21 AND 18这样的谓词将不返回任何记录。

与大多数谓词一样,BETWEEN可以使用NOT逻辑操作符进行倒装,如下例所示:

SELECT Name,Age FROM Sample.Person
WHERE Age NOT BETWEEN 20 AND 55
ORDER BY Age

这将返回Sample中的所有记录。 年龄值小于20或大于55Person表,不包括这些值。

BETWEEN通常用于一个数值范围,该范围按数字顺序排序。 但是,BETWEEN可以用于任何数据类型的值的排序序列范围。

BETWEEN使用与它所匹配的列相同的排序规则类型。 默认情况下,字符串数据类型排序不区分大小写。

IN和%INLIST谓词

IN谓词用于将一个值匹配到非结构化的一系列项。 它的语法如下:

WHERE field IN (item1,item2[,...])

Collation应用于IN比较,就像它应用于相等测试一样。 IN使用字段的默认排序规则。 默认情况下,与字段字符串值的比较不区分大小写。

%INLIST谓词是IRIS扩展,用于将值匹配到 IRIS列表结构的元素。 它的语法如下:

WHERE item %INLIST listfield

%INLIST使用EXACT排序。 因此,默认情况下,%INLIST字符串比较是区分大小写的。

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

Substring谓词

可以使用下面的方法来比较字段值和子字符串:

PredicateOperation
%STARTSWITH该值必须以指定的子字符串开始。
[包含运算符。该值必须包含指定的子字符串。

%STARTSWITH谓词

IRIS %STARTSWITH比较操作符允许对字符串或数字的初始字符执行部分匹配。 下面的示例使用%STARTSWITH。 选择“Name”“S”开头的记录:

SELECT Name,Age FROM Sample.Person
WHERE Name %STARTSWITH 'S'

与其他字符串字段比较一样,%STARTSWITH比较使用字段的默认排序规则。 默认情况下,字符串字段不区分大小写。 例如:

SELECT Name,Home_City,Home_State FROM Sample.Person
WHERE Home_City %STARTSWITH Home_State

包含运算符(()

Contains操作符是左括号符号:[。 它允许将子字符串(字符串或数字)匹配到字段值的任何部分。 比较总是区分大小写的。 下面的示例使用Contains操作符选择Name值中包含“S”的记录:

SELECT Name, Age FROM Sample.Person
WHERE Name [ 'S'

NULL 谓词

这将检测未定义的值。 可以检测所有空值,或所有非空值。 NULL谓词的语法如下:

WHERE field IS [NOT] NULL

NULL谓词条件是可以在WHERE子句中的流字段上使用的少数谓词之一。

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) 

FOR SOME 谓词

WHERE子句的FOR SOME谓词可用于根据一个或多个字段值的条件测试确定是否返回任何记录。 该谓词的语法如下:

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

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

下面的示例展示了如何使用FOR SOME谓词来确定是否返回结果集:

SELECT Name,Age AS AgeWithWorkers
FROM Sample.Person
WHERE FOR SOME (Sample.Person) (Age<65)
ORDER BY Age

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

FOR SOME %ELEMENT 谓词

WHERE子句的FOR SOME %ELEMENT谓词语法如下:

FOR SOME %ELEMENT(field) [AS e-alias] (predicate)

FOR SOME %ELEMENT谓词用指定的谓词子句值匹配字段中的元素。 SOME关键字指定字段中至少有一个元素必须满足指定的谓词条件。 谓词可以包含%VALUE%KEY关键字。

FOR SOME %ELEMENT谓词是一个集合谓词。

LIKE, %MATCHES, and %PATTERN 谓词

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

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

谓词和逻辑操作符

可以使用ANDOR逻辑操作符关联多个谓词。 可以使用括号对多个谓词进行分组。 由于IRIS使用已定义的索引和其他优化来优化WHERE子句的执行,因此无法预测andOR逻辑运算符链接的谓词的求值顺序。 因此,指定多个谓词的顺序对性能几乎没有影响。 如果希望严格地从左到右计算谓词,可以使用CASE语句。

注意:不能使用OR逻辑运算符将引用表字段的FOR SOME %ELEMENT集合谓词与引用另一个表中的字段的谓词关联起来。 例如,

WHERE FOR SOME %ELEMENT(t1.FavoriteColors) (%VALUE='purple') 
OR t2.Age < 65

因为这个限制取决于优化器如何使用索引,所以SQL只能在向表添加索引时强制执行这个限制。 强烈建议在所有查询中避免这种类型的逻辑。

0
0 82
文章 姚 鑫 · 十一月 26, 2021 2m read

第八十八章 SQL命令 WHERE(一)

指定一个或多个限制性条件的SELECT子句。

大纲

SELECT fields
FROM table
WHERE condition-expression

参数

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

描述

可选的WHERE子句可以用于以下目的:

  • 指定限制要返回哪些数据值的谓词。
  • 指定两个表之间的显式连接。
  • 指定基表和另一个表中的字段之间的隐式连接。

WHERE子句最常用于指定一个或多个谓词,这些谓词用于限制SELECT查询或子查询检索到的数据(过滤出行)。 还可以在UPDATE命令、DELETE命令或INSERT(或INSERT or UPDATE)命令的结果集中使用WHERE子句。

WHERE子句限定或取消查询选择中的特定行。 符合条件的行是那些条件表达式为真的行。 条件表达式可以是一个或多个逻辑测试(谓词)。 多个谓词可以通过ANDOR逻辑操作符链接。

如果谓词包含除法,并且数据库中有任何值可以生成值为零或NULL的除法,则不能依赖求值顺序来避免被零除法。 相反,使用CASE语句来抑制风险。

WHERE子句可以指定包含子查询的条件表达式。子查询必须用圆括号括起来。

WHERE子句可以使用=(内部连接)符号连接操作符指定两个表之间的显式连接。

WHERE子句可以使用箭头语法(- >)操作符在基表和来自另一个表的字段之间指定隐式连接。

指定字段

WHERE子句最简单的形式是指定一个比较字段和值的谓词,例如WHERE Age > 21。 有效的字段值包括以下:列名(WHERE Age > 21); %ID%TABLENAME,或%CLASSNAME; 标量函数指定列名(WHERE ROUND(Age,-1)=60),一个排序规则函数指定列名(WHERE %SQLUPPER(name) %STARTSWITH ' AB')。

不能按列号指定字段。

因为重新编译表时RowID字段的名称可能会改变,WHERE子句应该避免通过名称引用RowID(例如,WHERE ID=22)。 相反,使用%ID伪列名来引用RowID(例如,WHERE %ID=22)。

不能通过列别名指定字段; 尝试这样做会产生SQLCODE -29错误。 但是,可以使用子查询来定义列别名,然后在WHERE子句中使用该别名。 例如:

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

不能指定聚合字段; 尝试这样做将生成SQLCODE -19错误。 但是,可以通过使用子查询向WHERE子句提供聚合函数值。 例如:

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

整型和字符串

如果将定义为整数数据类型的字段与数值进行比较,则在执行比较之前将数值转换为规范形式。 例如,WHERE Age=007.00解析为WHERE Age=7。 这种转换发生在所有模式中。

如果将定义为整数数据类型的字段与Display模式下的字符串值进行比较,则将该字符串解析为数值。 例如,与任何非数字字符串一样,空字符串(")被解析为数字0。 这种解析遵循将字符串处理为数字的ObjectScript规则。 例如,WHERE Age='twenty'解析为WHERE Age=0; WHERE Age='20something'解析为WHERE Age=20。 SQL只在Display模式下执行解析; 在逻辑或ODBC模式下,将整数与字符串值进行比较将返回null`。

要比较字符串字段和包含单引号的字符串,请使用双引号。 例如,WHERE Name %STARTSWITH 'O''',返回的是 O’Neil and O’Connor, 而不是 Obama.

日期和时间

SQL日期和时间使用逻辑模式内部表示进行比较和存储。 它们可以以逻辑模式、显示模式或ODBC模式返回。 例如,1944年9月28日表示为:逻辑模式37891,显示模式09/28/1944,ODBC模式194409-28。 在条件表达式中指定日期或时间时,可能由于SQL模式与日期或时间格式不匹配,或由于无效的日期或时间值而发生错误。

WHERE子句条件表达式必须使用与当前模式相对应的日期或时间格式。 例如,在逻辑模式下,要返回出生日期为2005年的记录,WHERE子句将出现如下:WHERE DOB BETWEEN 59901 AND 60265 当在显示模式下,同样的WHERE子句会出现如下:WHERE DOB BETWEEN '01/01/2005' AND '12/31/2005'

如果条件表达式的日期或时间格式与显示模式不匹配,将导致错误:

  • 在显示模式或ODBC模式下,以不正确的格式指定日期数据将产生SQLCODE -146错误。 以不正确的格式指定时间数据将产生SQLCODE -147错误。
  • 在逻辑模式下,以不正确的格式指定日期或时间数据不会产生错误,但要么不返回数据,要么返回非预期的数据。 这是因为逻辑模式不会将显示或ODBC格式的日期或时间解析为日期或时间值。 WHERE DOB BETWEEN 37500 AND 38000 AND DOB <> '1944-09-28' '返回一系列DOB值,包括DOB=37891(1944年9月28日),这是<>谓词试图忽略的。

无效的日期或时间值还会生成SQLCODE -146-147错误。 无效日期是可以在显示模式/ODBC模式中指定的日期,但 IRIS不能转换为等效的逻辑模式。 例如,在ODBC模式下,以下命令会产生SQLCODE -146错误:WHERE DOB > '1830-01-01',因为 IRIS无法处理1840年12月31日之前的日期值。 以下在ODBC模式下也会产生SQLCODE -146错误:WHERE DOB BETWEEN '2005-01-01' AND '2005-02-29',因为2005不是闰年。

在逻辑模式下,Display模式或ODBC模式值不会被解析为日期或时间值,因此不会对其值进行验证。 因此,在逻辑模式下,WHERE子句(例如WHERE DOB > '1830-01-01')不会返回错误。

流字段

在大多数情况下,不能在WHERE子句谓词中使用流字段。 这样做将导致SQLCODE -313错误。 但是,在WHERE子句中允许使用流字段:

  • 流空测试:可以指定流字段IS null或流字段IS NOT null
  • 流长度测试:可以在WHERE子句谓词中指定CHARACTER_LENGTH(流字段)、CHAR_LENGTH(流字段)或DATALENGTH(流字段)函数。
  • 流子串测试:可以在WHERE子句谓词中指定substring (streamfield,start,length)函数。

List结构

IRIS支持列表结构数据类型%list(数据类型类%Library.List)。 这是一种压缩的二进制格式,并不映射到 SQL的相应本机数据类型。 它对应的数据类型为VARBINARY,默认MAXLEN32749。 因此,动态SQL不能在WHERE子句比较中使用%List数据。

要引用结构化列表数据,请使用%INLIST谓词或FOR SOME %ELEMENT谓词。

要在条件表达式中使用列表字段的数据值,可以使用%EXTERNAL将列表值与谓词进行比较。 例如,要返回FavoriteColors列表字段值由单个元素'Red'组成的所有记录:

SELECT Name,FavoriteColors FROM Sample.Person
WHERE %EXTERNAL(FavoriteColors)='Red'

%EXTERNAL将列表转换为DISPLAY格式时,显示的列表项似乎由一个空格分隔。 这个“空格”实际上是两个非显示字符CHAR(13)CHAR(10)。 要对列表中的多个元素使用条件表达式,必须指定这些字符。 例如,要返回FavoriteColors列表字段值由两个元素'Orange''Black'(按顺序)组成的所有记录:

SELECT Name,FavoriteColors FROM Sample.Person
WHERE %EXTERNAL(FavoriteColors)='Orange'||CHAR(13)||CHAR(10)||'Black'

变量

WHERE子句谓词可以指定:

%TABLENAME%CLASSNAME伪字段变量关键字。 %TABLENAME返回当前表名。 %CLASSNAME返回当前表对应的类名。 如果查询引用多个表,可以在关键字前加上表别名。 例如,t1.%TABLENAME

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

谓词列表

SQL谓词可分为以下几类:

  • Equality Comparison 谓词
  • BETWEEN 谓词
  • IN and %INLIST 谓词
  • %STARTSWITH Predicate and Contains Operator
  • NULL Predicate
  • EXISTS Predicate
  • FOR SOME Predicate
  • FOR SOME %ELEMENT Predicate
  • LIKE, %MATCHES, and %PATTERN Predicates
  • %INSET and %FIND Predicates

谓词区分大小写

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

%INLISTContains操作符([)、%MATCHES%PATTERN谓词不使用字段的默认排序规则。 它们总是使用区分大小写的EXACT排序法。

两个字面值字符串的谓词比较总是区分大小写的。

谓词条件和%NOINDEX

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

离群值的谓词条件

如果动态SQL查询中的WHERE子句选择了一个非空的离群值,可以通过将离群值文字括在双括号中来显著提高性能。 这些双括号导致动态SQL在优化时使用离群值选择性。 例如,如果企业位于马萨诸塞州,那么很大一部分员工将居住在马萨诸塞州。 对于EmployeesHome_State字段,'MA'是离群值。 要最优地选择这个值,应该指定WHERE Home_State=(('MA'))

在嵌入式SQL或视图定义中不应使用此语法。 在嵌入式SQL或视图定义中,总是使用离群值选择,不需要特殊编码。

动态SQL查询中的WHERE子句会自动针对空离群值进行优化。 例如,WHERE FavoriteColors IS NULL这样的子句。 当NULL是离群值时,is NULLis NOT NULL谓词不需要特殊编码。

离群值选择性由运行调优表实用程序决定。

0
0 161
文章 姚 鑫 · 十一月 25, 2021 4m read

第八十七章 SQL命令 VALUES

指定字段中使用的数据值的INSERT/UPDATE子句。

大纲

(field1{,fieldn})
     VALUES (value1{,valuen})

参数

  • field - 字段名或以逗号分隔的字段名列表。
  • value - 值或以逗号分隔的值列表。 每个值被赋给相应的字段。

描述

VALUES子句用于INSERTUPDATEINSERT or UPDATE语句中,以指定要插入到字段中的数据值。 通常:

  • INSERT查询使用以下语法:
INSERT INTO tablename (fieldname1,fieldname2,...)
     VALUES (value1,value2,...)
  • UPDATE查询使用以下语法:
UPDATE tablename (fieldname1,fieldname2,...)
     VALUES (value1,value2,...)

VALUES子句中的元素依次对应于表名后面指定的字段。 注意,如果在VALUES子句中只指定了一个value元素,则没有必要将元素括在括号中。

下面的嵌入式SQL示例显示了一个INSERT语句,它向“Employee”表添加了一行:

   &sql(INSERT INTO Employee (Name,SocSec,Telephone)
        VALUES("Boswell",333448888,"546-7989"))
   &sql(INSERT INTO Employee (Name,SocSec,Telephone)
        VALUES ('Boswell',333448888,'546-7989'))

插入和更新查询可以使用VALUES子句,而不需要在表名之后显式指定字段名列表。 为了省略表名后面的字段名列表,查询必须满足以下两个条件:

  • values子句中指定的值的数量与表中字段的数量相同(不包括ID字段)。
  • values子句中的值按字段的内部列号顺序列出,从列2开始。 列1总是为系统生成的ID字段保留,而不是在VALUES子句中指定。

例如,查询:

INSERT INTO Sample.Person VALUES (5,'John')

等价于查询:

INSERT INTO Sample.Person (Age,Name) VALUES (5,'John')

如果表“Sample.Person”正好有两个用户定义的字段。

在本例中,将值5分配给列号较低的字段,将值“John”分配给另一个字段。

VALUES子句可以指定数组的元素,如下面的嵌入式SQL示例所示:

   &sql( UPDATE Person(Tel)
        VALUES :per('tel',)
        WHERE ID = :id )

UPDATE查询还可以引用具有未指定最后一个下标的数组。INSERT使用数组元素的存在和不存在来为新创建的行赋值和默认值,而UPDATE使用数组元素的存在来指示应该更新相应的字段。例如,假设有六列的表使用以下数组:

emp("profile",2)="Smith"
emp("profile",3)=2
emp("profile",3,1)="1441 Main St."
emp("profile",3,2)="Cableton, IL 60433"
emp("profile",5)=NULL
emp("profile",7)=25
emp("profile","next")="F"

列1始终保留给ID字段,并且不是用户指定的。插入的“Employee”行将第2列“name”设置为“Smith”;将第3列“Address”设置为两行值;未指定第4列“Department”,因此将其设置为默认值;将第5列“Location”设置为NULL。由于相应的数组元素定义为空值,因此不使用“location”的默认值。数组元素“7”和“NEXT”“Employee”表中的列号不对应,因此查询会忽略它们。下面是使用此数组的UPDATE语句:

  &sql(UPDsqlATE Employee
       VALUES :emp('profile',)
       WHERE Employee = 379)

根据上面的定义和数组值,该语句将更新row ID = 379“Employee”行的“Name”“Address”“Location”字段的值。

然而,完全省略下标将导致SQLCODE -54错误:在VALUES之后需要数组指示符(省略了最后一个下标)。

你也可以使用一个数组引用的UPDATE查询,目标多行,例如:

  &sql(UPDATE Employee
       VALUES :emp('profile',)
       WHERE Type = 'PART-TIME')

VALUES子句变量不能使用点语法。 因此,下面的嵌入式SQL示例是正确的:

   SET sname = state.Name
   &sql(INSERT INTO StateTbl VALUES :sname)

以下是不正确的:

     &sql(INSERT INTO State VALUES :state.Name)

NULL和空字符串值是不同的。 为了向后兼容,旧数据中的所有空字符串(")值都被认为是NULL值。 在新数据中,空字符串以$CHAR(0)的形式存储在数据中。 通过SQL, NULL被引用为'NULL'。 例如:

INSERT INTO Sample.Person
(SSN,Name,Home_City) VALUES ('123-45-6789','Doe,John',NULL)

通过SQL,空字符串被引用为"(两个单引号)。 例如:

INSERT INTO Sample.Person
(SSN,Name,Home_City) VALUES ('123-45-6789','Doe,John','')

不能为ID字段插入NULL值。

示例

下面的嵌入式SQL示例将“Doe,John”的一条记录插入到示例中。 人表。 然后它选择这个记录,然后删除这个记录。 第二个SELECT确认删除。

ClassMethod Value()
{
	s x = "Doe,John",y = "123-45-6789",z = "Metropolis"
	s (a, b, c, d, e)=0
	n SQLCODE,%ROWCOUNT,%ROWID
	&sql(
		INSERT INTO Sample.Person
		(
			Name, SSN, Home_City
		) 
		VALUES 
		(
			:x, :y, :z
		)
	)
	if SQLCODE '= 0 {
		w !,"INSERT Error code ",SQLCODE
		q 
	}
	&sql(
		SELECT Name, SSN, Home_City
			INTO :a, :b, :c
			FROM Sample.Person
			WHERE Name = :x
	)
	if SQLCODE '= 0 {
		w !,"Error code ",SQLCODE 
	} else {
		w !,"After INSERT:"
		w !,"Name = ", a," SSN = ", b," City = ",c
		w !,"SQL code = ", SQLCODE," Number of rows = ", %ROWCOUNT 
	}
	&sql(
		DELETE FROM Sample.Person
			WHERE Name = :x
	)
	&sql(
		SELECT Name,SSN
			INTO :d,:e
			FROM Sample.Person
			WHERE Name='Doe,John'
	)
	if SQLCODE <0 {
		w !,"Error code ",SQLCODE 
	} else {
		w !,"After DELETE:"
		w !,"Name=",d," SSN=",e
		w !,"SQL code=",SQLCODE," Number of rows=",%ROWCOUNT 
	}
}
0
0 130
文章 姚 鑫 · 十一月 24, 2021 2m read

第八十六章 SQL命令 USE DATABASE

设置当前名称空间和数据库。

大纲

USE [DATABASE] dbname

参数

  • dbname - 当前进程将使用的名称空间和相应的数据库作为当前名称空间。

描述

USE DATABASE命令将当前进程切换到指定的名称空间及其关联的数据库。 这允许在SQL中更改名称空间。 DATABASE关键字是可选的。

指定的dbname是所需的命名空间和包含数据库文件的对应目录的名称。 指定dbname作为标识符。 命名空间名称不区分大小写。

由于USERSQL保留字,因此必须使用分隔标识符来指定用户命名空间,如下面的SQL Shell示例所示:

USER>>USE DATABASE Samples
SAMPLES>>USE DATABASE "User"
USER>>

如果指定的dbname不存在,IRIS将发出SQLCODE -400错误。

USE DATABASE命令是一个特权操作。 在使用USE DATABASE之前,必须以具有适当权限的用户身份登录。 如果不这样做,将导致SQLCODE -99错误(特权冲突)。

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

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

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

也可以使用ObjectScript ZNSPACE命令或SET $NAMESPACE语句切换到其他命名空间。

通过xDBC执行

当通过xDBC执行USE DATABASE命令时,服务器进程将执行一次模拟的连接重置。 清理服务器进程使用的数据结构。 但是,没有改变提交模式。 Read Committed设置也没有改变。 如果事务正在处理中,则事务将继续执行,不会提交或回滚。

0
0 115
文章 姚 鑫 · 十一月 23, 2021 3m read

第八十五章 SQL命令 UPDATE(四)

示例

本节中的示例更新SQLUser.MyStudents表。 下面的示例创建SQLUser.MyStudents表,并用数据填充它。 因为这个示例的重复执行会积累具有重复数据的记录,所以它使用TRUNCATE TABLE在调用INSERT之前删除旧数据。 在调用UPDATE示例之前执行这个示例:

ClassMethod Update1()
{
CreateStudentTable
	s stuDDL = 5
	s stuDDL(1) = "CREATE TABLE SQLUser.MyStudents ("
	s stuDDL(2) = "StudentName VARCHAR(32),StudentDOB DATE,"
	s stuDDL(3) = "StudentAge INTEGER COMPUTECODE {SET {StudentAge}="
	s stuDDL(4) = "$PIECE(($PIECE($H,"","",1)-{StudentDOB})/365,""."",1)} CALCULATED,"
	s stuDDL(5) = "Q1Grade CHAR,Q2Grade CHAR,Q3Grade CHAR,FinalGrade VARCHAR(2))"
	s tStatement = ##class(%SQL.Statement).%New(0,"Sample")
	s qStatus = tStatement.%Prepare(.stuDDL)
	if qStatus'=1 {
		w "DDL %Prepare failed:" 
		d $System.Status.DisplayError(qStatus) 
		q
	}
	s rtn = tStatement.%Execute()
	if rtn.%SQLCODE = 0 {
		w !,"表创建成功"
	} elseif rtn.%SQLCODE = -201 {
		w "表已经存在, SQLCODE=",rtn.%SQLCODE,!
	} else {
		w !,"表创建失败, SQLCODE=",rtn.%SQLCODE,!
		w rtn.%Message,! 
	}
RemoveOldData
	s clearit = "TRUNCATE TABLE SQLUser.MyStudents"
	s qStatus = tStatement.%Prepare(clearit)
	if qStatus'=1 {
		w "Truncate %Prepare failed:" 
		d $System.Status.DisplayError(qStatus) 
		q
	}
	s truncrtn = tStatement.%Execute()
	if truncrtn.%SQLCODE = 0 {
		w !,"表旧数据已经被删除",!
	} elseif truncrtn.%SQLCODE = 100 {
		w !,"没有数据可以删除",!
	} else {
		w !,"truncate failed, SQLCODE=",truncrtn.%SQLCODE," ",truncrtn.%Message,! 
	}
PopulateStudentTable
	s studentpop=2
	s studentpop(1)="INSERT INTO SQLUser.MyStudents (StudentName,StudentDOB) "
	s studentpop(2)="SELECT Name,DOB FROM Sample.Person WHERE Age <= '21'"
	s qStatus = tStatement.%Prepare(.studentpop)
	if qStatus '= 1 {
		w "填充 %Prepare 失败:" 
		d $System.Status.DisplayError(qStatus) 
		q
	}
	s poprtn = tStatement.%Execute()
	if poprtn.%SQLCODE = 0 {
		w !,"表填充成功",!
	    w poprtn.%ROWCOUNT," rows inserted"
	} else {
		w !,"表填充失败, SQLCODE=",poprtn.%SQLCODE,!
	    w poprtn.%Message 
	}
}

可以使用下面的查询来显示这些示例的结果:

SELECT %ID,* FROM SQLUser.MyStudents ORDER BY StudentAge,%ID

下面的一些UPDATE示例依赖于其他UPDATE示例设置的字段值; 它们应该按照指定的顺序运行。

在下面的动态SQL示例中,SET field=value UPDATE修改选定记录中的指定字段。 在我的学生表中,7岁以下的孩子是不给分数的:

ClassMethod Update2()
{
	s studentupdate=3
	s studentupdate(1)="UPDATE SQLUser.MyStudents "
	s studentupdate(2)="SET FinalGrade='NA' "
	s studentupdate(3)="WHERE StudentAge <= 6"
	s tStatement = ##class(%SQL.Statement).%New(0,"Sample")
	s qStatus = tStatement.%Prepare(.studentupdate)
	if qStatus'=1 {
		w "%Prepare failed:" 
		d $System.Status.DisplayError(qStatus) 
		q
	}
	s uprtn = tStatement.%Execute()
	if uprtn.%SQLCODE=0 {
		w !,"表更新成功"
		w !,"Rows updated=",uprtn.%ROWCOUNT," Final RowID=",uprtn.%ROWID
	} else {
		w !,"Table update failed, SQLCODE=",uprtn.%SQLCODE," ",uprtn.%Message 
	}
}

在下面的基于游标的嵌入式SQL示例中,SET field1=value1,field2=value2 UPDATE修改选定记录中的几个字段。 在MyStudents表中,它用Q1和Q2成绩更新指定的学生记录:

ClassMethod Update2()
{
	#SQLCompile Path = Sample
	n %ROWCOUNT,%ROWID
	&sql(
		DECLARE StuCursor CURSOR FOR 
			SELECT * FROM MyStudents
			WHERE %ID IN(10,12,14,16,18,20,22,24) AND StudentAge > 6)
	&sql(OPEN StuCursor)
	q:(SQLCODE'=0)
	for { 
		&sql(FETCH StuCursor)
		q:SQLCODE 
		&sql(
			Update MyStudents SET Q1Grade='A',Q2Grade='A'
			WHERE CURRENT OF StuCursor
		)
		if SQLCODE=0 {
			w !,"表更新成功"
			w !,"Row count=",%ROWCOUNT," RowID=",%ROWID 
		} else {
			w !,"表更新失败, SQLCODE=",SQLCODE 
		}
	}
	&sql(CLOSE StuCursor)
}

在下面的动态SQL示例中,字段列表VALUES值列表UPDATE修改选定记录中几个字段的值。 在我的学生表中,没有拿到期末成绩的孩子也没有拿到季度成绩:

ClassMethod Update4()
{
	s studentupdate=3
	s studentupdate(1)="UPDATE SQLUser.MyStudents "
	s studentupdate(2)="(Q1Grade,Q2Grade,Q3Grade) VALUES ('x','x','x') "
	s studentupdate(3)="WHERE FinalGrade='NA'"
	s tStatement = ##class(%SQL.Statement).%New()
	s qStatus = tStatement.%Prepare(.studentupdate)
	if qStatus'=1 {
		w "%Prepare failed:" 
		d $System.Status.DisplayError(qStatus) 
		q
	}
	s uprtn = tStatement.%Execute()
	if uprtn.%SQLCODE=0 {
		w !,"Table Update successful"
		w !,"Rows updated=",uprtn.%ROWCOUNT," Final RowID=",uprtn.%ROWID
	} else {
		w !,"Table Update failed, SQLCODE=",uprtn.%SQLCODE," ",uprtn.%Message,! 
	}
}

在下面的动态SQL示例中,VALUES值列表UPDATE修改所选记录中的所有字段值。 注意,此语法要求为记录中的每个字段指定一个值。 在“user.student”表中,有几个孩子已经从学校退学。 保留它们的记录id和名称,并在名称后附加单词withdraw; 所有其他数据被删除,DOB字段用于提取日期:

ClassMethod Update5()
{
	s studentupdate=4
	s studentupdate(1)="UPDATE SQLUser.MyStudents "
	s studentupdate(2)="VALUES (StudentName||' WITHDRAWN',"
	s studentupdate(3)="$PIECE($HOROLOG,',',1),00,'-','-','-','XX') "
	s studentupdate(4)="WHERE %ID IN(7,10,22)"
	s tStatement = ##class(%SQL.Statement).%New()
	s qStatus = tStatement.%Prepare(.studentupdate)
	if qStatus'=1 {
		w "%Prepare failed:" 
		d $System.Status.DisplayError(qStatus) 
		q
	}
	s uprtn = tStatement.%Execute()
	if uprtn.%SQLCODE=0 {
		w !,"Table Update successful"
		w !,"Rows updated=",uprtn.%ROWCOUNT," Final RowID=",uprtn.%ROWID
	} else {
		w !,"Table Update failed, SQLCODE=",uprtn.%SQLCODE," ",uprtn.%Message,! 
	}
}

在下面的动态SQL示例中,子查询UPDATE使用子查询来选择记录。 然后使用SET field=value语法修改这些记录。 因为在SQLUser中,StudentAge是从出生日期开始计算的。 我的学生们,任何不满一年的人的计算年龄都是<Null>,任何出生日期已被取消的人的计算年龄都非常高。 这里标记了StudentName字段,以便将来确认出生日期:

ClassMethod Update6()
{
	s studentupdate=3
	s studentupdate(1)="UPDATE (SELECT StudentName FROM SQLUser.MyStudents "
	s studentupdate(2)="WHERE StudentAge IS NULL OR StudentAge > 21) "
	s studentupdate(3)="SET StudentName = StudentName||' *** CHECK DOB' "
	s tStatement = ##class(%SQL.Statement).%New(0,"Sample")
	s qStatus = tStatement.%Prepare(.studentupdate)
	if qStatus'=1 {
		w "%Prepare failed:" 
		d $System.Status.DisplayError(qStatus) 
		q
	}
	s uprtn = tStatement.%Execute()
	if uprtn.%SQLCODE=0 {
		w !,"Table Update successful"
		w !,"Rows updated=",uprtn.%ROWCOUNT," Final RowID=",uprtn.%ROWID
	} else {
		w !,"Table Update failed, SQLCODE=",uprtn.%SQLCODE," ",uprtn.%Message,! 
	}
}

在下面的嵌入式SQL示例中,VALUES:array()UPDATE修改选定记录中的数组中列号指定的字段值。 只能在嵌入式SQL中进行VALUES:array()更新。 注意,此语法要求您通过DDL列号指定每个值(包括在列计数中RowID列(列1),但不为这个不可修改的字段提供值)。 在MyStudents表中,在Q1Grade(第5列)和Q2Grade(第6列)字段中,4到6岁(包括4到6岁)的孩子会得到一个“P”(代表“Present”)。 所有其他记录数据保持不变:

ClassMethod Update7()
{
	s arry(5)="P"
	s arry(6)="P"
	&sql(
		update SQLUser.MyStudents VALUES :arry() 
		where FinalGrade='NA' AND StudentAge > 3)
	if SQLCODE=0 {
		w "Table Update successful",!
		w "Rows updated=",%ROWCOUNT," Final RowID=",%ROWID
	} else {
		w "Table Update failed, SQLCODE=",SQLCODE,! 
	}
}
0
0 88
文章 姚 鑫 · 十一月 22, 2021 6m read

第八十四章 SQL命令 UPDATE(三)

参照完整性

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

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

UPDATE操作期间,对于每个具有更新字段值的外键引用,都会在被引用表中的旧(更新前)引用行和新(更新后)引用行上获得共享锁。 这些行在执行引用完整性检查和更新行时被锁定。 然后释放锁(直到事务结束才持有锁)。 这确保了引用的行不会在引用完整性检查和更新操作完成之间发生更改。 锁定旧行可以确保在可能的UPDATE回滚之前不会更改所引用的行。 锁定新行可以确保引用的行不会在引用完整性检查和更新操作完成之间发生更改。

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

原子性

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

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

  • 1IMPLICIT (autocommit on)——默认行为,如上所述。 每个UPDATE构成一个单独的事务。

  • 2EXPLICIT (autocommit off) -如果没有事务在进行中,UPDATE会自动启动一个事务,但是你必须显式地COMMITROLLBACK来结束事务。 在EXPLICIT模式下,每个事务的数据库操作数是用户定义的。

  • 0或NONE(没有自动事务)——调用UPDATE时不会启动任何事务。 失败的UPDATE操作可能会使数据库处于不一致的状态,一些指定的行被更新,而一些未被更新。 要在此模式中提供事务支持,必须使用START transaction来启动事务,并使用COMMITROLLBACK来结束事务。

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

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

ClassMethod Update()
{
	s stat = $SYSTEM.SQL.SetOption("AutoCommit", $RANDOM(3),.oldval)
	if stat '= 1 {
		w "SetOption failed:" d $System.Status.DisplayError(stat) q
	}
	s x = $SYSTEM.SQL.GetOption("AutoCommit")
	if x = 1 {
		w "默认的原子性行为",!
		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>错误,UPDATE将发出SQLCODE -110错误

计数器递增

  • ROWVERSION
  • SERIAL (%Counter)

ROWVERSION计数器增量

如果一个表有一个数据类型为ROWVERSION的字段,那么对一行执行更新将自动更新该字段的整数值。 ROWVERSION字段接受来自名称空间范围的行版本计数器的下一个顺序整数。 试图指定ROWVERSION字段的更新值将导致SQLCODE -138错误。

SERIAL (%Counter)计数器增量

UPDATE操作对SERIAL (%Library.Counter)计数器字段值没有影响。 但是,使用INSERT OR update执行的更新会导致在串行字段的后续插入操作中跳过整数序列。

权限

要执行更新,必须对指定的表(或视图)具有表级update权限,或者对指定的列具有列级update权限。 当更新一行中的所有字段时,请注意,列级特权覆盖GRANT命令中命名的所有表列; 表级权限涵盖所有表列,包括分配权限后添加的列。

  • 用户必须对指定的表具有UPDATE权限,或者对更新字段列表中的所有列具有列级UPDATE权限。
  • 用户必须对WHERE子句中的字段具有SELECT权限,无论这些字段是否要更新。 如果这些字段包含在更新字段列表中,则必须同时拥有这些字段的SELECTUPDATE权限。 在下面的示例中,Name字段必须(至少)具有列级的SELECT权限:
UPDATE Sample.Employee (Salary) VALUES (1000000) WHERE Name='Smith, John'

在上面的示例中,Salary字段只需要列级UPDATE特权。

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

当属性被定义为ReadOnly时,相应的表字段也被定义为ReadOnly。 只读字段只能使用InitialExpressionSqlComputed赋值。 尝试更新具有列级ReadOnly (SELECTREFERENCES)权限的字段的值(即使是NULL值)将导致SQLCODE -138错误:无法为只读字段插入/更新值。 当您使用链接表向导链接一个表时,您可以选择将字段定义为只读。 源系统上的字段可能不是只读的,但是如果IRIS将链接表的字段定义为只读,那么尝试引用该字段的UPDATE将导致SQLCODE -138错误。

级安全

IRIS行级安全允许UPDATE修改任何安全允许它访问的行。 它允许更新行,即使更新创建的行安全性不允许随后访问。 为了确保更新不会阻止您对行的后续SELECT访问,建议通过具有WITH CHECK OPTION的视图执行update

0
0 153
文章 姚 鑫 · 十一月 21, 2021 9m read

第八十三章 SQL命令 UPDATE(二)

显示到逻辑数据转换

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

对于已转换的数据,可以在LOGICAL模式(默认)中更新数据值,或者通过指定选择模式,使用更易于阅读的格式(DISPLAY模式或ODBC模式)更新数据值。 例如,通过指定选择模式,可以以DISPLAY格式(例如2/22/2018)、ODBC格式(例如2018-02-22)或逻辑格式(例如64701)更新日期。 对于某些数据类型,还可以在ODBC或DISPLAY选择模式下以LOGICAL格式指定数据。

列表结构

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

流值

可以按照如下方法更新流字段中的数据值:

  • 对于任何表:字符串字面值或包含字符串字面值的主机变量,例如:
    SET literal="update stream string value"
       //do the update; use a string
    &sql(UPDATE MyStreamTable SET MyStreamField = :literal WHERE %ID=21)
  • 对于非分片表:流对象的对象引用(OREF)。 IRIS打开这个对象并复制它的内容,更新stream字段。 例如:
    SET oref=##class(%Stream.GlobalCharacter).%New()
    DO oref.Write("Update stream string value non-shard 1")
       //do the update; use an actual OREF
    &sql(UPDATE MyStreamTable SET MyStreamField = :oref WHERE %ID=22)

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

    SET oref=##class(%Stream.GlobalCharacter).%New()
    DO oref.Write("Update stream string value non-shard 2")
       //next line converts OREF to a string OREF
    set string=oref_""
       //do the update
    &sql(UPDATE MyStreamTable SET MyStreamField = :string WHERE %ID=23)
  • 对于分片表:使用存储在^IRIS.Stream中的临时流对象的对象ID (OID)。 碎片全球:
    SET clob=##class(%Stream.GlobalCharacter).%New("Shard")
    DO clob.Write("Update sharded table stream string value")
    SET sc=clob.%Save() // Handle $$$ISERR(sc)
      set ClobOid=clob.%Oid()
    //do the update
    &sql(UPDATE MyStreamTable SET MyStreamField = :ClobOid WHERE %ID=24)

不能使用流字段的内容更新非流字段。 这将导致一个SQLCODE -303错误:“不支持在UPDATE赋值中隐式地将流值转换为非流字段”。 要用Stream数据更新字符串字段,必须首先使用SUBSTRING函数将Stream数据的前n个字符转换为字符串,如下面的示例所示:

UPDATE MyTable
     SET MyStringField=SUBSTRING(MyStreamField,1,2000)

计算字段

COMPUTECODE定义的字段可以作为UPDATE操作的一部分重新计算它的值,如下所示:

  • COMPUTECODE:值在INSERT时计算并存储,在UPDATE时不更改值。
  • 带有COMPUTEONCHANGECOMPUTECODE:值在INSERT时计算并存储,在UPDATE时重新计算并存储。
  • COMPUTECODE with DEFAULT and COMPUTEONCHANGE:默认值在INSERT时存储,值在UPDATE时计算并存储。 如果计算代码包含一个编程错误(例如,除以0),UPDATE操作将失败,并出现SQLCODE -415错误。
  • COMPUTECODE WITH COMPUTECODE WITH COMPUTED或TRANSPENT:不能更新此字段的值,因为没有存储值。查询时会计算该值。但是,如果尝试更新计算字段中的值, IRIS会对提供的值执行验证,如果值无效,则会发出错误。如果该值有效,则IRIS不执行更新操作,不发出SQLCODE错误,并递增ROWCOUNT

当没有实际更新发生时,COMPUTEONCHANGE计算字段不会重新计算:当update操作的新字段值与之前的字段值相同时。

在大多数情况下,将计算字段定义为只读。 这防止更新操作直接更改一个值,该值是涉及其他字段值的计算结果。 在本例中,试图使用UPDATE覆盖计算字段的值将导致SQLCODE -138错误。

但是,可能希望修改一个计算字段值,以反映对一个(或多个)源字段值的更新。 可以通过使用更新触发器来实现这一点,该更新触发器在您更新了指定的源字段之后重新计算计算过的字段值。 例如,对Salary数据字段的更新可能触发重新计算Bonus computed字段的触发器。 这个更新触发器重新计算Bonus并成功完成,即使Bonus是一个只读字段。

可以使用CREATE TABLE ON UPDATE关键字短语来定义一个字段,当记录被更新时,该字段被设置为文字或系统变量(例如当前时间戳)。

%SerialObject属性

当更新%SerialObject中的数据时,必须更新引用嵌入%SerialObject的表(持久化类); 不能直接更新%SerialObject。 从引用表中,可以:

  • 使用引用字段将多个%SerialObject属性的值更新为%List结构。 例如,如果持久化类有一个属性PAddress引用一个串行对象包含属性,城市和国家(依次),SET PAddress=$LISTBUILD('123 Main St.','Newtown','USA') or (PAddress) VALUES ($LISTBUILD('123 Main St.','Newtown','USA')) or (PAddress) VALUES (:vallist)%List必须包含串行对象(或占位符逗号)的属性值,其顺序与串行对象中指定的属性的顺序一致。

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

  • 使用下划线语法以任意顺序更新单个%SerialObject属性的值。例如,如果持久类的特性PAddress引用了包含特性StreetCityCountry的序列对象,则可以更新集合PAddress_City=‘Newtown’PAddress_Street=‘123 Main St.’PAddress_Country=‘USA’

此类型的更新执行%SerialObject属性值的验证。

FROM子句

UPDATE命令可能没有FROM关键字。它可以简单地指定要更新的表(或视图),并使用WHERE子句选择要更新的行。

但是,还可以在value-assignment-语句之后包括一个可选的FROM子句。此FROM子句指定用于确定要更新哪些记录的一个或多个表。FROM子句通常(但并非总是)与涉及多个表的WHERE子句一起使用。FROM子句可以很复杂,并且可以包括ANSI联接语法。UPDATE FROM子句允许SELECT FROM子句中支持的任何语法。此UPDATE FROM子句提供与Transact-SQL的功能兼容性。

以下示例显示如何使用此FROM子句。它更新Employees表中的那些记录,其中也可以在Requirees表中找到相同的EmpId

UPDATE Employees AS Emp
     SET retired='Yes'
     FROM Retirees AS Rt
     WHERE Emp.EmpId = Rt.EmpId

如果UPDATE TABLE-REFFROM子句引用同一个表,则这些引用可能是引用同一个表,也可能是引用该表的两个实例的联接。这取决于如何使用表别名:

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

%Keyword 参数

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

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

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

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

如果希望在指定%NOCHECK时阻止导致非唯一数据值的更新,请在更新之前执行EXISTS检查。

如果只希望禁用外键引用完整性检查,请使用$SYSTEM.SQL.Util.SetOption(“FilerRefIntegrity”)方法,而不是指定%NOCHECK。或者,可以使用NOCHECK关键字定义外键,这样就永远不会执行外键引用完整性检查。

  • %NOFPLAN - FROM子句语法仅:此操作忽略冻结的计划(如果有); 该操作将生成一个新的查询计划。 冻结的计划被保留,但不使用。
  • %NOINDEX -在UPDATE处理期间没有设置索引映射。 用户必须对当前名称空间具有相应的%NOINDEX管理权限才能应用此限制。 如果不这样做,会出现一个带有%msgSQLCODE -99错误,用户“name”没有%NOINDEX权限。
  • %NOJOURN -在更新操作期间抑制日志记录。 任何行中所做的更改都不会被记录到日志中,包括任何被拉出的触发器。 如果在带有%NOJOURN的语句之后执行ROLLBACK,则该语句所做的更改将不会回滚。
  • %NOLOCK -在UPDATE时未锁定行。 这应该只在单个用户/进程更新数据库时使用。 用户必须拥有当前命名空间相应的%NOLOCK管理权限才能应用此限制。 如果不这样做,将导致%msgSQLCODE -99错误,用户“name”没有%NOLOCK权限。
  • %NOTRIGGER—在UPDATE处理过程中不会提取基表触发器。 BEFOREAFTER触发器都不执行。 用户必须对当前命名空间具有相应的%NOTRIGGER管理权限才能应用此限制。 如果不这样做,将导致%msgSQLCODE -99错误。User 'name' does not have %NOTRIGGER privileges
  • %PROFILE%PROFILE_ALL—如果指定了这些关键字指令中的一个,将生成SQLStats收集代码。 这与打开PTools时生成的代码相同。 不同之处在于,SQLStats收集代码仅为该特定语句生成。 正在编译的例程/类中的所有其他SQL语句将生成代码,就像PTools被关闭一样。 这使用户能够分析/检查应用程序中的特定问题SQL语句,而不必为未被调查的SQL语句收集无关的统计信息。

%PROFILE为主查询模块收集SQLStats%PROFILE_ALL为主查询模块及其所有子查询模块收集SQLStats

可以以任何顺序指定多个%keyword 参数。 多个参数由空格分隔。

0
0 86
文章 姚 鑫 · 十一月 20, 2021 8m read

第八十二章 SQL命令 UPDATE(一)

为指定表中的指定列设置新值。

大纲

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

UPDATE [%keyword] table-ref [[AS] t-alias]
   value-assignment-statement
   [WHERE CURRENT OF cursor]

value-assignment-statement ::=
   SET column1 = scalar-expression1 {,column2 = scalar-expression2} ...  |
   [ (column1 {,column2} ...) ] VALUES (scalar-expression1 {,scalar-expression2} ...)  |
   VALUES :array()

参数

  • %keyword - 可选参数:%NOCHECK%NOFPLAN%NOINDEX%NOJOURN%NOLOCK%NOTRIGGER%PROFILE%PROFILE_ALL
  • table-ref - 要更新数据的现有表的名称。 还可以指定一个视图,通过该视图对表执行更新。 不能在此参数中指定表值函数或JOIN语法。 表名(或视图名)可以是限定的(schema.table)或非限定的(table)。 使用模式搜索路径(如果提供的话)或默认模式名将非限定名称匹配到其模式。
  • AS t-alias - 可选- table-ref(表或视图)名称的别名。 别名必须是有效的标识符。 AS关键字是可选的。
  • FROM select-table - 可选的——FROM子句,用于指定用于确定要更新哪些行的表。 多个表可以指定为逗号分隔的列表或与ANSI连接关键字关联。 可以指定任何表或视图的组合。 如果在这里在两个选择表之间指定逗号, IRIS将对表执行CROSS JOIN,并从JOIN操作的结果表中检索数据。 如果在这里指定两个选择表之间的ANSI连接关键字, IRIS将执行指定的连接操作。 可以选择指定一个或多个优化选项关键字来优化查询执行。 可用的选项有:%ALLINDEX%FIRSTTABLE select-table%FULL%INORDER%IGNOREINDICES%NOFLATTEN%NOMERGE%NOSVSO%NOTOPOPT% nounoropt%PARALLEL%STARTTABLE
  • WHERE condition-expression - 可选-指定一个或多个布尔谓词,用于确定要更新哪些行。 如果没有提供WHERE子句(或WHERE CURRENT OF子句),UPDATE将更新表中的所有行。
  • WHERE CURRENT OF cursor - 可选:仅嵌入SQL—指定UPDATE操作更新游标当前位置的记录。 可以指定WHERE CURRENT OF子句或WHERE子句,但不能同时指定两者。
  • column - 可选—现有列的名称。 多个列名指定为逗号分隔的列表。 如果省略,则更新所有列。
  • scalar-expression - 用标量表达式表示的列数据值。 多个数据值指定为逗号分隔的列表,其中每个数据值依次对应于一个列。
  • :array() - 仅嵌入式SQL—指定为主机变量的值数组。 数组的最低下标级别必须是未指定的。 因此::myupdates(), :myupdates(5,):myupdates(1,1,)都是有效的规范。

描述

UPDATE命令更改表中列的现有值。 可以直接更新表中的数据,也可以通过视图进行更新,或者使用括在括号中的子查询进行更新。 通过视图进行更新受制于需求和限制,如CREATE view中所述。

UPDATE命令为包含这些列的一个或多个现有基表行提供一个或多个新列值。 将数据值赋给列是使用值赋值语句完成的。 默认情况下,值赋值语句更新表中的所有行。

更常见的是,UPDATE根据条件表达式指定对特定的行(或行)进行更新。 默认情况下,UPDATE操作遍历表中的所有行,并更新满足条件表达式的所有行。 如果没有行满足条件表达式,UPDATE将成功完成并设置SQLCODE=100(不再有数据)。

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

UPDATE操作将%ROWCOUNT局部变量设置为更新的行数,将%ROWID局部变量设置为更新的最后一行的ROWID值。

默认情况下,UPDATE操作是一个全有或全无事件。 要么更新所有指定的行和列,要么不更新。

INSERT OR UPDATE

INSERT OR UPDATE语句是INSERT语句的变体,执行插入和更新操作。 首先,它尝试执行一个插入操作。 如果插入请求失败由于违反唯一键(字段(s)的一些独特的关键,存在这一行已经有相同的值(s)为插入指定的行),然后它会自动变成一个更新请求这一行,并插入或更新使用指定的字段值来更新现有的行。

SQLCODE错误

默认情况下,多行UPDATE是一个原子操作。 如果不能更新一行或多行,则UPDATE操作失败,不会更新任何行。 IRIS设置SQLCODE变量,该变量指示UPDATE的成功或失败,如果操作失败,还设置%msg。 要更新表,更新必须满足所有表、列名和值要求,如下所示。

表:

  • 表必须存在于当前(或指定)命名空间中。 如果无法找到指定的表,IRIS将发出SQLCODE -30错误。
  • 该表不能定义为READONLY。 试图编译引用只读表的UPDATE会导致SQLCODE -115错误。 注意,此错误是在编译时发出的,而不是在执行时发生的。 请参阅定义和使用类的其他持久化类选项章节中READONLY对象的描述。
  • 该表不能被其他进程以EXCLUSIVE模式锁定。 试图更新一个被锁定的表将导致SQLCODE -110错误,并带有%msg,如下所示: Person' on row with RowID = '10'。 注意,只有当UPDATE语句定位到要更新的第一条记录,然后不能在超时时间内锁定它时,才会出现SQLCODE -110错误。
  • 如果UPDATE指定了一个不存在的字段,则会发出SQLCODE -29。 要列出为指定表定义的所有字段名。 如果字段存在,但没有字段值满足UPDATE命令的WHERE子句,则不影响任何行,并发出SQLCODE 100(数据末尾)。
  • 在极少数情况下,使用%NOLOCKUPDATE找到要更新的行,但随后该行立即被另一个进程删除; 这种情况将导致SQLCODE -109错误:无法找到为UPDATE指定的行。 这个错误的%msg列出了表名和RowID
  • 如果通过视图更新表,则视图不能定义为WITH READ ONLY。 尝试这样做会导致SQLCODE -35错误。 如果视图基于分片表,则不能通过定义WITH CHECK OPTION的视图进行UPDATE。 尝试这样做会导致一个SQLCODE -35,其中%msg INSERT/UPDATE/DELETE不允许查看(sample.myview)基于带有检查选项条件的分片表。

列名和值:

  • 更新不能包含重复的字段名。 尝试指定两个具有相同名称的字段的更新将导致SQLCODE -377错误。
  • 不能更新已被另一个并发进程锁定的字段。 尝试这样做会导致SQLCODE -110错误。 如果执行的更新数量非常大,以致出现<LOCKTABLEFULL>错误,也会发生此SQLCODE错误。
  • 不能更新整数计数器字段。 这些字段是不可修改的。 RowID字段(SQLCODE -107); IDENTITY字段(SQLCODE -107); SERIAL (%Library.Counter)字段(SQLCODE -105); ROWVERSION字段(SQLCODE -138)。 这些字段的值是系统生成的,用户不能修改。 即使用户可以为计数器字段插入一个初始值,用户也不能更新该值。

唯一的例外是将SERIAL (%Library.Counter)字段添加到具有现有数据的表时。 对于这个添加的计数器字段,现有的记录将具有NULL值。 在这种情况下,可以使用UPDATENULL更改为整数值。

  • 不能更新shard键字段。 尝试更新属于分片键一部分的字段会产生SQLCODE -154错误。

  • 如果更新将违反字段的唯一性约束,则不能更新字段值。 试图更新一个字段(或一组字段)的值,使更新违反惟一性约束或主键约束,将导致SQLCODE -120错误。 如果字段具有UNIQUE数据约束,或者如果惟一字段约束已应用于一组字段,则返回此错误。 SQLCODE - 120% 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参数定义的持久化类的属性只能接受VALUELIST中列出的值中的一个作为有效值,或者不提供值(NULL)。 VALUELIST有效值区分大小写。 尝试使用与VALUELIST值不匹配的数据值进行更新会导致SQLCODE -105字段值验证失败错误。

  • 数字以规范形式插入,但可以用前导零和尾随零以及多个前导符号来指定。 然而,在SQL中,两个连续的负号被解析为单行注释指示符。 因此,试图指定具有两个连续前导减号的数字将导致SQLCODE -12错误。

  • 当使用WHERE CURRENT OF子句时,不能使用当前字段值更新字段以生成更新的值。 例如,SET Salary=Salary+100SET Name=UPPER(Name)。 尝试这样做会导致SQLCODE -69错误:SET <field> = <value expression> not allowed with WHERE CURRENT OF <cursor>

  • 如果更新其中一个指定的行会违反外键引用完整性(并且没有指定%NOCHECK), UPDATE将无法更新任何行,并发出SQLCODE -124错误。 如果外键是用NOCHECK关键字定义的,则不适用。

  • 不能用流数据更新非流字段。 这将导致SQLCODE -303错误,如下所述。

赋值

可以通过多种方式为指定的列分配新值。

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

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

  • 使用不带列列表的VALUES关键字,指定一个按列顺序隐式对应于行的列的标量表达式列表。 下面的示例指定了表中的所有列,指定了一个文本值来更新Address列:
VALUES (Name,DOB,'22 Main St. Anytown MA 12345',SSN)

在为隐式列列表赋值时,必须按照DDL中定义的列的顺序为每个可更新字段提供一个值。 (没有指定不可更新的RowID列。) 这些值可以是指定新值的文字,也可以是指定现有值的字段名。 不能指定占位符逗号或省略尾随字段。

  • 使用不带列列表的VALUES关键字,指定下标数组,其中数字下标对应列号,包括在列计数中不可更新的RowID作为列号1。 例如:
VALUES :myarray()

只能使用主机变量在嵌入式SQL中执行此值赋值。 与所有其他值赋值不同,这种用法允您延迟指定哪些列要更新到运行时(通过在运行时填充数组)。 所有其他类型的更新都要求必须在编译时指定要更新的列。 此语法不能用于链接表; 尝试这样做会导致SQLCODE=-155错误。

0
0 149
文章 姚 鑫 · 十一月 19, 2021 2m read

第八十一章 SQL命令 UNLOCK

解锁表

大纲

UNLOCK [TABLE] tablename IN EXCLUSIVE MODE [IMMEDIATE]

UNLOCK [TABLE] tablename IN SHARE MODE [IMMEDIATE]

参数

  • tablename - 要解锁的表的名称。 Tablename必须是已经存在的表。 表名可以是限定的(schema.table),也可以是非限定的(table)。 非限定表名接受默认模式名。 模式搜索路径被忽略。
  • IN EXCLUSIVE MODE / IN SHARE MODE - IN EXCLUSIVE MODE关键字短语释放常规的IRIS锁。 IN SHARE MODE关键字短语在IRIS级别释放共享锁。

描述

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

UNLOCKUNLOCK TABLE是同义词。

UNLOCK命令用来反转LOCK操作。 即使没有锁被持有,UNLOCK命令也会成功完成。 可以使用LOCK多次锁定一个表; 必须显式解锁表,解锁次数为表被显式锁定的次数。

权限

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

Nonexistent Table

如果试图解锁一个不存在的表,则unlock会失败,并出现编译错误,并且会出现SQLCODE=-30: table 'SQLUser. txt . txt消息。 mytable”未找到。

示例

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

ClassMethod Unlock()
{
	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 !,"创建表" 
	} else { 
		w !,"CREATE TABLE error: ",SQLCODE
		q 
	}
}
ClassMethod Unlock1()
{
	n SQLCODE,%msg
	&sql(
		LOCK mytest IN EXCLUSIVE MODE
	) 
	if SQLCODE =0  { 
		w !,"锁表" 
	} elseif SQLCODE = -110 { 
		w !,"表被另一个进程锁定",!,%msg 
	} else { 
		w !,"其他 LOCK error: ",SQLCODE,!,%msg 
	}
	&sql(
		UNLOCK mytest IN EXCLUSIVE MODE
	) 
	if SQLCODE=0 { 
		w !,"解锁表" 
	} else { 
		w !,"其他 UNLOCK error: ",SQLCODE,!,%msg 
	}
}

DHC-APP>d ##class(PHA.TEST.SQLCommand).Unlock1()
 
锁表
解锁表
0
0 140
文章 姚 鑫 · 十一月 18, 2021 9m read

第八十章 SQL命令 UNION

组合两个或多个SELECT语句。

大纲

select-statement {UNION [ALL] [%PARALLEL] select-statement}

select-statement {UNION [ALL]  [%PARALLEL] (query)}

(query) {UNION [ALL]  [%PARALLEL] select-statement}

(query) {UNION [ALL]  [%PARALLEL] (query)}

参数

  • ALL - 可选——关键字字面量。 如果指定,则返回重复的数据值。 如果省略,重复的数据值将被抑制。
  • %PARALLEL - 可选—%PARALLEL关键字。 如果指定,则union的每一边都作为单独的进程并行运行。
  • select-statement - 从数据库中检索数据的SELECT语句。
  • query - 组合一个或多个SELECT语句的查询。

描述

UNION将两个或多个查询组合为一个查询,该查询将数据检索到结果中。 由UNION组合的查询可以是由单个SELECT语句组成的简单查询,也可以是复合查询。

为了在SELECT语句之间实现联合,每个分支中指定的列数必须匹配。 指定具有不同列数的select将导致SQLCODE -9错误。 可以指定一个SELECT中的NULL列与另一个SELECT中的数据列配对,以匹配列的数量。 例如:

SELECT Name,Salary,BirthDate
FROM Sample.Employee
UNION ALL
SELECT Name,NULL,BirthDate
FROM Sample.Person

SQL通过自动计算UNION查询的所有分支并返回优先级最高的数据类型来确定结果列数据类型:VARCHARDOUBLENUMERICBIGINTINTEGERSMALLINTTINYINT。 其他数据类型,如DATE,没有分配优先级。 例如,下面的程序返回数据类型TINYINT,尽管DATE数据类型在其他上下文中具有更高的优先级。

SELECT MyTinyIntField FROM Table1
     UNION ALL
SELECT MyDateField FROM Table2

如果你想返回一个不同于所列数据类型的数据类型,你必须使用显式CAST语句,如下面的例子所示:

SELECT CAST(MyTinyInt AS DATE) FROM Table1
     UNION ALL
SELECT MyDateField FROM Table2

如果联合分支中的列在长度、精度或比例上不同,则给结果列分配最大的值。

结果列名取自联合的第一个分支中的列(或列别名)的名称。 在两个分支中对应的列没有相同名称的情况下,在所有分支中使用相同的列别名来标识结果列可能会很有用。

如果任何UNION分支中的任何列是空的,则结果列元数据报告为空的。

UNION结果中的字符串字段具有相应SELECT字段的排序规则类型,但如果字段排序规则不匹配,则分配精确排序规则。

UNION and UNION ALL

普通的UNION消除了结果中的重复行(所有值都相同)。 UNION ALL在结果中保留重复的行。

不同精度的字段不具有相同的值。 例如,值33(数据类型NUMERIC(9))和33.00(数据类型NUMERIC(9,2))并不被认为是相同的。

具有不同排序规则的字段没有相同的值。 例如,MyStringField%SQLUPPER(MyStringField)并不被认为是相同的,即使这两个值都是大写的。

TOP和ORDER BY子句

UNION语句可以以ORDER BY子句结束,该子句对结果进行排序。 这个ORDER BY适用于整个语句; 它必须是最外层查询的一部分,而不是子查询。 它不必与TOP子句配对。 下面的例子展示了ORDER BY的使用:两个SELECT语句选择数据,数据由UNION组合,然后ORDER BY对结果进行排序:

SELECT Name,Home_Zip FROM Sample.Person
  WHERE Home_Zip %STARTSWITH 9
UNION
SELECT Name,Office_Zip FROM Sample.Employee
  WHERE Office_Zip %STARTSWITH 8
ORDER BY Home_Zip 

ORDER BY中使用与SELECT列表列不对应的列号会导致SQLCODE -5错误。 在ORDER BY中使用与SELECT列表列不对应的列名会导致SQLCODE -6错误。

unionSELECT语句(或两者)也可以包含ORDER BY子句,但它必须与TOP子句配对。 这个ORDER BY用于确定TOP子句选择了哪些行。 下面的示例展示了ORDER BY的使用:两个SELECT语句都使用ORDER BY对它们的行进行排序,这决定了哪些行被选为顶部行。 选定的数据由UNION组合,然后最终的ORDER by对结果进行排序:

SELECT TOP 5 Name,Home_Zip FROM Sample.Person
  WHERE Home_Zip %STARTSWITH 9
  ORDER BY Name
UNION
SELECT TOP 5 Name,Office_Zip FROM Sample.Employee
  WHERE Office_Zip %STARTSWITH 8
  ORDER BY Office_Zip
ORDER BY Home_Zip

TOP可以应用于union中的第一个SELECT,也可以应用于union的结果,这取决于ORDER BY子句的位置:

  • TOP...ORDER BY应用于UNION结果:如果UNION位于FROM子句的子查询中,则TOPORDER BY将应用于UNION的结果。例如:
SELECT TOP 10 Name,Home_Zip
  FROM (SELECT Name,Home_Zip FROM Sample.Person
          WHERE Name %STARTSWITH 'A'
        UNION
        SELECT Name,Home_Zip FROM Sample.Person
          WHERE Home_Zip %STARTSWITH 8)
ORDER BY Home_Zip

image

  • TOP适用于第一个SELECTORDER BY适用于UNION结果。例如:
SELECT TOP 10 Name,Home_Zip 
  FROM Sample.Person
  WHERE Name %STARTSWITH 'A'
UNION
SELECT Name,Home_Zip FROM Sample.Person
  WHERE Home_Zip %STARTSWITH 8
ORDER BY Home_Zip

image

括起圆括号

UNION支持对其中一条SELECT语句或两条SELECT语句或整个UNION语句使用可选的圆括号。可以指定一对或多对括号。以下是括号的所有有效用法:

(SELECT ...) UNION SELECT ...
(SELECT ...) UNION (SELECT ...)
((SELECT ...)) UNION ((SELECT ...))
(SELECT ... UNION SELECT ...)
((SELECT ...) UNION (SELECT ...))

每次使用圆括号都会生成一个单独的缓存查询。

UNION/OR 优化

默认情况下,SQL自动优化会在认为合适的情况下将UNION子查询转换为OR条件。此UNION/OR转换允许EXISTS和其他低级谓词迁移到顶级条件,以便它们可用于 IRIS查询优化器索引。此默认转换在大多数情况下都是可取的。但是,在某些情况下,这种UNION/OR转换会带来很大的开销负担。%NOUNIONOROPT查询优化选项为与FROM子句关联的WHERE子句中的所有条件禁用此自动UNION/OR转换。因此,在复杂查询中,可以对一个子查询禁用自动UNION/OR优化,而在其他子查询中允许它。

如果将包含子查询的条件应用于UNION,则该条件将在每个UNION操作数内应用,而不是在末尾应用。这允许在每个UNION操作数中应用子查询优化。有关子查询优化选项的说明,请参阅FROM子句。在下面的示例中,WHERE子句条件应用于联合中的每个子查询,而不是联合的结果:

SELECT Name,Age FROM 
  (SELECT Name,Age FROM Sample.Person
   UNION SELECT Name,Age FROM Sample.Employee)
WHERE Age IN (SELECT TOP 5 Age FROM Sample.Employee WHERE Age>55 ORDER BY Age)

image

联合所有聚合优化

UNION ALL的SQL自动优化将顶级聚合推入UNION的分支中。 无论是否使用%PARALLEL关键字,都可以显著提高性能,例如:

SELECT COUNT(*) FROM (SELECT item1 FROM table1 UNION ALL SELECT item2 FROM table2) 

优化:

SELECT SUM(y) FROM (SELECT COUNT(*) AS y FROM table1 UNION ALL SELECT COUNT(*) AS y FROM table2) 

此优化适用于所有顶级聚合函数(不仅仅是COUNT),包括具有多个顶级聚合函数的查询。 要应用此优化,外部查询必须是一个“onerow”查询,没有WHEREGROUP BY子句,它不能引用%VID,并且UNION ALL必须是其FROM子句中的唯一流。 聚合不能嵌套,任何使用的聚合函数都不能使用%FOREACH() groupingDISTINCT

并行处理

关键字%PARALLEL支持多处理器系统上的并行和分布式处理。它使IRIS对UNION查询执行并行处理,将每个查询分配给同一台机器上的单独进程。在某些情况下,该过程会将查询发送到另一台机器进行处理。这些进程通过管道进行通信, IRIS创建一个或多个临时文件来保存子查询结果。主进程组合结果行并返回最终结果。比较带和不带%Parallel关键字的Show Plan。要确定当前系统上的处理器数量,请使用%SYSTEM.Util.NumberOfCPU()方法。

通常,生成每一行所花费的精力越多,%Parallel就会变得越有利。

指定%PARALLEL关键字将禁用自动并或优化。

下面的例子展示了%PARALLEL关键字的用法:

SELECT Name FROM Sample.Employee WHERE Name %STARTSWITH 'A'
UNION %PARALLEL
SELECT Name FROM Sample.Person WHERE Name %STARTSWITH 'A'
ORDER BY Name

image

SELECT Name FROM Sample.Employee WHERE Name %STARTSWITH 'A'
UNION ALL %PARALLEL
SELECT Name FROM Sample.Person WHERE Name %STARTSWITH 'A'
ORDER BY Name

image

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

添加%PARALLEL关键字可能不适用于所有UNION查询,并可能导致错误。 以下SQL构造通常不支持UNION %PARALLEL执行:外部连接、相关字段、包含子查询的IN谓词条件或集合谓词。 for SOME谓词支持UNION %PARALLEL,但for SOME %ELEMENT集合谓词不支持UNION %PARALLEL。 要确定UNION查询是否能够成功使用%PARALLEL,请分别测试UNION的每个分支。 通过添加FROM %PARALLEL关键字分别测试每个分支查询。 如果其中一个FROM %PARALLEL查询生成的查询计划没有显示并行化,那么UNION查询将不支持%PARALLEL

UNION ALL和聚合函数

SQL自动优化将UNION ALL聚合函数推入UNION分支子查询。 SQL计算每个子查询的聚合值,然后组合结果返回原始聚合值。 例如:

SELECT COUNT(Name) FROM (SELECT Name FROM Sample.Person
                    UNION ALL SELECT Name FROM Sample.Employee)

image

优化:

SELECT SUM(y) FROM (SELECT COUNT(Name) AS y FROM Sample.Person
                    UNION ALL SELECT COUNT(Name) AS y FROM Sample.Employee)

image

这可以带来实质性的性能改进。 无论是否使用%PARALLEL关键字,都将应用此优化。 该优化应用于多个聚合函数。

这种优化变换只在以下情况下发生:

  • 外部查询FROM子句必须只包含一个UNION ALL语句。
  • 外部查询不能包含WHERE子句或GROUP BY子句。
  • 外部查询不能包含%VID(视图ID)字段。
  • 聚合函数不能包含DISTINCT%FOREACH关键字。
  • 聚合函数不能嵌套。

示例

下面的示例创建一个结果,其中包含两个表中每个Name的一行; 如果在两个表中都找到Name,则创建两行。 当Name是雇员时,它列出办公地点,并将单词“office”连接为州,以及雇员的头衔。 当Name是一个人时,它列出主位置,将单词“home”连接为状态,并将<null>表示标题。 ORDER BY子句对结果进行操作; 合并的行按名称排序:

SELECT Name,Office_State||' office' AS State,Title 
FROM Sample.Employee
UNION
SELECT Name,Home_State||' home',NULL
FROM Sample.Person
ORDER BY Name

image

下面两个示例展示了ALL关键字的效果。 在第一个示例中,UNION只返回惟一的值。 在第二个示例中,UNION ALL返回所有值,包括重复值:

SELECT Name
FROM Sample.Employee
WHERE Name %STARTSWITH 'A'
UNION
SELECT Name
FROM Sample.Person
WHERE Name %STARTSWITH 'A'
ORDER BY Name

image

SELECT Name
FROM Sample.Employee
WHERE Name %STARTSWITH 'A'
UNION ALL
SELECT Name
FROM Sample.Person
WHERE Name %STARTSWITH 'A'
ORDER BY Name

image

0
0 172
文章 姚 鑫 · 十一月 16, 2021 4m read

第七十八章 SQL命令 TUNE TABLE

基于代表性数据调优表。

大纲

TUNE TABLE tablename [ tune_options ]

参数

  • tablename - 要调优的现有表的名称。 表名可以是限定的(schema.table),也可以是非限定的(table)。 非限定表名接受默认模式名。
  • tune_options - 可选—如果指定,一个或多个TUNE TABLE选项,以任意顺序指定,由空格分隔。 这些tune_options不区分大小写。

描述

TUNE TABLE命令根据表中当前的数据对现有表进行调优。 该数据应该代表表完全填充时所期望的数据。

TUNE TABLE根据代表性数据计算和设置表的块大小、选择性和范围大小。 通常,TUNE TABLE设置一个或多个这些值,并清除所有使用这个持久类(表)的缓存查询,以便查询将使用这些新值。 但是,如果TUNE TABLE没有更改这些值(例如,自上次对该表运行TUNE TABLE以来数据没有更改),则缓存查询不会被清除,该表的类定义也不会被标记为需要重新编译。

TUNE TABLE更新SQL表定义(因此需要特权来更改表定义)。 通常,TUNE TABLE还会更新相应的持久类定义。 这允许查询优化器使用调优后的值,而不需要进行类编译。 但是,如果部署了类,那么TUNE TABLE只更新SQL表定义; 查询优化器间接使用表定义中的调优值。

如果TUNE TABLE成功,它将SQLCODE设置为0。 如果指定的表名不存在,TUNE TABLE将发出SQLCODE -30错误。

权限

TUNE TABLE命令是一个特权操作。 用户必须具有%ALTER_TABLE管理权限才能执行TUNE TABLE。 如果不这样做,将导致%msgSQLCODE -99错误,User 'name' does not have %ALTER_TABLE privileges。 如果拥有适当的权限,可以使用GRANT命令为用户或角色分配%ALTER_TABLE权限。 管理权限是特定于名称空间的。

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

调表选项

  • %KEEP_UP_TO_DATE:如果未指定(默认值),则未设置修改后的类定义上的最新标志。 这表明类定义已经过期,并且被标记为需要重新编译。 如果指定,则类定义将保持标记为最新的。 这是在活动系统上更改统计信息时的首选选项,因为它降低了重新编译表类定义的可能性。
  • %CLEAR_VALUES:如果指定了现有的SELECTIVITY, EXTENTSIZE等值将从类和表定义中清除。 不指定此选项将提供默认的调优表行为。
  • %SAMPLE_PERCENT百分比:指定用于对TuneTable实用程序的数据进行抽样的表行的百分比。 这个百分比可以指定为。####%; 例如,在采样数据时,.1212%将导致TuneTable使用表中12%的行。 指定大于0且小于等于100%的百分比值; 超出此范围的值将发出SQLCODE -1错误。 这个值通常不需要指定。 仅当字段的潜在离群值不是均匀分布在整个表的行中时指定此值。 注意,对于任何区段大小< 1000行的表,无论%SAMPLE_PERCENT值如何,整个区段都将由TuneTable使用。
  • %RECOMPILE_CQ:如果指定了,TuneTable将使用新的调优表统计信息重新编译缓存的查询类,而不仅仅是清除调优表的缓存查询。 不指定此选项将提供默认的TuneTable行为。

如果指定的tune_options值不存在,TUNE TABLE将发出SQLCODE -25错误。 如果两次指定相同的tune_options值,TUNE TABLE将发出SQLCODE -326错误。

缓存查询

执行TUNE TABLE将创建一个缓存查询。 显示“Show Plan”表示没有创建查询计划。 未创建SQL语句。 缓存的查询对名称空间是通用的; 在具体的表格中没有列出。 可以使用缓存查询重新运行相同的TUNE TABLE语句。

执行TUNE TABLE将清除指定表的所有现有缓存查询,包括上次执行TUNE TABLE的缓存查询。 可以选择让TUNE TABLE使用新的TUNE TABLE值重新编译所有这些缓存的查询。

如果运行TUNE TABLE没有更改任何TUNE TABLE值,则不会清除缓存的查询。

运行调优表的其他方法

有两个其他接口运行Tune Table:

  • 通过使用Management Portal SQL接口Actions下拉列表,您可以在单个表或模式中的所有表上运行Tune Table
  • 为单个表或当前名称空间中的所有表调用$SYSTEM.SQL.Stats.Table.GatherTableStats()方法。

示例

下面的动态SQL示例调优一个表:

ClassMethod TuneTable()
{
	try {
		s mysql = "TUNE TABLE Sample.MyTest %KEEP_UP_TO_DATE"
		s tStatement = ##class(%SQL.Statement).%New()
		s qStatus = tStatement.%Prepare(mysql)
		if qStatus '= 1 {
			w "%Prepare failed:" 
			d $System.Status.DisplayError(qStatus) 
			q
		}
		s rset = tStatement.%Execute()
		if rset.%SQLCODE = 0 { 
			w !,"Executed Tune Table",! 
		} else { 
			s badSQL=##class(%Exception.SQL).%New(,rset.%SQLCODE,,rset.%Message)
			throw badSQL }
		ret
	}
	catch exp { 
		w "In the CATCH block",!
		if 1 = exp.%IsA("%Exception.SQL") {
			w "SQLCODE: ",exp.Code,!
			w "Message: ",exp.Data,! 
		} else { 
			w "Not an SQL exception",! 
		}
		ret
	}
}
0
0 109
文章 姚 鑫 · 十一月 15, 2021 6m read

第七十七章 SQL命令 TRUNCATE TABLE

从表中删除所有数据并重置计数器。

大纲

TRUNCATE TABLE [restriction] tablename

参数

  • restriction - 可选—以下限制关键字中的一个或多个,用空格隔开:%NOCHECK%NOLOCK
  • tablename - 要从中删除所有行的表。 还可以指定一个可更新视图,通过该视图可以删除表中的所有行。 表名(或视图名)可以是限定的(schema.table)或非限定的(table)。 使用模式搜索路径(如果提供的话)或默认模式名将非限定名称匹配到其模式。

描述

TRUNCATE TABLE命令从表中删除所有行,并重置所有表计数器。 可以直接截断表,也可以通过视图截断表。 通过视图截断表会受到删除要求和限制,如CREATE view中所述。

TRUNCATE TABLE重置用于生成RowID字段、IDENTITY字段和SERIAL (%Library.Counter)字段连续整数值的内部计数器。 IRIS为插入到TRUNCATE表后的表中的第一行中的这些字段赋值为1。 对表的所有行执行DELETE操作不会重置这些内部计数器。

TRUNCATE TABLE重置用于在数据插入到流字段时生成流字段OID值的内部计数器。 对表的所有行执行DELETE操作不会重置此内部计数器。

TRUNCATE TABLE总是将%ROWCOUNT本地变量设置为-1; 它没有将%ROWCOUNT设置为删除的行数。

TRUNCATE TABLE不会重置ROWVERSION计数器。

TRUNCATE TABLE禁止提取基表触发器,否则在DELETE处理期间提取基表触发器。 因为TRUNCATE TABLE执行的是带有%NOTRIGGER行为的删除,用户必须被授予%NOTRIGGER权限(使用GRANT语句)才能运行TRUNCATE TABLE。 TRUNCATE TABLE的这方面在功能上是相同的:

DELETE %NOTRIGGER FROM tablename

注意:DELETE命令也可以用来删除表中的所有行。 DELETE提供了比TRUNCATE TABLE更多的功能,包括返回%ROWCOUNT中已删除的行数。 DELETE不会重置内部计数器。

TRUNCATE TABLE为从其他数据库软件迁移代码提供了兼容性。

截断一个表:

  • 表必须存在于当前(或指定)命名空间中。 如果无法找到指定的表,IRIS将发出SQLCODE -30错误。

  • 即使没有定义触发器,用户也必须具有%NOTRIGGER管理权限。 如果没有此权限,则会出现%msg User does not have %NOTRIGGER权限的SQLCODE -99错误。

  • 用户必须对表具有DELETE权限。 如果没有此权限,将导致带有%msgSQLCODE -99。 可以通过调用%CHECKPRIV命令来确定当前用户是否具有DELETE权限。 可以通过调用$SYSTEM.SQL.Security.CheckPrivilege()方法来确定指定的用户是否具有DELETE权限。

  • 该表不能定义为READONLY。 试图编译引用只读表的TRUNCATE TABLE会导致SQLCODE -115错误。 注意,这个错误现在是在编译时发出的,而不是只在执行时发生。

  • 如果通过视图删除,视图必须是可更新的; 不能定义为WITH READ ONLY。 尝试这样做会导致SQLCODE -35错误。

  • 所有的行必须是可删除的。 默认情况下,如果不能删除一行或多行,则TRUNCATE TABLE操作失败,不会删除任何行。

如果表被其他进程以EXCLUSIVE模式或SHARE模式锁定,则TRUNCATE TABLE失败。 试图在一个锁定的表上执行TRUNCATE TABLE操作将导致SQLCODE -110错误,并带有%msg,如下所示: MyStuff' on row with RowID = '3'(其中指定的RowID是表中的第一行)。

如果删除一行会违反外键引用完整性,那么TRUNCATE TABLE将失败。 未删除任何行,因此TRUNCATE TABLE发出SQLCODE -124错误。 这个默认行为是可以修改的,如下所述。

Fast Truncate

如果可能,SQL优化器将执行高效的Fast Truncate表操作。 Fast Truncate操作删除表的范围,而不是单独删除每条记录。 在可能的情况下,快速截断将自动应用。 当无法实现快速截断时,将执行标准的Truncate TABLE操作。

注意:如果没有删除行,或者使用Fast TRUNCATE删除行,则TRUNCATE TABLE不会初始化或设置%ROWID。 因此,应该避免在TRUNCATE表之后使用%ROWID值。

Fast Truncate 限制

快速截断可以应用于标准表或分片表。

不能应用快速截断:

  • 如果用户无法获得表级锁(除非指定了%NOLOCK)。
  • 如果表是外键约束的目标。
  • 如果表包含带有指定LOCATION参数的流字段。 当所有流字段没有指定可选的LOCATION参数时,可以应用快速截断。

Atomicity

TRUNCATE TABLE不会在自动启动的事务中发生,因此不提供日志记录或回滚选项。

如果需要日志记录和回滚TRUNCATE TABLE选项,则必须显式指定START TRANSACTION,并以显式COMMITrollback结束。

这与SET TRANSACTION %COMMITMODE= NONE或0(没有自动事务)相同——调用TRUNCATE TABLE时不会启动任何事务。 失败的TRUNCATE TABLE操作可能会使数据库处于不一致的状态,一些行被删除,一些行没有被删除。 要在此模式中提供事务支持,必须使用START transaction来启动事务,并使用COMMITROLLBACK来结束事务。

分片表的TRUNCATE TABLE总是使用SET TRANSACTION %COMMITMODE NONE执行,即使用户显式地设置了SET TRANSACTION %COMMITMODE EXPLICIT

限制参数

要使用constraint参数,必须拥有当前名称空间对应的admin-privilege

指定约束参数限制处理如下:

  • %NOCHECK - 禁止对引用被删除行的外键进行引用完整性检查。
  • %NOLOCK - 抑制被删除行的行锁定。 这应该只在单个用户/进程更新数据库时使用。 如果不指定%NOLOCK,则快速截断将尝试获取表级锁。 如果TRUNCATE TABLE不能获得表级锁,它将执行一个标准的截断表,在表的每一行上获取行级锁。

可以以任何顺序指定多个限制参数。 多个参数由空格分隔。

如果在删除父记录时指定了约束参数,则在删除相应的子记录时将应用相同的约束参数。

TRUNCATE TABLE总是使用隐式的%NOTRIGGER行为执行删除操作,并且需要相应的admin-privilege

参照完整性

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

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

事务锁

IRIS对TRUNCATE TABLE操作执行标准锁定。 唯一的字段值在当前事务期间被锁定。

默认的锁阈值是每个表1000个锁。 这意味着,如果在事务期间从表中删除超过1000个惟一字段值,就会达到锁阈值,IRIS会自动将锁级别从惟一字段值锁提升到表锁。 这允许在事务期间进行大规模删除,而不会溢出锁表。

可以使用$SYSTEM.SQL.Util.GetOption(“LockThreshold”)方法确定当前系统范围的锁阈值。 这个系统范围的锁阈值是可配置的:

  • 使用$SYSTEM.SQL.Util.SetOption("LockThreshold")方法。
  • 通过管理门户。 进入系统管理,配置,SQL和对象设置,SQL。 查看和编辑“锁定升级阈值”的当前设置。

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

Imported SQL代码

ImportDDL("IRIS")Run()方法不支持TRUNCATE TABLE命令。 在这些方法导入的SQL代码文件中发现的TRUNCATE TABLE命令将被忽略。 这些导入方法确实支持DELETE命令。

示例

下面两个动态SQL示例比较了DELETETRUNCATE表。 每个示例都创建一个表,向表中插入行,删除表中的所有行,然后向现在为空的表中插入一行。

第一个示例使用DELETE删除表中的所有记录。 注意,DELETE不会重置RowID计数器:

ClassMethod TruncateTable()
{
	s tcreate = "CREATE TABLE SQLUser.MyStudents1 (StudentName VARCHAR(32),StudentDOB DATE)"
	s tinsert = "INSERT INTO SQLUser.MyStudents1 (StudentName,StudentDOB) "_
				"SELECT Name,DOB FROM Sample.Person WHERE Age <= '21'"
	s tinsert1 = "INSERT INTO SQLUser.MyStudents1 (StudentName,StudentDOB) VALUES ('Bob Jones',60123)"
	s tdelete = "DELETE SQLUser.MyStudents1"
	s tStatement = ##class(%SQL.Statement).%New()
	s qStatus = tStatement.%Prepare(tcreate)
	if qStatus '= 1 {
		w "%Prepare failed:" 
		d $System.Status.DisplayError(qStatus) 
		q
	}
	s rset = tStatement.%Execute()
	w rset.%StatementTypeName,!

	n %ROWCOUNT,%ROWID
	s qStatus = tStatement.%Prepare(tinsert)
	if qStatus '= 1 {
		w "%Prepare failed:" 
		d $System.Status.DisplayError(qStatus) 
		q
	}
	s rset = tStatement.%Execute()
	w rset.%StatementTypeName," rowcount ",rset.%ROWCOUNT,!

	s qStatus = tStatement.%Prepare(tdelete)
	if qStatus '= 1 {
		w "%Prepare failed:" 
		d $System.Status.DisplayError(qStatus) 
		q
	}
	s rset = tStatement.%Execute()
	w rset.%StatementTypeName," rowcount ",rset.%ROWCOUNT,!

	s qStatus = tStatement.%Prepare(tinsert1)
	if qStatus '= 1 {
		w "%Prepare failed:" 
		d $System.Status.DisplayError(qStatus) 
		q
	}
	s rset = tStatement.%Execute()
	w rset.%StatementTypeName," rowcount ",rset.%ROWCOUNT," RowID ",rset.%ROWID,!
	&sql(DROP TABLE SQLUser.MyStudents1)
}
DHC-APP>d ##class(PHA.TEST.SQLCommand).TruncateTable()
CREATE TABLE
INSERT rowcount 41
DELETE rowcount 41
INSERT rowcount 1 RowID 42

第二个示例使用TRUNCATE TABLE删除表中的所有记录。 注意,%StatementTypeNameTRUNCATE表返回“DELETE”。 注意,TRUNCATE TABLE会重置RowID计数器:

ClassMethod TruncateTable1()
{
	s tcreate = "CREATE TABLE SQLUser.MyStudents2 (StudentName VARCHAR(32),StudentDOB DATE)"
	s tinsert = "INSERT INTO SQLUser.MyStudents2 (StudentName,StudentDOB) "_
	"SELECT Name,DOB FROM Sample.Person WHERE Age <= '21'"
	s tinsert1 = "INSERT INTO SQLUser.MyStudents2 (StudentName,StudentDOB) VALUES ('Bob Jones',60123)"
	s ttrunc = "TRUNCATE TABLE SQLUser.MyStudents2"
	s tStatement = ##class(%SQL.Statement).%New()
	s qStatus = tStatement.%Prepare(tcreate)
	if qStatus '= 1 {
		w "%Prepare failed:" 
		d $System.Status.DisplayError(qStatus) 
		q
	}
	s rset = tStatement.%Execute()
	w rset.%StatementTypeName,!

	n %ROWCOUNT,%ROWID
	s qStatus = tStatement.%Prepare(tinsert)
	if qStatus '= 1 {
		w "%Prepare failed:" 
		d $System.Status.DisplayError(qStatus) 
		q
	}
	s rset = tStatement.%Execute()
	w rset.%StatementTypeName," rowcount ",rset.%ROWCOUNT,!

	s qStatus = tStatement.%Prepare(ttrunc)
	if qStatus '= 1 {
		w "%Prepare failed:" 
		d $System.Status.DisplayError(qStatus) 
		q
	}
	s rset = tStatement.%Execute()
	w rset.%StatementTypeName," (TRUNCATE TABLE) rowcount ",rset.%ROWCOUNT,!

	s qStatus = tStatement.%Prepare(tinsert1)
	if qStatus '= 1 {
		w "%Prepare failed:" 
		d $System.Status.DisplayError(qStatus) 
		q
	}
	s rset = tStatement.%Execute()
	w rset.%StatementTypeName," rowcount ",rset.%ROWCOUNT," RowID ",rset.%ROWID,!
	//&sql(DROP TABLE SQLUser.MyStudents2)
}
0
0 109
文章 姚 鑫 · 十一月 14, 2021 8m read

第七十六章 SQL命令 TOP

指定返回多少行的SELECT子句。

大纲

SELECT  [DISTINCT clause] 
  [TOP {[((]int[))] | ALL}]
  select-item{,select-item}

参数

  • int - 限制返回到指定整数的行数。 int参数可以是一个正整数、一个动态SQL输入参数(?)或一个解析为正整数的嵌入式SQL主机变量(:var)。在动态SQL中,int值可以选择用单括号或双括号括起来(双括号是首选语法); 这些括号禁止在相应的缓存查询中对int值进行文字替换。
  • ALL - TOP ALL仅在子查询或CREATE VIEW语句中有意义。 它用于在这些情况下支持使用ORDER BY子句,满足在子查询或CREATE VIEW中使用的查询中ORDER BY子句必须与TOP子句配对的要求。 TOP ALL不限制返回的行数。

描述

可选的TOP子句出现在SELECT关键字和可选的DISTINCT子句之后,以及第一个选择项之前。

TOP关键字用于动态SQL和基于指针的嵌入式SQL。 在非游标嵌入式SQL中,TOP关键字的唯一有意义的用法是TOP 0。 任何其他TOP int(其中int是任何非零整数)都是有效的,但没有意义,因为非游标嵌入式SQL中的SELECT总是最多返回一行数据。

SELECT语句的TOP子句将返回的行数限制为int中指定的行数。 如果没有指定TOP子句,则默认显示满足SELECT条件的所有行。 如果指定了TOP子句,则显示的行数或行数要么为int,要么为满足查询谓词要求的所有行(以较小的为准)。 如果指定ALL, SELECT将返回表中满足查询谓词要求的所有行。

如果查询中没有指定ORDER BY子句,那么将哪些记录作为“top”行返回是不可预测的。 如果指定了ORDER BY子句,则顶部的行与该子句中指定的顺序一致。

DISTINCT子句(如果指定)应用于TOP之前,指定返回(最多)int个唯一值。

当所有行已交付时,TOP短路。 因此,如果选择直到获得SQLCODE 100,则设置SQLCODE 100FETCH是即时的。

当通过视图或FROM子句子查询访问数据时,可以使用%vid视图ID而不是(或附加)TOP子句来限制返回的行数。

TOP int值

int数值可以是整数、数字字符串、动态SQL输入参数(?)或解析为整数值的输入主机变量(:var)。

int值指定要返回的行数。 允许的值是0和正数。 不能将int值指定为算术表达式、字段名、子查询列别名、标量函数或聚合函数。 小数或数字字符串被解析为其整数值。 0(0)是一个有效的整型值。 TOP 0执行查询,但不返回数据。

TOP ALL必须在查询中指定为关键字。 不能将ALL指定为? 输入参数或:var主机变量值。 查询解析器将以这种方式提供的字符串“ALL”解释为值为0的数字字符串。

注意,TOP参数元数据返回为xDBC数据类型12 (VARCHAR),而不是4 (INTEGER),因为可以将TOP int指定为数字字符串或整数。

TOP和缓存查询

int值可以用括号指定,也可以不使用括号指定。 这些括号影响如何缓存动态SQL查询(非游标嵌入式SQL查询不缓存)。 没有括号的整型值被转换为 a? 缓存查询中的参数变量。 这意味着重复使用不同的TOPint值调用相同的查询将调用相同的缓存查询,而不是每次都准备和优化查询。

括起来的圆括号禁止文字替换。 例如,TOP(7))。 当int被括在括号中时,缓存的查询保留特定的int值。 使用相同的TOP int值重新调用查询将使用缓存的查询; 使用不同的TOP int值调用查询将导致SQL准备、优化和缓存这个新版本的查询。

TOP ALL不是缓存为 a? 参数变量。 ALL被解析为关键字,而不是字面量。 因此,使用TOP 7TOP ALL的相同查询将生成两个不同的缓存查询。

TOP和ORDER BY

TOP通常用于带ORDER BY子句的SELECT中。 注意,默认升序ORDER BY排序顺序认为NULL是最低值(" top "),后面跟着空字符串(")。

当指定ORDER BY子句时,在子查询SELECTCREATE VIEW SELECT中需要TOP。 在这些情况下,可以指定TOP int(以限制返回的行数)或TOP ALL

TOP ALL只在子查询或CREATE VIEW语句中使用。 它用于在这些情况下支持使用ORDER BY子句,以满足在子查询或CREATE VIEW查询中ORDER BY子句必须与TOP子句配对的要求。 TOP ALL不限制返回的行数。 前所有… ORDER BY不会改变默认的SELECT优化。 ALL关键字不能用括号括起来。

TOP 优化

默认情况下,SELECT优化以最快的时间返回所有数据。 同时添加TOP int子句和ORDER BY子句可以优化以最快的时间返回第一行。 (注意,这两个子句都需要更改优化。) 可以使用%SYS.PToolsStatsSQLTotalTimeToFirstRow属性返回返回第一行所需的时间。

以下是特殊情况下的优化:

  • 可能希望使用TOPORDER BY优化策略,而不限制返回的行数; 例如,如您正在返回以页面单元显示的数据。 在这种情况下,能希望发出一个TOP子句,该子句的int值大于行总数。
  • 可能希望限制返回的行数并指定它们的顺序,而不改变默认的SELECT优化。 在这种情况下,指定TOP子句、ORDER BY子句和%NOTOPOPT关键字,以保留返回所有数据优化所需的最快时间。

TOP与聚合和函数

聚合函数或标量函数只能返回单个值。 如果查询选择项列表中只包含聚合和函数,则TOP子句的应用如下:

  • 如果选择项列表包含聚合函数,例如COUNT(*)AVG(Age),且不包含任何字段引用,则返回的行数不超过一行,无论TOP int值或ORDER BY子句是否存在。 这些子句被验证,但被忽略。 以下例子显示了这一点:
SELECT TOP 5 AVG(Age),CURRENT_TIMESTAMP(3) FROM Sample.Person
  /* returns 1 row */
SELECT TOP 1 AVG(Age),CURRENT_TIMESTAMP(3) FROM Sample.Person ORDER BY Age
  /* returns 1 row */
  • 如果选择项列表包含一个或多个标量函数、表达式、文字(如%TABLENAME)、子查询或宿主变量,并且不包含任何字段引用或聚合,则应用TOP子句。 下面的例子显示了这一点:
SELECT TOP 5 ROUND(678.987,2),CURRENT_TIMESTAMP(3) FROM Sample.Person
  /* returns 5 identical rows */

返回的实际行数取决于表中的行数,即使在没有引用表字段时也是如此。 例如:

SELECT TOP 300 CURRENT_TIMESTAMP(3) FROM Sample.Person
  /* returns either the number of rows in Sample.Person
     or 300 rows, whichever is smaller */

当查询受到谓词条件的限制时,即使在选择项列表中没有引用表字段,返回的行数也会受到该条件的限制。 例如:

SELECT TOP 300 CURRENT_TIMESTAMP(3) FROM Sample.Person WHERE Home_State = 'MA'
  /* returns either the number of rows in Sample.Person
     where Home_State = 'MA'
     or 300 rows, whichever is smaller */
  • 如果SELECT语句不包含FROM子句,则不管TOP值如何,最多返回一行。 例如:
SELECT TOP 5 ROUND(678.987,2),CURRENT_TIMESTAMP(3)
  /* returns 1 row */
  • DISTINCT子句进一步限制了TOP子句。 如果不同的值比TOP值少,则只返回具有不同值的行。 当仅引用标量函数时,只返回一行。 例如:
SELECT DISTINCT TOP 15 CURRENT_TIMESTAMP(3) FROM Sample.Person
  /* returns 1 row */
  • TOP 0总是不返回任何行,不管选择项列表的内容是什么,也不管SELECT语句是包含FROM子句还是DISTINCT子句。

在非游标嵌入式SQL中,TOP 0查询不返回任何行,并设置SQLCODE=100;带有TOP 1(或任何其他TOP int值)的非游标嵌入式SQL查询返回一行并设置SQLCODE=0。 在基于指针的嵌入式SQL中,获取循环的完成总是设置SQLCODE=100,而不管TOP int值如何。

示例

下面的查询返回从Sample检索到的前20行。 人按他们在数据库中的存储顺序排列。 这个记录顺序通常是不可预测的。

SELECT TOP 20 Home_State,Name FROM Sample.Person

下面的查询返回从Sample检索到的前20个不同的Home_State值。 人在升序排列顺序。

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

下面的查询返回前40个不同的FavoriteColor值。 “top”行反映了Sample中所有行的ORDER BY子句排序。 按降序(DESC)排序的人。 使用降序排序序列而不是默认的升序排序序列,因为众所周知FavoriteColors字段有null,它将出现在升序排序序列的顶部。

SELECT DISTINCT TOP 40 FavoriteColors FROM Sample.Person 
      ORDER BY FavoriteColors DESC

还要注意,在前面的示例中,由于FavoriteColors是一个列表字段,排序序列包括元素长度字节。 因此,六个字母的元素(YELLOW, PURPLE, ORANGE)被放在一起整理,在五个字母的元素(WHITE, GREEN等)之前列出。

动态SQL可以指定int值作为输入参数(用“?”表示)。 在下面的例子中,TOP ? 输入参数被%Execute方法设置为10:

ClassMethod Top()
{
	s myquery = "SELECT TOP ? Name,Age FROM Sample.Person"
	s tStatement = ##class(%SQL.Statement).%New()
	s qStatus = tStatement.%Prepare(myquery)
	s rset = tStatement.%Execute(10)
	d rset.%Display()
}

DHC-APP>d ##class(PHA.TEST.SQLCommand).Top()
Name    Age
yaoxin  31
xiaoli
姚鑫    7
姚鑫    7
...
10 Rows(s) Affected

以下基于游标的嵌入式SQL示例执行相同的操作:

ClassMethod Top1()
{
	SET topnum = 10
	&sql(
		DECLARE pCursorT CURSOR FOR
		SELECT TOP :topnum Name,Age INTO :name,:years FROM Sample.Person
	)
	&sql(OPEN pCursorT)
	q:(SQLCODE'=0)
	for { 
		&sql(FETCH pCursorT)
		q:SQLCODE
		w "Name=",name," Age=",years,!
	}
	&sql(CLOSE pCursorT)
}

DHC-APP>d ##class(PHA.TEST.SQLCommand).Top1()
Name=yaoxin Age=31
Name=xiaoli Age=
Name=姚鑫 Age=7
Name=姚鑫 Age=7
Name=姚鑫 Age=43
Name=姚鑫 Age=
0
0 188
文章 姚 鑫 · 十一月 13, 2021 11m read

第七十五章 SQL命令 START TRANSACTION

开始一个事务。

大纲

START TRANSACTION [%COMMITMODE commitmode]

START TRANSACTION [transactionmodes]

参数

  • commitmode - 可选-指定在当前进程中如何向数据库提交将来的事务。 取值包括EXPLICITIMPLICITNONE。 默认是维护现有的提交模式; 进程的初始提交模式默认值是IMPLICIT
  • transactionmodes - 可选—指定事务的隔离模式和访问模式。 可以将隔离模式、访问模式或这两种模式的值指定为逗号分隔的列表。隔离模式的有效值为ISOLATION LEVEL READ COMMITTED, ISOLATION LEVEL READ UNCOMMITTED, ISOLATION LEVEL READ VERIFIED. 默认 ISOLATION LEVEL READ UNCOMMITTED.访问模式的有效值为“READ ONLY”“READ WRITE”。 注意,只有隔离级别READ COMMITTED与读写模式READ WRITE兼容。

描述

START TRANSACTION语句启动一个事务。 START TRANSACTION立即启动一个事务,而不管当前的提交模式设置如何。 无论当前的提交模式设置如何,以START transaction开始的事务必须通过发出显式COMMITROLLBACK来结束。

START TRANSACTION是可选的。

  • 如果流程只查询数据(SELECT语句),可以使用SET TRANSACTION来建立隔离级别。 不需要START TRANSACTION
  • 如果进程正在修改数据,那么是否需要通过发出START transaction来显式地开始SQL事务,这取决于进程的当前提交模式设置(也称为AutoCommit设置)。 如果当前进程的提交模式是隐式的或显式的,则发出START TRANSACTION是可选的。 如果忽略START TRANSACTION,则在调用修改数据操作(DELETEUPDATEINSERT)时,系统会自动启动一个事务。 如果指定START TRANSACTION,则会立即启动一个事务,并且必须通过显式COMMITROLLBACK来结束该事务。

START TRANSACTION启动一个事务时,它将$TLEVEL事务级别计数器从0增加到1,表明事务正在进行。 还可以通过检查%INTRANSACTION语句设置的SQLCODE来确定事务是否在进行中。 当事务正在进行时发出START TRANSACTION$TLEVEL%INTRANSACTION没有影响。

SQL不支持嵌套事务。 当事务已经在进行时发出START TRANSACTION不会启动另一个事务,也不会返回错误代码。 SQL支持保存点,允许事务的部分回滚。

当发出SAVEPOINT语句时,如果事务没有在进行中,则SAVEPOINT将启动一个事务。 但是,不推荐使用这种方式启动事务。

如果事务操作未能成功完成,则会发出SQLCODE -400

设置参数

可以选择使用START TRANSACTION来设置参数。 设置的参数立即生效。 但是,无论如何设置commitmode参数,任何以START transaction启动的事务都必须以显式COMMITROLLBACK结束。 参数设置在当前进程期间继续有效,直到显式重置为止。 它们不会在事务结束时自动重置为默认值。

单个START TRANSACTION语句可用于设置提交模式参数或事务模式参数,但不能同时设置两者。 要设置两者,可以发出set TRANSACTIONSTART TRANSACTION,或者两条START TRANSACTION语句。 只有第一个START TRANSACTION才会启动一个事务。

在发出START TRANSACTION之后,可以在事务期间通过发出另一个START TRANSACTIONSET TRANSACTION或方法调用来更改这些参数设置。 更改commitmode参数并不会删除使用显式COMMITROLLBACK结束当前事务的需求。

可以使用SET TRANSACTION语句来设置提交模式或事务模式参数,而不需要启动事务。 还可以使用方法调用在事务外部或事务内部设置这些参数。

%COMMITMODE

%COMMITMODE关键字允许为当前流程指定自动事务启动和承诺行为。 START TRANSACTION %COMMITMODE更改当前流程中所有未来事务的提交模式设置。 它不会影响由START transaction语句发起的事务。 无论当前或设置提交模式,START TRANSACTION都会立即启动一个事务,并且这个事务必须通过发出显式的commitROLLBACK来结束。

可用的%COMMITMODE选项有:

  • IMPLICIT隐式:启用自动事务承诺(初始流程默认值)。 当程序发出数据库修改操作(INSERTUPDATEDELETE)时,SQL自动启动一个事务。 事务将继续进行,直到操作成功完成并SQL自动提交更改,或者操作无法在所有行上成功完成并SQL自动回滚整个操作。 每个数据库操作(INSERTUPDATEDELETE)构成一个单独的事务。 成功完成数据库操作将自动清除回滚日志、释放锁并减少$TLEVEL。 不需要COMMIT语句。
  • EXPLICIT:关闭自动事务承诺。 当程序发出第一个数据库修改操作(INSERTUPDATEDELETE)时,SQL自动启动一个事务。 该交易将继续进行,直到明确达成协议。 成功完成后,发出COMMIT语句。 如果数据库修改操作失败,则发出ROLLBACK语句将数据库恢复到事务开始之前的位置。 在EXPLICIT模式下,多个数据库修改操作可以组成一个事务。
  • NONE:没有自动事务处理。 除非由START TRANSACTION显式调用,否则不会初始化事务。 必须通过发出COMMITROLLBACK语句显式地结束所有事务。 因此,事务中是否包含数据库操作以及事务中数据库操作的数量都是用户定义的。

TRUNCATE TABLE不会在自动启动的事务中发生。 如果需要对TRUNCATE TABLE进行日志记录和回滚,则必须显式指定START TRANSACTION,并以显式COMMITrollback结束。

可以使用SetOption()方法在ObjectScript中设置%COMMITMODE,如下set status=$SYSTEM.SQL.Util.SetOption("AutoCommit",intval,.oldval)。 可用的方法值为0 (NONE)、1 (IMPLICIT)和2 (EXPLICIT)。

注意:分片表总是处于No AutoCommit模式(SetOption("AutoCommit",0)),这意味着所有对分片表的插入、更新和删除都是在事务范围之外执行的。

隔离级别

可以为发出查询的进程指定“隔离级别”。 “隔离级别”选项允指定正在进行的更改是否可用于查询的读访问。 如果另一个并发进程正在执行对表的插入或更新,并且对表的更改在事务中,那么这些更改正在进行中,并且可能会回滚。 通过为正在查询该表的流程设置ISOLATION LEVEL,可以指定是否希望在查询结果中包含或排除这些正在进行的更改。

  • READ UNCOMMITTED表示所有更改都可以立即用于查询访问。 这包括随后可能被回滚的更改。 READ UNCOMMITTED确保查询将在不等待并发插入或更新进程的情况下返回结果,并且不会因为锁定超时错误而失败。 然而,READ UNCOMMITTED的结果可能包括未提交的值; 这些值在内部可能不一致,因为插入或更新操作只部分完成,这些值可能随后被回滚。 如果查询进程不在显式事务中,或者事务没有指定隔离级别,则READ UNCOMMITTED是默认值。 READ UNCOMMITTEDREAD - WRITE访问不兼容; 试图在同一语句中同时指定这两个变量会导致SQLCODE -92错误。
  • READ VERIFIED声明来自其他事务的未提交数据立即可用,并且不执行锁操作。 这包括随后可能被回滚的更改。 然而,与READ UNCOMMITTED不同的是,READ VERIFIED事务将重新检查任何可能因未提交或新提交的数据而失效的条件,这将导致不满足查询条件的输出。 由于这种条件重新检查,READ VERIFIEDREAD UNCOMMITTED更准确,但效率更低,应该只在可能发生对条件检查的数据的并发更新时使用。 READ VERIFIEDREAD - WRITE访问不兼容; 试图在同一语句中同时指定这两个变量会导致SQLCODE -92错误。
  • READ COMMITTED表示只有那些已经提交的更改可以用于查询访问。 这确保了在数据库上以一致的状态执行查询,而不是在进行一组更改时执行,这组更改随后可能会回滚。 如果请求的数据已被更改,但更改尚未提交(或回滚),则查询将等待事务完成。 如果在等待该数据可用时发生锁定超时,则会发出SQLCODE -114错误。

READ UNCOMMITTED还是READ VERIFIED?

下面的例子演示了READ UNCOMMITTEDREAD VERIFIED之间的区别:

SELECT Name,SSN FROM Sample.Person WHERE Name >= 'M' 

查询优化器可能首先选择从Name索引中收集所有RowID包含的符合>= 'M'条件的Name。 收集之后,每次访问一个RowID,以检索NameSSN字段用于输出。 并发运行的更新事务可以将一个RowID 72PersonName字段从“Smith”更改为“Abel”,该字段位于查询的rowwid集合和它对表的逐行访问之间。 在本例中,索引中的RowID集合将包含不再符合Name >= 'M'条件的行的RowID

READ UNCOMMITTED查询处理假设Name >= 'M'条件已经被索引满足,并且将输出从索引中收集的每个RowID在表中出现的任何Name。 因此,在本例中,它将输出一个名称为'Abel'的行,该行不满足条件。

READ VERIFIED查询处理注意到,它正在从表中为output (Name)检索一个字段,该字段参与了之前应该由索引满足的条件,然后重新检查条件,以防在检查索引之后字段值发生变化。 在重新检查时,它注意到该行不再满足条件,并将其从输出中删除。 只有输出所需的值才会重新检查其条件:在本例中,SELECT SSN FROM Person WHERE Name >= 'M'将输出RowID为72的行。

READ COMMITTED异常

ISOLATION LEVEL read committed生效时,可以通过设置ISOLATION LEVEL read committed或SetOption()方法,如下SET status=$SYSTEM.SQL.Util.SetOption("IsolationMode",1,.oldval)。 SQL只能检索已提交数据的更改。 然而,也有一些明显的例外:

  • 查询永远不会返回已删除的行,即使删除该行的事务正在进行,且删除可能随后回滚。 ISOLATION LEVEL READ COMMITTED确保插入和更新处于一致状态,而不是删除。
  • 如果查询包含聚合函数,则聚合结果将返回数据的当前状态,而与指定的隔离级别无关。 因此,聚合结果中包含正在进行的插入和更新(随后可能回滚)。 正在进行的删除(随后可能会回滚)不包括在聚合结果中。 这是因为聚合操作需要访问表中的许多行数据。
  • 包含DISTINCT子句或GROUP BY子句的SELECT查询不受隔离级别设置的影响。 包含这些子句之一的查询将返回数据的当前状态,包括可能随后回滚的正在进行的更改。 这是因为这些查询操作需要访问表中的许多行数据。
  • 带有%NOLOCK关键字的查询。

注意:在使用ECP(企业缓存协议)的 IRIS实现上,与READ UNCOMMITTED相比,使用READ COMMITTED可能会导致明显的性能下降。 在定义包含ECP的事务时,开发人员应该权衡READ UNCOMMITTED的优越性能和READ COMMITTED的更高数据准确性。

有效隔离级别

可以使用set TRANSACTION(不启动事务)、START TRANSACTION(设置隔离模式并启动事务)或SetOption(“IsolationMode”)方法调用为进程设置隔离级别。

指定的隔离级别保持有效,直到由SET TRANSACTIONSTART TRANSACTIONSetOption(“IsolationMode”)方法调用显式重置。 由于COMMITROLLBACK仅对数据更改有意义,而对数据查询没有意义,因此COMMITROLLBACK操作对ISOLATION LEVEL设置没有影响。

在查询开始时有效的“隔离级别”在查询期间仍然有效。

可以使用GetOption(“IsolationMode”)方法调用确定当前进程的隔离级别。 还可以使用SetOption(“IsolationMode”)方法调用为当前进程设置隔离模式。 这些方法将READ UNCOMMITTED(默认值)指定为0,READ COMMITTED指定为1,READ VERIFIED指定为3。 指定任何其他数值将保持隔离模式不变。 如果将隔离模式设置为当前隔离模式,则不会发生错误或更改。 以下示例显示了这些方法的使用:

ClassMethod SetTransaction1()
{
	w $SYSTEM.SQL.GetOption("IsolationMode")," 默认",!
	&sql(
		START TRANSACTION ISOLATION LEVEL READ COMMITTED,READ WRITE
	)
	w $SYSTEM.SQL.GetOption("IsolationMode")," TART TRANSACTION之后",!
	d $SYSTEM.SQL.SetOption("IsolationMode",0,.stat)
	if stat=1 {
		w $SYSTEM.SQL.GetOption("IsolationMode")," after IsolationMode=0 call",! 
	} else { WRITE "Set IsolationMode error" 
	}
	&sql(COMMIT)
}

隔离模式和访问模式必须始终兼容。 更改访问模式将更改隔离模式,示例如下:

ClassMethod SetTransaction2()
{
	w $SYSTEM.SQL.GetOption("IsolationMode")," default",!
	&sql(
		SET TRANSACTION ISOLATION LEVEL READ COMMITTED,READ WRITE
	)
	w $SYSTEM.SQL.GetOption("IsolationMode")," after SET TRANSACTION",!
	&sql(START TRANSACTION READ ONLY)
	w $SYSTEM.SQL.GetOption("IsolationMode")," after changing access mode",!
	&sql(COMMIT)
}

ObjectScript和SQL事务

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

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

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

如果事务涉及SQL数据修改语句,则应该使用SQL START transaction语句启动事务,并使用SQL COMMIT语句提交事务。 (这些语句可以是显式的,也可以是隐式的,具体取决于%COMMITMODE设置。) 使用TSTART/TCOMMIT嵌套的方法可以包含在事务中,只要它们不初始化事务。 方法和存储过程通常不应该使用SQL事务控制语句,除非按照设计,它们是事务的主控制器。 存储过程通常不应该使用SQL事务控制语句,因为这些存储过程通常是从ODBC/JDBC调用的,ODBC/JDBC有自己的事务控制模型。

示例

下面的嵌入式SQL示例使用两个START TRANSACTION语句来启动事务并设置其参数。 注意,第一个START TRANSACTION启动一个事务,设置提交模式并增加$TLEVEL事务级别计数器。 第二个START TRANSACTION为当前事务中的查询读操作设置隔离模式,但不增加$TLEVEL,因为事务已经启动。 SAVEPOINT语句增加$TLEVEL:

ClassMethod StartTransaction()
{
	&sql(SET TRANSACTION %COMMITMODE EXPLICIT)
	w !,"设置事务提交模式, SQLCODE=",SQLCODE
	w !,"事务级别=",$TLEVEL
	&sql(SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED)
	w !,"设置事务隔离模式, SQLCODE=",SQLCODE
	w !,"事务级别=",$TLEVEL
	&sql(START TRANSACTION)
	w !,"开始事务, SQLCODE=",SQLCODE
	w !,"事务级别==",$TLEVEL
	&sql(SAVEPOINT a)
	w !,"设置保存点, SQLCODE=",SQLCODE
	w !,"事务级别==",$TLEVEL
	&sql(COMMIT)
	w !,"提交事务, SQLCODE=",SQLCODE
	w !,"事务级别==",$TLEVEL
}
0
0 152
文章 姚 鑫 · 十一月 12, 2021 9m read

第七十四章 SQL命令 SET TRANSACTION

设置事务的参数。

大纲

SET TRANSACTION [%COMMITMODE commitmode]

SET TRANSACTION [transactionmodes]

参数

  • %COMMITMODE commitmode - 可选-指定向数据库提交事务的方式。 取值包括EXPLICITIMPLICITNONE。 默认为IMPLICIT
  • transactionmodes - 可选—指定事务的隔离模式和访问模式。 可以将隔离模式、访问模式或这两种模式的值指定为逗号分隔的列表。隔离模式的有效值为ISOLATION LEVEL READ COMMITTED, ISOLATION LEVEL READ UNCOMMITTED, and ISOLATION LEVEL READ VERIFIED。 默认为“ISOLATION LEVEL READ UNCOMMITTED”。 访问模式的有效值为“READ ONLY”和“READ WRITE”。 注意,只有隔离级别READ COMMITTED与读写模式READ WRITE兼容。

描述

SET TRANSACTION语句为当前进程设置控制SQL事务的参数。 这些参数在下一个事务开始时生效,并在当前进程持续期间或直到显式重置为止。 它们不会在事务结束时自动重置为默认值。

单个SET TRANSACTION语句可用于设置提交模式参数或事务模式参数,但不能同时设置两者。

可以使用START TRANSACTION命令设置相同的参数,该命令既可以设置参数,也可以开始一个新事务。 也可以使用方法调用设置参数。

SET TRANSACTION不会开始一个事务,因此不会增加$TLEVEL事务级别计数器。

SET TRANSACTION可以在动态SQL(包括SQL Shell)和嵌入式SQL中使用。

%COMMITMODE

%COMMITMODE关键字允许您指定是否执行自动事务承诺。 可供选择的选项有:

  • IMPLICIT隐式:自动事务承诺是开启的(默认)。 当程序发出数据库修改操作(INSERTUPDATEDELETE)时,SQL自动启动一个事务。 事务将继续进行,直到操作成功完成并SQL自动提交更改,或者操作无法在所有行上成功完成并SQL自动回滚整个操作。 每个数据库操作(INSERTUPDATEDELETE)构成一个单独的事务。 成功完成数据库操作将自动清除回滚日志、释放锁并减少$TLEVEL。 不需要COMMIT语句。 这是默认设置。
  • EXPLICIT:关闭自动事务承诺。 当程序发出第一个数据库修改操作(INSERTUPDATEDELETE)时,SQL自动启动一个事务。 该交易将继续进行,直到明确达成协议。 成功完成后,发出COMMIT语句。 如果数据库修改操作失败,则发出ROLLBACK语句将数据库恢复到事务开始之前的位置。 在EXPLICIT模式下,每个事务的数据库操作数是用户定义的。
  • NONE:没有自动事务处理。 除非由START transaction语句显式调用,否则不会初始化事务。 必须通过发出COMMITROLLBACK语句显式地结束事务。 因此,事务中是否包含数据库操作以及事务中数据库操作的数量都是用户定义的。

TRUNCATE TABLE不会在自动启动的事务中发生。 如果需要对TRUNCATE TABLE进行日志记录和回滚,则必须显式指定START TRANSACTION,并以显式COMMITrollback结束。

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

ClassMethod SetTransaction()
{
	s stat = $SYSTEM.SQL.SetOption("AutoCommit",$RANDOM(3),.oldval)
	if stat '= 1 {
		w "SetOption failed:" 
		d $System.Status.DisplayError(stat) 
		q
	}
	s x = $SYSTEM.SQL.GetOption("AutoCommit")
	if x = 1 {
		w "%COMMITMODE IMPLICIT (default behavior):",!,
		"每个数据库操作都是一个独立的事务",!,
		"自动提交或回滚" 
	} elseif x = 0 {
		w "%COMMITMODE NONE:",!,
		"没有自动事务支持",!,
		"必须使用START TRANSACTION来启动事务",!,
		"和COMMIT或ROLLBACK来结束一个" 
	} else { 
		w "%COMMITMODE EXPLICIT:",!,
		"第一次数据库操作自动进行",!,
		"启动一个事务;结束事务",!,
		"需要显式的COMMIT或ROLLBACK" }
}

%COMMITMODE可以在ObjectScript中使用SetOption()方法设置,如下set status=$SYSTEM.SQL.Util.SetOption("AutoCommit",intval,.oldval)。 可用的方法值为0 (NONE)1 (IMPLICIT)2 (EXPLICIT)

隔离级别

可以为发出查询的进程指定“隔离级别”。 “隔离级别”选项允许指定正在进行的更改是否可用于查询的读访问。 如果另一个并发进程正在执行对表的插入或更新,并且对表的更改在事务中,那么这些更改正在进行中,并且可能会回滚。 通过为正在查询该表的流程设置ISOLATION LEVEL,可以指定是否希望在查询结果中包含或排除这些正在进行的更改。

  • READ UNCOMMITTED表示所有更改都可以立即用于查询访问。 这包括随后可能被回滚的更改。 READ UNCOMMITTED确保查询将在不等待并发插入或更新进程的情况下返回结果,并且不会因为锁定超时错误而失败。 然而,READ UNCOMMITTED的结果可能包括未提交的值; 这些值在内部可能不一致,因为插入或更新操作只部分完成,这些值可能随后被回滚。 如果查询进程不在显式事务中,或者事务没有指定隔离级别,则READ UNCOMMITTED是默认值。 READ UNCOMMITTEDREAD - WRITE访问不兼容; 试图在同一语句中同时指定这两个变量会导致SQLCODE -92错误。
  • READ VERIFIED声明来自其他事务的未提交数据立即可用,并且不执行锁操作。 这包括随后可能被回滚的更改。 然而,与READ UNCOMMITTED不同的是,READ VERIFIED事务将重新检查任何可能因未提交或新提交的数据而失效的条件,这将导致不满足查询条件的输出。 由于这种条件重新检查,READ VERIFIEDREAD UNCOMMITTED更准确,但效率更低,应该只在可能发生对条件检查的数据的并发更新时使用。 READ VERIFIEDREAD - WRITE访问不兼容; 试图在同一语句中同时指定这两个变量会导致SQLCODE -92错误。
  • READ COMMITTED表示只有那些已经提交的更改可以用于查询访问。 这确保了在数据库上以一致的状态执行查询,而不是在进行一组更改时执行,这组更改随后可能会回滚。 如果请求的数据已被更改,但更改尚未提交(或回滚),则查询将等待事务完成。 如果在等待该数据可用时发生锁定超时,则会发出SQLCODE -114错误。

READ UNCOMMITTED还是READ VERIFIED?

下面的例子演示了READ UNCOMMITTEDREAD VERIFIED之间的区别:

SELECT Name,SSN FROM Sample.Person WHERE Name >= 'M' 

查询优化器可能首先选择从Name索引中收集所有RowID包含的符合>= 'M'条件的Name。 收集之后,每次访问一个RowID,以检索NameSSN字段用于输出。 并发运行的更新事务可以将一个RowID 72PersonName字段从“Smith”更改为“Abel”,该字段位于查询的rowwid集合和它对表的逐行访问之间。 在本例中,索引中的RowID集合将包含不再符合Name >= 'M'条件的行的RowID

READ UNCOMMITTED查询处理假设Name >= 'M'条件已经被索引满足,并且将输出从索引中收集的每个RowID在表中出现的任何Name。 因此,在本例中,它将输出一个名称为'Abel'的行,该行不满足条件。

READ VERIFIED查询处理注意到,它正在从表中为output (Name)检索一个字段,该字段参与了之前应该由索引满足的条件,然后重新检查条件,以防在检查索引之后字段值发生变化。 在重新检查时,它注意到该行不再满足条件,并将其从输出中删除。 只有输出所需的值才会重新检查其条件:在本例中,SELECT SSN FROM Person WHERE Name >= 'M'将输出RowID72的行。

READ COMMITTED 异常

ISOLATION LEVEL read committed生效时,可以通过设置ISOLATION LEVEL read committedSetOption()方法,如下SET status=$SYSTEM.SQL.Util.SetOption("IsolationMode",1,.oldval)。 SQL只能检索已提交数据的更改。 然而,也有一些明显的例外:

  • 查询永远不会返回已删除的行,即使删除该行的事务正在进行,且删除可能随后回滚。 ISOLATION LEVEL READ COMMITTED确保插入和更新处于一致状态,而不是删除。
  • 如果查询包含聚合函数,则聚合结果将返回数据的当前状态,而与指定的隔离级别无关。 因此,聚合结果中包含正在进行的插入和更新(随后可能回滚)。 正在进行的删除(随后可能会回滚)不包括在聚合结果中。 这是因为聚合操作需要访问表中的许多行数据。
  • 包含DISTINCT子句或GROUP BY子句的SELECT查询不受隔离级别设置的影响。 包含这些子句之一的查询将返回数据的当前状态,包括可能随后回滚的正在进行的更改。 这是因为这些查询操作需要访问表中的许多行数据。
  • 带有%NOLOCK关键字的查询。

注意:在使用ECP(企业缓存协议)的IRIS实现上,与READ UNCOMMITTED相比,使用READ COMMITTED可能会导致明显的性能下降。 在定义包含ECP的事务时,开发人员应该权衡READ UNCOMMITTED的优越性能和READ COMMITTED的更高数据准确性。

有效隔离级别

可以使用set TRANSACTION(不启动事务)、START TRANSACTION(设置隔离模式并启动事务)或SetOption(“IsolationMode”)方法调用为进程设置隔离级别。

指定的隔离级别保持有效,直到由SET TRANSACTIONSTART TRANSACTIONSetOption(“IsolationMode”)方法调用显式重置。 由于COMMITROLLBACK仅对数据更改有意义,而对数据查询没有意义,因此COMMITROLLBACK操作对ISOLATION LEVEL设置没有影响。

在查询开始时有效的“隔离级别”在查询期间仍然有效。

可以使用GetOption(“IsolationMode”)方法调用确定当前进程的隔离级别。 还可以使用SetOption(“IsolationMode”)方法调用为当前进程设置隔离模式。 这些方法将READ UNCOMMITTED(默认值)指定为0,READ COMMITTED指定为1,READ VERIFIED指定为3。 指定任何其他数值将保持隔离模式不变。 如果将隔离模式设置为当前隔离模式,则不会发生错误或更改。 以下示例显示了这些方法的使用:

ClassMethod SetTransaction1()
{
	w $SYSTEM.SQL.GetOption("IsolationMode")," 默认",!
	&sql(
		START TRANSACTION ISOLATION LEVEL READ COMMITTED,READ WRITE
	)
	w $SYSTEM.SQL.GetOption("IsolationMode")," TART TRANSACTION之后",!
	d $SYSTEM.SQL.SetOption("IsolationMode",0,.stat)
	if stat=1 {
		w $SYSTEM.SQL.GetOption("IsolationMode")," after IsolationMode=0 call",! 
	} else { WRITE "Set IsolationMode error" 
	}
	&sql(COMMIT)
}

隔离模式和访问模式必须始终兼容。 更改访问模式将更改隔离模式,示例如下:

ClassMethod SetTransaction2()
{
	w $SYSTEM.SQL.GetOption("IsolationMode")," default",!
	&sql(
		SET TRANSACTION ISOLATION LEVEL READ COMMITTED,READ WRITE
	)
	w $SYSTEM.SQL.GetOption("IsolationMode")," after SET TRANSACTION",!
	&sql(START TRANSACTION READ ONLY)
	w $SYSTEM.SQL.GetOption("IsolationMode")," after changing access mode",!
	&sql(COMMIT)
}

示例

下面的嵌入式SQL示例使用两个SET TRANSACTION语句来设置事务参数。 注意,SET TRANSACTION不会增加事务级别($TLEVEL)。 START TRANSACTION命令启动一个事务并增加$TLEVEL:

ClassMethod SetTransaction3()
{
	&sql(SET TRANSACTION %COMMITMODE EXPLICIT)
	w !,"设置事务提交模式, SQLCODE=",SQLCODE
	w !,"事务级别=",$TLEVEL
	&sql(SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED)
	w !,"设置事务隔离模式, SQLCODE=",SQLCODE
	w !,"事务级别=",$TLEVEL
	&sql(START TRANSACTION)
	w !,"开始事务, SQLCODE=",SQLCODE
	w !,"事务级别==",$TLEVEL
	&sql(SAVEPOINT a)
	w !,"设置保存点, SQLCODE=",SQLCODE
	w !,"事务级别==",$TLEVEL
	&sql(COMMIT)
	w !,"提交事务, SQLCODE=",SQLCODE
	w !,"事务级别==",$TLEVEL
}

DHC-APP>d ##class(PHA.TEST.SQLCommand).SetTransaction3()
 
设置事务提交模式, SQLCODE=0
事务级别=0
设置事务隔离模式, SQLCODE=0
事务级别=0
开始事务, SQLCODE=0
事务级别==1
设置保存点, SQLCODE=0
事务级别==2
提交事务, SQLCODE=0
事务级别==0
0
0 88
文章 姚 鑫 · 十一月 11, 2021 8m read

第七十三章 SQL命令 SET OPTION

设置执行选项。

大纲

SET OPTION option_keyword = value

描述

SET OPTION语句用于设置执行选项,如编译模式、SQL配置设置和控制日期、时间和数字约定的区域设置。 每个set option语句只能设置一个关键字选项。

SET OPTION支持以下选项:

  • `AUTO_PARALLEL_THRESHOLD
  • COMPILEMODE
  • `DEFAULT_SCHEMA``
  • EXACT_DISTINCT
  • LOCK_ESCALATION_THRESHOLD
  • LOCK_TIMEOUT
  • PKEY_IS_IDKEY
  • SUPPORT_DELIMITED_IDENTIFIERS
  • Locale Options (date, time, and numeric conventions)

SET OPTION可以在动态SQL(包括SQL Shell)和嵌入式SQL中使用。

为了SQL兼容性,IRIS会解析其他SET OPTION参数(这里没有文档),但不执行任何操作。

因为SET OPTION的准备和执行速度很快,而且通常只运行一次,所以IRIS不会在ODBCJDBC或动态SQL中为SET OPTION创建缓存查询。

IRIS支持下列选项:

AUTO_PARALLEL_THRESHOLD

AUTO_PARALLEL_THRESHOLD选项被设置为一个整数n,用于确定当启用自动并行处理时是否应该对查询应用并行处理。 由于与并行处理相关的性能成本,因此需要为并行处理的优势确定一个阈值。 n越高,SQL查询使用并行处理执行的可能性就越低。 默认为3200。 这是一个系统范围的设置。 值n大致对应于所访问的映射中发生并行处理所需的最小元组数量。

当自动并行被禁用时,AUTO_PARALLEL_THRESHOLD选项没有作用。

也可以使用$SYSTEM.SQL.Util.SetOption()方法AutoParallelThreshold选项设置该选项。

COMPILEMODE

COMPILEMODE选项将当前名称空间的编译模式设置为DEFERREDIMMEDIATEINSTALLNOCHECK。 默认为IMMEDIATE。 从DEFERRED编译模式更改为IMMEDIATE编译模式会导致DEFERRED compile Queue中的任何类立即被编译。 如果所有类编译都成功,IRIS将SQLCODE设置为0。 如果有任何错误,SQLCODE设置为-400。 类编译错误记录在^mtemp2 ("Deferred Compile Mode","Error")中。 如果将SQLCODE设置为-400,则应该查看此全局结构以获得更精确的错误消息。 INSTALL编译模式类似于DEFERRED编译模式,但它应该只用于表中没有数据的DDL安装。

NOCHECK编译模式与IMMEDIATE编译模式类似,只是在编译时忽略了以下约束:如果一个表被删除, IRIS不检查引用被删除表的其他表中的外键约束。 如果添加了外键约束, IRIS不会检查现有数据以确保它对这个外键有效。 如果添加了NOT NULL约束, IRIS不会检查现有数据是否为NULL,也不会指定字段的默认值。 如果删除了UNIQUEPrimary Key约束 IRIS不会检查该表或其他表中的外键是否引用了被删除的键。

也可以使用$SYSTEM.SQL.Util.SetOption()方法CompileMode选项设置该选项。

DEFAULT_SCHEMA

DEFAULT_SCHEMA选项为所有名称空间设置系统范围的默认模式。 在显式更改之前,此默认值将保持有效。 默认模式名用于为所有未限定的表、视图或存储过程名提供模式名。

可以指定一个文字模式名或指定_CURRENT_USER。 如果指定_CURRENT_USER作为默认模式名, IRIS会将当前登录进程的用户名作为默认模式名。

EXACT_DISTINCT

EXACT_DISTINCT布尔值选项指定是否在系统范围内使用DISTINCT处理(TRUE)Fast DISTINCT处理(FALSE)。 系统范围的默认值是使用Fast Distinct处理。

EXACT_DISTINCT=TRUE时,GROUP BYDISTINCT查询生成原始值。 当EXACT_DISTINCT=FALSE时,启用快速Distinct,通过更好地使用索引(如果有索引),使涉及DistinctGROUP BY子句的SQL查询更有效地运行。 但是,这些查询返回的值以与存储在索引中的相同的方式进行排序。 这意味着此类查询的结果可能都是大写的。 这可能对区分大小写的应用程序有影响。

这个选项也可以使用$SYSTEM.SQL.Util.SetOption()方法FastDistinct boolean选项来设置。

LOCK_ESCALATION_THRESHOLD

LOCK_ESCALATION_THRESHOLD选项被设置为一个整数n,用于确定何时将行锁定升级为表锁定。 默认值是1000。 值n是单个事务中单个表的插入、更新或删除次数,当到达时将触发表级锁。 这是针对所有名称空间的系统范围设置。 例如,如果锁阈值为1000,并且进程启动一个事务,然后插入2000行,那么在插入第1001行之后,进程将尝试获取表级锁,而不是继续锁定各个行。 这有助于防止锁表变得太满。

这个选项也可以使用$SYSTEM.SQL.Util.SetOption()方法LockThreshold选项来设置。

LOCK_TIMEOUT

LOCK_TIMEOUT数值选项允许为当前进程设置默认的锁定超时。 LOCK_TIMEOUT值是SQL执行期间试图建立锁时等待的秒数。 当锁定冲突阻止当前进程对lockINSERTUPDATEDELETESELECT操作立即锁定一条记录、表或其他实体时,使用此锁定超时。 SQL继续尝试建立锁,直到超时超时,这时将生成SQLCODE -110-114错误。

可用的值是正整数和零。 超时设置是每个进程的。 可以使用$SYSTEM.SQL.Util.GetOption(“ProcessLockTimeout”)方法确定当前进程的锁定超时设置。

如果没有为当前进程设置锁定超时,则默认为当前系统范围的锁定超时设置。 如果您的ODBC连接断开并重新连接,重新连接的进程将使用当前系统范围的锁定超时设置。 系统范围的锁定超时默认为10秒。

PKEY_IS_IDKEY

PKEY_IS_IDKEY boolean选项指定主键是否也是系统范围内的ID键。 取值为TRUEFALSE。 如果为TRUE,且该字段不包含数据,则将主键创建为ID键。 也就是说,表的主键也成为了类定义中的IDKey索引。 如果字段不包含数据,则没有定义IDKey索引。 如果将主键定义为IDKey索引,则数据访问将更加有效,但主键值一旦设置,就永远不能修改。 一旦设置,就不能更改分配给主键的值,也不能将其他键指定为主键。 使用此选项还将更改主键排序规则的默认值; 主键字符串值默认为EXACT排序规则。 如果为FALSE,则主键和ID键被定义为独立的,效率较低。 但是,主键值是可修改的,主键字符串值默认为当前排序规则类型default,默认为SQLUPPER

要设置PKEY_IS_IDKEY选项,必须具有%Admin_Manage:USE权限。 否则,将收到一个SQLCODE -99错误(特权违反)。 一旦设置,该选项将在系统范围内对所有进程生效。 该选项的系统范围默认值也可以通过以下方式设置:

  • $SYSTEM.SQL.Util.SetOption()方法配置选项DDLPKeyNotIDKey。 要确定当前设置,调用$SYSTEM.SQL.CurrentSettings(),它显示通过DDL创建的是主键而不是ID键; 默认值是1。
  • 管理门户配置设置。 选择系统管理,配置,SQL和对象设置,SQL。 查看或修改通过DDL创建的表的“将主键定义为ID键”的当前设置。

PKEY_IS_IDKEY设置保持有效,直到通过另一个SET OPTION PKEY_IS_IDKEY重置或直到 IRIS Configuration被重新激活,将该参数重置为IRIS System Configuration设置。

SUPPORT_DELIMITED_IDENTIFIERS

默认情况下,系统范围内支持分隔标识符。 SUPPORT_DELIMITED_IDENTIFIERS布尔选项允许您更改系统范围内对分隔标识符的支持。 取值为TRUEFALSE。 如果为TRUE,用双引号分隔的字符串被认为是SQL语句中的标识符。 如果为FALSE,由双引号分隔的字符串被认为是SQL语句中的字符串字面值。

要设置SUPPORT_DELIMITED_IDENTIFIERS选项,必须具有%Admin_Manage:USE权限。 否则,将收到一个SQLCODE -99错误(特权违反)。 一旦设置,该选项将在系统范围内对所有进程生效。 SUPPORT_DELIMITED_IDENTIFIERS设置将保持有效,直到通过另一个设置选项SUPPORT_DELIMITED_IDENTIFIERS进行重置,或者直到由$SYSTEM.SQL.Util.SetOption()方法delimitedifiers选项在系统范围内进行更改。

Locale Options

区域设置选项是关键字选项,用于为当前进程的日期、时间和数字约定设置IRIS区域设置。 可选关键字有AM、DATE_FORMAT、DATE_MAXIMUM、DATE_MINIMUM、DATE_SEPARATOR、DECIMAL_SEPARATOR、MIDNIGHT、MINUS_SIGN、MONTH_ABBR、MONTH_NAME、NOON、NUMERIC_GROUP_SEPARATOR、NUMERIC_GROUP_SIZE、PM、PLUS_SIGN、TIME_FORMAT、TIME_PRECISION、TIME_SEPARATOR、WEEKDAY_ABBR、WEEKDAY_NAME、YEAR_OPTION。 所有这些选项都可以设置为文字,并且都采用默认值(美式英语惯例)。 TIME_PRECISION选项是可配置的(参见下面)。 如果将这些选项中的任何一个设置为无效值,InterSystems IRIS将发出SQLCODE -129错误(set OPTION区域设置属性的非法值)。

Date/Time Option KeywordDescription
AMString. 默认 'AM'
DATE_FORMATInteger. 默认值为1。取值范围为0 ~ 15
DATE_MAXIMUMInteger. 默认为2980013(12/31/9999)。可以设置为更早的日期,但不能设置为更晚的日期。
DATE_MINIMUMPositive Integer. 默认为0(12/31/1840)。可以设置为较晚的日期,但不能设置为较早的日期。
DATE_SEPARATORCharacter. Default is '/'
DECIMAL_SEPARATORCharacter. Default is '.'
MIDNIGHTString. Default is 'MIDNIGHT'
MINUS_SIGNCharacter. Default is '-'
MONTH_ABBRString. Default is ' Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'. (注意,该字符串以空格字符开始,这是默认分隔符.)
MONTH_NAMEString. Default is ' January February March April May June ... November December'. 注意,该字符串以空格字符开始,这是默认分隔符.)
NOONString. Default is 'NOON'
NUMERIC_GROUP_SEPARATORCharacter. Default is ','
NUMERIC_GROUP_SIZEInteger. Default is 3.PM String. Default is 'PM'
PLUS_SIGNCharacter. Default is '+'
TIME_FORMATInteger. Default is 1. 取值范围为1 ~ 4。
TIME_PRECISIONInteger from 0 through 9 (inclusive). Default is 0. 小数秒的位数。
TIME_SEPARATORCharacter. Default is ':'
WEEKDAY_ABBRString. Default is ' Sun Mon Tue Wed Thu Fri Sat'. (注意,该字符串以空格字符开始,这是默认分隔符.)
WEEKDAY_NAMEString. Default is ' Sunday Monday Tuesday Wednesday Thursday Friday Saturday'. (注意,该字符串以空格字符开始,这是默认分隔符.)
YEAR_OPTIONInteger. Default is 0. 取值范围为0 ~ 6。有关表示2位数和4位数年份的这些方法的解释,见ObjectScript $ZDATE函数。

要在系统范围内配置TIME_PRECISION,请进入管理门户,选择“系统管理”、“配置”、“SQL”和“对象设置”、“SQL”。 查看和编辑GETDATE()CURRENT_TIMECURRENT_TIMESTAMP的默认时间精度的当前设置。 它指定小数秒的精确位数。 默认值是0。 允许的值的范围是09位精度。 小数秒中有意义的数字的实际数目与平台有关。

0
0 111
文章 Muhammad Waseem · 十月 16, 2021 2m read

在本文中,我將演示以下內容:

  • 使用自定義實用程序函數從數據庫更新 ReferencesRange(OBX:7) 針對 ObservationIdentifier(OBX:3.1)[TestCode]
  • 根據數據庫實用程序函數中的 ObservationIdentifier(OBX:3.1)[TestCode] 和 ObservationValue(OBX:5)[Result] 更新異常標誌 (OBX:8)
  • 基於異常標誌的路由消息 (OBX:8)

以下是主要和轉換後的 HL7 2.5 ORU_R01 消息:
 

第 1 步:首先,我們需要在數據庫中保存參考範圍,為此我使用了 TestRanges 持久類:

第 2 步:我們需要創建一個自定義函數來獲取引用範圍,為此我在實用程序類中創建了 GetReferenceRange() ClassMethod 函數。 請注意這個類應該是來自 EnsRule.FunctionSet 的擴展

 

我們需要創建另一個函數來設置異常標誌,為此我在實用程序類中創建了 SetAbnormalFlag() ClassMethod 函數

1
0 193
文章 姚 鑫 · 十一月 10, 2021 6m read

第七十二章 SQL命令 SELECT(四)

WHERE子句

WHERE子句限定或取消查询选择中的特定行。 符合条件的行是那些条件表达式为真的行。 条件表达式是逻辑测试(谓词)的列表,可以通过ANDOR逻辑操作符链接这些测试(谓词)。 这些谓词可以使用NOT一元逻辑操作符进行反转。

SQL谓词可分为以下几类:

  • Comparison 谓词
  • BETWEEN 谓词
  • LIKE 谓词
  • NULL 谓词
  • IN and %INLIST 谓词
  • EXISTS 谓词
  • FOR SOME 谓词
  • FOR SOME %ELEMENT 谓词

条件表达式不能包含聚合函数。 如果希望使用聚合函数返回的值指定选择条件,请使用HAVING子句。

WHERE子句可以使用=(内部连接)符号连接操作符指定两个表之间的显式连接。

WHERE子句可以使用箭头语法(- >)操作符在基表和来自另一个表的字段之间指定隐式连接。

GROUP BY子句

GROUP BY子句接受查询的结果行,并根据一个或多个数据库列将它们分成单独的组。 当将SELECTGROUP BY结合使用时,将为GROUP BY字段的每个不同值检索一行。 GROUP BY子句在概念上类似于 IRIS扩展%FOREACH,但是GROUP BY操作整个查询,而%FOREACH允许在子填充上选择聚合,而不限制整个查询填充。 例如:

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

这个查询为每个不同的Home_State返回一行。

HAVING 子句

HAVING子句类似于对组进行操作的WHERE子句。 它通常与GROUP BY子句或%AFTERHAVING关键字一起使用。 HAVING子句限定或取消查询选择中的特定行。 符合条件的行是那些条件表达式为真的行。 条件表达式是逻辑测试(谓词)的列表,可以通过ANDOR逻辑操作符链接这些测试(谓词)。 条件表达式可以包含聚合函数。

ORDER BY 子句

ORDER BY子句由ORDER BY关键字后面跟着一个选择项或一个以逗号分隔的项列表组成,该列表指定显示行的顺序。 每个项目可以有一个可选的ASC(升序)或DESC(降序)。 默认为升序。 ORDER BY子句应用于查询的结果,并且经常与TOP子句配对。

下面的示例返回数据库中所有行的选定字段,并按年龄升序排列这些行:

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

SELECT和事务处理

执行查询的事务被定义为READ COMMITTEDREAD UNCOMMITTED。 默认是READ UNCOMMITTED。 不在事务中的查询定义为READ UNCOMMITTED

  • 如果READ UNCOMMITTED,则SELECT返回数据的当前状态,包括未提交的正在进行的事务对数据所做的更改。 这些更改可能随后被回滚。

  • 如果READ COMMITTED,则行为取决于SELECT语句的内容。 通常,在read committed模式下的SELECT语句只会返回对已提交数据的插入和更新更改。 已被正在进行的事务删除的数据行不会返回,即使这些删除尚未提交并可能回滚。

但是,如果SELECT语句包含%NOLOCK关键字、DISTINCT子句或GROUP BY子句,则SELECT返回数据的当前状态,包括当前事务中尚未提交的对数据的更改。 SELECT中的聚合函数还返回指定列的数据的当前状态,包括未提交的更改。

Query Metadata

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

示例

在下面的示例中,在Sample.Person中的所有记录上计算AvgAge computed字段。 HAVING子句管理AvgMiddleAge computed字段,从Sample.Person中的所有记录中计算那些超过40岁的人的平均年龄。 因此,AvgAgeAvgMiddleAge的每一行都有相同的值。 ORDER BY子句按照Home_State字段值的字母顺序对行进行显示。

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

WHERE/HAVING/ORDER BY

在下面的示例中,WHERE子句将选择限制在七个指定的东北部州。 AvgAge computed字段是根据来自那些Home_States的记录计算的。 HAVING子句管理AvgMiddleAge computed字段,从指定Home_States的记录中计算40岁以上的人的平均年龄。 因此,AvgAgeAvgMiddleAge的每一行都有相同的值。 ORDER BY子句按照Home_State字段值的字母顺序对行进行显示。

SELECT Name,Home_State,Age,AVG(Age) AS AvgAge,
 AVG(Age %AFTERHAVING) AS AvgMiddleAge
 FROM Sample.Person
 WHERE Home_State IN ('ME','NH','VT','MA','RI','CT','NY')
 HAVING Age > 40
 ORDER BY Home_State

GROUP BY/HAVING/ORDER BY

GROUP BY子句导致对每个Home_State组分别计算AvgAge computed字段。 GROUP BY子句还将输出显示限制为从每个Home_State遇到的第一个记录。 HAVING子句管理AvgMiddleAge computed字段,计算每个Home_State组中40岁以上人群的平均年龄。 ORDER BY子句按照Home_State字段值的字母顺序对行进行显示。

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

WHERE/GROUP BY/HAVING/ORDER BY

WHERE条款限制了对东北部七个州的选择。 GROUP BY子句导致对这七个Home_State组中的每一个单独计算AvgAge computed字段。 GROUP BY子句还将输出显示限制为从每个指定的Home_State遇到的第一个记录。 HAVING子句管理AvgMiddleAge computed字段,计算7个Home_State组中每个组中40岁以上人群的平均年龄。 ORDER BY子句按照Home_State字段值的字母顺序对行进行显示。

SELECT Name,Home_State,Age,AVG(Age) AS AvgAge,
 AVG(Age %AFTERHAVING) AS AvgMiddleAge
 FROM Sample.Person
 WHERE Home_State IN ('ME','NH','VT','MA','RI','CT','NY')
 GROUP BY Home_State
 HAVING Age > 40
 ORDER BY Home_State

嵌入式SQL和动态SQL示例

嵌入式SQL和动态SQL可用于从ObjectScript程序中发出SELECT查询。

下面的嵌入式SQL程序从一条记录中检索数据值,并将它们放在INTO子句中指定的输出主机变量中。

ClassMethod Select2()
{
	n SQLCODE,%ROWCOUNT
	&sql(
		SELECT Home_State, Name, Age
			INTO :a, :b, :c
		FROM Sample.Person)
	if SQLCODE=0 {
		w !,"  Name=",b
		w !,"  Age=",c
		w !,"  Home State=",a
		w !,"Row count is: ",%ROWCOUNT 
	} else {
		w !,"SELECT 失败, SQLCODE=",SQLCODE  
	}
}

DHC-APP>d ##class(PHA.TEST.SQLCommand).Select2()
 
  Name=yaoxin
  Age=31
  Home State=WI
Row count is: 1

这个程序检索(最多)一行,因此%ROWCOUNT变量被设置为0或1。 要检索多行,必须声明游标并使用FETCH命令。

下面的动态SQL示例首先测试所需的表是否存在,并检查当前用户对该表的SELECT特权。 然后执行查询并返回结果集。 它使用WHILE循环对结果集的前10条记录重复调用%Next方法。 它使用%GetData方法显示三个字段值,这些方法指定了SELECT语句中指定的字段位置:

ClassMethod Select3()
{
#;	s tname="Sample.Person"
#;	if $SYSTEM.SQL.TableExists(tname) & $SYSTEM.SQL.CheckPrivilege($USERNAME, "1," _ tname, "s"){
#;		GOTO SpecifyQuery
#;	} else {
#;		w "Table unavailable"  
#;		q
#;	}
#;SpecifyQuery
	s myquery = 3
	s myquery(1) = "SELECT Home_State,Name,SSN,Age"
	s myquery(2) = "FROM Sample.Person"
	s myquery(3) = "ORDER 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()
	if rset.%SQLCODE=0 {
		s x=0
		while x < 10 {
			s x = x + 1
			s status=rset.%Next()
			w rset.%GetData(2)," "   /* Name field */
			w rset.%GetData(1)," "   /* Home_State field */
			w rset.%GetData(4),!     /* Age field */
		}
		w !,"End of Data"
		w !,"SQLCODE=",rset.%SQLCODE," Row Count=",rset.%ROWCOUNT
	} else {
		w !,"SELECT 失败, SQLCODE=",rset.%SQLCODE 
	}
}

0
0 118
文章 姚 鑫 · 十一月 9, 2021 8m read

第七十一章 SQL命令 SELECT(三)

列别名

指定SELECT-ITEM时,可以使用AS关键字指定列名的别名:

SELECT Name AS PersonName, DOB AS BirthDate, ...

列别名在结果集中显示为列标题。指定列别名是可选的;始终提供默认值。列别名以指定的字母大小写显示;但是,当在ORDER BY子句中引用时,它不区分大小写。C别名必须是有效的标识符。C别名可以是分隔的标识符。使用带分隔符的标识符允许列别名包含空格、其他标点符号或作为SQL保留名称。例如,SELECT Name AS "Customer Name" or SELECT Home_State AS "From"

As关键字不是必需的,但使查询文本更易于阅读。因此,以下也是有效的语法:

SELECT Name PersonName, DOB BirthDate, ...

SQL不执行列别名的惟一性检查。 字段列和列别名可能具有相同的名称(尽管不可取),或者两个列别名相同。 当ORDER by子句引用此类非惟一列别名时,可能会导致SQLCODE -24“Ambiguous sort column”错误。 列别名与所有SQL标识符一样,不区分大小写。

其他SELECT子句中列别名的使用由查询语义处理顺序控制。 可以通过ORDER by子句中的列别名引用列。 不能在选择列表中的另一个选择项、DISTINCT BY子句、WHERE子句、GROUP BY子句或HAVING子句中引用列别名。 不能在JOIN操作的ON子句或USING子句中引用列别名。 但是,可以使用子查询使列别名可用来供其他这些其他SELECT子句使用。

字段列别名

选择项字段名不区分大小写。 但是,除非提供列别名,否则结果集中的字段列的名称应遵循与列属性相关联的SqlFieldName的字母大小写。 SqlFieldName的大小写对应于表定义中指定的字段名,而不是选择项列表中指定的字段名。 因此,SELECT name FROM Sample.Person返回字段列标签为Name。 使用字段列别名可以指定要显示的字母大小写,示例如下:

SELECT name,name AS NAME
FROM Sample.Person

字母大小写解析需要时间。 为了最大化SELECT性能,您可以指定字段名的确切字母大小写,如表定义中所指定的那样。 但是,在表定义中确定字段的确切字母大小写通常很不方便,而且容易出错。 相反,可以使用字段列别名来避免字母大小写问题。 注意,对字段列别名的所有引用必须以字母大小写匹配。

下面的动态SQL示例需要字母大小写解析(SqlFieldNames" Latitude "" Longitude "):

ClassMethod Select()
{
	s myquery = "SELECT latitude,longitude FROM Sample.USZipCode"
	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.latitude," ",rset.longitude,! 
	}
}

下面的动态SQL示例不需要区分大小写,因此执行得更快:

ClassMethod Select1()
{
	s myquery = "SELECT latitude AS northsouth,longitude AS eastwest FROM Sample.USZipCode"
	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.northsouth," ",rset.eastwest,! 
	}
}

列名中不包含t-alias表别名前缀。 因此,在下面的示例中,两列都被标记为Name:

SELECT p.Name,e.Name
FROM Sample.Person AS p LEFT JOIN Sample.Employee AS e ON p.Name=e.Name

要区分指定多个表的查询中的列,您应该指定列别名:

SELECT p.Name AS PersonName,e.Name AS EmployeeName
FROM Sample.Person AS p LEFT JOIN Sample.Employee AS e ON p.Name=e.Name

提供列别名以使数据更容易理解。 以表中“Home_State”列为例,将其重命名为“US_State_Abbrev”。

SELECT Name,Home_State AS US_State_Abbrev
FROM Sample.Person

请注意,%ID引用特定的列,因此返回字段名(默认为ID)或指定的列别名,如下面的示例所示:

SELECT %ID,%ID AS Ident,Name
FROM Sample.Person

Non-Field列别名

非字段列将自动分配一个列名。 如果没有为这些字段提供别名, SQL将提供一个惟一的列名,如“Expression_1”“Aggregate_3”。 整数后缀指SELECT语句中指定的选择项位置(选择项列号)。 它们不是该类型字段的计数。

下面是自动分配的列名(n是一个整数)。 这些内容的顺序越来越广泛。 例如,在数字上添加加号或减号将其从HostVar提升为表达式; 连接HostVarLiteral将其提升为表达式; 在子查询中指定LiteralHostVarAggregateExpression将其提升为子查询:

  • Literal_n:一个伪字段变量,比如%TABLENAME,或者NULL说明符。 注意%ID不是Literal_n; 它得到实际RowID字段的列名。
  • HostVar_n:主机变量。 这可能是一个字面量,如' text '123,或空字符串("),一个输入变量(:myvar),或? 由文字替换的输入参数。 请注意,任何对字面量的表达式求值,如在数字后附加符号、字符串连接或算术操作,都使其成为Expression_n。 提供给? 参数不受表达式求值影响而返回。 例如,提供5+7将返回字符串'5+7'作为HostVar_n
  • Aggregate_n:聚合函数,如AVG(Age)COUNT(*)。 如果最外层的操作是聚合函数,那么列就被命名为Aggregate_n,即使这个聚合包含一个表达式。 例如,COUNT(Name)+COUNT(Spouse)Expression_n,而MAX(COUNT(Name)+COUNT(Spouse))Aggregate_n, -AVG(Age)Expression_n,而AVG(-Age)Aggregate_n
  • Expression_n:在文本、字段或Aggregate_nHostVar_nLiteral_nSubquery_n选择项列表中的任何操作都会将其列名更改为Expression_n。 这包括对数字的一元操作(-Age),算术操作(Age+5),连接('USA:'||Home_State),数据类型CAST操作,SQL排序函数(%SQLUPPER(Name)%SQLUPPER Name), SQL标量函数($LENGTH(Name)),用户定义的类方法,CASE表达式,和特殊变量(如CURRENT_DATE$ZPI)。
  • Window_n:窗口函数的结果。 在OVER关键字的右括号之后指定列别名。
  • Subquery_n:指定单个选择项的子查询的结果。 选择项可以是字段、聚合函数、表达式或文字。 在子查询之后而不是在子查询中指定列别名。

在下面的例子中,AVG函数创建的聚合字段列的别名是“AvgAge”; 它的默认名称是“Aggregate_3”(一个在SELECT列表中位置3的聚合字段)。

SELECT Name, Age, AVG(Age) AS AvgAge FROM Sample.Person

下面的示例与上一个示例相同,只是此处省略了AS关键字。 建议使用该关键字,但不是必需的。

SELECT Name, Age, AVG(Age) AvgAge FROM Sample.Person

下面的示例演示如何为选择项子查询指定列别名:

SELECT Name AS PersonName,
       (SELECT Name FROM Sample.Employee) AS EmpName,
       Age AS YearsOld
FROM Sample.Person

FROM子句

FROM table-ref子句指定一个或多个表、视图、表值函数或子查询。 可以将这些table-ref类型的任意组合指定为逗号分隔列表或使用JOIN语法。 如果指定单个table-ref,则从该表或视图检索指定的数据。 如果指定多个表引用,SQL将对这些表执行连接操作,将它们的数据合并到一个结果表中,从这个结果表中检索指定的数据。

如果指定了多个table-ref,可以用逗号或显式连接语法关键字分隔这些表名。

可以使用$SYSTEM.SQL.Schema.TableExists("schema.tname")$SYSTEM.SQL.Schema.ViewExists("schema.vname")方法来确定当前名称空间中是否存在表或视图。 可以使用$SYSTEM.SQL.Security.CheckPrivilege()方法来确定是否对该表或视图具有SELECT权限。

表的别名

当指定table-ref时,可以使用AS关键字指定该表名或视图名的别名:

FROM Sample.Person AS P

AS关键字不是必需的,但使查询文本更容易阅读。 下面是有效的等价语法:

FROM Sample.Person P

t-alias名称必须是有效的标识符。 别名可以是分隔的标识符。 t-alias在查询中的表别名之间必须是唯一的。 与所有标识符一样,t-alias不区分大小写。 因此,不能指定两个只有字母大小写不同的t-alias名称。 这将导致SQLCODE -20“名称冲突”错误。

表别名用作字段名的前缀(带句点),以指示字段所属的表。 例如:

SELECT P.Name, E.Name
FROM Sample.Person AS P, Sample.Employee AS E

当查询指定多个具有相同字段名的表时,必须使用表引用前缀。 表引用前缀可以是t-alias如上所示),也可以是全限定表名,如下面的等价示例所示:

SELECT Sample.Person.Name, Sample.Employee.Name
FROM Sample.Person, Sample.Employee

但是,如果已为该表名分配了t-alias,则不能将完整表名作为该选择项的一部分。 尝试这样做会导致SQLCODE -23错误。

当查询仅引用一个表(或视图)时,可选择指定表别名。 当查询引用多个表(和/或视图)且引用的字段名对每个表都是唯一的时,指定表别名是可选的(但推荐)。 当查询引用多个表(和/或视图),并且在不同的表中引用的字段名相同时,需要指定表别名。 没有指定t-alias(或完全限定的表名)前缀将导致SQLCODE -27“字段%1D在适用的表中不明确”错误。

当指定如下子查询时,可以使用t-alias,但不是必需的:

SELECT Name,(SELECT Name FROM Sample.Vendor)
FROM Sample.Person

t-alias仅唯一标识查询执行的字段; 要惟一地标识用于显示查询结果集的字段,还必须使用列别名(c-alias)。 下面的示例使用了表别名(PerEmp)和列别名(PNameEname):

SELECT Per.Name AS PName, Emp.Name AS EName
FROM Sample.Person AS Per, Sample.Employee AS Emp
WHERE Per.Name %STARTSWITH 'G'

可以为字段、列别名和/或表别名使用相同的名称,而不会产生命名冲突。

如果需要区分引用的是哪个表,则使用t-alias前缀。 以下是一些例子:

SELECT P.%ID As PersonID,
       AVG(P.Age) AS AvgAge,
       Z.%TABLENAME||'=' AS Tablename,
       Z.*
FROM Sample.Person AS P, Sample.USZipCode AS Z
WHERE P.Home_City = Z.City
GROUP BY P.Home_City
ORDER BY Z.City 

Sharding Transparent to SELECT Queries

分片对SQL查询是透明的; 不需要特殊的查询语法。 查询不需要知道FROM子句中指定的表是分片的还是非分片的。 同一个查询可以访问分片表和非分片表。 查询可以包括分片表和非分片表之间的连接。

分片表使用CREATE table命令定义。 它必须在分片主数据服务器上的主命名空间中定义。 这个主命名空间还可以包括非分片表。

0
0 221
文章 姚 鑫 · 十一月 8, 2021 9m read

第七十章 SQL命令 SELECT(二)

select-item

这是所有SELECT语句的必选元素。 通常,选择项指的是FROM子句中指定的表中的一个字段。 选择项由下列一个或多个项组成,多个项之间用逗号分隔:

  • 列名(字段名),带或不带表名别名:
SELECT Name,Age FROM Sample.Person

字段名不区分大小写。 但是,结果集中与字段关联的标签使用表定义中指定的SqlFieldName的字母大小写,而不是选择项中指定的字母大小写。

包含一个或多个下划线的字段名引用嵌入的串行对象属性。 例如,对于字段名Home_City,表包含一个引用字段Home,该字段引用定义属性City的嵌入式串行对象。 对于字段名Home_Phone_AreaCode,该表包含一个引用字段Home,该字段引用嵌入式串行对象属性Phone,该属性引用定义AreaCode属性的嵌套嵌入式串行对象。 如果选择一个引用字段,如HomeHome_Phone,则以%List数据类型格式接收串行对象中所有属性的值。

要显示RowID(记录ID),可以使用%ID伪字段变量别名,该别名显示RowID,而不管它被分配的名称是什么。 默认情况下,RowID的名称是ID,但如果存在用户定义的名为ID的字段, IRIS可能会重命名它。 默认情况下,RowID是一个隐藏字段。

stream字段上的SELECT返回打开的stream对象的oref(对象引用):

SELECT Name,Picture FROM Sample.Employee WHERE Picture IS NOT NULL

FROM子句指定多个表或视图时,必须使用句点将表名(或表名别名)作为选择项的一部分,如下面的两个示例所示:

SELECT Sample.Person.Name,Sample.Employee.Company
      FROM Sample.Person, Sample.Employee

表名别名:

SELECT p.Name, e.Company
      FROM Sample.Person AS p, Sample.Employee AS e

但是,如果已为该表名分配了别名,则不能将完整表名作为选择项的一部分。 尝试这样做会导致SQLCODE -23错误。

可以使用排序函数指定选择项字段的排序和显示。 可以提供不带括号(SELECT %SQLUPPER Name)或带括号(SELECT %SQLUPPER(Name))的排序规则函数。 如果排序规则函数指定了截断,则括号是必需的(SELECT %SQLUPPER(Name,10))。

当选择项引用嵌入的串行对象属性(嵌入的串行类数据)时,使用下划线语法。 下划线语法由对象属性的名称、下划线和嵌入对象中的属性组成:例如,Home_CityHome_State。 (在其他上下文中,例如索引表,它们使用点语法表示:Home.City。)

SELECT Home_City,Home_State FROM Sample.Person

可以使用SELECT直接查询引用字段(例如Home),而不是使用下划线语法。 因为返回的数据是列表格式的,所以可能需要使用$LISTTOSTRING$LISTGET函数来显示数据。 例如:

SELECT $LISTTOSTRING(Home,'^') AS HomeAddress FROM Sample.Person
  • 子查询。 子查询返回指定表中的单个列。 这个列可以是单个表字段(SELECT Name)的值,也可以是作为单个列返回的多个表字段的值,可以使用连接(SELECT Home_City||Home_State)或指定容器字段(SELECT Home)。 子查询可以使用隐式连接(箭头语法)。 子查询不能使用星号语法,即使在子查询中引用的表只有一个数据字段。

子查询的一个常见用法是指定不受GROUP BY子句约束的聚合函数。 在下面的示例中,GROUP BY子句按几十年(例如,2534)对年龄进行分组。 AVG(Age)选择项给出了由group by子句定义的每个组的平均年龄。 为了获得所有组中所有记录的平均年龄,它使用了一个子查询:

SELECT Age AS Decade,
       COUNT(Age) AS PeopleInDecade,
       AVG(Age) AS AvgAgeForDecade,
       (SELECT AVG(Age) FROM Sample.Person) AS AvgAgeAllDecades
FROM Sample.Person
GROUP BY ROUND(Age,-1)
ORDER BY Age
  • 箭头语法,用于访问from子句表以外的表中的字段。 这被称为隐式连接。 在下面的示例中,示例。 Employee表包含Company字段,其中包含Sample中对应公司名称的RowID。 公司表。 箭头语法从表中检索公司名称:
SELECT Name,Company->Name AS CompanyName
      FROM Sample.Employee

在这种情况下,必须拥有被引用表的SELECT特权:对于被引用表的字段和RowID列,可以是表级的SELECT特权,也可以是列级的SELECT特权。

  • 星号语法(*),按列号顺序选择表中的所有列:
SELECT TOP 5 * FROM Sample.Person

星号语法选择嵌入的串行对象属性(字段),包括嵌套在串行对象中的串行对象的属性。 没有选择引用串行对象的字段。 例如,选择来自嵌入式串行对象的Home_City属性,但是使用Home引用字段访问Sample。 没有选择地址嵌入串行类(包含City属性)。

星号语法不选择隐藏字段。 默认情况下,RowID是隐藏的(不通过SELECT *显示)。 但是,如果表定义为%PUBLICROWID,则SELECT *返回RowID字段和所有非隐藏字段。 缺省情况下,该字段的名称为ID,但如果存在用户自定义的ID字段,IRIS可能会对其进行重命名。

在下面的示例中,select-item由一个非限定星号语法组成,用于从表中选择所有列。 注意,你也可以指定重复的列名(在本例中是Name)和非列的select-item元素(在本例中是{fn NOW}):

SELECT TOP 5 {fn NOW} AS QueryDate,
             Name AS Client,
             *
FROM Sample.Person

在下面的示例中,select-item由限定星号语法组成,用于从一个表中选择所有列,以及从另一个表中选择列名列表。

SELECT TOP 5 E.Name AS EmpName, 
             C.*, 
             E.Home_State AS EmpState
FROM Sample.Employee AS E, Sample.Company AS C

注意:SELECT *是 SQL完全支持的一部分,在应用程序开发和调试期间,它非常方便。 但是,在生产应用程序中,首选的编程实践是显式列出所选字段,而不是使用星号语法形式。 显式列出字段可以使应用程序更清晰、更容易理解、更容易维护,并更容易按名称搜索字段。

  • 包含一个或多个SQL聚合函数的选择项。 聚合函数总是返回单个值。 聚合函数的参数可以是下列任何一种:
    • 单个列名计算查询所选行的所有非空值的聚合:
    SELECT AVG(Age) FROM Sample.Person
    
    • 也允许使用标量表达式来计算聚合:
    SELECT SUM(Age) / COUNT(*) FROM Sample.Person
    
    • 星号语法(*)-与COUNT函数一起使用,用于计算表中的行数:
    SELECT COUNT(*) FROM Sample.Person
    
    • 一个选择不同的函数-通过消除冗余值计算聚合:
    SELECT COUNT(DISTINCT Home_State) FROM Sample.Person
    
    • 虽然ANSI SQL不允许在单个SELECT语句中组合列名和聚合函数,但SQL扩展了这一标准,允许这样做:
    SELECT Name, COUNT(DISTINCT Home_State) FROM Sample.Person
    ```sql
    - 使用`%FOREACH`的聚合函数。
    

这将导致对一个或多个列的每个不同值计算聚合:

SELECT DISTINCT Home_State, AVG(Age %FOREACH(Home_State)) 
   FROM Sample.Person
  • 使用%AFTERHAVING的聚合函数。 这导致在HAVING子句指定的子population上计算聚合:
SELECT Name,AVG(Age %AFTERHAVING) 
    FROM Sample.Person
    HAVING (Age > AVG(Age))

将返回年龄大于平均年龄的那些记录,给出年龄高于数据库中所有人平均年龄的那些人的平均年龄。

  • Window function语法,支持基于特定于该行的“窗口框架”为每一行计算聚合、排名和其他函数。 支持以下语法

    window-function() OVER (PARTITION BY partfield ORDER BY orderfield)
    
    • window-function:支持如下窗口函数:ROW_NUMBER()RANK()PERCENT_RANK()FIRST_VALUE(field)SUM(field)
    • OVER:必须在OVER关键字后面加上括号。 括号中的子句是可选的。
    • PARTITION BY partfield:可选子句,根据指定的partfield对行进行分区。 部分字段可以是单个字段,也可以是用逗号分隔的字段列表。 如果指定了,PARTITION BY必须在ORDER BY之前指定。
    • ORDER BY orderfield:可选子句,根据指定的orderfield对行进行排序。 Orderfield可以是单个字段,也可以是逗号分隔的字段列表。

Window function中指定的字段可以接受表别名前缀。

Window function可以指定列别名。 默认情况下,列被标记为Window_n

  • 作为过程存储的用户定义的类方法。 可以是非限定方法名,也可以是限定方法名。 以下是所有有效的类方法名: Sample.RandLetter()一个限定的类方法名; 和Sample.Rand_Letter()调用类方法“Rand_Letter”()。 在下面的例子中,RandCaseLetter()是一个返回随机字母的类方法,可以是大写字母(' U '),也可以是小写字母('L'):
SELECT RandCaseLetter('U')

该方法的返回值将自动从Logical格式转换为Display/ODBC格式。 默认情况下,该方法的输入值不会从Display/ODBC格式转换为Logical格式。 但是,可以使用$SYSTEM.SQL.Util.SetOption("SQLFunctionArgConversion")方法在系统范围内配置输入显示到逻辑的转换。 可以使用$SYSTEM.SQL.Util.GetOption("SQLFunctionArgConversion")来确定该选项的当前配置。

如果指定的方法在当前名称空间中不存在,系统将生成SQLCODE -359错误。 如果指定的方法不明确(可能引用多个方法),系统将生成SQLCODE -358错误。

  • 对数据库列进行操作的用户提供的ObjectScript函数调用(外部函数):
SELECT $$REFORMAT(Name)FROM MyTable

如果在系统范围内配置了“允许SQL语句中的外部函数”选项,则只能在SQL语句中调用用户提供的(外部)函数。 默认为“No”; 默认情况下,尝试调用用户提供的函数会产生SQLCODE -372错误。

不能使用用户提供的函数来调用%routine(以%字符开头的例程)。 尝试这样做会产生SQLCODE -373错误。

  • 对字段值应用额外处理的选择项:

算术运算:

SELECT Name, Age,Age-AVG(Age) FROM Sample.Person

如果选择项算术运算包括除法,并且数据库中该字段的任何值都可能产生值为零或NULL的除数,则不能依赖测试顺序来避免被零除法。 相反,使用case语句来抑制风险。

SQL函数:

SELECT Name,$LENGTH(Name) FROM Sample.Person

SQL case转换函数:

SQL case转换函数:

一个XMLELEMENTXMLFORESTXMLCONCAT函数,它在从指定列名检索的数据值周围放置XML(或HTML)标记。

  • 为所有记录返回相同值的选择项。

    当所有select-items都不引用表数据时,FROM子句是可选的。 如果包含FROM子句,则指定的表必须存在。

    • 算术运算:
    SELECT 7 * 7, 7 * 8 FROM Sample.Person
    
    SELECT Name, Age, 9 - 6 FROM Sample.Person
    
    • 字符串字面值或操作字符串字面值的函数:
    SELECT UCASE('fred') FROM Sample.Person
    

    字符串字面量可以用来产生更可读的输出,如下面的示例所示:

    SELECT TOP 10 Name,'was born on',%EXTERNAL(DOB)
    FROM Sample.Person
    

数值字面值的指定方式决定了它的数据类型。 因此,字符串'123'将被报告为数据类型VARCHAR,而数字123将被报告为数据类型INTEGERnumeric

  • %TABLENAME%CLASSNAME伪字段变量关键字。 %TABLENAME返回当前表名。 %CLASSNAME返回当前表对应的类名。 如果查询引用多个表,可以在关键字前加上表别名。 例如,t1.%TABLENAME
  • 以下ObjectScript特殊变量之一(或其缩写):$HOROLOG$JOB$NAMESPACE$TLEVEL$USERNAME$ZHOROLOG$ZJOB$ZNSPACE$ZPI$ZTIMESTAMP$ZTIMEZONE$ZVERSION
0
0 145
文章 姚 鑫 · 十一月 7, 2021 10m read

第六十九章 SQL命令 SELECT(一)

从数据库中的一个或多个表中检索行。

大纲

[(] SELECT [%keyword]
    [DISTINCT [BY (item {,item2})] | ALL] 
    [TOP {int | ALL}]
    select-item {,select-item, ...}
    [INTO host-variable-list]
    [FROM [optimize-option] table-ref [[AS] t-alias]
      {,table-ref [[AS] t-alias]} ]
    [WHERE condition-expression [{AND | OR condition-expression] ]
    [GROUP BY scalar-expression]
    [HAVING condition-expression [{AND | OR condition-expression] ]
    [ORDER BY item-order-list [ASC | DESC] ]
[)]

select-item ::= 
  [t-alias.]*   |
  [t-alias.]scalar-expression [[AS] c-alias]

参数

  • %keyword - 可选参数:%NOFPLAN%NOLOCK%NORUNTIME%PROFILE%PROFILE_ALL
  • DISTINCTDISTINCT BY (item)ALL - 可选—DISTINCT子句指定返回的每一行必须包含指定字段或字段组合的唯一值。 DISTINCT关键字指定选择项的值必须是唯一的。 DISTINCT BY关键字子句指定项值必须是唯一的。 项目(或用逗号分隔的项目列表)用括号括起来。 通常,项目是列的名称。 它可能作为选择项列出,也可能不作为选择项列出。 可选—ALL关键字指定返回满足SELECT条件的所有行。 这是SQL的默认值。 ALL关键字不执行任何操作; 它是为了SQL兼容性而提供的。
  • TOP intTOP ALL - 可选—TOP子句限制返回的行数为int中指定的行数。 如果查询中没有指定ORDER BY子句,那么将哪些记录作为“top”行返回是不可预测的。 如果指定了ORDER BY子句,顶部的行将按照指定的顺序排列。 DISTINCT关键字(如果指定)应用于TOP之前,指定要返回的唯一值的整型数。 int参数可以是一个正整数或动态SQL ? 解析为正整数的输入参数。 如果没有指定TOP关键字,则默认显示满足SELECT条件的所有行。 TOP ALL仅在子查询或CREATE VIEW语句中有意义。 它用于在这些情况下支持使用ORDER BY子句,满足在子查询或CREATE VIEW中使用的查询中ORDER BY子句必须与TOP子句配对的要求。 TOP ALL不限制返回的行数。
  • select-item - 要检索的一个或多个列(或其他值)。 多个选择项被指定为一个逗号分隔的列表。 还可以使用*符号检索所有列。
  • INTO host-variable-list - 可选-(仅嵌入式SQL):一个或多个主机变量,将选择项值放入其中。 多个主机变量被指定为逗号分隔的列表或单个主机变量数组。 在通过ODBC、JDBC或动态SQL处理的SELECT查询中指定INTO子句将导致SQLCODE -422错误。
  • FROM table-ref - 可选的——对一个或多个表的引用,从中检索数据。 每个FROM子句都需要一个有效的table-ref,即使SELECT不引用该表。 不引用表数据的SELECT可以省略FROM子句。 table-ref可以指定为一个或多个表、视图、表值函数或子查询,以逗号分隔的列表或使用JOIN语法指定。 在使用带有JOIN语法的视图时存在一些限制。 子查询必须用括号括起来。 table-ref可以是限定的(schema.tablename),也可以是不限定的(tablename)。 不限定的table-ref要么提供默认模式名,要么提供模式搜索路径中的模式名。 多个表可以指定为逗号分隔的列表或与ANSI连接关键字关联。 可以指定任何表或视图的组合。 如果在这里在两个表引用之间指定逗号, IRIS将对表执行CROSS JOIN,并从JOIN操作的结果表中检索数据。 如果在两个表引用之间指定ANSI连接关键字, IRIS将执行指定的连接操作。 可以选择为每个table-ref分配别名(t-alias)。 AS关键字是可选的。 可以选择指定一个或多个优化选项关键字来优化查询执行。 可用选项有:%ALLINDEX%FIRSTTABLE%FULL%INORDER%IGNOREINDEX%NOFLATTEN%NOMERGE%NOREDUCE%NOSVSO%NOTOPOPT%nounoropt%PARALLEL%STARTTABLE
  • WHERE condition-expression - 可选的——为要检索的数据指定一个或多个谓词条件的限定符
  • GROUP BY scalar-expression - 可选——用逗号分隔的一个或多个标量表达式列表,指定如何组织检索到的数据; 这些可能包括列名。
  • HAVING condition-expression - 可选的——为要检索的数据指定一个或多个谓词条件的限定符。
  • ORDER BY item-order-list - 可选—指定行显示顺序的选择项或以逗号分隔的项列表。 每个项目可以有一个可选的ASC(升序)或DESC(降序)。 默认为升序。 对查询结果使用ORDER BY子句。 子查询(例如UNION语句)中的ORDER BY子句必须与TOP子句配对。 如果没有指定ORDER BY子句,则返回记录的顺序是不可预测的。 ORDER BY子句可以包含窗口函数。
  • scalar-expression - 字段标识符、包含字段标识符的表达式或通用表达式,如函数调用或算术运算。
  • AS t-alias - 可选-表或视图名的别名(table-ref)。 别名必须是有效的标识符; 它可以是分隔的标识符。
  • AS c-alias - 可选-列名的别名(选择项)。 别名必须是有效的标识符。

描述

SELECT语句执行从IRIS数据库检索数据的查询。 在其最简单的形式中,它从单个表的一个或多个列(字段)中检索数据。 列由select-item列表指定,表由FROM table-ref子句指定,WHERE子句可选地提供一个或多个限制条件,选择哪些行返回它们的列值。

在更复杂的查询中,SELECT可以检索列、聚合和非列数据,可以使用连接从多个表检索数据,也可以使用视图检索数据。

SELECT还可以用于从SQL函数、宿主变量或字面量返回值。 SELECT查询可以将返回这些非数据库值与从表或视图检索值结合起来。 当SELECT仅用于返回此类非数据库值时,FROM子句是可选的。

SELECT查询返回的值称为结果集。 在动态SQL中,SELECT将值检索到%SQL中。 声明类。

IRIS设置一个状态变量SQLCODE,它指示SELECT的成功或失败。 此外,SELECT操作将%ROWCOUNT局部变量设置为选定的行数。 成功完成SELECT操作通常会将SQLCODE=0%ROWCOUNT设置为选中的行数。 在包含简单SELECT的嵌入式SQL的情况下,将选择(最多)一行的数据,因此SQLCODE=0%ROWCOUNT设置为01。 但是,对于声明游标并从多行获取数据的嵌入式SQL SELECT,当游标被推进到数据末尾时(SQLCODE=100),操作就完成了; 此时,%ROWCOUNT被设置为选中的行总数。

Uses of SELECT

可以在以下上下文中使用SELECT语句:

  • 作为一个独立的查询准备作为动态SQL查询,嵌入式SQL查询,或类查询。
  • 作为子查询,为外围SELECT语句的子句提供值的SELECT语句。 SELECT语句中的子查询可以在选择项列表、FROM子句或带EXISTSin谓词的WHERE子句中指定。 子查询也可以在UPDATEDELETE语句中指定。 子查询必须用括号括起来。
  • UNION语句允许将两个或多个SELECT语句组合成一个查询。
  • 作为CREATE VIEW的一部分,定义视图可用的数据。
  • 作为嵌入式SQL中使用的DECLARE CURSOR的一部分。
  • 作为带有SELECTINSERT的一部分。 INSERT语句可以使用SELECT将多行数据值插入到表中,从另一个表中选择数据。

可以用一组或多组括号将整个SELECT语句括起来,如下所示:

  • 对于独立的SELECT查询、UNION分支SELECT查询、CREATE VIEWSELECT查询或DECLARE CURSOR SELECT查询,括号是可选的。 将SELECT查询括在括号中会使它遵循子查询的语法规则; 具体来说,ORDER BY子句必须与TOP子句配对。
  • 对于子查询,括号是必须的。 一组括号是必须的; 可以指定附加的可选括号集。
  • INSERT语句SELECT查询不允许使用括号。

指定可选括号会为添加的每组括号生成一个单独的缓存查询。

权限

要在一个或多个表上执行SELECT查询,必须对所有指定的选择项列具有列级SELECT权限,或者对指定的表引用表或视图具有表级SELECT权限。 使用表别名(如t.Name“MyAlias”. name)指定的选择项列只需要列级的SELECT特权,而不需要表级的SELECT特权。

当使用SELECT *时,请注意列级权限覆盖GRANT语句中命名的所有表列; 表级权限涵盖所有表列,包括分配权限后添加的列。

没有必要的特权将导致SQLCODE -99错误(特权违反)。 可以通过调用%CHECKPRIV命令来确定当前用户是否具有SELECT权限。 通过调用$SYSTEM.SQL.Security.CheckPrivilege()方法,可以确定指定的用户是否具有表级的SELECT权限。

注意:对表具有表级SELECT特权并不能充分测试该表是否实际存在。 如果指定的用户具有%All角色,则CheckPrivilege()返回1,即使指定的表或视图不存在。

没有FROM子句的SELECT查询不需要任何SELECT特权。 包含FROM子句的SELECT查询需要SELECT特权,即使查询没有访问任何列数据。

必需子句

下面是所有SELECT语句的必需子句:

  • 要从表中检索或以其他方式生成的一个或多个项(select-item参数)的以逗号分隔的选择项列表。 最常见的是,这些项是表中列的名称。 选择项由指定一个或多个单独项的标量表达式或引用基表所有列的星号(*)组成。
  • FROM子句指定要从其中检索行的一个或多个表、视图或子查询。 这些表可以通过JOIN表达式关联。 在SQL中,对于任何引用表数据的SELECT,都需要一个带有有效表引用的FROM子句。 对于不访问表数据的SELECT, FROM子句是可选的。 可选的FROM子句在FROM子句参考页中有进一步的描述。

可选子句

以下可选子句对FROM子句返回的虚表进行操作。 所有都是可选的,但是,如果使用,必须按照指定的顺序出现:

  • DISTINCT子句,指定只返回不同的(非重复的)值。
  • 一个TOP子句,它指定要返回多少行。
  • WHERE子句,指定行必须匹配的布尔谓词条件。 WHERE子句谓词条件既确定返回哪些行,又将提供给聚合函数的值限制为来自这些行的值。 这些条件由逻辑操作符链接的一个或多个谓词指定; WHERE子句返回满足这些谓词条件的所有记录。 WHERE子句谓词不能包含聚合函数。
  • GROUP BY子句,它指定以逗号分隔的列列表。 它们将查询结果集组织为具有匹配一个或多个列值的子集,并确定返回行的顺序。 groupby允许标量表达式和列。
  • HAVING子句,指定行必须匹配的布尔谓词条件。 这些条件由逻辑操作符链接的一个或多个谓词指定。 HAVING子句谓词条件确定返回哪些行,但是(默认情况下)它不将提供给聚合函数的值限制为来自这些行的值。 可以使用%AFTERHAVING关键字覆盖此默认值。 HAVING子句谓词可以指定聚合函数。 这些谓词通常对group by子句指定的每个组进行操作。
  • ORDER BY子句,指定显示行的顺序。 子查询或CREATE VIEW查询中的ORDER BY子句必须与TOP子句配对。

以错误的顺序指定SELECT子句将产生SQLCODE -25错误。

SELECT语法顺序与SELECT子句语义处理顺序不同。

%Keyword 参数

要使用Keyword字参数,必须拥有当前名称空间相应的admin-privilege

指定Keyword字参数对处理的影响如下:

  • %NOFPLAN -此操作忽略冻结的计划(如果有); 该操作将生成一个新的查询计划。 冻结的计划被保留,但不使用。
  • %NOLOCK - IRIS将不对任何指定的表执行锁操作。 如果指定此关键字,则查询将以READUNCOMMITTED模式检索数据,而不管当前事务的隔离模式如何。
  • %NORUNTIME -运行时计划选择(RTPC)优化没有使用。
  • %PROFILE%PROFILE_ALL - 如果指定了这些关键字指令中的一个,将生成SQLStats收集代码。 这与打开PTools时生成的代码相同。 不同之处在于,SQLStats收集代码仅为该特定语句生成。 正在编译的例程/类中的所有其他SQL语句将生成代码,就像PTools被关闭一样。 这使用户能够分析/检查应用程序中的特定问题SQL语句,而不必为未被调查的SQL语句收集无关的统计信息。 %PROFILE为主查询模块收集SQLStats%PROFILE_ALL为主查询模块及其所有子查询模块收集SQLStats

可以以任何顺序指定多个%Keyword参数。 多个参数由空格分隔。

DISTINCT子句

DISTINCT关键字子句将消除冗余字段值。 它有两种形式:

  • SELECT DISTINCT:为每个选择项值的唯一组合返回一行。 可以指定一个或多个选择项。 例如,下面的查询返回一行,其中Home_StateAge值的每个唯一组合都有Home_StateAge值:
SELECT DISTINCT Home_State,Age FROM Sample.Person

SELECT DISTINCT BY (item):为每个项目值的唯一组合返回一行。 可以指定单个项目,也可以指定以逗号分隔的项目列表。 选择项列表可以(但不是必须)包含指定的项。 例如,下面的查询返回一行,其中包含Home_StateAge值的每个唯一组合的NameAge值:

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

项可以是任何有效的选择项值,星号除外。 它不能是列名别名。

任何类型的DISTINCT子句都可以指定多个项来测试唯一性。 列出一个以上的项将检索两个项组合中不同的所有行。 DISTINCT认为NULL是唯一的值。

TOP子句

TOP关键字子句指定SELECT语句只返回指定的行数。 它返回出现在返回的虚拟表的“顶部”的指定行数。 默认情况下,哪些行是表的“顶部”行是不可预测的。 但是, IRIS在选择TOP行之前应用DISTINCTORDER BY子句(如果指定了)。

0
0 291
文章 姚 鑫 · 十一月 6, 2021 4m read

第六十八章 SQL命令 SAVEPOINT

在事务中标记一个点。

大纲

SAVEPOINT pointname

参数

  • pointname - 保存点的名称,指定为标识符。

描述

SAVEPOINT语句标记事务中的一个点。建立保存点使能够执行事务回滚到保存点,撤消在此期间完成的所有工作并释放在此期间获得的所有锁。在长期运行的事务或具有内部控制结构的事务中,通常希望能够回滚事务的一部分,而不撤消在事务期间提交的所有工作。

保存点的建立会递增$TLEVEL事务级别计数器。回滚到保存点会将$TLEVEL事务级别计数器递减到紧接在保存点之前的值。可以在一个事务内建立最多255个保存点。超过这个保存点数量会导致SQLCODE-400致命错误,这是在SQL执行期间捕获的<TRANSACTION LEVEL> 异常。终端提示符将当前事务级别显示为提示符的TLn:前缀,其中n是介于1255之间的整数,表示当前$TLEVEL计数。

每个保存点都与一个保存点名称相关联,这是一个唯一的标识符。保存点名称不区分大小写。保存点名称可以是分隔的标识符。

  • 如果指定的保存点没有点名,或者指定的点名不是有效的标识符或SQL保留字,则会发出运行时SQLCODE-301错误。
  • 如果指定点名称以“SYS”开头的保存点,则会发出运行时SQLCODE-302错误。这些保存点名称是保留的。

保存点名称不区分大小写;因此resetpt, ResetPt“RESETPT”是相同的点名。此重复项是在回滚到保存点期间检测到的,而不是在保存点期间检测到的。当指定具有重复点名的SAVEPOINT语句时, IRIS会递增事务级别计数器,就像点名是唯一的一样。但是,最近的点名称会覆盖保存点名称表中所有先前重复的值。因此,当指定回滚到保存点点名时, IRIS会回滚到具有该点名称的最近建立的保存点,并相应地递减事务级别计数器。但是,如果再次指定回滚到同名的保存点点名,则会生成SQLCODE-375错误,并显示%msg:Cannot Rollback to Unestabled SavePoint‘name’,整个事务将回滚,$TLEVEL计数恢复为0

使用保存点

嵌入式SQL、动态SQL、ODBC和JDBC支持SAVEPOINT语句。在JDBC中,connection.setSavepoint(Pointname)设置一个保存点,connection.roll back(Pointname)回滚到指定的保存点。

如果已建立保存点,请执行以下操作:

  • 回滚到保存点点名将回滚自指定保存点以来所做的工作,删除该保存点和所有中间保存点,并将$TLEVEL事务级别计数器递减删除的保存点数量。如果pointname不存在或已经回滚,此命令将回滚整个事务,将$TLEVEL重置为0,并释放所有锁。
  • 回滚回滚当前事务期间完成的所有工作,回滚自START TRANSACTION以来完成的工作。它将$TLEVEL事务级别计数器重置为零,并释放所有锁。请注意,常规回滚会忽略保存点。
  • COMMIT提交在当前事务期间完成的所有工作。它将$TLEVEL事务级别计数器重置为零,并释放所有锁。请注意,提交操作会忽略保存点。

在事务内发出第二个START TRANSACTION对保存点或$TLEVEL事务级别计数器没有影响。

如果事务操作未能成功完成,则会发出SQLCODE-400错误。

示例

以下嵌入式SQL示例创建具有两个保存点的事务:

ClassMethod Savepoint()
{
	n SQLCODE,%ROWCOUNT,%ROWID
	&sql(
		START TRANSACTION
	)
	&sql(
		DELETE FROM Sample.Person WHERE Name = NULL
	)
	if SQLCODE = 100 { 
		w !,"没有要删除的空名称记录" 
	} elseif SQLCODE '= 0 {
		&sql(ROLLBACK)
	} else {
		w !,%ROWCOUNT," 已删除Null Name记录"
	}
	&sql(
		SAVEPOINT svpt_age1
	)
	&sql(
		DELETE FROM Sample.Person WHERE Age = NULL
	)
	if SQLCODE = 100 { 
		w !,"没有要删除的空年龄记录" 
	} elseif SQLCODE '= 0 {
		&sql(ROLLBACK TO SAVEPOINT svpt_age1)
	} else {
		w !,%ROWCOUNT," 删除空年龄记录"
	}
	&sql(
		SAVEPOINT svpt_age2
	)
	&sql(
		DELETE FROM Sample.Person WHERE Age > 65
	)
	if SQLCODE = 0 { 
		&sql(COMMIT)
	} elseif SQLCODE = 100 { 
		&sql(COMMIT)
	} else {
		&sql(
			ROLLBACK TO SAVEPOINT svpt_age2
		)
		w !,"退休年龄删除失败" 
	}
	&sql(COMMIT)
	&sql(COMMIT)
}

ObjectScript和SQL事务

使用TSTARTTCOMMITObjectScript事务处理与使用SQL语句START transactionSAVEPOINTCOMMIT的SQL事务处理不同,也不兼容。 ObjectScript和InterSystems SQL都提供了对嵌套事务的有限支持。 ObjectScript事务处理不与SQL锁控制变量交互; 特别需要关注的是SQL锁升级变量。 应用程序不应该尝试混合这两种事务处理类型。

如果事务涉及SQL更新语句,则事务应该由SQL START transaction语句启动,并使用SQL COMMIT语句提交。 使用TSTART/TCOMMIT嵌套的方法可以包含在事务中,只要它们不初始化事务。 方法和存储过程通常不应该使用SQL事务控制语句,除非按照设计,它们是事务的主控制器。

0
0 123
文章 姚 鑫 · 十一月 5, 2021 4m read

第六十七章 SQL命令 ROLLBACK

回滚事务。

大纲

ROLLBACK [WORK]

ROLLBACK TO SAVEPOINT pointname

参数

  • pointname - 作为标识符指定的现有保存点的名称。

描述

ROLLBACK语句将回滚事务,撤消已执行但未提交的工作,减少$TLEVEL事务级别计数器,并释放锁。 ROLLBACK用于将数据库恢复到以前的一致状态。

  • ROLLBACK回滚当前事务期间完成的所有工作,将$TLEVEL事务级别计数器重置为0,并释放所有锁。 这会将数据库恢复到事务开始之前的状态。 ROLLBACKROLLBACK WORK是等价的语句; 这两个版本都支持兼容性。
  • ROLLBACK TO SAVEPOINT pointname将回滚自指定保存点以来已完成的所有工作,并按未完成的保存点的数量递减$TLEVEL事务级别计数器。 当所有保存点都被回滚或提交,并且事务级别计数器重置为零时,事务就完成了。 如果指定的保存点不存在,或者已经回滚,ROLLBACK将发出SQLCODE -375错误并回滚整个当前事务。

回滚到SAVEPOINT必须指定一个点名。 如果不这样做,将导致SQLCODE -301错误。

如果事务操作未能成功完成,则会发出SQLCODE -400错误。

不回滚

ROLLBACK操作不影响以下项目:

  • 回滚不会减少默认类的IDKey计数器。 IDKey是由$INCREMENT(或$SEQUENCE)自动生成的,它维护一个独立于SQL事务的计数。
  • 回滚不会逆转缓存查询的创建、修改或清除。 这些操作不被视为事务的一部分。
  • 在事务中发生的DDL操作或调优表操作可以创建并运行临时例程。 这个临时例程与缓存查询一样被处理。 也就是说,临时例程的创建、编译和删除不被视为事务的一部分。 临时例程的执行被认为是事务的一部分。

回滚日志

提示回滚发生的消息和回滚操作中遇到的错误都记录在MGR目录下的Messages.log文件中。 您可以通过“管理门户系统操作”、“系统日志”、“消息日志”选项查看“Messages.Log”。

事务挂起

%SYSTEM.Process类的TransactionsSuspending()方法可用于挂起和恢复系统范围内的所有当前事务。挂起事务会挂起更改的日志记录。因此,如果在当前事务期间发生事务挂起,则ROLLBACK不能回滚在事务挂起期间所做的任何更改;但是,回滚将回滚在事务挂起生效之前或之后在当前事务期间所做的任何更改。

ObjectScript事务命令

ObjectScriptSQL TRANSACTION命令完全兼容且可互换,但以下情况除外:

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

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

ClassMethod ROLLBACK()
{
	&sql(SET TRANSACTION %COMMITMODE EXPLICIT)
	w !,"Set transaction mode, SQLCODE=",SQLCODE
	w !,"Transaction level=",$TLEVEL
	&sql(START TRANSACTION)
	w !,"Start transaction, SQLCODE=",SQLCODE
	w !,"Transaction level=",$TLEVEL
	&sql(SAVEPOINT a)
	w !,"Set Savepoint a, SQLCODE=",SQLCODE
	w !,"Transaction level=",$TLEVEL
	&sql(SAVEPOINT b)
	w !,"Set Savepoint b, SQLCODE=",SQLCODE
	w !,"Transaction level=",$TLEVEL
	&sql(SAVEPOINT c)
	w !,"Set Savepoint c, SQLCODE=",SQLCODE
	w !,"Transaction level=",$TLEVEL
	&sql(ROLLBACK)
	w !,"Rollback transaction, SQLCODE=",SQLCODE
	w !,"Transaction level=",$TLEVEL
}
DHC-APP>d ##class(PHA.TEST.SQLCommand).ROLLBACK()
 
Set transaction mode, SQLCODE=0
Transaction level=0
Start transaction, SQLCODE=0
Transaction level=1
Set Savepoint a, SQLCODE=0
Transaction level=2
Set Savepoint b, SQLCODE=0
Transaction level=3
Set Savepoint c, SQLCODE=0
Transaction level=4
Rollback transaction, SQLCODE=0
Transaction level=0

以下嵌入式SQL示例演示了回滚到保存点名称如何将事务级别($TLEVEL)恢复到紧靠指定保存点之前的级别:

ClassMethod ROLLBACK1()
{
	&sql(SET TRANSACTION %COMMITMODE EXPLICIT)
	w !,"Set transaction mode, SQLCODE=",SQLCODE
	w !,"Transaction level=",$TLEVEL
	&sql(START TRANSACTION)
	w !,"Start transaction, SQLCODE=",SQLCODE
	w !,"Transaction level=",$TLEVEL
	&sql(SAVEPOINT a)
	w !,"Set Savepoint a, SQLCODE=",SQLCODE
	w !,"Transaction level at a=",$TLEVEL
	&sql(SAVEPOINT b)
	w !,"Set Savepoint b, SQLCODE=",SQLCODE
	w !,"Transaction level at b=",$TLEVEL
	&sql(ROLLBACK TO SAVEPOINT b)
	w !,"Rollback to b, SQLCODE=",SQLCODE
	w !,"Rollback transaction level=",$TLEVEL
	&sql(SAVEPOINT c)
	w !,"Set Savepoint c, SQLCODE=",SQLCODE
	w !,"Transaction level at c=",$TLEVEL
	&sql(SAVEPOINT d)
	w !,"Set Savepoint d, SQLCODE=",SQLCODE
	w !,"Transaction level at d=",$TLEVEL
	&sql(COMMIT)
	w !,"Commit transaction, SQLCODE=",SQLCODE
	w !,"Transaction level=",$TLEVEL
}
DHC-APP>d ##class(PHA.TEST.SQLCommand).ROLLBACK1()
 
Set transaction mode, SQLCODE=0
Transaction level=0
Start transaction, SQLCODE=0
Transaction level=1
Set Savepoint a, SQLCODE=0
Transaction level at a=2
Set Savepoint b, SQLCODE=0
Transaction level at b=3
Rollback to b, SQLCODE=0
Rollback transaction level=2
Set Savepoint c, SQLCODE=0
Transaction level at c=3
Set Savepoint d, SQLCODE=0
Transaction level at d=4
Commit transaction, SQLCODE=0
Transaction level=0
0
0 291
文章 姚 鑫 · 十一月 4, 2021 9m read

第六十六章 SQL命令 REVOKE

从用户或角色中删除特权。

大纲

REVOKE admin-privilege FROM grantee 

REVOKE role FROM grantee 

REVOKE [GRANT OPTION FOR] object-privilege
       ON object-list FROM grantee [CASCADE | RESTRICT] [AS grantor] 

REVOKE [GRANT OPTION FOR] SELECT ON CUBE[S] object-list  FROM grantee 

REVOKE column-privilege (column-list) 
      ON table FROM grantee  [CASCADE | RESTRICT]

参数

  • admin-privilege - 管理员级特权或以前授予要撤销的管理员级特权的以逗号分隔的列表。 可用的syspriv选项包括16个对象定义权限和4个数据修改权限。对象定义权限为:%CREATE_FUNCTION%DROP_FUNCTION%CREATE_METHOD%DROP_METHOD%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个对象定义特权。数据修改权限为INSERTUPDATEDELETE操作的%NOCHECK%NOINDEX%NOLOCK%NOTRIGGER权限。
  • grantee - 拥有SQL系统权限、SQL对象权限或角色的一个或多个用户的列表。 有效值是一个以逗号分隔的用户或角色列表,或“*”。 星号(*)指定当前定义的所有没有%all角色的用户。
  • AS grantor - 此子句允许通过指定原始授予者的名称来撤销另一个用户授予的特权。 有效的授予者值是用户名、以逗号分隔的用户名列表或“*”。 星号(*)指定当前定义的所有授予者。 要使用AS授予器子句,必须具有%All角色或%Admin_Secure资源。
  • role - 一个角色或以逗号分隔的角色列表,这些角色的权限将从用户被撤销。
  • object-privilege - 基本级别特权或先前授予要撤销的基本级别特权的逗号分隔列表。 该列表可以包含以下一个或多个:%ALTERDELETESELECTINSERTUPDATEEXECUTEREFERENCES。 要撤销所有特权,可以使用“all [privileges]”“*”作为此参数的值。 注意,您只能从多维数据集撤销SELECT特权,因为这是惟一可授予的多维数据集特权。
  • object-list - 一个以逗号分隔的列表,其中包含一个或多个正在撤销对象特权的表、视图、存储过程或多维数据集。 可以使用SCHEMA关键字指定从指定模式中的所有对象撤销对象特权。 可以使用" * "指定从当前命名空间中的所有对象撤销对象特权。
  • column-privilege - 从一个或多个列列表列出的列撤销基本权限。 可用选项有SELECTINSERTUPDATEREFERENCES
  • column-list - 由一个或多个列名组成的列表,用逗号分隔,用括号括起来。
  • table - 包含列列表列的表或视图的名称。

描述

REVOKE语句撤销允许用户或角色在指定的表、视图、列或其他实体上执行指定任务的权限。 REVOKE还可以撤销用户分配的角色。 REVOKE撤销GRANT命令的操作;

特权只能由授予特权的用户撤消,或者通过CASCADE操作(如下所述)。

可以从指定用户、用户列表或所有用户(使用*语法)撤销角色或特权。

因为REVOKE的准备和执行速度很快,而且通常只运行一次,所以IRIS不会在ODBCJDBC或动态SQL中为REVOKE创建缓存查询。

即使不能执行实际的撤销(例如,指定的特权从未被授予或已经被撤销),REVOKE也会成功地完成。 但是,如果在REVOKE操作期间发生错误,SQLCODE将被设置为负数。

撤销的角色

角色可以通过SQL GRANTREVOKE命令授予或撤销,也可以通过^SECURITY IRIS System SECURITY命令授予或撤销。 可以使用REVOKE命令从某个用户撤消一个角色,也可以从另一个角色撤消一个角色。 不能使用IRIS System Security将角色授予或撤销给其他角色。 特殊变量$ROLES不显示授予角色的角色。

REVOKE可以指定单个角色,也可以指定要撤销的角色列表,以逗号分隔。 REVOKE可以从指定的用户(或角色)、用户(或角色)列表或所有用户(使用*语法)中撤销一个或多个角色。

GRANT命令可以将一个不存在的角色授予用户。 可以使用REVOKE命令从现有用户撤销不存在的角色。 但是,角色名必须使用与授予角色时相同的字母大小写来指定。

如果试图从不存在的用户或角色撤销现有角色, IRIS将发出SQLCODE -118错误。 如果不是超级用户,并且试图撤销一个不拥有且没有ADMIN OPTION的角色,InterSystems IRIS将发出SQLCODE -112错误。

撤销对象权限

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

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

可以使用SCHEMA SCHEMA -name作为对象列表值,以撤销指定模式中当前名称空间中的所有表、视图和存储过程的对象特权。 例如,REVOKE SELECT ON SCHEMA Sample FROM Deborah将撤销该用户对Sample模式中所有对象的SELECT权限。 可以将多个模式指定为逗号分隔的列表; 例如,REVOKE SELECT ON SCHEMA Sample,Cinema FROM Deborah撤销SampleCinema模式中所有对象的SELECT权限。

可以从用户或角色撤消对象特权。 如果从某个角色撤销该权限,则仅通过该角色拥有该权限的用户将不再拥有该权限。 不再拥有特权的用户不能再执行需要该对象特权的现有缓存查询。

REVOKE撤销对象特权时,它将成功完成并将SQLCODE设置为0。 如果REVOKE没有执行实际的撤销(例如,指定的对象权限从未被授予或已经被撤销),它将成功完成,并将SQLCODE设置为100(不再有数据)。 如果在REVOKE操作期间发生错误,它将SQLCODE设置为负数。

多维数据集是不受模式名称限制的SQL标识符。 要指定多维数据集对象列表,必须指定CUBE(或cubes)关键字。 因为多维数据集只能有SELECT权限,所以您只能从多维数据集撤销SELECT权限。

对象权限可以通过以下任意方式撤销:

  • REVOKE command.
  • $SYSTEM.SQL.Security.RevokePrivilege()方法。
  • 通过IRIS系统安全。 转到管理门户,选择系统管理、安全、用户(或系统管理、安全、角色),为所需的用户或角色选择Edit,然后选择SQL表或SQL视图选项卡。 在下拉列表中选择Namespace。 向下滚动到所需的表,然后单击revoke来撤销权限。

可以通过调用%CHECKPRIV命令来确定当前用户是否具有指定的对象特权。 通过调用$SYSTEM.SQL.Security.CheckPrivilege()方法,可以确定指定的用户是否具有指定的表级对象特权。

撤销对象所有者特权

如果从对象的所有者那里撤消对SQL对象的特权,那么所有者仍然隐式地拥有对对象的特权。 为了从对象的所有者完全撤销对象上的所有特权,必须更改对象以指定不同的所有者或没有所有者。

撤销表级和列级特权

REVOKE可用于撤销表级特权或列级特权的授予。 表级特权提供对表中所有列的访问。 列级特权提供对表中每个指定列的访问。 向表中的所有列授予列级特权在功能上等同于授予表级特权。 然而,这两者在功能上并不完全相同。 列级REVOKE只能撤销在列级授予的权限。 不能向表授予表级特权,然后在列级为一个或多个列撤销此特权。 在这种情况下,REVOKE语句对已授予的权限没有影响。

CASCADE 或 RESTRICT

IRIS支持可选的CASCADEESTRICT关键字来指定REVOKE对象特权行为。 如果没有指定关键字,则默认为RESTRICT

可以使用CASCADERESTRICT来指定从一个用户撤销对象特权或列特权是否也会从通过WITH GRANT OPTION接收到该特权的任何其他用户撤销该特权。 CASCADE撤销所有这些关联的特权。 当检测到关联的特权时,RESTRICT(默认值)导致REVOKE失败。 相反,它设置SQLCODE -126错误“REVOKE with RESTRICT failed”

下面的例子展示了这些关键字的使用:

--UserA
   GRANT Select ON MyTable TO UserB WITH GRANT OPTION
--UserB
   GRANT Select ON MyTable TO UserC
--UserA
   REVOKE Select ON MyTable FROM UserB
   -- This REVOKE fails with SQLCODE -126
--UserA
   REVOKE Select ON MyTable FROM UserB CASCADE
   -- This REVOKE succeeds
   -- It revokes this privilege from UserB and UserC

注意,CASCADERESTRICTUserB创建的引用MyTable的视图没有影响。

对缓存查询的影响

当撤销特权或角色时, IRIS将更新系统上所有缓存的查询,以反映特权中的这一更改。 但是,当无法访问某个名称空间时——例如,当连接到数据库服务器的ECP连接关闭时——REVOKE会成功完成,但不会对该名称空间中的缓存查询执行任何操作。 这是因为REVOKE不能更新不可达名称空间中的缓存查询,以撤销缓存查询级别的特权。 没有发出错误。

如果数据库服务器稍后启动,则该名称空间中缓存查询的权限可能不正确。 如果某个角色或特权可能在某个名称空间不可访问时被撤销,建议清除该名称空间中的缓存查询。

IRIS Security

REVOKE命令是一个特权操作。 在嵌入式SQL中使用REVOKE之前,必须以具有适当特权的用户身份登录。 如果不这样做,将导致SQLCODE -99错误(特权冲突)。

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

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

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

示例

下面的嵌入式SQL示例创建两个用户,创建一个角色,并将角色分配给用户。 然后,它使用星号(*)语法从所有用户撤销该角色。 如果用户或角色已经存在,CREATE语句将发出SQLCODE -118错误。 如果用户不存在,GRANTREVOKE语句将发出SQLCODE -118错误。 如果用户存在但角色不存在,则GRANTREVOKE语句发出SQLCODE 100。 如果用户和角色存在,则GRANTREVOKE语句发出SQLCODE 0。 即使已经完成了角色的授予或撤销,如果您试图撤销从未被授予的角色,也是如此。

ClassMethod Revoke()
{
	d $SYSTEM.Security.Login("_SYSTEM","SYS")
	&sql(
		CREATE USER User1 IDENTIFY BY fredpw
	)
	&sql(
		CREATE USER User2 IDENTIFY BY barneypw
	)
	w !,"CREATE USER error code: ",SQLCODE
	&sql(
		CREATE ROLE workerbee
	)
	w !,"CREATE ROLE error code: ",SQLCODE
	&sql(
		GRANT workerbee TO User1,User2
	)
	w !,"GRANT role error code: ",SQLCODE
	&sql(
		REVOKE workerbee FROM *
	)
	w !,"REVOKE role error code: ",SQLCODE
}

在下面的示例中,使用AS授予子句,一个用户(Joe)授予一个特权,另一个用户(John)撤销该特权:

   /* User Joe */
   GRANT SELECT ON Sample.Person TO Michael
   /* User John */
   REVOKE SELECT ON Sample.Person FROM Michael AS Joe

注意,John必须具有%All角色或%Admin_Secure资源。

0
0 89
文章 姚 鑫 · 十一月 3, 2021 2m read

第六十五章 SQL命令 PURGE CACHED QUERIES

删除一个或多个缓存查询。

大纲

PURGE [CACHED] QUERIES

PURGE [CACHED] QUERIES BY AGE n

PURGE [CACHED] QUERIES BY TABLE table-name

PURGE [CACHED] QUERIES BY NAME class-name [, class-name]

参数

  • n - 自上次使用缓存查询以来的整数天数,指定为带引号的字符串。
  • table-name - 存在缓存查询的现有表的名称。 表名可以是限定的(schema.table),也可以是非限定的(table)。 非限定表名接受默认模式名。
  • class-name- 缓存的查询类名称或以逗号分隔的缓存查询类名称列表。 缓存的查询类名是区分大小写的。

描述

PURGE CACHED QUERIES命令删除指定范围内定义的缓存查询:

  • PURGE CACHED QUERIES清除当前名称空间中的所有缓存查询。
  • PURGE CACHED QUERIES BY AGE n清除当前名称空间中最近n天内未使用(准备)的所有缓存查询。 指定n值为0将清除当前名称空间中的所有缓存查询。
  • PURGE CACHED QUERIES BY TABLE TABLE -name清除引用指定表的所有缓存查询。 如果查询引用了几个表,则会生成一个缓存查询,并为每个表列出一个查询。 对这些表中的任何一个发出PURGE CACHED QUERIES BY TABLE将从所有这些表中清除这个缓存的查询。
  • PURGE [CACHED] QUERIES BY NAME class- NAME清除由缓存查询类名称指定的缓存查询。 可以将多个缓存查询指定为逗号分隔的列表。 列出的缓存查询可以引用不同的表,但所有表都必须在当前名称空间内。 现金查询名称是区分大小写的。

CACHED关键字是可选的。

如果指定的类名不存在,或者指定的字母大小写不正确,则跳过该类名,命令继续清除列表中的下一个缓存查询; 对于无效的类名,不会执行任何操作,也不会生成任何错误。 如果指定的表没有任何关联的缓存查询,或者该表不存在,则不执行任何操作,也不会产生错误。

示例

下面的示例清除指定名称的缓存查询:

PURGE CACHED QUERIES BY NAME %sqlcq.USER.cls2

下面的示例清除所有过去两天内没有使用过的缓存查询:

PURGE CACHED QUERIES BY AGE "2"
0
0 159
文章 姚 鑫 · 十一月 2, 2021 5m read

第六十四章 SQL命令 ORDER BY(二)

示例

下面的示例按照RowID的反向顺序对记录进行排序:

SELECT %ID,Name
FROM Sample.Person
ORDER BY %ID DESC

下面两个示例展示了在ORDER BY子句中指定排序列的不同方法。 下面两个查询是等价的; 第一种方法使用列名作为排序项,第二种方法使用列号(选择项列表中项目的序号):

SELECT Name,Age,Home_State
FROM Sample.Person
ORDER BY Home_State,Age DESC
SELECT Name,Age,Home_State
FROM Sample.Person
ORDER BY 3,2 DESC

下面的示例按包含IRIS列表数据的字段进行排序。 因为IRIS列表是一个以格式化字符开始的编码字符串,所以本例使用$LISTTOSTRING来按实际字段数据值排序,而不是按列表元素编码:

SELECT Name,FavoriteColors
FROM Sample.Person
WHERE FavoriteColors IS NOT NULL
ORDER BY $LISTTOSTRING(FavoriteColors)

动态SQL可以使用输入参数为ORDER BY子句提供文字值; 它不能使用输入参数来提供字段名、字段别名、字段号或排序关键字。 下面的动态SQL示例使用输入参数按名字对结果集记录进行排序:

ClassMethod OrderBy()
{
	s myquery = 4
	s myquery(1) = "SELECT TOP ? Name,Age,"
	s myquery(2) = "CURRENT_DATE AS Today"
	s myquery(3) = "FROM Sample.Person WHERE Age > ?"
	s myquery(4) = "ORDER BY $PIECE(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(10,60,2)
	d rset.%Display()
	w !,"%Display SQLCODE=",rset.%SQLCODE
}
DHC-APP>d ##class(PHA.TEST.SQLCommand).OrderBy()
Name    Age     Today
Tweed,Al O.     86      66035
Vanzetti,Alexandra O.   67      66035
Jung,Alexandra S.       94      66035
Gore,Alfred M.  63      66035
Peterson,Alice E.       97      66035
Houseman,Alice R.       63      66035
Willeke,Alvin L.        79      66035
Klingman,Alvin M.       73      66035
Tillem,Andrew Z.        76      66035
Zucherro,Angelo M.      93      66035
 
10 Rows(s) Affected
%Display SQLCODE=100

以下基于游标的嵌入式SQL示例执行相同的操作:

ClassMethod OrderBy1()
{
	s topnum = 10, agemin = 60, firstname = 2
	&sql(
		DECLARE pCursor CURSOR FOR
			SELECT TOP :topnum Name, Age, CURRENT_DATE AS Today
			INTO :name, :years, :today FROM Sample.Person
			WHERE Age > :agemin
			ORDER BY $PIECE(Name, ',', :firstname) )
	&sql(OPEN pCursor)
	q:(SQLCODE'=0)
	for { 
		&sql(FETCH pCursor)
		q:SQLCODE
		w "Name=",name," Age=",years," today=",today,!
	}
	&sql(CLOSE pCursor)
}

DHC-APP>d ##class(PHA.TEST.SQLCommand).OrderBy1()
Name=Tweed,Al O. Age=86 today=66035
Name=Vanzetti,Alexandra O. Age=67 today=66035
Name=Jung,Alexandra S. Age=94 today=66035
Name=Gore,Alfred M. Age=63 today=66035
Name=Peterson,Alice E. Age=97 today=66035
Name=Houseman,Alice R. Age=63 today=66035
Name=Willeke,Alvin L. Age=79 today=66035
Name=Klingman,Alvin M. Age=73 today=66035
Name=Tillem,Andrew Z. Age=76 today=66035
Name=Zucherro,Angelo M. Age=93 today=66035

缓存查询

ORDER BY子句中使用的每个字面值都会生成一个不同的缓存查询。 不对ORDER BY字面值执行字面值替换。 这是因为ORDER BY可以使用整数来指定列号。 更改这个整数将导致一个完全不同的查询。

ORDER BY and CASE

可以使用CASE表达式定义一个通用查询,该查询可以根据提供的主机变量值进行排序。 例如,下面的示例可以根据名称或年龄排序,这取决于var的值:


SELECT Name,Age FROM Sample.Person ORDER BY 
CASE WHEN :var=1 then Name
     WHEN :var=2 then Age END

下面的示例指定了两个CASE表达式。 它按任何情况计算为true进行排序。 如果两种情况都为真,则按国家排序,在国家内按城市排序:

SELECT Country,City FROM Sample.Person ORDER BY 
CASE WHEN :var1=1 then Country END,
     WHEN :var2=1 then City END

ASCDESC参数在CASE END关键字之后指定。

注意,在CASE表达式中必须根据列名指定字段。 在此上下文中,不能指定列别名或列号。

ORDER BY和长全局引用

ORDER BY ordering-item的值不应该超过(大约)400500个字符,这取决于ordering-item的数量和其他因素。 如果一个ordering-item值超过这个最大长度,则运行带有ORDER BY子句的查询可能会导致SQLCODE -400致命错误。 这是因为全局引用的最大编码长度有限制,这是一个固定的 IRIS系统限制。 为了防止这个问题,在作为ORDER BY子句基础的字段的排序规则设置中使用截断长度。 例如,以下查询超过了这个限制:

ClassMethod OrderBy2()
{
	try {
		s myquery = 3
		s myquery(1) = "SELECT LocationCity,NarrativeSummary FROM Aviation.Event "
		s myquery(2) = "WHERE LocationCity %Startswith 'Be' "
		s myquery(3) = "ORDER BY NarrativeSummary"
		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()
		if rset.%SQLCODE = 0 { 
			w !,"Executed query",! 
		} else { 
			s badSQL = ##class(%Exception.SQL).%New(,rset.%SQLCODE,,rset.%Message)
			throw badSQL 
		}
		d rset.%Display()
		w !,"End of data"
		ret
	} catch exp { 
		w "In the CATCH block",!
		if 1 = exp.%IsA("%Exception.SQL") {
			w "SQLCODE: ",exp.Code,!
			w "Message: ",exp.Data,! 
		} else { 
			w "Not an SQL exception",! 
		}
		ret
	}
}

添加一个maxlen截断长度的排序函数允许该程序成功执行:

ClassMethod OrderBy3()
{
	try {
		s myquery = 3
		s myquery(1) = "SELECT LocationCity,NarrativeSummary FROM Aviation.Event "
		s myquery(2) = "WHERE LocationCity %Startswith 'Be' "
		s myquery(3) = "ORDER BY %SqlUpper(NarrativeSummary,400)"
		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()
		if rset.%SQLCODE = 0 { 
			w !,"Executed query",! 
		} else { 
			s badSQL = ##class(%Exception.SQL).%New(,rset.%SQLCODE,,rset.%Message)
			throw badSQL 
		}
		d rset.%Display()
		w !,"End of data"
		ret
	} catch exp { 
		w "In the CATCH block",!
		if 1 = exp.%IsA("%Exception.SQL") {
			w "SQLCODE: ",exp.Code,!
			w "Message: ",exp.Data,! 
		} else { 
			w "Not an SQL exception",! 
		}
		ret
	}
}

IRIS将字段的已整理值截断为400个字符。 请记住,如果字段内容在前400个字符内不是唯一的,则数据可能稍有混乱,但这种情况不太可能发生。 如果出现这种情况,可以尝试通过使用更大的值进行截断来避免显示无序的数据; 但是,如果值太大,将导致<SUBSCRIPT>错误。

还要注意,最大长度是全局引用的整个编码长度,包括全局名称的长度。 它不是简单的下标。

0
0 106