 | 级别: 初级 Knut Stolze, IBM 信息集成, IBM 德国
2003 年 3 月 01 日 本文展示了如何解析数据字符串,另外还展示对结果进行后续处理的一些可能性。
简介
客户经常遇到的一种情形是,需要处理在由 DB2 通用数据库(UDB)管理的数据库中存储的字符串。这些字符串包含某些形式的、要进一步解析和分析的并置数据。本文显示了如何解析这些数据,并展示了对这些结果进行后续处理的一些可能性。
为进一步阐明该方案,我们假定希望处理类似于在系统目录视图 SYSCAT.CHECKS 的 FUNC_PATH 列中可以访问到的字符串。该列列出了在创建检查约束时所使用的函数路径。
清单 1显示了该列中的一些样本内容。
1
清单 1. 查询 DB2 系统目录视图 SYSCAT.CHECKS
SELECT func_path FROM syscat.checks;
FUNC_PATH
--------------------------------------------------------
"SYSIBM","SYSFUN","SYSPROC","STOLZE"
"SYSIBM","SYSFUN","SYSPROC","MYSCHEMA"
"SYSIBM","SYSFUN","SYSPROC"
"SYSIBM","SYSFUN","SYSPROC"
4 record(s) selected.
|
正如您所见,每个字符串包含了由逗号分隔的几个元素,这个逗号就是定界符。现在我描述的解析将检索每个字符串中的单个元素。请注意,只要略微地改变一下定界符本身,就可以用完全相同的方法来处理使用其它定界符的字符串。
有几种方式可以实现所描述的解析。显然,用 Java 或 C/C++ 实现的用户定义的表函数可以完成此任务。这样的函数将每个字符串作为输入参数,然后分行返回每个元素。虽然用 C/C++ 实现的函数可以提供最佳性能,但可能并不要求使用外部编程语言,因为这会对开发环境增加一些要求;例如,必须为每个目标系统部署实现用户定义的函数(UDF)的库。作为编程语言的 SQL 已经能够处理该特定任务,并且数据库用户可以使用 SQL。
迭代字符串
解析字符串的基本任务是迭代字符串中的所有字符。SQL 提供了两种方式来完成此迭代:
DB2 V8.1 中添加了用于分区数据库(ESE)的内联 SQL PL。在 V8.1 中可以使用属于内联 SQL PL 的循环,但对于
V7.2,还没有用于分区数据库的循环。此外,如果使用循环,则更难以在表中表示字符串中被抽取的元素,尽管这种表示会简化 SQL 中的进一步处理。所以,本文将着重探讨基于递归查询的常规解决方案。
递归查询中的迭代将用于查找给定字符串中的所有定界符。根据定界符的位置,就可以抽取单个元素的子字符串。为了简化这些步骤,我们使用 SQL 函数来执行要执行的每步任务。
清单 2中显示的第一个函数将一个字符串作为输入参数,然后查找字符串中的所有定界符。该函数返回一些以升序方式表示的数字,这些数字用于标识每个定界符,另外还返回字符串中每个定界符的位置(索引)。用具有两列的表的形式返回标识符/位置对。
清单 2. 返回所有定界符的标识符和索引的函数
CREATE FUNCTION elemIdx ( string CLOB(64K) )
RETURNS TABLE ( ordinal INTEGER, index INTEGER )
LANGUAGE SQL
DETERMINISTIC
NO EXTERNAL ACTION
CONTAINS SQL
RETURN
WITH t(ordinal, index) AS
( VALUES ( 0, 0 )
UNION ALL
SELECT ordinal+1, COALESCE(NULLIF(
-- find the next delimiter ","
LOCATE(',', string, index+1), 0),
LENGTH(string)+1)
FROM t
-- to prevent a warning condition for infinite
-- recursions, we add the explicit upper
-- boundary for the "ordinal" values
WHERE ordinal < 10000 AND
-- terminate if there are no further delimiters
-- remaining
LOCATE(',', string, index+1) <> 0 )
SELECT ordinal, index
FROM t
UNION ALL
-- add indicator for the end of the string
SELECT MAX(ordinal)+1, LENGTH(string)+1
FROM t
;
|
注:该函数不仅返回每个定界符的索引,而且还添加了两行,一行代表索引 0(零),它表示字符串的开始;另一行表示字符串的结束。这样就可以方便地抽取第一个和最后一个元素。
清单 3显示了对于字符串“abc, def, ghi, 123”该函数的用法及其结果。
清单 3. 函数 elemIdx() 的样本输出
SELECT *
FROM TABLE ( elemIdx('abc, def, ghi, 123') ) AS t
ORDER BY 1;
ORDINAL INDEX
----------- -----------
0 0
1 4
2 9
3 14
4 19
5 record(s) selected.
|
这些结果显示,该字符串有 19 个字符(索引 4),并且在位置 4、9 和 14 处可以找到用于定界的逗号。正如前面所提到的,第一行只表示字符串的开始。
抽取子字符串
使用由函数
elemIdx() 返回的索引信息,现在可以抽取字符串中的元素。为达到此目的,先确定索引,然后将这些索引用作 DB2 内置函数 SUBSTR 的参数。
清单 4显示了该函数的模样。
清单 4. 返回单个元素的函数
CREATE FUNCTION elements ( string CLOB(64K) )
RETURNS TABLE ( elements CLOB(64K) )
LANGUAGE SQL
DETERMINISTIC
NO EXTERNAL ACTION
CONTAINS SQL
RETURN
WITH t(ordinal, index) AS
( SELECT ordinal, index
FROM TABLE ( elemIdx(string) ) AS x )
SELECT SUBSTR(string, t1.index+1, t2.index - t1.index - 1)
-- the join below makes sure that we have the lower and
-- upper index where we can find each of the ',' delimiters
-- that are separating the elements. (For this, we exploit
-- the additional indexes pointing to the beginning and end
-- of the string.)
FROM t AS t1 JOIN t AS t2 ON
( t2.ordinal = t1.ordinal+1 )
;
|
连接条件确保为每个元素组合了正确的上界和下界。然后,下界确定元素的起点,上界确定终点。使用上界和下界,通过简单的减法,可以计算 SUBSTR() 函数需要要抽取的字符串长度。现在,再次使用
清单 3中的同一个字符串。
清单 5
显示了一条样本查询,该查询从输入字符串抽取单个元素。
清单 5. 函数 elements() 的样本输出
SELECT VARCHAR(elem, 20)
FROM TABLE ( elements('abc, def, ghi, 123') ) AS t(elem);
1
--------------------
abc
def
ghi
123
4 record(s) selected.
|
正如您所见,所有元素都分行返回。这里保留了每个元素中的空格。
使用方案
为结束本文,我提供了一些样本查询,展示了在您的查询中如何包含
elements()
函数的结果,从而真正地执行所需的字符串分析,进而达到最初目的。
这些示例都会使用
清单 6中所显示的表和数据。在创建上面所描述的两个函数之后,可以执行这些查询,您会得到本节中所显示的结果。
清单 6. 创建表并插入示例数据
CREATE TABLE strings (
id INTEGER NOT NULL PRIMARY KEY,
str VARCHAR(128) NOT NULL
);
INSERT
INTO strings
VALUES ( 1, 'abc, def, ghi, 123' ),
( 2, '123,456789,abc,123' ),
( 3, 'a,b,c,a,b,c,a' ),
( 4, 'string' );
SELECT * FROM strings;
ID STR
----------- -------------------------------------------------
1 abc, def, ghi, 123
2 123,456789,abc,123
3 a,b,c,a,b,c,a
4 string
4 record(s) selected.
|
用
清单 3 所显示的查询可以对字符串中有多少个不同元素进行计数。使用函数
RTRIM() 和
LTRIM() 除去任何前导空格或结尾空格,如果完全根据空白,则这两种空格可能会使两个元素一模一样。第一个查询使用了包含四个元素的字符串,其中两个元素完全一样。换句话说,存在三个截然不同的元素。
清单 7中的第二个查询显示了对表字符串进行相同查询后的结果。
清单 7. 对字符串中不同元素进行计数
SELECT COUNT(DISTINCT VARCHAR(RTRIM(LTRIM(elem)), 1000))
FROM TABLE ( elements('abc, def,abc ,ghi') ) AS x(elem);
1
-----------
3
1 record(s) selected.
SELECT id, COUNT(DISTINCT VARCHAR(RTRIM(LTRIM(elem)), 20))
FROM strings, TABLE ( elements(str) ) AS t(elem)
GROUP BY id;
ID 2
----------- -----------
1 4
2 3
3 3
4 1
4 record(s) selected.
|
在另一种情况下,您可能希望知道字符串中元素“123”出现的频率。
清单 8显示了应如何用公式表示这样的查询。
清单 8. 对给定字符串的出现次数进行计数
SELECT id, ( SELECT COUNT(*)
FROM TABLE ( elements(str) ) AS x(elem)
WHERE VARCHAR(RTRIM(LTRIM(elem)), 100) = '123' )
FROM strings;
ID 2
----------- -----------
1 1
2 2
3 0
4 0
4 record(s) selected.
|
正如这些查询所显示的,我通常在包含要解析字符串的表与表函数
elements()
之间进行连接操作。由于表函数会与基于两个基表或视图中的列进行的正常连接不同,因此 DB2
处理那些表函数需要更加精心。在连接处理中,DB2 将会做以下事情:
- 从名为 strings 的表中取回一行数据
- 向表函数
elements() 给出该行的 STR 值
- 现在,将由这个单个输入参数的 elements() 返回的所有行与第 1 步中所取回的行相关联,而不和其它任何行相关联
- 回到第 1 步,取回下一行数据,直到处理完所有行
结束语
本文展示了如何仅用 SQL 构造就可以解析和处理用定界符分隔的几个元素组成的字符串。最后,我们定义了两个函数
elemIdx()
和
elements() ,这两个函数可以计算定界符在字符串中的位置,以及根据该信息,抽取作为子字符串的实际元素。为了说明结果以及如何将这些函数用于更复杂的
SQL 语句,还提供了几个示例。
参考资料
免责声明
本文包含样本代码。IBM 授予您(“被许可方”)非独占和无需专利费的许可证来使用该样本代码。然而,该样本代码以“按现状”来提供,不带任何形式的(无论明示的,还是暗示的)保证,包括对适销性、适用于某特定用途或非侵权性的默示保证。IBM 和它的许可证颁发者不负责被许可方因使用该软件而遭受到的任何损失。IBM 或它的许可证颁发者决不负责任何由于使用软件或无法使用软件所造成的收入以及利润或数据损失,也不负责任何因此而造成的直接的、间接的、特殊的、相因而生的、偶尔的或惩罚性损坏,无论这种损坏是如何造成的,也不管理论上责任该是怎样的,即使 IBM 已经得到可能造成这种损坏的警告,IBM 也不承担责任。
关于作者  | 
|  | 在
Knut Stolze 作为硅谷实验室(他在那里从事 DB2 Image Extender)的访问学者加入到 IBM 时,就开始从事
DB2 的研究。之后,他转向了 DB2 Spatial Extender V8,负责改善 Extender 的实用性、性能以及与标准的一致性达两年多。
目前,他作为德国耶拿大学的助教,在联合数据库领域继续为 IBM 工作。可以通过新闻组 comp.databases.ibm-db2 和 ibm.software.db2.udb.spatial 或
stolze@de.ibm.com与他联系。
|
对本文的评价
|  |