Group by month or 0 if not results? - mysql

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.

Related

SQL COUNT with conditions

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

How to display the name of the employees along with the date they were absent?

I have the following table called employees:
num_employee
name
001
George
002
Mary
And the table called records:
num_employee
date
001
2021-12-01
002
2021-12-01
001
2021-12-02
002
2021-12-01
001
2021-12-03
002
2021-12-06
In the example above, both employees attended on December 1st and 2nd; Mary was absent on day 3th and George was absent on day 6th. Days 4th and 5th were weekends, so they are not considered absences since they are non-working days (working days are from Monday to Friday).
What I am looking for is to obtain a result in which the employee number, his name and the date on which he was absent are displayed:
num_employee
name
missing
001
George
2021-12-03
002
Mary
2021-12-06
For now, the only thing I have achieved is to display the absences of a single employee with the following query:
SELECT GROUP_CONCAT(date) as missing FROM
(SELECT ADDDATE('1970-01-01',t4.i*10000 + t3.i*1000 + t2.i*100 + t1.i*10 + t0.i) 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 date BETWEEN '2021-12-01' AND '2021-12-31' AND date NOT IN (SELECT DATE_FORMAT(date,'%Y-%m-%d') FROM records WHERE num_employee = 001 AND date BETWEEN '2021 -12-01' AND '2022-12-31') AND DAYOFWEEK(date) BETWEEN 2 AND 5
And with that, I generate a table with all the dates, I specify a date range and I specify the employee number (in this case employee 001) and I get the following result:
missing
2021-12-06
How can I adapt my query to stop requiring filtering by employee and display them all with their respective dates when they were absent?
I am working with MariaDB in phpMyAdmin.
If I did understand your question correctly, this will do the trick.
this will give you all the data joined.
select e.num_employee, e.name, r.date
from employees e
join records r on r.num_employee = e.num_employee
this will give you a distinct per employee with the last missing.
select e.num_employee, max(r.date) as last_missing
from employees e
join records r on r.num_employee = e.num_employee
group by employees.num_employee
SELECT e.num_employee, e.name, max(r.date) AS missing
FROM records AS r
JOIN employees AS e ON e.num_employee = r.num_employee
GROUP BY e.num_employee
ORDER BY e.num_employee ASC
Assuming you have a date table (which you should have), you can do the following:
select
d.date
, e.num_employee
, e.name
from employees e
cross join date d
where 1=1
and d.is_weekend = 0
and d.date >= '2022-01-01'
This will give you all possible combinations of names and dates. Filter it, cause most date tables will hold dates till 1900 or 1990. Most of the time you will want to do that per year. Yes, cross join is not the best, but with those few records it won't be a problem.
Now you want to filter this, cause you only want the days where people were missing, i.e. where no entry exists.
select
sub.num_employee
, sub.name
, sub.date as missing
from (
select
d.date
, e.num_employee
, e.name
from employees e
cross join date d
where 1=1
and d.is_weekend = 0
and d.date >= '2022-01-01'
) sub
where not exists (
select
1
from records r
where 1=1
and r.num_employee = sub.num_employee
and r.date = sub.date
)
Assuming the records table contains employee presences and not absences, and assuming there's always at least one employee present every working day, this should work:
select v.num_employee, v.name, v.date as missing
from (select * from (select distinct `date` from records) u cross join employees) v left join records
on v.`date` = records.`date`
and v.num_employee = records.num_employee
where records.`date` is null;
Fiddle
Basically, you get all the distinct dates where at least one employee was present (from the records table), cross join it with the employees table to get a match between every employee and every working day, then outer join the result with the records table to check for absences.

Mysql query result generate months(1-12) for each category and arrange in same column order by category

I want to generate months no for 1-12 for each category. There are two categories, DEBIT and CREDIT. So, after finish print 1-12 for DEBIT. Then, it should go to next category and print the same with CREDIT in category column.
SELECT mon, #c:=#c+1 as cat_no, category
FROM
(
SELECT #m:=#m+1 as mon FROM
( 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 UNION SELECT 10 UNION SELECT 11 UNION SELECT 12)a,
(SELECT #m:=0)c
)d,
(SELECT 'DEBIT' as category UNION SELECT 'CREDIT' as category)b,
(SELECT #c:=0)e
The fiddle here.
Result:
The result show the column category display two categories for each month before go to next. But, I expected to output all 1-12 before go to next month.
Expected:
Thank you.
SELECT mon.mon, cat.cat_no, cat.category
FROM ( SELECT 1 mon 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 ) AS mon
CROSS JOIN ( SELECT 1 cat_no, 'DEBIT' category UNION
SELECT 2, 'CREDIT' ) cat
ORDER BY cat_no, mon
https://dbfiddle.uk/?rdbms=mysql_5.6&fiddle=60416e7875ba7eb886e804c0bddbcadb

MySQL query - convert to left join to show results with zero?

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.

MySQL group by month and day for current year

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 !!!