Difference between the values of multiple rows in SQL - sql

My table in SQL is like:-
RN Name value1 value2 Timestamp
1 Mark 110 210 20160119
1 Mark 106 205 20160115
1 Mark 103 201 20160112
2 Steve 120 220 20151218
2 Steve 111 210 20151210
2 Steve 104 206 20151203
Desired Output:-
RN Name value1Lag1 value1lag2 value2lag1 value2lag2
1 Mark 4 3 5 4
2 Steve 9 7 10 4
The difference is calculated from the most recent to the second recent and then from second recent to the third recent for RN 1
value1lag1 = 110-106 =4
value1lag2 = 106-103 = 3
value2lag1 = 210-205 = 5
value2lag2 = 205-201 = 4
similarly for other RN's also.
Note: For each RN there are 3 and only 3 rows.
I have tried in several ways by taking help from similar posts but no luck.

I've assumed that RN and Name are linked here. It's a bit messy, but if each RN always has 3 values and you always want to check them in this order, then something like this should work.
SELECT
t1.Name
, AVG(CASE WHEN table_ranked.Rank = 1 THEN table_ranked.value1 ELSE NULL END) - AVG(CASE WHEN table_ranked.Rank = 2 THEN table_ranked.value1 ELSE NULL END) value1Lag1
, AVG(CASE WHEN table_ranked.Rank = 2 THEN table_ranked.value1 ELSE NULL END) - AVG(CASE WHEN table_ranked.Rank = 3 THEN table_ranked.value1 ELSE NULL END) value1Lag2
, AVG(CASE WHEN table_ranked.Rank = 1 THEN table_ranked.value2 ELSE NULL END) - AVG(CASE WHEN table_ranked.Rank = 2 THEN table_ranked.value2 ELSE NULL END) value2Lag1
, AVG(CASE WHEN table_ranked.Rank = 2 THEN table_ranked.value2 ELSE NULL END) - AVG(CASE WHEN table_ranked.Rank = 3 THEN table_ranked.value2 ELSE NULL END) value2Lag2
FROM table t1
INNER JOIN
(
SELECT
t1.Name
, t1.value1
, t1.value2
, COUNT(t2.TimeStamp) Rank
FROM table t1
INNER JOIN table t2
ON t2.name = t1.name
AND t1.TimeStamp <= t2.TimeStamp
GROUP BY t1.Name, t1.value1, t1.value2
) table_ranked
ON table_ranked.Name = t1.Name
GROUP BY t1.Name

There are other answers here, but I think your problem is calling for analytic functions, specifically LAG():
select
rn,
name,
-- calculate the differences
value1 - v1l1 value1lag1,
v1l1 - v1l2 value1lag2,
value2 - v2l1 value2lag1,
v2l1 - v2l2 value2lag2
from (
select
rn,
name,
value1,
value2,
timestamp,
-- these two are the values from the row before this one ordered by timestamp (ascending)
lag(value1) over(partition by rn, name order by timestamp asc) v1l1,
lag(value2) over(partition by rn, name order by timestamp asc) v2l1
-- these two are the values from two rows before this one ordered by timestamp (ascending)
lag(value1, 2) over(partition by rn, name order by timestamp asc) v1l2,
lag(value2, 2) over(partition by rn, name order by timestamp asc) v2l2
from (
select
1 rn, 'Mark' name, 110 value1, 210 value2, '20160119' timestamp
from dual
union all
select
1 rn, 'Mark' name, 106 value1, 205 value2, '20160115' timestamp
from dual
union all
select
1 rn, 'Mark' name, 103 value1, 201 value2, '20160112' timestamp
from dual
union all
select
2 rn, 'Steve' name, 120 value1, 220 value2, '20151218' timestamp
from dual
union all
select
2 rn, 'Steve' name, 111 value1, 210 value2, '20151210' timestamp
from dual
union all
select
2 rn, 'Steve' name, 104 value1, 206 value2, '20151203' timestamp
from dual
) data
)
where
-- return only the rows that have defined values
v1l1 is not null and
v1l2 is not null and
v2l1 is not null and
v2l1 is not null
This approach has the benefit that Oracle does all the necessary buffering internally, avoiding self-joins and the like. For big data sets this can be important from a performance viewpoint.
As an example, the explain plan for that query would be something like
-------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 6 | 150 | 13 (8)| 00:00:01 |
|* 1 | VIEW | | 6 | 150 | 13 (8)| 00:00:01 |
| 2 | WINDOW SORT | | 6 | 138 | 13 (8)| 00:00:01 |
| 3 | VIEW | | 6 | 138 | 12 (0)| 00:00:01 |
| 4 | UNION-ALL | | | | | |
| 5 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 6 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 7 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 8 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 9 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 10 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
-------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("V1L1" IS NOT NULL AND "V1L2" IS NOT NULL AND "V2L1" IS
Note that there are no joins, just a WINDOW SORT that buffers the necessary data from the "data source" (in our case, the VIEW 3 that is the UNION ALL of our SELECT ... FROM DUAL) to partition and calculate the different lags.

if just in this case, it's not that difficult.you need 2 steps
self join and get the result of minus
select t1.RN,
t1.Name,
t1.rm,
t2.value1-t1.value1 as value1,
t2.value2-t1.value2 as value2
from
(select RN,Name,value1,value2,
row_number(partition by Name order by Timestamp desc) as rm from table)t1
left join
(select RN,Name,value1,value2,
row_number(partition by Name order by Timestamp desc) as rm from table) t2
on t1.rm = t2.rm-1
where t2.RN is not null.
you set this as a table let's say table3.
2.you pivot it
select * from (
select t3.RN, t3.Name,t3.rm,t3.value1,t3.value2 from table3 t3
)
pivot
(
max(value1)
for rm in ('1','2')
)v1
3.you get 2 pivot table for value1 and value2 join them together to get the result.
but i think there may be a better way and i m not sure if we can just join pivot when we pivot it so i ll use join after i get the pivot result that will make 2 more tables. its not good but the best i can do

-- test data
with data(rn,
name,
value1,
value2,
timestamp) as
(select 1, 'Mark', 110, 210, to_date('20160119', 'YYYYMMDD')
from dual
union all
select 1, 'Mark', 106, 205, to_date('20160115', 'YYYYMMDD')
from dual
union all
select 1, 'Mark', 103, 201, to_date('20160112', 'YYYYMMDD')
from dual
union all
select 2, 'Steve', 120, 220, to_date('20151218', 'YYYYMMDD')
from dual
union all
select 2, 'Steve', 111, 210, to_date('20151210', 'YYYYMMDD')
from dual
union all
select 2, 'Steve', 104, 206, to_date('20151203', 'YYYYMMDD') from dual),
-- first transform value1, value2 to value_id (1,2), value
data2 as
(select d.rn, d.name, 1 as val_id, d.value1 as value, d.timestamp
from data d
union all
select d.rn, d.name, 2 as val_id, d.value2 as value, d.timestamp
from data d)
select * -- find previous row P of row D, evaluate difference and build column name as desired
from (select d.rn,
d.name,
d.value - p.value as value,
'value' || d.val_id || 'Lag' || row_number() over(partition by d.rn, d.val_id order by d.timestamp desc) as col
from data2 p, data2 d
where p.rn = d.rn
and p.val_id = d.val_id
and p.timestamp =
(select max(pp.timestamp)
from data2 pp
where pp.rn = p.rn
and pp.val_id = p.val_id
and pp.timestamp < d.timestamp))
-- pivot
pivot(sum(value) for col in('value1Lag1',
'value1Lag2',
'value2Lag1',
'value2Lag2'));

Related

Comparing two rows in oracle without using lead and lag functions

how to compare two rows in a same table without using lead/lag functions in oracle?
I tried the self join of the table and using row number function but it didnt work for me.
suppose table is T1 and the data is stored in the format as mentioned below.
ID Name Address Date
1 A Noida 10-Apr-2019
1 A Gurugram 15-Apr-2019
1 A Mumbai 18-Apr-2019
and i want the output data to be like.
ID Name Old_value New_value Date
1 A Noida 10-Apr-2019
1 A Noida Gurugram 15-Apr-2019
1 A Gurugram Mumbai 18-Apr-2019
Here's one option:
SQL> with test (id, name, address, cdate) as
2 (select 1, 'A', 'Noida' , date '2019-04-10' from dual union all
3 select 1, 'A', 'Gurugram', date '2019-04-15' from dual union all
4 select 1, 'A', 'Mumbai' , date '2019-04-18' from dual
5 ),
6 temp as
7 (select t.*,
8 row_number() over (partition by name order by cdate) rn
9 from test t
10 )
11 select a.id, a.name, b.address old_value, a.address new_value, a.cdate
12 from temp a left join temp b on a.id = b.id and a.rn = b.rn + 1
13 order by a.cdate;
ID N OLD_VALU NEW_VALU CDATE
---------- - -------- -------- ----------
1 A Noida 10.04.2019
1 A Noida Gurugram 15.04.2019
1 A Gurugram Mumbai 18.04.2019
SQL>
If you don't want to use window functions, you can do it with a subquery:
select
t.ID, t.Name,
(select Address from tablename
where ID = t.ID
and "Date" = (
select max("Date")
from tablename
where ID = t.ID and "Date" < t."Date"
)
) Old_value,
t.Address New_value,
t."Date"
from tablename t
See the demo.
Results:
> ID | NAME | OLD_VALUE | NEW_VALUE | Date
> -: | :--- | :-------- | :-------- | :--------
> 1 | A | | Noida | 10-APR-19
> 1 | A | Noida | Gurugram | 15-APR-19
> 1 | A | Gurugram | Mumbai | 18-APR-19
In future questions, I strongly recommend:
Providing test data using CREATE TABLE and INSERT statements
Sharing your precise Oracle database version number.
Starting with version 12c, you can use row pattern matching:
with test (id, name, address, cdate) as
(select 1, 'A', 'Noida' , date '2019-04-10' from dual union all
select 1, 'A', 'Gurugram', date '2019-04-15' from dual union all
select 1, 'A', 'Mumbai' , date '2019-04-18' from dual
)
select * from test
match_recognize(
partition by id, name order by cdate
measures prev(address) old_address
all rows per match
pattern(a)
define a as 1=1
);
ID NAME CDATE OLD_ADDRESS ADDRESS
1 A 2019-04-10 00:00:00 Noida
1 A 2019-04-15 00:00:00 Noida Gurugram
1 A 2019-04-18 00:00:00 Gurugram Mumbai

SQL: summing a column starting from row immediately after two consecutive 'trigger' values in another column

How to sum all values after two consecutive YES's in the CONDITION_SATISFIED column?
ID | CONDITION_SATISFIED | VALUE
--------------------------------
1 | NO | 100
2 | NO | 300
3 | NO | 500
4 | YES | 100
5 | YES | 300
6 | NO | 500 <-
7 | NO | 100 <-
8 | YES | 300 <-
9 | NO | 500 <-
--------------------------------
SUM | 1400
Note: further occurrences of YES/NO are ignored once the summation is started.
I've gotten to the point where I am able to generate two extra columns for the CONDITION_SATISFIED column like this:
ID | CONDITION_SATISFIED | VALUE RANK | REPEAT_COUNT
-------------------------------- -------------------
1 | NO | 100 1 | 3
2 | NO | 300 1 | 3
3 | NO | 500 1 | 3
4 | YES | 100 2 | 2
5 | YES | 300 2 | 2
6 | NO | 500 3 | 2 <- start from here
7 | NO | 100 3 | 2
8 | YES | 300 4 | 1
9 | NO | 500 5 | 1
-------------------------------- -------------------
But I'm not able to figure out how to get the first instance of REPEAT_COUNT >= 2 AND CONDITION_SATISFIED = 'YES', and then start the summation immediately after the 2nd YES (as indicated).
Hmmm . . . You can get the first where the two yesses are using lag():
select t.*
from (select t.*,
lag(condition_satisfied) over (order by id) as prev_cs,
lag(condition_satisfied, 2) over (order by id) as prev2_cs
from t
) t
where prev2_cs = 'YES' and prev_cs = 'YES';
Then you can just use this in a query:
select t.*
from t join
(select min(t.id) as id
from (select t.*,
lag(condition_satisfied) over (order by id) as prev_cs,
lag(condition_satisfied, 2) over (order by id) as prev2_cs
from t
) t
where prev2_cs = 'YES' and prev_cs = 'YES'
) yy
on t.id >= yy.id;
Oracle 12c: pattern matching
with t1 (id, condition_satisfied, value) as (
select 1, 'NO' , 100 from dual union all
select 2, 'NO' , 300 from dual union all
select 3, 'NO' , 500 from dual union all
select 4, 'YES', 100 from dual union all
select 5, 'YES', 300 from dual union all
select 6, 'NO' , 500 from dual union all
select 7, 'NO' , 100 from dual union all
select 8, 'YES', 300 from dual union all
select 9, 'NO' , 500 from dual)
select sum(v_value) as sum_value
from t1
match_recognize(
order by id
measures s.value as v_value
all rows per match
pattern (yes{2} s+)
define
yes as condition_satisfied = 'YES'
);
SUM_VALUE
----------
1400
If you have version lower than 12 no need to self-join and generate/prevent duplicates:
with s (id, condition_satisfied, value) as (
select 1, 'NO' , 100 from dual union all
select 2, 'NO' , 300 from dual union all
select 3, 'NO' , 500 from dual union all
select 4, 'YES', 100 from dual union all
select 5, 'YES', 300 from dual union all
select 6, 'NO' , 500 from dual union all
select 7, 'YES' , 100 from dual union all
select 8, 'YES', 300 from dual union all
select 9, 'NO' , 500 from dual)
select sum(value) sum_value
from
(select s.*, min(first_id) over () min_id
from
(select s.*,
case when condition_satisfied = 'YES' and condition_satisfied = lag(condition_satisfied) over (order by id) then id end first_id
from s
) s
)
where id > min_id;
SUM_VALUE
----------
1400

Order rows by values

I trying order table by rank, but rows which have position value - have to have position according to value in position field. It is possible do it without additional tables, views etc?
I have table like this:
rank | position | name
999 | 10 | txt1
200 | 4 | txt2
32 | 1 | txt3
1200 | 2 | txt4
123 | null | txt5
234 | null | txt6
567 | null | txt7
234 | null | txt8
432 | null | txt9
877 | null | txt10
Desired output have to look like this:
rank | position | name
32 | 1 | txt3
1200 | 2 | txt4
877 | null | txt10
200 | 4 | txt2
567 | null | txt7
432 | null | txt9
345 | null | txt8
234 | null | txt6
123 | null | txt5
999 | 10 | txt1
Here is an idea. Assign the proper ordering to each row. Then, if the position is available use that instead. When there are ties, put the position value first:
select t.*
from (select t.*, row_number() over (order by rank desc) as seqnum
from t
) t
order by (case when position is not null then position else seqnum end),
(case when position is not null then 1 else 2 end);
SQL Fiddle doesn't seem to be working these days, but this query demonstrates the results:
with t(rank, position, t) as (
select 999, 10, 'txt1' union all
select 200, 4, 'txt2' union all
select 32 , 1, 'txt3' union all
select 1200, 2, 'txt4' union all
select 123, null, 'txt5' union all
select 234, null, 'txt6' union all
select 567, null, 'txt7' union all
select 234, null, 'txt8' union all
select 432, null, 'txt9' union all
select 877, null , 'txt10'
)
select t.*
from (select t.*, row_number() over (order by rank desc) as seqnum
from t
) t
order by (case when position is not null then position else seqnum end),
(case when position is not null then 1 else 2 end);
EDIT;
When I wrote the above, I had a nagging suspicion of a problem. Here is a solution that should work. It is more complicated but it does produce the right numbers:
with t(rank, position, t) as (
select 999, 10, 'txt1' union all
select 200, 4, 'txt2' union all
select 32 , 1, 'txt3' union all
select 1200, 2, 'txt4' union all
select 123, null, 'txt5' union all
select 234, null, 'txt6' union all
select 567, null, 'txt7' union all
select 234, null, 'txt8' union all
select 432, null, 'txt9' union all
select 877, null , 'txt10'
)
select *
from (select t.*, g.*,
row_number() over (partition by t.position order by t.rank) gnum
from generate_series(1, 10) g(n) left join
t
on t.position = g.n
) tg left join
(select t.*,
row_number() over (partition by t.position order by t.rank) as tnum
from t
) t
on tg.gnum = t.tnum and t.position is null
order by n;
This is a weird sort of interleaving problem. The idea is to create slots (using generate series) for the positions. Then, assign the known positions to the slots. Finally, enumerate the remaining slots and assign the values there.
Note: I hard-coded 10, but it is easy enough to put in count(*) from the table there.
suppose you stored data in table1.
Then you should update column "position" as follows:
update a
set position = x.pos_null
from table1
a
inner join
(
select
a.name,
COUNT(a.rank) as pos_null
from
(
select
*
from table1
where position is null
)
a
left join
(
select
*
from table1
)
b
on a.rank <= b.rank
group by
a.name
)
x
on a.name = x.name
select * from table1 order by position
Bye,
Angelo.

oracle query to obtain all major version segmented message values

I need to crete one sql Oracle query to obtain the major versions of each segmented message values.
I have the next tables with their relationships already filled with example registers:
*MESSAGE_TABLE*
ID NAME
1 hello
2 bye
*SEGMENT_TABLE*
ID VALUE
1 development
2 production
*MESSAGE_VALUE_TABLE*
ID ID_MESSAGE ID_SEGMENT VERSION VALUE
1 1 1 2 hello
2 1 1 1 hi
3 1 2 1 hi
4 1 null 3 hi
5 1 null 4 hello
6 2 1 1 bye
7 2 1 2 good bye
MESSAGE_VALUE_TABLE UNIQUE_CONSTRAINT is (ID_MESSAGE, ID_SEGMENT, VERSION)
ID_SEGMENT is nullable because null segment indicates default values.
VERSION is a simple number field.
The query has to obtain the major versions of each segmented message values (query results must include the segment value):
Selected result rows from MESSAGE_VALUE_TABLE are:
ID ID_MESSAGE ID_SEGMENT VERSION VALUE
1 1 1 2 hello
3 1 2 1 hi
5 1 null 4 hello
7 2 1 2 good bye
Query return values should be (same order as the previous selected rows list):
NAME(MESSAGE_TABLE) VALUE (SEGMENT_TABLE) VALUE (MESSAGE_VALUE_TABLE)
hello development hello
hello production hi
hello null / empty hello
bye development good bye
The solution is here, thanks to San that did the hard work:
WITH tab AS (SELECT ID,
id_message,
id_segment,
CASE WHEN lead(nvl(id_segment, -1)) over (partition by id_message ORDER BY id_segmento, id_version) IS NULL
THEN 1
WHEN (nvl(id_segment, -1) != lead(nvl(id_segment, -1)) over (partition by id_message ORDER BY id_segmento, id_version))
THEN 1
ELSE 0
END change_ind,
version,
VALUE
FROM MESSAGE_VALUE_TABLE)
SELECT b.NAME, nvl(c.VALUE, 'null/empty'), a.VALUE
FROM tab a
JOIN MESSAGE_TABLE b ON (b.ID=a.id_message)
LEFT OUTER JOIN SEGMENT_TABLE c ON (c.ID=a.id_segment)
WHERE change_ind = 1
As per my understanding, you want something like
you can get first part as
WITH MESSAGE_VALUE_TABLE(ID,ID_MESSAGE,ID_SEGMENT,VERSION,VALUE) as
(select 1,1,1,1,'hi' from dual union all
select 2,1,1,2,'hello' from dual union all
select 3,1,2,1,'hi' from dual union all
select 4,1,null,3,'hi' from dual union all
select 5,1,null,4,'hello' from dual union all
select 6,1,1,1,'hi' from dual union all
select 7,1,1,2,'hello' from dual union all
select 8,1,2,3,'hi' from dual union all
select 9,1,null,1,'hi' from dual union all
select 10,1,null,2,'hello' from dual union all
select 11,2,1,1,'bye' from dual union all
select 12,2,1,2,'good bye' from dual)
------
---End of data
------
SELECT id, id_message, id_segment, version, VALUE
from (
SELECT ID,
id_message,
id_segment,
CASE WHEN lead(nvl(id_segment, -1)) over (partition by id_message ORDER BY ID) IS NULL
THEN 1
WHEN (nvl(id_segment, -1) != lead(nvl(id_segment, -1)) over (partition by id_message ORDER BY ID))
THEN 1
ELSE 0
END change_ind,
version,
VALUE
FROM MESSAGE_VALUE_TABLE)
where change_ind = 1;
Output:
| ID | ID_MESSAGE | ID_SEGMENT | VERSION | VALUE |
|----|------------|------------|---------|----------|
| 2 | 1 | 1 | 2 | hello |
| 3 | 1 | 2 | 1 | hi |
| 5 | 1 | (null) | 4 | hello |
| 7 | 1 | 1 | 2 | hello |
| 8 | 1 | 2 | 3 | hi |
| 10 | 1 | (null) | 2 | hello |
| 12 | 2 | 1 | 2 | good bye |
And second Part as
WITH MESSAGE_VALUE_TABLE(ID,ID_MESSAGE,ID_SEGMENT,VERSION,VALUE) as
(select 1,1,1,1,'hi' from dual union all
select 2,1,1,2,'hello' from dual union all
select 3,1,2,1,'hi' from dual union all
select 4,1,null,3,'hi' from dual union all
select 5,1,null,4,'hello' from dual union all
select 6,1,1,1,'hi' from dual union all
select 7,1,1,2,'hello' from dual union all
select 8,1,2,3,'hi' from dual union all
select 9,1,null,1,'hi' from dual union all
select 10,1,null,2,'hello' from dual union all
select 11,2,1,1,'bye' from dual union all
select 12,2,1,2,'good bye' from dual),
MESSAGE_TABLE(ID,NAME) AS
(SELECT 1 , 'hello' FROM dual UNION ALL
SELECT 2, 'bye' FROM dual),
SEGMENT_TABLE(ID,VALUE) AS
(SELECT 1,'development' FROM dual UNION ALL
SELECT 2,'production' FROM dual),
------
---End of data
------
tab AS (SELECT ID,
id_message,
id_segment,
CASE WHEN lead(nvl(id_segment, -1)) over (partition by id_message ORDER BY ID) IS NULL
THEN 1
WHEN (nvl(id_segment, -1) != lead(nvl(id_segment, -1)) over (partition by id_message ORDER BY ID))
THEN 1
ELSE 0
END change_ind,
version,
VALUE
FROM MESSAGE_VALUE_TABLE)
SELECT b.NAME, nvl(c.VALUE, 'null/empty') C_VALUE, a.VALUE
FROM tab a
JOIN MESSAGE_TABLE b ON (b.ID=a.id_message)
LEFT OUTER JOIN SEGMENT_TABLE c ON (c.ID=a.id_segment)
WHERE change_ind = 1
ORDER BY a.ID
Output:
| NAME | C_VALUE | VALUE |
|-------|-------------|----------|
| hello | development | hello |
| hello | production | hi |
| hello | null/empty | hello |
| hello | development | hello |
| hello | production | hi |
| hello | null/empty | hello |
| bye | development | good bye |
So your final query will be :
WITH tab AS (SELECT ID,
id_message,
id_segment,
CASE WHEN lead(nvl(id_segment, -1)) over (partition by id_message ORDER BY ID) IS NULL
THEN 1
WHEN (nvl(id_segment, -1) != lead(nvl(id_segment, -1)) over (partition by id_message ORDER BY ID))
THEN 1
ELSE 0
END change_ind,
version,
VALUE
FROM MESSAGE_VALUE_TABLE)
SELECT b.NAME, nvl(c.VALUE, 'null/empty'), a.VALUE
FROM tab a
JOIN MESSAGE_TABLE b ON (b.ID=a.id_message)
LEFT OUTER JOIN SEGMENT_TABLE c ON (c.ID=a.id_segment)
WHERE change_ind = 1
ORDER BY a.ID
Not fully sure what you are trying to achieve. Try analytical query.
select message_name, segment_value, message_value from (
select m.name message_name, s.value segment_value, v.value message_value,
dense_rank() over (partition by v.id_message, v.id_segment order by version desc nulls last) version_rank
from message_value_table v
inner join message_table m on v.id_message = m.id
left outer join segment_table s on v.id_segment = s.id
) where version_rank = 1
;
http://sqlfiddle.com/#!4/35aa7/10

Oracle grouping/changing rows to columns

I have the following table named foo:
ID | KEY | VAL
----------------
1 | 47 | 97
2 | 47 | 98
3 | 47 | 99
4 | 48 | 100
5 | 48 | 101
6 | 49 | 102
I want to run a select query and have the results show like this
UNIQUE_ID | KEY | ID1 | VAL1 | ID2 | VAL2 | ID3 | VAL3
--------------------------------------------------------------
47_1:97_2:98_3:99| 47 | 1 | 97 | 2 | 98 | 3 | 99
48_4:100_5:101 | 48 | 4 | 100 | 5 | 101 | |
49_6:102 | 49 | 6 | 102 | | | |
So, basically all rows with the same KEY get collapsed into 1 row. There can be anywhere from 1-3 rows per KEY value
Is there a way to do this in a sql query (without writing a stored procedure or scripts)?
If not, I could also work with the less desirable choice of
UNIQUE_ID | KEY | IDS | VALS
--------------------------------------------------------------
47_1:97_2:98_3:99| 47 | 1,2,3 | 97,98,99
48_4:100_5:101 | 48 | 4,5 | 100, 101
49_6:102 | 49 | 6 | 102
Thanks!
UPDATE:
Unfortunately my real-world problem seems to be much more difficult than this example, and I'm having trouble getting either example to work :( My query is over 120 lines so it's not very easy to post. It kind of looks like:
with v_table as (select ...),
v_table2 as (select foo from v_table where...),
v_table3 as (select foo from v_table where ...),
...
v_table23 as (select foo from v_table where ...)
select distinct (...) as "UniqueID", myKey, myVal, otherCol1, ..., otherCol18
from tbl1 inner join tbl2 on...
...
inner join tbl15 on ...
If I try any of the methods below it seems that I cannot do group-bys correctly because of all the other data being returned.
Ex:
with v_table as (select ...),
v_table2 as (select foo from v_table where...),
v_table3 as (select foo from v_table where ...),
...
v_table23 as (select foo from v_table where ...)
select "Unique ID",
myKey, max(decode(id_col,1,id_col)) as id_1, max(decode(id_col,1,myVal)) as val_1,
max(decode(id_col,2,id_col)) as id_2,max(decode(id_col,2,myVal)) as val_2,
max(decode(id_col,3,id_col)) as id_3,max(decode(id_col,3,myVal)) as val_3
from (
select distinct (...) as "UniqueID", myKey, row_number() over (partition by myKey order by id) as id_col, id, myVal, otherCol1, ..., otherCol18
from tbl1 inner join tbl2 on...
...
inner join tbl15 on ...
) group by myKey;
Gives me the error: ORA-00979: not a GROUP BY expression
This is because I am selecting the UniqueID from the inner select. I will need to do this as well as select other columns from the inner table.
Any help would be appreciated!
Take a look ath this article about Listagg function, this will help you getting the comma separated results, it works only in the 11g version.
You may try this
select key,
max(decode(id_col,1,id_col)) as id_1,max(decode(id_col,1,val)) as val_1,
max(decode(id_col,2,id_col)) as id_2,max(decode(id_col,2,val)) as val_2,
max(decode(id_col,3,id_col)) as id_3,max(decode(id_col,3,val)) as val_3
from (
select key, row_number() over (partition by key order by id) as id_col,id,val
from your_table
)
group by key
As #O.D. suggests, you can generate the less desirable version with LISTAGG, for example (using a CTE to generate your sample data):
with foo as (
select 1 as id, 47 as key, 97 as val from dual
union select 2,47,98 from dual
union select 3,47,99 from dual
union select 4,48,100 from dual
union select 5,48,101 from dual
union select 6,49,102 from dual
)
select key ||'_'|| listagg(id ||':' ||val, '_')
within group (order by id) as unique_id,
key,
listagg(id, ',') within group (order by id) as ids,
listagg(val, ',') within group (order by id) as vals
from foo
group by key
order by key;
UNIQUE_ID KEY IDS VALS
----------------- ---- -------------------- --------------------
47_1:97_2:98_3:99 47 1,2,3 97,98,99
48_4:100_5:101 48 4,5 100,101
49_6:102 49 6 102
With a bit more manipulation you can get your preferred results:
with foo as (
select 1 as id, 47 as key, 97 as val from dual
union select 2,47,98 from dual
union select 3,47,99 from dual
union select 4,48,100 from dual
union select 5,48,101 from dual
union select 6,49,102 from dual
)
select unique_id, key,
max(id1) as id1, max(val1) as val1,
max(id2) as id2, max(val2) as val2,
max(id3) as id3, max(val3) as val3
from (
select unique_id,key,
case when r = 1 then id end as id1, case when r = 1 then val end as val1,
case when r = 2 then id end as id2, case when r = 2 then val end as val2,
case when r = 3 then id end as id3, case when r = 3 then val end as val3
from (
select key ||'_'|| listagg(id ||':' ||val, '_')
within group (order by id) over (partition by key) as unique_id,
key, id, val,
row_number() over (partition by key order by id) as r
from foo
)
)
group by unique_id, key
order by key;
UNIQUE_ID KEY ID1 VAL1 ID2 VAL2 ID3 VAL3
----------------- ---- ---- ---- ---- ---- ---- ----
47_1:97_2:98_3:99 47 1 97 2 98 3 99
48_4:100_5:101 48 4 100 5 101
49_6:102 49 6 102
Can't help feeling there ought to be a simpler way though...