PL/SQL 中逐行比较 CLOB






4.60/5 (4投票s)
一个在 PL/SQL 中实现的 Longest Common Subsequence (LCS) diff 算法。
引言
我编写这个包是为了能够用 PL/SQL 代码进行比较,纯粹是出于兴趣。 两个包之间的更改保存为 CLOB。 但它可以用于比较任意两个 CLOB,唯一的限制是最大行长度 (4000)。
它基于此处描述的朴素 Longest Common Subsequence 算法
从上面的链接中,我们可以看到,对于长度分别为 *m* 和 *n* 的两个序列 *X* 和 *Y*,这两个序列的最长公共子序列定义为
计算 LCS 矩阵 (M) 的伪代码如下:
function LCS_MATRIX(X[1..m], Y[1..n])
M = array(0..m, 0..n)
for i := 0..m
M[i,0] = 0
for j := 0..n
M[0,j] = 0
for i := 1..m
for j := 1..n
if X[i] = Y[j]
M[i,j] := C[i-1,j-1] + 1
else:
M[i,j] := max(M[i,j-1], M[i-1,j])
基于 LCS 矩阵,我们可以用以下伪代码计算两个序列之间的差异
function printDiff(M[0..m,0..n], X[1..m], Y[1..n], i, j)
if i > 0 and j > 0 and X[i] = Y[j]
printDiff(M, X, Y, i-1, j-1)
print " " + X[i]
else
if j > 0 and (i = 0 or M[i,j-1] ≥ M[i-1,j])
printDiff(M, X, Y, i, j-1)
print "+ " + Y[j]
else if i > 0 and (j = 0 or M[i,j-1] < M[i-1,j])
printDiff(M, X, Y, i-1, j)
print "- " + X[i]
else
print ""
我这篇文章的目标是在 PL/SQL 语言中实现上面的伪代码,其中序列 X 和 Y 由两个 CLOB 的行定义。
算法实现的工作原理
在该算法的第一步,CLOB 被转换为数组,其中 CLOB 的每一行都映射到数组的每个记录中。 这是在函数 clob_to_array
中完成的,它返回一个 varchar2
数组。
实现此算法的主要困难之一是 PL/SQL 对多维数组不友好。 虽然在其他语言中创建 m *x* n 数组非常容易,但在 PL/SQL 中,这有点复杂。 感谢 Oracle 论坛的 Solomon Yakobson 教我如何做到这一点
TYPE t_number_array IS TABLE OF NUMBER;
TYPE t_bidimensional_number_array IS TABLE OF t_number_array;
matrix t_bidimensional_number_array := t_bidimensional_number_array();
FOR i IN 1 .. m + 1
LOOP
matrix.extend;
matrix(i) := t_number_array();
FOR j IN 1 .. n + 1
LOOP
matrix(i).extend;
END LOOP;
END LOOP;
请注意,在 PL/SQL 中,数组从值 1 开始,因此为了能够拥有一个维度为 [0..m,0..n] 的矩阵,我使用的矩阵由 [1..m+1,1..n+1] 定义。
在获得旧文件和新文件数组并初始化 LCS 矩阵后,我们可以开始填充它。 以下过程描述了使用的逻辑
FOR i IN 1 .. m + 1
LOOP
FOR j IN 1 .. n + 1
LOOP
IF old_array(i - 1) = new_array(j - 1) THEN
matrix(i)(j) := matrix_i(i - 1) (j - 1) + 1;
ELSE
matrix(i)(j) := greatest(matrix(i) (j - 1), matrix(i - 1) (j));
END IF;
END LOOP;
END LOOP;
我希望逐行显示差异,而不是像 Introduction 中描述的那样只显示一个包含添加和删除行的简单输出。 所以我的主要输出是一个数组 t_differences_array
,它并排显示旧行和新行。
CREATE OR REPLACE TYPE t_differences AS OBJECT
(
old_line_number NUMBER,
old_file VARCHAR2(4000),
status VARCHAR2(20),
new_line_number NUMBER,
new_file VARCHAR2(4000)
);
CREATE OR REPLACE TYPE t_differences_array as table of t_differences;
例如,给定文件 *OLD_FILE{A,D,E,B}* 和 *NEW_FILE{A,C,B}*,标准算法将像这样显示差异
而我想要达到的目标是不添加不必要的额外行,这意味着将输出更改为
这个目标是通过向 get_differences
过程添加额外逻辑来实现的。 主要是通过保存差异开始的行,然后从这个保存的行开始填充另一侧的差异,直到下一行相等。
如果不同的行数不匹配,我们将有一侧显示一个空白行号。 这也有助于区分现有空白行和不存在的行之间的差异。
以下代码优化包含在该算法中
- 减少问题集:由于在现实生活中,我们通常比较修改很小的文件,如果我们在不改变行的文件的开头和结尾处不考虑,则可以大大减少 LCS 矩阵。
- 减少比较时间:当行太大时,我们可以通过散列行并仅比较散列值来减少比较时间。
由于减少的问题集优化,实际代码具有三个额外的参数,分别定义了差异开始的第一行和差异停止的两个结束行。 这两个参数使代码更难理解。
使用代码
它包括两个主要的比较函数
FUNCTION compare_line_by_line(old_file_i IN CLOB,
new_file_i IN CLOB) RETURN t_differences_array
FUNCTION compare_line_by_line_refcursor(old_file_i IN CLOB,
new_file_i IN CLOB) RETURN SYS_REFCURSOR
唯一的区别是输出类型。
我包含了一个测试表 compare_file_test,因此您可以轻松地针对两个不同的测试用例测试该程序。
在下面,您可以看到一个如何使用测试表来比较两个文件的示例
WITH t AS
(SELECT *
FROM compare_file_test
WHERE id = 1)
SELECT a.*
FROM t,
TABLE(clob_compare.compare_line_by_line(t.old_file, t.new_file)) a
仍然缺少什么
我计划在未来的版本中包含一些额外的选项
- 仅显示不同的行
- 忽略大小写
- 忽略空格
- 修剪行
最后的说明
我只针对几个测试用例测试了我的实现,可能仍然存在一些错误。
我感谢您的建议/意见,以改进和优化代码。
历史
- 版本 1.0: 2011/04/05.