Select N entries for each month value - mysql

I have the following SQL table:
Id DateTime Speed
1 2017-03-02 19:06:20 50
1 2017-03-02 19:10:18 52
1 2017-04-01 20:01:10 55
2 2017-03-02 18:06:20 60
2 2017-05-03 19:08:00 61
3 2017-04-12 19:01:40 80
3 2017-05-11 19:05:50 82
3 2017-05-14 11:00:00 81
I want to select any 2 entries for each month, so that months 3, 4 and 5 would have 2 observations.
Can anyone help with how to do it?
EXPECTED RESULT:
Id DateTime Speed
1 2017-03-02 19:06:20 50
1 2017-03-02 19:10:18 52
1 2017-04-01 20:01:10 55
3 2017-04-12 19:01:40 80
2 2017-05-03 19:08:00 61
3 2017-05-11 19:05:50 82

I think the most efficient general method in MySQL is to use variables:
select t.*
from (select t.*,
(#rn := if(#ym = date_format(date, '%Y-%m'), #rn + 1,
if(#ym := date_format(date, '%Y-%m'), 1, 1)
)
) as rn
from t cross join
(select #ym := '', #rn := 0) params
order by date_format(date, '%Y-%m')
) t
where rn <= 2;
This returns an arbitrary two rows from each month. You can add a second join key to get two particular values -- the first two, last two, highest speed or whatever.

Related

Use date (-1 day) on next row to be the end date for current row

I am trying to self join in my current script in order to find the next row and then whatever day specified it should minus 1 day from it and put that in the end date column for the current row, but I seem to be going wrong somewhere.
SELECT
BCG.BudgetId
,B.CustomerId
,CAST(BCG.StartOfPeriod AS DATE) AS StartOfPeriod
,BCG2.EndOfPeriod
,ROUND(SUM(BCG.Charge),2) AS ExpenditureBudget
,ROUND(SUM(BCG.Consumption),2) AS ConsumptionBudget
,ROW_NUMBER() OVER (PARTITION BY BCG.BudgetId ORDER BY BCG.StartOfPeriod ASC) AS rowNum
,B.Status
FROM Budgets_BudgetCalcGroup BCG
INNER JOIN Budgets_Budget B ON B.Id = BCG.BudgetId
LEFT JOIN Budgets_BudgetCalcGroup BCG2 ON
BCG2.EndOfPeriod = (SELECT MIN(StartOfPeriod)-1
FROM Budgets_BudgetCalcGroup AS t3
WHERE t3.StartOfPeriod > t1.StartOfPeriod
)
WHERE B.Status = 2
GROUP BY BCG.BudgetId,StartOfPeriod
Error Received:
Unknown Column BCG2.EndOfPeriod in field list
Expected Output:
254 41 2018-09-01 2018-09-30 29017.8 542331.59 1 2
254 41 2018-10-01 2018-10-31 27858.82 575545.97 2 2
254 41 2018-11-01 2018-11-30 28927.71 576106.15 3 2
254 41 2018-12-01 NULL 34639.71 613779.57 4 2
I found an alternative way other than doing a self join which utilises the LEAD() function.
DATE_ADD(CAST(LEAD(BCG.StartOfPeriod, 1) OVER (PARTITION BY BCG.BudgetId ORDER BY BCG.StartOfPeriod) AS DATE),INTERVAL -1 DAY) AS EndOfPeriod
Output:
254 41 2018-09-01 2018-09-30 29017.8 542331.59 1
254 41 2018-10-01 2018-10-31 27858.82 575545.97 2
254 41 2018-11-01 2018-11-30 28927.71 576106.15 3
254 41 2018-12-01 2018-12-31 34639.71 613779.57 4

How to select a few rows of Running total

I have running total
SELECT
id,
DepositValue,
action_date,
SUM(DepositValue) OVER(ORDER by action_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Running_total
The above select returns me the following:
id action_date DepositValue Running_total
1 2020-04-01 20 20
2 2020-04-02 2 22
3 2020-04-03 8 30
4 2020-04-04 10 38
5 2020-04-05 14 48
6 2020-04-06 15 62
7 2020-04-07 22 77
8 2020-04-08 12 99
9 2020-04-09 4 103
What i want to achieve is selecting only part of Running_total depend on action_date with already calculated values like this.
id action_date DepositValue Running_total
3 2020-04-03 8 30
4 2020-04-04 10 38
5 2020-04-05 14 48
You can turn your query to a subquery and filter in the outer query:
SELECT *
FROM (
SELECT
id,
DepositValue ,
action_date,
SUM(DepositValue) OVER(ORDER by action_date) AS Running_total
FROM mytable
) t
WHERE action_date BETWEEN '2020-04-03' AND '2020-04-05'
Note that window specification ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW is actually the default when not specificed, hence you can just remove it.
Also, your original query was missing a FROM clause, I added it.

MySQL: total users in DB aggregated over date

I need to calculate total number of users "aggregated", day by day, ex:
table users:
id create_date
0 2016-09-01 00:00:00
1 2016-09-01 00:00:00
2 2016-09-01 00:00:00
3 2016-09-02 00:00:00
4 2016-09-02 00:00:00
5 2016-09-02 00:00:00
6 2016-09-03 00:00:00
7 2016-09-03 00:00:00
8 2016-09-04 00:00:00
9 2016-09-04 00:00:00
using the following query:
select date(u.create_date),count(u.id)
from user u
group by date(u.create_date)
returns:
date(u.create_date) count(u.id)
2016-09-01 3
2016-09-02 3
2016-09-03 2
2016-09-04 2
but I need to return data aggregated like this:
date(u.create_date) count(u.id)
2016-09-01 3
2016-09-02 6
2016-09-03 8
2016-09-04 10
thx,
Note: table key "id" has holes, (Non-sequentially).
You want a cumulative sum. In MySQL, this is probably easiest using variables:
select dte, cnt, (#c := #c + cnt) as running_cnt
from (select date(u.create_date) as dte, count(u.id) as cnt
from user u
group by date(u.create_date)
) d cross join
(select #c := 0) params
order by dte;
Note: When using aggregation with variables, I find that the subquery is necessary.
You could also do:
select d.dte,
(select count(*)
from users u
where u.create_date < date_add(u.dte, interval 1 day)
) as running_cnt
from (select distinct date(u.create_date) as dte from user u) d;
For small amounts of data, this is fine performance-wise.

Get 4 groups of 6 records from the last 24 results

I would like to get the average (from each group individually) of 4 groups with 6 records each from the last 24 results. Basically get the average of the first 6 records (from the last 24 records), then of the next 6 records and so on. How can I do that?
Table example:
id | datetime | viewer_count
----------------------------------------
1 | 2015-10-31 22:00:38 | 1548
2 | 2015-10-31 23:00:42 | 1589
3 | 2015-11-01 00:00:44 | 1589
4 | 2015-11-01 01:00:37 | 1587
... | ... | ...
You can do it using a variable to enumerate successive records:
SELECT (rn-1) DIV 6 AS groupNo, AVG(viewer_count) AS averageCount
FROM (
SELECT id, datetime, viewer_count,
#rn := #rn + 1 AS rn
FROM mytable
CROSS JOIN (SELECT #rn := 0) var
ORDER BY id DESC ) t
WHERE t.rn <= 24
GROUP BY (rn-1) DIV 6
ORDER BY groupNo DESC
If you order by id DESC and enumerate consecutive records using a variable, such as #rn, then you can get the last 24 records using
WHERE t.rn <= 24
in an outer query.
You can group records in slices of 6 using integer division:
GROUP BY (rn-1) DIV 6
Finally, using:
ORDER BY groupNo DESC
you can re-establish initial row order.
Note: The above query assumes that your table contains 24 or more records.
Demo here

Not getting the right expected output for my Mysql Query?

I've 4 tables as shown below
doctors
id name
------------
1 Mathew
2 Praveen
3 Rosie
4 Arjun
5 Denis
doctors_appointments
id doctors_id patient_name contact date status
--------------------------------------------------------------------------------------
1 5 Nidhin 9876543210 2012-12-10 15:39:41 Registered
2 5 Sunny 9876543210 2012-12-18 15:39:48 Registered
3 5 Mani 9876543210 2012-12-12 15:39:57 Registered
4 2 John 9876543210 2012-12-24 15:40:09 Registered
5 4 Raj 9876543210 2012-12-05 15:41:57 Registered
6 3 Samuel 9876543210 2012-12-14 15:41:33 Registered
7 2 Louis 9876543210 2012-12-24 15:40:23 Registered
8 1 Federick 9876543210 2012-12-28 15:41:05 Registered
9 2 Sam 9876543210 2012-12-12 15:40:38 Registered
10 4 Sita 9876543210 2012-12-12 15:41:00 Registered
doctors_dutyplan
id doctor_id weeks time no_of_patients
------------------------------------------------------------------
1 1 3,6,7 9:00am-1:00pm 10
2 2 3,4,5 1:00pm-4:00pm 7
3 3 3,6,7 10:00am-2:00pm 10
4 4 3,4,5,6 8:30am-12:30pm 12
5 5 3,4,5,6,7 9:00am-4:00pm 30
emp_leave
id empid leavedate
--------------------------------
1 2 2012-12-05 14:42:36
2 2 2012-12-03 14:42:59
3 3 2012-12-03 14:43:06
4 3 2012-12-06 14:43:14
5 5 2012-12-04 14:43:24
My task is to find all the days in a month in which the doctor is available excluding the leave dates.
My query what is wrote is given below:
SELECT DATE_ADD( '2012-12-01', INTERVAL
ROW DAY ) AS Date,
ROW +1 AS DayOfMonth
FROM (
SELECT #row := #row +1 AS
ROW FROM (
SELECT 0
UNION ALL SELECT 1
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
UNION ALL SELECT 6
)t1, (
SELECT 0
UNION ALL SELECT 1
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
UNION ALL SELECT 6
)t2, (
SELECT #row := -1
)t3
LIMIT 31
)b
WHERE DATE_ADD( '2012-12-01', INTERVAL
ROW DAY )
BETWEEN '2012-12-01'
AND '2012-12-31'
AND DAYOFWEEK( DATE_ADD( '2012-12-01', INTERVAL
ROW DAY ) ) =2
AND DATE_ADD( '2012-12-01', INTERVAL
ROW DAY ) NOT
IN (
SELECT DATE_FORMAT( l.leavedate, '%Y-%m-%d' ) AS date
FROM doctors_dutyplan d
LEFT JOIN emp_leave AS l ON d.doctor_id = l.empid
WHERE doctor_id =2
)
This works fine for all doctors who took any leave in a particular day in a month (here in the example it is Decemeber 2012). and the result for the above query is shown below:
Date DayOfMonth
-----------------------
2012-12-10 10
2012-12-17 17
2012-12-24 24
2012-12-31 31
But on the other hand for the doctors who didn't took any leave , for that my query is showing empty table, example for the doctor Mathew whose id is 1, my query returns an empty result
can anyone please tell a solution for this problem.
Thanks in advance.
Your query is large, but this part looks fishy:
NOT IN (
SELECT DATE_FORMAT( l.leavedate, '%Y-%m-%d' ) AS date
FROM doctors_dutyplan d
LEFT JOIN emp_leave AS l ON d.doctor_id = l.empid
WHERE doctor_id =2
The left join means a null would be returned for doctor 1. Now, col1 not in (null) does not behave as you may expect. It translates to:
col1 <> null
Which is never true. You could solve this by changing the left join to an inner join, so an empty set instead of null is returned for a doctor without leave.