oracle sql statement to parse string - sql

I have a database field that stores a password with junk characters between each letter in the password. 3 junk chars, then 2 junk chars, then 3 junk chars, etc There will be 3 junk chars to start password and 2 or 3 junk chars at end of password.
So if password is BOB, the db value will be xxxBxxOxxxBxx where x is a random character.
Is there a way to return BOB in an oracle select statement using substrings,etc?
Thanks for anyone up for this challenge

You can use the regular expression ...((.)..(.)?)? and just keep the 2nd and 3rd capture groups:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name ( password ) AS
SELECT 'xxx' FROM DUAL UNION ALL
SELECT 'xxxBxx' FROM DUAL UNION ALL
SELECT 'xxxBxxOxxx' FROM DUAL UNION ALL
SELECT 'xxxBxxOxxxBxx' FROM DUAL UNION ALL
SELECT 'xxxBxxOxxxBxxBxxx' FROM DUAL UNION ALL
SELECT 'xxxBxxOxxxBxxBxxxOxx' FROM DUAL UNION ALL
SELECT 'xxxBxxOxxxBxxBxxxOxxBxxx' FROM DUAL UNION ALL
SELECT 'xxxBxxOxxxBxxBxxxOxxBxxxBxx' FROM DUAL UNION ALL
SELECT 'xxxBxxOxxxBxxBxxxOxxBxxxBxxOxxx' FROM DUAL UNION ALL
SELECT 'xxxBxxOxxxBxxBxxxOxxBxxxBxxOxxxBxx' FROM DUAL;
Query 1:
SELECT REGEXP_REPLACE(
password,
'...((.)..(.)?)?',
'\2\3'
) As password
FROM table_name
Results:
| PASSWORD |
|-----------|
| (null) |
| B |
| BO |
| BOB |
| BOBB |
| BOBBO |
| BOBBOB |
| BOBBOBB |
| BOBBOBBO |
| BOBBOBBOB |

If you have alphanumeric characters as junk as well and you know your maximum password length then you could do it the dirty way using substr() function. I've generated numbers with 2 and 3 letters gap from 4 to 98 first and cross joined it to table which stores passwords to avoid typing each number by hand. This will cover passwords up to 28 characters. Feel free to play with that.
Test data
create table t(pw varchar(255));
insert into t values ('xxxBxxOxxxBxxFxxxIxxVxxxExx!xxx');
insert into t values ('xxxPxxAxxxSxxSxxx');
Solution
Uses internal table to generate values used as input for substring function, cross join to apply each substring and then listagg to combine it again
with lookup as (
select column_value as nr
from table(sys.odcinumberlist(4,7,11,14,18,21,25,28,32,35,39,42,46,49,53,56,60,63,67,70,74,77,81,84,88,91,95,98))
)
select listagg(substr(t.pw, l.nr, 1), '') within group(ORDER BY l.nr) as password
from lookup l
cross join t
group by t.pw;
Output
password
--------
BOBFIVE!
PASS
Check it out here: Live DEMO
This solution may take a bit of time to process for many rows.

Here is a PL/SQL solution that will take a password of any size:
CREATE OR REPLACE FUNCTION decode_password (p_password IN VARCHAR2)
RETURN VARCHAR2
DETERMINISTIC
AS
l_ret VARCHAR2 (100);
l_pos INT := 4;
BEGIN
WHILE LENGTH (p_password) >= l_pos
LOOP
l_ret := l_ret || SUBSTR (p_password, l_pos, 1);
l_pos := l_pos + 3;
l_ret := l_ret || SUBSTR (p_password, l_pos, 1);
l_pos := l_pos + 4;
END LOOP;
RETURN l_ret;
END decode_password;
To test it:
WITH
aset
AS
(SELECT 'BBBBRRRRIIIAAAANNN' pwd
FROM DUAL)
SELECT pwd, decode_password (pwd) decoded
FROM aset;
The result is
BBBBRRRRIIIAAAANNN BRIAN

Reusing the idea from here, though in this case the REGEXP_REPLACE() approach from MTO is definitely the best one.
First we generate CTE as a number table with integer from 0 to the maximum of plaintext characters of a password minus 1.
We can get the number of cleartext characters in an obfuscated string as follows:
We can ignore the first 3 characters, those will always be garbage.
If the number of plaintext characters is even, there will be 7 characters for each 2 plaintext characters, because the total of the garbage characters is 5 for 2 plaintext characters.
So we get the number of plaintext characters with FLOOR((LENGTH(PASSWORDS.PASSWORD) - 3) / 7) * 2 if it is even.
If it is odd, it is one more than an even number of plaintext characters and the length of the string's length minus 3 is no longer divisible by 7, because the last 1 plaintext character will be followed by 2 garbage characters.
So we can check for the string length minus 3 modulo 7. If it is 0 the number of plaintext characters is even, we don't need to add anything. If it isn't 0, we add 1 and get the total (odd) number of plaintext characters.
Together that's FLOOR((LENGTH(PASSWORDS.PASSWORD) - 3) / 7) * 2 + DECODE(MOD(LENGTH(PASSWORDS.PASSWORD) - 3, 7), 0, 0, 1).
We left join that CTE to the passwords table, so that for each cleartext character of a password there is a row with the password and a number from 0 to the number of cleartext characters of the password.
We can now use SUBSTR() to get each cleartext character. The base offset is 4, as we can ignore the first three characters. The joined number from the CTE let us calculate the additional offset. We always advance by at least 3 characters, which gives us CTE.I * 3. Additionally every 2 cleartext characters we need to further advance 1 time, so we add FLOOR(CTE.I / 2) giving us SUBSTR(PASSWORDS.PASSWORD, 4 + CTE.I * 3 + FLOOR(CTE.I / 2), 1).
No we have every single cleartext character, but in different rows. To concatenate them back together we group by the obfuscated password (and possibly by an ID too, should there be more then one row in the base table with the same password) and use LISTAGG. Ordering the the number from the CTE makes sure every plaintext character gets the right position.
WITH CTE(I)
AS
(
SELECT 0 I
FROM DUAL
UNION ALL
SELECT CTE.I + 1
FROM CTE
WHERE CTE.I + 1 < (SELECT MAX(FLOOR((LENGTH(PASSWORDS.PASSWORD) - 3) / 7) * 2
+ DECODE(MOD(LENGTH(PASSWORDS.PASSWORD) - 3, 7),
0, 0,
1))
FROM PASSWORDS)
)
SELECT PASSWORDS.ID,
PASSWORDS.PASSWORD PASSWORD_OBFUSACTED,
LISTAGG(SUBSTR(PASSWORDS.PASSWORD, 4 + CTE.I * 3 + FLOOR(CTE.I / 2), 1))
WITHIN GROUP (ORDER BY CTE.I) PASSWORD_CLEARTEXT
FROM PASSWORDS
LEFT JOIN CTE
ON CTE.I < FLOOR((LENGTH(PASSWORDS.PASSWORD) - 3) / 7) * 2
+ DECODE(MOD(LENGTH(PASSWORDS.PASSWORD) - 3, 7),
0, 0,
1)
GROUP BY PASSWORDS.ID,
PASSWORDS.PASSWORD;
db<>fiddle
Note: This demonstrates, that an attacker must not even guess the random character (or characters, even that wouldn't make a difference), to get the cleartext password. Ergo this is an unsafe method to store passwords! Use hashing instead, that's (most likely, of course depending on the algorithm) irreversible.

Related

Issues with SUBSTR function Oracle_SQL

I used the SUBSTR function for the similar purposes, but I encountered the following issue:
I am extracting 6 characters from the right, but the data in column is inconsistent and for some rows it has characters less than 6, i.e. 5 or 4. So for such rows, the function returns blanks. How can I fix this?
Example Scenario 1:
SUBSTR('0000123456',-6,6)
Output: 123456
Scenario 2 (how do I fix this?, I need it to return '23456'):
SUBSTR('23456',-6,6)
Output: ""
You can use a case expression: if the string length is strictly greater than 6 then return just the last 6 characters; otherwise return the string itself. This way you don't need to call substr unless it is really needed.
Alternatively, if speed is not the biggest issue and you are allowed to use regular expressions, you can write this more compactly - select between 0 and 6 characters - as many as possible - at the end of the string.
Finally, if you don't mind using undocumented functions, you can use reverse and standard substr (starting from character 1 and extracting the first 6 characters; that will work as expected even if the string has length less than 6). So: reverse the string, extract first (up to) 6 characters, and then reverse again to restore the order. WARNING: This is shown only for fun; DO NOT USE THIS METHOD!
with
test_data (str) as (
select '0123449389' from dual union all
select '00000000' from dual union all
select null from dual union all
select 'abcd' from dual
)
select str,
case when length(str) > 6 then substr(str, -6) else str end as case_substr,
regexp_substr(str, '.{0,6}$') as regexp_substr,
reverse(substr(reverse(str), 1, 6)) as rev_substr
from test_data
;
STR CASE_SUBSTR REGEXP_SUBSTR REV_SUBSTR
---------- ------------- ------------- --------------
0123449389 449389 449389 449389
00000000 000000 000000 000000
abcd abcd abcd abcd
One method uses coalesce():
select coalesce(substr('23456', -6, 6), '23456')
Another tweaks the length:
select substr('23456', greatest(- length('23456'), -6), 6)

How to pull a value in between multiple values?

I have a column named Concatenated Segments which has 12 segment values, and I'm looking to edit the formula on the column to only show the 5th segment. The segments are separated by periods.
How would I need to edit the formula to do this?
Would using a substring work?
Alternatively, using good old SUBSTR + INSTR combination
possibly faster on large data sets
which doesn't care about uninterrupted strings (can contain anything between dots)
SQL> WITH
2 -- thank you for typing, #marcothesane
3 indata(s) AS (
4 SELECT '1201.0000.5611005.0099.211003.0000.2199.00099.00099.0000.0000.00000' FROM dual
5 )
6 select substr(s, instr(s, '.', 1, 4) + 1,
7 instr(s, '.', 1, 5) - instr(s, '.', 1, 4) - 1
8 ) result
9 from indata;
RESULT
------
211003
SQL>
Use REGEXP_SUBSTR(), searching for the 5th uninterrupted string of digits, or the 5th uninterrupted string of anything but a dot (\d and [^\.]) starting from position 1 of the input string:
WITH
-- your input ... paste it as text next time, so I don't have to manually re-type it ....
indata(s) AS (
SELECT '1201.0000.5611005.0099.211003.0000.2199.00099.00099.0000.0000.00000' FROM dual
)
SELECT
REGEXP_SUBSTR(s,'\d+',1,5) AS just_digits
, REGEXP_SUBSTR(s,'[^\.]+',1,5) AS between_dots
FROM indata;
-- out just_digits | between_dots
-- out -------------+--------------
-- out 211003 | 211003

Get first value and last value

i have below record like '8|12|53|123|97' and i need to find the range of values between 8 to 97, so that i need the number 8 and 97.
You can use REGEXP_SUBSTR as following:
SQL> SELECT
2 REGEXP_SUBSTR('8|12|53|123|97', '^[0-9]+') FIRSTVAL,
3 REGEXP_SUBSTR('8|12|53|123|97', '[0-9]+$') LASTVAL
4 FROM
5 DUAL;
FIRSTVAL LASTVAL
---------- ----------
8 97
SQL>
^ matches the beginning of a string.
$ matches the end of a string.
Cheers!!
Here is a solution which will work for any string which has at least one pipe.
with cte as (
select '8|12|53|123|97' str from dual
)
, rng as (
select to_number(substr(str, 1, instr(str, '|')-1)) as token_1
,to_number(substr(str, instr(str, '|', -1)+1)) as token_2
from cte )
select token_1 + level - 1 as tkn
from rng
connect by level <= (token_2 - token_1) + 1
/
The first subquery is just your test data. The second subquery identifies the first number (token_1) and the last number (token_2) in the string. It uses substr() and instr() just because they are faster than regex. instr() with a negative offset finds the last occurence of the search argument.
The main query generates a range of numbers from the bounds of the rng subquery. Not sure if that's in your requirement (depends on what you mean by "range of values between").
Because this model is not in First Normal Form you are exposed to data quality issues. The query will not produce results if the first or last tokens are not numeric, or there's only one token or the separator is not a pipe.

Number check in oracle sql

How to check in 10 digit number whether it contain 999 or 000 in the 4-6th bytes ?
I have a n idea with using INSTR but i don't know how to execute it
This is strange. If the "number" is really a string, then you can use like or substr():
where col like '___999%' or col like '___000%'
or:
where substr(col, 4, 3) in ('999', '000')
or even regular expressions.
Given the nature of your question, you can turn a number into a string and use these methods. However, if you are looking at particular digits, then the "number" should be stored as a string.
If they are actually numbers rather than strings then you could use numeric manipulation:
with t (n) as (
select 1234567890 from dual
union all select 1239997890 from dual
union all select 1230007890 from dual
union all select 1299967890 from dual
union all select 1234000890 from dual
)
select n,
mod(n, 10000000) as stage1,
mod(n, 10000000)/10000 as stage2,
trunc(mod(n, 10000000)/10000) as stage3,
case when trunc(mod(n, 10000000)/10000) in (0, 999) then 'Yes' else 'No' end as matches
from t;
N STAGE1 STAGE2 STAGE3 MATCHES
---------- ---------- ---------- ---------- -------
1234567890 4567890 456.789 456 No
1239997890 9997890 999.789 999 Yes
1230007890 7890 .789 0 Yes
1299967890 9967890 996.789 996 No
1234000890 4000890 400.089 400 No
Stage 1 effectively strips off the first three digits. Stage two almost strips off the last four digits, but leaves fractions, so stage 3 adds trunc() (you could also use floor()) to ignore those fractional parts.
The result of that is the numeric value of the 4-6th digits, and you can then test if that is 0, 999 or something else.
This is really looking at the 4th to 6th most significant digits, which is the same if the number is always 10 digits; if it might actually have different numbers of digits then you'd need to clarify what you want to see.
select
1 from dual where instr(98800054542,000,4,3)in (6) or instr(98800054542,999,4,3)in (6); let me know if this helped.

Getting string from rightside 10 char

My table is in oracle toad. teh table contain column name phone number varchar2 datatype.it contain set of phonenumbers. some numbers are more than 10 char. i want to filter that number from right side 10 char.
data's in the table
-------------------
phone number
9948184759
9948220955
994823298612
9948249815
99482599971234
9948277935
9948288258
99483015076789
9948335085
9948337552
9948338134
the above column values are phone numbers.but some numbers are more than 10 char length
that numbers are
----------------
994823298612
99482599971234
99483015076789
expected output for the above numbers
----------------------------------------
4823298612
2599971234
3015076789
Help me to do this? am new to oracle toad
Simpler:
select substr(phone_number, -10) from ...
You can achieve that by using Substr function for example
with T1 as
(
select 99482599971234 n from dual union all
select 99483015076789 n from dual union all
select 994823298612 n from dual
)
select substr(n, Length(n) - 9, 10) nn
from t1
Nn
-------------------
4823298612
2599971234
3015076789