@@ -21,6 +21,54 @@ create or replace package body ut_compound_data_helper is
2121 g_type_name_map t_type_name_map;
2222 g_anytype_name_map t_type_name_map;
2323
24+ gc_compare_sql_template varchar2(4000) :=
25+ q'[
26+ with exp as (
27+ select
28+ ucd.*,
29+ {:duplicate_number:} dup_no
30+ from (
31+ select
32+ ucd.item_data
33+ ,x.data_id data_id
34+ ,{:item_no:} item_no
35+ {:columns:}
36+ from {:ut3_owner:}.ut_compound_data_tmp x,
37+ xmltable('/ROWSET/ROW' passing x.item_data columns
38+ item_data xmltype path '*'
39+ ,position for ordinality
40+ {:xml_to_columns:} ) ucd
41+ where data_id = :exp_guid
42+ ) ucd
43+ )
44+ , act as (
45+ select
46+ ucd.*,
47+ {:duplicate_number:} dup_no
48+ from (
49+ select
50+ ucd.item_data
51+ ,x.data_id data_id
52+ ,{:item_no:} item_no
53+ {:columns:}
54+ from {:ut3_owner:}.ut_compound_data_tmp x,
55+ xmltable('/ROWSET/ROW' passing x.item_data columns
56+ item_data xmltype path '*'
57+ ,position for ordinality
58+ {:xml_to_columns:} ) ucd
59+ where data_id = :act_guid
60+ ) ucd
61+ )
62+ select
63+ a.item_data as act_item_data,
64+ a.data_id act_data_id,
65+ e.item_data as exp_item_data,
66+ e.data_id exp_data_id,
67+ {:item_no_select:} as item_no,
68+ nvl(e.dup_no,a.dup_no) dup_no
69+ from act a {:join_type:} exp e on ( {:join_condition:} )
70+ where {:where_condition:}]';
71+
2472 function get_columns_filter(
2573 a_exclude_xpath varchar2, a_include_xpath varchar2,
2674 a_table_alias varchar2 := 'ucd', a_column_alias varchar2 := 'item_data'
@@ -92,10 +140,10 @@ create or replace package body ut_compound_data_helper is
92140 return l_results;
93141 end;
94142
95- procedure generate_not_equal_stmt(
96- a_data_info ut_cursor_column, a_pk_table ut_varchar2_list,
97- a_not_equal_stmt in out nocopy clob
98- ) is
143+ function generate_not_equal_stmt(
144+ a_data_info ut_cursor_column, a_pk_table ut_varchar2_list
145+ ) return varchar2
146+ is
99147 l_pk_tab ut_varchar2_list := coalesce(a_pk_table,ut_varchar2_list());
100148 l_index integer;
101149 l_sql_stmt varchar2(32767);
@@ -110,19 +158,17 @@ create or replace package body ut_compound_data_helper is
110158 exit when l_index = l_pk_tab.count or (a_data_info.access_path = l_pk_tab(l_index));
111159 l_index := a_pk_table.next(l_index);
112160 end loop;
113- end if;
114-
115- if not(l_exists) then
116- l_sql_stmt := l_sql_stmt || case when a_not_equal_stmt is null then null else ' or ' end
117- ||' (decode(a.'||a_data_info.transformed_name||','||' e.'||a_data_info.transformed_name||',1,0) = 0)';
118- ut_utils.append_to_clob(a_not_equal_stmt,l_sql_stmt);
119161 end if;
162+ if not(l_exists) then
163+ l_sql_stmt := ' (decode(a.'||a_data_info.transformed_name||','||' e.'||a_data_info.transformed_name||',1,0) = 0)';
164+ end if;
165+ return l_sql_stmt;
120166 end;
121167
122- procedure generate_join_by_stmt(
123- a_data_info ut_cursor_column, a_pk_table ut_varchar2_list,
124- a_join_by_stmt in out nocopy clob
125- ) is
168+ function generate_join_by_stmt(
169+ a_data_info ut_cursor_column, a_pk_table ut_varchar2_list
170+ ) return varchar2
171+ is
126172 l_pk_tab ut_varchar2_list := coalesce(a_pk_table,ut_varchar2_list());
127173 l_index integer;
128174 l_sql_stmt varchar2(32767);
@@ -132,27 +178,24 @@ create or replace package body ut_compound_data_helper is
132178 loop
133179 if l_pk_tab(l_index) in (a_data_info.access_path, a_data_info.parent_name) then
134180 --When then table is nested and join is on whole table
135- l_sql_stmt := case when a_join_by_stmt is null then null else ' and ' end;
136181 l_sql_stmt := l_sql_stmt ||' a.'||a_data_info.transformed_name||q'[ = ]'||' e.'||a_data_info.transformed_name;
137182 end if;
138183 exit when (a_data_info.access_path = l_pk_tab(l_index)) or l_index = l_pk_tab.count;
139184 l_index := l_pk_tab.next(l_index);
140- end loop;
141- ut_utils.append_to_clob(a_join_by_stmt,l_sql_stmt);
142- end if ;
185+ end loop;
186+ end if;
187+ return l_sql_stmt ;
143188 end;
144189
145- procedure generate_equal_sql(a_equal_stmt in out nocopy clob, a_col_name in varchar2) is
146- l_sql_stmt varchar2(32767);
190+ function generate_equal_sql(a_col_name in varchar2) return varchar2 is
147191 begin
148- l_sql_stmt := case when a_equal_stmt is null then null else ' and ' end ||' a.'||a_col_name||q'[ = ]'||' e.'||a_col_name;
149- ut_utils.append_to_clob(a_equal_stmt,l_sql_stmt);
192+ return ' a.'||a_col_name||q'[ = ]'||' e.'||a_col_name;
150193 end;
151194
152- procedure generate_partition_stmt(
153- a_data_info ut_cursor_column, a_partition_stmt in out nocopy clob,
154- a_pk_table in ut_varchar2_list, a_alias varchar2 := 'ucd.'
155- ) is
195+ function generate_partition_stmt(
196+ a_data_info ut_cursor_column, a_pk_table in ut_varchar2_list, a_alias varchar2 := 'ucd.'
197+ ) return varchar2
198+ is
156199 l_index integer;
157200 l_sql_stmt varchar2(32767);
158201 begin
@@ -161,20 +204,20 @@ create or replace package body ut_compound_data_helper is
161204 loop
162205 if a_pk_table(l_index) in (a_data_info.access_path, a_data_info.parent_name) then
163206 --When then table is nested and join is on whole table
164- l_sql_stmt := case when a_partition_stmt is null then null else ',' end;
165207 l_sql_stmt := l_sql_stmt ||a_alias||a_data_info.transformed_name;
166- end if;
167-
208+ end if;
168209 exit when (a_data_info.access_path = a_pk_table(l_index)) or l_index = a_pk_table.count;
169210 l_index := a_pk_table.next(l_index);
170211 end loop;
171212 else
172- l_sql_stmt := case when a_partition_stmt is null then null else ',' end || a_alias||a_data_info.transformed_name;
213+ l_sql_stmt := a_alias||a_data_info.transformed_name;
173214 end if;
174- ut_utils.append_to_clob(a_partition_stmt, l_sql_stmt) ;
215+ return l_sql_stmt;
175216 end;
176217
177- procedure generate_select_stmt(a_data_info ut_cursor_column,a_sql_stmt in out nocopy clob, a_alias varchar2 := 'ucd.') is
218+ function generate_select_stmt(a_data_info ut_cursor_column, a_alias varchar2 := 'ucd.')
219+ return varchar2
220+ is
178221 l_alias varchar2(10) := a_alias;
179222 l_col_syntax varchar2(4000);
180223 l_ut_owner varchar2(250) := ut_utils.ut_owner;
@@ -183,12 +226,11 @@ create or replace package body ut_compound_data_helper is
183226 l_col_syntax := l_ut_owner ||'.ut_compound_data_helper.get_hash('||l_alias||a_data_info.transformed_name||'.getClobVal()) as '||a_data_info.transformed_name ;
184227 else
185228 l_col_syntax := l_alias||a_data_info.transformed_name||' as '|| a_data_info.transformed_name;
186- end if;
187- ut_utils.append_to_clob(a_sql_stmt,','|| l_col_syntax) ;
229+ end if;
230+ return l_col_syntax;
188231 end;
189232
190- procedure generate_xmltab_stmt(a_data_info ut_cursor_column, a_sql_stmt in out nocopy clob) is
191- l_sql_stmt varchar2(32767);
233+ function generate_xmltab_stmt(a_data_info ut_cursor_column) return varchar2 is
192234 l_col_type varchar2(4000);
193235 begin
194236 if a_data_info.is_sql_diffable = 0 then
@@ -209,9 +251,7 @@ create or replace package body ut_compound_data_helper is
209251 else null
210252 end;
211253 end if;
212- l_sql_stmt := case when a_sql_stmt is not null then ', ' end
213- ||a_data_info.transformed_name||' '||l_col_type||q'[ PATH ']'||a_data_info.access_path||q'[']';
214- ut_utils.append_to_clob(a_sql_stmt, l_sql_stmt);
254+ return a_data_info.transformed_name||' '||l_col_type||q'[ PATH ']'||a_data_info.access_path||q'[']';
215255 end;
216256
217257 procedure gen_sql_pieces_out_of_cursor(
@@ -225,76 +265,70 @@ create or replace package body ut_compound_data_helper is
225265 a_not_equal_stmt out nocopy clob
226266 ) is
227267 l_partition_tmp clob;
268+ l_xmltab_list ut_varchar2_list := ut_varchar2_list();
269+ l_select_list ut_varchar2_list := ut_varchar2_list();
270+ l_partition_list ut_varchar2_list := ut_varchar2_list();
271+ l_equal_list ut_varchar2_list := ut_varchar2_list();
272+ l_join_by_list ut_varchar2_list := ut_varchar2_list();
273+ l_not_equal_list ut_varchar2_list := ut_varchar2_list();
274+
275+ procedure add_element_to_list(a_list in out ut_varchar2_list, a_list_element in varchar2)
276+ is
277+ begin
278+ if a_list_element is not null then
279+ a_list.extend;
280+ a_list(a_list.last) := a_list_element;
281+ end if;
282+ end;
283+
228284 begin
229285 if a_data_info is not empty then
230286 for i in 1..a_data_info.count loop
231287 if a_data_info(i).has_nested_col = 0 then
232288 --Get XMLTABLE column list
233- generate_xmltab_stmt(a_data_info(i), a_xml_stmt );
289+ add_element_to_list(l_xmltab_list, generate_xmltab_stmt(a_data_info(i)) );
234290 --Get Select statment list of columns
235- generate_select_stmt(a_data_info(i), a_select_stmt );
291+ add_element_to_list(l_select_list, generate_select_stmt(a_data_info(i)) );
236292 --Get columns by which we partition
237- generate_partition_stmt(a_data_info(i), l_partition_tmp, a_pk_table);
293+ add_element_to_list(l_partition_list, generate_partition_stmt(a_data_info(i), a_pk_table) );
238294 --Get equal statement
239- generate_equal_sql(a_equal_stmt, a_data_info(i).transformed_name);
295+ add_element_to_list(l_equal_list,generate_equal_sql( a_data_info(i).transformed_name) );
240296 --Generate join by stmt
241- generate_join_by_stmt(a_data_info(i), a_pk_table, a_join_by_stmt );
297+ add_element_to_list(l_join_by_list, generate_join_by_stmt(a_data_info(i), a_pk_table) );
242298 --Generate not equal stmt
243- generate_not_equal_stmt(a_data_info(i), a_pk_table, a_not_equal_stmt );
299+ add_element_to_list(l_not_equal_list, generate_not_equal_stmt(a_data_info(i), a_pk_table) );
244300 end if;
245301 end loop;
246- ut_utils.append_to_clob(a_partition_stmt,', row_number() over (partition by ');
247- ut_utils.append_to_clob(a_partition_stmt, l_partition_tmp||' order by '||l_partition_tmp||' ) dup_no ');
302+
303+ a_xml_stmt := nullif(','||ut_utils.table_to_clob(l_xmltab_list, ' , '),',');
304+ a_select_stmt := nullif(','||ut_utils.table_to_clob(l_select_list, ' , '),',');
305+ l_partition_tmp := ut_utils.table_to_clob(l_partition_list, ' , ');
306+ ut_utils.append_to_clob(a_partition_stmt,' row_number() over (partition by '||l_partition_tmp||' order by '||l_partition_tmp||' ) ');
307+ a_equal_stmt := ut_utils.table_to_clob(l_equal_list, ' and ');
308+ a_join_by_stmt := ut_utils.table_to_clob(l_join_by_list, ' and ');
309+ a_not_equal_stmt := ut_utils.table_to_clob(l_not_equal_list, ' or ');
248310 else
249311 --Partition by piece when no data
250- ut_utils.append_to_clob(a_partition_stmt,', 1 dup_no ');
312+ ut_utils.append_to_clob(a_partition_stmt,' 1 ');
251313 end if;
252314 end;
253315
254- procedure get_act_and_exp_set(
255- a_current_stmt in out nocopy clob, a_partition_stmt clob, a_select_stmt clob,
256- a_xmltable_stmt clob, a_unordered boolean, a_type varchar2
257- ) is
258- l_temp_string varchar2(32767);
259- l_ut_owner varchar2(250) := ut_utils.ut_owner;
260- begin
261- ut_utils.append_to_clob(a_current_stmt, a_partition_stmt);
262-
263- l_temp_string := 'from (select ucd.item_data ';
264- ut_utils.append_to_clob(a_current_stmt,l_temp_string);
265- ut_utils.append_to_clob(a_current_stmt, a_select_stmt);
266-
267- l_temp_string := ',x.data_id, '
268- || case when not a_unordered then 'position + x.item_no ' else 'rownum ' end
269- ||'item_no from ' || l_ut_owner || '.ut_compound_data_tmp x,'
270- ||q'[ xmltable('/ROWSET/ROW' passing x.item_data columns ]' ;
271- ut_utils.append_to_clob(a_current_stmt, l_temp_string);
272-
273- ut_utils.append_to_clob(a_current_stmt, a_xmltable_stmt);
274- ut_utils.append_to_clob(a_current_stmt, case when a_xmltable_stmt is null then '' else ',' end||q'[ item_data xmltype PATH '*']');
275- if not a_unordered then
276- ut_utils.append_to_clob(a_current_stmt, ', position for ordinality ');
277- end if;
278- ut_utils.append_to_clob(a_current_stmt, ' ) ucd where data_id = :'||a_type||'_guid ) ucd ) ');
279- end;
280-
281-
282316 function gen_compare_sql(
283317 a_other ut_data_value_refcursor,
284318 a_join_by_list ut_varchar2_list,
285319 a_unordered boolean,
286320 a_inclusion_type boolean,
287321 a_is_negated boolean
288322 ) return clob is
289- l_compare_sql clob;
290- l_temp_string varchar2(32767);
291-
323+ l_compare_sql clob;
292324 l_xmltable_stmt clob;
293325 l_select_stmt clob;
294326 l_partition_stmt clob;
295327 l_equal_stmt clob;
296328 l_join_on_stmt clob;
297329 l_not_equal_stmt clob;
330+ l_where_stmt clob;
331+ l_ut_owner varchar2(250) := ut_utils.ut_owner;
298332
299333 function get_join_type(a_inclusion_compare in boolean,a_negated in boolean) return varchar2 is
300334 begin
@@ -305,62 +339,65 @@ create or replace package body ut_compound_data_helper is
305339 else ' full outer join '
306340 end;
307341 end;
308-
342+
343+ function get_item_no(a_unordered boolean) return varchar2 is
344+ begin
345+ return
346+ case
347+ when not a_unordered then 'position + x.item_no '
348+ else 'rownum '
349+ end;
350+ end;
351+
309352 begin
310- dbms_lob.createtemporary(l_compare_sql, true);
353+ dbms_lob.createtemporary(l_compare_sql, true);
354+ --Initiate a SQL template with placeholders
355+ ut_utils.append_to_clob(l_compare_sql, gc_compare_sql_template);
356+ --Generate a pieceso of dynamic SQL that will substitute placeholders
311357 gen_sql_pieces_out_of_cursor(
312358 a_other.cursor_details.cursor_columns_info, a_join_by_list,
313359 l_xmltable_stmt, l_select_stmt, l_partition_stmt, l_equal_stmt,
314360 l_join_on_stmt, l_not_equal_stmt
315361 );
316362
317- l_temp_string := 'with exp as ( select ucd.* ';
318- ut_utils.append_to_clob(l_compare_sql, l_temp_string);
319- get_act_and_exp_set(l_compare_sql, l_partition_stmt, l_select_stmt, l_xmltable_stmt, a_unordered ,'exp');
363+ l_compare_sql := replace(l_compare_sql,'{:duplicate_number:}',l_partition_stmt);
364+ l_compare_sql := replace(l_compare_sql,'{:columns:}',l_select_stmt);
365+ l_compare_sql := replace(l_compare_sql,'{:item_no:}',get_item_no(a_unordered));
366+ l_compare_sql := replace(l_compare_sql,'{:ut3_owner:}',l_ut_owner);
367+ l_compare_sql := replace(l_compare_sql,'{:xml_to_columns:}',l_xmltable_stmt);
368+ l_compare_sql := replace(l_compare_sql,'{:item_no_select:}',case when a_unordered then 'rownum' else 'nvl(e.item_no,a.item_no)' end);
369+ l_compare_sql := replace(l_compare_sql,'{:join_type:}',get_join_type(a_inclusion_type,a_is_negated));
320370
321-
322- l_temp_string :=',act as ( select ucd.* ';
323- ut_utils.append_to_clob(l_compare_sql, l_temp_string);
324- get_act_and_exp_set(l_compare_sql, l_partition_stmt, l_select_stmt, l_xmltable_stmt, a_unordered ,'act');
325-
326- l_temp_string := ' select a.item_data as act_item_data, a.data_id act_data_id,'
327- ||'e.item_data as exp_item_data, e.data_id exp_data_id, '||
328- case when a_unordered then 'rownum item_no' else 'nvl(e.item_no,a.item_no) item_no' end ||', nvl(e.dup_no,a.dup_no) dup_no '
329- ||'from act a '||get_join_type(a_inclusion_type,a_is_negated)||' exp e on ( ';
330- ut_utils.append_to_clob(l_compare_sql,l_temp_string);
331-
332- if a_unordered then
333- ut_utils.append_to_clob(l_compare_sql,' e.dup_no = a.dup_no and ');
334- end if;
335-
371+ --TODO : Refactor as equal and join is single stmt
336372 if (a_join_by_list.count > 0) then
337373 -- If key defined do the join or these and where on diffrences
338- ut_utils.append_to_clob (l_compare_sql,l_join_on_stmt);
374+ l_compare_sql := replace (l_compare_sql,'{:join_condition:}' ,l_join_on_stmt);
339375 elsif a_unordered then
340376 -- If no key defined do the join on all columns
341- ut_utils.append_to_clob (l_compare_sql,l_equal_stmt);
377+ l_compare_sql := replace (l_compare_sql,'{:join_condition:}',' e.dup_no = a.dup_no and '|| l_equal_stmt);
342378 else
343- ut_utils.append_to_clob (l_compare_sql, ' a.item_no = e.item_no ' );
379+ l_compare_sql := replace (l_compare_sql,'{:join_condition:}',' a.item_no = e.item_no ');
344380 end if;
345381
346- ut_utils.append_to_clob(l_compare_sql,' ) where ');
347-
348382 if l_not_equal_stmt is not null then
349383 if (a_join_by_list.count > 0 and not a_is_negated) or (not a_unordered) then
350- ut_utils.append_to_clob(l_compare_sql ,' ( '||l_not_equal_stmt||' ) or ');
384+ ut_utils.append_to_clob(l_where_stmt ,' ( '||l_not_equal_stmt||' ) or ');
351385 end if;
352386 end if;
353387
354388 --If its inclusion we expect a actual set to fully match and have no extra elements over expected
355389 if a_inclusion_type then
356- l_temp_string := case when a_is_negated then ' 1 = 1 ' else ' ( a.data_id is null ) ' end;
390+ ut_utils.append_to_clob(l_where_stmt, case when a_is_negated then ' 1 = 1 ' else ' ( a.data_id is null ) ' end) ;
357391 else
358- l_temp_string := ' (a.data_id is null or e.data_id is null) ';
359- end if;
360- ut_utils.append_to_clob(l_compare_sql,l_temp_string);
392+ ut_utils.append_to_clob(l_where_stmt,' (a.data_id is null or e.data_id is null) ');
393+ end if;
394+
395+ l_compare_sql := replace(l_compare_sql,'{:where_condition:}',l_where_stmt);
396+
397+ --dbms_output.put_line(l_compare_sql);
361398 return l_compare_sql;
362399 end;
363-
400+
364401 function get_column_extract_path(a_cursor_info ut_cursor_column_tab) return ut_varchar2_list is
365402 l_column_list ut_varchar2_list := ut_varchar2_list();
366403 begin
0 commit comments