Joining two tables with date ranges - mysql

I have two SQL tables that contain start and end dates.
Table 1: Name, AddedDate
Table 2: Name, RemovedDate
I'm am looking to join these two tables and dump the data into a temp table to show when a name was added and removed from a list.
The same name may have been added and removed multiple times.
Desired Output Example
- Name, AddedDate, RemovedDate
- Jane, 2017-02-01, 2017-02-03
- Bill, 2017-01-28, (blank)
- Mike, 2017-01-15, 2017-01-19
- Jane, 2017-01-13, 2017-01-14
Can someone please help? Thanks.

Another option is an OUTER APPLY (If SQL Server)
Example
Declare #Table1 table (Name varchar(25),AddedDate date)
Insert Into #Table1 Values
('Jane', '2017-02-01'),
('Bill', '2017-01-28'),
('Mike', '2017-01-15'),
('Jane', '2017-01-13')
Declare #Table2 table (Name varchar(25),RemovedDate date)
Insert Into #Table2 Values
('Jane', '2017-02-03'),
('Mike', '2017-01-19'),
('Jane', '2017-01-14')
Select A.Name
,A.AddedDate
,B.RemovedDate
From #Table1 A
Outer Apply (
Select RemovedDate=min(RemovedDate)
From #Table2
Where Name=A.Name
and RemovedDate>=A.AddedDate
) B
Returns
Name AddedDate RemovedDate
Jane 2017-02-01 2017-02-03
Bill 2017-01-28 NULL
Mike 2017-01-15 2017-01-19
Jane 2017-01-13 2017-01-14

Using correlated subquery to consider only the last time the name was added (and potentially removed)...
select Name,
AddedDate,
(select max(RemovedDate)
from table_2
where Name=q1.Name
and RemovedDate >= q1.AddedDate) as RemovedDate
from (select Name,
max(AddedDate) as AddedDate
from table_1
group by name) as q1
order by AddedDate desc,
Name;
Same correlated subquery approach to show every time a name was added and removed...
select Name,
AddedDate,
(select min(RemovedDate)
from table_2
where Name=q1.Name
and RemovedDate >= t1.AddedDate) as RemovedDate
from table_1 t1
order by AddedDate desc,
Name;

You could use left join on name
select t1.name, t1.AddedDate, t2.RemovedDate
from table1 t1
left join table2 t2 on t1.name = t2.name
order by name, t1.AddedDate, t2.RemovedDate

For SQL Server Use the below script
;WITH CTE
AS
(
SELECT
SeqNo = ROW_NUMBER() OVER(PARTITION BY T1.Name ORDER BY T1.AddedDate DESC,T2.RemovedDate DESC)
T1.Name,
T1.AddedDate,
T2.RemovedDate
FROM Table1 T1
LEFT JOIN Table2 T2
ON LTRIM(RTRIM(T1.name)) = LTRIM(RTRIM(T2.name))
)
SELECT
*
FROM CTE
WHERE SeqNo = 1
If you want all the records for a Name, such as if a name was added and removed multiple times and you want each of the dates, then just execute without the
WHERE SeqNo = 1
part

Related

How join statements execute in sql

I'm trying to fetch the data from user table such that every row contains date value(not null). If value is null then it should be view that column with a date of id of above date which have same id.
Without updating the table rows, only with select statement?
Here is the table
NAME, DATE, ID
A, 2021-01-21, 1
B, null, 1
C, null, 1
D, 2021-01-18, 2
D, null, 2
It should be viewed like
A, 2021-01-21, 1
B, 2021-01-21, 1
C, 2021-01-21, 1
D, 2021-01-18, 2
D, 2021-01-18, 2
Now the query I think is =>
select t1.name, t2.date ,t1.id from user t1
left join (select id ,date from user where id=1) t2
on t1.id=t2.id;
But this query doesn't work like I thought.
Can anyone please tell me how above join query works ? And how can I improve it ? So that I got the required result.
For testing of above query use this queries =>
create table user(
name varchar(20),
date date,
id integer
);
insert into user values("A",'2021-01-21',1);
insert into user values("",null,1);
insert into user values("",null,1);
insert into user values("",null,1);
insert into user values("",null,1);
insert into user values("",null,1);
insert into user values("B",'2021-01-20',2);
select t1.name, t2.date ,t1.id from user t1
left join (select id ,date from user where id=1) t2
on t1.id=t2.id;
The first problem is that you are joining a table with itself on the condition t1.id = t2.id. So if you have 4 rows with id=1 and 3 rows with id=2 just as an example, you will end up with a result that had 4 * 4 + 3 * 3 = 25 rows. In your specific case you will end up with 6 * 6 + 1 * 1 = 37 rows.
The second problem is that you have hard-code selecting id=1 in your subquery:
(select id ,date from user where id=1) t2
This can't be the appropriate value for all possible rows.
You could try the obvious:
select
t1.name,
ifnull(t1.date, (select t2.date from user t2 where t2.date is not null and t2.id = t1.id limit 1)) as date,
t1.id
from user t1
;
see db-fiddle
name
id
date
A
1
2021-01-21
1
2021-01-21
1
2021-01-21
1
2021-01-21
1
2021-01-21
1
2021-01-21
B
2
2021-01-20
But better would be to use a join:
select u.name, ifnull(u.date, sq.date) as date, u.id
from user u join (
select id, min(date) as date from user group by id
) sq on u.id = sq.id
;
see db-fiddle
I would expect the second version using a join to be more efficient because the first version has a dependent subquery that has to get executed for every row that has a null date.
You don't need a join. Just use a window function:
select name,
max(date) over (partition by id) as date,
id
from users;
Note that your sample data doesn't match the data in the question. That data suggests:
select max(name) over (partition by id) as name,
max(date) over (partition by id) as date,
id
from user;
Here is a db<>fiddle.

Select how many rows up to a date

having a list of people like:
name date_of_birth
john 1987-09-08
maria 1987-09-08
samuel 1987-09-09
claire 1987-09-10
jane 1987-09-10
rose 1987-09-12
...
How can I get a result view using SQL of how many people are born up to that date, like the output for that table should be:
date count
1987-09-08 2
1987-09-09 3
1987-09-10 5
1987-09-11 5
1987-09-12 6
...
Thanks!
Here is another way, in addition to Gordon's answer. It uses joins:
SELECT
t1.date_of_birth,
COUNT(*) AS count
FROM (SELECT DISTINCT date_of_birth FROM yourTable) t1
INNER JOIN yourTable t2
ON t1.date_of_birth >= t2.date_of_birth
GROUP BY
t1.date_of_birth;
Note: I left out a step. Apparently you also want to report missing dates. If so, then you may replace what I aliased as t1 with a calendar table. For the sake of demonstration, you can inline all the dates:
SELECT
t1.date_of_birth,
COUNT(*) AS count
FROM
(
SELECT '1987-09-08' AS date_of_birth UNION ALL
SELECT '1987-09-09' UNION ALL
SELECT '1987-09-10' UNION ALL
SELECT '1987-09-11' UNION ALL
SELECT '1987-09-12'
) t1
LEFT JOIN yourTable t2
ON t1.date_of_birth >= t2.date_of_birth
GROUP BY
t1.date_of_birth;
Demo
In practice, your calendar table would be a bona fide table which just contains all the dates you want to appear in your result set.
One method is a correlated subquery:
select dob.date_of_birth,
(select count(*) from t where t.date_of_birth <= dob.date_of_birth) as running_count
from (select distinct date_of_birth from t) dob;
This is not particularly efficient. If your data has any size, variables are better (or window functions if you are using MySQL 8.0):
select date_of_birth,
(#x := #x + cnt) as running_count
from (select date_of_birth, count(*) as cnt
from t
group by date_of_birth
order by date_of_birth
) dob cross join
(select #x := 0) params;
Use subquery with correlation approach :
select date_of_birth, (select count(*)
from table
where date_of_birth <= t.date_of_birth
) as count
from table t
group by date_of_birth;

Add up values for employees in three mysql tables

I have a very simple problem I am trying to solve but cannot wrap my head around it.
I have three tables of identical structure
t1.id, t1.cust_id, t1.name, t1.value
t2.id, t2.cust_id, t2.name, t2.value
t3.id, t3.cust_id, t3.name, t3.value
Customers appear in some tables but not in others; the 'value' record in each is a dollar amount.
I would like to run a query in mySQL that produces a summation table that adds up all the purchases made by each customer in the three tables.
My desired output would look something like:
Name Customer ID T1 T2 T3
Joe 88888 12.45 45.90 2.34
Ted 99999 8.90 3.45 null
Sue 12123 9.45 2.45 null
I've tried a few queries with JOINs but with no satisfactory results.
Thanks your help!
Use union all to combine the rows from 3 tables and then use aggregation.
select cust_id,name,sum(t1val),sum(t2val),sum(t3val)
from (
select id, cust_id, name, value as t1val, null as t2val, null as t3val from t1
union all
select id, cust_id, name, null, value, null from t2
union all
select id, cust_id, name, null, null ,value from t3
) t
group by cust_id,name
You can do it with SELECT, e.g.:
SELECT (
(SELECT COALESCE(SUM(value),0) FROM t1 WHERE cust_id = 100)
+
(SELECT COALESCE(SUM(value),0) FROM t2 WHERE cust_id = 100)
+
(SELECT COALESCE(SUM(value),0) FROM t3 WHERE cust_id = 100)
) as total;
Here's the SQL Fiddle.

Combining tables while changing id - SQL

I'm trying to create a search function in different tables using UNION and what happened is that the id's are duplicating making the search go wrong. How can I merge different tables into one while no id's are in common?
Here is the example
table1
id name desc
1 henry post
2 albert doth
3 jun cloth
table2
id name desc
1 kin revenge
2 pot eve
The result SHOULD be like this
id name desc
1 henry post
2 albert doth
3 jun cloth
4 kin revenge
5 pot eve
Please help me. Thanks.
In most databases, you would add a new id using the ANSI standard row_number() function:
select row_number() over (order by which, id) as newid, name, description
from (select 1 as which, t1.* from table1 t1 union all
select 2 as which, t2.* from table2 t2
) t;
Note that desc is a really bad name for a column, because it is a SQL keyword and usually a reserved word.
EDIT:
MySQL doesn't support this ANSI standard functionality. Instead, use variables:
select (#rn := #rn + 1) as newid, name, description
from (select 1 as which, t1.* from table1 t1 union all
select 2 as which, t2.* from table2 t2
) t cross join
(select #rn := 0) vars
order by which, id;
I've include the order by so the rows remain in the same order that you seem to want them in -- rows from the first table followed by rows from the second table. If you don't care about the order, just drop the order by.
For SQLite, the calculation is much more painful:
with cte as (
select 1 as which, t1.* from table1 t1 union all
select 2 as which, t2.* from table2 t2
)
select (select count(*)
from cte cte2
where cte2.which < cte.which or (ct2.which = cte.which and cte2.id <= cte.id
) as id,
name, description
from cte;
In MySql, you can simulate the row_number() function of Sql Server and Oracle using a mutating variable hack:
set #rownum := 0;
SELECT #rownum:=#rownum+1 AS` row_number`, `name`, `desc`
FROM
(
SELECT `name`, `desc` FROM table1
UNION
SELECT `name`, `desc` FROM table2
) AS x;
SqlFiddle
It looks like you have to Generate Id's so you can make you Union query as Sub select and generate Id's in Outer Query
MySQL does not have any system function like SQL Server’s row_number () to generate the row number for each row. However, it can be generated using the variable in the SELECT statement
SET #row_number:=0;
SELECT #row_number:=#row_number+1 As Id,
NAME,
desc
FROM (SELECT NAME,desc
FROM table1
UNION ALL
SELECT NAME,desc
FROM table2
UNION ALL
........
........) A
Order by NAME -- Change the column in Order by in which order you want to create New ID's

SQL Server Get Max

I have the following table:
ID Date FirstName Dept
1 1/2/12 James Act
1 2/5/12 Mike IT
2 5/6/12 Joe HR
2 7/6/12 Keith IT
What I need to do that for each ID, I need to get the max date.
I need to show ID, Date, FirstName, Dept for the record for each ID that has the Max Date.
So in this case for ID of 1, I would show 1 2/5/12 Mike IT
How do I do this in SQL Server T-SQL?
I know I need to do group by.
The table name is TblAct
You will use the MAX() function with a GROUP BY
select t1.id, t1.date, t1.fname, t1.dept
from tblAct t1
inner join
(
SELECT Max(Date) maxdate, ID
from TblAct
GROUP BY id
) t2
on t1.id = t2.id
and t1.date = t2.maxdate
See SQL Fiddle with Demo
You can do this with windows/ranking functions:
select ID, Date, FirstName, Dept
from (select t.*,
row_number() over (partition by id order by date desc) as seqnum
from t
) t
where seqnum = 1
This is ordering all the rows for each id by date, in reverse order. It then selects the first of them.
dont use group by :
select * from tblAct t1
where date=(select max(date) from tblAct where t1.id = id)
just enjoy.