Oracle Query to select separate alphabet+numbers and numbers after special character - sql

I am writing a query in oracle where I have requirement to separate alphabet+numbers and numbers after special character as 2 different columns
Eg.
Colum Value is
ABC 123#78800,XYZ#4666,PQR 444#9900
Output Required
Column 1 : ABC 123,XYZ,PQR 444
Column 2 : 78800,4666, 9900
I tried following query:
select TRANSLATE('ABC 123#78800,XYZ#4666,PQR 444#9900 ','ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#','ABCDEFGHIJKLMNOPQRSTUVWXYZ') from dual.
Output is "ABC ,XYZ,PQR " numbers are missing

What you need is regex
with table_name as
(
select 'ABC 123#78800,XYZ#4666,PQR 444#9900' col_name from dual
)
select
REGEXP_REPLACE(col_name,'#([^#,])*', null) alphabet_num
,REGEXP_REPLACE(col_name,'([^#,])*#', null) num_value
from
table_name;
Editted: Remove some redundant character as suggested by MTO

Oracle Setup:
CREATE TABLE table_name ( col VARCHAR2(100) );
INSERT INTO table_name VALUES ( 'ABC 123#78800,XYZ#4666,PQR 444#9900' );
CREATE TYPE key_value_pair AS OBJECT(
key VARCHAR2(20),
value NUMBER(10)
);
/
CREATE TYPE key_value_pair_table AS TABLE of key_value_pair;
/
Query:
SELECT *
FROM table_name t,
TABLE(
CAST(
MULTISET(
SELECT REGEXP_SUBSTR( t.col, '(^|,)([^,#]+)#(\d+)', 1, LEVEL, NULL, 2 ),
TO_NUMBER(
REGEXP_SUBSTR( t.col, '(^|,)([^,#]+)#(\d+)', 1, LEVEL, NULL, 3 )
)
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( t.col, '(^|,)([^,#]+)#(\d+)' )
)
AS key_value_pair_table
)
);
Output:
COL KEY VALUE
----------------------------------- ------- -----
ABC 123#78800,XYZ#4666,PQR 444#9900 ABC 123 78800
ABC 123#78800,XYZ#4666,PQR 444#9900 XYZ 4666
ABC 123#78800,XYZ#4666,PQR 444#9900 PQR 444 9900

Related

Parse field form xml clob

Ciao,
i've one problem with sql.
I've some table with datatype clob where is stored a clob.
In the same table if we have the xml format we use one function for take the xml_fied
CREATE OR REPLACE FUNCTION gettagvalue (
XMLBody IN CLOB, TagXml IN VARCHAR2) RETURN VARCHAR2 IS
BEGIN
return TO_CHAR (SUBSTR (XMLBody,
INSTR (XMLBody, '<'||TagXml||'>') + length(TagXml) + 2,
INSTR (XMLBody, '</'||TagXml||'>')
- INSTR (XMLBody, '<'||TagXml||'>')
- (length(TagXml) + 2)
)
);
END GetTagValue;
/
example :
Select errorhandler.GetTagValue(xml_content,'ORDERID')
from table_order
On the same table we have also some xml in json.
How i can create a copy of same function for take the field?
On xml is eay because we have the field with same name that start with and finish with but with json
I cannot understand how define the end of field
Do NOT try to parse XML or JSON as strings; use a proper XML or JSON parser.
If you have the table with the sample data:
CREATE TABLE table_name (
xml CLOB,
json CLOB CHECK ( json IS JSON )
);
INSERT INTO table_name ( xml, json ) VALUES (
'<a><b>BBB</b><c>CCC</c><d>DDD</d></a>',
'{"a":"aaa","b":"bbb","c":[1,2,3,4],"d":"ddd"}'
);
Then you can get the c values from both using the XMLQUERY or JSON_QUERY functions:
SELECT XMLQUERY(
'*/c/text()'
PASSING XMLTYPE(xml)
RETURNING CONTENT
) AS c_xml_value,
JSON_QUERY(
json,
'$.c'
RETURNING VARCHAR2(50)
) AS c_json_value
FROM table_name
Which outputs:
| C_XML_VALUE | C_JSON_VALUE |
| :---------- | :----------- |
| CCC | [1,2,3,4] |
If you have XML and JSON values in the same column then look at whether the first character is < or not and use the appropriate parsing function; do not try and create your own function to parse the values using substring matching.
For example:
CREATE TABLE table_name ( value CLOB );
INSERT INTO table_name ( value )
SELECT '<a><b>BBB</b><c>CCC</c><d>DDD</d></a>' FROM DUAL UNION ALL
SELECT '{"a":"aaa","b":"bbb","c":[1,2,3,4],"d":"ddd"}' FROM DUAL;
Then:
SELECT CASE
WHEN value LIKE '<%'
THEN CAST(
XMLQUERY( '*/c/text()' PASSING XMLTYPE(value) RETURNING CONTENT )
AS VARCHAR2(50)
)
ELSE JSON_QUERY( value, '$.c' RETURNING VARCHAR2(50) )
END AS c_value
FROM table_name
Outputs:
| C_VALUE |
| :-------- |
| CCC |
| [1,2,3,4] |
db<>fiddle here
Update
You can also use JSON_TABLE and XMLTABLE to get all the values out:
SELECT COALESCE( j.sourceChannel, x.sourceChannel ) AS sourceChannel,
COALESCE( j.transactionId, x.transactionId ) AS transactionId,
COALESCE( j.sessionId, x.sessionId ) AS transactionId,
COALESCE( j.status, x.status ) AS status,
COALESCE( j.errorcode, x.errorcode ) AS errorcode,
COALESCE( j.errordescription, x.errordescription ) AS errordescription
FROM table_name t
OUTER APPLY JSON_TABLE(
t.value,
'$.header'
COLUMNS (
sourceChannel VARCHAR2( 50) PATH '$.sourceChannel',
transactionId VARCHAR2( 50) PATH '$.transactionId',
sessionId VARCHAR2( 50) PATH '$.sessionId',
status VARCHAR2( 50) PATH '$.status',
errorcode VARCHAR2( 50) PATH '$.errorcode',
errordescription VARCHAR2(200) PATH '$.errordescription'
)
) j
LEFT OUTER JOIN LATERAL(
SELECT *
FROM XMLTABLE(
'/header'
PASSING XMLTYPE( value )
COLUMNS
sourceChannel VARCHAR2( 50) PATH 'sourceChannel',
transactionId VARCHAR2( 50) PATH 'transactionId',
sessionId VARCHAR2( 50) PATH 'sessionId',
status VARCHAR2( 50) PATH 'status',
errorcode VARCHAR2( 50) PATH 'errorcode',
errordescription VARCHAR2(200) PATH 'errordescription'
)
) x
ON ( t.value LIKE '<%' )
Which for the sample data:
CREATE TABLE table_name ( value CLOB );
INSERT INTO table_name ( value )
SELECT '<header>
<sourceChannel>xaaa</sourceChannel>
<transactionId>xbbb</transactionId>
<sessionId>xccc</sessionId>
<status>xddd</status>
<errorcode>xeee</errorcode>
<errordescription>xfff</errordescription>
</header>' FROM DUAL UNION ALL
SELECT '{"header":{"sourceChannel":"jaaa","transactionId":"jbbb","sessionId":"jccc","status":"jddd","errorcode":"jeee","errordescription":"jfff"}}' FROM DUAL;
Outputs:
SOURCECHANNEL
TRANSACTIONID
TRANSACTIONID
STATUS
ERRORCODE
ERRORDESCRIPTION
xaaa
xbbb
xccc
xddd
xeee
xfff
jaaa
jbbb
jccc
jddd
jeee
jfff

Multiple Sequence in SQL Oracle / Varchar2 ID with letters and digits

I am trying to 'generate' a Varchar ID that contains letters and digits too with a sequence, but I think there will be more sequences than one.
What I am looking forward to is something like this 'DJ_Digit_Digit_Letter_Letter_Letter'. An example would be DJ00WVX/DJ01HYZ/DJ99ZZZ. This is also my primary key so it would be good if I could not encounter any primary key errors.
I thought about working in ascii so I will generate numbers between 65 and 90 (A-Z) and Insert them into the column. Same for the numbers in ascii 45 to 57 or something like that. I don't know how I could use more sequences on the same column like 'letter_seq.nextvalue,digit_seq.nextvalue', i know that this example is wrong, but it was easier for me to explain it. Maybe all my thinking is wrong
In conclusion I need to get something that starts with DJ followed by 2 digits and 3 letters, all wrapped up in a primary key. It's ok to get like 20-30 unique entries, I think.
Thank you for your help!
You can create an INVISIBLE IDENTITY column and then create your letter-digit-letter sequence as a function generated from the invisible column:
CREATE TABLE table_name (
id_seq_value INT
INVISIBLE
GENERATED ALWAYS AS IDENTITY( START WITH 0 INCREMENT BY 1 MINVALUE 0 MAXVALUE 175799 )
NOT NULL
CONSTRAINT table_name__id_seq_value__u UNIQUE,
id VARCHAR2(7)
GENERATED ALWAYS AS (
CAST(
'DJ'
|| TO_CHAR( FLOOR( id_seq_value / POWER( 26, 3 ) ), 'FM00' )
|| CHR( 65 + MOD( FLOOR( id_seq_value / POWER(26, 2) ), 26 ) )
|| CHR( 65 + MOD( FLOOR( id_seq_value / POWER(26, 1) ), 26 ) )
|| CHR( 65 + MOD( id_seq_value, 26 ) )
AS VARCHAR2(7)
)
)
CONSTRAINT table_name__id__pk PRIMARY KEY,
name VARCHAR2(50)
);
Then you can do:
INSERT INTO table_name ( name ) VALUES ( 'Item1' );
INSERT INTO table_name ( name ) VALUES ( 'Item2' );
Then:
SELECT * FROM table_name;
Outputs:
ID | NAME
:------ | :----
DJ00AAA | Item1
DJ00AAB | Item2
(If you want to increment the digits and the letters together then change the IDENTITY to INCREMENT BY 17577 (263+1).)
db<>fiddle here
To me, it looks like this:
SQL> create table test (id varchar2(10));
Table created.
SQL> create sequence seq1;
Sequence created.
SQL> insert into test (id)
2 with
3 a as (select chr(64 + level) la from dual
4 connect by level <= 26),
5 b as (select chr(64 + level) lb from dual
6 connect by level <= 26),
7 c as (select chr(64 + level) lc from dual
8 connect by level <= 26)
9 select 'DJ' || lpad(seq1.nextval, 2, '0')
10 || la || lb || lc id
11 from a cross join b cross join c;
17576 rows created.
SQL>
Several sample values:
SQL> with temp as
2 (select id,
3 row_number() Over (order by id) rna,
4 row_Number() over (order by id desc) rnd
5 from test
6 )
7 select id
8 from temp
9 where rna <= 5
10 or rnd <= 5
11 order by id;
ID
----------
DJ01AAA
DJ02AAB
DJ03AAC
DJ04AAD
DJ05AAE
DJ99OUK
DJ99OUL
DJ99OUM
DJ99OUN
DJ99OUO
10 rows selected.
SQL>
However, if
It's ok to get like 20-30 unique entries, I think.
means that you need to generate at most 30 values, well, you'd easily create them manually; why would you develop any software solution for that? If you started typing, you'd be over by now.

Pass subquery value to IN statement

In one table named prefs, I have a column named "Value" of type clob that holds this value: 'T', 'L'
I need to query table attendance_code to retrieve the records where column att_code values are either T or L. The att_code column is of type varchar2.
As a manual model query, this works fine:
SELECT id FROM attendance_code ac WHERE ac.att_code IN ('T', 'L')
Result:
ID
1 4903
2 4901
Attempt 1
SELECT id FROM attendance_code ac WHERE ac.att_code IN (SELECT value from prefs WHERE name = 'AEADS|SAR|tdyCodes')
ORA-00932: inconsistent datatypes: expected - got CLOB
00932. 00000 - "inconsistent datatypes: expected %s got %s"
Attempt 2
SELECT id FROM attendance_code ac WHERE ac.att_code IN (SELECT dbms_lob.substr(value, 4000, 1) from prefs WHERE name = 'AEADS|SAR|tdyCodes')
This yields no error, but returns no rows.
Attempt 3
(Based on https://blogs.oracle.com/aramamoo/how-to-split-comma-separated-string-and-pass-to-in-clause-of-select-statement)
select id from attendance_code ac where ac.att_code IN (
select regexp_substr(value,'[^,]+', 1, level) from prefs WHERE name = 'AEADS|SAR|tdyCodes'
connect by regexp_substr(value, '[^,]+', 1, level) is not null );
ORA-00932: inconsistent datatypes: expected - got CLOB
00932. 00000 - "inconsistent datatypes: expected %s got %s"
Attempt 4
select id from attendance_code ac where ac.att_code IN (
select regexp_substr(dbms_lob.substr(value, 4000, 1),'[^,]+', 1, level) from prefs WHERE name = 'AEADS|SAR|tdyCodes'
connect by regexp_substr(dbms_lob.substr(value, 4000, 1), '[^,]+', 1, level) is not null );
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at line 1
06502. 00000 - "PL/SQL: numeric or value error%s"
*Cause: An arithmetic, numeric, string, conversion, or constraint error
occurred. For example, this error occurs if an attempt is made to
assign the value NULL to a variable declared NOT NULL, or if an
attempt is made to assign an integer larger than 99 to a variable
declared NUMBER(2).
*Action: Change the data, how it is manipulated, or how it is declared so
that values do not violate constraints.
I have also tried changing the value in the prefs table to just T,L (removing the single quotes) and running all of the above against that, to no avail.
What is the proper way to do this, please?
You can use hierarchical query with removed single quotes as follows:
SELECT ID
FROM ATTENDANCE_CODE AC
WHERE AC.ATT_CODE IN (
SELECT
REPLACE(TRIM(REGEXP_SUBSTR(
(SELECT value from prefs WHERE name = 'AEADS|SAR|tdyCodes'),
'[^,]+', 1, LEVEL)), '''', '') FROM DUAL
CONNECT BY REGEXP_SUBSTR(
(SELECT value from prefs WHERE name = 'AEADS|SAR|tdyCodes'),
'[^,]+', 1, LEVEL) IS NOT NULL
);
or simplify the above query with the CTE as following:
WITH DATAA ( VALS ) AS (
SELECT VALUE
FROM PREFS
WHERE NAME = 'AEADS|SAR|tdyCodes'
)
SELECT ID
FROM ATTENDANCE_CODE AC,
DATAA D
WHERE AC.ATT_CODE IN (
SELECT
REPLACE(TRIM(REGEXP_SUBSTR(D.VALS, '[^,]+', 1, LEVEL)), '''', '')
FROM DUAL
CONNECT BY
REGEXP_SUBSTR(D.VALS, '[^,]+', 1, LEVEL) IS NOT NULL
);
Example with Dual as following:
SQL> SELECT DUMMY FROM DUAL AC
2 WHERE
3 'T' IN (
4 SELECT
5 REPLACE(TRIM(REGEXP_SUBSTR(
6 (SELECT q'#'T', 'L'#' FROM DUAL), '[^,]+', 1, LEVEL)), '''', '')
7 FROM DUAL
8 CONNECT BY
9 REGEXP_SUBSTR((
10 SELECT q'#'T', 'L'#' FROM DUAL), '[^,]+', 1, LEVEL) IS NOT NULL
11 );
DUMMY
-------
X
SQL>
Cheers!!
Don't store delimited values in a string; put them in another table (where you can have one row per item) with a foreign key referencing to the row in the parent table (for example: db<>fiddle) or use a collection and a nested table.
An example of this second option is:
CREATE TYPE char_list IS TABLE OF CHAR(1);
Then
CREATE TABLE prefs (
id NUMBER(10,0) PRIMARY KEY,
name VARCHAR2(50) NOT NULL UNIQUE,
SchoolId NUMBER(10,0),
UserId NUMBER(10,0),
Value char_list,
YearID NUMBER(4,0)
) NESTED TABLE value STORE AS prefs__value;
So for your sample data:
INSERT INTO prefs ( id, name, schoolId, UserId, value, YearID )
VALUES ( 262806, 'AEADS|SAR|tdyCodes', 109, 0, char_list( 'T', 'L' ), 29 );
CREATE TABLE attendance_code ( id, att_code ) AS
SELECT 4903, 'L' FROM DUAL UNION ALL
SELECT 4902, 'X' FROM DUAL UNION ALL
SELECT 4901, 'T' FROM DUAL;
Your query would be:
SELECT *
FROM attendance_code
WHERE att_code MEMBER OF ( SELECT value FROM prefs WHERE name = 'AEADS|SAR|tdyCodes' );
Which outputs:
| ID |
| ---: |
| 4903 |
| 4901 |
db<>fiddle here

Making multiple rows out of single row, SAP HANA Calculation View

I have this below table
TYPE ID VERSION
A WXYZ a#bbb#aaa
A ABCD cc#qq
I want output like this
Type ID VERSION
A WXYZ a
A WXYZ bb
A WXYZ aaa
A ABCD cc
A ABCD qq
It is possible to do using Cursors, I have done it, is it possible to do it without Cursor?
create table v_test (type varchar(5), id varchar(10) , versionn varchar(100));
insert into v_test values ('A', 'WXYZ' , 'a#bbb#aaa');
insert into v_test values ('A', 'ABCD' , 'cc#qq');
select * from v_test
Select * from
( select "ID" "KEY", "ELEMENT_NUMBER" "ORD" ,
SUBSTR_REGEXPR('(?<=^|#)([^#]*)(?=#|$)' IN "V_TEST"."VERSIONN" OCCURRENCE "SERIES"."ELEMENT_NUMBER" GROUP 1) "VERSIONN"
from v_test,
SERIES_GENERATE_INTEGER(1, 1, 10 ) "SERIES" -- replace 10 with your max. number of values in CSV-Field
)
where "VERSIONN" is not null
order by "KEY", "ORD"

value substitution/replacement in a string

I have a string x-y+z. The values for x, y and z will be stored in a table. Say
x 10
y 15
z 20
This string needs to be changed like 10-15+20.
Anyway I can achieve this using plsql or sql?
using simple Pivot we can do
DECLARE #Table1 TABLE
( name varchar(1), amount int )
;
INSERT INTO #Table1
( name , amount )
VALUES
('x', 10),
('y', 15),
('Z', 25);
Script
Select CAST([X] AS VARCHAR) +'-'+CAST([Y] AS VARCHAR)+'+'+CAST([Z] AS VARCHAR) from (
select * from #Table1)T
PIVOT (MAX(amount) FOR name in ([X],[y],[z]))p
An approach could be the following, assuming a table like this:
create table stringToNumbers(str varchar2(16), num number);
insert into stringToNumbers values ('x', 10);
insert into stringToNumbers values ('y', 20);
insert into stringToNumbers values ('zz', 30);
insert into stringToNumbers values ('w', 40);
First tokenize your input string with something like this:
SQL> with test as (select 'x+y-zz+w' as string from dual)
2 SELECT 'operand' as typ, level as lev, regexp_substr(string, '[+-]+', 1, level) as token
3 FROM test
4 CONNECT BY regexp_instr(string, '[a-z]+', 1, level+1) > 0
5 UNION ALL
6 SELECT 'value', level, regexp_substr(string, '[^+-]+', 1, level) as token
7 FROM test
8 CONNECT BY regexp_instr(string, '[+-]', 1, level - 1) > 0
9 order by lev asc, typ desc;
TYP LEV TOKEN
------- ---------- --------------------------------
value 1 x
operand 1 +
value 2 y
operand 2 -
value 3 zz
operand 3 +
value 4 w
In the example I used lowercase literals and only +/- signs; you can easily edit it to handle something more complex; also, I assume the input string is well-formed.
Then you can join your decoding table to the tokenized string, building the concatenation:
SQL> select listagg(nvl(to_char(num), token)) within group (order by lev asc, typ desc)
2 from (
3 with test as (select 'x+y-zz+w' as string from dual)
4 SELECT 'operand' as typ, level as lev, regexp_substr(string, '[+-]+', 1, level) as token
5 FROM test
6 CONNECT BY regexp_instr(string, '[a-z]+', 1, level+1) > 0
7 UNION ALL
8 SELECT 'value', level, regexp_substr(string, '[^+-]+', 1, level) as token
9 FROM test
10 CONNECT BY regexp_instr(string, '[+-]', 1, level - 1) > 0
11 order by lev asc, typ desc
12 ) tokens
13 LEFT OUTER JOIN stringToNumbers on (str = token);
LISTAGG(NVL(TO_CHAR(NUM),TOKEN))WITHINGROUP(ORDERBYLEVASC,TYPDESC)
--------------------------------------------------------------------------------
10+20-30+40
This assumes that every literal in you input string has a corrensponding value in table. You can even handle the case of strings with no corrensponding number, for example assigning 0:
SQL> select listagg(
2 case
3 when typ = 'operand' then token
4 else to_char(nvl(num, 0))
5 end
6 ) within group (order by lev asc, typ desc)
7 from (
8 with test as (select 'x+y-zz+w-UNKNOWN' as string from dual)
9 SELECT
.. ...
16 ) tokens
17 LEFT OUTER JOIN stringToNumbers on (str = token);
LISTAGG(CASEWHENTYP='OPERAND'THENTOKENELSETO_CHAR(NVL(NUM,0))END)WITHINGROUP(ORD
--------------------------------------------------------------------------------
10+20-30+40-0
Create a function like this:
create table ttt1
( name varchar(1), amount int )
;
INSERT INTO ttt1 VALUES ('x', 10);
INSERT INTO ttt1 VALUES ('y', 15);
INSERT INTO ttt1 VALUES ('z', 25);
CREATE OR REPLACE FUNCTION replace_vars (in_formula VARCHAR2)
RETURN VARCHAR2
IS
f VARCHAR2 (2000) := UPPER (in_formula);
BEGIN
FOR c1 IN ( SELECT UPPER (name) name, amount
FROM ttt1
ORDER BY name DESC)
LOOP
f := REPLACE (f, c1.name, c1.amount);
END LOOP;
return f;
END;
select replace_vars('x-y+z') from dual
Here's another way to approach the problem that attempts to do it all in SQL. While not necessarily the most flexible or fastest, maybe you can get some ideas from another way to approach the problem. It also shows a way to execute the final formula to get the answer. See the comments below.
Assumes all variables are present in the variable table.
-- First build the table that holds the values. You won't need to do
-- this if you already have them in a table.
with val_tbl(x, y, z) as (
select '10', '15', '20' from dual
),
-- Table to hold the formula.
formula_tbl(formula) as (
select 'x-y+z' from dual
),
-- This table is built from a query that reads the formula a character at a time.
-- When a variable is found using the case statement, it is queried in the value
-- table and it's value is returned. Otherwise the operator is returned. This
-- results in a row for each character in the formula.
new_formula_tbl(id, new_formula) as (
select level, case regexp_substr(formula, '(.|$)', 1, level, NULL, 1)
when 'x' then
(select x from val_tbl)
when 'y' then
(select y from val_tbl)
when 'z' then
(select z from val_tbl)
else regexp_substr(formula, '(.|$)', 1, level, NULL, 1)
end
from formula_tbl
connect by level <= regexp_count(formula, '.')
)
-- select id, new_formula from new_formula_tbl;
-- This puts the rows back into a single string. Order by id (level) to keep operands
-- and operators in the right order.
select listagg(new_formula) within group (order by id) formula
from new_formula_tbl;
FORMULA
----------
10-15+20
Additionally you can get the result of the formula by passing the listagg() call to the following xmlquery() function:
select xmlquery(replace( listagg(new_formula) within group (order by id), '/', ' div ')
returning content).getNumberVal() as result
from new_formula_tbl;
RESULT
----------
15