SQL COUNT with conditions - mysql

I have this table that lists which months each product is available on the market. For example product 1 is available from Mar to Dec and product 2 is available from Jan to Feb.
product_id
start_month
end_month
1
3
12
2
1
2
3
4
6
4
4
8
5
5
5
6
10
11
I need to count how many product_ids each month of the year has but can't think of how to put: WHERE month >= start_month AND month >= end_month. Can I use a loop for this or would that be overkill>

I used dbFiddle to test out this solution.
It's dependent on there being at least 1 product available for sale in each month. Although, maybe it's better that a month isn't returned when there isn't a product for sale?
Could use #derviş-kayımbaşıoğlu approach to generating the months, but not group on product_id, but on month instead.
with months as (
Select distinct start_month [month]
from Product
)
Select m.month
,count(*) [products]
from months m
left join Product p
on m.month >= p.start_month and m.month <= p.end_month
group by m.month

something like this needs to help but you may have syntax error since we don't know exact DBMS and version
select product_id, count(*) cnts
from table1
inner join (
select 1 month 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
) t2
on t2.month between table1.start_month and table1.end_month
group by product_id

Related

Counting reservations for each day, where res could span multiple days and should count for each day

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

non available row in result group by

I used the following query
select month(SubmittedDate), count(policyid) from tblpolicy p join tlkppolicystatus s on p.StatusID=s.StatusID where SubmittedDate between
'2017-01-01' and sysdate() and s.StatusID=1 group by month(SubmittedDate);
This returns the following output which is correct as month number 3 and 4 don't have any data.
Month Total
-----|-----
1 | 62
2 | 34
5 | 1
But I want the output to be like
Month Total
-----|-----
1 | 62
2 | 34
3 | 0
4 | 0
5 | 1
So that means if any month do have any data then also it will show with a value 0
Thanks
If you have data for all months, but none of the data has a status of 1, then the simplest method is probably to use conditional aggregation:
select month(SubmittedDate), sum(s.StatusID = 1)
from tblpolicy p join
tlkppolicystatus s
on p.StatusID=s.StatusID
where SubmittedDate between '2017-01-01' and sysdate()
group by month(SubmittedDate);
Of course, if those conditions don't hold, then the left join with a derived table is the best solution.
Try this
select coalesce(t1.month,t2.month) as month, coalesce(ct1.count,0) as count from
(
select month(SubmittedDate) as month, count(policyid) as count
from tblpolicy p join tlkppolicystatus s on p.StatusID=s.StatusID
where SubmittedDate between
'2017-01-01' and sysdate() and s.StatusID=1 group by month(SubmittedDate)
) as t1 right join
(
select 1 as month union all
select 2 as month union all
select 3 as month union all
select 4 as month union all
select 5 as month union all
select 6 as month union all
select 7 as month union all
select 8 as month union all
select 9 as month union all
select 10 as month union all
select 11 as month union all
select 12 as month
) as t2 on t1.month <= t2.month;

mysql select date day by day

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.

Group by month or 0 if not results?

I have the following query
SELECT MONTH(date_added), COUNT(*)
FROM invite
WHERE YEAR(date_added) = 2013
GROUP BY MONTH(date_added)
And it works perfectly fine, but my problem is if there are no results for a month it doesn't output the month, I need it to say 0 instead.
I don't want to create a table with all 12 month values. And I don't want to run 12 queries, is there another way to do this?
You don't have to "create a table with 12 month values". You can just do it in the query:
SELECT m.mon, COUNT(i.date_added)
FROM (select 1 as mon 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
) m left outer join
invite i
on m.mon = i.month(date_added) and year(date_added) = 2013
GROUP BY m.mon;
Here's a cheesy way to do it:
create table months (int monthnum);
Insert the numbers 1 through 12 into months, so it's just a table with 1 column and 12 rows.
select monthnum, coalesce(ct, 0) from months left join (
select month(date_added) Mon, count(*) ct from invite
where year(date_added)=2013 group by Mon)
on monthnum = Mon
Coalesce gives you a zero instead of a null if the month is missing.

Put empty spaces in an SQL select

I'm having difficulty creating a month->count select query in SQL.
Basically, I have a list of entries, all of which have a date associated with them. What I want the end result to be, is a list containing 12 rows (one for each month), and each row would contain the month number (1 for January, 2 for February, etc), and a count of how many entries had that month set as it's date. Something like this:
Month - Count
1 - 12
2 - 0
3 - 7
4 - 0
5 - 9
6 - 0
I can get an result containing months that have a count of higher than 0, but if the month contains no entries, the row isn't created. I get this result just by doing
SELECT Month(goalDate) as monthNumber, count(*) as monthCount
FROM goalsList
WHERE Year(goalDate) = 2012
GROUP BY Month(goalDate)
ORDER BY monthNumber
Thanks in advance for the help!
Try something like this,
SELECT a.monthNo, COUNT(b.goalDate)
FROM (
SELECT 1 monthNo UNION SELECT 2 monthNo
UNION
SELECT 3 monthNo UNION SELECT 4 monthNo
UNION
SELECT 5 monthNo UNION SELECT 6 monthNo
UNION
SELECT 7 monthNo UNION SELECT 8 monthNo
UNION
SELECT 9 monthNo UNION SELECT 10 monthNo
UNION
SELECT 11 monthNo UNION SELECT 12 monthNo
) a LEFT JOIN goalsList b
ON a.monthNo = CAST(month(b.goalDate) as SIGNED)
GROUP BY a.monthNo
ORDER BY a.monthNo;
The idea was to create a list of records for month number in a temporary table and joins it with the table against goalsList. (Assuming that the OP doesn't have a table for month numbers)
SQLFiddle Demo