SQL Join two tables, only get latest entry of second table - mysql

I have these two tables:
items
itemname description belongs
A1 some_text user1
A2 some_text user1
A3 some_text user1
A4 some_text user1
A5 some_text user1
A1 some_text user2
B2 some_text user2
movements
itemname start_date end_date belongs
A1 2013-02-01 2014-01-12 user1
A1 2014-08-14 NULL user1
A1 2014-10-15 2015-01-01 user1
A2 2013-08-03 2014-08-14 user1
A2 2014-08-14 NULL user1
A3 2013-08-02 2014-08-20 user1
A3 2013-12-05 2014-01-07 user1
A4 2013-07-15 2014-09-13 user1
A4 2014-09-13 NULL user1
A5 2013-07-15 2014-09-13 user1
A5 2015-03-11 2016-03-12 user1
A5 2016-03-12 2016-04-13 user1
A1 2015-08-01 2015-08-12 user2
B2 2015-08-13 2015-08-23 user2
I was playing around with joins and max(date) but didn't come to a working solution.
A result for user1 should looks like this:
itemname description belongs start_date end_date
A1 some_text user1 2014-08-14 NULL
A2 some_text user1 2014-08-14 NULL
A3 some_text user1 2013-08-02 2014-08-20
A4 some_text user1 2014-09-13 NULL
A5 some_text user1 2016-03-12 2016-04-13
I need the line (movement) with the highest (latest, newest) end_date if there is no line where:
end_date = NULL
If there is a line where end_date = NULL, I need this line for that item.
Difficulty here is that sorting for max(start_date) would not work since sometimes there is a timeperiod inside another timeperiod for one item.
I hope you could understand my problem.
Greetings from germany :)

You need something like this
Find the most recent start date for each itemname & belongs combination then join the result back with max start date to get the result
SELECT i.itemname,i.description,i.belongs,m.start_date,m.end_date
FROM items i
JOIN movements m
ON i.itemname = m.itemname
AND i.belongs = m.belongs
JOIN (SELECT itemname,
belongs,
Max(COALESCE(end_date, start_date)) AS max_dt,
Max(end_date) AS max_end_dat,
Max(start_date) AS max_start_dat
FROM movements
GROUP BY itemname,
belongs) m1
ON m1.itemname = m.itemname
AND m1.belongs = m.belongs
AND ( ( m.end_date = m1.max_dt
AND m1.max_dt = m1.max_start_dat )
OR ( m1.max_dt = COALESCE(end_date, m.start_date)
AND m1.max_start_dat <> m1.max_dt )
OR ( m1.max_dt = m.start_date
AND m1.max_end_dat <> m1.max_dt ) )
ORDER BY i.belongs,
i.itemname
SQL FIDDLE DEMO
Update :
SELECT i.itemname,
i.description,
i.belongs,
m.start_date,
m.end_date
FROM items i
JOIN movements m
ON i.itemname = m.itemname
AND i.belongs = m.belongs
JOIN (SELECT itemname,
belongs,
Max(start_date) AS max_dat,
'st' AS indi
FROM movements
WHERE end_date IS NULL
GROUP BY itemname,
belongs
UNION ALL
SELECT itemname,
belongs,
Max(end_date) AS max_dat,
'ed'
FROM movements m
WHERE NOT EXISTS (SELECT 1
FROM movements m1
WHERE m.itemname = m1.itemname
AND m.belongs = m1.belongs
AND end_date IS NULL)
GROUP BY itemname,
belongs) m1
ON m1.itemname = m.itemname
AND m1.belongs = m.belongs
AND ( ( m1.max_dat = m.start_date
AND indi = 'st' )
OR ( m1.max_dat = m.end_date
AND indi <> 'st' ) )
ORDER BY i.belongs,
i.itemname
SQL FIDDLE DEMO
This will be really easy if your RDBMS supports ROW_NUMBER window function or APPLY operator

to select a record from a group of records:
IN ALL the Methods you can join with your Items table to get extra information.
Method-1 use self join:
Select a.*
from yourTable as a
left join YourTable as b
on b.belongs = a.Belongs
and b.Item_Name=a.Item_Name
and b.Start_date>a.Start_Date
where b.Start_Date is null
Method-2, use MaxDate.
with MaxDate as(
Select belongs, Item_Name, Max(Start_Date) as MaxDate
from yourTable
group by belongs, Item_Number
) select * from MaxDate as a
inner join YourTable as b
on b.belongs=a.belongs
and b.Item_Number=a.Item_Number
and b.Start_Date=a.MaxDate
Method-3, use Row_Number to flag the last record in the group
with LastRec as(
select *,
Row_Number() over(partition by belongs, Item_number order by Start_date desc) as RN
From yourTable
) select * from LastRec
where rn=1
Method-4, use sub-query to get the last record in the group.
Select * from your table as a
Where a.StartDate=(Select Max(Start_Date) from yourTable as b Where b.belongs=a.belongs and b.Item_Name=a.Item_Number)

Related

Find missing dates of each product where multiple products are stored in the same table

I have a table structure similar to this which tracks some product data in a daily base:
product_id|columnA|columnB|my_date|
1 | a1 | a2 |2021-03-03|
1 | a1 | a2 |2021-03-04|
1 | a1 | a2 |2021-03-06|
1 | a1 | a2 |2021-03-07|
1 | a1 | a2 |2021-03-10|
2 | a1 | a2 |2021-06-01|
2 | a1 | a2 |2021-06-03|
...
(more product_id)
As you can see, |2021-03-05|, |2021-03-08| and |2021-03-09| are missing for product_id 1 and |2021-06-02| is missing for product_id
2 .
I want to get all the missing dates for each product_id, the result table should look like:
product_id|mssing_date|
1 |2021-03-05|
1 |2021-03-08|
1 |2021-03-09|
2 |2021-06-02|
... ....
other_ids |other_missing dates|
Use a cross join to general all combinations of products and dates. Then remove them . . . one method is a left join:
select p.product_id, d.my_date
from (select distinct product_id from t) p cross join
(select distinct my_date from t) d left join
t
on t.product_id = p.product_id and t.my_date = p.my_date
where t.product_id is null;
EDIT:
For the revised question (based on the comments), you can just calculate the date range and use that for the query:
select p.product_id, d.my_date
from (select product_id, min(my_date) as min_my_date, max(my_date) as max_my_date
from t
group by product_id
) p join
(select distinct my_date from t) d
on d.my_date bewteen p.min_my_date and p.max_my_date left join
t
on t.product_id = p.product_id and
t.my_date = p.my_date
where t.product_id is null;
These methods assume that there is at least one row for each date in the data. In not, you need a different way to generate the dates, such as a calendar table or recursive CTE.
WITH RECURSIVE all_dates AS (
SELECT '2021-01-01' AS d_date
UNION
SELECT d_date + INTERVAL 1 DAY
FROM all_dates
WHERE d_date < '2021-12-31')
SELECT product_id, d_date
FROM all_dates
LEFT JOIN products ON d_date = product.date
WHERE product.date IS NULL;
Using MySQL 8
https://dba.stackexchange.com/questions/224182/generate-dates-between-date-ranges-in-mysql

mysql get attendance of all students for each date in a range

I have the following tables
Students Table
id name
1 A1
2 A2
3 A3
And Atendance table
student_id status date
1 P 2020-02-14
2 P 2020-02-14
1 A 202-02-15
What I want is that the attendance should be shown for all dates, irrespective of data is present in the data tables or not. I have tried the following query
SELECT student.name, attendance.status
from student
LEFT JOIN attendance ON student.id = attendance.student_id
AND attendance.date = '2020-02-14'
With the above query I get the output as
id name status date
1 A1 P 2020-02-14
2 A2 P 2020-02-14
3 A3 null null
But I want the output to be
id name status date
1 A1 P 2020-02-14
2 A2 P 2020-02-14
3 A3 null 2020-02-14
1 A1 A 2020-02-15
2 A2 null 2020-02-15
3 A3 null 2020-02-15
Is is possible to achieve the above output using a single query? Or do I need to do some operations by converting the query output into JSON to get the desired output?
Assuming there is no date on which all students have no record of their attendance, you can find all the relevant dates from the attendance table with a SELECT DISTINCT query; then you can CROSS JOIN this to the students table and LEFT JOIN to attendance to get your desired result:
SELECT s.id, s.name, a.status, d.date
FROM (SELECT DISTINCT `date` FROM attendance) d
CROSS JOIN students s
LEFT JOIN attendance a ON a.date = d.date AND a.student_id = s.id
ORDER BY d.date, s.id
Output:
id name status date
1 A1 P 2020-02-14
2 A2 P 2020-02-14
3 A3 null 2020-02-14
1 A1 A 2020-02-15
2 A2 null 2020-02-15
3 A3 null 2020-02-15
Demo on dbfiddle
You can use a recursive CTE to generate the dates. Then a cross join and left join:
with recursive dates as (
select date('2020-02-14') as dte
union all
select dte + interval 1 day
from dates
where dte < '2020-02-16'
)
select s.id, s.name, a.status, d.dte
from dates d cross join
students s left join
attendance a
on a.student_id = s.id and
a.date = d.dte;

Getting Data from Database with multiple conditions

I have below mentioned table:
ID Type Date Status1 Status2 Status3
1 458 2018-01-01 15:04:06 AAA A B
2 471 2017-12-31 15:04:06 AAA A B
3 458 2018-01-15 15:04:06 BBC C D
5 458 2018-01-18 15:04:06 AAA X D
There are many same value in Type column, i want to fetch the data with below mentioned condition:
Where any value in Type (should have more than one time in table) have Status1 as AAA and other rows with same value in Type are greater than Previous Date, with Status2 as C and Status3 as D together or Status3 must be D.
Output:
ID Type Date Status1 Status2 Status3
1 458 2018-01-01 15:04:06 AAA A B
3 458 2018-01-15 15:04:06 BBC C D
5 458 2018-01-18 15:04:06 AAA X D
I am using below mentioned query:
SELECT a.ID,a.Type,a.Date,b.Status1,a.Status2,a.Status3
From Table1 a
inner join Table2 b
on a.abc=b.xyz
inner join Table2 c
on a.efg=c.xyz
GROUP BY a.Type
HAVING Count(a.Type)>0
AND b.Status1='AAA'
UNION SELECT a.ID,a.Type,a.Date,b.Status1,a.Status2,a.Status3
FROM Table1 a
inner join Table2 b
on a.abc=b.xyz
inner join Table2 c
on a.efg=c.xyz
GROUP BY a.Type
HAVING Count(a.Type)>0
AND a.Date > (SELECT Date From Table1 GROUP BY Type HAVING b.Status1='AAA' AND Count(Type)>0)
AND ( Status2='A' AND Status3='D' )
OR Status3='D';
You can try something like below
SELECT t.* FROM `t` INNER JOIN
(
SELECT `type` , COUNT(`type`) ct FROM t GROUP BY `type` HAVING ct > 1
) t1
ON t1.type = t.type ORDER BY DATE ASC
I hope this will return what you want.
Try this query .
Hope this will give you the desire output .
SELECT
ID ,
Type ,
Date ,
Status1 ,
Status2 ,
Status3
FROM
[Tabel_Name]
GROUP BY
Type
HAVING
Count(Type)>0
AND
Status1='AAA'
UNION
SELECT
ID ,
Type ,
Date ,
Status1 ,
Status2 ,
Status3
FROM
[Tabel_Name]
GROUP BY
Type
HAVING
Count(Type)>0
AND
Date > (SELECT Date From [Tabel_Name] GROUP BY Type HAVING Status1='AAA' AND Count(Type)>0)
AND
( Status2='C' AND Status3='D' )
OR
Status3='D'

How to correct the following query

I am new to MYSQl, Please help me out..
Following query is partially working fine, but for some rows its double the values, it happens when both tables have a same date in two or more rows like here 2015-08-11 are 3 in first table and 2 in another table.
select all_dates.value1 as "Date",
sum(coalesce(g.Qty1, '-')) as "Inward(B_Qty)",
sum(coalesce(f.Qty2, '-')) as "Outward(B_Qty)"
from
( select distinct Date1 as value1 from inward where Name = 'A' union
select distinct Date2 from outward where Name = 'A'
) as all_dates
left join inward g
on g.Date1 = all_dates.value1 and g.Name = 'A'
left join outward f
on f.Date2 = all_dates.value1 and f.Name = 'A' group by all_dates.value1,f.Date2
table no. 1 :- Inward
Name Qty1 Date1
A 25000 2015-08-11
A 15000 2015-08-12
A 45000 2015-08-11
B 150000 2015-09-11
B 85000 2015-07-08
B 15000 2015-07-08
table no 1:Outward
Name Qty2 Date2
A 15000 2015-08-01
A 25000 2015-08-09
A 15000 2015-08-11
A 45000 2015-08-11
B 25000 2015-07-25
Expected OUtput
Date Inward Outward
2015-08-11 70000 60000
2015-08-09 - 25000
2015-08-01 - 15000
2015-08-12 15000 -
Actual ouput:
Date Inward(B_Qty) Outward(B_Qty)
2015-08-01 0 15000
2015-08-09 0 25000
2015-08-11 140000 120000
2015-08-12 15000 0
third rows of actual output is doubling the values..Why it is happening dont know.
Try joining to derived tables already containing sums per Date1, Date2:
select all_dates.value1 as `Date`,
coalesce(`Inward(B_Qty)`, '-') as `Inward(B_Qty)`,
coalesce(`Outward(B_Qty)`, '-') as `Outward(B_Qty)`
from
(
select distinct Date1 as value1 from inward where Name = 'A' union
select distinct Date2 from outward where Name = 'A'
) as all_dates
left join
(
select `Date1`, sum(Qty1) AS `Inward(B_Qty)`
from inward
where Name = 'A'
group by `Date1`
) as g
on g.Date1 = all_dates.value1
left join
(
select `Date2`, sum(Qty2) AS `Outward(B_Qty)`
from outward
where Name = 'A'
group by `Date2`
) as f
on f.Date2 = all_dates.value1
group by all_dates.value1
Demo here
Try this way:
select all_dates.value1 as "Date",
sum(coalesce(g.Qty1, '-')) as "Inward(B_Qty)",
sum(coalesce(f.Qty2, '-')) as "Outward(B_Qty)"
from
( select distinct value1 from (Select Date1 as value1 from inward where Name = 'A' union
select Date2 from outward where Name = 'A') as TmpDates
) as all_dates
left join inward g
on g.Date1 = all_dates.value1 and g.Name = 'A'
left join outward f
on f.Date2 = all_dates.value1 and f.Name = 'A' group by all_dates.value1,f.Date2

SQL: Find the biggest percentage change in response time

I'm trying to calculate and list the websites in order of biggest overall reduction in response time from one time period to the next.
I don't strictly need to use a single query to do this, I can potentially run multiple queries.
websites:
| id | url |
| 1 | stackoverflow.com |
| 2 | serverfault.com |
| 3 | stackexchange.com |
responses:
| id | website_id | response_time | created_at |
| 1 | 1 | 93.26 | 2014-01-28 11:51:39
| 2 | 1 | 99.46 | 2014-01-28 11:52:38
| 2 | 1 | 94.51 | 2014-01-28 11:53:38
| 2 | 1 | 104.46 | 2014-01-28 11:54:38
| 2 | 1 | 85.46 | 2014-01-28 11:56:38
| 2 | 1 | 100.00 | 2014-01-28 11:57:36
| 2 | 1 | 50.00 | 2014-01-28 11:58:37
| 2 | 2 | 100.00 | 2014-01-28 11:58:38
| 2 | 2 | 80 | 2014-01-28 11:58:39
Ideally the result would look like:
| percentage_change | website_id |
| 52 | 1 |
| 20 | 2 |
I've got as far as figuring out the largest response time, but no idea how to do another query to calculate the lowest response time and then do the math, then sort the maths.
SELECT * FROM websites
LEFT JOIN (
SELECT distinct *
FROM responses
ORDER BY response_time desc) responsetable
ON websites.id=responsetable.website_id group by website_id
Thanks
You need the equivalent of the lag() or lead() function. In MySQL, I do this using a correlated subquery:
select website_id, max(1 - (prev_response_time / response_time)) * 100
from (select t.*,
(select t2.response_time
from table t2
where t2.website_id = t.website_id and
t2.created_at < t.created_at
order by t2.created_at desc
limit 1
) as prev_response_time
from table t
) t
group by website_id;
EDIT:
If you want the change from the highest to the lowest:
select website_id, (1 - min(response_time) / max(response_time)) * 100
from table t
group by website_id;
Using a couple of sequence numbers:-
SELECT a.id, a.url, MAX(100 * (LeadingResponse.response_time - TrailingResponse.response_time) / LeadingResponse.response_time)
FROM
(
SELECT website_id, created_at, response_time, #aCnt1 := #aCnt1 + 1 AS SeqCnt
FROM responses
CROSS JOIN
(
SELECT #aCnt1:=1
) Deriv1
ORDER BY website_id, created_at
) TrailingResponse
INNER JOIN
(
SELECT website_id, created_at, response_time, #aCnt2 := #aCnt2 + 1 AS SeqCnt
FROM responses
CROSS JOIN
(
SELECT #aCnt2:=2
) Deriv2
ORDER BY website_id, created_at
) LeadingResponse
ON LeadingResponse.SeqCnt = TrailingResponse.SeqCnt
AND LeadingResponse.website_id = TrailingResponse.website_id
INNER JOIN websites a
ON LeadingResponse.website_id = a.id
GROUP BY a.id, a.url
SQL fiddle for this:-
http://www.sqlfiddle.com/#!2/ace08/1
EDIT - different way of doing it. This will only work if the id on the responses table is in date / time order.
SELECT a.id, a.url, MAX(100 * (r2.response_time - r1.response_time) / r2.response_time)
FROM responses r1
INNER JOIN responses r2
ON r1.website_id = r2.website_id
INNER JOIN
(
SELECT r1.website_id, r1.id, MAX(r2.id) AS prev_id
FROM responses r1
INNER JOIN responses r2
ON r1.website_id = r2.website_id
AND r1.id > r2.id
GROUP BY r1.website_id, r1.id
) ordering_query
ON r1.website_id = ordering_query.website_id
AND r1.id = ordering_query.id
AND r2.id = ordering_query.prev_id
INNER JOIN websites a
ON r1.website_id = a.id
GROUP BY a.id, a.url
You could do a similar thing based on the response_time field rather than the id, but that would require the response_time for a website to be unique.
EDIT
Just seen that you do not just want consecutive changes, rather just the highest to lowest response. Assuming that the lowest doesn't have to come after the highest:-
SELECT id, url, MAX(100 * (max_response - min_response) / max_response)
FROM
(
SELECT a.id, a.url, MIN(r1.response_time) AS min_response, MAX(r1.response_time) AS max_response
FROM responses r1
INNER JOIN websites a
ON r1.website_id = a.id
GROUP BY a.id, a.url
) Sub1
If you are only interested in the lower response time being after the higher one:-
SELECT id, url, MAX(100 * (max_response - min_following_response) / max_response)
FROM
(
SELECT a.id, a.url, MAX(r1.response_time) AS max_response, MIN(r2.response_time) AS min_following_response
FROM responses r1
INNER JOIN responses r2
ON r1.website_id = r2.website_id
AND (r1.created_at < r2.created_at
OR (r1.created_at = r2.created_at
AND r1.id < r2.id))
INNER JOIN websites a
ON r1.website_id = a.id
GROUP BY a.id, a.url
) Sub1
(assuming that the id field on the response table is unique and in created at order)
From your "I've got as far as figuring out the largest response time, but no idea how to do another query to calculate the lowest response time and then do the math, then sort the maths." I understant that you want smallest response time and largest response time and do your math.
drop table #test
create table #test(
id int, website_id int, response_time decimal, created_at datetime)
insert into #test (id , website_id , response_time , created_at) values ( 1 , 1 , 93.26, '2014-01-28 11:51:39')
insert into #test (id , website_id , response_time , created_at) values ( 2 , 1 , 99.46 , '2014-01-28 11:52:38')
insert into #test (id , website_id , response_time , created_at) values ( 2 , 1 , 94.51 , '2014-01-28 11:53:38')
insert into #test (id , website_id , response_time , created_at) values ( 2 , 1 , 104.46 , '2014-01-28 11:54:38')
insert into #test (id , website_id , response_time , created_at) values ( 2 , 1 , 85.46 , '2014-01-28 11:56:38')
insert into #test (id , website_id , response_time , created_at) values ( 2 , 1 , 100.00 , '2014-01-28 11:57:38')
insert into #test (id , website_id , response_time , created_at) values ( 2 , 1 , 50.00 , '2014-01-28 11:58:38')
insert into #test (id , website_id , response_time , created_at) values ( 2 , 2 , 100.00 , '2014-01-28 11:58:38')
insert into #test (id , website_id , response_time , created_at) values ( 2 , 2 , 80 , '2014-01-28 11:58:38')
select * from #test
select distinct MINT.website_id,MINT.MINRT,maxT.MINRT,(MINT.MINRT/maxT.MINRT)*100--Do your calculation here---
from #test t0
inner join(select min(response_time) as MINRT,website_id from #test group by website_id ) MINT
on MINT.website_id = t0.website_id
inner join(select max(response_time) as MINRT,website_id from #test group by website_id ) maxT
on maxT.website_id = t0.website_id
You want to divide the minimum response time by the maximum response time per website? That would simply be:
select
websites.id as website_id,
100 - min(response_time) / max(response_time) * 100 as percentage_change
from websites
left join responses on websites.id = responses.website_id
group by websites.id;
(I assume response_time can never be zero. In case it can, you will have to use a case statement for that.)
Group the response times by website_id, find MIN(response_time) and MAX(response_time) and compare whether MIN() happened after MAX() to filter only websites which improved their performance.
<?php
$rows = $db->fetchAll('
select
r.website_id, min(r.response_time) min_time, max(r.response_time) max_time,
(select
rmin.created_at
FROM
responses rmin
WHERE
rmin.response_time = min(r.response_time) AND
rmin.website_id = r.website_id
ORDER BY rmin.created_at
LIMIT 1) min_created_at,
(select
rmax.created_at
FROM
responses rmax
WHERE
rmax.response_time = max(r.response_time) AND
rmax.website_id = r.website_id
ORDER BY rmax.created_at DESC
LIMIT 1) max_created_at
FROM
responses r
GROUP BY
r.website_id');
foreach($rows as $row) {
if($row['max_created_at'] < $row['min_created_at']) {
echo 'Website '.$row['website_id'].
' improved by '.
(100 - (($row['min_time'] / $row['max_time']) * 100)).
"%\n";
}
}
The query will be however most probably pretty slow with large datasets. You'll have to optimize the indexes and/or query.
sqlfiddle: http://www.sqlfiddle.com/#!2/fa8f9/8