update row without considering space between two words - oracle

I am looking to replace text in column Text with format(A,B) where text contains A and B only and ignoring spaces between A and B.
Here is test data
Id Text
1 A B //should be replaced with format(A,B).
2 A B //should be replaced with format(A,B).
3 A B //should be replaced with format(A,B).
4 A 1 B //shouldn't be replaced.
5 A B 1 //should be replaced with format(A,B) 1.
I think I have to do something like
UPDATE test SET text = REPLACE(text, 'A[wild char for space]B', 'format(A,B)');
but how should I compare only for space? like % will compare everything.

You can use Oracle Regex fro this
UPDATE test SET text = REGEXP_REPLACE(testcol, '(A[:blank:]B)', 'format(A,B)')
Oracle Regex

Just a hint:
SQL> with t as (
2 select 'A B' col from dual union all
3 select 'A B' from dual union all
4 select 'A B' from dual union all
5 select 'AB' from dual union all
6 select 'A 1 B' from dual union all
7 select 'A B 1' from dual
8 )
9 select regexp_replace(t.col, 'A[[:space:]]*B', 'format(A,B)') from t
10 /
REGEXP_REPLACE(T.COL,'A[[:SPACE:]]*B','FORMAT(A,B)')
--------------------------------------------------------------------------------
format(A,B)
format(A,B)
format(A,B)
format(A,B)
A 1 B
format(A,B) 1

Sql for your scenario:
with tab(Id,Text) as
(select 1,'A B' from dual union all -- //should be replaced with format(A,B).
select 2,'B C' from dual union all -- //should be replaced with format(A,B).
select 3,'A B' from dual union all -- //should be replaced with format(A,B).
select 4,'A 2 B' from dual union all -- //shouldn't be replaced.
select 5,'Y Z 1' from dual) -- //should be replaced with format(A,B) 1.
select id,
text,
regexp_replace(text, '^\D\s+\D', 'format('||regexp_substr(text, '\D')||','||trim(regexp_substr(text, '\D+', 2))||')') format
from tab;
output:
| ID | TEXT | FORMAT |
|----|-------------|---------------|
| 1 | A B | format(A,B) |
| 2 | B C | format(B,C) |
| 3 | A B | format(A,B) |
| 4 | A 2 B | A 2 B |
| 5 | Y Z 1 | format(Y,Z) 1 |
And the update statement becomes
UPDATE test SET text = regexp_replace(text, '^\D\s+\D', 'format('||regexp_substr(text, '\D')||','||trim(regexp_substr(text, '\D+', 2))||')');

Related

Oracle Hierarchical query with multiple nodes

I have a View and a Table that has the following details:
V_Mgmt
DMId PMId TLId
1 1 1
1 1 2
1 2 3
2 3 4
2 3 5
2 4 6
T_ProjLevels
TLId DevId ParentDevId
1 1 0
1 2 1
1 3 1
2 4 0
2 5 4
2 6 4
2 7 6
3 8 0
3 9 0
4 10 0
4 11 0
4 12 11
Ideally, my tree structure would be as per the left image. But, I am required to create a tree structure as per the right image by skipping the TL's.
So far, I was successfully able to create my tree with only DevId's by using the below query. Need some help in creating this new tree structure.
SELECT DevId,ParentDevId from T_ProjLevels
START WITH ParentDevId=0
Connect By Nocycle Prior "DevId" = "ParentDevId"
ORDER SIBLINGS BY ParentDevId
Look at your data structure. You have one row representing DMID 1, PMID 1, which you want to show as two separate rows in your tree. Somehow, you need to create extra rows in your result set to represent these intermediate nodes. A good way to do that is using GROUPING SETS.
So, what we will do is join the two tables and GROUP BY GROUPING SETS(...) in such a way as to get a merged hierarchy of all the nodes, whether they are DM, PM, or DEV nodes.
Once we have that, we will just do a simple CONNECT BY query on the merged hierarchy.
I am pasting in a working implementation of that plan below, but first let me just say that if you need to do stuff like this often, seriously consider that maybe your data model is not properly designed.
Here is the query, with comments. This version will give you the tree on the right in your post: that is, the one that omits the TL levels. A simple modification would give the tree on the left instead... it was unclear which one you were really after (sorry).
-- First provide data to simulate your V_Mgmt table...
With V_Mgmt ( DMid, PMId, TLId ) AS (
SELECT 1, 1, 1 FROM DUAL UNION ALL
SELECT 1, 1, 2 FROM DUAL UNION ALL
SELECT 1, 2, 3 FROM DUAL UNION ALL
SELECT 2, 3, 4 FROM DUAL UNION ALL
SELECT 2, 3, 5 FROM DUAL UNION ALL
SELECT 2, 4, 6 FROM DUAL ),
-- ... and your T_ProjLevels table
T_ProjLevels (TLId, DevId, ParentDevId) AS (
SELECT 1, 1, 0 FROM DUAL UNION ALL
SELECT 1, 2, 1 FROM DUAL UNION ALL
SELECT 1, 3, 1 FROM DUAL UNION ALL
SELECT 2, 4, 0 FROM DUAL UNION ALL
SELECT 2, 5, 4 FROM DUAL UNION ALL
SELECT 2, 6, 4 FROM DUAL UNION ALL
SELECT 2, 7, 6 FROM DUAL UNION ALL
SELECT 3, 8, 0 FROM DUAL UNION ALL
SELECT 3, 9, 0 FROM DUAL UNION ALL
SELECT 4, 10, 0 FROM DUAL UNION ALL
SELECT 4, 11, 0 FROM DUAL UNION ALL
SELECT 4, 12, 11 FROM DUAL ),
-- Next, merge them together
-- (A) using GROUPING_SETS to create extra rows for the DM, PM, but not the TL-level nodes
-- (B) combining DM, PM, (but not TL), and DEV ids into a common set of "node", "parent_node", and "node_name" columns
merged_hierarchy ( node, parent_node, node_name ) AS (
SELECT rtrim(m.dmid || '.' || m.pmid || '.' || pl.devid,'.') node,
rtrim(
case
when grouping(m.pmid) = 1 then NULL
when grouping(m.tlid) = 1 then to_char(m.dmid)
when grouping(pl.devid) = 1 then m.dmid || '.' || m.pmid
else
m.dmid || '.' || m.pmid || '.' || nullif(pl.parentdevid,0) end,'.') parent_node,
case when grouping(pl.devid) = 0 THEN 'DEV' || pl.devid
when grouping(m.tlid) = 0 THEN 'TL' || m.tlid
when grouping(m.pmid) = 0 THEN 'PM' || m.pmid
when grouping(m.dmid) = 0 THEN 'DM' || m.dmid
end node_name
from v_mgmt m
left join t_projlevels pl on pl.tlid = m.tlid
group by grouping sets ( ( m.dmid ), ( m.dmid, m.pmid), (m.dmid, m.pmid, m.tlid, pl.devid, pl.parentdevid ) )
)
-- Finally, query the merged hierarchy as a straight-forward CONNECT BY query
SELECT lpad(' ',5*(level-1),' ') || node_name output
FROM merged_hierarchy
START WITH parent_node IS NULL
CONNECT BY parent_node = prior node
-- Exclude the outer-joined rows from T_ProjLevels...
AND node_name != 'DEV';
+--------------------------+
| OUTPUT |
+--------------------------+
| DM1 |
| PM1 |
| DEV1 |
| DEV2 |
| DEV3 |
| DEV4 |
| DEV5 |
| DEV6 |
| DEV7 |
| PM2 |
| DEV8 |
| DEV9 |
| DM2 |
| PM3 |
| DEV10 |
| DEV11 |
| DEV12 |
| PM4 |
+--------------------------+

Oracle add group function over result rows

I'm trying to add an aggregate function column to an existing result set. I've tried variations of OVER(), UNION, but cannot find a solution.
Example current result set:
ID ATTR VALUE
1 score 5
1 score 7
1 score 9
Example desired result set:
ID ATTR VALUE STDDEV (score)
1 score 5 2
1 score 7 2
1 score 9 2
Thank you
Seems like you're after:
stddev(value) over (partition by attr)
stddev(value) over (partition by id, attr)
It just depend on what you need to partition by. Based on sample data the attr should be enough; but I could see possibly the ID and attr.
Example:
With CTE (ID, Attr, Value) as (
SELECT 1, 'score', 5 from dual union all
SELECT 1, 'score', 7 from dual union all
SELECT 1, 'score', 9 from dual union all
SELECT 1, 'Z', 1 from dual union all
SELECT 1, 'Z', 5 from dual union all
SELECT 1, 'Z', 8 from dual)
SELECT A.*, stddev(value) over (partition by attr)
FROM cte A
ORDER BY attr, value
DOCS show that by adding an order by to the analytic, one can acquire the cumulative standard deviation per record.
Giving us:
+----+-------+-------+------------------------------------------+
| ID | attr | value | stdev |
+----+-------+-------+------------------------------------------+
| 1 | Z | 1 | 3.51188458428424628280046822063322249225 |
| 1 | Z | 5 | 3.51188458428424628280046822063322249225 |
| 1 | Z | 8 | 3.51188458428424628280046822063322249225 |
| 1 | score | 5 | 2 |
| 1 | score | 7 | 2 |
| 1 | score | 9 | 2 |
+----+-------+-------+------------------------------------------+

converting multiple comma separated columns into rows

I have an Oracle table which holds comma separated values in many columns. For example :
Id Column1 Column2
1 A,B,C H
2 D,E J,K
3 F L,M,N
I want to split all the columns into rows and the output should be this :
ID Column1 Column2
1 A H
1 B H
1 C H
2 D J
2 D K
2 E J
2 E K
3 F L
3 F M
3 F N
I found some suggestions which uses regexp_substr and connect by but it deals with only one column that has comma separated values. I have tried sub-query method also where I will be dealing with one column at a time in inner query and send the inner query output as input it outer query, this takes more time and the columns that hold comma separated values are more. So I cannot use the sub-query method.
For one column you can CROSS JOIN a TABLE() collection expression containing a correlated sub-query that uses a hierarchical query to split the column value up into separate strings. For two columns, you just do the same thing for the second column and the CROSS JOIN takes care of ensuring that each delimited value in column1 is paired with each delimited value in column2.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name ( Id, Column1, Column2 ) AS
SELECT 1, 'A,B,C', 'H' FROM DUAL UNION ALL
SELECT 2, 'D,E', 'J,K' FROM DUAL UNION ALL
SELECT 3, 'F', 'L,M,N' FROM DUAL;
Query 1:
SELECT t.id,
c1.COLUMN_VALUE AS c1,
c2.COLUMN_VALUE AS c2
FROM table_name t
CROSS JOIN
TABLE(
CAST(
MULTISET(
SELECT REGEXP_SUBSTR( t.Column1, '[^,]+', 1, LEVEL )
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( t.Column1, '[^,]+' )
) AS SYS.ODCIVARCHAR2LIST
)
) c1
CROSS JOIN
TABLE(
CAST(
MULTISET(
SELECT REGEXP_SUBSTR( t.Column2, '[^,]+', 1, LEVEL )
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( t.Column2, '[^,]+' )
) AS SYS.ODCIVARCHAR2LIST
)
) c2
Results:
| ID | C1 | C2 |
|----|----|----|
| 1 | A | H |
| 1 | B | H |
| 1 | C | H |
| 2 | D | J |
| 2 | D | K |
| 2 | E | J |
| 2 | E | K |
| 3 | F | L |
| 3 | F | M |
| 3 | F | N |
Below will give you an idea about to how to convert the comma separated string into rows. You can use this logic to satisfy your need.
select regexp_substr('a,b,c,v,f', '[^,]+',1,level)
from dual
connect by level <= regexp_count('a,b,c,v,f', ',') + 1;

Select onle group that have positivo and negative number in plsql

I have a table that have date grouped.
I need to select only groups that have positive and negative value inside.
For example:
id value1
2 7
2 8
2 -1
3 3
3 4
4 -1
4 -2
5 7
5 -5
the result should be
id value1
2 7
2 8
2 -1
5 7
5 -5
because the group with id 3 just have positive number and the group with id 4 just have negative number.
any idea how can I do it using case (when then) in a select or using if else inside a function. Or any other idea?
Try this.
select id,value1 FROM
(
select t.*,
count( DISTINCT SIGN (value1 ) ) OVER (PARTITION BY id ) n
from yourtable t
) WHERE n = 2
;
The Sign() function gives 1 for positive and -1 for negative numbers.
DEMO
If you group by the ID, you can use the aggregate functions MIN and MAX to find out if there are both positive and negative values. You need to decide how to treat 0 though... I have treated it as positive below :)
with your_table as(
-- Your example data here, this is not really part of the solution
select 2 as id, 7 as value1 from dual union all
select 2 as id, 8 as value1 from dual union all
select 2 as id, -1 as value1 from dual union all
select 3 as id, 3 as value1 from dual union all
select 3 as id, 4 as value1 from dual union all
select 4 as id, -1 as value1 from dual union all
select 4 as id, -2 as value1 from dual union all
select 5 as id, 7 as value1 from dual union all
select 5 as id, -5 as value1 from dual
)
select *
from your_table
where id in(select id
from your_table
group by id
having min(value1) < 0
and max(value1) >= 0);

Select an included values

I'm using Oracle SQL and i need help with a query. Hope it's not too much easy one. I did't find an answer for it in Google.
I have a table that need to be aggregated by ID column and then to select only the records that two values are included in a certain table (and both of them).
Table for example
ID | Value
1 | Y
1 | N
2 | N
2 | N
2 | Y
3 | Y
3 | Y
4 | Y
5 | Y
5 | N
5 | Y
5 | N
The output table need to include only the IDs that both Y and N are included in Value table. Output:
ID
1
2
5
Another solution that groups by the ID and uses HAVING to return only those with > 1 DISTINCT values:
with v_data(id, value) as (
select 1, 'Y' from dual union all
select 1, 'N' from dual union all
select 2, 'Y' from dual)
select id
from v_data
group by id
having count(distinct value) > 1
select distinct a.id
from your_table a inner join your_table b on a.id = b.id and a.value != b.value;

Resources