sql separators (DB2 but ORACLE can be too) - sql

I need help with separators in sql. I'm working on DB2 but Oracle is also good.
I need to build query where I've got data in format: aaa.bbb.ccc.ddd#domain.com
where 'aaa', 'bbb', 'ccc', 'ddd' got not constant length. Query should return bbb and ddd. In DB2 I can cut '#domain.com' which takes me really long line. Rest I have no idea how to move. I tried with SUBSTR but nothing has work like it should nad my queries are super long.
I need query not block.
EXAMPLE:
data in column:
John.W.Smith.JWS1#domain.com
Alexia.Nova.Alnov#domain.com
Martha.Heart.Martha2#domain.com
etc.
In general I need to get data from between 1st and 2nd separator . and the one which is in front of #.

I'm sure someone will have some clever REGEX way of doing it, but this is one way to do it.
with test as
( select 'John.W.Smith.JWS1#domain.com' col1 from dual union all
select 'Alexia.Nova.Alnov#domain.com' from dual union all
select 'Martha.Heart.Martha2#domain.com' from dual
)
select col1
, substr( col1, 1, dot_one-1 ) f1
, substr( col1, dot_one+1, dot_two - dot_one -1 ) f2
, no_domain
, substr( no_domain, dot_before_at+1 ) f3
from
(
select col1
,instr( col1, '#', -1 ) at_pos
,instr( col1, '.',1,1) dot_one
,instr( col1, '.',1,2) dot_two
,substr( col1, 1, instr(col1, '#', -1 )-1) no_domain
,instr( substr( col1, 1, instr( col1, '#', -1 ) -1 ) , '.', -1 ) dot_before_at
from test
)

Related

How to include the code into a REPLACE function in oracle?

User #psaraj12 helped me with a ticket here about finding ascii character in a string in my DB with the following code:
with test (col) as (
select
'L.A.D'
from
dual
union all
select
'L.􀈉.D.'
from
dual
)
select
col,
case when max(ascii_of_one_character) >= 65535 then 'NOT OK' else 'OK' end result
from
(
select
col,
substr(col, column_value, 1) one_character,
ascii(
substr(col, column_value, 1)
) ascii_of_one_character
from
test cross
join table(
cast(
multiset(
select
level
from
dual connect by level <= length(col)
) as sys.odcinumberlist
)
)
)
group by
col
having max(ascii_of_one_character) >= 4000000000;
The script looks for characters of a certain range GROUPs them and marks displays them.
Is it possible to include this in a REPLACE statement of a similar sort:
REPLACE(table.column, max(ascii_of_one_character) >= 4000000000, '')
EDIT: As per #flyaround answer this is the code I use changed a little bit:
with test (col) as (
select skunden.name1
from skunden
)
select col
, REGEXP_REPLACE(col, 'max(ascii_of_one_character)>=4000000000', '') as cleaned
, CASE WHEN REGEXP_COUNT(col, 'max(ascii_of_one_character)>=4000000000') > 0 THEN 0 ELSE 1 END as isOk
from test;
Coming back to your original code, because my suggested REGEX_REPLACE is not working sufficient with high surrogates. Your approach is already very effective, so I jumped into it to have a solution here.
MERGE
INTO skunden
USING (
select
id as innerId,
name as innerName,
case when max(ascii_of_one_character) >= 65535 then 0 else 1 end isOk,
listagg(case when ascii_of_one_character <65535 then one_character end , '') within group (order by rn) as cleaned
from
(
select
id,
name,
substr(name, column_value, 1) one_character,
ascii(
substr(name, column_value, 1)
) ascii_of_one_character
, rownum as rn
from
skunden cross
join table(
cast(
multiset(
select
level
from
dual connect by level <= length(name)
) as sys.odcinumberlist
)
)
)
group by
id, name
having max(ascii_of_one_character) >= 4000000000
)
ON (skunden.id = innerId)
WHEN MATCHED THEN
UPDATE
SET name = cleaned
;
On MERGE you can't use the referencing column for an update. Therefore you should use the unique key (I used 'id' in my example) of your table.
The resulting value will be 'L..D' for your example value of 'L.􀈉.D.'
If I got your question correctly you would like to remove characters with a higher decimal representation of characters than specified.
You could check to use REGEXP_REPLACE for this, like:
with test (col) as (
select
'L.A.D'
from
dual
union all
select
'L.􀈉.D.'
from
dual
)
select col
, REGEXP_REPLACE(col, '[^\u00010000-\u0010FFFF]+$', '') as cleaned
, CASE WHEN REGEXP_COUNT(col, '[^\u00010000-\u0010FFFF]+$') > 0 THEN 0 ELSE 1 END as isOk
from test;

Substring from underscore and onwards in Oracle

I have a string with under score and some characters. I need to apply substring and get values to the left excluding underscore. So I applied below formula and its working correctly for those strings which have underscore (_). But for strings without (_) it is bringing NULL. Any suggestions how this can be handled in the substring itself.
Ex: ABC_BASL ---> Works correctly; ABC ---> gives null
My formula as below -
select SUBSTR('ABC_BAS',1,INSTR('ABC_BAS','_')-1) from dual;
ABC
select SUBSTR('ABC',1,INSTR('ABC','_')-1) from dual;
(NULL)
You could use a CASE expression to first check for an underscore:
WITH yourTable AS (
SELECT 'ABC_BAS' AS col FROM dual UNION ALL
SELECT 'ABC' FROM dual
)
SELECT
CASE WHEN col LIKE '%\_%' ESCAPE '\'
THEN SUBSTR(col, 1, INSTR(col, '_') - 1)
ELSE col END AS col_out
FROM yourTable;
Use regular expression matching:
SELECT REGEXP_SUBSTR('ABC_BAS', '(.*)([_]|$)?', 1, 1, NULL, 1) FROM DUAL;
returns 'ABC', and
SELECT REGEXP_SUBSTR('ABC', '(.*)([_]|$)?', 1, 1, NULL, 1) FROM DUAL;
also returns 'ABC'.
db<>fiddle here
EDIT
The above gives correct results, but I missed the easiest possible regular expression to do the job:
SELECT REGEXP_SUBSTR('ABC_BAS', '[^_]*') FROM DUAL;
returns 'ABC', as does
SELECT REGEXP_SUBSTR('ABC', '[^_]*') FROM DUAL;
db<>fiddle here
Yet another approach is to use the DECODE in the length parameter of the substr as follows:
substr(str,
1,
decode(instr(str,'_'), 0, lenght(str), instr(str,'_') - 1)
)
You seem to want everything up to the first '_'. If so, one method usesregexp_replace():
select regexp_replace(str, '(^[^_]+)_.*$', '\1')
from (select 'ABC' as str from dual union all
select 'ABC_BAS' from dual
) s
A simpler method is:
select regexp_substr(str, '^[^_]+')
from (select 'ABC' as str from dual union all
select 'ABC_BAS' from dual
) s
Here is a db<>fiddle.
I'd use
regexp_replace(text,'_.*')
or if performance was a concern,
substr(text, 1, instr(text||'_', '_') -1)
For example,
with demo(text) as
( select column_value
from table(sys.dbms_debug_vc2coll('ABC', 'ABC_DEF', 'ABC_DEF_GHI')) )
select text
, regexp_replace(text,'_.*')
, substr(text, 1, instr(text||'_', '_') -1)
from demo;
TEXT REGEXP_REPLACE(TEXT,'_.*') SUBSTR(TEXT,1,INSTR(TEXT||'_','_')-1)
------------ --------------------------- -------------------------------------
ABC ABC ABC
ABC_DEF ABC ABC
ABC_DEF_GHI ABC ABC
Ok i think i got it. Add nvl to the substring and insert the condition as below -
select nvl(substr('ABC',1,instr('F4001Z','_')-1),'ABC') from dual;

SQL SELECT with bad data

If a column has bad data such as:
45612345698
(456)123-7452
125-145-9856
Without fixing the data. Is it possible to have a sql query of 1251459856 which then would return the 3rd item in the column?
Hmmm . . . you could use replace():
where replace(replace(replace(col, '-', ''), '(', ''), ')', '') = '1251459856'
If your data is worse than just "-",")" and "(" you could go for a more generic solution and strip on any non-numeric character with the following
WITH sample_data_tab (str) AS
(
SELECT '45612345698' FROM DUAL UNION
SELECT '(456)123-7452' FROM DUAL UNION
SELECT '125-145-9856' FROM DUAL UNION
SELECT '989 145 9856' FROM DUAL
)
SELECT regexp_replace(str, '[^0-9]', '') FROM sample_data_tab

SQL script to remove zeros between alphabetic and numeric values within a field

I would like to update a table within my db2 database and remove the zeros that are between the alphabetic and numeric values.
For example I have the column element: CompanyName. I would like to get all the CompanyName's that have the zero(s) between alphabetic and numeric values, i.e. ABCD001234, and replace it with ABCD1234. There are over 30000 of these values, so a script is needed.
Some more examples of the trimming are shown below:
ABCD1234 -> ABCD1234 (no change)
JFKD011011 -> JFKD11011
A000000001 -> A1
Z000000000 -> Z0
Preferably, I would like a script that I can test without the UPDATE, and then add the UPDATE statement after the results look correct.
try this:
select
trim(translate(CompanyName, ' ', '0123456789')) ||
cast(translate(CompanyName, ' ', 'ABCDEFGIJKLMNOPQRSTUVWXYZ') as integer)
from yourtable
Assuming your column is called COL1, in table TABLE1, you can obtain new values as desidered in NEWSTR. From there you can easily prepare the UPDATE:
SELECT COL1, SUBSTR(COL1, 1, ZX)
|| CAST( CAST(SUBSTR(COL1, ZX+1,99) AS INT) AS VARCHAR(18)) AS NEWSTR
FROM
(SELECT COL1, LENGTH(COL1) AS ZY, LENGTH(RTRIM(TRANSLATE(COL1, ' ', '0123456789 '))) AS ZX FROM TABLE1) X
;
To make a test:
SELECT COL1, SUBSTR(COL1, 1, ZX)
|| CAST( CAST(SUBSTR(COL1, ZX+1,99) AS INT) AS VARCHAR(18)) AS NEWSTR
FROM
(SELECT COL1, LENGTH(COL1) AS ZY, LENGTH(RTRIM(TRANSLATE(COL1, ' ', '0123456789 '))) AS ZX FROM
(SELECT 'ABCD1234' AS COL1 FROM sysibm.sysdummy1 UNION ALL SELECT 'JFKD011011' AS COL1 FROM sysibm.sysdummy1
UNION ALL SELECT 'A000000001' AS COL1 FROM sysibm.sysdummy1 UNION ALL SELECT 'Z000000000' AS COL1 FROM sysibm.sysdummy1
) K
) X
Output:
COL1 NEWSTR
---------- ---------
ABCD1234 ABCD1234
JFKD011011 JFKD11011
A000000001 A1
Z000000000 Z0
In MSSQL it should be easier, using PATINDEX() function.

Oracle split by regex and aggregate again

I have a table from where I need to get only some part of record with comma after one part of record.
for example I have
ABCD [1000-1987] BCD[101928-876] adgs[10987-786]
I want to get the record like :
1000-1987,101928-876,10987-786
Can you please help me out to get the record as mentioned.
If you don't use 11g and do not want to use wm_concat:
WITH
my_data AS (
SELECT 'ABCD [1000-1987] BCD[101928-876] adgs[10987-786]' AS val FROM dual
)
SELECT
ltrim(
MAX(
sys_connect_by_path(
rtrim(ltrim(regexp_substr(val, '\[[0-9-]*\]', 1, level, NULL), '['), ']'),
',')
),
',') AS val_part
FROM my_data
CONNECT BY regexp_substr(val, '\[[0-9-]*\]', 1, level, NULL) IS NOT NULL
;
If using wm_concat is ok for you:
WITH
my_data AS (
SELECT 'ABCD [1000-1987] BCD[101928-876] adgs[10987-786]' AS val FROM dual
)
SELECT
wm_concat(rtrim(ltrim(regexp_substr(val, '\[[0-9-]*\]', 1, level, NULL), '['), ']')) AS val_part
FROM my_data
CONNECT BY regexp_substr(val, '\[[0-9-]*\]', 1, level, NULL) IS NOT NULL
;
If you use 11g:
WITH
my_data AS (
SELECT 'ABCD [1000-1987] BCD[101928-876] adgs[10987-786]' AS val FROM dual
)
SELECT
listagg(regexp_substr(val, '[a-b ]*\[([0-9-]*)\] ?', 1, level, 'i', 1), ',') WITHIN GROUP (ORDER BY 1) AS val_part
FROM my_data
CONNECT BY regexp_substr(val, '[a-b ]*\[([0-9-]*)\] ?', 1, level, 'i', 1) IS NOT NULL
;
Read more about string aggregation techniques: Tim Hall about aggregation techniques
Read more about regexp_substr: regexp_substr - Oracle Documentation - 10g
Read more about regexp_substr: regexp_substr - Oracle Documentation - 11g
You don't have to split and then aggregate it. You can use regexp_replace to keep only those characters within square brackets, then replace the square brackets by comma.
WITH my_data
AS (SELECT 'ABCD [1000-1987] BCD[101928-876] adgs[10987-786]' AS val
FROM DUAL)
SELECT RTRIM (
REPLACE (
REGEXP_REPLACE (val, '(\[)(.*?\])|(.)', '\2'),
']', ','),
',')
FROM my_data;