Counting between dates - mysql

I need the count of all dates including the nonexistent
SELECT ifnull(COUNT(*),0) as num , date_format(c.dataCupo,"%d/%m/%Y") as data
FROM cupons c
WHERE c.dataCupo between "2017-02-02" AND "2018-05-04" AND c.proveidor!="VINCULADO" and c.empresa=1
group by date_format(c.dataCupo,"%Y-%m-%d")
//And I need to count all months including the nonexistent
SELECT ifnull(COUNT(*),0) as num , date_format(c.dataCupo,"%m/%Y") as data
FROM cupons c
WHERE c.dataCupo between "2017-02-02" AND "2018-05-04" AND c.proveidor!="VINCULADO" and c.empresa=1
group by date_format(c.dataCupo,"%Y-%m")
//And I need to count of all years including the nonexistent
SELECT ifnull(COUNT(*),0) as num , date_format(c.dataCupo,"%Y") as data
FROM cupons c
WHERE c.dataCupo between "2015-02-02" AND "2018-05-04" AND c.proveidor!="VINCULADO" and c.empresa=1
group by date_format(c.dataCupo,"%Y")
The result i want its:
02/02/2017 | 10
03/02/2017 | 0
04/02/2017 | 2
05/02/2017 | 0
....
AND
02/2017 | 50
03/2017 | 0
04/2017 | 10
AND
2015 | 0
2016 | 10
2017 | 15
2018 | 0

Easiest way to do this is with a Calendar table. This table will have a datetime column that you can join to and is really useful for reporting. Here goes an example of how to make one in MySQL.
https://gist.github.com/bryhal/4129042
Now that you have the Calendar table, you can join to it to find counts of all dates in a date range.
All days example:
select num, td.db_date
FROM
time_dimension td
left join
(SELECT ifnull(COUNT(*),0) as num , c.dataCupo as data
FROM cupons c
WHERE c.dataCupo between "2017-02-02" AND "2018-05-04" AND
c.proveidor!="VINCULADO" and c.empresa=1
group by c.dataCupo) t
on t.data = td.db_date
WHERE td.db_date between "2017-02-02" AND "2018-05-04"
All months example:
select
sum(t.num),
CONCAT(month(td.db_date),"-",year(td.db_date))
FROM
time_dimension td
left join
(SELECT
ifnull(COUNT(*),0) as num ,
c.dataCupo as data
FROM cupons c
WHERE c.dataCupo between "2017-02-02" AND "2018-05-04" AND
c.proveidor!="VINCULADO" and c.empresa=1) t
on c.data = t.data
WHERE td.db_date between "2017-02-02" AND "2018-05-04"
group by CONCAT(month(td.db_date),"-",year(td.db_date))

You should create A Temporary Table To Store All The Date Ranges Between Your Date Ranges
CREATE TEMPORARY TABLE IF NOT EXISTS AllDateRange engine=memory
SELECT DATE(cal.date) Date
FROM (
SELECT ( case when #prmToDate = #prmFromDate then #prmFromDate else
SUBDATE( #prmFromDate, INTERVAL (DATEDIFF(#prmToDate,#prmFromDate)) DAY) + INTERVAL xc DAY end ) AS Date
FROM (
SELECT #xi:=#xi+1 as xc from
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) xc1,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) xc2,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) xc3,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) xc4,
(SELECT #xi:=+1) xc0
) xxc1
) cal WHERE DATE( cal.date) >= DATE(#prmFromDate) and DATE( cal.date) <= DATE(#prmToDate) ;
And Then Join It With Your Table As.
SELECT count(COALESCE(c.empresa, 0)) as num , date_format(a.Date,"%d/%m/%Y") as data from AllDateTimeRange a
left join cupons c on a.Date=date_format(c.dataCupo,"%Y-%m-%d")
WHERE c.dataCupo between #prmFromDate AND #prmToDate AND c.proveidor!="VINCULADO" and c.empresa=1
group by date_format(c.dataCupo,"%Y-%m-%d");
Similarly Create Temp Tables For Month & Year And Then Join With Your Primary Table, as above in order to get your required results for month and year respectively.

Related

How can I select related items within the same table in one SQL query

I have a table looking like this:
id | date | related_id
1 2018-01-01
2 2018-01-01
3 2018-01-02
4 2018-01-05 2
5 2018-01-06
A query SELECT * FROM table WHERE date='2018-01-01' should produce the following result:
id | date | related_id
1 2018-01-01
2 2018-01-01
4 2018-01-05 2
How can I achieve that in one MySql query?
If you have only one "layer", you can do this:
SELECT t.*
FROM theTable AS t
LEFT JOIN theTable AS rt ON t.related_id = rt.id
WHERE t.`date` = searchValue OR rt.`date` = searchValue
;
If there are an indefinite number of layers, and you have MySQL 8.0, you can use a CTE:
WITH RECURSIVE myCte AS (
SELECT * FROM theTable WHERE `date` = searchValue
UNION
SELECT t.*
FROM theTable AS t
INNER JOIN myCTE ON t.related_id = myCTE.id
)
SELECT * FROM myCTE;
Disclaimer: I am more familiar with MS-SQL CTE's, so there could be some problems with that latter option.
Extend your WHERE condition to the related_id's date:
SELECT * FROM table t
WHERE
t.date = '2018-01-01'
OR
(SELECT date FROM table WHERE id = t.related_id) = '2018-01-01'
or with a self join:
SELECT t.*
FROM table t LEFT JOIN table tt
ON tt.id = t.related_id
WHERE
t.date = '2018-01-01'
OR
tt.date = '2018-01-01'
or with EXISTS:
SELECT t.*
FROM table t
WHERE
t.date = '2018-01-01'
OR
EXISTS (
SELECT 1 FROM table
WHERE id = t.related_id AND date = '2018-01-01'
)
You can use EXISTS :
SELECT t.*
FROM table t
WHERE t.date = '2018-01-01' OR
EXISTS (SELECT 1 FROM table t1 WHERE t.related_id = t1.id);
You can simply do this:
SELECT table.* FROM table LEFT JOIN table related
ON table.related_id = related.id
WHERE
table.date = '2018-01-01'
OR related.date = '2018-01-01';
this will work with the date function:
SELECT * FROM table WHERE date=DATE('2018-01-01');
or
SELECT * FROM table WHERE date=STR_TO_DATE(DATE, '%d/%m/%Y')

Get 3 consecutive days from MySQL data

I am trying to use a query from this SO question Check for x consecutive days - given timestamps in database to count a number of consecutive days a user has submitted an activity i.e. 3 days, 5 days, 7 days etc.
The query is:
SELECT IF(COUNT(1) > 0, 1, 0) AS has_consec
FROM
(
SELECT *
FROM
(
SELECT IF(b.dateAdded IS NULL, #val:=#val+1, #val) AS consec_set
FROM activity a
CROSS JOIN (SELECT #val:=0) var_init
LEFT JOIN activity b ON
a.userID = b.userID AND
a.dateAdded = b.dateAdded + INTERVAL 1 DAY
WHERE a.userID = 1
) a
GROUP BY a.consec_set
HAVING COUNT(1) >= 3
) a
The code works great when the date field is not dateTime but how would I modify the code to ignore the time component of dateTime? I have tried using DATE(dateAdded) but that didn't work.
My data looks like:
userID dateAdded
1 2016-07-01 17:01:56
1 2016-07-02 12:45:49
1 2016-07-03 13:06:27
1 2016-07-04 12:51:10
1 2016-07-05 15:51:10
2 2016-07-06 16:51:10
2 2016-07-07 11:51:10
1 2016-07-08 11:26:38
Thanks
Casted the dateAdded field to Date.
Please give it a try and let me know if it resolves the issue:
SELECT IF(COUNT(1) > 0, 1, 0) AS has_consec
FROM
(
SELECT *
FROM
(
SELECT IF(b.dateAdded IS NULL, #val:=#val+1, #val) AS consec_set
FROM activity a
CROSS JOIN (SELECT #val:=0) var_init
LEFT JOIN activity b ON
a.userID = b.userID AND
DATE(a.dateAdded) = DATE(b.dateAdded) + INTERVAL 1 DAY
WHERE a.userID = 1
) a
GROUP BY a.consec_set
HAVING COUNT(1) >= 3
) a;
Note: Using timestamp will return correct output only if they are having same time (hh:mm:ss)

Mysql Left outer join in my Query for optimize

I have two database table name called "tablestr table" and "restbookingtable":
tablestr:
str_id is primary key
restbooking:
bookingsection_id is foreign key
in booking table i storing str_id multiple values with comma separated and My query is
SELECT `str_id` FROM (`rest_tablestr`) WHERE str_id NOT IN (
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(t.bookingsection_id, ",", n.n), ",", -1) value FROM rest_restaurantbooking 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 ) n
WHERE n.n <= 1 + (LENGTH(t.bookingsection_id) -
LENGTH(REPLACE(t.bookingsection_id, ",", ""))) AND
t.res_id = 21 AND
t.booking_status not in ("cancelled","departed","noshow") AND
((t.bookingstart_time <= "2015-06-12 19:45:00" AND t.bookingend_time >= "2015-06-12 22:15:00") OR
(t.bookingend_time >= "2015-06-12 19:45:00" AND t.bookingend_time <= "2015-06-12 22:15:00") OR
(t.bookingstart_time >= "2015-06-12 19:45:00" AND t.bookingstart_time <= "2015-06-12 22:15:00") OR
(t.bookingstart_time >= "2015-06-12 19:45:00" AND t.bookingend_time <= "2015-06-12 22:15:00")) ) AND
`res_id` = '21' AND
`area_id` = '28' AND
`wait_table` = 'no' AND
`availability` = 'yes';
Result Set:
Can any body help me to rewrite query with left outer join or can be optimize query.

Difficult MySQL Query - Getting Max difference between dates

I have a MySQL table of the following form
account_id | call_date
1 2013-06-07
1 2013-06-09
1 2013-06-21
2 2012-05-01
2 2012-05-02
2 2012-05-06
I want to write a MySQL query that will get the maximum difference (in days) between successive dates in call_date for each account_id. So for the above example, the result of this query would be
account_id | max_diff
1 12
2 4
I'm not sure how to do this. Is this even possible to do in a MySQL query?
I can do datediff(max(call_date),min(call_date)) but this would ignore dates in between the first and last call dates. I need some way of getting the datediff() between each successive call_date for each account_id, then finding the maximum of those.
I'm sure fp's answer will be faster, but just for fun...
SELECT account_id
, MAX(diff) max_diff
FROM
( SELECT x.account_id
, DATEDIFF(MIN(y.call_date),x.call_date) diff
FROM my_table x
JOIN my_table y
ON y.account_id = x.account_id
AND y.call_date > x.call_date
GROUP
BY x.account_id
, x.call_date
) z
GROUP
BY account_id;
CREATE TABLE t
(`account_id` int, `call_date` date)
;
INSERT INTO t
(`account_id`, `call_date`)
VALUES
(1, '2013-06-07'),
(1, '2013-06-09'),
(1, '2013-06-21'),
(2, '2012-05-01'),
(2, '2012-05-02'),
(2, '2012-05-06')
;
select account_id, max(diff) from (
select
account_id,
timestampdiff(day, coalesce(#prev, call_date), call_date) diff,
#prev := call_date
from
t
, (select #prev:=null) v
order by account_id, call_date
) sq
group by account_id
| ACCOUNT_ID | MAX(DIFF) |
|------------|-----------|
| 1 | 12 |
| 2 | 4 |
see it working live in an sqlfiddle
If you have an index on account_id, call_date, then you can do this rather efficiently without variables:
select account_id, max(call_date - prev_call_date) as diff
from (select t.*,
(select t2.call_date
from table t2
where t2.account_id = t.account_id and t2.call_date < t.call_date
order by t2.call_date desc
limit 1
) as prev_call_date
from table t
) t
group by account_id;
Just for educational purposes, doing it with JOIN:
SELECT t1.account_id,
MAX(DATEDIFF(t2.call_date, t1.call_date)) AS max_diff
FROM t t1
LEFT JOIN t t2
ON t2.account_id = t1.account_id
AND t2.call_date > t1.call_date
LEFT JOIN t t3
ON t3.account_id = t1.account_id
AND t3.call_date > t1.call_date
AND t3.call_date < t2.call_date
WHERE t3.account_id IS NULL
GROUP BY t1.account_id
Since you didn't specify, this shows max_diff of NULL for accounts with only 1 call.
SELECT a1.account_id , max(a1.call_date - a2.call_date)
FROM account a2, account a1
WHERE a1.account_id = a2.account_id
AND a1.call_date > a2.call_date
AND NOT EXISTS
(SELECT 1 FROM account a3 WHERE a1.call_date > a3.call_date AND a2.call_date < a3.call_date)
GROUP BY a1.account_id
Which gives :
ACCOUNT_ID MAX(A1.CALL_DATE - A2.CALL_DATE)
1 12
2 4

find missing dates from date range

I have query regarding get the dates which are not exists in database table.
I have below dates in database.
2013-08-02
2013-08-02
2013-08-02
2013-08-03
2013-08-05
2013-08-08
2013-08-08
2013-08-09
2013-08-10
2013-08-13
2013-08-13
2013-08-13
and i want the result which is expected as below,
2013-08-01
2013-08-04
2013-08-06
2013-08-07
2013-08-11
2013-08-12
as you can see result has six dates which are not present into database,
i have tried below query
SELECT
DISTINCT DATE(w1.start_date) + INTERVAL 1 DAY AS missing_date
FROM
working w1
LEFT JOIN
(SELECT DISTINCT start_date FROM working ) w2 ON DATE(w1.start_date) = DATE(w2.start_date) - INTERVAL 1 DAY
WHERE
w1.start_date BETWEEN '2013-08-01' AND '2013-08-13'
AND
w2.start_date IS NULL;
but above return following result.
2013-08-04
2013-08-14
2013-08-11
2013-08-06
as you can see its giving me back four dates from that 14 is not needed but its still not contain 3 dates its because of left join.
Now please look into my query and let me know what are the best way i can do this?
Thanks for looking and giving time.
I guess you could always generate the date sequence and just use a NOT IN to eliminate the dates that actually exist. This will max out at a 1024 day range, but is easy to shrink or extend, the date column is called "mydate" and is in the table "table1";
SELECT * FROM (
SELECT DATE_ADD('2013-08-01', INTERVAL t4+t16+t64+t256+t1024 DAY) day
FROM
(SELECT 0 t4 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 ) t4,
(SELECT 0 t16 UNION ALL SELECT 4 UNION ALL SELECT 8 UNION ALL SELECT 12 ) t16,
(SELECT 0 t64 UNION ALL SELECT 16 UNION ALL SELECT 32 UNION ALL SELECT 48 ) t64,
(SELECT 0 t256 UNION ALL SELECT 64 UNION ALL SELECT 128 UNION ALL SELECT 192) t256,
(SELECT 0 t1024 UNION ALL SELECT 256 UNION ALL SELECT 512 UNION ALL SELECT 768) t1024
) b
WHERE day NOT IN (SELECT mydate FROM Table1) AND day<'2013-08-13';
From the "I would add an SQLfiddle if it wasn't down" dept.
Thanks for help here is the query i am end up with and its working
SELECT * FROM
(
SELECT DATE_ADD('2013-08-01', INTERVAL t4+t16+t64+t256+t1024 DAY) missingDates
FROM
(SELECT 0 t4 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 ) t4,
(SELECT 0 t16 UNION ALL SELECT 4 UNION ALL SELECT 8 UNION ALL SELECT 12 ) t16,
(SELECT 0 t64 UNION ALL SELECT 16 UNION ALL SELECT 32 UNION ALL SELECT 48 ) t64,
(SELECT 0 t256 UNION ALL SELECT 64 UNION ALL SELECT 128 UNION ALL SELECT 192) t256,
(SELECT 0 t1024 UNION ALL SELECT 256 UNION ALL SELECT 512 UNION ALL SELECT 768) t1024
) b
WHERE
missingDates NOT IN (SELECT DATE_FORMAT(start_date,'%Y-%m-%d')
FROM
working GROUP BY start_date)
AND
missingDates < '2013-08-13';
My bet would be probably to create a dedicated Calendar table just to be able to use it on a LEFT JOIN.
You could create the table on per need basis, but as it will not represent a such large amount of data, the simplest and probably most efficient approach is to create it once for all, as I do below using a stored procedure:
--
-- Create a dedicated "Calendar" table
--
CREATE TABLE Calendar (day DATE PRIMARY KEY);
DELIMITER //
CREATE PROCEDURE init_calendar(IN pStart DATE, IN pEnd DATE)
BEGIN
SET #theDate := pStart;
REPEAT
-- Here I use *IGNORE* in order to be able
-- to call init_calendar again for extend the
-- "calendar range" without to bother with
-- "overlapping" dates
INSERT IGNORE INTO Calendar VALUES (#theDate);
SET #theDate := #theDate + INTERVAL 1 DAY;
UNTIL #theDate > pEnd END REPEAT;
END; //
DELIMITER ;
CALL init_calendar('2010-01-01','2015-12-31');
In this example, the Calendar hold 2191 consecutive days, which represent at a roughly estimate less that 15KB. And storing all the dates from the 21th century will represent less that 300KB...
Now, this is your actual data table as described in the question:
--
-- *Your* actual data table
--
CREATE TABLE tbl (theDate DATE);
INSERT INTO tbl VALUES
('2013-08-02'),
('2013-08-02'),
('2013-08-02'),
('2013-08-03'),
('2013-08-05'),
('2013-08-08'),
('2013-08-08'),
('2013-08-09'),
('2013-08-10'),
('2013-08-13'),
('2013-08-13'),
('2013-08-13');
And finally the query:
--
-- Now the query to find date not "in range"
--
SET #start = '2013-08-01';
SET #end = '2013-08-13';
SELECT Calendar.day FROM Calendar LEFT JOIN tbl
ON Calendar.day = tbl.theDate
WHERE Calendar.day BETWEEN #start AND #end
AND tbl.theDate IS NULL;
Producing:
+------------+
| day |
+------------+
| 2013-08-01 |
| 2013-08-04 |
| 2013-08-06 |
| 2013-08-07 |
| 2013-08-11 |
| 2013-08-12 |
+------------+
This is how i would do it:
$db_dates = array (
'2013-08-02',
'2013-08-03',
'2013-08-05',
'2013-08-08',
'2013-08-09',
'2013-08-10',
'2013-08-13'
);
$missing = array();
$month = "08";
$year = "2013";
$day_start = 1;
$day_end = 14
for ($i=$day_start; $i<$day_end; $i++) {
$day = $i;
if ($i<10) {
$day = "0".$i;
}
$check_date = $year."-".$month."-".$day;
if (!in_array($check_date, $db_dates)) {
array_push($missing, $check_date);
}
}
print_r($missing);
I made it just to that interval but you can just define another interval or make it work for the whole year.
I'm adding this to the excellent answer by Dipesh if anybody wants more than 1024 days (or hours). I generated below 279936 hours from 2015 to 2046:
SELECT
DATE_ADD('2015-01-01', INTERVAL
POWER(6,6)*t6 + POWER(6,5)*t5 + POWER(6,4)*t4 + POWER(6,3)*t3 + POWER(6,2)*t2 +
POWER(6,1)*t1 + t0
HOUR) AS period
FROM
(SELECT 0 t0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t0,
(SELECT 0 t1 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t1,
(SELECT 0 t2 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t2,
(SELECT 0 t3 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t3,
(SELECT 0 t4 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t4,
(SELECT 0 t5 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t5,
(SELECT 0 t6 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t6
ORDER BY period
just plug this into the answer query.
The way I would solve in this in a datawarehouse-type situation is to populate a "static" table with dates over an appropriate period (there are example scripts for this type of thing which are easy to google) and then left outer join or right outer join your table to it: rows where there are no matches are the missing dates.
DECLARE #date date;
declare #dt_cnt int = 0;
set #date='2014-11-1';
while #date < '2014-12-31'
begin
select #dt_cnt = COUNT(att_id) from date_table where att_date=#date ;
if(#dt_cnt = 0)
BEGIN
print #date
END
set #date = DATEADD(day,1,#date);
end