How to get min and max from 2 tables in SQL - sql

I am Trying to get start date from min ID (ID=1) and end date from max ID (ID=3) but i am not sure how i can retrieve. Following is my data -
Table1 and Table2 are source table. I am trying to get output like 3rd table.
My requirement is get start date from first record of ID and End Date from last record of ID, we can recognize first and and last record with the help of ID field. If ID is min means first record and ID is max then last record
Please help me!

Here's one option; presuming you use Oracle (regarding you use Oracle SQL Developer), the x inline view selects
start_date which belongs to name with the lowest ID column value for that name (i.e. first_value partition by name order by id)
end_date which belongs to name with the highest ID column value for that name (i.e. first_value partition by name order by id DESC)
SQL> with
2 -- sample data
3 t1 (pid, name) as
4 (select 123, 'xyz' from dual union all
5 select 234, 'pqr' from dual
6 ),
7 t2 (id, name, start_date, end_date) as
8 (select 1, 'xyz', date '2020-01-01', date '2020-07-20' from dual union all
9 select 2, 'xyz', date '2020-02-01', date '2020-05-30' from dual union all
10 select 3, 'xyz', date '2020-06-30', date '2020-07-30' from dual union all
11 --
12 select 1, 'pqr', date '2020-04-30', date '2020-09-30' from dual union all
13 select 2, 'pqr', date '2020-05-30', date '2020-09-30' from dual union all
14 select 3, 'pqr', date '2020-06-30', date '2020-07-01' from dual
15 )
16 select a.pid,
17 x.name,
18 max(x.start_date) start_date,
19 max(x.end_date) end_date
20 from t1 a join
21 (
22 -- start_date: always for the lowest T2.ID value row
23 -- end_date : always for the highest T2.ID value row
24 select b.name,
25 first_value(b.start_date) over (partition by b.name order by b.id ) start_date,
26 first_value(b.end_date) over (partition by b.name order by b.id desc) end_date
27 from t2 b
28 ) x
29 on a.name = x.name
30 group by a.pid,
31 x.name
32 order by a.pid;
PID NAME START_DATE END_DATE
---------- ---- ---------- ----------
123 xyz 01/01/2020 07/30/2020
234 pqr 04/30/2020 07/01/2020
SQL>

Related

How do you combine query results from different rows into one?

My original query:
SELECT desc, start_date
FROM foo.bar
WHERE desc LIKE 'Fall%' AND desc NOT LIKE '%Med%'
UNION
SELECT desc, end_date
FROM foo.bar
WHERE desc LIKE 'Spring%' AND desc NOT LIKE '%Med%'
ORDER BY start_date;
With this query, I get (roughly) the data set I am looking for. I now need to take that data and combine the results taking two at a time in order and then produce a result like:
DESC
START_DATE
END_DATE
Fall 1971 - Spring 1972
15-AUG-71
15-MAY-72
Fall 1971 - Spring 1972
15-AUG-72
15-MAY-73
Where DESC is a concatenation of the DESC form row 1 and 2, START_DATE is the date from row 1 and END_DATE is the date from row 2. Following this same pattern for the entire data set.
Any help with a query that will produce the result I need is greatly appreciated. Not sure if I'm heading down the right path or if that originally query is just wrong.
As stated above, I tried the supplied query, which gives me the data I need. However, I've been unsuccessful in finding a way to format it into my desired output. It should also be noted that I am running this on an Oracle database.
Instead of union, use each of those queries as CTEs (with a slight modification - include row number you'll later use in JOIN):
Sample data:
SQL> with test (description, datum) as
2 (select 'Fall 1971' , date '1971-08-15' from dual union all
3 select 'Spring 1972', date '1972-05-15' from dual union all
4 select 'Fall 1972' , date '1972-08-15' from dual union all
5 select 'Spring 1973', date '1973-05-15' from dual union all
6 select 'Fall 1973' , date '1973-08-15' from dual union all
7 select 'Spring 1974', date '1974-05-15' from dual union all
8 select 'Fall 1974' , date '1974-08-15' from dual union all
9 select 'Spring 1975', date '1975-05-15' from dual
10 ),
Query begins here: t_start and t_end represent your current queries
11 t_start as
12 (select description, datum,
13 row_number() Over (order by datum) rn
14 from test
15 where description like 'Fall%' and description not like '%Med%'
16 ),
17 t_end as
18 (select description, datum,
19 row_number() Over (order by datum) rn
20 from test
21 where description like 'Spring%' and description not like '%Med%'
22 )
Finally:
23 select s.description ||' - '|| e.description as description,
24 s.datum start_date,
25 e.datum end_date
26 from t_start s join t_end e on s.rn = e.rn
27 order by s.rn;
DESCRIPTION START_DAT END_DATE
------------------------- --------- ---------
Fall 1971 - Spring 1972 15-AUG-71 15-MAY-72
Fall 1972 - Spring 1973 15-AUG-72 15-MAY-73
Fall 1973 - Spring 1974 15-AUG-73 15-MAY-74
Fall 1974 - Spring 1975 15-AUG-74 15-MAY-75
SQL>
You can also use the MODEL clause to avoid to scan the table twice:
with data(description,datum) as (
select 'Fall 1971' , date '1971-08-15' from dual union all
select 'Spring 1972', date '1972-05-15' from dual union all
select 'Fall 1972' , date '1972-08-15' from dual union all
select 'Spring 1973', date '1973-05-15' from dual union all
select 'Fall 1973' , date '1973-08-15' from dual union all
select 'Spring 1974', date '1974-05-15' from dual union all
select 'Fall 1974' , date '1974-08-15' from dual union all
select 'Spring 1975', date '1975-05-15' from dual
)
select description, start_date, end_date
from (
select rn, desc1 as description, start_date, end_date
from (
select row_number() over(order by datum) as rn, description, datum
from data
where description not like '%Med%'
)
model
dimension by (rn)
measures (
cast(' ' as varchar2(256)) as desc1, description, cast(NULL as DATE) start_date, cast(NULL as DATE) end_date , datum
)
rules (
desc1[mod(rn,2)=1] = description[cv()] || ' - ' || description[cv()+1],
start_date[mod(rn,2)=1] = datum[cv()],
end_date[mod(rn,2)=1] = datum[cv()+1]
)
)
where mod(rn,2)=1
;

Execute a oracle pl/sql query and return the result set based on the run time date value

I have below data
empid date amount
1 12-FEB-2017 10
1 12-FEB-2017 10
1 13-FEB-2017 10
1 14-FEB-2017 10
I need a query to return the total amount for a given id and date i.e, below result set
empid date amount
1 12-FEB-2017 20
1 13-FEB-2017 10
1 14-FEB-2017 10
but the think is, from the UI i will be getting the date as input.. if they pass the date return the result for that date .. if they dont pass the date return the result for most recent date.
below is the query that I wrote .. but it is working partially..
SELECT sum(amount),empid,date
FROM employee emp,
where
((date= :ddd) OR aum_valutn_dt = (select max(date) from emp))
AND emp.id = '1'
group by (empid,date)
Please help..
I think you could do something like this
but it is pretty bad you should try to do it some other way
it is doing extra work to get the most recent date
select amt, empid, date
from
(
select amt, empid, date, rank() over (order by date desc) date_rank
from
(SELECT sum(amount) amt,empid,date
FROM employee emp
where emp.id = '1'
and (date = :ddd or :ddd is null)
group by empid, date)
)
where date = :ddd or (:ddd is null and date_rank=1)
Here's another option; scans TEST table twice so ... mind the performance.
SQL> with test (empid, datum, amount) as
2 (select 1, date '2017-02-12', 10 from dual union all
3 select 1, date '2017-02-12', 10 from dual union all
4 select 1, date '2017-02-13', 10 from dual union all
5 select 1, date '2017-02-14', 10 from dual
6 )
7 select t.empid, t.datum, sum(t.amount) sum_amount
8 from test t
9 where t.datum = (select max(t1.datum)
10 from test t1
11 where t1.empid = t.empid
12 and (t1.datum = to_date('&&par_datum', 'dd.mm.yyyy')
13 or '&&par_datum' is null)
14 )
15 group by t.empid, t.datum;
Enter value for par_datum: 13.02.2017
EMPID DATUM SUM_AMOUNT
---------- ---------- ----------
1 13.02.2017 10
SQL> undefine par_datum
SQL> /
Enter value for par_datum:
EMPID DATUM SUM_AMOUNT
---------- ---------- ----------
1 14.02.2017 10
SQL>
SELECT sum(amount),empid,date
FROM employee emp,
where date =nvl((:ddd ,(select max(date) from emp))
AND emp.id = '1'
group by (empid,date)
My solution is following:
with t (empid, datum, amount) as
(select 1, date '2017-02-12', 10 from dual union all
select 1, date '2017-02-12', 10 from dual union all
select 1, date '2017-02-13', 10 from dual union all
select 1, date '2017-02-14', 10 from dual
)
select empid, datum, s
from (select empid, datum, sum(amount) s, max(datum) over (partition by empid) md
from t
group by empid, datum)
where datum = nvl(to_date(:p, 'yyyy-mm-dd'), md);
Calculate maximal date in the subquery and then, in outer subquery, compare the date with nvl(to_date(:p, 'yyyy-mm-dd'), md). If the paremeter is null, then the date field is compared with maximal date.

Query to obtain start and end dates from table with only start dates

I have a table with dates and values, something like:
START_DATE VALUE
------------ -----
01-JAN-2015 A
01-MAR-2015 B
01-AUG-2015 (null)
15-AUG-2015 A
01-SEP-2015 C
01-JAN-2016 B
01-JUN-2016 C
Each start_date represents the date when the value changed.
I'm trying to obtain an output that includes the end date as the next date (in chronological order) minus one day, that is:
START_DATE END_DATE VALUE
---------- ---------- -----
01-JAN-2015 28-FEB-2015 A
01-MAR-2015 31-JUL-2015 B
01-AUG-2015 14-AUG-2015 (null)
15-AUG-2015 31-AUG-2015 A
01-SEP-2015 31-DEC-2015 C
01-JAN-2016 31-MAY-2016 B
01-JUN-2016 (null) C
Is there a query I can use to obtain the start and end date for each interval?... maybe using hierarchical queries?
Here, an excerpt I'm using during development that can save some time:
with my_table
as(
select to_date('01-JAN-2015') start_date,'A' value from dual
union
select to_date('01-MAR-2015') start_date,'B' value from dual
union
select to_date('01-AUG-2015') start_date,'' value from dual
union
select to_date('15-AUG-2015') start_date,'A' value from dual
union
select to_date('01-SEP-2015') start_date,'C' value from dual
union
select to_date('01-JAN-2016') start_date,'B' value from dual
union
select to_date('01-JUN-2016') start_date,'C' value from dual
)
select ...
select start_date, lead(start_date) over (order by start_date) - 1 as end_date, value
from my_table
;
Try this
WITH A AS (SELECT ROWNUM AS RN , A.* FROM SALESNEW A)
SELECT X.START_DATE, Y.START_DATE-1 AS END_DATE, X.VALUE FROM A X , A Y
WHERE (CASE WHEN X.RN>=1 THEN X.RN+1 END) = Y.RN(+);

Finding missing dates in a sequence

I have following table with ID and DATE
ID DATE
123 7/1/2015
123 6/1/2015
123 5/1/2015
123 4/1/2015
123 9/1/2014
123 8/1/2014
123 7/1/2014
123 6/1/2014
456 11/1/2014
456 10/1/2014
456 9/1/2014
456 8/1/2014
456 5/1/2014
456 4/1/2014
456 3/1/2014
789 9/1/2014
789 8/1/2014
789 7/1/2014
789 6/1/2014
789 5/1/2014
789 4/1/2014
789 3/1/2014
In this table, I have three customer ids, 123, 456, 789 and date column which shows which month they worked.
I want to find out which of the customers have gap in their work.
Our customers work record is kept per month...so, dates are monthly..
and each customer have different start and end dates.
Expected results:
ID First_Absent_date
123 10/01/2014
456 06/01/2014
To get a simple list of the IDs with gaps, with no further details, you need to look at each ID separately, and as #mikey suggested you can count the number of months and look at the first and last date to see if how many months that spans.
If your table has a column called month (since date isn't allowed unless it's a quoted identifier) you could start with:
select id, count(month), min(month), max(month),
months_between(max(month), min(month)) + 1 as diff
from your_table
group by id
order by id;
ID COUNT(MONTH) MIN(MONTH) MAX(MONTH) DIFF
---------- ------------ ---------- ---------- ----------
123 8 01-JUN-14 01-JUL-15 14
456 7 01-MAR-14 01-NOV-14 9
789 7 01-MAR-14 01-SEP-14 7
Then compare the count with the month span, in a having clause:
select id
from your_table
group by id
having count(month) != months_between(max(month), min(month)) + 1
order by id;
ID
----------
123
456
If you can actually have multiple records in a month for an ID, and/or the date recorded might not be the start of the month, you can do a bit more work to normalise the dates:
select id,
count(distinct trunc(month, 'MM')),
min(trunc(month, 'MM')),
max(trunc(month, 'MM')),
months_between(max(trunc(month, 'MM')), min(trunc(month, 'MM'))) + 1 as diff
from your_table
group by id
order by id;
select id
from your_table
group by id
having count(distinct trunc(month, 'MM')) !=
months_between(max(trunc(month, 'MM')), min(trunc(month, 'MM'))) + 1
order by id;
Oracle Setup:
CREATE TABLE your_table ( ID, "DATE" ) AS
SELECT 123, DATE '2015-07-01' FROM DUAL UNION ALL
SELECT 123, DATE '2015-06-01' FROM DUAL UNION ALL
SELECT 123, DATE '2015-05-01' FROM DUAL UNION ALL
SELECT 123, DATE '2015-04-01' FROM DUAL UNION ALL
SELECT 123, DATE '2014-09-01' FROM DUAL UNION ALL
SELECT 123, DATE '2014-08-01' FROM DUAL UNION ALL
SELECT 123, DATE '2014-07-01' FROM DUAL UNION ALL
SELECT 123, DATE '2014-06-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-11-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-10-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-09-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-08-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-05-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-04-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-03-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-09-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-08-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-07-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-06-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-05-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-04-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-03-01' FROM DUAL;
Query:
SELECT ID,
MIN( missing_date )
FROM (
SELECT ID,
CASE WHEN LEAD( "DATE" ) OVER ( PARTITION BY ID ORDER BY "DATE" )
= ADD_MONTHS( "DATE", 1 ) THEN NULL
WHEN LEAD( "DATE" ) OVER ( PARTITION BY ID ORDER BY "DATE" )
IS NULL THEN NULL
ELSE ADD_MONTHS( "DATE", 1 )
END AS missing_date
FROM your_table
)
GROUP BY ID
HAVING COUNT( missing_date ) > 0;
Output:
ID MIN(MISSING_DATE)
---------- -------------------
123 2014-10-01 00:00:00
456 2014-06-01 00:00:00
You could use a Lag() function to see if records have been skipped for a particular date or not.Lag() basically helps in comparing the data in current row with previous row. So if we order by DATE, we could easily compare and find any gaps.
select * from
(
select ID,DATE_, case when DATE_DIFF>1 then 1 else 0 end comparison from
(
select ID, DATE_ ,DATE_-LAG(DATE_, 1) OVER (PARTITION BY ID ORDER BY DATE_) date_diff from trial
)
)
where comparison=1 order by ID,DATE_;
This groups all the entries by id, and then arranges the records by date. If a customer is always present, there would not be a gap in his date. So anyone who has a date difference greater than 1 had a gap. You could tweak this as per your requirement.
EDIT : Just observed that you are storing data in mm/dd/yyyy format, when I closely observed above answers.You are storing only first date of every month. So, the above query can be tweaked as :
select * from
(
select ID,DATE_,PREV_DATE,last_day(PREV_DATE)+1 ABSENT_DATE, case when DATE_DIFF>31 then 1 else 0 end comparison from
(
select ID, DATE_ ,LAG(DATE_,1) OVER (PARTITION BY ID ORDER BY DATE_) PREV_DATE,DATE_-LAG(DATE_, 1) OVER (PARTITION BY ID ORDER BY DATE_) date_diff from trial
)
)
where comparison=1 order by ID,DATE_;

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;