MySQL - Get the max count from a subquery group - mysql

I have a table logins with the following schema:
| id | user_id | weekday |
|----|---------|---------|
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 2 |
...
Weekday is a number from 0 to 6.
I want to get which weekday has the highest count, for each user_id in the table.
I tried the following query:
SELECT MAX(num) as max_num, user_id, weekday
FROM (
SELECT COUNT(*) as num, user_id, weekday
FROM logins
GROUP BY user_id, weekday
) C
WHERE user_id = C.user_id AND num = C.num
GROUP BY user_id;
Which gets me weekday = 1 instead of 2. I think that I shouldn't use a WHERE clause here, but I couldn't manage to get the correct result.
I've checked other similar questions with no luck, such as:
MYSQL, Max,Group by and Max
Select first row in each GROUP BY group?
I created a SQL Fiddle with my example: http://sqlfiddle.com/#!9/e43a71/1

Here is a method:
SELECT user_id, MAX(num) as max_num,
SUBSTRING_INDEX(GROUP_CONCAT(weekday ORDER BY num DESC), ',', 1) as weekday_max
FROM (SELECT user_id, weekday, COUNT(*) as num
FROM logins l
GROUP BY user_id, weekday
) uw
GROUP BY user_id;

SELECT days.user_id, days.weekday, days.num
FROM (
SELECT user_id, MAX(num) AS num
FROM (
SELECT user_id, weekday, COUNT(*) AS num
FROM logins
GROUP BY user_id, weekday
) max
GROUP BY user_id
) nums
JOIN (
SELECT user_id, weekday, COUNT(*) as num
FROM logins
GROUP BY user_id, weekday
) days ON(days.user_id = nums.user_id AND days.num = nums.num);
-- With Mariadb 10.2 or MySQL 8.0.2
WITH days AS (
SELECT user_id, weekday, COUNT(*) as num
FROM logins
GROUP BY user_id, weekday
)
SELECT days.user_id, days.weekday, days.num
FROM (
SELECT user_id, MAX(num) AS num
FROM days
GROUP BY user_id
) nums
JOIN days ON(days.user_id = nums.user_id AND days.num = nums.num);

Related

Cumulative Sum in MySQL

Using MySQL. I want to get cumulative sum.
This is my table
CREATE TABLE `user_infos`
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
(..)
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`) )
And what I want to get is
+-------+-------+----------------+
| month | count | cumulative_sum |
+-------+-------+----------------+
| 01 | 100 | 100 |
| 02 | 101 | 201 |
| ... | 110 | 311 |
| 12 | 200 | 511 |
+-------+-------+----------------+
but the result is
+-------+-------+----------------+
| month | count | cumulative_sum |
+-------+-------+----------------+
| 01 | 100 | 100 |
| 02 | 101 | 101 |
| ... | 110 | 110 |
| 12 | 200 | 200 |
+-------+-------+----------------+
This is my wrong query..
select
T1.Month,T1.Count,
#runnung_total := (#running_total + T1.Count) as cumulative_sum
from (
select date_format(created_at,'%m') as Month,count(1) as Count from users
where date_format(created_at,'%Y')='2016'
group by(date_format(created_at,'%m'))
union
select date_format(created_at,'%m') as Month,count(1) as Count from users
where date_format(created_at,'%Y')='2017'
group by(date_format(created_at,'%m')) ) as T1
join (select #running_total := 0) as R1;
I referred to this. What's wrong in my code?
You can achieve that in two steps: first of all get the sum for each year and month
select concat(year(created_at), lpad(month(created_at), 2, '0')) as ye_mo,
count(*) as cnt
from users
group by concat(year(created_at), lpad(month(created_at), 2, '0'))
Then join it with itself, having each row matched with all previous ones
select t1.ye_mo, sum(t2.cnt)
from (
select concat(year(created_at), lpad(month(created_at), 2, '0')) as ye_mo,
count(*) as cnt
from users
group by concat(year(created_at), lpad(month(created_at), 2, '0'))
) t1
join (
select concat(year(created_at), lpad(month(created_at), 2, '0')) as ye_mo,
count(*) as cnt
from users
group by concat(year(created_at), lpad(month(created_at), 2, '0'))
) t2
on t1.ye_mo >= t2.ye_mo
group by t1.ye_mo
order by t1.ye_mo
Edit
The query above assumes you want the running sum to increase across different years. If you want to display the months only, and aggregate the values of different years in the same month, you can change id this way
select t1.mnt, sum(t2.cnt)
from (
select month(created_at) as mnt,
count(*) as cnt
from userss
group by month(created_at)
) t1
join (
select month(created_at) as mnt,
count(*) as cnt
from userss
group by month(created_at)
) t2
on t1.mnt >= t2.mnt
group by t1.mnt
order by t1.mnt
Finally, if you want the running sum to reset at the beginning of each year, you can do that like this
select t1.yr, t1.mn, sum(t2.cnt)
from (
select year(created_at) as yr, month(created_at) as mn,
count(*) as cnt
from userss
group by year(created_at), month(created_at)
) t1
join (
select year(created_at) as yr, month(created_at) as mn,
count(*) as cnt
from userss
group by year(created_at), month(created_at)
) t2
on t1.yr = t2.yr and
t1.mn >= t2.mn
group by t1.yr, t1.mn
order by t1.yr, t1.mn
All three versions can be seen in action here
Variables are the right way to go. You can simplify your query:
select m.Month, m.cnt,
(#running_total := (#running_total + m.cnt) ) as cumulative_sum
from (select month(created_at) as Month, count(*) as cnt
from users
where year(created_at) in (2016, 2017)
group by month(created_at)
) m cross join
(select #running_total := 0) params
order by m.Month;
Starting with MySQL 8, the ideal approach to calculate cumulative sums is by using SQL standard window functions rather than the vendor-specific, and not stricly declarative approach of using local variables. Your query can be written as follows:
WITH data(month, count) AS (
SELECT date_format(create_at, '%m') AS month, count(*) AS count
FROM users
GROUP BY date_format(create_at, '%m')
)
SELECT
month,
count,
sum(count) OVER (ORDER BY month) AS cumulative_sum
FROM data

MySQL - get min/max of consecutive events in a series of rows

I have a table that looks like this:
http://sqlfiddle.com/#!9/152d2/1/0
CREATE TABLE Table1 (
id int,
value decimal(10,5),
dt datetime,
threshold_id int
);
Current Query:
SELECT sensors_id, DATE_FORMAT(datetime, '%Y-%m-%d'), MIN(value), MAX(value)
FROM Readings
WHERE datetime < "2015-11-18 00:00:00"
AND datetime > "2015-10-18 00:00:00"
AND sensors_id = 9
GROUP BY DATE_FORMAT(datetime, '%Y-%m-%d')
ORDER BY datetime DESC
What I'm trying to do is to return the min/max value in each group, where threshold_id IS NOT NULL. Therefore, the example should return something like:
min_value | max_value | start_date | end_date
9 | 10.5 | 2015-07-29 10:52:31 | 2015-07-29 10:57:31
8.5 | 9.5 | 2015-07-29 11:03:31 | 2015-07-29 11:05:31
I can't work out how to do this grouping. I need to return the min/max for each group of consecutive rows where the threshold_id IS NOT NULL.
Use user variables to compare existing value to the previous value and increment a column you can use to group by,tested on my machine.
SELECT MIN(value),MAX(value),MIN(dt),MAX(dt)
FROM (
SELECT id,value,dt,
CASE WHEN COALESCE(threshold_id,'')=#last_ci THEN #n ELSE #n:=#n+1 END AS g,
#last_ci := COALESCE(threshold_id,'') As th
FROM
Table1, (SELECT #n:=0) r
ORDER BY
id
) s
WHERE th!=''
GROUP BY
g
For mysql 8 this could be rewritten as below.Use a CTE to get different sequences and GROUP By the difference between them.
WITH cte as (
SELECT *,
ROW_NUMBER() OVER (ORDER BY id)as rn,
ROW_NUMBER() OVER (PARTITION BY threshold_id ORDER BY id)as rnn
FROM Table1
ORDER BY id
)
SELECT MIN(value),MAX(value),MIN(dt),MAX(dt) FROM cte WHERE threshold_id IS NOT NULL GROUP BY rn-rnn
MYSQL8
FIDDLE
Your sample data only includes a single day's worth, so you only get a single row back (assuming you want to group by day):
SELECT DAYOFYEAR(dt) `day`, MIN(`value`) min_value, MAX(`value`) max_value
FROM Table1
GROUP BY `day`
ORDER BY `day` ASC

specific status on consecutive days

I have a MySQL table ATT which has EMP_ID,ATT_DATE,ATT_STATUS with ATT_STATUS with different values 1-Present,2-Absent,3-Weekly-off. I want to find out those EMP_ID's which have status 2 consecutively for 10 days in a given date range.
Please help
Please have a try with this:
SELECT EMP_ID FROM (
SELECT
IF((#prevDate!=(q.ATT_DATE - INTERVAL 1 DAY)) OR (#prevEmp!=q.EMP_ID) OR (q.ATT_STATUS != 2), #rownum:=#rownum+1, #rownum:=#rownum) AS rownumber, #prevDate:=q.ATT_DATE, #prevEmp:=q.EMP_ID, q.*
FROM (
SELECT
EMP_ID
, ATT_DATE
, ATT_STATUS
FROM
org_tb_dailyattendance, (SELECT #rownum:=0, #prevDate:='', #prevEmp:=0) vars
WHERE ATT_DATE BETWEEN '2013-01-01' AND '2013-02-15'
ORDER BY EMP_ID, ATT_DATE, ATT_STATUS
) q
) sq
GROUP BY EMP_ID, rownumber
HAVING COUNT(*) >= 10
The logic is, to first sort the table by employee id and the dates. Then introduce a rownumber which increases only if
the days are not consecutive or
the employee id is not the previous one or
the status is not 2
Then I just grouped by this rownumber and counted if there are 10 rows in each group. That should be the ones who were absent for 10 days or more.
Have you tried something like this
SELECT EMP_ID count(*) as consecutive_count min(ATT_DATE)
FROM (SELECT * FROM ATT ORDER BY EMP_ID)
GROUP BY EMP_ID, ATT_DATE
WHERE ATT_STATUS = 2
HAVING consecutive_count > 10

Group count results by date between two tables in mysql

I have 2 different tables, users and votes. I would like to get a query to count total users and total votes (not sum) by date.
users
id | created_at
votes
id | created_at
I want the result to look like this:
date | total users | total votes
2012-06-01 | 50 | 90
2012-06-01 | 23 | 0*
2012-06-01 | 80 | 12
*It should take into account that on some days no votes were made
I know how to make the 2 separate queries
SELECT DATE(created_at), count(id)
FROM vote
GROUP by DATE(created_at)
ORDER by DATE(created_at) DESC
but I don't know how to join them.
Try this:
SELECT
DATE(created_at),
SUM(CASE WHEN `Type` = 'Votes' THEN 1 ELSE 0 END) AS 'TotalVotes',
SUM(CASE WHEN `Type` = 'Users' THEN 1 ELSE 0 END) AS 'TotalUsers'
FROM
(
SELECT created_at, 'Votes' `Type` FROM vote
UNION ALL
SELECT created_at, 'Users' FROM Users
) t
GROUP by DATE(created_at)
ORDER by DATE(created_at) DESC
Try this:
SELECT created_at, SUM(votes) AS 'TotalVotes', SUM(users) AS 'TotalUsers'
FROM(SELECT DATE(created_at) created_at, COUNT(ID) votes, 0 users
FROM votes GROUP BY DATE(created_at)
UNION
SELECT DATE(created_at) created_at, 0 votes, COUNT(ID) users
FROM Users GROUP BY DATE(created_at)) t
GROUP BY created_at
ORDER BY created_at DESC
SELECT DATE(created_at),SUM(usercolumnname) as totalusers,
SUM(//votescolumnname) as totalvotes from users u,votes v
where u.date=v.date
GROUP BY u.DATE(created_at)
ORDER BY u.DATE(created_at) DESC

least value in count

i have a table employee(id,dept_id,salary,hire_date,job_id) . the following query i have to execute.
Show all the employee who were hired on the day of the week on which least no of employee were hired.
i have done the query, but am not able to get the least. please check if am correct.
select id, WEEKDAY(hire_date)+1 as days,count(WEEKDAY(hire_date)+1) as count
from test.employee group by days
This should get you the weekday on which the least number of employees were hired:
SELECT
count(id) as `Total`,
WEEKDAY(hire_date) as `DoW`
FROM
test.employee
GROUP BY `DoW`
ORDER BY `Total` DESC LIMIT 1;
select id from test.employee where hire_date in
( select count(id) count,hire_date
from test.employee
order by count desc
limit 1)
this should work
You may try this, as it will not limit to one record if you have multiple week days where the same least number of employees were hired. In reality it makes sense. The following is based on sample data.
Query:
-- find minimum id count
SELECT MIN(e.counts) INTO #min
FROM (SELECT COUNT(*) as counts,
WEEKDAY(hire_date+1) as day
FROM employee
GROUP BY WEEKDAY(hire_date+1)) e
;
-- show weekdays with minimum id counts
SELECT e2.counts as mincount,
WEEKDAY(e1.hire_date+1) as weekday
FROM employee e1
JOIN (SELECT COUNT(id) as counts,
WEEKDAY(hire_date+1) as day
FROM employee
GROUP BY day
HAVING COUNT(*) = #min) e2
ON WEEKDAY(e1.hire_date+1) = e2.day;
Results:
MINCOUNT WEEKDAY
1 6
1 3
1 4
1 2
SQLFIDDLE
select min(id), WEEKDAY(hire_date)+1 as days,count(WEEKDAY(hire_date)+1) as count
from test.employee group by days
SELECT
*
FROM
employee
WHERE
DAYOFWEEK(hire_date)
IN
(
SELECT
weekday
FROM
(
SELECT
count(*) as bcount,
DAYOFWEEK(hire_date) as weekday
FROM
employee as a
GROUP BY
weekday
HAVING
bcount = (
SELECT
MIN(tcount)
FROM
(
SELECT
count(*) as tcount,
DAYOFWEEK(hire_date) as weekday
FROM
employee
GROUP BY
weekday
) as t
)
) as q