Cross Join query in Oracle - sql

I am trying to refractor a query that has in its Select concatenation of five fields, thus
SELECT
Id
'-'
Field_2
'-'
Field_3
'-'
Field_4
AS id
FROM some_table
Example result would be something like this
Id
1-a-b-c-d
2-e-f-g-h
#null#
#null#
My problem is whilst this query works, some or all of these fields could be null. It has been suggested that a cross join could be to allow a display of null when all or any of these fields are null. Unfortunately, I am not sure how to do this. The cross join has been suggested for performance due to the large number of rows.
Can someone please show me how

You don't give a lot of detail but I expect what you want is the following.
SELECT COALESCE(Id,'') || '-' ||
COALESCE(Field_2,'') || '-' ||
COALESCE(Field_3,'') || '-' ||
COALESCE(Field_4,'')
AS id
FROM some_table

try this
select nvl(concat(concat(concat(concat(a,b),c),d),e),'NULL') from some_table

when any of the fields we show the key as null with the word null
What could be joined here? Use simple case:
with sample_data(id, field1, field2, field3, field4) as (
select 1, 'a', 'b', 'c', 'd' from dual union all
select 2, 'e', 'f', 'g', 'h' from dual union all
select 3, 'p', 'q', null, 's' from dual union all
select 4, null, 'x', 'y', 'z' from dual )
select case when id is null or field1 is null or field2 is null or field3 is null or field4 is null
then '#null#'
else id || '-' || field1 || '-' || field2 || '-' || field3 || '-' || field4
end as id
from sample_data
Result:
ID
---------
1-a-b-c-d
2-e-f-g-h
#null#
#null#

Related

Oracle rows To Column

Old Result
Column_Name
A
B
C
D
New Required Result
Column_Name
'A', 'B', 'C', 'D'
Restrictions, (Count is unknown. Could be A, B, C or A, B, C, D, E and so on.
Considering your question literally, you have a query returning a single column with a variable number of rows, say
select 'A' column_name from dual union all
select 'B' column_name from dual union all
select 'C' column_name from dual union all
select 'D' column_name from dual
and you need to have the result in a single row with a single column containing the concatenation of the values returned by the original query, wrapped by ''; in your example, you need:
Column_Name
'A', 'B', 'C', 'D'
If this is correct, you may simply need listagg:
select listagg('''' || column_name || '''', ', ') within group ( order by column_name)
from (
select 'A' column_name from dual union all
select 'B' column_name from dual union all
select 'C' column_name from dual union all
select 'D' column_name from dual
) yourQuery
If you are looking to make a list of all the values in a certain column behind each other you can use LISTAGG.
For example:
SELECT LISTAGG(Column_Name, ', ') WITHIN GROUP (ORDER BY Column_Name) "Column_Listing"
FROM TableA;

Concatenate Character

I want to add a character to order_id column. The table below shows what I intend to achieve:
Add_field Order_id New_column
0 32639 032639
0 37378 037378
The result I want to achieve for each row is stored in New_column.
You can use below Sql query for concat column values in result
SELECT CONCAT(column1, ' ', column2) as columnName
FROM tableName
Here (column1, ' ', column2) you can change ' ' according your requirement.
Try:
select '0' || TO_CHAR(column1)
from yourTable
Example:
You can try (table indipendent):
select '0' || TO_CHAR(rownum)
from dual
union
select '0' || TO_CHAR(3)
from dual
union
select '0' || TO_CHAR('4')
from dual;
output will be:
01
03
04

How to pick only a particular set of values from a column and generate an update statement based on that

I have a problem where I need to run a set of update statements based on column values.
So we will get a set of IDs like below after a select query.
-------------------------------------
ID ChangedColumns ChangedValues
-------------------------------------
101 col1|col2| [A^B]|[123^456]|
102 col3| [XXX^YYY]|
... .... ....
and so on
Where ChangedColumns are the column names which are changed and ChangedValues will tell the old and new values for the respective columns (delimited by a pipe '|' and enclosed in braces '[]')
Based on this, I need to run update statements on another table, which will be something like
update table
set col1='A',
col2='123'
where id = 101;
update table
set col3='XXX'
where id = 102;
update table
.....
..... and so on
The number of Changed Columns can be 1 - 20 and its corresponding changed values will be in same order in ChangedValues column.
Could someone please let me know how to write a PL/SQL block for the same?
Many thanks
First I broke the data down by ID and ChangedColumn:
SQL> with tbl(ID, ChangedColumns, ChangedValues) as (
select 101, 'col1|col2|', '[A^B]|[123^456]|' from dual
union
select 102, 'col3|', '[XXX^YYY]|' from dual
)
SELECT ID,
REGEXP_SUBSTR(ChangedColumns ,'([^|]*)(\|)', 1, COLUMN_VALUE, NULL, 1 ) as changedcolumn,
REGEXP_SUBSTR(ChangedValues ,'([^|]*)(\|)', 1, COLUMN_VALUE, NULL, 1 ) as column_value
FROM tbl,
TABLE(
CAST(
MULTISET(
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( ChangedColumns ,'\|' )
) AS SYS.ODCINUMBERLIST
)
);
ID CHANGEDCOL COLUMN_VALUE
---------- ---------- ----------------
101 col1 [A^B]
101 col2 [123^456]
102 col3 [XXX^YYY]
SQL>
Then changed that to generate a separate update statement for each column that changed for each ID. The next step is to work it into one update statement for each ID, handling all columns that changed:
SQL> with tbl(ID, ChangedColumns, ChangedValues) as (
select 101, 'col1|col2|', '[A^B]|[123^456]|' from dual
union
select 102, 'col3|', '[XXX^YYY]|' from dual
)
SELECT 'update table set ' ||
REGEXP_SUBSTR(ChangedColumns, '([^|]*)(\|)', 1, COLUMN_VALUE, NULL, 1 ) ||
' = ''' ||
REGEXP_SUBSTR(ChangedValues, '\[([^\^]*)(\^)', 1, COLUMN_VALUE, NULL, 1 ) ||
'''' ||
' where id = ' || id || ';' as update_stmt
FROM tbl,
TABLE(
CAST(
MULTISET(
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( ChangedColumns ,'\|' )
) AS SYS.ODCINUMBERLIST
)
);
UPDATE_STMT
--------------------------------------------------------------------------------
update table set col1 = 'A' where id = 101;
update table set col2 = '123' where id = 101;
update table set col3 = 'XXX' where id = 102;
SQL>
EDIT: Here's the final product after some further refining and tweaking:
SQL> with tbl_a(ID, ChangedColumns, ChangedValues) as (
select 101, 'col1|col2|', '[A^B]|[123^456]|' from dual
union
select 102, 'col3|', '[XXX^YYY]|' from dual
union
select 103, 'col4|col5|col6|col7|col8|', '[XX0^YY0]|[XX1^YY1]|[XX2^YY2]|[XX3^YY3]|[XX4^YY4]|' from dual
),
tbl_b(ID, column_value) as (
SELECT ID,
REGEXP_SUBSTR(ChangedColumns ,'([^|]*)(\|)', 1, COLUMN_VALUE, NULL, 1 ) ||
' = ' || CHR(39) ||
REGEXP_SUBSTR(ChangedValues ,'\[([^\^]*)(\^)', 1, COLUMN_VALUE, NULL, 1 ) || CHR(39)
FROM tbl_a,
TABLE(
CAST(
MULTISET(
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( ChangedColumns ,'\|' )
) AS SYS.ODCINUMBERLIST
)
)
)
SELECT distinct ID,
'UPDATE TABLE SET ' || LISTAGG(column_value, ', ')
WITHIN GROUP (ORDER BY id, column_value)
OVER (PARTITION BY id) || ' WHERE ID = ' || ID || ';' as update_stmt
FROM tbl_b
ORDER BY ID;
ID UPDATE_STMT
--------- ------------------------------------------------------------------------------------------------------
101 UPDATE TABLE SET col1 = 'A', col2 = '123' WHERE ID = 101;
102 UPDATE TABLE SET col3 = 'XXX' WHERE ID = 102;
103 UPDATE TABLE SET col4 = 'XX0', col5 = 'XX1', col6 = 'XX2', col7 = 'XX3', col8 = 'XX4' WHERE ID = 103;
SQL>
I suspect this could be further simplified, but there's something to be said for breaking a problem down into smaller steps.
UPDATE table2
SET table2.col1 = table1.col1,
table2.col2 = table1.col2,
...
FROM table1, table2
WHERE table1.memberid = table2.memberid

Oracle REGEXP_LIKE to use lookaround or AND ignoring the order

I am trying to find a match with a given list of search parameters appearing anywhere in the given string. The search parameters can be OR or AND. REGEXP_LIKE with REPLACE works fine with OR (|) but not able to do for AND. Here is an example of OR:
select 'match' from dual WHERE REGEXP_LIKE('BCR081', REPLACE ('BCR;081', ';', '|')); --works
select 'match' from dual WHERE REGEXP_LIKE('BCR081', '(' || REPLACE ('BCR;081', ';', ').*?(') || ')');
-- Works when they are in order but order shouldn't matter.
select 'match' from dual WHERE REGEXP_LIKE('BCR081', '(' || REPLACE ('081;BCR', ';', ').*?(') || ')'); --I need this to work.
Is there something equivalent to
select 'match' from dual WHERE REGEXP_LIKE('BCR081', REPLACE ('BCR;081', ';', '&'));
Any help is greatly appreciated. I tried (look ahead?):
select 'match' from dual WHERE REGEXP_LIKE('BCR081','(?=' || REPLACE ('081;BCR', ';', ')(?=') || ')');
Note: The above is an example only, we can have anywhere from 1-20 search parameters. Also can't use the contains clause as it will throw too many results error.
We can tokenise the search string and look for one by one, by virtually generating rows.
with my_data as
(
select 'BCR081' as str , 'BCR;081' as pattern from dual
),
pattern_table as
(
select str, regexp_substr(pattern,'[^;]+',1,level) as pattern
from my_data
connect by level <= regexp_count(pattern,';') + 1
)
SELECT DECODE(
COUNT(DECODE(
INSTR(a.str,b.pattern),
0,
null,
1)
),
0,
'no match',
'match'
) as result
FROM my_data a, pattern_table b
WHERE a.str = b.str
GROUP BY a.str;

oracle user defined delimiter in sql

I have a sample table which looks like following:
WITH T(ID, SLOW, MEDIUM, FAST, SUPER_FAST) AS
(SELECT 1, 'Y', 'Y', 'N', 'Y' FROM DUAL
UNION ALL
SELECT 2, 'N','Y','N', 'Y' FROM DUAL
)
SELECT 'Identified in '
||CASE WHEN L_TEXT_A IS NOT NULL THEN L_TEXT_A END ||' , '
||CASE WHEN L_TEXT_B IS NOT NULL THEN L_TEXT_B END ||' , '
||CASE WHEN L_TEXT_C IS NOT NULL THEN L_TEXT_C END ||' & '
||CASE WHEN L_TEXT_D IS NOT NULL THEN L_TEXT_D END
FROM
(
SELECT CASE WHEN slow = 'Y' THEN 'slow' END L_TEXT_A,
CASE WHEN medium = 'Y' THEN 'medium' END L_TEXT_B,
CASE WHEN FAST = 'Y' THEN 'fast' END L_TEXT_C,
case when SUPER_FAST = 'Y' then 'super fast' END L_TEXT_D
FROM T
); --
Identified in slow , medium , & super fast
Identified in , medium , & super fast
I need to get a result which should return as like:
'Identified in slow, medium and super fast'
'Identified in medium and super fast'
The condition is no delimiter for single column which having Y , ampersand for two 'Y' values, and commas followed by ampersand which having more than 2 columns.
If you are using Oracle 11g2, a combination of unpivot and listagg can help, try this query
WITH T(ID, SLOW, MEDIUM, FAST, SUPER_FAST) AS
(SELECT 1, 'Y', 'Y', 'N', 'Y' FROM DUAL
UNION ALL
SELECT 2, 'N','Y','N', 'Y' FROM DUAL
union all
SELECT 3, 'N','N','N', 'Y' FROM DUAL
union all
SELECT 4, 'N','N','N', 'N' FROM DUAL
),
tlist as (select listagg(speed, ', ') within group (order by rownum) as list, id
from T
unpivot (pace for speed in (SLOW as 'Slow',MEDIUM as 'Medium', FAST as 'Fast', SUPER_FAST as 'Super Fast'))
where pace = 'Y'
group by id)
select case when instr(list, ', ', -1, 1) = 0 then
list
else
substr(list, 1, instr(list, ', ', -1, 1) - 1) || ' and ' || substr(list, instr(list, ', ', -1, 1) + 2)
end as list
from tlist;
output:
| LIST |
|-----------------------------|
| Slow, Medium and Super Fast |
| Medium and Super Fast |
| Super Fast |
Here is some code that uses simple instr, substr and replace functions built in functions which seems to work for all combinations. Note, if you have the option of doing this via a PL/SQL function you could probably simplify the code a bit:
with t(id, slow, medium, fast, super_fast) as (
select 1, 'n', 'n', 'n', 'n' from dual
union all
select 2, 'y', 'n', 'n', 'n' from dual
union all
select 3, 'n', 'y', 'n', 'n' from dual
union all
select 4, 'n', 'n', 'y', 'n' from dual
union all
select 5, 'n', 'n', 'n', 'y' from dual
union all
select 6, 'y', 'y', 'n', 'n' from dual
union all
select 7, 'y', 'n', 'y', 'n' from dual
union all
select 8, 'y', 'n', 'n', 'y' from dual
union all
select 9, 'n', 'y', 'y', 'n' from dual
union all
select 10, 'n', 'y', 'n', 'y' from dual
union all
select 11, 'n', 'n', 'y', 'y' from dual
union all
select 12, 'y', 'y', 'y', 'n' from dual
union all
select 13, 'y', 'y', 'n', 'y' from dual
union all
select 14, 'y', 'n', 'y', 'y' from dual
union all
select 15, 'n', 'y', 'y', 'y' from dual
union all
select 16, 'y', 'y', 'y', 'y' from dual
),
step1 as (
select 'identified in ' ||
decode(slow,'y', 'slow' || ', ', '') ||
decode(medium,'y', 'medium' || ', ', '') ||
decode(fast,'y', 'fast' || ', ', '') ||
decode(super_fast,'y', 'super fast' || ', ', '') str
from t
),
step2 as (
select length(str) - length(replace(str, ',', null)) as vals, -- count values (using commas)
substr(str, 1, length(str)-2) as str -- strip final comma
from step1
),
step3 as (
select str,
decode(vals, 0, 0, 1, 0, instr(str, ',', 1, vals -1)) as final_comma_pos,
vals
from step2
),
step4 as (
select decode(vals, 0, null,
1, str,
substr(str, 1, final_comma_pos - 1) || ' &' ||
substr(str, final_comma_pos + 1)
) as str
from step3
)
select * from step4;
Output:
identified in slow
identified in medium
identified in fast
identified in super fast
identified in slow & medium
identified in slow & fast
identified in slow & super fast
identified in medium & fast
identified in medium & super fast
identified in fast & super fast
identified in slow, medium & fast
identified in slow, medium & super fast
identified in slow, fast & super fast
identified in medium, fast & super fast
identified in slow, medium, fast & super fast
The below query delimits as you expected..
The Key is last occurrence of comma is replaced by AND.
regexp_count is used to find the last occurrence.
SQL> select regexp_replace('Identified in slow , medium , super fast',
',' ,' and ',1,
regexp_count('Identified in slow , medium , super fast',',')) from dual; 2 3
REGEXP_REPLACE('IDENTIFIEDINSLOW,MEDIUM,SUPE
--------------------------------------------
Identified in slow , medium and super fast
Full Version:
WITH T(ID, SLOW, MEDIUM, FAST, SUPER_FAST) AS
(SELECT 1, 'Y', 'Y', 'N', 'Y' FROM DUAL
UNION ALL
SELECT 2, 'N','Y','N', 'Y' FROM DUAL
),
mytext as
(SELECT trim(trailing ',' FROM 'Identified in '
||NVL2(L_TEXT_A,L_TEXT_A||',',NULL)
||NVL2(L_TEXT_B,L_TEXT_B||',',NULL)
||NVL2(L_TEXT_C,L_TEXT_C||',',NULL)
||(L_TEXT_D)) as text
FROM
(
SELECT CASE WHEN slow = 'Y' THEN 'slow' END L_TEXT_A,
CASE WHEN medium = 'Y' THEN 'medium' END L_TEXT_B,
CASE WHEN FAST = 'Y' THEN 'fast' END L_TEXT_C,
case when SUPER_FAST = 'Y' then 'super fast' END L_TEXT_D
FROM T
)
)
SELECT regexp_replace(text,
',' ,
' and ',
1,
regexp_count(text,',')) FROM mytext;