sqloraclecsv

How to get a value according to its number of occurrence


I have a Key-Value table like this:

enter image description here

How to get a value according to the occurrence index of the key.

I only got the key index using

select INSTR ( key,
       'key5<>'
      , 1,1
      )
  from table ;

How should I get the complementary value to the delimiter occurrence?

I want to get the parallel value to 'key5' which is 961.


Solution

  • Your instr query is telling you the position of the first character of your key within the string (i.e. that it's the 25th character), not the position of the element (i.e. that it's the fifth); which doesn't help you find the matching value at all, as that won't start in the same character position. It could also give you false positives, e.g. if 'key50' appeared before 'key5' (I'm guessing your keys aren't really sequential as you've shown).

    You can use regular expressions to extract all the keys and values, with a recursive CTE (subquery factoring) to work through all the key/value pairs.

    with rcte (all_keys, all_values, key_count, pos, key, value) as (
      select key, value, regexp_count(key, '(.*?)(<>|$)'), 1,
        regexp_substr(key, '(.*?)(<>|$)', 1, 1, null, 1),
        regexp_substr(value, '(.*?)(<>|$)', 1, 1, null, 1)
      from your_table
      union all
      select all_keys, all_values, key_count, pos + 1,
        regexp_substr(all_keys, '(.*?)(<>|$)', 1, pos + 1, null, 1),
        regexp_substr(all_values, '(.*?)(<>|$)', 1, pos + 1, null, 1)
      from rcte
      where pos < key_count - 1
    )
    select pos, key, value from rcte
    
    POS KEY VALUE
    1 key1 300
    2 key2 77
    3 key3 -15
    4 key4 40
    5 key5 961
    6 key6 1
    7 key7 300
    8 key8 -1

    The anchor branch gets the original delimited key and value strings, a count of how many keys there are, and kicks things off by finding the first values matching the regex pattern. The recursive branch then looks for successive matches, until all the keys have been used up.

    Then just filter for the key value you need:

    with rcte (all_keys, all_values, key_count, pos, key, value) as (
      select key, value, regexp_count(key, '(.*?)(<>|$)'), 1,
        regexp_substr(key, '(.*?)(<>|$)', 1, 1, null, 1),
        regexp_substr(value, '(.*?)(<>|$)', 1, 1, null, 1)
      from your_table
      union all
      select all_keys, all_values, key_count, pos + 1,
        regexp_substr(all_keys, '(.*?)(<>|$)', 1, pos + 1, null, 1),
        regexp_substr(all_values, '(.*?)(<>|$)', 1, pos + 1, null, 1)
      from rcte
      where pos < key_count - 1
    )
    select value
    from rcte
    where key = 'key5'
    
    VALUE
    961

    fiddle


    If you have a small-ish maximum number of keys you could also use a case expression with matching when/then clauses for each possible position, but it would be harder to maintain and would probably perform worse - but just for for fun, this fiddle shows that approach.

    Either way, having to do this demonstrates why it's not a good idea to store delimited data in strings; it's better to store it relationally in the first place.