Weird time calculation in mysql when adding / subtracting months - mysql

I'm trying to perform some time calculations (timestampadd, timestampdiff) on a query but I'm stuck with an unexpected behavior.
I executed in mysql this query:
select timestampdiff(MONTH, timestampadd(MONTH, 1, '2017-01-30'),'2017-01-30')
Using logic is adding to 2017-01-30 one month, then it requests the difference in months between this date and again 2017-01-30.
I'm expecting the result to be trivial and equal to 1 but instead the previous query evaluates to 0.
This screws my calculations.
Why is that?

This is straight forward,
you are adding 1 month in january 30 which will be feb 28 as in 2017
now the difference between jan30 and feb28 is only 29 days which is less than a month value. ( 30 days )
Therefore its 0
for accuracy, you need to handle february with care .

Related

Query data to create statistical chart

I have a table with these fields id, title, ddetail, date_created, type, website_id called Warning. I want query data with a given time and hava the following case(my opinion):
If the month and year are in the beginning time, the end time is equal, I will query weekly warning for that month.(eg, 01/07/2019 - 31/07/2019)
If both are the same year, I will query monthly warning, it mean I will count the number of warnings from the beginning month to the end month. (eg, 1/2019 - 9/2019)
The last case is almost similar to the above case but with longer time periods. (eg, 12/2018 - 3/2019)
Example for above cases:
Time: 01/07/2019 - 31/07/2019, the result:
01/07/2019 - 07/07/2019: 3 warnings
08/07/2019 - 14/07/2019: 0 warning
15/07/2019 - 21/07/2019: 1 warning
22/07/2019 - 28/07/2019: 2 warning
29/07/2019 - 31/07/2019: 0
Case 2, Time: 1/2019 - 6/2019
1/2019: 1 warnings
2/2019: 3 warnings
3/2019: 0 warnings
4/2019: 1 warnings
5/2019: 2 warnings
6/2019: 0 warnings
this is my solution, but I can't write SQL query. I need 3 SQL query for 3 case and fix my solution if possible.
To begin with, we know that we need to select warnings, then group them by a range of times. I am going to group by calendar week to start with.
All the warnings in a month:
SELECT `id` from warnings WHERE date_created >= 2019-07-01 AND date_created <= 2019-07-31;
To get HOW MANY warnings were in the month, it's almost the same:
SELECT count(`id`) from warnings WHERE date_created >= 2019-07-01 AND date_created <= 2019-07-31;
That will return one row with a single value in it. Not very interesting yet. To find out how many warnings happened each (calendar) week, you can group the results by the week.
SELECT count(`id`) as num_warnings, WEEK(date_created) as weeknum
FROM warning
WHERE `date_created` >= 2019-07-01 AND date_created <= 2019-07-31
GROUP BY weeknum;
This will give you the number of warnings in a calendar week. If the month started on a Friday, the first week will have a low number.
To query for seven-day intervals starting on the first of the month, things get a lot more complicated. (Also, obviously the last "week" won't be a full seven days.)
To help, I first referred to SELECT / GROUP BY - segments of time (10 seconds, 30 seconds, etc) which talks about grouping by a number of seconds. A week is 60*60*24*7 seconds, so the answer can be converted pretty easily - but there's a catch we will get to.
SELECT count(`id`) as num_warnings as weeknum
FROM warning
WHERE `date_created` >= 2019-07-01 AND date_created <= 2019-07-31
GROUP BY UNIX_TIMESTAMP(date_created) DIV 604800
This takes the timestamp of the warning and divides it by the number of seconds in a week and chops off the decimal. So every 604800 seconds the division will increase by 1. Almost there, but here's the catch: this will tell you how many weeks it has been since January 1, 1973, and you want to know how many weeks it has been since the first of the month. Put another way, you want zero to be at the start of the month, not in 1973.
SELECT count(`id`) as num_warnings
FROM warnings
WHERE `date_created` >= 2019-07-01 AND date_created <= 2019-07-31
GROUP BY (UNIX_TIMESTAMP(date_created) - UNIX_TIMESTAMP('2019-07-01')) DIV 604800
That's pretty much it for dividing a month by weeks. I know almost nothing of Django, so I can't help you with the code that would generate the query.
But what about dividing a year by months? At first it seems like a similar problem, but there's a catch: How many seconds are there in a month?
The answer for grouping by month over a year is actually more like the original solution above for dividing by week. It works because the year always starts at the beginning of a month:
SELECT count(`id`) as num_warnings, MONTH(date_created) as monthNum
FROM warnings
WHERE `date_created` >= 2019-01-01 AND date_created <= 2019-12-31
GROUP BY monthNum;
Should get you close to where you want to go.
The two queries are different enough that you will want to recognize the different cases in your Django code and build the appropriate query.

Group by average intervals across a timeframe

So let's say that I want to keep track of my CPU temperature, with simple columns 'date' and 'temperature'. I'd like to see what period saw the highest temperatures on average in the last week. I capture the data every 10 minutes, so I want each 10 minute block averaged with the same block from the other days of the week.
So for example:
2018-01-08 02:00:00 78.3
2018-01-08 03:00:00 81.2
2018-01-09 02:00:00 74.1
2018-01-09 03:00:00 75.9
I would want the averages of each day # 02:00:00, each day # 03:00:00, and so on. (except the real data is every 10 minutes) The exact datetime varies - it's not always 02:00:02, sometimes it could be 02:00:07, etc., so I can't just do an exact conditional.
Any idea how I'd go about making this data? I assume there's some way I can use GROUP BY for this, but I'm lost as to how.
Format just the hour and minute, and group by that.
SELECT DATE_FORMAT(date, '%H:%i') AS time, AVG(temperature) AS temp
FROM yourTable
GROUP BY time
This assumes that the readings are never delayed by more than a minute from the expected 10-minute periods -- if the reading for 02:10 happens at 02:11:01 it will not be put in the group.

date calculation with full date in separate columns

I'm trying to make a query for the last 3 months of an item with my month and year in separate columns like so:
YEAR_ PERIOD
2014 5
2013 6
2013 11
2011 6
2009 2
The query needs to always start from the current month and year. I've tried using DateAdd(), DateSerial(), and DateDiff() none of those worked. Whenever I try to use month(now()-3) i'm getting 2 instead of 11.
Adding or subtracting integers and dates simply adds or subtracts days from the date. So Now() - 3 results in 2016-02-15 (it is 2016-02-18 at the time of this posting). That is clearly still the month of February - hence your result of two.
Give this a try Month(DateAdd("m", -3, Now)). Here we are adding -3 months to the current date and then getting the resulting month. Based on today's date that will result in 11.
I figured it out.
DateDiff("m",CDate(Format([PERIOD] & "/" & [YEAR_],"mm/yyyy")),Now())
This took the two fields and made them a single date. I then took the difference from this month and the months between the two dates. I then set the criteria to <= 3.
Addendum
It can be simplified to:
DateDiff("m",CDate([PERIOD] & "/" & [YEAR_]),Date())
In general, however, you should never use string handling for dates if it can be avoided, and it easily can:
DateDiff("m",DateSerial([YEAR_],[PERIOD],1)),Date())

SQL / MySQL - how to get a unique week number (independent of the year)?

I'd like to get the number of a week from a DATE starting with Monday as the first day of the week. While WEEK() can partially accomplish this, I would like each week to be uniquely identified. I.e., rather than rolling over at 52 or 53 to 0 or 1, to continue counting to week 54, 55, etc.
What is the best way to accomplish this in SQL?
If the week numbers should be sequential (perhaps for calculating time spans), you can pick an arbitrary Sunday in the past that should be week 1, count how many days since that day, and divide by 7. (Choosing Sunday will make Monday the start of the week.)
SELECT CEIL( DATEDIFF( '2013-01-04', '1970-01-04' ) / 7 ) AS week; # 2244
If all you need is unique identification, you could use YEARWEEK() to get 201253, 201301 and so on.
SELECT YEARWEEK( '2013-01-04', 1 ) AS week; # 201301
I use the following solution, to make a unique string from month and a week
id=str(time.month)+str(time.isocalendar()[1])

SQL: Where between two dates without year?

I'm trying to query through historical data and I need to return data just from a 1 month period: 2 weeks back and 2 weeks forward,but I need the year to not matter.
So, if I was to make the query today I would want all rows with date between xxxx-06-31 and xxxx-07-27
Thanks in advance for the help!
EDIT:
I've tried two ways. both of which I believe will not work around the new year. One is to use datepart(day) and the other would be to simply take the year off of date and compare.
The best way to think of this problem is to convert your dates to a number between 0 and 365 corresponding to the day in the year. Then simply choosing dates where this difference is less than 14 gives you your two week window.
That will break down at the beginning or end of the year. But simple modular arithmetic gives you the answer.
Fortunately, MySQL has DAYOFYEAR(date), so it's not so complicated:
SELECT * FROM tbl t
WHERE
MOD(DAYOFYEAR(currdate) - DAYOFYEAR(t.the_date) + 365, 365) <= 14
OR MOD(DAYOFYEAR(t.the_date) - DAYOFYEAR(currdate) + 365, 365) <= 14
That extra + 365 is needed since MySQL's MOD will return negative numbers.
This answer doesn't account for leap years correctly. If the current year is not a leap year and the currdate is within 14 days of the end of the year, then you'll miss one day in Jan that you should have included. If you care about that, then you should replace 365 with [the number of days in the year - 1].
Supposed you have a date like this,
create table datelist
(
d date
);
insert into datelist values
('2012-07-01'),
('2011-06-29'),
('2012-07-02'),
('2010-07-05'),
('2012-05-31'),
('2010-06-30');
Try this query below,
SELECT d, date_format(d,'%Y-%b-%d')
FROM datelist
WHERE (MONTH(d) = 6 AND DAYOFMONTH(d) >= 30)
OR (MONTH(d) = 7 AND DAYOFMONTH(d) <= 27)
SQLFiddle Demo
Is it OK if the solution is terribly slow?
SELECT tbl.*
FROM tbl
INNER JOIN (SELECT COALESCE(DATE(CONCAT(yyyy, '-', MONTH(CURRENT_DATE), '-', DAYOFMONTH(CURRENT_DATE)),
DATE(CONCAT(yyyy, '-02-28'))) AS midpoint
FROM (SELECT DISTINCT(YEAR(d)) AS yyyy
FROM tbl) all_years) adjusted
ON tbl.datecol BETWEEN adjusted.midpoint - INTERVAL 2 WEEK
AND
adjusted.midpoint + INTERVAL 2 WEEK;
That computes all midpoints for all years in the data set, and then pulls records +- 2 weeks from any such midpoint, which handles end-of-year wrapping.
The COALESCE handles 02-29 on years without leapday (which MySQL will NULL-ify), forcing it down to 02-28.