Need to join row data into columns in Oracle - sql

I've data in below format.
ID GRP VALUE
1 P_1 AA
1 P_2 BB
1 X_1 CC
1 X_2 DD
1 M_1 EE
1 M_2 FF
1 N_1 GG
1 N_2 HH
1 K_1 II
1 K_2 JJ
And I need output in below format
ID GRP PAIRS
1 P_1,P_2 AA,BB
1 X_1,X_2 CC,DD
1 M_1,M_2 EE,FF
1 N_1,N_2 GG,HH
1 K_1,K_2 II,JJ
Kindly suggest a sql for this in oracle

You can use LISTAGG:
SELECT
MAX(ID),
LISTAGG(GRP, ',') WITHIN GROUP (ORDER BY SUBSTR(GRP, 1, 1)) AS GRP,
LISTAGG(VALUE, ',') WITHIN GROUP (ORDER BY SUBSTR(GRP, 1, 1)) AS PAIRS
FROM YourTable
GROUP BY SUBSTR(GRP, 1, 1);
I am assuming you want the maximum ID for each group in first column of the output (you wrote all 1s in your example).

If you only have two values, you can use MIN() and MAX():
select id,
min(grp) || ',' || max(grp) as grp,
min(value) || ',' || max(value) as pairs
from t
group by id, substr(grp, 1, 1);

Related

How to combine each three rows into single column in SQL Server 2008?

NOOfDays DISTRInutorID
-------------------------
1 abcd
1 cdef
2 DFSDF
2 SFSDD
2 SDFSD
2 WAOYWAR
7 WEFIWE
7 WEOFYWE
7 WFYREU
The above is one of my sample tables, I want to combine each two rows based on NOOfDays.
Expected output:
NOOfDays DiSTRInutorID
------------------------------
1 abcd, cdef
2 DFSDF, SFSDD
2 SDFSD, WAOYWAR
7 WEFIWE, WEOFYWE
7 WFYREU
In ancient versions of SQL Server, the logic looks like:
select n.NOOfDays,
stuff( (select ',' + t2.DISTRInutorID
from t t2
where t2.NOOfDays = n.NOOfDays
for xml path ('')
), 1, 1, ''
) as list
from (select distinct NOOfDays from t) n;
EDIT:
I misunderstood the original question. This is what you are looking for:
with cte as (
select t.*, row_number() over (order by (select null)) - 1 as seqnum
from t
)
select NOOfDays,
(case when count(*) = 1 then min(DISTRInutorID)
else max(case when seqnum % 2 = 0 then DISTRInutorID end) + ',' + max(case when seqnum % 2 = 1 then DISTRInutorID end)
end) as list
from cte
group by NOOfDays, floor(seqnum / 2);
Note that SQL tables represent unordered sets. This arbitrarily pairs rows where the first column is the same, but there is no guarantee that these are adjacent -- for the simple reason that "adjacent" is not defined.
Here is a db<>fiddle.

Assign column value based on the percentage of rows

In DB2 is there a way to assign a column value based on the first x%, then y% and remaining z% of rows?
I've tried using row_number() function but no luck!
Example below
Assuming that the below example count(id) is already arranged in descending order
Input:
ID count(id)
5 10
3 8
1 5
4 3
2 1
Output:
First 30% rows of the above input should be assigned code H, last 30% of the rows will have code L and remaining will have code M. If 30% of rows evaluates to decimal then round up-to 0 decimal place.
ID code
5 H
3 H
1 M
4 L
2 L
You can use window functions:
select t.id,
(case ntile(3) over (order by count(id) desc)
when 1 then 'H'
when 2 then 'M'
when 3 then 'L'
end) as grp
from t
group by t.id;
This puts them into equal sized groups.
For 30-40-30% split with your conditions, you have to be more careful:
select t.id,
(case when (seqnum - 1.0) < 0.3 * cnt then 'H'
when (seqnum + 1.0) > 0.7 * cnt then 'L'
else 'M'
end) as grp
from (select t.id,
count(*) as cnt,
count(*) over () as num_ids,
row_number() over (order by count(*) desc) as seqnum
from t
group by t.id
) t
Try this:
with t(ID, count_id) as (values
(5, 10)
, (3, 8)
, (1, 5)
, (4, 3)
, (2, 1)
)
select t.*
, case
when pst <=30 then 'H'
when pst <=70 then 'M'
else 'L'
end as code
from
(
select t.*
, rownumber() over (order by count_id desc) as rn
, 100*rownumber() over (order by count_id desc)/nullif(count(1) over(), 0) as pst
from t
) t;
The result is:
ID COUNT_ID RN PST CODE
-- -------- -- --- ----
5 10 1 20 H
3 8 2 40 M
1 5 3 60 M
4 3 4 80 L
2 1 5 100 L

Closest distance of a column

I need to find the two closest distance of each row based on all the values of the column.
I tried to do cross join and used the lead function to find the distance. I am totally not sure how to write it. Please suggest.
select a.id,lead(a.value,b.value) as distance from cluster a , cluster b
Input table:
ID Values
1 12.1
2 11
3 14
4 10
5 9
6 15
7 16
8 8
ID Values Closest_Value
1 12.1 11,10
2 11 9,10
3 14 15,16
4 10 9,11
5 9 8,10
6 15 14,16
7 16 14,15
8 8 9,10
One method uses a cross join and aggregation:
select id, value,
listagg(other_value, ',') within group (order by diff) as near_values
from (select c.id, c.value, c2.value as other_value
abs(c2.value = c.value) as diff,
row_number() over (partition by c.id order by abs(c2.value = c.value)) as seqnum
from cluster c join
cluster c2
on c.id <> c2.id
) c
where seqnum <= 2
group by id, value;
The above is not particularly efficient for larger amounts of data. An alternative is to use lead() and lag() to get the values, unpivot, and aggregate:
with vals as (
select c.id, c.value,
(case when n.n = 1 then prev_value_2
when n.n = 2 then prev_value
when n.n = 3 then next_value
when n.n = 4 then next_value_2
end) as other_value
from (select c.*,
lag(value, 2) over (order by value) as prev_value_2,
lag(value) over (order by value) as prev_value,
lead(value) over (order by value) as next_value,
lead(value, 2) over (order by value) as next_value_2,
from clusters c
) c cross join
(select rownum as n
from clusters
where rownum <= 4
) n -- just a list of 4 numbers
)
select v.id, v.value,
list_agg(other_value, ',') within group (order by diff)
from (select v.*,
abs(other_value - value) as diff
row_number() over (partition by id order by abs(other_value - value)) as seqnum
from vals v
) v
where seqnum <= 2
group by id, value;

concate or segregate strings based on requirement

USER_ID COLUMN1 COLUMN2
JOHN 24 CA
JOHN 24 LA
JOHN 63 CA
JOHN 63 LA
JOHN 66 CA
JOHN 66 LA
JOHN 9 AF
JOHN 9 AL
JOHN 9 AW
JOHN 9 DF
Required output:
USER_ID RESULT
JOHN 24~CA-LA + 63~CA-LA + 66~CA-LA + 9~AF-AL-AW-DF
This is my requirement. I am trying listagg():
select USER_ID,
(listagg(case when seqnum_p = 1 then COLUMN1 end, '-') within group (order by COLUMN1) ||
'~' ||
listagg(case when seqnum_b = 1 then COLUMN2 end, '-') within group (order by COLUMN2)
) as result
from (select TABLE.*,
row_number() over (partition by USER_ID, COLUMN1 order by COLUMN1) as seqnum_p,
row_number() over (partition by USER_ID, COLUMN2 order by COLUMN2) as seqnum_b
from TABLE
)
group by USER_ID;
Current output:
JOHN || AF-AL-AW-CA-DF-LA~24-63-66-9
You can do two levels of aggregation instead of dealing with the row numbers:
select user_id,
listagg(tmp, ' + ') within group (order by tmp) as result
from (
select user_id,
column1 ||'~'|| listagg(column2, '-') within group (order by column2) as tmp
from your_table
group by user_id, column1
)
group by user_id
order by user_id;
USER RESULT
---- --------------------------------------------------
JOHN 24~CA-LA + 63~CA-LA + 66~CA-LA + 9~AF-AL-AW-DF
The inner query gives you the first level:
USER TMP
---- --------------------------------------------------
JOHN 9~AF-AL-AW-DF
JOHN 24~CA-LA
JOHN 63~CA-LA
JOHN 66~CA-LA
and the outer level further aggregates those into a single string per user.
The order-by in the outer query aggregation is of a string starting with a number, which puts '9~...' after '24~...', which would normally be odd but seems to be what you expect.
If you actually wanted them in numerical column-1-order you can include that in the subquery and use it for ordering:
select user_id,
listagg(tmp, ' + ') within group (order by column1) as result
from (
select user_id, column1,
column1 ||'~'|| listagg(column2, '-') within group (order by column2) as tmp
from your_table
group by user_id, column1
)
group by user_id
order by user_id;
USER RESULT
---- --------------------------------------------------
JOHN 9~AF-AL-AW-DF + 24~CA-LA + 63~CA-LA + 66~CA-LA

how to add column value in next colum

id value
1 10
2 20
3 30
4 40
5 50
Required output
table name data.
id value
1 10 //( 10+0(previous value))
2 30 //( 20+10(previous value))
3 50 //( 30+20(previous value))
4 70 //( 40+30(previous value))
5 90 //(50+40(previous value))
please provide sql query
You are looking for LAG which is standard SQL and should be available in later DB2 versions if I'm not mistaken.
select
id,
value + coalesce( lag(value) over (order by id), 0 ) as value
from mytable
order by id;
In case LAG OVER is not available, SUM OVER may be:
select
id,
coalesce( sum(value) over (order by id rows between 1 preceding and current row), 0 )
as value
from mytable
order by id;
solution 1:
select f1.id,
ifnull((select f2.value from yourtable f2 where f1.id - 1 =f2.id), 0) + f1.value as value
from yourtable f1
solution 2:
select f1.id,
ifnull(f3.value, 0) + f1.value as value
from yourtable f1
left outer join lateral
(
select f2.value from yourtable f2
where f1.id - 1 =f2.id
) f3 on 1=1