Query works in SQL Server, trying to replicate in Oracle - sql

I have a query that works as intended in SQL Server, but when I try to replicate it in Oracle, it doesn't work.
In SQL Server, it returns Column1 with 5 characters.
In Oracle, it is returning the original values.
Oracle syntax isn't my strong point, how can I get this to work, any suggestions would be appreciated
SELECT
CASE
WHEN Column1 LIKE '__-____'
THEN CONCAT('000', (SUBSTR(LTRIM(RTRIM(Column1)), 1, 2)))
WHEN Column1 LIKE '___-____'
THEN CONCAT('00', (SUBSTR(LTRIM(RTRIM(Column1)), 1, 3)))
WHEN Column1 LIKE '_______-_____'
THEN (SUBSTR(LTRIM(RTRIM(Column1)), 3, 5))
ELSE Column1
END AS NewColumn
FROM
schema.table1
Here it is again with a CTE so you can see it works as intended in SQL Server:
WITH cte_test AS
(
SELECT '12-3456' AS Column1
UNION ALL
SELECT '78-9101'
UNION ALL
SELECT '1234567-89101'
UNION ALL
SELECT '123-4321'
)
SELECT
CASE
WHEN Column1 LIKE '__-____'
THEN CONCAT('000', (SUBSTRING(LTRIM(RTRIM(Column1)), 1, 2)))
WHEN Column1 LIKE '___-____'
THEN CONCAT('00', (SUBSTRING(LTRIM(RTRIM(Column1)), 1, 3)))
WHEN Column1 LIKE '_______-_____'
THEN (SUBSTRING(LTRIM(RTRIM(Column1)), 3, 5))
ELSE Column1
END AS NewColumn
FROM
cte_test

The issue is that you've declared the column in the Oracle table as char(20). char(20) is a fixed width data type so it will always be space padded out to 20 characters. Given your sample data, that wastes space and means that neither of your like clauses are going to match the sample data because of the trailing spaces. You really want to declare the column in the table as varchar2(20) so that it is not blank padded.
If you just take the CTE from your SQL Server example and use it with your Oracle code, the query returns what you want because Oracle treats the column in the CTE as a varchar2
WITH cte_test AS
(
SELECT '12-3456' AS Column1 from dual
UNION ALL
SELECT '78-9101' from dual
UNION ALL
SELECT '1234567-89101' from dual
UNION ALL
SELECT '123-4321' from dual
)
SELECT
CASE
WHEN Column1 LIKE '__-____'
THEN CONCAT('000', (SUBSTR(LTRIM(RTRIM(Column1)), 1, 2)))
WHEN Column1 LIKE '___-____'
THEN CONCAT('00', (SUBSTR(LTRIM(RTRIM(Column1)), 1, 3)))
WHEN Column1 LIKE '_______-_____'
THEN (SUBSTR(LTRIM(RTRIM(Column1)), 3, 5))
ELSE Column1
END AS NewColumn
FROM cte_test
If you create the table as a char(20) and insert the data, it gets space padded so the like statements don't do what you want
create table char_test( column1 char(20) );
insert into char_test
WITH cte_test AS
(
SELECT '12-3456' AS Column1 from dual
UNION ALL
SELECT '78-9101' from dual
UNION ALL
SELECT '1234567-89101' from dual
UNION ALL
SELECT '123-4321' from dual
)
select * from cte_test;
SELECT
CASE
WHEN Column1 LIKE '__-____'
THEN CONCAT('000', (SUBSTR(LTRIM(RTRIM(Column1)), 1, 2)))
WHEN Column1 LIKE '___-____'
THEN CONCAT('00', (SUBSTR(LTRIM(RTRIM(Column1)), 1, 3)))
WHEN Column1 LIKE '_______-_____'
THEN (SUBSTR(LTRIM(RTRIM(Column1)), 3, 5))
ELSE Column1
END AS NewColumn
FROM char_test
You can work around that by trimming Column1 before doing the like
SELECT
CASE
WHEN trim(Column1) LIKE '__-____'
THEN CONCAT('000', (SUBSTR(Column1, 1, 2)))
WHEN trim(Column1) LIKE '___-____'
THEN CONCAT('00', (SUBSTR(Column1, 1, 3)))
WHEN trim(Column1) LIKE '_______-_____'
THEN (SUBSTR(Column1, 3, 5))
ELSE Column1
END AS NewColumn
FROM char_test
But a better solution would be to declare the column as varchar2 in the first place so that you don't have the space padding to work around
create table varchar_test( column1 varchar2(20) );
insert into varchar_test
WITH cte_test AS
(
SELECT '12-3456' AS Column1 from dual
UNION ALL
SELECT '78-9101' from dual
UNION ALL
SELECT '1234567-89101' from dual
UNION ALL
SELECT '123-4321' from dual
)
select * from cte_test;
SELECT
CASE
WHEN Column1 LIKE '__-____'
THEN CONCAT('000', SUBSTR(Column1, 1, 2))
WHEN Column1 LIKE '___-____'
THEN CONCAT('00', SUBSTR(Column1, 1, 3))
WHEN Column1 LIKE '_______-_____'
THEN (SUBSTR(Column1, 3, 5))
ELSE Column1
END AS NewColumn
FROM varchar_test
Here is a fiddle that shows the various options.

Oracle alternative which (in temp CTE) extracts the first part of the string (up to the minus sign), and then - depending on its length - left pads it with zeros up to 5 characters in length, or takes the last 5 characters):
SQL> WITH cte_test AS
2 (
3 SELECT '12-3456' AS Column1 from dual
4 UNION ALL
5 SELECT '78-9101' from dual
6 UNION ALL
7 SELECT '1234567-89101' from dual
8 UNION ALL
9 SELECT '123-4321' from dual
10 ),
11 temp as
12 (select column1,
13 substr(column1, 1, instr(column1, '-') - 1) val
14 from cte_Test
15 )
16 select column1,
17 lpad(case when length(val) < 5 then val
18 else substr(val, -5)
19 end, 5, '0'
20 ) as result
21 from temp;
COLUMN1 RESULT
------------- --------------------
12-3456 00012
78-9101 00078
1234567-89101 34567
123-4321 00123
SQL>

You want something like:
SELECT CASE
WHEN Column1 LIKE '__-____ '
THEN '000' || SUBSTR(Column1, 1, 2)
WHEN Column1 LIKE '___-____ '
THEN '00' || SUBSTR(Column1, 1, 3)
WHEN Column1 LIKE '_______-_____ '
THEN SUBSTR(Column1, 3, 5)
ELSE Column1
END AS NewColumn
FROM schema.table1
or:
SELECT CASE
WHEN REGEXP_LIKE( Column1, '^\d{2}-\d{4}\s*$' )
THEN '000' || SUBSTR(Column1, 1, 2)
WHEN REGEXP_LIKE( Column1, '^\d{3}-\d{4}\s*$' )
THEN '00' || SUBSTR(Column1, 1, 3)
WHEN REGEXP_LIKE(Column1, '^\d{7}-\d{5}\s*$' )
THEN SUBSTR(Column1, 3, 5)
ELSE Column1
END AS NewColumn
FROM schema.table1
Or, more simply:
SELECT SUBSTR(
'000' || SUBSTR(column1, 1, INSTR(column1, '-') - 1),
-5
) AS newcolumn
FROM schema.table1
Which, for the sample data:
CREATE TABLE schema.table1( column1 CHAR(20) );
INSERT INTO schema.table1(column1)
SELECT '12-3456' FROM DUAL UNION ALL
SELECT '78-9101' FROM DUAL UNION ALL
SELECT '1234567-89101' FROM DUAL UNION ALL
SELECT '123-4321' FROM DUAL;
All output:
NEWCOLUMN
00012
00078
34567
00123
db<>fiddle here

Related

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;

Add numbers within a string in an SQL statement

I have below query of which i would like to modify.
I want to sum all the numbers that occur within a string with the condition that it is joined with the text GB or MB .
If it is in GB it first has to be converted to MB. (This i have done simply by multiplying by 1024)
SELECT /*+ PARALLEL */
'SOME TEXT 20GB+2GB+SOMETEXT' SOMETEXT,
CASE
WHEN REGEXP_SUBSTR('SOME TEXT 20GB+2GB+SOMETEXT','GB',1,1) = 'GB'
THEN 1024*to_number(regexp_replace(REGEXP_SUBSTR('SOME TEXT 20GB+2GB+SOMETEXT','(\d+)GB',1,1), '[^0-9]', ''))
ELSE to_number(regexp_replace(REGEXP_SUBSTR('SOME TEXT 20GB+2GB+SOMETEXT','(\d+)MB',1,1), '[^0-9]', ''))
END TOTAL_MBs
FROM DUAL;
TEST STRINGS
TEXT TEXT_35MB+ MORETEXT
OTHERTEXT 480MB + 3MB AND_TEXT
SOMETEXT 7MB + 7NUMBER
TEXT 1GB AND SOME_TEXT
SOME TEXT 20GB+2GB+SOMETEXT
Here is where i am stuck: To add the numbers that occur more than once in one text
For example:-
For this text OTHERTEXT 480MB + 3MB AND_TEXT I want my result to have 483 as TOTAL_MBS and not 480
Think you are searching for something like:
with da as (
select 1 id, 'TEXT TEXT_35MB+ MORETEXT' tcase from dual
union all select 2 id,'OTHERTEXT 480MB + 3MB AND_TEXT' tcase from dual
union all select 3 id,'SOMETEXT 7MB + 7NUMBER' tcase from dual
union all select 4 id,'TEXT 1GB AND SOME_TEXT' tcase from dual
union all select 5 id,'SOME TEXT 20GB+2GB+SOMETEXT' tcase from dual
union all select 6 id,'SOME TEXT 20MB+2GB+SOMETEXT' tcase from dual
),
split as(
select id
, tcase
, REGEXP_SUBSTR(tcase,'(\d+)(M|G)B',1,level) ot
from da
connect by REGEXP_SUBSTR(tcase,'(\d+)(M|G)B',1,level)is not null
and prior id = id
and PRIOR DBMS_RANDOM.VALUE IS NOT NULL)
select id
, tcase
, sum( case when ot like '%GB%' then 1024 else 1 end * regexp_substr(ot,'\d+')) v
from split
group by id
,tcase
order by id;
Result:
1 TEXT TEXT_35MB+ MORETEXT 35
2 OTHERTEXT 480MB + 3MB AND_TEXT 483
3 SOMETEXT 7MB + 7NUMBER 7
4 TEXT 1GB AND SOME_TEXT 1024
5 SOME TEXT 20GB+2GB+SOMETEXT 22528
6 SOME TEXT 20MB+2GB+SOMETEXT 2068
You can use a recursive sub-query factoring clause:
SELECT sometext,
COALESCE(
REGEXP_SUBSTR( sometext, '(\d+)([MG])B', 1, 1, NULL, 1 )
* CASE REGEXP_SUBSTR( sometext, '(\d+)([MG])B', 1, 1, NULL, 2 )
WHEN 'M' THEN 1
WHEN 'G' THEN 1024
END,
0
),
1,
REGEXP_COUNT( sometext, '(\d+)([MG])B' )
FROM test_data
UNION ALL
SELECT sometext,
total_mb
+ REGEXP_SUBSTR( sometext, '(\d+)([MG])B', 1, i + 1, NULL, 1 )
* CASE REGEXP_SUBSTR( sometext, '(\d+)([MG])B', 1, i + 1, NULL, 2 )
WHEN 'M' THEN 1
WHEN 'G' THEN 1024
END,
i + 1,
num_terms
FROM terms
WHERE i < num_terms
)
SELECT sometext,
total_mb
FROM terms
WHERE i >= num_terms;
which for the test data:
CREATE TABLE test_data ( sometext ) AS
SELECT 'SOME TEXT 20GB+2GB+SOMETEXT' FROM DUAL UNION ALL
SELECT '1MB+1GB+10MB+10GB' FROM DUAL;
outputs:
SOMETEXT | TOTAL_MB
:-------------------------- | -------:
SOME TEXT 20GB+2GB+SOMETEXT | 22528
1MB+1GB+10MB+10GB | 11275
db<>fiddle here
below I used a view/memory table to assign regex function to the specific string and it worked for me
with tbl1 as (
select 1 pd, ' 20GB+2GB sometext +7500 + 45sometext' string from dual
),
tbl2 as(
select pd
, string
, REGEXP_SUBSTR(string,'(\d+)(M|G)B',1,level) string2
from tbl1
connect by REGEXP_SUBSTR(string,'(\d+)(M|G)B',1,level)is not NULL
and prior pd = pd
and PRIOR DBMS_RANDOM.VALUE IS NOT NULL)
select pd
, string
, sum( case when string2 like '%GB%' then 1024 end * regexp_substr(string2,'\d+')) string3
from tbl2
group by pd
,string
order by pd;

distinct and sum if like

I have a table as the following
name
-----------
1#apple#1
2#apple#2
3#apple#4
4#box#4
5#box#5
and I want to get the result as:
name
--------------
apple 3
box 2
Thanks in advance for your help
This is what you need.
select
SUBSTRING(
name,
CHARINDEX('#', name) + 1,
LEN(name) - (
CHARINDEX('#', REVERSE(name)) + CHARINDEX('#', name)
)
),
count(1)
from
tbl
group by
SUBSTRING(
name,
CHARINDEX('#', name) + 1,
LEN(name) - (
CHARINDEX('#', REVERSE(name)) + CHARINDEX('#', name)
)
)
If your data does not contain any full stops (or periods depending on your vernacular), and the length of your string is less than 128 characters, then you can use PARSENAME to effectively split your string into parts, and extract the 2nd part:
DECLARE #T TABLE (Val VARCHAR(20));
INSERT #T (Val)
VALUES ('1#apple#1'), ('2#apple#2'), ('3#apple#4'),
('4#box#4'), ('5#box#5');
SELECT Val = PARSENAME(REPLACE(t.Val, '#', '.'), 2),
[Count] = COUNT(*)
FROM #T AS t
GROUP BY PARSENAME(REPLACE(t.Val, '#', '.'), 2);
Otherwise you will need to use CHARINDEX to find the first and last occurrence of # within your string (REVERSE is also needed to get the last position), then use SUBSTRING to extract the text between these positions:
DECLARE #T TABLE (Val VARCHAR(20));
INSERT #T (Val)
VALUES ('1#apple#1'), ('2#apple#2'), ('3#apple#4'),
('4#box#4'), ('5#box#5');
SELECT Val = SUBSTRING(t.Val, x.FirstPosition + 1, x.LastPosition - x.FirstPosition),
[Count] = COUNT(*)
FROM #T AS t
CROSS APPLY
( SELECT CHARINDEX('#', t.Val) ,
LEN(t.Val) - CHARINDEX('#', REVERSE(t.Val))
) AS x (FirstPosition, LastPosition)
GROUP BY SUBSTRING(t.Val, x.FirstPosition + 1, x.LastPosition - x.FirstPosition);
use case when
select case when name like '%apple%' then 'apple'
when name like '%box%' then 'box' end item_name,
count(*)
group by cas when name like '%apple%' then 'apple'
when name like '%box%' then 'box' end
No DBMS specified, so here is a postgres variant. The query does use regexps to simplify things a bit.
with t0 as (
select '1#apple#1' as value
union all select '2#apple#2'
union all select '3#apple#4'
union all select '4#box#4'
union all select '5#box#5'
),
trimmed as (
select regexp_replace(value,'[0-9]*#(.+?)#[0-9]*','\1') as name
from t0
)
select name, count(*)
from trimmed
group by name
order by name
DB Fiddle
Update
For Oracle DMBS, the query stays basically the same:
with t0 as (
select '1#apple#1' as value from dual
union all select '2#apple#2' from dual
union all select '3#apple#4' from dual
union all select '4#box#4' from dual
union all select '5#box#5' from dual
),
trimmed as (
select regexp_replace(value,'[0-9]*#(.+?)#[0-9]*','\1') as name
from t0
)
select name, count(*)
from trimmed
group by name
order by name
NAME | COUNT(*)
:---- | -------:
apple | 3
box | 2
db<>fiddle here
Update
MySQL 8.0
with t0 as (
select '1#apple#1' as value
union all select '2#apple#2'
union all select '3#apple#4'
union all select '4#box#4'
union all select '5#box#5'
),
trimmed as (
select regexp_replace(value,'[0-9]*#(.+?)#[0-9]*','$1') as name
from t0
)
select name, count(*)
from trimmed
group by name
order by name
name | count(*)
:---- | -------:
apple | 3
box | 2
db<>fiddle here
You can use case and group by to do the same.
select new_col , count(new_col)
from
(
select case when col_name like '%apple%' then 'apple'
when col_name like '%box%' then 'box'
else 'others' end new_col
from table_name
)
group by new_col
;

Need to replace alpha numeric characters by space and create new column

I need to convert the following
Column 1 Column 2
ABC, Company ABC Company
TA. Comp TA Comp
How can I get Column2 in sql where I am removing all ',' '.' to space.
How about:
with testdata as (
select 'ABC, Company Inc.' as col1 from dual
union all
select 'TA. Comp' as col1 from dual
)
select trim(regexp_replace(regexp_replace(col1, '[[:punct:]]',' '), ' {2,}', ' ')) as col2
from testdata;
Output:
ABC Company Inc
TA Comp
Assuming punctuation is what you're trying to blank out.
You can try to use:
SELECT REGEXP_REPLACE(column1, '[^a-zA-Z0-9 ]+', '')
FROM DUAL
with t (val) as
(
select 'ABC,. Cmpany' from dual union all
select 'A, VC' from dual union all
select 'A,, BC...com' from dual
)
select
val,
replace(replace(val, ',', ''), '.', '') x , -- one way
regexp_replace(val, '[,.]', '') y -- another way
from t
;
VAL X Y
--------------- ---------- ----------
ABC,. Cmpany ABC Cmpany ABC Cmpany
A, VC A VC A VC
A,, BC...com A BCcom A BCcom

how to parse a string to return only numbers meeting certain criteria in Oracle

I am trying to locate numbers within views where the text of the view is in a long format. I have already been able to convert the long to a char.
I have been messing around with replace and instr functions but haven't been able to get it out neatly.
Ideally I would like to populate a table with the following information.
the user (grantee) of the view, the ID values that are in the file, 1 row for each ID.
A sample view would be something like this.
Select column1, column2, column3 from table_with_data where ID in (1,5,322,54,12)
or
Select column1, column2, column3 from table_with_data where ID = 2
How could I create 5 rows in a table just pulling the numeric values here in the first case and just 1 for the second?
This might get you going:
CREATE TABLE SampleData (userName, viewString) AS SELECT * FROM (
SELECT 'auser', 'Select column1, column2, column3 from table_with_data where ID in (1,5,322,54,12)' FROM DUAL UNION ALL
SELECT 'buser', 'Select column1, column2, column3 from table_with_data where ID in (15,3,22,5,412)' FROM DUAL UNION ALL
SELECT 'cuser', 'Select column1, column2, column3 from table_with_data where ID = 2 ' FROM DUAL UNION ALL
SELECT 'duser', 'Select column1, column2, column3 from table_with_data where ID = 45' FROM DUAL
);
WITH
MultipleValues (userName, ids) AS (
SELECT /* MATERIALIZE */
userName
, ',' || SUBSTR(viewString, INSTR(viewString, '(') + 1, LENGTH(viewString) - INSTR(viewString, '(') - 1) || ', '
FROM SampleData
WHERE INSTR(viewString, '(') > 1
)
SELECT
userName
, TO_NUMBER(SUBSTR(ids, INSTR(ids, ',', 1, lvl) + 1, INSTR(ids, ',', 1, lvl + 1) - INSTR(ids, ',', 1, lvl) - 1)) id
FROM
(SELECT userName, ids FROM MultipleValues),
(SELECT LEVEL AS lvl FROM DUAL CONNECT BY LEVEL <= 100)
WHERE lvl <= LENGTH(ids) - LENGTH(REPLACE(ids, ',')) - 1
UNION ALL
SELECT /* MATERIALIZE */
userName
, TO_NUMBER(REGEXP_SUBSTR(TRIM(viewString), '(\d+)$'))
FROM SampleData
WHERE INSTR(viewString, '=') > 1
ORDER BY userName, id;
See it running: SQL Fiddle
Please comment, or extend your question if this needs further detail / adjustment.
You can try inserting into view, which will act as check option for insert statement
Lets say table has four column like below
CREATE TABLE table_with_data
(ID NUMBER,column1 VARCHAR2(100), column2 VARCHAR2(100), column3 VARCHAR2(100));
and view as below
CREATE OR REPLACE VIEW SAMP_VIEW
AS
Select * from table_with_data where ID = 2
After creation we can try inserting into view as below
INSERT INTO SAMP_VIEW values(2,'hello','hello','hello');
if you try insert other than id = 2 then it will throw
ORA-01402: view WITH CHECK OPTION where-clause violation
I hope this will help you