Как маскировать символы с X в поле varchar2 в Oracle SQL

У меня есть список записей varchar (столбец) varchar2(30) с именами. Как маскировать символы внутри имени с перечисленными критериями.

e.g.

Tristram Vladimir Chan   <---Original name
1234567890123456789012   <---ruler (character count)
.....XXXXX.....XXXXX.....<--- masking criteria: copy every 5 characters, mask next 5 characters to X
TristXXX XladimXX XXan   <--- expected results

Что делает это сложным, так это особая обработка символа пробела " "...

Какой самый быстрый способ сделать это? любые библиотеки, процедуры или ярлыки, которые можно использовать? Спасибо большое ребята!!!!


person Din    schedule 27.09.2013    source источник
comment
Лучше всего использовать REGEXP_REPLACE, это может занять некоторое время, чтобы написать его правильно.   -  person DARK_A    schedule 27.09.2013


Ответы (3)


Чтобы заменить все, кроме пробела, вы должны использовать regexp_replace. Если имена имеют максимальную длину (как я предполагаю), оператор легко написать. Если нет, то вам понадобится цикл, который вы бы реализовали либо с помощью хранимой функции, либо с помощью какого-то трюка. Вот утверждение для имен до 30 букв:

select substr(full_name,1,5) || regexp_replace(substr(full_name,6,5), '[^ ]', 'X') ||
       substr(full_name,11,5) || regexp_replace(substr(full_name,16,5), '[^ ]', 'X') ||
       substr(full_name,21,5) || regexp_replace(substr(full_name,26,5), '[^ ]', 'X') as masked_name
from
(  
  select 'Tristram Vladimir Chan ' as full_name from dual
)
person Thorsten Kettner    schedule 27.09.2013
comment
@ajmalmhd04: Да, это то, что я сказал выше. - person Thorsten Kettner; 30.09.2013
comment
Ага. Извините, я не могу сделать резкое сканирование там :) - person ajmalmhd04; 30.09.2013

Моя попытка идет здесь:

SELECT listagg(dd ,'') within group(ORDER BY rn)
from(
SELECT SPLIT,
     rn, case when mod(rn,10) <=5 and mod(rn,10) >0 then split WHEN split  = ' ' THEN ' ' else 'X' end dd    
FROM
     (SELECT regexp_substr('Tristram Vladimir Chan', '.',LEVEL)split,
          ROWNUM rn
     FROM dual
          CONNECT BY LEVEL <= LENGTH('Tristram Vladimir Chan')
     ));

http://sqlfiddle.com/#!4/d41d8/17810/0

person ajmalmhd04    schedule 27.09.2013

EDIT1: Извините, я забыл об управлении пробелами: вот a решение:

SET serveroutput ON format wraped;
DECLARE
  l_text        varchar2(64) := 'Tristram Vladimir Chan';
  l_mask        VARCHAR2(64) := '.....XXXX..XX.....X.XX';
  l_res         VARCHAR2(64);
  l_text_length INTEGER := length(l_text);
  l_mask_length INTEGER := length(l_mask);
  l_pos         INTEGER := 1;
  l_p           INTEGER := -1;
  l_x           INTEGER := -1;
  l_b           INTEGER := instr(l_text, ' ');
  l_lastb       INTEGER := 0;
  l_cpt         INTEGER := 1;
BEGIN
  -- in case text length doesn't match mask length
  if l_mask_length > l_text_length then
    l_mask := substr(l_mask, 1, l_text_length);
  elsif l_text_length > l_mask_length then
    l_mask := rpad(l_mask, l_text_length, '.');
  end if;
  l_mask_length := l_text_length;
  -- loop for each sequence of characters in the mask
  while l_pos <= l_mask_length loop
    l_p := instr(l_mask, '.'); -- first '.' position
    l_x := instr(l_mask, 'X'); -- first 'X' position
    -- if "." or "X' has not been found
    if l_p = 0 or l_x = 0 then 
      if l_p = 0 then -- no "." found, write 'X' until the end
        l_res := rpad(l_res, l_text_length, 'X');
      elsif l_x = 0 then -- no "X" found, write text until the end
        l_res := l_res || substr(l_text, l_pos, l_text_length);
      end if;
      l_pos := l_mask_length+1;
    else
      if l_p = 1 then -- '.' found, write text until the next 'X'
        l_res := l_res || substr(l_text, l_pos, l_x-1);
        l_mask := substr(l_mask, l_x);
        l_pos := l_pos + l_x-1;
      elsif l_x = 1 then -- 'X' found, write 'X' until the next '.'
        l_res := rpad(nvl(l_res,'X'), nvl(length(l_res),0)+l_p-1, 'X');
        l_mask := substr(l_mask, l_p);
        l_pos := l_pos + l_p-1;
      end if;
    end if;
    -- if a ' ' is found in the original text, replace it in the result
    if l_pos > l_b and l_b > l_lastb then
      l_b := instr(l_text, ' ', 1, l_cpt);
      l_res := substr(l_res, 1, l_b-1) || ' ' || substr(l_res,l_b+1,length(l_res));
      l_lastb := l_b;
      l_cpt := l_cpt + 1;
    end if;
  end loop;
  dbms_output.put_line(l_res);
END;

Результат: TristXXX VlXXimir XhXX

Возможно, это можно улучшить, я просто написал «процедурно». Преимущество по сравнению с другими решениями в том, что здесь можно менять маску.

EDIT2: я переработал код, он стал намного проще...:

SET serveroutput ON format wraped;
DECLARE
  l_text        varchar2(64) := 'Tristram Vladimir Chan';
  l_mask        VARCHAR2(64) := '.....XX..XX....XXXX...';
  l_res         VARCHAR2(64);
  l_text_length INTEGER := length(l_text);
  l_mask_length INTEGER := length(l_mask);
  l_pos         INTEGER := 1;
BEGIN
  -- in case text length doesn't match mask length
  if l_mask_length > l_text_length then
    l_mask := substr(l_mask, 1, l_text_length);
  elsif l_text_length > l_mask_length then
    l_mask := rpad(l_mask, l_text_length, '.');
  end if;
  l_mask_length := l_text_length;
  -- loop and build the result string
  while l_pos <= l_mask_length loop
    if substr(l_mask, l_pos, 1) = 'X' then
      l_res := l_res || 'X';
    else
      l_res := l_res || substr(l_text, l_pos, 1);
    end if;
    if substr(l_text, l_pos, 1) = ' ' then
      l_res := substr(l_res, 1, l_pos-1) || ' ';
    end if;
    l_pos := l_pos + 1;
  end loop;
  dbms_output.put_line(l_res);
END;

Результат: TristXXm XXadimXX Xhan

person Yann39    schedule 27.09.2013
comment
Спасибо. результат не содержит пробелов. есть ли способ исправить это? - person Din; 27.09.2013