oracle query to obtain all major version segmented message values - sql

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

Related

How to combine two tables in Oracle SQL on a quantitative base

beacause of a really old db design I need some help. This might be quite simple I'm just not seeing the wood for the trees at the moment.
TABLE A:
ID
1
2
3
4
5
TABLE B:
ID
VALUE B
1
10
1
20
2
10
2
20
3
10
3
20
3
30
4
10
TABLE C:
ID
VALUE C
1
11
1
21
2
11
2
21
2
31
3
11
5
11
Expected result:
where ID = 1
ID
VALUE B
VALUE C
1
10
11
1
20
21
where ID = 2
ID
VALUE B
VALUE C
2
10
11
2
20
21
2
null
31
where ID = 3
ID
VALUE B
VALUE C
3
10
11
3
20
null
3
30
null
where ID = 4
ID
VALUE B
VALUE C
4
10
null
where ID = 5
ID
VALUE B
VALUE C
5
null
11
The entries in table B and C are optional and could be unlimited, the ID from table A is the connection.
B and C are not directly connected. I need a quantitative comparision to find gaps in the database. The number of entries of table B and C should be the same (but not the value), usually entries are missing in either B or C.
I tried it with outer joins but I'm getting two much rows, because I need B or C join only one time per single row.
I hope anybody understand my problem and can help me.
It looks like, for each distinct ID, you want the nth row (ordered by VALUE) from TABLE_A to match with the nth row from TABLE_B. And if one table - A or B - has more values, you want those to match to null.
Your solution will have two parts. First, use row_number() over ( partition by id order by value) to order the rows in both tables. Then, use FULL OUTER JOIN to join on (id, rownumber).
Here is a full example:
-- WITH clauses are just test data...+
with table_a (id) as (
SELECT 1 FROM DUAL UNION ALL
SELECT 2 FROM DUAL UNION ALL
SELECT 3 FROM DUAL UNION ALL
SELECT 4 FROM DUAL UNION ALL
SELECT 5 FROM DUAL ),
table_b (id, value) as (
SELECT 1,10 FROM DUAL UNION ALL
SELECT 1,20 FROM DUAL UNION ALL
SELECT 2,10 FROM DUAL UNION ALL
SELECT 2,20 FROM DUAL UNION ALL
SELECT 3,10 FROM DUAL UNION ALL
SELECT 3,20 FROM DUAL UNION ALL
SELECT 3,30 FROM DUAL UNION ALL
SELECT 4,10 FROM DUAL ),
table_c (id, value) as (
SELECT 1,11 FROM DUAL UNION ALL
SELECT 1,21 FROM DUAL UNION ALL
SELECT 2,11 FROM DUAL UNION ALL
SELECT 2,21 FROM DUAL UNION ALL
SELECT 2,31 FROM DUAL UNION ALL
SELECT 3,11 FROM DUAL UNION ALL
SELECT 5,11 FROM DUAL )
-- Solution begins here
SELECT id, b.value b_value, c.value c_value
FROM ( SELECT b.*,
row_number() OVER ( PARTITION BY b.id ORDER BY b.value ) rn
FROM table_b b ) b
FULL OUTER JOIN ( SELECT c.*,
row_number() OVER ( PARTITION BY c.id ORDER BY c.value ) rn
FROM table_c c ) c USING (id, rn)
ORDER BY id, b_value, c_value;
+----+---------+---------+
| ID | B_VALUE | C_VALUE |
+----+---------+---------+
| 1 | 10 | 11 |
| 1 | 20 | 21 |
| 2 | 10 | 11 |
| 2 | 20 | 21 |
| 2 | | 31 |
| 3 | 10 | 11 |
| 3 | 20 | |
| 3 | 30 | |
| 4 | 10 | |
| 5 | | 11 |
+----+---------+---------+

Select rows when a value appears multiple times

I have a table like this one:
+------+------+
| ID | Cust |
+------+------+
| 1 | A |
| 1 | A |
| 1 | B |
| 1 | B |
| 2 | A |
| 2 | A |
| 2 | A |
| 2 | B |
| 3 | A |
| 3 | B |
| 3 | B |
+------+------+
I would like to get the IDs that have at least two times A and two times B. So in my example, the query should return only the ID 1,
Thanks!
In MySQL:
SELECT id
FROM test
GROUP BY id
HAVING GROUP_CONCAT(cust ORDER BY cust SEPARATOR '') LIKE '%aa%bb%'
In Oracle
WITH cte AS ( SELECT id, LISTAGG(cust, '') WITHIN GROUP (ORDER BY cust) custs
FROM test
GROUP BY id )
SELECT id
FROM cte
WHERE custs LIKE '%aa%bb%'
I would just use two levels of aggregation:
select id
from (select id, cust, count(*) as cnt
from t
where cust in ('A', 'B')
group by id, cust
) ic
group by id
having count(*) = 2 and -- both customers are in the result set
min(cnt) >= 2 -- and there are at least two instances
This is one option; lines #1 - 13 represent sample data. Query you might be interested in begins at line #14.
SQL> with test (id, cust) as
2 (select 1, 'a' from dual union all
3 select 1, 'a' from dual union all
4 select 1, 'b' from dual union all
5 select 1, 'b' from dual union all
6 select 2, 'a' from dual union all
7 select 2, 'a' from dual union all
8 select 2, 'a' from dual union all
9 select 2, 'b' from dual union all
10 select 3, 'a' from dual union all
11 select 3, 'b' from dual union all
12 select 3, 'b' from dual
13 )
14 select id
15 from (select
16 id,
17 sum(case when cust = 'a' then 1 else 0 end) suma,
18 sum(case when cust = 'b' then 1 else 0 end) sumb
19 from test
20 group by id
21 )
22 where suma = 2
23 and sumb = 2;
ID
----------
1
SQL>
You can use group by and having for the relevant Cust ('A' , 'B')
And query twice (I chose to use with to avoid multiple selects and to cache it)
with more_than_2 as
(
select Id, Cust, count(*) c
from tab
where Cust in ('A', 'B')
group by Id, Cust
having count(*) >= 2
)
select *
from tab
where exists ( select 1 from more_than_2 where more_than_2.Id = tab.Id and more_than_2.Cust = 'A')
and exists ( select 1 from more_than_2 where more_than_2.Id = tab.Id and more_than_2.Cust = 'B')
What you want is a perfect candidate for match_recognize. Here you go:
select id_ as id from t
match_recognize
(
order by id, cust
measures id as id_
pattern (A {2, } B {2, })
define A as cust = 'A',
B as cust = 'B'
)
Output:
Regards,
Ranagal

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

create window group based on value of preceding row

I have a table like so:
#standardSQL
WITH k AS (
SELECT 1 id, 1 subgrp, 'stuff1' content UNION ALL
SELECT 2, 2, 'stuff2' UNION ALL
SELECT 3, 3, 'stuff3' UNION ALL
SELECT 4, 4, 'stuff4' UNION ALL
SELECT 5, 1, 'ostuff1' UNION ALL
SELECT 6, 2, 'ostuff2' UNION ALL
SELECT 7, 3, 'ostuff3' UNION ALL
SELECT 8, 4, 'ostuff4'
)
and like to group based on the subgrp value to re-create the missing grp: if subgrp value is smaller than previous row, belongs to same group.
Intermediate result would be:
| id | grp | subgrp | content |
| 1 | 1 | 1 | stuff1 |
| 2 | 1 | 2 | stuff2 |
| 3 | 1 | 3 | stuff3 |
| 4 | 1 | 4 | stuff4 |
| 5 | 2 | 1 | ostuff1 |
| 6 | 2 | 2 | ostuff2 |
| 7 | 2 | 3 | ostuff3 |
| 8 | 2 | 4 | ostuff4 |
on which I can then apply
SELECT id, grp, ARRAY_AGG(STRUCT(subgrp, content)) rcd
FROM k ORDER BY id, grp
to have I nice nested structure.
Notes:
with 'id' ordered, subgrp is always in sequence so no 3 before 2
groups are not always 4 subgrp's - this is just to illustrate so cannot hardcode
Problem: how can I (re)create the grp column here ? I played with several Window functions to no avail.
EDIT
Although Gordon's answer work, it took 3min over 104M records to run and I had to remove an ORDER BY on the final resultset because of Resources exceeded during execution: The query could not be executed in the allotted memory. ORDER BY operator used too much memory.
Anyone having an alternative solution for large dataset ?
A simple way to assign the group is to do a cumulative count of the subgrp = 1 values:
select k.*,
sum(case when subgrp = 1 then 1 else 0 end) over (order by id) as grp
from k;
You can also do it your way, using lag() and a cumulative sum. That requires a subquery:
select k.*,
sum(case when prev_subgrp = subgrp then 0 else 1 end) over (order by id) as grp
from (select k.*,
lag(subgrp) over (order by id) as prev_subgrp
from k
) k
Below can potentially perform better - but has limitation - I assume there is no gaps in numbering within subgroups and respective ids
#standardSQL
WITH k AS (
SELECT 1 id, 1 subgrp, 'stuff1' content UNION ALL
SELECT 2, 2, 'stuff2' UNION ALL
SELECT 3, 3, 'stuff3' UNION ALL
SELECT 4, 4, 'stuff4' UNION ALL
SELECT 5, 1, 'ostuff1' UNION ALL
SELECT 6, 2, 'ostuff2' UNION ALL
SELECT 7, 3, 'ostuff3' UNION ALL
SELECT 8, 4, 'ostuff4'
)
SELECT
ROW_NUMBER() OVER(ORDER BY id) grp,
rcd
FROM (
SELECT
MIN(id) id,
ARRAY_AGG(STRUCT(subgrp, content)) rcd
FROM k
GROUP BY id - subgrp
)
result is
Row grp rcd.subgrp rcd.content
1 1 1 stuff1
2 stuff2
3 stuff3
4 stuff4
2 2 1 ostuff1
2 ostuff2
3 ostuff3
4 ostuff4

Difference between the values of multiple rows in 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'));