Union All Method
set null "(null)"set pagesize 45variable d varchar2(1)execute :d := ','break on key skip 1 duplicatesselect key, 1 as position, rtrim( regexp_substr( c || :d, '.*?' || :d, 1, 1 ), :d ) as valfrom tUNION ALLselect key, 2 as position, rtrim( regexp_substr( c || :d, '.*?' || :d, 1, 2 ), :d ) as valfrom twhere regexp_substr( c || :d, '.*?' || :d, 1, 2 ) is not nullUNION ALLselect key, 3 as position, rtrim( regexp_substr( c || :d, '.*?' || :d, 1, 3 ), :d ) as valfrom twhere regexp_substr( c || :d, '.*?' || :d, 1, 3 ) is not nullorder by key, position, val ; KEY POSITION VAL--- ---------- ----------r01 1 v1r02 1 v1r02 2 (null)r03 1 v1r03 2 (null)r03 3 (null)r04 1 (null)r04 2 v2r04 3 (null)r05 1 (null)r05 2 (null)r05 3 v3r06 1 v1r06 2 v2r07 1 v1r07 2 v2r07 3 (null)r08 1 v1r08 2 v2r08 3 v3r09 1 (null)r10 1 (null)r10 2 (null)r11 1 (null)r11 2 (null)r11 3 (null) Before we see the first solution though, let's have a look at the test data we will use.
select * from t order by key ; KEY C NOTE--- ---------- -------------------------r01 v1 One valuer02 v1, Two values, one emptyr03 v1,, Three values, two emptyr04 ,v2, Three values, two emptyr05 ,,v3 Three values, two emptyr06 v1,v2 Two valuesr07 v1,v2, Three values, one emptyr08 v1,v2,v3 Three values, none emptyr09 (null) One empty valuer10 , Two values, two emptyr11 ,, Three values, all empty Now let's examine one possible solution. set null "(null)"set pagesize 45break on key skip 1 duplicatesvariable d varchar2(1)execute :d := ','select ta.key , i.column_value as position , substr ( ta.c2, instr( ta.c2, :d, 1, i.column_value ) + 1, instr( ta.c2, :d, 1, i.column_value + 1 ) - ( instr( ta.c2, :d, 1, i.column_value ) + 1 ) ) as valfrom ( select key , :d || c || :d as c2 , length( c || :d ) - nvl( length( replace( c, :d, null ) ), 0 ) as value_count from t ) ta inner join table( integer_varray_type( 1,2,3,4,5 ) ) i on ( i.column_value <= ta.value_count )order by key, position; KEY POSITION VAL--- ---------- ----------r01 1 v1r02 1 v1r02 2 (null)r03 1 v1r03 2 (null)r03 3 (null)r04 1 (null)r04 2 v2r04 3 (null)r05 1 (null)r05 2 (null)r05 3 v3r06 1 v1r06 2 v2r07 1 v1r07 2 v2r07 3 (null)r08 1 v1r08 2 v2r08 3 v3r09 1 (null)r10 1 (null)r10 2 (null)r11 1 (null)r11 2 (null)r11 3 (null) How It Works
To understand how the query works consider its individual components.Component 1
First we look at values derived from T.clear breaksselect key , c , LENGTH( C || :D ) - NVL( LENGTH( REPLACE( C, :D, NULL ) ), 0 ) AS VALUE_COUNTfrom torder by 1; KEY C VALUE_COUNT--- ---------- -----------r01 v1 1r02 v1, 2r03 v1,, 3r04 ,v2, 3r05 ,,v3 3r06 v1,v2 2r07 v1,v2, 3r08 v1,v2,v3 3r09 (null) 1r10 , 2r11 ,, 3 In this query VALUE_COUNT is simply the total number of delimited values in the string, determined by deriving the total number of delimiter characters in the string plus one. If your delimiter is more than one character long you will need to adjust this logic to return a correct count. Component 2
The next component is a simple integer series generator.clear breaksselect * from table( integer_varray_type( 1,2,3,4,5 ) ) ; COLUMN_VALUE------------ 1 2 3 4 5 It contains a number of rows equal to the maximum number we expect to find in VALUE_COUNT. In our example we expect no more than 5 delimited values per string. This technique is described in more detail at Integer Series Generators: Type Constructor Expression Method. It requires a custom collection type (created in the Setup code for this section) defined like this.
desc integer_varray_type integer_varray_type VARRAY(10) OF NUMBER(38) If we did not have access to such a type or could not create our own there are other integer series generation techniques to choose from. See the feature graph at the end of SQL Techniques Tutorials: Integer Series Generators for a list of alternatives. Components 1 + 2
Next we combine components 1 and 2 to form a single query that returns one row for each delimited value in each STRING.break on key skip 1 duplicatescolumn c2 format a12select ta.key, ta.c2, ta.value_count, i.column_valuefrom ( select key , :d || c || :d as c2 , LENGTH( C || :D ) - NVL( LENGTH( REPLACE( C, :D, NULL ) ), 0 ) AS VALUE_COUNT from t ) ta inner join TABLE( INTEGER_VARRAY_TYPE( 1,2,3,4,5 ) ) I on ( i.column_value <= ta.value_count )order by 1, 2, 3, 4; KEY C2 VALUE_COUNT COLUMN_VALUE--- ------------ ----------- ------------r01 ,v1, 1 1r02 ,v1,, 2 1r02 ,v1,, 2 2r03 ,v1,,, 3 1r03 ,v1,,, 3 2r03 ,v1,,, 3 3r04 ,,v2,, 3 1r04 ,,v2,, 3 2r04 ,,v2,, 3 3r05 ,,,v3, 3 1r05 ,,,v3, 3 2r05 ,,,v3, 3 3r06 ,v1,v2, 2 1r06 ,v1,v2, 2 2r07 ,v1,v2,, 3 1r07 ,v1,v2,, 3 2r07 ,v1,v2,, 3 3r08 ,v1,v2,v3, 3 1r08 ,v1,v2,v3, 3 2r08 ,v1,v2,v3, 3 3r09 ,, 1 1r10 ,,, 2 1r10 ,,, 2 2r11 ,,,, 3 1r11 ,,,, 3 2r11 ,,,, 3 3 Component 3
To extract individual values from each string we use the parsing logic described in the String to Columns: Separated Values tutorial. Here is an example of how the logic extracts the first value from a delimited string.column first_val format a9select c2, instr( c2, :d, 1, 1 ) position_of_first_delim , instr( c2, :d, 1, 2 ) position_of_second_delim , substr ( c2, instr( c2, :d, 1, 1 ) + 1, -- == position of 1st character in value instr( c2, :d, 1, 2 ) - ( instr( c2, :d, 1, 1 ) + 1 ) -- == length of 1st value ) as first_valfrom ( select :d || c || :d as c2 from t where key = 'r08' ); C2 POSITION_OF_FIRST_DELIM POSITION_OF_SECOND_DELIM FIRST_VAL------------ ----------------------- ------------------------ ---------,v1,v2,v3, 1 4 v1 Final Solution
Adding the parsing logic from Component 3 to the query from "Components 1 + 2" yields the final solution.select ta.key , i.column_value as position , substr ( ta.c2, instr( ta.c2, :d, 1, i.column_value ) + 1, instr( ta.c2, :d, 1, i.column_value + 1 ) - ( instr( ta.c2, :d, 1, i.column_value ) + 1 ) ) as valfrom ( select key , :d || c || :d as c2 , length( c || :d ) - nvl( length( replace( c, :d, null ) ), 0 ) as value_count from t ) ta inner join table( integer_varray_type( 1,2,3,4,5 ) ) i on ( i.column_value <= ta.value_count )order by key, position; KEY POSITION VAL--- ---------- ----------r01 1 v1r02 1 v1r02 2 (null)r03 1 v1r03 2 (null)r03 3 (null)r04 1 (null)r04 2 v2r04 3 (null)r05 1 (null)r05 2 (null)r05 3 v3r06 1 v1r06 2 v2r07 1 v1r07 2 v2r07 3 (null)r08 1 v1r08 2 v2r08 3 v3r09 1 (null)r10 1 (null)r10 2 (null)r11 1 (null)r11 2 (null)r11 3 (null)
No comments:
Post a Comment