Put empty spaces in an SQL select - mysql

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

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

MySQL include empty WEEKS data as 0

is there way to include empty week value from empty result ? or how i can unionn empty missing weeks
there is bit of my query
SELECT
o.user_id , WEEK(FROM_UNIXTIME(o.cdate, '%Y-%m-%d'),7) as week_number,
FROM
(_orders AS `o`)
WHERE
o.cdate BETWEEN '1505409460' AND '1540815218'
GROUP BY
week_number
Result
1
2
4
6
8
requested result
1
2
3
4
5
6
7
8
This is just an example, there are numerous ways to achieve this. The first step is to have, or generate, a set on integers. Having a table of these is very handy actually. Here I use 2 subqueries cross joined to generate 100 rows (with n = 0 to 99)
select
ns.n, sq.*
from (
select
d1.digit + (d10.digit*10) as n
from (
SELECT 0 AS digit 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
) d1
cross join (
SELECT 0 AS digit 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
) d10
) ns
left join (
your query goes here
) sq on ns.n = sq.week_number
where n between 1 and 52
order by n

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.

Filling empty records through SELECT..INSERT statement

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.