Oracle SQL: Merging multiple columns into 1 with conditions - sql

I am new to SQL and don't really have a lot of experience. I need help on this where I have Table A and I want to write a SQL query to generate the result. Any help would be greatly appreciated! Thanks!
Table A
Name
Capacity A
Capacity B
Capacity C
Plant 1
10
20
Plant 2
10
Result Table
Name
Type
Capacity
Plant 1
A,C
10,20
Plant 2
B
10
I know listagg function might be able to combine few columns into one, but is there anyway for me to generate the additional column 'Type' where its smart enough to know which column I am taking my value from? Preferably without creating any additional views/table.

Use NVL2 (or CASE) and concatenate the columns and trim any excess trailing commas:
SELECT Name,
RTRIM(
NVL2(CapacityA,'A,',NULL)
||NVL2(CapacityB,'B,',NULL)
||NVL2(CapacityC,'C',NULL),
','
) AS type,
RTRIM(
NVL2(CapacityA,CapacityA||',',NULL)
||NVL2(CapacityB,CapacityB||',',NULL)
||NVL2(CapacityC,CapacityC,NULL),
','
) AS capacity
FROM table_name;
Which, for the sample data:
CREATE TABLE table_name (name, capacitya, capacityb, capacityc) AS
SELECT 'Plant1', 10, NULL, 20 FROM DUAL UNION ALL
SELECT 'Plant2', NULL, 10, NULL FROM DUAL;
Outputs:
NAME
TYPE
CAPACITY
Plant1
A,C
10,20
Plant2
B
10
db<>fiddle here

Here's one option:
sample data in lines #1 - 4
temp CTE simply - conditionally - concatenates types and capacities
final query (line #17)
removes double separators (commas) (regexp)
removes superfluous leading/trailing commas (trim)
SQL> with test (name, capa, capb, capc) as
2 (select 'Plant1', 10, null, 20 from dual union all
3 select 'Plant2', null, 10, null from dual
4 ),
5 temp as
6 (select name,
7 --
8 case when capa is not null then 'A' end ||','||
9 case when capb is not null then 'B' end ||','||
10 case when capc is not null then 'C' end as type,
11 --
12 case when capa is not null then capa end ||','||
13 case when capb is not null then capb end ||','||
14 case when capc is not null then capc end as capacity
15 from test
16 )
17 select name,
18 trim(both ',' from regexp_replace(type , ',+', ',')) as type,
19 trim(both ',' from regexp_replace(capacity, ',+', ',')) as capacity
20 from temp;
NAME TYPE CAPACITY
------ ---------- ----------
Plant1 A,C 10,20
Plant2 B 10
SQL>

Related

Columns to row converting

Table_A:
AA
BB
CC
DD
Output:
AA,BB,CC,DD
Any one help this
LISTAGG seems to be one choice:
Sample data:
SQL> with table_a(col) as
2 (select 'AA' from dual union all
3 select 'BB' from dual union all
4 select 'CC' from dual union all
5 select 'DD' from dual
6 )
Query:
7 select listagg(col, ',') within group (order by col) result
8 from table_a;
RESULT
------------------------------
AA,BB,CC,DD
SQL>
informatica only solution -
SRT : sort the data by col1.
EXP : create 3 ports. make sure their length be >3000 char
in_col1 = col1
v_col1 = col1|v_col1
p_col1= col1
o_col1= v_col1
AGG: create 2 ports
in_col1 <-- link o_col1 from previous step2
o_col1 = MAX(in_col1) -- link this to final target
Normally, there should be a key column by which you want to aggregate the strings. if you have any, you need to change step2 in v_col1.

REGEXP to validate a specific number

How can I search for a specific number in an array using REGEXP?
I have an array and need to verify if it has a specific number.
Ex: [5,2,1,4,6,19] and I am looking for number 1, but just the number 1 and not any number that contain the digit 1.
I had to do this:
case when REGEXP_INSTR(JSON_QUERY(MY_JSON_COLUMN,'$.path') , '[[]{1}[1][,]')<>0
or REGEXP_INSTR(JSON_QUERY(MY_JSON_COLUMN,'$.path') , '[,]{1}[1][,]{1}')<>0
or REGEXP_INSTR(JSON_QUERY(MY_JSON_COLUMN,'$.path') , '[,]{1}[1][]]')<>0
or REGEXP_INSTR(JSON_QUERY(MY_JSON_COLUMN,'$.path') , '[[]{1}[1][]]') <>0
then 'DIGIT_ONE' else 'NO_DIGIT_ONE'
end
Is there anything simpler?
You can use
(^|\D)1(\D|$)
This will seach for 1 not enclosed with other digits.
See this regex demo.
Details
(^|\D) - start of string or non-digit
1 - a 1 char
(\D|$) - non-digit or end of string.
Do NOT use regular expressions, use a proper JSON parser and then filter for the number you want:
SELECT my_json_column,
CASE
WHEN JSON_EXISTS( my_json_column, '$?(#.path[*] == 1)' )
THEN 'DIGIT ONE'
ELSE 'NO DIGIT ONE'
END AS has_one
FROM table_name;
or (if you are using Oracle 12.1 and cannot use path filter expressions with JSON_EXISTS, which is only available from Oracle 12.2):
SELECT my_json_column,
CASE
WHEN EXISTS(
SELECT 'X'
FROM JSON_TABLE(
t.my_json_column,
'$.path[*]'
COLUMNS (
value NUMBER PATH '$'
)
)
WHERE value = 1
)
THEN 'DIGIT ONE'
ELSE 'NO DIGIT ONE'
END
FROM table_name t;
Which, for the sample data:
CREATE TABLE table_name (
my_json_column CHECK ( my_json_column IS JSON )
) AS
SELECT '{"path":[5,2,1,4,6,19],"not_this_path":[1,2,3,4,5]}' FROM DUAL UNION ALL
SELECT '{"path":[5,2,4,6,19],"not_this_path":[1,2,3,4,5]}' FROM DUAL UNION ALL
SELECT '{"path":[11],"not_this_path":[1]}' FROM DUAL UNION ALL
SELECT '{"path":[2],"not_this_path":[1]}' FROM DUAL UNION ALL
SELECT '{"path":[1,11]}' FROM DUAL;
Both output:
MY_JSON_COLUMN | HAS_ONE
:-------------------------------------------------- | :-----------
{"path":[5,2,1,4,6,19],"not_this_path":[1,2,3,4,5]} | DIGIT ONE
{"path":[5,2,4,6,19],"not_this_path":[1,2,3,4,5]} | NO DIGIT ONE
{"path":[11],"not_this_path":[1]} | NO DIGIT ONE
{"path":[2],"not_this_path":[1]} | NO DIGIT ONE
{"path":[1,11]} | DIGIT ONE
db<>fiddle here
Alternatively, with a little bit more typing (a little bit? Am I kidding?!), splitting the string into rows and comparing values to the search string:
SQL> with test (col) as
2 (select '[5,2,1,4,6,19]' from dual)
3 select t.col,
4 case when '&par_search_string' in
5 (select regexp_substr(substr(col, 2, length(col) - 1), '[^,]+', 1, level) val
6 from test
7 connect by level <= regexp_count(col, ',') + 1
8 )
9 then 'Search string exists'
10 else 'Search string does not exist'
11 end result
12 from test t;
Enter value for par_search_string: 1
COL RESULT
-------------- ----------------------------
[5,2,1,4,6,19] Search string exists
SQL> /
Enter value for par_search_string: 24
COL RESULT
-------------- ----------------------------
[5,2,1,4,6,19] Search string does not exist
SQL>

How to union a hardcoded row after each grouped result

After every group / row i want to insert a hardcoded dummy row with a bunch of 'xxxx' to act a separator.
I would like to use oracle sql to do this query. i can execute it using a loop but i don't want to use plsql.
As the others suggest, it is best to do it on the front end.
However, if you have a burning need to be done as a query, here is how.
Here I did not use the rownum function as you have already done. I assume, your data is returned by a query, and you can replace my table with your query.
I made few more assumptions, as you have data with row numbers in it.
[I am not sure what do you mean by not PL/SQL]
Select Case When MOD(rownm, 2) = 0 then ' '
Else to_char((rownm + 1) / 2) End as rownm,
name, total, column1
From
(
select (rownm * 2 - 1) rownm,name, to_char(total) total ,column1 from t
union
SELECT (rownm * 2) rownm,'XXX' name, 'XXX' total, 'The row act .... ' column1 FROM t
) Q
Order by Q.rownm;
and here is the fiddle
Since you're already grouping the data, it might be easier to use GROUPING SETS instead of a UNION.
Grouping sets let you group by multiple sets of columns, including the same set twice to duplicate rows. Then the GROUP_ID function can be used to determine when the fake values should be used. This code will be a bit smaller than a UNION approach, and should be faster since it doesn't need to reference the table multiple times.
select
case when group_id() = 0 then name else '' end name,
case when group_id() = 0 then sum(some_value) else null end total,
case when group_id() = 1 then 'this rows...' else '' end column1
from
(
select 'jack' name, 22 some_value from dual union all
select 'jack' name, 1 some_value from dual union all
select 'john' name, 44 some_value from dual union all
select 'john' name, 1 some_value from dual union all
select 'harry' name, 1 some_value from dual union all
select 'harry' name, 1 some_value from dual
) raw_data
group by grouping sets (name, name)
order by raw_data.name, group_id();
You can use row generator technique (using CONNECT BY) and then use CASE..WHEN as follows:
SQL> SELECT CASE WHEN L.LVL = 1 THEN T.ROWNM END AS ROWNM,
2 CASE WHEN L.LVL = 1 THEN T.NAME
3 ELSE 'XXX' END AS NAME,
4 CASE WHEN L.LVL = 1 THEN TO_CHAR(T.TOTAL)
5 ELSE 'XXX' END AS TOTAL,
6 CASE WHEN L.LVL = 1 THEN T.COLUMN1
7 ELSE 'This row act as separator..' END AS COLUMN1
8 FROM T CROSS JOIN (
9 SELECT LEVEL AS LVL FROM DUAL CONNECT BY LEVEL <= 2
10 ) L ORDER BY T.ROWNM, L.LVL;
ROWNM NAME TOTAL COLUMN1
---------- ---------- ----- ---------------------------
1 Jack 23
XXX XXX This row act as separator..
2 John 45
XXX XXX This row act as separator..
3 harry 2
XXX XXX This row act as separator..
4 roy 45
XXX XXX This row act as separator..
5 Jacob 26
XXX XXX This row act as separator..
10 rows selected.
SQL>

Using Regex_substr in Oracle to select string up to the last occurence of a space within \n characters length

We have an issue where a column in our Oracle database has a longer character length than a field in another system.
Therefore I am trying to use case statements along with substr in order to split strings that are more than 40 characters in length. My case statements so far do what I want them to do in the fact that it leaves the first 40 characters of a string in column_a and then puts the remainder of the string in column_b.
However, the problem that I have is that by just using substr, the strings are being split midway through words.
So I was wondering if anybody knew of a couple of regular expressions that I could use with regex_substr that will -
select a string UP TO the last space within 40 characters - for
column_a
select a string AFTER the last space within 40 characters - for
column_b
These are the case statements that I have so far with substr:
CASE WHEN Length(column_a) > 10 THEN SubStr(column_a, 0, 40) END AS column_a,
CASE WHEN Length(column_a) > 40 THEN SubStr(addressnum, 41) END AS column_b
I am not familiar with regular expressions at all and so any help would be very much appreciated!
I've solved with instr/substr:
select substr(column_a,1,instr(substr(column_a,1,40), ' ', -1 )) column1,
substr(column_a,instr(substr(column_a,1,40), ' ', -1 )+1, 40) column2
from table1
A very similar problem was posted today on OTN. https://community.oracle.com/message/13928697#13928697
I posted a general solution, which will cover the problem proposed here as well. It may come in handy if there are similar needs in the future.
For the problem posted here on SO, the row_lengths table will have only one row, with r_id = 1 and r_len = 40. For demo purposes I am showing below an input_strings different from what I used on OTN.
Setup:
create table input_strings (str_id number, txt varchar2(500));
insert into input_strings values (1,
'One Hundred Sixty-Nine Thousand Eight Hundred Seventy-Four Dollars And Nine Cents');
insert into input_strings values (2, null);
insert into input_strings values (3, 'Mathguy rules');
create table row_lengths (r_id number, r_len number);
insert into row_lengths values (1, 40);
commit;
select * from input_strings;
STR_ID TXT
------- ---------------------------------------------------------------------------------
1 One Hundred Sixty-Nine Thousand Eight Hundred Seventy-Four Dollars And Nine Cents
2
3 Mathguy rules
3 rows selected
select * from row_lengths;
R_ID R_LEN
------- ----------
1 40
1 row selected.
Query and output: (NOTE: I include token length to verify that the first token is no more than 40 characters. OP did not answer if the SECOND token can be more than 40 characters; if it can't, one can add rows to the row_lengths table, perhaps with r_len = 40 for every row.)
with
r ( r_id, r_len ) as (
select r_id , r_len from row_lengths union all
select max(r_id) + 1, 4000 from row_lengths union all
select max(r_id) + 2, null from row_lengths
),
b (str_id, str, r_id, token, prev_pos, new_pos) as (
select str_id, txt || ' ', -1, null, null, 0
from input_strings
union all
select b.str_id, b.str, b.r_id + 1,
substr(str, prev_pos + 1, new_pos - prev_pos - 1),
b.new_pos,
new_pos + instr(substr(b.str, b.new_pos + 1, r.r_len + 1) , ' ', -1)
from b join r
on b.r_id + 2 = r.r_id
)
select str_id, r_id, token, nvl(length(token), 0) as len
from b
where r_id > 0
order by str_id, r_id;
STR_ID R_ID TOKEN LEN
------- ------- ------------------------------------------------ -------
1 1 One Hundred Sixty-Nine Thousand Eight 37
1 2 Hundred Seventy-Four Dollars And Nine Cents 43
2 1 0
2 2 0
3 1 Mathguy rules 13
3 2 0
6 rows selected.

Compare two delimited strings and return corresponding value in PL SQL

I have two columns with a hashtag delimited value, i.e. Email#Web#Telephone#SMS#MMS & 0#0#0#1#0 Note that each delimited value of the second column matches up with its corresponding delimited value in the first column, i.e. Email = 0, Web = 0, Telephone = 0, SMS = 1 etc.
Based on a parameter, I want to return the matching value of the second column. i.e. incoming param = Web#Telephone#SMS, thus the value that I want to return is 0#0#1.
This need to be done in PL SQL, and I have no clue where to start, which explains the lack of sample code.
Any help please?
There are a couple of very useful utility functions in an Oracle package called APEX_UTIL. (This package concerns Oracle Application Express aka APEX, but can be used anywhere). They are:
apex_util.string_to_table
apex_util.table_to_string
Using string_to_table you can convert the delimited string into a table of values:
declare
v_table apex_application_global.vc_arr2; -- This is the table type apex_util uses
begin
v_table := apex_util.table_to_string ('Email#Web#Telephone#SMS#MMS', '#');
end;
You now have an array with 5 elements ('Email', 'Web', 'Telephone', 'SMS', 'MMS');
You can do the same with the values string to get a table with elements ('0', '0', '0', '1', 0'). And you can do the same with the parameter to get a table with elements ('Web', 'Telephone', 'SMS').
You can then use PL/SQL logic to build a new array with elements for the values you need to return, i.e. ('0', '0', '1'). I have left this part to you!
Finally you can turn that back into a delimited string:
return apex_util.table_to_string (v_return_table, '#');
Firstly, you should normalize the table and have the attributes in different columns rather than delimited strings in a single column.
Anyway, you could do it in many ways using the techniques to Split comma delimited strings in a table
For example, using REGEXP_SUBSTR and CONNECT BY clause:
SQL> WITH DATA(attr, val) AS(
2 SELECT 'Email#Web#Telephone#SMS#MMS', '0#0#0#1#0' FROM dual
3 )
4 SELECT lines.COLUMN_VALUE,
5 trim(regexp_substr(t.attr, '[^#]+', 1, lines.COLUMN_VALUE)) attr,
6 trim(regexp_substr(t.val, '[^#]+', 1, lines.COLUMN_VALUE)) val
7 FROM data t,
8 TABLE (CAST (MULTISET
9 (SELECT LEVEL FROM dual CONNECT BY LEVEL <= regexp_count(t.attr, '#')+1
10 ) AS sys.odciNumberList ) ) lines
11 /
COLUMN_VALUE ATTR VAL
------------ --------------------------- ---------
1 Email 0
2 Web 0
3 Telephone 0
4 SMS 1
5 MMS 0
SQL>
Now, you can get the respective values for each attribute.
You could put the entire logic in a FUNCTION and return the corresponding values of each attribute and call the function in SELECT statement.
For example,
SQL> CREATE OR REPLACE
2 FUNCTION get_val_from_attr(
3 attr_name VARCHAR2)
4 RETURN NUMBER
5 IS
6 var_val NUMBER;
7 BEGIN
8 WITH DATA(attr, val) AS
9 ( SELECT 'Email#Web#Telephone#SMS#MMS', '0#0#0#1#0' FROM dual
10 ),
11 t2 AS
12 (SELECT lines.COLUMN_VALUE,
13 trim(regexp_substr(t.attr, '[^#]+', 1, lines.COLUMN_VALUE)) attr,
14 trim(regexp_substr(t.val, '[^#]+', 1, lines.COLUMN_VALUE)) val
15 FROM data t,
16 TABLE (CAST (MULTISET
17 (SELECT LEVEL FROM dual CONNECT BY LEVEL <= regexp_count(t.attr, '#')+1
18 ) AS sys.odciNumberList ) ) lines
19 )
20 SELECT val INTO var_val FROM t2 WHERE attr = attr_name;
21 RETURN var_val;
22 END;
23 /
Function created.
Let's call the function:
SQL> SELECT get_val_from_attr('Email') FROM dual;
GET_VAL_FROM_ATTR('EMAIL')
--------------------------
0
SQL> SELECT get_val_from_attr('SMS') FROM dual;
GET_VAL_FROM_ATTR('SMS')
------------------------
1