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
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;
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'
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
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