I have a table with some events in conjunction with timestamp as DATETIME.
Now I want to have a statistic about my data, e.g. how much events per day... In some cases I don´t have events every day and of course I don´t get in my statistic that days with no existens entries.
| id | DATE | COUNT |
| 1 | 2014-09-06 | 1 |
| 2 | 2014-09-07 | 8 |
| 3 | 2014-09-10 | 2 |
| 4 | 2014-09-14 | 78 |
So i wrote a little script who generates me a query to solv that problem. It generates a virtual table with my days i want to know and do a LEFT OUTER JOIN with my event table.
So i will got all dates without gaps! The query looks like this e.g.:
SELECT DATE_FORMAT(d.date, '%d.%m.%Y') as datum, COUNT(l.id) as anzahl
FROM
(
SELECT STR_TO_DATE('25.11.2014', '%d.%m.%Y') as date UNION ALL
SELECT STR_TO_DATE('26.11.2014', '%d.%m.%Y') as date UNION ALL
SELECT STR_TO_DATE('27.11.2014', '%d.%m.%Y') as date UNION ALL
SELECT STR_TO_DATE('28.11.2014', '%d.%m.%Y') as date UNION ALL
SELECT STR_TO_DATE('29.11.2014', '%d.%m.%Y') as date UNION ALL
SELECT STR_TO_DATE('30.11.2014', '%d.%m.%Y') as date UNION ALL
SELECT STR_TO_DATE('01.12.2014', '%d.%m.%Y') as date
) as d
LEFT OUTER JOIN events l ON d.date = DATE(l.date)
GROUP BY datum
ORDER BY datum DESC
This query works absolute perfectly and i have also dates with no data in my statistic.
But now comes the real problem i have: the sorting doesn´t work! I got some weird output. I have no idea what the problem is. The output looks like this:
| DATE | COUNT |
| 31.10.2014 | 0 |
| 30.11.2014 | 5 |
| 30.10.2014 | 0 |
| 29.11.2014 | 0 |
| 29.10.2014 | 0 |
| 28.11.2014 | 0 |
| 28.10.2014 | 0 |
| 27.11.2014 | 0 |
| 27.10.2014 | 0 |
| 26.11.2014 | 0 |
| 26.10.2014 | 0 |
| 25.11.2014 | 1 |
| 25.10.2014 | 0 |
| 24.11.2014 | 1 |
| 24.10.2014 | 0 |
| 23.11.2014 | 0 |
| 23.10.2014 | 0 |
| 22.11.2014 | 0 |
| 22.10.2014 | 0 |
| 21.11.2014 | 1 |
| 21.10.2014 | 0 |
| 20.11.2014 | 0 |
| 20.10.2014 | 0 |
| 19.11.2014 | 2 |
| 19.10.2014 | 0 |
| 18.11.2014 | 0 |
| 18.10.2014 | 0 |
| 17.11.2014 | 0 |
| 17.10.2014 | 0 |
| 16.11.2014 | 0 |
So what´s wrong with my query? I have conscious use the function STR_TO_DATE i got a "real" date format. Normaly the sorting should work with it, isn´t it?
Problem is your eyes see them as dates, but in your quesry they're strings so they are correctly sorted as string with the day and month first. Try having anotehr field formated for sorting and use that for the sort.
SELECT DATE_FORMAT(d.date, '%d.%m.%Y') as datum, DATE_FORMAT(d.date, '%Y.%m.%d') as sortdatum, COUNT(l.id) as anzahl
FROM
(
SELECT STR_TO_DATE('25.11.2014', '%d.%m.%Y') as date UNION ALL
SELECT STR_TO_DATE('26.11.2014', '%d.%m.%Y') as date UNION ALL
SELECT STR_TO_DATE('27.11.2014', '%d.%m.%Y') as date UNION ALL
SELECT STR_TO_DATE('28.11.2014', '%d.%m.%Y') as date UNION ALL
SELECT STR_TO_DATE('29.11.2014', '%d.%m.%Y') as date UNION ALL
SELECT STR_TO_DATE('30.11.2014', '%d.%m.%Y') as date UNION ALL
SELECT STR_TO_DATE('01.12.2014', '%d.%m.%Y') as date
) as d
LEFT OUTER JOIN events l ON d.date = DATE(l.date)
GROUP BY datum
ORDER BY sortdatum DESC
please it **unix_timestamp**
SELECT DATE_FORMAT(d.date, '%d.%m.%Y') as datum, COUNT(l.id) as anzahl
FROM
(
SELECT STR_TO_DATE('25.11.2014', '%d.%m.%Y') as date UNION ALL
SELECT STR_TO_DATE('26.11.2014', '%d.%m.%Y') as date UNION ALL
SELECT STR_TO_DATE('27.11.2014', '%d.%m.%Y') as date UNION ALL
SELECT STR_TO_DATE('28.11.2014', '%d.%m.%Y') as date UNION ALL
SELECT STR_TO_DATE('29.11.2014', '%d.%m.%Y') as date UNION ALL
SELECT STR_TO_DATE('30.11.2014', '%d.%m.%Y') as date UNION ALL
SELECT STR_TO_DATE('01.12.2014', '%d.%m.%Y') as date
) as d
LEFT OUTER JOIN events l ON unix_timestamp(d.date) = unix_timestamp(l.date)
GROUP BY datum
ORDER BY datum DESC
Related
In my Mysql database, I have 2 columns that store the start and end date of the process, respectively. I need to write a query that allows me to count the number of rows for each month in each column, and presents the count separately.
Table example:
+----+------------+----------------+
| id | startData | endData |
+----+-------------+----------------+
| 1 | 02/03/2020 | 02/03/2020 |
| 2 | 02/04/2020 | 02/04/2020 |
| 3 | 02/04/2020 | 02/05/2020 |
| 4 | 02/04/2020 | 02/05/2020 |
| 5 | 02/05/2020 | 02/06/2020 |
| 6 | 02/05/2020 | 02/06/2020 |
| 7 | 02/06/2020 | 02/07/2020 |
+----+-------------+----------------+
I want as a result:
+-------+--------------------+-------------------+
| month | count_month_start | count_month_end |
+-------+--------------------+-------------------+
| 03 | 01 | 01 |
| 04 | 03 | 01 |
| 05 | 02 | 02 |
| 06 | 01 | 02 |
| 07 | 00 | 01 |
+-------+--------------------+-------------------+
Assuming your start date and end date columns are of datatype date, you can do -
Select ifnull(Tb1.mn,Tb2.mn) As mn, ifnull(count_mn_start,0) As count_mn_start, ifnull(count_mn_end,0) As count_mn_end
from
(Select Month(StartDate) as mn, count(id) as count_mn_start
from
my_table
Group by Month(StartDate))Tb1
left Join (Select Month(EndDate) as mn, count(id) as count_mn_end
from my_table
Group by Month(EndDate)) Tb2
on Tb1.mn = Tb2.mn
UNION
Select ifnull(Tb1.mn,Tb2.mn) As mn, ifnull(count_mn_start,0) As count_mn_start, ifnull(count_mn_end,0) As count_mn_end
from
(Select Month(StartDate) as mn, count(id) as count_mn_start
from
my_table
Group by Month(StartDate))Tb1
Right Join (Select Month(EndDate) as mn, count(id) as count_mn_end
from my_table
Group by Month(EndDate)) Tb2
on Tb1.mn = Tb2.mn;
DB fiddle - https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=84ecddb9f5ed431ddff6a9eaab87e5df
PS : If your dates only have one year (2020 as your example) this would work, however ideally if you have different years in the data, consider having the year in the output as well and in that case use the same syntax ie Year(datefield) and add it in the select and group by in the sub-queries (same way as month in the above query).
A pretty simple way is to expand the time periods into days using a recursive CTE. Then just aggregate:
with recursive cte as (
select id, startdate as dte, enddate
from t
union all
select id, dte + interval 1 day, enddate
from cte
where dte < enddate
)
select year(dte), month(dte),
sum( day(dte) = 1 ) as cnt_start,
sum( day(dte) = day(last_day(dte)) ) as cnt_end
from cte
group by year(dte), month(dte) ;
Here is a db<>fiddle.
I have a table with 'ON' and 'OFF' values in column activity and another column datetime.
id(AUTOINCREMENT) id_device activity datetime
1 a ON 2017-05-26 22:00:00
2 b ON 2017-05-26 05:00:00
3 a OFF 2017-05-27 04:00:00
4 b OFF 2017-05-26 08:00:00
5 a ON 2017-05-28 12:00:00
6 a OFF 2017-05-28 15:00:00
I need to get total ON time by day
day id_device total_minutes_on
2017-05-26 a 120
2017-05-26 b 180
2017-05-27 a 240
2017-05-27 b 0
2017-05-28 a 180
2017-05-28 b 0
i have searched and tried answers for another posts, i tried TimeDifference and i get correct total time.
I don't find the way to get total time grouped by date
i appreciate your help
I'm not posting this as a definite answer rather it's an experiment for me and hopefully you'll find is useful in your case. Also I would like to mention that the MySQL database version I'm working with is quite old so the method I'm using is also very manual to say the least.
First of all lets extract your expected output:
The date value in day need to be repeated twice fro each of id_device a and b.
Minutes are calculated based on the activity; if activity is 'ON' until tomorrow, it needs to be calculated until the day end at 24:00:00 while the next day will calculate minutes until the activity is OFF.
What I come up with is this:
Creating condition (1):
SELECT * FROM
(SELECT DATE(datetime) dtt FROM mytable GROUP BY DATE(datetime)) a,
(SELECT id_device FROM mytable GROUP BY id_device) b
ORDER BY dtt,id_device;
The query above will return the following result:
+------------+-----------+
| dtt | id_device |
+------------+-----------+
| 2017-05-26 | a |
| 2017-05-26 | b |
| 2017-05-27 | a |
| 2017-05-27 | b |
| 2017-05-28 | a |
| 2017-05-28 | b |
+------------+-----------+
*Above will only work with all the dates you have in the table. If you want all date regardless if there's activity or not, I suggest you create a calendar table (refer: Generating a series of dates).
So this become the base query. Then I've added an outer query to left join the query above with the original data table:
SELECT v.*,
GROUP_CONCAT(w.activity ORDER BY w.datetime SEPARATOR ' ') activity,
GROUP_CONCAT(TIME_TO_SEC(TIME(w.datetime)) ORDER BY w.datetime SEPARATOR ' ') tr
FROM
-- this was the first query
(SELECT * FROM
(SELECT DATE(datetime) dtt FROM mytable GROUP BY DATE(datetime)) a,
(SELECT id_device FROM mytable GROUP BY id_device) b
ORDER BY a.dtt,b.id_device) v
--
LEFT JOIN
mytable w
ON v.dtt=DATE(w.datetime) AND v.id_device=w.id_device
GROUP BY DATE(v.dtt),v.id_device
What's new in the query is the addition of GROUP_CONCAT operation on both activity and time value extracted from datetime column which is converted into seconds value. You notice that in both of the GROUP_CONCAT there's a similar ORDER BY condition which is important in order to get the exact corresponding value.
The query above will return the following result:
+------------+-----------+----------+-------------+
| dtt | id_device | activity | tr |
+------------+-----------+----------+-------------+
| 2017-05-26 | a | ON | 79200 |
| 2017-05-26 | b | ON OFF | 18000 28800 |
| 2017-05-27 | a | OFF | 14400 |
| 2017-05-27 | b | (NULL) | (NULL) |
| 2017-05-28 | a | ON OFF | 43200 54000 |
| 2017-05-28 | b | (NULL) | (NULL) |
+------------+-----------+----------+-------------+
From here, I've added another query outside to calculate how many minutes and attempt to get the expected result:
SELECT dtt,id_device,
CASE
WHEN SUBSTRING_INDEX(activity,' ',1)='ON' AND SUBSTRING_INDEX(activity,' ',-1)='OFF'
THEN (SUBSTRING_INDEX(tr,' ',-1)-SUBSTRING_INDEX(tr,' ',1))/60
WHEN activity='ON' THEN 1440-(tr/60)
WHEN activity='OFF' THEN tr/60
WHEN activity IS NULL AND tr IS NULL THEN 0
END AS 'total_minutes_on'
FROM
-- from the last query
(SELECT v.*,
GROUP_CONCAT(w.activity ORDER BY w.datetime SEPARATOR ' ') activity,
GROUP_CONCAT(TIME_TO_SEC(TIME(w.datetime)) ORDER BY w.datetime SEPARATOR ' ') tr
FROM
-- this was the first query
(SELECT * FROM
(SELECT DATE(datetime) dtt FROM mytable GROUP BY DATE(datetime)) a,
(SELECT id_device FROM mytable GROUP BY id_device) b
ORDER BY a.dtt,b.id_device) v
--
LEFT JOIN
mytable w
ON v.dtt=DATE(w.datetime) AND v.id_device=w.id_device
GROUP BY DATE(v.dtt),v.id_device
--
) z
The last part I do is if the activity value have both ON and OFF on the same day then (OFF-ON)/60secs=total minutes. If activity value is only ON then minutes value for '24:00:00' > 24 hr*60 min= 1440-(ON/60secs)= total minutes, and if activity only OFF, I just convert seconds to minutes because the day starts at 00:00:00 anyhow.
+------------+-----------+------------------+
| dtt | id_device | total_minutes_on |
+------------+-----------+------------------+
| 2017-05-26 | a | 120 |
| 2017-05-26 | b | 180 |
| 2017-05-27 | a | 240 |
| 2017-05-27 | b | 0 |
| 2017-05-28 | a | 180 |
| 2017-05-28 | b | 0 |
+------------+-----------+------------------+
Hopefully this will give you some ideas. ;)
I am currently struggling on how to aggregate my daily data in other time aggregations (weeks, months, quarters etc).
Here is how my raw data type looks like:
| date | traffic_type | visits |
|----------|--------------|---------|
| 20180101 | 1 | 1221650 |
| 20180101 | 2 | 411424 |
| 20180101 | 4 | 108407 |
| 20180101 | 5 | 298117 |
| 20180101 | 6 | 26806 |
| 20180101 | 7 | 12033 |
| 20180101 | 8 | 80368 |
| 20180101 | 9 | 69544 |
| 20180101 | 10 | 39919 |
| 20180101 | 11 | 26291 |
| 20180102 | 1 | 1218490 |
| 20180102 | 2 | 410965 |
| 20180102 | 4 | 108037 |
| 20180102 | 5 | 297727 |
| 20180102 | 6 | 26719 |
| 20180102 | 7 | 12019 |
| 20180102 | 8 | 80074 |
First, I would like to check the sum of visits regardless of traffic_type:
SELECT date, SUM(visits) as visits_per_day
FROM visits_tbl
GROUP BY date
Here is the outcome:
| ymd | visits_per_day |
|:--------:|:--------------:|
| 20180101 | 2294563 |
| 20180102 | 2289145 |
| 20180103 | 2300367 |
| 20180104 | 2310256 |
| 20180105 | 2368098 |
| 20180106 | 2372257 |
| 20180107 | 2373863 |
| 20180108 | 2364236 |
However, if I want to check the specific day which the visits_per_day was the highest for each time aggregation (eg.: Month), I am struggling to retrieve the right output.
Here is what I did:
SELECT
(date div 100) as y_month, MAX(visits_per_day) as max_visit_per_day
FROM
(SELECT date, SUM(visits) as visits_per_day
FROM visits_tbl
GROUP BY date) as t1
GROUP BY
y_month
And here is the output of my query:
| y_month | max_visit_per_day |
|:-------:|:-----------------:|
| 201801 | 2435845 |
| 201802 | 2519000 |
| 201803 | 2528097 |
| 201804 | 2550645 |
However, I cannot know what was the exact day where the visits_per_day was the highest.
Desired output:
| y_month | max_visit_per_day | ymd |
|:-------:|:-----------------:|:--------:|
| 201801 | 2435845 | 20180130 |
| 201802 | 2519000 | 20180220 |
| 201803 | 2528097 | 20180325 |
| 201804 | 2550645 | 20180406 |
ymd would represent the day in which the visits_per_day was the highest.
This logic would be used in a dashboard with the help of programming in order to automatically select the time aggregation.
Can someone please help me?
This is a job for the structured part of structured query language. That is, you will write some subqueries and treat them as tables.
You already know how to find the number of visits per day. Let's add the month for each day to that query (http://sqlfiddle.com/#!9/a8455e/13/0).
SELECT date DIV 100 as month, date,
SUM(visits) as visits
FROM visits_tbl
GROUP BY date
Next you need to find the largest number of daily visits in each month. (http://sqlfiddle.com/#!9/a8455e/12/0)
SELECT month, MAX(visits) max_daily_visits
FROM (
SELECT date DIV 100 as month, date,
SUM(visits) as visits
FROM visits_tbl
GROUP BY date
) dayvisits
GROUP BY month
Then, the trick is retrieving the date on which that maximum occurred in each month. That requires a join. Without common table expressions (which MySQL lacks) you need to repeat the first subquery. (http://sqlfiddle.com/#!9/a8455e/11/0)
SELECT detail.*
FROM (
SELECT month, MAX(visits) max_daily_visits
FROM (
SELECT date DIV 100 as month, date,
SUM(visits) as visits
FROM visits_tbl
GROUP BY date
) dayvisits
GROUP BY month
) maxvisits
JOIN (
SELECT date DIV 100 as month, date,
SUM(visits) as visits
FROM visits_tbl
GROUP BY date
) detail ON detail.visits = maxvisits.max_daily_visits
AND detail.month = maxvisits.month
The outline of this rather complex query helps explain it. Instead of that subquery, we'll use an imaginary table called dayvisits.
SELECT detail.*
FROM (
SELECT month, MAX(visits) max_daily_visits
FROM dayvisits
GROUP BY date DIV 100
) maxvisits
JOIN dayvisits detail ON detail.visits = maxvisits.max_daily_visits
AND detail.month = maxvisits.month
You're seeking an extreme value for each month in the subquery. (This is a fairly standard sort of SQL operation.) To do that you find that value with a MAX() ... GROUP BY query. Then you join that to the subquery itself to find the other values corresponding to the extreme value.
If you did have common table expressions, the query would look like this. YOu might consider adopting the MySQL fork called MariaDB, which has CTEs.
WITH dayvisits AS (
SELECT date DIV 100 as month, date,
SUM(visits) as visits
FROM visits_tbl
GROUP BY date
)
SELECT dayvisits.*
FROM (
SELECT month, MAX(visits) max_daily_visits
FROM dayvisits
GROUP BY month
) maxvisits
JOIN dayvisits ON dayvisits.visits = maxvisits.max_daily_visits
AND dayvisits.month = maxvisits.month
[Query Check on MSSQL] its quick and efficient.
select visit_sum_day_wise.date
, visit_sum_day_wise.Max_Visits
, visit_sum_day_wise.traffic_type
, LAST_VALUE(visit_sum_day_wise.visits) OVER(PARTITION BY
visit_sum_day_wise.date ORDER BY visit_sum_day_wise.date ROWS BETWEEN
UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS max_visit_per_day
from (
select visits_tbl.date , visits_tbl.visits , visits_tbl.traffic_type
,max(visits_tbl.visits ) OVER ( PARTITION BY visits_tbl.date ORDER
BY visits_tbl.date ROWS BETWEEN UNBOUNDED PRECEDING AND 0
PRECEDING) Max_visits
from visits_tbl
) as visit_sum_day_wise
where visit_sum_day_wise.visits = (select max(visits_B.visits ) from
visits_tbl visits_B where visits_B.Date = visit_sum_day_wise.date )
enter image description here
I am building a query to return daily sales data. My current query returns a table similar to this:
----------------------------------
| DATE | SKU | TOTAL |
----------------------------------
| 2014-11-01 | AV155_A | 209.00 |
| 2014-11-02 | AV155_B | 627.00 |
| 2014-11-04 | AV155_C | 279.00 |
| 2014-11-05 | AV155 | 279.00 |
| 2014-11-08 | AV1556_A | 209.00 |
| 2014-11-09 | AV1556_B | 627.00 |
| 2014-11-10 | AV1556_C | 279.00 |
| 2014-11-12 | AV1556 | 279.00 |
What I would like is a results table that displays every day, even if there are no data points for that particular day. Something like this:
----------------------------------
| DATE | SKU | TOTAL |
----------------------------------
| 2014-11-01 | AV155_A | 209.00 |
| 2014-11-02 | AV155_B | 627.00 |
| 2014-11-03 | | 0 |
| 2014-11-04 | AV155_C | 279.00 |
| 2014-11-05 | AV155 | 279.00 |
| 2014-11-06 | | 0 |
| 2014-11-07 | | 0 |
| 2014-11-08 | AV1556_A | 209.00 |
| 2014-11-09 | AV1556_B | 627.00 |
| 2014-11-10 | AV1556_C | 279.00 |
| 2014-11-11 | | 0 |
| 2014-11-12 | AV1556 | 279.00 |
The query I currently have looks like this:
select
DATE_FORMAT(created_on, '%m-%d-%Y') as date,
sku,
SUM(price) as total
FROM order_items
WHERE created_on between FROM_UNIXTIME(1415577600) AND NOW()
GROUP BY MONTH(created_on), DAY(v.created_on), order_item_sku;
You need to use an outer join. The easiest way is if you have a calendar table, but you can make one on the fly:
select c.thedate, oi.sku, sum(price) as total
from (select date('2014-11-01') as thedate union all
date('2014-11-02') as thedate union all
date('2014-11-03') as thedate union all
date('2014-11-04') as thedate union all
date('2014-11-05') as thedate union all
date('2014-11-06') as thedate union all
date('2014-11-07') as thedate union all
date('2014-11-08') as thedate union all
date('2014-11-09') as thedate union all
date('2014-11-10') as thedate union all
date('2014-11-11') as thedate union all
date('2014-11-12') as thedate
) c left join
order_items oi
on c.thedate = date(oi.created_on)
where oi.created_on between FROM_UNIXTIME(1415577600) AND NOW()
group by ci.thedate, oi.sku
Here's an answer that addresses the need for a flexible list of dates. You need to figure out a way to get a virtual table containing all the dates in the appropriate range, and then join them to the summary. Here’s a query that will get the dates in the range.
SELECT mintime + INTERVAL seq.seq DAY AS reportdate
FROM (
SELECT MIN(DATE(created_on)) AS mintime,
MAX(DATE(created_on)) AS maxtime
FROM order_items
WHERE created_on >= starting_time
AND created_on <= NOW()
) AS order_items
JOIN seq_0_to_999 AS seq
ON seq.seq < TIMESTAMPDIFF(DAY,mintime,maxtime)
What’s going on here? Three things.
We have a subquery which determines the first and last day (min and max created_on) we care about reporting.
We apply a time range to that query. I like to avoid using BETWEEN for timestamp ranges because it often gets the ending time wrong in an off-by-one-second error.
We have a table called seq_0_to_999. It contains a sequence of a thousand cardinal numbers: the integers starting at zero. More about this in a moment.
Then, you can join that as a subquery to your aggregate query to get all the dates in the range listed, like so.
select DATE_FORMAT(d.reportdate, '%m-%d-%Y') as date,
sku,
SUM(price) as total
FROM (
SELECT mintime + INTERVAL seq.seq DAY AS reportdate
FROM (
SELECT MIN(DATE(created_on)) AS mintime,
MAX(DATE(created_on)) AS maxtime
FROM order_items
WHERE created_on >= starting_time
AND created_on <= NOW()
) AS order_items
JOIN seq_0_to_999 AS seq
ON seq.seq < TIMESTAMPDIFF(DAY,mintime,maxtime)
) AS d
LEFT JOIN order_items ON d.reportdate = DATE(order_items.created_on)
WHERE created_on >= starting_time
AND created_on <= NOW()
GROUP BY d.reportdate, sku
ORDER BY d.reportdate, sku
It looks like a big nasty hairball of a query. But if you think of it as a sandwich made of various layers of queries, it really isn't that complicated.
It uses LEFT JOIN so it makes sure all the dates in the range are preserved even if there's no corresponding data in your order_items table.
Finally, what about this seq_0_to_999 table? Where do we get those integers starting with zero? The answer is this: we have to arrange to do that; those numbers aren’t built in to MySQL. (They are built into the MySQL fork called MariaDB.) Create a short table with the integers from 0-9 in it, like so:
DROP TABLE IF EXISTS seq_0_to_9;
CREATE TABLE seq_0_to_9 AS
SELECT 0 AS seq 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;
Then create a view that joins that table with itself to generate 1000 combinations like this:
DROP VIEW IF EXISTS seq_0_to_999;
CREATE VIEW seq_0_to_999 AS (
SELECT (a.seq + 10 * (b.seq + 10 * c.seq)) AS seq
FROM seq_0_to_9 a
JOIN seq_0_to_9 b
JOIN seq_0_to_9 c
);
I wrote this up in some detail at http://www.plumislandmedia.net/mysql/filling-missing-data-sequences-cardinal-integers/
I have a table that defines time intervals.
_______________________________
| id | description | start_date |
|____|_____________|____________|
| 1 | First | NULL |
| 3 | Third | 2009-12-18 |
| 5 | Second | 2009-10-02 |
| 4 | Last | 2009-12-31 |
|____|_____________|____________|
It stores only the start date, the end date is a day before next date that follows.
I would like to have the next result:
____________________________________________
| id | description | start_date | end_date |
|____|_____________|____________|____________|
| 1 | First | NULL | 2009-10-01 |
| 5 | Second | 2009-10-02 | 2009-12-17 |
| 3 | Third | 2009-12-18 | 2009-12-30 |
| 4 | Last | 2009-12-31 | NULL |
|____|_____________|____________|____________|
How should I write this query, since a row contains values from other rows?
(I think MySQL function DATE_SUB could be useful.)
SELECT d.id, d.description, MIN(d.start_date), MIN(d2.start_date) - INTERVAL 1
DAY AS end_date
FROM start_dates d
LEFT OUTER JOIN start_dates d2 ON ( d2.start_date > d.start_date OR d.start_date IS NULL )
GROUP BY d.id, d.description
ORDER BY d.start_date ASC
try
select id, description, start_date, end_date from
(
select #rownum_start:=#rownum_start+1 rank, id, description, start_date
from inter, (select #rownum_start:=0) p
order by start_date
) start_dates
left join
(
select #rownum_end:=#rownum_end+1 rank, start_date - interval 1 day as end_date
from inter, (select #rownum_end:=0) p
where start_date is not null
order by start_date
) end_dates
using (rank)
where inter is your table
This actually returns:
mysql> select id, description, start_date, end_date from ...
+----+-------------+------------+------------+
| id | description | start_date | end_date |
+----+-------------+------------+------------+
| 1 | First | NULL | 2009-10-01 |
| 5 | Second | 2009-10-02 | 2009-12-17 |
| 3 | Third | 2009-12-18 | 2009-12-30 |
| 4 | Last | 2009-12-31 | NULL |
+----+-------------+------------+------------+
4 rows in set (0.00 sec)
if you are using PHP, just calculate the previous date in the script. that's easier.
if not, would something like:
SELECT ID,description,start_date,start_date-1 AS end_date FROM ...
work?
UPDATE: it works partially, as it returns 20091224 for startdate 2009-12-25 but won't work for dates like 2009-12-01 etc.
try
Select d.Id, d.Description, d.Start_Date,
n.Start_Date - 1 EndDate
From Table d
Left Join Table n
On n.Start_Date =
(Select Min(Start_date)
From Table
Where Start_Date > Coalesce(d.Start_Date, '1/1/1900')