0 关注者 · 478 帖子

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

文章 姚 鑫 · 六月 30, 2022 2m read

第十四章 信号(四)- 多进程任务示例

可根据此思想进行多任务启动查询汇总数据。

原理

  • 利用job机制开启后台进程。
  • 利用loop循环减少进程的数量等于开启进程的数量,判断多进程任务是否完成。
  1. 创建表并插入1000W条数据,统计Moeny字段总金额

  2. 创建demo代码如下。

Class Demo.SemaphoreDemo Extends %RegisteredObject
{

///  Do ##class(Demo.SemaphoreDemo).Sample(5)
ClassMethod Sample(pJobCount = 3)
{
	k ^yx("Amt"),^yxAmt
	
	 /* 1.启动信号 */
    s mSem = ##class(Demo.Sem).%New()
    If ('($isobject(mSem))) 
    {
		q "启动失败"
    }
    
    /* 2. 初始化信号量为0 */
    d mSem.Init(0)
    
    s t1 = $zh
    
    /* 3. 按指定数量,启动后台任务 */
    for i = 1 : 1 : pJobCount
    {
        j ..Task(i)
    }
    
    w "启动job时间:"_ ($zh - t1),!
	
    /* 4. 等待后台任务完成 */
    s tCount = i,tSC = 0
    /* 后台任务完成的判断条件是:减少的信号量=总后台任务数 */
    while (tSC < tCount)
    {
        s tSC = tSC + mSem.Decrement(tCount, 10)
    }
    
    w "完成时间:"_ ($zh - t1),!
    
    
	s moneyAmt = 0

		
	s data = ""
	for {
		s data = $o(^yxAmt(data))
		q:(data = "")
		s moneyAmt = moneyAmt + ^yxAmt(data)
	}
	d mSem.Delete()
    w "总金额" _ moneyAmt,!
    
    w "汇总时间:"_ ($zh - t1),!
	q
}

ClassMethod Task(i)
{
    s tSem = ##class(Demo.Sem).%New()
	s moneyAmt = 0
	for j = (i * 100000) + 1 : 1 : (i + 1) * 100000 {
		s money = $li(^M.YxPersonD(j), 3)
		s moneyAmt = moneyAmt + money
	}
	s ^yxAmt("moneyAmt" _ i) = moneyAmt
	s ^yx("Amt") = $i(^yx("Amt"))
    d tSem.Open(##class(Demo.Sem).Name())
    d tSem.Increment(1)
    d tSem.%Close()
	q moneyAmt
}
}
  1. 创建信号类,定义name和初始化信号方法。
Class Demo.Sem Extends %SYSTEM.Semaphore
{

ClassMethod Name() As %String
{
    q "Semaphore"
}

Method Init(initvalue = 0) As %Status
{
	try {
		If (..Create(..Name(), initvalue)) {
			ret 1
		} else {
			ret 0
		}
	} catch {
		ret 0
	}
}

}
  1. 调用
DHC-APP>Do ##class(Demo.SemaphoreDemo).Sample(5)
启动job时间:.098982
完成时间:.119744
总金额250088825096472
汇总时间:.119774
0
0 93
文章 姚 鑫 · 六月 29, 2022 10m read

第十三章 信号(三)- 示例演示

运行示例

MainProducerConsumer 这三个类中的每一个都有自己的 Run 方法,最好在各自的终端窗口中运行它们。每次运行时,它都会显示它为日志生成的消息。一旦用户通过提供它正在等待的输入来响应 Main 类,MainRun 方法将终止删除信号量。然后,用户可以通过键入命令查看所有进程的合并日志文件的显示

  Do ##class(Semaphore.Util).ShowLog()

注意:以下所有示例都假定所有类都已在“USER”命名空间中编译。

示例 1 - 创建和删除信号量

最简单的例子演示了信号量的创建和销毁。它使用 Semaphore.Main 类。请执行下列操作:

  1. 打开一个终端窗口。
  2. 输入命令——
Do ##class(Semaphore.Main).Run()
  1. 该方法创建信号量。如果成功,将看到消息“输入任何字符以终止运行方法”。按下 Enter 键。该方法显示信号量的初始化值,将其删除,然后退出。
  2. 通过发出命令显示日志文件
Do ##class(Semaphore.Util).ShowLog()

按照上述步骤在终端窗口中显示的消息示例如下

消息示例如下

DHC-APP>Do ##class(Semaphore.Main).Run()
(1) Main Started
(2) New semaphore
(3) Created: "Counter"; Value = 0; Id = 0x0x10000
(4) Semaphore create result: 1
(5) Enter any character to terminate Run method
(6) Final value = 0
(7) Semaphore delete status: 1
(8) Main Finished
(9) Closing Semaphore: Id = 0x10000

日志输出如下所示:
DHC-APP>  Do ##class(Semaphore.Util).ShowLog()
Message Log: Entries = 9
 
#    $JOB   Sender       Message
1)   24888  Main:        Main Started
2)   24888  Counter:     New semaphore
3)   24888  Counter:     Created: "Counter"; Value = 0; Id = 0x0x10000
4)   24888  Main:        Semaphore create result: 1
5)   24888  Main:        Enter any character to terminate Run method
6)   24888  Main:        Final value = 0
7)   24888  Main:        Semaphore delete status: 1
8)   24888  Main:        Main Finished
9)   24888  Counter:     Closing Semaphore: Id = 0x10000

示例 2——创建信号量并连续递增它

这个例子展示了生产者在工作,以及从两个进程中捕获日志消息。

  1. 打开两个单独的终端窗口。称它们为“A”“B”
  2. 在窗口 A 中,键入以下命令,但不要在末尾键入 ENTER 键 -
Do ##class(Semaphore.Main).Run()
  1. 在窗口 B 中,键入以下命令,但同样,不要在命令末尾键入 ENTER 键 -
Do ##class(Semaphore.Producer).Run()
  1. 现在,在窗口 A 中,按 ENTER 键。然后在窗口 B 中,按 ENTER 键。这将启动两个类并行执行。他们各自的消息显示在他们自己的窗口中。
  2. Producer 进程完成后,关闭 B 窗口。
  3. A 窗口中,按 ENTER 键以完成 Main 类。然后,使用以下命令显示日志 -
Do ##class(Semaphore.Util).ShowLog()

对于此示例,以下是输出

A 窗口

DHC-APP>Do ##class(Semaphore.Main).Run()
(1) Main Started
(2) New semaphore
(3) Created: "Counter"; Value = 0; Id = 0x0x20001
(4) Semaphore create result: 1
(5) Enter any character to terminate Run method
(17) Final value = 30
(18) Semaphore delete status: 1
(19) Main Finished
(20) Closing Semaphore: Id = 0x20001

B 窗口

DHC-APP>Do ##class(Semaphore.Producer).Run()
(6) Producer.1 Started
(7) New semaphore
(8) Open Id = 0x20001
(9) Increment 0x20001 = 0 by 5 wait 10 sec
(10) Increment 0x20001 = 5 by 5 wait 4 sec
(11) Increment 0x20001 = 10 by 3 wait 1 sec
(12) Increment 0x20001 = 13 by 5 wait 9 sec
(13) Increment 0x20001 = 18 by 5 wait 8 sec
(14) Increment 0x20001 = 23 by 4 wait 2 sec
(15) Increment 0x20001 = 27 by 1 wait 8 sec
(16) Increment 0x20001 = 28 by 2 wait 5 sec
(21) Producer.1 Finished
(22) Closing Semaphore: Id = 0x20001

日志显示

DHC-APP>Do ##class(Semaphore.Util).ShowLog()
Message Log: Entries = 22
 
#    $JOB   Sender       Message
1)   21080  Main:        Main Started
2)   21080  Counter:     New semaphore
3)   21080  Counter:     Created: "Counter"; Value = 0; Id = 0x0x20001
4)   21080  Main:        Semaphore create result: 1
5)   21080  Main:        Enter any character to terminate Run method
6)   27724  Producer.1:  Producer.1 Started
7)   27724  Counter:     New semaphore
8)   27724  Producer.1:  Open Id = 0x20001
9)   27724  Producer.1:  Increment 0x20001 = 0 by 5 wait 10 sec
10)  27724  Producer.1:  Increment 0x20001 = 5 by 5 wait 4 sec
11)  27724  Producer.1:  Increment 0x20001 = 10 by 3 wait 1 sec
12)  27724  Producer.1:  Increment 0x20001 = 13 by 5 wait 9 sec
13)  27724  Producer.1:  Increment 0x20001 = 18 by 5 wait 8 sec
14)  27724  Producer.1:  Increment 0x20001 = 23 by 4 wait 2 sec
15)  27724  Producer.1:  Increment 0x20001 = 27 by 1 wait 8 sec
16)  27724  Producer.1:  Increment 0x20001 = 28 by 2 wait 5 sec
17)  21080  Main:        Final value = 30
18)  21080  Main:        Semaphore delete status: 1
19)  21080  Main:        Main Finished
20)  21080  Counter:     Closing Semaphore: Id = 0x20001
21)  27724  Producer.1:  Producer.1 Finished
22)  27724  Counter:     Closing Semaphore: Id = 0x20001

示例 3 - 同时运行所有三个进程

此示例显示尝试以连贯的方式增加和减少相同的信号量。它使用所有三个主要类。

  1. 打开三个单独的终端窗口。称它们为“A”“B”“C”
  2. 在窗口 A 中,键入以下命令,但最后不要按 ENTER
Do ##class(Semaphore.Main).Run()
  1. 在窗口 B 中,键入以下命令,但同样,不要在命令末尾按 ENTER 键 -
Do ##class(Semaphore.Producer).Run()
  1. 在窗口 C 中,键入以下命令,但同样,不要在命令末尾按 ENTER 键 -
Do ##class(Semaphore.Consumer).Run()
  1. 从窗口 A 开始,访问每个窗口并键入 ENTER 键。这将启动 Main 类,然后是其他两个类。如前所述,每个进程都会在自己的窗口中显示其日志消息。
  2. ProducerConsumer 进程都完成后,关闭B 窗口和C 窗口。
  3. A 窗口中,按 ENTER 键以完成 Main 类。然后,使用以下命令显示日志
Do ##class(Semaphore.Util).ShowLog()

运行此示例会产生类似于以下内容的输出:

窗口 A

DHC-APP>Do ##class(Semaphore.Main).Run()
(1) Main Started
(2) New semaphore
(3) Created: "Counter"; Value = 0; Id = 0x0x40003
(4) Semaphore create result: 1
(5) Enter any character to terminate Run method
<ENTER>
(64) Final value = 0
(65) Semaphore delete status: 1
(66) Main Finished
(67) Closing Semaphore: Id = 0x40003

窗口B

DHC-APP>Do ##class(Semaphore.Producer).Run()
(6) Producer.1 Started
(7) New semaphore
(8) Open Id = 0x40003
(9) Increment 0x40003 = 0 by 5 wait 8 sec
(20) Increment 0x40003 = 0 by 4 wait 4 sec
(25) Increment 0x40003 = 0 by 3 wait 1 sec
(29) Increment 0x40003 = 0 by 2 wait 10 sec
(36) Increment 0x40003 = 0 by 4 wait 3 sec
(40) Increment 0x40003 = 0 by 5 wait 5 sec
(52) Increment 0x40003 = 0 by 5 wait 6 sec
(58) Increment 0x40003 = 0 by 2 wait 2 sec
(62) Producer.1 Finished
(63) Closing Semaphore: Id = 0x40003

窗口C

DHC-APP>Do ##class(Semaphore.Consumer).Run()
(10) Consumer.1 Started
(11) New semaphore
(12) Consumer: Open Id = 0x40003
(13) Decrement 0x40003 = 5 by 1 wait 10 sec
(14) WaitCompleted: 0x40003; Amt = 1
(15) Granted
(16) Decrement 0x40003 = 4 by 5 wait 2 sec
(17) WaitCompleted: 0x40003; Amt = 4
(18) Granted
(19) Decrement 0x40003 = 0 by 5 wait 8 sec
(21) WaitCompleted: 0x40003; Amt = 4
(22) Granted
(23) Decrement 0x40003 = 0 by 5 wait 6 sec
(25) WaitCompleted: 0x40003; Amt = 3
(26) Granted
(27) Decrement 0x40003 = 0 by 3 wait 1 sec
(28) Timeout
(30) Decrement 0x40003 = 0 by 4 wait 4 sec
(31) WaitCompleted: 0x40003; Amt = 2
(32) Granted
(33) Decrement 0x40003 = 0 by 2 wait 7 sec
(34) Timeout
(35) Decrement 0x40003 = 0 by 4 wait 9 sec
(37) WaitCompleted: 0x40003; Amt = 4
(38) Granted
(39) Decrement 0x40003 = 0 by 2 wait 5 sec
(41) WaitCompleted: 0x40003; Amt = 2
(42) Granted
(43) Decrement 0x40003 = 3 by 1 wait 3 sec
(44) WaitCompleted: 0x40003; Amt = 1
(45) Granted
(46) Decrement 0x40003 = 2 by 2 wait 10 sec
(47) WaitCompleted: 0x40003; Amt = 2
(48) Granted
(49) Decrement 0x40003 = 0 by 2 wait 4 sec
(50) Timeout
(51) Decrement 0x40003 = 0 by 3 wait 4 sec
(53) WaitCompleted: 0x40003; Amt = 5
(54) Granted
(55) Decrement 0x40003 = 0 by 1 wait 1 sec
(56) Timeout
(57) Decrement 0x40003 = 0 by 3 wait 7 sec
(59) WaitCompleted: 0x40003; Amt = 2
(60) Granted
(61) Consumer.1 Finished
 

日志显示

DHC-APP>Do ##class(Semaphore.Util).ShowLog()
Message Log: Entries = 67
 
#    $JOB   Sender       Message
1)   6412   Main:        Main Started
2)   6412   Counter:     New semaphore
3)   6412   Counter:     Created: "Counter"; Value = 0; Id = 0x0x40003
4)   6412   Main:        Semaphore create result: 1
5)   6412   Main:        Enter any character to terminate Run method
6)   22236  Producer.1:  Producer.1 Started
7)   22236  Counter:     New semaphore
8)   22236  Producer.1:  Open Id = 0x40003
9)   22236  Producer.1:  Increment 0x40003 = 0 by 5 wait 8 sec
10)  20224  Consumer.1:  Consumer.1 Started
11)  20224  Counter:     New semaphore
12)  20224  Consumer.1:  Consumer: Open Id = 0x40003
13)  20224  Consumer.1:  Decrement 0x40003 = 5 by 1 wait 10 sec
14)  20224  Counter:     WaitCompleted: 0x40003; Amt = 1
15)  20224  Consumer.1:  Granted
16)  20224  Consumer.1:  Decrement 0x40003 = 4 by 5 wait 2 sec
17)  20224  Counter:     WaitCompleted: 0x40003; Amt = 4
18)  20224  Consumer.1:  Granted
19)  20224  Consumer.1:  Decrement 0x40003 = 0 by 5 wait 8 sec
20)  22236  Producer.1:  Increment 0x40003 = 0 by 4 wait 4 sec
21)  20224  Counter:     WaitCompleted: 0x40003; Amt = 4
22)  20224  Consumer.1:  Granted
23)  20224  Consumer.1:  Decrement 0x40003 = 0 by 5 wait 6 sec
24)  22236  Producer.1:  Increment 0x40003 = 0 by 3 wait 1 sec
25)  20224  Counter:     WaitCompleted: 0x40003; Amt = 3
26)  20224  Consumer.1:  Granted
27)  20224  Consumer.1:  Decrement 0x40003 = 0 by 3 wait 1 sec
28)  20224  Consumer.1:  Timeout
29)  22236  Producer.1:  Increment 0x40003 = 0 by 2 wait 10 sec
30)  20224  Consumer.1:  Decrement 0x40003 = 0 by 4 wait 4 sec
31)  20224  Counter:     WaitCompleted: 0x40003; Amt = 2
32)  20224  Consumer.1:  Granted
33)  20224  Consumer.1:  Decrement 0x40003 = 0 by 2 wait 7 sec
34)  20224  Consumer.1:  Timeout
35)  20224  Consumer.1:  Decrement 0x40003 = 0 by 4 wait 9 sec
36)  22236  Producer.1:  Increment 0x40003 = 0 by 4 wait 3 sec
37)  20224  Counter:     WaitCompleted: 0x40003; Amt = 4
38)  20224  Consumer.1:  Granted
39)  20224  Consumer.1:  Decrement 0x40003 = 0 by 2 wait 5 sec
40)  22236  Producer.1:  Increment 0x40003 = 0 by 5 wait 5 sec
41)  20224  Counter:     WaitCompleted: 0x40003; Amt = 2
42)  20224  Consumer.1:  Granted
43)  20224  Consumer.1:  Decrement 0x40003 = 3 by 1 wait 3 sec
44)  20224  Counter:     WaitCompleted: 0x40003; Amt = 1
45)  20224  Consumer.1:  Granted
46)  20224  Consumer.1:  Decrement 0x40003 = 2 by 2 wait 10 sec
47)  20224  Counter:     WaitCompleted: 0x40003; Amt = 2
48)  20224  Consumer.1:  Granted
49)  20224  Consumer.1:  Decrement 0x40003 = 0 by 2 wait 4 sec
50)  20224  Consumer.1:  Timeout
51)  20224  Consumer.1:  Decrement 0x40003 = 0 by 3 wait 4 sec
52)  22236  Producer.1:  Increment 0x40003 = 0 by 5 wait 6 sec
53)  20224  Counter:     WaitCompleted: 0x40003; Amt = 5
54)  20224  Consumer.1:  Granted
55)  20224  Consumer.1:  Decrement 0x40003 = 0 by 1 wait 1 sec
56)  20224  Consumer.1:  Timeout
57)  20224  Consumer.1:  Decrement 0x40003 = 0 by 3 wait 7 sec
58)  22236  Producer.1:  Increment 0x40003 = 0 by 2 wait 2 sec
59)  20224  Counter:     WaitCompleted: 0x40003; Amt = 2
60)  20224  Consumer.1:  Granted
61)  20224  Consumer.1:  Consumer.1 Finished
62)  22236  Producer.1:  Producer.1 Finished
63)  22236  Counter:     Closing Semaphore: Id = 0x40003
64)  6412   Main:        Final value = 0
65)  6412   Main:        Semaphore delete status: 1
66)  6412   Main:        Main Finished
67)  6412   Counter:     Closing Semaphore: Id = 0x40003

其他变量

此示例的其他变量是可能的。虽然一次只能运行一个 Semaphore.Main,但在其他窗口中执行的生产者或消费者的数量没有限制。鼓励用户在各种场景中尝试不同数量的消费者和生产者,例如

  • 运行三个消费者和一个生产者,这样信号量就会有更大的“竞争”。运气好的话,日志会显示两个或多个消费者发出了减少信号量的请求,并且都成功了,因为信号量值大到足以满足两个请求的部分或全部。
  • 还可以使用这些类来演示删除信号量时其他进程中发生的情况。为此,在 ProducersConsumers 运行时,切换到 Main 类正在运行的窗口,然后按 ENTER。在完成处理过程中,Main 类将删除信号量,ProducerConsumerOREF 将不再有效。下次尝试使用将产生错误。
  • 通过将信号量的名称更改为看起来像全局名称的名称,可以将信号量映射到例如 ECP 系统上的不同实例。
0
0 110
文章 姚 鑫 · 六月 27, 2022 7m read

第十二章 信号(二)- 生产者消费者示例

下面是一系列使用信号量实现生产者/消费者场景的类。 “主”进程初始化信号量并等待用户指示活动已全部完成。生产者在循环中随机增加一个信号量值,更新之间的延迟可变。消费者尝试在随机时间从信号量中删除随机数量,也是在循环中。该示例由 5 个类组成:

  • Main – 初始化环境并等待信号量上的活动完成的类。
  • Counter – 实现信号量本身的类。它记录它的创建以及由于信号量在等待列表中而发生的任何回调。
  • Producer – 一个类,其主要方法增加信号量值。增量是一个随机选择的小整数。完成增量后,该方法会在下一个增量之前延迟一小段随机数秒。
  • Consumer 消费者——这是对生产者的补充。此类的主要方法尝试将信号量减少一个随机选择的小整数。它将递减请求添加到其等待列表中,等待时间也是随机选择的秒数。
  • Util - 这个类有几个方法被示例的其他类使用。几种方法解决了为所有活动维护公共日志的问题;其他人解决了多个消费者和多个生产者的命名问题。

注意:组成这些类的代码特意写得简单。尽可能地,每个语句只完成一个动作。这应该使用户更容易和更直接地修改示例。

Class: Semaphore.Main

此类建立演示环境。它调用实用程序类来初始化日志和名称索引工具。然后它用初始值 0 初始化公共信号量,并等待用户输入一个字符(通常是 ENTER 键),表明实验已经完成。

一旦它接收到用户输入,它就会报告信号量的当前值,尝试删除它,并终止执行。

Class Semaphore.Main Extends %RegisteredObject
{

/// 共享信号量的名称
Parameter ME = "Main";

/// 信号量演示的驱动程序
ClassMethod Run()
{
    // 初始化日志记录全局变量
    d ##class(Semaphore.Util).InitLog()
    d ##class(Semaphore.Util).InitIndex()
    
    s msg = ..#ME _ " 开始"
    d ..Log(msg)

    // 创建和初始化信号量
    s inventory = ##class(Semaphore.Counter).%New()
    if ('($isobject(inventory))) {
        s msg = "%New() of MySem failed"
        d ..Log(msg)
        q
    }
    
    s msg = "信号创建结果: " _ inventory.Init(0)
    d ..Log(msg)
    
    // 等待终止响应
    s msg = "输入任何字符以终止 Run 方法"
    d ..Log(msg)
    
    r *x
    
    // 报告最终值,删除信号量并完成
    s msg = "终值 = " _ inventory.GetValue()
    d ..Log(msg)
    s msg = "信号量删除状态: " _ inventory.Delete()
    d ..Log(msg)
    s msg = ..#ME _ " 结束"
    d ..Log(msg)
    
	q
}

/// 将收到的消息输入到公共日志中
ClassMethod Log(msg As %String) [ Private ]
{
    d ##class(Semaphore.Util).Logger(..#ME, msg)
    q
}

}

Class: Semaphore.Counter

此类实现示例中使用的信号量。根据需要,它是 %SYSTEM.Semaphore 的子类,并提供方法 WaitCompleted 的实现。为了简单起见,初始化信号量的代码也包含在这个类中。还有一个类方法提供此信号量的名称,以允许设置、生产者和消费者类获取它。

Class Semaphore.Counter Extends %SYSTEM.Semaphore
{

ClassMethod Name() As %String
{
    Quit "Counter"
}

/// 直接向日志工具发送消息
Method Log(Msg As %String) [ Private ]
{
    d ##class(Semaphore.Util).Logger(..Name(), Msg)
    q
}

/// 将信号量 id 值作为十六进制字符串返回
Method MyId() As %String
{
   q ("0x" _ $zhex(..SemID))
}

/// 创建实例时调用
Method %OnNew() As %Status
{
    s msg = "新信号"
    d ..Log(msg)
    q $$$OK
}

Method Init(initvalue = 0) As %Status
{
    try {
        if (..Create(..Name(), initvalue)) {
            s msg = "创建 """ _ ..Name() 
                    _ """; 值为 = " _ initvalue
                    _ "; Id = 0x" _ ..MyId()
            d ..Log(msg)
            ret 1
        } else {
			s msg = "信号创建失败: Name = """ _ ..Name() _ """"
			d ..Log(msg)
        	ret 0
        }
    } catch {
        s msg = "捕获信号量故障"
        d ..Log(msg)
        ret 0
    }
}

Method %OnClose() As %Status [ Private ]
{
    s msg = "关闭信号量: Id = " _ ..MyId()
    d ..Log(msg)
    q $$$OK
}

/// 此方法由 WaitMany() 作为回调调用。信号量中存在非零数量或等待超时。
/// 减少的数量作为参数传递给此方法;零,在超时的情况下。
/// 
/// 调用此方法后,信号量将从等待多列表中删除。
/// 需要显式调用 AddToWaitMany 才能将其放回等待列表。
Method WaitCompleted(amt As %Integer)
{
    // 只需报告递减量
    s msg = "WaitCompleted: " _ ..MyId() _ "; Amt = " _ amt
    d ..Log(msg)
	q
}

}

Class: Semaphore.Producer

此类负责获取公共信号量的 OREF。一旦它拥有了OREF,它就会尝试将信号量重复增加一个随机选择的小整数,并在每次增量之间暂停一个小的随机选择间隔。每次增加信号量的尝试都会输入到日志中。

Class Semaphore.Producer Extends %RegisteredObject
{

/// 类名
Parameter MeBase = "Producer";

/// 暂停后以少量随机增加信号量
ClassMethod Run() As %Status
{
    // 建立名称和访问信号量
    s ME = ##class(Semaphore.Util).IndexName(..#MeBase)
    s msg = ME _ " 开始"
    d ..Logger(ME, msg)
    
    s cell = ##class(Semaphore.Counter).%New()
    d cell.Open(##class(Semaphore.Counter).Name())
    
    s msg = "open Id = " _ cell.MyId()
    d ..Logger(ME, msg)
    
    // 在随机时间按随机量增加信号量
    for addcnt = 1 : 1 : 8 {
        s incamt = $random(5) + 1
        s waitsec = $random(10) + 1
        s msg = "increment " _ cell.MyId() 
                _ " = " _ cell.GetValue()
                _ " by " _ incamt
                _ " wait " _ waitsec _ " sec"
        d cell.Increment(incamt)
        d ..Logger(ME, msg)
        h waitsec
    }

    // 结束
    s msg = ME _ " 结束"
    d ..Logger(ME, msg)
    
    q $$$OK
}

/// 将消息传送到日期记录器
ClassMethod Logger(id As %String, msg As %String) [ Private ]
{
    d ##class(Semaphore.Util).Logger(id, msg)
    q
}

}

Class: Semaphore.Consumer

这个类是对 Semaphore.Producer 的补充。它也获得了公共信号量的 OREF,并以与 Producer 类似的方式尝试将信号量重复减少随机选择的数量,并在每次尝试之间随机选择暂停。每次尝试的成功或失败都会写入日志。

Class Semaphore.Consumer Extends %RegisteredObject
{

/// 类名
Parameter MeBase = "Consumer";

/// 暂停后将信号量减少少量随机数
ClassMethod Run() As %Status
{
    // 建立名称和访问信号量
    s ME = ##class(Semaphore.Util).IndexName(..#MeBase)
    s msg = ME _ " 开始"
    d ..Logger(ME, msg)
    
    s cell = ##class(Semaphore.Counter).%New()
    d cell.Open(##class(Semaphore.Counter).Name())
    s msg = "Consumer: Open Id = " _ cell.MyId()
    d ..Logger(ME, msg)
    
    // 以不同的数量和不同的时间反复递减信号量
    for deccnt = 1 : 1 : 15 {
        s decamt = $RANDOM(5) + 1
        s waitsec = $RANDOM(10) + 1
        s msg = "Decrement " _ cell.MyId() 
                _ " = " _ cell.GetValue()
                _ " by " _ decamt
                _ " wait " _ waitsec _ " sec"
        // 在这种情况下,我们等待一个信号量,但我们可以一次等待多个信号量减量(最多 200)
        d cell.AddToWaitMany(decamt)
        d ..Logger(ME, msg)
        s result = ##class(%SYSTEM.Semaphore).WaitMany(waitsec)
        s msg = $select((result > 0) : "授权", 1 : "超时")
        d ..Logger(ME, msg)

    }

    // 结束
    s msg = ME _ " 结束"
    d ..Logger(ME, msg)
    
    q $$$OK
}

/// 将消息传送到日志记录器
ClassMethod Logger(id As %String, msg As %String) [ Private ]
{
    d ##class(Semaphore.Util).Logger(id, msg)
    q
}

}

Class: Semaphore.Util

此类包含解决与此示例相关的两个问题的方法。第一个是保存记录消息所需的结构的初始化,以及归档提交到日志的消息及其后续显示的方法。

第二组方法处理生成编号序列的名称以识别生产者和消费者。这不是严格需要的,因为 $JOB 命令提供的进程 ID 也这样做,但使用更易于阅读的标签更容易。

Class Semaphore.Util Extends %RegisteredObject
{

/// 共享信号量的名称
Parameter ME = "Util";

/// 初始化输出日志
ClassMethod InitLog()
{
    // 初始化日志记录全局
    k ^SemaphoreLog
    s ^SemaphoreLog = 0
    q
}

/// 将收到的消息输入到全局以进行日志记录
/// 
ClassMethod Logger(sender As %String, msg As %String)
{
    s inx = $i(^SemaphoreLog)
    s ^SemaphoreLog(inx, 0) = $job
    s ^SemaphoreLog(inx, 1) = sender
    s ^SemaphoreLog(inx, 2) = msg
    w "(", ^SemaphoreLog, ") ", msg, !
    q
}

/// 显示日志中的消息
ClassMethod ShowLog()
{
    s msgcnt = $g(^SemaphoreLog, 0)
    w "消息日志:条目 = ", msgcnt, !, !
    w "#", ?5, "$Job", ?12, "Sender", ?25, "Message", !
    
    for i = 1 : 1 : msgcnt {
        s job = ^SemaphoreLog(i, 0)
        s sender = ^SemaphoreLog(i, 1)
        s msg = ^SemaphoreLog(i, 2)
        w i, ")", ?5, job, ?12, sender, ":", ?25, msg, !
    }
    q
}

/// 初始化名称索引
ClassMethod InitIndex()
{
    k ^SemaphoreNames
	q
}

/// 初始化名称索引
ClassMethod IndexName(name As %String) As %String
{
    if ($d(^SemaphoreNames(name)) = 0) {
        s ^SemaphoreNames(name) = 0
    }
    s index =  $i(^SemaphoreNames(name))
    q (name _ "." _ index)
}

}

0
0 124
文章 姚 鑫 · 六月 26, 2022 7m read

第十一章 信号(一) - 概念

背景

维基百科对信号量有这样的定义:“在计算机科学中,特别是在操作系统中,信号量是一种变量或抽象数据类型,用于控制多个进程在并行编程或多用户环境中对公共资源的访问。”信号量不同于互斥体(或锁)。互斥锁最常用于管理竞争进程对单个资源的访问。当一个资源有多个相同的副本并且这些副本中的每一个都可以由单独的进程同时使用时,就会使用信号量。

考虑一个办公用品商店。它可能有几台复印机供其客户使用,但每台复印机一次只能由一个客户使用。为了控制这一点,有一组键可以启用机器并记录使用情况。当客户想要复印文件时,他们向职员索取钥匙,使用机器,然后归还钥匙,并支付使用费。如果所有机器都在使用,客户必须等到钥匙归还。保存键的位置用作信号量。

该示例可以进一步推广到包括不同类型的复印机,也许可以通过它们可以制作的副本的大小来区分。在这种情况下,将有多个信号量,如果复制者在复制的大小上有任何重叠,那么希望复制共同大小的客户将有两个资源可供提取。

介绍

信号量是共享对象,用于在进程之间提供快速、高效的通信。每个信号量都是类 %SYSTEM.Semaphore 的一个实例。信号量可以建模为一个共享变量,它包含一个 64 位非负整数。信号量上的操作在共享它的所有进程中以同步的方式更改变量的值。按照惯例,值的变化会在共享信号量的进程之间传递信息。

尽管信号量和锁似乎有很多共同点,但使用信号量还是有一些优势的。其中最主要的事实是,当信号量被授予时,信号量会导致发送通知。因此使用信号量的进程不需要花费处理器周期或复杂的应用程序逻辑轮询锁以检查它是否已被释放。此外,信号量在 ECP 连接上透明地工作,并且比在相同情况下检查锁所需的交换效率更高。

信号概述

信号名称

信号量由它们的名称标识,这些名称在创建信号量时作为 ObjectScript 字符串提供。为信号量指定的名称应符合局部和全局变量的规则。信号量名称只是为了将其与所有其他现有信号量区分开来。有效名称字符串的示例有:SlotsOpen, J(3), ^pendingRequest(""j"")

通常,信号量存储在创建该信号量的实例上,并且对该实例上的所有进程可见。但是,当信号量名称看起来像全局变量的名称时,信号量存储在映射全局变量(包括下标)的系统上。这允许这样的信号量对在ECP系统的实例上运行的所有进程可见。

注意:信号量的名称是一个字符串,但在运行时可以由诸如"^" _ BaseName _ "(" _ (1 + 2) _ ")"这样的表达式构造。如果BaseName包含字符串“prters”,则信号量名称为^prters(3)

信号量值

信号量值存储为63位无符号整数,因此信号量值始终大于或等于零。

最大的63位无符号整数是9,223,372,036,854,775,807((2**63)-1)。信号量不能超过此值递增,也不能递减到零以下。

信号量实例和变量

信号量是派生自 %SYSTEM.Semaphore 的类的实例。一旦信号量被创建和初始化,它的 OREF 通常存储在一个 Objectscript 变量中,因此它可以用于其他操作,作为参数传递,并最终被删除。尽管包含对信号量的引用的变量的名称不必与信号量的名称相对应,但良好的编程习惯表明存在某种关系。

像所有对非持久数据的对象引用一样,当最后一个信号量引用被回收时,底层信号量也被删除。

信号量操作

基础

信号量操作可以分为两大类:直接操作信号量的操作和等待其他进程操作信号量的操作。第一组包括:

  • Create – 创建一个新的信号量实例并初始化它以供使用
  • Open ——访问并初始化现有的信号量
  • Delete - 使任何知道信号量的进程无法使用它
  • Increment - 将指定量添加到信号量的值
  • Decrement ——如果信号量值为零,则操作等待它变为正值。当信号量为正时,从信号量中减去递减量(或信号量的值,以较小者为准)。
  • GetValue – 返回信号量的当前值
  • SetValue – 将信号量的当前值设置为提供的非负值

管理多个信号量

第二组操作涉及将信号量列表作为一个组进行管理,并等待每个 on 待处理的操作完成:

  • AddToWaitMany – 将给定的信号量操作添加到等待列表
  • RemoveFromWaitMany – 从等待列表中删除指定的信号量操作
  • WaitMany – 等待等待列表中的所有信号量完成各自的操作。此操作可能会超时。

等候队列

每个使用 WaitMany 协调多个信号量的进程将信号量减量操作请求保存在一个内部列表中。对列表的操作处理如下:

  • 当调用AddToWaitMany方法在列表中放置递减操作时,系统会尝试在此时执行递减。如果信号量值非零,则递减成功。减去的量是信号量的值和请求的量中较小的一个。任何大于信号量的值的请求都会被忘记。
  • 如果在将请求添加到列表时信号量的值为零,则不执行任何操作,并且该请求被视为挂起。在未来的某个时间,如果目标信号量变为非零,将选择其中一个进程,其操作引用该信号量并执行其递减操作。如果该操作的结果是信号量仍然具有非零值,则将重复该过程,直到没有进一步的请求,或者信号量的值变为零。
  • 当进程调用WaitMany方法时,会检查等待列表中的每个操作。对于满足的请求,调用目标信号量的WaitComplete方法,然后从等待列表中删除该请求。当它处理完所有满足的请求时,它向调用方返回此类请求的数量;如果超过等待超时,则返回零。待处理和未满足的请求仍在等待名单上。
  • 由于信号量的异步性质,在调用WaitMany期间,等待列表上的某个挂起请求可能会得到满足。在这次WaitMany调用期间是否计入这个现已满足的请求,或者它是否将计入后续调用,目前尚不确定。
  • 将OREF保存到信号量的进程也可能删除它。在这种情况下,会发生什么取决于请求的操作是否得到满足。
    • 如果信号量是已满足的操作的目标,则将该请求标记为已将信号量递减零。无法调用WaitComplete方法,因为信号量不存在,但该请求被视为已在WaitMany返回的值中得到满足。
    • 如果请求仍处于挂起状态,则只需将其从等待列表中删除。

回调

信号量的实例继承一个抽象方法WaitComplete,用户需要实现该方法。在处理等待列表上满足的请求时,WaitMany对等待列表中的每个信号量调用WaitComplete方法,并将信号量递减的数量作为参数传递。当WaitComplete返回时,WaitMany将从等待列表中删除该请求。

其他考虑事项

同一等待列表上有多个递减请求

在同一等待列表中多次请求递减同一信号量并不是错误的。添加的请求处理方式如下:

  • 如果第一个请求不被满足,则将第二个请求的减少量与第一个请求的减少量相加。
  • 如果已满足第一个请求(全部或部分),则正常处理第二个请求。也就是说,如果信号量值非零,则完全或部分满足递减请求。但是,减去的实际数量与第一个请求获得的数量相加。

在调用WaitMany之前,不会通过回调报告递减量,因此对同一信号量的多个请求看起来就像是发出了一个组合请求。这会导致以下情况:

  1. 信号量A设置为0

  2. A 的减量请求 4

  3. A 的减量请求为 1;新的减量 = 5

  4. 信号量 A 设置为 4

  5. 请求满足; 4 授予。

  6. call waitmany ;等待完成A报告。

  7. 信号量A设置为1

  8. A的减量请求,共3个。

  9. 请求满足; 1 授予。

  10. A 的减量请求 4

  11. call WaitMany; WaitCompleted for A 报告 1。

  12. 信号量 A 设置为 1

  13. A 的减量请求为 3

  14. 请求满足; 1 授予。

  15. A 的减量请求 4

  16. 信号量 A 设置为 5

  17. 请求满足; 4 授予。

  18. 调用WaitManyWaitCompleted for A 报告 5;信号量 A= 1

信号量删除

信号量没有所有者,并且它们不像对象实例那样被引用计数。任何可以打开信号量的进程都可以将其删除。

当一个信号量被删除时,

  1. 如果任何等待列表中存在该信号量的挂起递减,则调用 WaitCompleted 回调,递减值为零。
  2. 它将从映射的系统(本地或远程)中删除。
  3. 任何其他进程进一步尝试访问它都会失败,并出现 <INVALID SEMAPHORE>错误。

工作终止和等待列表

进程终止时,它的等待列表被释放。保留在等待列表中但尚未由 WaitMany 处理的任何满足的递减请求都将被清除。它们各自的递减量不会添加回它们递减的信号量。等待列表中的任何未满足的请求都将被简单地删除。

信号量和 ECP

对于 ECP 系统上的信号量,信号量上的操作按照请求到达持有信号量的系统的顺序进行排序。每个操作都保证在下一个操作开始之前完成。远程信号量保证以下条件:

  • 信号量增加和减少将在 SETKILL 之后发生。
  • 当一个信号量被 SET、递增或递减时,ECP 数据缓存与服务器上的后续 SET、递增或递减是一致的。

因为信号量不是持久的,所以在服务中断的情况下,ECP 系统上跨服务器的未决信号量操作是不可恢复的。由于服务器或网络故障导致 ECP 中断后,应用服务器上的信号量将被删除,数据服务器上的待处理请求也将被删除。应用程序有责任检测这种情况并在正确状态下重新创建所需的信号量。

0
0 158
文章 姚 鑫 · 六月 25, 2022 2m read

第十章 设置结构化日志记录(二)

注:IRIS有,Cache无。

启用结构化日志记录

^LOGDMN 例程允许管理结构化日志记录;还有一个基于类的 API,将在下一节中介绍。

要使用 ^LOGDMN 启用结构化日志记录:

  1. 打开终端并输入以下命令:
set $namespace="%sys"
do ^LOGDMN

这将启动一个带有以下提示的例程:

 
1) Enable logging
2) Disable logging
3) Display configuration
4) Edit configuration
5) Set default configuration
6) Display logging status
7) Start logging
8) Stop logging
9) Restart logging
 
LOGDMN option?
  1. 4 以便可以指定配置详细信息。然后,该例程会提示输入以下项目:

a. 最低日志级别,以下之一:

  • -2 — 详细的调试消息(例如十六进制转储)。
  • -1 — 不太详细的调试消息。
  • 0 — 信息性消息,包括所有审计事件。
  • 1(默认值)— 警告,表示可能需要注意但未中断操作的问题。
  • 2 — 严重错误,表明问题已中断操作。
  • 3 — 致命错误,表示问题导致系统无法运行。

b. 管道命令,它指定系统将结构化日志发送到哪里。输入以下形式的响应:

irislogd -f c:/myfilename.log

但将 c:/myfilename.log 替换为目标日志文件的完全限定路径名。在此命令中,irislogd 是 可执行文件的名称,它将接收日志数据并将其写入指定文件(通过 -f 选项)。

对于管道命令,最简单的选择是使用此处提到的可执行文件 (irislogd.exe),但可以替换为不同的目标。

c. 发送到管道的数据格式。指定 NVP(默认)或 JSON。选项 NVP 发送由名称-值对组成的数据,以空格分隔。选项 JSONJSON 输出中发送数据。

d. 对管道命令的连续调用之间的间隔(以秒为单位)。默认值为 10 秒。

当例程再次显示主提示时(LOGDMN 选项?),按 1 启用日志记录。

7 开始记录。

用于结构化日志记录的基于类的 API

要管理结构化日志记录,可以使用 %SYS 命名空间中的 SYS.LogDmn 类,而不是使用 ^LOGDMN 例程。

irislogd 的其他选项

ArgumentPurpose
-d发出诊断和错误消息
-eerrfilename 将错误和诊断消息写入给定文件。
-flogfilename 将日志消息写入给定文件。
-hhostname 在结构化日志文件中包含给定的主机名。
-iirisinstance 在结构化日志文件中包含给定的实例名称。
-s将日志消息写入 Unix® syslog 工具(仅限 Unix®

此外,可以将输出写入标准输出。要在 Unix 上执行,请同时省略 -f-s 参数。要在 Windows 上执行此操作,请省略 -s 参数。

0
0 121
文章 姚 鑫 · 六月 24, 2022 4m read

第九章 设置结构化日志记录(一)

IRIS 支持结构化日志记录。

创建多个日志,每个日志用于不同的目的。从以前的产品迁移过来的客户可以像过去一样利用这些日志,但现在还可以将所有日志信息导入一个单一的、中央的、机器可读的日志文件——结构化日志。然后可以将此文件与第三方分析工具一起使用。

本文概述了结构化日志中的信息,展示了日志示例,并描述了如何启用结构化日志记录。

结构化日志中可用的信息

当启用结构化日志记录时,系统会将相同的数据写入结构化日志,它也会写入其他日志(无论哪个)。例如,系统将相同的行写入messages.log 和结构化日志。

启用结构化日志记录后,结构化日志包含以下所有信息:

  • 写入messages.log 的信息。这包括需要注意的警报、有关系统启动和关闭的信息、有关日志文件和 WIJ 文件的高级信息、有关配置更改 (CPF) 的信息以及与许可相关的信息。
  • 写入审计数据库的信息。详细信息取决于正在审核的事件。

示例输出

本部分显示结构化日志记录实用程序的示例输出,用于名称/值对格式和 JSON 格式。

名称/值对

以下输出使用格式选项 NVP(名称/值对)。此示例经过编辑以用于显示目的;在实际输出中,每个条目只占一行,条目之间没有空行。

when="2019-08-01 18:43:02.216" pid=8240 level=SEVERE event=Utility.Event 
text="Previous system shutdown was abnormal, system forced down or crashed"

when="2019-08-01 18:43:05.290" pid=8240 level=SEVERE event=Utility.Event 
text="LMF Error: No valid license key. Local key file not found and LicenseID not defined."

when="2019-08-01 18:43:05.493" pid=8240 level=WARNING event=Generic.Event 
text="Warning: Alternate and primary journal directories are the same"

when="2019-08-01 18:46:10.493" pid=11948 level=WARNING event=System.Monitor 
text="CPUusage Warning: CPUusage = 79 ( Warnvalue is 75)."

在这种格式中,文件中的每一行都包含一组由空格分隔的名称/值对。每个名称/值对的格式为 name=value,如果 value 包含空格字符,则 value 用括号括起来。日志文件中的行包含以下部分或全部名称/值对:

NameValue
host运行 ^LOGDMN 的主机的名称(如果在管道命令中提供)。
instance运行 ^LOGDMN 的实例的名称(如果在管道命令中提供)。
when始终包括在内。条目的时间戳,格式为 yyyy-mm-dd hh:mm:ss.sss
pid始终包括在内。生成条目的进程的 ID
level始终包括在内。此条目的日志级别。这具有以下值之一: - DEBUG2 用于详细的调试消息(例如十六进制转储)。 - DEBUG 用于不太详细的调试消息。 - INFO 用于信息性消息,包括所有审计事件。 - WARNING 用于指示可能需要注意但未中断操作的问题。 - SEVERE 用于严重错误,表示操作中断的问题。 -FATAL 用于致命错误,表示问题导致系统无法运行。
event始终包括在内。生成条目的代码的标识符,通常是类名。
text始终包括在内。解释条目的描述性字符串。
source作为审计事件源的组件。对于组件,这始终是 %System。当应用程序代码写入事件日志时,source 指示应用程序代码中的组件。
type对审计事件的信息进行分类。
group审计事件的组(如果有)。
namespace生成条目的命名空间。这对于检查特定于名称空间的行为很有用,例如应用程序错误和互操作性产品的活动。

JSON

以下输出使用格式选项 JSON。此示例经过编辑以用于显示目的;在实际输出中,每个条目只占一行,条目之间没有空行。

{ "when": "2019-08-07 14:11:04.904", "pid": "8540", "level": "SEVERE", "event": "Utility.Event", 
"text": "Previous system shutdown was abnormal, system forced down or crashed"}

{ "when": "2019-08-07 14:11:08.155", "pid": "8540", "level": "SEVERE", "event": "Utility.Event", 
"text": "LMF Error: No valid license key. Local key file not found and LicenseID not defined."}

{ "when": "2019-08-07 14:11:08.311", "pid": "8540", "level": "WARNING", "event": "Generic.Event", 
"text": "Warning: Alternate and primary journal directories are the same"}

{ "when": "2019-08-07 14:16:13.843", "pid": "10816", "level": "WARNING", "event": "System.Monitor", 
"text": "CPUusage Warning: CPUusage = 84 ( Warnvalue is 75)."}

在这种格式中,文件中的每一行都是一个带有一组属性的 JSON 对象。属性的名称(以及属性中包含的值)与上一节中为名称/值对列出的名称相同。

0
0 145
文章 姚 鑫 · 六月 23, 2022 3m read

第八章 操作位和位串(四)

操作以整数形式实现的位串

设置位

要创建一个存储为整数的新位串,请对每个位求和 2 的幂:

set bitint = (2**2) + (2**5) + (2**10)
 
write bitint
1060

要将现有位串中的位设置为 1,请使用 $zboolean 函数(逻辑 OR)的选项7(arg1 ! arg2)

set bitint = $zboolean(bitint, 2**4, 7)

write bitint
1076

要将现有位串中的位设置为 0,请使用 $zboolean 函数的选项 2(arg1 & ~arg2)


set bitint = $zboolean(bitint, 2**4, 2)

write bitint
1060

要在现有位串中切换位,请使用 $zboolean 函数(逻辑 XOR)的选项 6(arg1 ^ arg2)

set bitint = $zboolean(bitint, 2**4, 6)

write bitint
1076
set bitint = $zboolean(bitint, 2**4, 6)

write bitint
1060

测试位是否已设置

要将位字符串显示为整数,可以使用如下方法,该方法循环位并使用 $zboolean 函数:

Class Util.BitUtil Extends %RegisteredObject
{

/// w ##class(Util.BitUtil).LogicalToDisplay(1)
ClassMethod LogicalToDisplay(bitint As %Integer)
{
	s str = ""
	for i = 0 : 1 { 
		q:((2 ** i) > bitint)
		if $zboolean(bitint, 2 ** i, 1) {
			s str = str _ 1
		} else {
			s str = str _ 0
		}
	}
	q str
}

}

DHC-APP>w ##class(Util.BitUtil).LogicalToDisplay(101000)
00010001010100011

查找设置位

此方法使用$Zlog函数将位字符串中的哪些位设置为整数,该函数返回以10为底的对数值。该方法删除越来越小的位串块,直到没有剩余:

/// w ##class(Util.BitUtil).FindSetBits(2)
ClassMethod FindSetBits(bitint As %Integer)
{
	s bits = "" 
	while (bitint '= 0) { 
		s bit = $zlog(bitint) \ $zlog(2)
		s bits = bit _ " " _ bits
		s bitint = bitint - (2 ** bit) 
	} 
	q bits
}

DHC-APP>w ##class(Util.BitUtil).FindSetBits(3)
0 1

执行按位算术

使用 $zboolean 函数对存储为整数的位串执行按位逻辑运算。

对于此示例,假设有两个位串 ab,存储为整数,以及一个 LogicalToDisplay() 方法,如 Display Bits 中定义的,用于显示这些位。

do ##class(User.BitInt).LogicalToDisplay(a)
100110111
do ##class(User.BitInt).LogicalToDisplay(b)
001000101

使用 $zboolean 函数的选项 7 对位执行逻辑 OR

set c = $zboolean(a, b, 7)

do ##class(User.BitInt).LogicalToDisplay(c)
101110111

使用 $zboolean 函数的选项 1 对位执行逻辑与:

set d = $zboolean(a, b, 1)

do ##class(User.BitInt).LogicalToDisplay(d)
000000101

转换为常规位串

要将存储为整数的位串转换为常规位串,请使用 $factor 函数。对于此示例,假设有一个位串为整数的 bitint 和一个 FindSetBits() 方法,如 Find Set Bits 中所定义,以显示设置了哪些位。

do ##class(User.BitInt).FindSetBits(bitint)
2 5 10
set bitstring = $factor(bitint)

zwrite bitstring 
bitstring=$zwc(128,4)_$c(36,4,0,0)/*$bit(3,6,11)*/

请注意,常规位串中的位似乎向右移动了一位,因为位串没有位 0。位串中的第一位是位 1

0
0 131
文章 姚 鑫 · 六月 22, 2022 4m read

第七章 操作位和位串(三)

操作位串

要创建新的位串,请使用 $bit 函数将所需位设置为 1

kill bitstring
 
set $bit(bitstring, 3) = 1
 
set $bit(bitstring, 6) = 1
 
set $bit(bitstring, 11) = 1

使用 $bit 将现有位串中的位设置为 1

set $bit(bitstring, 5) = 1

使用 $bit 将现有位串中的位设置为 0

set $bit(bitstring, 5) = 0

由于位串中的第一位是位 1,因此尝试设置位 0 会返回错误:

set $bit(bitstring, 0) = 1
 
SET $BIT(bitstring, 0) = 1
^
<VALUE OUT OF RANGE>

测试位是否已设置

要测试是否在现有位串中设置了位,还可以使用 $bit 函数:

write $bit(bitstring, 6)
1
write $bit(bitstring, 5)
0

如果测试未明确设置的位,则 $bit 返回 0:

write $bit(bitstring, 4)
0
write $bit(bitstring, 55)
0

显示位

要显示位串中的位,请使用 $bitcount 函数获取位串中位的计数,然后遍历位:

for i=1:1:$bitcount(bitstring) {write $bit(bitstring, i)}
00100100001

还可以使用 $bitcount 来计算位串中 10 的数量:

write $bitcount(bitstring, 1)
3
write $bitcount(bitstring, 0)
8

查找设置位

要查找在位串中设置了哪些位,请使用 $bitfind 函数,该函数返回指定值的下一位的位置,从位串中的给定位置开始:

Class User.BitStr
{

ClassMethod FindSetBits(bitstring As %String)
{
   set bit = 0 
   for {
      set bit = $bitfind(bitstring, 1, bit) 
      quit:'bit  
      write bit, " " 
      set bit = bit + 1 
   }
}

}

此方法搜索字符串并在 $bitfind 返回 0 时退出,表示没有找到更多匹配项。

do ##class(User.BitStr).FindSetBits(bitstring)
3 6 11

在测试位串的比较时要非常小心。

例如,可以有两个位串 b1b2,它们具有相同的位集:

do ##class(User.BitStr).FindSetBits(b1)
3 6 11
do ##class(User.BitStr).FindSetBits(b2)
3 6 11

然而,如果你比较它们,你会发现它们实际上并不相等:

write b1 = b2
0

如果你使用 zwrite,你可以看到这两个比特环的内部表示是不同的:

zwrite b1
b1=$zwc(405,2,2,5,10)/*$bit(3,6,11)*/
 
zwrite b2
b2=$zwc(404,2,2,5,10)/*$bit(3,6,11)*/

在这种情况下,b2 将第 12 位设置为 0

for i=1:1:$bitcount(b1) {write $bit(b1, i)}
00100100001
USER>for i=1:1:$bitcount(b2) {write $bit(b2, i)}
001001000010

此外,还可能存在其他内部表示,例如由 $factor 创建的表示,它将整数转换为位串:

set b3 = $factor(1060)
 
zwrite b3
b3=$zwc(128,4)_$c(36,4,0,0)/*$bit(3,6,11)*/
 
for i=1:1:$bitcount(b3) {write $bit(b3, i)}
00100100001000000000000000000000

要比较可能具有不同内部表示的两个位串,可以使用 $bitlogic 函数直接比较设置的位,如执行按位算术中所述。

执行按位算术

使用 $bitlogic 函数对位串执行按位逻辑运算。

在此示例中,有两个位串 ab

for i=1:1:$bitcount(a) {write $bit(a, i)}
100110111
for i=1:1:$bitcount(b) {write $bit(b, i)}
001000101

使用 $bitlogic 函数对位执行逻辑或:

set c = $bitlogic(a|b)

for i=1:1:$bitcount(c) {write $bit(c, i)}
101110111

使用 $bitlogic 函数对位执行逻辑与:

set d = $bitlogic(a&b)

for i=1:1:$bitcount(d) {write $bit(d, i)}
000000101

此示例说明如何使用 $bitlogic 函数执行逻辑 XOR 以测试两个位串 b1b3 是否具有相同的设置位,而不管内部表示如何:

zwrite b1
b1=$zwc(405,2,2,5,10)/*$bit(3,6,11)*/

zwrite b3
b3=$zwc(128,4)_$c(36,4,0,0)/*$bit(3,6,11)*/

write $bitcount($bitlogic(b1^b3),1)
0

逻辑异或可以快速表明两个位串的设置位没有差异。

转换为位串整数

要将常规位串转换为存储为整数的位串,请使用 $bitfind 函数查找设置的位并将它们的 2 次方相加。请记住将结果除以 2 以将位向左移动,因为常规位串中的位 1 对应于位串中的位 0

ClassMethod BitstringToInt(bitstring As %String)
{
   set bitint = 0
   set bit = 0 
   for {
      set bit = $bitfind(bitstring, 1, bit) 
      quit:'bit  
      set bitint = bitint + (2**bit) 
      set bit = bit + 1 
   }
   return bitint/2
}

将位串转换为位串为整数:

for i=1:1:$bitcount(bitstring) {write $bit(bitstring, i)}
00100100001
set bitint = ##class(User.BitStr).BitstringToInt(bitstring)
 
write bitint
1060
0
0 112
文章 姚 鑫 · 六月 21, 2022 3m read

第六章 操作位和位串(二)

将位序列存储为整数

如果要将一系列布尔参数传递给方法,一种常见的方法是将它们作为编码为单个整数的位序列传递。

例如,Security.System.ExportAll() 方法用于从 IRIS 实例中导出安全设置。如果查看此方法的类引用,将看到它的定义如下:

classmethod ExportAll(FileName As %String = "SecurityExport.xml", 
ByRef NumExported As %String, Flags As %Integer = -1) as %Status

第三个参数 Flags 是一个整数,其中每个位代表一种可以导出的安全记录。

Flags - What type of records to export to the file, -1 = ALL
Bit 0 - System
Bit 1 - Events
Bit 2 - Services
Bit 4 - Resources
Bit 5 - Roles
Bit 6 - Users
Bit 7 - Applications
Bit 8 - SSL Configs
Bit 9 - PhoneProvider
Bit 10 - X509Credential
Bit 11 - OpenAMIdentityService
Bit 12 - SQL privileges
Bit 13 - X509Users
Bit 14 - DocDBs
Bit 15 - LDAPConfig
Bit 16 - KMIPServer

存储为整数的位串中的位 0 表示 20,位 1 表示 2^1,依此类推。如果要导出与位 5678101113 对应的类型的安全记录,可以通过将 Flags 设置为 2^5 +2^6 + 2^7+ 2^8 + 2^10 + 2^11 + 2^13 = 11744 来完成.

在 ObjectScript 中,这可能看起来像:

set flags = (2**5) + (2**6) + (2**7) + (2**8) + (2**10) + (2**11) + (2**13)
set status = ##class(Security.System).ExportAll("SecurityExport.xml", .numExported, flags)

一些 API 定义了宏以使代码更易于阅读。一种这样的情况是在 DataMove 实用程序中,其中 DataMove 对象是使用 DataMove.Data.CreateFromMapEdits() 方法创建的。无需过多介绍细节,该方法在类参考中定义如下:

classmethod CreateFromMapEdits(Name As %String, ByRef Properties As %String, 
ByRef Warnings As %String, ByRef Errors As %String) as %Status

它有以下参数:

  • Name - 要创建的 DataMove 对象的名称。
  • Properties - 用于创建对象的属性数组。可以选择指定以下属性。
  • Properties("Description")- 数据移动操作的描述,默认 =""
  • Properties("Flags") - 描述操作的标志,默认 = 0
  • Properties("LogFile")- 日志文件的目录和文件名,默认 = \iris\mgr\DataMovename.log

为了使 Properties("Flags") 更容易定义,这些宏可供使用:

控制数据移动的位标志。

  • $$$BitNoSrcJournal - 允许不记录源数据库
  • $$$BitNoWorkerJobs - 在复制数据期间不要使用“worker”作业
  • $$$BitBatchMode - 在“批处理”模式下运行复制作业
  • $$$BitCheckActivate - 在 Activate() 期间调用 $$CheckActivate^ZDATAMOVE()

这些宏定义为特定位的计算值,允许设置正确的位,而无需记住哪个位代表哪个标志:

#;DataMove 标志属性的定义

  • #define BitNoSrcJournal 1
  • #define BitNoWorkerJobs 512
  • #define BitBatchMode 2048
  • #define BitCheckActivate 4096

在代码中,可以使用此代码片段来设置标志并创建一个 DataMove 对象:

// Set properties("Flags") to 6657
set properties("Flags") = $$$BitNoSrcJournal + $$$BitNoWorkerJobs + $$$BitBatchMode + $$$BitCheckActivate
set status = ##class(DataMove.Data).CreateFromMapEdits("dm", .properties, .warnings, .errors)
0
0 128
文章 姚 鑫 · 六月 20, 2022 4m read

第五章 操作位和位串

有时可能希望在基于数据平台的应用程序中存储一系列相关的布尔值。可以创建许多布尔变量,也可以将它们存储在数组或列表中。或者可以使用称为“位串”的概念,它可以定义为位序列,首先呈现最低有效位。位串允许您以非常有效的方式存储此类数据,无论是在存储空间还是处理速度方面。

位串可以以两种方式之一存储,作为压缩字符串或整数。如果在没有上下文的情况下听到术语“位串”,则表示位序列存储为压缩字符串。本文向介绍了这两种类型的位串,然后介绍了一些可用于操作它们的技术。

将位序列存储为位串

存储位序列的最常见方式是在位串中,这是一种特殊的压缩字符串。除了节省存储空间外,还可以使用 ObjectScript 系统函数有效地操作位串。

这样的系统函数是 $factor,它将整数转换为位串。我们可以通过执行以下语句将整数 11744 转换为位串:

set bitstring = $factor(11744)

要查看位串内容的表示,可以使用 zwrite 命令:

zwrite bitstring
bitstring=$zwc(128,4)_$c(224,45,0,0)/*$bit(6..9,11,12,14)*/

起初它看起来很神秘,但在输出的末尾,会看到一条注释,其中显示了已设置的实际位的列表:6789111214。位串中的位 1表示 2^0,位 2 表示 2^1,依此类推。将所有位加在一起,我们得到 2^5 +2^6 + 2^7+ 2^8 + 2^10 + 2^11 + 2^13 = 11744

要获得更令人愉悦的视觉表示,可以使用另一个系统函数 $bit

for i=1:1:14 {write $bit(bitstring, i)}
00000111101101

在此示例中,$bit(bitstring, i) 返回位串的位 i 的值。

注意:要更深入地了解此位序列是如何在内部存储的,请仔细查看 zwrite 命令的输出:

bitstring=$zwc(128,4)_$c(224,45,0,0)/*$bit(6..9,11,12,14)*/

该位串存储在四个块中,每个块 8 位。分别计算每个块会给你 2244500

如果它有助于将位串视为一个字符串,可以将每个块视为一个 8 位字符。

位串的一个常见应用是位图索引的存储。位图索引是一种特殊类型的索引,它使用一系列位串来表示对应于特定属性的给定值的对象集。位图中的每个位代表类中的一个对象。

例如,假设创建一个动物及其特征的数据库,并按如下方式定义一个类:

Class User.Animal Extends %RegisteredObject
{

Property Name As %String [ Required ];

Property Classification As %String;

Property Diet As %String;

Property Swims As %Boolean;

Index ClassificationIDX On Classification [ Type = bitmap ];

Index DietIDX On Diet [ Type = bitmap ];

Index SwimsIDX On Swims [ Type = bitmap ];

}

用一些动物填充数据库后,它可能如下所示:

IDNameClassificationDietSwims
1PenguinBirdCarnivore1
2GiraffeMammalHerbivore0
3CheetahMammalCarnivore0
4BullfrogAmphibianCarnivore1
5SharkFishCarnivore1
6Fruit BatMammalHerbivore0
7Snapping TurtleReptileCarnivore1
8ManateeMammalHerbivore1
9AntInsectOmnivore0
10RattlesnakeReptileCarnivore0

位图索引 DietIDX 跟踪具有特定饮食属性值的动物。索引的每一行可以存储一个代表多达 64,000 只动物的块。

Diet属性的位图索引

DietChunkBitmap
Carnivore11011101001
Herbivore10100010100
Omnivore10000000010

在内部,位图索引存储在全局 ^User.AnimalI 的以下节点中:

^User.AnimalI("DietIDX"," CARNIVORE",1)
^User.AnimalI("DietIDX"," HERBIVORE",1)
^User.AnimalI("DietIDX"," OMNIVORE",1)

第一个下标是索引的名称 (DietIDX),第二个下标是被索引的属性的值(例如,CARNIVORE),第三个下标是块编号(在本例中为 1)。

同样,位图索引 SwimsIDX 跟踪具有特定 Swims 属性值的动物。

Swims 属性的位图索引

SwimsChunkBitmap
True11001101100
False10110010011

此位图索引存储在 ^User.AnimalI 的以下节点中:

^User.AnimalI("SwimsIDX",1,1)
^User.AnimalI("SwimsIDX",0,1)

为了了解位串的威力,可以通过计算位图中的CARNIVORE食肉动物数量非常轻松地计算数据库中的食肉动物数量,而无需检查实际数据。只需使用系统函数 $bitcount

set c = ^User.AnimalI("DietIDX"," CARNIVORE",1)

write $bitcount(c,1)
6

同样,可以计算 swim 动物数量:

set s = ^User.AnimalI("SwimsIDX",1,1)

write $bitcount(s,1)
5

要计算游动的食肉动物的数量,请使用 $bitlogic 函数查找两组的交集:

set cs = $bitlogic(c&s)

write $bitcount(cs,1)
4

注意:再次使用 zwrite 检查肉食动物的位图是如何在内部存储的:

zwrite ^User.AnimalI("DietIDX"," CARNIVORE",1)
^User.AnimalI("DietIDX"," CARNIVORE",1)=$zwc(413,2,0,2,6,8,9)/*$bit(2,4..6,8,11)*/

在这里,可以看到饮食属性为 CARNIVORE 的所有动物对应的位。如所知,位图索引被分成 64,000 位的块。为具有给定 ID 的动物存储的位存储在块 (ID\64000) + 1,位置 (ID#64000) + 1 中。因此,表示具有 ID 1 的动物的位存储在块 1,位置 2 中。所以,在这个位串中,位 2 代表企鹅,而不是长颈鹿。

SQL 引擎包括许多可以利用位图索引的特殊优化,因此可以在编写 SQL 查询时获得好处。

0
0 128
文章 姚 鑫 · 六月 19, 2022 3m read

第四章 锁定和并发控制(四)

避免死锁

增量锁定具有潜在危险,因为它可能导致称为死锁的情况。当两个进程各自对已被另一个进程锁定的变量断言增量锁定时,就会出现这种情况。因为尝试的锁是增量的,所以现有的锁不会被释放。结果,每个进程在等待另一个进程释放现有锁的同时挂起。

举个例子:

  1. 进程 A 发出此命令:lock + ^MyGlobal(15)
  2. 进程 B 发出此命令:lock + ^MyOtherGlobal(15)
  3. 进程 A 发出此命令:lock + ^MyOtherGlobal(15)

LOCK 命令不返回;进程被阻塞,直到进程 B 释放这个锁。

  1. 进程 B 发出此命令:lock + ^MyGlobal(15)

LOCK 命令不返回;进程被阻塞,直到进程 A 释放这个锁。但是,进程 A 被阻塞,无法释放锁。现在这些进程都在等待对方。

有几种方法可以防止死锁:

  • 始终包含 timeout 参数。
  • 对于发出增量 LOCK 命令的顺序,请遵循严格的协议。只要所有进程都遵循相同的锁名称顺序,就不会发生死锁。一个简单的协议是按排序顺序添加锁。
  • 使用简单锁定而不是增量锁定;也就是说,不要使用 + 运算符。如前所述,对于简单锁定,LOCK 命令首先释放进程持有的所有先前锁定。 (然而,在实践中,简单的锁定并不经常使用。)

如果发生死锁,可以使用管理门户或 ^LOCKTAB

锁的实际用途

本节介绍在实践中使用锁的基本方法。

控制对应用程序数据的访问

锁经常用于控制对存储在全局变量中的应用程序数据的访问。应用程序可能需要读取或修改此数据的特定部分,并且应用程序将在执行此操作之前创建一个或多个锁,如下所示:

  • 如果应用程序需要读取一个或多个全局节点,并且不希望其他进程在读取操作期间修改这些值,请为这些节点创建共享锁。
  • 如果应用程序需要修改一个或多个全局节点,并且不希望其他进程在修改期间读取这些节点,请为这些节点创建排他锁。

然后按计划阅读或进行修改。完成后,取下锁。

请记住,锁定机制纯粹按照约定工作。任何其他将读取或修改这些节点的代码也必须在执行这些操作之前尝试获取锁。

防止同步行为

锁也用于防止多个进程执行相同的活动行为。在这种情况下,还使用了GLOBAL,但GLOBAL包含用于应用程序内部目的的数据,而不是纯应用程序数据。作为一个简单的示例,假设有一个例程 (^NightlyBatch),在任何给定时间都不应由多个进程运行。该例程可以在其处理的早期阶段执行以下操作:

  1. 在特定全局节点上创建排他锁,例如 ^AppStateData("NightlyBatch")。为此操作指定超时。
  2. 如果获得锁,则在全局中设置节点以记录例程已启动(以及任何其他相关信息)。例如:
 set ^AppStateData("NightlyBatch")=1
 set ^AppStateData("NightlyBatch","user")=$USERNAME

或者,如果在超时期限内未获得锁,则退出并显示错误消息,指示该例程已启动。

然后,在其处理结束时,同一例程将清除适用的全局节点并释放锁。

以下部分示例演示了这种技术,该技术改编自内部使用的代码:

/// w ##class(PHA.TEST.AdvancedConcepts).Lock()
ClassMethod Lock()
{
	lock ^AppStateData("NightlyBatch"):0
	if '$TEST {
		write "现在无法运行此例程."
		write !, "此例程当前由用户运行: "_^AppStateData("NightlyBatch","user")
		quit
	}
	set ^AppStateData("NightlyBatch") = 1
	set ^AppStateData("NightlyBatch","user") = $USERNAME
	set ^AppStateData("NightlyBatch","starttime") = $h
	b
	kill ^AppStateData("NightlyBatch")
	lock -^AppStateData("NightlyBatch")
}

0
0 183
文章 姚 鑫 · 六月 17, 2022 7m read

第三章 锁定和并发控制(三)

升级锁

使用升级锁来管理大量锁。当锁定数组的节点时,它们是相关的,特别是当将多个节点锁定在同一下标级别时。

当给定进程在同一数组中的给定下标级别创建了超过特定数量(默认为 1000)的升级锁时, 将删除所有单独的锁名称并用新锁替换它们。新锁位于父级,这意味着数组的整个分支被隐式锁定。示例(如下所示)演示了这一点。

应用程序应在合适的情况下尽快释放特定子节点的锁(与非升级锁完全相同)。当释放锁时, 会减少相应的锁计数。当的应用程序移除足够多的锁时,会移除父节点上的锁。第二小节显示了一个示例。

锁升级示例

假设有 1000^MyGlobal("sales","EU",salesdate) 形式的锁,其中 salesdate 表示日期。锁表可能如下所示:

image

注意 Owner 19776 的条目(这是拥有锁的进程)。 ModeCount 列指示这些是共享的、升级的锁。

当同一进程试图创建另一个相同形式的锁时, 会升级它们。它会移除这些锁并用名称为 ^MyGlobal("sales","EU") 的单个锁替换它们。现在锁表可能如下所示:

image

ModeCount 列表明这是一个共享的升级锁,它的计数是 1001

请注意以下关键点:

  • ^MyGlobal("sales","EU") 的所有子节点现在都被隐式锁定,遵循数组锁定的基本规则。
  • 锁定表不再包含有关 ^MyGlobal("sales","EU") 的哪些子节点被特别锁定的信息。这在删除锁时具有重要意义。见下一小节。

当同一进程添加更多形式为 ^MyGlobal("sales","EU",salesdate) 的锁名称时,锁表会增加锁名称 ^MyGlobal("sales","EU") 的锁计数。锁定表可能如下所示:

image

ModeCount 列指示此锁的锁计数现在为 1026

移除升级锁

与非升级锁完全相同,应用程序应尽快释放特定子节点的锁。当这样做时, 会减少升级锁的锁计数。例如,假设代码删除了 ^MyGlobal("sales","EU",salesdate) 的锁定,其中 salesdate 对应于 2011 年的任何日期 — 因此删除了 365 个锁定。锁表现在看起来像这样:

image

请注意,即使现在锁的数量低于阈值 (1000),锁表也不包含 ^MyGlobal("sales","EU",salesdate). 的锁的单独条目。

节点 ^MyGlobal("sales")保持显式锁定,直到该过程再删除 661^MyGlobal("sales","EU",salesdate) 形式的锁定。

重要提示:有一点需要考虑,与前面的讨论有关。应用程序可能会“释放”数组节点上的锁,这些节点一开始就从未锁定,从而导致升级锁的锁计数不准确 - 并且可能在需要这样做之前释放升级锁。

例如,假设进程锁定 ^MyGlobal("sales","EU",salesdate) 中从 2010 年到现在的节点。这将创建超过 1000 个锁,并且此锁将按计划升级。假设应用程序中的错误删除了 1970 年节点的锁。 将允许此操作,即使这些节点以前没有被锁定,并且 会将锁计数减少 365。生成的锁计数不会是所需锁的准确计数。如果应用程序随后移除了其他年份的锁,则升级的锁可能会意外地提前移除。

Locks, Globals, and Namespaces

锁通常用于控制对全局变量的访问。因为可以从多个命名空间访问全局, 为其锁定机制提供自动跨命名空间支持。该行为是自动的,不需要干预,但在此描述以供参考。有几种情况需要考虑:

  • 任何命名空间都有一个默认数据库,其中包含持久类和任何其他全局变量的数据;这是此命名空间的全局数据库。访问数据时, IRIS 会从该数据库中检索数据,除非有其他考虑。一个给定的数据库可以是多个命名空间的全局数据库。请参见方案 1。
  • 命名空间可以包括提供对存储在其他数据库中的全局变量的访问的映射。请参见方案 2。
  • 命名空间可以包括下标级别的全局映射,这些映射提供对部分存储在其他数据库中的全局变量的访问。请参见方案 3。
  • 在一个命名空间中运行的代码可以使用扩展引用来访问在此命名空间中不可用的全局变量。请参见方案 4。

尽管锁名称本质上是任意的,但是当使用以插入符号 (^) 开头的锁名称时,IRIS 提供了适合这些情况的特殊行为。以下小节给出了详细信息。为简单起见,只讨论排他锁;共享锁的逻辑类似。

场景 1:具有相同Global数据库的多个命名空间

如前所述,虽然进程 A 拥有一个具有给定锁名的独占锁,但没有其他进程可以获取任何具有相同锁名的锁。

如果锁名称以插入符号开头,则此规则适用于使用相同全局数据库的所有命名空间。

例如,假设命名空间 ALPHABETA 都配置为使用数据库 GAMMA 作为其全局数据库。下面显示一个草图:

image

然后考虑以下场景:

  1. 在命名空间 ALPHA 中,进程 A 获得一个名为 ^MyGlobal(15) 的独占锁。
  2. 在命名空间 BETA 中,进程 B 尝试获取名称为 ^MyGlobal(15) 的锁。此 LOCK 命令不返回;进程被阻塞,直到进程 A 释放锁。

在这种情况下,锁表只包含进程 A 拥有的锁的条目。如果检查锁表,会注意到它指示了该锁应用到的数据库;请参阅目录列。例如:

image

场景 2:命名空间使用映射的Global

如果一个或多个命名空间包含全局映射,系统会自动跨适用的命名空间强制实施锁定机制。当在非默认命名空间中获得锁时, IRIS 会自动创建额外的锁表条目。

例如,假设命名空间 ALPHA 配置为使用数据库 ALPHADB 作为其全局数据库。假设命名空间 BETA 配置为使用不同的数据库 (BETADB) 作为其全局数据库。命名空间 BETA 还包括一个全局映射,它指定 ^MyGlobal 存储在 ALPHADB 数据库中。下面显示一个草图:

image

然后考虑以下场景:

  1. 在命名空间 ALPHA 中,进程 A 获得一个名为 ^MyGlobal(15) 的独占锁。

与前面的场景一样,锁表仅包含进程 A 拥有的锁的条目。此锁适用于 ALPHADB 数据库:

image

  1. 在命名空间 BETA 中,进程 B 尝试获取名称为 ^MyGlobal(15) 的锁。此 LOCK 命令不返回;进程被阻塞,直到进程 A 释放锁。

场景 3:命名空间使用映射的Global下标

如果一个或多个命名空间包含使用下标级别映射的全局映射,系统会自动跨适用的命名空间强制实施锁定机制。在这种情况下,当在非默认命名空间中获取锁时,IRIS 还会自动创建额外的锁表条目。

例如,假设命名空间 ALPHA 配置为使用数据库 ALPHADB 作为其全局数据库。命名空间 BETA 使用 BETADB 数据库作为其全局数据库。

还假设命名空间 BETA 还包括一个下标级别的全局映射,因此 ^MyGlobal(15) 存储在 ALPHADB 数据库中(而这个全局的其余部分存储在命名空间的默认位置)。下面显示一个草图:

image

然后考虑以下场景:

  1. 在命名空间 ALPHA 中,进程 A 获得一个名为 ^MyGlobal(15) 的独占锁。
  2. 与前面的场景一样,锁表仅包含进程 A 拥有的锁的条目。此锁适用于 ALPHADB 数据库(例如,c:\InterSystems\IRIS\mgr\alphadb)。

当非默认命名空间获得锁时,整体行为是相同的,但 IRIS 处理细节略有不同。假设在命名空间 BETA 中,一个进程获得了一个名为 ^MyGlobal(15) 的锁。在这种情况下,锁表包含两个条目,一个用于 ALPHADB 数据库,一个用于 BETADB 数据库。这两个锁都归命名空间 BETA 中的进程所有。

image

当此进程释放锁名称 ^MyGlobal(15) 时,系统会自动删除两个锁。

场景 4:扩展的Global引用

在一个命名空间中运行的代码可以使用扩展引用来访问在此命名空间中不可用的全局变量。在这种情况下,IRIS 将一个条目添加到影响相关数据库的锁表中。锁归创建它的进程所有。例如,考虑以下场景。为简单起见,此方案中没有全局映射。

  1. 进程 AALPHA 命名空间中运行,该进程使用以下命令获取 BETA 命名空间中可用的全局锁:
 lock ^["beta"]MyGlobal(15)
  1. 现在锁定表包括以下条目:

image

请注意,这仅显示全局名称(而不是用于访问它的引用)。此外,在这种情况下,BETADBBETA 命名空间的默认数据库。

  1. 在命名空间 BETA 中,进程 B 尝试获取名称为 ^MyGlobal(15) 的锁。此 LOCK 命令不返回;进程被阻塞,直到进程 A 释放锁。

进程私有Global在技术上是一种扩展引用,但 IRIS 不支持使用进程私有全局名称作为锁名称;无论如何,都不需要这样的锁,因为根据定义,只有一个进程可以访问这样的全局。

0
0 117
文章 姚 鑫 · 六月 16, 2022 4m read

第二章 锁定和并发控制(二)

关于零超时的说明

如上所述,如果您将 timeout 指定为 0, 会添加锁。但是,如果使用零超时锁定父节点,并且已经在子节点上锁定,则忽略零超时并使用内部 1 秒超时。

删除锁

要删除默认类型的锁,请使用 LOCK 命令,如下所示:

LOCK -lockname

如果执行此命令的进程拥有具有给定名称的锁(默认类型),则此命令将删除该锁。或者,如果进程拥有多个锁(默认类型),此命令将删除其中一个。

或者删除另一种类型的锁:

LOCK -lockname#locktype

其中 locktype 是一串锁类型代码。

LOCK 命令的其他基本变体

为了完整起见,本节讨论 LOCK 命令的其他基本变体:使用它来创建简单的锁并使用它来删除所有锁。这些变化在实践中并不常见。

创建简单的锁

对于 LOCK 命令,如果省略 + 运算符,LOCK 命令首先会删除该进程持有的所有现有锁,然后尝试添加新锁。在这种情况下,锁称为简单锁而不是增量锁。一个进程可以拥有多个简单的锁,如果该进程使用如下语法同时创建它们:

 LOCK (^MyVar1,^MyVar2,^MyVar3)

简单的锁在实践中并不常见,因为通常需要持有多个锁并在代码的不同步骤中获取它们。因此使用增量锁更实用。

但是,如果简单锁适合,请注意,可以在创建简单锁时指定 locktypetimeout 参数。此外,要删除一个简单的锁,可以使用带有减号 (-) 的 LOCK 命令。

移除所有锁

要删除当前进程持有的所有锁,请使用不带参数的 LOCK 命令。在实践中,以这种方式使用命令并不常见,原因有两个:

  • 最好尽快释放特定的锁。
  • 当进程结束时,它的所有锁都会自动释放。

锁类型

locktype 参数指定要添加或删除的锁的类型。添加锁时,请包含此参数,如下所示:

LOCK +lockname#locktype

或者在移除锁时:

LOCK -lockname#locktype

在任何一种情况下,locktype 都是用双引号括起来的一个或多个锁类型代码(以任何顺序)。请注意,如果指定 locktype 参数,则必须包含井号 (#) 以将锁名称与锁类型分开。

有四种锁类型代码,如下所示。请注意,这些不区分大小写。

  • S — 添加共享锁。
  • E — 添加升级锁。
  • I - 添加立即解锁的锁。
  • D — 添加延迟解锁的锁。

锁类型代码 DI 在事务中有特殊行为。对于同一个锁名称,不能同时使用这两个锁类型代码。

独占锁和共享锁

任何锁要么是独占的(默认),要么是共享的。这些类型具有以下意义:

  • 虽然一个进程拥有一个独占锁(具有给定的锁名称),但没有其他进程可以获取具有该锁名称的任何锁。
  • 当一个进程拥有一个共享锁(具有给定的锁名称)时,其他进程可以获取具有该锁名称的共享锁,但没有其他进程可以获取具有该锁名称的独占锁。

排他锁的典型目的是表明打算修改一个值,并且其他进程不应尝试读取或修改该值。共享锁的典型目的是表明打算读取一个值并且其他进程不应尝试修改该值;但是,他们可以读取该值。

非升级和升级锁

任何锁也是非升级(默认)或升级。升级锁的目的是为了更容易管理大量锁,这会消耗内存并增加锁表被填满的机会。

当锁定同一阵列的多个节点时,使用升级锁。对于升级锁,如果给定进程在给定阵列的并行节点上创建了超过特定数量(默认为 1000)的锁, 将替换各个锁名称并用包含锁计数的新锁替换它们. (相比之下, 从未对非升级锁执行此操作。)

注意:只能为包含下标的锁名称创建升级锁。如果尝试使用没有下标的锁名称创建升级锁,会发出 <COMMAND> 错误。

锁类型总结

下表列出了所有可能的锁类型及其描述:

 排他锁共享锁 (#"S" locks)
非升级锁locktype 省略 - 默认锁定类型
#"I" — 立即解锁的独占锁
#"D" — 具有延迟解锁的排他锁
#"S" — 共享锁
#"SI" — 立即解锁的共享锁
#"SD" — 具有延迟解锁的共享锁
升级锁 (#"E" locks)#"E" — 独占升级锁
#"EI" — 立即解锁的独占升级锁
#"ED" — 具有延迟解锁的独占升级锁
#"SE" — 共享升级锁
#"SEI" — 立即解锁的共享升级锁
#"SED" — 具有延迟解锁的共享升级锁

对于使用多个锁的任何锁类型,锁可以是任何顺序。例如,锁类型#"SI" 等价于#"IS"

0
0 77
文章 姚 鑫 · 六月 15, 2022 5m read

第一章 锁定和并发控制(一)

任何多进程系统的一个重要特征是并发控制,即防止不同进程同时更改特定数据元素的能力,从而导致损坏。 提供了一个锁管理系统。本文提供了一个概述。

此外,%Persistent 类提供了一种控制对象并发访问的方法,即 %OpenId() 的并发参数和该类的其他方法。这些方法最终使用本文讨论的 ObjectScript LOCK 命令。所有持久对象都继承这些方法。同样,系统会自动对 INSERTUPDATEDELETE 操作执行锁定(除非指定 %NOLOCK 关键字)。

%Persistent 类还提供方法 %GetLock()%ReleaseLock()%LockId()%UnlockId()%LockExtent()%UnlockExtent()

介绍

基本的锁定机制是 LOCK 命令。此命令的目的是延迟一个进程中的活动,直到另一个进程发出可以继续进行的信号。

锁本身并不能阻止活动行为。锁定仅按约定起作用:它要求相互竞争的进程都使用相同的锁定名称实现锁定。例如,下面描述了一个常见的场景:

  1. 进程 A 发出 LOCK 命令, 创建一个锁(默认情况下,一个独占锁)。通常,进程 A 然后对global中的节点进行更改。详细信息是特定于应用程序的。
  2. 进程 B 发出具有相同锁名称的 LOCK 命令。因为存在一个现有的排他锁,所以进程 B 暂停。具体来说,LOCK 命令不返回,并且不能执行连续的代码行。
  3. 当进程A释放锁时,进程B中的LOCK命令最终返回,进程B继续。通常,进程 B 然后对同一global中的节点进行更改。

锁名称

LOCK 命令的参数之一是锁名称。锁名称是任意的,但按照通用约定,程序员使用与要锁定的项目名称相同的锁名称。通常要锁定的项目是global或global的一个节点。因此锁名称通常看起来像global名称的名称或全局节点的名称。 (本文只讨论以脱字符开头的锁名称,因为这些是最常见的;有关名称不以脱字符(^)开头的锁的详细信息,请参阅 ObjectScript 参考中的“LOCK”。)

形式上,锁名称遵循与局部变量和全局变量相同的命名约定,如使用 ObjectScript 中的“变量”一章所述。与变量一样,锁名称区分大小写并且可以有下标。不要使用进程私有的global名称作为锁名称(无论如何都不需要这样的锁,因为根据定义,只有一个进程可以访问这样的全局)。

提示:由于锁定按约定工作并且锁定名称是任意的,因此无需在创建具有相同名称的锁定之前定义给定变量。

由于分配和管理内存的方式,锁名称的形式会影响性能。锁定针对使用下标的锁定名称进行了优化。一个例子是 ^sample.person(id)

相反并未针对锁名称进行优化,例如 ^name_concatenated_identifier。非下标锁名称也可能导致与 ECP 相关的性能问题。

锁表

维护系统范围的内存表,记录所有当前锁和拥有它们的进程。此表(锁定表)可通过管理门户访问,可以在其中查看锁定并(在极少数情况下,如果需要)删除它们。请注意,任何给定的进程都可以拥有多个具有不同锁名称的锁(甚至可以拥有多个具有相同锁名称的锁)。

当一个进程结束时,系统会自动释放该进程拥有的所有锁。因此,通常不需要通过管理门户移除锁,除非出现应用程序错误。

锁定表不能超过固定大小,可以使用 locksiz 设置指定该大小。因此,锁表可能会被填满,这样就不可能再有锁了。如果发生这种情况,会将以下消息写入 messages.log 文件:

LOCK TABLE FULL

填充锁表一般不认为是应用程序错误; IRIS 还提供了一个锁队列,进程等待直到有空间将它们的锁添加到锁表中。 (但是,死锁被认为是应用程序编程错误。请参阅本文后面的“避免死锁”。)

锁和阵列

锁定阵列时,可以锁定整个阵列或阵列中的一个或多个节点。锁定阵列节点时,会阻止其他进程锁定从属于该节点的任何节点。其他进程也被阻止锁定锁定节点的直接祖先。

下图显示了一个示例:

隐式锁不包含在锁表中,因此不会影响锁表的大小。

锁排队算法按接收到的顺序将相同锁名的所有锁排队,即使没有直接的资源争用。

使用 LOCK 命令

本节讨论如何使用 LOCK 命令添加和删除锁。

添加增量锁

要添加锁,请使用 LOCK 命令,如下所示:

LOCK +lockname

其中 lockname 是文字锁名称。加号(+)创建增量锁,这是常见的场景;

该命令执行以下操作:

  1. 尝试将给定的锁添加到锁表中。也就是说,这个条目被添加到锁队列中。
  2. 暂停执行,直到可以获取锁为止。

有不同类型的锁,它们的行为不同。要添加非默认锁类型的锁,请使用以下变体:

LOCK +lockname#locktype

其中 locktype 是用双引号括起来的一串锁类型代码;

注意一个给定的进程可以添加多个同名的增量锁;这些锁可以是不同的类型,也可以是相同的类型。

添加具有超时的增量锁

如果使用不当,增量锁可能会导致称为死锁的不良情况,稍后将在“避免死锁”中讨论。避免死锁的一种方法是在创建锁时指定超时时间。为此,请按如下方式使用 LOCK 命令:

LOCK +lockname#locktype :timeout

其中 timeout 是以秒为单位的超时时间。冒号前的空格是可选的。如果将超时指定为 0, 会尝试添加锁(但请参阅下面的注释)。

该命令执行以下操作:

  1. 尝试将给定的锁添加到锁表中。也就是说,这个条目被添加到锁队列中。
  2. 暂停执行,直到可以获取锁或超时期限结束,以先到者为准。
  3. 设置 $TEST 特殊变量的值。如果获得锁,将 $TEST 设置为 1。否则,将$TEST 设置为 0

这意味着如果使用 timeout 参数,代码接下来应该检查 $TEST 特殊变量的值并使用该值来选择是否继续。下面显示了一个示例:

 Lock +^ROUTINE(routinename):0 
 If '$TEST {  Return $$$ERROR("Cannot lock the routine: ",routinename)}
0
0 116
文章 姚 鑫 · 六月 14, 2022 5m read

第九章 其他参考资料(二)

特殊变量 (SQL)

系统提供的变量。

$HOROLOG
$JOB
$NAMESPACE
$TLEVEL
$USERNAME
$ZHOROLOG
$ZJOB
$ZPI
$ZTIMESTAMP
$ZTIMEZONE
$ZVERSION

SQL直接支持许多对象脚本特殊变量。这些变量包含系统提供的值。只要可以在SQL中指定文字值,就可以使用它们。

SQL特殊变量名不区分大小写。大多数可以使用缩写来指定。

VariableNameAbbreviationData Type Returned Use
$HOROLOG$H%String/VARCHAR当前进程的本地日期和时间
$JOB$J%String/VARCHAR当前进程的 job ID
$NAMESPACEnone%String/VARCHAR当前命名空间名称
$TLEVEL$TL%Integer/INTEGER
$USERNAMEnone%String/VARCHAR当前进程的用户名
$ZHOROLOG$ZH%Numeric/NUMERIC(21,6)自InterSystems IRIS启动后经过的秒数
$ZJOB$ZJ%Integer/INTEGER当前进程的job状态
$ZPInone%Numeric/NUMERIC(21,18)数值常量PI
$ZTIMESTAMP$ZTS%String/VARCHAR协调世界时间格式的当前日期和时间
$ZTIMEZONE$ZTZ%Integer/INTEGER当地时区与GMT的偏移量
$ZVERSION$ZV%String/VARCHARIRIS的当前版本

示例

SELECT TOP 5 Name,$H
FROM Sample.Person

以下示例仅在时区位于大陆内时才返回结果集:

SELECT TOP 5 Name,Home_State
FROM Sample.Person
WHERE $ZTIMEZONE BETWEEN -480 AND 480

字符串操作(SQL)

字符串操作函数和运算符。

SQL 支持多种类型的字符串操作:

  • 字符串可以通过长度、字符位置或子字符串值进行操作。
  • 字符串可以通过指定的分隔符或分隔符字符串来操作。
  • 字符串可以通过模式匹配和单词感知搜索来测试。
  • 特殊编码的字符串(称为列表)包含嵌入的子字符串标识符,而不使用分隔符。各种 $LIST 函数对这些与标准字符串不兼容的编码字符串进行操作。唯一的例外是 $LISTGET 函数和 $LIST 的单参数和双参数形式,它们将编码字符串作为输入,但将单个元素值作为标准字符串输出。

SQL 支持字符串函数、字符串条件表达式和字符串运算符。

ObjectScript 字符串操作区分大小写。字符串中的字母可以转换为大写、小写或混合大小写。字符串排序规则可以区分大小写,也可以不区分大小写;默认情况下,SQL 字符串排序规则是不区分大小写的 SQLUPPER。 SQL 提供了许多字母大小写和排序规则函数和运算符。

当为数字参数指定字符串时,大多数 SQL 函数执行以下字符串到数字的转换: 非数字字符串转换为数字 0;将数字字符串转换为规范数字;并且混合数字字符串在第一个非数字字符处被截断,然后转换为规范数字。

字符串连接

以下函数将子字符串连接成字符串:

  • CONCAT:连接两个子字符串,返回一个字符串。
  • STRING:连接两个或多个子字符串,返回单个字符串。
  • XMLAGG:连接列的所有值,返回单个字符串。
  • LIST:连接列的所有值,包括逗号分隔符,返回单个字符串。
  • 连接运算符 (||) 也可用于连接两个字符串。

字符串长度

以下函数可用于确定字符串的长度:

  • CHARACTER_LENGTHCHAR_LENGTH:返回字符串中的字符数,包括尾随空格。 NULL 返回 NULL
  • LENGTH:返回字符串中的字符数,不包括尾随空格。 NULL 返回 NULL。
  • $LENGTH:返回字符串中的字符数,包括尾随空格。 NULL 返回为 0。

Truncation and Trim

以下函数可用于截断或修剪字符串。截断限制字符串的长度,删除超出指定长度的所有字符。Trim从字符串中删除前导和/或尾随空格。

  • Truncation: CONVERT, %SQLSTRING, and %SQLUPPER.
  • Trimming: TRIM, LTRIM, and RTRIM.

子串搜索

以下函数在字符串中搜索子字符串并返回字符串位置:

  • POSITION:按子字符串值搜索,找到第一个匹配项,返回子字符串开始的位置。
  • CHARINDEX:按子字符串值搜索,找到第一个匹配项,返回子字符串开始的位置。可以指定起点。
  • $FIND:按子串值搜索,找到第一个匹配项,返回子串结束的位置。可以指定起点。
  • INSTR:按子字符串值搜索,找到第一个匹配项,返回子字符串开始的位置。可以指定起点和子串出现。

以下函数在字符串中按位置或分隔符搜索子字符串并返回子字符串:

  • $EXTRACT:按字符串位置搜索,返回由开始位置或开始和结束位置指定的子字符串。从字符串的开头搜索。
  • SUBSTRING:按字符串位置搜索,返回由开始位置或开始和长度指定的子字符串。从字符串的开头搜索。
  • SUBSTR:按字符串位置搜索,返回由起始位置或起始和长度指定的子字符串。从字符串的开头或结尾搜索。
  • $PIECE:按分隔符搜索,返回第一个分隔的子字符串。可以指定起点或默认为字符串的开头。
  • $LENGTH:按分隔符搜索,返回分隔子串的数量。从字符串的开头搜索。
  • $LIST:在特殊编码的列表字符串上按子字符串计数搜索。它通过子串计数定位子串并返回子串值。从字符串的开头搜索。
  • 包含运算符 ([) 也可用于确定子字符串是否出现在字符串中。
  • %STARTSWITH 比较运算符将指定的字符与字符串的开头进行匹配。

子串搜索和替换

以下函数在字符串中搜索子字符串并将其替换为另一个子字符串。

  • REPLACE:按字符串值搜索,用新的子字符串替换子字符串。从字符串的开头搜索。
  • STUFF:按字符串位置和长度搜索,用新的子字符串替换子字符串。从字符串的开头搜索。

字符类型和 Word-Aware 比较

%PATTERN 比较运算符将字符串与指定的字符类型模式匹配。

0
0 112
文章 姚 鑫 · 六月 13, 2022 5m read

第八章 其他参考资料(一)

默认用户名和密码(SQL)

IRIS® 数据平台提供了用于登录数据库和开始使用的默认用户名和密码。默认用户名为“_SYSTEM”(大写),密码为“sys”。

SQLCODE错误代码(SQL)

执行大多数 SQL操作都会发出SQLCODE值。发出的SQLCODE值为0100和负整数值。

  • SQLCODE=0表示SQL操作成功完成。对于SELECT语句,这通常意味着从表中成功检索数据。但是,如果SELECT执行聚合操作(例如:SELECT SUM(Myfield)),则聚合操作成功,即使myfield中没有数据,也会发出SQLCODE=0;在这种情况下,SUM返回NULL%ROWCOUNT=1
  • SQLCODE=100表示SQL操作成功,但没有找到可操作的数据。发生这种情况的原因有很多。对于SELECT,这些包括:指定表不包含数据;表不包含满足查询条件的数据;或者行检索已到达表的最后一行。对于UPDATEDELETE,它们包括:指定的表不包含数据;或者表不包含满足WHERE子句条件的数据行。在这些情况下,%ROWCOUNT=0
  • SQLCODE=-n表示错误。负整数值指定发生的错误类型。SQLCODE=-400是通用的致命错误代码。

字段约束(SQL)

字段约束指定管理字段允许的数据值的规则。一个字段可能有以下约束:

  • NOT NULL非空:必须在每条记录中为该字段指定值(可接受空字符串)。
  • UNIQUE唯一:如果在记录中为该字段指定值,则该值必须是唯一值(可接受的空字符串)。但是,可以为该字段创建多个没有值(NULL)的记录。
  • DEFAULT默认值:必须指定一个值,否则 IRIS会为每个记录中的此字段提供一个默认值(接受空字符串)。默认值可以是NULL、空字符串或适用于该数据类型的任何其他值。
  • UNIQUE NOT NULL:必须在每条记录中为该字段指定唯一的值(可接受一个空字符串)。可用作主键。
  • DEFAULT NOT NULL:必须指定一个值,否 IRIS会为每个记录中的此字段提供一个默认值(可接受的空字符串)。
  • UNIQUE DEFAULT唯一默认值:不推荐-必须指定唯一值,否则 IRIS会为每个记录中的此字段提供一个默认值(一个可接受的空字符串)。缺省值可以是NULL、空字符串或适用于该数据类型的任何其他值。仅当缺省值是唯一生成的值(例如,CURRENT_TIMESTAMP)时使用,或者如果缺省值仅使用一次。
  • UNIQUE DEFAULT NOT NULL:不建议-必须指定唯一的值,否则 IRIS会为每个记录中的此字段提供一个默认值(一个可接受的空字符串)。缺省值可以是空字符串或适用于该数据类型的任何其他值;它不能为空。仅当缺省值是唯一生成的值(例如,CURRENT_TIMESTAMP)时使用,或者如果缺省值仅使用一次。可用作主键。
  • IDENTITY: IRIS为每个记录中的该字段提供唯一的、系统生成的、不可修改的整数值。忽略其他字段约束关键字。可用作主键。

数据值必须适合该字段的数据类型。空字符串不是数值字段的可接受值。

保留字(SQL)

SQL保留字列表。

%AFTERHAVING | %ALLINDEX | %ALPHAUP | %ALTER | %BEGTRANS | 
%CHECKPRIV | %CLASSNAME | %CLASSPARAMETER | %DBUGFULL | %DELDATA | 
%DESCRIPTION | %EXACT | %EXTERNAL | %FILE | %FIRSTTABLE | %FLATTEN | 
%FOREACH | %FULL | %ID | %IDADDED | %IGNOREINDEX | %IGNOREINDICES | 
%INLIST | %INORDER | %INTERNAL | %INTEXT | %INTRANS | %INTRANSACTION | 
%KEY | %MATCHES | %MCODE | %MERGE | %MINUS | %MVR | %NOCHECK | 
%NODELDATA | %NOFLATTEN | %NOFPLAN | %NOINDEX | %NOLOCK | 
%NOMERGE | %NOPARALLEL | %NOREDUCE | %NORUNTIME | %NOSVSO | %NOTOPOPT | 
%NOTRIGGER | %NOUNIONOROPT | %NUMROWS | %ODBCIN | %ODBCOUT | 
%PARALLEL | %PLUS | %PROFILE | %PROFILE_ALL | %PUBLICROWID | %ROUTINE | 
%ROWCOUNT | %RUNTIMEIN | %RUNTIMEOUT | %STARTSWITH | 
%STARTTABLE | %SQLSTRING | %SQLUPPER | %STRING | %TABLENAME | 
%TRUNCATE | %UPPER | %VALUE | %VID
ABSOLUTE | ADD | ALL | ALLOCATE | ALTER | AND | ANY | ARE | AS | 
ASC | ASSERTION | AT | AUTHORIZATION | AVG | BEGIN | BETWEEN | 
BIT | BIT_LENGTH | BOTH | BY | CASCADE | CASE | CAST |
CHAR | CHARACTER | CHARACTER_LENGTH | CHAR_LENGTH | 
CHECK | CLOSE | COALESCE | COLLATE | COMMIT | CONNECT | 
CONNECTION | CONSTRAINT | CONSTRAINTS | CONTINUE | CONVERT | 
CORRESPONDING | COUNT | CREATE | CROSS | CURRENT | 
CURRENT_DATE | CURRENT_TIME | CURRENT_TIMESTAMP | 
CURRENT_USER | CURSOR | DATE | DEALLOCATE | DEC | DECIMAL | 
DECLARE | DEFAULT | DEFERRABLE | DEFERRED | DELETE | DESC | 
DESCRIBE | DESCRIPTOR | DIAGNOSTICS | DISCONNECT | DISTINCT | 
DOMAIN | DOUBLE | DROP | ELSE | END | ENDEXEC | ESCAPE | EXCEPT | 
EXCEPTION | EXEC | EXECUTE | EXISTS | EXTERNAL | EXTRACT | 
FALSE | FETCH | FIRST | FLOAT | FOR | FOREIGN | FOUND | FROM | FULL | 
GET | GLOBAL | GO | GOTO | GRANT | GROUP | HAVING | HOUR | 
IDENTITY | IMMEDIATE | IN | INDICATOR | INITIALLY | 
INNER | INPUT | INSENSITIVE | INSERT | INT | INTEGER | INTERSECT | 
INTERVAL | INTO | IS | ISOLATION | JOIN | LANGUAGE | LAST | 
LEADING | LEFT | LEVEL | LIKE | LOCAL | LOWER | MATCH | MAX | MIN | 
MINUTE | MODULE | NAMES | NATIONAL | NATURAL | NCHAR | 
NEXT | NO | NOT | NULL | NULLIF | NUMERIC | OCTET_LENGTH | OF | ON | 
ONLY | OPEN | OPTION | OR | OUTER | OUTPUT | OVERLAPS | 
PAD | PARTIAL | PREPARE | PRESERVE | PRIMARY | PRIOR | PRIVILEGES | 
PROCEDURE | PUBLIC | READ | REAL | REFERENCES | RELATIVE | 
RESTRICT | REVOKE | RIGHT | ROLE | ROLLBACK | ROWS | 
SCHEMA | SCROLL | SECOND | SECTION | SELECT | SESSION_USER | 
SET | SHARD | SMALLINT | SOME | SPACE | SQLERROR | SQLSTATE | 
STATISTICS | SUBSTRING | SUM | SYSDATE | SYSTEM_USER | TABLE | 
TEMPORARY | THEN | TIME | TIMEZONE_HOUR | TIMEZONE_MINUTE | 
TO | TOP | TRAILING | TRANSACTION | TRIM | TRUE | UNION | UNIQUE | 
UPDATE | UPPER | USER | USING | VALUES | VARCHAR | VARYING | WHEN | 
WHENEVER | WHERE | WITH | WORK | WRITE 

描述

在SQL中,某些字是保留的。不能将SQL保留字用作SQL标识符(如表、列、AS别名或其他实体的名称),除非:

  • 该单词用双引号("word")分隔,并且支持分隔的标识符。

该列表仅包含在此意义上保留的那些单词;它不包含所有SQL关键字。上面列出的几个单词以"%"字符开头,表示它们是 SQL专有扩展关键字。通常,不建议使用以"%"开头的单词作为表名和列名等标识符,因为将来可能会添加新的 SQL扩展关键字。

可以通过调用IsReserve vedWord()方法来检查某个字是否为SQL保留字,如下例所示。将保留字指定为引号字符串;保留字不区分大小写。$SYSTEM.SQL.IsReserve vedWord()返回布尔值。

/// d ##class(PHA.TEST.SQLFunction).ReservedWord()
ClassMethod ReservedWord()
{
	w !,"Reserved?: ",$SYSTEM.SQL.IsReservedWord("VARCHAR")
	w !,"Reserved?: ",$SYSTEM.SQL.IsReservedWord("varchar")
	w !,"Reserved?: ",$SYSTEM.SQL.IsReservedWord("VarChar")
	w !,"Reserved?: ",$SYSTEM.SQL.IsReservedWord("FRED")
}
DHC-APP> d ##class(PHA.TEST.SQLFunction).ReservedWord()
 
Reserved?: 1
Reserved?: 1
Reserved?: 1
Reserved?: 0

此方法也可以作为存储过程从ODBC或JDBC调用:%SYSTEM.SQL_IsReservedWord("nnnn")

0
0 223
文章 姚 鑫 · 五月 12, 2021 4m read

第五章 管理全局变量(一)

管理门户提供管理全局变量的工具,系统类提供执行某些相同任务的方法。本章介绍如何使用这些工具。

一般建议

与ObjectScript命令SETMERGEKILL和其他命令一样,这里描述的工具提供了直接访问操作全局变量的权限。如果通过全局访问删除或修改,则会绕过所有对象和SQL完整性检查,并且没有撤消选项。因此,在执行这些任务时要非常小心,这一点很重要。(查看和导出不会影响数据库,并且是安全活动。)

使用本章中介绍的工具时,请确保以下事项:

  • IRIS使用哪些全局变量。并不是所有这些都被视为“系统”全局变量-也就是说,即使没有选中“系统”复选框,它们中的一些也是可见的。其中一些全局变量存储代码,包括的代码。
  • 确保知道应用程序使用哪些全局变量。

即使应用程序从不执行任何直接全局访问,应用程序也会使用全局变量。请记住,如果创建持久类,则它们的数据和任何索引都存储在全局变量中,全局变量的名称基于类名(默认情况下)。

“全局变量”页简介

管理门户包括全局页面,该页面允许管理全局。在此页上,可以执行以下操作:

  • 在该全局的行中选择View以检查它。
  • 在该全局的行中选择编辑以对其进行修改。
  • 选择导出以导出全局变量。
  • 选择导入以导入全局变量。
  • 选择删除以删除全局变量。
  • 选择查找以查找全局变量中的值。

此页还包括用于查看例程和类的选项;此处不讨论这些选项。

要从管理门户主页访问此页,请执行以下操作:

  1. 选择系统资源管理器> Globals。
  2. 选择感兴趣的命名空间或数据库:
  • 从查找列表中选择名称空间或数据库。
  • 从显示的列表中选择所需的名称空间或数据库。

选择名称空间或数据库更新页面以显示其全局变量。

  1. 如果在寻找一个特定的全局变量,但一开始没有看到它的名称:
  • 可选地指定搜索掩码。 为此,请在Globals字段中输入一个值。 如果字符串以星号“*”结束,星号将被视为通配符,页面将显示名称以星号之前的字符串开头的每个全局变量。输入值后,按“Enter”。

  • 可选地选择System项目,以在搜索中包括所有系统全局变量。

  • 可选地从Page size中选择一个值,该值控制要在任何页面上列出的全局变量的数量。

查看全局变量数据

“视图全局数据”页列出了给定全局的节点。 在这个表中,第一列显示行号,下一列列出节点,右边一列显示值。 此页面最初显示全局中的前100个节点。

要访问此页面,请显示Globals页面并选择全局名称旁边的View链接。 或者单击View按钮。

在这个页面上,你可以做以下事情:

  • 指定搜索掩码。编辑“全局搜索掩码”中的值,如下所示:
    • 要显示单个节点,请使用完整的全局引用。例如:^Sample.PersonD(9)
    • 要显示子树,请使用不带右括号的部分全局引用。例如^%SYS("JOURNAL"
    • 要显示与给定下标匹配的所有节点,请包括所需的下标,并将其他下标字段留空。例如:^IRIS.Msg(,"en")
    • 要显示与给定下标匹配的所有子树,请使用与上一个选项相同的值,但也要省略右括号。例如: ^IRIS.Msg(,"en"
    • 要显示与某个范围的下标匹配的节点,请使用subptvalue1:scriptvalue2代替下标。例如:^Sample.PersonD(50:60)
    • 与前面的选项一样,如果省略右括号,系统将显示子树。然后单击显示或按Enter键。
  • 指定要显示的不同节点数。要执行此操作,请在最大行数中输入一个整数。
  • 重复上一次搜索。要执行此操作,请在搜索历史记录下拉列表中选择搜索掩码。
  • 选择允许编辑以使数据可编辑;
  • 若要关闭此页面,请单击“取消”。

编辑全局变量

注意:在进行任何编辑之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;请参阅“一般建议”。没有撤消选项。无法恢复修改后的全局设置。

使用“编辑全局数据”页面可以编辑全局数据。在表格中,第一列显示行号,下一列列出节点,右列显示值(带有蓝色下划线表示值可以编辑)。此页面最初显示全局中的前100个节点。

要访问和使用此页,请执行以下操作:

  1. 显示“全局变量”页。
  2. 选择全局名称旁边的编辑链接。
  3. 可以选择使用全局搜索掩码字段来细化显示内容。请参阅“查看全局数据”。
  4. (可选)指定要显示的不同节点数。要执行此操作,请在最大行数中输入一个整数。
  5. 如有必要,通过选择与之对应的下标导航到要编辑的值。
  6. 选择要编辑的值。
  • 然后,页面显示两个可编辑字段:
  • 顶部字段包含正在编辑的节点的完整全局引用。例如:^Sample.PersonD("18")
  • 可以将其编辑为引用不同的全局节点。如果这样做,您的操作将影响新指定的全局节点。
  • 底部字段包含此节点的当前值。例如:
$lb("",43144,$lb("White","Orange"),$lb("8262 Elm Avenue","Islip","RI",57581),"Rogers,Emilio L.",
$lb("7430 Washington Street","Albany","GA",66833),"650-37-4263","")

根据需要编辑值。 7. 如果进行编辑,请单击保存以保存更改,或单击取消。

或者,要删除节点,请执行以下操作:

  1. 也可以选择在删除过程中删除全局子节点
  2. 单击删除。
  3. 单击确定以确认此操作。
1
0 158
文章 姚 鑫 · 六月 12, 2022 5m read

第七章 日期和时间构造

验证和转换 ODBC 日期、时间或时间戳。

大纲

{d 'yyyy-mm-dd'}
{d nnnnnn}

{t 'hh:mm:ss[.fff]'}
{t nnnnn.nnn}

{ts 'yyyy-mm-dd [hh:mm:ss.fff]'}
{ts 'mm/dd/yyyy [hh:mm:ss.fff]'}
{ts nnnnnn}

描述

这些构造采用 ODBC 日期、时间或时间戳格式的整数或字符串,并将其转换为相应的 IRIS 日期、时间或时间戳格式。他们执行数据输入以及值和范围检查。

{d 'string'}

{d 'string'}日期构造验证 ODBC 格式的日期。如果日期有效,它将以 IRIS $HOROLOG 日期格式存储(逻辑模式)作为 1840-12-31 的整数计数值。 IRIS 不附加默认时间值。要支持早于 1840-12-31 的日期,必须在表中定义数据类型为 %Library.Date(MINVAL=-nnn) 的日期字段,其中 MINVAL 是从 1840-12-31 倒数的负天数(第 0 天)最大为 -672045 (0001-01-01)

  • 小于 -672045 (0001-01-01) 或大于 2980013 (9999-12-31) 的整数会生成 SQLCODE -400 <VALUE OUT OF RANGE>错误。
  • 无效日期(例如非 ODBC 格式的日期或非闰年的日期 02-29): IRIS 生成SQLCODE -146 错误:“yyyy-mm-dd”是无效的 ODBC/JDBC 日期值”。
  • ODBC 时间戳值: IRIS 验证时间戳的日期和时间部分。如果两者都有效,则仅转换日期部分。如果日期或时间无效,系统将生成 SQLCODE -146 错误。

{t 'string'}

{t 'string'} 时间构造验证 ODBC 格式的时间。如果时间有效,它以 IRIS $HOROLOG 时间格式将其存储(逻辑模式),作为从午夜开始的整数秒计数,并带有指定的小数秒。 IRIS 显示模式和 ODBC 模式不显示小数秒;从这些显示格式中截断小数秒。

  • 小于 0 (00:00:00) 或大于 86399.99 (23:59:59.99) 的整数会生成 SQLCODE -400 <ILLEGAL VALUE> 错误。
  • 无效时间(例如不是 ODBC 格式的时间或小时数>23 的时间): IRIS 生成 SQLCODE -147 错误:“hh:mi:ss.fff& is an invalid ODBC/JDBC Time value”。
  • ODBC 时间戳值: IRIS 生成 SQLCODE -147 错误。

{ts 'string'}

{ts 'string'} 时间戳构造验证日期/时间并以 ODBC 时间戳格式返回;始终保留并显示指定的小数秒。{ts 'string'} 时间戳构造还验证日期并以 ODBC 时间戳格式返回它,并提供 00:00:00 的时间值。

  • 正整数或负整数日期(-6720452980013): IRIS 附加时间值 00:00:00,然后以 ODBC 格式存储生成的时间戳。例如,64701 返回 2018-02-22 00:00:00。这是一个有效的 $HOROLOG 日期整数。 $HOROLOG01840-12-31
  • ODBC 格式的有效时间戳: IRIS 将提供的值保持不变 这是因为 IRIS 时间戳格式与 ODBC 时间戳格式相同。
  • 使用区域设置默认日期和时间格式的有效时间戳(例如,2/29/2016 12:23:46.77): IRIS 以 ODBC 格式存储和显示提供的值。
  • 无效的时间戳(例如在非闰年中日期部分指定为 02-29 或时间部分指定小时>23 的时间戳): IRIS 返回字符串“error”作为值。
  • 没有时间值的有效日期(以 ODBC 或区域设置格式): IRIS 附加时间值 00:00:00,然后以 ODBC 格式存储生成的时间戳。它在必要时提供前导零。例如,2/29/2016 返回 2016-02-29 00:00:00
  • 没有时间值的格式正确但无效的日期(以 ODBC 或区域设置格式): IRIS 附加时间值 00:00:00。然后它存储提供的日期部分。例如,02/29/2019 返回 02/29/2019 00:00:00
  • 格式不正确且无效的日期(以 ODBC、语言环境或 $HOROLOG 格式)且没有时间值:IRIS 返回字符串“error”。例如,2/29/2019(没有前导零和无效的日期值)返回“错误”。 00234(前导零的$HOROLOG)返回“错误”

示例

以下动态 SQL 示例验证以 ODBC 格式(带或不带前导零)提供的日期并将它们存储为等效的 $HOROLOG64701。此示例显示 %SelectMode 0(逻辑)值:

/// d ##class(PHA.TEST.SQLFunction).DateTime()
ClassMethod DateTime()
{
	s myquery = 2
	s myquery(1) = "SELECT {d '2018-02-22'} AS date1,"
	s myquery(2) = "{d '2018-2-22'} AS date2"
	s tStatement = ##class(%SQL.Statement).%New()
	s tStatement.%SelectMode=0
	s tStatus = tStatement.%Prepare(.myquery)
	s rset = tStatement.%Execute()
	d rset.%Display()
}
DHC-APP> d ##class(PHA.TEST.SQLFunction).DateTime()
date1   date2
64701   64701
 
1 Rows(s) Affected

以下动态 SQL 示例验证以 ODBC 格式(带或不带前导零)提供的时间,并将它们存储为等效的 $HOROLOG43469。此示例显示 %SelectMode 0(逻辑)值:

/// d ##class(PHA.TEST.SQLFunction).DateTime1()
ClassMethod DateTime1()
{
	s myquery = 3
	s myquery(1) = "SELECT {t '12:04:29'} AS time1,"
	s myquery(2) = "{t '12:4:29'} AS time2,"
	s myquery(3) = "{t '12:04:29.00000'} AS time3"
	s tStatement = ##class(%SQL.Statement).%New()
	s tStatement.%SelectMode=0
	s tStatus = tStatement.%Prepare(.myquery)
	s rset = tStatement.%Execute()
	d rset.%Display()
}
DHC-APP>d ##class(PHA.TEST.SQLFunction).DateTime1()
time1   time2   time3
43469   43469   43469
 
1 Rows(s) Affected

以下动态 SQL 示例使用小数秒验证以 ODBC 格式提供的时间,并将它们存储为等效的 $HOROLOG43469 并附加小数秒。尾随零被截断。此示例显示 %SelectMode 0(逻辑)值:

/// d ##class(PHA.TEST.SQLFunction).DateTime2()
ClassMethod DateTime2()
{
	s myquery = 3
	s myquery(1) = "SELECT {t '12:04:29.987'} AS time1,"
	s myquery(2) = "{t '12:4:29.987'} AS time2,"
	s myquery(3) = "{t '12:04:29.987000'} AS time3"
	s tStatement = ##class(%SQL.Statement).%New()
	s tStatement.%SelectMode=0
	s tStatus = tStatement.%Prepare(.myquery)
	s rset = tStatement.%Execute()
	d rset.%Display()
}
DHC-APP>d ##class(PHA.TEST.SQLFunction).DateTime2()
time1   time2   time3
43469.987       43469.987       43469.987
 
1 Rows(s) Affected

以下动态 SQL 示例以多种格式验证时间和日期值,并将它们存储为等效的 ODBC 时间戳。必要时提供时间值 00:00:00。此示例显示 ac%SelectMode 0(逻辑)值:

/// d ##class(PHA.TEST.SQLFunction).DateTime3()
ClassMethod DateTime3()
{
	s myquery = 6
	s myquery(1) = "SELECT {ts '2018-02-22 01:43:38'} AS ts1,"
	s myquery(2) = "{ts '2018-02-22'} AS ts2,"
	s myquery(3) = "{ts '02/22/2018 01:43:38.999'} AS ts3,"
	s myquery(4) = "{ts '2/22/2018 01:43:38'} AS ts4,"
	s myquery(5) = "{ts '02/22/2018'} AS ts5,"
	s myquery(6) = "{ts '64701'} AS ts6"
	s tStatement = ##class(%SQL.Statement).%New()
	s tStatement.%SelectMode=0
	s tStatus = tStatement.%Prepare(.myquery)
	s rset = tStatement.%Execute()
	if rset.%Next() {
		w rset.ts1,!
		w rset.ts2,!
		w rset.ts3,!
		w rset.ts4,!
		w rset.ts5,!
		w rset.ts6
	}
}
DHC-APP>d ##class(PHA.TEST.SQLFunction).DateTime3()
2018-02-22 01:43:38
2018-02-22 00:00:00
2018-02-22 01:43:38.999
2018-02-22 01:43:38
2018-02-22 00:00:00
2018-02-22 00:00:00
0
0 255
文章 姚 鑫 · 六月 11, 2022 4m read

第六章 数据类型(五)

数据类型的整数代码

在查询元数据和其他上下文中,为列定义的数据类型可以作为整数代码返回。 CType(客户端数据类型)整数代码列在 %SQL.StatementColumnclientType 属性中。

ODBC 和 JDBC 使用 xDBC 数据类型代码 (SQLType)。 ODBC 数据类型代码由 %SQL.Statement.%Metadata.columns.GetAt() 方法返回,如上例所示。 SQL Shell 元数据还返回 ODBC 数据类型代码。 JDBC 代码与 ODBC 代码相同,除了时间和日期数据类型的表示。下面列出了这些 ODBC 和 JDBC 值:

ODBCJDBCData Type
-11-11GUID
-7-7BIT
-6-6TINYINT
-5-5BIGINT
-4-4LONGVARBINARY
-3-3VARBINARY
-2-2BINARY
-1-1LONGVARCHAR
00Unknown type
11CHAR
22NUMERIC
33DECIMAL
44INTEGER
55SMALLINT
66FLOAT
77REAL
88DOUBLE
991DATE
1092TIME
1193TIMESTAMP
1212VARCHAR

IRIS 还支持使用多字节字符集的 ODBC 应用程序的 Unicode SQL 类型,例如中文、希伯来语、日语或韩语语言环境。

ODBCData Type
-10WLONGVARCHAR
-9WVARCHAR

创建用户定义的 DDL 数据类型

可以通过覆盖系统数据类型参数值的数据类型映射或定义新的用户数据类型来修改数据类型集。可以修改系统数据类型以覆盖 默认映射。可以创建用户定义的数据类型以提供 不提供的其他数据类型映射。

要查看和修改或添加到当前用户数据类型映射,请转到管理门户,选择系统管理、配置、SQL 和对象设置、用户 DDL 映射。要添加用户数据类型,请选择创建新的用户定义的 DDL 映射。在显示的框中,输入名称,例如 VARCHAR(100) 和数据类型,例如 MyString100(MAXLEN=100)

结果将是用户定义的 DDL 数据类型列表中的一个条目。

可以将用户定义的数据类型创建为数据类型类。例如,可能希望创建一个最多占用 10 个字符的字符串数据类型,然后截断其余的输入数据。将创建此数据类型 Sample.TruncStr,如下所示:

Class Sample.TruncStr Extends %Library.String
  {
  Parameter MAXLEN=10;
  Parameter TRUNCATE=1;
  }

要在表定义中使用此数据类型,只需指定数据类型类名称:

CREATE TABLE Sample.ShortNames (Name Sample.TruncStr)

如前面的示例所示,有几个有用的例程可用于输入用户定义的 DDL 数据类型:

  • maxval^%apiSQL() — 给定精度和比例,返回每个 IRIS 数字数据类型的最大有效值 (MAXVAL)。语法是:
     maxval^%apiSQL(precision,scale)
  • minval^%apiSQL() — 给定精度和比例,返回每个 IRIS 数字数据类型的最小有效值 (MINVAL)。语法是:
     minval^%apiSQL(precision,scale)

如果需要将 DDL 数据类型映射到集合类型为 Stream 的 IRIS 属性,请为字符流数据指定 %Stream.GlobalCharacter,为二进制流数据指定 %Stream.GlobalBinary

如果找不到DDL映射,则传递

如果 DDL 遇到不在 SystemDataTypes 表的 DDL 数据类型列中的数据类型,它接下来会检查 UserDataTypes 表。如果任一表中的数据类型都没有出现映射,则不会发生数据类型的转换,并且数据类型会直接传递给 DDL 中指定的类定义。

例如,以下字段定义可能出现在 DDL 语句中:

     CREATE TABLE TestTable (
          Field1 %String,
          Field2 %String(MAXLEN=45)
          )

鉴于上述定义,如果 DDL 在 SystemDataTypesUserDataTypes 中找不到 %String%String(MAXLEN=%1)%String(MAXLEN=45) 的映射,则传递 %String%String(MAXLEN=45) 类型直接到适当的类定义。

转换数据类型

要将数据从一种数据类型转换为另一种数据类型,请使用 CAST 或 CONVERT 函数。

CAST 支持转换为多种字符串和数字数据类型,以及 DATETIME 以及 TIMESTAMPPOSIXTIME 时间戳数据类型。

CONVERT 有两种语法形式。两种形式都支持与 DATETIME 以及 TIMESTAMPPOSIXTIME 时间戳数据类型之间的转换,以及其他数据类型之间的转换。

VARCHAR 的 CAST 和 CONVERT 处理

VARCHAR 数据类型(没有指定大小)映射到 1 个字符的 MAXLEN,如上表所示。但是,当 CASTCONVERT 将值转换为 VARCHAR 时,默认大小映射为 30 个字符。提供此 30 个字符的默认大小是为了与非 IRIS 软件要求兼容。

0
0 105
文章 姚 鑫 · 六月 10, 2022 8m read

第五章 数据类型(四)

Strings

%Library.String 数据类型支持的最大字符串长度为 3,641,144 个字符。通常,极长的字符串应分配为 %Stream.GlobalCharacter 数据类型之一。

因为 IRIS 支持 xDBC 协议 50 和更高版本,所以没有强制执行 ODBC 或 JDBC 字符串长度限制。如果 IRIS 实例和 ODBC 驱动程序支持不同的协议,则使用两个协议中较低的一个。实际使用的协议记录在 ODBC 日志中。

请注意,默认情况下 IRIS 建立系统范围的 ODBC VARCHAR 最大长度为 4096;此 ODBC 最大长度是可配置的。

列表结构

IRIS 支持列表结构数据类型 %List(数据类型类 %Library.List)。这是一种压缩的二进制格式,不会映射到 SQL 的相应本机数据类型。在其内部表示中,它对应于数据类型 VARBINARY,默认 MAXLEN32749。 IRIS 支持列表结构数据类型 %ListOfBinary(数据类型类 %Library.ListOfBinary)对应于数据类型 VARBINARY,默认 MAXLEN4096

因此,动态 SQL 不能在 WHERE 子句比较中使用 %List 数据。也不能使用 INSERTUPDATE 来设置 %List 类型的属性值。

动态 SQL 将列表结构化数据的数据类型返回为 VARCHAR。要确定查询中的字段是数据类型 %List 还是 %ListOfBinary,可以使用 select-item columns metadata isList 布尔标志。这些数据类型的 CType(客户端数据类型)整数代码是 6

如果使用 ODBC 或 JDBC 客户端,则使用 LogicalToOdbc 转换将 %List 数据投影到 VARCHAR 字符串数据。列表被投影为一个字符串,其元素由逗号分隔。这种类型的数据可以用在 WHERE 子句以及 INSERTUPDATE 语句中。请注意,默认情况下,IRIS 建立系统范围的 ODBC VARCHAR 最大长度为 4096;此 ODBC 最大长度是可配置的。

SQL 支持八种列表函数:$LIST$LISTBUILD$LISTDATA$LISTFIND$LISTFROMSTRING$LISTGET$LISTLENGTH$LISTTOSTRING。 ObjectScript 支持三个额外的列表函数:$LISTVALID 用于确定表达式是否为列表,$LISTSAME 用于比较两个列表,以及 $LISTNEXT 用于从列表中顺序检索元素。

位数据类型

BIT (%Library.Boolean) 数据类型接受 01NULL 作为有效值。

  • 在逻辑和 ODBC 模式下,唯一接受的值是 01NULL
  • 在显示模式下,DisplayToLogical 方法首先将非空输入值转换为 01,如下所示:
  • 非零数字或数字字符串 = 1,例如 3, '0.1', '-1', '7dwarves'
  • 非数字字符串 = 0。例如,“true”“false”
  • 空字符串 = 0。例如''

流数据类型

Stream 数据类型对应于 IRIS 类属性数据类型 %Stream.GlobalCharacter(用于 CLOB)和 %Stream.GlobalBinary(用于 BLOB)。这些数据类型类可以使用指定的 LOCATION 参数定义流字段,或者省略该参数并默认为系统定义的存储位置。

具有 Stream 数据类型的字段不能用作大多数 SQL 标量、聚合或一元函数的参数。尝试这样做会生成 SQLCODE -37 错误代码。

具有 Stream 数据类型的字段不能用作大多数 SQL 谓词条件的参数。尝试这样做会生成 SQLCODE -313 错误代码。

Stream 数据类型在索引中的使用以及在执行插入和更新时也受到限制。

串行数据类型

具有 SERIAL (%Library.Counter) 数据类型的字段可以采用用户指定的正整数值,或者 IRIS 可以为其分配一个连续的正整数值。 %Library.Counter 扩展了 %Library.BigInt

INSERT 操作为 SERIAL 字段指定以下值之一:

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

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

UPDATE 操作对自动分配的 SERIAL 计数器字段值没有影响。但是,使用 INSERT OR UPDATE 执行的更新会导致对 SERIAL 字段的后续插入操作跳过整数序列。

如果该字段当前没有值(NULL),或者它的值为 0,则 UPDATE 操作只能更改串行字段值。否则,将生成 SQLCODE -105 错误。 IRIS 对表中的 SERIAL 字段的数量没有限制。

ROWVERSION 数据类型

ROWVERSION 数据类型定义了一个只读字段,该字段包含一个唯一的系统分配的正整数,从 1 开始。 IRIS 分配顺序整数作为每个插入、更新或 %Save 操作的一部分。这些值不是用户可修改的。

IRIS 在命名空间范围内维护一个单行版本计数器。命名空间中包含 ROWVERSION 字段的所有表共享相同的行版本计数器。因此,ROWVERSION 字段提供行级版本控制,允许确定对命名空间中一个或多个表中的行进行更改的顺序。

每个表只能指定一个 ROWVERSION 数据类型的字段。

ROWVERSION 字段不应包含在唯一键或主键中。 ROWVERSION 字段不能是 IDKey 索引的一部分。

ROWVERSION 和 SERIAL 计数器

作为 INSERT 操作的一部分,ROWVERSIONSERIAL(%Library.Counter) 数据类型字段都从内部计数器接收顺序整数。但是这两个计数器有很大的不同,并且用于不同的目的:

  • ROWVERSION 计数器位于命名空间级别。 SERIAL 计数器位于表级别。这两个计数器完全相互独立,独立于 RowID 计数器。
  • ROWVERSION 计数器通过插入、更新或 %Save 操作递增。 SERIAL 计数器仅由插入操作递增。使用 INSERT OR UPDATE 执行的更新可能会导致 SERIAL 计数器序列出现间隙。
  • ROWVERSION 字段值不能由用户指定;该值始终由 ROWVERSION 计数器提供。如果没有为该字段指定值,则在插入期间从表的内部计数器提供一个 SERIAL 字段值。如果插入提供了一个 SERIAL 整数值,则插入该值而不是当前计数器值:
    • 如果插入提供的 SERIAL 字段值大于当前内部计数器值, IRIS 将该值插入该字段并将内部计数器重置为该值。
    • 如果插入提供的 SERIAL 字段值小于当前计数器值, IRIS 不会重置内部计数器。
    • 插入可以提供 SERIAL 字段值作为负整数或小数。 IRIS 将小数截断为其整数部分。如果提供的 SERIAL 字段值为 0NULL, IRIS 将忽略用户提供的值并插入当前的内部计数器值。
    • 不能更新现有的 SERIAL 字段值。
  • ROWVERSION 字段值始终是唯一的。因为可以插入用户指定的 SERIAL 字段值,所以必须指定 UNIQUE 字段约束以保证唯一的 SERIAL 字段值。
  • 无法重置 ROWVERSION 计数器。 TRUNCATE TABLE 重置 SERIAL 计数器;对所有行执行 DELETE 不会重置 SERIAL 计数器。
  • 每个表只允许一个 ROWVERSION 字段。可以在一个表中指定多个 SERIAL 字段。

ODBC / JDBC 公开的 DDL 数据类型

ODBC 公开了 DDL 数据类型的子集,并将其他数据类型映射到该数据类型的子集。这些映射是不可逆的。例如,语句 CREATE TABLE mytable (f1 BINARY) 创建一个 IRIS 类,该类作为 mytable (f1 VARBINARY) 投影到 ODBC。 IRIS 列表数据类型作为 VARCHAR 字符串投影到 ODBC。

ODBC 公开以下数据类型:BIGINTBITDATEDOUBLEGUIDINTEGERLONGVARBINARYLONGVARCHARNUMERICOREFPOSIXTIMESMALLINTTIMETIMESTAMPTINYINTVARBINARYVARCHAR。请注意,默认情况下 IRIS 建立系统范围的 ODBC VARCHAR 最大长度为 4096;此 ODBC 最大长度是可配置的。

当这些 ODBC/JDBC 数据类型值之一映射到 SQL 时,会发生以下操作: 使用 $DOUBLE 强制转换 DOUBLE 数据。 NUMERIC 数据使用 $DECIMAL 进行转换。

GUID 数据类型对应于 SQL UNIQUEIDENTIFIER 数据类型。未能为 GUID / UNIQUEIDENTIFIER 字段指定有效值会生成 #7212 一般错误。要生成 GUID 值,请使用 %SYSTEM.Util.CreateGUID() 方法。

查询元数据返回数据类型

可以使用动态 SQL 返回有关查询的元数据,包括查询中指定列的数据类型。

以下动态 SQL 示例为 Sample.PersonSample.Employee 中的每个列返回列名和 ODBC 数据类型的整数代码:

/// d ##class(PHA.TEST.SQLFunction).QueryMetadataReturnsDataType()
ClassMethod QueryMetadataReturnsDataType()
{
	s myquery = "SELECT * FROM Sample.Person"
	s tStatement = ##class(%SQL.Statement).%New()
	s tStatus = tStatement.%Prepare(myquery)
	s x = tStatement.%Metadata.columnCount
	while x > 0 {
		s column = tStatement.%Metadata.columns.GetAt(x)
		w !,x," ",column.colName," ",column.ODBCType
		s x = x-1 
	}
	w !,"end of columns"
}
DHC-APP>d ##class(PHA.TEST.SQLFunction).QueryMetadataReturnsDataType()
 
16 Office_Zip 12
15 Office_Street 12
14 Office_State 12
13 Office_City 12
12 Home_Zip 12
11 Home_Street 12
10 Home_State 12
9 Home_City 12
8 Spouse 4
7 SSN 12
6 Name 12
5 FavoriteColors 12
4 DOB 9
3 Age 4
2 AddDateTime 11
1 ID 4
end of columns
/// d ##class(PHA.TEST.SQLFunction).QueryMetadataReturnsDataType1()
ClassMethod QueryMetadataReturnsDataType1()
{
	s myquery = "SELECT * FROM Sample.Employee"
	s tStatement = ##class(%SQL.Statement).%New()
	s tStatus = tStatement.%Prepare(myquery)
	s x = tStatement.%Metadata.columnCount
	while x > 0 {
		s column = tStatement.%Metadata.columns.GetAt(x)
		w !,x," ",column.colName," ",column.ODBCType
		s x = x - 1 
	}
	w !,"end of columns"
}

DHC-APP>d ##class(PHA.TEST.SQLFunction).QueryMetadataReturnsDataType1()
 
20 Office_Zip 12
19 Office_Street 12
18 Office_State 12
17 Office_City 12
16 Home_Zip 12
15 Home_Street 12
14 Home_State 12
13 Home_City 12
12 Title 12
11 Spouse 4
10 Salary 4
9 SSN 12
8 Picture -4
7 Notes -1
6 Name 12
5 FavoriteColors 12
4 DOB 9
3 Company 4
2 Age 4
1 ID 4
end of columns

列出结构化数据(例如 Sample.Person 中的 FavoriteColors 列)返回数据类型 12 (VARCHAR),因为 ODBC 将 ObjectScript %List 数据类型值表示为逗号分隔值的字符串。

Steam 数据(例如 Sample.Employee 中的 NotesPicture 列)返回数据类型 -1 (LONGVARCHAR) 或 -4 (LONGVARBINARY)。

ROWVERSION 字段返回数据类型 -5,因为 %Library.RowVersion%Library.BigInt 的子类。

0
0 192
文章 姚 鑫 · 六月 9, 2022 6m read

第四章 数据类型(三)

日期、时间、PosixTime 和时间戳数据类型

可以定义日期、时间和时间戳数据类型,并通过标准 SQL 日期和时间函数相互转换日期和时间戳。例如,可以使用 CURRENT_DATECURRENT_TIMESTAMP 作为使用该数据类型定义的字段的输入,或者使用 DATEADDDATEDIFFDATENAMEDATEPART 来操作使用该数据类型存储的日期值。

数据类型类 %Library.Date%Library.Time%Library.PosixTime%Library.TimeStamp%MV.Date 对于 SqlCategory 的处理方式如下:

  1. %Library.Date 类以及逻辑值为 +$HOROLOG$HOROLOG 的日期部分)的任何用户定义数据类型类都应使用 DATE 作为 SqlCategory。默认情况下,DATE 和对应的 %Library.Date 数据类型只接受正整数,0 代表 1840-12-31。要支持早于 1840-12-31 的日期,必须在表中定义数据类型为 %Library.Date(MINVAL=-nnn) 的日期字段,其中 MINVAL 是从 1840-12-31 倒数的负天数最大为 -672045 (0001-01-01)%Library.Date 可以将日期值存储为 -6720452980013 范围内的无符号或负整数。日期值可以按如下方式输入:
  • 逻辑模式接受 +HOROLOG 整数值,例如 65619(2020 年 8 月 28 日)。
  • 显示模式使用 DisplayToLogical() 转换方法。它接受当前语言环境的显示格式的日期,例如“8/28/2020”。它还接受逻辑日期值(+HOROLOG 整数值)。
  • ODBC 模式使用 ODBCToLogical() 转换方法。它接受 ODBC 标准格式的日期,例如“2020–08–28”。它还接受逻辑日期值(+HOROLOG 整数值)。
  1. %Library.Time 类和任何逻辑值为 $PIECE($HOROLOG,”,”,2)$HOROLOG 的时间部分)的用户定义数据类型类都应使用 TIME 作为 SqlCategory%Library.Time 将时间值存储为 086399 范围内的无符号整数(自午夜以来的秒数)。时间值可以按如下方式输入:
  • 逻辑模式接受$PIECE($HOROLOG,”,”,2) 整数值,例如 84444 (23:27:24)
  • 显示模式使用 DisplayToLogical() 转换方法。它接受当前语言环境的显示格式的时间,例如“23:27:24”
  • ODBC 模式使用 ODBCToLogical() 转换方法。它接受 ODBC 标准格式的时间,例如“23:27:24”。它还接受逻辑时间值(086399 范围内的整数)。

TIME 支持小数秒,因此此数据类型也可用于 HH:MI:SS.FF 到用户指定的精度 (F) 小数位数,最多为 9。要支持小数秒,请设置 PRECISION范围。例如,TIME(0) (%Time(PRECISION=0)) 舍入到最接近的秒数;TIME(2) (%Time(PRECISION=2)) 将(或零填充)四舍五入到精度的两位小数。

如果提供的数据还指定了精度(例如,CURRENT_TIME(3)),则存储的小数位数如下:

  • 如果 TIME 未指定精度,而数据指定了精度,则使用数据的精度。
  • 如果 TIME 未指定精度且数据未指定精度,则使用系统范围配置的时间精度。
  • 如果 TIME 指定精度而数据未指定精度,则使用系统范围配置的时间精度作为数据精度。
  • 如果 TIME 指定了精度并且数据精度小于 TIME 精度,则使用数据精度。
  • 如果 TIME 指定了精度并且数据精度大于 TIME 精度,则使用 TIME 精度。

SQL 元数据将时间精度的小数位报告为“scale”;它使用“precision精度”一词来表示数据的总长度。使用 TIME 数据类型的字段报告精度和比例元数据如下:TIME(0)(%Time(PRECISION=0)) 的元数据精度为 8 (nn:nn:nn),比例为 0。TIME(2 ) (%Time(PRECISION=2)) 的元数据精度为 11 (nn:nn:nn.ff),比例为 2TIME (%Time 或 %Time(PPRECISION="") 采用其小数秒精度来自提供的数据,因此元数据精度为 18 和未定义的比例。

  1. %Library.PosixTime 类和任何具有编码的有符号 64 位整数逻辑值的用户定义数据类型类都应使用 POSIXTIME 作为 SqlCategory%PosixTime 是从 1970–01–01 00:00:00 以来的秒数(和小数秒)计算的编码时间戳。该日期之后的时间戳由正 %PosixTime 值表示,该日期之前的时间戳由负 %PosixTime 值表示。 %PosixTime 支持最多 6 位精度的小数秒。 %PosixTime 支持的最早日期为 0001-01-01 00:00:00,其逻辑值为 -6979664624441081856。支持的最后日期为 9999-12-3123:59:59.999999,其逻辑值为 1406323805406846975

因为 %PosixTime 值始终由编码的 64 位整数表示,所以它始终可以明确地区分于 %Date%TimeStamp 值。例如,1970–01–01 00:00:00%PosixTime 值为 11529215046068469762017–01–01 00:00:00%PosixTime 值为 11544047334068469761969–12–01%PosixTime00:00:00-6917531706041081856

%PosixTime%TimeStamp 更可取,因为它比 %TimeStamp 数据类型占用更少的磁盘空间和内存,并且提供比 %TimeStamp 更好的性能。

可以使用 ODBC 显示模式集成 %PosixTime%TimeStamp 值:

  • %PosixTime%TimeStamp 数据类型的逻辑模式值完全不同:%PosixTime 是有符号整数,%TimeStamp 是包含 ODBC 格式时间戳的字符串。
  • 显示方式:%PosixTime显示使用当前locale时间和日期格式参数(例如02/22/2018 08:14:11); %TimeStamp 显示为 ODBC 格式的时间戳。
  • ODBC 模式:%PosixTime%TimeStamp 都显示为 ODBC 格式的时间戳。精度的小数位数可能不同。

可以使用 TO_POSIXTIME 函数或 TOPOSIXTIME() 方法将 %TimeStamp 值转换为 %PosixTime。可以使用 IsValid() 方法来确定数值是否为有效的 %PosixTime 值。

  1. %Library.TimeStamp 类和任何具有 YYYY-MM-DD HH:MI:SS.FF 逻辑值的用户定义数据类型类都应使用 TIMESTAMP 作为 SqlCategory。请注意,%Library.TimeStamp 的最大精度来自系统平台的精度,最多为 9 位小数秒,而 %Library.PosixTime 的最大精度为 6 位。因此,在某些平台上,%Library.TimeStamp 可能比 %Library.PosixTime 更精确。 %Library.TimeStamp 规范化会自动将精度超过 9 位的输入值截断为 9 位小数秒。
  2. %Library.DateTime%Library.TimeStamp 的子类。它定义了一个名为 DATEFORMAT 的类型参数,它覆盖了 DisplayToLogical()OdbcToLogical() 方法来处理 TSQL 应用程序习惯的不精确的日期时间输入。
  3. %MV.Date 类,或任何具有 $HOROLOG-46385 逻辑日期值的用户定义数据类型类,应使用 MVDATE 作为 SqlCategory
  4. 不适合上述任何逻辑值的用户定义日期数据类型应将数据类型的 SqlCategory 定义为 DATE,并在数据类型类中提供 LogicalToDate() 方法以将用户定义的逻辑日期值转换为%Library.Date 逻辑值和 DateToLogical() 方法,用于将 %Library.Date 逻辑值转换为用户定义的逻辑日期值。
  5. 不适合上述任何逻辑值的用户定义时间数据类型应将数据类型的 SqlCategory 定义为 TIME,并在数据类型类中提供 LogicalToTime() 方法以将用户定义的逻辑时间值转换为%Library.Time逻辑值和 TimeToLogical() 方法,用于将 %Library.Time 逻辑值转换为用户定义的逻辑时间值。
  6. 不适合上述任何逻辑值的用户定义时间戳数据类型应将数据类型的 SqlCategory 定义为 TIMESTAMP,并在数据类型类中提供 LogicalToTimeStamp() 方法以将用户定义的逻辑时间戳值转换为%Library.TimeStamp 逻辑值和 TimeStampToLogical() 方法,用于将 %Library.TimeStamp 逻辑值转换为用户定义的逻辑时间戳值。

可以使用 =, <>,>, < 运算符将 POSIXTIMEDATETIMESTAMP 值进行比较。

在将 FMTIMESTAMP 类别值与 DATE 类别值进行比较时, IRIS 在将其与 DATE 进行比较之前不会从 FMTIMESTAMP 值中去除时间。这与比较 TIMESTAMPDATE 值以及比较 TIMESTAMPMVDATE 值的行为相同。它还与其他 SQL 供应商比较时间戳和日期的方式兼容。这意味着当使用 SQL 相等 (=) 运算符进行比较时,FMTIMESTAMP 320110202.12DATE 62124 的比较相等。应用程序必须将 FMTIMESTAMP 值转换为 DATEFMDATE 值以仅比较值的日期部分。

1840 年 12 月 31 日之前的日期

日期通常由 DATE 数据类型或 TIMESTAMP 数据类型表示。

DATE 数据类型以 $HOROLOG 格式存储日期,作为从 1840 年 12 月 31 日的任意开始日期算起的正整数天数。默认情况下,日期只能由正整数 (MINVAL=0) 表示,它对应于到 1840 年 12 月 31 日。但是,可以更改 %Library.Date MINVAL 类型参数以启用存储 1840 年 12 月 31 日之前的日期。通过将 MINVAL 设置为负数,可以存储 12 月 31 日之前的日期, 1840 为负整数。最早允许的 MINVAL 值为 -672045。这对应于第 1 年 (CE) 的 1 月 1 日。 DATE 数据类型不能表示 BCE(也称为 BC)日期。

TIMESTAMP 数据类型默认为 1840–12–31 00:00:00 作为最早允许的时间戳。但是,可以更改 MINVAL 参数以定义可以存储 1840 年 12 月 31 日之前的日期的字段或属性。例如,MyTS %Library.TimeStamp(MINVAL='1492-01-01 00:00:00')。最早允许的 MINVAL 值是 0001–01–01 00:00:00。这对应于第 1 年 (CE) 的 1 月 1 日。 %TimeStamp 数据类型不能表示 BCE(也称为 BC)日期。

注意:请注意,这些日期计算并未考虑公历改革(1582 年 10 月 15 日颁布,但直到 1752 年才在英国及其殖民地采用)引起的日期变化。

可以重新定义语言环境的最短日期,如下所示:

	s oldMinDate = ##class(%SYS.NLS.Format).GetFormatItem("DATEMINIMUM")
	if oldMinDate = 0 {
		d ##class(%SYS.NLS.Format).SetFormatItem("DATEMINIMUM",-672045)
		s newMinDate = ##class(%SYS.NLS.Format).GetFormatItem("DATEMINIMUM")
		w "Changed earliest date to ",newMinDate
	} else { 
		w "Earliest date was already reset to ",oldMinDate
	}

上面的示例将语言环境的 MINVAL 设置为允许的最早日期 (1/1/01)。

注意:IRIS 不支持使用带有负逻辑 DATE 值的儒略日期(%Library.Date 值,MINVAL<0)。因此,这些 MINVAL<0 值与 TO_CHAR 函数返回的儒略日期格式不兼容。

0
0 189
文章 姚 鑫 · 六月 8, 2022 4m read

第三章 数据类型(二)

SQL 系统数据类型映射

上表中为 DDL 和 IRIS 数据类型表达式显示的语法是为 SQL.SystemDataTypes 配置的默认映射。对于提供的系统数据类型和用户数据类型,有单独的映射表可用。

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

了解 DDL 数据类型映射

将数据类型从 DDL 映射到 IRIS 时,常规参数和函数参数遵循以下规则:

  • 常规参数 - 这些在 DDL 数据类型和 IRIS 数据类型中以 %# 格式标识。例如:
     VARCHAR(%1)

映射到:

     %String(MAXLEN=%1)

因此,DDL 数据类型为:

     VARCHAR(10)

映射到:

     %String(MAXLEN=10)
  • 函数参数 — 当 DDL 数据类型中的参数必须经过一些转换才能放入 IRIS 数据类型中时,使用这些参数。这方面的一个例子是将 DDL 数据类型的数值精度和比例参数转换为 IRIS 数据类型的 MAXVALMINVALSCALE 参数。例如:
     DECIMAL(%1,%2)

映射到:

     %Numeric(MAXVAL=<|'$$maxval^%apiSQL(%1,%2)'|>,
              MINVAL=<|'$$minval^%apiSQL(%1,%2)'|>,
              SCALE=%2)

DDL 数据类型 DECIMAL 采用参数 Precision (%1)Scale (%2),但IRIS 数据类型 %Numeric 没有精度参数。因此,要将 DECIMAL 转换为 %Numeric,必须将 Precision 参数转换为适当的 %Numeric 参数,在这种情况下,通过将IRIS 函数格式、maxvalminval 应用于 DECIMAL 提供的参数。特殊的 <|'xxx'|>语法(如上所示)指示 DDL 处理器进行参数替换,然后使用提供的值调用函数。 <|'xxx'|> 表达式随后被函数调用返回的值替换。

考虑这个具有实际值的示例,可能存在精度为 4 位、小数位数为 2 的 DECIMAL 数据类型:

     DECIMAL(4,2)

映射到:

     %Numeric(MAXVAL=<|'$$maxval^%apiSQL(4,2)'|>,
              MINVAL=<|'$$minval^%apiSQL(4,2)'|>,
              SCALE=2)

计算为:

     %Numeric(MAXVAL=99.99,MINVAL=-99.99,SCALE=2)
  • 附加参数——数据类型类可以定义不能使用 DDL 数据类型定义的附加数据定义参数。这些包括数据验证操作,例如允许的数据值的枚举列表、允许的数据值的模式匹配以及超过 MAXLEN 最大长度的数据值的自动截断。

数据类型优先级

当一个操作可以返回多个不同的值,并且这些值可能具有不同的数据类型时,IRIS 将返回值分配给具有最高优先级的数据类型。例如,NUMERIC 数据类型可以包含所有可能的 INTEGER 数据类型值,但 INTEGER 数据类型不能包含所有可能的 NUMERIC 数据类型值。因此 NUMERIC 具有更高的优先级(更具包容性)。

例如,如果 CASE 语句有一个数据类型为 INTEGER 的可能结果值,以及一个数据类型为 NUMERIC 的可能结果值,则无论采用这两种情况中的哪一种,实际结果始终为 NUMERIC 类型。

数据类型的优先级如下,从最高(包括最多)到最低:

LONGVARBINARY
LONGVARCHAR
VARBINARY
VARCHAR
GUID
TIMESTAMP
DOUBLE
NUMERIC
BIGINT
INTEGER
DATE
TIME
SMALLINT
TINYINT
BIT

规格化和验证

%Library.DataType超类包含特定数据类型的类。这些数据类型类提供了 Normalize() 方法来将输入值规范化为数据类型格式,并提供 IsValid() 方法来确定输入值是否对该数据类型有效,以及各种模式转换方法,例如 LogicalToDisplay( )DisplayToLogical()

以下示例显示了%TimeStamp 数据类型的 Normalize() 方法:

/// d ##class(PHA.TEST.SQLFunction).NormalizeValidate()
ClassMethod NormalizeValidate()
{
	s indate = 64701
	s tsdate = ##class(%Library.TimeStamp).Normalize(indate)
	w "%TimeStamp date: ",tsdate
}
DHC-APP>d ##class(PHA.TEST.SQLFunction).NormalizeValidate()
%TimeStamp date: 2018-02-22 00:00:00
/// d ##class(PHA.TEST.SQLFunction).NormalizeValidate2()
ClassMethod NormalizeValidate2()
{
	s indate = "2018-2-22"
	s tsdate = ##class(%Library.TimeStamp).Normalize(indate)
	w "%TimeStamp date: ",tsdate
}
DHC-APP> d ##class(PHA.TEST.SQLFunction).NormalizeValidate2()
%TimeStamp date: 2018-02-22 00:00:00

以下示例显示了 %TimeStamp 数据类型的 IsValid() 方法:

/// d ##class(PHA.TEST.SQLFunction).NormalizeValidate3()
ClassMethod NormalizeValidate3()
{
	s datestr = "July 4, 2018"
	s stat = ##class(%Library.TimeStamp).IsValid(datestr)
	if stat = 1 {
		w datestr," is a valid %TimeStamp",! 
	} else {
		w datestr," is not a valid %TimeStamp",!
	}
}
DHC-APP> d ##class(PHA.TEST.SQLFunction).NormalizeValidate3()
July 4, 2018 is not a valid %TimeStamp
/// d ##class(PHA.TEST.SQLFunction).NormalizeValidate4()
ClassMethod NormalizeValidate4()
{
	s leapdate = "2016-02-29 00:00:00"
	s noleap = "2018-02-29 00:00:00"
	s stat = ##class(%Library.TimeStamp).IsValid(leapdate)
	if stat = 1 {
		w leapdate," is a valid %TimeStamp",! 
	} else {
		w leapdate," is not a valid %TimeStamp",!
	}
	s stat = ##class(%Library.TimeStamp).IsValid(noleap)
	if stat = 1 {
		w noleap," is a valid %TimeStamp",! 
	} else {
		w noleap," is not a valid %TimeStamp",!
	}
}
DHC-APP> d ##class(PHA.TEST.SQLFunction).NormalizeValidate4()
2016-02-29 00:00:00 is a valid %TimeStamp
2018-02-29 00:00:00 is not a valid %TimeStamp
0
0 117
文章 姚 鑫 · 六月 7, 2022 9m read

第二章 数据类型(一)

指定 SQL 实体(如列)可以包含的数据类型。

描述

此处描述了以下主题:

  • 支持的 DDL 数据类型及其类属性映射表

  • 数据类型优先级用于从具有不同数据类型的数据值中选择最具包容性的数据类型

  • 日期、时间、PosixTime 和时间戳数据类型

    • 使用SqlCategory和用户定义的标准
    • 对 1840 年 12 月 31 日之前的日期的可配置支持
  • 支持字符串数据类型、列表数据类型和流数据类型

  • 支持 ROWVERSION 数据类型

  • IRIS® 数据平台 ODBC / JDBC 公开的数据类型

  • 使用查询元数据方法和数据类型整数代码确定列的数据类型

  • 创建用户定义的数据类型

  • 处理未定义的数据类型

  • 数据类型转换函数

数据类型指定列可以保存的值的种类。在使用 CREATE TABLEALTER TABLE 定义字段时指定数据类型。定义 SQL 字段时,可以指定下表(左列)中列出的 DDL 数据类型。当指定其中一种 DDL 数据类型时,它会映射到右侧列中列出的IRIS 数据类型类。在IRIS 中定义字段时,可以指定 DDL 数据类型或数据类型类。 DDL 数据类型名称不区分大小写。数据类型类名称区分大小写。 %Library 数据类型类可以通过全名(例如,%Library.String)或短名(%String)来指定。

它们映射到的 DDL 数据类型和数据类型类通常提供不同的参数和参数默认值。数据类型类通常提供比 DDL 数据类型更多的参数来定义允许的数据值。

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

image

还可以定义其他用户数据类型。要创建或查看用户数据类型映射,请转到管理门户,选择系统管理、配置、SQL 和对象设置、用户 DDL 映射。

DDL 数据类型表

DDL Data TypeCorresponding IRIS Data Type Class
BIGINT%Library.BigInt (MAXVAL=9223372036854775807, MINVAL=-9223372036854775807)如果 BIGINT 列可以同时包含 NULL 和极小的负数,需要重新定义索引空标记以支持标准索引排序规则。
BIGINT(%1)%Library.BigInt %1 被忽略。相当于 BIGINT。提供 MySQL 兼容性。
BINARY%Library.Binary(MAXLEN=1)
BINARY(%1)%Library.Binary(MAXLEN=%1)
BINARY VARYING%Library.Binary(MAXLEN=1)
BINARY VARYING(%1)%Library.Binary(MAXLEN=%1)
BIT%Library.Boolean .
CHAR%Library.String(MAXLEN=1)
CHAR(%1)%Library.String(MAXLEN=%1)
CHAR VARYING%Library.String(MAXLEN=1)
CHAR VARYING(%1)%Library.String(MAXLEN=%1)
CHARACTER%Library.String(MAXLEN=1)
CHARACTER VARYING%Library.String(MAXLEN=1)
CHARACTER VARYING(%1)%Library.String(MAXLEN=%1)
CHARACTER(%1)%Library.String(MAXLEN=%1)
DATE%Library.Date
DATETIME%Library.DateTime
DATETIME2%Library.DateTime
DEC%Library.Numeric MAXVAL=999999999999999, MINVAL=-999999999999999, SCALE=0.
DEC(%1)%Library.Numeric 一个 64 位有符号整数。如果 %1 小于 19,则 MAXVALMINVAL%1 位数。例如,DEC(8) MAXVAL=99999999,MINVAL=-99999999,SCALE=0%1 的最大有意义值是 19%1 值大于 19 不会产生错误,但默认为 19。如果 %119 或更大:MAXVAL=9223372036854775807,MINVAL=-9223372036854775808,SCALE=0。
DEC(%1,%2)%Library.Numeric (`MAXVAL=<
DECIMAL%Library.Numeric MAXVAL=999999999999999, MINVAL=-999999999999999, SCALE=0.
DECIMAL(%1)%Library.Numeric 一个 64 位有符号整数。如果 %1 小于 19,则 MAXVAL 和 MINVAL 是 %1 位数。例如,DECIMAL(8) MAXVAL=99999999,MINVAL=-99999999,SCALE=0。 %1 的最大有意义值是 19; %1 值大于 19 不会产生错误,但默认为 19。如果 %1 为 19 或更大:MAXVAL=9223372036854775807,MINVAL=-9223372036854775808,SCALE=0。
DECIMAL(%1,%2)%Library.Numeric (`MAXVAL=<
DOUBLE%Library.Double 这是 IEEE 浮点标准。具有此数据类型的 SQL 列返回的默认精度为 20。
DOUBLE PRECISION%Library.Double 这是 IEEE 浮点标准。具有此数据类型的 SQL 列返回的默认精度为 20。
FLOAT已弃用 — %Library.Double 这是 IEEE 浮点标准。具有此数据类型的 SQL 列返回的默认精度为 20。 FLOAT(%1) 已弃用 — %Library.Double 这是 IEEE 浮点标准。具有此数据类型的 SQL 列返回的默认精度为 20。
IMAGE%Stream.GlobalBinary
INT%Library.Integer (MAXVAL=2147483647, MINVAL=-2147483648)
INT(%1)%Library.Integer (MAXVAL=2147483647, MINVAL=-2147483648)。 %1 被忽略。相当于 INT。提供 MySQL 兼容性。
INTEGER%Library.Integer (MAXVAL=2147483647, MINVAL=-2147483648)
LONG%Stream.GlobalCharacter
LONG BINARY%Stream.GlobalBinary
LONG RAW%Stream.GlobalBinary
LONGTEXT%Stream.GlobalCharacter 等效于 LONG。提供 MySQL 兼容性。
LONG VARCHAR%Stream.GlobalCharacter
LONG VARCHAR(%1)%Stream.GlobalCharacter The %1 is ignored.
LONGVARBINARY%Stream.GlobalBinary
LONGVARBINARY(%1)%Stream.GlobalBinary The %1 is ignored.
LONGVARCHAR%Stream.GlobalCharacter
LONGVARCHAR(%1)%Stream.GlobalCharacter The %1 is ignored.
MEDIUMINT%Library.Integer(MAXVAL=8388607,MINVAL=-8388608) 提供 MySQL 兼容性。
MEDIUMINT(%1)%Library.Integer(MAXVAL=8388607,MINVAL=-8388608) %1 被忽略。提供 MySQL 兼容性。
MEDIUMTEXT%Stream.GlobalCharacter
MONEY%Library.Currency(MAXVAL=922337203685477.5807, MINVAL=-922337203685477.5808, SCALE=4)
NATIONAL CHAR%Library.String(MAXLEN=1)
NATIONAL CHAR(%1)%Library.String(MAXLEN=%1)
NATIONAL CHAR VARYING%Library.String(MAXLEN=1)
NATIONAL CHAR VARYING(%1)%Library.String(MAXLEN=%1)
NATIONAL CHARACTER%Library.String(MAXLEN=1)
NATIONAL CHARACTER(%1)%Library.String(MAXLEN=%1)
NATIONAL CHARACTER VARYING%Library.String(MAXLEN=1)
NATIONAL CHARACTER VARYING(%1)%Library.String(MAXLEN=%1)
NATIONAL VARCHAR%Library.String(MAXLEN=1)
NATIONAL VARCHAR(%1)%Library.String(MAXLEN=%1)
NCHAR%Library.String(MAXLEN=1)
NCHAR(%1)%Library.String(MAXLEN=%1)
NTEXT%Stream.GlobalCharacter
NUMBER%Library.Numeric 一个 64 位有符号整数。 (MAXVAL=9223372036854775807, MINVAL=-9223372036854775808, SCALE=0)
NUMBER(%1)%Library.Numeric 一个 64 位有符号整数。如果 %1 小于 19,则 MAXVAL 和 MINVAL 是 %1 位数。例如,NUMBER(8) MAXVAL=99999999,MINVAL=-99999999,SCALE=0。 %1 的最大有意义值是 19; %1 值大于 19 不会产生错误,但默认为 19。如果 %1 为 19 或更大:MAXVAL=9223372036854775807,MINVAL=-9223372036854775808,SCALE=0。
NUMBER(%1,%2)%Library.Numeric (`MAXVAL=<
NUMERIC%Library.Numeric MAXVAL=999999999999999, MINVAL=-999999999999999, SCALE=0.
NUMERIC(%1)%Library.Numeric 一个 64 位有符号整数。如果 %1 小于 19,则 MAXVAL 和 MINVAL 是 %1 位数。例如,NUMERIC(8) MAXVAL=99999999,MINVAL=-99999999,SCALE=0。 %1 的最大有意义值是 19; %1 值大于 19 不会产生错误,但默认为 19。如果 %1 为 19 或更大:MAXVAL=9223372036854775807,MINVAL=-9223372036854775808,SCALE=0。
NUMERIC(%1,%2)%Library.Numeric (`MAXVAL=<
NVARCHAR%Library.String(MAXLEN=1)
NVARCHAR(%1)%Library.String(MAXLEN=%1)
NVARCHAR(%1,%2)%Library.String(MAXLEN=%1)
NVARCHAR(MAX)%Stream.GlobalCharacter 等效于 LONGVARCHAR。提供 TSQL 兼容性。
POSIXTIME%Library.PosixTime MAXVAL=1406323805406846975, MINVAL=-6979664624441081856, SCALE=0.
RAW(%1)%Library.Binary(MAXLEN=%1) REAL 已弃用 — %Library.Double 这是 IEEE 浮点标准。具有此数据类型的 SQL 列返回的默认精度为 20。
ROWVERSION%Library.RowVersion(MAXVAL=9223372036854775807, MINVAL=1) 系统分配的顺序整数。
SERIAL%Library.Counter System-generated: (MAXVAL=9223372036854775807, MINVAL=1). User-supplied: (MAXVAL=9223372036854775807, MINVAL=-9223372036854775807)
SMALLDATETIME%Library.DateTime MAXVAL=’2079-06-06 23:59:59’; MINVAL=’1900-01-01 00:00:00’)
SMALLINT%Library.SmallInt (MAXVAL=32767, MINVAL=-32768)
SMALLINT(%1)%Library.SmallInt %1 被忽略。相当于 SMALLINT。提供 MySQL 兼容性。
SMALLMONEY%Library.Currency SCALE=4
SYSNAME%Library.String(MAXLEN=128)
TEXT%Stream.GlobalCharacter
TIME%Library.Time
TIME(%1)%Library.Time(精度=%1)。 PRECISION 是小数秒位数,一个介于 0 到 9 之间的整数值。
TIMESTAMP%Library.PosixTime
TIMESTAMP2%Library.TimeStamp
TINYINT%Library.TinyInt (MAXVAL=127, MINVAL=-128)
TINYINT(%1)%Library.TinyInt %1 被忽略。相当于 TINYINT。提供 MySQL 兼容性。
UNIQUEIDENTIFIER)%Library.UniqueIdentifier
VARBINARY)%Library.Binary(MAXLEN=1)
VARBINARY(%1))%Library.Binary(MAXLEN=%1)
VARCHAR )%Library.String(MAXLEN=1)
VARCHAR(%1) )%Library.String(MAXLEN=%1)
VARCHAR(%1,%2))%Library.String(MAXLEN=%1)
VARCHAR2(%1))%Library.String(MAXLEN=%1)
VARCHAR(MAX))%Stream.GlobalCharacter 等效于 LONGVARCHAR。仅提供 TSQL 兼容性。

重要提示:上面显示的每个 DDL 或 IRIS 数据类型表达式实际上都是一个连续的字符串。这些字符串可能包含空格字符,但通常不包含任何类型的空格。为了便于阅读,此表中出现了一些空白。

指定 MAXLEN

  • MAXLEN:没有 MAXLEN 值的字段可以取任意长度的值,最多为最大字符串长度。要定义最大长度的字符串字段,请指定 VARCHAR(''),这将创建数据类型为 %Library.String(MAXLEN="") 的属性。 VARCHAR() 创建数据类型为 %Library.String(MAXLEN=1) 的属性。要定义没有 MAXLEN 值的二进制字段,请指定 VARBINARY(''),这将创建数据类型为 %Library.Binary(MAXLEN="") 的属性。 VARBINARY() 创建数据类型为 %Library.Binary(MAXLEN=1) 的属性。
  • MAXLEN具有大 MAXLEN 值的字段仅分配实际数据值所需的空间。指定 %Library.String 数据类型时,指定的 MAXLEN 值不必与数据的实际大小密切对应。如果字段值为“ABC”, 仅使用磁盘、全局缓冲区和私有进程内存中的那么多空间。即使使用 MAXLEN=1000 声明该字段,私有进程内存也不会为该字段分配那么多空间。 只为字段值的实际大小分配内存,而不管声明的长度如何。

过大的 MAXLEN 值可能会影响 ODBC 应用程序。 ODBC 应用程序尝试根据来自服务器的元数据来决定所需字段的大小,因此应用程序可能会分配比实际需要更多的缓冲区空间。出于这个原因, 提供系统范围的默认 ODBC VARCHAR 最大长度 4096;此系统范围的默认设置可使用管理门户进行配置:从系统管理中选择配置,然后选择 SQL 和对象设置,然后选择 SQL。查看或设置 VARCHAR 选项的默认长度。要确定当前设置,请调用 $SYSTEM.SQL.CurrentSettings()。 ODBC 驱动程序从 TCP 缓冲区获取数据并将其转换为应用程序缓冲区,因此 MAXLEN 大小不会影响我们的 ODBC 客户端。

过大的 MAXLEN 值不应影响 JDBC 应用程序。 Java 和 .Net 没有应用程序分配缓冲区。客户端仅分配将数据保存为本机类型所需的内容。

精确度和范围

NUMERIC(6,2) 等数值数据类型具有两个整数值 (p,s) 精度和小数位数。这些映射到 ObjectScript %Library 类数据类型。在 SQL 数据类型中指定时,以下内容适用于 Windows 系统(最大值在其他系统上可能不同):

  • Precision 精度:019+s(含)之间的整数。该值确定最大和最小允许值。这通常是数字中的总位数;但是,其确切值由 %Library 类数据类型映射决定。最大整数值为 9223372036854775807。大于 19+s 的精度默认为 19+s
  • Scale:一个整数,指定允许的最大十进制(小数)位数。可以是正整数、0 或负整数。如果 s 大于或等于 p,则只允许小数,实际 p 值被忽略。允许的最大比例为 18,对应于 0.999999999999999999。大于 18 的比例默认为 18。

以下示例显示了精度和比例的不同组合的最大值:

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

DHC-APP>d ##class(PHA.TEST.SQLFunction).PrecisionScale()
Max for (0,2)=.99
Max for (1,2)=.99
Max for (2,2)=.99
Max for (3,2)=9.99
Max for (4,2)=99.99
Max for (5,2)=999.99
Max for (6,2)=9999.99
0
0 179
文章 Michael Lei · 六月 7, 2022 3m read

绕过ODBC使用ObjectScript访问远程系统

这是一个在 IRIS 2020.1 和 Caché 2018.1.3 上工作的代码示例
不会与新版本保持同步
也不会获得 InterSystems 提供的支持服务!   

我们经常会遇到这样的情况,由于各种原因ODBC是访问一个远程系统的唯一选择。如果你只需要检查或改变表,这就足够了。但你不能直接执行一些命令或改变一些Global。

特别感谢@Anna Golitsyna 启发我发表此文。

这个例子提供了3种 SQLprocedure 方法来实现访问远程系统这个目的,如果其他的访问方式被阻止,通常是被一些防火墙阻止。

0
0 148
文章 姚 鑫 · 六月 3, 2022 2m read

第161章 SQL函数 YEAR

返回日期表达式的年份的日期函数。

大纲

YEAR(date-expression)

{fn YEAR(date-expression)}

参数

  • date-expression - 计算结果为 日期整数、ODBC 日期字符串或时间戳的表达式。此表达式可以是列名、另一个标量函数的结果或日期或时间戳字面量。

描述

YEAR 将日期整数($HOROLOG 日期)、ODBC 格式日期字符串或时间戳作为输入。 YEAR 以整数形式返回相应的年份。

日期表达式时间戳可以是数据类型 %Library.PosixTime(编码的 64 位有符号整数),也可以是数据类型 %Library.TimeStamp (yyyy-mm-dd hh:mm:ss.fff)。

年份 (yyyy) 部分应该是 00019999 范围内的四位整数。输入时前导零是可选的。前导零在输出上被抑制。两位数的年份不会扩展到四位数。

date-expression 的日期部分经过验证,并且必须包含 112 范围内的月份以及指定月份和年份的有效日期值。否则,将生成 SQLCODE -400 错误 <ILLEGAL VALUE>

如果日期表达式的时间部分存在,则对其进行验证,但可以省略。

注意:为了与 内部日期表示兼容,强烈建议将所有年份值表示为 00019999 范围内的四位整数。

TO_DATETO_CHAR SQL 函数支持“儒略日期”,可用于表示 0001 之前的年份。ObjectScript 提供了支持此类儒略日期的方法调用。

年份格式默认为四位数年份。要更改今年显示默认值,请使用带有 YEAR_OPTION 选项的 SET OPTION 命令。

可以使用以下 SQL 标量函数返回日期时间字符串的元素:YEARMONTHDAYDAYOFMONTHHOURMINUTESECOND。使用 DATEPARTDATENAME 函数可以返回相同的元素。

也可以使用 YEAR() 方法调用从 ObjectScript 调用此函数:

$SYSTEM.SQL.Functions.YEAR(date-expression)

示例

以下示例返回整数 2018

SELECT YEAR('2018-02-22 12:45:37') AS ODBCDate_Year

2018
SELECT {fn YEAR(64701)} AS HorologDate_Year

2018

以下示例返回当前年份:

SELECT YEAR(GETDATE()) AS Year_Now

2022

以下嵌入式 SQL 示例从两个函数返回当前年份。 CURRENT_DATE 函数返回数据类型 DATENOW 函数返回数据类型 TIMESTAMPYEAR为两种输入数据类型返回一个四位数的年份整数:

/// d ##class(PHA.TEST.SQLFunction).Year()
ClassMethod Year()
{
	&sql(
		SELECT 
			{fn YEAR(CURRENT_DATE)},
			{fn YEAR({fn NOW()})} 
		INTO 
			:a,:b
		)
	if SQLCODE '= 0 {
		w !,"Error code ",SQLCODE 
	} else {
		w !,"CURRENT_DATE year is: ",a
		w !,"NOW year is: ",b 
	}
}
DHC-APP> d ##class(PHA.TEST.SQLFunction).Year()
 
CURRENT_DATE year is: 2022
NOW year is: 2022
0
0 91
文章 姚 鑫 · 六月 2, 2022 4m read

第160章 SQL函数 XMLFOREST

格式化多个 XML 标记以包含表达式值的函数。

大纲

XMLFOREST(expression [AS tag][,expression [AS tag]])

参数

  • expression - 任何有效的表达式。通常是包含要标记的数据值的列的名称。当指定为逗号分隔列表时,列表中的每个表达式都将包含在其自己的 XML 标记标记中。
  • AS tag - 可选 — XML 标记标记的名称。如果指定了标签,则 AS 关键字是必需的。保留标签中字母的大小写。 用双引号括起来的标签是可选的。如果省略双引号,标签必须遵循 XML 命名标准。用双引号括起来的标签消除了这些命名限制。

XMLFOREST 对有效的标签名称实施 XML 命名标准。它不能包含任何字符!"#$%&'()*+,/;<=>?@[\]^``{|}~,也不能包含空格字符,并且不能以 "-", ".", 或数字。

如果指定不带 AS 标记子句的表达式,则标记值是表达式列的名称(大写字母):<HOME_CITY>Chicago</HOME_CITY>

描述

XMLFOREST 函数返回用其自己的 XML 标记开始标记和结束标记标记的每个表达式的值,如 tag 中指定的那样。例如,XMLFOREST(Home_City AS City,Home_State AS State) 返回如下值:<City>Chicago</City><State>IL</State>XMLFOREST 不能用于生成空元素标记。

XMLFOREST 可用于引用表或视图的 SELECT 查询或子查询。 XMLFOREST 可以与普通列值一起出现在 SELECT 列表中。

返回指定的表达式值,由开始标记和结束标记括起来,格式如下:

<tag>value</tag>

通常,表达式是列的名称,或者是包含一个或多个列名称的表达式。表达式可以是任何类型的字段,包括数据流字段。 XMLFOREST 对每个表达式进行如下标记:

  • 如果指定了 AS 标记,则 XMLFOREST 用指定的标记标记结果值。标签值区分大小写。
  • 如果省略 AS 标记,并且表达式是列名,则 XMLFOREST 用列名标记结果值。列名默认标记始终为大写。
  • 如果表达式不是列名(例如,聚合函数、文字或两列的串联),则需要 AS 标记子句。
  • 如果表达式是一个流字段,则流值在生成的 XML 值中使用 <![CDATA[...]]>进行转义:
<tag><![CDATA[value]]></tag>

XMLFOREST 为逗号分隔列表中的每个项目提供一个单独的标记。 XMLELEMENT 将逗号分隔列表中的所有项目连接到单个标记中。

XMLFOREST 函数可以嵌套。允许嵌套 XMLFORESTXMLELEMENT 函数的任何组合。可以使用 XMLCONCAT 连接 XMLFOREST 函数。

NULL 值

XMLFOREST 函数仅返回实际数据值的标记。当表达式值为 NULL 时,它不返回标记。例如:

INSERT INTO Sample.Xmltest (f1,f2,f3) values (NULL,'Row 1',NULL)
SELECT XMLFOREST(f1,f2,f3) from Sample.Xmltest 
<F2>Row 1</F2>.

空字符串 ('') 被视为字符串数据类型字段的数据值。如果要标记的 f3 值为空字符串 (''),则 XMLFOREST 返回:

<F3></F3>

XMLFOREST 在处理 NULL 方面与 XMLELEMENT 不同。 XMLELEMENT 始终返回一个标记值,即使字段值为 NULL。因此,XMLELEMENT 不区分 NULL 或空字符串。两者都表示为 <tag></tag>

标点符号值

如果数据值包含 XML/HTML 可能解释为标记或其他编码的标点字符,则 XMLELEMENTXMLFOREST 将此字符转换为相应的编码形式:

  • ampersand (&) 变成 &amp;

  • apostrophe (') 变成 &apos;

  • quotation mark (") 变成 &quot;

  • open angle bracket (<) 变成 &lt;

  • close angle bracket (>) 变成 &gt;

要在提供的文本字符串中表示撇号,请指定两个撇号,如下例所示:'can''t'。列数据不需要加倍撇号。

示例

以下查询将 Sample.Person 中的 Name 列值作为普通数据和 xml 标记数据返回:

SELECT Name,XMLFOREST(Name) AS ExportName
     FROM Sample.Person

返回的数据示例行如下所示。这里的标签默认为列的名称:

Name                    ExportName
Emerson,Molly N.   <NAME>Emerson,Molly N.</NAME>

以下示例指定了多个列:

SELECT XMLFOREST(Home_City,
                 Home_State AS Home_State,
                 AVG(Age) AS AvAge) AS ExportData
FROM Sample.Person

Home_City 字段不指定标签;该标记由所有大写字母的列名生成:<HOME_CITY>Home_State 字段的 AS 子句是可选的。之所以在此处指定,是因为指定标记名称允许您控制标记的大小写:<Home_State>,而不是 <HOME_STATE>;。 AVG(Age) AS 子句是强制性的,因为该值是聚合值,而不是列值,因此没有列名。返回的数据示例行如下所示。

ExportData
<HOME_CITY>Chicago</HOME_CITY><Home_State>IL</Home_State>
<AvAge>48.0198019801980198</AvAge>

以下示例返回字符流数据:

SELECT XMLFOREST(name AS Para,Notes AS Para) AS XMLJobHistory
     FROM Sample.Employee

返回的数据示例行如下所示:

XMLJobHistory
<Para>Emerson,Molly N.</Para><Para><![CDATA[Molly worked at DynaMatix Holdings Inc. as a Marketing Manager]]></Para>

以下示例显示了使用子查询值的 XMLFOREST 函数:

SELECT XMLFOREST(Name,DOB,Age,
   (SELECT XMLFOREST(Name,DOB) FROM Sample.Person WHERE %ID=2) AS ExportName)
FROM Sample.Person where %ID=1

返回的数据示例行如下所示:

<NAME>Zahn,Rob F.</NAME><DOB>38405</DOB><AGE>71</AGE><ExportName><NAME>Quinn,Mark N.</NAME><DOB>30999</DOB></ExportName>
0
0 155
文章 姚 鑫 · 六月 1, 2022 6m read

第159章 SQL函数 XMLELEMENT

一种格式化 XML 标记标记以包含一个或多个表达式值的函数。

大纲

XMLELEMENT([NAME] tag,expression[,expression])

XMLELEMENT([NAME] tag,XMLATTRIBUTES(expression [AS alias]),expression[,expression])

参数

  • NAME tag - XML标记的名称。NAME关键字是可选的。该参数有三种语法形式: NAME "tag", "tag"NAME。前两者在功能上是相同的。如果指定,标记必须用双引号括起来。标签中的字母大小写保持不变。

XMLELEMENT不执行标记值的验证。然而,XML标准要求有效的标记名不能包含任何字符 !"#$%&'()*+,/;<=>?@[\]^``{|}~,并且不能以"-", "."或数字开头。

如果指定没有标记值的 NAME 关键字, 将提供默认标记值:<Name> ... </Name>NAME 关键字不区分大小写;结果标签初始大写。

  • expression - 任何有效的表达式。通常是包含要标记的数据值的列的名称。可以指定以逗号分隔的列列表或其他表达式,所有这些都将包含在同一标记中。第一个以逗号分隔的元素可以是 XMLATTRIBUTES 函数。只能指定一个 XMLATTRIBUTES 元素。

描述

XMLELEMENT 函数返回用标记中指定的 XML(或 HTML)标记开始标记和结束标记标记的表达式的值。例如,XMLELEMENT(NAME "Para",Home_City) 返回如下值:<Para>Chicago</Para>XMLELEMENT 不能用于生成空元素标记。

XMLELEMENT 可用于引用表或视图的 SELECT 查询或子查询。 XMLELEMENT 可以与普通字段值一起出现在 SELECT 列表中。

tag 参数使用双引号将文字字符串括起来。在几乎所有其他上下文中,SQL 使用单引号将文字字符串括起来;它使用双引号来指定分隔标识符。因此,必须启用分隔标识符支持才能使用此功能;默认情况下启用分隔标识符。

SQL 代码被指定为用双引号分隔的字符串时,例如在动态 SQL %Prepare() 方法中,必须通过指定两个双引号来转义标记双引号,如下所示:

/// d ##class(PHA.TEST.SQLFunction).XmlElement()
ClassMethod XmlElement()
{
	s myquery = "SELECT XMLELEMENT(""Para"",Name) FROM Sample.Person"
	s tStatement = ##class(%SQL.Statement).%New()
	s qStatus = tStatement.%Prepare(myquery)
}

通常,表达式是查询返回的多行中的字段名称(或包含一个或多个字段名称的表达式)。表达式可以是任何类型的字段。返回指定的表达式值,由开始标记和结束标记括起来,格式如下:

<tag>value</tag>

如果要标记的值是空字符串 ('') 值或 NULL,则返回以下内容:

<tag></tag>

如果表达式包含多个逗号分隔的元素,则将结果连接起来,如下格式所示:

<tag>value1value2</tag>

如果表达式是数据流字段,则使用 <![CDATA[...]]> 在生成的 XML 值中转义流值:

<tag><![CDATA[value]]></tag>

XMLELEMENT 函数可以嵌套。 XMLELEMENTXMLFOREST 函数可以任意组合嵌套。可以使用 XMLCONCAT 连接 XMLELEMENT 函数。但是,XMLELEMENT 不会对整个表达式进行 XML 类型解析。例如,XMLELEMENT 不能在 CASE 语句的子句中执行字符转换(参见下面的示例)。

XMLATTRIBUTES 函数

XMLATTRIBUTES 函数只能在 XMLELEMENT 函数中使用。如果 expression 的元素是 XMLATTRIBUTES 函数,则指定的表达式成为标记的属性,格式如下:

<tag ID='63' >value</tag>

只能在 XMLELEMENT 函数中指定一个 XMLATTRIBUTES 函数。按照惯例,它是第一个表达式元素,尽管它可以是表达式中的任何元素。 用单引号将属性值括起来,并在属性值和标记的右尖括号 (>) 之间插入一个空格。

XMLELEMENT 和 XMLFOREST 比较

  • XMLELEMENT 将其表达式列表的值连接到单个标记中。 XMLFOREST 为每个表达式项分配一个单独的标记。
  • XMLELEMENT 要求指定标记值。 XMLFOREST 允许采用默认标记值或指定单个标记值。
  • XMLELEMENT 允许使用 XMLATTRIBUTES 指定标记属性。 XMLFOREST 不允许指定标记属性。
  • XMLELEMENT 返回 NULL 的标记字符串。 XMLFOREST 不返回 NULL 的标记字符串。

标点符号值

如果数据值包含 XML/HTML 可能解释为标记或其他编码的标点字符,则 XMLELEMENTXMLFOREST 将此字符转换为相应的编码形式:

  • ampersand (&) 变成 &amp;

  • apostrophe (') 变成 &apos;

  • quotation mark (") 变成 &quot;

  • open angle bracket (<) 变成 &lt;

  • close angle bracket (>) 变成 &gt;

要在提供的文本字符串中表示撇号,请指定两个撇号,如下例所示:'can''t'。列数据不需要加倍撇号。

示例

以下示例将 Sample.Person 中每个人的 Name 字段值作为普通数据和 xml 标记数据返回:

SELECT Name,
       XMLELEMENT("Para",Name) AS ExportName
     FROM Sample.Person

返回的数据示例行如下所示:

Name                    ExportName
Emerson,Molly N.   <Para>Emerson,Molly N.</Para>

以下示例将 Sample.Person 中每个不同的 Home_CityHome_State 对值返回为带有标签 <Address> ... </Address>xml 标记数据。指定一个空格表达式以防止城市名称和州名称的连接:

SELECT DISTINCT 
   XMLELEMENT(NAME "Address",Home_City,' ',Home_State) AS CityState
   FROM Sample.Person
   ORDER BY Home_City

请注意,在上面的示例中,提供了可选的 NAME 关键字。在下一个示例中,提供的 NAME 关键字没有标记值:

SELECT DISTINCT 
   XMLELEMENT(NAME,Home_City,' ',Home_State) AS CityState
   FROM Sample.Person
   ORDER BY Home_City

在这种情况下,将返回相同的数据,但使用默认标记进行标记:<Name> ... </Name>

以下示例返回字符流数据:

SELECT XMLELEMENT("Para",Name) AS XMLNotes,XMLELEMENT("Para",Notes) AS XMLText
     FROM Sample.Employee

返回的数据示例行如下所示:

XMLName                         XMLText
<Para>Emerson,Molly N.</Para>   <Para><![CDATA[Molly worked at DynaMatix Holdings Inc. as a Marketing Manager]]></Para>

以下示例显示 XMLELEMENT 函数可以嵌套:

SELECT XMLELEMENT("Para",Home_State,
                 XMLELEMENT("Emphasis",Name),Age)
FROM Sample.Person

返回的数据示例行如下所示:

<Para>CA<Emphasis>Emerson,Molly N.</Emphasis>24</Para>

以下示例显示了使用子查询值的 XMLELEMENT 函数:

SELECT XMLELEMENT("Para",Name,DOB, XMLELEMENT("Emphasis",%ID),Age,
   (SELECT XMLELEMENT("NameSub",Name) FROM Sample.Person WHERE %ID=2)) AS ExportName
FROM Sample.Person WHERE %ID=1

返回的数据示例行如下所示:

<Para>Zucherro,Rob F.38405<Emphasis>1</Emphasis>71<NameSub>Quixote,Mark N.</NameSub></Para>

以下示例显示 XMLELEMENT 不能标记 CASE 语句子句中的值:

SELECT XMLELEMENT("Para",Home_State,
    XMLELEMENT("Para",Name),
    CASE WHEN Age < 21 THEN NULL
      ELSE XMLELEMENT("Para",Age) END )
FROM Sample.Person

返回的数据示例行如下所示:

<Para>CA<Para>Emerson,Molly N.</Para>&lt;Para&gt;24&lt;/Para&gt;</Para>

以下查询将 Sample.Person 中的 Name 字段值作为标记中的 XML 标记数据返回,该标记使用 ID 字段作为标记属性:

SELECT XMLELEMENT("Para",XMLATTRIBUTES(%ID),Name) AS ExportName
     FROM Sample.Person

返回的数据示例行如下所示:

ExportName
<Para ID='101' >Emerson,Molly N.</Para>

可以为属性指定别名,如以下示例所示:

SELECT XMLELEMENT("Para",XMLATTRIBUTES(%ID AS ItemKey),Name)
     FROM Sample.Person
<Para ItemKey='101' >Emerson,Molly N.</Para>
0
0 182
文章 姚 鑫 · 五月 31, 2022 1m read

第158章 SQL函数 XMLCONCAT

注:IRIS函数。

连接 XML 元素的函数。

大纲

XMLCONCAT(XmlElement1,XmlElement2[,...])

参数

  • XmlElement - XMLELEMENT 函数。指定两个或多个要连接的 XmlElement

描述

XMLCONCAT 函数将来自多个 XMLELEMENT 函数的值作为单个字符串返回。 XMLCONCAT 可用于引用表或视图的 SELECT 查询或子查询。 XMLCONCAT 可以与普通字段值一起出现在 SELECT 列表中。

示例

以下查询连接来自两个 XMLELEMENT 函数的值:

SELECT Name,XMLCONCAT(XMLELEMENT("Para",Name),
                      XMLELEMENT("Para",Home_City)) AS ExportString
     FROM Sample.Person
ExportString
<Para>Emerson,Molly N.</Para><Para>Boston</Para>

以下查询将 XMLCONCAT 嵌套在 XMLELEMENT 函数中:

SELECT XMLELEMENT("Item",Name,
          XMLCONCAT(
              XMLELEMENT("Para",Home_City,' ',Home_State),
              XMLELEMENT("Para",'is residence')))
       AS ExportString
FROM Sample.Person
ExportString
<Item>Emerson,Molly N.<Para>Boston MA</Para><Para>is residence</Para></Item> 
0
0 104
文章 姚 鑫 · 五月 30, 2022 4m read

第157章 SQL函数 WEEK

一个日期函数,它将一年中的第几周作为日期表达式的整数返回。

大纲

{fn WEEK(date-expression)}

参数

  • date-expression - 一个表达式,它是列的名称、另一个标量函数的结果,或者是日期或时间戳文字。

描述

WEEK 接受一个日期表达式,并返回该日期从年初开始的周数。

默认情况下,使用 $HOROLOG 日期(从 1840 年 12 月 31 日开始的正整数或负整数天数)计算周数。因此,周数是逐年计算的,因此第 1 周是完成从上一年最后一周开始的 7 天期间的天数。一周总是从星期日开始;因此,日历年的第一个星期日标志着从第 1 周到第 2 周的变化。如果一年中的第一个星期日是 1 月 1 日,则该星期日是第 1 周;如果一年中的第一个星期日晚于 1 月 1 日,则该星期日是第 2 周的第一天。因此,第 1 周的长度通常少于 7 天。可以使用 DAYOFWEEK 函数确定星期几。一年中的总周数通常为 53,闰年可能为 54

IRIS 还支持确定一年中星期的 ISO 8601 标准。该标准主要用于欧洲国家。当配置为 ISO 8601 时,WEEK 从星期一开始计算星期,并将星期分配给包含该星期星期四的年份。例如,2004 年的第 1 周从 2003 年 12 月 29 日星期一到 2004 年 1 月 4 日星期日,因为本周的星期四是 2004 年 1 月 1 日,这是 2004 年的第一个星期四。2005 年的第 1 周是从 2005 年 1 月 3 日星期一到 1 月 9 日星期日2005,因为它的星期四是 2005 年 1 月 6 日,也就是 2005 年的第一个星期四。一年中的总周数通常是 52,但偶尔也可能是 53。要激活 ISO 8601 计数, SET ^%SYS("sql","sys","week ISO8601")=1.

日期表达式可以是 日期整数、$HOROLOG$ZTIMESTAMP 值、ODBC 格式日期字符串或时间戳。

日期表达式时间戳可以是数据类型 %Library.PosixTime(编码的 64 位有符号整数),也可以是数据类型 %Library.TimeStamp (yyyy-mm-dd hh:mm:ss.fff)。

时间戳的时间部分不被评估,可以省略。

使用 DATEPARTDATENAME 函数可以返回相同的星期信息。

也可以使用 WEEK() 方法调用从 ObjectScript 调用此函数:

$SYSTEM.SQL.Functions.WEEK(date-expression)

日期验证

WEEK 对输入值执行以下检查。如果某个值未通过检查,则返回空字符串。

  • 日期字符串必须完整且格式正确,其中包含适当数量的元素和每个元素的数字,以及适当的分隔符。年份必须指定为四位数。
  • 日期值必须在有效范围内。年:0001 到 9999。月:1 到 12。日:1 到 31。
  • 一个月的天数必须与月份和年份相匹配。例如,日期“02–29”仅在指定年份是闰年时有效。
  • 小于 10 的日期值可以包括或省略前导零。不允许使用其他非规范整数值。因此,Day“07”“7”有效,但“007”“7.0”“7a”无效。

示例

以下嵌入式 SQL 示例返回 2005 年 1 月 2 日(星期日)和 2006 年 1 月 1 日(星期日)的星期几和一年中的星期几。

/// d ##class(PHA.TEST.SQLFunction).Week()
ClassMethod Week()
{
	SET x = "2005-1-2"
	SET y = "2006-1-1"
	&sql(
		SELECT 
			{fn DAYOFWEEK(:x)},{fn WEEK(:x)},
			{fn DAYOFWEEK(:y)},{fn WEEK(:y)}
		INTO 
			:a,:b,:c,:d
	)
	if SQLCODE '= 0 {
		w !,"Error code ",SQLCODE 
	} else {
		w !,"2005 Day of Week is: ",a," (Sunday=1)"
		w " Week of Year is: ",b
		w !,"2006 Day of Week is: ",c," (Sunday=1)"
		w " Week of Year is: ",d 
	}
}

DHC-APP>d ##class(PHA.TEST.SQLFunction).Week()
 
2005 Day of Week is: 1 (Sunday=1) Week of Year is: 2
2006 Day of Week is: 1 (Sunday=1) Week of Year is: 1

以下示例返回数字 9,因为日期是 2004 年的第九周:

SELECT {fn WEEK('2004-02-25')} AS Wk_Date,
       {fn WEEK('2004-02-25 08:35:22')} AS Wk_Tstamp,
       {fn WEEK(59590)} AS Wk_DInt
       
       
9	9	9

以下示例返回数字 54,因为此特定日期是闰年,从第 2 周开始,从第二天开始,如紧随其后的示例所示:

SELECT {fn WEEK('2000-12-31')} AS Week

54
SELECT {fn WEEK('2000-01-01')}||{fn DAYNAME('2000-01-01')} AS WeekofDay1,
       {fn WEEK('2000-01-02')}||{fn DAYNAME('2000-01-02')} AS WeekofDay2
       
       
       
1Saturday	2Sunday

以下示例均返回当前周:

SELECT {fn WEEK({fn NOW()})} AS Wk_Now,
       {fn WEEK(CURRENT_DATE)} AS Wk_CurrD,
       {fn WEEK(CURRENT_TIMESTAMP)} AS Wk_CurrTS,
       {fn WEEK($HOROLOG)} AS Wk_Horolog,
       {fn WEEK($ZTIMESTAMP)} AS Wk_ZTS
       
       
20	20	20	20	20

以下嵌入式 SQL 示例显示了 默认的一年中的一周以及应用 ISO 8601 标准的一年中的一周:

/// d ##class(PHA.TEST.SQLFunction).Week1()
ClassMethod Week1()
{
TestISO
	s def = $d(^%SYS("sql","sys","week ISO8601"))
	if def = 0 {
		s ^%SYS("sql","sys","week ISO8601")=0
	} else {
		s isoval = ^%SYS("sql","sys","week ISO8601")
	}
	if isoval = 1 {
		g UnsetISO 
	} else {
		s isoval=0 
		g WeekOfYear 
	}
UnsetISO
	s ^%SYS("sql","sys","week ISO8601") = 0
WeekOfYear
	&sql(
		SELECT 
			{fn WEEK($HOROLOG)} 
		INTO 
			:a
	)
	w "For Today:",!
	w "default week of year is ",a,!
	s ^%SYS("sql","sys","week ISO8601") = 1
	&sql(
		SELECT 
			{fn WEEK($HOROLOG)} 
		INTO 
			:b
	)
	w "ISO8601 week of year is ",b,!
ResetISO
	s ^%SYS("sql","sys","week ISO8601") = isoval
}
0
0 149
文章 姚 鑫 · 五月 29, 2022 1m read

第156章 SQL函数 USER

返回当前用户的用户名的函数。

大纲

USER

{fn USER}
{fn USER()}

描述

USER 不接受任何参数并返回当前用户的用户名(也称为授权 ID)。通用函数不允许括号; ODBC 标量函数可以指定或省略空括号。

用户名是使用 CREATE USER 命令定义的。

USER 的典型用途是在 SELECT 语句选择列表或查询的 WHERE 子句中。在设计报告时,USER 可用于打印正在为其生成报告的当前用户。

示例

以下示例返回当前用户名:

SELECT USER AS CurrentUser

yx

以下示例选择姓氏 ($PIECE(Name,',',1) 或名字(没有中间首字母)与当前用户名匹配的那些记录:

SELECT Name FROM Sample.Person 
WHERE %SQLUPPER(USER)=%SQLUPPER($PIECE(Name,',',1)) 
OR %SQLUPPER(USER)=%SQLUPPER($PIECE($PIECE(Name,',',2),' ',1))
0
0 115