MySQL GROUP BY WEEK Starting 1st of month - mysql

Need to GROUP BY WEEKS Starting from 1st of current month
DDLs:
CREATE TABLE `group_by_week` (
`id` int(4) NOT NULL AUTO_INCREMENT,
`date` date DEFAULT NULL,
`value` int(4) DEFAULT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `group_by_week`
(`date`,`value`)
VALUES
('2016-01-01',1),
('2016-01-02',2),
('2016-01-03',3),
('2016-01-04',4),
('2016-01-05',5),
('2016-01-06',6),
('2016-01-07',7),
('2016-01-08',8),
('2016-01-09',9),
('2016-01-10',10),
('2016-01-11',11),
('2016-01-12',12),
('2016-01-13',13),
('2016-01-14',14),
('2016-01-15',15),
('2016-01-16',16);
EXPECTED RESULT:
week 1 => 28
week 2 => 77
week 3 => 31

You may need to get the week number by diving the Day part with 7 and then you may need to round the result using FLOOR.
If dates from different months are there, then its better to add month name along with the week number. So I just did that way. So the first column values would be like monthname weeknumber. And we can group by with the same first column.
Query
SELECT
CONCAT(MONTHNAME(`date`), ' week ', FLOOR(((DAY(`date`) - 1) / 7) + 1)) `month & week`,
SUM(`value`) AS `value`
FROM `group_by_week`
GROUP BY `month & week`
ORDER BY month(`date`), `month & week`;
Result
+-----------------+-------+
| month & week | value |
+-----------------+-------+
| January week 1 | 28 |
| January week 2 | 77 |
| January week 3 | 31 |
+-----------------+-------+
SQL Fiddle Demo

E.g.:
SELECT CEILING(DATE_FORMAT(date,'%d')/7) w
, SUM(value) week_total
FROM group_by_week
GROUP
BY w;
Obviously, you need to extend the logic a little if dealing with more than one month, but I'll leave that as an exercise for the reader.
Note also that the '4' in 'INT(4)' is fairly meaningless - I doubt very much that it does whatever you think it does.

Related

DATEDIFF in MySQL with ranges extending new year

I have a database table with holiday requests in it. I now want to calculate, how many days the user has requested for a certain year. So far I did this:
Table:
CREATE TABLE `holidays` (
`id` int(11) NOT NULL,
`user` int(11) NOT NULL,
`begin` date NOT NULL,
`end` date NOT NULL,
`comment_user` text NOT NULL,
`entered_at` int(11) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Get all holidays in a year:
SELECT SUM((DATEDIFF(end, begin) + 1)) AS days
FROM holidays
WHERE user = :user AND begin LIKE '2021%'
But what can I do, if begin date is in the year before (e.g. 12/30/2020) or end date is in the next year (e.g. 01/05/2022), so the request extends over new year's eve.
EXAMPLE:
If I have a holiday request from 12/30/2020 to 01/02/2021, I wanna count just two days of that, not all four days.
Only the days in 2021
Is there any possibilty to limit the DATEDIFF calculation to 01/01/2021 AND 12/31/2021, but getting all requests affected in that year?
And how can I put that in one mysql-query? I can't find any solution on google for that.
Would be great, if someone had a similar problem and solved that.
Thank's in advance,
Tobias
I created a sample database like this (FIDDLE):
CREATE TABLE `holidays` (
`begin` date DEFAULT NULL,
`end` date DEFAULT NULL
)
INSERT INTO `holidays` VALUES
('2020-12-28','2021-01-05'),
('2020-12-05','2020-12-06'),
('2021-01-06','2021-01-06');
This SQL-statement:
select
begin,
CASE WHEN year(end)>year(begin) then ADDDATE(makedate(year(`end`),1),INTERVAL -1 DAY) else `end` end as END ,
year(begin) as year
from holidays
union all
select
makedate(year(`end`),1),
`end`,
year(`end`)
from holidays
where year(`begin`)<>year(`end`);
will have as output:
+------------+------------+------+
| begin | END | year |
+------------+------------+------+
| 2020-12-28 | 2020-12-31 | 2020 |
| 2020-12-05 | 2020-12-06 | 2020 |
| 2021-01-06 | 2021-01-06 | 2021 |
| 2021-01-01 | 2021-01-05 | 2021 |
+------------+------------+------+
BTW: One should never use, or at least try to avoid, reserved words in a table definition (like 'begin', 'end')

Count days of daterange in daterange

I want to count how many days of a start and end-date are inside another start and end-date. Basically: How many days of the date 01.06.2020 - 06.06.2020 are inside the date 03.06.2020 - 31.12.2020. In this case the answer would be 4. The 3rd, 4th, 5th and 6th of June 2020.
I know that I can use TIMESTAMPDIFF to do calculations between 2 dates, but I can't wrap my head around a simple solution to do it with 2 dates inside 2 dates.
I can't believe nobody ever asked that question, but I can't find any solution to this.
Simple table:
+-------------+--------------+
| Start_date | End_date |
+-------------+--------------+
| 2020-06-03 | 2020-12-31 |
| 2014-09-08 | 2015-09-07 |
| 2015-01-15 | 2015-02-01 |
+-------------+--------------+
I look for something along the lines of:
SELECT * FROM available_dates WHERE TIMESTAMPDIFF(DAY,'2020-06-01','2020-06-06', Start_date, End_date) > 5
You can do this by taking the intersection of the two ranges using GREATEST on the start dates and LEAST on the end dates. Intersecting ranges will return a positive number, non-intersecting a negative one. So we use GREATEST again to zero out negative values to get the overlap. For example:
CREATE TABLE test (
`Start_date` DATE,
`End_date` DATE
);
INSERT INTO test
(`Start_date`, `End_date`)
VALUES
('2020-06-03', '2020-12-31'),
('2014-09-08', '2015-09-07'),
('2015-01-15', '2015-02-01');
SET #start = '2015-01-20';
SET #end = '2015=02-04';
SELECT GREATEST(TIMESTAMPDIFF(DAY, GREATEST(Start_Date, #start), LEAST(End_Date, #end))+1, 0) AS overlap
FROM test;
Output:
overlap
0
16
13
Demo on dbfiddle

Is it possible to auto create querys according specific values?

Is it possible to create multiple querys choosing the day of week?
MYSQL query:
create table reservations (
id bigint(20) NOT NULL,
var_start datetime NOT NULL,
var_end datetime NOT NULL
)
For example
var_day = "3" // Wednesday
var_month = "11" // November
var_year = "2018"
var_start = "11:00” // 11 am
var_end = "13:00” // 1 pm
//This will create all inserts according all wednesday on november 2018.
insert into table var_start = "07-11-2018 11:00:00" var_end = "07-11-2018 13:00:00"
insert into table var_start = "14-11-2018 11:00:00" var_end = "07-11-2018 13:00:00”
insert into table var_start = "21-11-2018 11:00:00" var_end = "07-11-2018 13:00:00”
insert into table var_start = "28-11-2018 11:00:00" var_end = "07-11-2018 13:00:00”
Here is the html demo
I will appreciate some links or concepts to search the correct information about automatic querys or this kind of concepts.
Thanks in advance.
We will be basically dynamically generating all the required dates within the query itself. And then use that result-set to Insert into the reservations table.
I have changed id column to Primary Key and Auto Increment (as it should be).
In a Derived table, we will use a number generator from 0 to 4, as there can be at maximum 5 Wednesdays (and other weekdays) in a month.
Now we will try to get the first Sunday of the required month. For that, we will firstly create a date corresponding to first date of the month, using the input variable values for the month and year:
STR_TO_DATE(CONCAT('2018','11','01'), '%Y%c%d')
Concat('2018','11','01') basically generates 20181101 string. We can then use Str_to_date() function to convert it into MySQL date format. We could have use Concat() function directly to get in YYYY-MM-DD format; but this approach should be robust in case the input month is 9 instead of 09.
Now, we will use various Datetime functions to determine the nth Wednesday. I have expanded over the answer originally given here: https://stackoverflow.com/a/13405764/2469308
Number generator table will help us in calculating the 1st, 2nd, 3rd Wednesday and so on.. We can basically get the first Wednesday by adding 3 number of days to the first Sunday. Afterwards, we will basically add 7 days everytime to get next Wednesday in the month.
Eventually, we will use all these dates and AddTime() to them for determining var_start and var_end accordingly. Also, there is a possibility that in the 5th day, it may cross to next month. So we will filter those out using WHERE MONTH(..) .. AND YEAR(..) .. conditions.
Finally, INSERT INTO.. SELECT statement will be used to insert into the reservations table.
Schema (MySQL v5.7) View on DB Fiddle
create table reservations (
id bigint(20) NOT NULL PRIMARY KEY AUTO_INCREMENT,
var_start datetime NOT NULL,
var_end datetime NOT NULL
);
/*
var_day = "3" // Wednesday
var_month = "11" // November
var_year = "2018"
var_start = "11:00” // 11 am
var_end = "13:00” // 1 pm
*/
Query #1
INSERT INTO reservations (var_start, var_end)
SELECT
ADDTIME(dates.nth_date, '11:00') AS var_start,
ADDTIME(dates.nth_date, '13:00') AS var_end
FROM
(
SELECT
STR_TO_DATE(CONCAT('2018','11','01'), '%Y%c%d') +
INTERVAL (6 -
WEEKDAY(STR_TO_DATE(CONCAT('2018','11','01'), '%Y%c%d')) +
3 +
(7*nth)) DAY AS nth_date
FROM
(SELECT 0 AS nth UNION ALL
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4) AS num_gen
) AS dates
WHERE MONTH(dates.nth_date) = 11 AND
YEAR(dates.nth_date) = 2018;
Query #2
SELECT * FROM reservations;
| id | var_start | var_end |
| --- | ------------------- | ------------------- |
| 1 | 2018-11-07 11:00:00 | 2018-11-07 13:00:00 |
| 2 | 2018-11-14 11:00:00 | 2018-11-14 13:00:00 |
| 3 | 2018-11-21 11:00:00 | 2018-11-21 13:00:00 |
| 4 | 2018-11-28 11:00:00 | 2018-11-28 13:00:00 |
In terms of input variables (prefixed with : for parametric queries), the query would looks as follows:
INSERT INTO reservations (var_start, var_end)
SELECT
ADDTIME(dates.nth_date, :var_start) AS var_start,
ADDTIME(dates.nth_date, :var_end) AS var_end
FROM
(
SELECT
STR_TO_DATE(CONCAT(:var_year,:var_month,'01'), '%Y%c%d') +
INTERVAL (6 -
WEEKDAY(STR_TO_DATE(CONCAT(:var_year,:var_month,'01'), '%Y%c%d')) +
:var_day +
(7*nth)) DAY AS nth_date
FROM
(SELECT 0 AS nth UNION ALL
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4) AS num_gen
) AS dates
WHERE MONTH(dates.nth_date) = :var_month AND
YEAR(dates.nth_date) = :var_year;

mysql, getting time period between two time's when date and time are in separated column

I need to query the info in MySql where I'm given two time strings, so I need to find anything in between.
the format the table looks like
id | date | hour | other | columns | that are not important
-----------------------------------------------------------
1 | 2016-04-11| 1 | asdsa......
2 | 2016-04-11| 2 | asdasdsadsadas...
.
.
.
n | 2016-04-12| 23 | sadasdsadsadasd
Say I have the time strings 2016-04-11 1 and 2016-04-12 23 and I need to find all info from 1 to n. I can separate the date and hour and do a query using BETWEEN...AND for the date, but I have no idea how to fit the time into the formula. Using another BETWEEN definitely won't work, so I definitely need to fit the statement somewhere else. I'm not sure how to proceed though.
WHERE ((`date` = fromDate AND `hour` > fromHour) OR `date` > fromDate)
AND ((`date` = toDate AND `hour` < toHour) OR `date` < toDate)

Query database in weekly interval

I have a database with a created_at column containing the datetime in Y-m-d H:i:s format.
The latest datetime entry is 2011-09-28 00:10:02.
I need the query to be relative to the latest datetime entry.
The first value in the query should be the latest datetime entry.
The second value in the query should be the entry closest to 7 days from the first value.
The third value should be the entry closest to 7 days from the second value.
REPEAT #3.
What I mean by "closest to 7 days from":
The following are dates, the interval I desire is a week, in seconds a week is 604800 seconds.
7 days from the first value is equal to 1316578202 (1317183002-604800)
the value closest to 1316578202 (7 days) is... 1316571974
unix timestamp | Y-m-d H:i:s
1317183002 | 2011-09-28 00:10:02 -> appear in query (first value)
1317101233 | 2011-09-27 01:27:13
1317009182 | 2011-09-25 23:53:02
1316916554 | 2011-09-24 22:09:14
1316836656 | 2011-09-23 23:57:36
1316745220 | 2011-09-22 22:33:40
1316659915 | 2011-09-21 22:51:55
1316571974 | 2011-09-20 22:26:14 -> closest to 7 days from 1317183002 (first value)
1316499187 | 2011-09-20 02:13:07
1316064243 | 2011-09-15 01:24:03
1315967707 | 2011-09-13 22:35:07 -> closest to 7 days from 1316571974 (second value)
1315881414 | 2011-09-12 22:36:54
1315794048 | 2011-09-11 22:20:48
1315715786 | 2011-09-11 00:36:26
1315622142 | 2011-09-09 22:35:42
I would really appreciate any help, I have not been able to do this via mysql and no online resources seem to deal with relative date manipulation such as this. I would like the query to be modular enough to be able to change the interval weekly, monthly, or yearly. Thanks in advance!
Answer #1 Reply:
SELECT
UNIX_TIMESTAMP(created_at)
AS unix_timestamp,
(
SELECT MIN(UNIX_TIMESTAMP(created_at))
FROM my_table
WHERE created_at >=
(
SELECT max(created_at) - 7
FROM my_table
)
)
AS `random_1`,
(
SELECT MIN(UNIX_TIMESTAMP(created_at))
FROM my_table
WHERE created_at >=
(
SELECT MAX(created_at) - 14
FROM my_table
)
)
AS `random_2`
FROM my_table
WHERE created_at =
(
SELECT MAX(created_at)
FROM my_table
)
Returns:
unix_timestamp | random_1 | random_2
1317183002 | 1317183002 | 1317183002
Answer #2 Reply:
RESULT SET:
This is the result set for a yearly interval:
id | created_at | period_index | period_timestamp
267 | 2010-09-27 22:57:05 | 0 | 1317183002
1 | 2009-12-10 15:08:00 | 1 | 1285554786
I desire this result:
id | created_at | period_index | period_timestamp
626 | 2011-09-28 00:10:02 | 0 | 0
267 | 2010-09-27 22:57:05 | 1 | 1317183002
I hope this makes more sense.
It's not exactly what you asked for, but the following example is pretty close....
Example 1:
select
floor(timestampdiff(SECOND, tbl.time, most_recent.time)/604800) as period_index,
unix_timestamp(max(tbl.time)) as period_timestamp
from
tbl
, (select max(time) as time from tbl) most_recent
group by period_index
gives results:
+--------------+------------------+
| period_index | period_timestamp |
+--------------+------------------+
| 0 | 1317183002 |
| 1 | 1316571974 |
| 2 | 1315967707 |
+--------------+------------------+
This breaks the dataset into groups based on "periods", where (in this example) each period is 7-days (604800 seconds) long. The period_timestamp that is returned for each period is the 'latest' (most recent) timestamp that falls within that period.
The period boundaries are all computed based on the most recent timestamp in the database, rather than computing each period's start and end time individually based on the timestamp of the period before it. The difference is subtle - your question requests the latter (iterative approach), but I'm hoping that the former (approach I've described here) will suffice for your needs, since SQL doesn't lend itself well to implementing iterative algorithms.
If you really do need to determine each period based on the timestamp in the previous period, then your best bet is going to be an iterative approach -- either using a programming language of your choice (like php), or by building a stored procedure that uses a cursor.
Edit #1
Here's the table structure for the above example.
CREATE TABLE `tbl` (
`id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
`time` datetime NOT NULL
)
Edit #2
Ok, first: I've improved the original example query (see revised "Example 1" above). It still works the same way, and gives the same results, but it's cleaner, more efficient, and easier to understand.
Now... the query above is a group-by query, meaning it shows aggregate results for the "period" groups as I described above - not row-by-row results like a "normal" query. With a group-by query, you're limited to using aggregate columns only. Aggregate columns are those columns that are named in the group by clause, or that are computed by an aggregate function like MAX(time)). It is not possible to extract meaningful values for non-aggregate columns (like id) from within the projection of a group-by query.
Unfortunately, mysql doesn't generate an error when you try to do this. Instead, it just picks a value at random from within the grouped rows, and shows that value for the non-aggregate column in the grouped result. This is what's causing the odd behavior the OP reported when trying to use the code from Example #1.
Fortunately, this problem is fairly easy to solve. Just wrap another query around the group query, to select the row-by-row information you're interested in...
Example 2:
SELECT
entries.id,
entries.time,
periods.idx as period_index,
unix_timestamp(periods.time) as period_timestamp
FROM
tbl entries
JOIN
(select
floor(timestampdiff( SECOND, tbl.time, most_recent.time)/31536000) as idx,
max(tbl.time) as time
from
tbl
, (select max(time) as time from tbl) most_recent
group by idx
) periods
ON entries.time = periods.time
Result:
+-----+---------------------+--------------+------------------+
| id | time | period_index | period_timestamp |
+-----+---------------------+--------------+------------------+
| 598 | 2011-09-28 04:10:02 | 0 | 1317183002 |
| 996 | 2010-09-27 22:57:05 | 1 | 1285628225 |
+-----+---------------------+--------------+------------------+
Notes:
Example 2 uses a period length of 31536000 seconds (365-days). While Example 1 (above) uses a period of 604800 seconds (7-days). Other than that, the inner query in Example 2 is the same as the primary query shown in Example 1.
If a matching period_time belongs to more than one entry (i.e. two or more entries have the exact same time, and that time matches one of the selected period_time values), then the above query (Example 2) will include multiple rows for the given period timestamp (one for each match). Whatever code consumes this result set should be prepared to handle such an edge case.
It's also worth noting that these queries will perform much, much better if you define an index on your datetime column. For my example schema, that would look like this:
ALTER TABLE tbl ADD INDEX idx_time ( time )
If you're willing to go for the closest that is after the week is out then this'll work. You can extend it to work out the closest but it'll look so disgusting it's probably not worth it.
select unix_timestamp
, ( select min(unix_tstamp)
from my_table
where sql_tstamp >= ( select max(sql_tstamp) - 7
from my_table )
)
, ( select min(unix_tstamp)
from my_table
where sql_tstamp >= ( select max(sql_tstamp) - 14
from my_table )
)
from my_table
where sql_tstamp = ( select max(sql_tstamp)
from my_table )