MySQL count day of week between two dates over a table - mysql

I have a booking table with the following columns:
id, start_date, end_date
I want to know which days have had the most bookings over my dataset.
I can use dayofweek() on the start date and group by this also and use a count(*). But I also want to include the days between the start of booking and end.
An example output wouldbe
dayofweek count(*)
1 1
2 1
3 1
4 2
5 3
6 3
7 1
for the following set
id start_date end_date
1 2017-10-01 2017-10-07
2 2017-10-04 2017-10-07
3 2017-10-06 2017-10-08

I am assuming you wish to know something like how many rooms are filled for each date for the duration between the start and end. The"trick" here is that a long period between start/end will repeat the day or week and/or that the end day of week might be smaller than the start day of week. So, I have:
generated a list of 100,000 dates (1 per row)
joined those dates between the start/end of your table
converted each joined rows to a day of week number to be counted
left joined to a list of 1 to 7, and counted the rows of step 3
NOTE: if the end_date is a "check out date" then it may be necessary to deduct 1 day from each record to compensate (which is not done below).
This approach is available for review here at SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE Table1
(`id` int, `start_date` datetime, `end_date` datetime)
;
INSERT INTO Table1
(`id`, `start_date`, `end_date`)
VALUES
(1, '2017-09-21 00:00:00', '2017-10-07 00:00:00'), ## added this row
(1, '2017-10-01 00:00:00', '2017-10-07 00:00:00'),
(2, '2017-10-04 00:00:00', '2017-10-07 00:00:00'),
(3, '2017-10-06 00:00:00', '2017-10-08 00:00:00')
;
Query:
set #commence := str_to_date('2000-01-01','%Y-%m-%d')
select
w.dy
, count(t.wdy)
from (
select 1 dy union all select 2 dy union all select 3 dy union all
select 4 dy union all select 5 dy union all select 6 dy union all select 7 dy
) w
left join (
select DAYOFWEEK(cal.dy) wdy
from (
select adddate( #commence ,t4.i*10000 + t3.i*1000 + t2.i*100 + t1.i*10 + t0.i) dy
from ( select 0 i 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) t0
cross join (select 0 i 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) t1
cross join (select 0 i 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) t2
cross join (select 0 i 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) t3
cross join (select 0 i 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) t4
) cal
INNER JOIN Table1 t on cal.dy between t.start_date and t.end_date
) t on w.dy = t.wdy
group by
w.dy
Results:
| dy | count(t.wdy) |
|----|--------------|
| 1 | 4 |
| 2 | 3 |
| 3 | 3 |
| 4 | 4 |
| 5 | 5 |
| 6 | 6 |
| 7 | 6 |
Also see: How to get list of dates between two dates in mysql select query where the accepted answer is the basis for the set of cross joins that produces 100,000 dates starting from a nominated date. I modified that however for syntax (explicit cross join syntax), a parameter as start point, and use of union all for efficiency.

You can accomplish this with a recursive table:
WITH cte AS
(
SELECT DATE_ADD(start_date INTERVAL 1 DAY) AS date, end_date, DAYOFWEEK(start_date) AS dw from bookings
UNION ALL
SELECT DATE_ADD(start_date INTERVAL 1 DAY), end_date, DAYOFWEEK(date)
FROM cte WHERE date <= end_date
)
SELECT COUNT(*), dw FROM cte GROUP BY dw

Related

Get data from first date on database till today

I want to group my data by date and view it on chart. here my data example:
count created_at
----- ----------
5 2020/04/20
4 2020/04/21
3 2020/04/25
9 2020/04/28
i want my data when i fetch be like
count created_at
----- ----------
5 2020/04/20
4 2020/04/21
0 2020/04/22
0 2020/04/23
0 2020/04/24
3 2020/04/25
0 2020/04/26
0 2020/04/27
9 2020/04/28
when there is no data in that date it will shown 0. can u guys help me how to get it on laravel controller?
thanks before.
#Alun here is an example of how you can do this:
SELECT `dateList`.`Date`,
CASE WHEN `yt`.`date` IS NULL THEN 0
ELSE COUNT(`yt`.`id`)
END AS `amt`
FROM
-- this is the part you can copy/paste
-- to be used to left join in a table of your choice
-- ---------------------------------------------------------------------------------------------------------------------------
(
SELECT DATE(`a`.`Date`) AS `Date`
FROM (
SELECT NOW() - INTERVAL (`a`.`a` + (10 * `b`.`a`) + (100 * `c`.`a`)) DAY AS `Date`
FROM (SELECT 0 AS `a` 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) AS `a`
CROSS JOIN (SELECT 0 AS `a` 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) AS `b`
CROSS JOIN (SELECT 0 AS `a` 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) AS `c`
) AS `a`
WHERE `a`.`Date` BETWEEN '2020-04-01' AND NOW()
) AS `dateList`
-- ---------------------------------------------------------------------------------------------------------------------------
LEFT JOIN `yourTable` AS `yt` ON `dateList`.`Date` = DATE(`yt`.`date`)
GROUP BY `dateList`.`Date`
ORDER BY `dateList`.`Date` ASC
Just sub your table name in place of yourTable in this query (modifying the date column name from your table replacing yt.date accordingly) and this will give you a list for every day of the month showing 0 for any day of the month that doesn't have any value.
As far as getting this to work using eloquent in a Laravel controller it might be possible but off the top of my head I don't know how you would do it. You could use this this raw SQL in the controller to accomplish what you want though.
Here's an example of how it works in sql fiddle.
Hope this helps!

mysql select odd number values in a column that includes delimiter separated values

My next database table will be set up more optimally. Unfortunately this one was already set up where one column [data] contains checkbox array values that were saved the following way:
value 1|~|value 1 value 2|~|value 2 value 3|~|value 3
Not optimal, I know.
What I need is a mysql query that select only the values in [data] column in front of the |~|. Basically think I need to select the only odd values.
Any help pointing me in the right direction is greatly appreciated. I tried an if statement in a query and it did not work. Of course I deleted that by mistake.
What I need is a mysql query that select only the
values in [data] column in front of the |~|.
One thing to note the numbers before |~| must be unique.
It will not show the same number twice.
Query
SELECT
DISTINCT
SUBSTRING (
record_data.column
, LOCATE('|~|', record_data.`column` , number_generator.number) - 1
, 1
) AS number
FROM (
SELECT
#row := #row + 1 AS number
FROM (
SELECT 0 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
) record_1
CROSS JOIN (
SELECT 0 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
) record_2
CROSS JOIN (
SELECT 0 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
) record_4
CROSS JOIN (
SELECT 0 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
) record_5
CROSS JOIN (
SELECT #row := 0
) AS init_user_params
) AS number_generator
CROSS JOIN (
SELECT
*
FROM (
SELECT 'value 1|~|value 1 value 2|~|value 2 value 3|~|value 3' AS `column`
) AS record
) AS record_data
WHERE
LOCATE('|~|', record_data.`column` , number_generator.number) <> 0
Result
| number |
| ------ |
| 1 |
| 2 |
| 3 |
demo

Select missing dates from a table in MySQL

I'm thinking about what would be the best way to do what I'm trying to accomplish. As the title says, I have a table with missing dates in mysql, like this:
+------------+
| 2015-09-01 |
| 2015-09-03 |
| 2015-09-05 |
| 2015-09-06 |
| 2015-09-07 |
+------------+
I want to select them to assign them another column with a null value, so if I also wanted to select the missing dates (days 2 and 4 in my case), which are my options? I thought about making a "calendar" table and select both with a left join and using not in, but that means I'd have to fill it with a TON of dates (in case someone for some reason wants to select data from the year 2500 or 1800, and I don't want that).
I don't like the option of using a calendar table because of performance stuff, but it also brings another question: if I still used a calendar table, and restricted people from selecting dates in the past or in the future (thus freeing me from having to put hundreds of years of margin in case some crazy soul decided to select stuff from there), how could I make the table fill itself so it has a date for the current date without me having to insert the current date manually?
Can a trigger/function run itself everyday and insert the current date there?
Edit: My intention is using the table for chartjs, so it can have "holes" where there's missing data (with null values).
Check this:
select this_date,given_dates from
(SELECT ADDDATE('2015-09-01', INTERVAL #i:=#i+1 DAY) AS this_date
FROM (
SELECT a.a
FROM (SELECT 0 AS a 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) AS a
CROSS JOIN (SELECT 0 AS a 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) AS b
CROSS JOIN (SELECT 0 AS a 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) AS c
) a
JOIN (SELECT #i := -1) r1
WHERE
#i < DATEDIFF('2018-01-30', '2015-09-01')) as a
left JOIN
(select given_dates from
(select '2015-09-01' as given_dates union all
select '2015-09-03' union all
select '2015-09-05' union all
select '2015-09-06' union all
select '2015-09-07') as a) as b
on a.this_date = b.given_dates
sample Result:
2015-09-01 2015-09-01
2015-09-02
2015-09-03 2015-09-03
2015-09-04
2015-09-05 2015-09-05
2015-09-06 2015-09-06
2015-09-07 2015-09-07
2015-09-08
2015-09-09
2015-09-10
2015-09-11
You can get easily the null value of it.

How to show 0 when no data

I want to show 0 or something i want when no data.And this is my query.
SELECT `icDate`,IFNULL(SUM(`icCost`),0) AS icCost
FROM `incomp`
WHERE (`icDate` BETWEEN "2016-01-01" AND "2016-01-05")
AND `compID` = "DDY"
GROUP BY `icDate`
And this is result of this query.
icDate | icCost
--------------------------
2016-01-01 | 1000.00
2016-01-02 | 2000.00
2016-01-03 | 3000.00
2016-01-04 | 4000.00
2016-01-05 | 5000.00
If every day i want to show data it have a data,It wasn't problem.But it have some day,It don't have data. This will not show this day, Like this.
icDate | icCost
--------------------------
2016-01-01 | 1000.00
2016-01-02 | 2000.00
2016-01-04 | 4000.00
2016-01-05 | 5000.00
But i want it can show data like this.
icDate | icCost
--------------------------
2016-01-01 | 1000.00
2016-01-02 | 2000.00
2016-01-03 | 0.00
2016-01-04 | 4000.00
2016-01-05 | 5000.00
How to write query to get this answer.Thank you.
I made a simulation but I could not see your problem. I created a table for teste and after insert data this was my select. But the test was normal!
SELECT icDate,
format(ifnull(sum(icCost), 0),2) as icCost,
count(icDate) as entries
FROM incomp
WHERE icDate BETWEEN '2016-01-01' AND '2016-01-05'
AND compID = 'DDY'
group by icDate;
This is result of my test, exported in csv file:
icDate | icCost | entries
----------------------------------
2016-01-01 | 8,600.00 | 8
2016-01-02 | 5,600.00 | 4
2016-01-03 | 5,400.00 | 3
2016-01-04 | 0.00 | 1
2016-01-05 | 7,050.00 | 7
Does the icCost field is setting with null value ​​or number zero? Remember some cases that null values ​​setted may be different from other one as empty.
I found the answers, It worked with calendar table.
SELECT tbd.`db_date`,
(SELECT IFNULL(SUM(icCost),0) AS icCost
FROM `incomp`
WHERE icDate = tbd.db_date
AND compID = "DDY"
)AS icCost
FROM tb_date AS tbd
WHERE (tbd.`db_date` BETWEEN "2016-01-01" AND "2016-01-05")
GROUP BY tbd.`db_date`
LIMIT 0,100
Simply, But work.
Ok, you can investigate if you table is filled correctly every day. First you can create a temporary table like this:
CREATE TEMPORARY TABLE myCalendar (
CalendarDate date primary key not null
);
So, after you need to fill this table with valid days. For it, use this procedure:
DELIMITER $$
CREATE PROCEDURE doWhile()
BEGIN
# IF YOU WANT TO USE CURRENT MONTH
#SET #startCount = ADDDATE(LAST_DAY(SUBDATE(CURDATE(), INTERVAL 1 MONTH)), 1);
#SET #endCount = LAST_DAY(sysdate());
# USE TO SET A DATE
SET #startCount = '2016-01-01';
SET #endOfCount = '2016-01-30';
WHILE #startCount <= #endOfCount DO
INSERT INTO myCalendar (CalendarDate) VALUES (#startCount);
SET #startCount = date_add(#startCount, interval 1 day);
END WHILE;
END$$;
DELIMITER ;
You need to run this procedure by command:
CALL doWhile();
Now, run the follow:
SELECT format(ifnull(sum(t1.icCost), 0),2) as icCost,
ifnull(t1.icDate, 'Not found') as icDate,
t2.CalendarDate as 'For the day'
from incomp t1
right join myCalendar t2 ON
t2.CalendarDate = t1.icDate group by t2.CalendarDate;
I think this will help you to find a solution, for example, if exists a register for a day or not.
I hope this can help you!
[]'s
Sorry for my earlier answer. I gave a MSSQL answer instead of a MySQL answer.
You need a calendar table to have a set of all dates in your range. This could be a permanent table or a temporary table. Either way, there are a number of ways to populate it. Here is one way (borrowed from here):
set #beginDate = '2016-01-01';
set #endDate = '2016-01-05';
create table DateSequence(Date Date);
insert into DateSequence
select * 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
where selected_date between #beginDate and #endDate
Your best bet is probably to make a permanent table that has every possible date. That way you only have to populate it once and it's ready to go whenever you need it.
Now you can outer join the calendar table with your inComp table.
set #beginDate date = '2016-01-01'
set #endDate date = '2016-01-05'
select d.Date,
sum(ifnull(i.icCost, 0)) inComp
from DateSequence d
left outer join inComp i on i.icDate = d.Date
where d.Date between #beginDate and #endDate
and i.compID = 'DDY'
group by d.date
order by d.Date;

Select the latest recored in each 2-minute interval in MySQL

I want to read about 8000 files, each containing the daily stock prices of a distinct stock, into a single table and select the latest price in each 2-minute interval and write a Null if no record available in an interval. My idea is add a column called bucketNumber to indicate which interval the record falls into, create another table containing one column of values 1, 2, ..., 195 repeating 8000 times and then joining the two tables. At last select the record with largest timestamps for records with the same bucketNumber.
Is this a good way to do the job? If it is, then how to efficiently generate a table with one column of values 1, 2, ..., 195 repeating 8000 times.
Here's a query that will return you a column of integer values from 1 to 8000
SELECT thousands.d*1000 + hundreds.d*100 + tens.d*10 + ones.d + 1 AS num
FROM ( SELECT 0 AS d 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
) ones
CROSS
JOIN ( SELECT 0 AS d 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
) tens
CROSS
JOIN ( SELECT 0 AS d 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
) hundreds
CROSS
JOIN ( SELECT 0 AS d 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
) thousands
HAVING num <= 8000
ORDER BY num
Seems like a stored procedure would be the easiest approach. Just loop through each 2 minute interval and select the price from the record having the maximum time in the interval. You could include arguments for the start and end time, which would provide a more general solution.