Group-by data based on conditions - sql-server-2008

I will be getting some data which I need to filter out based on some conditions. Sample data:
Cust_ID Date Result
1 2013-08-15 On hold
2 2013-08-16 NULL
3 2013-08-18 WIP
1 2013-08-20 Completed
3 2013-08-25 NULL
4 2013-08-28 NULL
4 2013-08-29 NULL
Conditions:
Fetch the distinct Cust_ID, based on latest Date.(i.e Max(Date))
If the Result is Null for latest Date then get the latest record with any other Result apart from NULL.
If the Result is NULL for all the records with same Cust_ID, pick the latest one based on Date
The desired output should be:
Cust_ID Date Result
1 2013-08-20 Completed
2 2013-08-16 NULL
3 2013-08-18 WIP
4 2013-08-29 NULL
Please advise.

You can do it easy with a CTE, note the CTE is not "needed" (you could use sub-queries) but I think it makes it clear what you are doing.
WITH NonNull AS
(
SELECT CustID, MAX(Date) as Date
FROM tablename
GROUP BY CustID
WHERE Result is not null
), Others AS
(
SELECT CustID, MAX(Date) as Date
FROM tablename
GROUP BY CustID
WHERE CustID NOT IN (SELECT CustID FROM NonNull)
), AlltogetherNow -- not really needed but clearer
(
SELECT CustID, Date
FROM NonNull
UNION ALL
SELECT CustID, Date
FROM Others
)
SELECT A.CustID, A.Date, J.Results
FROM AlltogetherNow A
JOIN tablename J ON A.CustID = J.CustID AND A.Date = J.Date

First you need an IS NULL indicator on each row:
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE dbo.Results
([CustID] int, [Date] datetime, [Result] varchar(9))
GO
INSERT INTO dbo.Results
([CustID], [Date], [Result])
VALUES
(1, '2013-08-15 00:00:00', 'On Hold'),
(2, '2013-08-16 00:00:00', NULL),
(3, '2013-08-18 00:00:00', 'WIP'),
(1, '2013-08-20 00:00:00', 'Completed'),
(3, '2013-08-25 00:00:00', NULL),
(4, '2013-08-28 00:00:00', NULL),
(4, '2013-08-29 00:00:00', NULL)
GO
Query 1:
SELECT *,CASE WHEN Result IS NULL THEN 0 ELSE 1 END IsNotNull
FROM dbo.Results
Results:
| CUSTID | DATE | RESULT | ISNOTNULL |
|--------|-------------------------------|-----------|-----------|
| 1 | August, 15 2013 00:00:00+0000 | On Hold | 1 |
| 2 | August, 16 2013 00:00:00+0000 | (null) | 0 |
| 3 | August, 18 2013 00:00:00+0000 | WIP | 1 |
| 1 | August, 20 2013 00:00:00+0000 | Completed | 1 |
| 3 | August, 25 2013 00:00:00+0000 | (null) | 0 |
| 4 | August, 28 2013 00:00:00+0000 | (null) | 0 |
| 4 | August, 29 2013 00:00:00+0000 | (null) | 0 |
Then you need to determine the first NULL row and the first NOT NULL row for each customer. You can use the ROW_NUMBER() function for that. You also need to know for each customer if there are any NOT NULL rows:
Query 2:
SELECT *,
ROW_NUMBER()OVER(PARTITION BY CustID,IsNotNull ORDER BY [Date] DESC) _rn,
COUNT(Result)OVER(PARTITION BY CustID) NotNullCount
FROM(
SELECT *,CASE WHEN Result IS NULL THEN 0 ELSE 1 END IsNotNull
FROM dbo.Results
)X1
Results:
| CUSTID | DATE | RESULT | ISNOTNULL | _RN | NOTNULLCOUNT |
|--------|-------------------------------|-----------|-----------|-----|--------------|
| 1 | August, 20 2013 00:00:00+0000 | Completed | 1 | 1 | 2 |
| 1 | August, 15 2013 00:00:00+0000 | On Hold | 1 | 2 | 2 |
| 2 | August, 16 2013 00:00:00+0000 | (null) | 0 | 1 | 0 |
| 3 | August, 25 2013 00:00:00+0000 | (null) | 0 | 1 | 1 |
| 3 | August, 18 2013 00:00:00+0000 | WIP | 1 | 1 | 1 |
| 4 | August, 29 2013 00:00:00+0000 | (null) | 0 | 1 | 0 |
| 4 | August, 28 2013 00:00:00+0000 | (null) | 0 | 2 | 0 |
Finally you can just filter out the first NOT NULL row if there are any or the first NULL row if there are no NOT NULL rows by using the calculated row number:
Query 3:
SELECT CustID,[Date],Result
FROM(
SELECT *,
ROW_NUMBER()OVER(PARTITION BY CustID,IsNotNull ORDER BY [Date] DESC) _rn,
COUNT(Result)OVER(PARTITION BY CustID) NotNullCount
FROM(
SELECT *,CASE WHEN Result IS NULL THEN 0 ELSE 1 END IsNotNull
FROM dbo.Results
)X1
)X2
WHERE _rn = 1 AND SIGN(NotNullCount) = IsNotNull
Results:
| CUSTID | DATE | RESULT |
|--------|-------------------------------|-----------|
| 1 | August, 20 2013 00:00:00+0000 | Completed |
| 2 | August, 16 2013 00:00:00+0000 | (null) |
| 3 | August, 18 2013 00:00:00+0000 | WIP |
| 4 | August, 29 2013 00:00:00+0000 | (null) |

Related

Count from multiple tables with group

I have two tables. The first tables registers admissions.
The second one has exits, as shown below:
Table 1: Admissions
+----------+---------------------+---------+
| entry_id | join_date | name |
+----------+---------------------+---------+
| 26 | 2017-01-01 00:00:00 | James |
| 29 | 2017-01-01 00:00:00 | Jan |
| 27 | 2017-01-01 00:00:00 | Chris |
| 28 | 2017-01-01 00:00:00 | Mary |
| 22 | 2017-01-02 00:00:00 | Anna |
| 21 | 2017-01-02 00:00:00 | Andy |
| 24 | 2017-01-02 00:00:00 | Bob |
| 20 | 2017-01-04 00:00:00 | Alice |
| 23 | 2017-01-04 00:00:00 | Chris |
| 25 | 2017-01-04 00:00:00 | Happy |
+----------+---------------------+---------+
Table 2: Exits
+----------+---------------------+----------+
| entry_id | exit_date | name |
+----------+---------------------+----------+
| 322 | 2017-01-01 00:00:00 | Kay |
| 344 | 2017-01-01 00:00:00 | Agnes |
| 920 | 2017-01-02 00:00:00 | Andre |
| 728 | 2017-01-02 00:00:00 | Mark |
| 583 | 2017-01-03 00:00:00 | Alsta |
| 726 | 2017-01-03 00:00:00 | Bull |
| 816 | 2017-01-03 00:00:00 | Jane |
| 274 | 2017-01-04 00:00:00 | Jack |
| 723 | 2017-01-04 00:00:00 | Anna |
| 716 | 2017-01-04 00:00:00 | Bill |
+----------+---------------------+----------+
I am looking for a solution to know the number of admissions, the number of exits and the balance, grouped by date.
I am looking for this >
+---------------------+--------+--------+-----------+
| date | joins | exist | net |
+---------------------+--------+--------+-----------+
| 2017-01-01 00:00:00 | 4 | 2 | 2 |
| 2017-01-02 00:00:00 | 3 | 2 | 1 |
| 2017-01-03 00:00:00 | 0 | 3 | -3 |
| 2017-01-04 00:00:00 | 3 | 3 | 0 |
+---------------------+--------+--------+-----------+
Notes: There may be days when admissions occur, but no exits are registered and vice versa.
Here you go:
SELECT
d,
SUM(CASE WHEN t = 'j' THEN 1 ELSE 0 END) as joins,
SUM(CASE WHEN t = 'x' THEN 1 ELSE 0 END) as exits,
SUM(CASE WHEN t = 'j' THEN 1 ELSE 0 END) - SUM(CASE WHEN t = 'x' THEN 1 ELSE 0 END) as net
FROM
(SELECT join_date as d, 'j' as t FROM admissions) j
UNION ALL
(SELECT exit_date as d, 'x' as t FROM exits) x
GROUP BY d
We concatenate the data using a UNION ALL and make a note of its type- join or exit, with a simple char we can compare later
We group this up by d, giving one date per line, and we sum the result of conditionally looking at whether its a 'j'oin or an e'x'it. If the line is a j, then a 1 is added to the column tracking the joins total for that day, and so on
The only thing this doesn't give you, is days where there are no joins or exits.. (For example 2018-12-25, 0, 0, 0 because xmas day was closed and noone did anything on that day).. But you didn't say you wanted those.
If you do want lines with a date, and 0 exits, 0 joins, 0 net, then we have to we have to work some additional magic, and it's a bit more of a headache/makes it harder to understand (so i left it out)
Following will do:
select
CASE WHEN join_date is not null THEN join_date
WHEN exit_date is not null THEN exit_date END as date,
entry.cnt as joins,
exit.cnt as exits,
(extry.cnt - exit.cnt) as net
FROM
(select join_date, COALESCE(count(*), 0) as cnt from Admissions group by join_date) entry
FULL OUTER JOIN
(select exit_date, COALESCE(count(*), 0) as cnt from Exits group by exit_date) exit
ON
entry.join_date=exit.exit_date
;
I didn't find the answer. Here's an answer from one of my friends, below is a MySQL version:
select aa.date, IFNULL(aa.joins, 0) joins, IFNULL(bb.exits,0) exits, (IFNULL(aa.joins,0) - IFNULL(bb.exits,0)) net
from
(
select join_date date, count(name) joins
from Admissions
group by join_date
) aa
left join
(
select exit_date date, count(name) exits
from Exits
group by exit_date
) bb on aa.date = bb.date
UNION
select bb.date, IFNULL(aa.joins, 0) joins, IFNULL(bb.exits,0) exits, (IFNULL(aa.joins,0) - IFNULL(bb.exits,0)) net
from
(
select join_date date, count(name) joins
from Admissions
group by join_date
) aa
right join
(
select exit_date date, count(name) exits
from Exits
group by exit_date
) bb on aa.date = bb.date order by date;

MYSQL: Left JOIN from two SELECT to "fill gaps" in dates

Let's say I have a table "calendar"
+------------+
| day_date |
+------------+
| 2015-01-01 |
| 2015-01-02 |
| 2015-01-03 |
| .......... |
| 2015-07-14 |
| 2015-07-15 |
+------------+
With this query I can select the WEEK (that I need)
SELECT WEEK(day_date,1) AS NUM_WEEK,
YEAR(day_date) AS YEAR,
STR_TO_DATE(CONCAT(YEAR(day_date),WEEK(day_date,1),' Monday'), '%X%V %W') AS date_start
FROM calendar
GROUP BY NUM_WEEK
And this is the result:
+----------+------+------------+
| NUM_WEEK | YEAR | date_start |
+----------+------+------------+
| 29 | 2015 | 2015-07-20 |
| 30 | 2015 | 2015-07-27 |
| 31 | 2015 | 2015-08-03 |
| 32 | 2015 | 2015-08-10 |
| 33 | 2015 | 2015-08-17 |
| 34 | 2015 | 2015-08-24 |
| 35 | 2015 | 2015-08-31 |
| 36 | 2015 | 2015-09-07 |
| 37 | 2015 | 2015-09-14 |
| 38 | 2015 | 2015-09-21 |
| 39 | 2015 | 2015-09-28 |
| 40 | 2015 | 2015-10-05 |
| 41 | 2015 | 2015-10-12 |
| 42 | 2015 | 2015-10-19 |
| 43 | 2015 | 2015-10-26 |
+----------+------+------------+
Now I have another table:
+----+------------+--------+---------------------+
| id | id_account | amount | date_transaction |
+----+------------+--------+---------------------+
| 1 | 283 | 150 | 2015-06-21 15:50:47 |
| 2 | 283 | 47.74 | 2015-07-23 15:55:44 |
| 3 | 281 | 21.55 | 2015-08-24 12:27:11 |
| 4 | 283 | 11.22 | 2015-08-25 10:00:54 |
+----+------------+--------+---------------------+
They are gaps in date.
With a similar query:
SELECT WEEK(date_transaction,1) AS NUM_WEEK,
YEAR(date_transaction) AS YEAR,
STR_TO_DATE(CONCAT(YEAR(date_transaction),WEEK(date_transaction,1),' Monday'), '%X%V %W')
AS date_start,
transaction.id_account,
SUM(amount) as total FROM transaction
INNER JOIN account ON account.id_account = transaction.id_account
WHERE amount > 0 AND transaction.id_account
IN ( SELECT id_account FROM account WHERE id_customer = 12 )
GROUP BY id_account, WEEK(date_transaction,1)
I obtain this result (probably data are not accurate, referring to previous tables, just to explain).
+----------+------+------------+-----------+----------+
| NUM_WEEK | YEAR | date_start | idAccount | total |
+----------+------+------------+-----------+----------+
| 29 | 2015 | 2015-07-20 | 281 | 22377.00 |
| 30 | 2015 | 2015-07-27 | 281 | 11550.00 |
| 32 | 2015 | 2015-08-04 | 281 | 4500.00 |
| 30 | 2015 | 2015-07-27 | 283 | 1500 |
+----------+------+------------+-----------+----------+
What I would, RIGHT (or LEFT) JOINING the two tables?
The min (and max) WEEK, so I can... (see 2)
Fill the gaps with missing WEEKS with NULL VALUES.
E.g., in a more complicated resultset:
+----------+------+------------+-----------+----------+
| NUM_WEEK | YEAR | date_start | idAccount | total |
+----------+------+------------+-----------+----------+
| 29 | 2015 | 2015-07-20 | 281 | 22377.00 |
| 30 | 2015 | 2015-07-27 | 281 | 11550.00 |
| 31 | 2015 | 2015-07-02 | 281 | NULL |
| 32 | 2015 | 2015-08-09 | 281 | 4500.00 |
| 29 | 2015 | 2015-08-09 | 283 | NULL |
| 30 | 2015 | 2015-07-16 | 283 | 1500 |
| 31 | 2015 | 2015-07-16 | 283 | NULL |
| 32 | 2015 | 2015-07-16 | 283 | NULL |
+----------+------+------------+-----------+----------+
Note, for example, that id=283 now has NULL at WEEK 29, 31 and 32, for example, like id=281 has NULL in WEEK 31.
I prepared also SQLFiddle here: http://sqlfiddle.com/#!9/a8fdc/3
Thank you very much.
I take a look on your question and i came up with this solution. Here is how your query could look like:
SELECT t1.NUM_WEEK, t1.`YEAR`, t1.date_start, t1.id_account, t2.total
FROM (SELECT c.NUM_WEEK, c.`YEAR`, c.date_start, a.id_account
FROM (SELECT WEEK(day_date,1) AS NUM_WEEK,
YEAR(day_date) AS `YEAR`,
STR_TO_DATE(CONCAT(YEAR(day_date),WEEK(day_date,1),' Monday'), '%X%V %W') AS date_start,
(SELECT GROUP_CONCAT(id_account) FROM account WHERE id_customer=12) AS accounts_id
FROM calendar
GROUP BY NUM_WEEK) c
INNER JOIN account a
ON FIND_IN_SET(a.id_account, c.accounts_id)
ORDER BY a.id_account, c.NUM_WEEK) t1
LEFT JOIN
(SELECT WEEK(t.date_transaction,1) AS NUM_WEEK,
YEAR(t.date_transaction) AS `YEAR`,
STR_TO_DATE(CONCAT(YEAR(t.date_transaction),WEEK(t.date_transaction,1),' Monday'), '%X%V %W') AS date_start,
t.id_account, SUM(t.amount) AS total
FROM `transaction` t
INNER JOIN account a
ON a.id_account = t.id_account
WHERE t.amount > 0 AND
t.id_account IN (SELECT id_account FROM account WHERE id_customer = 12)
GROUP BY id_account, WEEK(date_transaction,1)) t2
ON t1.NUM_WEEK = t2.NUM_WEEK AND t1.YEAR = t2.YEAR AND t1.id_account = t2.id_account;
Here is SQL Fiddle for that so you can check up result. Hope that is what are you looking for.
Little explanation:
First think i done is that I little modified your first query where you extract data from table calendar and add there one new column called accounts_id. That query now look's like this:
SELECT WEEK(day_date,1) AS NUM_WEEK,
YEAR(day_date) AS `YEAR`,
STR_TO_DATE(CONCAT(YEAR(day_date),WEEK(day_date,1),' Monday'), '%X%V %W') AS date_start,
(SELECT GROUP_CONCAT(id_account) FROM account WHERE id_customer=12) AS accounts_id
FROM calendar
GROUP BY NUM_WEEK
Please pay attention on this line in SELECT statement
(SELECT GROUP_CONCAT(id_account) FROM account WHERE id_customer=12) AS accounts_id
Note that when you select for specific customer you need to change customer ID in this line too!!!
Here is Fiddle so you can check result that this query produce.
This is necessary because we need to connect each week with each account to get desired result.
Next step is to extend previous query so we could separate accounts_id column (look result of previous query) so we could get row for each value in that column. Extended query look like this:
SELECT c.NUM_WEEK, c.`YEAR`, c.date_start, a.id_account
FROM (SELECT WEEK(day_date,1) AS NUM_WEEK,
YEAR(day_date) AS `YEAR`,
STR_TO_DATE(CONCAT(YEAR(day_date),WEEK(day_date,1),' Monday'), '%X%V %W') AS date_start,
(SELECT GROUP_CONCAT(id_account) FROM account WHERE id_customer=12) AS accounts_id
FROM calendar
GROUP BY NUM_WEEK) c
INNER JOIN account a
ON FIND_IN_SET(a.id_account, c.accounts_id)
ORDER BY a.id_account, c.NUM_WEEK
and output you can see in this Fiddle
After that all we need to do is to make left join between this query and query you already wrote in your question (last query).
There might be a better solution or even this one maybe can be improved a little, but I don't have much time now to deal with that and this is the first think that cross my mind...
GL!
P. S. pay attention when you use reserved word in MySQL like YEAR, TRANSACTION etc for column name (as column_name).. that can cause you a treble if have to use them in name of column or table use backquote () to mark them (asyear`)...

mysql query for getting distinct and lastest record

I have made a view(joining four tables) like below:
ID | BookID | date | points |
1 | 11 | 2014-11-01 | 15 |
1 | 11 | 2015-01-01 | 16 |
1 | 11 | 2014-12-01 | 17 |
1 | 12 | 2014-02-11 | 18 |
1 | 12 | 2014-03-11 | 19 |
1 | 12 | 2014-04-11 | 15 |
1 | 13 | 2014-12-23 | 121 |
1 | 14 | 2014-01-15 | 113 |
1 | 14 | 2014-02-08 | 112 |
I want the result of this view as below
ID | BookID | Date | points |
1 | 11 | 2015-01-01 | 16 |
1 | 12 | 2014-04-11 | 15 |
1 | 13 | 2014-12-23 | 121 |
1 | 14 | 2014-02-08 | 112 |
It should be like Distincit Book ID with max date and showing as seprate points.
So far i have tried the group by with join and group by with date. But it is getting a bit over as i am unable to find a solution to this.
My Query is:
SELECT m1.* FROM viewPoints m1 LEFT JOIN viewPoints m2
ON (m1.BookID = m2.BookID AND m1.Date < m2.Date)
WHERE m1.ID= 1 and m2.Date IS NULL
ORDER BY m1.BookID
Any help! Thanks in Advance.
Maybe this is what you want?
select v.*
from viewPoints v
join (
select
BookID,
max(date) max_date
from viewPoints
where points is not null
group by BookID
) v2 on v.BookID = v2.BookID and v.date = v2.max_date
where v.points is not null
order by v.BookID
Sample SQL Fiddle
Sample output:
| ID | BOOKID | DATE | POINTS |
|----|--------|---------------------------------|--------|
| 1 | 11 | January, 01 2015 00:00:00+0000 | 16 |
| 1 | 12 | April, 11 2014 00:00:00+0000 | 15 |
| 1 | 13 | December, 23 2014 00:00:00+0000 | 121 |
| 1 | 14 | February, 08 2014 00:00:00+0000 | 112 |
SELECT *
FROM tablename
WHERE DATE
IN (
SELECT MAX( DATE )
FROM tablename
GROUP BY bookid
ORDER BY DATE DESC
)
ORDER BY DATE DESC
CREATE VIEW [BOOKLIST] AS
SELECT m1.* FROM viewPoints m1 LEFT JOIN viewPoints m2
ON (m1.BookID = m2.BookID AND m1.Date < m2.Date)
WHERE m1.ID= 1 and m2.Date IS NULL
ORDER BY m1.BookID
SELECT ID, DISTINCT BookID, Date, points FROM BOOKLIST
WHERE Date BETWEEN "start date" AND "end date"

MYSQL : How to select every YEAR_MONTH between two dates?

What I want to do :
I have a table like this :
TABLE mytable
- ID (INT)
- START (DATETIME)
- END (DATETIME)
Let's say I have these rows :
| ID | START | END |
|--------------------------------------------------
| 1 | 2014-01-02 00:00:00 | 2014-12-02 00:00:00 | => month between : 12
| 2 | 2014-01-03 00:00:00 | 2015-02-03 00:00:00 | => month between : 14
Note : the "month between" include the start and end months
I for each YEAR_MONTH between START and END, I want to display a row like this :
ID | MONTH | YEAR
---------------------
1 | 1 | 2014
1 | 2 | 2014
1 | 3 | 2014
1 | 4 | 2014
1 | 5 | 2014
1 | 6 | 2014
1 | 7 | 2014
1 | 8 | 2014
1 | 9 | 2014
1 | 10 | 2014
1 | 11 | 2014
1 | 12 | 2014
2 | 1 | 2014
2 | 2 | 2014
2 | 3 | 2014
2 | 4 | 2014
2 | 5 | 2014
2 | 6 | 2014
2 | 7 | 2014
2 | 8 | 2014
2 | 9 | 2014
2 | 10 | 2014
2 | 11 | 2014
2 | 12 | 2014
2 | 1 | 2015
2 | 2 | 2015
So 12 records for ID 1 and 14 for ID 2.
I'm a bit stuck when the number of month is > 12
WHERE I AM :
I'm doing this :
SELECT mytable.id,
months.id as month,
YEAR(start) as year
FROM mytable
/* Join on a list from 1 to 12 */
LEFT JOIN (SELECT 1 as id UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 10 UNION SELECT 11 UNION SELECT 12)
as months ON months.id BETWEEN MONTH(start) AND MONTH(end)
order by mytable.id, month, year
So ID 2 only has 2 rows for month 1 and 2 :
ID | MONTH | YEAR
---------------------
1 | 1 | 2014
1 | 2 | 2014
1 | 3 | 2014
1 | 4 | 2014
1 | 5 | 2014
1 | 6 | 2014
1 | 7 | 2014
1 | 8 | 2014
1 | 9 | 2014
1 | 10 | 2014
1 | 11 | 2014
1 | 12 | 2014
2 | 1 | 2014
2 | 2 | 2014
Do you have any ideas or advices for this problem ?
Is there a way to extract every YEAR_MONTH between two dates ?
Thank you.
HELPER :
Here is a script to create the table and insert the 2 rows mentionned :
CREATE TABLE mytable (
id INT PRIMARY KEY auto_increment,
start DATETIME NOT NULL,
end DATETIME NOT NULL
);
INSERT INTO mytable (start,end) VALUES
("2014-01-02 00:00:00","2014-12-02 00:00:00"),
("2014-01-03 00:00:00","2015-02-03 00:00:00");
If I understand you correctly, you need a table with dates (year - month) between each start and end date.
There's no simple select statement that will give you this, but you can create a procedure to do it. You need to create a temporary table, fill it with the values you need and then output the result.
Here's my proposed solution (considering a permanent table):
SQL Fiddle
MySQL 5.5.32 Schema Setup:
CREATE TABLE mytable (
id INT PRIMARY KEY auto_increment,
start DATETIME NOT NULL,
end DATETIME NOT NULL
)//
INSERT INTO mytable (start,end) VALUES
("2014-01-02 00:00:00","2014-12-02 00:00:00"),
("2014-01-03 00:00:00","2015-02-03 00:00:00")//
create procedure year_month_table()
begin
-- Declare the variables to fill the years_months table
declare id int;
declare start_date, end_date, d date;
-- Declare the "done" variable for the loop that fills the table,
-- the cursor to read the data, and the handler to check if the
-- loop should end.
declare done int default false;
declare cur_mytable cursor for
select * from mytable;
declare continue handler for not found
set done = true;
-- Create the table to hold your data
create table if not exists years_months (
row_id int unsigned not null auto_increment primary key,
id int not null,
month int,
year int,
unique index dedup(id, year, month),
index idx_id(id),
index idx_year(year),
index idx_month(month)
);
-- Open the cursor to read the ids and the start and end dates for each one
open cur_mytable;
-- Disable the indexes to speed up insertion
alter table years_months disable keys;
-- Start the loop
loop_data: loop
-- Read the values from your table and store them in the variables
fetch cur_mytable into id, start_date, end_date;
-- If you've reached the end of the table, then you must exit the loop
if done then
leave loop_data;
end if;
-- Initialize the date to fill the table
set d = start_date;
while d <= end_date do
-- Insert the values in your table
insert ignore into years_months (id, month, year) values (id, month(d), year(d));
-- Increment the d variable in 1 month
set d = date_add(d, interval +1 month);
end while;
end loop;
close cur_mytable;
-- Enable the indexes again
alter table years_months enable keys;
-- Show the result
select * from years_months;
end //
Query 1:
select * from mytable
Results:
| ID | START | END |
|----|--------------------------------|---------------------------------|
| 1 | January, 02 2014 00:00:00+0000 | December, 02 2014 00:00:00+0000 |
| 2 | January, 03 2014 00:00:00+0000 | February, 03 2015 00:00:00+0000 |
Query 2:
call year_month_table()
Results:
| ROW_ID | ID | MONTH | YEAR |
|--------|----|-------|------|
| 1 | 1 | 1 | 2014 |
| 2 | 1 | 2 | 2014 |
| 3 | 1 | 3 | 2014 |
| 4 | 1 | 4 | 2014 |
| 5 | 1 | 5 | 2014 |
| 6 | 1 | 6 | 2014 |
| 7 | 1 | 7 | 2014 |
| 8 | 1 | 8 | 2014 |
| 9 | 1 | 9 | 2014 |
| 10 | 1 | 10 | 2014 |
| 11 | 1 | 11 | 2014 |
| 12 | 1 | 12 | 2014 |
| 13 | 2 | 1 | 2014 |
| 14 | 2 | 2 | 2014 |
| 15 | 2 | 3 | 2014 |
| 16 | 2 | 4 | 2014 |
| 17 | 2 | 5 | 2014 |
| 18 | 2 | 6 | 2014 |
| 19 | 2 | 7 | 2014 |
| 20 | 2 | 8 | 2014 |
| 21 | 2 | 9 | 2014 |
| 22 | 2 | 10 | 2014 |
| 23 | 2 | 11 | 2014 |
| 24 | 2 | 12 | 2014 |
| 25 | 2 | 1 | 2015 |
| 26 | 2 | 2 | 2015 |
Notice that that last select statement in the procedure is the one that outputs the result. You can execute it every time you need.
Hope this helps
Important: As pointed by #amaster in his comment, this answer will fail if the period spans more than two years.
(Use the following code under your own risk ;) )
I've found another way to do this, but it's not a simple select statement and I think it's prone to errors, but I will put it here anyway:
select mytable.id, month, year
from mytable,
(select month, year
from
(select 1 as month
union select 2
union select 3
union select 4
union select 5
union select 6
union select 7
union select 8
union select 9
union select 10
union select 11
union select 12) as a,
(select year(start) as year from mytable
union select year(end) as year from mytable) as b) as a
where cast(concat_ws('-', a.year, a.month, day(mytable.start)) as date)
between date(mytable.start) and date(mytable.end)
order by mytable.id, year, month;
See this other SQL fiddle.
I know I am late to the party, but I was needing a good solution and sequencing was not working for my db version.
I started with https://stackoverflow.com/a/14813173/1707323 and made a few changes to get it working for my use like in this OP.
SELECT
DATE_FORMAT(m1, '%c') AS month_single,
DATE_FORMAT(m1, '%Y') AS this_year
FROM
(
SELECT
'2017-08-15' +INTERVAL m MONTH AS m1
FROM
(
SELECT
#rownum:=#rownum+1 AS m
from
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t1,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t2,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t3,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t4,
(SELECT #rownum:=-1) t0
) d1
) d2
WHERE
m1<='2020-03-23'
ORDER BY m1
This will get all of the months between these two dates. Please notice that the start date is in the second select clause and the end date is in the final where clause. This will include the starting month and ending month as well. It could be easily modified to exclude the starting and ending months with some extra +/- INTERVALS.

how to sum two columns at left and display total in third column

I have a table similar to below
Date | institution | qty1 | qty2
1 Aug 12 | xyz | 0 | 5
1 Aug 12 | xyz | 0 | 17
1 Aug 12 | abc | 12 | 0
2 Aug 12 | abc | 33 | 0
2 Aug 12 | xyz | 0 | 57
I want output similar to below
Date | ABC | XYZ | Total
1 Aug 12 | 12 | 22 | 34
2 Aug 12 | 33 | 57 | 90
Total | 45 | 79 | 124
Now I have written a query which displays only first three columns. I don't know how to add the last total column
select date, sum (case when institution = 'abc', qty1 else 0 end) as ABC,
sum(case when institution = 'xyz', qty2 else 0 end) as XYZ
group by date
with rollup
You are very close - I think this should do it:
select
date
, sum (case when institution = 'abc' then qty1 else 0 end) as ABC
, sum(case when institution = 'xyz' then qty2 else 0 end) as XYZ
, sum(qty1+qty2) as Total
from mytable
group by date
with rollup
Here is a link to this example on sqlfiddle.