How to merge two versioned tables? - sql

There are two tables:
Table1(id, p1, p2, date_from, date_to):
(1, 'z', 55, '01.05.2010 12:30:20', '17.05.2010 13:10:14'),
(1, 'c', null, '17.05.2010 13:10:15', '18.01.2010 04:13:15'),
(1, 'c', 25, '18.01.2010 04:13:16', '01.01.9999 00:00:00');
Table2(id, p3, date_from, date_to):
(1, 15, '01.04.2010 12:30:20', '02.05.2010 13:10:14'),
(1, 35, '02.05.2010 13:10:15', '01.01.9999 00:00:00');
It is necessary that when merging 2 tables, there are fields containing the history of changes to each parameter. That is:
Select(id, p1, p2, p3, date_from, date_to):
(1, null, null, 10, '01.04.2010 12:30:20', '01.05.2010 12:30:19'),
(1, 'z', 55, 15, '01.05.2010 12:30:20', '02.05.2010 13:10:14'),
(1, 'z', 55, 35, '02.05.2010 13:10:15', '17.05.2010 13:10:14'),
(1, 'c', null, 35, '17.05.2010 13:10:15', '18.01.2010 04:13:15'),
(1, 'c', 25, 35, '18.01.2010 04:13:16', '01.01.9999 00:00:00');
Where records from two tables do not overlap in time(or part of time), null should be output .
So far I have done:
select
a.id,
case
when a.date_to between b.date_from and b.date_to
or a.date_from between b.date_from and b.date_to
then p1
end as p1,
case
when a.date_to between b.date_from and b.date_to
or a.date_from between b.date_from and b.date_to
then p2
end as p2,
p3,
a.date_from as af,
b.date_from as bf,
a.date_to as at,
b.date_to as bt
from
Table1 a
left join Table2 b
on
a.id=b.id
and (b.date_from between a.date_from and a.date_to
or a.date_from between b.date_from and b.date_to)

You can get your desired result by first computing all the ranges for each id value using a sequence of CTEs:
WITH dates_from AS (
SELECT id, date_from FROM Table1
UNION
SELECT id, date_to + interval '1 second' FROM Table1
UNION
SELECT id, date_from FROM Table2
UNION
SELECT id, date_to + interval '1 second' FROM Table2
),
dates_to AS (
SELECT id, date_to FROM Table1
UNION
SELECT id, date_from - interval '1 second' FROM Table1
UNION
SELECT id, date_to FROM Table2
UNION
SELECT id, date_from - interval '1 second' FROM Table2
),
ranges AS (
SELECT df.id, date_from, MIN(date_to) AS date_to
FROM dates_from df
JOIN dates_to dt ON dt.id = df.id AND dt.date_to > df.date_from
GROUP BY df.id, date_from
)
SELECT *
FROM ranges
For your sample data (with an adjustment to make the to date in row 2 of Table1 greater than the from date), this gives:
id date_from date_to
1 2010-05-17 13:10:15 2011-01-18 04:13:15
1 2010-05-01 12:30:20 2010-05-02 13:10:14
1 2010-04-01 12:30:20 2010-05-01 12:30:19
1 2010-05-02 13:10:15 2010-05-17 13:10:14
1 2011-01-18 04:13:16 9999-01-01 00:00:00
This table can then be LEFT JOINed to both tables (on overlapping ranges) to extract the appropriate p1, p2 and p3 values for each range:
SELECT r.id,
t1.p1, t1.p2, t2.p3,
r.date_from, r.date_to
FROM ranges r
LEFT JOIN Table1 t1 ON t1.id = r.id AND t1.date_from <= r.date_to and t1.date_to >= r.date_from
LEFT JOIN Table2 t2 ON t2.id = r.id AND t2.date_from <= r.date_to and t2.date_to >= r.date_from
ORDER BY r.id, r.date_from
Output:
id p1 p2 p3 date_from date_to
1 15 2010-04-01 12:30:20 2010-05-01 12:30:19
1 z 55 15 2010-05-01 12:30:20 2010-05-02 13:10:14
1 z 55 35 2010-05-02 13:10:15 2010-05-17 13:10:14
1 c 35 2010-05-17 13:10:15 2011-01-18 04:13:15
1 c 25 35 2011-01-18 04:13:16 9999-01-01 00:00:00
Demo (including values for a second id) on dbfiddle

Try this way :
select COALESCE(t1.id,t2.id) as id,
t1.p1,t1.p2,t2.p3,
COALESCE(t1.date_from,t2.date_from) as date_from,
COALESCE(t1.date_to,t2.date_to) as date_to
from Table1 t1
FULL OUTER JOIN Table2 t2 ON t1.id = t2.id and t1.date_from= t2.date_from and t1.date_to= t2.date_to;

Related

Trying to include rows with a Full Outer Join & Comparing in the same table

I'm attempting to return the First person to check in in each Room_id by joining the PERSON and CHECK_IN tables
http://sqlfiddle.com/#!17/35d930 > Select PostGreSQL 9.6 > Build Schema > Paste Query
CREATE TABLE person
("id" int)
;
INSERT INTO person
("id")
VALUES
(1),
(2),
(3),
(4),
(5),
(6)
;
CREATE TABLE check_in
("id" int, "person_id" int, "room_id" int, "check_in_date" timestamp, "check_out_date" timestamp)
;
INSERT INTO check_in
("id", "person_id", "room_id", "check_in_date", "check_out_date")
VALUES
(100, 1, 202, '2020-10-01 00:00:00', '2021-09-05 00:00:00'),
(101, 2, 201, '2020-12-15 00:00:00', '2021-02-15 00:00:00'),
(104, 3, 204, '2021-05-20 00:00:00', '2021-07-04 00:00:00'),
(106, 4, 202, '2022-08-01 00:00:00', NULL),
(108, 3, 204, '2021-08-15 00:00:00', NULL)
;
select c1.person_id, c1.room_id, c1.check_in_date
from check_in c1
FULL OUTER JOIN check_in c2 on c2.room_id = c1.room_id
where c1.check_in_date < c2.check_in_date
order by c1.room_id
I'm returning room_ids 202 and 204, but cannot get the select to return 201.. Should I not be using a full outer join?
We don't need to join the person table as we have all the info we need in the check_in table.
select id
,person_id
,room_id
,check_in_date
,check_out_date
from (
select *
,row_number() over(partition by room_id order by check_in_date desc) as rn
from check_in
) t
where rn = 1
id
person_id
room_id
check_in_date
check_out_date
101
2
201
2020-12-15 00:00:00
2021-02-15 00:00:00
106
4
202
2022-08-01 00:00:00
null
108
3
204
2021-08-15 00:00:00
null
Fiddle
Your where condition transforms the outer join into an inner one. See the answer with window function above as well
select c1.person_id, c1.room_id, c1.check_in_date
from check_in c1
where c1.check_in_date = (select min(c2.check_in_date) from check_in c2
where c2.room_id = c1.room_id )
order by c1.room_id
select c1.person_id, c1.room_id, c1.check_in_date
from check_in as c1
where c1.check_in_date in (select min(check_in_date)
from check_in as c2
join person as p
on p.id = c2.person_id
group by c2.room_id)
group by 2,1,3
order by c1.room_id

Denormalize column

I have data in my database like this:
Code
meta
meta_ID
date
A
1,2
1
01/01/2022 08:08:08
B
1,2
2
01/01/2022 02:00:00
B
null
2
01/01/1900 02:00:00
C
null
3
01/01/2022 02:00:00
D
8
8
01/01/2022 02:00:00
E
5,6,7
5
01/01/2022 02:00:00
F
1,2
2
01/01/2022 02:00:00
I want to have this with the last date (comparing with day, month year)
Code
meta
meta_ID
list_Code
date
A
2,3
1
A,B,F
01/01/2022 08:08:08
B
1,3
2
A,B,F
01/01/2022 02:00:00
C
null
3
C
01/01/2022 02:00:00
D
8
8
D
01/01/2022 02:00:00
E
5,6,7
5
E
01/01/2022 02:00:00
F
1,2
3
A,B,F
01/01/2022 02:00:00
I want to have the list of code having the same meta group, do you know how to do it with SQL Server?
The code below inputs the 1st table and outputs the 2nd table exactly. The Meta and Date columns had duplicate values, so in the CTE I took the MAX for both fields. Different logic can be applied if needed.
It uses XML Path to merge all rows into one column to create the List_Code column. The Stuff function removes the leading comma (,) delimiter.
CREATE TABLE MetaTable
(
Code VARCHAR(5),
Meta VARCHAR(100),
Meta_ID INT,
Date DATETIME
)
GO
INSERT INTO MetaTable
VALUES
('A', '1,2', '1', '01/01/2022 08:08:08'),
('B', '1,2','2', '01/01/2022 02:00:00'),
('B', NULL,'2', '01/01/1900 02:00:00'),
('C', NULL,'3', '01/01/2022 02:00:00'),
('D', '8','8', '01/01/2022 02:00:00'),
('E', '5,6,7', '5', '01/01/2022 02:00:00'),
('F', '1,2','2', '01/01/2022 02:00:00')
GO
WITH CTE_Meta
AS
(
SELECT
Code,
MAX(Meta) AS 'Meta',
Meta_ID,
MAX(Date) AS 'Date'
FROM MetaTable
GROUP BY
Code,
Meta_ID
)
SELECT
T1.Code,
T1.Meta,
T1.Meta_ID,
STUFF
(
(
SELECT ',' + Code
FROM CTE_Meta T2
WHERE ISNULL(T1.Meta, '') = ISNULL(T2.Meta, '')
FOR XML PATH('')
), 1, 1, ''
) AS 'List_Code',
T1.Date
FROM CTE_Meta T1
ORDER BY 1
I like the first answer using XML. It's very concise. This is more verbose, but might be more flexible if the data can have different meta values spread about in different records. The CAST to varchar(12) in various places is just for the display. I use STRING_AGG and STRING_SPLIT instead of XML.
WITH TestData as (
SELECT t.*
FROM (
Values
('A', '1,2', '1', '01/01/2022 08:08:08'),
('B', '1,2', '2', '01/01/2022 02:00:00'),
('B', null, '2', '01/01/1900 02:00:00'),
('C', null, '3', '01/01/2022 02:00:00'),
('D', '8', '8', '01/01/2022 02:00:00'),
('E', '5,6,7', '5', '01/01/2022 02:00:00'),
('F', '1,2', '2', '01/01/2022 02:00:00'),
('G', '16', '17', '01/01/2022 02:00:00'),
('G', null, '17', '01/02/2022 03:00:00'),
('G', '19', '18', '01/03/2022 04:00:00'),
('G', '19', '18', '01/03/2022 04:00:00'),
('G', '20', '19', '01/04/2022 05:00:00'),
('G', '20', '20', '01/05/2022 06:00:00')
) t (Code, meta, meta_ID, date)
), CodeLookup as ( -- used to find the Code from the meta_ID
SELECT DISTINCT meta_ID, Code
FROM TestData
), Normalized as ( -- split out the meta values, one per row
SELECT t.Code, s.Value as [meta], meta_ID, [date]
FROM TestData t
OUTER APPLY STRING_SPLIT(t.meta, ',') s
), MetaLookup as ( -- used to find the distinct list of meta values for a Code
SELECT n.Code, CAST(STRING_AGG(n.meta, ',') WITHIN GROUP ( ORDER BY n.meta ASC ) as varchar(12)) as [meta]
FROM (
SELECT DISTINCT Code, meta
FROM Normalized
WHERE meta is not NULL
) n
GROUP BY n.Code
), MetaIdLookup as ( -- used to find the distinct list of meta_ID values for a Code
SELECT n.Code, CAST(STRING_AGG(n.meta_ID, ',') WITHIN GROUP ( ORDER BY n.meta_ID ASC ) as varchar(12)) as [meta_ID]
FROM (
SELECT DISTINCT Code, meta_ID
FROM Normalized
) n
GROUP BY n.Code
), ListCodeLookup as ( -- for every code, get all codes for the meta values
SELECT l.Code, CAST(STRING_AGG(l.lookupCode, ',') WITHIN GROUP ( ORDER BY l.lookupCode ASC ) as varchar(12)) as [list_Code]
FROM (
SELECT DISTINCT n.Code, c.Code as [lookupCode]
FROM Normalized n
INNER JOIN CodeLookup c
ON c.meta_ID = n.meta
UNION -- every record needs it's own code in the list_code?
SELECT DISTINCT n.Code, n.Code as [lookupCode]
FROM Normalized n
) l
GROUP BY l.Code
)
SELECT t.Code, m.meta, mi.meta_ID, lc.list_Code, t.[date]
FROM (
SELECT Code, MAX([date]) as [date]
FROM TestData
GROUP BY Code
) t
LEFT JOIN MetaLookup m
ON m.Code = t.Code
LEFT JOIN MetaIdLookup mi
ON mi.Code = t.Code
LEFT JOIN ListCodeLookup lc
ON lc.Code = t.Code
Code meta meta_ID list_Code date
---- ------------ ------------ ------------ -------------------
A 1,2 1 A,B,F 01/01/2022 08:08:08
B 1,2 2 A,B,F 01/01/2022 02:00:00
C NULL 3 C 01/01/2022 02:00:00
D 8 8 D 01/01/2022 02:00:00
E 5,6,7 5 E 01/01/2022 02:00:00
F 1,2 2 A,B,F 01/01/2022 02:00:00
G 16,19,20 17,18,19,20 G 01/05/2022 06:00:00

How do I do this ? Time interval

there is a table.
(1, 'b', '2010-01-01 00:00:00', '2020-01-01 00:00:00'),
(1, 'z', '2010-02-01 00:00:00', '2015-01-01 00:00:00'),
How to do this:
(1, 'b', '2010-01-01 00:00:00', '2010-01-31 23:59:59'),
(1, 'z', '2010-02-01 00:00:00', '2015-01-01 00:00:00'),
(1, 'b', '2015-01-01 00:00:01', '2020-01-01 00:00:00');
You can do it this way:
I ddint add the part when you take away a second from the enddate or add a second to the fromdate as I didnt see the logic there
with cte as
(
select 1 as a, 'b' as b, cast('2010-01-01 00:00:00'as date) as start_, cast('2020-01-01 00:00:00'as date) as end_
union select 1, 'z', '2010-02-01 00:00:00', '2015-01-01 00:00:00'
),
cte2 as
(
select start_ as date_ from cte union select end_ from cte
),
cte3 as
(
select a, b, date_ from cte2 a inner join cte b on date_ between start_ and end_
),
final as
(
select a.a, a.b, a.date_ as startdate,
case when a.b = lead(a.b)over(order by a.date_) then lead(a.date_)over(order by a.date_) end as enddate
from cte3 a
)
select * from final where enddate is not null order by startdate
Output:
a b startdate enddate
1 b 2010-01-01 2010-02-01
1 z 2010-02-01 2015-01-01
1 b 2015-01-01 2020-01-01

Query Optimization to reduce multiple joins statements

Here is the table:
CREATE TABLE ABC
(
key NUMBER(5),
orders NUMBER(5),
cost NUMBER(5),
dat DATE
);
insert into ABC (key, orders, cost, dat) values (1, 3, 5, to_date('10-11-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (1, 5, 2, to_date('02-10-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (1, 6, 1, to_date('03-10-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (1, 7, 2, to_date('05-10-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (1, 8, 3, to_date('07-10-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (1, 3, 4, to_date('08-10-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (2, 3, 6, to_date('02-10-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (2, 3, 9, to_date('01-10-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (2, 2 ,5, to_date('03-10-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (2, 3, 2, to_date('05-10-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (2, 1, 1, to_date('06-10-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (3, 4, 12, to_date('10-10-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (3, 3, 9, to_date('01-10-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (3, 2 ,5, to_date('05-10-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (3, 3, 2, to_date('06-10-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (3, 1, 1, to_date('07-10-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (3, 4, 12, to_date('11-10-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost, dat) values (1, 3, 5, to_date('10-01-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (1, 5, 2, to_date('02-17-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (1, 6, 1, to_date('03-18-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (1, 7, 2, to_date('05-14-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (1, 8, 3, to_date('07-13-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (1, 3, 4, to_date('08-12-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (2, 3, 6, to_date('02-11-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (2, 3, 9, to_date('01-15-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (2, 2 ,5, to_date('03-14-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (2, 3, 2, to_date('05-18-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (2, 1, 1, to_date('06-19-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (3, 4, 12, to_date('10-11-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (3, 3, 9, to_date('01-12-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (3, 2 ,5, to_date('05-16-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (3, 3, 2, to_date('06-17-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (3, 1, 1, to_date('07-12-
2017', 'mm-dd-yyyy'));
insert into ABC (key, orders, cost,dat) values (3, 4, 12, to_date('12-21-
2017', 'mm-dd-yyyy'));
Not sure why my results are repeating.
Here is my query:
with qone as
(select a.key, a.max_price, max(t.dat) as qo_dat from ABC t
JOIN
(select key, max(cost) as max_price from ABC
where dat >= to_date('01-01-2017', 'mm-dd-yyyy') and dat < to_date('04-01-
2017', 'mm-dd-yyyy')
group by key) a on a.key = t.key and a.max_price = t.cost
group by a.key, a.max_price),
qtwo as
(select a.key, a.max_price, max(t.dat) as qt_dat from ABC t
JOIN
(select key, max(cost) as max_price from ABC
where dat >= to_date('04-01-2017', 'mm-dd-yyyy') and dat < to_date('07-01-
2017', 'mm-dd-yyyy')
group by key) a on a.key = t.key and a.max_price = t.cost
group by a.key, a.max_price),
qthree as
(select a.key, a.max_price, max(t.dat) as qth_dat from ABC t
JOIN
(select key, max(cost) as max_price from ABC
where dat >= to_date('07-01-2017', 'mm-dd-yyyy') and dat < to_date('10-01-
2017', 'mm-dd-yyyy')
group by key) a on a.key = t.key and a.max_price = t.cost
group by a.key, a.max_price),
qfour as
(select a.key, a.max_price, max(t.dat) as qf_dat from ABC t
JOIN
(select key, max(cost) as max_price from ABC
where dat >= to_date('10-01-2017', 'mm-dd-yyyy') and dat < to_date('01-01-
2018', 'mm-dd-yyyy')
group by key) a on a.key = t.key and a.max_price = t.cost
group by a.key, a.max_price)
select qo.key, qo.max_price as max_q1, qo.qo_dat, qt.max_price as max_q2,
qt.qt_dat, qth.max_price as max_q3, qth.qth_dat, qf.max_price as max_q4,
qf.qf_dat from qone qo
join qtwo qt on qt.key = qo.key
join qthree qth on qth.key = qth.key
join qfour qf on qf.key = qf.key
order by keyenter code here
I want to know if there is a way to reduce the lines.
How I did it? I find the Max Price and Max Date for each quarter, I define the quarters using where statement.
I use Divide and Conquer technique, I find the Max price and the respective date for all four quarters and join them on the key. Sample of one self-defined quarter below.
`select a.key, a.max_price, max(t.dat) as qo_dat from ABC t
JOIN
(select key, max(cost) as max_price from ABC
where dat >= to_date('01-01-2017', 'mm-dd-yyyy') and dat < to_date('04-01-
2017', 'mm-dd-yyyy')
group by key) a on a.key = t.key and a.max_price = t.cost
group by a.key, a.max_price`
Output:
Possible optimized solution: but I m figuring out a way to add a corresponding date next to it
select
t.key,
max( case when t.dat >= Tmp.Q1From and t.dat < Tmp.Q1End then t.cost
else 0 end ) as Q1Tot,
max( case when t.dat >= Tmp.Q1End and t.dat < Tmp.Q2End then t.cost else
0 end ) as Q2Tot,
max( case when t.dat >= Tmp.Q2End and t.dat < Tmp.Q3End then t.cost else
0 end ) as Q3Tot,
max( case when t.dat >= Tmp.Q3End and t.dat < Tmp.Q4End then t.cost else
0 end ) as Q4Tot
from
ABC t,
( select
to_date('01-01-2017', 'mm-dd-yyyy') Q1From,
to_date('04-01-2017', 'mm-dd-yyyy') Q1End,
to_date('07-01-2017', 'mm-dd-yyyy') Q2End,
to_date('10-01-2017', 'mm-dd-yyyy') Q3End,
to_date('01-01-2018', 'mm-dd-yyyy') Q4End
from
dual ) Tmp
where
t.dat >= to_date('01-01-2017', 'mm-dd-yyyy')
and t.dat < to_date('01-01-2018', 'mm-dd-yyyy')
group by
t.key
Instead of using JOINs or cross joins, consider using the analytical function NTH_VALUE (see documentation) for displaying the required values for the 4 quarters side by side.
NTH_VALUE returns the measure_expr value of the nth row in the window
defined by the analytic_clause.
First step: find the "max costs" and their corresponding dates for all keys (and quarters).
select *
from (
select key, dat, to_char( dat, 'Q' ) quarter
, max( cost ) over ( partition by key, to_char( dat, 'Q' ) order by cost desc ) maxcost_
, max( dat ) over ( partition by key, to_char( dat, 'Q' ) order by cost desc ) maxdat_
, row_number() over ( partition by key, to_char( dat, 'Q' ) order by cost desc ) rownum_
from abc
)
where rownum_ = 1
-- result
KEY DAT QUARTER MAXCOST_ MAXDAT_ ROWNUM_
1 17-FEB-17 1 2 17-FEB-17 1
1 14-MAY-17 2 2 14-MAY-17 1
1 12-AUG-17 3 4 12-AUG-17 1
1 01-OCT-17 4 5 11-OCT-17 1
2 10-JAN-17 1 9 15-JAN-17 1
2 10-MAY-17 2 2 18-MAY-17 1
3 10-JAN-17 1 9 12-JAN-17 1
3 10-MAY-17 2 5 16-MAY-17 1
3 10-JUL-17 3 1 12-JUL-17 1
3 10-NOV-17 4 12 21-DEC-17 1
10 rows selected.
Final query: use the first query as an INLINE VIEW, and call NTH_VALUE for retrieving the values for each quarter.
select unique key
, nth_value( maxcost_, 1 ) from first over ( partition by key ) q1max
, nth_value( maxdat_, 1 ) from first over ( partition by key ) q1date
, nth_value( maxcost_, 2 ) from first over ( partition by key ) q2max
, nth_value( maxdat_, 2 ) from first over ( partition by key ) q2date
, nth_value( maxcost_, 3 ) from first over ( partition by key ) q3max
, nth_value( maxdat_, 3 ) from first over ( partition by key ) q3date
, nth_value( maxcost_, 4 ) from first over ( partition by key ) q4max
, nth_value( maxdat_, 4 ) from first over ( partition by key ) q4date
from (
select *
from (
select key, dat, to_char( dat, 'Q' ) quarter
, max( cost ) over ( partition by key, to_char( dat, 'Q' ) order by cost desc ) maxcost_
, max( dat ) over ( partition by key, to_char( dat, 'Q' ) order by cost desc ) maxdat_
, row_number() over ( partition by key, to_char( dat, 'Q' ) order by cost desc ) rownum_
from abc
)
where rownum_ = 1
) -- inline view (no name required)
order by key
;
-- result
KEY Q1MAX Q1DATE Q2MAX Q2DATE Q3MAX Q3DATE Q4MAX Q4DATE
1 2 17-FEB-17 2 14-MAY-17 4 12-AUG-17 5 11-OCT-17
2 9 15-JAN-17 2 18-MAY-17 NULL NULL NULL NULL
3 9 12-JAN-17 5 16-MAY-17 1 12-JUL-17 12 21-DEC-17
Maybe you could rewrite it in a shorter way, as in SQL Fiddle:
select a.key, qtr, a.max_price, max(t.dat) as qo_dat
from ABC t
join (
select key, to_char(dat, 'Q') as qtr, max(cost) as max_price
from ABC
where dat >= to_date('01-01-2017', 'mm-dd-yyyy')
and dat < to_date('01-01-2018', 'mm-dd-yyyy')
group by key, to_char(dat, 'Q')
) a on a.key = t.key and a.max_price = t.cost and a.qtr = to_char(t.dat, 'Q')
group by a.key, a.qtr, a.max_price
order by a.key, a.qtr, a.max_price
The output is a little bit different, but it shows what you want. Doesn't it?
select a.key, a.q1tot, b.dat, a.q2tot, c.dat, a.q3tot, d.dat, a.q4tot, e.dat from (
select
t.key,
max( case when t.dat >= Tmp.Q1From and t.dat < Tmp.Q1End then t.cost else 0 end ) as Q1Tot,
max( case when t.dat >= Tmp.Q1End and t.dat < Tmp.Q2End then t.cost else 0 end ) as Q2Tot,
max( case when t.dat >= Tmp.Q2End and t.dat < Tmp.Q3End then t.cost else 0 end ) as Q3Tot,
max( case when t.dat >= Tmp.Q3End and t.dat < Tmp.Q4End then t.cost else 0 end ) as Q4Tot
from
ABC t,
( select
to_date('01-01-2017', 'mm-dd-yyyy') Q1From,
to_date('04-01-2017', 'mm-dd-yyyy') Q1End,
to_date('07-01-2017', 'mm-dd-yyyy') Q2End,
to_date('10-01-2017', 'mm-dd-yyyy') Q3End,
to_date('01-01-2018', 'mm-dd-yyyy') Q4End
from
dual ) Tmp
where
t.dat >= to_date('01-01-2017', 'mm-dd-yyyy')
and t.dat < to_date('01-01-2018', 'mm-dd-yyyy')
group by
t.key) a
join
( select key, cost, dat from ABC
where dat < to_date('04-01-2017', 'mm-dd-yyyy')) b
on a.key = b.key and a.Q1tot = b.cost
join
( select key, cost, dat from ABC
where dat >= to_date('04-01-2017', 'mm-dd-yyyy') and dat < to_date('07-01-2017',
'mm-dd-yyyy')) c
on a.key = c.key and a.Q1tot = c.cost
join
( select key, cost, dat from ABC
where dat >= to_date('07-01-2017', 'mm-dd-yyyy') and dat < to_date('10-01-2017',
'mm-dd-yyyy')) d
on a.key = d.key and a.Q1tot = d.cost
join
( select key, cost, dat from ABC
where dat >= to_date('10-01-2017', 'mm-dd-yyyy') and dat < to_date('01-01-2018', 'mm-dd-yyyy')) e
on a.key = e.key and a.Q1tot = e.cost
This is my code, But the above two queries perform faster

How to fetch records that have an alternate entry

I need some help to fetch records having alternate set of entries associated with Unique value(ex: user_id)
I want output to be only (1111,2222,3333)
Here is the scenario:
user_id 1111 attended .net course from 2005-01-01 to 2006-12-31
he later attended java from 2007-01-01 to 2009-12-31
he later came back to .net
so i want to retrieve these kind of user_id's
user_id 4444 should not be in the output, because there is no alternative courses.
UPDATE: 4444 started his Java course from 2007 to 2009 he again
attended Java from 2010 - 2012 Later he attended .net but never came
back to Java so he must be excluded from output
If Group by is used, it will consider records irrespective of alternate course name.
We can create a procedure to accomplish this by looping and comparing the alternate course name but i want to know if a query can do this?
You can use two INNER JOIN operations:
SELECT DISTINCT user_id
FROM mytable AS t1
INNER JOIN mytable AS t2
ON t1.user_id = t2.user_id AND t1.id < t2.id AND t1.course_name <> t2.course_name
INNER JOIN mytable AS t3
ON t2.user_id = t3.user_id AND t2.id < t3.id AND t1.course_name = t3.course_name
I assume that id is an auto-increment field that reflects the order the rows have been inserted in the DB. Otherwise, you should use a date field in its place.
Same as Girogos Betsos' answer, only with select distinct to prevent duplicates.
SELECT DISTINCT user_id
FROM mytable AS t1
INNER JOIN mytable AS t2
ON t1.user_id = t2.user_id AND t1.Start_Date < t2.Start_Date AND
t1.course_name <> t2.course_name
INNER JOIN mytable AS t3
ON t2.user_id = t3.user_id AND t2.Start_Date < t3.Start_Date AND
t1.course_name = t3.course_name
EDIT: Using Start_Date since the answer has been updated and IDs are not necessarily sequential.
This is a version utilizing Windowed Aggregate Fuctions instead of multiple self joins:
SELECT DISTINCT user_id
FROM
(
SELECT user_id
,course_name
,start_date
,RANK() -- number all courses
OVER (PARTITION BY user_id
ORDER BY start_date)
-
RANK() -- number each course
OVER (PARTITION BY user_id, course_name
ORDER BY start_date) AS x
FROM tab
) dt
GROUP BY user_id, course_name
HAVING MIN(x) <> MAX(x) -- same course but another inbetween
If a user has a course multiple times in a series that x will stay the same, if there was another course inbetween it will change:
java 1 - 1 = 0
java 2 - 2 = 0 <--- min
.net 3 - 1 = 2
java 4 - 3 = 1 <--- max
java 1 - 1 = 0
java 2 - 2 = 0
.net 3 - 1 = 2
.net 4 - 2 = 2
Using a single table scan and does not rely on GROUP BY:
WITH table_name ( user_id, start_date, end_date, course_name, id ) AS (
SELECT 1111, DATE '2005-01-01', DATE '2006-12-31', '.net', 1 FROM DUAL UNION ALL
SELECT 1111, DATE '2007-01-01', DATE '2009-12-31', 'java', 2 FROM DUAL UNION ALL
SELECT 1111, DATE '2010-01-01', DATE '2020-12-31', '.net', 3 FROM DUAL UNION ALL
SELECT 2222, DATE '2005-01-01', DATE '2006-12-31', 'java', 4 FROM DUAL UNION ALL
SELECT 2222, DATE '2007-01-01', DATE '2008-12-31', '.net', 5 FROM DUAL UNION ALL
SELECT 2222, DATE '2009-01-01', DATE '2012-12-31', '.net', 6 FROM DUAL UNION ALL
SELECT 2222, DATE '2013-01-01', DATE '2016-12-31', 'java', 7 FROM DUAL UNION ALL
SELECT 3333, DATE '2005-01-01', DATE '2007-12-31', 'java', 8 FROM DUAL UNION ALL
SELECT 3333, DATE '2007-01-01', DATE '2008-12-31', '.net', 9 FROM DUAL UNION ALL
SELECT 3333, DATE '2009-01-01', DATE '2013-12-31', 'java', 10 FROM DUAL UNION ALL
SELECT 3333, DATE '2014-01-01', DATE '2016-12-31', '.net', 11 FROM DUAL UNION ALL
SELECT 4444, DATE '2007-01-01', DATE '2009-12-31', 'java', 12 FROM DUAL UNION ALL
SELECT 4444, DATE '2010-01-01', DATE '2012-12-31', 'java', 13 FROM DUAL UNION ALL
SELECT 4444, DATE '2013-01-01', DATE '2015-12-31', '.net', 14 FROM DUAL UNION ALL
SELECT 4444, DATE '2016-01-01', DATE '2016-12-31', '.net', 15 FROM DUAL
)
SELECT DISTINCT user_id
FROM (
SELECT user_id,
LEAD( course_name )
OVER ( PARTITION BY user_id, course_name ORDER BY start_date )
AS next_same_course,
LEAD( course_name )
OVER ( PARTITION BY user_id ORDER BY start_date )
AS next_course
FROM table_name
)
WHERE next_same_course IS NOT NULL
AND next_course <> next_same_course;