I'm not very good when it comes to using joins - so I have a single table where I'm counting the number of records that meet certain conditions, and returns those counts by week. The problem is, I need the weeks that have a zero count too....I tried to get this to work with a left join, but I'm struggling...any help appreciated: (Stamp is a datetime field)
Query:
SELECT week(stamp), count(*) AS mycount, YEAR(stamp) as theyear
FROM merges
WHERE completed = 1
AND stamp BETWEEN '2017/4/1 00:00:00' AND '2017/6/1 00:00:00' GROUP BY week(stamp)
This returns:
week(stamp) | mycount | theyear
15 | 21 |2017
17 | 10 |2017
18 | 62 |2017
19 | 13 |2017
20 | 76 |2017
21 | 22 |2017
Notice week 16 is missing? I need to have this result included in the above, like:
16 | 0 |2017
I appreciate any help - I know this isn't too difficult, but I'm pulling my hair out trying to understand how to do this while I read other posts....
select weekValue, yearValue, coalesce(mycount,0)
from
( SELECT distinct week(#startDate := #startDate + Interval 1 day) as weekValue,
year(#startDate := #startDate + Interval 1 day) as yearValue
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) t,
(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 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) t4,
(SELECT #startDate := '2017-03-31 00:00:00' ) as g
where
#startDate < '2017-06-01 00:00:00' ) as generateWeekYear left join
(SELECT week(stamp) as theweek, count(*) AS mycount, YEAR(stamp) as theyear
FROM merges
WHERE completed = 1
AND stamp BETWEEN '2017/4/1 00:00:00' AND '2017/6/1 00:00:00' GROUP BY week(stamp) ) as actualQuery
on generateWeekYear.weekValue = actualQuery.theweek
and generateWeekYear.yearValue = actualQuery.theyear
Let me explain the above query,
Sub Query generateWeekYear = This is used to genearate distinct week and year based on two inputs
lets say startDate and endDate. startDate should be 1 day less to actual startDate. Because if you do not
subtract 1 day then there might chance to loose one week.
Now you have all week and year which needs to be displayed.
Now you are thinking generateWeekYear is going to be more time to execute but this is not case. You can
check this generate an integer sequence in MySQL.
After that you simply join your table with above table and you can get your required result.
Related
I have a table with reservations in it. Each row is a reservation and has a start & end datetime field.
I want to construct a query which gives me the count of reservations on each day in a certain time interval, eg april 2018.
Selecting all the reservations within the given interval is fairly simple:
SELECT * FROM reservation
WHERE start <= '2018-05-01 00:00:00'
AND end >= '2018-04-01 00:00:00'
But then the 'trouble' starts.
I want to display a 'count' of reservations on each day in the interval. But a reservation could span multiple days. So grouping them on DAY(start) is not correct.
I don't want to query each day in the interval seperately as this would be very server-intensive.
Is there a way to do this through a MySQL query?
Sample data:
id | start | end
2 | 2018-04-01 12:00:00 | 2018-04-03 09:00:00
3 | 2018-04-01 09:00:00 | 2018-04-01 11:00:00
4 | 2018-04-06 13:00:00 | 2018-05-20 09:00:00
Result for 2018-04-01 to 2018-04-06:
2018-04-01 | 2 (2/3)
2018-04-02 | 1 (2)
2018-04-03 | 1 (2)
2018-04-04 | 0
2018-04-05 | 0
2018-04-06 | 1 (4)
in a sqlfiddle: http://sqlfiddle.com/#!9/e62ffa/2/0
First we will reuse the answer from DBA StackExchange. (You can use the accepted answer if you want, you would just need to create a dedicated table for that).
We will just modify the query a bit by using the condition that you need.
Your condition:
SELECT * FROM reservation
WHERE start <= '2018-05-01 00:00:00'
AND end >= '2018-04-01 00:00:00'
Modified answer from DBA Stackexchange:
SELECT date_field
FROM
(
SELECT
MAKEDATE(YEAR(NOW()),1) +
INTERVAL (MONTH(NOW())-1) MONTH +
INTERVAL daynum DAY date_field
FROM
(
SELECT t * 10 + u daynum
FROM
(SELECT 0 t UNION SELECT 1 UNION SELECT 2 UNION SELECT 3) A,
(SELECT 0 u 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) B
ORDER BY daynum
) AA
) AAA
/*WHERE MONTH(date_field) = MONTH(NOW())*/
WHERE date_field BETWEEN '2018-04-01' AND '2018-05-01'
Take note that I only changed the WHERE Clause.
Now using that query as a DERIVED TABLE, we will include your Reservations table using LEFT JOIN.
SELECT D.date_field
, COUNT(R.Id)
FROM (
/* The query from above goes here */
) D
LEFT JOIN Reservations R ON D.date_field BETWEEN DATE(R.StartDate) AND DATE(R.EndDate)
GROUP BY D.date_field
Notice again that we used the DATE function to truncate the TIME part of our StartDate and EndDate because for example, 2018-04-01 denotes the whole day and it cannot be in between 2018-04-01 09:00:00 and 2018-04-01 11:00:00 for some under the hood reason I am not completely familiar of.
Here is a SQL Fiddle Demo of the result.
If someone could help me on this one. SELECT '2018-04-02' BETWEEN '2018-04-01 23:59:59' AND '2018-04-02 00:00:00' will result to 1 (TRUE). It seems that by default DATE will have a TIMESTAMP of 00:00:00.
Update for More Flexible Date Range (2018-04-11)
The query above from DBA StackExchange only lists down the days of the current month. I tried to search a bit and found this another good answer here in StackOverflow. Here is a part of the query:
SELECT CURDATE() - INTERVAL (A.A+ (10 * B.A)) DAY AS Date
FROM (
SELECT 0 AS A 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
) AS A
CROSS JOIN (
SELECT 0 AS A 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
) AS B
The query above will generate numbers (1 to 100) using CROSS JOIN and then subtracting it to the Current Date, then you will have dates from now up to 100 days back. You can add another CROSS JOIN of numbers to generate 1000 numbers if necessary.
I assume you will have StartDate and EndDate in your stored procedure or somewhere. We can replace the CURDATE with EndDate and then we will have 100 days back up to our EndDate. We will just add a WHERE clause to filter only the dates that we need using subquery/derived table.
SELECT D.Date
FROM (
SELECT CURDATE() - INTERVAL (A.A+ (10 * B.A)) DAY AS Date
FROM (
SELECT 0 AS A 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
) AS A
CROSS JOIN (
SELECT 0 AS A 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
) AS B
) AS D
WHERE D.Date BETWEEN #startDate AND #endDate
We can now use LEFT JOIN to include the Reservations table.
Here is another SQL Fiddle Demo for that. This also includes the Start and End Date variables, and a sample date range spanning from a previous year to the current year.
Again if you need more than 100 days of range, we will just need to add another CROSS JOIN of numbers, let's name that as C:
CROSS JOIN (
SELECT 0 AS A 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
) AS C
And then add it to the calculation of past days in the SELECT statement.
SELECT CURDATE() - INTERVAL (A.A + (10 * B.A) + (100 * C.A)) DAY AS Date
I have table shown below :
login
date user
2016-11-23 1
2016-11-23 2
2016-11-23 3
2016-11-25 2
2016-11-25 5
2016-11-27 1
from above table what I want to get is like this:
date count(*)
2016-11-21 0
2016-11-22 0
2016-11-23 3
2016-11-24 0
2016-11-25 2
2016-11-26 0
2016-11-27 1
But, because there are only dates 2016-11-23 and 2016-11-25 and 2016-11-27, when I query like this :
select date, count(*)
from login
where date between (current_date()-interval 7 day) and current_date()
group by date
order by date asc
It can't get result like what I really want to get. Is that result possible from my login table?
One way is to generate all days before JOIN
select GenDate, count(Date)
from login
right join
(select a.GenDate
from (
select curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY as GenDate
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
where a.GenDate between (current_date()-interval 7 day) and current_date())x
ON x.GenDate=login.Date
group by GenDate
order by GenDate asc
Use a derived table with the wanted dates :
SELECT t.date, count(s.date)
FROM (SELECT '2016-11-21' as `date` UNION ALL
SELECT '2016-11-22' as `date` UNION ALL
...) t
LEFT JOIN login s
ON(t.date = s.date)
WHERE
t.date between (current_date()-interval 7 day) and current_date()
GROUP BY t.date
ORDER BY t.date
This is a very well known problem in programming. There are several solutions.
Go over the result with PHP, and fill the missing days in the resulting array.
AS sagi proposed, create a separate table that contains all the dates in the range of days your application works with, then you can JOIN that table with your query. One of the issues is that from time to time you have to add more days to this table, if you suddenly have missing days in future or in past.
I'm trying to count entries grouped per hour.
I've found some useful info inform on different sites and here on: MySQL Group By Hours
But the result is not what I've expected.
With the following code I get:
SELECT CONCAT(Hour, ':00-', Hour+1, ':00') AS Hours,
COUNT(`usage_time`) AS `usage` FROM `usage`
RIGHT JOIN (
SELECT 0 AS Hour
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
UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12
UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL SELECT 15
UNION ALL SELECT 16 UNION ALL SELECT 17 UNION ALL SELECT 18
UNION ALL SELECT 19 UNION ALL SELECT 20 UNION ALL SELECT 21
UNION ALL SELECT 22 UNION ALL SELECT 23
) AS AllHours ON HOUR(`usage_time`) = Hour
WHERE `usage_function` LIKE 'PlayedWholeSong' AND `usage_date` = DATE_SUB(CURDATE(), INTERVAL 0 DAY) OR `usage_time` IS NULL
GROUP BY Hour
ORDER BY Hour
Result:
Hours usage
2:00-3:00 0
4:00-5:00 6
6:00-7:00 2
8:00-9:00 3
9:00-10:00 20
10:00-11:00 1
14:00-15:00 14
15:00-16:00 1
16:00-17:00 32
17:00-18:00 10
As these are entry's from today, I don't have any entries after 19:00.
Also I don't see an entry from 00:00 - 01:00, 03:00 - 04:00 and several others are missing.
But I do want to show a list with every 24 hour and the result, even if there's nothing.
String thing is the result shows a 0 between 02:00 - 03:00.
I've learned a lot today about mysql, but nothing that solves my issue.
I hope you can learn me something, doesn't have to be code, a direction would be great.
I prefer LEFT JOIN over RIGHT JOIN personally. That way you can add your WHERE criteria in your JOIN and it won't constrict your results. Try this:
SELECT CONCAT(Hour, ':00-', Hour+1, ':00') AS Hours,
COUNT(`usage_time`) AS `usage`
FROM
(
SELECT 0 AS Hour
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
UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12
UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL SELECT 15
UNION ALL SELECT 16 UNION ALL SELECT 17 UNION ALL SELECT 18
UNION ALL SELECT 19 UNION ALL SELECT 20 UNION ALL SELECT 21
UNION ALL SELECT 22 UNION ALL SELECT 23
) AS AllHours
LEFT JOIN `usage` ON HOUR(`usage_time`) = Hour
AND `usage_function` LIKE 'PlayedWholeSong'
AND `usage_date` = DATE_SUB(CURDATE(), INTERVAL 0 DAY)
GROUP BY Hour
ORDER BY Hour
Here is a simplified SQL Fiddle.
Good luck.
I was wondering if this is possible:
I have some data where i have an datetime field. Now i want to make an sql query where i can make groups by month and in each month by day.
Something like this:
Month day COUNT(*)
1 1 200
1 2 300
1 3 500
2 1 600
2 2 0
Why i need this? I need to make an sql query to make an chart XY and show fill this requeriments:
SELECT series,value1,value2 FROM...WHERE...GROUP BY...ORDER BY.
So i want to make each month to be an SERIE, and then each day is value1, and the count value 2
Hope everyone understand my bot question...
Best Regards and tks in advanced
Is this all you're looking for?
SELECT MONTH(m), DAY(d), COUNT(*)
FROM sparkles
WHERE YEAR(y) = 2013
GROUP BY MONTH(m), DAY(d)
If your dates have gaps, you will need to use a date lookup table.
Use the MONTH() and DAYOFMONTH() functions.
Here is the documentation: dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html
Try this:
SELECT Month, Day, Count(*) FROM yout_table group by Month, Day
First you are going to need a table that holds every day this year:
CREATE TABLE DaysThisYear
(
dt datetime not null,
mm int, dd int,
primary key (dt)
);
INSERT INTO DaysThisYear (dt,mm,dd)
SELECT ymd,MONTH(ymd),DAY(ymd) FROM
(SELECT IFNULL(ymd + INTERVAL 0 SECOND,'1980-01-01 00:00:00') ymd
FROM (SELECT CONCAT(yy,'-',SUBSTR(mm+100,2),'-',SUBSTR(dd+100,2)) ymd,yy
FROM (SELECT 1 dd 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
UNION SELECT 13 UNION SELECT 14 UNION SELECT 15 UNION SELECT 16
UNION SELECT 17 UNION SELECT 18 UNION SELECT 19 UNION SELECT 20
UNION SELECT 21 UNION SELECT 22 UNION SELECT 23 UNION SELECT 24
UNION SELECT 25 UNION SELECT 26 UNION SELECT 27 UNION SELECT 28
UNION SELECT 29 UNION SELECT 30 UNION SELECT 31 UNION SELECT 32) AAA,
(SELECT YEAR(NOW()) yy,mm FROM
(SELECT 1 mm 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) M) BBB) AA) A
WHERE YEAR(ymd) <> 1980
ORDER BY ymd;
To see that every day for this year was loaded, run this:
SELECT * FROM DaysThisYear;
Now, if you have a table with a DATETIME column, you can join the DaysThisYear table to it
For example, lets say your table looks like this:
CREATE TABLE mydata
(
id int not null auto_increment,
dt DATETIME,
.
.
.
PRIMARY KEY (id),
KEY dt (dt)
);
You could perform something like this:
SELECT A.mm,A.dd,SUM(IF(ISNULL(B.mm),0,1)) mmdd_count
FROM DaysThisYear A LEFT JOIN
(SELECT MONTH(dt) mm,DAY(dy) dd FROM mydata) B
ON A.mm=B.mm AND A.dd=B.dd;
Give it a Try !!!
Before anything else, here is the simplified schema (with dummy records) of the database:
ItemList
ItemID ItemName DateAcquired Cost MonthlyDep CurrentValue
================================================================================
1 Stuff Toy 2011-12-25 100.00 10.00 100.00
2 Mouse 2011-12-23 250.00 50.00 200.00
3 Keyboard 2011-12-17 250.00 30.00 190.00
4 Umbrella 2011-12-28 150.00 20.00 110.00
5 Aircon 2011-12-29 950.00 25.00 925.00
DepreciationTransaction
ItemID DateOfDep MonthlyDep
======================================
2 2012-01-31 250.00
3 2012-01-31 30.00
4 2012-01-31 20.00
5 2012-01-31 25.00
3 2012-02-29 30.00
4 2012-02-29 20.00
I need your suggestions to help me solve this problem. Basically I am creating a depreciation monitoring system of a certain LGU. The problem of the current database is that it lacks some records for a specific date of depreciation, for instance:
Lacking Records (this is not a table from the database)
ItemID LackingDate
============================
1 2012-01-31
1 2012-02-29
2 2012-02-29
5 2012-02-29
And because of the lacking records, I cannot generate the depreciation report for the month of MARCH. Any idea how can I insert missing records on the DepreciationTransaction?
What have I done so far? None. But a simple query that calculates the newly depreciated value (which produces incorrect value because of the missing records)
The problem here is that you will have to generate data. MySQL is not intended to generate data, you should do that at an application level and just tell MySQL to store it. In this case, the application should check wether there are missing records and create them if needed.
Leaving that aside, you can (awfully) create dynamic data with MySQL like this:
select il.itemId, endOfMonths.aDate from ((
select aDate from (
select #maxDate - interval (a.a+(10*b.a)+(100*c.a)+(1000*d.a)) day aDate 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) a, /*10 day range*/
(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) b, /*100 day range*/
(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) c, /*1000 day range*/
(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) d, /*10000 day range*/
(select #minDate := (select min(dateAcquired) from il),
#maxDate := '2012-03-01') e
) f
where aDate between #minDate and #maxDate and aDate = last_day(aDate)
) endOfMonths, il)
left join dt
on il.itemId = dt.itemId and endOfMonths.aDate = dt.dateOfDep
where dt.itemId is null and last_day(il.dateAcquired) < endOfMonths.aDate
Depending on the length of the date range you can reduce the amount of dynamically generated results (10000 days means over 27 years of records each representing one day) by removing tables (d, c, b and a) and removing them from the upper formula. Setting the #minDate and #maxDate variables will allow you to specify the dates between you want to filter the results. This dates should be the min date from which you have an item and the max date should be march, in your case.
In plain english: If select min(dateAcquired) from il returns a date before '2012-03-01' - 10000 days then you'll have to add another union.
Finally, just add the insert statement (if you really need to insert those records).
You may build a temporary table, which contains the date needed. And use the table to LEFT OUTER JOIN the "DepreciationTransaction" table.
SELECT dt.date_value, dt.itemid, ISNULL(SUM(dt.MonthlyDep), 0)
FROM tmp_date
LEFT OUTER JOIN
DepreciationTransaction AS dt
ON tmp_date.date_value = dt.DateOfDep
GROUP BY dt.date_value, dt.itemid
Of course, if your want that all of the items to be on report, you should make a cartesian product with tmp_date and items_id.