Get record in range of multiples of 5 - mysql

i have a existing new week_table -
start_date end_date weekno ----------------------------------------------
1996-01-01 1996-01-05 1
1996-01-08 1996-01-12 2
1996-01-15 1996-01-19 3
1996-01-22 1996-01-26 4
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''till
1998-12-21 1998-12-26 156
i am trying to extract records with a count of 5 weeks in group. I am looking at results like
start_date end_date weekno_start weekno_end ----------------------------------------------
1996-01-01 1996-02-02 1 5
1996-02-05 1996-03-08 6 10
1996-03-11 1996-04-12 11 16
i do get the results but the weekno numbers keep running over the maximum week no in the database. for records over weekno 156 i get rows with null value.
How can i avoid the records with null and limit the view to the maximum week no
my current code is-
SELECT (t1.weekno * 5) - 4 AS start_id
,t3.start_date
,t4.end_date
,(t1.weekno * 5) AS end_id
FROM weekcon_table t1
LEFT JOIN weekcon_table t2 ON (t2.weekno = t1.weekno * 5)
LEFT JOIN weekcon_table t3 ON (t3.weekno = (t1.weekno * 5) - 4)
LEFT JOIN weekcon_table t4 ON (t4.weekno = (t1.weekno * 5))

Have you tried something like this:
select min(weekno) as `start_id`,
min(start_date) as `start_date`,
max(end_date) as `end_date`,
min(weekno) as `weekno_start`,
max(weekno) as `weekno_end`
from weekcon_table
group by ((weekno - 1) DIV 5)
order by ((weekno - 1) DIV 5) asc
Here is the output:
start_id start_date end_date weekno_start weekno_end
1 01/01/1996 26/01/1996 1 5
6 04/03/1996 24/02/1996 6 10
11 01/04/1996 30/03/1996 11 15
16 06/05/1996 27/04/1996 16 20
21 03/06/1996 25/05/1996 21 23
Record Count: 5; Execution Time: 1ms View Execution Plan link

I create two tables and asign a rank_id
the first one is for star_date ... will be each row weekno % 5 = 1
second table is for end_date ... will be each row weekno % 5 = 0 and also include the last date of all weeks.
Then join by rank_id
Sql Fiddle Demo In the demo you can change the select fields for * if want see what is happening
SELECT ini_range.start_date,
end_range.end_date,
ini_range.weekno,
end_range.weekno
FROM
(
SELECT r.* ,
(SELECT count(distinct r2.weekno)
FROM
(
SELECT *
FROM t_week
WHERE weekno % 5 = 1
) r2
WHERE r2.weekno <= r.weekno
) as rank
FROM
(
SELECT *
FROM t_week
WHERE weekno % 5 = 1
) r
) ini_range
JOIN
(
SELECT r.* ,
(SELECT count(distinct r2.weekno)
FROM
(
SELECT *
FROM t_week
WHERE weekno % 5 = 0
or weekno = (SELECT max(weekno) FROM t_week)
) r2
WHERE r2.weekno <= r.weekno
) as rank
FROM
(
SELECT *
FROM t_week
WHERE weekno % 5 = 0
or weekno = (SELECT max(weekno) FROM t_week)
) r
) end_range
ON ini_range.rank = end_range.rank
OUTPUT
| start_date | end_date | weekno | weekno |
|------------|------------|--------|--------|
| 01/01/1996 | 03/02/1996 | 1 | 5 |
| 05/02/1996 | 09/03/1996 | 6 | 10 |
| 11/03/1996 | 13/04/1996 | 11 | 15 |
| 15/04/1996 | 18/05/1996 | 16 | 20 |
| 20/05/1996 | 08/06/1996 | 21 | 23 | <- 23 is last week
and group only have
3 week instead of 5

I found another solution
SQL Fiddle Demo
SELECT *
FROM t_week w_ini
JOIN t_week w_end
ON w_ini.weekno = w_end.weekno + 4
OR w_ini.weekno + 5 > w_end.weekno
WHERE
w_ini.weekno % 5 = 1
and w_ini.weekno < w_end.weekno
and(
w_end.weekno % 5 = 0 or
w_end.weekno = (SELECT max(weekno) FROM t_week)
)

Related

MySQL: How to get the Active members by month in year

I have got the previous year working members and subtracted previous year relieving employees, then got the previous month relieving list and subtracted it from the result set. Then added the newly added members in a current month.
SQL Fiddle Link
I am sensing that there lot of improvements we can do to the current query. But right now I am out of ideas, Can someone kindly help on this?
IF I have interpreted your existing query correctly, I suggest the following:
select
mnth.num, count(*)
from (
select 1 AS num union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all
select 7 union all select 8 union all select 9 union all select 10 union all select 11 union all select 12
) mnth
left join (
select
e.emp_id
, case
when e.hired_date < date_format(current_date(), '%Y-01-01') then 1
else month(e.hired_date)
end AS start_month
, case
when es.relieving_date < date_format(current_date(), '%Y-01-01') then 0
when es.relieving_date >= date_format(current_date(), '%Y-01-01') then month(es.relieving_date)
else month(current_date())
end AS end_month
from employee e
left join employee_separation es on e.emp_id = es.emp_id
) emp on mnth.num between emp.start_month and emp.end_month
where mnth.num <= month(current_date())
group by
mnth.num
;
This produced the following result (current_date() on Nov 21 2017
| num | count(*) |
|-----|----------|
| 1 | 6 |
| 2 | 7 |
| 3 | 8 |
| 4 | 9 |
| 5 | 10 |
| 6 | 9 |
| 7 | 10 |
| 8 | 11 |
| 9 | 12 |
| 10 | 13 |
| 11 | 14 |
DEMO
Depending on data volumes adding a where clause in the emp subquery may help, this also affect a case expression:
, case
when es.relieving_date >= date_format(current_date(), '%Y-01-01') then month(es.relieving_date)
else month(current_date())
end AS end_month
from employee e
left join employee_separation es on e.emp_id = es.emp_id
where es.relieving_date >= date_format(current_date(), '%Y-01-01')
I think what you need to do is to get all the employees who are already working from the employee table with:
SELECT * FROM employee WHERE hired_date<= CURRENT_DATE;
Then get the list of employees whose relieving date is still in the future using:
SELECT * FROM employee_separation WHERE relieving_date > CURRENT_DATE;
Then join the two results and group by the month and year of the reliving date as shown below:
SELECT DATE_FORMAT(B.relieving_date, "%Y-%M") RELIEVING_DATE, COUNT(*)
NUMBER_OF_ACTIVE_MEMBERS FROM
(SELECT * FROM employee WHERE hired_date <= CURRENT_DATE) A INNER JOIN
(SELECT * FROM employee_separation WHERE relieving_date > CURRENT_DATE) B
ON A.emp_id=B.emp_id
GROUP BY DATE_FORMAT(B.relieving_date , "%Y-%M");
Here is a Demo on sql fiddle.

Check for maximum amount of records in a period

I've got a table with a "date" column (timestamp). What I'm trying to achieve is to check if after inserting a row there will be no more than 3 records contained in a single 24 hours period, for example:
I have records with the following dates:
1. 2015-05-31 23:14:00
2. 2015-06-01 02:07:00
3. 2015-06-01 15:16:00
So now I shouldn't be able to to insert a row with the date of (for example) 2015-06-01 16:01:00 or 2015-06-01 01:01:00 but I should be able to add records with the dates of (for example): 2015-06-01 23:50:00, 2015-05-31 01:05:00.
How can I achieve this?
There is a little trick that you can achieve this problem with purely SQL
SET #DATE = '2015-05-31 1:14:00';
INSERT tbldate(inputdate)
SELECT #DATE FROM
(
(
SELECT
COUNT(*) AS c
FROM tbldate AS t1 INNER JOIN tbldate AS t2
WHERE
t1.inputdate <= t2.inputdate AND
t2.inputdate <= t1.inputdate + INTERVAL 24 HOUR AND
t1.inputdate BETWEEN #DATE - INTERVAL 24 HOUR AND #DATE
GROUP BY
t1.inputdate
)
UNION ALL
(SELECT 0 AS c)
) AS r
HAVING MAX(r.c) < 2
where #DATE is date you want to insert.
So, essentially, you want to prevent the insertion of dates which fall within the following ranges, if there are already two dates within those ranges:
SELECT x.id, x.dt - INTERVAL 24 HOUR min_range, x.dt max_range FROM my_table x
UNION
SELECT x.id, x.dt, x.dt + INTERVAL 24 HOUR max_range FROM my_table x;
+----+---------------------+---------------------+
| id | min_range | max_range |
+----+---------------------+---------------------+
| 1 | 2015-05-30 23:14:00 | 2015-05-31 23:14:00 |
| 2 | 2015-05-31 02:07:00 | 2015-06-01 02:07:00 |
| 3 | 2015-05-31 15:16:00 | 2015-06-01 15:16:00 |
| 1 | 2015-05-31 23:14:00 | 2015-06-01 23:14:00 |
| 2 | 2015-06-01 02:07:00 | 2015-06-02 02:07:00 |
| 3 | 2015-06-01 15:16:00 | 2015-06-02 15:16:00 |
+----+---------------------+---------------------+
I'm not suggesting that this is the most efficient solution, but I think it works...
SET #dt = '2015-06-01 23:50:00'
INSERT INTO my_table (dt)
SELECT #dt
FROM (SELECT 1) m
LEFT
JOIN
( SELECT a.*
FROM
( SELECT x.id, x.dt - INTERVAL 24 HOUR min_range, x.dt max_range FROM my_table x
UNION
SELECT x.id, x.dt, x.dt + INTERVAL 24 HOUR FROM my_table x
) a
JOIN my_table b
ON b.dt BETWEEN a.min_range AND a.max_range
GROUP
BY a.id
, a.min_range
, a.max_range
HAVING COUNT(*) >= 3
) n
ON #dt BETWEEN n.min_range AND n.max_range
WHERE n.id IS NULL LIMIT 1;

Cumulative Sum of group count in mysql query

I have a table look like below....
ID HID Date UID
1 1 2012-01-01 1002
2 1 2012-01-24 2005
3 1 2012-02-15 5152
4 2 2012-01-01 6252
5 2 2012-01-19 10356
6 3 2013-01-06 10989
7 3 2013-03-25 25001
8 3 2014-01-14 35798
How can i group by HID, Year, Month and count(UID) and add a cumulative_sum (which is count of UID). So the final result look like this...
HID Year Month Count cumulative_sum
1 2012 01 2 2
1 2012 02 1 3
2 2012 01 2 2
3 2013 01 1 1
3 2013 03 1 2
3 2014 01 1 3
What's the best way to accomplish this using query?
I made assumptions about the original data set. You should be able to adapt this to the revised dataset - although note that the solution using variables (instead of my self-join) is faster...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(ID INT NOT NULL
,Date DATE NOT NULL
,UID INT NOT NULL PRIMARY KEY
);
INSERT INTO my_table VALUES
(1 ,'2012-01-01', 1002),
(1 ,'2012-01-24', 2005),
(1 ,'2012-02-15', 5152),
(2 ,'2012-01-01', 6252),
(2 ,'2012-01-19', 10356),
(3 ,'2013-01-06', 10989),
(3 ,'2013-03-25', 25001),
(3 ,'2014-01-14', 35798);
SELECT a.*
, SUM(b.count) cumulative
FROM
(
SELECT x.id,YEAR(date) year,MONTH(date) month, COUNT(0) count FROM my_table x GROUP BY id,year,month
) a
JOIN
(
SELECT x.id,YEAR(date) year,MONTH(date) month, COUNT(0) count FROM my_table x GROUP BY id,year,month
) b
ON b.id = a.id AND (b.year < a.year OR (b.year = a.year AND b.month <= a.month)
)
GROUP
BY a.id, a.year,a.month;
+----+------+-------+-------+------------+
| id | year | month | count | cumulative |
+----+------+-------+-------+------------+
| 1 | 2012 | 1 | 2 | 2 |
| 1 | 2012 | 2 | 1 | 3 |
| 2 | 2012 | 1 | 2 | 2 |
| 3 | 2013 | 1 | 1 | 1 |
| 3 | 2013 | 3 | 1 | 2 |
| 3 | 2014 | 1 | 1 | 3 |
+----+------+-------+-------+------------+
If you don't mind an extra column in the result, you can simplify (and accelerate) the above, as follows:
SELECT x.*
, #running:= IF(#previous=x.id,#running,0)+x.count cumulative
, #previous:=x.id
FROM
( SELECT x.id,YEAR(date) year,MONTH(date) month, COUNT(0) count FROM my_table x GROUP BY id,year,month ) x
,( SELECT #cumulative := 0,#running:=0) vals;
The code turns out kind of messy, and it reads as follows:
SELECT
HID,
strftime('%Y', `Date`) AS Year,
strftime('%m', `Date`) AS Month,
COUNT(UID) AS Count,
(SELECT
COUNT(UID)
FROM your_db A
WHERE
A.HID=B.HID
AND
(strftime('%Y', A.`Date`) < strftime('%Y', B.`Date`)
OR
(strftime('%Y', A.`Date`) = strftime('%Y', B.`Date`)
AND
strftime('%m', A.`Date`) <= strftime('%m', B.`Date`)))) AS cumulative_count
FROM your_db B
GROUP BY HID, YEAR, MONTH
Though by using views, it should become much clearer:
CREATE VIEW temp_data AS SELECT
HID,
strftime('%Y', `Date`) as Year,
strftime('%m', `Date`) as Month,
COUNT(UID) as Count
FROM your_db GROUP BY HID, YEAR, MONTH;
Then your statement will read as follows:
SELECT
HID,
Year,
Month,
`Count`,
(SELECT SUM(`Count`)
FROM temp_data A
WHERE
A.HID = B.HID
AND
(A.Year < B.Year
OR
(A.Year = B.Year
AND
A.Month <= B.Month))) AS cumulative_sum
FROM temp_data B;

count the number of rows between intervals

My table is like:
+---------+---------+------------+-----------------------+---------------------+
| visitId | userId | locationId | comments | time |
+---------+---------+------------+-----------------------+---------------------+
| 1 | 3 | 12 | It's a good day here! | 2012-12-12 11:50:12 |
+---------+---------+------------+-----------------------+---------------------+
| 2 | 3 | 23 | very beautiful | 2012-12-12 12:50:12 |
+---------+---------+------------+-----------------------+---------------------+
| 3 | 3 | 52 | nice | 2012-12-12 13:50:12 |
+---------+---------+------------+-----------------------+---------------------+
which records visitors' trajectory and some comments on the places visited.
I want to count the numbers of visitors that visit a specific place (say id=3227) from 0:00 to 23:59, over some interval (ie. 30mins)
I was trying to do this by :
SELECT COUNT(*) FROM visits
GROUP BY HOUR(time), SIGN( MINUTE(time) - 30 )// if they are in the same interval this will yield the same result
WHERE locationId=3227
The problem is that if there is no record that falls in some interval, this will NOT return that interval with count 0. For example, there are no visitors visiting the location from 02:00 to 03:00, this will not give me the intervals of 02:00-02:29 and 02:30-2:59.
I want a result with an exact size of 48 (one for every half hour), how can I do this?
You have to create a table with the 48 rows that you want and use left outer join:
select n.hr, n.hr, coalesce(v.cnt, 0) as cnt
from (select 0 as hr, -1 as sign union all
select 0, 1 union all
select 1, -1 union all
select 1, 1 union all
. . .
select 23, -1 union all
select 23, 1 union all
) left outer join
(SELECT HOUR(time) as hr, SIGN( MINUTE(time) - 30 ) as sign, COUNT(*) as cnt
FROM visits
WHERE locationId=3227
GROUP BY HOUR(time), SIGN( MINUTE(time) - 30 )
) v
on n.hr = v.hr and n.sign = v.sign
order by n.hr, n.hr

mysql update and set sum value of last 8 hours of the current row

Hi I would like to count the sum of chat_duration from 8 hours ago of the current data
I have :
agent text
start_time datetime
end_time datetime
chat_duration bigint
and i need to insert the calculation result into past8_hours_chat_duration
so when i have :
+----+--------+------------+----------+---------------+---------------------------+
| id | agent | start_time | end_time | chat_duration | past8_hours_chat_duration |
+----+--------+------------+----------+---------------+---------------------------+
| 1 | agent1 | 00.00.00 | 00.01.00 | 60 | |
| 2 | agent2 | 00.00.00 | 00.01.00 | 60 | |
| 3 | agent1 | 00.02.00 | 00.04.00 | 120 | |
| 4 | agent1 | 08.02.00 | 08.03.00 | 60 | |
+----+--------+------------+----------+---------------+---------------------------+
I'll try to explain as much as possible.
For each row I need to find the sum of duration past 8 hours of the current agent
or in another word : where the start_time is after (currentData.start_time minus 8 hour) and not itself ( current row) and not where the start_time is after currentData.start_time
for id 1, there is no session for agent1 where the start_time is after 00.00.00 minus 8 hour ( current start_time) so the total is 0
for id 2, there is also no session for agent2 where the start_time is after 00.00.00 minus 8 hour ( current start_time) so the total is 0
for id 3, since the start_time of id 1 is > 00.02.00(current) - 8 hours so the total is 60
and
for id 4, since the start_time of
id 1 is < 08.02.00(current) - 8 hours
& id 3 is > 08.02.00(current) - 8 hours
so the total is 120(from id 3)
i'm using mysql
at first i'm using :
UPDATE chats AS c
JOIN ( SELECT agent,
SUM(chat_duration) AS sum_duration
FROM abc
GROUP BY agent
) AS c2
ON c2.agent = c.agent
SET c.past8_hours_chat_duration = c2.sum_duration
WHERE c.id < 10;
but that's sum of all the agent duration, how should i find the sum of the past 8 hours chat data.
Thank you,
You can do this in a query using a correlated subquery:
select c.*,
(select sum(c2.duration)
from chats c2
where c2.agent = c.agent and
c2.start_time > c.start_time - interval 8 hour and
c2.start_time <= c.start_time
) as past8_hours_chat_duration
from chats c;
In MySQL, integrating this into an update is tricky, because you can only refer to the table being updated in the join clause. So:
update chats c join
(select c.*,
(select sum(c2.duration)
from chats c2
where c2.agent = c.agent and
c2.start_time > c.start_time - interval 8 hour and
c2.start_time <= c.start_time
) as past8_hours_chat_duration
from chats c
) cc
on c.id = cc.id
c.past8_hours_chat_duration = coalesce(cc.past8_hours_chat_duration, 0);