Opening balance and closing balance for a non-working date - mysql

Need a help on how to get opening balances and closing balances of a non-working date in my transaction table in my database.
transaction table
+------------------+----------+-----------+
| id | trans_date | debit | credit |
+----+-------------+----------+-----------+
| 1 | 2016-05-09 | 200.00 | 0.00 |
| 2 | 2016-05-11 | 0.00 | 50.00 |
+---------------+-------------+-----------+
Want a result like below. You will realize there were no transaction on "2016-05-10" but the result shows both opening balance and closing balance. Thanks
+-------------+--------------+-----------+-----------+------------+
| trans_date | open_bal | debit | credit |closing_bal |
+-------------+--------------+-----------+-----------+------------+
| 2016-05-09 | 0.00 | 200.00 | 0.00 | 200.00 |
| 2016-05-10 | 200.00 | 0.00 | 0.00 | 200.00 |
| 2016-05-11 | 200.00 | 0.00 | 50.00 | 150.00 |
+-------------+-------------+--------------+------------+---------+
I tried this but "2016-05-10" does not show in the result.
SELECT trans_date,open_balance
FROM(SELECT s.gen_id, s.trans_id, s.trans_date,
s.narations, s.account_code,
s.op_balance as open_balance,
s.debit, s.credit, s.closing_balance
from ( select t.gen_id, t.trans_id,
t.narations, t.account_code,
t.trans_date, t.credit, t.debit,
#tot_debit := if(#prev_client = t.account_code, #tot_debit + t.debit,t.debit) as tot_cred,
#tot_credit := if(#prev_client = t.account_code,#tot_credit + t.credit,t.credit) as tot_deb,
#cur_bal := if(#prev_client = t.account_code, #tot_debit - #tot_credit,t.debit-t.credit) as closing_balance,
(#cur_bal + t.credit) - t.debit as op_balance, #prev_client := t.account_code
from (select * from journal WHERE account_code = 41003
GROUP BY trans_date order by trans_date,account_code,trans_id)t, (select #prev_client:=0,#cur_bal:=0,#tot_debit:=0,#tot_credit:= 0,#open_balance:=0)r )s) g where trans_date <= '2016-05-11'

You can use this to select list of dates in MySQL.
SELECT DATE_ADD(CURDATE() , INTERVAL -(a.n + 10 * b.n + 100 * c.n) DAY) AS `date`
FROM (SELECT 1 AS n 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 0) AS a
CROSS JOIN (SELECT 1 AS n 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 0) AS b
CROSS JOIN (SELECT 1 AS n 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 0) AS c
If you want to include in result dates not presented at table transactions, it can be done with LEFT JOIN
SELECT * FROM
(SELECT DATE_ADD(CURDATE() , INTERVAL -(a.n + 10 * b.n + 100 * c.n) DAY) AS `date`
FROM (SELECT 1 AS n 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 0) AS a
CROSS JOIN (SELECT 1 AS n 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 0) AS b
CROSS JOIN (SELECT 1 AS n 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 0) AS c
) AS dates
LEFT JOIN transactions ON trans_date = dates.date
Note, that this will select dates from current for 999 past days. If you want more days ago, another CROSS JOIN can be added and 9 999 days will be returned.

Related

Group query by date which is between range

I have a table like:
id | date_start | date_end
1 | 2019.05.01 | 2019.05.06
2 | 2019.05.05 | 2019.05.05
3 | 2019.05.05 | 2019.05.08
Date_start and date_end means that entry is active during this period. Is it possible to get days with his count of active entries for intermediate values too? Like:
2019.05.01 | 1
2019.05.02 | 1
2019.05.03 | 1
2019.05.04 | 1
2019.05.05 | 3
2019.05.06 | 2
2019.05.07 | 1
2019.05.08 | 1
I think that this will work for you:
SELECT temp_date,
COUNT(*)
FROM (
SELECT ((SELECT MAX(date_start) FROM {dates_table}) - INTERVAL c.number DAY) AS temp_date
FROM (SELECT singles + tens + hundreds number
FROM ( SELECT 0 singles
UNION ALL SELECT 1 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
) singles
JOIN (SELECT 0 tens
UNION ALL SELECT 10 UNION ALL SELECT 20 UNION ALL SELECT 30
UNION ALL SELECT 40 UNION ALL SELECT 50 UNION ALL SELECT 60
UNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90
) tens
JOIN (SELECT 0 hundreds
UNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300
UNION ALL SELECT 400 UNION ALL SELECT 500 UNION ALL SELECT 600
UNION ALL SELECT 700 UNION ALL SELECT 800 UNION ALL SELECT 900
) hundreds
ORDER BY number DESC) c
WHERE c.number BETWEEN 0 AND (SELECT DATEDIFF(MAX(date_end),MIN(date_start)) FROM {dates_table})
) calendar
JOIN {dates_table} ON temp_date BETWEEN end_date AND start_date
GROUP BY temp_date
Just replace {dates_table} with the name of your table.
The base of this that generates the series of dates was taken from IvanD's answer on this SO question: How to populate a table with a range of dates?

How filter fname and lname? - mysql

I've updated "leave" table:
+--------+---------+---------+-------------+----------+----------+
|ID_LEAVE|ID_WORKER| BEGIN_DATE | END_DATE |
+--------+---------+---------+---------+-------------+-----------+
| 5 | 10 | 2019-03-22 07:00:00 |2019-03-25 15:00:00 |
+--------+---------+---------+-------------+----------+----------+
| 6 | 10 | 2019-03-28 07:00:00 |2019-04-12 15:00:00 |
+--------+---------+---------+-------------+----------+----------+
| 7 | 12 | 2019-03-28 07:00:00 |2019-04-09 15:00:00 |
+--------+---------+---------+-------------+----------+----------+
And "Workers table":
+---------+---------+-------+
|ID_WORKER| FNAME | LNAME |
+---------+---------+-------+
| 10 | MARIO | NEED |
+---------+---------+-------+
| 12 | DARIO | MARCO |
+---------+---------+-------+
I can display all workers (all from "workers table") with leaves times.
What i would like to do?
I want filter phrases of FNAME OR LNAME which is joined with workers table.
What i've tried?
I tried filter phrases of FNAME OR LNAME by code in below:
SELECT leave.ID_LEAVE, leave.ID_WORKER, workers.FNAME, workers.LNAME, leave.BEGIN_DATE, leave.END_DATE,
FROM
(SELECT ADDDATE('1970-01-01', t4 * 10000 + t3 * 1000 + t2 * 100 + t1 * 10 + t0) AS date_value
FROM
(SELECT 0 t0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t0,
(SELECT 0 t1 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t1,
(SELECT 0 t2 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t2,
(SELECT 0 t3 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t3,
(SELECT 0 t4 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t4) calendar
INNER JOIN leave ON calendar.date_value BETWEEN DATE(leave.BEGIN_DATE) AND DATE(leave.END_DATE)
INNER JOIN workers ON leave.ID_WORKER = workers.ID_WORKER
WHERE NOT WEEKDAY(date_value) IN (5, 6) AND (workers.FNAME LIKE 'Ma' OR workers.LNAME LIKE 'Ma')
GROUP BY ID_LEAVE;
But it shows nothing. When i filter LNAME or FNAME by writing Phrase 'Ma' it would show any content like that:
+--------+---------+---------+-------------+----------+--------------------------
|ID_LEAVE|ID_WORKER| FNAME | LNAME | BEGIN_DATE | END_DATE |
+--------+---------+---------+---------+-------------+--------------------+------
| 5 | 10 | MARIO | NEED |2019-03-22 07:00:00 |2019-03-25 15:00:00 |
+--------+---------+---------+-------------+----------+--------------------------
| 6 | 10 | MARIO | NEED |2019-03-28 07:00:00 |2019-04-09 15:00:00 |
+--------+---------+---------+-------------+----------+--------------------------
Any ideas? What should I change in that code? Can i count for your support? Thx for any advice.
You missed '%' symbol in the filter condition.
Also you have to use UPPER() function on the field and the data to be compared,
so that filtering will be case insensitive
SELECT leave.ID_LEAVE, leave.ID_WORKER, workers.FNAME, workers.LNAME, leave.BEGIN_DATE, leave.END_DATE,
FROM
(SELECT ADDDATE('1970-01-01', t4 * 10000 + t3 * 1000 + t2 * 100 + t1 * 10 + t0) AS date_value
FROM
(SELECT 0 t0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t0,
(SELECT 0 t1 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t1,
(SELECT 0 t2 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t2,
(SELECT 0 t3 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t3,
(SELECT 0 t4 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t4) calendar
INNER JOIN leave ON calendar.date_value BETWEEN DATE(leave.BEGIN_DATE) AND DATE(leave.END_DATE)
INNER JOIN workers ON leave.ID_WORKER = workers.ID_WORKER
WHERE NOT WEEKDAY(date_value) IN (5, 6) AND (UPPER(workers.FNAME) LIKE '%MA%' OR UPPER(workers.LNAME) LIKE '%MA%')
GROUP BY ID_LEAVE;
You can try this query
select leave.ID_LEAVE,
Workers.ID_WORKER,
Workers.FNAME,
Workers.LNAME,
leave.BEGIN_DATE,
leave.END_DATE from Workers
join leave on Workers.ID_WORKER = leave.ID_WORKER
where Workers.FNAME Like '%ma%';

MySQL: count unique days in period with overlaps and gaps (optimized)

How to calculate the number of unique days per month from a table with two date columns, in which the periods can have gaps and overlaps?
I rather not use a calendar table to get the unique days, because it generates a temporary table with thousands of records, and resources are limited.
Example table:
+---------+------------+------------+
| mygroup | alpha | omega |
+---------+------------+------------+
| 1 | 2017-02-04 | 2017-04-14 |
| 1 | 2017-03-25 | 2017-03-28 |
| 1 | 2017-01-23 | 2017-01-25 |
| 2 | 2017-02-05 | 2017-02-20 |
| 1 | 2017-04-28 | 2017-05-12 |
| etc. | etc. | etc. |
+---------+------------+------------+
Is it what you need?
select count(distinct selected_date),te.mygroup, MONTHNAME(selected_date)from
(select adddate('1970-01-01',t4.i*10000 + t3.i*1000 + t2.i*100 + t1.i*10 + t0.i) selected_date from
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4) v
cross join test te
where selected_date between te.alpha and te.omega
group by mygroup, MONTHNAME(selected_date)
Оutput for your example:
'17','1','April'
'25','1','February'
'3','1','January'
'31','1','March'
'12','1','May'
'16','2','February'
Count may be greater than number of the days in month because such overlap exists in few rows - it's not а mistake.
There is another way to do this roughly 10 times faster than a calendar table.
The biggest resource spoiler is the calendar table itself, it is used to filter unique days.
But instead of using a whole table record for that, it can be done using 31 bits in an UINT.
Recepe:
Create a calendar table with months only
Cut periods in months, and join them with the calendar table
Translate periods to bits of UINTs
OR the UINTs per month for uniqueness
Count their bits as unique days per month
Output:
+--------------+---------+---------+-------+
| Period | Group 1 | Group 2 | Total |
+--------------+---------+---------+-------+
| 2017 month 5 | 11 | 0 | 11 |
| 2017 month 4 | 15 | 0 | 15 |
| 2017 month 3 | 30 | 0 | 30 |
| 2017 month 2 | 24 | 15 | 39 |
| 2017 month 1 | 2 | 0 | 2 |
+--------------+---------+---------+-------+
MySQL query:
SELECT
`tabulate`.`period` AS `Period`,
SUM(IF(`tabulate`.`mygroup` = 1,
`tabulate`.`days`, 0)) AS `Group 1`,
SUM(IF(`tabulate`.`mygroup` = 2,
`tabulate`.`days`, 0)) AS `Group 2`,
SUM(`tabulate`.`days`) AS `Total`
FROM
( SELECT
`unique`.`period`,
BIT_COUNT(BIT_OR(CONV(CONCAT(
REPEAT("1", DAYOFMONTH(`unique`.`omega`) - DAYOFMONTH(`unique`.`alpha`)),
REPEAT("0", DAYOFMONTH(`unique`.`alpha`) - 1)
), 2, 10))) AS `days`,
`unique`.`mygroup`
FROM
( SELECT
DATE_FORMAT(`permonth`.`period_alpha`, "%Y month %c") AS `period`,
GREATEST(`permonth`.`period_alpha`, `permonth`.`example_alpha`) AS `alpha`,
LEAST(`permonth`.`period_omega`, `permonth`.`example_omega`) AS `omega`,
`permonth`.`mygroup`
FROM
( SELECT
`period`.`alpha` AS `period_alpha`,
DATE_SUB(`period`.`omega`, INTERVAL 1 DAY) AS `period_omega`,
`example`.`mygroup`,
IFNULL(`example`.`alpha`, `period`.`alpha`) AS `example_alpha`,
IFNULL(`example`.`omega`, CURDATE()) AS `example_omega`
FROM
( SELECT
DATE_ADD(
MAKEDATE(YEAR(CURDATE()), 1),
INTERVAL `season`.`n` + (`month`.`n` << 2) MONTH
) AS `alpha`,
DATE_ADD(
MAKEDATE(YEAR(CURDATE()), 1),
INTERVAL 1 + `season`.`n` + (`month`.`n` << 2) MONTH
) AS `omega`
FROM
( SELECT 0 AS `n`
UNION ALL SELECT 1
UNION ALL SELECT 2
) AS `month`
CROSS JOIN (SELECT 0 AS `n`
UNION ALL SELECT 1
UNION ALL SELECT 2
UNION ALL SELECT 3
) AS `season`
) AS `period`
INNER JOIN
( SELECT 1 AS `mygroup`, "2017-02-04" AS `alpha`, "2017-04-14" AS `omega`
UNION ALL SELECT 1, "2017-03-25", "2017-03-28"
UNION ALL SELECT 1, "2017-01-23", "2017-01-25"
UNION ALL SELECT 2, "2017-02-05", "2017-02-20"
UNION ALL SELECT 1, "2017-04-28", "2017-05-12"
) AS `example` ON (
(`example`.`alpha` < `period`.`omega` OR `example`.`alpha` IS NULL)
AND IFNULL(`example`.`omega`, CURDATE()) >= `period`.`alpha`
)
) AS `permonth`
) AS `unique`
GROUP BY
`unique`.`period`,
`unique`.`mygroup`
) AS `tabulate`
GROUP BY `tabulate`.`period`
ORDER BY `tabulate`.`period` DESC

sql get all dates between two table dates

another SQL challenge!
I want to write a MySQL query that gets all days between two dates in one record.
opening_times
id | begin | end
1 | 10:00:00 | 17:00:00
2 | 10:00:00 | 18:00:00
3 | 10:00:00 | 19:00:00
opening_periods
id | opening_time_id | begin | end
1 | 3 | 2016-03-26 | 2016-03-28
2 | 2 | 2016-03-29 | 2016-04-01
3 | 1 | 2016-04-02 | 2016-04-03
I want to have this output:
date | begin | end
2016-03-26 | 10:00:00 | 19:00:00
2016-03-27 | 10:00:00 | 19:00:00
2016-03-28 | 10:00:00 | 19:00:00
2016-03-29 | 10:00:00 | 18:00:00
2016-03-30 | 10:00:00 | 18:00:00
2016-03-31 | 10:00:00 | 18:00:00
2016-04-01 | 10:00:00 | 18:00:00
2016-04-02 | 10:00:00 | 17:00:00
2016-04-03 | 10:00:00 | 17:00:00
Should I use a subquery for this?
Thx for pointing me in the right direction!
You can solve it in mysql with a complex query.
First you need to build a subquery that will generate an integer sequence, like in this answer:
SELECT #row := #row + 1 as rown FROM
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t1,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t2,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t3,
(SELECT #row:=-1) x
This will be used to generate so many rows for each entry as many days you have in your dates interval.
SELECT DATEDIFF(`end`,`begin`) as number_of_days FROM `opening_periods`
And all put together will look like this:
SELECT DATE_ADD( o.`begin`, INTERVAL days day) as date_field, t.begin, t.end
FROM `opening_periods` o INNER JOIN (
SELECT id, rown as days
FROM `opening_periods`,
(SELECT #row := #row + 1 as rown FROM
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t1,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t2,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t3,
(SELECT #row:=-1) x) numbers_table
WHERE rown <= DATEDIFF(`end`,`begin`)) r
ON o.id = r.id
INNER JOIN `opening_times` t ON o.`opening_time_id` = t.id
ORDER BY o.id
Here is a fiddle: http://rextester.com/AKDRI84101

Find dates between start date and end date in mysql

I got 2 columns, one is start date and the other is end date. I need to find every single day between the two dates.
Need it in another table with two coloums, one name pno that refers to the pno's id in the first table, and the other with the dates between start date and end date.
As an example this could be my input
pno startdate end date
p1 2012-12-03 2012-12-06
p2 2013-01-05 2013-01-08
p3 2013-01-15 2012-01-20
and this has to be my output.
pno dates
----------
p01 2012-12-03
p01 2012-12-04
p01 2012-12-05
p01 2012-12-06
p02 2013-01-05
p02 2013-01-06
p02 2013-01-07
p02 2013-01-08
p03 2013-01-15
...
You can do it like this
SELECT pno, startdate + INTERVAL q.n - 1 DAY dates
FROM table1 t CROSS JOIN
(
SELECT a.N + b.N * 10 + 1 n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 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) a
,(SELECT 0 AS N UNION ALL SELECT 1 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) b
ORDER BY n
) q
WHERE q.n - 1 <= DATEDIFF(enddate, startdate)
ORDER BY pno, dates
The subquery generates a sequence of numbers from 1 to 100. You can adjust it for your needs (if date differences span more or less than 100 days) or completely substitute it with a persisted tally(numbers) table if you do a lot of such queries.
Output:
+------+------------+
| pno | dates |
+------+------------+
| p1 | 2012-12-03 |
| p1 | 2012-12-04 |
| p1 | 2012-12-05 |
| p1 | 2012-12-06 |
| p2 | 2013-01-05 |
| p2 | 2013-01-06 |
| p2 | 2013-01-07 |
| p2 | 2013-01-08 |
| p3 | 2013-01-15 |
| p3 | 2013-01-16 |
| p3 | 2013-01-17 |
| p3 | 2013-01-18 |
| p3 | 2013-01-19 |
| p3 | 2013-01-20 |
+------+------------+
Here is SQLFiddle demo
UPDATE: To create and populate persisted tally table use
CREATE TABLE tally (n INT NOT NULL PRIMARY KEY);
INSERT INTO tally (n)
SELECT a.n + b.n * 10 + c.n * 100 + d.n * 1000 + 1 n
FROM
(SELECT 0 AS n UNION ALL SELECT 1 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) a
,(SELECT 0 AS n UNION ALL SELECT 1 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) b
,(SELECT 0 AS n UNION ALL SELECT 1 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) c
,(SELECT 0 AS n UNION ALL SELECT 1 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) d
ORDER BY n;
You'll have in the tally table sequence of numbers from 1 to 10000. That will allow you to work with date ranges that span more than 27 years.
Now the query boils down to
SELECT pno, startdate + INTERVAL q.n - 1 DAY dates
FROM table1 t CROSS JOIN tally q
WHERE q.n - 1 <= DATEDIFF(enddate, startdate)
ORDER BY pno, dates
Here is SQLFiddle demo