【发布时间】:2015-05-21 07:21:11
【问题描述】:
在 Oracle 11g 中工作并使用 SQL 函数(不是 PL/SQL、Java 等),我想从限制为 4000 个字符的 CLOB 字段中将嵌套的 XML 数据提取到列中。
最初,我开发了一个解决方案,它使用 REGEXP_SUBSTR 和 CONNECTED BY LEVEL 来提取和扩展数据。这种方法适用于有限的样本数据集,但当应用于更大的数据集时,它花费了非常长的时间(几个小时)并且没有返回。 (我认为这是因为它会生成一个交叉连接并在之后过滤结果?)
现在我有了另一种使用 XMLTYPE 和 XMLTABLE 的解决方案,它适用于小型和大型集合,但有一个重要警告:数据集中的任何无效 XML 条目都会导致整个查询失败。在大型数据集中,我只在 CLOB 字段中文本的 LENGTH 等于 4000 的情况下观察到这种情况。在这种情况下,超过 4000 个字符的字符串被插入到 CLOB 中并被截断,从而破坏了XML 结构。因此,XMLTYPE 转换在这些行上失败并以错误结束查询。我有一个粗略的解决方法,它使用 WHERE 子句过滤掉 CLOB >= 4000 长度的行,以避免该特定错误。但这种方法显然不允许提取这些行中的可用数据,更重要的是,它暗示了依赖于仅包含有效 XML 的输入的解决方案的脆弱性。下面包含此方法的示例查询,可以在以下链接中找到 SQL Fiddle:http://sqlfiddle.com/#!4/d41d8/40794/0
SELECT
orig_string.message_seq_id,
xml_layer_1.unit_srl,
xml_layer_2.AB_Type
FROM (
SELECT
xml.message_seq_id,
XMLTYPE(
xml.xml_messg_text
) AS xml_messg_text
FROM (
SELECT 1001 AS message_seq_id, RPAD(Q'#<location_data unit_srl="MLRR-1" time_marker="02-04-2015 21:54:42" location_number="2335350"><RunData AB_Type="Rezoned" mr_cod="9982349482-2422422234" Timing="02-04-2015 21:54:42" Handle="L1L_upperband" LongCount="82742288322983299222759200000004" ClassTypeTest="HUCOHOTMMMWMM" Boc="242242" Closeout="LongOp" Founding="14" init_link="lls://rtg.ootir.orzz/prp"/>#', 3888, ' ') || Q'#<RunData AB_Type="Rezoned" mr_cod="9834398399934-39493943-###-09923049,0003-002,12" Timing="02-04-2015 21:54:42"#' AS xml_messg_text, TO_DATE('2015-02-04 21:06:00', 'YYYY-MM-DD HH24:MI:SS') AS create_date FROM DUAL UNION ALL
SELECT 1002 AS message_seq_id, Q'#<location_data unit_srl="UNTOUT-3" time_marker="02-05-2015 10:18:34" location_number="23422"><RunData AB_Type="Rezoned" mr_cod="9234092349,03940388,748209,23" Timing="02-05-2015 10:18:34" Handle="L1L_upperband" ClassTypeTest="URCHTPH" Boc="3245" Closeout="LongOp" Founding="21" init_link="lls://rtg.ootir.orzz/prp"/><RunData AB_Type="Rezoned" mr_cod="93488284983292492394242-####-0200234" Timing="02-05-2015 10:18:34" Handle="L1L_upperband" ClassTypeTest="BERDBP" Boc="6445" Closeout="LongOp" Founding="9453" init_link="lls://rtg.ootir.orzz/prp"/><RunData AB_Type="Lopped" mr_cod="4537747565656565" Timing="02-05-2015 10:18:34" Handle="L1L_upperband" ClassTypeTest="HIUFDDU" Boc="3324" Closeout="LongOp" Founding="29" init_link="lls://rtg.ootir.orzz/prp"/></location_data>#' AS xml_messg_text, TO_DATE('2015-02-05 09:31:23', 'YYYY-MM-DD HH24:MI:SS') AS create_date FROM DUAL UNION ALL
SELECT 1003 AS message_seq_id, Q'#<location_data unit_srl="LURET-22" time_marker="02-04-2015 15:34:55" location_number="1135461"><RunData Timing="02-04-2015 15:34:55" Boc="5644" Closeout="ChpstHousTul" init_link="lls://rtg.ootir.orzz/prp" Dest=""/></location_data>#' AS xml_messg_text, TO_DATE('2015-02-04 16:24:42', 'YYYY-MM-DD HH24:MI:SS') AS create_date FROM DUAL UNION ALL
SELECT 1004 AS message_seq_id, Q'#<location_data unit_srl="GBT-14" time_marker="02-04-2015 15:31:27" location_number="4395822"><RunData AB_Type="Rezoned" mr_cod="5948573853085309485938094853098345" Timing="02-04-2015 15:31:27" Handle="L1L_upperband" ClassTypeTest="BTTXRCH" Boc="232" Closeout="LongOp" Founding="3" init_link="lls://rtg.ootir.orzz/prp"/></location_data>#' AS xml_messg_text, TO_DATE('2015-02-04 16:35:37', 'YYYY-MM-DD HH24:MI:SS') AS create_date FROM DUAL
) xml
WHERE LENGTH(xml.xml_messg_text) < 4000
) orig_string,
XMLTABLE(
'/location_data'
PASSING orig_string.xml_messg_text
COLUMNS
unit_srl VARCHAR2(15) PATH '@unit_srl',
OBJECT XMLTYPE PATH 'RunData'
) xml_layer_1,
XMLTABLE(
'/RunData'
PASSING xml_layer_1.object
COLUMNS
AB_Type VARCHAR2(40) PATH '@AB_Type'
) xml_layer_2
;
请注意,message_seq_id = 1001 是问题行,如果将 WHERE 子句注释掉,则查询会导致错误。
由于我对数据库的访问是只读的,我将无法添加表约束或以其他方式排除格式错误的数据,因此任何解决方案都需要能够按原样处理数据。
如何解决 XMLTYPE 转换问题,以便所有行都可以访问,并且即使查询包含无效的 XML,也不会导致查询以错误结尾?
是否有比上述两种方法更可靠的数据提取方法?
【问题讨论】:
-
也许坏数据不应该从一开始就进入数据库?
-
@OldProgrammer 这也是我的偏好,但这是我无法控制的客户端系统的一个方面。