List of months within multiple date ranges in mysql - mysql

I have a list of date ranges and I like to get a list of all months that are within these date ranges. I can query my date ranges like so:
Select id, start, end
From date_range
And this query would give the following output:
1, 01-01-2016, 25-03-2016
2, 26-03-2016, 30-03-2016
3, 30-12-2016, 08-01-2017
Now I would like to find a MySQL query that just lists all months within these date ranges. So it should give the following output:
01-2016
02-2016
03-2016
12-2016
01-2017
There are already examples here on how to get a list of month between two dates, such as:
Creating a list of month names between two dates in MySQL
How to get a list of months between two dates in mysql
But these examples are about a single date range, but I have multiple date ranges. It would be great if someone can find an sql query for my problem.

Here is a solution:
#DROP TABLE IF EXISTS monthTest;
CREATE TABLE monthTest(id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `start` DATETIME, `end`DATETIME);
INSERT INTO monthTest(`start`, `end`) VALUES
('2016-01-01', '2016-03-25'),
('2016-03-26', '2016-03-30'),
('2016-12-30', '2017-08-01');
SELECT A.`start`, A.`end`, DATE_FORMAT(DATE_ADD(A.`start`, INTERVAL B.help_keyword_id MONTH), '%Y%m') FROM
monthTest A,
mysql.help_keyword B
WHERE PERIOD_DIFF(DATE_FORMAT(A.`end`, '%Y%m'), DATE_FORMAT(A.`start`, '%Y%m')) >= B.help_keyword_id
ORDER BY A.id;
Note that this query in the second JOIN table has a dependency that this table must contain more rows than the maximum number of months between any two dates and the join field must be an incrementing INTEGER starting from 0. This is due to the limitation that mysql doesn't (yet) contain a row generator so a workaround is necessary.
Regards,
James

Related

how can I calculate the SUM in 4days buckets over all dates

I have a MySQL DB where one column is the DATE and the other column is the SIGNAL. Now I would like to calculate the SUM over Signal for 4 days each.
f.e.
SUM(signal over DATE1,DATE2,DATE3,DATE4)
SUM(signal over DATE5,DATE6,DATE7,DATE8)
...
whereas Date_N = successor of DATE_N-1 but need not to be the day before
Moreless the algo should be variable in the days group. 4 ist just an example.
Can anyone here give me an advice how to perform this in MySQL?
I have found this here group by with count, maybe this could be helpful for my issue?
Thanks
Edit: One important note: My date ranges have gaps in it. you see this in the picture below, in the column count(DISTINCT(TradeDate)). It should be always 4 when I have no gaps. But I DO have gaps. But when I sort the date descending, I would like to group the dates together always 4 days, f.e. Group1: 2017-08-22 + 2017-08-21 + 2017-08-20 + 2017-08-19, Group2: 2017-08-18 + 2017-08-17+2017-08-15+2017-08-14, ...
maybe I could map the decending dateranges into a decending integer autoincrement number, then I would have a number without gaps. number1="2017-08-17" number2="2017-08-15" and so on ..
Edit2:
As I see the result from my table with this Query: I might I have double entries for one and the same date. How Can I distinct this date-doubles into only one reprensentative?
SELECT SUM(CondN1),count(id),count(DISTINCT(TradeDate)),min(TradeDate),max(TradeDate) ,min(TO_DAYS(DATE(TradeDate))),id FROM marketstat where Stockplace like '%' GROUP BY TO_DAYS(DATE(TradeDate)) DIV 4 order by TO_DAYS(DATE(TradeDate))
SUM() is a grouping function, so you need to GROUP BY something. That something should change only every four days. Let's start by grouping by one day:
SELECT SUM(signal)
FROM tableName
GROUP BY date
date should really be of type DATE, like you mentioned, not DATETIME or anything else. You could use DATE(date) to convert other date types to dates. Now we need to group by four dates:
SELECT SUM(signal)
FROM tableName
GROUP BY TO_DAYS(date) DIV 4
Note that this will create an arbitary group of four days, if you want control over that you can add a term like this:
SELECT SUM(signal)
FROM tableName
GROUP BY (TO_DAYS(date)+2) DIV 4
In the meantime and with help of KIKO I have found the solution:
I make a temp table with
CREATE TEMPORARY TABLE if not EXISTS tradedatemaptmp (id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY) SELECT Tradedate AS Tradedate, CondN1, CondN2 FROM marketstat WHERE marketstat.Stockplace like 'US' GROUP BY TradeDate ORDER BY TradeDate asc;
and use instead the originate tradedate the now created id in the temp table. So I could manage that - even when I have gaps in the tradedate range, the id in the tmp table has no gaps. And with this I can DIV 4 and get the always the corresponding 4 dates together.

Select leave data from attendance table given the following condition

I have attendance data for employees stored in the table attendance with the following column names:
emp_id (employee ID)
date
type (leave, absent, etc.)
(there are others but I'm omitting them for the sake of simplicity)
My objective is to retrieve all dates of the given month on which the employee was on leave (type = 'Leave') and the last leave taken in the last month, if any.
It's easy to do it using two queries (I'm using PHP to get process the data), but is there any way this can be done in a single query?
I'm answering my own question so as to close it. As #bpgergo pointed out in the comments, UNION will do the trick here.
SELECT * FROM table_name
WHERE type="Leave" AND
date <= (CURRENT_DATE() - 30)
Select the fields, etc you want then se a combined where clause using mysql's CURRENT_DATE() function. I subtracted 30 for 30 days in a month.
If date is a date column, this will return everyone who left 1 month or longer ago.
Edit:
If you want a specific date, change the 2nd month like this:
date <= (date_number - 30)

MySQL Date in where clause

I have a table which contains date (Field Type: Date and Date Format: %Y-%m-%d) as a field. I need to select all the rows from the table for all the years whose date is not between Dec 3rd and Dec 24th.
The table contains month and day as a separate fields.
The result can be obtained by using the following query:
select * from mytable where date not in (select date from mytable where month=12 and day between 3 and 24);
But i m trying to get the result in a single query like the below one but it gave empty rows:
select * from mytable where date not between '%Y-12-03' and '%Y-12-24';
Can it be done in a single query like the above one?
SELECT *
FROM mytable
WHERE MONTH(`date`) <> 12
OR DAY(`date`) NOT BETWEEN 3 AND 24
;
This will give you every row that meets the requirements. I'm sure someone has a faster way of doing this, since this will ignore all indexes and will likely be slow on a large dataset, but it does work and return the data you require, so if no-one can suggest an improvement this will answer your question.

MySQL: Need to Query for missing records from various start dates

I have two tables that are linked by an ID, and one table has a start date, and the child (linked) table has weekly entries of data. I need to be able to query and determine the ID's, that are missing a week's data, without knowing the actual dates.
Table1
ID INT
START_DATE DATE
Table2
ID INT (foreign Key to Table 1)
TRAN_DATE DATE
VALUE INT
Each INT might have a different start date, and the values are saved weekly (every Monday, Tuesday, etc... based on Start Date)
Some IDs will have missed posting their value one week, and I need to look back historically for when a record is missing.
Assuming a Start_Date of Sept 9, 2013, the dates would be (9/9/2013. 9/16/2013, 9/23/2013,...) I need to see if TRAN_DATE for ID 1 is 9/9/2013, then add 7 days (9/16/2013), and check for that record, then add 7 days (9/23/2013) and check for that record to exist. Then repeat for the different IDs. This would end with the current date, or any date into the future (if this is easier).
I can do this with a program simply enough, but I need to do this at a customer site and I can not distribute code into the site, so I need to try to do it with a query).
The following query returns any gaps in table2:
select distinct id
from table2 t2
where t2.tran_date < now() - interval 7 day and
not exists (select 1
from table2 t2a
where t2a.id = t2.id and
datediff(t2a.tran_date, t2.tran_date) = 7
);
This assumes that the first transaction is not missing. Is that possible?

SQL query that returns all dates not used in a table

So lets say I have some records that look like:
2011-01-01 Cat
2011-01-02 Dog
2011-01-04 Horse
2011-01-06 Lion
How can I construct a query that will return 2011-01-03 and 2011-01-05, ie the unused dates. I postdate blogs into the future and I want a query that will show me the days I don't have anything posted yet. It would look from the current date to 2 weeks into the future.
Update:
I am not too excited about building a permanent table of dates. After thinking about it though it seems like the solution might be to make a small stored procedure that creates a temp table. Something like:
CREATE PROCEDURE MISSING_DATES()
BEGIN
CREATE TABLE TEMPORARY DATES (FUTURE DATETIME NULL)
INSERT INTO DATES (FUTURE) VALUES (CURDATE())
INSERT INTO DATES (FUTURE) VALUES (ADDDATE(CURDATE(), INTERVAL 1 DAY))
...
INSERT INTO DATES (FUTURE) VALUES (ADDDATE(CURDATE(), INTERVAL 14 DAY))
SELECT FUTURE FROM DATES WHERE FUTURE NOT IN (SELECT POSTDATE FROM POSTS)
DROP TABLE TEMPORARY DATES
END
I guess it just isn't possible to select the absence of data.
You're right — SQL does not make it easy to identify missing data. The usual technique is to join your sequence (with gaps) against a complete sequence, and select those elements in the latter sequence without a corresponding partner in your data.
So, #BenHoffstein's suggestion to maintain a permanent date table is a good one.
Short of that, you can dynamically create that date range with an integers table. Assuming the integers table has a column i with numbers at least 0 – 13, and that your table has its date column named datestamp:
SELECT candidate_date AS missing
FROM (SELECT CURRENT_DATE + INTERVAL i DAY AS candidate_date
FROM integers
WHERE i < 14) AS next_two_weeks
LEFT JOIN my_table ON candidate_date = datestamp
WHERE datestamp is NULL;
One solution would be to create a separate table with one column to hold all dates from now until eternity (or whenever you expect to stop blogging). For example:
CREATE TABLE Dates (dt DATE);
INSERT INTO Dates VALUES ('2011-01-01');
INSERT INTO Dates VALUES ('2011-01-02');
...etc...
INSERT INTO Dates VALUES ('2099-12-31');
Once this reference table is set up, you can simply outer join to determine the unused dates like so:
SELECT d.dt
FROM Dates d LEFT JOIN Blogs b ON d.dt = b.dt
WHERE b.dt IS NULL
If you want to limit the search to two weeks in the future, you could add this to the WHERE clause:
AND d.dt BETWEEN NOW() AND ADDDATE(NOW(), INTERVAL 14 DAY)
The way to extract rows from the mysql database is via SELECT. Thus you cannot select rows that do not exist.
What I would do is fill my blog table with all possible dates (for a year, then repeat the process)
create table blog (
thedate date not null,
thetext text null,
primary key (thedate));
doing a loop to create all dates entries for 2011 (using a program, eg $mydate is the date you want to insert)
insert IGNORE into blog (thedate,thetext) values ($mydate, null);
(the IGNORE keyword to not create an error (thedate is a primary key) if thedate exists already).
Then you insert the values normally
insert into blog (thedate,thetext) values ($mydate, "newtext")
on duplicate key update thetext="newtext";
Finally to select empty entries, you just have to
select thedate from blog where thetext is null;
You probably not going to like this:
select '2011-01-03', count(*) from TABLE where postdate='2011-01-03'
having count(*)=0 union
select '2011-01-04', count(*) from TABLE where postdate='2011-01-04'
having count(*)=0 union
select '2011-01-05', count(*) from TABLE where postdate='2011-01-05'
having count(*)=0 union
... repeat for 2 weeks
OR
create a table with all days in 2011, then do a left join, like
select a.days_2011
from all_days_2011
left join TABLE on a.days_2011=TABLE.postdate
where a.days_2011 between date(now()) and date(date_add(now(), interval 2 week))
and TABLE.postdate is null;